From ed55fa708d9314230568d2479d9c35ebf4facf4e Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 19 Jul 2024 11:25:10 +0800 Subject: [PATCH] Update to 7.7.0 Since version 7.7.0, we recommend yours update python to 3.12. [+] Using nginx technology to load static files improves access speed [+] Refactor homepage, website, FTP, and database using vue3 [+] Table loading changed to skeleton screen [+] Add Website statistics-v2 professional plug-in [+] Add Home page - top 5 resource occupancy [+] Add protection for Files management (requires Tamper-proof for Enterprise 3.7) [+] Website, FTP, Databases page add program status [+] Add FTP log analysis (only supports Centos) [+] Add password-free login to phpMyAdmin [+] Add Proxy Project in Website (Supported when web service uses Nginx) [+] Add WP Toolkit (Pro version only) [+] Redesigned Docker module [+] Add WP Toolkit Protection [+] Add WP Toolkit Backup and Restore [+] Add WP Toolkit Migrated [+] Add WP Toolkit Clone site (supports new domain and subdomain) [+] Add WP Toolkit Create site from backup of other panel [+] Add WP Toolkit support for Cron automatic backup (only save Local disk) [+] Add WP Toolkit operation log [+] Add Integrity check for WP Toolkit [+] Add WP Toolkit plug-in management and themes management [*] Optimize phpMyAdmin formula access method [*] Optimize Home page PHP display problem [*] Optimize jump to the login interface after the login expires [*] Optimize automatic renewal of SSL at some times [*] Optimize Let's Encrypt to increase application success rate [-] Fix Logs Audit cannot be opened [-] Fix apache URL rewrite issue [-] Fix phpmyadmin installation problem [-] Fix the problem that some servers cannot install software [-] Fix upload file error [-] Fix left menu hiding problem [-] Fix aaPanel Mobile QR code display problem [-] Fix problem that third-party plug-ins are not displayed in the App Store [-] Fix issue where the menu bar is blank when opening new tabs [-] Fixed panel not being accessible in some cases [-] Fix the issue where Curl warning caused the inability to apply for SSL [-] Fix Quota issues for Website, FTP, Databases [-] Fix file interface display problem on mobile terminal --- BT-Panel | 201 +- BT-Task | 6 +- BTPanel/__init__.py | 4660 ++- BTPanel/app.py | 671 + BTPanel/routes/flask_hook.py | 194 + BTPanel/routes/v1.py | 2311 ++ BTPanel/routes/v2.py | 2397 ++ BTPanel/static/amd/config.js | 2438 +- BTPanel/static/amd/utils.min.js | 6158 ++- .../bootstrap-3.3.5/css/bootstrap.min.css | 2 +- BTPanel/static/ckeditor/CHANGES.md | 1703 + BTPanel/static/ckeditor/LICENSE.md | 1420 + BTPanel/static/ckeditor/README.md | 39 + BTPanel/static/ckeditor/adapters/jquery.js | 10 + BTPanel/static/ckeditor/build-config.js | 190 + BTPanel/static/ckeditor/ckeditor.js | 1337 + BTPanel/static/ckeditor/config.js | 11 + BTPanel/static/ckeditor/contents.css | 208 + BTPanel/static/ckeditor/lang/af.js | 5 + BTPanel/static/ckeditor/lang/ar.js | 5 + BTPanel/static/ckeditor/lang/az.js | 5 + BTPanel/static/ckeditor/lang/bg.js | 5 + BTPanel/static/ckeditor/lang/bn.js | 5 + BTPanel/static/ckeditor/lang/bs.js | 5 + BTPanel/static/ckeditor/lang/ca.js | 5 + BTPanel/static/ckeditor/lang/cs.js | 5 + BTPanel/static/ckeditor/lang/cy.js | 5 + BTPanel/static/ckeditor/lang/da.js | 5 + BTPanel/static/ckeditor/lang/de-ch.js | 5 + BTPanel/static/ckeditor/lang/de.js | 5 + BTPanel/static/ckeditor/lang/el.js | 5 + BTPanel/static/ckeditor/lang/en-au.js | 5 + BTPanel/static/ckeditor/lang/en-ca.js | 5 + BTPanel/static/ckeditor/lang/en-gb.js | 5 + BTPanel/static/ckeditor/lang/en.js | 5 + BTPanel/static/ckeditor/lang/eo.js | 5 + BTPanel/static/ckeditor/lang/es-mx.js | 5 + BTPanel/static/ckeditor/lang/es.js | 5 + BTPanel/static/ckeditor/lang/et.js | 5 + BTPanel/static/ckeditor/lang/eu.js | 5 + BTPanel/static/ckeditor/lang/fa.js | 5 + BTPanel/static/ckeditor/lang/fi.js | 5 + BTPanel/static/ckeditor/lang/fo.js | 5 + BTPanel/static/ckeditor/lang/fr-ca.js | 5 + BTPanel/static/ckeditor/lang/fr.js | 5 + BTPanel/static/ckeditor/lang/gl.js | 5 + BTPanel/static/ckeditor/lang/gu.js | 5 + BTPanel/static/ckeditor/lang/he.js | 5 + BTPanel/static/ckeditor/lang/hi.js | 5 + BTPanel/static/ckeditor/lang/hr.js | 5 + BTPanel/static/ckeditor/lang/hu.js | 5 + BTPanel/static/ckeditor/lang/id.js | 5 + BTPanel/static/ckeditor/lang/is.js | 5 + BTPanel/static/ckeditor/lang/it.js | 5 + BTPanel/static/ckeditor/lang/ja.js | 5 + BTPanel/static/ckeditor/lang/ka.js | 5 + BTPanel/static/ckeditor/lang/km.js | 5 + BTPanel/static/ckeditor/lang/ko.js | 5 + BTPanel/static/ckeditor/lang/ku.js | 5 + BTPanel/static/ckeditor/lang/lt.js | 5 + BTPanel/static/ckeditor/lang/lv.js | 5 + BTPanel/static/ckeditor/lang/mk.js | 5 + BTPanel/static/ckeditor/lang/mn.js | 5 + BTPanel/static/ckeditor/lang/ms.js | 5 + BTPanel/static/ckeditor/lang/nb.js | 5 + BTPanel/static/ckeditor/lang/nl.js | 5 + BTPanel/static/ckeditor/lang/no.js | 5 + BTPanel/static/ckeditor/lang/oc.js | 5 + BTPanel/static/ckeditor/lang/pl.js | 5 + BTPanel/static/ckeditor/lang/pt-br.js | 5 + BTPanel/static/ckeditor/lang/pt.js | 5 + BTPanel/static/ckeditor/lang/ro.js | 5 + BTPanel/static/ckeditor/lang/ru.js | 5 + BTPanel/static/ckeditor/lang/si.js | 5 + BTPanel/static/ckeditor/lang/sk.js | 5 + BTPanel/static/ckeditor/lang/sl.js | 5 + BTPanel/static/ckeditor/lang/sq.js | 5 + BTPanel/static/ckeditor/lang/sr-latn.js | 5 + BTPanel/static/ckeditor/lang/sr.js | 5 + BTPanel/static/ckeditor/lang/sv.js | 5 + BTPanel/static/ckeditor/lang/th.js | 5 + BTPanel/static/ckeditor/lang/tr.js | 5 + BTPanel/static/ckeditor/lang/tt.js | 5 + BTPanel/static/ckeditor/lang/ug.js | 5 + BTPanel/static/ckeditor/lang/uk.js | 5 + BTPanel/static/ckeditor/lang/vi.js | 5 + BTPanel/static/ckeditor/lang/zh-cn.js | 5 + BTPanel/static/ckeditor/lang/zh.js | 5 + .../plugins/a11yhelp/dialogs/a11yhelp.js | 10 + .../dialogs/lang/_translationstatus.txt | 25 + .../plugins/a11yhelp/dialogs/lang/af.js | 11 + .../plugins/a11yhelp/dialogs/lang/ar.js | 11 + .../plugins/a11yhelp/dialogs/lang/az.js | 11 + .../plugins/a11yhelp/dialogs/lang/bg.js | 11 + .../plugins/a11yhelp/dialogs/lang/ca.js | 13 + .../plugins/a11yhelp/dialogs/lang/cs.js | 12 + .../plugins/a11yhelp/dialogs/lang/cy.js | 11 + .../plugins/a11yhelp/dialogs/lang/da.js | 11 + .../plugins/a11yhelp/dialogs/lang/de-ch.js | 12 + .../plugins/a11yhelp/dialogs/lang/de.js | 13 + .../plugins/a11yhelp/dialogs/lang/el.js | 13 + .../plugins/a11yhelp/dialogs/lang/en-au.js | 11 + .../plugins/a11yhelp/dialogs/lang/en-gb.js | 11 + .../plugins/a11yhelp/dialogs/lang/en.js | 11 + .../plugins/a11yhelp/dialogs/lang/eo.js | 13 + .../plugins/a11yhelp/dialogs/lang/es-mx.js | 13 + .../plugins/a11yhelp/dialogs/lang/es.js | 13 + .../plugins/a11yhelp/dialogs/lang/et.js | 11 + .../plugins/a11yhelp/dialogs/lang/eu.js | 12 + .../plugins/a11yhelp/dialogs/lang/fa.js | 11 + .../plugins/a11yhelp/dialogs/lang/fi.js | 11 + .../plugins/a11yhelp/dialogs/lang/fo.js | 11 + .../plugins/a11yhelp/dialogs/lang/fr-ca.js | 11 + .../plugins/a11yhelp/dialogs/lang/fr.js | 13 + .../plugins/a11yhelp/dialogs/lang/gl.js | 12 + .../plugins/a11yhelp/dialogs/lang/gu.js | 11 + .../plugins/a11yhelp/dialogs/lang/he.js | 11 + .../plugins/a11yhelp/dialogs/lang/hi.js | 11 + .../plugins/a11yhelp/dialogs/lang/hr.js | 11 + .../plugins/a11yhelp/dialogs/lang/hu.js | 12 + .../plugins/a11yhelp/dialogs/lang/id.js | 11 + .../plugins/a11yhelp/dialogs/lang/it.js | 13 + .../plugins/a11yhelp/dialogs/lang/ja.js | 9 + .../plugins/a11yhelp/dialogs/lang/km.js | 11 + .../plugins/a11yhelp/dialogs/lang/ko.js | 10 + .../plugins/a11yhelp/dialogs/lang/ku.js | 11 + .../plugins/a11yhelp/dialogs/lang/lt.js | 11 + .../plugins/a11yhelp/dialogs/lang/lv.js | 12 + .../plugins/a11yhelp/dialogs/lang/mk.js | 11 + .../plugins/a11yhelp/dialogs/lang/mn.js | 11 + .../plugins/a11yhelp/dialogs/lang/nb.js | 12 + .../plugins/a11yhelp/dialogs/lang/nl.js | 12 + .../plugins/a11yhelp/dialogs/lang/no.js | 11 + .../plugins/a11yhelp/dialogs/lang/oc.js | 12 + .../plugins/a11yhelp/dialogs/lang/pl.js | 13 + .../plugins/a11yhelp/dialogs/lang/pt-br.js | 13 + .../plugins/a11yhelp/dialogs/lang/pt.js | 12 + .../plugins/a11yhelp/dialogs/lang/ro.js | 12 + .../plugins/a11yhelp/dialogs/lang/ru.js | 11 + .../plugins/a11yhelp/dialogs/lang/si.js | 10 + .../plugins/a11yhelp/dialogs/lang/sk.js | 11 + .../plugins/a11yhelp/dialogs/lang/sl.js | 11 + .../plugins/a11yhelp/dialogs/lang/sq.js | 12 + .../plugins/a11yhelp/dialogs/lang/sr-latn.js | 12 + .../plugins/a11yhelp/dialogs/lang/sr.js | 12 + .../plugins/a11yhelp/dialogs/lang/sv.js | 11 + .../plugins/a11yhelp/dialogs/lang/th.js | 11 + .../plugins/a11yhelp/dialogs/lang/tr.js | 12 + .../plugins/a11yhelp/dialogs/lang/tt.js | 11 + .../plugins/a11yhelp/dialogs/lang/ug.js | 12 + .../plugins/a11yhelp/dialogs/lang/uk.js | 12 + .../plugins/a11yhelp/dialogs/lang/vi.js | 11 + .../plugins/a11yhelp/dialogs/lang/zh-cn.js | 9 + .../plugins/a11yhelp/dialogs/lang/zh.js | 9 + .../ckeditor/plugins/about/dialogs/about.js | 8 + .../about/dialogs/hidpi/logo_ckeditor.png | Bin 0 -> 12236 bytes .../plugins/about/dialogs/logo_ckeditor.png | Bin 0 -> 5650 bytes .../plugins/clipboard/dialogs/paste.js | 11 + .../colordialog/dialogs/colordialog.css | 20 + .../colordialog/dialogs/colordialog.js | 14 + .../cursors/cursor-disabled.svg | 25 + .../plugins/copyformatting/cursors/cursor.svg | 14 + .../copyformatting/styles/copyformatting.css | 45 + .../plugins/dialog/dialogDefinition.js | 4 + .../ckeditor/plugins/div/dialogs/div.js | 9 + .../ckeditor/plugins/find/dialogs/find.js | 25 + .../ckeditor/plugins/flash/dialogs/flash.js | 24 + .../plugins/flash/images/placeholder.png | Bin 0 -> 256 bytes .../ckeditor/plugins/forms/dialogs/button.js | 8 + .../plugins/forms/dialogs/checkbox.js | 9 + .../ckeditor/plugins/forms/dialogs/form.js | 8 + .../plugins/forms/dialogs/hiddenfield.js | 7 + .../ckeditor/plugins/forms/dialogs/radio.js | 9 + .../ckeditor/plugins/forms/dialogs/select.js | 21 + .../plugins/forms/dialogs/textarea.js | 8 + .../plugins/forms/dialogs/textfield.js | 11 + .../plugins/forms/images/hiddenfield.gif | Bin 0 -> 178 bytes BTPanel/static/ckeditor/plugins/icons.png | Bin 0 -> 12421 bytes .../static/ckeditor/plugins/icons_hidpi.png | Bin 0 -> 40265 bytes .../ckeditor/plugins/iframe/dialogs/iframe.js | 10 + .../plugins/iframe/images/placeholder.png | Bin 0 -> 265 bytes .../ckeditor/plugins/image/dialogs/image.js | 44 + .../ckeditor/plugins/image/images/noimage.png | Bin 0 -> 1610 bytes .../ckeditor/plugins/link/dialogs/anchor.js | 8 + .../ckeditor/plugins/link/dialogs/link.js | 30 + .../ckeditor/plugins/link/images/anchor.png | Bin 0 -> 752 bytes .../plugins/link/images/hidpi/anchor.png | Bin 0 -> 1109 bytes .../plugins/liststyle/dialogs/liststyle.js | 10 + .../magicline/images/hidpi/icon-rtl.png | Bin 0 -> 176 bytes .../plugins/magicline/images/hidpi/icon.png | Bin 0 -> 199 bytes .../plugins/magicline/images/icon-rtl.png | Bin 0 -> 138 bytes .../plugins/magicline/images/icon.png | Bin 0 -> 133 bytes .../plugins/pagebreak/images/pagebreak.gif | Bin 0 -> 99 bytes .../plugins/pastefromword/filter/default.js | 55 + .../ckeditor/plugins/preview/preview.html | 13 + .../ckeditor/plugins/scayt/CHANGELOG.md | 20 + .../static/ckeditor/plugins/scayt/LICENSE.md | 28 + .../static/ckeditor/plugins/scayt/README.md | 81 + .../ckeditor/plugins/scayt/dialogs/dialog.css | 23 + .../ckeditor/plugins/scayt/dialogs/options.js | 32 + .../plugins/scayt/dialogs/toolbar.css | 71 + .../plugins/scayt/skins/moono-lisa/scayt.css | 25 + .../showblocks/images/block_address.png | Bin 0 -> 152 bytes .../showblocks/images/block_blockquote.png | Bin 0 -> 154 bytes .../plugins/showblocks/images/block_div.png | Bin 0 -> 127 bytes .../plugins/showblocks/images/block_h1.png | Bin 0 -> 120 bytes .../plugins/showblocks/images/block_h2.png | Bin 0 -> 127 bytes .../plugins/showblocks/images/block_h3.png | Bin 0 -> 123 bytes .../plugins/showblocks/images/block_h4.png | Bin 0 -> 123 bytes .../plugins/showblocks/images/block_h5.png | Bin 0 -> 126 bytes .../plugins/showblocks/images/block_h6.png | Bin 0 -> 123 bytes .../plugins/showblocks/images/block_p.png | Bin 0 -> 115 bytes .../plugins/showblocks/images/block_pre.png | Bin 0 -> 128 bytes .../ckeditor/plugins/smiley/dialogs/smiley.js | 11 + .../plugins/smiley/images/angel_smile.gif | Bin 0 -> 1245 bytes .../plugins/smiley/images/angel_smile.png | Bin 0 -> 1172 bytes .../plugins/smiley/images/angry_smile.gif | Bin 0 -> 1219 bytes .../plugins/smiley/images/angry_smile.png | Bin 0 -> 1220 bytes .../plugins/smiley/images/broken_heart.gif | Bin 0 -> 732 bytes .../plugins/smiley/images/broken_heart.png | Bin 0 -> 1139 bytes .../plugins/smiley/images/confused_smile.gif | Bin 0 -> 1202 bytes .../plugins/smiley/images/confused_smile.png | Bin 0 -> 1101 bytes .../plugins/smiley/images/cry_smile.gif | Bin 0 -> 795 bytes .../plugins/smiley/images/cry_smile.png | Bin 0 -> 1214 bytes .../plugins/smiley/images/devil_smile.gif | Bin 0 -> 1239 bytes .../plugins/smiley/images/devil_smile.png | Bin 0 -> 1220 bytes .../smiley/images/embaressed_smile.gif | Bin 0 -> 786 bytes .../smiley/images/embarrassed_smile.gif | Bin 0 -> 786 bytes .../smiley/images/embarrassed_smile.png | Bin 0 -> 1145 bytes .../plugins/smiley/images/envelope.gif | Bin 0 -> 506 bytes .../plugins/smiley/images/envelope.png | Bin 0 -> 760 bytes .../ckeditor/plugins/smiley/images/heart.gif | Bin 0 -> 692 bytes .../ckeditor/plugins/smiley/images/heart.png | Bin 0 -> 999 bytes .../ckeditor/plugins/smiley/images/kiss.gif | Bin 0 -> 683 bytes .../ckeditor/plugins/smiley/images/kiss.png | Bin 0 -> 1003 bytes .../plugins/smiley/images/lightbulb.gif | Bin 0 -> 660 bytes .../plugins/smiley/images/lightbulb.png | Bin 0 -> 919 bytes .../plugins/smiley/images/omg_smile.gif | Bin 0 -> 820 bytes .../plugins/smiley/images/omg_smile.png | Bin 0 -> 1122 bytes .../plugins/smiley/images/regular_smile.gif | Bin 0 -> 1209 bytes .../plugins/smiley/images/regular_smile.png | Bin 0 -> 1084 bytes .../plugins/smiley/images/sad_smile.gif | Bin 0 -> 782 bytes .../plugins/smiley/images/sad_smile.png | Bin 0 -> 1115 bytes .../plugins/smiley/images/shades_smile.gif | Bin 0 -> 1231 bytes .../plugins/smiley/images/shades_smile.png | Bin 0 -> 1204 bytes .../plugins/smiley/images/teeth_smile.gif | Bin 0 -> 1201 bytes .../plugins/smiley/images/teeth_smile.png | Bin 0 -> 1183 bytes .../plugins/smiley/images/thumbs_down.gif | Bin 0 -> 715 bytes .../plugins/smiley/images/thumbs_down.png | Bin 0 -> 985 bytes .../plugins/smiley/images/thumbs_up.gif | Bin 0 -> 714 bytes .../plugins/smiley/images/thumbs_up.png | Bin 0 -> 959 bytes .../plugins/smiley/images/tongue_smile.gif | Bin 0 -> 1210 bytes .../plugins/smiley/images/tongue_smile.png | Bin 0 -> 1132 bytes .../plugins/smiley/images/tounge_smile.gif | Bin 0 -> 1210 bytes .../images/whatchutalkingabout_smile.gif | Bin 0 -> 775 bytes .../images/whatchutalkingabout_smile.png | Bin 0 -> 1039 bytes .../plugins/smiley/images/wink_smile.gif | Bin 0 -> 1202 bytes .../plugins/smiley/images/wink_smile.png | Bin 0 -> 1114 bytes .../dialogs/lang/_translationstatus.txt | 20 + .../plugins/specialchar/dialogs/lang/af.js | 13 + .../plugins/specialchar/dialogs/lang/ar.js | 13 + .../plugins/specialchar/dialogs/lang/az.js | 10 + .../plugins/specialchar/dialogs/lang/bg.js | 13 + .../plugins/specialchar/dialogs/lang/ca.js | 14 + .../plugins/specialchar/dialogs/lang/cs.js | 13 + .../plugins/specialchar/dialogs/lang/cy.js | 14 + .../plugins/specialchar/dialogs/lang/da.js | 11 + .../plugins/specialchar/dialogs/lang/de-ch.js | 13 + .../plugins/specialchar/dialogs/lang/de.js | 13 + .../plugins/specialchar/dialogs/lang/el.js | 13 + .../plugins/specialchar/dialogs/lang/en-au.js | 13 + .../plugins/specialchar/dialogs/lang/en-ca.js | 13 + .../plugins/specialchar/dialogs/lang/en-gb.js | 13 + .../plugins/specialchar/dialogs/lang/en.js | 13 + .../plugins/specialchar/dialogs/lang/eo.js | 12 + .../plugins/specialchar/dialogs/lang/es-mx.js | 13 + .../plugins/specialchar/dialogs/lang/es.js | 13 + .../plugins/specialchar/dialogs/lang/et.js | 13 + .../plugins/specialchar/dialogs/lang/eu.js | 13 + .../plugins/specialchar/dialogs/lang/fa.js | 12 + .../plugins/specialchar/dialogs/lang/fi.js | 13 + .../plugins/specialchar/dialogs/lang/fr-ca.js | 10 + .../plugins/specialchar/dialogs/lang/fr.js | 12 + .../plugins/specialchar/dialogs/lang/gl.js | 13 + .../plugins/specialchar/dialogs/lang/he.js | 12 + .../plugins/specialchar/dialogs/lang/hr.js | 13 + .../plugins/specialchar/dialogs/lang/hu.js | 12 + .../plugins/specialchar/dialogs/lang/id.js | 13 + .../plugins/specialchar/dialogs/lang/it.js | 14 + .../plugins/specialchar/dialogs/lang/ja.js | 9 + .../plugins/specialchar/dialogs/lang/km.js | 13 + .../plugins/specialchar/dialogs/lang/ko.js | 10 + .../plugins/specialchar/dialogs/lang/ku.js | 13 + .../plugins/specialchar/dialogs/lang/lt.js | 13 + .../plugins/specialchar/dialogs/lang/lv.js | 13 + .../plugins/specialchar/dialogs/lang/nb.js | 11 + .../plugins/specialchar/dialogs/lang/nl.js | 13 + .../plugins/specialchar/dialogs/lang/no.js | 11 + .../plugins/specialchar/dialogs/lang/oc.js | 12 + .../plugins/specialchar/dialogs/lang/pl.js | 12 + .../plugins/specialchar/dialogs/lang/pt-br.js | 11 + .../plugins/specialchar/dialogs/lang/pt.js | 13 + .../plugins/specialchar/dialogs/lang/ro.js | 13 + .../plugins/specialchar/dialogs/lang/ru.js | 13 + .../plugins/specialchar/dialogs/lang/si.js | 13 + .../plugins/specialchar/dialogs/lang/sk.js | 13 + .../plugins/specialchar/dialogs/lang/sl.js | 12 + .../plugins/specialchar/dialogs/lang/sq.js | 13 + .../specialchar/dialogs/lang/sr-latn.js | 13 + .../plugins/specialchar/dialogs/lang/sr.js | 13 + .../plugins/specialchar/dialogs/lang/sv.js | 11 + .../plugins/specialchar/dialogs/lang/th.js | 13 + .../plugins/specialchar/dialogs/lang/tr.js | 12 + .../plugins/specialchar/dialogs/lang/tt.js | 13 + .../plugins/specialchar/dialogs/lang/ug.js | 13 + .../plugins/specialchar/dialogs/lang/uk.js | 12 + .../plugins/specialchar/dialogs/lang/vi.js | 14 + .../plugins/specialchar/dialogs/lang/zh-cn.js | 9 + .../plugins/specialchar/dialogs/lang/zh.js | 9 + .../specialchar/dialogs/specialchar.js | 14 + .../ckeditor/plugins/table/dialogs/table.js | 21 + .../tableselection/styles/tableselection.css | 36 + .../plugins/tabletools/dialogs/tableCell.js | 19 + .../plugins/templates/dialogs/templates.css | 84 + .../plugins/templates/dialogs/templates.js | 10 + .../plugins/templates/templates/default.js | 7 + .../templates/templates/images/template1.gif | Bin 0 -> 539 bytes .../templates/templates/images/template2.gif | Bin 0 -> 497 bytes .../templates/templates/images/template3.gif | Bin 0 -> 557 bytes .../ckeditor/plugins/widget/images/handle.png | Bin 0 -> 220 bytes .../static/ckeditor/plugins/wsc/LICENSE.md | 28 + BTPanel/static/ckeditor/plugins/wsc/README.md | 80 + .../ckeditor/plugins/wsc/dialogs/ciframe.html | 66 + .../plugins/wsc/dialogs/tmpFrameset.html | 52 + .../ckeditor/plugins/wsc/dialogs/wsc.css | 82 + .../ckeditor/plugins/wsc/dialogs/wsc.js | 91 + .../ckeditor/plugins/wsc/dialogs/wsc_ie.js | 11 + .../plugins/wsc/skins/moono-lisa/wsc.css | 43 + .../static/ckeditor/samples/css/samples.css | 1637 + .../ckeditor/samples/img/github-top.png | Bin 0 -> 383 bytes .../static/ckeditor/samples/img/header-bg.png | Bin 0 -> 13086 bytes .../ckeditor/samples/img/header-separator.png | Bin 0 -> 123 bytes BTPanel/static/ckeditor/samples/img/logo.png | Bin 0 -> 5634 bytes BTPanel/static/ckeditor/samples/img/logo.svg | 13 + .../ckeditor/samples/img/navigation-tip.png | Bin 0 -> 12029 bytes BTPanel/static/ckeditor/samples/index.html | 128 + BTPanel/static/ckeditor/samples/js/sample.js | 53 + BTPanel/static/ckeditor/samples/js/sf.js | 17 + BTPanel/static/ckeditor/samples/old/ajax.html | 85 + BTPanel/static/ckeditor/samples/old/api.html | 210 + .../static/ckeditor/samples/old/appendto.html | 59 + .../samples/old/assets/inlineall/logo.png | Bin 0 -> 4283 bytes .../old/assets/outputxhtml/outputxhtml.css | 204 + .../samples/old/assets/posteddata.php | 59 + .../ckeditor/samples/old/assets/sample.jpg | Bin 0 -> 14449 bytes .../old/assets/uilanguages/languages.js | 7 + .../ckeditor/samples/old/datafiltering.html | 508 + .../samples/old/dialog/assets/my_dialog.js | 48 + .../ckeditor/samples/old/dialog/dialog.html | 190 + .../ckeditor/samples/old/divreplace.html | 144 + .../samples/old/enterkey/enterkey.html | 106 + .../assets/outputforflash/outputforflash.fla | Bin 0 -> 85504 bytes .../assets/outputforflash/outputforflash.swf | Bin 0 -> 15571 bytes .../assets/outputforflash/swfobject.js | 19 + .../old/htmlwriter/outputforflash.html | 283 + .../samples/old/htmlwriter/outputhtml.html | 224 + .../static/ckeditor/samples/old/index.html | 131 + .../ckeditor/samples/old/inlineall.html | 314 + .../ckeditor/samples/old/inlinebycode.html | 124 + .../ckeditor/samples/old/inlinetextarea.html | 113 + .../static/ckeditor/samples/old/jquery.html | 103 + .../samples/old/magicline/magicline.html | 209 + .../static/ckeditor/samples/old/readonly.html | 76 + .../ckeditor/samples/old/replacebyclass.html | 60 + .../ckeditor/samples/old/replacebycode.html | 59 + .../static/ckeditor/samples/old/sample.css | 357 + BTPanel/static/ckeditor/samples/old/sample.js | 50 + .../samples/old/sample_posteddata.php | 16 + .../static/ckeditor/samples/old/tabindex.html | 78 + .../ckeditor/samples/old/toolbar/toolbar.html | 235 + .../static/ckeditor/samples/old/uicolor.html | 72 + .../ckeditor/samples/old/uilanguages.html | 122 + .../samples/old/wysiwygarea/fullpage.html | 80 + .../ckeditor/samples/old/xhtmlstyle.html | 234 + .../toolbarconfigurator/css/fontello.css | 55 + .../toolbarconfigurator/font/LICENSE.txt | 10 + .../toolbarconfigurator/font/config.json | 28 + .../toolbarconfigurator/font/fontello.eot | Bin 0 -> 4988 bytes .../toolbarconfigurator/font/fontello.svg | 14 + .../toolbarconfigurator/font/fontello.ttf | Bin 0 -> 4820 bytes .../toolbarconfigurator/font/fontello.woff | Bin 0 -> 2904 bytes .../samples/toolbarconfigurator/index.html | 446 + .../js/abstracttoolbarmodifier.js | 13 + .../js/fulltoolbareditor.js | 9 + .../toolbarconfigurator/js/toolbarmodifier.js | 33 + .../js/toolbartextmodifier.js | 14 + .../lib/codemirror/LICENSE | 19 + .../lib/codemirror/codemirror.css | 325 + .../lib/codemirror/codemirror.js | 288 + .../lib/codemirror/javascript.js | 25 + .../lib/codemirror/neo.css | 36 + .../lib/codemirror/show-hint.css | 38 + .../lib/codemirror/show-hint.js | 16 + .../ckeditor/skins/moono-lisa/dialog.css | 5 + .../ckeditor/skins/moono-lisa/dialog_ie.css | 5 + .../ckeditor/skins/moono-lisa/dialog_ie8.css | 5 + .../skins/moono-lisa/dialog_iequirks.css | 5 + .../ckeditor/skins/moono-lisa/editor.css | 5 + .../skins/moono-lisa/editor_gecko.css | 5 + .../ckeditor/skins/moono-lisa/editor_ie.css | 5 + .../ckeditor/skins/moono-lisa/editor_ie8.css | 5 + .../skins/moono-lisa/editor_iequirks.css | 5 + .../ckeditor/skins/moono-lisa/icons.png | Bin 0 -> 12421 bytes .../ckeditor/skins/moono-lisa/icons_hidpi.png | Bin 0 -> 40265 bytes .../skins/moono-lisa/images/arrow.png | Bin 0 -> 191 bytes .../skins/moono-lisa/images/close.png | Bin 0 -> 615 bytes .../skins/moono-lisa/images/hidpi/close.png | Bin 0 -> 1238 bytes .../moono-lisa/images/hidpi/lock-open.png | Bin 0 -> 1071 bytes .../skins/moono-lisa/images/hidpi/lock.png | Bin 0 -> 1062 bytes .../skins/moono-lisa/images/hidpi/refresh.png | Bin 0 -> 1623 bytes .../skins/moono-lisa/images/lock-open.png | Bin 0 -> 511 bytes .../ckeditor/skins/moono-lisa/images/lock.png | Bin 0 -> 506 bytes .../skins/moono-lisa/images/refresh.png | Bin 0 -> 757 bytes .../skins/moono-lisa/images/spinner.gif | Bin 0 -> 2984 bytes .../ckeditor/skins/moono-lisa/readme.md | 46 + BTPanel/static/ckeditor/styles.js | 137 + BTPanel/static/css/site.css | 4 +- BTPanel/static/error_page/502.json | 4 + BTPanel/static/img/file_icon/Thumbs.db | Bin 0 -> 5120 bytes .../file_icon/icon-new-file-lock-active.svg | 7 + .../img/file_icon/icon-new-file-lock.svg | 7 + .../file_icon/icon-new-file-unlock-active.svg | 8 + .../img/file_icon/icon-new-file-unlock.svg | 8 + BTPanel/static/img/soft_ico/ico-frp.png | Bin 0 -> 767 bytes BTPanel/static/img/soft_ico/ico-monitor.png | Bin 0 -> 1490 bytes BTPanel/static/js/firewall.js | 2 +- BTPanel/static/js/index.js | 2 +- BTPanel/static/js/jquery.i18n.properties.js | 498 + BTPanel/static/js/logs.js | 8 +- BTPanel/static/js/public_backup.js | 16807 ++++---- BTPanel/static/js/site.js | 5 +- BTPanel/static/js/soft.js | 55 +- BTPanel/static/js/tools.js | 5787 ++- BTPanel/static/language/English/lan.js | 6 +- BTPanel/static/language/English/public.json | 2 +- .../static/language/Simplified_Chinese/lan.js | 2 +- .../language/Simplified_Chinese/public.json | 2 +- .../language/en/LC_MESSAGES/en_monitor.po | 201 + BTPanel/static/language/old_en/en.mo | Bin 0 -> 162283 bytes BTPanel/static/language/old_en/en.po | 5095 +++ BTPanel/static/oldcss/files_style.css | 2975 ++ BTPanel/static/oldcss/site.css | 14603 +++++++ BTPanel/static/vite/css/details.css | 1 + BTPanel/static/vite/css/index.css | 1 + BTPanel/static/vite/css/index10.css | 1 + BTPanel/static/vite/css/index11.css | 1 + BTPanel/static/vite/css/index12.css | 1 + BTPanel/static/vite/css/index13.css | 1 + BTPanel/static/vite/css/index14.css | 1 + BTPanel/static/vite/css/index15.css | 1 + BTPanel/static/vite/css/index16.css | 1 + BTPanel/static/vite/css/index17.css | 1 + BTPanel/static/vite/css/index18.css | 1 + BTPanel/static/vite/css/index19.css | 1 + BTPanel/static/vite/css/index2.css | 1 + BTPanel/static/vite/css/index20.css | 1 + BTPanel/static/vite/css/index21.css | 32 + BTPanel/static/vite/css/index22.css | 1 + BTPanel/static/vite/css/index23.css | 1 + BTPanel/static/vite/css/index24.css | 1 + BTPanel/static/vite/css/index25.css | 1 + BTPanel/static/vite/css/index26.css | 1 + BTPanel/static/vite/css/index27.css | 1 + BTPanel/static/vite/css/index28.css | 1 + BTPanel/static/vite/css/index29.css | 1 + BTPanel/static/vite/css/index3.css | 1 + BTPanel/static/vite/css/index30.css | 1 + BTPanel/static/vite/css/index31.css | 1 + BTPanel/static/vite/css/index32.css | 1 + BTPanel/static/vite/css/index33.css | 1 + BTPanel/static/vite/css/index34.css | 1 + BTPanel/static/vite/css/index4.css | 1 + BTPanel/static/vite/css/index5.css | 1 + BTPanel/static/vite/css/index6.css | 1 + BTPanel/static/vite/css/index7.css | 1 + BTPanel/static/vite/css/index8.css | 1 + BTPanel/static/vite/css/index9.css | 1 + BTPanel/static/vite/css/new.css | 1 + BTPanel/static/vite/css/old.css | 1 + BTPanel/static/vite/css/submit.css | 1 + BTPanel/static/vite/css/useTableColumns.css | 1 + BTPanel/static/vite/data/world.json | 32100 ++++++++++++++++ BTPanel/static/vite/favicon.ico | Bin 0 -> 15406 bytes .../static/vite/fonts/YouSheBiaoTiHei-2.ttf | Bin 0 -> 1414784 bytes .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 17848 bytes BTPanel/static/vite/iconfont/demo.css | 539 + BTPanel/static/vite/iconfont/demo_index.html | 835 + BTPanel/static/vite/iconfont/iconfont.css | 129 + BTPanel/static/vite/iconfont/iconfont.js | 1 + BTPanel/static/vite/iconfont/iconfont.json | 205 + BTPanel/static/vite/iconfont/iconfont.ttf | Bin 0 -> 13568 bytes BTPanel/static/vite/iconfont/iconfont.woff | Bin 0 -> 8952 bytes BTPanel/static/vite/iconfont/iconfont.woff2 | Bin 0 -> 7200 bytes BTPanel/static/vite/images/banner.png | Bin 0 -> 5558 bytes .../images/glyphicons-halflings-regular.svg | 288 + BTPanel/static/vite/images/mail-server.png | Bin 0 -> 6509 bytes BTPanel/static/vite/images/preview.png | Bin 0 -> 86456 bytes BTPanel/static/vite/images/recommend.svg | 14 + .../static/vite/images/soft-ico/deletes.png | Bin 0 -> 168 bytes .../vite/images/soft-ico/deletes_hover.png | Bin 0 -> 230 bytes BTPanel/static/vite/images/soft-ico/hot.png | Bin 0 -> 357 bytes BTPanel/static/vite/images/soft-ico/hots.png | Bin 0 -> 4561 bytes .../vite/images/soft-ico/ico-616seo.png | Bin 0 -> 1139 bytes .../vite/images/soft-ico/ico-Phddns.png | Bin 0 -> 2137 bytes .../vite/images/soft-ico/ico-a_site_click.png | Bin 0 -> 140 bytes .../vite/images/soft-ico/ico-aliddns.png | Bin 0 -> 861 bytes .../vite/images/soft-ico/ico-alioss.png | Bin 0 -> 1569 bytes .../vite/images/soft-ico/ico-alipan.png | Bin 0 -> 15526 bytes .../vite/images/soft-ico/ico-alldns.png | Bin 0 -> 2591 bytes .../static/vite/images/soft-ico/ico-aloss.png | Bin 0 -> 852 bytes .../vite/images/soft-ico/ico-anti_spam.png | Bin 0 -> 498 bytes .../vite/images/soft-ico/ico-apache.png | Bin 0 -> 1417 bytes .../static/vite/images/soft-ico/ico-app.png | Bin 0 -> 1453 bytes .../vite/images/soft-ico/ico-asp_jexus.png | Bin 0 -> 1166 bytes .../images/soft-ico/ico-attack_blocking.png | Bin 0 -> 1062 bytes .../vite/images/soft-ico/ico-autoclear.png | Bin 0 -> 1011 bytes .../vite/images/soft-ico/ico-autoshield.png | Bin 0 -> 809 bytes .../vite/images/soft-ico/ico-aws_s3.png | Bin 0 -> 2044 bytes .../vite/images/soft-ico/ico-backup.png | Bin 0 -> 1850 bytes .../images/soft-ico/ico-baidu_netdisk.png | Bin 0 -> 1839 bytes .../vite/images/soft-ico/ico-baidu_push.png | Bin 0 -> 2661 bytes .../vite/images/soft-ico/ico-baidupan.png | Bin 0 -> 1872 bytes .../vite/images/soft-ico/ico-baktomail.png | Bin 0 -> 2272 bytes .../static/vite/images/soft-ico/ico-bdbos.png | Bin 0 -> 500 bytes .../vite/images/soft-ico/ico-bocecom.png | Bin 0 -> 4286 bytes .../static/vite/images/soft-ico/ico-boot.png | Bin 0 -> 638 bytes .../static/vite/images/soft-ico/ico-bos.png | Bin 0 -> 793 bytes .../vite/images/soft-ico/ico-bt_bbr.png | Bin 0 -> 1566 bytes .../vite/images/soft-ico/ico-bt_boce.png | Bin 0 -> 2035 bytes .../vite/images/soft-ico/ico-bt_boce1.png | 0 .../vite/images/soft-ico/ico-bt_hids.png | Bin 0 -> 682 bytes .../vite/images/soft-ico/ico-bt_security.png | Bin 0 -> 388 bytes .../vite/images/soft-ico/ico-bt_ssh_auth.png | Bin 0 -> 1055 bytes .../vite/images/soft-ico/ico-bt_vsftpd.png | Bin 0 -> 898 bytes .../vite/images/soft-ico/ico-btapibot.png | Bin 0 -> 669 bytes .../static/vite/images/soft-ico/ico-btapp.png | Bin 0 -> 1179 bytes .../vite/images/soft-ico/ico-btappmanager.png | Bin 0 -> 2463 bytes .../static/vite/images/soft-ico/ico-btco.png | Bin 0 -> 886 bytes .../vite/images/soft-ico/ico-btgitea.png | Bin 0 -> 1571 bytes .../vite/images/soft-ico/ico-btgogs.png | Bin 0 -> 1495 bytes .../vite/images/soft-ico/ico-btiplibrary.png | Bin 0 -> 4311 bytes .../images/soft-ico/ico-btrenwubeifen.png | Bin 0 -> 1105 bytes .../images/soft-ico/ico-bttomcat_push.png | 0 .../static/vite/images/soft-ico/ico-btwaf.png | Bin 0 -> 2174 bytes .../vite/images/soft-ico/ico-btwaf_httpd.png | Bin 0 -> 1812 bytes .../images/soft-ico/ico-build_website.png | Bin 0 -> 1765 bytes .../vite/images/soft-ico/ico-cahelper.png | Bin 0 -> 2109 bytes .../vite/images/soft-ico/ico-cf_ipchange.png | Bin 0 -> 550 bytes .../vite/images/soft-ico/ico-chafenba.png | Bin 0 -> 1663 bytes .../static/vite/images/soft-ico/ico-clear.png | Bin 0 -> 2067 bytes .../vite/images/soft-ico/ico-clickhouse.png | 0 .../vite/images/soft-ico/ico-cloud189.png | Bin 0 -> 1744 bytes .../images/soft-ico/ico-cloud_db_diff.png | Bin 0 -> 2220 bytes .../vite/images/soft-ico/ico-cloudflare.png | Bin 0 -> 910 bytes .../vite/images/soft-ico/ico-cmd-hover.png | Bin 0 -> 280 bytes .../static/vite/images/soft-ico/ico-cmd.png | Bin 0 -> 280 bytes .../vite/images/soft-ico/ico-code_server.png | Bin 0 -> 452 bytes .../vite/images/soft-ico/ico-coll_admin.png | Bin 0 -> 625 bytes .../vite/images/soft-ico/ico-colscript.png | Bin 0 -> 1743 bytes .../vite/images/soft-ico/ico-composer.png | Bin 0 -> 713 bytes .../static/vite/images/soft-ico/ico-cosfs.png | Bin 0 -> 2503 bytes .../images/soft-ico/ico-crmeb_zzff_v144.png | Bin 0 -> 11809 bytes .../vite/images/soft-ico/ico-customtime.png | Bin 0 -> 2408 bytes .../images/soft-ico/ico-database_mater.png | Bin 0 -> 1995 bytes .../vite/images/soft-ico/ico-dbcache.png | Bin 0 -> 1070 bytes .../vite/images/soft-ico/ico-deployment.png | Bin 0 -> 529 bytes .../static/vite/images/soft-ico/ico-disk.png | Bin 0 -> 2752 bytes .../images/soft-ico/ico-disk_analysis.png | Bin 0 -> 1107 bytes .../vite/images/soft-ico/ico-diskquota.png | Bin 0 -> 1608 bytes .../vite/images/soft-ico/ico-disktool.png | Bin 0 -> 1221 bytes .../vite/images/soft-ico/ico-dk_adminer.png | Bin 0 -> 1914 bytes .../vite/images/soft-ico/ico-dk_axigen.png | Bin 0 -> 6762 bytes .../vite/images/soft-ico/ico-dk_caddy.png | Bin 0 -> 3498 bytes .../vite/images/soft-ico/ico-dk_canal.png | Bin 0 -> 14956 bytes .../images/soft-ico/ico-dk_chatgpt_web.png | Bin 0 -> 9326 bytes .../vite/images/soft-ico/ico-dk_collabora.png | Bin 0 -> 6896 bytes .../vite/images/soft-ico/ico-dk_coturn.png | Bin 0 -> 8744 bytes .../vite/images/soft-ico/ico-dk_couchdb.png | Bin 0 -> 8799 bytes .../vite/images/soft-ico/ico-dk_cpolar.png | Bin 0 -> 900 bytes .../vite/images/soft-ico/ico-dk_drupal.png | Bin 0 -> 2522 bytes .../images/soft-ico/ico-dk_dubbo_admin.png | Bin 0 -> 784 bytes .../vite/images/soft-ico/ico-dk_elk.png | Bin 0 -> 14611 bytes .../vite/images/soft-ico/ico-dk_erpnext.png | Bin 0 -> 1251 bytes .../static/vite/images/soft-ico/ico-dk_es.png | Bin 0 -> 14611 bytes .../vite/images/soft-ico/ico-dk_flarum.png | Bin 0 -> 10312 bytes .../images/soft-ico/ico-dk_gh_proxy_py.png | Bin 0 -> 659 bytes .../vite/images/soft-ico/ico-dk_ghost.png | Bin 0 -> 2473 bytes .../vite/images/soft-ico/ico-dk_gitea.png | Bin 0 -> 1267 bytes .../vite/images/soft-ico/ico-dk_gitlab_ce.png | Bin 0 -> 7163 bytes .../vite/images/soft-ico/ico-dk_gocd.png | Bin 0 -> 8692 bytes .../vite/images/soft-ico/ico-dk_grafana.png | Bin 0 -> 10409 bytes .../vite/images/soft-ico/ico-dk_hadoop.png | Bin 0 -> 1081 bytes .../vite/images/soft-ico/ico-dk_harbor.png | Bin 0 -> 2233 bytes .../vite/images/soft-ico/ico-dk_hexo.png | Bin 0 -> 4764 bytes .../vite/images/soft-ico/ico-dk_jenkins.png | Bin 0 -> 13895 bytes .../images/soft-ico/ico-dk_jupyterhub.png | Bin 0 -> 1258 bytes .../vite/images/soft-ico/ico-dk_kafka.png | Bin 0 -> 944 bytes .../soft-ico/ico-dk_mariadb_manager.png | Bin 0 -> 5840 bytes .../vite/images/soft-ico/ico-dk_memos.png | Bin 0 -> 11636 bytes .../vite/images/soft-ico/ico-dk_minio.png | Bin 0 -> 729 bytes .../vite/images/soft-ico/ico-dk_mosquitto.png | Bin 0 -> 1912 bytes .../images/soft-ico/ico-dk_mysql_manager.png | Bin 0 -> 11005 bytes .../vite/images/soft-ico/ico-dk_nacos.png | Bin 0 -> 8171 bytes .../vite/images/soft-ico/ico-dk_nextcloud.png | Bin 0 -> 6524 bytes .../vite/images/soft-ico/ico-dk_nexus3.png | Bin 0 -> 4998 bytes .../vite/images/soft-ico/ico-dk_odoo.png | Bin 0 -> 1094 bytes .../vite/images/soft-ico/ico-dk_onenav.png | Bin 0 -> 885 bytes .../images/soft-ico/ico-dk_onlyoffice.png | Bin 0 -> 3108 bytes .../vite/images/soft-ico/ico-dk_opencat.png | Bin 0 -> 14133 bytes .../vite/images/soft-ico/ico-dk_opentts.png | Bin 0 -> 8268 bytes .../vite/images/soft-ico/ico-dk_openvpn.png | Bin 0 -> 9448 bytes .../vite/images/soft-ico/ico-dk_owncloud.png | Bin 0 -> 8566 bytes .../vite/images/soft-ico/ico-dk_playedu.png | Bin 0 -> 1131 bytes .../vite/images/soft-ico/ico-dk_polaris.png | Bin 0 -> 871 bytes .../vite/images/soft-ico/ico-dk_portainer.png | Bin 0 -> 1081 bytes .../images/soft-ico/ico-dk_prometheus.png | Bin 0 -> 7418 bytes .../vite/images/soft-ico/ico-dk_qinglong.png | Bin 0 -> 24383 bytes .../vite/images/soft-ico/ico-dk_rabbitmq.png | Bin 0 -> 5611 bytes .../images/soft-ico/ico-dk_redisearch.png | Bin 0 -> 10815 bytes .../vite/images/soft-ico/ico-dk_rocketmq.png | Bin 0 -> 8368 bytes .../images/soft-ico/ico-dk_roundcubemail.png | Bin 0 -> 6488 bytes .../vite/images/soft-ico/ico-dk_rtmp.png | Bin 0 -> 1362 bytes .../vite/images/soft-ico/ico-dk_rustdesk.png | Bin 0 -> 3591 bytes .../vite/images/soft-ico/ico-dk_seata.png | Bin 0 -> 10806 bytes .../vite/images/soft-ico/ico-dk_solr.png | Bin 0 -> 10235 bytes .../vite/images/soft-ico/ico-dk_sphinx.png | Bin 0 -> 8434 bytes .../vite/images/soft-ico/ico-dk_spug.png | Bin 0 -> 5847 bytes .../vite/images/soft-ico/ico-dk_squid.png | Bin 0 -> 16430 bytes .../vite/images/soft-ico/ico-dk_srs.png | Bin 0 -> 2025 bytes .../vite/images/soft-ico/ico-dk_supabase.png | Bin 0 -> 6172 bytes .../vite/images/soft-ico/ico-dk_teamspeak.png | Bin 0 -> 2926 bytes .../vite/images/soft-ico/ico-dk_teleport.png | Bin 0 -> 10397 bytes .../vite/images/soft-ico/ico-dk_tinyproxy.png | Bin 0 -> 834 bytes .../vite/images/soft-ico/ico-dk_umami.png | Bin 0 -> 971 bytes .../vite/images/soft-ico/ico-dk_walle.png | Bin 0 -> 848 bytes .../vite/images/soft-ico/ico-dk_wordpress.png | Bin 0 -> 1385 bytes .../vite/images/soft-ico/ico-dk_xxljob.png | Bin 0 -> 14321 bytes .../vite/images/soft-ico/ico-dk_zabbix.png | Bin 0 -> 7246 bytes .../vite/images/soft-ico/ico-dk_zookeeper.png | Bin 0 -> 4375 bytes .../vite/images/soft-ico/ico-dmandwp.png | Bin 0 -> 2741 bytes .../static/vite/images/soft-ico/ico-dns.png | Bin 0 -> 514 bytes .../vite/images/soft-ico/ico-dns_manager.png | Bin 0 -> 694 bytes .../vite/images/soft-ico/ico-dnscom.png | Bin 0 -> 1150 bytes .../vite/images/soft-ico/ico-dnspod.png | Bin 0 -> 2707 bytes .../vite/images/soft-ico/ico-dnspod_ddns.png | Bin 0 -> 1426 bytes .../soft-ico/ico-dnspod_parsing_switch.png | Bin 0 -> 1426 bytes .../images/soft-ico/ico-dnspod_record.png | Bin 0 -> 1426 bytes .../vite/images/soft-ico/ico-docker.png | Bin 0 -> 1597 bytes .../images/soft-ico/ico-docker_project_m.png | 0 .../vite/images/soft-ico/ico-dotnet.png | Bin 0 -> 5858 bytes .../static/vite/images/soft-ico/ico-dv.png | Bin 0 -> 461 bytes .../static/vite/images/soft-ico/ico-ehost.png | Bin 0 -> 1055 bytes .../images/soft-ico/ico-elasticsearch.png | Bin 0 -> 702 bytes .../images/soft-ico/ico-encryption365.png | Bin 0 -> 692 bytes .../images/soft-ico/ico-enterprise_backup.png | Bin 0 -> 1409 bytes .../vite/images/soft-ico/ico-envbackup.png | 0 .../vite/images/soft-ico/ico-fail2ban.png | Bin 0 -> 2280 bytes .../vite/images/soft-ico/ico-fastosdocker.png | Bin 0 -> 7227 bytes .../vite/images/soft-ico/ico-fecmall.png | Bin 0 -> 1033 bytes .../vite/images/soft-ico/ico-ffmpeg.png | 0 .../images/soft-ico/ico-file_hash_check.png | Bin 0 -> 637 bytes .../soft-ico/ico-file_real_time_log.png | Bin 0 -> 524 bytes .../vite/images/soft-ico/ico-firewall.png | Bin 0 -> 692 bytes .../images/soft-ico/ico-free_promotion.png | Bin 0 -> 611 bytes .../vite/images/soft-ico/ico-free_waf.png | Bin 0 -> 2501 bytes .../vite/images/soft-ico/ico-freefrp.png | Bin 0 -> 2263 bytes .../vite/images/soft-ico/ico-freefrpc.png | Bin 0 -> 223 bytes .../static/vite/images/soft-ico/ico-frp.png | 0 .../static/vite/images/soft-ico/ico-frpc.png | Bin 0 -> 368 bytes .../static/vite/images/soft-ico/ico-frps.png | Bin 0 -> 223 bytes .../vite/images/soft-ico/ico-frps_simple.png | Bin 0 -> 824 bytes .../static/vite/images/soft-ico/ico-ftp.png | Bin 0 -> 1457 bytes .../vite/images/soft-ico/ico-ftplimit.png | Bin 0 -> 3075 bytes .../images/soft-ico/ico-gcloud_storage.png | Bin 0 -> 1763 bytes .../vite/images/soft-ico/ico-gdrive.png | Bin 0 -> 1879 bytes .../vite/images/soft-ico/ico-gepfree.png | Bin 0 -> 3774 bytes .../vite/images/soft-ico/ico-git_deploy.png | Bin 0 -> 1687 bytes .../soft-ico/ico-git_repository_deploy.png | Bin 0 -> 1687 bytes .../vite/images/soft-ico/ico-gitlab.png | Bin 0 -> 1155 bytes .../vite/images/soft-ico/ico-godaddydns.png | Bin 0 -> 2359 bytes .../vite/images/soft-ico/ico-hash_pk.png | Bin 0 -> 2219 bytes .../vite/images/soft-ico/ico-hm_shell_san.png | Bin 0 -> 1770 bytes .../vite/images/soft-ico/ico-host_login.png | Bin 0 -> 348 bytes .../static/vite/images/soft-ico/ico-hwobs.png | Bin 0 -> 2224 bytes .../vite/images/soft-ico/ico-hxsquota.png | Bin 0 -> 2514 bytes .../images/soft-ico/ico-image_compress.png | Bin 0 -> 2435 bytes .../vite/images/soft-ico/ico-imgtool.png | Bin 0 -> 1098 bytes .../vite/images/soft-ico/ico-imgtools.png | Bin 0 -> 16718 bytes .../images/soft-ico/ico-ip_configuration.png | Bin 0 -> 971 bytes .../vite/images/soft-ico/ico-java_manager.png | Bin 0 -> 1800 bytes .../vite/images/soft-ico/ico-jdcloud.png | Bin 0 -> 749 bytes .../vite/images/soft-ico/ico-jdk_manager.png | Bin 0 -> 1194 bytes .../static/vite/images/soft-ico/ico-jdoss.png | Bin 0 -> 1901 bytes .../static/vite/images/soft-ico/ico-jexus.png | Bin 0 -> 2389 bytes .../vite/images/soft-ico/ico-jimjiami.png | Bin 0 -> 942 bytes .../vite/images/soft-ico/ico-jumpserver.png | Bin 0 -> 1933 bytes .../static/vite/images/soft-ico/ico-kafka.png | Bin 0 -> 740 bytes .../vite/images/soft-ico/ico-linksforbt.png | Bin 0 -> 1207 bytes .../vite/images/soft-ico/ico-linuxsys.png | Bin 0 -> 1575 bytes .../vite/images/soft-ico/ico-load_balance.png | Bin 0 -> 893 bytes .../images/soft-ico/ico-load_leveling.png | Bin 0 -> 600 bytes .../vite/images/soft-ico/ico-lotusweblog.png | Bin 0 -> 1611 bytes .../vite/images/soft-ico/ico-lzfhdwz.png | Bin 0 -> 5167 bytes .../vite/images/soft-ico/ico-mail_sys.png | Bin 0 -> 984 bytes .../vite/images/soft-ico/ico-masterslave.png | Bin 0 -> 592 bytes .../vite/images/soft-ico/ico-memcached.png | Bin 0 -> 1615 bytes .../vite/images/soft-ico/ico-mfboot.png | Bin 0 -> 2376 bytes .../vite/images/soft-ico/ico-mfftpdev.png | Bin 0 -> 2704 bytes .../vite/images/soft-ico/ico-mflogview.png | Bin 0 -> 2704 bytes .../vite/images/soft-ico/ico-mfngxwaf.png | Bin 0 -> 2376 bytes .../vite/images/soft-ico/ico-mfsearch.png | Bin 0 -> 2704 bytes .../vite/images/soft-ico/ico-mfsmbd.png | Bin 0 -> 2704 bytes .../vite/images/soft-ico/ico-mfwatermark.png | Bin 0 -> 2704 bytes .../vite/images/soft-ico/ico-mfwebdav.png | Bin 0 -> 2376 bytes .../vite/images/soft-ico/ico-modbtywaf.png | Bin 0 -> 2501 bytes .../vite/images/soft-ico/ico-mongodb.png | Bin 0 -> 1464 bytes .../vite/images/soft-ico/ico-monitor.png | Bin 0 -> 1490 bytes .../vite/images/soft-ico/ico-mounter.png | Bin 0 -> 535 bytes .../vite/images/soft-ico/ico-msg_push.png | Bin 0 -> 1491 bytes .../vite/images/soft-ico/ico-msonedrive.png | Bin 0 -> 1330 bytes .../vite/images/soft-ico/ico-my_toolbox.png | Bin 0 -> 1821 bytes .../vite/images/soft-ico/ico-myhaproxy.png | Bin 0 -> 1534 bytes .../static/vite/images/soft-ico/ico-mysql.png | Bin 0 -> 1464 bytes .../images/soft-ico/ico-mysql_replicate.png | Bin 0 -> 563 bytes .../vite/images/soft-ico/ico-mysqlfind.png | Bin 0 -> 2336 bytes .../vite/images/soft-ico/ico-netdisk.png | Bin 0 -> 1950 bytes .../vite/images/soft-ico/ico-new_gitlab.png | Bin 0 -> 1155 bytes .../vite/images/soft-ico/ico-nfs_tools.png | Bin 0 -> 633 bytes .../static/vite/images/soft-ico/ico-nfsgo.png | Bin 0 -> 2237 bytes .../static/vite/images/soft-ico/ico-nginx.png | Bin 0 -> 1248 bytes .../vite/images/soft-ico/ico-node_admin.png | Bin 0 -> 1700 bytes .../vite/images/soft-ico/ico-nodejs.png | Bin 0 -> 990 bytes .../static/vite/images/soft-ico/ico-obs.png | Bin 0 -> 2449 bytes .../static/vite/images/soft-ico/ico-oneav.png | Bin 0 -> 1683 bytes .../static/vite/images/soft-ico/ico-oos.png | Bin 0 -> 1616 bytes .../images/soft-ico/ico-openlitespeed.png | Bin 0 -> 2175 bytes .../vite/images/soft-ico/ico-openrasp.png | Bin 0 -> 2562 bytes .../vite/images/soft-ico/ico-openssl.png | Bin 0 -> 774 bytes .../static/vite/images/soft-ico/ico-ossfs.png | Bin 0 -> 527 bytes .../images/soft-ico/ico-osvulintelligence.png | Bin 0 -> 5383 bytes .../soft-ico/ico-ouken_helper_goto_dir.png | Bin 0 -> 1076 bytes .../soft-ico/ico-panel_jsbridge_installer.png | Bin 0 -> 563 bytes .../images/soft-ico/ico-pgsql_manager.png | Bin 0 -> 2022 bytes .../vite/images/soft-ico/ico-php-5.6.png | 0 .../vite/images/soft-ico/ico-php-8.1.png | Bin 0 -> 1635 bytes .../static/vite/images/soft-ico/ico-php.png | Bin 0 -> 1635 bytes .../vite/images/soft-ico/ico-php_encoder.png | Bin 0 -> 792 bytes .../vite/images/soft-ico/ico-php_filter.png | Bin 0 -> 1831 bytes .../vite/images/soft-ico/ico-phpencoder.png | Bin 0 -> 792 bytes .../vite/images/soft-ico/ico-phpguard.png | Bin 0 -> 1867 bytes .../vite/images/soft-ico/ico-phpmyadmin.png | Bin 0 -> 2064 bytes .../static/vite/images/soft-ico/ico-phpok.png | Bin 0 -> 1560 bytes .../vite/images/soft-ico/ico-phtunnel.png | Bin 0 -> 27454 bytes .../static/vite/images/soft-ico/ico-pm2.png | Bin 0 -> 990 bytes .../vite/images/soft-ico/ico-portblast.png | Bin 0 -> 1445 bytes .../vite/images/soft-ico/ico-postfix.png | Bin 0 -> 2628 bytes .../vite/images/soft-ico/ico-psync_api.png | Bin 0 -> 666 bytes .../images/soft-ico/ico-publicwelfare404.png | Bin 0 -> 1984 bytes .../vite/images/soft-ico/ico-pureftpd.png | Bin 0 -> 1227 bytes .../images/soft-ico/ico-pysqliteadmin.png | Bin 0 -> 2808 bytes .../images/soft-ico/ico-pythonmamager.png | Bin 0 -> 1494 bytes .../vite/images/soft-ico/ico-qiankeji.png | Bin 0 -> 879 bytes .../static/vite/images/soft-ico/ico-qiniu.png | Bin 0 -> 1510 bytes .../static/vite/images/soft-ico/ico-qnoss.png | Bin 0 -> 610 bytes .../vite/images/soft-ico/ico-qrcode.png | Bin 0 -> 333 bytes .../vite/images/soft-ico/ico-rabbitmq.png | Bin 0 -> 296 bytes .../static/vite/images/soft-ico/ico-redis.png | Bin 0 -> 1985 bytes .../vite/images/soft-ico/ico-redisutil.png | Bin 0 -> 2794 bytes .../images/soft-ico/ico-resource_manager.png | Bin 0 -> 1239 bytes .../static/vite/images/soft-ico/ico-rsync.png | Bin 0 -> 963 bytes .../vite/images/soft-ico/ico-safelogin.png | Bin 0 -> 694 bytes .../vite/images/soft-ico/ico-san_security.png | Bin 0 -> 2188 bytes .../static/vite/images/soft-ico/ico-score.png | Bin 0 -> 1731 bytes .../vite/images/soft-ico/ico-sdetection.svg | 10 + .../images/soft-ico/ico-security_notice.png | Bin 0 -> 1499 bytes .../static/vite/images/soft-ico/ico-seos.png | Bin 0 -> 2115 bytes .../vite/images/soft-ico/ico-seotip.png | Bin 0 -> 2929 bytes .../soft-ico/ico-server_port_watcher.png | Bin 0 -> 4799 bytes .../images/soft-ico/ico-server_proxy_res.png | Bin 0 -> 2269 bytes .../vite/images/soft-ico/ico-servicehot.png | Bin 0 -> 1900 bytes .../vite/images/soft-ico/ico-shorturl.png | Bin 0 -> 1240 bytes .../vite/images/soft-ico/ico-site_cluster.png | Bin 0 -> 665 bytes .../images/soft-ico/ico-site_optimization.png | Bin 0 -> 1988 bytes .../vite/images/soft-ico/ico-site_speed.png | Bin 0 -> 683 bytes .../vite/images/soft-ico/ico-sitemap.png | Bin 0 -> 1499 bytes .../vite/images/soft-ico/ico-socks5.png | Bin 0 -> 790 bytes .../vite/images/soft-ico/ico-sphinx.png | Bin 0 -> 752 bytes .../vite/images/soft-ico/ico-srs_cloud.png | Bin 0 -> 2811 bytes .../vite/images/soft-ico/ico-ssh_manager.png | Bin 0 -> 542 bytes .../vite/images/soft-ico/ico-sshkey.png | Bin 0 -> 1921 bytes .../vite/images/soft-ico/ico-ssl_verify.png | Bin 0 -> 607 bytes .../static/vite/images/soft-ico/ico-ssp.png | Bin 0 -> 1941 bytes .../vite/images/soft-ico/ico-static_cdn.png | Bin 0 -> 1018 bytes .../vite/images/soft-ico/ico-su_baidu.png | Bin 0 -> 2721 bytes .../vite/images/soft-ico/ico-supervisor.png | Bin 0 -> 1988 bytes .../static/vite/images/soft-ico/ico-svn.png | Bin 0 -> 2905 bytes .../vite/images/soft-ico/ico-svn_deploy.png | Bin 0 -> 2905 bytes .../static/vite/images/soft-ico/ico-swn.png | Bin 0 -> 1343 bytes .../vite/images/soft-ico/ico-sysdisk.png | 0 .../vite/images/soft-ico/ico-syssafe.png | Bin 0 -> 977 bytes .../vite/images/soft-ico/ico-system_scan.png | Bin 0 -> 2188 bytes .../vite/images/soft-ico/ico-tamper_core.png | Bin 0 -> 534 bytes .../vite/images/soft-ico/ico-tamper_drive.png | Bin 0 -> 534 bytes .../vite/images/soft-ico/ico-tamper_proof.png | Bin 0 -> 534 bytes .../vite/images/soft-ico/ico-task_manager.png | Bin 0 -> 433 bytes .../vite/images/soft-ico/ico-tencent_cdn.png | Bin 0 -> 1752 bytes .../vite/images/soft-ico/ico-tomcat.png | Bin 0 -> 1871 bytes .../vite/images/soft-ico/ico-tomcat2.png | Bin 0 -> 1800 bytes .../static/vite/images/soft-ico/ico-total.png | Bin 0 -> 1448 bytes .../vite/images/soft-ico/ico-tpl_hyaline.png | Bin 0 -> 1825 bytes .../vite/images/soft-ico/ico-tuboshufenxi.png | Bin 0 -> 2122 bytes .../static/vite/images/soft-ico/ico-txcdn.png | Bin 0 -> 2430 bytes .../static/vite/images/soft-ico/ico-txcos.png | Bin 0 -> 1860 bytes .../static/vite/images/soft-ico/ico-txoss.png | Bin 0 -> 1752 bytes .../vite/images/soft-ico/ico-typechomg.png | Bin 0 -> 822 bytes .../static/vite/images/soft-ico/ico-upyun.png | Bin 0 -> 1672 bytes .../vite/images/soft-ico/ico-urgoaccess.png | Bin 0 -> 1504 bytes .../vite/images/soft-ico/ico-urlpush.png | Bin 0 -> 2284 bytes .../static/vite/images/soft-ico/ico-us3.png | Bin 0 -> 2341 bytes .../static/vite/images/soft-ico/ico-users.png | Bin 0 -> 2151 bytes .../vite/images/soft-ico/ico-vuln_push.png | 0 .../vite/images/soft-ico/ico-w7assistant.png | Bin 0 -> 1173 bytes .../vite/images/soft-ico/ico-wdcp_bt.png | Bin 0 -> 468 bytes .../vite/images/soft-ico/ico-webhook.png | Bin 0 -> 782 bytes .../vite/images/soft-ico/ico-webshell.png | Bin 0 -> 2597 bytes .../images/soft-ico/ico-webshell_check.png | Bin 0 -> 2188 bytes .../vite/images/soft-ico/ico-webssh.png | Bin 0 -> 629 bytes .../vite/images/soft-ico/ico-wechatcheck.png | Bin 0 -> 1752 bytes .../vite/images/soft-ico/ico-west_fss.png | Bin 0 -> 1150 bytes .../static/vite/images/soft-ico/ico-whois.png | Bin 0 -> 3240 bytes .../static/vite/images/soft-ico/ico-word.svg | 12 + .../vite/images/soft-ico/ico-wxapijnoocom.png | Bin 0 -> 3246 bytes .../vite/images/soft-ico/ico-xjTools.png | Bin 0 -> 1544 bytes .../vite/images/soft-ico/ico-xshark.png | Bin 0 -> 1414 bytes .../images/soft-ico/ico-y6w_createwebs.png | Bin 0 -> 1484 bytes .../images/soft-ico/ico-y6w_speedtest.png | Bin 0 -> 1643 bytes .../static/vite/images/soft-ico/ico-yb_wm.png | Bin 0 -> 16569 bytes .../vite/images/soft-ico/ico-yichaxin.png | Bin 0 -> 2841 bytes .../soft-ico/soft_icoico-docker_project_m.png | Bin 0 -> 1391 bytes .../images/soft-ico/soft_icoico-vuln_push.png | Bin 0 -> 2617 bytes BTPanel/static/vite/images/update-back.png | Bin 0 -> 17636 bytes BTPanel/static/vite/images/update-bg.png | Bin 0 -> 64351 bytes BTPanel/static/vite/images/update-rocket.svg | 1 + BTPanel/static/vite/js/Alert-legacy.js | 1 + BTPanel/static/vite/js/Alert.js | 1 + BTPanel/static/vite/js/ButtonGroup-legacy.js | 1 + BTPanel/static/vite/js/ButtonGroup.js | 1 + BTPanel/static/vite/js/Checkbox-legacy.js | 1 + BTPanel/static/vite/js/Checkbox.js | 1 + BTPanel/static/vite/js/Countdown-legacy.js | 1 + BTPanel/static/vite/js/Countdown.js | 1 + BTPanel/static/vite/js/DataTable-legacy.js | 1 + BTPanel/static/vite/js/DataTable.js | 1 + BTPanel/static/vite/js/DatePicker-legacy.js | 1 + BTPanel/static/vite/js/DatePicker.js | 1 + BTPanel/static/vite/js/Ellipsis-legacy.js | 1 + BTPanel/static/vite/js/Ellipsis.js | 1 + BTPanel/static/vite/js/Empty-legacy.js | 1 + BTPanel/static/vite/js/Empty.js | 1 + BTPanel/static/vite/js/InputGroup-legacy.js | 1 + BTPanel/static/vite/js/InputGroup.js | 1 + .../static/vite/js/InputGroupLabel-legacy.js | 1 + BTPanel/static/vite/js/InputGroupLabel.js | 1 + BTPanel/static/vite/js/Progress-legacy.js | 1 + BTPanel/static/vite/js/Progress.js | 1 + BTPanel/static/vite/js/RadioButton-legacy.js | 1 + BTPanel/static/vite/js/RadioButton.js | 1 + BTPanel/static/vite/js/Select-legacy.js | 1 + BTPanel/static/vite/js/Select.js | 1 + BTPanel/static/vite/js/Skeleton-legacy.js | 1 + BTPanel/static/vite/js/Skeleton.js | 1 + BTPanel/static/vite/js/Space-legacy.js | 1 + BTPanel/static/vite/js/Space.js | 1 + BTPanel/static/vite/js/Spin-legacy.js | 1 + BTPanel/static/vite/js/Spin.js | 1 + BTPanel/static/vite/js/Tabs-legacy.js | 1 + BTPanel/static/vite/js/Tabs.js | 1 + BTPanel/static/vite/js/Tag-legacy.js | 1 + BTPanel/static/vite/js/Tag.js | 1 + .../static/vite/js/_baseFindIndex-legacy.js | 1 + BTPanel/static/vite/js/_baseFindIndex.js | 1 + BTPanel/static/vite/js/_baseUniq-legacy.js | 1 + BTPanel/static/vite/js/_baseUniq.js | 1 + .../vite/js/_createCompounder-legacy.js | 1 + BTPanel/static/vite/js/_createCompounder.js | 1 + BTPanel/static/vite/js/batch-legacy.js | 1 + BTPanel/static/vite/js/batch-legacy2.js | 1 + BTPanel/static/vite/js/batch-legacy3.js | 1 + BTPanel/static/vite/js/batch.js | 1 + BTPanel/static/vite/js/batch2.js | 1 + BTPanel/static/vite/js/batch3.js | 1 + BTPanel/static/vite/js/capitalize-legacy.js | 1 + BTPanel/static/vite/js/capitalize.js | 1 + BTPanel/static/vite/js/check-legacy.js | 1 + BTPanel/static/vite/js/check.js | 1 + BTPanel/static/vite/js/color-legacy.js | 1 + BTPanel/static/vite/js/color.js | 1 + BTPanel/static/vite/js/columns-legacy.js | 1 + BTPanel/static/vite/js/columns.js | 1 + BTPanel/static/vite/js/data-legacy.js | 1 + BTPanel/static/vite/js/data.js | 1 + BTPanel/static/vite/js/details-legacy.js | 1 + BTPanel/static/vite/js/details.js | 1 + BTPanel/static/vite/js/file-legacy.js | 1 + BTPanel/static/vite/js/file-legacy2.js | 1 + BTPanel/static/vite/js/file-version.js | 1 + BTPanel/static/vite/js/file.js | 1 + BTPanel/static/vite/js/file2.js | 1 + BTPanel/static/vite/js/form-legacy.js | 1 + BTPanel/static/vite/js/form.js | 1 + ..._vue_type_script_setup_true_lang-legacy.js | 1 + ...vue_type_script_setup_true_lang-legacy2.js | 1 + ...orm.vue_vue_type_script_setup_true_lang.js | 1 + ...rm.vue_vue_type_script_setup_true_lang2.js | 1 + BTPanel/static/vite/js/hooks-legacy.js | 1 + BTPanel/static/vite/js/hooks.js | 1 + BTPanel/static/vite/js/index-legacy.js | 14 + BTPanel/static/vite/js/index-legacy10.js | 1 + BTPanel/static/vite/js/index-legacy100.js | 1 + BTPanel/static/vite/js/index-legacy101.js | 1 + BTPanel/static/vite/js/index-legacy102.js | 1 + BTPanel/static/vite/js/index-legacy103.js | 1 + BTPanel/static/vite/js/index-legacy104.js | 1 + BTPanel/static/vite/js/index-legacy105.js | 1 + BTPanel/static/vite/js/index-legacy106.js | 1 + BTPanel/static/vite/js/index-legacy107.js | 1 + BTPanel/static/vite/js/index-legacy108.js | 1 + BTPanel/static/vite/js/index-legacy109.js | 1 + BTPanel/static/vite/js/index-legacy11.js | 1 + BTPanel/static/vite/js/index-legacy110.js | 1 + BTPanel/static/vite/js/index-legacy111.js | 1 + BTPanel/static/vite/js/index-legacy112.js | 1 + BTPanel/static/vite/js/index-legacy113.js | 1 + BTPanel/static/vite/js/index-legacy114.js | 1 + BTPanel/static/vite/js/index-legacy115.js | 1 + BTPanel/static/vite/js/index-legacy116.js | 1 + BTPanel/static/vite/js/index-legacy117.js | 1 + BTPanel/static/vite/js/index-legacy118.js | 1 + BTPanel/static/vite/js/index-legacy119.js | 1 + BTPanel/static/vite/js/index-legacy12.js | 1 + BTPanel/static/vite/js/index-legacy120.js | 1 + BTPanel/static/vite/js/index-legacy121.js | 24 + BTPanel/static/vite/js/index-legacy122.js | 1 + BTPanel/static/vite/js/index-legacy123.js | 1 + BTPanel/static/vite/js/index-legacy124.js | 1 + BTPanel/static/vite/js/index-legacy125.js | 1 + BTPanel/static/vite/js/index-legacy126.js | 1 + BTPanel/static/vite/js/index-legacy127.js | 1 + BTPanel/static/vite/js/index-legacy128.js | 1 + BTPanel/static/vite/js/index-legacy129.js | 1 + BTPanel/static/vite/js/index-legacy13.js | 1 + BTPanel/static/vite/js/index-legacy130.js | 1 + BTPanel/static/vite/js/index-legacy131.js | 1 + BTPanel/static/vite/js/index-legacy132.js | 1 + BTPanel/static/vite/js/index-legacy133.js | 1 + BTPanel/static/vite/js/index-legacy134.js | 1 + BTPanel/static/vite/js/index-legacy135.js | 1 + BTPanel/static/vite/js/index-legacy136.js | 1 + BTPanel/static/vite/js/index-legacy137.js | 1 + BTPanel/static/vite/js/index-legacy138.js | 1 + BTPanel/static/vite/js/index-legacy139.js | 1 + BTPanel/static/vite/js/index-legacy14.js | 1 + BTPanel/static/vite/js/index-legacy140.js | 1 + BTPanel/static/vite/js/index-legacy141.js | 1 + BTPanel/static/vite/js/index-legacy142.js | 1 + BTPanel/static/vite/js/index-legacy143.js | 1 + BTPanel/static/vite/js/index-legacy144.js | 1 + BTPanel/static/vite/js/index-legacy145.js | 1 + BTPanel/static/vite/js/index-legacy146.js | 1 + BTPanel/static/vite/js/index-legacy147.js | 1 + BTPanel/static/vite/js/index-legacy148.js | 1 + BTPanel/static/vite/js/index-legacy149.js | 1 + BTPanel/static/vite/js/index-legacy15.js | 1 + BTPanel/static/vite/js/index-legacy16.js | 1 + BTPanel/static/vite/js/index-legacy17.js | 1 + BTPanel/static/vite/js/index-legacy18.js | 1 + BTPanel/static/vite/js/index-legacy19.js | 1 + BTPanel/static/vite/js/index-legacy2.js | 1 + BTPanel/static/vite/js/index-legacy20.js | 1 + BTPanel/static/vite/js/index-legacy21.js | 1 + BTPanel/static/vite/js/index-legacy22.js | 1 + BTPanel/static/vite/js/index-legacy23.js | 1 + BTPanel/static/vite/js/index-legacy24.js | 1 + BTPanel/static/vite/js/index-legacy25.js | 1 + BTPanel/static/vite/js/index-legacy26.js | 1 + BTPanel/static/vite/js/index-legacy27.js | 7 + BTPanel/static/vite/js/index-legacy28.js | 1 + BTPanel/static/vite/js/index-legacy29.js | 1 + BTPanel/static/vite/js/index-legacy3.js | 1 + BTPanel/static/vite/js/index-legacy30.js | 1 + BTPanel/static/vite/js/index-legacy31.js | 1 + BTPanel/static/vite/js/index-legacy32.js | 1 + BTPanel/static/vite/js/index-legacy33.js | 1 + BTPanel/static/vite/js/index-legacy34.js | 1 + BTPanel/static/vite/js/index-legacy35.js | 1 + BTPanel/static/vite/js/index-legacy36.js | 1 + BTPanel/static/vite/js/index-legacy37.js | 1 + BTPanel/static/vite/js/index-legacy38.js | 1 + BTPanel/static/vite/js/index-legacy39.js | 1 + BTPanel/static/vite/js/index-legacy4.js | 18 + BTPanel/static/vite/js/index-legacy40.js | 1 + BTPanel/static/vite/js/index-legacy41.js | 1 + BTPanel/static/vite/js/index-legacy42.js | 1 + BTPanel/static/vite/js/index-legacy43.js | 1 + BTPanel/static/vite/js/index-legacy44.js | 1 + BTPanel/static/vite/js/index-legacy45.js | 1 + BTPanel/static/vite/js/index-legacy46.js | 1 + BTPanel/static/vite/js/index-legacy47.js | 1 + BTPanel/static/vite/js/index-legacy48.js | 1 + BTPanel/static/vite/js/index-legacy49.js | 1 + BTPanel/static/vite/js/index-legacy5.js | 1 + BTPanel/static/vite/js/index-legacy50.js | 1 + BTPanel/static/vite/js/index-legacy51.js | 1 + BTPanel/static/vite/js/index-legacy52.js | 1 + BTPanel/static/vite/js/index-legacy53.js | 1 + BTPanel/static/vite/js/index-legacy54.js | 1 + BTPanel/static/vite/js/index-legacy55.js | 1 + BTPanel/static/vite/js/index-legacy56.js | 1 + BTPanel/static/vite/js/index-legacy57.js | 1 + BTPanel/static/vite/js/index-legacy58.js | 1 + BTPanel/static/vite/js/index-legacy59.js | 1 + BTPanel/static/vite/js/index-legacy6.js | 1 + BTPanel/static/vite/js/index-legacy60.js | 1 + BTPanel/static/vite/js/index-legacy61.js | 1 + BTPanel/static/vite/js/index-legacy62.js | 1 + BTPanel/static/vite/js/index-legacy63.js | 1 + BTPanel/static/vite/js/index-legacy64.js | 1 + BTPanel/static/vite/js/index-legacy65.js | 7 + BTPanel/static/vite/js/index-legacy66.js | 1 + BTPanel/static/vite/js/index-legacy67.js | 1 + BTPanel/static/vite/js/index-legacy68.js | 1 + BTPanel/static/vite/js/index-legacy69.js | 1 + BTPanel/static/vite/js/index-legacy7.js | 1 + BTPanel/static/vite/js/index-legacy70.js | 1 + BTPanel/static/vite/js/index-legacy71.js | 1 + BTPanel/static/vite/js/index-legacy72.js | 1 + BTPanel/static/vite/js/index-legacy73.js | 1 + BTPanel/static/vite/js/index-legacy74.js | 1 + BTPanel/static/vite/js/index-legacy75.js | 1 + BTPanel/static/vite/js/index-legacy76.js | 1 + BTPanel/static/vite/js/index-legacy77.js | 1 + BTPanel/static/vite/js/index-legacy78.js | 1 + BTPanel/static/vite/js/index-legacy79.js | 1 + BTPanel/static/vite/js/index-legacy8.js | 1 + BTPanel/static/vite/js/index-legacy80.js | 1 + BTPanel/static/vite/js/index-legacy81.js | 1 + BTPanel/static/vite/js/index-legacy82.js | 1 + BTPanel/static/vite/js/index-legacy83.js | 1 + BTPanel/static/vite/js/index-legacy84.js | 1 + BTPanel/static/vite/js/index-legacy85.js | 1 + BTPanel/static/vite/js/index-legacy86.js | 1 + BTPanel/static/vite/js/index-legacy87.js | 1 + BTPanel/static/vite/js/index-legacy88.js | 1 + BTPanel/static/vite/js/index-legacy89.js | 1 + BTPanel/static/vite/js/index-legacy9.js | 1 + BTPanel/static/vite/js/index-legacy90.js | 1 + BTPanel/static/vite/js/index-legacy91.js | 1 + BTPanel/static/vite/js/index-legacy92.js | 1 + BTPanel/static/vite/js/index-legacy93.js | 1 + BTPanel/static/vite/js/index-legacy94.js | 1 + BTPanel/static/vite/js/index-legacy95.js | 1 + BTPanel/static/vite/js/index-legacy96.js | 1 + BTPanel/static/vite/js/index-legacy97.js | 1 + BTPanel/static/vite/js/index-legacy98.js | 1 + BTPanel/static/vite/js/index-legacy99.js | 1 + BTPanel/static/vite/js/index.js | 4 + ..._vue_type_script_setup_true_lang-legacy.js | 1 + ...ue_type_script_setup_true_lang-legacy10.js | 1 + ...ue_type_script_setup_true_lang-legacy11.js | 1 + ...ue_type_script_setup_true_lang-legacy12.js | 1 + ...ue_type_script_setup_true_lang-legacy13.js | 9 + ...ue_type_script_setup_true_lang-legacy14.js | 1 + ...ue_type_script_setup_true_lang-legacy15.js | 1 + ...ue_type_script_setup_true_lang-legacy16.js | 1 + ...ue_type_script_setup_true_lang-legacy17.js | 1 + ...ue_type_script_setup_true_lang-legacy18.js | 1 + ...vue_type_script_setup_true_lang-legacy2.js | 1 + ...vue_type_script_setup_true_lang-legacy3.js | 1 + ...vue_type_script_setup_true_lang-legacy4.js | 1 + ...vue_type_script_setup_true_lang-legacy5.js | 1 + ...vue_type_script_setup_true_lang-legacy6.js | 1 + ...vue_type_script_setup_true_lang-legacy7.js | 1 + ...vue_type_script_setup_true_lang-legacy8.js | 1 + ...vue_type_script_setup_true_lang-legacy9.js | 1 + ...dex.vue_vue_type_script_setup_true_lang.js | 1 + ...x.vue_vue_type_script_setup_true_lang10.js | 2 + ...x.vue_vue_type_script_setup_true_lang11.js | 1 + ...x.vue_vue_type_script_setup_true_lang12.js | 2 + ...x.vue_vue_type_script_setup_true_lang13.js | 9 + ...x.vue_vue_type_script_setup_true_lang14.js | 1 + ...x.vue_vue_type_script_setup_true_lang15.js | 1 + ...x.vue_vue_type_script_setup_true_lang16.js | 2 + ...x.vue_vue_type_script_setup_true_lang17.js | 1 + ...x.vue_vue_type_script_setup_true_lang18.js | 1 + ...ex.vue_vue_type_script_setup_true_lang2.js | 1 + ...ex.vue_vue_type_script_setup_true_lang3.js | 1 + ...ex.vue_vue_type_script_setup_true_lang4.js | 1 + ...ex.vue_vue_type_script_setup_true_lang5.js | 1 + ...ex.vue_vue_type_script_setup_true_lang6.js | 1 + ...ex.vue_vue_type_script_setup_true_lang7.js | 1 + ...ex.vue_vue_type_script_setup_true_lang8.js | 1 + ...ex.vue_vue_type_script_setup_true_lang9.js | 1 + ....vue_vue_type_style_index_0_lang-legacy.js | 1 + .../index.vue_vue_type_style_index_0_lang.js | 1 + BTPanel/static/vite/js/index10.js | 2 + BTPanel/static/vite/js/index100.js | 1 + BTPanel/static/vite/js/index101.js | 1 + BTPanel/static/vite/js/index102.js | 1 + BTPanel/static/vite/js/index103.js | 1 + BTPanel/static/vite/js/index104.js | 1 + BTPanel/static/vite/js/index105.js | 1 + BTPanel/static/vite/js/index106.js | 1 + BTPanel/static/vite/js/index107.js | 1 + BTPanel/static/vite/js/index108.js | 1 + BTPanel/static/vite/js/index109.js | 1 + BTPanel/static/vite/js/index11.js | 2 + BTPanel/static/vite/js/index110.js | 1 + BTPanel/static/vite/js/index111.js | 1 + BTPanel/static/vite/js/index112.js | 1 + BTPanel/static/vite/js/index113.js | 1 + BTPanel/static/vite/js/index114.js | 1 + BTPanel/static/vite/js/index115.js | 1 + BTPanel/static/vite/js/index116.js | 1 + BTPanel/static/vite/js/index117.js | 1 + BTPanel/static/vite/js/index118.js | 1 + BTPanel/static/vite/js/index119.js | 1 + BTPanel/static/vite/js/index12.js | 2 + BTPanel/static/vite/js/index120.js | 1 + BTPanel/static/vite/js/index121.js | 24 + BTPanel/static/vite/js/index122.js | 1 + BTPanel/static/vite/js/index123.js | 1 + BTPanel/static/vite/js/index124.js | 1 + BTPanel/static/vite/js/index125.js | 1 + BTPanel/static/vite/js/index126.js | 1 + BTPanel/static/vite/js/index127.js | 1 + BTPanel/static/vite/js/index128.js | 1 + BTPanel/static/vite/js/index129.js | 1 + BTPanel/static/vite/js/index13.js | 1 + BTPanel/static/vite/js/index130.js | 1 + BTPanel/static/vite/js/index131.js | 1 + BTPanel/static/vite/js/index132.js | 2 + BTPanel/static/vite/js/index133.js | 1 + BTPanel/static/vite/js/index134.js | 1 + BTPanel/static/vite/js/index135.js | 1 + BTPanel/static/vite/js/index136.js | 1 + BTPanel/static/vite/js/index137.js | 1 + BTPanel/static/vite/js/index138.js | 1 + BTPanel/static/vite/js/index139.js | 1 + BTPanel/static/vite/js/index14.js | 2 + BTPanel/static/vite/js/index140.js | 1 + BTPanel/static/vite/js/index141.js | 1 + BTPanel/static/vite/js/index142.js | 1 + BTPanel/static/vite/js/index143.js | 1 + BTPanel/static/vite/js/index144.js | 1 + BTPanel/static/vite/js/index145.js | 1 + BTPanel/static/vite/js/index146.js | 1 + BTPanel/static/vite/js/index147.js | 1 + BTPanel/static/vite/js/index148.js | 1 + BTPanel/static/vite/js/index149.js | 1 + BTPanel/static/vite/js/index15.js | 2 + BTPanel/static/vite/js/index16.js | 1 + BTPanel/static/vite/js/index17.js | 2 + BTPanel/static/vite/js/index18.js | 2 + BTPanel/static/vite/js/index19.js | 2 + BTPanel/static/vite/js/index2.js | 1 + BTPanel/static/vite/js/index20.js | 2 + BTPanel/static/vite/js/index21.js | 1 + BTPanel/static/vite/js/index22.js | 1 + BTPanel/static/vite/js/index23.js | 2 + BTPanel/static/vite/js/index24.js | 1 + BTPanel/static/vite/js/index25.js | 1 + BTPanel/static/vite/js/index26.js | 2 + BTPanel/static/vite/js/index27.js | 8 + BTPanel/static/vite/js/index28.js | 1 + BTPanel/static/vite/js/index29.js | 1 + BTPanel/static/vite/js/index3.js | 1 + BTPanel/static/vite/js/index30.js | 1 + BTPanel/static/vite/js/index31.js | 2 + BTPanel/static/vite/js/index32.js | 2 + BTPanel/static/vite/js/index33.js | 2 + BTPanel/static/vite/js/index34.js | 2 + BTPanel/static/vite/js/index35.js | 2 + BTPanel/static/vite/js/index36.js | 2 + BTPanel/static/vite/js/index37.js | 2 + BTPanel/static/vite/js/index38.js | 1 + BTPanel/static/vite/js/index39.js | 2 + BTPanel/static/vite/js/index4.js | 19 + BTPanel/static/vite/js/index40.js | 1 + BTPanel/static/vite/js/index41.js | 1 + BTPanel/static/vite/js/index42.js | 1 + BTPanel/static/vite/js/index43.js | 1 + BTPanel/static/vite/js/index44.js | 1 + BTPanel/static/vite/js/index45.js | 1 + BTPanel/static/vite/js/index46.js | 1 + BTPanel/static/vite/js/index47.js | 1 + BTPanel/static/vite/js/index48.js | 1 + BTPanel/static/vite/js/index49.js | 2 + BTPanel/static/vite/js/index5.js | 1 + BTPanel/static/vite/js/index50.js | 1 + BTPanel/static/vite/js/index51.js | 2 + BTPanel/static/vite/js/index52.js | 1 + BTPanel/static/vite/js/index53.js | 2 + BTPanel/static/vite/js/index54.js | 1 + BTPanel/static/vite/js/index55.js | 1 + BTPanel/static/vite/js/index56.js | 1 + BTPanel/static/vite/js/index57.js | 2 + BTPanel/static/vite/js/index58.js | 1 + BTPanel/static/vite/js/index59.js | 1 + BTPanel/static/vite/js/index6.js | 1 + BTPanel/static/vite/js/index60.js | 1 + BTPanel/static/vite/js/index61.js | 1 + BTPanel/static/vite/js/index62.js | 1 + BTPanel/static/vite/js/index63.js | 2 + BTPanel/static/vite/js/index64.js | 1 + BTPanel/static/vite/js/index65.js | 7 + BTPanel/static/vite/js/index66.js | 1 + BTPanel/static/vite/js/index67.js | 1 + BTPanel/static/vite/js/index68.js | 1 + BTPanel/static/vite/js/index69.js | 1 + BTPanel/static/vite/js/index7.js | 1 + BTPanel/static/vite/js/index70.js | 1 + BTPanel/static/vite/js/index71.js | 1 + BTPanel/static/vite/js/index72.js | 1 + BTPanel/static/vite/js/index73.js | 1 + BTPanel/static/vite/js/index74.js | 1 + BTPanel/static/vite/js/index75.js | 1 + BTPanel/static/vite/js/index76.js | 1 + BTPanel/static/vite/js/index77.js | 1 + BTPanel/static/vite/js/index78.js | 1 + BTPanel/static/vite/js/index79.js | 1 + BTPanel/static/vite/js/index8.js | 2 + BTPanel/static/vite/js/index80.js | 1 + BTPanel/static/vite/js/index81.js | 2 + BTPanel/static/vite/js/index82.js | 1 + BTPanel/static/vite/js/index83.js | 1 + BTPanel/static/vite/js/index84.js | 1 + BTPanel/static/vite/js/index85.js | 1 + BTPanel/static/vite/js/index86.js | 2 + BTPanel/static/vite/js/index87.js | 1 + BTPanel/static/vite/js/index88.js | 1 + BTPanel/static/vite/js/index89.js | 1 + BTPanel/static/vite/js/index9.js | 2 + BTPanel/static/vite/js/index90.js | 1 + BTPanel/static/vite/js/index91.js | 1 + BTPanel/static/vite/js/index92.js | 1 + BTPanel/static/vite/js/index93.js | 1 + BTPanel/static/vite/js/index94.js | 1 + BTPanel/static/vite/js/index95.js | 1 + BTPanel/static/vite/js/index96.js | 1 + BTPanel/static/vite/js/index97.js | 1 + BTPanel/static/vite/js/index98.js | 1 + BTPanel/static/vite/js/index99.js | 1 + BTPanel/static/vite/js/mail-legacy.js | 1 + BTPanel/static/vite/js/mail.js | 1 + BTPanel/static/vite/js/new-legacy.js | 1 + BTPanel/static/vite/js/new.js | 1 + BTPanel/static/vite/js/old-legacy.js | 1 + BTPanel/static/vite/js/old-legacy2.js | 1 + BTPanel/static/vite/js/old-legacy3.js | 1 + BTPanel/static/vite/js/old-legacy4.js | 1 + BTPanel/static/vite/js/old.js | 1 + BTPanel/static/vite/js/old2.js | 1 + BTPanel/static/vite/js/old3.js | 1 + BTPanel/static/vite/js/old4.js | 1 + BTPanel/static/vite/js/omit-legacy.js | 1 + BTPanel/static/vite/js/omit.js | 1 + BTPanel/static/vite/js/php-legacy.js | 1 + BTPanel/static/vite/js/php.js | 1 + BTPanel/static/vite/js/pinia-legacy.js | 7 + BTPanel/static/vite/js/pinia.js | 6 + BTPanel/static/vite/js/polyfills-legacy.js | 4 + BTPanel/static/vite/js/quota-config-legacy.js | 1 + BTPanel/static/vite/js/quota-config.js | 1 + BTPanel/static/vite/js/refersh-legacy.js | 1 + BTPanel/static/vite/js/refersh.js | 1 + BTPanel/static/vite/js/rewrite-legacy.js | 1 + BTPanel/static/vite/js/rewrite.js | 1 + BTPanel/static/vite/js/set-legacy.js | 1 + BTPanel/static/vite/js/set.js | 1 + BTPanel/static/vite/js/site-legacy.js | 1 + BTPanel/static/vite/js/site-legacy2.js | 1 + BTPanel/static/vite/js/site.js | 1 + BTPanel/static/vite/js/site2.js | 1 + BTPanel/static/vite/js/submit-legacy.js | 1 + BTPanel/static/vite/js/submit.js | 1 + ..._vue_type_script_setup_true_lang-legacy.js | 1 + ...ble.vue_vue_type_script_setup_true_lang.js | 2 + BTPanel/static/vite/js/uniq-legacy.js | 1 + BTPanel/static/vite/js/uniq.js | 1 + BTPanel/static/vite/js/update-back-legacy.js | 1 + BTPanel/static/vite/js/update-back.js | 1 + .../static/vite/js/use-theme-vars-legacy.js | 1 + BTPanel/static/vite/js/use-theme-vars.js | 1 + BTPanel/static/vite/js/useLoading-legacy.js | 1 + BTPanel/static/vite/js/useLoading.js | 1 + BTPanel/static/vite/js/useLoop-legacy.js | 1 + BTPanel/static/vite/js/useLoop.js | 1 + BTPanel/static/vite/js/useSocket-legacy.js | 1 + BTPanel/static/vite/js/useSocket.js | 1 + .../static/vite/js/useTableColumns-legacy.js | 1 + BTPanel/static/vite/js/useTableColumns.js | 2 + BTPanel/static/vite/js/useTableData-legacy.js | 1 + BTPanel/static/vite/js/useTableData.js | 1 + BTPanel/static/vite/js/vue-legacy.js | 46 + BTPanel/static/vite/js/vue.js | 49 + BTPanel/static/vite/lang/en-US/database.json | 130 + BTPanel/static/vite/lang/en-US/docker.json | 584 + BTPanel/static/vite/lang/en-US/ftp.json | 144 + BTPanel/static/vite/lang/en-US/global.json | 10 + BTPanel/static/vite/lang/en-US/home.json | 173 + BTPanel/static/vite/lang/en-US/mail.json | 16 + BTPanel/static/vite/lang/en-US/public.json | 126 + BTPanel/static/vite/lang/en-US/site.json | 610 + BTPanel/static/vite/lang/en-US/wp.json | 150 + BTPanel/static/vite/oldjs/bt_upload.js | 1 + BTPanel/static/vite/oldjs/clipboard.min.js | 7 + BTPanel/static/vite/oldjs/control.js | 1363 + BTPanel/static/vite/oldjs/crontab.js | 1508 + BTPanel/static/vite/oldjs/docker.js | 3452 ++ BTPanel/static/vite/oldjs/echarts.min.js | 45 + BTPanel/static/vite/oldjs/files.js | 6587 ++++ BTPanel/static/vite/oldjs/firewall.js | 3365 ++ BTPanel/static/vite/oldjs/index.js | 3426 ++ BTPanel/static/vite/oldjs/jquery-2.2.4.min.js | 4 + .../vite/oldjs/jquery.dragsort-0.5.2.min.js | 3 + .../static/vite/oldjs/jquery.qrcode.min.js | 28 + BTPanel/static/vite/oldjs/jsencrypt.min.js | 5454 +++ BTPanel/static/vite/oldjs/logs.js | 1102 + BTPanel/static/vite/oldjs/polyfill.min.js | 6 + BTPanel/static/vite/oldjs/public.js | 7306 ++++ BTPanel/static/vite/oldjs/public_backup.js | 10168 +++++ BTPanel/static/vite/oldjs/site.js | 12912 +++++++ BTPanel/static/vite/oldjs/soft.js | 5543 +++ BTPanel/static/vite/oldjs/term.js | 1238 + BTPanel/static/vite/oldjs/tools.js | 3193 ++ BTPanel/static/vite/oldjs/upload-drog.js | 958 + BTPanel/static/vite/oldjs/xterm.js | 7 + BTPanel/templates/default/autherr.html | 283 +- BTPanel/templates/default/error3.html | 505 +- BTPanel/templates/default/firewall.html | 1 + BTPanel/templates/default/index_new.html | 53 + BTPanel/templates/default/layout.html | 1352 +- BTPanel/templates/default/login.html | 2108 +- README.md | 84 +- check-pip-packs.txt | 112 + class/PluginLoader.aarch64.Python3.12.so | Bin 0 -> 535520 bytes class/PluginLoader.x86_64.Python3.12.so | Bin 0 -> 498128 bytes class/acme_v2.py | 1037 +- class/ajax.py | 149 +- class/apache.py | 52 +- class/backup_bak.py | 6 +- class/cachelib/simple.py | 153 +- class/common.py | 172 +- class/config.py | 22 +- class/crontab.py | 9 +- class/crontab_ssl.py | 6 +- class/data.py | 10 +- class/database.py | 38 +- class/databaseModel/base.py | 2 +- class/databaseModel/mongodbModel.py | 16 +- class/databaseModel/pgsqlModel.py | 23 +- class/databaseModel/redisModel.py | 13 +- class/databaseModel/sqliteModel.py | 6 +- class/databaseModel/sqlserverModel.py | 19 +- class/datatool.py | 4 +- class/db.py | 10 +- class/db_mysql.py | 6 +- class/downloadFile.py | 6 +- class/file_execute_deny.py | 32 +- class/files.py | 271 +- class/filesModel/downModel.py | 92 +- class/filesModel/gzModel.py | 684 +- class/filesModel/logsModel.py | 578 +- class/filesModel/rarModel.py | 486 +- class/filesModel/searchModel.py | 614 +- class/filesModel/sizeModel.py | 574 +- class/filesModel/uploadModel.py | 340 +- class/filesModel/zipModel.py | 654 +- class/firewallModel/app/appBase.py | 78 + class/firewallModel/app/firewalld.py | 750 + class/firewallModel/app/iptables.py | 939 + class/firewallModel/app/ufw.py | 729 + class/firewallModel/comModel.py | 1718 + class/firewallModel/firewallBase.py | 220 + class/firewall_new.py | 20 +- class/firewalld.py | 4 +- class/firewalls.py | 19 +- class/ftp.py | 14 +- class/ftplog.py | 139 +- class/http_requests.py | 26 +- class/jobs.py | 63 +- class/log_analysis.py | 12 +- class/logsModel/ftpModel.py | 921 +- class/logsModel/panelModel.py | 8 +- class/logsModel/siteModel.py | 6 +- class/monitor.py | 6 +- class/monitorModel/process_managementModel.py | 1112 +- class/msg/dingding_msg.py | 6 +- class/msg/feishu_msg.py | 4 +- class/msg/mail_msg.py | 6 +- class/msg/sms_msg.py | 6 +- class/msg/tg_msg.py | 6 +- class/msg/weixin_msg.py | 6 +- class/msg/wx_account_msg.py | 6 +- class/nginx.py | 44 +- class/ols.py | 40 +- class/one_key_wp.py | 34 +- class/page.py | 10 +- class/panelApi.py | 6 +- class/panelAuth.py | 74 +- class/panelBackup.py | 30 +- class/panelController.py | 10 +- class/panelDatabaseController.py | 10 +- class/panelDefense.py | 4 +- class/panelDnsapi.py | 6 +- class/panelHttpProxy.py | 10 +- class/panelLets.py | 8 +- class/panelMessage.py | 4 +- class/panelModel/backupModel.py | 6 +- class/panelModel/base.py | 6 +- class/panelModel/publicModel.py | 274 + class/panelMssql.py | 33 +- class/panelMysql.py | 6 +- class/panelPHP.py | 18 +- class/panelPlugin.py | 761 +- class/panelPmd.py | 6 +- class/panelProjectController.py | 10 +- class/panelPush.py | 6 +- class/panelRedirect.py | 32 +- class/panelRun.py | 6 +- class/panelSSL.py | 13 +- class/panelSafe.py | 42 +- class/panelSafeController.py | 10 +- class/panelSearch.py | 2 +- class/panelSite.py | 401 +- class/panelSiteController.py | 6 +- class/panelTask.py | 10 +- class/panelVideo.py | 4 +- class/panelWaf.py | 2 +- class/panelWarning.py | 10 +- class/panel_php_run.php | 6 +- class/panel_restore.py | 12 +- class/panel_search.py | 2 +- class/panel_telegram_bot.py | 46 +- class/password.py | 26 +- class/pay.py | 6 +- class/php_execute_deny.py | 32 +- class/plugin_deployment.py | 10 +- class/process_task.py | 6 +- class/projectModel/bt_docker/dk_compose.py | 10 +- class/projectModel/bt_docker/dk_container.py | 6 +- class/projectModel/bt_docker/dk_host.py | 6 +- class/projectModel/bt_docker/dk_image.py | 6 +- class/projectModel/bt_docker/dk_monitor.py | 6 +- class/projectModel/bt_docker/dk_network.py | 6 +- class/projectModel/bt_docker/dk_public.py | 8 +- class/projectModel/bt_docker/dk_registry.py | 6 +- class/projectModel/bt_docker/dk_screen.py | 6 +- class/projectModel/bt_docker/dk_setup.py | 8 +- class/projectModel/bt_docker/dk_status.py | 6 +- class/projectModel/bt_docker/dk_volume.py | 6 +- class/projectModel/dockerModel.py | 10 +- class/projectModel/nodejsModel.py | 8 +- class/projectModel/quotaModel.py | 608 +- class/public/PluginLoader.py | 24 + class/public/__init__.py | 20 + class/public/common.py | 8277 ++++ class/public/exceptions.py | 11 + class/public/mysqlmgr.py | 136 + class/public/regexplib.py | 29 + class/public/tools.py | 35 + class/public/translations.py | 24 + class/public/validate.py | 1253 + class/public/websitemgr.py | 181 + class/push/base_push.py | 4 +- class/push/panel_push.py | 6 +- class/push/site_push.py | 4 +- class/push/tamper_push.py | 4 +- class/safeModel/base.py | 2 +- class/safeModel/firewallModel.py | 657 +- class/safeModel/freeipModel.py | 6 +- class/safeModel/ipsModel.py | 236 +- class/safeModel/sshModel.py | 10 +- class/safeModel/syslogModel.py | 1932 +- class/safe_warning/sw_alias_ls_rm.py | 4 +- class/safe_warning/sw_audit_log_keep.py | 2 +- class/safe_warning/sw_bashrc.py | 6 +- class/safe_warning/sw_cshrc.py | 6 +- class/safe_warning/sw_cve_2019_5736.py | 2 +- class/safe_warning/sw_cve_2021_4034.py | 8 +- class/safe_warning/sw_database_backup.py | 6 +- class/safe_warning/sw_database_priv.py | 4 +- class/safe_warning/sw_debug_mode.py | 6 +- class/safe_warning/sw_dir_mode.py | 6 +- class/safe_warning/sw_docker_api.py | 8 +- class/safe_warning/sw_file_lock.py | 4 +- class/safe_warning/sw_file_mod.py | 6 +- class/safe_warning/sw_files_recycle_bin.py | 6 +- class/safe_warning/sw_firewall_open.py | 6 +- class/safe_warning/sw_ftp_login.py | 2 +- class/safe_warning/sw_ftp_pass.py | 4 +- class/safe_warning/sw_kernel_space.py | 6 +- class/safe_warning/sw_login_message.py | 6 +- class/safe_warning/sw_login_user.py | 6 +- class/safe_warning/sw_memcached_port.py | 6 +- class/safe_warning/sw_mysql_pass.py | 8 +- class/safe_warning/sw_mysql_port.py | 6 +- class/safe_warning/sw_mysql_priv.py | 4 +- class/safe_warning/sw_nginx_server.py | 6 +- class/safe_warning/sw_panel_control.py | 8 +- class/safe_warning/sw_panel_pass.py | 6 +- class/safe_warning/sw_panel_path.py | 6 +- class/safe_warning/sw_panel_port.py | 6 +- class/safe_warning/sw_panel_swing.py | 8 +- .../safe_warning/sw_php_disable_functions.py | 6 +- class/safe_warning/sw_php_expose.py | 6 +- class/safe_warning/sw_ping.py | 6 +- class/safe_warning/sw_pingin.py | 6 +- class/safe_warning/sw_redis_pass.py | 6 +- class/safe_warning/sw_redis_port.py | 6 +- class/safe_warning/sw_site_spath.py | 6 +- class/safe_warning/sw_site_ssl.py | 6 +- class/safe_warning/sw_site_ssl_expire.py | 6 +- class/safe_warning/sw_ssh_clientalive.py | 10 +- class/safe_warning/sw_ssh_forward.py | 2 +- class/safe_warning/sw_ssh_maxauth.py | 10 +- class/safe_warning/sw_ssh_minclass.py | 8 +- class/safe_warning/sw_ssh_notpass.py | 6 +- class/safe_warning/sw_ssh_passmax.py | 8 +- class/safe_warning/sw_ssh_passmin.py | 8 +- class/safe_warning/sw_ssh_port.py | 8 +- class/safe_warning/sw_ssh_root.py | 8 +- class/safe_warning/sw_ssh_security.py | 8 +- class/safe_warning/sw_umask.py | 6 +- class/safe_warning/sw_waf_install.py | 6 +- class/san_baseline.py | 22 +- class/send_mail.py | 10 +- class/send_to_user.py | 6 +- class/setPanelLets.py | 6 +- class/sewer/cli.py | 4 +- class/site_dir_auth.py | 30 +- class/sites.py | 10 +- class/ssh_authentication.py | 10 +- class/ssh_security.py | 32 +- class/ssh_terminal.py | 8 +- class/ssl_info.py | 220 + class/ssl_manage.py | 4 +- class/system.py | 33 +- class/tomcat.py | 6 +- class/tools.py | 10 +- class/userRegister.py | 174 + class/userlogin.py | 340 +- class/vilidate.py | 4 +- class/webserver.py | 429 + class/webshell_check.py | 6 +- class/website_auto_index.py | 22 +- class/wxapp.py | 6 +- class_v2/acme_v3.py | 2208 ++ class_v2/ajax_v2.py | 1956 + class_v2/apache_v2.py | 381 + class_v2/backup_bak_v2.py | 617 + class_v2/btdockerModelV2/appModel.py | 79 + class_v2/btdockerModelV2/backupModel.py | 185 + class_v2/btdockerModelV2/composeModel.py | 1044 + .../config/com_reg_mirror.json | 3 + .../btdockerModelV2/config/com_registry.json | 11 + .../config/docker_hub_last_update.pl | 1 + .../config/docker_hub_repos.db | Bin 0 -> 112640 bytes class_v2/btdockerModelV2/containerModel.py | 1743 + class_v2/btdockerModelV2/dk_public.py | 293 + class_v2/btdockerModelV2/dkgroupModel_.py | 459 + class_v2/btdockerModelV2/dockerBase.py | 117 + .../btdockerModelV2/dockerSock/__init__.py | 0 .../btdockerModelV2/dockerSock/container.py | 48 + class_v2/btdockerModelV2/dockerSock/image.py | 80 + .../btdockerModelV2/dockerSock/sockBase.py | 19 + class_v2/btdockerModelV2/dockerSock/volume.py | 34 + class_v2/btdockerModelV2/host.py | 60 + class_v2/btdockerModelV2/imageModel.py | 733 + class_v2/btdockerModelV2/monitorModel.py | 129 + class_v2/btdockerModelV2/networkModel.py | 276 + class_v2/btdockerModelV2/projectModel.py | 540 + class_v2/btdockerModelV2/proxyModel.py | 301 + class_v2/btdockerModelV2/registryModel.py | 293 + class_v2/btdockerModelV2/screen.py | 84 + .../btdockerModelV2/script/flush_plugin_.py | 134 + .../btdockerModelV2/script/syncreposdb.py | 92 + class_v2/btdockerModelV2/securityModel.py | 1058 + class_v2/btdockerModelV2/setupModel.py | 686 + class_v2/btdockerModelV2/statusModel.py | 258 + class_v2/btdockerModelV2/volumeModel.py | 159 + class_v2/common_v2.py | 324 + class_v2/config_v2.py | 3352 ++ class_v2/crontab_ssl_v2.py | 58 + class_v2/crontab_v2.py | 564 + class_v2/data_v2.py | 892 + class_v2/databaseModelV2/__init__.py | 1 + class_v2/databaseModelV2/base.py | 542 + class_v2/databaseModelV2/mongodbModel.py | 933 + class_v2/databaseModelV2/pgsqlModel.py | 769 + class_v2/databaseModelV2/redisModel.py | 611 + class_v2/databaseModelV2/sqliteModel.py | 22 + class_v2/databaseModelV2/sqlserverModel.py | 667 + class_v2/database_v2.py | 2187 ++ class_v2/datatool_v2.py | 168 + class_v2/db_mysql_v2.py | 374 + class_v2/db_v2.py | 392 + class_v2/dk_db.py | 574 + class_v2/download_file_v2.py | 64 + class_v2/fastcgi_client_two_v2.py | 399 + class_v2/fastcgi_client_v2.py | 196 + class_v2/file_execute_deny_v2.py | 279 + class_v2/files_v2.py | 3333 ++ class_v2/firewallModelV2/app/appBase.py | 78 + class_v2/firewallModelV2/app/firewalld.py | 750 + class_v2/firewallModelV2/app/iptables.py | 939 + class_v2/firewallModelV2/app/ufw.py | 729 + class_v2/firewallModelV2/comModel.py | 1718 + class_v2/firewallModelV2/firewallBase.py | 220 + class_v2/firewall_new_v2.py | 494 + class_v2/firewalld_v2.py | 345 + class_v2/firewalls_v2.py | 384 + class_v2/flask_compress_v2.py | 146 + class_v2/flask_sockets_v2.py | 113 + class_v2/ftp_log_v2.py | 563 + class_v2/ftp_v2.py | 331 + class_v2/http_requests_v2.py | 612 + class_v2/jobs_v2.py | 927 + class_v2/letsencrypt_v2.py | 252 + class_v2/log_analysis_v2.py | 300 + class_v2/logsModelV2/base.py | 141 + class_v2/logsModelV2/ftpModel.py | 944 + class_v2/logsModelV2/panelModel.py | 321 + class_v2/logsModelV2/siteModel.py | 110 + class_v2/monitorModelV2/base.py | 16 + .../monitorModelV2/process_managementModel.py | 560 + class_v2/monitor_v2.py | 380 + class_v2/one_key_wp_v2.py | 1842 + class_v2/panelControllerV2.py | 119 + class_v2/panelDatabaseControllerV2.py | 128 + class_v2/panelDockerControllerV2.py | 133 + class_v2/panelModControllerV2.py | 117 + class_v2/panelModelV2/backupModel.py | 83 + class_v2/panelModelV2/base.py | 21 + class_v2/panelModelV2/publicModel.py | 280 + class_v2/panelProjectControllerV2.py | 136 + class_v2/panelSafeControllerV2.py | 110 + class_v2/panel_api_v2.py | 252 + class_v2/panel_auth_v2.py | 781 + class_v2/panel_dns_api_v2.py | 565 + class_v2/panel_http_proxy_v2.py | 277 + class_v2/panel_lets_v2.py | 796 + class_v2/panel_mssql_v2.py | 121 + class_v2/panel_mysql_v2.py | 231 + class_v2/panel_php_v2.py | 622 + class_v2/panel_ping_v2.py | 87 + class_v2/panel_plugin_v2.py | 2849 ++ class_v2/panel_push_v2.py | 703 + class_v2/panel_redirect_v2.py | 754 + class_v2/panel_restore_v2.py | 226 + class_v2/panel_site_v2.py | 7633 ++++ class_v2/panel_ssl_v2.py | 1759 + class_v2/panel_task_v2.py | 686 + class_v2/panel_video_V2.py | 62 + class_v2/panel_warning_v2.py | 1668 + class_v2/password_v2.py | 218 + class_v2/plugin_auth_v2.py | 97 + class_v2/plugin_deployment_v2.py | 519 + class_v2/projectModelV2/base.py | 63 + class_v2/projectModelV2/common/__init__.py | 8 + class_v2/projectModelV2/common/base.py | 47 + class_v2/projectModelV2/common/limit_net.py | 239 + class_v2/projectModelV2/common/redirect.py | 806 + class_v2/projectModelV2/dockerModel.py | 58 + class_v2/projectModelV2/nodejsModel.py | 2240 ++ class_v2/projectModelV2/quotaModel.py | 695 + class_v2/safeModelV2/base.py | 92 + class_v2/safeModelV2/firewallModel.py | 4300 +++ class_v2/safeModelV2/freeipModel.py | 97 + class_v2/safeModelV2/ipsModel.py | 118 + class_v2/safeModelV2/securityModel.py | 1691 + class_v2/safeModelV2/sshModel.py | 189 + class_v2/safeModelV2/syslogModel.py | 983 + class_v2/safe_warning_v2/sw_alias_ls_rm.py | 42 + class_v2/safe_warning_v2/sw_audit_docker.py | 34 + class_v2/safe_warning_v2/sw_audit_log_keep.py | 31 + class_v2/safe_warning_v2/sw_bashrc.py | 46 + class_v2/safe_warning_v2/sw_bootloader_mod.py | 37 + class_v2/safe_warning_v2/sw_chmod_sid.py | 38 + .../safe_warning_v2/sw_chmod_stickybit.py | 36 + class_v2/safe_warning_v2/sw_cshrc.py | 47 + class_v2/safe_warning_v2/sw_cve_2019_5736.py | 45 + class_v2/safe_warning_v2/sw_cve_2021_4034.py | 92 + class_v2/safe_warning_v2/sw_cve_2022_2068.py | 55 + class_v2/safe_warning_v2/sw_cve_2022_25845.py | 92 + class_v2/safe_warning_v2/sw_cve_2023_0386.py | 100 + .../safe_warning_v2/sw_database_backup.py | 59 + class_v2/safe_warning_v2/sw_database_priv.py | 57 + class_v2/safe_warning_v2/sw_debug_mode.py | 40 + class_v2/safe_warning_v2/sw_dir_mode.py | 82 + class_v2/safe_warning_v2/sw_docker_api.py | 60 + class_v2/safe_warning_v2/sw_docker_mod.py | 39 + .../safe_warning_v2/sw_empty_passwd_user.py | 38 + class_v2/safe_warning_v2/sw_file_lock.py | 44 + class_v2/safe_warning_v2/sw_file_mod.py | 66 + .../safe_warning_v2/sw_files_recycle_bin.py | 39 + class_v2/safe_warning_v2/sw_firewall_open.py | 43 + class_v2/safe_warning_v2/sw_ftp_login.py | 30 + class_v2/safe_warning_v2/sw_ftp_pass.py | 54 + class_v2/safe_warning_v2/sw_ftp_root.py | 33 + class_v2/safe_warning_v2/sw_ftp_umask.py | 30 + .../safe_warning_v2/sw_httpd_trace_enable.py | 35 + .../safe_warning_v2/sw_httpd_version_leak.py | 39 + class_v2/safe_warning_v2/sw_kernel_space.py | 40 + .../safe_warning_v2/sw_login_fail_limit.py | 37 + class_v2/safe_warning_v2/sw_login_message.py | 49 + class_v2/safe_warning_v2/sw_login_user.py | 67 + class_v2/safe_warning_v2/sw_memcached_port.py | 50 + class_v2/safe_warning_v2/sw_mongodb_auth.py | 33 + class_v2/safe_warning_v2/sw_mysql_pass.py | 46 + class_v2/safe_warning_v2/sw_mysql_port.py | 81 + class_v2/safe_warning_v2/sw_mysql_priv.py | 57 + class_v2/safe_warning_v2/sw_nginx_malware.py | 37 + class_v2/safe_warning_v2/sw_nginx_md5.py | 37 + class_v2/safe_warning_v2/sw_nginx_server.py | 45 + class_v2/safe_warning_v2/sw_panel_control.py | 40 + class_v2/safe_warning_v2/sw_panel_pass.py | 1156 + class_v2/safe_warning_v2/sw_panel_path.py | 65 + class_v2/safe_warning_v2/sw_panel_port.py | 43 + class_v2/safe_warning_v2/sw_panel_swing.py | 42 + class_v2/safe_warning_v2/sw_passwd_repeat.py | 36 + class_v2/safe_warning_v2/sw_php_backdoor.py | 70 + .../sw_php_disable_functions.py | 67 + .../safe_warning_v2/sw_php_display_errors.py | 60 + class_v2/safe_warning_v2/sw_php_expose.py | 62 + class_v2/safe_warning_v2/sw_ping.py | 47 + class_v2/safe_warning_v2/sw_pingin.py | 47 + class_v2/safe_warning_v2/sw_pip_poison.py | 29 + .../safe_warning_v2/sw_protected_hardlinks.py | 42 + .../safe_warning_v2/sw_protected_symlinks.py | 42 + class_v2/safe_warning_v2/sw_redis_pass.py | 48 + class_v2/safe_warning_v2/sw_redis_port.py | 85 + class_v2/safe_warning_v2/sw_risk_file.py | 40 + class_v2/safe_warning_v2/sw_site_logs.py | 70 + class_v2/safe_warning_v2/sw_site_spath.py | 93 + class_v2/safe_warning_v2/sw_site_ssl.py | 53 + .../safe_warning_v2/sw_site_ssl_expire.py | 77 + .../safe_warning_v2/sw_ssh_clientalive.py | 63 + class_v2/safe_warning_v2/sw_ssh_forward.py | 30 + class_v2/safe_warning_v2/sw_ssh_hosts.py | 31 + .../safe_warning_v2/sw_ssh_login_grace.py | 45 + class_v2/safe_warning_v2/sw_ssh_maxauth.py | 64 + class_v2/safe_warning_v2/sw_ssh_minclass.py | 44 + class_v2/safe_warning_v2/sw_ssh_notpass.py | 49 + class_v2/safe_warning_v2/sw_ssh_passmax.py | 46 + class_v2/safe_warning_v2/sw_ssh_passmin.py | 45 + class_v2/safe_warning_v2/sw_ssh_port.py | 77 + class_v2/safe_warning_v2/sw_ssh_root.py | 46 + class_v2/safe_warning_v2/sw_ssh_security.py | 46 + class_v2/safe_warning_v2/sw_ssh_v2.py | 33 + .../safe_warning_v2/sw_strace_backdoor.py | 32 + class_v2/safe_warning_v2/sw_su_root.py | 39 + .../safe_warning_v2/sw_sudoers_nopasswd.py | 46 + class_v2/safe_warning_v2/sw_suid_dumpable.py | 42 + class_v2/safe_warning_v2/sw_system_user.py | 36 + class_v2/safe_warning_v2/sw_tcp_syn_cookie.py | 35 + class_v2/safe_warning_v2/sw_telnet_server.py | 24 + class_v2/safe_warning_v2/sw_time_out.py | 33 + class_v2/safe_warning_v2/sw_tmp_malware.py | 35 + class_v2/safe_warning_v2/sw_tomcat_pass.py | 61 + class_v2/safe_warning_v2/sw_umask.py | 45 + class_v2/safe_warning_v2/sw_waf_install.py | 67 + class_v2/san_baseline_v2.py | 1315 + class_v2/site_dir_auth_v2.py | 420 + class_v2/ssh_security_v2.py | 1018 + class_v2/ssh_terminal_v2.py | 1259 + class_v2/system_v2.py | 1036 + class_v2/user_login_v2.py | 422 + class_v2/vilidate_v2.py | 130 + class_v2/wp_toolkit/__init__.py | 17 + class_v2/wp_toolkit/core.py | 3818 ++ class_v2/wp_toolkit/security.py | 223 + class_v2/wxapp_v2.py | 110 + config/GeoLite2-City.mmdb | Bin 0 -> 9723538 bytes config/databases.json | 698 + config/docker_project_info.json | 338 + config/menu.json | 9 +- config/php_versions.json | 1 + config/safe_autofix.json | 45 + data/pay_type.json | 9 +- data/softList.conf | 2 +- init.sh | 169 +- install/public.sh | 2 +- mod/__init__.py | 0 mod/base/__init__.py | 31 + mod/base/backup_tool/__init__.py | 135 + mod/base/backup_tool/util.py | 65 + mod/base/backup_tool/versions_tool.py | 291 + mod/base/database_tool/__init__.py | 53 + mod/base/database_tool/base.py | 34 + mod/base/database_tool/mongodb.py | 51 + mod/base/database_tool/mysql.py | 98 + mod/base/database_tool/pgsql.py | 54 + mod/base/database_tool/sql_server.py | 28 + mod/base/database_tool/util.py | 68 + mod/base/git_tool/__init__.py | 1 + mod/base/git_tool/install.py | 40 + mod/base/git_tool/install.sh | 26 + mod/base/git_tool/tool.py | 543 + mod/base/git_tool/util.py | 56 + mod/base/msg/__init__.py | 145 + mod/base/msg/dingding_msg.py | 155 + mod/base/msg/feishu_msg.py | 139 + mod/base/msg/mail_msg.py | 135 + mod/base/msg/manager.py | 282 + mod/base/msg/sms_msg.py | 121 + mod/base/msg/test.json | 105 + mod/base/msg/util.py | 139 + mod/base/msg/web_hook_msg.py | 221 + mod/base/msg/weixin_msg.py | 130 + mod/base/msg/wx_account_msg.py | 556 + mod/base/process/__init__.py | 7 + mod/base/process/process.py | 889 + mod/base/process/process_ps.json | 133 + mod/base/process/server.py | 715 + mod/base/process/user.py | 630 + mod/base/process/user_readme.text | 86 + mod/base/push_mod/__init__.py | 495 + mod/base/push_mod/base_task.py | 207 + mod/base/push_mod/compatible.py | 27 + mod/base/push_mod/database_push.py | 239 + mod/base/push_mod/database_push_template.json | 154 + mod/base/push_mod/load_push.py | 271 + mod/base/push_mod/manager.py | 312 + mod/base/push_mod/mods.py | 368 + mod/base/push_mod/rsync_push.py | 301 + mod/base/push_mod/send_tool.py | 133 + mod/base/push_mod/site_push.py | 1290 + mod/base/push_mod/site_push_template.json | 571 + mod/base/push_mod/system.py | 422 + mod/base/push_mod/system_push.py | 421 + mod/base/push_mod/system_push_template.json | 284 + mod/base/push_mod/task_manager_push.py | 503 + mod/base/push_mod/tool.py | 75 + mod/base/push_mod/util.py | 98 + mod/base/web_conf/__init__.py | 66 + mod/base/web_conf/access_restriction.py | 607 + mod/base/web_conf/config_mgr.py | 154 + mod/base/web_conf/default_site.py | 136 + mod/base/web_conf/dir_tool.py | 252 + mod/base/web_conf/dns_api.py | 1682 + mod/base/web_conf/domain_tool.py | 335 + mod/base/web_conf/ip_restrict.py | 326 + mod/base/web_conf/limit_net.py | 238 + mod/base/web_conf/logmanager.py | 855 + mod/base/web_conf/proxy.py | 636 + mod/base/web_conf/redirect.py | 737 + mod/base/web_conf/referer.py | 363 + mod/base/web_conf/ssl.py | 1327 + mod/base/web_conf/util.py | 200 + mod/common/__init__.py | 8 + mod/common/base.py | 47 + mod/common/limit_net.py | 239 + mod/common/redirect.py | 746 + mod/modController.py | 100 + mod/project/docker/__init__.py | 0 mod/project/docker/routetestMod.py | 65 + mod/project/java/__init__.py | 0 mod/project/java/groupMod.py | 1169 + mod/project/java/group_script.py | 34 + mod/project/java/java_web_conf.py | 575 + mod/project/java/jmxquery/JMXQuery-0.1.8.jar | Bin 0 -> 16891 bytes mod/project/java/jmxquery/__init__.py | 215 + mod/project/java/projectMod.py | 3252 ++ mod/project/java/project_update.py | 600 + mod/project/java/server_proxy.py | 644 + mod/project/java/springboot_parser.py | 1047 + mod/project/java/utils.py | 946 + mod/project/php/__init__.py | 0 mod/project/php/aepgMod.py | 1691 + mod/project/php/php_asyncMod.py | 1898 + mod/project/php/serviceconfMod.py | 17 + mod/project/proxy/__init__.py | 0 mod/project/proxy/comMod.py | 3884 ++ mod/project/push/__init__.py | 0 mod/project/push/msgconfMod.py | 70 + mod/project/push/taskMod.py | 140 + mod/test/__init__.py | 0 mod/test/docker/routetestModTest.py | 92 + mod/test/java/__init__.py | 0 mod/test/java/test_spring_config.py | 46 + mod/test/java/test_sprintboot_mod.py | 46 + mod/test/java/test_util.py | 126 + mod/test/php/__init__.py | 0 mod/test/php/test_php_asyncMod.py | 273 + mod/test/process/__init__.py | 0 mod/test/process/test.py | 4 + mod/test/process/test_process.py | 105 + mod/test/process/test_server.py | 69 + mod/test/process/test_user.py | 149 + mod/test/test_backup_tool.py | 35 + mod/test/test_database_tool.py | 54 + mod/test/test_git/test_git_tool.py | 59 + mod/test/test_version_tool.py | 33 + mod/test/test_web_conf/__init__.py | 190 + .../test_web_conf/test_access_restriction.py | 119 + mod/test/test_web_conf/test_config_mgr.py | 68 + mod/test/test_web_conf/test_default_site.py | 66 + mod/test/test_web_conf/test_dir_tool.py | 95 + mod/test/test_web_conf/test_dns_api.py | 1 + mod/test/test_web_conf/test_domain_tool.py | 68 + mod/test/test_web_conf/test_ip_restrict.py | 123 + mod/test/test_web_conf/test_logmanager.py | 159 + mod/test/test_web_conf/test_proxy.py | 107 + mod/test/test_web_conf/test_redirect.py | 87 + mod/test/test_web_conf/test_referer.py | 70 + mod/test/test_web_conf/test_ssl.py | 63 + requirements.txt | 164 +- runserver.py | 6 +- script/Werkzeug-2.2.3-py3-none-any.whl | Bin 0 -> 233551 bytes script/backup.py | 7 +- script/check_msg.py | 2 +- script/dk_log_split.py | 95 + script/logsBackup | 3 +- script/logsBackup.py | 3 +- script/polkit_upgrade.py | 6 +- script/process_network_total.py | 6 +- script/run_script.py | 6 +- script/webserver-ctl.sh | 228 + task.py | 26 +- tools.py | 99 +- webserver/tpls/webserver.conf | 100 + webserver/tpls/webserver_listen.conf | 2 + webserver/tpls/webserver_listen_ssl.conf | 6 + webserver/tpls/webserver_ssl.conf | 16 + 1949 files changed, 329210 insertions(+), 26644 deletions(-) create mode 100644 BTPanel/app.py create mode 100644 BTPanel/routes/flask_hook.py create mode 100644 BTPanel/routes/v1.py create mode 100644 BTPanel/routes/v2.py create mode 100644 BTPanel/static/ckeditor/CHANGES.md create mode 100644 BTPanel/static/ckeditor/LICENSE.md create mode 100644 BTPanel/static/ckeditor/README.md create mode 100644 BTPanel/static/ckeditor/adapters/jquery.js create mode 100644 BTPanel/static/ckeditor/build-config.js create mode 100644 BTPanel/static/ckeditor/ckeditor.js create mode 100644 BTPanel/static/ckeditor/config.js create mode 100644 BTPanel/static/ckeditor/contents.css create mode 100644 BTPanel/static/ckeditor/lang/af.js create mode 100644 BTPanel/static/ckeditor/lang/ar.js create mode 100644 BTPanel/static/ckeditor/lang/az.js create mode 100644 BTPanel/static/ckeditor/lang/bg.js create mode 100644 BTPanel/static/ckeditor/lang/bn.js create mode 100644 BTPanel/static/ckeditor/lang/bs.js create mode 100644 BTPanel/static/ckeditor/lang/ca.js create mode 100644 BTPanel/static/ckeditor/lang/cs.js create mode 100644 BTPanel/static/ckeditor/lang/cy.js create mode 100644 BTPanel/static/ckeditor/lang/da.js create mode 100644 BTPanel/static/ckeditor/lang/de-ch.js create mode 100644 BTPanel/static/ckeditor/lang/de.js create mode 100644 BTPanel/static/ckeditor/lang/el.js create mode 100644 BTPanel/static/ckeditor/lang/en-au.js create mode 100644 BTPanel/static/ckeditor/lang/en-ca.js create mode 100644 BTPanel/static/ckeditor/lang/en-gb.js create mode 100644 BTPanel/static/ckeditor/lang/en.js create mode 100644 BTPanel/static/ckeditor/lang/eo.js create mode 100644 BTPanel/static/ckeditor/lang/es-mx.js create mode 100644 BTPanel/static/ckeditor/lang/es.js create mode 100644 BTPanel/static/ckeditor/lang/et.js create mode 100644 BTPanel/static/ckeditor/lang/eu.js create mode 100644 BTPanel/static/ckeditor/lang/fa.js create mode 100644 BTPanel/static/ckeditor/lang/fi.js create mode 100644 BTPanel/static/ckeditor/lang/fo.js create mode 100644 BTPanel/static/ckeditor/lang/fr-ca.js create mode 100644 BTPanel/static/ckeditor/lang/fr.js create mode 100644 BTPanel/static/ckeditor/lang/gl.js create mode 100644 BTPanel/static/ckeditor/lang/gu.js create mode 100644 BTPanel/static/ckeditor/lang/he.js create mode 100644 BTPanel/static/ckeditor/lang/hi.js create mode 100644 BTPanel/static/ckeditor/lang/hr.js create mode 100644 BTPanel/static/ckeditor/lang/hu.js create mode 100644 BTPanel/static/ckeditor/lang/id.js create mode 100644 BTPanel/static/ckeditor/lang/is.js create mode 100644 BTPanel/static/ckeditor/lang/it.js create mode 100644 BTPanel/static/ckeditor/lang/ja.js create mode 100644 BTPanel/static/ckeditor/lang/ka.js create mode 100644 BTPanel/static/ckeditor/lang/km.js create mode 100644 BTPanel/static/ckeditor/lang/ko.js create mode 100644 BTPanel/static/ckeditor/lang/ku.js create mode 100644 BTPanel/static/ckeditor/lang/lt.js create mode 100644 BTPanel/static/ckeditor/lang/lv.js create mode 100644 BTPanel/static/ckeditor/lang/mk.js create mode 100644 BTPanel/static/ckeditor/lang/mn.js create mode 100644 BTPanel/static/ckeditor/lang/ms.js create mode 100644 BTPanel/static/ckeditor/lang/nb.js create mode 100644 BTPanel/static/ckeditor/lang/nl.js create mode 100644 BTPanel/static/ckeditor/lang/no.js create mode 100644 BTPanel/static/ckeditor/lang/oc.js create mode 100644 BTPanel/static/ckeditor/lang/pl.js create mode 100644 BTPanel/static/ckeditor/lang/pt-br.js create mode 100644 BTPanel/static/ckeditor/lang/pt.js create mode 100644 BTPanel/static/ckeditor/lang/ro.js create mode 100644 BTPanel/static/ckeditor/lang/ru.js create mode 100644 BTPanel/static/ckeditor/lang/si.js create mode 100644 BTPanel/static/ckeditor/lang/sk.js create mode 100644 BTPanel/static/ckeditor/lang/sl.js create mode 100644 BTPanel/static/ckeditor/lang/sq.js create mode 100644 BTPanel/static/ckeditor/lang/sr-latn.js create mode 100644 BTPanel/static/ckeditor/lang/sr.js create mode 100644 BTPanel/static/ckeditor/lang/sv.js create mode 100644 BTPanel/static/ckeditor/lang/th.js create mode 100644 BTPanel/static/ckeditor/lang/tr.js create mode 100644 BTPanel/static/ckeditor/lang/tt.js create mode 100644 BTPanel/static/ckeditor/lang/ug.js create mode 100644 BTPanel/static/ckeditor/lang/uk.js create mode 100644 BTPanel/static/ckeditor/lang/vi.js create mode 100644 BTPanel/static/ckeditor/lang/zh-cn.js create mode 100644 BTPanel/static/ckeditor/lang/zh.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/a11yhelp.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/_translationstatus.txt create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/af.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ar.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/az.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/bg.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ca.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/cs.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/cy.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/da.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/de-ch.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/de.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/el.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/en-au.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/en-gb.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/en.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/eo.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/es-mx.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/es.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/et.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/eu.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/fa.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/fi.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/fo.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/fr-ca.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/fr.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/gl.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/gu.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/he.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/hi.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/hr.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/hu.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/id.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/it.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ja.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/km.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ko.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ku.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/lt.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/lv.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/mk.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/mn.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/nb.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/nl.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/no.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/oc.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/pl.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/pt-br.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/pt.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ro.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ru.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/si.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/sk.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/sl.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/sq.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/sr-latn.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/sr.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/sv.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/th.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/tr.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/tt.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/ug.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/uk.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/vi.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/zh-cn.js create mode 100644 BTPanel/static/ckeditor/plugins/a11yhelp/dialogs/lang/zh.js create mode 100644 BTPanel/static/ckeditor/plugins/about/dialogs/about.js create mode 100644 BTPanel/static/ckeditor/plugins/about/dialogs/hidpi/logo_ckeditor.png create mode 100644 BTPanel/static/ckeditor/plugins/about/dialogs/logo_ckeditor.png create mode 100644 BTPanel/static/ckeditor/plugins/clipboard/dialogs/paste.js create mode 100644 BTPanel/static/ckeditor/plugins/colordialog/dialogs/colordialog.css create mode 100644 BTPanel/static/ckeditor/plugins/colordialog/dialogs/colordialog.js create mode 100644 BTPanel/static/ckeditor/plugins/copyformatting/cursors/cursor-disabled.svg create mode 100644 BTPanel/static/ckeditor/plugins/copyformatting/cursors/cursor.svg create mode 100644 BTPanel/static/ckeditor/plugins/copyformatting/styles/copyformatting.css create mode 100644 BTPanel/static/ckeditor/plugins/dialog/dialogDefinition.js create mode 100644 BTPanel/static/ckeditor/plugins/div/dialogs/div.js create mode 100644 BTPanel/static/ckeditor/plugins/find/dialogs/find.js create mode 100644 BTPanel/static/ckeditor/plugins/flash/dialogs/flash.js create mode 100644 BTPanel/static/ckeditor/plugins/flash/images/placeholder.png create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/button.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/checkbox.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/form.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/hiddenfield.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/radio.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/select.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/textarea.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/dialogs/textfield.js create mode 100644 BTPanel/static/ckeditor/plugins/forms/images/hiddenfield.gif create mode 100644 BTPanel/static/ckeditor/plugins/icons.png create mode 100644 BTPanel/static/ckeditor/plugins/icons_hidpi.png create mode 100644 BTPanel/static/ckeditor/plugins/iframe/dialogs/iframe.js create mode 100644 BTPanel/static/ckeditor/plugins/iframe/images/placeholder.png create mode 100644 BTPanel/static/ckeditor/plugins/image/dialogs/image.js create mode 100644 BTPanel/static/ckeditor/plugins/image/images/noimage.png create mode 100644 BTPanel/static/ckeditor/plugins/link/dialogs/anchor.js create mode 100644 BTPanel/static/ckeditor/plugins/link/dialogs/link.js create mode 100644 BTPanel/static/ckeditor/plugins/link/images/anchor.png create mode 100644 BTPanel/static/ckeditor/plugins/link/images/hidpi/anchor.png create mode 100644 BTPanel/static/ckeditor/plugins/liststyle/dialogs/liststyle.js create mode 100644 BTPanel/static/ckeditor/plugins/magicline/images/hidpi/icon-rtl.png create mode 100644 BTPanel/static/ckeditor/plugins/magicline/images/hidpi/icon.png create mode 100644 BTPanel/static/ckeditor/plugins/magicline/images/icon-rtl.png create mode 100644 BTPanel/static/ckeditor/plugins/magicline/images/icon.png create mode 100644 BTPanel/static/ckeditor/plugins/pagebreak/images/pagebreak.gif create mode 100644 BTPanel/static/ckeditor/plugins/pastefromword/filter/default.js create mode 100644 BTPanel/static/ckeditor/plugins/preview/preview.html create mode 100644 BTPanel/static/ckeditor/plugins/scayt/CHANGELOG.md create mode 100644 BTPanel/static/ckeditor/plugins/scayt/LICENSE.md create mode 100644 BTPanel/static/ckeditor/plugins/scayt/README.md create mode 100644 BTPanel/static/ckeditor/plugins/scayt/dialogs/dialog.css create mode 100644 BTPanel/static/ckeditor/plugins/scayt/dialogs/options.js create mode 100644 BTPanel/static/ckeditor/plugins/scayt/dialogs/toolbar.css create mode 100644 BTPanel/static/ckeditor/plugins/scayt/skins/moono-lisa/scayt.css create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_address.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_blockquote.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_div.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_h1.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_h2.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_h3.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_h4.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_h5.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_h6.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_p.png create mode 100644 BTPanel/static/ckeditor/plugins/showblocks/images/block_pre.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/dialogs/smiley.js create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/angel_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/angel_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/angry_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/angry_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/broken_heart.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/broken_heart.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/confused_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/confused_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/cry_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/cry_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/devil_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/devil_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/embaressed_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/embarrassed_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/embarrassed_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/envelope.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/envelope.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/heart.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/heart.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/kiss.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/kiss.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/lightbulb.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/lightbulb.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/omg_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/omg_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/regular_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/regular_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/sad_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/sad_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/shades_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/shades_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/teeth_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/teeth_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/thumbs_down.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/thumbs_down.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/thumbs_up.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/thumbs_up.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/tongue_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/tongue_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/tounge_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/whatchutalkingabout_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/whatchutalkingabout_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/wink_smile.gif create mode 100644 BTPanel/static/ckeditor/plugins/smiley/images/wink_smile.png create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/_translationstatus.txt create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/af.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ar.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/az.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/bg.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ca.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/cs.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/cy.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/da.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/de-ch.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/de.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/el.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/en-au.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/en-ca.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/en-gb.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/en.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/eo.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/es-mx.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/es.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/et.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/eu.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/fa.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/fi.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/fr-ca.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/fr.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/gl.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/he.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/hr.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/hu.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/id.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/it.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ja.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/km.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ko.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ku.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/lt.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/lv.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/nb.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/nl.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/no.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/oc.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/pl.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/pt-br.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/pt.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ro.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ru.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/si.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/sk.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/sl.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/sq.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/sr-latn.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/sr.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/sv.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/th.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/tr.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/tt.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/ug.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/uk.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/vi.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/zh-cn.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/lang/zh.js create mode 100644 BTPanel/static/ckeditor/plugins/specialchar/dialogs/specialchar.js create mode 100644 BTPanel/static/ckeditor/plugins/table/dialogs/table.js create mode 100644 BTPanel/static/ckeditor/plugins/tableselection/styles/tableselection.css create mode 100644 BTPanel/static/ckeditor/plugins/tabletools/dialogs/tableCell.js create mode 100644 BTPanel/static/ckeditor/plugins/templates/dialogs/templates.css create mode 100644 BTPanel/static/ckeditor/plugins/templates/dialogs/templates.js create mode 100644 BTPanel/static/ckeditor/plugins/templates/templates/default.js create mode 100644 BTPanel/static/ckeditor/plugins/templates/templates/images/template1.gif create mode 100644 BTPanel/static/ckeditor/plugins/templates/templates/images/template2.gif create mode 100644 BTPanel/static/ckeditor/plugins/templates/templates/images/template3.gif create mode 100644 BTPanel/static/ckeditor/plugins/widget/images/handle.png create mode 100644 BTPanel/static/ckeditor/plugins/wsc/LICENSE.md create mode 100644 BTPanel/static/ckeditor/plugins/wsc/README.md create mode 100644 BTPanel/static/ckeditor/plugins/wsc/dialogs/ciframe.html create mode 100644 BTPanel/static/ckeditor/plugins/wsc/dialogs/tmpFrameset.html create mode 100644 BTPanel/static/ckeditor/plugins/wsc/dialogs/wsc.css create mode 100644 BTPanel/static/ckeditor/plugins/wsc/dialogs/wsc.js create mode 100644 BTPanel/static/ckeditor/plugins/wsc/dialogs/wsc_ie.js create mode 100644 BTPanel/static/ckeditor/plugins/wsc/skins/moono-lisa/wsc.css create mode 100644 BTPanel/static/ckeditor/samples/css/samples.css create mode 100644 BTPanel/static/ckeditor/samples/img/github-top.png create mode 100644 BTPanel/static/ckeditor/samples/img/header-bg.png create mode 100644 BTPanel/static/ckeditor/samples/img/header-separator.png create mode 100644 BTPanel/static/ckeditor/samples/img/logo.png create mode 100644 BTPanel/static/ckeditor/samples/img/logo.svg create mode 100644 BTPanel/static/ckeditor/samples/img/navigation-tip.png create mode 100644 BTPanel/static/ckeditor/samples/index.html create mode 100644 BTPanel/static/ckeditor/samples/js/sample.js create mode 100644 BTPanel/static/ckeditor/samples/js/sf.js create mode 100644 BTPanel/static/ckeditor/samples/old/ajax.html create mode 100644 BTPanel/static/ckeditor/samples/old/api.html create mode 100644 BTPanel/static/ckeditor/samples/old/appendto.html create mode 100644 BTPanel/static/ckeditor/samples/old/assets/inlineall/logo.png create mode 100644 BTPanel/static/ckeditor/samples/old/assets/outputxhtml/outputxhtml.css create mode 100644 BTPanel/static/ckeditor/samples/old/assets/posteddata.php create mode 100644 BTPanel/static/ckeditor/samples/old/assets/sample.jpg create mode 100644 BTPanel/static/ckeditor/samples/old/assets/uilanguages/languages.js create mode 100644 BTPanel/static/ckeditor/samples/old/datafiltering.html create mode 100644 BTPanel/static/ckeditor/samples/old/dialog/assets/my_dialog.js create mode 100644 BTPanel/static/ckeditor/samples/old/dialog/dialog.html create mode 100644 BTPanel/static/ckeditor/samples/old/divreplace.html create mode 100644 BTPanel/static/ckeditor/samples/old/enterkey/enterkey.html create mode 100644 BTPanel/static/ckeditor/samples/old/htmlwriter/assets/outputforflash/outputforflash.fla create mode 100644 BTPanel/static/ckeditor/samples/old/htmlwriter/assets/outputforflash/outputforflash.swf create mode 100644 BTPanel/static/ckeditor/samples/old/htmlwriter/assets/outputforflash/swfobject.js create mode 100644 BTPanel/static/ckeditor/samples/old/htmlwriter/outputforflash.html create mode 100644 BTPanel/static/ckeditor/samples/old/htmlwriter/outputhtml.html create mode 100644 BTPanel/static/ckeditor/samples/old/index.html create mode 100644 BTPanel/static/ckeditor/samples/old/inlineall.html create mode 100644 BTPanel/static/ckeditor/samples/old/inlinebycode.html create mode 100644 BTPanel/static/ckeditor/samples/old/inlinetextarea.html create mode 100644 BTPanel/static/ckeditor/samples/old/jquery.html create mode 100644 BTPanel/static/ckeditor/samples/old/magicline/magicline.html create mode 100644 BTPanel/static/ckeditor/samples/old/readonly.html create mode 100644 BTPanel/static/ckeditor/samples/old/replacebyclass.html create mode 100644 BTPanel/static/ckeditor/samples/old/replacebycode.html create mode 100644 BTPanel/static/ckeditor/samples/old/sample.css create mode 100644 BTPanel/static/ckeditor/samples/old/sample.js create mode 100644 BTPanel/static/ckeditor/samples/old/sample_posteddata.php create mode 100644 BTPanel/static/ckeditor/samples/old/tabindex.html create mode 100644 BTPanel/static/ckeditor/samples/old/toolbar/toolbar.html create mode 100644 BTPanel/static/ckeditor/samples/old/uicolor.html create mode 100644 BTPanel/static/ckeditor/samples/old/uilanguages.html create mode 100644 BTPanel/static/ckeditor/samples/old/wysiwygarea/fullpage.html create mode 100644 BTPanel/static/ckeditor/samples/old/xhtmlstyle.html create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/css/fontello.css create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/font/LICENSE.txt create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/font/config.json create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/font/fontello.eot create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/font/fontello.svg create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/font/fontello.ttf create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/font/fontello.woff create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/index.html create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/js/abstracttoolbarmodifier.js create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/js/fulltoolbareditor.js create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/js/toolbarmodifier.js create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/js/toolbartextmodifier.js create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/LICENSE create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/codemirror.css create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/codemirror.js create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/javascript.js create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/neo.css create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/show-hint.css create mode 100644 BTPanel/static/ckeditor/samples/toolbarconfigurator/lib/codemirror/show-hint.js create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/dialog.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/dialog_ie.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/dialog_ie8.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/dialog_iequirks.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/editor.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/editor_gecko.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/editor_ie.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/editor_ie8.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/editor_iequirks.css create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/icons.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/icons_hidpi.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/arrow.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/close.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/hidpi/close.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/hidpi/lock-open.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/hidpi/lock.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/hidpi/refresh.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/lock-open.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/lock.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/refresh.png create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/images/spinner.gif create mode 100644 BTPanel/static/ckeditor/skins/moono-lisa/readme.md create mode 100644 BTPanel/static/ckeditor/styles.js create mode 100644 BTPanel/static/error_page/502.json create mode 100644 BTPanel/static/img/file_icon/Thumbs.db create mode 100644 BTPanel/static/img/file_icon/icon-new-file-lock-active.svg create mode 100644 BTPanel/static/img/file_icon/icon-new-file-lock.svg create mode 100644 BTPanel/static/img/file_icon/icon-new-file-unlock-active.svg create mode 100644 BTPanel/static/img/file_icon/icon-new-file-unlock.svg create mode 100644 BTPanel/static/img/soft_ico/ico-frp.png create mode 100644 BTPanel/static/img/soft_ico/ico-monitor.png create mode 100644 BTPanel/static/js/jquery.i18n.properties.js create mode 100644 BTPanel/static/language/en/LC_MESSAGES/en_monitor.po create mode 100644 BTPanel/static/language/old_en/en.mo create mode 100644 BTPanel/static/language/old_en/en.po create mode 100644 BTPanel/static/oldcss/files_style.css create mode 100644 BTPanel/static/oldcss/site.css create mode 100644 BTPanel/static/vite/css/details.css create mode 100644 BTPanel/static/vite/css/index.css create mode 100644 BTPanel/static/vite/css/index10.css create mode 100644 BTPanel/static/vite/css/index11.css create mode 100644 BTPanel/static/vite/css/index12.css create mode 100644 BTPanel/static/vite/css/index13.css create mode 100644 BTPanel/static/vite/css/index14.css create mode 100644 BTPanel/static/vite/css/index15.css create mode 100644 BTPanel/static/vite/css/index16.css create mode 100644 BTPanel/static/vite/css/index17.css create mode 100644 BTPanel/static/vite/css/index18.css create mode 100644 BTPanel/static/vite/css/index19.css create mode 100644 BTPanel/static/vite/css/index2.css create mode 100644 BTPanel/static/vite/css/index20.css create mode 100644 BTPanel/static/vite/css/index21.css create mode 100644 BTPanel/static/vite/css/index22.css create mode 100644 BTPanel/static/vite/css/index23.css create mode 100644 BTPanel/static/vite/css/index24.css create mode 100644 BTPanel/static/vite/css/index25.css create mode 100644 BTPanel/static/vite/css/index26.css create mode 100644 BTPanel/static/vite/css/index27.css create mode 100644 BTPanel/static/vite/css/index28.css create mode 100644 BTPanel/static/vite/css/index29.css create mode 100644 BTPanel/static/vite/css/index3.css create mode 100644 BTPanel/static/vite/css/index30.css create mode 100644 BTPanel/static/vite/css/index31.css create mode 100644 BTPanel/static/vite/css/index32.css create mode 100644 BTPanel/static/vite/css/index33.css create mode 100644 BTPanel/static/vite/css/index34.css create mode 100644 BTPanel/static/vite/css/index4.css create mode 100644 BTPanel/static/vite/css/index5.css create mode 100644 BTPanel/static/vite/css/index6.css create mode 100644 BTPanel/static/vite/css/index7.css create mode 100644 BTPanel/static/vite/css/index8.css create mode 100644 BTPanel/static/vite/css/index9.css create mode 100644 BTPanel/static/vite/css/new.css create mode 100644 BTPanel/static/vite/css/old.css create mode 100644 BTPanel/static/vite/css/submit.css create mode 100644 BTPanel/static/vite/css/useTableColumns.css create mode 100644 BTPanel/static/vite/data/world.json create mode 100644 BTPanel/static/vite/favicon.ico create mode 100644 BTPanel/static/vite/fonts/YouSheBiaoTiHei-2.ttf create mode 100644 BTPanel/static/vite/fonts/glyphicons-halflings-regular.eot create mode 100644 BTPanel/static/vite/fonts/glyphicons-halflings-regular.svg create mode 100644 BTPanel/static/vite/fonts/glyphicons-halflings-regular.ttf create mode 100644 BTPanel/static/vite/fonts/glyphicons-halflings-regular.woff create mode 100644 BTPanel/static/vite/fonts/glyphicons-halflings-regular.woff2 create mode 100644 BTPanel/static/vite/iconfont/demo.css create mode 100644 BTPanel/static/vite/iconfont/demo_index.html create mode 100644 BTPanel/static/vite/iconfont/iconfont.css create mode 100644 BTPanel/static/vite/iconfont/iconfont.js create mode 100644 BTPanel/static/vite/iconfont/iconfont.json create mode 100644 BTPanel/static/vite/iconfont/iconfont.ttf create mode 100644 BTPanel/static/vite/iconfont/iconfont.woff create mode 100644 BTPanel/static/vite/iconfont/iconfont.woff2 create mode 100644 BTPanel/static/vite/images/banner.png create mode 100644 BTPanel/static/vite/images/glyphicons-halflings-regular.svg create mode 100644 BTPanel/static/vite/images/mail-server.png create mode 100644 BTPanel/static/vite/images/preview.png create mode 100644 BTPanel/static/vite/images/recommend.svg create mode 100644 BTPanel/static/vite/images/soft-ico/deletes.png create mode 100644 BTPanel/static/vite/images/soft-ico/deletes_hover.png create mode 100644 BTPanel/static/vite/images/soft-ico/hot.png create mode 100644 BTPanel/static/vite/images/soft-ico/hots.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-616seo.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-Phddns.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-a_site_click.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-aliddns.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-alioss.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-alipan.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-alldns.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-aloss.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-anti_spam.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-apache.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-app.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-asp_jexus.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-attack_blocking.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-autoclear.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-autoshield.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-aws_s3.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-backup.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-baidu_netdisk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-baidu_push.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-baidupan.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-baktomail.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bdbos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bocecom.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-boot.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_bbr.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_boce.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_boce1.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_hids.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_security.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_ssh_auth.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bt_vsftpd.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btapibot.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btapp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btappmanager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btco.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btgitea.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btgogs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btiplibrary.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btrenwubeifen.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-bttomcat_push.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btwaf.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-btwaf_httpd.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-build_website.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cahelper.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cf_ipchange.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-chafenba.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-clear.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-clickhouse.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cloud189.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cloud_db_diff.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cloudflare.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cmd-hover.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cmd.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-code_server.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-coll_admin.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-colscript.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-composer.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-cosfs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-crmeb_zzff_v144.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-customtime.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-database_mater.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dbcache.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-deployment.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-disk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-disk_analysis.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-diskquota.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-disktool.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_adminer.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_axigen.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_caddy.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_canal.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_chatgpt_web.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_collabora.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_coturn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_couchdb.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_cpolar.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_drupal.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_dubbo_admin.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_elk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_erpnext.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_es.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_flarum.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_gh_proxy_py.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_ghost.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_gitea.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_gitlab_ce.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_gocd.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_grafana.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_hadoop.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_harbor.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_hexo.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_jenkins.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_jupyterhub.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_kafka.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_mariadb_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_memos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_minio.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_mosquitto.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_mysql_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_nacos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_nextcloud.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_nexus3.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_odoo.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_onenav.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_onlyoffice.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_opencat.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_opentts.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_openvpn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_owncloud.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_playedu.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_polaris.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_portainer.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_prometheus.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_qinglong.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_rabbitmq.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_redisearch.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_rocketmq.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_roundcubemail.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_rtmp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_rustdesk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_seata.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_solr.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_sphinx.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_spug.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_squid.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_srs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_supabase.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_teamspeak.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_teleport.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_tinyproxy.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_umami.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_walle.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_wordpress.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_xxljob.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_zabbix.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dk_zookeeper.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dmandwp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dns.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dns_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dnscom.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dnspod.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dnspod_ddns.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dnspod_parsing_switch.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dnspod_record.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-docker.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-docker_project_m.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dotnet.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-dv.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ehost.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-elasticsearch.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-encryption365.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-enterprise_backup.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-envbackup.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-fail2ban.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-fastosdocker.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-fecmall.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ffmpeg.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-file_hash_check.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-file_real_time_log.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-firewall.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-free_promotion.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-free_waf.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-freefrp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-freefrpc.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-frp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-frpc.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-frps.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-frps_simple.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ftp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ftplimit.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-gcloud_storage.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-gdrive.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-gepfree.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-git_deploy.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-git_repository_deploy.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-gitlab.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-godaddydns.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-hash_pk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-hm_shell_san.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-host_login.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-hwobs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-hxsquota.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-image_compress.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-imgtool.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-imgtools.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ip_configuration.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-java_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-jdcloud.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-jdk_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-jdoss.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-jexus.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-jimjiami.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-jumpserver.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-kafka.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-linksforbt.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-linuxsys.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-load_balance.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-load_leveling.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-lotusweblog.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-lzfhdwz.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mail_sys.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-masterslave.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-memcached.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfboot.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfftpdev.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mflogview.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfngxwaf.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfsearch.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfsmbd.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfwatermark.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mfwebdav.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-modbtywaf.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mongodb.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-monitor.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mounter.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-msg_push.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-msonedrive.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-my_toolbox.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-myhaproxy.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mysql.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mysql_replicate.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-mysqlfind.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-netdisk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-new_gitlab.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-nfs_tools.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-nfsgo.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-nginx.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-node_admin.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-nodejs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-obs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-oneav.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-oos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-openlitespeed.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-openrasp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-openssl.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ossfs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-osvulintelligence.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ouken_helper_goto_dir.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-panel_jsbridge_installer.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-pgsql_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-php-5.6.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-php-8.1.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-php.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-php_encoder.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-php_filter.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-phpencoder.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-phpguard.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-phpmyadmin.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-phpok.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-phtunnel.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-pm2.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-portblast.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-postfix.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-psync_api.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-publicwelfare404.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-pureftpd.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-pysqliteadmin.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-pythonmamager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-qiankeji.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-qiniu.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-qnoss.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-qrcode.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-rabbitmq.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-redis.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-redisutil.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-resource_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-rsync.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-safelogin.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-san_security.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-score.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-sdetection.svg create mode 100644 BTPanel/static/vite/images/soft-ico/ico-security_notice.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-seos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-seotip.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-server_port_watcher.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-server_proxy_res.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-servicehot.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-shorturl.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-site_cluster.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-site_optimization.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-site_speed.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-sitemap.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-socks5.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-sphinx.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-srs_cloud.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ssh_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-sshkey.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ssl_verify.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-ssp.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-static_cdn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-su_baidu.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-supervisor.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-svn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-svn_deploy.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-swn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-sysdisk.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-syssafe.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-system_scan.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tamper_core.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tamper_drive.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tamper_proof.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-task_manager.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tencent_cdn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tomcat.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tomcat2.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-total.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tpl_hyaline.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-tuboshufenxi.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-txcdn.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-txcos.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-txoss.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-typechomg.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-upyun.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-urgoaccess.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-urlpush.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-us3.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-users.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-vuln_push.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-w7assistant.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-wdcp_bt.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-webhook.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-webshell.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-webshell_check.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-webssh.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-wechatcheck.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-west_fss.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-whois.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-word.svg create mode 100644 BTPanel/static/vite/images/soft-ico/ico-wxapijnoocom.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-xjTools.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-xshark.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-y6w_createwebs.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-y6w_speedtest.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-yb_wm.png create mode 100644 BTPanel/static/vite/images/soft-ico/ico-yichaxin.png create mode 100644 BTPanel/static/vite/images/soft-ico/soft_icoico-docker_project_m.png create mode 100644 BTPanel/static/vite/images/soft-ico/soft_icoico-vuln_push.png create mode 100644 BTPanel/static/vite/images/update-back.png create mode 100644 BTPanel/static/vite/images/update-bg.png create mode 100644 BTPanel/static/vite/images/update-rocket.svg create mode 100644 BTPanel/static/vite/js/Alert-legacy.js create mode 100644 BTPanel/static/vite/js/Alert.js create mode 100644 BTPanel/static/vite/js/ButtonGroup-legacy.js create mode 100644 BTPanel/static/vite/js/ButtonGroup.js create mode 100644 BTPanel/static/vite/js/Checkbox-legacy.js create mode 100644 BTPanel/static/vite/js/Checkbox.js create mode 100644 BTPanel/static/vite/js/Countdown-legacy.js create mode 100644 BTPanel/static/vite/js/Countdown.js create mode 100644 BTPanel/static/vite/js/DataTable-legacy.js create mode 100644 BTPanel/static/vite/js/DataTable.js create mode 100644 BTPanel/static/vite/js/DatePicker-legacy.js create mode 100644 BTPanel/static/vite/js/DatePicker.js create mode 100644 BTPanel/static/vite/js/Ellipsis-legacy.js create mode 100644 BTPanel/static/vite/js/Ellipsis.js create mode 100644 BTPanel/static/vite/js/Empty-legacy.js create mode 100644 BTPanel/static/vite/js/Empty.js create mode 100644 BTPanel/static/vite/js/InputGroup-legacy.js create mode 100644 BTPanel/static/vite/js/InputGroup.js create mode 100644 BTPanel/static/vite/js/InputGroupLabel-legacy.js create mode 100644 BTPanel/static/vite/js/InputGroupLabel.js create mode 100644 BTPanel/static/vite/js/Progress-legacy.js create mode 100644 BTPanel/static/vite/js/Progress.js create mode 100644 BTPanel/static/vite/js/RadioButton-legacy.js create mode 100644 BTPanel/static/vite/js/RadioButton.js create mode 100644 BTPanel/static/vite/js/Select-legacy.js create mode 100644 BTPanel/static/vite/js/Select.js create mode 100644 BTPanel/static/vite/js/Skeleton-legacy.js create mode 100644 BTPanel/static/vite/js/Skeleton.js create mode 100644 BTPanel/static/vite/js/Space-legacy.js create mode 100644 BTPanel/static/vite/js/Space.js create mode 100644 BTPanel/static/vite/js/Spin-legacy.js create mode 100644 BTPanel/static/vite/js/Spin.js create mode 100644 BTPanel/static/vite/js/Tabs-legacy.js create mode 100644 BTPanel/static/vite/js/Tabs.js create mode 100644 BTPanel/static/vite/js/Tag-legacy.js create mode 100644 BTPanel/static/vite/js/Tag.js create mode 100644 BTPanel/static/vite/js/_baseFindIndex-legacy.js create mode 100644 BTPanel/static/vite/js/_baseFindIndex.js create mode 100644 BTPanel/static/vite/js/_baseUniq-legacy.js create mode 100644 BTPanel/static/vite/js/_baseUniq.js create mode 100644 BTPanel/static/vite/js/_createCompounder-legacy.js create mode 100644 BTPanel/static/vite/js/_createCompounder.js create mode 100644 BTPanel/static/vite/js/batch-legacy.js create mode 100644 BTPanel/static/vite/js/batch-legacy2.js create mode 100644 BTPanel/static/vite/js/batch-legacy3.js create mode 100644 BTPanel/static/vite/js/batch.js create mode 100644 BTPanel/static/vite/js/batch2.js create mode 100644 BTPanel/static/vite/js/batch3.js create mode 100644 BTPanel/static/vite/js/capitalize-legacy.js create mode 100644 BTPanel/static/vite/js/capitalize.js create mode 100644 BTPanel/static/vite/js/check-legacy.js create mode 100644 BTPanel/static/vite/js/check.js create mode 100644 BTPanel/static/vite/js/color-legacy.js create mode 100644 BTPanel/static/vite/js/color.js create mode 100644 BTPanel/static/vite/js/columns-legacy.js create mode 100644 BTPanel/static/vite/js/columns.js create mode 100644 BTPanel/static/vite/js/data-legacy.js create mode 100644 BTPanel/static/vite/js/data.js create mode 100644 BTPanel/static/vite/js/details-legacy.js create mode 100644 BTPanel/static/vite/js/details.js create mode 100644 BTPanel/static/vite/js/file-legacy.js create mode 100644 BTPanel/static/vite/js/file-legacy2.js create mode 100644 BTPanel/static/vite/js/file-version.js create mode 100644 BTPanel/static/vite/js/file.js create mode 100644 BTPanel/static/vite/js/file2.js create mode 100644 BTPanel/static/vite/js/form-legacy.js create mode 100644 BTPanel/static/vite/js/form.js create mode 100644 BTPanel/static/vite/js/form.vue_vue_type_script_setup_true_lang-legacy.js create mode 100644 BTPanel/static/vite/js/form.vue_vue_type_script_setup_true_lang-legacy2.js create mode 100644 BTPanel/static/vite/js/form.vue_vue_type_script_setup_true_lang.js create mode 100644 BTPanel/static/vite/js/form.vue_vue_type_script_setup_true_lang2.js create mode 100644 BTPanel/static/vite/js/hooks-legacy.js create mode 100644 BTPanel/static/vite/js/hooks.js create mode 100644 BTPanel/static/vite/js/index-legacy.js create mode 100644 BTPanel/static/vite/js/index-legacy10.js create mode 100644 BTPanel/static/vite/js/index-legacy100.js create mode 100644 BTPanel/static/vite/js/index-legacy101.js create mode 100644 BTPanel/static/vite/js/index-legacy102.js create mode 100644 BTPanel/static/vite/js/index-legacy103.js create mode 100644 BTPanel/static/vite/js/index-legacy104.js create mode 100644 BTPanel/static/vite/js/index-legacy105.js create mode 100644 BTPanel/static/vite/js/index-legacy106.js create mode 100644 BTPanel/static/vite/js/index-legacy107.js create mode 100644 BTPanel/static/vite/js/index-legacy108.js create mode 100644 BTPanel/static/vite/js/index-legacy109.js create mode 100644 BTPanel/static/vite/js/index-legacy11.js create mode 100644 BTPanel/static/vite/js/index-legacy110.js create mode 100644 BTPanel/static/vite/js/index-legacy111.js create mode 100644 BTPanel/static/vite/js/index-legacy112.js create mode 100644 BTPanel/static/vite/js/index-legacy113.js create mode 100644 BTPanel/static/vite/js/index-legacy114.js create mode 100644 BTPanel/static/vite/js/index-legacy115.js create mode 100644 BTPanel/static/vite/js/index-legacy116.js create mode 100644 BTPanel/static/vite/js/index-legacy117.js create mode 100644 BTPanel/static/vite/js/index-legacy118.js create mode 100644 BTPanel/static/vite/js/index-legacy119.js create mode 100644 BTPanel/static/vite/js/index-legacy12.js create mode 100644 BTPanel/static/vite/js/index-legacy120.js create mode 100644 BTPanel/static/vite/js/index-legacy121.js create mode 100644 BTPanel/static/vite/js/index-legacy122.js create mode 100644 BTPanel/static/vite/js/index-legacy123.js create mode 100644 BTPanel/static/vite/js/index-legacy124.js create mode 100644 BTPanel/static/vite/js/index-legacy125.js create mode 100644 BTPanel/static/vite/js/index-legacy126.js create mode 100644 BTPanel/static/vite/js/index-legacy127.js create mode 100644 BTPanel/static/vite/js/index-legacy128.js create mode 100644 BTPanel/static/vite/js/index-legacy129.js create mode 100644 BTPanel/static/vite/js/index-legacy13.js create mode 100644 BTPanel/static/vite/js/index-legacy130.js create mode 100644 BTPanel/static/vite/js/index-legacy131.js create mode 100644 BTPanel/static/vite/js/index-legacy132.js create mode 100644 BTPanel/static/vite/js/index-legacy133.js create mode 100644 BTPanel/static/vite/js/index-legacy134.js create mode 100644 BTPanel/static/vite/js/index-legacy135.js create mode 100644 BTPanel/static/vite/js/index-legacy136.js create mode 100644 BTPanel/static/vite/js/index-legacy137.js create mode 100644 BTPanel/static/vite/js/index-legacy138.js create mode 100644 BTPanel/static/vite/js/index-legacy139.js create mode 100644 BTPanel/static/vite/js/index-legacy14.js create mode 100644 BTPanel/static/vite/js/index-legacy140.js create mode 100644 BTPanel/static/vite/js/index-legacy141.js create mode 100644 BTPanel/static/vite/js/index-legacy142.js create mode 100644 BTPanel/static/vite/js/index-legacy143.js create mode 100644 BTPanel/static/vite/js/index-legacy144.js create mode 100644 BTPanel/static/vite/js/index-legacy145.js create mode 100644 BTPanel/static/vite/js/index-legacy146.js create mode 100644 BTPanel/static/vite/js/index-legacy147.js create mode 100644 BTPanel/static/vite/js/index-legacy148.js create mode 100644 BTPanel/static/vite/js/index-legacy149.js create mode 100644 BTPanel/static/vite/js/index-legacy15.js create mode 100644 BTPanel/static/vite/js/index-legacy16.js create mode 100644 BTPanel/static/vite/js/index-legacy17.js create mode 100644 BTPanel/static/vite/js/index-legacy18.js create mode 100644 BTPanel/static/vite/js/index-legacy19.js create mode 100644 BTPanel/static/vite/js/index-legacy2.js create mode 100644 BTPanel/static/vite/js/index-legacy20.js create mode 100644 BTPanel/static/vite/js/index-legacy21.js create mode 100644 BTPanel/static/vite/js/index-legacy22.js create mode 100644 BTPanel/static/vite/js/index-legacy23.js create mode 100644 BTPanel/static/vite/js/index-legacy24.js create mode 100644 BTPanel/static/vite/js/index-legacy25.js create mode 100644 BTPanel/static/vite/js/index-legacy26.js create mode 100644 BTPanel/static/vite/js/index-legacy27.js create mode 100644 BTPanel/static/vite/js/index-legacy28.js create mode 100644 BTPanel/static/vite/js/index-legacy29.js create mode 100644 BTPanel/static/vite/js/index-legacy3.js create mode 100644 BTPanel/static/vite/js/index-legacy30.js create mode 100644 BTPanel/static/vite/js/index-legacy31.js create mode 100644 BTPanel/static/vite/js/index-legacy32.js create mode 100644 BTPanel/static/vite/js/index-legacy33.js create mode 100644 BTPanel/static/vite/js/index-legacy34.js create mode 100644 BTPanel/static/vite/js/index-legacy35.js create mode 100644 BTPanel/static/vite/js/index-legacy36.js create mode 100644 BTPanel/static/vite/js/index-legacy37.js create mode 100644 BTPanel/static/vite/js/index-legacy38.js create mode 100644 BTPanel/static/vite/js/index-legacy39.js create mode 100644 BTPanel/static/vite/js/index-legacy4.js create mode 100644 BTPanel/static/vite/js/index-legacy40.js create mode 100644 BTPanel/static/vite/js/index-legacy41.js create mode 100644 BTPanel/static/vite/js/index-legacy42.js create mode 100644 BTPanel/static/vite/js/index-legacy43.js create mode 100644 BTPanel/static/vite/js/index-legacy44.js create mode 100644 BTPanel/static/vite/js/index-legacy45.js create mode 100644 BTPanel/static/vite/js/index-legacy46.js create mode 100644 BTPanel/static/vite/js/index-legacy47.js create mode 100644 BTPanel/static/vite/js/index-legacy48.js create mode 100644 BTPanel/static/vite/js/index-legacy49.js create mode 100644 BTPanel/static/vite/js/index-legacy5.js create mode 100644 BTPanel/static/vite/js/index-legacy50.js create mode 100644 BTPanel/static/vite/js/index-legacy51.js create mode 100644 BTPanel/static/vite/js/index-legacy52.js create mode 100644 BTPanel/static/vite/js/index-legacy53.js create mode 100644 BTPanel/static/vite/js/index-legacy54.js create mode 100644 BTPanel/static/vite/js/index-legacy55.js create mode 100644 BTPanel/static/vite/js/index-legacy56.js create mode 100644 BTPanel/static/vite/js/index-legacy57.js create mode 100644 BTPanel/static/vite/js/index-legacy58.js create mode 100644 BTPanel/static/vite/js/index-legacy59.js create mode 100644 BTPanel/static/vite/js/index-legacy6.js create mode 100644 BTPanel/static/vite/js/index-legacy60.js create mode 100644 BTPanel/static/vite/js/index-legacy61.js create mode 100644 BTPanel/static/vite/js/index-legacy62.js create mode 100644 BTPanel/static/vite/js/index-legacy63.js create mode 100644 BTPanel/static/vite/js/index-legacy64.js create mode 100644 BTPanel/static/vite/js/index-legacy65.js create mode 100644 BTPanel/static/vite/js/index-legacy66.js create mode 100644 BTPanel/static/vite/js/index-legacy67.js create mode 100644 BTPanel/static/vite/js/index-legacy68.js create mode 100644 BTPanel/static/vite/js/index-legacy69.js create mode 100644 BTPanel/static/vite/js/index-legacy7.js create mode 100644 BTPanel/static/vite/js/index-legacy70.js create mode 100644 BTPanel/static/vite/js/index-legacy71.js create mode 100644 BTPanel/static/vite/js/index-legacy72.js create mode 100644 BTPanel/static/vite/js/index-legacy73.js create mode 100644 BTPanel/static/vite/js/index-legacy74.js create mode 100644 BTPanel/static/vite/js/index-legacy75.js create mode 100644 BTPanel/static/vite/js/index-legacy76.js create mode 100644 BTPanel/static/vite/js/index-legacy77.js create mode 100644 BTPanel/static/vite/js/index-legacy78.js create mode 100644 BTPanel/static/vite/js/index-legacy79.js create mode 100644 BTPanel/static/vite/js/index-legacy8.js create mode 100644 BTPanel/static/vite/js/index-legacy80.js create mode 100644 BTPanel/static/vite/js/index-legacy81.js create mode 100644 BTPanel/static/vite/js/index-legacy82.js create mode 100644 BTPanel/static/vite/js/index-legacy83.js create mode 100644 BTPanel/static/vite/js/index-legacy84.js create mode 100644 BTPanel/static/vite/js/index-legacy85.js create mode 100644 BTPanel/static/vite/js/index-legacy86.js create mode 100644 BTPanel/static/vite/js/index-legacy87.js create mode 100644 BTPanel/static/vite/js/index-legacy88.js create mode 100644 BTPanel/static/vite/js/index-legacy89.js create mode 100644 BTPanel/static/vite/js/index-legacy9.js create mode 100644 BTPanel/static/vite/js/index-legacy90.js create mode 100644 BTPanel/static/vite/js/index-legacy91.js create mode 100644 BTPanel/static/vite/js/index-legacy92.js create mode 100644 BTPanel/static/vite/js/index-legacy93.js create mode 100644 BTPanel/static/vite/js/index-legacy94.js create mode 100644 BTPanel/static/vite/js/index-legacy95.js create mode 100644 BTPanel/static/vite/js/index-legacy96.js create mode 100644 BTPanel/static/vite/js/index-legacy97.js create mode 100644 BTPanel/static/vite/js/index-legacy98.js create mode 100644 BTPanel/static/vite/js/index-legacy99.js create mode 100644 BTPanel/static/vite/js/index.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy10.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy11.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy12.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy13.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy14.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy15.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy16.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy17.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy18.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy2.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy3.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy4.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy5.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy6.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy7.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy8.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang-legacy9.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang10.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang11.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang12.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang13.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang14.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang15.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang16.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang17.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang18.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang2.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang3.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang4.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang5.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang6.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang7.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang8.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_script_setup_true_lang9.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_style_index_0_lang-legacy.js create mode 100644 BTPanel/static/vite/js/index.vue_vue_type_style_index_0_lang.js create mode 100644 BTPanel/static/vite/js/index10.js create mode 100644 BTPanel/static/vite/js/index100.js create mode 100644 BTPanel/static/vite/js/index101.js create mode 100644 BTPanel/static/vite/js/index102.js create mode 100644 BTPanel/static/vite/js/index103.js create mode 100644 BTPanel/static/vite/js/index104.js create mode 100644 BTPanel/static/vite/js/index105.js create mode 100644 BTPanel/static/vite/js/index106.js create mode 100644 BTPanel/static/vite/js/index107.js create mode 100644 BTPanel/static/vite/js/index108.js create mode 100644 BTPanel/static/vite/js/index109.js create mode 100644 BTPanel/static/vite/js/index11.js create mode 100644 BTPanel/static/vite/js/index110.js create mode 100644 BTPanel/static/vite/js/index111.js create mode 100644 BTPanel/static/vite/js/index112.js create mode 100644 BTPanel/static/vite/js/index113.js create mode 100644 BTPanel/static/vite/js/index114.js create mode 100644 BTPanel/static/vite/js/index115.js create mode 100644 BTPanel/static/vite/js/index116.js create mode 100644 BTPanel/static/vite/js/index117.js create mode 100644 BTPanel/static/vite/js/index118.js create mode 100644 BTPanel/static/vite/js/index119.js create mode 100644 BTPanel/static/vite/js/index12.js create mode 100644 BTPanel/static/vite/js/index120.js create mode 100644 BTPanel/static/vite/js/index121.js create mode 100644 BTPanel/static/vite/js/index122.js create mode 100644 BTPanel/static/vite/js/index123.js create mode 100644 BTPanel/static/vite/js/index124.js create mode 100644 BTPanel/static/vite/js/index125.js create mode 100644 BTPanel/static/vite/js/index126.js create mode 100644 BTPanel/static/vite/js/index127.js create mode 100644 BTPanel/static/vite/js/index128.js create mode 100644 BTPanel/static/vite/js/index129.js create mode 100644 BTPanel/static/vite/js/index13.js create mode 100644 BTPanel/static/vite/js/index130.js create mode 100644 BTPanel/static/vite/js/index131.js create mode 100644 BTPanel/static/vite/js/index132.js create mode 100644 BTPanel/static/vite/js/index133.js create mode 100644 BTPanel/static/vite/js/index134.js create mode 100644 BTPanel/static/vite/js/index135.js create mode 100644 BTPanel/static/vite/js/index136.js create mode 100644 BTPanel/static/vite/js/index137.js create mode 100644 BTPanel/static/vite/js/index138.js create mode 100644 BTPanel/static/vite/js/index139.js create mode 100644 BTPanel/static/vite/js/index14.js create mode 100644 BTPanel/static/vite/js/index140.js create mode 100644 BTPanel/static/vite/js/index141.js create mode 100644 BTPanel/static/vite/js/index142.js create mode 100644 BTPanel/static/vite/js/index143.js create mode 100644 BTPanel/static/vite/js/index144.js create mode 100644 BTPanel/static/vite/js/index145.js create mode 100644 BTPanel/static/vite/js/index146.js create mode 100644 BTPanel/static/vite/js/index147.js create mode 100644 BTPanel/static/vite/js/index148.js create mode 100644 BTPanel/static/vite/js/index149.js create mode 100644 BTPanel/static/vite/js/index15.js create mode 100644 BTPanel/static/vite/js/index16.js create mode 100644 BTPanel/static/vite/js/index17.js create mode 100644 BTPanel/static/vite/js/index18.js create mode 100644 BTPanel/static/vite/js/index19.js create mode 100644 BTPanel/static/vite/js/index2.js create mode 100644 BTPanel/static/vite/js/index20.js create mode 100644 BTPanel/static/vite/js/index21.js create mode 100644 BTPanel/static/vite/js/index22.js create mode 100644 BTPanel/static/vite/js/index23.js create mode 100644 BTPanel/static/vite/js/index24.js create mode 100644 BTPanel/static/vite/js/index25.js create mode 100644 BTPanel/static/vite/js/index26.js create mode 100644 BTPanel/static/vite/js/index27.js create mode 100644 BTPanel/static/vite/js/index28.js create mode 100644 BTPanel/static/vite/js/index29.js create mode 100644 BTPanel/static/vite/js/index3.js create mode 100644 BTPanel/static/vite/js/index30.js create mode 100644 BTPanel/static/vite/js/index31.js create mode 100644 BTPanel/static/vite/js/index32.js create mode 100644 BTPanel/static/vite/js/index33.js create mode 100644 BTPanel/static/vite/js/index34.js create mode 100644 BTPanel/static/vite/js/index35.js create mode 100644 BTPanel/static/vite/js/index36.js create mode 100644 BTPanel/static/vite/js/index37.js create mode 100644 BTPanel/static/vite/js/index38.js create mode 100644 BTPanel/static/vite/js/index39.js create mode 100644 BTPanel/static/vite/js/index4.js create mode 100644 BTPanel/static/vite/js/index40.js create mode 100644 BTPanel/static/vite/js/index41.js create mode 100644 BTPanel/static/vite/js/index42.js create mode 100644 BTPanel/static/vite/js/index43.js create mode 100644 BTPanel/static/vite/js/index44.js create mode 100644 BTPanel/static/vite/js/index45.js create mode 100644 BTPanel/static/vite/js/index46.js create mode 100644 BTPanel/static/vite/js/index47.js create mode 100644 BTPanel/static/vite/js/index48.js create mode 100644 BTPanel/static/vite/js/index49.js create mode 100644 BTPanel/static/vite/js/index5.js create mode 100644 BTPanel/static/vite/js/index50.js create mode 100644 BTPanel/static/vite/js/index51.js create mode 100644 BTPanel/static/vite/js/index52.js create mode 100644 BTPanel/static/vite/js/index53.js create mode 100644 BTPanel/static/vite/js/index54.js create mode 100644 BTPanel/static/vite/js/index55.js create mode 100644 BTPanel/static/vite/js/index56.js create mode 100644 BTPanel/static/vite/js/index57.js create mode 100644 BTPanel/static/vite/js/index58.js create mode 100644 BTPanel/static/vite/js/index59.js create mode 100644 BTPanel/static/vite/js/index6.js create mode 100644 BTPanel/static/vite/js/index60.js create mode 100644 BTPanel/static/vite/js/index61.js create mode 100644 BTPanel/static/vite/js/index62.js create mode 100644 BTPanel/static/vite/js/index63.js create mode 100644 BTPanel/static/vite/js/index64.js create mode 100644 BTPanel/static/vite/js/index65.js create mode 100644 BTPanel/static/vite/js/index66.js create mode 100644 BTPanel/static/vite/js/index67.js create mode 100644 BTPanel/static/vite/js/index68.js create mode 100644 BTPanel/static/vite/js/index69.js create mode 100644 BTPanel/static/vite/js/index7.js create mode 100644 BTPanel/static/vite/js/index70.js create mode 100644 BTPanel/static/vite/js/index71.js create mode 100644 BTPanel/static/vite/js/index72.js create mode 100644 BTPanel/static/vite/js/index73.js create mode 100644 BTPanel/static/vite/js/index74.js create mode 100644 BTPanel/static/vite/js/index75.js create mode 100644 BTPanel/static/vite/js/index76.js create mode 100644 BTPanel/static/vite/js/index77.js create mode 100644 BTPanel/static/vite/js/index78.js create mode 100644 BTPanel/static/vite/js/index79.js create mode 100644 BTPanel/static/vite/js/index8.js create mode 100644 BTPanel/static/vite/js/index80.js create mode 100644 BTPanel/static/vite/js/index81.js create mode 100644 BTPanel/static/vite/js/index82.js create mode 100644 BTPanel/static/vite/js/index83.js create mode 100644 BTPanel/static/vite/js/index84.js create mode 100644 BTPanel/static/vite/js/index85.js create mode 100644 BTPanel/static/vite/js/index86.js create mode 100644 BTPanel/static/vite/js/index87.js create mode 100644 BTPanel/static/vite/js/index88.js create mode 100644 BTPanel/static/vite/js/index89.js create mode 100644 BTPanel/static/vite/js/index9.js create mode 100644 BTPanel/static/vite/js/index90.js create mode 100644 BTPanel/static/vite/js/index91.js create mode 100644 BTPanel/static/vite/js/index92.js create mode 100644 BTPanel/static/vite/js/index93.js create mode 100644 BTPanel/static/vite/js/index94.js create mode 100644 BTPanel/static/vite/js/index95.js create mode 100644 BTPanel/static/vite/js/index96.js create mode 100644 BTPanel/static/vite/js/index97.js create mode 100644 BTPanel/static/vite/js/index98.js create mode 100644 BTPanel/static/vite/js/index99.js create mode 100644 BTPanel/static/vite/js/mail-legacy.js create mode 100644 BTPanel/static/vite/js/mail.js create mode 100644 BTPanel/static/vite/js/new-legacy.js create mode 100644 BTPanel/static/vite/js/new.js create mode 100644 BTPanel/static/vite/js/old-legacy.js create mode 100644 BTPanel/static/vite/js/old-legacy2.js create mode 100644 BTPanel/static/vite/js/old-legacy3.js create mode 100644 BTPanel/static/vite/js/old-legacy4.js create mode 100644 BTPanel/static/vite/js/old.js create mode 100644 BTPanel/static/vite/js/old2.js create mode 100644 BTPanel/static/vite/js/old3.js create mode 100644 BTPanel/static/vite/js/old4.js create mode 100644 BTPanel/static/vite/js/omit-legacy.js create mode 100644 BTPanel/static/vite/js/omit.js create mode 100644 BTPanel/static/vite/js/php-legacy.js create mode 100644 BTPanel/static/vite/js/php.js create mode 100644 BTPanel/static/vite/js/pinia-legacy.js create mode 100644 BTPanel/static/vite/js/pinia.js create mode 100644 BTPanel/static/vite/js/polyfills-legacy.js create mode 100644 BTPanel/static/vite/js/quota-config-legacy.js create mode 100644 BTPanel/static/vite/js/quota-config.js create mode 100644 BTPanel/static/vite/js/refersh-legacy.js create mode 100644 BTPanel/static/vite/js/refersh.js create mode 100644 BTPanel/static/vite/js/rewrite-legacy.js create mode 100644 BTPanel/static/vite/js/rewrite.js create mode 100644 BTPanel/static/vite/js/set-legacy.js create mode 100644 BTPanel/static/vite/js/set.js create mode 100644 BTPanel/static/vite/js/site-legacy.js create mode 100644 BTPanel/static/vite/js/site-legacy2.js create mode 100644 BTPanel/static/vite/js/site.js create mode 100644 BTPanel/static/vite/js/site2.js create mode 100644 BTPanel/static/vite/js/submit-legacy.js create mode 100644 BTPanel/static/vite/js/submit.js create mode 100644 BTPanel/static/vite/js/table.vue_vue_type_script_setup_true_lang-legacy.js create mode 100644 BTPanel/static/vite/js/table.vue_vue_type_script_setup_true_lang.js create mode 100644 BTPanel/static/vite/js/uniq-legacy.js create mode 100644 BTPanel/static/vite/js/uniq.js create mode 100644 BTPanel/static/vite/js/update-back-legacy.js create mode 100644 BTPanel/static/vite/js/update-back.js create mode 100644 BTPanel/static/vite/js/use-theme-vars-legacy.js create mode 100644 BTPanel/static/vite/js/use-theme-vars.js create mode 100644 BTPanel/static/vite/js/useLoading-legacy.js create mode 100644 BTPanel/static/vite/js/useLoading.js create mode 100644 BTPanel/static/vite/js/useLoop-legacy.js create mode 100644 BTPanel/static/vite/js/useLoop.js create mode 100644 BTPanel/static/vite/js/useSocket-legacy.js create mode 100644 BTPanel/static/vite/js/useSocket.js create mode 100644 BTPanel/static/vite/js/useTableColumns-legacy.js create mode 100644 BTPanel/static/vite/js/useTableColumns.js create mode 100644 BTPanel/static/vite/js/useTableData-legacy.js create mode 100644 BTPanel/static/vite/js/useTableData.js create mode 100644 BTPanel/static/vite/js/vue-legacy.js create mode 100644 BTPanel/static/vite/js/vue.js create mode 100644 BTPanel/static/vite/lang/en-US/database.json create mode 100644 BTPanel/static/vite/lang/en-US/docker.json create mode 100644 BTPanel/static/vite/lang/en-US/ftp.json create mode 100644 BTPanel/static/vite/lang/en-US/global.json create mode 100644 BTPanel/static/vite/lang/en-US/home.json create mode 100644 BTPanel/static/vite/lang/en-US/mail.json create mode 100644 BTPanel/static/vite/lang/en-US/public.json create mode 100644 BTPanel/static/vite/lang/en-US/site.json create mode 100644 BTPanel/static/vite/lang/en-US/wp.json create mode 100644 BTPanel/static/vite/oldjs/bt_upload.js create mode 100644 BTPanel/static/vite/oldjs/clipboard.min.js create mode 100644 BTPanel/static/vite/oldjs/control.js create mode 100644 BTPanel/static/vite/oldjs/crontab.js create mode 100644 BTPanel/static/vite/oldjs/docker.js create mode 100644 BTPanel/static/vite/oldjs/echarts.min.js create mode 100644 BTPanel/static/vite/oldjs/files.js create mode 100644 BTPanel/static/vite/oldjs/firewall.js create mode 100644 BTPanel/static/vite/oldjs/index.js create mode 100644 BTPanel/static/vite/oldjs/jquery-2.2.4.min.js create mode 100644 BTPanel/static/vite/oldjs/jquery.dragsort-0.5.2.min.js create mode 100644 BTPanel/static/vite/oldjs/jquery.qrcode.min.js create mode 100644 BTPanel/static/vite/oldjs/jsencrypt.min.js create mode 100644 BTPanel/static/vite/oldjs/logs.js create mode 100644 BTPanel/static/vite/oldjs/polyfill.min.js create mode 100644 BTPanel/static/vite/oldjs/public.js create mode 100644 BTPanel/static/vite/oldjs/public_backup.js create mode 100644 BTPanel/static/vite/oldjs/site.js create mode 100644 BTPanel/static/vite/oldjs/soft.js create mode 100644 BTPanel/static/vite/oldjs/term.js create mode 100644 BTPanel/static/vite/oldjs/tools.js create mode 100644 BTPanel/static/vite/oldjs/upload-drog.js create mode 100644 BTPanel/static/vite/oldjs/xterm.js create mode 100644 BTPanel/templates/default/index_new.html create mode 100644 check-pip-packs.txt create mode 100644 class/PluginLoader.aarch64.Python3.12.so create mode 100644 class/PluginLoader.x86_64.Python3.12.so create mode 100644 class/firewallModel/app/appBase.py create mode 100644 class/firewallModel/app/firewalld.py create mode 100644 class/firewallModel/app/iptables.py create mode 100644 class/firewallModel/app/ufw.py create mode 100644 class/firewallModel/comModel.py create mode 100644 class/firewallModel/firewallBase.py create mode 100644 class/panelModel/publicModel.py create mode 100644 class/public/PluginLoader.py create mode 100644 class/public/__init__.py create mode 100644 class/public/common.py create mode 100644 class/public/exceptions.py create mode 100644 class/public/mysqlmgr.py create mode 100644 class/public/regexplib.py create mode 100644 class/public/tools.py create mode 100644 class/public/translations.py create mode 100644 class/public/validate.py create mode 100644 class/public/websitemgr.py create mode 100644 class/ssl_info.py create mode 100644 class/userRegister.py create mode 100644 class/webserver.py create mode 100644 class_v2/acme_v3.py create mode 100644 class_v2/ajax_v2.py create mode 100644 class_v2/apache_v2.py create mode 100644 class_v2/backup_bak_v2.py create mode 100644 class_v2/btdockerModelV2/appModel.py create mode 100644 class_v2/btdockerModelV2/backupModel.py create mode 100644 class_v2/btdockerModelV2/composeModel.py create mode 100644 class_v2/btdockerModelV2/config/com_reg_mirror.json create mode 100644 class_v2/btdockerModelV2/config/com_registry.json create mode 100644 class_v2/btdockerModelV2/config/docker_hub_last_update.pl create mode 100644 class_v2/btdockerModelV2/config/docker_hub_repos.db create mode 100644 class_v2/btdockerModelV2/containerModel.py create mode 100644 class_v2/btdockerModelV2/dk_public.py create mode 100644 class_v2/btdockerModelV2/dkgroupModel_.py create mode 100644 class_v2/btdockerModelV2/dockerBase.py create mode 100644 class_v2/btdockerModelV2/dockerSock/__init__.py create mode 100644 class_v2/btdockerModelV2/dockerSock/container.py create mode 100644 class_v2/btdockerModelV2/dockerSock/image.py create mode 100644 class_v2/btdockerModelV2/dockerSock/sockBase.py create mode 100644 class_v2/btdockerModelV2/dockerSock/volume.py create mode 100644 class_v2/btdockerModelV2/host.py create mode 100644 class_v2/btdockerModelV2/imageModel.py create mode 100644 class_v2/btdockerModelV2/monitorModel.py create mode 100644 class_v2/btdockerModelV2/networkModel.py create mode 100644 class_v2/btdockerModelV2/projectModel.py create mode 100644 class_v2/btdockerModelV2/proxyModel.py create mode 100644 class_v2/btdockerModelV2/registryModel.py create mode 100644 class_v2/btdockerModelV2/screen.py create mode 100644 class_v2/btdockerModelV2/script/flush_plugin_.py create mode 100644 class_v2/btdockerModelV2/script/syncreposdb.py create mode 100644 class_v2/btdockerModelV2/securityModel.py create mode 100644 class_v2/btdockerModelV2/setupModel.py create mode 100644 class_v2/btdockerModelV2/statusModel.py create mode 100644 class_v2/btdockerModelV2/volumeModel.py create mode 100644 class_v2/common_v2.py create mode 100644 class_v2/config_v2.py create mode 100644 class_v2/crontab_ssl_v2.py create mode 100644 class_v2/crontab_v2.py create mode 100644 class_v2/data_v2.py create mode 100644 class_v2/databaseModelV2/__init__.py create mode 100644 class_v2/databaseModelV2/base.py create mode 100644 class_v2/databaseModelV2/mongodbModel.py create mode 100644 class_v2/databaseModelV2/pgsqlModel.py create mode 100644 class_v2/databaseModelV2/redisModel.py create mode 100644 class_v2/databaseModelV2/sqliteModel.py create mode 100644 class_v2/databaseModelV2/sqlserverModel.py create mode 100644 class_v2/database_v2.py create mode 100644 class_v2/datatool_v2.py create mode 100644 class_v2/db_mysql_v2.py create mode 100644 class_v2/db_v2.py create mode 100644 class_v2/dk_db.py create mode 100644 class_v2/download_file_v2.py create mode 100644 class_v2/fastcgi_client_two_v2.py create mode 100644 class_v2/fastcgi_client_v2.py create mode 100644 class_v2/file_execute_deny_v2.py create mode 100644 class_v2/files_v2.py create mode 100644 class_v2/firewallModelV2/app/appBase.py create mode 100644 class_v2/firewallModelV2/app/firewalld.py create mode 100644 class_v2/firewallModelV2/app/iptables.py create mode 100644 class_v2/firewallModelV2/app/ufw.py create mode 100644 class_v2/firewallModelV2/comModel.py create mode 100644 class_v2/firewallModelV2/firewallBase.py create mode 100644 class_v2/firewall_new_v2.py create mode 100644 class_v2/firewalld_v2.py create mode 100644 class_v2/firewalls_v2.py create mode 100644 class_v2/flask_compress_v2.py create mode 100644 class_v2/flask_sockets_v2.py create mode 100644 class_v2/ftp_log_v2.py create mode 100644 class_v2/ftp_v2.py create mode 100644 class_v2/http_requests_v2.py create mode 100644 class_v2/jobs_v2.py create mode 100644 class_v2/letsencrypt_v2.py create mode 100644 class_v2/log_analysis_v2.py create mode 100644 class_v2/logsModelV2/base.py create mode 100644 class_v2/logsModelV2/ftpModel.py create mode 100644 class_v2/logsModelV2/panelModel.py create mode 100644 class_v2/logsModelV2/siteModel.py create mode 100644 class_v2/monitorModelV2/base.py create mode 100644 class_v2/monitorModelV2/process_managementModel.py create mode 100644 class_v2/monitor_v2.py create mode 100644 class_v2/one_key_wp_v2.py create mode 100644 class_v2/panelControllerV2.py create mode 100644 class_v2/panelDatabaseControllerV2.py create mode 100644 class_v2/panelDockerControllerV2.py create mode 100644 class_v2/panelModControllerV2.py create mode 100644 class_v2/panelModelV2/backupModel.py create mode 100644 class_v2/panelModelV2/base.py create mode 100644 class_v2/panelModelV2/publicModel.py create mode 100644 class_v2/panelProjectControllerV2.py create mode 100644 class_v2/panelSafeControllerV2.py create mode 100644 class_v2/panel_api_v2.py create mode 100644 class_v2/panel_auth_v2.py create mode 100644 class_v2/panel_dns_api_v2.py create mode 100644 class_v2/panel_http_proxy_v2.py create mode 100644 class_v2/panel_lets_v2.py create mode 100644 class_v2/panel_mssql_v2.py create mode 100644 class_v2/panel_mysql_v2.py create mode 100644 class_v2/panel_php_v2.py create mode 100644 class_v2/panel_ping_v2.py create mode 100644 class_v2/panel_plugin_v2.py create mode 100644 class_v2/panel_push_v2.py create mode 100644 class_v2/panel_redirect_v2.py create mode 100644 class_v2/panel_restore_v2.py create mode 100644 class_v2/panel_site_v2.py create mode 100644 class_v2/panel_ssl_v2.py create mode 100644 class_v2/panel_task_v2.py create mode 100644 class_v2/panel_video_V2.py create mode 100644 class_v2/panel_warning_v2.py create mode 100644 class_v2/password_v2.py create mode 100644 class_v2/plugin_auth_v2.py create mode 100644 class_v2/plugin_deployment_v2.py create mode 100644 class_v2/projectModelV2/base.py create mode 100644 class_v2/projectModelV2/common/__init__.py create mode 100644 class_v2/projectModelV2/common/base.py create mode 100644 class_v2/projectModelV2/common/limit_net.py create mode 100644 class_v2/projectModelV2/common/redirect.py create mode 100644 class_v2/projectModelV2/dockerModel.py create mode 100644 class_v2/projectModelV2/nodejsModel.py create mode 100644 class_v2/projectModelV2/quotaModel.py create mode 100644 class_v2/safeModelV2/base.py create mode 100644 class_v2/safeModelV2/firewallModel.py create mode 100644 class_v2/safeModelV2/freeipModel.py create mode 100644 class_v2/safeModelV2/ipsModel.py create mode 100644 class_v2/safeModelV2/securityModel.py create mode 100644 class_v2/safeModelV2/sshModel.py create mode 100644 class_v2/safeModelV2/syslogModel.py create mode 100644 class_v2/safe_warning_v2/sw_alias_ls_rm.py create mode 100644 class_v2/safe_warning_v2/sw_audit_docker.py create mode 100644 class_v2/safe_warning_v2/sw_audit_log_keep.py create mode 100644 class_v2/safe_warning_v2/sw_bashrc.py create mode 100644 class_v2/safe_warning_v2/sw_bootloader_mod.py create mode 100644 class_v2/safe_warning_v2/sw_chmod_sid.py create mode 100644 class_v2/safe_warning_v2/sw_chmod_stickybit.py create mode 100644 class_v2/safe_warning_v2/sw_cshrc.py create mode 100644 class_v2/safe_warning_v2/sw_cve_2019_5736.py create mode 100644 class_v2/safe_warning_v2/sw_cve_2021_4034.py create mode 100644 class_v2/safe_warning_v2/sw_cve_2022_2068.py create mode 100644 class_v2/safe_warning_v2/sw_cve_2022_25845.py create mode 100644 class_v2/safe_warning_v2/sw_cve_2023_0386.py create mode 100644 class_v2/safe_warning_v2/sw_database_backup.py create mode 100644 class_v2/safe_warning_v2/sw_database_priv.py create mode 100644 class_v2/safe_warning_v2/sw_debug_mode.py create mode 100644 class_v2/safe_warning_v2/sw_dir_mode.py create mode 100644 class_v2/safe_warning_v2/sw_docker_api.py create mode 100644 class_v2/safe_warning_v2/sw_docker_mod.py create mode 100644 class_v2/safe_warning_v2/sw_empty_passwd_user.py create mode 100644 class_v2/safe_warning_v2/sw_file_lock.py create mode 100644 class_v2/safe_warning_v2/sw_file_mod.py create mode 100644 class_v2/safe_warning_v2/sw_files_recycle_bin.py create mode 100644 class_v2/safe_warning_v2/sw_firewall_open.py create mode 100644 class_v2/safe_warning_v2/sw_ftp_login.py create mode 100644 class_v2/safe_warning_v2/sw_ftp_pass.py create mode 100644 class_v2/safe_warning_v2/sw_ftp_root.py create mode 100644 class_v2/safe_warning_v2/sw_ftp_umask.py create mode 100644 class_v2/safe_warning_v2/sw_httpd_trace_enable.py create mode 100644 class_v2/safe_warning_v2/sw_httpd_version_leak.py create mode 100644 class_v2/safe_warning_v2/sw_kernel_space.py create mode 100644 class_v2/safe_warning_v2/sw_login_fail_limit.py create mode 100644 class_v2/safe_warning_v2/sw_login_message.py create mode 100644 class_v2/safe_warning_v2/sw_login_user.py create mode 100644 class_v2/safe_warning_v2/sw_memcached_port.py create mode 100644 class_v2/safe_warning_v2/sw_mongodb_auth.py create mode 100644 class_v2/safe_warning_v2/sw_mysql_pass.py create mode 100644 class_v2/safe_warning_v2/sw_mysql_port.py create mode 100644 class_v2/safe_warning_v2/sw_mysql_priv.py create mode 100644 class_v2/safe_warning_v2/sw_nginx_malware.py create mode 100644 class_v2/safe_warning_v2/sw_nginx_md5.py create mode 100644 class_v2/safe_warning_v2/sw_nginx_server.py create mode 100644 class_v2/safe_warning_v2/sw_panel_control.py create mode 100644 class_v2/safe_warning_v2/sw_panel_pass.py create mode 100644 class_v2/safe_warning_v2/sw_panel_path.py create mode 100644 class_v2/safe_warning_v2/sw_panel_port.py create mode 100644 class_v2/safe_warning_v2/sw_panel_swing.py create mode 100644 class_v2/safe_warning_v2/sw_passwd_repeat.py create mode 100644 class_v2/safe_warning_v2/sw_php_backdoor.py create mode 100644 class_v2/safe_warning_v2/sw_php_disable_functions.py create mode 100644 class_v2/safe_warning_v2/sw_php_display_errors.py create mode 100644 class_v2/safe_warning_v2/sw_php_expose.py create mode 100644 class_v2/safe_warning_v2/sw_ping.py create mode 100644 class_v2/safe_warning_v2/sw_pingin.py create mode 100644 class_v2/safe_warning_v2/sw_pip_poison.py create mode 100644 class_v2/safe_warning_v2/sw_protected_hardlinks.py create mode 100644 class_v2/safe_warning_v2/sw_protected_symlinks.py create mode 100644 class_v2/safe_warning_v2/sw_redis_pass.py create mode 100644 class_v2/safe_warning_v2/sw_redis_port.py create mode 100644 class_v2/safe_warning_v2/sw_risk_file.py create mode 100644 class_v2/safe_warning_v2/sw_site_logs.py create mode 100644 class_v2/safe_warning_v2/sw_site_spath.py create mode 100644 class_v2/safe_warning_v2/sw_site_ssl.py create mode 100644 class_v2/safe_warning_v2/sw_site_ssl_expire.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_clientalive.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_forward.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_hosts.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_login_grace.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_maxauth.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_minclass.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_notpass.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_passmax.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_passmin.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_port.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_root.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_security.py create mode 100644 class_v2/safe_warning_v2/sw_ssh_v2.py create mode 100644 class_v2/safe_warning_v2/sw_strace_backdoor.py create mode 100644 class_v2/safe_warning_v2/sw_su_root.py create mode 100644 class_v2/safe_warning_v2/sw_sudoers_nopasswd.py create mode 100644 class_v2/safe_warning_v2/sw_suid_dumpable.py create mode 100644 class_v2/safe_warning_v2/sw_system_user.py create mode 100644 class_v2/safe_warning_v2/sw_tcp_syn_cookie.py create mode 100644 class_v2/safe_warning_v2/sw_telnet_server.py create mode 100644 class_v2/safe_warning_v2/sw_time_out.py create mode 100644 class_v2/safe_warning_v2/sw_tmp_malware.py create mode 100644 class_v2/safe_warning_v2/sw_tomcat_pass.py create mode 100644 class_v2/safe_warning_v2/sw_umask.py create mode 100644 class_v2/safe_warning_v2/sw_waf_install.py create mode 100644 class_v2/san_baseline_v2.py create mode 100644 class_v2/site_dir_auth_v2.py create mode 100644 class_v2/ssh_security_v2.py create mode 100644 class_v2/ssh_terminal_v2.py create mode 100644 class_v2/system_v2.py create mode 100644 class_v2/user_login_v2.py create mode 100644 class_v2/vilidate_v2.py create mode 100644 class_v2/wp_toolkit/__init__.py create mode 100644 class_v2/wp_toolkit/core.py create mode 100644 class_v2/wp_toolkit/security.py create mode 100644 class_v2/wxapp_v2.py create mode 100644 config/GeoLite2-City.mmdb create mode 100644 config/databases.json create mode 100644 config/docker_project_info.json create mode 100644 config/php_versions.json create mode 100644 config/safe_autofix.json create mode 100644 mod/__init__.py create mode 100644 mod/base/__init__.py create mode 100644 mod/base/backup_tool/__init__.py create mode 100644 mod/base/backup_tool/util.py create mode 100644 mod/base/backup_tool/versions_tool.py create mode 100644 mod/base/database_tool/__init__.py create mode 100644 mod/base/database_tool/base.py create mode 100644 mod/base/database_tool/mongodb.py create mode 100644 mod/base/database_tool/mysql.py create mode 100644 mod/base/database_tool/pgsql.py create mode 100644 mod/base/database_tool/sql_server.py create mode 100644 mod/base/database_tool/util.py create mode 100644 mod/base/git_tool/__init__.py create mode 100644 mod/base/git_tool/install.py create mode 100644 mod/base/git_tool/install.sh create mode 100644 mod/base/git_tool/tool.py create mode 100644 mod/base/git_tool/util.py create mode 100644 mod/base/msg/__init__.py create mode 100644 mod/base/msg/dingding_msg.py create mode 100644 mod/base/msg/feishu_msg.py create mode 100644 mod/base/msg/mail_msg.py create mode 100644 mod/base/msg/manager.py create mode 100644 mod/base/msg/sms_msg.py create mode 100644 mod/base/msg/test.json create mode 100644 mod/base/msg/util.py create mode 100644 mod/base/msg/web_hook_msg.py create mode 100644 mod/base/msg/weixin_msg.py create mode 100644 mod/base/msg/wx_account_msg.py create mode 100644 mod/base/process/__init__.py create mode 100644 mod/base/process/process.py create mode 100644 mod/base/process/process_ps.json create mode 100644 mod/base/process/server.py create mode 100644 mod/base/process/user.py create mode 100644 mod/base/process/user_readme.text create mode 100644 mod/base/push_mod/__init__.py create mode 100644 mod/base/push_mod/base_task.py create mode 100644 mod/base/push_mod/compatible.py create mode 100644 mod/base/push_mod/database_push.py create mode 100644 mod/base/push_mod/database_push_template.json create mode 100644 mod/base/push_mod/load_push.py create mode 100644 mod/base/push_mod/manager.py create mode 100644 mod/base/push_mod/mods.py create mode 100644 mod/base/push_mod/rsync_push.py create mode 100644 mod/base/push_mod/send_tool.py create mode 100644 mod/base/push_mod/site_push.py create mode 100644 mod/base/push_mod/site_push_template.json create mode 100644 mod/base/push_mod/system.py create mode 100644 mod/base/push_mod/system_push.py create mode 100644 mod/base/push_mod/system_push_template.json create mode 100644 mod/base/push_mod/task_manager_push.py create mode 100644 mod/base/push_mod/tool.py create mode 100644 mod/base/push_mod/util.py create mode 100644 mod/base/web_conf/__init__.py create mode 100644 mod/base/web_conf/access_restriction.py create mode 100644 mod/base/web_conf/config_mgr.py create mode 100644 mod/base/web_conf/default_site.py create mode 100644 mod/base/web_conf/dir_tool.py create mode 100644 mod/base/web_conf/dns_api.py create mode 100644 mod/base/web_conf/domain_tool.py create mode 100644 mod/base/web_conf/ip_restrict.py create mode 100644 mod/base/web_conf/limit_net.py create mode 100644 mod/base/web_conf/logmanager.py create mode 100644 mod/base/web_conf/proxy.py create mode 100644 mod/base/web_conf/redirect.py create mode 100644 mod/base/web_conf/referer.py create mode 100644 mod/base/web_conf/ssl.py create mode 100644 mod/base/web_conf/util.py create mode 100644 mod/common/__init__.py create mode 100644 mod/common/base.py create mode 100644 mod/common/limit_net.py create mode 100644 mod/common/redirect.py create mode 100644 mod/modController.py create mode 100644 mod/project/docker/__init__.py create mode 100644 mod/project/docker/routetestMod.py create mode 100644 mod/project/java/__init__.py create mode 100644 mod/project/java/groupMod.py create mode 100644 mod/project/java/group_script.py create mode 100644 mod/project/java/java_web_conf.py create mode 100644 mod/project/java/jmxquery/JMXQuery-0.1.8.jar create mode 100644 mod/project/java/jmxquery/__init__.py create mode 100644 mod/project/java/projectMod.py create mode 100644 mod/project/java/project_update.py create mode 100644 mod/project/java/server_proxy.py create mode 100644 mod/project/java/springboot_parser.py create mode 100644 mod/project/java/utils.py create mode 100644 mod/project/php/__init__.py create mode 100644 mod/project/php/aepgMod.py create mode 100644 mod/project/php/php_asyncMod.py create mode 100644 mod/project/php/serviceconfMod.py create mode 100644 mod/project/proxy/__init__.py create mode 100644 mod/project/proxy/comMod.py create mode 100644 mod/project/push/__init__.py create mode 100644 mod/project/push/msgconfMod.py create mode 100644 mod/project/push/taskMod.py create mode 100644 mod/test/__init__.py create mode 100644 mod/test/docker/routetestModTest.py create mode 100644 mod/test/java/__init__.py create mode 100644 mod/test/java/test_spring_config.py create mode 100644 mod/test/java/test_sprintboot_mod.py create mode 100644 mod/test/java/test_util.py create mode 100644 mod/test/php/__init__.py create mode 100644 mod/test/php/test_php_asyncMod.py create mode 100644 mod/test/process/__init__.py create mode 100644 mod/test/process/test.py create mode 100644 mod/test/process/test_process.py create mode 100644 mod/test/process/test_server.py create mode 100644 mod/test/process/test_user.py create mode 100644 mod/test/test_backup_tool.py create mode 100644 mod/test/test_database_tool.py create mode 100644 mod/test/test_git/test_git_tool.py create mode 100644 mod/test/test_version_tool.py create mode 100644 mod/test/test_web_conf/__init__.py create mode 100644 mod/test/test_web_conf/test_access_restriction.py create mode 100644 mod/test/test_web_conf/test_config_mgr.py create mode 100644 mod/test/test_web_conf/test_default_site.py create mode 100644 mod/test/test_web_conf/test_dir_tool.py create mode 100644 mod/test/test_web_conf/test_dns_api.py create mode 100644 mod/test/test_web_conf/test_domain_tool.py create mode 100644 mod/test/test_web_conf/test_ip_restrict.py create mode 100644 mod/test/test_web_conf/test_logmanager.py create mode 100644 mod/test/test_web_conf/test_proxy.py create mode 100644 mod/test/test_web_conf/test_redirect.py create mode 100644 mod/test/test_web_conf/test_referer.py create mode 100644 mod/test/test_web_conf/test_ssl.py create mode 100644 script/Werkzeug-2.2.3-py3-none-any.whl create mode 100644 script/dk_log_split.py create mode 100644 script/webserver-ctl.sh create mode 100644 webserver/tpls/webserver.conf create mode 100644 webserver/tpls/webserver_listen.conf create mode 100644 webserver/tpls/webserver_listen_ssl.conf create mode 100644 webserver/tpls/webserver_ssl.conf diff --git a/BT-Panel b/BT-Panel index 2188b694..c75ae114 100644 --- a/BT-Panel +++ b/BT-Panel @@ -1,26 +1,36 @@ #!/www/server/panel/pyenv/bin/python #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- from gevent import monkey + + monkey.patch_all() -import os,sys,ssl,time,logging + + +import os +import sys +import ssl +import time +import logging +import psutil + _PATH = '/www/server/panel' os.chdir(_PATH) -os.system("nohup ./pyenv/bin/python3 class/jobs.py &>/dev/null &") -upgrade_file = 'script/upgrade_flask.sh' -if os.path.exists(upgrade_file): - os.system("nohup bash {} &>/dev/null &".format(upgrade_file)) -upgrade_file = 'script/upgrade_gevent.sh' -if os.path.exists(upgrade_file): - os.system("nohup bash {} &>/dev/null &".format(upgrade_file)) +# upgrade_file = 'script/upgrade_flask.sh' +# if os.path.exists(upgrade_file): +# os.system("nohup bash {} &>/dev/null &".format(upgrade_file)) +# +# upgrade_file = 'script/upgrade_gevent.sh' +# if os.path.exists(upgrade_file): +# os.system("nohup bash {} &>/dev/null &".format(upgrade_file)) upgrade_file = 'script/upgrade_telegram.sh' if os.path.exists(upgrade_file): @@ -44,13 +54,13 @@ def check_plugin_loader(): machine = os.uname().machine except: pass - plugin_loader_src_file = "class/PluginLoader.{}.Python3.7.so".format(machine) + plugin_loader_src_file = "class/PluginLoader.{}.Python3.12.so".format(machine) if machine == 'x86_64': glibc_version = public.get_glibc_version() if glibc_version in ['2.14','2.13','2.12','2.11','2.10']: - plugin_loader_src_file = "class/PluginLoader.{}.glibc214.Python3.7.so".format(machine) + plugin_loader_src_file = "class/PluginLoader.{}.glibc214.Python3.12.so".format(machine) if os.path.exists(plugin_loader_src_file): - os.system("\cp -f {} {}".format(plugin_loader_src_file, plugin_loader_file)) + os.system(r"\cp -f {} {}".format(plugin_loader_src_file, plugin_loader_file)) check_plugin_loader() @@ -67,7 +77,8 @@ if is_debug: re.compile('{}/(tmp|temp)/.+'.format(_PATH)), re.compile('{}/pyenv/.+'.format(_PATH)), re.compile('{}/class/projectModel/.+'.format(_PATH)), - re.compile('{}/class/databaseModel/.+'.format(_PATH)) + re.compile('{}/class/databaseModel/.+'.format(_PATH)), + re.compile('{}/panel/data/mail/in_bulk/content/.+'.format(_PATH)) ] _lsat_time = 0 @@ -155,6 +166,29 @@ def daemon_task(): run_task() continue +def get_process_count(): + ''' + @name 获取进程数量 + @return int + ''' + + # 如果存在用户配置,则直接返回用户配置的进程数量 + process_count_file = "{}/data/process_count.pl".format(_PATH) + if os.path.exists(process_count_file): + str_count = public.readFile(process_count_file).strip() + try: + if str_count: return int(str_count) + except: pass + + # 否则根据内存和CPU核心数来决定启动进程数量 + memory = psutil.virtual_memory().total / 1024 / 1024 + cpu_count = psutil.cpu_count() + if memory < 4000 or cpu_count < 4: return 1 # 内存小于4G或CPU核心小于4核,则只启动1个进程 + if memory < 8000 and cpu_count > 3: return 2 # 内存大于4G且小于8G,且CPU核心大于3核,则启动2个进程 + if memory > 14000 and cpu_count > 7: return 3 # 内存大于8G且14G,且CPU核心大于7核,则启动3个进程 + if memory > 30000 and cpu_count > 15: return 4 # 内存大于30G且CPU核心大于15核,则启动4个进程 + return 1 + if __name__ == '__main__': pid_file = "{}/logs/panel.pid".format(_PATH) if os.path.exists(pid_file): @@ -172,6 +206,8 @@ if __name__ == '__main__': sys.stdout.flush() sys.stderr.flush() + # 面板启动任务初始化 + os.system("nohup ./pyenv/bin/python3 class/jobs.py &>/dev/null &") try: f = open('data/port.pl') @@ -199,8 +235,7 @@ if __name__ == '__main__': print(ex) import threading - task_thread = threading.Thread(target=daemon_task) - task_thread.setDaemon(True) + task_thread = threading.Thread(target=daemon_task, daemon=True) task_thread.start() if is_ssl: @@ -232,18 +267,76 @@ if __name__ == '__main__': app.logger = logger from gevent.pywsgi import WSGIServer - try: - import flask_sock + import webserver + + class BtWSGIServer(WSGIServer): + def wrap_socket_and_handle(self, client_socket, address): + try: + return super(BtWSGIServer, self).wrap_socket_and_handle(client_socket, address) + except OSError as e: + pass + # public.print_exc_stack(e) + + def do_read(self): + try: + return super(BtWSGIServer, self).do_read() + except OSError as e: + pass + # public.print_exc_stack(e) + + webserver_obj = webserver.webserver() + is_webserver = webserver_obj.run_webserver() + # is_webserver = False + + if is_webserver: + from gevent import socket + listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + unix_socket = '/tmp/panel.sock' + if os.path.exists(unix_socket): + os.remove(unix_socket) + listener.bind(unix_socket) + listener.listen(500) + os.chmod(unix_socket, 0o777) + try: + import flask_sock + http_server = BtWSGIServer(listener, app,log=app.logger) + except: + from geventwebsocket.handler import WebSocketHandler + http_server = BtWSGIServer(listener, app,handler_class=WebSocketHandler,log=app.logger) + else: if is_ssl: - http_server = WSGIServer((HOST, PORT), app,ssl_context = ssl_context,log=app.logger) - else: - http_server = WSGIServer((HOST, PORT), app,log=app.logger) - except: - from geventwebsocket.handler import WebSocketHandler - if is_ssl: - http_server = WSGIServer((HOST, PORT), app,ssl_context = ssl_context,handler_class=WebSocketHandler,log=app.logger) - else: - http_server = WSGIServer((HOST, PORT), app,handler_class=WebSocketHandler,log=app.logger) + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) + if hasattr(ssl_context, "minimum_version"): + ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2 + else: + ssl_context.options = (ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1) + + ssl_context.set_ciphers("ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE") + is_ssl_verify = os.path.exists('/www/server/panel/data/ssl_verify_data.pl') + if is_ssl_verify: + crlfile = '/www/server/panel/ssl/crl.pem' + rootcafile = '/www/server/panel/ssl/ca.pem' + #注销列表 + # ssl_context.load_verify_locations(crlfile) + # ssl_context.verify_flags |= ssl.VERIFY_CRL_CHECK_CHAIN + #加载证书 + ssl_context.load_verify_locations(rootcafile) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.set_default_verify_paths() + + try: + import flask_sock + if is_ssl: + http_server = BtWSGIServer((HOST, PORT), app,ssl_context = ssl_context,log=app.logger) + else: + http_server = BtWSGIServer((HOST, PORT), app,log=app.logger) + except: + from geventwebsocket.handler import WebSocketHandler + if is_ssl: + http_server = BtWSGIServer((HOST, PORT), app,ssl_context = ssl_context,handler_class=WebSocketHandler,log=app.logger) + else: + http_server = BtWSGIServer((HOST, PORT), app,handler_class=WebSocketHandler,log=app.logger) if is_debug: @@ -258,20 +351,58 @@ if __name__ == '__main__': try: http_server.serve_forever() except: - pass + from traceback import format_exc + public.print_log(format_exc()) app.run(host=HOST, port=PORT, threaded=True) else: http_server.start() from multiprocessing import Process + + def serve_forever(): http_server.start_accepting() http_server._stop_event.wait() - process_count = 2 - for i in range(process_count): - p = Process(target=serve_forever) - p.daemon = True - p.start() + # 获取最大进程数量,最小为2个 + process_count = get_process_count() + if process_count < 2: process_count = 2 + + # 启动主进程 + main_p = Process(target=serve_forever) + main_p.daemon = True + main_p.start() + main_psutil = psutil.Process(main_p.pid) + + # 动态按需调整子进程数量 + process_dict = {} while 1: - time.sleep(1000) \ No newline at end of file + t = time.time() + # 当主进程CPU占用率超过90%时,尝试启动新的子进程协同处理 + cpu_percent = main_psutil.cpu_percent(interval=1) + if cpu_percent > 90: + is_alive = 0 + process_num = 0 + + # 检查是否存在空闲的子进程 + for i in process_dict.keys(): + process_num += 1 + if process_dict[i][2].cpu_percent(interval=1) > 0: + is_alive += 1 + + # 如果没有空闲的子进程,且当前子进程数量小于最大进程数量,则启动新的子进程 + if process_num == is_alive and process_num < process_count: + p = Process(target=serve_forever) + p.daemon = True + p.start() + process_dict[p.pid] = [p, t, psutil.Process(p.pid)] + + # 结束创建时间超过60秒,且连续空闲5秒钟以上的子进程 + keys = list(process_dict.keys()) + for i in keys: + if t - process_dict[i][1] < 60: continue + if process_dict[i][2].cpu_percent(interval=5) > 0: continue + process_dict[i][0].kill() + process_dict.pop(i) + + time.sleep(1) diff --git a/BT-Task b/BT-Task index c9b0ca46..65683ed0 100644 --- a/BT-Task +++ b/BT-Task @@ -1,11 +1,11 @@ #!/www/server/panel/pyenv/bin/python #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import os,sys diff --git a/BTPanel/__init__.py b/BTPanel/__init__.py index 527deba4..092fd1e4 100755 --- a/BTPanel/__init__.py +++ b/BTPanel/__init__.py @@ -1,11 +1,17 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -# +------------------------------------------------------------------- -# | Author: hwliang +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- +# | Author: hwliang +# +--- + +# from .app import * +# from .routes.flask_hook import * +# from .routes.v1 import * +# from .routes.v2 import * + import logging import sys import json @@ -21,7 +27,8 @@ if not os.name in ['nt']: os.chdir(panel_path) if not 'class/' in sys.path: sys.path.insert(0, 'class/') - +if not 'class_v2/' in sys.path: + sys.path.insert(0, 'class_v2/') from flask import Flask, session, render_template, send_file, request, redirect, g, make_response, \ render_template_string, abort, stream_with_context, Response as Resp from cachelib import SimpleCache, SimpleCacheSession @@ -33,12 +40,15 @@ cache = SimpleCache(5000) import public # 初始化Flask应用 -app = Flask(__name__, template_folder="templates/{}".format(public.GetConfigValue('template'))) +app = Flask(__name__, + template_folder="templates/{}".format( + public.GetConfigValue('template'))) Compress(app) try: from flask_sock import Sock except: from flask_sockets import Sockets as Sock + sockets = Sock(app) # 注册HOOK hooks = {} @@ -62,7 +72,9 @@ if os.path.exists(basic_auth_conf): pass # 初始化SESSION服务 -app.secret_key = public.md5(str(os.uname()) + str(psutil.boot_time())) # uuid.UUID(int=uuid.getnode()).hex[-12:] +app.secret_key = public.md5( + str(os.uname()) + + str(psutil.boot_time())) # uuid.UUID(int=uuid.getnode()).hex[-12:] local_ip = None my_terms = {} app.config['SESSION_MEMCACHED'] = SimpleCacheSession(1000, 86400) @@ -92,7 +104,8 @@ text_header = {'Content-Type': 'text/plain; charset=utf-8'} cache.set('p_token', 'bmac_' + public.Md5(public.get_mac_address())) admin_path_file = 'data/admin_path.pl' admin_path = '/' -if os.path.exists(admin_path_file): admin_path = public.readFile(admin_path_file).strip() +if os.path.exists(admin_path_file): + admin_path = public.readFile(admin_path_file).strip() admin_path_checks = [ '/', '/san', @@ -139,39 +152,55 @@ admin_path_checks = [ '/tips', '/message', '/warning', - '/userRegister', - + '/userRegister', # 面板内注册 + '/docker', + '/btdocker', ] if admin_path in admin_path_checks: admin_path = '/bt' if admin_path[-1] == '/': admin_path = admin_path[:-1] uri_match = re.compile( - r"(^/static/[\w_\./\-]+\.(js|css|png|jpg|gif|ico|svg|woff|woff2|ttf|otf|eot|map)$|^/[\w_\./\-]*$)") + r"(^/static/[\w_\./\-]+\.(js|css|png|jpg|gif|ico|svg|woff|woff2|ttf|otf|eot|map)$|^/[\w_\./\-]*$)" +) session_id_match = re.compile(r"^[\w\.\-]+$") +route_v2 = '/v2' # v2版本路由前缀 + +# load translations +from public.translations import load_translations +load_translations() # ===================================Flask HOOK========================# + # Flask请求勾子 @app.before_request def request_check(): if request.method not in ['GET', 'POST']: return abort(404) + + # 获取客户端真实IP + x_real_ip = request.headers.get('X-Real-Ip') + if x_real_ip: + request.remote_addr = x_real_ip + request.environ.setdefault('REMOTE_PORT', public.get_remote_port()) + g.request_time = time.time() + g.return_message = False # 路由和URI长度过滤 if len(request.path) > 256: return abort(403) if len(request.url) > 1024: return abort(403) # URI过滤 if not uri_match.match(request.path): return abort(403) # POST参数过滤 - if request.path in ['/login', - '/safe', - '/hook', - '/public', - '/down', - '/get_app_bind_status', - '/check_bind', - '/userRegister', - - ]: + if request.path in [ + '/login', + '/safe', + '/hook', + '/public', + '/down', + '/get_app_bind_status', + '/check_bind', + '/userRegister', + ]: pdata = request.form.to_dict() for k in pdata.keys(): if len(k) > 48: return abort(403) @@ -188,8 +217,11 @@ def request_check(): g.get_csrf_html_token_key = public.get_csrf_html_token_key() if app.config['BASIC_AUTH_OPEN']: - if request.path in ['/public', '/download', '/mail_sys', '/hook', '/down', '/check_bind', - '/get_app_bind_status']: return + if request.path in [ + '/public', '/download', '/mail_sys', '/hook', '/down', + '/check_bind', '/get_app_bind_status' + ]: + return auth = request.authorization if not comm.get_sk(): return if not auth: return send_authenticated() @@ -198,7 +230,13 @@ def request_check(): or public.md5(auth.password.strip() + tips) != app.config['BASIC_AUTH_PASSWORD']: return send_authenticated() - if not request.path in ['/safe', '/hook', '/public', '/mail_sys', '/down']: + if not request.path in [ + '/safe', + '/hook', + '/public', + '/mail_sys', + '/down' + ]: ip_check = public.check_ip_panel() if ip_check: return ip_check @@ -210,27 +248,126 @@ def request_check(): if public.is_local(): not_networks = ['uninstall_plugin', 'install_plugin', 'UpdatePanel'] if request.args.get('action') in not_networks: - return public.returnJson(False, 'This feature cannot be used in offline mode!'), json_header - - if request.path in ['/site', '/ftp', '/database', '/soft', '/control', '/firewall', '/files', '/xterm', '/crontab', - '/config']: + return public.returnJson( + False, + 'This feature cannot be used in offline mode!'), json_header + # 适配docker---- '/docker', + if request.path in [ + '/site', '/ftp', '/database', '/soft', '/control', '/firewall', + '/files', '/xterm', '/crontab', '/config', '/docker', '/btdocker', + ]: if public.is_error_path(): return redirect('/error', 302) if not request.path in ['/config']: if session.get('password_expire', False): return redirect('/modify_password', 302) + # 新增 适配docker时增加 未测试 + # 处理登录页面相对路径的静态文件 + if request.path.find('/static/') > 0: + new_auth_path = _auth_path = public.get_admin_path() + + # 2024/1/3 下午 8:35 检测_auth_path是否有包含2个以上/符号,如果有则取最后一个/符号前的字符串然后替换成_auth_path + if _auth_path.count('/') > 1: + new_auth_path = _auth_path[:_auth_path.rfind('/')] + + if not public.path_safe_check(request.path): return abort(404) # 路径安全检查 + + _new_route = request.path[0:request.path.find('/static/')] + if request.path.find(_auth_path) == 0: + static_file = public.get_panel_path() + '/BTPanel' + request.path.replace(_auth_path, '').replace('//', '/') + if not os.path.exists(static_file): return abort(404) + return send_file(static_file, conditional=True, etag=True) + elif request.path.find(new_auth_path) == 0: + static_file = public.get_panel_path() + '/BTPanel' + request.path.replace(new_auth_path, '').replace('//', + '/') + if not os.path.exists(static_file): return abort(404) + return send_file(static_file, conditional=True, etag=True) + elif _new_route in admin_path_checks: + + static_file = public.get_panel_path() + '/BTPanel' + request.path[len(_new_route):].replace('//', '/') + # if not os.path.exists(static_file): return abort(404) + # 检测是否是插件静态文件 + plugin_static_file = public.get_panel_path() + '/plugin/' + request.path + is_plugin_static = os.path.exists(plugin_static_file) + + # 既不是面板静态文件也不是插件静态文件 + if not os.path.exists(static_file) and not is_plugin_static: return abort(404) + + # 如果是插件静态文件 + if is_plugin_static: + return send_file(plugin_static_file, conditional=True, etag=True) + + # 如果是面板静态文件 + return send_file(static_file, conditional=True, etag=True) + + if request.path.find('/static/img/soft_ico/ico') >= 0: + static_file = "{}/BTPanel/{}".format(panel_path, request.path) + if not os.path.exists(static_file): + static_file = "{}/BTPanel/static/img/soft_ico/icon_plug.svg".format(panel_path) + return send_file(static_file, conditional=True, etag=True) + + # 处理登录成功状态,更新节点 + if 'login' in session and session['login'] == True: + if not cache.get('bt_home_node'): + public.run_thread(public.ExecShell, ('btpython /www/server/panel/script/reload_check.py hour',)) + cache.set('bt_home_node', True, 3600) + + # Flask 请求结束勾子 @app.teardown_request def request_end(reques=None): if request.method not in ['GET', 'POST']: return - if not request.path.startswith('/static/'): + if not request.path.startswith('/static/') or not request.path.startswith('/v2/static/'): + # import public public.write_request_log(reques) + + # 当路由为v2版才检测,且不检测/plugin时 + if request.path.startswith('/v2'): + now_time = time.time() + session_timeout = session.get('session_timeout', 0) + if (session_timeout > now_time or session_timeout == 0) and not request.path.startswith('/v2/plugin'): + # 首页涉及的请求模块,暂不强制 + prefixes = ["/v2/site", "/v2/ftp", "/v2/database", "/v2/docker", "/v2/safe/security/set_security", + "/v2/safe/security/get_repair_bar"] + for prefix in prefixes: + if request.path.startswith(prefix): + if 'return_message' in g: + if not g.return_message: + # public.print_log("当前路由且未使用统一响应函数public.return_message") + # return abort(404) + # return public.returnJson( + # False, 'Request failed!Request not using unified response!' + # ), json_header + pass + else: + # return abort(405) + g.return_message = False + # public.print_log("当前路由已使用统一响应函数public.return_message") + break if 'api_request' in g: if g.api_request: session.clear() +# Flask 405页面勾子 +@app.errorhandler(405) +def error_404(e): + if request.method not in ['GET', 'POST']: return + if not session.get('login', None): + g.auth_error = True + return public.error_not_login() + errorStr = ''' +405 Not Found + +

请求接口请使用统一响应函数

+
nginx
+ +''' + headers = {"Content-Type": "text/html"} + return Response(errorStr, status=404, headers=headers) + + # Flask 404页面勾子 @app.errorhandler(404) def error_404(e): @@ -245,9 +382,7 @@ def error_404(e):
nginx
''' - headers = { - "Content-Type": "text/html" - } + headers = {"Content-Type": "text/html"} return Response(errorStr, status=404, headers=headers) @@ -265,30 +400,49 @@ def error_403(e):
nginx
''' - headers = { - "Content-Type": "text/html" - } + headers = {"Content-Type": "text/html"} return Response(errorStr, status=403, headers=headers) -# Flask 500页面勾子 -@app.errorhandler(500) +# 错误收集 +@app.errorhandler(Exception) def error_500(e): - if request.method not in ['GET', 'POST']: return + # handle the hint exception. + if isinstance(e, public.HintException): + return public.fail_v2(str(e)) + + # Print error traceback. + from traceback import format_exc + public.print_log(format_exc()) + + if request.method not in ['GET', 'POST']: return Response(status=500) + if not session.get('login', None): g.auth_error = True return public.error_not_login() + ss = '''404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. During handling of the above exception, another exception occurred:''' error_info = public.get_error_info().strip().split(ss)[-1].strip() + + nn = 'During handling of the above exception, another exception occurred:' + if error_info.find(nn) != -1 and error_info.find('public.error_conn_cloud') != -1: + error_info = error_info.split(nn)[0].strip() + _form = request.form.to_dict() if 'username' in _form: _form['username'] = '******' if 'password' in _form: _form['password'] = '******' if 'phone' in _form: _form['phone'] = '******' + if 'pem' in _form: _form['pem'] = '******' + if 'pwd' in _form: _form['pwd'] = '******' + if 'key' in _form: _form['key'] = '******' + if 'csr' in _form: _form['csr'] = '******' + if 'db_user' in _form: _form['db_user'] = '******' + if 'db_password' in _form: _form['db_pwd'] = '******' + request_info = '''REQUEST_DATE: {request_date} - PAN_VERSION: {panel_version} - OS_VERSION: {os_version} + VERSION: {os_version} - {panel_version} REMOTE_ADDR: {remote_addr} REQUEST_URI: {method} {full_path} REQUEST_FORM: {request_form} @@ -300,162 +454,182 @@ REQUEST_FORM: {request_form} request_form=public.xsssec(str(_form)), user_agent=public.xsssec(request.headers.get('User-Agent')), panel_version=public.version(), - os_version=public.get_os_version() - ) + os_version=public.get_os_version()) + error_title = error_info.split("\n")[-1].replace('public.PanelError: ', + '').strip() + if error_info.find('Failed to connect to the cloud server') != -1: + error_title = "Failed to connect to the cloud server!" + + result = public.readFile( + public.get_panel_path() + + '/BTPanel/templates/default/panel_error.html').format( + error_title=error_title, + request_info=request_info, + error_msg=error_info) + + # 用户信息 + # if not public.cache_get("infos"): + # user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path()))) + # public.cache_set("infos", user_info, 1800) + # else: + # user_info = public.cache_get("infos") + try: + if "import panelSSL" in error_info: + result = public.ExecShell("btpip list|grep pyOpenSSL")[0] + error_info = "{}\n版本信息:{}".format(error_info, result.strip()) + except: + error_info = "{}\n版本信息: 获取失败".format(error_info) + + # 错误信息 + error_infos = { + # "UID": user_info['uid'], # 用户ID + # 'ACCESS_KEY': user_info['access_key'], # 用户密钥 + # 'SERVER_ID': user_info['serverid'], # 服务器ID + "REQUEST_DATE": public.getDate(), # 请求时间 + "PANEL_VERSION": public.version(), # 面板版本 + "OS_VERSION": public.get_os_version(), # 操作系统版本 + "REMOTE_ADDR": public.GetClientIp(), # 请求IP + "REQUEST_URI": request.method + request.full_path, # 请求URI + "REQUEST_FORM": public.xsssec(str(_form)), # 请求表单 + "USER_AGENT": public.xsssec(request.headers.get('User-Agent')), # 客户端连接信息 + "ERROR_INFO": error_info, # 错误信息 + "PACK_TIME": public.readFile("/www/server/panel/config/update_time.pl") if os.path.exists( + "/www/server/panel/config/update_time.pl") else public.getDate(), # 打包时间 + "TYPE": 100, + "ERROR_ID": str(e) + } + pkey = public.Md5(error_infos["ERROR_ID"]) + + # 提交异常报告 + if not public.cache_get(pkey): + try: + public.run_thread(public.httpPost, ("https://geterror.aapanel.com/bt_error/index.php", error_infos)) + public.cache_set(pkey, 1, 1800) + except Exception as e: + pass - result = public.readFile(public.get_panel_path() + '/BTPanel/templates/default/panel_error.html').format( - error_title=error_info.split("\n")[-1], request_info=request_info, error_msg=error_info) return Resp(result, 500) -# # 错误收集适配 -# @app.errorhandler(Exception) -# def error_500(e): -# if request.method not in ['GET', 'POST']: return Response(status=500) -# if not session.get('login', None): -# g.auth_error = True -# return public.error_not_login() -# ss = '''404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. -# -# During handling of the above exception, another exception occurred:''' -# error_info = public.get_error_info().strip().split(ss)[-1].strip() -# -# nn = 'During handling of the above exception, another exception occurred:' -# if error_info.find(nn) != -1 and error_info.find('public.error_conn_cloud') != -1: -# error_info = error_info.split(nn)[0].strip() -# -# _form = request.form.to_dict() -# if 'username' in _form: _form['username'] = '******' -# if 'password' in _form: _form['password'] = '******' -# if 'phone' in _form: _form['phone'] = '******' -# if 'pem' in _form: _form['pem'] = '******' -# if 'pwd' in _form: _form['pwd'] = '******' -# if 'key' in _form: _form['key'] = '******' -# if 'csr' in _form: _form['csr'] = '******' -# if 'db_user' in _form: _form['db_user'] = '******' -# if 'db_password' in _form: _form['db_pwd'] = '******' -# -# request_info = '''REQUEST_DATE: {request_date} -# VERSION: {os_version} - {panel_version} -# REMOTE_ADDR: {remote_addr} -# REQUEST_URI: {method} {full_path} -# REQUEST_FORM: {request_form} -# USER_AGENT: {user_agent}'''.format( -# request_date=public.getDate(), -# remote_addr=public.GetClientIp(), -# method=request.method, -# full_path=public.xsssec(request.full_path), -# request_form=public.xsssec(str(_form)), -# user_agent=public.xsssec(request.headers.get('User-Agent')), -# panel_version=public.version(), -# os_version=public.get_os_version()) -# error_title = error_info.split("\n")[-1].replace('public.PanelError: ', -# '').strip() -# if error_info.find('连接云端服务器失败') != -1: -# error_title = "连接云端服务器失败!" -# -# result = public.readFile( -# public.get_panel_path() + -# '/BTPanel/templates/default/panel_error.html').format( -# error_title=error_title, -# request_info=request_info, -# error_msg=error_info) -# -# # 用户信息 -# # if not public.cache_get("infos"): -# # user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path()))) -# # public.cache_set("infos", user_info, 1800) -# # else: -# # user_info = public.cache_get("infos") -# try: -# if "import panelSSL" in error_info: -# result = public.ExecShell("btpip list|grep pyOpenSSL")[0] -# error_info = "{}\n版本信息:{}".format(error_info, result.strip()) -# except: -# error_info = "{}\n版本信息: 获取失败".format(error_info) -# -# -# # 错误信息 -# error_infos = { -# # "UID": user_info['uid'], # 用户ID -# # 'ACCESS_KEY': user_info['access_key'], # 用户密钥 -# # 'SERVER_ID': user_info['serverid'], # 服务器ID -# "REQUEST_DATE": public.getDate(), # 请求时间 -# "PANEL_VERSION": public.version(), # 面板版本 -# "OS_VERSION": public.get_os_version(), # 操作系统版本 -# "REMOTE_ADDR": public.GetClientIp(), # 请求IP -# "REQUEST_URI": request.method + request.full_path, # 请求URI -# "REQUEST_FORM": public.xsssec(str(_form)), # 请求表单 -# "USER_AGENT": public.xsssec(request.headers.get('User-Agent')), # 客户端连接信息 -# "ERROR_INFO": error_info, # 错误信息 -# "PACK_TIME": public.readFile("/www/server/panel/config/update_time.pl") if os.path.exists("/www/server/panel/config/update_time.pl") else public.getDate(), # 打包时间 -# "TYPE": 0, -# "ERROR_ID": str(e) -# } -# pkey = public.Md5(error_infos["ERROR_ID"]) -# -# # 提交异常报告 -# if not public.cache_get(pkey): -# try: -# public.run_thread(public.httpPost, ("https://api.bt.cn/bt_error/index.php", error_infos)) -# public.cache_set(pkey, 1, 1800) -# except Exception as e: -# pass -# -# return Resp(result, 500) - - # ===================================Flask HOOK========================# # ===================================普通路由区========================# -@app.route('/', methods=method_all) -def home(): +# @app.route('/', methods=method_all) +# def home(): +# # 面板首页 +# comReturn = comm.local() +# if comReturn: return comReturn +# data = {} +# data[public.to_string([112, +# 100])], data['pro_end'], data['ltd_end'] = get_pd() +# data['siteCount'] = public.M('sites').count() +# data['ftpCount'] = public.M('ftps').count() +# data['databaseCount'] = public.M('databases').count() +# data['lan'] = public.GetLan('index') +# data['js_random'] = get_js_random() +# return render_template('index.html', data=data) + + +@app.route('/', methods=method_get) +@app.route('/', methods=method_get) +def index_new(sub_path: str = ''): # 面板首页 comReturn = comm.local() if comReturn: return comReturn data = {} - data[public.to_string([112, 100])], data['pro_end'], data['ltd_end'] = get_pd() - data['siteCount'] = public.M('sites').count() - data['ftpCount'] = public.M('ftps').count() - data['databaseCount'] = public.M('databases').count() - data['lan'] = public.GetLan('index') - data['js_random'] = get_js_random() - return render_template('index.html', data=data) -@app.route('/xterm', methods=method_all) -def xterm(): - # 宝塔终端管理 - comReturn = comm.local() - if comReturn: return comReturn - if request.method == method_get[0]: + if sub_path == '': + data[public.to_string([112, + 100])], data['pro_end'], data['ltd_end'] = get_pd() + data['siteCount'] = public.M('sites').count() + data['ftpCount'] = public.M('ftps').count() + data['databaseCount'] = public.M('databases').count() + data['lan'] = public.GetLan('index') + data['js_random'] = get_js_random() + elif sub_path.startswith('config'): + import system, wxapp, config + c_obj = config.config() + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('config') + try: + data['wx'] = wxapp.wxapp().get_user_info(None)['msg'] + except: + data['wx'] = 'INIT_WX_NOT_BIND' + data['api'] = '' + data['ipv6'] = '' + sess_out_path = 'data/session_timeout.pl' + if not os.path.exists(sess_out_path): + public.writeFile(sess_out_path, '86400') + s_time_tmp = public.readFile(sess_out_path) + if not s_time_tmp: s_time_tmp = '0' + data['session_timeout'] = int(s_time_tmp) + if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + if c_obj.get_token(None)['open']: data['api'] = 'checked' + data['basic_auth'] = c_obj.get_basic_auth_stat(None) + data['status_code'] = c_obj.get_not_auth_status() + data['basic_auth']['value'] = public.getMsg('CLOSED') + if data['basic_auth']['open']: + data['basic_auth']['value'] = public.getMsg('OPENED') + data['debug'] = '' + data['js_random'] = get_js_random() + if app.config['DEBUG']: data['debug'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + data['public_key'] = public.get_rsa_public_key().replace("\n", "") + elif sub_path.startswith('soft'): import system data = system.system().GetConcifInfo() - return render_template('xterm.html', data=data) - import ssh_terminal - ssh_host_admin = ssh_terminal.ssh_host_admin() - defs = ( - 'get_host_list', 'get_host_find', 'modify_host', 'create_host', 'remove_host', 'set_sort', 'get_command_list', - 'create_command', 'get_command_find', 'modify_command', 'remove_command') - return publicObject(ssh_host_admin, defs, None) - -@app.route('/modify_password', methods=method_get) -def modify_password(): - comReturn = comm.local() - if comReturn: return comReturn - # if not session.get('password_expire',False): return redirect('/',302) - data = {} - g.title = public.get_msg_gettext('The password has expired, please change it!') - return render_template('modify_password.html', data=data) - - -@app.route('/site', methods=method_all) -def site(pdata=None): - # 网站管理 - comReturn = comm.local() - if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - # data = {} + data['lan'] = public.GetLan('soft') + data['js_random'] = get_js_random() + elif sub_path.startswith('crontab'): + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('crontab') + data['js_random'] = get_js_random() + elif sub_path.startswith('docker'): + import system + data = system.system().GetConcifInfo() + data['js_random'] = get_js_random() + data['lan'] = public.GetLan('files') + elif sub_path.startswith('control'): + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('control') + data['js_random'] = get_js_random() + elif sub_path.startswith('logs'): + data = {} + data['lan'] = public.GetLan('soft') + data['show_workorder'] = not os.path.exists('data/not_workorder.pl') + elif sub_path.startswith('database'): + import ajax + from panelPlugin import panelPlugin + session['phpmyadminDir'] = False + if panelPlugin().get_phpmyadmin_stat(): + pmd = get_phpmyadmin_dir() + if pmd: + session['phpmyadminDir'] = 'http://' + public.GetHost( + ) + ':' + pmd[1] + '/' + pmd[0] + ajax.ajax().set_phpmyadmin_session() + import system + data = system.system().GetConcifInfo() + data['isSetup'] = os.path.exists( + public.GetConfigValue('setup_path') + '/mysql/bin') + data['mysql_root'] = public.M('config').where( + 'id=?', (1,)).getField('mysql_root') + data['lan'] = public.GetLan('database') + data['js_random'] = get_js_random() + elif sub_path.startswith('ftp'): + FtpPort() + import system + data = system.system().GetConcifInfo() + data['isSetup'] = True + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + + '/pure-ftpd') == False: + data['isSetup'] = False + data['lan'] = public.GetLan('ftp') + elif sub_path.startswith('site'): import system data = system.system().GetConcifInfo() data['isSetup'] = True @@ -465,99 +639,217 @@ def site(pdata=None): and os.path.exists(public.GetConfigValue('setup_path') + '/apache') == False \ and os.path.exists('/usr/local/lsws/bin/lswsctrl') == False: data['isSetup'] = False - return render_template('site.html', data=data) + elif sub_path.startswith('xterm'): + import system + data = system.system().GetConcifInfo() + elif sub_path.startswith('firewall'): + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + elif sub_path.startswith('files'): + import system + data = system.system().GetConcifInfo() + data['recycle_bin'] = os.path.exists('data/recycle_bin.pl') + data['lan'] = public.GetLan('files') + data['js_random'] = get_js_random() + elif sub_path.startswith('ssh_security'): + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + + data['isSetup'] = True + if os.path.exists(public.GetConfigValue('setup_path') + '/nginx') == False \ + and os.path.exists(public.GetConfigValue('setup_path') + '/apache') == False \ + and os.path.exists('/usr/local/lsws/bin/lswsctrl') == False: + data['isSetup'] = False + + import base64 + + # load translations + data['translations'] = base64.b64encode(json.dumps(load_translations()).encode()).decode() + + return render_template('index_new.html', data=data) + + + +@app.route('/xterm', methods=method_post) +def xterm(): + # 宝塔终端管理 + comReturn = comm.local() + if comReturn: return comReturn + import ssh_terminal + ssh_host_admin = ssh_terminal.ssh_host_admin() + defs = ('get_host_list', 'get_host_find', 'modify_host', 'create_host', + 'remove_host', 'set_sort', 'get_command_list', 'create_command', + 'get_command_find', 'modify_command', 'remove_command') + return publicObject(ssh_host_admin, defs, None) + + +@app.route('/modify_password', methods=method_get) +def modify_password(): + comReturn = comm.local() + if comReturn: return comReturn + # if not session.get('password_expire',False): return redirect ('/',302) + data = {} + g.title = public.get_msg_gettext( + 'The password has expired, please change it!') + return render_template('modify_password.html', data=data) + + +@app.route('/site', methods=method_post) +def site(pdata=None): + # 网站管理 + comReturn = comm.local() + if comReturn: return comReturn import panelSite siteObject = panelSite.panelSite() - defs = ('get_auto_restart_rph', 'remove_auto_restart_rph', 'auto_restart_rph', 'check_del_data', 'upload_csv', - 'create_website_multiple', 'del_redirect_multiple', 'del_proxy_multiple', 'delete_dir_auth_multiple', - 'delete_dir_bind_multiple', 'delete_domain_multiple', 'set_site_etime_multiple', - 'set_site_php_version_multiple', - 'delete_website_multiple', 'set_site_status_multiple', 'get_site_err_log', 'get_site_domains', - 'GetRedirectFile', - 'SaveRedirectFile', 'DeleteRedirect', 'GetRedirectList', 'CreateRedirect', 'ModifyRedirect', - "set_error_redirect", - 'set_dir_auth', 'delete_dir_auth', 'get_dir_auth', 'modify_dir_auth_pass', 'reset_wp_db', 'export_domains', - 'import_domains', - 'GetSiteLogs', 'GetSiteDomains', 'GetSecurity', 'SetSecurity', 'ProxyCache', 'CloseToHttps', 'HttpToHttps', - 'SetEdate', - 'SetRewriteTel', 'GetCheckSafe', 'CheckSafe', 'GetDefaultSite', 'SetDefaultSite', 'CloseTomcat', - 'SetTomcat', 'apacheAddPort', - 'AddSite', 'GetPHPVersion', 'SetPHPVersion', 'DeleteSite', 'AddDomain', 'DelDomain', 'GetDirBinding', - 'AddDirBinding', 'GetDirRewrite', - 'DelDirBinding', 'get_site_types', 'add_site_type', 'remove_site_type', 'modify_site_type_name', - 'set_site_type', 'UpdateRulelist', - 'SetSiteRunPath', 'GetSiteRunPath', 'SetPath', 'SetIndex', 'GetIndex', 'GetDirUserINI', 'SetDirUserINI', - 'GetRewriteList', 'SetSSL', - 'SetSSLConf', 'CreateLet', 'CloseSSLConf', 'GetSSL', 'SiteStart', 'SiteStop', 'Set301Status', - 'Get301Status', 'CloseLimitNet', 'SetLimitNet', - 'GetLimitNet', 'RemoveProxy', 'GetProxyList', 'GetProxyDetals', 'CreateProxy', 'ModifyProxy', - 'GetProxyFile', 'SaveProxyFile', 'ToBackup', - 'DelBackup', 'GetSitePHPVersion', 'logsOpen', 'GetLogsStatus', 'CloseHasPwd', 'SetHasPwd', 'GetHasPwd', - 'GetDnsApi', 'SetDnsApi', - 'reset_wp_password', 'is_update', 'purge_all_cache', 'set_fastcgi_cache', 'update_wp', 'get_wp_username', - 'get_language', 'deploy_wp', - # 网站管理新增 - 'test_domains_api', 'site_rname', - - ) + defs = ( + 'get_auto_restart_rph', + 'remove_auto_restart_rph', + 'auto_restart_rph', + 'check_del_data', + 'upload_csv', + 'create_website_multiple', + 'del_redirect_multiple', + 'del_proxy_multiple', + 'delete_dir_auth_multiple', + 'delete_dir_bind_multiple', + 'delete_domain_multiple', + 'set_site_etime_multiple', + 'set_site_php_version_multiple', + 'delete_website_multiple', + 'set_site_status_multiple', + 'get_site_err_log', + 'get_site_domains', + 'GetRedirectFile', + 'SaveRedirectFile', + 'DeleteRedirect', + 'GetRedirectList', + 'CreateRedirect', + 'ModifyRedirect', + "set_error_redirect", + 'set_dir_auth', + 'delete_dir_auth', + 'get_dir_auth', + 'modify_dir_auth_pass', + 'reset_wp_db', + 'export_domains', + 'import_domains', + 'GetSiteLogs', + 'GetSiteDomains', + 'GetSecurity', + 'SetSecurity', + 'ProxyCache', + 'CloseToHttps', + 'HttpToHttps', + 'SetEdate', + 'SetRewriteTel', + 'GetCheckSafe', + 'CheckSafe', + 'GetDefaultSite', + 'SetDefaultSite', + 'CloseTomcat', + 'SetTomcat', + 'apacheAddPort', + 'AddSite', + 'GetPHPVersion', + 'SetPHPVersion', + 'DeleteSite', + 'AddDomain', + 'DelDomain', + 'GetDirBinding', + 'AddDirBinding', + 'GetDirRewrite', + 'DelDirBinding', + 'get_site_types', + 'add_site_type', + 'remove_site_type', + 'modify_site_type_name', + 'set_site_type', + 'UpdateRulelist', + 'SetSiteRunPath', + 'GetSiteRunPath', + 'SetPath', + 'SetIndex', + 'GetIndex', + 'GetDirUserINI', + 'SetDirUserINI', + 'GetRewriteList', + 'SetSSL', + 'SetSSLConf', + 'CreateLet', + 'CloseSSLConf', + 'GetSSL', + 'SiteStart', + 'SiteStop', + 'Set301Status', + 'Get301Status', + 'CloseLimitNet', + 'SetLimitNet', + 'GetLimitNet', + 'RemoveProxy', + 'GetProxyList', + 'GetProxyDetals', + 'CreateProxy', + 'ModifyProxy', + 'GetProxyFile', + 'SaveProxyFile', + 'ToBackup', + 'DelBackup', + 'GetSitePHPVersion', + 'logsOpen', + 'GetLogsStatus', + 'CloseHasPwd', + 'SetHasPwd', + 'GetHasPwd', + 'GetDnsApi', + 'SetDnsApi', + 'reset_wp_password', + 'is_update', + 'purge_all_cache', + 'set_fastcgi_cache', + 'update_wp', + 'get_wp_username', + 'get_language', + 'deploy_wp', + # 网站管理新增 + 'test_domains_api', + 'site_rname', + ) return publicObject(siteObject, defs, None, pdata) -@app.route('/ftp', methods=method_all) +@app.route('/ftp', methods=method_post) def ftp(pdata=None): # FTP管理 comReturn = comm.local() if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - FtpPort() - import system - data = system.system().GetConcifInfo() - data['isSetup'] = True - data['js_random'] = get_js_random() - if os.path.exists(public.GetConfigValue('setup_path') + '/pure-ftpd') == False: data['isSetup'] = False - data['lan'] = public.GetLan('ftp') - return render_template('ftp.html', data=data) import ftp ftpObject = ftp.ftp() - defs = ('AddUser', 'DeleteUser', 'SetUserPassword', 'SetStatus', 'setPort', 'set_user_home', 'get_login_logs', - 'get_action_logs', 'set_ftp_logs') + defs = ('AddUser', 'DeleteUser', 'SetUserPassword', 'SetStatus', 'setPort', + 'set_user_home', 'get_login_logs', 'get_action_logs', + 'set_ftp_logs') return publicObject(ftpObject, defs, None, pdata) -@app.route('/database', methods=method_all) +@app.route('/database', methods=method_post) def database(pdata=None): # 数据库管理 comReturn = comm.local() if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - import ajax - from panelPlugin import panelPlugin - session['phpmyadminDir'] = False - if panelPlugin().get_phpmyadmin_stat(): - pmd = get_phpmyadmin_dir() - if pmd: - session['phpmyadminDir'] = 'http://' + public.GetHost() + ':' + pmd[1] + '/' + pmd[0] - ajax.ajax().set_phpmyadmin_session() - import system - data = system.system().GetConcifInfo() - data['isSetup'] = os.path.exists(public.GetConfigValue('setup_path') + '/mysql/bin') - data['mysql_root'] = public.M('config').where('id=?', (1,)).getField('mysql_root') - data['lan'] = public.GetLan('database') - data['js_random'] = get_js_random() - return render_template('database.html', data=data) import database databaseObject = database.database() - defs = ( - 'GetdataInfo', 'check_del_data', 'get_database_size', 'GetInfo', 'ReTable', 'OpTable', 'AlTable', 'GetSlowLogs', - 'GetRunStatus', - 'SetDbConf', 'GetDbStatus', 'BinLog', 'GetErrorLog', 'GetMySQLInfo', 'SetDataDir', 'SetMySQLPort', - 'AddCloudDatabase', - 'AddDatabase', 'DeleteDatabase', 'SetupPassword', 'ResDatabasePassword', 'ToBackup', 'DelBackup', - 'AddCloudServer', - 'GetCloudServer', 'RemoveCloudServer', 'ModifyCloudServer', - 'InputSql', 'SyncToDatabases', 'SyncGetDatabases', 'GetDatabaseAccess', 'SetDatabaseAccess', - 'get_mysql_user', 'check_mysql_ssl_status', 'write_ssl_to_mysql', 'GetdataInfo') + defs = ('GetdataInfo', 'check_del_data', 'get_database_size', 'GetInfo', + 'ReTable', 'OpTable', 'AlTable', 'GetSlowLogs', 'GetRunStatus', + 'SetDbConf', 'GetDbStatus', 'BinLog', 'GetErrorLog', + 'GetMySQLInfo', 'SetDataDir', 'SetMySQLPort', 'AddCloudDatabase', + 'AddDatabase', 'DeleteDatabase', 'SetupPassword', + 'ResDatabasePassword', 'ToBackup', 'DelBackup', 'AddCloudServer', + 'GetCloudServer', 'RemoveCloudServer', 'ModifyCloudServer', + 'InputSql', 'SyncToDatabases', 'SyncGetDatabases', + 'GetDatabaseAccess', 'SetDatabaseAccess', 'get_mysql_user', + 'check_mysql_ssl_status', 'write_ssl_to_mysql', 'GetdataInfo') return publicObject(databaseObject, defs, None, pdata) @@ -568,9 +860,10 @@ def acme(pdata=None): if comReturn: return comReturn import acme_v2 acme_v2_object = acme_v2.acme_v2() - defs = ('get_orders', 'remove_order', 'get_order_find', 'revoke_order', 'create_order', 'get_account_info', - 'set_account_info', 'update_zip', 'get_cert_init_api', - 'get_auths', 'auth_domain', 'check_auth_status', 'download_cert', 'apply_cert', 'renew_cert', + defs = ('get_orders', 'remove_order', 'get_order_find', 'revoke_order', + 'create_order', 'get_account_info', 'set_account_info', + 'update_zip', 'get_cert_init_api', 'get_auths', 'auth_domain', + 'check_auth_status', 'download_cert', 'apply_cert', 'renew_cert', 'apply_cert_api', 'apply_dns_auth') return publicObject(acme_v2_object, defs, None, pdata) @@ -596,49 +889,22 @@ def api(pdata=None): if comReturn: return comReturn import panelApi api_object = panelApi.panelApi() - defs = ('get_token', 'check_bind', 'get_bind_status', 'get_apps', 'add_bind_app', 'remove_bind_app', 'set_token', - 'get_tmp_token', 'get_app_bind_status', 'login_for_app') + defs = ('get_token', 'check_bind', 'get_bind_status', 'get_apps', + 'add_bind_app', 'remove_bind_app', 'set_token', 'get_tmp_token', + 'get_app_bind_status', 'login_for_app') return publicObject(api_object, defs, None, pdata) -@app.route('/control', methods=method_all) -def control(pdata=None): - # 监控页面 - comReturn = comm.local() - if comReturn: return comReturn - import system - data = system.system().GetConcifInfo() - data['lan'] = public.GetLan('control') - data['js_random'] = get_js_random() - return render_template('control.html', data=data) - - -@app.route('/logs', methods=method_all) -def logs(pdata=None): - comReturn = comm.local() - if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - data = {} - data['lan'] = public.GetLan('soft') - data['show_workorder'] = not os.path.exists('data/not_workorder.pl') - return render_template('logs.html', data=data) - - -@app.route('/firewall', methods=method_all) +@app.route('/firewall', methods=method_post) def firewall(pdata=None): # 安全页面 comReturn = comm.local() if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - import system - data = system.system().GetConcifInfo() - data['lan'] = public.GetLan('firewall') - data['js_random'] = get_js_random() - return render_template('firewall.html', data=data) import firewalls firewallObject = firewalls.firewalls() - defs = ('GetList', 'AddDropAddress', 'DelDropAddress', 'FirewallReload', 'SetFirewallStatus', - 'AddAcceptPort', 'DelAcceptPort', 'SetSshStatus', 'SetPing', 'SetSshPort', 'GetSshInfo', + defs = ('GetList', 'AddDropAddress', 'DelDropAddress', 'FirewallReload', + 'SetFirewallStatus', 'AddAcceptPort', 'DelAcceptPort', + 'SetSshStatus', 'SetPing', 'SetSshPort', 'GetSshInfo', 'SetFirewallStatus') return publicObject(firewallObject, defs, None, pdata) @@ -648,20 +914,20 @@ def ssh_security(pdata=None): # SSH安全 comReturn = comm.local() if comReturn: return comReturn - if request.method == method_get[0] and not pdata and not request.args.get('action', '') in ['download_key']: - data = {} - data['lan'] = public.GetLan('firewall') - data['js_random'] = get_js_random() - return render_template('firewall.html', data=data) + if request.method == method_get[0] and not pdata and not request.args.get( + 'action', '') in ['download_key']: + return index_new('ssh_security') import ssh_security firewallObject = ssh_security.ssh_security() is_csrf = True if request.args.get('action', '') in ['download_key']: is_csrf = False - defs = ('san_ssh_security', 'set_password', 'set_sshkey', 'stop_key', 'get_config', 'download_key', - 'stop_password', 'get_key', 'return_ip', 'add_return_ip', 'del_return_ip', 'start_jian', 'stop_jian', - 'get_jian', 'get_logs', 'set_root', 'stop_root', 'start_auth_method', 'stop_auth_method', 'get_auth_method', - 'check_so_file', 'get_so_file', 'get_pin', 'set_login_send', 'get_login_send', 'get_msg_push_list', - 'clear_login_send') + defs = ('san_ssh_security', 'set_password', 'set_sshkey', 'stop_key', + 'get_config', 'download_key', 'stop_password', 'get_key', + 'return_ip', 'add_return_ip', 'del_return_ip', 'start_jian', + 'stop_jian', 'get_jian', 'get_logs', 'set_root', 'stop_root', + 'start_auth_method', 'stop_auth_method', 'get_auth_method', + 'check_so_file', 'get_so_file', 'get_pin', 'set_login_send', + 'get_login_send', 'get_msg_push_list', 'clear_login_send') return publicObject(firewallObject, defs, None, pdata, is_csrf) @@ -672,7 +938,8 @@ def panel_monitor(pdata=None): if comReturn: return comReturn import monitor dataObject = monitor.Monitor() - defs = ('get_spider', 'get_exception', 'get_request_count_qps', 'load_and_up_flow', 'get_request_count_by_hour') + defs = ('get_spider', 'get_exception', 'get_request_count_qps', + 'load_and_up_flow', 'get_request_count_by_hour') return publicObject(dataObject, defs, None, pdata) @@ -683,7 +950,8 @@ def san_baseline(pdata=None): if comReturn: return comReturn import san_baseline dataObject = san_baseline.san_baseline() - defs = ('start', 'get_api_log', 'get_resut', 'get_ssh_errorlogin', 'repair', 'repair_all') + defs = ('start', 'get_api_log', 'get_resut', 'get_ssh_errorlogin', + 'repair', 'repair_all') return publicObject(dataObject, defs, None, pdata) @@ -694,10 +962,10 @@ def panel_password(pdata=None): if comReturn: return comReturn import password dataObject = password.password() - defs = ('set_root_password', 'get_mysql_root', 'set_mysql_password', 'set_panel_password', - 'SetPassword', 'SetSshKey', 'StopKey', 'GetConfig', 'StopPassword', 'GetKey', - 'get_databses', 'rem_mysql_pass', 'set_mysql_access', "get_panel_username" - ) + defs = ('set_root_password', 'get_mysql_root', 'set_mysql_password', + 'set_panel_password', 'SetPassword', 'SetSshKey', 'StopKey', + 'GetConfig', 'StopPassword', 'GetKey', 'get_databses', + 'rem_mysql_pass', 'set_mysql_access', "get_panel_username") return publicObject(dataObject, defs, None, pdata) @@ -707,7 +975,8 @@ def panel_warning(pdata=None): comReturn = comm.local() if comReturn: return comReturn if public.get_csrf_html_token_key() in session and 'login' in session: - if not check_csrf(): return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header get = get_input() ikey = 'warning_list' import panelWarning @@ -725,7 +994,8 @@ def panel_warning(pdata=None): pass return result - defs = ('get_list', 'set_ignore', 'check_find', 'check_cve', 'set_vuln_ignore', 'get_scan_bar', 'get_tmp_result', + defs = ('get_list', 'set_ignore', 'check_find', 'check_cve', + 'set_vuln_ignore', 'get_scan_bar', 'get_tmp_result', 'kill_get_list') if get.action in ['set_ignore', 'check_find', 'set_vuln_ignore']: @@ -740,11 +1010,11 @@ def backup_bak(pdata=None): if comReturn: return comReturn import backup_bak dataObject = backup_bak.backup_bak() - defs = ('get_sites', 'get_databases', 'backup_database', 'backup_site', 'backup_path', 'get_database_progress', - 'get_site_progress', 'down', 'get_down_progress', 'download_path', 'backup_site_all', - 'get_all_site_progress', - 'backup_date_all', 'get_all_date_progress' - ) + defs = ('get_sites', 'get_databases', 'backup_database', 'backup_site', + 'backup_path', 'get_database_progress', 'get_site_progress', + 'down', 'get_down_progress', 'download_path', 'backup_site_all', + 'get_all_site_progress', 'backup_date_all', + 'get_all_date_progress') return publicObject(dataObject, defs, None, pdata) @@ -755,9 +1025,9 @@ def abnormal(pdata=None): if comReturn: return comReturn import abnormal dataObject = abnormal.abnormal() - defs = ('mysql_server', 'mysql_cpu', 'mysql_count', 'php_server', 'php_conn_max', - 'php_cpu', 'CPU', 'Memory', 'disk', 'not_root_user', 'start' - ) + defs = ('mysql_server', 'mysql_cpu', 'mysql_count', 'php_server', + 'php_conn_max', 'php_cpu', 'CPU', 'Memory', 'disk', + 'not_root_user', 'start') return publicObject(dataObject, defs, None, pdata) @@ -792,16 +1062,32 @@ def msgcontroller(mod_name, def_name): return publicObject(project_obj, defs, None, get) -@app.route('/docker', methods=method_all) -def docker(pdata=None): - comReturn = comm.local() - if comReturn: return comReturn - if request.method == method_get[0]: - import system - data = system.system().GetConcifInfo() - data['js_random'] = get_js_random() - data['lan'] = public.GetLan('files') - return render_template('docker.html', data=data) +# @app.route('/docker', methods=method_all) +# def docker(pdata=None): +# comReturn = comm.local() +# if comReturn: return comReturn +# if request.method == method_get[0]: +# import system +# data = system.system().GetConcifInfo() +# data['js_random'] = get_js_random() +# data['lan'] = public.GetLan('files') +# return render_template('docker.html', data=data) + + +# @app.route('/docker', methods=method_all) +# @app.route('/docker/', methods=method_all) +# @app.route('/docker_ifame', methods=method_all) +# def docker(action=None, pdata=None): +# if not public.is_bind(): +# return redirect('/bind', 302) +# comReturn = comm.local() +# if comReturn: return comReturn +# if request.method == method_get[0]: +# import system +# data = system.system().GetConcifInfo() +# data['js_random'] = get_js_random() +# data['lan'] = public.GetLan('files') +# return render_template('index1.html', data=data) @app.route('/dbmodel//', methods=method_all) @@ -825,150 +1111,238 @@ def files(pdata=None): comReturn = comm.local() if comReturn: return comReturn if request.method == method_get[0] and not request.args.get('path') and not pdata: - import system - data = system.system().GetConcifInfo() - data['recycle_bin'] = os.path.exists('data/recycle_bin.pl') - data['lan'] = public.GetLan('files') - data['js_random'] = get_js_random() - return render_template('files.html', data=data) + return index_new('files') import files filesObject = files.files() - defs = ('files_search', 'files_replace', 'get_replace_logs', 'get_images_resize', 'add_files_rsync', - 'get_file_attribute', 'get_file_hash', 'CreateLink', 'get_progress', 'restore_website', 'fix_permissions', - 'get_all_back', - 'restore_path_permissions', 'del_path_premissions', 'get_path_premissions', 'back_path_permissions', - 'upload_file_exists', - 'CheckExistsFiles', 'GetExecLog', 'GetSearch', 'ExecShell', 'GetExecShellMsg', 'exec_git', 'exec_composer', - 'create_download_url', - 'UploadFile', 'GetDir', 'CreateFile', 'CreateDir', 'DeleteDir', 'DeleteFile', 'get_download_url_list', - 'remove_download_url', 'modify_download_url', - 'CopyFile', 'CopyDir', 'MvFile', 'GetFileBody', 'SaveFileBody', 'Zip', 'UnZip', 'get_download_url_find', - 'set_file_ps', - 'SearchFiles', 'upload', 'read_history', 're_history', 'auto_save_temp', 'get_auto_save_body', 'get_videos', - 'GetFileAccess', 'SetFileAccess', 'GetDirSize', 'SetBatchData', 'BatchPaste', 'install_rar', - 'get_path_size', - 'DownloadFile', 'GetTaskSpeed', 'CloseLogs', 'InstallSoft', 'UninstallSoft', 'SaveTmpFile', + defs = ('files_search', 'files_replace', 'get_replace_logs', + 'get_images_resize', 'add_files_rsync', 'get_file_attribute', + 'get_file_hash', 'CreateLink', 'get_progress', 'restore_website', + 'fix_permissions', 'get_all_back', 'restore_path_permissions', + 'del_path_premissions', 'get_path_premissions', + 'back_path_permissions', 'upload_file_exists', 'CheckExistsFiles', + 'GetExecLog', 'GetSearch', 'ExecShell', 'GetExecShellMsg', + 'exec_git', 'exec_composer', 'create_download_url', 'UploadFile', + 'GetDir', 'CreateFile', 'CreateDir', 'DeleteDir', 'DeleteFile', + 'get_download_url_list', 'remove_download_url', + 'modify_download_url', 'CopyFile', 'CopyDir', 'MvFile', + 'GetFileBody', 'SaveFileBody', 'Zip', 'UnZip', + 'get_download_url_find', 'set_file_ps', 'SearchFiles', 'upload', + 'read_history', 're_history', 'auto_save_temp', + 'get_auto_save_body', 'get_videos', 'GetFileAccess', + 'SetFileAccess', 'GetDirSize', 'SetBatchData', 'BatchPaste', + 'install_rar', 'get_path_size', 'DownloadFile', 'GetTaskSpeed', + 'CloseLogs', 'InstallSoft', 'UninstallSoft', 'SaveTmpFile', 'get_composer_version', 'exec_composer', 'update_composer', - 'GetTmpFile', 'del_files_store', 'add_files_store', 'get_files_store', 'del_files_store_types', - 'add_files_store_types', 'exec_git', - 'RemoveTask', 'ActionTask', 'Re_Recycle_bin', 'Get_Recycle_bin', 'Del_Recycle_bin', 'Close_Recycle_bin', - 'Recycle_bin', 'file_webshell_check', 'dir_webshell_check', - 'files_search', 'files_replace', 'get_replace_logs' - ) + 'GetTmpFile', 'del_files_store', 'add_files_store', + 'get_files_store', 'del_files_store_types', + 'add_files_store_types', 'exec_git', 'RemoveTask', 'ActionTask', + 'Re_Recycle_bin', 'Get_Recycle_bin', 'Del_Recycle_bin', + 'Close_Recycle_bin', 'Recycle_bin', 'file_webshell_check', + 'dir_webshell_check', 'files_search', 'files_replace', + 'get_replace_logs') return publicObject(filesObject, defs, None, pdata) -@app.route('/crontab', methods=method_all) +@app.route('/crontab', methods=method_post) def crontab(pdata=None): # 计划任务 comReturn = comm.local() if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - import system - data = system.system().GetConcifInfo() - data['lan'] = public.GetLan('crontab') - data['js_random'] = get_js_random() - return render_template('crontab.html', data=data) import crontab crontabObject = crontab.crontab() - defs = ('GetCrontab', 'AddCrontab', 'GetDataList', 'GetLogs', 'DelLogs', 'DelCrontab', - 'StartTask', 'set_cron_status', 'get_crond_find', 'modify_crond', 'get_backup_list' - ) + defs = ('GetCrontab', 'AddCrontab', 'GetDataList', 'GetLogs', 'DelLogs', + 'DelCrontab', 'StartTask', 'set_cron_status', 'get_crond_find', + 'modify_crond', 'get_backup_list') return publicObject(crontabObject, defs, None, pdata) -@app.route('/soft', methods=method_all) -def soft(pdata=None): - # 软件商店页面 - comReturn = comm.local() - if comReturn: return comReturn - import system - data = system.system().GetConcifInfo() - data['lan'] = public.GetLan('soft') - data['js_random'] = get_js_random() - return render_template('soft.html', data=data) - - -@app.route('/config', methods=method_all) +@app.route('/config', methods=method_post) def config(pdata=None): # 面板设置页面 comReturn = comm.local() if comReturn: return comReturn - if request.method == method_get[0] and not pdata: - import system, wxapp, config - c_obj = config.config() - data = system.system().GetConcifInfo() - data['lan'] = public.GetLan('config') - try: - data['wx'] = wxapp.wxapp().get_user_info(None)['msg'] - except: - data['wx'] = 'INIT_WX_NOT_BIND' - data['api'] = '' - data['ipv6'] = '' - sess_out_path = 'data/session_timeout.pl' - if not os.path.exists(sess_out_path): public.writeFile(sess_out_path, '86400') - s_time_tmp = public.readFile(sess_out_path) - if not s_time_tmp: s_time_tmp = '0' - data['session_timeout'] = int(s_time_tmp) - if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' - if c_obj.get_token(None)['open']: data['api'] = 'checked' - data['basic_auth'] = c_obj.get_basic_auth_stat(None) - data['status_code'] = c_obj.get_not_auth_status() - data['basic_auth']['value'] = public.getMsg('CLOSED') - if data['basic_auth']['open']: data['basic_auth']['value'] = public.getMsg('OPENED') - data['debug'] = '' - data['js_random'] = get_js_random() - if app.config['DEBUG']: data['debug'] = 'checked' - data['is_local'] = '' - if public.is_local(): data['is_local'] = 'checked' - data['public_key'] = public.get_rsa_public_key().replace("\n", "") - return render_template('config.html', data=data) import config defs = ( - 'send_by_telegram', 'set_empty', 'set_backup_notification', 'get_panel_ssl_status', 'set_file_deny', - 'del_file_deny', 'get_file_deny', 'set_improvement', - 'get_httpd_access_log_format_parameter', 'set_httpd_format_log_to_website', 'get_httpd_access_log_format', - 'del_httpd_access_log_format', 'add_httpd_access_log_format', 'get_nginx_access_log_format_parameter', - 'set_format_log_to_website', 'get_nginx_access_log_format', 'del_nginx_access_log_format', 'set_click_logs', + 'send_by_telegram', + 'set_empty', + 'set_backup_notification', + 'get_panel_ssl_status', + 'set_file_deny', + 'del_file_deny', + 'get_file_deny', + 'set_improvement', + 'get_httpd_access_log_format_parameter', + 'set_httpd_format_log_to_website', + 'get_httpd_access_log_format', + 'del_httpd_access_log_format', + 'add_httpd_access_log_format', + 'get_nginx_access_log_format_parameter', + 'set_format_log_to_website', + 'get_nginx_access_log_format', + 'del_nginx_access_log_format', + 'set_click_logs', 'get_node_config', - 'add_nginx_access_log_format', 'get_ols_private_cache_status', 'get_ols_value', 'set_ols_value', + 'add_nginx_access_log_format', + 'get_ols_private_cache_status', + 'get_ols_value', + 'set_ols_value', 'set_node_config', - 'get_ols_private_cache', 'get_ols_static_cache', 'set_ols_static_cache', 'switch_ols_private_cache', + 'get_ols_private_cache', + 'get_ols_static_cache', + 'set_ols_static_cache', + 'switch_ols_private_cache', 'set_ols_private_cache', - 'set_coll_open', 'get_qrcode_data', 'check_two_step', 'set_two_step_auth', 'create_user', 'remove_user', + 'set_coll_open', + 'get_qrcode_data', + 'check_two_step', + 'set_two_step_auth', + 'create_user', + 'remove_user', 'modify_user', - 'get_key', 'get_php_session_path', 'set_php_session_path', 'get_cert_source', 'get_users', 'set_request_iptype', - 'set_local', 'set_debug', 'get_panel_error_logs', 'clean_panel_error_logs', 'get_menu_list', + 'get_key', + 'get_php_session_path', + 'set_php_session_path', + 'get_cert_source', + 'get_users', + 'set_request_iptype', + 'set_local', + 'set_debug', + 'get_panel_error_logs', + 'clean_panel_error_logs', + 'get_menu_list', 'set_hide_menu_list', - 'get_basic_auth_stat', 'set_basic_auth', 'get_cli_php_version', 'get_tmp_token', 'get_temp_login', - 'set_temp_login', 'remove_temp_login', 'clear_temp_login', 'get_temp_login_logs', - 'set_cli_php_version', 'DelOldSession', 'GetSessionCount', 'SetSessionConf', 'set_not_auth_status', - 'GetSessionConf', 'get_ipv6_listen', 'set_ipv6_status', 'GetApacheValue', 'SetApacheValue', + 'get_basic_auth_stat', + 'set_basic_auth', + 'get_cli_php_version', + 'get_tmp_token', + 'get_temp_login', + 'set_temp_login', + 'remove_temp_login', + 'clear_temp_login', + 'get_temp_login_logs', + 'set_cli_php_version', + 'DelOldSession', + 'GetSessionCount', + 'SetSessionConf', + 'set_not_auth_status', + 'GetSessionConf', + 'get_ipv6_listen', + 'set_ipv6_status', + 'GetApacheValue', + 'SetApacheValue', 'install_msg_module', - 'GetNginxValue', 'SetNginxValue', 'get_token', 'set_token', 'set_admin_path', 'is_pro', 'set_msg_config', - 'get_php_config', 'get_config', 'SavePanelSSL', 'GetPanelSSL', 'GetPHPConf', 'SetPHPConf', + 'GetNginxValue', + 'SetNginxValue', + 'get_token', + 'set_token', + 'set_admin_path', + 'is_pro', + 'set_msg_config', + 'get_php_config', + 'get_config', + 'SavePanelSSL', + 'GetPanelSSL', + 'GetPHPConf', + 'SetPHPConf', 'uninstall_msg_module', - 'GetPanelList', 'AddPanelInfo', 'SetPanelInfo', 'DelPanelInfo', 'ClickPanelInfo', 'SetPanelSSL', + 'GetPanelList', + 'AddPanelInfo', + 'SetPanelInfo', + 'DelPanelInfo', + 'ClickPanelInfo', + 'SetPanelSSL', 'get_msg_configs', - 'SetTemplates', 'Set502', 'setPassword', 'setUsername', 'setPanel', 'setPathInfo', 'setPHPMaxSize', + 'SetTemplates', + 'Set502', + 'setPassword', + 'setUsername', + 'setPanel', + 'setPathInfo', + 'setPHPMaxSize', 'get_msg_fun', - 'getFpmConfig', 'setFpmConfig', 'setPHPMaxTime', 'syncDate', 'setPHPDisable', 'SetControl', 'get_settings2', - 'del_tg_info', 'set_tg_bot', - 'ClosePanel', 'AutoUpdatePanel', 'SetPanelLock', 'return_mail_list', 'del_mail_list', 'add_mail_address', - 'user_mail_send', 'get_user_mail', 'set_dingding', 'get_dingding', - 'get_settings', 'user_stmp_mail_send', 'user_dingding_send', 'get_login_send', 'set_login_send', - 'clear_login_send', 'get_login_log', 'login_ipwhite', - 'set_ssl_verify', 'get_ssl_verify', 'get_password_config', 'set_password_expire', 'set_password_safe', + 'getFpmConfig', + 'setFpmConfig', + 'setPHPMaxTime', + 'syncDate', + 'setPHPDisable', + 'SetControl', + 'get_settings2', + 'del_tg_info', + 'set_tg_bot', + 'ClosePanel', + 'AutoUpdatePanel', + 'SetPanelLock', + 'return_mail_list', + 'del_mail_list', + 'add_mail_address', + 'user_mail_send', + 'get_user_mail', + 'set_dingding', + 'get_dingding', + 'get_settings', + 'user_stmp_mail_send', + 'user_dingding_send', + 'get_login_send', + 'set_login_send', + 'clear_login_send', + 'get_login_log', + 'login_ipwhite', + 'set_ssl_verify', + 'get_ssl_verify', + 'get_password_config', + 'set_password_expire', + 'set_password_safe', 'get_module_template', # 新增nps评分 - 'write_nps_new', 'get_nps_new', "check_nps", + 'write_nps_new', + 'get_nps_new', + "check_nps", # 提交报错信息 # 错误收集 'err_collection' + ) return publicObject(config.config(), defs, None, pdata) +@app.route('/config', methods=method_get) +def config_old(pdata=None): + # 面板设置页面 + comReturn = comm.local() + if comReturn: return comReturn + + import system, wxapp, config + c_obj = config.config() + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('config') + try: + data['wx'] = wxapp.wxapp().get_user_info(None)['msg'] + except: + data['wx'] = 'INIT_WX_NOT_BIND' + data['api'] = '' + data['ipv6'] = '' + sess_out_path = 'data/session_timeout.pl' + if not os.path.exists(sess_out_path): + public.writeFile(sess_out_path, '86400') + s_time_tmp = public.readFile(sess_out_path) + if not s_time_tmp: s_time_tmp = '0' + data['session_timeout'] = int(s_time_tmp) + if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + if c_obj.get_token(None)['open']: data['api'] = 'checked' + data['basic_auth'] = c_obj.get_basic_auth_stat(None) + data['status_code'] = c_obj.get_not_auth_status() + data['basic_auth']['value'] = public.getMsg('CLOSED') + if data['basic_auth']['open']: + data['basic_auth']['value'] = public.getMsg('OPENED') + data['debug'] = '' + data['js_random'] = get_js_random() + if app.config['DEBUG']: data['debug'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + data['public_key'] = public.get_rsa_public_key().replace("\n", "") + return render_template('config.html', data=data) + + @app.route('/ajax', methods=method_all) def ajax(pdata=None): # 面板系统服务状态接口 @@ -976,18 +1350,22 @@ def ajax(pdata=None): if comReturn: return comReturn import ajax ajaxObject = ajax.ajax() - defs = ('get_lines', 'php_info', 'change_phpmyadmin_ssl_port', 'set_phpmyadmin_ssl', 'get_phpmyadmin_ssl', 'get_pd', - 'check_user_auth', 'to_not_beta', 'get_beta_logs', 'apple_beta', 'GetApacheStatus', 'GetCloudHtml', - 'get_pay_type', - 'get_load_average', 'GetOpeLogs', 'GetFpmLogs', 'GetFpmSlowLogs', 'SetMemcachedCache', 'GetMemcachedStatus', - 'GetRedisStatus', 'GetWarning', 'SetWarning', 'CheckLogin', 'GetSpeed', 'GetAd', 'phpSort', 'ToPunycode', - 'GetBetaStatus', 'SetBeta', 'setPHPMyAdmin', 'delClose', 'KillProcess', 'GetPHPInfo', 'GetQiniuFileList', - 'get_process_tops', 'get_process_cpu_high', - 'UninstallLib', 'InstallLib', 'SetQiniuAS', 'GetQiniuAS', 'GetLibList', 'GetProcessList', 'GetNetWorkList', - 'GetNginxStatus', 'GetPHPStatus', 'GetTaskCount', 'GetSoftList', 'GetNetWorkIo', 'GetDiskIo', 'GetCpuIo', - 'CheckInstalled', 'UpdatePanel', 'GetInstalled', 'GetPHPConfig', 'SetPHPConfig', 'log_analysis', - 'speed_log', - 'get_result', 'get_detailed', 'ignore_version') + defs = ('get_lines', 'php_info', 'change_phpmyadmin_ssl_port', + 'set_phpmyadmin_ssl', 'get_phpmyadmin_ssl', 'get_pd', + 'check_user_auth', 'to_not_beta', 'get_beta_logs', 'apple_beta', + 'GetApacheStatus', 'GetCloudHtml', 'get_pay_type', + 'get_load_average', 'GetOpeLogs', 'GetFpmLogs', 'GetFpmSlowLogs', + 'SetMemcachedCache', 'GetMemcachedStatus', 'GetRedisStatus', + 'GetWarning', 'SetWarning', 'CheckLogin', 'GetSpeed', 'GetAd', + 'phpSort', 'ToPunycode', 'GetBetaStatus', 'SetBeta', + 'setPHPMyAdmin', 'delClose', 'KillProcess', 'GetPHPInfo', + 'GetQiniuFileList', 'get_process_tops', 'get_process_cpu_high', + 'UninstallLib', 'InstallLib', 'SetQiniuAS', 'GetQiniuAS', + 'GetLibList', 'GetProcessList', 'GetNetWorkList', 'GetNginxStatus', + 'GetPHPStatus', 'GetTaskCount', 'GetSoftList', 'GetNetWorkIo', + 'GetDiskIo', 'GetCpuIo', 'CheckInstalled', 'UpdatePanel', + 'GetInstalled', 'GetPHPConfig', 'SetPHPConfig', 'log_analysis', + 'speed_log', 'get_result', 'get_detailed', 'ignore_version') return publicObject(ajaxObject, defs, None, pdata) @@ -999,10 +1377,11 @@ def system(pdata=None): if comReturn: return comReturn import system sysObject = system.system() - defs = ('get_io_info', 'UpdatePro', 'GetAllInfo', 'GetNetWorkApi', 'GetLoadAverage', 'ClearSystem', - 'GetNetWorkOld', 'GetNetWork', 'GetDiskInfo', 'GetCpuInfo', 'GetBootTime', 'GetSystemVersion', - 'GetMemInfo', 'GetSystemTotal', 'GetConcifInfo', 'ServiceAdmin', 'ReWeb', 'RestartServer', 'ReMemory', - 'RepPanel') + defs = ('get_io_info', 'UpdatePro', 'GetAllInfo', 'GetNetWorkApi', + 'GetLoadAverage', 'ClearSystem', 'GetNetWorkOld', 'GetNetWork', + 'GetDiskInfo', 'GetCpuInfo', 'GetBootTime', 'GetSystemVersion', + 'GetMemInfo', 'GetSystemTotal', 'GetConcifInfo', 'ServiceAdmin', + 'ReWeb', 'RestartServer', 'ReMemory', 'RepPanel') return publicObject(sysObject, defs, None, pdata) @@ -1013,7 +1392,8 @@ def deployment(pdata=None): if comReturn: return comReturn import plugin_deployment sysObject = plugin_deployment.plugin_deployment() - defs = ('GetList', 'AddPackage', 'DelPackage', 'SetupPackage', 'GetSpeed', 'GetPackageOther') + defs = ('GetList', 'AddPackage', 'DelPackage', 'SetupPackage', 'GetSpeed', + 'GetPackageOther') return publicObject(sysObject, defs, None, pdata) @@ -1036,30 +1416,30 @@ def ssl(pdata=None): if comReturn: return comReturn import panelSSL toObject = panelSSL.panelSSL() - defs = ('check_url_txt', 'RemoveCert', 'renew_lets_ssl', 'SetCertToSite', 'GetCertList', 'SaveCert', 'GetCert', - 'GetCertName', 'again_verify', - 'DelToken', 'GetToken', 'GetUserInfo', 'GetOrderList', 'GetDVSSL', 'Completed', 'SyncOrder', - 'download_cert', 'set_cert', 'cancel_cert_order', - 'get_order_list', 'get_order_find', 'apply_order_pay', 'get_pay_status', 'apply_order', 'get_verify_info', - 'get_verify_result', 'get_product_list', 'set_verify_info', - 'GetSSLInfo', 'downloadCRT', 'GetSSLProduct', 'Renew_SSL', 'Get_Renew_SSL', - # 新增 购买证书对接接口 - 'get_product_list_v2', 'apply_cert_order_pay', 'get_cert_admin', 'apply_order_ca', 'apply_cert_install_pay', - - # 'pay_test' - - ) + defs = ( + 'check_url_txt', 'RemoveCert', 'renew_lets_ssl', 'SetCertToSite', 'GetCertList', + 'SaveCert', 'GetCert', 'GetCertName', 'again_verify', 'DelToken', 'GetToken', + 'GetUserInfo', 'GetOrderList', 'GetDVSSL', 'Completed', 'SyncOrder', 'download_cert', + 'set_cert', 'cancel_cert_order', 'get_order_list', 'get_order_find', 'apply_order_pay', + 'get_pay_status', 'apply_order', 'get_verify_info', 'get_verify_result', 'get_product_list', + 'set_verify_info', 'GetSSLInfo', 'downloadCRT', 'GetSSLProduct', 'Renew_SSL', 'Get_Renew_SSL', + # 新增 购买证书对接接口 + 'get_product_list_v2', 'apply_cert_order_pay', 'get_cert_admin', 'apply_order_ca', + 'apply_cert_install_pay', + # 'pay_test' + ) get = get_input() if get.action == 'download_cert': from io import BytesIO import base64 result = toObject.download_cert(get) - # public.print_log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@1111111111111111 result: {}".format(result)) - # {'success': False, 'res': '[code: 0] no data [file: /www/wwwroot/192.168.1.139/app/Api/Cert/controllers/Cert.php] [line: 955]', 'nonce': 1706498844} fp = BytesIO(base64.b64decode(result['res']['data'])) - return send_file(fp, download_name=result['res']['filename'], as_attachment=True, mimetype='application/zip') + return send_file(fp, + download_name=result['res']['filename'], + as_attachment=True, + mimetype='application/zip') result = publicObject(toObject, defs, get.action, get) return result @@ -1071,7 +1451,8 @@ def task(pdata=None): if comReturn: return comReturn import panelTask toObject = panelTask.bt_task() - defs = ('get_task_lists', 'remove_task', 'get_task_find', "get_task_log_by_id") + defs = ('get_task_lists', 'remove_task', 'get_task_find', + "get_task_log_by_id") result = publicObject(toObject, defs, None, pdata) return result @@ -1083,14 +1464,15 @@ def plugin(pdata=None): if comReturn: return comReturn import panelPlugin pluginObject = panelPlugin.panelPlugin() - defs = ( - 'get_usually_plugin', 'check_install_limit', 'set_score', 'get_score', 'update_zip', 'input_zip', 'export_zip', - 'add_index', 'remove_index', 'sort_index', - 'install_plugin', 'uninstall_plugin', 'get_soft_find', 'get_index_list', 'get_soft_list', 'get_cloud_list', - 'check_deps', 'flush_cache', 'GetCloudWarning', 'install', 'unInstall', 'getPluginList', 'getPluginInfo', - 'get_make_args', 'add_make_args', - 'getPluginStatus', 'setPluginStatus', 'a', 'getCloudPlugin', 'getConfigHtml', 'savePluginSort', 'del_make_args', - 'set_make_args') + defs = ('get_usually_plugin', 'check_install_limit', 'set_score', + 'get_score', 'update_zip', 'input_zip', 'export_zip', 'add_index', + 'remove_index', 'sort_index', 'install_plugin', 'uninstall_plugin', + 'get_soft_find', 'get_index_list', 'get_soft_list', + 'get_cloud_list', 'check_deps', 'flush_cache', 'GetCloudWarning', + 'install', 'unInstall', 'getPluginList', 'getPluginInfo', + 'get_make_args', 'add_make_args', 'getPluginStatus', + 'setPluginStatus', 'a', 'getCloudPlugin', 'getConfigHtml', + 'savePluginSort', 'del_make_args', 'set_make_args') return publicObject(pluginObject, defs, None, pdata) @@ -1102,7 +1484,8 @@ def panel_wxapp(pdata=None): if comReturn: return comReturn import wxapp toObject = wxapp.wxapp() - defs = ('blind', 'get_safe_log', 'blind_result', 'get_user_info', 'blind_del', 'blind_qrcode') + defs = ('blind', 'get_safe_log', 'blind_result', 'get_user_info', + 'blind_del', 'blind_qrcode') result = publicObject(toObject, defs, None, pdata) return result @@ -1114,16 +1497,19 @@ def auth(pdata=None): if comReturn: return comReturn import panelAuth toObject = panelAuth.panelAuth() - defs = ('free_trial', 'renew_product_auth', 'auth_activate', 'get_product_auth', 'get_stripe_session_id', - 'get_re_order_status_plugin', 'create_plugin_other_order', 'get_order_stat', - 'get_voucher_plugin', 'create_order_voucher_plugin', 'get_product_discount_by', - 'get_re_order_status', 'create_order_voucher', 'create_order', 'get_order_status', - 'get_voucher', 'flush_pay_status', 'create_serverid', 'check_serverid', - 'get_plugin_list', 'check_plugin', 'get_buy_code', 'check_pay_status', + defs = ('free_trial', 'renew_product_auth', 'auth_activate', + 'get_product_auth', 'get_stripe_session_id', + 'get_re_order_status_plugin', 'create_plugin_other_order', + 'get_order_stat', 'get_voucher_plugin', + 'create_order_voucher_plugin', 'get_product_discount_by', + 'get_re_order_status', 'create_order_voucher', 'create_order', + 'get_order_status', 'get_voucher', 'flush_pay_status', + 'create_serverid', 'check_serverid', 'get_plugin_list', + 'check_plugin', 'get_buy_code', 'check_pay_status', 'get_renew_code', 'check_renew_code', 'get_business_plugin', - 'get_ad_list', 'check_plugin_end', 'get_plugin_price', 'get_plugin_remarks', - 'get_paypal_session_id', 'check_paypal_status', - ) + 'get_ad_list', 'check_plugin_end', 'get_plugin_price', + 'get_plugin_remarks', 'get_paypal_session_id', + 'check_paypal_status') result = publicObject(toObject, defs, None, pdata) return result @@ -1136,20 +1522,26 @@ def download(): filename = request.args.get('filename') if filename.find('|') != -1: filename = filename.split('|')[0] # 改为获取本地备份 - if not filename: return public.ReturnJson(False, "INIT_ARGS_ERR"), json_header + if not filename: + return public.ReturnJson(False, "INIT_ARGS_ERR"), json_header # if filename in ['alioss','qiniu','upyun','txcos','ftp','msonedrive','gcloud_storage', 'gdrive', 'aws_s3']: return panel_cloud() - if not os.path.exists(filename): return public.ReturnJson(False, "FILE_NOT_EXISTS"), json_header + if not os.path.exists(filename): + return public.ReturnJson(False, "FILE_NOT_EXISTS"), json_header if request.args.get('play') == 'true': import panelVideo start, end = panelVideo.get_range(request) + g.return_message = True return panelVideo.partial_response(filename, start, end) else: mimetype = "application/octet-stream" extName = filename.split('.')[-1] if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None - public.WriteLog("TYPE_FILE", 'FILE_DOWNLOAD', (filename, public.GetClientIp())) - return send_file(filename, mimetype=mimetype, + public.WriteLog("TYPE_FILE", 'FILE_DOWNLOAD', + (filename, public.GetClientIp())) + g.return_message = True + return send_file(filename, + mimetype=mimetype, as_attachment=True, etag=True, conditional=True, @@ -1163,7 +1555,8 @@ def panel_cloud(is_csrf=True): comReturn = comm.local() if comReturn: return comReturn if is_csrf: - if not check_csrf(): return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header get = get_input() _filename = get.filename plugin_name = "" @@ -1172,19 +1565,25 @@ def panel_cloud(is_csrf=True): else: plugin_name = get.filename - if not os.path.exists('plugin/' + plugin_name + '/' + plugin_name + '_main.py'): - return public.returnJson(False, 'The specified plugin does not exist!'), json_header + if not os.path.exists('plugin/' + plugin_name + '/' + plugin_name + + '_main.py'): + return public.returnJson( + False, 'The specified plugin does not exist!'), json_header public.package_path_append('plugin/' + plugin_name) plugin_main = __import__(plugin_name + '_main') public.mod_reload(plugin_main) tmp = eval("plugin_main.%s_main()" % plugin_name) - if not hasattr(tmp, 'download_file'): return public.returnJson(False, - 'Specified plugin has no file download function!'), json_header + if not hasattr(tmp, 'download_file'): + return public.returnJson( + False, + 'Specified plugin has no file download function!'), json_header download_url = tmp.download_file(get.name) if plugin_name == 'ftp': - if download_url.find("ftp") != 0: download_url = "ftp://" + download_url + if download_url.find("ftp") != 0: + download_url = "ftp://" + download_url else: - if download_url.find('http') != 0: download_url = 'http://' + download_url + if download_url.find('http') != 0: + download_url = 'http://' + download_url if "toserver" in get and get.toserver == "true": download_dir = "/tmp/" @@ -1200,13 +1599,15 @@ def panel_cloud(is_csrf=True): if os.path.isfile(local_file): return { "status": True, - "msg": "The file already exists and will be restored locally.", + "msg": + "The file already exists and will be restored locally.", "task_id": -1, "local_file": local_file } from panelTask import bt_task task_obj = bt_task() - task_id = task_obj.create_task('Download file', 1, download_url, local_file) + task_id = task_obj.create_task('Download file', 1, download_url, + local_file) return { "status": True, "msg": "The download task was created successfully", @@ -1252,17 +1653,26 @@ def proxy_rspamd_requests(path): for h in request.headers.keys(): headers[h] = request.headers[h] if request.method == "GET": - if re.search("\.(js|css)$", path): - return send_file('/usr/share/rspamd/www/rspamd/' + path, conditional=True, etag=True) + if re.search(r"\.(js|css)$", path): + return send_file('/usr/share/rspamd/www/rspamd/' + path, + conditional=True, + etag=True) if path == "/": - return send_file('/usr/share/rspamd/www/rspamd/', conditional=True, etag=True) + return send_file('/usr/share/rspamd/www/rspamd/', + conditional=True, + etag=True) url = "http://127.0.0.1:11334/rspamd/" + path + "?" + param - for i in ['stat', 'auth', 'neighbours', 'list_extractors', 'list_transforms', 'graph', 'maps', 'actions', - 'symbols', 'history', 'errors', 'check_selector', 'saveactions', 'savesymbols', 'getmap']: + for i in [ + 'stat', 'auth', 'neighbours', 'list_extractors', + 'list_transforms', 'graph', 'maps', 'actions', 'symbols', + 'history', 'errors', 'check_selector', 'saveactions', + 'savesymbols', 'getmap' + ]: if i in path: url = "http://127.0.0.1:11334/" + path + "?" + param req = requests.get(url, headers=headers, stream=True) - return Resp(stream_with_context(req.iter_content()), content_type=req.headers['content-type']) + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) else: url = "http://127.0.0.1:11334/" + path for i in request.form.keys(): @@ -1270,7 +1680,8 @@ def proxy_rspamd_requests(path): # public.writeFile('/tmp/2',data+"\n","a+") req = requests.post(url, data=data, headers=headers, stream=True) - return Resp(stream_with_context(req.iter_content()), content_type=req.headers['content-type']) + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) @app.route('/tips', methods=method_get) @@ -1279,16 +1690,14 @@ def tips(): comReturn = comm.local() if comReturn: return abort(404) get = get_input() - if len(get.__dict__.keys()) > 1: return abort(404) + if len(get.get_items().keys()) > 1: return abort(404) return render_template('tips.html') # ======================普通路由区============================# - # ======================严格排查区域============================# - route_path = os.path.join(admin_path, '') if not route_path: route_path = '/' if route_path[-1] == '/': route_path = route_path[:-1] @@ -1303,7 +1712,8 @@ def login(): if os.path.exists('install.pl'): return redirect('/install') global admin_check_auth, admin_path, route_path is_auth_path = False - if admin_path != '/bt' and os.path.exists(admin_path_file) and not 'admin_auth' in session: + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: is_auth_path = True # 登录输入验证 if request.method == method_post[0]: @@ -1315,9 +1725,12 @@ def login(): if v in ['username', 'password']: continue pv = request.form.get(v, '').strip() if v == 'cdn_url': - if len(pv) > 32: return public.return_msg_gettext(False, 'Wrong parameter length!'), json_header - if not re.match(r"^[\w\.-]+$", pv): public.return_msg_gettext(False, - 'Wrong parameter format!'), json_header + if len(pv) > 32: + return public.return_msg_gettext( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^[\w\.-]+$", pv): + public.return_msg_gettext( + False, 'Wrong parameter format!'), json_header continue if not pv: continue @@ -1325,20 +1738,28 @@ def login(): if v == 'code': p_len = 4 if v == 'vcode': p_len = 6 if len(pv) != p_len: - if v == 'code': return public.returnJson(False, 'Verification code length error!'), json_header - return public.returnJson(False, 'Wrong parameter length!'), json_header + if v == 'code': + return public.returnJson( + False, 'Verification code length error!'), json_header + return public.returnJson( + False, 'Wrong parameter length!'), json_header if not re.match(r"^\w+$", pv): - return public.returnJson(False, 'Wrong parameter format!'), json_header - + return public.returnJson( + False, 'Wrong parameter format!'), json_header for n in request.form.keys(): + if not n in v_list: - return public.returnJson(False, 'There can be no extra parameters in the login parameters'), json_header + return public.returnJson( + False, + 'There can be no extra parameters in the login parameters' + ), json_header get = get_input() import userlogin if hasattr(get, 'tmp_token'): result = userlogin.userlogin().request_tmp(get) return is_login(result) + # 过滤爬虫 if public.is_spider(): return abort(404) if hasattr(get, 'dologin'): @@ -1348,8 +1769,10 @@ def login(): if session['login'] != False: session['login'] = False cache.set('dologin', True) - public.write_log_gettext('Logout', 'Client: {}, has manually exited the panel', - (public.GetClientIp() + ":" + str(request.environ.get('REMOTE_PORT')),)) + public.write_log_gettext( + 'Logout', 'Client: {}, has manually exited the panel', + (public.GetClientIp() + ":" + + str(request.environ.get('REMOTE_PORT')),)) if 'tmp_login_expire' in session: s_file = 'data/session/{}'.format(session['tmp_login_id']) if os.path.exists(s_file): @@ -1383,7 +1806,8 @@ def login(): session['admin_auth'] = True comReturn = common.panelSetup().init() - if comReturn: return comReturn + if comReturn: + return comReturn if request.method == method_post[0]: result = userlogin.userlogin().request_post(get) @@ -1404,7 +1828,10 @@ def login(): else: data['hosts'] = json.dumps(data['hosts']) data['app_login'] = os.path.exists('data/app_login.pl') - public.cache_set(public.Md5(uuid.UUID(int=uuid.getnode()).hex[-12:] + public.GetClientIp()), 'check', 360) + public.cache_set( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp()), 'check', 360) # 生成登录token last_key = 'last_login_token' @@ -1432,19 +1859,14 @@ def login(): # return render_template('login.html', data=data) +# 新增面板内注册 @app.route('/userRegister', methods=method_all) def userRegister(): comReturn = comm.local() if comReturn: return comReturn - # from panelSafeController import SafeController - # project_obj = SafeController() import userRegister reg = userRegister.userRegister() - defs = ('toRegister', ) - # get = get_input() - # get.action = 'model' - # get.mod_name = mod_name - # get.def_name = def_name + defs = ('toRegister',) return publicObject(reg, defs, None, None) @@ -1463,11 +1885,12 @@ def get_app_bind_status(pdata=None): # APP绑定状态查询 if not public.check_app('app_bind'): return abort(404) get = get_input() - if len(get.__dict__.keys()) > 2: return 'There are meaningless parameters!' + if len(get.get_items().keys()) > 2: return 'There are meaningless parameters!' v_list = ['bind_token', 'data'] - for n in get.__dict__.keys(): + for n in get.get_items().keys(): if not n in v_list: - return public.returnJson(False, 'There can be no redundant parameters'), json_header + return public.returnJson( + False, 'There can be no redundant parameters'), json_header import panelApi api_object = panelApi.panelApi() return json.dumps(api_object.get_app_bind_status(get_input())), json_header @@ -1478,11 +1901,12 @@ def check_bind(pdata=None): # APP绑定查询 if not public.check_app('app_bind'): return abort(404) get = get_input() - if len(get.__dict__.keys()) > 4: return 'There are meaningless parameters!' + if len(get.get_items().keys()) > 4: return 'There are meaningless parameters!' v_list = ['bind_token', 'client_brand', 'client_model', 'data'] - for n in get.__dict__.keys(): + for n in get.get_items().keys(): if not n in v_list: - return public.returnJson(False, 'There can be no redundant parameters'), json_header + return public.returnJson( + False, 'There can be no redundant parameters'), json_header import panelApi api_object = panelApi.panelApi() return json.dumps(api_object.check_bind(get_input())), json_header @@ -1527,16 +1951,19 @@ def down(token=None, fname=None): if fname: fname = fname.strip('/') if not token: return abort(404) if len(token) > 48: return abort(404) - char_list = ['\\', '/', ':', '*', '?', '"', '<', '>', '|', ';', '&', '`'] + char_list = [ + '\\', '/', ':', '*', '?', '"', '<', '>', '|', ';', '&', '`' + ] for char in char_list: if char in token: return abort(404) if not request.args.get('play') in ['true', None, '']: return abort(404) args = get_input() v_list = ['fname', 'play', 'file_password', 'data'] - for n in args.__dict__.keys(): + for n in args.get_items().keys(): if not n in v_list: - return public.returnJson(False, 'There can be no redundant parameters'), json_header + return public.returnJson( + False, 'There can be no redundant parameters'), json_header if not re.match(r"^[\w\.]+$", token): return abort(404) find = public.M('download_token').where('token=?', (token,)).find() @@ -1547,12 +1974,14 @@ def down(token=None, fname=None): if find['password'] and not token in session: if 'file_password' in args: if not re.match(r"^\w+$", args.file_password): - return public.ReturnJson(False, 'Wrong password!'), json_header + return public.ReturnJson(False, + 'Wrong password!'), json_header if re.match(r"^\d+$", args.file_password): args.file_password = str(int(args.file_password)) args.file_password += ".0" if args.file_password != str(find['password']): - return public.ReturnJson(False, 'Wrong password!'), json_header + return public.ReturnJson(False, + 'Wrong password!'), json_header session[token] = 1 session['down'] = True else: @@ -1595,7 +2024,8 @@ def down(token=None, fname=None): extName = filename.split('.')[-1] if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None b_name = os.path.basename(filename) - return send_file(filename, mimetype=mimetype, + return send_file(filename, + mimetype=mimetype, as_attachment=True, download_name=b_name, max_age=0) @@ -1603,8 +2033,15 @@ def down(token=None, fname=None): return abort(404) -@app.route('/database//', methods=method_all) -def databaseModel(mod_name, def_name): +@app.route('/database/mongodb/', methods=method_all) +@app.route('/database/pgsql/', methods=method_all) +@app.route('/database/redis/', methods=method_all) +@app.route('/database/sqlite/', methods=method_all) +@app.route('/database/sqlserver/', methods=method_all) +def databaseModel(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 4: return comReturn = comm.local() if comReturn: return comReturn from panelDatabaseController import DatabaseController @@ -1612,15 +2049,23 @@ def databaseModel(mod_name, def_name): defs = ('model',) get = get_input() get.action = 'model' - get.mod_name = mod_name + get.mod_name = path_split[2] get.def_name = def_name return publicObject(project_obj, defs, None, get) # 系统安全模型页面 -@app.route('/safe//', methods=method_all) -def safeModel(mod_name, def_name): +@app.route('/safe/firewall/', methods=method_all) +@app.route('/safe/freeip/', methods=method_all) +@app.route('/safe/ips/', methods=method_all) +@app.route('/safe/security/', methods=method_all) +@app.route('/safe/ssh/', methods=method_all) +@app.route('/safe/syslog/', methods=method_all) +def safeModel(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 4: return comReturn = comm.local() if comReturn: return comReturn from panelSafeController import SafeController @@ -1628,7 +2073,7 @@ def safeModel(mod_name, def_name): defs = ('model',) get = get_input() get.action = 'model' - get.mod_name = mod_name + get.mod_name = path_split[2] get.def_name = def_name return publicObject(project_obj, defs, None, get) @@ -1651,13 +2096,14 @@ def allModule(index, mod_name, def_name): get.action = 'model' get.mod_name = mod_name get.def_name = def_name + return publicObject(controller_obj, defs, None, get) @app.route('/public', methods=method_all) def panel_public(): get = get_input() - if len("{}".format(get.__dict__)) > 1024 * 32: + if len("{}".format(get.get_items())) > 1024 * 32: return 'ERROR' # 获取ping测试 @@ -1675,28 +2121,37 @@ def panel_public(): return abort(404) if public.cache_get( - public.Md5(uuid.UUID(int=uuid.getnode()).hex[-12:] + public.GetClientIp())) != 'check': return abort(404) + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp())) != 'check': + return abort(404) global admin_check_auth, admin_path, route_path, admin_path_file - if admin_path != '/bt' and os.path.exists(admin_path_file) and not 'admin_auth' in session: + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: return abort(404) v_list = ['fun', 'name', 'filename', 'data', 'secret_key'] - for n in get.__dict__.keys(): + for n in get.get_items().keys(): if not n in v_list: return abort(404) get.client_ip = public.GetClientIp() num_key = get.client_ip + '_wxapp' if not public.get_error_num(num_key, 10): - return public.return_msg_gettext(False, '10 consecutive authentication failures are prohibited for 1 hour') + return public.return_msg_gettext( + False, + '10 consecutive authentication failures are prohibited for 1 hour') if not hasattr(get, 'name'): get.name = '' if not hasattr(get, 'fun'): return abort(404) - if not public.path_safe_check("%s/%s" % (get.name, get.fun)): return abort(404) + if not public.path_safe_check("%s/%s" % (get.name, get.fun)): + return abort(404) if get.fun in ['login_qrcode', 'is_scan_ok', 'set_login']: # 检查是否验证过安全入口 - if admin_path != '/bt' and os.path.exists(admin_path_file) and not 'admin_auth' in session: + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: return abort(404) # 验证是否绑定了设备 - if not public.check_app('app'): return public.return_msg_gettext(False, 'Unbound user') + if not public.check_app('app'): + return public.return_msg_gettext(False, 'Unbound user') import wxapp pluwx = wxapp.wxapp() checks = pluwx._check(get) @@ -1712,6 +2167,9 @@ def panel_public(): @app.route('//', methods=method_all) @app.route('///', methods=method_all) def panel_other(name=None, fun=None, stype=None): + if name in ('site', 'database', 'docker', 'wp', 'mail'): + return index_new('{}/{}'.format(name, fun)) + # 插件接口 if public.is_error_path(): return redirect('/error', 302) @@ -1724,7 +2182,7 @@ def panel_other(name=None, fun=None, stype=None): if not stype: tmp = fun.split('.') fun = tmp[0] - if len(tmp) == 1: tmp.append('') + if len(tmp) == 1: tmp.append('') stype = tmp[1] if fun: if name == 'btwaf' and fun == 'index': @@ -1736,16 +2194,23 @@ def panel_other(name=None, fun=None, stype=None): elif stype == 'html': pass else: - if public.get_csrf_cookie_token_key() in session and 'login' in session: - if not check_csrf(): return public.ReturnJson(False, - 'CSRF calibration failed, please login again'), json_header + if public.get_csrf_cookie_token_key( + ) in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson( + False, + 'CSRF calibration failed, please login again' + ), json_header args = None else: p_path = public.get_plugin_path() + '/' + name if not os.path.exists(p_path): return abort(404) args = get_input() - args_list = ['mail_from', 'password', 'mail_to', 'subject', 'content', 'subtype', 'data'] - for k in args.__dict__: + args_list = [ + 'mail_from', 'password', 'mail_to', 'subject', 'content', + 'subtype', 'data' + ] + for k in args.get_items(): if not k in args_list: return abort(404) is_accept = False @@ -1753,13 +2218,17 @@ def panel_other(name=None, fun=None, stype=None): if not stype: tmp = fun.split('.') fun = tmp[0] - if len(tmp) == 1: tmp.append('') + if len(tmp) == 1: tmp.append('') stype = tmp[1] if not name: name = 'coll' - if not public.path_safe_check("%s/%s/%s" % (name, fun, stype)): return abort(404) - if name.find('./') != -1 or not re.match(r"^[\w-]+$", name): return abort(404) - if not name: return public.returnJson(False, 'Please pass in the plug-in name!'), json_header + if not public.path_safe_check("%s/%s/%s" % (name, fun, stype)): + return abort(404) + if name.find('./') != -1 or not re.match(r"^[\w-]+$", name): + return abort(404) + if not name: + return public.returnJson( + False, 'Please pass in the plug-in name!'), json_header p_path = public.get_plugin_path() + '/' + name if not os.path.exists(p_path): if name == 'btwaf' and fun == 'index': @@ -1778,7 +2247,8 @@ def panel_other(name=None, fun=None, stype=None): # 是否响插件应静态文件 if fun == 'static': - if stype.find('./') != -1 or not os.path.exists(p_path + '/static'): return abort(404) + if stype.find('./') != -1 or not os.path.exists(p_path + '/static'): + return abort(404) s_file = p_path + '/static/' + stype if s_file.find('..') != -1: return abort(404) if not re.match(r"^[\w\./-]+$", s_file): return abort(404) @@ -1793,66 +2263,20 @@ def panel_other(name=None, fun=None, stype=None): # 初始化插件对象 try: - is_php = os.path.exists(p_path + '/index.php') - if not is_php: - import panelPlugin - plu_panel = panelPlugin.panelPlugin() - plugin_list = plu_panel.get_cloud_list() - waf = 0 - if not 'pro' in plugin_list: plugin_list['pro'] = -1 - for p in plugin_list['list']: - if p['name'] in ['btwaf']: - if p['endtime'] != 0 and p['endtime'] < time.time(): - waf = -1 - try: - public.package_path_append(p_path) - plugin_main = __import__(name + '_main') - if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list['pro'] == -1: - return render_template('error3.html', data={}) - except: - if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list['pro'] == -1: - return render_template('error3.html', data={}) - if os.path.exists("{}/btwaf".format(public.get_plugin_path())): - return render_template('error3.html', data={}) - try: - if sys.version_info[0] == 2: - reload(plugin_main) - else: - from imp import reload - reload(plugin_main) - except: - pass - # public.print_log("plugin_main22222: {}".format(plugin_main)) - plu = eval('plugin_main.' + name + '_main()') - - # methods = dir(plu) - # # 遍历列表并打印每个方法的名称 - # for method in methods: - # # 排除以双下划线开头和结尾的特殊属性和方法 - # if not method.startswith("__") and not method.endswith("__"): - # public.print_log(method) - - if not hasattr(plu, fun): - # public.print_log("333333: {}".format(plu)) - # public.print_log("444444: {}".format(fun)) - return public.returnJson(False, 'Plugin does not exist'), json_header - # public.print_log("aaaa plu: {}".format(plu)) - # public.print_log("bbbbb fun: {}".format(fun)) - # 执行插件方法 - if not is_php: - if is_accept: - checks = plu._check(args) - if type(checks) != bool or not checks: - return public.getJson(checks), json_header - data = eval('plu.' + fun + '(args)') - else: - comReturn = comm.local() - if comReturn: return comReturn - import panelPHP + import PluginLoader + try: args.s = fun - args.name = name - data = panelPHP.panelPHP(name).exec_php_script(args) + data = PluginLoader.plugin_run(name, fun, args) + if isinstance(data, dict): + if 'status' in data and data['status'] == False and 'msg' in data: + if isinstance(data['msg'], str): + if data['msg'].find('加载失败') != -1 or data['msg'].find('Traceback ') == 0: + raise public.PanelError(data['msg']) + except Exception as ex: + if name == 'btwaf' and fun == 'index' and str(ex).find('未购买') != -1: + return render_template('error3.html', data={}) + return public.get_error_object(None, plugin_name=name) r_type = type(data) if r_type in [Response, Resp]: @@ -1865,7 +2289,9 @@ def panel_other(name=None, fun=None, stype=None): t_path_root = p_path + '/templates/' t_path = t_path_root + fun + '.html' if not os.path.exists(t_path): - return public.returnJson(False, 'The specified template does not exist!'), json_header + return public.returnJson( + False, + 'The specified template does not exist!'), json_header t_body = public.readFile(t_path) # 处理模板包含 @@ -1883,8 +2309,12 @@ def panel_other(name=None, fun=None, stype=None): r_type = type(data) if r_type == dict: if name == 'btwaf' and 'msg' in data: - return render_template('error3.html', data={"error_msg": data['msg']}) - return public.returnJson(False, public.getMsg('Bad return type [{}]').format(r_type)), json_header + return render_template('error3.html', + data={"error_msg": data['msg']}) + return public.returnJson( + False, + public.getMsg('Bad return type [{}]').format(r_type)), json_header + # public.getMsg('PUBLIC_ERR_RETURN')), json_header return data except: return public.get_error_info() @@ -1925,17 +2355,22 @@ def install(): elif request.method == method_post[0]: if not os.path.exists('install.pl'): return redirect(ret_login) get = get_input() - if not hasattr(get, 'bt_username'): return public.get_msg_gettext('The user name cannot be empty!') - if not get.bt_username: return public.get_msg_gettext('The user name cannot be empty!') - if not hasattr(get, 'bt_password1'): return public.get_msg_gettext('Password can not be blank!') - if not get.bt_password1: return public.get_msg_gettext('Password can not be blank!') - if get.bt_password1 != get.bt_password2: return public.get_msg_gettext( - 'The passwords entered twice do not match, please re-enter!') - public.M('users').where("id=?", (1,)).save('username,password', - (get.bt_username, - public.password_salt(public.md5(get.bt_password1.strip()), uid=1) - ) - ) + if not hasattr(get, 'bt_username'): + return public.get_msg_gettext('The user name cannot be empty!') + if not get.bt_username: + return public.get_msg_gettext('The user name cannot be empty!') + if not hasattr(get, 'bt_password1'): + return public.get_msg_gettext('Password can not be blank!') + if not get.bt_password1: + return public.get_msg_gettext('Password can not be blank!') + if get.bt_password1 != get.bt_password2: + return public.get_msg_gettext( + 'The passwords entered twice do not match, please re-enter!') + public.M('users').where("id=?", (1,)).save( + 'username,password', + (get.bt_username, + public.password_salt(public.md5(get.bt_password1.strip()), + uid=1))) os.remove('install.pl') public.M('config').where("id=?", ('1',)).setField('status', 1) data = {} @@ -1946,7 +2381,6 @@ def install(): # ==================================================# - # ======================公共方法区域START============================# @@ -1971,7 +2405,8 @@ def get_dir_down(filename, token, find): pdata['expire'] = public.format_date(times=find['expire']) else: pdata['expire'] = public.get_msg_gettext('Never Expires') - pdata['filename'] = (find['filename'].split('/')[-1] + '/' + to_path).strip('/') + pdata['filename'] = (find['filename'].split('/')[-1] + '/' + + to_path).strip('/') return render_template('down.html', data=pdata, to_size=public.to_size) @@ -1983,21 +2418,24 @@ def get_phpmyadmin_dir(): try: import re if session['webserver'] == 'nginx': - filename = public.GetConfigValue('setup_path') + '/nginx/conf/nginx.conf' + filename = public.GetConfigValue( + 'setup_path') + '/nginx/conf/nginx.conf' conf = public.readFile(filename) rep = r"listen\s+([0-9]+)\s*;" rtmp = re.search(rep, conf) if rtmp: phpport = rtmp.groups()[0] if session['webserver'] == 'apache': - filename = public.GetConfigValue('setup_path') + '/apache/conf/extra/httpd-vhosts.conf' + filename = public.GetConfigValue( + 'setup_path') + '/apache/conf/extra/httpd-vhosts.conf' conf = public.readFile(filename) rep = r"Listen\s+([0-9]+)\s*\n" rtmp = re.search(rep, conf) if rtmp: phpport = rtmp.groups()[0] if session['webserver'] == 'openlitespeed': - filename = public.GetConfigValue('setup_path') + '/panel/vhost/openlitespeed/listen/888.conf' + filename = public.GetConfigValue( + 'setup_path') + '/panel/vhost/openlitespeed/listen/888.conf' public.writeFile('/tmp/2', filename) conf = public.readFile(filename) rep = r"address\s*\*\:\s*(\d+)" @@ -2019,12 +2457,11 @@ class run_exec: # 模块访问对像 def run(self, toObject, defs, get): result = None - # public.print_log("OOOOOOOOOOOO))))))))))))))))))000000000000000000000000000000000run_exec: ") - # public.print_log("defs----: {}".format(defs)) - # public.print_log("get ----: {}".format(get)) if not get.action in defs: - return public.ReturnJson(False, 'Specific parameters are invalid!'), json_header + return public.ReturnJson( + False, 'Specific parameters are invalid!'), json_header + result = getattr(toObject, get.action)(get) if not hasattr(get, 'html') and not hasattr(get, 's_module'): r_type = type(result) @@ -2033,6 +2470,7 @@ class run_exec: if g.is_aes: result = public.aes_encrypt(result[0], g.aes_key), json_header + return result @@ -2048,15 +2486,19 @@ def check_csrf(): def publicObject(toObject, defs, action=None, get=None, is_csrf=True): try: # 模块访问前置检查 - if is_csrf and public.get_csrf_sess_html_token_value() and session.get('login', None): - if not check_csrf(): return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + if is_csrf and public.get_csrf_sess_html_token_value() and session.get( + 'login', None): + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header - if not get: get = get_input() + if not get: + get = get_input() if action: get.action = action if hasattr(get, 'path'): get.path = get.path.replace('//', '/').replace('\\', '/') - if get.path.find('./') != -1: return public.ReturnJson(False, 'Unsafe path'), json_header + if get.path.find('./') != -1: + return public.ReturnJson(False, 'Unsafe path'), json_header if get.path.find('->') != -1: get.path = get.path.split('->')[0].strip() get.path = public.xssdecode(get.path) @@ -2071,11 +2513,12 @@ def publicObject(toObject, defs, action=None, get=None, is_csrf=True): get.dfile = public.xssdecode(get.dfile) if hasattr(toObject, 'site_path_check'): - if not toObject.site_path_check(get): return public.ReturnJson(False, - "Overstepping one authority!"), json_header + if not toObject.site_path_check(get): + return public.ReturnJson( + False, "Overstepping one authority!"), json_header return run_exec().run(toObject, defs, get) - except: - return error_500(None) + except Exception as e: + return error_500(e) def check_login(http_token=None): @@ -2084,121 +2527,23 @@ def check_login(http_token=None): if 'login' in session: loginStatus = session['login'] if loginStatus and http_token: - if public.get_csrf_sess_html_token_value() != http_token: return False + if public.get_csrf_sess_html_token_value() != http_token: + return False return loginStatus return False def get_pd(): # 获取授权信息 - tmp = -1 - # try: - # import panelPlugin - # get = public.dict_obj() - # # get.init = 1 - # tmp1 = panelPlugin.panelPlugin().get_cloud_list(get) - # except: - tmp1 = None - if tmp1: - tmp = tmp1[public.to_string([112, 114, 111])] - ltd = tmp1.get('ltd', -1) - else: - ltd = -1 - tmp4 = cache.get(public.to_string([112, 95, 116, 111, 107, 101, 110])) - if tmp4: - tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4 - if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1') - tmp = public.readFile(tmp_f) - if tmp: tmp = int(tmp) - - if ltd < 1: - if ltd == -2: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, 100, - 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, - 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, - 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, - 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, - 36807, - 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, - 98, 116, - 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, - 115, 111, - 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, 82, - 69, 78, 69, 87, 60, 47, 97, - 62, 60, 47, 115, 112, 97, 110, 62]) - elif tmp == -1: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, - 116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, 111, 110, 99, 108, 105, 99, - 107, - 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114, - 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107, 32, - 116, 111, 32, - 103, 101, 116, 32, 80, 82, 79, 34, 62, 20813, 36153, 29256, 60, 47, 115, 112, 97, - 110, 62]) - elif tmp == -2: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, - 112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, - 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, - 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, - 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, - 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399, - 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, - 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, - 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114, - 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, - 62]) - if tmp >= 0 and ltd in [-1, -2]: - if tmp == 0: - tmp2 = public.to_string([76, 105, 102, 101, 116, 105, 109, 101]) - - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, 114, - 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 60, 115, 112, 97, 110, 32, 115, - 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, - 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, - 98, 111, 108, 100, 59, 34, 62, 123, 48, 125, 60, 47, 115, 112, 97, 110, 62, 60, - 47, 115, 112, 97, 110, 62]).format(tmp2) - - else: - tmp2 = time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(tmp)) - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, - 112, 114, 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, - 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, - 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, - 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, - 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, - 48, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, - 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, - 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, - 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, - 47, 115, 112, 97, 110, 62]).format(tmp2) - else: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, - 114, 111, 45, 103, 114, 97, 121, 34, 32, 111, 110, 99, 108, 105, 99, 107, - 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, - 114, 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107, - 32, 116, - 111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 70, 82, - 69, 69, 60, 47, 115, 112, 97, 110, 62]) - else: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, - 100, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, 97, 110, 32, 115, 116, - 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, - 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, - 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, - 112, 120, 34, 62, 123, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, - 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, - 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, - 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, - 112, 97, 110, 62]).format( - time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(ltd))) - - return tmp3, tmp, ltd + return public.get_pd() def send_authenticated(): # 发送http认证信息 request_host = public.GetHost() - result = Response('', 401, {'WWW-Authenticate': 'Basic realm="%s"' % request_host.strip()}) + result = Response( + '', 401, + {'WWW-Authenticate': 'Basic realm="%s"' % request_host.strip()}) if not 'login' in session and not 'admin_auth' in session: session.clear() return result @@ -2209,7 +2554,8 @@ def FtpPort(): if session.get('port'): return import re try: - file = public.GetConfigValue('setup_path') + '/pure-ftpd/etc/pure-ftpd.conf' + file = public.GetConfigValue( + 'setup_path') + '/pure-ftpd/etc/pure-ftpd.conf' conf = public.readFile(file) rep = r"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)" port = re.search(rep, conf).groups()[0] @@ -2222,20 +2568,6 @@ def is_login(result): # 判断是否登录2 if 'login' in session: if session['login'] == True: - # result = make_response(result) - # request_token = public.GetRandomString(48) - # request_token_key = public.get_csrf_cookie_token_key() - # session[request_token_key] = request_token - # samesite = app.config['SESSION_COOKIE_SAMESITE'] - # secure = app.config['SESSION_COOKIE_SECURE'] - # if app.config['SSL'] and request.full_path.find('/login?tmp_token=') == 0: - # samesite = 'None' - # secure = True - # result.set_cookie(request_token_key, request_token, - # max_age=86400 * 30, - # samesite= samesite, - # secure=secure - # ) pass return result @@ -2257,9 +2589,12 @@ def get_input(): data.set(key, str(request.args.get(key, ''))) try: for key in request.form.keys(): - if key in exludes: continue + if key in exludes: + continue data.set(key, str(request.form.get(key, ''))) - except: + + except Exception as ex: + try: post = request.form.to_dict() for key in post.keys(): @@ -2272,7 +2607,8 @@ def get_input(): for k in g.form_data.keys(): data.set(k, str(g.form_data[k])) - if not hasattr(data, 'data'): data.data = [] + if not hasattr(data, 'data'): + data.data = [] return data @@ -2357,53 +2693,65 @@ def ws_panel_thread(get): ''' if not hasattr(get, 'ws_callback'): - get._ws.send(public.getJson(public.return_status_code(1001, 'ws_callback'))) + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) return if not hasattr(get, 'mod_name'): - get._ws.send(public.getJson(public.return_status_code(1001, 'mod_name'))) + get._ws.send( + public.getJson(public.return_status_code(1001, 'mod_name'))) return if not hasattr(get, 'def_name'): - get._ws.send(public.getJson(public.return_status_code(1001, 'def_name'))) + get._ws.send( + public.getJson(public.return_status_code(1001, 'def_name'))) return get.mod_name = get.mod_name.strip() get.def_name = get.def_name.strip() check_str = '{}{}'.format(get.mod_name, get.def_name) - if not re.match("^\w+$", check_str) or get.mod_name in ['public', 'common', 'db', 'db_mysql', 'downloadFile', - 'jobs']: - get._ws.send(public.getJson(public.return_status_code(1000, 'Unsafe mod_name, def_name parameter content'))) + if not re.match(r"^\w+$", check_str) or get.mod_name in [ + 'public', 'common', 'db', 'db_mysql', 'downloadFile', 'jobs' + ]: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Unsafe mod_name, def_name parameter content'))) return - # if not hasattr(get, 'args'): - # get._ws.send(public.getJson(public.return_status_code(1001, 'args'))) - # return mod_file = '{}/{}.py'.format(public.get_class_path(), get.mod_name) + if not os.path.exists(mod_file): get._ws.send( - public.getJson(public.return_status_code(1000, 'Specified module {} does not exist'.format(get.mod_name)))) + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) return _obj = public.get_script_object(mod_file) if not _obj: get._ws.send( - public.getJson(public.return_status_code(1000, 'Specified module {} does not exist'.format(get.mod_name)))) + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) return _cls = getattr(_obj, get.mod_name) if not _cls: get._ws.send( - public.getJson(public.return_status_code(1000, - 'The {} object was not found in the {} module'.format(get.mod_name, - get.mod_name)))) + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.mod_name)))) return _def = getattr(_cls(), get.def_name) if not _def: get._ws.send( - public.getJson(public.return_status_code(1000, - 'The {} object was not found in the {} module'.format(get.mod_name, - get.def_name)))) + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.def_name)))) return - result = { - 'callback': get.ws_callback, - 'result': _def(get) - } + result = {'callback': get.ws_callback, 'result': _def(get)} get._ws.send(public.getJson(result)) @@ -2432,6 +2780,59 @@ def ws_project(ws): p.start() +# docker模块内用到的ws +@sockets.route('/ws_model') +def ws_model(ws): + ''' + @name 模型控制器ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + + comReturn = comm.local() + if comReturn: return comReturn + get = ws.receive() + get = json.loads(get) + + if not check_csrf_websocket(ws, get): return + + from panelController import Controller + model_obj = Controller() + while True: + pdata = ws.receive() + if pdata in ['{}', {}, None, '']: + ws.send(json.dumps(public.return_status_code(1000, '请求参数不能为空'))) + break + try: + get = public.to_dict_obj(json.loads(pdata)) + except: + request.form = { + "error": pdata + } + raise Exception('json load error !') + get._ws = ws + get.model_index = get.model_index.strip() + p = threading.Thread(target=ws_model_thread, args=(model_obj, get)) + p.start() + + +def ws_model_thread(_obj, get): + ''' + @name 模型控制器ws线程 + @author hwliang<2021-07-24> + @param _obj 控制器对像 + @param get 请求参数 + @return void + ''' + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + result = {'callback': get.ws_callback, 'result': _obj.model(get)} + get._ws.send(public.getJson(result)) + + def ws_project_thread(_obj, get): ''' @name 项目管理ws线程 @@ -2441,12 +2842,10 @@ def ws_project_thread(_obj, get): @return void ''' if not hasattr(get, 'ws_callback'): - get._ws.send(public.getJson(public.return_status_code(1001, 'ws_callback'))) + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) return - result = { - 'callback': get.ws_callback, - 'result': _obj.model(get) - } + result = {'callback': get.ws_callback, 'result': _obj.model(get)} get._ws.send(public.getJson(result)) @@ -2524,15 +2923,23 @@ def kill_closed(): def sock_recv(cmdstring, ws): global sock_pids try: - p = subprocess.Popen(cmdstring + " 2>&1", close_fds=True, shell=True, bufsize=4096, stdout=subprocess.PIPE, + p = subprocess.Popen(cmdstring + " 2>&1", + close_fds=True, + shell=True, + bufsize=4096, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) sock_pids[p.pid] = ws kill_closed() while p.poll() == None: send_line = p.stdout.readline().decode() if not send_line or send_line.find('tail: ') != -1: continue - ws.send(send_line) - ws.send(p.stdout.read().decode()) + # ws.send(send_line) + # ws.send(p.stdout.read().decode()) + if ws.connected: + ws.send(send_line) + if ws.connected: + ws.send(p.stdout.read().decode()) except: kill_closed() @@ -2550,16 +2957,20 @@ def close_sock_shell(): comReturn = comm.local() if comReturn: return comReturn args = get_input() - if not check_csrf(): return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header cmdstring = args.cmdstring.strip() skey = public.md5(cmdstring) pid = cache.get(skey) if not pid: return json.dumps( - public.return_data(False, [], error_msg='The specified sock has been terminated!')), json_header + public.return_data( + False, [], error_msg='The specified sock has been terminated!') + ), json_header os.kill(pid, 9) cache.delete(skey) - return json.dumps(public.return_data(True, 'Successful operation!')), json_header + return json.dumps(public.return_data(True, + 'Successful operation!')), json_header def check_csrf_websocket(ws, args): @@ -2615,7 +3026,8 @@ def webssh(ws): if 'pkey' in get: ssh_info['pkey'] = get['pkey'].strip() - if get['host'] in ['127.0.0.1', 'localhost'] and 'port' not in ssh_info: + if get['host'] in ['127.0.0.1', 'localhost' + ] and 'port' not in ssh_info: ssh_info = sp.get_ssh_info('127.0.0.1') if not ssh_info: ssh_info = sp.get_ssh_info('localhost') if not ssh_info: ssh_info = {"host": "127.0.0.1"} @@ -2630,7 +3042,9 @@ def webssh(ws): if not 'username' in ssh_info: ssh_info = sp.get_ssh_info(ssh_info['host']) if not ssh_info: - ws.send('The specified host information is not found, please add it again!') + ws.send( + 'The specified host information is not found, please add it again!' + ) return p = ssh_terminal.ssh_terminal() p.run(ws, ssh_info) @@ -2642,6 +3056,7 @@ def webssh(ws): # --------------------- websocket END -------------------------- # + @app.route("/daily", methods=method_all) def daily(): """面板日报数据""" @@ -2670,7 +3085,8 @@ def pma_proxy(path_full=None): pmd = cache.get(cache_key) if not pmd: pmd = get_phpmyadmin_dir() - if not pmd: return 'phpMyAdmin is not installed, please go to the [App Store] page to install it!' + if not pmd: + return 'phpMyAdmin is not installed, please go to the [App Store] page to install it!' pmd = list(pmd) cache.set(cache_key, pmd, 10) panel_pool = 'http://' @@ -2683,7 +3099,9 @@ def pma_proxy(path_full=None): else: panel_pool = 'http://' - proxy_url = '{}127.0.0.1:{}/{}/'.format(panel_pool, pmd[1], pmd[0]) + request.full_path.replace('/phpmyadmin/', '') + proxy_url = '{}127.0.0.1:{}/{}/'.format( + panel_pool, pmd[1], pmd[0]) + request.full_path.replace( + '/phpmyadmin/', '') from panelHttpProxy import HttpProxy px = HttpProxy() return px.proxy(proxy_url) @@ -2701,7 +3119,8 @@ def proxy_port(port, full_path=None): comReturn = comm.local() if comReturn: return comReturn - full_path = request.full_path.replace('/p/{}/'.format(port), '').replace('/p/{}'.format(port), '') + full_path = request.full_path.replace('/p/{}/'.format(port), + '').replace('/p/{}'.format(port), '') uri = '{}/{}'.format(port, full_path) uri = uri.replace('//', '/') proxy_url = 'http://127.0.0.1:{}'.format(uri) @@ -2716,8 +3135,2621 @@ def push(pdata=None): if comReturn: return comReturn import panelPush toObject = panelPush.panelPush() - defs = ('set_push_status', 'get_push_msg_list', 'get_modules_list', 'install_module', 'uninstall_module', - 'get_module_template', 'set_push_config', 'get_push_config', 'del_push_config', 'get_module_logs', - 'get_module_config', 'get_push_list', 'get_push_logs') + defs = ('set_push_status', 'get_push_msg_list', 'get_modules_list', + 'install_module', 'uninstall_module', 'get_module_template', + 'set_push_config', 'get_push_config', 'del_push_config', + 'get_module_logs', 'get_module_config', 'get_push_list', + 'get_push_logs') result = publicObject(toObject, defs, None, pdata) return result + + +# ===========================================================v2路由区start===========================================================# +# docker模块内用到的ws +@sockets.route(route_v2 + '/ws_model') +def ws_model_v2(ws): + ''' + @name 模型控制器ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + # 开发时暂时注释---------------- + comReturn = comm.local() + if comReturn: return comReturn + get = ws.receive() + get = json.loads(get) + + # 开发时暂时注释---------------- + if not check_csrf_websocket(ws, get): return + + from panelControllerV2 import Controller + model_obj = Controller() + while True: + pdata = ws.receive() + if pdata in ['{}', {}, None, '']: + ws.send(json.dumps(public.return_status_code(1000, 'The request parameter cannot be null'))) + break + try: + get = public.to_dict_obj(json.loads(pdata)) + except: + request.form = { + "error": pdata + } + raise Exception('json load error !') + get._ws = ws + get.model_index = get.model_index.strip() + p = threading.Thread(target=ws_model_thread, args=(model_obj, get)) + p.start() + + +# ======================普通路由区start============================# + + +@app.route(route_v2 + '/', methods=method_all) +def home_v2(): + # 面板首页 + comReturn = comm.local() + if comReturn: return comReturn + data = {} + data[public.to_string([112, + 100])], data['pro_end'], data['ltd_end'] = get_pd() + data['siteCount'] = public.M('sites').count() + data['ftpCount'] = public.M('ftps').count() + data['databaseCount'] = public.M('databases').count() + data['lan'] = public.GetLan('index') + data['js_random'] = get_js_random() + return render_template('index.html', data=data) + + +@app.route(route_v2 + '/xterm', methods=method_all) +def xterm_v2(): + # 宝塔终端管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0]: + import system_v2 + data = system_v2.system().GetConcifInfo() + return render_template('xterm.html', data=data) + import ssh_terminal_v2 + ssh_host_admin = ssh_terminal_v2.ssh_host_admin() + defs = ('get_host_list', 'get_host_find', 'modify_host', 'create_host', + 'remove_host', 'set_sort', 'get_command_list', 'create_command', + 'get_command_find', 'modify_command', 'remove_command') + return publicObject(ssh_host_admin, defs, None) + + +@app.route(route_v2 + '/modify_password', methods=method_get) +def modify_password_v2(): + comReturn = comm.local() + if comReturn: return comReturn + # if not session.get('password_expire',False): return redirect('/',302) + data = {} + g.title = public.get_msg_gettext( + 'The password has expired, please change it!') + return render_template('modify_password.html', data=data) + + +@app.route(route_v2 + '/site', methods=method_all) +def site_v2(pdata=None): + # 网站管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + # data = {} + import system_v2 + data = system_v2.system().GetConcifInfo() + data['isSetup'] = True + data['lan'] = public.getLan('site') + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + '/nginx') == False \ + and os.path.exists(public.GetConfigValue('setup_path') + '/apache') == False \ + and os.path.exists('/usr/local/lsws/bin/lswsctrl') == False: + data['isSetup'] = False + return render_template('site.html', data=data) + + import panel_site_v2 + siteObject = panel_site_v2.panelSite() + defs = ( + 'get_auto_restart_rph', + 'remove_auto_restart_rph', + 'auto_restart_rph', + 'check_del_data', + 'upload_csv', + 'create_website_multiple', + 'del_redirect_multiple', + 'del_proxy_multiple', + 'delete_dir_auth_multiple', + 'delete_dir_bind_multiple', + 'delete_domain_multiple', + 'set_site_etime_multiple', + 'set_site_php_version_multiple', + 'delete_website_multiple', + 'set_site_status_multiple', + 'get_site_err_log', + 'get_site_domains', + 'GetRedirectFile', + 'SaveRedirectFile', + 'DeleteRedirect', + 'GetRedirectList', + 'CreateRedirect', + 'ModifyRedirect', + "set_error_redirect", + 'set_dir_auth', + 'delete_dir_auth', + 'get_dir_auth', + 'modify_dir_auth_pass', + 'reset_wp_db', + 'export_domains', + 'import_domains', + 'GetSiteLogs', + 'GetSiteDomains', + 'GetSecurity', + 'SetSecurity', + 'ProxyCache', + 'CloseToHttps', + 'HttpToHttps', + 'SetEdate', + 'SetRewriteTel', + 'GetCheckSafe', + 'CheckSafe', + 'GetDefaultSite', + 'SetDefaultSite', + 'CloseTomcat', + 'SetTomcat', + 'apacheAddPort', + 'AddSite', + 'GetPHPVersion', + 'SetPHPVersion', + 'DeleteSite', + 'AddDomain', + 'DelDomain', + 'GetDirBinding', + 'AddDirBinding', + 'GetDirRewrite', + 'DelDirBinding', + 'get_site_types', + 'add_site_type', + 'remove_site_type', + 'modify_site_type_name', + 'set_site_type', + 'UpdateRulelist', + 'SetSiteRunPath', + 'GetSiteRunPath', + 'SetPath', + 'SetIndex', + 'GetIndex', + 'GetDirUserINI', + 'SetDirUserINI', + 'GetRewriteList', + 'SetSSL', + 'SetSSLConf', + 'CreateLet', + 'CloseSSLConf', + 'GetSSL', + 'SiteStart', + 'SiteStop', + 'Set301Status', + 'Get301Status', + 'CloseLimitNet', + 'SetLimitNet', + 'GetLimitNet', + 'RemoveProxy', + 'GetProxyList', + 'GetProxyDetals', + 'CreateProxy', + 'ModifyProxy', + 'GetProxyFile', + 'SaveProxyFile', + 'ToBackup', + 'DelBackup', + 'GetSitePHPVersion', + 'logsOpen', + 'GetLogsStatus', + 'CloseHasPwd', + 'SetHasPwd', + 'GetHasPwd', + 'GetDnsApi', + 'SetDnsApi', + 'reset_wp_password', + 'is_update', + 'purge_all_cache', + 'set_fastcgi_cache', + 'update_wp', + 'get_wp_username', + 'get_language', + 'deploy_wp', + # 网站管理新增 + 'test_domains_api', + 'site_rname', + 'get_wp_versions', + 'AddWPSite', + 'get_wp_configurations', + 'save_wp_configurations', + 'wp_backup_list', + 'wp_backup', + 'wp_restore', + 'wp_remove_backup', + 'get_wp_security_info', + 'open_wp_file_protection', + 'close_wp_file_protection', + 'get_wp_file_info', + 'open_wp_firewall_protection', + 'close_wp_firewall_protection', + 'get_wp_firewall_info', + 'wp_migrate_from_website_to_wptoolkit', + 'wp_can_migrate_from_website_to_wptoolkit', + 'wp_create_with_aap_bak', + 'wp_create_with_plesk_or_cpanel_bak', + 'wp_clone', + 'wp_integrity_check', + 'wp_reinstall_files', + 'wp_plugin_list', + 'wp_install_plugin', + 'wp_installed_plugins', + 'wp_update_plugin', + 'wp_set_plugin_auto_update', + 'wp_set_plugin_status', + 'wp_uninstall_plugin', + 'wp_theme_list', + 'wp_install_theme', + 'wp_installed_themes', + 'wp_update_theme', + 'wp_set_theme_auto_update', + 'wp_switch_theme', + 'wp_uninstall_theme', + ) + return publicObject(siteObject, defs, None, pdata) + + +@app.route(route_v2 + '/ftp', methods=method_all) +def ftp_v2(pdata=None): + # FTP管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + FtpPort() + import system_v2 + data = system_v2.system().GetConcifInfo() + data['isSetup'] = True + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + + '/pure-ftpd') == False: + data['isSetup'] = False + data['lan'] = public.GetLan('ftp') + return render_template('ftp.html', data=data) + import ftp_v2 + ftpObject = ftp_v2.ftp() + defs = ('AddUser', 'DeleteUser', 'SetUserPassword', 'SetStatus', 'setPort', + 'set_user_home', 'get_login_logs', 'get_action_logs', + 'set_ftp_logs') + return publicObject(ftpObject, defs, None, pdata) + + +@app.route(route_v2 + '/database', methods=method_all) +def database_v2(pdata=None): + # 数据库管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import ajax_v2 + from panelPlugin import panelPlugin + session['phpmyadminDir'] = False + if panelPlugin().get_phpmyadmin_stat(): + pmd = get_phpmyadmin_dir() + if pmd: + session['phpmyadminDir'] = 'http://' + public.GetHost( + ) + ':' + pmd[1] + '/' + pmd[0] + ajax_v2.ajax().set_phpmyadmin_session() + import system_v2 + data = system_v2.system().GetConcifInfo() + data['isSetup'] = os.path.exists( + public.GetConfigValue('setup_path') + '/mysql/bin') + data['mysql_root'] = public.M('config').where( + 'id=?', (1,)).getField('mysql_root') + data['lan'] = public.GetLan('database') + data['js_random'] = get_js_random() + return render_template('database.html', data=data) + import database_v2 + databaseObject = database_v2.database() + defs = ( + 'GetdataInfo', + 'check_del_data', + 'get_database_size', + 'GetInfo', + 'ReTable', + 'OpTable', + 'AlTable', + 'GetSlowLogs', + 'GetRunStatus', + 'SetDbConf', + 'GetDbStatus', + 'BinLog', + 'GetErrorLog', + 'GetMySQLInfo', + 'SetDataDir', + 'SetMySQLPort', + 'AddCloudDatabase', + 'AddDatabase', + 'DeleteDatabase', + 'SetupPassword', + 'ResDatabasePassword', + 'ToBackup', + 'DelBackup', + 'AddCloudServer', + 'GetCloudServer', + 'RemoveCloudServer', + 'ModifyCloudServer', + 'InputSql', + 'SyncToDatabases', + 'SyncGetDatabases', + 'GetDatabaseAccess', + 'SetDatabaseAccess', + 'get_mysql_user', + 'check_mysql_ssl_status', + 'write_ssl_to_mysql', + 'GetdataInfo', + 'GetBackup', + ) + return publicObject(databaseObject, defs, None, pdata) + + +@app.route(route_v2 + '/acme', methods=method_all) +def acme_v2(pdata=None): + # Let's 证书管理 + comReturn = comm.local() + if comReturn: return comReturn + import acme_v3 + acme_v2_object = acme_v3.acme_v2() + defs = ('get_orders', 'remove_order', 'get_order_find', 'revoke_order', + 'create_order', 'get_account_info', 'set_account_info', + 'update_zip', 'get_cert_init_api', 'get_auths', 'auth_domain', + 'check_auth_status', 'download_cert', 'apply_cert', 'renew_cert', + 'apply_cert_api', 'apply_dns_auth') + return publicObject(acme_v2_object, defs, None, pdata) + + +@app.route(route_v2 + '/api', methods=method_all) +def api_v2(pdata=None): + # APP使用的API接口管理 + comReturn = comm.local() + if comReturn: return comReturn + import panel_api_v2 + api_object = panel_api_v2.panelApi() + defs = ('get_token', 'check_bind', 'get_bind_status', 'get_apps', + 'add_bind_app', 'remove_bind_app', 'set_token', 'get_tmp_token', + 'get_app_bind_status', 'login_for_app') + return publicObject(api_object, defs, None, pdata) + + +@app.route(route_v2 + '/control', methods=method_all) +def control_v2(pdata=None): + # 监控页面 + comReturn = comm.local() + if comReturn: return comReturn + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('control') + data['js_random'] = get_js_random() + return render_template('control.html', data=data) + + +@app.route(route_v2 + '/logs', methods=method_all) +def logs_v2(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + data = {} + data['lan'] = public.GetLan('soft') + data['show_workorder'] = not os.path.exists('data/not_workorder.pl') + return render_template('logs.html', data=data) + + +@app.route(route_v2 + '/firewall', methods=method_all) +def firewall_v2(pdata=None): + # 安全页面 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + return render_template('firewall.html', data=data) + import firewalls_v2 + firewallObject = firewalls_v2.firewalls() + defs = ('GetList', 'AddDropAddress', 'DelDropAddress', 'FirewallReload', + 'SetFirewallStatus', 'AddAcceptPort', 'DelAcceptPort', + 'SetSshStatus', 'SetPing', 'SetSshPort', 'GetSshInfo', + 'SetFirewallStatus') + return publicObject(firewallObject, defs, None, pdata) + + +@app.route(route_v2 + '/ssh_security', methods=method_all) +def ssh_security_v2(pdata=None): + # SSH安全 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata and not request.args.get( + 'action', '') in ['download_key']: + data = {} + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + return render_template('firewall.html', data=data) + import ssh_security_v2 + firewallObject = ssh_security_v2.ssh_security() + is_csrf = True + if request.args.get('action', '') in ['download_key']: is_csrf = False + defs = ('san_ssh_security', 'set_password', 'set_sshkey', 'stop_key', + 'get_config', 'download_key', 'stop_password', 'get_key', + 'return_ip', 'add_return_ip', 'del_return_ip', 'start_jian', + 'stop_jian', 'get_jian', 'get_logs', 'set_root', 'stop_root', + 'start_auth_method', 'stop_auth_method', 'get_auth_method', + 'check_so_file', 'get_so_file', 'get_pin', 'set_login_send', + 'get_login_send', 'get_msg_push_list', 'clear_login_send') + return publicObject(firewallObject, defs, None, pdata, is_csrf) + + +@app.route(route_v2 + '/monitor', methods=method_all) +def panel_monitor_v2(pdata=None): + # 云控统计信息 + comReturn = comm.local() + if comReturn: return comReturn + import monitor_v2 + dataObject = monitor_v2.Monitor() + defs = ('get_spider', 'get_exception', 'get_request_count_qps', + 'load_and_up_flow', 'get_request_count_by_hour') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/san', methods=method_all) +def san_baseline_v2(pdata=None): + # 云控安全扫描 + comReturn = comm.local() + if comReturn: return comReturn + import san_baseline_v2 + dataObject = san_baseline_v2.san_baseline() + defs = ('start', 'get_api_log', 'get_resut', 'get_ssh_errorlogin', + 'repair', 'repair_all') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/password', methods=method_all) +def panel_password_v2(pdata=None): + # 云控密码管理 + comReturn = comm.local() + if comReturn: return comReturn + import password_v2 + dataObject = password_v2.password() + defs = ('set_root_password', 'get_mysql_root', 'set_mysql_password', + 'set_panel_password', 'SetPassword', 'SetSshKey', 'StopKey', + 'GetConfig', 'StopPassword', 'GetKey', 'get_databses', + 'rem_mysql_pass', 'set_mysql_access', "get_panel_username") + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/warning', methods=method_all) +def panel_warning_v2(pdata=None): + # 首页安全警告 + comReturn = comm.local() + if comReturn: return comReturn + if public.get_csrf_html_token_key() in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + get = get_input() + ikey = 'warning_list' + import panel_warning_v2 + dataObject = panel_warning_v2.panelWarning() + if get.action == 'get_list': + result = cache.get(ikey) + if not result or 'force' in get: + result = json.loads('{"ignore":[],"risk":[],"security":[]}') + try: + defs = ("get_list",) + result = publicObject(dataObject, defs, None, pdata) + cache.set(ikey, result, 3600) + return result + except: + pass + return result + + defs = ('get_list', 'set_ignore', 'check_find', 'check_cve', + 'set_vuln_ignore', 'get_scan_bar', 'get_tmp_result', + 'kill_get_list') + + if get.action in ['set_ignore', 'check_find', 'set_vuln_ignore']: + cache.delete(ikey) + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/bak', methods=method_all) +def backup_bak_v2(pdata=None): + # 云控备份服务 + comReturn = comm.local() + if comReturn: return comReturn + import backup_bak_v2 + dataObject = backup_bak_v2.backup_bak() + defs = ('get_sites', 'get_databases', 'backup_database', 'backup_site', + 'backup_path', 'get_database_progress', 'get_site_progress', + 'down', 'get_down_progress', 'download_path', 'backup_site_all', + 'get_all_site_progress', 'backup_date_all', + 'get_all_date_progress') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/abnormal', methods=method_all) +def abnormal_v2(pdata=None): + # 云控系统统计 + comReturn = comm.local() + if comReturn: return comReturn + import abnormal_v2 + dataObject = abnormal_v2.abnormal() + defs = ('mysql_server', 'mysql_cpu', 'mysql_count', 'php_server', + 'php_conn_max', 'php_cpu', 'CPU', 'Memory', 'disk', + 'not_root_user', 'start') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/project/nodejs/', methods=method_all) +@app.route(route_v2 + '/project/nodejs//html', methods=method_all) +@app.route(route_v2 + '/project/docker/', methods=method_all) +@app.route(route_v2 + '/project/docker//html', methods=method_all) +@app.route(route_v2 + '/project/quota/', methods=method_all) +@app.route(route_v2 + '/project/quota//html', methods=method_all) +@app.route(route_v2 + '/project/proxy/', methods=method_all) +@app.route(route_v2 + '/project/proxy//html', methods=method_all) +def project_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + comReturn = comm.local() + if comReturn: return comReturn + from panelProjectControllerV2 import ProjectController + project_obj = ProjectController() + defs = ('model',) + get = get_input() + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + if request.path.endswith('/html'): + return project_obj.model(get) + return publicObject(project_obj, defs, None, get) + + +@app.route(route_v2 + '/msg//', methods=method_all) +def msgcontroller_v2(mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + from MsgControllerV2 import MsgController + project_obj = MsgController() + defs = ('model',) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + return publicObject(project_obj, defs, None, get) + + +# @app.route(route_v2 + '/docker', methods=method_all) +# def docker_v2(pdata=None): +# comReturn = comm.local() +# if comReturn: return comReturn +# if request.method == method_get[0]: +# import system_v2 +# data = system_v2.system().GetConcifInfo() +# data['js_random'] = get_js_random() +# data['lan'] = public.GetLan('files') +# return render_template('docker.html', data=data) + +# @app.route(route_v2 + '/docker', methods=method_all) +@app.route(route_v2 + '/btdocker/app/', methods=method_all) +@app.route(route_v2 + '/btdocker/backup/', methods=method_all) +@app.route(route_v2 + '/btdocker/container/', methods=method_all) +@app.route(route_v2 + '/btdocker/compose/', methods=method_all) +@app.route(route_v2 + '/btdocker/dkgroup/', methods=method_all) +@app.route(route_v2 + '/btdocker/image/', methods=method_all) +@app.route(route_v2 + '/btdocker/network/', methods=method_all) +@app.route(route_v2 + '/btdocker/proxy/', methods=method_all) +@app.route(route_v2 + '/btdocker/project/', methods=method_all) +@app.route(route_v2 + '/btdocker/registry/', methods=method_all) +@app.route(route_v2 + '/btdocker/setup/', methods=method_all) +@app.route(route_v2 + '/btdocker/site/', methods=method_all) +@app.route(route_v2 + '/btdocker/status/', methods=method_all) +@app.route(route_v2 + '/btdocker/volume/', methods=method_all) +def docker_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + get = get_input() + get.action = 'model' + get.model_index = 'btDocker' + get.mod_name = path_split[3] + get.def_name = def_name + + comReturn = comm.local() + if comReturn: return comReturn + # p_path = public.get_plugin_path() + '/' + path_split[2] + # if os.path.exists(p_path): + # return panel_other(get.model_index, get.mod_name, def_name) + from panelDockerControllerV2 import DockerController + controller_obj = DockerController() + defs = ('model',) + return publicObject(controller_obj, defs, None, get) + + +@app.route(route_v2 + '/dbmodel//', methods=method_all) +def dbmodel_v2(mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + from panelDatabaseControllerV2 import DatabaseController + database_obj = DatabaseController() + defs = ('model',) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + + return publicObject(database_obj, defs, None, get) + + +@app.route(route_v2 + '/files', methods=method_all) +def files_v2(pdata=None): + # 文件管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not request.args.get( + 'path') and not pdata: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['recycle_bin'] = os.path.exists('data/recycle_bin.pl') + data['lan'] = public.GetLan('files') + data['js_random'] = get_js_random() + return render_template('files.html', data=data) + import files_v2 + filesObject = files_v2.files() + defs = ('files_search', 'files_replace', 'get_replace_logs', + 'get_images_resize', 'add_files_rsync', 'get_file_attribute', + 'get_file_hash', 'CreateLink', 'get_progress', 'restore_website', + 'fix_permissions', 'get_all_back', 'restore_path_permissions', + 'del_path_premissions', 'get_path_premissions', + 'back_path_permissions', 'upload_file_exists', 'CheckExistsFiles', + 'GetExecLog', 'GetSearch', 'ExecShell', 'GetExecShellMsg', + 'exec_git', 'exec_composer', 'create_download_url', 'UploadFile', + 'GetDir', 'CreateFile', 'CreateDir', 'DeleteDir', 'DeleteFile', + 'get_download_url_list', 'remove_download_url', + 'modify_download_url', 'CopyFile', 'CopyDir', 'MvFile', + 'GetFileBody', 'SaveFileBody', 'Zip', 'UnZip', + 'get_download_url_find', 'set_file_ps', 'SearchFiles', 'upload', + 'read_history', 're_history', 'auto_save_temp', + 'get_auto_save_body', 'get_videos', 'GetFileAccess', + 'SetFileAccess', 'GetDirSize', 'SetBatchData', 'BatchPaste', + 'install_rar', 'get_path_size', 'DownloadFile', 'GetTaskSpeed', + 'CloseLogs', 'InstallSoft', 'UninstallSoft', 'SaveTmpFile', + 'get_composer_version', 'exec_composer', 'update_composer', + 'GetTmpFile', 'del_files_store', 'add_files_store', + 'get_files_store', 'del_files_store_types', + 'add_files_store_types', 'exec_git', 'RemoveTask', 'ActionTask', + 'Re_Recycle_bin', 'Get_Recycle_bin', 'Del_Recycle_bin', + 'Close_Recycle_bin', 'Recycle_bin', 'file_webshell_check', + 'dir_webshell_check', 'files_search', 'files_replace', + 'get_replace_logs') + return publicObject(filesObject, defs, None, pdata) + + +@app.route(route_v2 + '/crontab', methods=method_all) +def crontab_v2(pdata=None): + # 计划任务 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('crontab') + data['js_random'] = get_js_random() + return render_template('crontab.html', data=data) + import crontab_v2 + crontabObject = crontab_v2.crontab() + defs = ('GetCrontab', 'AddCrontab', 'GetDataList', 'GetLogs', 'DelLogs', + 'DelCrontab', 'StartTask', 'set_cron_status', 'get_crond_find', + 'modify_crond', 'get_backup_list') + return publicObject(crontabObject, defs, None, pdata) + + +@app.route(route_v2 + '/soft', methods=method_all) +def soft_v2(pdata=None): + # 软件商店页面 + comReturn = comm.local() + if comReturn: return comReturn + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('soft') + data['js_random'] = get_js_random() + return render_template('soft.html', data=data) + + +@app.route(route_v2 + '/config', methods=method_all) +def config_v2(pdata=None): + # 面板设置页面 + comReturn = comm.local() + if comReturn: return comReturn + + if request.method == method_get[0] and not pdata: + import system_v2, wxapp_v2, config_v2 + c_obj = config_v2.config() + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('config') + try: + data['wx'] = wxapp_v2.wxapp().get_user_info(None)['msg'] + except: + data['wx'] = 'INIT_WX_NOT_BIND' + data['api'] = '' + data['ipv6'] = '' + sess_out_path = 'data/session_timeout.pl' + if not os.path.exists(sess_out_path): + public.writeFile(sess_out_path, '86400') + s_time_tmp = public.readFile(sess_out_path) + if not s_time_tmp: s_time_tmp = '0' + data['session_timeout'] = int(s_time_tmp) + if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + if c_obj.get_token(None)['open']: data['api'] = 'checked' + data['basic_auth'] = c_obj.get_basic_auth_stat(None) + data['status_code'] = c_obj.get_not_auth_status() + data['basic_auth']['value'] = public.getMsg('CLOSED') + if data['basic_auth']['open']: + data['basic_auth']['value'] = public.getMsg('OPENED') + data['debug'] = '' + data['js_random'] = get_js_random() + if app.config['DEBUG']: data['debug'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + data['public_key'] = public.get_rsa_public_key().replace("\n", "") + return render_template('config.html', data=data) + import config_v2 + defs = ( + 'send_by_telegram', + 'set_empty', + 'set_backup_notification', + 'get_panel_ssl_status', + 'set_file_deny', + 'del_file_deny', + 'get_file_deny', + 'set_improvement', + 'get_httpd_access_log_format_parameter', + 'set_httpd_format_log_to_website', + 'get_httpd_access_log_format', + 'del_httpd_access_log_format', + 'add_httpd_access_log_format', + 'get_nginx_access_log_format_parameter', + 'set_format_log_to_website', + 'get_nginx_access_log_format', + 'del_nginx_access_log_format', + 'set_click_logs', + 'get_node_config', + 'add_nginx_access_log_format', + 'get_ols_private_cache_status', + 'get_ols_value', + 'set_ols_value', + 'set_node_config', + 'get_ols_private_cache', + 'get_ols_static_cache', + 'set_ols_static_cache', + 'switch_ols_private_cache', + 'set_ols_private_cache', + 'set_coll_open', + 'get_qrcode_data', + 'check_two_step', + 'set_two_step_auth', + 'create_user', + 'remove_user', + 'modify_user', + 'get_key', + 'get_php_session_path', + 'set_php_session_path', + 'get_cert_source', + 'get_users', + 'set_request_iptype', + 'set_local', + 'set_debug', + 'get_panel_error_logs', + 'clean_panel_error_logs', + 'get_menu_list', + 'set_hide_menu_list', + 'get_basic_auth_stat', + 'set_basic_auth', + 'get_cli_php_version', + 'get_tmp_token', + 'get_temp_login', + 'set_temp_login', + 'remove_temp_login', + 'clear_temp_login', + 'get_temp_login_logs', + 'set_cli_php_version', + 'DelOldSession', + 'GetSessionCount', + 'SetSessionConf', + 'set_not_auth_status', + 'GetSessionConf', + 'get_ipv6_listen', + 'set_ipv6_status', + 'GetApacheValue', + 'SetApacheValue', + 'install_msg_module', + 'GetNginxValue', + 'SetNginxValue', + 'get_token', + 'set_token', + 'set_admin_path', + 'is_pro', + 'set_msg_config', + 'get_php_config', + 'get_config', + 'SavePanelSSL', + 'GetPanelSSL', + 'GetPHPConf', + 'SetPHPConf', + 'uninstall_msg_module', + 'GetPanelList', + 'AddPanelInfo', + 'SetPanelInfo', + 'DelPanelInfo', + 'ClickPanelInfo', + 'SetPanelSSL', + 'get_msg_configs', + 'SetTemplates', + 'Set502', + 'setPassword', + 'setUsername', + 'setPanel', + 'setPathInfo', + 'setPHPMaxSize', + 'get_msg_fun', + 'getFpmConfig', + 'setFpmConfig', + 'setPHPMaxTime', + 'syncDate', + 'setPHPDisable', + 'SetControl', + 'get_settings2', + 'del_tg_info', + 'set_tg_bot', + 'ClosePanel', + 'AutoUpdatePanel', + 'SetPanelLock', + 'return_mail_list', + 'del_mail_list', + 'add_mail_address', + 'user_mail_send', + 'get_user_mail', + 'set_dingding', + 'get_dingding', + 'get_settings', + 'user_stmp_mail_send', + 'user_dingding_send', + 'get_login_send', + 'set_login_send', + 'clear_login_send', + 'get_login_log', + 'login_ipwhite', + 'set_ssl_verify', + 'get_ssl_verify', + 'get_password_config', + 'set_password_expire', + 'set_password_safe', + 'get_module_template', + # 新增nps评分 + 'write_nps_new', + 'get_nps_new', + "check_nps", + 'get_translations', + ) + return publicObject(config_v2.config(), defs, None, pdata) + + +@app.route(route_v2 + '/ajax', methods=method_all) +def ajax_v2(pdata=None): + # 面板系统服务状态接口 + comReturn = comm.local() + if comReturn: return comReturn + import ajax_v2 + ajaxObject = ajax_v2.ajax() + defs = ('get_lines', 'php_info', 'change_phpmyadmin_ssl_port', + 'set_phpmyadmin_ssl', 'get_phpmyadmin_ssl', 'get_pd', + 'check_user_auth', 'to_not_beta', 'get_beta_logs', 'apple_beta', + 'GetApacheStatus', 'GetCloudHtml', 'get_pay_type', + 'get_load_average', 'GetOpeLogs', 'GetFpmLogs', 'GetFpmSlowLogs', + 'SetMemcachedCache', 'GetMemcachedStatus', 'GetRedisStatus', + 'GetWarning', 'SetWarning', 'CheckLogin', 'GetSpeed', 'GetAd', + 'phpSort', 'ToPunycode', 'GetBetaStatus', 'SetBeta', + 'setPHPMyAdmin', 'delClose', 'KillProcess', 'GetPHPInfo', + 'GetQiniuFileList', 'get_process_tops', 'get_process_cpu_high', + 'UninstallLib', 'InstallLib', 'SetQiniuAS', 'GetQiniuAS', + 'GetLibList', 'GetProcessList', 'GetNetWorkList', 'GetNginxStatus', + 'GetPHPStatus', 'GetTaskCount', 'GetSoftList', 'GetNetWorkIo', + 'GetDiskIo', 'GetCpuIo', 'CheckInstalled', 'UpdatePanel', + 'GetInstalled', 'GetPHPConfig', 'SetPHPConfig', 'log_analysis', + 'speed_log', 'get_result', 'get_detailed', 'ignore_version') + + return publicObject(ajaxObject, defs, None, pdata) + + +@app.route(route_v2 + '/system', methods=method_all) +def system_v2(pdata=None): + # 面板系统状态接口 + comReturn = comm.local() + if comReturn: return comReturn + import system_v2 + sysObject = system_v2.system() + defs = ('get_io_info', 'UpdatePro', 'GetAllInfo', 'GetNetWorkApi', + 'GetLoadAverage', 'ClearSystem', 'GetNetWorkOld', 'GetNetWork', + 'GetDiskInfo', 'GetCpuInfo', 'GetBootTime', 'GetSystemVersion', + 'GetMemInfo', 'GetSystemTotal', 'GetConcifInfo', 'ServiceAdmin', + 'ReWeb', 'RestartServer', 'ReMemory', 'RepPanel') + return publicObject(sysObject, defs, None, pdata) + + +@app.route(route_v2 + '/deployment', methods=method_all) +def deployment_v2(pdata=None): + # 一键部署接口 + comReturn = comm.local() + if comReturn: return comReturn + import plugin_deployment_v2 + sysObject = plugin_deployment_v2.plugin_deployment() + defs = ('GetList', 'AddPackage', 'DelPackage', 'SetupPackage', 'GetSpeed', + 'GetPackageOther') + return publicObject(sysObject, defs, None, pdata) + + +@app.route(route_v2 + '/data', methods=method_all) +@app.route(route_v2 + '/panel_data', methods=method_all) +def panel_data_v2(pdata=None): + # 从数据库获取数据接口 + comReturn = comm.local() + if comReturn: return comReturn + import data_v2 + dataObject = data_v2.data() + defs = ('setPs', 'getData', 'getFind', 'getKey') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/ssl', methods=method_all) +def ssl_v2(pdata=None): + # 商业SSL证书申请接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_ssl_v2 + toObject = panel_ssl_v2.panelSSL() + defs = ( + 'check_url_txt', + 'RemoveCert', + 'renew_lets_ssl', + 'SetCertToSite', + 'GetCertList', + 'SaveCert', + 'GetCert', + 'GetCertName', + 'again_verify', + 'DelToken', + 'GetToken', + 'GetUserInfo', + 'GetOrderList', + 'GetDVSSL', + 'Completed', + 'SyncOrder', + 'download_cert', + 'set_cert', + 'cancel_cert_order', + 'get_order_list', + 'get_order_find', + 'apply_order_pay', + 'get_pay_status', + 'apply_order', + 'get_verify_info', + 'get_verify_result', + 'get_product_list', + 'set_verify_info', + 'GetSSLInfo', + 'downloadCRT', + 'GetSSLProduct', + 'Renew_SSL', + 'Get_Renew_SSL', + # 新增 购买证书对接接口 + 'get_product_list_v2', + 'apply_cert_order_pay', + 'get_cert_admin', + 'apply_order_ca', + 'apply_cert_install_pay', + + # 'pay_test' + ) + get = get_input() + + if get.action == 'download_cert': + from io import BytesIO + import base64 + result = toObject.download_cert(get) + # public.print_log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@1111111111111111 result: {}".format(result)) + # {'success': False, 'res': '[code: 0] no data [file: /www/wwwroot/192.168.1.139/app/Api/Cert/controllers/Cert.php] [line: 955]', 'nonce': 1706498844} + + fp = BytesIO(base64.b64decode(result['res']['data'])) + return send_file(fp, + download_name=result['res']['filename'], + as_attachment=True, + mimetype='application/zip') + result = publicObject(toObject, defs, get.action, get) + return result + + +@app.route(route_v2 + '/task', methods=method_all) +def task_v2(pdata=None): + # 后台任务接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_task_v2 + toObject = panel_task_v2.bt_task() + defs = ('get_task_lists', 'remove_task', 'get_task_find', + "get_task_log_by_id") + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route(route_v2 + '/plugin', methods=method_all) +def plugin_v2(pdata=None): + # 插件系统接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_plugin_v2 + pluginObject = panel_plugin_v2.panelPlugin() + defs = ('get_usually_plugin', 'check_install_limit', 'set_score', + 'get_score', 'update_zip', 'input_zip', 'export_zip', 'add_index', + 'remove_index', 'sort_index', 'install_plugin', 'uninstall_plugin', + 'get_soft_find', 'get_index_list', 'get_soft_list', + 'get_cloud_list', 'check_deps', 'flush_cache', 'GetCloudWarning', + 'install', 'unInstall', 'getPluginList', 'getPluginInfo', + 'get_make_args', 'add_make_args', 'getPluginStatus', + 'setPluginStatus', 'a', 'getCloudPlugin', 'getConfigHtml', + 'savePluginSort', 'del_make_args', 'set_make_args') + return publicObject(pluginObject, defs, None, pdata) + + +@app.route(route_v2 + '/wxapp', methods=method_all) +@app.route(route_v2 + '/panel_wxapp', methods=method_all) +def panel_wxapp_v2(pdata=None): + # 微信小程序绑定接口 + comReturn = comm.local() + if comReturn: return comReturn + import wxapp_v2 + toObject = wxapp_v2.wxapp() + defs = ('blind', 'get_safe_log', 'blind_result', 'get_user_info', + 'blind_del', 'blind_qrcode') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route(route_v2 + '/auth', methods=method_all) +def auth_v2(pdata=None): + # 面板认证接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_auth_v2 + toObject = panel_auth_v2.panelAuth() + defs = ('free_trial', 'renew_product_auth', 'auth_activate', + 'get_product_auth', 'get_stripe_session_id', + 'get_re_order_status_plugin', 'create_plugin_other_order', + 'get_order_stat', 'get_voucher_plugin', + 'create_order_voucher_plugin', 'get_product_discount_by', + 'get_re_order_status', 'create_order_voucher', 'create_order', + 'get_order_status', 'get_voucher', 'flush_pay_status', + 'create_serverid', 'check_serverid', 'get_plugin_list', + 'check_plugin', 'get_buy_code', 'check_pay_status', + 'get_renew_code', 'check_renew_code', 'get_business_plugin', + 'get_ad_list', 'check_plugin_end', 'get_plugin_price', + 'get_plugin_remarks', 'get_paypal_session_id', + 'check_paypal_status', 'get_wx_order_status', 'get_apply_copon', + 'get_coupon_list', 'ignore_coupon_time', 'set_user_adviser', 'rest_unbind_count', + 'unbind_authorization', 'get_all_voucher_plugin', 'get_pay_unbind_count', + 'get_coupons', 'get_credits', 'create_with_credit_by_panel', 'get_last_paid_time', + 'get_all_coupons') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route(route_v2 + '/download', methods=method_get) +def download_v2(): + # 文件下载接口 + comReturn = comm.local() + if comReturn: return comReturn + filename = request.args.get('filename') + if filename.find('|') != -1: + filename = filename.split('|')[0] # 改为获取本地备份 + if not filename: + return public.ReturnJson(False, "INIT_ARGS_ERR"), json_header + # if filename in ['alioss','qiniu','upyun','txcos','ftp','msonedrive','gcloud_storage', 'gdrive', 'aws_s3']: return panel_cloud() + if not os.path.exists(filename): + return public.ReturnJson(False, "FILE_NOT_EXISTS"), json_header + + if request.args.get('play') == 'true': + import panelVideo + start, end = panelVideo.get_range(request) + g.return_message = True + return panelVideo.partial_response(filename, start, end) + else: + mimetype = "application/octet-stream" + extName = filename.split('.')[-1] + if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + public.WriteLog("TYPE_FILE", 'FILE_DOWNLOAD', + (filename, public.GetClientIp())) + g.return_message = True + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + etag=True, + conditional=True, + download_name=os.path.basename(filename), + max_age=0) + + +@app.route(route_v2 + '/cloud', methods=method_all) +def panel_cloud_v2(is_csrf=True): + # 从对像存储下载备份文件接口 + comReturn = comm.local() + if comReturn: return comReturn + if is_csrf: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + get = get_input() + _filename = get.filename + plugin_name = "" + if _filename.find('|') != -1: + plugin_name = get.filename.split('|')[1] + else: + plugin_name = get.filename + + if not os.path.exists('plugin/' + plugin_name + '/' + plugin_name + + '_main.py'): + return public.returnJson( + False, 'The specified plugin does not exist!'), json_header + public.package_path_append('plugin/' + plugin_name) + plugin_main = __import__(plugin_name + '_main') + public.mod_reload(plugin_main) + tmp = eval("plugin_main.%s_main()" % plugin_name) + if not hasattr(tmp, 'download_file'): + return public.returnJson( + False, + 'Specified plugin has no file download function!'), json_header + download_url = tmp.download_file(get.name) + if plugin_name == 'ftp': + if download_url.find("ftp") != 0: + download_url = "ftp://" + download_url + else: + if download_url.find('http') != 0: + download_url = 'http://' + download_url + + if "toserver" in get and get.toserver == "true": + download_dir = "/tmp/" + if "download_dir" in get: + download_dir = get.download_dir + local_file = os.path.join(download_dir, get.name) + + input_from_local = False + if "input_from_local" in get: + input_from_local = True if get.input_from_local == "true" else False + + if input_from_local: + if os.path.isfile(local_file): + return { + "status": True, + "msg": + "The file already exists and will be restored locally.", + "task_id": -1, + "local_file": local_file + } + from panel_task_v2 import bt_task + task_obj = bt_task() + task_id = task_obj.create_task('Download file', 1, download_url, + local_file) + return { + "status": True, + "msg": "The download task was created successfully", + "local_file": local_file, + "task_id": task_id + } + + return redirect(download_url) + + +@app.route(route_v2 + '/btwaf_error', methods=method_get) +def btwaf_error_v2(): + # 图标 + comReturn = comm.local() + if comReturn: return comReturn + get = get_input() + p_path = os.path.join('/www/server/panel/plugin/', "btwaf") + if not os.path.exists(p_path): + if get.name == 'btwaf' and get.fun == 'index': + return render_template('error3.html', data={}) + return render_template('error3.html', data={}) + + +@app.route(route_v2 + '/favicon.ico', methods=method_get) +def send_favicon_v2(): + # 图标 + comReturn = comm.local() + if comReturn: return abort(404) + s_file = '/www/server/panel/BTPanel/static/favicon.ico' + if not os.path.exists(s_file): return abort(404) + return send_file(s_file, conditional=True, etag=True) + + +@app.route(route_v2 + '/rspamd', defaults={'path': ''}, methods=method_all) +@app.route(route_v2 + '/rspamd/', methods=method_all) +def proxy_rspamd_requests_v2(path): + comReturn = comm.local() + if comReturn: return comReturn + param = str(request.url).split('?') + param = "" if len(param) < 2 else param[-1] + import requests + headers = {} + for h in request.headers.keys(): + headers[h] = request.headers[h] + if request.method == "GET": + if re.search(r"\.(js|css)$", path): + return send_file('/usr/share/rspamd/www/rspamd/' + path, + conditional=True, + etag=True) + if path == "/": + return send_file('/usr/share/rspamd/www/rspamd/', + conditional=True, + etag=True) + url = "http://127.0.0.1:11334/rspamd/" + path + "?" + param + for i in [ + 'stat', 'auth', 'neighbours', 'list_extractors', + 'list_transforms', 'graph', 'maps', 'actions', 'symbols', + 'history', 'errors', 'check_selector', 'saveactions', + 'savesymbols', 'getmap' + ]: + if i in path: + url = "http://127.0.0.1:11334/" + path + "?" + param + req = requests.get(url, headers=headers, stream=True) + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) + else: + url = "http://127.0.0.1:11334/" + path + for i in request.form.keys(): + data = '{}='.format(i) + # public.writeFile('/tmp/2',data+"\n","a+") + req = requests.post(url, data=data, headers=headers, stream=True) + + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) + + +@app.route(route_v2 + '/tips', methods=method_get) +def tips_v2(): + # 提示页面 + comReturn = comm.local() + if comReturn: return abort(404) + get = get_input() + if len(get.get_items().keys()) > 1: return abort(404) + return render_template('tips.html') + + +# ======================普通路由区end============================# + +# ======================严格排查区域start============================# + + +@app.route(route_v2 + '/login', methods=method_all) +@app.route(route_v2 + route_path, methods=method_all) +@app.route(route_v2 + route_path + '/', methods=method_all) +def login_v2(): + # 面板登录接口 + if os.path.exists('install.pl'): return redirect('/install') + global admin_check_auth, admin_path, route_path + is_auth_path = False + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + is_auth_path = True + # 登录输入验证 + if request.method == method_post[0]: + if is_auth_path: + g.auth_error = True + return public.error_not_login(None) + v_list = ['username', 'password', 'code', 'vcode', 'cdn_url'] + for v in v_list: + if v in ['username', 'password']: continue + pv = request.form.get(v, '').strip() + if v == 'cdn_url': + if len(pv) > 32: + return public.return_msg_gettext( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^[\w\.-]+$", pv): + public.return_msg_gettext( + False, 'Wrong parameter format!'), json_header + continue + + if not pv: continue + p_len = 32 + if v == 'code': p_len = 4 + if v == 'vcode': p_len = 6 + if len(pv) != p_len: + if v == 'code': + return public.returnJson( + False, 'Verification code length error!'), json_header + return public.returnJson( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^\w+$", pv): + return public.returnJson( + False, 'Wrong parameter format!'), json_header + + for n in request.form.keys(): + if not n in v_list: + return public.returnJson( + False, + 'There can be no extra parameters in the login parameters' + ), json_header + + get = get_input() + import user_login_v2 + if hasattr(get, 'tmp_token'): + result = user_login_v2.userlogin().request_tmp(get) + return is_login(result) + # 过滤爬虫 + if public.is_spider(): return abort(404) + if hasattr(get, 'dologin'): + login_path = '/login' + if not 'login' in session: return redirect(login_path) + if os.path.exists(admin_path_file): login_path = route_path + if session['login'] != False: + session['login'] = False + cache.set('dologin', True) + public.write_log_gettext( + 'Logout', 'Client: {}, has manually exited the panel', + (public.GetClientIp() + ":" + + str(request.environ.get('REMOTE_PORT')),)) + if 'tmp_login_expire' in session: + s_file = 'data/session/{}'.format(session['tmp_login_id']) + if os.path.exists(s_file): + os.remove(s_file) + token_key = public.get_csrf_html_token_key() + if token_key in session: + del (session[token_key]) + session.clear() + sess_file = 'data/sess_files/' + public.get_sess_key() + if os.path.exists(sess_file): + try: + os.remove(sess_file) + except: + pass + sess_tmp_file = public.get_full_session_file() + if os.path.exists(sess_tmp_file): os.remove(sess_tmp_file) + g.dologin = True + return redirect(public.get_admin_path()) + + if is_auth_path: + if route_path != request.path and route_path + '/' != request.path: + referer = request.headers.get('Referer', 'err') + referer_tmp = referer.split('/') + referer_path = referer_tmp[-1] + if referer_path == '': + referer_path = referer_tmp[-2] + if route_path != '/' + referer_path: + g.auth_error = True + # return render_template('autherr.html') + return public.error_not_login(None) + + session['admin_auth'] = True + comReturn = common.panelSetup().init() + if comReturn: return comReturn + + if request.method == method_post[0]: + result = userlogin.userlogin().request_post(get) + return is_login(result) + + if request.method == method_get[0]: + result = userlogin.userlogin().request_get(get) + if result: + return result + data = {} + data['lan'] = public.GetLan('login') + data['hosts'] = '[]' + hosts_file = 'plugin/static_cdn/hosts.json' + if os.path.exists(hosts_file): + data['hosts'] = public.get_cdn_hosts() + if type(data['hosts']) == dict: + data['hosts'] = '[]' + else: + data['hosts'] = json.dumps(data['hosts']) + data['app_login'] = os.path.exists('data/app_login.pl') + public.cache_set( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp()), 'check', 360) + + # 生成登录token + last_key = 'last_login_token' + # ----------- + last_time_key = 'last_login_token_time' + s_time = int(time.time()) + if last_key in session and last_time_key in session: + # 10秒内不重复生成token + if s_time - session[last_time_key] > 10: + session[last_key] = public.GetRandomString(32) + session[last_time_key] = s_time + else: + session[last_key] = public.GetRandomString(32) + session[last_time_key] = s_time + + data[last_key] = session[last_key] + data['public_key'] = public.get_rsa_public_key() + return render_template('login.html', data=data) + + +@app.route(route_v2 + '/close', methods=method_get) +def close_v2(): + # 面板已关闭页面 + if not os.path.exists('data/close.pl'): return redirect('/') + data = {} + data['lan'] = public.getLan('close') + return render_template('close.html', data=data) + + +@app.route(route_v2 + '/get_app_bind_status', methods=method_all) +def get_app_bind_status_v2(pdata=None): + # APP绑定状态查询 + if not public.check_app('app_bind'): return abort(404) + get = get_input() + if len(get.get_items().keys()) > 2: return 'There are meaningless parameters!' + v_list = ['bind_token', 'data'] + for n in get.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + import panel_api_v2 + api_object = panel_api_v2.panelApi() + return json.dumps(api_object.get_app_bind_status(get_input())), json_header + + +@app.route(route_v2 + '/check_bind', methods=method_all) +def check_bind_v2(pdata=None): + # APP绑定查询 + if not public.check_app('app_bind'): return abort(404) + get = get_input() + if len(get.get_items().keys()) > 4: return 'There are meaningless parameters!' + v_list = ['bind_token', 'client_brand', 'client_model', 'data'] + for n in get.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + import panel_api_v2 + api_object = panel_api_v2.panelApi() + return json.dumps(api_object.check_bind(get_input())), json_header + + +@app.route(route_v2 + '/code', methods=method_get) +def code_v2(): + if not 'code' in session: return '' + if not session['code']: return '' + # 获取图片验证码 + try: + import vilidate_v2 + except: + public.ExecShell("btpip install Pillow -I") + return "Pillow not install!" + vie = vilidate_v2.vieCode() + codeImage = vie.GetCodeImage(80, 4) + if sys.version_info[0] == 2: + try: + from cStringIO import StringIO + except: + from StringIO import StringIO + out = StringIO() + else: + from io import BytesIO + out = BytesIO() + codeImage[0].save(out, "png") + cache.set("codeStr", public.md5("".join(codeImage[1]).lower()), 180) + cache.set("codeOut", 1, 0.1) + out.seek(0) + return send_file(out, mimetype='image/png', max_age=0) + + +@app.route(route_v2 + '/down/', methods=method_all) +def down_v2(token=None, fname=None): + # 文件分享对外接口 + try: + if public.M('download_token').count() == 0: return abort(404) + fname = request.args.get('fname') + if fname: + if (len(fname) > 256): return abort(404) + if fname: fname = fname.strip('/') + if not token: return abort(404) + if len(token) > 48: return abort(404) + char_list = [ + '\\', '/', ':', '*', '?', '"', '<', '>', '|', ';', '&', '`' + ] + for char in char_list: + if char in token: return abort(404) + if not request.args.get('play') in ['true', None, '']: + return abort(404) + args = get_input() + v_list = ['fname', 'play', 'file_password', 'data'] + for n in args.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + if not re.match(r"^[\w\.]+$", token): return abort(404) + find = public.M('download_token').where('token=?', (token,)).find() + + if not find: return abort(404) + if time.time() > int(find['expire']): return abort(404) + + if not os.path.exists(find['filename']): return abort(404) + if find['password'] and not token in session: + if 'file_password' in args: + if not re.match(r"^\w+$", args.file_password): + return public.ReturnJson(False, + 'Wrong password!'), json_header + if re.match(r"^\d+$", args.file_password): + args.file_password = str(int(args.file_password)) + args.file_password += ".0" + if args.file_password != str(find['password']): + return public.ReturnJson(False, + 'Wrong password!'), json_header + session[token] = 1 + session['down'] = True + else: + pdata = { + "to_path": "", + "src_path": find['filename'], + "password": True, + "filename": find['filename'].split('/')[-1], + "ps": find['ps'], + "total": find['total'], + "token": find['token'], + "expire": public.format_date(times=find['expire']) + } + session['down'] = True + return render_template('down.html', data=pdata) + + if not find['password']: + session['down'] = True + session[token] = 1 + + if session[token] != 1: + return abort(404) + + filename = find['filename'] + if fname: + filename = os.path.join(filename, fname) + if not public.path_safe_check(fname, False): return abort(404) + if os.path.isdir(filename): + return get_dir_down(filename, token, find) + else: + if os.path.isdir(filename): + return get_dir_down(filename, token, find) + + if request.args.get('play') == 'true': + import panel_video_v2 + start, end = panel_video_v2.get_range(request) + return panel_video_v2.partial_response(filename, start, end) + else: + mimetype = "application/octet-stream" + extName = filename.split('.')[-1] + if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + b_name = os.path.basename(filename) + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + download_name=b_name, + max_age=0) + except: + return abort(404) + + +@app.route(route_v2 + '/database/mongodb/', methods=method_all) +@app.route(route_v2 + '/database/pgsql/', methods=method_all) +@app.route(route_v2 + '/database/redis/', methods=method_all) +@app.route(route_v2 + '/database/sqlite/', methods=method_all) +@app.route(route_v2 + '/database/sqlserver/', methods=method_all) +def databaseModel_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + comReturn = comm.local() + if comReturn: return comReturn + from panelDatabaseControllerV2 import DatabaseController + project_obj = DatabaseController() + defs = ('model',) + get = get_input() + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + return publicObject(project_obj, defs, None, get) + + +# 系统安全模型页面 +# @app.route(route_v2+'/safe//', methods=method_all) +@app.route(route_v2 + '/safe/firewall/', methods=method_all) +@app.route(route_v2 + '/safe/freeip/', methods=method_all) +@app.route(route_v2 + '/safe/ips/', methods=method_all) +@app.route(route_v2 + '/safe/security/', methods=method_all) +@app.route(route_v2 + '/safe/ssh/', methods=method_all) +@app.route(route_v2 + '/safe/syslog/', methods=method_all) +def safeModel_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + comReturn = comm.local() + if comReturn: return comReturn + from panelSafeControllerV2 import SafeController + project_obj = SafeController() + defs = ('model',) + get = get_input() + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + + return publicObject(project_obj, defs, None, get) + + +# 通用模型路由 +@app.route(route_v2 + '/panel/binlog/', methods=method_all) +@app.route(route_v2 + '/panel/bt_check/', methods=method_all) +@app.route(route_v2 + '/panel/clear/', methods=method_all) +@app.route(route_v2 + '/panel/content/', methods=method_all) +@app.route(route_v2 + '/panel/docker/', methods=method_all) +@app.route(route_v2 + '/panel/go/', methods=method_all) +@app.route(route_v2 + '/panel/java/', methods=method_all) +@app.route(route_v2 + '/panel/nodejs/', methods=method_all) +@app.route(route_v2 + '/panel/other/', methods=method_all) +@app.route(route_v2 + '/panel/php/', methods=method_all) +@app.route(route_v2 + '/panel/python/', methods=method_all) +@app.route(route_v2 + '/panel/quota/', methods=method_all) +@app.route(route_v2 + '/panel/quota/', methods=method_all) +@app.route(route_v2 + '/panel/safe_detect/', methods=method_all) +@app.route(route_v2 + '/panel/scanning/', methods=method_all) +@app.route(route_v2 + '/panel/start_content/', methods=method_all) +@app.route(route_v2 + '/panel/totle_db/', methods=method_all) +@app.route(route_v2 + '/panel/webscanning/', methods=method_all) +@app.route(route_v2 + '/panel/public/', methods=method_all) +@app.route(route_v2 + '/monitor/process_management/', + methods=method_all) +@app.route(route_v2 + '/monitor/soft/', methods=method_all) +@app.route(route_v2 + '/files/down/', methods=method_all) +@app.route(route_v2 + '/files/gz/', methods=method_all) +@app.route(route_v2 + '/files/logs/', methods=method_all) +@app.route(route_v2 + '/files/rar/', methods=method_all) +@app.route(route_v2 + '/files/search/', methods=method_all) +@app.route(route_v2 + '/files/size/', methods=method_all) +@app.route(route_v2 + '/files/upload/', methods=method_all) +@app.route(route_v2 + '/files/zip/', methods=method_all) +@app.route(route_v2 + '/logs/ftp/', methods=method_all) +@app.route(route_v2 + '/logs/panel/', methods=method_all) +@app.route(route_v2 + '/logs/site/', methods=method_all) +def allModule_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 4: return + comReturn = comm.local() + if comReturn: return comReturn + p_path = public.get_plugin_path() + '/' + path_split[2] + + defs = ('model',) + get = get_input() + get.model_index = path_split[2] + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + if not request.path.startswith(route_v2 + '/monitor/') and os.path.exists(p_path): + return panel_other(get.model_index, get.mod_name, def_name) + + from panelControllerV2 import Controller + controller_obj = Controller() + return publicObject(controller_obj, defs, None, get) + + +@app.route(route_v2 + '/public', methods=method_all) +def panel_public_v2(): + get = get_input() + if len("{}".format(get.get_items())) > 1024 * 32: + return 'ERROR' + + # 获取ping测试 + if 'get_ping' in get: + try: + import panel_ping_v2 + p = panel_ping_v2.Test() + get = p.check(get) + if not get: return 'ERROR' + result = getattr(p, get['act'])(get) + result_type = type(result) + if str(result_type).find('Response') != -1: return result + return public.getJson(result), json_header + except: + return abort(404) + + if public.cache_get( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp())) != 'check': + return abort(404) + global admin_check_auth, admin_path, route_path, admin_path_file + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + return abort(404) + v_list = ['fun', 'name', 'filename', 'data', 'secret_key'] + for n in get.get_items().keys(): + if not n in v_list: + return abort(404) + + get.client_ip = public.GetClientIp() + num_key = get.client_ip + '_wxapp' + if not public.get_error_num(num_key, 10): + return public.return_msg_gettext( + False, + '10 consecutive authentication failures are prohibited for 1 hour') + if not hasattr(get, 'name'): get.name = '' + if not hasattr(get, 'fun'): return abort(404) + if not public.path_safe_check("%s/%s" % (get.name, get.fun)): + return abort(404) + if get.fun in ['login_qrcode', 'is_scan_ok', 'set_login']: + # 检查是否验证过安全入口 + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + return abort(404) + # 验证是否绑定了设备 + if not public.check_app('app'): + return public.return_msg_gettext(False, 'Unbound user') + import wxapp_v2 + pluwx = wxapp_v2.wxapp() + checks = pluwx._check(get) + if type(checks) != bool or not checks: + public.set_error_num(num_key) + return public.getJson(checks), json_header + data = public.getJson(eval('pluwx.' + get.fun + '(get)')) + return data, json_header + else: + return abort(404) + + +@app.route(route_v2 + '//', methods=method_all) +@app.route(route_v2 + '///', methods=method_all) +def panel_other_v2(name=None, fun=None, stype=None): + # 插件接口 + if public.is_error_path(): + return redirect('/error', 302) + if not name: return abort(404) + if not re.match(r"^[\w\-]+$", name): return abort(404) + if fun and not re.match(r"^[\w\-\.]+$", fun): return abort(404) + if name != "mail_sys" or fun != "send_mail_http.json": + comReturn = comm.local() + if comReturn: return comReturn + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + if fun: + if name == 'btwaf' and fun == 'index': + pass + elif name == 'firewall' and fun == 'get_file': + pass + elif fun == 'static': + pass + elif stype == 'html': + pass + else: + if public.get_csrf_cookie_token_key( + ) in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson( + False, + 'CSRF calibration failed, please login again' + ), json_header + args = None + else: + p_path = public.get_plugin_path() + '/' + name + if not os.path.exists(p_path): return abort(404) + args = get_input() + args_list = [ + 'mail_from', 'password', 'mail_to', 'subject', 'content', + 'subtype', 'data' + ] + for k in args.get_items(): + if not k in args_list: return abort(404) + + is_accept = False + if not fun: fun = 'index.html' + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + + if not name: name = 'coll' + if not public.path_safe_check("%s/%s/%s" % (name, fun, stype)): + return abort(404) + if name.find('./') != -1 or not re.match(r"^[\w-]+$", name): + return abort(404) + if not name: + return public.returnJson( + False, 'Please pass in the plug-in name!'), json_header + p_path = public.get_plugin_path() + '/' + name + if not os.path.exists(p_path): + if name == 'btwaf' and fun == 'index': + pdata = {} + import panel_plugin_v2 + plu_panel = panel_plugin_v2.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + for p in plugin_list['list']: + if p['name'] in ['btwaf']: + if p['endtime'] != 0 and p['endtime'] < time.time(): + pdata['error_msg'] = 1 + break + return render_template('error3.html', data=pdata) + return abort(404) + + # 是否响插件应静态文件 + if fun == 'static': + if stype.find('./') != -1 or not os.path.exists(p_path + '/static'): + return abort(404) + s_file = p_path + '/static/' + stype + if s_file.find('..') != -1: return abort(404) + if not re.match(r"^[\w\./-]+$", s_file): return abort(404) + if not public.path_safe_check(s_file): return abort(404) + if not os.path.exists(s_file): return abort(404) + return send_file(s_file, conditional=True, etag=True) + + # 准备参数 + if not args: args = get_input() + args.client_ip = public.GetClientIp() + args.fun = fun + + # 初始化插件对象 + try: + is_php = os.path.exists(p_path + '/index.php') + if not is_php: + import panel_plugin_v2 + plu_panel = panel_plugin_v2.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + waf = 0 + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + for p in plugin_list['list']: + if p['name'] in ['btwaf']: + if p['endtime'] != 0 and p['endtime'] < time.time(): + waf = -1 + try: + public.package_path_append(p_path) + plugin_main = __import__(name + '_main') + if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list[ + 'pro'] == -1: + return render_template('error3.html', data={}) + except: + if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list[ + 'pro'] == -1: + return render_template('error3.html', data={}) + if os.path.exists("{}/btwaf".format(public.get_plugin_path())): + return render_template('error3.html', data={}) + try: + if sys.version_info[0] == 2: + reload(plugin_main) + else: + from imp import reload + reload(plugin_main) + except: + pass + + plu = eval('plugin_main.' + name + '_main()') + + if not hasattr(plu, fun): + return public.returnJson(False, + 'Plugin does not exist'), json_header + # 执行插件方法 + if not is_php: + if is_accept: + checks = plu._check(args) + if type(checks) != bool or not checks: + return public.getJson(checks), json_header + data = eval('plu.' + fun + '(args)') + else: + comReturn = comm.local() + if comReturn: return comReturn + import panel_php_v2 + args.s = fun + args.name = name + data = panel_php_v2.panelPHP(name).exec_php_script(args) + + r_type = type(data) + if r_type in [Response, Resp]: + return data + + # 处理响应 + if stype == 'json': # 响应JSON + return public.getJson(data), json_header + elif stype == 'html': # 使用模板 + t_path_root = p_path + '/templates/' + t_path = t_path_root + fun + '.html' + if not os.path.exists(t_path): + return public.returnJson( + False, + 'The specified template does not exist!'), json_header + t_body = public.readFile(t_path) + + # 处理模板包含 + rep = r'{%\s?include\s"(.+)"\s?%}' + includes = re.findall(rep, t_body) + for i_file in includes: + filename = p_path + '/templates/' + i_file + i_body = 'ERROR: File ' + filename + ' does not exists.' + if os.path.exists(filename): + i_body = public.readFile(filename) + t_body = re.sub(rep.replace('(.+)', i_file), i_body, t_body) + + return render_template_string(t_body, data=data) + else: # 直接响应插件返回值,可以是任意flask支持的响应类型 + r_type = type(data) + if r_type == dict: + if name == 'btwaf' and 'msg' in data: + return render_template('error3.html', + data={"error_msg": data['msg']}) + return public.returnJson( + False, + public.getMsg('Bad return type [{}]').format( + r_type)), json_header + return data + except: + return public.get_error_info() + return public.get_error_object(None, plugin_name=name) + + +@app.route(route_v2 + '/hook', methods=method_all) +def panel_hook_v2(): + # webhook接口 + get = get_input() + if not os.path.exists('plugin/webhook'): + return abort(404) + public.package_path_append('plugin/webhook') + import webhook_main + return public.getJson(webhook_main.webhook_main().RunHook(get)) + + +@app.route(route_v2 + '/install', methods=method_all) +def install_v2(): + # 初始化面板接口 + if public.is_spider(): return abort(404) + if not os.path.exists('install.pl'): return redirect('/login') + if public.M('config').where("id=?", ('1',)).getField('status') == 1: + if os.path.exists('install.pl'): os.remove('install.pl') + session.clear() + return redirect('/login') + ret_login = os.path.join('/', admin_path) + if admin_path == '/' or admin_path == '/bt': ret_login = '/login' + session['admin_path'] = False + session['login'] = False + if request.method == method_get[0]: + if not os.path.exists('install.pl'): return redirect(ret_login) + data = {} + data['status'] = os.path.exists('install.pl') + data['username'] = public.GetRandomString(8).lower() + return render_template('install.html', data=data) + + elif request.method == method_post[0]: + if not os.path.exists('install.pl'): return redirect(ret_login) + get = get_input() + if not hasattr(get, 'bt_username'): + return public.get_msg_gettext('The user name cannot be empty!') + if not get.bt_username: + return public.get_msg_gettext('The user name cannot be empty!') + if not hasattr(get, 'bt_password1'): + return public.get_msg_gettext('Password can not be blank!') + if not get.bt_password1: + return public.get_msg_gettext('Password can not be blank!') + if get.bt_password1 != get.bt_password2: + return public.get_msg_gettext( + 'The passwords entered twice do not match, please re-enter!') + public.M('users').where("id=?", (1,)).save( + 'username,password', + (get.bt_username, + public.password_salt(public.md5(get.bt_password1.strip()), + uid=1))) + os.remove('install.pl') + public.M('config').where("id=?", ('1',)).setField('status', 1) + data = {} + data['status'] = os.path.exists('install.pl') + data['username'] = get.bt_username + return render_template('install.html', data=data) + + +# --------------------- websocket START -------------------------- # + + +@sockets.route(route_v2 + '/workorder_client') +def workorder_client_v2(ws): + comReturn = comm.local() + if comReturn: return comReturn + + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): + return + + import panelWorkorder + toObject = panelWorkorder.panelWorkorder() + get = get_input() + toObject.client(ws, get) + + +@sockets.route(route_v2 + '/ws_panel') +def ws_panel_v2(ws): + ''' + @name 面板接口ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + comReturn = comm.local() + if comReturn: return comReturn + + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + while True: + pdata = ws.receive() + if pdata == '{}': break + data = json.loads(pdata) + get = public.to_dict_obj(data) + get._ws = ws + p = threading.Thread(target=ws_panel_thread_v2, args=(get,)) + p.start() + + +def ws_panel_thread_v2(get): + ''' + @name 面板管理ws线程 + @author hwliang<2021-07-24> + @param get 请求参数 + @return void + ''' + + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + if not hasattr(get, 'mod_name'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'mod_name'))) + return + if not hasattr(get, 'def_name'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'def_name'))) + return + get.mod_name = get.mod_name.strip() + get.def_name = get.def_name.strip() + check_str = '{}{}'.format(get.mod_name, get.def_name) + if not re.match(r"^\w+$", check_str) or get.mod_name in [ + 'public', 'common', 'db', 'db_mysql', 'downloadFile', 'jobs' + ]: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Unsafe mod_name, def_name parameter content'))) + return + + mod_file = '{}/{}.py'.format(public.get_class_path(), get.mod_name) + if not os.path.exists(mod_file): + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) + return + _obj = public.get_script_object(mod_file) + if not _obj: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) + return + _cls = getattr(_obj, get.mod_name) + if not _cls: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.mod_name)))) + return + _def = getattr(_cls(), get.def_name) + if not _def: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.def_name)))) + return + result = {'callback': get.ws_callback, 'result': _def(get)} + get._ws.send(public.getJson(result)) + + +@sockets.route(route_v2 + '/ws_project') +def ws_project_v2(ws): + ''' + @name 项目管理ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + comReturn = comm.local() + if comReturn: return comReturn + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + from panelProjectControllerV2 import ProjectController + project_obj = ProjectController() + while True: + pdata = ws.receive() + if pdata in '{}': break + get = public.to_dict_obj(json.loads(pdata)) + get._ws = ws + p = threading.Thread(target=ws_project_thread_v2, + args=(project_obj, get)) + p.start() + + +def ws_project_thread_v2(_obj, get): + ''' + @name 项目管理ws线程 + @author hwliang<2021-07-24> + @param _obj 项目管理控制器对像 + @param get 请求参数 + @return void + ''' + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + result = {'callback': get.ws_callback, 'result': _obj.model(get)} + get._ws.send(public.getJson(result)) + + +sock_pids = {} + + +@sockets.route(route_v2 + '/sock_shell') +def sock_shell_v2(ws): + ''' + @name 执行指定命令,实时输出命令执行结果 + @author hwliang<2021-07-19> + @return void + + 示例: + p = new WebSocket('ws://192.168.1.247:8888/sock_shell') + p.send('ping www.bt.cn -c 100') + ''' + comReturn = comm.local() + if comReturn: + ws.send(str(comReturn)) + return + kill_closed_v2() + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + t = None + try: + while True: + cmdstring = ws.receive() + if cmdstring in ['stop', 'error'] or not cmdstring: + break + t = threading.Thread(target=sock_recv, args=(cmdstring, ws)) + t.start() + kill_closed_v2() + except: + kill_closed_v2() + + +def kill_closed_v2(): + ''' + @name 关闭已关闭的连接 + @author hwliang<2021-07-24> + @return void + ''' + global sock_pids + import psutil + pids = psutil.pids() + keys = sock_pids.copy().keys() + for pid in keys: + if hasattr(sock_pids[pid], 'closed'): + is_closed = sock_pids[pid].closed + else: + is_closed = not sock_pids[pid].connected + + logging.debug("PID: {} , sock_stat: {}".format(pid, is_closed)) + if not is_closed: continue + + if pid in pids: + try: + p = psutil.Process(pid) + for cp in p.children(): + cp.kill() + p.kill() + logging.debug("killed: {}".format(pid)) + sock_pids.pop(pid) + except: + pass + else: + sock_pids.pop(pid) + + +@app.route(route_v2 + '/close_sock_shell', methods=method_all) +def close_sock_shell_v2(): + ''' + @name 关闭指定命令 + @author hwliang<2021-07-19> + @param cmdstring 完整命令行 + @return dict + 示例: + $.post('/close_sock_shell',{cmdstring:'ping www.bt.cn -c 100'}) + ''' + comReturn = comm.local() + if comReturn: return comReturn + args = get_input() + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + cmdstring = args.cmdstring.strip() + skey = public.md5(cmdstring) + pid = cache.get(skey) + if not pid: + return json.dumps( + public.return_data( + False, [], error_msg='The specified sock has been terminated!') + ), json_header + os.kill(pid, 9) + cache.delete(skey) + return json.dumps(public.return_data(True, + 'Successful operation!')), json_header + + +@sockets.route(route_v2 + '/webssh') +def webssh_v2(ws): + # 宝塔终端连接 + comReturn = comm.local() + if comReturn: + ws.send(str(comReturn)) + return + if not ws: return 'False' + get = ws.receive() + if not get: return + get = json.loads(get) + if not check_csrf_websocket(ws, get): + return + + import ssh_terminal_v2 + sp = ssh_terminal_v2.ssh_host_admin() + if 'host' in get: + ssh_info = {} + ssh_info['host'] = get['host'].strip() + if 'port' in get: + ssh_info['port'] = int(get['port']) + if 'username' in get: + ssh_info['username'] = get['username'].strip() + if 'password' in get: + ssh_info['password'] = get['password'].strip() + if 'pkey' in get: + ssh_info['pkey'] = get['pkey'].strip() + + if get['host'] in ['127.0.0.1', 'localhost' + ] and 'port' not in ssh_info: + ssh_info = sp.get_ssh_info('127.0.0.1') + if not ssh_info: ssh_info = sp.get_ssh_info('localhost') + if not ssh_info: ssh_info = {"host": "127.0.0.1"} + ssh_info['port'] = public.get_ssh_port() + else: + ssh_info = sp.get_ssh_info('127.0.0.1') + if not ssh_info: ssh_info = sp.get_ssh_info('localhost') + if not ssh_info: ssh_info = {"host": "127.0.0.1"} + ssh_info['port'] = public.get_ssh_port() + + if not ssh_info['host'] in ['127.0.0.1', 'localhost']: + if not 'username' in ssh_info: + ssh_info = sp.get_ssh_info(ssh_info['host']) + if not ssh_info: + ws.send( + 'The specified host information is not found, please add it again!' + ) + return + p = ssh_terminal_v2.ssh_terminal() + p.run(ws, ssh_info) + del (p) + if ws.connected: + ws.close() + return 'False' + + +# --------------------- websocket END -------------------------- # + + +@app.route(route_v2 + "/daily", methods=method_all) +def daily_v2(): + """面板日报数据""" + + comReturn = comm.local() + if comReturn: return comReturn + + import panelDaily + toObject = panelDaily.panelDaily() + + defs = ("get_app_usage", "get_daily_data", "get_daily_list") + result = publicObject(toObject, defs) + return result + + +@app.route(route_v2 + '/phpmyadmin/', methods=method_all) +def pma_proxy_v2(path_full=None): + ''' + @name phpMyAdmin代理 + @author hwliang<2022-01-19> + @return Response + ''' + comReturn = comm.local() + if comReturn: return comReturn + cache_key = 'pmd_port_path' + pmd = cache.get(cache_key) + if not pmd: + pmd = get_phpmyadmin_dir() + if not pmd: + return 'phpMyAdmin is not installed, please go to the [App Store] page to install it!' + pmd = list(pmd) + cache.set(cache_key, pmd, 10) + panel_pool = 'http://' + if request.url_root[:5] == 'https': + panel_pool = 'https://' + import ajax + ssl_info = ajax.ajax().get_phpmyadmin_ssl(None) + if ssl_info['status']: + pmd[1] = ssl_info['port'] + else: + panel_pool = 'http://' + + proxy_url = '{}127.0.0.1:{}/{}/'.format( + panel_pool, pmd[1], pmd[0]) + request.full_path.replace( + '/phpmyadmin/', '') + from panel_http_proxy_v2 import HttpProxy + px = HttpProxy() + return px.proxy(proxy_url) + + +@app.route(route_v2 + '/p/', methods=method_all) +@app.route(route_v2 + '/p//', methods=method_all) +@app.route(route_v2 + '/p//', methods=method_all) +def proxy_port_v2(port, full_path=None): + ''' + @name 代理指定端口 + @author hwliang<2022-01-19> + @return Response + ''' + + comReturn = comm.local() + if comReturn: return comReturn + full_path = request.full_path.replace('/p/{}/'.format(port), + '').replace('/p/{}'.format(port), '') + uri = '{}/{}'.format(port, full_path) + uri = uri.replace('//', '/') + proxy_url = 'http://127.0.0.1:{}'.format(uri) + from panel_http_proxy_v2 import HttpProxy + px = HttpProxy() + return px.proxy(proxy_url) + + +@app.route(route_v2 + '/push', methods=method_all) +def push_v2(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + import panel_push_v2 + toObject = panel_push_v2.panelPush() + defs = ('set_push_status', 'get_push_msg_list', 'get_modules_list', + 'install_module', 'uninstall_module', 'get_module_template', + 'set_push_config', 'get_push_config', 'del_push_config', + 'get_module_logs', 'get_module_config', 'get_push_list', + 'get_push_logs') + result = publicObject(toObject, defs, None, pdata) + return result + + +# 2024/1/24 上午 11:42 新场景模型的路由 +# @app.route(route_v2 + '/mod///', methods=method_all) +# @app.route(route_v2 + '/mod////', methods=method_all) +# def panel_mod_v2(name=None, sub_name=None, fun=None, stype=None): +# @app.route('/mod/proxy/com/', methods=method_all) +# @app.route('/mod/proxy/com//', methods=method_all) +@app.route(route_v2 + '/mod/proxy/com/', methods=method_all) +@app.route(route_v2 + '/mod/proxy/com//', methods=method_all) +# @app.route(route_v2 + '/project/proxy/', methods=method_all) +# @app.route(route_v2 + '/project/proxy//', methods=method_all) +def panel_mod_v2(fun=None, stype=None): + ''' + @name 新场景模型的路由 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + name = path_split[3] + sub_name = path_split[4] + # if not public.is_bind(): + # return redirect('/bind', 302) + if public.is_error_path(): + return redirect('/error', 302) + if not name: return abort(404) + if not sub_name: return abort(404) + if not re.match(r"^[\w\-]+$", name): return abort(404) + if not re.match(r"^[\w\-]+$", sub_name): return abort(404) + if fun and not re.match(r"^[\w\-\.]+$", fun): return abort(404) + + comReturn = comm.local() + if comReturn: return comReturn + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + if fun: + if public.get_csrf_cookie_token_key() in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + + args = get_mod_input() + + if not fun: fun = 'index.html' + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + + if not name: name = 'coll' + if not public.path_safe_check("%s/%s/%s/%s" % (name, sub_name, fun, stype)): + return abort(404) + if name.find('./') != -1 or not re.match(r"^[\w-]+$", name): + return abort(404) + if sub_name.find('./') != -1 or not re.match(r"^[\w-]+$", sub_name): + return abort(404) + if not name: + return public.returnJson(False, 'PLUGIN_INPUT_ERR'), json_header + + args.client_ip = public.GetClientIp() + + # 初始化新场景模型对象 + try: + from mod.modController import Controller + controller_obj = Controller() + defs = ('model',) + args.model_index = "mod" + args.action = 'model' + args.mod_name = name + args.sub_mod_name = sub_name + args.def_name = fun + data = publicObject(controller_obj, defs, None, args) + r_type = type(data) + if r_type in [Response, Resp]: + return data + + p_path = public.get_mod_path() + '/' + name + # 处理响应 + if stype == 'json': # 响应JSON + return public.getJson(data), json_header + elif stype == 'html': # 使用模板 + t_path_root = p_path + '/templates/' + t_path = t_path_root + fun + '.html' + if not os.path.exists(t_path): + return public.returnJson(False, + 'PLUGIN_NOT_TEMPLATE'), json_header + t_body = public.readFile(t_path) + # 处理模板包含 + rep = r'{%\s?include\s"(.+)"\s?%}' + includes = re.findall(rep, t_body) + for i_file in includes: + filename = p_path + '/templates/' + i_file + i_body = 'ERROR: File ' + filename + ' does not exists.' + if os.path.exists(filename): + i_body = public.readFile(filename) + t_body = re.sub(rep.replace('(.+)', i_file), i_body, t_body) + return render_template_string(t_body, data=data) + else: # 直接响应插件返回值,可以是任意flask支持的响应类型 + r_type = type(data) + if r_type == dict: + if name == 'btwaf' and 'msg' in data: + return render_template('error3.html', + data={"error_msg": data['msg']}) + return public.returnJson( + False, + public.getMsg('PUBLIC_ERR_RETURN').format( + r_type)), json_header + return data + except: + if not 'login' in session: return abort(404) + return public.get_error_object(None, plugin_name=name) + +@app.route(route_v2 + '/check_auth', methods=method_all) +def check_auth_v2(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + if os.path.exists('data/.is_pro.pl'): + return public.return_message(0,0,'true') + return public.return_message(-1,0,'false') + +@app.route('/bind', methods=method_get) +def bind(): + comReturn = comm.local() + if comReturn: return comReturn + if public.is_bind(): return redirect('/', 302) + data = {} + data['lan'] = public.GetLan('index_new') + # g.title = '请先绑定宝塔帐号' + return render_template('index_new.html', data=data) + + + + +# ===========================================================v2路由区end===========================================================# + +@app.route('/v2/install_finish', methods=method_post) +def install_finish(): + with open('{}/data/install_finished.mark'.format(public.get_panel_path()), 'w') as fp: + fp.write('True') + return public.return_message(0, 0, 'Successfully') + + +@app.route('/v2/wp/login/', methods=method_get) +def wp_login(site_id: int): + if site_id < 1: + return public.gettext_msg('Invalid site_id') + + from wp_toolkit import wpmgr + + return wpmgr(site_id).auto_login() + + +# 获取新场景模型的传参数据 +def get_mod_input(): + ''' + @name # 获取新场景模型的传参数据 + @author wzz <2024/1/24 上午 11:42> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + data = public.dict_obj() + exludes = ['blob'] + for key in request.args.keys(): + data.set(key, str(request.args.get(key, ''))) + + if request.is_json: + for key in request.get_json().keys(): + data.set(key, str(request.get_json()[key])) + else: + try: + for key in request.form.keys(): + if key in exludes: continue + data.set(key, str(request.form.get(key, ''))) + except: + try: + post = request.form.to_dict() + for key in post.keys(): + if key in exludes: continue + data.set(key, str(post[key])) + except: + pass + + if 'form_data' in g: + for k in g.form_data.keys(): + data.set(k, str(g.form_data[k])) + + if not hasattr(data, 'data'): data.data = [] + return data diff --git a/BTPanel/app.py b/BTPanel/app.py new file mode 100644 index 00000000..6b62c077 --- /dev/null +++ b/BTPanel/app.py @@ -0,0 +1,671 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +import logging +import sys +import json +import os +import threading +import time +import re +import uuid +import psutil + +panel_path = '/www/server/panel' +if not os.name in ['nt']: + os.chdir(panel_path) +if not 'class/' in sys.path: + sys.path.insert(0, 'class/') +if not 'class_v2/' in sys.path: + sys.path.insert(0, 'class_v2/') +from flask import Flask, session, render_template, send_file, request, redirect, g, make_response, \ + render_template_string, abort, stream_with_context, Response as Resp +from cachelib import SimpleCache, SimpleCacheSession +from werkzeug.wrappers import Response +from werkzeug.routing import BaseConverter +from flask_session import Session +from flask_compress import Compress + +cache = SimpleCache(5000) +import public + +# class RestrictedImportHook: +# def __init__(self, allowed_modules): +# self.allowed_modules = allowed_modules +# +# def find_spec(self, fullname, path, target=None): +# # 判断当前导入是否是功能模块,如果是则禁止导入 +# if fullname in self.allowed_modules: +# return None +# return self +# +# def loader(self, fullname): +# # 不禁止public模块的导入 +# if fullname == 'public': +# pass +# +# # 其他功能模块 不能互相导入 +# # elif fullname in ['aaa', 'bbb', 'ccc']: +# # return APIModuleLoader(self.allowed_module) +# 设置导入钩子 +# sys.meta_path.insert(0, RestrictedImportHook(['panelPlugin', 'pay', 'ols', 'wxapp'])) +# sys.meta_path.insert(0, RestrictedImportHook(['wxapp'])) +# class APIModuleLoader: +# def __init__(self, allowed_module): +# self.allowed_module = allowed_module +# +# def exec_module(self, module): +# if self.allowed_module != 'public': +# raise ImportError(f"API module '{self.allowed_module}' cannot import other API modules") +# +# if module.__name__ != 'public': +# module.__dict__['public'] = sys.modules['public'] + +# # 设置导入钩子 +# sys.meta_path.insert(0, RestrictedImportHook('public')) + +# 初始化Flask应用 +app = Flask(__name__, + template_folder="templates/{}".format( + public.GetConfigValue('template'))) +Compress(app) +try: + from flask_sock import Sock +except: + from flask_sockets import Sockets as Sock + +sockets = Sock(app) +# 注册HOOK +hooks = {} +if not hooks: + public.check_hooks() +# import db +dns_client = None +app.config['DEBUG'] = os.path.exists('data/debug.pl') +app.config['SSL'] = os.path.exists('data/ssl.pl') + +# 设置BasicAuth +basic_auth_conf = 'config/basic_auth.json' +app.config['BASIC_AUTH_OPEN'] = False +if os.path.exists(basic_auth_conf): + try: + ba_conf = json.loads(public.readFile(basic_auth_conf)) + app.config['BASIC_AUTH_USERNAME'] = ba_conf['basic_user'] + app.config['BASIC_AUTH_PASSWORD'] = ba_conf['basic_pwd'] + app.config['BASIC_AUTH_OPEN'] = ba_conf['open'] + except: + pass + +# 初始化SESSION服务 +app.secret_key = public.md5( + str(os.uname()) + + str(psutil.boot_time())) # uuid.UUID(int=uuid.getnode()).hex[-12:] +local_ip = None +my_terms = {} +app.config['SESSION_MEMCACHED'] = SimpleCacheSession(1000, 86400) +app.config['SESSION_TYPE'] = 'memcached' +app.config['SESSION_PERMANENT'] = True +app.config['SESSION_USE_SIGNER'] = True +app.config['SESSION_KEY_PREFIX'] = 'BT_:' +app.config['SESSION_COOKIE_NAME'] = public.md5(app.secret_key) +app.config['PERMANENT_SESSION_LIFETIME'] = 86400 * 30 +if app.config['SSL']: + app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' + app.config['SESSION_COOKIE_SECURE'] = True +else: + app.config['SESSION_COOKIE_SAMESITE'] = None + +Session(app) + +import common + +# 初始化路由 +comm = common.panelAdmin() +method_all = ['GET', 'POST'] +method_get = ['GET'] +method_post = ['POST'] +json_header = {'Content-Type': 'application/json; charset=utf-8'} +text_header = {'Content-Type': 'text/plain; charset=utf-8'} +cache.set('p_token', 'bmac_' + public.Md5(public.get_mac_address())) +admin_path_file = 'data/admin_path.pl' +admin_path = '/' +if os.path.exists(admin_path_file): + admin_path = public.readFile(admin_path_file).strip() +admin_path_checks = [ + '/', + '/san', + '/bak', + '/monitor', + '/abnormal', + '/close', + '/task', + '/login', + '/config', + '/site', + '/sites', + '/ftp', + '/public', + '/database', + '/data', + '/download_file', + '/control', + '/crontab', + '/firewall', + '/files', + '/soft', + '/ajax', + '/system', + '/panel_data', + '/code', + '/ssl', + '/plugin', + '/wxapp', + '/hook', + '/safe', + '/yield', + '/downloadApi', + '/pluginApi', + '/auth', + '/download', + '/cloud', + '/webssh', + '/connect_event', + '/panel', + '/acme', + '/down', + '/api', + '/tips', + '/message', + '/warning', + '/userRegister', # 面板内注册 + '/docker', + '/btdocker', +] +if admin_path in admin_path_checks: admin_path = '/bt' +if admin_path[-1] == '/': admin_path = admin_path[:-1] +uri_match = re.compile( + r"(^/static/[\w_\./\-]+\.(js|css|png|jpg|gif|ico|svg|woff|woff2|ttf|otf|eot|map)$|^/[\w_\./\-]*$)" +) +session_id_match = re.compile(r"^[\w\.\-]+$") + +route_path = os.path.join(admin_path, '') +if not route_path: route_path = '/' +if route_path[-1] == '/': route_path = route_path[:-1] +if route_path[0] != '/': route_path = '/' + route_path + +route_v2 = '/v2' #v2版本路由前缀 + +# ======================公共方法区域START============================# + +def error_500(e: Exception): + if request.method not in ['GET', 'POST']: return + if not session.get('login', None): + g.auth_error = True + return public.error_not_login() + ss = '''404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. + +During handling of the above exception, another exception occurred:''' + error_info = public.get_error_info().strip().split(ss)[-1].strip() + _form = request.form.to_dict() + if 'username' in _form: _form['username'] = '******' + if 'password' in _form: _form['password'] = '******' + if 'phone' in _form: _form['phone'] = '******' + request_info = '''REQUEST_DATE: {request_date} + PAN_VERSION: {panel_version} + OS_VERSION: {os_version} + REMOTE_ADDR: {remote_addr} + REQUEST_URI: {method} {full_path} +REQUEST_FORM: {request_form} + USER_AGENT: {user_agent}'''.format( + request_date=public.getDate(), + remote_addr=public.GetClientIp(), + method=request.method, + full_path=public.xsssec(request.full_path), + request_form=public.xsssec(str(_form)), + user_agent=public.xsssec(request.headers.get('User-Agent')), + panel_version=public.version(), + os_version=public.get_os_version()) + + result = public.readFile( + public.get_panel_path() + + '/BTPanel/templates/default/panel_error.html').format( + error_title=error_info.split("\n")[-1], + request_info=request_info, + error_msg=error_info) + return Resp(result, 500) + +def get_dir_down(filename, token, find): + # 获取分享目录信息 + import files + args = public.dict_obj() + args.path = filename + args.share = True + to_path = filename.replace(find['filename'], '').strip('/') + + if request.args.get('play') == 'true': + pdata = files.files().get_videos(args) + return public.GetJson(pdata), json_header + else: + pdata = files.files().GetDir(args) + pdata['token'] = token + pdata['ps'] = find['ps'] + pdata['src_path'] = find['filename'] + pdata['to_path'] = to_path + if find['expire'] < (time.time() + (86400 * 365 * 10)): + pdata['expire'] = public.format_date(times=find['expire']) + else: + pdata['expire'] = public.get_msg_gettext('Never Expires') + pdata['filename'] = (find['filename'].split('/')[-1] + '/' + + to_path).strip('/') + return render_template('down.html', data=pdata, to_size=public.to_size) + + +def get_phpmyadmin_dir(): + # 获取phpmyadmin目录 + path = public.GetConfigValue('setup_path') + '/phpmyadmin' + if not os.path.exists(path): return None + phpport = '888' + try: + import re + if session['webserver'] == 'nginx': + filename = public.GetConfigValue( + 'setup_path') + '/nginx/conf/nginx.conf' + conf = public.readFile(filename) + rep = r"listen\s+([0-9]+)\s*;" + rtmp = re.search(rep, conf) + if rtmp: + phpport = rtmp.groups()[0] + if session['webserver'] == 'apache': + filename = public.GetConfigValue( + 'setup_path') + '/apache/conf/extra/httpd-vhosts.conf' + conf = public.readFile(filename) + rep = r"Listen\s+([0-9]+)\s*\n" + rtmp = re.search(rep, conf) + if rtmp: + phpport = rtmp.groups()[0] + if session['webserver'] == 'openlitespeed': + filename = public.GetConfigValue( + 'setup_path') + '/panel/vhost/openlitespeed/listen/888.conf' + public.writeFile('/tmp/2', filename) + conf = public.readFile(filename) + rep = r"address\s*\*\:\s*(\d+)" + rtmp = re.search(rep, conf) + if rtmp: + phpport = rtmp.groups()[0] + except: + pass + + for filename in os.listdir(path): + filepath = path + '/' + filename + if os.path.isdir(filepath): + if filename[0:10] == 'phpmyadmin': + return str(filename), phpport + return None + + +class run_exec: + # 模块访问对像 + def run(self, toObject, defs, get): + result = None + + if not get.action in defs: + return public.ReturnJson( + False, 'Specific parameters are invalid!'), json_header + + result = getattr(toObject, get.action)(get) + if not hasattr(get, 'html') and not hasattr(get, 's_module'): + r_type = type(result) + if r_type in [Response, Resp]: return result + result = public.GetJson(result), json_header + + if g.is_aes: + result = public.aes_encrypt(result[0], g.aes_key), json_header + + return result + + +def check_csrf(): + # CSRF校验 + if app.config['DEBUG']: return True + http_token = request.headers.get('x-http-token') + if not http_token: return False + if http_token != public.get_csrf_sess_html_token_value(): return False + return True + + +def publicObject(toObject, defs, action=None, get=None, is_csrf=True): + try: + # 模块访问前置检查 + if is_csrf and public.get_csrf_sess_html_token_value() and session.get( + 'login', None): + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + + if not get: get = get_input() + if action: get.action = action + + if hasattr(get, 'path'): + get.path = get.path.replace('//', '/').replace('\\', '/') + if get.path.find('./') != -1: + return public.ReturnJson(False, 'Unsafe path'), json_header + if get.path.find('->') != -1: + get.path = get.path.split('->')[0].strip() + get.path = public.xssdecode(get.path) + if hasattr(get, 'filename'): + get.filename = public.xssdecode(get.filename) + + if hasattr(get, 'sfile'): + get.sfile = get.sfile.replace('//', '/').replace('\\', '/') + get.sfile = public.xssdecode(get.sfile) + if hasattr(get, 'dfile'): + get.dfile = get.dfile.replace('//', '/').replace('\\', '/') + get.dfile = public.xssdecode(get.dfile) + + if hasattr(toObject, 'site_path_check'): + if not toObject.site_path_check(get): + return public.ReturnJson( + False, "Overstepping one authority!"), json_header + return run_exec().run(toObject, defs, get) + except: + return error_500(None) + + +def check_login(http_token=None): + # 检查是否登录面板 + if cache.get('dologin'): return False + if 'login' in session: + loginStatus = session['login'] + if loginStatus and http_token: + if public.get_csrf_sess_html_token_value() != http_token: + return False + return loginStatus + return False + + +def get_pd(): + # 获取授权信息 + tmp = -1 + # try: + # import panelPlugin + # get = public.dict_obj() + # # get.init = 1 + # tmp1 = panelPlugin.panelPlugin().get_cloud_list(get) + # except: + tmp1 = None + if tmp1: + tmp = tmp1[public.to_string([112, 114, 111])] + ltd = tmp1.get('ltd', -1) + else: + ltd = -1 + tmp4 = cache.get(public.to_string([112, 95, 116, 111, 107, 101, 110])) + if tmp4: + tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4 + if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1') + tmp = public.readFile(tmp_f) + if tmp: tmp = int(tmp) + + if ltd < 1: + if ltd == -2: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, + 116, 108, 116, 100, 45, 103, 114, 97, 121, 34, 62, 60, 115, + 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, + 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, + 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, + 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, + 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399, + 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, + 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, + 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, + 117, 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, + 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, + 62 + ]) + elif tmp == -1: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, + 116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, 111, 110, + 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, + 46, 114, 101, 110, 101, 119, 95, 112, 114, 111, 40, 41, 34, 32, + 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107, 32, + 116, 111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 20813, + 36153, 29256, 60, 47, 115, 112, 97, 110, 62 + ]) + elif tmp == -2: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, + 116, 112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60, 115, + 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, + 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, + 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, + 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, + 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399, + 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, + 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, + 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, + 114, 101, 110, 101, 119, 95, 112, 114, 111, 40, 41, 34, 62, 82, + 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, 62 + ]) + if tmp >= 0 and ltd in [-1, -2]: + if tmp == 0: + tmp2 = public.to_string([27704, 20037, 25480, 26435]) + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 112, 114, 111, 34, 62, 123, 48, 125, 60, 115, 112, + 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, + 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, + 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, + 98, 111, 108, 100, 59, 34, 62, 123, 49, 125, 60, 47, 115, + 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62 + ]).format( + public.to_string([21040, 26399, 26102, 38388, 65306]), + tmp2) + else: + tmp2 = time.strftime( + public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), + time.localtime(tmp)) + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 112, 114, 111, 34, 62, 69, 120, 112, 105, 114, + 101, 58, 32, 60, 115, 112, 97, 110, 32, 115, 116, 121, 108, + 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, + 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, + 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, + 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, + 120, 34, 62, 123, 48, 125, 60, 47, 115, 112, 97, 110, 62, + 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, + 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, + 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, + 101, 119, 95, 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, + 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, 62 + ]).format(tmp2) + else: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, + 116, 112, 114, 111, 45, 103, 114, 97, 121, 34, 32, 111, 110, + 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, + 46, 114, 101, 110, 101, 119, 95, 112, 114, 111, 40, 41, 34, 32, + 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107, 32, + 116, 111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 70, 82, + 69, 69, 60, 47, 115, 112, 97, 110, 62 + ]) + else: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, + 108, 116, 100, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, + 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, + 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, + 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, + 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, + 58, 53, 112, 120, 34, 62, 123, 125, 60, 47, 115, 112, 97, 110, 62, + 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, + 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, + 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114, 111, + 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, + 112, 97, 110, 62 + ]).format( + time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), + time.localtime(ltd))) + + return tmp3, tmp, ltd + + +def send_authenticated(): + # 发送http认证信息 + request_host = public.GetHost() + result = Response( + '', 401, + {'WWW-Authenticate': 'Basic realm="%s"' % request_host.strip()}) + if not 'login' in session and not 'admin_auth' in session: session.clear() + return result + + +# 取端口 +def FtpPort(): + # 获取FTP端口 + if session.get('port'): return + import re + try: + file = public.GetConfigValue( + 'setup_path') + '/pure-ftpd/etc/pure-ftpd.conf' + conf = public.readFile(file) + rep = r"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)" + port = re.search(rep, conf).groups()[0] + except: + port = '21' + session['port'] = port + + +def is_login(result): + # 判断是否登录2 + if 'login' in session: + if session['login'] == True: + # result = make_response(result) + # request_token = public.GetRandomString(48) + # request_token_key = public.get_csrf_cookie_token_key() + # session[request_token_key] = request_token + # samesite = app.config['SESSION_COOKIE_SAMESITE'] + # secure = app.config['SESSION_COOKIE_SECURE'] + # if app.config['SSL'] and request.full_path.find('/login?tmp_token=') == 0: + # samesite = 'None' + # secure = True + # result.set_cookie(request_token_key, request_token, + # max_age=86400 * 30, + # samesite= samesite, + # secure=secure + # ) + pass + return result + + +# js随机数模板使用,用于不更新版本号时更新前端文件不需要用户强制刷新浏览器 +def get_js_random(): + js_random = public.readFile('data/js_random.pl') + if not js_random or js_random == '1': + js_random = public.GetRandomString(16) + public.writeFile('data/js_random.pl', js_random) + return js_random + + +# 获取输入数据 +def get_input(): + data = public.dict_obj() + exludes = ['blob'] + for key in request.args.keys(): + data.set(key, str(request.args.get(key, ''))) + try: + for key in request.form.keys(): + if key in exludes: + continue + data.set(key, str(request.form.get(key, ''))) + + except Exception as ex: + # public.print_log("error1 {}".format(ex)) + + try: + post = request.form.to_dict() + for key in post.keys(): + if key in exludes: continue + data.set(key, str(post[key])) + except: + pass + + if 'form_data' in g: + for k in g.form_data.keys(): + data.set(k, str(g.form_data[k])) + + if not hasattr(data, 'data'): + data.data = [] + return data + + +# 取数据对象 +def get_input_data(data): + pdata = public.dict_obj() + for key in data.keys(): + pdata[key] = str(data[key]) + return pdata + + +# 检查Token +def check_token(data): + # 已作废 + pluginPath = 'plugin/safelogin/token.pl' + if not os.path.exists(pluginPath): return False + from urllib import unquote + from binascii import unhexlify + from json import loads + + result = unquote(unhexlify(data)) + token = public.readFile(pluginPath).strip() + + result = loads(result) + if not result: return False + if result['token'] != token: return False + return result + + +# ======================公共方法区域END============================# + + +# ======================自定义路由URL匹配规则转换器Begin===================== # + +class RegexConverter(BaseConverter): + """ + 自定义URL匹配正则表达式 + """ + + def __init__(self, map, regex): + super(RegexConverter, self).__init__(map) + self.regex = regex + + def to_python(self, value): + """ + 路由匹配时,匹配成功后传递给视图函数中参数的值 + :param value: + :return: + """ + return value + + def to_url(self, value): + """ + 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 + :param value: + :return: + """ + val = super(RegexConverter, self).to_url(value) + return val + + +# 添加到flask中 +app.url_map.converters.update({ + 'regex': RegexConverter +}) + +# ======================自定义路由URL匹配规则转换器End===================== # diff --git a/BTPanel/routes/flask_hook.py b/BTPanel/routes/flask_hook.py new file mode 100644 index 00000000..221c98c5 --- /dev/null +++ b/BTPanel/routes/flask_hook.py @@ -0,0 +1,194 @@ +# ===================================Flask HOOK========================# +from BTPanel.app import * + +# Flask请求勾子 +@app.before_request +def request_check(): + if request.method not in ['GET', 'POST']: return abort(404) + + # 获取客户端真实IP + x_real_ip = request.headers.get('X-Real-Ip') + if x_real_ip: + request.remote_addr = x_real_ip + request.environ.setdefault('REMOTE_PORT', public.get_remote_port()) + + g.request_time = time.time() + g.return_message = False + # 路由和URI长度过滤 + if len(request.path) > 256: return abort(403) + if len(request.url) > 1024: return abort(403) + # URI过滤 + if not uri_match.match(request.path): return abort(403) + # POST参数过滤 + if request.path in [ + '/login', + '/safe', + # '/v2_safe', + '/hook', + '/public', + '/down', + '/get_app_bind_status', + '/check_bind', + '/userRegister', + ]: + pdata = request.form.to_dict() + for k in pdata.keys(): + if len(k) > 48: return abort(403) + if len(pdata[k]) > 256: return abort(403) + # SESSIONID过滤 + session_id = request.cookies.get(app.config['SESSION_COOKIE_NAME'], '') + if session_id and not session_id_match.match(session_id): return abort(403) + + # 请求头过滤 + # if not public.filter_headers(): + # return abort(403) + + if session.get('debug') == 1: return + g.get_csrf_html_token_key = public.get_csrf_html_token_key() + + if app.config['BASIC_AUTH_OPEN']: + if request.path in [ + '/public', '/download', '/mail_sys', '/hook', '/down', + '/check_bind', '/get_app_bind_status' + ]: + return + auth = request.authorization + if not comm.get_sk(): return + if not auth: return send_authenticated() + tips = '_bt.cn' + if public.md5(auth.username.strip() + tips) != app.config['BASIC_AUTH_USERNAME'] \ + or public.md5(auth.password.strip() + tips) != app.config['BASIC_AUTH_PASSWORD']: + return send_authenticated() + + if not request.path in [ + '/safe', + # '/v2_safe', + '/hook', + '/public', + '/mail_sys', + '/down' + ]: + ip_check = public.check_ip_panel() + if ip_check: return ip_check + + if request.path.startswith('/static/') or request.path == '/code': + if not 'login' in session and not 'admin_auth' in session and not 'down' in session: + return abort(401) + domain_check = public.check_domain_panel() + if domain_check: return domain_check + if public.is_local(): + not_networks = ['uninstall_plugin', 'install_plugin', 'UpdatePanel'] + if request.args.get('action') in not_networks: + return public.returnJson( + False, + 'This feature cannot be used in offline mode!'), json_header + + if request.path in [ + '/site', '/ftp', '/database', '/soft', '/control', '/firewall', + '/files', '/xterm', '/crontab', '/config' + ]: + if public.is_error_path(): + return redirect('/error', 302) + if not request.path in ['/config']: + if session.get('password_expire', False): + return redirect('/modify_password', 302) + + +# Flask 请求结束勾子 +@app.teardown_request +def request_end(reques=None): + if request.method not in ['GET', 'POST']: return + if not request.path.startswith('/static/'): + public.write_request_log(reques) + #当路由为/plugin时,不检测g.return_message + # if not request.path.startswith('/plugin'): + if request.path.startswith('/sitetest'): + if 'return_message' in g: + if not g.return_message: + public.print_log("当前为网站路由,且未使用统一响应函数public.return_message") + return abort(403) + # return public.returnJson( + # False, 'Request failed!Request not using unified response!' + # ), json_header + else: + g.return_message = False + public.print_log("当前为网站路由,且已使用统一响应函数public.return_message") + if 'api_request' in g: + if g.api_request: + session.clear() + + +# Flask 404页面勾子 +@app.errorhandler(404) +def error_404(e): + if request.method not in ['GET', 'POST']: return + if not session.get('login', None): + g.auth_error = True + return public.error_not_login() + errorStr = ''' +404 Not Found + +

404 Not Found

+
nginx
+ +''' + headers = {"Content-Type": "text/html"} + return Response(errorStr, status=404, headers=headers) + + +# Flask 403页面勾子 +@app.errorhandler(403) +def error_403(e): + if request.method not in ['GET', 'POST']: return + if not session.get('login', None): + g.auth_error = True + return public.error_not_login() + errorStr = ''' +403 Forbidden + +

403 Forbidden

+
nginx
+ +''' + headers = {"Content-Type": "text/html"} + return Response(errorStr, status=403, headers=headers) + + +# Flask 500页面勾子 +@app.errorhandler(500) +def error_500(e): + if request.method not in ['GET', 'POST']: return + if not session.get('login', None): + g.auth_error = True + return public.error_not_login() + ss = '''404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. + +During handling of the above exception, another exception occurred:''' + error_info = public.get_error_info().strip().split(ss)[-1].strip() + _form = request.form.to_dict() + if 'username' in _form: _form['username'] = '******' + if 'password' in _form: _form['password'] = '******' + if 'phone' in _form: _form['phone'] = '******' + request_info = '''REQUEST_DATE: {request_date} + PAN_VERSION: {panel_version} + OS_VERSION: {os_version} + REMOTE_ADDR: {remote_addr} + REQUEST_URI: {method} {full_path} +REQUEST_FORM: {request_form} + USER_AGENT: {user_agent}'''.format( + request_date=public.getDate(), + remote_addr=public.GetClientIp(), + method=request.method, + full_path=public.xsssec(request.full_path), + request_form=public.xsssec(str(_form)), + user_agent=public.xsssec(request.headers.get('User-Agent')), + panel_version=public.version(), + os_version=public.get_os_version()) + + result = public.readFile( + public.get_panel_path() + + '/BTPanel/templates/default/panel_error.html').format( + error_title=error_info.split("\n")[-1], + request_info=request_info, + error_msg=error_info) + return Resp(result, 500) diff --git a/BTPanel/routes/v1.py b/BTPanel/routes/v1.py new file mode 100644 index 00000000..fb7eafc5 --- /dev/null +++ b/BTPanel/routes/v1.py @@ -0,0 +1,2311 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 V1路由 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +from BTPanel.app import * + +@app.route('/', methods=method_all) +def home(): + # 面板首页 + comReturn = comm.local() + if comReturn: return comReturn + data = {} + data[public.to_string([112, + 100])], data['pro_end'], data['ltd_end'] = get_pd() + data['siteCount'] = public.M('sites').count() + data['ftpCount'] = public.M('ftps').count() + data['databaseCount'] = public.M('databases').count() + data['lan'] = public.GetLan('index') + data['js_random'] = get_js_random() + return render_template('index.html', data=data) + + +@app.route('/xterm', methods=method_all) +def xterm(): + # 宝塔终端管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0]: + import system + data = system.system().GetConcifInfo() + return render_template('xterm.html', data=data) + import ssh_terminal + ssh_host_admin = ssh_terminal.ssh_host_admin() + defs = ('get_host_list', 'get_host_find', 'modify_host', 'create_host', + 'remove_host', 'set_sort', 'get_command_list', 'create_command', + 'get_command_find', 'modify_command', 'remove_command') + return publicObject(ssh_host_admin, defs, None) + + +@app.route('/modify_password', methods=method_get) +def modify_password(): + comReturn = comm.local() + if comReturn: return comReturn + # if not session.get('password_expire',False): return redirect ('/',302) + data = {} + g.title = public.get_msg_gettext( + 'The password has expired, please change it!') + return render_template('modify_password.html', data=data) + + +@app.route('/site', methods=method_all) +def site(pdata=None): + # 网站管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + # data = {} + import system + data = system.system().GetConcifInfo() + data['isSetup'] = True + data['lan'] = public.getLan('site') + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + '/nginx') == False \ + and os.path.exists(public.GetConfigValue('setup_path') + '/apache') == False \ + and os.path.exists('/usr/local/lsws/bin/lswsctrl') == False: + data['isSetup'] = False + return render_template('site.html', data=data) + import panelSite + siteObject = panelSite.panelSite() + + defs = ( + 'get_auto_restart_rph', + 'remove_auto_restart_rph', + 'auto_restart_rph', + 'check_del_data', + 'upload_csv', + 'create_website_multiple', + 'del_redirect_multiple', + 'del_proxy_multiple', + 'delete_dir_auth_multiple', + 'delete_dir_bind_multiple', + 'delete_domain_multiple', + 'set_site_etime_multiple', + 'set_site_php_version_multiple', + 'delete_website_multiple', + 'set_site_status_multiple', + 'get_site_err_log', + 'get_site_domains', + 'GetRedirectFile', + 'SaveRedirectFile', + 'DeleteRedirect', + 'GetRedirectList', + 'CreateRedirect', + 'ModifyRedirect', + "set_error_redirect", + 'set_dir_auth', + 'delete_dir_auth', + 'get_dir_auth', + 'modify_dir_auth_pass', + 'reset_wp_db', + 'export_domains', + 'import_domains', + 'GetSiteLogs', + 'GetSiteDomains', + 'GetSecurity', + 'SetSecurity', + 'ProxyCache', + 'CloseToHttps', + 'HttpToHttps', + 'SetEdate', + 'SetRewriteTel', + 'GetCheckSafe', + 'CheckSafe', + 'GetDefaultSite', + 'SetDefaultSite', + 'CloseTomcat', + 'SetTomcat', + 'apacheAddPort', + 'AddSite', + 'GetPHPVersion', + 'SetPHPVersion', + 'DeleteSite', + 'AddDomain', + 'DelDomain', + 'GetDirBinding', + 'AddDirBinding', + 'GetDirRewrite', + 'DelDirBinding', + 'get_site_types', + 'add_site_type', + 'remove_site_type', + 'modify_site_type_name', + 'set_site_type', + 'UpdateRulelist', + 'SetSiteRunPath', + 'GetSiteRunPath', + 'SetPath', + 'SetIndex', + 'GetIndex', + 'GetDirUserINI', + 'SetDirUserINI', + 'GetRewriteList', + 'SetSSL', + 'SetSSLConf', + 'CreateLet', + 'CloseSSLConf', + 'GetSSL', + 'SiteStart', + 'SiteStop', + 'Set301Status', + 'Get301Status', + 'CloseLimitNet', + 'SetLimitNet', + 'GetLimitNet', + 'RemoveProxy', + 'GetProxyList', + 'GetProxyDetals', + 'CreateProxy', + 'ModifyProxy', + 'GetProxyFile', + 'SaveProxyFile', + 'ToBackup', + 'DelBackup', + 'GetSitePHPVersion', + 'logsOpen', + 'GetLogsStatus', + 'CloseHasPwd', + 'SetHasPwd', + 'GetHasPwd', + 'GetDnsApi', + 'SetDnsApi', + 'reset_wp_password', + 'is_update', + 'purge_all_cache', + 'set_fastcgi_cache', + 'update_wp', + 'get_wp_username', + 'get_language', + 'deploy_wp', + # 网站管理新增 + 'test_domains_api', + 'site_rname', + ) + return publicObject(siteObject, defs, None, pdata) + + +@app.route('/ftp', methods=method_all) +def ftp(pdata=None): + # FTP管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + FtpPort() + import system + data = system.system().GetConcifInfo() + data['isSetup'] = True + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + + '/pure-ftpd') == False: + data['isSetup'] = False + data['lan'] = public.GetLan('ftp') + return render_template('ftp.html', data=data) + import ftp + ftpObject = ftp.ftp() + defs = ('AddUser', 'DeleteUser', 'SetUserPassword', 'SetStatus', 'setPort', + 'set_user_home', 'get_login_logs', 'get_action_logs', + 'set_ftp_logs') + return publicObject(ftpObject, defs, None, pdata) + + +@app.route('/database', methods=method_all) +def database(pdata=None): + # 数据库管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import ajax + from panelPlugin import panelPlugin + session['phpmyadminDir'] = False + if panelPlugin().get_phpmyadmin_stat(): + pmd = get_phpmyadmin_dir() + if pmd: + session['phpmyadminDir'] = 'http://' + public.GetHost( + ) + ':' + pmd[1] + '/' + pmd[0] + ajax.ajax().set_phpmyadmin_session() + import system + data = system.system().GetConcifInfo() + data['isSetup'] = os.path.exists( + public.GetConfigValue('setup_path') + '/mysql/bin') + data['mysql_root'] = public.M('config').where( + 'id=?', (1, )).getField('mysql_root') + data['lan'] = public.GetLan('database') + data['js_random'] = get_js_random() + return render_template('database.html', data=data) + import database + databaseObject = database.database() + defs = ('GetdataInfo', 'check_del_data', 'get_database_size', 'GetInfo', + 'ReTable', 'OpTable', 'AlTable', 'GetSlowLogs', 'GetRunStatus', + 'SetDbConf', 'GetDbStatus', 'BinLog', 'GetErrorLog', + 'GetMySQLInfo', 'SetDataDir', 'SetMySQLPort', 'AddCloudDatabase', + 'AddDatabase', 'DeleteDatabase', 'SetupPassword', + 'ResDatabasePassword', 'ToBackup', 'DelBackup', 'AddCloudServer', + 'GetCloudServer', 'RemoveCloudServer', 'ModifyCloudServer', + 'InputSql', 'SyncToDatabases', 'SyncGetDatabases', + 'GetDatabaseAccess', 'SetDatabaseAccess', 'get_mysql_user', + 'check_mysql_ssl_status', 'write_ssl_to_mysql', 'GetdataInfo') + return publicObject(databaseObject, defs, None, pdata) + + +@app.route('/acme', methods=method_all) +def acme(pdata=None): + # Let's 证书管理 + comReturn = comm.local() + if comReturn: return comReturn + import acme_v2 + acme_v2_object = acme_v2.acme_v2() + defs = ('get_orders', 'remove_order', 'get_order_find', 'revoke_order', + 'create_order', 'get_account_info', 'set_account_info', + 'update_zip', 'get_cert_init_api', 'get_auths', 'auth_domain', + 'check_auth_status', 'download_cert', 'apply_cert', 'renew_cert', + 'apply_cert_api', 'apply_dns_auth') + return publicObject(acme_v2_object, defs, None, pdata) + + +# import panelMessage +# message_object = panelMessage.panelMessage() +# @app.route('/message/', methods=method_all) +# def message(action=None): +# # 提示消息管理 +# comReturn = comm.local() +# if comReturn: return comReturn +# import panelMessage +# message_object = panelMessage.panelMessage() +# defs = ( +# 'get_messages', 'get_message_find', 'create_message', 'status_message', 'remove_message', 'get_messages_all') +# return publicObject(message_object, defs, action, None) + + +@app.route('/api', methods=method_all) +def api(pdata=None): + # APP使用的API接口管理 + comReturn = comm.local() + if comReturn: return comReturn + import panelApi + api_object = panelApi.panelApi() + defs = ('get_token', 'check_bind', 'get_bind_status', 'get_apps', + 'add_bind_app', 'remove_bind_app', 'set_token', 'get_tmp_token', + 'get_app_bind_status', 'login_for_app') + return publicObject(api_object, defs, None, pdata) + + +@app.route('/control', methods=method_all) +def control(pdata=None): + # 监控页面 + comReturn = comm.local() + if comReturn: return comReturn + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('control') + data['js_random'] = get_js_random() + return render_template('control.html', data=data) + + +@app.route('/logs', methods=method_all) +def logs(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + data = {} + data['lan'] = public.GetLan('soft') + data['show_workorder'] = not os.path.exists('data/not_workorder.pl') + return render_template('logs.html', data=data) + + +@app.route('/firewall', methods=method_all) +def firewall(pdata=None): + # 安全页面 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + return render_template('firewall.html', data=data) + import firewalls + firewallObject = firewalls.firewalls() + defs = ('GetList', 'AddDropAddress', 'DelDropAddress', 'FirewallReload', + 'SetFirewallStatus', 'AddAcceptPort', 'DelAcceptPort', + 'SetSshStatus', 'SetPing', 'SetSshPort', 'GetSshInfo', + 'SetFirewallStatus') + return publicObject(firewallObject, defs, None, pdata) + + +@app.route('/ssh_security', methods=method_all) +def ssh_security(pdata=None): + # SSH安全 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata and not request.args.get( + 'action', '') in ['download_key']: + data = {} + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + return render_template('firewall.html', data=data) + import ssh_security + firewallObject = ssh_security.ssh_security() + is_csrf = True + if request.args.get('action', '') in ['download_key']: is_csrf = False + defs = ('san_ssh_security', 'set_password', 'set_sshkey', 'stop_key', + 'get_config', 'download_key', 'stop_password', 'get_key', + 'return_ip', 'add_return_ip', 'del_return_ip', 'start_jian', + 'stop_jian', 'get_jian', 'get_logs', 'set_root', 'stop_root', + 'start_auth_method', 'stop_auth_method', 'get_auth_method', + 'check_so_file', 'get_so_file', 'get_pin', 'set_login_send', + 'get_login_send', 'get_msg_push_list', 'clear_login_send') + return publicObject(firewallObject, defs, None, pdata, is_csrf) + + +@app.route('/monitor', methods=method_all) +def panel_monitor(pdata=None): + # 云控统计信息 + comReturn = comm.local() + if comReturn: return comReturn + import monitor + dataObject = monitor.Monitor() + defs = ('get_spider', 'get_exception', 'get_request_count_qps', + 'load_and_up_flow', 'get_request_count_by_hour') + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/san', methods=method_all) +def san_baseline(pdata=None): + # 云控安全扫描 + comReturn = comm.local() + if comReturn: return comReturn + import san_baseline + dataObject = san_baseline.san_baseline() + defs = ('start', 'get_api_log', 'get_resut', 'get_ssh_errorlogin', + 'repair', 'repair_all') + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/password', methods=method_all) +def panel_password(pdata=None): + # 云控密码管理 + comReturn = comm.local() + if comReturn: return comReturn + import password + dataObject = password.password() + defs = ('set_root_password', 'get_mysql_root', 'set_mysql_password', + 'set_panel_password', 'SetPassword', 'SetSshKey', 'StopKey', + 'GetConfig', 'StopPassword', 'GetKey', 'get_databses', + 'rem_mysql_pass', 'set_mysql_access', "get_panel_username") + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/warning', methods=method_all) +def panel_warning(pdata=None): + # 首页安全警告 + comReturn = comm.local() + if comReturn: return comReturn + if public.get_csrf_html_token_key() in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + get = get_input() + ikey = 'warning_list' + import panelWarning + dataObject = panelWarning.panelWarning() + if get.action == 'get_list': + result = cache.get(ikey) + if not result or 'force' in get: + result = json.loads('{"ignore":[],"risk":[],"security":[]}') + try: + defs = ("get_list", ) + result = publicObject(dataObject, defs, None, pdata) + cache.set(ikey, result, 3600) + return result + except: + pass + return result + + defs = ('get_list', 'set_ignore', 'check_find', 'check_cve', + 'set_vuln_ignore', 'get_scan_bar', 'get_tmp_result', + 'kill_get_list') + + if get.action in ['set_ignore', 'check_find', 'set_vuln_ignore']: + cache.delete(ikey) + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/bak', methods=method_all) +def backup_bak(pdata=None): + # 云控备份服务 + comReturn = comm.local() + if comReturn: return comReturn + import backup_bak + dataObject = backup_bak.backup_bak() + defs = ('get_sites', 'get_databases', 'backup_database', 'backup_site', + 'backup_path', 'get_database_progress', 'get_site_progress', + 'down', 'get_down_progress', 'download_path', 'backup_site_all', + 'get_all_site_progress', 'backup_date_all', + 'get_all_date_progress') + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/abnormal', methods=method_all) +def abnormal(pdata=None): + # 云控系统统计 + comReturn = comm.local() + if comReturn: return comReturn + import abnormal + dataObject = abnormal.abnormal() + defs = ('mysql_server', 'mysql_cpu', 'mysql_count', 'php_server', + 'php_conn_max', 'php_cpu', 'CPU', 'Memory', 'disk', + 'not_root_user', 'start') + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/project///', methods=method_all) +def project(mod_name, def_name, stype=None): + comReturn = comm.local() + if comReturn: return comReturn + from panelProjectController import ProjectController + project_obj = ProjectController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + get.stype = stype + if stype == "html": + return project_obj.model(get) + return publicObject(project_obj, defs, None, get) + + +@app.route('/msg//', methods=method_all) +def msgcontroller(mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + from MsgController import MsgController + project_obj = MsgController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + return publicObject(project_obj, defs, None, get) + + +@app.route('/docker', methods=method_all) +def docker(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0]: + import system + data = system.system().GetConcifInfo() + data['js_random'] = get_js_random() + data['lan'] = public.GetLan('files') + return render_template('docker.html', data=data) + + +@app.route('/dbmodel//', methods=method_all) +def dbmodel(mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + from panelDatabaseController import DatabaseController + database_obj = DatabaseController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + + return publicObject(database_obj, defs, None, get) + + +@app.route('/files', methods=method_all) +def files(pdata=None): + # 文件管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not request.args.get( + 'path') and not pdata: + import system + data = system.system().GetConcifInfo() + data['recycle_bin'] = os.path.exists('data/recycle_bin.pl') + data['lan'] = public.GetLan('files') + data['js_random'] = get_js_random() + return render_template('files.html', data=data) + import files + filesObject = files.files() + defs = ('files_search', 'files_replace', 'get_replace_logs', + 'get_images_resize', 'add_files_rsync', 'get_file_attribute', + 'get_file_hash', 'CreateLink', 'get_progress', 'restore_website', + 'fix_permissions', 'get_all_back', 'restore_path_permissions', + 'del_path_premissions', 'get_path_premissions', + 'back_path_permissions', 'upload_file_exists', 'CheckExistsFiles', + 'GetExecLog', 'GetSearch', 'ExecShell', 'GetExecShellMsg', + 'exec_git', 'exec_composer', 'create_download_url', 'UploadFile', + 'GetDir', 'CreateFile', 'CreateDir', 'DeleteDir', 'DeleteFile', + 'get_download_url_list', 'remove_download_url', + 'modify_download_url', 'CopyFile', 'CopyDir', 'MvFile', + 'GetFileBody', 'SaveFileBody', 'Zip', 'UnZip', + 'get_download_url_find', 'set_file_ps', 'SearchFiles', 'upload', + 'read_history', 're_history', 'auto_save_temp', + 'get_auto_save_body', 'get_videos', 'GetFileAccess', + 'SetFileAccess', 'GetDirSize', 'SetBatchData', 'BatchPaste', + 'install_rar', 'get_path_size', 'DownloadFile', 'GetTaskSpeed', + 'CloseLogs', 'InstallSoft', 'UninstallSoft', 'SaveTmpFile', + 'get_composer_version', 'exec_composer', 'update_composer', + 'GetTmpFile', 'del_files_store', 'add_files_store', + 'get_files_store', 'del_files_store_types', + 'add_files_store_types', 'exec_git', 'RemoveTask', 'ActionTask', + 'Re_Recycle_bin', 'Get_Recycle_bin', 'Del_Recycle_bin', + 'Close_Recycle_bin', 'Recycle_bin', 'file_webshell_check', + 'dir_webshell_check', 'files_search', 'files_replace', + 'get_replace_logs') + return publicObject(filesObject, defs, None, pdata) + + +@app.route('/crontab', methods=method_all) +def crontab(pdata=None): + # 计划任务 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('crontab') + data['js_random'] = get_js_random() + return render_template('crontab.html', data=data) + import crontab + crontabObject = crontab.crontab() + defs = ('GetCrontab', 'AddCrontab', 'GetDataList', 'GetLogs', 'DelLogs', + 'DelCrontab', 'StartTask', 'set_cron_status', 'get_crond_find', + 'modify_crond', 'get_backup_list') + return publicObject(crontabObject, defs, None, pdata) + + +@app.route('/soft', methods=method_all) +def soft(pdata=None): + # 软件商店页面 + comReturn = comm.local() + if comReturn: return comReturn + import system + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('soft') + data['js_random'] = get_js_random() + return render_template('soft.html', data=data) + + +@app.route('/config', methods=method_all) +def config(pdata=None): + # 面板设置页面 + comReturn = comm.local() + if comReturn: return comReturn + + if request.method == method_get[0] and not pdata: + import system, wxapp, config + c_obj = config.config() + data = system.system().GetConcifInfo() + data['lan'] = public.GetLan('config') + try: + data['wx'] = wxapp.wxapp().get_user_info(None)['msg'] + except: + data['wx'] = 'INIT_WX_NOT_BIND' + data['api'] = '' + data['ipv6'] = '' + sess_out_path = 'data/session_timeout.pl' + if not os.path.exists(sess_out_path): + public.writeFile(sess_out_path, '86400') + s_time_tmp = public.readFile(sess_out_path) + if not s_time_tmp: s_time_tmp = '0' + data['session_timeout'] = int(s_time_tmp) + if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + if c_obj.get_token(None)['open']: data['api'] = 'checked' + data['basic_auth'] = c_obj.get_basic_auth_stat(None) + data['status_code'] = c_obj.get_not_auth_status() + data['basic_auth']['value'] = public.getMsg('CLOSED') + if data['basic_auth']['open']: + data['basic_auth']['value'] = public.getMsg('OPENED') + data['debug'] = '' + data['js_random'] = get_js_random() + if app.config['DEBUG']: data['debug'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + data['public_key'] = public.get_rsa_public_key().replace("\n", "") + return render_template('config.html', data=data) + import config + defs = ( + 'send_by_telegram', + 'set_empty', + 'set_backup_notification', + 'get_panel_ssl_status', + 'set_file_deny', + 'del_file_deny', + 'get_file_deny', + 'set_improvement', + 'get_httpd_access_log_format_parameter', + 'set_httpd_format_log_to_website', + 'get_httpd_access_log_format', + 'del_httpd_access_log_format', + 'add_httpd_access_log_format', + 'get_nginx_access_log_format_parameter', + 'set_format_log_to_website', + 'get_nginx_access_log_format', + 'del_nginx_access_log_format', + 'set_click_logs', + 'get_node_config', + 'add_nginx_access_log_format', + 'get_ols_private_cache_status', + 'get_ols_value', + 'set_ols_value', + 'set_node_config', + 'get_ols_private_cache', + 'get_ols_static_cache', + 'set_ols_static_cache', + 'switch_ols_private_cache', + 'set_ols_private_cache', + 'set_coll_open', + 'get_qrcode_data', + 'check_two_step', + 'set_two_step_auth', + 'create_user', + 'remove_user', + 'modify_user', + 'get_key', + 'get_php_session_path', + 'set_php_session_path', + 'get_cert_source', + 'get_users', + 'set_request_iptype', + 'set_local', + 'set_debug', + 'get_panel_error_logs', + 'clean_panel_error_logs', + 'get_menu_list', + 'set_hide_menu_list', + 'get_basic_auth_stat', + 'set_basic_auth', + 'get_cli_php_version', + 'get_tmp_token', + 'get_temp_login', + 'set_temp_login', + 'remove_temp_login', + 'clear_temp_login', + 'get_temp_login_logs', + 'set_cli_php_version', + 'DelOldSession', + 'GetSessionCount', + 'SetSessionConf', + 'set_not_auth_status', + 'GetSessionConf', + 'get_ipv6_listen', + 'set_ipv6_status', + 'GetApacheValue', + 'SetApacheValue', + 'install_msg_module', + 'GetNginxValue', + 'SetNginxValue', + 'get_token', + 'set_token', + 'set_admin_path', + 'is_pro', + 'set_msg_config', + 'get_php_config', + 'get_config', + 'SavePanelSSL', + 'GetPanelSSL', + 'GetPHPConf', + 'SetPHPConf', + 'uninstall_msg_module', + 'GetPanelList', + 'AddPanelInfo', + 'SetPanelInfo', + 'DelPanelInfo', + 'ClickPanelInfo', + 'SetPanelSSL', + 'get_msg_configs', + 'SetTemplates', + 'Set502', + 'setPassword', + 'setUsername', + 'setPanel', + 'setPathInfo', + 'setPHPMaxSize', + 'get_msg_fun', + 'getFpmConfig', + 'setFpmConfig', + 'setPHPMaxTime', + 'syncDate', + 'setPHPDisable', + 'SetControl', + 'get_settings2', + 'del_tg_info', + 'set_tg_bot', + 'ClosePanel', + 'AutoUpdatePanel', + 'SetPanelLock', + 'return_mail_list', + 'del_mail_list', + 'add_mail_address', + 'user_mail_send', + 'get_user_mail', + 'set_dingding', + 'get_dingding', + 'get_settings', + 'user_stmp_mail_send', + 'user_dingding_send', + 'get_login_send', + 'set_login_send', + 'clear_login_send', + 'get_login_log', + 'login_ipwhite', + 'set_ssl_verify', + 'get_ssl_verify', + 'get_password_config', + 'set_password_expire', + 'set_password_safe', + 'get_module_template', + # 新增nps评分 + 'write_nps_new', + 'get_nps_new', + "check_nps") + return publicObject(config.config(), defs, None, pdata) + + +@app.route('/ajax', methods=method_all) +def ajax(pdata=None): + # 面板系统服务状态接口 + comReturn = comm.local() + if comReturn: return comReturn + import ajax + ajaxObject = ajax.ajax() + defs = ('get_lines', 'php_info', 'change_phpmyadmin_ssl_port', + 'set_phpmyadmin_ssl', 'get_phpmyadmin_ssl', 'get_pd', + 'check_user_auth', 'to_not_beta', 'get_beta_logs', 'apple_beta', + 'GetApacheStatus', 'GetCloudHtml', 'get_pay_type', + 'get_load_average', 'GetOpeLogs', 'GetFpmLogs', 'GetFpmSlowLogs', + 'SetMemcachedCache', 'GetMemcachedStatus', 'GetRedisStatus', + 'GetWarning', 'SetWarning', 'CheckLogin', 'GetSpeed', 'GetAd', + 'phpSort', 'ToPunycode', 'GetBetaStatus', 'SetBeta', + 'setPHPMyAdmin', 'delClose', 'KillProcess', 'GetPHPInfo', + 'GetQiniuFileList', 'get_process_tops', 'get_process_cpu_high', + 'UninstallLib', 'InstallLib', 'SetQiniuAS', 'GetQiniuAS', + 'GetLibList', 'GetProcessList', 'GetNetWorkList', 'GetNginxStatus', + 'GetPHPStatus', 'GetTaskCount', 'GetSoftList', 'GetNetWorkIo', + 'GetDiskIo', 'GetCpuIo', 'CheckInstalled', 'UpdatePanel', + 'GetInstalled', 'GetPHPConfig', 'SetPHPConfig', 'log_analysis', + 'speed_log', 'get_result', 'get_detailed', 'ignore_version') + + return publicObject(ajaxObject, defs, None, pdata) + + +@app.route('/system', methods=method_all) +def system(pdata=None): + # 面板系统状态接口 + comReturn = comm.local() + if comReturn: return comReturn + import system + sysObject = system.system() + defs = ('get_io_info', 'UpdatePro', 'GetAllInfo', 'GetNetWorkApi', + 'GetLoadAverage', 'ClearSystem', 'GetNetWorkOld', 'GetNetWork', + 'GetDiskInfo', 'GetCpuInfo', 'GetBootTime', 'GetSystemVersion', + 'GetMemInfo', 'GetSystemTotal', 'GetConcifInfo', 'ServiceAdmin', + 'ReWeb', 'RestartServer', 'ReMemory', 'RepPanel') + return publicObject(sysObject, defs, None, pdata) + + +@app.route('/deployment', methods=method_all) +def deployment(pdata=None): + # 一键部署接口 + comReturn = comm.local() + if comReturn: return comReturn + import plugin_deployment + sysObject = plugin_deployment.plugin_deployment() + defs = ('GetList', 'AddPackage', 'DelPackage', 'SetupPackage', 'GetSpeed', + 'GetPackageOther') + return publicObject(sysObject, defs, None, pdata) + + +@app.route('/data', methods=method_all) +@app.route('/panel_data', methods=method_all) +def panel_data(pdata=None): + # 从数据库获取数据接口 + comReturn = comm.local() + if comReturn: return comReturn + import data + dataObject = data.data() + defs = ('setPs', 'getData', 'getFind', 'getKey') + return publicObject(dataObject, defs, None, pdata) + + +@app.route('/ssl', methods=method_all) +def ssl(pdata=None): + # 商业SSL证书申请接口 + comReturn = comm.local() + if comReturn: return comReturn + import panelSSL + toObject = panelSSL.panelSSL() + defs = ( + 'check_url_txt', + 'RemoveCert', + 'renew_lets_ssl', + 'SetCertToSite', + 'GetCertList', + 'SaveCert', + 'GetCert', + 'GetCertName', + 'again_verify', + 'DelToken', + 'GetToken', + 'GetUserInfo', + 'GetOrderList', + 'GetDVSSL', + 'Completed', + 'SyncOrder', + 'download_cert', + 'set_cert', + 'cancel_cert_order', + 'get_order_list', + 'get_order_find', + 'apply_order_pay', + 'get_pay_status', + 'apply_order', + 'get_verify_info', + 'get_verify_result', + 'get_product_list', + 'set_verify_info', + 'GetSSLInfo', + 'downloadCRT', + 'GetSSLProduct', + 'Renew_SSL', + 'Get_Renew_SSL', + # 新增 购买证书对接接口 + 'get_product_list_v2', + 'apply_cert_order_pay', + 'get_cert_admin', + 'apply_order_ca', + 'apply_cert_install_pay', + + # 'pay_test' + ) + get = get_input() + + if get.action == 'download_cert': + from io import BytesIO + import base64 + result = toObject.download_cert(get) + # public.print_log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@1111111111111111 result: {}".format(result)) + # {'success': False, 'res': '[code: 0] no data [file: /www/wwwroot/192.168.1.139/app/Api/Cert/controllers/Cert.php] [line: 955]', 'nonce': 1706498844} + + fp = BytesIO(base64.b64decode(result['res']['data'])) + return send_file(fp, + download_name=result['res']['filename'], + as_attachment=True, + mimetype='application/zip') + result = publicObject(toObject, defs, get.action, get) + return result + + +@app.route('/task', methods=method_all) +def task(pdata=None): + # 后台任务接口 + comReturn = comm.local() + if comReturn: return comReturn + import panelTask + toObject = panelTask.bt_task() + defs = ('get_task_lists', 'remove_task', 'get_task_find', + "get_task_log_by_id") + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route('/plugin', methods=method_all) +def plugin(pdata=None): + # 插件系统接口 + comReturn = comm.local() + if comReturn: return comReturn + import panelPlugin + pluginObject = panelPlugin.panelPlugin() + defs = ('get_usually_plugin', 'check_install_limit', 'set_score', + 'get_score', 'update_zip', 'input_zip', 'export_zip', 'add_index', + 'remove_index', 'sort_index', 'install_plugin', 'uninstall_plugin', + 'get_soft_find', 'get_index_list', 'get_soft_list', + 'get_cloud_list', 'check_deps', 'flush_cache', 'GetCloudWarning', + 'install', 'unInstall', 'getPluginList', 'getPluginInfo', + 'get_make_args', 'add_make_args', 'getPluginStatus', + 'setPluginStatus', 'a', 'getCloudPlugin', 'getConfigHtml', + 'savePluginSort', 'del_make_args', 'set_make_args') + return publicObject(pluginObject, defs, None, pdata) + + +@app.route('/wxapp', methods=method_all) +@app.route('/panel_wxapp', methods=method_all) +def panel_wxapp(pdata=None): + # 微信小程序绑定接口 + comReturn = comm.local() + if comReturn: return comReturn + import wxapp + toObject = wxapp.wxapp() + defs = ('blind', 'get_safe_log', 'blind_result', 'get_user_info', + 'blind_del', 'blind_qrcode') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route('/auth', methods=method_all) +def auth(pdata=None): + # 面板认证接口 + comReturn = comm.local() + if comReturn: return comReturn + import panelAuth + toObject = panelAuth.panelAuth() + defs = ('free_trial', 'renew_product_auth', 'auth_activate', + 'get_product_auth', 'get_stripe_session_id', + 'get_re_order_status_plugin', 'create_plugin_other_order', + 'get_order_stat', 'get_voucher_plugin', + 'create_order_voucher_plugin', 'get_product_discount_by', + 'get_re_order_status', 'create_order_voucher', 'create_order', + 'get_order_status', 'get_voucher', 'flush_pay_status', + 'create_serverid', 'check_serverid', 'get_plugin_list', + 'check_plugin', 'get_buy_code', 'check_pay_status', + 'get_renew_code', 'check_renew_code', 'get_business_plugin', + 'get_ad_list', 'check_plugin_end', 'get_plugin_price', + 'get_plugin_remarks', 'get_paypal_session_id', + 'check_paypal_status') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route('/download', methods=method_get) +def download(): + # 文件下载接口 + comReturn = comm.local() + if comReturn: return comReturn + filename = request.args.get('filename') + if filename.find('|') != -1: + filename = filename.split('|')[0] # 改为获取本地备份 + if not filename: + return public.ReturnJson(False, "INIT_ARGS_ERR"), json_header + # if filename in ['alioss','qiniu','upyun','txcos','ftp','msonedrive','gcloud_storage', 'gdrive', 'aws_s3']: return panel_cloud() + if not os.path.exists(filename): + return public.ReturnJson(False, "FILE_NOT_EXISTS"), json_header + + if request.args.get('play') == 'true': + import panelVideo + start, end = panelVideo.get_range(request) + g.return_message = True + return panelVideo.partial_response(filename, start, end) + else: + mimetype = "application/octet-stream" + extName = filename.split('.')[-1] + if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + public.WriteLog("TYPE_FILE", 'FILE_DOWNLOAD', + (filename, public.GetClientIp())) + g.return_message = True + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + etag=True, + conditional=True, + download_name=os.path.basename(filename), + max_age=0) + + +@app.route('/cloud', methods=method_all) +def panel_cloud(is_csrf=True): + # 从对像存储下载备份文件接口 + comReturn = comm.local() + if comReturn: return comReturn + if is_csrf: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + get = get_input() + _filename = get.filename + plugin_name = "" + if _filename.find('|') != -1: + plugin_name = get.filename.split('|')[1] + else: + plugin_name = get.filename + + if not os.path.exists('plugin/' + plugin_name + '/' + plugin_name + + '_main.py'): + return public.returnJson( + False, 'The specified plugin does not exist!'), json_header + public.package_path_append('plugin/' + plugin_name) + plugin_main = __import__(plugin_name + '_main') + public.mod_reload(plugin_main) + tmp = eval("plugin_main.%s_main()" % plugin_name) + if not hasattr(tmp, 'download_file'): + return public.returnJson( + False, + 'Specified plugin has no file download function!'), json_header + download_url = tmp.download_file(get.name) + if plugin_name == 'ftp': + if download_url.find("ftp") != 0: + download_url = "ftp://" + download_url + else: + if download_url.find('http') != 0: + download_url = 'http://' + download_url + + if "toserver" in get and get.toserver == "true": + download_dir = "/tmp/" + if "download_dir" in get: + download_dir = get.download_dir + local_file = os.path.join(download_dir, get.name) + + input_from_local = False + if "input_from_local" in get: + input_from_local = True if get.input_from_local == "true" else False + + if input_from_local: + if os.path.isfile(local_file): + return { + "status": True, + "msg": + "The file already exists and will be restored locally.", + "task_id": -1, + "local_file": local_file + } + from panelTask import bt_task + task_obj = bt_task() + task_id = task_obj.create_task('Download file', 1, download_url, + local_file) + return { + "status": True, + "msg": "The download task was created successfully", + "local_file": local_file, + "task_id": task_id + } + + return redirect(download_url) + + +@app.route('/btwaf_error', methods=method_get) +def btwaf_error(): + # 图标 + comReturn = comm.local() + if comReturn: return comReturn + get = get_input() + p_path = os.path.join('/www/server/panel/plugin/', "btwaf") + if not os.path.exists(p_path): + if get.name == 'btwaf' and get.fun == 'index': + return render_template('error3.html', data={}) + return render_template('error3.html', data={}) + + +@app.route('/favicon.ico', methods=method_get) +def send_favicon(): + # 图标 + comReturn = comm.local() + if comReturn: return abort(404) + s_file = '/www/server/panel/BTPanel/static/favicon.ico' + if not os.path.exists(s_file): return abort(404) + return send_file(s_file, conditional=True, etag=True) + + +@app.route('/rspamd', defaults={'path': ''}, methods=method_all) +@app.route('/rspamd/', methods=method_all) +def proxy_rspamd_requests(path): + comReturn = comm.local() + if comReturn: return comReturn + param = str(request.url).split('?') + param = "" if len(param) < 2 else param[-1] + import requests + headers = {} + for h in request.headers.keys(): + headers[h] = request.headers[h] + if request.method == "GET": + if re.search(r"\.(js|css)$", path): + return send_file('/usr/share/rspamd/www/rspamd/' + path, + conditional=True, + etag=True) + if path == "/": + return send_file('/usr/share/rspamd/www/rspamd/', + conditional=True, + etag=True) + url = "http://127.0.0.1:11334/rspamd/" + path + "?" + param + for i in [ + 'stat', 'auth', 'neighbours', 'list_extractors', + 'list_transforms', 'graph', 'maps', 'actions', 'symbols', + 'history', 'errors', 'check_selector', 'saveactions', + 'savesymbols', 'getmap' + ]: + if i in path: + url = "http://127.0.0.1:11334/" + path + "?" + param + req = requests.get(url, headers=headers, stream=True) + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) + else: + url = "http://127.0.0.1:11334/" + path + for i in request.form.keys(): + data = '{}='.format(i) + # public.writeFile('/tmp/2',data+"\n","a+") + req = requests.post(url, data=data, headers=headers, stream=True) + + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) + + +@app.route('/tips', methods=method_get) +def tips(): + # 提示页面 + comReturn = comm.local() + if comReturn: return abort(404) + get = get_input() + if len(get.get_items().keys()) > 1: return abort(404) + return render_template('tips.html') + +@app.route('/login', methods=method_all) +@app.route(route_path, methods=method_all) +@app.route(route_path + '/', methods=method_all) +def login(): + # 面板登录接口 + if os.path.exists('install.pl'): return redirect('/install') + global admin_check_auth, admin_path, route_path + is_auth_path = False + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + is_auth_path = True + # 登录输入验证 + if request.method == method_post[0]: + if is_auth_path: + g.auth_error = True + return public.error_not_login(None) + v_list = ['username', 'password', 'code', 'vcode', 'cdn_url'] + for v in v_list: + if v in ['username', 'password']: continue + pv = request.form.get(v, '').strip() + if v == 'cdn_url': + if len(pv) > 32: + return public.return_msg_gettext( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^[\w\.-]+$", pv): + public.return_msg_gettext( + False, 'Wrong parameter format!'), json_header + continue + + if not pv: continue + p_len = 32 + if v == 'code': p_len = 4 + if v == 'vcode': p_len = 6 + if len(pv) != p_len: + if v == 'code': + return public.returnJson( + False, 'Verification code length error!'), json_header + return public.returnJson( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^\w+$", pv): + return public.returnJson( + False, 'Wrong parameter format!'), json_header + + for n in request.form.keys(): + if not n in v_list: + return public.returnJson( + False, + 'There can be no extra parameters in the login parameters' + ), json_header + + get = get_input() + import userlogin + if hasattr(get, 'tmp_token'): + result = userlogin.userlogin().request_tmp(get) + return is_login(result) + # 过滤爬虫 + if public.is_spider(): return abort(404) + if hasattr(get, 'dologin'): + login_path = '/login' + if not 'login' in session: return redirect(login_path) + if os.path.exists(admin_path_file): login_path = route_path + if session['login'] != False: + session['login'] = False + cache.set('dologin', True) + public.write_log_gettext( + 'Logout', 'Client: {}, has manually exited the panel', + (public.GetClientIp() + ":" + + str(request.environ.get('REMOTE_PORT')), )) + if 'tmp_login_expire' in session: + s_file = 'data/session/{}'.format(session['tmp_login_id']) + if os.path.exists(s_file): + os.remove(s_file) + token_key = public.get_csrf_html_token_key() + if token_key in session: + del (session[token_key]) + session.clear() + sess_file = 'data/sess_files/' + public.get_sess_key() + if os.path.exists(sess_file): + try: + os.remove(sess_file) + except: + pass + sess_tmp_file = public.get_full_session_file() + if os.path.exists(sess_tmp_file): os.remove(sess_tmp_file) + g.dologin = True + return redirect(public.get_admin_path()) + + if is_auth_path: + if route_path != request.path and route_path + '/' != request.path: + referer = request.headers.get('Referer', 'err') + referer_tmp = referer.split('/') + referer_path = referer_tmp[-1] + if referer_path == '': + referer_path = referer_tmp[-2] + if route_path != '/' + referer_path: + g.auth_error = True + # return render_template('autherr.html') + return public.error_not_login(None) + + session['admin_auth'] = True + comReturn = common.panelSetup().init() + if comReturn: return comReturn + + if request.method == method_post[0]: + result = userlogin.userlogin().request_post(get) + return is_login(result) + + if request.method == method_get[0]: + result = userlogin.userlogin().request_get(get) + if result: + return result + data = {} + data['lan'] = public.GetLan('login') + data['hosts'] = '[]' + hosts_file = 'plugin/static_cdn/hosts.json' + if os.path.exists(hosts_file): + data['hosts'] = public.get_cdn_hosts() + if type(data['hosts']) == dict: + data['hosts'] = '[]' + else: + data['hosts'] = json.dumps(data['hosts']) + data['app_login'] = os.path.exists('data/app_login.pl') + public.cache_set( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp()), 'check', 360) + + # 生成登录token + last_key = 'last_login_token' + # ----------- + last_time_key = 'last_login_token_time' + s_time = int(time.time()) + if last_key in session and last_time_key in session: + # 10秒内不重复生成token + if s_time - session[last_time_key] > 10: + session[last_key] = public.GetRandomString(32) + session[last_time_key] = s_time + else: + session[last_key] = public.GetRandomString(32) + session[last_time_key] = s_time + + data[last_key] = session[last_key] + data['public_key'] = public.get_rsa_public_key() + return render_template('login.html', data=data) + # ----------- + + # rsa_key = 'public_key' + # session[last_key] = public.GetRandomString(32) + # data[last_key] = session[last_key] + # data[rsa_key] = public.get_rsa_public_key().replace("\n", "") + # return render_template('login.html', data=data) + + +@app.route('/close', methods=method_get) +def close(): + # 面板已关闭页面 + if not os.path.exists('data/close.pl'): return redirect('/') + data = {} + data['lan'] = public.getLan('close') + return render_template('close.html', data=data) + + +@app.route('/get_app_bind_status', methods=method_all) +def get_app_bind_status(pdata=None): + # APP绑定状态查询 + if not public.check_app('app_bind'): return abort(404) + get = get_input() + if len(get.get_items().keys()) > 2: return 'There are meaningless parameters!' + v_list = ['bind_token', 'data'] + for n in get.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + import panelApi + api_object = panelApi.panelApi() + return json.dumps(api_object.get_app_bind_status(get_input())), json_header + + +@app.route('/check_bind', methods=method_all) +def check_bind(pdata=None): + # APP绑定查询 + if not public.check_app('app_bind'): return abort(404) + get = get_input() + if len(get.get_items().keys()) > 4: return 'There are meaningless parameters!' + v_list = ['bind_token', 'client_brand', 'client_model', 'data'] + for n in get.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + import panelApi + api_object = panelApi.panelApi() + return json.dumps(api_object.check_bind(get_input())), json_header + + +@app.route('/code', methods=method_get) +def code(): + if not 'code' in session: return '' + if not session['code']: return '' + # 获取图片验证码 + try: + import vilidate + except: + public.ExecShell("btpip install Pillow -I") + return "Pillow not install!" + vie = vilidate.vieCode() + codeImage = vie.GetCodeImage(80, 4) + if sys.version_info[0] == 2: + try: + from cStringIO import StringIO + except: + from StringIO import StringIO + out = StringIO() + else: + from io import BytesIO + out = BytesIO() + codeImage[0].save(out, "png") + cache.set("codeStr", public.md5("".join(codeImage[1]).lower()), 180) + cache.set("codeOut", 1, 0.1) + out.seek(0) + return send_file(out, mimetype='image/png', max_age=0) + + +@app.route('/down/', methods=method_all) +def down(token=None, fname=None): + # 文件分享对外接口 + try: + if public.M('download_token').count() == 0: return abort(404) + fname = request.args.get('fname') + if fname: + if (len(fname) > 256): return abort(404) + if fname: fname = fname.strip('/') + if not token: return abort(404) + if len(token) > 48: return abort(404) + char_list = [ + '\\', '/', ':', '*', '?', '"', '<', '>', '|', ';', '&', '`' + ] + for char in char_list: + if char in token: return abort(404) + if not request.args.get('play') in ['true', None, '']: + return abort(404) + args = get_input() + v_list = ['fname', 'play', 'file_password', 'data'] + for n in args.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + if not re.match(r"^[\w\.]+$", token): return abort(404) + find = public.M('download_token').where('token=?', (token, )).find() + + if not find: return abort(404) + if time.time() > int(find['expire']): return abort(404) + + if not os.path.exists(find['filename']): return abort(404) + if find['password'] and not token in session: + if 'file_password' in args: + if not re.match(r"^\w+$", args.file_password): + return public.ReturnJson(False, + 'Wrong password!'), json_header + if re.match(r"^\d+$", args.file_password): + args.file_password = str(int(args.file_password)) + args.file_password += ".0" + if args.file_password != str(find['password']): + return public.ReturnJson(False, + 'Wrong password!'), json_header + session[token] = 1 + session['down'] = True + else: + pdata = { + "to_path": "", + "src_path": find['filename'], + "password": True, + "filename": find['filename'].split('/')[-1], + "ps": find['ps'], + "total": find['total'], + "token": find['token'], + "expire": public.format_date(times=find['expire']) + } + session['down'] = True + return render_template('down.html', data=pdata) + + if not find['password']: + session['down'] = True + session[token] = 1 + + if session[token] != 1: + return abort(404) + + filename = find['filename'] + if fname: + filename = os.path.join(filename, fname) + if not public.path_safe_check(fname, False): return abort(404) + if os.path.isdir(filename): + return get_dir_down(filename, token, find) + else: + if os.path.isdir(filename): + return get_dir_down(filename, token, find) + + if request.args.get('play') == 'true': + import panelVideo + start, end = panelVideo.get_range(request) + return panelVideo.partial_response(filename, start, end) + else: + mimetype = "application/octet-stream" + extName = filename.split('.')[-1] + if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + b_name = os.path.basename(filename) + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + download_name=b_name, + max_age=0) + except: + return abort(404) + + +@app.route('/database/mongodb/', methods=method_all) +@app.route('/database/pgsql/', methods=method_all) +@app.route('/database/redis/', methods=method_all) +@app.route('/database/sqlite/', methods=method_all) +@app.route('/database/sqlserver/', methods=method_all) +def databaseModel(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 4: return + comReturn = comm.local() + if comReturn: return comReturn + from panelDatabaseController import DatabaseController + project_obj = DatabaseController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = path_split[2] + get.def_name = def_name + + return publicObject(project_obj, defs, None, get) + + +# 系统安全模型页面 +@app.route('/safe/firewall/', methods=method_all) +@app.route('/safe/freeip/', methods=method_all) +@app.route('/safe/ips/', methods=method_all) +@app.route('/safe/security/', methods=method_all) +@app.route('/safe/ssh/', methods=method_all) +@app.route('/safe/syslog/', methods=method_all) +def safeModel(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 4: return + comReturn = comm.local() + if comReturn: return comReturn + from panelSafeController import SafeController + project_obj = SafeController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = path_split[2] + get.def_name = def_name + # if get.mod_name.startswith("v2_"): + # get.mod_name = 'v2_' + mod_name + # return publicObject(project_obj, defs, None, get) + + return publicObject(project_obj, defs, None, get) + + +# 通用模型路由 +@app.route('///', methods=method_all) +def allModule(index, mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + p_path = public.get_plugin_path() + '/' + index + if os.path.exists(p_path): + return panel_other(index, mod_name, def_name) + + from panelController import Controller + controller_obj = Controller() + defs = ('model', ) + get = get_input() + get.model_index = index + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + return publicObject(controller_obj, defs, None, get) + + +@app.route('/public', methods=method_all) +def panel_public(): + get = get_input() + if len("{}".format(get.get_items())) > 1024 * 32: + return 'ERROR' + + # 获取ping测试 + if 'get_ping' in get: + try: + import panelPing + p = panelPing.Test() + get = p.check(get) + if not get: return 'ERROR' + result = getattr(p, get['act'])(get) + result_type = type(result) + if str(result_type).find('Response') != -1: return result + return public.getJson(result), json_header + except: + return abort(404) + + if public.cache_get( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp())) != 'check': + return abort(404) + global admin_check_auth, admin_path, route_path, admin_path_file + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + return abort(404) + v_list = ['fun', 'name', 'filename', 'data', 'secret_key'] + for n in get.get_items().keys(): + if not n in v_list: + return abort(404) + + get.client_ip = public.GetClientIp() + num_key = get.client_ip + '_wxapp' + if not public.get_error_num(num_key, 10): + return public.return_msg_gettext( + False, + '10 consecutive authentication failures are prohibited for 1 hour') + if not hasattr(get, 'name'): get.name = '' + if not hasattr(get, 'fun'): return abort(404) + if not public.path_safe_check("%s/%s" % (get.name, get.fun)): + return abort(404) + if get.fun in ['login_qrcode', 'is_scan_ok', 'set_login']: + # 检查是否验证过安全入口 + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + return abort(404) + # 验证是否绑定了设备 + if not public.check_app('app'): + return public.return_msg_gettext(False, 'Unbound user') + import wxapp + pluwx = wxapp.wxapp() + checks = pluwx._check(get) + if type(checks) != bool or not checks: + public.set_error_num(num_key) + return public.getJson(checks), json_header + data = public.getJson(eval('pluwx.' + get.fun + '(get)')) + return data, json_header + else: + return abort(404) + + +@app.route('//', methods=method_all) +@app.route('///', methods=method_all) +def panel_other(name=None, fun=None, stype=None): + # 插件接口 + if public.is_error_path(): + return redirect('/error', 302) + if not name: return abort(404) + if not re.match(r"^[\w\-]+$", name): return abort(404) + if fun and not re.match(r"^[\w\-\.]+$", fun): return abort(404) + if name != "mail_sys" or fun != "send_mail_http.json": + comReturn = comm.local() + if comReturn: return comReturn + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + if fun: + if name == 'btwaf' and fun == 'index': + pass + elif name == 'firewall' and fun == 'get_file': + pass + elif fun == 'static': + pass + elif stype == 'html': + pass + else: + if public.get_csrf_cookie_token_key( + ) in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson( + False, + 'CSRF calibration failed, please login again' + ), json_header + args = None + else: + p_path = public.get_plugin_path() + '/' + name + if not os.path.exists(p_path): return abort(404) + args = get_input() + args_list = [ + 'mail_from', 'password', 'mail_to', 'subject', 'content', + 'subtype', 'data' + ] + for k in args.get_items(): + if not k in args_list: return abort(404) + + is_accept = False + if not fun: fun = 'index.html' + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + + if not name: name = 'coll' + if not public.path_safe_check("%s/%s/%s" % (name, fun, stype)): + return abort(404) + if name.find('./') != -1 or not re.match(r"^[\w-]+$", name): + return abort(404) + if not name: + return public.returnJson( + False, 'Please pass in the plug-in name!'), json_header + p_path = public.get_plugin_path() + '/' + name + if not os.path.exists(p_path): + if name == 'btwaf' and fun == 'index': + pdata = {} + import panelPlugin + plu_panel = panelPlugin.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + for p in plugin_list['list']: + if p['name'] in ['btwaf']: + if p['endtime'] != 0 and p['endtime'] < time.time(): + pdata['error_msg'] = 1 + break + return render_template('error3.html', data=pdata) + return abort(404) + + # 是否响插件应静态文件 + if fun == 'static': + if stype.find('./') != -1 or not os.path.exists(p_path + '/static'): + return abort(404) + s_file = p_path + '/static/' + stype + if s_file.find('..') != -1: return abort(404) + if not re.match(r"^[\w\./-]+$", s_file): return abort(404) + if not public.path_safe_check(s_file): return abort(404) + if not os.path.exists(s_file): return abort(404) + return send_file(s_file, conditional=True, etag=True) + + # 准备参数 + if not args: args = get_input() + args.client_ip = public.GetClientIp() + args.fun = fun + + # 初始化插件对象 + try: + is_php = os.path.exists(p_path + '/index.php') + if not is_php: + import panelPlugin + plu_panel = panelPlugin.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + waf = 0 + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + for p in plugin_list['list']: + if p['name'] in ['btwaf']: + if p['endtime'] != 0 and p['endtime'] < time.time(): + waf = -1 + try: + public.package_path_append(p_path) + plugin_main = __import__(name + '_main') + if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list[ + 'pro'] == -1: + return render_template('error3.html', data={}) + except: + if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list[ + 'pro'] == -1: + return render_template('error3.html', data={}) + if os.path.exists("{}/btwaf".format(public.get_plugin_path())): + return render_template('error3.html', data={}) + try: + if sys.version_info[0] == 2: + reload(plugin_main) + else: + from imp import reload + reload(plugin_main) + except: + pass + + + plu = eval('plugin_main.' + name + '_main()') + + # methods = dir(plu) + # # 遍历列表并打印每个方法的名称 + # for method in methods: + # # 排除以双下划线开头和结尾的特殊属性和方法 + # if not method.startswith("__") and not method.endswith("__"): + # public.print_log(method) + + if not hasattr(plu, fun): + + return public.returnJson(False, + 'Plugin does not exist'), json_header + + # 执行插件方法 + if not is_php: + if is_accept: + checks = plu._check(args) + if type(checks) != bool or not checks: + return public.getJson(checks), json_header + data = eval('plu.' + fun + '(args)') + else: + comReturn = comm.local() + if comReturn: return comReturn + import panelPHP + args.s = fun + args.name = name + data = panelPHP.panelPHP(name).exec_php_script(args) + + r_type = type(data) + if r_type in [Response, Resp]: + return data + + # 处理响应 + if stype == 'json': # 响应JSON + return public.getJson(data), json_header + elif stype == 'html': # 使用模板 + t_path_root = p_path + '/templates/' + t_path = t_path_root + fun + '.html' + if not os.path.exists(t_path): + return public.returnJson( + False, + 'The specified template does not exist!'), json_header + t_body = public.readFile(t_path) + + # 处理模板包含 + rep = r'{%\s?include\s"(.+)"\s?%}' + includes = re.findall(rep, t_body) + for i_file in includes: + filename = p_path + '/templates/' + i_file + i_body = 'ERROR: File ' + filename + ' does not exists.' + if os.path.exists(filename): + i_body = public.readFile(filename) + t_body = re.sub(rep.replace('(.+)', i_file), i_body, t_body) + + return render_template_string(t_body, data=data) + else: # 直接响应插件返回值,可以是任意flask支持的响应类型 + r_type = type(data) + if r_type == dict: + if name == 'btwaf' and 'msg' in data: + return render_template('error3.html', + data={"error_msg": data['msg']}) + return public.returnJson( + False, + public.getMsg('Bad return type [{}]').format( + r_type)), json_header + return data + except: + return public.get_error_info() + return public.get_error_object(None, plugin_name=name) + + +@app.route('/hook', methods=method_all) +def panel_hook(): + # webhook接口 + get = get_input() + if not os.path.exists('plugin/webhook'): + return abort(404) + public.package_path_append('plugin/webhook') + import webhook_main + return public.getJson(webhook_main.webhook_main().RunHook(get)) + + +@app.route('/install', methods=method_all) +def install(): + # 初始化面板接口 + if public.is_spider(): return abort(404) + if not os.path.exists('install.pl'): return redirect('/login') + if public.M('config').where("id=?", ('1', )).getField('status') == 1: + if os.path.exists('install.pl'): os.remove('install.pl') + session.clear() + return redirect('/login') + ret_login = os.path.join('/', admin_path) + if admin_path == '/' or admin_path == '/bt': ret_login = '/login' + session['admin_path'] = False + session['login'] = False + if request.method == method_get[0]: + if not os.path.exists('install.pl'): return redirect(ret_login) + data = {} + data['status'] = os.path.exists('install.pl') + data['username'] = public.GetRandomString(8).lower() + return render_template('install.html', data=data) + + elif request.method == method_post[0]: + if not os.path.exists('install.pl'): return redirect(ret_login) + get = get_input() + if not hasattr(get, 'bt_username'): + return public.get_msg_gettext('The user name cannot be empty!') + if not get.bt_username: + return public.get_msg_gettext('The user name cannot be empty!') + if not hasattr(get, 'bt_password1'): + return public.get_msg_gettext('Password can not be blank!') + if not get.bt_password1: + return public.get_msg_gettext('Password can not be blank!') + if get.bt_password1 != get.bt_password2: + return public.get_msg_gettext( + 'The passwords entered twice do not match, please re-enter!') + public.M('users').where("id=?", (1, )).save( + 'username,password', + (get.bt_username, + public.password_salt(public.md5(get.bt_password1.strip()), + uid=1))) + os.remove('install.pl') + public.M('config').where("id=?", ('1', )).setField('status', 1) + data = {} + data['status'] = os.path.exists('install.pl') + data['username'] = get.bt_username + return render_template('install.html', data=data) + + +# --------------------- websocket START -------------------------- # + + +@sockets.route('/workorder_client') +def workorder_client(ws): + comReturn = comm.local() + if comReturn: return comReturn + + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): + return + + import panelWorkorder + toObject = panelWorkorder.panelWorkorder() + get = get_input() + toObject.client(ws, get) + + +@sockets.route('/ws_panel') +def ws_panel(ws): + ''' + @name 面板接口ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + comReturn = comm.local() + if comReturn: return comReturn + + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + while True: + pdata = ws.receive() + if pdata == '{}': break + data = json.loads(pdata) + get = public.to_dict_obj(data) + get._ws = ws + p = threading.Thread(target=ws_panel_thread, args=(get, )) + p.start() + + +def ws_panel_thread(get): + ''' + @name 面板管理ws线程 + @author hwliang<2021-07-24> + @param get 请求参数 + @return void + ''' + + public.print_log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + if not hasattr(get, 'mod_name'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'mod_name'))) + return + if not hasattr(get, 'def_name'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'def_name'))) + return + get.mod_name = get.mod_name.strip() + get.def_name = get.def_name.strip() + check_str = '{}{}'.format(get.mod_name, get.def_name) + if not re.match(r"^\w+$", check_str) or get.mod_name in [ + 'public', 'common', 'db', 'db_mysql', 'downloadFile', 'jobs' + ]: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Unsafe mod_name, def_name parameter content'))) + return + # if not hasattr(get, 'args'): + # get._ws.send(public.getJson(public.return_status_code(1001, 'args'))) + # return + + mod_file = '{}/{}.py'.format(public.get_class_path(), get.mod_name) + + if not os.path.exists(mod_file): + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) + return + _obj = public.get_script_object(mod_file) + if not _obj: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) + return + _cls = getattr(_obj, get.mod_name) + if not _cls: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.mod_name)))) + return + _def = getattr(_cls(), get.def_name) + if not _def: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.def_name)))) + return + result = {'callback': get.ws_callback, 'result': _def(get)} + get._ws.send(public.getJson(result)) + + +@sockets.route('/ws_project') +def ws_project(ws): + ''' + @name 项目管理ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + comReturn = comm.local() + if comReturn: return comReturn + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + from panelProjectController import ProjectController + project_obj = ProjectController() + while True: + pdata = ws.receive() + if pdata in '{}': break + get = public.to_dict_obj(json.loads(pdata)) + get._ws = ws + p = threading.Thread(target=ws_project_thread, args=(project_obj, get)) + p.start() + + +def ws_project_thread(_obj, get): + ''' + @name 项目管理ws线程 + @author hwliang<2021-07-24> + @param _obj 项目管理控制器对像 + @param get 请求参数 + @return void + ''' + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + result = {'callback': get.ws_callback, 'result': _obj.model(get)} + get._ws.send(public.getJson(result)) + + +import subprocess + +sock_pids = {} + + +@sockets.route('/sock_shell') +def sock_shell(ws): + ''' + @name 执行指定命令,实时输出命令执行结果 + @author hwliang<2021-07-19> + @return void + + 示例: + p = new WebSocket('ws://192.168.1.247:8888/sock_shell') + p.send('ping www.bt.cn -c 100') + ''' + comReturn = comm.local() + if comReturn: + ws.send(str(comReturn)) + return + kill_closed() + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + t = None + try: + while True: + cmdstring = ws.receive() + if cmdstring in ['stop', 'error'] or not cmdstring: + break + t = threading.Thread(target=sock_recv, args=(cmdstring, ws)) + t.start() + kill_closed() + except: + kill_closed() + + +def kill_closed(): + ''' + @name 关闭已关闭的连接 + @author hwliang<2021-07-24> + @return void + ''' + global sock_pids + import psutil + pids = psutil.pids() + keys = sock_pids.copy().keys() + for pid in keys: + if hasattr(sock_pids[pid], 'closed'): + is_closed = sock_pids[pid].closed + else: + is_closed = not sock_pids[pid].connected + + logging.debug("PID: {} , sock_stat: {}".format(pid, is_closed)) + if not is_closed: continue + + if pid in pids: + try: + p = psutil.Process(pid) + for cp in p.children(): + cp.kill() + p.kill() + logging.debug("killed: {}".format(pid)) + sock_pids.pop(pid) + except: + pass + else: + sock_pids.pop(pid) + + +def sock_recv(cmdstring, ws): + global sock_pids + try: + p = subprocess.Popen(cmdstring + " 2>&1", + close_fds=True, + shell=True, + bufsize=4096, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + sock_pids[p.pid] = ws + kill_closed() + while p.poll() == None: + send_line = p.stdout.readline().decode() + if not send_line or send_line.find('tail: ') != -1: continue + ws.send(send_line) + ws.send(p.stdout.read().decode()) + except: + kill_closed() + + +@app.route('/close_sock_shell', methods=method_all) +def close_sock_shell(): + ''' + @name 关闭指定命令 + @author hwliang<2021-07-19> + @param cmdstring 完整命令行 + @return dict + 示例: + $.post('/close_sock_shell',{cmdstring:'ping www.bt.cn -c 100'}) + ''' + comReturn = comm.local() + if comReturn: return comReturn + args = get_input() + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + cmdstring = args.cmdstring.strip() + skey = public.md5(cmdstring) + pid = cache.get(skey) + if not pid: + return json.dumps( + public.return_data( + False, [], error_msg='The specified sock has been terminated!') + ), json_header + os.kill(pid, 9) + cache.delete(skey) + return json.dumps(public.return_data(True, + 'Successful operation!')), json_header + + +def check_csrf_websocket(ws, args): + ''' + @name 检查websocket是否被csrf攻击 + @author hwliang<2021-07-24> + @param ws websocket对像 + @return void + ''' + if g.is_aes: return True + if g.api_request: return True + if public.is_debug(): return True + is_success = True + if not 'x-http-token' in args: + is_success = False + + if is_success: + if public.get_csrf_sess_html_token_value() != args['x-http-token']: + is_success = False + + if not is_success: + ws.send('token error') + return False + + return True + + +@sockets.route('/webssh') +def webssh(ws): + # 宝塔终端连接 + comReturn = comm.local() + if comReturn: + ws.send(str(comReturn)) + return + if not ws: return 'False' + get = ws.receive() + if not get: return + get = json.loads(get) + if not check_csrf_websocket(ws, get): + return + + import ssh_terminal + sp = ssh_terminal.ssh_host_admin() + if 'host' in get: + ssh_info = {} + ssh_info['host'] = get['host'].strip() + if 'port' in get: + ssh_info['port'] = int(get['port']) + if 'username' in get: + ssh_info['username'] = get['username'].strip() + if 'password' in get: + ssh_info['password'] = get['password'].strip() + if 'pkey' in get: + ssh_info['pkey'] = get['pkey'].strip() + + if get['host'] in ['127.0.0.1', 'localhost' + ] and 'port' not in ssh_info: + ssh_info = sp.get_ssh_info('127.0.0.1') + if not ssh_info: ssh_info = sp.get_ssh_info('localhost') + if not ssh_info: ssh_info = {"host": "127.0.0.1"} + ssh_info['port'] = public.get_ssh_port() + else: + ssh_info = sp.get_ssh_info('127.0.0.1') + if not ssh_info: ssh_info = sp.get_ssh_info('localhost') + if not ssh_info: ssh_info = {"host": "127.0.0.1"} + ssh_info['port'] = public.get_ssh_port() + + if not ssh_info['host'] in ['127.0.0.1', 'localhost']: + if not 'username' in ssh_info: + ssh_info = sp.get_ssh_info(ssh_info['host']) + if not ssh_info: + ws.send( + 'The specified host information is not found, please add it again!' + ) + return + p = ssh_terminal.ssh_terminal() + p.run(ws, ssh_info) + del (p) + if ws.connected: + ws.close() + return 'False' + + +# --------------------- websocket END -------------------------- # + +@app.route("/daily", methods=method_all) +def daily(): + """面板日报数据""" + + comReturn = comm.local() + if comReturn: return comReturn + + import panelDaily + toObject = panelDaily.panelDaily() + + defs = ("get_app_usage", "get_daily_data", "get_daily_list") + result = publicObject(toObject, defs) + return result + + +@app.route('/phpmyadmin/', methods=method_all) +def pma_proxy(path_full=None): + ''' + @name phpMyAdmin代理 + @author hwliang<2022-01-19> + @return Response + ''' + comReturn = comm.local() + if comReturn: return comReturn + cache_key = 'pmd_port_path' + pmd = cache.get(cache_key) + if not pmd: + pmd = get_phpmyadmin_dir() + if not pmd: + return 'phpMyAdmin is not installed, please go to the [App Store] page to install it!' + pmd = list(pmd) + cache.set(cache_key, pmd, 10) + panel_pool = 'http://' + if request.url_root[:5] == 'https': + panel_pool = 'https://' + import ajax + ssl_info = ajax.ajax().get_phpmyadmin_ssl(None) + if ssl_info['status']: + pmd[1] = ssl_info['port'] + else: + panel_pool = 'http://' + + proxy_url = '{}127.0.0.1:{}/{}/'.format( + panel_pool, pmd[1], pmd[0]) + request.full_path.replace( + '/phpmyadmin/', '') + from panelHttpProxy import HttpProxy + px = HttpProxy() + return px.proxy(proxy_url) + + +@app.route('/p/', methods=method_all) +@app.route('/p//', methods=method_all) +@app.route('/p//', methods=method_all) +def proxy_port(port, full_path=None): + ''' + @name 代理指定端口 + @author hwliang<2022-01-19> + @return Response + ''' + + comReturn = comm.local() + if comReturn: return comReturn + full_path = request.full_path.replace('/p/{}/'.format(port), + '').replace('/p/{}'.format(port), '') + uri = '{}/{}'.format(port, full_path) + uri = uri.replace('//', '/') + proxy_url = 'http://127.0.0.1:{}'.format(uri) + from panelHttpProxy import HttpProxy + px = HttpProxy() + return px.proxy(proxy_url) + + +@app.route('/push', methods=method_all) +def push(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + import panelPush + toObject = panelPush.panelPush() + defs = ('set_push_status', 'get_push_msg_list', 'get_modules_list', + 'install_module', 'uninstall_module', 'get_module_template', + 'set_push_config', 'get_push_config', 'del_push_config', + 'get_module_logs', 'get_module_config', 'get_push_list', + 'get_push_logs') + result = publicObject(toObject, defs, None, pdata) + return result diff --git a/BTPanel/routes/v2.py b/BTPanel/routes/v2.py new file mode 100644 index 00000000..00e8ec55 --- /dev/null +++ b/BTPanel/routes/v2.py @@ -0,0 +1,2397 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 V2路由 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +from BTPanel.app import * + +@app.route('/new', methods=method_get) +@app.route('/new/', methods=method_get) +def index_new(sub_path: str = ''): + # 面板首页 + comReturn = comm.local() + if comReturn: return comReturn + data = {} + data[public.to_string([112, + 100])], data['pro_end'], data['ltd_end'] = get_pd() + data['siteCount'] = public.M('sites').count() + data['ftpCount'] = public.M('ftps').count() + data['databaseCount'] = public.M('databases').count() + data['lan'] = public.GetLan('index') + data['js_random'] = get_js_random() + return render_template('index_new.html', data=data) + +@app.route(route_v2 + '/', methods=method_all) +def home_v2(): + # 面板首页 + comReturn = comm.local() + if comReturn: return comReturn + data = {} + data[public.to_string([112, + 100])], data['pro_end'], data['ltd_end'] = get_pd() + data['siteCount'] = public.M('sites').count() + data['ftpCount'] = public.M('ftps').count() + data['databaseCount'] = public.M('databases').count() + data['lan'] = public.GetLan('index') + data['js_random'] = get_js_random() + return render_template('index.html', data=data) + + +@app.route(route_v2 + '/xterm', methods=method_all) +def xterm_v2(): + # 宝塔终端管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0]: + import system_v2 + data = system_v2.system().GetConcifInfo() + return render_template('xterm.html', data=data) + import ssh_terminal_v2 + ssh_host_admin = ssh_terminal_v2.ssh_host_admin() + defs = ('get_host_list', 'get_host_find', 'modify_host', 'create_host', + 'remove_host', 'set_sort', 'get_command_list', 'create_command', + 'get_command_find', 'modify_command', 'remove_command') + return publicObject(ssh_host_admin, defs, None) + + +@app.route(route_v2 + '/modify_password', methods=method_get) +def modify_password_v2(): + comReturn = comm.local() + if comReturn: return comReturn + # if not session.get('password_expire',False): return redirect('/',302) + data = {} + g.title = public.get_msg_gettext( + 'The password has expired, please change it!') + return render_template('modify_password.html', data=data) + + +@app.route(route_v2 + '/site', methods=method_all) +def site_v2(pdata=None): + # public.print_log("----/v2/site_v2----1") + # 网站管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + # data = {} + import system_v2 + data = system_v2.system().GetConcifInfo() + data['isSetup'] = True + data['lan'] = public.getLan('site') + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + '/nginx') == False \ + and os.path.exists(public.GetConfigValue('setup_path') + '/apache') == False \ + and os.path.exists('/usr/local/lsws/bin/lswsctrl') == False: + data['isSetup'] = False + return render_template('site.html', data=data) + import panel_site_v2 + siteObject = panel_site_v2.panelSite() + defs = ( + 'get_auto_restart_rph', + 'remove_auto_restart_rph', + 'auto_restart_rph', + 'check_del_data', + 'upload_csv', + 'create_website_multiple', + 'del_redirect_multiple', + 'del_proxy_multiple', + 'delete_dir_auth_multiple', + 'delete_dir_bind_multiple', + 'delete_domain_multiple', + 'set_site_etime_multiple', + 'set_site_php_version_multiple', + 'delete_website_multiple', + 'set_site_status_multiple', + 'get_site_err_log', + 'get_site_domains', + 'GetRedirectFile', + 'SaveRedirectFile', + 'DeleteRedirect', + 'GetRedirectList', + 'CreateRedirect', + 'ModifyRedirect', + "set_error_redirect", + 'set_dir_auth', + 'delete_dir_auth', + 'get_dir_auth', + 'modify_dir_auth_pass', + 'reset_wp_db', + 'export_domains', + 'import_domains', + 'GetSiteLogs', + 'GetSiteDomains', + 'GetSecurity', + 'SetSecurity', + 'ProxyCache', + 'CloseToHttps', + 'HttpToHttps', + 'SetEdate', + 'SetRewriteTel', + 'GetCheckSafe', + 'CheckSafe', + 'GetDefaultSite', + 'SetDefaultSite', + 'CloseTomcat', + 'SetTomcat', + 'apacheAddPort', + 'AddSite', + 'GetPHPVersion', + 'SetPHPVersion', + 'DeleteSite', + 'AddDomain', + 'DelDomain', + 'GetDirBinding', + 'AddDirBinding', + 'GetDirRewrite', + 'DelDirBinding', + 'get_site_types', + 'add_site_type', + 'remove_site_type', + 'modify_site_type_name', + 'set_site_type', + 'UpdateRulelist', + 'SetSiteRunPath', + 'GetSiteRunPath', + 'SetPath', + 'SetIndex', + 'GetIndex', + 'GetDirUserINI', + 'SetDirUserINI', + 'GetRewriteList', + 'SetSSL', + 'SetSSLConf', + 'CreateLet', + 'CloseSSLConf', + 'GetSSL', + 'SiteStart', + 'SiteStop', + 'Set301Status', + 'Get301Status', + 'CloseLimitNet', + 'SetLimitNet', + 'GetLimitNet', + 'RemoveProxy', + 'GetProxyList', + 'GetProxyDetals', + 'CreateProxy', + 'ModifyProxy', + 'GetProxyFile', + 'SaveProxyFile', + 'ToBackup', + 'DelBackup', + 'GetSitePHPVersion', + 'logsOpen', + 'GetLogsStatus', + 'CloseHasPwd', + 'SetHasPwd', + 'GetHasPwd', + 'GetDnsApi', + 'SetDnsApi', + 'reset_wp_password', + 'is_update', + 'purge_all_cache', + 'set_fastcgi_cache', + 'update_wp', + 'get_wp_username', + 'get_language', + 'deploy_wp', + # 网站管理新增 + 'test_domains_api', + 'site_rname', + ) + return publicObject(siteObject, defs, None, pdata) + + +@app.route(route_v2 + '/ftp', methods=method_all) +def ftp_v2(pdata=None): + # FTP管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + FtpPort() + import system_v2 + data = system_v2.system().GetConcifInfo() + data['isSetup'] = True + data['js_random'] = get_js_random() + if os.path.exists(public.GetConfigValue('setup_path') + + '/pure-ftpd') == False: + data['isSetup'] = False + data['lan'] = public.GetLan('ftp') + return render_template('ftp.html', data=data) + import ftp_v2 + ftpObject = ftp_v2.ftp() + defs = ('AddUser', 'DeleteUser', 'SetUserPassword', 'SetStatus', 'setPort', + 'set_user_home', 'get_login_logs', 'get_action_logs', + 'set_ftp_logs') + return publicObject(ftpObject, defs, None, pdata) + + +@app.route(route_v2 + '/database', methods=method_all) +def database_v2(pdata=None): + # 数据库管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import ajax_v2 + from panelPlugin import panelPlugin + session['phpmyadminDir'] = False + if panelPlugin().get_phpmyadmin_stat(): + pmd = get_phpmyadmin_dir() + if pmd: + session['phpmyadminDir'] = 'http://' + public.GetHost( + ) + ':' + pmd[1] + '/' + pmd[0] + ajax_v2.ajax().set_phpmyadmin_session() + import system_v2 + data = system_v2.system().GetConcifInfo() + data['isSetup'] = os.path.exists( + public.GetConfigValue('setup_path') + '/mysql/bin') + data['mysql_root'] = public.M('config').where( + 'id=?', (1, )).getField('mysql_root') + data['lan'] = public.GetLan('database') + data['js_random'] = get_js_random() + return render_template('database.html', data=data) + import database_v2 + databaseObject = database_v2.database() + defs = ( + 'GetdataInfo', + 'check_del_data', + 'get_database_size', + 'GetInfo', + 'ReTable', + 'OpTable', + 'AlTable', + 'GetSlowLogs', + 'GetRunStatus', + 'SetDbConf', + 'GetDbStatus', + 'BinLog', + 'GetErrorLog', + 'GetMySQLInfo', + 'SetDataDir', + 'SetMySQLPort', + 'AddCloudDatabase', + 'AddDatabase', + 'DeleteDatabase', + 'SetupPassword', + 'ResDatabasePassword', + 'ToBackup', + 'DelBackup', + 'AddCloudServer', + 'GetCloudServer', + 'RemoveCloudServer', + 'ModifyCloudServer', + 'InputSql', + 'SyncToDatabases', + 'SyncGetDatabases', + 'GetDatabaseAccess', + 'SetDatabaseAccess', + 'get_mysql_user', + 'check_mysql_ssl_status', + 'write_ssl_to_mysql', + 'GetdataInfo', + 'GetBackup', + ) + return publicObject(databaseObject, defs, None, pdata) + + +@app.route(route_v2 + '/acme', methods=method_all) +def acme_v2(pdata=None): + # Let's 证书管理 + comReturn = comm.local() + if comReturn: return comReturn + import acme_v3 + acme_v2_object = acme_v3.acme_v2() + defs = ('get_orders', 'remove_order', 'get_order_find', 'revoke_order', + 'create_order', 'get_account_info', 'set_account_info', + 'update_zip', 'get_cert_init_api', 'get_auths', 'auth_domain', + 'check_auth_status', 'download_cert', 'apply_cert', 'renew_cert', + 'apply_cert_api', 'apply_dns_auth') + return publicObject(acme_v2_object, defs, None, pdata) + + +@app.route(route_v2 + '/api', methods=method_all) +def api_v2(pdata=None): + # APP使用的API接口管理 + comReturn = comm.local() + if comReturn: return comReturn + import panel_api_v2 + api_object = panel_api_v2.panelApi() + defs = ('get_token', 'check_bind', 'get_bind_status', 'get_apps', + 'add_bind_app', 'remove_bind_app', 'set_token', 'get_tmp_token', + 'get_app_bind_status', 'login_for_app') + return publicObject(api_object, defs, None, pdata) + + +@app.route(route_v2 + '/control', methods=method_all) +def control_v2(pdata=None): + # 监控页面 + comReturn = comm.local() + if comReturn: return comReturn + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('control') + data['js_random'] = get_js_random() + return render_template('control.html', data=data) + + +@app.route(route_v2 + '/logs', methods=method_all) +def logs_v2(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + data = {} + data['lan'] = public.GetLan('soft') + data['show_workorder'] = not os.path.exists('data/not_workorder.pl') + return render_template('logs.html', data=data) + + +@app.route(route_v2 + '/firewall', methods=method_all) +def firewall_v2(pdata=None): + # 安全页面 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + return render_template('firewall.html', data=data) + import firewalls_v2 + firewallObject = firewalls_v2.firewalls() + defs = ('GetList', 'AddDropAddress', 'DelDropAddress', 'FirewallReload', + 'SetFirewallStatus', 'AddAcceptPort', 'DelAcceptPort', + 'SetSshStatus', 'SetPing', 'SetSshPort', 'GetSshInfo', + 'SetFirewallStatus') + return publicObject(firewallObject, defs, None, pdata) + + +@app.route(route_v2 + '/ssh_security', methods=method_all) +def ssh_security_v2(pdata=None): + # SSH安全 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata and not request.args.get( + 'action', '') in ['download_key']: + data = {} + data['lan'] = public.GetLan('firewall') + data['js_random'] = get_js_random() + return render_template('firewall.html', data=data) + import ssh_security_v2 + firewallObject = ssh_security_v2.ssh_security() + is_csrf = True + if request.args.get('action', '') in ['download_key']: is_csrf = False + defs = ('san_ssh_security', 'set_password', 'set_sshkey', 'stop_key', + 'get_config', 'download_key', 'stop_password', 'get_key', + 'return_ip', 'add_return_ip', 'del_return_ip', 'start_jian', + 'stop_jian', 'get_jian', 'get_logs', 'set_root', 'stop_root', + 'start_auth_method', 'stop_auth_method', 'get_auth_method', + 'check_so_file', 'get_so_file', 'get_pin', 'set_login_send', + 'get_login_send', 'get_msg_push_list', 'clear_login_send') + return publicObject(firewallObject, defs, None, pdata, is_csrf) + + +@app.route(route_v2 + '/monitor', methods=method_all) +def panel_monitor_v2(pdata=None): + # 云控统计信息 + comReturn = comm.local() + if comReturn: return comReturn + import monitor_v2 + dataObject = monitor_v2.Monitor() + defs = ('get_spider', 'get_exception', 'get_request_count_qps', + 'load_and_up_flow', 'get_request_count_by_hour') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/san', methods=method_all) +def san_baseline_v2(pdata=None): + # 云控安全扫描 + comReturn = comm.local() + if comReturn: return comReturn + import san_baseline_v2 + dataObject = san_baseline_v2.san_baseline() + defs = ('start', 'get_api_log', 'get_resut', 'get_ssh_errorlogin', + 'repair', 'repair_all') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/password', methods=method_all) +def panel_password_v2(pdata=None): + # 云控密码管理 + comReturn = comm.local() + if comReturn: return comReturn + import password_v2 + dataObject = password_v2.password() + defs = ('set_root_password', 'get_mysql_root', 'set_mysql_password', + 'set_panel_password', 'SetPassword', 'SetSshKey', 'StopKey', + 'GetConfig', 'StopPassword', 'GetKey', 'get_databses', + 'rem_mysql_pass', 'set_mysql_access', "get_panel_username") + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/warning', methods=method_all) +def panel_warning_v2(pdata=None): + # 首页安全警告 + comReturn = comm.local() + if comReturn: return comReturn + if public.get_csrf_html_token_key() in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + get = get_input() + ikey = 'warning_list' + import panel_warning_v2 + dataObject = panel_warning_v2.panelWarning() + if get.action == 'get_list': + result = cache.get(ikey) + if not result or 'force' in get: + result = json.loads('{"ignore":[],"risk":[],"security":[]}') + try: + defs = ("get_list", ) + result = publicObject(dataObject, defs, None, pdata) + cache.set(ikey, result, 3600) + return result + except: + pass + return result + + defs = ('get_list', 'set_ignore', 'check_find', 'check_cve', + 'set_vuln_ignore', 'get_scan_bar', 'get_tmp_result', + 'kill_get_list') + + if get.action in ['set_ignore', 'check_find', 'set_vuln_ignore']: + cache.delete(ikey) + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/bak', methods=method_all) +def backup_bak_v2(pdata=None): + # 云控备份服务 + comReturn = comm.local() + if comReturn: return comReturn + import backup_bak_v2 + dataObject = backup_bak_v2.backup_bak() + defs = ('get_sites', 'get_databases', 'backup_database', 'backup_site', + 'backup_path', 'get_database_progress', 'get_site_progress', + 'down', 'get_down_progress', 'download_path', 'backup_site_all', + 'get_all_site_progress', 'backup_date_all', + 'get_all_date_progress') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/abnormal', methods=method_all) +def abnormal_v2(pdata=None): + # 云控系统统计 + comReturn = comm.local() + if comReturn: return comReturn + import abnormal_v2 + dataObject = abnormal_v2.abnormal() + defs = ('mysql_server', 'mysql_cpu', 'mysql_count', 'php_server', + 'php_conn_max', 'php_cpu', 'CPU', 'Memory', 'disk', + 'not_root_user', 'start') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/project/nodejs/', methods=method_all) +@app.route(route_v2 + '/project/nodejs//html', methods=method_all) +@app.route(route_v2 + '/project/docker/', methods=method_all) +@app.route(route_v2 + '/project/docker//html', methods=method_all) +@app.route(route_v2 + '/project/quota/', methods=method_all) +@app.route(route_v2 + '/project/quota//html', methods=method_all) +def project_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + comReturn = comm.local() + if comReturn: return comReturn + from panelProjectControllerV2 import ProjectController + project_obj = ProjectController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + if request.path.endswith('/html'): + return project_obj.model(get) + return publicObject(project_obj, defs, None, get) + + +@app.route(route_v2 + '/msg//', methods=method_all) +def msgcontroller_v2(mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + from MsgControllerV2 import MsgController + project_obj = MsgController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + return publicObject(project_obj, defs, None, get) + + +@app.route(route_v2 + '/docker', methods=method_all) +def docker_v2(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0]: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['js_random'] = get_js_random() + data['lan'] = public.GetLan('files') + return render_template('docker.html', data=data) + + +@app.route(route_v2 + '/dbmodel//', methods=method_all) +def dbmodel_v2(mod_name, def_name): + comReturn = comm.local() + if comReturn: return comReturn + from panelDatabaseControllerV2 import DatabaseController + database_obj = DatabaseController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = mod_name + get.def_name = def_name + + return publicObject(database_obj, defs, None, get) + + +@app.route(route_v2 + '/files', methods=method_all) +def files_v2(pdata=None): + # 文件管理 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not request.args.get( + 'path') and not pdata: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['recycle_bin'] = os.path.exists('data/recycle_bin.pl') + data['lan'] = public.GetLan('files') + data['js_random'] = get_js_random() + return render_template('files.html', data=data) + import files_v2 + filesObject = files_v2.files() + defs = ('files_search', 'files_replace', 'get_replace_logs', + 'get_images_resize', 'add_files_rsync', 'get_file_attribute', + 'get_file_hash', 'CreateLink', 'get_progress', 'restore_website', + 'fix_permissions', 'get_all_back', 'restore_path_permissions', + 'del_path_premissions', 'get_path_premissions', + 'back_path_permissions', 'upload_file_exists', 'CheckExistsFiles', + 'GetExecLog', 'GetSearch', 'ExecShell', 'GetExecShellMsg', + 'exec_git', 'exec_composer', 'create_download_url', 'UploadFile', + 'GetDir', 'CreateFile', 'CreateDir', 'DeleteDir', 'DeleteFile', + 'get_download_url_list', 'remove_download_url', + 'modify_download_url', 'CopyFile', 'CopyDir', 'MvFile', + 'GetFileBody', 'SaveFileBody', 'Zip', 'UnZip', + 'get_download_url_find', 'set_file_ps', 'SearchFiles', 'upload', + 'read_history', 're_history', 'auto_save_temp', + 'get_auto_save_body', 'get_videos', 'GetFileAccess', + 'SetFileAccess', 'GetDirSize', 'SetBatchData', 'BatchPaste', + 'install_rar', 'get_path_size', 'DownloadFile', 'GetTaskSpeed', + 'CloseLogs', 'InstallSoft', 'UninstallSoft', 'SaveTmpFile', + 'get_composer_version', 'exec_composer', 'update_composer', + 'GetTmpFile', 'del_files_store', 'add_files_store', + 'get_files_store', 'del_files_store_types', + 'add_files_store_types', 'exec_git', 'RemoveTask', 'ActionTask', + 'Re_Recycle_bin', 'Get_Recycle_bin', 'Del_Recycle_bin', + 'Close_Recycle_bin', 'Recycle_bin', 'file_webshell_check', + 'dir_webshell_check', 'files_search', 'files_replace', + 'get_replace_logs') + return publicObject(filesObject, defs, None, pdata) + + +@app.route(route_v2 + '/crontab', methods=method_all) +def crontab_v2(pdata=None): + # 计划任务 + comReturn = comm.local() + if comReturn: return comReturn + if request.method == method_get[0] and not pdata: + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('crontab') + data['js_random'] = get_js_random() + return render_template('crontab.html', data=data) + import crontab_v2 + crontabObject = crontab_v2.crontab() + defs = ('GetCrontab', 'AddCrontab', 'GetDataList', 'GetLogs', 'DelLogs', + 'DelCrontab', 'StartTask', 'set_cron_status', 'get_crond_find', + 'modify_crond', 'get_backup_list') + return publicObject(crontabObject, defs, None, pdata) + + +@app.route(route_v2 + '/soft', methods=method_all) +def soft_v2(pdata=None): + # 软件商店页面 + comReturn = comm.local() + if comReturn: return comReturn + import system_v2 + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('soft') + data['js_random'] = get_js_random() + return render_template('soft.html', data=data) + + +@app.route(route_v2 + '/config', methods=method_all) +def config_v2(pdata=None): + # 面板设置页面 + comReturn = comm.local() + if comReturn: return comReturn + + if request.method == method_get[0] and not pdata: + import system_v2, wxapp_v2, config_v2 + c_obj = config_v2.config() + data = system_v2.system().GetConcifInfo() + data['lan'] = public.GetLan('config') + try: + data['wx'] = wxapp_v2.wxapp().get_user_info(None)['msg'] + except: + data['wx'] = 'INIT_WX_NOT_BIND' + data['api'] = '' + data['ipv6'] = '' + sess_out_path = 'data/session_timeout.pl' + if not os.path.exists(sess_out_path): + public.writeFile(sess_out_path, '86400') + s_time_tmp = public.readFile(sess_out_path) + if not s_time_tmp: s_time_tmp = '0' + data['session_timeout'] = int(s_time_tmp) + if c_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + if c_obj.get_token(None)['open']: data['api'] = 'checked' + data['basic_auth'] = c_obj.get_basic_auth_stat(None) + data['status_code'] = c_obj.get_not_auth_status() + data['basic_auth']['value'] = public.getMsg('CLOSED') + if data['basic_auth']['open']: + data['basic_auth']['value'] = public.getMsg('OPENED') + data['debug'] = '' + data['js_random'] = get_js_random() + if app.config['DEBUG']: data['debug'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + data['public_key'] = public.get_rsa_public_key().replace("\n", "") + return render_template('config.html', data=data) + import config_v2 + defs = ( + 'send_by_telegram', + 'set_empty', + 'set_backup_notification', + 'get_panel_ssl_status', + 'set_file_deny', + 'del_file_deny', + 'get_file_deny', + 'set_improvement', + 'get_httpd_access_log_format_parameter', + 'set_httpd_format_log_to_website', + 'get_httpd_access_log_format', + 'del_httpd_access_log_format', + 'add_httpd_access_log_format', + 'get_nginx_access_log_format_parameter', + 'set_format_log_to_website', + 'get_nginx_access_log_format', + 'del_nginx_access_log_format', + 'set_click_logs', + 'get_node_config', + 'add_nginx_access_log_format', + 'get_ols_private_cache_status', + 'get_ols_value', + 'set_ols_value', + 'set_node_config', + 'get_ols_private_cache', + 'get_ols_static_cache', + 'set_ols_static_cache', + 'switch_ols_private_cache', + 'set_ols_private_cache', + 'set_coll_open', + 'get_qrcode_data', + 'check_two_step', + 'set_two_step_auth', + 'create_user', + 'remove_user', + 'modify_user', + 'get_key', + 'get_php_session_path', + 'set_php_session_path', + 'get_cert_source', + 'get_users', + 'set_request_iptype', + 'set_local', + 'set_debug', + 'get_panel_error_logs', + 'clean_panel_error_logs', + 'get_menu_list', + 'set_hide_menu_list', + 'get_basic_auth_stat', + 'set_basic_auth', + 'get_cli_php_version', + 'get_tmp_token', + 'get_temp_login', + 'set_temp_login', + 'remove_temp_login', + 'clear_temp_login', + 'get_temp_login_logs', + 'set_cli_php_version', + 'DelOldSession', + 'GetSessionCount', + 'SetSessionConf', + 'set_not_auth_status', + 'GetSessionConf', + 'get_ipv6_listen', + 'set_ipv6_status', + 'GetApacheValue', + 'SetApacheValue', + 'install_msg_module', + 'GetNginxValue', + 'SetNginxValue', + 'get_token', + 'set_token', + 'set_admin_path', + 'is_pro', + 'set_msg_config', + 'get_php_config', + 'get_config', + 'SavePanelSSL', + 'GetPanelSSL', + 'GetPHPConf', + 'SetPHPConf', + 'uninstall_msg_module', + 'GetPanelList', + 'AddPanelInfo', + 'SetPanelInfo', + 'DelPanelInfo', + 'ClickPanelInfo', + 'SetPanelSSL', + 'get_msg_configs', + 'SetTemplates', + 'Set502', + 'setPassword', + 'setUsername', + 'setPanel', + 'setPathInfo', + 'setPHPMaxSize', + 'get_msg_fun', + 'getFpmConfig', + 'setFpmConfig', + 'setPHPMaxTime', + 'syncDate', + 'setPHPDisable', + 'SetControl', + 'get_settings2', + 'del_tg_info', + 'set_tg_bot', + 'ClosePanel', + 'AutoUpdatePanel', + 'SetPanelLock', + 'return_mail_list', + 'del_mail_list', + 'add_mail_address', + 'user_mail_send', + 'get_user_mail', + 'set_dingding', + 'get_dingding', + 'get_settings', + 'user_stmp_mail_send', + 'user_dingding_send', + 'get_login_send', + 'set_login_send', + 'clear_login_send', + 'get_login_log', + 'login_ipwhite', + 'set_ssl_verify', + 'get_ssl_verify', + 'get_password_config', + 'set_password_expire', + 'set_password_safe', + 'get_module_template', + # 新增nps评分 + 'write_nps_new', + 'get_nps_new', + "check_nps") + return publicObject(config_v2.config(), defs, None, pdata) + + +@app.route(route_v2 + '/ajax', methods=method_all) +def ajax_v2(pdata=None): + # 面板系统服务状态接口 + comReturn = comm.local() + if comReturn: return comReturn + import ajax_v2 + ajaxObject = ajax_v2.ajax() + defs = ('get_lines', 'php_info', 'change_phpmyadmin_ssl_port', + 'set_phpmyadmin_ssl', 'get_phpmyadmin_ssl', 'get_pd', + 'check_user_auth', 'to_not_beta', 'get_beta_logs', 'apple_beta', + 'GetApacheStatus', 'GetCloudHtml', 'get_pay_type', + 'get_load_average', 'GetOpeLogs', 'GetFpmLogs', 'GetFpmSlowLogs', + 'SetMemcachedCache', 'GetMemcachedStatus', 'GetRedisStatus', + 'GetWarning', 'SetWarning', 'CheckLogin', 'GetSpeed', 'GetAd', + 'phpSort', 'ToPunycode', 'GetBetaStatus', 'SetBeta', + 'setPHPMyAdmin', 'delClose', 'KillProcess', 'GetPHPInfo', + 'GetQiniuFileList', 'get_process_tops', 'get_process_cpu_high', + 'UninstallLib', 'InstallLib', 'SetQiniuAS', 'GetQiniuAS', + 'GetLibList', 'GetProcessList', 'GetNetWorkList', 'GetNginxStatus', + 'GetPHPStatus', 'GetTaskCount', 'GetSoftList', 'GetNetWorkIo', + 'GetDiskIo', 'GetCpuIo', 'CheckInstalled', 'UpdatePanel', + 'GetInstalled', 'GetPHPConfig', 'SetPHPConfig', 'log_analysis', + 'speed_log', 'get_result', 'get_detailed', 'ignore_version') + + return publicObject(ajaxObject, defs, None, pdata) + + +@app.route(route_v2 + '/system', methods=method_all) +def system_v2(pdata=None): + # 面板系统状态接口 + comReturn = comm.local() + if comReturn: return comReturn + import system_v2 + sysObject = system_v2.system() + defs = ('get_io_info', 'UpdatePro', 'GetAllInfo', 'GetNetWorkApi', + 'GetLoadAverage', 'ClearSystem', 'GetNetWorkOld', 'GetNetWork', + 'GetDiskInfo', 'GetCpuInfo', 'GetBootTime', 'GetSystemVersion', + 'GetMemInfo', 'GetSystemTotal', 'GetConcifInfo', 'ServiceAdmin', + 'ReWeb', 'RestartServer', 'ReMemory', 'RepPanel') + return publicObject(sysObject, defs, None, pdata) + + +@app.route(route_v2 + '/deployment', methods=method_all) +def deployment_v2(pdata=None): + # 一键部署接口 + comReturn = comm.local() + if comReturn: return comReturn + import plugin_deployment_v2 + sysObject = plugin_deployment_v2.plugin_deployment() + defs = ('GetList', 'AddPackage', 'DelPackage', 'SetupPackage', 'GetSpeed', + 'GetPackageOther') + return publicObject(sysObject, defs, None, pdata) + + +@app.route(route_v2 + '/data', methods=method_all) +@app.route(route_v2 + '/panel_data', methods=method_all) +def panel_data_v2(pdata=None): + # 从数据库获取数据接口 + comReturn = comm.local() + if comReturn: return comReturn + import data_v2 + dataObject = data_v2.data() + defs = ('setPs', 'getData', 'getFind', 'getKey') + return publicObject(dataObject, defs, None, pdata) + + +@app.route(route_v2 + '/ssl', methods=method_all) +def ssl_v2(pdata=None): + # 商业SSL证书申请接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_ssl_v2 + toObject = panel_ssl_v2.panelSSL() + defs = ( + 'check_url_txt', + 'RemoveCert', + 'renew_lets_ssl', + 'SetCertToSite', + 'GetCertList', + 'SaveCert', + 'GetCert', + 'GetCertName', + 'again_verify', + 'DelToken', + 'GetToken', + 'GetUserInfo', + 'GetOrderList', + 'GetDVSSL', + 'Completed', + 'SyncOrder', + 'download_cert', + 'set_cert', + 'cancel_cert_order', + 'get_order_list', + 'get_order_find', + 'apply_order_pay', + 'get_pay_status', + 'apply_order', + 'get_verify_info', + 'get_verify_result', + 'get_product_list', + 'set_verify_info', + 'GetSSLInfo', + 'downloadCRT', + 'GetSSLProduct', + 'Renew_SSL', + 'Get_Renew_SSL', + # 新增 购买证书对接接口 + 'get_product_list_v2', + 'apply_cert_order_pay', + 'get_cert_admin', + 'apply_order_ca', + 'apply_cert_install_pay', + + # 'pay_test' + ) + get = get_input() + + if get.action == 'download_cert': + from io import BytesIO + import base64 + result = toObject.download_cert(get) + # public.print_log("@@@@@@@@@@@@@@@@@@@@@@@@@@@@1111111111111111 result: {}".format(result)) + # {'success': False, 'res': '[code: 0] no data [file: /www/wwwroot/192.168.1.139/app/Api/Cert/controllers/Cert.php] [line: 955]', 'nonce': 1706498844} + + fp = BytesIO(base64.b64decode(result['res']['data'])) + return send_file(fp, + download_name=result['res']['filename'], + as_attachment=True, + mimetype='application/zip') + result = publicObject(toObject, defs, get.action, get) + return result + + +@app.route(route_v2 + '/task', methods=method_all) +def task_v2(pdata=None): + # 后台任务接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_task_v2 + toObject = panel_task_v2.bt_task() + defs = ('get_task_lists', 'remove_task', 'get_task_find', + "get_task_log_by_id") + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route(route_v2 + '/plugin', methods=method_all) +def plugin_v2(pdata=None): + # 插件系统接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_plugin_v2 + pluginObject = panel_plugin_v2.panelPlugin() + defs = ('get_usually_plugin', 'check_install_limit', 'set_score', + 'get_score', 'update_zip', 'input_zip', 'export_zip', 'add_index', + 'remove_index', 'sort_index', 'install_plugin', 'uninstall_plugin', + 'get_soft_find', 'get_index_list', 'get_soft_list', + 'get_cloud_list', 'check_deps', 'flush_cache', 'GetCloudWarning', + 'install', 'unInstall', 'getPluginList', 'getPluginInfo', + 'get_make_args', 'add_make_args', 'getPluginStatus', + 'setPluginStatus', 'a', 'getCloudPlugin', 'getConfigHtml', + 'savePluginSort', 'del_make_args', 'set_make_args') + return publicObject(pluginObject, defs, None, pdata) + + +@app.route(route_v2 + '/wxapp', methods=method_all) +@app.route(route_v2 + '/panel_wxapp', methods=method_all) +def panel_wxapp_v2(pdata=None): + # 微信小程序绑定接口 + comReturn = comm.local() + if comReturn: return comReturn + import wxapp_v2 + toObject = wxapp_v2.wxapp() + defs = ('blind', 'get_safe_log', 'blind_result', 'get_user_info', + 'blind_del', 'blind_qrcode') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route(route_v2 + '/auth', methods=method_all) +def auth_v2(pdata=None): + # 面板认证接口 + comReturn = comm.local() + if comReturn: return comReturn + import panel_auth_v2 + toObject = panel_auth_v2.panelAuth() + defs = ('free_trial', 'renew_product_auth', 'auth_activate', + 'get_product_auth', 'get_stripe_session_id', + 'get_re_order_status_plugin', 'create_plugin_other_order', + 'get_order_stat', 'get_voucher_plugin', + 'create_order_voucher_plugin', 'get_product_discount_by', + 'get_re_order_status', 'create_order_voucher', 'create_order', + 'get_order_status', 'get_voucher', 'flush_pay_status', + 'create_serverid', 'check_serverid', 'get_plugin_list', + 'check_plugin', 'get_buy_code', 'check_pay_status', + 'get_renew_code', 'check_renew_code', 'get_business_plugin', + 'get_ad_list', 'check_plugin_end', 'get_plugin_price', + 'get_plugin_remarks', 'get_paypal_session_id', + 'check_paypal_status') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route(route_v2 + '/download', methods=method_get) +def download_v2(): + # 文件下载接口 + comReturn = comm.local() + if comReturn: return comReturn + filename = request.args.get('filename') + if filename.find('|') != -1: + filename = filename.split('|')[0] # 改为获取本地备份 + if not filename: + return public.ReturnJson(False, "INIT_ARGS_ERR"), json_header + # if filename in ['alioss','qiniu','upyun','txcos','ftp','msonedrive','gcloud_storage', 'gdrive', 'aws_s3']: return panel_cloud() + if not os.path.exists(filename): + return public.ReturnJson(False, "FILE_NOT_EXISTS"), json_header + + if request.args.get('play') == 'true': + import panelVideo + start, end = panelVideo.get_range(request) + g.return_message = True + return panelVideo.partial_response(filename, start, end) + else: + mimetype = "application/octet-stream" + extName = filename.split('.')[-1] + if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + public.WriteLog("TYPE_FILE", 'FILE_DOWNLOAD', + (filename, public.GetClientIp())) + g.return_message = True + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + etag=True, + conditional=True, + download_name=os.path.basename(filename), + max_age=0) + + +@app.route(route_v2 + '/cloud', methods=method_all) +def panel_cloud_v2(is_csrf=True): + # 从对像存储下载备份文件接口 + comReturn = comm.local() + if comReturn: return comReturn + if is_csrf: + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + get = get_input() + _filename = get.filename + plugin_name = "" + if _filename.find('|') != -1: + plugin_name = get.filename.split('|')[1] + else: + plugin_name = get.filename + + if not os.path.exists('plugin/' + plugin_name + '/' + plugin_name + + '_main.py'): + return public.returnJson( + False, 'The specified plugin does not exist!'), json_header + public.package_path_append('plugin/' + plugin_name) + plugin_main = __import__(plugin_name + '_main') + public.mod_reload(plugin_main) + tmp = eval("plugin_main.%s_main()" % plugin_name) + if not hasattr(tmp, 'download_file'): + return public.returnJson( + False, + 'Specified plugin has no file download function!'), json_header + download_url = tmp.download_file(get.name) + if plugin_name == 'ftp': + if download_url.find("ftp") != 0: + download_url = "ftp://" + download_url + else: + if download_url.find('http') != 0: + download_url = 'http://' + download_url + + if "toserver" in get and get.toserver == "true": + download_dir = "/tmp/" + if "download_dir" in get: + download_dir = get.download_dir + local_file = os.path.join(download_dir, get.name) + + input_from_local = False + if "input_from_local" in get: + input_from_local = True if get.input_from_local == "true" else False + + if input_from_local: + if os.path.isfile(local_file): + return { + "status": True, + "msg": + "The file already exists and will be restored locally.", + "task_id": -1, + "local_file": local_file + } + from panel_task_v2 import bt_task + task_obj = bt_task() + task_id = task_obj.create_task('Download file', 1, download_url, + local_file) + return { + "status": True, + "msg": "The download task was created successfully", + "local_file": local_file, + "task_id": task_id + } + + return redirect(download_url) + + +@app.route(route_v2 + '/btwaf_error', methods=method_get) +def btwaf_error_v2(): + # 图标 + comReturn = comm.local() + if comReturn: return comReturn + get = get_input() + p_path = os.path.join('/www/server/panel/plugin/', "btwaf") + if not os.path.exists(p_path): + if get.name == 'btwaf' and get.fun == 'index': + return render_template('error3.html', data={}) + return render_template('error3.html', data={}) + + +@app.route(route_v2 + '/favicon.ico', methods=method_get) +def send_favicon_v2(): + # 图标 + comReturn = comm.local() + if comReturn: return abort(404) + s_file = '/www/server/panel/BTPanel/static/favicon.ico' + if not os.path.exists(s_file): return abort(404) + return send_file(s_file, conditional=True, etag=True) + + +@app.route(route_v2 + '/rspamd', defaults={'path': ''}, methods=method_all) +@app.route(route_v2 + '/rspamd/', methods=method_all) +def proxy_rspamd_requests_v2(path): + comReturn = comm.local() + if comReturn: return comReturn + param = str(request.url).split('?') + param = "" if len(param) < 2 else param[-1] + import requests + headers = {} + for h in request.headers.keys(): + headers[h] = request.headers[h] + if request.method == "GET": + if re.search(r"\.(js|css)$", path): + return send_file('/usr/share/rspamd/www/rspamd/' + path, + conditional=True, + etag=True) + if path == "/": + return send_file('/usr/share/rspamd/www/rspamd/', + conditional=True, + etag=True) + url = "http://127.0.0.1:11334/rspamd/" + path + "?" + param + for i in [ + 'stat', 'auth', 'neighbours', 'list_extractors', + 'list_transforms', 'graph', 'maps', 'actions', 'symbols', + 'history', 'errors', 'check_selector', 'saveactions', + 'savesymbols', 'getmap' + ]: + if i in path: + url = "http://127.0.0.1:11334/" + path + "?" + param + req = requests.get(url, headers=headers, stream=True) + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) + else: + url = "http://127.0.0.1:11334/" + path + for i in request.form.keys(): + data = '{}='.format(i) + # public.writeFile('/tmp/2',data+"\n","a+") + req = requests.post(url, data=data, headers=headers, stream=True) + + return Resp(stream_with_context(req.iter_content()), + content_type=req.headers['content-type']) + + +@app.route(route_v2 + '/tips', methods=method_get) +def tips_v2(): + # 提示页面 + comReturn = comm.local() + if comReturn: return abort(404) + get = get_input() + if len(get.get_items().keys()) > 1: return abort(404) + return render_template('tips.html') + + +# ======================普通路由区end============================# + +# ======================严格排查区域start============================# + + +@app.route(route_v2 + '/login', methods=method_all) +@app.route(route_v2 + route_path, methods=method_all) +@app.route(route_v2 + route_path + '/', methods=method_all) +def login_v2(): + # 面板登录接口 + if os.path.exists('install.pl'): return redirect('/install') + global admin_check_auth, admin_path, route_path + is_auth_path = False + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + is_auth_path = True + # 登录输入验证 + if request.method == method_post[0]: + if is_auth_path: + g.auth_error = True + return public.error_not_login(None) + v_list = ['username', 'password', 'code', 'vcode', 'cdn_url'] + for v in v_list: + if v in ['username', 'password']: continue + pv = request.form.get(v, '').strip() + if v == 'cdn_url': + if len(pv) > 32: + return public.return_msg_gettext( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^[\w\.-]+$", pv): + public.return_msg_gettext( + False, 'Wrong parameter format!'), json_header + continue + + if not pv: continue + p_len = 32 + if v == 'code': p_len = 4 + if v == 'vcode': p_len = 6 + if len(pv) != p_len: + if v == 'code': + return public.returnJson( + False, 'Verification code length error!'), json_header + return public.returnJson( + False, 'Wrong parameter length!'), json_header + if not re.match(r"^\w+$", pv): + return public.returnJson( + False, 'Wrong parameter format!'), json_header + + for n in request.form.keys(): + if not n in v_list: + return public.returnJson( + False, + 'There can be no extra parameters in the login parameters' + ), json_header + + get = get_input() + import user_login_v2 + if hasattr(get, 'tmp_token'): + result = user_login_v2.userlogin().request_tmp(get) + return is_login(result) + # 过滤爬虫 + if public.is_spider(): return abort(404) + if hasattr(get, 'dologin'): + login_path = '/login' + if not 'login' in session: return redirect(login_path) + if os.path.exists(admin_path_file): login_path = route_path + if session['login'] != False: + session['login'] = False + cache.set('dologin', True) + public.write_log_gettext( + 'Logout', 'Client: {}, has manually exited the panel', + (public.GetClientIp() + ":" + + str(request.environ.get('REMOTE_PORT')), )) + if 'tmp_login_expire' in session: + s_file = 'data/session/{}'.format(session['tmp_login_id']) + if os.path.exists(s_file): + os.remove(s_file) + token_key = public.get_csrf_html_token_key() + if token_key in session: + del (session[token_key]) + session.clear() + sess_file = 'data/sess_files/' + public.get_sess_key() + if os.path.exists(sess_file): + try: + os.remove(sess_file) + except: + pass + sess_tmp_file = public.get_full_session_file() + if os.path.exists(sess_tmp_file): os.remove(sess_tmp_file) + g.dologin = True + return redirect(public.get_admin_path()) + + if is_auth_path: + if route_path != request.path and route_path + '/' != request.path: + referer = request.headers.get('Referer', 'err') + referer_tmp = referer.split('/') + referer_path = referer_tmp[-1] + if referer_path == '': + referer_path = referer_tmp[-2] + if route_path != '/' + referer_path: + g.auth_error = True + # return render_template('autherr.html') + return public.error_not_login(None) + + session['admin_auth'] = True + comReturn = common.panelSetup().init() + if comReturn: return comReturn + + if request.method == method_post[0]: + result = userlogin.userlogin().request_post(get) + return is_login(result) + + if request.method == method_get[0]: + result = userlogin.userlogin().request_get(get) + if result: + return result + data = {} + data['lan'] = public.GetLan('login') + data['hosts'] = '[]' + hosts_file = 'plugin/static_cdn/hosts.json' + if os.path.exists(hosts_file): + data['hosts'] = public.get_cdn_hosts() + if type(data['hosts']) == dict: + data['hosts'] = '[]' + else: + data['hosts'] = json.dumps(data['hosts']) + data['app_login'] = os.path.exists('data/app_login.pl') + public.cache_set( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp()), 'check', 360) + + # 生成登录token + last_key = 'last_login_token' + # ----------- + last_time_key = 'last_login_token_time' + s_time = int(time.time()) + if last_key in session and last_time_key in session: + # 10秒内不重复生成token + if s_time - session[last_time_key] > 10: + session[last_key] = public.GetRandomString(32) + session[last_time_key] = s_time + else: + session[last_key] = public.GetRandomString(32) + session[last_time_key] = s_time + + data[last_key] = session[last_key] + data['public_key'] = public.get_rsa_public_key() + return render_template('login.html', data=data) + # ----------- + + # rsa_key = 'public_key' + # session[last_key] = public.GetRandomString(32) + # data[last_key] = session[last_key] + # data[rsa_key] = public.get_rsa_public_key().replace("\n", "") + # return render_template('login.html', data=data) + + +@app.route(route_v2 + '/close', methods=method_get) +def close_v2(): + # 面板已关闭页面 + if not os.path.exists('data/close.pl'): return redirect('/') + data = {} + data['lan'] = public.getLan('close') + return render_template('close.html', data=data) + + +@app.route(route_v2 + '/get_app_bind_status', methods=method_all) +def get_app_bind_status_v2(pdata=None): + # APP绑定状态查询 + if not public.check_app('app_bind'): return abort(404) + get = get_input() + if len(get.get_items().keys()) > 2: return 'There are meaningless parameters!' + v_list = ['bind_token', 'data'] + for n in get.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + import panel_api_v2 + api_object = panel_api_v2.panelApi() + return json.dumps(api_object.get_app_bind_status(get_input())), json_header + + +@app.route(route_v2 + '/check_bind', methods=method_all) +def check_bind_v2(pdata=None): + # APP绑定查询 + if not public.check_app('app_bind'): return abort(404) + get = get_input() + if len(get.get_items().keys()) > 4: return 'There are meaningless parameters!' + v_list = ['bind_token', 'client_brand', 'client_model', 'data'] + for n in get.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + import panel_api_v2 + api_object = panel_api_v2.panelApi() + return json.dumps(api_object.check_bind(get_input())), json_header + + +@app.route(route_v2 + '/code', methods=method_get) +def code_v2(): + if not 'code' in session: return '' + if not session['code']: return '' + # 获取图片验证码 + try: + import vilidate_v2 + except: + public.ExecShell("btpip install Pillow -I") + return "Pillow not install!" + vie = vilidate_v2.vieCode() + codeImage = vie.GetCodeImage(80, 4) + if sys.version_info[0] == 2: + try: + from cStringIO import StringIO + except: + from StringIO import StringIO + out = StringIO() + else: + from io import BytesIO + out = BytesIO() + codeImage[0].save(out, "png") + cache.set("codeStr", public.md5("".join(codeImage[1]).lower()), 180) + cache.set("codeOut", 1, 0.1) + out.seek(0) + return send_file(out, mimetype='image/png', max_age=0) + + +@app.route(route_v2 + '/down/', methods=method_all) +def down_v2(token=None, fname=None): + # 文件分享对外接口 + try: + if public.M('download_token').count() == 0: return abort(404) + fname = request.args.get('fname') + if fname: + if (len(fname) > 256): return abort(404) + if fname: fname = fname.strip('/') + if not token: return abort(404) + if len(token) > 48: return abort(404) + char_list = [ + '\\', '/', ':', '*', '?', '"', '<', '>', '|', ';', '&', '`' + ] + for char in char_list: + if char in token: return abort(404) + if not request.args.get('play') in ['true', None, '']: + return abort(404) + args = get_input() + v_list = ['fname', 'play', 'file_password', 'data'] + for n in args.get_items().keys(): + if not n in v_list: + return public.returnJson( + False, 'There can be no redundant parameters'), json_header + if not re.match(r"^[\w\.]+$", token): return abort(404) + find = public.M('download_token').where('token=?', (token, )).find() + + if not find: return abort(404) + if time.time() > int(find['expire']): return abort(404) + + if not os.path.exists(find['filename']): return abort(404) + if find['password'] and not token in session: + if 'file_password' in args: + if not re.match(r"^\w+$", args.file_password): + return public.ReturnJson(False, + 'Wrong password!'), json_header + if re.match(r"^\d+$", args.file_password): + args.file_password = str(int(args.file_password)) + args.file_password += ".0" + if args.file_password != str(find['password']): + return public.ReturnJson(False, + 'Wrong password!'), json_header + session[token] = 1 + session['down'] = True + else: + pdata = { + "to_path": "", + "src_path": find['filename'], + "password": True, + "filename": find['filename'].split('/')[-1], + "ps": find['ps'], + "total": find['total'], + "token": find['token'], + "expire": public.format_date(times=find['expire']) + } + session['down'] = True + return render_template('down.html', data=pdata) + + if not find['password']: + session['down'] = True + session[token] = 1 + + if session[token] != 1: + return abort(404) + + filename = find['filename'] + if fname: + filename = os.path.join(filename, fname) + if not public.path_safe_check(fname, False): return abort(404) + if os.path.isdir(filename): + return get_dir_down(filename, token, find) + else: + if os.path.isdir(filename): + return get_dir_down(filename, token, find) + + if request.args.get('play') == 'true': + import panel_video_v2 + start, end = panel_video_v2.get_range(request) + return panel_video_v2.partial_response(filename, start, end) + else: + mimetype = "application/octet-stream" + extName = filename.split('.')[-1] + if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + b_name = os.path.basename(filename) + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + download_name=b_name, + max_age=0) + except: + return abort(404) + + +@app.route(route_v2 + '/database/mongodb/', methods=method_all) +@app.route(route_v2 + '/database/pgsql/', methods=method_all) +@app.route(route_v2 + '/database/redis/', methods=method_all) +@app.route(route_v2 + '/database/sqlite/', methods=method_all) +@app.route(route_v2 + '/database/sqlserver/', methods=method_all) +def databaseModel_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + comReturn = comm.local() + if comReturn: return comReturn + from panelDatabaseControllerV2 import DatabaseController + project_obj = DatabaseController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + return publicObject(project_obj, defs, None, get) + + +# 系统安全模型页面 +# @app.route(route_v2+'/safe//', methods=method_all) +@app.route(route_v2 + '/safe/firewall/', methods=method_all) +@app.route(route_v2 + '/safe/freeip/', methods=method_all) +@app.route(route_v2 + '/safe/ips/', methods=method_all) +@app.route(route_v2 + '/safe/security/', methods=method_all) +@app.route(route_v2 + '/safe/ssh/', methods=method_all) +@app.route(route_v2 + '/safe/syslog/', methods=method_all) +def safeModel_v2(mod_name, def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 5: return + comReturn = comm.local() + if comReturn: return comReturn + from panelSafeControllerV2 import SafeController + project_obj = SafeController() + defs = ('model', ) + get = get_input() + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + + return publicObject(project_obj, defs, None, get) + + +# 通用模型路由 +@app.route(route_v2 + '/panel/binlog/', methods=method_all) +@app.route(route_v2 + '/panel/bt_check/', methods=method_all) +@app.route(route_v2 + '/panel/clear/', methods=method_all) +@app.route(route_v2 + '/panel/content/', methods=method_all) +@app.route(route_v2 + '/panel/docker/', methods=method_all) +@app.route(route_v2 + '/panel/go/', methods=method_all) +@app.route(route_v2 + '/panel/java/', methods=method_all) +@app.route(route_v2 + '/panel/nodejs/', methods=method_all) +@app.route(route_v2 + '/panel/other/', methods=method_all) +@app.route(route_v2 + '/panel/php/', methods=method_all) +@app.route(route_v2 + '/panel/python/', methods=method_all) +@app.route(route_v2 + '/panel/quota/', methods=method_all) +@app.route(route_v2 + '/panel/quota/', methods=method_all) +@app.route(route_v2 + '/panel/safe_detect/', methods=method_all) +@app.route(route_v2 + '/panel/scanning/', methods=method_all) +@app.route(route_v2 + '/panel/start_content/', methods=method_all) +@app.route(route_v2 + '/panel/totle_db/', methods=method_all) +@app.route(route_v2 + '/panel/webscanning/', methods=method_all) +@app.route(route_v2 + '/panel/public/', methods=method_all) +@app.route(route_v2 + '/monitor/process_management/', + methods=method_all) +@app.route(route_v2 + '/monitor/soft/', methods=method_all) +@app.route(route_v2 + '/files/down/', methods=method_all) +@app.route(route_v2 + '/files/gz/', methods=method_all) +@app.route(route_v2 + '/files/logs/', methods=method_all) +@app.route(route_v2 + '/files/rar/', methods=method_all) +@app.route(route_v2 + '/files/search/', methods=method_all) +@app.route(route_v2 + '/files/size/', methods=method_all) +@app.route(route_v2 + '/files/upload/', methods=method_all) +@app.route(route_v2 + '/files/zip/', methods=method_all) +@app.route(route_v2 + '/logs/ftp/', methods=method_all) +@app.route(route_v2 + '/logs/panel/', methods=method_all) +@app.route(route_v2 + '/logs/site/', methods=method_all) +def allModule_v2(def_name): + if request.method not in ['GET', 'POST']: return + path_split = request.path.split("/") + if len(path_split) < 4: return + comReturn = comm.local() + if comReturn: return comReturn + p_path = public.get_plugin_path() + '/' + path_split[2] + if os.path.exists(p_path): + return panel_other(index, mod_name, def_name) + + from panelControllerV2 import Controller + controller_obj = Controller() + defs = ('model', ) + get = get_input() + get.model_index = path_split[2] + get.action = 'model' + get.mod_name = path_split[3] + get.def_name = def_name + return publicObject(controller_obj, defs, None, get) + + +@app.route(route_v2 + '/public', methods=method_all) +def panel_public_v2(): + get = get_input() + if len("{}".format(get.get_items())) > 1024 * 32: + return 'ERROR' + + # 获取ping测试 + if 'get_ping' in get: + try: + import panel_ping_v2 + p = panel_ping_v2.Test() + get = p.check(get) + if not get: return 'ERROR' + result = getattr(p, get['act'])(get) + result_type = type(result) + if str(result_type).find('Response') != -1: return result + return public.getJson(result), json_header + except: + return abort(404) + + if public.cache_get( + public.Md5( + uuid.UUID(int=uuid.getnode()).hex[-12:] + + public.GetClientIp())) != 'check': + return abort(404) + global admin_check_auth, admin_path, route_path, admin_path_file + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + return abort(404) + v_list = ['fun', 'name', 'filename', 'data', 'secret_key'] + for n in get.get_items().keys(): + if not n in v_list: + return abort(404) + + get.client_ip = public.GetClientIp() + num_key = get.client_ip + '_wxapp' + if not public.get_error_num(num_key, 10): + return public.return_msg_gettext( + False, + '10 consecutive authentication failures are prohibited for 1 hour') + if not hasattr(get, 'name'): get.name = '' + if not hasattr(get, 'fun'): return abort(404) + if not public.path_safe_check("%s/%s" % (get.name, get.fun)): + return abort(404) + if get.fun in ['login_qrcode', 'is_scan_ok', 'set_login']: + # 检查是否验证过安全入口 + if admin_path != '/bt' and os.path.exists( + admin_path_file) and not 'admin_auth' in session: + return abort(404) + # 验证是否绑定了设备 + if not public.check_app('app'): + return public.return_msg_gettext(False, 'Unbound user') + import wxapp_v2 + pluwx = wxapp_v2.wxapp() + checks = pluwx._check(get) + if type(checks) != bool or not checks: + public.set_error_num(num_key) + return public.getJson(checks), json_header + data = public.getJson(eval('pluwx.' + get.fun + '(get)')) + return data, json_header + else: + return abort(404) + + +@app.route(route_v2 + '//', methods=method_all) +@app.route(route_v2 + '///', methods=method_all) +def panel_other_v2(name=None, fun=None, stype=None): + # 插件接口 + if public.is_error_path(): + return redirect('/error', 302) + if not name: return abort(404) + if not re.match(r"^[\w\-]+$", name): return abort(404) + if fun and not re.match(r"^[\w\-\.]+$", fun): return abort(404) + if name != "mail_sys" or fun != "send_mail_http.json": + comReturn = comm.local() + if comReturn: return comReturn + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + if fun: + if name == 'btwaf' and fun == 'index': + pass + elif name == 'firewall' and fun == 'get_file': + pass + elif fun == 'static': + pass + elif stype == 'html': + pass + else: + if public.get_csrf_cookie_token_key( + ) in session and 'login' in session: + if not check_csrf(): + return public.ReturnJson( + False, + 'CSRF calibration failed, please login again' + ), json_header + args = None + else: + p_path = public.get_plugin_path() + '/' + name + if not os.path.exists(p_path): return abort(404) + args = get_input() + args_list = [ + 'mail_from', 'password', 'mail_to', 'subject', 'content', + 'subtype', 'data' + ] + for k in args.get_items(): + if not k in args_list: return abort(404) + + is_accept = False + if not fun: fun = 'index.html' + if not stype: + tmp = fun.split('.') + fun = tmp[0] + if len(tmp) == 1: tmp.append('') + stype = tmp[1] + + if not name: name = 'coll' + if not public.path_safe_check("%s/%s/%s" % (name, fun, stype)): + return abort(404) + if name.find('./') != -1 or not re.match(r"^[\w-]+$", name): + return abort(404) + if not name: + return public.returnJson( + False, 'Please pass in the plug-in name!'), json_header + p_path = public.get_plugin_path() + '/' + name + if not os.path.exists(p_path): + if name == 'btwaf' and fun == 'index': + pdata = {} + import panel_plugin_v2 + plu_panel = panel_plugin_v2.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + for p in plugin_list['list']: + if p['name'] in ['btwaf']: + if p['endtime'] != 0 and p['endtime'] < time.time(): + pdata['error_msg'] = 1 + break + return render_template('error3.html', data=pdata) + return abort(404) + + # 是否响插件应静态文件 + if fun == 'static': + if stype.find('./') != -1 or not os.path.exists(p_path + '/static'): + return abort(404) + s_file = p_path + '/static/' + stype + if s_file.find('..') != -1: return abort(404) + if not re.match(r"^[\w\./-]+$", s_file): return abort(404) + if not public.path_safe_check(s_file): return abort(404) + if not os.path.exists(s_file): return abort(404) + return send_file(s_file, conditional=True, etag=True) + + # 准备参数 + if not args: args = get_input() + args.client_ip = public.GetClientIp() + args.fun = fun + + # 初始化插件对象 + try: + is_php = os.path.exists(p_path + '/index.php') + if not is_php: + import panel_plugin_v2 + plu_panel = panel_plugin_v2.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + waf = 0 + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + for p in plugin_list['list']: + if p['name'] in ['btwaf']: + if p['endtime'] != 0 and p['endtime'] < time.time(): + waf = -1 + try: + public.package_path_append(p_path) + plugin_main = __import__(name + '_main') + if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list[ + 'pro'] == -1: + return render_template('error3.html', data={}) + except: + if name == 'btwaf' and fun == 'index' and waf == -1 and plugin_list[ + 'pro'] == -1: + return render_template('error3.html', data={}) + if os.path.exists("{}/btwaf".format(public.get_plugin_path())): + return render_template('error3.html', data={}) + try: + if sys.version_info[0] == 2: + reload(plugin_main) + else: + from imp import reload + reload(plugin_main) + except: + pass + # public.print_log("plugin_main22222: {}".format(plugin_main)) + + plu = eval('plugin_main.' + name + '_main()') + + # methods = dir(plu) + # # 遍历列表并打印每个方法的名称 + # for method in methods: + # # 排除以双下划线开头和结尾的特殊属性和方法 + # if not method.startswith("__") and not method.endswith("__"): + # public.print_log(method) + + if not hasattr(plu, fun): + + return public.returnJson(False, + 'Plugin does not exist'), json_header + + # 执行插件方法 + if not is_php: + if is_accept: + checks = plu._check(args) + if type(checks) != bool or not checks: + return public.getJson(checks), json_header + data = eval('plu.' + fun + '(args)') + else: + comReturn = comm.local() + if comReturn: return comReturn + import panel_php_v2 + args.s = fun + args.name = name + data = panel_php_v2.panelPHP(name).exec_php_script(args) + + r_type = type(data) + if r_type in [Response, Resp]: + return data + + # 处理响应 + if stype == 'json': # 响应JSON + return public.getJson(data), json_header + elif stype == 'html': # 使用模板 + t_path_root = p_path + '/templates/' + t_path = t_path_root + fun + '.html' + if not os.path.exists(t_path): + return public.returnJson( + False, + 'The specified template does not exist!'), json_header + t_body = public.readFile(t_path) + + # 处理模板包含 + rep = r'{%\s?include\s"(.+)"\s?%}' + includes = re.findall(rep, t_body) + for i_file in includes: + filename = p_path + '/templates/' + i_file + i_body = 'ERROR: File ' + filename + ' does not exists.' + if os.path.exists(filename): + i_body = public.readFile(filename) + t_body = re.sub(rep.replace('(.+)', i_file), i_body, t_body) + + return render_template_string(t_body, data=data) + else: # 直接响应插件返回值,可以是任意flask支持的响应类型 + r_type = type(data) + if r_type == dict: + if name == 'btwaf' and 'msg' in data: + return render_template('error3.html', + data={"error_msg": data['msg']}) + return public.returnJson( + False, + public.getMsg('Bad return type [{}]').format( + r_type)), json_header + return data + except: + return public.get_error_info() + return public.get_error_object(None, plugin_name=name) + + +@app.route(route_v2 + '/hook', methods=method_all) +def panel_hook_v2(): + # webhook接口 + get = get_input() + if not os.path.exists('plugin/webhook'): + return abort(404) + public.package_path_append('plugin/webhook') + import webhook_main + return public.getJson(webhook_main.webhook_main().RunHook(get)) + + +@app.route(route_v2 + '/install', methods=method_all) +def install_v2(): + # 初始化面板接口 + if public.is_spider(): return abort(404) + if not os.path.exists('install.pl'): return redirect('/login') + if public.M('config').where("id=?", ('1', )).getField('status') == 1: + if os.path.exists('install.pl'): os.remove('install.pl') + session.clear() + return redirect('/login') + ret_login = os.path.join('/', admin_path) + if admin_path == '/' or admin_path == '/bt': ret_login = '/login' + session['admin_path'] = False + session['login'] = False + if request.method == method_get[0]: + if not os.path.exists('install.pl'): return redirect(ret_login) + data = {} + data['status'] = os.path.exists('install.pl') + data['username'] = public.GetRandomString(8).lower() + return render_template('install.html', data=data) + + elif request.method == method_post[0]: + if not os.path.exists('install.pl'): return redirect(ret_login) + get = get_input() + if not hasattr(get, 'bt_username'): + return public.get_msg_gettext('The user name cannot be empty!') + if not get.bt_username: + return public.get_msg_gettext('The user name cannot be empty!') + if not hasattr(get, 'bt_password1'): + return public.get_msg_gettext('Password can not be blank!') + if not get.bt_password1: + return public.get_msg_gettext('Password can not be blank!') + if get.bt_password1 != get.bt_password2: + return public.get_msg_gettext( + 'The passwords entered twice do not match, please re-enter!') + public.M('users').where("id=?", (1, )).save( + 'username,password', + (get.bt_username, + public.password_salt(public.md5(get.bt_password1.strip()), + uid=1))) + os.remove('install.pl') + public.M('config').where("id=?", ('1', )).setField('status', 1) + data = {} + data['status'] = os.path.exists('install.pl') + data['username'] = get.bt_username + return render_template('install.html', data=data) + +# --------------------- websocket START -------------------------- # + + +@sockets.route(route_v2 + '/workorder_client') +def workorder_client_v2(ws): + comReturn = comm.local() + if comReturn: return comReturn + + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): + return + + import panelWorkorder + toObject = panelWorkorder.panelWorkorder() + get = get_input() + toObject.client(ws, get) + + +@sockets.route(route_v2 + '/ws_panel') +def ws_panel_v2(ws): + ''' + @name 面板接口ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + comReturn = comm.local() + if comReturn: return comReturn + + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + while True: + pdata = ws.receive() + if pdata == '{}': break + data = json.loads(pdata) + get = public.to_dict_obj(data) + get._ws = ws + p = threading.Thread(target=ws_panel_thread_v2, args=(get, )) + p.start() + + +def ws_panel_thread_v2(get): + ''' + @name 面板管理ws线程 + @author hwliang<2021-07-24> + @param get 请求参数 + @return void + ''' + + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + if not hasattr(get, 'mod_name'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'mod_name'))) + return + if not hasattr(get, 'def_name'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'def_name'))) + return + get.mod_name = get.mod_name.strip() + get.def_name = get.def_name.strip() + check_str = '{}{}'.format(get.mod_name, get.def_name) + if not re.match(r"^\w+$", check_str) or get.mod_name in [ + 'public', 'common', 'db', 'db_mysql', 'downloadFile', 'jobs' + ]: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Unsafe mod_name, def_name parameter content'))) + return + # if not hasattr(get, 'args'): + # get._ws.send(public.getJson(public.return_status_code(1001, 'args'))) + # return + + mod_file = '{}/{}.py'.format(public.get_class_path(), get.mod_name) + if not os.path.exists(mod_file): + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) + return + _obj = public.get_script_object(mod_file) + if not _obj: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, 'Specified module {} does not exist'.format( + get.mod_name)))) + return + _cls = getattr(_obj, get.mod_name) + if not _cls: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.mod_name)))) + return + _def = getattr(_cls(), get.def_name) + if not _def: + get._ws.send( + public.getJson( + public.return_status_code( + 1000, + 'The {} object was not found in the {} module'.format( + get.mod_name, get.def_name)))) + return + result = {'callback': get.ws_callback, 'result': _def(get)} + get._ws.send(public.getJson(result)) + + +@sockets.route(route_v2 + '/ws_project') +def ws_project_v2(ws): + ''' + @name 项目管理ws入口 + @author hwliang<2021-07-24> + @param ws websocket会话对像 + @return void + ''' + comReturn = comm.local() + if comReturn: return comReturn + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + from panelProjectControllerV2 import ProjectController + project_obj = ProjectController() + while True: + pdata = ws.receive() + if pdata in '{}': break + get = public.to_dict_obj(json.loads(pdata)) + get._ws = ws + p = threading.Thread(target=ws_project_thread_v2, + args=(project_obj, get)) + p.start() + + +def ws_project_thread_v2(_obj, get): + ''' + @name 项目管理ws线程 + @author hwliang<2021-07-24> + @param _obj 项目管理控制器对像 + @param get 请求参数 + @return void + ''' + if not hasattr(get, 'ws_callback'): + get._ws.send( + public.getJson(public.return_status_code(1001, 'ws_callback'))) + return + result = {'callback': get.ws_callback, 'result': _obj.model(get)} + get._ws.send(public.getJson(result)) + + +sock_pids = {} + + +@sockets.route(route_v2 + '/sock_shell') +def sock_shell_v2(ws): + ''' + @name 执行指定命令,实时输出命令执行结果 + @author hwliang<2021-07-19> + @return void + + 示例: + p = new WebSocket('ws://192.168.1.247:8888/sock_shell') + p.send('ping www.bt.cn -c 100') + ''' + comReturn = comm.local() + if comReturn: + ws.send(str(comReturn)) + return + kill_closed_v2() + get = ws.receive() + get = json.loads(get) + if not check_csrf_websocket(ws, get): return + + t = None + try: + while True: + cmdstring = ws.receive() + if cmdstring in ['stop', 'error'] or not cmdstring: + break + t = threading.Thread(target=sock_recv, args=(cmdstring, ws)) + t.start() + kill_closed_v2() + except: + kill_closed_v2() + + +def kill_closed_v2(): + ''' + @name 关闭已关闭的连接 + @author hwliang<2021-07-24> + @return void + ''' + global sock_pids + import psutil + pids = psutil.pids() + keys = sock_pids.copy().keys() + for pid in keys: + if hasattr(sock_pids[pid], 'closed'): + is_closed = sock_pids[pid].closed + else: + is_closed = not sock_pids[pid].connected + + logging.debug("PID: {} , sock_stat: {}".format(pid, is_closed)) + if not is_closed: continue + + if pid in pids: + try: + p = psutil.Process(pid) + for cp in p.children(): + cp.kill() + p.kill() + logging.debug("killed: {}".format(pid)) + sock_pids.pop(pid) + except: + pass + else: + sock_pids.pop(pid) + + +@app.route(route_v2 + '/close_sock_shell', methods=method_all) +def close_sock_shell_v2(): + ''' + @name 关闭指定命令 + @author hwliang<2021-07-19> + @param cmdstring 完整命令行 + @return dict + 示例: + $.post('/close_sock_shell',{cmdstring:'ping www.bt.cn -c 100'}) + ''' + comReturn = comm.local() + if comReturn: return comReturn + args = get_input() + if not check_csrf(): + return public.ReturnJson(False, 'INIT_CSRF_ERR'), json_header + cmdstring = args.cmdstring.strip() + skey = public.md5(cmdstring) + pid = cache.get(skey) + if not pid: + return json.dumps( + public.return_data( + False, [], error_msg='The specified sock has been terminated!') + ), json_header + os.kill(pid, 9) + cache.delete(skey) + return json.dumps(public.return_data(True, + 'Successful operation!')), json_header + + +@sockets.route(route_v2 + '/webssh') +def webssh_v2(ws): + # 宝塔终端连接 + comReturn = comm.local() + if comReturn: + ws.send(str(comReturn)) + return + if not ws: return 'False' + get = ws.receive() + if not get: return + get = json.loads(get) + if not check_csrf_websocket(ws, get): + return + + import ssh_terminal_v2 + sp = ssh_terminal_v2.ssh_host_admin() + if 'host' in get: + ssh_info = {} + ssh_info['host'] = get['host'].strip() + if 'port' in get: + ssh_info['port'] = int(get['port']) + if 'username' in get: + ssh_info['username'] = get['username'].strip() + if 'password' in get: + ssh_info['password'] = get['password'].strip() + if 'pkey' in get: + ssh_info['pkey'] = get['pkey'].strip() + + if get['host'] in ['127.0.0.1', 'localhost' + ] and 'port' not in ssh_info: + ssh_info = sp.get_ssh_info('127.0.0.1') + if not ssh_info: ssh_info = sp.get_ssh_info('localhost') + if not ssh_info: ssh_info = {"host": "127.0.0.1"} + ssh_info['port'] = public.get_ssh_port() + else: + ssh_info = sp.get_ssh_info('127.0.0.1') + if not ssh_info: ssh_info = sp.get_ssh_info('localhost') + if not ssh_info: ssh_info = {"host": "127.0.0.1"} + ssh_info['port'] = public.get_ssh_port() + + if not ssh_info['host'] in ['127.0.0.1', 'localhost']: + if not 'username' in ssh_info: + ssh_info = sp.get_ssh_info(ssh_info['host']) + if not ssh_info: + ws.send( + 'The specified host information is not found, please add it again!' + ) + return + p = ssh_terminal_v2.ssh_terminal() + p.run(ws, ssh_info) + del (p) + if ws.connected: + ws.close() + return 'False' + + +# --------------------- websocket END -------------------------- # + + +@app.route(route_v2 + "/daily", methods=method_all) +def daily_v2(): + """面板日报数据""" + + comReturn = comm.local() + if comReturn: return comReturn + + import panelDaily + toObject = panelDaily.panelDaily() + + defs = ("get_app_usage", "get_daily_data", "get_daily_list") + result = publicObject(toObject, defs) + return result + + +@app.route(route_v2 + '/phpmyadmin/', methods=method_all) +def pma_proxy_v2(path_full=None): + ''' + @name phpMyAdmin代理 + @author hwliang<2022-01-19> + @return Response + ''' + comReturn = comm.local() + if comReturn: return comReturn + cache_key = 'pmd_port_path' + pmd = cache.get(cache_key) + if not pmd: + pmd = get_phpmyadmin_dir() + if not pmd: + return 'phpMyAdmin is not installed, please go to the [App Store] page to install it!' + pmd = list(pmd) + cache.set(cache_key, pmd, 10) + panel_pool = 'http://' + if request.url_root[:5] == 'https': + panel_pool = 'https://' + import ajax + ssl_info = ajax.ajax().get_phpmyadmin_ssl(None) + if ssl_info['status']: + pmd[1] = ssl_info['port'] + else: + panel_pool = 'http://' + + proxy_url = '{}127.0.0.1:{}/{}/'.format( + panel_pool, pmd[1], pmd[0]) + request.full_path.replace( + '/phpmyadmin/', '') + from panel_http_proxy_v2 import HttpProxy + px = HttpProxy() + return px.proxy(proxy_url) + + +@app.route(route_v2 + '/p/', methods=method_all) +@app.route(route_v2 + '/p//', methods=method_all) +@app.route(route_v2 + '/p//', methods=method_all) +def proxy_port_v2(port, full_path=None): + ''' + @name 代理指定端口 + @author hwliang<2022-01-19> + @return Response + ''' + + comReturn = comm.local() + if comReturn: return comReturn + full_path = request.full_path.replace('/p/{}/'.format(port), + '').replace('/p/{}'.format(port), '') + uri = '{}/{}'.format(port, full_path) + uri = uri.replace('//', '/') + proxy_url = 'http://127.0.0.1:{}'.format(uri) + from panel_http_proxy_v2 import HttpProxy + px = HttpProxy() + return px.proxy(proxy_url) + + +@app.route(route_v2 + '/push', methods=method_all) +def push_v2(pdata=None): + comReturn = comm.local() + if comReturn: return comReturn + import panel_push_v2 + toObject = panel_push_v2.panelPush() + defs = ('set_push_status', 'get_push_msg_list', 'get_modules_list', + 'install_module', 'uninstall_module', 'get_module_template', + 'set_push_config', 'get_push_config', 'del_push_config', + 'get_module_logs', 'get_module_config', 'get_push_list', + 'get_push_logs') + result = publicObject(toObject, defs, None, pdata) + return result + + +@app.route('/hello/') +def test_regex_route(username: str): + return 'hi, '+username + + +@app.route('/v2/install_finish', methods=method_post) +def install_finish(): + with open('{}/data/install_finished.mark'.format(public.get_panel_path()), 'w') as fp: + fp.write('True') + return public.return_message(0, 0, 'Successfully') + + +@app.route('/v2/login_wp', methods=method_get) +def login_wp(): + admin_php = '/www/wwwroot/wp-study.aap/wp-admin/includes/admin.php' + + with open(admin_php, 'r') as fp: + admin_php_content = fp.read() + + if admin_php_content.find('''/** Automatic login administrator account */ +require_once ABSPATH . 'wp-admin/includes/auto-login.php';''') < 0: + with open(admin_php, 'a') as fp: + fp.write(''' +/** Automatic login administrator account */ +require_once ABSPATH . 'wp-admin/includes/auto-login.php'; +''') + + mark_file_name = public.GetRandomString(16) + mark_file = '/www/wwwroot/wp-study.aap/wp-admin/includes/{}.mark'.format(mark_file_name) + + with open(mark_file, 'w') as fp: + fp.write('True') + + user_id = 1 + token_key = public.GetRandomString(16) + token = public.GetRandomString(32) + + auto_login_wp = ''' 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; + case 0: + case 1: + t = op; + break; + case 4: + _.label++; + return { value: op[1], done: false }; + case 5: + _.label++; + y = op[1]; + op = [0]; + continue; + case 7: + op = _.ops.pop(); + _.trys.pop(); + continue; + default: + if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) { + _ = 0; + continue; + } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { + _.label = op[1]; + break; + } + if (op[0] === 6 && _.label < t[1]) { + _.label = t[1]; + t = op; + break; + } + if (t && _.label < t[2]) { + _.label = t[2]; + _.ops.push(op); + break; + } + if (t[2]) _.ops.pop(); + _.trys.pop(); + continue; } op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { + } catch (e) { + op = [6, e]; + y = 0; + } finally { + f = t = 0; + } + if (op[0] & 5) throw op[1]; + return { value: op[0] ? op[1] : void 0, done: true }; + } + }; +var __spreadArray = + (this && this.__spreadArray) || + function (to, from, pack) { + if (pack || arguments.length === 2) + for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; + } } - } - return to.concat(ar || Array.prototype.slice.call(from)); -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -define(["require", "exports", "./snabbdom", "./public/public", "./panelConfig", "./safeConfig", "./noticeConfig"], function (require, exports, snabbdom_1, public_1, panelConfig_1, safeConfig_1, noticeConfig_1) { - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); + return to.concat(ar || Array.prototype.slice.call(from)); + }; +var __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +define(['require', 'exports', './snabbdom', './public/public', './panelConfig', './safeConfig', './noticeConfig'], function ( + require, + exports, + snabbdom_1, + public_1, + panelConfig_1, + safeConfig_1, + noticeConfig_1 +) { + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); exports.Config = void 0; public_1 = __importDefault(public_1); panelConfig_1 = __importDefault(panelConfig_1); @@ -73,1099 +176,1164 @@ define(["require", "exports", "./snabbdom", "./public/public", "./panelConfig", var safeConfig = new safeConfig_1.default(); var noticeConfig = new noticeConfig_1.default(); var Config = (function (_super) { - __extends(Config, _super); - function Config() { - var _this = _super.call(this) || this; - _this.Info = {}; - _this.configInfo = {}; - _this.formInfo = {}; - _this.alertListModule = {}; - _this.panelSiteList = []; - _this.taskTypeList = [ - { title: 'Website certificate (SSL) expires', value: 'ssl', model: 'site_push' }, - { title: 'Website expiration', value: 'site_endtime', model: 'site_push' }, - { title: 'Panel password validity period', value: 'panel_pwd_endtime', model: 'site_push' }, - { title: 'Panel login alarm', value: 'panel_login', model: 'site_push' }, - { title: 'SSH login alarm', value: 'ssh_login', model: 'site_push' }, - { title: 'SSH login failure alarm', value: 'ssh_login_error', model: 'site_push' }, - { title: 'Panel security alarm', value: 'panel_safe_push', model: 'site_push' }, - // { title: 'Panel update reminder', value: 'panel_update', model: 'site_push' }, - ]; - _this.disabledOption = ['site_endtime', 'ssh_login', 'ssh_login_error', 'panel_login', 'panel_pwd_endtime', 'panel_safe_push', 'panel_update']; - _this.apiInfo = { - getConfig: ['config/get_config', lan.public.the], - getCheckTwoStep: ['config/check_two_step', lan.public.the], - getPasswordConfig: ['config/get_password_config', 'Getting the password complexity verification status, please wait...'], - getMenuList: ['config/get_menu_list', 'Getting panel menu bar, please wait...'], - getMessageChannel: ['config/get_msg_configs', 'Getting profile, please wait...'], - getLoginAlarm: ['config/get_login_send', 'Getting login information, please wait...'], - setMsgConfigmail: ['config/set_msg_config&name=mail', 'Setting recipient email'], - setPanelConfig: ['config/setPanel', lan.config.config_save], - }; - _this.alertConfigForm = [ - { - label: 'Task type', - group: { - type: 'select', - name: 'type', - width: '250px', - value: 'ssl', - class: 'projectBox', - list: _this.taskTypeList, - disabled: false, - change: function (formData, element, that) { - var config = _this.switchPushType(that.config.form, formData); - that.$again_render_form(config); - }, - }, + __extends(Config, _super); + function Config() { + var _this = _super.call(this) || this; + _this.Info = {}; + _this.configInfo = {}; + _this.formInfo = {}; + _this.alertListModule = {}; + _this.panelSiteList = []; + _this.taskTypeList = [ + { title: 'Website certificate (SSL) expires', value: 'ssl', model: 'site_push' }, + { title: 'Website expiration', value: 'site_endtime', model: 'site_push' }, + { title: 'Panel password validity period', value: 'panel_pwd_endtime', model: 'site_push' }, + { title: 'Panel login alarm', value: 'panel_login', model: 'site_push' }, + { title: 'SSH login alarm', value: 'ssh_login', model: 'site_push' }, + { title: 'SSH login failure alarm', value: 'ssh_login_error', model: 'site_push' }, + { title: 'Panel security alarm', value: 'panel_safe_push', model: 'site_push' }, + // { title: 'Panel update reminder', value: 'panel_update', model: 'site_push' }, + ]; + _this.disabledOption = ['site_endtime', 'ssh_login', 'ssh_login_error', 'panel_login', 'panel_pwd_endtime', 'panel_safe_push', 'panel_update']; + _this.apiInfo = { + getConfig: ['config/get_config', lan.public.the], + getCheckTwoStep: ['config/check_two_step', lan.public.the], + getPasswordConfig: ['config/get_password_config', 'Getting the password complexity verification status, please wait...'], + getMenuList: ['config/get_menu_list', 'Getting panel menu bar, please wait...'], + getMessageChannel: ['config/get_msg_configs', 'Getting profile, please wait...'], + getLoginAlarm: ['config/get_login_send', 'Getting login information, please wait...'], + setMsgConfigmail: ['config/set_msg_config&name=mail', 'Setting recipient email'], + setPanelConfig: ['config/setPanel', lan.config.config_save], + }; + _this.alertConfigForm = [ + { + label: 'Task type', + group: { + type: 'select', + name: 'type', + width: '250px', + value: 'ssl', + class: 'projectBox', + list: _this.taskTypeList, + disabled: false, + change: function (formData, element, that) { + var config = _this.switchPushType(that.config.form, formData); + that.$again_render_form(config); + }, + }, + }, + { + label: 'Website', + group: { + type: 'select', + name: 'site', + width: '250px', + value: '', + list: [], + }, + }, + { + label: 'Remaining days', + group: { + type: 'number', + name: 'cycle', + width: '70px', + unit: 'Day(s)', + value: 1, + }, + }, + { + label: 'Cycle', + hide: true, + group: [ + { + type: 'number', + name: 'where1', + width: '70px', + value: 30, + unit: 'Minute(s)
Frequency
', + input: function (data, b, c, d, e) { + var $input = $(e.currentTarget); + var num = $input.val(); + if (num < 0) { + $input.val(0); + num = 0; + } + var text = ''.concat(num, ' minute').concat(num > 1 ? 's' : ''); + $('.condition_tips').find('.minute').text(text); }, - { - label: 'Website', - group: { - type: 'select', - name: 'site', - width: '250px', - value: '', - list: [], - }, + }, + { + type: 'number', + name: 'count', + width: '50px', + style: { 'vertical-align': 'initial', 'margin-left': '10px' }, + value: 3, + unit: 'Time(s) ', + input: function (data, b, c, d, e) { + var $input = $(e.currentTarget); + var num = $input.val(); + if (num < 0) { + $input.val(0); + num = 0; + } + var text = ''.concat(num, ' time').concat(num > 1 ? 's' : ''); + $('.condition_tips').find('.time').text(text); }, - { - label: 'Remaining days', - group: { - type: 'number', - name: 'cycle', - width: '70px', - unit: 'Day(s)', - value: 1, - }, - }, - { - label: 'Cycle', - hide: true, - group: [ - { - type: 'number', - name: 'where1', - width: '70px', - value: 30, - unit: 'Minute(s)
Frequency
', - input: function (data, b, c, d, e) { - var $input = $(e.currentTarget); - var num = $input.val(); - if (num < 0) { - $input.val(0); - num = 0; - } - var text = "".concat(num, " minute").concat(num > 1 ? 's' : ''); - $('.condition_tips').find('.minute').text(text); - }, - }, - { - type: 'number', - name: 'count', - width: '50px', - style: { 'vertical-align': 'initial', 'margin-left': '10px' }, - value: 3, - unit: 'Time(s) ', - input: function (data, b, c, d, e) { - var $input = $(e.currentTarget); - var num = $input.val(); - if (num < 0) { - $input.val(0); - num = 0; - } - var text = "".concat(num, " time").concat(num > 1 ? 's' : ''); - $('.condition_tips').find('.time').text(text); - }, - }, - { - type: 'div', - dispaly: 'block', - class: 'condition_tips', - style: { 'margin-top': '10px', color: '#666' }, - content: 'Login failed 3 times within 30 minutes', - }, - ], - }, - { - label: 'Interval', - group: { - type: 'number', - name: 'interval', - width: '70px', - value: 600, - unit: 'second(s)
Monitor the trigger condition again after an interval of 600 seconds
', - input: function (data, b, c, d, e) { - var $input = $(e.currentTarget); - var num = $input.val(); - if (num < 0) { - $input.val(0); - num = 0; - } - var text = "".concat(num, " second").concat(num > 1 ? 's' : ''); - $input.next().find('.count').text(text); - }, - }, - }, - { - label: 'Send times', - group: { - type: 'number', - name: 'push_count', - width: '70px', - value: 1, - unit: 'Time(s)
After sending 1 time, no more alarm messages will be sent,
if you want to send multiple times, please fill in more than 2 times.
', - input: function (data, b, c, d, e) { - var $input = $(e.currentTarget); - var num = $input.val(); - if (num < 0) { - $input.val(0); - num = 0; - } - var text = "".concat(num, " time").concat(num > 1 ? 's' : ''); - $input.next().find('.count').text(text); - }, - }, - }, - { - label: 'Alarm mode', - group: [], - }, - { - label: 'Alarm content', - hide: true, - group: { - type: 'help', - style: { 'margin-top': '6px' }, - list: ['panel user change, panel log delete, panel open developer, panel open API'], - }, - }, - { - label: '', - group: { - type: 'button', - name: 'submitForm', - title: 'Add task', - event: function (formData, element, that) { - that.submit(formData); - }, - }, - }, - ]; - _this.init(); - return _this; + }, + { + type: 'div', + dispaly: 'block', + class: 'condition_tips', + style: { 'margin-top': '10px', color: '#666' }, + content: 'Login failed 3 times within 30 minutes', + }, + ], + }, + { + label: 'Interval', + group: { + type: 'number', + name: 'interval', + width: '70px', + value: 600, + unit: 'second(s)
Monitor the trigger condition again after an interval of 600 seconds
', + input: function (data, b, c, d, e) { + var $input = $(e.currentTarget); + var num = $input.val(); + if (num < 0) { + $input.val(0); + num = 0; + } + var text = ''.concat(num, ' second').concat(num > 1 ? 's' : ''); + $input.next().find('.count').text(text); + }, + }, + }, + { + label: 'Send times', + group: { + type: 'number', + name: 'push_count', + width: '70px', + value: 1, + unit: 'Time(s)
After sending 1 time, no more alarm messages will be sent,
if you want to send multiple times, please fill in more than 2 times.
', + input: function (data, b, c, d, e) { + var $input = $(e.currentTarget); + var num = $input.val(); + if (num < 0) { + $input.val(0); + num = 0; + } + var text = ''.concat(num, ' time').concat(num > 1 ? 's' : ''); + $input.next().find('.count').text(text); + }, + }, + }, + { + label: 'Alarm mode', + group: [], + }, + { + label: 'Alarm content', + hide: true, + group: { + type: 'help', + style: { 'margin-top': '6px' }, + list: ['panel user change, panel log delete, panel open developer, panel open API'], + }, + }, + { + label: '', + group: { + type: 'button', + name: 'submitForm', + title: 'Add task', + event: function (formData, element, that) { + that.submit(formData); + }, + }, + }, + ]; + _this.init(); + return _this; + } + Config.prototype.init = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + this.$apiInit(this.apiInfo); + this.render(); + this.event(); + return [2]; + }); + }); + }; + Config.prototype.render = function () { + var _this = this; + var loadT = this.$load(lan.public.the); + Promise.all([ + this.$request('getConfig', false), + this.$request('getCheckTwoStep', { loading: false, msg: false }), + this.$request('getPasswordConfig', { loading: false, msg: false }), + this.$request('getUserInfo', { loading: false, msg: false }), + this.$request('getMessageChannel', { loading: false, msg: false }), + this.$request('getLoginAlarm', { loading: false, msg: false }), + this.$request('getMenuList', { loading: false, msg: false }), + ]) + .then(function (resArr) { + var configInfo = resArr[0], + twoStep = resArr[1], + pawComplexity = resArr[2], + bindUserInfo = resArr[3], + messageChannelInfo = resArr[4], + loginAlarmInfo = resArr[5], + menuList = resArr[6]; + panelConfig.init({ configInfo: configInfo, menuList: menuList, bindUserInfo: bindUserInfo }); + safeConfig.init({ configInfo: configInfo, twoStep: twoStep, pawComplexity: pawComplexity }); + noticeConfig.init({ messageChannelInfo: messageChannelInfo, loginAlarmInfo: loginAlarmInfo }); + }) + .catch(function (err) { + console.log(err); + _this.$error(err.msg || 'Server Error'); + }) + .finally(function () { + loadT.close(); + }); + }; + Config.prototype.event = function () { + var _this = this; + $('#configTab').on('click', '.tabs-item', function (ev) { + var el = $(ev.currentTarget); + var type = el.attr('data-type'); + el.addClass('active').siblings().removeClass('active'); + $('.configure-box .panel-config').addClass('hide'); + if (type === 'allConfig') { + $('.configure-box .panel-config:not(.alert-view-box)').removeClass('hide'); + } else { + if (type === 'alertConfig') _this.renderAlertView(); + $('.configure-box .panel-config[data-type="' + type + '"]').removeClass('hide'); + } + _this.$setCookie('config-tab', type); + }); + this.cateClick(); + $('input[type="text"]').on('input', function (ev) { + return __awaiter(_this, void 0, void 0, function () { + var el, value, oldValue; + return __generator(this, function (_a) { + el = $(ev.target); + value = el.val(); + oldValue = el.attr('value'); + value != oldValue ? el.parent().next().removeAttr('disabled') : el.parent().next().attr('disabled', 'disabled'); + return [2]; + }); + }); + }); + $('.savePanelConfig').click(function () { + return __awaiter(_this, void 0, void 0, function () { + var data, res, href; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + data = this.getInputData(); + return [4, this.$request('setPanelConfig', data)]; + case 1: + res = _a.sent(); + href = ''; + if (data.domain) { + href = window.location.protocol + '//' + data.domain + ':' + window.location.port + window.location.pathname; + } else { + href = window.location.protocol + '//' + data.address + ':' + window.location.port + window.location.pathname; + } + res.status && this.$refreshBrowser(); + return [2]; + } + }); + }); + }); + $('.setPanelPort').click(function () { + return _this.setPanelPortView(); + }); + $('#addAlertTask').on('click', '.alertInstall', function (ev) { + var _type = $(ev.currentTarget).parent('span').siblings('input').attr('name'); + _this.setAlertConfigType(_type); + }); + + setTimeout(function () { + $.fn.serializeObject = function () { + var hasOwnProperty = Object.prototype.hasOwnProperty; + return this.serializeArray().reduce(function (data, pair) { + if (!hasOwnProperty.call(data, pair.name)) { + data[pair.name] = pair.value; + } + return data; + }, {}); + }; + }, 300); + + panelConfig.event(); + safeConfig.event(); + noticeConfig.event(); + }; + Config.prototype.cateClick = function () { + var configTab = this.$getCookie('config-tab') || 'allConfig'; + if (!isNaN(Number(configTab))) { + configTab = 'allConfig'; } - Config.prototype.init = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - this.$apiInit(this.apiInfo); - this.render(); - this.event(); - return [2]; - }); - }); - }; - Config.prototype.render = function () { - var _this = this; - var loadT = this.$load(lan.public.the); - Promise.all([ - this.$request('getConfig', false), - this.$request('getCheckTwoStep', { loading: false, msg: false }), - this.$request('getPasswordConfig', { loading: false, msg: false }), - this.$request('getUserInfo', { loading: false, msg: false }), - this.$request('getMessageChannel', { loading: false, msg: false }), - this.$request('getLoginAlarm', { loading: false, msg: false }), - this.$request('getMenuList', { loading: false, msg: false }), - ]) - .then(function (resArr) { - var configInfo = resArr[0], twoStep = resArr[1], pawComplexity = resArr[2], bindUserInfo = resArr[3], messageChannelInfo = resArr[4], loginAlarmInfo = resArr[5], menuList = resArr[6]; - panelConfig.init({ configInfo: configInfo, menuList: menuList, bindUserInfo: bindUserInfo }); - safeConfig.init({ configInfo: configInfo, twoStep: twoStep, pawComplexity: pawComplexity }); - noticeConfig.init({ messageChannelInfo: messageChannelInfo, loginAlarmInfo: loginAlarmInfo }); - }) - .catch(function (err) { - console.log(err); - _this.$error(err.msg || 'Server Error'); - }) - .finally(function () { - loadT.close(); - }); - }; - Config.prototype.event = function () { - var _this = this; - $('#configTab').on('click', '.tabs-item', function (ev) { - var el = $(ev.currentTarget); - var type = el.attr('data-type'); - el.addClass('active').siblings().removeClass('active'); - $('.configure-box .panel-config').addClass('hide'); - if (type === 'allConfig') { - $('.configure-box .panel-config:not(.alert-view-box)').removeClass('hide'); - } - else { - if (type === 'alertConfig') - _this.renderAlertView(); - $('.configure-box .panel-config[data-type="' + type + '"]').removeClass('hide'); - } - _this.$setCookie('config-tab', type); - }); - this.cateClick(); - $('input[type="text"]').on('input', function (ev) { return __awaiter(_this, void 0, void 0, function () { - var el, value, oldValue; - return __generator(this, function (_a) { - el = $(ev.target); - value = el.val(); - oldValue = el.attr('value'); - value != oldValue ? el.parent().next().removeAttr('disabled') : el.parent().next().attr('disabled', 'disabled'); - return [2]; - }); - }); }); - $('.savePanelConfig').click(function () { return __awaiter(_this, void 0, void 0, function () { - var data, res, href; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - data = this.getInputData(); - return [4, this.$request('setPanelConfig', data)]; - case 1: - res = _a.sent(); - href = ''; - if (data.domain) { - href = window.location.protocol + '//' + data.domain + ':' + window.location.port + window.location.pathname; - } - else { - href = window.location.protocol + '//' + data.address + ':' + window.location.port + window.location.pathname; - } - res.status && this.$refreshBrowser(href); - return [2]; + $('#configTab .tabs-item[data-type="' + configTab + '"]').trigger('click'); + }; + Config.prototype.setPanelPortView = function () { + var _this = this; + var $input = $('input[name="port"]'); + var port = $input.val(); + this.$open({ + title: 'Change Panel Port', + area: ['380px', '380px'], + btn: ['Confirm', 'Cancel'], + content: { + data: { port: port, agreement: false }, + template: function () { + return (0, snabbdom_1.jsx)( + 'div', + { class: this.$class('pd20 bt-form') }, + this.$ul({ className: 'explainDescribeList', style: 'margin-top:0;' }, [ + ['1. Have a security group server, please release the new port in the security group in advance.', 'red'], + ['2. If the panel is inaccessible after modifying the port, change the original port to the SSH command line by using the bt command.', 'red'], + ]), + this.$line({ title: 'Port', width: '60px' }, this.$input({ model: 'port', width: '210px' })), + this.$learnMore({ + title: (0, snabbdom_1.jsx)( + 'span', + null, + 'I already understand, ', + this.$link({ title: 'How to release the port?', href: 'https://forum.aapanel.com/d/599-how-to-release-the-aapanel-port' }) + ), + model: 'agreement', + id: 'checkPanelPort', + }) + ); + }, + }, + yes: function (content) { + return __awaiter(_this, void 0, void 0, function () { + var close, vm, port, data, rdata; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + (close = content.close), (vm = content.vm), (port = parseInt(vm.port)); + if (!vm.agreement) return [2, this.$tips({ el: '#checkPanelPort', msg: 'Please tick the one I already know' })]; + return [4, this.$verifySubmit(!this.$checkPort(port), 'Please enter correct panel port!')]; + case 1: + _a.sent(); + data = this.getInputData(); + data.port = port; + return [4, this.$request('setPanelConfig', data)]; + case 2: + rdata = _a.sent(); + if (rdata.status) { + close(); + this.$refreshBrowser(''.concat(location.protocol, '//').concat(location.hostname, ':').concat(port).concat(location.pathname)); } - }); - }); }); - $('.setPanelPort').click(function () { return _this.setPanelPortView(); }); - $('#addAlertTask').on('click', '.alertInstall', function (ev) { - var _type = $(ev.currentTarget).parent('span').siblings('input').attr('name'); - _this.setAlertConfigType(_type); + return [2]; + } + }); }); - - setTimeout(function () { - $.fn.serializeObject = function () { - var hasOwnProperty = Object.prototype.hasOwnProperty; - return this.serializeArray().reduce(function (data, pair) { - if (!hasOwnProperty.call(data, pair.name)) { - data[pair.name] = pair.value; - } - return data; - }, {}); - }; - }, 300); - - panelConfig.event(); - safeConfig.event(); - noticeConfig.event(); - }; - Config.prototype.cateClick = function () { - var configTab = this.$getCookie('config-tab') || 'allConfig'; - if (!isNaN(Number(configTab))) { - configTab = 'allConfig'; + }, + }).catch(function (err) {}); + }; + Config.prototype.getInputData = function () { + var data = {}; + $('.savePanelConfig').each(function (index, item) { + var $input = $(item).parents('.line').find('input[type="text"]'); + var key = $input.attr('name'); + var value = $input.val(); + data[key] = value; + }); + return data; + }; + Config.renderFormColumn = function (configInfo) { + for (var key in configInfo) { + if (Object.prototype.hasOwnProperty.call(configInfo, key)) { + var value = configInfo[key].value; + var el = $('input[name="' + key + '"]'); + var type = el.attr('type'); + if (type === 'checkbox') { + el.prop('checked', value); + } else { + el.val(value); } - $('#configTab .tabs-item[data-type="' + configTab + '"]').trigger('click'); - }; - Config.prototype.setPanelPortView = function () { - var _this = this; - var $input = $('input[name="port"]'); - var port = $input.val(); - this.$open({ - title: 'Change Panel Port', - area: ['380px', '380px'], - btn: ['Confirm', 'Cancel'], - content: { - data: { port: port, agreement: false }, - template: function () { - return ((0, snabbdom_1.jsx)("div", { class: this.$class('pd20 bt-form') }, - this.$ul({ className: 'explainDescribeList', style: 'margin-top:0;' }, [ - ['1. Have a security group server, please release the new port in the security group in advance.', 'red'], - ['2. If the panel is inaccessible after modifying the port, change the original port to the SSH command line by using the bt command.', 'red'], - ]), - this.$line({ title: 'Port', width: '60px' }, this.$input({ model: 'port', width: '210px' })), - this.$learnMore({ - title: (0, snabbdom_1.jsx)("span", null, - "I already understand, ", - this.$link({ title: 'How to release the port?', href: 'https://forum.aapanel.com/d/599-how-to-release-the-aapanel-port' })), - model: 'agreement', - id: 'checkPanelPort', - }))); - }, + } + } + }; + Config.prototype.renderAlertView = function () { + return __awaiter(this, void 0, void 0, function () { + var _this = this; + return __generator(this, function (_a) { + this.alertTaskList(); + $('.alert-view-box') + .unbind('click') + .on('click', '.tab-nav-border span', function (ev) { + var el = $(ev.currentTarget), + index = $(el).index(); + $(el).addClass('on').siblings().removeClass('on'); + $(el).parent().next().find('.tab-block').eq(index).addClass('on').siblings().removeClass('on'); + switch (index) { + case 0: + _this.alertTaskList(); + break; + case 1: + _this.alertConfigTable(); + break; + case 2: + _this.alertLogsTable(); + break; + } + }); + return [2]; + }); + }); + }; + Config.prototype.alertTaskList = function () { + return __awaiter(this, void 0, void 0, function () { + var ChannelMessage, resetChannelMessage, prevArray, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; + case 1: + ChannelMessage = _b.sent(); + resetChannelMessage = []; + prevArray = []; + Object.getOwnPropertyNames(ChannelMessage).forEach(function (key) { + var mod = ChannelMessage[key]; + key == 'wx_account' ? prevArray.push(mod) : resetChannelMessage.push(mod); + }); + this.alertListModule = __spreadArray(__spreadArray([], prevArray, true), resetChannelMessage, true); + _a = this; + return [4, this.$request(['crontab/GetDataList'], { type: 'sites' })]; + case 2: + _a.panelSiteList = _b.sent(); + return [4, this.addAlertTask()]; + case 3: + _b.sent(); + this.renderAlarmList(); + return [2]; + } + }); + }); + }; + Config.prototype.addAlertTask = function () { + return __awaiter(this, void 0, void 0, function () { + var _config; + var _this = this; + return __generator(this, function (_a) { + _config = this.switchPushType(__spreadArray([], this.alertConfigForm, true)); + bt_tools.form({ + el: '#addAlertTask', + form: _config, + submit: function (formData) { + _this.setAlertConfigTask(formData); + }, + }); + return [2]; + }); + }); + }; + Config.prototype.renderAlarmList = function () { + var _this = this; + $('#alertList').empty(); + var alertListTabel = bt_tools.table({ + el: '#alertList', + url: '/push?action=get_push_list', + default: 'The alarm list is empty', + autoHeight: true, + height: 320, + dataFilter: function (res) { + $.each(res.site_push, function (index, item) { + item['id'] = index; + }); + var data = Object.values(res.site_push || []); + return { data: data }; + }, + column: [ + { + type: 'checkbox', + width: 20, + }, + { + fid: 'title', + title: 'Title', + type: 'text', + template: function (row) { + var _title = ''; + switch (row.type) { + case 'ssl': + _title = '['.concat(row.project == 'all' ? 'All' : row.project, ']').concat(row.title); + break; + default: + _title = row.title; + break; + } + return ''.concat(_title, ''); + }, + }, + { + fid: 'status', + title: 'Status', + config: { + icon: true, + list: [ + [true, 'Normal', 'bt_success', 'glyphicon-play'], + [false, 'Suspend', 'bt_danger', 'glyphicon-pause'], + ], + }, + type: 'status', + event: function (row) { + return __awaiter(_this, void 0, void 0, function () { + var param, eData, rdata; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (row.type == 'ssh_login') return [2, layer.msg('Do not support suspend SSH login alarm, if you want to stop it, please delete it directly.', { icon: 0 })]; + (param = {}), (eData = $.extend(true, row, { status: row.status ? false : true })); + param['name'] = row.module_type; + param['id'] = row.id; + param['data'] = JSON.stringify(eData); + return [4, this.$request(['push/set_push_config', 'Setting alarm tasks'], param)]; + case 1: + rdata = _a.sent(); + if (!rdata.status) return [3, 3]; + return [4, this.alertTaskList()]; + case 2: + _a.sent(); + _a.label = 3; + case 3: + return [2]; + } + }); + }); + }, + }, + { + title: 'Alarm mode', + type: 'text', + width: 265, + template: function (row) { + var alertMode = row.module.split(','), + _mode = ''; + _this.alertListModule.forEach(function (mod) { + if ($.inArray(mod.name, alertMode) >= 0) _mode += mod.title + ','; + }); + _mode = _mode.substring(0, _mode.length - 1); + return '' + _mode + ''; + }, + }, + { + fid: 'cycle', + title: 'Alarm condition', + template: function (row) { + switch (row.type) { + case 'ssl': + case 'site_endtime': + case 'panel_pwd_endtime': + return 'Less than ' + .concat(row.cycle, ' days remaining ') + .concat(typeof row.push_count != 'undefined' ? '(If not processed, it will be resent 1 time the next day for ' + row.push_count + ' days)' : '', ''); + case 'ssh_login_error': + return 'Triggered by ' + .concat(row.count, ' consecutive failed login attempts within ') + .concat(row.cycle, ' minutes, to be detected again after every ') + .concat(row.interval, ' seconds'); + case 'panel_update': + return 'Send a notification when a new version is detected'; + default: + return '--'; + } + }, + }, + { + title: lan.public.operate, + type: 'group', + width: 150, + align: 'right', + group: [ + { + title: lan.public.edit, + event: function (row) { + _this.setAlertTaskConfig(row); + }, }, - yes: function (content) { return __awaiter(_this, void 0, void 0, function () { - var close, vm, port, data, rdata; - return __generator(this, function (_a) { + { + title: lan.public.del, + event: function (row) { + return __awaiter(_this, void 0, void 0, function () { + var rdata; + return __generator(this, function (_a) { switch (_a.label) { - case 0: - close = content.close, vm = content.vm, port = parseInt(vm.port); - if (!vm.agreement) - return [2, this.$tips({ el: '#checkPanelPort', msg: 'Please tick the one I already know' })]; - return [4, this.$verifySubmit(!this.$checkPort(port), 'Please enter correct panel port!')]; - case 1: - _a.sent(); - data = this.getInputData(); - data.port = port; - return [4, this.$request('setPanelConfig', data)]; - case 2: - rdata = _a.sent(); - if (rdata.status) { - close(); - this.$refreshBrowser("".concat(location.protocol, "//").concat(location.hostname, ":").concat(port).concat(location.pathname)); - } - return [2]; - } - }); - }); }, - }).catch(function (err) { }); - }; - Config.prototype.getInputData = function () { - var data = {}; - $('.savePanelConfig').each(function (index, item) { - var $input = $(item).parents('.line').find('input[type="text"]'); - var key = $input.attr('name'); - var value = $input.val(); - data[key] = value; - }); - return data; - }; - Config.renderFormColumn = function (configInfo) { - for (var key in configInfo) { - if (Object.prototype.hasOwnProperty.call(configInfo, key)) { - var value = configInfo[key].value; - var el = $('input[name="' + key + '"]'); - var type = el.attr('type'); - if (type === 'checkbox') { - el.prop('checked', value); - } - else { - el.val(value); - } - } - } - }; - Config.prototype.renderAlertView = function () { - return __awaiter(this, void 0, void 0, function () { - var _this = this; - return __generator(this, function (_a) { - this.alertTaskList(); - $('.alert-view-box') - .unbind('click') - .on('click', '.tab-nav-border span', function (ev) { - var el = $(ev.currentTarget), index = $(el).index(); - $(el).addClass('on').siblings().removeClass('on'); - $(el).parent().next().find('.tab-block').eq(index).addClass('on').siblings().removeClass('on'); - switch (index) { - case 0: - _this.alertTaskList(); - break; - case 1: - _this.alertConfigTable(); - break; - case 2: - _this.alertLogsTable(); - break; - } - }); - return [2]; - }); - }); - }; - Config.prototype.alertTaskList = function () { - return __awaiter(this, void 0, void 0, function () { - var ChannelMessage, resetChannelMessage, prevArray, _a; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; - case 1: - ChannelMessage = _b.sent(); - resetChannelMessage = []; - prevArray = []; - Object.getOwnPropertyNames(ChannelMessage).forEach(function (key) { - var mod = ChannelMessage[key]; - key == 'wx_account' ? prevArray.push(mod) : resetChannelMessage.push(mod); - }); - this.alertListModule = __spreadArray(__spreadArray([], prevArray, true), resetChannelMessage, true); - _a = this; - return [4, this.$request(['crontab/GetDataList'], { type: 'sites' })]; - case 2: - _a.panelSiteList = _b.sent(); - return [4, this.addAlertTask()]; - case 3: - _b.sent(); - this.renderAlarmList(); - return [2]; - } - }); - }); - }; - Config.prototype.addAlertTask = function () { - return __awaiter(this, void 0, void 0, function () { - var _config; - var _this = this; - return __generator(this, function (_a) { - _config = this.switchPushType(__spreadArray([], this.alertConfigForm, true)); - bt_tools.form({ - el: '#addAlertTask', - form: _config, - submit: function (formData) { - _this.setAlertConfigTask(formData); - }, - }); - return [2]; - }); - }); - }; - Config.prototype.renderAlarmList = function () { - var _this = this; - $('#alertList').empty(); - var alertListTabel = bt_tools.table({ - el: '#alertList', - url: '/push?action=get_push_list', - default: 'The alarm list is empty', - autoHeight: true, - height: 320, - dataFilter: function (res) { - $.each(res.site_push, function (index, item) { - item['id'] = index; - }); - var data = Object.values(res.site_push || []); - return { data: data }; - }, - column: [ - { - type: 'checkbox', - width: 20, - }, - { - fid: 'title', - title: 'Title', - type: 'text', - template: function (row) { - var _title = ''; - switch (row.type) { - case 'ssl': - _title = "[".concat(row.project == 'all' ? 'All' : row.project, "]").concat(row.title); - break; - default: - _title = row.title; - break; - } - return "".concat(_title, ""); - }, - }, - { - fid: 'status', - title: 'Status', - config: { - icon: true, - list: [ - [true, 'Normal', 'bt_success', 'glyphicon-play'], - [false, 'Suspend', 'bt_danger', 'glyphicon-pause'], - ], - }, - type: 'status', - event: function (row) { return __awaiter(_this, void 0, void 0, function () { - var param, eData, rdata; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (row.type == 'ssh_login') - return [2, layer.msg('Do not support suspend SSH login alarm, if you want to stop it, please delete it directly.', { icon: 0 })]; - param = {}, eData = $.extend(true, row, { status: row.status ? false : true }); - param['name'] = row.module_type; - param['id'] = row.id; - param['data'] = JSON.stringify(eData); - return [4, this.$request(['push/set_push_config', 'Setting alarm tasks'], param)]; - case 1: - rdata = _a.sent(); - if (!rdata.status) return [3, 3]; - return [4, this.alertTaskList()]; - case 2: - _a.sent(); - _a.label = 3; - case 3: return [2]; - } - }); - }); }, - }, - { - title: 'Alarm mode', - type: 'text', - width: 265, - template: function (row) { - var alertMode = row.module.split(','), _mode = ''; - _this.alertListModule.forEach(function (mod) { - if ($.inArray(mod.name, alertMode) >= 0) - _mode += mod.title + ','; - }); - _mode = _mode.substring(0, _mode.length - 1); - return '' + _mode + ''; - }, - }, - { - fid: 'cycle', - title: 'Alarm condition', - template: function (row) { - switch (row.type) { - case 'ssl': - case 'site_endtime': - case 'panel_pwd_endtime': - return "Less than ".concat(row.cycle, " days remaining ").concat(typeof row.push_count != 'undefined' ? '(If not processed, it will be resent 1 time the next day for ' + row.push_count + ' days)' : '', ""); - case 'ssh_login_error': - return "Triggered by ".concat(row.count, " consecutive failed login attempts within ").concat(row.cycle, " minutes, to be detected again after every ").concat(row.interval, " seconds"); - case 'panel_update': - return "Send a notification when a new version is detected"; - default: - return "--"; - } - }, - }, - { - title: lan.public.operate, - type: 'group', - width: 150, - align: 'right', - group: [ - { - title: lan.public.edit, - event: function (row) { - _this.setAlertTaskConfig(row); - }, - }, - { - title: lan.public.del, - event: function (row) { return __awaiter(_this, void 0, void 0, function () { - var rdata; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4, this.$confirm({ - title: 'Delete Alarm Tasks', - msg: 'Delete will no longer alert this task, do you want to continue?', - })]; - case 1: - _a.sent(); - return [4, this.$request(['push/del_push_config', 'Deleting the alarm task'], { name: row.module_type, id: row.id })]; - case 2: - rdata = _a.sent(); - if (!rdata.status) return [3, 4]; - return [4, this.alertTaskList()]; - case 3: - _a.sent(); - _a.label = 4; - case 4: return [2]; - } - }); - }); }, - }, - ], - }, - ], - tootls: [ - { - type: 'batch', - positon: ['left', 'bottom'], - config: { - title: ' Delete', - url: 'push?action=del_push_config', - load: true, - param: function (row) { - return { name: row.module_type, id: row.id }; - }, - callback: function (that) { - bt.confirm({ title: 'Batch Delete Tasks', msg: 'The batch deletion will not be recovered, does it continue?', icon: 0 }, function (index) { - layer.close(index); - that.start_batch({}, function (list) { - var html = ''; - for (var i = 0; i < list.length; i++) { - var item = list[i]; - html += - '' + - (typeof item.project == 'undefined' ? item.title : (item.project == 'all' ? 'All' : item.project) + item.title) + - '
' + - item.request.msg + - '
'; - } - alertListTabel.$batch_success_table({ title: 'Batch Delete Tasks', th: 'Task title', html: html }); - alertListTabel.$refresh_table_list(true); - }); - }); - }, - }, - }, - ], - }); - }; - Config.prototype.setAlertTaskConfig = function (row) { - return __awaiter(this, void 0, void 0, function () { - var _config; - var _this = this; - return __generator(this, function (_a) { - _config = this.switchPushType($.extend(true, {}, this.alertConfigForm), row); - _config[0].group.disabled = true; - _config[0].group.unit = ''; - _config[8].hide = true; - if (row.type == 'ssh_login_error') { - row.where1 = row.cycle; - } - bt_tools.open({ - type: 1, - title: 'Edit Alert Tasks', - area: '540px', - skin: 'panel_alert_task_view', - btn: [lan.public.save, lan.public.cancel], - content: { - class: 'pd15', - data: row, - form: _config, - }, - success: function (layers) { - $(layers) - .find('.layui-layer-content') - .css('overflow', window.innerHeight > $(layers).height() ? 'inherit' : 'auto'); - $('.alertInstall').click(function (ev) { - var _type = $(ev.currentTarget).parent('span').siblings('input').attr('name'); - _this.setAlertConfigType(_type); - }); - }, - yes: function (formData, index) { - _this.setAlertConfigTask($.extend(true, {}, row, formData), index); - }, - }); - return [2]; - }); - }); - }; - Config.prototype.setAlertConfigTask = function (row, close) { - if (close === void 0) { close = null; } - return __awaiter(this, void 0, void 0, function () { - var _configD, eData, pushType, otherType, isCheck, rdata; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _configD = {}, eData = {}, pushType = this.taskTypeList.find(function (el) { return el.value == row.type; }), otherType = this.disabledOption; - _configD['name'] = typeof row.module_type !== 'undefined' ? row.module_type : pushType.model; - _configD['id'] = row.id ? row.id : $.inArray(row.type, otherType) >= 0 ? row.type : new Date().getTime(); - eData['interval'] = 600; - switch (row.type) { - case 'ssl': - case 'site_endtime': - case 'panel_pwd_endtime': - if (row.type == 'ssl') - eData['project'] = row.site || 'all'; - if (row.cycle == '' || row.cycle < 0) - return [2, this.$msg({ icon: 2, msg: 'Remaining days cannot be less than 1', time: 0, closeBtn: 2 })]; - eData['cycle'] = Number(row.cycle); - eData['push_count'] = Number(row.push_count); - break; - case 'ssh_login_error': - if (row.where1 == '' || row.where1 <= 0) - return [2, this.$msg({ icon: 2, msg: 'Trigger time cannot be less than 1', time: 0, closeBtn: 2 })]; - if (row.count == '' || row.count <= 0) - return [2, this.$msg({ icon: 2, msg: 'Trigger times cannot be less than 1', time: 0, closeBtn: 2 })]; - if (row.interval == '' || row.interval <= 0) - return [2, this.$msg({ icon: 2, msg: 'Interval cannot be less than 1', time: 0, closeBtn: 2 })]; - eData['cycle'] = Number(row.where1); - eData['count'] = Number(row.count); - eData['interval'] = Number(row.interval); - break; - } - isCheck = []; - $((row.id ? '.panel_alert_task_view ' : '#addAlertTask ') + '.module-check') - .not('.check_disabled') - .each(function () { - if ($(this).find('input').prop('checked')) { - isCheck.push($(this).find('input').prop('name')); - } - }); - eData['type'] = row.type; - eData['module'] = isCheck.join(); - if (typeof eData['push_count'] != 'undefined' && (eData['push_count'] <= 0 || eData['push_count'] == '')) { - this.$msg({ icon: 2, msg: 'The number of sending cannot be less than 1', time: 0, closeBtn: 2 }); - return [2, false]; - } - if (!eData['module']) { - this.$msg({ icon: 2, msg: 'Please select an alarm mode', time: 0, closeBtn: 2 }); - return [2, false]; - } - eData['status'] = typeof row.status !== 'undefined' ? row.status : true; - eData['title'] = $((row.id ? '.panel_alert_task_view ' : '#addAlertTask ') + '.projectBox .bt_select_content').html(); - _configD['data'] = JSON.stringify(eData); - return [4, this.$request(['push/set_push_config', 'Setting alarm task, Please wait...'], _configD)]; - case 1: + case 0: + return [ + 4, + this.$confirm({ + title: 'Delete Alarm Tasks', + msg: 'Delete will no longer alert this task, do you want to continue?', + }), + ]; + case 1: + _a.sent(); + return [4, this.$request(['push/del_push_config', 'Deleting the alarm task'], { name: row.module_type, id: row.id })]; + case 2: rdata = _a.sent(); - if (!rdata.status) return [3, 3]; - layer.close(close); + if (!rdata.status) return [3, 4]; return [4, this.alertTaskList()]; - case 2: + case 3: _a.sent(); - _a.label = 3; - case 3: return [2]; - } - }); - }); - }; - Config.prototype.switchPushType = function (config, formData) { - if (formData === void 0) { formData = {}; } - var _checklist = [], isCheckType = [], siteList = [{ title: 'All Website', value: 'all' }], accountConfigStatus = false; - if (!formData.type) { - formData.type = 'ssl'; - config[1].group.value = 'all'; - } - this.panelSiteList['data'].forEach(function (key) { - siteList.push({ title: key.name, value: key.name }); - }); - this.alertListModule.forEach(function (mod, i) { - if (formData.type != 'ssl' && mod.name == 'sms') - return; - if (formData.module) { - isCheckType = formData.module.split(','); - } - if (mod.name === 'wx_account') { - if (!$.isEmptyObject(mod.data) && mod.data.res.is_subscribe && mod.data.res.is_bound) { - accountConfigStatus = true; - } - } - _checklist.push({ - type: 'checkbox', - name: mod.name, - class: 'module-check ' + (!mod.setup || $.isEmptyObject(mod.data) ? 'check_disabled' : mod.name == 'wx_account' && !accountConfigStatus ? 'check_disabled' : '') + '', - style: { 'margin-right': '10px' }, - disabled: !mod.setup || $.isEmptyObject(mod.data) ? true : mod.name == 'wx_account' && !accountConfigStatus ? true : false, - value: $.inArray(mod.name, isCheckType) >= 0 ? 1 : 0, - title: (mod.name == 'wx_account' ? ' [Recommend]' : '') + - mod.title + - (!mod.setup || $.isEmptyObject(mod.data) - ? ' [Install]' - : mod.name == 'wx_account' && !accountConfigStatus - ? ' [Not set]' - : ''), - event: function (formData, element, thatE) { - thatE.config.form[6].group[i].value = !formData[mod.name] ? 0 : 1; - }, - }); - }); - if (!formData.id) { - var checkActive = _checklist.findIndex(function (ev) { return !ev.disabled; }); - if (checkActive >= 0) - _checklist[checkActive].value = 1; - } - else { - if (formData.type == 'ssl') - config[1].group.value = formData.project; - } - config[1].hide = true; - config[3].hide = true; - config[4].hide = true; - config[5].hide = false; - delete config[0].group.unit; - switch (formData.type) { - case 'ssl': - config[1].hide = false; - config[2].hide = false; - config[2].group.value = 15; - break; - case 'site_endtime': - config[2].hide = false; - config[2].group.value = 7; - break; - case 'panel_pwd_endtime': - config[2].hide = false; - config[2].group.value = 15; - break; - case 'panel_login': - case 'ssh_login': - case 'panel_safe_push': - case 'panel_update': - config[2].hide = true; - config[5].hide = true; - if (formData.type == 'panel_update') { - config[0].group.unit = '* Send a notification when a new version is detected'; - } - break; - case 'ssh_login_error': - config[2].hide = true; - config[3].hide = false; - config[4].hide = false; - config[5].hide = true; - break; - } - config[7].hide = formData.type === 'panel_safe_push' ? false : true; - config[0].group.value = formData.type; - config[1].group.list = siteList; - config[6].group = _checklist; - return config; - }; - Config.prototype.setAlertConfigType = function (type) { - return __awaiter(this, void 0, void 0, function () { - var _configData; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; - case 1: - _configData = _a.sent(); - switch (type) { - case 'mail': - renderMailConfigView(_configData[type]); - break; - case 'dingding': - case 'feishu': - case 'weixin': - renderAlertUrlTypeChannelView(_configData[type]); - break; - case 'tg': - renderTelegramConfigView(_configData[type]); - break; - } + _a.label = 4; + case 4: return [2]; - } - }); - }); - }; - Config.prototype.alertConfigTable = function () { - return __awaiter(this, void 0, void 0, function () { - var ChannelInfo, html, tbody, prevHTML; - var _this = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; - case 1: - ChannelInfo = _a.sent(); - html = '', tbody = '', prevHTML = ''; - $('#alertConfig').empty(); - Object.getOwnPropertyNames(ChannelInfo).forEach(function (key) { - var item = ChannelInfo[key], btnGroup = ''; - if (item.setup) { - if (item.name != 'sms') { - if (!$.isEmptyObject(item.data)) { - if (item.name == 'mail') - btnGroup += 'Recipient | '; - btnGroup += - '' + lan.public.edit + ' | Test | ' + lan.public.del + ''; - if (item.name == 'wx_account') - btnGroup = 'Bind | Test | ' + lan.public.del + ''; - } - else { - btnGroup = '' + lan.public.set + ''; - } - } - else { - btnGroup = '' + lan.public.del + ''; - } - } - else { - btnGroup = '' + lan.public.set + ''; - } - var renderHTML = "\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t").concat(item.title, "\n\t\t\t\t\t\t

").concat(item.ps, ">>").concat(lan.public.help, "

\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t").concat(_this.renderAlertModelConfigInfo(item), "\n\t\t\t\t").concat(item.version, "\n\t\t\t\t").concat(btnGroup, "\n\t\t\t"); - item.name === 'wx_account' ? (prevHTML = renderHTML) : (tbody += renderHTML); - }); - html = "
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t").concat(prevHTML + tbody, "\n\t\t\t\t\t\t\t
Alarm moduleConfigurationVersion".concat(lan.public.operate, "
\n\t\t\t\t\t\t
"); - $('#alertConfig').html(html); - this.alertEventBind(ChannelInfo); - return [2]; - } - }); - }); - }; - Config.prototype.renderAlertModelConfigInfo = function (mode) { - var _info = '', noConfig = 'Unconfigured', _data = mode.data, isEmpty = $.isEmptyObject(_data); - if (mode.setup) { - if (mode.name != 'sms' && mode.name != 'wx_account') { - if (!$.isEmptyObject(_data)) { - switch (mode.name) { - case 'mail': - if (_data.receive[0] == '') { - _info = "No incoming email set"; - } - else { - _info = "".concat(_data.receive.length, " incoming email has been set up, Click to view"); - } - break; - case 'dingding': - case 'feishu': - case 'weixin': - _info = "Receiver: [".concat(isEmpty ? '' : _data.list.default.title, "]"); - break; - case 'tg': - _info = "Receiver: [".concat(isEmpty ? '' : _data.my_id, "]"); - break; } - } - else { - _info = noConfig; - } - } - else if (mode.name == 'sms') { - _info = "\u5269\u4F59\u53D1\u9001\u544A\u8B66".concat(_data.count, "\u6B21"); - } - else if (mode.name == 'wx_account') { - var boundCheck = '', res = $.isEmptyObject(_data) ? { is_subscribe: 0, is_bound: 0 } : _data.res; - if (!res.is_subscribe || !res.is_bound) - boundCheck = '未订阅公众号或绑定微信'; - if (res.is_subscribe && res.is_bound) - boundCheck = "\u5FAE\u4FE1\u8D26\u53F7\u3010".concat(res.nickname, "\u3011,\u4ECA\u65E5\u5269\u4F59\u53D1\u9001\u6B21\u6570:").concat(res.remaining); - _info = boundCheck; - } - } - else { - _info = noConfig; - } - return _info; - }; - Config.prototype.alertEventBind = function (info) { - var _this = this; - $('.receiveMail').click(function () { return __awaiter(_this, void 0, void 0, function () { - var currentItem; - var _this = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; - case 1: - currentItem = _a.sent(); - this.$open({ - title: 'Recipient Email', - area: ['335px', '280px'], - btn: [lan.public.save, lan.public.cancel], - skin: 'alert-receive-view', - content: "
\n\t\t\t\t
Fill in one mailbox per line, e:
xxx@163.com
xxx@qq.com
", - success: function () { - var _tips = $('textarea[name=recipient_textarea]'); - var msg = ''; - if (!$.isEmptyObject(currentItem['mail']['data']['receive'])) { - msg = currentItem['mail']['data']['receive'] ? currentItem['mail']['data']['receive'].join('\n') : ''; - } - _tips.html(msg); - if (_tips.val() == '') - $('.reci_tips.placeholder').show(); - $('.placeholder').click(function () { - $(this).hide().siblings('textarea').focus(); - }); - _tips.focus(function () { - $('.reci_tips.placeholder').hide(); - }); - _tips.blur(function () { - _tips.val() == '' ? $('.reci_tips.placeholder').show() : $('.reci_tips.placeholder').hide(); - }); - }, - yes: function (config) { return __awaiter(_this, void 0, void 0, function () { - var close, reci_, rdata; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - close = config.close; - reci_ = $('textarea[name=recipient_textarea]').val(); - return [4, this.$request('setMsgConfigmail', { mails: reci_ })]; - case 1: - rdata = _a.sent(); - rdata.status && close(); - return [2]; - } - }); - }); }, - }); - return [2]; - } - }); - }); }); - $('.configEdit').click(function (ev) { return __awaiter(_this, void 0, void 0, function () { - var _type; - return __generator(this, function (_a) { - _type = $(ev.currentTarget).parents('tr').data('name'); - this.setAlertConfigType(_type); - return [2]; - }); - }); }); - $('.alertTest').click(function (ev) { return __awaiter(_this, void 0, void 0, function () { - var _type; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _type = $(ev.currentTarget).parents('tr').data('name'); - return [4, this.$request(['config/get_msg_fun', 'Testing Send, Please wait...'], { fun_name: 'push_data', module_name: _type, msg: 'Testing Send' })]; - case 1: - _a.sent(); - return [2]; - } - }); - }); }); - $('.replaceWx').click(function () { - _this.setAlertConfigType('wx_account'); - }); - $('.uninstall_alert').click(function (ev) { return __awaiter(_this, void 0, void 0, function () { - var _type, rdata, _a; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _type = $(ev.currentTarget).parents('tr').data('name'); - return [4, this.$confirm({ - title: 'Delete ' + info[_type].title + ' module', - msg: 'After deleting the ' + info[_type].title + ' module, it will not be able to send panel alert messages, should I continue?', - })]; - case 1: - _b.sent(); - return [4, this.$request(['config/uninstall_msg_module&name=' + _type, 'Delete ' + info[_type].title + ' alert module'])]; - case 2: - rdata = _b.sent(); - _a = rdata.status; - if (!_a) return [3, 4]; - return [4, this.alertConfigTable()]; - case 3: - _a = (_b.sent()); - _b.label = 4; - case 4: - _a; - return [2]; - } - }); - }); }); - }; - Config.prototype.alertLogsTable = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - $('#alertLog').empty(); - bt_tools.table({ - el: '#alertLog', - load: 'Getting the alarm log list', - url: '/push?action=get_push_logs', - default: 'The alarm log is empty', - dataFilter: function (res) { - return { data: res.data }; - }, - column: [ - { - fid: 'log', - title: 'Title', - type: 'text', - }, - { - fid: 'addtime', - title: 'Time', - type: 'text', - }, - ], - tootls: [ - { - type: 'page', - positon: ['right', 'bottom'], - pageParam: 'p', - page: 1, - numberParam: 'limit', - number: 20, - numberList: [10, 20, 50, 100, 200], - numberStatus: true, - jump: true, - }, - ], + }); }); - return [2]; + }, + }, + ], + }, + ], + tootls: [ + { + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' Delete', + url: 'push?action=del_push_config', + load: true, + param: function (row) { + return { name: row.module_type, id: row.id }; + }, + callback: function (that) { + bt.confirm({ title: 'Batch Delete Tasks', msg: 'The batch deletion will not be recovered, does it continue?', icon: 0 }, function (index) { + layer.close(index); + that.start_batch({}, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + (typeof item.project == 'undefined' ? item.title : (item.project == 'all' ? 'All' : item.project) + item.title) + + '
' + + item.request.msg + + '
'; + } + alertListTabel.$batch_success_table({ title: 'Batch Delete Tasks', th: 'Task title', html: html }); + alertListTabel.$refresh_table_list(true); + }); + }); + }, + }, + }, + ], + }); + }; + Config.prototype.setAlertTaskConfig = function (row) { + return __awaiter(this, void 0, void 0, function () { + var _config; + var _this = this; + return __generator(this, function (_a) { + _config = this.switchPushType($.extend(true, {}, this.alertConfigForm), row); + _config[0].group.disabled = true; + _config[0].group.unit = ''; + _config[8].hide = true; + if (row.type == 'ssh_login_error') { + row.where1 = row.cycle; + } + bt_tools.open({ + type: 1, + title: 'Edit Alert Tasks', + area: '540px', + skin: 'panel_alert_task_view', + btn: [lan.public.save, lan.public.cancel], + content: { + class: 'pd15', + data: row, + form: _config, + }, + success: function (layers) { + $(layers) + .find('.layui-layer-content') + .css('overflow', window.innerHeight > $(layers).height() ? 'inherit' : 'auto'); + $('.alertInstall').click(function (ev) { + var _type = $(ev.currentTarget).parent('span').siblings('input').attr('name'); + _this.setAlertConfigType(_type); }); + }, + yes: function (formData, index) { + _this.setAlertConfigTask($.extend(true, {}, row, formData), index); + }, }); - }; - return Config; - }(public_1.default)); + return [2]; + }); + }); + }; + Config.prototype.setAlertConfigTask = function (row, close) { + if (close === void 0) { + close = null; + } + return __awaiter(this, void 0, void 0, function () { + var _configD, eData, pushType, otherType, isCheck, rdata; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + (_configD = {}), + (eData = {}), + (pushType = this.taskTypeList.find(function (el) { + return el.value == row.type; + })), + (otherType = this.disabledOption); + _configD['name'] = typeof row.module_type !== 'undefined' ? row.module_type : pushType.model; + _configD['id'] = row.id ? row.id : $.inArray(row.type, otherType) >= 0 ? row.type : new Date().getTime(); + eData['interval'] = 600; + switch (row.type) { + case 'ssl': + case 'site_endtime': + case 'panel_pwd_endtime': + if (row.type == 'ssl') eData['project'] = row.site || 'all'; + if (row.cycle == '' || row.cycle < 0) return [2, this.$msg({ icon: 2, msg: 'Remaining days cannot be less than 1', time: 0, closeBtn: 2 })]; + eData['cycle'] = Number(row.cycle); + eData['push_count'] = Number(row.push_count); + break; + case 'ssh_login_error': + if (row.where1 == '' || row.where1 <= 0) return [2, this.$msg({ icon: 2, msg: 'Trigger time cannot be less than 1', time: 0, closeBtn: 2 })]; + if (row.count == '' || row.count <= 0) return [2, this.$msg({ icon: 2, msg: 'Trigger times cannot be less than 1', time: 0, closeBtn: 2 })]; + if (row.interval == '' || row.interval <= 0) return [2, this.$msg({ icon: 2, msg: 'Interval cannot be less than 1', time: 0, closeBtn: 2 })]; + eData['cycle'] = Number(row.where1); + eData['count'] = Number(row.count); + eData['interval'] = Number(row.interval); + break; + } + isCheck = []; + $((row.id ? '.panel_alert_task_view ' : '#addAlertTask ') + '.module-check') + .not('.check_disabled') + .each(function () { + if ($(this).find('input').prop('checked')) { + isCheck.push($(this).find('input').prop('name')); + } + }); + eData['type'] = row.type; + eData['module'] = isCheck.join(); + if (typeof eData['push_count'] != 'undefined' && (eData['push_count'] <= 0 || eData['push_count'] == '')) { + this.$msg({ icon: 2, msg: 'The number of sending cannot be less than 1', time: 0, closeBtn: 2 }); + return [2, false]; + } + if (!eData['module']) { + this.$msg({ icon: 2, msg: 'Please select an alarm mode', time: 0, closeBtn: 2 }); + return [2, false]; + } + eData['status'] = typeof row.status !== 'undefined' ? row.status : true; + eData['title'] = $((row.id ? '.panel_alert_task_view ' : '#addAlertTask ') + '.projectBox .bt_select_content').html(); + _configD['data'] = JSON.stringify(eData); + return [4, this.$request(['push/set_push_config', 'Setting alarm task, Please wait...'], _configD)]; + case 1: + rdata = _a.sent(); + if (!rdata.status) return [3, 3]; + layer.close(close); + return [4, this.alertTaskList()]; + case 2: + _a.sent(); + _a.label = 3; + case 3: + return [2]; + } + }); + }); + }; + Config.prototype.switchPushType = function (config, formData) { + if (formData === void 0) { + formData = {}; + } + var _checklist = [], + isCheckType = [], + siteList = [{ title: 'All Website', value: 'all' }], + accountConfigStatus = false; + if (!formData.type) { + formData.type = 'ssl'; + config[1].group.value = 'all'; + } + this.panelSiteList['data'].forEach(function (key) { + siteList.push({ title: key.name, value: key.name }); + }); + this.alertListModule.forEach(function (mod, i) { + if (formData.type != 'ssl' && mod.name == 'sms') return; + if (formData.module) { + isCheckType = formData.module.split(','); + } + if (mod.name === 'wx_account') { + if (!$.isEmptyObject(mod.data) && mod.data.res.is_subscribe && mod.data.res.is_bound) { + accountConfigStatus = true; + } + } + _checklist.push({ + type: 'checkbox', + name: mod.name, + class: 'module-check ' + (!mod.setup || $.isEmptyObject(mod.data) ? 'check_disabled' : mod.name == 'wx_account' && !accountConfigStatus ? 'check_disabled' : '') + '', + style: { 'margin-right': '10px' }, + disabled: !mod.setup || $.isEmptyObject(mod.data) ? true : mod.name == 'wx_account' && !accountConfigStatus ? true : false, + value: $.inArray(mod.name, isCheckType) >= 0 ? 1 : 0, + title: + (mod.name == 'wx_account' ? ' [Recommend]' : '') + + mod.title + + (!mod.setup || $.isEmptyObject(mod.data) + ? ' [Install]' + : mod.name == 'wx_account' && !accountConfigStatus + ? ' [Not set]' + : ''), + event: function (formData, element, thatE) { + thatE.config.form[6].group[i].value = !formData[mod.name] ? 0 : 1; + }, + }); + }); + if (!formData.id) { + var checkActive = _checklist.findIndex(function (ev) { + return !ev.disabled; + }); + if (checkActive >= 0) _checklist[checkActive].value = 1; + } else { + if (formData.type == 'ssl') config[1].group.value = formData.project; + } + config[1].hide = true; + config[3].hide = true; + config[4].hide = true; + config[5].hide = false; + delete config[0].group.unit; + switch (formData.type) { + case 'ssl': + config[1].hide = false; + config[2].hide = false; + config[2].group.value = 15; + break; + case 'site_endtime': + config[2].hide = false; + config[2].group.value = 7; + break; + case 'panel_pwd_endtime': + config[2].hide = false; + config[2].group.value = 15; + break; + case 'panel_login': + case 'ssh_login': + case 'panel_safe_push': + case 'panel_update': + config[2].hide = true; + config[5].hide = true; + if (formData.type == 'panel_update') { + config[0].group.unit = '* Send a notification when a new version is detected'; + } + break; + case 'ssh_login_error': + config[2].hide = true; + config[3].hide = false; + config[4].hide = false; + config[5].hide = true; + break; + } + config[7].hide = formData.type === 'panel_safe_push' ? false : true; + config[0].group.value = formData.type; + config[1].group.list = siteList; + config[6].group = _checklist; + return config; + }; + Config.prototype.setAlertConfigType = function (type) { + return __awaiter(this, void 0, void 0, function () { + var _configData; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; + case 1: + _configData = _a.sent(); + switch (type) { + case 'mail': + renderMailConfigView(_configData[type]); + break; + case 'dingding': + case 'feishu': + case 'weixin': + renderAlertUrlTypeChannelView(_configData[type]); + break; + case 'tg': + renderTelegramConfigView(_configData[type]); + break; + } + return [2]; + } + }); + }); + }; + Config.prototype.alertConfigTable = function () { + return __awaiter(this, void 0, void 0, function () { + var ChannelInfo, html, tbody, prevHTML; + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; + case 1: + ChannelInfo = _a.sent(); + (html = ''), (tbody = ''), (prevHTML = ''); + $('#alertConfig').empty(); + Object.getOwnPropertyNames(ChannelInfo).forEach(function (key) { + var item = ChannelInfo[key], + btnGroup = ''; + if (item.setup) { + if (item.name != 'sms') { + if (!$.isEmptyObject(item.data)) { + if (item.name == 'mail') btnGroup += 'Recipient | '; + btnGroup += + '' + + lan.public.edit + + ' | Test | ' + + lan.public.del + + ''; + if (item.name == 'wx_account') + btnGroup = 'Bind | Test | ' + lan.public.del + ''; + } else { + btnGroup = '' + lan.public.set + ''; + } + } else { + btnGroup = '' + lan.public.del + ''; + } + } else { + btnGroup = '' + lan.public.set + ''; + } + var renderHTML = '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t') + .concat(item.title, '\n\t\t\t\t\t\t

') + .concat(item.ps, '>>') + .concat(lan.public.help, '

\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t') + .concat(_this.renderAlertModelConfigInfo(item), '\n\t\t\t\t') + .concat(item.version, '\n\t\t\t\t') + .concat(btnGroup, '\n\t\t\t'); + item.name === 'wx_account' ? (prevHTML = renderHTML) : (tbody += renderHTML); + }); + html = + '
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t') + .concat(prevHTML + tbody, '\n\t\t\t\t\t\t\t
Alarm moduleConfigurationVersion' + .concat(lan.public.operate, '
\n\t\t\t\t\t\t
'); + $('#alertConfig').html(html); + this.alertEventBind(ChannelInfo); + return [2]; + } + }); + }); + }; + Config.prototype.renderAlertModelConfigInfo = function (mode) { + var _info = '', + noConfig = 'Unconfigured', + _data = mode.data, + isEmpty = $.isEmptyObject(_data); + if (mode.setup) { + if (mode.name != 'sms' && mode.name != 'wx_account') { + if (!$.isEmptyObject(_data)) { + switch (mode.name) { + case 'mail': + if (_data.receive[0] == '') { + _info = 'No incoming email set'; + } else { + _info = ''.concat(_data.receive.length, ' incoming email has been set up, Click to view'); + } + break; + case 'dingding': + case 'feishu': + case 'weixin': + _info = 'Receiver: ['.concat(isEmpty ? '' : _data.list.default.title, ']'); + break; + case 'tg': + _info = 'Receiver: ['.concat(isEmpty ? '' : _data.my_id, ']'); + break; + } + } else { + _info = noConfig; + } + } else if (mode.name == 'sms') { + _info = '\u5269\u4F59\u53D1\u9001\u544A\u8B66'.concat(_data.count, '\u6B21'); + } else if (mode.name == 'wx_account') { + var boundCheck = '', + res = $.isEmptyObject(_data) ? { is_subscribe: 0, is_bound: 0 } : _data.res; + if (!res.is_subscribe || !res.is_bound) boundCheck = '未订阅公众号或绑定微信'; + if (res.is_subscribe && res.is_bound) boundCheck = '\u5FAE\u4FE1\u8D26\u53F7\u3010'.concat(res.nickname, '\u3011,\u4ECA\u65E5\u5269\u4F59\u53D1\u9001\u6B21\u6570:').concat(res.remaining); + _info = boundCheck; + } + } else { + _info = noConfig; + } + return _info; + }; + Config.prototype.alertEventBind = function (info) { + var _this = this; + $('.receiveMail').click(function () { + return __awaiter(_this, void 0, void 0, function () { + var currentItem; + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + return [4, this.$request('getMessageChannel', { loading: false, msg: false })]; + case 1: + currentItem = _a.sent(); + this.$open({ + title: 'Recipient Email', + area: ['335px', '280px'], + btn: [lan.public.save, lan.public.cancel], + skin: 'alert-receive-view', + content: + '
\n\t\t\t\t
', + success: function () { + var _tips = $('textarea[name=recipient_textarea]'); + var msg = ''; + if (!$.isEmptyObject(currentItem['mail']['data']['receive'])) { + msg = currentItem['mail']['data']['receive'] ? currentItem['mail']['data']['receive'].join('\n') : ''; + } + _tips.html(msg); + if (_tips.val() == '') $('.reci_tips.placeholder').show(); + $('.placeholder').click(function () { + $(this).hide().siblings('textarea').focus(); + }); + _tips.focus(function () { + $('.reci_tips.placeholder').hide(); + }); + _tips.blur(function () { + _tips.val() == '' ? $('.reci_tips.placeholder').show() : $('.reci_tips.placeholder').hide(); + }); + }, + yes: function (config) { + return __awaiter(_this, void 0, void 0, function () { + var close, reci_, rdata; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + close = config.close; + reci_ = $('textarea[name=recipient_textarea]').val(); + return [4, this.$request('setMsgConfigmail', { mails: reci_ })]; + case 1: + rdata = _a.sent(); + rdata.status && close(); + return [2]; + } + }); + }); + }, + }); + return [2]; + } + }); + }); + }); + $('.configEdit').click(function (ev) { + return __awaiter(_this, void 0, void 0, function () { + var _type; + return __generator(this, function (_a) { + _type = $(ev.currentTarget).parents('tr').data('name'); + this.setAlertConfigType(_type); + return [2]; + }); + }); + }); + $('.alertTest').click(function (ev) { + return __awaiter(_this, void 0, void 0, function () { + var _type; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _type = $(ev.currentTarget).parents('tr').data('name'); + return [4, this.$request(['config/get_msg_fun', 'Testing Send, Please wait...'], { fun_name: 'push_data', module_name: _type, msg: 'Testing Send' })]; + case 1: + _a.sent(); + return [2]; + } + }); + }); + }); + $('.replaceWx').click(function () { + _this.setAlertConfigType('wx_account'); + }); + $('.uninstall_alert').click(function (ev) { + return __awaiter(_this, void 0, void 0, function () { + var _type, rdata, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _type = $(ev.currentTarget).parents('tr').data('name'); + return [ + 4, + this.$confirm({ + title: 'Delete ' + info[_type].title + ' module', + msg: 'After deleting the ' + info[_type].title + ' module, it will not be able to send panel alert messages, should I continue?', + }), + ]; + case 1: + _b.sent(); + return [4, this.$request(['config/uninstall_msg_module&name=' + _type, 'Delete ' + info[_type].title + ' alert module'])]; + case 2: + rdata = _b.sent(); + _a = rdata.status; + if (!_a) return [3, 4]; + return [4, this.alertConfigTable()]; + case 3: + _a = _b.sent(); + _b.label = 4; + case 4: + _a; + return [2]; + } + }); + }); + }); + }; + Config.prototype.alertLogsTable = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + $('#alertLog').empty(); + bt_tools.table({ + el: '#alertLog', + load: 'Getting the alarm log list', + url: '/push?action=get_push_logs', + default: 'The alarm log is empty', + dataFilter: function (res) { + return { data: res.data }; + }, + column: [ + { + fid: 'log', + title: 'Title', + type: 'text', + }, + { + fid: 'addtime', + title: 'Time', + type: 'text', + }, + ], + tootls: [ + { + type: 'page', + positon: ['right', 'bottom'], + pageParam: 'p', + page: 1, + numberParam: 'limit', + number: 20, + numberList: [10, 20, 50, 100, 200], + numberStatus: true, + jump: true, + }, + ], + }); + return [2]; + }); + }); + }; + return Config; + })(public_1.default); exports.Config = Config; }); diff --git a/BTPanel/static/amd/utils.min.js b/BTPanel/static/amd/utils.min.js index 0ef85d4e..e2b7d128 100644 --- a/BTPanel/static/amd/utils.min.js +++ b/BTPanel/static/amd/utils.min.js @@ -1,3227 +1,3053 @@ var __createBinding = - (this && this.__createBinding) || - (Object.create - ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - }, - }); - } - : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; - }); + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { + enumerable: true, + get: function () { + return m[k]; + }, + }); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); var __setModuleDefault = - (this && this.__setModuleDefault) || - (Object.create - ? function (o, v) { - Object.defineProperty(o, 'default', {enumerable: true, value: v}); - } - : function (o, v) { - o['default'] = v; - }); + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { enumerable: true, value: v }); + } + : function (o, v) { + o['default'] = v; + }); var __importStar = - (this && this.__importStar) || - function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; - }; + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; + }; var __exportStar = - (this && this.__exportStar) || - function (m, exports) { - for (var p in m) if (p !== 'default' && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); - }; + (this && this.__exportStar) || + function (m, exports) { + for (var p in m) if (p !== 'default' && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); + }; var __awaiter = - (this && this.__awaiter) || - function (thisArg, _arguments, P, generator) { - function adopt(value) { - return value instanceof P - ? value - : new P(function (resolve) { - resolve(value); - }); - } - - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - } - - function rejected(value) { - try { - step(generator['throw'](value)); - } catch (e) { - reject(e); - } - } - - function step(result) { - result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); - } - - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); - }; + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value); + }); + } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator['throw'](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); + }; var __generator = - (this && this.__generator) || - function (thisArg, body) { - var _ = { - label: 0, - sent: function () { - if (t[0] & 1) throw t[1]; - return t[1]; - }, - trys: [], - ops: [], - }, - f, - y, - t, - g; - return ( - (g = {next: verb(0), throw: verb(1), return: verb(2)}), - typeof Symbol === 'function' && - (g[Symbol.iterator] = function () { - return this; - }), - g - ); - - function verb(n) { - return function (v) { - return step([n, v]); - }; - } - - function step(op) { - if (f) throw new TypeError('Generator is already executing.'); - while (_) - try { - if (((f = 1), y && (t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)) return t; - if (((y = 0), t)) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: - case 1: - t = op; - break; - case 4: - _.label++; - return {value: op[1], done: false}; - case 5: - _.label++; - y = op[1]; - op = [0]; - continue; - case 7: - op = _.ops.pop(); - _.trys.pop(); - continue; - default: - if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) { - _ = 0; - continue; - } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { - _.label = op[1]; - break; - } - if (op[0] === 6 && _.label < t[1]) { - _.label = t[1]; - t = op; - break; - } - if (t && _.label < t[2]) { - _.label = t[2]; - _.ops.push(op); - break; - } - if (t[2]) _.ops.pop(); - _.trys.pop(); - continue; - } - op = body.call(thisArg, _); - } catch (e) { - op = [6, e]; - y = 0; - } finally { - f = t = 0; - } - if (op[0] & 5) throw op[1]; - return {value: op[0] ? op[1] : void 0, done: true}; - } - }; + (this && this.__generator) || + function (thisArg, body) { + var _ = { + label: 0, + sent: function () { + if (t[0] & 1) throw t[1]; + return t[1]; + }, + trys: [], + ops: [], + }, + f, + y, + t, + g; + return ( + (g = { next: verb(0), throw: verb(1), return: verb(2) }), + typeof Symbol === 'function' && + (g[Symbol.iterator] = function () { + return this; + }), + g + ); + function verb(n) { + return function (v) { + return step([n, v]); + }; + } + function step(op) { + if (f) throw new TypeError('Generator is already executing.'); + while (_) + try { + if (((f = 1), y && (t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done)) return t; + if (((y = 0), t)) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: + case 1: + t = op; + break; + case 4: + _.label++; + return { value: op[1], done: false }; + case 5: + _.label++; + y = op[1]; + op = [0]; + continue; + case 7: + op = _.ops.pop(); + _.trys.pop(); + continue; + default: + if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) { + _ = 0; + continue; + } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { + _.label = op[1]; + break; + } + if (op[0] === 6 && _.label < t[1]) { + _.label = t[1]; + t = op; + break; + } + if (t && _.label < t[2]) { + _.label = t[2]; + _.ops.push(op); + break; + } + if (t[2]) _.ops.pop(); + _.trys.pop(); + continue; + } + op = body.call(thisArg, _); + } catch (e) { + op = [6, e]; + y = 0; + } finally { + f = t = 0; + } + if (op[0] & 5) throw op[1]; + return { value: op[0] ? op[1] : void 0, done: true }; + } + }; var __importDefault = - (this && this.__importDefault) || - function (mod) { - return mod && mod.__esModule ? mod : {default: mod}; - }; + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; var __extends = - (this && this.__extends) || - (function () { - var extendStatics = function (d, b) { - extendStatics = - Object.setPrototypeOf || - ({__proto__: []} instanceof Array && - function (d, b) { - d.__proto__ = b; - }) || - function (d, b) { - for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; - }; - return extendStatics(d, b); - }; - return function (d, b) { - if (typeof b !== 'function' && b !== null) throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); - extendStatics(d, b); - - function __() { - this.constructor = d; - } - - d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __()); - }; - })(); + (this && this.__extends) || + (function () { + var extendStatics = function (d, b) { + extendStatics = + Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && + function (d, b) { + d.__proto__ = b; + }) || + function (d, b) { + for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; + }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== 'function' && b !== null) throw new TypeError('Class extends value ' + String(b) + ' is not a constructor or null'); + extendStatics(d, b); + function __() { + this.constructor = d; + } + d.prototype = b === null ? Object.create(b) : ((__.prototype = b.prototype), new __()); + }; + })(); define('h', ['require', 'exports', './vnode', './is'], function (require, exports, vnode_1, is) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.h = void 0; - is = __importStar(is); - - function addNS(data, children, sel) { - data.ns = 'http://www.w3.org/2000/svg'; - if (sel !== 'foreignObject' && children !== undefined) { - for (var i = 0; i < children.length; ++i) { - var childData = children[i].data; - if (childData !== undefined) { - addNS(childData, children[i].children, children[i].sel); - } - } - } - } - - function h(sel, b, c) { - var data = {}; - var children; - var text; - var i; - if (c !== undefined) { - if (b !== null) { - data = b; - } - if (is.array(c)) { - children = c; - } else if (is.primitive(c)) { - text = c.toString(); - } else if (c && c.sel) { - children = [c]; - } - } else if (b !== undefined && b !== null) { - if (is.array(b)) { - children = b; - } else if (is.primitive(b)) { - text = b.toString(); - } else if (b && b.sel) { - children = [b]; - } else { - data = b; - } - } - if (children !== undefined) { - for (i = 0; i < children.length; ++i) { - if (is.primitive(children[i])) children[i] = (0, vnode_1.vnode)(undefined, undefined, undefined, children[i], undefined); - } - } - if (sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' && (sel.length === 3 || sel[3] === '.' || sel[3] === '#')) { - addNS(data, children, sel); - } - return (0, vnode_1.vnode)(sel, data, children, text, undefined); - } - - exports.h = h; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.h = void 0; + is = __importStar(is); + function addNS(data, children, sel) { + data.ns = 'http://www.w3.org/2000/svg'; + if (sel !== 'foreignObject' && children !== undefined) { + for (var i = 0; i < children.length; ++i) { + var childData = children[i].data; + if (childData !== undefined) { + addNS(childData, children[i].children, children[i].sel); + } + } + } + } + function h(sel, b, c) { + var data = {}; + var children; + var text; + var i; + if (c !== undefined) { + if (b !== null) { + data = b; + } + if (is.array(c)) { + children = c; + } else if (is.primitive(c)) { + text = c.toString(); + } else if (c && c.sel) { + children = [c]; + } + } else if (b !== undefined && b !== null) { + if (is.array(b)) { + children = b; + } else if (is.primitive(b)) { + text = b.toString(); + } else if (b && b.sel) { + children = [b]; + } else { + data = b; + } + } + if (children !== undefined) { + for (i = 0; i < children.length; ++i) { + if (is.primitive(children[i])) children[i] = (0, vnode_1.vnode)(undefined, undefined, undefined, children[i], undefined); + } + } + if (sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' && (sel.length === 3 || sel[3] === '.' || sel[3] === '#')) { + addNS(data, children, sel); + } + return (0, vnode_1.vnode)(sel, data, children, text, undefined); + } + exports.h = h; }); define('hooks', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); }); define('htmldomapi', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.htmlDomApi = void 0; - - function createElement(tagName, options) { - return document.createElement(tagName, options); - } - - function createElementNS(namespaceURI, qualifiedName, options) { - return document.createElementNS(namespaceURI, qualifiedName, options); - } - - function createTextNode(text) { - return document.createTextNode(text); - } - - function createComment(text) { - return document.createComment(text); - } - - function insertBefore(parentNode, newNode, referenceNode) { - parentNode.insertBefore(newNode, referenceNode); - } - - function removeChild(node, child) { - node.removeChild(child); - } - - function appendChild(node, child) { - node.appendChild(child); - } - - function parentNode(node) { - return node.parentNode; - } - - function nextSibling(node) { - return node.nextSibling; - } - - function tagName(elm) { - return elm.tagName; - } - - function setTextContent(node, text) { - node.textContent = text; - } - - function getTextContent(node) { - return node.textContent; - } - - function isElement(node) { - return node.nodeType === 1; - } - - function isText(node) { - return node.nodeType === 3; - } - - function isComment(node) { - return node.nodeType === 8; - } - - exports.htmlDomApi = { - createElement: createElement, - createElementNS: createElementNS, - createTextNode: createTextNode, - createComment: createComment, - insertBefore: insertBefore, - removeChild: removeChild, - appendChild: appendChild, - parentNode: parentNode, - nextSibling: nextSibling, - tagName: tagName, - setTextContent: setTextContent, - getTextContent: getTextContent, - isElement: isElement, - isText: isText, - isComment: isComment, - }; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.htmlDomApi = void 0; + function createElement(tagName, options) { + return document.createElement(tagName, options); + } + function createElementNS(namespaceURI, qualifiedName, options) { + return document.createElementNS(namespaceURI, qualifiedName, options); + } + function createTextNode(text) { + return document.createTextNode(text); + } + function createComment(text) { + return document.createComment(text); + } + function insertBefore(parentNode, newNode, referenceNode) { + parentNode.insertBefore(newNode, referenceNode); + } + function removeChild(node, child) { + node.removeChild(child); + } + function appendChild(node, child) { + node.appendChild(child); + } + function parentNode(node) { + return node.parentNode; + } + function nextSibling(node) { + return node.nextSibling; + } + function tagName(elm) { + return elm.tagName; + } + function setTextContent(node, text) { + node.textContent = text; + } + function getTextContent(node) { + return node.textContent; + } + function isElement(node) { + return node.nodeType === 1; + } + function isText(node) { + return node.nodeType === 3; + } + function isComment(node) { + return node.nodeType === 8; + } + exports.htmlDomApi = { + createElement: createElement, + createElementNS: createElementNS, + createTextNode: createTextNode, + createComment: createComment, + insertBefore: insertBefore, + removeChild: removeChild, + appendChild: appendChild, + parentNode: parentNode, + nextSibling: nextSibling, + tagName: tagName, + setTextContent: setTextContent, + getTextContent: getTextContent, + isElement: isElement, + isText: isText, + isComment: isComment, + }; }); define('init', ['require', 'exports', './vnode', './is', './htmldomapi'], function (require, exports, vnode_2, is, htmldomapi_1) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.init = void 0; - is = __importStar(is); - - function isUndef(s) { - return s === undefined; - } - - function isDef(s) { - return s !== undefined; - } - - var emptyNode = (0, vnode_2.vnode)('', {}, [], undefined, undefined); - - function sameVnode(vnode1, vnode2) { - var _a, _b; - var isSameKey = vnode1.key === vnode2.key; - var isSameIs = ((_a = vnode1.data) === null || _a === void 0 ? void 0 : _a.is) === ((_b = vnode2.data) === null || _b === void 0 ? void 0 : _b.is); - var isSameSel = vnode1.sel === vnode2.sel; - return isSameSel && isSameKey && isSameIs; - } - - function isVnode(vnode) { - return vnode.sel !== undefined; - } - - function createKeyToOldIdx(children, beginIdx, endIdx) { - var _a; - var map = {}; - for (var i = beginIdx; i <= endIdx; ++i) { - var key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key; - if (key !== undefined) { - map[key] = i; - } - } - return map; - } - - var hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post']; - - function init(modules, domApi) { - var cbs = { - create: [], - update: [], - remove: [], - destroy: [], - pre: [], - post: [], - }; - var api = domApi !== undefined ? domApi : htmldomapi_1.htmlDomApi; - for (var _i = 0, hooks_1 = hooks; _i < hooks_1.length; _i++) { - var hook = hooks_1[_i]; - for (var _f = 0, modules_1 = modules; _f < modules_1.length; _f++) { - var module_1 = modules_1[_f]; - var currentHook = module_1[hook]; - if (currentHook !== undefined) { - cbs[hook].push(currentHook); - } - } - } - - function emptyNodeAt(elm) { - var id = elm.id ? '#' + elm.id : ''; - var classes = elm.getAttribute('class'); - var c = classes ? '.' + classes.split(' ').join('.') : ''; - return (0, vnode_2.vnode)(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm); - } - - function createRmCb(childElm, listeners) { - return function rmCb() { - if (--listeners === 0) { - var parent_1 = api.parentNode(childElm); - api.removeChild(parent_1, childElm); - } - }; - } - - function createElm(vnode, insertedVnodeQueue) { - var _a, _b; - var i; - var data = vnode.data; - if (data !== undefined) { - var init_1 = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init; - if (isDef(init_1)) { - init_1(vnode); - data = vnode.data; - } - } - var children = vnode.children; - var sel = vnode.sel; - if (sel === '!') { - if (isUndef(vnode.text)) { - vnode.text = ''; - } - vnode.elm = api.createComment(vnode.text); - } else if (sel !== undefined) { - var hashIdx = sel.indexOf('#'); - var dotIdx = sel.indexOf('.', hashIdx); - var hash = hashIdx > 0 ? hashIdx : sel.length; - var dot = dotIdx > 0 ? dotIdx : sel.length; - var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; - var elm = (vnode.elm = isDef(data) && isDef((i = data.ns)) ? api.createElementNS(i, tag, data) : api.createElement(tag, data)); - if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot)); - if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' ')); - for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); - if (is.array(children)) { - for (i = 0; i < children.length; ++i) { - var ch = children[i]; - if (ch != null) { - api.appendChild(elm, createElm(ch, insertedVnodeQueue)); - } - } - } else if (is.primitive(vnode.text)) { - api.appendChild(elm, api.createTextNode(vnode.text)); - } - var hook = vnode.data.hook; - if (isDef(hook)) { - (_b = hook.create) === null || _b === void 0 ? void 0 : _b.call(hook, emptyNode, vnode); - if (hook.insert) { - insertedVnodeQueue.push(vnode); - } - } - } else { - vnode.elm = api.createTextNode(vnode.text); - } - return vnode.elm; - } - - function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { - for (; startIdx <= endIdx; ++startIdx) { - var ch = vnodes[startIdx]; - if (ch != null) { - api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before); - } - } - } - - function invokeDestroyHook(vnode) { - var _a, _b; - var data = vnode.data; - if (data !== undefined) { - (_b = (_a = data === null || data === void 0 ? void 0 : data.hook) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a, vnode); - for (var i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); - if (vnode.children !== undefined) { - for (var j = 0; j < vnode.children.length; ++j) { - var child = vnode.children[j]; - if (child != null && typeof child !== 'string') { - invokeDestroyHook(child); - } - } - } - } - } - - function removeVnodes(parentElm, vnodes, startIdx, endIdx) { - var _a, _b; - for (; startIdx <= endIdx; ++startIdx) { - var listeners = void 0; - var rm = void 0; - var ch = vnodes[startIdx]; - if (ch != null) { - if (isDef(ch.sel)) { - invokeDestroyHook(ch); - listeners = cbs.remove.length + 1; - rm = createRmCb(ch.elm, listeners); - for (var i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); - var removeHook = (_b = (_a = ch === null || ch === void 0 ? void 0 : ch.data) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.remove; - if (isDef(removeHook)) { - removeHook(ch, rm); - } else { - rm(); - } - } else { - api.removeChild(parentElm, ch.elm); - } - } - } - } - - function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) { - var oldStartIdx = 0; - var newStartIdx = 0; - var oldEndIdx = oldCh.length - 1; - var oldStartVnode = oldCh[0]; - var oldEndVnode = oldCh[oldEndIdx]; - var newEndIdx = newCh.length - 1; - var newStartVnode = newCh[0]; - var newEndVnode = newCh[newEndIdx]; - var oldKeyToIdx; - var idxInOld; - var elmToMove; - var before; - while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { - if (oldStartVnode == null) { - oldStartVnode = oldCh[++oldStartIdx]; - } else if (oldEndVnode == null) { - oldEndVnode = oldCh[--oldEndIdx]; - } else if (newStartVnode == null) { - newStartVnode = newCh[++newStartIdx]; - } else if (newEndVnode == null) { - newEndVnode = newCh[--newEndIdx]; - } else if (sameVnode(oldStartVnode, newStartVnode)) { - patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); - oldStartVnode = oldCh[++oldStartIdx]; - newStartVnode = newCh[++newStartIdx]; - } else if (sameVnode(oldEndVnode, newEndVnode)) { - patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); - oldEndVnode = oldCh[--oldEndIdx]; - newEndVnode = newCh[--newEndIdx]; - } else if (sameVnode(oldStartVnode, newEndVnode)) { - patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); - api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm)); - oldStartVnode = oldCh[++oldStartIdx]; - newEndVnode = newCh[--newEndIdx]; - } else if (sameVnode(oldEndVnode, newStartVnode)) { - patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); - api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); - oldEndVnode = oldCh[--oldEndIdx]; - newStartVnode = newCh[++newStartIdx]; - } else { - if (oldKeyToIdx === undefined) { - oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); - } - idxInOld = oldKeyToIdx[newStartVnode.key]; - if (isUndef(idxInOld)) { - api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); - } else { - elmToMove = oldCh[idxInOld]; - if (elmToMove.sel !== newStartVnode.sel) { - api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); - } else { - patchVnode(elmToMove, newStartVnode, insertedVnodeQueue); - oldCh[idxInOld] = undefined; - api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm); - } - } - newStartVnode = newCh[++newStartIdx]; - } - } - if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) { - if (oldStartIdx > oldEndIdx) { - before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; - addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); - } else { - removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); - } - } - } - - function patchVnode(oldVnode, vnode, insertedVnodeQueue) { - var _a, _b, _c, _d, _e; - var hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook; - (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode); - var elm = (vnode.elm = oldVnode.elm); - var oldCh = oldVnode.children; - var ch = vnode.children; - if (oldVnode === vnode) return; - if (vnode.data !== undefined) { - for (var i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode); - (_d = (_c = vnode.data.hook) === null || _c === void 0 ? void 0 : _c.update) === null || _d === void 0 ? void 0 : _d.call(_c, oldVnode, vnode); - } - if (isUndef(vnode.text)) { - if (isDef(oldCh) && isDef(ch)) { - if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue); - } else if (isDef(ch)) { - if (isDef(oldVnode.text)) api.setTextContent(elm, ''); - addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); - } else if (isDef(oldCh)) { - removeVnodes(elm, oldCh, 0, oldCh.length - 1); - } else if (isDef(oldVnode.text)) { - api.setTextContent(elm, ''); - } - } else if (oldVnode.text !== vnode.text) { - if (isDef(oldCh)) { - removeVnodes(elm, oldCh, 0, oldCh.length - 1); - } - api.setTextContent(elm, vnode.text); - } - (_e = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _e === void 0 ? void 0 : _e.call(hook, oldVnode, vnode); - } - - return function patch(oldVnode, vnode) { - var i, elm, parent; - var insertedVnodeQueue = []; - for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); - if (!isVnode(oldVnode)) { - oldVnode = emptyNodeAt(oldVnode); - } - if (sameVnode(oldVnode, vnode)) { - patchVnode(oldVnode, vnode, insertedVnodeQueue); - } else { - elm = oldVnode.elm; - parent = api.parentNode(elm); - createElm(vnode, insertedVnodeQueue); - if (parent !== null) { - api.insertBefore(parent, vnode.elm, api.nextSibling(elm)); - removeVnodes(parent, [oldVnode], 0, 0); - } - } - for (i = 0; i < insertedVnodeQueue.length; ++i) { - insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]); - } - for (i = 0; i < cbs.post.length; ++i) cbs.post[i](); - return vnode; - }; - } - - exports.init = init; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.init = void 0; + is = __importStar(is); + function isUndef(s) { + return s === undefined; + } + function isDef(s) { + return s !== undefined; + } + var emptyNode = (0, vnode_2.vnode)('', {}, [], undefined, undefined); + function sameVnode(vnode1, vnode2) { + var _a, _b; + var isSameKey = vnode1.key === vnode2.key; + var isSameIs = ((_a = vnode1.data) === null || _a === void 0 ? void 0 : _a.is) === ((_b = vnode2.data) === null || _b === void 0 ? void 0 : _b.is); + var isSameSel = vnode1.sel === vnode2.sel; + return isSameSel && isSameKey && isSameIs; + } + function isVnode(vnode) { + return vnode.sel !== undefined; + } + function createKeyToOldIdx(children, beginIdx, endIdx) { + var _a; + var map = {}; + for (var i = beginIdx; i <= endIdx; ++i) { + var key = (_a = children[i]) === null || _a === void 0 ? void 0 : _a.key; + if (key !== undefined) { + map[key] = i; + } + } + return map; + } + var hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post']; + function init(modules, domApi) { + var cbs = { + create: [], + update: [], + remove: [], + destroy: [], + pre: [], + post: [], + }; + var api = domApi !== undefined ? domApi : htmldomapi_1.htmlDomApi; + for (var _i = 0, hooks_1 = hooks; _i < hooks_1.length; _i++) { + var hook = hooks_1[_i]; + for (var _f = 0, modules_1 = modules; _f < modules_1.length; _f++) { + var module_1 = modules_1[_f]; + var currentHook = module_1[hook]; + if (currentHook !== undefined) { + cbs[hook].push(currentHook); + } + } + } + function emptyNodeAt(elm) { + var id = elm.id ? '#' + elm.id : ''; + var classes = elm.getAttribute('class'); + var c = classes ? '.' + classes.split(' ').join('.') : ''; + return (0, vnode_2.vnode)(api.tagName(elm).toLowerCase() + id + c, {}, [], undefined, elm); + } + function createRmCb(childElm, listeners) { + return function rmCb() { + if (--listeners === 0) { + var parent_1 = api.parentNode(childElm); + api.removeChild(parent_1, childElm); + } + }; + } + function createElm(vnode, insertedVnodeQueue) { + var _a, _b; + var i; + var data = vnode.data; + if (data !== undefined) { + var init_1 = (_a = data.hook) === null || _a === void 0 ? void 0 : _a.init; + if (isDef(init_1)) { + init_1(vnode); + data = vnode.data; + } + } + var children = vnode.children; + var sel = vnode.sel; + if (sel === '!') { + if (isUndef(vnode.text)) { + vnode.text = ''; + } + vnode.elm = api.createComment(vnode.text); + } else if (sel !== undefined) { + var hashIdx = sel.indexOf('#'); + var dotIdx = sel.indexOf('.', hashIdx); + var hash = hashIdx > 0 ? hashIdx : sel.length; + var dot = dotIdx > 0 ? dotIdx : sel.length; + var tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; + var elm = (vnode.elm = isDef(data) && isDef((i = data.ns)) ? api.createElementNS(i, tag, data) : api.createElement(tag, data)); + if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot)); + if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' ')); + for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode); + if (is.array(children)) { + for (i = 0; i < children.length; ++i) { + var ch = children[i]; + if (ch != null) { + api.appendChild(elm, createElm(ch, insertedVnodeQueue)); + } + } + } else if (is.primitive(vnode.text)) { + api.appendChild(elm, api.createTextNode(vnode.text)); + } + var hook = vnode.data.hook; + if (isDef(hook)) { + (_b = hook.create) === null || _b === void 0 ? void 0 : _b.call(hook, emptyNode, vnode); + if (hook.insert) { + insertedVnodeQueue.push(vnode); + } + } + } else { + vnode.elm = api.createTextNode(vnode.text); + } + return vnode.elm; + } + function addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) { + for (; startIdx <= endIdx; ++startIdx) { + var ch = vnodes[startIdx]; + if (ch != null) { + api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before); + } + } + } + function invokeDestroyHook(vnode) { + var _a, _b; + var data = vnode.data; + if (data !== undefined) { + (_b = (_a = data === null || data === void 0 ? void 0 : data.hook) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a, vnode); + for (var i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode); + if (vnode.children !== undefined) { + for (var j = 0; j < vnode.children.length; ++j) { + var child = vnode.children[j]; + if (child != null && typeof child !== 'string') { + invokeDestroyHook(child); + } + } + } + } + } + function removeVnodes(parentElm, vnodes, startIdx, endIdx) { + var _a, _b; + for (; startIdx <= endIdx; ++startIdx) { + var listeners = void 0; + var rm = void 0; + var ch = vnodes[startIdx]; + if (ch != null) { + if (isDef(ch.sel)) { + invokeDestroyHook(ch); + listeners = cbs.remove.length + 1; + rm = createRmCb(ch.elm, listeners); + for (var i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm); + var removeHook = (_b = (_a = ch === null || ch === void 0 ? void 0 : ch.data) === null || _a === void 0 ? void 0 : _a.hook) === null || _b === void 0 ? void 0 : _b.remove; + if (isDef(removeHook)) { + removeHook(ch, rm); + } else { + rm(); + } + } else { + api.removeChild(parentElm, ch.elm); + } + } + } + } + function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) { + var oldStartIdx = 0; + var newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx; + var idxInOld; + var elmToMove; + var before; + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (oldStartVnode == null) { + oldStartVnode = oldCh[++oldStartIdx]; + } else if (oldEndVnode == null) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (newStartVnode == null) { + newStartVnode = newCh[++newStartIdx]; + } else if (newEndVnode == null) { + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode)) { + patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode)) { + patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { + patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); + api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm)); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { + patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); + api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (oldKeyToIdx === undefined) { + oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); + } + idxInOld = oldKeyToIdx[newStartVnode.key]; + if (isUndef(idxInOld)) { + api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); + } else { + elmToMove = oldCh[idxInOld]; + if (elmToMove.sel !== newStartVnode.sel) { + api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm); + } else { + patchVnode(elmToMove, newStartVnode, insertedVnodeQueue); + oldCh[idxInOld] = undefined; + api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm); + } + } + newStartVnode = newCh[++newStartIdx]; + } + } + if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) { + if (oldStartIdx > oldEndIdx) { + before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm; + addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); + } else { + removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); + } + } + } + function patchVnode(oldVnode, vnode, insertedVnodeQueue) { + var _a, _b, _c, _d, _e; + var hook = (_a = vnode.data) === null || _a === void 0 ? void 0 : _a.hook; + (_b = hook === null || hook === void 0 ? void 0 : hook.prepatch) === null || _b === void 0 ? void 0 : _b.call(hook, oldVnode, vnode); + var elm = (vnode.elm = oldVnode.elm); + var oldCh = oldVnode.children; + var ch = vnode.children; + if (oldVnode === vnode) return; + if (vnode.data !== undefined) { + for (var i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode); + (_d = (_c = vnode.data.hook) === null || _c === void 0 ? void 0 : _c.update) === null || _d === void 0 ? void 0 : _d.call(_c, oldVnode, vnode); + } + if (isUndef(vnode.text)) { + if (isDef(oldCh) && isDef(ch)) { + if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue); + } else if (isDef(ch)) { + if (isDef(oldVnode.text)) api.setTextContent(elm, ''); + addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); + } else if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1); + } else if (isDef(oldVnode.text)) { + api.setTextContent(elm, ''); + } + } else if (oldVnode.text !== vnode.text) { + if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1); + } + api.setTextContent(elm, vnode.text); + } + (_e = hook === null || hook === void 0 ? void 0 : hook.postpatch) === null || _e === void 0 ? void 0 : _e.call(hook, oldVnode, vnode); + } + return function patch(oldVnode, vnode) { + var i, elm, parent; + var insertedVnodeQueue = []; + for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); + if (!isVnode(oldVnode)) { + oldVnode = emptyNodeAt(oldVnode); + } + if (sameVnode(oldVnode, vnode)) { + patchVnode(oldVnode, vnode, insertedVnodeQueue); + } else { + elm = oldVnode.elm; + parent = api.parentNode(elm); + createElm(vnode, insertedVnodeQueue); + if (parent !== null) { + api.insertBefore(parent, vnode.elm, api.nextSibling(elm)); + removeVnodes(parent, [oldVnode], 0, 0); + } + } + for (i = 0; i < insertedVnodeQueue.length; ++i) { + insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i]); + } + for (i = 0; i < cbs.post.length; ++i) cbs.post[i](); + return vnode; + }; + } + exports.init = init; }); define('is', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.primitive = exports.array = void 0; - exports.array = Array.isArray; - - function primitive(s) { - return typeof s === 'string' || typeof s === 'number' || s instanceof String || s instanceof Number; - } - - exports.primitive = primitive; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.primitive = exports.array = void 0; + exports.array = Array.isArray; + function primitive(s) { + return typeof s === 'string' || typeof s === 'number' || s instanceof String || s instanceof Number; + } + exports.primitive = primitive; }); define('jsx', ['require', 'exports', './vnode', './h'], function (require, exports, vnode_3, h_1) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.jsx = void 0; - - function flattenAndFilter(children, flattened) { - for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { - var child = children_1[_i]; - if (child !== undefined && child !== null && child !== false && child !== '') { - if (Array.isArray(child)) { - flattenAndFilter(child, flattened); - } else if (typeof child === 'string' || typeof child === 'number' || typeof child === 'boolean') { - flattened.push((0, vnode_3.vnode)(undefined, undefined, undefined, String(child), undefined)); - } else { - flattened.push(child); - } - } - } - return flattened; - } - - function jsx(tag, data) { - var children = []; - for (var _i = 2; _i < arguments.length; _i++) { - children[_i - 2] = arguments[_i]; - } - var flatChildren = flattenAndFilter(children, []); - if (typeof tag === 'function') { - return tag(data, flatChildren); - } else { - if (flatChildren.length === 1 && !flatChildren[0].sel && flatChildren[0].text) { - return (0, h_1.h)(tag, data, flatChildren[0].text); - } else { - return (0, h_1.h)(tag, data, flatChildren); - } - } - } - - exports.jsx = jsx; - (function (jsx) { - })(jsx || (exports.jsx = jsx = {})); + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.jsx = void 0; + function flattenAndFilter(children, flattened) { + for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { + var child = children_1[_i]; + if (child !== undefined && child !== null && child !== false && child !== '') { + if (Array.isArray(child)) { + flattenAndFilter(child, flattened); + } else if (typeof child === 'string' || typeof child === 'number' || typeof child === 'boolean') { + flattened.push((0, vnode_3.vnode)(undefined, undefined, undefined, String(child), undefined)); + } else { + flattened.push(child); + } + } + } + return flattened; + } + function jsx(tag, data) { + var children = []; + for (var _i = 2; _i < arguments.length; _i++) { + children[_i - 2] = arguments[_i]; + } + var flatChildren = flattenAndFilter(children, []); + if (typeof tag === 'function') { + return tag(data, flatChildren); + } else { + if (flatChildren.length === 1 && !flatChildren[0].sel && flatChildren[0].text) { + return (0, h_1.h)(tag, data, flatChildren[0].text); + } else { + return (0, h_1.h)(tag, data, flatChildren); + } + } + } + exports.jsx = jsx; + (function (jsx) {})(jsx || (exports.jsx = jsx = {})); }); define('snabbdom', [ - 'require', - 'exports', - './htmldomapi', - './init', - './thunk', - './vnode', - './helpers/attachto', - './is', - './tovnode', - './h', - './hooks', - './modules/attributes', - './modules/class', - './modules/dataset', - './modules/eventlisteners', - './modules/props', - './modules/style', - './jsx', + 'require', + 'exports', + './htmldomapi', + './init', + './thunk', + './vnode', + './helpers/attachto', + './is', + './tovnode', + './h', + './hooks', + './modules/attributes', + './modules/class', + './modules/dataset', + './modules/eventlisteners', + './modules/props', + './modules/style', + './jsx', ], function (require, exports, htmldomapi_2, init_2, thunk_1, vnode_4, attachto_1, is_1, tovnode_1, h_2, hooks_2, attributes_1, class_1, dataset_1, eventlisteners_1, props_1, style_1, jsx_1) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.jsx = - exports.styleModule = - exports.propsModule = - exports.eventListenersModule = - exports.datasetModule = - exports.classModule = - exports.attributesModule = - exports.h = - exports.toVNode = - exports.primitive = - exports.array = - exports.attachTo = - exports.vnode = - exports.thunk = - exports.init = - exports.htmlDomApi = - void 0; - Object.defineProperty(exports, 'htmlDomApi', { - enumerable: true, - get: function () { - return htmldomapi_2.htmlDomApi; - }, - }); - Object.defineProperty(exports, 'init', { - enumerable: true, - get: function () { - return init_2.init; - }, - }); - Object.defineProperty(exports, 'thunk', { - enumerable: true, - get: function () { - return thunk_1.thunk; - }, - }); - Object.defineProperty(exports, 'vnode', { - enumerable: true, - get: function () { - return vnode_4.vnode; - }, - }); - Object.defineProperty(exports, 'attachTo', { - enumerable: true, - get: function () { - return attachto_1.attachTo; - }, - }); - Object.defineProperty(exports, 'array', { - enumerable: true, - get: function () { - return is_1.array; - }, - }); - Object.defineProperty(exports, 'primitive', { - enumerable: true, - get: function () { - return is_1.primitive; - }, - }); - Object.defineProperty(exports, 'toVNode', { - enumerable: true, - get: function () { - return tovnode_1.toVNode; - }, - }); - Object.defineProperty(exports, 'h', { - enumerable: true, - get: function () { - return h_2.h; - }, - }); - __exportStar(hooks_2, exports); - Object.defineProperty(exports, 'attributesModule', { - enumerable: true, - get: function () { - return attributes_1.attributesModule; - }, - }); - Object.defineProperty(exports, 'classModule', { - enumerable: true, - get: function () { - return class_1.classModule; - }, - }); - Object.defineProperty(exports, 'datasetModule', { - enumerable: true, - get: function () { - return dataset_1.datasetModule; - }, - }); - Object.defineProperty(exports, 'eventListenersModule', { - enumerable: true, - get: function () { - return eventlisteners_1.eventListenersModule; - }, - }); - Object.defineProperty(exports, 'propsModule', { - enumerable: true, - get: function () { - return props_1.propsModule; - }, - }); - Object.defineProperty(exports, 'styleModule', { - enumerable: true, - get: function () { - return style_1.styleModule; - }, - }); - Object.defineProperty(exports, 'jsx', { - enumerable: true, - get: function () { - return jsx_1.jsx; - }, - }); + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.jsx = + exports.styleModule = + exports.propsModule = + exports.eventListenersModule = + exports.datasetModule = + exports.classModule = + exports.attributesModule = + exports.h = + exports.toVNode = + exports.primitive = + exports.array = + exports.attachTo = + exports.vnode = + exports.thunk = + exports.init = + exports.htmlDomApi = + void 0; + Object.defineProperty(exports, 'htmlDomApi', { + enumerable: true, + get: function () { + return htmldomapi_2.htmlDomApi; + }, + }); + Object.defineProperty(exports, 'init', { + enumerable: true, + get: function () { + return init_2.init; + }, + }); + Object.defineProperty(exports, 'thunk', { + enumerable: true, + get: function () { + return thunk_1.thunk; + }, + }); + Object.defineProperty(exports, 'vnode', { + enumerable: true, + get: function () { + return vnode_4.vnode; + }, + }); + Object.defineProperty(exports, 'attachTo', { + enumerable: true, + get: function () { + return attachto_1.attachTo; + }, + }); + Object.defineProperty(exports, 'array', { + enumerable: true, + get: function () { + return is_1.array; + }, + }); + Object.defineProperty(exports, 'primitive', { + enumerable: true, + get: function () { + return is_1.primitive; + }, + }); + Object.defineProperty(exports, 'toVNode', { + enumerable: true, + get: function () { + return tovnode_1.toVNode; + }, + }); + Object.defineProperty(exports, 'h', { + enumerable: true, + get: function () { + return h_2.h; + }, + }); + __exportStar(hooks_2, exports); + Object.defineProperty(exports, 'attributesModule', { + enumerable: true, + get: function () { + return attributes_1.attributesModule; + }, + }); + Object.defineProperty(exports, 'classModule', { + enumerable: true, + get: function () { + return class_1.classModule; + }, + }); + Object.defineProperty(exports, 'datasetModule', { + enumerable: true, + get: function () { + return dataset_1.datasetModule; + }, + }); + Object.defineProperty(exports, 'eventListenersModule', { + enumerable: true, + get: function () { + return eventlisteners_1.eventListenersModule; + }, + }); + Object.defineProperty(exports, 'propsModule', { + enumerable: true, + get: function () { + return props_1.propsModule; + }, + }); + Object.defineProperty(exports, 'styleModule', { + enumerable: true, + get: function () { + return style_1.styleModule; + }, + }); + Object.defineProperty(exports, 'jsx', { + enumerable: true, + get: function () { + return jsx_1.jsx; + }, + }); }); define('thunk', ['require', 'exports', './h'], function (require, exports, h_3) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.thunk = void 0; - - function copyToThunk(vnode, thunk) { - vnode.data.fn = thunk.data.fn; - vnode.data.args = thunk.data.args; - thunk.data = vnode.data; - thunk.children = vnode.children; - thunk.text = vnode.text; - thunk.elm = vnode.elm; - } - - function init(thunk) { - var cur = thunk.data; - var vnode = cur.fn.apply(cur, cur.args); - copyToThunk(vnode, thunk); - } - - function prepatch(oldVnode, thunk) { - var i; - var old = oldVnode.data; - var cur = thunk.data; - var oldArgs = old.args; - var args = cur.args; - if (old.fn !== cur.fn || oldArgs.length !== args.length) { - copyToThunk(cur.fn.apply(cur, args), thunk); - return; - } - for (i = 0; i < args.length; ++i) { - if (oldArgs[i] !== args[i]) { - copyToThunk(cur.fn.apply(cur, args), thunk); - return; - } - } - copyToThunk(oldVnode, thunk); - } - - var thunk = function thunk(sel, key, fn, args) { - if (args === undefined) { - args = fn; - fn = key; - key = undefined; - } - return (0, h_3.h)(sel, { - key: key, - hook: {init: init, prepatch: prepatch}, - fn: fn, - args: args, - }); - }; - exports.thunk = thunk; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.thunk = void 0; + function copyToThunk(vnode, thunk) { + vnode.data.fn = thunk.data.fn; + vnode.data.args = thunk.data.args; + thunk.data = vnode.data; + thunk.children = vnode.children; + thunk.text = vnode.text; + thunk.elm = vnode.elm; + } + function init(thunk) { + var cur = thunk.data; + var vnode = cur.fn.apply(cur, cur.args); + copyToThunk(vnode, thunk); + } + function prepatch(oldVnode, thunk) { + var i; + var old = oldVnode.data; + var cur = thunk.data; + var oldArgs = old.args; + var args = cur.args; + if (old.fn !== cur.fn || oldArgs.length !== args.length) { + copyToThunk(cur.fn.apply(cur, args), thunk); + return; + } + for (i = 0; i < args.length; ++i) { + if (oldArgs[i] !== args[i]) { + copyToThunk(cur.fn.apply(cur, args), thunk); + return; + } + } + copyToThunk(oldVnode, thunk); + } + var thunk = function thunk(sel, key, fn, args) { + if (args === undefined) { + args = fn; + fn = key; + key = undefined; + } + return (0, h_3.h)(sel, { + key: key, + hook: { init: init, prepatch: prepatch }, + fn: fn, + args: args, + }); + }; + exports.thunk = thunk; }); define('tovnode', ['require', 'exports', './vnode', './htmldomapi'], function (require, exports, vnode_5, htmldomapi_3) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.toVNode = void 0; - - function toVNode(node, domApi) { - var api = domApi !== undefined ? domApi : htmldomapi_3.htmlDomApi; - var text; - if (api.isElement(node)) { - var id = node.id ? '#' + node.id : ''; - var cn = node.getAttribute('class'); - var c = cn ? '.' + cn.split(' ').join('.') : ''; - var sel = api.tagName(node).toLowerCase() + id + c; - var attrs = {}; - var children = []; - var name_1; - var i = void 0, - n = void 0; - var elmAttrs = node.attributes; - var elmChildren = node.childNodes; - for (i = 0, n = elmAttrs.length; i < n; i++) { - name_1 = elmAttrs[i].nodeName; - if (name_1 !== 'id' && name_1 !== 'class') { - attrs[name_1] = elmAttrs[i].nodeValue; - } - } - for (i = 0, n = elmChildren.length; i < n; i++) { - children.push(toVNode(elmChildren[i], domApi)); - } - return (0, vnode_5.vnode)(sel, {attrs: attrs}, children, undefined, node); - } else if (api.isText(node)) { - text = api.getTextContent(node); - return (0, vnode_5.vnode)(undefined, undefined, undefined, text, node); - } else if (api.isComment(node)) { - text = api.getTextContent(node); - return (0, vnode_5.vnode)('!', {}, [], text, node); - } else { - return (0, vnode_5.vnode)('', {}, [], undefined, node); - } - } - - exports.toVNode = toVNode; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.toVNode = void 0; + function toVNode(node, domApi) { + var api = domApi !== undefined ? domApi : htmldomapi_3.htmlDomApi; + var text; + if (api.isElement(node)) { + var id = node.id ? '#' + node.id : ''; + var cn = node.getAttribute('class'); + var c = cn ? '.' + cn.split(' ').join('.') : ''; + var sel = api.tagName(node).toLowerCase() + id + c; + var attrs = {}; + var children = []; + var name_1; + var i = void 0, + n = void 0; + var elmAttrs = node.attributes; + var elmChildren = node.childNodes; + for (i = 0, n = elmAttrs.length; i < n; i++) { + name_1 = elmAttrs[i].nodeName; + if (name_1 !== 'id' && name_1 !== 'class') { + attrs[name_1] = elmAttrs[i].nodeValue; + } + } + for (i = 0, n = elmChildren.length; i < n; i++) { + children.push(toVNode(elmChildren[i], domApi)); + } + return (0, vnode_5.vnode)(sel, { attrs: attrs }, children, undefined, node); + } else if (api.isText(node)) { + text = api.getTextContent(node); + return (0, vnode_5.vnode)(undefined, undefined, undefined, text, node); + } else if (api.isComment(node)) { + text = api.getTextContent(node); + return (0, vnode_5.vnode)('!', {}, [], text, node); + } else { + return (0, vnode_5.vnode)('', {}, [], undefined, node); + } + } + exports.toVNode = toVNode; }); define('vnode', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.vnode = void 0; - - function vnode(sel, data, children, text, elm) { - var key = data === undefined ? undefined : data.key; - return {sel: sel, data: data, children: children, text: text, elm: elm, key: key}; - } - - exports.vnode = vnode; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.vnode = void 0; + function vnode(sel, data, children, text, elm) { + var key = data === undefined ? undefined : data.key; + return { sel: sel, data: data, children: children, text: text, elm: elm, key: key }; + } + exports.vnode = vnode; }); define('helpers/attachto', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.attachTo = void 0; - - function pre(vnode, newVnode) { - var attachData = vnode.data.attachData; - newVnode.data.attachData.placeholder = attachData.placeholder; - newVnode.data.attachData.real = attachData.real; - vnode.elm = vnode.data.attachData.real; - } - - function post(_, vnode) { - vnode.elm = vnode.data.attachData.placeholder; - } - - function destroy(vnode) { - if (vnode.elm !== undefined) { - vnode.elm.parentNode.removeChild(vnode.elm); - } - vnode.elm = vnode.data.attachData.real; - } - - function create(_, vnode) { - var real = vnode.elm; - var attachData = vnode.data.attachData; - var placeholder = document.createElement('span'); - vnode.elm = placeholder; - attachData.target.appendChild(real); - attachData.real = real; - attachData.placeholder = placeholder; - } - - function attachTo(target, vnode) { - if (vnode.data === undefined) vnode.data = {}; - if (vnode.data.hook === undefined) vnode.data.hook = {}; - var data = vnode.data; - var hook = vnode.data.hook; - data.attachData = {target: target, placeholder: undefined, real: undefined}; - hook.create = create; - hook.prepatch = pre; - hook.postpatch = post; - hook.destroy = destroy; - return vnode; - } - - exports.attachTo = attachTo; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.attachTo = void 0; + function pre(vnode, newVnode) { + var attachData = vnode.data.attachData; + newVnode.data.attachData.placeholder = attachData.placeholder; + newVnode.data.attachData.real = attachData.real; + vnode.elm = vnode.data.attachData.real; + } + function post(_, vnode) { + vnode.elm = vnode.data.attachData.placeholder; + } + function destroy(vnode) { + if (vnode.elm !== undefined) { + vnode.elm.parentNode.removeChild(vnode.elm); + } + vnode.elm = vnode.data.attachData.real; + } + function create(_, vnode) { + var real = vnode.elm; + var attachData = vnode.data.attachData; + var placeholder = document.createElement('span'); + vnode.elm = placeholder; + attachData.target.appendChild(real); + attachData.real = real; + attachData.placeholder = placeholder; + } + function attachTo(target, vnode) { + if (vnode.data === undefined) vnode.data = {}; + if (vnode.data.hook === undefined) vnode.data.hook = {}; + var data = vnode.data; + var hook = vnode.data.hook; + data.attachData = { target: target, placeholder: undefined, real: undefined }; + hook.create = create; + hook.prepatch = pre; + hook.postpatch = post; + hook.destroy = destroy; + return vnode; + } + exports.attachTo = attachTo; }); define('modules/attributes', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.attributesModule = void 0; - var xlinkNS = 'http://www.w3.org/1999/xlink'; - var xmlNS = 'http://www.w3.org/XML/1998/namespace'; - var colonChar = 58; - var xChar = 120; - - function updateAttrs(oldVnode, vnode) { - var key; - var elm = vnode.elm; - var oldAttrs = oldVnode.data.attrs; - var attrs = vnode.data.attrs; - if (!oldAttrs && !attrs) return; - if (oldAttrs === attrs) return; - oldAttrs = oldAttrs || {}; - attrs = attrs || {}; - for (key in attrs) { - var cur = attrs[key]; - var old = oldAttrs[key]; - if (old !== cur) { - if (cur === true) { - elm.setAttribute(key, ''); - } else if (cur === false) { - elm.removeAttribute(key); - } else { - if (key.charCodeAt(0) !== xChar) { - elm.setAttribute(key, cur); - } else if (key.charCodeAt(3) === colonChar) { - elm.setAttributeNS(xmlNS, key, cur); - } else if (key.charCodeAt(5) === colonChar) { - elm.setAttributeNS(xlinkNS, key, cur); - } else { - elm.setAttribute(key, cur); - } - } - } - } - for (key in oldAttrs) { - if (!(key in attrs)) { - elm.removeAttribute(key); - } - } - } - - exports.attributesModule = { - create: updateAttrs, - update: updateAttrs, - }; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.attributesModule = void 0; + var xlinkNS = 'http://www.w3.org/1999/xlink'; + var xmlNS = 'http://www.w3.org/XML/1998/namespace'; + var colonChar = 58; + var xChar = 120; + function updateAttrs(oldVnode, vnode) { + var key; + var elm = vnode.elm; + var oldAttrs = oldVnode.data.attrs; + var attrs = vnode.data.attrs; + if (!oldAttrs && !attrs) return; + if (oldAttrs === attrs) return; + oldAttrs = oldAttrs || {}; + attrs = attrs || {}; + for (key in attrs) { + var cur = attrs[key]; + var old = oldAttrs[key]; + if (old !== cur) { + if (cur === true) { + elm.setAttribute(key, ''); + } else if (cur === false) { + elm.removeAttribute(key); + } else { + if (key.charCodeAt(0) !== xChar) { + elm.setAttribute(key, cur); + } else if (key.charCodeAt(3) === colonChar) { + elm.setAttributeNS(xmlNS, key, cur); + } else if (key.charCodeAt(5) === colonChar) { + elm.setAttributeNS(xlinkNS, key, cur); + } else { + elm.setAttribute(key, cur); + } + } + } + } + for (key in oldAttrs) { + if (!(key in attrs)) { + elm.removeAttribute(key); + } + } + } + exports.attributesModule = { + create: updateAttrs, + update: updateAttrs, + }; }); define('modules/class', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.classModule = void 0; - - function updateClass(oldVnode, vnode) { - var cur; - var name; - var elm = vnode.elm; - var oldClass = oldVnode.data.class; - var klass = vnode.data.class; - if (!oldClass && !klass) return; - if (oldClass === klass) return; - oldClass = oldClass || {}; - klass = klass || {}; - for (name in oldClass) { - if (oldClass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) { - elm.classList.remove(name); - } - } - for (name in klass) { - cur = klass[name]; - if (cur !== oldClass[name]) { - elm.classList[cur ? 'add' : 'remove'](name); - } - } - } - - exports.classModule = {create: updateClass, update: updateClass}; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.classModule = void 0; + function updateClass(oldVnode, vnode) { + var cur; + var name; + var elm = vnode.elm; + var oldClass = oldVnode.data.class; + var klass = vnode.data.class; + if (!oldClass && !klass) return; + if (oldClass === klass) return; + oldClass = oldClass || {}; + klass = klass || {}; + for (name in oldClass) { + if (oldClass[name] && !Object.prototype.hasOwnProperty.call(klass, name)) { + elm.classList.remove(name); + } + } + for (name in klass) { + cur = klass[name]; + if (cur !== oldClass[name]) { + elm.classList[cur ? 'add' : 'remove'](name); + } + } + } + exports.classModule = { create: updateClass, update: updateClass }; }); define('modules/dataset', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.datasetModule = void 0; - var CAPS_REGEX = /[A-Z]/g; - - function updateDataset(oldVnode, vnode) { - var elm = vnode.elm; - var oldDataset = oldVnode.data.dataset; - var dataset = vnode.data.dataset; - var key; - if (!oldDataset && !dataset) return; - if (oldDataset === dataset) return; - oldDataset = oldDataset || {}; - dataset = dataset || {}; - var d = elm.dataset; - for (key in oldDataset) { - if (!dataset[key]) { - if (d) { - if (key in d) { - delete d[key]; - } - } else { - elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase()); - } - } - } - for (key in dataset) { - if (oldDataset[key] !== dataset[key]) { - if (d) { - d[key] = dataset[key]; - } else { - elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]); - } - } - } - } - - exports.datasetModule = { - create: updateDataset, - update: updateDataset, - }; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.datasetModule = void 0; + var CAPS_REGEX = /[A-Z]/g; + function updateDataset(oldVnode, vnode) { + var elm = vnode.elm; + var oldDataset = oldVnode.data.dataset; + var dataset = vnode.data.dataset; + var key; + if (!oldDataset && !dataset) return; + if (oldDataset === dataset) return; + oldDataset = oldDataset || {}; + dataset = dataset || {}; + var d = elm.dataset; + for (key in oldDataset) { + if (!dataset[key]) { + if (d) { + if (key in d) { + delete d[key]; + } + } else { + elm.removeAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase()); + } + } + } + for (key in dataset) { + if (oldDataset[key] !== dataset[key]) { + if (d) { + d[key] = dataset[key]; + } else { + elm.setAttribute('data-' + key.replace(CAPS_REGEX, '-$&').toLowerCase(), dataset[key]); + } + } + } + } + exports.datasetModule = { + create: updateDataset, + update: updateDataset, + }; }); define('modules/eventlisteners', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.eventListenersModule = void 0; - - function invokeHandler(handler, vnode, event) { - if (typeof handler === 'function') { - handler.call(vnode, event, vnode); - } else if (typeof handler === 'object') { - for (var i = 0; i < handler.length; i++) { - invokeHandler(handler[i], vnode, event); - } - } - } - - function handleEvent(event, vnode) { - var name = event.type; - var on = vnode.data.on; - if (on && on[name]) { - invokeHandler(on[name], vnode, event); - } - } - - function createListener() { - return function handler(event) { - handleEvent(event, handler.vnode); - }; - } - - function updateEventListeners(oldVnode, vnode) { - var oldOn = oldVnode.data.on; - var oldListener = oldVnode.listener; - var oldElm = oldVnode.elm; - var on = vnode && vnode.data.on; - var elm = vnode && vnode.elm; - var name; - if (oldOn === on) { - return; - } - if (oldOn && oldListener) { - if (!on) { - for (name in oldOn) { - oldElm.removeEventListener(name, oldListener, false); - } - } else { - for (name in oldOn) { - if (!on[name]) { - oldElm.removeEventListener(name, oldListener, false); - } - } - } - } - if (on) { - var listener = (vnode.listener = oldVnode.listener || createListener()); - listener.vnode = vnode; - if (!oldOn) { - for (name in on) { - elm.addEventListener(name, listener, false); - } - } else { - for (name in on) { - if (!oldOn[name]) { - elm.addEventListener(name, listener, false); - } - } - } - } - } - - exports.eventListenersModule = { - create: updateEventListeners, - update: updateEventListeners, - destroy: updateEventListeners, - }; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.eventListenersModule = void 0; + function invokeHandler(handler, vnode, event) { + if (typeof handler === 'function') { + handler.call(vnode, event, vnode); + } else if (typeof handler === 'object') { + for (var i = 0; i < handler.length; i++) { + invokeHandler(handler[i], vnode, event); + } + } + } + function handleEvent(event, vnode) { + var name = event.type; + var on = vnode.data.on; + if (on && on[name]) { + invokeHandler(on[name], vnode, event); + } + } + function createListener() { + return function handler(event) { + handleEvent(event, handler.vnode); + }; + } + function updateEventListeners(oldVnode, vnode) { + var oldOn = oldVnode.data.on; + var oldListener = oldVnode.listener; + var oldElm = oldVnode.elm; + var on = vnode && vnode.data.on; + var elm = vnode && vnode.elm; + var name; + if (oldOn === on) { + return; + } + if (oldOn && oldListener) { + if (!on) { + for (name in oldOn) { + oldElm.removeEventListener(name, oldListener, false); + } + } else { + for (name in oldOn) { + if (!on[name]) { + oldElm.removeEventListener(name, oldListener, false); + } + } + } + } + if (on) { + var listener = (vnode.listener = oldVnode.listener || createListener()); + listener.vnode = vnode; + if (!oldOn) { + for (name in on) { + elm.addEventListener(name, listener, false); + } + } else { + for (name in on) { + if (!oldOn[name]) { + elm.addEventListener(name, listener, false); + } + } + } + } + } + exports.eventListenersModule = { + create: updateEventListeners, + update: updateEventListeners, + destroy: updateEventListeners, + }; }); define('modules/module', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); }); define('modules/props', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.propsModule = void 0; - - function updateProps(oldVnode, vnode) { - var key; - var cur; - var old; - var elm = vnode.elm; - var oldProps = oldVnode.data.props; - var props = vnode.data.props; - if (!oldProps && !props) return; - if (oldProps === props) return; - oldProps = oldProps || {}; - props = props || {}; - for (key in props) { - cur = props[key]; - old = oldProps[key]; - if (old !== cur && (key !== 'value' || elm[key] !== cur)) { - elm[key] = cur; - } - } - } - - exports.propsModule = {create: updateProps, update: updateProps}; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.propsModule = void 0; + function updateProps(oldVnode, vnode) { + var key; + var cur; + var old; + var elm = vnode.elm; + var oldProps = oldVnode.data.props; + var props = vnode.data.props; + if (!oldProps && !props) return; + if (oldProps === props) return; + oldProps = oldProps || {}; + props = props || {}; + for (key in props) { + cur = props[key]; + old = oldProps[key]; + if (old !== cur && (key !== 'value' || elm[key] !== cur)) { + elm[key] = cur; + } + } + } + exports.propsModule = { create: updateProps, update: updateProps }; }); define('modules/style', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - exports.styleModule = void 0; - var raf = (typeof window !== 'undefined' && window.requestAnimationFrame.bind(window)) || setTimeout; - var nextFrame = function (fn) { - raf(function () { - raf(fn); - }); - }; - var reflowForced = false; - - function setNextFrame(obj, prop, val) { - nextFrame(function () { - obj[prop] = val; - }); - } - - function updateStyle(oldVnode, vnode) { - var cur; - var name; - var elm = vnode.elm; - var oldStyle = oldVnode.data.style; - var style = vnode.data.style; - if (!oldStyle && !style) return; - if (oldStyle === style) return; - oldStyle = oldStyle || {}; - style = style || {}; - var oldHasDel = 'delayed' in oldStyle; - for (name in oldStyle) { - if (!style[name]) { - if (name[0] === '-' && name[1] === '-') { - elm.style.removeProperty(name); - } else { - elm.style[name] = ''; - } - } - } - for (name in style) { - cur = style[name]; - if (name === 'delayed' && style.delayed) { - for (var name2 in style.delayed) { - cur = style.delayed[name2]; - if (!oldHasDel || cur !== oldStyle.delayed[name2]) { - setNextFrame(elm.style, name2, cur); - } - } - } else if (name !== 'remove' && cur !== oldStyle[name]) { - if (name[0] === '-' && name[1] === '-') { - elm.style.setProperty(name, cur); - } else { - elm.style[name] = cur; - } - } - } - } - - function applyDestroyStyle(vnode) { - var style; - var name; - var elm = vnode.elm; - var s = vnode.data.style; - if (!s || !(style = s.destroy)) return; - for (name in style) { - elm.style[name] = style[name]; - } - } - - function applyRemoveStyle(vnode, rm) { - var s = vnode.data.style; - if (!s || !s.remove) { - rm(); - return; - } - if (!reflowForced) { - vnode.elm.offsetLeft; - reflowForced = true; - } - var name; - var elm = vnode.elm; - var i = 0; - var style = s.remove; - var amount = 0; - var applied = []; - for (name in style) { - applied.push(name); - elm.style[name] = style[name]; - } - var compStyle = getComputedStyle(elm); - var props = compStyle['transition-property'].split(', '); - for (; i < props.length; ++i) { - if (applied.indexOf(props[i]) !== -1) amount++; - } - elm.addEventListener('transitionend', function (ev) { - if (ev.target === elm) --amount; - if (amount === 0) rm(); - }); - } - - function forceReflow() { - reflowForced = false; - } - - exports.styleModule = { - pre: forceReflow, - create: updateStyle, - update: updateStyle, - destroy: applyDestroyStyle, - remove: applyRemoveStyle, - }; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + exports.styleModule = void 0; + var raf = (typeof window !== 'undefined' && window.requestAnimationFrame.bind(window)) || setTimeout; + var nextFrame = function (fn) { + raf(function () { + raf(fn); + }); + }; + var reflowForced = false; + function setNextFrame(obj, prop, val) { + nextFrame(function () { + obj[prop] = val; + }); + } + function updateStyle(oldVnode, vnode) { + var cur; + var name; + var elm = vnode.elm; + var oldStyle = oldVnode.data.style; + var style = vnode.data.style; + if (!oldStyle && !style) return; + if (oldStyle === style) return; + oldStyle = oldStyle || {}; + style = style || {}; + var oldHasDel = 'delayed' in oldStyle; + for (name in oldStyle) { + if (!style[name]) { + if (name[0] === '-' && name[1] === '-') { + elm.style.removeProperty(name); + } else { + elm.style[name] = ''; + } + } + } + for (name in style) { + cur = style[name]; + if (name === 'delayed' && style.delayed) { + for (var name2 in style.delayed) { + cur = style.delayed[name2]; + if (!oldHasDel || cur !== oldStyle.delayed[name2]) { + setNextFrame(elm.style, name2, cur); + } + } + } else if (name !== 'remove' && cur !== oldStyle[name]) { + if (name[0] === '-' && name[1] === '-') { + elm.style.setProperty(name, cur); + } else { + elm.style[name] = cur; + } + } + } + } + function applyDestroyStyle(vnode) { + var style; + var name; + var elm = vnode.elm; + var s = vnode.data.style; + if (!s || !(style = s.destroy)) return; + for (name in style) { + elm.style[name] = style[name]; + } + } + function applyRemoveStyle(vnode, rm) { + var s = vnode.data.style; + if (!s || !s.remove) { + rm(); + return; + } + if (!reflowForced) { + vnode.elm.offsetLeft; + reflowForced = true; + } + var name; + var elm = vnode.elm; + var i = 0; + var style = s.remove; + var amount = 0; + var applied = []; + for (name in style) { + applied.push(name); + elm.style[name] = style[name]; + } + var compStyle = getComputedStyle(elm); + var props = compStyle['transition-property'].split(', '); + for (; i < props.length; ++i) { + if (applied.indexOf(props[i]) !== -1) amount++; + } + elm.addEventListener('transitionend', function (ev) { + if (ev.target === elm) --amount; + if (amount === 0) rm(); + }); + } + function forceReflow() { + reflowForced = false; + } + exports.styleModule = { + pre: forceReflow, + create: updateStyle, + update: updateStyle, + destroy: applyDestroyStyle, + remove: applyRemoveStyle, + }; }); define('public/describe', ['require', 'exports'], function (require, exports) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); }); define('public/vdom', ['require', 'exports', 'snabbdom'], function (require, exports, snabbdom_1) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - var patch = (0, snabbdom_1.init)([snabbdom_1.classModule, snabbdom_1.propsModule, snabbdom_1.styleModule, snabbdom_1.eventListenersModule]); - var Vm = (function () { - function Vm(config) { - var _this = this; - var el = config.el, - template = config.template, - methods = config.methods, - mounted = config.mounted; - this.$element = document.querySelector(''.concat(el)); - this.$el = el; - this.$methods = methods; - this.$template = template; - this.$mounted = mounted; - this.$keys = {}; - this.$watcher({ - data: config.data, - watch: function (key, oldVal, newVal) { - _this.$reanderElement(_this.$el); - }, - }); - this.$eventHandle(); - this.$reanderElement(el); - } - - Vm.prototype.$watcher = function (opts) { - this._data = this.$getBaseType(opts.data) === 'Object' ? opts.data : {}; - this.$watch = opts.watch; - for (var key in opts.data) { - this.$setData(key); - } - }; - Vm.prototype.$getBaseType = function (target) { - var typeStr = Object.prototype.toString.apply(target); - return typeStr.slice(8, -1); - }; - Vm.prototype.$reanderElement = function (el) { - var _this = this; - var jsxTemplate = (0, snabbdom_1.jsx)( - 'div', - { - props: {id: el.replace('#', '')}, - hook: { - insert: function (vnode) { - if (typeof _this.$VirtualDOM === 'undefined') _this.$mounted && _this.$mounted(); - }, - }, - }, - ' ', - this.$template(this) - ); - this.$VirtualDOM = patch(typeof this.$VirtualDOM !== 'undefined' ? this.$VirtualDOM : this.$element, jsxTemplate); - }; - Vm.prototype.$setData = function (_key) { - Object.defineProperty(this, _key, { - get: function () { - return this._data[_key]; - }, - set: function (val) { - var oldVal = this._data[_key]; - if (oldVal === val) return val; - this._data[_key] = val; - this.$watch.call(this, _key, oldVal, val); - return val; - }, - }); - }; - Vm.prototype.$eventHandle = function () { - Object.assign(this, this.$methods); - for (var key in this.$methods) { - if (Object.prototype.hasOwnProperty.call(this.$methods, key)) { - this.$methods[key].bind(this); - } - } - }; - Vm.prototype.$class = function (array) { - if (typeof array === 'string') array = array.trim().split(' '); - var classList = {}; - for (var i = 0; i < array.length; i++) classList[array[i]] = true; - return classList; - }; - Vm.prototype.$style = function (str) { - if (!str) return {}; - if (typeof str === 'object') return str; - var style = {}, - styleList = str.split(';'); - for (var i = 0; i < styleList.length; i++) { - if (styleList[i] === '') continue; - var styleItem = styleList[i].split(':'); - style[styleItem[0].trim()] = styleItem[1]; - } - return style; - }; - Vm.prototype.$ul = function (config, arry) { - if (Array.isArray(config)) { - arry = config; - config = {style: {}, className: ''}; - } - var style = config.style, - className = config.className; - var liList = arry.map(function (item, index) { - if (typeof item === 'string') item = [item]; - return (0, snabbdom_1.jsx)('li', {key: index, style: {color: item[1] || ''}}, item[0] || item); - }); - return (0, snabbdom_1.jsx)('ul', { - class: this.$class('help-info-text c7 '.concat(className || '')), - style: this.$style(style) - }, liList); - }; - Vm.prototype.$line = function (config, content) { - if (typeof config != 'object') config = {title: config}; - var title = config.title, - width = config.width, - style = config.style, - hide = config.hide; - return (0, snabbdom_1.jsx)( - 'div', - {class: {line: true, hide: hide}}, - (0, snabbdom_1.jsx)('span', { - class: {tname: true}, - style: this.$style(''.concat(width ? 'width:' + width + ';' : '').concat(style)) - }, title), - (0, snabbdom_1.jsx)('div', {class: {'info-r': true}, style: {marginLeft: width}}, content) - ); - }; - Vm.prototype.$box = function (element1, element2) { - return (0, snabbdom_1.jsx)('div', {class: this.$class('group-box')}, ' ', element1, ' ', element2); - }; - Vm.prototype.$switch = function (config) { - var checked = config.checked, - change = config.change, - model = config.model, - name = config.name; - if (model) (checked = this[model]), (name = model); - return (0, snabbdom_1.jsx)( - 'div', - {class: {'info-block': true}}, - (0, snabbdom_1.jsx)('input', { - class: this.$class('btswitch btswitch-ios'), - props: {id: model + '_vm', type: 'checkbox', name: name, checked: checked}, - on: {input: this.$inputEvent.bind(this), change: change}, - }), - (0, snabbdom_1.jsx)('label', { - style: {position: 'relative', top: '5px'}, - class: {'btswitch-btn': true}, - props: {htmlFor: model + '_vm'} - }) - ); - }; - Vm.prototype.$input = function (config) { - var name = config.name, - readonly = config.readonly, - disabled = config.disabled, - style = config.style, - value = config.value, - type = config.type, - change = config.change, - className = config.className, - model = config.model, - width = config.width, - id = config.id, - placeholder = config.placeholder, - keyup = config.keyup; - if (model) (value = this[model]), (name = model); - if (width) style = 'width:'.concat(width, ';').concat(style); - return (0, snabbdom_1.jsx)('input', { - class: this.$class('bt-input-text mr5 '.concat(className || '')), - props: { - readonly: readonly, - name: name, - type: type || 'text', - disabled: disabled, - value: value, - id: id, - placeholder: placeholder - }, - style: this.$style(style), - on: {input: this.$inputEvent.bind(this), change: change, keyup: keyup}, - }); - }; - Vm.prototype.$select = function (config) { - var style = config.style, - className = config.className, - options = config.options, - model = config.model, - name = config.name, - value = config.value, - width = config.width, - change = config.change; - if (model) (value = this[model]), (name = model); - if (width) style = 'width:'.concat(width, ';').concat(style); - var optionList = options.map(function (item, index) { - if (typeof item === 'string') item = {value: index, label: item}; - return (0, snabbdom_1.jsx)('option', { - key: index, - props: {value: item.value, selected: item.value === value} - }, item.label); - }); - return (0, snabbdom_1.jsx)( - 'select', - { - class: this.$class('bt-input-text mr5 '.concat(className || '')), - props: {name: name}, - on: {input: this.$inputEvent.bind(this), change: change}, - style: this.$style(style) - }, - optionList - ); - }; - Vm.prototype.$textarea = function (config) { - var style = config.style, - className = config.className, - value = config.value, - model = config.model, - name = config.name, - width = config.width, - height = config.height, - id = config.id; - if (model) (value = this[model]), (name = model); - if (width) style = 'width:'.concat(width, ';').concat(style); - if (height) style = 'height:'.concat(height, ';').concat(style); - return (0, snabbdom_1.jsx)( - 'textarea', - { - class: this.$class('bt-input-text '.concat(className || '')), - style: this.$style(style), - props: {name: name, id: id}, - on: {input: this.$inputEvent.bind(this)} - }, - value - ); - }; - Vm.prototype.$button = function (config) { - var type = config.type, - size = config.size, - click = config.click, - style = config.style, - className = config.className, - title = config.title, - width = config.width, - height = config.height; - if (width) style = 'width:'.concat(width, ';').concat(style); - if (height) style = 'height:'.concat(height, ';').concat(style); - return (0, snabbdom_1.jsx)( - 'button', - { - class: this.$class( - 'btn btn-' - .concat(type || 'success', ' btn-') - .concat(size || 'sm', ' ') - .concat(className || '') - ), - style: this.$style(style), - on: {click: click}, - }, - title - ); - }; - Vm.prototype.$link = function (config) { - var click = config.click, - style = config.style, - className = config.className, - title = config.title, - href = config.href, - target = config.target; - return (0, snabbdom_1.jsx)( - 'a', - { - class: this.$class('btlink ' + className), - props: {href: href || 'javascript:;', target: target || '_blank'}, - style: this.$style(style), - on: {click: click} - }, - title - ); - }; - Vm.prototype.$icon = function (config) { - var click = config.click, - style = config.style, - type = config.type; - return (0, snabbdom_1.jsx)('span', { - class: this.$class('glyphicon glyphicon-'.concat(type, ' cursor')), - style: this.$style(style), - on: {click: click} - }); - }; - Vm.prototype.$warningTitle = function (tips) { - return (0, snabbdom_1.jsx)( - 'div', - {class: {mb15: true, 'layer-info-head': true}}, - (0, snabbdom_1.jsx)('i', {class: this.$class('layui-layer-ico layui-layer-ico3 layer-info-ico')}), - (0, snabbdom_1.jsx)('h3', {class: {'layer-info-title': true}}, tips) - ); - }; - Vm.prototype.$learnMore = function (config) { - var title = config.title, - model = config.model, - className = config.className, - style = config.style, - id = config.id, - link = config.link, - relation = model + '_more'; - return (0, snabbdom_1.jsx)( - 'div', - { - class: this.$class('mt10 agreementBox '.concat(className || '')), - props: {id: id}, - style: this.$style(style) - }, - (0, snabbdom_1.jsx)( - 'div', - {class: this.$class('agreementCont')}, - this.$input({type: 'checkbox', model: model, id: relation}), - (0, snabbdom_1.jsx)('label', {props: {htmlFor: relation}}, title) - ), - link - ); - }; - Vm.prototype.$table = function (config) { - return (0, snabbdom_1.jsx)('div', {class: {divtable: true}}, (0, snabbdom_1.jsx)('table', {class: {'table table-bordered table-hover': true}})); - }; - Vm.prototype.$tab = function (config) { - var title = config.title, - content = config.content, - className = config.className, - style = config.style, - id = config.id; - return (0, snabbdom_1.jsx)( - 'div', - {class: {'bt-w-main': true}}, - (0, snabbdom_1.jsx)('div', {class: {'bt-w-menu': true}}), - (0, snabbdom_1.jsx)('div', {class: {'bt-w-con': true, pd15: true}}, {content: content}) - ); - }; - Vm.prototype.$tabItem = function (config) { - var title = config.title, - content = config.content, - hide = config.hide; - return (0, snabbdom_1.jsx)('div', {class: {'bt-w-item': true, hide: hide}, props: {title: title}}, content); - }; - Vm.prototype.$inputEvent = function (ev, fn) { - var targets = ev.target; - var targetValue = targets.value; - var targetType = targets.getAttribute('type'); - var name = targets.getAttribute('name'); - if (ev.type === 'input' && targetType === 'checkbox') { - this[name] = targets.checked; - } else { - this[name] = targetValue; - } - if (typeof fn === 'function') fn(ev); - }; - return Vm; - })(); - exports.default = Vm; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + var patch = (0, snabbdom_1.init)([snabbdom_1.classModule, snabbdom_1.propsModule, snabbdom_1.styleModule, snabbdom_1.eventListenersModule]); + var Vm = (function () { + function Vm(config) { + var _this = this; + var el = config.el, + template = config.template, + methods = config.methods, + mounted = config.mounted; + this.$element = document.querySelector(''.concat(el)); + this.$el = el; + this.$methods = methods; + this.$template = template; + this.$mounted = mounted; + this.$keys = {}; + this.$watcher({ + data: config.data, + watch: function (key, oldVal, newVal) { + _this.$reanderElement(_this.$el); + }, + }); + this.$eventHandle(); + this.$reanderElement(el); + } + Vm.prototype.$watcher = function (opts) { + this._data = this.$getBaseType(opts.data) === 'Object' ? opts.data : {}; + this.$watch = opts.watch; + for (var key in opts.data) { + this.$setData(key); + } + }; + Vm.prototype.$getBaseType = function (target) { + var typeStr = Object.prototype.toString.apply(target); + return typeStr.slice(8, -1); + }; + Vm.prototype.$reanderElement = function (el) { + var _this = this; + var jsxTemplate = (0, snabbdom_1.jsx)( + 'div', + { + props: { id: el.replace('#', '') }, + hook: { + insert: function (vnode) { + if (typeof _this.$VirtualDOM === 'undefined') _this.$mounted && _this.$mounted(); + }, + }, + }, + ' ', + this.$template(this) + ); + this.$VirtualDOM = patch(typeof this.$VirtualDOM !== 'undefined' ? this.$VirtualDOM : this.$element, jsxTemplate); + }; + Vm.prototype.$setData = function (_key) { + Object.defineProperty(this, _key, { + get: function () { + return this._data[_key]; + }, + set: function (val) { + var oldVal = this._data[_key]; + if (oldVal === val) return val; + this._data[_key] = val; + this.$watch.call(this, _key, oldVal, val); + return val; + }, + }); + }; + Vm.prototype.$eventHandle = function () { + Object.assign(this, this.$methods); + for (var key in this.$methods) { + if (Object.prototype.hasOwnProperty.call(this.$methods, key)) { + this.$methods[key].bind(this); + } + } + }; + Vm.prototype.$class = function (array) { + if (typeof array === 'string') array = array.trim().split(' '); + var classList = {}; + for (var i = 0; i < array.length; i++) classList[array[i]] = true; + return classList; + }; + Vm.prototype.$style = function (str) { + if (!str) return {}; + if (typeof str === 'object') return str; + var style = {}, + styleList = str.split(';'); + for (var i = 0; i < styleList.length; i++) { + if (styleList[i] === '') continue; + var styleItem = styleList[i].split(':'); + style[styleItem[0].trim()] = styleItem[1]; + } + return style; + }; + Vm.prototype.$ul = function (config, arry) { + if (Array.isArray(config)) { + arry = config; + config = { style: {}, className: '' }; + } + var style = config.style, + className = config.className; + var liList = arry.map(function (item, index) { + if (typeof item === 'string') item = [item]; + return (0, snabbdom_1.jsx)('li', { key: index, style: { color: item[1] || '' } }, item[0] || item); + }); + return (0, snabbdom_1.jsx)('ul', { class: this.$class('help-info-text c7 '.concat(className || '')), style: this.$style(style) }, liList); + }; + Vm.prototype.$line = function (config, content) { + if (typeof config != 'object') config = { title: config }; + var title = config.title, + width = config.width, + style = config.style, + hide = config.hide; + return (0, snabbdom_1.jsx)( + 'div', + { class: { line: true, hide: hide } }, + (0, snabbdom_1.jsx)('span', { class: { tname: true }, style: this.$style(''.concat(width ? 'width:' + width + ';' : '').concat(style)) }, title), + (0, snabbdom_1.jsx)('div', { class: { 'info-r': true }, style: { marginLeft: width } }, content) + ); + }; + Vm.prototype.$box = function (element1, element2) { + return (0, snabbdom_1.jsx)('div', { class: this.$class('group-box') }, ' ', element1, ' ', element2); + }; + Vm.prototype.$switch = function (config) { + var checked = config.checked, + change = config.change, + model = config.model, + name = config.name; + if (model) (checked = this[model]), (name = model); + return (0, snabbdom_1.jsx)( + 'div', + { class: { 'info-block': true } }, + (0, snabbdom_1.jsx)('input', { + class: this.$class('btswitch btswitch-ios'), + props: { id: model + '_vm', type: 'checkbox', name: name, checked: checked }, + on: { input: this.$inputEvent.bind(this), change: change }, + }), + (0, snabbdom_1.jsx)('label', { style: { position: 'relative', top: '5px' }, class: { 'btswitch-btn': true }, props: { htmlFor: model + '_vm' } }) + ); + }; + Vm.prototype.$input = function (config) { + var name = config.name, + readonly = config.readonly, + disabled = config.disabled, + style = config.style, + value = config.value, + type = config.type, + change = config.change, + className = config.className, + model = config.model, + width = config.width, + id = config.id, + placeholder = config.placeholder, + keyup = config.keyup; + if (model) (value = this[model]), (name = model); + if (width) style = 'width:'.concat(width, ';').concat(style); + return (0, snabbdom_1.jsx)('input', { + class: this.$class('bt-input-text mr5 '.concat(className || '')), + props: { readonly: readonly, name: name, type: type || 'text', disabled: disabled, value: value, id: id, placeholder: placeholder }, + style: this.$style(style), + on: { input: this.$inputEvent.bind(this), change: change, keyup: keyup }, + }); + }; + Vm.prototype.$select = function (config) { + var style = config.style, + className = config.className, + options = config.options, + model = config.model, + name = config.name, + value = config.value, + width = config.width, + change = config.change; + if (model) (value = this[model]), (name = model); + if (width) style = 'width:'.concat(width, ';').concat(style); + var optionList = options.map(function (item, index) { + if (typeof item === 'string') item = { value: index, label: item }; + return (0, snabbdom_1.jsx)('option', { key: index, props: { value: item.value, selected: item.value === value } }, item.label); + }); + return (0, snabbdom_1.jsx)( + 'select', + { class: this.$class('bt-input-text mr5 '.concat(className || '')), props: { name: name }, on: { input: this.$inputEvent.bind(this), change: change }, style: this.$style(style) }, + optionList + ); + }; + Vm.prototype.$textarea = function (config) { + var style = config.style, + className = config.className, + value = config.value, + model = config.model, + name = config.name, + width = config.width, + height = config.height, + id = config.id; + if (model) (value = this[model]), (name = model); + if (width) style = 'width:'.concat(width, ';').concat(style); + if (height) style = 'height:'.concat(height, ';').concat(style); + return (0, snabbdom_1.jsx)( + 'textarea', + { class: this.$class('bt-input-text '.concat(className || '')), style: this.$style(style), props: { name: name, id: id }, on: { input: this.$inputEvent.bind(this) } }, + value + ); + }; + Vm.prototype.$button = function (config) { + var type = config.type, + size = config.size, + click = config.click, + style = config.style, + className = config.className, + title = config.title, + width = config.width, + height = config.height; + if (width) style = 'width:'.concat(width, ';').concat(style); + if (height) style = 'height:'.concat(height, ';').concat(style); + return (0, snabbdom_1.jsx)( + 'button', + { + class: this.$class( + 'btn btn-' + .concat(type || 'success', ' btn-') + .concat(size || 'sm', ' ') + .concat(className || '') + ), + style: this.$style(style), + on: { click: click }, + }, + title + ); + }; + Vm.prototype.$link = function (config) { + var click = config.click, + style = config.style, + className = config.className, + title = config.title, + href = config.href, + target = config.target; + return (0, snabbdom_1.jsx)( + 'a', + { class: this.$class('btlink ' + className), props: { href: href || 'javascript:;', target: target || '_blank' }, style: this.$style(style), on: { click: click } }, + title + ); + }; + Vm.prototype.$icon = function (config) { + var click = config.click, + style = config.style, + type = config.type; + return (0, snabbdom_1.jsx)('span', { class: this.$class('glyphicon glyphicon-'.concat(type, ' cursor')), style: this.$style(style), on: { click: click } }); + }; + Vm.prototype.$warningTitle = function (tips) { + return (0, snabbdom_1.jsx)( + 'div', + { class: { mb15: true, 'layer-info-head': true } }, + (0, snabbdom_1.jsx)('i', { class: this.$class('layui-layer-ico layui-layer-ico3 layer-info-ico') }), + (0, snabbdom_1.jsx)('h3', { class: { 'layer-info-title': true } }, tips) + ); + }; + Vm.prototype.$learnMore = function (config) { + var title = config.title, + model = config.model, + className = config.className, + style = config.style, + id = config.id, + link = config.link, + relation = model + '_more'; + return (0, snabbdom_1.jsx)( + 'div', + { class: this.$class('mt10 agreementBox '.concat(className || '')), props: { id: id }, style: this.$style(style) }, + (0, snabbdom_1.jsx)( + 'div', + { class: this.$class('agreementCont') }, + this.$input({ type: 'checkbox', model: model, id: relation }), + (0, snabbdom_1.jsx)('label', { props: { htmlFor: relation } }, title) + ), + link + ); + }; + Vm.prototype.$table = function (config) { + return (0, snabbdom_1.jsx)('div', { class: { divtable: true } }, (0, snabbdom_1.jsx)('table', { class: { 'table table-bordered table-hover': true } })); + }; + Vm.prototype.$tab = function (config) { + var title = config.title, + content = config.content, + className = config.className, + style = config.style, + id = config.id; + return (0, snabbdom_1.jsx)( + 'div', + { class: { 'bt-w-main': true } }, + (0, snabbdom_1.jsx)('div', { class: { 'bt-w-menu': true } }), + (0, snabbdom_1.jsx)('div', { class: { 'bt-w-con': true, pd15: true } }, { content: content }) + ); + }; + Vm.prototype.$tabItem = function (config) { + var title = config.title, + content = config.content, + hide = config.hide; + return (0, snabbdom_1.jsx)('div', { class: { 'bt-w-item': true, hide: hide }, props: { title: title } }, content); + }; + Vm.prototype.$inputEvent = function (ev, fn) { + var targets = ev.target; + var targetValue = targets.value; + var targetType = targets.getAttribute('type'); + var name = targets.getAttribute('name'); + if (ev.type === 'input' && targetType === 'checkbox') { + this[name] = targets.checked; + } else { + this[name] = targetValue; + } + if (typeof fn === 'function') fn(ev); + }; + return Vm; + })(); + exports.default = Vm; }); define('public/utils', ['require', 'exports', 'public/vdom'], function (require, exports, vdom_1) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - vdom_1 = __importDefault(vdom_1); - var Utils = (function () { - function Utils() { - this.System = 'linux'; - this.Language = 'zh-CN'; - this.API = {}; - this.vDdomList = {}; - this.layerIndex = 0; - this.$ajaxSetup(); - this.$requestInit(); - } - - Utils.prototype.$ajaxSetup = function () { - var my_headers = {}; - var request_token_ele = document.getElementById('request_token_head'); - if (request_token_ele) { - var request_token = request_token_ele.getAttribute('token'); - if (request_token) { - my_headers['x-http-token'] = request_token; - } - } - var request_token_cookie = this.$getCookie('request_token'); - if (request_token_cookie) { - my_headers['x-cookie-token'] = request_token_cookie; - } - if (my_headers) { - $.ajaxSetup({ - headers: my_headers, - error: function (jqXHR, textStatus, errorThrown) { - if (!jqXHR.responseText) return; - if (typeof String.prototype.trim === 'undefined') { - String.prototype.trim = function () { - return String(this).replace(/^\s+|\s+$/g, ''); - }; - } - var error_key = 'We need to make sure this has a favicon so that the debugger does'; - var error_find = jqXHR.responseText.indexOf(error_key); - if (jqXHR.status == 500 && (jqXHR.responseText.indexOf('An error occurred while the panel was running') != -1 || error_find != -1)) { - if (error_find != -1) { - var error_body = jqXHR.responseText.split('', ''); - var tmp = error_body.split('During handling of the above exception, another exception occurred:'); - error_body = tmp[tmp.length - 1]; - var error_msg = - '
\ + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + vdom_1 = __importDefault(vdom_1); + var Utils = (function () { + function Utils() { + this.System = 'linux'; + this.Language = 'zh-CN'; + this.API = {}; + this.vDdomList = {}; + this.layerIndex = 0; + this.$ajaxSetup(); + this.$requestInit(); + } + Utils.prototype.$ajaxSetup = function () { + var my_headers = {}; + var request_token_ele = document.getElementById('request_token_head'); + if (request_token_ele) { + var request_token = request_token_ele.getAttribute('token'); + if (request_token) { + my_headers['x-http-token'] = request_token; + } + } + var request_token_cookie = this.$getCookie('request_token'); + if (request_token_cookie) { + my_headers['x-cookie-token'] = request_token_cookie; + } + if (my_headers) { + $.ajaxSetup({ + headers: my_headers, + error: function (jqXHR, textStatus, errorThrown) { + if (!jqXHR.responseText) return; + if (typeof String.prototype.trim === 'undefined') { + String.prototype.trim = function () { + return String(this).replace(/^\s+|\s+$/g, ''); + }; + } + var error_key = 'We need to make sure this has a favicon so that the debugger does'; + var error_find = jqXHR.responseText.indexOf(error_key); + if (jqXHR.status == 500 && (jqXHR.responseText.indexOf('An error occurred while the panel was running') != -1 || error_find != -1)) { + if (error_find != -1) { + var error_body = jqXHR.responseText.split('', ''); + var tmp = error_body.split('During handling of the above exception, another exception occurred:'); + error_body = tmp[tmp.length - 1]; + var error_msg = + '
\

An error occurred while the panel was running!

\
' +
-                                    error_body.trim() +
-                                    '
\ + error_body.trim() + + '\
    \
  • Sorry, please try to resolve this error in the following order:
  • \
  • 1. Click the repair panel in the upper right corner of the [Homepage], log out of the panel and log in again.
  • \
  • 2. If the above attempts fail to resolve this error, please screenshot this window and post it to the Pagoda Forum for help, forum address:https://forum.aapanel.com
  • \
\
'; - } else { - error_msg = jqXHR.responseText; - } - $('.layui-layer-padding').parents('.layer-anim').remove(); - $('.layui-layer-shade').remove(); - setTimeout(function () { - layer.open({ - title: false, - content: error_msg, - closeBtn: 2, - area: ['1000px', '800px'], - btn: false, - shadeClose: false, - shade: 0.3, - success: function () { - $('pre').scrollTop(100000000000); - }, - }); - }, 100); - } - }, - }); - } - }; - Utils.prototype.$checkIp = function (ip) { - var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; - return reg.test(ip); - }; - Utils.prototype.$checkIps = function (ips) { - var reg = /^\d{1, 3}\.\d{1, 3}\.\d{1, 3}\.\d{1, 3}(\/\d{1, 2})?$/; - return reg.test(ips); - }; - Utils.prototype.$checkDomainList = function (domainInfo) { - if (typeof domainInfo === 'string') domainInfo = domainInfo.split(','); - var reg = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0, 61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2, 6}$/; - for (var _i = 0, domainInfo_1 = domainInfo; _i < domainInfo_1.length; _i++) { - var item = domainInfo_1[_i]; - if (!reg.test(item)) return false; - } - return true; - }; - Utils.prototype.$checkPawComplexity = function (paw) { - var regList = { - length: /^.{8,}$/, - number: /\d+/, - lowercase: /[a-z]+/, - capital: /[A-Z]+/, - special: /[^A-Za-z0-9]+/, - }; - return false; - }; - Utils.prototype.$checkWeakCipher = function (paw) { - var checks = ['admin888', '123123123', '12345678', '45678910', '87654321', 'asdfghjkl', 'password', 'qwerqwer'], - pchecks = 'abcdefghijklmnopqrstuvwxyz1234567890', - lower = paw.toLowerCase(), - isError = ''; - for (var i = 0; i < pchecks.length; i++) { - var item = pchecks[i]; - checks.push(item + item + item + item + item + item + item + item); - } - for (var i = 0; i < checks.length; i++) { - var item = checks[i]; - if (lower === item) isError += '['.concat(item, ']'); - break; - } - return { - status: !isError, - msg: isError, - }; - }; - Utils.prototype.$checkUrl = function (url) { - var reg = /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/; - return reg.test(url); - }; - Utils.prototype.$checkPort = function (port) { - var reg = /^([1-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/; - return reg.test(port.toString()); - }; - Utils.prototype.$checkChinese = function (chinese) { - var reg = /^[\u4e00-\u9fa5]+$/; - return reg.test(chinese); - }; - Utils.prototype.$checkDomain = function (domain) { - var reg = /^([\w\u4e00-\u9fa5\-\*]{1, 100}\.){1, 10}([\w\u4e00-\u9fa5\-]{1, 24}|[\w\u4e00-\u9fa5\-]{1, 24}\.[\w\u4e00-\u9fa5\-]{1, 24})$/; - return reg.test(domain); - }; - Utils.prototype.$checkEmail = function (email) { - var reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; - return reg.test(email); - }; - Utils.prototype.$checkPhone = function (phone) { - var reg = /^1[3456789]\d{9}$/; - return reg.test(phone.toString()); - }; - Utils.prototype.$containsStr = function (str, subStr) { - if (typeof str !== 'string' && typeof subStr !== 'string') return false; - return str.indexOf(subStr) > -1; - }; - Utils.prototype.$replaceTrim = function (str) { - return str.replace(/\s+/g, ''); - }; - Utils.prototype.$ltrim = function (str, l) { - var reg = new RegExp('/(^\\' + l + '+)/g'); - return str.replace(reg, ''); - }; - Utils.prototype.$rtrim = function (str, r) { - var reg = new RegExp('/(\\' + r + '+$)/g'); - return str.replace(reg, ''); - }; - Utils.prototype.$formatTime = function (time, format) { - if (format === void 0) { - format = 'yyyy/MM/dd hh:mm:ss'; - } - var timestamp = ''; - if (typeof time === 'object') timestamp = time.getTime().toString(); - if (typeof time === 'string') timestamp = new Date(time).getTime().toString(); - if (typeof time === 'number') timestamp = time.toString(); - if (timestamp.length > 10) timestamp = timestamp.substring(0, 10); - var date = new Date(parseInt(timestamp) * 1000); - var o = { - 'M+': date.getMonth() + 1, - 'd+': date.getDate(), - 'h+': date.getHours(), - 'm+': date.getMinutes(), - 's+': date.getSeconds(), - 'q+': Math.floor((date.getMonth() + 3) / 3), - S: date.getMilliseconds(), - }; - if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); - for (var k in o) { - if (new RegExp('(' + k + ')').test(format)) { - format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); - } - } - return format; - }; - Utils.prototype.$formatSize = function (bytes, isUnit, fixed, endUnit) { - if (bytes === void 0) { - bytes = 0; - } - if (isUnit === void 0) { - isUnit = true; - } - if (fixed === void 0) { - fixed = 2; - } - if (endUnit === void 0) { - endUnit = ''; - } - if (typeof bytes === 'string') bytes = parseInt(bytes); - var unit = [' B', ' KB', ' MB', ' GB', 'TB'], - c = 1024; - for (var i = 0; i < unit.length; i++) { - var cUnit = unit[i]; - var val = bytes; - if (fixed !== 0 && i === 0) val = bytes.toFixed(fixed); - if (endUnit) { - if (cUnit.trim() == endUnit.trim()) { - if (endUnit) { - return val + cUnit; - } else { - return val; - } - } - } else { - if (bytes < c) { - if (isUnit) { - return val + cUnit; - } else { - return val; - } - } - } - bytes /= c; - } - }; - Utils.prototype.$formatPath = function (path) { - var reg = /(\\)/g; - path = path.replace(reg, '/'); - return path; - }; - Utils.prototype.$getFilePath = function (filename) { - if (filename === '/') return '/'; - filename = (filename + '/').replace(/\/\//g, '/'); - var arr = filename.split('/'), - last = arr[arr.length - 1]; - return filename.replace('/' + arr[arr.length - (last === '' ? 2 : 1)], ''); - }; - Utils.prototype.$getRandom = function (len) { - if (len === void 0) { - len = 32; - } - var $chars = 'AaBbCcDdEeFfGHhiJjKkLMmNnPpRSrTsWtXwYxZyz2345678', - maxPos = $chars.length; - var password = ''; - for (var i = 0; i < len; i++) { - password += $chars.charAt(Math.floor(Math.random() * maxPos)); - } - return password; - }; - Utils.prototype.$getRandomNum = function (min, max) { - if (min === void 0) { - min = 0; - } - if (max === void 0) { - max = 9; - } - return Math.floor(Math.random() * (max - min + 1) + min); - }; - Utils.prototype.$getStorage = function (key) { - return window.localStorage.getItem(key); - }; - Utils.prototype.$setStorage = function (key, value) { - window.localStorage.setItem(key, value); - }; - Utils.prototype.$removeStorage = function (key) { - window.localStorage.removeItem(key); - }; - Utils.prototype.$getCookie = function (name) { - var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); - var itExist = document.cookie.match(reg); - if (itExist) { - var val = unescape(itExist[2]); - return val == 'undefined' ? '' : val; - } else { - return null; - } - }; - Utils.prototype.$setCookie = function (name, value, time) { - if (time === void 0) { - time = 2592000000; - } - var date = '', - expires = new Date(); - expires.setTime(expires.getTime() + time); - date = expires.toGMTString(); - var isHttps = window.location.protocol === 'https:'; - var sameSite = ';Secure; Path=/; SameSite=None'; - document.cookie = name + '=' + escape(value) + ';expires=' + time + (isHttps ? sameSite : ''); - }; - Utils.prototype.$removeCookie = function (name) { - this.$setCookie(name, '', 0); - }; - Utils.prototype.$requestInit = function () { - var _this_1 = this; - var requestTokenHead = document.getElementById('request_token_head'); - var headers = {'x-http-token': '', 'x-cookie-token': ''}; - var httpToken = (requestTokenHead && requestTokenHead.getAttribute('token')) || ''; - var cookieToken = this.$getCookie('request_token') || ''; - httpToken && (headers['x-http-token'] = httpToken); - cookieToken && (headers['x-cookie-token'] = cookieToken); - if (httpToken) { - $.ajaxSetup({ - headers: headers, - error: function (XHR) { - return __awaiter(_this_1, void 0, void 0, function () { - var resText, resStatus, monitorStr, content, errorHead, isErrorHead, errorBody, tmp; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - (resText = XHR.responseText), (resStatus = XHR.status), (monitorStr = ['/static/favicon.ico', '/static/img/qrCode.png', '']), (content = ''); - if (resText) return [2, false]; - if ( - typeof resText == 'string' && - monitorStr.some(function (item) { - return resText.indexOf(item) > -1; - }) - ) - return [2, this.$refreshBrowser('/login')]; - (errorHead = 'We need to make sure this has a favicon so that the debugger does'), (isErrorHead = resText.indexOf(errorHead) > -1); - if (resStatus === 500 && resText.indexOf('运行时发生错误') > -1 && isErrorHead) { - if (resText.indexOf('请先绑定宝塔帐号!') > -1) { - this.$refreshBrowser('/bind?redirect='.concat(encodeURIComponent(window.location.href))); - return [2, false]; - } - if (isErrorHead) { - errorBody = resText.split('', ''); - tmp = errorBody.split('During handling of the above exception, another exception occurred:'); - errorBody = tmp[tmp.length - 1]; - content = - '
\n

Something went wrong, an error occurred while the panel was running!

\n
'.concat(
-                                                        errorBody.trim(),
-                                                        '
\n
    \n
  • Sorry, an unexpected error occurred while panel was running, please try to resolve this error in the following order:
  • \n
  • 1. Click Fix Panel in the upper right corner of [Home] and log out of the panel to log in again.
  • \n
  • 2. if the above attempts fail to lift this error, please screenshot this window to the aaPanel Forum to post for help, forum address: https://forum.aapanel.com
\n
' - ); - } else { - content = resText; - } - } - return [ - 4, - this.$open({ - title: false, - content: content, - area: ['1200px', '810px'], - btn: false, - }), - ]; - case 1: - _a.sent(); - return [2]; - } - }); - }); - }, - }); - } - }; - Utils.prototype.$send = function (param, param1, param2) { - var _this_1 = this; - if (param2 === void 0) { - param2 = ''; - } - return new Promise(function (resolve, reject) { - var config = { - url: '', - method: 'POST', - msg: false, - data: {}, - }, - loading = '', - loadT; - if (typeof param === 'string') { - var urls = param.split('/'); - config.url = '/'.concat(urls[0], '?action=').concat(urls[1]); - if (typeof param1 === 'string') { - loading = param1; - } else if (typeof param1 === 'object') { - config.data = param1; - loading = param2; - } - } else if (typeof param === 'object') { - config.url = param.url; - config.method = param.method || 'POST'; - config.msg = param.msg || false; - config.data = param.data || {}; - loading = param.loading || ''; - } - if (loading) loadT = _this_1.$load(''.concat(loading)); - $.ajax({ - url: config.url, - method: config.method, - data: config.data, - success: function (rdata) { - return __awaiter(_this_1, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (loadT) loadT.close(); - if (!(typeof rdata === 'object')) return [3, 3]; - if (!(typeof rdata.msg === 'string' && rdata.msg.indexOf('CSRF校验失败,请重新登录面板') > -1)) return [3, 2]; - return [4, this.$confirm({ - title: 'Tips', - msg: 'Panel login has expired, please login again!' - })]; - case 1: - _a.sent(); - layer.closeAll(); - this.$load('Logging out, please wait...'); - this.$refreshBrowser('/login?dologin=True'); - return [2, false]; - case 2: - if (typeof rdata.msg === 'string' && rdata.msg === '没有权限' && !rdata.status) { - layer.closeAll(); - this.$error(rdata.msg); - return [2, false]; - } - if (typeof rdata.status === 'boolean' && !rdata.status && rdata.msg && config.msg) this.$error(rdata.msg); - _a.label = 3; - case 3: - resolve(rdata); - return [2]; - } - }); - }); - }, - error: function (err) { - reject(err); - }, - }); - }); - }; - Utils.prototype.$apiInit = function (apiInfo) { - this.API = Object.assign(this.API, apiInfo); - }; - Utils.prototype.$request = function (info, param, config) { - var _this_1 = this; - if (param === void 0) { - param = {}; - } - return new Promise(function (resolve, reject) { - return __awaiter(_this_1, void 0, void 0, function () { - var url, loading, rdata, error_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (typeof info === 'string') info = this.API[info]; - if (typeof param === 'boolean') (config = param), (param = {}); - if (typeof param === 'object' && param.hasOwnProperty('loading') && param.hasOwnProperty('msg')) (config = param), (param = {}); - if (typeof config === 'boolean') config = {msg: config}; - if (typeof config === 'undefined') config = {msg: true, loading: true}; - (url = info[0]), (loading = info[1] || ''); - if (!config.loading) loading = ''; - _a.label = 1; - case 1: - _a.trys.push([1, 3, , 4]); - return [4, this.$send(url, param, loading)]; - case 2: - rdata = _a.sent(); - config.msg && typeof rdata.msg === 'string' && this.$msg(rdata); - resolve(rdata); - return [3, 4]; - case 3: - error_1 = _a.sent(); - reject(error_1); - return [3, 4]; - case 4: - return [2]; - } - }); - }); - }); - }; - Utils.prototype.$open = function (param) { - var _this_1 = this; - var _this = this; - return new Promise(function (resolve, reject) { - var id = 'virtual-'.concat(_this_1.$getRandom(5)); - var content = param.content, - success = param.success, - yes = param.yes, - btn2 = param.btn2, - cancel = param.cancel, - title = param.title, - shadeClose = param.shadeClose, - closeBtn = param.closeBtn, - skin = param.skin, - type = param.type; - var config = typeof param.content === 'string' ? '' : param.content; - var vm; - switch (typeof param.content) { - case 'string': - content = param.content; - break; - case 'object': - content = '
'); - break; - } - var layerConfig = { - title: title, - type: type || 1, - shadeClose: shadeClose, - content: content, - skin: skin, - closeBtn: closeBtn || 2, - success: function (layers, indexs) { - if (typeof config === 'object') { - var vm_1 = new vdom_1.default(Object.assign(config, {el: '#'.concat(id)})); - vm_1['$closeLayer'] = function () { - layer.close(indexs); - return indexs; - }; - _this.vDdomList[indexs] = vm_1; - } - success && success(layers, indexs, vm); - }, - yes: function (indexs) { - _this.layerIndex = indexs; - var config = { - close: function () { - layer.close(indexs); - return indexs; - }, - vm: _this.vDdomList[indexs], - }; - yes && yes(config); - resolve(config); - }, - cancel: function (indexs) { - _this.layerIndex = indexs; - if (cancel) { - cancel && cancel(); - } else { - reject({cancel: true, type: 'cancel'}); - } - delete _this.vDdomList[indexs]; - }, - btn2: function (indexs) { - _this.layerIndex = indexs; - if (btn2) { - btn2 && btn2(); - } else { - reject({cancel: true, type: 'btn2'}); - } - delete _this.vDdomList[indexs]; - }, - end: function () { - delete _this.vDdomList[_this.layerIndex]; - }, - }; - layer.open(Object.assign(param, layerConfig)); - }); - }; - Utils.prototype.$confirm = function (param) { - return new Promise(function (resolve, reject) { - var msg = ''; - if (param.hasOwnProperty('msg')) { - msg = param.msg; - delete param.msg; - } - layer.confirm( - msg, - Object.assign( - { - title: 'Tips', - icon: 3, - btn: [lan.public.confirm, lan.public.cancel], - shadeClose: false, - closeBtn: 2, - cancel: function () { - return reject({cancel: true}); - }, - }, - param - ), - function (indexs) { - return resolve(indexs); - }, - function () { - return reject({cancel: true}); - } - ); - }); - }; - Utils.prototype.$tips = function (param) { - var msg = '', - el = ''; - if (param.msg) { - msg = param.msg; - delete param.msg; - } - if (param.el) { - el = param.el; - delete param.el; - } - param.success = function (layero) { - var oldLeft = layero.css('left'); - oldLeft = oldLeft.substring(0, oldLeft.indexOf('px')); - layero.css('left', ''.concat(oldLeft - 10, 'px')); - }; - return { - layer: layer.tips(msg, el, Object.assign({tips: [1, 'red']}, param)), - close: function () { - layer.close(this.layer); - }, - }; - }; - Utils.prototype.$close = function (index) { - layer.close(index); - }; - Utils.prototype.$msg = function (param, param1) { - var config = { - time: 1500, - shade: 0.3, - shadeClose: true, - closeBtn: 0, - icon: 1, - }, - msg = ''; - if (typeof param === 'object') { - for (var key in config) { - if (Object.prototype.hasOwnProperty.call(config, key)) { - if (typeof param[key] !== 'undefined') config[key] = param[key]; - } - } - msg = param.msg + (param.msg_error || '') + (param.msg_solve || ''); - if (typeof msg == 'string' && typeof param.status === 'boolean') { - config.icon = typeof param.status === 'boolean' ? (param.status ? 1 : 2) : 1; - } - } else if (typeof param === 'string') { - msg = param; - if (typeof param1 === 'boolean' || typeof param1 === 'number') { - config.icon = typeof param1 === 'boolean' ? (param1 ? 1 : 2) : param1; - } - } - return { - layer: layer.msg(msg, config), - close: function () { - layer.close(this.layer); - }, - }; - }; - Utils.prototype.$error = function (msg) { - return this.$msg({msg: msg, icon: 2}); - }; - Utils.prototype.$warning = function (msg) { - return this.$msg({msg: msg, icon: 0}); - }; - Utils.prototype.$load = function (tips) { - if (tips === void 0) { - tips = 'Processing, please wait...'; - } - return this.$msg({msg: tips, icon: 16, time: 0, shade: [0.3, '#000']}); - }; - Utils.prototype.$verifySubmit = function (param, msg) { - var _this_1 = this; - return new Promise(function (resolve) { - var status = false; - status = typeof param === 'function' ? param() : param; - if (status) { - _this_1.$error(msg); - } else { - resolve(status); - } - }); - }; - Utils.prototype.$verifySubmitList = function (list) { - var _this_1 = this; - return new Promise(function (resolve) { - return __awaiter(_this_1, void 0, void 0, function () { - var status, i, element; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - status = true; - i = 0; - _a.label = 1; - case 1: - if (!(i < list.length)) return [3, 4]; - element = list[i]; - return [4, this.$verifySubmit(element[0], element[1])]; - case 2: - status = _a.sent(); - if (status) return [3, 4]; - _a.label = 3; - case 3: - i++; - return [3, 1]; - case 4: - if (!status) resolve(true); - return [2]; - } - }); - }); - }); - }; - Utils.prototype.$refreshBrowser = function (href, time) { - if (href === void 0) { - href = 1500; - } - if (time === void 0) { - time = 1500; - } - typeof href === 'number' && (time = href); - setTimeout(function () { - switch (typeof href) { - case 'string': - location.href = href; - break; - case 'number': - location.reload(); - break; - } - }, time); - }; - Utils.prototype.$require = function (moduleName) { - return new Promise(function (resolve, reject) { - try { - if (!Array.isArray(moduleName)) moduleName = [moduleName]; - require(moduleName, function () { - var param = {}; - for (var i = 0; i < arguments.length; i++) { - param[moduleName[i]] = arguments[i]; - } - resolve(param); - }); - } catch (error) { - reject(error); - } - }); - }; - Utils.prototype.$delay = function (time) { - if (time === void 0) { - time = 1000; - } - return new Promise(function (resolve) { - setTimeout(function () { - resolve(time); - }, time); - }); - }; - return Utils; - })(); - exports.default = Utils; + } else { + error_msg = jqXHR.responseText; + } + $('.layui-layer-padding').parents('.layer-anim').remove(); + $('.layui-layer-shade').remove(); + setTimeout(function () { + layer.open({ + title: false, + content: error_msg, + closeBtn: 2, + area: ['1000px', '800px'], + btn: false, + shadeClose: false, + shade: 0.3, + success: function () { + $('pre').scrollTop(100000000000); + }, + }); + }, 100); + } + }, + }); + } + }; + Utils.prototype.$checkIp = function (ip) { + var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; + return reg.test(ip); + }; + Utils.prototype.$checkIps = function (ips) { + var reg = /^\d{1, 3}\.\d{1, 3}\.\d{1, 3}\.\d{1, 3}(\/\d{1, 2})?$/; + return reg.test(ips); + }; + Utils.prototype.$checkDomainList = function (domainInfo) { + if (typeof domainInfo === 'string') domainInfo = domainInfo.split(','); + var reg = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0, 61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2, 6}$/; + for (var _i = 0, domainInfo_1 = domainInfo; _i < domainInfo_1.length; _i++) { + var item = domainInfo_1[_i]; + if (!reg.test(item)) return false; + } + return true; + }; + Utils.prototype.$checkPawComplexity = function (paw) { + var regList = { + length: /^.{8,}$/, + number: /\d+/, + lowercase: /[a-z]+/, + capital: /[A-Z]+/, + special: /[^A-Za-z0-9]+/, + }; + return false; + }; + Utils.prototype.$checkWeakCipher = function (paw) { + var checks = ['admin888', '123123123', '12345678', '45678910', '87654321', 'asdfghjkl', 'password', 'qwerqwer'], + pchecks = 'abcdefghijklmnopqrstuvwxyz1234567890', + lower = paw.toLowerCase(), + isError = ''; + for (var i = 0; i < pchecks.length; i++) { + var item = pchecks[i]; + checks.push(item + item + item + item + item + item + item + item); + } + for (var i = 0; i < checks.length; i++) { + var item = checks[i]; + if (lower === item) isError += '['.concat(item, ']'); + break; + } + return { + status: !isError, + msg: isError, + }; + }; + Utils.prototype.$checkUrl = function (url) { + var reg = /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/; + return reg.test(url); + }; + Utils.prototype.$checkPort = function (port) { + var reg = /^([1-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/; + return reg.test(port.toString()); + }; + Utils.prototype.$checkChinese = function (chinese) { + var reg = /^[\u4e00-\u9fa5]+$/; + return reg.test(chinese); + }; + Utils.prototype.$checkDomain = function (domain) { + var reg = /^([\w\u4e00-\u9fa5\-\*]{1, 100}\.){1, 10}([\w\u4e00-\u9fa5\-]{1, 24}|[\w\u4e00-\u9fa5\-]{1, 24}\.[\w\u4e00-\u9fa5\-]{1, 24})$/; + return reg.test(domain); + }; + Utils.prototype.$checkEmail = function (email) { + var reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; + return reg.test(email); + }; + Utils.prototype.$checkPhone = function (phone) { + var reg = /^1[3456789]\d{9}$/; + return reg.test(phone.toString()); + }; + Utils.prototype.$containsStr = function (str, subStr) { + if (typeof str !== 'string' && typeof subStr !== 'string') return false; + return str.indexOf(subStr) > -1; + }; + Utils.prototype.$replaceTrim = function (str) { + return str.replace(/\s+/g, ''); + }; + Utils.prototype.$ltrim = function (str, l) { + var reg = new RegExp('/(^\\' + l + '+)/g'); + return str.replace(reg, ''); + }; + Utils.prototype.$rtrim = function (str, r) { + var reg = new RegExp('/(\\' + r + '+$)/g'); + return str.replace(reg, ''); + }; + Utils.prototype.$formatTime = function (time, format) { + if (format === void 0) { + format = 'yyyy/MM/dd hh:mm:ss'; + } + var timestamp = ''; + if (typeof time === 'object') timestamp = time.getTime().toString(); + if (typeof time === 'string') timestamp = new Date(time).getTime().toString(); + if (typeof time === 'number') timestamp = time.toString(); + if (timestamp.length > 10) timestamp = timestamp.substring(0, 10); + var date = new Date(parseInt(timestamp) * 1000); + var o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds(), + }; + if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); + for (var k in o) { + if (new RegExp('(' + k + ')').test(format)) { + format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); + } + } + return format; + }; + Utils.prototype.$formatSize = function (bytes, isUnit, fixed, endUnit) { + if (bytes === void 0) { + bytes = 0; + } + if (isUnit === void 0) { + isUnit = true; + } + if (fixed === void 0) { + fixed = 2; + } + if (endUnit === void 0) { + endUnit = ''; + } + if (typeof bytes === 'string') bytes = parseInt(bytes); + var unit = [' B', ' KB', ' MB', ' GB', 'TB'], + c = 1024; + for (var i = 0; i < unit.length; i++) { + var cUnit = unit[i]; + var val = bytes; + if (fixed !== 0 && i === 0) val = bytes.toFixed(fixed); + if (endUnit) { + if (cUnit.trim() == endUnit.trim()) { + if (endUnit) { + return val + cUnit; + } else { + return val; + } + } + } else { + if (bytes < c) { + if (isUnit) { + return val + cUnit; + } else { + return val; + } + } + } + bytes /= c; + } + }; + Utils.prototype.$formatPath = function (path) { + var reg = /(\\)/g; + path = path.replace(reg, '/'); + return path; + }; + Utils.prototype.$getFilePath = function (filename) { + if (filename === '/') return '/'; + filename = (filename + '/').replace(/\/\//g, '/'); + var arr = filename.split('/'), + last = arr[arr.length - 1]; + return filename.replace('/' + arr[arr.length - (last === '' ? 2 : 1)], ''); + }; + Utils.prototype.$getRandom = function (len) { + if (len === void 0) { + len = 32; + } + var $chars = 'AaBbCcDdEeFfGHhiJjKkLMmNnPpRSrTsWtXwYxZyz2345678', + maxPos = $chars.length; + var password = ''; + for (var i = 0; i < len; i++) { + password += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return password; + }; + Utils.prototype.$getRandomNum = function (min, max) { + if (min === void 0) { + min = 0; + } + if (max === void 0) { + max = 9; + } + return Math.floor(Math.random() * (max - min + 1) + min); + }; + Utils.prototype.$getStorage = function (key) { + return window.localStorage.getItem(key); + }; + Utils.prototype.$setStorage = function (key, value) { + window.localStorage.setItem(key, value); + }; + Utils.prototype.$removeStorage = function (key) { + window.localStorage.removeItem(key); + }; + Utils.prototype.$getCookie = function (name) { + var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); + var itExist = document.cookie.match(reg); + if (itExist) { + var val = unescape(itExist[2]); + return val == 'undefined' ? '' : val; + } else { + return null; + } + }; + Utils.prototype.$setCookie = function (name, value, time) { + if (time === void 0) { + time = 2592000000; + } + var date = '', + expires = new Date(); + expires.setTime(expires.getTime() + time); + date = expires.toGMTString(); + var isHttps = window.location.protocol === 'https:'; + var sameSite = ';Secure; Path=/; SameSite=None'; + document.cookie = name + '=' + escape(value) + ';expires=' + time + (isHttps ? sameSite : ''); + }; + Utils.prototype.$removeCookie = function (name) { + this.$setCookie(name, '', 0); + }; + Utils.prototype.$requestInit = function () { + var _this_1 = this; + var requestTokenHead = document.getElementById('request_token_head'); + var headers = { 'x-http-token': '', 'x-cookie-token': '' }; + var httpToken = (requestTokenHead && requestTokenHead.getAttribute('token')) || ''; + var cookieToken = this.$getCookie('request_token') || ''; + httpToken && (headers['x-http-token'] = httpToken); + cookieToken && (headers['x-cookie-token'] = cookieToken); + if (httpToken) { + $.ajaxSetup({ + headers: headers, + error: function (XHR) { + return __awaiter(_this_1, void 0, void 0, function () { + var resText, resStatus, monitorStr, content, errorHead, isErrorHead, errorBody, tmp; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + (resText = XHR.responseText), (resStatus = XHR.status), (monitorStr = ['/static/favicon.ico', '/static/img/qrCode.png', '']), (content = ''); + if (resText) return [2, false]; + if ( + typeof resText == 'string' && + monitorStr.some(function (item) { + return resText.indexOf(item) > -1; + }) + ) + return [2, this.$refreshBrowser('/login')]; + (errorHead = 'We need to make sure this has a favicon so that the debugger does'), (isErrorHead = resText.indexOf(errorHead) > -1); + if (resStatus === 500 && resText.indexOf('运行时发生错误') > -1 && isErrorHead) { + if (resText.indexOf('请先绑定宝塔帐号!') > -1) { + this.$refreshBrowser('/bind?redirect='.concat(encodeURIComponent(window.location.href))); + return [2, false]; + } + if (isErrorHead) { + errorBody = resText.split('', ''); + tmp = errorBody.split('During handling of the above exception, another exception occurred:'); + errorBody = tmp[tmp.length - 1]; + content = + '
\n

Something went wrong, an error occurred while the panel was running!

\n
'.concat(
+														errorBody.trim(),
+														'
\n
    \n
  • Sorry, an unexpected error occurred while panel was running, please try to resolve this error in the following order:
  • \n
  • 1. Click Fix Panel in the upper right corner of [Home] and log out of the panel to log in again.
  • \n
  • 2. if the above attempts fail to lift this error, please screenshot this window to the aaPanel Forum to post for help, forum address: https://forum.aapanel.com
\n
' + ); + } else { + content = resText; + } + } + return [ + 4, + this.$open({ + title: false, + content: content, + area: ['1200px', '810px'], + btn: false, + }), + ]; + case 1: + _a.sent(); + return [2]; + } + }); + }); + }, + }); + } + }; + Utils.prototype.$send = function (param, param1, param2) { + var _this_1 = this; + if (param2 === void 0) { + param2 = ''; + } + return new Promise(function (resolve, reject) { + var config = { + url: '', + method: 'POST', + msg: false, + data: {}, + }, + loading = '', + loadT; + if (typeof param === 'string') { + var urls = param.split('/'); + config.url = '/'.concat(urls[0], '?action=').concat(urls[1]); + if (typeof param1 === 'string') { + loading = param1; + } else if (typeof param1 === 'object') { + config.data = param1; + loading = param2; + } + } else if (typeof param === 'object') { + config.url = param.url; + config.method = param.method || 'POST'; + config.msg = param.msg || false; + config.data = param.data || {}; + loading = param.loading || ''; + } + if (loading) loadT = _this_1.$load(''.concat(loading)); + $.ajax({ + url: config.url, + method: config.method, + data: config.data, + success: function (rdata) { + return __awaiter(_this_1, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (loadT) loadT.close(); + if (!(typeof rdata === 'object')) return [3, 3]; + if (!(typeof rdata.msg === 'string' && rdata.msg.indexOf('CSRF校验失败,请重新登录面板') > -1)) return [3, 2]; + return [4, this.$confirm({ title: 'Tips', msg: 'Panel login has expired, please login again!' })]; + case 1: + _a.sent(); + layer.closeAll(); + this.$load('Logging out, please wait...'); + this.$refreshBrowser('/login?dologin=True'); + return [2, false]; + case 2: + if (typeof rdata.msg === 'string' && rdata.msg === '没有权限' && !rdata.status) { + layer.closeAll(); + this.$error(rdata.msg); + return [2, false]; + } + if (typeof rdata.status === 'boolean' && !rdata.status && rdata.msg && config.msg) this.$error(rdata.msg); + _a.label = 3; + case 3: + resolve(rdata); + return [2]; + } + }); + }); + }, + error: function (err) { + reject(err); + }, + }); + }); + }; + Utils.prototype.$apiInit = function (apiInfo) { + this.API = Object.assign(this.API, apiInfo); + }; + Utils.prototype.$request = function (info, param, config) { + var _this_1 = this; + if (param === void 0) { + param = {}; + } + return new Promise(function (resolve, reject) { + return __awaiter(_this_1, void 0, void 0, function () { + var url, loading, rdata, error_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (typeof info === 'string') info = this.API[info]; + if (typeof param === 'boolean') (config = param), (param = {}); + if (typeof param === 'object' && param.hasOwnProperty('loading') && param.hasOwnProperty('msg')) (config = param), (param = {}); + if (typeof config === 'boolean') config = { msg: config }; + if (typeof config === 'undefined') config = { msg: true, loading: true }; + (url = info[0]), (loading = info[1] || ''); + if (!config.loading) loading = ''; + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4, this.$send(url, param, loading)]; + case 2: + rdata = _a.sent(); + config.msg && typeof rdata.msg === 'string' && this.$msg(rdata); + resolve(rdata); + return [3, 4]; + case 3: + error_1 = _a.sent(); + reject(error_1); + return [3, 4]; + case 4: + return [2]; + } + }); + }); + }); + }; + Utils.prototype.$open = function (param) { + var _this_1 = this; + var _this = this; + return new Promise(function (resolve, reject) { + var id = 'virtual-'.concat(_this_1.$getRandom(5)); + var content = param.content, + success = param.success, + yes = param.yes, + btn2 = param.btn2, + cancel = param.cancel, + title = param.title, + shadeClose = param.shadeClose, + closeBtn = param.closeBtn, + skin = param.skin, + type = param.type; + var config = typeof param.content === 'string' ? '' : param.content; + var vm; + switch (typeof param.content) { + case 'string': + content = param.content; + break; + case 'object': + content = '
'); + break; + } + var layerConfig = { + title: title, + type: type || 1, + shadeClose: shadeClose, + content: content, + skin: skin, + closeBtn: closeBtn || 2, + success: function (layers, indexs) { + if (typeof config === 'object') { + var vm_1 = new vdom_1.default(Object.assign(config, { el: '#'.concat(id) })); + vm_1['$closeLayer'] = function () { + layer.close(indexs); + return indexs; + }; + _this.vDdomList[indexs] = vm_1; + } + success && success(layers, indexs, vm); + }, + yes: function (indexs) { + _this.layerIndex = indexs; + var config = { + close: function () { + layer.close(indexs); + return indexs; + }, + vm: _this.vDdomList[indexs], + }; + yes && yes(config); + resolve(config); + }, + cancel: function (indexs) { + _this.layerIndex = indexs; + if (cancel) { + cancel && cancel(); + } else { + reject({ cancel: true, type: 'cancel' }); + } + delete _this.vDdomList[indexs]; + }, + btn2: function (indexs) { + _this.layerIndex = indexs; + if (btn2) { + btn2 && btn2(); + } else { + reject({ cancel: true, type: 'btn2' }); + } + delete _this.vDdomList[indexs]; + }, + end: function () { + delete _this.vDdomList[_this.layerIndex]; + }, + }; + layer.open(Object.assign(param, layerConfig)); + }); + }; + Utils.prototype.$confirm = function (param) { + return new Promise(function (resolve, reject) { + var msg = ''; + if (param.hasOwnProperty('msg')) { + msg = param.msg; + delete param.msg; + } + layer.confirm( + msg, + Object.assign( + { + title: 'Tips', + icon: 3, + btn: [lan.public.confirm, lan.public.cancel], + shadeClose: false, + closeBtn: 2, + cancel: function () { + return reject({ cancel: true }); + }, + }, + param + ), + function (indexs) { + return resolve(indexs); + }, + function () { + return reject({ cancel: true }); + } + ); + }); + }; + Utils.prototype.$tips = function (param) { + var msg = '', + el = ''; + if (param.msg) { + msg = param.msg; + delete param.msg; + } + if (param.el) { + el = param.el; + delete param.el; + } + param.success = function (layero) { + var oldLeft = layero.css('left'); + oldLeft = oldLeft.substring(0, oldLeft.indexOf('px')); + layero.css('left', ''.concat(oldLeft - 10, 'px')); + }; + return { + layer: layer.tips(msg, el, Object.assign({ tips: [1, 'red'] }, param)), + close: function () { + layer.close(this.layer); + }, + }; + }; + Utils.prototype.$close = function (index) { + layer.close(index); + }; + Utils.prototype.$msg = function (param, param1) { + var config = { + time: 1500, + shade: 0.3, + shadeClose: true, + closeBtn: 0, + icon: 1, + }, + msg = ''; + if (typeof param === 'object') { + for (var key in config) { + if (Object.prototype.hasOwnProperty.call(config, key)) { + if (typeof param[key] !== 'undefined') config[key] = param[key]; + } + } + msg = param.msg + (param.msg_error || '') + (param.msg_solve || ''); + if (typeof msg == 'string' && typeof param.status === 'boolean') { + config.icon = typeof param.status === 'boolean' ? (param.status ? 1 : 2) : 1; + } + } else if (typeof param === 'string') { + msg = param; + if (typeof param1 === 'boolean' || typeof param1 === 'number') { + config.icon = typeof param1 === 'boolean' ? (param1 ? 1 : 2) : param1; + } + } + return { + layer: layer.msg(msg, config), + close: function () { + layer.close(this.layer); + }, + }; + }; + Utils.prototype.$error = function (msg) { + return this.$msg({ msg: msg, icon: 2 }); + }; + Utils.prototype.$warning = function (msg) { + return this.$msg({ msg: msg, icon: 0 }); + }; + Utils.prototype.$load = function (tips) { + if (tips === void 0) { + tips = 'Processing, please wait...'; + } + return this.$msg({ msg: tips, icon: 16, time: 0, shade: [0.3, '#000'] }); + }; + Utils.prototype.$verifySubmit = function (param, msg) { + var _this_1 = this; + return new Promise(function (resolve) { + var status = false; + status = typeof param === 'function' ? param() : param; + if (status) { + _this_1.$error(msg); + } else { + resolve(status); + } + }); + }; + Utils.prototype.$verifySubmitList = function (list) { + var _this_1 = this; + return new Promise(function (resolve) { + return __awaiter(_this_1, void 0, void 0, function () { + var status, i, element; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + status = true; + i = 0; + _a.label = 1; + case 1: + if (!(i < list.length)) return [3, 4]; + element = list[i]; + return [4, this.$verifySubmit(element[0], element[1])]; + case 2: + status = _a.sent(); + if (status) return [3, 4]; + _a.label = 3; + case 3: + i++; + return [3, 1]; + case 4: + if (!status) resolve(true); + return [2]; + } + }); + }); + }); + }; + Utils.prototype.$refreshBrowser = function (href, time) { + if (href === void 0) { + href = 1500; + } + if (time === void 0) { + time = 1500; + } + typeof href === 'number' && (time = href); + setTimeout(function () { + switch (typeof href) { + case 'string': + window.parent.location.href = href; + break; + case 'number': + window.parent.location.reload(); + break; + } + }, time); + }; + Utils.prototype.$require = function (moduleName) { + return new Promise(function (resolve, reject) { + try { + if (!Array.isArray(moduleName)) moduleName = [moduleName]; + require(moduleName, function () { + var param = {}; + for (var i = 0; i < arguments.length; i++) { + param[moduleName[i]] = arguments[i]; + } + resolve(param); + }); + } catch (error) { + reject(error); + } + }); + }; + Utils.prototype.$delay = function (time) { + if (time === void 0) { + time = 1000; + } + return new Promise(function (resolve) { + setTimeout(function () { + resolve(time); + }, time); + }); + }; + return Utils; + })(); + exports.default = Utils; }); define('public/public', ['require', 'exports', 'snabbdom', 'public/utils'], function (require, exports, snabbdom_2, utils_1) { - 'use strict'; - Object.defineProperty(exports, '__esModule', {value: true}); - utils_1 = __importDefault(utils_1); - var Public = (function (_super) { - __extends(Public, _super); - - function Public() { - var _this_1 = _super.call(this) || this; - _this_1.apiInfo = { - getUserInfo: ['ssl/GetUserInfo', 'Getting bind account information, please wait...'], - unbindUserInfo: ['ssl/DelToken', 'Unbinding the pagoda account, please wait...'], - restartPanel: ['system/ReWeb', lan.public.the], - GetToken: ['ssl/GetToken', lan.config.token_get], - getFileDir: ['files/GetDir', lan.public.the], - getMsgConfig: ['config/get_settings2', 'Getting profile, please wait...'], - setTelegramConfig: ['config/set_tg_bot', 'The notification is being generated, please wait...'], - setDingDingConfig: ['config/set_msg_config&name=dingding', 'The notification is being generated, please wait...'], - clearTelegramConfig: ['config/del_tg_info', 'Deleting notification, please wait...'], - addMailAddress: ['config/add_mail_address', 'Please wait while creating recipient list...'], - delMailAddress: ['config/del_mail_list', 'Deleting email, please wait...'], - getMailList: ['config/get_settings2', lan.public.the], - setMailConfig: ['config/user_mail_send', 'The notification is being generated, please wait...'], - }; - _this_1.$apiInit(_this_1.apiInfo); - return _this_1; - } - - Public.prototype.fixedTableHead = function (el, height, isBorder) { - if (isBorder === void 0) { - isBorder = true; - } - $(el).css({border: 'none'}); - var data = {'overflow-y': 'auto', 'max-height': height}; - if (isBorder) data.border = '1px solid #ddd'; - $(el).parent().css(data); - $(el) - .parent() - .bind('scroll', function () { - var scrollTop = this.scrollTop; - $(this) - .find('thead') - .css({ - transform: 'translateY(' + scrollTop + 'px)', - position: 'relative', - 'z-index': '1', - }); - }); - }; - Public.prototype.bindBtAccount = function (isEdit) { - if (isEdit === void 0) { - isEdit = false; - } - var that = this; - var title = !isEdit ? lan.config.config_user_binding : lan.config.config_user_edit; - var bindBtn = !isEdit ? lan.config.binding : lan.public.edit; - this.$open({ - title: title, - area: ['420px', '360px'], - content: { - data: { - username: '', - password: '', - }, - template: function () { - return (0, snabbdom_2.jsx)( - 'div', - {class: this.$class('pd20 bt-form libLogin')}, - (0, snabbdom_2.jsx)('h4', {class: this.$class('c2 f18 text-center mtb20')}, lan.public_backup.bind_bt_account), - (0, snabbdom_2.jsx)('div', {class: {line: true}}, this.$input({ - model: 'username', - placeholder: lan.public.user - })), - (0, snabbdom_2.jsx)('div', {class: {line: true}}, this.$input({ - model: 'password', - type: 'password', - placeholder: lan.public.pass, - keyup: this.bindUserInfo.bind(this) - })), - (0, snabbdom_2.jsx)('div', {class: {line: true}}, this.$button({ - className: 'login-button', - title: bindBtn, - click: this.bindUserInfo.bind(this), - width: '360px', - height: '40px' - })), - (0, snabbdom_2.jsx)('p', {class: {'text-right': true}}, ' ', this.$link({ - title: lan.public_backup.no_account, - href: 'https://www.aapanel.com/user_admin/register' - })) - ); - }, - methods: { - bindUserInfo: function (ev) { - return __awaiter(this, void 0, void 0, function () { - var _a, username, password, param, rdata; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - if (ev.type === 'keyup' && ev.keyCode !== 13) return [2, false]; - (_a = this), (username = _a.username), (password = _a.password); - return [ - 4, - that.$verifySubmitList([ - [!username, 'Please input account'], - [!password, 'Please input password'], - ]), - ]; - case 1: - _b.sent(); - param = {username: username, password: password}; - return [4, that.$request('GetToken', param)]; - case 2: - rdata = _b.sent(); - that.$msg(rdata); - rdata.status && that.$refreshBrowser(); - return [2]; - } - }); - }); - }, - }, - }, - }).catch(function (err) { - }); - }; - Public.prototype.selectFileDir = function (id, type, success, default_path) { - var _this_1 = this; - if (type === void 0) { - type = 'all'; - } - this.$setCookie('SetName', ''); - if (typeof type !== 'string') (success = type), (type = 'dir'); - this.$open({ - area: '680px', - title: type === 'all' ? 'Select directories or files' : type === 'file' ? lan.bt.file : lan.bt.dir, - closeBtn: 2, - content: - "\n
\n
\n \n
") - .concat( - lan.bt.path, - "
\n
\n
\n
\n
\n
\n
\n
\n
\n
    \n
    \n \n \n \n \n \n \n \n \n \n \n \n
    " - ) - .concat(lan.bt.filename, "") - .concat(lan.bt.etime, "") - .concat(lan.bt.access, "") - .concat( - lan.bt.own, - "
    \n
    \n
    \n
    \n
    \n
    \n \n \n \n
    \n '), - success: function (layers, indexs) { - var el = null; - if (id.indexOf('.') === 0) el = $('.' + id); - if (id.indexOf('#') === 0) el = $('#' + id); - if (id.indexOf('.') !== 0 && id.indexOf('#') !== 0) el = $(id); - _this_1.fixedTableHead('.file-list .table', '100%', false); - $('#btn_back').on('click', function () { - var path = $('#PathPlace').find('span').text(); - path = _this_1.$rtrim(_this_1.$formatPath(path), '/'); - _this_1.getFileList(_this_1.$getFilePath(path), type); - }); - $('#bt_select').on('click', function () { - var path = _this_1.$formatPath($('#PathPlace').find('span').text()); - if (type === 'file' && !$('#tbody tr.active').length) { - layer.msg('Select the file first!', {icon: 0}); - return false; - } - if ($('#tbody tr').hasClass('active')) path = $('#tbody tr.active .bt_open_dir').attr('path'); - path = _this_1.$rtrim(path, '/'); - el.val(path).trigger('input'); - success && success(path); - layer.close(indexs); - }); - $('.closeLayer').on('click', function () { - layer.close(indexs); - }); - _this_1.getFileList(el.val() || '/www/wwwroot', type); - }, - }).catch(function (err) { - }); - }; - Public.prototype.getFileList = function (path, type) { - if (type === void 0) { - type = 'dir'; - } - return __awaiter(this, void 0, void 0, function () { - var _this, diskHtml, fileHtml, dirHtml, rdata, fileList, dirList, diskList, i_1, item, i_2, item, - unfoldList, dirName, i, item, unfoldList, fileName; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - (_this = this), (diskHtml = ''), (fileHtml = ''), (dirHtml = ''); - return [4, this.$request('getFileDir', {path: path, disk: true})]; - case 1: - rdata = _a.sent(); - (fileList = rdata.FILES), (dirList = rdata.DIR), (diskList = rdata.DISK); - for (i_1 = 0; i_1 < diskList.length; i_1++) { - item = diskList[i_1]; - diskHtml += '
     " + item.path + '
    '; - } - for (i_2 = 0; i_2 < dirList.length; i_2++) { - (item = dirList[i_2]), (unfoldList = item.split(';')); - dirName = unfoldList[0]; - if (dirName.length > 20) dirName = dirName.substring(0, 20) + '...'; - if (this.$checkChinese(dirName) && dirName.length > 10) dirName = dirName.substring(0, 10) + '...'; - dirHtml += - '' + - (type === 'all' || type === 'dir' ? '' : '') + - '" + - dirName + - '' + - this.$formatTime(parseInt(unfoldList[2])) + - '' + - unfoldList[3] + - '' + - unfoldList[4] + - ''; - } - for (i = 0; i < fileList.length; i++) { - (item = fileList[i]), (unfoldList = item.split(';')); - fileName = unfoldList[0]; - if (fileName.length > 20) fileName = fileName.substring(0, 20) + '...'; - if (this.$checkChinese(fileName) && fileName.length > 10) fileName = fileName.substring(0, 10) + '...'; - fileHtml += - '' + - (type === 'all' || type === 'file' ? '' : '') + - '" + - fileName + - '' + - this.$formatTime(parseInt(unfoldList[2])) + - '' + - unfoldList[3] + - '' + - unfoldList[4] + - ''; - } - $('#changecomlist').html(diskHtml); - $('.default').hide(); - $('.file-list').show(); - $('#tbody').html(dirHtml + fileHtml); - if (rdata.PATH.substr(rdata.PATH.length - 1, 1) != '/') { - rdata.PATH += '/'; - } - $('#PathPlace').find('span').html(rdata.PATH); - $('#tbody tr').click(function () { - if ($(this).find('td:eq(0) input').length > 0) { - if ($(this).hasClass('active')) { - $(this).removeClass('active'); - $(this).find('td:eq(0) input').prop('checked', false); - } else { - $(this).find('td:eq(0) input').prop('checked', true); - $(this).siblings().find('td:eq(0) input').prop('checked', false); - $(this).addClass('active').siblings().removeClass('active'); - } - } - }); - $('#changecomlist dd').click(function () { - _this.getFileList($(this).attr('path'), type); - }); - $('.bt_open_dir span').click(function () { - if ($(this).parent().data('type') == 'dir') _this.getFileList($(this).parent().attr('path'), type); - }); - return [2]; - } - }); - }); - }; - Public.prototype.setMessageChannelView = function (type, info) { - if (type === void 0) { - type = 0; - } - return __awaiter(this, void 0, void 0, function () { - var rdata, _a, setup, id, token, error_2; - var _this_1 = this; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _b.trys.push([0, 3, , 4]); - return [4, this.$request('getMsgConfig')]; - case 1: - rdata = _b.sent(); - (_a = rdata.telegram), (setup = _a.setup), (id = _a.my_id), (token = _a.bot_token); - console.log(rdata); - console.log(rdata); - return [ - 4, - this.$open({ - area: '600px', - title: 'Setting up notification', - skin: 'layer-channel-auth', - content: '
    \n
    \n
    \n

    Email

    \n

    DingDing

    \n

    Feishu

    \n

    Telegram

    \n
    \n
    \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n
    \n \n \n \n \n \n \n \n \n \n \n
    EmailOperating
    No Data
    \n
    \n
    \n
    \n
    \n\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t\t
    \n Name\n
    \n \n
    \n
    \n\t\t\t\t\t\t\t\t\t\t
    \n Url\n
    \n \n
    \n
    \n\t\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t' - ) - .concat( - setup ? '' : '', - '\n\t\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t\t
    \n Name\n
    \n \n
    \n
    \n\t\t\t\t\t\t\t\t\t\t
    \n Url\n
    \n \n
    \n
    \n\t\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t' - ) - .concat( - setup ? '' : '', - '\n\t\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t\t
    \n\t\t\t\t\t\t\t\t
    \n
    \n
    \n
    \n ID\n
    \n \n
    \n
    \n
    \n TOKEN\n
    \n \n
    \n
    \n \n \n ' - ) - .concat( - setup ? '' : '', - '\n
    \n
    \n
    \n
      \n
    • ID: Your telegram user ID
    • \n
    • Token: Your telegram bot token
    • \n
    • e.g: [ 12345677:AAAAAAAAA_a0VUo2jjr__CCCCDDD ] Help
    • \n
    \n
    \n
    \n
    \n
    \n
    ' - ), - success: function ($layer) { - $('.bt-w-menu p').click(function () { - var index = $(this).index(); - $(this).addClass('bgw').siblings().removeClass('bgw'); - $('.conter_box').eq(index).removeClass('hide').siblings().addClass('hide'); - }); - $('.addRecipient').on('click', function () { - return _this_1.addMessageMail(); - }); - $('.setMailMessageView').on('click', function () { - return _this_1.setMailMessageView(info); - }); - $('#receive_table').on('click', '.del_email', function (ev) { - return __awaiter(_this_1, void 0, void 0, function () { - var email, res, err_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - email = $(ev.target).data('mail'); - _a.label = 1; - case 1: - _a.trys.push([1, 6, , 7]); - return [4, this.$confirm({ - title: 'Delete Email ['.concat(email, ']'), - msg: 'Are your sure to delete the Email?' - })]; - case 2: - _a.sent(); - return [4, this.$request('delMailAddress', {email: email})]; - case 3: - res = _a.sent(); - if (!res.status) return [2]; - return [4, this.$delay()]; - case 4: - _a.sent(); - return [4, this.renderMailMessageList()]; - case 5: - _a.sent(); - return [3, 7]; - case 6: - err_1 = _a.sent(); - return [3, 7]; - case 7: - return [2]; - } - }); - }); - }); - $('.addTelegram').click(function () { - return __awaiter(_this_1, void 0, void 0, function () { - var id_1, token_1, res, err_2; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 3, , 4]); - id_1 = $('input[name="telegram_id"]').val(); - token_1 = $('input[name="telegram_token"]').val(); - return [4, this.$verifySubmit(id_1 == '' || token_1 == '', 'input box cannot be empty!')]; - case 1: - _a.sent(); - return [4, this.$request('setTelegramConfig', { - my_id: id_1, - bot_token: token_1 - })]; - case 2: - res = _a.sent(); - if (!res.status) return [2]; - $('.addTelegram').after(''); - this.setLinkText('.setMessageChannelTelegram', 'Telegram is set', 'btlink'); - return [3, 4]; - case 3: - err_2 = _a.sent(); - return [3, 4]; - case 4: - return [2]; - } - }); - }); - }); - $layer.on('click', '.delTelegram', function () { - return __awaiter(_this_1, void 0, void 0, function () { - var res, err_3; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 3, , 4]); - return [4, this.$confirm({ - title: 'Clear set', - msg: 'Delete Clear Settings?' - })]; - case 1: - _a.sent(); - return [4, this.$request('clearTelegramConfig')]; - case 2: - res = _a.sent(); - if (!res.status) return [2]; - $('.delTelegram').remove(); - $('input[name="telegram_id"]').val(''); - $('input[name="telegram_token"]').val(''); - this.setLinkText('.setMessageChannelTelegram', 'Telegram is not set', 'bt_warning'); - return [3, 4]; - case 3: - err_3 = _a.sent(); - return [3, 4]; - case 4: - return [2]; - } - }); - }); - }); - $('.addDingDing').click(function () { - return __awaiter(_this_1, void 0, void 0, function () { - var name_2, url, res, err_4; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 3, , 4]); - name_2 = $('input[name=chatName]').val(); - url = $('[name=dingding_url]').val(); - return [4, this.$verifySubmit(name_2 == '' || url == '', 'input box cannot be empty!')]; - case 1: - _a.sent(); - return [4, this.$request('setDingDingConfig', { - title: name_2, - url: url, - atall: 'True' - })]; - case 2: - res = _a.sent(); - if (!res.status) return [2]; - return [3, 4]; - case 3: - err_4 = _a.sent(); - return [3, 4]; - case 4: - return [2]; - } - }); - }); - }); - _this_1.fixedTableHead('#receive_table', '356px'); - _this_1.renderMailMessageList(); - }, - }), - ]; - case 2: - _b.sent(); - return [3, 4]; - case 3: - error_2 = _b.sent(); - return [3, 4]; - case 4: - return [2]; - } - }); - }); - }; - Public.prototype.setLinkText = function (el, text, type) { - var $el = $(el); - switch (type) { - case 'btlink': - $el.text(text).addClass('btlink').removeClass('bt_warning'); - break; - case 'bt_warning': - $el.text(text).addClass('bt_warning').removeClass('btlink'); - break; - } - }; - Public.prototype.addMessageMail = function () { - return __awaiter(this, void 0, void 0, function () { - var error_3; - var _this_1 = this; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - _a.trys.push([0, 2, , 3]); - return [ - 4, - this.$open({ - area: '400px', - title: 'Add recipient email', - btn: ['Create', 'Close'], - content: - '
    \n
    \n Recipient mailbox\n
    \n \n
    \n
    \n
    ', - yes: function (config) { - return __awaiter(_this_1, void 0, void 0, function () { - var email, rdata; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - email = $('input[name=creater_email_value]').val(); - return [ - 4, - this.$verifySubmitList([ - [email === '', 'Recipient mailbox cannot be empty!'], - [!this.$checkEmail(email), 'Recipient mailbox format is incorrect!'], - ]), - ]; - case 1: - _a.sent(); - return [4, this.$request('addMailAddress', {email: email})]; - case 2: - rdata = _a.sent(); - if (!rdata.status) return [3, 5]; - config.close(); - return [4, this.$delay()]; - case 3: - _a.sent(); - return [4, this.renderMailMessageList()]; - case 4: - _a.sent(); - _a.label = 5; - case 5: - return [2]; - } - }); - }); - }, - }), - ]; - case 1: - _a.sent(); - return [3, 3]; - case 2: - error_3 = _a.sent(); - return [3, 3]; - case 3: - return [2]; - } - }); - }); - }; - Public.prototype.setMailMessageView = function (info) { - return __awaiter(this, void 0, void 0, function () { - var rdata, _a, qq_mail, qq_stmp_pwd, hosts, port, verifyPortList, err_5; - var _this_1 = this; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _b.trys.push([0, 3, , 4]); - return [4, this.$request('getMsgConfig')]; - case 1: - rdata = _b.sent(); - (_a = rdata.user_mail.info.msg), (qq_mail = _a.qq_mail), (qq_stmp_pwd = _a.qq_stmp_pwd), (hosts = _a.hosts), (port = _a.port); - qq_mail = qq_mail || ''; - qq_stmp_pwd = qq_stmp_pwd || ''; - hosts = hosts || ''; - port = port || ''; - verifyPortList = ['25', '465', '587', '']; - return [ - 4, - this.$open({ - title: 'Set sender email information', - area: '466px', - content: - '
    \n
    \n Sender email\n
    \n
    \n
    \n SMTP password\n
    \n
    \n
    \n SMTP server\n
    \n
    \n
    \n SMTP port\n
    \n \n \n
    \n
    \n
      \n
    • 465 port is recommended, the protocol is SSL/TLS
    • \n
    • Port 25 is SMTP protocol, port 587 is STARTTLS protocol
    • \n
    \n
    \n ' - ) - .concat( - qq_mail ? '' : '', - '\n \n \n
    \n
    ' - ), - success: function (layers, indexs) { - return __awaiter(_this_1, void 0, void 0, function () { - var portSelect, mailPort; - var _this_1 = this; - return __generator(this, function (_a) { - portSelect = $('#port_select'); - mailPort = $('input[name=channel_email_port]'); - portSelect.change(function (ev) { - var that = $(ev.target), - mailPort = $('input[name=channel_email_port]'); - that.css('width', ev.target.value === 'other' ? '100px' : '300px'); - mailPort.css('display', ev.target.value === 'other' ? 'inline-block' : 'none'); - }); - $('.SetChannelEmail').click(function () { - return __awaiter(_this_1, void 0, void 0, function () { - var email, stmp_pwd, hosts, port, portSelectVal, rdata, text, - type; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - email = $('input[name="channel_email_value"]').val(); - stmp_pwd = $('input[name="channel_email_password"]').val(); - hosts = $('input[name="channel_email_server"]').val(); - (port = ''), (portSelectVal = portSelect.val()); - port = portSelectVal === 'other' ? mailPort.val() : portSelectVal; - return [ - 4, - this.$verifySubmitList([ - [!email, 'Email address cannot be empty!'], - [!stmp_pwd, 'STMP password cannot be empty!'], - [!hosts, 'STMP server address cannot be empty!'], - [!port, 'STMP server port cannot be empty!'], - [!this.$checkPort(port), 'STMP server port format is incorrect'], - ]), - ]; - case 1: - _a.sent(); - return [4, this.$request('setMailConfig', { - email: email, - stmp_pwd: stmp_pwd, - hosts: hosts, - port: port - })]; - case 2: - rdata = _a.sent(); - if (!rdata.status) return [2]; - layer.close(indexs); - info.isSetEmail = true; - text = info.mail ? 'Already set' : 'Not set'; - type = info.mail ? 'btlink' : 'bt_warning'; - this.setLinkText('.setMessageChannelMail', 'Email is set', 'btlink'); - this.setLinkText('.setAlarmMail', text, type); - return [4, this.$delay()]; - case 3: - _a.sent(); - return [4, this.renderMailMessageList()]; - case 4: - _a.sent(); - return [2]; - } - }); - }); - }); - $('.smtp_closeBtn').click(function () { - layer.close(indexs); - }); - $('.set_empty').click(function () { - return __awaiter(_this_1, void 0, void 0, function () { - var rdata; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - return [4, this.$request(['config/set_empty', 'notification, please wait...'], {type: 'mail'})]; - case 1: - rdata = _a.sent(); - if (!rdata.status) return [2]; - layer.close(indexs); - info.isSetEmail = false; - this.setLinkText('.setMessageChannelMail', 'Email is not set', 'bt_warning'); - this.setLinkText('.setAlarmMail', 'Email is not set', 'bt_warning'); - return [2]; - } - }); - }); - }); - return [2]; - }); - }); - }, - }), - ]; - case 2: - _b.sent(); - return [3, 4]; - case 3: - err_5 = _b.sent(); - return [3, 4]; - case 4: - return [2]; - } - }); - }); - }; - Public.prototype.renderMailMessageList = function () { - return __awaiter(this, void 0, void 0, function () { - var rdata, _html, _list, i, item; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - return [4, this.$request('getMailList', {loading: true, msg: false})]; - case 1: - rdata = _a.sent(); - (_html = ''), (_list = rdata.user_mail.mail_list); - if (_list.length > 0) { - for (i = 0; i < _list.length; i++) { - item = _list[i]; - _html += '\n ' - .concat(item, '.\n \n Del\n \n '); - } - } else { - _html = 'No Data'; - } - $('#receive_table tbody').html(_html); - return [2]; - } - }); - }); - }; - return Public; - })(utils_1.default); - exports.default = Public; + 'use strict'; + Object.defineProperty(exports, '__esModule', { value: true }); + utils_1 = __importDefault(utils_1); + var Public = (function (_super) { + __extends(Public, _super); + function Public() { + var _this_1 = _super.call(this) || this; + _this_1.apiInfo = { + getUserInfo: ['ssl/GetUserInfo', 'Getting bind account information, please wait...'], + unbindUserInfo: ['ssl/DelToken', 'Unbinding the pagoda account, please wait...'], + restartPanel: ['system/ReWeb', lan.public.the], + GetToken: ['ssl/GetToken', lan.config.token_get], + getFileDir: ['files/GetDir', lan.public.the], + getMsgConfig: ['config/get_settings2', 'Getting profile, please wait...'], + setTelegramConfig: ['config/set_tg_bot', 'The notification is being generated, please wait...'], + setDingDingConfig: ['config/set_msg_config&name=dingding', 'The notification is being generated, please wait...'], + clearTelegramConfig: ['config/del_tg_info', 'Deleting notification, please wait...'], + addMailAddress: ['config/add_mail_address', 'Please wait while creating recipient list...'], + delMailAddress: ['config/del_mail_list', 'Deleting email, please wait...'], + getMailList: ['config/get_settings2', lan.public.the], + setMailConfig: ['config/user_mail_send', 'The notification is being generated, please wait...'], + }; + _this_1.$apiInit(_this_1.apiInfo); + return _this_1; + } + Public.prototype.fixedTableHead = function (el, height, isBorder) { + if (isBorder === void 0) { + isBorder = true; + } + $(el).css({ border: 'none' }); + var data = { 'overflow-y': 'auto', 'max-height': height }; + if (isBorder) data.border = '1px solid #ddd'; + $(el).parent().css(data); + $(el) + .parent() + .bind('scroll', function () { + var scrollTop = this.scrollTop; + $(this) + .find('thead') + .css({ + transform: 'translateY(' + scrollTop + 'px)', + position: 'relative', + 'z-index': '1', + }); + }); + }; + Public.prototype.bindBtAccount = function (isEdit) { + if (isEdit === void 0) { + isEdit = false; + } + var that = this; + var title = !isEdit ? lan.config.config_user_binding : lan.config.config_user_edit; + var bindBtn = !isEdit ? lan.config.binding : lan.public.edit; + this.$open({ + title: title, + area: ['420px', '360px'], + content: { + data: { + username: '', + password: '', + }, + template: function () { + return (0, snabbdom_2.jsx)( + 'div', + { class: this.$class('pd20 bt-form libLogin') }, + (0, snabbdom_2.jsx)('h4', { class: this.$class('c2 f18 text-center mtb20') }, lan.public_backup.bind_bt_account), + (0, snabbdom_2.jsx)('div', { class: { line: true } }, this.$input({ model: 'username', placeholder: lan.public.user })), + (0, snabbdom_2.jsx)('div', { class: { line: true } }, this.$input({ model: 'password', type: 'password', placeholder: lan.public.pass, keyup: this.bindUserInfo.bind(this) })), + (0, snabbdom_2.jsx)('div', { class: { line: true } }, this.$button({ className: 'login-button', title: bindBtn, click: this.bindUserInfo.bind(this), width: '360px', height: '40px' })), + (0, snabbdom_2.jsx)('p', { class: { 'text-right': true } }, ' ', this.$link({ title: lan.public_backup.no_account, href: 'https://brandnew.aapanel.com/user_admin/register' })) + ); + }, + methods: { + bindUserInfo: function (ev) { + return __awaiter(this, void 0, void 0, function () { + var _a, username, password, param, rdata; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (ev.type === 'keyup' && ev.keyCode !== 13) return [2, false]; + (_a = this), (username = _a.username), (password = _a.password); + return [ + 4, + that.$verifySubmitList([ + [!username, 'Please input account'], + [!password, 'Please input password'], + ]), + ]; + case 1: + _b.sent(); + param = { username: username, password: password }; + return [4, that.$request('GetToken', param)]; + case 2: + rdata = _b.sent(); + that.$msg(rdata); + rdata.status && that.$refreshBrowser(); + return [2]; + } + }); + }); + }, + }, + }, + }).catch(function (err) {}); + }; + Public.prototype.selectFileDir = function (id, type, success, default_path) { + var _this_1 = this; + if (type === void 0) { + type = 'all'; + } + this.$setCookie('SetName', ''); + if (typeof type !== 'string') (success = type), (type = 'dir'); + this.$open({ + area: '680px', + title: type === 'all' ? 'Select directories or files' : type === 'file' ? lan.bt.file : lan.bt.dir, + closeBtn: 2, + content: + "\n
    \n
    \n \n
    ") + .concat( + lan.bt.path, + "
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
      \n
      \n \n \n \n \n \n \n \n \n \n \n \n
      " + ) + .concat(lan.bt.filename, "") + .concat(lan.bt.etime, "") + .concat(lan.bt.access, "") + .concat( + lan.bt.own, + "
      \n
      \n
      \n
      \n
      \n
      \n \n \n \n
      \n '), + success: function (layers, indexs) { + var el = null; + if (id.indexOf('.') === 0) el = $('.' + id); + if (id.indexOf('#') === 0) el = $('#' + id); + if (id.indexOf('.') !== 0 && id.indexOf('#') !== 0) el = $(id); + _this_1.fixedTableHead('.file-list .table', '100%', false); + $('#btn_back').on('click', function () { + var path = $('#PathPlace').find('span').text(); + path = _this_1.$rtrim(_this_1.$formatPath(path), '/'); + _this_1.getFileList(_this_1.$getFilePath(path), type); + }); + $('#bt_select').on('click', function () { + var path = _this_1.$formatPath($('#PathPlace').find('span').text()); + if (type === 'file' && !$('#tbody tr.active').length) { + layer.msg('Select the file first!', { icon: 0 }); + return false; + } + if ($('#tbody tr').hasClass('active')) path = $('#tbody tr.active .bt_open_dir').attr('path'); + path = _this_1.$rtrim(path, '/'); + el.val(path).trigger('input'); + success && success(path); + layer.close(indexs); + }); + $('.closeLayer').on('click', function () { + layer.close(indexs); + }); + _this_1.getFileList(el.val() || '/www/wwwroot', type); + }, + }).catch(function (err) {}); + }; + Public.prototype.getFileList = function (path, type) { + if (type === void 0) { + type = 'dir'; + } + return __awaiter(this, void 0, void 0, function () { + var _this, diskHtml, fileHtml, dirHtml, rdata, fileList, dirList, diskList, i_1, item, i_2, item, unfoldList, dirName, i, item, unfoldList, fileName; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + (_this = this), (diskHtml = ''), (fileHtml = ''), (dirHtml = ''); + return [4, this.$request('getFileDir', { path: path, disk: true })]; + case 1: + rdata = _a.sent(); + (fileList = rdata.FILES), (dirList = rdata.DIR), (diskList = rdata.DISK); + for (i_1 = 0; i_1 < diskList.length; i_1++) { + item = diskList[i_1]; + diskHtml += '
       " + item.path + '
      '; + } + for (i_2 = 0; i_2 < dirList.length; i_2++) { + (item = dirList[i_2]), (unfoldList = item.split(';')); + dirName = unfoldList[0]; + if (dirName.length > 20) dirName = dirName.substring(0, 20) + '...'; + if (this.$checkChinese(dirName) && dirName.length > 10) dirName = dirName.substring(0, 10) + '...'; + dirHtml += + '' + + (type === 'all' || type === 'dir' ? '' : '') + + '" + + dirName + + '' + + this.$formatTime(parseInt(unfoldList[2])) + + '' + + unfoldList[3] + + '' + + unfoldList[4] + + ''; + } + for (i = 0; i < fileList.length; i++) { + (item = fileList[i]), (unfoldList = item.split(';')); + fileName = unfoldList[0]; + if (fileName.length > 20) fileName = fileName.substring(0, 20) + '...'; + if (this.$checkChinese(fileName) && fileName.length > 10) fileName = fileName.substring(0, 10) + '...'; + fileHtml += + '' + + (type === 'all' || type === 'file' ? '' : '') + + '" + + fileName + + '' + + this.$formatTime(parseInt(unfoldList[2])) + + '' + + unfoldList[3] + + '' + + unfoldList[4] + + ''; + } + $('#changecomlist').html(diskHtml); + $('.default').hide(); + $('.file-list').show(); + $('#tbody').html(dirHtml + fileHtml); + if (rdata.PATH.substr(rdata.PATH.length - 1, 1) != '/') { + rdata.PATH += '/'; + } + $('#PathPlace').find('span').html(rdata.PATH); + $('#tbody tr').click(function () { + if ($(this).find('td:eq(0) input').length > 0) { + if ($(this).hasClass('active')) { + $(this).removeClass('active'); + $(this).find('td:eq(0) input').prop('checked', false); + } else { + $(this).find('td:eq(0) input').prop('checked', true); + $(this).siblings().find('td:eq(0) input').prop('checked', false); + $(this).addClass('active').siblings().removeClass('active'); + } + } + }); + $('#changecomlist dd').click(function () { + _this.getFileList($(this).attr('path'), type); + }); + $('.bt_open_dir span').click(function () { + if ($(this).parent().data('type') == 'dir') _this.getFileList($(this).parent().attr('path'), type); + }); + return [2]; + } + }); + }); + }; + Public.prototype.setMessageChannelView = function (type, info) { + if (type === void 0) { + type = 0; + } + return __awaiter(this, void 0, void 0, function () { + var rdata, _a, setup, id, token, error_2; + var _this_1 = this; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _b.trys.push([0, 3, , 4]); + return [4, this.$request('getMsgConfig')]; + case 1: + rdata = _b.sent(); + (_a = rdata.telegram), (setup = _a.setup), (id = _a.my_id), (token = _a.bot_token); + console.log(rdata); + console.log(rdata); + return [ + 4, + this.$open({ + area: '600px', + title: 'Setting up notification', + skin: 'layer-channel-auth', + content: '
      \n
      \n
      \n

      Email

      \n

      DingDing

      \n

      Feishu

      \n

      Telegram

      \n
      \n
      \n
      \n
      \n
      \n
      \n \n \n
      \n
      \n
      \n \n \n \n \n \n \n \n \n \n \n
      EmailOperating
      No Data
      \n
      \n
      \n
      \n
      \n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t\t
      \n Name\n
      \n \n
      \n
      \n\t\t\t\t\t\t\t\t\t\t
      \n Url\n
      \n \n
      \n
      \n\t\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t' + ) + .concat( + setup ? '' : '', + '\n\t\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t\t
      \n Name\n
      \n \n
      \n
      \n\t\t\t\t\t\t\t\t\t\t
      \n Url\n
      \n \n
      \n
      \n\t\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t' + ) + .concat( + setup ? '' : '', + '\n\t\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t
      \n
      \n
      \n
      \n ID\n
      \n \n
      \n
      \n
      \n TOKEN\n
      \n \n
      \n
      \n \n \n ' + ) + .concat( + setup ? '' : '', + '\n
      \n
      \n
      \n
        \n
      • ID: Your telegram user ID
      • \n
      • Token: Your telegram bot token
      • \n
      • e.g: [ 12345677:AAAAAAAAA_a0VUo2jjr__CCCCDDD ] Help
      • \n
      \n
      \n
      \n
      \n
      \n
      ' + ), + success: function ($layer) { + $('.bt-w-menu p').click(function () { + var index = $(this).index(); + $(this).addClass('bgw').siblings().removeClass('bgw'); + $('.conter_box').eq(index).removeClass('hide').siblings().addClass('hide'); + }); + $('.addRecipient').on('click', function () { + return _this_1.addMessageMail(); + }); + $('.setMailMessageView').on('click', function () { + return _this_1.setMailMessageView(info); + }); + $('#receive_table').on('click', '.del_email', function (ev) { + return __awaiter(_this_1, void 0, void 0, function () { + var email, res, err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + email = $(ev.target).data('mail'); + _a.label = 1; + case 1: + _a.trys.push([1, 6, , 7]); + return [4, this.$confirm({ title: 'Delete Email ['.concat(email, ']'), msg: 'Are your sure to delete the Email?' })]; + case 2: + _a.sent(); + return [4, this.$request('delMailAddress', { email: email })]; + case 3: + res = _a.sent(); + if (!res.status) return [2]; + return [4, this.$delay()]; + case 4: + _a.sent(); + return [4, this.renderMailMessageList()]; + case 5: + _a.sent(); + return [3, 7]; + case 6: + err_1 = _a.sent(); + return [3, 7]; + case 7: + return [2]; + } + }); + }); + }); + $('.addTelegram').click(function () { + return __awaiter(_this_1, void 0, void 0, function () { + var id_1, token_1, res, err_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 3, , 4]); + id_1 = $('input[name="telegram_id"]').val(); + token_1 = $('input[name="telegram_token"]').val(); + return [4, this.$verifySubmit(id_1 == '' || token_1 == '', 'input box cannot be empty!')]; + case 1: + _a.sent(); + return [4, this.$request('setTelegramConfig', { my_id: id_1, bot_token: token_1 })]; + case 2: + res = _a.sent(); + if (!res.status) return [2]; + $('.addTelegram').after(''); + this.setLinkText('.setMessageChannelTelegram', 'Telegram is set', 'btlink'); + return [3, 4]; + case 3: + err_2 = _a.sent(); + return [3, 4]; + case 4: + return [2]; + } + }); + }); + }); + $layer.on('click', '.delTelegram', function () { + return __awaiter(_this_1, void 0, void 0, function () { + var res, err_3; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 3, , 4]); + return [4, this.$confirm({ title: 'Clear set', msg: 'Delete Clear Settings?' })]; + case 1: + _a.sent(); + return [4, this.$request('clearTelegramConfig')]; + case 2: + res = _a.sent(); + if (!res.status) return [2]; + $('.delTelegram').remove(); + $('input[name="telegram_id"]').val(''); + $('input[name="telegram_token"]').val(''); + this.setLinkText('.setMessageChannelTelegram', 'Telegram is not set', 'bt_warning'); + return [3, 4]; + case 3: + err_3 = _a.sent(); + return [3, 4]; + case 4: + return [2]; + } + }); + }); + }); + $('.addDingDing').click(function () { + return __awaiter(_this_1, void 0, void 0, function () { + var name_2, url, res, err_4; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 3, , 4]); + name_2 = $('input[name=chatName]').val(); + url = $('[name=dingding_url]').val(); + return [4, this.$verifySubmit(name_2 == '' || url == '', 'input box cannot be empty!')]; + case 1: + _a.sent(); + return [4, this.$request('setDingDingConfig', { title: name_2, url: url, atall: 'True' })]; + case 2: + res = _a.sent(); + if (!res.status) return [2]; + return [3, 4]; + case 3: + err_4 = _a.sent(); + return [3, 4]; + case 4: + return [2]; + } + }); + }); + }); + _this_1.fixedTableHead('#receive_table', '356px'); + _this_1.renderMailMessageList(); + }, + }), + ]; + case 2: + _b.sent(); + return [3, 4]; + case 3: + error_2 = _b.sent(); + return [3, 4]; + case 4: + return [2]; + } + }); + }); + }; + Public.prototype.setLinkText = function (el, text, type) { + var $el = $(el); + switch (type) { + case 'btlink': + $el.text(text).addClass('btlink').removeClass('bt_warning'); + break; + case 'bt_warning': + $el.text(text).addClass('bt_warning').removeClass('btlink'); + break; + } + }; + Public.prototype.addMessageMail = function () { + return __awaiter(this, void 0, void 0, function () { + var error_3; + var _this_1 = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 3]); + return [ + 4, + this.$open({ + area: '400px', + title: 'Add recipient email', + btn: ['Create', 'Close'], + content: + '
      \n
      \n Recipient mailbox\n
      \n \n
      \n
      \n
      ', + yes: function (config) { + return __awaiter(_this_1, void 0, void 0, function () { + var email, rdata; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + email = $('input[name=creater_email_value]').val(); + return [ + 4, + this.$verifySubmitList([ + [email === '', 'Recipient mailbox cannot be empty!'], + [!this.$checkEmail(email), 'Recipient mailbox format is incorrect!'], + ]), + ]; + case 1: + _a.sent(); + return [4, this.$request('addMailAddress', { email: email })]; + case 2: + rdata = _a.sent(); + if (!rdata.status) return [3, 5]; + config.close(); + return [4, this.$delay()]; + case 3: + _a.sent(); + return [4, this.renderMailMessageList()]; + case 4: + _a.sent(); + _a.label = 5; + case 5: + return [2]; + } + }); + }); + }, + }), + ]; + case 1: + _a.sent(); + return [3, 3]; + case 2: + error_3 = _a.sent(); + return [3, 3]; + case 3: + return [2]; + } + }); + }); + }; + Public.prototype.setMailMessageView = function (info) { + return __awaiter(this, void 0, void 0, function () { + var rdata, _a, qq_mail, qq_stmp_pwd, hosts, port, verifyPortList, err_5; + var _this_1 = this; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + _b.trys.push([0, 3, , 4]); + return [4, this.$request('getMsgConfig')]; + case 1: + rdata = _b.sent(); + (_a = rdata.user_mail.info.msg), (qq_mail = _a.qq_mail), (qq_stmp_pwd = _a.qq_stmp_pwd), (hosts = _a.hosts), (port = _a.port); + qq_mail = qq_mail || ''; + qq_stmp_pwd = qq_stmp_pwd || ''; + hosts = hosts || ''; + port = port || ''; + verifyPortList = ['25', '465', '587', '']; + return [ + 4, + this.$open({ + title: 'Set sender email information', + area: '466px', + content: + '
      \n
      \n Sender email\n
      \n
      \n
      \n SMTP password\n
      \n
      \n
      \n SMTP server\n
      \n
      \n
      \n SMTP port\n
      \n \n \n
      \n
      \n
        \n
      • 465 port is recommended, the protocol is SSL/TLS
      • \n
      • Port 25 is SMTP protocol, port 587 is STARTTLS protocol
      • \n
      \n
      \n ' + ) + .concat( + qq_mail ? '' : '', + '\n \n \n
      \n
      ' + ), + success: function (layers, indexs) { + return __awaiter(_this_1, void 0, void 0, function () { + var portSelect, mailPort; + var _this_1 = this; + return __generator(this, function (_a) { + portSelect = $('#port_select'); + mailPort = $('input[name=channel_email_port]'); + portSelect.change(function (ev) { + var that = $(ev.target), + mailPort = $('input[name=channel_email_port]'); + that.css('width', ev.target.value === 'other' ? '100px' : '300px'); + mailPort.css('display', ev.target.value === 'other' ? 'inline-block' : 'none'); + }); + $('.SetChannelEmail').click(function () { + return __awaiter(_this_1, void 0, void 0, function () { + var email, stmp_pwd, hosts, port, portSelectVal, rdata, text, type; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + email = $('input[name="channel_email_value"]').val(); + stmp_pwd = $('input[name="channel_email_password"]').val(); + hosts = $('input[name="channel_email_server"]').val(); + (port = ''), (portSelectVal = portSelect.val()); + port = portSelectVal === 'other' ? mailPort.val() : portSelectVal; + return [ + 4, + this.$verifySubmitList([ + [!email, 'Email address cannot be empty!'], + [!stmp_pwd, 'STMP password cannot be empty!'], + [!hosts, 'STMP server address cannot be empty!'], + [!port, 'STMP server port cannot be empty!'], + [!this.$checkPort(port), 'STMP server port format is incorrect'], + ]), + ]; + case 1: + _a.sent(); + return [4, this.$request('setMailConfig', { email: email, stmp_pwd: stmp_pwd, hosts: hosts, port: port })]; + case 2: + rdata = _a.sent(); + if (!rdata.status) return [2]; + layer.close(indexs); + info.isSetEmail = true; + text = info.mail ? 'Already set' : 'Not set'; + type = info.mail ? 'btlink' : 'bt_warning'; + this.setLinkText('.setMessageChannelMail', 'Email is set', 'btlink'); + this.setLinkText('.setAlarmMail', text, type); + return [4, this.$delay()]; + case 3: + _a.sent(); + return [4, this.renderMailMessageList()]; + case 4: + _a.sent(); + return [2]; + } + }); + }); + }); + $('.smtp_closeBtn').click(function () { + layer.close(indexs); + }); + $('.set_empty').click(function () { + return __awaiter(_this_1, void 0, void 0, function () { + var rdata; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + return [4, this.$request(['config/set_empty', 'notification, please wait...'], { type: 'mail' })]; + case 1: + rdata = _a.sent(); + if (!rdata.status) return [2]; + layer.close(indexs); + info.isSetEmail = false; + this.setLinkText('.setMessageChannelMail', 'Email is not set', 'bt_warning'); + this.setLinkText('.setAlarmMail', 'Email is not set', 'bt_warning'); + return [2]; + } + }); + }); + }); + return [2]; + }); + }); + }, + }), + ]; + case 2: + _b.sent(); + return [3, 4]; + case 3: + err_5 = _b.sent(); + return [3, 4]; + case 4: + return [2]; + } + }); + }); + }; + Public.prototype.renderMailMessageList = function () { + return __awaiter(this, void 0, void 0, function () { + var rdata, _html, _list, i, item; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + return [4, this.$request('getMailList', { loading: true, msg: false })]; + case 1: + rdata = _a.sent(); + (_html = ''), (_list = rdata.user_mail.mail_list); + if (_list.length > 0) { + for (i = 0; i < _list.length; i++) { + item = _list[i]; + _html += '\n ' + .concat(item, '.\n \n Del\n \n '); + } + } else { + _html = 'No Data'; + } + $('#receive_table tbody').html(_html); + return [2]; + } + }); + }); + }; + return Public; + })(utils_1.default); + exports.default = Public; }); diff --git a/BTPanel/static/bootstrap-3.3.5/css/bootstrap.min.css b/BTPanel/static/bootstrap-3.3.5/css/bootstrap.min.css index da4797c2..f3605bee 100644 --- a/BTPanel/static/bootstrap-3.3.5/css/bootstrap.min.css +++ b/BTPanel/static/bootstrap-3.3.5/css/bootstrap.min.css @@ -2,4 +2,4 @@ * Bootstrap v3.3.5 (http://getbootstrap.com) * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-vip:before{content:"\e600"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before,.glyphicon-True:before{content:"\e072"}.glyphicon-pause:before,.glyphicon-False:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:none}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:none}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:none}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#fff;background-color:#10952a;border-color:#398439}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#fff;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#20a53a;border-color:#20a53a}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#10952a;border-color:#255625}.btn-success:hover{color:#fff;background-color:#10952a;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#10952a;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#20a53a;border-color:#20a53a}.btn-success .badge{color:#20a53a;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#20a53a}.label-success[href]:focus,.label-success[href]:hover{background-color:#10952a}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#20a53a}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\0}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;z-index:1;text-align:right;white-space:nowrap}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select .selectpicker:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select .selectpicker:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle:before{content:'';display:inline-block}.bootstrap-select .dropdown-toggle .filter-option{position:absolute;top:0;left:0;padding-top:inherit;padding-right:inherit;padding-bottom:inherit;padding-left:inherit;height:100%;width:100%;text-align:left}.bootstrap-select .dropdown-toggle .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-vip:before{content:"\e600"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before,.glyphicon-True:before{content:"\e072"}.glyphicon-pause:before,.glyphicon-False:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{text-decoration:none}a:focus{outline:none}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:none}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:none}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#555;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#fff;background-color:#10952a;border-color:#398439}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#fff;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#20a53a;border-color:#20a53a}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#10952a;border-color:#255625}.btn-success:hover{color:#fff;background-color:#10952a;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#10952a;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#20a53a;border-color:#20a53a}.btn-success .badge{color:#20a53a;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#20a53a}.label-success[href]:focus,.label-success[href]:hover{background-color:#10952a}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#20a53a}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}.bootstrap-select>select.bs-select-hidden,select.bs-select-hidden,select.selectpicker{display:none!important}.bootstrap-select{width:220px\0}.bootstrap-select>.dropdown-toggle{position:relative;width:100%;z-index:1;text-align:right;white-space:nowrap}.bootstrap-select>.dropdown-toggle.bs-placeholder,.bootstrap-select>.dropdown-toggle.bs-placeholder:active,.bootstrap-select>.dropdown-toggle.bs-placeholder:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder:hover{color:#999}.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-danger:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-dark:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-info:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-primary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-secondary:hover,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:active,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:focus,.bootstrap-select>.dropdown-toggle.bs-placeholder.btn-success:hover{color:rgba(255,255,255,.5)}.bootstrap-select>select{position:absolute!important;bottom:0;left:50%;display:block!important;width:.5px!important;height:100%!important;padding:0!important;opacity:0!important;border:none}.bootstrap-select>select.mobile-device{top:0;left:0;display:block!important;width:100%!important;z-index:2}.bootstrap-select.is-invalid .dropdown-toggle,.error .bootstrap-select .dropdown-toggle,.has-error .bootstrap-select .dropdown-toggle,.was-validated .bootstrap-select .selectpicker:invalid+.dropdown-toggle{border-color:#b94a48}.bootstrap-select.is-valid .dropdown-toggle,.was-validated .bootstrap-select .selectpicker:valid+.dropdown-toggle{border-color:#28a745}.bootstrap-select.fit-width{width:auto!important}.bootstrap-select:not([class*=col-]):not([class*=form-control]):not(.input-group-btn){width:220px}.bootstrap-select .dropdown-toggle:focus{outline:thin dotted #333!important;outline:5px auto -webkit-focus-ring-color!important;outline-offset:-2px}.bootstrap-select.form-control{margin-bottom:0;padding:0;border:none}:not(.input-group)>.bootstrap-select.form-control:not([class*=col-]){width:100%}.bootstrap-select.form-control.input-group-btn{z-index:auto}.bootstrap-select.form-control.input-group-btn:not(:first-child):not(:last-child)>.btn{border-radius:0}.bootstrap-select:not(.input-group-btn),.bootstrap-select[class*=col-]{float:none;display:inline-block;margin-left:0}.bootstrap-select.dropdown-menu-right,.bootstrap-select[class*=col-].dropdown-menu-right,.row .bootstrap-select[class*=col-].dropdown-menu-right{float:right}.form-group .bootstrap-select,.form-horizontal .bootstrap-select,.form-inline .bootstrap-select{margin-bottom:0}.form-group-lg .bootstrap-select.form-control,.form-group-sm .bootstrap-select.form-control{padding:0}.form-group-lg .bootstrap-select.form-control .dropdown-toggle,.form-group-sm .bootstrap-select.form-control .dropdown-toggle{height:100%;font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-lg .dropdown-toggle,.bootstrap-select.form-control-sm .dropdown-toggle{font-size:inherit;line-height:inherit;border-radius:inherit}.bootstrap-select.form-control-sm .dropdown-toggle{padding:.25rem .5rem}.bootstrap-select.form-control-lg .dropdown-toggle{padding:.5rem 1rem}.form-inline .bootstrap-select .form-control{width:100%}.bootstrap-select.disabled,.bootstrap-select>.disabled{cursor:not-allowed}.bootstrap-select.disabled:focus,.bootstrap-select>.disabled:focus{outline:0!important}.bootstrap-select.bs-container{position:absolute;top:0;left:0;height:0!important;padding:0!important}.bootstrap-select.bs-container .dropdown-menu{z-index:1060}.bootstrap-select .dropdown-toggle:before{content:'';display:inline-block}.bootstrap-select .dropdown-toggle .filter-option{position:absolute;top:0;left:0;padding-top:inherit;padding-right:inherit;padding-bottom:inherit;padding-left:inherit;height:100%;width:100%;text-align:left}.bootstrap-select .dropdown-toggle .filter-option-inner{padding-right:inherit}.bootstrap-select .dropdown-toggle .filter-option-inner-inner{overflow:hidden}.bootstrap-select .dropdown-toggle .caret{position:absolute;top:50%;right:12px;margin-top:-2px;vertical-align:middle}.input-group .bootstrap-select.form-control .dropdown-toggle{border-radius:inherit}.bootstrap-select[class*=col-] .dropdown-toggle{width:100%}.bootstrap-select .dropdown-menu{min-width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .dropdown-menu>.inner:focus{outline:0!important}.bootstrap-select .dropdown-menu.inner{position:static;float:none;border:0;padding:0;margin:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.bootstrap-select .dropdown-menu li{position:relative}.bootstrap-select .dropdown-menu li.active small{color:rgba(255,255,255,.5)!important}.bootstrap-select .dropdown-menu li.disabled a{cursor:not-allowed}.bootstrap-select .dropdown-menu li a{cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bootstrap-select .dropdown-menu li a.opt{position:relative;padding-left:2.25em}.bootstrap-select .dropdown-menu li a span.check-mark{display:none}.bootstrap-select .dropdown-menu li a span.text{display:inline-block}.bootstrap-select .dropdown-menu li small{padding-left:.5em}.bootstrap-select .dropdown-menu .notify{position:absolute;bottom:5px;width:96%;margin:0 2%;min-height:26px;padding:3px 5px;background:#f5f5f5;border:1px solid #e3e3e3;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05);pointer-events:none;opacity:.9;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bootstrap-select .no-results{padding:3px;background:#f5f5f5;margin:0 5px;white-space:nowrap}.bootstrap-select.fit-width .dropdown-toggle .filter-option{position:static;display:inline;padding:0}.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner,.bootstrap-select.fit-width .dropdown-toggle .filter-option-inner-inner{display:inline}.bootstrap-select.fit-width .dropdown-toggle .caret{position:static;top:auto;margin-top:-1px}.bootstrap-select.show-tick .dropdown-menu .selected span.check-mark{position:absolute;display:inline-block;right:15px;top:5px}.bootstrap-select.show-tick .dropdown-menu li a span.text{margin-right:34px}.bootstrap-select .bs-ok-default:after{content:'';display:block;width:.5em;height:1em;border-style:solid;border-width:0 .26em .26em 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle{z-index:1061}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:before{content:'';border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid rgba(204,204,204,.2);position:absolute;bottom:-4px;left:9px;display:none}.bootstrap-select.show-menu-arrow .dropdown-toggle .filter-option:after{content:'';border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;bottom:-4px;left:10px;display:none}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:before{bottom:auto;top:-4px;border-top:7px solid rgba(204,204,204,.2);border-bottom:0}.bootstrap-select.show-menu-arrow.dropup .dropdown-toggle .filter-option:after{bottom:auto;top:-4px;border-top:6px solid #fff;border-bottom:0}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:before{right:12px;left:auto}.bootstrap-select.show-menu-arrow.pull-right .dropdown-toggle .filter-option:after{right:13px;left:auto}.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.open>.dropdown-toggle .filter-option:before,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:after,.bootstrap-select.show-menu-arrow.show>.dropdown-toggle .filter-option:before{display:block}.bs-actionsbox,.bs-donebutton,.bs-searchbox{padding:4px 8px}.bs-actionsbox{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-actionsbox .btn-group button{width:50%}.bs-donebutton{float:left;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.bs-donebutton .btn-group button{width:100%}.bs-searchbox+.bs-actionsbox{padding:0 8px 4px}.bs-searchbox .form-control{margin-bottom:0;width:100%;float:none} \ No newline at end of file diff --git a/BTPanel/static/ckeditor/CHANGES.md b/BTPanel/static/ckeditor/CHANGES.md new file mode 100644 index 00000000..acb979c1 --- /dev/null +++ b/BTPanel/static/ckeditor/CHANGES.md @@ -0,0 +1,1703 @@ +CKEditor 4 Changelog +==================== + +## CKEditor 4.11.4 + +Fixed Issues: + +* [#589](https://github.com/ckeditor/ckeditor-dev/issues/589): Fixed: The editor causes memory leaks in create and destroy cycles. +* [#1397](https://github.com/ckeditor/ckeditor-dev/issues/1397): Fixed: Using the dialog to remove headers from a [table](https://ckeditor.com/cke4/addon/table) with one header row only throws an error. +* [#1479](https://github.com/ckeditor/ckeditor-dev/issues/1479): Fixed: [Justification](https://ckeditor.com/cke4/addon/justify) for styled content in BR mode is disabled. +* [#2816](https://github.com/ckeditor/ckeditor-dev/issues/2816): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) resize handler is visible in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#2874](https://github.com/ckeditor/ckeditor-dev/issues/2874): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) resize handler is not created when the editor is initialized in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#2775](https://github.com/ckeditor/ckeditor-dev/issues/2775): Fixed: [Clipboard](https://ckeditor.com/cke4/addon/clipboard) paste buttons have wrong state when [read-only](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html) mode is set by the mouse event listener with the [Div Editing Area](https://ckeditor.com/cke4/addon/divarea) plugin. +* [#1901](https://github.com/ckeditor/ckeditor-dev/issues/1901): Fixed: Cannot open the context menu over a [Widget](https://ckeditor.com/cke4/addon/widget) with the Shift+F10 keyboard shortcut. + +Other Changes: + +* Updated [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) (WSC) and [SpellCheckAsYouType](https://ckeditor.com/cke4/addon/scayt) (SCAYT) plugins: + * Language dictionary update: German language was extended with over 600k new words. + * Language dictionary update: Swedish language was extended with over 300k new words. + * Grammar support added for Australian and New Zealand English, Polish, Slovak, Slovenian and Austrian languages. + * Changed wavy red and green lines that underline spelling and grammar errors to straight ones. + * [#55](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/55): Fixed: WSC does not use [`CKEDITOR.getUrl()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#method-getUrl) when referencing style sheets. + * [#166](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/166): Fixed: SCAYT does not use [`CKEDITOR.getUrl()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#method-getUrl) when referencing style sheets. + * [#56](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/56): [Chrome] Fixed: SCAYT/WSC throws errors when running inside a Chrome extension. + * Fixed: After removing a dictionary, the words are not underlined and considered as incorrect. + * Fixed: The Slovenian (`sl_SL`) language does not work. + * Fixed: Quotes with code `U+2019` (Right single quotation mark) are considered separators. + * Fixed: Wrong error message formatting when the service ID is invalid. + * Fixed: Absent languages in the Languages tab when using SCAYT with the [Shared Spaces](https://ckeditor.com/cke4/addon/sharedspace) plugin. + +## CKEditor 4.11.3 + +Fixed Issues: + +* [#2721](https://github.com/ckeditor/ckeditor-dev/issues/2721), [#487](https://github.com/ckeditor/ckeditor-dev/issues/487): Fixed: The order of sublist items is reversed when a higher level list item is removed. +* [#2527](https://github.com/ckeditor/ckeditor-dev/issues/2527): Fixed: [Emoji](https://ckeditor.com/cke4/addon/emoji) autocomplete order does not prioritize emojis with the name starting from the used string. +* [#2572](https://github.com/ckeditor/ckeditor-dev/issues/2572): Fixed: Icons in the [Emoji](https://ckeditor.com/cke4/addon/emoji) dropdown navigation groups are not centered. +* [#1191](https://github.com/ckeditor/ckeditor-dev/issues/1191): Fixed: Items in the [elements path](https://ckeditor.com/cke4/addon/elementspath) are draggable. +* [#2292](https://github.com/ckeditor/ckeditor-dev/issues/2292): Fixed: Dropping a list with a link on the editor's margin causes a console error and removes the dragged text from editor. +* [#2756](https://github.com/ckeditor/ckeditor-dev/issues/2756): Fixed: The [Auto Link](https://ckeditor.com/cke4/addon/autolink) plugin causes an error when typing in the [source editing mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_sourcearea.html). +* [#1986](https://github.com/ckeditor/ckeditor-dev/issues/1986): Fixed: The Cell Properties dialog from the [Table Tools](https://ckeditor.com/cke4/addon/tabletools) plugin shows styles that are not allowed through [`config.allowedContent`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-allowedContent). +* [#2565](https://github.com/ckeditor/ckeditor-dev/issues/2565): [IE, Edge] Fixed: Buttons in the [editor toolbar](https://ckeditor.com/cke4/addon/toolbar) are activated by clicking them with the right mouse button. +* [#2792](https://github.com/ckeditor/ckeditor-dev/pull/2792): Fixed: A bug in the [Copy Formatting](https://ckeditor.com/cke4/addon/copyformatting) plugin that caused the following issues: + * [#2780](https://github.com/ckeditor/ckeditor-dev/issues/2780): Fixed: Undo steps disappear after multiple changes of selection. + * [#2470](https://github.com/ckeditor/ckeditor-dev/issues/2470): [Firefox] Fixed: Widget's nested editable gets blurred upon focus. + * [#2655](https://github.com/ckeditor/ckeditor-dev/issues/2655): [Chrome, Safari] Fixed: Widget's nested editable cannot be focused under certain circumstances. + +## CKEditor 4.11.2 + +Fixed Issues: + +* [#2403](https://github.com/ckeditor/ckeditor-dev/issues/2403): Fixed: Styling inline editor initialized inside a table with the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin is causing style leaks. +* [#2514](https://github.com/ckeditor/ckeditor-dev/issues/2403): Fixed: Pasting table data into inline editor initialized inside a table with the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin inserts pasted content into the wrapping table. +* [#2451](https://github.com/ckeditor/ckeditor-dev/issues/2451): Fixed: The [Remove Format](https://ckeditor.com/cke4/addon/removeformat) plugin changes selection. +* [#2546](https://github.com/ckeditor/ckeditor-dev/issues/2546): Fixed: The separator in the toolbar moves when buttons are focused. +* [#2506](https://github.com/ckeditor/ckeditor-dev/issues/2506): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) throws a type error when an empty `
      ` tag with an `image` class is upcasted. +* [#2650](https://github.com/ckeditor/ckeditor-dev/issues/2650): Fixed: [Table](https://ckeditor.com/cke4/addon/table) dialog validator fails when the `getValue()` function is defined in the global scope. +* [#2690](https://github.com/ckeditor/ckeditor-dev/issues/2690): Fixed: Decimal characters are removed from the inside of numbered lists when pasting content using the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#2205](https://github.com/ckeditor/ckeditor-dev/issues/2205): Fixed: It is not possible to add new list items under an item containing a block element. +* [#2411](https://github.com/ckeditor/ckeditor-dev/issues/2411), [#2438](https://github.com/ckeditor/ckeditor-dev/issues/2438) Fixed: Apply numbered list option throws a console error for a specific markup. +* [#2430](https://github.com/ckeditor/ckeditor-dev/issues/2430) Fixed: [Color Button](https://ckeditor.com/cke4/addon/colorbutton) and [List Block](https://ckeditor.com/cke4/addon/listblock) items are draggable. + +Other Changes: + +* Updated the [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) (WSC) plugin: + * [#52](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/52) Fixed: Clicking "Finish Checking" without a prior action would hang the Spell Checking dialog. +* [#2603](https://github.com/ckeditor/ckeditor-dev/issues/2603): Corrected the GPL license entry in the `package.json` file. + +## CKEditor 4.11.1 + +Fixed Issues: + +* [#2571](https://github.com/ckeditor/ckeditor-dev/issues/2571): Fixed: Clicking the categories in the [Emoji](https://ckeditor.com/cke4/addon/emoji) dropdown panel scrolls the entire page. + +## CKEditor 4.11 + +**Security Updates:** + +* Fixed XSS vulnerability in the HTML parser reported by [maxarr](https://hackerone.com/maxarr). + + Issue summary: It was possible to execute XSS inside CKEditor after persuading the victim to: (i) switch CKEditor to source mode, then (ii) paste a specially crafted HTML code, prepared by the attacker, into the opened CKEditor source area, and (iii) switch back to WYSIWYG mode. + +**An upgrade is highly recommended!** + +New Features: + +* [#2062](https://github.com/ckeditor/ckeditor-dev/pull/2062): Added the emoji dropdown that allows the user to choose the emoji from the toolbar and search for them using keywords. +* [#2154](https://github.com/ckeditor/ckeditor-dev/issues/2154): The [Link](https://ckeditor.com/cke4/addon/link) plugin now supports phone number links. +* [#1815](https://github.com/ckeditor/ckeditor-dev/issues/1815): The [Auto Link](https://ckeditor.com/cke4/addon/autolink) plugin supports typing link completion. +* [#2478](https://github.com/ckeditor/ckeditor-dev/issues/2478): [Link](https://ckeditor.com/cke4/addon/link) can be inserted using the Ctrl/Cmd + K keystroke. +* [#651](https://github.com/ckeditor/ckeditor-dev/issues/651): Text pasted using the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin preserves indentation in paragraphs. +* [#2248](https://github.com/ckeditor/ckeditor-dev/issues/2248): Added support for justification in the [BBCode](https://ckeditor.com/cke4/addon/bbcode) plugin. Thanks to [Matěj Kmínek](https://github.com/KminekMatej)! +* [#706](https://github.com/ckeditor/ckeditor-dev/issues/706): Added a different cursor style when selecting cells for the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin. +* [#2072](https://github.com/ckeditor/ckeditor-dev/issues/2072): The [UI Button](https://ckeditor.com/cke4/addon/button) plugin supports custom `aria-haspopup` property values. The [Menu Button](https://ckeditor.com/cke4/addon/menubutton) `aria-haspopup` value is now `menu`, the [Panel Button](https://ckeditor.com/cke4/addon/panelbutton) and [Rich Combo](https://ckeditor.com/cke4/addon/richcombo) `aria-haspopup` value is now `listbox`. +* [#1176](https://github.com/ckeditor/ckeditor-dev/pull/1176): The [Balloon Panel](https://ckeditor.com/cke4/addon/balloonpanel) can now be attached to a selection instead of an element. +* [#2202](https://github.com/ckeditor/ckeditor-dev/issues/2202): Added the `contextmenu_contentsCss` configuration option to allow adding custom CSS to the [Context Menu](https://ckeditor.com/cke4/addon/contextmenu). + +Fixed Issues: + +* [#1477](https://github.com/ckeditor/ckeditor-dev/issues/1477): Fixed: On destroy, [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) does not destroy its content. +* [#2394](https://github.com/ckeditor/ckeditor-dev/issues/2394): Fixed: [Emoji](https://ckeditor.com/cke4/addon/emoji) dropdown does not show up with repeated symbols in a single line. +* [#1181](https://github.com/ckeditor/ckeditor-dev/issues/1181): [Chrome] Fixed: Opening the context menu in a read-only editor results in an error. +* [#2276](https://github.com/ckeditor/ckeditor-dev/issues/2276): [iOS] Fixed: [Button](https://ckeditor.com/cke4/addon/button) state does not refresh properly. +* [#1489](https://github.com/ckeditor/ckeditor-dev/issues/1489): Fixed: Table contents can be removed in read-only mode when the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin is used. +* [#1264](https://github.com/ckeditor/ckeditor-dev/issues/1264) Fixed: Right-click does not clear the selection created with the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin. +* [#586](https://github.com/ckeditor/ckeditor-dev/issues/586) Fixed: The `required` attribute is not correctly recognized by the [Form Elements](https://ckeditor.com/cke4/addon/forms) plugin dialog. Thanks to [Roli Züger](https://github.com/rzueger)! +* [#2380](https://github.com/ckeditor/ckeditor-dev/issues/2380) Fixed: Styling HTML comments in a top-level element results in extra paragraphs. +* [#2294](https://github.com/ckeditor/ckeditor-dev/issues/2294) Fixed: Pasting content from Microsoft Outlook and then bolding it results in an error. +* [#2035](https://github.com/ckeditor/ckeditor-dev/issues/2035) [Edge] Fixed: `Permission denied` is thrown when opening a [Panel](https://ckeditor.com/cke4/addon/panel) instance. +* [#965](https://github.com/ckeditor/ckeditor-dev/issues/965) Fixed: The [`config.forceSimpleAmpersand`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-forceSimpleAmpersand) option does not work. Thanks to [Alex Maris](https://github.com/alexmaris)! +* [#2448](https://github.com/ckeditor/ckeditor-dev/issues/2448): Fixed: The [`Escape HTML Entities`] plugin with custom [additional entities](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-entities_additional) configuration breaks HTML escaping. +* [#898](https://github.com/ckeditor/ckeditor-dev/issues/898): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) long alternative text protrudes into the editor when the image is selected. +* [#1113](https://github.com/ckeditor/ckeditor-dev/issues/1113): [Firefox] Fixed: Nested contenteditable elements path is not updated on focus with the [Div Editing Area](https://ckeditor.com/cke4/addon/divarea) plugin. +* [#1682](https://github.com/ckeditor/ckeditor-dev/issues/1682) Fixed: Hovering the [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) panel changes its size, causing flickering. +* [#421](https://github.com/ckeditor/ckeditor-dev/issues/421) Fixed: Expandable [Button](https://ckeditor.com/cke4/addon/button) puts the `(Selected)` text at the end of the label when clicked. +* [#1454](https://github.com/ckeditor/ckeditor-dev/issues/1454): Fixed: The [`onAbort`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_fileTools_uploadWidgetDefinition.html#property-onAbort) method of the [Upload Widget](https://ckeditor.com/cke4/addon/uploadwidget) is not called when the loader is aborted. +* [#1451](https://github.com/ckeditor/ckeditor-dev/issues/1451): Fixed: The context menu is incorrectly positioned when opened with Shift+F10. +* [#1722](https://github.com/ckeditor/ckeditor-dev/issues/1722): [`CKEDITOR.filter.instances`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_filter.html#static-property-instances) is causing memory leaks. +* [#2491](https://github.com/ckeditor/ckeditor-dev/issues/2491): Fixed: The [Mentions](https://ckeditor.com/cke4/addon/mentions) plugin is not matching diacritic characters. +* [#2519](https://github.com/ckeditor/ckeditor-dev/issues/2519): Fixed: The [Accessibility Help](https://ckeditor.com/cke4/addon/a11yhelp) dialog should display all available keystrokes for a single command. + +API Changes: + +* [#2453](https://github.com/ckeditor/ckeditor-dev/issues/2453): The [`CKEDITOR.ui.panel.block.getItems`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui_panel_block.html#method-getItems) method now also returns `input` elements in addition to links. +* [#2224](https://github.com/ckeditor/ckeditor-dev/issues/2224): The [`CKEDITOR.tools.convertToPx`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-convertToPx) function now converts negative values. +* [#2253](https://github.com/ckeditor/ckeditor-dev/issues/2253): The widget definition [`insert`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget_definition.html#property-insert) method now passes `editor` and `commandData`. Thanks to [marcparmet](https://github.com/marcparmet)! +* [#2045](https://github.com/ckeditor/ckeditor-dev/issues/2045): Extracted [`tools.eventsBuffer`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-eventsBuffer) and [`tools.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-throttle) functions logic into a separate namespace. + * [`tools.eventsBuffer`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-eventsBuffer) was extracted into [`tools.buffers.event`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_buffers_event.html), + * [`tools.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-throttle) was extracted into [`tools.buffers.throttle`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_buffers_throttle.html). +* [#2466](https://github.com/ckeditor/ckeditor-dev/issues/2466): The [`CKEDITOR.filter`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-constructor) constructor accepts an additional `rules` parameter allowing to bind the editor and filter together. +* [#2493](https://github.com/ckeditor/ckeditor-dev/issues/2493): The [`editor.getCommandKeystroke`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getCommandKeystroke) method accepts an additional `all` parameter allowing to retrieve an array of all command keystrokes. +* [#2483](https://github.com/ckeditor/ckeditor-dev/issues/2483): Button's DOM element created with the [`hasArrow`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui.html#method-addButton) definition option can by identified by the `.cke_button_expandable` CSS class. + +Other Changes: + +* [#1713](https://github.com/ckeditor/ckeditor-dev/issues/1713): Removed the redundant `lang.title` entry from the [Clipboard](https://ckeditor.com/cke4/addon/clipboard) plugin. + +## CKEditor 4.10.1 + +Fixed Issues: + +* [#2114](https://github.com/ckeditor/ckeditor-dev/issues/2114): Fixed: [Autocomplete](https://ckeditor.com/cke4/addon/autocomplete) cannot be initialized before [`instanceReady`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-instanceReady). +* [#2107](https://github.com/ckeditor/ckeditor-dev/issues/2107): Fixed: Holding and releasing the mouse button is not inserting an [autocomplete](https://ckeditor.com/cke4/addon/autocomplete) suggestion. +* [#2167](https://github.com/ckeditor/ckeditor-dev/issues/2167): Fixed: Matching in [Emoji](https://ckeditor.com/cke4/addon/emoji) plugin is not case insensitive. +* [#2195](https://github.com/ckeditor/ckeditor-dev/issues/2195): Fixed: [Emoji](https://ckeditor.com/cke4/addon/emoji) shows the suggestion box when the colon is preceded with other characters than white space. +* [#2169](https://github.com/ckeditor/ckeditor-dev/issues/2169): [Edge] Fixed: Error thrown when pasting into the editor. +* [#1084](https://github.com/ckeditor/ckeditor-dev/issues/1084) Fixed: Using the "Automatic" option with [Color Button](https://ckeditor.com/cke4/addon/colorbutton) on a text with the color already defined sets an invalid color value. +* [#2271](https://github.com/ckeditor/ckeditor-dev/issues/2271): Fixed: Custom color name not used as a label in the [Color Button](https://ckeditor.com/cke4/addon/image2) plugin. Thanks to [Eric Geloen](https://github.com/egeloen)! +* [#2296](https://github.com/ckeditor/ckeditor-dev/issues/2296): Fixed: The [Color Button](https://ckeditor.com/cke4/addon/colorbutton) plugin throws an error when activated on content containing HTML comments. +* [#966](https://github.com/ckeditor/ckeditor-dev/issues/966): Fixed: Executing [`editor.destroy()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-destroy) during the [file upload](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_fileTools_uploadWidgetDefinition.html#property-onUploading) throws an error. Thanks to [Maksim Makarevich](https://github.com/MaksimMakarevich)! +* [#1719](https://github.com/ckeditor/ckeditor-dev/issues/1719): Fixed: Ctrl/Cmd + A inadvertently focuses inline editor if it is starting and ending with a list. Thanks to [theNailz](https://github.com/theNailz)! +* [#1046](https://github.com/ckeditor/ckeditor-dev/issues/1046): Fixed: Subsequent new links do not include the `id` attribute. Thanks to [Nathan Samson](https://github.com/nathansamson)! +* [#1348](https://github.com/ckeditor/ckeditor-dev/issues/1348): Fixed: [Enhanced Image](https://ckeditor.com/cke4/addon/image2) plugin aspect ratio locking uses an old width and height on image URL change. +* [#1791](https://github.com/ckeditor/ckeditor-dev/issues/1791): Fixed: [Image](https://ckeditor.com/cke4/addon/image) and [Enhanced Image](https://ckeditor.com/cke4/addon/image2) plugins can be enabled when [Easy Image](https://ckeditor.com/cke4/addon/easyimage) is present. +* [#2254](https://github.com/ckeditor/ckeditor-dev/issues/2254): Fixed: [Image](https://ckeditor.com/cke4/addon/image) ratio locking is too precise for resized images. Thanks to [Jonathan Gilbert](https://github.com/logiclrd)! +* [#1184](https://github.com/ckeditor/ckeditor-dev/issues/1184): [IE8-11] Fixed: Copying and pasting data in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#property-readOnly) throws an error. +* [#1916](https://github.com/ckeditor/ckeditor-dev/issues/1916): [IE9-11] Fixed: Pressing the Delete key in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#property-readOnly) throws an error. +* [#2003](https://github.com/ckeditor/ckeditor-dev/issues/2003): [Firefox] Fixed: Right-clicking multiple selected table cells containing empty paragraphs removes the selection. +* [#1816](https://github.com/ckeditor/ckeditor-dev/issues/1816): Fixed: Table breaks when Enter is pressed over the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin. +* [#1115](https://github.com/ckeditor/ckeditor-dev/issues/1115): Fixed: The `` tag is not preserved when proper configuration is provided and a style is applied by the [Font](https://ckeditor.com/cke4/addon/font) plugin. +* [#727](https://github.com/ckeditor/ckeditor-dev/issues/727): Fixed: Custom styles may be invisible in the [Styles Combo](https://ckeditor.com/cke4/addon/stylescombo) plugin. +* [#988](https://github.com/ckeditor/ckeditor-dev/issues/988): Fixed: ACF-enabled custom elements prefixed with `object`, `embed`, `param` are removed from the editor content. + +API Changes: + +* [#2249](https://github.com/ckeditor/ckeditor-dev/issues/1791): Added the [`editor.plugins.detectConflict()`](https://ckeditor.com/docs/ckeditor4/latest/CKEDITOR_editor_plugins.html#method-detectConflict) method finding conflicts between provided plugins. + +## CKEditor 4.10 + +New Features: + +* [#1751](https://github.com/ckeditor/ckeditor-dev/issues/1751): Introduced the **Autocomplete** feature that consists of the following plugins: + * [Autocomplete](https://ckeditor.com/cke4/addon/autocomplete) – Provides contextual completion feature for custom text matches based on user input. + * [Text Watcher](https://ckeditor.com/cke4/addon/textWatcher) – Checks whether an editor's text change matches the chosen criteria. + * [Text Match](https://ckeditor.com/cke4/addon/textMatch) – Allows to search [`CKEDITOR.dom.range`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html) for matching text. +* [#1703](https://github.com/ckeditor/ckeditor-dev/issues/1703): Introduced the [Mentions](https://ckeditor.com/cke4/addon/mentions) plugin providing smart completion feature for custom text matches based on user input starting with a chosen marker character. +* [#1746](https://github.com/ckeditor/ckeditor-dev/issues/1703): Introduced the [Emoji](https://ckeditor.com/cke4/addon/emoji) plugin providing completion feature for emoji ideograms. +* [#1761](https://github.com/ckeditor/ckeditor-dev/issues/1761): The [Auto Link](https://ckeditor.com/cke4/addon/autolink) plugin now supports email links. + +Fixed Issues: + +* [#1458](https://github.com/ckeditor/ckeditor-dev/issues/1458): [Edge] Fixed: After blurring the editor it takes 2 clicks to focus a widget. +* [#1034](https://github.com/ckeditor/ckeditor-dev/issues/1034): Fixed: JAWS leaves forms mode after pressing the Enter key in an inline editor instance. +* [#1748](https://github.com/ckeditor/ckeditor-dev/pull/1748): Fixed: Missing [`CKEDITOR.dialog.definition.onHide`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dialog_definition.html#property-onHide) API documentation. Thanks to [sunnyone](https://github.com/sunnyone)! +* [#1321](https://github.com/ckeditor/ckeditor-dev/issues/1321): Fixed: Ideographic space character (`\u3000`) is lost when pasting text. +* [#1776](https://github.com/ckeditor/ckeditor-dev/issues/1776): Fixed: Empty caption placeholder of the [Image Base](https://ckeditor.com/cke4/addon/imagebase) plugin is not hidden when blurred. +* [#1592](https://github.com/ckeditor/ckeditor-dev/issues/1592): Fixed: The [Image Base](https://ckeditor.com/cke4/addon/imagebase) plugin caption is not visible after paste. +* [#620](https://github.com/ckeditor/ckeditor-dev/issues/620): Fixed: The [`config.forcePasteAsPlainText`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-forcePasteAsPlainText) option is not respected in internal and cross-editor pasting. +* [#1467](https://github.com/ckeditor/ckeditor-dev/issues/1467): Fixed: The resizing cursor of the [Table Resize](https://ckeditor.com/cke4/addon/tableresize) plugin appearing in the middle of a merged cell. + +API Changes: + +* [#850](https://github.com/ckeditor/ckeditor-dev/issues/850): Backward incompatibility: Replaced the `replace` dialog from the [Find / Replace](https://ckeditor.com/cke4/addon/find) plugin with a `tabId` option in the `find` command. +* [#1582](https://github.com/ckeditor/ckeditor-dev/issues/1582): The [`CKEDITOR.editor.addCommand()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-addCommand) method can now accept a [`CKEDITOR.command`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_command.html) instance as a parameter. +* [#1712](https://github.com/ckeditor/ckeditor-dev/issues/1712): The [`extraPlugins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-extraPlugins), [`removePlugins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-removePlugins) and [`plugins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-plugins) configuration options allow whitespace. +* [#1802](https://github.com/ckeditor/ckeditor-dev/issues/1802): The [`extraPlugins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-extraPlugins), [`removePlugins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-removePlugins) and [`plugins`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-plugins) configuration options allow passing plugin names as an array. +* [#1724](https://github.com/ckeditor/ckeditor-dev/issues/1724): Added an option to the [`getClientRect()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_element.html#method-getClientRect) function allowing to retrieve an absolute bounding rectangle of the element, i.e. a position relative to the upper-left corner of the topmost viewport. +* [#1498](https://github.com/ckeditor/ckeditor-dev/issues/1498) : Added a new [`getClientRects()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-getClientRects) method to `CKEDITOR.dom.range`. It returns a list of rectangles for each selected element. +* [#1993](https://github.com/ckeditor/ckeditor-dev/issues/1993): Added the [`CKEDITOR.tools.throttle()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-throttle) function. + +Other Changes: + +* Updated [SCAYT](https://ckeditor.com/cke4/addon/scayt) (Spell Check As You Type) and [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) (WSC) plugins: + * Language dictionary update: Added support for the Uzbek Latin language. + * Languages no longer supported as additional languages: Manx - Isle of Man (`gv_GB`) and Interlingua (`ia_XR`). + * Extended and improved language dictionaries: Georgian and Swedish. Also added the missing word _"Ensure"_ to the American, British and Canada English language. + * [#141](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/141) Fixed: SCAYT throws "Uncaught Error: Error in RangyWrappedRange module: createRange(): Parameter must be a Window object or DOM node". + * [#153](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/153) [Chrome] Fixed: Correcting a word in the widget in SCAYT moves focus to another editable. + * [#155](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/155) [IE8] Fixed: SCAYT throws an error and does not work. + * [#156](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/156) [IE10] Fixed: SCAYT does not seem to work. + * Fixed: After some text is dragged and dropped, the markup is not refreshed for grammar problems in SCAYT. + * Fixed: Request to FastCGI fails when the user tries to replace a word with non-English characters with a proper suggestion in WSC. + * [Firefox] Fixed: Ctrl+Z removes focus in SCAYT. + * Grammar support for default languages was improved. + * New application source URL was added in SCAYT. + * Removed green marks and legend related to grammar-supported languages in the Languages tab of SCAYT. Grammar is now supported for almost all the anguages in the list for an additional fee. + * Fixed: JavaScript error in the console: "Cannot read property 'split' of undefined" in SCAYT and WSC. + * [IE10] Fixed: Markup is not set for a specific case in SCAYT. + * Fixed: Accessibility issue: No `alt` attribute for the logo image in the About tab of SCAYT. + +## CKEditor 4.9.2 + +**Security Updates:** + +* Fixed XSS vulnerability in the [Enhanced Image](https://ckeditor.com/cke4/addon/image2) (`image2`) plugin reported by [Kyaw Min Thein](https://twitter.com/kyawminthein99). + + Issue summary: It was possible to execute XSS inside CKEditor using the `` tag and specially crafted HTML. Please note that the default presets (Basic/Standard/Full) do not include this plugin, so you are only at risk if you made a custom build and enabled this plugin. + +We would like to thank the [Drupal security team](https://www.drupal.org/drupal-security-team) for bringing this matter to our attention and coordinating the fix and release process! + +## CKEditor 4.9.1 + +Fixed Issues: + +* [#1835](https://github.com/ckeditor/ckeditor-dev/issues/1835): Fixed: Integration between [CKFinder](https://ckeditor.com/ckeditor-4/ckfinder/) and the [File Browser](https://ckeditor.com/cke4/addon/filebrowser) plugin does not work. + +## CKEditor 4.9 + +New Features: + +* [#932](https://github.com/ckeditor/ckeditor-dev/issues/932): Introduced Easy Image feature for inserting images that are automatically rescaled, optimized, responsive and delivered through a blazing-fast CDN. Three new plugins were added to support it: + * [Easy Image](https://ckeditor.com/cke4/addon/easyimage), + * [Cloud Services](https://ckeditor.com/cke4/addon/cloudservices) + * [Image Base](https://ckeditor.com/cke4/addon/imagebase) +* [#1338](https://github.com/ckeditor/ckeditor-dev/issues/1338): Keystroke labels are displayed for function keys (like F7, F8). +* [#643](https://github.com/ckeditor/ckeditor-dev/issues/643): The [File Browser](https://ckeditor.com/cke4/addon/filebrowser) plugin can now upload files using XHR requests. This allows for setting custom HTTP headers using the [`config.fileTools_requestHeaders`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-fileTools_requestHeaders) configuration option. +* [#1365](https://github.com/ckeditor/ckeditor-dev/issues/1365): The [File Browser](https://ckeditor.com/cke4/addon/filebrowser) plugin uses XHR requests by default. +* [#1399](https://github.com/ckeditor/ckeditor-dev/issues/1399): Added the possibility to set [`CKEDITOR.config.startupFocus`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-startupFocus) as `start` or `end` to specify where the editor focus should be after the initialization. +* [#1441](https://github.com/ckeditor/ckeditor-dev/issues/1441): The [Magic Line](https://ckeditor.com/cke4/addon/magicline) plugin line element can now be identified by the `data-cke-magic-line="1"` attribute. + +Fixed Issues: + +* [#595](https://github.com/ckeditor/ckeditor-dev/issues/595): Fixed: Pasting does not work on mobile devices. +* [#869](https://github.com/ckeditor/ckeditor-dev/issues/869): Fixed: Empty selection clears cached clipboard data in the editor. +* [#1419](https://github.com/ckeditor/ckeditor-dev/issues/1419): Fixed: The [Widget Selection](https://ckeditor.com/cke4/addon/widgetselection) plugin selects the editor content with the Alt+A key combination on Windows. +* [#1274](https://github.com/ckeditor/ckeditor-dev/issues/1274): Fixed: [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) does not match a single selected image using the [`contextDefinition.cssSelector`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_balloontoolbar_contextDefinition.html#property-cssSelector) matcher. +* [#1232](https://github.com/ckeditor/ckeditor-dev/issues/1232): Fixed: [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) buttons should be registered as focusable elements. +* [#1342](https://github.com/ckeditor/ckeditor-dev/issues/1342): Fixed: [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) should be re-positioned after the [`change`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-change) event. +* [#1426](https://github.com/ckeditor/ckeditor-dev/issues/1426): [IE8-9] Fixed: Missing [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) background in the [Kama](https://ckeditor.com/cke4/addon/kama) skin. Thanks to [Christian Elmer](https://github.com/keinkurt)! +* [#1470](https://github.com/ckeditor/ckeditor-dev/issues/1470): Fixed: [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) is not visible after drag and drop of a widget it is attached to. +* [#1048](https://github.com/ckeditor/ckeditor-dev/issues/1048): Fixed: [Balloon Panel](https://ckeditor.com/cke4/addon/balloonpanel) is not positioned properly when a margin is added to its non-static parent. +* [#889](https://github.com/ckeditor/ckeditor-dev/issues/889): Fixed: Unclear error message for width and height fields in the [Image](https://ckeditor.com/cke4/addon/image) and [Enhanced Image](https://ckeditor.com/cke4/addon/image2) plugins. +* [#859](https://github.com/ckeditor/ckeditor-dev/issues/859): Fixed: Cannot edit a link after a double-click on the text in the link. +* [#1013](https://github.com/ckeditor/ckeditor-dev/issues/1013): Fixed: [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) does not work correctly with the [`config.forcePasteAsPlainText`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-forcePasteAsPlainText) option. +* [#1356](https://github.com/ckeditor/ckeditor-dev/issues/1356): Fixed: [Border parse function](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_style_parse.html#method-border) does not allow spaces in the color value. +* [#1010](https://github.com/ckeditor/ckeditor-dev/issues/1010): Fixed: The CSS `border` shorthand property was incorrectly expanded ignoring the `border-color` style. +* [#1535](https://github.com/ckeditor/ckeditor-dev/issues/1535): Fixed: [Widget](https://ckeditor.com/cke4/addon/widget) mouseover border contrast is insufficient. +* [#1516](https://github.com/ckeditor/ckeditor-dev/issues/1516): Fixed: Fake selection allows removing content in read-only mode using the Backspace and Delete keys. +* [#1570](https://github.com/ckeditor/ckeditor-dev/issues/1570): Fixed: Fake selection allows cutting content in read-only mode using the Ctrl/Cmd + X keys. +* [#1363](https://github.com/ckeditor/ckeditor-dev/issues/1363): Fixed: Paste notification is unclear and it might confuse users. + +API Changes: + +* [#1346](https://github.com/ckeditor/ckeditor-dev/issues/1346): [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) [context manager API](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.plugins.balloontoolbar.contextManager.html) is now available in the [`pluginDefinition.init()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_pluginDefinition.html#method-init) method of the [requiring](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_pluginDefinition.html#property-requires) plugin. +* [#1530](https://github.com/ckeditor/ckeditor-dev/issues/1530): Added the possibility to use custom icons for [buttons](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui_button.html.html). + +Other Changes: + +* Updated [SCAYT](https://ckeditor.com/cke4/addon/scayt) (Spell Check As You Type) and [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) (WSC) plugins: + * SCAYT [`scayt_minWordLength`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#scayt_minWordLength) configuration option now defaults to 3 instead of 4. + * SCAYT default number of suggested words in the context menu changed to 3. + * [#90](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/90): Fixed: Selection is lost on link creation if SCAYT highlights the word. + * Fixed: SCAYT crashes when the browser `localStorage` is disabled. + * [IE11] Fixed: `Unable to get property type of undefined or null reference` error in the browser console when SCAYT is disabled/enabled. + * [#46](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/46): Fixed: Editing is blocked when remote spell checker server is offline. + * Fixed: User Dictionary cannot be created in WSC due to `You already have the dictionary` error. + * Fixed: Words with apostrophe `'` on the replacement make the WSC dialog inaccessible. + * Fixed: SCAYT/WSC causes the `Uncaught TypeError` error in the browser console. +* [#1337](https://github.com/ckeditor/ckeditor-dev/issues/1337): Updated the samples layout with the new CKEditor 4 logo and color scheme. +* [#1591](https://github.com/ckeditor/ckeditor-dev/issues/1591): CKBuilder and language tools are now downloaded over HTTPS. Thanks to [August Detlefsen](https://github.com/augustd)! + +## CKEditor 4.8 + +**Important Notes:** + +* [#1249](https://github.com/ckeditor/ckeditor-dev/issues/1249): Enabled the [Upload Image](https://ckeditor.com/cke4/addon/uploadimage) plugin by default in standard and full presets. Also, it will no longer log an error in case of missing [`config.imageUploadUrl`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-imageUploadUrl) property. + +New Features: + +* [#933](https://github.com/ckeditor/ckeditor-dev/issues/933): Introduced [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) plugin. +* [#662](https://github.com/ckeditor/ckeditor-dev/issues/662): Introduced image inlining for the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#468](https://github.com/ckeditor/ckeditor-dev/issues/468): [Edge] Introduced support for the Clipboard API. +* [#607](https://github.com/ckeditor/ckeditor-dev/issues/607): Manually inserted Hex color is prefixed with a hash character (`#`) if needed. It ensures a valid Hex color value is used when setting the table cell border or background color with the [Color Dialog](https://ckeditor.com/cke4/addon/colordialog) window. +* [#584](https://github.com/ckeditor/ckeditor-dev/issues/584): [Font size and Family](https://ckeditor.com/cke4/addon/font) and [Format](https://ckeditor.com/cke4/addon/format) drop-downs are not toggleable anymore. Default option to reset styles added. +* [#856](https://github.com/ckeditor/ckeditor-dev/issues/856): Introduced the [`CKEDITOR.tools.keystrokeToArray()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools.html#method-keystrokeToArray) method. It converts a keystroke into its string representation, returning every key name as a separate array element. +* [#1053](https://github.com/ckeditor/ckeditor-dev/issues/1053): Introduced the [`CKEDITOR.tools.object.merge()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-merge) method. It allows to merge two objects, returning the new object with all properties from both objects deeply cloned. +* [#1073](https://github.com/ckeditor/ckeditor-dev/issues/1073): Introduced the [`CKEDITOR.tools.array.every()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_array.html#method-every) method. It invokes a given test function on every array element and returns `true` if all elements pass the test. + +Fixed Issues: + +* [#796](https://github.com/ckeditor/ckeditor-dev/issues/796): Fixed: A list is pasted from OneNote in the reversed order. +* [#834](https://github.com/ckeditor/ckeditor-dev/issues/834): [IE9-11] Fixed: The editor does not save the selected state of radio buttons inserted by the [Form Elements](https://ckeditor.com/cke4/addon/forms) plugin. +* [#704](https://github.com/ckeditor/ckeditor-dev/issues/704): [Edge] Fixed: Using Ctrl/Cmd + Z breaks widget structure. +* [#591](https://github.com/ckeditor/ckeditor-dev/issues/591): Fixed: A column is inserted in a wrong order inside the table if any cell has a vertical split. +* [#787](https://github.com/ckeditor/ckeditor-dev/issues/787): Fixed: Using Cut inside a nested table does not cut the selected content. +* [#842](https://github.com/ckeditor/ckeditor-dev/issues/842): Fixed: List style not restored when toggling list indent level in the [Indent List](https://ckeditor.com/cke4/addon/indentlist) plugin. +* [#711](https://github.com/ckeditor/ckeditor-dev/issues/711): Fixed: Dragging widgets should only work with the left mouse button. +* [#862](https://github.com/ckeditor/ckeditor-dev/issues/862): Fixed: The "Object Styles" group in the [Styles Combo](https://ckeditor.com/cke4/addon/stylescombo) plugin is visible only if the whole element is selected. +* [#994](https://github.com/ckeditor/ckeditor-dev/pull/994): Fixed: Typo in the [`CKEDITOR.focusManager.focus()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_focusManager.html#method-focus) API documentation. Thanks to [benjy](https://github.com/benjy)! +* [#1014](https://github.com/ckeditor/ckeditor-dev/issues/1014): Fixed: The [Table Tools](https://ckeditor.com/cke4/addon/tabletools) Cell Properties dialog is now [Advanced Content Filter](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_acf.html) aware — it is not possible to change the cell width or height if corresponding styles are disabled. +* [#877](https://github.com/ckeditor/ckeditor-dev/issues/877): Fixed: A list with custom bullets with exotic characters crashes the editor when [pasted from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#605](https://github.com/ckeditor/ckeditor-dev/issues/605): Fixed: Inline widgets do not preserve trailing spaces. +* [#1008](https://github.com/ckeditor/ckeditor-dev/issues/1008): Fixed: Shorthand Hex colors from the [`config.colorButton_colors`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-colorButton_colors) option are not correctly highlighted in the [Color Button](https://ckeditor.com/cke4/addon/colorbutton) Text Color or Background Color panel. +* [#1094](https://github.com/ckeditor/ckeditor-dev/issues/1094): Fixed: Widget definition [`upcast`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget_definition.html#property-upcasts) methods are called for every element. +* [#1057](https://github.com/ckeditor/ckeditor-dev/issues/1057): Fixed: The [Notification](https://ckeditor.com/addon/notification) plugin overwrites Web Notifications API due to leakage to the global scope. +* [#1068](https://github.com/ckeditor/ckeditor-dev/issues/1068): Fixed: Upload widget paste listener ignores changes to the [`uploadWidgetDefinition`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.fileTools.uploadWidgetDefinition.html). +* [#921](https://github.com/ckeditor/ckeditor-dev/issues/921): Fixed: [Edge] CKEditor erroneously perceives internal copy and paste as type "external". +* [#1213](https://github.com/ckeditor/ckeditor-dev/issues/1213): Fixed: Multiple images uploaded using [Upload Image](https://ckeditor.com/cke4/addon/uploadimage) plugin are randomly duplicated or mangled. +* [#532](https://github.com/ckeditor/ckeditor-dev/issues/532): Fixed: Removed an outdated user guide link from the [About](https://ckeditor.com/cke4/addon/about) dialog. +* [#1221](https://github.com/ckeditor/ckeditor-dev/issues/1221): Fixed: Invalid CSS loaded by [Balloon Panel](https://ckeditor.com/cke4/addon/balloonpanel) plugin when [`config.skin`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-skin) is loaded using a custom path. +* [#522](https://github.com/ckeditor/ckeditor-dev/issues/522): Fixed: Widget selection is not removed when widget is inside table cell with [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin enabled. +* [#1027](https://github.com/ckeditor/ckeditor-dev/issues/1027): Fixed: Cannot add multiple images to the table with [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin in certain situations. +* [#1069](https://github.com/ckeditor/ckeditor-dev/issues/1069): Fixed: Wrong shape processing by [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#995](https://github.com/ckeditor/ckeditor-dev/issues/995): Fixed: Hyperlinked image gets inserted twice by [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#1287](https://github.com/ckeditor/ckeditor-dev/issues/1287): Fixed: [Widget](https://ckeditor.com/cke4/addon/widget) plugin throws exception if included in editor build but not loaded into editor's instance. + +API Changes: + +* [#1097](https://github.com/ckeditor/ckeditor-dev/issues/1097): Widget [`upcast`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget_definition.html#property-upcast) methods are now called in the [widget definition's](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_plugins_widget.html#property-definition) context. +* [#1118](https://github.com/ckeditor/ckeditor-dev/issues/1118): Added the `show` option in the [`balloonPanel.attach()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_ui_balloonPanel.html#method-attach) method, allowing to attach a hidden [Balloon Panel](https://ckeditor.com/cke4/addon/balloonpanel) instance. +* [#1145](https://github.com/ckeditor/ckeditor-dev/issues/1145): Added the [`skipNotifications`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_fileTools_uploadWidgetDefinition.html#property-skipNotifications) option to the [`CKEDITOR.fileTools.uploadWidgetDefinition`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.fileTools.uploadWidgetDefinition.html), allowing to switch off default notifications displayed by upload widgets. + +Other Changes: + +* [#815](https://github.com/ckeditor/ckeditor-dev/issues/815): Removed Node.js dependency from the CKEditor build script. +* [#1041](https://github.com/ckeditor/ckeditor-dev/pull/1041), [#1131](https://github.com/ckeditor/ckeditor-dev/issues/1131): Updated URLs pointing to [CKSource](https://cksource.com/) and [CKEditor](https://ckeditor.com/) resources after the launch of new websites. + +## CKEditor 4.7.3 + +New Features: + +* [#568](https://github.com/ckeditor/ckeditor-dev/issues/568): Added possibility to adjust nested editables' filters using the [`CKEDITOR.filter.disallowedContent`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_filter.html#property-disallowedContent) property. + +Fixed Issues: + +* [#554](https://github.com/ckeditor/ckeditor-dev/issues/554): Fixed: [`change`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#event-change) event not fired when typing the first character after pasting into the editor. Thanks to [Daniel Miller](https://github.com/millerdev)! +* [#566](https://github.com/ckeditor/ckeditor-dev/issues/566): Fixed: The CSS `border` shorthand property with zero width (`border: 0px solid #000;`) causes the table to have the border attribute set to 1. +* [#779](https://github.com/ckeditor/ckeditor-dev/issues/779): Fixed: The [Remove Format](https://ckeditor.com/cke4/addon/removeformat) plugin removes elements with language definition inserted by the [Language](https://ckeditor.com/cke4/addon/language) plugin. +* [#423](https://github.com/ckeditor/ckeditor-dev/issues/423): Fixed: The [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin pastes paragraphs into the editor even if [`CKEDITOR.config.enterMode`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-enterMode) is set to `CKEDITOR.ENTER_BR`. +* [#719](https://github.com/ckeditor/ckeditor-dev/issues/719): Fixed: Image inserted using the [Enhanced Image](https://ckeditor.com/cke4/addon/image2) plugin can be resized when the editor is in [read-only mode](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_readonly.html). +* [#577](https://github.com/ckeditor/ckeditor-dev/issues/577): Fixed: The "Delete Columns" command provided by the [Table Tools](https://ckeditor.com/cke4/addon/tabletools) plugin throws an error when trying to delete columns. +* [#867](https://github.com/ckeditor/ckeditor-dev/issues/867): Fixed: Typing into a selected table throws an error. +* [#817](https://github.com/ckeditor/ckeditor-dev/issues/817): Fixed: The [Save](https://ckeditor.com/cke4/addon/save) plugin does not work in [Source Mode](https://ckeditor.com/cke4/addon/sourcearea). + +Other Changes: + +* Updated the [WebSpellChecker](https://ckeditor.com/cke4/addon/wsc) plugin: + * [#40](https://github.com/WebSpellChecker/ckeditor-plugin-wsc/issues/40): Fixed: IE10 throws an error when spell checking is started. +* [#800](https://github.com/ckeditor/ckeditor-dev/issues/800): Added the [`CKEDITOR.dom.selection.isCollapsed()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_selection.html#method-isCollapsed) method which is a simpler way to check if the selection is collapsed. +* [#830](https://github.com/ckeditor/ckeditor-dev/issues/830): Added an option to define which dialog tab should be shown by default when creating [`CKEDITOR.dialogCommand`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.dialogCommand.html). + +## CKEditor 4.7.2 + +New Features: + +* [#455](https://github.com/ckeditor/ckeditor-dev/issues/455): Added [Advanced Content Filter](https://ckeditor.com/docs/ckeditor4/latest/guide/dev_acf.html) integration with the [Justify](https://ckeditor.com/cke4/addon/justify) plugin. + +Fixed Issues: + +* [#663](https://github.com/ckeditor/ckeditor-dev/issues/663): [Chrome] Fixed: Clicking the scrollbar throws an `Uncaught TypeError: element.is is not a function` error. +* [#694](https://github.com/ckeditor/ckeditor-dev/pull/694): Refactoring in the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin: + * [#520](https://github.com/ckeditor/ckeditor-dev/issues/520): Fixed: Widgets cannot be properly pasted into a table cell. + * [#460](https://github.com/ckeditor/ckeditor-dev/issues/460): Fixed: Editor gone after pasting into an editor within a table. +* [#579](https://github.com/ckeditor/ckeditor-dev/issues/579): Fixed: Internal `cke_table-faked-selection-table` class is visible in the Stylesheet Classes field of the [Table Properties](https://ckeditor.com/cke4/addon/table) dialog. +* [#545](https://github.com/ckeditor/ckeditor-dev/issues/545): [Edge] Fixed: Error thrown when pressing the [Select All](https://ckeditor.com/cke4/addon/selectall) button in [Source Mode](https://ckeditor.com/cke4/addon/sourcearea). +* [#582](https://github.com/ckeditor/ckeditor-dev/issues/582): Fixed: Double slash in the path to stylesheet needed by the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin. Thanks to [Marius Dumitru Florea](https://github.com/mflorea)! +* [#491](https://github.com/ckeditor/ckeditor-dev/issues/491): Fixed: Unnecessary dependency on the [Editor Toolbar](https://ckeditor.com/cke4/addon/toolbar) plugin inside the [Notification](https://ckeditor.com/cke4/addon/notification) plugin. +* [#646](https://github.com/ckeditor/ckeditor-dev/issues/646): Fixed: Error thrown into the browser console after opening the [Styles Combo](https://ckeditor.com/cke4/addon/stylescombo) plugin menu in the editor without any selection. +* [#501](https://github.com/ckeditor/ckeditor-dev/issues/501): Fixed: Double click does not open the dialog for modifying anchors inserted via the [Link](https://ckeditor.com/cke4/addon/link) plugin. +* [#9780](https://dev.ckeditor.com/ticket/9780): [IE8-9] Fixed: Clicking inside an empty [read-only](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#property-readOnly) editor throws an error. +* [#16820](https://dev.ckeditor.com/ticket/16820): [IE10] Fixed: Clicking below a single horizontal rule throws an error. +* [#426](https://github.com/ckeditor/ckeditor-dev/issues/426): Fixed: The [`range.cloneContents()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-cloneContents) method selects the whole element when the selection starts at the beginning of that element. +* [#644](https://github.com/ckeditor/ckeditor-dev/issues/644): Fixed: The [`range.extractContents()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-extractContents) method returns an incorrect result when multiple nodes are selected. +* [#684](https://github.com/ckeditor/ckeditor-dev/issues/684): Fixed: The [`elementPath.contains()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_elementPath.html#method-contains) method incorrectly excludes the last element instead of root when the `fromTop` parameter is set to `true`. + +Other Changes: + +* Updated the [SCAYT](https://ckeditor.com/cke4/addon/scayt) (Spell Check As You Type) plugin: + * [#148](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/148): Fixed: SCAYT leaves underlined word after the CKEditor Replace dialog corrects it. +* [#751](https://github.com/ckeditor/ckeditor-dev/issues/751): Added the [`CKEDITOR.dom.nodeList.toArray()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_nodeList.html#method-toArray) method which returns an array representation of a [node list](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.dom.nodeList.html). + +## CKEditor 4.7.1 + +New Features: + +* Added a new Mexican Spanish localization. Thanks to [David Alexandro Rodriguez](https://www.transifex.com/user/profile/darsco16/)! +* [#413](https://github.com/ckeditor/ckeditor-dev/issues/413): Added Paste as Plain Text keyboard shortcut to the [Accessibility Help](https://ckeditor.com/cke4/addon/a11yhelp) instructions. + +Fixed Issues: + +* [#515](https://github.com/ckeditor/ckeditor-dev/issues/515): [Chrome] Fixed: Mouse actions on CKEditor scrollbar throw an exception when the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin is loaded. +* [#493](https://github.com/ckeditor/ckeditor-dev/issues/493): Fixed: Selection started from a nested table causes an error in the browser while scrolling down. +* [#415](https://github.com/ckeditor/ckeditor-dev/issues/415): [Firefox] Fixed: Enter key breaks the table structure when pressed in a table selection. +* [#457](https://github.com/ckeditor/ckeditor-dev/issues/457): Fixed: Error thrown when deleting content from the editor with no selection. +* [#478](https://github.com/ckeditor/ckeditor-dev/issues/478): [Chrome] Fixed: Error thrown by the [Enter Key](https://ckeditor.com/cke4/addon/enterkey) plugin when pressing Enter with no selection. +* [#424](https://github.com/ckeditor/ckeditor-dev/issues/424): Fixed: Error thrown by [Tab Key Handling](https://ckeditor.com/cke4/addon/tab) and [Indent List](https://ckeditor.com/cke4/addon/indentlist) plugins when pressing Tab with no selection in inline editor. +* [#476](https://github.com/ckeditor/ckeditor-dev/issues/476): Fixed: Anchors inserted with the [Link](https://ckeditor.com/cke4/addon/link) plugin on collapsed selection cannot be edited. +* [#417](https://github.com/ckeditor/ckeditor-dev/issues/417): Fixed: The [Table Resize](https://ckeditor.com/cke4/addon/tableresize) plugin throws an error when used with a table with only header or footer rows. +* [#523](https://github.com/ckeditor/ckeditor-dev/issues/523): Fixed: The [`editor.getCommandKeystroke()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getCommandKeystroke) method does not obtain the correct keystroke. +* [#534](https://github.com/ckeditor/ckeditor-dev/issues/534): [IE] Fixed: [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) does not work in Quirks Mode. +* [#450](https://github.com/ckeditor/ckeditor-dev/issues/450): Fixed: [`CKEDITOR.filter`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.filter.html) incorrectly transforms the `margin` CSS property. + +## CKEditor 4.7 + +**Important Notes:** + +* [#13793](https://dev.ckeditor.com/ticket/13793): The [`embed_provider`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-embed_provider) configuration option for the [Media Embed](https://ckeditor.com/cke4/addon/embed) and [Semantic Media Embed](https://ckeditor.com/cke4/addon/embedsemantic) plugins is no longer preset by default. +* The [UI Color](https://ckeditor.com/cke4/addon/uicolor) plugin now uses a custom color picker instead of the `YUI 2.7.0` library which has some known vulnerabilities (it's a security precaution, there was no security issue in CKEditor due to the way it was used). + +New Features: + +* [#16755](https://dev.ckeditor.com/ticket/16755): Added the [Table Selection](https://ckeditor.com/cke4/addon/tableselection) plugin that lets you select and manipulate an arbitrary rectangular table fragment (a few cells, a row or a column). +* [#16961](https://dev.ckeditor.com/ticket/16961): Added support for pasting from Microsoft Excel. +* [#13381](https://dev.ckeditor.com/ticket/13381): Dynamic code evaluation call in [`CKEDITOR.template`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.template.html) removed. CKEditor can now be used without the `unsafe-eval` Content Security Policy. Thanks to [Caridy Patiño](http://caridy.name)! +* [#16971](https://dev.ckeditor.com/ticket/16971): Added support for color in the `background` property containing also other styles for table cells in the [Table Tools](https://ckeditor.com/cke4/addon/tabletools) plugin. +* [#16847](https://dev.ckeditor.com/ticket/16847): Added support for parsing and inlining any formatting created using the Microsoft Word style system to the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#16818](https://dev.ckeditor.com/ticket/16818): Added table cell height parsing in the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#16850](https://dev.ckeditor.com/ticket/16850): Added a new [`config.enableContextMenu`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-enableContextMenu) configuration option for enabling and disabling the [context menu](https://ckeditor.com/cke4/addon/contextmenu). +* [#16937](https://dev.ckeditor.com/ticket/16937): The `command` parameter in [`CKEDITOR.editor.getCommandKeystroke()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getCommandKeystroke) now also accepts a command name as an argument. +* [#17010](https://dev.ckeditor.com/ticket/17010): The [`CKEDITOR.dom.range.shrink()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-shrink) method now allows for skipping bogus `
      ` elements. + +Fixed Issues: + +* [#16935](https://dev.ckeditor.com/ticket/16935): [Chrome] Fixed: Blurring the editor in [Source Mode](https://ckeditor.com/cke4/addon/sourcearea) throws an error. +* [#16825](https://dev.ckeditor.com/ticket/16825): [Chrome] Fixed: Error thrown when destroying a focused inline editor. +* [#16857](https://dev.ckeditor.com/ticket/16857): Fixed: Ctrl+Shift+V blocked by [Copy Formatting](https://ckeditor.com/cke4/addon/copyformatting). +* [#16845](https://dev.ckeditor.com/ticket/16845): [IE] Fixed: Cursor jumps to the top of the scrolled editor after focusing it when the [Copy Formatting](https://ckeditor.com/cke4/addon/copyformatting) plugin is enabled. +* [#16786](https://dev.ckeditor.com/ticket/16786): Fixed: Added missing translations for the [Copy Formatting](https://ckeditor.com/cke4/addon/copyformatting) plugin. +* [#14714](https://dev.ckeditor.com/ticket/14714): [WebKit/Blink] Fixed: Exception thrown on refocusing a blurred inline editor. +* [#16913](https://dev.ckeditor.com/ticket/16913): [Firefox, IE] Fixed: [Paste as Plain Text](https://ckeditor.com/cke4/addon/pastetext) keystroke does not work. +* [#16968](https://dev.ckeditor.com/ticket/16968): Fixed: [Safari] [Paste as Plain Text](https://ckeditor.com/cke4/addon/pastetext) is not handled by the editor. +* [#16912](https://dev.ckeditor.com/ticket/16912): Fixed: Exception thrown when a single image is pasted using [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#16821](https://dev.ckeditor.com/ticket/16821): Fixed: Extraneous `` elements with `height` style stacked when [pasting from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#16866](https://dev.ckeditor.com/ticket/16866): [IE, Edge] Fixed: Whitespaces not preserved when [pasting from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#16860](https://dev.ckeditor.com/ticket/16860): Fixed: Paragraphs which only look like lists incorrectly transformed into them when [pasting from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#16817](https://dev.ckeditor.com/ticket/16817): Fixed: When [pasting from Word](https://ckeditor.com/cke4/addon/pastefromword), paragraphs are transformed into lists with some corrupted data. +* [#16833](https://dev.ckeditor.com/ticket/16833): [IE11] Fixed: Malformed list with headers [pasted from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#16826](https://dev.ckeditor.com/ticket/16826): [IE] Fixed: Superfluous paragraphs within lists [pasted from Word](https://ckeditor.com/cke4/addon/pastefromword). +* [#12465](https://dev.ckeditor.com/ticket/12465): Fixed: Cannot change the state of checkboxes or radio buttons if the properties dialog was invoked with a double-click. +* [#13062](https://dev.ckeditor.com/ticket/13062): Fixed: Impossible to unlink when the caret is at the edge of the link. +* [#13585](https://dev.ckeditor.com/ticket/13585): Fixed: Error when wrapping two adjacent `
      ` elements with a `
      `. +* [#16811](https://dev.ckeditor.com/ticket/16811): Fixed: Table alignment is not preserved by the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#16810](https://dev.ckeditor.com/ticket/16810): Fixed: Vertical align in tables is not supported by the [Paste from Word](https://ckeditor.com/cke4/addon/pastefromword) plugin. +* [#11956](https://dev.ckeditor.com/ticket/11956): [Blink, IE] Fixed: [Link](https://ckeditor.com/cke4/addon/link) dialog does not open on a double click on the second word of the link with a background color or other styles. +* [#10472](https://dev.ckeditor.com/ticket/10472): Fixed: Unable to use [Table Resize](https://ckeditor.com/cke4/addon/tableresize) on table header and footer. +* [#14762](https://dev.ckeditor.com/ticket/14762): Fixed: Hovering over an empty table (without rows or cells) throws an error when the [Table Resize](https://ckeditor.com/cke4/addon/tableresize) plugin is active. +* [#16777](https://dev.ckeditor.com/ticket/16777): [Edge] Fixed: The [Clipboard](https://ckeditor.com/cke4/addon/clipboard) plugin does not allow to drop widgets into the editor. +* [#14894](https://dev.ckeditor.com/ticket/14894): [Chrome] Fixed: The editor scrolls to the top after focusing or when a dialog is opened. +* [#14769](https://dev.ckeditor.com/ticket/14769): Fixed: URLs with '-' in host are not detected by the [Auto Link](https://ckeditor.com/cke4/addon/autolink) plugin. +* [#16804](https://dev.ckeditor.com/ticket/16804): Fixed: Focus is not on the first menu item when the user opens a context menu or a drop-down list from the editor toolbar. +* [#14407](https://dev.ckeditor.com/ticket/14407): [IE] Fixed: Non-editable widgets can be edited. +* [#16927](https://dev.ckeditor.com/ticket/16927): Fixed: An error thrown if a bundle containing the [Color Button](https://ckeditor.com/cke4/addon/colorbutton) plugin is run in ES5 strict mode. Thanks to [Igor Rubinovich](https://github.com/IgorRubinovich)! +* [#16920](https://dev.ckeditor.com/ticket/16920): Fixed: Several plugins not using the [Dialog](https://ckeditor.com/cke4/addon/dialog) plugin as a direct dependency. +* [PR#336](https://github.com/ckeditor/ckeditor-dev/pull/336): Fixed: Typo in [`CKEDITOR.getCss()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#method-getCss) API documentation. Thanks to [knusperpixel](https://github.com/knusperpixel)! +* [#17027](https://dev.ckeditor.com/ticket/17027): Fixed: Command event data should be initialized as an empty object. +* Fixed the behavior of HTML parser when parsing `src`/`srcdoc` attributes of the `\n\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\t\t
      \n\t\t\t\t\t\t\tApp Sort\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
      \n\t\t\t\t\t
      \n\t\t\t\t\n\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t
      Upgrade to Pro edition, all plugins, free to use!
      \n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t\t\t
      Recently visited plugin:
      \n\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t\t
      \n\t\t\t\t\t\t
      \n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t'),window.usePay=dC,$("#soft-main").append("\n\t\t\t'); + if (aceEditor.editor !== null) { + if (aceEditor.isAceView == false) { + aceEditor.isAceView = true; + $('.aceEditors .layui-layer-max').click(); + } + aceEditor.openEditorView(path); + return false; + } + var r = layer.open({ + type: 1, + maxmin: true, + shade: false, + area: ['80%', '80%'], + title: lan.public.online_text_editor, + skin: 'aceEditors', + zIndex: 19999, + content: _aceTmplate, + success: function (layero, index) { + function set_edit_file() { + // aceEditor.layer_view = index; + aceEditor.ace_active = ''; + aceEditor.eventEditor(); + $('#ace_conter').addClass(aceEditor.editorTheme); + ace.require('/ace/ext/language_tools'); + ace.config.set('modePath', '/static/ace'); + ace.config.set('workerPath', '/static/ace'); + ace.config.set('themePath', '/static/ace'); + aceEditor.openEditorView(path); + var _left = parseInt($(layero).css('left')), + _top = parseInt($(layero).css('top')); + _left < 0 ? $(layero).css('left', Math.abs(_left)) : $(layero).css('left', _left); + _top < 0 ? $(layero).css('top', Math.abs(_top)) : $(layero).css('top', _top); + // $('.aceEditors .layui-layer-min').click(function(e) { + // aceEditor.isAceView = false; + // setTimeout(function() { + // var _id = $('.ace_conter_menu .active').attr('data-id'); + // aceEditor.editor['ace_editor_' + _id].ace.resize(); + // }, 105); + // }); + // $('.aceEditors .layui-layer-max').click(function(e) { + // setTimeout(function() { + // aceEditor.setEditorView(); + // var _id = $('.ace_conter_menu .active').attr('data-id'); + // aceEditor.editor['ace_editor_' + _id].ace.resize(); + // }, 105); + // }); + $('.aceEditors .layui-layer-min').click(function (e) { + aceEditor.setEditorView(); + }); + $('.aceEditors .layui-layer-max').click(function (e) { + aceEditor.setEditorView(); + }); + } + var aceConfig = aceEditor.getStorage('aceConfig'); + if (aceConfig == null) { + // 获取编辑器配置 + aceEditor.getAceConfig(function (res) { + aceEditor.aceConfig = res; // 赋值配置参数 + set_edit_file(); + }); + } else { + aceEditor.aceConfig = JSON.parse(aceConfig); + typeof aceEditor.aceConfig == 'string' ? (aceEditor.aceConfig = JSON.parse(aceEditor.aceConfig)) : ''; + set_edit_file(); + } + }, + cancel: function () { + for (var item in aceEditor.editor) { + if (aceEditor.editor[item].fileType == 1) { + layer.open({ + type: 1, + area: ['400px', '180px'], + title: lan.public.save_tips, + content: + '\ +
      \ +
      \ +
      ' + + lan.public.save_tips1 + + '
      \ +
      ' + + lan.public.save_tips2 + + '
      \ +
      \ + \ + \ + \ +
      \ +
      ', + success: function (layers, indexs) { + $('.ace-clear-btn button').click(function () { + var _type = $(this).attr('data-type'); + switch (_type) { + case '2': + aceEditor.editor = null; + layer.closeAll(); + break; + case '1': + layer.close(indexs); + break; + case '0': + var _arry = [], + editor = aceEditor['editor']; + for (var item in editor) { + _arry.push({ + path: editor[item]['path'], + data: editor[item]['ace'].getValue(), + encoding: editor[item]['encoding'], + }); + } + aceEditor.saveAllFileBody(_arry, function () { + $('.ace_conter_menu>.item').each(function (el, indexx) { + var _id = $(this).attr('data-id'); + $(this).find('i').removeClass('glyphicon-exclamation-sign').addClass('glyphicon-remove').attr('data-file-state', '0'); + aceEditor.editor['ace_editor_' + _id].fileType = 0; + }); + aceEditor.editor = null; + aceEditor.pathAarry = []; + layer.closeAll(); + }); + break; + } + }); + }, + }); + return false; + } + } + }, + full: function (layero, index) { + //最大化 + aceEditor.editorStatus = 1; + }, + min: function (layero, index) { + //最小化 + aceEditor.editorStatus = -1; + }, + restore: function (layero, index) { + //还原 + aceEditor.editorStatus = 0; + }, + end: function () { + aceEditor.ace_active = ''; + aceEditor.editor = null; + aceEditor.pathAarry = []; + aceEditor.menu_path = ''; + }, + }); +} + +/** + * AES加密 + * @param {string} s_text 等待加密的字符串 + * @param {string} s_key 16位密钥 + * @param {array} ctx 可选,默认为 { mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.ZeroPadding } + * @return {string} + */ +function aes_encrypt(s_text, s_key, ctx) { + if (ctx == undefined) ctx = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.ZeroPadding }; + var key = CryptoJS.enc.Utf8.parse(s_key); + var encrypt_data = CryptoJS.AES.encrypt(s_text, key, ctx); + return encrypt_data.toString(); +} + +/** + * AES解密 + * @param {string} s_text 等待解密的密文 + * @param {string} s_key 16位密钥 + * @param {array} ctx 可选,默认为 { mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.ZeroPadding } + * @return {string} + */ +function aes_decrypt(s_text, s_key, ctx) { + if (ctx == undefined) ctx = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.ZeroPadding }; + var key = CryptoJS.enc.Utf8.parse(s_key); + var decrypt_data = CryptoJS.AES.decrypt(s_text, key, ctx); + return decrypt_data.toString(CryptoJS.enc.Utf8); +} + +/** + * ajax内容解密 + * @param {string} data 加密的响应数据 + * @param {string} stype ajax中定义的数据类型 + * @return {string} 解密后的响应数据 + */ +function ajax_decrypt(data, stype) { + if (!data) return data; + if (data.substring(0, 6) == 'BT-CRT') { + var token = $('#request_token_head').attr('token'); + var pwd = token.substring(0, 8) + token.substring(40, 48); + data = aes_decrypt(data.substring(6), pwd); + if (stype == undefined) { + stype = ''; + } + if (stype.toLowerCase() != 'json') { + data = JSON.parse(data); + } + } + return data; +} +/** + * 格式化form_data数据,并加密 + * @param {string} form_data 加密前的form_data数据 + * @return {string} 加密后的form_data数据 + */ +function format_form_data(form_data) { + var data_tmp = form_data.split('&'); + var form_info = {}; + var token = $('#request_token_head').attr('token'); + if (!token) return form_data; + var pwd = token.substring(0, 8) + token.substring(40, 48); + for (var i = 0; i < data_tmp.length; i++) { + var tmp = data_tmp[i].split('='); + if (tmp.length < 2) continue; + // if(!tmp[1]) continue; + var val = decodeURIComponent(tmp[1].replace(/\+/g, '%20')); + if (val.length > 3) { + form_info[tmp[0]] = 'BT-CRT' + aes_encrypt(val, pwd); + } else { + form_info[tmp[0]] = val; + } + } + return $.param(form_info); +} + +function ajax_encrypt(request) { + if (!this.type || !this.data || !this.contentType) return; + if ($('#panel_debug').attr('data') == 'True') return; + if ($('#panel_debug').attr('data-pyversion') == '2') return; + if (this.type == 'POST' && this.data.length > 1) { + this.data = format_form_data(this.data); + } +} + +// function ajaxSetup() { +// var my_headers = {}; +// var request_token_ele = document.getElementById("request_token_head"); +// if (request_token_ele) { +// var request_token = request_token_ele.getAttribute('token'); +// if (request_token) { +// my_headers['x-http-token'] = request_token +// } +// } +// request_token_cookie = getCookie('request_token'); +// if (request_token_cookie) { +// my_headers['x-cookie-token'] = request_token_cookie +// } +// +// if (my_headers) { +// $.ajaxSetup({ +// headers: my_headers, +// // dataFilter: ajax_decrypt, +// // beforeSend: ajax_encrypt +// }); +// } +// } +function ajaxSetup() { + var my_headers = {}; + var request_token_ele = document.getElementById('request_token_head'); + if (request_token_ele) { + var request_token = request_token_ele.getAttribute('token'); + if (request_token) { + my_headers['x-http-token'] = request_token; + } + } + request_token_cookie = getCookie('request_token'); + if (request_token_cookie) { + my_headers['x-cookie-token'] = request_token_cookie; + } + + if (my_headers) { + $.ajaxSetup({ + headers: my_headers, + error: function (jqXHR, textStatus, errorThrown) { + if (!jqXHR.responseText) return; + if (typeof String.prototype.trim === 'undefined') { + String.prototype.trim = function () { + return String(this).replace(/^\s+|\s+$/g, ''); + }; + } + + error_key = 'We need to make sure this has a favicon so that the debugger does'; + error_find = jqXHR.responseText.indexOf(error_key); + if (jqXHR.status == 500 && (jqXHR.responseText.indexOf('An error occurred while the panel was running') != -1 || error_find != -1)) { + // if(jqXHR.responseText.indexOf('请先绑定宝塔帐号!') != -1){ + // bt.pub.bind_btname(function(){ + // window.location.reload(); + // }); + // return; + // } + if (error_find != -1) { + var error_body = jqXHR.responseText.split('', ''); + var tmp = error_body.split('During handling of the above exception, another exception occurred:'); + error_body = tmp[tmp.length - 1]; + var error_msg = + '
      \ +

      出错了,面板运行时发生错误!

      \ +
      ' +
      +							error_body.trim() +
      +							'
      \ +
        \ +
      • 很抱歉,面板运行时意外发生错误,请尝试按以下顺序尝试解除此错误:
      • \ +
      • 1、在[首页]右上角点击修复面板,并退出面板重新登录。
      • \ +
      • 2、如上述尝试未能解除此错误,请截图此窗口到宝塔论坛发贴寻求帮助, 论坛地址:https://www.bt.cn/bbs
      • \ +
      \ +
      '; + } else { + var error_msg = jqXHR.responseText; + } + $('.layui-layer-padding').parents('.layer-anim').remove(); + $('.layui-layer-shade').remove(); + setTimeout(function () { + layer.open({ + title: false, + content: error_msg, + closeBtn: 2, + area: ['1000px', '800px'], + btn: false, + shadeClose: false, + shade: 0.3, + success: function () { + $('pre').scrollTop(100000000000); + }, + }); + }, 100); + } + }, + // dataFilter: ajax_decrypt, + // beforeSend: ajax_encrypt + }); + } +} +ajaxSetup(); + +function RandomStrPwd(b) { + b = b || 32; + var c = 'AaBbCcDdEeFfGHhiJjKkLMmNnPpRSrTsWtXwYxZyz2345678'; + var a = c.length; + var d = ''; + for (i = 0; i < b; i++) { + d += c.charAt(Math.floor(Math.random() * a)); + } + return d; +} + +function repeatPwd(a) { + $('#MyPassword').val(RandomStrPwd(a)); +} + +function refresh() { + window.location.reload(); +} + +function GetBakPost(b) { + $('.baktext').hide().prev().show(); + var c = $('.baktext').attr('data-id'); + var a = $('.baktext').val(); + if (a == '') { + a = lan.bt.empty; + } + setWebPs(b, c, a); + $("a[data-id='" + c + "']").html(a); + $('.baktext').remove(); +} + +function setWebPs(b, e, a) { + var d = layer.load({ + shade: true, + shadeClose: false, + }); + var c = 'ps=' + a; + $.post('/data?action=setPs', 'table=' + b + '&id=' + e + '&' + c, function (f) { + if (f == true) { + if (b == 'sites') { + getWeb(1); + } else { + if (b == 'ftps') { + getFtp(1); + } else { + getData(1); + } + } + layer.closeAll(); + layer.msg(lan.public.edit_ok, { + icon: 1, + }); + } else { + layer.msg(lan.public.edit_err, { + icon: 2, + }); + layer.closeAll(); + } + }); +} + +$('.menu-icon').click(function () { + $('.sidebar-scroll').toggleClass('sidebar-close'); + $('.main-content').toggleClass('main-content-open'); + if ($('.sidebar-close')) { + $('.sub-menu').find('.sub').css('display', 'none'); + } +}); +var Upload, percentage; + +Date.prototype.format = function (b) { + var c = { + 'M+': this.getMonth() + 1, + 'd+': this.getDate(), + 'h+': this.getHours(), + 'm+': this.getMinutes(), + 's+': this.getSeconds(), + 'q+': Math.floor((this.getMonth() + 3) / 3), + S: this.getMilliseconds(), + }; + if (/(y+)/.test(b)) { + b = b.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)); + } + for (var a in c) { + if (new RegExp('(' + a + ')').test(b)) { + b = b.replace(RegExp.$1, RegExp.$1.length == 1 ? c[a] : ('00' + c[a]).substr(('' + c[a]).length)); + } + } + return b; +}; + +function getLocalTime(a) { + a = a.toString(); + if (a.length > 10) { + a = a.substring(0, 10); + } + return new Date(parseInt(a) * 1000).format('yyyy/MM/dd hh:mm:ss'); +} + +function ToSize(a) { + var d = [' B', ' KB', ' MB', ' GB', ' TB', ' PB']; + var e = 1024; + for (var b = 0; b < d.length; b++) { + if (a < e) { + return (b == 0 ? a : a.toFixed(2)) + d[b]; + } + a /= e; + } +} + +function ChangePath(d) { + setCookie('SetId', d); + setCookie('SetName', ''); + var c = layer.open({ + type: 1, + area: '680px', + title: lan.bt.dir, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
      " + + lan.bt.path + + ":
      " + + lan.bt.comp + + "
        " + + lan.bt.filename + + "" + + lan.bt.etime + + "" + + lan.bt.access + + "" + + lan.bt.own + + "
        ', + }); + setCookie('ChangePath', c); + var b = $('#' + d).val(); + tmp = b.split('.'); + if (tmp[tmp.length - 1] == 'gz') { + tmp = b.split('/'); + b = ''; + for (var a = 0; a < tmp.length - 1; a++) { + b += '/' + tmp[a]; + } + setCookie('SetName', tmp[tmp.length - 1]); + } + b = b.replace(/\/\//g, '/'); + GetDiskList(b); + ActiveDisk(); +} + +function GetDiskList(b) { + var d = ''; + var a = ''; + var c = 'path=' + b + '&disk=True'; + $.post('/files?action=GetDir', c, function (h) { + if (h.status == false) { + layer.close(layer.index); + layer.msg(h.msg, { icon: 2 }); + return false; + } + if (h.DISK != undefined) { + for (var f = 0; f < h.DISK.length; f++) { + a += '
         " + h.DISK[f].path + '
        '; + } + $('#changecomlist').html(a); + } + for (var f = 0; f < h.DIR.length; f++) { + var g = h.DIR[f].split(';'); + var e = g[0]; + if (e.length > 20) { + e = e.substring(0, 20) + '...'; + } + if (isChineseChar(e)) { + if (e.length > 10) { + e = e.substring(0, 10) + '...'; + } + } + d += + '" + + e + + '' + + getLocalTime(g[2]) + + '' + + g[3] + + '' + + g[4] + + "X'; + } + if (h.FILES != null && h.FILES != '') { + for (var f = 0; f < h.FILES.length; f++) { + var g = h.FILES[f].split(';'); + var e = g[0]; + if (e.length > 20) { + e = e.substring(0, 20) + '...'; + } + if (isChineseChar(e)) { + if (e.length > 10) { + e = e.substring(0, 10) + '...'; + } + } + d += "" + e + '' + getLocalTime(g[2]) + '' + g[3] + '' + g[4] + ''; + } + } + $('.default').hide(); + $('.file-list').show(); + $('#tbody').html(d); + if (h.PATH.substr(h.PATH.length - 1, 1) != '/') { + h.PATH += '/'; + } + $('#PathPlace').find('span').html(h.PATH); + ActiveDisk(); + return; + }); +} + +function CreateFolder() { + var a = + "   '; + if ($('#tbody tr').length == 0) { + $('#tbody').append(a); + } else { + $('#tbody tr:first-child').before(a); + } + $('.newFolderName').focus(); + $('#nameOk').click(function () { + var c = $('#newFolderName').val(); + var b = $('#PathPlace').find('span').text(); + newTxt = b.replace(new RegExp(/(\/\/)/g), '/') + c; + var d = 'path=' + newTxt; + $.post('/files?action=CreateDir', d, function (e) { + if (e.status == true) { + layer.msg(e.msg, { + icon: 1, + }); + } else { + layer.msg(e.msg, { + icon: 2, + }); + } + GetDiskList(b); + }); + }); + $('#nameNOk').click(function () { + $(this).parents('tr').remove(); + }); +} + +function NewDelFile(c) { + var a = $('#PathPlace').find('span').text(); + newTxt = c.replace(new RegExp(/(\/\/)/g), '/'); + var b = 'path=' + newTxt + '&empty=True'; + $.post('/files?action=DeleteDir', b, function (d) { + if (d.status == true) { + layer.msg(d.msg, { + icon: 1, + }); + } else { + layer.msg(d.msg, { + icon: 2, + }); + } + GetDiskList(a); + }); +} + +function ActiveDisk() { + var a = $('#PathPlace').find('span').text().substring(0, 1); + switch (a) { + case 'C': + $('.path-con-left dd:nth-of-type(1)').css('background', '#eee').siblings().removeAttr('style'); + break; + case 'D': + $('.path-con-left dd:nth-of-type(2)').css('background', '#eee').siblings().removeAttr('style'); + break; + case 'E': + $('.path-con-left dd:nth-of-type(3)').css('background', '#eee').siblings().removeAttr('style'); + break; + case 'F': + $('.path-con-left dd:nth-of-type(4)').css('background', '#eee').siblings().removeAttr('style'); + break; + case 'G': + $('.path-con-left dd:nth-of-type(5)').css('background', '#eee').siblings().removeAttr('style'); + break; + case 'H': + $('.path-con-left dd:nth-of-type(6)').css('background', '#eee').siblings().removeAttr('style'); + break; + default: + $('.path-con-left dd').removeAttr('style'); + } +} + +function BackMyComputer() { + $('.default').show(); + $('.file-list').hide(); + $('#PathPlace').find('span').html(''); + ActiveDisk(); +} + +function BackFile() { + var c = $('#PathPlace').find('span').text(); + if (c.substr(c.length - 1, 1) == '/') { + c = c.substr(0, c.length - 1); + } + var d = c.split('/'); + var a = ''; + if (d.length > 1) { + var e = d.length - 1; + for (var b = 0; b < e; b++) { + a += d[b] + '/'; + } + GetDiskList(a.replace('//', '/')); + } else { + a = d[0]; + } + if (d.length == 1) { + } +} + +function GetfilePath() { + var a = $('#PathPlace').find('span').text(); + a = a.replace(new RegExp(/(\\)/g), '/'); + setCookie('path_dir_change', a); + $('#' + getCookie('SetId')).val(a + getCookie('SetName')); + layer.close(getCookie('ChangePath')); +} + +function setCookie(a, c) { + var b = 30; + var d = new Date(); + d.setTime(d.getTime() + b * 24 * 60 * 60 * 1000); + document.cookie = a + '=' + escape(c) + ';expires=' + d.toGMTString(); +} + +function getCookie(b) { + var a, + c = new RegExp('(^| )' + b + '=([^;]*)(;|$)'); + if ((a = document.cookie.match(c))) { + return unescape(a[2]); + } else { + return null; + } +} + +function aotuHeight() { + var a = $('body').height() - 52 - 32; + $('.main-content').css('min-height', a); +} +$(function () { + aotuHeight(); +}); +$(window).resize(function () { + aotuHeight(); +}); + +function showHidePwd() { + var a = 'glyphicon-eye-open', + b = 'glyphicon-eye-close'; + $('.pw-ico').click(function () { + var g = $(this).attr('class'), + e = $(this).prev(); + if (g.indexOf(a) > 0) { + var h = e.attr('data-pw'); + $(this).removeClass(a).addClass(b); + e.text(h); + } else { + $(this).removeClass(b).addClass(a); + e.text('**********'); + } + var d = $(this).next().position().left; + var f = $(this).next().position().top; + var c = $(this).next().width(); + $(this) + .next() + .next() + .css({ + left: d + c + 'px', + top: f + 'px', + }); + }); +} + +function openPath(a) { + setCookie('Path', a); + window.location.href = '/files'; +} + +function OnlineEditFile(k, f) { + if (k != 0) { + var l = $('#PathPlace input').val(); + var h = encodeURIComponent($('#textBody').val()); + var a = $('select[name=encoding]').val(); + var loadT = layer.msg(lan.bt.save_file, { + icon: 16, + time: 0, + }); + $.post('/files?action=SaveFileBody', 'data=' + h + '&path=' + encodeURIComponent(f) + '&encoding=' + a, function (m) { + if (k == 1) { + layer.close(loadT); + } + layer.msg(m.msg, { + icon: m.status ? 1 : 2, + }); + }); + return; + } + var e = layer.msg(lan.bt.read_file, { + icon: 16, + time: 0, + }); + var g = f.split('.'); + var b = g[g.length - 1]; + var d; + switch (b) { + case 'html': + var j = { + name: 'htmlmixed', + scriptTypes: [ + { + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null, + }, + { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: 'vbscript', + }, + ], + }; + d = j; + break; + case 'htm': + var j = { + name: 'htmlmixed', + scriptTypes: [ + { + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null, + }, + { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: 'vbscript', + }, + ], + }; + d = j; + break; + case 'js': + d = 'text/javascript'; + break; + case 'json': + d = 'application/ld+json'; + break; + case 'css': + d = 'text/css'; + break; + case 'php': + d = 'application/x-httpd-php'; + break; + case 'tpl': + d = 'application/x-httpd-php'; + break; + case 'xml': + d = 'application/xml'; + break; + case 'sql': + d = 'text/x-sql'; + break; + case 'conf': + d = 'text/x-nginx-conf'; + break; + default: + var j = { + name: 'htmlmixed', + scriptTypes: [ + { + matches: /\/x-handlebars-template|\/x-mustache/i, + mode: null, + }, + { + matches: /(text|application)\/(x-)?vb(a|script)/i, + mode: 'vbscript', + }, + ], + }; + d = j; + } + $.post('/files?action=GetFileBody', 'path=' + encodeURIComponent(f), function (s) { + if (s.status === false) { + layer.msg(s.msg, { icon: 5 }); + return; + } + layer.close(e); + var u = ['utf-8', 'GBK', 'GB2312', 'BIG5']; + var n = ''; + var m = ''; + var o = ''; + for (var p = 0; p < u.length; p++) { + m = s.encoding == u[p] ? 'selected' : ''; + n += ''; + } + var r = layer.open({ + type: 1, + shift: 5, + closeBtn: 2, + area: ['90%', '90%'], + title: lan.bt.edit_title + '[' + f + ']', + content: + '

        ' + + lan.bt.edit_ps + + '

        '; + $('.taskcon').html(lbody); + var ob = document.getElementById('exec_log'); + ob.scrollTop = ob.scrollHeight; + }); +} + +function get_msg_data(a, fun) { + a = a == undefined ? 1 : a; + $.post('/data?action=getData', 'tojs=remind&table=tasks&result=2,4,6,8&limit=10&search=1&p=' + a, function (g) { + fun(g); + }); +} + +function remind(a) { + get_msg_data(a, function (g) { + var e = ''; + var f = false; + var task_count = 0; + for (var d = 0; d < g.data.length; d++) { + if (g.data[d].status != '1') { + task_count++; + continue; + } + e += + '
        ' + + g.data[d].name + + g.data[d].addtime + + '【' + + lan.bt.task_ok + + '】' + + lan.bt.time + + (g.data[d].end - g.data[d].start) + + lan.bt.s + + '
        ' + + g.data[d].addtime + + ''; + } + var con = + '
        \ + \ + ' + + e + + '\ +
        ' + + lan.bt.task_name + + '' + + lan.bt.task_time + + '
        \ +
        \ + \ +
        \ +
        '; + + var msg_count = g.page.match(/\'Pcount\'>.+<\/span>/)[0].replace(/[^0-9]/gi, ''); + $('.msg_count').text(parseInt(msg_count) - task_count); + $('.taskcon').html(con); + $('#taskPage').html(g.page); + $('#Rs-checkAll').click(function () { + if ($(this).prop('checked')) { + $('#remind').find('input').prop('checked', true); + } else { + $('#remind').find('input').prop('checked', false); + } + }); + }); +} + +function GetReloads() { + var a = 0; + var mm = $('#taskList').html(); + if (mm == undefined || mm.indexOf(lan.bt.task_list) == -1) { + clearInterval(speed); + a = 0; + speed = null; + return; + } + if (speed) return; + speed = setInterval(function () { + var mm = $('#taskList').html(); + if (mm == undefined || mm.indexOf(lan.bt.task_list) == -1) { + clearInterval(speed); + speed = null; + a = 0; + return; + } + a++; + $.post('/files?action=GetTaskSpeed', '', function (h) { + if (h.task == undefined) { + $('.cmdlist').html(lan.bt.task_not_list); + return; + } + + if (h.status === false) { + clearInterval(speed); + speed = null; + a = 0; + return; + } + + var b = ''; + var d = ''; + $('#task').text(h.task.length); + $('.task_count').text(h.task.length); + for (var g = 0; g < h.task.length; g++) { + if (h.task[g].status == '-1') { + if (h.task[g].type != 'download') { + var c = ''; + var f = h.msg.split('\n'); + for (var e = 0; e < f.length; e++) { + c += f[e] + '
        '; + } + if (h.task[g].name.indexOf(lan.public.scan) != -1) { + b = + "
      • " + + h.task[g].name + + "" + + lan.bt.task_scan + + " | ' + + lan.public.close + + "
        " + + c + + '
      • '; + } else { + b = + "
      • " + + h.task[g].name + + "" + + lan.bt.task_install + + " | ' + + lan.public.close + + "
        " + + c + + '
      • '; + } + } else { + b = + "
      • " + + h.task[g].name + + "" + + (ToSize(h.msg.used) + '/' + ToSize(h.msg.total)) + + "" + + h.msg.pre + + "%" + + lan.bt.task_downloading + + " | ' + + lan.public.close + + '
      • '; + } + } else { + d += + "
      • " + + h.task[g].name + + "" + + lan.bt.task_sleep + + " | ' + + lan.public.del + + '
      • '; + } + } + $('.cmdlist').html(b + d); + $('.cmd').html(c); + try { + if ($('.cmd')[0].scrollHeight) $('.cmd').scrollTop($('.cmd')[0].scrollHeight); + } catch (e) { + return; + } + }).error(function () {}); + }, 1000); +} + +//检查选中项 +function RscheckSelect() { + setTimeout(function () { + var checkList = $('#remind').find('input'); + var count = 0; + for (var i = 0; i < checkList.length; i++) { + if (checkList[i].checked) count++; + } + if (count > 0) { + $('.buttongroup .btn').removeAttr('disabled'); + } else { + $('.rs-del,.rs-read').attr('disabled', 'disabled'); + } + }, 5); +} + +function tasklist(a) { + var con = '
          ' + lan.public.task_long_time_not_exec + ''; + $('.taskcon').html(con); + a = a == undefined ? 1 : a; + $.post('/data?action=getData', 'tojs=GetTaskList&table=tasks&limit=10&p=' + a, function (g) { + var e = ''; + var b = ''; + var c = ''; + var f = false; + var task_count = 0; + for (var d = 0; d < g.data.length; d++) { + switch (g.data[d].status) { + case '-1': + f = true; + if (g.data[d].type != 'download') { + b = + "
        • " + + g.data[d].name + + "" + + lan.bt.task_install + + " | ' + + lan.public.close + + "
        • "; + } else { + b = + "
        • " + + g.data[d].name + + "0.0M/12.5M0%" + + lan.bt.task_downloading + + " | ' + + lan.public.close + + '
        • '; + } + task_count++; + break; + case '0': + c += + "
        • " + + g.data[d].name + + "" + + lan.bt.task_sleep + + ' | " + + lan.public.del + + '
        • '; + task_count++; + break; + } + } + + $('.task_count').text(task_count); + + get_msg_data(1, function (d) { + var msg_count = d.page.match(/\'Pcount\'>.+<\/span>/)[0].replace(/[^0-9]/gi, ''); + $('.msg_count').text(parseInt(msg_count)); + }); + + $('.cmdlist').html(b + c); + GetReloads(); + return f; + }); +} + +//检查登陆状态 +function check_login() { + $.post('/ajax?action=CheckLogin', {}, function (rdata) { + if (rdata === true) return; + }); +} + +//登陆跳转 +function to_login() { + layer.confirm(lan.public.login_expire, { title: lan.public.session_expire, icon: 2, closeBtn: 1, shift: 5 }, function () { + location.reload(); + }); +} +//表格头固定 +function table_fixed(name) { + var tableName = document.querySelector('#' + name); + tableName.addEventListener('scroll', scroll_handle); +} + +function scroll_handle(e) { + var scrollTop = this.scrollTop; + $(this) + .find('thead') + .css({ transform: 'translateY(' + scrollTop + 'px)', position: 'relative', 'z-index': '1' }); +} +var clipboard, interval, socket, term, ssh_login, term_box; + +var pdata_socket = { + x_http_token: document.getElementById('request_token_head').getAttribute('token'), +}; + +var Term = { + bws: null, //websocket对象 + route: '/webssh', //被访问的方法 + term: null, + term_box: null, + ssh_info: {}, + last_body: false, + last_cd: null, + config: { + cols: 0, + rows: 0, + fontSize: 12, + }, + + // 缩放尺寸 + detectZoom: (function () { + var ratio = 0, + screen = window.screen, + ua = navigator.userAgent.toLowerCase(); + if (window.devicePixelRatio !== undefined) { + ratio = window.devicePixelRatio; + } else if (~ua.indexOf('msie')) { + if (screen.deviceXDPI && screen.logicalXDPI) { + ratio = screen.deviceXDPI / screen.logicalXDPI; + } + } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) { + ratio = window.outerWidth / window.innerWidth; + } + + if (ratio) { + ratio = Math.round(ratio * 100); + } + return ratio; + })(), + //连接websocket + connect: function () { + if (!Term.bws || Term.bws.readyState == 3 || Term.bws.readyState == 2) { + //连接 + ws_url = (window.location.protocol === 'http:' ? 'ws://' : 'wss://') + window.location.host + Term.route; + + Term.bws = new WebSocket(ws_url); + + //绑定事件 + Term.bws.addEventListener('message', Term.on_message); + Term.bws.addEventListener('close', Term.on_close); + Term.bws.addEventListener('error', Term.on_error); + Term.bws.addEventListener('open', Term.on_open); + + //if (Term.ssh_info) Term.send(JSON.stringify(Term.ssh_info)) + } + }, + //连接服务器成功 + on_open: function (ws_event) { + var http_token = $('#request_token_head').attr('token'); + Term.send(JSON.stringify({ 'x-http-token': http_token })); + if (JSON.stringify(Term.ssh_info) !== '{}') Term.send(JSON.stringify(Term.ssh_info)); + // Term.term.FitAddon.fit(); + // Term.resize(); + // var f_path = $("#fileInputPath").val(); + var f_path = $('#fileInputPath').attr('data-path'); + if (f_path) { + Term.last_cd = 'cd ' + f_path; + Term.send(Term.last_cd + '\n'); + } + }, + + //服务器消息事件 + // on_message: function(ws_event) { + // result = ws_event.data; + // if (result === "\r'Server connection failed'!\r" || result === "\rWrong user name or password!\r") { + // show_ssh_login(result); + // Term.close(); + // return; + // } + // Term.term.write(result); + + // if (result == '\r\n登出\r\n' || result == '登出\r\n' || result == '\r\nlogout\r\n' || result == 'logout\r\n') { + // setTimeout(function() { + // layer.close(Term.term_box); + // }, 500); + // Term.close(); + // Term.bws = null; + // } + // }, + on_message: function (ws_event) { + result = ws_event.data; + if ((result.indexOf('@127.0.0.1:') != -1 || result.indexOf('@localhost:') != -1) && result.indexOf('Authentication failed') != -1) { + Term.term.write(result); + Term.localhost_login_form(result); + Term.close(); + return; + } + if (Term.last_cd) { + if (result.indexOf(Term.last_cd) != -1 && result.length - Term.last_cd.length < 3) { + Term.last_cd = null; + return; + } + } + if (result === '\rServer connection failed!\r' || result == '\rWrong user name or password!\r') { + Term.close(); + return; + } + if (result.length > 1 && Term.last_body === false) { + Term.last_body = true; + } + Term.term.write(result); + if (result == '\r\n登出\r\n' || result == '\r\n注销\r\n' || result == '注销\r\n' || result == '登出\r\n' || result == '\r\nlogout\r\n' || result == 'logout\r\n') { + setTimeout(function () { + layer.close(Term.term_box); + Term.term.dispose(); + }, 500); + Term.close(); + Term.bws = null; + } + }, + //websocket关闭事件 + on_close: function (ws_event) { + Term.bws = null; + }, + + //websocket错误事件 + // on_error: function(ws_event) { + // if(ws_event.target.readyState === 3){ + // var msg = 'Error: unable to create websocket connection, please close 【Developer mode】 on the settings page'; + // layer.msg(msg,{time:5000}) + // if(Term.state === 3) return + // Term.term.write(msg) + // Term.state = 3; + // }else{ + // console.log(ws_event) + // } + // }, + on_error: function (ws_event) { + if (ws_event.target.readyState === 3) { + if (Term.state === 3) return; + Term.term.write(msg); + Term.state = 3; + } else { + console.log(ws_event); + } + }, + + //关闭连接 + close: function () { + if (Term.bws) { + Term.bws.close(); + } + }, + + resize: function () { + setTimeout(function () { + $('#term').height($('.term_box_all .layui-layer-content').height() - 18); + Term.term.FitAddon.fit(); + Term.send(JSON.stringify({ resize: 1, rows: Term.term.rows, cols: Term.term.cols })); + Term.term.focus(); + }, 100); + }, + // resize: function() { + // var m_width = 100; + // var m_height = 34; + // Term.term.resize(m_width, m_height); + // Term.term.scrollToBottom(); + // Term.term.focus(); + // Term.send('new_terminal'); + // }, + + //发送数据 + //@param event 唯一事件名称 + //@param data 发送的数据 + //@param collback 服务器返回结果时回调的函数,运行完后将被回收 + send: function (data, num) { + //如果没有连接,则尝试连接服务器 + if (!Term.bws || Term.bws.readyState == 3 || Term.bws.readyState == 2) { + Term.connect(); + } + + //判断当前连接状态,如果!=1,则100ms后尝试重新发送 + if (Term.bws.readyState === 1) { + Term.bws.send(data); + } else { + if (Term.state === 3) return; + if (!num) num = 0; + if (num < 5) { + num++; + setTimeout(function () { + Term.send(data, num++); + }, 100); + } + } + }, + // run: function (ssh_info) { + // var termCols = 100; + // var termRows = 34; + // var loadT = layer.msg('It is loading the files required by the terminal. Please wait...', { icon: 16, time: 0, shade: 0.3 }); + // loadScript([ + // "/static/build/xterm.min.js?v=1721298337096", + // "/static/build/addons/attach/attach.min.js?v=1721298337096", + // "/static/build/addons/fit/fit.min.js?v=1721298337096", + // "/static/build/addons/fullscreen/fullscreen.min.js?v=1721298337096", + // "/static/build/addons/search/search.min.js?v=1721298337096", + // "/static/build/addons/winptyCompat/winptyCompat.js?v=1721298337096" + // ], function () { + // layer.close(loadT); + // Term.term = new Terminal({ cols: termCols, rows: termRows, screenKeys: true, useStyle: true }); + // Term.term.setOption('cursorBlink', true); + // Term.term_box = layer.open({ + // type: 1, + // title: lan.public.terminal, + // area: ['920px', '630px'], + // closeBtn: 2, + // shadeClose: false, + // content: '\ + // \ + // [' + lan.public.set + ']\ + //
          ', + // cancel: function () { + // Term.term.destroy(); + // }, + // success: function () { + // Term.term.open(document.getElementById('term')); + // Term.resize(); + // } + // }); + // Term.term.on('data', function (data) { + // try { + // Term.bws.send(data) + // } catch (e) { + // Term.term.write('\r\nThe connection is lost and you are trying to reconnect!\r\n') + // Term.connect() + // } + // }); + // if (ssh_info) Term.ssh_info = ssh_info + // Term.connect(); + // }) + + // }, + run: function (ssh_info) { + // if($("#panel_debug").attr("data") == 'True') { + // layer.msg('Error: unable to create websocket connection, please close 【Developer mode】 on the settings page!',{icon:2,time:5000}); + // return; + // } + var loadT = layer.msg('It is loading the files required by the terminal. Please wait...', { icon: 16, time: 0, shade: 0.3 }); + loadScript(['/static/js/xterm.js?v=1721298337096'], function () { + layer.close(loadT); + Term.term = new Terminal({ + rendererType: 'canvas', + cols: 100, + rows: 31, + fontSize: 15, + screenKeys: true, + useStyle: true, + }); + Term.term.setOption('cursorBlink', true); + Term.last_body = false; + Term.term_box = layer.open({ + type: 1, + title: lan.public.terminal, + area: ['925px', '630px'], + closeBtn: 2, + shadeClose: false, + skin: 'term_box_all', + content: + '\ +
          ', + cancel: function (index, lay) { + bt.confirm( + { + msg: '
          Closing the SSH session, the command in progress in the current command line session may be aborted. Continute?
          ', + title: 'Cofirm to close the SSH session?', + }, + function (ix) { + Term.term.dispose(); + layer.close(index); + layer.close(ix); + Term.close(); + } + ); + return false; + }, + success: function () { + $('.term_box_all').css('background-color', '#000'); + Term.term.open(document.getElementById('term')); + Term.term.FitAddon = new FitAddon.FitAddon(); + Term.term.loadAddon(Term.term.FitAddon); + Term.term.WebLinksAddon = new WebLinksAddon.WebLinksAddon(); + Term.term.loadAddon(Term.term.WebLinksAddon); + Term.term.focus(); + }, + }); + Term.term.onData(function (data) { + try { + Term.bws.send(data); + } catch (e) { + Term.term.write('\r\nThe connection is lost and you are trying to reconnect!\r\n'); + Term.connect(); + } + }); + if (ssh_info) Term.ssh_info = ssh_info; + Term.connect(); + }); + }, + reset_login: function () { + var ssh_info = { + data: JSON.stringify({ + host: $("input[name='host']").val(), + port: $("input[name='port']").val(), + username: $("input[name='username']").val(), + password: $("input[name='password']").val(), + }), + }; + $.post('/term_open', ssh_info, function (rdata) { + if (rdata.status === false) { + layer.msg(rdata.msg); + return; + } + layer.closeAll(); + Term.connect(); + Term.term.scrollToBottom(); + Term.term.focus(); + }); + }, + localhost_login_form: function (result) { + var template = + '
          Login failed, please fill the local server information!
          \ +
          \ + Server IP\ +
          \ + \ + \ +
          \ +
          \ +
          \ + SSH account\ +
          \ + \ +
          \ +
          \ +
          \ + Verification\ +
          \ +
          \ + \ + \ +
          \ +
          \ +
          \ +
          \ + Password\ +
          \ + \ +
          \ +
          \ +
          '; + $('.term-box').after(template); + $('.auth_type_checkbox').click(function () { + var index = $(this).index(); + $(this).addClass('btn-success').removeClass('btn-default').siblings().removeClass('btn-success').addClass('btn-default'); + switch (index) { + case 0: + $('.c_password_view').addClass('show').removeClass('hidden'); + $('.c_pkey_view').addClass('hidden').removeClass('show').find('input').val(''); + break; + case 1: + $('.c_password_view').addClass('hidden').removeClass('show').find('input').val(''); + $('.c_pkey_view').addClass('show').removeClass('hidden'); + break; + } + }); + $('.localhost-form-view > button').click(function () { + var form = {}; + $('.localhost-form-view input,.localhost-form-view textarea').each(function (index, el) { + var name = $(this).attr('name'), + value = $(this).val(); + form[name] = value; + switch (name) { + case 'port': + if (!bt.check_port(value)) { + bt.msg({ status: false, msg: 'Server port format error!' }); + return false; + } + break; + case 'username': + if (value == '') { + bt.msg({ status: false, msg: 'Server user name cannot be empty!' }); + return false; + } + break; + case 'password': + if (value == '' && $('.c_password_view').hasClass('show')) { + bt.msg({ status: false, msg: 'Server password cannot be empty!' }); + return false; + } + break; + case 'pkey': + if (value == '' && $('.c_pkey_view').hasClass('show')) { + bt.msg({ status: false, msg: 'The server key cannot be empty!' }); + return false; + } + break; + } + }); + form.ps = 'Local server'; + + if (result) { + if (result.indexOf('@127.0.0.1') != -1) { + var user = result.split('@')[0].split(',')[1]; + var port = result.split('1:')[1]; + $("input[name='username']").val(user); + $("input[name='port']").val(port); + } + } + var loadT = bt.load('Adding server information, please wait...'); + bt.send('create_host', 'xterm/create_host', form, function (res) { + loadT.close(); + bt.msg(res); + if (res.status) { + bt.msg({ status: true, msg: 'Login successful!' }); + $('.layui-layer-shade').remove(); + $('.term_box_all').remove(); + Term.term.dispose(); + Term.close(); + web_shell(); + } + }); + }); + $('.localhost-form-view [name="password"]') + .keyup(function (e) { + if (e.keyCode == 13) { + $('.localhost-form-view > button').click(); + } + }) + .focus(); + }, +}; + +function web_shell() { + Term.run(); +} + +socket = { + emit: function (data, data2) { + if (data === 'webssh') { + data = data2; + } + if (typeof data === 'object') { + return; + } + Term.send(data); + }, +}; + +function show_ssh_login(is_config) { + if ($("input[name='ssh_user']").attr('autocomplete')) return; + var s_body = + '
          \ + \ +
          IP
          \ +
          Port
          \ +
          Username
          \ +
          Method
          \ +
          Password
          \ + \ +
          \ +

          Only support login to this server

          \ +
          '; + ssh_login = layer.open({ + type: 1, + title: is_config ? 'Please fill in the SSH connection configuration' : 'Please enter the SSH login account and password', + area: '500px', + closeBtn: 0, + shadeClose: false, + content: s_body, + }); + + setTimeout(function removeReadonly() { + $("input[name='ssh_user']").removeAttr('readonly'); + $("input[name='ssh_passwd']").removeAttr('readonly'); + $("input[name='ssh_passwd']").focus(); + + $("input[name='ssh_passwd']").keydown(function (e) { + if (e.keyCode == 13) { + $('.ssh-login').click(); + } + }); + }, 500); +} + +function pass_check() { + $('#pass_check').attr('class', 'ssh_check_s2'); + $('#rsa_check').attr('class', 'ssh_check_s1'); + $('.ssh_pkey').hide(); + $('.ssh_passwd').show(); +} + +function rsa_check() { + $('#pass_check').attr('class', 'ssh_check_s1'); + $('#rsa_check').attr('class', 'ssh_check_s2'); + $('.ssh_pkey').show(); + $('.ssh_passwd').hide(); +} + +function send_ssh_info() { + pdata = { + host: $("input[name='ssh_host']").val(), + port: Number($("input[name='ssh_port']").val()), + password: $("input[name='ssh_passwd']").val(), + username: $("input[name='ssh_user']").val(), + pkey: $("textarea[name='ssh_pkey']").val(), + }; + if (pdata['host'] !== '127.0.0.1' && pdata['host'] !== 'localhost') { + layer.msg('Connection address can only be [ 127.0.0.1 or localhost ]'); + $("input[name='ssh_host']").focus(); + return; + } + if (pdata['port'] < 1 || pdata['port'] > 65535) { + layer.msg('Port range is incorrect [1-65535]'); + $("input[name='ssh_port']").focus(); + return; + } + if (!pdata['username']) { + layer.msg('Username can not be empty!'); + $("input[name='ssh_user']").focus(); + return; + } + + if ($('#rsa_check').attr('class') === 'ssh_check_s2') { + pdata['c_type'] = 'True'; + if (!pdata['pkey']) { + layer.msg('Private key cannot be empty!'); + $("input[name='ssh_pkey']").focus(); + return; + } + } else { + if (!pdata['password']) { + layer.msg('Password can not be blank!'); + $("input[name='ssh_passwd']").focus(); + return; + } + } + if ($('#ssh_is_save').prop('checked')) { + pdata['is_save'] = '1'; + } + + var loadT = layer.msg('Trying to log in to SSH...', { icon: 16, time: 0, shade: 0.3 }); + $.post('/term_open', { data: JSON.stringify(pdata) }, function () { + layer.close(loadT); + Term.send('reset_connect'); + layer.close(ssh_login); + Term.term.focus(); + }); +} + +acme = { + speed_msg: "
          [MSG]
          ", + loadT: null, + //获取订单列表 + get_orders: function (callback) { + acme.request( + 'get_orders', + {}, + function (rdata) { + callback(rdata); + }, + 'Getting order list...' + ); + }, + //取指定订单 + get_find: function (index, callback) { + acme.request( + 'get_order_find', + { index: index }, + function (rdata) { + callback(rdata); + }, + 'Getting order information...' + ); + }, + + //下载指定证书包 + download_cert: function (index, callback) { + acme.request( + 'update_zip', + { index: index }, + function (rdata) { + if (!rdata.status) { + bt.msg(rdata); + return; + } + if (callback) { + callback(rdata); + } else { + window.location.href = '/download?filename=' + rdata.msg; + } + }, + 'Preparing to download..' + ); + }, + + //删除订单 + remove: function (index, callback) { + acme.request('remove_order', { index: index }, function (rdata) { + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + + //吊销证书 + revoke: function (index, callback) { + acme.request( + 'revoke_order', + { index: index }, + function (rdata) { + bt.msg(rdata); + if (callback) callback(rdata); + }, + 'Revoking certificate...' + ); + }, + + //验证域名(手动DNS申请) + auth_domain: function (index, callback) { + acme.show_speed_window('Verifying DNS...', function () { + acme.request( + 'apply_dns_auth', + { index: index }, + function (rdata) { + callback(rdata); + }, + false + ); + }); + }, + + //取证书基本信息 + get_cert_init: function (pem_file, siteName, callback) { + acme.request( + 'get_cert_init_api', + { pem_file: pem_file, siteName: siteName }, + function (cert_init) { + callback(cert_init); + }, + 'Getting certificate information...' + ); + }, + + //显示进度 + show_speed: function () { + bt.send( + 'get_lines', + 'ajax/get_lines', + { + num: 10, + filename: '/www/server/panel/logs/letsencrypt.log', + }, + function (rdata) { + if ($('#create_lst').text() === '') return; + if (rdata.status === true) { + $('#create_lst').text(rdata.msg); + $('#create_lst').scrollTop($('#create_lst')[0].scrollHeight); + } + setTimeout(function () { + acme.show_speed(); + }, 1000); + } + ); + }, + + //显示进度窗口 + show_speed_window: function (msg, callback) { + acme.loadT = layer.open({ + title: false, + type: 1, + closeBtn: 0, + shade: 0.3, + area: '500px', + offset: '30%', + content: acme.speed_msg.replace('[MSG]', msg), + success: function (layers, index) { + setTimeout(function () { + acme.show_speed(); + }, 1000); + if (callback) callback(); + }, + }); + }, + + //一键申请 + //domain 域名列表 [] + //auth_type 验证类型 model/http + //auth_to 验证路径 网站根目录或dnsapi + //auto_wildcard 是否自动组合通配符 1.是 0.否 默认0 + apply_cert: function (domains, auth_type, auth_to, auto_wildcard, callback) { + acme.show_speed_window('Applying for a certificate...', function () { + if (auto_wildcard === undefined) auto_wildcard = '0'; + pdata = { + domains: JSON.stringify(domains), + auth_type: auth_type, + auth_to: auth_to, + auto_wildcard: auto_wildcard, + }; + + if (acme.id) pdata['id'] = acme.id; + if (acme.siteName) pdata['siteName'] = acme.siteName; + acme.request( + 'apply_cert_api', + pdata, + function (rdata) { + callback(rdata); + }, + false + ); + }); + }, + + //续签证书 + renew: function (index, callback) { + acme.show_speed_window('Renewing certificate...', function () { + acme.request( + 'renew_cert', + { index: index }, + function (rdata) { + callback(rdata); + }, + false + ); + }); + }, + + //获取用户信息 + get_account_info: function (callback) { + acme.request('get_account_info', {}, function (rdata) { + callback(rdata); + }); + }, + + //设置用户信息 + set_account_info: function (account, callback) { + acme.request('set_account_info', account, function (rdata) { + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + + //发送到请求 + request: function (action, pdata, callback, msg) { + if (msg == undefined) msg = 'Processing, please wait...'; + if (msg) { + var loadT = layer.msg(msg, { icon: 16, time: 0, shade: 0.3 }); + } + $.post('/acme?action=' + action, pdata, function (res) { + if (msg) layer.close(loadT); + if (callback) callback(res); + }); + }, +}; + +/** 消息通道 **/ +function MessageChannelSettings() { + MessageChannel.get_channel_settings(function (rdata) { + layer.open({ + type: 1, + area: '600px', + title: 'Setting up notification', + skin: 'layer-channel-auth', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + '
          \ +
          \ +
          \ +

          Email

          \ +

          Telegram

          \ +
          \ +
          \ +
          \ +
          \ +
          \ +
          \ + \ + \ +
          \ +
          \ +
          \ +
          EmailOperating
          \ +
          \ +
          \ +
          \ +
          \ +
          \ + \ +
          \ +
          \ +
          \ +
          ', + success: function () { + $('.addTelegram').click(function () { + var _id = $('[name=telegram_id]').val(), + _token = $('[name=telegram_token]').val(); + if (_id == '' || _token == '') return layer.msg('input box cannot be empty!'); + var loadT = layer.msg('The notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/config?action=set_tg_bot', { bot_token: _token, my_id: _id }, function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + $('.delTelegram').click(function () { + var loadTs = layer.msg('Deleting notification, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/config?action=del_tg_info', function (rdata) { + layer.close(loadTs); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + $('[name=telegram_id]').val(''); + $('[name=telegram_token]').val(''); + $('.delTelegram').hide(); + } + }); + }); + }, + }); + $('.bt-w-menu p').click(function () { + var index = $(this).index(); + $(this).addClass('bgw').siblings().removeClass('bgw'); + $('.conter_box').eq(index).show().siblings().hide(); + }); + MessageChannel.get_receive_list(); + }); +} +var MessageChannel = { + //获取推送设置 + get_channel_settings: function (callback) { + var loadT = layer.msg('Getting profile, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/config?action=get_settings2', function (rdata) { + layer.close(loadT); + if (callback) callback(rdata); + }); + }, + // 获取收件者列表 + get_receive_list: function () { + $.post('/config?action=get_settings2', function (rdata) { + var _html = '', + _list = rdata.user_mail.mail_list; + if (_list.length > 0) { + for (var i = 0; i < _list.length; i++) { + _html += + '\ + ' + + _list[i] + + '\ + Del\ + '; + } + } else { + _html = 'No Data'; + } + $('#receive_table').html(_html); + }); + }, + // 添加收件者 + add_receive_info: function () { + var _this = this; + layer.open({ + type: 1, + area: '400px', + title: 'Add recipient email', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + '
          \ +
          \ + Recipient mailbox\ +
          \ + \ +
          \ +
          \ +
          \ + \ + \ +
          \ +
          ', + success: function (layers, index) { + $('.CreaterReceive').click(function () { + var _receive = $('input[name=creater_email_value]').val(); + if (_receive != '') { + var loadT = layer.msg('Please wait while creating recipient list...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + layer.close(index); + $.post('/config?action=add_mail_address', { email: _receive }, function (rdata) { + layer.close(loadT); + // 刷新收件列表 + _this.get_receive_list(); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + } else { + layer.msg('Recipient mailbox cannot be empty!!', { icon: 2 }); + } + }); + + $('.smtp_closeBtn').click(function () { + layer.close(index); + }); + }, + }); + }, + // 删除收件者 + del_email: function (mail) { + var loadT = layer.msg('Deleting[' + mail + '],please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }), + _this = this; + $.post('/config?action=del_mail_list', { email: mail }, function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + _this.get_receive_list(); + }); + }, + // 设置发送者邮箱信息 + sender_info_edit: function () { + var loadT = layer.msg('Getting profile, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/config?action=get_settings2', function (rdata) { + layer.close(loadT); + var qq_mail = rdata.user_mail.info.msg.qq_mail ? rdata.user_mail.info.msg.qq_mail : '', + qq_stmp_pwd = rdata.user_mail.info.msg.qq_stmp_pwd ? rdata.user_mail.info.msg.qq_stmp_pwd : '', + hosts = rdata.user_mail.info.msg.hosts ? rdata.user_mail.info.msg.hosts : '', + port = rdata.user_mail.info.msg.port ? rdata.user_mail.info.msg.port : '', + is_custom = $.inArray(port, ['25', '465', '587', '']) != -1; //是否自定义 + layer.open({ + type: 1, + area: '460px', + title: 'Set sender email information', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + '
          \ +
          \ + Sender email\ +
          \ + \ +
          \ +
          \ +
          \ + SMTP password\ +
          \ + \ +
          \ +
          \ +
          \ + SMTP server\ +
          \ + \ +
          \ +
          \ +
          \ + SMTP port\ +
          \ + \ + \ +
          \ +
          \ +
            \ +
          • 465 port is recommended, the protocol is SSL/TLS
          • \ +
          • Port 25 is SMTP protocol, port 587 is STARTTLS protocol
          • \ +
          \ +
          \ + ' + + (qq_mail != '' ? '' : '') + + '\ + \ +
          \ +
          ', + success: function (layers, index) { + var _option = ''; + if (is_custom) { + if (port == '465' || port == '') { + _option = ''; + } else if (port == '25') { + _option = ''; + } else { + _option = ''; + } + } else { + _option = ''; + } + $('#port_select').html(_option); + $('#port_select').change(function (e) { + if (e.target.value == 'other') { + $('#port_select').css('width', '100px'); + $('input[name=channel_email_port]').css('display', 'inline-block'); + } else { + $('#port_select').css('width', '300px'); + $('input[name=channel_email_port]').css('display', 'none'); + } + }); + $('.SetChannelEmail').click(function () { + var _email = $('input[name=channel_email_value]').val(); + var _passW = $('input[name=channel_email_password]').val(); + var _server = $('input[name=channel_email_server]').val(), + _port = ''; + if ($('#port_select').val() == 'other') { + _port = $('input[name=channel_email_port]').val(); + } else { + _port = $('#port_select').val(); + } + if (!_email) return layer.msg('Email address cannot be empty!', { icon: 2 }); + if (!_passW) return layer.msg('STMP password cannot be empty!', { icon: 2 }); + if (!_server) return layer.msg('STMP server address cannot be empty!', { icon: 2 }); + if (!_port) return layer.msg('STMP server port cannot be empty!', { icon: 2 }); + + var loadT = layer.msg('The notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/config?action=user_mail_send', { email: _email, stmp_pwd: _passW, hosts: _server, port: _port }, function (rdata) { + layer.close(loadT); + if (rdata.status) { + layer.close(index); + MessageChannel.get_channel_settings(); + } + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }); + $('.smtp_closeBtn').click(function () { + layer.close(index); + }); + $('.set_empty').click(function () { + var loadTs = layer.msg('notification, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] }); + $.post('/config?action=set_empty', { type: 'mail' }, function (rdata) { + layer.close(loadTs); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + layer.close(index); + } + }); + }); + }, + }); + }); + }, +}; +/** 消息通道 end**/ +var product_recommend = { + data: null, + /** + * @description 初始化 + */ + init: function (callback) { + var _this = this; + if (location.pathname.indexOf('bind') > -1) return; + this.get_product_type(function (rdata) { + _this.data = rdata; + if (callback) callback(rdata); + }); + }, + /** + * @description 获取推荐类型 + * @param {object} type 参数{type:类型} + */ + get_recommend_type: function (type) { + var config = null, + pathname = location.pathname.replace('/', '') || 'home'; + for (var i = 0; i < this.data.length; i++) { + var item = this.data[i]; + if (item.type == type && item.show) config = item; + } + return config; + }, + + /** + * @description 或指定版本事件 + * @param {} name + */ + get_version_event: function (item, param) { + var pay_status = this.get_pay_status(); + bt.soft.get_soft_find(item.name, function (res) { + if ((res.type === 12 && pay_status.is_pay && pay_status.advanced !== 'ltd') || !pay_status.is_pay) { + product_recommend.recommend_product_view(item); + } else if (!res.setup) { + bt.soft.install(item.name); + } else { + bt.plugin.get_plugin_byhtml(item.name, function (html) { + if (typeof html === 'string') { + layer.open({ + type: 1, + shade: 0, + skin: 'hide', + content: html, + success: function () { + var is_event = false; + for (var i = 0; i < item.eventList.length; i++) { + var data = item.eventList[i]; + var oldVersion = data.version.replace('.', ''), + newVersion = res.version.replace('.', ''); + if (newVersion <= oldVersion) { + is_event = true; + setTimeout(function () { + new Function(data.event.replace('$siteName', param))(); + }, 100); + break; + } + } + if (!is_event) new Function(item.eventList[item.eventList.length - 1].event.replace('$siteName', param))(); + }, + }); + } + }); + } + }); + }, + /** + * @description 获取支付状态 + */ + get_pay_status: function () { + var pro_end = parseInt(bt.get_cookie('pro_end') || -1); + var ltd_end = parseInt(bt.get_cookie('ltd_end') || -1); + var is_pay = pro_end > -1 || ltd_end > -1; // 是否购买付费版本 + var advanced = 'pro'; // 已购买,专业版优先显示 + if (pro_end === -2 || pro_end > -1) advanced = 'pro'; + if (ltd_end === -2 || ltd_end > -1) advanced = 'ltd'; + var end_time = advanced === 'ltd' ? ltd_end : pro_end; // 到期时间 + return { advanced: advanced, is_pay: is_pay, end_time: end_time }; + }, + + pay_product_sign: function (type, source) { + switch (type) { + case 'pro': + bt.soft['updata_' + type](source); + break; + case 'ltd': + bt.soft['updata_' + type](false, source); + break; + } + }, + /** + * @description 获取项目类型 + * @param {Function} callback 回调函数 + */ + get_product_type: function (callback) { + bt.send('get_pay_type', 'ajax/get_pay_type', {}, function (rdata) { + bt.set_storage('session', 'get_pay_type', JSON.stringify(rdata)); + if (callback) callback(rdata); + }); + }, + /** + * @description 推荐购买产品 + * @param {Object} pay_id 购买的入口id + */ + recommend_product_view: function (config) { + var name = config.name.split('_')[0]; + var status = this.get_pay_status(); + console.log(status); + bt.open({ + title: false, + area: '650px', + btn: false, + content: + '
          \ +
          \ +
          \ +
          ' + + config.pluginName + + '
          \ +
          ' + + config.ps + + '
          \ +
          \ + 产品预览\ +
          \ +
          \ +
          ', + success: function () { + // 产品预览 + $('.product_view img').click(function () { + layer.open({ + type: 1, + title: '查看图片', + area: ['650px', '450px'], + closeBtn: 2, + btn: false, + content: '', + }); + }); + // 立即购买 + $('.buyNow').click(function () { + bt.set_cookie('pay_source', config.pay); + bt.soft['updata_' + status.advanced](); + }); + }, + }); + }, +}; + +var rsa = { + publicKey: null, + /** + * @name 使用公钥加密 + * @param {string} text + * @returns string + */ + encrypt_public: function (text) { + this.publicKey = document.querySelector('.public_key').attributes.data.value; + if (this.publicKey.length < 10) return text; + var encrypt = new JSEncrypt(); + encrypt.setPublicKey(this.publicKey); + return encrypt.encrypt(text); + }, + /** + * @name 使用公钥解密 + * @param {string} text + * @returns string + */ + decrypt_public: function (text) { + this.publicKey = document.querySelector('.public_key').attributes.data.value; + if (this.publicKey.length < 10) return null; + var decrypt = new JSEncrypt(); + decrypt.setPublicKey(this.publicKey); + return decrypt.decryptp(text); + }, +}; + +/** + * @description 渲染邮箱配置视图 + */ +function renderMailConfigView(data) { + layer.open({ + type: 1, + title: 'Set sender email information', + area: ['470px', '376px'], + btn: [lan.public.save, lan.public.cancel], + skin: 'alert-send-view', + content: + '
          \ +
          \ + Sender email\ +
          \ + \ +
          \ +
          \ +
          \ + SMTP password\ +
          \ + \ +
          \ +
          \ +
          \ + SMTP server\ +
          \ + \ +
          \ +
          \ +
          \ + SMTP port\ +
          \ + \ +
          \ +
          \ +
            \ +
          • 465 port is recommended, the protocol is SSL/TLS
          • \ +
          • Port 25 is SMTP protocol, port 587 is STARTTLS protocol
          • \ +
          \ +
          ', + success: function () { + if (!$.isEmptyObject(data) && !$.isEmptyObject(data.data.send)) { + var send = data.data.send, + mail_ = send.qq_mail || '', + stmp_pwd_ = send.qq_stmp_pwd || '', + hosts_ = send.hosts || '', + port_ = send.port || ''; + + $('input[name=sender_mail_value]').val(mail_); + $('input[name=sender_mail_password]').val(stmp_pwd_); + $('input[name=sender_mail_server]').val(hosts_); + $('input[name=sender_mail_port]').val(port_); + } else { + $('input[name=sender_mail_port]').val('465'); + } + }, + yes: function (indexs) { + var _email = $('input[name=sender_mail_value]').val(), + _passW = $('input[name=sender_mail_password]').val(), + _server = $('input[name=sender_mail_server]').val(), + _port = $('input[name=sender_mail_port]').val(); + + if (_email == '') return layer.msg('Email address cannot be empty!', { icon: 2 }); + if (_passW == '') return layer.msg('STMP password cannot be empty!', { icon: 2 }); + if (_server == '') return layer.msg('STMP server address cannot be empty!', { icon: 2 }); + if (_port == '') return layer.msg('STMP server port cannot be empty!', { icon: 2 }); + + if (!data.setup) { + bt_tools.send( + { url: '/config?action=install_msg_module&name=' + data.name, data: {} }, + function (res) { + if (res.status) { + bt_tools.send( + { url: '/config?action=set_msg_config&name=mail', data: { send: 1, qq_mail: _email, qq_stmp_pwd: _passW, hosts: _server, port: _port } }, + function (configM) { + if (configM.status) { + layer.close(indexs); + layer.msg(configM.msg, { + icon: configM.status ? 1 : 2, + }); + if ($('.alert-view-box').length >= 0) $('.alert-view-box .tab-nav-border span:eq(1)').click(); + } + }, + 'Setting email Settings' + ); + } else { + layer.msg(res.msg, { icon: 2 }); + } + }, + 'Creating ' + data.title + ' module' + ); + } else { + bt_tools.send( + { + url: '/config?action=set_msg_config&name=mail', + data: { + send: 1, + qq_mail: _email, + qq_stmp_pwd: _passW, + hosts: _server, + port: _port, + }, + }, + function (configM) { + if (configM.status) { + layer.close(indexs); + layer.msg(configM.msg, { + icon: configM.status ? 1 : 2, + }); + } + }, + 'Setting email Settings' + ); + } + }, + }); +} + +/** + * @description 渲染url通道方式视图 + */ +function renderAlertUrlTypeChannelView(data) { + var isEmpty = $.isEmptyObject(data.data); + layer.open({ + type: 1, + title: data['title'] + ' robot configuration', + area: ['480px', '345px'], + btn: [lan.public.save, lan.public.cancel], + skin: 'alert-send-view', + content: + '
          \ +
          \ + Name\ +
          \ + \ +
          \ +
          \ +
          \ + URL\ +
          \ + \ +
          \ + \ +
          \ +
          ', + success: function () { + if (!$.isEmptyObject(data.data)) { + var url = data['data'][data.name + '_url'] || ''; + $('textarea[name=channel_url_value]').val(url); + } + }, + yes: function (indexs) { + var _index = $('.alert-view-box span.on').index(); + var _url = $('textarea[name=channel_url_value]').val(), + _name = $('input[name=chatName]').val(); + if (_name == '') return layer.msg('Please enter the robot name or remarks', { icon: 2 }); + if (_url == '') return layer.msg('Please enter the robot url', { icon: 2 }); + if (!data.setup) { + bt_tools.send( + { url: '/config?action=install_msg_module&name=' + data.name, data: {} }, + function (res) { + if (res.status) { + setTimeout(function () { + bt_tools.send( + { + url: '/config?action=set_msg_config&name=' + data.name, + data: { + url: _url, + title: _name, + atall: 'True', + }, + }, + function (rdata) { + layer.close(indexs); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + if ($('.alert-view-box').length >= 0) { + $('.alert-view-box .tab-nav-border span:eq(' + _index + ')').click(); + } + }, + 'Setting ' + data.title + ' configuration' + ); + }, 100); + } else { + layer.msg(res.msg, { icon: 2 }); + } + }, + 'Creating ' + data.title + ' module' + ); + } else { + bt_tools.send( + { + url: '/config?action=set_msg_config&name=' + data.name, + data: { + url: _url, + title: _name, + atall: 'True', + }, + }, + function (rdata) { + layer.close(indexs); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + if ($('.alert-view-box').length >= 0) { + $('.alert-view-box .tab-nav-border span:eq(' + _index + ')').click(); + } + }, + 'Setting ' + data.title + ' module' + ); + } + }, + }); +} + +function renderTelegramConfigView(data) { + layer.open({ + type: 1, + title: 'Telegram configuration', + area: ['460px', '320px'], + btn: [lan.public.save, lan.public.cancel], + skin: 'alert-send-view', + content: + '
          \ +
          \ + ID\ +
          \ + \ +
          \ +
          \ +
          \ + TOKEN\ +
          \ + \ +
          \ +
            \ +
          • ID: Your telegram user ID
          • \ +
          • Token: Your telegram bot token
          • \ +
          • e.g: [ 12345677:AAAAAAAAA_a0VUo2jjr__CCCCDDD ] Help
          • \ +
          \ +
          \ +
          ', + success: function () { + var res = data.data; + if (res) { + $('[name="telegram_id"]').val(res.my_id); + $('[name="telegram_token"]').val(res.bot_token); + } + }, + yes: function (indexs) { + var id = $('input[name=telegram_id]').val(); + var token = $('input[name=telegram_token]').val(); + var _index = $('.alert-view-box span.on').index(); + + if (id == '') return layer.msg('Please enter Telegram ID!', { icon: 2 }); + if (token == '') return layer.msg('Please enter Telegram token', { icon: 2 }); + + function saveConfig() { + bt_tools.send( + { + url: '/config?action=set_msg_config&name=' + data.name, + data: { + my_id: id, + bot_token: token, + }, + }, + function (rdata) { + layer.close(indexs); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + if ($('.alert-view-box').length >= 0) { + $('.alert-view-box .tab-nav-border span:eq(' + _index + ')').click(); + } + }, + 'Setting ' + data.title + ' module' + ); + } + + if (!data.setup) { + bt_tools.send( + { + url: '/config?action=install_msg_module&name=' + data.name, + data: {}, + }, + function (res) { + if (res.status) { + saveConfig(); + } else { + layer.msg(res.msg, { icon: 2 }); + } + }, + 'Creating ' + data.title + ' module' + ); + } else { + saveConfig(); + } + }, + }); +} + +// true: 消息推送 false: 消息通道 +var ConfigIsPush = false; +// 消息推送弹框 +var ConfigIndex = -1; + +// 打开消息通道/消息推送 +function open_three_channel_auth(stype) { + var _title = 'Set Notification'; + var _area = '650px'; + var isPush = false; + var assign = ''; + + if (stype === 'MsgPush') { + // 类型为消息推送 + _title = 'Set message push'; + _area = ['900px', '603px']; + isPush = true; + } else if (typeof stype != 'undefined' && stype) { + // 指定选择消息通道的某个菜单 + assign = stype; + } + + ConfigIsPush = isPush; + + ConfigIndex = layer.open({ + type: 1, + area: _area, + title: _title, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + '\ +
          \ +
          \ +
          \ +
          \ +
          \ +
          \ +
          \ +
          \ +
          ', + success: function () { + // 获取菜单配置 + getMsgConfig(assign ? assign : ''); + + // 卸载/禁用模块 + $('.alarm-view').on('click', '.btn-uninstall', function () { + uninstallMsgModuleConfig(); + }); + + // 立即更新 + $('.alarm-view').on('click', '.btn-update', function () { + installMsgModuleConfig(); + }); + }, + }); +} + +// 获取模板配置 +function getTemplateMsgConfig(item, shtml) { + $.post( + '/' + (ConfigIsPush ? 'push' : 'config') + '?action=get_module_template', + { + module_name: item.name, + }, + function (res) { + if (res.status) { + // 添加菜单内容 + $('.bt-w-main .plugin_body').html(res.msg.trim()); + // 添加底部内容 + var updateInfo = ''; + // 是否更新 + if (item.version !== item.info.version) { + updateInfo = '【' + item['title'] + '】模块存在新的版本,为了不影响使用,请更新。'; + } + // $(".bt-w-main .plugin_update").html('\ + //
          \ + //
          ' + updateInfo + '
          \ + //
          \ + //
          '); + } else { + $('.bt-w-main .plugin_body').html(shtml); + } + new Function(item.name + '.init()')(); + } + ); +} + +// 获取消息配置 +function getMsgConfig(openType) { + var _api = '/config?action=get_msg_configs'; + if (ConfigIsPush) _api = '/push?action=get_modules_list'; + + $.post(_api, function (rdata) { + var _menu = ''; + var menu_data = $('.alarm-view .bt-w-menu p.bgw').data('data'); + $('.alarm-view .bt-w-menu').html(''); + $.each(rdata, function (index, item) { + var _default = item.data && item.data.default; + var _flag = ''; + if (_default) { + _flag = ''; + } + _menu = $("

          " + item['title'] + _flag + '

          ').data('data', item); + $('.alarm-view .bt-w-menu').append(_menu); + }); + // $('.alarm-view .bt-w-menu').append('更新列表'); + $('.alarm-view .bt-w-menu p').click(function () { + $(this).addClass('bgw').siblings().removeClass('bgw'); + var _item = $(this).data('data'); + + var shtml = + ''; + if (_item['setup']) { + getTemplateMsgConfig(_item, shtml); + } else { + $('.bt-w-main .plugin_body').html(shtml); + $('.bt-w-main .plugin_update').html(''); + } + }); + if (menu_data) { + $('.men_' + menu_data['name']).click(); + } else { + if (typeof openType != 'undefined' && openType) { + $('.alarm-view .bt-w-menu p.men_' + openType).trigger('click'); + } else { + $('.alarm-view .bt-w-menu p').eq(0).trigger('click'); + } + } + }); +} + +function installMsgModuleConfig(name) { + var _api = '/config?action=install_msg_module'; + if (ConfigIsPush) _api = '/push?action=install_module'; + name = name ? '.men_' + name : ''; + var _item = $('.alarm-view .bt-w-menu p.bgw' + name).data('data'); + var spt = '安装'; + if (_item.setup) spt = '更新'; + + layer.confirm( + '是否要' + spt + '【' + _item.title + '】模块', + { + title: '安装模块', + closeBtn: 2, + icon: 0, + }, + function () { + var loadT = layer.msg('正在' + spt + _item.title + '模块中,请稍候...', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + $.post(_api + '&name=' + _item.name + '', function (res) { + getMsgConfig(); + layer.close(loadT); + layer.msg(res.msg, { + icon: res.status ? 1 : 2, + }); + }); + } + ); +} + +function uninstallMsgModuleConfig() { + var _api = '/config?action=uninstall_msg_module'; + if (ConfigIsPush) _api = '/push?action=uninstall_module'; + + var _item = $('.alarm-view .bt-w-menu p.bgw').data('data'); + + layer.confirm( + '是否确定要卸载【' + _item.title + '】模块', + { + title: '卸载模块', + closeBtn: 2, + icon: 0, + }, + function () { + var loadT = layer.msg('正在卸载' + _item.title + '模块中,请稍候...', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + $.post(_api + '&name=' + _item.name + '', function (res) { + layer.close(loadT); + getMsgConfig(); + layer.msg(res.msg, { + icon: res.status ? 1 : 2, + }); + }); + } + ); +} + +function refreshThreeChannelAuth() { + var _api = '/config?action=get_msg_configs'; + if (ConfigIsPush) _api = '/push?action=get_modules_list'; + + var loadT = layer.msg('正在更新模块列表中,请稍候...', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + layer.confirm( + '是否确定获取最新的模块列表', + { + title: '刷新列表', + closeBtn: 2, + icon: 0, + }, + function (index) { + layer.close(index); + layer.close(ConfigIndex); + $.post( + _api, + { + force: 1, + }, + function (rdata) { + layer.close(loadT); + open_three_channel_auth(ConfigIsPush ? 'MsgPush' : ''); + } + ); + } + ); +} diff --git a/BTPanel/static/vite/oldjs/public_backup.js b/BTPanel/static/vite/oldjs/public_backup.js new file mode 100644 index 00000000..cbac4d5f --- /dev/null +++ b/BTPanel/static/vite/oldjs/public_backup.js @@ -0,0 +1,10168 @@ +var bt = { + os: 'Linux', + check_ip: function ( + ip //验证ip + ) { + var reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; + return reg.test(ip); + }, + check_ips: function ( + ips //验证ip段 + ) { + var reg = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$/; + return reg.test(ip); + }, + check_url: function ( + url //验证url + ) { + var reg = /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/; + return reg.test(url); + }, + check_port: function (port) { + var reg = /^([1-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/; + return reg.test(port); + }, + check_chinese: function (str) { + var reg = /[\u4e00-\u9fa5]/; + return reg.test(str); + }, + check_domain: function ( + domain //验证域名 + ) { + var reg = /^([\w\u4e00-\u9fa5\-\*]{1,100}\.){1,4}([\w\u4e00-\u9fa5\-]{1,24}|[\w\u4e00-\u9fa5\-]{1,24}\.[\w\u4e00-\u9fa5\-]{1,24})$/; + return reg.test(bt.strim(domain)); + }, + check_img: function ( + fileName //验证是否图片 + ) { + var exts = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'tiff', 'ico']; + var check = bt.check_exts(fileName, exts); + return check; + }, + check_email: function (email) { + var reg = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/; + return reg.test(email); + }, + check_phone: function (phone) { + var reg = /^1(3|4|5|6|7|8|9)\d{9}$/; + return reg.test(phone); + }, + check_zip: function (fileName) { + var ext = fileName.split('.'); + var extName = ext[ext.length - 1].toLowerCase(); + if (extName == 'zip') return 0; + if (extName == 'rar') return 2; + if (extName == 'gz' || extName == 'tgz') return 1; + return -1; + }, + clear_cookie: function (key) { + this.set_cookie(key, '', new Date()); + }, + check_text: function (fileName) { + var exts = ['rar', 'zip', 'tar.gz', 'gz', 'iso', 'xsl', 'doc', 'xdoc', 'jpeg', 'jpg', 'png', 'gif', 'bmp', 'tiff', 'exe', 'so', '7z', 'bz']; + return bt.check_exts(fileName, exts) ? false : true; + }, + check_exts: function (fileName, exts) { + var ext = fileName.split('.'); + if (ext.length < 2) return false; + var extName = ext[ext.length - 1].toLowerCase(); + for (var i = 0; i < exts.length; i++) { + if (extName == exts[i]) return true; + } + return false; + }, + check_version: function (version, cloud_version) { + var arr1 = version.split('.'); // + var arr2 = cloud_version.split('.'); + var leng = arr1.length > arr2.length ? arr1.length : arr2.length; + while (leng - arr1.length > 0) { + arr1.push(0); + } + while (leng - arr2.length > 0) { + arr2.push(0); + } + for (var i = 0; i < leng; i++) { + if (i == leng - 1) { + if (arr1[i] != arr2[i]) return 2; //子版本匹配不上 + } else { + if (arr1[i] != arr2[i]) return -1; //版本匹配不上 + } + } + return 1; //版本正常 + }, + replace_all: function (str, old_data, new_data) { + var reg_str = '/(' + old_data + '+)/g'; + var reg = eval(reg_str); + return str.replace(reg, new_data); + }, + get_file_ext: function (fileName) { + var text = fileName.split('.'); + var n = text.length - 1; + text = text[n]; + return text; + }, + get_file_path: function (filename) { + var arr = filename.split('/'); + path = filename.replace('/' + arr[arr.length - 1], ''); + return path; + }, + get_date: function (a) { + var dd = new Date(); + dd.setTime(dd.getTime() + (a == undefined || isNaN(parseInt(a)) ? 0 : parseInt(a)) * 86400000); + var y = dd.getFullYear(); + var m = dd.getMonth() + 1; + var d = dd.getDate(); + return y + '-' + (m < 10 ? '0' + m : m) + '-' + (d < 10 ? '0' + d : d); + }, + get_form: function (select) { + var sarr = $(select).serializeArray(); + var iarr = {}; + for (var i = 0; i < sarr.length; i++) { + iarr[sarr[i].name] = sarr[i].value; + } + return iarr; + }, + ltrim: function (str, r) { + var reg_str = '/(^\\' + r + '+)/g'; + var reg = eval(reg_str); + str = str.replace(reg, ''); + return str; + }, + rtrim: function (str, r) { + var reg_str = '/(\\' + r + '+$)/g'; + var reg = eval(reg_str); + str = str.replace(reg, ''); + return str; + }, + strim: function (str) { + var reg_str = '/ /g'; + var reg = eval(reg_str); + str = str.replace(reg, ''); + return str; + }, + contains: function (str, substr) { + if (str) { + return str.indexOf(substr) >= 0; + } + return false; + }, + format_size: function ( + bytes, + is_unit, + fixed, + end_unit //字节转换,到指定单位结束 is_unit:是否显示单位 fixed:小数点位置 end_unit:结束单位 + ) { + if (bytes == undefined) return 0; + + if (is_unit == undefined) is_unit = true; + if (fixed == undefined) fixed = 2; + if (end_unit == undefined) end_unit = ''; + + if (typeof bytes == 'string') bytes = parseInt(bytes); + var unit = [' B', ' KB', ' MB', ' GB', 'TB']; + var c = 1024; + for (var i = 0; i < unit.length; i++) { + var cUnit = unit[i]; + if (end_unit) { + if (cUnit.trim() == end_unit.trim()) { + var val = i == 0 ? bytes : fixed == 0 ? bytes : bytes.toFixed(fixed); + if (is_unit) { + return val + cUnit; + } else { + val = parseFloat(val); + return val; + } + } + } else { + if (bytes < c) { + var val = i == 0 ? bytes : fixed == 0 ? bytes : bytes.toFixed(fixed); + if (is_unit) { + return val + cUnit; + } else { + val = parseFloat(val); + return val; + } + } + } + + bytes /= c; + } + }, + format_data: function (tm, format) { + if (format == undefined) format = 'yyyy/MM/dd hh:mm:ss'; + tm = tm.toString(); + if (tm.length > 10) { + tm = tm.substring(0, 10); + } + var data = new Date(parseInt(tm) * 1000); + var o = { + 'M+': data.getMonth() + 1, //month + 'd+': data.getDate(), //day + 'h+': data.getHours(), //hour + 'm+': data.getMinutes(), //minute + 's+': data.getSeconds(), //second + 'q+': Math.floor((data.getMonth() + 3) / 3), //quarter + S: data.getMilliseconds(), //millisecond + }; + if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (data.getFullYear() + '').substr(4 - RegExp.$1.length)); + for (var k in o) if (new RegExp('(' + k + ')').test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); + + return format; + }, + format_path: function (path) { + var reg = /(\\)/g; + path = path.replace(reg, '/'); + return path; + }, + get_random: function (len) { + len = len || 32; + var $chars = 'AaBbCcDdEeFfGHhiJjKkLMmNnPpRSrTsWtXwYxZyz2345678'; // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 + var maxPos = $chars.length; + var pwd = ''; + for (i = 0; i < len; i++) { + pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; + }, + refresh_pwd: function (length, obj) { + if (obj == undefined) obj = 'MyPassword'; + var _input = $('#' + obj); + if (_input.length > 0) { + _input.val(bt.get_random(length)); + } else { + $('.' + obj).val(bt.get_random(length)); + } + }, + get_random_num: function ( + min, + max //生成随机数 + ) { + var range = max - min; + var rand = Math.random(); + var num = min + Math.round(rand * range); //四舍五入 + return num; + }, + + /** + * 生成计算数字(加强计算,用于删除重要数据二次确认) + * */ + get_random_code: function () { + var flist = [20, 21, 22, 23]; + + var num1 = bt.get_random_num(13, 19); + var t1 = num1 % 10; + + var num2 = bt.get_random_num(13, 29); + var t2 = num2 % 10; + + while ($.inArray(num2, flist) >= 0 || t1 + t2 <= 10 || t1 == t2) { + num2 = bt.get_random_num(13, 29); + t2 = num2 % 10; + } + return { num1: num1, num2: num2 }; + }, + /** + * @description 设置本地存储,local和session + * @param {String} type 存储类型,可以为空,默认为session类型。 + * @param {String} key 存储键名 + * @param {String} val 存储键值 + * @return 无返回值 + */ + set_storage: function (type, key, val) { + if (type != 'local' && type != 'session') (val = key), (key = type), (type = 'local'); + window[type + 'Storage'].setItem(key, val); + }, + + /** + * @description 获取本地存储,local和session + * @param {String} type 存储类型,可以为空,默认为session类型。 + * @param {String} key 存储键名 + * @return {String} 返回存储键值 + */ + get_storage: function (type, key) { + if (type != 'local' && type != 'session') (key = type), (type = 'local'); + return window[type + 'Storage'].getItem(key); + }, + + /** + * @description 删除指定本地存储,local和session + * @param {String} type 类型,可以为空,默认为session类型。 + * @param {String} key 键名 + * @return 无返回值 + */ + remove_storage: function (type, key) { + if (type != 'local' && type != 'session') (key = type), (type = 'local'); + window[type + 'Storage'].removeItem(key); + }, + + /** + * @description 删除指定类型的所有存储信息储,local和session + * @param {String} type 类型,可以为空,默认为session类型。 + * @return 无返回值 + */ + clear_storage: function (type) { + if (type != 'local' && type != 'session') (key = type), (type = 'local'); + window[type + 'Storage'].clear(); + }, + set_cookie: function (key, val, time) { + if (time != undefined) { + var exp = new Date(); + exp.setTime(exp.getTime() + time); + time = exp.toGMTString(); + } else { + var Days = 30; + var exp = new Date(); + exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); + time = exp.toGMTString(); + } + document.cookie = key + '=' + escape(val) + ';expires=' + time; + }, + get_cookie: function (key) { + var arr, + reg = new RegExp('(^| )' + key + '=([^;]*)(;|$)'); + if ((arr = document.cookie.match(reg))) { + var val = unescape(arr[2]); + return val == 'undefined' ? '' : val; + } else { + return null; + } + }, + /** + * @description 选择文件目录或文件 + * @param id {string} 元素ID + * @param type {string || function} 选择方式,文件或目录 + * @param success {function} 成功后的回调 + */ + select_path: function (id, type, success, default_path) { + _this = this; + _this.set_cookie('SetName', ''); + if (typeof type !== 'string') { + success = type; + type = 'dir'; + } + var loadT = bt.open({ + type: 1, + area: '680px', + title: type === 'all' ? 'Select directories or files' : type === 'file' ? lan.bt.file : lan.bt.dir, + closeBtn: 2, + shift: 5, + content: + "
          " + + lan.bt.path + + ":
          " + + lan.bt.comp + + "
            " + + lan.bt.filename + + "" + + lan.bt.etime + + "" + + lan.bt.access + + "" + + lan.bt.own + + "
            ', + success: function () { + $('#btn_back').click(function () { + var path = $('#PathPlace').find('span').text(); + path = bt.rtrim(bt.format_path(path), '/'); + var back_path = bt.get_file_path(path); + _this.get_file_list(back_path, type); + }); + //选择 + $('#bt_select').on('click', function () { + var path = bt.format_path($('#PathPlace').find('span').text()); + if (type === 'file' && !$('#tbody tr.active').length) { + layer.msg('Select the file first!', { icon: 0 }); + return false; + } + if ($('#tbody tr').hasClass('active')) { + path = $('#tbody tr.active .bt_open_dir').attr('path'); + } + path = bt.rtrim(path, '/'); + if (path.length === 0) { + path = [$('#PathPlace').find('span').text()]; + } + $('#' + id) + .val(path) + .change(); + $('.' + id) + .val(path) + .change(); + if (typeof success === 'function') success(path); + loadT.close(); + }); + var element = $('#' + id), + paths = element.val(), + defaultPath = $('#defaultPath'); + if (defaultPath.length > 0 && element.parents('.tab-body').length > 0) { + paths = defaultPath.text(); + } + if (default_path) { + paths = default_path; + } + _this.get_file_list(paths, type); + bt.fixed_table('file-list-table'); + }, + }); + _this.set_cookie('ChangePath', loadT.form); + // var paths = $("#" + id).val(); + // if ($('#defaultPath').length > 0 && $("#" + id).parents('.tab-body').length > 0) { + // paths = $('#defaultPath').text(); + // } + // _this.get_file_list(paths, type); + + // function ActiveDisk() { + // var a = $("#PathPlace").find("span").text().substring(0, 1); + // switch (a) { + // case "C": + // $(".path-con-left dd:nth-of-type(1)").css("background", "#eee").siblings().removeAttr("style"); + // break; + // case "D": + // $(".path-con-left dd:nth-of-type(2)").css("background", "#eee").siblings().removeAttr("style"); + // break; + // case "E": + // $(".path-con-left dd:nth-of-type(3)").css("background", "#eee").siblings().removeAttr("style"); + // break; + // case "F": + // $(".path-con-left dd:nth-of-type(4)").css("background", "#eee").siblings().removeAttr("style"); + // break; + // case "G": + // $(".path-con-left dd:nth-of-type(5)").css("background", "#eee").siblings().removeAttr("style"); + // break; + // case "H": + // $(".path-con-left dd:nth-of-type(6)").css("background", "#eee").siblings().removeAttr("style"); + // break; + // default: + // $(".path-con-left dd").removeAttr("style") + // } + // } + }, + get_file_list: function (path, type) { + type = type || 'dir'; + var _that = this; + bt.send('GetDir', 'files/GetDir', { path: path, disk: true }, function (rdata) { + var d = '', + a = '', + disk = rdata.DISK; + if (disk != undefined) { + for (var f = 0; f < disk.length; f++) { + a += + '
            " + + disk[f].path + + '
            '; + } + $('#changecomlist').html(a); + } + for (var f = 0; f < rdata.DIR.length; f++) { + var g = rdata.DIR[f].split(';'); + var e = g[0]; + if (e.length > 20) { + e = e.substring(0, 20) + '...'; + } + if (isChineseChar(e)) { + if (e.length > 10) { + e = e.substring(0, 10) + '...'; + } + } + d += + '' + + (type === 'all' || type === 'dir' ? '' : '') + + '" + + e + + '' + + bt.format_data(g[2]) + + '' + + g[3] + + '' + + g[4] + + ''; + } + + if (rdata.FILES != null && rdata.FILES != '') { + for (var f = 0; f < rdata.FILES.length; f++) { + var g = rdata.FILES[f].split(';'); + var e = g[0]; + if (e.length > 20) { + e = e.substring(0, 20) + '...'; + } + if (isChineseChar(e)) { + if (e.length > 10) { + e = e.substring(0, 10) + '...'; + } + } + d += + '' + + (type === 'all' || type === 'file' ? '' : '') + + '" + + e + + '' + + bt.format_data(g[2]) + + '' + + g[3] + + '' + + g[4] + + ''; + } + } + + $('.default').hide(); + $('.file-list').show(); + $('#tbody').html(d); + if (rdata.PATH.substr(rdata.PATH.length - 1, 1) != '/') { + rdata.PATH += '/'; + } + $('#PathPlace').find('span').html(rdata.PATH); + $('#tbody tr').click(function () { + if ($(this).find('td:eq(0) input').length > 0) { + if ($(this).hasClass('active')) { + $(this).removeClass('active'); + $(this).find('td:eq(0) input').prop('checked', false); + } else { + $(this).find('td:eq(0) input').prop('checked', true); + $(this).siblings().find('td:eq(0) input').prop('checked', false); + $(this).addClass('active').siblings().removeClass('active'); + } + } + }); + $('#changecomlist dd').click(function () { + _that.get_file_list($(this).attr('path'), type); + }); + $('.bt_open_dir span').click(function () { + if ($(this).parent().data('type') == 'dir') _that.get_file_list($(this).parent().attr('path'), type); + }); + }); + }, + prompt_confirm: function (title, msg, callback) { + layer.open({ + type: 1, + title: title, + area: '480px', + closeBtn: 2, + btn: ['OK', 'Cancel'], + content: + "
            \ +

            " + + msg + + "

            \ +
            \ + \ +
            If you confirm the operation, enter it manually '" + + title + + "'
            \ +
            \ +
            ", + success: function () { + var black_txt_ = $('#prompt_input_box'); + + $('.placeholder').click(function () { + $(this).hide().siblings('input').focus(); + }); + black_txt_.focus(function () { + $('.prompt_input_tips.placeholder').hide(); + }); + black_txt_.blur(function () { + black_txt_.val() == '' ? $('.prompt_input_tips.placeholder').show() : $('.prompt_input_tips.placeholder').hide(); + }); + black_txt_.keyup(function () { + if (black_txt_.val() == '') { + $('.prompt_input_tips.placeholder').show(); + $('.prompt_input_ps').hide(); + } else { + $('.prompt_input_tips.placeholder').hide(); + } + }); + }, + yes: function (layers, index) { + var result = $('#prompt_input_box').val().trim(); + if (result == title) { + layer.close(layers); + if (callback) callback(); + } else { + $('.prompt_input_ps').show(); + } + }, + }); + }, + show_confirm: function (title, msg, fun, error) { + if (error == undefined) { + error = ''; + } + var d = Math.round(Math.random() * 9 + 1); + var c = Math.round(Math.random() * 9 + 1); + var e = ''; + e = d + c; + sumtext = d + ' + ' + c; + bt.set_cookie('vcodesum', e); + var mess = layer.open({ + type: 1, + title: title, + area: '350px', + closeBtn: 2, + shadeClose: true, + content: + "

            " + + msg + + '

            ' + + error + + "
            " + + lan.bt.cal_msg + + "" + + sumtext + + "=
            ', + success: function ($layer) { + $('#vcodeResult') + .focus() + .keyup(function (a) { + if (a.keyCode == 13) { + $('#toSubmit').click(); + } + }); + }, + }); + + $('.bt-cancel').click(function () { + layer.close(mess); + }); + $('#toSubmit').click(function () { + var a = $('#vcodeResult').val().replace(/ /g, ''); + if (a == undefined || a == '') { + layer.msg(lan.bt.cal_err); + return; + } + if (a != bt.get_cookie('vcodesum')) { + layer.msg(lan.bt.cal_err); + return; + } + layer.close(mess); + fun(); + }); + }, + /** + * @description 计算提示弹窗 + * @param {Object} config 弹窗对象 {title: 提示标题, msg: 提示内容} + * @param {function} callback 回调函数 + */ + compute_confirm: function (config, callback) { + var d = Math.round(Math.random() * 9 + 1), + c = Math.round(Math.random() * 9 + 1), + t = d + ' + ' + c, + e = d + c; + + function submit(index, layero) { + var a = $('#vcodeResult'), + val = a.val().replace(/ /g, ''); + if (val == undefined || val == '') { + layer.msg(lan.bt.cal_err); + return; + } + if (val != a.data('value')) { + layer.msg(lan.bt.cal_err); + return; + } + layer.close(index); + if (callback) callback(); + } + layer.open({ + type: 1, + title: config.title, + area: '430px', + closeBtn: 2, + shadeClose: true, + btn: [lan['public'].ok, lan['public'].cancel], + content: + '
            \ +
            \ + \ +
            ' + + config.msg + + '
            \ +
            \ +
            Result:' + + t + + '=
            \ +
            ', + success: function (layero, index) { + $('#vcodeResult') + .focus() + .keyup(function (a) { + if (a.keyCode == 13) { + submit(index, layero); + } + }); + }, + yes: submit, + }); + }, + to_login: function () { + layer.confirm(lan.public_backup.login_expire, { title: lan.public_backup.session_expire, icon: 2, closeBtn: 1, shift: 5 }, function () { + location.reload(); + }); + }, + do_login: function () { + bt.confirm({ msg: lan.bt.loginout }, function () { + window.location.href = '/login?dologin=True'; + }); + }, + send: function (response, module, data, callback, sType) { + if (sType == undefined) sType = 1; + + module = module.replace('panel_data', 'data'); + sType = 1; + var str = bt.get_random(16); + console.time(str); + if (!response) alert(lan.get('lack_param', ['response'])); + modelTmp = module.split('/'); + if (modelTmp.length < 2) alert(lan.get('lack_param', ['s_module', 'action'])); + if (bt.os == 'Linux' && sType === 0) { + socket.on(response, function (rdata) { + socket.removeAllListeners(response); + var rRet = rdata.data; + if (rRet.status === -1) { + bt.to_login(); + return; + } + console.timeEnd(str); + if (callback) callback(rRet); + }); + if (!data) data = {}; + data = bt.linux_format_param(data); + data['s_response'] = response; + data['s_module'] = modelTmp[0]; + data['action'] = modelTmp[1]; + socket.emit('panel', data); + } else { + data = bt.win_format_param(data); + var url = '/' + modelTmp[0] + '?action=' + modelTmp[1]; + $.post(url, data, function (rdata) { + //会话失效时自动跳转到登录页面 + if (typeof rdata == 'string') { + if ((rdata.indexOf('/static/favicon.ico') != -1 && rdata.indexOf('/static/img/qrCode.png') != -1) || rdata.indexOf('') === 0) { + window.location.href = '/login'; + return; + } + } + + if (callback) callback(rdata); + }); + } + }, + linux_format_param: function (param) { + if (typeof param == 'string') { + var data = {}; + arr = param.split('&'); + var reg = /(^[^=]*)=(.*)/; + for (var i = 0; i < arr.length; i++) { + var tmp = arr[i].match(reg); + if (tmp.length >= 3) data[tmp[1]] = tmp[2] == 'undefined' ? '' : tmp[2]; + } + return data; + } + return param; + }, + win_format_param: function (param) { + if (typeof data == 'object') { + var data = ''; + for (var key in param) { + data += key + '=' + param[key] + '&'; + } + if (data.length > 0) data = data.substr(0, data.length - 1); + return data; + } + return param; + }, + msg: function (config) { + var btnObj = { + title: config.title ? config.title : false, + shadeClose: config.shadeClose ? config.shadeClose : true, + closeBtn: config.closeBtn ? config.closeBtn : 0, + area: config.area ? config.area : 'auto', + scrollbar: true, + shade: 0.3, + }; + if (!config.hasOwnProperty('time')) config.time = 2000; + if (typeof config.msg == 'string' && bt.contains(config.msg, 'ERROR')) config.time = 0; + + if (config.hasOwnProperty('icon')) { + if (typeof config.icon == 'boolean') config.icon = config.icon ? 1 : 2; + } else if (config.hasOwnProperty('status')) { + config.icon = config.status ? 1 : 2; + if (!config.status) { + btnObj.time = 0; + } + } + if (config.icon) btnObj.icon = config.icon; + btnObj.time = config.time; + var msg = ''; + if (config.msg) msg += config.msg; + if (config.msg_error) msg += config.msg_error; + if (config.msg_solve) msg += config.msg_solve; + + layer.msg(msg, btnObj); + }, + confirm: function (config, callback, callback1) { + var btnObj = { + title: config.title ? config.title : false, + time: config.time ? config.time : 0, + shadeClose: config.shadeClose ? config.shadeClose : true, + closeBtn: config.closeBtn ? config.closeBtn : 2, + scrollbar: true, + shade: 0.3, + icon: 3, + area: config.area ? config.area : 'auto', + cancel: config.cancel ? config.cancel : function () {}, + }; + layer.confirm( + config.msg, + btnObj, + function (index) { + if (callback) callback(index); + }, + function (index) { + if (callback1) callback1(index); + } + ); + }, + load: function (msg) { + if (!msg) msg = lan.public.the; + var loadT = layer.msg(msg, { icon: 16, time: 0, shade: [0.3, '#000'] }); + var load = { + form: loadT, + close: function () { + layer.close(load.form); + }, + }; + return load; + }, + open: function (config) { + config.closeBtn = 2; + var loadT = layer.open(config); + var load = { + form: loadT, + close: function () { + layer.close(load.form); + }, + }; + return load; + }, + closeAll: function () { + layer.closeAll(); + }, + check_select: function () { + setTimeout(function () { + var num = $('input[type="checkbox"].check:checked').length; + if (num == 1) { + $('button[batch="true"]').hide(); + $('button[batch="false"]').show(); + } else if (num > 1) { + $('button[batch="true"]').show(); + $('button[batch="false"]').show(); + } else { + $('button[batch="true"]').hide(); + $('button[batch="false"]').hide(); + } + }, 5); + }, + render_help: function (arr) { + var html = '
              '; + for (var i = 0; i < arr.length; i++) { + html += '
            • ' + arr[i] + '
            • '; + } + html += '
            '; + return html; + }, + render_ps: function (item) { + var html = "

            " + item.title + '

            '; + for (var i = 0; i < item.list.length; i++) { + html += '

            ' + item.list[i].title + ':' + item.list[i].val + '

            '; + } + html += '

            '; + return html; + }, + render_table: function (obj, arr, append) { + //渲染表单表格 + var html = ''; + for (var key in arr) { + html += '' + key + ''; + if (typeof arr[key] != 'object') { + html += '' + arr[key] + ''; + } else { + for (var i = 0; i < arr[key].length; i++) { + html += '' + arr[key][i] + ''; + } + } + html += ''; + } + if (append) { + $('#' + obj).append(html); + } else { + $('#' + obj).html(html); + } + }, + + fixed_table: function (name) { + $('#' + name) + .parent() + .bind('scroll', function () { + var scrollTop = this.scrollTop; + $(this) + .find('thead') + .css({ transform: 'translateY(' + scrollTop + 'px)', position: 'relative', 'z-index': '1' }); + }); + }, + render_tab: function (obj, arr) { + var _obj = $('#' + obj).addClass('tab-nav'); + for (var i = 0; i < arr.length; i++) { + var item = arr[i]; + var _tab = $('' + item.title + ''); + if (item.callback) { + _tab.data('callback', item.callback); + _tab.click(function () { + $('#' + obj) + .find('span') + .removeClass('on'); + $(this).addClass('on'); + var _contents = $('#' + obj).next('.tab-con'); + _contents.html(''); + $(this).data('callback')(_contents); + }); + } + _obj.append(_tab); + } + }, + render_form_line: function (item, bs, form) { + var clicks = [], + _html = '', + _hide = '', + is_title_css = ' ml0'; + if (!bs) bs = ''; + if (item.title) { + _html += '' + item.title + ''; + is_title_css = ''; + } + _html += "
            "; + + var _name = item.name; + var _placeholder = item.placeholder; + if (item.items && item.type != 'select') { + for (var x = 0; x < item.items.length; x++) { + var _obj = item.items[x]; + if (!_name && !_obj.name) { + alert(lan.public_backup.name_err); + return; + } + if (_obj.hide) continue; + if (_obj.name) _name = _obj.name; + if (_obj.placeholder) _placeholder = _obj.placeholder; + if (_obj.title) _html += '
            ' + _obj.title + ' '; + var _add_class = _obj.add_class ? ' ' + _obj.add_class : ''; + switch (_obj.type) { + case 'select': + var _width = _obj.width ? _obj.width : '100px'; + _html += ''; + break; + case 'textarea': + var _width = _obj.width ? _obj.width : '330px', + _height = _obj.height ? _obj.height : '100px'; + _html += + ''; + if (_placeholder) _html += '
            ' + _placeholder + '
            '; + break; + case 'button': + var _width = _obj.width ? _obj.width : '330px'; + _html += "'; + break; + case 'radio': + var _v = _obj.value === true ? 'checked' : ''; + _html += + ''; + break; + case 'radio_group': + $.each(_obj.list, function (index, item) { + var id = _name + '_' + index, + _v = _obj.value === item.value ? 'checked' : ''; + _html += + '
            '; + }); + break; + case 'checkbox': + var _v = _obj.value === true ? 'checked' : ''; + _html += + ''; + break; + case 'number': + var _width = _obj.width ? _obj.width : '330px'; + _html += + ""; + _html += _obj.unit ? _obj.unit : ''; + break; + case 'password': + var _width = _obj.width ? _obj.width : '330px'; + _html += + ""; + break; + case 'div': + var _width = _obj.width ? _obj.width : '330px', + _height = _obj.height ? _obj.height : '100px'; + _html += + '
            ' + + (_obj.value ? _obj.value : '') + + '
            '; + if (_placeholder) _html += '
            ' + _placeholder + '
            '; + break; + case 'switch': + _html += + '
            \ + \ + \ +
            '; + break; + case 'html': + _html += _obj.html; + break; + default: + var _width = _obj.width ? _obj.width : '330px'; + _html += + ""; + break; + } + if (_obj.title) _html += '
            '; + if (_obj.callback) clicks.push({ bind: _name + bs, callback: _obj.callback }); + if (_obj.event) { + _html += ''; + if (_obj.event.callback) clicks.push({ bind: 'icon_' + _name + bs, callback: _obj.event.callback }); + } + if (_obj.ps) _html += " " + _obj.ps + ''; + if (_obj.ps_help) _html += "?"; + } + if (item.ps) _html += " " + item.ps + ''; + if (item.ps_help) _html += "?"; + } else { + switch (item.type) { + case 'select': + var _width = item.width ? item.width : '100px'; + _html += ''; + break; + case 'button': + var _width = item.width ? item.width : '330px'; + _html += "'; + break; + case 'number': + var _width = item.width ? item.width : '330px'; + _html += + ""; + break; + case 'checkbox': + var _v = item.value === true ? 'checked' : ''; + _html += + ''; + break; + case 'password': + var _width = item.width ? item.width : '330px'; + _html += + ""; + break; + case 'textarea': + var _width = item.width ? item.width : '330px'; + var _height = item.height ? item.height : '100px'; + _html += + ''; + if (_placeholder) _html += '
            ' + _placeholder + '
            '; + break; + default: + var _width = item.width ? item.width : '330px'; + + _html += + ""; + break; + } + if (item.callback) clicks.push({ bind: _name + bs, callback: item.callback }); + if (item.ps) _html += " " + item.ps + ''; + if (item.ps_help) _html += "?"; + } + _html += '
            '; + if (!item.class) item.class = ''; + if (item.hide) _hide = 'style="display:none;"'; + _html = '
            ' + _html + '
            '; + + if (form) { + form.append(_html); + bt.render_clicks(clicks); + } + return { html: _html, clicks: clicks, data: item }; + }, + render_form: function (data, callback) { + if (data) { + var bs = '_' + bt.get_random(6); + var _form = $("
            "); + var _lines = data.list; + var clicks = []; + for (var i = 0; i < _lines.length; i++) { + var _obj = _lines[i]; + if (_obj.hasOwnProperty('html')) { + _form.append(_obj.html); + } else { + var rRet = bt.render_form_line(_obj, bs); + for (var s = 0; s < rRet.clicks.length; s++) clicks.push(rRet.clicks[s]); + _form.append(rRet.html); + } + } + + var _btn_html = ''; + for (var i = 0; i < data.btns.length; i++) { + var item = data.btns[i]; + var css = item.css ? item.css : 'btn-danger'; + _btn_html += "'; + clicks.push({ bind: item.name + bs, callback: item.callback }); + } + _form.append("
            " + _btn_html + '
            '); + var loadOpen = bt.open({ + type: 1, + skin: data.skin, + area: data.area, + title: data.title, + closeBtn: 2, + content: _form.prop('outerHTML'), + end: data.end ? data.end : false, + success: function () { + if (data.success) data.success(); + }, + }); + setTimeout(function () { + bt.render_clicks(clicks, loadOpen, callback); + }, 100); + } + return bs; + }, + render_clicks: function (clicks, loadOpen, callback) { + for (var i = 0; i < clicks.length; i++) { + var obj = clicks[i]; + + var btn = $('.' + obj.bind); + btn.data('item', obj); + btn.data('load', loadOpen); + btn.data('callback', callback); + + switch (btn.prop('tagName')) { + case 'SPAN': + btn.click(function () { + var _obj = $(this).data('item'); + _obj.callback($(this).attr('data-id')); + }); + break; + case 'SELECT': + btn.change(function () { + var _obj = $(this).data('item'); + _obj.callback($(this)); + }); + break; + case 'TEXTAREA': + case 'INPUT': + case 'BUTTON': + if (btn.prop('tagName') == 'BUTTON' || btn.attr('type') == 'checkbox') { + btn.click(function () { + var _obj = $(this).data('item'); + var load = $(this).data('load'); + var _callback = $(this).data('callback'); + var parent = $(this).parents('.bt-form'); + + if (_obj.callback) { + var data = {}; + parent.find('*').each(function (index, _this) { + var _name = $(_this).attr('name'); + + if (_name) { + if ($(_this).attr('type') == 'checkbox' || $(_this).attr('type') == 'radio') { + data[_name] = $(_this).prop('checked'); + } else { + data[_name] = $(_this).val(); + } + } + }); + _obj.callback(data, load, function (rdata) { + if (_callback) _callback(rdata); + }); + } else { + load.close(); + } + }); + } else { + if (btn.attr('type') == 'radio') { + btn.click(function () { + var _obj = $(this).data('item'); + _obj.callback($(this)); + }); + } else { + btn.on('input', function () { + var _obj = $(this).data('item'); + _obj.callback($(this)); + }); + } + } + break; + } + } + }, + render: function ( + obj //columns 行 + ) { + if (obj.columns) { + var checks = {}; + $(obj.table).html(''); + var thead = ''; + for (var h = 0; h < obj.columns.length; h++) { + var item = obj.columns[h]; + if (item) { + thead += ''; + } + if (item.help) thead += '?'; + + thead += ''; + } + } + thead += ''; + var _tab = $(obj.table).append(thead); + if (obj.data.length > 0) { + for (var i = 0; i < obj.data.length; i++) { + var val = obj.data[i]; + var tr = $(''); + for (var h = 0; h < obj.columns.length; h++) { + var item = obj.columns[h]; + if (item) { + var _val = val[item.field]; + if (typeof _val == 'string') _val = _val.replace(/\\/g, ''); + if (item.hasOwnProperty('templet')) _val = item.templet(val); + if (item.type == 'checkbox') _val = ''; + var td = ''); + tr.data('item', val); + _tab.append(tr); + } + } + } + } else { + _tab.append("" + obj.empty ? obj.empty : lan.bt.no_data + ''); + } + $(obj.table) + .find('.check') + .click(function () { + var checked = $(this).prop('checked'); + if ($(this).parent().prop('tagName') == 'TH') { + $('.check').prop('checked', checked ? 'checked' : ''); + } + }); + var asc = 'glyphicon-triangle-top'; + var desc = 'glyphicon-triangle-bottom'; + + var orderby = bt.get_cookie('order'); + if (orderby != undefined) { + var arrys = orderby.split(' '); + if (arrys.length == 2) { + if (arrys[1] == 'asc') { + $(obj.table) + .find('th span[data-id="' + arrys[0] + '"]') + .removeClass(desc) + .addClass(asc); + } else { + $(obj.table) + .find('th span[data-id="' + arrys[0] + '"]') + .removeClass(asc) + .addClass(desc); + } + } + } + + $(obj.table) + .find('th') + .data('checks', checks) + .click(function () { + var _th = $(this); + var _checks = _th.data('checks'); + var _span = _th.find('span'); + if (_span.length > 0) { + var or = _span.attr('data-id'); + if (_span.hasClass(asc)) { + bt.set_cookie('order', or + ' desc'); + $(obj.table) + .find('th span[data-id="' + or + '"]') + .removeClass(asc) + .addClass(desc); + _checks[or](); + } else if (_span.hasClass(desc)) { + bt.set_cookie('order', or + ' asc'); + $(obj.table) + .find('th span[data-id="' + arrys[0] + '"]') + .removeClass(desc) + .addClass(asc); + _checks[or](); + } + } + }); + } + return _tab; + }, + // ACE编辑配置文件 + aceEditor: function (obj) { + var aEditor = { + ACE: ace.edit(obj.el, { + theme: obj.theme ? obj.theme : 'ace/theme/chrome', // 主题 + mode: 'ace/mode/' + (obj.mode || 'nginx'), // 语言类型 + wrap: true, + showInvisibles: false, + showPrintMargin: false, + showFoldWidgets: false, + useSoftTabs: true, + tabSize: 2, + showPrintMargin: false, + readOnly: false, + }), + path: obj.path, + content: '', + saveCallback: obj.saveCallback, + }, + _this = this; + $('#' + obj.el).css('fontSize', '12px'); + aEditor.ACE.commands.addCommand({ + name: '保存文件', + bindKey: { win: 'Ctrl-S', mac: 'Command-S' }, + exec: function (editor) { + _this.saveEditor(aEditor, aEditor.saveCallback); + }, + readOnly: false, // 如果不需要使用只读模式,这里设置false + }); + if (obj.path != undefined) { + var loadT = layer.msg(lan.soft.get_config, { icon: 16, time: 0, shade: [0.3, '#000'] }); + bt.send('GetFileBody', 'files/GetFileBody', { path: obj.path }, function (res) { + layer.close(loadT); + if (!res.status) { + bt.msg(res); + return false; + } + aEditor.ACE.setValue(res.data); //设置配置文件内容 + aEditor.ACE.moveCursorTo(0, 0); //设置文件光标位置 + aEditor.ACE.resize(); + }); + } else if (obj.content != undefined) { + aEditor.ACE.setValue(obj.content); + aEditor.ACE.moveCursorTo(0, 0); //设置文件光标位置 + aEditor.ACE.resize(); + } + return aEditor; + }, + // 保存编辑器文件 + saveEditor: function (ace) { + if (!ace.saveCallback) { + var loadT = bt.load(lan.soft.the_save); + bt.send('SaveFileBody', 'files/SaveFileBody', { data: ace.ACE.getValue(), path: ace.path, encoding: 'utf-8' }, function (rdata) { + loadT.close(); + bt.msg(rdata); + }); + } else { + ace.saveCallback(ace.ACE.getValue()); + } + }, + /** + * @description 遍历数组和对象 + * @param {Array|Object} obj 遍历数组|对象 + * @param {Function} fn 遍历对象或数组 + * @return 当前对象 + */ + each: function (obj, fn) { + var key, + that = this; + if (typeof fn !== 'function') return that; + obj = obj || []; + if (obj.constructor === Object) { + for (key in obj) { + if (fn.call(obj[key], key, obj[key])) break; + } + } else { + for (key = 0; key < obj.length; key++) { + if (fn.call(obj[key], key, obj[key])) break; + } + } + return that; + }, + /** + * @description 普通提示弹窗 + * @param {Object} config 弹窗对象 {title:标题, msg:提示内容} + * @param {function} callback 确认回调函数 + * @param {function} callback1 取消回调函数 + */ + simple_confirm: function (config, callback, callback1) { + layer.open({ + type: 1, + title: config.title, + area: '430px', + closeBtn: 2, + shadeClose: false, + btn: [lan['public'].ok, lan['public'].cancel], + content: + '
            \ +
            \ + \ +
            ' + + config.msg + + '
            \ +
            \ +
            ', + yes: function (index, layero) { + if (callback && typeof callback(index) === 'undefined') layer.close(index); + }, + btn2: function (index) { + //取消返回回调 + if (callback1 && typeof callback1(index) === 'undefined') layer.close(index); + }, + cancel: function (index) { + //取消返回回调 + if (callback1 && typeof callback1(index) === 'undefined') layer.close(index); + }, + }); + }, + /** + * @description 需求反馈弹窗 + * @param {Object} param 配置对象 {title:标题, placeholder:反馈问题pl(可带标签),recover:input框下方提示语, key:反馈问题key, proType:产品类型} + */ + openFeedback: function (param) { + // 需求反馈 + var openFeed = bt_tools.open({ + area: ['570px', '400px'], + btn: false, + content: + '
            \ +
            \ + ' + + param.title + + ' \ +
            \ +
            \ +
            \ +
            \ +
            \ +
            \ +
            ', + success: function (that) { + var id = 'x66ed9v07MjVjYjczNTUyMDE0Le8BEdl'; + bt_tools.send( + { url: '/config?action=get_nps_new', data: { product_type: param.proType } }, + function (ress) { + //请求回调 + console.log(ress); + if (ress.res) { + id = ress.res[0].id; + } + }, + { load: 'Loading...', verify: false } + ); + //打开弹窗后执行的事件 + that.find('.layui-layer-title').remove(); + bt_tools.form({ + el: '#feedForm', + form: [ + { + group: { + type: 'textarea', + name: 'feed', + style: { + width: '500px', + 'min-width': '500px', + 'min-height': '130px', + 'line-height': '22px', + 'padding-top': '10px', + resize: 'none', + }, + tips: { + //使用hover的方式显示提示 + text: param.placeholder, + style: { top: '126px', left: '50px' }, + }, + }, + }, + { + group: { + name: 'tips', + type: 'other', + boxcontent: '
            ' + param.recover + '
            ', + }, + }, + { + group: { + type: 'button', + size: '', + name: 'submitForm', + class: 'feedBtn', + style: 'margin:10px auto 0;padding:6px 40px;', + title: 'Submit', + event: function (formData, element, that) { + // 触发submit + if (formData.feed == '') { + return bt.msg({ status: false, msg: 'Please fill in the feedback' }); + } + var config = {}; + config[id] = formData.feed; + bt_tools.send( + { url: 'config?action=write_nps_new', data: { questions: JSON.stringify(config), rate: 0, product_type: param.proType } }, + function (ress) { + if (ress.status) { + openFeed.close(); + layer.open({ + title: false, + btn: false, + shadeClose: true, + shade: 0.1, + closeBtn: 0, + skin: 'qa_thank_dialog', + area: '230px', + content: + '

            Thank you for your participation!

            ', + success: function (layero, index) { + $(layero).find('.layui-layer-content').css({ padding: '0', 'border-radius': '5px' }); + $(layero).css({ 'border-radius': '5px', 'min-width': '230px' }); + + setTimeout(function () { + layer.close(index); + }, 3000); + }, + }); + } + }, + 'submit feedback' + ); + }, + }, + }, + ], + }); + }, + yes: function () { + //点击确定时,如果btn:false,当前事件将无法使用 + }, + cancel: function () { + //点击右上角关闭时,如果btn:false,当前事件将无法使用 + }, + }); + }, +}; + +bt.pub = { + get_data: function (data, callback, hide) { + if (!hide) var loading = bt.load(lan.public.the); + bt.send('getData', 'data/getData', data, function (rdata) { + if (loading) loading.close(); + if (callback) callback(rdata); + }); + }, + set_data_by_key: function (tab, key, obj) { + var _span = $(obj); + var _input = $(""); + _span.hide().after(_input); + _input.focus(); + _input.blur(function () { + var item = $(this).parents('tr').data('item'); + var _txt = $(this); + var data = { table: tab, id: item.id }; + data[key] = _txt.val(); + bt.pub.set_data_ps(data, function (rdata) { + if (rdata.status) { + _span.text(_txt.val()); + _span.show(); + _txt.remove(); + } + }); + }); + _input.keyup(function () { + if (event.keyCode == 13) { + _input.trigger('blur'); + } + }); + }, + set_data_ps: function (data, callback) { + bt.send('setPs', 'data/setPs', data, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_server_status: function (serverName, type) { + if (bt.contains(serverName, 'php-')) { + serverName = 'php-fpm-' + serverName.replace('php-', '').replace('.', ''); + } + if (serverName == 'pureftpd') serverName = 'pure-ftpd'; + if (serverName == 'mysql') serverName = 'mysqld'; + serverName = serverName.replace('_soft', ''); + var data = 'name=' + serverName + '&type=' + type; + var msg = lan.bt[type]; + var typeName = ''; + switch (type) { + case 'stop': + typeName = lan.public_backup.stop; + break; + case 'restart': + typeName = lan.public_backup.restart; + break; + case 'reload': + typeName = lan.public_backup.reload; + break; + } + bt.confirm({ msg: lan.get('service_confirm', [msg, serverName]), title: typeName + serverName + lan.public_backup.server }, function () { + var load = bt.load(lan.get('service_the', [msg, serverName])); + bt.send('system', 'system/ServiceAdmin', data, function (rdata) { + load.close(); + var f = rdata.status ? lan.get('service_ok', [serverName, msg]) : lan.get('service_err', [serverName, msg]); + bt.msg({ msg: f, icon: rdata.status }); + + if (type != 'reload' && rdata.status) { + setTimeout(function () { + window.location.reload(); + }, 1000); + } + if (!rdata.status) { + bt.msg(rdata); + } + }); + }); + }, + set_ftp_logs: function (type) { + var serverName = 'pure-ftpd'; + var data = 'exec_name=' + type; + var typeName = 'enabling '; + var TypeName = 'Enabling '; + switch (type) { + case 'stop': + typeName = 'disabling '; + TypeName = 'Disabling '; + break; + } + var status = type == 'stop' ? false : true; + layer.confirm( + 'After ' + + typeName + + 'pure-ftpd Logs management,' + + (status ? 'all login and operation records of FTP users will be recorded.' : 'it will no longer be possible to record all login and operation records of FTP users. ') + + ' Do you want to proceed?', + { + title: TypeName + serverName + ' logs management', + closeBtn: 2, + icon: 3, + cancel: function () { + $('#isFtplog').prop('checked', !status); + }, + }, + function () { + var load = bt.load(TypeName + 'Pure-FTPd logs management, please wait...'); + bt.send('ftp', 'ftp/set_ftp_logs', data, function (rdata) { + load.close(); + bt.msg(rdata); + $('.bt-soft-menu p').eq(3).click(); + }); + }, + function () { + $('#isFtplog').prop('checked', !status); + } + ); + }, + get_ftp_logs: function (callback) { + bt.send('ftp', 'ftp/set_ftp_logs', { exec_name: 'getlog' }, function (res) { + var _status = res.msg === 'start' ? true : false; + if (callback) callback(_status); + }); + }, + set_server_status_by: function (data, callback) { + bt.send('system', 'system/ServiceAdmin', data, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_task_count: function (callback) { + bt.send('GetTaskCount', 'ajax/GetTaskCount', {}, function (rdata) { + $('.task').text(rdata); + if (callback) callback(rdata); + }); + }, + check_install: function (callback) { + bt.send('CheckInstalled', 'ajax/CheckInstalled', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_user_info: function (callback) { + var loading = bt.load(); + bt.send('GetUserInfo', 'ssl/GetUserInfo', {}, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + show_hide_pass: function (obj) { + var a = 'glyphicon-eye-open'; + var b = 'glyphicon-eye-close'; + + if ($(obj).hasClass(a)) { + $(obj).removeClass(a).addClass(b); + $(obj).prev().text($(obj).prev().attr('data-pw')); + } else { + $(obj).removeClass(b).addClass(a); + $(obj).prev().text('**********'); + } + }, + copy_pass: function (password) { + var clipboard = new ClipboardJS('#bt_copys'); + clipboard.on('success', function (e) { + bt.msg({ msg: lan.public_backup.cp_success, icon: 1 }); + }); + + clipboard.on('error', function (e) { + bt.msg({ msg: lan.public_backup.cp_fail, icon: 2 }); + }); + $('#bt_copys').attr('data-clipboard-text', password); + $('#bt_copys').click(); + }, + login_btname: function (username, password, callback) { + var loadT = bt.load(lan.config.token_get); + bt.send('GetToken', 'ssl/GetToken', 'username=' + username + '&password=' + password, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (rdata.status) { + if (callback) callback(rdata); + } + }); + }, + bind_btname: function (callback) { + layer.open({ + type: 1, + title: lan.public_backup.bind_bt_account, + area: ['420px', '360px'], + closeBtn: 2, + shadeClose: false, + content: + '

            ' + + lan.public_backup.bind_bt_account + + '

            ' + + lan.public_backup.no_account + + '

            ', + }); + setTimeout(function () { + $('.login-button').click(function () { + p1 = $('#p1').val(); + p2 = $('#p2').val(); + var loadT = bt.load(lan.config.token_get); + bt.send('GetToken', 'ssl/GetToken', 'username=' + p1 + '&password=' + p2, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (rdata.status) { + if (callback) { + layer.closeAll(); + callback(rdata); + } else { + window.location.reload(); + } + $("input[name='btusername']").val(p1); + } + }); + }); + }, 100); + }, + unbind_bt: function () { + var name = $("input[name='btusername']").val(); + bt.confirm({ msg: lan.config.binding_un_msg, title: lan.config.binding_un_title }, function () { + bt.send('DelToken', 'ssl/DelToken', {}, function (rdata) { + bt.msg(rdata); + $("input[name='btusername']").val(''); + }); + }); + }, + get_menm: function (callback) { + var loading = bt.load(); + bt.send('GetMemInfo', 'system/GetMemInfo', {}, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + on_edit_file: function (type, fileName) { + if (type != 0) { + var l = $('#PathPlace input').val(); + var body = encodeURIComponent($('#textBody').val()); + var encoding = $('select[name=encoding]').val(); + var loadT = bt.load(lan.bt.save_file); + bt.send('SaveFileBody', 'files/SaveFileBody', 'data=' + body + '&path=' + fileName + '&encoding=' + encoding, function (rdata) { + if (type == 1) loadT.close(); + bt.msg(rdata); + }); + return; + } + var loading = bt.load(lan.bt.read_file); + ext = bt.get_file_ext(fileName); + doctype = ''; + switch (ext) { + case 'html': + var mixedMode = { + name: 'htmlmixed', + scriptTypes: [ + { matches: /\/x-handlebars-template|\/x-mustache/i, mode: null }, + { matches: /(text|application)\/(x-)?vb(a|script)/i, mode: 'vbscript' }, + ], + }; + doctype = mixedMode; + break; + case 'htm': + var mixedMode = { + name: 'htmlmixed', + scriptTypes: [ + { matches: /\/x-handlebars-template|\/x-mustache/i, mode: null }, + { matches: /(text|application)\/(x-)?vb(a|script)/i, mode: 'vbscript' }, + ], + }; + doctype = mixedMode; + break; + case 'js': + doctype = 'text/javascript'; + break; + case 'json': + doctype = 'application/ld+json'; + break; + case 'css': + doctype = 'text/css'; + break; + case 'php': + doctype = 'application/x-httpd-php'; + break; + case 'tpl': + doctype = 'application/x-httpd-php'; + break; + case 'xml': + doctype = 'application/xml'; + break; + case 'sql': + doctype = 'text/x-sql'; + break; + case 'conf': + doctype = 'text/x-nginx-conf'; + break; + default: + var mixedMode = { + name: 'htmlmixed', + scriptTypes: [ + { matches: /\/x-handlebars-template|\/x-mustache/i, mode: null }, + { matches: /(text|application)\/(x-)?vb(a|script)/i, mode: 'vbscript' }, + ], + }; + doctype = mixedMode; + break; + } + bt.send('GetFileBody', 'files/GetFileBody', 'path=' + fileName, function (rdata) { + if (!rdata.status) { + bt.msg({ msg: rdata.msg, icon: 5 }); + return; + } + loading.close(); + var u = ['utf-8', 'GBK', 'GB2312', 'BIG5']; + var n = ''; + var m = ''; + var o = ''; + for (var p = 0; p < u.length; p++) { + m = rdata.encoding == u[p] ? 'selected' : ''; + n += ''; + } + var r = bt.open({ + type: 1, + shift: 5, + closeBtn: 1, + //maxmin: true, + area: ['90%', '90%'], + shade: false, + title: lan.bt.edit_title + '[' + fileName + ']', + content: + '

            ' + + lan.bt.edit_ps + + '

            \ +
            \ +
            ' + + lan.public_backup.cret_format + + '
            \ + \ +
            \ +
            \ + \ +
            \ +
            \ + \ + '; + bt.open({ + type: 1, + area: '600px', + title: lan.public_backup.custom_panel_set, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: certBody, + }); + + $('#btn_submit').click(function () { + key = $('#key').val(); + csr = $('#csr').val(); + _this.set_panel_ssl({ privateKey: key, certPem: csr }); + }); + }); + }, + set_panel_ssl: function (data, callback) { + var loadT = bt.load(lan.config.ssl_msg); + bt.send('SavePanelSSL', 'config/SavePanelSSL', data, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + set_username: function (type) { + if (type == 1) { + if (p1 == '' || p1.length < 3) { + bt.msg({ msg: lan.bt.user_len, icon: 2 }); + return; + } + if (p1 != p2) { + bt.msg({ msg: lan.bt.user_err_re, icon: 2 }); + return; + } + var checks = ['admin', 'root', 'admin123', '123456']; + if ($.inArray(p1, checks)) { + bt.msg({ msg: lan.public_backup.usually_user_ban, icon: 2 }); + return; + } + bt.send('setUsername', 'config/setUsername', { username1: p1, username2: p2 }, function (rdata) { + if (rdata.status) { + layer.closeAll(); + $("input[name='username_']").val(p1); + } + bt.msg(rdata); + }); + return; + } + bt.open({ + type: 1, + area: '290px', + title: lan.bt.user_title, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
            " + + lan.bt.user + + "
            " + + lan.bt.pass_re + + "
            ', + }); + }, + set_password: function (type) { + if (type == 1) { + p1 = $('#p1').val(); + p2 = $('#p2').val(); + if (p1 == '' || p1.length < 8) { + bt.msg({ msg: lan.bt.pass_err_len, icon: 2 }); + return; + } + + //准备弱口令匹配元素 + var checks = ['admin888', '123123123', '12345678', '45678910', '87654321', 'asdfghjkl', 'password', 'qwerqwer']; + pchecks = 'abcdefghijklmnopqrstuvwxyz1234567890'; + for (var i = 0; i < pchecks.length; i++) { + checks.push(pchecks[i] + pchecks[i] + pchecks[i] + pchecks[i] + pchecks[i] + pchecks[i] + pchecks[i] + pchecks[i]); + } + + //检查弱口令 + cps = p1.toLowerCase(); + var isError = ''; + for (var i = 0; i < checks.length; i++) { + if (cps == checks[i]) { + isError += '[' + checks[i] + '] '; + } + } + if (isError != '') { + bt.msg({ msg: lan.bt.pass_err + isError, icon: 2 }); + return; + } + + if (p1 != p2) { + bt.msg({ msg: lan.bt.pass_err_re, icon: 2 }); + return; + } + bt.send('setPassword', 'config/setPassword', { password1: p1, password2: p2 }, function (rdata) { + layer.closeAll(); + bt.msg(rdata); + }); + return; + } + layer.open({ + type: 1, + area: '290px', + title: lan.bt.pass_title, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
            " + + lan.public.pass + + "
            " + + lan.bt.pass_re + + "
            " + + lan.bt.pass_rep_btn + + "
            ', + }); + }, +}; + +bt.system = { + get_total: function (callback) { + bt.send('GetSystemTotal', 'system/GetSystemTotal', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_net: function (callback) { + bt.send('GetNetWork', 'system/GetNetWork', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_disk_list: function (callback) { + bt.send('GetDiskInfo', 'system/GetDiskInfo', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + re_memory: function (callback) { + bt.send('ReMemory', 'system/ReMemory', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + check_update: function (callback, check) { + var data = {}; + if (check == undefined) data = { check: true }; + if (check === false) data = {}; + if (check) var load = bt.load(lan.index.update_get); + bt.send('UpdatePanel', 'ajax/UpdatePanel', data, function (rdata) { + if (check) load.close(); + if (callback) callback(rdata); + }); + }, + to_update: function (callback) { + var load = bt.load(lan.index.update_the); + bt.send('UpdatePanel', 'ajax/UpdatePanel', { toUpdate: 'yes' }, function (rdata) { + load.close(); + if (callback) callback(rdata); + }); + }, + reload_panel: function (callback) { + bt.send('ReWeb', 'system/ReWeb', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + rep_panel: function (callback) { + var loading = bt.load(lan.index.rep_panel_the); + $.ajax({ + type: 'POST', + url: 'system?action=RepPanel', + error: function (err) { + setTimeout(() => { + loading.close(); + bt.system.reload_panel(function () { + location.reload(); + }); + }, 1000 * 60 * 5); + }, + success: function (rdata) { + loading.close(); + if (rdata) { + if (callback) callback({ status: rdata, msg: lan.index.rep_panel_ok }); + bt.system.reload_panel(); + } + }, + }); + }, + get_warning: function (callback) { + bt.send('GetWarning', 'ajax/GetWarning', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + root_reload: function (callback) { + bt.send('RestartServer', 'system/RestartServer', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, +}; + +bt.control = { + get_status: function (callback) { + loading = bt.load(lan.public.read); + bt.send('GetControl', 'control/SetControl', { type: 1 }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_control: function (type, day, callback) { + loadT = bt.load(lan.public.the); + bt.send('SetControl', 'config/SetControl', { type: type, day: day }, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + clear_control: function (callback) { + bt.confirm({ msg: lan.control.close_log_msg, title: lan.control.close_log }, function () { + loadT = bt.load(lan.public.the); + bt.send('SetControl', 'config/SetControl', { type: 'del' }, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }); + }, + get_data: function (type, start, end, callback) { + action = ''; + switch (type) { + case 'cpu': //cpu和内存一起获取 + action = 'GetCpuIo'; + break; + case 'disk': + action = 'GetDiskIo'; + break; + case 'net': + action = 'GetNetWorkIo'; + break; + case 'load': + action = 'get_load_average'; + break; + } + if (!action) bt.msg(lan.get('lack_param', 'type')); + bt.send(action, 'ajax/' + action, { start: start, end: end }, function (rdata) { + if (callback) callback(rdata, type); + }); + }, + format_option: function (obj, type) { + option = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + }, + formatter: obj.formatter, + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: obj.tData, + axisLine: { + lineStyle: { + color: '#666', + }, + }, + }, + yAxis: { + type: 'value', + name: obj.unit, + boundaryGap: [0, '100%'], + min: 0, + splitLine: { + lineStyle: { + color: '#ddd', + }, + }, + axisLine: { + lineStyle: { + color: '#666', + }, + }, + }, + dataZoom: [ + { + type: 'inside', + start: 0, + zoomLock: true, + }, + { + start: 0, + handleIcon: + 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', + handleSize: '80%', + handleStyle: { + color: '#fff', + shadowBlur: 3, + shadowColor: 'rgba(0, 0, 0, 0.6)', + shadowOffsetX: 2, + shadowOffsetY: 2, + }, + }, + ], + series: [], + }; + if (obj.legend) option.legend = obj.legend; + if (obj.dataZoom) option.dataZoom = obj.dataZoom; + + for (var i = 0; i < obj.list.length; i++) { + var item = obj.list[i]; + series = { + name: item.name, + type: item.type ? item.type : 'line', + smooth: item.smooth ? item.smooth : true, + symbol: item.symbol ? item.symbol : 'none', + showSymbol: item.showSymbol ? item.showSymbol : false, + sampling: item.sampling ? item.sampling : 'average', + areaStyle: item.areaStyle ? item.areaStyle : {}, + lineStyle: item.lineStyle ? item.lineStyle : {}, + itemStyle: item.itemStyle ? item.itemStyle : { normal: { color: 'rgb(0, 153, 238)' } }, + symbolSize: 6, + symbol: 'circle', + data: item.data, + }; + option.series.push(series); + } + return option; + }, +}; + +bt.firewall = { + get_log_list: function (page, search, callback) { + if (page == undefined) page = 1; + search = search == undefined ? '' : search; + var order = bt.get_cookie('order') ? '&order=' + bt.get_cookie('order') : ''; + + var data = 'tojs=firewall.get_log_list&table=logs&limit=10&p=' + page + '&search=' + search + order; + bt.pub.get_data(data, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_list: function (page, search, callback) { + if (page == undefined) page = 1; + search = search == undefined ? '' : search; + var order = bt.get_cookie('order') ? '&order=' + bt.get_cookie('order') : ''; + + var data = 'tojs=firewall.get_list&table=firewall&limit=10&p=' + page + '&search=' + search + order; + bt.pub.get_data(data, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_logs_size: function (callback) { + if (bt.os == 'Linux') { + bt.files.get_dir_size('/www/wwwlogs', function (rdata) { + if (callback) callback(rdata); + }); + } + }, + get_ssh_info: function (callback) { + bt.send('GetSshInfo', 'firewall/GetSshInfo', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_mstsc: function (port, callback) { + bt.confirm({ msg: lan.firewall.ssh_port_msg, title: lan.firewall.ssh_port_title }, function () { + loading = bt.load(lan.public.the); + bt.send('SetSshPort', 'firewall/SetSshPort', { port: port }, function (rdata) { + loading.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }); + }, + ping: function (status, callback) { + var msg = status == 0 ? lan.firewall.ping_msg : lan.firewall.ping_un_msg; + layer.confirm( + msg, + { + closeBtn: 2, + title: lan.firewall.ping_title, + cancel: function () { + if (callback) callback(-1); //取消 + }, + }, + function () { + loading = bt.load(lan.public.the); + bt.send('SetPing', 'firewall/SetPing', { status: status }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + function () { + if (callback) callback(-1); //关闭 + } + ); + }, + set_mstsc_status: function (status, callback) { + var msg = status == 1 ? lan.firewall.ssh_off_msg : lan.firewall.ssh_on_msg; + layer.confirm( + msg, + { + icon: 0, + closeBtn: 2, + title: lan.public.warning, + cancel: function () { + if (callback) callback(-1); //取消 + }, + }, + function () { + loading = bt.load(lan.public.the); + bt.send('SetSshStatus', 'firewall/SetSshStatus', { status: status }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + function () { + if (callback) callback(-1); //关闭 + } + ); + }, + add_accept_port: function (type, port, ps, callback) { + var action = 'AddDropAddress'; + if (type == 'port') { + ports = port.split(':'); + if (port.indexOf('-') != -1) ports = port.split('-'); + for (var i = 0; i < ports.length; i++) { + if (!bt.check_port(ports[i])) { + layer.msg(lan.firewall.port_err, { icon: 5 }); + return; + } + } + action = 'AddAcceptPort'; + } + + // if (ps.length < 1) { + // layer.msg(lan.firewall.ps_err, { icon: 2 }); + // return -1; + // } + loading = bt.load(); + bt.send(action, 'firewall/' + action, { port: port, type: type, ps: ps }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + del_accept_port: function (id, port, callback) { + var action = 'DelDropAddress'; + if (port.indexOf('.') == -1) { + action = 'DelAcceptPort'; + } + bt.confirm({ msg: lan.get('confirm_del', [port]), title: lan.firewall.del_title }, function (index) { + var loadT = bt.load(lan.public.the_del); + bt.send(action, 'firewall/' + action, { id: id, port: port }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }); + }, + clear_logs_files: function (callback) { + var loadT = bt.load(lan.firewall.close_the); + bt.send('CloseLogs', 'files/CloseLogs', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + clear_logs: function (callback) { + bt.confirm({ msg: lan.firewall.close_log_msg, title: lan.firewall.close_log }, function () { + var loadT = bt.load(lan.firewall.close_the); + bt.send('delClose', 'ajax/delClose', {}, function (rdata) { + loadT.close(); + if (callback) { + callback(rdata); + } else { + bt.msg(rdata); + } + }); + }); + }, +}; + +bt.soft = { + SSL_flag: false, + pub: { + wxpayTimeId: 0, + }, + php: { + get_config: function (version, callback) { + //获取禁用函数,扩展列表 + // var loading = bt.load(); + bt.send('GetPHPConfig', 'ajax/GetPHPConfig', { version: version }, function (rdata) { + // loading.close(); + if (callback) callback(rdata); + }); + }, + get_limit_config: function (version, callback) { + //获取超时限制,上传限制 + var loading = bt.load(); + bt.send('get_php_config', 'config/get_php_config', { version: version }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_php_config: function (version, callback) { + var loading = bt.load(); + bt.send('GetPHPConf', 'config/GetPHPConf', { version: version }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + install_php_lib: function (version, name, title, callback) { + bt.confirm({ msg: lan.soft.php_ext_install_confirm.replace('{1}', name), title: lan.public_backup.install + '【' + name + '】' }, function () { + name = name.toLowerCase(); + var loadT = bt.load(lan.soft.add_install); + bt.send('InstallSoft', 'files/InstallSoft', { name: name, version: version, type: '1' }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + fly('bi-btn'); + }); + }, + un_install_php_lib: function (version, name, title, callback) { + bt.confirm({ msg: lan.soft.php_ext_uninstall_confirm.replace('{1}', name), title: lan.public_backup.uninstall + '【' + name + '】' }, function () { + name = name.toLowerCase(); + var data = 'name=' + name + '&version=' + version; + var loadT = bt.load(); + bt.send('UninstallSoft', 'files/UninstallSoft', { name: name, version: version }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }); + }, + set_upload_max: function (version, max, callback) { + var loadT = bt.load(lan.soft.the_save); + bt.send('setPHPMaxSize', 'config/setPHPMaxSize', { version: version, max: max }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + set_php_timeout: function (version, time, callback) { + var loadT = bt.load(lan.soft.the_save); + bt.send('setPHPMaxTime', 'config/setPHPMaxTime', { version: version, time: time }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + disable_functions: function (version, fs, callback) { + var loadT = bt.load(); + bt.send('setPHPDisable', 'config/setPHPDisable', { version: version, disable_functions: fs }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_fpm_config: function (version, callback) { + var loadT = bt.load(); + bt.send('getFpmConfig', 'config/getFpmConfig', { version: version }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + set_fpm_config: function (version, data, callback) { + var loadT = bt.load(); + data.version = version; + bt.send('setFpmConfig', 'config/setFpmConfig', data, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_php_status: function (version, callback) { + var loadT = bt.load(); + bt.send('GetPHPStatus', 'ajax/GetPHPStatus', { version: version }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + // 获取PHP_session + get_php_session: function (version, callback) { + var loadT = bt.load(); + bt.send('GetSessionConf', 'config/GetSessionConf', { version: version }, function (res) { + loadT.close(); + if (callback) callback(res); + }); + }, + // 设置PHP_session文件 + set_php_session: function (obj, callback) { + var loadT = bt.load(); + bt.send('SetSessionConf', 'config/SetSessionConf', obj, function (res) { + loadT.close(); + if (callback) callback(res); + }); + }, + // 获取PHP_session清理信息 + get_session_count: function (callback) { + var loadT = bt.load(); + bt.send('GetSessionCount', 'config/GetSessionCount', {}, function (res) { + loadT.close(); + if (callback) callback(res); + }); + }, + // 清理php_session + clear_session_count: function (obj, callback) { + bt.confirm({ msg: obj.msg, title: obj.title }, function () { + var loadT = bt.load(); + bt.send('DelOldSession', 'config/DelOldSession', {}, function (res) { + loadT.close(); + if (callback) callback(res); + }); + }); + }, + get_fpm_logs: function (version, callback) { + var loadT = bt.load(); + bt.send('GetFpmLogs', 'ajax/GetFpmLogs', { version: version }, function (logs) { + loadT.close(); + if (logs.status !== true) { + logs.msg = ''; + } + if (logs.msg == '') logs.msg = lan.public_backup.no_fpm_log; + if (callback) callback(logs); + }); + }, + get_slow_logs: function (version, callback) { + var loadT = bt.load(); + bt.send('GetFpmSlowLogs', 'ajax/GetFpmSlowLogs', { version: version }, function (logs) { + loadT.close(); + if (logs.status !== true) { + logs.msg = ''; + } + if (logs.msg == '') logs.msg = lan.public_backup.no_slow_log; + if (callback) callback(logs); + }); + }, + }, + redis: { + get_redis_status: function (callback) { + var loadT = bt.load(); + bt.send('GetRedisStatus', 'ajax/GetRedisStatus', {}, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + }, + pro: { + conver_unit: function (name) { + var unit = ''; + switch (name) { + case 'year': + unit = lan.public_backup.year; + break; + case 'month': + unit = lan.public_backup.month; + break; + case 'day': + unit = lan.public_backup.day; + break; + case '1month': + unit = lan.public_backup.month; + break; + case '3month': + unit = lan.public_backup.month3; + break; + case '6month': + unit = lan.public_backup.month6; + break; + case '1year': + unit = lan.public_backup.year1; + break; + case '2year': + unit = lan.public_backup.year2; + break; + case '3year': + unit = lan.public_backup.year3; + break; + case '1': + unit = lan.public_backup.month1; + break; + case '3': + unit = lan.public_backup.month3; + break; + case '6': + unit = lan.public_backup.month6; + break; + case '12': + unit = lan.public_backup.year1; + break; + case '24': + unit = lan.public_backup.year2; + break; + case '36': + unit = lan.public_backup.year3; + break; + case '999': + unit = lan.public_backup.permanent; + break; + } + return unit; + }, + get_product_discount_by: function (product_id, callback) { + if (product_id) { + bt.send('get_plugin_price', 'auth/get_plugin_price', { product_id: product_id }, function (rdata) { + if (callback) callback(rdata); + }); + } else { + bt.send('get_product_discount_by', 'auth/get_product_discount_by', {}, function (rdata) { + if (callback) callback(rdata); + }); + } + }, + get_plugin_coupon: function (pid, callback) { + bt.send('check_pay_status', 'auth/check_pay_status', { id: pid }, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_re_order_status: function (callback) { + bt.send('get_re_order_status', 'auth/get_re_order_status', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_voucher: function (pid, callback) { + if (pid) { + bt.send('get_voucher_plugin', 'auth/get_voucher_plugin', { pid: pid }, function (rdata) { + if (callback) callback(rdata); + }); + } else { + bt.send('get_voucher', 'auth/get_voucher', {}, function (rdata) { + if (callback) callback(rdata); + }); + } + }, + get_check_out_info: function (data, callback) { + bt.send('get_stripe_session_id', 'auth/get_stripe_session_id', data, function (rdata) { + if (callback) callback(rdata); + }); + }, + create_order_voucher: function (pid, code, coupon_id, cycle, cycle_unit, charge_type, callback) { + var loading = bt.load(); + if (pid) { + bt.send( + 'create_order_voucher_plugin', + 'auth/create_order_voucher_plugin', + { pid: pid, coupon_id: coupon_id, cycle: cycle, cycle_unit: cycle_unit, charge_type: charge_type }, + function (rdata) { + loading.close(); + if (callback) callback(rdata); + bt.msg(rdata); + } + ); + } else { + bt.send('create_order_voucher', 'auth/create_order_voucher', { code: code }, function (rdata) { + loading.close(); + if (callback) { + callback(rdata); + } else { + bt.soft.pro.update(); + } + }); + } + }, + create_order: function (data, callback) { + if (data.pid) { + // var loadT = bt.load("Getting product information!"); + bt.soft.get_panel_ssl_status(function (res) { + // loadT.close() + if (res.status) { + // var _cycle_unit = $('#libPay-content .li-con .active span').attr("data-unit"), + // _pay_channel = $('#libPay-mode .pay-cycle-btn span').text().indexOf('Stripe') > -1?'2':'10' + // requestNmae = data.serial_no ? 'renew_product_auth' : 'get_buy_code'; + var requestNmae = 'get_buy_code'; + // if (!data.serial_no) { + // data.charge_type = 1; + // } else { + // data.pay_channel = 2; + // } + bt.send(requestNmae, 'auth/' + requestNmae, data, function (rdata) { + // loadT.close() + if (callback) callback(rdata); + }); + } else { + $('#libPay-content').empty(); + $('.libPay-mask').hide(); + $('#libPay-pay').empty().append( + '\ +
            \ +
            Purchase on the panel:
            \ +
              \ +
            • You need to open the panel SSL
            • \ +
            \ +
            Purchase on the official website:
            \ +
              \ +
            • No need to open panel SSL
            • \ +
            • You can purchase multiple licenses at the same time and get higher discounts
            • \ +
            \ +
            \ +
            \ + \ +
            ' + ); + $('#turn_on_ssl').on('click', function (e) { + setPanelSSL(); + }); + return false; + } + }); + } else { + bt.send( + 'create_order', + 'auth/create_order', + { + cycle: data.cycle, + }, + function (rdata) { + if (callback) callback(rdata); + } + ); + } + }, + }, + updata_commercial_view: function () { + layer.closeAll(); + var html = + '
            \ +
            \ +
            Pro
            \ +
            \ +

            推荐5人以上或企事业单位购买

            \ +
            \ +
            \ +

            包含所有专业版功能和:

            \ +

            1、提供在线客服工单协助

            \ +

            2、多用户管理插件(仅可查看日志)

            \ +

            3、后期还会有10+企业版专用插件

            \ +

            4、官方跟进响应的QQ群(需年付)

            \ +

            4、官方跟进响应的QQ群(需年付)

            \ +

            5、不定期线上运维培训(需年付)

            \ +
            \ +
            \ +
            \ + \ + 148\ + /月\ +
            \ +
            \ +
            \ + \ + 999\ + /年\ +
            \ +
            \ + \ +
            \ +
            '; + layer.open({ + type: 1, + closeBtn: 2, + area: '500px', + title: lan.public_backup.up_pro_use_allplug_free, + shade: 0.6, + anim: 0, + content: html, + success: function (layero, index) { + $('.btn-price').click(function () { + var _type = $(this).attr('data-type'); + if (_type == 'pro') { + bt.soft.updata_pro(); + layer.close(index); + } else { + bt.soft.updata_ltd(); + layer.close(index); + } + }); + }, + }); + }, + get_index_renew: function () { + bt.soft.get_product_renew(function (res) { + var html = $('
            '); + if (res.length > 0) { + bt.soft.each(res, function (index, item) { + html.append( + $( + '

            ' + + item.msg + + '    [ 忽略提示 ]

            ' + ).data(item) + ); + }); + $('#messageError').show().html(html); + $('.set_messages_status').click(function () { + var data = $(this).parent().data(), + that = this; + bt.soft.set_product_renew_status({ id: data.id, state: 0 }, function (rdata) { + if (!res.status) { + $(that).parent().remove(); + } + bt.msg(rdata); + }); + }); + } + }); + }, + // 获取产品续费状态 + get_product_renew: function (callback) { + $.get('/message/get_messages', function (res) { + if (res.status === false) { + layer.msg(res.msg, { icon: 2 }); + return false; + } + if (callback) callback(res); + }); + }, + //获取产品ssl + get_panel_ssl_status: function (callback) { + $.post('/config?action=get_panel_ssl_status', function (res) { + if (callback) callback(res); + }); + }, + set_product_renew_status: function (data, callback) { + $.post('/message/status_message', { id: data.id, state: data.state }, function (res) { + if (res.status === false) { + layer.msg(res.msg, { icon: 2 }); + return false; + } + if (callback) callback(res); + }); + }, + // 产品支付视图(配置参数) + product_pay_view: function (config) { + var that = this; + + $.get('/ssl?action=GetUserInfo', function (b) { + if (!b.status) { + bt.pub.bind_btname(function () { + window.location.reload(); + }); + return; + } + if (config.renew === -1) config.renew = false; + if (typeof config == 'string') config = JSON.parse(config); + + config = $.extend( + { + plugin: null, + renew: null, + active: '', + type: '', + pro: parseInt(bt.get_cookie('pro_end')) || -1, + ltd: parseInt(bt.get_cookie('ltd_end')) || -1, + }, + config + ); + + var totalNum = config.totalNum ? config.totalNum : ''; + if (totalNum) { + bt.set_cookie('pay_source', parseInt(totalNum)); + } + + bt.open({ + type: 1, + title: false, + skin: 'libPay-view', + area: ['1050px', '700px'], + shadeClose: false, + content: + '\ +
            \ +
            \ +
            \ +
            \ +
            \ + \ +
            \ +
            \ +
            \ +
            \ +
            \ + \ +
            \ +
            \ +
            \ +
            \ +
            \ +
            \ +
            Choose your plan
            \ +
            \ +
            \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              Number of authorizations
              \ +
              \ +
                \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              \ + \ +
              \ +
              \ + \ +
              \ +
              \ + \ +
              \ +
              \ + \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              Loading, please wait!
              \ +
              \ +
              \ +
              Account:
              \ +
              --
              \ + Change\ +
              \ +
              \ + Total: \ + $0\ + , After discount \ + $0\ + /1 year\ +
              \ +
              \ + \ + Pay for subscription\ + $0/year\ + \ + $\ + 0\ + \ + /first year\ + ?\ +
              \ + \ +
              \ +
              \ +
              \ +
              \ +
              \ +
              Account:
              \ +
              --
              \ + Change\ +
              \ +
              \ + Total: \ + $0\ + , After discount \ + $0\ + /1 year\ +
              \ +
              \ +
              \ +
              \ +
              \ +
              Vouchers
              \ +
                \ +
                \ + \ +
                \ +
                \ +
                \ +
                Authorization information
                \ +
                  \ +
                  \ + \ +
                  \ +
                  \ +
                  \ +
                  \ + \ +
                  \ +
                  \ +
                  ', + end: function () { + bt.clear_cookie('pay_source'); + }, + success: function ($layer, index) { + $.getScript('https://js.stripe.com/v3/'); + + var layerThat = this; + + layerThat.renderUserInfo(); + layerThat.renderProductMenu(); + + var init = function () { + var removeLoad = layerThat.addLoading('.libPay-layer-item'); + layerThat.renderFeature(); + layerThat.renderAuthList(function (rdata) { + if (rdata.length > 0) { + cutTab('authorization'); + layerThat.renderProductPrice(function () { + removeLoad(); + }); + } else { + layerThat.renderVoucher(function (rdata) { + if (rdata.length > 0) { + cutTab('voucher'); + } + layerThat.renderProductPrice(function () { + removeLoad(); + }); + }); + } + }); + }; + + init(); + + $('.switch-cycle-right').click(function () { + var num = $(this).prev().children().length; + var totalWidth = 80; + $.each($('.pay-pro-cycle .pay-cycle-btns'), function (i, elem) { + totalWidth += elem.offsetWidth; + }); + var libPayWidht = $('.libPay-line-item').width(); + var width = totalWidth - libPayWidht + 40; + var remainder = num % 4; // 获取余数 + $(this) + .prev() + .css('transform', 'translateX(-' + remainder * width + 'px)'); + $('.switch-cycle-left').removeClass('hide'); + $('#libPay-theme-price').css('padding-left', '30px'); + $(this).addClass('hide'); + }); + + $('.switch-cycle-left').click(function (ev) { + if (bt.del_seven_coupon) { + var $children = $('.pay-pro-cycle').children(); + if ($($children[0]).data('data').nums.length == 1) { + $($children[0]).remove(); + bt.del_seven_coupon = false; + } + } + $('#libPay-theme-price').removeAttr('style'); + $('.switch-cycle-right').removeClass('hide'); + $(this).next().removeAttr('style'); + $(this).addClass('hide'); + }); + + $('.pay-btn-group').on('click', 'li', function () { + $(this).addClass('active').siblings('.active').removeClass('active'); + }); + + // 切换菜单 + $('.libPay-menu').on('click', '.libPay-menu-type', function () { + $(this).addClass('active').siblings('.active').removeClass('active'); + var data = $('.libPay-menu .libPay-menu-type.active').data(); + if (data.title !== 'PRO') { + $('.pay-pro-cycle').css('transform', 'translateX(-27px)'); + } + + layerThat.renderProductPrice(); + layerThat.renderAuthNums(); + init(); + }); + + // 产品周期切换 + $('#libPay-theme-price .pay-pro-cycle').on('click', 'li', function () { + $(this).addClass('active').siblings('.active').removeClass('active'); + layerThat.renderAuthNums(); + var numConfig = $('#authorization .auth-num .active').data(); + var condition = $('.libPay-qcode-left .pay-type-btn.active').data('condition'); + if (condition === 'stripe') { + layerThat.renderTotalPrice(numConfig.num, false); + } else if (condition === 'paypal') { + layerThat.renderPaypal(numConfig.num, false); + } + }); + + // 多台授权切换 + $('#authorization .auth-num').on('click', 'li', function () { + $(this).addClass('active').siblings('.active').removeClass('active'); + var numConfig = $('#authorization .auth-num .active').data(); + var condition = $('.libPay-qcode-left .pay-type-btn.active').data('condition'); + if (condition === 'stripe') { + layerThat.renderTotalPrice(numConfig.num, true); + } else if (condition === 'paypal') { + layerThat.renderPaypal(numConfig.num, true); + } + }); + + // 点击支付 + $('#checkout-button').click(function () { + var config = $(this).data('data'); + var loadT = bt.load('Getting the session ID,Please waiting!'); + var stripe = Stripe(config.stripe_publishable_key); + var subscribe = config.subscription_price > 0 && $('.pay-subscription input').prop('checked') ? 1 : 0; + that.pro.get_check_out_info( + { + order_no: config.order_no, + subscribe: subscribe, + }, + function (res) { + loadT.close(); + if (res.id) { + stripe.redirectToCheckout({ sessionId: res.id }); + } else { + layer.msg('Payment order failed, please contact administrator!', { icon: 2 }); + } + } + ); + }); + + // 切换tab + function cutTab(condition) { + var $el = $('.libPay-qcode-left .pay-type-btn[data-condition="' + condition + '"]'); + var index = $el.index(); + $el.addClass('active').siblings('.active').removeClass('active'); + $('.libPay-qcode-right .libPay-qcode-item').eq(index).removeClass('hide').siblings('.libPay-qcode-item').addClass('hide'); + } + + // 支付方式切换 + $('.libPay-qcode-left .pay-type-btn').click(function () { + var condition = $(this).data('condition'); + cutTab(condition); + + var condition = $(this).data('condition'); + + var numConfig = $('#authorization .auth-num .active').data(); + switch (condition) { + case 'stripe': + layerThat.renderTotalPrice(numConfig.num || 1); + break; + case 'paypal': + layerThat.renderPaypal(numConfig.num || 1); + break; + case 'voucher': + layerThat.renderVoucher(); + break; + case 'authorization': + layerThat.renderAuthList(); + break; + } + }); + + // 切换用户 + $('.libPay-qcode-item .userinfo a').click(function () { + bt.pub.bind_btname(function () { + // bt.soft.product_pay_view(config); + window.location.reload(); + }); + }); + + // 使用抵扣卷 + $('#use-voucher').click(function () { + if ($(this).hasClass('disabled')) return false; + + var data = $('.voucher-group .active').data(); + if (!data.serial_no) { + layer.msg('No vouchers'); + return false; + } + bt.soft.pro.create_order_voucher(data.pid, data.code, data.id, data.cycle, data.cycle_unit, data.charge_type, function (rdata) { + layer.closeAll(); + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + bt.msg(rdata); + if (rdata.status) { + getPaymentStatus(); + } + }); + }); + + // 授权 + $('#use-auth').click(function () { + if ($(this).hasClass('disabled')) return false; + + var _serial_no = $('.auth-group .active').attr('data-id'); + if (typeof _serial_no == 'undefined') return false; + var loadU = bt.load('Under licensing!'); + bt.send('auth_activate', 'auth/auth_activate', { serial_no: _serial_no }, function (res) { + loadU.close(); + if (res) { + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + if (res.status) { + window.location.reload(); + } + } + }); + }); + + var layerIndex = -1; + + $('.pay-subscription .bt-ico-ask').hover( + function () { + layerIndex = layer.tips('
                  when subscription creation, your authorization will auto generated
                  when subscription renewal, your authorization will auto renewed
                  ', $(this), { + tips: [1, '#999'], + time: 0, + area: '410px', + }); + }, + function () { + layer.close(layerIndex); + } + ); + }, + addLoading: function (elem) { + var $el = $(elem); + $el.children().addClass('hide'); + if ($el.children('.cloading').length === 0) { + $el.append('
                  Loading, please wait!
                  '); + } + return function removeLoad() { + $el.children('.cloading').remove(); + $el.children().removeClass('hide'); + }; + }, + getConfig: function () { + var data = $('.libPay-menu .libPay-menu-type.active').data(); + return data; + }, + // 渲染菜单 + renderProductMenu: function () { + var menus = []; + if (config.plugin) { + menus.push({ + title: config.name, + name: config.name, + ps: 'Plug-in only', + desc: config.ps, + pid: config.pid, + renew: config.renew || false, + is_pro: false, + active: (config.pro < 0 && config.ltd < 0) || (config.type == 12 && config.ltd < 0) ? true : false, + }); + } + if ( + (((config.pro > 0 || config.pro == -2 || config.ltd < 0) && ((config.ltd > 0 && config.ltd != config.pro) || config.ltd < 0) && config.type != 12) || + config.limit == 'pro' || + (config.ltd < 0 && config.pro == -1)) && + config.type != 12 && + ((config.ltd < 0 && config.pro > 0) || (config.ltd < 0 && config.pro < 0)) + ) { + menus.push({ + title: 'PRO', + name: '', + pid: '100000058', + ps: 'Recommended', + renew: config.renew || false, + is_pro: true, + active: + ((config.type == 8 && !config.plugin) || config.limit == 'pro' || config.pro > 0 || config.pro == -2) && + config.ltd < 0 && + (config.ltd == -2 ? (config.pro == -2 ? false : true) : true), + }); + } + + var $el = null; + $.each(menus, function (index, item) { + $el = $( + '
                  \ +

                  \ + ' + + (item.is_pro ? '' : '') + + '' + + item.title + + '\ +

                  \ +

                  ' + + item.ps + + '

                  \ +
                  ' + ).data(item); + $('.libPay-menu').append($el); + }); + }, + renderFeature: function () { + var config = this.getConfig(); + + if (config.is_pro) { + $('.pro-left-introduce .pro-left-title>div').html('' + config.title + ''); + $('.pro-left-introduce .pro-left-title>span').removeClass('hide').html(config.ps); + $('.pro-left-list-title').text('Pro Feature: '); + $('.pro-price-herf').removeClass('hide'); + bt.send('get_plugin_remarks', 'auth/get_plugin_remarks', { product_id: '100000058' }, function (rdata) { + var html = ''; + $.each(rdata.res, function (index, item) { + html += '
                  ' + item + '
                  '; + }); + $('.pro-left-list-content').removeAttr('style').html(html); + }); + } else { + $('.pro-left-introduce .pro-left-title>div').html('' + config.title + ''); + $('.pro-left-introduce .pro-left-title>span').addClass('hide').html(config.ps); + $('.pro-left-list-title').text('Plug-in description: '); + $('.pro-left-list-content') + .css({ + width: '186px', + 'line-height': '23px', + }) + .html(config.desc); + $('.pro-price-herf').addClass('hide'); + } + }, + + // 默认选中的套餐索引 + // defaultPackageIndex: 0, + + // // 默认选中的授权台数索引 + // defaultAuthIndex: 0, + + // 渲染产品价格 + renderProductPrice: function (callback) { + var layerThat = this; + + // 获取产品价格数据 + var config = layerThat.getConfig(); + + that.get_product_discount_cache(config, function (rdata) { + if (callback) callback(rdata); + + // 大于4个显示左右切换按钮 + var num = rdata.length; + if (num > 4) { + $('.switch-cycle-right').removeClass('hide'); + } else { + $('.switch-cycle-right,.switch-cycle-left').addClass('hide'); + } + + // 遍历渲染产品价格 + var html = ''; + var htmlNum = ''; + $('#libPay-theme-price .pay-pro-cycle').empty(); + $('#authorization .auth-num').empty(); + + that.each(rdata, function (key, item) { + const priceItem = item.children[0]; // 默认选中的授权台数索引 + var keys = item.cycle; + var unit = item.cycle_unit; + var priceByDay = (priceItem.price / ((priceItem.cycle / (priceItem.cycle_unit === 'year' ? 1 : 12)) * 365)).toFixed(2); + // 产品周期 + var cycleUnit = ''; + // 每天低至XX$ + var dayPrice = ''; + if (item.cycle === '999') { + cycleUnit = 'Lifetime'; + dayPrice = 'Best value'; + } else { + cycleUnit = item.cycle + ' ' + item.cycle_unit + (item.cycle > 1 ? 's' : ''); + dayPrice = 'As low as $' + priceByDay + '/day'; + } + + const authNum = item.children; + if (key === 0) { + that.each(authNum, function (key1, numItem) { + htmlNum = + '\ +
                • \ + ' + + numItem.num + + (key1 === 0 ? '' : numItem.discount_rate != 1 ? '' + (100 - numItem.discount_rate * 100).toFixed(2) + '% off' : '') + + '\ +
                • '; + $('#authorization .auth-num').append( + $(htmlNum).data( + $.extend( + { + pid: config.pid, + dom_index: key, + }, + numItem + ) + ) + ); + }); + } + + html = + '\ +
                • \ +
                  \ +
                  \ +
                  $' + + priceItem.price.toFixed(2) + + '
                  \ +
                  /' + + cycleUnit + + '
                  \ +
                  \ +

                  OP: $' + + priceItem.market_price + + '

                  \ +
                  \ +
                  ' + + dayPrice + + '
                  \ + ' + + // (priceItem.discount_rate != 1 ? '' + (100 - priceItem.discount_rate * 100) + '% off' : '') + + (key === 0 ? '' + 'Most popular' + '' : '') + + '\ +
                • '; + $('#libPay-theme-price .pay-pro-cycle').append( + $(html).data( + $.extend( + { + pid: config.pid, + dom_index: key, + }, + priceItem + ) + ) + ); + }); + + layerThat.renderTotalPrice(); + }); + layerThat.renderAuthNums(); + }, + + // 渲染授权数量选项 + renderAuthNums: function () { + var layerThat = this; + + // 获取产品价格数据 + var config = layerThat.getConfig(); + + var selectedCycle = $('#libPay-theme-price .pay-pro-cycle li.active').data('type'); + var selectedunit = $('#libPay-theme-price .pay-pro-cycle li.active').data('unit'); + var selectedAuthIndex = $('#authorization .auth-num li.active').index(); + that.get_product_discount_cache(config, function (rdata) { + // 查找对应周期的授权数量数据 + var authNumData = null; + for (var i = 0; i < rdata.length; i++) { + if (rdata[i].cycle == selectedCycle && rdata[i].cycle_unit == selectedunit) { + authNumData = rdata[i].children; + break; + } + } + + if (authNumData) { + // 清空授权数量选项 + $('#authorization .auth-num').empty(); + + // 遍历渲染授权数量选项 + for (var j = 0; j < authNumData.length; j++) { + var authNumItem = authNumData[j]; + + var htmlNum = + '
                • ' + + authNumItem.num + + (j === 0 ? '' : authNumItem.discount_rate !== 1 ? '' + (100 - authNumItem.discount_rate * 100).toFixed(2) + '% off' : '') + + '
                • '; + + var numItem = { + pid: config.pid, + dom_index: j, + }; + + var authNumElement = $(htmlNum).data($.extend(numItem, authNumItem)); + $('#authorization .auth-num').append(authNumElement); + } + } + }); + }, + + // 渲染用户信息 + renderUserInfo: function () { + var bt_user_info = bt.get_cookie('bt_user_info'); + if (!bt_user_info) { + bt.pub.get_user_info(function (res) { + $('.libPay-qcode-right .userinfo .info_value').html(res.data.username); + }); + } else { + $('.libPay-qcode-right .userinfo .info_value').html(JSON.parse(bt_user_info).data.username); + } + }, + renderEndTime: function () { + var endTime = null; + if (!config.is_alone) { + // 条件:当前为插件 + if (config.plugin) { + title = (!config.renew ? 'Buy ' : '续费') + config.name; + ndTime = !config.renew ? config.renew : null; + } else if (config.pro == -1 && config.ltd == -1) { + // 条件:专业版和企业版都没有购买过 + title = 'Upgrade to Pro, all plugins are free to use'; + } else if (config.ltd > 0) { + // 条件:企业版续费 + title = 'Renew ' + (config.name == '' ? '宝塔专业版' : config.name); + endTime = config.ltd; + } else if (config.pro > 0 || config.pro == -2) { + // 条件:专业版续费 + title = 'Renew ' + (config.name == '' ? '宝塔专业版' : config.name); + endTime = config.pro; + } else if (config.ltd == -2) { + title = 'Renew ' + (config.name == '' ? '宝塔专业版' : config.name); + endTime = config.ltd; + } + } else { + title = (config.ltd > 0 ? '续费' : '购买') + '宝塔企业版'; + } + if (endTime != null) { + $('.endTime span').html(endTime > parseInt(new Date().getTime() / 1000) ? bt.format_data(endTime) : 'Expired'); + } else { + $('.endTime').hide(); + } + }, + // 渲染总价格 + renderTotalPrice: function (num, isNum) { + var condition = $('.libPay-qcode-left .pay-type-btn.active').data('condition'); + if (condition !== 'stripe') { + return; + } + + var layerThat = this; + var removeLoad = layerThat.addLoading('.libPay-qcode-item:not(.hide)'); + + var numActive = $('#authorization .auth-num .active').data(); + var priceActive = $('#libPay-theme-price .pay-pro-cycle li.active').data(); + + var config = null; + if (isNum === true) { + config = $('#authorization .auth-num .active').data(); + } else { + config = $('#libPay-theme-price .pay-pro-cycle .active').data(); + } + config = priceActive; + config.num = numActive; + + var param = { pid: config.pid, cycle: config.cycle, cycle_unit: config.cycle_unit, charge_type: config.charge_type, num: num || 1 }; + param.source = parseInt(bt.get_cookie('pay_source') || 0); + if (param.source === 0) { + if ($('.btpro-gray').length == 1) { + // 是否免费版 + param.source = 27; + } else { + param.source = 28; + } + } + if (!param.pid) delete param.pid; + if (bt.get_cookie('serial_no') != null) param.serial_no = config.serial_no || bt.get_cookie('serial_no'); + that.pro.create_order(param, function (rdata) { + removeLoad(); + if (rdata.status === false) { + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + layer.msg(rdata.msg, { icon: 2 }); + return; + } + + var cycle = ''; + if (config.cycle === 999) { + cycle = 'Lifetime'; + } else { + cycle = config.cycle + ' ' + config.cycle_unit + (config.cycle > 1 ? 's' : ''); + } + // var unit = config.num + ' unit' + (config.num > 1 ? 's' : ''); + // 改成接口请求后获取到的价格 rdata + $('.libPayTotal').html('$' + rdata.price); // + $('.pay-price .org_price').text('$' + rdata.market_price + '/' + cycle); // + $('.pay-price .libPayCycle').html('/' + cycle); + + if (rdata.subscription_price > 0) { + // + $('.pay-subscription').removeClass('hide'); + if (config.first_subscription_price > 0) { + $('.pay-subscription .org-price').removeClass('hide'); + // 改成接口请求后获取到的价格 rdata + $('.pay-subscription .org-price').text('$' + rdata.subscription_price + '/' + config.cycle_unit); // + $('.pay-subscription .first-price .num').text(config.first_subscription_price); // + $('.pay-subscription .cycle-unit').text('/first ' + config.cycle_unit); // + } else { + $('.pay-subscription .org-price').addClass('hide'); + $('.pay-subscription .first-price .num').text(rdata.subscription_price); // + $('.pay-subscription .cycle-unit').text('/' + config.cycle_unit); // + } + } else { + $('.pay-subscription').addClass('hide'); + } + + $('#checkout-button').data('data', rdata); + }); + }, + + // 渲染paypal + renderPaypal: function (num, isNum) { + var layerThat = this; + + var removeLoad = layerThat.addLoading('.libPay-qcode-item:not(.hide)'); + // var config = $('#libPay-theme-price .pay-pro-cycle .active').data(); + + var numActive = $('#authorization .auth-num .active').data(); + var priceActive = $('#libPay-theme-price .pay-pro-cycle li.active').data(); + + var config = null; + if (isNum === true) { + config = $('#authorization .auth-num .active').data(); + } else { + config = $('#libPay-theme-price .pay-pro-cycle .active').data(); + } + config = priceActive; + config.num = numActive; + + var param = { pid: config.pid, cycle: config.cycle, cycle_unit: config.cycle_unit, charge_type: config.charge_type, num: num || 1 }; + param.source = parseInt(bt.get_cookie('pay_source') || 0); + if (param.source === 0) { + if ($('.btpro-gray').length == 1) { + // 是否免费版 + param.source = 27; + } else { + param.source = 28; + } + } + if (!param.pid) delete param.pid; + if (bt.get_cookie('serial_no') != null) param.serial_no = config.serial_no || bt.get_cookie('serial_no'); + that.pro.create_order(param, function (rdata) { + removeLoad(); + + if (rdata.status === false) { + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + layer.msg(rdata.msg, { icon: 2 }); + return; + } + + var cycle = ''; + if (config.cycle === 999) { + cycle = 'Lifetime'; + } else { + cycle = config.cycle + ' ' + config.cycle_unit + (config.cycle > 1 ? 's' : ''); + } + // var unit = config.num + ' unit' + (config.num > 1 ? 's' : ''); + $('.libPayTotal').html('$' + rdata.price); + $('.pay-price .org_price').text('$' + rdata.market_price + '/' + cycle); + $('.pay-price .libPayCycle').html('/' + cycle); + + layerThat.renderPaypalBtn(rdata); + }); + }, + renderPaypalBtn: function (rdata) { + $('#paypal-button-container').empty(); + + jQuery + .ajax({ + url: '/static/js/polyfill.min.js?v=1715756461542', + dataType: 'script', + cache: true, + }) + .done(function () { + var httpPromise = function (config) { + return new Promise((resolve, reject) => { + $.ajax({ + type: 'POST', + url: config.url, + data: config.data, + success: function (res) { + resolve(res); + }, + error: function (err) { + reject(err); + }, + }); + }); + }; + var check_response = function (response) { + return new Promise((resolve, reject) => { + if (response.status) { + return resolve(response.res); + } + bt.msg(response); + reject(response.res); + }); + }; + jQuery + .ajax({ + url: 'https://www.paypal.com/sdk/js?client-id=' + rdata.paypal_client_id, + dataType: 'script', + cache: true, + }) + .done(function () { + var buttons = paypal.Buttons({ + // 只渲染paypal支付按钮 + fundingSource: paypal.FUNDING.PAYPAL, + // 绑定PayPal订单创建事件处理函数,请求paypal订单创建接口 + // 注意: 该函数必须返回Promise对象, 若使用 JQuery.ajax() 函数 + // 请使用 new Promise(fn(resolve(), reject())) 创建Promise对象并将其返回 + // Promise必须返回从接口获取的id + createOrder: () => { + return httpPromise({ + url: '/auth?action=get_paypal_session_id', + data: { oid: rdata.order_id }, + }) + .then(check_response) + .then(res => res); + }, + // 绑定支付确认事件,请求paypal订单支付确认接口并传递orderId + // 注意: 该函数必须返回Promise对象,若使用 JQuery.ajax() 函数 请参照 step3 + // Promise最终请展示支付结果 + onApprove: data => { + return ( + httpPromise({ + url: '/auth?action=check_paypal_status', + data: { paypal_order_id: data.orderID }, + }) + // res是支付成功跳转的url + .then(check_response) + .then(res => { + layer.msg('Payment successful', { icon: 1 }); + setTimeout(() => { + location.reload(); + }, 1500); + }) + ); + }, + }); + buttons.render('#paypal-button-container'); + }); + }); + }, + // 渲染抵扣卷 + renderVoucher: function (callback) { + var layerThat = this; + var config = layerThat.getConfig(); + var removeLoad = layerThat.addLoading('.libPay-qcode-item:not(.hide)'); + bt.soft.pro.get_voucher(config.pid, function (rdata) { + removeLoad(); + + if (callback) callback(rdata); + + $('.voucher-group').empty(); + if (rdata == null && !Array.isArray(rdata)) rdata = []; + if (rdata.length == 0) { + $('.voucher-group').addClass('hide'); + $('#use-voucher').addClass('disabled').text('No vouchers'); + return; + } + + var $li = null; + that.each(rdata, function (index, item) { + var name = item.cycle_unit == 'month' && item.cycle == 999 ? 'Lifetime' : item.cycle + that.pro.conver_unit(item.cycle_unit); + $li = $('
                • ' + name + '
                • ').data($.extend({ pid: config.pid }, item)); + $('.voucher-group').append($li); + }); + + $('.voucher-group').removeClass('hide'); + $('#use-voucher').removeClass('disabled').text('Pay'); + }); + }, + // 渲染授权列表 + renderAuthList: function (callback) { + var layerThat = this; + var config = layerThat.getConfig(); + var removeLoad = layerThat.addLoading('.libPay-qcode-item:not(.hide)'); + bt.send('get_product_auth', 'auth/get_product_auth', { page: 1, pageSize: 15, pid: config.pid }, function (rdata) { + removeLoad(); + + if (callback) callback(rdata); + + $('.auth-group').empty(); + if (rdata == null && !Array.isArray(rdata)) rdata = []; + if (rdata.length == 0) { + $('.auth-group').addClass('hide'); + $('#use-auth').addClass('disabled').text('No authorization'); + return; + } + + var html = ''; + $.each(rdata, function (index, item) { + if (config.pid == item.product_id) { + html += + '
                • ' + + (item.cycle === 999 ? 'Lifetime' : bt.format_data(item.end_time)) + + '
                • '; + } + }); + $('.auth-group').append(html); + $('.auth-group').removeClass('hide'); + $('#use-auth').removeClass('disabled').text('Authorization'); + }); + }, + }); + }); + // if (!bt.get_cookie('bt_user_info')) { + // bt.pub.bind_btname(function () { + // window.location.reload(); + // }); + // return false; + // } + }, + product_cache: {}, //产品周期缓存 + order_cache: {}, + // 获取产品周期 ,并进行对象缓存 + get_product_discount_cache: function (config, callback) { + var that = this; + if (typeof this.product_cache[config.pid] != 'undefined') { + if (callback) callback(this.product_cache[config.pid]); + } else { + bt.soft.pro.get_product_discount_by(config.pid, function (rdata) { + //rdata = {"36": {"discount": 1, "did": 0, "price": 3564, "name": "正常", "sprice": 3564}, "24": {"discount": 1, "did": 0, "price": 2376, "name": "正常", "sprice": 2376}, "12": {"discount": 1, "did": 0, "price": 1188, "name": "正常", "sprice": 1188}, "6": {"discount": 1, "did": 0, "price": 594, "name": "正常", "sprice": 594}, "3": {"discount": 1, "did": 0, "price": 297, "name": "正常", "sprice": 297}, "1": {"discount": 1, "did": 0, "price": 99, "name": "正常", "sprice": 99}, "pid": "100000045"}; + if (typeof rdata.status === 'boolean') { + if (!rdata.status) { + bt.msg(rdata); + return false; + } + } + that.product_cache[config.pid] = rdata; + setTimeout(function () { + delete that.product_cache[config.pid]; + }, 60000); + if (callback) callback(rdata); + }); + } + }, + // 产品页面刷新 + product_pay_page_refresh: function (config) { + var that = this; + var condition = config.condition; + switch (condition) { + case 1: + var loadT = bt.load(); + bt.send('get_product_auth', 'auth/get_product_auth', { page: 1, pageSize: 15, pid: config.pid }, function (res) { + bt.soft.pro.get_voucher(config.pid, function (rdata) { + loadT.close(); + var _arry = [ + { title: '微信支付', condition: 2 }, + { title: 'Stripe', condition: 3 }, + { title: 'voucher', condition: 4 }, + { title: 'Authorization', condition: 7, _pid: config.pid }, + ]; + // if (config.renew) { + // _arry.splice(_arry.length - 1, 1); + // _arry[rdata.length > 0 ? '2' : '1'].active = true; + // config.condition = rdata.length > 0 ? 4 : 2; + // } else { + _arry[res.length > 0 ? '3' : rdata.length > 0 ? '2' : '1'].active = true; + config.condition = res.length > 0 ? 7 : rdata.length > 0 ? 4 : 3; + // } + if (res == null) res = []; + if (rdata == null) rdata = []; + $('#libPay-mode .li-con') + .empty() + .append( + that.product_pay_swicth('payment', { + name: config.name, + pid: config.pid, + data: _arry, + voucher_data: rdata, + }) + ); + if (config.renew) { + if (rdata.length > 0) config.voucher_data = rdata; + } else { + if (rdata.length > 0) config.voucher_data = rdata; + if (res.length > 0) config.voucher_data = res; + } + that.product_pay_page_refresh(config); + }); + }); + break; + case 2: + case 3: + $('#libPay-content .li-tit').text('Choose your plan'); + config.pay = condition; + if (config.pid == '100000030') { + $('#libPay-tips').show(); + } else { + $('#libPay-tips').hide(); + } + var loadT = bt.load(); + bt.soft.get_product_discount_cache(config, function (rdata) { + loadT.close(); + var _arry = []; + var index = 0; + try { + delete rdata.pid; + } catch (error) { + console.log(rdata.pid); + } + that.each(rdata, function (key, item) { + _arry.push($.extend({ cycle: parseInt(key) }, item)); + }); + _arry[index].active = true; + $('#libPay-content .li-con') + .empty() + .append( + that.product_pay_swicth('time', { + name: config.name, + pid: config.pid, + data: _arry, + }) + ); + config.condition = 5; + config = $.extend(config, _arry[index]); + that.product_pay_page_refresh(config); + }); + break; + case 4: + var loadP = bt.load('Getting deduction volume information!'); + clearInterval(bt.soft.pub.wxpayTimeId); + $('#libPay-content').empty().append('
                  '); + $('#libPay-pay').empty(); + $('#libPay-content .li-tit').text('Vouchers'); + $('#libPay-pay').removeAttr('data-qecode'); + $('#libPay-tips').hide(); + + function callback(rdata) { + loadP.close(); + if (rdata == null && !Array.isArray(rdata)) rdata = []; + if (rdata.length == 0) { + $('#libPay-content .li-con').empty(); + that.product_pay_page_refresh({ condition: 6, pid: '', code: false }); + return false; + } + rdata[0].active = true; + $('#libPay-content .li-con').append( + that.product_pay_swicth('voucher', { + name: config.name, + pid: config.pid, + data: rdata, + }) + ); + config.condition = 6; + that.product_pay_page_refresh($.extend(config, rdata[0])); + } + if (config.voucher_data) { + callback(config.voucher_data); + } else { + bt.soft.pro.get_voucher(config.pid, function (rdata) { + callback(rdata); + }); + } + break; + case 5: + $('#libPay-content .li-con').css('height', 'auto'); + $('.libPay-mask').show(); + if ($('#libPay-pay').attr('data-qecode')) { + var qcode = $('#libPay-content li').eq(config.dom_index).data('qrcode-url'); + $('#libPay-pay').find('.sale-price').html(config.price.toFixed(2)); + $('#libPay-pay') + .find('.cost-price') + .css('display', config.sprice > config.price ? 'inline-block' : 'none') + .html('$ ' + config.sprice.toFixed(2)); + $('#libPay-pay').find('#PayQcode').html('
                  Loading, please wait!
                  '); + if (qcode) { + $('#libPay-pay').find('#PayQcode').empty().qrcode(qcode); + that.product_pay_monitor({ pid: config.pid, name: config.name }); + $('.libPay-mask').hide(); + return false; + } + } else { + $('#libPay-pay').html('
                  Loading, please wait!
                  '); + } + var paream = { pid: config.pid, cycle: config.cycle }; + paream.source = parseInt(bt.get_cookie('pay_source') || 0); + console.log(paream.source); + if (paream.source === 0) { + if ($('.btpro-gray').length == 1) { + // 是否免费版 + paream.source = 27; + } else { + paream.source = 28; + } + } + if (!paream.pid) delete paream.pid; + if (bt.get_cookie('serial_no') != null) paream.serial_no = config.serial_no || bt.get_cookie('serial_no'); + that.pro.create_order(paream, function (rdata) { + if (rdata.status === false) { + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + layer.msg(rdata.msg, { icon: 2 }); + return; + } + config.pay = parseInt($('#libPay-mode .pay-cycle-btn.active').data('condition')); + // 二维码显示界面 + $('#libPay-pay') + .empty() + .append(that.product_pay_swicth(config.pay == 2 ? 'wechat' : 'alipay', $.extend({ order_no: rdata.order_no, stripe_publishable_key: rdata.stripe_publishable_key }, config))); + }); + $('#libPay-pay').on('click', '#checkout-button', function () { + var loadT = bt.load('Getting the session ID,Please waiting!'); + var stripe = Stripe($(this).data('keys')); + that.pro.get_check_out_info($(this).data('code'), function (res) { + loadT.close(); + if (res.id) { + stripe.redirectToCheckout({ sessionId: res.id }); + } else { + layer.msg('Payment order failed, please contact administrator!', { icon: 2 }); + } + }); + }); + break; + case 6: + var _html = $('
                  '); + var _button = $( + '' + ); + _button.click(function (ev) { + if (!config.serial_no) { + layer.msg('No vouchers'); + return false; + } + bt.soft.pro.create_order_voucher(config.pid, config.code, config.id, config.cycle, config.cycle_unit, config.charge_type, function (rdata) { + layer.closeAll(); + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + bt.msg(rdata.res); + }); + }); + $('#libPay-pay').empty().append(_html.append(_button)); + break; + case 7: + if (config.renew) return false; + var loadA = bt.load('Obtaining authorization information!'); + $('#libPay-content').empty().append('
                  '); + $('#libPay-pay').empty(); + var _pid = config.pid; + $('#libPay-content .li-tit').text('Authorization information'); + bt.send('get_product_auth', 'auth/get_product_auth', { page: 1, pageSize: 15, pid: config.pid }, function (res) { + loadA.close(); + if (res.status == false) { + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + return false; + } + _html = $('
                    '); + if (res.length == 0) { + _html.append($('
                  • No authorization
                  • ')); + } + var authorization_flag = false; + $.each(res, function (index, item) { + if (_pid == item.product_id) { + _html.append( + $('
                  • ' + bt.format_data(item.end_time) + '
                  • ') + ); + authorization_flag = true; + } + if (authorization_flag == false && index == res.length - 1) { + _html.append($('
                  • No authorization
                  • ')); + } + }); + $('#libPay-content .li-con').empty().append(_html); + $('#libPay-content ul li').click(function () { + $(this).addClass('active').siblings().removeClass('active'); + }); + $('#libPay-pay') + .empty() + .append($('
                    ')); + //授权 + $('#authorization').unbind(); + var html = $('#libPay-content .li-con ul li span').html() || ''; + if (html.indexOf('No authorization') > 0) { + $('#authorization').attr('disabled', 'disabled'); + } else { + $('#authorization').removeAttr('disabled'); + $('#authorization').on('click', function (e) { + var _serial_no = $('#libPay-content .active').attr('data-id'); + if (typeof _serial_no == 'undefined') return false; + var loadU = bt.load('Under licensing!'); + bt.send('auth_activate', 'auth/auth_activate', { serial_no: _serial_no }, function (res) { + loadU.close(); + if (res) { + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + if (res.status) { + window.location.reload(); + } + } + }); + }); + } + }); + break; + } + }, + // 产品购买,渲染方法 + product_pay_swicth: function (type, config) { + var _html = '', + that = this; + switch (type) { + case 'type': // 产品类型(配置参数) + _html = $('
                      '); + this.each(config, function (index, item) { + _html.append( + $( + '
                    • ' + + (item.recommend ? '' : '') + + '' + + item.title + + '' + + '' + + item.ps + + '' + + '
                    • ' + ) + .data(item) + .click(function (ev) { + var data = $(this).data(); + if (!$(this).hasClass('active')) that.product_pay_page_refresh($.extend({ condition: 1 }, data)); + $(this).addClass('active').siblings().removeClass('active'); + }) + ); + }); + break; + case 'payment': // 产品付款方式 + _html = $('
                        '); + this.each(config.data, function (index, item) { + if (item.title != '微信支付') { + _html.append( + $('
                      • ' + item.title + '
                      • ') + .data($.extend({ pid: config.pid, name: config.name }, item)) + .click(function (ev) { + var data = $(this).data(); + if (!$(this).hasClass('active')) that.product_pay_page_refresh($.extend({ condition: $(this).attr('data-condition') }, data)); + $(this).addClass('active').siblings().removeClass('active'); + }) + ); + } + }); + break; + case 'time': // 产品开通时长(配置参数) + _html = $('
                          '); + this.each(config.data, function (index, item) { + _html.append( + $( + '
                        • ' + + that.pro.conver_unit(item.cycle + item.cycle_unit) + + '' + + (item.discount_rate != 1 ? '' + (100 - item.discount_rate * 100) + '% off' : '') + + '
                        • ' + ) + .data( + $.extend( + { + pid: config.pid, + dom_index: index, + }, + item + ) + ) + .click(function (ev) { + var data = $(this).data(); + if (!$(this).hasClass('active')) that.product_pay_page_refresh($.extend({ condition: 5 }, data)); + $(this).addClass('active').siblings().removeClass('active'); + }) + ); + }); + break; + case 'voucher': // 产品抵扣卷(配置参数) + _html = $('
                            '); + + this.each(config.data, function (index, item) { + _html.append( + $( + '
                          • ' + + (item.cycle_unit == 'month' && item.cycle == 999 ? '永久' : item.cycle + that.pro.conver_unit(item.cycle_unit)) + + '
                          • ' + ) + .data($.extend({ pid: config.pid }, item)) + .click(function (ev) { + var data = $(this).data(); + $(this).addClass('active').siblings().removeClass('active'); + that.product_pay_page_refresh($.extend({ condition: 6 }, data)); + }) + ); + }); + break; + case 'wechat': + case 'alipay': + _html = $( + '
                            ' + + 'Total' + + '$' + + config.price.toFixed(2) + + '' + + '$ ' + + config.market_price.toFixed(2) + + '
                            ' + + '
                            ' + + '' + ); + // $(_html).find('#PayQcode').qrcode(config.data); + $('.libPay-mask').hide(); + // that.product_pay_monitor({ pid: config.pid, name: config.name }); + break; + } + return _html; + }, + + // 支付状态监听 + product_pay_monitor: function (config) { + var that = this; + function callback(rdata) { + if (rdata.status) { + clearInterval(bt.soft.pub.wxpayTimeId); + layer.closeAll(); + var title = ''; + if (config.pid == 100000032 || config.pid === '') { + title = config.pid === '' ? '专业版支付成功!' : '企业版支付成功!'; + setTimeout(function () { + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + location.reload(true); + }, 2000); // 需要重服务端重新获取软件列表,并刷新软件管理浏览器页面 + } else { + title = config.name + '插件支付成功!'; + setTimeout(function () { + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + location.reload(true); + }, 2000); // 需要重服务端重新获取软件列表, + } + bt.msg({ msg: title, icon: 1, shade: [0.3, '#000'] }); + } + } + clearInterval(bt.soft.pub.wxpayTimeId); + function intervalFun() { + if (config.pid) { + that.pro.get_plugin_coupon(config.pid, callback); + } else { + that.pro.get_re_order_status(callback); + } + } + intervalFun(); + bt.soft.pub.wxpayTimeId = setInterval(function () { + intervalFun(); + }, 2500); + }, + updata_ltd: function (is_alone) { + var param = { name: '宝塔面板企业版', pid: 100000032, limit: 'ltd' }; + if (is_alone || false) $.extend(param, { source: 5, is_alone: true }); + bt.soft.product_pay_view(param); + }, + //遍历数组和对象 + each: function (obj, fn) { + var key, + that = this; + if (typeof fn !== 'function') return that; + obj = obj || []; + if (obj.constructor === Object) { + for (key in obj) { + if (fn.call(obj[key], key, obj[key])) break; + } + } else { + for (key = 0; key < obj.length; key++) { + if (fn.call(obj[key], key, obj[key])) break; + } + } + return that; + }, + /** + * @description 升级专业版 + */ + updata_pro: function (num) { + var param = { + name: 'Pro', + pid: 100000058, + limit: 'pro', + }; + if (num) param['totalNum'] = num; + bt.set_cookie('pay_source', num); + bt.soft.product_pay_view(param); + }, + /** + * @description 续费专业版 + */ + renew_pro: function () { + var config = { name: 'Pro', pid: '100000058', limit: 'pro', renew: true }; + if (!bt.get_cookie('serial_no')) delete config.renew; + bt.soft.product_pay_view(config); + }, + // updata_pro: function() { + // bt.pub.get_user_info(function(rdata) { + // if (!rdata.status) { + // bt.pub.bind_btname(0, function(rdata) { + // if (rdata.status) bt.soft.updata_pro(); + // }) + // return; + // } + // var payhtml = '
                            \ + //
                            \ + //
                            \ + //

                            ' + lan.public_backup.buy_multiplev_bt_pro + '' + lan.public_backup.goto_bt + '

                            \ + //
                            '; + + // bt.open({ + // type: 1, + // title: lan.public_backup.up_pro_use_allplug_free, + // area: ['616px', '540px'], + // closeBtn: 2, + // shadeClose: false, + // content: payhtml + // }); + // setTimeout(function() { + // bt.soft.get_product_discount('', 0); + // $(".pay-btn-group > li").click(function() { + // $(this).addClass("active").siblings().removeClass("active"); + // }); + // }, 100) + // }) + // }, + re_plugin_pay: function (pluginName, pid, type) { + bt.pub.get_user_info(function (rdata) { + if (!rdata.status) { + bt.pub.bind_btname(0, function (rdata) { + if (rdata.status) bt.soft.re_plugin_pay(pluginName, pid, type); + }); + return; + } + var txt = lan.public_backup.buy; + if (type) txt = lan.public_backup.renew; + var payhtml = + '
                            \ +
                            \ +
                            ' + + lan.public_backup.type + + '
                            \ +
                            \ +
                              \ +
                            • ' + + pluginName + + '' + + lan.public_backup.apiece_of_plug + + '
                            • \ +
                            • ' + + lan.public_backup.up_pro + + '' + + lan.public_backup.use_allplug_free + + '
                            • \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            '; + + layer.open({ + type: 1, + title: txt + pluginName, + area: ['616px', '680px'], + closeBtn: 2, + shadeClose: false, + content: payhtml, + }); + setTimeout(function () { + bt.soft.get_product_discount(pluginName, pid); + $('.li-c-item li').click(function () { + var i = $(this).index(); + $(this).addClass('active').siblings().removeClass('active'); + if (i == 0) { + bt.soft.get_product_discount(pluginName, pid); + $('.pro-info').hide(); + } else { + bt.soft.get_product_discount('', 0); + $('.pro-info').show(); + } + }); + $('.pay-btn-group > li').click(function () { + $(this).addClass('active').siblings().removeClass('active'); + }); + }, 100); + }); + }, + + re_plugin_pay_other: function (pluginName, pid, type, price) { + bt.pub.get_user_info(function (rdata) { + if (!rdata.status) { + bt.pub.bind_btname(0, function (rdata) {}); + return; + } + var txt = lan.public_backup.buy; + if (type) txt = lan.public_backup.renew; + var payhtml = + '
                            \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            ' + + txt + + lan.public_backup.duration + + '
                            \ +
                              \ +
                            • ' + + lan.public_backup.month1 + + '
                            • \ +
                            • ' + + lan.public_backup.month3 + + '
                            • \ +
                            • ' + + lan.public_backup.month6 + + '
                            • \ +
                            • ' + + lan.public_backup.year + + '
                            • \ +
                            \ +
                            \ +
                            ' + + lan.public_backup.total + + '' + + lan.public_backup.rmb + + '
                            \ +
                            \ +
                            \ +
                            ' + + lan.public_backup.pay_by_wechatqrcore + + '
                            \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            '; + + layer.open({ + type: 1, + title: txt + pluginName, + area: ['616px', '450px'], + closeBtn: 2, + shadeClose: false, + content: payhtml, + }); + bt.soft.get_rscode_other(pid, price, 1, type); + setTimeout(function () { + $('.pay-btn-group > li').click(function () { + $(this).addClass('active').siblings().removeClass('active'); + }); + }, 100); + }); + }, + get_rscode_other: function (pid, price, cycle, type) { + var loadT = layer.msg(lan.public_backup.get_payment_info, { icon: 16, time: 0, shade: 0.3 }); + $.post('/auth?action=create_plugin_other_order', { pid: pid, cycle: cycle, type: type }, function (rdata) { + layer.close(loadT); + if (!rdata.status) { + layer.closeAll(); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + return; + } + + if (!rdata.msg.code) { + layer.closeAll(); + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + soft.flush_cache(); + return; + } + $('.sale-price').text((price * cycle).toFixed(2)); + $('.pay-wx').html(''); + $('.pay-wx').qrcode(rdata.msg.code); + bt.set_cookie('other_oid', rdata.msg.oid); + bt.soft.get_order_stat(rdata.msg.oid, type); + }); + }, + get_order_stat: function (order_id, type) { + if (bt.get_cookie('other_oid') != order_id) return; + setTimeout(function () { + $.post('/auth?action=get_order_stat', { oid: order_id, type: type }, function (stat) { + if (stat == 1) { + layer.closeAll(); + soft.flush_cache(); + return; + } + + if ($('.pay-btn-group').length > 0) { + bt.soft.get_order_stat(order_id, type); + } + }); + }, 1000); + }, + get_voucher_list: function (pid) { + $('#couponlist').html("
                            " + lan.public_backup.loading + '
                            '); + bt.soft.pro.get_voucher(pid, function (rdata) { + if (rdata != null && rdata.length > 0) { + var con = ''; + var len = rdata.length; + for (var i = 0; i < len; i++) { + if (rdata[i].status != 1) { + var cyc = rdata[i].cycle + bt.soft.pro.conver_unit(rdata[i].unit); + if (rdata[i].cycle == 999) { + cyc = lan.public_backup.permanent; + } + con += '
                          • ' + cyc + '
                          • '; + } + } + $('#couponlist').html('
                              ' + con + '
                            '); + $('.pay-btn-group > li').click(function () { + $(this).addClass('active').siblings().removeClass('active'); + $('.paymethod-submit button').css({ 'background-color': '#20a53a', 'border-color': '#20a53a' }); + }); + $('.paymethod-submit button').click(function () { + var _active = $('#couponlist .pay-btn-group .active'), + code = _active.attr('data-code'), + coupon_id = _active.attr('data-coupon-id'), + charge_type = _active.attr('data-charge-type'); + + var _span = $('#couponlist .pay-btn-group .active span'), + cycle = parseInt(_span.html()), + cycle_unit = _span.html.indexOf('Month') ? 'month' : 'year'; + if (code == undefined) { + layer.msg(lan.public_backup.choose_cash_coupon); + } else { + bt.soft.pro.create_order_voucher(pid, code, coupon_id, cycle, cycle_unit, charge_type, function (rdata) { + layer.closeAll(); + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + bt.msg(rdata.res); + }); + } + }); + } else { + $('#couponlist').html("

                            " + lan.public_backup.no_cash_coupon + '

                            '); + } + }); + }, + get_rscode: function (pid, price, sprice, cycle) { + $('.sale-price').text(price); + if (price == sprice) { + $('.cost-price') + .text(sprice + lan.public_backup.rmb) + .hide(); + } else { + $('.cost-price') + .text(sprice + lan.public_backup.rmb) + .show(); + } + $('.pay-wx').html('' + lan.public_backup.loading + ''); + $('.libPay').append('
                            '); + bt.soft.pro.create_order(pid, cycle, function (rdata) { + $('.payloadingmask').remove(); + if (rdata.status === false) { + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + layer.msg(rdata.msg, { icon: 2 }); + return; + } + $('.pay-wx').html(''); + $('.pay-wx').qrcode(rdata.msg); + clearInterval(bt.soft.pub.wxpayTimeId); + if (pid) { + bt.soft.pub.wxpayTimeId = setInterval(function () { + bt.soft.pro.get_plugin_coupon(pid, function (rdata) { + if (rdata.status) { + layer.closeAll(); + clearInterval(bt.soft.pub.wxpayTimeId); + bt.msg({ msg: lan.public_backup.pay_plug_success, icon: 16, time: 0, shade: [0.3, '#000'] }); + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + return; + } + }); + }, 3000); + } else { + bt.soft.pub.wxpayTimeId = setInterval(function () { + bt.soft.pro.get_re_order_status(function (rdata) { + if (rdata.status) { + layer.closeAll(); + clearInterval(bt.soft.pub.wxpayTimeId); + bt.msg({ msg: lan.public_backup.pay_pro_success, icon: 16, time: 0, shade: [0.3, '#000'] }); + bt.set_cookie('force', 1); + if (soft) soft.flush_cache(); + return; + } + }); + }, 3000); + } + }); + }, + get_product_discount: function (pluginName, pid) { + if (pluginName == undefined) pluginName = ''; + if (pid == undefined) pid = 0; + var con = + '
                            \ +
                            ' + + lan.public_backup.pay_method + + '
                            \ +
                            • ' + + lan.public_backup.pay_by_wechat + + '
                            • ' + + lan.public_backup.cash_coupon + + '
                            \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            ' + + lan.public_backup.opening_time + + '
                            \ +
                            \ +
                            \ +
                            ' + + lan.public_backup.total + + '' + + lan.public_backup.rmb + + '
                            \ +
                            \ +
                            \ +
                            ' + + lan.public_backup.pay_by_wechatqrcore + + '
                            \ +
                            \ +
                            \ + \ +
                            '; + $('.libpay-con').html("
                            " + lan.public_backup.loading + '
                            '); + + bt.soft.pro.get_product_discount_by(pluginName, function (rdata) { + if (rdata != null) { + var coucon = ''; + var qarr = Object.keys(rdata); + var qlen = qarr.length; + if (pluginName) qlen = qlen - 1; + //折扣列表 + for (var i = 0; i < qlen; i++) { + var j = qarr[i]; + var a = rdata[j].price.toFixed(2); + var b = rdata[j].sprice.toFixed(2); + var c = rdata[j].discount; + coucon += + '
                          • ' + + bt.soft.pro.conver_unit(j) + + '' + + (c == 1 ? '' : '' + c * 10 + lan.public_backup.discount + '') + + '
                          • '; + } + $('.libpay-con').html(con); + $('#PayCycle').html('
                              ' + coucon + '
                            '); + $('.pay-btn-group li').click(function () { + $(this).addClass('active').siblings().removeClass('active'); + }); + $('.pay-cycle li').click(function () { + var i = $(this).index(); + $(this).addClass('active').siblings().removeClass('active'); + $('.payment-con > div').eq(i).show().siblings().hide(); + }); + $('#PayCycle .pay-btn-group li').eq(0).click(); + } + }); + }, + get_index_list: function (callback) { + bt.send('get_index_list', 'plugin/get_index_list', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_sort_index: function (data, callback) { + var loading = bt.load(); + bt.send('sort_index', 'plugin/sort_index', { ssort: data }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_soft_list: function (p, type, search, callback) { + if (p == undefined) p = 1; + if (type == undefined) type = 0; + if (search == undefined) search = ''; + var force = bt.get_cookie('force'); + if (force == undefined) force = 0; + p = p + ''; + if (p.indexOf('not_load') == -1) { + var loading = bt.load(lan.public.the, 1); + } else { + var loading = null; + p = p.split('not_load')[0]; + } + + bt.send('get_soft_list', 'plugin/get_soft_list', { p: p, type: type, tojs: 'soft.get_list', force: force, query: search }, function (rdata) { + if (loading) loading.close(); + bt.set_cookie('force', 0); + if (rdata.pro_authorization_sn != null) { + bt.set_cookie('serial_no', rdata.pro_authorization_sn); + } else { + bt.clear_cookie('serial_no'); + } + bt.set_cookie('pro_end', rdata.pro); + if (callback) callback(rdata); + }); + }, + to_index: function (name, callback) { + var status = $('#index_' + name).prop('checked') ? '0' : '1'; + if (name.indexOf('php-') >= 0) { + var verinfo = name.replace(/\./, ''); + status = $('#index_' + verinfo).prop('checked') ? '0' : '1'; + } + if (status == 1) { + bt.send('add_index', 'plugin/add_index', { sName: name }, function (rdata) { + rdata.time = 1000; + if (!rdata.status) bt.msg(rdata); + if (callback) callback(rdata); + }); + } else { + bt.send('remove_index', 'plugin/remove_index', { sName: name }, function (rdata) { + rdata.time = 1000; + if (!rdata.status) bt.msg(rdata); + if (callback) callback(rdata); + }); + } + }, + add_make_args: function (name, init) { + name = bt.soft.get_name(name); + pdata = { + name: name, + args_name: $("input[name='make_name']").val(), + init: init, + ps: $("input[name='make_ps']").val(), + args: $("input[name='make_args']").val(), + }; + if (pdata.args_name.length < 1 || pdata.args.length < 1) { + layer.msg('Custom module name and parameter cannot be empty!'); + return; + } + loadT = bt.load('Adding custom module...'); + bt.send('add_make_args', 'plugin/add_make_args', pdata, function (rdata) { + loadT.close(); + bt.soft.get_make_args(name); + bt.msg(rdata); + if (rdata.status === true) bt.soft.loadOpen.close(); + }); + }, + show_make_args: function (name) { + name = bt.soft.get_name(name); + var _aceEditor = ''; + bt.soft.loadOpen = bt.open({ + type: 1, + title: 'Add custom module', + area: '500px', + btn: [lan.public.submit, lan.public.close], + content: + '
                            \ + \ +
                            \ + Name\ +
                            \ + \ +
                            \ +
                            \ +
                            \ + Details\ +
                            \ + \ +
                            \ +
                            \ +
                            \ + Parameter\ +
                            \ + \ +
                            \ +
                            \ +
                            \ + Prefix script\ +
                            \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            ', + success: function (layer, index) { + _aceEditor = ace.edit('preposition_shell', { + theme: 'ace/theme/chrome', //主题 + mode: 'ace/mode/sh', // 语言类型 + wrap: true, + showInvisibles: false, + showPrintMargin: false, + showFoldWidgets: false, + useSoftTabs: true, + tabSize: 2, + showPrintMargin: false, + readOnly: false, + }); + _aceEditor.setValue('# The shell script content executed before compilation is usually prepared for the dependent installation and source download of the third-party module'); + }, + yes: function () { + bt.soft.add_make_args(name, _aceEditor.getValue()); + }, + }); + }, + modify_make_args: function (name, args_name) { + name = bt.soft.get_name(name); + var _aceEditor = ''; + bt.soft.loadOpen = bt.open({ + type: 1, + title: 'Edit custom option module[' + name + ':' + args_name + ']', + area: '500px', + btn: [lan.public.submit, lan.public.close], + content: + '
                            \ + \ +
                            \ + Module name\ +
                            \ + \ +
                            \ +
                            \ +
                            \ + Module details\ +
                            \ + \ +
                            \ +
                            \ +
                            \ + Module parameter\ +
                            \ + \ +
                            \ +
                            \ +
                            \ + Prefix script\ +
                            \ +
                            \ +
                            \ +
                            \ +
                            \ +
                            ', + success: function (layer, index) { + _aceEditor = ace.edit('preposition_shell', { + theme: 'ace/theme/chrome', //主题 + mode: 'ace/mode/sh', // 语言类型 + wrap: true, + showInvisibles: false, + showPrintMargin: false, + showFoldWidgets: false, + useSoftTabs: true, + tabSize: 2, + showPrintMargin: false, + readOnly: false, + }); + _aceEditor.setValue(bt.soft.make_data[args_name].init); + }, + yes: function () { + bt.soft.add_make_args(name, _aceEditor.getValue()); + }, + }); + }, + set_make_args: function (_this, name, args_name) { + name = bt.soft.get_name(name); + if ($('.args_' + args_name)[0].checked) { + bt.soft.make_config.push(args_name); + } else { + index = bt.soft.make_config.indexOf(args_name); + if (index === -1) return; + bt.soft.make_config.splice(index, 1); + } + index = bt.soft.make_config.indexOf(''); + if (index !== -1) bt.soft.make_config.splice(index, 1); + bt.send('set_make_args', 'plugin/set_make_args', { name: name, args_names: bt.soft.make_config.join('\n') }, function (rdata) { + if (!rdata.status) { + bt.msg(rdata); + } + }); + }, + //遍历数组和对象 + each: function (obj, fn) { + var key, + that = this; + if (typeof fn !== 'function') return that; + obj = obj || []; + if (obj.constructor === Object) { + for (key in obj) { + if (fn.call(obj[key], key, obj[key])) break; + } + } else { + for (key = 0; key < obj.length; key++) { + if (fn.call(obj[key], key, obj[key])) break; + } + } + return that; + }, + del_make_args: function (name, args_name) { + name = bt.soft.get_name(name); + bt.confirm({ msg: 'Confirm delete[' + name + ':' + args_name + ']module?', title: 'Delete[' + name + ':' + args_name + ']module!' }, function () { + loadT = bt.load('Removing module[' + args_name + ']...'); + bt.send('del_make_args', 'plugin/del_make_args', { name: name, args_name: args_name }, function (rdata) { + bt.soft.get_make_args(name); + bt.msg(rdata); + }); + }); + }, + get_make_args: function (name) { + name = bt.soft.get_name(name); + loadT = bt.load('Getting optional modules...'); + bt.send('get_make_args', 'plugin/get_make_args', { name: name }, function (rdata) { + loadT.close(); + var module_html = ''; + bt.soft.make_config = rdata.config.split('\n'); + bt.soft.make_data = {}; + for (var i = 0; i < rdata.args.length; i++) { + bt.soft.make_data[rdata.args[i].name] = rdata.args[i]; + var checked_str = bt.soft.make_config.indexOf(rdata.args[i].name) == -1 ? '' : 'checked="checked"'; + module_html += + '\ + \ + \ + \ + ' + + rdata.args[i].name + + '' + + rdata.args[i].ps + + '\ + \ + Edit\ + | Del\ + \ + '; + } + $('.modules_list').html(module_html); + }); + }, + check_make_is: function (name) { + name = bt.soft.get_name(name); + var shows = ['nginx', 'apache', 'mysql', 'php']; + for (var i = 0; i < shows.length; i++) { + if (name.indexOf(shows[i]) === 0) { + return true; + } + } + return false; + }, + get_name: function (name) { + if (name.indexOf('php-') === 0) { + return 'php'; + } + return name; + }, + install: function (name, that) { + var _this = this; + if (bt.soft.is_install) { + layer.msg('Installing other software, please operate later!', { icon: 0 }); + return false; + } + _this.get_soft_find(name, function (rdata) { + var arrs = ['apache', 'nginx', 'mysql']; + if ($.inArray(name, arrs) >= 0 || name.indexOf('php-') >= 0) { + var SelectVersion = '', + shtml = name; + if (rdata.versions.length > 1) { + for (var i = 0; i < rdata.versions.length; i++) { + var item = rdata.versions[i]; + SelectVersion += ''; + } + shtml = "'; + } else { + shtml = "" + name + ''; + } + var loadOpen = bt.open({ + type: 1, + title: name + lan.soft.install_title, + area: '400px', + content: + "
                            \ +
                            " + + lan.soft.install_version + + ':' + + shtml + + "
                            \ +
                            " + + lan.bt.install_type + + ":
                            \ + \ +
                            \ + \ + \ +
                            \ +
                            ', + success: function ($layer, index) { + $layer.find('.btn-close').click(function () { + layer.close(index); + }); + }, + }); + + $('.fangshi input').click(function () { + $(this).attr('checked', 'checked').parent().siblings().find('input').removeAttr('checked'); + var type = $('.fangshi input:eq(0)').prop('checked') ? '0' : '1'; + if (type === '1') { + $('.install_modules').hide(); + return; + } + + if (bt.soft.check_make_is(name)) { + $('.install_modules').show(); + bt.soft.get_make_args(name); + } + }); + + $('#bi-btn').click(function () { + loadOpen.close(); + var info = $('#SelectVersion').val().toLowerCase(); + name = info.split(' ')[0]; + version = info.split(' ')[1]; + var type = $('.fangshi input:eq(0)').prop('checked') ? '0' : '1'; + if (rdata.versions.length > 1) { + _this.install_soft(rdata, version, type); + } else { + _this.install_soft(rdata, rdata.versions[0].m_version, type, that); + } + }); + } else if (rdata.versions.length > 1) { + var SelectVersion = ''; + for (var i = 0; i < rdata.versions.length; i++) { + var item = rdata.versions[i]; + var v_type = parseInt(item.beta) === 1 ? ' Beta' : ' Stable'; + // SelectVersion += ''; + var version = parseInt(rdata.type) === 5 ? item.m_version : item.full_version; + SelectVersion += ''; + } + var loadOpen = bt.open({ + type: 1, + title: name + lan.soft.install_title, + area: '350px', + content: + "
                            \ +
                            " + + lan.soft.install_version + + ":
                            \ +
                            \ + \ + \ +
                            \ +
                            ', + }); + $('#bi-btn').click(function () { + loadOpen.close(); + var info = $('#SelectVersion').val().toLowerCase(); + name = info.split(' ')[0]; + version = info.split(' ')[1]; + _this.install_soft(rdata, version, 0, that); + }); + } else { + // _this.install_soft(rdata, rdata.versions[0].full_version, 0, that); + _this.install_soft(rdata, parseInt(rdata.type) == 5 ? rdata.versions[0].m_version : rdata.versions[0].full_version, 0, that); + } + }); + }, + is_loop_speed: true, + is_install: false, + //显示进度 + show_speed: function () { + bt.send( + 'get_lines', + 'ajax/get_lines', + { + num: 10, + filename: '/tmp/panelShell.pl', + }, + function (rdata) { + if ($('#install_show').length < 1) return; + if (rdata.status === true) { + $('#install_show').text(rdata.msg); + $('#install_show').scrollTop(1000000000); + } + setTimeout(function () { + bt.soft.show_speed(); + }, 1000); + } + ); + }, + loadT: null, + speed_msg: "
                            [MSG]
                            ", + //显示进度窗口 + // show_speed_window: function(msg, callback) { + // bt.soft.loadT = layer.open({ + // title: false, + // type: 1, + // closeBtn: 0, + // shade: 0.3, + // area: "500px", + // offset: "30%", + // content: bt.soft.speed_msg.replace('[MSG]', msg), + // success: function(layers, index) { + // setTimeout(function() { + // bt.soft.show_speed(); + // }, 1000); + // if (callback) callback(); + // } + // }); + // }, + show_speed_window: function (config, callback) { + if (!config.soft) config['soft'] = { type: 10 }; + if (config.soft.type == 5) { + //使用消息盒子安装 + if (callback) callback(); + return false; + } else if (config.soft.type == 10 && !config.status) { + //第三方安装, 非安装,仅下载安装脚本 + if (callback) callback(); + return false; + } + layer.closeAll(); + bt.soft.loadT = layer.open({ + title: config.title || 'Executing setup script, please wait...', + type: 1, + closeBtn: false, + maxmin: true, + shade: false, + skin: 'install_soft', + area: ['500px', '300px'], + content: + "
                            " +
                            +				config.msg +
                            +				'
                            ', + success: function (layers, index) { + if (typeof config.event === 'string') { + $(config.event).removeAttr('onclick').html('Installing'); + } + $('.layui-layer-max').hide(); + bt.soft.is_loop_speed = true; + bt.soft.is_install = true; + bt.soft.show_speed(); + if (callback) callback(); + }, + end: function () { + bt.soft.is_install = false; + bt.soft.is_loop_speed = false; + }, + min: function () { + $('.layui-layer-max').show(); + }, + restore: function () { + $('.layui-layer-max').hide(); + }, + }); + }, + // install_soft: function(item, version, type,that) { //安装单版本 + // if (type == undefined) type = 0; + // item.title = bt.replace_all(item.title, '-' + version, ''); + // var msg = item.type != 5 ? lan.soft.lib_insatll_confirm.replace('{1}', item.title) : lan.get('install_confirm', [item.title, version]); + + // bt.confirm({ msg: '
                            '+msg+'
                            ', title: item.type != 5 ? lan.soft.lib_install : lan.soft.install_title }, function() { + // bt.soft.show_speed_window(lan.soft.lib_install_the, function() { + // bt.send('install_plugin', 'plugin/install_plugin', { sName: item.name, version: version, type: type }, function(rdata) { + + // if (rdata.size) { + // layer.close(bt.soft.loadT); + // _this.install_other(rdata) + // return; + // } + // layer.close(bt.soft.loadT); + // bt.pub.get_task_count(); + // if (soft) soft.get_list(); + // bt.msg(rdata); + // }) + // }) + // }) + // }, + install_soft: function (item, version, type, that) { + //安装单版本 + if (type == undefined) type = 0; + var loadT = ''; + item.title = bt.replace_all(item.title, '-' + version, ''); + layer.confirm( + item.type != 5 ? lan.soft.lib_insatll_confirm.replace('{1}', item.title) : lan.get('install_confirm', [item.title, version]), + { + btn: [lan.public.confirm, lan.public.close], + title: item.type != 5 ? lan.soft.lib_install : lan.soft.install_title, + icon: 0, + closeBtn: 2, + }, + function () { + layer.closeAll(); + bt.soft.show_speed_window( + { + title: 'Installing ' + item.title + ', please wait...', + msg: lan.soft.lib_install_the, + soft: item, + event: that, + }, + function () { + if (item.type == 10) + loadT = layer.msg('Getting third party installation information, please wait', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + bt.send( + 'install_plugin', + 'plugin/install_plugin', + { + sName: item.name, + version: version, + type: type, + }, + function (rdata) { + if (rdata.size) { + layer.close(loadT); + bt.soft.install_other(rdata, status, that); + return; + } + layer.close(bt.soft.loadT); + bt.pub.get_task_count(function (rdata) { + if (rdata > 0 && item.type === 5) messagebox(); + }); + if (!rdata.status) { + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + } else { + if (window.location.pathname != '/soft') { + window.location.reload(); + } + } + setTimeout(function () { + if (typeof soft != 'undefined') soft.get_list(); + }, 2000); + } + ); + } + ); + } + ); + }, + install_other: function (data, status, callback) { + layer.closeAll(); + var loadT = layer.open({ + type: 1, + area: '500px', + title: (data.update ? lan.public_backup.update : lan.public_backup.install) + lan.public_backup.third_party_plug, + closeBtn: 2, + shift: 5, + shadeClose: false, + btn: [data.update ? lan.public_backup.update : lan.public_backup.install, lan.public_backup.cancel], + content: + '\ +
                            \ + \ +
                              \ + ' + + (data.update ? '
                            • ' + lan.public_backup.update_wait + '
                            • ' : '
                            • ' + lan.public_backup.install_wait + '
                            • ' + lan.public_backup.exist_cover + '
                            • ') + + '\ +
                            \ +
                            ', + yes: function (index, event) { + soft.input_zip(data.name, data.tmp_path, data, callback); + }, + }); + }, + update_soft: function (name, title, version, min_version, update_msg, type) { + var _this = this; + var msg = '
                          • ' + lan.public_backup.update_tips + '
                          • '; + if (name == 'mysql') + msg = "
                            • " + lan.public_backup.db_update_tips + '
                            • ' + lan.public_backup.update_tips1 + '
                            • ' + lan.public_backup.update_tips + '
                            '; + if (update_msg) + msg += + '
                            Update description:
                            ' +
                            +				update_msg.replace(/(_bt_)/g, '\n') +
                            +				'

                            '; + bt.show_confirm( + lan.public_backup.update + '[' + title + ']', + lan.public_backup.update_tips2.replace('{1}', title).replace('{2}', version).replace('{3}', min_version), + function () { + // bt.soft.show_speed_window('Updating to [' + title + '-' + version + '.' + min_version + '],Please wait...', function() { + // bt.send('install_plugin', 'plugin/install_plugin', { sName: name, version: version, upgrade: version }, function(rdata) { + // if (rdata.size) { + // _this.install_other(rdata) + // return; + // } + // layer.close(bt.soft.loadT); + // bt.pub.get_task_count(function(rdata){ + // if(rdata > 0 && item.type === 5) messagebox(); + // }); + // if (soft) soft.get_list(); + // if (rdata.status === true && rdata.msg.indexOf('queue') === -1) rdata.msg = 'Update completed!'; + // bt.msg(rdata); + // }) + // }) + // bt.soft.show_speed_window({ title: 'Updating to [' + title + '-' + version + '.' + min_version + '],Please wait...', status: true, soft: { type: parseInt(type) } }, function () { + // bt.send('install_plugin', 'plugin/install_plugin', { sName: name, version: version + '.' + min_version, upgrade: version + '.' + min_version }, function (rdata) { + // console.log(rdata); + // if (rdata.size) { + // _this.install_other(rdata); + // return; + // } + // layer.close(bt.soft.loadT); + // bt.pub.get_task_count(function (rdata) { + // if (rdata > 0 && item.type === 5) messagebox(); + // }); + // if (typeof soft != 'undefined') soft.get_list(); + // bt.msg(rdata); + // }); + // }); + _this.get_soft_find(name, function (item) { + var full_version = parseInt(item.type) === 5 ? version : version + '.' + min_version; + bt.soft.show_speed_window({ title: 'Updating to [' + title + '-' + version + '.' + min_version + '],Please wait...', status: true, soft: { type: parseInt(type) } }, function () { + bt.send('install_plugin', 'plugin/install_plugin', { sName: name, version: full_version, upgrade: full_version }, function (rdata) { + console.log(rdata); + if (rdata.size) { + _this.install_other(rdata); + return; + } + layer.close(bt.soft.loadT); + bt.pub.get_task_count(function (rdata) { + if (rdata > 0 && parseInt(item.type) === 5) messagebox(); + }); + if (typeof soft != 'undefined') soft.get_list(); + bt.msg(rdata); + }); + }); + }); + }, + msg + ); + }, + un_install: function (name) { + var _this = this; + _this.get_soft_find(name, function (item) { + var version = ''; + for (var i = 0; i < item.versions.length; i++) { + if (item.versions[i].setup && bt.contains(item.version, item.versions[i].m_version)) { + version = item.versions[i].m_version; + if (version.indexOf('.') < 0) version += '.' + item.versions[i].version; + break; + } + } + var title = bt.replace_all(item.title, '-' + version, ''); + bt.confirm({ msg: lan.soft.uninstall_confirm.replace('{1}', title).replace('{2}', version), title: lan.soft.uninstall, icon: 3, closeBtn: 2 }, function () { + var loadT = bt.load(lan.soft.lib_uninstall_the); + bt.send('uninstall_plugin', 'plugin/uninstall_plugin', { sName: name, version: version }, function (rdata) { + loadT.close(); + bt.pub.get_task_count(); + if (soft) soft.get_list(); + bt.msg(rdata); + }); + }); + }); + }, + get_soft_find: function (name, callback) { + var loadT = bt.load(); + bt.send('get_soft_find', 'plugin/get_soft_find', { sName: name }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_config_path: function (name) { + var fileName = ''; + if (bt.os == 'Linux') { + switch (name) { + case 'mysql': + case 'mysqld': + fileName = '/etc/my.cnf'; + break; + case 'nginx': + fileName = '/www/server/nginx/conf/nginx.conf'; + break; + case 'pureftpd': + fileName = '/www/server/pure-ftpd/etc/pure-ftpd.conf'; + break; + case 'apache': + fileName = '/www/server/apache/conf/httpd.conf'; + break; + case 'tomcat': + fileName = '/www/server/tomcat/conf/server.xml'; + break; + case 'memcached': + fileName = '/etc/init.d/memcached'; + break; + case 'redis': + fileName = '/www/server/redis/redis.conf'; + break; + case 'openlitespeed': + fileName = '/usr/local/lsws/conf/httpd_config.conf'; + break; + default: + fileName = '/www/server/php/' + name + '/etc/php.ini'; + break; + } + } + return fileName; + }, + set_lib_config: function (name, title) { + var loadT = bt.load(lan.soft.menu_temp); + bt.send('getConfigHtml', 'plugin/getConfigHtml', { name: name }, function (rhtml) { + loadT.close(); + if (rhtml.status === false) { + if (name == 'phpguard') { + layer.msg(lan.soft.menu_phpsafe, { icon: 1 }); + } else { + layer.msg(rhtml.msg, { icon: 2 }); + } + return; + } + bt.open({ + type: 1, + shift: 5, + offset: '20%', + closeBtn: 2, + area: '700px', + title: '' + title, + content: rhtml.replace('"javascript/text"', '"text/javascript"'), + }); + /*rtmp = rhtml.split('',''); + setTimeout(function(){ + if(!!(window.attachEvent && !window.opera)){ + execScript(rcode); + }else{ + window.eval(rcode); + } + },200)*/ + }); + }, + save_config: function (fileName, data) { + var encoding = 'utf-8'; + var loadT = bt.load(lan.soft.the_save); + bt.send('SaveFileBody', 'files/SaveFileBody', { data: data, path: fileName, encoding: encoding }, function (rdata) { + loadT.close(); + bt.msg(rdata); + }); + }, +}; + +bt.database = { + get_list: function (page, search, callback) { + if (page == undefined) page = 1; + search = search == undefined ? '' : search; + var order = bt.get_cookie('order') ? '&order=' + bt.get_cookie('order') : ''; + + var data = 'tojs=database.get_list&table=databases&limit=15&p=' + page + '&search=' + search + order; + bt.pub.get_data(data, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_root_pass: function (callback) { + bt.send('getKey', 'data/getKey', { table: 'config', key: 'mysql_root', id: 1 }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_root: function (type) { + if (type == 'mongo' || type == 'pgsql') { + var t = bt.data.database.getType(); + bt_tools.send('database/' + t + '/get_root_pwd', function (rdata) { + if (type == 'pgsql') bt.data.database.mongo['list'][0]['title'] = lan.database.admin_password; + var bs = bt.render_form(bt.data.database.mongo); + $('.password' + bs).val(rdata.msg); + }); + } else { + bt.database.get_root_pass(function (rdata) { + var bs = bt.render_form(bt.data.database.root); + $('.password' + bs).val(rdata); + }); + } + }, + set_data_pass: function (callback) { + var bs = bt.render_form(bt.data.database.data_pass, function (rdata) { + if (callback) callback(rdata); + }); + return bs; + }, + set_data_access: function (name) { + var loading = bt.load(); + bt.send('GetDatabaseAccess', 'database/GetDatabaseAccess', { name: name }, function (rdata) { + loading.close(); + var bs = bt.render_form(bt.data.database.data_access); + $('.name' + bs).val(name); + $('.bt-form .line .tname').css('width', '125px'); + setTimeout(function () { + if (rdata.msg.permission == '127.0.0.1' || rdata.msg.permission == '%') { + $('.dataAccess' + bs).val(rdata.msg.permission); + } else { + $('.dataAccess' + bs) + .val('ip') + .trigger('change'); + $('#dataAccess_subid').val(rdata.msg.permission); + } + $('#force_ssl').prop('checked', rdata.msg.ssl ? true : false); + $('#force_ssl').change(function () { + var open_type = $('#force_ssl').prop('checked'); + if (open_type) { + var t = + '
                            \ +

                            Warning! This feature requires Advanced Knowledge!

                            \ +
                              \ +
                            • After enabling the forced SSL connection, it may affect your application connection and database performance.
                            • \ +
                            \ +
                            '; + var loadP = layer.confirm( + t, + { + btn: ['Confirm', 'Cancel'], + icon: 3, + area: '561px', + closeBtn: 2, + title: 'Confirm Open?', + }, + function () { + $('#force_ssl').prop('checked', true); + layer.close(loadP); + }, + function () { + $('#force_ssl').prop('checked', false); + layer.close(loadP); + } + ); + } + }); + }, 100); + }); + }, + add_database: function (cloudList, callback) { + var type = bt.data.database.getType(); + if (type === 'mysql') { + bt.data.database.data_add.list[2].items[0].value = bt.get_random(16); + bt.data.database.data_add.list[4].items[0].items = cloudList; + bt.render_form(bt.data.database.data_add, function (rdata) { + if (callback) callback(rdata); + }); + } else { + var copyDataAdd = $.extend(true, {}, bt.data.database.data_add); + copyDataAdd.list[2].items[0].value = bt.get_random(16); + switch (type) { + case 'sqlserver': + case 'mongodb': + case 'pgsql': + delete copyDataAdd.list[0].items[1]; + copyDataAdd.list.splice(3); + copyDataAdd.list.push(bt.data.database.data_add.list[4]); + copyDataAdd.list.push(bt.data.database.data_add.list[5]); + copyDataAdd.list[3].items[0].items = cloudList; + break; + } + // 没有本地或者远程数据库 + if (cloudList.length == 0) { + copyDataAdd.list[copyDataAdd.list.length - 1].hide = true; + } + bt.render_form($.extend(true, {}, copyDataAdd), function (rdata) { + if (callback) callback(rdata); + }); + } + }, + del_database: function (data, callback) { + var loadT = bt.load(lan.get('del_all_task_the', [data.name])); + var type = bt.data.database.getType(); + var params = { url: 'database?action=DeleteDatabase', data: data }; + if (type != 'mysql') { + params.url = 'database/' + type + '/DeleteDatabase'; + params.data = { data: JSON.stringify(data) }; + } + bt_tools.send(params, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + sync_database: function (sid, callback) { + var loadT = bt.load(lan.database.sync_the); + var type = bt.data.database.getType(); + var params = { url: 'database?action=SyncGetDatabases', data: { sid: sid } }; + if (type != 'mysql') { + params.url = 'database/' + type + '/SyncGetDatabases'; + params.data = { data: JSON.stringify({ sid: sid }) }; + } + bt_tools.send(params, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + sync_to_database: function (data, callback) { + var loadT = bt.load(lan.database.sync_the); + var type = bt.data.database.getType(); + var params = { url: 'database?action=SyncToDatabases', data: data }; + if (type != 'mysql') { + params.url = 'database/' + type + '/SyncToDatabases'; + params.data = { data: JSON.stringify(data) }; + } + bt_tools.send(params, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }, + open_phpmyadmin: function (name, username, password) { + if ($('#toPHPMyAdmin').attr('action').indexOf('phpmyadmin') == -1) { + layer.msg(lan.database.phpmyadmin_err, { icon: 2, shade: [0.3, '#000'] }); + setTimeout(function () { + window.location.href = '/soft'; + }, 3000); + return; + } + $('#toPHPMyAdmin').attr('action', $('#toPHPMyAdmin').attr('public-data')); + var murl = $('#toPHPMyAdmin').attr('action'); + $('#pma_username').val(username); + $('#pma_password').val(password); + $('#db').val(name); + layer.msg(lan.database.phpmyadmin, { icon: 16, shade: [0.3, '#000'], time: 1000 }); + setTimeout(function () { + $('#toPHPMyAdmin').submit(); + layer.closeAll(); + }, 200); + }, + submit_phpmyadmin: function (name, username, password, pub) { + if (pub === true) { + $('#toPHPMyAdmin').attr('action', $('#toPHPMyAdmin').attr('public-data')); + } else { + $('#toPHPMyAdmin').attr('action', '/phpmyadmin/index.php'); + } + var murl = $('#toPHPMyAdmin').attr('action'); + $('#pma_username').val(username); + $('#pma_password').val(password); + $('#db').val(name); + layer.msg(lan.database.phpmyadmin, { icon: 16, shade: [0.3, '#000'], time: 1000 }); + setTimeout(function () { + $('#toPHPMyAdmin').submit(); + layer.closeAll(); + }, 200); + }, + input_sql: function (fileName, dataName) { + bt.confirm({ msg: lan.database.input_confirm, title: lan.database.input_title }, function (index) { + var loading = bt.load(lan.database.input_the); + var type = bt.data.database.getType(); + var params = { url: 'database?action=InputSql', data: { file: fileName, name: dataName } }; + if (type != 'mysql') { + params.url = 'database/' + type + '/InputSql'; + params.data = { data: JSON.stringify({ file: fileName, name: dataName }) }; + } + bt_tools.send(params, function (rdata) { + loading.close(); + bt.msg(rdata); + }); + }); + }, + backup_data: function (id, callback) { + var loadT = bt.load(lan.database.backup_the); + var type = bt.data.database.getType(); + var params = { url: 'database?action=ToBackup', data: { id: id } }; + if (type != 'mysql') { + params.url = 'database/' + type + '/ToBackup'; + params.data = { data: JSON.stringify({ id: id }) }; + } + bt_tools.send(params, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + del_backup: function (id, success, error) { + bt.confirm({ msg: lan.database.backup_del_confirm, title: lan.database.backup_del_title }, function (index) { + var loadT = bt.load(); + bt.send('DelBackup', 'database/DelBackup', { id: id }, function (frdata) { + loadT.close(); + bt.msg(frdata); + if (frdata.status) { + success && success(frdata); + } else { + error && error(frdata); + } + }); + }); + }, +}; + +bt.send('get_config', 'config/get_config', {}, function (rdata) { + bt.config = rdata; +}); + +bt.plugin = { + get_plugin_byhtml: function (name, callback) { + bt.send('getConfigHtml', 'plugin/getConfigHtml', { name: name }, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_firewall_state: function (callback) { + var typename = getCookie('serverType'); + var name = 'btwaf_httpd'; + if (typename == 'nginx') name = 'btwaf'; + bt.send('a', 'plugin/a', { name: name, s: 'get_total_all' }, function (rdata) { + if (callback) callback(rdata); + }); + }, +}; + +bt.site = { + get_list: function (page, search, type, callback) { + if (page == undefined) page = 1; + type = type == undefined ? '&type=-1' : '&type=' + type; + search = search == undefined ? '' : search; + var order = bt.get_cookie('order') ? '&order=' + bt.get_cookie('order') : ''; + var data = 'tojs=site.get_list&table=sites&limit=15&p=' + page + '&search=' + search + order + type; + bt.pub.get_data(data, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_domains: function (id, callback) { + var data = 'table=domain&list=True&search=' + id; + bt.pub.get_data( + data, + function (rdata) { + if (callback) callback(rdata); + }, + 1 + ); + }, + get_type: function (callback) { + bt.send('get_site_types', 'site/get_site_types', '', function (rdata) { + if (callback) callback(rdata); + }); + }, + add_type: function (name, callback) { + bt.send('add_site_type', 'site/add_site_type', { name: name }, function (rdata) { + if (callback) callback(rdata); + }); + }, + edit_type: function (data, callback) { + bt.send('modify_site_type_name', 'site/modify_site_type_name', { id: data.id, name: data.name }, function (rdata) { + if (callback) callback(rdata); + }); + }, + del_type: function (id, callback) { + bt.send('remove_site_type', 'site/remove_site_type', { id: id }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_site_type: function (data, callback) { + bt.send('set_site_type', 'site/set_site_type', { id: data.id, site_ids: data.site_array }, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_site_domains: function (id, callback) { + var loading = bt.load(); + bt.send('GetSiteDomains', 'site/GetSiteDomains', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + add_domains: function (id, webname, domains, callback) { + var loading = bt.load(); + bt.send('AddDomain', 'site/AddDomain', { domain: domains, webname: webname, id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }, + del_domain: function (siteId, siteName, domain, port, callback) { + var loading = bt.load(); + bt.send('DelDomain', 'site/DelDomain', { id: siteId, webname: siteName, domain: domain, port: port }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }, + get_dirbind: function (id, callback) { + var loading = bt.load(); + bt.send('GetDirBinding', 'site/GetDirBinding', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + add_dirbind: function (id, domain, dirName, callback) { + var loading = bt.load(); + bt.send('AddDirBinding', 'site/AddDirBinding', { id: id, domain: domain, dirName: dirName }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + del_dirbind: function (id, callback) { + var loading = bt.load(); + bt.send('DelDirBinding', 'site/DelDirBinding', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_dir_rewrite: function (data, callback) { + var loading = bt.load(); + bt.send('GetDirRewrite', 'site/GetDirRewrite', data, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_site_path: function (id, callback) { + bt.send('getKey', 'data/getKey', { table: 'sites', key: 'path', id: id }, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_dir_userini: function (id, path, callback) { + bt.send('GetDirUserINI', 'site/GetDirUserINI', { id: id, path: path }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_dir_userini: function (path, id, callback) { + var loading = bt.load(); + bt.send('SetDirUserINI', 'site/SetDirUserINI', { path: path, id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_logs_status: function (id, callback) { + var loading = bt.load(); + bt.send('logsOpen', 'site/logsOpen', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_site_runpath: function (id, path, callback) { + var loading = bt.load(); + bt.send('SetSiteRunPath', 'site/SetSiteRunPath', { id: id, runPath: path }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_site_path: function (id, path, callback) { + var loading = bt.load(); + bt.send('SetPath', 'site/SetPath', { id: id, path: path }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_site_path_new: function (id, path, name, callback) { + var loading = bt.load(); + bt.send('SetPath', 'site/SetPath', { id: id, path: path, name: name }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_site_pwd: function (id, username, password, callback) { + var loading = bt.load(); + bt.send('SetHasPwd', 'site/SetHasPwd', { id: id, username: username, password: password }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + close_site_pwd: function (id, callback) { + var loading = bt.load(); + bt.send('SetHasPwd', 'site/CloseHasPwd', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_limitnet: function (id, callback) { + bt.send('GetLimitNet', 'site/GetLimitNet', { id: id }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_limitnet: function (id, perserver, perip, limit_rate, callback) { + var loading = bt.load(); + bt.send('SetLimitNet', 'site/SetLimitNet', { id: id, perserver: perserver, perip: perip, limit_rate: limit_rate }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + close_limitnet: function (id, callback) { + var loading = bt.load(); + bt.send('CloseLimitNet', 'site/CloseLimitNet', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_rewrite_list: function (siteName, callback) { + bt.send('GetRewriteList', 'site/GetRewriteList', { siteName: siteName }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_rewrite_tel: function (name, data, callback) { + var loading = bt.load(lan.site.saving_txt); + bt.send('SetRewriteTel', 'site/SetRewriteTel', { name: name, data: data }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_index: function (id, callback) { + bt.send('GetIndex', 'site/GetIndex', { id: id }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_index: function (id, index, callback) { + var loading = bt.load(); + bt.send('SetIndex', 'site/SetIndex', { id: id, Index: index }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_site_config: function (siteName, callback) { + if (bt.os == 'Linux') { + var sPath = '/www/server/panel/vhost/' + bt.get_cookie('serverType') + '/' + siteName + '.conf'; + bt.files.get_file_body(sPath, function (rdata) { + if (callback) callback(rdata); + }); + } + }, + set_site_config: function (siteName, data, encoding, callback) { + var loading = bt.load(lan.site.saving_txt); + if (bt.os == 'Linux') { + var sPath = '/www/server/panel/vhost/' + bt.get_cookie('serverType') + '/' + siteName + '.conf'; + bt.files.set_file_body(sPath, data, 'utf-8', function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + } + }, + set_phpversion: function (siteName, version, other, callback) { + var loading = bt.load(); + bt.send('SetPHPVersion', 'site/SetPHPVersion', { siteName: siteName, version: version, other: other }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + // 重定向列表 + get_redirect_list: function (name, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('GetRedirectList', 'site/GetRedirectList', { sitename: name }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + // 重定向列表 + get_redirect_list: function (name, callback) { + var loadT = layer.load(); + bt.send('GetRedirectList', 'site/GetRedirectList', { sitename: name }, function (rdata) { + layer.close(loadT); + if (callback) callback(rdata); + }); + }, + create_redirect: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('CreateRedirect', 'site/CreateRedirect', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + modify_redirect: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('ModifyRedirect', 'site/ModifyRedirect', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + remove_redirect: function (sitename, redirectname, callback) { + bt.show_confirm(lan.public_backup.del_rep + '[' + redirectname + ']', lan.public_backup.sure_del_rep, function () { + var loadT = bt.load(lan.site.the_msg); + bt.send('DeleteRedirect', 'site/DeleteRedirect', { sitename: sitename, redirectname: redirectname }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }); + }, + get_redirect_config: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('GetRedirectFile', 'site/GetRedirectFile', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + save_redirect_config: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('SaveProxyFile', 'site/SaveRedirectFile', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_site_proxy: function (siteName, callback) { + bt.send('GetProxy', 'site/GetProxy', { name: siteName }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_site_proxy: function (siteName, type, proxyUrl, toDomain, sub1, sub2, callback) { + var loading = bt.load(); + bt.send('SetProxy', 'site/SetProxy', { name: siteName, type: type, proxyUrl: proxyUrl, toDomain: toDomain, sub1: sub1, sub2: sub2 }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_open_proxy_cache: function (siteName, callback) { + var loading = bt.load(); + bt.send('ProxyCache', 'site/ProxyCache', { siteName: siteName }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_proxy_list: function (name, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('GetProxyList', 'site/GetProxyList', { sitename: name }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + create_proxy: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('CreateProxy', 'site/CreateProxy', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + remove_proxy: function (sitename, proxyname, callback) { + bt.show_confirm(lan.public_backup.del_proxy + '[' + proxyname + ']', lan.public_backup.sure_del_proxy, function () { + var loadT = bt.load(lan.site.the_msg); + bt.send('RemoveProxy', 'site/RemoveProxy', { sitename: sitename, proxyname: proxyname }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }); + }, + modify_proxy: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('ModifyProxy', ' site/ModifyProxy', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_proxy_config: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('GetProxyFile', 'site/GetProxyFile', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + save_proxy_config: function (obj, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('SaveProxyFile', 'site/SaveProxyFile', obj, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_site_security: function (id, name, callback) { + bt.send('GetSecurity', 'site/GetSecurity', { id: id, name: name }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_site_security: function (id, name, fix, domains, status, return_rule, callback) { + var loading = bt.load(lan.site.the_msg); + bt.send('SetSecurity', 'site/SetSecurity', { id: id, name: name, fix: fix, domains: domains, status: status, return_rule: return_rule }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_site_301: function (siteName, callback) { + bt.send('Get301Status', 'site/Get301Status', { siteName: siteName }, function (rdata) { + if (callback) callback(rdata); + }); + }, + set_site_301: function (siteName, srcDomain, toUrl, type, callback) { + var loading = bt.load(); + bt.send('Set301Status', 'site/Set301Status', { siteName: siteName, toDomain: toUrl, srcDomain: srcDomain, type: type }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_tomcat: function (siteName, callback) { + var loading = bt.load(lan.public.config); + bt.send('SetTomcat', 'site/SetTomcat', { siteName: siteName }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_site_logs: function (siteName, callback) { + var loading = bt.load(); + bt.send('GetSiteLogs', 'site/GetSiteLogs', { siteName: siteName }, function (rdata) { + loading.close(); + if (rdata.status !== true) rdata.msg = ''; + if (rdata.msg == '') rdata.msg = lan.public_backup.no_log; + if (callback) callback(rdata); + }); + }, + get_site_error_logs: function (siteName, callback) { + var loading = bt.load(); + bt.send( + 'get_site_err_log', + 'site/get_site_err_log', + { + siteName: siteName, + }, + function (rdata) { + loading.close(); + if (rdata.status !== true) rdata.msg = ''; + if (rdata.msg == '') rdata.msg = lan.public_backup.no_log; + if (callback) callback(rdata); + } + ); + }, + get_site_ssl: function (siteName, callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('GetSSL', 'site/GetSSL', { siteName: siteName }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + create_let: function (data, callback) { + var loadT = layer.open({ + title: false, + type: 1, + closeBtn: 0, + shade: 0.3, + area: '500px', + offset: '30%', + content: + "
                            " + lan.public_backup.preparing_for_cert + '...
                            ', + success: function (layers, index) { + bt.site.get_let_logs(); + bt.send('CreateLet', 'site/CreateLet', data, function (rdata) { + layer.close(loadT); + if (callback) callback(rdata); + }); + }, + }); + }, + get_let_logs: function () { + bt.send( + 'get_lines', + 'ajax/get_lines', + { + num: 10, + filename: '/www/server/panel/logs/letsencrypt.log', + }, + function (rdata) { + if ($('#create_lst').text() === '') return; + if (rdata.status === true) { + $('#create_lst').text(rdata.msg); + $('#create_lst').scrollTop($('#create_lst')[0].scrollHeight); + } + setTimeout(function () { + bt.site.get_let_logs(); + }, 1000); + } + ); + }, + get_dns_api: function (callback) { + var loadT = bt.load(); + bt.send('GetDnsApi', 'site/GetDnsApi', {}, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + set_dns_api: function (data, callback) { + var loadT = bt.load(); + bt.send('SetDnsApi', 'site/SetDnsApi', data, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + verify_domain: function (partnerOrderId, siteName, callback) { + var loadT = bt.load(lan.site.ssl_apply_2); + bt.send('Completed', 'ssl/Completed', { partnerOrderId: partnerOrderId, siteName: siteName }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_dv_ssl: function (domain, path, callback) { + var loadT = bt.load(lan.site.ssl_apply_1); + bt.send('GetDVSSL', 'ssl/GetDVSSL', { domain: domain, path: path }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_module_config: function (param, callback) { + var loadT = bt.load('Obtaining the alarm configuration, please wait...'); + bt.send( + 'get_module_config', + 'push/get_module_config', + { + name: param.name, + type: param.type, + }, + function (rdata) { + loadT.close(); + if (callback) callback(rdata); + } + ); + }, + + // 设置 + set_push_config: function (param, callback) { + var loadT = bt.load('Please wait while setting alarm configuration...'); + bt.send( + 'set_push_config', + 'push/set_push_config', + { + name: param.name, + id: param.id, + data: param.data, + }, + function (rdata) { + loadT.close(); + if (callback) callback(rdata); + } + ); + }, + // 获取消息推送配置 + get_msg_configs: function (callback) { + var loadT = bt.load('Getting the message push configuration, please wait...'); + bt.send('get_msg_configs', 'config/get_msg_configs', {}, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + // 下载证书 + download_cert: function (param, callback) { + var loadT = bt.load('Please wait while downloading the certificate...'); + bt.send( + 'download_cert', + 'site/download_cert', + { + siteName: param.siteName, + ssl_type: param.ssl_type || 'csr', + pem: param.pem, + key: param.key, + pwd: param.pwd || '', //密码,非必填 + }, + function (rdata) { + loadT.close(); + if (callback) callback(rdata); + } + ); + }, + get_ssl_info: function (partnerOrderId, siteName, callback) { + var loadT = bt.load(lan.site.ssl_apply_3); + bt.send('GetSSLInfo', 'ssl/GetSSLInfo', { partnerOrderId: partnerOrderId, siteName: siteName }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + set_cert_ssl: function (certName, siteName, callback) { + var loadT = bt.load(lan.public_backup.deploy_cert); + bt.send('SetCertToSite', 'ssl/SetCertToSite', { certName: certName, siteName: siteName }, function (rdata) { + loadT.close(); + site.reload(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }, + remove_cert_ssl: function (certName, callback) { + bt.show_confirm(lan.public_backup.del_cert, lan.public_backup.sure_del_cert, function () { + var loadT = bt.load(lan.site.the_msg); + bt.send('RemoveCert', 'ssl/RemoveCert', { certName: certName }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }); + }, + set_http_to_https: function (siteName, callback) { + var loading = bt.load(); + bt.send('HttpToHttps', 'site/HttpToHttps', { siteName: siteName }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }, + close_http_to_https: function (siteName, callback) { + var loading = bt.load(); + bt.send('CloseToHttps', 'site/CloseToHttps', { siteName: siteName }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + }, + set_ssl: function (siteName, data, callback) { + if (data.path) { + //iis导入证书 + } else { + var loadT = bt.load(lan.site.saving_txt); + bt.send('SetSSL', 'site/SetSSL', { type: 1, siteName: siteName, key: data.key, csr: data.csr }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + } + }, + set_ssl_status: function (action, siteName, callback) { + var loadT = bt.load(lan.site.get_ssl_list); + bt.send(action, 'site/' + action, { updateOf: 1, siteName: siteName }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_cer_list: function (callback) { + var loadT = bt.load(lan.site.the_msg); + bt.send('GetCertList', 'ssl/GetCertList', {}, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_order_list: function (siteName, callback) { + bt.send('GetOrderList', 'ssl/GetOrderList', { siteName: siteName }, function (rdata) { + if (callback) callback(rdata); + }); + }, + del_site: function (data, callback) { + var loadT = bt.load(lan.get('del_all_task_the', [data.webname])); + bt.send('DeleteSite', 'site/DeleteSite', data, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + add_site: function (callback) { + var _form = $.extend(true, {}, bt.data.site.add); + bt.site.get_all_phpversion(function (rdata) { + bt.site.get_type(function (tdata) { + for (var i = 0; i < _form.list.length; i++) { + if (_form.list[i].name == 'version') { + var items = []; + for (var j = rdata.length - 1; j >= 0; j--) { + var o = rdata[j]; + o.value = o.version; + o.title = o.name; + items.push(o); + } + _form.list[i].items = items; + } else if (_form.list[i].name == 'type_id') { + for (var x = 0; x < tdata.length; x++) _form.list[i].items.push({ value: tdata[x].id, title: tdata[x].name }); + } + } + var bs = bt.render_form(_form, function (rdata) { + if (callback) callback(rdata); + }); + $('.placeholder').click(function () { + $(this).hide(); + $('.webname' + bs).focus(); + }); + $('.path' + bs).val($('#defaultPath').text()); + $('.webname' + bs).focus(function () { + $('.placeholder').hide(); + }); + $('.webname' + bs).blur(function () { + if ($(this).val().length == 0) { + $('.placeholder').show(); + } + }); + $('.webname' + bs).focus(function () { + var _this = $(this), + tips = + 'www will not add by default, if you need to access,please add it like:\ +
                            hostname.com\ +
                            www.hostname.com'; + _this.attr('placeholder', ''); + var loadT = layer.tips(tips, _this, { + tips: [1, '#20a53a'], + time: 0, + area: _this[0].clientWidth + 'px', + }); + $(this).one('blur', function () { + layer.close(loadT); + }); + }); + $('.line').on('mouseenter', '.bt-ico-ask', function () { + var idd = $(this).attr('class').split(' ')[1], + tip = $(this).attr('tip'); + layer.tips(tip, '.' + idd + '', { tips: [1, '#d4d4d4'], time: 0, area: '300px' }); + }); + $('.line').on('mouseleave', '.bt-ico-ask', function () { + layer.closeAll('tips'); + }); + $('.domain_textarea').parents('.bt-form').css({ 'max-height': '565px', overflow: 'auto' }); + }); + }); + }, + get_all_phpversion: function (callback) { + bt.send('GetPHPVersion', 'site/GetPHPVersion', {}, function (rdata) { + if (callback) callback(rdata); + }); + }, + get_site_phpversion: function (siteName, callback) { + bt.send('GetSitePHPVersion', 'site/GetSitePHPVersion', { siteName: siteName }, function (rdata) { + if (callback) callback(rdata); + }); + }, + stop: function (id, name, callback) { + bt.confirm({ title: lan.public_backup.stop_site + ' 【' + name + '】', msg: lan.site.site_stop_txt }, function (index) { + if (index > 0) { + var loadT = bt.load(); + bt.send('SiteStop', 'site/SiteStop', { id: id, name: name }, function (ret) { + loadT.close(); + if (site && typeof callback == 'undefined') { + site.get_list(); + } else { + if (callback) callback(ret); + } + bt.msg(ret); + }); + } + }); + }, + start: function (id, name, callback) { + bt.confirm({ title: lan.public_backup.start_site + ' 【' + name + '】', msg: lan.site.site_start_txt }, function (index) { + if (index > 0) { + var loadT = bt.load(); + bt.send('SiteStart', 'site/SiteStart', { id: id, name: name }, function (ret) { + loadT.close(); + if (site && typeof callback == 'undefined') { + site.get_list(); + } else { + if (callback) callback(ret); + } + bt.msg(ret); + }); + } + }); + }, + backup_data: function (id, callback) { + var loadT = bt.load(lan.database.backup_the); + bt.send('ToBackup', 'site/ToBackup', { id: id }, function (rdata) { + loadT.close(); + bt.msg(rdata); + if (callback) callback(rdata); + }); + }, + del_backup: function (id, siteId, siteName) { + bt.confirm({ msg: lan.site.webback_del_confirm, title: lan.site.del_bak_file }, function (index) { + var loadT = bt.load(); + bt.send('DelBackup', 'site/DelBackup', { id: id }, function (frdata) { + loadT.close(); + if (frdata.status) { + if (site) site.site_detail(siteId, siteName); + } + bt.msg(frdata); + }); + }); + }, + set_endtime: function (id, dates, callback) { + var loadT = bt.load(lan.site.saving_txt); + bt.send('SetEdate', 'site/SetEdate', { id: id, edate: dates }, function (rdata) { + loadT.close(); + if (callback) callback(rdata); + }); + }, + get_default_path: function (type, callback) { + var vhref = ''; + if (bt.os == 'Linux') { + switch (type) { + case 0: + vhref = '/www/server/panel/data/defaultDoc.html'; + break; + case 1: + vhref = '/www/server/panel/data/404.html'; + break; + case 2: + var serverType = bt.get_cookie('serverType'); + vhref = '/www/server/apache/htdocs/index.html'; + if (serverType == 'nginx') vhref = '/www/server/nginx/html/index.html'; + break; + case 3: + vhref = '/www/server/stop/index.html'; + break; + } + } + if (callback) callback(vhref); + }, + get_default_site: function (callback) { + var loading = bt.load(); + bt.send('GetDefaultSite', 'site/GetDefaultSite', {}, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + set_default_site: function (name, callback) { + var loading = bt.load(); + bt.send('SetDefaultSite', 'site/SetDefaultSite', { name: name }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_dir_auth: function (id, callback) { + var loading = bt.load(); + bt.send('get_dir_auth', 'site/get_dir_auth', { id: id }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + get_php_deny: function (website, callback) { + var loading = bt.load(); + bt.send('get_file_deny', 'config/get_file_deny', { website: website }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + edit_php_deny: function (data, callback) { + var loading = bt.load(); + bt.send('set_file_deny', 'config/set_file_deny', data, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + del_php_deny: function (data, callback) { + var loading = bt.load(); + bt.send('del_file_deny', 'config/del_file_deny', data, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + create_dir_guard: function (data, callback) { + var loading = bt.load(); + bt.send('set_dir_auth', 'site/set_dir_auth', { id: data.id, name: data.name, site_dir: data.site_dir, username: data.username, password: data.password }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + edit_dir_account: function (data, callback) { + var loading = bt.load(); + bt.send('modify_dir_auth_pass', 'site/modify_dir_auth_pass', { id: data.id, name: data.name, username: data.username, password: data.password }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }, + delete_dir_guard: function (id, data, callback) { + var loading = bt.load(); + bt.show_confirm(lan.public_backup.del + '[' + data + ']', lan.public_backup.del_dir, function () { + bt.send('delete_dir_auth', 'site/delete_dir_auth', { id: id, name: data }, function (rdata) { + loading.close(); + if (callback) callback(rdata); + }); + }); + }, +}; + +bt.form = { + btn: { + close: function (title, callback) { + var obj = { title: lan.public_backup.turn_off, name: 'btn-danger' }; + if (title) obj.title = title; + if (callback) obj['callback'] = callback; + return obj; + }, + submit: function (title, callback) { + var obj = { title: lan.public_backup.submit, name: 'submit', css: 'btn-success' }; + if (title) obj.title = title; + if (callback) obj['callback'] = callback; + return obj; + }, + }, + item: { + data_access: { + title: 'Permission', + items: [ + { + name: 'dataAccess', + type: 'select', + width: '100px', + items: [ + { title: lan.public_backup.local_server, value: '127.0.0.1' }, + { title: lan.public_backup.every_one, value: '%' }, + { title: lan.public_backup.specify_ip, value: 'ip' }, + ], + callback: function (obj) { + var subid = obj.attr('name') + '_subid'; + $('#' + subid).remove(); + if (obj.val() == 'ip') { + obj + .parent() + .append( + '' + ); + } + }, + }, + ], + }, + password: { + title: lan.public_backup.pass, + name: 'password', + items: [ + { + type: 'text', + width: '311px', + value: bt.get_random(16), + event: { + css: 'glyphicon-repeat', + callback: function (obj) { + bt.refresh_pwd(16, obj); + }, + }, + }, + ], + }, + }, +}; + +bt.data = { + database: { + getType: function () { + return bt.get_cookie('db_page_model') || 'mysql'; + }, + root: { + title: lan.database.edit_pass_title, + area: '530px', + list: [ + { + title: lan.public_backup.rootpass, + name: 'password', + items: [ + { + type: 'text', + width: '311px', + event: { + css: 'glyphicon-repeat', + callback: function (obj) { + bt.refresh_pwd(16, obj); + }, + }, + }, + ], + }, + ], + btns: [ + bt.form.btn.close(), + bt.form.btn.submit(lan.public_backup.submit, function (rdata, load) { + var loading = bt.load(); + var type = bt.data.database.getType(); + var params = { url: 'database?action=SetupPassword', data: rdata }; + if (type != 'mysql') { + params.url = 'database/' + type + '/SetupPassword'; + params.data = { data: JSON.stringify(rdata) }; + } + bt_tools.send(params, function (rRet) { + loading.close(); + bt.msg(rRet); + if (rRet.status) load.close(); + }); + }), + ], + }, + mongo: { + title: lan.database.edit_pass_title, + area: '530px', + list: [ + { + title: lan.public_backup.rootpass, + name: 'password', + items: [ + { + type: 'text', + width: '311px', + event: { + css: 'glyphicon-repeat', + callback: function (obj) { + bt.refresh_pwd(16, obj); + }, + }, + }, + ], + }, + ], + btns: [ + bt.form.btn.close(), + bt.form.btn.submit(lan.public_backup.submit, function (rdata, load) { + var loading = bt.load(); + var type = bt.data.database.getType(); + var url = 'database/' + type + '/set_auth_status'; + if (type == 'pgsql') url = 'database/' + type + '/set_root_pwd'; + bt_tools.send( + { + url: url, + data: { data: JSON.stringify($.extend(rdata, { status: 1 })) }, + }, + function (rRet) { + loading.close(); + bt.msg(rRet); + load.close(); + } + ); + }), + ], + }, + data_add: { + title: lan.database.add_title, + area: '530px', + list: [ + { + title: 'DBName', + items: [ + { + name: 'name', + placeholder: lan.public_backup.new_db_name, + type: 'text', + width: '65%', + callback: function (obj) { + $('input[name="db_user"]').val(obj.val()); + }, + }, + { + name: 'codeing', + type: 'select', + width: '27%', + items: [ + { title: 'utf-8', value: 'utf8' }, + { title: 'utf8mb4', value: 'utf8mb4' }, + { title: 'gbk', value: 'gbk' }, + { title: 'big5', value: 'big5' }, + ], + }, + ], + }, + { + name: 'db_user', + title: lan.public_backup.user_name, + placeholder: lan.public_backup.db_user, + width: '65%', + }, + bt.form.item.password, + bt.form.item.data_access, + { + title: lan.public_backup.add_to, + items: [ + { + name: 'sid', + width: '65%', + type: 'select', + items: [], + }, + ], + }, + { + html: '\ +
                            \ + Force SSL\ +
                            \ + \ + \ +
                            \ +
                            \ + ', + }, + ], + btns: [ + bt.form.btn.close(), + bt.form.btn.submit(lan.public_backup.submit, function (rdata, load, callback) { + if (!rdata.address) rdata.address = rdata.dataAccess; + if (!rdata.ps) rdata.ps = rdata.name; + if (!rdata.ssl) rdata.ssl = $('#check_ssl').prop('checked') ? 'REQUIRE SSL' : ''; + var loading = bt.load(); + var type = bt.data.database.getType(); + var param = { + url: 'database/' + type + '/AddDatabase', + data: { data: JSON.stringify(rdata) }, + }; + if (type == 'mysql') { + rdata['dtype'] = 'MySQL'; + param = { url: 'database?action=AddDatabase', data: rdata }; + } + bt_tools.send(param, function (rRet) { + loading.close(); + if (rRet.status) load.close(); + if (callback) callback(rRet); + bt.msg(rRet); + }); + }), + ], + success: function () { + $('[name=sid]').after('' + lan.public.manage_cloud_server + ''); + var type = bt.data.database.getType(); + // 当前类型为mongodb + if (type == 'mongodb') { + // 是否开启安全认证,没开启隐藏用户名跟密码 + if (!mongodb.mongoDBAccessStatus) { + $('.layui-layer.layui-layer-page .line').eq(1).hide(); + $('.layui-layer.layui-layer-page .line').eq(2).hide(); + } + // 远程服务器类型判断 + $('[name=sid]').change(function () { + // 为远程服务器时,默认开启安全认证 + if ($(this).val() != 0) { + $('.layui-layer.layui-layer-page .line').eq(1).show(); + $('.layui-layer.layui-layer-page .line').eq(2).show(); + } else { + if (!mongodb.mongoDBAccessStatuss) { + $('.layui-layer.layui-layer-page .line').eq(1).hide(); + $('.layui-layer.layui-layer-page .line').eq(2).hide(); + } + } + }); + } + }, + }, + data_access: { + title: lan.public_backup.set_db_permissions, + area: '480px', + list: [ + { title: 'name', name: 'name', hide: true }, + bt.form.item.data_access, + { + title: 'Force SSL', + items: [ + { + name: 'force_ssl', + type: 'switch', + value: 'false', + }, + ], + }, + ], + btns: [ + bt.form.btn.close(), + { + title: lan.public_backup.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load) { + var loading = bt.load(); + rdata.access = rdata.dataAccess; + if (rdata.access == 'ip') rdata.access = rdata.address; + rdata.ssl = $('#force_ssl').prop('checked') ? 'REQUIRE SSL' : ''; + bt.send('SetDatabaseAccess', 'database/SetDatabaseAccess', rdata, function (rRet) { + loading.close(); + bt.msg(rRet); + if (rRet.status) load.close(); + }); + }, + }, + ], + }, + data_pass: { + title: lan.public_backup.change_db_pass, + area: '530px', + list: [ + { title: 'id', name: 'id', hide: true }, + { title: lan.public_backup.user_name, name: 'name', disabled: true }, + { + title: lan.public_backup.pass, + name: 'password', + items: [ + { + type: 'text', + event: { + css: 'glyphicon-repeat', + callback: function (obj) { + bt.refresh_pwd(16, obj); + }, + }, + }, + ], + }, + ], + btns: [ + { title: lan.public_backup.turn_off, name: 'close' }, + { + title: lan.public_backup.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + var loading = bt.load(); + var type = bt.data.database.getType(); + var params = { url: 'database?action=ResDatabasePassword', data: rdata }; + if (type != 'mysql') { + params.url = 'database/' + type + '/ResDatabasePassword'; + params.data = { data: JSON.stringify(rdata) }; + } + bt_tools.send(params, function (rRet) { + loading.close(); + bt.msg(rRet); + if (rRet.status) load.close(); + if (callback) callback(rRet); + }); + }, + }, + ], + }, + }, + site: { + add: { + title: lan.site.site_add, + area: '680px', + list: [ + { + title: lan.public_backup.domain, + name: 'webname', + class: 'domain_textarea', + items: [ + { + type: 'textarea', + width: '420px', + height: '80px', + style: 'padding:10px;line-height: 15px;', + callback: function (obj) { + var array = obj.val().split('\n'); + var ress = array[0].split(':')[0]; + var res = bt.strim(ress.replace(new RegExp(/([-.])/g), '_')); + var ftp_user = res; + var data_user = res; + if (!isNaN(res.substr(0, 1))) { + ftp_user = 'ftp_' + ftp_user; + data_user = 'sql_' + data_user; + } + if (data_user.length > 16) data_user = data_user.substr(0, 16); + obj.data('ftp', ftp_user); + obj.data('database', data_user); + $('.ftp_username').val(ftp_user); + $('.datauser').val(data_user); + var _form = obj.parents('div.bt-form'); + var _path_obj = _form.find('input[name="path"]'); + var path = _path_obj.val(); + var defaultPath = $('#defaultPath').text(); + var dPath = bt.rtrim(defaultPath, '/'); + if (path.substr(0, dPath.length) == dPath) _path_obj.val(dPath + '/' + ress); + _form.find('input[name="ps"]').val(ress); + clearTimeout(bt.setTimeouts); + bt.setTimeouts = setTimeout(function () { + if (bt.check_domain(ress)) { + if (ress.indexOf('www.') !== 0) { + $('.redirect_checkbox label').html('Add [www.' + ress + '] domain name to the main domain name'); + } else if (ress.indexOf('www.') === 0) { + $('.redirect_checkbox label').html('Add ' + ress.replace(/^www\./, '') + ' to the main domain'); + } + $('.redirect_checkbox').show(); + } else { + $('.redirect_checkbox,redirect_tourl').hide(); + } + }, 100); + }, + placeholder: lan.public_backup.domian_tips, + }, + ], + }, + { + title: '', + name: 'redirect', + class: 'redirect_checkbox', + hide: true, + items: [ + { + type: 'checkbox', + text: '', + callback: function (obj) { + var domain = $('.redirect_checkbox').find('span').text(), + domain_textarea = $('.domain_textarea textarea'), + domainList = domain_textarea.val().split('\n'), + domain_one = domainList[0].split(':')[0]; + if (obj.redirect) { + domain_textarea.val(domain_textarea.val() + '\r' + domain); + var line = $( + bt.render_form_line({ + title: 'Redirect', + name: 'tourl', + class: 'redirect_tourl', + items: [ + { + type: 'radio_group', + value: 0, + list: [ + { value: 0, text: 'No' }, + { + value: 1, + text: 'Redirect the main domain name [ ' + domain_one + '] to [' + domain + '] domain name', + }, + { + value: 2, + text: 'Redirect the [' + domain + '] domain name to the main domain [' + domain_one + ']', + }, + ], + }, + ], + }).html + ); + $('.redirect_checkbox.line').after(line); + } else { + for (var i = domainList.length - 1; i >= 0; i--) { + if (domainList[i] === domain) domainList.splice(i, 1); + } + domain_textarea.val(domainList.join('\n')); + $('.redirect_checkbox').next('.redirect_tourl').remove(); + } + }, + }, + ], + }, + { title: lan.public_backup.ps, name: 'ps', placeholder: lan.public_backup.site_ps }, + { + title: lan.public_backup.root_dir, + name: 'path', + items: [ + { + type: 'text', + width: '330px', + event: { + css: 'glyphicon-folder-open', + callback: function (obj) { + bt.select_path(obj); + }, + }, + }, + ], + }, + { + title: 'FTP', + items: [ + { + name: 'ftp', + type: 'select', + items: [ + { value: 'false', title: lan.public_backup.dont_create }, + { value: 'true', title: lan.public_backup.create }, + ], + callback: function (obj) { + var subid = obj.attr('name') + '_subid'; + $('#' + subid).remove(); + if (obj.val() == 'true') { + var _bs = obj.parents('div.bt-form').attr('data-id'); + var ftp_user = $('textarea[name="webname"]').data('ftp'); + var item = { + title: lan.public_backup.set_ftp, + class: 'pb0', + name: 'ftp_tips', + items: [ + { name: 'ftp_username', title: lan.public_backup.user_name, width: '160px', value: ftp_user }, + { name: 'ftp_password', title: lan.public_backup.pass, width: '160px', value: bt.get_random(16) }, + ], + ps_help: lan.public_backup.ftp_tips, + }; + var _tr = bt.render_form_line(item); + + obj.parents('div.line').append('
                            ' + _tr.html + '
                            '); + } + }, + }, + ], + }, + { + title: lan.public_backup.db, + items: [ + { + name: 'sql', + type: 'select', + items: [ + { value: 'false', title: lan.public_backup.dont_create }, + { value: 'MySQL', title: 'MySQL' }, + { value: 'SQLServer', title: 'SQLServer' }, + ], + callback: function (obj) { + var subid = obj.attr('name') + '_subid'; + $('#' + subid).remove(); + if (obj.val() != 'false') { + if (bt.os == 'Linux' && obj.val() == 'SQLServer') { + obj.val('false'); + bt.msg({ msg: lan.public_backup.unsupport_sqlserver, icon: 2 }); + return; + } + var _bs = obj.parents('div.bt-form').attr('data-id'); + var data_user = $('textarea[name="webname"]').data('database'); + var item = { + title: lan.public_backup.db_set, + class: 'pb0', + name: 'sql_tips', + items: [ + { name: 'datauser', title: lan.public_backup.user_name, width: '160px', value: data_user }, + { name: 'datapassword', title: lan.public_backup.pass, width: '160px', value: bt.get_random(16) }, + ], + ps_help: lan.public_backup.create_site_tips, + }; + var _tr = bt.render_form_line(item); + obj.parents('div.line').append('
                            ' + _tr.html + '
                            '); + } + }, + }, + { + name: 'codeing', + type: 'select', + items: [ + { value: 'utf8', title: 'utf-8' }, + { value: 'utf8mb4', title: 'utf8mb4' }, + { value: 'gbk', title: 'gbk' }, + { value: 'big5', title: 'big5' }, + ], + }, + ], + }, + { + title: 'Program type', + type: 'select', + name: 'type', + disabled: bt.contains(bt.get_cookie('serverType'), 'IIS') ? false : true, + items: [ + { value: 'PHP', title: 'PHP' }, + { value: 'Asp', title: 'Asp' }, + { value: 'Aspx', title: 'Aspx' }, + ], + callback: function (obj) { + if (obj.val() == 'Asp' || obj.val() == 'Aspx') { + obj.parents('div.line').next().hide(); + } else { + obj.parents('div.line').next().show(); + } + }, + }, + { + title: lan.public_backup.php_v, + name: 'version', + type: 'select', + items: [{ value: '00', title: lan.public_backup.sitic }], + }, + { + title: lan.public_backup.site_classification, + name: 'type_id', + type: 'select', + width: 'auto', + items: [], + }, + { + title: 'SSL', + class: 'ssl_checkbox', + items: [ + { + type: 'checkbox', + name: 'set_ssl', + text: 'Apply for SSL', + callback: function (obj) { + if (!obj.set_ssl) { + $('[name="force_ssl"]').prop('checked', false); + } + }, + }, + { + type: 'checkbox', + name: 'force_ssl', + text: 'HTTP redirect to HTTPS', + callback: function (obj) { + if (obj.force_ssl) { + $('[name="set_ssl"]').prop('checked', true); + } + }, + }, + { + type: 'html', + html: '
                            • If you need to apply for SSL, please make sure that the domain name has added A record resolution for the domain name
                            ', + }, + ], + }, + ], + btns: [ + { title: lan.public_backup.turn_off, name: 'close' }, + { + title: lan.public_backup.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + var loading = bt.load(); + if (!rdata.webname) { + bt.msg({ msg: lan.public_backup.domain_format_not_right, icon: 2 }); + return; + } + var webname = bt.replace_all(rdata.webname, 'http:\\/\\/', ''); + webname = bt.replace_all(webname, 'https:\\/\\/', ''); + var arrs = webname.split('\n'); + var list = []; + var domain_name, port; + for (var i = 0; i < arrs.length; i++) { + if (arrs[i]) { + var temp = arrs[i].split(':'); + var item = {}; + item['name'] = temp[0]; + item['port'] = temp.length > 1 ? temp[1] : 80; + if (!bt.check_domain(item.name)) { + bt.msg({ msg: lan.site.domain_err_txt, icon: 2 }); + return; + } + if (i > 0) { + list.push(arrs[i]); + } else { + domain_name = item.name; + port = item.port; + } + } + } + var domain = {}; + domain['domain'] = domain_name; + domain['domainlist'] = list; + domain['count'] = list.length; + rdata.webname = JSON.stringify(domain); + rdata.port = port; + rdata.tourl = parseInt($('[name="tourl"]:checked').val()); + if (rdata.redirect) { + if (rdata.tourl) { + var domains = $('#tourl_' + rdata.tourl) + .next() + .find('span'); + rdata.redirect = $(domains[0]).text(); + rdata.tourl = $(domains[1]).text(); + } else { + delete rdata.redirect; + delete rdata.tourl; + } + } else { + delete rdata.redirect; + delete rdata.tourl; + } + rdata.set_ssl = rdata.set_ssl ? 1 : 0; + rdata.force_ssl = rdata.force_ssl ? 1 : 0; + bt.send('AddSite', 'site/AddSite', rdata, function (rRet) { + loading.close(); + if (rRet.siteStatus) load.close(); + if (callback) callback(rRet); + }); + }, + }, + ], + }, + }, + ftp: { + add: { + title: lan.ftp.add_title, + area: '530px', + list: [ + { + title: lan.public_backup.user_name, + name: 'ftp_username', + callback: function (obj) { + var defaultPath = $('#defaultPath').text(); + var wootPath = bt.rtrim(defaultPath, '/'); + if (bt.contains($('input[name="path"]').val(), wootPath)) { + $('input[name="path"]').val(wootPath + '/' + obj.val()); + } + }, + }, + { + title: lan.public_backup.pass, + name: 'ftp_password', + items: [ + { + type: 'text', + width: '330px', + value: bt.get_random(16), + event: { + css: 'glyphicon-repeat', + callback: function (obj) { + bt.refresh_pwd(16, obj); + }, + }, + }, + ], + }, + { + title: lan.public_backup.root_dir, + name: 'path', + items: [ + { + type: 'text', + event: { + css: 'glyphicon-folder-open', + callback: function (obj) { + bt.select_path(obj); + }, + }, + }, + ], + }, + ], + btns: [ + { title: lan.public_backup.turn_off, name: 'close' }, + { + title: lan.public_backup.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + var loading = bt.load(); + if (!rdata.ps) rdata.ps = rdata.ftp_username; + bt.send('AddUser', 'ftp/AddUser', rdata, function (rRet) { + loading.close(); + if (rRet.status) load.close(); + if (callback) callback(rRet); + bt.msg(rRet); + }); + }, + }, + ], + }, + set_port: { + title: lan.ftp.port_title, + skin: '', + area: '500px', + list: [{ title: lan.public_backup.default_port, name: 'port', width: '250px' }], + btns: [ + { title: lan.public_backup.turn_off, name: 'close' }, + { + title: lan.public_backup.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + var loading = bt.load(); + bt.send('setPort', 'ftp/setPort', rdata, function (rRet) { + loading.close(); + if (rRet.status) load.close(); + //if(callback) callback(rRet); + bt.msg(rRet); + }); + }, + }, + ], + }, + set_password: { + title: lan.ftp.pass_title, + area: '530px', + list: [ + { title: 'id', name: 'id', hide: true }, + { title: lan.public_backup.user_name, name: 'ftp_username', disabled: true }, + { + title: lan.public_backup.pass, + name: 'new_password', + items: [ + { + type: 'text', + event: { + css: 'glyphicon-repeat', + callback: function (obj) { + bt.refresh_pwd(16, obj); + }, + }, + }, + ], + }, + ], + btns: [ + { title: lan.public_backup.turn_off, name: 'close' }, + { + title: lan.public_backup.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + bt.confirm({ msg: lan.ftp.pass_confirm, title: lan.ftp.stop_title }, function () { + var loading = bt.load(); + bt.send('SetUserPassword', 'ftp/SetUserPassword', rdata, function (rRet) { + loading.close(); + if (rRet.status) load.close(); + if (callback) callback(rRet); + bt.msg(rRet); + }); + }); + }, + }, + ], + }, + }, +}; +var form_group = { + select_all: function (_arry) { + for (var j = 0; j < _arry.length; j++) { + this.select(_arry[j]); + } + }, + select: function (elem) { + $(elem).after( + '
                            请选择
                              ' + ); + var _html = '', + select_el = $(elem), + select_group = select_el.next(), + select_ul = select_group.find('.bt_select_ul'), + select_val = select_group.find('.select_val'), + select_icon = select_group.find('.glyphicon'); + select_el.find('option').each(function (index, el) { + var active = select_el.val() === $(el).val(), + _val = $(el).val(), + _name = $(el).text(); + _html += '
                            • ' + _name + '
                            • '; + if (active) { + select_val.text(_name); + _val !== '' ? select_val.removeClass('default') : select_val.addClass('default'); + } + }); + select_el.hide(); + select_ul.html(_html); + $(elem) + .next('.bt_select_group') + .find('.bt_select_active') + .unbind('click') + .click(function (e) { + if (!$(this).next().hasClass('active')) { + $(this) + .parents() + .find('li') + .siblings() + .find('.bt_select_ul.active') + .each(function () { + is_show_slect_parent(this); + }); + $(this) + .parents('.rec-box') + .siblings() + .find('.bt_select_ul.active') + .each(function () { + is_show_slect_parent(this); + }); + } + is_show_select_ul($(this).next().hasClass('active')); + $(document).click(function (ev) { + is_show_select_ul(true); + $(this).unbind('click'); + ev.stopPropagation(); + ev.preventDefault(); + }); + e.stopPropagation(); + e.preventDefault(); + }); + $(elem) + .next('.bt_select_group') + .find('.bt_select_ul li') + .unbind('click') + .click(function () { + var _val = $(this).attr('data-val'), + _name = $(this).text(); + $(this).addClass('active').siblings().removeClass('active'); + _val !== '' ? select_val.removeClass('default') : select_val.addClass('default'); + select_val.text(_name); + select_el.val(_val); + $(elem) + .find('option[value="' + _val + '"]') + .change(); + is_show_select_ul(true); + }); + function is_show_slect_parent(that) { + $(that).removeClass('active fadeInUp animated'); + $(that).prev().find('.glyphicon').removeAttr('style'); + $(that).parent().removeAttr('style'); + } + function is_show_select_ul(active) { + if (active) { + select_group.removeAttr('style'); + select_icon.css({ transform: 'rotate(0deg)' }); + select_ul.removeClass('active fadeInUp animated'); + } else { + select_group.css('borderColor', '#20a53a'); + select_icon.css({ transform: 'rotate(180deg)' }); + select_ul.addClass('active fadeInUp animated'); + } + } + }, + checkbox: function () { + $('input[type="checkbox"]').each(function (index, el) { + $(el).hide(); + $(el).after('
                              '); + }); + $('.bt_checkbox_group').click(function () { + $(this).prev().click(); + if ($(this).hasClass('active')) { + $(this).removeClass('active'); + $(this).prev().removeAttr('checked'); + } else { + $(this).addClass('active'); + $(this).prev().attr('checked', 'checked'); + } + }); + }, +}; + +bt.public = { + // 设置目录配额 + modify_path_quota: function (data, callback) { + var loadT = bt.load(lan.public.modify_path_quota); + $.post('/project/quota/modify_path_quota', data, function (res) { + loadT.close(); + if (callback) callback(res); + }); + }, + + // 设置mysql配额 + modify_mysql_quota: function (data, callback) { + var loadT = bt.load(lan.public.modify_mysql_quota); + $.post('/project/quota/modify_mysql_quota', data, function (res) { + loadT.close(); + if (callback) callback(res); + }); + }, + + /** + * @description 获取quoto容量 + */ + + get_quota_config: function (type) { + return { + fid: 'quota', + title: lan.public.capacity, + width: 120, + template: function (row, index) { + var quota = row.quota; + if (!quota.size) return '' + lan.public.notConfigured + ''; + var size = quota.size * 1024 * 1024; + var speed = ((quota.used / size) * 100).toFixed(2); + var quotaFull = false; + if (quota.size > 0 && quota.used >= size) quotaFull = true; + return ( + '
                              ' + + '
                              ' + + '
                              ' + ); + }, + event: function (row, index, ev) { + var quota = row.quota; + var size = quota.size * 1024 * 1024; + var usedList = bt.format_size(quota.used).split(' '); + var quotaFull = false; + if (quota.size > 0 && quota.used >= size) quotaFull = true; + var types = { + site: lan.site.website, + ftp: lan.site.add_site.ftp, + database: lan.site.database, + }; + layer.open({ + type: 1, + title: '[' + row.name + '] ' + types[type] + ' ' + lan.public.quotaCapacity, + area: '476px', + closeBtn: 2, + btn: [lan.public.save, lan.public.cancel], + content: + '
                              ' + + '' + + lan.public.currentUsedCapacity + + '' + + '
                              ' + + '' + + (!quotaFull ? (quota.size != 0 ? usedList[1] : 'MB') : '') + + '' + + '
                              ' + + '' + + lan.public.quotaCapacity + + '' + + '
                              ' + + 'MB' + + '
                              ' + + '
                              ' + + '
                                ' + + '
                              • ' + + lan.public.capacityTips1 + + '
                              • ' + + '
                              • ' + + lan.public.capacityTips2 + + '
                              • ' + + '
                              • ' + + lan.public.capacityTips3 + + '
                              • ' + + '
                              • ' + + lan.public.capacityTips4 + + '
                              • ' + + '
                              • ' + + lan.public.capacityTips5 + + '
                              • ' + + '
                              ' + + '
                              ', + yes: function (indexs) { + var quota_size = $('[name="quota_size"]').val(); + if (type === 'site' || type === 'ftp') { + bt.public.modify_path_quota({ data: JSON.stringify({ size: quota_size, path: row.path }) }, function (res) { + if (res.status) { + bt.msg(res); + layer.close(indexs); + setTimeout(function () { + location.reload(); + }, 200); + } else { + layer.msg(res.msg, { icon: res.status ? 1 : 2, area: '650px', time: 0, shade: 0.3, closeBtn: 2 }); + } + }); + } else { + bt.public.modify_mysql_quota({ data: JSON.stringify({ size: quota_size, db_name: row.name }) }, function (res) { + bt.msg(res); + if (res.status) { + layer.close(indexs); + setTimeout(function () { + location.reload(); + }, 200); + } + }); + } + }, + }); + }, + }; + }, +}; + +//设置面板SSL +function setPanelSSL() { + var loadT = layer.msg(lan.config.ssl_msg, { icon: 16, time: 0, shade: [0.3, '#000'] }); + bt.send('get_cert_source', 'config/get_cert_source', {}, function (rdata) { + layer.close(loadT); + var sdata = rdata; + var _data = { + title: 'Panel SSL', + area: '630px', + class: 'ssl_cert_from', + list: [ + { + html: + '

                              ' + + lan.config.ssl_open_ps + + '

                              • ' + + lan.config.ssl_open_ps_1 + + '
                              • ' + + lan.config.ssl_open_ps_2 + + '
                              • ' + + lan.config.ssl_open_ps_3 + + '
                              ', + }, + { + title: 'Cert Type', + name: 'cert_type', + type: 'select', + width: '200px', + value: sdata.cert_type, + items: [ + { value: '1', title: 'Self-signed certificate' }, + { value: '2', title: "Let's Encrypt" }, + ], + callback: function (obj) { + var subid = obj.attr('name') + '_subid'; + $('#' + subid).remove(); + if (obj.val() == '2') { + var _tr = bt.render_form_line({ + title: 'Admin E-Mail', + name: 'email', + width: '320px', + placeholder: 'Admin E-Mail', + value: sdata.email, + }); + obj.parents('div.line').append('
                              ' + _tr.html + '
                              '); + } + }, + }, + { + html: + '
                              ' + + lan.config.ssl_open_ps_5 + + '

                              ', + }, + ], + btns: [ + { + title: 'Close', + name: 'close', + callback: function (rdata, load, callback) { + load.close(); + $('#panelSSL').prop('checked', false); + }, + }, + { + title: 'Submit', + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + if (!$('#checkSSL').is(':checked')) { + bt.msg({ status: false, msg: 'Please confirm the risk first!' }); + return; + } + var confirm = layer.confirm('Whether to open the panel SSL certificate', { title: 'Tips', btn: ['Confirm', 'Cancel'], icon: 0, closeBtn: 2 }, function () { + var loading = bt.load(); + bt.send('SetPanelSSL', 'config/SetPanelSSL', rdata, function (rdata) { + loading.close(); + if (rdata.status) { + layer.msg(rdata.msg, { icon: 1 }); + $.get('/system?action=ReWeb', function () {}); + setTimeout(function () { + window.location.href = (window.location.protocol.indexOf('https') != -1 ? 'http://' : 'https://') + window.location.host + window.location.pathname; + }, 1500); + } else { + layer.msg(rdata.msg, { icon: 2 }); + } + }); + }); + }, + }, + ], + end: function () {}, + }; + + var _bs = bt.render_form(_data); + // setTimeout(function () { + // $('.cert_type' + _bs).trigger('change') + // }, 200); + }); +} + +var dynamic = { + loadList: [], + fileFunList: {}, + load: false, + callback: null, + + // 初始化执行 + execution: function () { + for (var i = 0; i < this.loadList.length; i++) { + var fileName = this.loadList[i]; + if (fileName in this.fileFunList) this.fileFunList[fileName](); + } + }, + + /** + * @description 动态加载js,css文件 + * @param urls {string|array} 文件路径或文件数组 + * @param fn {function|undefined} 回调函数 + */ + require: function (urls, fn) { + if (!Array.isArray(urls)) urls = [urls]; + + this.fileFunList = {}; + + var i = 0; + var that = this; + var total = urls.length; + var callback = function () { + i++; + if (i < total) { + that.loadFile(urls[i], callback); + } else { + fn && fn(); + } + }; + this.loadFile(urls[i], callback); + }, + /** + * @description 加载js,css文件 + * @param {string} url 文件路径 + * @param {function} fn 回调函数 + */ + loadFile: function (url, fn) { + this.load = true; + + var that = this; + var element = this.createElement(url); + + if (element.readyState) { + element.onreadystatechange = function (ev) { + if (element.readyState === 'loaded' || element.readyState === 'complete') { + element.onreadystatechange = null; + that.execution(); + fn && fn.call(that); + that.load = false; + } + }; + } else { + element.onload = function (ev) { + that.execution(); + fn && fn.call(that); + that.load = false; + }; + } + document.getElementsByTagName('head')[0].appendChild(element); + }, + /** + * @description 创建元素 + * @param {string} url 文件路径 + * @returns + */ + createElement: function (url) { + var element = null; + if (url.indexOf('.js?v=1715756461542') > -1) { + element = document.createElement('script'); + element.type = 'text/javascript'; + element.src = bt.url_merge('/vue/' + url); + } else if (url.indexOf('.css?v=1715756461542') > -1) { + element = document.createElement('link'); + element.rel = 'stylesheet'; + element.href = bt.url_merge('/vue/' + url); + } + return element; + }, + /** + * @default 执行延迟文件内容执行 + * @param fileName {string} 文件名称,不要加文件后缀 + * @param callback {function} 回调行数 + */ + delay: function delay(fileName, callback) { + if (!this.load) { + callback(); + return false; + } + this.fileFunList[fileName] = callback; + }, +}; + +// 过滤编码 +bt.htmlEncode = { + /** + * @description 正则转换特殊字符 + * @param {string} layid 字符内容 + */ + htmlEncodeByRegExp: function (str) { + if (typeof str == 'undefined' || str.length == 0) return ''; + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/ /g, ' ') + .replace(/\'/g, ''') + .replace(/\"/g, '"') + .replace(/\(/g, '(') + .replace(/\)/g, ')') + .replace(/`/g, '`') + .replace(/=/g, '='); + }, +}; diff --git a/BTPanel/static/vite/oldjs/site.js b/BTPanel/static/vite/oldjs/site.js new file mode 100644 index 00000000..7531772f --- /dev/null +++ b/BTPanel/static/vite/oldjs/site.js @@ -0,0 +1,12912 @@ +$('#cutMode .tabs-item').on('click', function () { + var type = $(this).data('type'); + var index = $(this).index(); + $(this).addClass('active').siblings().removeClass('active'); + $('#site_table_view').find('.tab-con-block').eq(index).removeClass('hide').siblings().addClass('hide'); + switch (type) { + case 'php': + $('#bt_site_table').empty(); + if (!window.isSetup) { + $('.site_table_view .mask_layer') + .removeClass('hide') + .find('.prompt_description') + .html( + 'Web server is not installed,Install Nginx  |  Install Apache' + ); + } else { + $('.site_table_view .mask_layer').addClass('hide'); + } + product_recommend.init(function () { + site.php_table_view(); + }); + // site.get_types(); + break; + case 'nodejs': + $('#bt_node_table').empty(); + $.get('/plugin?action=getConfigHtml', { name: 'nodejs' }, function (res) { + // if(typeof res !== 'string') $('.site_table_view .mask_layer').removeClass('hide').find('.prompt_description').html('Node version manager is not installed,Click install'); + if (typeof res !== 'string') { + $('#bt_node_table+.mask_layer') + .removeClass('hide') + .find('.prompt_description') + .html('Node version manager is not installed,Click install'); + } else { + $('#bt_node_table+.mask_layer').addClass('hide'); + } + }); + site.node_porject_view(); + break; + } + bt.set_cookie('site_model', type); +}); + +var site_table; +var node_table; +var countryList = []; +var site = { + node: { + /** + * @description 选择路径配置 + * @return config {object} 选中文件配置 + * + */ + get_project_select_path: function (path) { + var that = this; + return { + type: 'text', + width: '320px', + name: 'project_script', + value: path, + placeholder: 'Please select the project startup file and enter the startup command. It cannot be empty', + icon: { + type: 'glyphicon-folder-open', + select: 'file', + event: function (ev) {}, + }, + }; + }, + get_project_select: function (path) { + var that = this; + return { + type: 'select', + name: 'project_script', + width: '200px', + disabled: true, + unit: '* Get the startup mode in package.json', + placeholder: 'Select the project continue', + list: path + ? function (configs) { + that.get_project_script_list(path, configs[2], this); + } + : [], + change: function (formData, elements, formConfig) { + var project_script = $("[data-name='project_script']"); + if (formData.project_script === '') { + if ($('#project_script_two').length === 0) { + project_script + .parent() + .after( + '
                              ' + ); + } + } else { + project_script.parent().next().remove(); + } + }, + }; + }, + + /** + * @description 选择启动脚本配置 + * @param path {string} 项目目录 + * @param form {object} 表单元素 + * @param formObject {object} 表单对象 + * @return config {object} 选中文件配置 + */ + get_project_script_list: function (path, form, formObject) { + var that = this; + that.get_start_command( + { project_cwd: path }, + function (res) { + var arry = []; + for (var resKey in res) { + arry.push({ title: resKey + ' 【' + res[resKey] + '】', value: resKey }); + } + arry.push({ title: 'Custom command', value: '' }); + form.group = that.get_project_select(path); + form.group.list = arry; + form.group.disabled = false; + formObject.$replace_render_content(2); + if (arry.length === 1) { + var project_script = $("[data-name='project_script']"); + // form.group.value = ''; + project_script + .parent() + .after( + '
                              ' + ); + } + }, + function () { + form.label = 'Start file/command'; + form.group = that.get_project_select_path(path); + formObject.$replace_render_content(2); + } + ); + return []; + }, + + /** + * + * @description 获取Node版本列表 + * @return {{dataFilter: (function(*): *[]), url: string}} + */ + get_node_version_list: function () { + return { + url: '/project/nodejs/get_nodejs_version', + dataFilter: function (res) { + if (res.length === 0) { + layer.closeAll(); + bt.msg({ + status: false, + msg: 'Please open [Node Version Manager], install at least 1 Node version to continue', + }); + return; + } + var arry = []; + for (var i = 0; i < res.length; i++) { + arry.push({ title: res[i], value: res[i] }); + } + return arry; + }, + }; + }, + + /** + * @description 获取Node通用Form配置 + * @param config {object} 获取配置参数 + * @return form模板 + */ + get_node_general_config: function (config) { + config = config || {}; + var that = this, + formLineConfig = [ + { + label: 'Path', + group: { + type: 'text', + width: '350px', + name: 'project_cwd', + readonly: true, + icon: { + type: 'glyphicon-folder-open', + event: function (ev) {}, + callback: function (path) { + var filename = path.split('/'); + var project_script_config = this.config.form[2], + project_name_config = this.config.form[1], + project_ps_config = this.config.form[6]; + project_name_config.group.value = filename[filename.length - 1]; + project_ps_config.group.value = filename[filename.length - 1]; + project_script_config.group.disabled = false; + this.$replace_render_content(1); + this.$replace_render_content(6); + that.get_project_script_list(path, project_script_config, this); + }, + }, + value: bt.get_cookie('sites_path') ? bt.get_cookie('sites_path') : '/www/wwwroot', + placeholder: 'Please select the project directory', + }, + }, + { + label: 'Name', + group: { + type: 'text', + name: 'project_name', + width: '350px', + placeholder: 'Please enter the name of the Node project', + input: function (formData, formElement, formConfig) { + var project_ps_config = formConfig.config.form[6]; + project_ps_config.group.value = formData.project_name; + formConfig.$replace_render_content(6); + }, + }, + }, + { + label: 'Run opt', + group: (function () { + return that.get_project_select(config.path); + })(), + }, + { + label: 'Port', + group: { + type: 'number', + name: 'port', + width: '200px', + placeholder: 'Port of the project', + unit: '* Port of the project', + }, + }, + { + label: 'User', + group: { + type: 'select', + name: 'run_user', + width: '150px', + unit: '* No special requirements,choose www user', + list: [ + { title: 'www', value: 'www' }, + { title: 'root', value: 'root' }, + ], + tips: 'sssss', + }, + }, + { + label: 'Node', + group: { + type: 'select', + name: 'nodejs_version', + width: '150px', + unit: '* Choose the right Node version, Install other', + list: (function () { + return that.get_node_version_list(); + })(), + }, + }, + { + label: 'Remarks', + group: { + type: 'text', + name: 'project_ps', + width: '420px', + placeholder: 'Please enter project remarks', + value: config.ps, + }, + }, + { + label: 'Domain name', + group: { + type: 'textarea', //当前表单的类型 支持所有常规表单元素、和复合型的组合表单元素 + name: 'domains', //当前表单的name + style: { width: '420px', height: '120px', 'line-height': '22px' }, + tips: { + //使用hover的方式显示提示 + text: 'Please enter the domain name to be bound, this option can be empty
                              One domain name per line, the default is port 80
                              Pan-analysis adding method *.domain.com
                              If the format of the additional port is www.domain.com:88', + style: { top: '10px', left: '15px' }, + }, + }, + }, + { + group: { + type: 'help', + list: [ + '[Run opt]: The scripts list in package.json is read by default, or you can select the [Custom Command] option to manually enter the start command', + '[Custom start]: You can select the startup file or directly enter the startup command. Supported startup methods: npm/node/pm2/yarn', + '[Port]:The wrong port will lead to access to 502, if you dont know the port, you can change to the correct port after starting', + '[User]:For security reasons, the www user is used by default to run, and root user running may bring security risks', + ], + }, + }, + ]; + + if (config.path) { + formLineConfig.splice(-1, 1); + return formLineConfig.concat([ + { + label: 'Boot', + group: { + type: 'checkbox', + name: 'is_power_on', + width: '220px', + title: 'Follow the system to start the service', + }, + }, + { + label: '', + group: { + type: 'button', + name: 'saveNodeConfig', + title: 'Save', + event: function (data, form, that) { + if (data.project_cwd === '') { + bt.msg({ status: false, msg: 'The project directory cannot be empty' }); + return false; + } + var project_script_two = $('[name="project_script_two"]'); + if ((data.project_script === '' && project_script_two.length < 1) || (project_script_two.length > 1 && project_script_two.val() === '')) { + bt.msg({ status: false, msg: 'Start file/command cannot be empty' }); + return false; + } + if (data.port === '') { + bt.msg({ status: false, msg: 'Project port cannot be empty' }); + return false; + } + if (data.project_script === '') { + data.project_script = project_script_two.val(); + delete data.project_script_two; + } + config.callback(data, form, that); + }, + }, + }, + ]); + } + return formLineConfig; + }, + + /** + * @description 添加node项目表单 + * @returns {{form: 当前实例对象, close: function(): void}} + */ + add_node_form: function (callback) { + var that = this; + var add_node_project = bt_tools.open({ + title: 'Add Node project', + area: '700px', + btn: ['Confirm', 'Cancel'], + content: { + class: 'pd30', + form: (function () { + return that.get_node_general_config({ + form: add_node_project, + }); + })(), + }, + yes: function (form, indexs, layers) { + var defaultParam = { + bind_extranet: 0, + is_power_on: 1, + max_memory_limit: 4096, + project_env: '', + }; + if (form.domains !== '') { + var arry = form.domains.replace('\n', '').split('\r'), + newArry = []; + for (var i = 0; i < arry.length; i++) { + var item = arry[i]; + if (bt.check_domain(item)) { + newArry.push(item.indexOf(':') > -1 ? item : item + ':80'); + } else { + bt.msg({ + status: false, + msg: '[' + item + '] The format of the bound domain name is incorrect', + }); + break; + } + } + defaultParam.bind_extranet = 1; + defaultParam.domains = newArry; + } + if (form.project_name === '') { + bt.msg({ status: false, msg: 'Project name cannot be empty' }); + return false; + } + var project_script_two = $('[name="project_script_two"]'); + if (project_script_two.length && project_script_two.val() === '') { + bt.msg({ status: false, msg: 'Please enter a custom startup command, it cannot be empty!' }); + return false; + } + if (form.port === '') { + bt.msg({ status: false, msg: 'Project port cannot be empty' }); + return false; + } + if (form.project_script === null) { + bt.msg({ status: false, msg: 'Please select the project directory to get the start command!' }); + return false; + } + form = $.extend(form, defaultParam); + if (project_script_two.length) { + form.project_script = project_script_two.val(); + delete form.project_script_two; + } + var _command = null; + setTimeout(function () { + if (_command < 0) return false; + _command = that.request_module_log_command({ shell: 'tail -f /www/server/panel/logs/npm-exec.log' }); + }, 500); + site.node.add_node_project(form, function (res) { + if (!res.status) _command = -1; + if (_command > 0) layer.close(_command); + if (callback) callback(res, indexs); + }); + }, + }); + return add_node_project; + }, + + /** + * @description 添加node项目请求 + * @param param {object} 请求参数 + * @param callback {function} 回调函数 + */ + add_node_project: function (param, callback) { + this.http({ create_project: false, verify: false }, param, callback); + }, + + /** + * @description 获取Node环境 + * @param callback {function} 回调函数 + */ + get_node_environment: function (callback) { + bt_tools.send( + { + url: '/project/nodejs/is_install_nodejs', + }, + function (res) { + if (callback) callback(res); + }, + { load: 'Get the Node project environment' } + ); + }, + + /** + * @description 编辑Node项目请求 + * @param param {object} 请求参数 + * @param callback {function} 回调函数 + */ + modify_node_project: function (param, callback) { + this.http({ modify_project: 'Modify Node project configuration' }, param, callback); + }, + + /** + * @description 删除Node项目请求 + * @param param {object} 请求参数 + * @param callback {function} 回调函数 + */ + remove_node_project: function (param, callback) { + this.http({ remove_project: 'Delete Node project' }, param, callback); + }, + + /** + * @description 获取node项目域名 + * @param callback {function} 回调行数 + */ + get_node_project_domain: function (callback) { + this.http({ project_get_domain: 'Get the list of Node project domain names' }, callback); + }, + + /** + * @description 获取启动命令列表 + * @param param {object} 请求参数 + * @param callback {function} 成功回调行数 + * @param callback1 {function} 错误回调行数 + */ + get_start_command: function (params, callback, callback1) { + this.http({ get_run_list: 'Getting project start command' }, params, callback, callback1); + }, + /** + * @description 添加Node项目域名 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + add_node_project_domain: function (param, callback) { + this.http({ project_add_domain: false, verify: false }, param, callback); + }, + + /** + * @description 删除Node项目域名 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + remove_node_project_domain: function (param, callback) { + this.http({ project_remove_domain: 'Delete the Node project domain name' }, param, callback); + }, + + /** + * @description 启动Node项目 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + start_node_project: function (param, callback) { + this.http({ start_project: 'Enable Node project' }, param, callback); + }, + + /** + * @description 停止Node项目 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + stop_node_project: function (param, callback) { + this.http({ stop_project: 'Stop the Node project' }, param, callback); + }, + + /** + * @description 重启Node项目 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + restart_node_project: function (param, callback) { + this.http({ restart_project: 'Restart the Node project' }, param, callback); + }, + + /** + * @description 获取值指定Node项目信息 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + get_node_project_info: function (param, callback) { + this.http({ get_project_info: 'Get Node project information' }, param, callback); + }, + + /** + * @description 绑定外网映射 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + bind_node_project_map: function (param, callback) { + this.http({ bind_extranet: 'Mapping', verify: false }, param, callback); + }, + /** + * @description 绑定外网映射 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + unbind_node_project_map: function (param, callback) { + this.http({ unbind_extranet: 'Unmapping', verify: false }, param, callback); + }, + /** + * @description 安装node项目依赖 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + install_node_project_packages: function (param, callback) { + this.http({ install_packages: false, verify: false }, param, callback); + }, + + /** + * @description 安装指定模块 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + npm_install_node_module: function (param, callback) { + this.http({ install_module: 'Install Node module' }, param, callback); + }, + /** + * @description 更新指定模块 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ + upgrade_node_module: function (param, callback) { + this.http({ upgrade_module: 'Update Node module' }, param, callback); + }, + /** + * @description 删除指定模块 + * @param param {object} 请求参数 + * @param callback {function} 回调行数 + */ uninstall_node_module: function (param, callback) { + this.http({ uninstall_module: 'Uninstall the Node module' }, param, callback); + }, + /** + * @description 模拟点击 + */ + simulated_click: function (num) { + $('.bt-w-menu p:eq(' + num + ')').click(); + }, + /** + * @description 获取Node项目信息 + * @param row {object} 当前行,项目信息 + */ + set_node_project_view: function (row) { + var that = this; + bt.open({ + type: 1, + title: 'Node project management-[' + row.name + '], add time [' + row.addtime + ']', + skin: 'node_project_dialog', + area: ['860px', '750px'], + content: + '
                              ' + + '' + + '
                              ' + + '
                              ' + + '
                              Please turn on mapping to viewing the configuration information
                              ' + + '
                              ', + btn: false, + success: function (layers) { + var $layers = $(layers), + $content = $layers.find('#webedit-con'); + + function reander_tab_list(config) { + for (var i = 0; i < config.list.length; i++) { + var item = config.list[i], + tab = $('

                              ' + item.title + '

                              '); + $(config.el).append(tab); + (function (i, item) { + tab.on('click', function (ev) { + $('.mask_module').addClass('hide'); + $(this).addClass('bgw').siblings().removeClass('bgw'); + if ($(this).hasClass('bgw')) { + that.get_node_project_info({ project_name: row.name }, function (res) { + config.list[i].event.call(that, $content, res, ev); + }); + } + }); + if (item.active) tab.click(); + })(i, item); + } + } + + reander_tab_list({ + el: $layers.find('.bt-w-menu'), + list: [ + { + title: 'Project config', + active: true, + event: that.reander_node_project_config, + }, + { + title: 'Domain', + event: that.reander_node_domain_manage, + }, + { + title: 'Mapping', + event: that.reander_node_project_map, + }, + { + title: 'URL rewrite', + event: that.reander_node_project_rewrite, + }, + { + title: 'Config file', + event: that.reander_node_file_config, + }, + { + title: 'SSL', + event: that.reander_node_project_ssl, + }, + { + title: 'Load status', + event: that.reander_node_service_condition, + }, + { + title: 'service status', + event: that.reander_node_service_status, + }, + { + title: 'Module', + event: that.reander_node_project_module, + }, + { + title: 'Project log', + event: that.reander_node_project_log, + }, + { + title: 'Website log', + event: that.reander_node_site_log, + }, + ], + }); + }, + }); + }, + + /** + * @description 渲染Node项目配置视图 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + * @param that {object} 当前node项目对象 + */ + reander_node_project_config: function (el, rows) { + var row = $.extend(true, {}, rows); + var that = this, + edit_node_project = bt_tools.form({ + el: '#webedit-con', + data: row.project_config, + class: 'ptb10', + form: (function () { + var fromConfig = that.get_node_general_config({ + form: edit_node_project, + path: row.path, + ps: row.ps, + callback: function (data, form, formNew) { + data['is_power_on'] = data['is_power_on'] ? 1 : 0; + var project_script_two = $('[name="project_script_two"]'); + if (project_script_two.length && project_script_two.val() === '') { + bt.msg({ + status: false, + msg: 'Please enter a custom startup command, it cannot be empty!', + }); + return false; + } + if (form.port === '') { + bt.msg({ status: false, msg: 'Project port cannot be empty' }); + return false; + } + if (form.project_script === null) { + bt.msg({ + status: false, + msg: 'Please select the project directory to get the start command!', + }); + return false; + } + site.node.modify_node_project(data, function (res) { + if (res.status) { + row['project_config'] = $.extend(row, data); + row['path'] = data.project_script; + row['ps'] = data.ps; + } + bt.msg({ status: res.status, msg: res.data }); + site.node.simulated_click(0); + }); + }, + }); + setTimeout(function () { + var is_existence = false, + list = fromConfig[2].group.list; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + if (item.value === rows.project_config.project_script) { + is_existence = true; + break; + } + } + if (!is_existence && list.length > 1) { + $('[data-name="project_script"] li:eq(' + (list.length - 1) + ')').click(); + $('[name="project_script_two"]').val(rows.project_config.project_script); + } + if (list.length === 1) { + $('[data-name="project_script"] li:eq(0)').click(); + $('[name="project_script_two"]').val(rows.project_config.project_script); + } + }, 250); + + fromConfig[1].group.disabled = true; + fromConfig[fromConfig.length - 3].hide = true; + fromConfig[fromConfig.length - 3].group.disabled = true; + return fromConfig; + })(), + }); + setTimeout(function () { + $(el).append( + '
                                ' + + '
                              • [Run opt]: The scripts list in package.json is read by default, or you can select the [Custom Command] option to manually enter the start command
                              • ' + + '
                              • [Custom command]: You can select the startup file or directly enter the startup command. Supported startup methods: npm/node/pm2/yarn
                              • ' + + '
                              • [Port]:The wrong port will lead to access to 502, if you don’t know the port, you can fill it out at will, and then change to the correct port after starting the project
                              • ' + + '
                              • [User]:For security reasons, the www user is used by default to run, and root user running may bring security risks
                              • ' + + '
                              ' + ); + if (!row.listen_ok) + $(el) + .find('input[name="port"]') + .parent() + .after( + '
                              The project port may be wrong, it is detected that the current project listens to the following ports[ ' + + row.listen.join('/') + + ' ]
                              ' + ); + }, 100); + }, + + /** + * @description 渲染Node项目服务状态 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_service_status: function (el, row) { + var arry = [ + { title: 'Start', event: this.start_node_project }, + { title: 'Stop', event: this.stop_node_project }, + { title: 'Restart', event: this.restart_node_project }, + ], + that = this, + html = $('

                              '); + + function reander_service(status) { + var status_info = status ? ['Start', '#20a53a', 'play'] : ['Stop', 'red', 'pause']; + return 'Status: ' + status_info[0] + ''; + } + + html.find('.status').html(reander_service(row.run)); + el.html(html); + for (var i = 0; i < arry.length; i++) { + var item = arry[i], + btn = $(''); + (function (btn, item, indexs) { + !(row.run && indexs === 0) || btn.addClass('hide'); + !(!row.run && indexs === 1) || btn.addClass('hide'); + btn + .on('click', function () { + bt.confirm( + { + title: item.title + 'Project-[' + row.name + ']', + msg: 'Are you sure you want the' + item.title + 'item,' + (row.run ? 'The project may be affected,' : '') + 'continue?', + }, + function (index) { + layer.close(index); + item.event.call(that, { project_name: row.name }, function (res) { + row.run = indexs === 0 ? true : indexs === 1 ? false : row.run; + html.find('.status').html(reander_service(row.run)); + $('.sfm-opt button').eq(0).addClass('hide'); + $('.sfm-opt button').eq(1).addClass('hide'); + $('.sfm-opt button') + .eq(row.run ? 1 : 0) + .removeClass('hide'); + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + }); + } + ); + }) + .text(item.title); + })(btn, item, i); + el.find('.sfm-opt').append(btn); + } + }, + + /** + * @description 渲染Node项目域名管理 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_domain_manage: function (el, row) { + var that = this, + list = [ + { + class: 'mb0', + items: [ + { + name: 'nodedomain', + width: '340px', + type: 'textarea', + placeholder: + 'Please enter the domain name to be mapped, this option can be empty
                              One domain name per line, default port 80
                              How to add wildcard domain names *.domain.com
                              Specify the port used www.domain.com:88', + }, + { + name: 'btn_node_submit_domain', + text: 'Add', + type: 'button', + callback: function (sdata) { + var arrs = sdata.nodedomain.split('\n'); + var domins = []; + for (var i = 0; i < arrs.length; i++) domins.push(arrs[i]); + that.add_node_project_domain({ project_name: row.name, domains: domins }, function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + if (res.status) { + $('[name=nodedomain]').val(''); + $('.placeholder').css('display', 'block'); + project_domian.$refresh_table_list(true); + } + }); + }, + }, + ], + }, + ]; + var _form_data = bt.render_form_line(list[0]), + loadT = null, + placeholder = null; + el.html(_form_data.html + '
                              '); + bt.render_clicks(_form_data.clicks); + // domain样式 + $('.btn_node_submit_domain').addClass('pull-right').css('margin', '30px 35px 0 0'); + $('textarea[name=nodedomain]').css('height', '120px'); + placeholder = $('.placeholder'); + placeholder + .click(function () { + $(this).hide(); + $('.nodedomain').focus(); + }) + .css({ + width: '340px', + heigth: '120px', + left: '0px', + top: '0px', + 'padding-top': '10px', + 'padding-left': '15px', + }); + $('.nodedomain') + .focus(function () { + placeholder.hide(); + loadT = layer.tips(placeholder.html(), $(this), { tips: [1, '#20a53a'], time: 0, area: $(this).width() }); + }) + .blur(function () { + if ($(this).val().length == 0) placeholder.show(); + layer.close(loadT); + }); + var project_domian = bt_tools.table({ + el: '#project_domian_list', + url: '/project/nodejs/project_get_domain', + default: 'No domain name list yet', + param: { project_name: row.name }, + height: 375, + beforeRequest: function (params) { + if (params.hasOwnProperty('data') && typeof params.data === 'string') return params; + return { data: JSON.stringify(params) }; + }, + column: [ + { type: 'checkbox', class: '', width: 20 }, + { + fid: 'name', + title: 'Domain Name', + type: 'text', + template: function (row) { + return '' + row.name + ''; + }, + }, + { + fid: 'port', + title: 'Port', + type: 'text', + }, + { + title: 'OPT', + type: 'group', + width: '100px', + align: 'right', + group: [ + { + title: 'Del', + event: function (rowc, index, ev, key, rthat) { + bt.confirm( + { + title: 'Delete domain [ ' + row.name + ' ]', + msg: lan.site.domain_del_confirm, + }, + function () { + that.remove_node_project_domain( + { + project_name: row.name, + domain: rowc.name + ':' + rowc.port, + }, + function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + rthat.$refresh_table_list(true); + } + ); + } + ); + }, + }, + ], + }, + ], + tootls: [ + { + // 批量操作 + type: 'batch', + positon: ['left', 'bottom'], + placeholder: 'Please select bulk operation', + buttonValue: 'Batch operation', + disabledSelectValue: 'Please select the site that needs batch operation!', + selectList: [ + { + title: 'Delete domain name', + load: true, + url: '/project/nodejs/project_remove_domain', + param: function (crow) { + return { + data: JSON.stringify({ + project_name: row.name, + domain: crow.name + ':' + crow.port, + }), + }; + }, + callback: function (that) { + // 手动执行,data参数包含所有选中的站点 + bt.show_confirm('Delete domain names in bulk', "Delete the selected domain name at the same time, do you want to continue?", function () { + var param = {}; + that.start_batch(param, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + item.name + + '
                              ' + + (item.request.status ? 'Success' : 'Fail') + + '
                              '; + } + project_domian.$batch_success_table({ + title: 'Batch deletion', + th: 'Delete domain name', + html: html, + }); + project_domian.$refresh_table_list(true); + }); + }); + }, + }, + ], + }, + ], + }); + setTimeout(function () { + $(el).append( + '
                                ' + + '
                              • If yours is an HTTP project and needs to be mapped, please bind at least one domain name
                              • ' + + '
                              • It is recommended that all domain names use the default port 80
                              • ' + + '
                              ' + ); + }, 100); + }, + + /** + * @description 渲染Node项目映射 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_project_map: function (el, row) { + var that = this; + el.html( + '
                              ' + + ' Mapping' + + '
                              ' + + ' ' + + ' ' + + '
                              ' + + '
                              • If your project is an HTTP project and you need to access the Internet through 80443, please use the mapping
                              • Before using the mapping, please add at least 1 domain name in [Domain Name Management]
                              ' + ); + $('#node_project_map').attr('checked', row['project_config']['bind_extranet'] ? true : false); + $('[name=node_project_map]').click(function () { + var _check = $('#node_project_map').prop('checked'), + param = { project_name: row.name }; + if (!_check) param['domains'] = row['project_config']['domains']; + layer.confirm( + (!_check ? 'Enable' : 'Disable') + ' mapping!,do you want to continue?', + { + btn: ['Confirm', 'Cancel'], + title: 'Mapping', + icon: 0, + closeBtn: 2, + cancel: function () { + $('#node_project_map').attr('checked', _check); + }, + }, + function () { + that[_check ? 'unbind_node_project_map' : 'bind_node_project_map'](param, function (res) { + if (!res.status) $('#node_project_map').attr('checked', _check); + bt.msg({ status: res.status, msg: typeof res.data != 'string' ? res.error_msg : res.data }); + row['project_config']['bind_extranet'] = _check ? 0 : 1; + }); + }, + function () { + $('#node_project_map').attr('checked', _check); + } + ); + }); + }, + + /** + * @description 渲染Node项目模块 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_project_module: function (el, row) { + var that = this; + el.html( + '
                              ' + + '
                              ' + + '
                              ' + + '
                              ' + ); + var node_project_module_table = bt_tools.table({ + el: '#node_module_list', + url: '/project/nodejs/get_project_modules', + default: 'The module is not installed, click one-click to install the project module, the default prompt when the data is empty', + param: { project_name: row.name, project_cwd: row.path }, + height: '576px', + load: 'Retrieving module list, please wait...', + beforeRequest: function (params) { + if (params.hasOwnProperty('data') && typeof params.data === 'string') return params; + return { data: JSON.stringify(params) }; + }, + column: [ + { + fid: 'name', + title: 'Module', + type: 'text', + }, + { + fid: 'version', + title: 'Ver', + type: 'text', + width: '60px', + }, + { + fid: 'license', + title: 'License', + type: 'text', + template: function (row) { + if (typeof row.license === 'object') return '' + row.license.type + ''; + return '' + row.license + ''; + }, + }, + { + fid: 'description', + title: 'Description', + width: 235, + type: 'text', + template: function (row) { + return '' + row.description + ''; + }, + }, + { + title: 'OPT', + type: 'group', + width: '125px', + align: 'right', + group: [ + { + title: 'Update', + event: function (rowc, index, ev, key, rthat) { + bt.show_confirm('Update module', "Updating the [" + rowc.name + '] module may affect the operation of the project, continue?', function () { + that.upgrade_node_module({ project_name: row.name, mod_name: rowc.name }, function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + rthat.$refresh_table_list(true); + }); + }); + }, + }, + { + title: 'Uninstall', + event: function (rowc, index, ev, key, rthat) { + bt.show_confirm('Uninstall the module', "Uninstalling the [" + rowc.name + '] module may affect the operation of the project, continue?', function () { + that.uninstall_node_module( + { + project_name: row.name, + mod_name: rowc.name, + }, + function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + rthat.$refresh_table_list(true); + } + ); + }); + }, + }, + ], + }, + ], + success: function (config) { + // 隐藏一键安装 + if (config.data.length > 0) $('.npm_install_node_config').addClass('hide'); + }, + }); + //安装模块 + $('.install_node_module').on('click', function () { + var _mname = $('input[name=mname]').val(); + if (!_mname) return layer.msg('Please enter the module name and version', { icon: 2 }); + that.npm_install_node_module({ project_name: row.name, mod_name: _mname }, function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + node_project_module_table.$refresh_table_list(true); + }); + }); + //一键安装项目模块 + $('.npm_install_node_config').on('click', function () { + var _command = that.request_module_log_command({ shell: 'tail -f /www/server/panel/logs/npm-exec.log' }); + that.install_node_project_packages({ project_name: row.name }, function (res) { + if (res.status) { + node_project_module_table.$refresh_table_list(true); + } + layer.close(_command); + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + }); + }); + }, + + /** + * @description 渲染Node项目伪静态 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_project_rewrite: function (el, row) { + el.empty(); + if (row.project_config.bind_extranet === 0) { + $('.mask_module').removeClass('hide').find('.node_mask_module_text:eq(1)').hide().prev().show(); + return false; + } + site.edit.get_rewrite_list({ name: 'node_' + row.name }, function () { + $('.webedit-box .line:first').remove(); + $('[name=btn_save_to]').remove(); + $('.webedit-box .help-info-text li:first').remove(); + }); + }, + /** + * @description 渲染Node配置文件 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_file_config: function (el, row) { + el.empty(); + if (row.project_config.bind_extranet === 0) { + $('.mask_module').removeClass('hide').find('.node_mask_module_text:eq(1)').hide().prev().show(); + return false; + } + site.edit.set_config({ name: 'node_' + row.name }); + }, + /** + * @description 渲染node项目使用情况 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_service_condition: function (el, row) { + if (!row.run) { + el.html('').next().removeClass('hide'); + if (el.next().find('.node_mask_module_text').length === 1) { + el.next() + .find('.node_mask_module_text') + .hide() + .parent() + .append( + '
                              Please start the service first and try again,Set service status
                              ' + ); + } else { + el.next().find('.node_mask_module_text:eq(1)').show().prev().hide(); + } + return false; + } + el.html( + '
                              PID
                              ' + ); + var _option = '', + tabelCon = ''; + for (var load in row.load_info) { + if (row.load_info.hasOwnProperty(load)) { + _option += ''; + } + } + var node_pid = $('[name=node_project_pid]'); + node_pid.html(_option); + node_pid + .change(function () { + var _pid = $(this).val(), + rdata = row['load_info'][_pid], + fileBody = '', + connectionsBody = ''; + for (var i = 0; i < rdata.open_files.length; i++) { + var itemi = rdata.open_files[i]; + fileBody += + '' + + '' + + itemi['path'] + + '' + + '' + + itemi['mode'] + + '' + + '' + + itemi['position'] + + '' + + '' + + itemi['flags'] + + '' + + '' + + itemi['fd'] + + '' + + ''; + } + for (var k = 0; k < rdata.connections.length; k++) { + var itemk = rdata.connections[k]; + connectionsBody += + '' + + '' + + itemk['client_addr'] + + '' + + '' + + itemk['client_rport'] + + '' + + '' + + itemk['family'] + + '' + + '' + + itemk['fd'] + + '' + + '' + + itemk['local_addr'] + + '' + + '' + + itemk['local_port'] + + '' + + '' + + itemk['status'] + + '' + + ''; + } + + // tabelCon = reand_table_config([ + // [{"名称":rdata.name},{"PID":rdata.pid},{"状态":rdata.status},{"父进程":rdata.ppid}], + // [{"用户":rdata.user},{"Socket":rdata.connects},{"CPU":rdata.cpu_percent},{"线程":rdata.threads}], + // [{"内存":rdata.user},{"io读":rdata.connects},{"io写":rdata.cpu_percent},{"启动时间":rdata.threads}], + // [{"启动命令":rdata.user}], + // ]) + // + // console.log(tabelCon) + // + // + // function reand_table_config(conifg){ + // var html = ''; + // for (var i = 0; i < conifg.length; i++) { + // var item = conifg[i]; + // html += ''; + // for (var j = 0; j < item; j++) { + // var items = config[j],name = Object.keys(items)[0]; + // console.log(items,name) + // html += ''+ name +''+ items[name] +'' + // } + // console.log(html) + // html += '' + // } + // return '
                              '+ html +'
                              '; + // } + + tabelCon = + '
                              ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
                              Name' + + rdata.name + + 'Status' + + rdata.status + + 'User' + + rdata.user + + 'Start Time' + + getLocalTime(rdata.create_time) + + '
                              PID' + + rdata.pid + + 'PPID' + + rdata.ppid + + 'Thread' + + rdata.threads + + 'Socket' + + rdata.connects + + '
                              CPU' + + rdata.cpu_percent + + '%RAM' + + ToSize(rdata.memory_used) + + 'Disk/R' + + ToSize(rdata.io_read_bytes) + + 'Dis/W' + + ToSize(rdata.io_write_bytes) + + '
                              Command' + + rdata.exe + + '
                              ' + + '
                              ' + + '

                              Network

                              ' + + '
                              ' + + '
                              ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + connectionsBody + + '' + + '
                              Client addressClient portProtocolFDlocal addresslocal portStatus
                              ' + + '
                              ' + + '
                              ' + + '

                              Open files

                              ' + + '
                              ' + + '
                              ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + fileBody + + '' + + '
                              Filesmodepositionflagsfd
                              ' + + '
                              ' + + '
                              '; + $('.node_project_pid_datail').html(tabelCon); + bt_tools.$fixed_table_thead('#nodeNetworkList'); + bt_tools.$fixed_table_thead('#nodeFileList'); + }) + .change() + .html(_option); + }, + + /** + * @description 渲染Node项目日志 + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_project_log: function (el, row) { + el.html('
                              '); + bt_tools.send( + { + url: '/project/nodejs/get_project_log', + type: 'GET', + data: { data: JSON.stringify({ project_name: row.name }) }, + }, + function (res) { + $('#webedit-con .node_project_log').html('
                              ' + (typeof res == 'object' ? res.error_msg : res) + '
                              '); + $('.command_output_pre').scrollTop($('.command_output_pre').prop('scrollHeight')); + }, + { load: 'Get Node project log', verify: false } + ); + }, + + reander_node_site_log: function (el, row) { + el.empty(); + if (row.project_config.bind_extranet === 0) { + $('.mask_module').removeClass('hide').find('.node_mask_module_text:eq(1)').hide().prev().show(); + return false; + } + site.edit.get_site_logs({ name: row.name }); + }, + + /** + * @description node项目SSL + * @param el {object} 当前element节点 + * @param row {object} 当前项目数据 + */ + reander_node_project_ssl: function (el, row) { + el.empty(); + if (row.project_config.bind_extranet === 0) { + $('.mask_module').removeClass('hide').find('.node_mask_module_text:eq(1)').hide().prev().show(); + return false; + } + site.set_ssl({ name: row.name, ele: el, id: row.id }); + site.ssl.reload(); + }, + /** + * @description 请求模块日志终端 + * @param config {object} 当前配置数据 + */ + request_module_log_command: function (config) { + var r_command = layer.open({ + title: config.name || 'Module is being installed, please wait...', + type: 1, + closeBtn: 0, + area: ['500px', '342px'], + skin: config.class || 'module_commmand', + shadeClose: false, + content: '
                              ', + success: function () { + bt_tools.command_line_output({ + el: '.site_module_command', + shell: config.shell, + area: config.area || ['100%', '300px'], + }); + }, + }); + return r_command; + }, + + /** + * @description 请求封装 + * @param keyMethod 接口名和loading,键值对 + * @param param {object || function} 参数,可为空,为空则为callback参数 + * @param callback {function} 成功回调函数 + * @param callback1 {function} 错误调函数 + */ + http: function (keyMethod, param, callback, callback1) { + var method = Object.keys(keyMethod), + config = { + url: '/project/nodejs/' + method[0], + data: (param && { data: JSON.stringify(param) }) || {}, + }, + success = function (res) { + callback && callback(res); + }; + if (callback1) { + bt_tools.send(config, success, callback1, { + load: keyMethod[method[0]], + verify: method[1] ? keyMethod[method[1]] : true, + }); + } else { + bt_tools.send(config, success, { + load: keyMethod[method[0]], + verify: method[1] ? keyMethod[method[1]] : true, + }); + } + }, + }, + node_porject_view: function () { + var node_table = bt_tools.table({ + el: '#bt_node_table', + url: '/project/nodejs/get_project_list', + minWidth: '1000px', + autoHeight: true, + default: 'The item list is empty', //数据为空时的默认提示\ + load: 'Getting the list of Node projects, please wait...', + beforeRequest: function (params) { + if (params.hasOwnProperty('data') && typeof params.data === 'string') { + var oldParams = JSON.parse(params['data']); + delete params['data']; + return { data: JSON.stringify($.extend(oldParams, params)) }; + } + return { data: JSON.stringify(params) }; + }, + column: [ + { type: 'checkbox', class: '', width: 20 }, + { + fid: 'name', + title: 'Name', + width: 85, + type: 'link', + event: function (row, index, ev) { + site.node.set_node_project_view(row); + }, + template: function (row, index) { + return ( + '' + + row.name + + '' + ); + }, + }, + { + fid: 'run', + title: 'Status', + width: 85, + config: { + icon: true, + list: [ + [true, 'Running', 'bt_success', 'glyphicon-play'], + [false, 'Stop', 'bt_danger', 'glyphicon-pause'], + ], + }, + type: 'status', + event: function (row, index, ev, key, that) { + var status = row.run; + bt.confirm( + { + title: status ? 'Stop project' : 'Startup project', + msg: status ? 'After stopping the project, the project service will stop running, continue?' : 'Startup Node project [' + row.name + '], continue operation?', + }, + function (index) { + layer.close(index); + site.node[status ? 'stop_node_project' : 'start_node_project']({ project_name: row.name }, function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + that.$refresh_table_list(true); + }); + } + ); + }, + }, + { + fid: 'pid', + title: 'PID', + width: 160, + type: 'text', + template: function (row) { + if ($.isEmptyObject(row['load_info'])) return '-'; + var _id = []; + for (var i in row.load_info) { + if (row.load_info.hasOwnProperty(i)) { + _id.push(i); + } + } + return '' + _id.join(',') + ''; + }, + }, + { + title: 'CPU', + type: 'text', + width: 70, + template: function (row) { + if ($.isEmptyObject(row['load_info'])) return '-'; + var _cpu_total = 0; + for (var i in row.load_info) { + _cpu_total += row.load_info[i]['cpu_percent']; + } + return '' + _cpu_total.toFixed(2) + '%'; + }, + }, + { + title: 'RAM', + type: 'text', + width: 80, + template: function (row) { + if ($.isEmptyObject(row['load_info'])) return '-'; + var _cpu_total = 0; + for (var i in row.load_info) { + _cpu_total += row.load_info[i]['memory_used']; + } + return '' + bt.format_size(_cpu_total) + ''; + }, + }, + { + fid: 'path', + title: 'Root directory', + tips: 'Open Directory', + type: 'link', + event: function (row, index, ev) { + openPath(row.path); + }, + template: function (row, index) { + return ( + '' + + row.path + + '' + ); + }, + }, + { + fid: 'node_version', + title: 'Node version', + type: 'text', + width: 102, + template: function (row) { + return '' + row['project_config']['nodejs_version'] + ''; + }, + }, + { + fid: 'ps', + title: 'Remark', + type: 'input', + blur: function (row, index, ev, key, that) { + if (row.ps == ev.target.value) return false; + bt.pub.set_data_ps({ id: row.id, table: 'sites', ps: ev.target.value }, function (res) { + bt_tools.msg(res, { is_dynamic: true }); + }); + }, + keyup: function (row, index, ev) { + if (ev.keyCode === 13) { + $(this).blur(); + } + }, + }, + { + fid: 'ssl', + title: 'SSL', + tips: 'Deployment certificate', + width: 100, + type: 'text', + template: function (row, index) { + var _ssl = row.ssl, + _info = '', + _arry = [ + ['issuer', 'issuer'], + ['notAfter', 'Due date'], + ['notBefore', 'Application date'], + ['dns', 'Available domain names'], + ]; + try { + if (typeof row.ssl.endtime != 'undefined') { + if (row.ssl.endtime < 0) { + return 'Exp in ' + Math.row.ssl.endtime + ' days'; + } + } + } catch (error) {} + for (var i = 0; i < _arry.length; i++) { + var item = _ssl[_arry[i][0]]; + _info += _arry[i][1] + ':' + item + (_arry.length - 1 != i ? '\n' : ''); + } + return row.ssl === -1 + ? 'Not Set' + : 'Exp in ' + row.ssl.endtime + ' days'; + }, + event: function (row) { + site.node.set_node_project_view(row); + setTimeout(function () { + $('.site-menu p:eq(5)').click(); + }, 500); + }, + }, + { + title: 'OPT', + type: 'group', + width: 100, + align: 'right', + group: [ + { + title: 'Set', + event: function (row, index, ev, key, that) { + site.node.set_node_project_view(row); + }, + }, + { + title: 'Del', + event: function (row, index, ev, key, that) { + bt.prompt_confirm('Delete item', 'You are deleting the Node project-[' + row.name + '], continue?', function () { + site.node.remove_node_project({ project_name: row.name }, function (res) { + bt.msg({ status: res.status, msg: res.data || res.error_msg }); + node_table.$refresh_table_list(true); + }); + }); + }, + }, + ], + }, + ], + sortParam: function (data) { + return { order: data.name + ' ' + data.sort }; + }, + // 渲染完成 + tootls: [ + { + // 按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Add Node project', + active: true, + event: function (ev) { + site.node.add_node_form(function (res, index) { + if (res.status) { + layer.close(index); + node_table.$refresh_table_list(true); + } + bt.msg({ + status: res.status, + msg: (!Array.isArray(res.data) ? res.data : false) || res.error_msg, + }); + }); + }, + }, + { + title: 'Node version manager', + event: function (ev) { + bt.soft.set_lib_config('nodejs', 'Node.js version manager'); + }, + }, + ], + }, + { + // 搜索内容 + type: 'search', + positon: ['right', 'top'], + placeholder: 'Please enter the project name', + searchParam: 'search', //搜索请求字段,默认为 search + value: '', // 当前内容,默认为空 + }, + { + // 批量操作 + type: 'batch', //batch_btn + positon: ['left', 'bottom'], + placeholder: 'Please select bulk operation', + buttonValue: 'Batch operation', + disabledSelectValue: 'Please select the site that needs batch operation!', + selectList: [ + { + title: 'Delete item', + url: '/project/nodejs/remove_project', + param: function (row) { + return { + data: JSON.stringify({ project_name: row.name }), + }; + }, + refresh: true, + callback: function (that) { + bt.prompt_confirm('Delete items in bulk', 'You are deleting the selected Node project. Continue?', function () { + that.start_batch({}, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + item.name + + '
                              ' + + (item.requests.status ? item.requests.data : item.requests.error_msg) + + '
                              '; + } + node_table.$batch_success_table({ + title: 'Delete items in bulk', + th: 'project name', + html: html, + }); + node_table.$refresh_table_list(true); + }); + }); + }, + }, + ], + }, + { + //分页显示 + type: 'page', + positon: ['right', 'bottom'], // 默认在右下角 + pageParam: 'p', //分页请求字段,默认为 : p + page: 1, //当前分页 默认:1 + numberParam: 'limit', + //分页数量请求字段默认为 : limit + number: 20, + //分页数量默认 : 20条 + numberList: [10, 20, 50, 100, 200], // 分页显示数量列表 + numberStatus: true, // 是否支持分页数量选择,默认禁用 + jump: true, //是否支持跳转分页,默认禁用 + }, + ], + }); + }, + php_table_view: function () { + var hoverInfo = {}; + $('#bt_site_table').empty(); + + $('.site_table_view').after( + '
                              \ +
                              \ +
                              \ + ' + + lan.site.copy + + '\ + ' + + lan.site.rename + + '\ +
                              \ +
                              ' + ); + + // 点击复制网站 + $('.web_name_copy') + .unbind() + .click(function (e) { + var clipboard = new ClipboardJS('#site_name_copy'); + clipboard.on('success', function (e) { + bt.msg({ + msg: lan.mail_sys.success_copy, + icon: 1, + }); + }); + clipboard.on('error', function (e) { + bt.msg({ + msg: lan.pgsql.cp_fail, + icon: 2, + }); + }); + $('#site_name_copy').attr('data-clipboard-text', hoverInfo.site); + $('#site_name_copy').click(function (e) { + e.stopPropagation(); + }); + $('#site_name_copy').click(); + }); + + // 点击重命名网站 + $('.web_name_rename') + .unbind() + .click(function (e) { + var site_name = hoverInfo.site, + site_id = hoverInfo.id; + $(this) + .parent() + .prev() + .find('.web_name_text') + .html('') + .find('input') + .focus() + .click(function (e) { + e.stopPropagation(); + }); + $(this) + .parent() + .prev() + .find('.web_name_text') + .find('input') + .blur(function () { + var new_name = $(this).val(); + if (new_name == site_name) { + $('.web_name_hover').addClass('hide'); + return; + } + if (new_name != '') { + bt_tools.send( + { + url: '/site?action=site_rname', + data: { id: site_id, rname: escapeXml(new_name) }, + }, + function (res) { + site_table.$refresh_table_list(true); + bt.msg(res); + $('.web_name_hover').addClass('hide'); + } + ); + } + }); + $(this) + .parent() + .prev() + .find('.web_name_text') + .find('input') + .keyup(function (e) { + if (e.keyCode == 13) { + $(this).blur(); + } + }); + // 防止网站xss + function escapeXml(unsafe) { + return unsafe.replace(/[<>&'"]/g, function (c) { + switch (c) { + case '<': + return '<'; + case '>': + return '>'; + case '&': + return '&'; + case "'": + return '''; + case '"': + return '"'; + } + }); + } + e.stopPropagation(); + }); + + site_table = bt_tools.table({ + el: '#bt_site_table', + url: '/data?action=getData', + cookiePrefix: 'site_table', // cookie前缀,用于状态存储,如果不设置,着所有状态不存储, + param: { table: 'sites' }, //参数 + minWidth: '1000px', + autoHeight: true, + default: 'Site list is empty', // 数据为空时的默认提示 + beforeRequest: function (param) { + param.type = bt.get_cookie('site_type') || -1; + return param; + }, + column: [ + { type: 'checkbox', class: '', width: 20 }, + { + fid: 'rname', + title: lan.site.site_name, + sort: true, + sortValue: 'asc', + class: 'site_name', + type: 'link', + width: 130, + isDisabled: true, + event: function (row, index, ev) { + site.web_edit(row, true); + }, + // template: function (row, index) { + // return ''; + // } + template: function (row, index) { + var install = false; + var recomConfig = product_recommend.get_recommend_type(5); + if (recomConfig) { + for (var j = 0; j < recomConfig['list'].length; j++) { + var item = recomConfig['list'][j]; + if (item.name == 'btwaf' || item.name == 'btwaf_httpd') { + if (item.install === true) install = true; + } + } + } + if (bt.get_cookie('serverType') == 'openlitespeed') { + return ( + '' + ); + } else { + var color = !$.isEmptyObject(row.waf) && row.waf.status && install ? 'green' : 'grey'; + return ( + '
                              \ + \ + ' + + row.rname + + '\ + \ +
                              ' + ); + } + }, + }, + { + fid: 'status', + title: lan.site.status, + sort: true, + width: 85, + config: { + icon: true, + list: [ + ['1', lan.site.running_text, 'bt_success', 'glyphicon-play'], + ['0', lan.site.stopped, 'bt_danger', 'glyphicon-pause'], + ], + }, + type: 'status', + event: function (row, index, ev, key, that) { + bt.site[parseInt(row.status) ? 'stop' : 'start'](row.id, row.name, function (res) { + if (res.status) that.$modify_row_data({ status: parseInt(row.status) ? '0' : '1' }); + }); + }, + }, + { + fid: 'backup_count', + title: lan.site.backup, + width: 80, + type: 'link', + template: function (row, index) { + var backup = lan.site.backup_no, + _class = 'bt_warning'; + if (row.backup_count > 0) (backup = lan.site.backup_yes), (_class = 'bt_success'); + return '' + backup + (row.backup_count > 0 ? '(' + row.backup_count + ')' : '') + ''; + }, + event: function (row, index) { + site.backup_site_view({ id: row.id, name: row.name }, site_table); + }, + }, + { + fid: 'path', + title: lan.site.root_dir, + tips: 'Open path', + type: 'link', + event: function (row, index, ev) { + openPath(row.path); + }, + template: function (row, index) { + return ''; + }, + }, + bt.public.get_quota_config('site'), + { + fid: 'edate', + title: lan.site.endtime, + width: 115, + class: 'set_site_edate', + sort: true, + type: 'link', + template: function (row, index) { + var _endtime = row.edate || row.endtime; + if (_endtime === '0000-00-00') { + return lan.site.web_end_time; + } else { + if (new Date(_endtime).getTime() < new Date().getTime()) { + return '' + _endtime + ''; + } else { + return _endtime; + } + } + }, + event: function (row) {}, + }, + { + fid: 'ps', + title: lan.site.note, + type: 'input', + blur: function (row, index, ev) { + if (row.ps == ev.target.value) return false; + bt.pub.set_data_ps({ id: row.id, table: 'sites', ps: ev.target.value }, function (res) { + if (!res.status) layer.msg(res.msg, { status: 2 }); + }); + }, + keyup: function (row, index, ev) { + if (ev.keyCode === 13) { + $(this).blur(); + } + }, + }, + { + fid: 'php_version', + title: 'PHP', + tips: 'Selete php version', + width: 57, + type: 'link', + template: function (row, index) { + if (row.php_version.indexOf('static') > -1) return row.php_version; + return row.php_version; + }, + event: function (row, index) { + site.web_edit(row); + setTimeout(function () { + $('.site-menu p:eq(9)').click(); + }, 500); + }, + }, + { + fid: 'site_ssl', + title: 'SSL', + tips: 'Deployment certificate', + width: 130, + sort: true, + type: 'text', + template: function (row, index) { + var _ssl = row.ssl, + _info = '', + _arry = [ + ['issuer', 'Certificate'], + ['notAfter', 'Due date'], + ['notBefore', 'Application date'], + ['dns', 'Domain name'], + ]; + try { + if (typeof row.ssl.endtime != 'undefined') { + if (row.ssl.endtime < 0) { + return 'Exp in ' + Math.row.ssl.endtime + ' days'; + } + } + } catch (error) {} + for (var i = 0; i < _arry.length; i++) { + var item = _ssl[_arry[i][0]]; + _info += _arry[i][1] + ':' + item + (_arry.length - 1 != i ? '\n' : ''); + } + return row.ssl === -1 + ? 'Not Set' + : 'Exp in ' + row.ssl.endtime + ' days'; + }, + event: function (row, index, ev, key, that) { + // console.log(row, '111'); + site.web_edit(row); + setTimeout(function () { + $('.site-menu p:eq(8)').click(); + }, 500); + }, + }, + { + fid: 'attack', + title: 'Attack', + width: 80, + type: 'text', + template: function (row, index) { + return '' + row.attack + ''; + }, + event: function (row) { + site.web_edit(row); + setTimeout(function () { + $('.site-menu p:eq(' + ($('.site-menu p').length - 1) + ')').click(); + setTimeout(function () { + $('#tabLogs span:eq(2)').click(); + }, 500); + }, 500); + }, + }, + { + title: lan.site.operate, + type: 'group', + width: 140, + align: 'right', + group: [ + { + title: 'WAF', + event: function (row, index, ev, key, that) { + site.site_waf(row.name); + }, + }, + { + title: lan.site.set, + event: function (row, index, ev, key, that) { + site.web_edit(row, true); + }, + }, + { + title: 'Del', + event: function (row, index, ev, key, that) { + site.del_site(row.id, row.name, function () { + that.$refresh_table_list(true); + }); + }, + }, + ], + }, + ], + sortParam: function (data) { + return { order: data.name + ' ' + data.sort }; + }, + // 表格渲染完成后 + success: function (that) { + $('.event_edate_' + that.random).each(function () { + var $this = $(this); + laydate.render({ + elem: $this[0], //指定元素 + min: bt.get_date(1), + max: '2099-12-31', + vlue: bt.get_date(365), + type: 'date', + format: 'yyyy-MM-dd', + trigger: 'click', + btns: ['perpetual', 'confirm'], + theme: '#20a53a', + ready: function () { + $this.click(); + }, + done: function (date) { + var item = that.event_rows_model.rows; + bt.site.set_endtime(item.id, date, function (res) { + if (res.status) { + layer.msg(res.msg); + return false; + } + bt.msg(res); + }); + }, + }); + }); + if ($('#bt_site_table table thead th:eq(9) a').length == 0) { + var Attack_tips = + '
                                \ +
                              • Log analysis: Scan the logs(/www/wwwroot/.log) for requests with attack (types include:xss,sql,san,php)
                              • \ +
                              • Analyzed log data contains intercepted requests
                              • \ +
                              • By default, the last scan data is displayed (if not, please click log scan)
                              • \ +
                              • If the log file is too large, scanning may take a long time, please be patient
                              • \ +
                              • aaPanel WAF can effectively block such attacks
                              • \ +
                              '; + $('#bt_site_table table thead th:eq(9)>span').css({ width: '42px', display: 'initial' }); //设置扫描th大小 + //追加tips并设置样式 + $('#bt_site_table table thead th:eq(9)').append( + $('?') + .css({ 'border-color': '#666', color: '#666' }) + .hover( + function () { + $(this).css({ 'border-color': '#fb7d00', color: '#fff' }); + layer.tips(Attack_tips, $(this), { time: 0, tips: [1, '#fff'], area: ['500px', '180px'] }); + }, + function () { + $(this).css({ 'border-color': '#666', color: '#666' }); + layer.closeAll('tips'); + } + ) + ); + } + + $('.web_name').hover( + function (e) { + if ($('.web_name_input').is(':focus')) return; + // 获取元素基于浏览器的位置 + function getElementPosition(element) { + let top = element.offsetTop; //这是获取元素距父元素顶部的距离 + let left = element.offsetLeft; + var current = element.offsetParent; //这是获取父元素 + while (current !== null) { + //当它上面有元素时就继续执行 + top += current.offsetTop; //这是获取父元素距它的父元素顶部的距离累加起来 + left += current.offsetLeft; + current = current.offsetParent; //继续找父元素 + } + return { + top, + left, + }; + } + hoverInfo['site'] = $(this).data('type'); + hoverInfo['id'] = $(this).data('id'); + $('.web_name_hover') + .find('.web_name_title') + .html(lan.site.website_name + '' + $(this).data('type') + ''); + var _that = $(this); + $('.web_name_hover').css({ left: getElementPosition(e.target).left - 110 + _that.width() / 2 + 'px', top: getElementPosition(e.target).top - 80 + 'px' }); + $('.web_name_hover').removeClass('hide'); + }, + function (e) { + $('.web_name_hover').hover( + function () { + // 鼠标进入web_name_hover时显示 + $(this).removeClass('hide'); + }, + function () { + if ($('.web_name_input').is(':focus')) return; + // 鼠标离开web_name_hover时隐藏 + $(this).addClass('hide'); + } + ); + $('.site_name') + .parent() + .mouseleave(function () { + if ($('.web_name_input').is(':focus')) return; + $('.web_name_hover').addClass('hide'); + }); + } + ); + }, + // 渲染完成 + tootls: [ + { + // 按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Add site', + active: true, + event: function (ev) { + site.add_site(function (res, param) { + var id = bt.get_cookie('site_type'); + if (param) { + // 创建站点 + if (id != -1 && id != param.type_id) { + $('#php_cate_select .bt_select_list .item.active').click(); + } else { + site_table.$refresh_table_list(true); + } + } else { + // 批量添加 + $('#php_cate_select .bt_select_list li[data-id="-1"]').click(); + } + }); + }, + }, + { + title: 'Default Page', + event: function (ev) { + site.set_default_page(); + }, + }, + { + title: 'Default Website', + event: function (ev) { + site.set_default_site(); + }, + }, + { + title: 'PHP CLI version', + event: function (ev) { + site.get_cli_version(); + }, + }, + ], + }, + { + // 搜索内容 + type: 'search', + positon: ['right', 'top'], + placeholder: 'Domain or Remarks', + searchParam: 'search', //搜索请求字段,默认为 search + value: '', // 当前内容,默认为空 + }, + { + // 批量操作 + type: 'batch', //batch_btn + positon: ['left', 'bottom'], + placeholder: 'Select batch operation', + buttonValue: 'Execute', + disabledSelectValue: 'Select the website to execute!', + selectList: [ + { + group: [ + { title: lan.site.enable_website, param: { status: 1 } }, + { + title: 'Disable website', + param: { status: 0 }, + }, + ], + url: '/site?action=set_site_status_multiple', + confirmVerify: false, //是否提示验证方式 + paramName: 'sites_id', //列表参数名,可以为空 + paramId: 'id', // 需要传入批量的id + theadName: 'Name', + refresh: true, + }, + { + title: lan.site.backup_website, + url: '/site?action=ToBackup', + paramId: 'id', + load: true, + theadName: 'Name', + refresh: true, + callback: function (that) { + // 手动执行,data参数包含所有选中的站点 + that.start_batch({}, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + item.name + + '' + + item.request.msg + + ''; + } + site_table.$batch_success_table({ title: 'Batch backup', th: 'Site name', html: html }); + site_table.$refresh_table_list(true); + }); + }, + }, + { + title: lan.site.set_expired, + url: '/site?action=set_site_etime_multiple', + paramName: 'sites_id', //列表参数名,可以为空 + paramId: 'id', // 需要传入批量的id + theadName: 'Name', + refresh: true, + confirm: { + title: 'Batch set expired date', + content: + '
                              Expired date
                              ', + success: function () { + laydate.render({ + elem: '#site_edate', + min: bt.format_data(new Date().getTime(), 'yyyy-MM-dd'), + max: '2099-12-31', + vlue: bt.get_date(365), + type: 'date', + format: 'yyyy-MM-dd', + trigger: 'click', + btns: ['perpetual', 'confirm'], + theme: '#20a53a', + }); + }, + yes: function (index, layers, request) { + var site_edate = $('#site_edate'), + site_edate_val = site_edate.val(); + if (site_edate_val != '') { + request({ edate: site_edate_val === 'Forever' ? '0000-00-00' : site_edate_val }); + } else { + layer.tips('Input expired date', '#site_edate', { tips: ['1', 'red'] }); + $('#site_edate').css('border-color', 'red'); + $('#site_edate').click(); + setTimeout(function () { + $('#site_edate').removeAttr('style'); + }, 3000); + return false; + } + }, + }, + }, + { + title: lan.site.set_php_version, + url: '/site?action=set_site_php_version_multiple', + paramName: 'sites_id', //列表参数名,可以为空 + paramId: 'id', // 需要传入批量的id + theadName: 'Name', + refresh: true, + confirm: { + title: 'Batch set php version', + area: '420px', + content: + '
                              PHP version
                              • Please select the version according to your program requirements.
                              • If not necessary, please try not to use PHP 5.2, which will reduce your server security.
                              • PHP 7 does not support mysql extension, mysqli and mysql_pdo will be installed by default.
                              ', + success: function () { + bt.site.get_all_phpversion(function (res) { + var html = ''; + $.each(res, function (index, item) { + html += ''; + }); + $('[name="versions"]').html(html); + }); + }, + yes: function (index, layers, request) { + request({ version: $('[name="versions"]').val() }); + }, + }, + }, + { + title: lan.site.set_category, + url: '/site?action=set_site_type', + paramName: 'site_ids', //列表参数名,可以为空 + paramId: 'id', // 需要传入批量的id + refresh: true, + beforeRequest: function (list) { + var arry = []; + $.each(list, function (index, item) { + arry.push(item.id); + }); + return JSON.stringify(arry); + }, + confirm: { + title: 'Batch set category', + content: + '
                              Site category
                              ', + success: function () { + bt.site.get_type(function (res) { + var html = ''; + $.each(res, function (index, item) { + html += ''; + }); + $('[name="site_types"]').html(html); + }); + }, + yes: function (index, layers, request) { + request({ id: $('[name="site_types"]').val() }); + }, + }, + tips: false, + refresh: true, + success: function (res, list, that) { + var html = ''; + $.each(list, function (index, item) { + html += '' + item.name + '
                              ' + res.msg + '
                              '; + }); + that.$batch_success_table({ title: 'Batch set category', th: 'Site name', html: html }); + that.$refresh_table_list(true); + }, + }, + { + title: lan.site.del_website, + url: '/site?action=DeleteSite', + // paramName:'sites_id', //列表参数名,可以为空 + // paramId:'id', //需要传入批量的id + // theadName:'Name', + refresh: true, + param: function (row) { + return { + id: row.id, + webname: row.name, + }; + }, + load: true, + callback: function (that) { + // bt.show_confirm("Delete site","Confirm delete the FTP、database、root path of the selected site with the same name", function(){ + // var param = {}; + // $('.bacth_options input[type=checkbox]').each(function(){ + // var checked = $(this).is(":checked"); + // if(checked) param[$(this).attr('name')] = checked?1:0; + // }) + // if(callback) callback(param); + // },"
                              "); + var ids = []; + for (var i = 0; i < that.check_list.length; i++) { + ids.push(that.check_list[i].id); + } + site.del_site(ids, function (param) { + that.start_batch(param, function (list) { + layer.closeAll(); + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + item.name + + '
                              ' + + item.request.msg + + '
                              '; + } + site_table.$batch_success_table({ + title: 'Batch delete', + th: 'site name', + html: html, + }); + site_table.$refresh_table_list(true); + }); + }); + }, + }, + ], + }, + { + //分页显示 + type: 'page', + positon: ['right', 'bottom'], // 默认在右下角 + pageParam: 'p', //分页请求字段,默认为 : p + page: 1, //当前分页 默认:1 + numberParam: 'limit', //分页数量请求字段默认为 : limit + number: 20, //分页数量默认 : 20条 + numberList: [10, 20, 50, 100, 200], // 分页显示数量列表 + numberStatus: true, // 是否支持分页数量选择,默认禁用 + jump: true, //是否支持跳转分页,默认禁用 + }, + ], + }); + + this.init_site_type(); + }, + + /** + * @description 初始化php分类和需求反馈 + */ + init_site_type: function () { + $('#php_cate_select').remove(); + $('.feedback-btn').remove(); + $('.tootls_group.tootls_top .pull-left').append( + '\ +
                              \ +
                              \ + Classification: \ + \ +
                              \ +
                                \ +
                                \ + ' + ); + bt.site.get_type(function (res) { + site.reader_site_type(res); + }); + }, + reader_site_type: function (res, config) { + var html = '', + active = bt.get_cookie('site_type') || -1, + select = $('#php_cate_select'), + config = site_table; + + if (select.find('.bt_select_list li').length > 1) return false; + + res.unshift({ id: -1, name: 'Category manager' }); + + $.each(res, function (index, item) { + html += '
                              • ' + item.name + '
                              • '; + }); + + html += '
                              • Category set
                              • '; + + select.find('.bt_select_value').on('click', function (ev) { + var $this = this; + $(this).next().show(); + $(document).one('click', function () { + $($this).next().hide(); + }); + ev.stopPropagation(); + }); + + select + .find('.bt_select_list') + .unbind('click') + .on('click', 'li', function () { + var id = $(this).data('id'); + if (id === 'type_sets') { + site.set_class_type(); + } else { + bt.set_cookie('site_type', id); + config.config.page.page = 1; + config.$refresh_table_list(true); + $(this).addClass('active').siblings().removeClass('active'); + // select.find('.bt_select_value .bt_select_content').text('Classification: ' + $(this).text()); + select.find('.bt_select_value .bt_select_content').text($(this).text()); + } + }) + .empty() + .html(html); + + select = $(select[0]); + + if (!select.find('.bt_select_list li.active').length) { + select.find('.bt_select_list li:eq(0)').addClass('active'); + // select.find('.bt_select_value .bt_select_content').text('Classification: 默认分类'); + select.find('.bt_select_value .bt_select_content').text('Default category'); + } else { + // select.find('.bt_select_value .bt_select_content').text('Classification: ' + select.find('.bt_select_list li.active').text()); + select.find('.bt_select_value .bt_select_content').text(select.find('.bt_select_list li.active').text()); + } + }, + get_list: function (page, search, type) { + if (page == undefined) page = 1; + if (type == '-1' || type == undefined) { + type = bt.get_cookie('site_type'); + } + if (!search) search = $('#SearchValue').val(); + bt.site.get_list(page, search, type, function (rdata) { + $('.dataTables_paginate').html(rdata.page); + var data = rdata.data; + var _tab = bt.render({ + table: '#webBody', + columns: [ + { field: 'id', type: 'checkbox', width: 30 }, + { + field: 'name', + title: lan.site.site_name, + width: 150, + templet: function (item) { + return '' + item.name + ''; + }, + sort: function () { + site.get_list(); + }, + }, + { + field: 'status', + title: lan.site.status, + width: 98, + templet: function (item) { + var _status = ''; + _status += '' + lan.site.running_text + ' '; + } else { + _status += ' onclick="bt.site.start(' + item.id + ",'" + item.name + '\')"'; + _status += '' + lan.site.stopped + ' '; + } + return _status; + }, + sort: function () { + site.get_list(); + }, + }, + { + field: 'backup', + title: lan.site.backup, + width: 105, + templet: function (item) { + var backup = lan.site.backup_no; + if (item.backup_count > 0) backup = lan.site.backup_yes; + return '' + backup + ''; + }, + }, + { + field: 'path', + title: lan.site.root_dir, + templet: function (item) { + var _path = bt.format_path(item.path); + return '' + _path + ''; + }, + }, + { + field: 'edate', + title: lan.site.endtime, + width: 127, + templet: function (item) { + var _endtime = ''; + if (item.edate) _endtime = item.edate; + if (item.endtime) _endtime = item.endtime; + _endtime = _endtime == '0000-00-00' ? lan.site.web_end_time : _endtime; + return '' + _endtime + ''; + }, + sort: function () { + site.get_list(); + }, + }, + { + field: 'ps', + title: lan.site.note, + templet: function (item) { + return "" + item.ps + ''; + }, + }, + { + field: 'php_version', + width: 70, + title: 'PHP', + templet: function (item) { + return '' + item.php_version + ''; + }, + }, + { + field: 'ssl', + title: 'SSL', + templet: function (item) { + var _ssl = ''; + if (item.ssl == -1) { + _ssl = 'Not Set'; + } else { + var ssl_info = 'Certificate: ' + item.ssl.issuer + '
                                Due date: ' + item.ssl.notAfter + '
                                Application date: ' + item.ssl.notBefore + '
                                Domain name: ' + item.ssl.dns.join('/'); + if (item.ssl.endtime < 0) { + _ssl = 'Expired'; + } else if (item.ssl.endtime < 20) { + _ssl = 'Exp in ' + (item.ssl.endtime + ' days') + ''; + } else { + _ssl = 'Exp in ' + item.ssl.endtime + ' days'; + } + } + return _ssl; + }, + }, + { + field: 'opt', + width: 90, + title: lan.site.operate, + align: 'right', + templet: function (item) { + var opt = ''; + var _check = ' onclick="site.site_waf(\'' + item.name + '\')"'; + + //if (bt.os == 'Linux') opt += '' + lan.site.firewalld + ' | '; + opt += '' + lan.site.set + ' | '; + opt += '' + lan.site.del + ''; + return opt; + }, + }, + ], + data: data, + }); + var outTime = ''; + $('.ssl_tips').hover( + function () { + var that = this, + tips = $(that).attr('data-tips'); + if (!tips) return false; + outTime = setTimeout(function () { + layer.tips(tips, $(that), { + tips: [2, '#20a53a'], //还可配置颜色 + time: 0, + }); + }, 500); + }, + function () { + outTime != '' ? clearTimeout(outTime) : ''; + layer.closeAll('tips'); + } + ); + $('.ssl_tips').click(function () { + site.web_edit(this); + var timeVal = setInterval(function () { + var content = $('#webedit-con').html(); + if (content != '') { + $('.site-menu p:contains("SSL")').click(); + clearInterval(timeVal); + } + }, 100); + }); + $('.phpversion_tips').click(function () { + site.web_edit(this); + var timeVal = setInterval(function () { + var content = $('#webedit-con').html(); + if (content != '') { + $('.site-menu p:contains("PHP version")').click(); + clearInterval(timeVal); + } + }, 100); + }); + //浏览器窗口大小变化时调整内容宽度 + var ticket_with = $('#webBody').width(), + td_width = (ticket_with - 667 - $('#webBody th:contains("SSL")').width()) / 2; + $('#webBody .webPath').css('max-width', td_width); + $(window).resize(function () { + var ticket_with = $('#webBody').width(), + td_width = (ticket_with - 667 - $('#webBody th:contains("SSL")').width()) / 2; + $('#webBody .webPath').css('max-width', td_width); + }); + //设置到期时间 + $('a.setTimes').each(function () { + var _this = $(this); + var _tr = _this.parents('tr'); + var id = _this.attr('id'); + laydate.render({ + elem: '#' + id, //指定元素 + lang: 'en', + min: bt.get_date(1), + max: '2099-12-31', + vlue: bt.get_date(365), + type: 'date', + format: 'yyyy-MM-dd', + trigger: 'click', + btns: ['perpetual', 'confirm'], + theme: '#20a53a', + done: function (dates) { + var item = _tr.data('item'); + bt.site.set_endtime(item.id, dates, function () {}); + }, + }); + }); + //}) + }); + }, + site_waf: function (siteName) { + try { + site_waf_config(siteName); + } catch (err) { + site.no_firewall(); + } + }, + html_encode: function (html) { + var temp = document.createElement('div'); + //2.然后将要转换的字符串设置为这个元素的innerText(ie支持)或者textContent(火狐,google支持) + temp.textContent != undefined ? (temp.textContent = html) : (temp.innerText = html); + //3.最后返回这个元素的innerHTML,即得到经过HTML编码转换的字符串了 + var output = temp.innerHTML; + temp = null; + return output; + }, + get_types: function (callback) { + bt.site.get_type(function (rdata) { + var optionList = ''; + var t_val = bt.get_cookie('site_type'); + for (var i = 0; i < rdata.length; i++) { + optionList += ''; + } + if ($('.dataTables_paginate').next().hasClass('site_type')) $('.site_type').remove(); + $('.dataTables_paginate').after( + '
                                ' + optionList + '
                                ' + ); + $('.site_type button').click(function () { + var val = $(this).attr('value'); + bt.set_cookie('site_type', val); + site.get_list(0, '', val); + $('.site_type button').removeClass('btn-success').addClass('btn-default'); + $(this).addClass('btn-success'); + }); + if (callback) callback(rdata); + }); + }, + no_firewall: function (obj) { + var typename = bt.get_cookie('serverType'); + layer.confirm( + lan.site.firewalld_nonactivated_tips.replace('{1}', typename).replace('{2}', typename), + { + title: typename + lan.site.site_classification, + icon: 7, + closeBtn: 2, + cancel: function () { + if (obj) $(obj).prop('checked', false); + }, + }, + function () { + window.location.href = '/soft'; + }, + function () { + if (obj) $(obj).prop('checked', false); + } + ); + }, + site_detail: function (id, siteName, page) { + if (page == undefined) page = '1'; + var loadT = bt.load(lan.public.the_get); + bt.pub.get_data('table=backup&search=' + id + '&limit=5&type=0&tojs=site.site_detail&p=' + page, function (frdata) { + loadT.close(); + var ftpdown = ''; + var body = ''; + var port; + frdata.page = frdata.page.replace(/'/g, '"').replace(/site.site_detail\(/g, 'site.site_detail(' + id + ",'" + siteName + "',"); + if ($('#SiteBackupList').length <= 0) { + bt.open({ + type: 1, + skin: 'demo-class', + area: '700px', + title: lan.site.backup_title, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
                                • Before restoring data, all data in the root dir of the website will be moved to the panel recycle bin.
                                ", + }); + } + setTimeout(function () { + $('.sitebackup_page').html(frdata.page); + var _tab = bt.render({ + table: '#SiteBackupList', + columns: [ + { + field: 'name', + title: lan.site.filename, + templet: function (item) { + var _opt = '' + item.name + ''; + return _opt; + }, + }, + { + field: 'size', + title: lan.site.filesize, + templet: function (item) { + return bt.format_size(item.size); + }, + }, + { field: 'addtime', title: lan.site.backup_time }, + { + field: 'opt', + title: lan.site.operate, + align: 'right', + templet: function (item) { + var _opt = 'Restore | '; + _opt += '' + lan.site.download + ' | '; + _opt += '' + lan.site.del + ''; + return _opt; + }, + }, + ], + data: frdata.data, + }); + $('#btn_data_backup') + .unbind('click') + .click(function () { + bt.site.backup_data(id, function (rdata) { + if (rdata.status) site.site_detail(id, siteName); + site.get_list(); + }); + }); + $('#SiteBackupList .restore') + .unbind('click') + .click(function () { + var data = {}; + data.file_name = $(this).attr('backup-name'); + data.site_id = $(this).attr('site-id'); + // console.log(data); + layer.confirm( + 'Are you sure to restore backup file?', + { + icon: 0, + closeBtn: 2, + title: 'Restore backup file', + }, + function (index) { + $.post('/files?action=restore_website', data, function (rdata) { + layer.close(index); + site.backup_output_stop = true; + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + site.backup_output_logs(); + } + ); + }); + }, 100); + }); + }, + /** + * @description 备份站点视图 + * @param {object} config 配置参数 + * @param {function} callback 回调函数 + */ + backup_site_view: function (config, thatC, callback) { + bt_tools.open({ + title: lan.site.backup_title + ' - [ ' + config.name + ' ]', + area: '720px', + btn: false, + skin: 'bt_backup_table', + content: '
                                ', + success: function ($layer) { + var backup_table = bt_tools.table({ + el: '#bt_backup_table', + url: '/data?action=getData', + param: { table: 'backup', search: config.id, type: '0' }, + default: '[' + config.name + '] Currently no backup', //数据为空时的默认提示 + column: [ + { type: 'checkbox', class: '', width: 20 }, + { fid: 'name', title: lan.site.filename, width: 250, fixed: true }, + { + fid: 'size', + title: lan.site.filesize, + width: 80, + type: 'text', + template: function (row, index) { + return bt.format_size(row.size); + }, + }, + { fid: 'addtime', width: 150, title: lan.site.backup_time }, + { + title: lan.site.operate, + type: 'group', + width: 165, + align: 'right', + group: [ + { + title: 'Restore', + event: function (row) { + var data = {}; + data.file_name = row.name; + data.site_id = config.id; + // console.log(data); + layer.confirm( + 'Are you sure to restore backup file?', + { + icon: 0, + closeBtn: 2, + title: 'Restore backup file', + }, + function (index) { + $.post('/files?action=restore_website', data, function (rdata) { + layer.close(index); + site.backup_output_stop = true; + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + site.backup_output_logs(); + } + ); + }, + }, + { + title: lan.site.download, + template: function (row, index, ev, key, that) { + return '' + lan.site.download + ''; + }, + }, + { + title: lan.site.del, + event: function (row, index, ev, key, that) { + that.del_site_backup({ name: row.name, id: row.id }, function (rdata) { + bt_tools.msg(rdata); + if (rdata.status) { + thatC.$modify_row_data({ backup_count: thatC.event_rows_model.rows.backup_count - 1 }); + that.$refresh_table_list(); + } + }); + }, + }, + ], + }, + ], + methods: { + /** + * @description 删除站点备份 + * @param {object} config + * @param {function} callback + */ + del_site_backup: function (config, callback) { + bt.confirm( + { + title: lan.site.del_bak_file, + msg: 'The website backup is about to be deleted [' + config.name + '], do you want to continue?', + }, + function () { + bt_tools.send( + 'site/DelBackup', + { id: config.id }, + function (rdata) { + if (callback) callback(rdata); + }, + true + ); + } + ); + }, + }, + success: function () { + if (callback) callback(); + $('.bt_backup_table').css('top', ($(window).height() - $('.bt_backup_table').height()) / 2 + 'px'); + }, + tootls: [ + { + // 按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Backup', + active: true, + event: function (ev, that) { + bt.site.backup_data(config.id, function (rdata) { + bt_tools.msg(rdata); + if (rdata.status) { + thatC.$modify_row_data({ backup_count: thatC.event_rows_model.rows.backup_count + 1 }); + that.$refresh_table_list(); + } + }); + }, + }, + ], + }, + { + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' Delete', + url: '/site?action=DelBackup', + paramId: 'id', + load: true, + callback: function (that) { + bt.confirm( + { + title: 'Delete site backups in bulk', + msg: 'Do you want to delete selected site backups in batches?', + icon: 0, + }, + function (index) { + layer.close(index); + that.start_batch({}, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + item.name + + '' + + item.request.msg + + ''; + } + backup_table.$batch_success_table({ + title: 'Delete site backups in bulk', + th: 'file name', + html: html, + }); + backup_table.$refresh_table_list(true); + thatC.$modify_row_data({ backup_count: thatC.event_rows_model.rows.backup_count - list.length }); + }); + } + ); + }, + }, //分页显示 + }, + { + type: 'page', + positon: ['right', 'bottom'], // 默认在右下角 + pageParam: 'p', //分页请求字段,默认为 : p + page: 1, //当前分页 默认:1 + numberParam: 'limit', + //分页数量请求字段默认为 : limit + number: 10, + //分页数量默认 : 20条 + }, + ], + }); + }, + }); + }, + backup_output_stop: false, + //实时显示过程 + backup_output_logs: function () { + var layerT = layer.open({ + type: 1, + area: '590px', + title: 'Recovering the backup...', + closeBtn: 0, + content: '
                                ', + }); + var show_output = setInterval(function () { + $.post('/files?action=get_progress', function (rdata) { + if (site.backup_output_stop) { + layer.close(layerT); + clearInterval(show_output); + } + $('.backup_logs').html(rdata.msg); + $('.backup_logs').scrollTop($('.backup_logs')[0].scrollHeight); + }); + }, 1000); + }, + /** + * @description 添加站点 + */ + add_site: function (callback) { + var typeId = bt.get_cookie('site_type'); + var add_web = bt_tools.form({ + data: {}, //用于存储初始值和编辑时的赋值内容 + class: '', + form: [ + { + label: lan.site.add_site.domain, + must: '*', + group: [ + { + type: 'textarea', //当前表单的类型 支持所有常规表单元素、和复合型的组合表单元素 + name: 'webname', //当前表单的name + style: { width: '440px', height: '100px', 'line-height': '22px' }, + tips: { + //使用hover的方式显示提示 + text: lan.site.domain_help, + style: { top: '15px', left: '15px' }, + }, + keyup: function (value, form, that, config, ev) { + //键盘事件 + var array = value.webname.split('\n'), + ress = array[0].split(':')[0], + oneVal = bt.strim(ress.replace(new RegExp(/([-.])/g), '_')), + defaultPath = $('#defaultPath').text(), + is_oneVal = ress.length > 0; + that.$set_find_value( + is_oneVal + ? { + ftp_username: 'ftp_' + oneVal, + ftp_password: bt.get_random(16), + datauser: is_oneVal ? 'sql_' + oneVal.substr(0, 16) : '', + datapassword: bt.get_random(16), + ps: oneVal, + path: bt.rtrim(defaultPath, '/') + '/' + ress, + } + : { + ftp_username: '', + ftp_password: '', + datauser: '', + datapassword: '', + ps: '', + path: bt.rtrim(defaultPath, '/'), + } + ); + if (bt.check_domain(ress)) { + form['redirect'].parents('.block').removeClass('hide'); + if (ress.indexOf('www.') !== 0) { + form['redirect'] + .next() + .find('span') + .text('www.' + ress); + } else if (ress.indexOf('www.') === 0) { + form['redirect'] + .next() + .find('span') + .text(ress.replace(/^www\./, '')); + } + } else { + form['tourl'].parents('.line').addClass('hide'); + form['redirect'].parents('.block').addClass('hide'); + } + }, + }, + { + type: 'checkbox', + block: true, + block_class: 'redirect_check', + hide: true, + name: 'redirect', + label_tips: 'Add [] domain name to the main domain name', + event: function (value, form, that, config, ev) { + var domain = form['redirect'].next().find('span').text(), + domain_textarea = form['webname'], + domainList = domain_textarea.val().split('\n'), + domain_one = domainList[0].split(':')[0]; + if (value['redirect'] == 'on') { + domain_textarea.val(domain_textarea.val() + '\r' + domain); + form['tourl'].parents('.line').removeClass('hide'); + var radio_list = form['tourl'].parents('.line').find('.redirect_tourl'); + $('.redirect_tourl:eq(1)') + .find('label') + .html('Redirect the main domain name [ ' + domain_one + '] to [' + domain + '] domain name'); + $('.redirect_tourl:eq(2)') + .find('label') + .html('Redirect the [' + domain + '] domain name to the main domain [' + domain_one + ']'); + } else { + for (var i = domainList.length - 1; i >= 0; i--) { + if (domainList[i] === domain) domainList.splice(i, 1); + } + domain_textarea.val(domainList.join('\n')); + form['tourl'].parents('.line').addClass('hide'); + } + }, + }, + ], + }, + { + label: 'Redirect', + hide: true, + group: [ + { + type: 'radio', + name: 'tourl', + block: true, + block_class: 'redirect_tourl', + label_tips: [ + 'No', + 'Redirect the main domain name [] to [] domain name', + 'Redirect the [] domain name to the main domain []', + ], + }, + ], + }, + { + label: lan.site.add_site.description, + group: { + type: 'text', + name: 'ps', + width: '400px', + placeholder: lan.note_ph, //默认标准备注提示 + }, + }, + { + label: lan.site.add_site.root, + must: '*', + group: { + type: 'text', + width: '400px', + name: 'path', + icon: { + type: 'glyphicon-folder-open', + event: function (ev) {}, + }, + value: '/www/wwwroot', + placeholder: lan.site.add_site.root_ph, + }, + }, + { + label: lan.site.add_site.ftp, + group: [ + { + type: 'select', + name: 'ftp', + width: '135px', + disabled: (function () { + if (bt.config['pure-ftpd']) return !bt.config['pure-ftpd'].setup; + return true; + })(), + list: [ + { title: lan.site.add_site.dont_create, value: false }, + { title: lan.site.add_site.create, value: true }, + ], + change: function (value, form, that, config, ev) { + if (value['ftp'] === 'true') { + form['ftp_username'].parents('.line').removeClass('hide'); + } else { + form['ftp_username'].parents('.line').addClass('hide'); + } + }, + }, + (function () { + if (bt.config['pure-ftpd']['setup']) return {}; + return { + type: 'link', + title: 'FTP is not installed, click Install', + name: 'installed_ftp', + event: function (ev) { + bt.soft.install('pureftpd'); + }, + }; + })(), + ], + }, + { + label: lan.site.add_site.ftp_set, + hide: true, + group: [ + { + type: 'text', + name: 'ftp_username', + placeholder: lan.site.add_site.ftp_ph, + width: '175px', + style: { 'margin-right': '15px' }, + }, + { + label: lan.site.add_site.password, + type: 'text', + placeholder: lan.site.add_site.ftp_password, + name: 'ftp_password', + width: '175px', + }, + ], + help: { + list: [lan.site.ftp_help], + }, + }, + { + label: lan.site.add_site.database, + group: [ + { + type: 'select', + name: 'sql', + width: '135px', + disabled: (function () { + if (bt.config['mysql']) return !bt.config['mysql'].setup; + return true; + })(), + list: [ + { title: lan.site.add_site.dont_create, value: false }, + { title: 'MySQL', value: 'MySQL' }, + { + title: 'SQLServer', + value: 'SQLServer', + disabled: true, + tips: lan.public_backup.unsupport_sqlserver, + }, + ], + change: function (value, form, that, config, ev) { + if (value['sql'] === 'MySQL') { + form['datauser'].parents('.line').removeClass('hide'); + form['codeing'].parents('.bt_select_updown').removeClass('hide'); + } else { + form['datauser'].parents('.line').addClass('hide'); + form['codeing'].parents('.bt_select_updown').addClass('hide'); + } + }, + }, + (function () { + if (bt.config.mysql.setup) return {}; + return { + type: 'link', + title: 'Database not installed, click Install', + name: 'installed_database', + event: function (ev) { + bt.soft.install('mysql'); + }, + }; + })(), + { + type: 'select', + name: 'codeing', + hide: true, + width: '135px', + list: [ + { title: 'utf8', value: 'utf8' }, + { title: 'utf8mb4', value: 'utf8mb4' }, + { title: 'gbk', value: 'gbk' }, + { title: 'big5', value: 'big5' }, + ], + }, + ], + }, + { + label: lan.site.add_site.database_set, + hide: true, + group: [ + { + type: 'text', + name: 'datauser', + placeholder: lan.site.add_site.database_ph, + width: '175px', + style: { 'margin-right': '15px' }, + }, + { + label: lan.site.add_site.password, + type: 'text', + placeholder: lan.site.add_site.database_password, + name: 'datapassword', + width: '175px', + }, + ], + help: { + class: '', + style: '', + list: [lan.site.database_help], + }, + }, + { + label: lan.site.add_site.php_version, + group: [ + { + type: 'select', + name: 'version', + width: '135px', + list: { + url: '/site?action=GetPHPVersion', + dataFilter: function (res) { + var arry = []; + for (var i = res.length - 1; i >= 0; i--) { + var item = res[i]; + arry.push({ title: item.name, value: item.version }); + } + return arry; + }, + }, + }, + ], + }, + { + label: lan.site.add_site.category, + group: [ + { + type: 'select', + name: 'type_id', + width: '135px', + list: { + url: '/site?action=get_site_types', + dataFilter: function (res) { + var arry = []; + $.each(res, function (index, item) { + arry.push({ title: item.name, value: item.id }); + }); + return arry; + }, + success: function (res, formObj) { + setTimeout(function () { + var index = -1; + for (var i = 0; i < res.length; i++) { + if (res[i].id == typeId) { + index = i; + break; + } + } + if (index != -1) formObj.element.find('.bt_select_updown[data-name="type_id"]').find('.bt_select_list li').eq(index).click(); + }, 100); + }, + }, + }, + ], + }, + { + label: 'SSL', + class: 'ssl_checkbox', + help: { + style: 'color: red;line-height: 17px;margin-top: 8px;', + list: ['If you need to apply for SSL, please make sure that the domain name has added A record resolution for the domain name'], + }, + group: [ + { + type: 'checkbox', + name: 'set_ssl', + title: 'Apply for SSL', + class: 'site_ssl_check', + style: { 'margin-right': '10px', 'margin-left': '0' }, + }, + { + type: 'checkbox', + name: 'force_ssl', + class: 'site_ssl_check', + title: 'HTTP redirect to HTTPS', + event: function (value, form, that, config, ev) { + var force_ssl = $(this).is(':checked'); + if (force_ssl) { + $('.site_ssl_check:eq(0)').find('i').addClass('active'); + $('input[name=set_ssl]').prop('checked', force_ssl); + } + }, + }, + ], + }, + ], + }); + var bath_web = bt_tools.form({ + class: 'plr10', + form: [ + { + line_style: { position: 'relative' }, + group: { + type: 'textarea', //当前表单的类型 支持所有常规表单元素、和复合型的组合表单元素 + name: 'bath_code', //当前表单的name + style: { width: '560px', height: '180px', 'line-height': '22px', 'font-size': '13px' }, + value: lan.site.add_site.bath_code_ph, + }, + }, + { + group: { + type: 'help', + style: { 'margin-top': '0' }, + class: 'none-list-style', + list: [ + lan.site.add_site.bath_tips1, + lan.site.add_site.bath_tips2, + lan.site.add_site.bath_tips3, + lan.site.add_site.bath_tips4, + lan.site.add_site.bath_tips5, + lan.site.add_site.bath_tips6, + lan.site.add_site.bath_tips7, + lan.site.add_site.bath_tips8, + ], + }, + }, + ], + }); + var deploy_wp = bt_tools.form({ + form: [ + { + label: 'Domain', + group: { + type: 'text', + name: 'domain', + width: '400px', + placeholder: 'Your website domain name', + }, + }, + { + label: 'Website Title', + group: { + type: 'text', + name: 'weblog_title', + width: '400px', + placeholder: 'Website title for wordpress', + }, + }, + { + label: 'Language', + group: [ + { + type: 'select', + name: 'language', + width: '230px', + list: { + url: '/site?action=get_language', + dataFilter: function (rlist) { + var lan = []; + $.each(rlist.msg, function (index, item) { + lan.push({ title: item, value: index }); + }); + return lan; + }, + }, + }, + ], + }, + { + label: 'PHP version', + group: [ + { + type: 'select', + name: 'php_version', + width: '230px', + list: add_web['config']['form'][8]['group'][0]['list'], + }, + ], + }, + { + label: 'User name', + group: { + type: 'text', + name: 'user_name', + width: '400px', + placeholder: 'WordPress backend user', + }, + }, + { + label: 'Password', + group: [ + { + type: 'text', + name: 'admin_password', + placeholder: 'WordPress backend password', + width: '230px', + style: { 'margin-right': '15px' }, + }, + { + type: 'checkbox', + name: 'pw_weak', + title: 'Allow weak passwords', + }, + ], + }, + { + label: 'Email', + group: { + type: 'text', + name: 'admin_email', + width: '400px', + placeholder: 'Your email address', + }, + }, + { + label: 'Prefix', + group: { + type: 'text', + name: 'prefix', + width: '400px', + value: 'wp_', + placeholder: 'Wordpress table name prefix', + }, + }, + { + label: 'Enable cache', + style: { 'min-height': '0', 'line-height': '15px', 'margin-bottom': ' 0' }, + group: [ + { + type: 'checkbox', + name: 'enable_cache', + title: 'Enable caching, currently only supports nginx', + }, + ], + }, + ], + }); + + var web_tab = bt_tools.tab({ + class: 'pd20', + type: 0, + theme: { nav: 'mlr20' }, + active: 1, //激活TAB下标 + list: [ + { + title: lan.site.add_site.create_site, + name: 'createSite', + content: add_web.$reader_content(), + success: function () { + add_web.$event_bind(); + }, + }, + { + title: lan.site.add_site.batch_creat, + name: 'batchCreation', + content: bath_web.$reader_content(), + success: function () { + bath_web.$event_bind(); + }, + }, + { + title: 'Wordpress deploy', + name: 'wordpressDeploy', + content: '', + success: function (el) { + el.html(deploy_wp.$reader_content()); + deploy_wp.$event_bind(); + $(el).find('form .line:last-child .tname').css({ height: '22px', 'line-height': '22px' }); + }, + }, + ], + }); + bt_tools.open({ + title: lan.site.add_site.add_site_title, + skin: 'custom_layer', + btn: [lan.public.submit, lan.site.no], + content: web_tab.$reader_content(), + success: function ($layer) { + web_tab.$init(); + + $layer.find('.tab-con').scroll(function () { + $layer.find('.bt_select_list').removeClass('show'); + }); + // $layer.find('.layui-layer-content, .tab-con').css('overflow', $(window).height() > $layer.height() ? 'visible' : 'auto'); + }, + yes: function (indexs) { + var tabContent = add_web, + tabActive = web_tab.active; + switch (tabActive) { + case 1: + tabContent = bath_web; + break; + case 2: + tabContent = deploy_wp; + break; + } + var formValue = tabContent.$get_form_value(); + if (tabActive != 2 && formValue.webname === '') { + bt.msg({ status: false, msg: '网站域名不能为空!' }); + return false; + } + + if (tabActive == 0) { + // 创建站点 + var loading = bt.load(); + add_web.$get_form_element(true); + if (formValue.webname === '') { + add_web.form_element.webname.focus(); + bt_tools.msg(lan.public.domain_format_not_right, 2); + return; + } + var webname = bt.replace_all(formValue.webname, 'http[s]?:\\/\\/', ''), + web_list = webname.split('\n'), + param = { webname: { domain: '', domainlist: [], count: 0 }, type: 'PHP', port: 80 }, + arry = ['ps', ['path', lan.site.site_menu_2], 'type_id', 'version', 'ftp', 'sql', 'ftp_username', 'ftp_password', 'datauser', 'datapassword', 'codeing']; + for (var i = 0; i < web_list.length; i++) { + var temps = web_list[i].replace(/\r\n/, '').split(':'); + if (i === 0) { + param['webname']['domain'] = web_list[i]; + if (typeof temps[1] != 'undefined') param['port'] = temps[1]; + } else { + param['webname']['domainlist'].push(web_list[i]); + } + } + param['webname']['count'] = param['webname']['domainlist'].length; + param['webname'] = JSON.stringify(param['webname']); + $.each(arry, function (index, item) { + if (formValue[item] == '' && Array.isArray(item)) { + bt_tools.msg(item[1] + lan.site.add_site.empty_ps, 2); + return false; + } + Array.isArray(item) ? (item = item[0]) : ''; + if (formValue['ftp'] === 'false' && (item === 'ftp_username' || item === 'ftp_password')) return true; + if (formValue['sql'] === 'false' && (item === 'datauser' || item === 'datapassword')) return true; + param[item] = formValue[item]; + }); + param['set_ssl'] = $('input[name=set_ssl]').prop('checked') ? 1 : 0; + param['force_ssl'] = $('input[name=force_ssl]').prop('checked') ? 1 : 0; + var is_redirect = $('.redirect_check').hasClass('hide'); + if (!is_redirect) { + var redirect_check = $('.redirect_check input[name=redirect]').is(':checked'); + if (redirect_check) { + var domains = $('.redirect_tourl input[name=tourl]:checked').next().find('span'); + if (domains.length != 0) { + param.redirect = $(domains[0]).text(); + param.tourl = $(domains[1]).text(); + } + } + } + bt.send('AddSite', 'site/AddSite', param, function (rdata) { + loading.close(); + if (rdata.siteStatus) { + layer.close(indexs); + if (callback) callback(rdata, param); + var html = '', + ftpData = '', + sqlData = ''; + if (rdata.ftpStatus) { + var list = []; + list.push({ title: lan.site.user, val: rdata.ftpUser }); + list.push({ title: lan.site.password, val: rdata.ftpPass }); + var item = {}; + item.title = lan.site.ftp; + item.list = list; + ftpData = bt.render_ps(item); + } + if (rdata.databaseStatus) { + var list = []; + list.push({ title: lan.site.database_name, val: rdata.databaseUser }); + list.push({ title: lan.site.user, val: rdata.databaseUser }); + list.push({ title: lan.site.password, val: rdata.databasePass }); + var item = {}; + item.title = lan.site.database_txt; + item.list = list; + sqlData = bt.render_ps(item); + } + if (ftpData == '' && sqlData == '') { + bt.msg({ msg: lan.site.success_txt, icon: 1 }); + } else { + bt.open({ + type: 1, + area: '600px', + title: lan.site.success_txt, + closeBtn: 2, + shadeClose: false, + content: "
                                " + ftpData + sqlData + '
                                ', + }); + + if ($('.success-msg').height() < 150) { + $('.success-msg').find('img').css({ width: '150px', 'margin-top': '30px' }); + } + } + } else { + bt.msg(rdata); + } + }); + } else if (tabActive == 1) { + //批量创建 + var loading = bt.load(); + if (formValue.bath_code === '') { + bt_tools.msg(lan.site.add_site.batch_site_ps, 2); + return false; + } else { + var arry = formValue.bath_code.split('\n'), + config = '', + _list = []; + for (var i = 0; i < arry.length; i++) { + var item = arry[i], + params = item.split('|'), + _arry = []; + if (item === '') continue; + for (var j = 0; j < params.length; j++) { + var line = i + 1, + items = bt.strim(params[j]); + _arry.push(items); + switch (j) { + case 0: //参数一:域名 + var domainList = items.split(','); + for (var z = 0; z < domainList.length; z++) { + var domain_info = domainList[z], + _domain = domain_info.split(':'); + if (!bt.check_domain(_domain[0])) { + bt_tools.msg(lan.site.add_site.error_line + line + lan.site.add_site.domain_error + '【' + domain_info + '】', 2); + return false; + } + if (typeof _domain[1] !== 'undefined') { + if (!bt.check_port(_domain[1])) { + bt_tools.msg(lan.site.add_site.error_line + line + lan.site.add_site.port_error + '【' + _domain[1] + '】', 2); + return false; + } + } + } + break; + case 1: //参数二:站点目录 + if (items !== '1') { + if (items.indexOf('/') < -1) { + bt_tools.msg(lan.site.add_site.error_line + line + lan.site.add_site.port_error + '【' + items + '】', 2); + return false; + } + } + break; + } + } + _list.push(_arry.join('|').replace(/\r|\n/, '')); + } + } + bt.send( + 'create_type', + 'site/create_website_multiple', + { + create_type: 'txt', + websites_content: JSON.stringify(_list), + }, + function (rdata) { + loading.close(); + if (rdata.status) { + var _html = ''; + layer.close(indexs); + if (callback) callback(rdata); + $.each(rdata.error, function (key, item) { + _html += '' + key + '----' + item + ''; + }); + $.each(rdata.success, function (key, item) { + _html += + '' + + key + + '' + + (item.ftp_status ? '' + lan.site.add_site.success + '' : '' + lan.site.add_site.not_created + '') + + '' + + (item.db_status ? '' + lan.site.add_site.success + '' : '' + lan.site.add_site.not_created + '') + + '' + + lan.site.add_site.created + + ''; + }); + bt.open({ + type: 1, + title: lan.site.add_site.batch_add_site, + area: ['500px', '450px'], + shadeClose: false, + closeBtn: 2, + content: + '
                                ' + + _html + + '
                                ' + + lan.site.add_site.site_name + + 'FTP' + + lan.site.add_site.database + + '' + + lan.site.add_site.opt_result + + '
                                ', + success: function () { + $('.fiexd_thead').scroll(function () { + var scrollTop = this.scrollTop; + this.querySelector('thead').style.transform = 'translateY(' + scrollTop + 'px)'; + }); + }, + }); + } else { + bt.msg(rdata); + } + } + ); + } else { + //wp一键部署 + var param = { webname: { domain: '', domainlist: [], count: 0 }, type: 'PHP', port: 80, type_id: 0, ftp: false, sql: 'MySQL', codeing: 'utf8', set_ssl: 0, force_ssl: 0, project_type: 'WP' }; + if (formValue.domain == '') return layer.msg('Wordpress domain name cannot be empty', { icon: 2 }); + if (formValue.weblog_title == '') return layer.msg('Wordpress site title cannot be empty', { icon: 2 }); + if (formValue.user_name == '') return layer.msg('Wordpress backend user cannot be empty', { icon: 2 }); + if (formValue.admin_password == '') return layer.msg('Wordpress backend password cannot be empty', { icon: 2 }); + if (formValue.admin_email == '') return layer.msg('Email address cannot be empty', { icon: 2 }); + if (formValue.prefix == '') return layer.msg('Wordpress table name prefix cannot be empty', { icon: 2 }); + + var _domain = bt.strim(formValue.domain.replace(new RegExp(/([-.])/g), '_')); + param['webname']['domain'] = formValue.domain; + param['webname'] = JSON.stringify(param['webname']); + + param['path'] = '/www/wwwroot/' + formValue.domain; + param['ps'] = _domain; + param['version'] = formValue.php_version; + param['datauser'] = 'sql_' + _domain; + param['datapassword'] = bt.get_random(16); + + // 密码强度判断 + param['password'] = formValue['admin_password']; + param['pw_weak'] = formValue['pw_weak'] ? 'on' : 'off'; + param['email'] = formValue['admin_email']; + + var loading = bt.load('Creating website, please wait...'); + // 1.通过主域名生成域名文件地址等信息 + bt.send('AddSite', 'site/AddSite', param, function (rdata) { + loading.close(); + if (typeof rdata.status === 'boolean' && !rdata.status) return layer.msg(rdata.msg, { icon: 2, time: 0, shade: 0.3, shadeClose: true }); + // 2.通过回调的siteId,d_id部署wp + if (rdata.databaseStatus) { + formValue['d_id'] = rdata.d_id; + formValue['s_id'] = rdata.siteId; + formValue['enable_cache'] = formValue['enable_cache'] ? 1 : 0; + formValue['pw_weak'] = formValue['pw_weak'] ? 'on' : 'off'; + + var loadingWp = bt.load('Deploying wordpress, please wait...'); + bt.send('deploy_wp', 'site/deploy_wp', formValue, function (deploy) { + loadingWp.close(); + if (deploy.status) { + layer.close(indexs); + if (callback) callback(deploy); + } + layer.msg(deploy.msg, { icon: deploy.status ? 1 : 2 }); + }); + } + }); + } + }, + }); + }, + set_default_page: function () { + bt.open({ + type: 1, + area: '460px', + title: lan.site.change_defalut_page, + closeBtn: 2, + shift: 0, + content: + '
                                ', + }); + setTimeout(function () { + $('.change-default button').click(function () { + bt.site.get_default_path($(this).index(), function (path) { + bt.pub.on_edit_file(0, path); + }); + }); + }, 100); + }, + set_default_site: function () { + bt.site.get_default_site(function (rdata) { + var arrs = []; + arrs.push({ title: lan.site.default_site_not_set, value: '0' }); + for (var i = 0; i < rdata.sites.length; i++) + arrs.push({ + title: rdata.sites[i].name, + value: rdata.sites[i].name, + }); + var form = { + title: lan.site.default_site_yes, + area: '530px', + list: [ + { + title: lan.site.default_site, + name: 'defaultSite', + width: '300px', + value: rdata.defaultSite, + type: 'select', + items: arrs, + }, + ], + btns: [ + bt.form.btn.close(), + bt.form.btn.submit(lan.site.submit, function (rdata, load) { + bt.site.set_default_site(rdata.defaultSite, function (rdata) { + load.close(); + bt.msg(rdata); + }); + }), + ], + }; + bt.render_form(form); + $('.line').after($(bt.render_help([lan.site.default_site_help_1, lan.site.default_site_help_2])).addClass('plr20')); + }); + }, + //PHP-CLI + get_cli_version: function () { + $.post('/config?action=get_cli_php_version', {}, function (rdata) { + if (rdata.status === false) { + layer.msg(rdata.msg, { icon: 2 }); + return; + } + var _options = ''; + for (var i = rdata.versions.length - 1; i >= 0; i--) { + var ed = ''; + if (rdata.select.version == rdata.versions[i].version) ed = 'selected'; + _options += ''; + } + var body = + '
                                \ +
                                \ + ' + + lan.site.php_cli_ver + + '\ +
                                \ + \ +
                                \ +
                                \ +
                                  \ +
                                • ' + + lan.site.php_cli_tips1 + + '
                                • \ +
                                • ' + + lan.site.php_cli_tips2 + + '
                                • \ +
                                \ +
                                '; + + layer.open({ + type: 1, + title: lan.site.set_php_cli_cmd, + area: '560px', + closeBtn: 2, + shadeClose: false, + content: body, + }); + }); + }, + set_cli_version: function () { + var php_version = $("select[name='php_version']").val(); + var loading = bt.load(); + $.post('/config?action=set_cli_php_version', { php_version: php_version }, function (rdata) { + loading.close(); + if (rdata.status) { + layer.closeAll(); + } + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + }); + }, + del_site: function (wid, wname, callback) { + var num1 = bt.get_random_num(1, 9), + num2 = bt.get_random_num(1, 9), + title = ''; + title = typeof wname === 'function' ? 'Deleting sites in batches' : lan.site.site_del_title + ' [ ' + wname + ' ]'; + layer.open({ + type: 1, + title: title, + icon: 0, + skin: 'delete_site_layer', + area: '480px', + closeBtn: 2, + shadeClose: true, + content: + '\ +
                                \ + \ +
                                ' + + lan.site.site_del_info + + '
                                \ +
                                \ + \ + \ + \ +
                                \ +
                                \ + ' + + lan.bt.cal_msg + + '' + + num1 + + ' + ' + + num2 + + '=\ + \ +
                                \ +
                                \ + ', + btn: [lan.public.ok, lan.public.cancel], + success: function (layers, indexs) { + $(layers) + .find('.check_type_group label') + .hover( + function () { + var name = $(this).find('input').attr('name'); + if (name === 'data' && !recycle_bin_db_open) { + layer.tips('Risky operation: the current database recycle bin is not open, delete the database will disappear forever!', this, { + tips: [1, 'red'], + time: 0, + }); + } else if (name === 'path' && !recycle_bin_open) { + layer.tips('Risky operation: The current file recycle bin is not open, delete the site directory will disappear forever!', this, { + tips: [1, 'red'], + time: 0, + }); + } + }, + function () { + layer.closeAll('tips'); + } + ); + }, + yes: function (indexs) { + var vcodeResult = $('#vcodeResult'), + data = { id: wid, webname: wname }; + $('#site_delete_form input[type=checkbox]').each(function (index, item) { + if ($(item).is(':checked')) data[$(item).attr('name')] = 1; + }); + if (vcodeResult.val() === '') { + layer.tips('The result cannot be null', vcodeResult, { tips: [1, 'red'], time: 3000 }); + vcodeResult.focus(); + return false; + } else if (parseInt(vcodeResult.val()) !== num1 + num2) { + layer.tips('The calculation is incorrect', vcodeResult, { tips: [1, 'red'], time: 3000 }); + vcodeResult.focus(); + return false; + } + var is_database = data.hasOwnProperty('database'), + is_path = data.hasOwnProperty('path'), + is_ftp = data.hasOwnProperty('ftp'); + if (!is_database && !is_path && (!is_ftp || is_ftp)) { + if (typeof wname === 'function') { + wname(data); + return false; + } + bt.site.del_site(data, function (rdata) { + layer.close(indexs); + if (callback) callback(rdata); + bt.msg(rdata); + }); + return false; + } + if (typeof wname === 'function') { + delete data.id; + delete data.webname; + } + layer.close(indexs); + var ids = JSON.stringify(wid instanceof Array ? wid : [wid]), + countDown = typeof wname === 'string' ? 4 : 9; + title = typeof wname === 'function' ? 'Verify the information twice and delete sites in batches' : 'Verify information twice, delete site [ ' + wname + ' ]'; + var loadT = bt.load('Checking site data information, please wait...'); + bt.send('check_del_data', 'site/check_del_data', { ids: ids }, function (res) { + loadT.close(); + layer.open({ + type: 1, + title: title, + closeBtn: 2, + skin: 'verify_site_layer_info active', + area: '740px', + content: + '
                                ' + + '' + + '
                                Please calm down for a few seconds and confirm the following data to be deleted.
                                ' + + '
                                ' + + '
                                ' + + '
                                ' + + '
                                ' + + '
                                ' + + '
                                ' + + '
                                Risks: The database recycle bin function is not enabled at present. After the database is deleted, the database will disappear forever!
                                ' + + '
                                Risk: The file recycle bin function is disabled at present. After a site directory is deleted, the site directory will disappear forever!
                                ' + + '
                                Please read the above information to be deleted carefully to prevent site data from being deleted by mistake. Confirm that there are still ' + + countDown + + ' seconds left to delete.
                                ' + + '
                                ', + btn: ['Confirm deletion (continue operation after ' + countDown + 'seconds)', 'Cancel'], + success: function (layers) { + var html = '', + rdata = res.data; + for (var i = 0; i < rdata.length; i++) { + var item = rdata[i], + newTime = parseInt(new Date().getTime() / 1000), + t_icon = ''; + + (site_html = (function (item) { + if (!is_path) return ''; + var is_time_rule = newTime - item.st_time > 86400 * 30 && item.total > 1024 * 10, + is_path_rule = res.file_size <= item.total, + dir_time = bt.format_data(item.st_time, 'yyyy-MM-dd'), + dir_size = bt.format_size(item.total); + + var f_html = + ' ' + (item.limit ? 'More than 50 MB' : dir_size) + ' ' + (is_path_rule ? t_icon : ''); + var f_title = + (is_path_rule ? 'Note: This directory may contain important data. Exercise caution when performing this operation.\n' : '') + + 'directory:' + + item.path + + '(' + + (item.limit ? 'greater than ' : '') + + dir_size + + ')'; + + return ( + '
                                ' + + 'Site: ' + + item.name + + '' + + 'Path: ' + + item.path + + ' (' + + f_html + + ')' + + 'Create: ' + + dir_time + + '' + + '
                                ' + ); + })(item)), + (database_html = (function (item) { + if (!is_database || !item.database) return ''; + var is_time_rule = newTime - item.st_time > 86400 * 30 && item.total > 1024 * 10, + is_database_rule = res.db_size <= item.database.total, + database_time = bt.format_data(item.database.st_time, 'yyyy-MM-dd'), + database_size = bt.format_size(item.database.total); + + var f_size = ' ' + database_size + ' ' + (is_database_rule ? t_icon : ''); + var t_size = 'Note: This database is large and may contain important data. Exercise caution when performing this operation.\ndatabase:' + database_size; + + return ( + '
                                ' + + 'DB: ' + + item.database.name + + '' + + 'Size: ' + + f_size + + '' + + 'Create: ' + + database_time + + '' + + '
                                ' + ); + })(item)); + if (site_html + database_html !== '') html += '
                                ' + site_html + database_html + '
                                '; + } + if (html === '') html = '
                                No data
                                '; + $('.check_layer_content').html(html); + var interVal = setInterval(function () { + countDown--; + $(layers) + .find('.layui-layer-btn0') + .text('Confirm deletion (continue operation after ' + countDown + ' seconds)'); + $(layers).find('.check_layer_message span').text(countDown); + }, 1000); + setTimeout(function () { + $(layers).find('.layui-layer-btn0').text('Confirm the deletion'); + $(layers).find('.check_layer_message').html('Note: please read the above information carefully to prevent site data from being deleted by mistake'); + $(layers).removeClass('active'); + clearInterval(interVal); + }, countDown * 1000); + }, + yes: function (indes, layers) { + if ($(layers).hasClass('active')) { + layer.tips('Please confirm the information and try again later. ' + countDown + ' seconds left', $(layers).find('.layui-layer-btn0'), { + tips: [1, 'red'], + time: 3000, + }); + return; + } + if (typeof wname === 'function') { + wname(data); + } else { + bt.site.del_site(data, function (rdata) { + layer.closeAll(); + if (rdata.status) site.get_list(); + if (callback) callback(rdata); + bt.msg(rdata); + }); + } + }, + }); + }); + }, + }); + }, + batch_site: function (type, obj, result) { + if (obj == undefined) { + obj = {}; + var arr = []; + result = { count: 0, error_list: [] }; + $('input[type="checkbox"].check:checked').each(function () { + var _val = $(this).val(); + if (!isNaN(_val)) arr.push($(this).parents('tr').data('item')); + }); + if (type == 'site_type') { + bt.site.get_type(function (tdata) { + var types = []; + for (var i = 0; i < tdata.length; i++) types.push({ title: tdata[i].name, value: tdata[i].id }); + var form = { + title: lan.site.set_site_classification, + area: '530px', + list: [ + { + title: lan.site.default_site, + name: 'type_id', + width: '300px', + type: 'select', + items: types, + }, + ], + btns: [ + bt.form.btn.close(), + bt.form.btn.submit(lan.site.submit, function (rdata, load) { + var ids = []; + for (var x = 0; x < arr.length; x++) ids.push(arr[x].id); + bt.site.set_site_type( + { + id: rdata.type_id, + site_array: JSON.stringify(ids), + }, + function (rrdata) { + if (rrdata.status) { + load.close(); + site.get_list(); + } + bt.msg(rrdata); + } + ); + }), + ], + }; + bt.render_form(form); + }); + return; + } + var thtml = "
                                '; + bt.show_confirm( + lan.site.all_del_site, + "" + lan.get('del_all_site', [arr.length]) + '', + function () { + if ($('#delpath').is(':checked')) obj.path = '1'; + obj.data = arr; + bt.closeAll(); + site.batch_site(type, obj, result); + }, + thtml + ); + + return; + } + var item = obj.data[0]; + switch (type) { + case 'del': + if (obj.data.length < 1) { + site.get_list(); + bt.msg({ msg: lan.get('del_all_site_ok', [result.count]), icon: 1, time: 5000 }); + return; + } + var data = { id: item.id, webname: item.name, path: obj.path }; + bt.site.del_site(data, function (rdata) { + if (rdata.status) { + result.count += 1; + } else { + result.error_list.push({ name: item.item, err_msg: rdata.msg }); + } + obj.data.splice(0, 1); + site.batch_site(type, obj, result); + }); + break; + } + }, + set_class_type: function () { + var _form_data = bt.render_form_line({ + title: '', + items: [ + { placeholder: lan.site.input_classification_name, name: 'type_name', width: '50%', type: 'text' }, + { + name: 'btn_submit', + text: lan.site.add, + type: 'button', + callback: function (sdata) { + bt.site.add_type(sdata.type_name, function (ldata) { + if (ldata.status) { + $('[name="type_name"]').val(''); + site.get_class_type(); + site.init_site_type(); + } + bt.msg(ldata); + }); + }, + }, + ], + }); + bt.open({ + type: 1, + area: '350px', + title: lan.site.mam_site_classificacion, + closeBtn: 2, + shift: 5, + shadeClose: true, + content: + "
                                " + + _form_data.html + + "
                                ", + success: function () { + bt.render_clicks(_form_data.clicks); + site.get_class_type(function (res) { + $('#type_table').on('click', '.del_type', function () { + var _this = $(this); + var item = _this.parents('tr').data('item'); + if (item.id == 0) { + bt.msg({ icon: 2, msg: lan.site.default_classification_cant_operation }); + return; + } + bt.confirm( + { + msg: lan.site.sure_del_classification, + title: lan.site.del_classification + '【' + item.name + '】', + }, + function () { + bt.site.del_type(item.id, function (ret) { + if (ret.status) { + site.get_class_type(); + site.init_site_type(); + bt.set_cookie('site_type', '-1'); + } + bt.msg(ret); + }); + } + ); + }); + $('#type_table').on('click', '.edit_type', function () { + var item = $(this).parents('tr').data('item'); + if (item.id == 0) { + bt.msg({ icon: 2, msg: lan.site.default_classification_cant_operation }); + return; + } + bt.render_form({ + title: lan.site.edit_classification_mam + '【' + item.name + '】', + area: '350px', + list: [ + { + title: lan.site.classification_name, + width: '150px', + name: 'name', + value: item.name, + }, + ], + btns: [ + { title: lan.site.turn_off, name: 'close' }, + { + title: lan.site.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + bt.site.edit_type({ id: item.id, name: rdata.name }, function (edata) { + if (edata.status) { + load.close(); + site.get_class_type(); + site.init_site_type(); + } + bt.msg(edata); + }); + }, + }, + ], + }); + }); + }); + }, + }); + }, + get_class_type: function (callback) { + site.get_types(function (rdata) { + bt.render({ + table: '#type_table', + columns: [ + { field: 'name', title: lan.site.name }, + { + field: 'opt', + width: '80px', + title: lan.site.operate, + templet: function (item) { + return '' + lan.site.edit + ' | ' + lan.site.del + ''; + }, + }, + ], + data: rdata, + }); + $('.layui-layer-page').css({ + 'margin-top': '-' + $('.layui-layer-page').height() / 2 + 'px', + top: '50%', + }); + if (callback) callback(rdata); + }); + }, + ssl: { + my_ssl_msg: null, + + //续签订单内 + renew_ssl: function (siteName, auth_type, index) { + acme.siteName = siteName; + if (index.length === 32 && index.indexOf('/') === -1) { + acme.renew(index, function (rdata) { + site.ssl.ssl_result(rdata, auth_type, siteName); + }); + } else { + acme.get_cert_init(index, siteName, function (cert_init) { + acme.domains = cert_init.dns; + var options = ''; + for (var i = 0; i < cert_init.dnsapi.length; i++) { + options += ''; + } + acme.select_loadT = layer.open({ + title: "Renew Let's Encrypt Certificate", + type: 1, + closeBtn: 2, + shade: 0.3, + area: '500px', + offset: '30%', + content: + '
                                \ +
                                \ +
                                Please select a verification method:
                                \ +
                                \ + \ + \ + \ +
                                \ +
                                \ +
                                  \ +
                                • Wildcard certificate cannot use [File Authentication], please select DNS authentication
                                • \ +
                                • Use [File Authentication], please make sure that [Enable HTTPS / 301 Redirect / Reverse Proxy] and other functions are not enabled.
                                • \ +
                                • Use [Alibaba Cloud DNS] [DnsPod] and other authentication methods to set the correct key
                                • \ +
                                • After the renewal is successful, the certificate will try to renew automatically 30 days before the next expiration
                                • \ +
                                • Using [DNS Authentication-Manual Resolution] Renewed certificate cannot be automatically renewed 30 days before the next expiration
                                • \ +
                                \ +
                                ', + success: function (layers) { + $("select[name='auth_to']").change(function () { + var dnsapi = $(this).val(); + $('.dnsapi-btn').html(''); + for (var i = 0; i < cert_init.dnsapi.length; i++) { + if (cert_init.dnsapi[i].name !== dnsapi) continue; + acme.dnsapi = cert_init.dnsapi[i]; + if (!cert_init.dnsapi[i].data) continue; + $('.dnsapi-btn').html(''); + if (cert_init.dnsapi[i].data[0].value || cert_init.dnsapi[i].data[1].value) break; + site.ssl.show_dnsapi_setup(); + } + }); + }, + }); + }); + } + }, + //续签其它 + renew_ssl_other: function () { + var auth_to = $("select[name='auth_to']").val(); + var auth_type = 'http'; + if (auth_to === 'http') { + if (JSON.stringify(acme.domains).indexOf('*.') !== -1) { + layer.msg('Domain names containing wildcards cannot use File Authentication (HTTP)!', { icon: 2 }); + return; + } + auth_to = acme.id; + } else { + if (auth_to !== 'dns') { + if (auth_to === 'Dns_com') { + acme.dnsapi.data = [{ value: 'None' }, { value: 'None' }]; + } + if (!acme.dnsapi.data[0].value || !acme.dnsapi.data[1].value) { + layer.msg('Please set [' + acme.dnsapi.title + '] interface information first!', { icon: 2 }); + return; + } + auth_to = auth_to + '|' + acme.dnsapi.data[0].value + '|' + acme.dnsapi.data[1].value; + } + auth_type = 'dns'; + } + layer.close(acme.select_loadT); + acme.apply_cert(acme.domains, auth_type, auth_to, '0', function (rdata) { + site.ssl.ssl_result(rdata, auth_type, acme.siteName); + }); + }, + show_dnsapi_setup: function () { + var dnsapi = acme.dnsapi; + acme.dnsapi_loadT = layer.open({ + title: 'Set [' + dnsapi.title + '] interface', + type: 1, + closeBtn: 0, + shade: 0.3, + area: '550px', + offset: '30%', + content: + '
                                \ +
                                \ + ' + + dnsapi.data[0].key + + '\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + ' + + dnsapi.data[1].key + + '\ +
                                \ + \ +
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                  \ +
                                • ' + + dnsapi.help + + '
                                • \ +
                                \ +
                                ', + success: function (layers) { + $('.dnsapi-save').click(function () { + var dnsapi_key = $('.dnsapi-key'); + var dnsapi_token = $('.dnsapi-token'); + pdata = {}; + pdata[dnsapi_key.attr('name')] = dnsapi_key.val(); + pdata[dnsapi_token.attr('name')] = dnsapi_token.val(); + acme.dnsapi.data[0].value = dnsapi_key.val(); + acme.dnsapi.data[1].value = dnsapi_token.val(); + bt.site.set_dns_api({ pdata: JSON.stringify(pdata) }, function (ret) { + if (ret.status) layer.close(acme.dnsapi_loadT); + bt.msg(ret); + }); + }); + }, + }); + }, + set_cert: function (siteName, res) { + var loadT = bt.load(lan.site.saving_txt); + var pdata = { + type: 1, + siteName: siteName, + key: res.private_key, + csr: res.cert + res.root, + }; + bt.send('SetSSL', 'site/SetSSL', pdata, function (rdata) { + loadT.close(); + site.reload(); + layer.msg(res.msg, { icon: 1 }); + }); + }, + show_error: function (res, auth_type) { + var area_size = '500px'; + var err_info = ''; + if (res.msg[1].challenges === undefined) { + err_info += '

                                Response status:' + res.msg[1].status + '

                                '; + err_info += '

                                Error type:' + res.msg[1].type + '

                                '; + err_info += '

                                Error code:' + res.msg[1].detail + '

                                '; + } else { + if (!res.msg[1].challenges[1]) { + if (res.msg[1].challenges[0]) { + res.msg[1].challenges[1] = res.msg[1].challenges[0]; + } + } + if (res.msg[1].status === 'invalid') { + area_size = '600px'; + var trs = $('#dns_txt_jx tbody tr'); + var dns_value = ''; + + for (var imd = 0; imd < trs.length; imd++) { + if (trs[imd].outerText.indexOf(res.msg[1].identifier.value) == -1) continue; + var s_tmp = trs[imd].outerText.split('\t'); + if (s_tmp.length > 1) { + dns_value = s_tmp[1]; + break; + } + } + + err_info += '

                                Verify domain name:' + res.msg[1].identifier.value + '

                                '; + if (auth_type === 'dns') { + var check_url = '_acme-challenge.' + res.msg[1].identifier.value; + err_info += '

                                Verify record:' + check_url + '

                                '; + err_info += '

                                Verify content:' + dns_value + '

                                '; + err_info += '

                                Error code:' + site.html_encode(res.msg[1].challenges[1].error.detail) + '

                                '; + } else { + var check_url = 'http://' + res.msg[1].identifier.value + '/.well-known/acme-challenge/' + res.msg[1].challenges[0].token; + err_info += "

                                Verify URL:Click to view

                                "; + err_info += '

                                Verify content:' + res.msg[1].challenges[0].token + '

                                '; + err_info += '

                                Error code:' + site.html_encode(res.msg[1].challenges[0].error.detail) + '

                                '; + } + err_info += "

                                Verify results: Verify failed

                                "; + } + } + + layer.msg('
                                ' + res.msg[0] + '' + err_info + '
                                ', { + icon: 2, + time: 0, + shade: 0.3, + shadeClose: true, + area: area_size, + }); + }, + ssl_result: function (res, auth_type, siteName) { + layer.close(acme.loadT); + if (res.status === false && typeof res.msg === 'string') { + bt.msg(res); + return; + } + if (res.status === true || res.status === 'pending' || res.save_path !== undefined) { + if (auth_type == 'dns' && res.status === 'pending') { + var b_load = bt.open({ + type: 1, + area: '700px', + title: 'Manually parse TXT records', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
                                \ +

                                Please do TXT analysis according to the following list:

                                \ +
                                \ +
                                \ + \ +
                                \ +
                                ", + }); + + //手动验证事件 + $('.btn_check_txt').click(function () { + acme.auth_domain(res.index, function (res1) { + layer.close(acme.loadT); + if (res1.status === true) { + b_load.close(); + site.ssl.set_cert(siteName, res1); + } else { + site.ssl.show_error(res1, auth_type); + } + }); + }); + + //显示手动验证信息 + setTimeout(function () { + var data = []; + acme_txt = '_acme-challenge.'; + for (var j = 0; j < res.auths.length; j++) { + data.push({ + name: acme_txt + res.auths[j].domain.replace('*.', ''), + type: 'TXT', + txt: res.auths[j].auth_value, + force: 'Yes', + }); + data.push({ + name: res.auths[j].domain.replace('*.', ''), + type: 'CAA', + txt: '0 issue "letsencrypt.org"', + force: 'No', + }); + } + bt.render({ + table: '#dns_txt_jx', + columns: [ + { field: 'name', width: '220px', title: 'Resolving domain names' }, + { field: 'txt', title: 'Record value' }, + { field: 'type', title: 'Types of' }, + { field: 'force', title: 'essential' }, + ], + data: data, + }); + $('.div_txt_jx').append( + bt.render_help([ + 'It takes some time to resolve the domain name to take effect. After completing all the resolution operations, please wait 1 minute before clicking the verification button.', + 'You can manually verify whether the domain name resolution is effective through CMD commands: nslookup -q=txt ' + acme_txt + res.auths[0].domain.replace('*.', ''), + 'If you are using Pagoda Cloud Resolution Plugin, Alibaba Cloud DNS, DnsPod as DNS, you can use the DNS interface to automatically resolve', + ]) + ); + }); + return; + } + site.ssl.set_cert(siteName, res); + return; + } + + site.ssl.show_error(res, auth_type); + }, + get_renew_stat: function () { + $.post('/ssl?action=Get_Renew_SSL', {}, function (task_list) { + if (!task_list.status) return; + var s_body = ''; + var b_stat = false; + for (var i = 0; i < task_list.data.length; i++) { + s_body += '

                                ' + task_list.data[i].subject + ' >> ' + task_list.data[i].msg + '

                                '; + if (task_list.data[i].status !== true && task_list.data[i].status !== false) { + b_stat = true; + } + } + + if (site.ssl.my_ssl_msg) { + $('.my-renew-ssl').html(s_body); + } else { + site.ssl.my_ssl_msg = layer.msg('
                                ' + s_body + '
                                ', { + time: 0, + icon: 16, + shade: 0.3, + }); + } + + if (!b_stat) { + setTimeout(function () { + layer.close(site.ssl.my_ssl_msg); + site.ssl.my_ssl_msg = null; + }, 3000); + return; + } + + setTimeout(function () { + site.ssl.get_renew_stat(); + }, 1000); + }); + }, + onekey_ssl: function (partnerOrderId, siteName) { + bt.site.get_ssl_info(partnerOrderId, siteName, function (rdata) { + bt.msg(rdata); + if (rdata.status) site.set_ssl(site.web); + }); + }, + set_ssl_status: function (action, siteName, ssl_id) { + bt.site.set_ssl_status(action, siteName, function (rdata) { + bt.msg(rdata); + if (rdata.status) { + site.set_ssl(site.web); + if (ssl_id != undefined) { + setTimeout(function () { + $('#ssl_tabs span:eq(' + ssl_id + ')').click(); + }, 1000); + } + if (action == 'CloseSSLConf') { + layer.msg(lan.site.ssl_close_info, { icon: 1, time: 5000 }); + } + } + }); + }, + verify_domain: function (partnerOrderId, siteName) { + bt.site.verify_domain(partnerOrderId, siteName, function (vdata) { + bt.msg(vdata); + if (vdata.status) { + if (vdata.data.stateCode == 'COMPLETED') { + site.ssl.onekey_ssl(partnerOrderId, siteName); + } else { + layer.msg('Waiting for CA verification, if it fails to verify successfully for a long time, please log in to the official website and use DNS to re-apply...'); + } + } + }); + }, + reload: function (index) { + if (index == undefined) index = 0; + var _sel = $('#ssl_tabs .on'); + if (_sel.length == 0) _sel = $('#ssl_tabs span:eq(0)'); + _sel.trigger('click'); + }, + set_auto_restart_rph: function (sitename) { + var $checkbox = $('#auto_restart_rph'); + var checked = $checkbox.is(':checked'); + var url = checked ? 'remove_auto_restart_rph' : 'auto_restart_rph'; + var loadT = bt.load(lan.site.the_msg); + $.post( + '/site?action=' + url, + { + sitename: sitename, + }, + function (res) { + loadT.close(); + bt.msg(res); + if (res.status) { + $checkbox.prop('checked', !checked); + } + } + ); + }, + }, + edit: { + update_composer: function () { + loadT = bt.load(); + $.post( + '/files?action=update_composer', + { + repo: $("select[name='repo']").val(), + }, + function (v_data) { + loadT.close(); + bt.msg(v_data); + } + ); + }, + show_composer_log: function () { + $.post( + '/ajax?action=get_lines', + { + filename: '/tmp/composer.log', + num: 30, + }, + function (v_body) { + var log_obj = $('#composer-log'); + if (log_obj.length < 1) return; + log_obj.html(v_body.msg); + var div = document.getElementById('composer-log'); + div.scrollTop = div.scrollHeight; + if (v_body.msg.indexOf('BT-Exec-Completed') != -1) { + //layer.close(site.edit.comp_showlog); + layer.msg('Execution complete', { + icon: 1, + }); + return; + } + + setTimeout(function () { + site.edit.show_composer_log(); + }, 1000); + } + ); + }, + comp_confirm: 0, + comp_showlog: 0, + exec_composer: function () { + site.edit.comp_confirm = layer.confirm( + 'The impact of Composer execution depends on the composer.json configuration file in this directory. Continue?', + { + title: 'Execute composer', + closeBtn: 2, + icon: 3, + }, + function (index) { + layer.close(site.edit.comp_confirm); + var pdata = { + php_version: $("select[name='php_version']").val(), + composer_args: $("select[name='composer_args']").val(), + composer_cmd: $("input[name='composer_cmd']").val(), + repo: $("select[name='repo']").val(), + path: $("input[name='composer_path']").val(), + user: $("select[name='composer_user']").val(), + }; + $.post('/files?action=exec_composer', pdata, function (rdatas) { + if (!rdatas.status) { + layer.msg(rdatas.msg, { + icon: 2, + }); + return false; + } + if (rdatas.status === true) { + site.edit.comp_showlog = layer.open({ + area: '800px', + type: 1, + shift: 5, + closeBtn: 2, + title: 'Execute Composer in the [' + pdata['path'] + '] directory. After execution, please close this window after confirming that there is no problem', + content: "
                                ",
                                +							});
                                +							setTimeout(function () {
                                +								site.edit.show_composer_log();
                                +							}, 200);
                                +						}
                                +					});
                                +				}
                                +			);
                                +		},
                                +		remove_composer_lock: function (path) {
                                +			$.post(
                                +				'/files?action=DeleteFile',
                                +				{
                                +					path: path + '/composer.lock',
                                +				},
                                +				function (rdata) {
                                +					bt.msg(rdata);
                                +					$('.composer-msg').remove();
                                +					$('.composer-rm').remove();
                                +				}
                                +			);
                                +		},
                                +		set_composer: function (web) {
                                +			$.post(
                                +				'/files?action=get_composer_version',
                                +				{
                                +					path: web.path,
                                +				},
                                +				function (v_data) {
                                +					if (v_data.status === false) {
                                +						bt.msg(v_data);
                                +						return;
                                +					}
                                +
                                +					var php_versions = '';
                                +					for (var i = 0; i < v_data.php_versions.length; i++) {
                                +						if (v_data.php_versions[i].version == '00') continue;
                                +						php_versions += '';
                                +					}
                                +
                                +					var msg = '';
                                +					if (v_data.comp_lock) {
                                +						msg += '' + v_data.comp_lock + ' [Delete]';
                                +					}
                                +					if (v_data.comp_json !== true) {
                                +						msg += '' + v_data.comp_json + '';
                                +					}
                                +
                                +					var com_body =
                                +						'' +
                                +						'
                                Version
                                ' + + '
                                PHP
                                ' + + '' + + '
                                ' + + '
                                Parameters
                                ' + + '' + + '
                                ' + + '
                                Extra commands
                                ' + + '' + + '
                                ' + + '
                                Source
                                ' + + '' + + '
                                ' + + '
                                User
                                ' + + '' + + '
                                ' + + '
                                Dir
                                ' + + '' + + '
                                ' + + '
                                ' + + msg + + '
                                ' + + '
                                ' + + '
                                ' + + '
                                  ' + + '
                                • Directory:Website root dir by default, please make sure that the dir contains composer.json
                                • ' + + '
                                • User:The default user www, unless your website is run with root privileges, it is not recommended to use the root user to execute composer
                                • ' + + '
                                • Source:source of composer
                                • ' + + '
                                • Parameters:Install (install dependent package), Update (upgrade dependent package), please select as needed
                                • ' + + '
                                • Extra commands: If this is empty, it will be executed according to the conf in composer.json, Supported fill in the complete composer command
                                • ' + + '
                                • PHP version:The PHP version used to execute composer, it is recommended to try the default, if the installation fails, try to choose another PHP version
                                • ' + + '
                                • Composer version:Composer version, you can click [Upgrade Composer] on the right to upgrade Composer to the latest stable version
                                • ' + + '
                                '; + $('#webedit-con').html(com_body); + } + ); + }, + set_domains: function (web) { + var _this = this; + var list = [ + { + items: [ + { name: 'newdomain', width: '400px', type: 'textarea', placeholder: lan.site.domain_help }, + { + name: 'btn_submit_domain', + text: lan.site.add, + type: 'button', + callback: function (sdata) { + var arrs = sdata.newdomain.split('\n'); + var domins = ''; + for (var i = 0; i < arrs.length; i++) domins += arrs[i] + ','; + bt.site.add_domains(web.id, web.name, bt.rtrim(domins, ','), function (ret) { + if (ret.status) site.reload(0); + }); + }, + }, + ], + }, + ]; + var _form_data = bt.render_form_line(list[0]), + loadT = null, + placeholder = null; + $('#webedit-con').html(_form_data.html + "
                                "); + bt.render_clicks(_form_data.clicks); + $('.btn_submit_domain').addClass('pull-right').css('margin', '30px 35px 0 0'); + placeholder = $('.placeholder'); + placeholder + .click(function () { + $(this).hide(); + $('.newdomain').focus(); + }) + .css({ + width: '340px', + heigth: '100px', + left: '0px', + top: '0px', + 'padding-top': '10px', + 'padding-left': '15px', + }); + $('.newdomain') + .focus(function () { + placeholder.hide(); + loadT = layer.tips(placeholder.html(), $(this), { tips: [1, '#20a53a'], time: 0, area: $(this).width() }); + }) + .blur(function () { + if ($(this).val().length == 0) placeholder.show(); + layer.close(loadT); + }); + + bt_tools.table({ + el: '#domain_table', + url: '/data?action=getData', + param: { table: 'domain', list: 'True', search: web.id }, + dataFilter: function (res) { + return { data: res }; + }, + column: [ + { type: 'checkbox', width: 20, keepNumber: 1 }, + { + fid: 'name', + title: lan.site.domain, + template: function (row) { + return '' + row.name + ''; + }, + }, + { fid: 'port', title: lan.site.port, width: 50, type: 'text' }, + { + title: 'OPT', + width: 80, + type: 'group', + align: 'right', + group: [ + { + title: 'Del', + template: function (row, that) { + return that.data.length === 1 ? 'Inoperable' : 'Del'; + }, + event: function (row, index, ev, key, that) { + if (that.data.length === 1) { + bt.msg({ status: false, msg: 'The last domain name cannot be deleted!' }); + return false; + } + bt.confirm( + { + title: 'Delete domain [ ' + row.name + ' ]', + msg: lan.site.domain_del_confirm, + }, + function () { + bt.site.del_domain(web.id, web.name, row.name, row.port, function (res) { + if (res.status) that.$delete_table_row(index); + bt.msg(res); + }); + } + ); + }, + }, + ], + }, + ], + tootls: [ + { + // 批量操作 + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' delete', + url: '/site?action=delete_domain_multiple', + param: { id: web.id }, + paramId: 'id', + paramName: 'domains_id', + theadName: 'Domain', + confirmVerify: false, //是否提示验证方式 + refresh: true, + }, + }, + ], + }); + $('#domain_table>.divtable').css('max-height', '350px'); + }, + set_dirbind: function (web) { + var _this = this; + $('#webedit-con').html('
                                '); + bt_tools.table({ + el: '#sub_dir_table', + url: '/site?action=GetDirBinding', + param: { id: web.id }, + dataFilter: function (res) { + if ($('#webedit-con').children().length === 2) return { data: res.binding }; + var dirs = []; + for (var n = 0; n < res.dirs.length; n++) dirs.push({ title: res.dirs[n], value: res.dirs[n] }); + var data = { + title: '', + class: 'mb0', + items: [ + { title: lan.site.domain, width: '140px', name: 'domain' }, + { title: lan.site.subdirectories, name: 'dirName', type: 'select', items: dirs }, + { + text: lan.site.add, + type: 'button', + name: 'btn_add_subdir', + callback: function (sdata) { + if (!sdata.domain || !sdata.dirName) { + layer.msg(lan.site.d_s_empty, { icon: 2 }); + return; + } + bt.site.add_dirbind(web.id, sdata.domain, sdata.dirName, function (ret) { + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + if (ret.status) site.reload(1); + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(data); + $('#webedit-con').prepend(_form_data.html); + bt.render_clicks(_form_data.clicks); + return { data: res.binding }; + }, + column: [ + { type: 'checkbox', width: 20, keepNumber: 1 }, + { fid: 'domain', title: lan.site.domain, type: 'text' }, + { fid: 'port', title: lan.site.port, width: 70, type: 'text' }, + { fid: 'path', title: lan.site.subdirectories, width: 70, type: 'text' }, + { + title: 'Opt', + width: 130, + type: 'group', + align: 'right', + group: [ + { + title: 'URL rewrite', + event: function (row, index, ev, key, that) { + bt.site.get_dir_rewrite({ id: row.id }, function (ret) { + if (!ret.status) { + var confirmObj = layer.confirm( + lan.site.url_rewrite_alter, + { + icon: 3, + closeBtn: 2, + }, + function () { + bt.site.get_dir_rewrite({ id: row.id, add: 1 }, function (ret) { + layer.close(confirmObj); + show_dir_rewrite(ret); + }); + } + ); + return; + } + show_dir_rewrite(ret); + + function get_rewrite_file(name) { + var spath = '/www/server/panel/rewrite/' + (bt.get_cookie('serverType') == 'openlitespeed' ? 'apache' : bt.get_cookie('serverType')) + '/' + name + '.conf'; + if (bt.get_cookie('serverType') == 'nginx') { + if (name == 'default') spath = '/www/server/panel/vhost/rewrite/' + web.name + '_' + row['path'] + '.conf'; + } else { + if (name == 'default') spath = '/www/wwwroot/' + web.name + '/' + row['path'] + '.htaccess'; + } + bt.files.get_file_body(spath, function (sdata) { + $('.dir_config').text(sdata.data); + }); + } + + function show_dir_rewrite(ret) { + var load_form = bt.open({ + type: 1, + area: ['510px', '530px'], + title: lan.site.config_url, + closeBtn: 2, + shift: 5, + skin: 'bt-w-con', + shadeClose: true, + content: "
                                ", + success: function () { + var _html = $('.webedit-dir-box'), + arrs = []; + for (var i = 0; i < ret.rlist.length; i++) { + if (i == 0) { + arrs.push({ title: ret.rlist[i], value: 'default' }); + } else { + arrs.push({ title: ret.rlist[i], value: ret.rlist[i] }); + } + } + var datas = [ + { + name: 'dir_rewrite', + type: 'select', + width: '130px', + items: arrs, + callback: function (obj) { + get_rewrite_file(obj.val()); + }, + }, + { + items: [ + { + name: 'dir_config', + type: 'textarea', + value: ret.data, + width: '470px', + height: '260px', + }, + ], + }, + { + items: [ + { + name: 'btn_save', + text: 'Save', + type: 'button', + callback: function (ldata) { + // console.log(ret) + bt.files.set_file_body(ret.filename, ldata.dir_config, 'utf-8', function (sdata) { + if (sdata.status) load_form.close(); + bt.msg(sdata); + }); + }, + }, + ], + }, + ]; + var clicks = []; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + _html.append(_form_data.html); + var _other = + bt.os == 'Linux' && i == 0 ? 'Rewrite rule converter:Apache to Nginx' : ''; + _html.find('.info-r').append(_other); + clicks = clicks.concat(_form_data.clicks); + } + _html.append( + bt.render_help([ + 'Please select your application.', + 'If the site cannot be accessed after the rewrite rules set, please try to reset to default.', + 'You are able to modify rewrite rules, just save it after modification.', + ]) + ); + bt.render_clicks(clicks); + get_rewrite_file($('.dir_rewrite option:eq(0)').val()); + }, + }); + } + }); + }, + }, + { + title: 'Del', + event: function (row, index, ev, key, that) { + bt.confirm( + { + title: 'Are you sure to delete this【' + row.path + '】 subdirectory binding?', + msg: lan.site.s_bin_del, + }, + function () { + bt.site.del_dirbind(row.id, function (res) { + if (res.status) that.$delete_table_row(index); + bt.msg(res); + }); + } + ); + }, + }, + ], + }, + ], + tootls: [ + { + // 批量操作 + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' execute', + url: '/site?action=delete_dir_bind_multiple', + param: { id: web.id }, + paramId: 'id', + paramName: 'bind_ids', + theadName: 'Domain', + confirmVerify: false, //是否提示验证方式 + }, + }, + ], + }); + }, + set_dirpath: function (web) { + var loading = bt.load(); + bt.site.get_site_path(web.id, function (path) { + bt.site.get_dir_userini(web.id, path, function (rdata) { + loading.close(); + var dirs = []; + var is_n = false; + for (var n = 0; n < rdata.runPath.dirs.length; n++) { + dirs.push({ title: rdata.runPath.dirs[n], value: rdata.runPath.dirs[n] }); + if (rdata.runPath.runPath === rdata.runPath.dirs[n]) is_n = true; + } + if (!is_n) dirs.push({ title: rdata.runPath.runPath, value: rdata.runPath.runPath }); + var datas = [ + { + title: '', + items: [ + { + name: 'userini', + type: 'checkbox', + text: lan.site.anti_XSS_attack + '(open_basedir)', + value: rdata.userini, + callback: function (sdata) { + bt.site.set_dir_userini(path, web.id, function (ret) { + if (ret.status) site.reload(2); + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + }); + }, + }, + { + name: 'logs', + type: 'checkbox', + text: lan.site.write_access_log, + value: rdata.logs, + callback: function (sdata) { + bt.site.set_logs_status(web.id, function (ret) { + if (ret.status) site.reload(2); + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + }); + }, + }, + ], + }, + { + title: '', + items: [ + { + name: 'path', + title: lan.site.site_menu_2, + width: '240px', + value: path, + add_class: 'ml5', + event: { + css: 'glyphicon-folder-open', + callback: function (obj) { + bt.select_path(obj); + }, + }, + }, + { + name: 'btn_site_path', + type: 'button', + text: lan.site.save, + callback: function (pdata) { + bt.site.set_site_path_new(web.id, pdata.path, web.name, function (ret) { + if (ret.status) site.reload(2); + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + }); + }, + }, + ], + }, + { + title: '', + items: [ + { + title: lan.site.run_dir, + width: '240px', + value: rdata.runPath.runPath, + name: 'dirName', + type: 'select', + add_class: 'ml5 mr20', + items: dirs, + }, + { + name: 'btn_run_path', + type: 'button', + text: lan.site.save, + callback: function (pdata) { + bt.site.set_site_runpath(web.id, pdata.dirName, function (ret) { + if (ret.status) site.reload(2); + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + }); + }, + }, + ], + }, + ]; + var _html = $("
                                "); + var clicks = []; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + _html.append($(_form_data.html).addClass('line mtb10')); + clicks = clicks.concat(_form_data.clicks); + } + _html.find('input[name="path"]').parent().css('padding-left', '27px'); + _html.find('input[type="checkbox"]').parent().addClass('label-input-group ptb10'); + _html.find('button[name="btn_run_path"]').addClass('ml45'); + _html.find('button[name="btn_site_path"]').addClass('ml33'); + _html.append(bt.render_help([lan.site.specify_subdir])); + if (bt.os == 'Linux') + _html.append( + '
                                ' + + lan.site.pass_visit + + '
                                ' + ); + + $('#webedit-con').append(_html); + bt.render_clicks(clicks); + $('#pathSafe').click(function () { + var val = $(this).prop('checked'); + var _div = $('.user_pw'); + if (val) { + var dpwds = [ + { + title: lan.site.access_account, + width: '250px', + name: 'username_get', + placeholder: lan.site.no_change_set_empty, + }, + { + title: lan.site.pass_visit, + width: '250px', + type: 'password', + name: 'password_get_1', + placeholder: lan.site.no_change_set_empty, + }, + { + title: lan.site.pass_again, + width: '250px', + type: 'password', + name: 'password_get_2', + placeholder: lan.site.no_change_set_empty, + }, + { + name: 'btn_password_get', + text: lan.site.save, + type: 'button', + callback: function (rpwd) { + if (rpwd.password_get_1 != rpwd.password_get_2) { + layer.msg(lan.bt.pass_err_re, { icon: 2 }); + return; + } + bt.site.set_site_pwd(web.id, rpwd.username_get, rpwd.password_get_1, function (ret) { + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + if (ret.status) site.reload(2); + }); + }, + }, + ]; + for (var i = 0; i < dpwds.length; i++) { + var _from_pwd = bt.render_form_line(dpwds[i]); + _div.append('
                                ' + _from_pwd.html + '
                                '); + bt.render_clicks(_from_pwd.clicks); + } + } else { + bt.site.close_site_pwd(web.id, function (rdata) { + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + _div.html(''); + }); + } + }); + if (rdata.pass) $('#pathSafe').trigger('click'); + }); + }); + }, + set_dirguard: function (web) { + $('#webedit-con').html('
                                '); + var tab = + '
                                \ + Limit accessDeny access\ +
                                \ +
                                \ + '; + $('#set_dirguard').html(tab); + var dir_dirguard = bt_tools.table({ + el: '#dir_dirguard', + url: '/site?action=get_dir_auth', + param: { id: web.id }, + dataFilter: function (res) { + return { data: res[web.name] }; + }, + column: [ + { type: 'checkbox', width: 20 }, + { fid: 'name', title: lan.site.name, type: 'text' }, + { fid: 'site_dir', title: 'Path', type: 'text' }, + { + title: lan.site.operate, + width: 110, + type: 'group', + align: 'right', + group: [ + { + title: lan.site.edit, + event: function (row, index, ev, key, that) { + site.edit.template_Dir(web.id, false, row); + }, + }, + { + title: lan.site.del, + event: function (row, index, ev, key, that) { + bt.site.delete_dir_guard(web.id, row.name, function (res) { + if (res.status) that.$delete_table_row(index); + bt.msg(res); + }); + }, + }, + ], + }, + ], + tootls: [ + { + // 按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Add limit access', + active: true, + event: function (ev) { + site.edit.template_Dir(web.id, true); + }, + }, + ], + }, + { + // 批量操作 + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' delete', + url: '/site?action=delete_dir_auth', + param: function (row) { + return { + id: web.id, + name: row.name, + }; + }, + load: true, + callback: function (that) { + // 手动执行,data参数包含所有选中的站点 + bt.show_confirm('Delete limit access', 'Do you want to delete limit access ?', function () { + that.start_batch({}, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += '' + item.name + '' + item.request.msg + ''; + } + dir_dirguard.$batch_success_table({ + title: 'Limit access', + th: 'Limit access name', + html: html, + }); + dir_dirguard.$refresh_table_list(true); + }); + }); + }, + }, + }, + ], + }); + var php_dirguard = bt_tools.table({ + el: '#php_dirguard', + url: '/config?action=get_file_deny', + param: { + website: web.name, + }, + dataFilter: function (res) { + return { + data: res, + }; + }, + column: [ + { type: 'checkbox', width: 20 }, + { + fid: 'name', + title: lan.site.name, + type: 'text', + }, + { + fid: 'dir', + title: 'Path', + type: 'text', + template: function (row) { + return '' + row.dir + ''; + }, + }, + { + fid: 'suffix', + title: 'Suffix', + template: function (row) { + return '' + row.suffix + ''; + }, + }, + { + title: lan.site.operate, + width: 110, + type: 'group', + align: 'right', + group: [ + { + title: lan.site.edit, + event: function (row, index, ev, key, that) { + site.edit.template_php(web.name, row); + }, + }, + { + title: lan.site.del, + event: function (row, index, ev, key, that) { + site.edit.del_php_deny(web.name, row.name, function (res) { + if (res.status) that.$delete_table_row(index); + bt.msg(res); + }); + }, + }, + ], + }, + ], + tootls: [ + { + // 按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Add deny access', + active: true, + event: function (ev) { + site.edit.template_php(web.name); + }, + }, + ], + }, + { + // 批量操作 + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' delete', + url: '/site?action=del_file_deny', + param: function (row) { + return { + website: web.name, + deny_name: row.name, + }; + }, + load: true, + callback: function (that) { + // 手动执行,data参数包含所有选中的站点 + bt.show_confirm('Delete deny access', 'Do you want to delete deny access?', function () { + that.start_batch({}, function (list) { + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i]; + html += + '' + + item.name + + '
                                ' + + item.request.msg + + '
                                '; + } + php_dirguard.$batch_success_table({ + title: 'Deny access', + th: 'Deny access name', + html: html, + }); + php_dirguard.$refresh_table_list(true); + }); + }); + }, + }, + }, + ], + }); + $('#dir_dirguard>.divtable,#php_dirguard>.divtable').css('max-height', '340px'); + $('#dir_dirguard').append( + "
                                  \ +
                                • After setting, you need to enter the password to access it.
                                • \ +
                                • For example, if I set the limit path /test/ , then I need to enter the account password to access http://aaa.com/test/
                                • \ +
                                " + ); + $('#php_dirguard').append( + "
                                  \ +
                                • Suffix: Indicates the suffix that is not allowed to access, if there are more than one, separate with'|'.
                                • \ +
                                • Path: Quote rules in this directory. e.g: /a/
                                • \ +
                                • For Example, if you want to deny http://test.com/a/index.php
                                • \ +
                                • Please fill in [ /a/ ]
                                • \ +
                                " + ); + $('#set_dirguard').on('click', '.tab-nav span', function () { + var index = $(this).index(); + $(this).addClass('on').siblings().removeClass('on'); + if (index == 0) { + $('#dir_dirguard').show(); + $('#php_dirguard').hide(); + } else { + $('#php_dirguard').show(); + $('#dir_dirguard').hide(); + } + }); + }, + ols_cache: function (web) { + bt.send('get_ols_static_cache', 'config/get_ols_static_cache', { id: web.id }, function (rdata) { + var clicks = [], + newkey = [], + newval = [], + checked = false; + Object.keys(rdata).forEach(function (key) { + //for (let key in rdata) { + newkey.push(key); + newval.push(rdata[key]); + }); + var datas = [ + { title: newkey[0], name: newkey[0], width: '30%', value: newval[0] }, + { title: newkey[1], name: newkey[1], width: '30%', value: newval[1] }, + { title: newkey[2], name: newkey[2], width: '30%', value: newval[2] }, + { title: newkey[3], name: newkey[3], width: '30%', value: newval[3] }, + { + name: 'static_save', + text: lan.site.save, + type: 'button', + callback: function (ldata) { + var cdata = {}, + loadT = bt.load(); + Object.assign(cdata, ldata); + delete cdata.static_save; + delete cdata.maxage; + delete cdata.exclude_file; + delete cdata.private_save; + bt.send( + 'set_ols_static_cache', + 'config/set_ols_static_cache', + { + values: JSON.stringify(cdata), + id: web.id, + }, + function (res) { + loadT.close(); + bt.msg(res); + } + ); + }, + }, + { title: 'test', name: 'test', width: '30%', value: '11' }, + { title: 'maxage', name: 'maxage', width: '30%', value: '43200' }, + { title: 'exclude file', name: 'exclude_file', width: '35%', value: 'fdas.php' }, + { + name: 'private_save', + text: lan.site.save, + type: 'button', + callback: function (ldata) { + var edata = {}, + loadT = bt.load(); + if (checked) { + edata.id = web.id; + edata.max_age = parseInt($("input[name='maxage']").val()); + edata.exclude_file = $("textarea[name='exclude_file']").val(); + bt.send('set_ols_private_cache', 'config/set_ols_private_cache', edata, function (res) { + loadT.close(); + bt.msg(res); + }); + } + }, + }, + ], + _html = $('
                                '); + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + _html.append(_form_data.html); + clicks = clicks.concat(_form_data.clicks); + } + $('#webedit-con').append(_html); + $("input[name='exclude_file']").parent().removeAttr('class').html(''); + $("input[name='test']") + .parent() + .parent() + .html( + '
                                private cache
                                ' + ); + var private = $("input[name='maxage'],textarea[name='exclude_file'],button[name='private_save']").parent().parent(); + $('input.bt-input-text').parent().append('sec'); + $("button[name='static_save']") + .parent() + .append(bt.render_help(['The default static file cache time is 604800 seconds', 'If you want to shut down, please change it to 0 seconds'])); + $('.ols').append(bt.render_help(['Private cache only supports page caching for PHP and cache time is 120 seconds by default', 'Exclude files only support files with PHP as the suffix'])); + private.hide(); + bt.send('get_ols_private_cache_status', 'config/get_ols_private_cache_status', { id: web.id }, function (kdata) { + checked = kdata; + if (kdata) { + bt.send('get_ols_private_cache', 'config/get_ols_private_cache', { id: web.id }, function (fdata) { + $("input[name='maxage']").val(fdata.maxage); + var ss = fdata.exclude_file.join(' '); + $("textarea[name='exclude_file']").html(ss); + $('#ols').attr('checked', true); + private.show(); + }); + } + }); + $('#ols').on('click', function () { + var loadT = bt.load(); + bt.send('switch_ols_private_cache', 'config/switch_ols_private_cache', { id: web.id }, function (res) { + loadT.close(); + private.toggle(); + checked = private.is(':hidden') ? false : true; + bt.msg(res); + if (checked) { + bt.send('get_ols_private_cache', 'config/get_ols_private_cache', { id: web.id }, function (fdata) { + private.show(); + $("input[name='maxage']").val(fdata.maxage); + $("textarea[name='exclude_file']").html(fdata.exclude_file.join(' ')); + }); + } + }); + }); + bt.render_clicks(clicks); + $("button[name='private_save']").parent().css('margin-bottom', '-13px'); + $('.ss-text').css('margin-left', '66px'); + $('.ols .btn-success').css('margin-left', '125px'); + }); + }, + limit_network: function (web) { + bt.site.get_limitnet(web.id, function (rdata) { + var limits = [ + { title: lan.site.bbs_or_blog, value: 1, items: { perserver: 300, perip: 25, limit_rate: 512 } }, + { title: lan.site.photo_station, value: 2, items: { perserver: 200, perip: 10, limit_rate: 1024 } }, + { title: lan.site.download_station, value: 3, items: { perserver: 50, perip: 3, limit_rate: 2048 } }, + { title: lan.site.mall, value: 4, items: { perserver: 500, perip: 10, limit_rate: 2048 } }, + { title: lan.site.portal_site, value: 5, items: { perserver: 400, perip: 15, limit_rate: 1024 } }, + { title: lan.site.enterprise, value: 6, items: { perserver: 60, perip: 10, limit_rate: 512 } }, + { title: lan.site.video, value: 7, items: { perserver: 150, perip: 4, limit_rate: 1024 } }, + ]; + var datas = [ + { + items: [ + { + name: 'status', + type: 'checkbox', + value: rdata.perserver != 0 ? true : false, + text: lan.site.limit_net_8, + callback: function (ldata) { + if (ldata.status) { + bt.site.set_limitnet(web.id, ldata.perserver, ldata.perip, ldata.limit_rate, function (ret) { + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + if (ret.status) site.reload(3); + }); + } else { + bt.site.close_limitnet(web.id, function (ret) { + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + if (ret.status) site.reload(3); + }); + } + }, + }, + ], + }, + { + title: lan.site.limit_net_9 + ' ', + width: '160px', + name: 'limit', + type: 'select', + items: limits, + callback: function (obj) { + var data = limits.filter(function (p) { + return p.value === parseInt(obj.val()); + })[0]; + for (var key in data.items) $('input[name="' + key + '"]').val(data.items[key]); + }, + }, + { + title: lan.site.limit_net_10 + ' ', + type: 'number', + width: '200px', + value: rdata.perserver, + name: 'perserver', + }, + { + title: lan.site.limit_net_12 + ' ', + type: 'number', + width: '200px', + value: rdata.perip, + name: 'perip', + }, + { + title: lan.site.limit_net_14 + ' ', + type: 'number', + width: '200px', + value: rdata.limit_rate, + name: 'limit_rate', + }, + { + name: 'btn_limit_get', + text: lan.site.save, + type: 'button', + callback: function (ldata) { + bt.site.set_limitnet(web.id, ldata.perserver, ldata.perip, ldata.limit_rate, function (ret) { + layer.msg(ret.msg, { icon: ret.status ? 1 : 2 }); + if (ret.status) site.reload(3); + }); + }, + }, + ]; + var _html = $("
                                "); + var clicks = []; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + _html.append(_form_data.html); + clicks = clicks.concat(_form_data.clicks); + } + _html.find('input[type="checkbox"]').parent().addClass('label-input-group ptb10'); + _html.append(bt.render_help([lan.site.limit_net_11, lan.site.limit_net_13, lan.site.limit_net_15])); + $('#webedit-con').append(_html); + $('.newnanme .tname').css('width', '138px'); + bt.render_clicks(clicks); + if (rdata.perserver == 0) $("select[name='limit']").trigger('change'); + $('#status,.btn_limit_get').css('margin-left', '138px'); + }); + }, + get_rewrite_list: function (web) { + var filename = '/www/server/panel/vhost/rewrite/' + web.name + '.conf'; + bt.site.get_rewrite_list(web.name, function (rdata) { + var arrs = [], + webserver = bt.get_cookie('serverType'); + if (webserver == 'apache' || webserver == 'openlitespeed') filename = rdata.sitePath + '/.htaccess'; + if (webserver == 'openlitespeed') webserver = 'apache'; + for (var i = 0; i < rdata.rewrite.length; i++) + arrs.push({ + title: rdata.rewrite[i], + value: rdata.rewrite[i], + }); + var datas = [ + { + name: 'rewrite', + type: 'select', + width: '130px', + items: arrs, + callback: function (obj) { + if (bt.os == 'Linux') { + var spath = filename; + if (obj.val() != lan.site.rewritename) spath = '/www/server/panel/rewrite/' + (webserver == 'openlitespeed' ? 'apache' : webserver) + '/' + obj.val() + '.conf'; + bt.files.get_file_body(spath, function (ret) { + if (ret.status == false) { + layer.msg(ret.msg, { icon: 2 }); + return false; + } + aceEditor.ACE.setValue(ret.data); + aceEditor.ACE.moveCursorTo(0, 0); + aceEditor.path = spath; + }); + } + }, + }, + { items: [{ name: 'config', type: 'div', value: rdata.data, widht: '340px', height: '200px' }] }, + { + items: [ + { + name: 'btn_save', + text: lan.site.save, + type: 'button', + callback: function (ldata) { + // bt.files.set_file_body(filename, editor.getValue(), 'utf-8', function(ret) { + // if (ret.status) site.reload(4) + // bt.msg(ret); + // }) + aceEditor.path = filename; + bt.saveEditor(aceEditor); + }, + }, + { + name: 'btn_save_to', + text: lan.site.save_as_template, + type: 'button', + callback: function (ldata) { + var temps = { + title: lan.site.save_rewrite_temp, + area: '330px', + list: [ + { + title: lan.site.template_name, + placeholder: lan.site.template_name, + width: '160px', + name: 'tempname', + }, + ], + btns: [ + { title: lan.site.turn_off, name: 'close' }, + { + title: lan.site.submit, + name: 'submit', + css: 'btn-success', + callback: function (rdata, load, callback) { + var name = rdata.tempname; + if (name === '') return layer.msg('The template name cannot be empty!', { icon: 2 }); + var isSameName = false; + for (var i = 0; i < arrs.length; i++) { + if (arrs[i].value == name) { + isSameName = true; + break; + } + } + var save_to = function () { + bt.site.set_rewrite_tel(name, aceEditor.ACE.getValue(), function (rRet) { + if (rRet.status) { + load.close(); + site.reload(4); + } + bt.msg(rRet); + }); + }; + if (isSameName) { + return layer.msg('The template name already exists, please re-enter the template name!', { icon: 2 }); + } else { + save_to(); + } + }, + }, + ], + }; + bt.render_form(temps); + }, + }, + ], + }, + ]; + var _html = $("
                                "); + var clicks = []; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + _html.append(_form_data.html); + var _other = + bt.os == 'Linux' && i == 0 + ? '' + lan.site.rewrite_change_tools + ':' + lan.site.ap_change_ng + '' + : ''; + _html.find('.info-r').append(_other); + clicks = clicks.concat(_form_data.clicks); + } + _html.append(bt.render_help([lan.site.rewrite_tips_1, lan.site.rewrite_tips_2, lan.site.edit_rewrite])); + $('#webedit-con').append(_html); + bt.render_clicks(clicks); + + // $('textarea.config').attr('id', 'config_rewrite'); + // var editor = CodeMirror.fromTextArea(document.getElementById("config_rewrite"), { + // extraKeys: { "Ctrl-Space": "autocomplete" }, + // lineNumbers: true, + // matchBrackets: true, + // }); + + // $(".CodeMirror-scroll").css({ "height": "340px", "margin": 0, "padding": 0 }); + // $(".soft-man-con .CodeMirror").css({ "height": "342px" }); + // setTimeout(function() { + // editor.refresh(); + // }, 250); + $('div.config').attr('id', 'config_rewrite').css({ height: '360px', width: '540px' }); + var aceEditor = bt.aceEditor({ el: 'config_rewrite', content: rdata.data }); + + $('select.rewrite').trigger('change'); + }); + }, + set_default_index: function (web) { + bt.site.get_index(web.id, function (rdata) { + rdata = rdata.replace(new RegExp(/(,)/g), '\n'); + var data = { + items: [ + { name: 'Dindex', height: '230px', width: '50%', type: 'textarea', value: rdata }, + { + name: 'btn_submit', + text: lan.site.add, + type: 'button', + callback: function (ddata) { + var Dindex = ddata.Dindex.replace(new RegExp(/(\n)/g), ','); + bt.site.set_index(web.id, Dindex, function (ret) { + if (!ret.status) { + bt.msg(ret); + return; + } + + site.reload(5); + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(data); + var _html = $(_form_data.html); + _html.append(bt.render_help([lan.site.default_doc_help])); + $('#webedit-con').append(_html); + $('.btn_submit').addClass('pull-right').css('margin', '90px 100px 0 0'); + bt.render_clicks(_form_data.clicks); + }); + }, + set_config: function (web) { + var con = + '

                                Tips:Ctrl+F Search keywords,Ctrl+S Save,Ctrl+H Search and replace

                                \ + \ +
                                  \ +
                                • This is primary configuration file of the site.
                                • \ +
                                • Do not modify it at will if you do not know configuration rules.
                                • \ +
                                '; + $('#webedit-con').html(con); + var webserve = bt.get_cookie('serverType'), + config = bt.aceEditor({ + el: 'siteConfigBody', + path: '/www/server/panel/vhost/' + (webserve == 'openlitespeed' ? webserve + '/detail' : webserve) + '/' + web.name + '.conf', + }); + $('#OnlineEditFileBtn').click(function (e) { + bt.saveEditor(config); + }); + }, + set_php_version: function (web) { + bt.site.get_site_phpversion(web.name, function (sdata) { + if (sdata.status === false) { + bt.msg(sdata); + return; + } + bt.site.get_all_phpversion(function (vdata) { + var versions = []; + for (var j = vdata.length - 1; j >= 0; j--) { + var o = vdata[j]; + o.value = o.version; + o.title = o.name; + versions.push(o); + } + + // var data = { + // items: [ + // { + // title: 'PHP版本', + // name: 'versions', + // value: sdata.phpversion, + // type: 'select', + // items: versions , + // ps:'' + // }, + // { + // text: '切换', + // name: 'btn_change_phpversion', + // type: 'button', + // callback: function(pdata) { + // var other = $('.other-version').val(); + // if(pdata.versions == 'other' && other == ''){ + // layer.msg('自定义PHP版本时,PHP连接配置不能为空'); + // $('.other-version').focus(); + // return; + // } + // bt.site.set_phpversion(web.name, pdata.versions, other, function(ret) { + // if (ret.status) { + // var versions = $('[name="versions"]').val(); + // versions = versions.slice(0, versions.length - 1) + '.' + versions.slice(-1); + // if (versions == '0.0') versions = '静态'; + // site_table.$refresh_table_list(true); + // site.reload() + // setTimeout(function() { + // bt.msg(ret); + // }, 1000); + // }else{ + // bt.msg(ret); + // } + + // }) + // } + // } + // ] + // } + // var _form_data = bt.render_form_line(data); + // var _html = $(_form_data.html); + // _html.append(bt.render_help([lan.site.switch_php_help1, lan.site.switch_php_help2, lan.site.switch_php_help3])); + // $('#webedit-con').append(_html); + // bt.render_clicks(_form_data.clicks); + // $('#webedit-con').append('
                                ' + lan.site.session_off + '
                                ' + bt.render_help([lan.site.independent_storage])); + + // function get_session_status() { + // var loading = bt.load('Getting session status...'); + // bt.send('get_php_session_path', 'config/get_php_session_path', { id: web.id }, function(tdata) { + // loading.close(); + // $('#session_switch').prop("checked", tdata); + // }) + // }; + // get_session_status() + // $('#session_switch').click(function() { + // var val = $(this).prop('checked'); + // bt.send('set_php_session_path', 'config/set_php_session_path', { id: web.id, act: val ? 1 : 0 }, function(rdata) { + // get_session_status(); + // bt.msg(rdata) + // }); + // }) + + var data = { + items: [ + { + title: 'PHP version', + name: 'versions', + value: sdata.phpversion, + type: 'select', + items: versions, + ps: + '', + }, + { + text: 'Switch', + name: 'btn_change_phpversion', + type: 'button', + callback: function (pdata) { + var other = $('.other-version').val(); + if (pdata.versions == 'other' && other == '') { + layer.msg('When customizing the PHP version, the PHP connection configuration cannot be empty'); + $('.other-version').focus(); + return; + } + bt.site.set_phpversion(web.name, pdata.versions, other, function (ret) { + if (ret.status) { + var versions = $('[name="versions"]').val(); + versions = versions.slice(0, versions.length - 1) + '.' + versions.slice(-1); + if (versions == '0.0') versions = 'Static'; + site_table.$refresh_table_list(true); + site.reload(); + setTimeout(function () { + bt.msg(ret); + }, 1000); + } else { + bt.msg(ret); + } + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(data); + var _html = $(_form_data.html); + _html.append( + bt.render_help([ + 'Select the version according to your program requirements', + 'Try not to use PHP5.2 unless you have to, as this can reduce your server security', + 'PHP7 does not support the MySQL extension. The default installation is mysqli and mysql-pdo', + '[Customize] You can customize the PHP connection information by selecting the available PHP connection configuration', + '[Customize] Currently only support NGINX', + 'Support TCP or UNIX configuration. Example: 192.168.1.25:9001 or unix:/tmp/php8.sock', + ]) + ); + $('#webedit-con').append(_html); + bt.render_clicks(_form_data.clicks); + if (sdata.phpversion != 'other') { + var tips = bt.render_help([ + 'When enabled, session files will be stored in a separate folder, not in a common storage location with other sites', + 'Do not enable this option if you are saving sessions to caches such as memcache/redis in your PHP configuration', + ]); + + $('#webedit-con').append( + '\ +
                                \ + ' + + lan.site.session_off + + '\ + \ + \ + \ + \ +
                                \ +
                                ' + + tips + ); + } + if (sdata.phpversion != 'other') { + $('.other-version').hide(); + } + setTimeout(function () { + $('select[name="versions"]').change(function () { + var phpversion = $(this).val(); + // console.log(phpversion); + if (phpversion == 'other') { + $('.other-version').show(); + } else { + $('.other-version').hide(); + } + }); + }, 500); + + function get_session_status() { + var loading = bt.load('Please wait while getting session status'); + bt.send('get_php_session_path', 'config/get_php_session_path', { id: web.id }, function (tdata) { + loading.close(); + $('#session_switch').prop('checked', tdata); + }); + } + get_session_status(); + $('#session_switch').click(function () { + var val = $(this).prop('checked'); + bt.send( + 'set_php_session_path', + 'config/set_php_session_path', + { + id: web.id, + act: val ? 1 : 0, + }, + function (rdata) { + bt.msg(rdata); + } + ); + setTimeout(function () { + get_session_status(); + }, 500); + }); + }); + }); + }, + set_wp_config: function (web) { + var loadup = bt.load('Getting Wordpress information, please wait...'); + bt.send('is_update', 'site/is_update', { s_id: web.id }, function (rdata) { + loadup.close(); + var loadin = bt.load('Getting wordpress account information, please wait...'); + bt.send('get_wp_username', 'site/get_wp_username', { s_id: web.id }, function (wlist) { + loadin.close(); + var robj = $('#webedit-con'); + + if (wlist.status === false) { + wlist.time = 0; + wlist.closeBtn = 2; + bt.msg(wlist); + var data = { + items: [ + { + title: 'Database name', + name: 'database', + value: '', + type: 'input', + width: '250px', + placeholder: 'Please enter the database name', + }, + { + text: 'Set', + name: 'btn_change_database', + type: 'button', + callback: function (pdata) { + if (pdata.database == '') { + return layer.msg('The database name cannot be empty', { icon: 2 }); + } + var param = { + site_id: web.id, + db_name: pdata.database, + }; + var load = bt.load('Setting Database name, please wait...'); + bt.send('reset_wp_db', 'site/reset_wp_db', param, function (res) { + load.close(); + bt.msg(res); + if (res.status) { + setTimeout(function () { + robj.html(''); + site.edit.set_wp_config(web); + }, 1500); + } + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(data); + var _html = $(_form_data.html); + _html.append(bt.render_help(['Please enter the database name for this wordpress website'])); + robj.append(_html); + bt.render_clicks(_form_data.clicks); + return; + } + + var _html = $('
                                '), + user_array = [], + clicks = []; + + $.each(wlist.msg, function (index, item) { + user_array.push({ title: item, value: item }); + }); + var datas = [ + { + title: 'WP Version', + items: [ + { + name: 'wp_version', + type: 'html', + html: rdata['msg']['update'] + ? 'The current version is: ' + + rdata['msg']['local_v'] + + '' + : 'The latest version', + }, + ], + }, + { + title: 'Cache', + items: [ + { + type: 'checkbox', + name: 'cache_switch', + text: ' Open cache', + value: web.cache_status, + callback: function (sdata) { + var loads = bt.load((sdata.cache_switch ? 'Turning on' : 'Turining off') + ' [ ' + web.name + ' ] cache, please wait...'); + bt.send('set_fastcgi_cache', 'site/set_fastcgi_cache', { version: web.php_version, sitename: web.name, act: sdata.cache_switch ? 'enable' : 'disable' }, function (res) { + loads.close(); + bt.msg(res); + if (res.status) { + site.php_table_view(); + web.cache_status = sdata.cache_switch; + } + }); + }, + }, + { + name: 'remove_cache', + text: 'Purge all cache', + type: 'button', + callback: function (sdata) { + var loadC = bt.load('Clearing all caches, please wait...'); + bt.send('purge_all_cache', 'site/purge_all_cache', { s_id: web.id }, function (res) { + loadC.close(); + bt.msg(res); + }); + }, + }, + ], + }, + { + title: 'Reset password', + items: [ + { + name: 'user', + type: 'select', + items: user_array, + width: '200px', + }, + { + title: '', + name: 'new_pass', + placeholder: 'Please enter a new password', + width: '200px', + }, + { + name: 'submit_pw', + text: 'Save password', + type: 'button', + callback: function (sdata) { + var loads = bt.load('Resetting password, please wait...'); + bt.send('reset_wp_password', 'site/reset_wp_password', { s_id: web.id, user: sdata.user, new_pass: sdata.new_pass }, function (res) { + loads.close(); + bt.msg(res); + }); + }, + }, + ], + }, + ]; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + _html.append(_form_data.html); + clicks = clicks.concat(_form_data.clicks); + } + _html.find('input[type="checkbox"]').parent().addClass('label-input-group'); + _html.find('button[name="submit_pw"]').css('margin', '15px 0'); + robj.append(_html); + bt.render_clicks(clicks); + + //wp版本更新 + $('.update_wp_version').click(function () { + var load_wp = bt.load('Updating Wordpress version, please wait...'); + bt.send('update_wp', 'site/update_wp', { s_id: web.id, version: rdata['msg']['online_v'] }, function (res) { + load_wp.close(); + bt.msg(res); + if (res.status) $('.bt-w-menu.site-menu p.bgw').click(); + }); + }); + }); + }); + }, + templet_301: function (sitename, id, types, obj) { + if (types) { + obj = { + redirectname: new Date().valueOf(), + tourl: 'http://', + redirectdomain: [], + redirectpath: '', + redirecttype: '', + type: 1, + domainorpath: 'domain', + holdpath: 1, + }; + } + var helps = [lan.site.redirect_tips1, lan.site.redirect_tips2, lan.site.redirect_tips3, lan.site.redirect_tips4, lan.site.redirect_tips5, lan.site.redirect_tips6]; + bt.site.get_domains(id, function (rdata) { + var domain_html = ''; + for (var i = 0; i < rdata.length; i++) { + domain_html += ''; + } + var form_redirect = bt.open({ + type: 1, + skin: 'demo-class', + area: '650px', + title: types ? lan.site.create_redirect : lan.site.modify_redirect + '[' + obj.redirectname + ']', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "" + + "
                                " + + "" + + lan.site.open_redirect + + '' + + "
                                " + + "" + + "
                                " + + "" + + lan.site.reserve_url + + '' + + "" + + '
                                ' + + '
                                ' + + '
                                ' + + "' + + "
                                " + + "" + + lan.site.redirect_type + + '' + + "
                                " + + "' + + "" + + lan.site.redirect_mode + + '' + + "
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.redirect_domain + + '' + + "
                                " + + "' + + '
                                ' + + "" + + lan.site.target_url + + '' + + "
                                " + + "" + + '
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.redirect_path + + '' + + "
                                " + + "" + + "" + + lan.site.target_url + + '' + + "" + + '
                                ' + + '
                                ' + + "
                                  " + + bt.render_help(helps) + + '
                                ' + + "
                                ' + + '', + }); + setTimeout(function () { + $('.selectpicker').selectpicker({ + noneSelectedText: lan.site.choose_domain, + selectAllText: lan.site.choose_all, + deselectAllText: lan.site.cancel_all, + }); + $('.selectpicker').selectpicker('val', obj.redirectdomain); + $('#form_redirect').parent().css('overflow', 'inherit'); + $('[name="domainorpath"]').change(function () { + if ($(this).val() == 'path') { + $('.redirectpath').show(); + $('.redirectdomain').hide(); + $('.selectpicker').selectpicker('val', []); + } else { + $('.redirectpath').hide(); + $('.redirectdomain').show(); + $('[name="redirectpath"]').val(''); + } + }); + $('.btn-colse-prosy').click(function () { + form_redirect.close(); + }); + $('.btn-submit-redirect').click(function () { + var type = $('[name="type"]').prop('checked') ? 1 : 0; + var holdpath = $('[name="holdpath"]').prop('checked') ? 1 : 0; + var redirectname = $('[name="redirectname"]').val(); + var redirecttype = $('[name="redirecttype"]').val(); + var domainorpath = $('[name="domainorpath"]').val(); + var redirectpath = $('[name="redirectpath"]').val(); + var redirectdomain = JSON.stringify($('.selectpicker').val() || []); + var tourl = $(domainorpath == 'path' ? '[name="tourl1"]' : '[name="tourl"]').val(); + if (!types) { + bt.site.modify_redirect( + { + type: type, + sitename: sitename, + holdpath: holdpath, + redirectname: redirectname, + redirecttype: redirecttype, + domainorpath: domainorpath, + redirectpath: redirectpath, + redirectdomain: redirectdomain, + tourl: tourl, + }, + function (rdata) { + if (rdata.status) { + form_redirect.close(); + site.reload(11); + } + bt.msg(rdata); + } + ); + } else { + bt.site.create_redirect( + { + type: type, + sitename: sitename, + holdpath: holdpath, + redirectname: redirectname, + redirecttype: redirecttype, + domainorpath: domainorpath, + redirectpath: redirectpath, + redirectdomain: redirectdomain, + tourl: tourl, + }, + function (rdata) { + if (rdata.status) { + form_redirect.close(); + site.reload(11); + } + bt.msg(rdata); + } + ); + } + }); + }, 100); + }); + }, + template_Dir: function (id, type, obj) { + if (type) { + obj = { name: '', sitedir: '', username: '', password: '' }; + } else { + obj = { name: obj.name, sitedir: obj.site_dir, username: '', password: '' }; + } + var form_directory = bt.open({ + type: 1, + skin: 'demo-class', + area: '475px', + title: type ? 'Add limit access' : 'Edit limit access', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
                                " + + "
                                " + + "" + + lan.bt.task_name + + '' + + "
                                " + + '
                                ' + + "
                                " + + "Path" + + "
                                " + + '
                                ' + + "
                                " + + "" + + lan.bt.panel_user + + '' + + "
                                " + + '
                                ' + + "
                                " + + "" + + lan.bt.panel_pass + + '' + + "
                                " + + '
                                ' + + "
                                  " + + '
                                • After the path is protected, you need to enter the account password to access it.
                                • ' + + '
                                • For example, if I set the protection directory /test/ , then I need to enter the account password to access http://aaa.com/test/
                                • ' + + '
                                ' + + "
                                ', + }); + $('.btn-colse-guard').click(function () { + form_directory.close(); + }); + $('.btn-submit-guard').click(function () { + var guardData = {}; + guardData['id'] = id; + guardData['name'] = $('input[name="dir_name"]').val(); + guardData['site_dir'] = $('input[name="dir_sitedir"]').val(); + guardData['username'] = $('input[name="dir_username"]').val(); + guardData['password'] = $('input[name="dir_password"]').val(); + if (type) { + bt.site.create_dir_guard(guardData, function (rdata) { + if (rdata.status) { + form_directory.close(); + site.reload(); + } + bt.msg(rdata); + }); + } else { + bt.site.edit_dir_account(guardData, function (rdata) { + if (rdata.status) { + form_directory.close(); + site.reload(); + } + bt.msg(rdata); + }); + } + }); + setTimeout(function () { + if (!type) { + $('input[name="dir_name"]').attr('disabled', 'disabled'); + $('input[name="dir_sitedir"]').attr('disabled', 'disabled'); + } + }, 500); + }, + template_php: function (website, obj) { + var _type = 'add', + _name = '', + _bggrey = ''; + if (obj == undefined) { + obj = { name: '', suffix: 'php|jsp', dir: '' }; + } else { + obj = { name: obj.name, suffix: obj.suffix, dir: obj.dir }; + _type = 'edit'; + _name = ' readonly'; + _bggrey = 'background: #eee;'; + } + var form_directory = bt.open({ + type: 1, + area: '440px', + title: 'Deny access', + closeBtn: 2, + btn: ['Save', 'Cancel'], + content: + "
                                " + + "
                                " + + "Name" + + "
                                " + + '
                                ' + + "
                                " + + "Suffix" + + "
                                " + + '
                                ' + + "
                                " + + "Path" + + "
                                " + + '
                                ' + + "
                                  " + + '
                                • Name:The rule name.
                                • ' + + "
                                • Suffix: Indicates the suffix that is not allowed to access, if there are more than one, separate with'|'
                                • " + + '
                                • Path: Quote rules in this directory. e.g: /a/
                                • ' + + '
                                • For Example, if you want to deny http://test.com/a/index.php
                                • ' + + '
                                • Please fill in [ /a/ ]' + + '
                                ', + yes: function () { + var dent_data = $('.php_deny').serializeObject(); + dent_data.act = _type; + dent_data.website = website; + var loading = bt.load(); + bt.site.edit_php_deny(dent_data, function (rdata) { + loading.close(); + if (rdata.status) { + form_directory.close(); + site.reload(); + $('#set_dirguard .tab-nav span:eq(1)').click(); + } + bt.msg(rdata); + }); + }, + }); + }, + del_php_deny: function (website, deny_name, callback) { + layer.confirm( + 'Are you sure to delete [ ' + deny_name + ' ] this deny?', + { + icon: 0, + closeBtn: 2, + title: 'Delete deny', + }, + function (index) { + bt.site.del_php_deny({ website: website, deny_name: deny_name }, function (rdata) { + layer.close(index); + if (callback) callback(rdata); + }); + } + ); + }, + set_301_old: function (web) { + bt.site.get_domains(web.id, function (rdata) { + var domains = [{ title: lan.site.site, value: 'all' }]; + for (var i = 0; i < rdata.length; i++) domains.push({ title: rdata[i].name, value: rdata[i].name }); + + bt.site.get_site_301(web.name, function (pdata) { + var _val = pdata.src == '' ? 'all' : pdata.src; + var datas = [ + { + title: lan.site.access_domain, + width: '360px', + name: 'domains', + value: _val, + disabled: pdata.status, + type: 'select', + items: domains, + }, + { title: lan.site.target_url, width: '360px', name: 'toUrl', value: pdata.url }, + { + title: ' ', + text: lan.site.enable_301, + value: pdata.status, + name: 'status', + class: 'label-input-group', + type: 'checkbox', + callback: function (sdata) { + bt.site.set_site_301(web.name, sdata.domains, sdata.toUrl, sdata.status ? '1' : '0', function (ret) { + if (ret.status) site.reload(10); + bt.msg(ret); + }); + }, + }, + ]; + var robj = $('#webedit-con'); + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + robj.append(bt.render_help([lan.site.to301_help_1, lan.site.to301_help_2])); + }); + }); + }, + set_301: function (web) { + $('#webedit-con').html('
                                '); + bt_tools.table({ + el: '#redirect_list', + url: '/site?action=GetRedirectList', + param: { sitename: web.name }, + dataFilter: function (res) { + return { data: res }; + }, + column: [ + { type: 'checkbox', width: 20 }, + { + fid: 'sitename', + title: lan.site.redirect_type, + type: 'text', + template: function (row) { + if (row.domainorpath == 'path') { + conter = row.redirectpath; + } else { + conter = row.redirectdomain ? row.redirectdomain.join('、') : lan.site.empty; + } + return '' + conter + ''; + }, + }, + { fid: 'redirecttype', title: lan.site.redirect_mode, type: 'text' }, + { + fid: 'holdpath', + title: lan.site.reserve_url, + config: { + icon: false, + list: [ + [1, lan.site.turn_on, 'bt_success'], + [0, lan.site.turn_off, 'bt_danger'], + ], + }, + type: 'status', + event: function (row, index, ev, key, that) { + row.holdpath = row.holdpath == 0 ? 1 : 0; + row.redirectdomain = JSON.stringify(row['redirectdomain']); + bt.site.modify_redirect(row, function (res) { + row.redirectdomain = JSON.parse(row['redirectdomain']); + that.$modify_row_data({ holdpath: row.holdpath }); + bt.msg(res); + }); + }, + }, + { + fid: 'type', + title: lan.site.status, + config: { + icon: true, + list: [ + [1, lan.site.running_text, 'bt_success', 'glyphicon-play'], + [0, lan.site.already_stop, 'bt_danger', 'glyphicon-pause'], + ], + }, + type: 'status', + event: function (row, index, ev, key, that) { + row.type = row.type == 0 ? 1 : 0; + row.redirectdomain = JSON.stringify(row['redirectdomain']); + bt.site.modify_redirect(row, function (res) { + row.redirectdomain = JSON.parse(row['redirectdomain']); + that.$modify_row_data({ status: row.type }); + bt.msg(res); + }); + }, + }, + { + title: lan.site.operate, + width: 129, + type: 'group', + align: 'right', + group: [ + { + title: 'Conf', + event: function (row, index, ev, key, that) { + bt.site.get_redirect_config( + { + sitename: web.name, + redirectname: row.redirectname, + webserver: bt.get_cookie('serverType'), + }, + function (rdata) { + if (typeof rdata == 'object' && rdata.constructor == Array) { + if (!rdata[0].status) bt.msg(rdata); + } else { + if (!rdata.status) bt.msg(rdata); + } + var datas = [ + { + items: [ + { + name: 'redirect_configs', + type: 'textarea', + value: rdata[0].data, + widht: '340px', + height: '200px', + }, + ], + }, + { + name: 'btn_config_submit', + text: 'Save', + type: 'button', + callback: function (ddata) { + bt.site.save_redirect_config( + { + path: rdata[1], + data: editor.getValue(), + encoding: rdata[0].encoding, + }, + function (ret) { + if (ret.status) { + site.reload(11); + redirect_config.close(); + } + bt.msg(ret); + } + ); + }, + }, + ]; + redirect_config = bt.open({ + type: 1, + area: ['550px', '550px'], + title: 'Edit profile [' + row.redirectname + ']', + closeBtn: 2, + shift: 0, + content: "
                                ", + }); + var robj = $('#redirect_config_con'); + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + robj.append(bt.render_help(['This is the configuration file of the load balancing. Not modify if you do not understand the configuration rules.'])); + $('textarea.redirect_configs').attr('id', 'configBody'); + var editor = CodeMirror.fromTextArea(document.getElementById('configBody'), { + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + lineNumbers: true, + matchBrackets: true, + }); + $('.CodeMirror-scroll').css({ height: '350px', margin: 0, padding: 0 }); + setTimeout(function () { + editor.refresh(); + }, 250); + } + ); + }, + }, + { + title: lan.site.edit, + event: function (row, index, ev, key, that) { + site.edit.templet_301(web.name, web.id, false, row); + }, + }, + { + title: lan.site.del, + event: function (row, index, ev, key, that) { + bt.site.remove_redirect(web.name, row.redirectname, function (rdata) { + if (rdata.status) that.$delete_table_row(index); + }); + }, + }, + ], + }, + ], + tootls: [ + { + //按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Add redirection', + active: true, + event: function (ev) { + site.edit.templet_301(web.name, web.id, true); + }, + }, + ], + }, + { + //批量操作 + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' delete', + url: '/site?action=del_redirect_multiple', + param: { site_id: web.id }, + paramId: 'redirectname', + paramName: 'redirectnames', + theadName: 'Name', + confirmVerify: false, // 是否提示验证方式 + }, + }, + ], + }); + }, + templet_proxy: function (sitename, type, obj) { + if (type) { + obj = { + type: 1, + cache: 0, + proxyname: '', + proxydir: '/', + proxysite: 'http://', + cachetime: 1, + todomain: '$host', + subfilter: [{ sub1: '', sub2: '' }], + }; + } + var sub_conter = ''; + for (var i = 0; i < obj.subfilter.length; i++) { + if (i == 0 || obj.subfilter[i]['sub1'] != '') { + sub_conter += + "
                                " + + "" + + "" + + "Del" + + '
                                '; + } + if (i == 2) $('.add-replace-prosy').attr('disabled', 'disabled'); + } + var helps = [lan.site.proxy_tips1, lan.site.proxy_tips2, lan.site.proxy_tips3, lan.site.proxy_tips4]; + var form_proxy = bt.open({ + type: 1, + skin: 'demo-class', + area: '650px', + title: type ? lan.site.create_proxy : lan.site.modify_proxy + '[' + obj.proxyname + ']', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + "
                                " + + "
                                " + + "" + + lan.site.open_proxy + + '' + + "
                                " + + "" + + "
                                " + + "" + + lan.site.proxy_cache + + '' + + "" + + '
                                ' + + "
                                " + + "" + + lan.site.proxy_adv + + '' + + "" + + '
                                ' + + '
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.proxy_name + + '' + + "
                                " + + '
                                ' + + "
                                " + + "" + + lan.site.cache_time + + '' + + "
                                " + + lan.site.minute + + '
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.proxy_dir + + '' + + "
                                " + + '
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.target_url + + '' + + "
                                " + + "" + + '
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.proxy_domain + + '' + + "
                                " + + "" + + '
                                ' + + '
                                ' + + "
                                " + + "" + + lan.site.con_rep + + '' + + "
                                " + + sub_conter + + '
                                ' + + '
                                ' + + "
                                " + + "
                                " + + "' + + '
                                ' + + '
                                ' + + "
                                  " + + bt.render_help(helps) + + "
                                  ' + + '', + }); + bt.set_cookie('form_proxy', form_proxy); + $('.add-replace-prosy').click(function () { + var length = $('.replace_conter .sub-groud').length; + if (length == 2) $(this).attr('disabled', 'disabled'); + var conter = + "
                                  " + + "" + + "" + + "" + + lan.site.del + + '' + + '
                                  '; + $('.replace_conter .info-r').append(conter); + }); + $('[name="proxysite"]').keyup(function () { + var val = $(this).val(), + ip_reg = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/; + val = val.replace(/^http[s]?:\/\//, ''); + // val = val.replace(/:([0-9]*)$/, ''); + val = val.replace(/(:|\?|\/|\\)(.*)$/, ''); + if (ip_reg.test(val)) { + $("[name='todomain']").val('$host'); + } else { + $("[name='todomain']").val(val); + } + }); + $('#openAdvanced').click(function () { + if ($(this).prop('checked')) { + $('.advanced').show(); + } else { + $('.advanced').hide(); + } + }); + $('#openNginx').click(function () { + if ($(this).prop('checked')) { + $('.cachetime').show(); + } else { + $('.cachetime').hide(); + } + }); + $('.btn-colse-prosy').click(function () { + form_proxy.close(); + }); + $('.replace_conter').on('click', '.proxy_del_sub', function () { + $(this).parent().remove(); + $('.add-replace-prosy').removeAttr('disabled'); + }); + $('.btn-submit-prosy').click(function () { + var form_proxy_data = {}; + $.each($('#form_proxy').serializeArray(), function () { + if (form_proxy_data[this.name]) { + if (!form_proxy_data[this.name].push) { + form_proxy_data[this.name] = [form_proxy_data[this.name]]; + } + form_proxy_data[this.name].push(this.value || ''); + } else { + form_proxy_data[this.name] = this.value || ''; + } + }); + form_proxy_data['type'] = form_proxy_data['type'] == undefined ? 0 : 1; + form_proxy_data['cache'] = form_proxy_data['cache'] == undefined ? 0 : 1; + form_proxy_data['advanced'] = form_proxy_data['advanced'] == undefined ? 0 : 1; + form_proxy_data['sitename'] = sitename; + form_proxy_data['subfilter'] = JSON.stringify([ + { sub1: form_proxy_data['rep1'] || '', sub2: form_proxy_data['rep2'] || '' }, + { sub1: form_proxy_data['rep3'] || '', sub2: form_proxy_data['rep4'] || '' }, + { sub1: form_proxy_data['rep5'] || '', sub2: form_proxy_data['rep6'] || '' }, + ]); + for (var i in form_proxy_data) { + if (i.indexOf('rep') != -1) { + delete form_proxy_data[i]; + } + } + if (type) { + bt.site.create_proxy(form_proxy_data, function (rdata) { + if (rdata.status) { + form_proxy.close(); + site.reload(12); + } + bt.msg(rdata); + }); + } else { + bt.site.modify_proxy(form_proxy_data, function (rdata) { + if (rdata.status) { + form_proxy.close(); + site.reload(12); + } + bt.msg(rdata); + }); + } + }); + }, + set_proxy: function (web) { + var limit_len = bt.get_cookie('serverType') == 'nginx' ? 'proxy_list_limit_4' : 'proxy_list_limit_3'; + $('#webedit-con').html('
                                  '); + String.prototype.myReplace = function (f, e) { + //吧f替换成e + var reg = new RegExp(f, 'g'); //创建正则RegExp对象 + return this.replace(reg, e); + }; + bt_tools.table({ + el: '#proxy_list', + url: '/site?action=GetProxyList', + param: { sitename: web.name }, + dataFilter: function (res) { + return { data: res }; + }, + column: [ + { type: 'checkbox', width: 20 }, + { + fid: 'proxyname', + title: lan.site.name, + template: function (row, index) { + return '' + row.proxyname + ''; + }, + }, + { + fid: 'proxydir', + title: lan.site.proxy_dir, + template: function (row, index) { + return '' + row.proxydir + ''; + }, + }, + { fid: 'proxysite', title: lan.site.target_url, type: 'link', href: true }, + bt.get_cookie('serverType') == 'nginx' + ? { + fid: 'cache', + title: lan.site.cache, + config: { + icon: false, + list: [ + [1, lan.site.already_open, 'bt_success'], + [0, lan.site.already_close, 'bt_danger'], + ], + }, + type: 'status', + event: function (row, index, ev, key, that) { + row['cache'] = !row['cache'] ? 1 : 0; + row['subfilter'] = JSON.stringify(row['subfilter']); + bt.site.modify_proxy(row, function (rdata) { + row['subfilter'] = JSON.parse(row['subfilter']); + if (rdata.status) that.$modify_row_data({ cache: row['cache'] }); + bt.msg(rdata); + }); + }, + } + : {}, + { + fid: 'type', + title: lan.site.status, + config: { + icon: true, + list: [ + [1, lan.site.running_text, 'bt_success', 'glyphicon-play'], + [0, lan.site.already_stop, 'bt_danger', 'glyphicon-pause'], + ], + }, + type: 'status', + event: function (row, index, ev, key, that) { + row['type'] = !row['type'] ? 1 : 0; + row['subfilter'] = JSON.stringify(row['subfilter']); + bt.site.modify_proxy(row, function (rdata) { + row['subfilter'] = JSON.parse(row['subfilter']); + if (rdata.status) that.$modify_row_data({ type: row['type'] }); + bt.msg(rdata); + }); + }, + }, + { + title: lan.site.operate, + width: 115, + type: 'group', + align: 'right', + group: [ + { + title: 'Conf', + event: function (row, index, ev, key, that) { + bt.site.get_proxy_config( + { + sitename: web.name, + proxyname: row.proxyname, + webserver: bt.get_cookie('serverType'), + }, + function (rdata) { + if (typeof rdata == 'object' && rdata.constructor == Array) { + if (!rdata[0].status) bt.msg(rdata); + } else { + if (!rdata.status) bt.msg(rdata); + } + var datas = [ + { + items: [ + { + name: 'proxy_configs', + type: 'textarea', + value: rdata[0].data, + widht: '340px', + height: '200px', + }, + ], + }, + { + name: 'btn_config_submit', + text: 'Save', + type: 'button', + callback: function (ddata) { + bt.site.save_proxy_config( + { + path: rdata[1], + data: editor.getValue(), + encoding: rdata[0].encoding, + }, + function (ret) { + if (ret.status) { + site.reload(12); + proxy_config.close(); + } + bt.msg(ret); + } + ); + }, + }, + ]; + proxy_config = bt.open({ + type: 1, + area: ['550px', '550px'], + title: 'Edit profile [' + row.proxyname + ']', + closeBtn: 2, + shift: 0, + content: "
                                  ", + }); + var robj = $('#proxy_config_con'); + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + robj.append(bt.render_help(['This is the configuration file of the load balancing. Not modify if you do not understand the configuration rules.'])); + $('textarea.proxy_configs').attr('id', 'configBody'); + var editor = CodeMirror.fromTextArea(document.getElementById('configBody'), { + extraKeys: { 'Ctrl-Space': 'autocomplete' }, + lineNumbers: true, + matchBrackets: true, + }); + $('.CodeMirror-scroll').css({ height: '350px', margin: 0, padding: 0 }); + setTimeout(function () { + editor.refresh(); + }, 250); + } + ); + }, + }, + { + title: 'Edit', + event: function (row, index, ev, key, that) { + site.edit.templet_proxy(web.name, false, row); + }, + }, + { + title: 'Del', + event: function (row, index, ev, key, that) { + bt.site.remove_proxy(web.name, row.proxyname, function (rdata) { + if (rdata.status) that.$delete_table_row(index); + }); + }, + }, + ], + }, + ], + tootls: [ + { + //按钮组 + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: 'Add reverse proxy', + active: true, + event: function (ev) { + site.edit.templet_proxy(web.name, true); + }, + }, + ], + }, + { + //批量操作 + type: 'batch', + positon: ['left', 'bottom'], + config: { + title: ' delete', + url: '/site?action=del_proxy_multiple', + param: { site_id: web.id }, + paramId: 'proxyname', + paramName: 'proxynames', + theadName: 'Name', + confirmVerify: false, // 是否提示验证方式 + }, + }, + ], + }); + }, + set_security: function (web) { + bt.site.get_site_security(web.id, web.name, function (rdata) { + var robj = $('#webedit-con'); + var datas = [ + { + title: lan.site.url_suffix, + name: 'sec_fix', + value: rdata.fix, + disabled: rdata.status, + width: '300px', + }, + { + title: lan.site.access_domain1, + items: [ + { + text: lan.site.start_anti_leech, + name: 'sec_domains', + width: '300px', + height: '210px', + disabled: rdata.status, + value: rdata.domains.replace(/,/g, '\n'), + type: 'textarea', + }, + ], + }, + { + title: 'Response', + name: 'return_rule', + value: rdata.return_rule, + disabled: rdata.status, + width: '300px', + }, + { + title: ' ', + class: 'label-input-group', + items: [ + { + text: lan.site.start_anti_leech, + name: 'status', + value: rdata.status, + type: 'checkbox', + callback: function (sdata) { + bt.site.set_site_security(web.id, web.name, sdata.sec_fix, sdata.sec_domains.split('\n').join(','), sdata.status, sdata.return_rule, function (ret) { + if (ret.status) site.reload(13); + bt.msg(ret); + }); + }, + }, + { + text: 'Allow empty HTTP_REFERER requests', + name: 'none', + value: rdata.none, + type: 'checkbox', + callback: function (sdata) { + bt.site.set_site_security(web.id, web.name, sdata.sec_fix, sdata.sec_domains.split('\n').join(','), '1', sdata.return_rule, function (ret) { + if (ret.status) site.reload(13); + bt.msg(ret); + }); + }, + }, + ], + }, + ]; + + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + robj.find('#none').css('margin-top', '10px'); + $('#none').before('
                                  '); + var helps = [lan.site.access_empty_ref_default, lan.site.multi_url, lan.site.trigger_return_404]; + robj.append(bt.render_help(helps)); + }); + }, + set_tomact: function (web) { + bt.site.get_site_phpversion(web.name, function (rdata) { + var robj = $('#webedit-con'); + if (!rdata.tomcatversion) { + robj.html('' + lan.site.tomcat_err_msg1 + ''); + layer.msg(lan.site.tomcat_err_msg, { icon: 2 }); + return; + } + var data = { + class: 'label-input-group', + items: [ + { + text: lan.site.enable_tomcat, + name: 'tomcat', + value: rdata.tomcat == -1 ? false : true, + type: 'checkbox', + callback: function (sdata) { + bt.site.set_tomcat(web.name, function (ret) { + if (ret.status) site.reload(9); + bt.msg(ret); + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(data); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + var helps = [lan.site.tomcat_help1 + ' ' + rdata.tomcatversion + ',' + lan.site.tomcat_help2, lan.site.tomcat_help3, lan.site.tomcat_help4, lan.site.tomcat_help5]; + robj.append(bt.render_help(helps)); + }); + }, + get_site_logs: function (web) { + $('#webedit-con').append('
                                  '); + var serverType = bt.get_cookie('serverType'), + shell = 'tail -n 100 -f /www/wwwlogs/' + web.name; + var _tab = [ + { + title: 'Access log', + on: true, + callback: function (robj) { + bt.site.get_site_logs(web.name, function (rdata) { + var _logs_info = $('
                                  ').text(rdata.msg); + var logs = { class: 'bt-logs', items: [{ name: 'site_logs', height: '560px', value: _logs_info.html(), width: '100%', type: 'textarea' }] }, + _form_data = bt.render_form_line(logs); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + $('textarea[name="site_logs"]').attr('readonly', true); + $('textarea[name="site_logs"]').scrollTop(100000000000); + }); + }, + }, + { + title: 'Error log', + callback: function (robj) { + bt.site.get_site_error_logs(web.name, function (rdata) { + var _logs_info = $('
                                  ').text(rdata.msg); + var logs = { class: 'bt-logs', items: [{ name: 'site_logs', height: '560px', value: _logs_info.html(), width: '100%', type: 'textarea' }] }, + _form_data = bt.render_form_line(logs); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + $('textarea[name="site_logs"]').attr('readonly', true); + $('textarea[name="site_logs"]').scrollTop(100000000000); + }); + }, + }, + { + title: 'Log Security Analysis', + callback: function (robj) { + var _serverType = bt.get_cookie('serverType'), + pathFile = '', + progress = '', //扫描进度 + loadT = bt.load('Getting log analytics data, please wait...'); + + switch (_serverType) { + case 'nginx': + pathFile = web.name + '.log'; + break; + case 'apache': + pathFile = web.name + '-access_log'; + break; + default: + pathFile = web.name + '_ols.access_log'; + break; + } + $.post('/ajax?action=get_result&path=/www/wwwlogs/' + pathFile, function (rdata) { + loadT.close(); + //1.扫描按钮 + var analyes_log_btn = ''; + + //2.功能介绍 + var analyse_help = + '
                                    \ +
                                  • Log analysis: Scan the logs(/www/wwwroot/.log) for requests with attack (types include:xss,sql,san,php)
                                  • \ +
                                  • Analyzed log data contains intercepted requests
                                  • \ +
                                  • By default, the last scan data is displayed (if not, please click log scan)
                                  • \ +
                                  • If the log file is too large, scanning may take a long time, please be patient
                                  • \ +
                                  • aaPanel WAF can effectively block such attacks
                                  • \ +
                                  '; + + robj.append(analyes_log_btn + '
                                  ' + analyse_help); + render_analyse_list(rdata); + + //事件 + $(robj) + .find('.analyes_log') + .click(function () { + bt.confirm( + { + title: 'Scan website logs', + msg: + 'It is recommended to perform security analysis when the server load is low. This time, the [' + + web.name + + '.log] file will be scanned. It may take a long time. Do you want to continue?', + }, + function (index) { + layer.close(index); + progress = layer.open({ + type: 1, + closeBtn: 2, + title: false, + shade: 0, + area: '400px', + content: + '
                                  Scanning, scanning progress...
                                  \ +
                                  \ +
                                  0%
                                  \ +
                                  \ +
                                  ', + success: function () { + // 开启扫描并且持续获取进度 + $.post('/ajax?action=log_analysis&path=/www/wwwlogs/' + pathFile, function (rdata) { + if (rdata.status) { + detect_progress(); + } else { + layer.close(progress); + layer.msg(rdata.msg, { icon: 2, time: 0, shade: 0.3, shadeClose: true }); + } + }); + }, + }); + } + ); + }); + }); + // 渲染分析日志列表 + function render_analyse_list(rdata) { + var analyse_list = + '
                                  \ + \ + '; + if (rdata.is_status) { + //检测是否有扫描数据 + analyse_list += + '\ + \ + \ + \ + \ + \ + \ + \ + \ + '; + } else { + analyse_list += ''; + } + analyse_list += '
                                  DateTimeXSSSQLSacnPHPIP(top100)URL(top100)
                                  ' + + rdata.start_time + + '' + + rdata.time.substring(0, 4) + + ' Sec 0 ? 'style="color:red"' : '') + + ' name="xss">' + + rdata.xss + + ' 0 ? 'style="color:red"' : '') + + ' name="sql">' + + rdata.sql + + ' 0 ? 'style="color:red"' : '') + + ' name="san">' + + rdata.san + + ' 0 ? 'style="color:red"' : '') + + ' name="php">' + + rdata.php + + '' + + rdata.ip + + '' + + rdata.url + + '
                                  no scan data
                                  '; + $('.analyse_log_table').html(analyse_list); + $('.onChangeLogDatail').css('cursor', 'pointer').attr('title', 'Details'); + //查看详情 + $('.onChangeLogDatail').on('click', function () { + get_analysis_data_datail($(this).attr('name')); + }); + } + // 扫描进度 + function detect_progress() { + $.post('/ajax?action=speed_log&path=/www/wwwlogs/' + pathFile, function (res) { + var pro = res.msg; + if (pro !== 100) { + if (pro > 100) pro = 100; + if (pro !== NaN) { + $('.pro_style .progress-bar') + .css('width', pro + '%') + .html(pro + '%'); + } + setTimeout(function () { + detect_progress(); + }, 1000); + } else { + layer.msg('Scan complete', { icon: 1, timeout: 4000 }); + layer.close(progress); + get_analysis_data(); + } + }); + } + // 获取扫描结果 + function get_analysis_data() { + var loadTGA = bt.load('Getting log analytics data, please wait...'); + $.post('/ajax?action=get_result&path=/www/wwwlogs/' + pathFile, function (rdata) { + loadTGA.close(); + render_analyse_list(rdata, true); + }); + } + // 获取扫描结果详情日志 + function get_analysis_data_datail(name) { + layer.open({ + type: 1, + closeBtn: 2, + shadeClose: false, + title: '[ ' + name + ' ] log details', + area: '650px', + content: '
                                  ',
                                  +								success() {
                                  +									var loadTGD = bt.load('Getting log details data, please wait...');
                                  +									$.post('/ajax?action=get_detailed&path=/www/wwwlogs/' + pathFile + '&type=' + name + '', function (logs) {
                                  +										loadTGD.close();
                                  +										$('#analysis_pre').text((name == 'ip' || name == 'url' ? ' [Access Times] [' + name + '] \n' : '') + logs);
                                  +									});
                                  +								},
                                  +							});
                                  +						}
                                  +					},
                                  +				},
                                  +			];
                                  +			bt.render_tab('tabLogs', _tab);
                                  +			$('#tabLogs span:eq(0)').click();
                                  +		},
                                  +	},
                                  +	create_let: function (ddata, callback) {
                                  +		bt.site.create_let(ddata, function (ret) {
                                  +			if (ret.status) {
                                  +				if (callback) {
                                  +					callback(ret);
                                  +				} else {
                                  +					site.ssl.reload(1);
                                  +					bt.msg(ret);
                                  +					return;
                                  +				}
                                  +			} else {
                                  +				if (ret.msg) {
                                  +					if (typeof ret.msg == 'string') {
                                  +						ret.msg = [ret.msg, ''];
                                  +					}
                                  +				}
                                  +				if (!ret.out) {
                                  +					if (callback) {
                                  +						callback(ret);
                                  +						return;
                                  +					}
                                  +					bt.msg(ret);
                                  +					return;
                                  +				}
                                  +				var data = '

                                  ' + ret.msg + '


                                  '; + if (ret.err[0].length > 10) data += '

                                  ' + ret.err[0].replace(/\n/g, '
                                  ') + '

                                  '; + if (ret.err[1].length > 10) data += '

                                  ' + ret.err[1].replace(/\n/g, '
                                  ') + '

                                  '; + + layer.msg(data, { icon: 2, area: '500px', time: 0, shade: 0.3, shadeClose: true }); + } + }); + }, + reload: function (index) { + if (index == undefined) index = 0; + + var _sel = $('.site-menu p.bgw'); + if (_sel.length == 0) _sel = $('.site-menu p:eq(0)'); + _sel.trigger('click'); + }, + plugin_firewall: function (callback) { + var typename = bt.get_cookie('serverType'); + var name = 'btwaf_httpd'; + if (typename == 'nginx') name = 'btwaf'; + + bt.plugin.get_plugin_byhtml(name, function (rhtml) { + if (rhtml.status === false) { + layer.msg(rhtml.msg, { icon: 2 }); + return; + } + + var list = rhtml.split('', ''); + } else { + list = rhtml.split('', ''); + } + rcss = rhtml.split('')[0]; + rcode = rcode.replace(' wafview()', ''); + $('body').append('
                                  '); + + setTimeout(function () { + if (!!(window.attachEvent && !window.opera)) { + execScript(rcode); + } else { + window.eval(rcode); + } + }, 200); + + setTimeout(function () { + if (callback) callback(); + }, 400); + }); + }, + select_site_txt: function (box, value) { + var that = this; + layer.open({ + type: 1, + closeBtn: 2, + title: lan.site.set_ssl.cust_domain, + area: '600px', + btn: [lan.public.ok, lan.public.cancel], + content: + '
                                  Domain name
                                  \ +
                                    \ +
                                  • ' + + lan.site.set_ssl.cust_tip1 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.cust_tip2 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.cust_tip3 + + '
                                  • \ +
                                  • 1、' + + lan.site.set_ssl.cust_tip4 + + '
                                  • \ +
                                  • 2、' + + lan.site.set_ssl.cust_tip5 + + '
                                  • \ +
                                  • 3、' + + lan.site.set_ssl.cust_tip6 + + '
                                  • \ +
                                  \ +
                                  ', + success: function ($layer) { + $('[name="site_name"]').focus(); + }, + yes: function (layers, index) { + var domain = $('.ssl_site_name_rc').val(), + code = $('.perfect_ssl_info').data('code'); + if (!bt.check_domain(domain)) { + return layer.msg(lan.site.set_ssl.sing_domain_err, { icon: 2 }); + } else if (code.indexOf('wildcard') === -1) { + if (domain.indexOf('*') > -1) { + return layer.msg(lan.site.set_ssl.sing_domain_more, { icon: 2 }); + } + } + layer.close(layers); + $('#' + box).val($('.ssl_site_name_rc').val()); + // that.check_domain_error(domain); + that.check_domain_dns(); + }, + }); + }, + /** + * @descripttion: 选择站点 + * @author: Lifu + * @Date: 2020-08-14 + * @param {String} box 输出时所用ID + * @return: 无返回值 + */ + select_site_list: function (box, code) { + var that = this, + _optArray = [], + all_site_list = []; + bt.send('getData', 'data/getData', { tojs: 'site.get_list', table: 'domain', limit: 10000, search: '', p: 1, order: 'id desc', type: -1 }, function (res) { + var _tbody = ''; + if (res.data.length > 0) { + $.each(res.data, function (index, item) { + _body = + '' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + '' + + item['name'] + + '' + + ''; + if (code.indexOf('wildcard') > -1) { + if (item['name'].indexOf('*.') > -1) { + all_site_list.push(item['name']); + _tbody += _body; + } + } else { + all_site_list.push(item['name']); + _tbody += _body; + } + }); + if (all_site_list.length == 0) { + _tbody = '' + lan.bt.no_data + ''; + } + } else { + _tbody = '' + lan.bt.no_data + ''; + } + + layer.open({ + type: 1, + closeBtn: 2, + title: lan.site.set_ssl.select_domain, + area: ['600px', '650px'], + btn: [lan.public.ok, lan.public.cancel], + content: + '\ +
                                  \ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ + ' + + _tbody + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.domain_name + + '
                                  \ +
                                  \ +
                                  \ +
                                    \ +
                                  • ' + + lan.site.set_ssl.cust_tip1 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.cust_tip2 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.cust_tip3 + + '
                                  • \ +
                                  • 1、' + + lan.site.set_ssl.cust_tip4 + + '
                                  • \ +
                                  • 2、' + + lan.site.set_ssl.cust_tip5 + + '
                                  • \ +
                                  • 3、' + + lan.site.set_ssl.cust_tip6 + + '
                                  • \ +
                                  \ +
                                  ', + success: function ($layer) { + // 固定表格头部 + if (jQuery.prototype.fixedThead) { + $('.dynamic_list_table .divtable').fixedThead({ resize: false }); + } else { + $('.dynamic_list_table .divtable').css({ overflow: 'auto' }); + } + //检索输入 + $('input[name=serach_site]').on('input', function () { + var _serach = $(this).val(); + if (_serach.trim() != '') { + $('.dynamic_list tr').each(function () { + var _td = $(this).find('td').eq(1).html(); + if (_td.indexOf(_serach) == -1) { + $(this).hide(); + } else { + $(this).show(); + } + }); + } else { + $('.dynamic_list tr').show(); + } + }); + + // 单选设置 + $('.dynamic_list').on('click', '.bt_checkbox_groups', function (e) { + var _tr = $(this).parents('tr'); + if ($(this).hasClass('active')) { + $(this).removeClass('active'); + } else { + $('.dynamic_list .bt_checkbox_groups').removeClass('active'); + $(this).addClass('active'); + _optArray = [_tr.find('td').eq(1).text()]; + } + e.preventDefault(); + e.stopPropagation(); + }); + // tr点击时 + $('.dynamic_list').on('click', 'tr', function (e) { + $(this).find('.bt_checkbox_groups').click(); + e.preventDefault(); + e.stopPropagation(); + }); + }, + yes: function (layers, index) { + var _olist = []; + if (_optArray.length > 0) { + $.each(_optArray, function (index, item) { + if ($.inArray(item, _olist) == -1) { + _olist.push(item); + } + }); + } + layer.close(layers); + // 多域名时,将olist过滤并追加到site.domain_dns_list + if (site.domain_dns_type == 'multi') { + var domainList = site.domain_dns_list; + var newDomainList = []; + $.each(_olist, function (index, item) { + if ($.inArray(item, domainList) == -1) { + newDomainList.push(item); + } + }); + domainList = domainList.concat(newDomainList); + } else { + domainList = _olist; + } + $('#' + box).val(domainList.join('\n')); + site.domain_dns_list = domainList; + $('textarea[name=lb_site]').focus(); + + // that.check_domain_error(_olist[0]); + that.check_domain_dns(); + }, + }); + }); + }, + web_edit: function (obj) { + var _this = this; + var item = obj; + bt.open({ + type: 1, + area: ['860px', '740px'], + title: lan.site.website_change + ' [' + item.name + '] -- ' + lan.site.addtime + ' [' + item.addtime + ']', + closeBtn: 2, + shift: 0, + content: "
                                  ", + }); + setTimeout(function () { + var webcache = + bt.get_cookie('serverType') == 'openlitespeed' + ? { + title: 'LS-Cache', + callback: site.edit.ols_cache, + } + : ''; + var menus = [ + { title: lan.site.domain_man, callback: site.edit.set_domains }, + { title: lan.site.site_menu_1, callback: site.edit.set_dirbind }, + { title: lan.site.site_menu_2, callback: site.edit.set_dirpath }, + { title: 'Limit access', callback: site.edit.set_dirguard }, + { title: lan.site.site_menu_3, callback: site.edit.limit_network }, + { title: lan.site.site_menu_4, callback: site.edit.get_rewrite_list }, + { title: lan.site.site_menu_5, callback: site.edit.set_default_index }, + { title: lan.site.site_menu_6, callback: site.edit.set_config }, + { title: lan.site.site_menu_7, callback: site.set_ssl }, + { title: lan.site.php_ver, callback: site.edit.set_php_version }, + { title: 'Composer', callback: site.edit.set_composer }, + // { title: lan.site.site_menu_9, callback: site.edit.set_tomact }, + // { title: lan.site.redirect, callback: site.edit.set_301_old }, + { title: lan.site.redirect_test, callback: site.edit.set_301 }, + { title: lan.site.site_menu_11, callback: site.edit.set_proxy }, + { title: lan.site.site_menu_12, callback: site.edit.set_security }, + { title: lan.site.response_log, callback: site.edit.get_site_logs }, + ]; + if (webcache !== '') menus.splice(3, 0, webcache); + if (item.project_type == 'WP') menus.splice(10, 0, { title: 'Wordpress Setting', callback: site.edit.set_wp_config }); + for (var i = 0; i < menus.length; i++) { + var men = menus[i]; + var _p = $('

                                  ' + men.title + '

                                  '); + _p.data('callback', men.callback); + $('.site-menu').append(_p); + } + $('.site-menu p').click(function () { + $('#webedit-con').html(''); + $(this).addClass('bgw').siblings().removeClass('bgw'); + var callback = $(this).data('callback'); + if (callback) callback(item); + }); + site.reload(0); + }, 100); + }, + domain_dns_list: [], // dns域名列表 + domain_dns_type: '', // dns域名类型[one/multi] + dns_data_treating: [], // 原始dns接口数据 + dns_configured_table: [], // 已配置的dns域名列表 + dns_interface_list: [], // dns接口列表 + dnsForm: null, + //检测域名api配置是否正确 + check_domain_dns: function () { + if (site.domain_dns_type == 'one') { + var domain = $('#apply_site_name').val(); + site.domain_dns_list = [domain]; + if (domain == '') return layer.msg(lan.site.set_ssl.domain_name_pl, { icon: 0 }); + } else if (site.domain_dns_type == 'multi') { + if (site.domain_dns_list.length == 0) return layer.msg(lan.site.set_ssl.domain_name_pla, { icon: 0 }); + } + site.refresh_dns_interface(); + }, + // 管理dns接口 + set_dns_api_open: function () { + bt_tools.open({ + title: lan.site.set_ssl.man_dns, + area: '600px', + btn: false, + content: '
                                  ', + success: function (layers) { + bt_tools.table({ + el: '.dnsManager', + url: '/site?action=GetDnsApi', + height: '400', + dataFilter: function (res) { + var data = []; + for (var i = 1; i < res.length; i++) { + var resI = res[i]; + for (var j = 0; j < resI.data.length; j++) { + var info = {}; + info = { + ps: resI.data[j].ps, + id: resI.data[j].id, + domain: resI.data[j].domain, + conf: resI.data[j].conf, + typeTitle: resI.title, + dns_type: resI.name, + name: resI.data[j].conf[0].value, + value: resI.data[j].conf[1].value, + }; + // 获取dns接口配置信息 + for (var k = 0; k < resI.data[j].conf.length; k++) { + info[resI.data[j].conf[k].name] = resI.data[j].conf[k].value; + } + data.push(info); + } + } + site.dns_configured_table = site.data_treating(res); + return { data: data }; + }, + column: [ + { + fid: 'typeTitle', + title: lan.public_backup.type, + }, + { + fid: 'ps', + title: lan.soft.ps, + }, + { + title: bt.public.action, + align: 'right', + type: 'group', + group: [ + { + title: bt.public.edit, + event: function (row, index, ev, key, _that) { + site.editDns(row, _that); + }, + }, + { + title: bt.public.del, + event: function (row, index, ev, key, _that) { + bt.simple_confirm({ title: 'delete【' + row.ps + '】', msg: lan.site.set_ssl.del_api_confirm + '?' }, function () { + bt_tools.send( + { url: '/site?action=remove_dns_api', data: { dns_type: row.dns_type, api_id: row.id } }, + function (res) { + bt_tools.msg(res); + if (res.status) _that.$refresh_table_list(true); + }, + 'Delete authentication interface' + ); + }); + }, + }, + ], + }, + ], + tootls: [ + { + type: 'group', + positon: ['left', 'top'], + list: [ + { + title: lan.site.set_ssl.add_dns, + active: true, + event: function (row, _that) { + site.editDns(undefined, _that); + }, + }, + ], + }, + ], + }); + }, + }); + }, + + // 添加dns接口 + add_dns_interface: function () { + var _this = this; + if ($('.dns_interface_line').length > 0) return; + $('.check_model_line.line').after('
                                  '); // 插入dns + $('.isdnsbtn').show(); // 显示dns刷新按钮 + var formConfig = [ + { + label: lan.site.set_ssl.select_dns, + group: [ + { + type: 'select', + name: 'dns_select', + width: '250px', + placeholder: lan.site.set_ssl.select_parse, + list: [ + // {title:lan.site.set_ssl.auto_parse,value:'dns#@api'}, + { title: lan.site.set_ssl.manual_parse, value: 'dns' }, + ], + change: function (formData, el, that) { + that.config.form[0].group[0].value = formData.dns_select; + if (formData.dns_select == 'dns#@api') { + that.config.form[0].group[0].suffix = ''; + that.config.form[0].group[1].display = true; + } else { + that.config.form[0].group[0].suffix = '
                                  ' + lan.site.set_ssl.parse_tip + ''; + that.config.form[0].group[1].display = false; + } + that.$replace_render_content(0); + }, + }, + { + display: false, + type: 'button', + class: 'btn-sub-success', + style: { 'margin-left': '10px', 'vertical-align': 'middle' }, + title: lan.site.set_ssl.dns_api_config, + }, + ], + }, + ]; + if ($('#ssl_tabs span.on').text().indexOf('Let') > -1) { + // formConfig插入数据 + formConfig.push( + { + label: '', + group: [ + { + name: 'app_root', + type: 'checkbox', + title: lan.site.set_ssl.auto_more_domain, + }, + ], + }, + { + label: '', + group: [ + { + type: 'help', + style: { margin: '0' }, + list: [lan.site.set_ssl.auto_more_tip], + }, + ], + } + ); + } + // 渲染dns + _this.dnsForm = bt_tools.form({ + el: '.dnsForm', + form: formConfig, + }); + //管理dns点击事件 + $('.dnsForm') + .unbind('click') + .on('click', '.btn-sub-success.btn-success', function () { + site.set_dns_api_open(); + }); + }, + // 添加/编辑dns接口 + editDns: function (row, _that, isGlobal) { + var isEdit = row && row.hasOwnProperty('name') ? true : false; + bt_tools.open({ + title: isEdit ? 'Edit【' + row.ps + '】' : lan.site.set_ssl.add_dns_ver, + area: '530px', + skin: 'dns_layer_form', + content: { + class: 'pd20', + data: row, + form: this.switch_dns_add_key(isEdit ? row.dns_type : site.dns_interface_list[0].value, isGlobal), + }, + success: function (layero) { + $('.dns-help li').eq(3).show().siblings().hide(); + $(layero).find('.layui-layer-content').css('overflow', 'inherit'); + if (isGlobal) { + $('textarea[name=domains]').val(isEdit ? row.domain.join('\n') : ''); + } + }, + yes: function (formData, indexs) { + // 是否验证成功 + var isverify = true, + param = { dns_type: formData.dns_type, ps: formData.ps }, + paramArr = []; + // 限制ps的长度 + if (formData.ps.length > 35) return layer.msg(lan.site.set_ssl.ps_pl, { icon: 0 }); + // dns_layer_form下input如果为空,提示对应的placeholder,然后return false + $('.dns_layer_form input').each(function (index, item) { + var _val = $(item).val(); + if (_val == '' || _val.replace(/\s+/g, '') == '') { + layer.msg($(item).attr('placeholder'), { icon: 0 }); + isverify = false; + return false; + } + }); + if (!isverify) return false; + // 排除formData中的dns_type和ps,循环添加到paramArr中 + for (var key in formData) { + if (key !== 'dns_type' && key !== 'ps') { + paramArr.push({ name: key, value: formData[key] }); + } + } + param['pdata'] = JSON.stringify(paramArr); + + if (isEdit) param['api_id'] = row.id; + if (typeof row != 'undefined' && row.hasOwnProperty('domains')) { + param['domains'] = JSON.stringify([row.domains]); // 没有dns接口,默认已当前域名添加 + } else if (isGlobal) { + // 高级设置类型 + param['domains'] = JSON.stringify(formData.domains.split('\r\n')); + } else { + param['domains'] = JSON.stringify([]); //不设定固定域名,单纯链接dns接口 + } + bt_tools.send( + { url: isEdit ? '/site?action=set_dns_api' : '/site?action=add_dns_api', data: param }, + function (res) { + bt_tools.msg(res); + if (res.status) { + layer.close(indexs); + if (_that) { + _that.$refresh_table_list(true); + } else { + bt_tools.send({ url: '/site?action=GetDnsApi' }, function (data) { + site.dns_configured_table = site.data_treating(data); + }); + } + } + }, + isEdit ? 'Modify authentication interface' : 'Add authentication interface' + ); + }, + }); + }, + /** + * 生成不同dns类型的配置 + * @param {*} type dns|DNSPodDns|AliyunDns|CloudflareDns|GodaddyDns|DNSLADns| + * @returns config 用于重新渲染表单 + */ + switch_dns_add_key: function (type, isGlobal) { + // 在原始数据中查找type相同的数据 + var sthat = this, + firstApi = site.dns_data_treating.find(function (item) { + return item.name === type; + }), + helpObj = {}, + configKey = []; + for (var i = 0; i < site.dns_interface_list.length; i++) { + var help = site.dns_interface_list[i].help; + helpObj[site.dns_interface_list[i].value] = [ + '' + help[0].title + '', + '' + help[1].title + '', + ]; + } + var config = [ + { + label: lan.site.set_ssl.ver_type, + group: [ + { + type: 'select', + name: 'dns_type', + width: '330px', + list: this.dns_interface_list, + change: function (formData, element, that) { + // 根据不同的dns接口,渲染不同的输入框 + that.config.form[0].group[0].value = formData.dns_type; + that.$again_render_form(sthat.switch_dns_add_key(formData.dns_type)); + }, + }, + ], + }, + { + label: lan.soft.ps, + group: [ + { + type: 'text', + name: 'ps', + width: '330px', + placeholder: lan.site.set_ssl.ps_pls, + }, + ], + }, + { + group: [ + { + type: 'help', + class: 'dns-help', + list: helpObj[type], + }, + ], + }, + ]; + //高级设置类型追加显示已添加的域名 + if (isGlobal) { + // 往config的第二个种,追加域名 + config.splice(2, 0, { + label: lan.site.set_ssl.as_domain, + group: [ + { + type: 'textarea', + name: 'domains', + style: { + width: '330px', + 'min-width': '330px', + 'min-height': '130px', + 'line-height': '22px', + 'padding-top': '10px', + resize: 'both', + }, + placeholder: lan.site.set_ssl.more_domain_pl, + }, + ], + }); + } + $.each(firstApi.add_table[0].fields, function (index, item) { + configKey.push({ + label: item, + group: [ + { + type: 'text', + name: item, + width: '330px', + placeholder: 'Please enter' + item, + }, + ], + }); + }); + config = config.slice(0, 1).concat(configKey).concat(config.slice(1)); + return config; + }, + // 深度获取所有dns账号中的域名 + data_treating: function (data) { + var tableData = [], + dns_type = []; + this.dns_data_treating = data.filter(function (item) { + return item.name !== 'dns'; + }); // 原始数据 + for (var i = 0; i < data.length; i++) { + if (data[i].name == 'dns') continue; //手动解析 + var item = data[i]; + if (item.data) { + for (var j = 0; j < item.data.length; j++) { + tableData.push({ + ps: item.data[j].ps, + id: item.data[j].id, + domain: item.data[j].domain, + conf: item.data[j].conf, + typeTitle: item.title, + dns_type: item.name, + name: item.data[j].conf ? item.data[j].conf[0].value : '', + value: item.data[j].conf ? item.data[j].conf[1].value : '', + help: item.help, + add_table: item.add_table, + }); + } + } + dns_type.push({ title: item.title, value: item.name, help: item.help }); + } + this.dns_interface_list = dns_type; + return tableData; + }, + // 移除dns接口 + remove_dns_interface: function () { + $('.dns_interface_line.line').remove(); + $('.isdnsbtn').hide(); + }, + // 自动刷新dns域名情况 + refresh_dns_interface: function () { + // 当前解析类型 + var dns_type = $('select[name=dns_select]').val(), + that = this; + // 是否手动解析 + if (dns_type == 'dns') { + return false; + } + bt_tools.send({ url: 'site?action=test_domains_api', data: { domains: JSON.stringify(site.domain_dns_list) } }, function (res) { + if (site.domain_dns_type == 'one') { + if ($.isEmptyObject(res[0])) { + $('.damin_dns_result').html( + '
                                  ' + ); + } else { + $('.damin_dns_result').html( + '
                                  ' + ); + } + + // 点击配置 + $('.damin_dns_result').on('click', 'button', function () { + // 没有dns接口,直接跳转到添加 + if (site.dns_configured_table.length == 0) return site.editDns({ domains: $('#apply_site_name').val() }); + // 渲染dns列表 + var options = ''; + $.each(site.dns_configured_table, function (index, item) { + options += '
                                • ' + item.typeTitle + '[' + item.ps + ']' + '
                                • '; + }); + $('.damin_dns_result .dropdown-menu').html(options); + }); + // 点击选择 + $('.damin_dns_result .dropdown-menu') + .unbind('click') + .on('click', 'li', function () { + var dns_id = $(this).data('id'), + param = { api_id: dns_id }, + item = site.dns_configured_table.find(function (item) { + return item.id == dns_id; + }); + + param['dns_type'] = item.dns_type; + param['force_domain'] = $('#apply_site_name').val(); + bt_tools.send({ url: 'site?action=set_dns_api', data: param }, function (res) { + that.refresh_dns_interface(); + }); + }); + } else { + var view = ''; + $.each(site.domain_dns_list, function (index, item) { + // 是否空对象(没有设置dns) + var isDnsEmpty = $.isEmptyObject(res[index]); + view += + '
                                  \ +
                                  ' + + item + + '
                                  \ +
                                  \ +
                                  \ + \ + \ +
                                  \ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  '; + }); + $('.dns_domains_multi_list').html(view); + + // 点击配置 + $('.dns_domains_item_btn button').click(function () { + // 没有dns接口,直接跳转到添加 + if (site.dns_configured_table.length == 0) return site.editDns({ domains: $(this).parents('.dns_domains_item').find('.dns_domains_item_title').text() }); + // 渲染dns列表 + var options = ''; + $.each(site.dns_configured_table, function (index, item) { + options += '
                                • ' + item.typeTitle + '[' + item.ps + ']' + '
                                • '; + }); + $('.dns_domains_item_btn .dropdown-menu').html(options); + }); + // 点击选择 + $('.dns_domains_item_btn .dropdown-menu') + .unbind('click') + .on('click', 'li', function () { + var dns_id = $(this).data('id'), + param = { api_id: dns_id }, + item = site.dns_configured_table.find(function (item) { + return item.id == dns_id; + }); + + param['dns_type'] = item.dns_type; + param['force_domain'] = $(this).parents('.dns_domains_item').find('.dns_domains_item_title').text(); + bt_tools.send({ url: 'site?action=set_dns_api', data: param }, function (res) { + that.refresh_dns_interface(); + }); + }); + // 点击删除 + $('.dns_domains_item_close') + .unbind('click') + .click(function () { + // 从site.domain_dns_list中删除当前选中的域名 + var domain = $(this).parents('.dns_domains_item').find('.dns_domains_item_title').text(); + site.domain_dns_list = site.domain_dns_list.filter(function (item) { + return item != domain; + }); + $(this).parents('.dns_domains_item').remove(); + }); + // dns_domains_item鼠标经过时添加active,离开时移除active + $('.dns_domains_item') + .unbind('mouseenter') + .mouseenter(function () { + $(this).addClass('active'); + }); + $('.dns_domains_item') + .unbind('mouseleave') + .mouseleave(function () { + $(this).removeClass('active'); + }); + } + }); + }, + //批量设置站点证书 + setBathSiteSsl: function (batch_list, callback) { + bt_tools.send( + { + url: '/ssl?action=SetBatchCertToSite', + data: { + BatchInfo: JSON.stringify(batch_list), + }, + }, + function (res) { + if (callback) callback(res); + }, + 'Set site certificates in batches' + ); + }, + set_ssl: function (web) { + site.web = web + + //站点/项目名、放置位置 + bt.site.get_site_ssl(web.name, function (rdata) { + var type = rdata.type; // 类型 + var certificate = rdata.cert_data; // 证书信息 + var pushAlarm = rdata.push; // 是否推送告警 + var isStart = rdata.status; // 是否启用 + var layers = null; + var expirationTime = certificate.endtime; // 证书过期时间 + var isRenew = (function () { + // 是否续签 + var state = false; + if (expirationTime <= 30) state = true; + if (type === 2 && expirationTime < 0) state = true; + if (type === 0 || type === -1) state = false; + return state; + })(); + + // 续签视图 + function renewal_ssl_view(item) { + bt.confirm( + { + title: 'Visa renewal letter', + msg: 'The current certificate order needs to be regenerated into a new order, which requires manual renewal and re-deployment of the certificate. Do you want to continue?', + }, + function () { + var loadT = bt.load(lan.site.set_ssl.renew_cert_load); + bt.send( + 'renew_cert_order', + 'ssl/renew_cert_order', + { + pdata: JSON.stringify({ oid: item.oid }), + }, + function (res) { + loadT.close(); + site.reload(); + setTimeout(function () { + bt.msg(res); + }, 1000); + } + ); + } + ); + } + + // 申请宝塔证书 + function apply_bt_certificate() { + var html = ''; + var domains = []; + for (var i = 0; i < rdata.domain.length; i++) { + var item = rdata.domain[i]; + if (item.name.indexOf('*') == -1) domains.push({ title: item.name, value: item.name }); + } + for (var i = 0; i < domains.length; i++) { + var item = domains[i]; + html += ''; + } + bt.open({ + type: 1, + title: lan.site.set_ssl.req_free_cert, + area: '610px', + content: + '
                                  \ +
                                  \ + \ +
                                  提示:尊敬的用户您好,感谢您对宝塔免费SSL证书的支持,由于证书签发机制的调整,我们的免费SSL证书签发将于2023年12月31日进行下架,查看详情
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.cert_info + + '\ +
                                  \ + TrustAsia TLS RSA CA(Free edition)\ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.domain_name + + '\ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.per_name + + '\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.lo_area + + '\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.address + + '\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.public_backup.mobile_phone_or_email + + '\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.pos_code + + '\ +
                                  \ + \ +
                                  \ +
                                  \ + \ +
                                  \ + \ +
                                  \ + ' + + lan.site.set_ssl.add_tips + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  ', + success: function (layero, index) { + $('.submit_ssl_info').click(function () { + var form = $('.free_ssl_info').serializeObject(); + for (var key in form) { + if (Object.hasOwnProperty.call(form, key)) { + var value = form[key], + el = $('[name="' + key + '"]'); + if (value == '') { + layer.tips(el.attr('placeholder'), el, { tips: ['1', 'red'] }); + el.focus(); + el.css('borderColor', 'red'); + return false; + } else { + el.css('borderColor', ''); + } + switch (key) { + case 'orgPhone': + if (!bt.check_phone(value)) { + layer.tips(lan.site.set_ssl.phone_ver, el, { tips: ['1', 'red'] }); + el.focus(); + el.css('borderColor', 'red'); + return false; + } + break; + case 'orgPostalCode': + if (!/^[0-9]\d{5}(?!\d)$/.test(value)) { + layer.tips(lan.site.set_ssl.postal_ver, el, { tips: ['1', 'red'] }); + el.focus(); + el.css('borderColor', 'red'); + return false; + } + break; + } + } + } + if (form.domain.indexOf('www.') != -1) { + var rootDomain = form.domain.split(/www\./)[1]; + if (!$.inArray(domains, rootDomain)) { + layer.msg(lan.site.set_ssl.no_root_tip(form.domain, rootDomain), { icon: 2, time: 5000 }); + return; + } + } + var loadT = bt.load(lan.site.set_ssl.sub_cert); + bt.send('ApplyDVSSL', 'ssl/ApplyDVSSL', $.extend(form, { path: web.path }), function (tdata) { + loadT.close(); + if (tdata.msg.indexOf('
                                  ') != -1) { + layer.msg(tdata.msg, { time: 0, shadeClose: true, area: '600px', icon: 2, shade: 0.3 }); + } else { + bt.msg(tdata); + } + if (tdata.status) { + layer.close(index); + site.ssl.verify_domain(tdata.data.partnerOrderId, web.name); + } + }); + }); + $('.free_ssl_info input').keyup(function (res) { + var value = $(this).val(); + if (value == '') { + layer.tips($(this).attr('placeholder'), $(this), { tips: ['1', 'red'] }); + $(this).focus(); + $(this).css('borderColor', 'red'); + } else { + $(this).css('borderColor', ''); + } + }); + }, + }); + } + + if (!Array.isArray(certificate.dns)) certificate = { dns: [] }; + + $('#webedit-con').html( + '
                                  ' + + '

                                  ' + + lan.site.set_ssl.tips1 + + '

                                  ' + + '

                                  ' + + lan.site.set_ssl.tips2( + '' + + certificate.dns.join(', ') + + '', + expirationTime < 0 + ) + + '

                                  ' + + '
                                  ' + + '
                                  ' + ); + var tabs = [ + { + title: lan.site.set_ssl.menu1 + ' - [' + (rdata.status ? lan.site.set_ssl.deployed : lan.site.set_ssl.not_deployed) + ']', + callback: function (content) { + acme.id = web.id; + var classify = ''; + var typeList = [lan.site.set_ssl.cert_type1, lan.site.set_ssl.cert_type2, lan.site.set_ssl.cert_type3, lan.site.set_ssl.cert_type4]; + var state = $( + '
                                  ' + + '
                                  ' + + '' + + '
                                  ' + + lan.site.set_ssl.cert_brand + + ':' + + certificate.issuer + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + lan.site.set_ssl.auth_domain + + '' + + certificate.dns.join('、') + + '
                                  ' + + '
                                  ' + + lan.site.set_ssl.expire_time + + '' + + (expirationTime >= 0 ? lan.site.set_ssl.expire_time_text(rdata.cert_data.notAfter, expirationTime.toFixed(0)) : lan.site.set_ssl.expired) + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + lan.site.set_ssl.force_https + + '
                                  ' + + // '
                                  ' + lan.site.set_ssl.expire_reminder + ':Config
                                  ' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + lan.site.set_ssl.ssl_key + + '
                                  ' + + '
                                  ' + + lan.site.set_ssl.ssl_crt + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + '' + + '' + + '' + + '
                                  ' + ); + content.append(state); + content.append( + bt.render_help([lan.site.set_ssl.save_ssl_tips1, lan.public_backup.cret_err, lan.public_backup.pem_format, lan.site.set_ssl.save_ssl_tips2, lan.site.set_ssl.save_ssl_tips3]) + ); + // if(rdata.status) { + // bt_tools.send({url: '/site?action=check_ssl',data: {hostname: web.name}}, function (res) { + // if(!res.status) content.prepend('
                                  '+ res.msg +'
                                  ') + // },{verify:false}) + // } + var setAlarmMode = bt.get_cookie('setAlarmMode'); + if (!pushAlarm.status && rdata.csr && !setAlarmMode) { + // if (true) { + bt.set_cookie('setAlarmMode', 1); + layer.tips(lan.site.set_ssl.set_alarm_mode_tips, '.setAlarmMode', { + tips: [1, '#d9534f'], + area: '380px', + time: 5000, + }); + setTimeout(function () { + $(window).one('click', function () { + layer.closeAll('tips'); + }); + }, 500); + } + var moduleConfig = null; + function cacheModule(callback) { + if (moduleConfig && callback) return callback(moduleConfig); + bt.site.get_module_config({ name: 'site_push', type: 'ssl' }, function (rdata1) { + moduleConfig = rdata1; + if (callback) callback(rdata1); + }); + } + + /** + * 提醒到期弹框 + * @param $check 到期提醒开关 + */ + function alarmMode($check) { + var time = new Date().getTime(); + var isExpiration = pushAlarm.status; + if ($check) isExpiration = $check.is(':checked'); + layer.open({ + type: 1, + title: lan.site.set_ssl.expire_reminder_title, + area: '470px', + closeBtn: 2, + content: + '\ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.expire_reminder + + '\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.site + + '\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.remaining_days + + '\ +
                                  \ +
                                  \ + \ + Days\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.number_ransmissions + + '\ +
                                  \ +
                                  \ + \ + ,' + + lan.site.set_ssl.no_more + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.notification_mode + + '\ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.application_config + + '\ +
                                  \ +
                                  \ +
                                  \ + \ + \ + ' + + lan.site.set_ssl.apply_all + + ' ' + + lan.site.set_ssl.no_config_site + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  ', + btn: [lan.site.set_ssl.save_config, lan.public.cancel], + success: function ($layer) { + cacheModule(function (rdata1) { + // 获取配置 + bt.site.get_msg_configs(function (rdata) { + var html = '', + unInstall = '', + pushList = rdata1.push; + for (var key in rdata) { + var item = rdata[key], + _html = '', + accountConfigStatus = false, + module = pushAlarm.module || []; + if (pushList.indexOf(item.name) === -1) continue; + if (key == 'sms') continue; + if (key === 'wx_account') { + if (!$.isEmptyObject(item.data) && item.data.res.is_subscribe && item.data.res.is_bound) { + accountConfigStatus = true; //安装微信公众号模块且绑定 + } + } + _html = + '
                                  ' + + '
                                  ' + + '' + + '' + + '' + + item.title + + (!item.setup || $.isEmptyObject(item.data) + ? '[' + lan.public_backup.install + ']' + : key == 'wx_account' && !accountConfigStatus + ? '[' + lan.public_backup.install + ']' + : '') + + '' + + '
                                  ' + + '
                                  '; + if (!item.setup) { + unInstall += _html; + } else { + html += _html; + } + } + $('.installPush').html(html + unInstall); + $('.setAllSsl').on('click', function () { + var that = $(this).find('i'); + if (that.hasClass('active')) { + that.removeClass('active'); + that.next().prop('checked', false); + } else { + that.addClass('active'); + that.next().prop('checked', true); + } + }); + if (pushAlarm.project === 'all' && pushAlarm.status) $('.setAllSsl').trigger('click'); + }); + }); + + // 安装消息通道 + $('.installPush').on('click', '.form-checkbox-label', function () { + var that = $(this).find('i'); + if (!that.parent().parent().hasClass('check_disabled')) { + if (that.hasClass('active')) { + that.removeClass('active'); + that.next().prop('checked', false); + } else { + that.addClass('active'); + that.next().prop('checked', true); + } + } + }); + $('.triggerCycle').on('input', function () { + $('.siteSslHelp span').html($(this).val()); + }); + + $('.installPush').on('click', '.installNotice', function () { + var type = $(this).data('type'); + openAlertModuleInstallView(type); + }); + }, + yes: function (index) { + var status = $('input[name="due_alarm"]').is(':checked'); + var cycle = $('.triggerCycle').val(); + var push_count = $('.triggerPushCount').val(); + var arry = []; + var module = ''; + var isAll = $('[name="allSsl"]').is(':checked'); + $('.installPush .active').each(function (item) { + var item = $(this).attr('data-type'); + arry.push(item); + }); + if (!arry.length) return layer.msg('Please select an alarm mode', { icon: 2 }); + if (!parseInt(cycle)) return layer.msg('Remaining days cannot be less than 1', { icon: 2 }); + if (!parseInt(push_count)) return layer.msg('Send times cannot be less than 1', { icon: 2 }); + + // 参数 + var data = { + status: status, + type: 'ssl', + project: web.name, + cycle: parseInt(cycle), + title: 'Website SSL expiration alert', + module: arry.join(','), + interval: 600, + push_count: parseInt(push_count), + }; + + // 判断是否点击全局应用 + if (isAll) { + // 请求设置全局应用告警配置 + var allData = Object.assign({}, data); + allData.status = true; + allData.project = 'all'; + bt.site.set_push_config({ + name: 'site_push', + id: time, + data: JSON.stringify(allData), + }); + } + + // 请求设置本站点告警配置 + bt.site.set_push_config( + { + name: 'site_push', + id: pushAlarm.id ? pushAlarm.id : time, + data: JSON.stringify(data), + }, + function (rdata) { + bt.msg(rdata); + setTimeout(function () { + site.reload(); + }, 1000); + layer.close(index); + } + ); + }, + cancel: function () { + $check && $check.prop('checked', !isExpiration); + }, + btn2: function () { + $check && $check.prop('checked', !isExpiration); + }, + }); + } + // 设置强制HTTPS + $('#https').on('click', function () { + var that = $(this), + isHttps = $(this).is(':checked'); + if (!isHttps) { + layer.confirm( + lan.site.set_ssl.force_https_confirm, + { + icon: 3, + closeBtn: 2, + title: 'Turn off forced HTTPS', + cancel: function () { + that.prop('checked', !isHttps); + }, + btn2: function () { + that.prop('checked', !isHttps); + }, + }, + function () { + bt.site.close_http_to_https(web.name, function (rdata) { + if (rdata.status) { + setTimeout(function () { + site.set_ssl(site.web); + }, 3000); + } else { + that.prop('checked', !isHttps); + } + }); + } + ); + } else { + bt.site.set_http_to_https(web.name, function (rdata) { + if (rdata.status) { + site.set_ssl(site.web); + } else { + that.prop('checked', !isHttps); + layer.confirm(lan.site.set_ssl.open_ssl_comfirm, { icon: 3, title: 'Tips' }, function (index) { + $.ajaxSettings.async = false; + $('.saveCertificate').click(); + $.ajaxSettings.async = true; + $('#https').click(); + layer.close(index); + }); + } + }); + } + }); + + // 设置告警通知 + $('#expiration').on('click', function () { + layer.close(layers); + var _that = $(this); + var isExpiration = $(this).is(':checked'); + var time = new Date().getTime(); + if (isExpiration) { + alarmMode(_that); + } else { + var data = JSON.stringify({ + status: isExpiration, + type: 'ssl', + project: web.name, + cycle: parseInt(pushAlarm.cycle), + title: 'Website SSL expiration alert', + module: pushAlarm.module, + interval: 600, + push_count: 1, + }); + var id = pushAlarm.id ? pushAlarm.id : time; + if (pushAlarm.project === 'all') id = time; + bt.site.set_push_config( + { + name: 'site_push', + id: id, + data: data, + }, + function (rdata) { + bt.msg(rdata); + setTimeout(function () { + site.reload(); + }, 1000); + } + ); + } + }); + + // 保存证书 + $('.saveCertificate').on('click', function () { + var key = $('[name="key"]').val(), + csr = $('[name="csr"]').val(); + function set_ssl() { + if (key === '' || csr === '') return bt.msg({ status: false, msg: 'Please fill in the complete certificate content' }); + bt.site.set_ssl( + web.name, + { + type: rdata.type, + siteName: rdata.siteName, + key: key, + csr: csr, + }, + function (ret) { + if (ret.status) site.set_ssl(web); + if (site.model_table) site.model_table.$refresh_table_list(true); + if (node_table) node_table.$refresh_table_list(true); + if (site_table) site_table.$refresh_table_list(true); + bt.msg(ret); + } + ); + } + + if ((key !== rdata.key && rdata.key) || (csr !== rdata.csr && rdata.key)) { + layer.confirm( + lan.site.set_ssl.edit_cert_comfirm, + { + icon: 3, + closeBtn: 2, + title: 'Certificate saving prompt', + }, + set_ssl + ); + } else { + set_ssl(); + } + }); + + // 告警方式 + $('.setAlarmMode').on('click', function () { + layer.close(layers); + alarmMode(); + }); + + // 续签证书 + $('.renewCertificate') + .unbind('click') + .on('click', function () { + var type = parseInt($(this).attr('data-type')); + switch (type /**/) { + case 3: // 商业证书续签 + renewal_ssl_view({ oid: rdata.oid }); + break; + case 2: // 宝塔证书 续签 + apply_bt_certificate(); + layer.msg('The current certificate type does not support one-click renewal. Please fill in the information application again', { icon: 2, time: 2000 }); + break; + case 1: // Let's Encrypt 续签 + site.ssl.renew_ssl(web.name, rdata.auth_type, rdata.index); + break; + } + }); + + // 关闭证书 + $('.closeCertificate').on('click', function () { + site.ssl.set_ssl_status('CloseSSLConf', web.name); + }); + + // 切换证书类型 + $('.cutSslType').on('click', function () { + var type = $(this).attr('data-type'); + switch (type) { + case '0': + type = 0; + break; + case '1': + type = 3; + break; + case '2': + type = 2; + break; + case '3': + type = 1; + break; + } + $('#ssl_tabs span:eq(' + type + ')').trigger('click'); + }); + + // 下载证书 + $('.downloadCertificate').on('click', function () { + var key = $('[name="key"]').val(), + pem = $('[name="csr"]').val(); + bt.site.download_cert( + { + siteName: web.name, + pem: pem, + key: key, + }, + function (rdata) { + if (rdata.status) { + window.open('/download?filename=' + encodeURIComponent(rdata.msg)); + } else { + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + } + } + ); + }); + }, + }, + { + title: lan.site.set_ssl.menu2 + '', + callback: function (robj) { + $.getScript('https://js.stripe.com/v3/'); + robj = $('#webedit-con .tab-con'); + bt.pub.get_user_info(function (udata) { + if (udata.status) { + var deploy_ssl_info = rdata, + html = '', + deploy_html = '', + product_list, + userInfo, + order_list, + is_check = true, + itemData, + activeData, + loadY, + pay_ssl_layer; + bt.send('get_order_list', 'ssl/get_order_list', {}, function (res) { + var rdata = res.res; + order_list = rdata; + if (rdata.length == 0) { + $('#ssl_order_list tbody').html( + '' + + lan.site.set_ssl.no_cert + + ' ->' + + lan.site.set_ssl.apply_certificate + + '' + ); + return; + } + $.each(rdata, function (index, item) { + if (deploy_ssl_info.type == 3 && deploy_ssl_info.oid === item.uc_id) { + deploy_html += + '' + + '' + + item.domains.join('、') + + '' + + item.title + + '' + + (function () { + var dayTime = new Date().getTime() / 1000, + color = '', + endTiems = ''; + if (item.end_date != '') { + item.end_date = parseInt(item.end_date); + endTiems = parseInt((item.end_date - dayTime) / 86400); + if (endTiems <= 15) color = 'orange'; + if (endTiems <= 7) color = 'red'; + if (endTiems < 0) return '' + lan.site.set_ssl.expired + ''; + return '' + lan.site.set_ssl.expire_date_text(endTiems) + ''; + } else { + return '--'; + } + })() + + '' + + lan.site.set_ssl.order_complate + + '' + + lan.site.set_ssl.deployed + + ' | ' + + lan.public.close + + ''; + } else { + html += + '' + + '' + + (item.domains == null ? '--' : item.domains.join('、')) + + '' + + item.title + + '' + + (function () { + var dayTime = new Date().getTime() / 1000, + color = '', + endTiems = ''; + if (item.end_date != '') { + item.end_date = parseInt(item.end_date); + endTiems = parseInt((item.end_date - dayTime) / 86400); + if (endTiems <= 15) color = 'orange'; + if (endTiems <= 7) color = 'red'; + if (endTiems < 0) return '' + lan.site.set_ssl.expired + ''; + return '' + lan.site.set_ssl.expire_date_text(endTiems) + ''; + } else { + return '--'; + } + })() + + '' + + (function () { + var suggest = ''; + if (!item.install) + suggest = + ' |' + + lan.site.set_ssl.troubleshooting_method + + '?
                                  • ' + + lan.site.set_ssl.check_oneself + + '

                                    ' + + lan.site.set_ssl.check_self_tip + + '

                                  • Labor Service Purchase

                                    Need deployment assistance? Human customer service available.

                                  • ' + + '
                                  '; + if (item.certId == '') { + return '' + lan.site.set_ssl.data_com + '' + suggest; + } else if (item.status === 1) { + switch (item.order_status) { + case 'COMPLETE': + return '' + lan.site.set_ssl.order_complate + ''; + break; + case 'PENDING': + return '' + lan.site.set_ssl.in_verify + '' + suggest; + break; + case 'CANCELLED': + return '' + lan.site.set_ssl.cancelled + ''; + break; + case 'FAILED': + return '' + lan.site.set_ssl.app_fail + ''; + break; + default: + return '' + lan.site.set_ssl.to_verified + ''; + break; + } + } else { + switch (item.status) { + case 0: + return '' + lan.site.set_ssl.no_pay + ''; + break; + case -1: + return '' + lan.site.set_ssl.cancelled + ''; + break; + default: + return '--'; + } + } + })() + + '' + + (function () { + var html = ''; + if (item.renew) html += '' + lan.site.set_ssl.renew_cert + '  |  '; + if (item.certId == '') { + // if (item.install) html += '人工服务 | '; + html += '' + lan.site.set_ssl.complete_data + ''; + return html; + } else if (item.status === 1) { + var html = ''; + switch (item.order_status) { + case 'COMPLETE': //申请成功 + return ( + '' + + lan.site.set_ssl.deploy + + '  |  ' + + lan.site.set_ssl.download + + '' + ); + break; + case 'PENDING': //申请中 + // if (item.install) html += '人工服务 | '; + html += '' + lan.site.set_ssl.verify + ''; + return html; + break; + case 'CANCELLED': //已取消 + return lan.site.set_ssl.no_action; + break; + case 'FAILED': + return '' + lan.site.set_ssl.detail + ''; + break; + default: + // if (item.install) html += '人工服务 | '; + html += '' + lan.site.set_ssl.verify + ''; + return html; + break; + } + } else { + return '--'; + } + })() + + '' + + ''; + } + }); + $('#ssl_order_list tbody').html(deploy_html + html); + //解决方案事件 + $('#ssl_order_list').on('click', '.bt_ssl_suggest', function (e) { + var $this = $(this); + var $cont = $this.find('.suggest_content'); + var rect = $this.offset(); + var top = rect.top + $this.height() + 7; + var left = rect.left - 153; + $cont.css({ + top: top + 'px', + left: left + 'px', + right: 'auto', + bottom: 'auto', + width: '410px', + }); + $('.suggest_content').hide(); + $cont.show(); + $(document).one('click', function () { + $cont.hide(); + }); + e.stopPropagation(); + }); + // 表格滚动隐藏解决方案内容 + $('.ssl_order_list').scroll(function (e) { + $('.suggest_content').hide(); + }); + //人工客服购买 + $('#ssl_order_list').on('click', '.service_buy', function () { + var loads = bt.load('Payment order is being generated, please wait...'); + bt.send( + 'apply_cert_install_pay', + 'ssl/apply_cert_install_pay', + { + uc_id: $(this).data('oid'), + }, + function (res) { + loads.close(); + if (!res.success) { + layer.msg(res.res, { icon: 2 }); + return; + } + var stripe = Stripe(res.res.stripe_public_key); + stripe.redirectToCheckout({ sessionId: res.res.session_id }); + // if (res.status != undefined && !res.status) { + // return layer.msg(res.msg, { time: 0, shadeClose: true, icon: 2, shade: 0.3 }); + // } + // open_service_buy(res); + } + ); + }); + + //人工客服咨询 + $('.service_method').click(function () { + bt.onlineService(); + }); + }); + robj.append( + '
                                  ' + + lan.site.set_ssl.enterprise_certificate + + '
                                  ' + + lan.site.set_ssl.exceptional_application + + '
                                  ' + + lan.site.set_ssl.anti_hijackingTampering + + '
                                  ' + + lan.site.set_ssl.increase_seo + + '
                                  ' + + lan.site.set_ssl.indemnity_guarantee + + '
                                  ' + + lan.site.set_ssl.refund_failure + + '
                                  ' + + lan.site.set_ssl.official_use + + '
                                  \ +
                                  \ + \ +
                                  \ + \ + \ + \ +
                                  ' + + lan.public_backup.domain + + '' + + lan.site.set_ssl.certificate_type + + '' + + lan.site.set_ssl.expire_date + + '' + + lan.public.status + + '' + + lan.public.action + + '
                                  ' + + lan.site.set_ssl.get_certificate_list + + '...
                                  \ +
                                  \ +
                                    \ +
                                  • ' + + lan.site.set_ssl.bus_tip1 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.bus_tip2 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.bus_tip3 + + '
                                  • \ +
                                  • ' + + lan.site.set_ssl.bus_tip4 + + '
                                  • \ +
                                  ' + ); + $('.service_buy_before').click(function () { + bt.onlineService(); + }); + bt.fixed_table('ssl_order_list'); + /** + * @description 证书购买人工服务 + * @param {Object} param 支付回调参数 + * @returns void + */ + function open_service_buy(param) { + var order_info = {}, + is_check = true; + pay_ssl_layer = bt.open({ + type: 1, + title: '购买人工服务', + area: ['790px', '770px'], + skin: 'service_buy_view', + content: + '
                                  \ +
                                  \ +
                                  微信支付支付宝支付
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + 总计\ + \ +
                                  \ +
                                  \ +
                                  \ + 商品名称\ + \ +
                                  \ +
                                  \ + 下单时间\ + \ +
                                  \ +
                                  \ +
                                  微信扫一扫支付
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  支付成功
                                  \ +
                                  \ +
                                  \ + 商品名称:\ + \ +
                                  \ +
                                  \ +
                                  \ + 商品价格:\ + \ +
                                  \ +
                                  \ + 下单时间:\ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  请打开微信扫一扫联系人工客服
                                  \ +
                                  qrcode
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  ', + success: function (layero, indexs) { + var order_wxoid = null, + qq_info = null; + + $('.guide_nav span').click(function () { + $(this).addClass('active').siblings().removeClass('active'); + $('.lib-prompt span').html($(this).index() == 0 ? '微信扫一扫支付' : '支付宝扫一扫支付'); + $('#PayQcode').empty(); + $('#PayQcode').qrcode({ + render: 'canvas', + width: 200, + height: 200, + text: $(this).index() != 0 ? order_info.alicode : order_info.wxcode, + }); + }); + reader_applay_qcode( + $.extend( + { + name: '证书安装服务', + price: param.price, + time: bt.format_data(new Date().getTime()), + }, + param + ), + function (info) { + check_applay_status(function (rdata) { + $('.order_service_check').addClass('active').siblings().removeClass('active'); + $('.order_service_check .lib-price-detailed .text-right:eq(0)').html(info.name); + $('.order_service_check .lib-price-detailed .text-right:eq(1)').html('¥' + info.price); + $('.order_service_check .lib-price-detailed .text-right:eq(2)').html(info.time); + $('#ssl_tabs .on').click(); + //人工客服二维码 + $('#contact_qcode').qrcode({ + render: 'canvas', + width: 120, + height: 120, + text: 'https://work.weixin.qq.com/kfid/kfc9151a04b864d993f', + }); + //缩小展示窗口 + $('.service_buy_view') + .width(690) + .height(350) + .css({ + //设置最外层弹窗大小 + left: (document.body.clientWidth - 690) / 2 + 'px', + top: (document.body.clientHeight - 350) / 2 + 'px', + }); + }); //检测支付状态 + } + ); //渲染二维码 + + function reader_applay_qcode(data, callback) { + order_wxoid = data.wxoid; + qq_info = data.qq; + order_info = data; + + $('#PayQcode').empty().qrcode({ + render: 'canvas', + width: 240, + height: 240, + text: data.wxcode, + }); + $('.price-txt .sale-price').html(data.price); + $('.lib-price-detailed .info:eq(0) span:eq(1)').html(data.name); + $('.lib-price-detailed .info:eq(1) span:eq(1)').html(data.time); + if (typeof data.qq != 'undefined') { + $('.order_pay_btn a:eq(0)').attr({ + href: data.qq, + target: '_blank', + }); + } else { + $('.order_pay_btn a:eq(0)').remove(); + } + if (callback) callback(data); + } + + function check_applay_status(callback) { + bt.send( + 'get_wx_order_status', + 'auth/get_wx_order_status', + { + wxoid: order_wxoid, + }, + function (res) { + if (res.status) { + is_check = false; + if (callback) callback(res); + } else { + if (!is_check) return false; + setTimeout(function () { + check_applay_status(callback); + }, 2000); + } + } + ); + } + }, + cancel: function (index) { + if (is_check) { + if (confirm('当前正在支付订单,是否取消?')) { + layer.close(index); + is_check = false; + } + return false; + } + }, + }); + } + /** + * @description 对指定表单元素的内容进行效验 + * @param {Object} el jqdom对象 + * @param {String} name 表单元素name名称 + * @param {*} value 表单元素的值 + * @returns 返回当前元素的值 + */ + function check_ssl_user_info(el, name, value, config) { + el.css('borderColor', '#ccc'); + var status; + switch (name) { + case 'domains': + el = site.domain_dns_type == 'multi' ? $('.dns_domains_multi_view') : el; + value = bt.strim(value).replace(/\n*$/, ''); + var list = value.split('\n'); + if (value == '') { + set_info_tips(el, { msg: 'The domain name cannot be empty!', color: 'red' }); + status = false; + } + if (!Array.isArray(list)) list = [list]; + $.each(list, function (index, item) { + if (bt.check_domain(item)) { + var type = item.indexOf(), + index = null; + if (config.code.indexOf('multi') > -1) index = 0; + if (config.code.indexOf('wildcard') > -1) index = 1; + if (config.code.indexOf('wildcard') > -1 && config.code.indexOf('multi') > -1) index = 2; + switch (index) { + case 0: + if (list.length > config.limit) { + set_info_tips(el, { msg: lan.site.set_ssl.more_cert_tip1(config.limit), color: 'red' }); + status = false; + } else if (list.length == 1) { + set_info_tips(el, { msg: lan.site.set_ssl.more_cert_tip2(config.limit), color: 'red' }); + status = false; + } + break; + case 1: + if (item.indexOf('*') != 0) { + set_info_tips(el, { msg: "Wildcard domain name format error, correct writing '*.bt.cn'", color: 'red' }); + status = false; + } + break; + case 2: + if (list.length > config.limit) { + set_info_tips(el, { msg: lan.site.set_ssl.more_cert_tip1(config.limit), color: 'red' }); + status = false; + } else if (list.length == 1) { + set_info_tips(el, { msg: lan.site.set_ssl.more_cert_tip2(config.limit), color: 'red' }); + status = false; + } + if (item.indexOf('*') != 0) { + set_info_tips(el, { msg: "Wildcard domain name format error, correct writing '*.bt.cn'", color: 'red' }); + status = false; + } + break; + } + } else { + if (value != '') { + set_info_tips(el, { msg: '【 ' + item + ' 】' + ',Domain name format error!', color: 'red' }); + } else { + set_info_tips(el, { msg: 'The domain name cannot be empty!', color: 'red' }); + } + status = false; + } + }); + value = list; + break; + case 'state': + if (value == '') { + set_info_tips(el, { msg: 'The province cannot be empty!', color: 'red' }); + status = false; + } + break; + case 'city': + if (value == '') { + set_info_tips(el, { msg: 'Your city/county cannot be empty!', color: 'red' }); + status = false; + } + break; + case 'city': + if (value == '') { + set_info_tips(el, { msg: 'Your city/county cannot be empty!', color: 'red' }); + status = false; + } + break; + case 'organation': + if (value == '') { + set_info_tips(el, { msg: 'The company name cannot be empty, if it is an individual application, please enter your name!', color: 'red' }); + status = false; + } + break; + case 'address': + if (value == '') { + set_info_tips(el, { msg: 'Please enter the company address, cannot be empty, specific requirements see the description', color: 'red' }); + status = false; + } + break; + case 'name': + if (value == '') { + set_info_tips(el, { msg: 'User name cannot be empty!', color: 'red' }); + status = false; + } + break; + case 'email': + if (value == '') { + set_info_tips(el, { msg: 'User email address cannot be empty!', color: 'red' }); + status = false; + } + if (!bt.check_email(value)) { + set_info_tips(el, { msg: 'User email address format error!', color: 'red' }); + status = false; + } + break; + // case 'mobile': + // if (value != '') { + // if (!bt.check_phone(value)) { + // set_info_tips(el, { msg: 'User mobile phone number format error!', color: 'red' }); + // status = false; + // } + // } + // break; + // case 'phonePre': + // if (value != '') { + // var reg = /^\+\d+/ + // if (!reg.test(value)) { + // set_info_tips(el, { msg: 'User mobile phone number format error!', color: 'red' }); + // status = false; + // } + // } + // break; + default: + status = value; + break; + } + if (typeof status == 'boolean' && status === false) return false; + status = value; + return status; + } + + /** + * @description 设置元素的提示和边框颜色 + * @param {Object} el jqdom对象 + * @param {Object} config = { + * @param {String} config.msg 提示内容 + * @param {String} config.color 提示颜色 + * } + */ + function set_info_tips(el, config) { + $('html').append($('' + config.msg + '')); + layer.tips(config.msg, el, { tips: [1, config.color], time: 3000 }); + el.css('borderColor', config.color); + $('#width_test').remove(); + } + /** + * @description 更换域名验证方式 + * @param {Number} oid 域名订单ID + * @returns void + */ + function again_verify_veiw(oid, is_success) { + var loads = bt.load('Please wait while obtaining verification method...'); + bt.send('get_verify_result', 'ssl/get_verify_result', { uc_id: oid }, function (res) { + loads.close(); + var type = res.data.dcvList[0].dcvMethod; + loadT = bt.open({ + type: 1, + title: lan.site.set_ssl.ver_file(type), + area: '520px', + btn: [lan.public.edit, lan.public.cancel], + content: + '
                                  Verification mode
                                  \ +
                                    ' + + lan.site.set_ssl.file_ver_tip + + '
                                  \ +
                                  ', + success: function (layero, index) { + var _option_list = { 'File Validation (HTTP)': 'HTTP_CSR_HASH', 'File Validation (HTTPS)': 'HTTPS_CSR_HASH', 'DNS Authentication (CNAME resolution)': 'CNAME_CSR_HASH' }, + _option = ''; + $.each(_option_list, function (index, item) { + _option += ''; + }); + $('select[name=file_rule]').html(_option); + }, + yes: function (index, layero) { + var new_type = $('select[name=file_rule]').val(); + if (type == new_type) return layer.msg('Duplicate authentication mode', { icon: 2 }); + var loads = bt.load('Changing the verification mode, please wait...'); + bt.send('again_verify', 'ssl/again_verify', { uc_id: oid, dcv_method: new_type }, function (res) { + loads.close(); + if (res.success) layer.close(index); + layer.msg(res.res, { icon: res.success ? 1 : 2 }); + }); + }, + }); + }); + } + /** + * @description 验证域名 + * @param {Number} oid 域名订单ID + * @param {Boolean} openTips 是否展示状态 + * @returns void + */ + function verify_order_veiw(oid, is_success, openTips) { + var loads = bt.load('Obtaining verification results, please wait...'); + bt.send('get_verify_result', 'ssl/get_verify_result', { uc_id: oid }, function (res) { + loads.close(); + if (!res.status) { + bt.msg(res); + return false; + } + if (res.status == 'COMPLETE') { + site.ssl.reload(); + return false; + } + var rdata = res.data; + var domains = [], + type = rdata.dcvList[0].dcvMethod != 'CNAME_CSR_HASH', + info = {}; + $.each(rdata.dcvList, function (key, item) { + domains.push(item['domainName']); + }); + if (type) { + info = { fileName: rdata.DCVfileName, fileContent: rdata.DCVfileContent, filePath: '/.well-known/pki-validation/', paths: res.paths, kfqq: res.kfqq }; + } else { + info = { dnsHost: rdata.DCVdnsHost, dnsType: rdata.DCVdnsType, dnsValue: rdata.DCVdnsValue, paths: res.paths, kfqq: res.kfqq }; + } + if (is_success) { + is_success({ type: type, domains: domains, info: info }); + return false; + } + loadT = bt.open({ + type: 1, + title: lan.site.set_ssl.ver_file(type), + area: '620px', + content: reader_domains_cname_check({ type: type, domains: domains, info: info }), + success: function (layero, index) { + //展示验证状态 + setTimeout(function () { + if (openTips && res.status == 'PENDING') layer.msg('Verification, please wait patiently', { time: 0, shadeClose: true, icon: 0, shade: 0.3 }); + }, 500); + var clipboard = new ClipboardJS('.parsing_info .parsing_icon'); + clipboard.on('success', function (e) { + bt.msg({ status: true, msg: 'Successful copy' }); + e.clearSelection(); + }); + clipboard.on('error', function (e) { + bt.msg({ status: true, msg: 'Copy failed, please manually ctrl+c copy!' }); + }); + $('.verify_ssl_domain').click(function () { + verify_order_veiw(oid, false, true); + layer.close(index); + }); + + $('.set_verify_type').click(function () { + again_verify_veiw(oid); + layer.close(index); + }); + + $('.return_ssl_list').click(function () { + layer.close(index); + $('#ssl_tabs span.on').click(); + }); + + // 重新验证按钮 + $('.domains_table').on('click', '.check_url_results', function () { + var _url = $(this).data('url'), + _con = $(this).data('content'); + check_url_txt(_url, _con, this); + }); + }, + }); + }); + } + + /** + * @description 重新验证 + * @param {String} url 验证地址 + * @param {String} content 验证内容 + * @returns 返回验证状态 + */ + function check_url_txt(url, content, _this) { + var loads = bt.load('Obtaining verification results, please wait...'); + bt.send('check_url_txt', 'ssl/check_url_txt', { url: url, content: content }, function (res) { + loads.close(); + var html = + 'fail[' + + res + + ']?'; + if (res === 1) { + html = 'pass'; + } + $(_this).parents('tr').find('td:nth-child(2)').html(html); + }); + } + /** + * @description 渲染验证模板接口 + * @param {Object} data 验证数据 + * @returns void + */ + function reader_domains_cname_check(data) { + var html = ''; + if (data.type) { + var check_html = + '
                                  '; + var paths = data.info.paths; + for (var i = 0; i < paths.length; i++) { + check_html += + ''; + } + check_html += '
                                  URL' + + lan.site.set_ssl.verification_result + + '' + + lan.public.action + + '
                                  ' + + paths[i].url + + '' + + (paths[i].status == 1 + ? 'pass' + : 'fail[' + + paths[i].status + + ']?') + + 'copy | open | reverify
                                  '; + html = + '
                                  \ +
                                  Please give the following domain name【 ' + + data.domains.join('、') + + ' 】Add a verification file. The verification information is as follows:
                                  \ +
                                  File location:
                                  \ +
                                  File name:
                                  copy
                                  \ +
                                  File content:
                                  copy
                                  ' + + check_html + + '
                                  · The verification result is verified by [this server], and the actual verification will be verified by [CA server]. Please wait patiently
                                  · Please ensure that all items in the above list are successfully verified and click [Verify domain name] to submit verification again
                                  · If the authentication fails for a long time, please change it to [DNS authentication] through [Modify Authentication method].
                                  · SSL Adds the file authentication mode ->> View the tutorial
                                  \ +
                                  \ +
                                  '; + } else { + html = + '
                                  \ +
                                  Please give the following domain name【 ' + + data.domains.join('、') + + ' 】add“' + + data.info.dnsType + + '”the parsing parameters as follows:
                                  \ +
                                  Host record:
                                  copy
                                  \ +
                                  Record type:
                                  \ +
                                  Record value:
                                  copy
                                  \ +
                                  · The verification result is verified by [this server], and the actual verification will be verified by [CA server]. Please wait patiently
                                  · Please ensure that all items in the above list are successfully verified and click [Verify domain name] to submit verification again
                                  · If the authentication fails for a long time, please change it to [DNS authentication] through [Modify Authentication method].
                                  · How to add domain name resolution,And consult the server operator
                                  · How do I verify commercial certificates?
                                  \ +
                                  \ +
                                  '; + } + return html; + } + // 购买证书信息 + function pay_ssl_business() { + var order_info = {}, + user_info = {}, + is_check = false; + var loadT = bt.load(lan.site.set_ssl.get_apply_cert); + bt.send('get_product_list_v2', 'ssl/get_product_list_v2', {}, function (rdata) { + loadT.close(); + var res = rdata.res; + var dataLength = res['data'] && res.data.length, + data_list = res.data, + list = [], + prompt_msg = res.info; + bt.open({ + type: 1, + title: lan.site.set_ssl.buy_cert, + area: ['810px', '832px'], + skin: 'layer-business-ssl', + content: + '\ +
                                  \ +
                                  \ + \ + ' + + lan.site.set_ssl.bus_cert_tip + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.domain_num + + '\ +
                                  \ +
                                  \ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ +

                                  ' + + lan.site.set_ssl.select_domain_num + + '

                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.cert_class + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.ov_cert + + '
                                  \ +
                                  ' + + lan.site.set_ssl.recom_enterprise + + '
                                  \ +
                                  \ +
                                  \ + Hot\ +
                                  ' + + lan.site.set_ssl.dv_cert + + '
                                  \ +
                                  ' + + lan.site.set_ssl.recom_pseson + + '
                                  \ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.ev_cert + + '
                                  \ +
                                  ' + + lan.site.set_ssl.recom_large + + '
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.cert_brand + + '\ +
                                  \ +
                                  \ +
                                  Positive
                                  \ +
                                  sslTrus
                                  \ +
                                  CFCA
                                  \ +
                                  Digicert
                                  \ +
                                  GeoTrust
                                  \ +
                                  Sectigo
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.cert_type + + '\ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.one_domain + + '
                                  \ +
                                  ' + + lan.site.set_ssl.more_domain + + '
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.pur_period + + '\ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.num_year_test(1) + + '
                                  \ +
                                  ' + + lan.site.set_ssl.num_year_test(2) + + '
                                  \ +
                                  ' + + lan.site.set_ssl.num_year_test(3) + + '
                                  \ +
                                  ' + + lan.site.set_ssl.num_year_test(4) + + '
                                  \ +
                                  ' + + lan.site.set_ssl.num_year_test(5) + + '
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.deploy_service + + '\ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.no_necess + + '
                                  \ +
                                  ' + + lan.site.set_ssl.deploy_service + + '
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.goods_include + + ':
                                  \ +
                                  ' + + lan.site.set_ssl.total_cost + + ':
                                  \ + $278.66/1year(' + + lan.site.set_ssl.in_service + + ')\ +
                                  \ +
                                  Original price$342/1year
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.wechat_pay + + '\ + ' + + lan.site.set_ssl.Alipay_pay + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.total + + '\ + $\ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.tar_name + + '\ + \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.order_time + + '\ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.wechat_swipe + + '\ +
                                  \ +
                                  \ +
                                  \ +
                                  ' + + lan.site.set_ssl.pay_sus + + '
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.tar_name + + '\ + \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.com_price + + '\ + \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.order_time + + '\ + \ +
                                  \ +
                                  \ + \ +
                                    \ +
                                  • ' + + lan.site.set_ssl.buy_cert_tip1 + + '
                                  • \ +
                                  \ +
                                  \ +
                                  \ +
                                  ', + //
                                  '+ lan.site.set_ssl.deploy_service +'
                                  \ + //
                                  '+ lan.site.set_ssl.service_secret +'
                                  \ + //
                                • 如果已购买人工服务,请点击“人工服务”咨询帮助。
                                • \ + success: function (layero, indexs) { + $.getScript('https://js.stripe.com/v3/'); + + var numBtn = $('.domain_number_reduce,.domain_number_add'), + ssl_type_item = $('.ssl_type_item'), + ssl_brand_item = $('.ssl_brand_item'), + ssl_service_item = $('.ssl_service_item'), + ssl_price_item = $('.ssl_price_item'), + ssl_year_item = $('.ssl_year_item'), + business_brand_list = $('.business_brand_list'), + input = $('.domain_number_input'), + ssl_type_tips = $('.ssl_type_tips'), + ssl_brand_tips = $('.ssl_brand_tips'), + ssl_year_tips = $('.ssl_year_tips'), + ssl_price_tips = $('.ssl_price_tips'), + ssl_service_unit = $('.ssl_service_unit'), + ssl_service_tips = $('.ssl_service_tips'); + var dataInfo = [], + ylist = [], + is_single = false, //是否存在单域名 + is_worldxml = false, //是否存在泛域名 + year = 1, + serviceprice = 0, + install = 0, + add_domain_number = 0, + order_id = null, + qq_info = null; + + $('.ssl-service').click(function () { + bt.onlineService(); + }); + + // 数量加减 + numBtn.click(function () { + var type = $(this).data('type'), + reduce = input.prev(), + add = input.next(), + min = 1, + max = 99, + input_val = parseInt(input.val()); + if ($(this).hasClass('is_disable')) { + layer.msg(type === 'reduce' ? 'The number of current domain names cannot be 0' : 'The number of domain names cannot be greater than 99'); + return false; + } + switch (type) { + case 'reduce': + input_val--; + if (min > input_val < max) { + input.val(min); + } + break; + case 'add': + input_val++; + if (min > input_val < max) { + input.val(input_val); + add.removeClass('is_disable'); + } + if (input_val == max) $(this).addClass('is_disable'); + break; + } + if (input_val == min) { + reduce.addClass('is_disable'); + } else if (input.val() == max) { + add.addClass('is_disable'); + } else { + reduce.removeClass('is_disable'); + add.removeClass('is_disable'); + } + + reader_product_info({ current_num: parseInt(input_val) }); + }); + $('.domain_number_input').on('input', function () { + var _input = $(this), + input_val = parseInt(_input.val()), + input_min = 1, + input_max = 99, + reduce = _input.prev(), + add = _input.next(); + if (input_val <= input_min) { + _input.val(input_min); + reduce.addClass('is_disable'); + } else if (input_val >= input_max) { + input.val(input_max); + add.addClass('is_disable'); + } else { + reduce.removeClass('is_disable'); + add.removeClass('is_disable'); + } + if (_input.val() == '') { + _input.val(input_min); + input_val = input_min; + reduce.addClass('is_disable'); + } + reader_product_info({ current_num: parseInt(_input.val()) }); + }); + + function automatic_msg() { + layer.msg('The current certificate brand does not support multi-domain certificate, has automatically switched to the supported certificate brand for you!'); + } + + //总计费用信息 + function reader_product_info(config) { + config.current_num = config.current_num !== '' ? config.current_num : 1; + add_domain_number = config.current_num; + input.val(config.current_num !== '' ? config.current_num : 1); + var p_index = $('.ssl_price_item.active').index(); //证书类型下标 + var year_list = ylist.filter(function (s) { + return p_index ? s.code.indexOf('wildcard') > -1 : s.code.indexOf('wildcard') === -1; + }); + var is_flag = year_list.some(function (s) { + return s.code.indexOf('multi') > -1 || s.brand === 'Digicert'; + }); + if (p_index) { + ssl_type_item.eq(2).addClass('disabled'); + } else { + ssl_type_item.eq(2).removeClass('disabled'); + } + if (input.val() > 1) { + // disabled + //证书类型禁用 + var is_type_disabled = ylist + .filter(function (s) { + return !p_index ? s.code.indexOf('wildcard') > -1 : s.code.indexOf('wildcard') === -1; + }) + .some(function (s) { + return s.code.indexOf('multi') > -1 || s.brand === 'Digicert'; + }); + if (!is_type_disabled) { + ssl_price_item.eq(p_index ? 0 : 1).addClass('disabled'); + } else { + ssl_price_item.eq(p_index ? 0 : 1).removeClass('disabled'); + } + //证书品牌禁用 + for (var i = 0; i < ssl_brand_item.length; i++) { + if (ssl_brand_item.eq(i).css('display') !== 'none') { + var brand_data = ssl_brand_item.eq(i).data(); + if (p_index) { + if (!brand_data.is_multi_w) ssl_brand_item.eq(i).addClass('disabled'); + else ssl_brand_item.eq(i).removeClass('disabled'); + } else { + if (!brand_data.is_multi) ssl_brand_item.eq(i).addClass('disabled'); + else ssl_brand_item.eq(i).removeClass('disabled'); + } + } + } + } else { + ssl_price_item.eq(p_index ? 0 : 1).removeClass('disabled'); + ssl_brand_item.removeClass('disabled'); + } + if (!is_flag) { + if (input.val() > 1) { + for (var i = 0; i < ssl_brand_item.length; i++) { + if (ssl_brand_item.eq(i).css('display') !== 'none') { + var brand_data = ssl_brand_item.eq(i).data(); + if (p_index) { + if (brand_data.is_multi_w) { + automatic_msg(); + return ssl_brand_item.eq(i).click(); + } + } else { + if (brand_data.is_multi) { + automatic_msg(); + return ssl_brand_item.eq(i).click(); + } + } + } + } + } + } + //选中的证书信息 + var data_info = year_list.filter(function (s) { + return $('.domain_number_input').val() > 1 ? (s.brand === 'Digicert' ? s.code.indexOf('multi') === -1 : s.code.indexOf('multi') > -1) : s.code.indexOf('multi') === -1; + })[0]; + dataInfo = data_info; + dataInfo['current_num'] = config['current_num']; + //服务费 + var service_price = [0, data_info.deploy_price / 100 || 0, data_info.install_price_v2 || 0]; + for (var i = 0; i < service_price.length; i++) { + ssl_service_item.eq(i).data('serviceprice', service_price[i]); + // if(i == 1){ + // ssl_service_item.eq(i).data('serviceprice', 28.9); + // } + } + serviceprice = $('.ssl_service_item.active').data('serviceprice'); + if ($('.ssl_service_item.active').index()) ssl_service_unit.html('' + lan.site.set_ssl.deploy_cost + '$' + serviceprice + '/1 Time'); + else ssl_service_unit.html(''); + var cur_num = (config.current_num < data_info.num ? data_info.num : config.current_num) - data_info.num; + var p_price = parseFloat(Number(serviceprice) * 100 + (data_info.price + data_info.add_price * cur_num) * year).toFixed(2); + if (config.current_num > 1 || data_info.brand === 'Digicert') { + if (data_info.brand !== 'Digicert') ssl_price_item.eq(0).text('Universal domain'); + if (data_info.brand === 'Digicert') ssl_price_item.eq(0).text('Single domain'); + $('.domain_number_tips').html(lan.site.set_ssl.default_over(data_info.num) + '$' + (data_info.add_price / 100).toFixed(2) + '/one/year'); + } else { + ssl_price_item.eq(0).text('Single domain'); + $('.domain_number_tips').empty(); + } + var pp_html = '$' + Number(p_price) / 100 + '/' + year + 'year' + ($('.ssl_service_item.active').index() ? '(' + lan.site.set_ssl.in_service + ')' : ''), + op_html = + lan.site.set_ssl.or_price + + '$' + + parseFloat(parseFloat((Number(serviceprice) * 100 + (data_info.other_price + data_info.add_price * cur_num) * year) / 100).toFixed(2)) + + '/' + + year + + 'year'; + var price_pack = parseFloat(parseFloat(data_info.price * year).toFixed(2)), + price_extra = data_info.add_price * cur_num * year; + $('.business_ssl_btn .bname span').html( + lan.site.set_ssl.default_domain(data_info.num) + + '$' + + price_pack / 100 + + '/' + + year + + 'year' + + (cur_num ? lan.site.set_ssl.over_domain(cur_num) + '$' + price_extra / 100 + '/' + year + 'year' : '') + ); + $('.business_ssl_btn .present_price').html(pp_html); + $('.business_ssl_btn .original_price').html(op_html); + } + + setTimeout(function () { + ssl_type_item.eq(1).click(); + }, 50); + //证书分类切换 + ssl_type_item.click(function () { + if ($(this).hasClass('disabled')) return layer.msg(lan.site.set_ssl.dis_tip); + if (!$(this).hasClass('active')) $(this).addClass('active').siblings().removeClass('active'); + //证书类型 + var type = $(this).data('type'), + brand_list = []; //品牌类型 + list = dataLength + ? data_list.filter(function (s) { + return s.type.indexOf(type) > -1; + }) + : []; + var type_tips_list = prompt_msg['type'][type.toLowerCase()], + type_tips = ''; + for (var i = 0; i < type_tips_list.length; i++) { + type_tips += '

                                  ' + type_tips_list[i] + '

                                  '; + } + ssl_type_tips.html(type_tips); //提示信息 + $.each(list, function (i, item) { + brand_list.push(item.brand); + }); + brand_list = Array.from(new Set(brand_list)); //去重 + var recommend = prompt_msg['recommend'][type.toLowerCase()]; + business_brand_list.find('em').remove(); + business_brand_list.find('[data-type="' + prompt_msg['recommend'][type.toLowerCase()] + '"]').prepend('Hot'); + ssl_brand_item.hide(); + for (var i = 0; i < brand_list.length; i++) { + business_brand_list.find('[data-type="' + brand_list[i] + '"]').show(); + var b_list = list.filter(function (s) { + return s.brand === brand_list[i]; + }); + //品牌是否存在多域名 + var is_multi = b_list.some(function (s) { + return (s.code.indexOf('wildcard') === -1 && s.code.indexOf('multi') > -1) || s.brand === 'Digicert'; + }); + var is_multi_w = b_list.some(function (s) { + return (s.code.indexOf('wildcard') > -1 && s.code.indexOf('multi') > -1) || s.brand === 'Digicert'; + }); + business_brand_list.find('[data-type="' + brand_list[i] + '"]').data({ is_multi: is_multi, is_multi_w: is_multi_w }); + } + business_brand_list.find('[data-type="' + recommend + '"]').click(); + }); + + //证书品牌 + ssl_brand_item.click(function () { + var p_index = $('.ssl_price_item.active').index(); + if ($(this).hasClass('disabled')) { + if (p_index !== -1) { + for (var i = 0; i < ssl_brand_item.length; i++) { + if (ssl_brand_item.eq(i).css('display') !== 'none') { + var brand_data = ssl_brand_item.eq(i).data(); + if (p_index) { + if (brand_data.is_multi_w) return ssl_brand_item.eq(i).click(); + } else { + if (brand_data.is_multi) return ssl_brand_item.eq(i).click(); + } + } + } + } + return layer.msg('The current certificate brand does not support multi-domain wildcard certificates. Please select another brand certificate'); + } + if (!$(this).hasClass('active')) $(this).addClass('active').siblings().removeClass('active'); + var type = $(this).data('type'), + years_list = [], + max_years = 0, + years_html = '', + cert_html = ''; + ylist = list.filter(function (s) { + return s.brand.indexOf(type) > -1; + }); + brand_type = $('.ssl_type_item.active').data('type'); + var brand_tips_list = prompt_msg['brand'][type === 'Positive' || type === 'sslTrus' ? type : type.toLowerCase()], + brand_tips = ''; + for (var i = 0; i < brand_tips_list.length; i++) { + brand_tips += '

                                  ' + brand_tips_list[i] + '

                                  '; + } + ssl_brand_tips.html(brand_tips); //提示信息 + $.each(ylist, function (i, item) { + years_list.push(item.max_years); + }); + //是否存在单域名/泛域名按钮 + is_single = ylist.some(function (s) { + return s.code.indexOf('wildcard') === -1; + }); + is_worldxml = ylist.some(function (s) { + return s.code.indexOf('wildcard') > -1; + }); + if (is_single) { + ssl_price_item.eq(0).show(); + } else { + ssl_price_item.eq(0).hide(); + } + if (is_worldxml) { + ssl_price_item.eq(1).show(); + } else { + ssl_price_item.eq(1).hide(); + } + max_years = Array.from(new Set(years_list))[0]; + for (var i = 0; i < 5; i++) { + if (i < max_years) { + ssl_year_item.eq(i).show(); + } else { + ssl_year_item.eq(i).hide(); + } + } + //证书类型点击 + var p_index = $('.ssl_price_item.active').index(); + ssl_price_item.eq(p_index !== -1 ? (!is_worldxml && p_index === 1 ? 0 : p_index) : 0).click(); + }); + //证书类型 + $('.business_ssl_form').on('click', '.ssl_price_item', function () { + if ($(this).hasClass('disabled')) return layer.msg(lan.site.set_ssl.dis_tip); + if (!$(this).hasClass('active')) $(this).addClass('active').siblings().removeClass('active'); + var price_tips_list_0 = [lan.site.set_ssl.single_tip1, lan.site.set_ssl.single_tip2], + price_tips_list_1 = [lan.site.set_ssl.more_tip1, lan.site.set_ssl.more_tip2]; + var price_tips_list = $(this).index() ? price_tips_list_1 : price_tips_list_0, + price_tips = ''; + for (var i = 0; i < price_tips_list.length; i++) { + price_tips += '

                                  ' + price_tips_list[i] + '

                                  '; + } + ssl_price_tips.html(price_tips); + //购买年限点击 + var y_index = $('.ssl_year_item.active').index(); + + ssl_year_item.eq(y_index !== -1 && $('.ssl_year_item.active').css('display') !== 'none' ? y_index : 0).click(); + }); + //购买年限 + $('.business_ssl_form').on('click', '.ssl_year_item', function () { + if (!$(this).hasClass('active')) $(this).addClass('active').siblings().removeClass('active'); + year = $(this).data('year'); + var year_tips_list = prompt_msg['times'][year + '_year'], + year_tips = ''; + for (var i = 0; i < year_tips_list.length; i++) { + year_tips += '

                                  ' + year_tips_list[i] + '

                                  '; + } + ssl_year_tips.html(year_tips); //提示信息 + + var ser_index = $('.ssl_service_item.active').index(); + ssl_service_item.eq(ser_index !== -1 ? ser_index : 1).click(); + }); + + //部署服务点击 + ssl_service_item.click(function () { + if (!$(this).hasClass('active')) $(this).addClass('active').siblings().removeClass('active'); + var index = $(this).index(); + (serviceprice = $(this).data('serviceprice')), (install = $(this).data('install')); + ssl_service_tips.html( + index + ? index === 1 + ? 'aaPanel provides manual deployment certificate deployment services from China time 9:00 -18:30 to help customers troubleshoot deployment certificate validity problems and quickly go online' + : '宝塔提供9:00 - 24:00的人工部署国密算法证书部署服务,帮助客户排查部署证书部署生效问题,快速上线' + : '' + ); + var value = $('.domain_number_input').val(); + reader_product_info({ current_num: value === '' ? value : parseInt(value) }); + }); + + //购买事件 + $('.business_ssl_pay').click(function () { + var loadT = bt.load('Payment order is being generated, please wait...'), + num = 0; + add_domain_number = input.val(); + if (dataInfo.add_price !== 0) num = parseInt(dataInfo.current_num - dataInfo.num); + bt.send( + 'apply_cert_order_pay', + 'ssl/apply_cert_order_pay', + { + pdata: JSON.stringify({ + pid: dataInfo.pid, + deploy: install, + years: year, + num: num, + }), + }, + function (rdata) { + loadT.close(); + if (rdata.success) { + is_check = true; + var res = rdata.res; + + var stripe = Stripe(res.stripe_public_key); + stripe.redirectToCheckout({ sessionId: res.session_id }); + } + } + ); + }); + //支付切换 + $('.guide_nav span').click(function () { + var price = $('.business_ssl_btn .present_price span').text(), + is_wx_quota = parseFloat(price) >= 6000; + if ($(this).index() === 0 && is_wx_quota) { + layer.msg('Wechat single transaction limit 6000 yuan, please use Alipay payment', { + icon: 0, + }); + } else { + $(this).addClass('active').siblings().removeClass('active'); + $('.lib-prompt span').html($(this).index() == 0 ? lan.site.set_ssl.wechat_swipe : 'Pay with a swipe on Alipay'); + $('#PayQcode').empty(); + $('#PayQcode').qrcode({ + render: 'canvas', + width: 200, + height: 200, + text: $(this).index() != 0 ? order_info.alicode : order_info.wxcode, + }); + } + }); + $('.order_pay_btn a').click(function () { + switch ($(this).data('type')) { + case 'info': + confirm_certificate_info( + $.extend(dataInfo, { + oid: order_id, + qq: qq_info, + install: install ? true : false, + limit: add_domain_number, + }) + ); + break; + case 'clear': + layer.close(indexs); + break; + } + }); + }, + cancel: function (index) { + if (is_check) { + if (confirm('The order is currently being paid, would you like to cancel it?')) { + layer.close(index); + is_check = false; + } + return false; + } + }, + }); + }); + } + // 确认证书信息 + function confirm_certificate_info(config) { + var userLoad = bt.load('Getting user info, please wait...'); + bt.send('get_cert_admin', 'ssl/get_cert_admin', {}, function (rdata) { + var res = rdata.res; + userLoad.close(); + var html = ''; + var isWildcard = config.code.indexOf('wildcard') > -1; + var isMulti = config.code.indexOf('multi') > -1; + if (typeof pay_ssl_layer != 'undefined') pay_ssl_layer.close(); + if (config.code.indexOf('multi') > -1) { + if (isWildcard) { + placeholder = lan.site.set_ssl.more_cert_pl1(config.limit); + } else { + placeholder = lan.site.set_ssl.more_cert_pl2(config.limit); + } + site.domain_dns_type = 'multi'; + html = + '
                                  Select a website domain name
                                  '; + } else { + if (isWildcard) { + placeholder = 'single domain wildcard certificate, for example, *.bt.cn'; + } else { + placeholder = 'single domain name certificate, for example, www.bt.cn'; + } + site.domain_dns_type = 'one'; + html = + '
                                  '; + } + bt.open({ + type: 1, + title: lan.site.set_ssl.inpro_cert_info, + area: '640px', + content: + '
                                  \ +
                                  \ + Cert Info\ +
                                  \ + ' + + config.title + + (config.limit > 1 ? ',Contains ' + config.limit + 'domain names' : '') + + '\ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.domain_name + + '\ +
                                  ' + + html + + '
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.checking_mode + + '\ +
                                  \ +
                                  \ + \ + \ + \ +
                                  \ +
                                  \ + \ + \ + \ +
                                  \ +
                                  \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + Created with Pixso.\ + \ + \ + \ + \ +
                                  \ + Show user Info\ + \ +
                                  \ +
                                  \ + Local area\ +
                                  \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.site.set_ssl.address + + '\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + Company name\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + Name\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ + Email\ +
                                  \ + \ +
                                  \ +
                                  \ +
                                  \ + ' + + lan.public_backup.mobile_phone_or_email + + '\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                    \ +
                                  • Wildcard certificates support only DNS authentication
                                  • \ +
                                  • Multiple domain names support only DNS authentication
                                  • \ +
                                  • https or http authentication: Ensure that the website can be accessed through http/https
                                  • \ +
                                  • The domain name prefix is www, reminding users to resolve the upper-level root domain name, such as www.bt.cn, please ensure that the resolution of bt.cn
                                  • \ +
                                  • How do I verify commercial certificates?
                                  • \ +
                                  \ +
                                    \ +
                                  • OV/EV certificate application process conditions:
                                  • \ +
                                  • 1、Fill in the website authentication information (file authentication or DNS authentication)
                                  • \ +
                                  • 2、Complete the email authentication, and improve the email content according to the mail sent by CA (just fill in Chinese)
                                  • \ +
                                  • 3、Enterprise check or love enterprise check, Baidu map, 114best can query relevant enterprise information, and the company name and company address exactly match
                                  • \ +
                                  • 4、The phone number left by the company or other platforms can guarantee that you can hear the CA certification phone from Monday to Friday (7:00-15:00), the phone number belongs to the United States, please pay attention to answer.
                                  • \ +
                                  \ +
                                  ', + check_dns_interface: function (callback) { + var val = $('input[name="dcvMethod"]:radio:checked').val(); + if (val !== 'CNAME_CSR_HASH') { + if (callback) callback(); + return; + } + var dns_val = $('.dns_interface_select').val(); + if (dns_val == 'dns') { + if (callback) callback(); + } else { + bt.site.get_dns_api(function (res) { + var config; + for (var i = 0; i < res.length; i++) { + if (res[i].name == dns_val) { + config = res[i]; + break; + } + } + var check = true; + var title = ''; + if (config && config.data) { + for (var j = 0; j < config.data.length; j++) { + if (config.data[j].value === '') { + check = false; + title = config.title; + break; + } + } + } + if (check) { + if (callback) callback(); + } else { + layer.msg('No key is configured for the selected DNS interface [' + title + ']', { icon: 2 }); + } + }); + } + }, + success: function (layero, index) { + $.ajax({ + type: 'GET', + url: '/static/js/countryCode.json', + data: 'data', + dataType: 'JSON', + success: function (data) { + countryList = data; + var _option = ''; + var couOp = ''; + $.each(data, function (index, item) { + _option += ''; + couOp += ''; + }); + var node_pid = $('.pre[name=phonePre]'); + var countrySelect = $('.cou[name=country]'); + node_pid.html(_option); + node_pid.val(res.tel_prefix); + countrySelect.html(couOp); + countrySelect.val(res.country); + }, + }); + $('.basics-clid').hide(); + if (config.code.indexOf('multi') > -1) { + $('#CNAME_CSR_HASH').click(); + $('#HTTP_CSR_HASH,#HTTPS_CSR_HASH').attr('disabled', 'disabled'); + + // dns_domains_add_block下的input焦点触发时,隐藏selct_site_btn + $('.dns_domains_add_block input').on('focus blur keyup', function (e) { + e = e || window.event; + switch (e.type) { + case 'focus': + $('.selct_site_btn').hide(); + break; + case 'blur': + $('.selct_site_btn').css('display', 'inline-block'); + break; + case 'keyup': + if (e.keyCode != 13 && e.type == 'keyup') return false; + var val = $(this).val(); + if (!bt.check_domain(val)) return layer.msg('Domain name format error', { icon: 2 }); + site.domain_dns_list.push(val); + $(this).val(''); // 清空输入框 + site.refresh_dns_interface(); + break; + } + }); + } + $('.perfect_ssl_info').data('code', config.code); + var _this_layer = this; + bt_tools.send({ url: '/site?action=GetDnsApi' }, function (data) { + site.dns_configured_table = site.data_treating(data); + site.add_dns_interface(); + // 基础信息隐藏显示 + $('.basics-info').click(function () { + if ($('.basics-info').hasClass('active')) { + $('.basics-info').removeClass('active'); + $('.basics-title').text('Show user Info'); + $('.basics-clid').hide(); + } else { + $('.basics-info').addClass('active'); + $('.basics-title').text('Hide user Info'); + $('.basics-clid').show(); + //config.code.含有ov、ev时,隐藏公司详细地址 + // if(config.code.indexOf('ov') == -1 || config.code.indexOf('ev') == -1){ + // $('.basics-clid').eq(1).hide(); + // } + } + $('.ssl_help_info').toggle(); + }); + // 判断基础信息中是否存在空置,将基础信息自动显示 + $('.basics-clid input').each(function () { + // isMulti为false时,不需要验证公司详细地址 + if (!isMulti && $(this).attr('name') == 'address') return true; + if ($(this).val() == '') { + $('.basics-info').click(); + return false; + } + }); + + // 验证方式 + $('input[name="dcvMethod"]').change(function () { + var val = $(this).val(); + if (val == 'CNAME_CSR_HASH') { + site.add_dns_interface(); + } else { + site.remove_dns_interface(); + } + }); + + // 公司详细地址联动 + $('.perfect_ssl_info').on('input', 'input[name="state"], input[name="city"]', function (e) { + var is_ovev = config.code.indexOf('ov') > -1 || config.code.indexOf('ev') > -1; + if (!is_ovev) { + var state = $('.perfect_ssl_info input[name="state"]').val(); + var city = $('.perfect_ssl_info input[name="city"]').val(); + $('.perfect_ssl_info input[name="address"]').val(state + city); + } + }); + $('.perfect_ssl_info') + .on('focus', 'input[type=text],textarea', function () { + $(this).focus() + // var placeholder = $(this).attr('placeholder'); + // $('html').append($('' + placeholder + '')); + // $(this).attr('data-placeholder', placeholder); + // layer.tips(placeholder, $(this), { tips: [1, '#20a53a'], time: 0 }); + // $(this).attr('placeholder', ''); + // $('#width_test').remove(); + }) + .on('blur', 'input[type=text],textarea', function () { + var name = $(this).attr('name'), + val = $(this).val(); + layer.closeAll('tips'); + $(this).attr('placeholder', $(this).attr('data-placeholder')); + check_ssl_user_info($(this), name, val, config); + }); + function btserializeDiv(div) { + var result = {}; + var elements = div.querySelectorAll('input, select, textarea'); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + var name = element.name; + var value = element.value; + if (name && !element.disabled) { + result[name] = value; + } + } + return result; + } + $('.submit_ssl_info').on('click', function () { + var data = {}, + // form = $('.perfect_ssl_info').serializeObject(), + form = btserializeDiv(document.querySelector('.perfect_ssl_info')), + is_ovev = config.code.indexOf('ov') > -1 || config.code.indexOf('ev') > -1, + loadT = null; + // var reg = /^[\u4E00-\u9FA5]+$/; + // if (form.name.length < 2 || !reg.test(form.name)) return layer.msg('The name shall be Chinese and two characters or more in length'); + $('.perfect_ssl_info') + .find('input,textarea') + .each(function () { + var name = $(this).attr('name'), + value = $(this).val(), + value = check_ssl_user_info($(this), name, value, config); + if (typeof value === 'boolean') { + form = false; + return false; + } + form[name] = value; + }); + form.phonePre = $('.perfect_ssl_info [name=phonePre]').val(); + form.phonePre = $('.perfect_ssl_info [name=phonePre]').val(); + if (typeof form == 'boolean') return false; + delete form['undefined']; // 删除undefined + form['domains'] = site.domain_dns_list; + + if (!is_ovev) form['address'] = form['state'] + form['city']; + if (typeof config.limit == 'undefined') config.limit = config.num; + if (form.domains.length < config.limit) { + bt.confirm({ title: 'Tips', msg: 'The current certificate supports ' + config.limit + ' domain names. Do you want to continue to add domain names?' }, function () { + req(true); + }); + return false; + } + req(true); + function req(verify) { + if (verify) { + bt.open({ + title: 'The user information is confirmed twice', + area: ['600px'], + btn: ['Continue to submit', 'Cancel'], + content: + '
                                  ' + + '
                                  ' + + 'Local area' + + '
                                  ' + + '' + + '' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + 'Address' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + 'Company name' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + 'Name' + + '
                                  ' + + '' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + 'Email' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + lan.public_backup.mobile_phone_or_email + + '' + + '
                                  ' + + '' + + '' + + '
                                  ' + + '
                                  ' + + '
                                ' + + '
                                ', + yes: function () { + var isVerify = true; + $('.certificate_confirm') + .find('input') + .each(function () { + var name = $(this).attr('name'), + value = $(this).val(), + value = check_ssl_user_info($(this), name, value, config); + if (typeof value === 'boolean') { + form = false; + return false; + } + form[name] = value; + }); + req(false); + }, + success: function () { + if (countryList.length > 0) { + var _option = ''; + var couOp = ''; + $.each(countryList, function (index, item) { + _option += ''; + couOp += ''; + }); + var node_pid = $('[name=phonePre]'); + var countrySelect = $('[name=country]'); + node_pid.html(_option); + node_pid.val(form.phonePre); + countrySelect.html(couOp); + countrySelect.val(form.country); + } + // $.ajax({ + // type: "GET", + // url: "/static/js/countryCode.json", + // data: "data", + // dataType: "JSON", + // success: function (data) { + // console.log(data); + + // } + // }) + $('.certificate_confirm [name="organation"]').change(function () { + $('.perfect_ssl_info [name="organation"]').val($(this).val()); + form.organation = $(this).val(); + }); + $('.certificate_confirm [name="address"]').change(function () { + $('.perfect_ssl_info [name="address"]').val($(this).val()); + form.address = $(this).val(); + }); + $('.checkInfo').on('click', function (e) { + window.open('https://www.qcc.com/web/search?key=' + $('.certificate_confirm [name="organation"]').val()); + }); + }, + }); + return false; + } + _this_layer.check_dns_interface(function () { + var loadT = bt.load('Please wait while submitting certificate information...'); + var auth_to = $("[name='dns_select']") ? $("[name='dns_select']").val() : ''; + bt.send( + 'apply_order_ca', + 'ssl/apply_order_ca', + { + pdata: JSON.stringify({ + pid: config.pid, + oid: config.oid, + domains: form.domains, + dcvMethod: $("[name='dcvMethod']:checked").val(), + auth_to: auth_to, + uc_id: config.uc_id, + Administrator: { + job: 'General affairs', + postCode: '523000', + country: form.country, + firstName: form.firstName, + lastName: form.name, + state: form.state, + city: form.city, + address: form.address, + organation: form.organation, + email: form.email, + tel_prefix: form.phonePre, + mobile: form.mobile, + lastName: form.name, + }, + }), + }, + function (res) { + loadT.close(); + if (typeof res.msg == 'object') { + for (var key in res.msg.errors) { + if (Object.hasOwnProperty.call(res.msg.errors, key)) { + var element = res.msg.errors[key]; + bt.msg({ + status: false, + msg: element, + }); + } + } + } else { + if (res.caa_list) { + site.show_domain_error_dialog(res.caa_list, res.msg); + } else { + bt.msg({ status: res.success, msg: res.res }); + } + } + if (res.success) { + layer.close(index); + verify_order_veiw(config.uc_id); + $('#ssl_tabs span.on').click(); + } + } + ); + }); + } + }); + + $('.check_method_item label').click(function (e) { + e.stopPropagation(); + }); + + $('.check_method_item').click(function () { + // 选中 + $(this).find('label').trigger('click'); + // 判断是否显示异常 + var show = $(this).data('show-tips'); + if (!show) return; + $(this).data('show-tips', false); + // 判断是否存在异常数据 + var data = $(this).data('error-data'); + if (!data) return; + $(this).find('.error-link').trigger('click'); + }); + + $('.check_method_item').on('click', '.error-link', function (e) { + e.stopPropagation(); + var data = $(this).parents('.check_method_item').data('error-data'); + + if ($.isPlainObject(data)) { + site.show_domain_error_dialog(data); + } + if (Array.isArray(data)) { + var html = ''; + $.each(data, function (i, item) { + html += '

                                ' + item + '

                                '; + }); + layer.msg(html, { + icon: 2, + shade: 0.3, + closeBtn: 2, + time: 0, + success: function ($layer) { + $layer.css({ 'max-width': '560px' }); + var width = $(window).width(); + var lWidth = $layer.width(); + $layer.css({ + left: (width - lWidth) / 2 + 'px', + }); + }, + }); + } + }); + + var Timer = null; + $('.CNAME_CSR_HASH,.HTTP_CSR_HASH,.HTTPS_CSR_HASH').hover( + function () { + var $this = $(this); + var data = $(this).data('error-data'); + if (data) return; + var arry = [ + 'If the website has not been filed, optional [DNS verification]', + 'If the 301, 302, forced HTTPS, and reverse proxy functions are not enabled, select HTTP', + 'If the website enables "mandatory HTTPS", please select "HTTPS verification".', + ]; + var tips = arry[$this.index()]; + clearTimeout(Timer); + Timer = setTimeout(function () { + $this.data({ + tips: layer.tips(tips, $this.find('label'), { tips: 1, time: 0 }), + }); + }, 200); + }, + function () { + clearTimeout(Timer); + layer.close($(this).data('tips')); + } + ); + }); + }, + }); + }); + } + $('.ssl_business_application').click(function () { + pay_ssl_business(); + }); + //订单证书操作 + $('.ssl_order_list') + .unbind('click') + .on('click', '.options_ssl', function () { + var type = $(this).data('type'), + tr = $(this).parents('tr'); + itemData = order_list[tr.data('index')]; + switch (type) { + case 'deploy_ssl': // 部署证书 + bt.confirm( + { + title: 'Deployment certificate', + msg: + 'Whether to deploy the certificate and whether to continue?
                                Certificate type:' + + itemData.title + + '
                                Certificate supported domain name:' + + itemData.domains.join('、') + + '
                                Deployment site name:' + + web.name + + '', + }, + function (index) { + var loads = bt.load('Please wait while certificates are deployed...'); + bt.send('set_cert', 'ssl/set_cert', { uc_id: itemData.uc_id, siteName: web.name }, function (rdata) { + layer.close(index); + $('#webedit-con').empty(); + site.set_ssl(site.web); + site.ssl.reload(); + bt.msg(rdata); + }); + } + ); + break; + case 'verify_order': // 验证订单 + verify_order_veiw(itemData.uc_id); + break; + case 'clear_order': // 取消订单 + bt.confirm( + { + title: 'Cancel order', + msg: 'Whether to cancel the order, the order domain name [' + itemData.domains.join('、') + '], whether to continue?', + }, + function (index) { + var loads = bt.load('Cancelling order, please wait...'); + bt.send('cancel_cert_order', 'ssl/cancel_cert_order', { oid: itemData.oid }, function (rdata) { + layer.close(index); + if (rdata.status) { + $('#ssl_tabs span:eq(2)').click(); + setTimeout(function () { + bt.msg(rdata); + }, 2000); + } + bt.msg(rdata); + }); + } + ); + break; + case 'perfect_user_info': //完善用户信息 + confirm_certificate_info(itemData); + break; + case 'renewal_ssl': + renewal_ssl_view(itemData); + break; + } + }); + } else { + robj.append('
                                ' + lan.site.set_ssl.no_bind + '
                                '); + var datas = [ + { title: lan.public.user, name: 'bt_username', value: '', width: '260px', placeholder: lan.public_backup.mobile_phone_or_email }, + { title: lan.public.pass, type: 'password', name: 'bt_password', value: '', width: '260px' }, + { + title: ' ', + items: [ + { + text: lan.public_backup.login, + name: 'btn_ssl_login', + type: 'button', + callback: function (sdata) { + var username = $('input[name="bt_username"]').val(); + var password = $('input[name="bt_password"]').val(); + bt.pub.login_btname(username, password, function (ret) { + if (ret.status) site.set_ssl(site.web); + }); + }, + }, + { + text: lan.site.set_ssl.register_ac, + name: 'bt_register', + type: 'button', + callback: function (sdata) { + window.open('https://www.aapanel.com/user_admin/register'); + }, + }, + ], + }, + ]; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + robj.append( + bt.render_help([ + lan.site.set_ssl.bind_tip1 + '' + lan.site.set_ssl.click_view + '', + lan.site.set_ssl.bind_tip2, + ]) + ); + } + }); + }, + }, + { + title: "Let's Encrypt", + callback: function (robj) { + robj = $('#webedit-con .tab-con'); + // console.log(robj,'obj'); + acme.get_account_info(function (let_user) {}); + acme.id = web.id; + if (rdata.status && rdata.type == 1) { + var cert_info = ''; + if (rdata.cert_data['notBefore']) { + cert_info = + '
                                \ + ' + + lan.site.deploy_success_cret + + '' + + lan.site.try_renew_cret + + '\ + \ + ' + + lan.site.cert_brand + + '' + + rdata.cert_data.issuer + + '\ + ' + + lan.site.auth_domain + + ' ' + + (rdata.cert_data.dns ? rdata.cert_data.dns.join(', ') : '') + + '\ + ' + + lan.site.expire_time + + ' ' + + rdata.cert_data.notAfter + + '
                                '; + } + robj.append('
                                ' + cert_info + '
                                ' + lan.site.ssl_key + '' + lan.site.ssl_crt + '
                                '); + var datas = [ + { + items: [ + { name: 'key', width: '48%', height: '220px', type: 'textarea', value: rdata.key }, + { name: 'csr', width: '48%', height: '220px', type: 'textarea', value: rdata.csr }, + ], + }, + { + items: [ + { + text: lan.site.ssl_close, + name: 'btn_ssl_close', + hide: !rdata.status, + type: 'button', + callback: function (sdata) { + site.ssl.set_ssl_status('CloseSSLConf', web.name); + }, + }, + { + text: lan.site.ssl_renew, + name: 'btn_ssl_renew', + hide: !rdata.status, + type: 'button', + callback: function (sdata) { + site.ssl.renew_ssl(web.name, rdata.auth_type, rdata.index); + }, + }, + ], + }, + ]; + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + robj + .find('textarea') + .css({ + 'background-color': '#f6f6f6', + resize: 'none', + }) + .attr('readonly', true); + robj.find('[name=csr]').css('margin-right', '0'); + var helps = [lan.site.ssl_tips1, lan.site.ssl_tips2, lan.site.ssl_tips3, lan.site.ssl_tips4, lan.site.ssl_tips5]; + robj.append(bt.render_help([lan.site.ssl_help_2, lan.site.ssl_help_3])); + return; + } + bt.site.get_site_domains(web.id, function (ddata) { + var helps = [ + [lan.site.bt_ssl_help_5, lan.site.bt_ssl_help_8, lan.site.bt_ssl_help_9, lan.site.ssl_tips5], + [lan.site.dns_check_tips1, lan.site.dns_check_tips2, lan.site.dns_check_tips3, lan.site.dns_check_tips4], + ]; + var datas = [ + { + title: lan.site.checking_mode, + items: [ + { + name: 'check_file', + text: lan.site.file_check, + type: 'radio', + callback: function (obj) { + $('.checks_line').remove(); + $(obj).siblings().removeAttr('checked'); + + $('.help-info-text').html($(bt.render_help(helps[0]))); + //var _form_data = bt.render_form_line({ title: ' ', class: 'checks_line label-input-group', items: [{ name: 'force', type: 'checkbox', value: true, text: '提前校验域名(提前发现问题,减少失败率)' }] }); + //$(obj).parents('.line').append(_form_data.html); + + $('#ymlist li input[type="checkbox"]').each(function () { + if ($(this).val().indexOf('*') >= 0) { + $(this).parents('li').hide(); + } + }); + }, + }, + { + name: 'check_dns', + text: lan.site.check_dns, + type: 'radio', + callback: function (obj) { + $('.checks_line').remove(); + $(obj).siblings().removeAttr('checked'); + $('.help-info-text').html($(bt.render_help(helps[1]))); + $('#ymlist li').show(); + + var arrs_list = [], + arr_obj = {}; + bt.site.get_dns_api(function (api) { + site.dnsapi = {}; + + for (var x = 0; x < api.length; x++) { + site.dnsapi[api[x].name] = {}; + site.dnsapi[api[x].name].s_key = 'None'; + site.dnsapi[api[x].name].s_token = 'None'; + if (api[x].data) { + site.dnsapi[api[x].name].s_key = api[x].data[0].value; + site.dnsapi[api[x].name].s_token = api[x].data[1].value; + } + arrs_list.push({ title: api[x].title, value: api[x].name }); + arr_obj[api[x].name] = api[x]; + } + + var data = [ + { + title: lan.site.choose_dns, + class: 'checks_line', + items: [ + { + name: 'dns_select', + width: 'auto', + type: 'select', + items: arrs_list, + callback: function (obj) { + var _val = obj.val(); + $('.set_dns_config').remove(); + var _val_obj = arr_obj[_val]; + var _form = { + title: '', + area: '530px', + list: [], + btns: [{ title: lan.site.turn_off, name: 'close' }], + }; + + var helps = []; + if (_val_obj.data !== false) { + _form.title = lan.site.set + '【' + _val_obj.title + '】' + lan.site.interface; + if (_val_obj.help == 'How to get API Token') { + _val_obj.help = + '' + + _val_obj.help + + ''; + } + helps.push(_val_obj.help); + var is_hide = true; + for (var i = 0; i < _val_obj.data.length; i++) { + _form.list.push({ + title: _val_obj.data[i].name, + name: _val_obj.data[i].key, + value: _val_obj.data[i].value, + }); + if (!_val_obj.data[i].value) is_hide = false; + } + if (_val_obj.title == 'CloudFlare') { + _form.list.push({ + html: + '
                                API-Limit
                                ', + }); + } + _form.btns.push({ + title: lan.site.save, + css: 'btn-success', + name: 'btn_submit_save', + callback: function (ldata, load) { + bt.site.set_dns_api({ pdata: JSON.stringify(ldata) }, function (ret) { + if (ret.status) { + load.close(); + robj.find('input[type="radio"]:eq(0)').trigger('click'); + robj.find('input[type="radio"]:eq(1)').trigger('click'); + } + bt.msg(ret); + }); + }, + }); + if (is_hide) { + obj.after(''); + $('.set_dns_config').click(function () { + var _bs = bt.render_form(_form); + $('div[data-id="form' + _bs + '"]').append(bt.render_help(helps)); + }); + } else { + var _bs = bt.render_form(_form); + $('div[data-id="form' + _bs + '"]').append(bt.render_help(helps)); + } + } + }, + }, + ], + }, + { + title: ' ', + class: 'checks_line label-input-group', + items: [ + { + css: 'label-input-group ptb10', + text: 'Automatically combine pan-domain names', + name: 'app_root', + type: 'checkbox', + }, + ], + }, + ]; + for (var i = 0; i < data.length; i++) { + var _form_data = bt.render_form_line(data[i]); + $(obj).parents('.line').append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + }); + }, + }, + ], + }, + ]; + + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + robj.append(_form_data.html); + bt.render_clicks(_form_data.clicks); + } + var _ul = $( + '
                                ' + ); + for (var i = 0; i < ddata.domains.length; i++) { + if (ddata.domains[i].binding === true) continue; + _ul.append('
                              • ' + ddata.domains[i].name + '
                              • '); + } + var _line = $("
                                "); + _line.append('' + lan.site.domain + ''); + _line.append(_ul); + robj.append(_line); + robj.find('input[type="radio"]').parent().addClass('label-input-group ptb10'); + $('#ymlist li input').click(function (e) { + e.stopPropagation(); + var a = true; + $('#ymlist li input').each(function () { + var o = $(this).prop('checked'); + if (!o) { + a = false; + return false; + } + }); + $('#ymlist div input').prop('checked', a); + }); + $('#ymlist li').click(function () { + var o = $(this).find('input'), + a = true; + if (o.prop('checked')) { + o.prop('checked', false); + } else { + o.prop('checked', true); + } + $('#ymlist li input').each(function () { + var o = $(this).prop('checked'); + if (!o) { + a = false; + return false; + } + }); + $('#ymlist div input').prop('checked', a); + }); + $('#ymlist div').click(function () { + var o = $('#ymlist div input'), + p = $('#ymlist input'); + if (o.prop('checked')) { + p.prop('checked', true); + } else { + p.prop('checked', false); + } + }); + var _btn_data = bt.render_form_line({ + title: ' ', + text: lan.site.btapply, + name: 'letsApply', + type: 'button', + callback: function (ldata) { + ldata['domains'] = []; + $('#ymlist li:visible input[type="checkbox"]:checked').each(function () { + ldata['domains'].push($(this).val()); + }); + ldata.app_root = $('#app_root').prop('checked'); + // console.log(ldata) + var auth_type = 'http'; + var auth_to = web.id; + var auto_wildcard = '0'; + if ($('.check_dns').prop('checked')) { + auth_type = 'dns'; + auth_to = 'dns'; + auto_wildcard = ldata.app_root ? '1' : '0'; + var dns_select = $('.dns_select').val() + if (dns_select !== auth_to) { + if (!site.dnsapi[dns_select].s_key) { + layer.msg('No key information is set for the specified dns interface'); + return; + } + auth_to = dns_select + '|' + site.dnsapi[dns_select].s_key + '|' + site.dnsapi[dns_select].s_token; + } + } + if (ldata['domains'].length <= 0) { + return layer.msg('Need at least a domain name!', { icon: 2 }); + } + site.show_certificate_confirm(web.name, function () { + acme.apply_cert(ldata['domains'], auth_type, auth_to, auto_wildcard, function (res) { + site.ssl.ssl_result(res, auth_type, web.name); + }); + }); + }, + }); + robj.append(_btn_data.html); + bt.render_clicks(_btn_data.clicks); + + robj.append(bt.render_help(helps[0])); + robj.find('input[type="radio"]:eq(0)').trigger('click'); + }); + }, + }, + // { + // title: lan.site.other_ssl, + // callback: function (robj) { + // robj = $('#webedit-con .tab-con') + // var cert_info = ''; + // if (rdata.cert_data['notBefore']) { + // cert_info = '
                                \ + // ' + (rdata.status ? lan.site.deploy_success_tips : lan.site.not_deploy_and_save) + '\ + // ' + lan.site.cert_brand + '' + rdata.cert_data.issuer + '\ + // ' + lan.site.auth_domain + ' ' + (rdata.cert_data.dns ? rdata.cert_data.dns.join(', ') : '') + '\ + // ' + lan.site.expire_time + ' ' + rdata.cert_data.notAfter + '
                                ' + // } + // robj.append('
                                ' + cert_info + '
                                ' + lan.site.ssl_key + '' + lan.site.ssl_crt + '
                                '); + // var datas = [{ + // items: [ + // {name: 'key', width: '48%', height: '220px', type: 'textarea', value: rdata.key}, + // {name: 'csr', width: '48%', height: '220px', type: 'textarea', value: rdata.csr} + // ] + // }, + // { + // items: [{ + // text: lan.site.save, + // name: 'btn_ssl_save', + // type: 'button', + // callback: function (sdata) { + // bt.site.set_ssl(web.name, sdata, function (ret) { + // if (ret.status) site.reload(7); + // bt.msg(ret); + // }) + // } + // }, + // { + // text: lan.site.ssl_close, + // name: 'btn_ssl_close', + // hide: !rdata.status, + // type: 'button', + // callback: function (sdata) { + // site.ssl.set_ssl_status('CloseSSLConf', web.name); + // } + // } + // ] + // } + // ] + // for (var i = 0; i < datas.length; i++) { + // var _form_data = bt.render_form_line(datas[i]); + // robj.append(_form_data.html); + // bt.render_clicks(_form_data.clicks); + // } + // var helps = [ + // lan.site.bt_ssl_help_10, + // lan.public_backup.cret_err, + // lan.public_backup.pem_format, + // lan.site.ssl_tips5, + // ] + // robj.append(bt.render_help(helps)); + // robj.find(".help-info-text").css('margin-top', '0'); + // robj.find('textarea').css('resize', 'none'); + // robj.find('[name=csr]').css('margin-right', '0'); + // } + // }, + // { + // title: lan.site.turn_off, + // callback: function (robj) { + // robj = $('#webedit-con .tab-con'); + // if (rdata.type == -1) { + // robj.html("
                                " + lan.site.ssl_help_1 + "
                                "); + // } else { + // var txt = ''; + // switch (rdata.type) { + // case 1: + // txt = "Let's Encrypt"; + // break; + // case 0: + // txt = lan.site.other_ssl; + // break; + // case 2: + // txt = lan.site.bt_ssl; + // break; + // } + // robj.html('\ + //
                                ' + lan.get('ssl_enable', [txt]) + '
                                \ + //
                                \ + // \ + //
                                \ + // '); + // } + // var loadT = bt.load(lan.site.the_msg); + // $.post('/site?action=get_auto_restart_rph', { + // sitename: web.name + // }, function (res) { + // loadT.close(); + // if (res) { + // var checked_str = res.status ? 'checked="true"' : ''; + // robj.append('\ + //
                                \ + //
                                Auto restart proxy, redirect, http to https when apply or renew SSL
                                \ + //
                                \ + // \ + // \ + //
                                \ + // '); + // } + // }); + // } + // }, + { + title: lan.site.ssl_dir, + callback: function (robj) { + robj = $('#webedit-con .tab-con'); + robj.html("
                                "); + bt.site.get_cer_list(function (rdata) { + bt.render({ + table: '#cer_list_table', + columns: [ + { + field: 'subject', + title: lan.site.domain, + templet: function (item) { + return item.dns.join('
                                '); + }, + }, + { field: 'notAfter', width: '100px', title: lan.site.endtime }, + { field: 'issuer', width: '150px', title: lan.site.brand }, + { + field: 'opt', + width: '100px', + align: 'right', + title: lan.site.operate, + templet: function (item) { + var opt = + '' + + lan.site.deploy + + ' | '; + opt += + '' + + lan.site.del + + ''; + return opt; + }, + }, + ], + data: rdata, + }); + }); + }, + }, + ]; + + bt.render_tab('ssl_tabs', tabs); + + // $('#ssl_tabs').append('
                                ' + lan.site.force_https + '
                                '); + // $("#toHttps").attr('checked', rdata.httpTohttps); + // $('#toHttps').click(function (sdata) { + // var isHttps = $("#toHttps").attr('checked'); + // if (isHttps) { + // layer.confirm('After closing HTTPS, you need to clear your browser cache to see the effect. Continue?', { + // icon: 3, + // title: "Turn off forced HTTPS\"" + // }, function () { + // bt.site.close_http_to_https(web.name, function (rdata) { + // if (rdata.status) { + // setTimeout(function () { + // site.reload(7); + // }, 3000); + // } + // }) + // }); + // } else { + // bt.site.set_http_to_https(web.name, function (rdata) { + // if (!rdata.status) { + // setTimeout(function () { + // site.reload(7); + // }, 3000); + // } + + // }) + // } + // }) + // switch (rdata.type) { + // case 1: + // $('#ssl_tabs span:eq(0)').trigger('click'); + // break; + // case 0: + // $('#ssl_tabs span:eq(0)').trigger('click'); + // break; + // default: + // $('#ssl_tabs span:eq(0)').trigger('click'); + // break; + // } + + $('#ssl_tabs span:eq(' + (rdata.status ? (rdata.csr ? 0 : 1) : 1) + ')').trigger('click'); + + $('.cutTabView').on('click', function () { + $('#ssl_tabs span:eq(1)').trigger('click'); + setTimeout(function () { + $('.ssl_business_application').trigger('click'); + }, 400); + }); + }); + }, + show_certificate_confirm: function (sitename, callback) { + var _this = this; + var auto_restart_rph = function (index, loading) { + if (loading) loadT = bt.load(lan.site.the_msg); + $.post( + '/site?action=auto_restart_rph', + { + sitename: sitename, + }, + function (res) { + loadT.close(); + if (res.status) { + if (index) layer.close(index); + if (callback) callback(res); + } + } + ); + }; + var loadT = bt.load(lan.site.the_msg); + $.post( + '/site?action=get_auto_restart_rph', + { + sitename: sitename, + }, + function (res) { + if (res && res.status) { + auto_restart_rph(); + } else { + loadT.close(); + layer.open({ + type: 1, + area: '530px', + title: 'Apply SSL', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + '\ +
                                \ +
                                \ + \ +

                                Apply or renew SSL

                                \ +
                                  \ +
                                • The reverse proxy, redirection and http to https will be automatically restart during the application or renewal of SSL!
                                • \ +
                                • The application and renewal of SSL will not be affected by the redirection, reverse proxy and http to https
                                • \ +
                                \ +
                                \ +
                                \ + \ + \ +
                                \ +
                                \ + ', + success: function (layers, index) { + $('.submit_cert').click(function () { + auto_restart_rph(index, true); + }); + $('.close_cert').click(function () { + layer.close(index); + }); + }, + }); + } + } + ); + }, +}; + +// $('#cutMode .tabs-item[data-type="' + (bt.get_cookie('site_model') || 'php') + '"]').trigger('click'); +// site.get_types(); + +// $.prototype.serializeObject = function() { +// var a, o, h, i, e; +// a = this.serializeArray(); +// o = {}; +// h = o.hasOwnProperty; +// for (i = 0; i < a.length; i++) { +// e = a[i]; +// if (!h.call(o, e.name)) { +// o[e.name] = e.value; +// } +// } +// return o; +// }; diff --git a/BTPanel/static/vite/oldjs/soft.js b/BTPanel/static/vite/oldjs/soft.js new file mode 100644 index 00000000..ebd4107f --- /dev/null +++ b/BTPanel/static/vite/oldjs/soft.js @@ -0,0 +1,5543 @@ +var soft = { + is_install: false, + trail: 0, //是否试用 + is_setup: false, + is_setup_name: '', + refresh_data: [], + get_list: function (page, type, search) { + if (page == undefined || page == 'null' || page == 'undefined') page = 0; + if (type == undefined || type == 'null' || type == 'undefined') type = 0; + if (!search) search = $('#SearchValue').val(); + if (search == undefined || search == 'null' || search == 'undefined' || search == '') search = undefined; + var _this = this, + commonly_software = $('#commonly_software'); + var istype = getCookie('softType'); + if (istype == 'undefined' || istype == 'null' || !istype) { + istype = 0; + } + if (type == 0) type = bt.get_cookie('softType'); + if (page == 0) page = bt.get_cookie('p' + type); + if (type == '11') { + soft.get_dep_list(1); + commonly_software.hide(); + return; + } + soft.is_install = false; + bt.soft.get_soft_list(page, type, search, function (rdata) { + _this.trail = rdata.trail; + // if (rdata.pro < 0) { + // // $("#updata_pro_info").html(''); + // } else + // if (rdata.pro === -2) { + // $("#updata_pro_info").html('
                                ' + lan.soft.pro_expire + ''); + // } else if (rdata.pro === -1) { + // $("#updata_pro_info").html('
                                ' + lan.soft.upgrade_pro + '\
                                '); + // } + soft.set_soft_tips(rdata, type); + + // if (type == 10) { + // $("#updata_pro_info").html('
                                ' + lan.soft.bt_developer + '' + lan.soft.get_third_party_apps + '
                                ') + // } else if (type == 11) { + // $("#updata_pro_info").html('
                                ' + lan.soft.comingsoon + '
                                ') + // } + var tBody = ''; + rdata.type.unshift({ icon: 'icon', id: 0, ps: lan.soft.all, sort: 1, title: lan.soft.all }, { icon: 'icon', id: -1, ps: 'Installed', sort: 1, title: 'Installed' }); + for (var i = 0; i < rdata.type.length; i++) { + var c = ''; + if (istype == rdata.type[i].id) { + c = 'class="on"'; + } + // 注释软件管理的付费插件,第三方插件,一键部署 + // if (rdata.type[i].id != "11" && rdata.type[i].id != "10" && rdata.type[i].id != "8") { + if (rdata.type[i].id != '11') { + tBody += '' + rdata.type[i].title + ''; + } + } + if (page) bt.set_cookie('p' + type, page); + $('.softtype').html(tBody); + $('.menu-sub span').click(function () { + var _type = $(this).attr('typeid'); + bt.set_cookie('softType', _type); + $(this).addClass('on').siblings().removeClass('on'); + if (_type !== '11') { + soft.get_list(0, _type); + commonly_software.show(); + } else { + soft.get_dep_list(0); + commonly_software.hide(); + } + }); + var data = rdata.list.data; + $('#softPage').html(rdata.list.page); + if (data.length > 0) { + for (var i = 0; i < data.length; i++) { + if (data[i].task == '-1') { + soft.is_setup = true; + soft.is_setup_name = data[i].name; + break; + } else { + soft.is_setup = false; + soft.is_install = false; + soft.is_setup_name = ''; + } + } + } + if (soft.is_setup == true && soft.is_setup_name != '') { + _this.soft_setup_find(); + } + if (soft.refresh_data.length == 0) { + _this.refresh_table(page, type, search, rdata); + soft.refresh_data = data; + } else if (JSON.stringify(data) != JSON.stringify(soft.refresh_data)) { + _this.refresh_table(page, type, search, rdata); + soft.refresh_data = data; + } + bt.set_cookie('load_page', (page + '').split('not_load')[0]); + bt.set_cookie('load_type', type); + bt.set_cookie('load_search', search); + if (soft.is_install && soft.is_setup == false) { + setTimeout(function () { + soft.get_list(bt.get_cookie('load_page') + 'not_load', bt.get_cookie('load_type'), bt.get_cookie('load_search')); + }, 3000); + soft.is_install = false; + } + // if(rdata.recommend){ + // _this.render_promote_list(rdata.recommend); + // } + }); + }, + // 查找正在安装软件的状态 + soft_setup_find: function () { + var _this = this; + if (soft.is_setup == true && soft.is_setup_name != '') { + $.post('plugin?action=get_soft_find', { sName: soft.is_setup_name }, function (rdata) { + if (rdata.task == '-1') { + setTimeout(function () { + _this.soft_setup_find(); + }, 3000); + } else { + soft.is_install = true; + setTimeout(function () { + soft.get_list(bt.get_cookie('load_page') + 'not_load', bt.get_cookie('load_type'), bt.get_cookie('load_search')); + }, 3000); + } + }); + } + }, + // 刷新列表 + refresh_table: function (page, type, search, rdata) { + var _this = this; + var phps = ['php-5.2', 'php-5.3', 'php-5.4']; + var data = rdata.list.data; + var _tab = bt.render({ + table: '#softList', + columns: [ + { + field: 'title', + title: lan.soft.app_name, + width: 165, + templet: function (item) { + var fName = item.name, + version = item.version; + if (bt.contains(item.name, 'php-')) { + fName = 'php'; + version = ''; + } + var click_opt = ' ', + sStyle = ''; + if (item.setup) { + sStyle = ' style="cursor:pointer"'; + if (item.admin) { + if (item.endtime >= 0 || item.price == 0) { + click_opt += 'onclick="bt.soft.set_lib_config(\'' + item.name + "','" + item.title + '\')" '; + } + } else { + click_opt += ' onclick="soft.set_soft_config(\'' + item.name + '\')" '; + } + } + var is_php5 = item.name.indexOf('php-5') >= 0, + webcache = bt.get_cookie('serverType') == 'openlitespeed' ? true : false, + distribution = bt.get_cookie('distribution'); + if (webcache) { + switch (distribution) { + case 'centos8': + if (is_php5 || item.name == 'php-7.0') { + click_opt = ' title="' + lan.soft.ap2_2_not_support + '"'; + } + break; + case 'centos7': + if (item.name == 'php-5.2') { + click_opt = ' title="' + lan.soft.ap2_2_not_support + '"'; + } + break; + default: + if (is_php5) { + click_opt = ' title="' + lan.soft.ap2_2_not_support + '"'; + } + break; + } + } else if (rdata.apache22 && item.name.indexOf('php-') >= 0 && $.inArray(item.name, phps) == -1) { + click_opt = ' title="' + lan.soft.ap2_2_not_support + '"'; + } + //if (rdata.apache22 && item.name.indexOf('php-') >= 0 && $.inArray(item.name, phps) == -1) click_opt = ' title="' + lan.soft.ap2_2_not_support + '"'; + return '' + item.title + ' ' + version + ''; + }, + }, + { + field: 'price', + title: 'Developer', + width: 110, + templet: function (item) { + if (!item.author) return 'official'; + return item.author; + }, + }, + { + field: 'ps', + title: lan.soft.instructions, + templet: function (item) { + var ps = item.ps; + var is_php = item.name.indexOf('php-') >= 0; + + if (is_php && item.setup) { + if (rdata.apache22 && $.inArray(item.name, phps) >= 0) { + if (item.fpm) { + ps += " (" + lan.soft.apache22 + ')'; + } + } else if (!rdata.apache22) { + if (!item.fpm) { + ps += " (" + lan.soft.apache24 + ')'; + } + } + } + return '' + ps + ''; + }, + }, + { + field: 'price', + title: lan.soft.price, + width: 92, + templet: function (item) { + var price = lan.soft.free; + if (item.price > 0) { + price = '$' + item.price + ''; + } + return price; + }, + }, + type == 10 + ? { + field: 'sort', + width: 80, + title: 'Rated', + templet: function (item) { + return item.sort !== undefined + ? '' + + (item.sort <= 0 || item.sort > 5 ? lan.soft.not_rated : item.sort.toFixed(1)) + + '' + : '--'; + }, + } + : '', + { + field: 'endtime', + width: 120, + title: lan.soft.expire_time, + templet: function (item) { + var endtime = '--'; + if (item.pid > 0) { + if (item.endtime > 0) { + if (item.type != 10) { + endtime = bt.format_data(item.endtime, 'yyyy/MM/dd'); + } else { + endtime = bt.format_data(item.endtime, 'yyyy/MM/dd'); + } + } else if (item.endtime === 0) { + endtime = lan.soft.permanent; + } else if (item.endtime === -1) { + endtime = lan.soft.not_open; + } else if (item.endtime === -2) { + if (item.type != 10) { + endtime = lan.soft.already_expire; + } else { + endtime = lan.soft.already_expire; + } + } + } + return endtime; + }, + }, + { + field: 'path', + width: 40, + title: lan.soft.location, + templet: function (item) { + var path = ''; + if (item.setup) { + path = ''; + } + return path; + }, + }, + type != 10 + ? { + field: 'status', + width: 40, + title: lan.soft.status1, + templet: function (item) { + var status = ''; + if (item.setup) { + if (item.status) { + status = ''; + } else { + status = ''; + } + } + return status; + }, + } + : '', + { + field: 'index', + width: 100, + title: lan.soft.display_at_homepage, + templet: function (item) { + var to_index = ''; + if (item.setup) { + var checked = ''; + if (item.index_display) checked = 'checked'; + var item_id = item.name.replace(/\./, ''); + to_index = + '
                                '; + } + return to_index; + }, + }, + { + field: 'opt', + width: 190, + title: lan.soft.operate, + align: 'right', + templet: function (item) { + var option = ''; + + var pay_opt = ''; + if (item.endtime < 0 && item.pid > 0) { + var re_msg = ''; + var re_status = 0; + var buy_type = 0; + switch (item.endtime) { + case -1: + re_msg = lan.soft.buy_now; + buy_type = 31; + break; + case -2: + re_msg = lan.soft.renew_now; + re_status = 1; + buy_type = 32; + break; + } + if (item.type != 10) { + pay_opt = + '" + + re_msg + + ''; + } else { + pay_opt = '' + re_msg + ''; + } + } + var is_php = item.name.indexOf('php-') >= 0, + is_php5 = item.name.indexOf('php-5') >= 0, + webcache = bt.get_cookie('serverType') == 'openlitespeed' ? true : false, + distribution = bt.get_cookie('distribution'); + if (webcache && is_php) { + if ((is_php5 || item.name == 'php-7.0') && distribution == 'centos8') { + option = '' + lan.soft.not_comp + ''; + } else if (distribution == 'centos7' && item.name == 'php-5.2') { + option = '' + lan.soft.not_comp + ''; + } else { + if (distribution != 'centos7' && is_php5) { + option = '' + lan.soft.not_comp + ''; + } else { + if (item.setup && item.task == '1') { + if (pay_opt == '') { + if (item.versions.length > 1) { + for (var i = 0; i < item.versions.length; i++) { + var min_version = item.versions[i]; + var ret = bt.check_version(item.version, min_version.m_version + '.' + min_version.version); + if (ret > 0) { + if (ret == 2) + option += + '' + + lan.soft.update + + ' | '; + break; + } + } + } else { + var min_version = item.versions[0]; + var cloud_version = min_version.m_version + '.' + min_version.version; + if (item.version != cloud_version) + option += + '' + + lan.soft.update + + ' | '; + } + if (item.admin) { + option += '' + lan.soft.setup + ' | '; + } else { + option += '' + lan.soft.setup + ' | '; + } + } else { + option = pay_opt + ' | ' + option; + } + option += '' + lan.soft.uninstall + ''; + } else if (item.task == '-1') { + option = '' + lan.soft.installing + ''; + soft.is_install = true; + } else if (item.task == '0') { + option = '' + lan.soft.wait_install + ''; + soft.is_install = true; + } else if (item.task == '-2') { + option = 'Updating'; + soft.is_install = true; + } else { + if (pay_opt) { + option = pay_opt; + } else { + option = '' + lan.soft.install + ''; + } + } + } + } + } else { + if (rdata.apache22 && is_php && $.inArray(item.name, phps) == -1) { + if (item.setup) { + option = '' + lan.soft.uninstall + ''; + } else { + option = '' + lan.soft.not_comp + ''; + } + } else if (rdata.apache24 && item.name == 'php-5.2') { + if (item.setup) { + option = '' + lan.soft.uninstall + ''; + } else { + option = '' + lan.soft.not_comp + ''; + } + } else { + if (item.setup && item.task == '1') { + if (pay_opt == '') { + if (item.versions.length > 1) { + for (var i = 0; i < item.versions.length; i++) { + var min_version = item.versions[i]; + var ret = bt.check_version(item.version, min_version.m_version + '.' + min_version.version); + if (ret > 0) { + if (ret == 2) + option += + '' + + lan.soft.update + + ' | '; + break; + } + } + } else { + var min_version = item.versions[0]; + var cloud_version = min_version.m_version + '.' + min_version.version; + if (item.version != cloud_version) + option += + '' + + lan.soft.update + + ' | '; + } + if (item.admin) { + option += '' + lan.soft.setup + ' | '; + } else { + option += '' + lan.soft.setup + ' | '; + } + } else { + option = pay_opt + ' | ' + option; + } + option += '' + lan.soft.uninstall + ''; + } else if (item.task == '-1') { + option = '' + lan.soft.installing + ''; + soft.is_install = true; + } else if (item.task == '0') { + option = '' + lan.soft.wait_install + ''; + soft.is_install = true; + } else if (item.task == '-2') { + option = 'Updating'; + soft.is_install = true; + } else { + if (pay_opt) { + option = pay_opt; + } else { + option = '' + lan.soft.install + ''; + } + } + } + } + return option; + }, + }, + ], + data: data, + empty: + 'If the search content is not found, submit the demand feedback', + }); + // 需求反馈 + if (data.length == 0) { + $('.feedback-btn').remove(); + $('.soft-filter-box .soft-search').after( + '' + ); + } + }, + // 渲染列表 + render_promote_list: function (data) { + if ($('#soft_recom_list').length > 0) $('#soft_recom_list').remove(); + var html = $('
                                  '), + that = this; + for (var i = 0; i < data.length; i++) { + var type = '', + item = data[i]; + (function (item) { + switch (item.type) { + case 'link': // 链接推荐 + type = $('' + (item.title || '') + ''); + break; + case 'soft': // 软件推荐 + case 'other': // 第三方推荐 + case 'onekey': // 一键部署推荐 + type = $('' + (item.title || '') + '').click(function () { + that.render_promote_view(item); + }); + break; + } + html.append($('
                                • ').append(type)); + })(item); + // html.append($('
                                • ').append(type)); + } + $('#updata_pro_info').before(html); + }, + // 渲染软件列表 + render_promote_view: function (find) { + var that = this, + is_single_product = find.data.length > 1, + find_data = find.data; + if (is_single_product) { + layer.open({ + title: find.title, + area: '800px', + btn: false, + closeBtn: 2, + shadeClose: false, + content: (function () { + var html = ''; + for (var i = 0; i < find_data.length; i++) { + var item = find_data[i], + thtml = ''; + if (!item.setup) { + thtml = ''; + } else { + if (item.pid != 0) { + if (item.endtime == 0) { + //永久 + thtml = ''; + } else if (item.endtime > 0) { + //已购买 + thtml = ''; + } else if (item.endtime == -1) { + //未购买 + + thtml = + '"; + } else if (item.endtime == -2) { + //已过期 + thtml = + '"; + } + } else { + thtml = ''; + } + } + html += + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + item.title + + ' v' + + item.version + + '
                                  ' + + '
                                  $' + + item.price + + '/month
                                  ' + + '
                                  ' + + '
                                  ' + + item.ps + + '
                                  ' + + '
                                  ' + + thtml + + '
                                  ' + + '
                                  ' + + '
                                  '; + } + return html; + })(), + }); + } + }, + set_soft_tips: function (rdata, type) { + var tips_info = $('
                                  '), + explain = tips_info.find('.soft_tips_text'), + btn_ground = tips_info.find('.btn-ground'), + _this = this, + el = '#updata_pro_info'; + $(el).empty(); + type = parseInt(type); + if (type != 11) $(el).next('.onekey-menu-sub').remove(); + if (type == 10) { + $(el).css('display', 'block'); + explain.text( + 'Security Reminder: aaPanel officially conducted a security audit before the third-party plug-in was put on the shelves, but there may be security risks. Please check it out before using it in the production environment.' + ); + btn_ground = soft.render_tips_btn(btn_ground, [ + //{title:'免费入驻',href:'https://www.bt.cn/developer/',rel:'noreferrer noopener',target:'_blank',btn:'免费入驻',class:'btn btn-success btn-xs va0',style:"margin-left:10px;"}, + { + title: 'Get third-party apps', + rel: 'noreferrer noopener', + href: 'https://www.bt.cn/bbs/forum-40-1.html', + target: '_blank', + btn: 'Get third-party apps', + class: 'btn btn-success btn-xs va0 ml15', + style: 'margin-left:10px;', + }, + { + title: 'Import plugins', + href: 'javascript:;', + btn: 'Import plugins', + class: 'btn btn-success btn-xs va0 ml15', + style: 'margin-left:10px;', + click: function (e) { + var input = $('') + .change(function (e) { + var files = $(this)[0].files; + if (files.length == 0) return; + soft.update_zip(files[0]); + }) + .click(); + }, + }, + ]); + $(el).append(tips_info.addClass('alert-danger')); + } else if (type == 11) { + explain.text('BT one click宝塔一键部署已上线,诚邀全球优秀项目入驻(限项目官方) '); + btn_ground = soft.render_tips_btn(btn_ground, [ + { + title: '免费入驻', + href: 'https://www.bt.cn/bbs/thread-33063-1-1.html', + rel: 'noreferrer noopener', + target: '_blank', + btn: '免费入驻', + class: 'btn btn-success btn-xs va0', + style: 'margin-left:10px;', + }, + { title: '导入项目', href: 'javascript:;', rel: 'noreferrer noopener', btn: '导入项目', class: 'btn btn-success btn-xs va0', style: 'margin-left:10px;', click: soft.input_package }, + ]); + $(el).append(tips_info.addClass('alert-info')); + } else { + var genre = true, + is_buy = false; + if (rdata.ltd > 0 || type === 12) { + genre = false; + } else if (rdata.pro >= 0 || type === 8) { + genre = true; + } + if (rdata.ltd > 0 || rdata.pro >= 0) is_buy = true; + if (type === 12 && rdata.ltd < 0) is_buy = false; + var buy_type = is_buy ? 30 : 29; + var ltd = parseInt(bt.get_cookie('ltd_end') || -1), + pro = parseInt(bt.get_cookie('pro_end') || -1), + todayDate = parseInt(new Date().getTime() / 1000), + _ltd = null; + if ((ltd > 0 && (ltd == pro || pro < 0)) || (ltd < 0 && pro >= 0) || (ltd > 0 && pro >= 0)) { + _ltd = (ltd > 0 && (ltd == pro || pro < 0)) || (ltd > 0 && pro >= 0) ? 1 : 0; + explain.html( + 'The ' + + (_ltd ? 'Pro' : 'Pro') + + ' edition can use the ' + + (_ltd ? '专业版及企业版插件' : 'professional plug-in for free,') + + (!(pro == 0 && ltd < 0) + ? 'expiration time: ' + + bt.format_data(_ltd ? ltd : pro, 'yyyy/MM/dd') + + '' + + ((_ltd ? ltd : pro) - todayDate <= 15 * 24 * 60 * 60 + ? ',Only ' + Math.round(((_ltd ? ltd : pro) - todayDate) / (24 * 60 * 60)) + ' days until expiration' + : '') + : ' Expire: Lifetime') + ); + } else if (ltd == -1 && pro == -1) { + explain.html('Upgrade to Pro edition, all plugins, free to use!'); + } else if (pro == 0 && ltd < 0) { + _ltd = 2; + explain.html( + 'The Pro edition can use the professional plug-in for free, expiration time: 永久授权。' + + (type == 12 ? '  升级企业版,企业可以免费试用企业版插件及专业版插件。' : '') + ); + if (type == 12) { + btn_ground = soft.render_tips_btn(btn_ground, { + title: '立即升级', + href: 'javascript:;', + btn: '立即升级', + class: 'btn btn-success btn-xs va0 ml15', + style: 'margin-left:10px;', + click: bt.soft.updata_ltd, + }); + } + } else if (ltd == -2 || pro == -2) { + _ltd = ltd == -2 ? 1 : 0; + explain.html( + '当前为' + + (_ltd ? '企业版' : '专业版') + + ',' + + (_ltd ? '企业版' : '专业版') + + '可以免费使用' + + (_ltd ? '专业版及企业版插件' : '专业版插件') + + ',' + + (_ltd ? '企业版' : '专业版') + + '已过期' + ); + } + var btn_config = { title: null, href: 'javascript:;', btn: null, class: 'btn btn-success btn-xs va0 ml15', style: 'margin-left:10px;', click: null }; + var set_btn_style = function (res) { + if (!res.status || !res) { + fun = function () { + bt.pub.bind_btname(function () { + window.location.reload(); + }); + }; + $.extend(btn_config, { title: 'Login', btn: 'Login', click: fun }); + } else { + if (type == 12 && ltd < 0 && pro >= 0) { + explain.html( + '企业版可以免费使用专业版及企业版插件,了解专业版和企业版的区别,请点击查看详情《专业版升级企业版教程》' + ); + $(el).append(tips_info.addClass('alert-ltd-success')); + return false; + } else { + // var btn = $('' + (is_buy ? 'Renew Now' : 'Upgrade now') + '') + // btn.on('click', function () { + // genre ? bt.soft.updata_pro(buy_type) : bt.soft.updata_ltd(undefined,buy_type) + // }) + // tips_info.addClass('showprofun').find('.btn-ground').append(btn) + } + } + if (pro !== 0) { + var btn = $( + '' + + (is_buy ? 'Renew Now' : 'Upgrade now') + + '' + ); + btn.on('click', function () { + window.usePay({ + source: buy_type, + }); + // genre ? bt.soft.updata_pro(buy_type) : bt.soft.updata_ltd(undefined, buy_type); + }); + tips_info.addClass('showprofun').find('.btn-ground').append(btn); + } + // if(_ltd != 2){ + // if(!(pro == 0 && ltd < 0)){ + // btn_ground = soft.render_tips_btn(btn_ground); + // } + // } + $(el).append(tips_info.addClass(_ltd == 1 ? 'alert-ltd-success' : 'alert-success')); + if (_this.trail) { + // setTimeout(function (){ + // $('.btn-ground').after('Try the Pro edition for free') + // var trail = $('Click to try'); + // trail.click((!res.status || !res)?fun:function(){ + // var loadT = bt.load() + // bt.confirm({ + // title:"Pro Edition", + // msg:"Get 7-day Pro edition free, get it now?" + // },function (){ + // bt.send('free_trial','auth/free_trial',{},function(res){ + // loadT.close() + // bt.msg(res) + // setTimeout(function () { window.location.reload() },2000) + // }) + // }) + // }) + // $('.pro_trail').after(trail) + // },100) + } + }; + var bt_user_info = bt.get_cookie('bt_user_info'); + if (!bt_user_info) { + bt.pub.get_user_info(function (res) { + if (!res.status) { + set_btn_style(false); + return false; + } + bt.set_cookie('bt_user_info', JSON.stringify(res), 300000); + set_btn_style(res); + }); + } else { + set_btn_style(JSON.parse(bt.get_cookie('bt_user_info'))); + } + } + }, + /** + * @description 设置软件信息 + * @param {object} rdata 软件列表请求数据 + * @param {string} type 列表类型 + */ + render_soft_recommend: function () { + bt.send('get_usually_plugin', 'plugin/get_usually_plugin', {}, function (res) { + var html = ''; + for (var i = 0; i < res.length; i++) { + var item = res[i]; + html += + '
                                  ' + + item.title + + '
                                  '; + } + $('#commonly_software .commonly_software_list').html(html); + }); + }, + render_tips_btn: function (node, arry) { + if (!Array.isArray(arry)) arry = [arry]; + for (var i = 0; i < arry.length; i++) { + var item = arry[i], + btn = ''; + if (item.click) { + btn = $(btn).on('click', item.click); + } + node.append(btn); + } + return node; + }, + get_dep_list: function (p) { + var loadT = layer.msg('Getting list ', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + var pdata = {}; + var search = $('#SearchValue').val(); + if (search != '') { + pdata['search'] = search; + } + var type = ''; + var istype = getCookie('depType'); + if (istype == 'undefined' || istype == 'null' || !istype) { + istype = '0'; + } + pdata['type'] = istype; + + var force = bt.get_cookie('force'); + if (force === '1') { + pdata['force'] = force; + } + bt.set_cookie('force', 0); + $.post('/deployment?action=GetList', pdata, function (rdata) { + layer.close(loadT); + var tBody = ''; + soft.set_soft_tips(rdata, 11); + rdata.type.unshift( + { + icon: 'icon', + id: 0, + ps: 'All', + sort: 1, + title: 'All', + }, + { + icon: 'icon', + id: -1, + ps: 'Installed', + sort: 1, + title: 'Installed', + } + ); + for (var i = 0; i < rdata.type.length; i++) { + var c = ''; + if ('11' == rdata.type[i].id) { + c = 'class="on"'; + } + tBody += '' + rdata.type[i].title + ''; + } + $('.softtype').html(tBody); + + $('.menu-sub span').click(function () { + var _type = $(this).attr('typeid'); + bt.set_cookie('softType', _type); + $(this).addClass('on').siblings().removeClass('on'); + if (_type !== '11') { + soft.get_list(0, _type); + } else { + soft.get_dep_list(1); + } + }); + if ($('.onekey-type').attr('class') === undefined) { + tbody = + '
                                  '; + + rdata.dep_type.unshift({ + tid: 0, + title: 'All', + }); + rdata.dep_type.push({ + tid: 100, + title: 'Other', + }); + for (var i = 0; i < rdata.dep_type.length; i++) { + var c = ''; + if (istype == rdata.dep_type[i].tid) { + c = 'class="on"'; + } + tbody += '' + rdata.dep_type[i].title + ''; + } + tbody += '
                                  '; + $('#updata_pro_info').html(tbody); + $('.onekey-menu-sub span').click(function () { + setCookie('depType', $(this).attr('typeid')); + $(this).addClass('on').siblings().removeClass('on'); + soft.get_dep_list(1); + }); + } + + var zbody = + '\ + \ + Name\ + Version\ + Introduction\ + Support for PHP version\ + Provider\ + Score\ + Operate\ + \ + '; + var icon_other = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYFJREFUeNpi/P//P8NAAhZkzrVr1zyB1FwglqSiHSZAfBZZQEtLC7sDQJZLSUlJcnNzU8Xm27dvg6jVQByqqqp6FpsaJjS+JCcnJ8O/f/+ogkFATk5uJ8gRQMcYE+MAqgN2dvYMeXn5g0DmGmyOYKJHQmNjY0tQUFA4gc0RLLS0mI+PD5YOQCACSp8BYka6OEBUVJRBXFwcW8IkPgQ+r2lk+PPsJnl5XEqdgTeknvhyABsAWS6Ytwyr3PtJUTjlYPKEAEWJEGQ5MZbQzAGEQoDmDhgNAWITGk0dQCkYcAewUJoIh38IgIpTchMai6Qa5Q4gVJYP+SgYcAcwIjfLHxZo0qWNLj/hOp4GCQs7bo0121D4D1u8wGIwGlkcd/3+k/xyANlgdMcgy8McRbM0QIoFVC8J8VkOCxVSHMdCTZ+TEyooDvjPKfCP8ddXJgZGJqISIskW/v8HtIP/H85seGdWYT3L/eN1jN8/0qR8+M8l8PePgkWzSlp/I1YHDAQACDAAtKS/DHmsv9AAAAAASUVORK5CYII='; + for (var i = 0; i < rdata.list.length; i++) { + var remove_opt = ''; + if (rdata.list[i].id === 0) { + remove_opt = + ' | ' + + lan.public.update + + ' | ' + + lan.public.del + + ''; + rdata.list[i].min_image = icon_other; + } else { + rdata.list[i].min_image += '?t=' + new Date().format('yyyyMMdd'); + } + zbody += + '' + + '' + + rdata.list[i].title + + '' + + '' + + rdata.list[i].version + + '' + + '' + + rdata.list[i].ps + + '' + + '' + + rdata.list[i].php + + '' + + '' + + (rdata.list[i].author == 'aaPanel' ? rdata.list[i].title : rdata.list[i].author) + + '' + + '' + + (rdata.list[i].sort !== undefined + ? '' + + (rdata.list[i].sort <= 0 || rdata.list[i].sort > 5 ? 'No rating' : rdata.list[i].sort.toFixed(1)) + + '' + : '--') + + '' + + 'One-Click' + + remove_opt + + '' + + ''; + } + $('#softList').html(zbody); + $('#softPage').html(''); + $('.searchInput').val(''); + }); + }, + remove_other_dep: function (name) { + bt.show_confirm(lan.soft.del_custom_item, lan.soft.confirm_del.replace('{1}', name), function () { + var loadT = layer.msg(lan.soft.deleting, { + icon: 16, + time: 0, + shade: 0.3, + }); + $.post( + '/deployment?action=DelPackage', + { + dname: name, + }, + function (rdata) { + layer.close(loadT); + if (rdata.status) soft.get_dep_list(); + setTimeout(function () { + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + }, 1000); + } + ); + }); + }, + input_package: function () { + var con = + '
                                  \ +
                                  Index name\ +
                                  \ + Format: [0-9A-Za-z_-]+, Do not have spaces and special characters\ +
                                  \ +
                                  \ +
                                  Name\ +
                                  \ + The name used to display to the list\ +
                                  \ +
                                  \ +
                                  PHP Version\ + \ + Please use multiple "," (comma) to separate, do not use PHP5.2\ +
                                  \ +
                                  Unblocked function\ + \ + Multiples should be separated by "," (comma), only the necessary functions are unblocked.\ +
                                  \ +
                                  Project version\ + \ + Currently imported project version\ +
                                  \ +
                                  Introduction\ +
                                  \ +
                                  \ +
                                  Upload project package\ + \ + Please upload the project package in zip format, which must contain the auto_insatll.json configuration file.\ +
                                  \ +
                                  \ + \ + \ +
                                  \ + '; + layer.open({ + type: 1, + title: 'Import a one-click deployment project package', + area: '600px', + closeBtn: 2, + shadeClose: false, + content: con, + }); + }, + update_package: function (p_name) { + $.post( + '/deployment?action=GetPackageOther', + { + p_name: p_name, + }, + function (rdata) { + var con = + '\ +
                                  Index name\ + \ + Format: [0-9A-Za-z_-]+, Do not have spaces and special characters\ +
                                  \ +
                                  Name\ + \ + The name used to display to the list\ +
                                  \ +
                                  PHP Version\ + \ + Please use multiple "," (comma) to separate, do not use PHP5.2\ +
                                  \ +
                                  Unblocked function\ + \ + Multiples should be separated by "," (comma), only the necessary functions are unblocked.\ +
                                  \ +
                                  Project version\ + \ + Currently imported project version\ +
                                  \ +
                                  Introduction\ +
                                  \ +
                                  \ +
                                  Upload project package\ + \ + Please upload the project package in zip format, which must contain the auto_insatll.json configuration file.\ +
                                  \ +
                                  \ + \ + \ +
                                  \ + '; + layer.open({ + type: 1, + title: 'Update one-click deployment project package', + area: '600px', + closeBtn: 2, + shadeClose: false, + content: con, + }); + } + ); + }, + input_package_to: function () { + var pdata = new FormData($('#input_package')[0]); + if (!pdata.get('name') || !pdata.get('title') || !pdata.get('version') || !pdata.get('php') || !pdata.get('ps')) { + layer.msg('The following are required (Index name / Name / Project version / PHP version / Introduction)', { + icon: 2, + }); + return; + } + var fs = $("input[name='dep_zip']")[0].files; + if (fs.length < 1) { + layer.msg('Please select the project package file', { + icon: 2, + }); + return; + } + var f = fs[0]; + if (f.type.indexOf('zip') == -1) { + layer.msg('Only supports files in zip format!'); + return; + } + if (!pdata.get('dep_zip')) pdata.append('dep_zip', f); + + var loadT = layer.msg('Importing...', { + icon: 16, + time: 0, + shade: 0.3, + }); + + $.ajax({ + url: '/deployment?action=AddPackage', + type: 'POST', + data: pdata, + processData: false, + contentType: false, + success: function (data) { + layer.close(loadT); + if (data.status) { + layer.closeAll(); + setCookie('depType', 100); + soft.get_dep_list(); + setTimeout(function () { + layer.msg('Successfully imported!'); + }, 1000); + } + }, + error: function (responseStr) { + layer.msg('Upload failed 2!', { + icon: 2, + }); + }, + }); + }, + flush_cache: function () { + bt.set_cookie('force', 1); + soft.get_list(); + }, + get_config_menu: function ( + name //获取设置菜单显示 + ) { + var meun = ''; + if (bt.os == 'Linux') { + var datas = { + public: [ + { + type: 'config', + title: lan.soft.config_edit, + }, + { + type: 'change_version', + title: lan.soft.nginx_version, + }, + ], + openlitespeed: [ + { + type: 'openliMa_set', + title: 'OpenLiteSpeed', + }, + ], + mysqld: [ + { + type: 'change_data_path', + title: lan.soft.save_path, + }, + { + type: 'change_mysql_port', + title: lan.site.port, + }, + { + type: 'change_mysql_ssl', + title: lan.site.site_menu_7, + }, + { + type: 'get_mysql_run_status', + title: lan.soft.status, + }, + { + type: 'get_mysql_status', + title: lan.soft.php_main7, + }, + { + type: 'mysql_log', + title: lan.soft.log, + }, + { + type: 'mysql_slow_log', + title: lan.public.slow_log, + }, + ], + phpmyadmin: [ + { + type: 'phpmyadmin_php', + title: lan.soft.php_version, + }, + { + type: 'phpmyadmin_safe', + title: lan.soft.safe, + }, + ], + memcached: [ + { + type: 'memcached_status', + title: lan.soft.php_main8, + }, + { + type: 'memcached_set', + title: lan.soft.php_main7, + }, + ], + redis: [ + { + type: 'get_redis_status', + title: lan.soft.php_main8, + }, + ], + tomcat: [ + { + type: 'log', + title: lan.soft.run_log, + }, + ], + apache: [ + { + type: 'apache_set', + title: lan.soft.php_main7, + }, + { + type: 'apache_status', + title: lan.soft.nginx_status, + }, + { + type: 'apache_format_log', + title: 'Logs format', + }, + { + type: 'log', + title: lan.soft.run_log, + }, + ], + nginx: [ + { + type: 'nginx_set', + title: lan.soft.php_main7, + }, + { + type: 'nginx_status', + title: lan.soft.nginx_status, + }, + { + type: 'nginx_format_log', + title: 'Logs format', + }, + { + type: 'log', + title: lan.soft.err_log, + }, + ], + }; + var arrs = datas.public; + if (name == 'phpmyadmin') arrs = []; + if (name == 'openlitespeed') arrs.length = 1; + if (name === 'pureftpd') arrs.push({ type: 'pureftpd_log', title: 'Logs Manage' }); + arrs = arrs.concat(datas[name]); + if (arrs) { + for (var i = 0; i < arrs.length; i++) { + var item = arrs[i]; + if (item) { + var tit = item.title.length >= 24 ? item.title : ''; + meun += '

                                  ' + item.title + '

                                  '; + } + } + } + } + return meun; + }, + set_soft_config: function (name) { + //软件设置 + var _this = this; + var loading = bt.load(); + bt.soft.get_soft_find(name, function (rdata) { + loading.close(); + + if (name == 'mysql') name = 'mysqld'; + var menuing = bt.open({ + type: 1, + area: '800px', + title: name + lan.soft.admin, + closeBtn: 2, + shift: 0, + content: + '
                                  ', + }); + var menu = $('.bt-soft-menu').data('data', rdata); + setTimeout(function () { + menu.append($('

                                  ' + lan.soft.service + '

                                  ')); + if (rdata.version_coexist) { + var ver = name.split('-')[1].replace('.', ''); + var opt_list = [ + { + type: 'set_php_config', + val: ver, + title: lan.soft.php_main5, + }, + { + type: 'config_edit', + val: ver, + title: lan.soft.config_edit, + }, + { + type: 'set_upload_limit', + val: ver, + title: lan.soft.php_main2, + }, + { + type: 'set_timeout_limit', + val: ver, + title: lan.soft.php_main3, + php53: true, + }, + { + type: 'config', + val: ver, + title: lan.soft.php_main4, + }, + { type: 'fpm_config', val: ver, title: 'FPM profile' }, + { + type: 'set_dis_fun', + val: ver, + title: lan.soft.php_main6, + }, + { + type: 'set_fpm_config', + val: ver, + title: lan.soft.php_main7, + apache24: true, + php53: true, + }, + { + type: 'get_php_status', + val: ver, + title: lan.soft.php_main8, + apache24: true, + php53: true, + }, + { + type: 'get_php_session', + val: ver, + title: lan.soft.php_main9, + apache24: true, + php53: true, + }, + { + type: 'get_fpm_logs', + val: ver, + title: lan.soft.log, + apache24: true, + php53: true, + }, + { + type: 'get_slow_logs', + val: ver, + title: lan.public.slow_log, + apache24: true, + php53: true, + }, + { + type: 'get_phpinfo', + val: ver, + title: 'phpinfo', + }, + ]; + + var phpSort = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + webcache = bt.get_cookie('serverType') == 'openlitespeed' ? true : false; + for (var i = 0; i < phpSort.length; i++) { + var item = opt_list[i]; + if (item) { + if (item.os == undefined || item['os'] == bt.os) { + if (name.indexOf('5.2') >= 0 && item.php53) continue; + if (webcache && (item.type == 'set_fpm_config' || item.type == 'get_php_status')) continue; + var apache24 = item.apache24 ? 'class="apache24"' : ''; + menu.append($('

                                  ' + item.title + '

                                  ').data('item', item)); + } + } + } + } else { + menu.append(soft.get_config_menu(name)); + } + $('.bt-w-menu p').click(function () { + $(this).addClass('bgw').siblings().removeClass('bgw'); + }); + $('.bt-w-menu p:eq(0)').trigger('click'); + bt.soft.get_soft_find('apache', function (rdata) { + if (rdata.setup) { + if (rdata.version.indexOf('2.2') >= 0) { + if (name.indexOf('php-') != -1) { + $('.apache24').hide(); + $('.bt_server').remove(); + $('.bt-w-menu p:eq(0)').trigger('click'); + } + + if (name.indexOf('apache') != -1) { + $('.bt-soft-menu p:eq(3)').remove(); + $('.bt-soft-menu p:eq(3)').remove(); + } + } + } + }); + }, 100); + }); + }, + get_tab_contents: function ( + key, + obj //获取设置菜单操作 + ) { + var data = $(obj).parents('.bt-soft-menu').data('data'); + var version = data.name; + if (data.name.indexOf('php-') >= 0) version = data.name.split('-')[1].replace('.', ''); + switch (key) { + case 'pureftpd_log': //ftp日志管理 + var tabCon = $('.soft-man-con').empty(); + bt.pub.get_ftp_logs(function (_status) { + tabCon.append( + '
                                  \ + Logs manage switch\ +
                                  \ + \ + \ +
                                  \ +
                                  • After enabling it, all login and operation records of FTP users will be logged.
                                  ' + ); + $('.ftp-log .isFtplog') + .unbind('click') + .click(function () { + var status = $(this).prev().prop('checked'); + bt.pub.set_ftp_logs(status ? 'stop' : 'start'); + }); + var pro = parseInt(bt.get_cookie('pro_end') || -1); + if (pro < 0) { + tabCon.append( + '
                                  \ +
                                  This feature is exclusive to the Professional version, Buy Now
                                  ' + ); + } + }); + break; + case 'service': + var tabCon = $('.soft-man-con').empty(); + var status_list = [ + { + opt: data.status ? 'stop' : 'start', + title: data.status ? lan.soft.stop : lan.soft.start, + }, + { + opt: 'restart', + title: lan.soft.restart, + }, + { + opt: 'reload', + title: lan.soft.reload, + }, + ]; + if (data.name == 'phpmyadmin') { + status_list = [status_list[0]]; + } else { + var btns = $('
                                  '); + for (var i = 0; i < status_list.length; i++) + btns.append(''); + tabCon.append( + '

                                  ' + + lan.soft.status + + ':' + + (data.status ? lan.soft.on : lan.soft.off) + + '

                                  '); + // for (var i = 0; i < status_list.length; i++) btns.append(''); + // tabCon.append('

                                  ' + lan.soft.status + ':' + (data.status ? lan.soft.running : lan.soft.stop) + '\ +

                                  \ +

                                  Public access address: ' + + data.ext.url + + '

                                  \ +
                                  ' + ); + tabCon.append( + '
                                    \ +
                                  • PhpMyAdmin enabling public access may have security risks. It is recommended not to enable it unnecessarily!
                                  • \ +
                                  • The current version of phpmyadin no longer relies on Nginx / Apache without requiring public access.
                                  • \ +
                                  • The service state of phpMyAdmin does not affect access to phpMyAdmin through the panel (non-public).
                                  • \ +
                                  • If the public access right is not turned on, the panel will take over the access right, that is, you need to log in to the panel to access.
                                  • \ +
                                  ' + ); + } + + var help = '
                                  • ' + lan.soft.mysql_mem_err + '
                                  '; + if (name == 'mysqld') tabCon.append(help); + break; + case 'config': + var tabCon = $('.soft-man-con').empty(); + tabCon.append('

                                  ' + lan.bt.edit_ps + '

                                  '); + tabCon.append(''); + tabCon.append(''); + tabCon.append(bt.render_help([lan.get('config_edit_ps', [version])])); + + var fileName = bt.soft.get_config_path(version); + if (data.php_ini) fileName = data.php_ini; + var loadT = bt.load(lan.soft.get); + bt.send( + 'GetFileBody', + 'files/GetFileBody', + { + path: fileName, + }, + function (rdata) { + loadT.close(); + $('#textBody').text(rdata.data); + $('.CodeMirror').remove(); + var editor = CodeMirror.fromTextArea(document.getElementById('textBody'), { + extraKeys: { + 'Ctrl-Space': 'autocomplete', + }, + lineNumbers: true, + matchBrackets: true, + }); + editor.focus(); + $('.CodeMirror-scroll').css({ + height: '510px', + margin: 0, + padding: 0, + }); + $('#OnlineEditFileBtn').click(function () { + $('#textBody').text(editor.getValue()); + bt.soft.save_config(fileName, editor.getValue()); + }); + } + ); + break; + case 'fpm_config': + var tabCon = $('.soft-man-con').empty(); + tabCon.append('

                                  ' + lan.bt.edit_ps + '

                                  '); + tabCon.append('
                                  '); + tabCon.append(''); + var _arry = ['If you do not understand the php-fpm configuration file, please do not modify it!']; + tabCon.append(bt.render_help(_arry)); + $('.return_php_info').click(function () { + $('.bt-soft-menu p:eq(12)').click(); + }); + var fileName = bt.soft.get_config_path(version).replace('php.ini', 'php-fpm.conf'); + var loadT = bt.load(lan.soft.get); + var config = bt.aceEditor({ el: 'textBody', path: fileName }); + $('#OnlineEditFileBtn').click(function () { + bt.saveEditor(config); + }); + break; + case 'change_version': + var _list = []; + var opt_version = ''; + for (var i = 0; i < data.versions.length; i++) { + if (data.versions[i].setup) opt_version = data.name + ' ' + data.versions[i].m_version; + _list.push({ + value: data.name + ' ' + data.versions[i].m_version, + title: data.name + ' ' + data.versions[i].m_version, + }); + } + var _form_data = { + title: lan.soft.select_version, + items: [ + { + name: 'phpVersion', + width: '160px', + type: 'select', + value: opt_version, + items: _list, + }, + { + name: 'btn_change_version', + type: 'button', + text: lan.soft.version_to, + callback: function (ldata) { + if (ldata.phpVersion == opt_version) { + bt.msg({ + msg: 'Is already[' + opt_version + ']', + icon: 2, + }); + return; + } + if (data.name == 'mysql') { + var ver = ldata.phpVersion.split('mysql '), + pdata = { sName: 'mysql', version: ver[1], type: 0 }; + $.post('/plugin?action=check_install_limit', pdata, function (rdata) { + if (rdata !== null && rdata.status == false) { + bt.msg({ msg: rdata.msg, icon: 2, time: 3000 }); + return false; + } + bt.database.get_list(1, '', function (ddata) { + if (ddata.data.length > 0) { + bt.msg({ + msg: lan.soft.mysql_d, + icon: 5, + time: 5000, + }); + return; + } + bt.soft.install_soft(data, ldata.phpVersion.split(' ')[1], 0); + }); + }); + } else { + bt.soft.install_soft(data, ldata.phpVersion.split(' ')[1], 0); + } + }, + }, + ], + }; + bt.render_form_line(_form_data, '', $('.soft-man-con').empty()); + break; + case 'change_data_path': + bt.send('GetMySQLInfo', 'database/GetMySQLInfo', {}, function (rdata) { + var form_data = { + items: [ + { + type: 'text', + name: 'datadir', + value: rdata.datadir, + event: { + css: 'glyphicon-folder-open', + callback: function (obj) { + bt.select_path(obj); + }, + }, + }, + { + name: 'btn_change_path', + type: 'button', + text: lan.soft.mysql_to, + callback: function (ldata) { + var loadT = bt.load(lan.soft.mysql_to_msg1); + bt.send( + 'SetDataDir', + 'database/SetDataDir', + { + datadir: ldata.datadir, + }, + function (rdata) { + loadT.close(); + bt.msg(rdata); + } + ); + }, + }, + ], + }; + bt.render_form_line(form_data, '', $('.soft-man-con').empty()); + }); + break; + case 'change_mysql_port': + bt.send('GetMySQLInfo', 'database/GetMySQLInfo', {}, function (rdata) { + var form_data = { + items: [ + { + type: 'text', + width: '100px', + name: 'port', + value: rdata.port, + }, + { + name: 'btn_change_port', + type: 'button', + text: lan.public.edit, + callback: function (ldata) { + var loadT = bt.load(); + bt.send( + 'SetMySQLPort', + 'database/SetMySQLPort', + { + port: ldata.port, + }, + function (rdata) { + loadT.close(); + bt.msg(rdata); + } + ); + }, + }, + ], + }; + bt.render_form_line(form_data, '', $('.soft-man-con').empty()); + }); + break; + case 'change_mysql_ssl': + bt.send('check_mysql_ssl_status', 'database/check_mysql_ssl_status', {}, function (rdata) { + var form_data = { + title: 'Mysql SSL', + items: [ + { + type: 'switch', + name: 'write_ssl', + value: rdata, + }, + ], + }; + bt.render_form_line(form_data, '', $('.soft-man-con').empty()); + var downssl = '/www/server/data/ssl.zip'; + $('.soft-man-con').append( + bt.render_help(['After setting, manually restart the database to take effect', "Download Mysql SSL self-signed certificate【SSL.zip】"]) + ); + $('a.downssl').click(function () { + window.open('/download?filename=' + encodeURIComponent(downssl)); + }); + $('#write_ssl').change(function () { + var loadT = bt.load(); + $.post('/database?action=write_ssl_to_mysql', function (rdata) { + loadT.close(loadT); + var open_type = $('#write_ssl').prop('checked') ? 'turned on' : 'turned off', + loadP = layer.confirm( + 'The SSL setting is ' + open_type + ' successfully.
                                  Do you need to restart the database immediately to make it effective?', + { + btn: ['Restart now', 'Restart later'], + icon: 3, + title: 'Confirm Restart?', + }, + function () { + bt.pub.set_server_status('mysql', 'restart'); + }, + function () { + layer.close(loadP); + } + ); + }); + }); + }); + break; + case 'get_mysql_run_status': + bt.send('GetRunStatus', 'database/GetRunStatus', {}, function (rdata) { + var cache_size = ((parseInt(rdata.Qcache_hits) / (parseInt(rdata.Qcache_hits) + parseInt(rdata.Qcache_inserts))) * 100).toFixed(2) + '%'; + if (cache_size == 'NaN%') cache_size = 'OFF'; + var title10 = ((1 - rdata.Threads_created / rdata.Connections) * 100).toFixed(2); + var title11 = ((1 - rdata.Key_reads / rdata.Key_read_requests) * 100).toFixed(2); + var title12 = ((1 - rdata.Innodb_buffer_pool_reads / rdata.Innodb_buffer_pool_read_requests) * 100).toFixed(2); + var title14 = ((rdata.Created_tmp_disk_tables / rdata.Created_tmp_tables) * 100).toFixed(2); + var Con = + '
                                  \ + \ + \ + \ + \ + \ + \ +
                                  ' + + lan.soft.mysql_status_title1 + + '' + + getLocalTime(rdata.Run) + + '' + + lan.soft.mysql_status_title5 + + '' + + parseInt(rdata.Questions / rdata.Uptime) + + '
                                  ' + + lan.soft.mysql_status_title2 + + '' + + rdata.Connections + + '' + + lan.soft.mysql_status_title6 + + '' + + parseInt((parseInt(rdata.Com_commit) + parseInt(rdata.Com_rollback)) / rdata.Uptime) + + '
                                  ' + + lan.soft.mysql_status_title3 + + '' + + ToSize(rdata.Bytes_sent) + + '' + + lan.soft.mysql_status_title7 + + '' + + rdata.File + + '
                                  ' + + lan.soft.mysql_status_title4 + + '' + + ToSize(rdata.Bytes_received) + + '' + + lan.soft.mysql_status_title8 + + '' + + rdata.Position + + '
                                  \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
                                  ' + + lan.soft.mysql_status_title9 + + '' + + rdata.Threads_running + + '/' + + rdata.Max_used_connections + + '' + + lan.soft.mysql_status_ps1 + + '
                                  ' + + lan.soft.mysql_status_title10 + + '' + + (!isNaN(title10) ? title10 : '0') + + '%' + + lan.soft.mysql_status_ps2 + + '
                                  ' + + lan.soft.mysql_status_title11 + + '' + + (!isNaN(title11) ? title11 : '0') + + '%' + + lan.soft.mysql_status_ps3 + + '
                                  ' + + lan.soft.mysql_status_title12 + + '' + + (!isNaN(title12) ? title12 : '0') + + '%' + + lan.soft.mysql_status_ps4 + + '
                                  ' + + lan.soft.mysql_status_title13 + + '' + + cache_size + + '' + + lan.soft.mysql_status_ps5 + + '
                                  ' + + lan.soft.mysql_status_title14 + + '' + + (!isNaN(title14) ? title14 : '0') + + '%' + + lan.soft.mysql_status_ps6 + + '
                                  ' + + lan.soft.mysql_status_title15 + + '' + + rdata.Open_tables + + '' + + lan.soft.mysql_status_ps7 + + '
                                  ' + + lan.soft.mysql_status_title16 + + '' + + rdata.Select_full_join + + '' + + lan.soft.mysql_status_ps8 + + '
                                  ' + + lan.soft.mysql_status_title17 + + '' + + rdata.Select_range_check + + '' + + lan.soft.mysql_status_ps9 + + '
                                  ' + + lan.soft.mysql_status_title18 + + '' + + rdata.Sort_merge_passes + + '' + + lan.soft.mysql_status_ps10 + + '
                                  ' + + lan.soft.mysql_status_title19 + + '' + + rdata.Table_locks_waited + + '' + + lan.soft.mysql_status_ps11 + + '
                                  '; + $('.soft-man-con').html(Con); + }); + break; + case 'get_mysql_status': + bt.send('GetDbStatus', 'database/GetDbStatus', {}, function (rdata) { + var key_buffer_size = bt.format_size(rdata.mem.key_buffer_size, false, 0, 'MB'); + var query_cache_size = bt.format_size(rdata.mem.query_cache_size, false, 0, 'MB'); + var tmp_table_size = bt.format_size(rdata.mem.tmp_table_size, false, 0, 'MB'); + var innodb_buffer_pool_size = bt.format_size(rdata.mem.innodb_buffer_pool_size, false, 0, 'MB'); + var innodb_additional_mem_pool_size = bt.format_size(rdata.mem.innodb_additional_mem_pool_size, false, 0, 'MB'); + var innodb_log_buffer_size = bt.format_size(rdata.mem.innodb_log_buffer_size, false, 0, 'MB'); + + var sort_buffer_size = bt.format_size(rdata.mem.sort_buffer_size, false, 0, 'MB'); + var read_buffer_size = bt.format_size(rdata.mem.read_buffer_size, false, 0, 'MB'); + var read_rnd_buffer_size = bt.format_size(rdata.mem.read_rnd_buffer_size, false, 0, 'MB'); + var join_buffer_size = bt.format_size(rdata.mem.join_buffer_size, false, 0, 'MB'); + var thread_stack = bt.format_size(rdata.mem.thread_stack, false, 0, 'MB'); + var binlog_cache_size = bt.format_size(rdata.mem.binlog_cache_size, false, 0, 'MB'); + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size; + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size; + var memSize = a + rdata.mem.max_connections * b; + + var mysql_select = { + 1: { + title: '1-2GB', + data: { + key_buffer_size: 128, + query_cache_size: 64, + tmp_table_size: 64, + innodb_buffer_pool_size: 256, + sort_buffer_size: 768, + read_buffer_size: 768, + read_rnd_buffer_size: 512, + join_buffer_size: 1024, + thread_stack: 256, + binlog_cache_size: 64, + thread_cache_size: 64, + table_open_cache: 128, + max_connections: 100, + }, + }, + 2: { + title: '2-4GB', + data: { + key_buffer_size: 256, + query_cache_size: 128, + tmp_table_size: 384, + innodb_buffer_pool_size: 384, + sort_buffer_size: 768, + read_buffer_size: 768, + read_rnd_buffer_size: 512, + join_buffer_size: 2048, + thread_stack: 256, + binlog_cache_size: 64, + thread_cache_size: 96, + table_open_cache: 192, + max_connections: 200, + }, + }, + 3: { + title: '4-8GB', + data: { + key_buffer_size: 384, + query_cache_size: 192, + tmp_table_size: 512, + innodb_buffer_pool_size: 512, + sort_buffer_size: 1024, + read_buffer_size: 1024, + read_rnd_buffer_size: 768, + join_buffer_size: 2048, + thread_stack: 256, + binlog_cache_size: 128, + thread_cache_size: 128, + table_open_cache: 384, + max_connections: 300, + }, + }, + 4: { + title: '8-16GB', + data: { + key_buffer_size: 512, + query_cache_size: 256, + tmp_table_size: 1024, + innodb_buffer_pool_size: 1024, + sort_buffer_size: 2048, + read_buffer_size: 2048, + read_rnd_buffer_size: 1024, + join_buffer_size: 4096, + thread_stack: 384, + binlog_cache_size: 192, + thread_cache_size: 192, + table_open_cache: 1024, + max_connections: 400, + }, + }, + 5: { + title: '16-32GB', + data: { + key_buffer_size: 1024, + query_cache_size: 384, + tmp_table_size: 2048, + innodb_buffer_pool_size: 4096, + sort_buffer_size: 4096, + read_buffer_size: 4096, + read_rnd_buffer_size: 2048, + join_buffer_size: 8192, + thread_stack: 512, + binlog_cache_size: 256, + thread_cache_size: 256, + table_open_cache: 2048, + max_connections: 500, + }, + }, + }; + var mysql_arrs = [ + { + value: 0, + title: lan.soft.mysql_set_select, + }, + ]; + for (var key in mysql_select) + mysql_arrs.push({ + value: key, + title: mysql_select[key].title, + }); + + var form_datas = [ + { + items: [ + { + title: lan.soft.mysql_set_msg, + name: 'mysql_set', + type: 'select', + items: mysql_arrs, + callback: function (item) { + if (item.val() > 0) { + var data = mysql_select[item.val()].data; + for (var key in data) $('.' + key).val(data[key]); + if (!data.query_cache_size) data['query_cache_size'] = 0; + $("input[name='max_connections']").trigger('change'); + } + }, + }, + { + title: lan.soft.mysql_set_maxmem, + name: 'memSize', + width: '70px', + disabled: true, + value: memSize.toFixed(2), + ps: 'MB', + }, + ], + }, + { + title: 'key_buffer_size', + type: 'number', + name: 'key_buffer_size', + width: '70px', + value: key_buffer_size, + ps: 'MB, ' + lan.soft.mysql_set_key_buffer_size + '', + }, + { + title: 'query_cache_size', + type: 'number', + name: 'query_cache_size', + width: '70px', + value: query_cache_size, + ps: 'MB, ' + lan.soft.mysql_set_query_cache_size + '', + }, + { + title: 'tmp_table_size', + type: 'number', + name: 'tmp_table_size', + width: '70px', + value: tmp_table_size, + ps: 'MB, ' + lan.soft.mysql_set_tmp_table_size + '', + }, + { + title: 'innodb_buffer_pool_size', + type: 'number', + name: 'innodb_buffer_pool_size', + value: innodb_buffer_pool_size, + width: '70px', + ps: 'MB, ' + lan.soft.mysql_set_innodb_buffer_pool_size + '', + }, + { + title: 'innodb_log_buffer_size', + type: 'number', + name: 'innodb_log_buffer_size', + value: innodb_log_buffer_size, + width: '70px', + ps: 'MB, ' + lan.soft.mysql_set_innodb_log_buffer_size + '', + }, + { + title: 'sort_buffer_size', + type: 'number', + name: 'sort_buffer_size', + width: '70px', + value: sort_buffer_size * 1024, + ps: 'KB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_sort_buffer_size + '', + }, + { + title: 'read_buffer_size', + type: 'number', + name: 'read_buffer_size', + width: '70px', + value: read_buffer_size * 1024, + ps: 'KB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_buffer_size + '', + }, + { + title: 'read_rnd_buffer_size', + type: 'number', + name: 'read_rnd_buffer_size', + width: '70px', + value: read_rnd_buffer_size * 1024, + ps: 'KB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_read_rnd_buffer_size + '', + }, + { + title: 'join_buffer_size', + type: 'number', + name: 'join_buffer_size', + width: '70px', + value: join_buffer_size * 1024, + ps: 'KB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_join_buffer_size + '', + }, + { + title: 'thread_stack', + type: 'number', + name: 'thread_stack', + width: '70px', + value: thread_stack * 1024, + ps: 'KB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_thread_stack + '', + }, + { + title: 'binlog_cache_size', + type: 'number', + name: 'binlog_cache_size', + value: binlog_cache_size * 1024, + width: '70px', + ps: 'KB * ' + lan.soft.mysql_set_conn + ', ' + lan.soft.mysql_set_binlog_cache_size + '', + }, + { + title: 'thread_cache_size', + type: 'number', + name: 'thread_cache_size', + value: rdata.mem.thread_cache_size, + width: '70px', + ps: lan.soft.mysql_set_thread_cache_size, + }, + { + title: 'table_open_cache', + type: 'number', + name: 'table_open_cache', + value: rdata.mem.table_open_cache, + width: '70px', + ps: lan.soft.mysql_set_table_open_cache, + }, + { + title: 'max_connections', + type: 'number', + name: 'max_connections', + value: rdata.mem.max_connections, + width: '70px', + ps: lan.soft.mysql_set_max_connections, + }, + { + items: [ + { + text: lan.soft.mysql_set_restart, + type: 'button', + name: 'bt_mysql_restart', + callback: function (ldata) { + bt.pub.set_server_status('mysqld', 'restart'); + }, + }, + { + text: lan.public.save, + type: 'button', + name: 'bt_mysql_save', + callback: function (ldata) { + ldata.query_cache_type = 0; + if (ldata.query_cache_size > 0) ldata.query_cache_type = 1; + ldata['max_heap_table_size'] = ldata.tmp_table_size; + bt.send('SetDbConf', 'database/SetDbConf', ldata, function (rdata) { + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + }); + }, + }, + ], + }, + ]; + var tabCon = $('.soft-man-con').empty().append("
                                  "); + for (var i = 0; i < form_datas.length; i++) { + bt.render_form_line(form_datas[i], '', $('.tab-db-status')); + } + + $(".tab-db-status input[name*='size'],.tab-db-status input[name='max_connections'],.tab-db-status input[name='thread_stack']").change(function () { + var key_buffer_size = parseInt($("input[name='key_buffer_size']").val()); + var query_cache_size = parseInt($("input[name='query_cache_size']").val()); + var tmp_table_size = parseInt($("input[name='tmp_table_size']").val()); + var innodb_buffer_pool_size = parseInt($("input[name='innodb_buffer_pool_size']").val()); + var innodb_log_buffer_size = parseInt($("input[name='innodb_log_buffer_size']").val()); + + var sort_buffer_size = $("input[name='sort_buffer_size']").val() / 1024; + var read_buffer_size = $("input[name='read_buffer_size']").val() / 1024; + var read_rnd_buffer_size = $("input[name='read_rnd_buffer_size']").val() / 1024; + var join_buffer_size = $("input[name='join_buffer_size']").val() / 1024; + var thread_stack = $("input[name='thread_stack']").val() / 1024; + var binlog_cache_size = $("input[name='binlog_cache_size']").val() / 1024; + var max_connections = $("input[name='max_connections']").val(); + + var a = key_buffer_size + query_cache_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size; + var b = sort_buffer_size + read_buffer_size + read_rnd_buffer_size + join_buffer_size + thread_stack + binlog_cache_size; + var memSize = a + max_connections * b; + $("input[name='memSize']").val(memSize.toFixed(2)); + }); + }); + break; + case 'mysql_log': + var loadT = bt.load(); + bt.send( + 'BinLog', + 'database/BinLog', + { + status: 1, + }, + function (rdata) { + loadT.close(); + var limitCon = + '

                                  \ + ' + + lan.soft.mysql_log_bin + + ' ' + + ToSize(rdata.msg) + + '\ + \ +

                                  ' + + lan.soft.mysql_log_err + + '

                                  \ + \ +

                                  '; + $('.soft-man-con').html(limitCon); + + //设置二进制日志 + $('.btn-bin').click(function () { + var loadT = layer.msg(lan.public.the, { + icon: 16, + time: 0, + shade: 0.3, + }); + $.post('/database?action=BinLog', '', function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 5, + }); + soft.get_tab_contents('mysql_log'); + }); + }); + + //清空日志 + $('.btn-clear').click(function () { + var loadT = layer.msg(lan.public.the, { + icon: 16, + time: 0, + shade: 0.3, + }); + $.post('/database?action=GetErrorLog', 'close=1', function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 5, + }); + soft.get_tab_contents('mysql_log'); + }); + }); + bt.send('GetErrorLog', 'database/GetErrorLog', {}, function (error_body) { + if (error_body.status === false) { + layer.msg(error_body.msg, { + icon: 5, + }); + error_body = lan.soft.mysql_log_ps1; + } + if (error_body == '') error_body = lan.soft.mysql_log_ps1; + $('#error_log').text(error_body); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + } + ); + break; + case 'mysql_slow_log': + var loadT = bt.load(); + bt.send('GetSlowLogs', 'database/GetSlowLogs', {}, function (logs) { + loadT.close(); + if (!logs.status) { + logs.msg = ''; + } + if (logs.msg == '') logs.msg = lan.soft.no_slow_log; + var phpCon = ''; + $('.soft-man-con').html(phpCon); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + break; + case 'log': + var loadT = bt.load(lan.public.the_get); + bt.send( + 'GetOpeLogs', + 'ajax/GetOpeLogs', + { + path: '/www/wwwlogs/nginx_error.log', + }, + function (rdata) { + loadT.close(); + if (rdata.msg == '') rdata.msg = lan.soft.no_log; + var ebody = + '
                                  '; + $('.soft-man-con').html(ebody); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + } + ); + break; + case 'nginx_status': + var loadT = bt.load(); + bt.send('GetNginxStatus', 'ajax/GetNginxStatus', {}, function (rdata) { + loadT.close(); + $('.soft-man-con').html("
                                  "); + var arrs = []; + arrs[lan.bt.nginx_active] = rdata.active; + arrs[lan.bt.nginx_accepts] = rdata.accepts; + arrs[lan.bt.nginx_handled] = rdata.handled; + arrs[lan.bt.nginx_requests] = rdata.requests; + arrs[lan.bt.nginx_reading] = rdata.Reading; + arrs[lan.bt.nginx_writing] = rdata.Writing; + arrs[lan.bt.nginx_waiting] = rdata.Waiting; + arrs[lan.bt.nginx_worker] = rdata.worker; + arrs[lan.bt.nginx_workercpu] = rdata.workercpu; + arrs[lan.bt.nginx_workermen] = rdata.workermen; + bt.render_table('tab-nginx-status', arrs); + }); + break; + case 'nginx_format_log': + var loadT = bt.load(); + bt.send('get_nginx_access_log_format', 'config/get_nginx_access_log_format', {}, function (rdata) { + $('.soft-man-con').html( + "
                                  NameFormatOpt
                                  " + ); + bt.send('get_nginx_access_log_format_parameter', 'config/get_nginx_access_log_format_parameter', {}, function (res) { + loadT.close(); + var _format_ul = '
                                    '; + Object.keys(res).map(function (key) { + _format_ul += '
                                  • ' + key + ' : ' + res[key] + '
                                  • '; + }); + _format_ul += '
                                  '; + for (const j in rdata) { + if (rdata.hasOwnProperty(j)) { + const result = rdata[j]; + var _format = result.map(function (item, index) { + return Object.keys(item)[0]; + }); + var element = '' + _format.join('') + '', + _td = + '' + + j + + '\ + ' + + element + + '\ + Apply | Set | Del'; + $('#tab-nginx-logs-format tbody').append(_td); + //表格头固定 + $('#tab-nginx-logs-format') + .parent() + .on('scroll', function () { + var scrollTop = $('#tab-nginx-logs-format').parent().scrollTop(); + $('#tab-nginx-logs-format thead').css({ transform: 'translateY(' + scrollTop + 'px)', position: 'relative', 'z-index': '1' }); + }); + } + } + $('.table-add-format, .table-set-format').click(function () { + if ($(this).hasClass('table-set-format')) { + var format_title = 'Set format', + first_format = '', + add_type = 'edit', + td_format = $(this).parent().prev().find('.nginx-one-format'); + format_name = $(this).parent().attr('data-name'); + for (var i = 0; i < td_format.length; i++) { + first_format += + '
                                  \ +
                                  \ +
                                  \ +
                                  ' + + td_format.eq(i).text() + + '
                                  \ + \ +
                                  \ +
                                  \ + Del\ +
                                  '; + } + } else { + var format_title = 'Add format', + format_name = '', + add_type = 'add', + first_format = '', + format_list = ['$http_x_forwarded_for', '$remote_addr', '-', '[$time_local]', '$request', '$status', '$body_bytes_sent', '$http_referer', '$http_user_agent']; + for (var i = 0; i < format_list.length; i++) { + first_format += + '
                                  \ +
                                  \ +
                                  \ +
                                  ' + + format_list[i] + + '
                                  \ + \ +
                                  \ +
                                  \ + Del\ +
                                  '; + } + } + layer.open({ + type: 1, + title: format_title, + closeBtn: 2, + area: '375px', + btn: ['Confirm', 'Cancel'], + content: + '
                                  \ +
                                  \ + Name: \ + \ +
                                  \ + Format:\ +
                                  \ +
                                  ' + + first_format + + '
                                  \ + ' + + _format_ul + + '\ +
                                  \ + \ +
                                  • The format are executed in the order of parameters.
                                  \ +
                                  ', + success: function (index, layero) { + $('.nginx-add-format [name=log_format_name]').val(format_name); + $('.nginx-add-format').parents('.layui-layer-content').css('overflow', 'inherit'); + $('.nginx-add-format').on('click', '.bt-select-input', function (e) { + if ($(this).hasClass('active')) { + $('.nginx-add-format .bt-select-list').removeClass('active'); + $(this).removeClass('active').find('.bt-down-icon').css('transform', 'rotate(-45deg)'); + } else { + var _choose = $(this).find('.bt-select-val').text(); + $('.bt-select-list li').removeClass('active'); + $('.active.bt-select-input').removeClass('active').find('.bt-down-icon').css('transform', 'rotate(-45deg)'); + $('.bt-select-list li:contains(' + _choose + ')').addClass('active'); + $('.nginx-add-format .bt-select-list') + .addClass('active') + .css('top', $(this).offset().top - $('.format-table').offset().top + 33); + $(this).addClass('active').find('.bt-down-icon').css('transform', 'rotate(135deg)'); + } + e.stopPropagation(); + $(document).click(function (e) { + $('.active.bt-select-list').removeClass('active'); + $(this).find('.bt-down-icon').css('transform', 'rotate(-45deg)'); + e.preventDefault(); + e.stopPropagation(); + }); + }); + $('.nginx-add-format').on('click', '.bt-select-list li', function (e) { + var _value = $(this).attr('data-val'); + $('.active.bt-select-input').find('.bt-select-val').attr('data-active', _value).text(_value); + $('.nginx-add-format .bt-select-list,.active.bt-select-input').removeClass('active'); + }); + $('.btn-add-format').click(function (e) { + var _new_line = + '
                                  \ +
                                  \ +
                                  \ +
                                  $server_name
                                  \ + \ +
                                  \ +
                                  \ + Del\ +
                                  '; + $('.format-table').append(_new_line); + $('.format-table').scrollTop(10000000); + }); + $('.nginx-add-format').on('click', '.del-format', function (e) { + if ($('.del-format').length == 1) { + layer.msg('This is the last parameter.', { icon: 2 }); + return false; + } + $(this).parent().remove(); + }); + }, + yes: function (index, layero) { + if ($('.nginx-add-format [name=log_format_name]').val() == '') { + layer.msg('The format name cannot be empty!', { icon: 2 }); + return false; + } + var log_format = []; + $('.nginx-add-format .format-table .bt-select-val').each(function () { + log_format.push($(this).attr('data-active')); + }); + var format_data = { + log_format_name: $('.nginx-add-format [name=log_format_name]').val(), + log_format: JSON.stringify(log_format), + act: add_type, + }; + bt.send('add_nginx_access_log_format', 'config/add_nginx_access_log_format', format_data, function (res) { + layer.close(index); + $('.bt-soft-menu p:contains("Logs format")').click(); + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + }); + }, + }); + }); + $('#tab-nginx-logs-format').on('click', '.table-del-format', function (e) { + var log_format_name = $(this).parent().attr('data-name'), + loadP = layer.confirm( + 'Confirm to delete【' + log_format_name + '】this logs format?', + { + title: 'Confirm Delete?', + closeBtn: 2, + }, + function () { + layer.close(loadP); + bt.send('del_nginx_access_log_format', 'config/del_nginx_access_log_format', { log_format_name: log_format_name }, function (res) { + if (res.status) $('.bt-soft-menu p:contains("Logs format")').click(); + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + }); + } + ); + }); + $('#tab-nginx-logs-format').on('click', '.table-apply-format', function (e) { + var log_format_name = $(this).parent().attr('data-name'); + bt.send('get_nginx_access_log_format_parameter', 'config/get_nginx_access_log_format_parameter', { log_format_name: log_format_name }, function (res) { + if (Object.keys(res.site_list).length == 0) { + layer.msg('There is no site can apply!', { icon: 2 }); + return false; + } + var _site_ul = '
                                    '; + Object.keys(res.site_list).map(function (key) { + _site_ul += '
                                  • ' + key + '
                                  • '; + }); + _site_ul += '
                                  '; + layer.open({ + type: 1, + title: 'Website apply format', + closeBtn: 2, + btn: ['Confirm', 'Cancel'], + content: + '
                                  \ +
                                  \ + Site: \ + ' + + _site_ul + + '\ +
                                  \ +
                                  The checked site would used the format.
                                  \ +
                                  ', + success: function (index, layero) { + $('.nginx-add-site').on('click', '.format-site-list li', function (e) { + if ($(this).find('.bt_checkbox_groups').hasClass('active')) { + $(this).find('.bt_checkbox_groups').removeClass('active'); + } else { + $(this).find('.bt_checkbox_groups').addClass('active'); + } + }); + }, + yes: function (index, layero) { + var sites = []; + $('.nginx-add-site .format-site-list .bt_checkbox_groups.active').each(function () { + sites.push($(this).attr('data-val')); + }); + var format_data = { + log_format_name: log_format_name, + sites: JSON.stringify(sites), + }; + bt.send('set_format_log_to_website', 'config/set_format_log_to_website', format_data, function (res) { + layer.close(index); + if (res.status) $('.bt-soft-menu p:contains("Logs format")').click(); + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + }); + }, + }); + }); + }); + }); + }); + break; + case 'apache_format_log': + var loadT = bt.load(); + bt.send('get_httpd_access_log_format', 'config/get_httpd_access_log_format', {}, function (rdata) { + $('.soft-man-con').html( + "
                                  NameFormatOpt
                                  " + ); + bt.send('get_httpd_access_log_format_parameter', 'config/get_httpd_access_log_format_parameter', {}, function (res) { + loadT.close(); + var _format_ul = '
                                    '; + Object.keys(res).map(function (key) { + _format_ul += '
                                  • ' + key + ' : ' + res[key] + '
                                  • '; + }); + _format_ul += '
                                  '; + for (const j in rdata) { + if (rdata.hasOwnProperty(j)) { + const result = rdata[j]; + var _format = result.map(function (item, index) { + return Object.keys(item)[0]; + }); + var element = '' + _format.join('') + '', + _td = + '' + + j + + '\ + ' + + element + + '\ + Apply | Set | Del'; + $('#tab-nginx-logs-format tbody').append(_td); + //表格头固定 + $('#tab-nginx-logs-format') + .parent() + .on('scroll', function () { + var scrollTop = $('#tab-nginx-logs-format').parent().scrollTop(); + $('#tab-nginx-logs-format thead').css({ transform: 'translateY(' + scrollTop + 'px)', position: 'relative', 'z-index': '1' }); + }); + } + } + $('.table-add-format, .table-set-format').click(function () { + if ($(this).hasClass('table-set-format')) { + var format_title = 'Set format', + first_format = '', + add_type = 'edit', + td_format = $(this).parent().prev().find('.nginx-one-format'); + format_name = $(this).parent().attr('data-name'); + for (var i = 0; i < td_format.length; i++) { + first_format += + '
                                  \ +
                                  \ +
                                  \ +
                                  ' + + td_format.eq(i).text() + + '
                                  \ + \ +
                                  \ +
                                  \ + Del\ +
                                  '; + } + } else { + var format_title = 'Add format', + format_name = '', + add_type = 'add', + first_format = '', + format_list = ['%{X-Forwarded-For}i', '%h', '%l', '%u', '%t', '%r', '%>s', '%b', '%{Referer}i', '%{User-agent}i']; + for (var i = 0; i < format_list.length; i++) { + first_format += + '
                                  \ +
                                  \ +
                                  \ +
                                  ' + + format_list[i] + + '
                                  \ + \ +
                                  \ +
                                  \ + Del\ +
                                  '; + } + } + layer.open({ + type: 1, + title: format_title, + closeBtn: 2, + area: '375px', + btn: ['Confirm', 'Cancel'], + content: + '
                                  \ +
                                  \ + Name: \ + \ +
                                  \ + Format:\ +
                                  \ +
                                  ' + + first_format + + '
                                  \ + ' + + _format_ul + + '\ +
                                  \ + \ +
                                  • The format are executed in the order of parameters.
                                  \ +
                                  ', + success: function (index, layero) { + $('.nginx-add-format [name=log_format_name]').val(format_name); + $('.nginx-add-format').parents('.layui-layer-content').css('overflow', 'inherit'); + $('.nginx-add-format').on('click', '.bt-select-input', function (e) { + if ($(this).hasClass('active')) { + $('.nginx-add-format .bt-select-list').removeClass('active'); + $(this).removeClass('active').find('.bt-down-icon').css('transform', 'rotate(-45deg)'); + } else { + var _choose = $(this).find('.bt-select-val').text(); + $('.bt-select-list li').removeClass('active'); + $('.active.bt-select-input').removeClass('active').find('.bt-down-icon').css('transform', 'rotate(-45deg)'); + $('.bt-select-list li:contains(' + _choose + ')').addClass('active'); + $('.nginx-add-format .bt-select-list') + .addClass('active') + .css('top', $(this).offset().top - $('.format-table').offset().top + 33); + $(this).addClass('active').find('.bt-down-icon').css('transform', 'rotate(135deg)'); + } + e.stopPropagation(); + $(document).click(function (e) { + $('.active.bt-select-list').removeClass('active'); + $(this).find('.bt-down-icon').css('transform', 'rotate(-45deg)'); + e.preventDefault(); + e.stopPropagation(); + }); + }); + $('.nginx-add-format').on('click', '.bt-select-list li', function (e) { + var _value = $(this).attr('data-val'); + $('.active.bt-select-input').find('.bt-select-val').attr('data-active', _value).text(_value); + $('.nginx-add-format .bt-select-list,.active.bt-select-input').removeClass('active'); + }); + $('.btn-add-format').click(function (e) { + var _new_line = + '
                                  \ +
                                  \ +
                                  \ +
                                  %>s
                                  \ + \ +
                                  \ +
                                  \ + Del\ +
                                  '; + $('.format-table').append(_new_line); + $('.format-table').scrollTop(10000000); + }); + $('.nginx-add-format').on('click', '.del-format', function (e) { + if ($('.del-format').length == 1) { + layer.msg('This is the last parameter.', { icon: 2 }); + return false; + } + $(this).parent().remove(); + }); + }, + yes: function (index, layero) { + if ($('.nginx-add-format [name=log_format_name]').val() == '') { + layer.msg('The format name cannot be empty!', { icon: 2 }); + return false; + } + var log_format = []; + $('.nginx-add-format .format-table .bt-select-val').each(function () { + log_format.push($(this).attr('data-active')); + }); + var format_data = { + log_format_name: $('.nginx-add-format [name=log_format_name]').val(), + log_format: JSON.stringify(log_format), + act: add_type, + }; + bt.send('add_httpd_access_log_format', 'config/add_httpd_access_log_format', format_data, function (res) { + layer.close(index); + if (res.status) $('.bt-soft-menu p:contains("Logs format")').click(); + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + }); + }, + }); + }); + $('#tab-nginx-logs-format').on('click', '.table-del-format', function (e) { + var log_format_name = $(this).parent().attr('data-name'), + loadP = layer.confirm( + 'Confirm to delete【' + log_format_name + '】this logs format?', + { + title: 'Confirm Delete?', + closeBtn: 2, + }, + function () { + layer.close(loadP); + bt.send('del_httpd_access_log_format', 'config/del_httpd_access_log_format', { log_format_name: log_format_name }, function (res) { + if (res.status) $('.bt-soft-menu p:contains("Logs format")').click(); + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + }); + } + ); + }); + $('#tab-nginx-logs-format').on('click', '.table-apply-format', function (e) { + var log_format_name = $(this).parent().attr('data-name'); + bt.send('get_httpd_access_log_format_parameter', 'config/get_httpd_access_log_format_parameter', { log_format_name: log_format_name }, function (res) { + if (Object.keys(res.site_list).length == 0) { + layer.msg('There is no site can apply!', { icon: 2 }); + return false; + } + var _site_ul = '
                                    '; + Object.keys(res.site_list).map(function (key) { + _site_ul += '
                                  • ' + key + '
                                  • '; + }); + _site_ul += '
                                  '; + layer.open({ + type: 1, + title: 'Website apply format', + closeBtn: 2, + btn: ['Confirm', 'Cancel'], + content: + '
                                  \ +
                                  \ + Site: \ + ' + + _site_ul + + '\ +
                                  \ +
                                  The checked site would used the format.
                                  \ +
                                  ', + success: function (index, layero) { + $('.nginx-add-site').on('click', '.format-site-list li', function (e) { + if ($(this).find('.bt_checkbox_groups').hasClass('active')) { + $(this).find('.bt_checkbox_groups').removeClass('active'); + } else { + $(this).find('.bt_checkbox_groups').addClass('active'); + } + }); + }, + yes: function (index, layero) { + var sites = []; + $('.nginx-add-site .format-site-list .bt_checkbox_groups.active').each(function () { + sites.push($(this).attr('data-val')); + }); + var format_data = { + log_format_name: log_format_name, + sites: JSON.stringify(sites), + }; + bt.send('set_httpd_format_log_to_website', 'config/set_httpd_format_log_to_website', format_data, function (res) { + layer.close(index); + if (res.status) $('.bt-soft-menu p:contains("Logs format")').click(); + layer.msg(res.msg, { icon: res.status ? 1 : 2 }); + }); + }, + }); + }); + }); + }); + }); + break; + case 'apache_status': + var loadT = bt.load(); + bt.send('GetApacheStatus', 'ajax/GetApacheStatus', {}, function (rdata) { + loadT.close(); + $('.soft-man-con').html("
                                  "); + var arrs = []; + arrs[lan.bt.apache_uptime] = rdata.UpTime; + arrs[lan.bt.apache_idleworkers] = rdata.IdleWorkers; + arrs[lan.bt.apache_totalaccesses] = rdata.TotalAccesses; + arrs[lan.bt.apache_totalkbytes] = rdata.TotalKBytes; + arrs[lan.bt.apache_workermem] = rdata.workermem; + arrs[lan.bt.apache_workercpu] = rdata.workercpu; + arrs[lan.bt.apache_reqpersec] = rdata.ReqPerSec; + arrs[lan.bt.apache_restarttime] = rdata.RestartTime; + arrs[lan.bt.apache_busyworkers] = rdata.BusyWorkers; + bt.render_table('tab-Apache-status', arrs); + }); + break; + case 'nginx_set': + var loadT = bt.load(); + bt.send('GetNginxValue', 'config/GetNginxValue', {}, function (rdata) { + loadT.close(); + var form_datas = []; + for (var i = 0; i < rdata.length; i++) { + if (rdata[i].name == 'worker_processes') { + form_datas.push({ + title: rdata[i].name, + name: rdata[i].name, + width: '60px', + value: rdata[i].value, + ps: rdata[i].ps, + text: '', + }); + } else if (rdata[i].name == 'gzip') { + form_datas.push({ + title: rdata[i].name, + type: 'select', + items: [ + { + title: lan.soft.on, + value: 'on', + }, + { + title: lan.soft.off, + value: 'off', + }, + ], + name: rdata[i].name, + width: '60px', + value: rdata[i].value, + ps: rdata[i].ps, + text: '', + }); + } else { + form_datas.push({ + title: rdata[i].name, + type: 'number', + name: rdata[i].name, + width: '60px', + value: rdata[i].value, + ps: rdata[i].ps, + text: '', + }); + } + } + form_datas.push({ + items: [ + { + text: lan.public.save, + type: 'button', + name: 'bt_nginx_save', + callback: function (item) { + delete item['bt_nginx_save']; + bt.send('SetNginxValue', 'config/SetNginxValue', item, function (rdata) { + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + }); + }, + }, + ], + }); + $('.soft-man-con').empty().append('
                                  '); + for (var i = 0; i < form_datas.length; i++) { + bt.render_form_line(form_datas[i], '', $('.soft-man-con .set_nginx_config')); + } + }); + break; + case 'apache_set': + var loadT = bt.load(); + bt.send('GetNginxValue', 'config/GetApacheValue', {}, function (rdata) { + loadT.close(); + var form_datas = []; + for (var i = 0; i < rdata.length; i++) { + if (rdata[i].name == 'KeepAlive') { + form_datas.push({ + title: rdata[i].name, + type: 'select', + items: [ + { + title: lan.soft.on, + value: 'on', + }, + { + title: lan.soft.off, + value: 'off', + }, + ], + name: rdata[i].name, + width: '65px', + value: rdata[i].value, + ps: rdata[i].ps, + text: '', + }); + } else { + form_datas.push({ + title: rdata[i].name, + type: 'number', + name: rdata[i].name, + width: '65px', + value: rdata[i].value, + ps: rdata[i].ps, + text: '', + }); + } + } + form_datas.push({ + items: [ + { + text: lan.public.save, + type: 'button', + name: 'bt_apache_save', + callback: function (item) { + delete item['bt_apache_save']; + bt.send('SetApacheValue', 'config/SetApacheValue', item, function (rdata) { + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 2, + }); + }); + }, + }, + ], + }); + $('.soft-man-con').empty().append('
                                  '); + for (var i = 0; i < form_datas.length; i++) { + bt.render_form_line(form_datas[i], '', $('.soft-man-con .set_Apache_config')); + } + }); + break; + case 'memcached_status': + case 'memcached_set': + var loadT = bt.load(lan.public.get_the); + bt.send('GetMemcachedStatus', 'ajax/GetMemcachedStatus', {}, function (rdata) { + loadT.close(); + if (key == 'memcached_set') { + var form_data = [ + { + title: 'BindIP', + name: 'ip', + width: '120px', + value: rdata.bind, + ps: lan.soft.listen_ip_tips, + }, + { + title: 'PORT', + name: 'port', + type: 'number', + width: '120px', + value: rdata.port, + ps: lan.soft.listen_port_tips, + }, + { + title: 'CACHESIZE', + name: 'cachesize', + type: 'number', + width: '120px', + value: rdata.cachesize, + ps: lan.soft.cache_size, + }, + { + title: 'MAXCONN', + name: 'maxconn', + type: 'number', + width: '120px', + value: rdata.maxconn, + ps: lan.soft.mac_connect, + }, + { + title: ' ', + items: [ + { + text: lan.public.save, + name: 'btn_set_memcached', + type: 'button', + callback: function (ldata) { + if (ldata.ip.split('.').length < 4) { + layer.msg(lan.soft.ip_format_err, { + icon: 2, + }); + return; + } + if (ldata.port < 1 || ldata.port > 65535) { + layer.msg(lan.soft.port_range_err, { + icon: 2, + }); + return; + } + if (ldata.cachesize < 8) { + layer.msg(lan.soft.cache_too_small, { + icon: 2, + }); + return; + } + if (ldata.maxconn < 4) { + layer.msg(lan.soft.connect_too_small, { + icon: 2, + }); + return; + } + var loadT = bt.load(lan.public.the); + bt.send('SetMemcachedCache', 'ajax/SetMemcachedCache', ldata, function (rdata) { + loadT.close(); + bt.msg(rdata); + }); + }, + }, + ], + }, + ]; + var tabCon = $('.soft-man-con').empty(); + for (var i = 0; i < form_data.length; i++) { + bt.render_form_line(form_data[i], '', tabCon); + } + return; + } else { + var arr = {}; + arr['BindIP'] = [rdata.bind, lan.soft.listen_ip]; + arr['PORT'] = [rdata.port, lan.soft.listen_port]; + arr['CACHESIZE'] = [rdata.cachesize + ' MB', lan.soft.max_cache]; + arr['MAXCONN'] = [rdata.maxconn, lan.soft.max_connect_limit]; + arr['curr_connections'] = [rdata.curr_connections, lan.soft.curr_connect]; + arr['cmd_get'] = [rdata.cmd_get, lan.soft.get_request_num]; + arr['get_hits'] = [rdata.get_hits, lan.soft.get_hit_num]; + arr['get_misses'] = [rdata.get_misses, lan.soft.get_miss_num]; + arr['hit'] = [rdata.hit.toFixed(2) + ' %', lan.soft.get_hit_percent]; + arr['curr_items'] = [rdata.curr_items, lan.soft.curr_cache_rows]; + arr['evictions'] = [rdata.evictions, lan.soft.mem_not_enough]; + arr['bytes'] = [ToSize(rdata.bytes), lan.soft.curr_mem_use]; + arr['bytes_read'] = [ToSize(rdata.bytes_read), lan.soft.request_size_total]; + arr['bytes_written'] = [ToSize(rdata.bytes_written), lan.soft.send_size_total]; + + var con = + '
                                  ' + + lan.soft.field + + '' + + lan.soft.curr_val + + '' + + lan.soft.instructions + + '
                                  '; + $('.soft-man-con').html(con); + bt.render_table('tab_memcached_status', arr, true); + } + }); + break; + case 'phpmyadmin_php': + bt.send('GetPHPVersion', 'site/GetPHPVersion', {}, function (rdata) { + var sdata = $('.bt-soft-menu').data('data'); + + var body = + "
                                  " + + lan.soft.php_version + + "
                                  '; + $('.soft-man-con').html(body); + $('.btn-success').click(function () { + var loadT = bt.load(lan.public.the); + bt.send( + 'setPHPMyAdmin', + 'ajax/setPHPMyAdmin', + { + phpversion: $('#get_phpVersion').val(), + }, + function (rdata) { + loadT.close(); + bt.msg(rdata); + if (rdata.status) { + setTimeout(function () { + window.location.reload(); + }, 3000); + } + } + ); + }); + }); + break; + case 'phpmyadmin_safe': + var sdata = $('.bt-soft-menu').data('data'), + sslPortNum = ''; + var con = + '
                                  \ + ' + + lan.soft.pma_port + + '\ + \ + \ +
                                  \ +
                                  \ + Open SSL\ + \ + \ + \ +
                                  \ +
                                  \ + SSL port\ + \ + \ +
                                  \ +
                                  \ + ' + + lan.soft.pma_pass + + '\ + \ + \ + \ +
                                  \ +
                                  \ +

                                  ' + + lan.soft.pma_user + + '

                                  \ +

                                  ' + + lan.soft.pma_pass1 + + '

                                  \ +

                                  ' + + lan.soft.pma_pass2 + + '

                                  \ +

                                  \ +
                                  \ +
                                  • ' + + lan.soft.pma_ps + + '
                                  '; + + $('.soft-man-con').html(con); + if (sdata.ext.port) { + $('.user_pw').show(); + } + + function get_phpmyadmin_ssl() { + var loading = bt.load('Getting SSL Status...'); + bt.send('get_phpmyadmin_ssl', 'ajax/get_phpmyadmin_ssl', {}, function (tdata) { + loading.close(); + $('#ssl_safe_checkbox').prop('checked', tdata.status); + $('#sslport').val(tdata.port); + }); + } + get_phpmyadmin_ssl(); + $('.phpmyadmin_port').click(function () { + var pmport = $('#pmport').val(); + var loadT = bt.load(lan.public.the); + bt.send( + 'setPHPMyAdmin', + 'ajax/setPHPMyAdmin', + { + port: pmport, + }, + function (rdata) { + loadT.close(); + bt.msg(rdata); + } + ); + }); + $('.ssl_safe_label').click(function () { + var stat = $('#ssl_safe_checkbox').prop('checked'); + bt.send( + 'set_phpmyadmin_ssl', + 'ajax/set_phpmyadmin_ssl', + { + v: !stat ? 1 : 0, + }, + function (rdata) { + bt.msg(rdata); + } + ); + setTimeout(function () { + get_phpmyadmin_ssl(); + }, 500); + }); + $('.ssl_port_button').click(function () { + var sslPort = $('#sslport').val(); + if (!bt.check_port(sslPort)) { + layer.msg(lan.firewall.port_err, { + icon: 2, + }); + return; + } + var loadTo = bt.load(lan.public.the); + if (sslPort > 0) { + bt.send( + 'change_phpmyadmin_ssl_port', + 'ajax/change_phpmyadmin_ssl_port', + { + port: sslPort, + }, + function (rdata) { + loadTo.close(); + bt.msg(rdata); + } + ); + } + }); + $('.phpmyadmin_safe').click(function () { + var stat = $('#phpmyadminsafe').prop('checked'); + if (stat) { + $('.user_pw').hide(); + set_phpmyadmin('close'); + } else { + $('.user_pw').show(); + } + }); + $('.phpmyadmin_safe_save').click(function () { + set_phpmyadmin('get'); + }); + + function set_phpmyadmin(msg) { + var type = 'password'; + if (msg == 'close') { + bt.confirm( + { + msg: lan.soft.pma_pass_close, + }, + function () { + var loading = bt.load(lan.public.the); + bt.send( + 'setPHPMyAdmin', + 'ajax/setPHPMyAdmin', + { + password: msg, + siteName: 'phpmyadmin', + }, + function (rdata) { + loading.close(); + bt.msg(rdata); + } + ); + } + ); + return; + } else { + username = $('#username_get').val(); + password_1 = $('#password_get_1').val(); + password_2 = $('#password_get_2').val(); + if (username.length < 1 || password_1.length < 1) { + bt.msg({ + msg: lan.soft.pma_pass_empty, + icon: 2, + }); + return; + } + if (password_1 != password_2) { + bt.msg({ + msg: lan.soft.pass_err_re, + icon: 2, + }); + return; + } + } + var loading = bt.load(lan.public.the); + bt.send( + 'setPHPMyAdmin', + 'ajax/setPHPMyAdmin', + { + password: password_1, + username: username, + siteName: 'phpmyadmin', + }, + function (rdata) { + loading.close(); + bt.msg(rdata); + setTimeout(function () { + location.reload(); + }, 1000); + } + ); + } + break; + case 'set_php_config': + if (!obj.notLoading) var loading = bt.load(lan.public.the); + bt.soft.php.get_config(version, function (rdata) { + if (!obj.notLoading) loading.close(); + obj.notLoading = false; + var divObj = document.getElementById('phpextdiv'); + var scrollTopNum = 0; + if (divObj) scrollTopNum = divObj.scrollTop; + + $('.soft-man-con') + .empty() + .append( + '
                                  ' + ); + + var list = []; + for (var i = 0; i < rdata.libs.length; i++) { + if (rdata.libs[i].versions.indexOf(version) == -1) continue; + list.push(rdata.libs[i]); + } + var _tab = bt.render({ + table: '#tab_phpext', + data: list, + columns: [ + { + field: 'name', + title: lan.soft.php_ext_name, + }, + { + field: 'type', + title: lan.soft.php_ext_type, + width: 64, + }, + { + field: 'msg', + title: lan.soft.php_ext_ps, + }, + { + field: 'status', + title: lan.soft.php_ext_status, + width: 40, + templet: function (item) { + return ''; + }, + }, + { + field: 'opt', + title: lan.public.action, + width: 60, + templet: function (item) { + var opt = '' + lan.soft.install + ''; + if (item['task'] == '-1' && item.phpversions.indexOf(version) != -1) { + opt = '' + lan.soft.the_install + ''; + } else if (item['task'] == '0' && item.phpversions.indexOf(version) != -1) { + opt = '' + lan.soft.sleep_install + ''; + } else if (item.status) { + opt = '' + lan.soft.uninstall + ''; + } + return opt; + }, + }, + ], + }); + var helps = [lan.soft.php_plug_tips1, lan.soft.php_plug_tips2]; + $('.soft-man-con').append(bt.render_help(helps)); + + var divObj = document.getElementById('phpextdiv'); + if (divObj) divObj.scrollTop = scrollTopNum; + $('a').click(function () { + var _obj = $(this); + if (_obj.hasClass('lib-uninstall')) { + bt.soft.php.un_install_php_lib(version, _obj.attr('data-name'), _obj.attr('data-title'), function (rdata) { + setTimeout(function () { + soft.get_tab_contents('set_php_config', obj); + }, 1000); + }); + } else if (_obj.hasClass('lib-install')) { + bt.soft.php.install_php_lib(version, _obj.attr('data-name'), _obj.attr('data-title'), function (rdata) { + setTimeout(function () { + soft.get_tab_contents('set_php_config', obj); + }, 1000); + }); + } + }); + setTimeout(function () { + if ($('.bt-soft-menu .bgw').text() === 'Install extensions') { + obj.notLoading = true; + soft.get_tab_contents('set_php_config', obj); + } + }, 3000); + }); + break; + case 'get_phpinfo': + var con = ''; + var p_status = { + true: 'Yes', + false: 'No', + }; + var loading = bt.load(lan.public.the); + $.post( + '/ajax?action=php_info', + { + php_version: version, + }, + function (php_info) { + loading.close(); + con += ''; + con += '

                                  ' + lan.soft.php_base_info + '

                                  '; + con += '
                                  '; + con += ''; + con += ''; + con += ''; + con += '
                                  ' + lan.soft.version + '' + php_info.phpinfo.php_version + '' + lan.soft.install_path + '' + php_info.phpinfo.php_path + '
                                  php.ini' + php_info.phpinfo.php_ini + '
                                  ' + lan.soft.loaded + '' + php_info.phpinfo.modules + '
                                  '; + Object.keys(php_info) + .sort() + .forEach(function (k) { + if (k !== 'phpinfo') { + con += '

                                  ' + php_info.phpinfo.keys[k] + '

                                  '; + con += ''; + var nkey = 0; + Object.keys(php_info[k]).forEach(function (key) { + if (nkey == 0) con += ''; + con += ''; + nkey++; + if (nkey >= 3) { + nkey = 0; + con += ''; + } + }); + + con += '
                                  ' + key + '' + p_status[php_info[k][key]] + '
                                  '; + } + }); + + $('.soft-man-con').html(con); + + $('#btn_phpinfo').click(function () { + var loadT = bt.load(lan.soft.get); + bt.send( + 'GetPHPInfo', + 'ajax/GetPHPInfo', + { + version: version, + }, + function (rdata) { + loadT.close(); + var content = rdata + .replace('a:link {color: #009; text-decoration: none; background-color: #fff;}', '') + .replace('a:link {color: #000099; text-decoration: none; background-color: #ffffff;}', ''); + bt.open({ + type: 1, + title: 'PHP-' + version + '-PHPINFO', + area: ['73%', '90%'], + closeBtn: 2, + shadeClose: true, + content: '
                                  ' + content + '
                                  ', + }); + } + ); + }); + } + ); + + break; + case 'config_edit': + bt.soft.php.get_php_config(version, function (rdata) { + var mlist = ''; + for (var i = 0; i < rdata.length; i++) { + var w = '70'; + if (rdata[i].name == 'error_reporting') w = '250'; + var ibody = ''; + switch (rdata[i].type) { + case 0: + var selected_1 = rdata[i].value == 1 ? 'selected' : ''; + var selected_0 = rdata[i].value == 0 ? 'selected' : ''; + ibody = + ''; + break; + case 1: + var selected_1 = rdata[i].value == 'On' ? 'selected' : ''; + var selected_0 = rdata[i].value == 'Off' ? 'selected' : ''; + ibody = + ''; + break; + } + mlist += '

                                  ' + rdata[i].name + '' + ibody + ', ' + rdata[i].ps + '

                                  '; + } + var tabCon = $('.soft-man-con').empty(); + tabCon.append('
                                  ' + mlist + '
                                  '); + var datas = { + title: ' ', + items: [ + { + name: 'btn_fresh', + text: lan.public.fresh, + type: 'button', + callback: function (ldata) { + soft.get_tab_contents(key, obj); + }, + }, + { + name: 'btn_save', + text: lan.public.save, + type: 'button', + callback: function (ldata) { + var loadT = bt.load(); + ldata['version'] = version; + bt.send('SetPHPConf', 'config/SetPHPConf', ldata, function (rdata) { + loadT.close(); + soft.get_tab_contents(key, obj); + bt.msg(rdata); + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(datas); + $('.conf_p').append(_form_data.html); + bt.render_clicks(_form_data.clicks); + $('.conf_p > .line').css('margin-top', '25px'); + }); + break; + case 'set_upload_limit': + bt.soft.php.get_limit_config(version, function (ret) { + var datas = [ + { + items: [ + { + title: '', + type: 'number', + width: '100px', + value: ret.max, + unit: 'MB', + name: 'phpUploadLimit', + }, + { + name: 'btn_limit_get', + text: lan.public.save, + type: 'button', + callback: function (ldata) { + var max = ldata.phpUploadLimit; + if (max < 2) { + layer.msg(lan.soft.php_upload_size, { + icon: 2, + }); + return; + } + bt.soft.php.set_upload_max(version, max, function (rdata) { + if (rdata.status) { + soft.get_tab_contents(key, obj); + } + bt.msg(rdata); + }); + }, + }, + ], + }, + ]; + var clicks = []; + var tabCon = $('.soft-man-con').empty().append("
                                  "); + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + $('.set_upload_limit').append(_form_data.html); + clicks = clicks.concat(_form_data.clicks); + } + bt.render_clicks(clicks); + }); + break; + case 'set_timeout_limit': + bt.soft.php.get_limit_config(version, function (ret) { + var datas = [ + { + items: [ + { + title: '', + type: 'number', + width: '100px', + value: ret.maxTime, + name: 'phpTimeLimit', + unit: 'Sec', + }, + { + name: 'btn_limit_get', + text: lan.public.save, + type: 'button', + callback: function (ldata) { + var max = ldata.phpTimeLimit; + bt.soft.php.set_php_timeout(version, max, function (rdata) { + if (rdata.status) { + soft.get_tab_contents(key, obj); + } + bt.msg(rdata); + }); + }, + }, + ], + }, + ]; + var clicks = []; + var tabCon = $('.soft-man-con').empty().append("
                                  "); + for (var i = 0; i < datas.length; i++) { + var _form_data = bt.render_form_line(datas[i]); + $('.set_timeout_limit').append(_form_data.html); + clicks = clicks.concat(_form_data.clicks); + } + bt.render_clicks(clicks); + }); + break; + case 'set_dis_fun': + var loading = bt.load(lan.public.the); + bt.soft.php.get_config(version, function (rdata) { + loading.close(); + var list = []; + var disable_functions = rdata.disable_functions.split(','); + for (var i = 0; i < disable_functions.length; i++) { + if (disable_functions[i] == '') continue; + list.push({ + name: disable_functions[i], + }); + } + var _bt_form = $("
                                  "); + var tabCon = $('.soft-man-con').empty().append(_bt_form); + var _line = bt.render_form_line( + { + title: '', + items: [ + { + name: 'disable_function_val', + placeholder: lan.soft.fun_ps1, + width: '410px', + }, + { + name: 'btn_disable_function_val', + text: lan.public.save, + type: 'button', + callback: function (ldata) { + var disable_functions = rdata.disable_functions.split(','); + if ($.inArray(ldata.disable_function_val, disable_functions) >= 0) { + bt.msg({ + msg: lan.soft.fun_msg, + icon: 5, + }); + return; + } + disable_functions.push(ldata.disable_function_val); + set_disable_functions(version, disable_functions.join(',')); + }, + }, + ], + }, + '', + _bt_form + ); + + bt.render_clicks(_line.clicks); + _bt_form.append("
                                  "); + var _tab = bt.render({ + table: '#blacktable', + data: list, + columns: [ + { + field: 'name', + title: lan.soft.php_ext_name, + }, + { + field: 'opt', + title: lan.public.action, + width: 50, + templet: function (item) { + var new_disable_functions = disable_functions.slice(); + new_disable_functions.splice($.inArray(item.name, new_disable_functions), 1); + return ( + '' + + lan.soft.del + + '' + ); + }, + }, + ], + }); + tabCon.append(bt.render_help([lan.soft.fun_ps2, lan.soft.fun_ps3])); + }); + break; + case 'set_fpm_config': + bt.soft.php.get_fpm_config(version, function (rdata) { + var datas = { + '1GB Ram': { + max_children: 30, + start_servers: 5, + min_spare_servers: 5, + max_spare_servers: 20, + }, + '2GB Ram': { + max_children: 50, + start_servers: 5, + min_spare_servers: 5, + max_spare_servers: 30, + }, + '4GB Ram': { + max_children: 80, + start_servers: 10, + min_spare_servers: 10, + max_spare_servers: 30, + }, + '8GB Ram': { + max_children: 120, + start_servers: 10, + min_spare_servers: 10, + max_spare_servers: 30, + }, + '16GB Ram': { + max_children: 200, + start_servers: 15, + min_spare_servers: 15, + max_spare_servers: 50, + }, + '32GB Ram': { + max_children: 300, + start_servers: 20, + min_spare_servers: 20, + max_spare_servers: 50, + }, + }; + var limits = [], + pmList = []; + var my_selected = ''; + var num_max = Number(rdata.max_children); + for (var k in datas) { + if (datas[k].max_children === num_max) { + my_selected = k; + } + limits.push({ + title: k, + value: k, + }); + } + var _form_datas = [ + { + title: lan.soft.concurrency_type, + name: 'limit', + value: my_selected, + type: 'select', + items: limits, + callback: function (iKey) { + var item = datas[iKey.val()]; + for (var sk in item) $('.' + sk).val(item[sk]); + }, + }, + { + title: 'Connection', + name: 'listen', + value: rdata.unix, + type: 'select', + items: [ + { title: 'UNIX socket', value: 'unix' }, + { title: 'TCP socket', value: 'tcp' }, + ], + ps: '* UNIX socket recommended', + }, + { + title: lan.soft.php_fpm_model, + name: 'pm', + value: rdata.pm, + type: 'select', + items: [ + { + title: lan.bt.static, + value: 'static', + }, + { + title: lan.bt.dynamic, + value: 'dynamic', + }, + { title: 'On-demand', value: 'ondemand' }, + ], + ps: '*' + lan.soft.php_fpm_ps1, + }, + { + title: 'max_children', + name: 'max_children', + value: rdata.max_children, + type: 'number', + width: '100px', + ps: '*' + lan.soft.php_fpm_ps2, + }, + { + title: 'start_servers', + name: 'start_servers', + value: rdata.start_servers, + type: 'number', + width: '100px', + ps: '*' + lan.soft.php_fpm_ps3, + }, + { + title: 'min_spare_servers', + name: 'min_spare_servers', + value: rdata.min_spare_servers, + type: 'number', + width: '100px', + ps: '*' + lan.soft.php_fpm_ps4, + }, + { + title: 'max_spare_servers', + name: 'max_spare_servers', + value: rdata.max_spare_servers, + type: 'number', + width: '100px', + ps: '*' + lan.soft.php_fpm_ps5, + }, + { + title: ' ', + text: lan.public.save, + name: 'btn_children_submit', + css: 'btn-success', + type: 'button', + callback: function (ldata) { + bt.pub.get_menm(function (memInfo) { + var limit_children = parseInt(memInfo['memTotal'] / 8); + if (limit_children < parseInt(ldata.max_children)) { + layer.msg(lan.soft.php_child_process.replace('{1}', limit_children), { + icon: 2, + }); + $("input[name='max_children']").focus(); + return; + } + if (parseInt(ldata.max_children) < parseInt(ldata.max_spare_servers)) { + layer.msg(lan.soft.php_fpm_err1, { + icon: 2, + }); + return; + } + if (parseInt(ldata.min_spare_servers) > parseInt(ldata.start_servers)) { + layer.msg(lan.soft.php_fpm_err2, { + icon: 2, + }); + return; + } + if (parseInt(ldata.max_spare_servers) < parseInt(ldata.min_spare_servers)) { + layer.msg(lan.soft.php_fpm_err3, { + icon: 2, + }); + return; + } + if (parseInt(ldata.max_children) < parseInt(ldata.start_servers)) { + layer.msg(lan.soft.php_fpm_err4, { + icon: 2, + }); + return; + } + if (parseInt(ldata.max_children) < 1 || parseInt(ldata.start_servers) < 1 || parseInt(ldata.min_spare_servers) < 1 || parseInt(ldata.max_spare_servers) < 1) { + layer.msg(lan.soft.php_fpm_err5, { + icon: 2, + }); + return; + } + ldata['version'] = version; + bt.soft.php.set_fpm_config(version, ldata, function (rdata) { + soft.get_tab_contents(key, obj); + bt.msg(rdata); + }); + }); + }, + }, + ]; + var tabCon = $('.soft-man-con').empty(); + var _c_form = $('
                                  '); + var clicks = []; + for (var i = 0; i < _form_datas.length; i++) { + var _form = bt.render_form_line(_form_datas[i]); + _c_form.append(_form.html); + clicks = clicks.concat(_form.clicks); + } + _c_form.append( + '
                                    \ +
                                  • [Max num of child processes] The larger the number, the stronger the concurrency,
                                         but max_children should not exceed 5000.
                                  • \ +
                                  • [Ram] Each PHP child process needs about 20MB of Ram,
                                         too large max_children will cause server instability.
                                  • \ +
                                  • [Static mode] In the static mode, the set number of child processes is always maintained,
                                         which has a large Ram overhead, but has a good concurrency capability.
                                  • \ +
                                  • [Dynamic mode] will recover the process according to the set max number of idle processes,
                                         the Ram overhead is small, it is recommended to use a small Ram machine.
                                  • \ +
                                  • [64GB Ram recommended value] max_children <= 1000, start / min_spare = 50, max_spare <= 200
                                  • \ +
                                  • [Multi-PHP Version] If you have installed multiple PHP versions and are using them,
                                         it is recommended to reduce the concurrent configuration appropriately.
                                  • \ +
                                  • [No database] If no database such as mysql is installed,
                                         it is recommended to set 2 times the recommended concurrency.
                                  • \ +
                                  • [Note] The above are the recommended configuration instructions.
                                         The online projects are complex and diverse. Please adjust according to actual conditions.
                                  • \ +
                                  ' + ); + tabCon.append(_c_form); + + bt.render_clicks(clicks); + }); + break; + case 'get_php_status': + bt.soft.php.get_php_status(version, function (rdata) { + var arr = {}; + arr[lan.bt.php_pool] = rdata.pool; + arr[lan.bt.php_manager] = rdata['process manager'] == 'dynamic' ? lan.bt.dynamic : lan.bt.static; + arr[lan.bt.php_start] = rdata['start time']; + arr[lan.bt.php_accepted] = rdata['accepted conn']; + arr[lan.bt.php_queue] = rdata['listen queue']; + arr[lan.bt.php_max_queue] = rdata['max listen queue']; + arr[lan.bt.php_len_queue] = rdata['listen queue len']; + arr[lan.bt.php_idle] = rdata['idle processes']; + arr[lan.bt.php_active] = rdata['active processes']; + arr[lan.bt.php_total] = rdata['total processes']; + arr[lan.bt.php_max_active] = rdata['max active processes']; + arr[lan.bt.php_max_children] = rdata['max children reached']; + arr[lan.bt.php_slow] = rdata['slow requests']; + + var con = "
                                  "; + $('.soft-man-con').html(con); + bt.render_table('tab_php_status', arr); + }); + break; + case 'get_php_session': + bt.soft.php.get_php_session(version, function (res) { + $('.soft-man-con').html( + '
                                  ' + + '
                                  ' + + '' + + lan.soft.storage_mode + + '' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + lan.soft.ip_addr + + '' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + lan.soft.port + + '' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + lan.soft.passwd + + '' + + '
                                  ' + + '' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + '' + + '
                                  ' + + '
                                    ' + + '
                                  • ' + + lan.soft.php_seesion_tips1 + + '
                                  • ' + + '
                                  • ' + + lan.soft.php_seesion_tips2 + + '
                                  • ' + + '
                                  • ' + + lan.soft.php_seesion_tips3 + + '
                                  • ' + + '
                                  ' + + '
                                  ' + + '
                                  ' + + lan.soft.clear_seesion_files + + '
                                  ' + + '
                                  ' + ); + if (res.save_handler == 'files') { + bt.soft.php.get_session_count(function (res) { + $('.clear_conter').html( + '
                                  ' + + lan.soft.total_seesion_files + + '' + + res.total + + '
                                  ' + + lan.soft.can_clear_seesion + + '' + + res.oldfile + + '
                                  ' + ); + $('.clear_session_file').click(function () { + bt.soft.php.clear_session_count( + { + title: lan.soft.clear_php_seesion_files, + msg: lan.soft.sure_clear_php_seesion_files, + }, + function (res) { + layer.msg(res.msg, { + icon: res.status ? 1 : 2, + }); + setTimeout(function () { + $('.bt-soft-menu p:eq(9)').click(); + }, 2000); + } + ); + }); + }); + } else { + $('.clear_conter').html(lan.soft.only_files_storage_mode_can_clear).attr('style', 'color:#666'); + } + switch_type(res.save_handler); + $('.change_select_session').change(function () { + switch_type($(this).val()); + switch ($(this).val()) { + case 'redis': + $('[name="ip"]').val('127.0.0.1'); + $('[name="port"]').val('6379'); + break; + case 'memcache': + $('[name="ip"]').val('127.0.0.1'); + $('[name="port"]').val('11211'); + break; + case 'memcached': + $('[name="ip"]').val('127.0.0.1'); + $('[name="port"]').val('11211'); + break; + } + }); + $('.btn_conf_save').click(function () { + bt.soft.php.set_php_session( + { + version: version, + save_handler: $('[name="save_handler"]').val(), + ip: $('[name="ip"]').val(), + port: $('[name="port"]').val(), + passwd: $('[name="passwd"]').val(), + }, + function (res) { + layer.msg(res.msg, { + icon: res.status ? 1 : 2, + }); + // setTimeout(function() { + // $('.bt-soft-menu p:eq(9)').click(); + // }, 2000); + } + ); + }); + + function switch_type(type) { + switch (type) { + case 'files': + $('[name="ip"]').attr('disabled', 'disabled').val(''); + $('[name="port"]').attr('disabled', 'disabled').val(''); + $('[name="passwd"]').attr('disabled', 'disabled').val(''); + break; + case 'redis': + $('[name="ip"]').attr('disabled', false); + $('[name="port"]').attr('disabled', false); + $('[name="passwd"]').attr('disabled', false); + break; + case 'memcache': + $('[name="ip"]').attr('disabled', false); + $('[name="port"]').attr('disabled', false); + $('[name="passwd"]').attr('disabled', 'disabled').val(''); + break; + case 'memcached': + $('[name="ip"]').attr('disabled', false); + $('[name="port"]').attr('disabled', false); + $('[name="passwd"]').attr('disabled', 'disabled').val(''); + break; + } + } + }); + break; + case 'get_fpm_logs': + bt.soft.php.get_fpm_logs(version, function (logs) { + var phpCon = ''; + $('.soft-man-con').html(phpCon); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + break; + case 'get_slow_logs': + bt.soft.php.get_slow_logs(version, function (logs) { + var phpCon = ''; + $('.soft-man-con').html(phpCon); + var ob = document.getElementById('error_log'); + ob.scrollTop = ob.scrollHeight; + }); + break; + case 'get_redis_status': + bt.soft.redis.get_redis_status(function (rdata) { + var hit = ((parseInt(rdata.keyspace_hits) / (parseInt(rdata.keyspace_hits) + parseInt(rdata.keyspace_misses))) * 100).toFixed(2); + var arrs = []; + arrs['uptime_in_days'] = [rdata.uptime_in_days, lan.soft.run_days]; + arrs['tcp_port'] = [rdata.tcp_port, lan.soft.curr_listen_port]; + arrs['connected_clients'] = [rdata.connected_clients, lan.soft.connected_clients]; + arrs['used_memory_rss'] = [bt.format_size(rdata.used_memory_rss), lan.soft.used_memory_rss]; + arrs['used_memory'] = [bt.format_size(rdata.used_memory), lan.soft.used_memory]; + arrs['mem_fragmentation_ratio'] = [rdata.mem_fragmentation_ratio, lan.soft.mem_fragmentation_ratio]; + arrs['total_connections_received'] = [rdata.total_connections_received, lan.soft.total_connections_received]; + arrs['total_commands_processed'] = [rdata.total_commands_processed, lan.soft.total_commands_processed]; + arrs['instantaneous_ops_per_sec'] = [rdata.instantaneous_ops_per_sec, lan.soft.instantaneous_ops_per_sec]; + arrs['keyspace_hits'] = [rdata.keyspace_hits, lan.soft.keyspace_hits]; + arrs['keyspace_misses'] = [rdata.keyspace_misses, lan.soft.keyspace_misses]; + arrs['hit'] = [hit, lan.soft.db_his]; + arrs['latest_fork_usec'] = [rdata.latest_fork_usec, lan.soft.latest_fork_usec]; + + var con = + '
                                  ' + + lan.soft.field + + '' + + lan.soft.curr_val + + '' + + lan.soft.instructions + + '
                                  '; + $('.soft-man-con').html(con); + bt.render_table('tab_get_redis_status', arrs, true); + }); + break; + case 'openliMa_set': + var loadT = bt.load(); + $.post('/config?action=get_ols_value', function (rdata) { + loadT.close(); + var _mlist_data = '', + tips_i = 0, + help_tips = [ + '#Enables GZIP/Brotli compression for both static and dynamic responses.', + '#Specifies the level of GZIP compression applied to dynamic content. Ranges from 1 (lowest) to 9 (highest).', + '', + '#Specifies the maximum number of concurrent connections that the server can accept.
                                  \ + #This includes both plain TCP connections and SSL connections', + '#Specifies the maximum number of concurrent SSL connections the server will accept
                                  \ + #Since total concurrent SSL and non-SSL connections cannot exceed the limit specified by “Max Connections”,
                                  \ + #the actual number of concurrent SSL connections allowed must be lower than this limit.', + '#Specifies the maximum connection idle time (seconds) allowed during processing one request', + '#Specifies the maximum number of requests that can be served through a keep-alive (persistent) session', + ]; + for (var i in rdata) { + var mlist = { title: '', items: [] }, + list = {}; + list.name = i; + list.width = '130px'; + list.value = rdata[i]; + list.type = i == 'enableGzipCompress' ? 'switch' : 'input'; + list.ps_help = help_tips[tips_i]; + mlist.items.push(list); + mlist.title = i; + _mlist_data += bt.render_form_line(mlist).html; + tips_i++; + } + var tabCon = $('.soft-man-con').empty(); + tabCon.append('
                                  ' + _mlist_data + '
                                  '); + var datas = { + title: ' ', + class: 'openlite_button', + items: [ + { + name: 'btn_fresh', + text: lan.public.fresh, + type: 'button', + callback: function (ldata) { + soft.get_tab_contents(key, obj); + }, + }, + { + name: 'btn_save', + text: lan.public.save, + type: 'button', + width: '62px', + callback: function (ldata) { + var datal = {}, + loadP = bt.load(); + delete ldata.btn_fresh; + delete ldata.btn_save; + ldata['enableGzipCompress'] = $('#enableGzipCompress').prop('checked') ? 1 : 0; + ldata = JSON.stringify(ldata); + datal = { array: ldata }; + bt.send('set_ols_value', 'config/set_ols_value', datal, function (res) { + loadP.close(); + soft.get_tab_contents(key, obj); + bt.msg(res); + }); + }, + }, + ], + }; + var _form_data = bt.render_form_line(datas); + $('.openlite_set').append(_form_data.html); + bt.render_clicks(_form_data.clicks); + $('.enableGzipCompress_help').css('margin-left', '104px'); + $('.openlite_set').on('mouseenter', '.bt-ico-ask', function () { + var idd = $(this).attr('class').split(' ')[1], + tip = $(this).attr('tip'); + layer.tips(tip, '.' + idd + '', { tips: [1, '#d4d4d4'], time: 0, area: '300px' }); + }); + $('.openlite_set').on('mouseleave', '.bt-ico-ask', function () { + layer.closeAll('tips'); + }); + }); + break; + } + }, + update_zip_open: function () { + $('#update_zip').on('change', function () { + var files = $('#update_zip')[0].files; + if (files.length == 0) { + return; + } + soft.update_zip(files[0]); + $('#update_zip').val(''); + }); + + $('#update_zip').click(); + }, + update_zip: function (file) { + var formData = new FormData(); + formData.append('plugin_zip', file); + $.ajax({ + url: '/plugin?action=update_zip', + type: 'POST', + data: formData, + processData: false, + contentType: false, + success: function (data) { + if (data.status === false) { + layer.msg(data.msg, { + icon: 2, + }); + return; + } + var loadT = layer.open({ + type: 1, + area: '500px', + title: lan.soft.install_third_party_apps, + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + '\ +
                                  \ + \ +
                                    \ +
                                  • ' + + lan.soft.third_party_apps_tips1 + + '
                                  • \ +
                                  • ' + + lan.soft.third_party_apps_tips2 + + '
                                  • \ +
                                  • ' + + lan.third_party_apps_tips3 + + '
                                  • \ +
                                  \ +
                                  \ +
                                  ', + }); + }, + error: function (responseStr) { + layer.msg(lan.soft.upload_fail2, { + icon: 2, + }); + }, + }); + }, + + input_zip: function (plugin_name, tmp_path, data, callback) { + bt.soft.show_speed_window({ title: 'Installing, this may take a few minutes...', status: true }, function () { + $.post('/plugin?action=input_zip', { plugin_name: plugin_name, tmp_path: tmp_path }, function (rdata) { + layer.closeAll(); + if (rdata.status) { + soft.get_list(); + } + setTimeout(function () { + layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 }); + if (rdata.status) { + setTimeout(function () { + callback && callback(); + }, 1500); + } + }, 1000); + }); + }); + }, +}; + +function soft_td_width_auto() { + var thead_width = '', + winWidth = $(window).width(); + if (winWidth <= 1370 && winWidth > 1280) { + thead_width = winWidth / 4; + } else if (winWidth <= 1280 && winWidth > 1210) { + thead_width = winWidth / 5; + } else if (winWidth <= 1210) { + thead_width = winWidth / 6; + } else { + thead_width = winWidth / 3.5; + } + //$('#softList thead th:eq(2)').width(thead_width); + $('#softList tbody tr td:nth-child(8n+2)>span').width(thead_width + 75); +} + +function set_disable_functions(version, data) { + bt.soft.php.disable_functions(version, data, function (rdata) { + if (rdata.status) { + soft.get_tab_contents('set_dis_fun', $('.bgw')); + } + bt.msg(rdata); + }); +} + +var openId = (add = null); + +function AddDeployment(maction) { + if (maction == 1) { + var pdata = + 'title=' + + $("input[name='title']").val() + + '&dname=' + + $("input[name='name']").val() + + '&ps=' + + $("input[name='ps']").val() + + '&version=' + + $("input[name='version']").val() + + '&rewrite=' + + ($("input[name='rewrite']").attr('checked') ? 1 : 0) + + '&shell=' + + ($("input[name='shell']").attr('checked') ? 1 : 0) + + '&php=' + + $("input[name='php']").val() + + '&md5=' + + $("input[name='md5']").val() + + '&download=' + + $("input[name='download']").val(); + var loadT = layer.msg('Submitting ', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + $.post('/deployment?action=AddPackage', pdata, function (rdata) { + layer.close(loadT); + layer.msg(rdata.msg, { + icon: rdata.status ? 1 : 5, + }); + if (rdata.status) { + GetSrcList(); + layer.close(openId); + } + }); + + return; + } + openId = layer.open({ + type: 1, + skin: 'demo-class', + area: '480px', + title: '添加源码包', + closeBtn: 2, + shift: 5, + shadeClose: false, + content: + 'Title:
                                  \ + Identification:
                                  \ + Description:
                                  \ + Version:
                                  \ + Whether to use URL rewrite:
                                  \ + Whether to execute the installation script:
                                  \ + Supported PHP version:
                                  \ + md5:\ + Download link:
                                  \ + ', + }); +} + +$('.searchInput').keyup(function (e) { + if (e.keyCode == 13) { + GetSrcList(); + } +}); + +function AddSite(codename, title) { + var array; + var str = ''; + var domainlist = ''; + var domain = (array = $('#mainDomain').val().split('\n')); + var Webport = []; + var checkDomain = domain[0].split('.'); + if (checkDomain.length < 1) { + layer.msg('The domain name is not in the correct format. Please re-enter!', { + icon: 2, + }); + return; + } + for (var i = 1; i < domain.length; i++) { + domainlist += '"' + domain[i] + '",'; + } + Webport = domain[0].split(':')[1]; //主域名端口 + if (Webport == undefined) { + Webport = '80'; + } + domainlist = domainlist.substring(0, domainlist.length - 1); //子域名json + mainDomain = domain[0].split(':')[0]; + domain = '{"domain":"' + domain[0] + '","domainlist":[' + domainlist + '],"count":' + domain.length + '}'; //拼接json + var php_version = $("select[name='version']").val(); + var loadT = layer.msg('Creating site ', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + var data = $('#addweb').serialize() + '&port=' + Webport + '&webname=' + domain + '&ftp=false&sql=true&address=localhost&codeing=utf8&version=' + php_version; + $.post('/site?action=AddSite', data, function (ret) { + layer.close(loadT); + if (!ret.siteStatus) { + layer.msg(ret.msg, { + icon: 5, + }); + return; + } + layer.close(add); + var sqlData = ''; + if (ret.databaseStatus) { + sqlData = + "

                                  Database account information

                                  \ +

                                  Database name:" + + ret.databaseUser + + '

                                  \ +

                                  User:' + + ret.databaseUser + + '

                                  \ +

                                  Password:' + + ret.databasePass + + '

                                  \ + '; + } + var pdata = 'dname=' + codename + '&site_name=' + mainDomain + '&php_version=' + php_version; + var loadT = layer.msg('
                                  Submitting
                                  ', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + + setTimeout(function () { + GetSpeed(); + }, 2000); + + $.post('/deployment?action=SetupPackage', pdata, function (rdata) { + layer.close(loadT); + if (!rdata.status) { + layer.msg(rdata.msg, { + icon: 5, + time: 10000, + }); + return; + } + + if (rdata.msg.admin_username != '') { + sqlData = + "

                                  Successfully deployed, no need to install, please login to modify the default account password.

                                  \ +

                                  User:" + + rdata.msg.admin_username + + '

                                  \ +

                                  Password:' + + rdata.msg.admin_password + + '

                                  \ + '; + } + sqlData += "

                                  Visit site:http://" + mainDomain + rdata.msg.success_url + '

                                  '; + + layer.open({ + type: 1, + area: '600px', + title: 'Successfully deployed [' + title + ']', + closeBtn: 2, + shadeClose: false, + content: + "
                                  \ +
                                  \ +
                                  \ + " + + sqlData + + '\ +
                                  \ +
                                  ', + }); + if ($('.success-msg').height() < 150) { + $('.success-msg').find('img').css({ + width: '150px', + 'margin-top': '30px', + }); + } + }); + }); +} + +function GetSpeed() { + if (!$('.depSpeed')) return; + $.get('/deployment?action=GetSpeed', function (speed) { + if (speed.status === false) return; + if (speed.name == 'Download file') { + speed = + '

                                  正在' + + speed.name + + '

                                  \ +
                                  ' + + speed.pre + + '%
                                  \ +

                                  ' + + ToSize(speed.used) + + '/' + + ToSize(speed.total) + + '' + + ToSize(speed.speed) + + '/s

                                  '; + $('.depSpeed').prev().hide(); + $('.depSpeed').css({ + 'margin-left': '-37px', + width: '380px', + }); + $('.depSpeed').parents('.layui-layer').css({ + 'margin-left': '-100px', + }); + } else { + speed = '

                                  ' + speed.name + '

                                  '; + $('.depSpeed').prev().show(); + $('.depSpeed').removeAttr('style'); + $('.depSpeed').parents('.layui-layer').css({ + 'margin-left': '0', + }); + } + + $('.depSpeed').html(speed); + setTimeout(function () { + GetSpeed(); + }, 1000); + }); +} + +function onekeyCodeSite(codename, versions, title, enable_functions) { + $.post('/site?action=GetPHPVersion', function (rdata) { + var php_version = ''; + var n = 0; + for (var i = rdata.length - 1; i >= 0; i--) { + if (versions.indexOf(rdata[i].version) != -1) { + php_version += "'; + n++; + } + } + + if (n == 0) { + layer.msg('Missing supported PHP version, please install!', { + icon: 5, + }); + return; + } + var default_path = bt.get_cookie('sites_path'); + if (!default_path) default_path = '/www/wwwroot'; + + var con = + '\ +
                                  Domain\ +
                                  \ +
                                  Fill in a domain name per line, the default is 80 ports
                                  Pan-analysis add method *.domain.com
                                  If the additional port format is www.domain.com:88
                                  \ +
                                  \ +
                                  \ +
                                  Note\ +
                                  \ +
                                  \ +
                                  Document Root\ +
                                  \ +
                                  \ +
                                  Database\ +
                                  \ + \ + \ +
                                  \ +
                                  \ +
                                  Source code\ + \ + Prepare the source code for your deployment\ +
                                  \ +
                                  PHP Version\ + \ + Please select the php version supported by the source program.\ +
                                  \ +
                                  \ + \ + \ +
                                  \ + '; + add = layer.open({ + type: 1, + title: 'aaPanel One-Click [' + title + ']', + area: '560px', + closeBtn: 2, + shadeClose: false, + content: con, + }); + + if (enable_functions.length > 2) { + layer.msg("Note: The following functions will be released when deploying this project.:
                                  " + enable_functions + '
                                  ', { + icon: 7, + time: 10000, + }); + } + var placeholder = + "
                                  Fill in a domain name per line, the default is 80 ports
                                  Pan-analysis add method *.domain.com
                                  If the additional port format is www.domain.com:88
                                  "; + $('.onekeycodeclose').click(function () { + layer.close(add); + }); + $('#mainDomain').after(placeholder); + $('.placeholder').click(function () { + $(this).hide(); + $('#mainDomain').focus(); + }); + $('#mainDomain').focus(function () { + $('.placeholder').hide(); + }); + + $('#mainDomain').blur(function () { + if ($(this).val().length == 0) { + $('.placeholder').show(); + } + }); + //FTP账号数据绑定域名 + $('#mainDomain').on('input', function () { + var defaultPath = bt.get_cookie('sites_path'); + if (!defaultPath) defaultPath = '/www/wwwroot'; + var array; + var res, ress; + var str = $(this).val(); + var len = str.replace(/[^\x00-\xff]/g, '**').length; + array = str.split('\n'); + ress = array[0].split(':')[0]; + res = ress.replace(new RegExp(/([-.])/g), '_'); + if (res.length > 15) res = res.substr(0, 15); + if ($('#inputPath').val().substr(0, defaultPath.length) == defaultPath) $('#inputPath').val(defaultPath + '/' + ress); + if (!isNaN(res.substr(0, 1))) res = 'sql' + res; + if (res.length > 15) res = res.substr(0, 15); + $('#Wbeizhu').val(ress); + $('#datauser').val(res); + }); + $('#Wbeizhu').on('input', function () { + var str = $(this).val(); + var len = str.replace(/[^\x00-\xff]/g, '**').length; + if (len > 20) { + str = str.substring(0, 20); + $(this).val(str); + layer.msg('Do not exceed 20 characters', { + icon: 0, + }); + } + }); + //获取当前时间时间戳,截取后6位 + var timestamp = new Date().getTime().toString(); + var dtpw = timestamp.substring(7); + $('#datauser').val('sql' + dtpw); + $('#datapassword').val(_getRandomString(10)); + }); +} + +//生成n位随机密码 +function _getRandomString(len) { + len = len || 32; + var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 + var maxPos = $chars.length; + var pwd = ''; + for (i = 0; i < len; i++) { + pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; +} +var score = { + total: 1, + type: '', + data: [], + // 获取评论信息 + get_score_info: function (obj, callback) { + var loadT = layer.msg('
                                  Getting comment information
                                  ', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + bt.send( + 'get_score', + 'plugin/get_score', + { + pid: obj.pid, + p: obj.p, + limit_num: obj.limit_num, + }, + function (res) { + layer.close(loadT); + if (res.status === false) { + layer.msg(res.msg, { + icon: 2, + }); + return false; + } + if (callback) callback(res); + } + ); + }, + render_score_info: function (obj, callback) { + var config = { + pid: obj.pid, + }, + _this = this; + obj.p == undefined ? (config.p = 1) : (config.p = parseInt(obj.p)); + obj.limit_num == undefined ? (config.limit_num = '') : (config.limit_num = obj.limit_num); + score.get_score_info(config, function (res) { + var _split_score = res.split.reverse(), + _average_score = (_split_score[4] * 1 + _split_score[3] * 2 + _split_score[2] * 3 + _split_score[1] * 4 + _split_score[0] * 5) / res.total, + _data = res.data, + _html = ''; + _this.total = res.total; + $('.comment_user_count').text(obj.count); + $('.comment_num').text((res.total !== 0 ? _average_score : 0).toFixed(1)); + $('.comment_partake').text(res.total); + $('.comment_rate').text(res.total !== 0 ? ((_split_score[0] + _split_score[1]) / res.total).toFixed(2) * 100 + '%' : '0%'); + for (var i = 0; i < 5; i++) { + $('.comment_star_group:eq(' + i + ')') + .find('.comment_progress .comment_progress_bgw') + .css('width', (_split_score[i] / res.total).toFixed(2) * 100 + '%'); + } + $('.comment_tab span:eq(1)') + .find('i') + .text(_split_score[0] + _split_score[1]); + $('.comment_tab span:eq(2)') + .find('i') + .text(_split_score[2] + _split_score[3]); + $('.comment_tab span:eq(3)').find('i').text(_split_score[4]); + + for (var j = 0; j < _data.length; j++) { + _html += + '
                                  \ +
                                  \ + \ + \ + \ + \ + \ + \ + \ + ' + + _data[j].nickname + + '\ + ' + + timeago(_data[j].addtime * 1000) + + '\ +
                                  \ +
                                  ' + + (getLength(_data[j].ps) > 65 ? reBytesStr(_data[j].ps, 65) + '... Details' : _data[j].ps) + + '
                                  \ +
                                  '; + // console.log(getLength(_data[j].ps)>70?reBytesStr(_data[j].ps,70)+' 详情':_data[j].ps); + } + _this.data = _this.data.concat(_data); + if (res.total > 10 && _data.length === 10) { + _html += '
                                  Click for more comments
                                  '; + } + $('.comment_content').find('.get_next_page').remove(); + $('.comment_content').append(_html); + if ($('.comment_content .comment_box').length > 6) { + $('.comment_content').addClass('box-shadow'); + } else { + $('.comment_content').removeClass('box-shadow'); + } + if (callback) callback(res); + }); + }, + // 设置评论信息 + set_score_info: function (obj, callback) { + var loadT = layer.msg('
                                  Submitting comment
                                  ', { + icon: 16, + time: 0, + shade: [0.3, '#000'], + }); + bt.send( + 'set_score', + 'plugin/set_score', + { + pid: obj.pid, + num: obj.num, + ps: obj.ps, + }, + function (res) { + layer.close(loadT); + if (res.status === false) { + layer.msg(res.msg, { + icon: 2, + }); + return false; + } + if (callback) callback(res); + } + ); + }, + open_score_view: function (_pid, _name, _count) { + layer.open({ + type: 1, + title: '[ ' + _name + '] Score', + area: ['550px', '350px'], + closeBtn: 2, + shadeClose: false, + content: + '
                                  \ +
                                  \ +
                                  --
                                  \ +
                                    \ +
                                  • user count --
                                  • \ +
                                  •  -- people participated in the score
                                  • \ +
                                  • -- Favorable rate
                                  • \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + All evaluation\ + Praise -- \ + Average -- \ + Bad review -- \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  Recommended: 5 points
                                  \ + \ + Can also enter 60 words\ +
                                  \ +
                                  \ + Participate in the score\ +
                                  \ +
                                  ', + success: function (index, layero) { + score.data = []; + score.render_score_info( + { + pid: _pid, + count: _count, + }, + function () { + $('.score_info_view').show(); + } + ); + score.score_icon_time = null; + $('.score_icon_group span').hover(function () { + var _active = $(this).hasClass('active'); + // if($(this).prevAll().length == 0 && $(this).nextAll('.active').length == 0 && _active){ + // $(this).removeClass('active').nextAll().removeClass('active') + // $('.score_icon_group_tips').html('选择以上图标选择评分等级1-5'); + // $('.score_icon_group').attr('data-icon',0) + // }else{ + // $(this).addClass('active').nextAll().removeClass('active'); + // $(this).prevAll().addClass('active'); + // $('.score_icon_group').attr('data-icon',$(this).prevAll().length +1) + // var _title = $(this).attr('title'); + // $('.score_icon_group_tips').text(_title); + // } + }); + $('.score_icon_group span').click(function () { + var _active = $(this).hasClass('active'); + if ($(this).prevAll().length == 0 && $(this).nextAll('.active').length == 0 && _active) { + $('.edit_view').addClass('active'); + $(this).removeClass('active').nextAll().removeClass('active'); + $('.score_icon_group_tips').html('Click on the selection icon to rate 1-5 stars'); + $('.score_icon_group').attr('data-icon', 0); + } else { + $('.edit_view').removeClass('active'); + $(this).addClass('active').nextAll().removeClass('active'); + $(this).prevAll().addClass('active'); + $('.score_icon_group').attr('data-icon', $(this).prevAll().length + 1); + var _title = $(this).attr('title'); + $('.score_icon_group_tips').text(_title); + } + }); + $('.comment_tab span').click(function (e) { + var _num = $(this).attr('data-num'); + $('.comment_content').removeClass('box-shadow'); + $(this).addClass('active').siblings().removeClass('active'); + $('.comment_content').html(''); + score.data = []; + score.type = _num; + score.render_score_info({ + pid: _pid, + limit_num: _num, + count: _count, + }); + }); + $('.comment_content').on('click', '.get_next_page', function () { + var _next_page = $('.comment_content .comment_box').length / 10 + 1; + score.render_score_info({ + pid: _pid, + limit_num: score.type, + p: _next_page, + count: _count, + }); + }); + $('.comment_content').on('click', '.comment_box', function () { + if (!$(this).hasClass('get_next_page')) { + var _index = $(this).attr('data-index'); + layer.open({ + type: 1, + title: false, + area: ['350px', '200px'], + closeBtn: 2, + shadeClose: false, + content: '
                                  ' + $(this).html() + '
                                  ', + success: function (index, layers) { + $('.score_details .comment_box_content').html(score.data[_index]['ps']); + }, + }); + } + }); + $('.edit_view').click(function () { + if ($('.edit_view').hasClass('active')) { + // layer.msg('请选择评分等级',{icon:2}); + $('.score_icon_group_tips').css('color', 'red'); + setTimeout(function () { + $('.score_icon_group_tips').removeAttr('style'); + }, 1000); + return false; + } + var _num = parseInt($('.score_icon_group').attr('data-icon')), + _ps = $('.score_input').val(); + if (_num == 0) { + layer.msg('Rating level cannot be empty', { + icon: 2, + }); + return false; + } + if (120 - getLength(_ps) < 0) { + layer.msg('Evaluation information cannot exceed 60 words', { + icon: 2, + }); + return false; + } + score.set_score_info( + { + pid: _pid, + num: _num, + ps: _ps == '' ? 'User did not make any evaluation' : _ps, + }, + function (res) { + layer.msg(res.msg, { + icon: 1, + }); + score.render_score_info({ + pid: _pid, + limit_num: score.type, + count: _count, + }); + soft.flush_cache(); + layer.close(index); + } + ); + return false; + layer.open({ + type: 1, + title: 'Add review', + area: ['400px', '350px'], + closeBtn: 2, + shadeClose: false, + btn: ['Confirm', 'Cancel'], + content: + '
                                  \ +
                                  \ + \ + \ + \ + \ + \ +
                                  \ +
                                  (Click on the icon above to select rating 1-5)
                                  \ + \ + Can also enter 60 words\ +
                                  ', + success: function () { + $('.score_icon_group span').click(function () { + var _active = $(this).hasClass('active'); + if ($(this).prevAll().length == 0 && $(this).nextAll('.active').length == 0 && _active) { + $(this).removeClass('active').nextAll().removeClass('active'); + $('.score_icon_group_tips').html('(Click on the icon above to select rating 1-5)'); + $('.score_icon_group').attr('data-icon', 0); + } else { + $(this).addClass('active').nextAll().removeClass('active'); + $(this).prevAll().addClass('active'); + $('.score_icon_group').attr('data-icon', $(this).prevAll().length + 1); + var _title = $(this).attr('title'); + $('.score_icon_group_tips').text(_title); + } + }); + $('.score_input').on('keydown keyup focus click', function () { + var _val = $('.score_input').val(), + _size = 120 - getLength(_val); + if (_size > 0) { + $('.score_input_tips i') + .css('color', _size > 20 ? '#666' : 'red') + .text(parseInt(_size / 2)); + $('.score_input').attr('style', ''); + } else { + $('.score_input_tips i').text(0); + $('.score_input').css({ + 'outline-color': 'red', + border: '1px solid red', + }); + } + }); + }, + yes: function (index, layero) { + var _num = parseInt($('.score_icon_group').attr('data-icon')), + _ps = $('.score_input').val(); + if (_num == 0) { + layer.msg('Rating level cannot be empty', { + icon: 2, + }); + return false; + } + if (120 - getLength(_ps) < 0) { + layer.msg('Evaluation information cannot exceed 60 words', { + icon: 2, + }); + return false; + } + score.set_score_info( + { + pid: _pid, + num: _num, + ps: _ps == '' ? 'User did not make any evaluation' : _ps, + }, + function (res) { + layer.msg(res.msg, { + icon: 1, + }); + score.render_score_info({ + pid: _pid, + limit_num: score.type, + count: _count, + }); + soft.flush_cache(); + layer.close(index); + } + ); + }, + }); + }); + }, + }); + }, +}; + +function timeago(dateTimeStamp) { + //dateTimeStamp是一个时间毫秒,注意时间戳是秒的形式,在这个毫秒的基础上除以1000,就是十位数的时间戳。13位数的都是时间毫秒。 + if (dateTimeStamp.toString().length < 10) dateTimeStamp = dateTimeStamp * 1000; + var minute = 1000 * 60, + hour = minute * 60, + day = hour * 24, + week = day * 7, + halfamonth = day * 15, + month = day * 30, + now = new Date().getTime(), //获取当前时间毫秒 + diffValue = now - dateTimeStamp; //时间差 + if (diffValue <= 0) { + return 'Just a moment ago'; + } + var minC = diffValue / minute, //计算时间差的分,时,天,周,月 + hourC = diffValue / hour, + dayC = diffValue / day, + weekC = diffValue / week, + monthC = diffValue / month, + result = 'Just a moment ago'; + if (monthC >= 1 && monthC <= 3) { + result = ' ' + parseInt(monthC) + 'month ago'; + } else if (weekC >= 1 && weekC <= 3) { + result = ' ' + parseInt(weekC) + 'week ago'; + } else if (dayC >= 1 && dayC <= 6) { + result = ' ' + parseInt(dayC) + 'day ago'; + } else if (hourC >= 1 && hourC <= 23) { + result = ' ' + parseInt(hourC) + 'hour ago'; + } else if (minC >= 1 && minC <= 59) { + result = ' ' + parseInt(minC) + 'minute ago'; + } else if (diffValue >= 0 && diffValue <= minute) { + result = 'Just a moment ago'; + } else { + var datetime = new Date(); + datetime.setTime(dateTimeStamp); + var Nyear = datetime.getFullYear(), + Nmonth = datetime.getMonth() + 1 < 10 ? '0' + (datetime.getMonth() + 1) : datetime.getMonth() + 1, + Ndate = datetime.getDate() < 10 ? '0' + datetime.getDate() : datetime.getDate(), + Nhour = datetime.getHours() < 10 ? '0' + datetime.getHours() : datetime.getHours(), + Nminute = datetime.getMinutes() < 10 ? '0' + datetime.getMinutes() : datetime.getMinutes(), + Nsecond = datetime.getSeconds() < 10 ? '0' + datetime.getSeconds() : datetime.getSeconds(), + result = Nmonth + '-' + Ndate; + } + if (!result) result = 'Just a moment ago'; + return result == undefined || result == 'undefined' ? 'Just a moment ago' : result; +} +// 规则转码 +function escapeHTML(val) { + val = '' + val; + return val + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '‘') + .replace(/\(/g, '(') + .replace(/\</g, '<') + .replace(/\>/g, '>') + .replace(/`/g, '`') + .replace(/=/g, '='); +} + +function getLength(val) { + var str = new String(val); + var bytesCount = 0; + for (var i = 0, n = str.length; i < n; i++) { + var c = str.charCodeAt(i); + if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) { + bytesCount += 1; + } else { + bytesCount += 2; + } + } + return bytesCount; +} + +function reBytesStr(str, len) { + if (!str && typeof str != 'undefined') { + return ''; + } + var num = 0; + var str1 = str; + var str = ''; + for (var i = 0, lens = str1.length; i < lens; i++) { + num += str1.charCodeAt(i) > 255 ? 2 : 1; + if (num > len) { + break; + } else { + str = str1.substring(0, i + 1); + } + } + return str; +} diff --git a/BTPanel/static/vite/oldjs/term.js b/BTPanel/static/vite/oldjs/term.js new file mode 100644 index 00000000..9accd602 --- /dev/null +++ b/BTPanel/static/vite/oldjs/term.js @@ -0,0 +1,1238 @@ +function Terms(el,config){ + if(typeof config == "undefined") config = {}; + this.el = el; + this.id = config.ssh_info.id || ''; + this.bws = null; //websocket对象 + this.route ='/webssh'; // 访问的方法 + this.term =null; //term对象 + this.info =null; // 请求数据 + this.last_body =null; + this.fontSize =15; //终端字体大小 + this.ssh_info = config.ssh_info; + this.run(); +} +Terms.prototype = { + // websocket持久化连接 + connect:function(callback){ + var that = this; + // 判断当前websocket连接是否存在 + if(!this.bws || this.bws.readyState == 3 || this.bws.readyState == 2){ + this.bws = new WebSocket((window.location.protocol === 'http:' ? 'ws://' : 'wss://') + window.location.host + this.route); + this.bws.addEventListener('message',function(ev){that.on_message(ev)}); + this.bws.addEventListener('close',function(ev){that.on_close(ev)}); + this.bws.addEventListener('error',function(ev){that.on_error(ev)}); + this.bws.addEventListener('open',function(ev){that.on_open(ev)}); + if(callback) callback(this.bws) + } + }, + + //连接服务器成功 + on_open:function(ws_event){ + var http_token = $("#request_token_head").attr('token'); + this.send(JSON.stringify({'x-http-token':http_token})) + this.send(JSON.stringify(this.ssh_info || {})) + this.term.FitAddon.fit(); + this.resize({cols:this.term.cols, rows:this.term.rows}); + }, + //服务器消息事件 + on_message: function (ws_event){ + result= ws_event.data; + if(!result) return; + that = this; + if ((result.indexOf("@127.0.0.1:") != -1 || result.indexOf("@localhost:") != -1) && result.indexOf('Authentication failed') != -1) { + that.term.write(result); + host_trem.localhost_login_form(result); + that.close(); + return; + } + if(result.length > 1 && that.last_body === false){ + that.last_body = true; + } + that.term.write(result); + that.set_term_icon(1); + if (result == '\r\n登出\r\n' || result == '登出\r\n' || result == '\r\nlogout\r\n' || result == 'logout\r\n') { + that.close(); + that.bws = null; + + } + }, + //websocket关闭事件 + on_close: function (ws_event) { + this.set_term_icon(0); + this.bws = null; + }, + + /** + * @name 设置终端标题状态 + * @author chudong<2020-08-10> + * @param {number} status 终端状态 + * @return void + */ + set_term_icon:function(status){ + var icon_list = ['icon-warning','icon-sucess','icon-info']; + if(status == 1){ + if($("[data-id='"+ this.id +"']").attr("class").indexOf('active') == -1){ + status = 2; + } + } + $("[data-id='"+ this.id +"']").find('.icon').removeAttr('class').addClass(icon_list[status]+' icon'); + if(status == 2){ + that = this; + setTimeout(function(){ + $("[data-id='"+ that.id +"']").find('.icon').removeAttr('class').addClass(icon_list[1]+' icon'); + },200); + } + }, + //websocket错误事件 + on_error: function (ws_event) { + if(ws_event.target.readyState === 3){ + // var msg = '错误: 无法创建WebSocket连接,请在面板设置页面关闭【开发者模式】'; + // layer.msg(msg,{time:5000}) + // if(Term.state === 3) return + // Term.term.write(msg) + // Term.state = 3; + }else{ + console.log(ws_event) + } + }, + //发送数据 + //@param event 唯一事件名称 + //@param data 发送的数据 + //@param callback 服务器返回结果时回调的函数,运行完后将被回收 + send: function (data, num) { + var that = this; + //如果没有连接,则尝试连接服务器 + if (!this.bws || this.bws.readyState == 3 || this.bws.readyState == 2) { + this.connect(); + } + //判断当前连接状态,如果!=1,则100ms后尝试重新发送 + if (this.bws.readyState === 1) { + this.bws.send(data); + } else { + if(this.state === 3) return; + if (!num) num = 0; + if (num < 5) { + num++; + setTimeout(function () { that.send(data, num++); }, 100) + } + } + }, + //关闭连接 + close: function () { + this.bws.close(); + this.set_term_icon(0); + }, + resize: function (size){ + if(this.bws){ + size['resize'] = 1; + this.send(JSON.stringify(size)); + } + + }, + run: function (ssh_info) { + var that = this; + this.term = new Terminal({fontSize:this.fontSize, screenKeys: true, useStyle: true }); + this.term.setOption('cursorBlink', true); + this.last_body = false; + this.term.open($(this.el)[0]); + + this.term.FitAddon = new FitAddon.FitAddon(); + this.term.loadAddon(this.term.FitAddon); + this.term.WebLinksAddon = new WebLinksAddon.WebLinksAddon() + this.term.loadAddon(this.term.WebLinksAddon) + if (ssh_info) this.ssh_info = ssh_info + this.connect(); + that.term.onData(function (data) { + try { + that.bws.send(data) + } catch (e) { + that.term.write('\r\nConnection lost, trying to connect again!\r\n') + that.connect() + } + }); + this.term.focus(); + } +} + +var host_trem = { + host_term:{}, + host_list:[], + command_list:[], + sort_time:null, + is_full:false, + command_form:{ + title:'', + shell:'', + }, + host_form:{ + host:'', + port:'22', //默认端口22 + username:'root', + password:'', + pkey: '', + ps: '' + }, + init:function(){ + var that = this; + Object.defineProperty(host_trem,'is_full',{ + get:function(val){ + return val; + }, + set:function(newValue) { + if(newValue){ + $('body').addClass('full_term_view'); + var win = $(window)[0],win_width = win.innerHeight,win_height = win.innerHeight; + $('.main-content .safe').height(win_height); + $('#term_box_view,.term_tootls').height(win_height); + $('.tootls_host_list').height((win_height - 80) * .75); + $('.tootls_commonly_list').height((win_height - 80) * .25); + $('.tab_tootls .glyphicon').removeClass('glyphicon-resize-full').addClass('glyphicon-resize-small').attr('title','Exit full screen'); + }else{ + $('body').removeClass('full_term_view'); + $('.tab_tootls .glyphicon').removeClass('glyphicon-resize-small').addClass('glyphicon-resize-full').attr('title','Full Screen'); + } + } + }); + document.onkeydown = function(e){ + e = e || window.event; + if ((e.metaKey && e.keyCode == 82) || e.keyCode == 116){ + return false; + } + if(that.is_full && e.keyCode == 27){ + return false; + } + } + //本地存储 + var _tool_status = localStorage.getItem("tool_status"); + _tool_host_height = localStorage.getItem("hostHeight"), + _tool_commonly_height = localStorage.getItem("commonlyHeight"); + if(_tool_commonly_height <= 100 )_tool_commonly_height=100; + $(window).resize(function(ev){ + that.on_resize(that); + }); + $('.tab_tootls').on('click','.glyphicon-resize-full',function(){ + $(this).removeClass('glyphicon-resize-full').addClass('glyphicon-resize-small').attr('title','Exit full Screen'); + $('body').addClass('full_term_view'); + that.requestFullScreen(); + }); + $('.tab_tootls').on('click','.glyphicon-resize-small',function(){ + $(this).removeClass('glyphicon-resize-small').addClass('glyphicon-resize-full').attr('title','Full Screen'); + $('body').removeClass('full_term_view'); + that.exitFullscreen(); + }); + + $(document).ready(function (e) { + var win = $(window)[0],win_width = win.innerHeight,win_height = win.innerHeight,host_commonly = win_height - 185; + $('.main-content .safe').height(win_height - 105); + $('#term_box_view,.term_tootls').height(win_height - 105); + if(_tool_host_height !=0&&_tool_commonly_height !=0){ + $(".tootls_host_list").css("height",_tool_host_height+"px"), + $(".tootls_commonly_list").css("height", _tool_commonly_height+"px"); + }else{ + $('.tootls_host_list').height(host_commonly * .75); + $('.tootls_commonly_list').height(host_commonly * .25); + } + that.open_term_view(); + }); + + // 添加服务器信息 + $('.addServer').on('click',function(){ + that.editor_host_view(); + }); + + // 切换服务器终端视图 + $('.term_item_tab .list').on('click','span.item',function(ev){ + var index = $(this).index(),data = $(this).data(); + if($(this).hasClass('addServer')){ + + }else if($(this).hasClass('tab_tootls')){ + + }else{ + $(this).addClass('active').siblings().removeClass('active'); + $('.term_content_tab .term_item:eq('+ index +')').addClass('active').siblings().removeClass('active'); + that.host_term[data.id]; + var item = that.host_term[data.id]; + item.term.focus(); + item.term.FitAddon.fit(); + item.resize({cols:item.term.cols, rows:item.term.rows}); + } + + }); + //通过本地存储获取显示设置 + if(_tool_status==0){ + $(".term-tool-button").empty(); + $(".term-tool-button").append('').addClass("tool-hide").removeClass("tool-show"); + $(".term_box").css("margin-right","260px"); + $(".term_tootls").css("display","block"); + if(_tool_host_height !=0&&_tool_commonly_height !=0){ + $(".tootls_host_list").css("height",_tool_host_height+"px"), + $(".tootls_commonly_list").css("height", _tool_commonly_height+"px"); + } + }else{ + $(".term-tool-button").empty(); + $(".term-tool-button").append('').addClass("tool-show").removeClass("tool-hide"); + $(".term_box").css("margin-right","0px"); + $(".term_tootls").css("display","none"); + } + + //终端工具栏显示 + $('.term_content_tab').on('click','.tool-show',function(){ + $(this).empty(); + $(this).append('').addClass("tool-hide").removeClass("tool-show"); + $(".term_box").css("margin-right","260px"); + $(".term_tootls").css("display","block"); + localStorage.setItem("tool_status",0); + that.on_resize(that); + }); + + //终端工具栏隐藏 + $('.term_content_tab').on('click','.tool-hide',function(){ + $(this).empty(); + $(this).append('').addClass("tool-show").removeClass("tool-hide"); + $(".term_box").css("margin-right","0px"); + $(".term_tootls").css("display","none"); + localStorage.setItem("tool_status",1); + that.on_resize(that); + }); + + $(".term-move-border").on('mousedown', function (e) { + var hostbox_height = parseInt($(".tootls_host_list").css("height")), + commonlybox_height = parseInt($(".tootls_commonly_list").css("height")), + max_height = hostbox_height + commonlybox_height+38, + move_y = e.clientY; + $(document).on('mousemove', function (ev) { + var offsetY = ev.clientY - move_y, + _host = hostbox_height+offsetY; + _commonly = commonlybox_height-offsetY; + if(_host <= 300){ + _host = 300;_commonly = max_height-_host-38; + }else + if(_commonly <= 100){ + _commonly = 100;_host = max_height-_commonly-38; + } + $(".tootls_host_list").css("height",_host+"px"),$(".tootls_commonly_list").css("height",_commonly+"px"); + }); + $(document).on('mouseup', function (ev) { + var _host_height = parseInt($(".tootls_host_list").css("height")), + _commonly_height = parseInt($(".tootls_commonly_list").css("height")); + localStorage.setItem("hostHeight",_host_height); + localStorage.setItem("commonlyHeight",_commonly_height); + $(this).unbind('mousemove mouseup'); + }); + e.stopPropagation(); + }); + + $('.term_item_tab').on('click','.icon-trem-close',function(){ + var id = $(this).parent().data('id'); + that.remove_term_view(id); + }) + + + // 服务器列表工具箱 + $('.tootls_host_list').on('click','li .tootls span',function(ev){ + var item = $(this).parent().parent(),host = item.data('host'),index = item.data('index'); + if(!$(this).index()){ + that.get_host_find(host,function(rdata){ + if(rdata.status === false){ + bt.msg(rdata); + return false; + } + that.editor_host_view({ + form:rdata, + config: {btn: 'Save', title: 'Edit server information [ '+ host +' ]'} + }); + }); + }else{ + bt.confirm({title:'Delete information',msg:'Delete service information [ '+ host +' ], continue?',icon:0},function(index){ + that.remove_host(host,function(rdata){ + layer.close(index); + that.reader_host_list(function(){ + bt.msg(rdata); + }); + }); + }); + } + ev.stopPropagation(); + }); + // 服务器列表工具箱 + $('.tootls_commonly_list').on('click','li .tootls span',function(ev){ + var item = $(this).parent().parent(),title = item.data('title'),index = item.data('index'); + if(!$(this).index()){ + that.get_command_find(title,function(rdata){ + if(rdata.status === false){ + bt.msg(rdata); + return false; + } + that.editor_command_view({ + form:rdata, + config: {btn: 'Save', title: 'Edit command information【'+ title +'】'} + }); + }); + }else{ + bt.confirm({title:'Delete command',msg:'Delete service command 【'+ title +'】, continue?',icon:0},function(index){ + that.remove_command(title,function(rdata){ + layer.close(index); + that.reader_command_list(function(){ + bt.msg(rdata); + }); + }); + }); + } + ev.stopPropagation(); + }); + // 右键菜单 + $('.term_item_tab .list').on('mousedown','.item',function(ev){ + if(ev.which == 3){ + that.reader_right_menu({ + el:$(this), + position:[ev.clientX,ev.clientY], + list:[ + [{title:'Copy session',event:function(el,data){ + that.open_term_view(that.host_term[data.id].ssh_info); + }}], + [{title:'Close session',event:function(el,data){ + that.remove_term_view(data.id); + }}, + {title:'Close to right',event:function(el,data){ + bt.confirm({msg:'After closing the terminal session, the command in progress in the current command line session may be aborted. Continue?',title: "Close the terminal session?"},function(index){ + that.remove_term_right_view(data.id); + layer.close(index); + }); + }}, + {title:'Close other',event:function(el,data){ + bt.confirm({msg:'After closing the terminal session, the command in progress in the current command line session may be aborted. Continue?',title: "Close the terminal session?"},function(index){ + that.remove_term_other_view(data.id); + layer.close(index); + }); + }}] + ] + },function(){ + $(document).unbind('contextmenu'); + }); + ev.preventDefault(); + $(document).contextmenu(function(e){ + e.preventDefault(); + }); + } + }); + + // 添加服务器和常用秘钥 + $('.term_tootls .tootls_tab a').click(function(){ + var type = $(this).data('type'); + if(type == 'host'){ + that.editor_host_view(); + }else{ + that.editor_command_view(); + } + }); + // var clientX = null,clientY = null; + // $('.tootls_host_list').on('mousedown','li',function(e){ + // clientX = e.clientX,clientY = e.clientY; + // }) + // 模拟触发服务器列表点击事件 + // $('.tootls_host_list').on('mouseup','li',function(e){ + // if(e.button == 0){ + // if(clientX == e.clientX && clientY == e.clientY){ + + // } + // } + // }); + + $('.tootls_host_list').on('click','li',function(e){ + var index = $(this).index(),host = $(this).data('host'); + $(this).find('i').addClass('active'); + if($('.item[data-host="'+ host +'"]').length > 0){ + layer.msg('For multi session window, right click the terminal title to copy the session!',{icon:0,time:3000}) + }else{ + that.open_term_view(that.host_list[index]); + } + }); + // 服务器列表拖动 + $('.tootls_host_list').dragsort({ + dragSelector:'li i', + dragEnd:function(){ + clearTimeout(that.sort_time); + that.sort_time = setTimeout(function(){ + var sort_list = {}; + $('.tootls_host_list li').each(function(index,el){ + sort_list[$(this).data('host')] = index; + }); + that.set_sort(sort_list,function(rdata){ + if(!rdata.status){ + bt.msg(rdata); + } + }); + },500); + }, + dragBetween:false, + }); + this.reader_host_list(); + this.reader_command_list(); + }, + // 判断全屏状态 + isFullScreen:function() { + var is_full = document.isFullScreen || document.mozIsFullScreen || document.webkitIsFullScreen; + this.is_full = is_full + return is_full; + }, + + // 进入全屏 + requestFullScreen:function(element){ + if(element == undefined) element = document.documentElement; + // 判断各种浏览器,找到正确的方法 + var requestMethod = element.requestFullScreen || //W3C + element.webkitRequestFullScreen || //FireFox + element.mozRequestFullScreen || //Chrome等 + element.msRequestFullScreen; //IE11 + if (requestMethod) { + requestMethod.call(element); + } else if (typeof window.ActiveXObject !== "undefined") { //for Internet Explorer + var wscript = new ActiveXObject("WScript.Shell"); + if (wscript !== null) { + wscript.SendKeys("{F11}"); + } + } + this.is_full = true; + }, + // 退出全屏 + exitFullscreen:function(element) { + if(element == undefined) element = document.documentElement; + // 判断各种浏览器,找到正确的方法 + var exitMethod = document.exitFullscreen || //W3C + document.mozCancelFullScreen || //FireFox + document.webkitExitFullscreen || //Chrome等 + document.webkitExitFullscreen; //IE11 + if (exitMethod) { + exitMethod.call(document); + } else if (typeof window.ActiveXObject !== "undefined") { //for Internet Explorer + var wscript = new ActiveXObject("WScript.Shell"); + if (wscript !== null) { + wscript.SendKeys("{F11}"); + } + } + this.is_full = false; + }, + + on_resize:function(that){ + var win = $(window)[0],win_width = win.innerHeight,win_height = win.innerHeight,host_commonly = win_height - 185; + if(that.isFullScreen()){ + $('.main-content .safe').height(win_height); + $('#term_box_view,.term_tootls').height(win_height); + $('.tootls_host_list').height((win_height - 80) * .75); + $('.tootls_commonly_list').height((win_height - 80) * .25); + }else{ + $('.main-content .safe').height(win_height - 105); + $('#term_box_view,.term_tootls').height(win_height - 105); + $('.tootls_host_list').height(host_commonly * .75); + $('.tootls_commonly_list').height(host_commonly * .25); + } + var id = $('.term_item_tab .active').data('id'); + var item_term = that.host_term[id].term; + item_term.FitAddon.fit(); + that.host_term[id].resize({cols:item_term.cols, rows:item_term.rows}); + }, + + /** + * @name 本地服务器登录表单 + * @author chudong<2020-08-10> + * @return void + */ + localhost_login_form: function (result) { + var host_form_view = $('#host_form_view').html() + if (!host_form_view) return + var that = this,form = $(this.render_template({html:host_form_view,data:{form:$.extend(that.host_form,{host:'127.0.0.1'})}})),id = $('.localhost_item').data('id') + form.find('.ssh_ps_tips').remove(); + form.prepend('
                                  Login failed, please fill the local server information!
                                  '); + form.append(''); + $('#'+id).append('
                                  '+ form[0].innerHTML +'
                                  '); + if(result){ + if(result.indexOf('@127.0.0.1') != -1){ + var user = result.split('@')[0].split(',')[1]; + var port = result.split('1:')[1] + $("input[name='username']").val(user); + $("input[name='port']").val(port); + + } + + } + $('.auth_type_checkbox').click(function(){ + var index = $(this).index(); + $(this).addClass('btn-success').removeClass('btn-default').siblings().removeClass('btn-success').addClass('btn-default') + switch(index){ + case 0: + $('.c_password_view').addClass('show').removeClass('hidden'); + $('.c_pkey_view').addClass('hidden').removeClass('show').find('input').val(''); + $('.key_pwd_line').addClass('hidden').removeClass('show'); + break; + case 1: + $('.c_password_view').addClass('hidden').removeClass('show').find('input').val(''); + $('.c_pkey_view').addClass('show').removeClass('hidden'); + $('.key_pwd_line').addClass('show').removeClass('hidden'); + break; + } + }); + $('.localhost-form-view > button').click(function(){ + var form = {}; + $('.localhost-form-view input,.localhost-form-view textarea').each(function(index,el){ + var name = $(this).attr('name'),value = $(this).val(); + form[name] = value; + switch(name){ + case 'port': + if(!bt.check_port(value)){ + bt.msg({status:false,msg:'Server port format error!'}); + return false; + } + break; + case 'username': + if(value == ''){ + bt.msg({status:false,msg:'Server user name cannot be empty!'}); + return false; + } + break; + case 'password': + if(value == '' && $('.c_password_view').hasClass('show')){ + bt.msg({status:false,msg:'Server password cannot be empty!'}); + return false; + } + break; + case 'pkey': + if(value == '' && $('.c_pkey_view').hasClass('show')){ + bt.msg({status:false,msg:'The server key cannot be empty!'}); + return false; + } + break; + } + }); + delete form.sort + form.ps = 'Local server'; + that.create_host(form,function(res){ + bt.msg(res); + if(res.status){ + bt.msg({status:true,msg:'Login successful!'}); + $('.localhost_item .icon-trem-close').click(); + that.open_term_view(); + } + }); + }); + $('.localhost-form-view [name="password"]').keyup(function(e){ + if(e.keyCode == 13){ + $('.localhost-form-view > button').click(); + } + }).focus(); + }, + + reader_right_menu:function(config,callback){ + var menu = $('').css({'top':config.position[1],'left':config.position[0]}),html = ''; + bt.each(config.list,function(index,item){ + bt.each(item,function(indexs,items){ + (function(items){ + menu.append($('
                                • ').append($(''+ items.title +'').click(function(e){ + if(items.event) items.event(config.el,$(config.el).data()) + menu.remove(); + if(callback) callback(); + }))); + }(items)); + }); + if(index != config.list.length - 1){ + menu.append('
                                • '); + } + }); + if(!$('#term_title_menu').length){ + $('body').append(menu); + }else{ + $('#term_title_menu').replaceWith(menu); + } + $(document).click(function(e){ + menu.remove(); + $(this).unbind('click'); + if(callback) callback(); + }) + }, + /** + * @name 主机信息添加或编辑 + * @author chudong<2020-08-10> + * @param {Objeact} obj 需要编辑的form数据,可以为空,为空则添加 + * @return void + */ + editor_host_view:function(obj){ + var that = this; + if (!obj) { + obj = { + form: this.host_form, + config: { + btn: 'Submit', title: 'Add host information' + } + } + } + this.render_template({ + html: host_form_view.innerHTML, + data: obj + }, function (html) { + layer.open({ + type: 1 //Page层类型 + , area: '510px' + , closeBtn: 2 + , title: obj.config.title + , btn: [obj.config.btn, 'Cancel'] + , content: html + , success: function (layers, index) { + $('.auth_type_checkbox').click(function () { + var index = $(this).index(); + $(this).addClass('btn-success').removeClass('btn-default').siblings().removeClass('btn-success').addClass('btn-default') + switch (index) { + case 0: + $('.c_password_view').addClass('show').removeClass('hidden'); + $('.c_pkey_view').addClass('hidden').removeClass('show').find('input').val(''); + $('.key_pwd_line').addClass('hidden').removeClass('show'); + break; + case 1: + $('.c_password_view').addClass('hidden').removeClass('show').find('input').val(''); + $('.c_pkey_view').addClass('show').removeClass('hidden'); + $('.key_pwd_line').addClass('show').removeClass('hidden'); + break; + } + }); + + $('[name="host"]').on('change',function(){ + var host_val = $(this).val(); + if(!host_val) return; + + var s_host,s_port,s_username,s_password; + s_host = host_val; + if(host_val.indexOf('@') != -1){ + var tmp = host_val.split('@') + s_host = tmp[1] + s_username = tmp[0] + + if (s_username.indexOf(':') != -1){ + var tmp = s_username.split(':') + s_username = tmp[0] + s_password = tmp[1] + } + } + if(s_host.indexOf(':')!=-1){ + var tmp = s_host.split(':') + s_host = tmp[0] + s_port = tmp[1] + } + + if(s_host) { + $(this).val(s_host); + $('[name="ps"]').val(s_host); + } + if(s_port) $('[name="port"]').val(s_port); + if(s_username) $('[name="username"]').val(s_username); + if(s_password) $('[name="password"]').val(s_password); + }); + + $('[name="host"]').on('input',function(){ + $('[name="ps"]').val($(this).val()); + }); + $('[name="password"],[name="ps"]').keyup(function(e){ + if(e.keyCode === 13){ + $('#layui-layer'+ index +' .layui-layer-btn0').click(); + } + }); + }, + yes:function(indexs,layero){ + var form = {}; + $('.bt-form input,.bt-form textarea').each(function(index,el){ + var name = $(this).attr('name'),value = $(this).val(); + form[name] = value; + switch(name){ + // case 'host': + // if(!bt.check_ip(value)){ + // bt.msg({status:false,msg:'服务器ip地址格式错误!'}); + // return false; + // } + // break; + case 'port': + if(!bt.check_port(value)){ + bt.msg({status:false,msg:'Server port format error!'}); + return false; + } + break; + case 'username': + if(value == ''){ + bt.msg({status:false,msg:'Server user name cannot be empty!'}); + return false; + } + break; + case 'password': + if(value == '' && $('.c_password_view').hasClass('show')){ + bt.msg({status:false,msg:'Server password cannot be empty!'}); + return false; + } + break; + case 'pkey': + if(value == '' && $('.c_pkey_view').hasClass('show')){ + bt.msg({status:false,msg:'Server key cannot be empty!'}); + return false; + } + break; + } + }); + if(!obj.form.sort){ + delete form.sort; + that.create_host(form,function(res){ + if(res.status){ + that.open_term_view(form); + layer.close(indexs) + that.reader_host_list(function(){ + bt.msg(res); + }) + } + }); + }else{ + form.new_host = form.host; + form.host = obj.form.host; + that.modify_host(form,function(res){ + if(res.status){ + layer.close(indexs) + that.reader_host_list(function(){ + bt.msg(res); + }) + } + }) + } + } + }); + }); + }, + + /** + * @name 常用信息添加或编辑 + * @author chudong<2020-08-10> + * @param {Objeact} obj 需要编辑的form数据,可以为空,为空则添加 + * @return void + */ + editor_command_view: function (obj) { + var that = this; + if (!obj) { + obj = { + form: this.command_form, + config: { + btn: 'Submit', title: 'Add command information' + } + }; + } + this.render_template({ + html: shell_form_view.innerHTML, + data: obj + }, function (html) { + layer.open({ + type: 1 //Page层类型 + , area: '510px' + , closeBtn: 2 + , title: obj.config.title + , btn: [obj.config.btn, 'Cancel'] + , content: html + , yes: function (indexs, layero) { + var shell = $('[name="shell"]').val(), title = $('[name="title"]').val(); + if (title == '') { + bt.msg({status: false, msg: 'Command description cannot be empty!'}); + return false; + } + if (shell == '') { + bt.msg({status: false, msg: 'Command cannot be empty!'}); + return false; + } + + if(!obj.form.title){ + that.create_command({shell:shell,title:title},function(res){ + if(res.status){ + layer.close(indexs); + that.reader_command_list(function(){ + bt.msg(res); + }); + } + }); + }else{ + that.modify_command({new_title:title,title:obj.form.title,shell:shell},function(res){ + if(res.status){ + layer.close(indexs); + that.reader_command_list(function(){ + bt.msg(res); + }); + } + }); + } + }, + + }); + }); + }, + /** + * @name 设置终端标题状态 + * @author chudong<2020-08-10> + * @param {String} id 终端ID + * @param {number} status 终端状态 + * @return void + */ + set_term_icon:function(id,status){ + var icon_list = ['icon-warning','icon-sucess','icon-info']; + $("[data-id='"+ id +"']").find('.icon').removeAttr('class').addClass(icon_list[status]+' icon'); + }, + /** + * @name 打开终端显示视图 + * @author chudong<2020-08-10> + * @param {Objeact} info 终端数据 { + * ps:已添加的备注, + * host:已添加的服务器ip + * } + * @return void + */ + open_term_view:function(info){ + if(typeof info === "undefined") info = {host:'127.0.0.1',ps:'Local server'} + var random = bt.get_random(9),tab_content = $('.term_content_tab'),item_list = $('.term_item_tab .list'); + tab_content.find('.term_item').removeClass('active').siblings().removeClass('active'); + tab_content.append('
                                  '); + item_list.find('.item').removeClass('active'); + item_list.append('
                                  '+ info.ps +'
                                  '); + this.host_term[random] = new Terms('#'+random,{ssh_info:{host:info.host,ps:info.ps,id:random}}); + }, + /** + * @name 关闭终端显示视图 + * @author chudong<2020-08-10> + * @param {String} id 终端id + * @return void + */ + remove_term_view:function(id){ + var item = $('[data-id="'+ id +'"]'),next = item.next(),prev = item.prev(); + $('#'+id).remove(); + item.remove(); + try { + this.host_term[id].bws.close(); + } catch (error) { + } + delete this.host_term[id]; + if(item.hasClass('active')){ + if(next.length > 0){ + next.click(); + }else{ + prev.click(); + } + } + }, + /** + * @name 关闭选中右侧终端显示视图 + * @author chudong<2020-08-10> + * @param {String} id 终端id + * @return void + */ + remove_term_right_view:function(id){ + var arry = [],item = $('[data-id="'+ id +'"]'),nextAll = item.nextAll(),that = this; + if(!nextAll.length){ + return false; + } + nextAll.each(function(index,el){ + var data = $(this).data(); + try { + that.host_term[data.id].bws.close(); + } catch (error) { + } + delete that.host_term[data.id]; + }); + nextAll.remove(); + item.addClass('active'); + $('#'+id).addClass('active').nextAll().remove(); + }, + /** + * @name 关闭其他终端显示视图 + * @author chudong<2020-08-10> + * @param {String} id 终端id + * @return void + */ + remove_term_other_view:function(id){ + var arry = [],item = $('[data-id="'+ id +'"]'),siblings = item.siblings(),that = this; + if(!siblings.length){ + return false; + } + siblings.each(function(index,el){ + var data = $(this).data(); + try { + that.host_term[data.id].bws.close(); + } catch (error) { + } + delete that.host_term[data.id]; + }); + siblings.remove(); + item.addClass('active'); + $('#'+id).addClass('active').siblings().remove(); + }, + /** + * @name 渲染常用命令列表 + * @author chudong<2020-08-10> + * @param {Objeact} callback 回调函数,回调参数1:当前请求内容 + * @return void + */ + reader_command_list:function(callback){ + var that = this,html = ''; + this.get_command_list(function(rdata){ + bt.each(rdata,function(index,item){ + html += '
                                • '+ item.title +''+ + ''+ + ''+ + '
                                • '; + }); + $('.tootls_commonly_list').html(html); + var clipboard = new ClipboardJS('.tootls_commonly_list li'); + clipboard.on('success', function(e) { + layer.msg('Copy succeeded!',{icon:1}); + e.clearSelection(); + }); + + clipboard.on('error', function(e) { + console.error('Action:', e.action); + console.error('Trigger:', e.trigger); + }); + that.command_list = rdata; + if(callback) callback(rdata) + }); + }, + + + + /** + * @name 渲染主机视图列表 + * @author chudong<2020-08-10> + * @param {Objeact} callback 回调函数,回调参数1:当前请求内容 + * @return void + */ + reader_host_list:function(callback){ + var that = this,html = ''; + this.get_host_list(function(rdata){ + bt.each(rdata,function(index,item){ + html += '
                                • '+ (item.ps == item.host?item.ps:(item.ps +'【'+ item.host +'】')) +''+ + ''+ + ''+ + '
                                • '; + }); + $('.tootls_host_list').html(html); + that.host_list = rdata; + if(callback) callback() + }); + }, + /** + * @name 获取host列表 + * @author chudong<2020-08-010> + * @param {Objeact} callback 回调函数,回调参数1:当前请求内容 + * @return void + */ + get_host_list:function(callback){ + var loadT = bt.load('Getting server list, please wait...'); + this.post('get_host_list',{},function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + /** + * @name 获取指定host信息 + * @author hwliang<2020-08-07> + * @param host string host地址 + * @return void + */ + get_host_find:function(host,callback){ + var loadT = bt.load('Getting the specified server information, please wait...'); + this.post('get_host_find',{host:host},function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + + /** + * @name 创建新的host信息 + * @author hwliang<2020-08-07> + * @param ssh_info array ssh信息对象 { + * host: 主机地址, + port: 端口 + ps: 备注 + username: 用户名 + password: 密码 + pkey: 密钥(如果不为空,将使用密钥连接) + * } + * @return void + */ + create_host:function(ssh_info,callback){ + var loadT = bt.load('Adding server information, please wait...'); + this.post('create_host',ssh_info,function(rdata){ + loadT.close(); + if(!rdata.status){ + bt.msg(rdata); + return false; + } + if(callback) callback(rdata); + }); + }, + + /** + * @name 修改host信息 + * @author hwliang<2020-08-07> + * @param ssh_info array ssh信息对象 { + * host: 主机地址, + port: 端口 + ps: 备注 + sort: 排序(可选,默认0) + username: 用户名 + password: 密码 + pkey: 密钥(如果不为空,将使用密钥连接) + * } + * @return void + */ + modify_host:function(ssh_info,callback){ + var loadT = bt.load('Modifying the specified server information, please wait...'); + this.post('modify_host',ssh_info,function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + + /** + * @name 删除host信息 + * @author hwliang<2020-08-07> + * @param host string host地址 + * @return void + */ + remove_host:function(host,callback){ + var loadT = bt.load('Deleting specified server information, please wait...'); + this.post('remove_host',{host:host},function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + + /** + * @name 设置host排序(使用降序) + * @author hwliang<2020-08-07> + * @param sort_list array 排序对象{ + * "127.0.0.1":3, + * "192.168.1.254":2 + * } + * @return void + */ + set_sort:function(sort_list,callabck){ + this.post('set_sort',{sort_list:JSON.stringify(sort_list)},function(rdata){ + if(callabck) callabck(rdata); + }); + }, + + + /** + * @name 获取常用命令列表 + * @author hwliang<2020-08-08> + * @return void + */ + get_command_list: function(callback){ + var loadT = bt.load('Getting list of frequently used commands, please wait...'); + this.post('get_command_list',{},function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + /** + * @name 创建常用命令 + * @author hwliang<2020-08-08> + * @param {title} string 命令标题 + * @parma {shell} string 命令文本 + * @return void + */ + create_command: function(obj,callback){ + var loadT = bt.load('Creating common commands, please wait...'); + this.post('create_command',obj,function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + + /** + * @name 获取指定常用命令 + * @author hwliang<2020-08-08> + * @param {title} string 命令标题 + * @return void + */ + get_command_find: function(title,callback){ + var loadT = bt.load('Getting specified common command data, please wait...'); + this.post('get_command_find',{title:title},function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + /** + * @name 修改常用命令 + * @author hwliang<2020-08-08> + * @param {title} string 命令标题 + * @param {new_title} string 新的命令标题 + * @parma {shell} string 命令文本 + * @return void + */ + modify_command: function(obj,callback){ + var loadT = bt.load('Modifying specified common commands, please wait...'); + this.post('modify_command',obj,function(rdata){ + loadT.close(); + if(callback) callback(rdata); + }); + }, + + /** + * @name 删除指定常用命令 + * @author hwliang<2020-08-08> + * @param {title} string 命令标题 + * @return void + */ + remove_command: function(title,callback){ + var loadT = bt.load('Deleting specified common commands, please wait...'); + this.post('remove_command',{title:title},function(rdata){ + loadT.close(); + // console.log(rdata) + if(callback) callback(rdata); + }); + }, + + + /** + * @name 请求到后端 + * @author hwliang<2020-08-07> + * @param fun_name string 要访问的方法名称 + * @param pdata array POST数据 + * @param callback function 回调函数 + * @return void + */ + post:function(fun_name,pdata,callback){ + bt.send(fun_name,'xterm/'+fun_name,pdata,callback); + }, + + /** + * @author chudong<2020-08-08> + * @param {Object} obj 需要渲染的配置对象 + * @param {*} callback 渲染完成的回调方法 + */ + render_template:function(obj, callback) { + if (!obj.html) return '缺少模板HTML'; + var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', + cursor = 0; + var add = function (line, js) { + js ? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') : + (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : ''); + return add; + } + while (match = re.exec(obj.html)) { + add(obj.html.slice(cursor, match.index))(match[1], true); + cursor = match.index + match[0].length; + } + add(obj.html.substr(cursor, obj.html.length - cursor)); + code += 'return r.join("");'; + if (callback) { + callback(new Function(code.replace(/[\r\t\n]/g, '')).apply(obj.data)); + } else { + return new Function(code.replace(/[\r\t\n]/g, '')).apply(obj.data); + } + } +} +host_trem.init(); \ No newline at end of file diff --git a/BTPanel/static/vite/oldjs/tools.js b/BTPanel/static/vite/oldjs/tools.js new file mode 100644 index 00000000..35bbe7ed --- /dev/null +++ b/BTPanel/static/vite/oldjs/tools.js @@ -0,0 +1,3193 @@ +var bt_tools = { + commandConnectionPool: {}, //ws连接池 + /** + * @description 表格渲染 + * @param {object} config 配置对象 参考说明 + * @return 当前实例对象 + */ + table: function (config) { + var that = this, + table = $(config.el), + tableData = table.data('table'); + if (tableData && table.find('table').length > 0) { + if (config.url !== undefined) { + tableData.$refresh_table_list(true); + } else if (config.data !== undefined) { + tableData.$reader_content(config.data); + } + return tableData; + } + function ReaderTable(config) { + this.config = config; + this.$load(); + } + + ReaderTable.prototype = { + style_list: [], // 样式列表 + event_list: {}, // 事件列表,已绑定事件 + checkbox_list: [], // 元素选中列表 + batch_active: {}, + event_rows_model: {}, // 事件行内元素模型,行内元素点击都会将数据临时存放 + data: [], + page: '', + column: [], + batch_thread: [], + random: bt.get_random(5), + init: false, // 是否初始化渲染 + checked: false, // 是否激活,用来判断是否失去焦点 + /** + * @description 加载数据 + * @return void + */ + $load: function () { + var _that = this; + if (this.config.init) this.config.init(this); + $(this.config.el).addClass('bt_table'); + if (this.config.minWidth) + this.style_list.push({ + className: this.config.el + ' table', + css: 'min-width:' + this.config.minWidth, + }); + if (this.config.tootls) { + this.$reader_tootls(this.config.tootls); + } else { + if ($(_that.config.el + '.divtable').length === 0) $(_that.config.el).append('
                                  '); + } + this.$reader_content(); + if (_that.config.url !== undefined) { + this.$refresh_table_list(_that.config.load || false); + } else if (this.config.data !== undefined) { + this.$reader_content(this.config.data); + } else { + alert('缺少data或url参数'); + } + if (this.config.methods) { + //挂载实例方法 + $.extend(this, this.config.methods); + } + if (this.config.height) bt_tools.$fixed_table_thead(this.config.el + ' .divtable'); + }, + + /** + * @description 刷新表格数据 + * @param {boolean} load + * @param {function} callback 回调函数 + * @return void + */ + $refresh_table_list: function (load, callback) { + var _that = this; + this.$http(load, function (data) { + if (callback) callback(data); + _that.$reader_content(data.data, typeof data.total != 'undefined' ? parseInt(data.total) : data.page); + }); + }, + + /** + * @description 渲染内容 + * @param {object} data 渲染的数据 + * @param {number} page 数据分页/总数 + * @return void + */ + $reader_content: function (data, page) { + var _that = this, + thead = '', + tbody = '', + i = 0, + column = this.config.column, + event_list = {}, + checkbox = $(_that.config.el + ' .checkbox_' + _that.random); + data = data || []; + this.data = data; + if (checkbox.length) { + checkbox.removeClass('active selected'); + _that.checkbox_list = []; + _that.$set_batch_view(); + } + do { + var rows = data[i], + completion = 0; + if (data.length > 0) tbody += ''; + for (var j = 0; j < column.length; j++) { + var item = column[j]; + if ($.isEmptyObject(item)) { + completion++; + continue; + } + if (i === 0 && !this.init) { + if (!this.init) this.style_list.push(this.$dynamic_merge_style(item, j - completion)); + var sortName = 'sort_' + this.random + '', + checkboxName = 'checkbox_' + this.random, + sortValue = item.sortValue || 'desc'; + thead += + '' + + (item.type == 'checkbox' + ? '' + : '' + item.title + '') + + (item.sort ? '' : '') + + ''; + if (i === 0) { + if (!event_list[sortName] && item.sort) + event_list[sortName] = { + event: this.config.sortEvent, + eventType: 'click', + type: 'sort', + }; + if (!event_list[checkboxName]) + event_list[checkboxName] = { + event: item.checked, + eventType: 'click', + type: 'checkbox', + }; + } + } + if (rows !== undefined) { + var template = '', + className = 'event_' + item.fid + '_' + this.random; + if (item.template) { + template = _that.$custom_template_render(item, rows, j); + } + if (typeof template === 'undefined' || typeof item.template === 'undefined') { + template = this.$reader_column_type(item, rows); + event_list = $.extend(event_list, template[1]); + template = template[0]; + } + var fixed = false; + if (typeof item.fixed != 'undefined' && item.fixed) { + if (typeof item.class != 'undefined') { + if (item.class.indexOf('fixed') === -1) item.class += ' fixed'; + } else { + item.class = 'fixed'; + } + fixed = true; + } + tbody += + '' + + template + + ''; + if (i === 0) { + if (!event_list[className] && item.event) + event_list[className] = { + event: item.event, + eventType: 'click', + type: 'rows', + }; + } + } + } + if (data.length > 0) tbody += ''; + if (data.length == 0) tbody += '' + (this.config['default'] || lan['public'].empty) + ''; + i++; + } while (i < data.length); + if (!this.init) this.$style_bind(this.style_list); + this.$event_bind(event_list); + if (!this.init) { + $(this.config.el + ' .divtable').append( + '' + thead + '' + tbody + '
                                  ' + ); + } else { + $(this.config.el + ' .divtable tbody').html(tbody); + if (this.config.page) { + $(this.config.el + ' .page').replaceWith(this.$reader_page(this.config.page, page)); + } + } + this.init = true; + if (this.config.success) this.config.success(this); + }, + /** + * @description 自定模板渲染 + * @param {object} item 当前元素模型 + * @param {object} rows 当前元素数据 + * @param {number} j 当前模板index + * @return void + */ + $custom_template_render: function (item, rows, j) { + var className = 'event_' + item.fid + '_' + this.random, + _template = item.template(rows, j), + $template = $(_template); + if ($template.length > 0) { + template = $template.addClass(className)[0].outerHTML; + } else { + if (item.type === 'text') { + template = '' + _template + ''; + } else { + template = '' + _template + ''; + } + } + return template; + }, + + /** + * @description 替换table数据 + * @param {string} newValue 内容数据 + * @return void + */ + $modify_row_data: function (newValue) { + this.event_rows_model.rows = $.extend(this.event_rows_model.rows, newValue); + var row_model = this.event_rows_model, + template = null; + if (typeof row_model.model.template != 'undefined') { + template = $(this.$custom_template_render(row_model.model, row_model.rows, row_model.index)); + if (!template.length) template = $(this.$reader_column_type(row_model.model, row_model.rows)[0]); + } else { + template = $(this.$reader_column_type(row_model.model, row_model.rows)[0]); + } + if (row_model.model.type == 'group') { + $(row_model.el).parent().empty().append(template); + } else { + row_model.el.replaceWith(template); + } + row_model.el = template; + }, + + /** + * @description 批量执行程序 + * @param {object} config 配置文件 + * @return void + */ + $batch_success_table: function (config) { + var _that = this, + length = $(config.html).length; + bt.open({ + type: 1, + title: config.title, + area: config.area || ['400px'], + shadeClose: false, + closeBtn: 2, + content: + config.content || + '
                                  ' + + config.title + + ' ' + + lan['public'].success + + '
                                  ' + + config.html + + '
                                  ' + + config.th + + '' + + lan['public'].result + + '
                                  ', + success: function () { + if (length > 4) _that.$fixed_table_thead('.fiexd_thead'); + }, + }); + }, + /** + * @description 固定表头 + * @param {string} el DOM选择器 + * @return void + */ + $fixed_table_thead: function (el) { + $(el).scroll(function () { + var scrollTop = this.scrollTop; + this.querySelector('thead').style.transform = 'translateY(' + (scrollTop - 1) + 'px)'; + }); + }, + /** + * @description 删除行内数据 + */ + $delete_table_row: function (index) { + this.data.splice(index, 1); + this.$reader_content(this.data); + }, + + /** + * @description 设置批量操作显示 + * @return void 无 + */ + $set_batch_view: function () { + if (typeof this.config.batch != 'undefined') { + //判断是否存在批量操作 + var bt_select_btn = $(this.config.el + ' .set_batch_option'); + if (typeof this.config.batch.config != 'undefined') { + // 判断批量操作是多个还是单个 + if (this.checkbox_list.length > 0) { + bt_select_btn + .removeClass('bt-disabled btn-default') + .addClass('btn-success') + .text(lan['public'].please_choose + this.batch_active.title + '(' + lan['public'].selected + this.checkbox_list.length + ')'); + } else { + bt_select_btn + .addClass('bt-disabled btn-default') + .removeClass('btn-success') + .text(lan['public'].please_choose + this.batch_active.title); + } + } else { + var bt_select_val = $(this.config.el + ' .bt_table_select_group .bt_select_value'); + if (this.checkbox_list.length > 0) { + bt_select_btn.removeClass('bt-disabled btn-default').addClass('btn-success').prev().removeClass('bt-disabled'); + bt_select_val.find('em').html('(' + lan['public'].selected + this.checkbox_list.length + ')'); + } else { + bt_select_btn.addClass('bt-disabled btn-default').removeClass('btn-success').prev().addClass('bt-disabled'); + bt_select_val + .children() + .eq(0) + .html(lan['public'].please_choose + ''); + bt_select_val.next().find('li').removeClass('active'); + this.batch_active = {}; + } + } + } + }, + + /** + * @description 渲染指定类型列内容 + * @param {object} data 渲染的数据 + * @param {object} rows 渲染的模板 + * @return void + */ + $reader_column_type: function (item, rows) { + var value = rows[item.fid], + event_list = {}, + className = 'click_' + item.fid + '_' + this.random, + config = [], + _that = this; + switch (item.type) { + case 'text': //普通文本 + config = [value, event_list]; + break; + case 'checkbox': //单选内容 + config = ['', event_list]; + break; + case 'password': + var _copy = '', + _eye_open = '', + className = 'ico_' + _that.random + '_', + html = '**********'; + if (item.eye_open) { + html += ''; + if (!event_list[className + 'eye_open']) + event_list[className + 'eye_open'] = { + type: 'eye_open_password', + }; + } + if (item.copy) { + html += ''; + if (!event_list[className + 'copy']) + event_list[className + 'copy'] = { + type: 'copy_password', + }; + } + config = [html, event_list]; + break; + case 'link': //超链接类型 + className += '_' + item.fid; + if (!event_list[className] && item.event) + event_list[className] = { + event: item.event, + type: 'rows', + }; + config = [ + '' + + value + + '', + event_list, + ]; + break; + case 'input': //可编辑类型 + blurName = 'blur_' + item.fid + '_' + this.random; + keyupName = 'keyup_' + item.fid + '_' + this.random; + if (!event_list[blurName] && item.blur) + event_list[blurName] = { + event: item.blur, + eventType: 'blur', + type: 'rows', + }; + if (!event_list[keyupName] && item.keyup) + event_list[keyupName] = { + event: item.keyup, + eventType: 'keyup', + type: 'rows', + }; + config = ['', event_list]; + break; + case 'status': // 状态类型 + var active = ''; + $.each(item.config.list, function (index, items) { + if (items[0] === value) active = items; + }); + if (!event_list[className] && item.event) + event_list[className] = { + event: item.event, + type: 'rows', + }; + config = [ + '' + + active[1] + + '' + + (item.config.icon ? '' : '') + + '', + event_list, + ]; + break; + case 'switch': //开关类型 + var active = '', + _random = bt.get_random(5); + active = new Number(value) == true ? 'checked' : ''; + if (!event_list[className] && item.event) + event_list[className] = { + event: item.event, + type: 'rows', + }; + config = [ + '
                                  ', + event_list, + ]; + break; + case 'group': + var _html = ''; + $.each(item.group, function (index, items) { + className = (item.fid ? item.fid : 'group') + '_' + index + '_' + _that.random; + var _hide = false; + if (items.template) { + var _template = items.template(rows, _that), + $template = $(_template); + if ($template.length > 0) { + _html += $template.addClass(className)[0].outerHTML; + } else { + _html += '' + _template + ''; + } + } else { + if (typeof items.hide != 'undefined') { + _hide = typeof items.hide === 'boolean' ? items.hide : items.hide(rows); + if (typeof _hide != 'boolean') return false; + } + _html += '' + items.title + ''; + } + //当前操作按钮长度等于当前所以值时不向后添加分割 + if (!_hide) { + if (items.template) { + var _template = items.template(rows, _that); + if (_template == '') { + _html += ''; + } else { + _html += item.group.length == index + 1 ? '' : ' | '; + } + } else { + _html += item.group.length == index + 1 ? '' : ' | '; + } + } + if (!event_list[className] && items.event) + event_list[className] = { + event: items.event, + type: 'rows', + }; + }); + config = [_html, event_list]; + break; + default: + config = [value, event_list]; + break; + } + return config; + }, + /** + * @description 批量执行程序 + * @param {object} config 配置文件 + * @return void + */ + $batch_success_table: function (config) { + that.$batch_success_table(config); + }, + /** + * @description 渲染工具条 + * @param {object} data 配置参数 + * @return void + */ + $reader_tootls: function (config) { + var _that = this, + event_list = {}; + + /** + * @description 请求方法 + * @param {Function} callback 回调函数 + * @returns void + */ + function request(active, check_list) { + var loadT = bt.load(active.title + lan.site.executing), + batch_config = {}, + list = _that.$get_data_batch_list(active.paramId, check_list); + if (!active.beforeRequest) { + batch_config[active.paramName] = list.join(','); + } else { + batch_config[active.paramName] = active.beforeRequest(check_list); + } + bt_tools.send( + { + url: active.url || _that.config.batch.url, + data: $.extend(active.param || {}, batch_config), + }, + function (res) { + loadT.close(); + if (res.status === false && typeof res.success === 'undefined') { + bt_tools.msg(res); + return false; + } + if (typeof active.tips === 'undefined' || active.tips) { + var html = ''; + $.each(res.error, function (key, item) { + html += + '' + + key + + '
                                  ' + + item + + '
                                  '; + }); + $.each(res.success, function (index, item) { + html += + '' + + item + + '
                                  ' + + lan['public'].success + + '
                                  '; + }); + _that.$batch_success_table({ + title: active.title, + th: active.theadName, + html: html, + }); + if (active.refresh) _that.$refresh_table_list(true); + } else { + if (!active.success) { + var html = ''; + $.each(check_list, function (index, item) { + html += + '' + + item.name + + '
                                  ' + + ((typeof active.theadValue != 'undefined' ? active.theadValue[res.status ? 0 : 1] : null) || res.msg) + + '
                                  '; + }); + _that.$batch_success_table({ + title: 'Batch' + active.title + 'Success', + th: active.theadName, + html: html, + }); + if (active.refresh) _that.$refresh_table_list(true); + } + } + if (active.success) { + active.success(res, check_list, _that); + } + } + ); + } + + /** + * @description 执行批量,包含递归批量和自动化批量 + * @returns void + */ + function execute_batch(active, check_list, success) { + // if(active.recursion) 递归方式 + var bacth = { + loadT: 0, + config: {}, + check_list: check_list, + bacth_status: true, + start_batch: function (param, callback) { + var _this = this; + if (typeof param == 'undefined') param = {}; + if (typeof param == 'function') (callback = param), (param = {}); + if (active.load) + this.loadT = layer.msg( + lan['public'].executeing + active.title + ',' + lan['public'].schedule + ':0/' + this.check_list.length + ',' + lan['public'].please_wait, + { + icon: 16, + skin: 'batch_tips', + shade: 0.3, + time: 0, + area: '400px', + } + ); + this.config = { + param: param, + url: active.url, + }; + this.bacth(callback); + }, + /** + * + * @param {Number} index 递归批量程序 + * @param {Function} callback 回调函数 + * @return void(0) + */ + bacth: function (index, callback) { + var _this = this, + param = {}; + if (typeof index === 'function' || typeof index === 'undefined') (callback = index), (index = 0); + if (index < this.check_list.length) { + 'function' == typeof active.url ? (this.config.url = active.url(check_list[index])) : (this.config.url = active.url); + if (typeof active.param == 'function') { + param = active.param(check_list[index]); + } else { + param = active.param; + } + this.config.param = $.extend(this.config.param, param); + if (typeof active.paramId != 'undefined') _this.config.param[active.paramName || active.paramId] = _this.check_list[index][active.paramId]; + if (typeof active.beforeBacth != 'undefined') this.config.param = $.extend(this.config.param, active.beforeBacth(_this.check_list[index])); + if (this.config.param['bacth'] && index == this.check_list.length - 1) { + delete this.config.param['bacth']; + } + if (!_this.bacth_status) return false; + if (active.load) + $('#layui-layer' + _this.loadT) + .find('.layui-layer-content') + .html( + '' + + lan['public'].executeing + + active.title + + ',' + + lan['public'].schedule + + ':' + + index + + '/' + + _this.check_list.length + + ',' + + lan['public'].please_wait + + (active.clear ? 'cancel' : '') + ); + bt_tools.send( + { + url: this.config.url, + data: this.config.param, + bacth: true, + }, + function (res) { + $.extend( + _this.check_list[index], + { + request: { + status: typeof res.status === 'boolean' ? res.status : false, + msg: res.msg || lan['public'].request_error, + }, + }, + { requests: res } + ); + index++; + _this.bacth(index, callback); + } + ); + } else { + if (success) success(); + if (callback) { + callback(this.check_list); + } + if (active.automatic) { + var html = ''; + for (var i = 0; i < this.check_list.length; i++) { + var item = this.check_list[i]; + html += + '' + + (typeof item[active.paramThead] != 'undefined' ? item[active.paramThead] : item.name) + + '
                                  ' + + item.request.msg + + '
                                  '; + } + _that.$batch_success_table({ + title: 'Batch' + active.title, + th: active.theadName, + html: html, + }); + if (active.refresh) _that.$refresh_table_list(true); + _that.$clear_table_checkbox(); + } + layer.close(this.loadT); + } + }, + clear_bacth: function () { + this.bacth_status = false; + layer.close(this.loadT); + }, + }; + if (active.callback) { + active.callback(bacth); + } else { + if (!active.confirm || active.recursion) { + if (active.confirmVerify) { + bt.show_confirm('批量操作' + active.title + '已选中', '批量' + active.title + ',该操作可能会存在风险,是否继续?', function (index) { + layer.close(index); + if (!active.recursion) { + request(active, check_list); + } else { + bacth.start_batch(); + } + }); + } else { + bt.confirm( + { + title: 'Batch ' + active.title, + msg: 'Please be cautious, The selected item will be [ ' + active.title + ' ] after confirmation', + shadeClose: active.shadeClose ? active.shadeClose : false, + }, + function (index) { + layer.close(index); + if (!active.recursion) { + request(active, check_list); + } else { + bacth.start_batch(); + } + } + ); + } + } else { + request(active, check_list); + } + } + } + + for (var i = 0; i < config.length; i++) { + var template = ''; + var item = config[i]; + var positon = []; + switch (item.type) { + case 'group': + positon = item.positon || ['left', 'top']; + $.each(item.list, function (index, items) { + var _btn = item.type + '_' + _that.random + '_' + index, + html = ''; + if (items.type == 'division') { + template += ''; + } else { + if (!items.group) { + template += + ''; + } else { + template += + '
                                  \ + \ + \ +
                                  '; + if (item.list) { + $.each(item.list, function (index, items) { + html += '
                                • ' + items[item.key] + '
                                • '; + }); + } + if (items.init) + setTimeout(function () { + items.init(_btn); + }, 400); + } + } + if (!event_list[_btn]) + event_list[_btn] = { + event: items.event, + type: 'button', + }; + }); + break; + case 'search': + positon = item.positon || ['right', 'top']; + item.value = item.value || ''; + this.config.search = item; + var _input = 'search_input_' + this.random, + _focus = 'search_focus_' + this.random, + _btn = 'search_btn_' + this.random; + template = + ''; + if (!event_list[_input]) + event_list[_input] = { + eventType: 'keyup', + type: 'search_input', + }; + if (!event_list[_focus]) + event_list[_focus] = { + type: 'search_focus', + eventType: 'focus', + }; + if (!event_list[_btn]) + event_list[_btn] = { + type: 'search_btn', + }; + break; + case 'batch': + positon = item.positon || ['left', 'bottom']; + item.placeholder = item.placeholder || '请选择批量操作'; + item.buttonValue = item.buttonValue || '批量操作'; + this.config.batch = item; + var batch_list = [], + _html = '', + active = item.config; + if (typeof item.config != 'undefined') { + _that.batch_active = active; + $(_that.config.el).on('click', '.set_batch_option', function (e) { + var check_list = []; + for (var i = 0; i < _that.checkbox_list.length; i++) { + check_list.push(_that.data[_that.checkbox_list[i]]); + } + if ($(this).hasClass('bt-disabled')) { + layer.tips(_that.config.batch.disabledTips || 'Select batch operation', $(this), { + tips: [1, 'red'], + time: 2000, + }); + return false; + } + switch (typeof active.confirm) { + case 'function': + active.confirm(active, function (param, callback) { + active.param = $.extend(active.param, param); + execute_batch(active, check_list, callback); + }); + break; + case 'undefined': + execute_batch(active, check_list); + break; + case 'object': + var config = active.confirm; + bt.open({ + title: config.title || 'Batch execute', + area: config.area || '350px', + btn: config.btn || ['Confirm', 'Cancel'], + content: config.content, + success: function (layero, index) { + config.success(layero, index, active); + }, + yes: function (index, layero) { + config.yes(index, layero, function (param, callback) { + active.param = $.extend(active.param, param); + request(active, check_list); + }); + }, + }); + break; + } + }); + } else { + $.each(item.selectList, function (index, items) { + if (items.group) { + $.each(items.group, function (indexs, itemss) { + batch_list.push($.extend({}, items, itemss)); + _html += '
                                • ' + itemss.title + '
                                • '; + }); + delete items.group; + } else { + batch_list.push(items); + _html += '
                                • ' + items.title + '
                                • '; + } + }); + // 打开批量类型列表 + $(_that.config.el) + .unbind() + .on('click', '.bt_table_select_group .bt_select_value', function (e) { + var _this = this, + $parent = $(this).parent(), + bt_selects = $parent.find('.bt_selects'), + area = $parent.offset(), + _win_area = _that.$get_win_area(); + if ($parent.hasClass('bt-disabled')) { + layer.tips(_that.config.batch.disabledSelectValue, $parent, { + tips: [1, 'red'], + time: 2000, + }); + return false; + } + if ($parent.hasClass('active')) { + $parent.removeClass('active'); + } else { + $parent.addClass('active'); + } + if (bt_selects.height() > _win_area[1] - area.top) { + bt_selects.addClass('top'); + } else { + bt_selects.removeClass('top'); + } + $(document).one('click', function () { + $(_that.config.el).find('.bt_table_select_group').removeClass('active'); + return false; + }); + return false; + }); + // 选择批量的类型 + $(_that.config.el).on('click', '.bt_table_select_group .item', function (e) { + var _text = $(this).text(), + _index = $(this).index(); + $(this).addClass('active').siblings().removeClass('active'); + $(_that.config.el + ' .bt_select_tips').html('Batch execute ' + _text + '(' + lan.site.have_been_selected + _that.checkbox_list.length + ')'); + _that.batch_active = batch_list[_index]; + if (!_that.checked) $('.bt_table_select_group').removeClass('active'); + }); + // 执行批量操作 + $(_that.config.el).on('click', '.set_batch_option', function (e) { + var check_list = [], + active = _that.batch_active; + if ($(this).hasClass('bt-disabled')) { + layer.tips(_that.config.batch.disabledSelectValue, $(this), { + tips: [1, 'red'], + time: 2000, + }); + return false; + } + for (var i = 0; i < _that.checkbox_list.length; i++) { + check_list.push(_that.data[_that.checkbox_list[i]]); + } + if (JSON.stringify(active) === '{}') { + var bt_table_select_group = $(_that.config.el + ' .bt_table_select_group'); + layer.tips(lan['public'].select_opt_type, bt_table_select_group, { + tips: [1, 'red'], + time: 2000, + }); + bt_table_select_group.css('border', '1px solid red'); + setTimeout(function () { + bt_table_select_group.removeAttr('style'); + }, 2000); + return false; + } + switch (typeof active.confirm) { + case 'function': + active.confirm(active, function (param, callback) { + active.param = $.extend(active.param, param); + execute_batch(active, check_list, callback); + }); + break; + case 'undefined': + execute_batch(active, check_list); + break; + case 'object': + var config = active.confirm; + bt.open({ + type: 1, + title: config.title || lan['public'].bulk_opt, + area: config.area || '350px', + btn: config.btn || [lan['public'].confirm, lan['public'].cancel], + content: config.content, + success: function (layero, index) { + config.success(layero, index, active); + }, + yes: function (index, layero) { + config.yes(index, layero, function (param, callback) { + active.param = $.extend(active.param, param); + layer.close(index); + request(active, check_list); + }); + }, + }); + break; + } + }); + } + template = + '
                                  ' + + (typeof item.config != 'undefined' + ? '' + : '
                                  ' + + lan['public'].select_opt_type + + '
                                    ' + + _html + + '
                                  ') + + '
                                  '; + break; + case 'page': + positon = item.positon || ['right', 'bottom']; + item.page = config.page || 1; + item.pageParam = item.pageParam || 'p'; + item.number = item.number || 20; + item.numberList = item.numberList || [10, 20, 50, 100, 200]; + item.numberParam = typeof item.numberParam === 'boolean' ? item.numberParam : item.numberParam || 'limit'; + this.config.page = item; + // var pageNumber = bt.get_cookie('page_number') + var pageNumber = this.$get_page_number(); + // if (this.config.cookiePrefix && pageNumber) this.config.page.number = pageNumber + if (pageNumber) this.config.page.number = pageNumber; + template = this.$reader_page(this.config.page, '
                                  1Total 0
                                  '); + break; + } + if (template) { + var tools_group = $(_that.config.el + ' .tootls_' + positon[1]); + if (tools_group.length) { + var tools_item = tools_group.find('.pull-' + positon[0]); + tools_item.append(template); + } else { + var tools_group_elment = + '
                                  ' + + (positon[0] === 'left' ? template : '') + + '
                                  ' + + (positon[0] === 'right' ? template : '') + + '
                                  '; + if (positon[1] === 'top') { + $(_that.config.el).append(tools_group_elment); + if ($(_that.config.el + ' .divtable').length === 0) $(_that.config.el).append('
                                  '); + } else { + if ($(_that.config.el + ' .divtable').length === 0) $(_that.config.el).append('
                                  '); + $(_that.config.el).append(tools_group_elment); + } + } + } + } + if (!this.init) this.$event_bind(event_list); + }, + + $clear_table_checkbox: function () { + $(this.config.el).find('.bt_table .cust—checkbox').removeClass('selected active'); + }, + /** + * @description 获取数据批量列表 + * @param {string} 需要获取的字段 + * @return {array} 当前需要批量列表 + */ + $get_data_batch_list: function (fid, data) { + var arry = []; + $.each(data || this.data, function (index, item) { + arry.push(item[fid]); + }); + return arry; + }, + + /** + * @description 渲染分页 + * @param {object} config 配置文件 + * @param {object} page 分页 + * @return string + */ + $reader_page: function (config, page) { + var template = '', + eventList = {}, + _that = this, + $page = null; + + if (config.number && !page) { + template = + (config.page !== 1 ? 'Start' : '') + + (config.page !== 1 ? 'Prev' : '') + + (_that.data.length === config.number ? 'Next' : '') + + 'Page ' + + config.page + + ''; + eventList['page_link_' + this.random] = { type: 'cut_page_number' }; + } else { + if (typeof page === 'number') page = this.$custom_page(page); + $page = $(page); + $page.find('a').addClass('page_link_' + this.random); + template += $page.html(); + if (config.numberStatus) { + var className = 'page_select_' + this.random, + number = _that.$get_page_number(); + template += ''; + eventList[className] = { eventType: 'change', type: 'page_select' }; + } + if (config.jump) { + var inputName = 'page_jump_input_' + this.random; + var btnName = 'page_jump_btn_' + this.random; + template += + '
                                  ' + + lan['public'].jump_to_page + + '
                                  '; + eventList[inputName] = { + eventType: 'keyup', + type: 'page_jump_input', + }; + eventList[btnName] = { + type: 'page_jump_btn', + }; + } + eventList['page_link_' + this.random] = { + type: 'cut_page_number', + }; + _that.config.page.total = + $page.length === 0 + ? 0 + : typeof page == 'number' + ? page + : parseInt( + $page + .find('.Pcount') + .html() + .match(/([0-9]*)/g)[1] + ); + } + + _that.$event_bind(eventList); + return '
                                  ' + template + '
                                  '; + }, + /** + * @description 渲染样式 + * @param {object|string} data 样式配置 + * @return {string} 样式 + */ + $reader_style: function (data) { + var style = ''; + if (typeof data === 'string') return data; + if (typeof data === 'undefined') return ''; + $.each(data, function (key, item) { + style += key + ':' + item + ';'; + }); + return style; + }, + /** + * @description 自定义分页 + * @param {} + */ + $custom_page: function (total) { + var html = '
                                  ', + config = this.config.page, + page = Math.ceil(total / config.number), + tmpPageIndex = 0; + if (config.page > 1 && page > 1) { + html += 'HomePrev'; + } + if (page <= 10) { + for (var i = 1; i <= page; i++) { + i == config.page ? (html += '' + i + '') : (html += '' + i + ''); + } + } else if (config.page < 10) { + for (var i = 1; i <= 10; i++) i == config.page ? (html += '' + i + '') : (html += '' + i + ''); + html += '...'; + } else if (page - config.page < 7) { + page - 7 > 1 && ((html += '1'), (html += '...')); + for (var i = page - 7; i <= page; i++) + i == config.page ? (html += '' + i + '') : (html += 1 == i ? '...' : '' + i + ''); + } else { + 0 == tmpPageIndex && (tmpPageIndex = config.page), + (tmpPageIndex <= config.page - 5 || tmpPageIndex >= config.page + 5) && (tmpPageIndex = config.page), + (html += '1'), + (html += '...'); + for (var i = tmpPageIndex - 3; i <= tmpPageIndex + 3; i++) + i == config.page ? (html += '' + i + '') : (html += '' + i + ''); + (html += '...'), (html += '' + page + ''); + } + return ( + page > 1 && config.page < page && (html += 'NextLast'), + (html += 'Total ' + total + '
                                  ') + ); + }, + + /** + * @deprecated 动态处理合并行内,css样式 + * @param {object} rows 当前行数据 + * @return {stinrg} className class类名 + * @return void + */ + $dynamic_merge_style: function (column, index) { + var str = ''; + $.each(column, function (key, item) { + switch (key) { + case 'align': + str += 'text-align:' + item + ';'; + break; + case 'width': + str += 'width:' + (typeof item == 'string' ? item : item + 'px') + ';'; + break; + case 'style': + str += item; + break; + case 'minWidth': + str += 'min-width:' + (typeof item == 'string' ? item : item + 'px') + ';'; + break; + case 'maxWidth': + str += 'max-width:' + (typeof item == 'string' ? item : item + 'px') + ';'; + break; + } + }); + return { + index: index, + css: str, + }; + }, + + /** + * @description 事件绑定 + * @param {array} eventList 事件列表 + * @return void + */ + $event_bind: function (eventList) { + var _that = this; + $.each(eventList, function (key, item) { + if (_that.event_list[key] && _that.event_list[key].eventType === item.eventType) return true; + _that.event_list[key] = item; + $(_that.config.el).on(item.eventType || 'click', '.' + key, function (ev) { + var index = $(this).parents('tr').index(), + data1 = $(this).data(), + arry = [], + column_data = _that.config.column[$(this).parents('td').index()]; + switch (item.type) { + case 'rows': + _that.event_rows_model = { + el: $(this), + model: column_data, + rows: _that.data[index], + index: index, + }; + arry = [_that.event_rows_model.rows, _that.event_rows_model.index, ev, key, _that]; + break; + case 'sort': + var model = _that.config.column[data1.index]; + if ($(this).hasClass('sort-active')) + $('.sort_' + _that.random + ' .sort-active').data({ + sort: 'desc', + }); + $('.sort_' + _that.random) + .removeClass('sort-active') + .find('.glyphicon') + .removeClass('glyphicon-triangle-top') + .addClass('glyphicon-triangle-bottom'); + $(this).addClass('sort-active'); + if (data1.sort == 'asc') { + $(this).data({ + sort: 'desc', + }); + $(this).find('.glyphicon').removeClass('glyphicon-triangle-top').addClass('glyphicon-triangle-bottom'); + } else { + $(this).data({ + sort: 'asc', + }); + $(this).find('.glyphicon').removeClass('glyphicon-triangle-bottom').addClass('glyphicon-triangle-top'); + } + _that.config.sort = _that.config.sortParam({ + name: model.fid, + sort: data1.sort, + }); + _that.$refresh_table_list(true); + break; + case 'checkbox': + var all = $(_that.config.el + ' [data-checkbox="all"]'), + checkbox_list = $(_that.config.el + ' tbody .checkbox_' + _that.random); + if (data1.checkbox == undefined) { + if (!$(this).hasClass('active')) { + $(this).addClass('active'); + _that.checkbox_list.push(index); + if (_that.data.length === _that.checkbox_list.length) { + all.addClass('active').removeClass('selected'); + } else if (_that.checkbox_list.length > 0) { + all.addClass('selected'); + } + } else { + $(this).removeClass('active'); + _that.checkbox_list.splice(_that.checkbox_list.indexOf(index), 1); + if (_that.checkbox_list.length > 0) { + all.addClass('selected').removeClass('active'); + } else { + all.removeClass('selected active'); + } + } + } else { + if (_that.checkbox_list.length === _that.data.length) { + _that.checkbox_list = []; + checkbox_list.removeClass('active selected').next().prop('checked', 'checked'); + all.removeClass('active'); + } else { + checkbox_list.each(function (index, item) { + if (!$(this).hasClass('active')) { + $(this).addClass('active').next().prop('checked', 'checked'); + _that.checkbox_list.push(index); + } + }); + all.removeClass('selected').addClass('active'); + } + } + _that.$set_batch_view(); + break; + case 'button': + arry.push(ev, _that); + break; + case 'search_focus': + var search_tips = $(_that.config.el + ' .bt_search_tips'); + if ($(_that.config.el + ' .bt_search_tips').length > 0) { + search_tips.remove(); + } + break; + case 'search_input': + if (ev.keyCode == 13) { + $(_that.config.el + ' .search_btn_' + _that.random).click(); + return false; + } + break; + case 'search_btn': + var _search = $(_that.config.el + ' .search_input'), + val = $(_that.config.el + ' .search_input').val(), + _filterBox = $('
                                  ').text(val), + _filterText = _filterBox.html(); + + val = _filterText; //过滤xss + val = val.replace(/(^\s*)|(\s*$)/g, ''); + _search.text(val); + _that.config.search.value = val; + if (_that.config.page) _that.config.page.page = 1; + _search.append('
                                  ' + val + '
                                  '); + _that.$refresh_table_list(true); + break; + case 'page_select': + var limit = parseInt($(this).val()); + _that.$set_page_number(limit); + _that.config.page.number = limit; + _that.config.page.page = 1; + _that.$refresh_table_list(true); + return false; + break; + case 'page_jump_input': + if (ev.keyCode === 13) { + $(_that.config.el + ' .page_jump_btn_' + _that.random).click(); + $(this).focus(); + } + return false; + break; + case 'page_jump_btn': + var jump_page = parseInt($(_that.config.el + ' .page_jump_input_' + _that.random).val()), + max_number = Math.ceil(_that.config.page.total / _that.config.page.number); + if (isNaN(jump_page)) jump_page = 1; + if (jump_page > max_number) jump_page = _that.config.page.page; + _that.config.page.page = jump_page; + _that.$refresh_table_list(true); + break; + case 'cut_page_number': + var page = + $(this).data('page') || + parseInt( + $(this) + .attr('href') + .match(/([0-9]*)$/)[0] + ); + _that.config.page.page = page; + _that.$refresh_table_list(true); + return false; + break; + case 'eye_open_password': + if ($(this).hasClass('glyphicon-eye-open')) { + $(this).addClass('glyphicon-eye-close').removeClass('glyphicon-eye-open'); + $(this).prev().text(_that.data[index][column_data.fid]); + } else { + $(this).addClass('glyphicon-eye-open').removeClass('glyphicon-eye-close'); + $(this).prev().html('**********'); + } + return false; + break; + case 'copy_password': + bt.pub.copy_pass(_that.data[index][column_data.fid]); + return false; + break; + } + if (item.event) item.event.apply(this, arry); + }); + }); + }, + + /** + * @description 样式绑定 + * @param {array} style_list 样式列表 + * @return void + */ + $style_bind: function (style_list, status) { + var str = '', + _that = this; + $.each(style_list, function (index, item) { + if (item.css != '') { + if (!item.className) { + str += + _that.config.el + + ' thead th:nth-child(' + + (item.index + 1) + + '),' + + _that.config.el + + ' tbody tr td:nth-child' + + (item.span ? ' span' : '') + + '(' + + (item.index + 1) + + '){' + + item.css + + '}'; + } else { + str += item.className + '{' + item.css + '}'; + } + } + }); + if ($('#bt_table_' + _that.random).length == 0) $(_that.config.el).append(''); + }, + + /** + * @deprecated 获取WIN高度或宽度 + * @return 返回当期的宽度和高度 + */ + $get_win_area: function () { + return [window.innerWidth, window.innerHeight]; + }, + + /** + * @description 获取分页条数 + * @return 返回分页条数 + */ + $get_page_number: function () { + var name = this.config.pageName; + if (name) { + return bt.get_cookie(name + '_page_number'); + } + }, + + /** + * @description 设置分页条数 + * @param {object} limit 分页条数 + * @return void + */ + $set_page_number: function (limit) { + var name = this.config.pageName; + if (name) bt.set_cookie(name + '_page_number', limit); + }, + /** + * @description 请求数据, + * @param {object} param 参数和请求路径 + * @return void + */ + $http: function (load, success) { + var page_number = this.$get_page_number(), + that = this, + param = {}, + config = this.config, + _page = config.page, + _search = config.search, + _sort = config.sort || {}; + if (_page) { + if (page_number && !_page.number) _page.number = page_number; + if (_page.defaultNumber) _page.number = _page.defaultNumber; + + if (_page.numberParam) param[_page.numberParam] = _page.number; + param[_page.pageParam] = _page.page; + } + + if (_search) param[_search.searchParam] = _search.value; + var params = $.extend(config.param, param, _sort); + if (this.config.beforeRequest) { + if (this.config.beforeRequest === 'model') { + config.param = (function () { + if (params.hasOwnProperty('data') && typeof params.data === 'string') { + var oldParams = JSON.parse(params['data']); + delete params['data']; + return { data: JSON.stringify($.extend(oldParams, params)) }; + } + return { data: JSON.stringify(params) }; + })(); + } else { + config.param = this.config.beforeRequest(params); + } + } else { + config.param = params; + } + + bt_tools.send( + { + url: config.url, + data: config.param, + }, + function (res) { + if (typeof config.dataFilter != 'undefined') { + var data = config.dataFilter(res, that); + if (typeof data.tootls != 'undefined') data.tootls = parseInt(data.tootls); + if (success) success(data); + } else { + if (void 0 === res.data) { + success && + success({ + data: res, + }); + } else + success && + success({ + data: res.data, + page: res.page, + }); + } + }, + { load: load ? 'Getting Data' : false, verify: typeof config.dataVerify === 'undefined' ? true : !!config.dataVerify } + ); + }, + }; + var example = new ReaderTable(config); + $(config.el).data('table', example); + return example; + }, + /** + * @description 验证表单 + * @param {object} el 节点 + * @param {object} config 验证配置 + * @param {function} callback 验证成功回调 + */ + verifyForm: function (el, config, callback) { + var verify = false, + formValue = this.getFormValue(el, []); + for (var i = 0; i < config.length; i++) { + var item = config[i]; + verify = item.validator.apply(this, [formValue[item.name], formValue]); + if (typeof verify === 'string') { + this.error(verify); + return false; + } + } + callback && callback(typeof verify !== 'string', formValue); + }, + /** + * @description 获取表单值 + * @param {String} el 表单元素 + * @param {Array} filter 过滤列表 + * @returns {Object} 表单值 + */ + getFormValue: function (el, filter) { + var form = $(el).serializeObject(); + filter = filter && []; + for (var key in form) { + if (filter.indexOf(key) > -1) delete form[key]; + } + return form; + }, + + /** + * @description 设置layer定位 + * @param {object} el 节点 + */ + setLayerArea: function (el) { + var $el = $(el), + width = $el.width(), + height = $el.height(), + winWidth = $(window).width(), + winHeight = $(window).height(); + $el.css({ left: (winWidth - width) / 2, top: (winHeight - height) / 2 }); + }, + + /** + * @description 渲染表单行内容 + * @param {object} config 配置参数 + */ + line: function (config) { + var $line = $( + '
                                  ' + + ((typeof config.must !== 'undefined' && config.must != '' ? '' + config.must + '' : '') + config.label || '') + + '
                                  ' + ); + var $form = this.renderLineForm(config); + $form.data({ line: $line }); + $line.find('.info-r').append($form); + return { + $line: $line, + $form: $form, + }; + }, + + /** + * @description 帮助提示 + * @param {object} config 配置参数 + */ + help: function (config) { + var $help = ''; + for (var i = 0; i < config.list.length; i++) { + var item = config.list[i]; + $help += '
                                • ' + item + '
                                • '; + } + return $('
                                    ' + $help + '
                                  '); + }, + + /** + * @description 渲染表单行内容 + * @param {object} config 配置参数 + * @returns {jQuery|HTMLElement|*} + */ + renderLineForm: function (config) { + config.type = config.type || 'text'; + var lineFilter = ['label', 'labelWidth', 'group', 'on', 'width', 'options', 'type']; // 排除渲染这些属性 + var $form = null; + var props = (function () { + var attrs = {}; + for (var key in config) { + if (lineFilter.indexOf(key) === -1) { + attrs[key] = config[key]; + } + } + return attrs; + })(); + var width = config.width ? 'style="width:' + config.width + '"' : ''; + switch (config.type) { + case 'textarea': + $form = ''; + break; + case 'select': + var options = config.options, + optionsHtml = ''; + for (var i = 0; i < options.length; i++) { + var item = options[i], + newItem = item; + if (typeof item === 'string') newItem = { label: item, value: item }; + optionsHtml += ''; + } + $form = ''; + break; + case 'text': + $form = ''; + break; + } + $form = $($form); + $form.width(config.width || '100%').attr(props); + if (!config.on) config.on = {}; + for (var onKey in config.on) { + (function (onKey) { + $form.on(onKey, function (ev) { + config.on[onKey].apply(this, [ev, $(this).val()]); + }); + })(onKey); + } + return $form; + }, + + /** + * @description 渲染表单行组 + * @param {object} el 配置参数 + * @param {object} config 配置参数 + * @param {object|undefined} formData 表单数据 + */ + fromGroup: function (el, config, formData) { + var $el = $(el), + lineList = {}; + for (var i = 0; i < config.length; i++) { + var item = config[i]; + if (item.type === 'tips') { + $el.append(this.help(item)); + } else { + var line = this.line(item); + if (typeof formData != 'undefined') line.$form.val(formData[item.name] || ''); + lineList[line.$form.attr('name')] = line; + $el.append(line.$line); + } + } + return lineList; + }, + + /** + * @description 渲染Form表单 + * @param {*} config + * @return 当前实例对象 + */ + form: function (config) { + var _that = this; + + function ReaderForm(config) { + this.config = config; + this.el = config.el; + this.submit = config.submit; + this.data = config.data || {}; + this.$load(); + } + + ReaderForm.prototype = { + element: null, + style_list: [], // 样式列表 + event_list: {}, // 事件列表,已绑定事件 + event_type: ['click', 'event', 'focus', 'keyup', 'blur', 'change', 'input'], + hide_list: [], + form_element: {}, + form_config: {}, + random: bt.get_random(5), + $load: function () { + var that = this; + if (this.el) { + $(this.el).html(this.$reader_content()); + this.$event_bind(); + } + }, + + /** + * @description 渲染Form内容 + * @param {Function} callback 回调函数 + */ + $reader_content: function (callback) { + var that = this, + html = '', + _content = ''; + $.each(that.config.form, function (index, item) { + if (item.separate) { + html += '
                                  ' + item.separate + '
                                  '; + } else { + html += that.$reader_content_row(index, item); + } + }); + that.element = $('' + html + ''); + _content = $('
                                  '); + _content.append(that.element); + if (callback) callback(); + return _content[0].outerHTML; + }, + + /** + * @description 渲染行内容 + * @param {object} data Form数据 + * @param {number} index 下标 + * @return {string} HTML结构 + */ + $reader_content_row: function (index, data) { + try { + var that = this, + help = data.help || false, + labelWidth = data.formLabelWidth || this.config.formLabelWidth; + if (data.display === false) return ''; + return ( + '
                                  ' + + (typeof data.label !== 'undefined' + ? '' + + (typeof data.must !== 'undefined' && data.must != '' ? '' + data.must + '' : '') + + data.label + + '' + : '') + + '
                                  ' + + that.$reader_form_element(data.group, index) + + (help ? '
                                  ' + help.list.join('
                                  ') + '
                                  ' : '') + + '
                                  ' + + '
                                  ' + ); + } catch (error) { + console.log(error); + } + }, + + /** + * @description 渲染form类型 + * @param {object} data 表单数据 + * @param {number} index 下标 + * @return {string} HTML结构 + */ + $reader_form_element: function (data, index) { + var that = this, + html = ''; + if (!Array.isArray(data)) data = [data]; + $.each(data, function (key, item) { + item.find_index = index; + html += that.$reader_form_find(item); + that.form_config[item.name] = item; + }); + return html; + }, + /** + * @descripttion 渲染单个表单元素 + * @param {Object} item 配置 + * @return: viod + */ + $reader_form_find: function (item) { + var that = this, + html = '', + style = that.$reader_style(item.style) + _that.$verify(item.width, 'width', 'style'), + attribute = that.$verify_group(item, ['name', 'placeholder', 'disabled', 'readonly', 'autofocus', 'autocomplete', 'min', 'max']), + event_group = that.$create_event_config(item), + eventName = '', + index = item.find_index; + if (item.display === false) return html; + html += item.label ? '' + item.label + '' : ''; + if (typeof item['name'] !== 'undefined') { + that.$check_event_bind(item.name, event_group); + } + html += '
                                  '; + var _value = typeof that.data[item.name] !== 'undefined' && that.data[item.name] != '' ? that.data[item.name] : item.value || ''; + switch (item.type) { + case 'text': // 文本选择 + case 'checkbox': // 复选框 + case 'password': // 密码 + case 'radio': // 单选框 + case 'number': // 数字 + var _event = 'event_' + item.name + '_' + that.random; + switch (item.type) { + case 'checkbox': // 复选框 + html += + ''; + if (!(typeof item.disabled != 'undefined' && item.disabled)) { + that.$check_event_bind(_event, { + input: { + type: 'checkbox', + config: item, + event: item.event, + }, + }); + } + break; + case 'radio': + $.each(item.list,function(keys,rItem){ + var radioRandom = _event+'_radio_'+keys + html+= '' + that.$check_event_bind(radioRandom, {'input': {type: 'radio', config: item, event: item.event}}) + }) + break; + default: + html += + ''; + break; + } + if (item.btn && !item.disabled) { + html += '' + item.btn.title + ''; + if (typeof item.btn.event !== 'undefined') { + that.$check_event_bind(item.name + '_btn', { + click: { + config: item, + event: item.btn.event, + }, + }); + } + } + if (item.icon) { + html += + ''; + if (typeof item.icon.event !== 'undefined') { + that.$check_event_bind(item.name + '_icon', { + click: { + type: 'select_path', + select: item.icon.select || '', + config: item, + children: '.' + item.name + '_icon', + event: item.icon.event, + callback: item.icon.callback, + }, + }); + } + } + break; + case 'textarea': + html += ''; + $.each(['blur', 'focus', 'input'], function (index, items) { + if (item.tips) { + var added = null, + event = {}; + switch (items) { + case 'blur': + added = function (ev, item, element) { + if ($(this).val() === '') $(this).next().show(); + layer.close(item.tips.loadT); + $(ev.target).data('layer', ''); + }; + break; + case 'focus': + added = function (ev, item) { + $(this).next().hide(); + item.tips.loadT = layer.tips(tips, $(this), { + tips: [1, '#20a53a'], + time: 0, + area: $(this).width(), + }); + }; + break; + } + } + that.event_list[item.name][items] + ? (that.event_list[item.name][items]['added'] = added) + : (that.event_list[item.name][items] = { + type: item.type, + cust: false, + event: item[items], + added: added, + }); + }); + if (item.tips) { + var tips = ''; + if (typeof item.tips.list === 'undefined') { + tips = item.tips.text; + } else { + tips = item.tips.list.join('
                                  '); + } + html += '
                                  ' + tips + '
                                  '; + that.$check_event_bind(item.name + '_tips', { + click: { + type: 'textarea_tips', + config: item, + }, + }); + } + break; + case 'select': + html += that.$reader_select(item, style, attribute, index); + that.$check_event_bind('custom_select', { + click: { + type: 'custom_select', + children: '.bt_select_value', + }, + }); + that.$check_event_bind('custom_select_item', { + click: { + type: 'custom_select_item', + children: 'li.item', + }, + }); + break; + case 'link': + eventName = 'event_link_' + that.random + '_' + item.name; + html += '' + item.title + ''; + that.$check_event_bind(eventName, { + click: { + type: 'link_event', + event: item.event, + }, + }); + break; + case 'button': + html += + // ''; + break; + case 'help': + var _html = ''; + $.each(item.list, function (index, items) { + _html += '
                                • ' + items + '
                                • '; + }); + html += '
                                    ' + _html + '
                                  '; + break; + case 'other': + html += item.boxcontent; + } + html += item.unit ? '' + item.unit + '' : ''; + html += '
                                  '; + return html; + }, + + /** + * @descripttion 检测检测名称 + * @param {string} eventName 配置 + * @param {object} config 事件配置 + */ + $check_event_bind: function (eventName, config) { + if (!this.event_list[eventName]) { + if (!this.event_list.hasOwnProperty(eventName)) { + this.event_list[eventName] = config; + } + } + }, + /** + * @description 创建事件配置 + * @param {object} item 行内配置 + * @return {object} 配置信息 + */ + $create_event_config: function (item) { + var config = {}; + if (typeof item['name'] === 'undefined') return {}; + $.each(this.event_type, function (key, items) { + if (item[items]) { + config[items === 'event' ? 'click' : items] = { + type: item.type, + event: item[items], + cust: ['select', 'checkbox', 'radio'].indexOf(item.type) > -1, + config: item, + }; + } + }); + return config; + }, + /** + * @description 渲染样式 + * @param {object|string} data 样式配置 + * @return {string} 样式 + */ + $reader_style: function (data) { + var style = ''; + if (typeof data === 'string') return data; + if (typeof data === 'undefined') return ''; + $.each(data, function (key, item) { + style += key + ':' + item + ';'; + }); + return style; + }, + /** + * @descripttion 局部刷新form表单元素 + * @param {String} name 需要刷新的元素 + * @param {String} name 元素新数据 + * @return: viod + */ + $local_refresh: function (name, config) { + var formFind = this.element.find('[data-name=' + name + ']'); + if (this.element.find('[data-name=' + name + ']').length === 0) formFind = this.element.find('[name=' + name + ']'); + formFind.parent().replaceWith(this.$reader_form_find(config)); + }, + /** + * @description 渲染下拉,内容方法 + */ + $reader_select: function (item, style, attribute, index) { + var that = this, + list = '', + option = '', + active = {}; + if (typeof item.list === 'function') { + var event = item.list; + event.call(this, this.config.form); + item.list = []; + } + if (!Array.isArray(item.list)) { + var config = item.list; + bt_tools.send( + { + url: config.url, + data: config.param || config.data || {}, + }, + function (res) { + if (res.status !== false) { + var list = item.list.dataFilter ? item.list.dataFilter(res, that) : res; + if (item.list.success) item.list.success(res, that, that.config.form[index], list); + item.list = list; + if (!item.list.length) { + item.disabled = true; + layer.msg(item.placeholder || '数据获取为空', { icon: 2 }); + } + that.$replace_render_content(index); + } else { + bt.msg(res); + } + } + ); + return false; + } + if (typeof that.data[item.name] === 'undefined') active = item.list[0]; + $.each(item.list, function (key, items) { + if (items.value === item.value || items.value === that.data[item.name]) { + active = items; + return false; + } + }); + $.each(item.list, function (key, items) { + list += '
                                • ' + items.title + '
                                • '; + option += ''; + }); + var title = !Array.isArray(item.list) ? 'Getting data...' : active ? active.title : item.placeholder; + return ( + '
                                  ' + + '' + + (title || item.placeholder) + + '' + + '
                                    ' + + (list || '') + + '
                                  ' + + '' + + (option || '') + + '' + + '
                                  ' + ); + }, + + /** + * @description 替换渲染内容 + */ + $replace_render_content: function (index) { + var that = this, + config = this.config.form[index], + html = that.$reader_content_row(index, config); + $('[data-form=' + that.random + ']') + .find('.line:eq(' + index + ')') + .replaceWith(html); + this.$event_bind(); + }, + + /** + * @description 重新渲染内容 + * @param {object} formConfig 配置 + */ + $again_render_form: function (formConfig) { + var formElement = $('[data-form=' + this.random + ']'), + that = this; + formConfig = formConfig || this.config.form; + formElement.empty(); + for (var i = 0; i < formConfig.length; i++) { + var config = formConfig[i]; + if (config.display === false) continue; + formElement.append(that.$reader_content_row(i, config)); + } + this.config.form = formConfig; + this.$event_bind(); + }, + + /** + * @description 事件绑定功能 + * @param {Object} eventList 事件列表 + * @param {Function} callback 回调函数 + * @return void + */ + $event_bind: function (eventList, callback) { + var that = this, + _event = {}; + that.element = $(typeof eventList === 'object' ? that.element : '[data-form=' + that.random + ']'); + _event = eventList; + if (typeof eventList === 'undefined') _event = that.event_list; + $.each(_event, function (key, item) { + if ($.isEmptyObject(item)) return true; + $.each(item, function (keys, items) { + if (!!item.type) return false; + if (!items.hasOwnProperty('bind')) { + items.bind = true; + } else { + return false; + } + var childNode = ''; + if (typeof items.cust === 'boolean') { + childNode = '[' + (items.cust ? 'data-' : '') + 'name=' + key + ']'; + } else { + childNode = '.' + key; + } + (function (items, key) { + if (items.onEvent === false) { + switch (items.type) { + case 'input_checked': + $(childNode).on(keys != 'event' ? keys : 'click', function (ev) { + items.event.apply(this, [ev, that]); + }); + break; + } + return true; + } else { + if (items.type === 'select') return true; + that.element.on(keys !== 'event' ? keys : 'click', items.children ? items.children : childNode, function (ev) { + var form = that.$get_form_element(true), + config = that.form_config[key]; + switch (items.type) { + case 'textarea_tips': + $(this).hide().prev().focus(); + break; + case 'custom_select': + if ($(this).parent().hasClass('bt-disabled')) return false; + var select_value = $(this).next(); + if (!select_value.hasClass('show')) { + // var $target = $(ev.currentTarget); + // var $layer = $target.parents('.layui-layer'); + // var layerTop = $layer.length > 0 ? parseFloat($layer.css('top')) : 0; + // var layerLeft = $layer.length > 0 ? parseFloat($layer.css('left')) : 0; + // var offset = $target[0].getBoundingClientRect(); + // var top = offset.top - layerTop + offset.height + 2; + // var left = offset.left - layerLeft; + // $('.bt_select_list').css({ + // position: 'fixed', + // width: offset.width + 'px', + // top: top + 'px', + // left: left + 'px', + // }); + $('.bt_select_list').removeClass('show'); + select_value.addClass('show'); + } else { + select_value.removeClass('show'); + } + $(document).click(function () { + that.element.find('.bt_select_list').removeClass('show'); + $(this).unbind('click'); + return false; + }); + return false; + break; + case 'custom_select_item': + config = that.form_config[$(this).parents('.bt_select_updown').attr('data-name')]; + var item_config = config.list[$(this).index()]; + if ($(this).hasClass('disabled')) { + $(this).parent().removeClass('show'); + if (item_config.tips) layer.msg(item_config.tips, { icon: 2 }); + return true; + } + if (!$(this).hasClass('active') && !$(this).hasClass('disabled')) { + var value = item_config.value.toString(); + $(this).parent().prev().find('.bt_select_content').text($(this).text()); + $(this).addClass('active').siblings().removeClass('active'); + $(this).parent().next().val(value); + $(this).parent().removeClass('show'); + } + that.data[config.name] = value; + if (items.event) items.event = null; + if (config.change) items.event = config.change; + break; + case 'select_path': + bt.select_path('event_' + $(this).prev().attr('name') + '_' + that.random, items.select || '', !items.callback || items.callback.bind(that)); + break; + case 'checkbox': + var checked = $(this).is(':checked'); + if (checked) { + $(this).prev().addClass('active'); + } else { + $(this).prev().removeClass('active'); + } + break; + } + if (items.event) items.event.apply(this, [that.$get_form_value(), form, that, config, ev]); // 事件 + if (items.added) items.added.apply(this, [ev, config, form]); + }); + } + })(items, key); + }); + }); + if (callback) callback(); + }, + + /** + * @description 获取表单数据 + * @return {object} 表单数据 + */ + $get_form_value: function () { + var form = {}; + this.element.find('input,textarea[disabled="disabled"]').each(function (index, item) { + var val = $(this).val(); + if ($(this).attr('type') === 'checkbox') { + val = $(this).prop('checked'); + } + form[$(this).attr('name')] = val; + }); + return $.extend({}, this.element.serializeObject(), form); + }, + + /** + * @description 设置指定数据 + * + */ + $set_find_value: function (name, value) { + var config = {}, + that = this; + typeof name != 'string' ? (config = name) : (config[name] = value); + $.each(config, function (key, item) { + that.form_element[key].val(item); + }); + }, + + /** + * @description 获取Form,jquery节点 + * @param {Boolean} afresh 是否强制刷新 + * @return {object} + */ + $get_form_element: function (afresh) { + var form = {}, + that = this; + if (afresh || $.isEmptyObject(that.form_element)) { + this.element.find(':input').each(function (index) { + form[$(this).attr('name')] = $(this); + }); + that.form_element = form; + return form; + } else { + return that.form_element; + } + }, + + /** + * @description 验证值整个列表是否存在,存在则转换成属性字符串格式 + */ + $verify_group: function (config, group) { + var that = this, + str = ''; + $.each(group, function (index, item) { + if (typeof config[item] === 'undefined') return true; + if (['disabled', 'readonly'].indexOf(item) > -1) { + str += ' ' + (config[item] ? item + '="' + item + '"' : ''); + } else { + str += ' ' + item + '="' + config[item] + '"'; + } + }); + return str; + }, + + /** + * @description 验证绑定事件 + * @param {String} value + */ + $verify_bind_event: function (eventName, row, group) { + var event_list = {}; + $.each(group, function (index, items) { + var event_fun = row[items]; + if (event_fun) { + if (typeof event_list[eventName] === 'object') { + if (!Array.isArray(event_list[eventName])) event_list[eventName] = [event_list[eventName]]; + event_list[eventName].push({ + event: event_fun, + eventType: items, + }); + } else { + event_list[eventName] = { + event: event_fun, + eventType: items, + }; + } + } + }); + return event_list; + }, + + /** + * @description 验证值是否存在 + * @param {String} value 内容/值 + * @param {String|Boolean} attr 属性 + * @param {String} type 属性 + */ + $verify: function (value, attr, type) { + if (!value) return ''; + if (type === true) return value ? ' ' + attr : ''; + if (type === 'style') return attr ? attr + ':' + value + ';' : value; + return attr ? ' ' + attr + '="' + value + '"' : ' ' + value; + }, + /** + * @description 验证form表单 + */ + $verify_form: function () { + var form_list = {}, + form = this.config.form, + form_value = this.$get_form_value(), + form_element = this.$get_form_element(true); + for (var key = 0; key < form.length; key++) { + var item = form[key]; + if (!Array.isArray(item.group)) item.group = [item.group]; + if (item.separate) continue; + for (var i = 0; i < item.group.length; i++) { + var items = item.group[i], + name = items.name; + if (items.type === 'help') continue; + if (typeof items.verify != 'undefined') { + var value = items.verify(form_value[name], form_element[name], items, true); + if (value === false && form_value[name] !== false) return false; + form_list[name] = value; + } else { + form_list[name] = typeof form_value[name] === 'undefined' && items.disabled ? $('[name="' + name + '"]').val() : form_value[name]; + } + } + } + return form_list; + }, + /** + * @description 提交内容,需要传入url + * @param {Object|Function} param 附加参数或回调函数 + * @param {Function} callback 回调 + */ + $submit: function (param, callback, tips) { + var form = this.$verify_form(); + if (typeof param === 'function') (tips = callback), (callback = param), (param = {}); + if (!form) return false; + form = $.extend(form, param); + if (typeof this.config.url == 'undefined') { + bt_tools.msg('Request Submission address cannot be empty!', false); + return false; + } + bt_tools.send( + { + url: this.config.url, + data: form, + }, + function (res) { + if (callback) { + callback(res, form); + } else { + bt_tools.msg(res); + } + }, + tips || 'Submit' + ); + }, + }; + return new ReaderForm(config); + }, + /** + * @description tab切换,支持三种模式 + * @param {object} config + * @return 当前实例对象 + */ + tab: function (config) { + var _that = this; + + function ReaderTab(config) { + this.config = config; + this.theme = this.config.theme || {}; + this.$load(); + } + + ReaderTab.prototype = { + type: 1, + theme_list: [ + { + content: 'tab-body', + nav: 'tab-nav', + body: 'tab-con', + active: 'on', + }, + { + content: 'bt-w-body', + nav: 'bt-w-menu', + body: 'bt-w-con', + }, + ], + random: bt.get_random(5), + $init: function () { + var that = this, + active = this.config.active, + config = that.config.list, + _theme = {}; + this.$event_bind(); + if (config[that.active].success) config[that.active].success(); + config[that.active]['init'] = true; + }, + $load: function () { + var that = this; + }, + $reader_content: function () { + var that = this, + _list = that.config.list, + _tab = '', + _tab_con = '', + _theme = that.theme, + config = that.config; + if (typeof that.active === 'undefined') that.active = 0; + if (!$.isEmptyObject(config.theme)) { + _theme = this.theme_list[that.active]; + $.each(config.theme, function (key, item) { + if (_theme[key]) _theme[key] += ' ' + item; + }); + that.theme = _theme; + } + if (config.type && $.isEmptyObject(config.theme)) this.theme = this.theme_list[that.active]; + $.each(_list, function (index, item) { + var active = that.active === index, + _active = _theme['active'] || 'active'; + _tab += '' + item.title + ''; + _tab_con += '
                                  ' + (active ? item.content : '') + '
                                  '; + }); + that.element = $( + '
                                  ' + + _tab + + '
                                  ' + + _tab_con + + '
                                  ' + ); + return that.element[0].outerHTML; + }, + /** + * @description 事件绑定 + * + */ + $event_bind: function () { + var that = this, + _theme = that.theme, + active = _theme['active'] || 'active'; + if (!that.el) that.element = $('#tab_' + that.random); + that.element.on('click', '.' + _theme['nav'].replace(/\s+/g, '.') + ' span', function () { + var index = $(this).index(), + config = that.config.list[index]; + $(this).addClass(active).siblings().removeClass(active); + $('#tab_' + that.random + ' .' + _theme['body'] + '>div:eq(' + index + ')') + .addClass(active) + .siblings() + .removeClass(active); + that.active = index; + if (!config.init) { + // console.log(_theme) + var contentItem = $('#tab_' + that.random + ' .' + _theme['body'] + '>div:eq(' + index + ')'); + contentItem.html(config.content); + if (config.success) config.success(contentItem); + config.init = true; + } + }); + }, + }; + return new ReaderTab(config); + }, + /** + * @description loading过渡 + * @param {*} title + * @param {*} is_icon + * @return void + */ + load: function (title) { + var random = bt.get_random(5), + layel = $( + '
                                  ' + + title + + ',please wait...
                                  ' + ), + mask = '', + loadT = ''; + $('body').append(layel); + var win = $(window), + msak = $('.layer-loading-mask'), + layel = $('#' + random); + layel.css({ top: (win.height() - 64) / 2, left: (win.width() - 320) / 2 }); + if (title === true) loadT = layer.load(); + return { + close: function () { + if (typeof loadT == 'number') { + layer.close(loadT); + } else { + $('body') + .find('#' + random + ',#layer-mask-' + random) + .remove(); + } + }, + }; + }, + /** + * @description 弹窗方法,有默认的参数和重构的参数 + * @param {object} config 和layer参数一致 + * @require 当前关闭弹窗方法 + */ + open: function (config) { + var _config = {}, + layerT = null, + form = null; + _config = $.extend({ type: 1, area: '640px', closeBtn: 2, btn: ['Yes', 'No'] }, config); + if (typeof _config.content == 'object') { + var param = _config.content; + form = bt_tools.form(param); + _config.success = function (layero, indexs) { + form.$event_bind(); + if (typeof config.success != 'undefined') config.success(layero, indexs); + }; + _config.yes = function (indexs, layero) { + var form_val = form.$verify_form(); + if (!form_val) return false; + if (typeof config.yes != 'undefined') { + var yes = config.yes.apply(form, [form_val, indexs, layero]); + if (!yes) return false; + } + }; + _config.content = form.$reader_content(); + } + layerT = layer.open(_config); + return { + close: function () { + layer.close(layerT); + }, + form: form, + }; + }, + /** + * @description 封装msg方法 + * @param {object|string} param1 配置参数,请求方法参数 + * @param {number} param2 图标ID + * @require 当前关闭弹窗方法 + */ + msg: function (param1, param2) { + var layerT = null, + msg = '', + config = {}; + if (typeof param1 === 'object') { + if (typeof param1.status === 'boolean') { + (msg = param1.msg), (config = { icon: param1.status ? 1 : 2 }); + if (!param1.status) config = $.extend(config, { time: !param2 ? 0 : 3000, closeBtn: 2, shade: 0.3 }); + } + } + if (typeof param1 === 'string') { + (msg = param1), + (config = { + icon: typeof param2 !== 'undefined' ? param2 : 1, + }); + } + layerT = layer.msg(msg, config); + return { + close: function () { + layer.close(layerT); + }, + }; + }, + /** + * @description 成功提示 + * @param {string} msg 信息 + */ + success: function (msg) { + this.msg({ msg: msg, status: true }); + }, + + /** + * @description 错误提示 + * @param {string} msg 信息 + */ + error: function (msg) { + this.msg({ msg: msg, status: false }); + }, + /** + * @description 请求封装 + * @param {string|object} conifg ajax配置参数/请求地址 + * @param {function|object} callback 回调函数/请求参数 + * @param {function} callback1 回调函数/可为空 + * @returns void 无 + */ + send: function (param1, param2, param3, param4, param5, param6) { + var params = {}, + success = null, + error = null, + config = [], + param_one = ''; + $.each(arguments, function (index, items) { + config.push([items, typeof items]); + }); + function diff_data(i) { + try { + success = config[i][1] == 'function' ? config[i][0] : null; + error = config[i + 1][1] == 'function' ? config[i + 1][0] : null; + } catch (error) {} + } + param_one = config[0]; + switch (param_one[1]) { + case 'string': + $.each(config, function (index, items) { + var value = items[0], + type = items[1]; + if (index > 1 && (type == 'boolean' || type == 'string' || type == 'object')) { + var arry = param_one[0].split('/'); + params['url'] = '/' + arry[0] + '?action=' + arry[1]; + if (type == 'object') { + params['load'] = value.load; + params['verify'] = value.verify; + if (value.plugin) params['url'] = '/plugin?action=a&name=' + arry[0] + '&s=' + arry[1]; + } else if (type == 'string') { + params['load'] = value; + } + return false; + } else { + params['url'] = param_one[0]; + } + }); + if (config[1][1] === 'object') { + params['data'] = config[1][0]; + diff_data(2); + } else { + diff_data(1); + } + break; + case 'object': + params['url'] = param_one[0].url; + params['data'] = param_one[0].data || {}; + $.each(config, function (index, items) { + var value = items[0], + type = items[1]; + if (index > 1 && (type == 'boolean' || type == 'string' || type == 'object')) { + switch (type) { + case 'object': + params['load'] = value.load; + params['verify'] = value.verify; + break; + case 'string': + params['load'] = value; + break; + } + return true; + } + }); + if (config[1][1] === 'object') { + params['data'] = config[1][0]; + diff_data(2); + } else { + diff_data(1); + } + break; + } + if (params.load) params.load = this.load(params.load); + $.ajax({ + type: params.type || 'POST', + url: params.url, + data: params.data || {}, + dataType: params.dataType || 'JSON', + complete: function (res) { + if (params.load) params.load.close(); + }, + success: function (res) { + if (typeof params.verify == 'boolean' && !params.verify) { + if (success) success(res); + return false; + } + if (typeof res === 'string') { + layer.msg(res, { + icon: 2, + time: 0, + closeBtn: 2, + }); + return false; + } + if (params.batch) { + if (success) success(res); + return false; + } + if (res.status === false && (res.hasOwnProperty('msg') || res.hasOwnProperty('error_msg'))) { + if (error) { + error(res); + } else { + bt_tools.msg({ status: res.status, msg: !res.hasOwnProperty('msg') ? res.error_msg : res.msg }); + } + return false; + } + + if (params.tips) { + bt_tools.msg(res); + } + if (success) success(res); + }, + }); + }, + + /** + * @description 命令行输入 + */ + command_line_output: function (config) { + var _that = this, + uuid = bt.get_random(15); + + /** + * @description 渲染 + * @param config + * @return {object} + * @constructor + */ + function ReaderCommand(config) { + var that = this; + for (var key in _that.commandConnectionPool) { + var item = _that.commandConnectionPool[key], + element = $(item.config.el); + if (config.shell === item.config.shell && element.length) { + item.el = element; + return item; + } + } + if (typeof config === 'undefined') config = {}; + this.config = $.extend({ route: '/sock_shell' }, config); + this.xterm_config = $.extend(this.xterm_config, this.config.xterm); + this.el = $(this.config.el); + this.open = config.open; + this.close = config.close; + this.message = config.message; + if (!this.config.hasOwnProperty('el')) { + _that.msg({ msg: '请输入选择器element,不可为空', status: false }); + return false; + } + if (!this.config.hasOwnProperty('shell')) { + _that.msg({ msg: '请输入命令,不可为空', status: false }); + return false; + } + if (this.config.hasOwnProperty('time')) { + setTimeout(function () { + that.close_connect(); + }, this.config.time); + } + this.init(); + } + + ReaderCommand.prototype = { + socket: null, //websocket 连接保持的对象 + socketToken: null, + timeout: 0, // 连接到期时间,为0代表永久有效 + monitor_interval: 2000, + element_detection: null, + uuid: uuid, + fragment: [], + error: 0, // 错误次数,用于监听元素内容是否还存在 + retry: 0, // 重试次数 + forceExit: false, // 强制断开连接 + /** + * @description 程序初始化 + */ + init: function () { + var oldUUID = bt.get_cookie('commandInputViewUUID'), + that = this; + if (!this.el[0]) { + if (this.error > 10) return false; + setTimeout(function () { + that.init(); + this.error++; + }, 2000); + return false; + } + this.error = 0; + if (this.el[0].localName !== 'pre') { + this.el.append('
                                  ');
                                  +					this.el = this.el.find('pre');
                                  +					this.config.el = this.config.el + ' pre';
                                  +				} else {
                                  +					this.el.addClass('command_output_pre');
                                  +				}
                                  +				if (Array.isArray(this.config.area)) {
                                  +					this.el.css({ width: this.config.area[0], height: this.config.area[1] });
                                  +				} else {
                                  +					this.el.css({ width: '100%', height: '100%' });
                                  +				}
                                  +				if (oldUUID && typeof _that.commandConnectionPool[oldUUID] != 'undefined') {
                                  +					_that.commandConnectionPool[oldUUID].close_connect();
                                  +					delete _that.commandConnectionPool[oldUUID];
                                  +				}
                                  +				bt.set_cookie('commandInputViewUUID', this.uuid);
                                  +				this.element_detection = setInterval(function () {
                                  +					if (!$(that.config.el).length) {
                                  +						clearInterval(that.element_detection);
                                  +						that.forceExit = true;
                                  +						that.close_connect();
                                  +					}
                                  +				}, 1 * 60 * 1000);
                                  +				this.set_full_screen();
                                  +				this.create_websocket_connect(this.config.route, this.config.shell);
                                  +				this.monitor_element();
                                  +			},
                                  +			/**
                                  +			 * @description 创建websocket连接
                                  +			 * @param {string} url websocket连接地址
                                  +			 * @param {string} shell 需要传递的命令
                                  +			 */
                                  +			create_websocket_connect: function (url, shell) {
                                  +				var that = this;
                                  +				this.socket = new WebSocket((location.protocol === 'http:' ? 'ws://' : 'wss://') + location.host + url);
                                  +				this.socket.addEventListener('open', function (ev) {
                                  +					if (!this.socketToken) {
                                  +						var _token = document.getElementById('request_token_head').getAttribute('token');
                                  +						this.socketToken = { 'x-http-token': _token };
                                  +					}
                                  +					this.send(JSON.stringify(this.socketToken));
                                  +					this.send(shell);
                                  +					if (that.open) that.open();
                                  +					that.retry = 0;
                                  +				});
                                  +				this.socket.addEventListener('close', function (ev) {
                                  +					if (!that.forceExit) {
                                  +						if (ev.code !== 1000 && that.retry <= 10) {
                                  +							that.socket = that.create_websocket_connect(that.config.route, that.config.shell);
                                  +							that.retry++;
                                  +						}
                                  +						if (that.close) that.close(ev);
                                  +					}
                                  +				});
                                  +				this.socket.addEventListener('message', function (ws_event) {
                                  +					var result = ws_event.data;
                                  +					if (!result) return;
                                  +					that.refresh_data(result);
                                  +					if (that.message) that.message(result);
                                  +				});
                                  +				return this.socket;
                                  +			},
                                  +
                                  +			/**
                                  +			 * @description 设置全屏视图
                                  +			 */
                                  +			set_full_screen: function () {
                                  +				// 1
                                  +			},
                                  +
                                  +			htmlEncodeByRegExp: function (str) {
                                  +				if (str.length == 0) return '';
                                  +				return str.replace(/&/g, '&').replace(//g, '>').replace(/ /g, ' ').replace(/\'/g, ''').replace(/\"/g, '"');
                                  +			},
                                  +
                                  +			/**
                                  +			 * @description 刷新Pre数据
                                  +			 * @param {object} data 需要插入的数据
                                  +			 */
                                  +			refresh_data: function (data) {
                                  +				var data = this.htmlEncodeByRegExp(data);
                                  +				this.fragment.push(data);
                                  +				if (this.fragment.length >= 300) {
                                  +					this.fragment.splice(0, 150);
                                  +					this.el.html(this.fragment.join(''));
                                  +				} else {
                                  +					this.el.append(data);
                                  +				}
                                  +				if (this.el.length > 0) {
                                  +					this.el.scrollTop(this.el[0].scrollHeight);
                                  +				}
                                  +			},
                                  +
                                  +			/**
                                  +			 * @description 监听元素状态,判断是否移除当前的ws连接
                                  +			 *
                                  +			 */
                                  +			monitor_element: function () {
                                  +				var that = this;
                                  +				this.monitor_interval = setInterval(function () {
                                  +					if (!that.el.length) {
                                  +						that.close_connect();
                                  +						clearInterval(that.monitor_interval);
                                  +					}
                                  +				}, that.config.monitorTime || 2000);
                                  +			},
                                  +
                                  +			/**
                                  +			 * @description 断开命令响应和websocket连接
                                  +			 */
                                  +			close_connect: function () {
                                  +				this.socket.send('');
                                  +				this.socket.close();
                                  +				delete _that.commandConnectionPool[this.uuid];
                                  +			},
                                  +		};
                                  +		this.commandConnectionPool[uuid] = new ReaderCommand(config);
                                  +		return this.commandConnectionPool[uuid];
                                  +	},
                                  +
                                  +	/**
                                  +	 * @description 清理验证提示样式
                                  +	 * @param {object} element  元素节点
                                  +	 */
                                  +	$clear_verify_tips: function (element) {
                                  +		element.removeClass('bt-border-error bt-border-sucess');
                                  +		layer.close('tips');
                                  +	},
                                  +
                                  +	/**
                                  +	 * @description 验证提示
                                  +	 * @param {object} element  元素节点
                                  +	 * @param {string} tips 警告提示
                                  +	 * @return void
                                  +	 */
                                  +	$verify_tips: function (element, tips, is_error) {
                                  +		if (typeof is_error === 'undefined') is_error = true;
                                  +		element.removeClass('bt-border-error bt-border-sucess').addClass(is_error ? 'bt-border-error' : 'bt-border-sucess');
                                  +		element.focus();
                                  +		layer.tips('' + tips + '', element, {
                                  +			tips: [1, is_error ? 'red' : '#20a53a'],
                                  +			time: 3000,
                                  +			area: element.width(),
                                  +		});
                                  +	},
                                  +	/**
                                  +	 * @description 验证值是否存在
                                  +	 * @param {String} value 内容/值
                                  +	 * @param {String|Boolean} attr 属性
                                  +	 * @param {String} type 属性
                                  +	 */
                                  +	$verify: function (value, attr, type) {
                                  +		if (!value) return '';
                                  +		if (type === true) return value ? ' ' + attr : '';
                                  +		if (type === 'style') return attr ? attr + ':' + value + ';' : value;
                                  +		return attr ? ' ' + attr + '="' + value + '"' : ' ' + value;
                                  +	},
                                  +
                                  +	/**
                                  +	 * @description 批量操作的结果
                                  +	 * @param {*} config
                                  +	 */
                                  +	$batch_success_table: function (config) {
                                  +		var _that = this,
                                  +			length = $(config.html).length;
                                  +		bt.open({
                                  +			type: 1,
                                  +			title: config.title,
                                  +			area: config.area || ['400px'],
                                  +			shadeClose: false,
                                  +			closeBtn: 2,
                                  +			content:
                                  +				config.content ||
                                  +				'
                                  ' + + config.title + + ' ' + + lan['public'].success + + '
                                  ' + + config.html + + '
                                  ' + + config.th + + '' + + lan['public'].result + + '
                                  ', + success: function () { + if (length > 4) _that.$fixed_table_thead('.fiexd_thead'); + }, + }); + }, + /** + * @description 固定表头 + * @param {string} el DOM选择器 + * @return void + */ + $fixed_table_thead: function (el) { + $(el).scroll(function () { + var scrollTop = this.scrollTop; + this.querySelector('thead').style.transform = 'translateY(' + scrollTop + 'px)'; + }); + }, + /** + * @description 插件视图设置 + * @param {object|string} layid dom元素或layer_id + * @param {object} config 插件宽度高度或其他配置 + */ + $piugin_view_set: function (layid, config) { + var element = $(typeof layid === 'string' ? '#layui-layer' + layid : layid).hide(), + win = $(window); + setTimeout(function () { + var width = config.width || element.width(), + height = config.height || element.height(); + element + .css( + $.extend(config, { + left: (win.width() - width) / 2, + top: (win.height() - height) / 2, + }) + ) + .addClass('custom_layer'); + }, 50); + setTimeout(function () { + element.show(); + }, 500); + }, +}; + +setTimeout(function () { + $.fn.serializeObject = function () { + var hasOwnProperty = Object.prototype.hasOwnProperty; + return this.serializeArray().reduce(function (data, pair) { + if (!hasOwnProperty.call(data, pair.name)) { + data[pair.name] = pair.value; + } + return data; + }, {}); + }; +}, 300); + +function arryCopy(arrys) { + var list = arrys.concat(), + arry = []; + for (var i = 0; i < list.length; i++) { + arry.push($.extend(true, {}, list[i])); + } + return arry; +} diff --git a/BTPanel/static/vite/oldjs/upload-drog.js b/BTPanel/static/vite/oldjs/upload-drog.js new file mode 100644 index 00000000..a436c53e --- /dev/null +++ b/BTPanel/static/vite/oldjs/upload-drog.js @@ -0,0 +1,958 @@ +'use strict'; + +/* + * @Description: 上传文件,三合一组件 + * @Version: 1.0 + * @Autor: chudong + * @Date: 2021-10-11 15:54:00 + * @LastEditors: chudong + * @LastEditTime: 2021-11-04 17:12:51 + */ +function UploadFile() { + this.uploadPath = ''; // 上传文件位置 + this.init = false; // 是否初始化 + this.compatible = true; // 是否兼容当前系统,默认全部兼容 + this.uploadElement = null; // 更新视图 + this.isUpload = true; // 是否可上传 + this.isGetFiles = false; // 是否可获取文件,如果当前拦截判断已经触发,则无法继续获取文件 + this.limit = { + size: 30 * 1024 * 1024 * 1024, + number: 1000, + }; + this.init_data(); // 初始化数据 + this.initialize_view(); // 初始化视图 + this.event_bind(); // 事件绑定 +} + +var uploadListhtml = + '
                                  • File nameFile sizeFile status
                                    '; + +// 是否显示内容 +function is_show(el, show) { + el.style.display = show ? 'block' : 'none'; +} + +UploadFile.prototype = { + /** + * @description 创建节点 + */ + createEl: function createEl(name) { + return document.createElement(name); + }, + + /** + * @description 获取节点 + */ + queryEl: function queryEl(el) { + return document.querySelector(el); + }, + + /** + * @description 获取所有节点 + */ + queryElAll: function queryElAll(el) { + return document.querySelectorAll(el); + }, + + /** + * @description 绑定事件 + */ + bind: function bind(el, type, fn) { + if (typeof el === 'string') el = this.queryElAll(el); + if (typeof el.length !== 'number') el = [el]; + if (typeof type === 'function') (fn = type), (type = 'click'); + for (var i = 0; i < el.length; i++) { + var item = el[i]; + (function (item) { + item.addEventListener( + type || 'click', + function (ev) { + var index = [].indexOf.call(el, item); + fn.call(item, ev, index); + }, + false + ); + })(item); + } + }, + + /** + * @description 初始化数据 + */ + init_data: function init_data() { + this.uploadStatus = 0; // 上传状态 0:等待上传 1:上传成功 2:上传中 + this.uploadLimitSize = 1024 * 1024 * 2; // 上传限制字节 + this.uploadList = []; // 上传列表 + this.isUpload = true; + this.uploadTime = { + endTime: 0, + startTime: 0, + }; + this.uploadInfo = { + // 提示信息,用于通知当前上传状态 + estimatedTime: 0, // 上传预计耗时,时间戳 + uploadedSize: 0, // 已上传文件大小 + speedInterval: null, // 平局速度定时器 + speedAverage: 0, //平均速度 + uploadTime: 0, // 上传总耗时 + fileSize: 0, // 文件上传字节 + startTime: 0, // 文件上传开始时间 + endTime: 0, // 文件上传结束时间 + }; + + this.fileList = []; // 文件列表 + this.fileTotalSize = 0; // 全部文件大小 + this.fileTotalNumber = 0; //全部文件数量 + + this.uploadInterval = null; + this.uploadCycleSize = []; // 上传周期的字段 + + this.speedLastTime = 0; + this.timerSpeed = 0; + + this.uploadError = 0; + }, + + /** + * @description 初始化上传路径 + */ + init_upload_path: function (path) { + this.uploadPath = path || bt.get_cookie('Path'); // 上传目录 + }, + + /** + * @description 视图初始化 + */ + initialize_view: function () { + var title = this.createEl('span'); + this.uploadElement = this.createEl('div'); + this.uploadElement.setAttribute( + 'style', + 'position:fixed;top:0;left:0;right:0;bottom:0; background:rgba(255,255,255,0.6);border:3px #ccc dashed;z-index:99999999;color:#999;font-size:40px;text-align:center;overflow:hidden;' + ); + this.uploadElement.id = 'uploadView'; + is_show(this.uploadElement, false); + title.setAttribute('style', 'position: fixed;top: 50%;left: 50%;margin-left: -200px;margin-top: -40px;z-index:99999998'); + title.innerText = 'Upload files to the current directory'; + this.uploadElement.appendChild(title); + document.querySelector('body').appendChild(this.uploadElement); + }, + + /** + * @description 绑定事件 + */ + event_bind: function event_bind() { + var _this = this; + + // 进入目标 + this.bind(document, 'dragenter', function (ev) { + console.log(ev); + _this.file_drag_hover(ev); + }); + + // 在放置目标上 + this.bind(this.uploadElement, 'dragover', function (ev) { + _this.file_drag_hover(ev); + }); + + // 离开放置目录 + this.bind(this.uploadElement, 'dragleave', function (ev) { + if (ev.path[0].id == 'uploadView') { + if (ev.screenX == 0 || ev.screenY == 0) { + ev.path[0].style.display = 'none'; + } + return false; + } + // _this.file_drag_hover(ev); + }); + + // 放置目标 + this.bind(this.uploadElement, 'drop', function (ev) { + _this.isGetFiles = true; + if (_this.uploadStatus === 2) { + layer.msg('Uploading files, please wait...', { + icon: 0, + }); + _this.file_drag_hover(ev); + return false; + } + var path = $('#fileInputPath').attr('data-path'); + $('.againUpload').click(); + _this.init_upload_path(path); + _this.upload_layer(); + _this.isUpload = true; + _this.file_select_handler(ev); + }); + }, + + /** + * @description 文件拖拽悬浮状态 + */ + file_drag_hover: function file_drag_hover(event) { + try { + if (event.dataTransfer.items[0].kind == 'string') return false; + } catch (error) {} + is_show(this.uploadElement, !(event.type === 'dragleave' || event.type === 'drop')); + event.preventDefault(); + event.stopPropagation(); + }, + + /** + * @description 上传弹窗 + */ + upload_layer: function (list) { + var _this2 = this; + + if (typeof list === 'undefined') list = []; + if (this.layer) return false; + var layerMax = null, + layerShade = null, + uploadPath = this.uploadPath || bt.get_cookie('Path'); + this.layer = layer.open({ + type: 1, + closeBtn: 1, + maxmin: true, + area: ['650px', '605px'], + title: 'Upload files to [' + uploadPath + '] --- Support breakpoint renewal', + skin: 'file_dir_uploads', + content: + '
                                    \n
                                    \n
                                    \n \n \n \n
                                    \n
                                    \n
                                    \n Total process , uploading ,\n Upload fail \n Speed Getting\n Expect time Getting\n \n
                                    \n
                                    \n
                                    ' + + (list.length > 0 ? uploadListhtml : 'Please drag the file here') + + '
                                    \n
                                    \n
                                    \n \n \n
                                    ', + success: function success(layers, indexs) { + layerMax = _this2.queryEl('.file_dir_uploads').querySelector('.layui-layer-max'); + layerShade = document.querySelector('.layui-layer-shade'); + layerMax.style.display = 'none'; + var cancelUpload = _this2.queryEl('.cancelUpload'), + startUpload = _this2.queryEl('.startUpload'), + uploadFileBtn = _this2.queryEl('.upload_file_btn'), + dropdownItem = _this2.queryElAll('.dropdown-menu li'); + // 下拉选项 + _this2.bind(dropdownItem, function (ev) { + var type = ev.target.dataset.type; + if (type === 'file') { + _this2.queryEl('.upload_file_input').removeAttribute('webkitdirectory'); + } else if (type === 'dir') { + _this2.queryEl('.upload_file_input').setAttribute('webkitdirectory', ''); + } + _this2.queryEl('.upload_file_input').click(); + }); + + _this2.bind('.empty-record', function (ev) { + var $li = _this2.queryElAll('.dropUpLoadFile li'); + if ($li.length <= 0) { + layer.msg('Please select file!', { icon: 0 }); + } else { + layer.confirm( + 'Do you want to clear the upload list?', + { + btn: ['Confirm', 'Cancel'], + title: 'clear the upload list', + icon: 0, + }, + function (indexs) { + _this2.empty_upload_list(); + layer.close(indexs); + } + ); + } + }); + + function create_upload_input() { + // 选择文件或文件夹 + var uploadFileInput = _this2.createEl('input'), + uploadBtnGroud = _this2.queryEl('.upload_btn_groud'); + uploadFileInput.setAttribute('type', 'file'); + uploadFileInput.setAttribute('multiple', 'multiple'); + uploadFileInput.style.display = 'none'; + uploadFileInput.classList.add('upload_file_input'); + uploadBtnGroud.appendChild(uploadFileInput); + _this2.bind(uploadFileInput, 'change', function (ev) { + _this2.isUpload = true; + _this2.isGetFiles = true; + var files = ev.target.files; + // console.log(files) + for (var i = 0; i < files.length; i++) { + if (!_this2.file_upload_limit(files[i])) return false; + } + if (!_this2.isGetFiles) return false; + _this2.render_file_list(_this2.fileList); + uploadBtnGroud.removeChild(uploadFileInput); + create_upload_input(); + }); + } + + create_upload_input(); + + // 点击上传文件按钮 + _this2.bind(uploadFileBtn, function (ev) { + _this2.queryEl('.upload_file_input').click(); + }); + + // 关闭文件视图 + _this2.bind(cancelUpload, function (ev) { + _this2.cancel_upload(ev); + }); + // 开始上传 + _this2.bind(startUpload, function (e) { + var btn = e.target; + if (btn.classList.contains('againUpload')) { + $('.ico-tips-close').click(); + btn.innerText = 'Upload'; + btn.classList.remove('againUpload'); + btn.classList.add('startUpload'); + } else { + if (_this2.fileList.length === 0) { + layer.msg('Please select file!', { icon: 0 }); + return false; + } + _this2.upload_file(); + } + }); + }, + min: function () { + layerMax.style.display = ''; + layerShade.style.display = 'none'; + }, + restore: function () { + layerMax.style.display = 'none'; + layerShade.style.display = ''; + }, + cancel: function () { + _this2.cancel_upload(); + return false; + }, + }); + }, + + // 清空上传列表 + empty_upload_list: function () { + var dropUpLoadFile = this.queryEl('.dropUpLoadFile'); + var $li = this.queryElAll('.dropUpLoadFile li'); + for (var i = 0; i < $li.length; i++) { + dropUpLoadFile.removeChild($li[i]); + } + this.init_data(); + var $head = this.queryEl('.dropUpLoadFileHead'); + if ($head) $head.removeAttribute('style'); + }, + + /** + * @description 获取实时时间 + */ + get_time_real: function get_time_real() { + return new Date().getTime(); + }, + + /** + * @description 上传前检查文件是否存在 + */ + upload_file_exists: function (config) { + var _this = this; + var filename = config.path + config.name; + var files = this.uploaded_files; + if (!files) { + this.uploaded_files = []; + files = []; + } + var data = { + filename: filename, + }; + // 判断是否上传过该文件名 + var isUploaded = false; + for (var i = 0; i < files.length; i++) { + if (files[i] === config.name) { + isUploaded = true; + break; + } + } + // 上传过就直接跳过 + if (isUploaded) { + config.success(); + return; + } + this.uploaded_files.push(config.name); + bt.send('upload_file_exists', 'files/upload_file_exists', data, function (res) { + if (res.status) { + var is_open = _this.is_open_cover_layer; + if (is_open === 0) { + bt.open({ + type: 1, + area: '400px', + title: 'Upload file to [' + config.path.substr(0, config.path.length - 1) + ']', + btn: ['Confirm', 'Skip'], + content: + '\ +
                                    \ +
                                    \ + \ +
                                    A file with the same name [' + + config.name + + '] is detected, do you want to overwrite it?
                                    \ +
                                    \ +
                                    \ +
                                    \ + \ + \ +
                                    \ +
                                    \ +
                                    \ + ', + success: function (layero) { + var fileList = _this.fileList; + if (fileList.length <= 1) { + layero.find('.details').remove(); + } + }, + cancel: function () { + config.error(); + }, + yes: function (index) { + var checked = $('#all_operation').is(':checked'); + if (checked) { + _this.is_open_cover_layer = 1; + } + config.success(); + layer.close(index); + }, + btn2: function (index) { + var checked = $('#all_operation').is(':checked'); + if (checked) { + _this.is_open_cover_layer = 2; + } + config.error(); + layer.close(index); + }, + }); + } else if (is_open === 1) { + config.success(); + } else { + config.error(); + } + } else { + config.success(); + } + }); + }, + + /** + * @description 上传文件 + */ + upload_file: function (fileStart, index) { + var _this3 = this; + this.uploadPath = this.uploadPath || bt.get_cookie('Path'); + if (this.fileList.length === 0) return false; + // 开始上传 + if (fileStart == undefined && this.uploadList.length == 0) { + (fileStart = 0), (index = 0); + this.is_open_cover_layer = 0; + this.uploadStatus = 2; + this.uploaded_files = []; // 上传过的文件 + this.uploadCycleSize = []; // 上传速度匹配 + this.uploadTime.startTime = this.get_time_real(); // 设置上传开始时间 + var startUpload = this.queryEl('.startUpload'); + startUpload.setAttribute('disabled', 'disabled'); + startUpload.innerText = 'Uploading'; + } + // 结束上传 + if (this.fileList.length === index) { + clearTimeout(this.uploadInterval); + this.uploadStatus = 1; + this.uploadTime.endTime = this.get_time_real(); // 设置上传开始时间 + this.set_upload_view(); + this.init_data(); + bt_file.reader_file_list({ path: this.uploadPath }); + return false; + } + + // 创建文件对象和切割文件 + var item = this.fileList[index], + fileEnd = ''; + if (item == undefined) return false; + + // 检测文件是否存在 + var f_path = this.uploadPath + item.path + '/'; + f_path = f_path.replace(/\/\//g, '/'); + this.upload_file_exists({ + name: item.name, + path: f_path, + error: function () { + $('.dropUpLoadFile li').eq(index).find('.fileStatus .upload_info').html('File already exists'); + _this3.upload_file(0, ++index); + }, + success: function () { + // 设置切割文件时间段 + var uploadedSize = _this3.uploadInfo.uploadedSize; + _this3.uploadInterval = setInterval(function () { + if (_this3.uploadCycleSize.length === 4) _this3.uploadCycleSize.splice(0, 1); + _this3.uploadCycleSize.push(Math.abs(_this3.uploadInfo.uploadedSize - uploadedSize)); + }, 1000); + + // 渲染上传时间和上传进度 + _this3.uploadInfo.endTime = _this3.get_time_real(); + _this3.reander_timer_speed(); + _this3.uploadInfo.startTime = _this3.get_time_real(); + + // 获取上传速度 + var speed = _this3.get_update_speed(), + // 获取上传速度 + limitSize = _this3.uploadLimitSize; + // 判断速度是否操作阈值,超过阀值后,采用倍速的方案,最大只能支持4,8MB + var maxDouble = Math.floor(speed / _this3.uploadLimitSize); + if (maxDouble && index > 1) limitSize = (maxDouble > 4 ? 4 : maxDouble) * limitSize; + + // 实时反馈 + if (fileStart == 0) { + _this3.uploadInfo.startTime = _this3.get_time_real(); + item = $.extend(item, { + percent: '0%', + upload: 2, + upload_size: '0B', + }); + } + _this3.set_upload_view(index, item); + + fileEnd = Math.min(item.file.size, fileStart + limitSize); + _this3.uploadInfo.fileSize = fileEnd - fileStart; + + var form = new FormData(); + form.append('f_path', f_path); + form.append('f_name', item.name); + form.append('f_size', item.file.size); + form.append('f_start', fileStart); + form.append('blob', item.file.slice(fileStart, fileEnd)); + + // 发送请求 + $.ajax({ + url: '/files?action=upload', + type: 'POST', + data: form, + async: true, + processData: false, + contentType: false, + success: function success(rdata) { + // 判断是否为数字 + if (typeof rdata === 'number') { + _this3.set_upload_view( + index, + $.extend(item, { + percent: ((rdata / item.file.size) * 100).toFixed(2) + '%', + upload: 2, + upload_size: bt.format_size(rdata), + }) + ); + + // 判断是否为文件结束,已上传文件大小 + if (fileEnd != rdata) { + _this3.uploadInfo.uploadedSize += rdata; + } else { + _this3.uploadInfo.uploadedSize += parseInt(fileEnd - fileStart); + } + + // console.log(rdata, index); + + // 继续上传文件 + _this3.upload_file(rdata, index); + } else { + // 请求状态,判断文件是否上传成功 + if (rdata.status) { + _this3.uploadInfo.endTime = _this3.get_time_real(); + _this3.uploadInfo.uploadedSize += parseInt(fileEnd - fileStart); + _this3.set_upload_view( + index, + $.extend(item, { + upload: 1, + upload_size: item.size, + }) + ); + } else { + _this3.set_upload_view( + index, + $.extend(item, { + upload: -1, + errorMsg: rdata.msg, + }) + ); + _this3.uploadError++; + } + _this3.upload_file(0, ++index); + } + // 实时更新文件上传状态 + }, + error: function (e) { + if (_this3.fileList[index].req_error === undefined) _this3.fileList[index].req_error = 1; + if (_this3.fileList[index].req_error > 2) { + _this3.set_upload_view( + index, + $.extend(_this3.fileList[index], { + upload: -1, + errorMsg: e.statusText == 'error' ? lan.public.network_err : e.statusText, + }) + ); + _this3.uploadError++; + _this3.upload_file(fileStart, (index += 1)); + return false; + } + _this3.fileList[index].req_error += 1; + _this3.upload_file(fileStart, index); + }, + }); + }, + }); + }, + + /** + * @description 设置上传视图 + */ + set_upload_view: function set_upload_view(index, config) { + var _this4 = this; + if (typeof index === 'undefined') { + var file_upload_info = this.queryEl('.file_upload_info'), + time = this.get_time_real(), + s_peed = this.to_size(this.uploadInfo.uploadedSize / ((time - this.uploadTime.startTime) / 1000)); + file_upload_info.innerHTML = + 'Uploading ' + + this.uploadList.length + + ' file(s), ' + + (this.uploadError ? 'Fail ' + this.uploadError + ' file(s), ' : '') + + 'time: ' + + this.diff_time(this.uploadTime.startTime, time) + + ', speed: ' + + s_peed + + '/s'; + this.bind(file_upload_info.querySelector('.ico-tips-close'), function (ev) { + var parent = this.parentNode.parentNode; + parent.querySelector('.btn-group').classList.remove('hide'); + parent.querySelector('.file_upload_info').classList.add('hide'); + _this4.empty_upload_list(); + }); + var startUpload = this.queryEl('.startUpload'); + startUpload.removeAttribute('disabled'); + startUpload.innerText = 'Upload again'; + startUpload.classList.remove('startUpload'); + startUpload.classList.add('againUpload'); + _this4.init_data(); + return false; + } + try { + var item = document.querySelectorAll('.dropUpLoadFile li')[index]; + var file_info = this.queryEl('.file_upload_info'); + + if (file_info.querySelectorAll('.uploadProgress').length === 0) { + file_info.innerHTML = + '\n Total process ,\n uploading , \n Upload fail \n Speed Getting\n Expect time Getting'; + } + + var file_info_parent = file_info.parentElement; + file_info_parent.querySelector('.btn-group').classList.add('hide'); + file_info_parent.querySelector('.file_upload_info').classList.remove('hide'); + + var file_info_error = file_info.querySelector('.uploadError'); + file_info_error.innerText = '(' + this.uploadError + '份)'; + if (this.uploadError > 0) file_info_error.parentElement.style.display = 'block'; + + if (config.upload === 1 || config.upload === -1) { + this.fileList[index].is_upload = true; + this.uploadList.push(this.fileList[index]); + item.querySelector('.fileLoading').setAttribute('style', 'width:100%;opacity:.5;background:' + (config.upload == -1 ? '#ffadad' : '#20a53a21')); + item.querySelector('.filesize').innerText = config.size; + item.querySelector('.fileStatus').innerHTML = this.is_upload_status( + config.upload, + config.upload === 1 ? '(time:' + this.diff_time(this.uploadTime.startTime, this.uploadTime.endTime) + ')' : config.errorMsg + ); + var dropUpLoadFile = this.queryEl('.dropUpLoadFile'); + if (this.uploadList.length === 1) dropUpLoadFile.scrollTop = 0; + if (this.uploadList.length > 1) dropUpLoadFile.scrollTop += 45.5; + } else { + item.querySelector('.fileLoading').setAttribute('style', 'width:' + config.percent); + item.querySelector('.filesize').innerText = config.upload_size + '/' + config.size; + item.querySelector('.fileStatus').innerHTML = this.is_upload_status(config.upload, '(' + config.percent + ')'); + } + + file_info.querySelector('.uploadNumber').innerText = '(' + this.uploadList.length + '/' + this.fileList.length + ')'; + file_info.querySelector('.uploadProgress').innerText = ((this.uploadInfo.uploadedSize / this.fileTotalSize) * 100).toFixed(2) + '%'; + } catch (e) { + console.log(e); + } + }, + + /** + * @description 上传状态 + * @param {*} status + * @param {*} val + * @returns + */ + is_upload_status: function is_upload_status(status, val) { + if (val === undefined) val = ''; + switch (status) { + case -1: + return 'Fail' + (val != '' ? ', ' + val : '') + ''; + case 0: + return 'Waiting to upload'; + case 1: + return 'Uploaded'; + case 2: + return 'Uploading ' + val + ''; + case 3: + return 'Stoped'; + } + }, + + /** + * @description 取10秒内上传平均值 + */ + get_update_speed: function get_update_speed() { + var sum = 0; + for (var i = 0; i < this.uploadCycleSize.length; i++) { + sum += this.uploadCycleSize[i]; + } + var content = sum / this.uploadCycleSize.length; + return isNaN(content) ? 0 : content; + }, + + // 渲染上传速度 + reander_timer_speed: function reander_timer_speed() { + var done_time = new Date().getTime(); + if (done_time - this.speedLastTime > 1000) { + var s_time = (this.uploadInfo.endTime - this.uploadInfo.startTime) / 1000; + this.timerSpeed = (this.uploadInfo.fileSize / s_time).toFixed(2); + if (this.timerSpeed < 2) return; + this.queryEl('.file_upload_info').querySelector('.uploadSpeed').innerText = bt.format_size(isNaN(this.timerSpeed) ? 0 : this.timerSpeed) + '/s'; + var estimateTime = this.time(parseInt(((this.fileTotalSize - this.uploadInfo.uploadedSize) / this.timerSpeed) * 1000)); + if (!isNaN(this.timerSpeed)) this.queryEl('.file_upload_info').querySelector('.uploadEstimate').innerText = estimateTime.indexOf('NaN') == -1 ? estimateTime : '0 sec'; + this.speedLastTime = done_time; + } + }, + to_size: function to_size(a) { + var d = [' B', ' KB', ' MB', ' GB', ' TB', ' PB']; + var e = 1024; + for (var b = 0; b < d.length; b += 1) { + if (a < e) { + var num = a.toFixed(2) + d[b]; + return !isNaN(a.toFixed(2)) && typeof num != 'undefined' ? num : '0B'; + } + a /= e; + } + }, + time: function time(date) { + var hours = Math.floor(date / (60 * 60 * 1000)); + var minutes = Math.floor(date / (60 * 1000)); + var seconds = parseInt((date % (60 * 1000)) / 1000); + var result = seconds + 'sec'; + if (minutes > 0) { + result = minutes + 'min' + seconds + 'sec'; + } + if (hours > 0) { + result = hours + 'hour' + Math.floor((date - hours * (60 * 60 * 1000)) / (60 * 1000)) + 'min'; + } + return result; + }, + diff_time: function diff_time(start_date, end_date) { + if (typeof start_date !== 'number') start_date = start_date.getTime(); + if (typeof end_date !== 'number') end_date = end_date.getTime(); + var diff = end_date - start_date, + minutes = Math.floor(diff / (60 * 1000)), + leave3 = diff % (60 * 1000), + seconds = leave3 / 1000, + result = seconds.toFixed(minutes > 0 ? 0 : 2) + 'sec'; + if (minutes > 0) { + result = minutes + 'min' + seconds.toFixed(0) + 'sec'; + } + return result; + }, + + /** + * @description 取消上传 + */ + cancel_upload: function cancel_upload(index) { + var _this4 = this; + if (this.uploadStatus === 2) { + layer.confirm( + 'Do you want to cancel the upload of files? It need to delete the uploaded files manually. Continue?', + { + title: 'Cancel file upload', + icon: 0, + }, + function (indexs) { + _this4.init_data(); + _this4.cancel_upload_layer(); + layer.close(indexs); + } + ); + } else { + _this4.init_data(); + _this4.cancel_upload_layer(); + } + }, + + /** + * @description 关闭上传弹窗 + */ + cancel_upload_layer: function cancel_upload_layer() { + layer.close(this.layer); + this.layer = false; + this.init_data(); + }, + + /** + * @description 渲染列表 + * + */ + render_file_list: function (list) { + var _this5 = this; + var html = ''; + for (var i = 0; i < list.length; i++) { + var item = list[i], + name = ((item.path || '/') + '/' + item.name).replace(/\/\//g, '/'); + html += + '
                                  • ' + + name + + '\n ' + + item.size + + '\n Waiting to upload\n
                                    \n
                                  • '; + this.uploadInfo.fileSize += item.file.size; + } + var upload_file_body = this.queryEl('.upload_file_body'); + upload_file_body.className = 'upload_file_body'; + upload_file_body.innerHTML = uploadListhtml; + this.queryEl('.upload_file_gourp').className = 'upload_file_gourp'; + this.queryEl('.dropUpLoadFile').innerHTML = html; + var dropUpLoadFileHead = this.queryEl('.dropUpLoadFileHead'); + if (list.length <= 10) { + dropUpLoadFileHead.removeAttribute('style'); + } else { + dropUpLoadFileHead.style.paddingRight = bt_file.upload_file_body_width + 'px'; + dropUpLoadFileHead.style.boxShadow = '0 5px 2px -3px #ececec'; + } + var cancelBtn = this.queryElAll('.cancel-btn'); + this.bind(cancelBtn, function (ev, index) { + _this5.fileTotalSize = _this5.fileTotalSize - _this5.fileList[index].file.size; + _this5.fileTotalNumber = _this5.fileTotalNumber - 1; + _this5.fileList.splice(index, 1); + _this5.queryElAll('.dropUpLoadFile li')[index].remove(); + }); + }, + + /** + * @description 文件上传限制 + * @param {Object} e 文件对象 + */ + file_upload_limit: function (e, path) { + if (!this.isGetFiles) return false; + var extName = e.name.split('.'); + path = path || e.webkitRelativePath; + var paths = path.split('/'); + path = ('/' + paths.slice(0, paths.length - 1).join('/')).replace('//', '/'); + this.fileList.push({ + file: e, + path: path, + name: e.name, + size: bt.format_size(e.size), + type: extName.length > 1 ? extName[extName.length - 1] : 'txt', + status: 0, + }); + this.fileTotalSize += e.size; + this.fileTotalNumber++; + if (this.fileTotalNumber >= this.limit.number) { + layer.msg('The number of files has exceeded the file upload limit ' + this.limit.number + ', please zip the folder and try again!'); + this.init_data(); + this.isGetFiles = false; // 停止文件内容获取 + this.empty_upload_list(); + return false; + } + if (this.fileTotalSize >= this.limit.size) { + layer.msg('The file size has exceeded the file upload' + bt.format_size(e.size) + ' limit, please use tools such as SFTP/FTP to upload the file!'); + this.init_data(); + this.isGetFiles = false; // 停止文件内容获取 + this.empty_upload_list(); + return false; + } + return true; + }, + + /** + * @description 文件夹文件内容递归 + * @param {object} item 文件对象 + */ + traverse_file_tree: function (item) { + var _this6 = this; + + var path = item.fullPath || ''; + if (item.isFile) { + item.file(function (e) { + _this6.file_upload_limit(e, path); + }); + clearTimeout(this.timeNumber); + this.timeNumber = setTimeout(function () { + _this6.load.close(); + if (_this6.isUpload && _this6.isGetFiles) { + _this6.render_file_list(_this6.fileList); + } else { + var layers = _this6.layer; + _this6.init_data(); + _this6.layers = layers; + } + }, 10); + } else if (item.isDirectory) { + var dirReader = item.createReader(); + var fnReadEntries = function (entries) { + [].forEach.call(entries, function (e) { + if (!_this6.isUpload) return false; + _this6.traverse_file_tree(e); + }); + if (entries.length > 0) { + dirReader.readEntries(fnReadEntries); + } + }; + dirReader.readEntries(fnReadEntries); + setTimeout(function () { + if (_this6.fileList.length === 0 && _this6.isGetFiles) { + layer.msg('Drag and drop upload folder content is empty'); + } + }, 500); + } + }, + + /** + * @description 文件选择处理程序 + * @param {object} ev 事件 + */ + file_select_handler: function (ev) { + var _this7 = this; + this.file_drag_hover(ev); + this.load = bt.load('Getting file information, please wait...'); + this.timeNumber = 0; + if (ev.target.files) { + var items = ev.target.files; + [].forEach.call(items, function (item) { + _this7.traverse_file_tree(item); + }); + } else if (ev.dataTransfer.items) { + var items = ev.dataTransfer.items; + [].forEach.call(items, function (ev) { + var getAsEntry = ev.webkitGetAsEntry || ev.getAsEntry; + var item = getAsEntry.call(ev); + if (item) { + if (!_this7.isUpload) return false; + _this7.traverse_file_tree(item); + } + }); + } + }, +}; + +var uploadFiles = new UploadFile(); diff --git a/BTPanel/static/vite/oldjs/xterm.js b/BTPanel/static/vite/oldjs/xterm.js new file mode 100644 index 00000000..e7251c08 --- /dev/null +++ b/BTPanel/static/vite/oldjs/xterm.js @@ -0,0 +1,7 @@ +!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var r=t();for(var i in r)("object"==typeof exports?exports:e)[i]=r[i]}}(window,(function(){return function(e){var t={};function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(i,n,function(t){return e[t]}.bind(null,n));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=34)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.forwardEvent=t.EventEmitter=void 0;var i=function(){function e(){this._listeners=[],this._disposed=!1}return Object.defineProperty(e.prototype,"event",{get:function(){var e=this;return this._event||(this._event=function(t){return e._listeners.push(t),{dispose:function(){if(!e._disposed)for(var r=0;r>22},t.prototype.getChars=function(){return 2097152&this.content?this.combinedData:2097151&this.content?o.stringFromCodePoint(2097151&this.content):""},t.prototype.getCode=function(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content},t.prototype.setFromCharData=function(e){this.fg=e[s.CHAR_DATA_ATTR_INDEX],this.bg=0;var t=!1;if(e[s.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[s.CHAR_DATA_CHAR_INDEX].length){var r=e[s.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=r&&r<=56319){var i=e[s.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=i&&i<=57343?this.content=1024*(r-55296)+i-56320+65536|e[s.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[s.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[s.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[s.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[s.CHAR_DATA_WIDTH_INDEX]<<22)},t.prototype.getAsCharData=function(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]},t}(a.AttributeData);t.CellData=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ISoundService=t.ISelectionService=t.IRenderService=t.IMouseService=t.ICoreBrowserService=t.ICharSizeService=void 0;var i=r(14);t.ICharSizeService=i.createDecorator("CharSizeService"),t.ICoreBrowserService=i.createDecorator("CoreBrowserService"),t.IMouseService=i.createDecorator("MouseService"),t.IRenderService=i.createDecorator("RenderService"),t.ISelectionService=i.createDecorator("SelectionService"),t.ISoundService=i.createDecorator("SoundService")},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;var i=function(){function e(){this.fg=0,this.bg=0,this.extended=new n}return e.toColorRGB=function(e){return[e>>>16&255,e>>>8&255,255&e]},e.fromColorRGB=function(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]},e.prototype.clone=function(){var t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t},e.prototype.isInverse=function(){return 67108864&this.fg},e.prototype.isBold=function(){return 134217728&this.fg},e.prototype.isUnderline=function(){return 268435456&this.fg},e.prototype.isBlink=function(){return 536870912&this.fg},e.prototype.isInvisible=function(){return 1073741824&this.fg},e.prototype.isItalic=function(){return 67108864&this.bg},e.prototype.isDim=function(){return 134217728&this.bg},e.prototype.getFgColorMode=function(){return 50331648&this.fg},e.prototype.getBgColorMode=function(){return 50331648&this.bg},e.prototype.isFgRGB=function(){return 50331648==(50331648&this.fg)},e.prototype.isBgRGB=function(){return 50331648==(50331648&this.bg)},e.prototype.isFgPalette=function(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)},e.prototype.isBgPalette=function(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)},e.prototype.isFgDefault=function(){return 0==(50331648&this.fg)},e.prototype.isBgDefault=function(){return 0==(50331648&this.bg)},e.prototype.isAttributeDefault=function(){return 0===this.fg&&0===this.bg},e.prototype.getFgColor=function(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}},e.prototype.getBgColor=function(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}},e.prototype.hasExtendedAttrs=function(){return 268435456&this.bg},e.prototype.updateExtended=function(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456},e.prototype.getUnderlineColor=function(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()},e.prototype.getUnderlineColorMode=function(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()},e.prototype.isUnderlineColorRGB=function(){return 268435456&this.bg&&~this.extended.underlineColor?50331648==(50331648&this.extended.underlineColor):this.isFgRGB()},e.prototype.isUnderlineColorPalette=function(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()},e.prototype.isUnderlineColorDefault=function(){return 268435456&this.bg&&~this.extended.underlineColor?0==(50331648&this.extended.underlineColor):this.isFgDefault()},e.prototype.getUnderlineStyle=function(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0},e}();t.AttributeData=i;var n=function(){function e(e,t){void 0===e&&(e=0),void 0===t&&(t=-1),this.underlineStyle=e,this.underlineColor=t}return e.prototype.clone=function(){return new e(this.underlineStyle,this.underlineColor)},e.prototype.isEmpty=function(){return 0===this.underlineStyle},e}();t.ExtendedAttrs=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.addDisposableDomListener=void 0,t.addDisposableDomListener=function(e,t,r,i){e.addEventListener(t,r,i);var n=!1;return{dispose:function(){n||(n=!0,e.removeEventListener(t,r,i))}}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=t.utf32ToString=t.stringFromCodePoint=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t,r){void 0===t&&(t=0),void 0===r&&(r=e.length);for(var i="",n=t;n65535?(o-=65536,i+=String.fromCharCode(55296+(o>>10))+String.fromCharCode(o%1024+56320)):i+=String.fromCharCode(o)}return i};var i=function(){function e(){this._interim=0}return e.prototype.clear=function(){this._interim=0},e.prototype.decode=function(e,t){var r=e.length;if(!r)return 0;var i=0,n=0;this._interim&&(56320<=(a=e.charCodeAt(n++))&&a<=57343?t[i++]=1024*(this._interim-55296)+a-56320+65536:(t[i++]=this._interim,t[i++]=a),this._interim=0);for(var o=n;o=r)return this._interim=s,i;var a;56320<=(a=e.charCodeAt(o))&&a<=57343?t[i++]=1024*(s-55296)+a-56320+65536:(t[i++]=s,t[i++]=a)}else t[i++]=s}return i},e}();t.StringToUtf32=i;var n=function(){function e(){this.interim=new Uint8Array(3)}return e.prototype.clear=function(){this.interim.fill(0)},e.prototype.decode=function(e,t){var r=e.length;if(!r)return 0;var i,n,o,s,a=0,c=0,l=0;if(this.interim[0]){var h=!1,u=this.interim[0];u&=192==(224&u)?31:224==(240&u)?15:7;for(var f=0,_=void 0;(_=63&this.interim[++f])&&f<4;)u<<=6,u|=_;for(var d=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,p=d-f;l=r)return 0;if(128!=(192&(_=e[l++]))){l--,h=!0;break}this.interim[f++]=_,u<<=6,u|=63&_}h||(2===d?u<128?l--:t[a++]=u:3===d?u<2048||u>=55296&&u<=57343||(t[a++]=u):u<65536||u>1114111||(t[a++]=u)),this.interim.fill(0)}for(var v=r-4,g=l;g=r)return this.interim[0]=i,a;if(128!=(192&(n=e[g++]))){g--;continue}if((c=(31&i)<<6|63&n)<128){g--;continue}t[a++]=c}else if(224==(240&i)){if(g>=r)return this.interim[0]=i,a;if(128!=(192&(n=e[g++]))){g--;continue}if(g>=r)return this.interim[0]=i,this.interim[1]=n,a;if(128!=(192&(o=e[g++]))){g--;continue}if((c=(15&i)<<12|(63&n)<<6|63&o)<2048||c>=55296&&c<=57343)continue;t[a++]=c}else if(240==(248&i)){if(g>=r)return this.interim[0]=i,a;if(128!=(192&(n=e[g++]))){g--;continue}if(g>=r)return this.interim[0]=i,this.interim[1]=n,a;if(128!=(192&(o=e[g++]))){g--;continue}if(g>=r)return this.interim[0]=i,this.interim[1]=n,this.interim[2]=o,a;if(128!=(192&(s=e[g++]))){g--;continue}if((c=(7&i)<<18|(63&n)<<12|(63&o)<<6|63&s)<65536||c>1114111)continue;t[a++]=c}}return a},e}();t.Utf8ToUtf32=n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CHAR_ATLAS_CELL_SPACING=t.DIM_OPACITY=t.INVERTED_DEFAULT_COLOR=void 0,t.INVERTED_DEFAULT_COLOR=257,t.DIM_OPACITY=.5,t.CHAR_ATLAS_CELL_SPACING=1},function(e,t,r){"use strict";var i,n,o,s;function a(e){var t=e.toString(16);return t.length<2?"0"+t:t}function c(e,t){return e>>0}}(i=t.channels||(t.channels={})),(n=t.color||(t.color={})).blend=function(e,t){var r=(255&t.rgba)/255;if(1===r)return{css:t.css,rgba:t.rgba};var n=t.rgba>>24&255,o=t.rgba>>16&255,s=t.rgba>>8&255,a=e.rgba>>24&255,c=e.rgba>>16&255,l=e.rgba>>8&255,h=a+Math.round((n-a)*r),u=c+Math.round((o-c)*r),f=l+Math.round((s-l)*r);return{css:i.toCss(h,u,f),rgba:i.toRgba(h,u,f)}},n.isOpaque=function(e){return 255==(255&e.rgba)},n.ensureContrastRatio=function(e,t,r){var i=s.ensureContrastRatio(e.rgba,t.rgba,r);if(i)return s.toColor(i>>24&255,i>>16&255,i>>8&255)},n.opaque=function(e){var t=(255|e.rgba)>>>0,r=s.toChannels(t),n=r[0],o=r[1],a=r[2];return{css:i.toCss(n,o,a),rgba:t}},n.opacity=function(e,t){var r=Math.round(255*t),n=s.toChannels(e.rgba),o=n[0],a=n[1],c=n[2];return{css:i.toCss(o,a,c,r),rgba:i.toRgba(o,a,c,r)}},(t.css||(t.css={})).toColor=function(e){switch(e.length){case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}throw new Error("css.toColor: Unsupported css format")},function(e){function t(e,t,r){var i=e/255,n=t/255,o=r/255;return.2126*(i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4))+.7152*(n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4))+.0722*(o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(o=t.rgb||(t.rgb={})),function(e){function t(e,t,r){for(var i=e>>24&255,n=e>>16&255,s=e>>8&255,a=t>>24&255,l=t>>16&255,h=t>>8&255,u=c(o.relativeLuminance2(a,h,l),o.relativeLuminance2(i,n,s));u0||l>0||h>0);)a-=Math.max(0,Math.ceil(.1*a)),l-=Math.max(0,Math.ceil(.1*l)),h-=Math.max(0,Math.ceil(.1*h)),u=c(o.relativeLuminance2(a,h,l),o.relativeLuminance2(i,n,s));return(a<<24|l<<16|h<<8|255)>>>0}function r(e,t,r){for(var i=e>>24&255,n=e>>16&255,s=e>>8&255,a=t>>24&255,l=t>>16&255,h=t>>8&255,u=c(o.relativeLuminance2(a,h,l),o.relativeLuminance2(i,n,s));u>>0}e.ensureContrastRatio=function(e,i,n){var s=o.relativeLuminance(e>>8),a=o.relativeLuminance(i>>8);if(c(s,a)>24&255,e>>16&255,e>>8&255,255&e]},e.toColor=function(e,t,r){return{css:i.toCss(e,t,r),rgba:i.toRgba(e,t,r)}}}(s=t.rgba||(t.rgba={})),t.toPaddedHex=a,t.contrastRatio=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isLinux=t.isWindows=t.isIphone=t.isIpad=t.isMac=t.isSafari=t.isFirefox=void 0;var i="undefined"==typeof navigator,n=i?"node":navigator.userAgent,o=i?"node":navigator.platform;function s(e,t){return e.indexOf(t)>=0}t.isFirefox=!!~n.indexOf("Firefox"),t.isSafari=/^((?!chrome|android).)*safari/i.test(n),t.isMac=s(["Macintosh","MacIntel","MacPPC","Mac68K"],o),t.isIpad="iPad"===o,t.isIphone="iPhone"===o,t.isWindows=s(["Windows","Win16","Win32","WinCE"],o),t.isLinux=o.indexOf("Linux")>=0},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.C1=t.C0=void 0,function(e){e.NUL="\0",e.SOH="",e.STX="",e.ETX="",e.EOT="",e.ENQ="",e.ACK="",e.BEL="",e.BS="\b",e.HT="\t",e.LF="\n",e.VT="\v",e.FF="\f",e.CR="\r",e.SO="",e.SI="",e.DLE="",e.DC1="",e.DC2="",e.DC3="",e.DC4="",e.NAK="",e.SYN="",e.ETB="",e.CAN="",e.EM="",e.SUB="",e.ESC="",e.FS="",e.GS="",e.RS="",e.US="",e.SP=" ",e.DEL=""}(t.C0||(t.C0={})),function(e){e.PAD="€",e.HOP="",e.BPH="‚",e.NBH="ƒ",e.IND="„",e.NEL="…",e.SSA="†",e.ESA="‡",e.HTS="ˆ",e.HTJ="‰",e.VTS="Š",e.PLD="‹",e.PLU="Œ",e.RI="",e.SS2="Ž",e.SS3="",e.DCS="",e.PU1="‘",e.PU2="’",e.STS="“",e.CCH="”",e.MW="•",e.SPA="–",e.EPA="—",e.SOS="˜",e.SGCI="™",e.SCI="š",e.CSI="›",e.ST="œ",e.OSC="",e.PM="ž",e.APC="Ÿ"}(t.C1||(t.C1={}))},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BaseRenderLayer=void 0;var i=r(3),n=r(9),o=r(25),s=r(6),a=r(28),c=r(10),l=r(17),h=function(){function e(e,t,r,i,n,o,s,a){this._container=e,this._alpha=i,this._colors=n,this._rendererId=o,this._bufferService=s,this._optionsService=a,this._scaledCharWidth=0,this._scaledCharHeight=0,this._scaledCellWidth=0,this._scaledCellHeight=0,this._scaledCharLeft=0,this._scaledCharTop=0,this._currentGlyphIdentifier={chars:"",code:0,bg:0,fg:0,bold:!1,dim:!1,italic:!1},this._canvas=document.createElement("canvas"),this._canvas.classList.add("xterm-"+t+"-layer"),this._canvas.style.zIndex=r.toString(),this._initCanvas(),this._container.appendChild(this._canvas)}return e.prototype.dispose=function(){var e;l.removeElementFromParent(this._canvas),null===(e=this._charAtlas)||void 0===e||e.dispose()},e.prototype._initCanvas=function(){this._ctx=a.throwIfFalsy(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()},e.prototype.onOptionsChanged=function(){},e.prototype.onBlur=function(){},e.prototype.onFocus=function(){},e.prototype.onCursorMove=function(){},e.prototype.onGridChanged=function(e,t){},e.prototype.onSelectionChanged=function(e,t,r){void 0===r&&(r=!1)},e.prototype.setColors=function(e){this._refreshCharAtlas(e)},e.prototype._setTransparency=function(e){if(e!==this._alpha){var t=this._canvas;this._alpha=e,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,t),this._refreshCharAtlas(this._colors),this.onGridChanged(0,this._bufferService.rows-1)}},e.prototype._refreshCharAtlas=function(e){this._scaledCharWidth<=0&&this._scaledCharHeight<=0||(this._charAtlas=o.acquireCharAtlas(this._optionsService.options,this._rendererId,e,this._scaledCharWidth,this._scaledCharHeight),this._charAtlas.warmUp())},e.prototype.resize=function(e){this._scaledCellWidth=e.scaledCellWidth,this._scaledCellHeight=e.scaledCellHeight,this._scaledCharWidth=e.scaledCharWidth,this._scaledCharHeight=e.scaledCharHeight,this._scaledCharLeft=e.scaledCharLeft,this._scaledCharTop=e.scaledCharTop,this._canvas.width=e.scaledCanvasWidth,this._canvas.height=e.scaledCanvasHeight,this._canvas.style.width=e.canvasWidth+"px",this._canvas.style.height=e.canvasHeight+"px",this._alpha||this._clearAll(),this._refreshCharAtlas(this._colors)},e.prototype._fillCells=function(e,t,r,i){this._ctx.fillRect(e*this._scaledCellWidth,t*this._scaledCellHeight,r*this._scaledCellWidth,i*this._scaledCellHeight)},e.prototype._fillBottomLineAtCells=function(e,t,r){void 0===r&&(r=1),this._ctx.fillRect(e*this._scaledCellWidth,(t+1)*this._scaledCellHeight-window.devicePixelRatio-1,r*this._scaledCellWidth,window.devicePixelRatio)},e.prototype._fillLeftLineAtCell=function(e,t,r){this._ctx.fillRect(e*this._scaledCellWidth,t*this._scaledCellHeight,window.devicePixelRatio*r,this._scaledCellHeight)},e.prototype._strokeRectAtCell=function(e,t,r,i){this._ctx.lineWidth=window.devicePixelRatio,this._ctx.strokeRect(e*this._scaledCellWidth+window.devicePixelRatio/2,t*this._scaledCellHeight+window.devicePixelRatio/2,r*this._scaledCellWidth-window.devicePixelRatio,i*this._scaledCellHeight-window.devicePixelRatio)},e.prototype._clearAll=function(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))},e.prototype._clearCells=function(e,t,r,i){this._alpha?this._ctx.clearRect(e*this._scaledCellWidth,t*this._scaledCellHeight,r*this._scaledCellWidth,i*this._scaledCellHeight):(this._ctx.fillStyle=this._colors.background.css,this._ctx.fillRect(e*this._scaledCellWidth,t*this._scaledCellHeight,r*this._scaledCellWidth,i*this._scaledCellHeight))},e.prototype._fillCharTrueColor=function(e,t,r){this._ctx.font=this._getFont(!1,!1),this._ctx.textBaseline="middle",this._clipRow(r),this._ctx.fillText(e.getChars(),t*this._scaledCellWidth+this._scaledCharLeft,r*this._scaledCellHeight+this._scaledCharTop+this._scaledCharHeight/2)},e.prototype._drawChars=function(e,t,r){var o,s,a=this._getContrastColor(e);a||e.isFgRGB()||e.isBgRGB()?this._drawUncachedChars(e,t,r,a):(e.isInverse()?(o=e.isBgDefault()?n.INVERTED_DEFAULT_COLOR:e.getBgColor(),s=e.isFgDefault()?n.INVERTED_DEFAULT_COLOR:e.getFgColor()):(s=e.isBgDefault()?i.DEFAULT_COLOR:e.getBgColor(),o=e.isFgDefault()?i.DEFAULT_COLOR:e.getFgColor()),o+=this._optionsService.options.drawBoldTextInBrightColors&&e.isBold()&&o<8?8:0,this._currentGlyphIdentifier.chars=e.getChars()||i.WHITESPACE_CELL_CHAR,this._currentGlyphIdentifier.code=e.getCode()||i.WHITESPACE_CELL_CODE,this._currentGlyphIdentifier.bg=s,this._currentGlyphIdentifier.fg=o,this._currentGlyphIdentifier.bold=!!e.isBold(),this._currentGlyphIdentifier.dim=!!e.isDim(),this._currentGlyphIdentifier.italic=!!e.isItalic(),this._charAtlas&&this._charAtlas.draw(this._ctx,this._currentGlyphIdentifier,t*this._scaledCellWidth+this._scaledCharLeft,r*this._scaledCellHeight+this._scaledCharTop)||this._drawUncachedChars(e,t,r))},e.prototype._drawUncachedChars=function(e,t,r,i){if(this._ctx.save(),this._ctx.font=this._getFont(!!e.isBold(),!!e.isItalic()),this._ctx.textBaseline="middle",e.isInverse())if(i)this._ctx.fillStyle=i.css;else if(e.isBgDefault())this._ctx.fillStyle=c.color.opaque(this._colors.background).css;else if(e.isBgRGB())this._ctx.fillStyle="rgb("+s.AttributeData.toColorRGB(e.getBgColor()).join(",")+")";else{var o=e.getBgColor();this._optionsService.options.drawBoldTextInBrightColors&&e.isBold()&&o<8&&(o+=8),this._ctx.fillStyle=this._colors.ansi[o].css}else if(i)this._ctx.fillStyle=i.css;else if(e.isFgDefault())this._ctx.fillStyle=this._colors.foreground.css;else if(e.isFgRGB())this._ctx.fillStyle="rgb("+s.AttributeData.toColorRGB(e.getFgColor()).join(",")+")";else{var a=e.getFgColor();this._optionsService.options.drawBoldTextInBrightColors&&e.isBold()&&a<8&&(a+=8),this._ctx.fillStyle=this._colors.ansi[a].css}this._clipRow(r),e.isDim()&&(this._ctx.globalAlpha=n.DIM_OPACITY),this._ctx.fillText(e.getChars(),t*this._scaledCellWidth+this._scaledCharLeft,r*this._scaledCellHeight+this._scaledCharTop+this._scaledCharHeight/2),this._ctx.restore()},e.prototype._clipRow=function(e){this._ctx.beginPath(),this._ctx.rect(0,e*this._scaledCellHeight,this._bufferService.cols*this._scaledCellWidth,this._scaledCellHeight),this._ctx.clip()},e.prototype._getFont=function(e,t){return(t?"italic":"")+" "+(e?this._optionsService.options.fontWeightBold:this._optionsService.options.fontWeight)+" "+this._optionsService.options.fontSize*window.devicePixelRatio+"px "+this._optionsService.options.fontFamily},e.prototype._getContrastColor=function(e){if(1!==this._optionsService.options.minimumContrastRatio){var t=this._colors.contrastCache.getColor(e.bg,e.fg);if(void 0!==t)return t||void 0;var r=e.getFgColor(),i=e.getFgColorMode(),n=e.getBgColor(),o=e.getBgColorMode(),s=!!e.isInverse(),a=!!e.isInverse();if(s){var l=r;r=n,n=l;var h=i;i=o,o=h}var u=this._resolveBackgroundRgba(o,n,s),f=this._resolveForegroundRgba(i,r,s,a),_=c.rgba.ensureContrastRatio(u,f,this._optionsService.options.minimumContrastRatio);if(_){var d={css:c.channels.toCss(_>>24&255,_>>16&255,_>>8&255),rgba:_};return this._colors.contrastCache.setColor(e.bg,e.fg,d),d}this._colors.contrastCache.setColor(e.bg,e.fg,null)}},e.prototype._resolveBackgroundRgba=function(e,t,r){switch(e){case 16777216:case 33554432:return this._colors.ansi[t].rgba;case 50331648:return t<<8;case 0:default:return r?this._colors.foreground.rgba:this._colors.background.rgba}},e.prototype._resolveForegroundRgba=function(e,t,r,i){switch(e){case 16777216:case 33554432:return this._optionsService.options.drawBoldTextInBrightColors&&i&&t<8&&(t+=8),this._colors.ansi[t].rgba;case 50331648:return t<<8;case 0:default:return r?this._colors.background.rgba:this._colors.foreground.rgba}},e}();t.BaseRenderLayer=h},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.createDecorator=t.getServiceDependencies=t.serviceRegistry=void 0;function i(e,t,r){t.di$target===t?t.di$dependencies.push({id:e,index:r}):(t.di$dependencies=[{id:e,index:r}],t.di$target=t)}t.serviceRegistry=new Map,t.getServiceDependencies=function(e){return e.di$dependencies||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);var r=function(e,t,n){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");i(r,e,n)};return r.toString=function(){return e},t.serviceRegistry.set(e,r),r}},function(e,t,r){"use strict";function i(e,t,r,i){if(void 0===r&&(r=0),void 0===i&&(i=e.length),r>=e.length)return e;r=(e.length+r)%e.length,i=i>=e.length?e.length:(e.length+i)%e.length;for(var n=r;n>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):r]},e.prototype.set=function(e,t){this._data[3*e+1]=t[n.CHAR_DATA_ATTR_INDEX],t[n.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[n.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[n.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[n.CHAR_DATA_WIDTH_INDEX]<<22},e.prototype.getWidth=function(e){return this._data[3*e+0]>>22},e.prototype.hasWidth=function(e){return 12582912&this._data[3*e+0]},e.prototype.getFg=function(e){return this._data[3*e+1]},e.prototype.getBg=function(e){return this._data[3*e+2]},e.prototype.hasContent=function(e){return 4194303&this._data[3*e+0]},e.prototype.getCodePoint=function(e){var t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t},e.prototype.isCombined=function(e){return 2097152&this._data[3*e+0]},e.prototype.getString=function(e){var t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?i.stringFromCodePoint(2097151&t):""},e.prototype.loadCell=function(e,t){var r=3*e;return t.content=this._data[r+0],t.fg=this._data[r+1],t.bg=this._data[r+2],2097152&t.content&&(t.combinedData=this._combined[e]),268435456&t.bg&&(t.extended=this._extendedAttrs[e]),t},e.prototype.setCell=function(e,t){2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg},e.prototype.setCellFromCodePoint=function(e,t,r,i,n,o){268435456&n&&(this._extendedAttrs[e]=o),this._data[3*e+0]=t|r<<22,this._data[3*e+1]=i,this._data[3*e+2]=n},e.prototype.addCodepointToCell=function(e,t){var r=this._data[3*e+0];2097152&r?this._combined[e]+=i.stringFromCodePoint(t):(2097151&r?(this._combined[e]=i.stringFromCodePoint(2097151&r)+i.stringFromCodePoint(t),r&=-2097152,r|=2097152):r=t|1<<22,this._data[3*e+0]=r)},e.prototype.insertCells=function(e,t,r,i){if((e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodePoint(e-1,0,1,(null==i?void 0:i.fg)||0,(null==i?void 0:i.bg)||0,(null==i?void 0:i.extended)||new s.ExtendedAttrs),t=0;--a)this.setCell(e+t+a,this.loadCell(e+a,n));for(a=0;athis.length){var r=new Uint32Array(3*e);this.length&&(3*e=e&&delete this._combined[o]}}else this._data=new Uint32Array(0),this._combined={};this.length=e}},e.prototype.fill=function(e){this._combined={},this._extendedAttrs={};for(var t=0;t=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0},e.prototype.copyCellsFrom=function(e,t,r,i,n){var o=e._data;if(n)for(var s=i-1;s>=0;s--)for(var a=0;a<3;a++)this._data[3*(r+s)+a]=o[3*(t+s)+a];else for(s=0;s=t&&(this._combined[l-t+r]=e._combined[l])}},e.prototype.translateToString=function(e,t,r){void 0===e&&(e=!1),void 0===t&&(t=0),void 0===r&&(r=this.length),e&&(r=Math.min(r,this.getTrimmedLength()));for(var o="";t>22||1}return o},e}();t.BufferLine=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removeElementFromParent=void 0,t.removeElementFromParent=function(){for(var e,t=[],r=0;r24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(o=t.WindowsOptionsReportType||(t.WindowsOptionsReportType={}));var m=function(){function e(e,t,r,i){this._bufferService=e,this._coreService=t,this._logService=r,this._optionsService=i,this._data=new Uint32Array(0)}return e.prototype.hook=function(e){this._data=new Uint32Array(0)},e.prototype.put=function(e,t,r){this._data=h.concat(this._data,e.subarray(t,r))},e.prototype.unhook=function(e){if(e){var t=u.utf32ToString(this._data);switch(this._data=new Uint32Array(0),t){case'"q':return this._coreService.triggerDataEvent(s.C0.ESC+'P1$r0"q'+s.C0.ESC+"\\");case'"p':return this._coreService.triggerDataEvent(s.C0.ESC+'P1$r61;1"p'+s.C0.ESC+"\\");case"r":var r=this._bufferService.buffer.scrollTop+1+";"+(this._bufferService.buffer.scrollBottom+1)+"r";return this._coreService.triggerDataEvent(s.C0.ESC+"P1$r"+r+s.C0.ESC+"\\");case"m":return this._coreService.triggerDataEvent(s.C0.ESC+"P1$r0m"+s.C0.ESC+"\\");case" q":var i={block:2,underline:4,bar:6}[this._optionsService.options.cursorStyle];return i-=this._optionsService.options.cursorBlink?1:0,this._coreService.triggerDataEvent(s.C0.ESC+"P1$r"+i+" q"+s.C0.ESC+"\\");default:this._logService.debug("Unknown DCS $q %s",t),this._coreService.triggerDataEvent(s.C0.ESC+"P0$r"+s.C0.ESC+"\\")}}else this._data=new Uint32Array(0)},e}(),C=function(e){function t(t,r,i,n,o,l,h,d,v){void 0===v&&(v=new c.EscapeSequenceParser);var y=e.call(this)||this;y._bufferService=t,y._charsetService=r,y._coreService=i,y._dirtyRowService=n,y._logService=o,y._optionsService=l,y._coreMouseService=h,y._unicodeService=d,y._parser=v,y._parseBuffer=new Uint32Array(4096),y._stringDecoder=new u.StringToUtf32,y._utf8Decoder=new u.Utf8ToUtf32,y._workCell=new p.CellData,y._windowTitle="",y._iconName="",y._windowTitleStack=[],y._iconNameStack=[],y._curAttrData=f.DEFAULT_ATTR_DATA.clone(),y._eraseAttrDataInternal=f.DEFAULT_ATTR_DATA.clone(),y._onRequestBell=new _.EventEmitter,y._onRequestRefreshRows=new _.EventEmitter,y._onRequestReset=new _.EventEmitter,y._onRequestScroll=new _.EventEmitter,y._onRequestSyncScrollBar=new _.EventEmitter,y._onRequestWindowsOptionsReport=new _.EventEmitter,y._onA11yChar=new _.EventEmitter,y._onA11yTab=new _.EventEmitter,y._onCursorMove=new _.EventEmitter,y._onLineFeed=new _.EventEmitter,y._onScroll=new _.EventEmitter,y._onTitleChange=new _.EventEmitter,y.register(y._parser),y._parser.setCsiHandlerFallback((function(e,t){y._logService.debug("Unknown CSI code: ",{identifier:y._parser.identToString(e),params:t.toArray()})})),y._parser.setEscHandlerFallback((function(e){y._logService.debug("Unknown ESC code: ",{identifier:y._parser.identToString(e)})})),y._parser.setExecuteHandlerFallback((function(e){y._logService.debug("Unknown EXECUTE code: ",{code:e})})),y._parser.setOscHandlerFallback((function(e,t,r){y._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:r})})),y._parser.setDcsHandlerFallback((function(e,t,r){"HOOK"===t&&(r=r.toArray()),y._logService.debug("Unknown DCS code: ",{identifier:y._parser.identToString(e),action:t,payload:r})})),y._parser.setPrintHandler((function(e,t,r){return y.print(e,t,r)})),y._parser.setCsiHandler({final:"@"},(function(e){return y.insertChars(e)})),y._parser.setCsiHandler({intermediates:" ",final:"@"},(function(e){return y.scrollLeft(e)})),y._parser.setCsiHandler({final:"A"},(function(e){return y.cursorUp(e)})),y._parser.setCsiHandler({intermediates:" ",final:"A"},(function(e){return y.scrollRight(e)})),y._parser.setCsiHandler({final:"B"},(function(e){return y.cursorDown(e)})),y._parser.setCsiHandler({final:"C"},(function(e){return y.cursorForward(e)})),y._parser.setCsiHandler({final:"D"},(function(e){return y.cursorBackward(e)})),y._parser.setCsiHandler({final:"E"},(function(e){return y.cursorNextLine(e)})),y._parser.setCsiHandler({final:"F"},(function(e){return y.cursorPrecedingLine(e)})),y._parser.setCsiHandler({final:"G"},(function(e){return y.cursorCharAbsolute(e)})),y._parser.setCsiHandler({final:"H"},(function(e){return y.cursorPosition(e)})),y._parser.setCsiHandler({final:"I"},(function(e){return y.cursorForwardTab(e)})),y._parser.setCsiHandler({final:"J"},(function(e){return y.eraseInDisplay(e)})),y._parser.setCsiHandler({prefix:"?",final:"J"},(function(e){return y.eraseInDisplay(e)})),y._parser.setCsiHandler({final:"K"},(function(e){return y.eraseInLine(e)})),y._parser.setCsiHandler({prefix:"?",final:"K"},(function(e){return y.eraseInLine(e)})),y._parser.setCsiHandler({final:"L"},(function(e){return y.insertLines(e)})),y._parser.setCsiHandler({final:"M"},(function(e){return y.deleteLines(e)})),y._parser.setCsiHandler({final:"P"},(function(e){return y.deleteChars(e)})),y._parser.setCsiHandler({final:"S"},(function(e){return y.scrollUp(e)})),y._parser.setCsiHandler({final:"T"},(function(e){return y.scrollDown(e)})),y._parser.setCsiHandler({final:"X"},(function(e){return y.eraseChars(e)})),y._parser.setCsiHandler({final:"Z"},(function(e){return y.cursorBackwardTab(e)})),y._parser.setCsiHandler({final:"`"},(function(e){return y.charPosAbsolute(e)})),y._parser.setCsiHandler({final:"a"},(function(e){return y.hPositionRelative(e)})),y._parser.setCsiHandler({final:"b"},(function(e){return y.repeatPrecedingCharacter(e)})),y._parser.setCsiHandler({final:"c"},(function(e){return y.sendDeviceAttributesPrimary(e)})),y._parser.setCsiHandler({prefix:">",final:"c"},(function(e){return y.sendDeviceAttributesSecondary(e)})),y._parser.setCsiHandler({final:"d"},(function(e){return y.linePosAbsolute(e)})),y._parser.setCsiHandler({final:"e"},(function(e){return y.vPositionRelative(e)})),y._parser.setCsiHandler({final:"f"},(function(e){return y.hVPosition(e)})),y._parser.setCsiHandler({final:"g"},(function(e){return y.tabClear(e)})),y._parser.setCsiHandler({final:"h"},(function(e){return y.setMode(e)})),y._parser.setCsiHandler({prefix:"?",final:"h"},(function(e){return y.setModePrivate(e)})),y._parser.setCsiHandler({final:"l"},(function(e){return y.resetMode(e)})),y._parser.setCsiHandler({prefix:"?",final:"l"},(function(e){return y.resetModePrivate(e)})),y._parser.setCsiHandler({final:"m"},(function(e){return y.charAttributes(e)})),y._parser.setCsiHandler({final:"n"},(function(e){return y.deviceStatus(e)})),y._parser.setCsiHandler({prefix:"?",final:"n"},(function(e){return y.deviceStatusPrivate(e)})),y._parser.setCsiHandler({intermediates:"!",final:"p"},(function(e){return y.softReset(e)})),y._parser.setCsiHandler({intermediates:" ",final:"q"},(function(e){return y.setCursorStyle(e)})),y._parser.setCsiHandler({final:"r"},(function(e){return y.setScrollRegion(e)})),y._parser.setCsiHandler({final:"s"},(function(e){return y.saveCursor(e)})),y._parser.setCsiHandler({final:"t"},(function(e){return y.windowOptions(e)})),y._parser.setCsiHandler({final:"u"},(function(e){return y.restoreCursor(e)})),y._parser.setCsiHandler({intermediates:"'",final:"}"},(function(e){return y.insertColumns(e)})),y._parser.setCsiHandler({intermediates:"'",final:"~"},(function(e){return y.deleteColumns(e)})),y._parser.setExecuteHandler(s.C0.BEL,(function(){return y.bell()})),y._parser.setExecuteHandler(s.C0.LF,(function(){return y.lineFeed()})),y._parser.setExecuteHandler(s.C0.VT,(function(){return y.lineFeed()})),y._parser.setExecuteHandler(s.C0.FF,(function(){return y.lineFeed()})),y._parser.setExecuteHandler(s.C0.CR,(function(){return y.carriageReturn()})),y._parser.setExecuteHandler(s.C0.BS,(function(){return y.backspace()})),y._parser.setExecuteHandler(s.C0.HT,(function(){return y.tab()})),y._parser.setExecuteHandler(s.C0.SO,(function(){return y.shiftOut()})),y._parser.setExecuteHandler(s.C0.SI,(function(){return y.shiftIn()})),y._parser.setExecuteHandler(s.C1.IND,(function(){return y.index()})),y._parser.setExecuteHandler(s.C1.NEL,(function(){return y.nextLine()})),y._parser.setExecuteHandler(s.C1.HTS,(function(){return y.tabSet()})),y._parser.setOscHandler(0,new g.OscHandler((function(e){y.setTitle(e),y.setIconName(e)}))),y._parser.setOscHandler(1,new g.OscHandler((function(e){return y.setIconName(e)}))),y._parser.setOscHandler(2,new g.OscHandler((function(e){return y.setTitle(e)}))),y._parser.setEscHandler({final:"7"},(function(){return y.saveCursor()})),y._parser.setEscHandler({final:"8"},(function(){return y.restoreCursor()})),y._parser.setEscHandler({final:"D"},(function(){return y.index()})),y._parser.setEscHandler({final:"E"},(function(){return y.nextLine()})),y._parser.setEscHandler({final:"H"},(function(){return y.tabSet()})),y._parser.setEscHandler({final:"M"},(function(){return y.reverseIndex()})),y._parser.setEscHandler({final:"="},(function(){return y.keypadApplicationMode()})),y._parser.setEscHandler({final:">"},(function(){return y.keypadNumericMode()})),y._parser.setEscHandler({final:"c"},(function(){return y.fullReset()})),y._parser.setEscHandler({final:"n"},(function(){return y.setgLevel(2)})),y._parser.setEscHandler({final:"o"},(function(){return y.setgLevel(3)})),y._parser.setEscHandler({final:"|"},(function(){return y.setgLevel(3)})),y._parser.setEscHandler({final:"}"},(function(){return y.setgLevel(2)})),y._parser.setEscHandler({final:"~"},(function(){return y.setgLevel(1)})),y._parser.setEscHandler({intermediates:"%",final:"@"},(function(){return y.selectDefaultCharset()})),y._parser.setEscHandler({intermediates:"%",final:"G"},(function(){return y.selectDefaultCharset()}));var b=function(e){S._parser.setEscHandler({intermediates:"(",final:e},(function(){return y.selectCharset("("+e)})),S._parser.setEscHandler({intermediates:")",final:e},(function(){return y.selectCharset(")"+e)})),S._parser.setEscHandler({intermediates:"*",final:e},(function(){return y.selectCharset("*"+e)})),S._parser.setEscHandler({intermediates:"+",final:e},(function(){return y.selectCharset("+"+e)})),S._parser.setEscHandler({intermediates:"-",final:e},(function(){return y.selectCharset("-"+e)})),S._parser.setEscHandler({intermediates:".",final:e},(function(){return y.selectCharset("."+e)})),S._parser.setEscHandler({intermediates:"/",final:e},(function(){return y.selectCharset("/"+e)}))},S=this;for(var C in a.CHARSETS)b(C);return y._parser.setEscHandler({intermediates:"#",final:"8"},(function(){return y.screenAlignmentPattern()})),y._parser.setErrorHandler((function(e){return y._logService.error("Parsing error: ",e),e})),y._parser.setDcsHandler({intermediates:"$",final:"q"},new m(y._bufferService,y._coreService,y._logService,y._optionsService)),y}return n(t,e),Object.defineProperty(t.prototype,"onRequestBell",{get:function(){return this._onRequestBell.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestRefreshRows",{get:function(){return this._onRequestRefreshRows.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestReset",{get:function(){return this._onRequestReset.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestScroll",{get:function(){return this._onRequestScroll.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestSyncScrollBar",{get:function(){return this._onRequestSyncScrollBar.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestWindowsOptionsReport",{get:function(){return this._onRequestWindowsOptionsReport.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yChar",{get:function(){return this._onA11yChar.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yTab",{get:function(){return this._onA11yTab.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onCursorMove",{get:function(){return this._onCursorMove.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onLineFeed",{get:function(){return this._onLineFeed.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onScroll",{get:function(){return this._onScroll.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onTitleChange",{get:function(){return this._onTitleChange.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){e.prototype.dispose.call(this)},t.prototype.parse=function(e){var t=this._bufferService.buffer,r=t.x,i=t.y;if(this._logService.debug("parsing data",e),this._parseBuffer.length131072)for(var n=0;n0&&2===_.getWidth(o.x-1)&&_.setCellFromCodePoint(o.x-1,0,1,f.fg,f.bg,f.extended);for(var p=t;p=c)if(l){for(;o.x=this._bufferService.rows&&(o.y=this._bufferService.rows-1),o.lines.get(o.ybase+o.y).isWrapped=!0),_=o.lines.get(o.ybase+o.y)}else if(o.x=c-1,2===n)continue;if(h&&(_.insertCells(o.x,n,o.getNullCell(f),f),2===_.getWidth(c-1)&&_.setCellFromCodePoint(c-1,d.NULL_CELL_CODE,d.NULL_CELL_WIDTH,f.fg,f.bg,f.extended)),_.setCellFromCodePoint(o.x++,i,n,f.fg,f.bg,f.extended),n>0)for(;--n;)_.setCellFromCodePoint(o.x++,0,0,f.fg,f.bg,f.extended)}else _.getWidth(o.x-1)?_.addCodepointToCell(o.x-1,i):_.addCodepointToCell(o.x-2,i)}r-t>0&&(_.loadCell(o.x-1,this._workCell),2===this._workCell.getWidth()||this._workCell.getCode()>65535?this._parser.precedingCodepoint=0:this._workCell.isCombined()?this._parser.precedingCodepoint=this._workCell.getChars().charCodeAt(0):this._parser.precedingCodepoint=this._workCell.content),o.x0&&0===_.getWidth(o.x)&&!_.hasContent(o.x)&&_.setCellFromCodePoint(o.x,0,1,f.fg,f.bg,f.extended),this._dirtyRowService.markDirty(o.y)},t.prototype.addCsiHandler=function(e,t){var r=this;return"t"!==e.final||e.prefix||e.intermediates?this._parser.addCsiHandler(e,t):this._parser.addCsiHandler(e,(function(e){return!S(e.params[0],r._optionsService.options.windowOptions)||t(e)}))},t.prototype.addDcsHandler=function(e,t){return this._parser.addDcsHandler(e,new y.DcsHandler(t))},t.prototype.addEscHandler=function(e,t){return this._parser.addEscHandler(e,t)},t.prototype.addOscHandler=function(e,t){return this._parser.addOscHandler(e,new g.OscHandler(t))},t.prototype.bell=function(){this._onRequestBell.fire()},t.prototype.lineFeed=function(){var e=this._bufferService.buffer;this._dirtyRowService.markDirty(e.y),this._optionsService.options.convertEol&&(e.x=0),e.y++,e.y===e.scrollBottom+1?(e.y--,this._onRequestScroll.fire(this._eraseAttrData())):e.y>=this._bufferService.rows&&(e.y=this._bufferService.rows-1),e.x>=this._bufferService.cols&&e.x--,this._dirtyRowService.markDirty(e.y),this._onLineFeed.fire()},t.prototype.carriageReturn=function(){this._bufferService.buffer.x=0},t.prototype.backspace=function(){var e,t=this._bufferService.buffer;if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),void(t.x>0&&t.x--);if(this._restrictCursor(this._bufferService.cols),t.x>0)t.x--;else if(0===t.x&&t.y>t.scrollTop&&t.y<=t.scrollBottom&&(null===(e=t.lines.get(t.ybase+t.y))||void 0===e?void 0:e.isWrapped)){t.lines.get(t.ybase+t.y).isWrapped=!1,t.y--,t.x=this._bufferService.cols-1;var r=t.lines.get(t.ybase+t.y);r.hasWidth(t.x)&&!r.hasContent(t.x)&&t.x--}this._restrictCursor()},t.prototype.tab=function(){if(!(this._bufferService.buffer.x>=this._bufferService.cols)){var e=this._bufferService.buffer.x;this._bufferService.buffer.x=this._bufferService.buffer.nextStop(),this._optionsService.options.screenReaderMode&&this._onA11yTab.fire(this._bufferService.buffer.x-e)}},t.prototype.shiftOut=function(){this._charsetService.setgLevel(1)},t.prototype.shiftIn=function(){this._charsetService.setgLevel(0)},t.prototype._restrictCursor=function(e){void 0===e&&(e=this._bufferService.cols-1),this._bufferService.buffer.x=Math.min(e,Math.max(0,this._bufferService.buffer.x)),this._bufferService.buffer.y=this._coreService.decPrivateModes.origin?Math.min(this._bufferService.buffer.scrollBottom,Math.max(this._bufferService.buffer.scrollTop,this._bufferService.buffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._bufferService.buffer.y)),this._dirtyRowService.markDirty(this._bufferService.buffer.y)},t.prototype._setCursor=function(e,t){this._dirtyRowService.markDirty(this._bufferService.buffer.y),this._coreService.decPrivateModes.origin?(this._bufferService.buffer.x=e,this._bufferService.buffer.y=this._bufferService.buffer.scrollTop+t):(this._bufferService.buffer.x=e,this._bufferService.buffer.y=t),this._restrictCursor(),this._dirtyRowService.markDirty(this._bufferService.buffer.y)},t.prototype._moveCursor=function(e,t){this._restrictCursor(),this._setCursor(this._bufferService.buffer.x+e,this._bufferService.buffer.y+t)},t.prototype.cursorUp=function(e){var t=this._bufferService.buffer.y-this._bufferService.buffer.scrollTop;t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1))},t.prototype.cursorDown=function(e){var t=this._bufferService.buffer.scrollBottom-this._bufferService.buffer.y;t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1)},t.prototype.cursorForward=function(e){this._moveCursor(e.params[0]||1,0)},t.prototype.cursorBackward=function(e){this._moveCursor(-(e.params[0]||1),0)},t.prototype.cursorNextLine=function(e){this.cursorDown(e),this._bufferService.buffer.x=0},t.prototype.cursorPrecedingLine=function(e){this.cursorUp(e),this._bufferService.buffer.x=0},t.prototype.cursorCharAbsolute=function(e){this._setCursor((e.params[0]||1)-1,this._bufferService.buffer.y)},t.prototype.cursorPosition=function(e){this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1)},t.prototype.charPosAbsolute=function(e){this._setCursor((e.params[0]||1)-1,this._bufferService.buffer.y)},t.prototype.hPositionRelative=function(e){this._moveCursor(e.params[0]||1,0)},t.prototype.linePosAbsolute=function(e){this._setCursor(this._bufferService.buffer.x,(e.params[0]||1)-1)},t.prototype.vPositionRelative=function(e){this._moveCursor(0,e.params[0]||1)},t.prototype.hVPosition=function(e){this.cursorPosition(e)},t.prototype.tabClear=function(e){var t=e.params[0];0===t?delete this._bufferService.buffer.tabs[this._bufferService.buffer.x]:3===t&&(this._bufferService.buffer.tabs={})},t.prototype.cursorForwardTab=function(e){if(!(this._bufferService.buffer.x>=this._bufferService.cols))for(var t=e.params[0]||1;t--;)this._bufferService.buffer.x=this._bufferService.buffer.nextStop()},t.prototype.cursorBackwardTab=function(e){if(!(this._bufferService.buffer.x>=this._bufferService.cols))for(var t=e.params[0]||1,r=this._bufferService.buffer;t--;)r.x=r.prevStop()},t.prototype._eraseInBufferLine=function(e,t,r,i){void 0===i&&(i=!1);var n=this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase+e);n.replaceCells(t,r,this._bufferService.buffer.getNullCell(this._eraseAttrData()),this._eraseAttrData()),i&&(n.isWrapped=!1)},t.prototype._resetBufferLine=function(e){var t=this._bufferService.buffer.lines.get(this._bufferService.buffer.ybase+e);t.fill(this._bufferService.buffer.getNullCell(this._eraseAttrData())),t.isWrapped=!1},t.prototype.eraseInDisplay=function(e){var t;switch(this._restrictCursor(),e.params[0]){case 0:for(t=this._bufferService.buffer.y,this._dirtyRowService.markDirty(t),this._eraseInBufferLine(t++,this._bufferService.buffer.x,this._bufferService.cols,0===this._bufferService.buffer.x);t=this._bufferService.cols&&(this._bufferService.buffer.lines.get(t+1).isWrapped=!1);t--;)this._resetBufferLine(t);this._dirtyRowService.markDirty(0);break;case 2:for(t=this._bufferService.rows,this._dirtyRowService.markDirty(t-1);t--;)this._resetBufferLine(t);this._dirtyRowService.markDirty(0);break;case 3:var r=this._bufferService.buffer.lines.length-this._bufferService.rows;r>0&&(this._bufferService.buffer.lines.trimStart(r),this._bufferService.buffer.ybase=Math.max(this._bufferService.buffer.ybase-r,0),this._bufferService.buffer.ydisp=Math.max(this._bufferService.buffer.ydisp-r,0),this._onScroll.fire(0))}},t.prototype.eraseInLine=function(e){switch(this._restrictCursor(),e.params[0]){case 0:this._eraseInBufferLine(this._bufferService.buffer.y,this._bufferService.buffer.x,this._bufferService.cols);break;case 1:this._eraseInBufferLine(this._bufferService.buffer.y,0,this._bufferService.buffer.x+1);break;case 2:this._eraseInBufferLine(this._bufferService.buffer.y,0,this._bufferService.cols)}this._dirtyRowService.markDirty(this._bufferService.buffer.y)},t.prototype.insertLines=function(e){this._restrictCursor();var t=e.params[0]||1,r=this._bufferService.buffer;if(!(r.y>r.scrollBottom||r.yr.scrollBottom||r.yt.scrollBottom||t.yt.scrollBottom||t.yt.scrollBottom||t.yt.scrollBottom||t.y0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent(s.C0.ESC+"[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent(s.C0.ESC+"[?6c"))},t.prototype.sendDeviceAttributesSecondary=function(e){e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent(s.C0.ESC+"[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent(s.C0.ESC+"[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent(s.C0.ESC+"[>83;40003;0c"))},t.prototype._is=function(e){return 0===(this._optionsService.options.termName+"").indexOf(e)},t.prototype.setMode=function(e){for(var t=0;t=2||2===i[1]&&o+n>=5)break;i[1]&&(n=1)}while(++o+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()},t.prototype.charAttributes=function(e){if(1===e.length&&0===e.params[0])return this._curAttrData.fg=f.DEFAULT_ATTR_DATA.fg,void(this._curAttrData.bg=f.DEFAULT_ATTR_DATA.bg);for(var t,r=e.length,i=this._curAttrData,n=0;n=30&&t<=37?(i.fg&=-50331904,i.fg|=16777216|t-30):t>=40&&t<=47?(i.bg&=-50331904,i.bg|=16777216|t-40):t>=90&&t<=97?(i.fg&=-50331904,i.fg|=16777224|t-90):t>=100&&t<=107?(i.bg&=-50331904,i.bg|=16777224|t-100):0===t?(i.fg=f.DEFAULT_ATTR_DATA.fg,i.bg=f.DEFAULT_ATTR_DATA.bg):1===t?i.fg|=134217728:3===t?i.bg|=67108864:4===t?(i.fg|=268435456,this._processUnderline(e.hasSubParams(n)?e.getSubParams(n)[0]:1,i)):5===t?i.fg|=536870912:7===t?i.fg|=67108864:8===t?i.fg|=1073741824:2===t?i.bg|=134217728:21===t?this._processUnderline(2,i):22===t?(i.fg&=-134217729,i.bg&=-134217729):23===t?i.bg&=-67108865:24===t?i.fg&=-268435457:25===t?i.fg&=-536870913:27===t?i.fg&=-67108865:28===t?i.fg&=-1073741825:39===t?(i.fg&=-67108864,i.fg|=16777215&f.DEFAULT_ATTR_DATA.fg):49===t?(i.bg&=-67108864,i.bg|=16777215&f.DEFAULT_ATTR_DATA.bg):38===t||48===t||58===t?n+=this._extractColor(e,n,i):59===t?(i.extended=i.extended.clone(),i.extended.underlineColor=-1,i.updateExtended()):100===t?(i.fg&=-67108864,i.fg|=16777215&f.DEFAULT_ATTR_DATA.fg,i.bg&=-67108864,i.bg|=16777215&f.DEFAULT_ATTR_DATA.bg):this._logService.debug("Unknown SGR attribute: %d.",t)},t.prototype.deviceStatus=function(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(s.C0.ESC+"[0n");break;case 6:var t=this._bufferService.buffer.y+1,r=this._bufferService.buffer.x+1;this._coreService.triggerDataEvent(s.C0.ESC+"["+t+";"+r+"R")}},t.prototype.deviceStatusPrivate=function(e){switch(e.params[0]){case 6:var t=this._bufferService.buffer.y+1,r=this._bufferService.buffer.x+1;this._coreService.triggerDataEvent(s.C0.ESC+"[?"+t+";"+r+"R")}},t.prototype.softReset=function(e){this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._bufferService.buffer.scrollTop=0,this._bufferService.buffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=f.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._bufferService.buffer.savedX=0,this._bufferService.buffer.savedY=this._bufferService.buffer.ybase,this._bufferService.buffer.savedCurAttrData.fg=this._curAttrData.fg,this._bufferService.buffer.savedCurAttrData.bg=this._curAttrData.bg,this._bufferService.buffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1},t.prototype.setCursorStyle=function(e){var t=e.params[0]||1;switch(t){case 1:case 2:this._optionsService.options.cursorStyle="block";break;case 3:case 4:this._optionsService.options.cursorStyle="underline";break;case 5:case 6:this._optionsService.options.cursorStyle="bar"}var r=t%2==1;this._optionsService.options.cursorBlink=r},t.prototype.setScrollRegion=function(e){var t,r=e.params[0]||1;(e.length<2||(t=e.params[1])>this._bufferService.rows||0===t)&&(t=this._bufferService.rows),t>r&&(this._bufferService.buffer.scrollTop=r-1,this._bufferService.buffer.scrollBottom=t-1,this._setCursor(0,0))},t.prototype.windowOptions=function(e){if(S(e.params[0],this._optionsService.options.windowOptions)){var t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(o.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(o.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(s.C0.ESC+"[8;"+this._bufferService.rows+";"+this._bufferService.cols+"t");break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}}},t.prototype.saveCursor=function(e){this._bufferService.buffer.savedX=this._bufferService.buffer.x,this._bufferService.buffer.savedY=this._bufferService.buffer.ybase+this._bufferService.buffer.y,this._bufferService.buffer.savedCurAttrData.fg=this._curAttrData.fg,this._bufferService.buffer.savedCurAttrData.bg=this._curAttrData.bg,this._bufferService.buffer.savedCharset=this._charsetService.charset},t.prototype.restoreCursor=function(e){this._bufferService.buffer.x=this._bufferService.buffer.savedX||0,this._bufferService.buffer.y=Math.max(this._bufferService.buffer.savedY-this._bufferService.buffer.ybase,0),this._curAttrData.fg=this._bufferService.buffer.savedCurAttrData.fg,this._curAttrData.bg=this._bufferService.buffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._bufferService.buffer.savedCharset&&(this._charsetService.charset=this._bufferService.buffer.savedCharset),this._restrictCursor()},t.prototype.setTitle=function(e){this._windowTitle=e,this._onTitleChange.fire(e)},t.prototype.setIconName=function(e){this._iconName=e},t.prototype.nextLine=function(){this._bufferService.buffer.x=0,this.index()},t.prototype.keypadApplicationMode=function(){this._logService.debug("Serial port requested application keypad."),this._coreService.decPrivateModes.applicationKeypad=!0,this._onRequestSyncScrollBar.fire()},t.prototype.keypadNumericMode=function(){this._logService.debug("Switching back to normal keypad."),this._coreService.decPrivateModes.applicationKeypad=!1,this._onRequestSyncScrollBar.fire()},t.prototype.selectDefaultCharset=function(){this._charsetService.setgLevel(0),this._charsetService.setgCharset(0,a.DEFAULT_CHARSET)},t.prototype.selectCharset=function(e){2===e.length?"/"!==e[0]&&this._charsetService.setgCharset(b[e[0]],a.CHARSETS[e[1]]||a.DEFAULT_CHARSET):this.selectDefaultCharset()},t.prototype.index=function(){this._restrictCursor();var e=this._bufferService.buffer;this._bufferService.buffer.y++,e.y===e.scrollBottom+1?(e.y--,this._onRequestScroll.fire(this._eraseAttrData())):e.y>=this._bufferService.rows&&(e.y=this._bufferService.rows-1),this._restrictCursor()},t.prototype.tabSet=function(){this._bufferService.buffer.tabs[this._bufferService.buffer.x]=!0},t.prototype.reverseIndex=function(){this._restrictCursor();var e=this._bufferService.buffer;if(e.y===e.scrollTop){var t=e.scrollBottom-e.scrollTop;e.lines.shiftElements(e.ybase+e.y,t,1),e.lines.set(e.ybase+e.y,e.getBlankLine(this._eraseAttrData())),this._dirtyRowService.markRangeDirty(e.scrollTop,e.scrollBottom)}else e.y--,this._restrictCursor()},t.prototype.fullReset=function(){this._parser.reset(),this._onRequestReset.fire()},t.prototype.reset=function(){this._curAttrData=f.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=f.DEFAULT_ATTR_DATA.clone()},t.prototype._eraseAttrData=function(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal},t.prototype.setgLevel=function(e){this._charsetService.setgLevel(e)},t.prototype.screenAlignmentPattern=function(){var e=new p.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg;var t=this._bufferService.buffer;this._setCursor(0,0);for(var r=0;r256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}return e.fromArray=function(t){var r=new e;if(!t.length)return r;for(var i=t[0]instanceof Array?1:0;i>8,i=255&this._subParamsIdx[t];i-r>0&&e.push(Array.prototype.slice.call(this._subParams,r,i))}return e},e.prototype.reset=function(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1},e.prototype.addParam=function(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>2147483647?2147483647:e}},e.prototype.addSubParam=function(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=e>2147483647?2147483647:e,this._subParamsIdx[this.length-1]++}},e.prototype.hasSubParams=function(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0},e.prototype.getSubParams=function(e){var t=this._subParamsIdx[e]>>8,r=255&this._subParamsIdx[e];return r-t>0?this._subParams.subarray(t,r):null},e.prototype.getSubParamsAll=function(){for(var e={},t=0;t>8,i=255&this._subParamsIdx[t];i-r>0&&(e[t]=this._subParams.slice(r,i))}return e},e.prototype.addDigit=function(e){var t;if(!(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)){var r=this._digitIsSub?this._subParams:this.params,i=r[t-1];r[t-1]=~i?Math.min(10*i+e,2147483647):e}},e}();t.Params=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;var i=r(23),n=r(8),o=function(){function e(){this._state=0,this._id=-1,this._handlers=Object.create(null),this._handlerFb=function(){}}return e.prototype.addHandler=function(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);var r=this._handlers[e];return r.push(t),{dispose:function(){var e=r.indexOf(t);-1!==e&&r.splice(e,1)}}},e.prototype.setHandler=function(e,t){this._handlers[e]=[t]},e.prototype.clearHandler=function(e){this._handlers[e]&&delete this._handlers[e]},e.prototype.setHandlerFallback=function(e){this._handlerFb=e},e.prototype.dispose=function(){this._handlers=Object.create(null),this._handlerFb=function(){}},e.prototype.reset=function(){2===this._state&&this.end(!1),this._id=-1,this._state=0},e.prototype._start=function(){var e=this._handlers[this._id];if(e)for(var t=e.length-1;t>=0;t--)e[t].start();else this._handlerFb(this._id,"START")},e.prototype._put=function(e,t,r){var i=this._handlers[this._id];if(i)for(var o=i.length-1;o>=0;o--)i[o].put(e,t,r);else this._handlerFb(this._id,"PUT",n.utf32ToString(e,t,r))},e.prototype._end=function(e){var t=this._handlers[this._id];if(t){for(var r=t.length-1;r>=0&&!1===t[r].end(e);r--);for(r--;r>=0;r--)t[r].end(!1)}else this._handlerFb(this._id,"END",e)},e.prototype.start=function(){this.reset(),this._id=-1,this._state=1},e.prototype.put=function(e,t,r){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,r)}},e.prototype.end=function(e){0!==this._state&&(3!==this._state&&(1===this._state&&this._start(),this._end(e)),this._id=-1,this._state=0)},e}();t.OscParser=o;var s=function(){function e(e){this._handler=e,this._data="",this._hitLimit=!1}return e.prototype.start=function(){this._data="",this._hitLimit=!1},e.prototype.put=function(e,t,r){this._hitLimit||(this._data+=n.utf32ToString(e,t,r),this._data.length>i.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))},e.prototype.end=function(e){var t;return this._hitLimit?t=!1:e&&(t=this._handler(this._data)),this._data="",this._hitLimit=!1,t},e}();t.OscHandler=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PAYLOAD_LIMIT=void 0,t.PAYLOAD_LIMIT=1e7},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;var i=r(8),n=r(21),o=r(23),s=[],a=function(){function e(){this._handlers=Object.create(null),this._active=s,this._ident=0,this._handlerFb=function(){}}return e.prototype.dispose=function(){this._handlers=Object.create(null),this._handlerFb=function(){}},e.prototype.addHandler=function(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);var r=this._handlers[e];return r.push(t),{dispose:function(){var e=r.indexOf(t);-1!==e&&r.splice(e,1)}}},e.prototype.setHandler=function(e,t){this._handlers[e]=[t]},e.prototype.clearHandler=function(e){this._handlers[e]&&delete this._handlers[e]},e.prototype.setHandlerFallback=function(e){this._handlerFb=e},e.prototype.reset=function(){this._active.length&&this.unhook(!1),this._active=s,this._ident=0},e.prototype.hook=function(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||s,this._active.length)for(var r=this._active.length-1;r>=0;r--)this._active[r].hook(t);else this._handlerFb(this._ident,"HOOK",t)},e.prototype.put=function(e,t,r){if(this._active.length)for(var n=this._active.length-1;n>=0;n--)this._active[n].put(e,t,r);else this._handlerFb(this._ident,"PUT",i.utf32ToString(e,t,r))},e.prototype.unhook=function(e){if(this._active.length){for(var t=this._active.length-1;t>=0&&!1===this._active[t].unhook(e);t--);for(t--;t>=0;t--)this._active[t].unhook(!1)}else this._handlerFb(this._ident,"UNHOOK",e);this._active=s,this._ident=0},e}();t.DcsParser=a;var c=function(){function e(e){this._handler=e,this._data="",this._hitLimit=!1}return e.prototype.hook=function(e){this._params=e.clone(),this._data="",this._hitLimit=!1},e.prototype.put=function(e,t,r){this._hitLimit||(this._data+=i.utf32ToString(e,t,r),this._data.length>o.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))},e.prototype.unhook=function(e){var t;return this._hitLimit?t=!1:e&&(t=this._handler(this._data,this._params||new n.Params)),this._params=void 0,this._data="",this._hitLimit=!1,t},e}();t.DcsHandler=c},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.removeTerminalFromCache=t.acquireCharAtlas=void 0;var i=r(26),n=r(43),o=[];t.acquireCharAtlas=function(e,t,r,s,a){for(var c=i.generateConfig(s,a,e,r),l=0;l=0){if(i.configEquals(u.config,c))return u.atlas;1===u.ownedBy.length?(u.atlas.dispose(),o.splice(l,1)):u.ownedBy.splice(h,1);break}}for(l=0;l1)for(var u=this._getJoinedRanges(i,a,o,t,n),f=0;f1)for(u=this._getJoinedRanges(i,a,o,t,n),f=0;f=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new i.CellData)},e.prototype.translateToString=function(e,t,r){return this._line.translateToString(e,t,r)},e}(),f=function(){function e(e){this._core=e}return e.prototype.registerCsiHandler=function(e,t){return this._core.addCsiHandler(e,(function(e){return t(e.toArray())}))},e.prototype.addCsiHandler=function(e,t){return this.registerCsiHandler(e,t)},e.prototype.registerDcsHandler=function(e,t){return this._core.addDcsHandler(e,(function(e,r){return t(e,r.toArray())}))},e.prototype.addDcsHandler=function(e,t){return this.registerDcsHandler(e,t)},e.prototype.registerEscHandler=function(e,t){return this._core.addEscHandler(e,t)},e.prototype.addEscHandler=function(e,t){return this.registerEscHandler(e,t)},e.prototype.registerOscHandler=function(e,t){return this._core.addOscHandler(e,t)},e.prototype.addOscHandler=function(e,t){return this.registerOscHandler(e,t)},e}(),_=function(){function e(e){this._core=e}return e.prototype.register=function(e){this._core.unicodeService.register(e)},Object.defineProperty(e.prototype,"versions",{get:function(){return this._core.unicodeService.versions},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"activeVersion",{get:function(){return this._core.unicodeService.activeVersion},set:function(e){this._core.unicodeService.activeVersion=e},enumerable:!1,configurable:!0}),e}()},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.Terminal=void 0;var o=r(36),s=r(37),a=r(38),c=r(12),l=r(19),h=r(40),u=r(50),f=r(51),_=r(11),d=r(7),p=r(18),v=r(54),g=r(55),y=r(56),b=r(57),S=r(59),m=r(0),C=r(16),w=r(27),E=r(60),L=r(5),A=r(61),R=r(62),k=r(63),x=r(64),D=r(65),T="undefined"!=typeof window?window.document:null,O=function(e){function t(t){void 0===t&&(t={});var r=e.call(this,t)||this;return r.browser=_,r._keyDownHandled=!1,r._onCursorMove=new m.EventEmitter,r._onKey=new m.EventEmitter,r._onRender=new m.EventEmitter,r._onSelectionChange=new m.EventEmitter,r._onTitleChange=new m.EventEmitter,r._onFocus=new m.EventEmitter,r._onBlur=new m.EventEmitter,r._onA11yCharEmitter=new m.EventEmitter,r._onA11yTabEmitter=new m.EventEmitter,r._setup(),r.linkifier=r._instantiationService.createInstance(u.Linkifier),r.linkifier2=r.register(r._instantiationService.createInstance(k.Linkifier2)),r.register(r._inputHandler.onRequestBell((function(){return r.bell()}))),r.register(r._inputHandler.onRequestRefreshRows((function(e,t){return r.refresh(e,t)}))),r.register(r._inputHandler.onRequestReset((function(){return r.reset()}))),r.register(r._inputHandler.onRequestScroll((function(e,t){return r.scroll(e,t||void 0)}))),r.register(r._inputHandler.onRequestWindowsOptionsReport((function(e){return r._reportWindowsOptions(e)}))),r.register(m.forwardEvent(r._inputHandler.onCursorMove,r._onCursorMove)),r.register(m.forwardEvent(r._inputHandler.onTitleChange,r._onTitleChange)),r.register(m.forwardEvent(r._inputHandler.onA11yChar,r._onA11yCharEmitter)),r.register(m.forwardEvent(r._inputHandler.onA11yTab,r._onA11yTabEmitter)),r.register(r._bufferService.onResize((function(e){return r._afterResize(e.cols,e.rows)}))),r}return n(t,e),Object.defineProperty(t.prototype,"options",{get:function(){return this.optionsService.options},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onCursorMove",{get:function(){return this._onCursorMove.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onKey",{get:function(){return this._onKey.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRender",{get:function(){return this._onRender.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onSelectionChange",{get:function(){return this._onSelectionChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onTitleChange",{get:function(){return this._onTitleChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onFocus",{get:function(){return this._onFocus.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onBlur",{get:function(){return this._onBlur.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yChar",{get:function(){return this._onA11yCharEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onA11yTab",{get:function(){return this._onA11yTabEmitter.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){var t,r,i;this._isDisposed||(e.prototype.dispose.call(this),null===(t=this._renderService)||void 0===t||t.dispose(),this._customKeyEventHandler=void 0,this.write=function(){},null===(i=null===(r=this.element)||void 0===r?void 0:r.parentNode)||void 0===i||i.removeChild(this.element))},t.prototype._setup=function(){e.prototype._setup.call(this),this._customKeyEventHandler=void 0},Object.defineProperty(t.prototype,"buffer",{get:function(){return this.buffers.active},enumerable:!1,configurable:!0}),t.prototype.focus=function(){this.textarea&&this.textarea.focus({preventScroll:!0})},t.prototype._updateOptions=function(t){var r,i,n,o;switch(e.prototype._updateOptions.call(this,t),t){case"fontFamily":case"fontSize":null===(r=this._renderService)||void 0===r||r.clear(),null===(i=this._charSizeService)||void 0===i||i.measure();break;case"cursorBlink":case"cursorStyle":this.refresh(this.buffer.y,this.buffer.y);break;case"drawBoldTextInBrightColors":case"letterSpacing":case"lineHeight":case"fontWeight":case"fontWeightBold":case"minimumContrastRatio":this._renderService&&(this._renderService.clear(),this._renderService.onResize(this.cols,this.rows),this.refresh(0,this.rows-1));break;case"rendererType":this._renderService&&(this._renderService.setRenderer(this._createRenderer()),this._renderService.onResize(this.cols,this.rows));break;case"scrollback":null===(n=this.viewport)||void 0===n||n.syncScrollArea();break;case"screenReaderMode":this.optionsService.options.screenReaderMode?!this._accessibilityManager&&this._renderService&&(this._accessibilityManager=new y.AccessibilityManager(this,this._renderService)):(null===(o=this._accessibilityManager)||void 0===o||o.dispose(),this._accessibilityManager=void 0);break;case"tabStopWidth":this.buffers.setupTabStops();break;case"theme":this._setTheme(this.optionsService.options.theme)}},t.prototype._onTextAreaFocus=function(e){this._coreService.decPrivateModes.sendFocus&&this._coreService.triggerDataEvent(c.C0.ESC+"[I"),this.updateCursorStyle(e),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()},t.prototype.blur=function(){var e;return null===(e=this.textarea)||void 0===e?void 0:e.blur()},t.prototype._onTextAreaBlur=function(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this._coreService.decPrivateModes.sendFocus&&this._coreService.triggerDataEvent(c.C0.ESC+"[O"),this.element.classList.remove("focus"),this._onBlur.fire()},t.prototype._syncTextArea=function(){if(this.textarea&&this.buffer.isCursorInViewport&&!this._compositionHelper.isComposing){var e=Math.ceil(this._charSizeService.height*this.optionsService.options.lineHeight),t=this._bufferService.buffer.y*e,r=this._bufferService.buffer.x*this._charSizeService.width;this.textarea.style.left=r+"px",this.textarea.style.top=t+"px",this.textarea.style.width=this._charSizeService.width+"px",this.textarea.style.height=e+"px",this.textarea.style.lineHeight=e+"px",this.textarea.style.zIndex="-5"}},t.prototype._initGlobal=function(){var e=this;this._bindKeys(),this.register(d.addDisposableDomListener(this.element,"copy",(function(t){e.hasSelection()&&a.copyHandler(t,e._selectionService)})));var t=function(t){return a.handlePasteEvent(t,e.textarea,e._coreService)};this.register(d.addDisposableDomListener(this.textarea,"paste",t)),this.register(d.addDisposableDomListener(this.element,"paste",t)),_.isFirefox?this.register(d.addDisposableDomListener(this.element,"mousedown",(function(t){2===t.button&&a.rightClickHandler(t,e.textarea,e.screenElement,e._selectionService,e.options.rightClickSelectsWord)}))):this.register(d.addDisposableDomListener(this.element,"contextmenu",(function(t){a.rightClickHandler(t,e.textarea,e.screenElement,e._selectionService,e.options.rightClickSelectsWord)}))),_.isLinux&&this.register(d.addDisposableDomListener(this.element,"auxclick",(function(t){1===t.button&&a.moveTextAreaUnderMouseCursor(t,e.textarea,e.screenElement)})))},t.prototype._bindKeys=function(){var e=this;this.register(d.addDisposableDomListener(this.textarea,"keyup",(function(t){return e._keyUp(t)}),!0)),this.register(d.addDisposableDomListener(this.textarea,"keydown",(function(t){return e._keyDown(t)}),!0)),this.register(d.addDisposableDomListener(this.textarea,"keypress",(function(t){return e._keyPress(t)}),!0)),this.register(d.addDisposableDomListener(this.textarea,"compositionstart",(function(){return e._compositionHelper.compositionstart()}))),this.register(d.addDisposableDomListener(this.textarea,"compositionupdate",(function(t){return e._compositionHelper.compositionupdate(t)}))),this.register(d.addDisposableDomListener(this.textarea,"compositionend",(function(){return e._compositionHelper.compositionend()}))),this.register(this.onRender((function(){return e._compositionHelper.updateCompositionElements()}))),this.register(this.onRender((function(t){return e._queueLinkification(t.start,t.end)})))},t.prototype.open=function(e){var t=this;if(!e)throw new Error("Terminal requires a parent element.");T.body.contains(e)||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this._document=e.ownerDocument,this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.setAttribute("tabindex","0"),e.appendChild(this.element);var r=T.createDocumentFragment();this._viewportElement=T.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),r.appendChild(this._viewportElement),this._viewportScrollArea=T.createElement("div"),this._viewportScrollArea.classList.add("xterm-scroll-area"),this._viewportElement.appendChild(this._viewportScrollArea),this.screenElement=T.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._helperContainer=T.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),r.appendChild(this.screenElement),this.textarea=T.createElement("textarea"),this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",p.promptLabel),this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this.register(d.addDisposableDomListener(this.textarea,"focus",(function(e){return t._onTextAreaFocus(e)}))),this.register(d.addDisposableDomListener(this.textarea,"blur",(function(){return t._onTextAreaBlur()}))),this._helperContainer.appendChild(this.textarea);var i=this._instantiationService.createInstance(x.CoreBrowserService,this.textarea);this._instantiationService.setService(L.ICoreBrowserService,i),this._charSizeService=this._instantiationService.createInstance(A.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(L.ICharSizeService,this._charSizeService),this._compositionView=T.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(o.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this.element.appendChild(r),this._theme=this.options.theme||this._theme,this._colorManager=new w.ColorManager(T,this.options.allowTransparency),this.register(this.optionsService.onOptionChange((function(e){return t._colorManager.onOptionsChange(e)}))),this._colorManager.setTheme(this._theme);var n=this._createRenderer();this._renderService=this.register(this._instantiationService.createInstance(E.RenderService,n,this.rows,this.screenElement)),this._instantiationService.setService(L.IRenderService,this._renderService),this.register(this._renderService.onRenderedBufferChange((function(e){return t._onRender.fire(e)}))),this.onResize((function(e){return t._renderService.resize(e.cols,e.rows)})),this._soundService=this._instantiationService.createInstance(v.SoundService),this._instantiationService.setService(L.ISoundService,this._soundService),this._mouseService=this._instantiationService.createInstance(R.MouseService),this._instantiationService.setService(L.IMouseService,this._mouseService),this.viewport=this._instantiationService.createInstance(s.Viewport,(function(e,r){return t.scrollLines(e,r)}),this._viewportElement,this._viewportScrollArea),this.viewport.onThemeChange(this._colorManager.colors),this.register(this._inputHandler.onRequestSyncScrollBar((function(){return t.viewport.syncScrollArea()}))),this.register(this.viewport),this.register(this.onCursorMove((function(){t._renderService.onCursorMove(),t._syncTextArea()}))),this.register(this.onResize((function(){return t._renderService.onResize(t.cols,t.rows)}))),this.register(this.onBlur((function(){return t._renderService.onBlur()}))),this.register(this.onFocus((function(){return t._renderService.onFocus()}))),this.register(this._renderService.onDimensionsChange((function(){return t.viewport.syncScrollArea()}))),this._selectionService=this.register(this._instantiationService.createInstance(f.SelectionService,this.element,this.screenElement)),this._instantiationService.setService(L.ISelectionService,this._selectionService),this.register(this._selectionService.onRequestScrollLines((function(e){return t.scrollLines(e.amount,e.suppressScrollEvent)}))),this.register(this._selectionService.onSelectionChange((function(){return t._onSelectionChange.fire()}))),this.register(this._selectionService.onRequestRedraw((function(e){return t._renderService.onSelectionChanged(e.start,e.end,e.columnSelectMode)}))),this.register(this._selectionService.onLinuxMouseSelection((function(e){t.textarea.value=e,t.textarea.focus(),t.textarea.select()}))),this.register(this.onScroll((function(){t.viewport.syncScrollArea(),t._selectionService.refresh()}))),this.register(d.addDisposableDomListener(this._viewportElement,"scroll",(function(){return t._selectionService.refresh()}))),this._mouseZoneManager=this._instantiationService.createInstance(g.MouseZoneManager,this.element,this.screenElement),this.register(this._mouseZoneManager),this.register(this.onScroll((function(){return t._mouseZoneManager.clearAll()}))),this.linkifier.attachToDom(this.element,this._mouseZoneManager),this.linkifier2.attachToDom(this.element,this._mouseService,this._renderService),this.register(d.addDisposableDomListener(this.element,"mousedown",(function(e){return t._selectionService.onMouseDown(e)}))),this._coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager=new y.AccessibilityManager(this,this._renderService)),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()},t.prototype._createRenderer=function(){switch(this.options.rendererType){case"canvas":return this._instantiationService.createInstance(h.Renderer,this._colorManager.colors,this.screenElement,this.linkifier,this.linkifier2);case"dom":return this._instantiationService.createInstance(b.DomRenderer,this._colorManager.colors,this.element,this.screenElement,this._viewportElement,this.linkifier,this.linkifier2);default:throw new Error('Unrecognized rendererType "'+this.options.rendererType+'"')}},t.prototype._setTheme=function(e){var t,r,i;this._theme=e,null===(t=this._colorManager)||void 0===t||t.setTheme(e),null===(r=this._renderService)||void 0===r||r.setColors(this._colorManager.colors),null===(i=this.viewport)||void 0===i||i.onThemeChange(this._colorManager.colors)},t.prototype.bindMouse=function(){var e=this,t=this,r=this.element;function i(e){var r,i,n=t._mouseService.getRawByteCoords(e,t.screenElement,t.cols,t.rows);if(!n)return!1;switch(e.overrideType||e.type){case"mousemove":i=32,void 0===e.buttons?(r=3,void 0!==e.button&&(r=e.button<3?e.button:3)):r=1&e.buttons?0:4&e.buttons?1:2&e.buttons?2:3;break;case"mouseup":i=0,r=e.button<3?e.button:3;break;case"mousedown":i=1,r=e.button<3?e.button:3;break;case"wheel":0!==e.deltaY&&(i=e.deltaY<0?0:1),r=4;break;default:return!1}return!(void 0===i||void 0===r||r>4)&&t._coreMouseService.triggerMouseEvent({col:n.x-33,row:n.y-33,button:r,action:i,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey})}var n={mouseup:null,wheel:null,mousedrag:null,mousemove:null},o=function(t){return i(t),t.buttons||(e._document.removeEventListener("mouseup",n.mouseup),n.mousedrag&&e._document.removeEventListener("mousemove",n.mousedrag)),e.cancel(t)},s=function(t){return i(t),t.preventDefault(),e.cancel(t)},a=function(e){e.buttons&&i(e)},l=function(e){e.buttons||i(e)};this.register(this._coreMouseService.onProtocolChange((function(t){t?("debug"===e.optionsService.options.logLevel&&e._logService.debug("Binding to mouse events:",e._coreMouseService.explainEvents(t)),e.element.classList.add("enable-mouse-events"),e._selectionService.disable()):(e._logService.debug("Unbinding from mouse events."),e.element.classList.remove("enable-mouse-events"),e._selectionService.enable()),8&t?n.mousemove||(r.addEventListener("mousemove",l),n.mousemove=l):(r.removeEventListener("mousemove",n.mousemove),n.mousemove=null),16&t?n.wheel||(r.addEventListener("wheel",s,{passive:!1}),n.wheel=s):(r.removeEventListener("wheel",n.wheel),n.wheel=null),2&t?n.mouseup||(n.mouseup=o):(e._document.removeEventListener("mouseup",n.mouseup),n.mouseup=null),4&t?n.mousedrag||(n.mousedrag=a):(e._document.removeEventListener("mousemove",n.mousedrag),n.mousedrag=null)}))),this._coreMouseService.activeProtocol=this._coreMouseService.activeProtocol,this.register(d.addDisposableDomListener(r,"mousedown",(function(t){if(t.preventDefault(),e.focus(),e._coreMouseService.areMouseEventsActive&&!e._selectionService.shouldForceSelection(t))return i(t),n.mouseup&&e._document.addEventListener("mouseup",n.mouseup),n.mousedrag&&e._document.addEventListener("mousemove",n.mousedrag),e.cancel(t)}))),this.register(d.addDisposableDomListener(r,"wheel",(function(t){if(n.wheel);else if(!e.buffer.hasScrollback){var r=e.viewport.getLinesScrolled(t);if(0===r)return;for(var i=c.C0.ESC+(e._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t.deltaY<0?"A":"B"),o="",s=0;s47)},t.prototype._keyUp=function(e){this._customKeyEventHandler&&!1===this._customKeyEventHandler(e)||(function(e){return 16===e.keyCode||17===e.keyCode||18===e.keyCode}(e)||this.focus(),this.updateCursorStyle(e))},t.prototype._keyPress=function(e){var t;if(this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(this.cancel(e),e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e))&&(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this._coreService.triggerDataEvent(t,!0),!0)},t.prototype.bell=function(){this._soundBell()&&this._soundService.playBellSound()},t.prototype.resize=function(t,r){t!==this.cols||r!==this.rows?e.prototype.resize.call(this,t,r):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()},t.prototype._afterResize=function(e,t){var r,i;null===(r=this._charSizeService)||void 0===r||r.measure(),null===(i=this.viewport)||void 0===i||i.syncScrollArea(!0)},t.prototype.clear=function(){if(0!==this.buffer.ybase||0!==this.buffer.y){this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(var e=1;e=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;var o=r(5),s=r(1),a=function(){function e(e,t,r,i,n,o){this._textarea=e,this._compositionView=t,this._bufferService=r,this._optionsService=i,this._charSizeService=n,this._coreService=o,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0}}return Object.defineProperty(e.prototype,"isComposing",{get:function(){return this._isComposing},enumerable:!1,configurable:!0}),e.prototype.compositionstart=function(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._compositionView.classList.add("active")},e.prototype.compositionupdate=function(e){var t=this;this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout((function(){t._compositionPosition.end=t._textarea.value.length}),0)},e.prototype.compositionend=function(){this._finalizeComposition(!0)},e.prototype.keydown=function(e){if(this._isComposing||this._isSendingComposition){if(229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)},e.prototype._finalizeComposition=function(e){var t=this;if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){var r={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout((function(){if(t._isSendingComposition){t._isSendingComposition=!1;var e=void 0;e=t._isComposing?t._textarea.value.substring(r.start,r.end):t._textarea.value.substring(r.start),t._coreService.triggerDataEvent(e,!0)}}),0)}else{this._isSendingComposition=!1;var i=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(i,!0)}},e.prototype._handleAnyTextareaChanges=function(){var e=this,t=this._textarea.value;setTimeout((function(){if(!e._isComposing){var r=e._textarea.value.replace(t,"");r.length>0&&e._coreService.triggerDataEvent(r,!0)}}),0)},e.prototype.updateCompositionElements=function(e){var t=this;if(this._isComposing){if(this._bufferService.buffer.isCursorInViewport){var r=Math.ceil(this._charSizeService.height*this._optionsService.options.lineHeight),i=this._bufferService.buffer.y*r,n=this._bufferService.buffer.x*this._charSizeService.width;this._compositionView.style.left=n+"px",this._compositionView.style.top=i+"px",this._compositionView.style.height=r+"px",this._compositionView.style.lineHeight=r+"px",this._compositionView.style.fontFamily=this._optionsService.options.fontFamily,this._compositionView.style.fontSize=this._optionsService.options.fontSize+"px";var o=this._compositionView.getBoundingClientRect();this._textarea.style.left=n+"px",this._textarea.style.top=i+"px",this._textarea.style.width=o.width+"px",this._textarea.style.height=o.height+"px",this._textarea.style.lineHeight=o.height+"px"}e||setTimeout((function(){return t.updateCompositionElements(!0)}),0)}},e=i([n(2,s.IBufferService),n(3,s.IOptionsService),n(4,o.ICharSizeService),n(5,s.ICoreService)],e)}();t.CompositionHelper=a},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;var a=r(2),c=r(7),l=r(5),h=r(1),u=function(e){function t(t,r,i,n,o,s,a){var l=e.call(this)||this;return l._scrollLines=t,l._viewportElement=r,l._scrollArea=i,l._bufferService=n,l._optionsService=o,l._charSizeService=s,l._renderService=a,l.scrollBarWidth=0,l._currentRowHeight=0,l._lastRecordedBufferLength=0,l._lastRecordedViewportHeight=0,l._lastRecordedBufferHeight=0,l._lastTouchY=0,l._lastScrollTop=0,l._wheelPartialScroll=0,l._refreshAnimationFrame=null,l._ignoreNextScrollEvent=!1,l.scrollBarWidth=l._viewportElement.offsetWidth-l._scrollArea.offsetWidth||15,l.register(c.addDisposableDomListener(l._viewportElement,"scroll",l._onScroll.bind(l))),setTimeout((function(){return l.syncScrollArea()}),0),l}return n(t,e),t.prototype.onThemeChange=function(e){this._viewportElement.style.backgroundColor=e.background.css},t.prototype._refresh=function(e){var t=this;if(e)return this._innerRefresh(),void(null!==this._refreshAnimationFrame&&cancelAnimationFrame(this._refreshAnimationFrame));null===this._refreshAnimationFrame&&(this._refreshAnimationFrame=requestAnimationFrame((function(){return t._innerRefresh()})))},t.prototype._innerRefresh=function(){if(this._charSizeService.height>0){this._currentRowHeight=this._renderService.dimensions.scaledCellHeight/window.devicePixelRatio,this._lastRecordedViewportHeight=this._viewportElement.offsetHeight;var e=Math.round(this._currentRowHeight*this._lastRecordedBufferLength)+(this._lastRecordedViewportHeight-this._renderService.dimensions.canvasHeight);this._lastRecordedBufferHeight!==e&&(this._lastRecordedBufferHeight=e,this._scrollArea.style.height=this._lastRecordedBufferHeight+"px")}var t=this._bufferService.buffer.ydisp*this._currentRowHeight;this._viewportElement.scrollTop!==t&&(this._ignoreNextScrollEvent=!0,this._viewportElement.scrollTop=t),this._refreshAnimationFrame=null},t.prototype.syncScrollArea=function(e){if(void 0===e&&(e=!1),this._lastRecordedBufferLength!==this._bufferService.buffer.lines.length)return this._lastRecordedBufferLength=this._bufferService.buffer.lines.length,void this._refresh(e);if(this._lastRecordedViewportHeight===this._renderService.dimensions.canvasHeight){var t=this._bufferService.buffer.ydisp*this._currentRowHeight;this._lastScrollTop===t&&this._lastScrollTop===this._viewportElement.scrollTop&&this._renderService.dimensions.scaledCellHeight/window.devicePixelRatio===this._currentRowHeight||this._refresh(e)}else this._refresh(e)},t.prototype._onScroll=function(e){if(this._lastScrollTop=this._viewportElement.scrollTop,this._viewportElement.offsetParent)if(this._ignoreNextScrollEvent)this._ignoreNextScrollEvent=!1;else{var t=Math.round(this._lastScrollTop/this._currentRowHeight)-this._bufferService.buffer.ydisp;this._scrollLines(t,!0)}},t.prototype._bubbleScroll=function(e,t){var r=this._viewportElement.scrollTop+this._lastRecordedViewportHeight;return!(t<0&&0!==this._viewportElement.scrollTop||t>0&&r0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(t*=this._bufferService.rows),t},t.prototype._applyScrollModifier=function(e,t){var r=this._optionsService.options.fastScrollModifier;return"alt"===r&&t.altKey||"ctrl"===r&&t.ctrlKey||"shift"===r&&t.shiftKey?e*this._optionsService.options.fastScrollSensitivity*this._optionsService.options.scrollSensitivity:e*this._optionsService.options.scrollSensitivity},t.prototype.onTouchStart=function(e){this._lastTouchY=e.touches[0].pageY},t.prototype.onTouchMove=function(e){var t=this._lastTouchY-e.touches[0].pageY;return this._lastTouchY=e.touches[0].pageY,0!==t&&(this._viewportElement.scrollTop+=t,this._bubbleScroll(e,t))},t=o([s(3,h.IBufferService),s(4,h.IOptionsService),s(5,l.ICharSizeService),s(6,l.IRenderService)],t)}(a.Disposable);t.Viewport=u},function(e,t,r){"use strict";function i(e){return e.replace(/\r?\n/g,"\r")}function n(e,t){return t?"[200~"+e+"[201~":e}function o(e,t,r){e=n(e=i(e),r.decPrivateModes.bracketedPasteMode),r.triggerDataEvent(e,!0),t.value=""}function s(e,t,r){var i=r.getBoundingClientRect(),n=e.clientX-i.left-10,o=e.clientY-i.top-10;t.style.width="20px",t.style.height="20px",t.style.left=n+"px",t.style.top=o+"px",t.style.zIndex="1000",t.focus()}Object.defineProperty(t,"__esModule",{value:!0}),t.rightClickHandler=t.moveTextAreaUnderMouseCursor=t.paste=t.handlePasteEvent=t.copyHandler=t.bracketTextForPaste=t.prepareTextForTerminal=void 0,t.prepareTextForTerminal=i,t.bracketTextForPaste=n,t.copyHandler=function(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()},t.handlePasteEvent=function(e,t,r){e.stopPropagation(),e.clipboardData&&o(e.clipboardData.getData("text/plain"),t,r)},t.paste=o,t.moveTextAreaUnderMouseCursor=s,t.rightClickHandler=function(e,t,r,i,n){s(e,t,r),n&&!i.isClickInSelection(e)&&i.selectWordAtCursor(e),t.value=i.selectionText,t.select()}},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;var o=r(2),s=r(15),a=r(21),c=r(22),l=r(24),h=function(){function e(e){this.table=new Uint8Array(e)}return e.prototype.setDefault=function(e,t){s.fill(this.table,e<<4|t)},e.prototype.add=function(e,t,r,i){this.table[t<<8|e]=r<<4|i},e.prototype.addMany=function(e,t,r,i){for(var n=0;n1)throw new Error("only one byte as prefix supported");if((r=e.prefix.charCodeAt(0))&&60>r||r>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(var i=0;in||n>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");r<<=8,r|=n}}if(1!==e.final.length)throw new Error("final must be a single byte");var o=e.final.charCodeAt(0);if(t[0]>o||o>t[1])throw new Error("final must be in range "+t[0]+" .. "+t[1]);return r<<=8,r|=o},r.prototype.identToString=function(e){for(var t=[];e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")},r.prototype.dispose=function(){this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null),this._oscParser.dispose(),this._dcsParser.dispose()},r.prototype.setPrintHandler=function(e){this._printHandler=e},r.prototype.clearPrintHandler=function(){this._printHandler=this._printHandlerFb},r.prototype.addEscHandler=function(e,t){var r=this._identifier(e,[48,126]);void 0===this._escHandlers[r]&&(this._escHandlers[r]=[]);var i=this._escHandlers[r];return i.push(t),{dispose:function(){var e=i.indexOf(t);-1!==e&&i.splice(e,1)}}},r.prototype.setEscHandler=function(e,t){this._escHandlers[this._identifier(e,[48,126])]=[t]},r.prototype.clearEscHandler=function(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]},r.prototype.setEscHandlerFallback=function(e){this._escHandlerFb=e},r.prototype.setExecuteHandler=function(e,t){this._executeHandlers[e.charCodeAt(0)]=t},r.prototype.clearExecuteHandler=function(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]},r.prototype.setExecuteHandlerFallback=function(e){this._executeHandlerFb=e},r.prototype.addCsiHandler=function(e,t){var r=this._identifier(e);void 0===this._csiHandlers[r]&&(this._csiHandlers[r]=[]);var i=this._csiHandlers[r];return i.push(t),{dispose:function(){var e=i.indexOf(t);-1!==e&&i.splice(e,1)}}},r.prototype.setCsiHandler=function(e,t){this._csiHandlers[this._identifier(e)]=[t]},r.prototype.clearCsiHandler=function(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]},r.prototype.setCsiHandlerFallback=function(e){this._csiHandlerFb=e},r.prototype.addDcsHandler=function(e,t){return this._dcsParser.addHandler(this._identifier(e),t)},r.prototype.setDcsHandler=function(e,t){this._dcsParser.setHandler(this._identifier(e),t)},r.prototype.clearDcsHandler=function(e){this._dcsParser.clearHandler(this._identifier(e))},r.prototype.setDcsHandlerFallback=function(e){this._dcsParser.setHandlerFallback(e)},r.prototype.addOscHandler=function(e,t){return this._oscParser.addHandler(e,t)},r.prototype.setOscHandler=function(e,t){this._oscParser.setHandler(e,t)},r.prototype.clearOscHandler=function(e){this._oscParser.clearHandler(e)},r.prototype.setOscHandlerFallback=function(e){this._oscParser.setHandlerFallback(e)},r.prototype.setErrorHandler=function(e){this._errorHandler=e},r.prototype.clearErrorHandler=function(){this._errorHandler=this._errorHandlerFb},r.prototype.reset=function(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingCodepoint=0},r.prototype.parse=function(e,t){for(var r=0,i=0,n=this.currentState,o=this._oscParser,s=this._dcsParser,a=this._collect,c=this._params,l=this._transitions.table,h=0;h>4){case 2:for(var u=h+1;;++u){if(u>=t||(r=e[u])<32||r>126&&r<160){this._printHandler(e,h,u),h=u-1;break}if(++u>=t||(r=e[u])<32||r>126&&r<160){this._printHandler(e,h,u),h=u-1;break}if(++u>=t||(r=e[u])<32||r>126&&r<160){this._printHandler(e,h,u),h=u-1;break}if(++u>=t||(r=e[u])<32||r>126&&r<160){this._printHandler(e,h,u),h=u-1;break}}break;case 3:this._executeHandlers[r]?this._executeHandlers[r]():this._executeHandlerFb(r),this.precedingCodepoint=0;break;case 0:break;case 1:if(this._errorHandler({position:h,code:r,currentState:n,collect:a,params:c,abort:!1}).abort)return;break;case 7:for(var f=this._csiHandlers[a<<8|r],_=f?f.length-1:-1;_>=0&&!1===f[_](c);_--);_<0&&this._csiHandlerFb(a<<8|r,c),this.precedingCodepoint=0;break;case 8:do{switch(r){case 59:c.addParam(0);break;case 58:c.addSubParam(-1);break;default:c.addDigit(r-48)}}while(++h47&&r<60);h--;break;case 9:a<<=8,a|=r;break;case 10:for(var d=this._escHandlers[a<<8|r],p=d?d.length-1:-1;p>=0&&!1===d[p]();p--);p<0&&this._escHandlerFb(a<<8|r),this.precedingCodepoint=0;break;case 11:c.reset(),c.addParam(0),a=0;break;case 12:s.hook(a<<8|r,c);break;case 13:for(var v=h+1;;++v)if(v>=t||24===(r=e[v])||26===r||27===r||r>127&&r<160){s.put(e,h,v),h=v-1;break}break;case 14:s.unhook(24!==r&&26!==r),27===r&&(i|=1),c.reset(),c.addParam(0),a=0,this.precedingCodepoint=0;break;case 4:o.start();break;case 5:for(var g=h+1;;g++)if(g>=t||(r=e[g])<32||r>127&&r<=159){o.put(e,h,g),h=g-1;break}break;case 6:o.end(24!==r&&26!==r),27===r&&(i|=1),c.reset(),c.addParam(0),a=0,this.precedingCodepoint=0}n=15&i}this._collect=a,this.currentState=n},r}(o.Disposable);t.EscapeSequenceParser=u},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Renderer=void 0;var a=r(41),c=r(47),l=r(48),h=r(49),u=r(29),f=r(2),_=r(5),d=r(1),p=r(25),v=r(0),g=1,y=function(e){function t(t,r,i,n,o,s,f,_,d){var p=e.call(this)||this;p._colors=t,p._screenElement=r,p._bufferService=o,p._charSizeService=s,p._optionsService=f,p._id=g++,p._onRequestRedraw=new v.EventEmitter;var y=p._optionsService.options.allowTransparency;return p._characterJoinerRegistry=new u.CharacterJoinerRegistry(p._bufferService),p._renderLayers=[new a.TextRenderLayer(p._screenElement,0,p._colors,p._characterJoinerRegistry,y,p._id,p._bufferService,f),new c.SelectionRenderLayer(p._screenElement,1,p._colors,p._id,p._bufferService,f),new h.LinkRenderLayer(p._screenElement,2,p._colors,p._id,i,n,p._bufferService,f),new l.CursorRenderLayer(p._screenElement,3,p._colors,p._id,p._onRequestRedraw,p._bufferService,f,_,d)],p.dimensions={scaledCharWidth:0,scaledCharHeight:0,scaledCellWidth:0,scaledCellHeight:0,scaledCharLeft:0,scaledCharTop:0,scaledCanvasWidth:0,scaledCanvasHeight:0,canvasWidth:0,canvasHeight:0,actualCellWidth:0,actualCellHeight:0},p._devicePixelRatio=window.devicePixelRatio,p._updateDimensions(),p.onOptionsChanged(),p}return n(t,e),Object.defineProperty(t.prototype,"onRequestRedraw",{get:function(){return this._onRequestRedraw.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){for(var t=0,r=this._renderLayers;t0&&h===a[0][0]){f=!0;var d=a.shift();u=new l.JoinedCellData(this._workCell,s.translateToString(!0,d[0],d[1]),d[1]-d[0]),_=d[1]-1}!f&&this._isOverlapping(u)&&_this._characterWidth;return this._ctx.restore(),this._characterOverlapCache[t]=r,r},t}(s.BaseRenderLayer);t.TextRenderLayer=u},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.GridCache=void 0;var i=function(){function e(){this.cache=[]}return e.prototype.resize=function(e,t){for(var r=0;r>>24,n=t.rgba>>>16&255,o=t.rgba>>>8&255,s=0;s=this.capacity)r=this._head,this._unlinkNode(r),delete this._map[r.key],r.key=e,r.value=t,this._map[e]=r;else{var i=this._nodePool;i.length>0?((r=i.pop()).key=e,r.value=t):r={prev:null,next:null,key:e,value:t},this._map[e]=r,this.size++}this._appendNode(r)},e}();t.LRUMap=i},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionRenderLayer=void 0;var o=function(e){function t(t,r,i,n,o,s){var a=e.call(this,t,"selection",r,!0,i,n,o,s)||this;return a._clearState(),a}return n(t,e),t.prototype._clearState=function(){this._state={start:void 0,end:void 0,columnSelectMode:void 0,ydisp:void 0}},t.prototype.resize=function(t){e.prototype.resize.call(this,t),this._clearState()},t.prototype.reset=function(){this._state.start&&this._state.end&&(this._clearState(),this._clearAll())},t.prototype.onSelectionChanged=function(e,t,r){if(this._didStateChange(e,t,r,this._bufferService.buffer.ydisp))if(this._clearAll(),e&&t){var i=e[1]-this._bufferService.buffer.ydisp,n=t[1]-this._bufferService.buffer.ydisp,o=Math.max(i,0),s=Math.min(n,this._bufferService.rows-1);if(!(o>=this._bufferService.rows||s<0)){if(this._ctx.fillStyle=this._colors.selectionTransparent.css,r){var a=e[0],c=t[0]-a,l=s-o+1;this._fillCells(a,o,c,l)}else{a=i===o?e[0]:0;var h=o===s?t[0]:this._bufferService.cols;this._fillCells(a,o,h-a,1);var u=Math.max(s-o-1,0);if(this._fillCells(0,o+1,this._bufferService.cols,u),o!==s){var f=n===s?t[0]:this._bufferService.cols;this._fillCells(0,s,f,1)}}this._state.start=[e[0],e[1]],this._state.end=[t[0],t[1]],this._state.columnSelectMode=r,this._state.ydisp=this._bufferService.buffer.ydisp}}else this._clearState()},t.prototype._didStateChange=function(e,t,r,i){return!this._areCoordinatesEqual(e,this._state.start)||!this._areCoordinatesEqual(t,this._state.end)||r!==this._state.columnSelectMode||i!==this._state.ydisp},t.prototype._areCoordinatesEqual=function(e,t){return!(!e||!t)&&(e[0]===t[0]&&e[1]===t[1])},t}(r(13).BaseRenderLayer);t.SelectionRenderLayer=o},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.CursorRenderLayer=void 0;var o=r(13),s=r(4),a=function(e){function t(t,r,i,n,o,a,c,l,h){var u=e.call(this,t,"cursor",r,!0,i,n,a,c)||this;return u._onRequestRedraw=o,u._coreService=l,u._coreBrowserService=h,u._cell=new s.CellData,u._state={x:0,y:0,isFocused:!1,style:"",width:0},u._cursorRenderers={bar:u._renderBarCursor.bind(u),block:u._renderBlockCursor.bind(u),underline:u._renderUnderlineCursor.bind(u)},u}return n(t,e),t.prototype.resize=function(t){e.prototype.resize.call(this,t),this._state={x:0,y:0,isFocused:!1,style:"",width:0}},t.prototype.reset=function(){this._clearCursor(),this._cursorBlinkStateManager&&(this._cursorBlinkStateManager.dispose(),this._cursorBlinkStateManager=void 0,this.onOptionsChanged())},t.prototype.onBlur=function(){this._cursorBlinkStateManager&&this._cursorBlinkStateManager.pause(),this._onRequestRedraw.fire({start:this._bufferService.buffer.y,end:this._bufferService.buffer.y})},t.prototype.onFocus=function(){this._cursorBlinkStateManager?this._cursorBlinkStateManager.resume():this._onRequestRedraw.fire({start:this._bufferService.buffer.y,end:this._bufferService.buffer.y})},t.prototype.onOptionsChanged=function(){var e,t=this;this._optionsService.options.cursorBlink?this._cursorBlinkStateManager||(this._cursorBlinkStateManager=new c(this._coreBrowserService.isFocused,(function(){t._render(!0)}))):(null===(e=this._cursorBlinkStateManager)||void 0===e||e.dispose(),this._cursorBlinkStateManager=void 0),this._onRequestRedraw.fire({start:this._bufferService.buffer.y,end:this._bufferService.buffer.y})},t.prototype.onCursorMove=function(){this._cursorBlinkStateManager&&this._cursorBlinkStateManager.restartBlinkAnimation()},t.prototype.onGridChanged=function(e,t){!this._cursorBlinkStateManager||this._cursorBlinkStateManager.isPaused?this._render(!1):this._cursorBlinkStateManager.restartBlinkAnimation()},t.prototype._render=function(e){if(this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden){var t=this._bufferService.buffer.ybase+this._bufferService.buffer.y,r=t-this._bufferService.buffer.ydisp;if(r<0||r>=this._bufferService.rows)this._clearCursor();else{var i=Math.min(this._bufferService.buffer.x,this._bufferService.cols-1);if(this._bufferService.buffer.lines.get(t).loadCell(i,this._cell),void 0!==this._cell.content){if(!this._coreBrowserService.isFocused){this._clearCursor(),this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css;var n=this._optionsService.options.cursorStyle;return n&&"block"!==n?this._cursorRenderers[n](i,r,this._cell):this._renderBlurCursor(i,r,this._cell),this._ctx.restore(),this._state.x=i,this._state.y=r,this._state.isFocused=!1,this._state.style=n,void(this._state.width=this._cell.getWidth())}if(!this._cursorBlinkStateManager||this._cursorBlinkStateManager.isCursorVisible){if(this._state){if(this._state.x===i&&this._state.y===r&&this._state.isFocused===this._coreBrowserService.isFocused&&this._state.style===this._optionsService.options.cursorStyle&&this._state.width===this._cell.getWidth())return;this._clearCursor()}this._ctx.save(),this._cursorRenderers[this._optionsService.options.cursorStyle||"block"](i,r,this._cell),this._ctx.restore(),this._state.x=i,this._state.y=r,this._state.isFocused=!1,this._state.style=this._optionsService.options.cursorStyle,this._state.width=this._cell.getWidth()}else this._clearCursor()}}}else this._clearCursor()},t.prototype._clearCursor=function(){this._state&&(this._clearCells(this._state.x,this._state.y,this._state.width,1),this._state={x:0,y:0,isFocused:!1,style:"",width:0})},t.prototype._renderBarCursor=function(e,t,r){this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css,this._fillLeftLineAtCell(e,t,this._optionsService.options.cursorWidth),this._ctx.restore()},t.prototype._renderBlockCursor=function(e,t,r){this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css,this._fillCells(e,t,r.getWidth(),1),this._ctx.fillStyle=this._colors.cursorAccent.css,this._fillCharTrueColor(r,e,t),this._ctx.restore()},t.prototype._renderUnderlineCursor=function(e,t,r){this._ctx.save(),this._ctx.fillStyle=this._colors.cursor.css,this._fillBottomLineAtCells(e,t),this._ctx.restore()},t.prototype._renderBlurCursor=function(e,t,r){this._ctx.save(),this._ctx.strokeStyle=this._colors.cursor.css,this._strokeRectAtCell(e,t,r.getWidth(),1),this._ctx.restore()},t}(o.BaseRenderLayer);t.CursorRenderLayer=a;var c=function(){function e(e,t){this._renderCallback=t,this.isCursorVisible=!0,e&&this._restartInterval()}return Object.defineProperty(e.prototype,"isPaused",{get:function(){return!(this._blinkStartTimeout||this._blinkInterval)},enumerable:!1,configurable:!0}),e.prototype.dispose=function(){this._blinkInterval&&(window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)},e.prototype.restartBlinkAnimation=function(){var e=this;this.isPaused||(this._animationTimeRestarted=Date.now(),this.isCursorVisible=!0,this._animationFrame||(this._animationFrame=window.requestAnimationFrame((function(){e._renderCallback(),e._animationFrame=void 0}))))},e.prototype._restartInterval=function(e){var t=this;void 0===e&&(e=600),this._blinkInterval&&window.clearInterval(this._blinkInterval),this._blinkStartTimeout=window.setTimeout((function(){if(t._animationTimeRestarted){var e=600-(Date.now()-t._animationTimeRestarted);if(t._animationTimeRestarted=void 0,e>0)return void t._restartInterval(e)}t.isCursorVisible=!1,t._animationFrame=window.requestAnimationFrame((function(){t._renderCallback(),t._animationFrame=void 0})),t._blinkInterval=window.setInterval((function(){if(t._animationTimeRestarted){var e=600-(Date.now()-t._animationTimeRestarted);return t._animationTimeRestarted=void 0,void t._restartInterval(e)}t.isCursorVisible=!t.isCursorVisible,t._animationFrame=window.requestAnimationFrame((function(){t._renderCallback(),t._animationFrame=void 0}))}),600)}),e)},e.prototype.pause=function(){this.isCursorVisible=!0,this._blinkInterval&&(window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)},e.prototype.resume=function(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this.restartBlinkAnimation()},e}()},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.LinkRenderLayer=void 0;var o=r(13),s=r(9),a=r(26),c=function(e){function t(t,r,i,n,o,s,a,c){var l=e.call(this,t,"link",r,!0,i,n,a,c)||this;return o.onShowLinkUnderline((function(e){return l._onShowLinkUnderline(e)})),o.onHideLinkUnderline((function(e){return l._onHideLinkUnderline(e)})),s.onShowLinkUnderline((function(e){return l._onShowLinkUnderline(e)})),s.onHideLinkUnderline((function(e){return l._onHideLinkUnderline(e)})),l}return n(t,e),t.prototype.resize=function(t){e.prototype.resize.call(this,t),this._state=void 0},t.prototype.reset=function(){this._clearCurrentLink()},t.prototype._clearCurrentLink=function(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);var e=this._state.y2-this._state.y1-1;e>0&&this._clearCells(0,this._state.y1+1,this._state.cols,e),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}},t.prototype._onShowLinkUnderline=function(e){if(e.fg===s.INVERTED_DEFAULT_COLOR?this._ctx.fillStyle=this._colors.background.css:e.fg&&a.is256Color(e.fg)?this._ctx.fillStyle=this._colors.ansi[e.fg].css:this._ctx.fillStyle=this._colors.foreground.css,e.y1===e.y2)this._fillBottomLineAtCells(e.x1,e.y1,e.x2-e.x1);else{this._fillBottomLineAtCells(e.x1,e.y1,e.cols-e.x1);for(var t=e.y1+1;t=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseZone=t.Linkifier=void 0;var o=r(0),s=r(1),a=function(){function e(e,t,r){this._bufferService=e,this._logService=t,this._unicodeService=r,this._linkMatchers=[],this._nextLinkMatcherId=0,this._onShowLinkUnderline=new o.EventEmitter,this._onHideLinkUnderline=new o.EventEmitter,this._onLinkTooltip=new o.EventEmitter,this._rowsToLinkify={start:void 0,end:void 0}}return Object.defineProperty(e.prototype,"onShowLinkUnderline",{get:function(){return this._onShowLinkUnderline.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onHideLinkUnderline",{get:function(){return this._onHideLinkUnderline.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onLinkTooltip",{get:function(){return this._onLinkTooltip.event},enumerable:!1,configurable:!0}),e.prototype.attachToDom=function(e,t){this._element=e,this._mouseZoneManager=t},e.prototype.linkifyRows=function(t,r){var i=this;this._mouseZoneManager&&(void 0===this._rowsToLinkify.start||void 0===this._rowsToLinkify.end?(this._rowsToLinkify.start=t,this._rowsToLinkify.end=r):(this._rowsToLinkify.start=Math.min(this._rowsToLinkify.start,t),this._rowsToLinkify.end=Math.max(this._rowsToLinkify.end,r)),this._mouseZoneManager.clearAll(t,r),this._rowsTimeoutId&&clearTimeout(this._rowsTimeoutId),this._rowsTimeoutId=setTimeout((function(){return i._linkifyRows()}),e._timeBeforeLatency))},e.prototype._linkifyRows=function(){this._rowsTimeoutId=void 0;var e=this._bufferService.buffer;if(void 0!==this._rowsToLinkify.start&&void 0!==this._rowsToLinkify.end){var t=e.ydisp+this._rowsToLinkify.start;if(!(t>=e.lines.length)){for(var r=e.ydisp+Math.min(this._rowsToLinkify.end,this._bufferService.rows)+1,i=Math.ceil(2e3/this._bufferService.cols),n=this._bufferService.buffer.iterator(!1,t,r,i,i);n.hasNext();)for(var o=n.next(),s=0;s=0;t--)if(e.priority<=this._linkMatchers[t].priority)return void this._linkMatchers.splice(t+1,0,e);this._linkMatchers.splice(0,0,e)}else this._linkMatchers.push(e)},e.prototype.deregisterLinkMatcher=function(e){for(var t=0;t>9&511:void 0;r.validationCallback?r.validationCallback(a,(function(e){n._rowsTimeoutId||e&&n._addLink(l[1],l[0]-n._bufferService.buffer.ydisp,a,r,f)})):c._addLink(l[1],l[0]-c._bufferService.buffer.ydisp,a,r,f)},c=this;null!==(i=o.exec(t));){if("break"===a())break}},e.prototype._addLink=function(e,t,r,i,n){var o=this;if(this._mouseZoneManager&&this._element){var s=this._unicodeService.getStringCellWidth(r),a=e%this._bufferService.cols,l=t+Math.floor(e/this._bufferService.cols),h=(a+s)%this._bufferService.cols,u=l+Math.floor((a+s)/this._bufferService.cols);0===h&&(h=this._bufferService.cols,u--),this._mouseZoneManager.add(new c(a+1,l+1,h+1,u+1,(function(e){if(i.handler)return i.handler(e,r);var t=window.open();t?(t.opener=null,t.location.href=r):console.warn("Opening link blocked as opener could not be cleared")}),(function(){o._onShowLinkUnderline.fire(o._createLinkHoverEvent(a,l,h,u,n)),o._element.classList.add("xterm-cursor-pointer")}),(function(e){o._onLinkTooltip.fire(o._createLinkHoverEvent(a,l,h,u,n)),i.hoverTooltipCallback&&i.hoverTooltipCallback(e,r,{start:{x:a,y:l},end:{x:h,y:u}})}),(function(){o._onHideLinkUnderline.fire(o._createLinkHoverEvent(a,l,h,u,n)),o._element.classList.remove("xterm-cursor-pointer"),i.hoverLeaveCallback&&i.hoverLeaveCallback()}),(function(e){return!i.willLinkActivate||i.willLinkActivate(e,r)})))}},e.prototype._createLinkHoverEvent=function(e,t,r,i,n){return{x1:e,y1:t,x2:r,y2:i,cols:this._bufferService.cols,fg:n}},e._timeBeforeLatency=200,e=i([n(0,s.IBufferService),n(1,s.ILogService),n(2,s.IUnicodeService)],e)}();t.Linkifier=a;var c=function(e,t,r,i,n,o,s,a,c){this.x1=e,this.y1=t,this.x2=r,this.y2=i,this.clickCallback=n,this.hoverCallback=o,this.tooltipCallback=s,this.leaveCallback=a,this.willLinkActivate=c};t.MouseZone=c},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionService=void 0;var a=r(11),c=r(52),l=r(4),h=r(0),u=r(5),f=r(1),_=r(30),d=r(53),p=r(2),v=String.fromCharCode(160),g=new RegExp(v,"g"),y=function(e){function t(t,r,i,n,o,s,a){var u=e.call(this)||this;return u._element=t,u._screenElement=r,u._bufferService=i,u._coreService=n,u._mouseService=o,u._optionsService=s,u._renderService=a,u._dragScrollAmount=0,u._enabled=!0,u._workCell=new l.CellData,u._mouseDownTimeStamp=0,u._onLinuxMouseSelection=u.register(new h.EventEmitter),u._onRedrawRequest=u.register(new h.EventEmitter),u._onSelectionChange=u.register(new h.EventEmitter),u._onRequestScrollLines=u.register(new h.EventEmitter),u._mouseMoveListener=function(e){return u._onMouseMove(e)},u._mouseUpListener=function(e){return u._onMouseUp(e)},u._coreService.onUserInput((function(){u.hasSelection&&u.clearSelection()})),u._trimListener=u._bufferService.buffer.lines.onTrim((function(e){return u._onTrim(e)})),u.register(u._bufferService.buffers.onBufferActivate((function(e){return u._onBufferActivate(e)}))),u.enable(),u._model=new c.SelectionModel(u._bufferService),u._activeSelectionMode=0,u}return n(t,e),Object.defineProperty(t.prototype,"onLinuxMouseSelection",{get:function(){return this._onLinuxMouseSelection.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestRedraw",{get:function(){return this._onRedrawRequest.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onSelectionChange",{get:function(){return this._onSelectionChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRequestScrollLines",{get:function(){return this._onRequestScrollLines.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){this._removeMouseDownListeners()},t.prototype.reset=function(){this.clearSelection()},t.prototype.disable=function(){this.clearSelection(),this._enabled=!1},t.prototype.enable=function(){this._enabled=!0},Object.defineProperty(t.prototype,"selectionStart",{get:function(){return this._model.finalSelectionStart},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"selectionEnd",{get:function(){return this._model.finalSelectionEnd},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"hasSelection",{get:function(){var e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t)&&(e[0]!==t[0]||e[1]!==t[1])},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"selectionText",{get:function(){var e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";var r=this._bufferService.buffer,i=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";for(var n=e[1];n<=t[1];n++){var o=r.translateBufferLineToString(n,!0,e[0],t[0]);i.push(o)}}else{var s=e[1]===t[1]?t[0]:void 0;i.push(r.translateBufferLineToString(e[1],!0,e[0],s));for(n=e[1]+1;n<=t[1]-1;n++){var c=r.lines.get(n);o=r.translateBufferLineToString(n,!0);c&&c.isWrapped?i[i.length-1]+=o:i.push(o)}if(e[1]!==t[1]){c=r.lines.get(t[1]),o=r.translateBufferLineToString(t[1],!0,0,t[0]);c&&c.isWrapped?i[i.length-1]+=o:i.push(o)}}return i.map((function(e){return e.replace(g," ")})).join(a.isWindows?"\r\n":"\n")},enumerable:!1,configurable:!0}),t.prototype.clearSelection=function(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()},t.prototype.refresh=function(e){var t=this;(this._refreshAnimationFrame||(this._refreshAnimationFrame=window.requestAnimationFrame((function(){return t._refresh()}))),a.isLinux&&e)&&(this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText))},t.prototype._refresh=function(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})},t.prototype.isClickInSelection=function(e){var t=this._getMouseBufferCoords(e),r=this._model.finalSelectionStart,i=this._model.finalSelectionEnd;return!!(r&&i&&t)&&this._areCoordsInSelection(t,r,i)},t.prototype._areCoordsInSelection=function(e,t,r){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]},t.prototype.selectWordAtCursor=function(e){var t=this._getMouseBufferCoords(e);t&&(this._selectWordAt(t,!1),this._model.selectionEnd=void 0,this.refresh(!0))},t.prototype.selectAll=function(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()},t.prototype.selectLines=function(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()},t.prototype._onTrim=function(e){this._model.onTrim(e)&&this.refresh()},t.prototype._getMouseBufferCoords=function(e){var t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t},t.prototype._getMouseEventScrollAmount=function(e){var t=_.getCoordsRelativeToElement(e,this._screenElement)[1],r=this._renderService.dimensions.canvasHeight;return t>=0&&t<=r?0:(t>r&&(t-=r),t=Math.min(Math.max(t,-50),50),(t/=50)/Math.abs(t)+Math.round(14*t))},t.prototype.shouldForceSelection=function(e){return a.isMac?e.altKey&&this._optionsService.options.macOptionClickForcesSelection:e.shiftKey},t.prototype.onMouseDown=function(e){if(this._mouseDownTimeStamp=e.timeStamp,(2!==e.button||!this.hasSelection)&&0===e.button){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._onIncrementalClick(e):1===e.detail?this._onSingleClick(e):2===e.detail?this._onDoubleClick(e):3===e.detail&&this._onTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}},t.prototype._addMouseDownListeners=function(){var e=this;this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=window.setInterval((function(){return e._dragScroll()}),50)},t.prototype._removeMouseDownListeners=function(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0},t.prototype._onIncrementalClick=function(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))},t.prototype._onSingleClick=function(e){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),this._model.selectionStart){this._model.selectionEnd=void 0;var t=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);t&&t.length!==this._model.selectionStart[0]&&0===t.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}},t.prototype._onDoubleClick=function(e){var t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=1,this._selectWordAt(t,!0))},t.prototype._onTripleClick=function(e){var t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))},t.prototype.shouldColumnSelect=function(e){return e.altKey&&!(a.isMac&&this._optionsService.options.macOptionClickForcesSelection)},t.prototype._onMouseMove=function(e){if(e.stopImmediatePropagation(),this._model.selectionStart){var t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),this._model.selectionEnd){2===this._activeSelectionMode?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));var r=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}},t.prototype._onMouseUp=function(e){var t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){var r=this._mouseService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(r&&void 0!==r[0]&&void 0!==r[1]){var i=d.moveToCellSequence(r[0]-1,r[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(i,!0)}}}else this.hasSelection&&this._onSelectionChange.fire()},t.prototype._onBufferActivate=function(e){var t=this;this.clearSelection(),this._trimListener.dispose(),this._trimListener=e.activeBuffer.lines.onTrim((function(e){return t._onTrim(e)}))},t.prototype._convertViewportColToCharacterIndex=function(e,t){for(var r=t[0],i=0;t[0]>=i;i++){var n=e.loadCell(i,this._workCell).getChars().length;0===this._workCell.getWidth()?r--:n>1&&t[0]!==i&&(r+=n-1)}return r},t.prototype.setSelection=function(e,t,r){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=r,this.refresh()},t.prototype._getWordAt=function(e,t,r,i){if(void 0===r&&(r=!0),void 0===i&&(i=!0),!(e[0]>=this._bufferService.cols)){var n=this._bufferService.buffer,o=n.lines.get(e[1]);if(o){var s=n.translateBufferLineToString(e[1],!1),a=this._convertViewportColToCharacterIndex(o,e),c=a,l=e[0]-a,h=0,u=0,f=0,_=0;if(" "===s.charAt(a)){for(;a>0&&" "===s.charAt(a-1);)a--;for(;c1&&(_+=v-1,c+=v-1);d>0&&a>0&&!this._isCharWordSeparator(o.loadCell(d-1,this._workCell));){o.loadCell(d-1,this._workCell);var g=this._workCell.getChars().length;0===this._workCell.getWidth()?(h++,d--):g>1&&(f+=g-1,a-=g-1),a--,d--}for(;p1&&(_+=y-1,c+=y-1),c++,p++}}c++;var b=a+l-h+f,S=Math.min(this._bufferService.cols,c-a+h+u-f-_);if(t||""!==s.slice(a,c).trim()){if(r&&0===b&&32!==o.getCodePoint(0)){var m=n.lines.get(e[1]-1);if(m&&o.isWrapped&&32!==m.getCodePoint(this._bufferService.cols-1)){var C=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(C){var w=this._bufferService.cols-C.start;b-=w,S+=w}}}if(i&&b+S===this._bufferService.cols&&32!==o.getCodePoint(this._bufferService.cols-1)){var E=n.lines.get(e[1]+1);if(E&&E.isWrapped&&32!==E.getCodePoint(0)){var L=this._getWordAt([0,e[1]+1],!1,!1,!0);L&&(S+=L.length)}}return{start:b,length:S}}}}},t.prototype._selectWordAt=function(e,t){var r=this._getWordAt(e,t);if(r){for(;r.start<0;)r.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[r.start,e[1]],this._model.selectionStartLength=r.length}},t.prototype._selectToWordAt=function(e){var t=this._getWordAt(e,!0);if(t){for(var r=e[1];t.start<0;)t.start+=this._bufferService.cols,r--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,r++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,r]}},t.prototype._isCharWordSeparator=function(e){return 0!==e.getWidth()&&this._optionsService.options.wordSeparator.indexOf(e.getChars())>=0},t.prototype._selectLineAt=function(e){var t=this._bufferService.buffer.getWrappedRangeForLine(e);this._model.selectionStart=[0,t.first],this._model.selectionEnd=[this._bufferService.cols,t.last],this._model.selectionStartLength=0},t=o([s(2,f.IBufferService),s(3,f.ICoreService),s(4,u.IMouseService),s(5,f.IOptionsService),s(6,u.IRenderService)],t)}(p.Disposable);t.SelectionService=y},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0;var i=function(){function e(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}return e.prototype.clearSelection=function(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0},Object.defineProperty(e.prototype,"finalSelectionStart",{get:function(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"finalSelectionEnd",{get:function(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){var e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}return this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]?[Math.max(this.selectionStart[0]+this.selectionStartLength,this.selectionEnd[0]),this.selectionEnd[1]]:this.selectionEnd}},enumerable:!1,configurable:!0}),e.prototype.areSelectionValuesReversed=function(){var e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])},e.prototype.onTrim=function(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)},e}();t.SelectionModel=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.moveToCellSequence=void 0;var i=r(12);function n(e,t,r,i){var n=e-o(r,e),a=t-o(r,t);return l(Math.abs(n-a)-function(e,t,r){for(var i=0,n=e-o(r,e),a=t-o(r,t),c=0;c=0&&tt?"A":"B"}function a(e,t,r,i,n,o){for(var s=e,a=t,c="";s!==r||a!==i;)s+=n?1:-1,n&&s>o.cols-1?(c+=o.buffer.translateBufferLineToString(a,!1,e,s),s=0,e=0,a++):!n&&s<0&&(c+=o.buffer.translateBufferLineToString(a,!1,0,e+1),e=s=o.cols-1,a--);return c+o.buffer.translateBufferLineToString(a,!1,e,s)}function c(e,t){var r=t?"O":"[";return i.C0.ESC+r+e}function l(e,t){e=Math.floor(e);for(var r="",i=0;i0?i-o(s,i):t;var f=i,_=function(e,t,r,i,s,a){var c;c=n(r,i,s,a).length>0?i-o(s,i):t;if(e=r&&ce?"D":"C",l(Math.abs(h-e),c(s,i));s=u>t?"D":"C";var f=Math.abs(u-t);return l(function(e,t){return t.cols-e}(u>t?e:h,r)+(f-1)*r.cols+1+((u>t?h:e)-1),c(s,i))}},function(e,t,r){"use strict";var i=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.SoundService=void 0;var o=r(1),s=function(){function e(e){this._optionsService=e}return Object.defineProperty(e,"audioContext",{get:function(){if(!e._audioContext){var t=window.AudioContext||window.webkitAudioContext;if(!t)return console.warn("Web Audio API is not supported by this browser. Consider upgrading to the latest version"),null;e._audioContext=new t}return e._audioContext},enumerable:!1,configurable:!0}),e.prototype.playBellSound=function(){var t=e.audioContext;if(t){var r=t.createBufferSource();t.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.options.bellSound)),(function(e){r.buffer=e,r.connect(t.destination),r.start(0)}))}},e.prototype._base64ToArrayBuffer=function(e){for(var t=window.atob(e),r=t.length,i=new Uint8Array(r),n=0;n=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseZoneManager=void 0;var a=r(2),c=r(7),l=r(5),h=r(1),u=function(e){function t(t,r,i,n,o,s){var a=e.call(this)||this;return a._element=t,a._screenElement=r,a._bufferService=i,a._mouseService=n,a._selectionService=o,a._optionsService=s,a._zones=[],a._areZonesActive=!1,a._lastHoverCoords=[void 0,void 0],a._initialSelectionLength=0,a.register(c.addDisposableDomListener(a._element,"mousedown",(function(e){return a._onMouseDown(e)}))),a._mouseMoveListener=function(e){return a._onMouseMove(e)},a._mouseLeaveListener=function(e){return a._onMouseLeave(e)},a._clickListener=function(e){return a._onClick(e)},a}return n(t,e),t.prototype.dispose=function(){e.prototype.dispose.call(this),this._deactivate()},t.prototype.add=function(e){this._zones.push(e),1===this._zones.length&&this._activate()},t.prototype.clearAll=function(e,t){if(0!==this._zones.length){e&&t||(e=0,t=this._bufferService.rows-1);for(var r=0;re&&i.y1<=t+1||i.y2>e&&i.y2<=t+1||i.y1t+1)&&(this._currentZone&&this._currentZone===i&&(this._currentZone.leaveCallback(),this._currentZone=void 0),this._zones.splice(r--,1))}0===this._zones.length&&this._deactivate()}},t.prototype._activate=function(){this._areZonesActive||(this._areZonesActive=!0,this._element.addEventListener("mousemove",this._mouseMoveListener),this._element.addEventListener("mouseleave",this._mouseLeaveListener),this._element.addEventListener("click",this._clickListener))},t.prototype._deactivate=function(){this._areZonesActive&&(this._areZonesActive=!1,this._element.removeEventListener("mousemove",this._mouseMoveListener),this._element.removeEventListener("mouseleave",this._mouseLeaveListener),this._element.removeEventListener("click",this._clickListener))},t.prototype._onMouseMove=function(e){this._lastHoverCoords[0]===e.pageX&&this._lastHoverCoords[1]===e.pageY||(this._onHover(e),this._lastHoverCoords=[e.pageX,e.pageY])},t.prototype._onHover=function(e){var t=this,r=this._findZoneEventAt(e);r!==this._currentZone&&(this._currentZone&&(this._currentZone.leaveCallback(),this._currentZone=void 0,this._tooltipTimeout&&clearTimeout(this._tooltipTimeout)),r&&(this._currentZone=r,r.hoverCallback&&r.hoverCallback(e),this._tooltipTimeout=window.setTimeout((function(){return t._onTooltip(e)}),this._optionsService.options.linkTooltipHoverDuration)))},t.prototype._onTooltip=function(e){this._tooltipTimeout=void 0;var t=this._findZoneEventAt(e);t&&t.tooltipCallback&&t.tooltipCallback(e)},t.prototype._onMouseDown=function(e){if(this._initialSelectionLength=this._getSelectionLength(),this._areZonesActive){var t=this._findZoneEventAt(e);(null==t?void 0:t.willLinkActivate(e))&&(e.preventDefault(),e.stopImmediatePropagation())}},t.prototype._onMouseLeave=function(e){this._currentZone&&(this._currentZone.leaveCallback(),this._currentZone=void 0,this._tooltipTimeout&&clearTimeout(this._tooltipTimeout))},t.prototype._onClick=function(e){var t=this._findZoneEventAt(e),r=this._getSelectionLength();t&&r===this._initialSelectionLength&&(t.clickCallback(e),e.preventDefault(),e.stopImmediatePropagation())},t.prototype._getSelectionLength=function(){var e=this._selectionService.selectionText;return e?e.length:0},t.prototype._findZoneEventAt=function(e){var t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows);if(t)for(var r=t[0],i=t[1],n=0;n=o.x1&&r=o.x1||i===o.y2&&ro.y1&&ie;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()},t.prototype._createAccessibilityTreeNode=function(){var e=document.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e},t.prototype._onTab=function(e){for(var t=0;t0)this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e);else this._charsToAnnounce+=e;"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent+=o.tooMuchOutput)),s.isMac&&this._liveRegion.textContent&&this._liveRegion.textContent.length>0&&!this._liveRegion.parentNode&&setTimeout((function(){t._accessibilityTreeRoot.appendChild(t._liveRegion)}),0)}},t.prototype._clearLiveRegion=function(){this._liveRegion.textContent="",this._liveRegionLineCount=0,s.isMac&&u.removeElementFromParent(this._liveRegion)},t.prototype._onKey=function(e){this._clearLiveRegion(),this._charsToConsume.push(e)},t.prototype._refreshRows=function(e,t){this._renderRowsDebouncer.refresh(e,t,this._terminal.rows)},t.prototype._renderRows=function(e,t){for(var r=this._terminal.buffer,i=r.lines.length.toString(),n=e;n<=t;n++){var o=r.translateBufferLineToString(r.ydisp+n,!0),s=(r.ydisp+n+1).toString(),a=this._rowElements[n];a&&(0===o.length?a.innerHTML=" ":a.textContent=o,a.setAttribute("aria-posinset",s),a.setAttribute("aria-setsize",i))}this._announceCharacters()},t.prototype._refreshRowsDimensions=function(){if(this._renderService.dimensions.actualCellHeight){this._rowElements.length!==this._terminal.rows&&this._onResize(this._terminal.rows);for(var e=0;e=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;var a=r(58),c=r(9),l=r(2),h=r(5),u=r(1),f=r(0),_=r(10),d=r(17),p=1,v=function(e){function t(t,r,i,n,o,s,c,l,h){var u=e.call(this)||this;return u._colors=t,u._element=r,u._screenElement=i,u._viewportElement=n,u._linkifier=o,u._linkifier2=s,u._charSizeService=c,u._optionsService=l,u._bufferService=h,u._terminalClass=p++,u._rowElements=[],u._rowContainer=document.createElement("div"),u._rowContainer.classList.add("xterm-rows"),u._rowContainer.style.lineHeight="normal",u._rowContainer.setAttribute("aria-hidden","true"),u._refreshRowElements(u._bufferService.cols,u._bufferService.rows),u._selectionContainer=document.createElement("div"),u._selectionContainer.classList.add("xterm-selection"),u._selectionContainer.setAttribute("aria-hidden","true"),u.dimensions={scaledCharWidth:0,scaledCharHeight:0,scaledCellWidth:0,scaledCellHeight:0,scaledCharLeft:0,scaledCharTop:0,scaledCanvasWidth:0,scaledCanvasHeight:0,canvasWidth:0,canvasHeight:0,actualCellWidth:0,actualCellHeight:0},u._updateDimensions(),u._injectCss(),u._rowFactory=new a.DomRendererRowFactory(document,u._optionsService,u._colors),u._element.classList.add("xterm-dom-renderer-owner-"+u._terminalClass),u._screenElement.appendChild(u._rowContainer),u._screenElement.appendChild(u._selectionContainer),u._linkifier.onShowLinkUnderline((function(e){return u._onLinkHover(e)})),u._linkifier.onHideLinkUnderline((function(e){return u._onLinkLeave(e)})),u._linkifier2.onShowLinkUnderline((function(e){return u._onLinkHover(e)})),u._linkifier2.onHideLinkUnderline((function(e){return u._onLinkLeave(e)})),u}return n(t,e),Object.defineProperty(t.prototype,"onRequestRedraw",{get:function(){return(new f.EventEmitter).event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),d.removeElementFromParent(this._rowContainer,this._selectionContainer,this._themeStyleElement,this._dimensionsStyleElement),e.prototype.dispose.call(this)},t.prototype._updateDimensions=function(){this.dimensions.scaledCharWidth=this._charSizeService.width*window.devicePixelRatio,this.dimensions.scaledCharHeight=Math.ceil(this._charSizeService.height*window.devicePixelRatio),this.dimensions.scaledCellWidth=this.dimensions.scaledCharWidth+Math.round(this._optionsService.options.letterSpacing),this.dimensions.scaledCellHeight=Math.floor(this.dimensions.scaledCharHeight*this._optionsService.options.lineHeight),this.dimensions.scaledCharLeft=0,this.dimensions.scaledCharTop=0,this.dimensions.scaledCanvasWidth=this.dimensions.scaledCellWidth*this._bufferService.cols,this.dimensions.scaledCanvasHeight=this.dimensions.scaledCellHeight*this._bufferService.rows,this.dimensions.canvasWidth=Math.round(this.dimensions.scaledCanvasWidth/window.devicePixelRatio),this.dimensions.canvasHeight=Math.round(this.dimensions.scaledCanvasHeight/window.devicePixelRatio),this.dimensions.actualCellWidth=this.dimensions.canvasWidth/this._bufferService.cols,this.dimensions.actualCellHeight=this.dimensions.canvasHeight/this._bufferService.rows;for(var e=0,t=this._rowElements;et;)this._rowContainer.removeChild(this._rowElements.pop())},t.prototype.onResize=function(e,t){this._refreshRowElements(e,t),this._updateDimensions()},t.prototype.onCharSizeChanged=function(){this._updateDimensions()},t.prototype.onBlur=function(){this._rowContainer.classList.remove("xterm-focus")},t.prototype.onFocus=function(){this._rowContainer.classList.add("xterm-focus")},t.prototype.onSelectionChanged=function(e,t,r){for(;this._selectionContainer.children.length;)this._selectionContainer.removeChild(this._selectionContainer.children[0]);if(e&&t){var i=e[1]-this._bufferService.buffer.ydisp,n=t[1]-this._bufferService.buffer.ydisp,o=Math.max(i,0),s=Math.min(n,this._bufferService.rows-1);if(!(o>=this._bufferService.rows||s<0)){var a=document.createDocumentFragment();if(r)a.appendChild(this._createSelectionElement(o,e[0],t[0],s-o+1));else{var c=i===o?e[0]:0,l=o===s?t[0]:this._bufferService.cols;a.appendChild(this._createSelectionElement(o,c,l));var h=s-o-1;if(a.appendChild(this._createSelectionElement(o+1,0,this._bufferService.cols,h)),o!==s){var u=n===s?t[0]:this._bufferService.cols;a.appendChild(this._createSelectionElement(s,0,u))}}this._selectionContainer.appendChild(a)}}},t.prototype._createSelectionElement=function(e,t,r,i){void 0===i&&(i=1);var n=document.createElement("div");return n.style.height=i*this.dimensions.actualCellHeight+"px",n.style.top=e*this.dimensions.actualCellHeight+"px",n.style.left=t*this.dimensions.actualCellWidth+"px",n.style.width=this.dimensions.actualCellWidth*(r-t)+"px",n},t.prototype.onCursorMove=function(){},t.prototype.onOptionsChanged=function(){this._updateDimensions(),this._injectCss()},t.prototype.clear=function(){for(var e=0,t=this._rowElements;e=n&&(e=0,r++)}},t=o([s(6,h.ICharSizeService),s(7,u.IOptionsService),s(8,u.IBufferService)],t)}(l.Disposable);t.DomRenderer=v},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=t.CURSOR_STYLE_UNDERLINE_CLASS=t.CURSOR_STYLE_BAR_CLASS=t.CURSOR_STYLE_BLOCK_CLASS=t.CURSOR_BLINK_CLASS=t.CURSOR_CLASS=t.UNDERLINE_CLASS=t.ITALIC_CLASS=t.DIM_CLASS=t.BOLD_CLASS=void 0;var i=r(9),n=r(3),o=r(4),s=r(10);t.BOLD_CLASS="xterm-bold",t.DIM_CLASS="xterm-dim",t.ITALIC_CLASS="xterm-italic",t.UNDERLINE_CLASS="xterm-underline",t.CURSOR_CLASS="xterm-cursor",t.CURSOR_BLINK_CLASS="xterm-cursor-blink",t.CURSOR_STYLE_BLOCK_CLASS="xterm-cursor-block",t.CURSOR_STYLE_BAR_CLASS="xterm-cursor-bar",t.CURSOR_STYLE_UNDERLINE_CLASS="xterm-cursor-underline";var a=function(){function e(e,t,r){this._document=e,this._optionsService=t,this._colors=r,this._workCell=new o.CellData}return e.prototype.setColors=function(e){this._colors=e},e.prototype.createRow=function(e,r,o,a,l,h,u){for(var f=this._document.createDocumentFragment(),_=0,d=Math.min(e.length,u)-1;d>=0;d--)if(e.loadCell(d,this._workCell).getCode()!==n.NULL_CELL_CODE||r&&d===a){_=d+1;break}for(d=0;d<_;d++){e.loadCell(d,this._workCell);var p=this._workCell.getWidth();if(0!==p){var v=this._document.createElement("span");if(p>1&&(v.style.width=h*p+"px"),r&&d===a)switch(v.classList.add(t.CURSOR_CLASS),l&&v.classList.add(t.CURSOR_BLINK_CLASS),o){case"bar":v.classList.add(t.CURSOR_STYLE_BAR_CLASS);break;case"underline":v.classList.add(t.CURSOR_STYLE_UNDERLINE_CLASS);break;default:v.classList.add(t.CURSOR_STYLE_BLOCK_CLASS)}this._workCell.isBold()&&v.classList.add(t.BOLD_CLASS),this._workCell.isItalic()&&v.classList.add(t.ITALIC_CLASS),this._workCell.isDim()&&v.classList.add(t.DIM_CLASS),this._workCell.isUnderline()&&v.classList.add(t.UNDERLINE_CLASS),this._workCell.isInvisible()?v.textContent=n.WHITESPACE_CELL_CHAR:v.textContent=this._workCell.getChars()||n.WHITESPACE_CELL_CHAR;var g=this._workCell.getFgColor(),y=this._workCell.getFgColorMode(),b=this._workCell.getBgColor(),S=this._workCell.getBgColorMode(),m=!!this._workCell.isInverse();if(m){var C=g;g=b,b=C;var w=y;y=S,S=w}switch(y){case 16777216:case 33554432:this._workCell.isBold()&&g<8&&this._optionsService.options.drawBoldTextInBrightColors&&(g+=8),this._applyMinimumContrast(v,this._colors.background,this._colors.ansi[g])||v.classList.add("xterm-fg-"+g);break;case 50331648:var E=s.rgba.toColor(g>>16&255,g>>8&255,255&g);this._applyMinimumContrast(v,this._colors.background,E)||this._addStyle(v,"color:#"+c(g.toString(16),"0",6));break;case 0:default:this._applyMinimumContrast(v,this._colors.background,this._colors.foreground)||m&&v.classList.add("xterm-fg-"+i.INVERTED_DEFAULT_COLOR)}switch(S){case 16777216:case 33554432:v.classList.add("xterm-bg-"+b);break;case 50331648:this._addStyle(v,"background-color:#"+c(b.toString(16),"0",6));break;case 0:default:m&&v.classList.add("xterm-bg-"+i.INVERTED_DEFAULT_COLOR)}f.appendChild(v)}}return f},e.prototype._applyMinimumContrast=function(e,t,r){if(1===this._optionsService.options.minimumContrastRatio)return!1;var i=this._colors.contrastCache.getColor(this._workCell.bg,this._workCell.fg);return void 0===i&&(i=s.color.ensureContrastRatio(t,r,this._optionsService.options.minimumContrastRatio),this._colors.contrastCache.setColor(this._workCell.bg,this._workCell.fg,null!=i?i:null)),!!i&&(this._addStyle(e,"color:"+i.css),!0)},e.prototype._addStyle=function(e,t){e.setAttribute("style",""+(e.getAttribute("style")||"")+t+";")},e}();function c(e,t,r){for(;e.length"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};t.evaluateKeyboardEvent=function(e,t,r,o){var s={type:0,cancel:!1,key:void 0},a=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?s.key=t?i.C0.ESC+"OA":i.C0.ESC+"[A":"UIKeyInputLeftArrow"===e.key?s.key=t?i.C0.ESC+"OD":i.C0.ESC+"[D":"UIKeyInputRightArrow"===e.key?s.key=t?i.C0.ESC+"OC":i.C0.ESC+"[C":"UIKeyInputDownArrow"===e.key&&(s.key=t?i.C0.ESC+"OB":i.C0.ESC+"[B");break;case 8:if(e.shiftKey){s.key=i.C0.BS;break}if(e.altKey){s.key=i.C0.ESC+i.C0.DEL;break}s.key=i.C0.DEL;break;case 9:if(e.shiftKey){s.key=i.C0.ESC+"[Z";break}s.key=i.C0.HT,s.cancel=!0;break;case 13:s.key=e.altKey?i.C0.ESC+i.C0.CR:i.C0.CR,s.cancel=!0;break;case 27:s.key=i.C0.ESC,e.altKey&&(s.key=i.C0.ESC+i.C0.ESC),s.cancel=!0;break;case 37:if(e.metaKey)break;a?(s.key=i.C0.ESC+"[1;"+(a+1)+"D",s.key===i.C0.ESC+"[1;3D"&&(s.key=i.C0.ESC+(r?"b":"[1;5D"))):s.key=t?i.C0.ESC+"OD":i.C0.ESC+"[D";break;case 39:if(e.metaKey)break;a?(s.key=i.C0.ESC+"[1;"+(a+1)+"C",s.key===i.C0.ESC+"[1;3C"&&(s.key=i.C0.ESC+(r?"f":"[1;5C"))):s.key=t?i.C0.ESC+"OC":i.C0.ESC+"[C";break;case 38:if(e.metaKey)break;a?(s.key=i.C0.ESC+"[1;"+(a+1)+"A",r||s.key!==i.C0.ESC+"[1;3A"||(s.key=i.C0.ESC+"[1;5A")):s.key=t?i.C0.ESC+"OA":i.C0.ESC+"[A";break;case 40:if(e.metaKey)break;a?(s.key=i.C0.ESC+"[1;"+(a+1)+"B",r||s.key!==i.C0.ESC+"[1;3B"||(s.key=i.C0.ESC+"[1;5B")):s.key=t?i.C0.ESC+"OB":i.C0.ESC+"[B";break;case 45:e.shiftKey||e.ctrlKey||(s.key=i.C0.ESC+"[2~");break;case 46:s.key=a?i.C0.ESC+"[3;"+(a+1)+"~":i.C0.ESC+"[3~";break;case 36:s.key=a?i.C0.ESC+"[1;"+(a+1)+"H":t?i.C0.ESC+"OH":i.C0.ESC+"[H";break;case 35:s.key=a?i.C0.ESC+"[1;"+(a+1)+"F":t?i.C0.ESC+"OF":i.C0.ESC+"[F";break;case 33:e.shiftKey?s.type=2:s.key=i.C0.ESC+"[5~";break;case 34:e.shiftKey?s.type=3:s.key=i.C0.ESC+"[6~";break;case 112:s.key=a?i.C0.ESC+"[1;"+(a+1)+"P":i.C0.ESC+"OP";break;case 113:s.key=a?i.C0.ESC+"[1;"+(a+1)+"Q":i.C0.ESC+"OQ";break;case 114:s.key=a?i.C0.ESC+"[1;"+(a+1)+"R":i.C0.ESC+"OR";break;case 115:s.key=a?i.C0.ESC+"[1;"+(a+1)+"S":i.C0.ESC+"OS";break;case 116:s.key=a?i.C0.ESC+"[15;"+(a+1)+"~":i.C0.ESC+"[15~";break;case 117:s.key=a?i.C0.ESC+"[17;"+(a+1)+"~":i.C0.ESC+"[17~";break;case 118:s.key=a?i.C0.ESC+"[18;"+(a+1)+"~":i.C0.ESC+"[18~";break;case 119:s.key=a?i.C0.ESC+"[19;"+(a+1)+"~":i.C0.ESC+"[19~";break;case 120:s.key=a?i.C0.ESC+"[20;"+(a+1)+"~":i.C0.ESC+"[20~";break;case 121:s.key=a?i.C0.ESC+"[21;"+(a+1)+"~":i.C0.ESC+"[21~";break;case 122:s.key=a?i.C0.ESC+"[23;"+(a+1)+"~":i.C0.ESC+"[23~";break;case 123:s.key=a?i.C0.ESC+"[24;"+(a+1)+"~":i.C0.ESC+"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(r&&!o||!e.altKey||e.metaKey)r&&!e.altKey&&!e.ctrlKey&&e.metaKey?65===e.keyCode&&(s.type=1):e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length?s.key=e.key:e.key&&e.ctrlKey&&"_"===e.key&&(s.key=i.C0.US);else{var c=n[e.keyCode],l=c&&c[e.shiftKey?1:0];if(l)s.key=i.C0.ESC+l;else if(e.keyCode>=65&&e.keyCode<=90){var h=e.ctrlKey?e.keyCode-64:e.keyCode+32;s.key=i.C0.ESC+String.fromCharCode(h)}}else e.keyCode>=65&&e.keyCode<=90?s.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?s.key=i.C0.NUL:e.keyCode>=51&&e.keyCode<=55?s.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?s.key=i.C0.DEL:219===e.keyCode?s.key=i.C0.ESC:220===e.keyCode?s.key=i.C0.FS:221===e.keyCode&&(s.key=i.C0.GS)}return s}},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;var a=r(31),c=r(0),l=r(2),h=r(32),u=r(7),f=r(1),_=r(5),d=function(e){function t(t,r,i,n,o,s){var l=e.call(this)||this;if(l._renderer=t,l._rowCount=r,l._isPaused=!1,l._needsFullRefresh=!1,l._isNextRenderRedrawOnly=!0,l._needsSelectionRefresh=!1,l._canvasWidth=0,l._canvasHeight=0,l._selectionState={start:void 0,end:void 0,columnSelectMode:!1},l._onDimensionsChange=new c.EventEmitter,l._onRender=new c.EventEmitter,l._onRefreshRequest=new c.EventEmitter,l.register({dispose:function(){return l._renderer.dispose()}}),l._renderDebouncer=new a.RenderDebouncer((function(e,t){return l._renderRows(e,t)})),l.register(l._renderDebouncer),l._screenDprMonitor=new h.ScreenDprMonitor,l._screenDprMonitor.setListener((function(){return l.onDevicePixelRatioChange()})),l.register(l._screenDprMonitor),l.register(s.onResize((function(e){return l._fullRefresh()}))),l.register(n.onOptionChange((function(){return l._renderer.onOptionsChanged()}))),l.register(o.onCharSizeChange((function(){return l.onCharSizeChanged()}))),l._renderer.onRequestRedraw((function(e){return l.refreshRows(e.start,e.end,!0)})),l.register(u.addDisposableDomListener(window,"resize",(function(){return l.onDevicePixelRatioChange()}))),"IntersectionObserver"in window){var f=new IntersectionObserver((function(e){return l._onIntersectionChange(e[e.length-1])}),{threshold:0});f.observe(i),l.register({dispose:function(){return f.disconnect()}})}return l}return n(t,e),Object.defineProperty(t.prototype,"onDimensionsChange",{get:function(){return this._onDimensionsChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRenderedBufferChange",{get:function(){return this._onRender.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onRefreshRequest",{get:function(){return this._onRefreshRequest.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"dimensions",{get:function(){return this._renderer.dimensions},enumerable:!1,configurable:!0}),t.prototype._onIntersectionChange=function(e){this._isPaused=0===e.intersectionRatio,!this._isPaused&&this._needsFullRefresh&&(this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)},t.prototype.refreshRows=function(e,t,r){void 0===r&&(r=!1),this._isPaused?this._needsFullRefresh=!0:(r||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,t,this._rowCount))},t.prototype._renderRows=function(e,t){this._renderer.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.onSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0},t.prototype.resize=function(e,t){this._rowCount=t,this._fireOnCanvasResize()},t.prototype.changeOptions=function(){this._renderer.onOptionsChanged(),this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize()},t.prototype._fireOnCanvasResize=function(){this._renderer.dimensions.canvasWidth===this._canvasWidth&&this._renderer.dimensions.canvasHeight===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.dimensions)},t.prototype.dispose=function(){e.prototype.dispose.call(this)},t.prototype.setRenderer=function(e){var t=this;this._renderer.dispose(),this._renderer=e,this._renderer.onRequestRedraw((function(e){return t.refreshRows(e.start,e.end,!0)})),this._needsSelectionRefresh=!0,this._fullRefresh()},t.prototype._fullRefresh=function(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)},t.prototype.setColors=function(e){this._renderer.setColors(e),this._fullRefresh()},t.prototype.onDevicePixelRatioChange=function(){this._renderer.onDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1)},t.prototype.onResize=function(e,t){this._renderer.onResize(e,t),this._fullRefresh()},t.prototype.onCharSizeChanged=function(){this._renderer.onCharSizeChanged()},t.prototype.onBlur=function(){this._renderer.onBlur()},t.prototype.onFocus=function(){this._renderer.onFocus()},t.prototype.onSelectionChanged=function(e,t,r){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=r,this._renderer.onSelectionChanged(e,t,r)},t.prototype.onCursorMove=function(){this._renderer.onCursorMove()},t.prototype.clear=function(){this._renderer.clear()},t.prototype.registerCharacterJoiner=function(e){return this._renderer.registerCharacterJoiner(e)},t.prototype.deregisterCharacterJoiner=function(e){return this._renderer.deregisterCharacterJoiner(e)},t=o([s(3,f.IOptionsService),s(4,_.ICharSizeService),s(5,f.IBufferService)],t)}(l.Disposable);t.RenderService=d},function(e,t,r){"use strict";var i=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;var o=r(1),s=r(0),a=function(){function e(e,t,r){this._optionsService=r,this.width=0,this.height=0,this._onCharSizeChange=new s.EventEmitter,this._measureStrategy=new c(e,t,this._optionsService)}return Object.defineProperty(e.prototype,"hasValidSize",{get:function(){return this.width>0&&this.height>0},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onCharSizeChange",{get:function(){return this._onCharSizeChange.event},enumerable:!1,configurable:!0}),e.prototype.measure=function(){var e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())},e=i([n(2,o.IOptionsService)],e)}();t.CharSizeService=a;var c=function(){function e(e,t,r){this._document=e,this._parentElement=t,this._optionsService=r,this._result={width:0,height:0},this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W",this._measureElement.setAttribute("aria-hidden","true"),this._parentElement.appendChild(this._measureElement)}return e.prototype.measure=function(){this._measureElement.style.fontFamily=this._optionsService.options.fontFamily,this._measureElement.style.fontSize=this._optionsService.options.fontSize+"px";var e=this._measureElement.getBoundingClientRect();return 0!==e.width&&0!==e.height&&(this._result.width=e.width,this._result.height=Math.ceil(e.height)),this._result},e}()},function(e,t,r){"use strict";var i=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseService=void 0;var o=r(5),s=r(30),a=function(){function e(e,t){this._renderService=e,this._charSizeService=t}return e.prototype.getCoords=function(e,t,r,i,n){return s.getCoords(e,t,r,i,this._charSizeService.hasValidSize,this._renderService.dimensions.actualCellWidth,this._renderService.dimensions.actualCellHeight,n)},e.prototype.getRawByteCoords=function(e,t,r,i){var n=this.getCoords(e,t,r,i);return s.getRawByteCoords(n)},e=i([n(0,o.IRenderService),n(1,o.ICharSizeService)],e)}();t.MouseService=a},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}),o=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier2=void 0;var a=r(1),c=r(0),l=r(2),h=r(7),u=function(e){function t(t){var r=e.call(this)||this;return r._bufferService=t,r._linkProviders=[],r._linkCacheDisposables=[],r._isMouseOut=!0,r._activeLine=-1,r._onShowLinkUnderline=r.register(new c.EventEmitter),r._onHideLinkUnderline=r.register(new c.EventEmitter),r.register(l.getDisposeArrayDisposable(r._linkCacheDisposables)),r}return n(t,e),Object.defineProperty(t.prototype,"onShowLinkUnderline",{get:function(){return this._onShowLinkUnderline.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onHideLinkUnderline",{get:function(){return this._onHideLinkUnderline.event},enumerable:!1,configurable:!0}),t.prototype.registerLinkProvider=function(e){var t=this;return this._linkProviders.push(e),{dispose:function(){var r=t._linkProviders.indexOf(e);-1!==r&&t._linkProviders.splice(r,1)}}},t.prototype.attachToDom=function(e,t,r){var i=this;this._element=e,this._mouseService=t,this._renderService=r,this.register(h.addDisposableDomListener(this._element,"mouseleave",(function(){i._isMouseOut=!0,i._clearCurrentLink()}))),this.register(h.addDisposableDomListener(this._element,"mousemove",this._onMouseMove.bind(this))),this.register(h.addDisposableDomListener(this._element,"click",this._onClick.bind(this)))},t.prototype._onMouseMove=function(e){if(this._lastMouseEvent=e,this._element&&this._mouseService){var t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(t){this._isMouseOut=!1;for(var r=e.composedPath(),i=0;ie?this._bufferService.cols:s.link.range.end.x,l=a;l<=c;l++){if(r.has(l)){n.splice(o--,1);break}r.add(l)}}},t.prototype._checkLinkProviderResult=function(e,t,r){var i,n=this;if(!this._activeProviderReplies)return r;for(var o=this._activeProviderReplies.get(e),s=!1,a=0;a=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,l.disposeArray(this._linkCacheDisposables))},t.prototype._handleNewLink=function(e){var t=this;if(this._element&&this._lastMouseEvent&&this._mouseService){var r=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);r&&this._linkAtPosition(e.link,r)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:function(){var e,r;return null===(r=null===(e=t._currentLink)||void 0===e?void 0:e.state)||void 0===r?void 0:r.decorations.pointerCursor},set:function(e){var r,i;(null===(r=t._currentLink)||void 0===r?void 0:r.state)&&t._currentLink.state.decorations.pointerCursor!==e&&(t._currentLink.state.decorations.pointerCursor=e,t._currentLink.state.isHovered&&(null===(i=t._element)||void 0===i||i.classList.toggle("xterm-cursor-pointer",e)))}},underline:{get:function(){var e,r;return null===(r=null===(e=t._currentLink)||void 0===e?void 0:e.state)||void 0===r?void 0:r.decorations.underline},set:function(r){var i,n,o;(null===(i=t._currentLink)||void 0===i?void 0:i.state)&&(null===(o=null===(n=t._currentLink)||void 0===n?void 0:n.state)||void 0===o?void 0:o.decorations.underline)!==r&&(t._currentLink.state.decorations.underline=r,t._currentLink.state.isHovered&&t._fireUnderlineEvent(e.link,r))}}}),this._renderService&&this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange((function(e){var r=0===e.start?0:e.start+1+t._bufferService.buffer.ydisp;t._clearCurrentLink(r,e.end+1+t._bufferService.buffer.ydisp)}))))}},t.prototype._linkHover=function(e,t,r){var i;(null===(i=this._currentLink)||void 0===i?void 0:i.state)&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(r,t.text)},t.prototype._fireUnderlineEvent=function(e,t){var r=e.range,i=this._bufferService.buffer.ydisp,n=this._createLinkUnderlineEvent(r.start.x-1,r.start.y-i-1,r.end.x,r.end.y-i-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(n)},t.prototype._linkLeave=function(e,t,r){var i;(null===(i=this._currentLink)||void 0===i?void 0:i.state)&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(r,t.text)},t.prototype._linkAtPosition=function(e,t){var r=e.range.start.y===e.range.end.y,i=e.range.start.yt.y;return(r&&e.range.start.x<=t.x&&e.range.end.x>=t.x||i&&e.range.end.x>=t.x||n&&e.range.start.x<=t.x||i&&n)&&e.range.start.y<=t.y&&e.range.end.y>=t.y},t.prototype._positionFromMouseEvent=function(e,t,r){var i=r.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(i)return{x:i[0],y:i[1]+this._bufferService.buffer.ydisp}},t.prototype._createLinkUnderlineEvent=function(e,t,r,i,n){return{x1:e,y1:t,x2:r,y2:i,cols:this._bufferService.cols,fg:n}},t=o([s(0,a.IBufferService)],t)}(l.Disposable);t.Linkifier2=u},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CoreBrowserService=void 0;var i=function(){function e(e){this._textarea=e}return Object.defineProperty(e.prototype,"isFocused",{get:function(){return document.activeElement===this._textarea&&document.hasFocus()},enumerable:!1,configurable:!0}),e}();t.CoreBrowserService=i},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;var o=r(2),s=r(1),a=r(66),c=r(67),l=r(68),h=r(74),u=r(75),f=r(0),_=r(76),d=r(77),p=r(78),v=r(80),g=r(81),y=r(19),b=r(82),S=function(e){function t(t){var r=e.call(this)||this;return r._onBinary=new f.EventEmitter,r._onData=new f.EventEmitter,r._onLineFeed=new f.EventEmitter,r._onResize=new f.EventEmitter,r._onScroll=new f.EventEmitter,r._instantiationService=new a.InstantiationService,r.optionsService=new h.OptionsService(t),r._instantiationService.setService(s.IOptionsService,r.optionsService),r._bufferService=r.register(r._instantiationService.createInstance(l.BufferService)),r._instantiationService.setService(s.IBufferService,r._bufferService),r._logService=r._instantiationService.createInstance(c.LogService),r._instantiationService.setService(s.ILogService,r._logService),r._coreService=r.register(r._instantiationService.createInstance(u.CoreService,(function(){return r.scrollToBottom()}))),r._instantiationService.setService(s.ICoreService,r._coreService),r._coreMouseService=r._instantiationService.createInstance(_.CoreMouseService),r._instantiationService.setService(s.ICoreMouseService,r._coreMouseService),r._dirtyRowService=r._instantiationService.createInstance(d.DirtyRowService),r._instantiationService.setService(s.IDirtyRowService,r._dirtyRowService),r.unicodeService=r._instantiationService.createInstance(p.UnicodeService),r._instantiationService.setService(s.IUnicodeService,r.unicodeService),r._charsetService=r._instantiationService.createInstance(v.CharsetService),r._instantiationService.setService(s.ICharsetService,r._charsetService),r._inputHandler=new y.InputHandler(r._bufferService,r._charsetService,r._coreService,r._dirtyRowService,r._logService,r.optionsService,r._coreMouseService,r.unicodeService),r.register(f.forwardEvent(r._inputHandler.onLineFeed,r._onLineFeed)),r.register(r._inputHandler),r.register(f.forwardEvent(r._bufferService.onResize,r._onResize)),r.register(f.forwardEvent(r._coreService.onData,r._onData)),r.register(f.forwardEvent(r._coreService.onBinary,r._onBinary)),r.register(r.optionsService.onOptionChange((function(e){return r._updateOptions(e)}))),r._writeBuffer=new b.WriteBuffer((function(e){return r._inputHandler.parse(e)})),r}return n(t,e),Object.defineProperty(t.prototype,"onBinary",{get:function(){return this._onBinary.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onData",{get:function(){return this._onData.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onLineFeed",{get:function(){return this._onLineFeed.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onResize",{get:function(){return this._onResize.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onScroll",{get:function(){return this._onScroll.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"cols",{get:function(){return this._bufferService.cols},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"rows",{get:function(){return this._bufferService.rows},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"buffers",{get:function(){return this._bufferService.buffers},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){var t;this._isDisposed||(e.prototype.dispose.call(this),null===(t=this._windowsMode)||void 0===t||t.dispose(),this._windowsMode=void 0)},t.prototype.write=function(e,t){this._writeBuffer.write(e,t)},t.prototype.writeSync=function(e){this._writeBuffer.writeSync(e)},t.prototype.resize=function(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,l.MINIMUM_COLS),t=Math.max(t,l.MINIMUM_ROWS),this._bufferService.resize(e,t))},t.prototype.scroll=function(e,t){void 0===t&&(t=!1);var r,i=this._bufferService.buffer;(r=this._cachedBlankLine)&&r.length===this.cols&&r.getFg(0)===e.fg&&r.getBg(0)===e.bg||(r=i.getBlankLine(e,t),this._cachedBlankLine=r),r.isWrapped=t;var n=i.ybase+i.scrollTop,o=i.ybase+i.scrollBottom;if(0===i.scrollTop){var s=i.lines.isFull;o===i.lines.length-1?s?i.lines.recycle().copyFrom(r):i.lines.push(r.clone()):i.lines.splice(o+1,0,r.clone()),s?this._bufferService.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this._bufferService.isUserScrolling||i.ydisp++)}else{var a=o-n+1;i.lines.shiftElements(n+1,a-1,-1),i.lines.set(o,r.clone())}this._bufferService.isUserScrolling||(i.ydisp=i.ybase),this._dirtyRowService.markRangeDirty(i.scrollTop,i.scrollBottom),this._onScroll.fire(i.ydisp)},t.prototype.scrollLines=function(e,t){var r=this._bufferService.buffer;if(e<0){if(0===r.ydisp)return;this._bufferService.isUserScrolling=!0}else e+r.ydisp>=r.ybase&&(this._bufferService.isUserScrolling=!1);var i=r.ydisp;r.ydisp=Math.max(Math.min(r.ydisp+e,r.ybase),0),i!==r.ydisp&&(t||this._onScroll.fire(r.ydisp))},t.prototype.scrollPages=function(e){this.scrollLines(e*(this.rows-1))},t.prototype.scrollToTop=function(){this.scrollLines(-this._bufferService.buffer.ydisp)},t.prototype.scrollToBottom=function(){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)},t.prototype.scrollToLine=function(e){var t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)},t.prototype.addEscHandler=function(e,t){return this._inputHandler.addEscHandler(e,t)},t.prototype.addDcsHandler=function(e,t){return this._inputHandler.addDcsHandler(e,t)},t.prototype.addCsiHandler=function(e,t){return this._inputHandler.addCsiHandler(e,t)},t.prototype.addOscHandler=function(e,t){return this._inputHandler.addOscHandler(e,t)},t.prototype._setup=function(){this.optionsService.options.windowsMode&&this._enableWindowsMode()},t.prototype.reset=function(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this._coreService.reset(),this._coreMouseService.reset()},t.prototype._updateOptions=function(e){var t;switch(e){case"scrollback":this.buffers.resize(this.cols,this.rows);break;case"windowsMode":this.optionsService.options.windowsMode?this._enableWindowsMode():(null===(t=this._windowsMode)||void 0===t||t.dispose(),this._windowsMode=void 0)}},t.prototype._enableWindowsMode=function(){var e=this;if(!this._windowsMode){var t=[];t.push(this.onLineFeed(g.updateWindowsModeWrappedState.bind(null,this._bufferService))),t.push(this.addCsiHandler({final:"H"},(function(){return g.updateWindowsModeWrappedState(e._bufferService),!1}))),this._windowsMode={dispose:function(){for(var e=0,r=t;e0?n[0].index:t.length;if(t.length!==u)throw new Error("[createInstance] First service dependency of "+e.name+" at position "+(u+1)+" conflicts with "+t.length+" static arguments");return new(e.bind.apply(e,i([void 0],i(t,s))))},e}();t.InstantiationService=a},function(e,t,r){"use strict";var i=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}},o=this&&this.__spreadArrays||function(){for(var e=0,t=0,r=arguments.length;t=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=t.MINIMUM_ROWS=t.MINIMUM_COLS=void 0;var a=r(1),c=r(69),l=r(0),h=r(2);t.MINIMUM_COLS=2,t.MINIMUM_ROWS=1;var u=function(e){function r(r){var i=e.call(this)||this;return i._optionsService=r,i.isUserScrolling=!1,i._onResize=new l.EventEmitter,i.cols=Math.max(r.options.cols,t.MINIMUM_COLS),i.rows=Math.max(r.options.rows,t.MINIMUM_ROWS),i.buffers=new c.BufferSet(r,i),i}return n(r,e),Object.defineProperty(r.prototype,"onResize",{get:function(){return this._onResize.event},enumerable:!1,configurable:!0}),Object.defineProperty(r.prototype,"buffer",{get:function(){return this.buffers.active},enumerable:!1,configurable:!0}),r.prototype.dispose=function(){e.prototype.dispose.call(this),this.buffers.dispose()},r.prototype.resize=function(e,t){this.cols=e,this.rows=t,this.buffers.resize(e,t),this.buffers.setupTabStops(this.cols),this._onResize.fire({cols:e,rows:t})},r.prototype.reset=function(){this.buffers.dispose(),this.buffers=new c.BufferSet(this._optionsService,this),this.isUserScrolling=!1},r=o([s(0,a.IOptionsService)],r)}(h.Disposable);t.BufferService=u},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;var o=r(70),s=r(0),a=function(e){function t(t,r){var i=e.call(this)||this;return i._onBufferActivate=i.register(new s.EventEmitter),i._normal=new o.Buffer(!0,t,r),i._normal.fillViewportRows(),i._alt=new o.Buffer(!1,t,r),i._activeBuffer=i._normal,i.setupTabStops(),i}return n(t,e),Object.defineProperty(t.prototype,"onBufferActivate",{get:function(){return this._onBufferActivate.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"alt",{get:function(){return this._alt},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"active",{get:function(){return this._activeBuffer},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"normal",{get:function(){return this._normal},enumerable:!1,configurable:!0}),t.prototype.activateNormalBuffer=function(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))},t.prototype.activateAltBuffer=function(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))},t.prototype.resize=function(e,t){this._normal.resize(e,t),this._alt.resize(e,t)},t.prototype.setupTabStops=function(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)},t}(r(2).Disposable);t.BufferSet=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.BufferStringIterator=t.Buffer=t.MAX_BUFFER_SIZE=void 0;var i=r(71),n=r(16),o=r(4),s=r(3),a=r(72),c=r(73),l=r(20),h=r(6);t.MAX_BUFFER_SIZE=4294967295;var u=function(){function e(e,t,r){this._hasScrollback=e,this._optionsService=t,this._bufferService=r,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.savedY=0,this.savedX=0,this.savedCurAttrData=n.DEFAULT_ATTR_DATA.clone(),this.savedCharset=l.DEFAULT_CHARSET,this.markers=[],this._nullCell=o.CellData.fromCharData([0,s.NULL_CELL_CHAR,s.NULL_CELL_WIDTH,s.NULL_CELL_CODE]),this._whitespaceCell=o.CellData.fromCharData([0,s.WHITESPACE_CELL_CHAR,s.WHITESPACE_CELL_WIDTH,s.WHITESPACE_CELL_CODE]),this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new i.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}return e.prototype.getNullCell=function(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new h.ExtendedAttrs),this._nullCell},e.prototype.getWhitespaceCell=function(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new h.ExtendedAttrs),this._whitespaceCell},e.prototype.getBlankLine=function(e,t){return new n.BufferLine(this._bufferService.cols,this.getNullCell(e),t)},Object.defineProperty(e.prototype,"hasScrollback",{get:function(){return this._hasScrollback&&this.lines.maxLength>this._rows},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"isCursorInViewport",{get:function(){var e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:r},e.prototype.fillViewportRows=function(e){if(0===this.lines.length){void 0===e&&(e=n.DEFAULT_ATTR_DATA);for(var t=this._rows;t--;)this.lines.push(this.getBlankLine(e))}},e.prototype.clear=function(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new i.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()},e.prototype.resize=function(e,t){var r=this.getNullCell(n.DEFAULT_ATTR_DATA),i=this._getCorrectBufferLength(t);if(i>this.lines.maxLength&&(this.lines.maxLength=i),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+s+1?(this.ybase--,s++,this.ydisp>0&&this.ydisp--):this.lines.push(new n.BufferLine(e,r)));else for(a=this._rows;a>t;a--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(i0&&(this.lines.trimStart(c),this.ybase=Math.max(this.ybase-c,0),this.ydisp=Math.max(this.ydisp-c,0),this.savedY=Math.max(this.savedY-c,0)),this.lines.maxLength=i}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),s&&(this.y+=s),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(o=0;othis._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))},e.prototype._reflowLarger=function(e,t){var r=a.reflowLargerGetLinesToRemove(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(n.DEFAULT_ATTR_DATA));if(r.length>0){var i=a.reflowLargerCreateNewLayout(this.lines,r);a.reflowLargerApplyNewLayout(this.lines,i.layout),this._reflowLargerAdjustViewport(e,t,i.countRemoved)}},e.prototype._reflowLargerAdjustViewport=function(e,t,r){for(var i=this.getNullCell(n.DEFAULT_ATTR_DATA),o=r;o-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;s--){var c=this.lines.get(s);if(!(!c||!c.isWrapped&&c.getTrimmedLength()<=e)){for(var l=[c];c.isWrapped&&s>0;)c=this.lines.get(--s),l.unshift(c);var h=this.ybase+this.y;if(!(h>=s&&h0&&(i.push({start:s+l.length+o,newLines:p}),o+=p.length),l.push.apply(l,p);var y=f.length-1,b=f[y];0===b&&(b=f[--y]);for(var S=l.length-_-1,m=u;S>=0;){var C=Math.min(m,b);if(l[y].copyCellsFrom(l[S],m-C,b-C,C,!0),0===(b-=C)&&(b=f[--y]),0===(m-=C)){S--;var w=Math.max(S,0);m=a.getWrappedLineTrimmedLength(l,w,this._cols)}}for(v=0;v0;)0===this.ybase?this.y0){var L=[],A=[];for(v=0;v=0;v--)if(D&&D.start>k+T){for(var O=D.newLines.length-1;O>=0;O--)this.lines.set(v--,D.newLines[O]);v++,L.push({index:k+1,amount:D.newLines.length}),T+=D.newLines.length,D=i[++x]}else this.lines.set(v,A[k--]);var M=0;for(v=L.length-1;v>=0;v--)L[v].index+=M,this.lines.onInsertEmitter.fire(L[v]),M+=L[v].amount;var P=Math.max(0,R+o-this.lines.maxLength);P>0&&this.lines.onTrimEmitter.fire(P)}},e.prototype.stringIndexToBufferIndex=function(e,t,r){for(void 0===r&&(r=!1);t;){var i=this.lines.get(e);if(!i)return[-1,-1];for(var n=r?i.getTrimmedLength():i.length,o=0;o0&&this.lines.get(t).isWrapped;)t--;for(;r+10;);return e>=this._cols?this._cols-1:e<0?0:e},e.prototype.nextStop=function(e){for(null==e&&(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e},e.prototype.addMarker=function(e){var t=this,r=new c.Marker(e);return this.markers.push(r),r.register(this.lines.onTrim((function(e){r.line-=e,r.line<0&&r.dispose()}))),r.register(this.lines.onInsert((function(e){r.line>=e.index&&(r.line+=e.amount)}))),r.register(this.lines.onDelete((function(e){r.line>=e.index&&r.linee.index&&(r.line-=e.amount)}))),r.register(r.onDispose((function(){return t._removeMarker(r)}))),r},e.prototype._removeMarker=function(e){this.markers.splice(this.markers.indexOf(e),1)},e.prototype.iterator=function(e,t,r,i,n){return new f(this,e,t,r,i,n)},e}();t.Buffer=u;var f=function(){function e(e,t,r,i,n,o){void 0===r&&(r=0),void 0===i&&(i=e.lines.length),void 0===n&&(n=0),void 0===o&&(o=0),this._buffer=e,this._trimRight=t,this._startIndex=r,this._endIndex=i,this._startOverscan=n,this._endOverscan=o,this._startIndex<0&&(this._startIndex=0),this._endIndex>this._buffer.lines.length&&(this._endIndex=this._buffer.lines.length),this._current=this._startIndex}return e.prototype.hasNext=function(){return this._currentthis._endIndex+this._endOverscan&&(e.last=this._endIndex+this._endOverscan),e.first=Math.max(e.first,0),e.last=Math.min(e.last,this._buffer.lines.length);for(var t="",r=e.first;r<=e.last;++r)t+=this._buffer.translateBufferLineToString(r,this._trimRight);return this._current=e.last+1,{range:e,content:t}},e}();t.BufferStringIterator=f},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;var i=r(0),n=function(){function e(e){this._maxLength=e,this.onDeleteEmitter=new i.EventEmitter,this.onInsertEmitter=new i.EventEmitter,this.onTrimEmitter=new i.EventEmitter,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}return Object.defineProperty(e.prototype,"onDelete",{get:function(){return this.onDeleteEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onInsert",{get:function(){return this.onInsertEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"onTrim",{get:function(){return this.onTrimEmitter.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"maxLength",{get:function(){return this._maxLength},set:function(e){if(this._maxLength!==e){for(var t=new Array(e),r=0;rthis._length)for(var t=this._length;t=e;n--)this._array[this._getCyclicIndex(n+r.length)]=this._array[this._getCyclicIndex(n)];for(n=0;nthis._maxLength){var o=this._length+r.length-this._maxLength;this._startIndex+=o,this._length=this._maxLength,this.onTrimEmitter.fire(o)}else this._length+=r.length},e.prototype.trimStart=function(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)},e.prototype.shiftElements=function(e,t,r){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+r<0)throw new Error("Cannot shift elements in list beyond index 0");if(r>0){for(var i=t-1;i>=0;i--)this.set(e+i+r,this.get(e+i));var n=e+t+r-this._length;if(n>0)for(this._length+=n;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(i=0;i=a&&n0&&(S>u||0===h[S].getTrimmedLength());S--)b++;b>0&&(s.push(a+h.length-b),s.push(b)),a+=h.length-1}}}return s},t.reflowLargerCreateNewLayout=function(e,t){for(var r=[],i=0,n=t[i],o=0,s=0;sl&&(s-=l,a++);var h=2===e[a].getWidth(s-1);h&&s--;var u=h?r-1:r;n.push(u),c+=u}return n},t.getWrappedLineTrimmedLength=i},function(e,t,r){"use strict";var i,n=this&&this.__extends||(i=function(e,t){return(i=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r])})(e,t)},function(e,t){function r(){this.constructor=e}i(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;var o=r(0),s=function(e){function t(r){var i=e.call(this)||this;return i.line=r,i._id=t._nextId++,i.isDisposed=!1,i._onDispose=new o.EventEmitter,i}return n(t,e),Object.defineProperty(t.prototype,"id",{get:function(){return this._id},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onDispose",{get:function(){return this._onDispose.event},enumerable:!1,configurable:!0}),t.prototype.dispose=function(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire())},t._nextId=1,t}(r(2).Disposable);t.Marker=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=t.DEFAULT_BELL_SOUND=void 0;var i=r(0),n=r(11),o=r(33);t.DEFAULT_BELL_SOUND="data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",t.DEFAULT_OPTIONS=Object.freeze({cols:80,rows:24,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,bellSound:t.DEFAULT_BELL_SOUND,bellStyle:"none",drawBoldTextInBrightColors:!0,fastScrollModifier:"alt",fastScrollSensitivity:5,fontFamily:"courier-new, courier, monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",lineHeight:1,linkTooltipHoverDuration:500,letterSpacing:0,logLevel:"info",scrollback:1e3,scrollSensitivity:1,screenReaderMode:!1,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!0,allowTransparency:!1,tabStopWidth:8,theme:{},rightClickSelectsWord:n.isMac,rendererType:"canvas",windowOptions:{},windowsMode:!1,wordSeparator:" ()[]{}',\"`",convertEol:!1,termName:"xterm",cancelEvents:!1});var s=["cols","rows"],a=function(){function e(e){this._onOptionChange=new i.EventEmitter,this.options=o.clone(t.DEFAULT_OPTIONS);for(var r=0,n=Object.keys(e);r=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},s=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;var a=r(1),c=r(0),l=r(33),h=r(2),u=Object.freeze({insertMode:!1}),f=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,origin:!1,reverseWraparound:!1,sendFocus:!1,wraparound:!0}),_=function(e){function t(t,r,i,n){var o=e.call(this)||this;return o._bufferService=r,o._logService=i,o._optionsService=n,o.isCursorInitialized=!1,o.isCursorHidden=!1,o._onData=o.register(new c.EventEmitter),o._onUserInput=o.register(new c.EventEmitter),o._onBinary=o.register(new c.EventEmitter),o._scrollToBottom=t,o.register({dispose:function(){return o._scrollToBottom=void 0}}),o.modes=l.clone(u),o.decPrivateModes=l.clone(f),o}return n(t,e),Object.defineProperty(t.prototype,"onData",{get:function(){return this._onData.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onUserInput",{get:function(){return this._onUserInput.event},enumerable:!1,configurable:!0}),Object.defineProperty(t.prototype,"onBinary",{get:function(){return this._onBinary.event},enumerable:!1,configurable:!0}),t.prototype.reset=function(){this.modes=l.clone(u),this.decPrivateModes=l.clone(f)},t.prototype.triggerDataEvent=function(e,t){if(void 0===t&&(t=!1),!this._optionsService.options.disableStdin){var r=this._bufferService.buffer;r.ybase!==r.ydisp&&this._scrollToBottom(),t&&this._onUserInput.fire(),this._logService.debug('sending data "'+e+'"',(function(){return e.split("").map((function(e){return e.charCodeAt(0)}))})),this._onData.fire(e)}},t.prototype.triggerBinaryEvent=function(e){this._optionsService.options.disableStdin||(this._logService.debug('sending binary "'+e+'"',(function(){return e.split("").map((function(e){return e.charCodeAt(0)}))})),this._onBinary.fire(e))},t=o([s(1,a.IBufferService),s(2,a.ILogService),s(3,a.IOptionsService)],t)}(h.Disposable);t.CoreService=_},function(e,t,r){"use strict";var i=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreMouseService=void 0;var o=r(1),s=r(0),a={NONE:{events:0,restrict:function(){return!1}},X10:{events:1,restrict:function(e){return 4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)}},VT200:{events:19,restrict:function(e){return 32!==e.action}},DRAG:{events:23,restrict:function(e){return 32!==e.action||3!==e.button}},ANY:{events:31,restrict:function(e){return!0}}};function c(e,t){var r=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(r|=64,r|=e.action):(r|=3&e.button,4&e.button&&(r|=64),8&e.button&&(r|=128),32===e.action?r|=32:0!==e.action||t||(r|=3)),r}var l=String.fromCharCode,h={DEFAULT:function(e){var t=[c(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":""+l(t[0])+l(t[1])+l(t[2])},SGR:function(e){var t=0===e.action&&4!==e.button?"m":"M";return"[<"+c(e,!0)+";"+e.col+";"+e.row+t}},u=function(){function e(e,t){this._bufferService=e,this._coreService=t,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._onProtocolChange=new s.EventEmitter,this._lastEvent=null;for(var r=0,i=Object.keys(a);r=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._compareEvents(this._lastEvent,e))return!1;if(!this._protocols[this._activeProtocol].restrict(e))return!1;var t=this._encodings[this._activeEncoding](e);return t&&("DEFAULT"===this._activeEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0},e.prototype.explainEvents=function(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}},e.prototype._compareEvents=function(e,t){return e.col===t.col&&(e.row===t.row&&(e.button===t.button&&(e.action===t.action&&(e.ctrl===t.ctrl&&(e.alt===t.alt&&e.shift===t.shift)))))},e=i([n(0,o.IBufferService),n(1,o.ICoreService)],e)}();t.CoreMouseService=u},function(e,t,r){"use strict";var i=this&&this.__decorate||function(e,t,r,i){var n,o=arguments.length,s=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(s=(o<3?n(s):o>3?n(t,r,s):n(t,r))||s);return o>3&&s&&Object.defineProperty(t,r,s),s},n=this&&this.__param||function(e,t){return function(r,i){t(r,i,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DirtyRowService=void 0;var o=r(1),s=function(){function e(e){this._bufferService=e,this.clearRange()}return Object.defineProperty(e.prototype,"start",{get:function(){return this._start},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"end",{get:function(){return this._end},enumerable:!1,configurable:!0}),e.prototype.clearRange=function(){this._start=this._bufferService.buffer.y,this._end=this._bufferService.buffer.y},e.prototype.markDirty=function(e){ethis._end&&(this._end=e)},e.prototype.markRangeDirty=function(e,t){if(e>t){var r=e;e=t,t=r}ethis._end&&(this._end=t)},e.prototype.markAllDirty=function(){this.markRangeDirty(0,this._bufferService.rows-1)},e=i([n(0,o.IBufferService)],e)}();t.DirtyRowService=s},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;var i=r(0),n=r(79),o=function(){function e(){this._providers=Object.create(null),this._active="",this._onChange=new i.EventEmitter;var e=new n.UnicodeV6;this.register(e),this._active=e.version,this._activeProvider=e}return Object.defineProperty(e.prototype,"onChange",{get:function(){return this._onChange.event},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"versions",{get:function(){return Object.keys(this._providers)},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"activeVersion",{get:function(){return this._active},set:function(e){if(!this._providers[e])throw new Error('unknown Unicode version "'+e+'"');this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)},enumerable:!1,configurable:!0}),e.prototype.register=function(e){this._providers[e.version]=e},e.prototype.wcwidth=function(e){return this._activeProvider.wcwidth(e)},e.prototype.getStringCellWidth=function(e){for(var t=0,r=e.length,i=0;i=r)return t+this.wcwidth(n);var o=e.charCodeAt(i);56320<=o&&o<=57343?n=1024*(n-55296)+o-56320+65536:t+=this.wcwidth(o)}t+=this.wcwidth(n)}return t},e}();t.UnicodeService=o},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;var i,n=r(15),o=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],s=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];var a=function(){function e(){if(this.version="6",!i){i=new Uint8Array(65536),n.fill(i,1),i[0]=0,n.fill(i,0,1,32),n.fill(i,0,127,160),n.fill(i,2,4352,4448),i[9001]=2,i[9002]=2,n.fill(i,2,11904,42192),i[12351]=1,n.fill(i,2,44032,55204),n.fill(i,2,63744,64256),n.fill(i,2,65040,65050),n.fill(i,2,65072,65136),n.fill(i,2,65280,65377),n.fill(i,2,65504,65511);for(var e=0;et[n][1])return!1;for(;n>=i;)if(e>t[r=i+n>>1][1])i=r+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1},e}();t.UnicodeV6=a},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0;var i=function(){function e(){this.glevel=0,this._charsets=[]}return e.prototype.reset=function(){this.charset=void 0,this._charsets=[],this.glevel=0},e.prototype.setgLevel=function(e){this.glevel=e,this.charset=this._charsets[e]},e.prototype.setgCharset=function(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)},e}();t.CharsetService=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=void 0;var i=r(3);t.updateWindowsModeWrappedState=function(e){var t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),r=null==t?void 0:t.get(e.cols-1),n=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);n&&r&&(n.isWrapped=r[i.CHAR_DATA_CODE_INDEX]!==i.NULL_CELL_CODE&&r[i.CHAR_DATA_CODE_INDEX]!==i.WHITESPACE_CELL_CODE)}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;var i=function(){function e(e){this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0}return e.prototype.writeSync=function(e){if(this._writeBuffer.length){for(var t=this._bufferOffset;t5e7)throw new Error("write data discarded, use flow control to avoid losing data");this._writeBuffer.length||(this._bufferOffset=0,setTimeout((function(){return r._innerWrite()}))),this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)},e.prototype._innerWrite=function(){for(var e=this,t=Date.now();this._writeBuffer.length>this._bufferOffset;){var r=this._writeBuffer[this._bufferOffset],i=this._callbacks[this._bufferOffset];if(this._bufferOffset++,this._action(r),this._pendingData-=r.length,i&&i(),Date.now()-t>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout((function(){return e._innerWrite()}),0)):(this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0)},e}();t.WriteBuffer=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0;var i=function(){function e(){this._addons=[]}return e.prototype.dispose=function(){for(var e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()},e.prototype.loadAddon=function(e,t){var r=this,i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=function(){return r._wrappedAddonDispose(i)},t.activate(e)},e.prototype._wrappedAddonDispose=function(e){if(!e.isDisposed){for(var t=-1,r=0;rr.cols;)p-=r.cols,h++;var v={start:{x:l+1,y:d+1},end:{x:p,y:h}};c.push({range:v,text:f,activate:i})}return c},e._translateBufferLineToStringWithWrap=function(e,t,n){var r,i,o="";do{if(!(s=n.buffer.active.getLine(e)))break;s.isWrapped&&e--,i=s.isWrapped}while(i);var a=e;do{var s,u=n.buffer.active.getLine(e+1);if(r=!!u&&u.isWrapped,!(s=n.buffer.active.getLine(e)))break;o+=s.translateToString(!r&&t).substring(0,n.cols),e++}while(r);return[o,a]},e}();t.LinkComputer=i}])})); \ No newline at end of file diff --git a/BTPanel/templates/default/autherr.html b/BTPanel/templates/default/autherr.html index 2cdc7789..67605900 100644 --- a/BTPanel/templates/default/autherr.html +++ b/BTPanel/templates/default/autherr.html @@ -1,168 +1,145 @@ - + + + + + Ingress verification failed + - + .logo { + cursor: pointer; + } + + - -
                                    - - - + + + diff --git a/BTPanel/templates/default/error3.html b/BTPanel/templates/default/error3.html index 7f79dacc..4c3a5130 100644 --- a/BTPanel/templates/default/error3.html +++ b/BTPanel/templates/default/error3.html @@ -1,283 +1,292 @@ -{% extends "layout.html" %} -{% block content %} +{% extends "layout.html" %} {% block content %} -
                                    -
                                    +
                                    +
                                    {% if 'error_msg' in data %} -
                                    -
                                    -
                                    aaPanel WAF
                                    -
                                    +
                                    +
                                    +
                                    aaPanel WAF
                                    -
                                    -
                                    -

                                    Tip: This page can be turned off in the panel settings

                                    -
                                    -
                                    -
                                    - Nginx WAF function introduction -
                                      -
                                    • Only supports Nginx
                                    • -
                                    • Defend against CC attacks
                                    • -
                                    • Keyword blocking
                                    • -
                                    • Block malicious scans
                                    • -
                                    • Stop hackers
                                    • -
                                    -
                                    - Buy now -
                                    +
                                    +
                                    +
                                    +

                                    Tip: This page can be turned off in the panel settings

                                    +
                                    +
                                    +
                                    + Nginx WAF function introduction +
                                      +
                                    • Only supports Nginx
                                    • +
                                    • Defend against CC attacks
                                    • +
                                    • Keyword blocking
                                    • +
                                    • Block malicious scans
                                    • +
                                    • Stop hackers
                                    • +
                                    + -
                                    -
                                      -
                                    • Overview
                                    • -
                                    • Report
                                    • -
                                    • Global
                                    • -
                                    • WebSite
                                    • -
                                    • Blockade
                                    • -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    +
                                    +
                                    +
                                      +
                                    • Overview
                                    • +
                                    • Report
                                    • +
                                    • Global
                                    • +
                                    • WebSite
                                    • +
                                    • Blockade
                                    • +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    {% else %} -
                                    -
                                    -
                                    aaPanel WAF
                                    -
                                    +
                                    +
                                    +
                                    aaPanel WAF
                                    -
                                    -
                                    -

                                    Tip: This page can be turned off in the panel settings

                                    -
                                    -
                                    -
                                    - Nginx WAF function introduction -
                                      -
                                    • Only supports Nginx
                                    • -
                                    • Defend against CC attacks
                                    • -
                                    • Keyword blocking
                                    • -
                                    • Block malicious scans
                                    • -
                                    • Stop hackers
                                    • -
                                    -
                                    - Buy now -
                                    +
                                    +
                                    +
                                    +

                                    Tip: This page can be turned off in the panel settings

                                    +
                                    +
                                    +
                                    + Nginx WAF function introduction +
                                      +
                                    • Only supports Nginx
                                    • +
                                    • Defend against CC attacks
                                    • +
                                    • Keyword blocking
                                    • +
                                    • Block malicious scans
                                    • +
                                    • Stop hackers
                                    • +
                                    + -
                                    -
                                      -
                                    • Overview
                                    • -
                                    • Report
                                    • -
                                    • Global
                                    • -
                                    • WebSite
                                    • -
                                    • Blockade
                                    • -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    +
                                    +
                                    +
                                      +
                                    • Overview
                                    • +
                                    • Report
                                    • +
                                    • Global
                                    • +
                                    • WebSite
                                    • +
                                    • Blockade
                                    • +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    {% endif %} -
                                    +
                                    -{% endblock %} -{% block scripts %} - {{ super() }} - + -{% endblock %} \ No newline at end of file + } + ); + }, 1000); + } + // $('.thumbnail-box').on('click',function(){ + // layer.open({ + // title:false, + // btn:false, + // shadeClose:true, + // closeBtn: 2, + // area:['950px','725px'], + // content:'
                                    ' + // }) + // }) + +{% endblock %} diff --git a/BTPanel/templates/default/firewall.html b/BTPanel/templates/default/firewall.html index b31ca809..54309acb 100644 --- a/BTPanel/templates/default/firewall.html +++ b/BTPanel/templates/default/firewall.html @@ -842,5 +842,6 @@ {% endblock %} {% block scripts %} {{ super() }} + {% endblock %} diff --git a/BTPanel/templates/default/index_new.html b/BTPanel/templates/default/index_new.html new file mode 100644 index 00000000..7fa969ab --- /dev/null +++ b/BTPanel/templates/default/index_new.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + aaPanel Linux Panel + + + + + + + + + + + + + +
                                    + + + + + + diff --git a/BTPanel/templates/default/layout.html b/BTPanel/templates/default/layout.html index 08a851aa..4b76e9b3 100644 --- a/BTPanel/templates/default/layout.html +++ b/BTPanel/templates/default/layout.html @@ -1,257 +1,258 @@ - + - - - - - - {{g.title}} - - - - - {% for css_f in g['other_css'] %} - - {% endfor %} - - - - - -
                                    - - -
                                    - - - {% block content %}{% endblock %} -
                                    - + .contextmenu li:hover { + background: #707070; + border-left: 3px solid #333; + } + + .contextmenu li a { + display: block; + padding: 5px 10px; + color: #000000; + text-decoration: none; + transition: ease 0.3s; + cursor: default; + } + + .contextmenu li:hover a { + color: #fff; + } + .toolbar-right { + width: 35px; + height: 35px; + position: fixed; + right: 0; + bottom: 120px; + display: flex; + background-color: #fff; + box-shadow: 0 0 4px 0 #ccc; + flex-direction: column; + flex-wrap: nowrap; + align-items: center; + justify-content: center; + border-radius: 4px; + } + + .toolbar-right .feedback { + display: inline-block; + height: 35px; + width: 35px; + position: relative; + background-repeat: no-repeat; + background-position: center center; + border-radius: 4px; + background-size: 13.5px; + } + .toolbar-right .feedback { + background-image: url('/static/img/feedback.svg'); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } + + .feedback-iframe .layui-layer-content { + overflow: hidden !important; + } + + + + +
                                    + + +
                                    + + + {% block content %}{% endblock %} +
                                    + +
                                    +
                                    - -
                                    - + - {% if request.path == '/btwaf/index' %} - - - - - - + {% if request.path == '/btwaf/index' %} + + + + + + - - - - {% endif %} + + + + {% endif %} {% block scripts %} + + + + + + - {% block scripts %} - - - - - - - - - - - - - - - {% for js_f in g['other_js'] %} - - {% endfor %} - + + + + + + {% for js_f in g['other_js'] %} + + {% endfor %} + - {% endblock %} -
                                    - + if (bt.get_cookie('bt_user_info') == null || bt.get_cookie('bt_user_info') == '') { + bt.pub.get_user_info(function (userInfo) { + if (userInfo.status) { + bt.set_cookie('bt_user_info', JSON.stringify(userInfo)); + getPaymentStatus(); + } + }); + } else { + getPaymentStatus(); + } + + {% endblock %} + +
                                    + + diff --git a/BTPanel/templates/default/login.html b/BTPanel/templates/default/login.html index 9e329e80..c02a6578 100644 --- a/BTPanel/templates/default/login.html +++ b/BTPanel/templates/default/login.html @@ -1,750 +1,766 @@ - - - - - {{g.title}} - - + + + +
                                    +
                                    + +
                                    +
                                    {{data['lan']['QR_CORE_LOGIN']}}
                                    +
                                    +
                                    + +
                                    +
                                    +
                                    Scan
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    +
                                    {{data['lan']['SCAN_MORE_SAFETY']}}
                                    +
                                    +
                                    +
                                    +
                                    + + + + - - -
                                    -
                                    - -
                                    -
                                    {{data['lan']['QR_CORE_LOGIN']}}
                                    -
                                    -
                                    - -
                                    -
                                    -
                                    Scan
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    -
                                    {{data['lan']['SCAN_MORE_SAFETY']}}
                                    -
                                    -
                                    -
                                    -
                                    - - - - - - \ No newline at end of file + callback: function () { + var btn = document.querySelector('#auth_verif_btn'); + var codeInput = document.querySelector('.v_code'); + codeInput.onkeydown = function (e) { + if (e.keyCode == 13) btn.click(); + }; + btn.onclick = function () { + var code = codeInput.value.trim(); + if (is_empty(code)) { + popup.msg('Please enter Verification code!'); + return; + } + data['vcode'] = code; + popup.msg('{{data.lan.JS2}}', { icon: 16, shade: [0.3, '#333'] }); + request.post('/login', data, function (rdata) { + popup.remove_msg(); + if (!rdata.status) { + popup.msg(rdata.msg, { icon: 2 }); + return false; + } + popup.msg( + rdata.msg, + { + icon: 16, + time: 800, + shade: [0.3, '#000'], + }, + function () { + window.location.href = '/'; + } + ); + }); + }; + }, + }); + return; + } + var status = res.status; + if (!status) { + popup.msg(res.msg); + codeInput.value = ''; + passwordInput.value = ''; + errorTipsBox.innerHTML = res.msg; + loginBox.classList.add('code'); + show_check_img(); + return; + } + popup.msg(res.msg, { time: 0, icon: 16, shade: [0.3, '#333'] }); + window.location.href = '/'; + }); + }); + }; + // 切换登录方式 + entrance.onclick = function () { + var tips = this.nextSibling.nextSibling; + var icon = tips.querySelector('.icon'); + var text = tips.querySelector('.text'); + var scanCode = document.querySelector('.scan_code'); + var account = document.querySelector('.account'); + if (this.classList.contains('pc')) { + this.classList.remove('pc'); + icon.classList.add('scan'); + icon.classList.remove('safe'); + text.innerText = '{{data.lan.SCAN_MORE_SAFETY}}'; + account.classList.remove('hide'); + scanCode.classList.add('hide'); + clear_control_time(); + } else { + this.classList.add('pc'); + icon.classList.add('safe'); + icon.classList.remove('scan'); + text.innerText = 'Switch account login'; + account.classList.add('hide'); + scanCode.classList.remove('hide'); + generate_qrcode(); + set_control_time(); + } + }; + window.onresize = function () { + popup.set_msg_center(); + popup.set_open_center(); + }; + if ("{{data['app_login']}}" == 'True') { + entrance.click(); + } + } + /** + * 请求 + */ + function Request() { + this.xhr = new XMLHttpRequest(); + } + Request.prototype = { + post: function (url, data, callback) { + var option = { + method: 'POST', + url: url, + callback: callback, + }; + var form = new FormData(); + for (var key in data) { + if (data[key] !== '' && data[key] !== undefined && data[key] !== null) { + form.append(key, data[key]); + } + } + option.data = form; + this.http(option); + }, + http: function (option) { + var xhr = this.xhr; + var toUrl = window.location.protocol + '//' + window.location.host + option.url; + var method = option.method || 'GET'; + // 打开连接 + xhr.open(method, toUrl, true); + // 发送请求 + if (method == 'GET') { + xhr.send(); + } else if (method == 'POST') { + xhr.send(option.data); + } + // 请求后的回调接口 + xhr.onreadystatechange = function () { + if (xhr.readyState == 4 && xhr.status == 200) { + var res = xhr.responseText; + res = res ? JSON.parse(res) : ''; + if (option.callback) option.callback(res); + } + }; + }, + }; + /** + * 弹框 + */ + function Popup() { + this.zIndex = 200000; + } + Popup.prototype = { + /** + * 弹框 + */ + open: function (option) { + var _this = this; + option = option || {}; + this.set_open_option(option); + this.add_overlay(option); + var container = document.createElement('div'); + container.classList.add('layui-layer'); + var content = document.createElement('div'); + content.classList.add('layui-layer-content'); + var setwin = document.createElement('div'); + setwin.classList.add('layui-layer-setwin'); + var close = document.createElement('a'); + close.classList.add('layui-layer-close'); + if (option.area[0]) container.style.width = option.area[0]; + if (option.area[1]) container.style.height = option.area[1]; + content.innerHTML = option.content; + close.onclick = function () { + _this.remove_open(); + }; + container.style['z-index'] = this.zIndex; + this.zIndex++; + setwin.appendChild(close); + container.appendChild(content); + container.appendChild(setwin); + document.documentElement.appendChild(container); + this.set_open_center('layui-layer'); + if (option.callback) option.callback(); + }, + /** + * 提示框 + */ + msg: function (msg, option, callback) { + var _this = this; + option = option || {}; + this.remove_msg(); + this.add_overlay(option); + this.set_msg_option(option); + var container = document.createElement('div'); + container.className = 'layui-layer-msg'; + container.style['z-index'] = 200000; + var box = document.createElement('div'); + box.className = 'layui-layer-content'; + var icon = document.createElement('i'); + icon.className = 'layui-layer-ico layui-layer-ico' + option.icon; + box.appendChild(icon); + box.innerHTML += msg; + container.style['z-index'] = this.zIndex; + this.zIndex++; + container.appendChild(box); + document.documentElement.appendChild(container); + this.set_msg_center(); + if (option.time !== 0) { + setTimeout(function () { + _this.remove_msg(); + if (callback) callback(); + }, option.time); + } + }, + /** + * 添加遮罩层 + */ + add_overlay: function (option) { + if (option.hasOwnProperty('shade')) { + var shade = option.shade; + var overlay = document.createElement('div'); + overlay.className = 'layui-layer-shade'; + overlay.style.opacity = shade[0]; + overlay.style['background-color'] = shade[1]; + overlay.style['z-index'] = this.zIndex; + this.zIndex++; + document.documentElement.appendChild(overlay); + } + }, + /** + * 弹框配置项 + */ + set_open_option: function (option) { + var defaultOption = { + area: ['390px', '280px'], + shade: [0.3, '#000'], + content: '', + }; + this.set_option(option, defaultOption); + }, + /** + * msg配置项 + */ + set_msg_option: function (option) { + var defaultOption = { + icon: 2, + time: 2000, + }; + this.set_option(option, defaultOption); + }, + /** + * 设置默认配置项 + */ + set_option: function (option, defaultOption) { + for (var key in defaultOption) { + if (option[key] === undefined) { + option[key] = defaultOption[key]; + } + } + }, + /** + * 提示框设置居中 + */ + set_msg_center: function () { + this.set_center('layui-layer-msg'); + }, + /** + * 弹框设置居中 + */ + set_open_center: function () { + this.set_center('layui-layer'); + }, + /** + * 设置居中 + */ + set_open_center: function () { + this.set_center('layui-layer'); + }, + /** + * 居中 + */ + set_center: function (_class) { + var dom = document.querySelector('.' + _class); + if (!dom) return; + var bodyWidth = document.documentElement.clientWidth; + var bodyHeight = document.documentElement.clientHeight; + var domWidth = dom.clientWidth; + var domHeight = dom.clientHeight; + dom.style.left = (bodyWidth - domWidth) / 2 + 'px'; + dom.style.top = (bodyHeight - domHeight) / 2 + 'px'; + }, + /** + * 删除提示框 + */ + remove_msg: function () { + this.remove('layui-layer-msg'); + }, + /** + * 删除弹框 + */ + remove_open: function () { + this.remove('layui-layer'); + }, + /** + * 删除 + */ + remove: function (_class) { + var dom = document.querySelector('.' + _class); + if (!dom) return; + var overlay = dom.previousElementSibling; + if (overlay.classList.contains('layui-layer-shade')) { + document.documentElement.removeChild(overlay); + this.zIndex--; + } + document.documentElement.removeChild(dom); + this.zIndex--; + }, + }; + + + diff --git a/README.md b/README.md index 215dbea3..06368e52 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,28 @@ -
                                    - aaPanel -
                                    -
                                    - -
                                    -aaPanel -
                                    -
                                    -
                                    - -[![BTWAF](https://img.shields.io/badge/aaPanel-aaPanel-blue)](https://github.com/aaPanel/aaPanel) -[![social](https://img.shields.io/github/stars/aaPanel/aaPanel?style=social)](https://github.com/aaPanel/aaPanel) - -
                                    -

                                    - Official | - documentation | - Demo | -

                                    - -## About aaPanel - -**aaPanel is a simple but powerful hosting control panel**, it can manage the web server through web-based GUI(Graphical User Interface). - -* **one-click function:** such as one-click install LNMP/LAMP developing environment and software. -* **save the time:** Our main goal is helping users to save the time of deploying, thus users just focus on their own project that is fine. - -## Demo - -Demo:https://demo.aapanel.com/fdgi87jbn/
                                    - -![image](https://github.com/aaPanel/aaPanel/assets/31841517/c40d68f5-1cbb-4117-ab47-b52b14228cce) - -## What can I do - -aaPanel is a server management software that supports the Linux system. - -It can easily manage the server through the Web terminal, improving the operation and maintenance efficiency. - -## Installation - -> Make sure it is a clean operating system, and have not installed Apache /Nginx/php/MySQL from other environments -> aaPanel is developed based on Centos7+, it is strongly recommended to use centos7 + linux distribution*\** - - Note, please execute the installation command with root authority - -* Memory: 512M or more, 768M or more is recommended (Pure panel for about 60M of system memory) - -* Hard disk: More than 100M available hard disk space (Pure panel for about 20M disk space) - -* System: CentOS 7.1+ (Ubuntu16.04+., Debian9.0+), to ensure that it is a clean operating system, there is no other environment with Apache/Nginx/php/MySQL installed (the existing environment can not be installed) - -**aaPanel Installation Command** - -`URL=https://www.aapanel.com/script/install_6.0_en.sh && if [ -f /usr/bin/curl ];then curl -ksSO "$URL" ;else wget --no-check-certificate -O install_6.0_en.sh "$URL";fi;bash install_6.0_en.sh 66959f96` - -**aaPanel Docker Deployment** - -> The docker image is officially released by aaPanel +#aaPanel Docker Deployment +The docker image is officially released by aaPanel Maintained by: [aaPanel](https://www.aapanel.com) -How to use +##How to use `$docker run -d -p 8886:8888 -p 22:21 -p 443:443 -p 80:80 -p 889:888 -v ~/website_data:/www/wwwroot -v ~/mysql_data:/www/server/data -v ~/vhost:/www/server/panel/vhost aapanel/aapanel:lib` Now you can access aaPanel at http://youripaddress:8886/ from your host system. -* Default username:`aapanel` -* Default password:`aapanel123` +Default username:`aapanel` -Port usage analysis -* Control Panel : 8888 -* Phpmyadmin : 888 +Default password:`aapanel123` -Dir usage analysis -* Website data : /www/wwwroot -* Mysql data : /www/server/data -* Vhost file : /www/server/panel/vhost +####Port usage analysis +Control Panel : 8888 +Phpmyadmin : 888 + +####Dir usage analysis +Website data : /www/wwwroot +Mysql data : /www/server/data +Vhost file : /www/server/panel/vhost **Note: after the deployment is complete, please immediately modify the user name and password in the panel settings and add the installation entry** - diff --git a/check-pip-packs.txt b/check-pip-packs.txt new file mode 100644 index 00000000..00601b9c --- /dev/null +++ b/check-pip-packs.txt @@ -0,0 +1,112 @@ +aliyun-python-sdk-core +aliyun-python-sdk-core-v3 +aliyun-python-sdk-kms +async-timeout +bcrypt +beautifulsoup4 +cachelib +cachetools +certifi +cffi +chardet +charset-normalizer +click +configobj +configparser +cos-python-sdk-v5 +crcmod +cryptography +Cython +decorator +dicttoxml +dnspython +docker +enum34 +Flask +Flask-Session +flask-sock +Flask-SQLAlchemy +future +geoip2 +gevent +gevent-websocket +google-api-core +google-api-python-client +google-auth +google-auth-httplib2 +google-auth-oauthlib +google-cloud-core +google-cloud-storage +google-crc32c +google-resumable-media +googleapis-common-protos +greenlet +h11 +httplib2 +idna +importlib-metadata +iniparse +ipaddress +IPy +itsdangerous +Jinja2 +jmespath +kitchen +MarkupSafe +mongo +natsort +oauthlib +oss2 +packaging +paramiko +peewee +pillow +pip +protobuf +psutil +psycopg2-binary +pyasn1 +pyasn1-modules +pyasyncore +pycparser +pycryptodome +pycurl +Pygments +pyinotify +pymongo +PyMySQL +PyNaCl +pyOpenSSL +pyparsing +pypdf +PySocks +pytz +pyudev +pyxattr +PyYAML +qiniu +qrcode +redis +requests +requests-file +requests-oauthlib +rsa +setuptools +simple-websocket +six +soupsieve +SQLAlchemy +supervisor +typing_extensions +upyun +uritemplate +urlgrabber +urllib3 +websocket-client +Werkzeug +wheel +wsproto +xmltodict +zipp +zope.event +zope.interface \ No newline at end of file diff --git a/class/PluginLoader.aarch64.Python3.12.so b/class/PluginLoader.aarch64.Python3.12.so new file mode 100644 index 0000000000000000000000000000000000000000..b1150e3eb846791e616a002d84add0b0f7180d50 GIT binary patch literal 535520 zcmd2k2Ut|c^A7|?C5nm$`>CkJ%F&6afS{nL7r>4=4mjlWC~^lvG>RQNDwafJh|!1! z6?;uYC9y{=v0_3Ldryo#`k#F>OL^W?lb?Ryf4_|Hm^W{Bc6N4lc6Z*(82_NI=4NIU z1S%DTErJ>R&g*F|V!R0-Zf`}u1i@SI5GujHjf93Gt!2rbSAR6!jCc7}5TC$*D*}1{ z=Xk&WbM_be8?UM+;-A9Srg-JgF{v0u5j|H#^!zF?0#@kCrM3VRgZ->=Ca(5~7WkFX zvZA_djAFlw>!O^}tKLo|6Zn-s_X#|QEy4S5`qzdCf~h_^y<}5Q@hde)Y@03cV7I&V z>La9l*Qt5meV;U+;?wJSbXQGSM<1NZ=-XN8ZtWTe%pX(f1DnN2Vk zswh%~3QhgtuW>f%Gn)vCFPAzLDXf0-O|u$X(Ogj2rsdn)UYaz}e4t=wCA3TPXi_n} zieQr_{Owt4Pd)YF!QMqWo4uAwa@Uj5v*!h{JH}f{HDO}TS|0F^j|L(^;obA?q6p@tBy@YY&s z`#Yuy#~reLgxWTt!k!GlBfa7%8!LOkEUm^=Gr#cKf=aNfB?t;DM@7Yz7T)Hq(=3{p zrFpxUJ6KsLGOX$bCFVJJo2MsPnb}l!wh$@{j)Gw2?cyz1`B{4}Y+{?=G|jGpW0n54 zHH0?6%e=s>wv|^ z`A%tau0e!j<%(ds^#v>68iJKj0ft%t^i2~xv-Bdt8}5YL1O?~y5-LpfQ3yho;O*`F zVMRgVE7(jm%k_FBICu-q6ys_uGAo$-hYRU~w@;Iaf2v%yu(3N76F27O%;#`-Zl0|`GgC;LhZ_a)hb)qEK@WeyEnawqmR9nE1$IXg3S*$`*pXLdfWDp@=YRBHT|GYWmtZnN@Y{&&Km-f=%V6856+rtbEL2%sW)5 z+``;OsAATgYEvz-a1`M6jKoIQ4t#`~`T0x;An3&Fvx=N@mnCidTqZ!kMGJs`~r zY~RDH$UMQ^s;S_;Oyz9_IX|u(A{2 zM}S@I4=B6wBILvMXMlYG`vDFEuyO?87l5Mx#{o_OuyPvUEWibTB7lnktXu)O3UCwP z4#0hY2LP=64e$`)3BWUe7XYk4mkQ!2qAq-wYQHza!}HNVrA;=m3TS3!0!N;0Ez*w0>#hPW93E9P*u1gHdH1yBXR8sGx}8vw9!p#eZ6fW`n#0Ga_b z2WSD{0KiIXxV8mo58w#k4B!Ib4&Vvk1;9#YxcUP419SuE4nQH2pn~6@0E7at(g&`6 z0s7P5gWx(CKn)NMpaF;khyu_8L<0;3U_}qt1c2cH$p9$;p8F=-Lng#F;z_$S30jvPX23Q5aOAh>A z4X_4aExLGtW>3| zHC(IF@9J>11*ij151;`+Lx9ErO#zw#uwoC_<^U}L+5xl&a0YMz@BsK2fRzq#^`hUM z;OYzD2ha^55TFMDD?Q<=0tf~O1?U6NA7CIr7{FivHGl>n3V;Ss!rU6U`m;o>oU^c)U09G>LIv-#mz#@RJ0KNwJ7GMRyN`O@W z-vg`$SPPH~unu57z(#;g09ydI0sH{K$}YI(0ptVh12_n92;eBdaR63M!u1rud4LN5 zMF4*QTmoR_3S6%N+yuAO zD=Bat1uz;Q4PYDqE92oh5nvL)RDfv!(*b4x%mQF#E?wurbs@kP0E+>#0G0!6TD1N1 zxM7pJo@-Y7@ROa2R{GzDHBCuQ+xn!nW#fD9c^$@v);<`rWl@*??oo5r`82z;Jm800 zMvL7~KKN*lcg0^Co!dOx{nuTKtN+yC=MilpXRZBl!)S-xU-e#REq#jpu{$RXlh1`N03DcI(HU_D%DCao^(N?;ARAT6|Co zbyb@>c}}HmL*qVc+1GnuXN%dbY7X|P@Xz|hpNl@Ou<_368qaEsn|txb?-#$Fb0v24 zUq$UMezt1m(-*U}+h*La_PJO5-%o9OoiRQ=cdu*k;d(2Yo*VJo#O$^WLZkQAC|LWq zy~V?CjynwWS}~}?+`_@_w+byp-U*4;!0LNu5;p*2K|KhmWj?s8fCJ*Xw(Xda|NzZkM%x7yr<{V*gQb zMvrH^4y?=ycDAjWYjJo}9mOmki|wuEsCq7Mayj_d?f1{_+N(bBVnA_9_@gm_9X^=( zBzlVT2j*Lg4Fl^f z*KEdz*(a7vJ2^8X{ST+ik&_F8`|Q>Y47R;;V*cDwn&XulXnf9f9x>p{d)B`+8?@;7 z&qZElIr?7*jSotXO?sXi*mNNIq=J181 z+aLe#vkf1&o4)%{m~*|E=bD_T851(6jqS5P8x}vApbC8vIdk=#%+3cs*yZud>~XHQ z%}*;+)7M?!KcHz!@6f*1VOO*p&SclAI`6EVaO1}=8*N9~{d0Hnxv*0gH-2H~n*jBU zSn}nv!Nw6&;~)IIa}g)lq-eRd}HRNWDT8B@les0(V1rp7j*Nl79BP| zaeL#RznkIw!Pu!!>Rqk%{kV{;GrJ}yoSK@xIIha2A9X+Ewfp2nw~BW!ul4Y&H^Z1e zDPy4fq#fuk$e)7)!yp`vz`$Y&QmI9U0Q;Ez8y z|G4eFpH+Krr}wh;=rZc(f4X+hnRi6neDsWzr!I#VO_)7k{=CZr^G3&K&wMzq|G8P+ zJ|FaOS8#swZ)$kZeIH3vih1+7kk~)blI`dY;F_%j{enc$DHXN*KI_tr9Mrpx>|l# zXU^{{A2uH{*t62YQC98dpBgf_Yx6bLt6lk}Pmt%&L+Wj7`J=O6#Xe0R>4WO5J~AMC z=cMITC*L~aUhm>6ssK`bvinyUys>{--Ic4~4pR zF0OL^jCH#k$tU7m&+oC>mbAOhzT3Nh`O~9?Yy*8cU5VvsXj2s zQy;uG=7X>qty&d4-1vOH)wk2u&7ESQoYTwa^vU6c`QFW!>jo}RZu@=EpRF{OzkS_! z_0hf0dVU!D&9+lFF1EAYwPco$U7m2S#^)W|53tv?upcVim|A%G>uHCGJiCv|`_Kkxr;XhHQBAo1=u==jrpMQ_UL3Vkx86K-@=*PqUu^;Z#qXrL@awXY3drv@rG9l8o7bF+Y4g$8w?1o)^aH0U`_HzQD3>vC1vFxyRk=~lMYl4H+me10!!*M!MGd^UOR%*cMf<$A1f^|h(6-n(w$ zxVEQ5_O3eed1$2_(Np^+^tlz)?pVgGe_G$K(0#Daqw_xrh7%8ZrJSzNELZ-Z`{7TDiks@aRHmF>=XQVGfmk6n>%@VM6_yrc%<0*E`h`E@ zXME7EN$bJ7i-Q_YyIX9&xWk^taBYx!<=B^9!jH8&_*=^jla61?K6ByW*4vr_Y-CxnaH8w==?T|FGoP$eZgY?5sBDj{TKZ zH|)Pz8tZ<)sQb_ZtHwt>edc7WG{r9Dz#7*)&!F&$)35Zd(fZiHduQwSbUoj6dPVDC zGv)m80lzuyUc64R`1$PbifpSr+E6tphyM1G~J#2LA?5&g5 zCl)n&w(9Eh*4OWOt#QqFZShr|0qIqKn!RkS`IeK`w~u`uy3Fpc*%6WRtvmO*)iu0{ zRp&)}@A$e!_igm#?!HIMoQ`D_eK@;ute0WR&W3%0umAp0c>nFiHC9`EU18OosFThG z9X+>X$8}P#Te)O>a?iB3nr-HP{y8weVq?379W-B@JGgRuQRPG-)?>xQIG3W;9b?vv z_YYdvZI|D71sUm`X9n(A^R4HBPR~E;wRw$hNbKzRTPN(#yKdiU@%^ez+Vf|B_FRy}a-@E7yGF0fkCukpC#{!dd1T4ZH?5p~43-IWRrJMD`7 z<%fuogRWijJLnYCBWFv&gB7&WQz)RJWQf~Zp zH9V?kora1#AKX%{+|wpC*Uj#w-@WI}jx?^(U`k?}mXY(icQmhAp>m`Ve&75v>g3ix z+MW1e=r_4{R7)03Sy@A?9~L+D{Et;s$rD2!NBrT_<)@~>oti$J(7$rp&iJU*d%m#^ z*SFENxv_TH?8%S&-(8SehC+~uH?OB6bM0u_2HKEq%P0IG+aSv{!^sOIpVQ%(-OIAq( zguWwRnmw=8V(IxYgIXLPc4@BL-teyv)vLZev%QzfqG0Jz_b)p>=zF5sqUp~vzZhBn zkBgIjKE3kc@~=)+ADmV=FRA@sR@Hr9_L#gUd{$#$Po3M)f4-R9rTX8(B;~?mgHpY= ze(Zd8+PW`B{xz^g+O#OQ4wpyweb8vT-GzHq8tMC2t6n?LA@ropkv_SPAAeTAex*+L zeA-2>xDYq(-riMTjO-WDZQ+60iH@QEwOu@V95~eR$faQ0hkfr|8rb$??p1xuKZ*^{ z`-dnyjIsN4%&mVe{59|4ey<-w$E_W^uXnw@Ra9Rc+_ty!u3o!$o$FWsm?Cjbi&ba- z=Q)%<9b#4y88MK8nf*{VdKtGRY%+icyY$L%lVyk3kv(~xbL5L;oP6oifTRatoq%= zBRPJZqnk~g`<1g|X~^8pb}g3P_^{CFrxg*?Y_>J6RTS@dL4C*d_oFW}zqnX@!!ot< z+54TAS$=g;Q-6GyymP-ss~UIS>(aN@Vq5d6pS75N^{nNL?Hz_b+xh8X%R$!L!cL9L zPndZ}X*d?rJ@vyUdu~tbS?rv%zfQ$ESqo156+Y_V#Ll}m&dP1mv{suPg`ZBZ)8g*~ zRrc2VYKQqgeeY*>Cx19}a{MFXP-nxDRoRPsp1X8D{~oiez_e5*eiciwSn&Zw#8XPq-@w|u&FNWu^HXL}#| zz2o_Zhv!UR)Ap|0kyaKnvX6a|f49&u{pq2>uCUQ}qhIh3O=~Thyz%B0i`JKG#TSov zyWqa{<;rKfty@>zcsOZgyO-JZT!WkpCt4h{nZMb0z3+_B`Xg3`Htsxp)%c?8OI2;_ z#@6graCnOSscn}1?w(k_^Wu92G4!^-ETVUqXE{V z#}7`e)acae`93wrMV;rZ=^-n=Sca0pr>&LMh>vUQ?H}lba+xc;m_MQGTUhqn|Q)5Zj6+4Uk zhi2T&Y^^JPer9<82JZE5@NZu8%DJ=U9IAv3uNIv-4xqB1ZQ8 z@xj<9?Iv1l_5_}(_UPfufwyk?o%*!Lqh%3Zf3Ik}IL3Us_v(7C@txQH9rLTr{O;AK zgk4Nr(dNg=%_Bx^8#igs;)yL=Y}?_KHd}KWVPV!zYgRD){Tg%9kEki$925()>H2-z&OyiSf z`1rzMt111&%gDJ5&f!eakF0JQf6Cr8{-YMA@x?Oq!(`ffU#49LWaMGn^@>-!OBf`B zuUpZyoK>OIP3a-W%QU`+hiQDDR;KaSW$dkcglT#^8UAT9^0$`hM+b$O$#&__o33|L zOVjw!6Q=RU+)U#?C^U^<m+pNyRpv}FhHua@5cWvU6W}f1He+*8$!N6a24(Fs9_`C4+YX z`AzAkk>z$mq>S#-6Xr_y;E$`Jc(?bGVE>3;`vZYOmrW)A+SAerd3bemcsu_kIJ@ z{Joo*nVgRdke6Ue&H-}r$mqYPj68E>mm8^3QcUGG0K<7JtQf7=jZnoqT+rtNc*OuO#M z%;U>s^gL6>u6~kfuNL&L0NqJT=SHUGA0cCJ=VbiAMx|*!4l;4Y8ksmDU&gNvlZhV^ zW$f))L(}rqmGSdqWaNA)qvxqIcCt=p9L0m;P1)ganf}@=W6x{BznS7QO@@9~JJWiY zAQN}Bknv0VW!gLQXVd(5$&8l;GIB1F(L)so#KC2~Qle$}FO`wgQO3@bWcb9%^m{Ft zIPI(q{UsOE{go!;2hPjbXVRyp_3%)}Z#=a!O@B#7KSeU*dWe^4J_lvm+gD~>o5_r? zMKbN(C}R&HGI~pq!Pk_VA7$zdC^X&PlQR0*D5FnH8NXBy#$!{VsxUo~C3-;K3|aQa zz=z?t>JXnn_zR;DAIg$lphOXVB=IREK0$<^UBOJ`V^POK!~_uj7$he${{PU+boPetTob4Ao_~E5x7xUoY zULh=~7=7kNq=)8<<$JJOhW7|Xd?t*4R+L7>FP?;W8$=6p6A*7j3FV=LKN^Sl&|i?g zfbc(y@pe_gf+d1MaU}ed50T!1B_=`PdYb~jMR{5wp6e$i7x4isX$Xo!{1AL)e!NS{aab*mtL;{e2m5&p8cPN^#7u*5u8=yFFK?^OjOB?&&De%bXC z^6_BFPEfd=ghApglRum#L_txA|3aSG7{rIM#49Lm2rpP*dxOYs?Fs*hJJQ>;#4sq_ z&S${FlJSqCgfa)BXL+3r-+=6p>#fL~^jRDA$?dHvjB7@p%@Wk0WDuX*Hb{SqB~n4* z{cgV&>9Z*D%Z})44Mco@mV5@qk?`OCfOxC3i0AavU>>On^`^7LKqxkNTUfXp>31G3 zPv3S9;;p|#Jh#t%F#j_7x03$3KEH#+XNJ!r{quS|_d~qRJmkaM^;LbuTSOzC%vvbe zkND|_5YP424<_ZBCiYhin~~n(SELUmJJ|$nWb)`~9C10j|AF`*mJ|miOZ-<5o`PUZ zKlv<~krnW#tXyb|c>NEkHRpO>-YyrI zXPKN*d%F)u156(KmPpU-E$0Q&TQo*H;r5?xhxW4{UbSF_^FIdj8sn2qiKi?VoRvG! zp{(AbiO8S#%aki5kHl~Hdx-cbmP81}Ui=sG_4*<{EVDd5?iu2ZEI|+o%XMbO{Tkw> z^MY{_;zyC4@ODijKQoFY1w!!<|Am~%<49k866^INd{h$RcYcp}uD20Q5N|_%GL7g@ zHbQ*LFr?>pYXkduOg|1g5w9TnZu1epaW~?*-lma0w~{`2y*KtDy)Df<+)m8Me!|Fp zcz-or0eQvnrjf&0Y%jN;A&Zbt&SI<=)F_t1UWk{@*C}HW@4}Kzp>R2iED@jWgm_-> zd6{`UNP+Y!J<@YO<7pm$kCL72j6*yhM+X+5JXU)UA3*iq2ZLnw#uyOapYRhuKzXEb*W-_ozKHCW^S=%A z662FX@dL0G%jrXi-&lJ+5%01C>*e}_6yA^5uOmM99^yHl z5pxhPjZeM-d6|Bs@mS<&#AmT2d?;SxzmS(scAh;0@m&6OpjbA(9*aQMkK)4=iVyj?9s@74v3gaf%j+TO9^$XPKs?LkXC=ZL z@lrol6UZ2!$8C}RIU|O0e&%9&EC+jNm;r8=fi}-xW_{tFXXJ8!~js2@9{u6H@y&d&$1H#9v z5U-z&{CU3@gBVN?8A`+}h<^Gw#4lclc=nDMD>KFO2w128g7wA_J~$rf7p_2h-Yy8! zME(oOPPjZx_aMHousr?sxrjeQ@w}~AE39uQ{;{F>Cx-C*NDpU74?OPOOXKt;trvLz z7EeU}F_QT0sS)uB-=jRdf3u?8LcDaG+BHYKbY9Ngi1=HZkdGh9|AP2S z=lyhPbC{E*c6*&#@77~AJ1|7=mEhrN8!0mkfR^lTW*BLZk9B8~` zQoS*GNFPe}!};|62=S_TkWU!J!yd$E zbT;BMjv^muj#z4ciFl8_h;M)wp)2*H2gN_UUj}l!l8oyHpCBLUd_5f0E{;=2WITcD z-JwMMVI$&Igs*Ik?aB|vcCq(AS=rwW>BA)Pe`r&r4`(kRLh%#-g?EY0BK=m1XZW~y z2?}R=vt=(NLgD_ob_CMrWuZJkCYJp&^LU0#d|TxN^2sNE$o)xbKH}4UNB&%%3S@`5 zdws)zJZ#9u>k{-%exc&F1eqWY~^nAYf@he&-h0-Zt}40d1`ChQ}q6llDUtL_dJmziH$*xW95qM*5%}XM5&wmo5T8Qpcdq}nw4bn;_7fb5{!lQ|XUsu9+&(X#LVN*x z*%1oobA{rW7>VDzeHiICUM|na2p!G(Yd_6Loc|TruVdr!UKr{Px=Ae0dJ~@v8iJjW)esGw%vLBSdkZ6bXH_|-?V5&u{+ z-Uq+|BmJ{y;D0OoBEf~!Ul@- zrRPMQ;d3=iKMKkDQ62cO1;g7aQE$9|7xh4S3dnA4sDgelp)mS35>_y@V=@vUXzqH$EOAK5>z_Ymo!IH5eB;iS+0q|Y-X=ReKRA5Lc< zP=Lbmqv?FdhP{Ljh5PyL6hAzt_<{HPVdC#ddVuDLW!4dFR{;5E-j8+aB7U(J`E&iN zW&Bu|Wk?@>29`D zc?yWn2C|1yw65Xx+7u(7@GDp^xAP3*f0+0?5}*C>u^Of~={}+DE#%Wi63@&8u^GJ( zg#7tDwyL;&%b|0m)T=)4lTRV+<5BYrEzQFeq+4M)5S${+@{#U`G=zc1`YVs-BNg%Qdk68h4^TfDBu^`fmojPH$J;gR2GUFS!4A@X zpmcqaAQLA%h64*G&we-TFFteKT5*nJ}6&rttz`}v#p2c+%Y z{TS&B+_Bzb(nFz)oD0bR+kA}lCyD;5GwOdK`zQ<)1=(BWyU2gp--uX7`n*nY>`vMr zC?r0=)I>h}W04OvLwK+Y`D{Ipd=?Y^-;)rZA=wwo1o4FjQAAEzCOuA1p!Q1FeZJ(MAKRlm+&{D@eLB)UMmX{L zi~M{P`xp%ruAi5W@cb_P7qkp}4<*UxHiq?g8Zga1ang5n$?6HBKnh_{xEN4pn@ zFQ)OUAo|tM5#OM$g^1Zn`05moN#ngE>yZ8oolA26{Dk6}w83a^>7@UkdL#X{4D1(9 zA4UG~+HUGEs`o?(q?g7s7bxzP#y>qrAibW(BcIpw6wg?EjQpR&cwlAtR;1s!y?nj< zRw6#V0P!wJE}WW$czcQuy$Rn}PxRA~9^9r_mL5dBG)~w89m>Y3#Z1(X2kB?o6XNfH z`X6P5e6q=JA11%e{Y>sdq|c*uB{WAY1>F&UZxYI5fz=3w@S#FBE($!5e*>bw9fR~1 zbe_odc1v7u))j0Tg96KxFF_$p{`}|2*o*k90+Ej`)ywrZuodD9;!&Rd#H)1_;-zs& z%`n8DT!i$XF0s@ai}(iX5YPRL#a_g_u#bB|;eOl72IaJ3AIF2j<#DHZKb!W$q4{Dl z%Jgp*`K265zEJKLSa0ahehSo1!Z!MA# zpZe7GK3-b8#R z?UyQu|16TTnB?>#yhere&uPBke6~_t9m+mv3B^eMvz|=;#)|34Cu#@EvzYi>cR_q! z8sftU?+5EsHjYNI4`o6L7ypHC@eM=zGvp^hJh8lJP5g%=9}q(#qO`E&Z*DTtTOr)xVQKI3zwA4T$q6eB*9)&(H8 zSZ1F>ymVc|!ULA?xAg*!FIWzXWhvReOBV9?B0Mu=MlYO4dj1{J)oIAzLNbpWh7Mx= zVo&oFxBrXYh_@Ys{5ihh4B}G?5g$eM-lqA*I}q#T@#jSHCl8JyeJ0W8%FN3_b~s;4 z^Hi6AM)J^lAEr66OjRR3HwF3geyL4U@;64DjIj$?#RcB^k5|Z8Xe+YX#A!SzJV+9&!=^w3*kG!LXX+QElIvm<|oKU zIzRTzN4!e1UlaokVSExIv0v$h}AEfrSo$PZN z?f-K;ruHJ>t_P^X4#`2UGSSALwqete=MT4QSr6BfQ-<#Cu52TVrP; zek;WToR5>teA>{5=(SjH5b^h@ZyMi$?7?1gZq-X>-mGvD`Pke>{*J_dT0PXW$7t*q zo`1DrG1W`+0=M%~)bG-HJZU!4=hJ$M>!+YH;_YZZkn>U3!hV#VbEQ&T7v_L+#*m!9 zgLrJ*Wwu2;A76iMK>R}TKU~i@LJ=?BA7J0rW%EcH#dX;rA1e+Qh>s+G9ybH=(tP+n z+d3!t7Mm{IWZ|JdF_y|AhL3(b!S!VxGL-Ua$02%^?kC&sMaB;qq z%=;~%qnW=geuDj5gco7{A4q@eR(UylRYiI9?1RQo!o`0D;WGEb)Q?=x%gIip@xwq` zUr71BenV= z{ew(wFZZ{<`6E7WJmL>iz1dw6f0*)BTnPUS*?A$^Ip=ek^2%~)zgj``f6L6X)n(@W zAeni8?H-h;0j)cDd)EgdK7iKg9KV|EP)~Nq$9qj`uf@3X?Va~2>ESEHb3I@G4)H-0 z=kWI0alcLb58Pi(hK4gcOw%JDE|1O(?WzsE=aonL>CzDC^>j|n?V%MYoY`%_-)OM? zsUIJnMfxz(1GmGSz>w7|jo&_lz>?u(C_d!<`0Z`Pr~ip^PAC5GAuo|$dX7_@{QoW5 zU*hem-x%f0q{J4!PupfG$wT((P3^t87V!l%&vHKAo`erad3d`jlHT-9a6CdY#j;su z9!#R~D4mxNok#w5H;}&<@ky4M_h+e)e(`Li=k_z?GUC0iB0h@vJpP^Jm(2U~S0Fxy z?1$UcNZMCFOn#<6@p1Z@=*bUrdwbCw^&B%B?O_7-SL^#oZ&v~Ja|>?^t1O9+Q+a(Z zm)RF`TaEPglK5xqFvL47Lq6cH#ZrU(;WF~K*(Cq&5Ty5;i}ZPfj~I-2>3dA)ixDq< zzhE-S<45vvy%}PN&nV;{K>UBNjCLDF`7@z}AGsUprFpr#ry~BO#GZd1ig=IF$e)kv z-7s+2cobfug7ZJb3daA1^wRf<22%b~h9rM!Cg53on@jPng5*5e68VQMMgCmRI^wfX z!Y2nB$i}sFey{fg@ro8`4?)yl>AxY~?k2V?jp}W41o6^+`z|te^4lt;m+t4U*oXKA zG~R89|L=W?Kg~P5AD@zcUMz_d{yK;B1=mqdPVY1c@zVDMi$FiDUu*_odow7WnN5C7 zn$L3&)>%w%L9~y@(@M0QRm9SxtXdhC#pF9>GWPI%Ap!|IN zJ{pJk&<%*^e$S#E@;B~6{`_3qZwS)oFD+01;Zej(_Zi|VBHqdw>G`<$v^(MpX@2B> zbx|M0OW&Ugs)zV+8rNL^k?@1**=rlh$@Lc27V$BJXZt~{C?b(h*hrLz>miHcObhap z+)nZjBmGV~r{HoLdLw>oEb`&?2G_uPA4H)%*RWdQlWHVSU&PzsV?u4RL+N`-tBFq* zz3<>be9l8Zi~ZOW8Jr=!;(Npwkeu0sPolUqh~m-*gzwP;<;mTQ^6+sTCX>gap!q1} zSb4oQ*^2dA(>jam$GSc8Px}k`@2BzpK!NlNDGo^?dHj44pHBNf0fetV4)M}Bp<5@! zhthr*3=^>wc0qjDSd=q?@C#x8p@vXbuxo?^rGW60tD~F&15wVCBwy ze``ndn@P{%VW{T}!r!BOQEA@P*~!Rf`nK}*=F&Py8lTjF@xt^|OmSKa@n4;d_+^qf zXLM)8OXt(j9}$1}9MJ8qB^jjtS z_C07Gm-^calrJj%9>vX#BtPvl@Nuyn42;RMnAU^bFI6JEG!LigOr+0{tY?oyXvO5u zRH6KicoBy3`Io+5!Tamfr^x4;WWBaN2JwY7Z*ux)UlX3<4R6xtjUdEZO+m%?r+PP< zV?4Q&^5^;ZYMF!dndF~2pAocvNk~OLF2rA#h4j+6s6N@73)x#3m**qYgT-zXB>e-t zaZ@JVu!f45-nQOHdhY+n(tMXDS?^nDP#&qAe$NqqhVmt%NS==0A->=Uwrc|6|NaE= z(m2N`6F+PM@tB=>w?#go#OE{^48v#AzK}QJ*F(QE{QhL5=lc1pCAK$;{SVln@cFbS z*+VATLmT2Vu|M*eJ|FoQ37;ik|K=zxL`)vx2OEffCDJdWaimy?c-0=nbG!1`hj?i` zGdBY9()dS5dMIj*e7vdNR<&^6%%wP=`@?0v$ftnj>w83hsw3hZXVFj87K-wb|3cb# z;^So#&Es~IC&1g~PV-18%_A@j#qt%+Ukzyf@+15%SZ^`;8<3rFKBLwle&Y$0pUZjX zB;rHK??n-xXB59l32UyY$UwoT&z_c#Cu1T@2>@;5no7h^6?%+ z`}xwmz8%mI)?N>?p9Umn!UM!h_mdCLCjOFr%lj0cOrZ6C2Jy+diS*XDP)-Hm=RZaK z1kyA2Gd*GcV)E>l+9*17svah-Ylelyb$?wK55W!#>aLs;<=qal*!v^nt=4z zR+N`>JXFN`G2=VzM|->oW9fW$qa<(Ycqa16%R@d~&K|TLPq=`1ZYKk1Tr@~Rd=}{^ zqY(Kx(Eb&4lURN`iufWrALsq607t>(RE$6YA0s2oF_Js&SQ zSxB$?2I;vy_>V`tegopUU-BJ_cC;X!tC{NH( zl!xo5`68qbpz|ZnC!YNF1KMZsB0iO9ow7d%`4(*Oy5buo_;j534PnHJxa6KEkAl`2*;<=vJ z$((bIqmljil#!v* zWIvowdy4<1`>Q86BcD+cJ{wOU{?=8jH;n47Jsk02w5}XQ_@euWx2k9%VkW@6%ZdZZ zZzTD-{@YVrnooIs-Xb;RAx@@x$v$%kZ~F-GDfN+mKH+DNNBjgDkBbT4+Y#}8G|zJT zTn!z;`uFf`Y!{G; zBtIsNe|ltKz0!5!aZr>vf3g1oGL%rPUf4^`NWX^56wV60kqx^ApX7SdtcIX#-Y?-8MMFQK=c6@ zkWT{b!(=Pj^)Vfx%jaSM;cdw$*m>A4<$8b|U=_NjWq#KGDn z{ci1p2*gX{qRwDfY@gPK_GzuCUfBK-`AFj*uSCSRp>?qh(RV(GcKPe(siMC0NT=|}2^w^hS>7k-NJ z6j6Imkey4<@1nu~H-Yh+Q5OO{DEKe@|IA5m;pdT$Lc9zAFL7GON#ibmP!#i@Hj?w5 z`oN#zH^yLl6`(Ix94eulVa~{3Mfm%x5ifl&(zuH7lKFZm2+aCjMf$fx_QE0_M^PS> zg7E%RkUniY*6Tv}xB-a2M&Hxm_EV`F;)RJw&+VaiF3}%HJg@iT7{p7@0gurv zCh1MT4EvX_v&N9$kbch=%pCM-Ewq`0`Xup%5y@Du^gB^aD{7%B(f%j5+dX8@()HB1V5FDECktr*M;eDzA-^YG*PNe&e5CobpVB_6^!r*i$C19^ zB({ssgZJtnemdcAl`!3t-ODopaYmaM}=d5@%NR! zK8N(ub#B@}h|i_NWs{OPYy*@rtP^%dZYK_+D^n!X|FV93xf-Y*P zAwEv&)FsjA!>BaI_(Zp!;sgHhzZ|9~~kJ`wB$EKpv{$WQ5BLh2iz6c?pQOnuWMPL3syMA0)kt-)UfvWGZ{;+yCTCuKLS zv5O`$MjNGyAE8b3O%05T(x!kts|T9YyzB%1O4ng!7#Y-E|92MtGTpCqDKE9ZP9k$c-2(D5a8yTCx`u0^~H@!A)2{9uZMAHP@zwNU#^ib zGceKNU%1KGdBThlmjtdp#Z3)2<28YLePp1{-`_vfQLXA4s#f&~RjO605NEYtLa38k zl*m=BiVg8(vWFNwjq!}BE^dfkNH)YJz%OHTdCp2FwHl~WkSs975DDCYxlRRj#HxBg z3xSVXovcyoVTSUL4RKX}CbB4*LQ342(h2O?Pd5bg6qIBXJIbVyO6mNSj*%sg$?$ed za&`g@_zzXVbY_yPn+_>-@)SvKE{sR4JcSM^^s*F6$8L!_LsBsFV*aW~T`UwCfs~$I zfXe6x!w0;i*g$6||DiI}9wI?EZ5(*sULlTZbe#Usw;--c2Wp20O*Z_^Px`wJ6a9ir z)ox;wU~UmBl69_1=YEMgqt-tq0V0$*BX}vfuJ8b#VKnNZQ<;WzY=FeZC(Ah`mnhaJ zP3F`~C98%F&X5uH`>2~tMyu-Tqsv-`l9mm1wWtVHR^mFwo>FNrPR zWi`r@#y~xi5%=!v!6+0W%*dofy*dI6PHhka?SUTPDD@)})dtaCm99gyMs=(vQmu)K zO4J$*!66305Fa^AYZP?xkw!i9=8*}h%6P-?Cr#Hjnu~*DE136K>fYxaf2Vq z5@K|RhU#^3T2svl(XgQ#4DEELwo}tX)iC$?hq$YQLm~oU0E6#?c}|R;dcb5v#PKj0 zo6u2YvML!BA@b*15#@unLK9iFVk_AXl9Wx|Z_@)v-Y0GZC>WMphNM`~zIsSXiaJ4? z2+JvmhK;G}WJlnyRI*Nv(iw({y_^say~SLi0c;~0ZlR``Q82Y7h`MBAvF@U%p?rU0 zZ;B0}j&fJCD&h^vLQVfVMVI={UOX$e{j)RAzw1jf^Ptitp ziH!ma1;+w?2+MI+H$#WSX!UxrY0yrv=h$*aT++cM%tx?U8a4;?+7Q-|XeOzfix8cV z1U47V#q?lGU_@ADv_Y#K2CH~x*if;nAptCzv4l_r{)X}4maL70EdmG;!B|2J{>&RO z#f2EP!{tpX0%BA(@PUEs%!V}$WatVRuAT`ny{aQLk;8b0nyQ`0!;uIxdL?jKG); zFLuXWv=qh;Fh&T4n3@4Esx(nMJV|RXs&xs*)C90Sz&m1(L}`o~FbS|B*1>Vu!%Ai8 zO((XKLd%YlZiVh)s`3zLBB)dBE*(xxT8$x94ZBE2{L{c9bTy9mG6djo;uDBOd;(lm zG_2gqKE$?U)Uf4K=CMdvO2s9yb&aQ3cLIzcs2uD8f;zETC1@wa!!5RQg~l-_B)YO! zt;U6^njG}2cXe#74qH>|NVbRC-&;LO=KzhNbm5HsSawYhOF%JH@2k-#Y5%+O{5_!_&~$`0kz%H@ zx_eiZ=u!fE;Oe1d4eRNrGqBCqz-SQOQ>}CjhVD+*hOoU>*p?jq+=sMw9N~dj%t9jw7^hSv)h^vVX&LM06g;cpOCK%;a79)pubX;?xP7U#t!9rI(m8 zU~XnpGcJ=$YhfFd?_?Hp^MO@Ff*3@+(FE8|D})UG>`FiV{Ud@yUzrd7%UCP#HO13I z7D$y?kUD`yY$84a4ie)a=rTseNbLs)pGXBu$`TqEaQU%p=YyG>!Bu2{6F`W;nXNsO z1)af=0Q(I<0eb>ampVErE>NfQ4>3BqCd7-Ue{35Xma(uVgL{$8tZ^~wujI8W1dz@xQ46bG zgSJ%gx2op9Hq1vI;=>yTJ2L;ddbZ<&YXMK#FiuL0>uDGgAud|W4f=OB96jOer)&uH z9!5NP6*f7Saar$7dUufC7nU9V5#=lJBu}o5=3C)zn&p$|`KGuStvWMw z5^T%ChDCJzo8%}fhO87OgkTdWGx7JaesT>ournm-@HSr zrBd8dQ%9u2<}OS`25}EXNgMHO?}Y{JY{QL`Gx#<>!{KIG*=>7}cF-GbTohKfnfD zSZ9m7Wa2g)Zc8YYi5i%6L0~wLW_w5m_BZRT5TkRfCIL1T^)SEdVcV7MFS1=mqf3HD zmlz0b)q^s5TpePBkre?a@>(qrmLr16$Wa}l)x#=R3F~cecreG6V+oitoBnTCFZpzL zl|Ehrxmplzi$?I4V)H4XF;tZ&U9`!LA6LIn=MRx&9>`DO` zVEf=Uwc5wP&RPKh2@`4+OC(?&Cy^pb`>GUWshrBn>dz*63>uw%{6n~w*l+iqnqk@= zS~xxipQcp95i)QQZ}?+7*$q{sUK~Q~@JQ?C!c+kJ>8c(v?1Vi^Bj$045Ah{qiLfXJ z30o?Lcnrlj!5zLc9}Br1czh>T?}rU6Q+=rlT%dPYSCsNCry+iudJ3v7ciR!3=C@St z>UeRZ2Gs47I7BV(w}mDpu#~_uI(NdDNv##rVsMO=5r^&?lEi}yCUUu_S%qBG(hAG+ z$Foh*YB2XBSzt#AQ95yN*Mv7IvvtpowBQ&s3W#AhT|9H3=Sq8lSu{I3Ej4=>=dRjV z*ft3*rA;%-ODL*iZB0V)?)E7hP&wu{Fi{Qd^8aftGD1~p6MVWAKN`pIm6 z(m%o@n9bZ(qc><_x=MR#FrCBI>D6=>AP!ryF$>m3POt%=$w`cq6Rgx^8&aYJA#WEc30 zCQ=ZooJznii${P4raNf0uB2UM$;2=@UK@)Fe=j|X<|kJ0_9tWF9y#0gX2OC$X2*b( zQ3<}0CtqL?2mi{Xs$f0=29XefV+Nt43bcbhmuhi@g}T8WQ*CCWrES+>Qz z!7|w6en>4F5{J9?Lg5pCbGQ`qk%MApTtBBrDvCP zl8GrooOenH#E73{h4Wi5d!_?EEs3j-a$$IBWN%_-ddr>yXGs}H?`5WftT`}nO6`3g zx`r1N_)&O?9$_a>T@o_HYG6SiajBxB#gI>2NW!9@l^8#cs39 zh`CGaEK!IfJKMq;P^tq%)}K|9I$6{;zZfb!U&$U1DLHhGKac8YgFrALETNumeMVPf=t! zk)=MTbW$!y=143OlfXdv^a@=}Qz9JRmswkuT~s)t!KuqXSe8OGa3&h~I&h_B*q5R5 zNJv%V%v4%?ncGSyexGfum}cRuj~~KTVJb0qN80WZw%A?DG7Wy^R%w%@w}>pf7PSs=L4(Z`3-$=@59G zxOmaP*y#+|0~~|6!1^gBF@6L%BUn;F;*KV9h?qEo>rJ3=;n1DU2ykrj<~JO~d^k9xVXrK}J^>62p4(YEA(UixxXPX>yFum#>_oAZNwVa; z@hzTaS={VVRj7E52j)s&^;`(mw6H~ zVlpWduNl-(_Ej6v+}t5=ZGKKmpJYjzv zB!x8v{E2K~e=sys56uo?`F(gZIzb(s;HWl0nr?{oi$}dBv+e7{)Y9IS*$icet8cRz$~WHLw%(HbgEwzD zn39pT3O9Ad<6l_`asPtt)=C6??fRM1mX7~-e8dG!lFR6OSK9_$Yo%>?3oir>SAo?t z_XRJpF{>|qTrRigmGcu|0;!7PrayZFPQv9~wT}<*G6K5Nf#ti&x_s{yxRZB7@BH4{ zfk~ZklH@457?!a5N_ywJ>J+78SICGcpK;)+=}NCLYt zn1c}%p=2+3{kJS!|2VBbZDy3|r+1|_7%;LY(C=yVzz!K0VnThO8L3YZ;&i5r1 z{+Bc%(Yw8pboCbQ=hY$i77xh{qc^5XNv+|Pv*?mn|DZwa4F0_)dAY-4SxoAcILvq0 z442Za*-|Thm;6oTmApg#N_e9jzIuVHL-d+u2L3zCkWnhpb@;LrwpXfoKmH}*J*HE$ z*oBgvgYZ4__h(|xbly5tl3nzkw3Z;9JKv$TdBW^MrY3pY`W;rHbQDdx%x39(8JQl? z7Odm_HN`CiH|ij~koNvNRG2eMp25S)Qcb3rImpLme z)zCZa0avU*d`tU%Gy&fJU^?RGo$sX$Zd8M0iSrJ%M4up%{Q%Kx_K|$!jmCI*bz9sF z5-kz-zh0By3@c!^ZtDtH7QC@Nk#Z@^Z+x0P4qGAOiVdD-Sq>C6v*c{;5O~A&?J}Gp zj5R75K98UgsHlhuPFBr<%0O0?t zkpI>)=;d!PMQ$1YL*@U`UU+``>n%XCUV67{LVUCDy;TtyA%3O@7D6TKEQl2T!;6dj zT}3#$0+#%Z#AJFMvAp~xRk45p0#fGahO+m&%afT*$Yy>H!V3xIyV7}?bZoXR7C#*! zx~Q_dU?vlFonA@Q1yA_OZa9uqo|wxH`%P|L%WsL7^^S04BJM^@cB0|+lMuB*OkGvO zX96SR6H-fjyErwNt`kIKhXVALiMXlF6A9q-{gsxKeLKh0eR^LtJ_35bTfbSe0fgvoyRiL~(G=4Vd?n*<7Zc^1{5U*7A*> z|2YTS15S_1zT5P_=5#ZD_=c6rgu3Azfw>+NW~p>4dnOYo9t!!Fp2mPJyRZ*8&~8H6 zX8-$>4E!!nFve>vIi}R~{ynpjk;)i3l^UUVW>pdxm0B800o4v?E=7C?j=2=28uQ>p zQwfSy`Uk>kOUaqwtGBzq{x*BM#Li4VU3%M59RT^kcpw=R>j-k`f+Ap}8|o+hfPk_@ z$}&WfRyg{@nHsdU9HUogy1xYt@p)}s#HS36tC3}Dvx7Z}5Om|!n#(`&DyvuPc=OSl zQmJPjz=Ut3un*PooKkYKuRTI5XIC&Ol{h!PA7A3Tq!tBs_-4U8AkPLSl33aRf@j`X zM-+W81Za6~b0qsfsg91%-`f0fwPd=&5)bjej7cXa=7s2tHwM>Z}n-U6LQmU{2#O*ac@jvk^XTERY79llJ>K8>LepAZgYX{hy9xf;3YwiCP@%SKn zQx*SmoQd_kGSDDHLL6z(6ZA&%+srf~uQ!i1ux>ZX=Ya21tI2J$q>R!LA=)t@_>}cO z8r%OkJ|WxmT@TN{)n zL5+-sylpi9|Ib1UU7Sp&xP=(XrznX;unv@_!ijV~EI~^hqhoI`!b-gC7v#$91eYxE z<@Y$1_=WAV*=hfd0Xr=%&3i80BYumz-Qa_ZEMr>S`vvbIw+b)8v$(co!GcTU(&)B? zs(emdDOd67(t!1?=s~%(sx0MGp8x-`_criRRd@dX%?&~T#efPC6_P;_Q85WmfwnkI z1OqB&1H}}#B$*Hlh!7K`@u4;j)>7P3E^U$3EuCDfMcQK4Z7H}d#-X)TTN|;p)c)4S z!CGotm*PW-75IO?=RSRAGLuY_iS7PhW!GljpL_22e9yx@Pxst=yAOdmoO}|%-mz9A z)8=kcY)|;5^3%RMd)J*c?dAs0nG0lj1jBWvjCj~`#ONg^8t^#i+#LMLH zNZmZVfEwH3@QfLDf#ShBEFf8r)yc*1#rn%gvN#zQOCX&nTq zzQKO33eRI>+mp4mri)rtfD_y@kpjVJ7fU&@DC=ZfWckBIeZ6|9!Xst2A)wzeBu ziyzpYP*_ohm5=VqX8JOF8>QsLI(wI){hk&)I3*izlTB-7ccwEEmA5IX-G)w{dh^?) z4Q8sN!K5n=&Xv~9@&`s=-cm`j*1ONnxR+&;?jh9(yxDOd4JFGquX*TW1a7<6*E~&i zp(fQVOmU&6ugOnP;yqtuNmfd3=5|@RWqzEnZl}?av?Mc8txlhhiK=wZt-D!H^||L( zx~R;(GnT@VqgRuA9)+`%-=lF7iqqK(%=bT%yy36&E@0eIuy+CX=Jd`kr+EuDZHq-W zYVCxyyKgU>(zdZA(Z*OWn*P}xg)7G1+9^w-NTk~FDLws%5OXOul~xo?j@^r!*oU-b zGHUFHy^pq_5Knwt8=uOmKK0>8Py4N{4ZX;KP22s{-t5xOmrU&aE^W1y zS?-*1U8A{WSDDlMh@I`5-i4LUtwi_^%5ppwEAFWs!m*cd8e_7)_0i9}gsn&tZ52*s z@@_w5hrH-fx>Xa8xug6)TKlQj<4uS1GN||`^?GC!PIP{OnY(G+iUW;s+*a5$Mp0nd z`jB=-FRaCDpscn|UIdzWFL4TGIC&8sbxcJkJ5RaTHhU_ z4bt|K&6K!T)ncya&7ukt9d&1F-R%g)=$&?ib}|+0G9nyxBkK8XQnew`T9)!2&Ax4g z{Tf_pH50re*ZN#f!PI3dl_6fUb-UWM;WS}zlcnF^&XMm(lOJtgJ`c260< zZ86oEwEM-|{&Kg|<4)c%wtt4pXk1FA8R%kqh!y1&cT#i3j58>=8fYrfqc_pcnX{@C zqs21(M@{8Fl2dLAovKsU^=DSeKFk@t$*K>{#bdejdb;iGq8>{yP-ioIxd^g?SW#_o1K%V z^JeeWYw@lx?AKFUtlb-|^3`Q`b2km4Ca zN}n!wN!1I0@HVD3^;NfYd-olNz5VFqa-zXil8xe)Z)(F4Mn6TdcUUEZx*fG8F)X*z z<;_CQ{&TB{y!H8*4QHwD81aVveKeHpW)rf*n&5aN+xYTNd4GAA_p9u;Y$Ou;)1 z=FL|wdV35o4RT9=UeE7=#oe>(qqS+WFT#EMNZ&jwm;z(G&l)dJ?R+VsTHwvm&aVHy z#fYh;^`b(F$j#LFODctJuX1l14NEeI<1fvgmsvS(_Ld;Iaz8H$zub9#n zM3rM|fuySqmHx{ht3HoAT9-5#oS~k@9 zE|Y-gNK!pLW=Cf)YN1@A_5emT((=BwWTiq`9Jdia$sSI((@rfL-Ir5J_RN2^^8iF)N`Odj^#zrGHP zdYOhajg6e=S5h4u^~&4s$49*~n`DU8D_gOnq=jo&t?lAF?`V>O*a$AOYcxApd$roX zPLFWn85;XJPc^Z?D5AT7_}U|;JYK*~&vMRF&*k7T3G7|R&T|PWEtd1)`uHbN^XJU> zPMDw433d$PNsnTX6B5PXJn-lEAt{98)Xq8&i#j1>PL(i(>sog<3e>zT)<);7vCK&$ zhy^J;WQjh+E}u@>ZslTpP%ISK?>|obd~pgdMYdmM(uEy0fRyeTch{1?Tv^$XQ1Oou zBU4o!6yIy{6mplz^m3%zpk9t}P#VNbX`*%}duuT+9CzF-es{`pyRZxK-GlDR1u%a+vaFmgHl}^@8Re#RkNv#{!+CSJNiR|lu`>T zkLh;X!X|IsQg!a{+`6UeW}jNOR3qv4)-6>>($%^pT&9SB5wr)gq8AHO)bF8yIwc$84uIZB>mG>algXiHla${T>dUJH7>9Ia1&9vYua3@6TzR zcN^~c;}$i1x4+k({Kjv?oFea;~74Ghmz4>TLIh` z`$#Hf0xwfOc$)0}Z=KJSBE^gZ|Eo^NJ1z0Xj@Y-mC4cH$J^|uZ4k?|)g@Z2yId@>) z$Ig?KiS_Egz7L_j@r-&ymh)ye_l|!zchlwZPd99z$MZ>?!Jz8~e0<4yNnf9~3dB6G znnL27NB#Po(|y}2km}-dPqzy6;&xM#LUL8mZuS@)sb?$ed1 zuJk-zJ+3G{k0O;?;_XIE`P({9R`uf4sp|8y@y~9=JqChD6y#g1*qrO`o3(E6t53#I ztm*yMPCeBCA3$PqfQZp6$8K{?4_R*U7K;zHFvO@7dk+37!inx7|hfWNmTn9jkA3UpX!9?fE)Y2OHNHxi?RRg*HzU(^j5%Ja_i zGUNin*>?!6Gu1odyFb(GrM>OKvG1?)bhV9G#bn)SBbO3YgCu)$EnnTY3u1PZV85@L zLSpPWe3%(_^RG{$vI}3XwGzJ%|UglvTk6li1f9v#oao5&eq+< zg;;bi`~01dyM*4%xa}MX5)2Yr_s85I~e-nyV|!V z4V1d#JGIh1uSH$9ct}j*0Y>MN;UbUyn(>s1RQI~dqzpM_zjV46*FHY5Xnp*6Zm0IO z=Q*RYI<>~NPbC_^Gq`t~DwEuXaBh&(-j6Z~>U!*5$vxN-V7;n4i5-mhFoKvT^&Ojh z)cDWcGu8cx1-(7diV-Wmh!pX8+pbcLvsb6O>6G0x?oijh7`n*t;rETzPv<`)09FE)r?>>|NW|+w9er@iexp z@%w?hy#FVq;LIj*L3Ou3+j+XSZ}}j7KJ~|myGK+aJW3tJXMJjL*JX7>P5s&me5|aZ zz9v|;stT{g2qvqT`RJv(o11mh^d}qXcAG)cLBuR}VG~uvJZI$WqxXjtLmg>N)S1IeofC}^Z>GBfL}VZu3@>w#pfArT8r*RfOI5 z<-JO0lJ2}$wo>YUyKLfBN>l7FRoFxwX0LpYYb+D@nf&^to-FRjo$6k7c`A*)?DJAx zeoH#hjmgJUt@Wa5PTCBo9A~0txW{u+YmsmcuP>!KDgE#o%9OL@AFBDna&ZrvuT;~S zO7oSft?niyy}BM^_EaVrg7x_VNU7mxiLo_>9#RX)z{P-^Daida3*%-+fA>9@ZGf1DSZA0GqQZ=ITB~<8=h-Y zUyAPHu7bDw(fVqK{Q?%0Y5_jox@=iJ3UbYIDM~DR$wxUbhmjg*Z+esi&aH+nW8sF; z-Ite5L#_tT2{e8##aiQ(qSuTU;I@TTzBiM>Q*dU*|27H3R?=~y0pHQL$}hM_xIsFTlmIQ%w`m4 zLl9n8i0{#tu358cwtS7M3h$GWaPb>eyiaC$<`cauH`P~vQK5LLOsl(2F?iJ-oaT=<6iic_*zkJb>RWGIJ*RQST14yYZtw~^tqG$K zMRdI*O^$Zj7Kdt!yT(|Z#$_#UaJP55;TvRl3+bFPY;-Q`w1q8EJ?*p)%X))#Z(dre zh7IBl`kfEExZI|qtIg*B@7a_g*{ZY_C}Z%vU>YxC`}?(5mC8!T{p6}|nBMK>g- z3eLK;bwAg+Yp6^*J5!|t?=qaz&Lyt_-B^f!V8m&3k{fJ@nLVoEvo$R6>HyqMe6tj_ z1rw0C=Rc$lC3(Og@g9q=pZ?%I7G8BKh#z|;&llAVcY1~cPe3F|R5zA7r8w0IcbRHi zyt}{&p23*b^N;`G#_+-twGCySeP_6r$>O2%l!{bu)jBt}t>-@beq+1$B^>vplvVNL zZw@EB`cxf}`<1F%bb8|fKCc+_+JNqFyyEU@qIJ}YdDlr)co}f7?&bE(q&Io)`4)kx z!J1oEtXAV>Tqv$3JRgJy-fyc}y;fZDWNGu97f!|;Sh2d>@LkPTl3$C~d?GEEwPmW; z*LB=N+CR)?Z6~WY*R`9Zzw2dfC7@5&wPoVNxv*7SX}+?P*>tZw4BKd3EY-WLbakV; zx&xXOiAFgPQyJx;RZD4dCsq=Tc>X@!xiyH58P%oQ zpSwv)*LC0LRpPi+QMca5>~2W4m$!1}!)gssxzgSmB(~}1N^EP8>dG^<1`F5L+&-^b zo{hq~EIz)1XN=YE5o;wFuOMD~CpOzyA9q=>uwhC?Sxp^wVqi~H&GKbcYpXhKC&#pO zMMK5vJ65g2mL|Ls6AM>(rap+-wOY|FzQPmKD{pPihw}ThXvqcJ;`2*JWTf z)mve%ufnrB;$i2{!^oJLSENhV=_SS!27PWd)T<*-xnvC|jw5&Asy0~0-tcm15R%ywsKRu-- zmi{qHOFa39E-kbeXp7wm9QG#E+=9IdT~6($cM2|3D>b)Mt$#&dCWx5fOsmIyZq02K zRn^tlZ{#^=?_ZL=Rtcsx%aYwb?R@hzu>t$$=)$#fPZTG~Y`arTGWoWrOzwcXZ^Lvo zJxOUQ5qCd5@yl@%ITLrHeC!i%UAcA9nz<|NxBb&NJ*|VI&U+sc&JFrE&IB`qv_{y) zyudAuf~nS6{CaHvSyimQ!4cHa$D&1PM)k6f87{dSyR0>y>9oX z*H#UgU0+{y=l>n2t*l;f$8ABZP^W!%E!}&PeLpfSDlV9p>Wc)__lAnx7YSgSR_~Wp z)r>55_?qfu%$XZn!~0INjbw>cvx<~cYfU23lvry*)0@=B&1HI!SPaVlUWx5IGfAyz zyGpFOIn@1t6_q`1VW#uI*c$`50~d^2uyNH|TTFFXcuqG%)4mN@FktS#!H~TN-OGs1 zt94ky>-}o^l!Tj_JFN)zEsg+onp>|Uq;#Zp$cy(jqE^_;%hhUp3YX(64Th|9Lc&G zF0k@9<+LZx>xz{!>gl&z5pm@^?Rxd@@&3If5f|s=(lz$2c`u98+^kH#uI5hlmHGIA zZ+csnY1WnYA;hHWO8r`Eklm@Y(wnLob5<|2BT{v--%6<HD!_ewpc3uzi(cBC~Z}b zbLvBA&8A(m<__7(vn;NPr1!F)I&bybX;bE`>eoo7eH^>15KDYtpWdWQMpEoI5_@YN z>5XEFBxKG%R%7BUU1Dm}`GbM#W8w6TM(2f)or7WuhS`3ipz)tiM8NpZCnh(GwFBF% z{Y}K=K|K#RDDR(ysV+81;&G2|KA)(3!OZDKwDD5m6J?p@MK^)0RAE6n1xp;r`Fvt$ zN1RU~COwiM%Gh%_o~~DP3j4aE<8I5wDver1kXK#H&0XBLDyUmgQ(g0^75D^^dQo2e z9o1_q)_Px3#MgA>-aT&h&$BoaDL$?5|9BTuv=hT$fYNbSnaUlveI9c2{Yvk7xF*RMkEiybv?B z#Y;B@lA(ACz^gXy0|=`=giM!3xm2sV>vB&r-CFhQTc#(tAuJ1T-tMOb-rzCOIzilx zSZ6Un#?8HWaNeH3cV0u&nA+U4B(zW#@&+-V|hFHwxf}n!+hBu%Wg; zW0MWd*HwQAF~p6w(>lY7TX&I#Vq|T6m2_4mySY|V-wdvG-)~8coNwqiJhKkRjHmYSQ0O!nB$Xy2CRW6U(q8 z4P#<^>=84{zW%oSIT-fIIHfFpR9&_Z&j-q|S`C8bW1#a^FRN*+SbIm^s+xK8${Xx2 zS*j0w3&|7m_LnLsE*XJ{_cE|ZVQ}V+=$*~1)`*@g$~Z557S#Y4S)7Ieusgb=A$33J zuKHd3|jvS^tJn=np(t&2efzkA9Yt}Br z<8+-?(9_l$tJi!b7z@p?Zhw^3ESLLt;@q^D$8F?*O!4tXox=EmaoQSmH9WN)!SFP( z7jUm1ho%@;$Y3#UD3`|U@3$_zu+wsH8olnfF1)b+y6{5Pg=2{bpFEb&9oU~_lV%F* zap-F083Fa~M|qZb9v)3{pID7sbS+2CpND5l?LHWlBbik+)IG8~KhEl8I66-}v=#=owIVK%0@-k#!yKN&5NOe`Za4|81Effj+^0XVMQzy&Ye4_3>M{|6G!2-*LK!Q zN;+%!`ds|E7+|Ypi|r{ZkPjYW(ZxL^>wEV1XC&mbJL>SVml}6$E?Uv@6|1XOxw9pa zU?Ju#ygN=Fk}q9ZdR?vS{j?gj^>0pTZRvGhnr5uUqj?Q@vG{F>q}EBP7q4~xbrOqw zx_Q}(TWai2g{^8RTewDaoV0N5s)l*h=*tV_^)C2olzM4QwbU>*Z@~Z#U*5%QXVx@W zpLSnVA>&TG?GJtj=V7G>OSm#l(Hd^6Xt+zgW8j8{TMA;aP!JKUSe>sNojqrvovn)& zPX;)mQx!$KCg09n{GMEnHy4yv+b?{UqXkp1sacLKk@a`htqtHk&bLZVm#Qy|yN9pA zi};cpF6`pe!i487nrAz@Xz>DU-bQtAtO~AJS0Jx1uU=J!XCv3%StnoITE0e>vKB2= z!B;d?ty+DDYLQSmRy3k1%LS(i-sb+grMkMJL8w5T zW9bzYjd3q^GSzSEe{~$r zRR5>D|0`!Wq2<^Bko*E!I#5OzXS&DF(7tFNm++tc%w-sGWND*tTx{O{;uEkpP`kwDWhWlO@iVkSn_qh3 z)-z;$GTY|0mu<yHV1DchyR`_G2|Ay?JApLF9wbP!R^DPtVf3rv*O?o!zpCUUsqz|>4 z5_MKC=^H73CXhao?BtXFHia)Dy@>qrlRlI564IBGol??2O5w{%FDAW`^xb5qmh@{V zd>!dOq;xfsejSBxB7Fem+Xm95jGcd*N&go|#NTbCFQxEd(pQuHous!=x>`tIP4@Sa zK9B6Tl0KR293=f7veQQT`DEuX>FX(cJL$!gpJBVbmFssRrK^?nC1k&ybf-yD(b(;k z*!f?IH<$DWC|!Qi$CCX@(!WIhG?6}v!iP!!U$$?zSK`ktKSBQIlKu+{KY{dx6h5Ey9aLULq;DeKPx@12zl3xOu9cD=p!_T+ z{Yg5ZlJpzM&sx&ea*dsLb)^58^0Sfj&E#hj>35O+4WyHs+Gf%*5Oe=+BfXsLhe`jr z8ye#>(od1zLb}zmTL<@&jsc(hua)$zWalX9|4reK zk^TyWKSBCn*L(cPuzEkK=Q@SYBK=mX580%Dm*UMK{i75b{a|7NpB+kKJs$| z>FX%`X3~QcejDlEAU#a_nXZ?p|D^wk^0S5XJrwU=($6A0t)y2|_=BX^k^MH(vq?Wp z`j1I(C%uj0Jxcm_Df}_ghmwAR^iPnVn(o#A3#ojwNdGwL*`$Ay?B|fakMvy9w~{`A z^n1vDKIsdn9E(W5nf&*Y{u9cd64IZdcuPtDK7}tQeHVqVB>hJ6vzGL=1J9|lAN_s2lwUpk2 zqz@-Q+em+x;yq0IAqwA4`UujGlKw~1kCEO?_D_&Lh5Xbqz52g|!e@~lrtsOM|D5t6 zhx9Wjd@ku@NS{D@i0V~7=^v-?MWkOvx}WrOC?85lk5IZwNk5mumy^DY!dH_12*q1V z`Vb0VNBRwKbz`RJLwmY{iCG6M*bWl{Xz-xNNF^k*quxuowSI}=FXMtVN!yU0!v=@(JFe$vN~UPAg<(o0GI7Ue@Z z={rfUB>mImPc7?If9gp8ETyZF^xMf!6X{>2csGz&qlvLwYu)E0=U1g`YtBJn|=>^l=ovi1dp|_mlo8#alx9 zx5<7f>6eh5a?- zkw0P5!=&#dy_W2>kUpHk?T@gUU#IW~Nsm(aHqzgrbR8!B0`jMw^z+EhQPLNa zevI^bvVVf~|Dk-=272}XvlKpy^h+sxHtGLC;d4lDB>!_smo+Kp-vrYCh5XMay^z8e zk&fF(TGHR5cFX@}fek1`*yl%N83n0K=~Oa{f}g4C+X*s-a`7zWM?nw|3=|kNzbSB z9wfbh^fuD}o$MSY{ch6RNiQTjM@gSd`Z3baru;cU`l}RP%kt{~6w2EvnairN&g7>xsCMUWIs%LF4^Bn`pu-bkp3~UznAoX zC4X8;-%R!GAnCKnP8;b}6#g*j>&c&X(%&TgDCx3;#rb!P^zV?LCrH2A2`qmHd-cD8 z{K+Ey39_F}`fa4=kbW`wnM?Xt$e#(M`^let(x0I4MWjDK_Wh*KCOajhkD&0Sq^~Cb z%SnHM>{ODzlKiP9JwV~>NG~S6k@TOD-bDIvvcG}!=g7~^q`yygwvm1 z|1ITD3+Z2_@Ow$0L3%6cv#9hS9vyJpBO7CIPA0+$jr2EPKQPSTb`^QK(C|xH= zKSJ?pL%jO`0>zs}`cw*^P5PI~eh%qRlRvqnpGkHmkp7<(Z$9bv)-GkOi1g0hAmEu`C9^i(W+Nxzc(X(ip+OD88BBz+3S+eUh;vTXg&VbbSN z_;%9oA^j-nuaTW&q~}w*PLTd>s<+xuul`5LP8R8}lbvkR=TrC`(yt>wb4kC6;+;VH zS!6$-^d{1aNdE!pe$uZee@aL{m&&V@^e>QJPWn#rzmoKSA^&Sh|0IR4BYhTyZzTOe z(wj&hL3TEfehsB}GwDC1^ll@40qJ4VD=FTcq(4dVwvhf1h2Km1jby)-^!F(ILDI7+ zpW8?;B|C>nFQfFflm1iEkCJ{h#e0nOOwvz~{zKBWGrjuXNcoUOxWmPh`hW`VO*FLi%H*my%vjcFIXVPX1Sto=qsvnJB_4orSMIpA0&S^kiL-YY$p8;3cro?MHD_v`o~G%N%}bQr-k$!O7C9M@1S^F zNv|dSAn948w~>A=#e10aVv4t&^kxcwl=LNZT^u9*FJ$Kg>9>%so#oa4O474P50aiu z`hQTma!7xb{K+MKG3EaR(r>2l`J{i2;w>WmCbHuv{d)4Jg!B^1w^GvoNcPJ~50PF; z`n_bomh>-^{W{XWPvIL$FDL(-NH@vO2GX~aKbuL%V-4=VZKVH*^f2kaB|AGw|AcEr z(^^PhLi%3PuO<7fq-T(Rkn~@Y-bVV(WdAVfU!{C#C;jte=P2oQl+VXV|8LSykp3p+ zgEq{o|Nlz>MS%lKeSF z`j05RCrH1Z!fR)H_5YJx|4IKV*~un-E!oK-{jcPIF6sYE{!bu%EQQY}eICVIM0zgS z@sqxk{4XK>yJWwV^q-JkPWlOox03Yv6mKo*|3l&HNI#$KH@<;HLH=(b{c^Iu zne4 z_mMwGNuNP>j*F;d4npM*0NO2a^B! zqzB1<5$S`-e?RGEWWR*;A5-{J(km!@Iq8GRpGwkSCVy&4FC_bQq<@a`zmfFIDc&a1 ztI7Tb(wC9Gne<ROk=~q(tHqw7j z_79UjiTrOTy@~7`CH)G@&ts%NNdBB4eL2Oe4fpE*5wep-dM$;|CcT-$=a61bdM@c_ zkpB}%zlQXD(l?X;MWo+Dx}Wq`inoOH736;@>4(WqIq54Yd?o4UQMuHzPIl@@{|%+9 zk@P6}*+lv((l?O)5sG&+>0hFFw~;=F!iPz}mD077^xb5qh4fF6oxP;5B0H_5|C02B zr2mBMw~_uZ>4!<*METZE`cblfl=Rz3KSug}Wak9wzaw26;nn}ENY5hOPkv^T{(CB~ z9MX4CzU7ksH?lv0^tGhtlfH)R6p=oV;`NihhVr3=^c7^Ml=L#v%So>zJC&p_BR^|N z{~qN}9qIo?b{a|FK>jz8emjNVKza?uyP5O>fNnEX6U`a$xio%F}a&Qa3sN9&cfW2A4V@Fz&Gr+BqrL_b4kCQ^a-R7r1a*K{&mueNdG+9@soan{3#*br0}JrKSp{v z>1)YOCFwa--)c$ULU!s%pH2QZl70u-Zz6pV+1WsPJK5Px`fMtfZKO{mKf|P7Mfy(C zXHvW^q>rTVdr99){^eI#>n@Rr~h2KW{ zU&wx#^!a3GC+YJjd<*G5()W@+mF%>VelO+QLDFv}Kifz*Nk2^bbkf^NFQxoEO8RFg z-eaV1AU{u#-bVJd^St{1X|kV1`hK#XP5J@~pF?^Rh0i5@KZTz_`h65WpY(^w&mz(< zrE>9;9wIv>q&HB!rKDd>dO7L$lbuS^KTCQo=~q)e*OC4Jg>NMNbEG$szK;CaK>Cf8 z51UC}NbznXeFNDKlm2YJ5eFTL+O!@~D zzMb@E$j_ssN6F7)q+d?3h>)&Jj6yji4QOm?zK|2)~rA^kbBlS}%8q)#CI zKC+)r`ZJWSBGPwIKKMyLM)pfc{}}mMO8O+y%Sk^%b}C7~g#4@}{UWkcNBX~!okr3- zC|ymYe}U|5V4du2CVdm>+erT+>0#10lfIMm0Oe;3>AxjE_mXaqomSGfQ2rcbox-<~ z{!L2nVbYh7KkcLsA^j-n-z5DQ>ElR0LHb4Hr#8x~|5=pYEYkmn>|~Q(MS2eDuTi>k zNxy~Sok04>$WA`#1IV8u(npZ)Cw&dYTSEFDDE~`IpF;7LlRk$0tR($eino^ZOGvLH zeKgr`Bz-1@ZzBB^h2OwBh2Kp2mndD^NdGeFVbT|oot>orn)DXZKS}wpm-NRed@Jc+ zp?D9HzLoSg(jOxIFzF{rZzuhID&M1|-#~sIBmGXw&l9A7o9t*gUj5%j@n(_!6|$2} z`asHu9MZ>;o=f_Lq)#CIt7Jc)^tqI-BGO+XJATq%Aiae2`DDM8^t}|mob-(pzLNA6 zly9}9f1m8skv^UDM$#XlbTyG)K;bu#{_j*Sn@N9|^lha7n*0otUP$(Ll71znw}tc& zh2Km1ZVKN@`Xl7eLDF9&J8h&tO@1CG{ZnMWo%BT%{wV4HLGd0V{i~#(AbmUO+WB7n zf1C6y(!WM}Ht9D~x^hSlQ}|rc2ax|0NWYEJn@@T(*(oA@G}RwJ=_Z9QA^m-_UrPE8 z3SUn8QS!5r^golGTGIa?*{LJ_QPLYpKTdXF<%9y`(=z@wSp)ME)Nn{a?sV8|mLAJBLZXiu`FO{V!zaDCy%VUB^gY zPyU=B{TmdoHrlKIx00PK(r1#LY|_sme{x8lMd5Qv|0(GcNI#$KE)zfNp>npA4=hCN#8~O)R8`z>@<@8O$y&c`b$*q8%Uo<@opyl z;}m`y>CclMCjD*FcanaZ^cK>;MSkuj{S``AE9u{+@CQlXO?n&Yf24R1ll~E^C+(!? zQ~0B#KSB17kzP&VPmtb1;Whicj!sn8HF6k3U{}ZJ* zpY*w;7m3-7xlhRc}`f>8Jl=P=bFDLzZvQtU=C#W9QlKvRksUv+2rMHpvKa>AW zq-T^lBqlAN%sy z+{|A(|KZrN;kv&gTi4Vv#RI`U;vwJw@i1_Zcmy~^JQ8dYj|NAG$AF{6KCqtQ#XlbG zBc2Ej5KjUJi3`CY;%Q)$cqTYPJPRBp4uEyti+?WIM?4=KAYK3t5|@EP#EZcu@e*)^ zcquqa90co`Ui{0!KH`<&0P$*YkhmTkB3=hJiSGhOi0=hQi9=w0fEWJ*U?1^DaDaFd zI7qw&93tKdHi;hwM~JtBqr@gyKf{avF|dz#7dSw?8yqBl5*#Al12&1D0!N7VgQLU| zus+a>|5>n)_&IQZ_yur~_z*Zm{4&@iegzyMeia-gj)L_;Ui@!>eZ+5q1H^BEgT%+d zA>#MICh_~=2=OU!lvtmQ__MtD2ZDXXL%;#zVc;O~2ylpaB-kV#4UQ0x0Y`~_V12L` z|9G&Ecp^AJJP8~mE(C{&r-4o4ncxWVEO3-K0M>_i@y`YOi06X?#0$Vd;xcfEcrn-{ zUILB~F9k=5gJ6BA7yojwk9Z|GK)f0pB(4XCh}VHl;=8~R;(NhS;t*Ir(~JKBu#b2n zI6%A!937}!U=3mhQc4Gt1N2@VnO0h`26fg{BG z!BOG}SRdxa|18)?{2Vwy`~o;gd{MzXFaBzY2~LN5T5pUi@!>eZ+5q1H^BE zgT%+dA>#MICh_~=2=OU!lvocS{%kM)fnXo;5O9Ea7&u5g0vsY92{wsGgCoRaz)@l! zSRd}iKOXEOo(K*QPXY&t3&A1cX<(ChCOASo3mhd5fb|hx{ByxR;`!hJ@d9v=xC|U3 zUJN!}ZX3P?93fr`juHpK`Z-SgZNry?eZ(ul0piu*AaOl7M7$1c65j=m5Z?=q5{JP0 zxnBGafPKUp!2#k;;2`l9aEN#-*d%@!93kEgjuM+-{X8%J$G|?~UEl!mZg7zJNpOgG z57;Dr3LGKc4~`N?!1_op{%65H;^)8t;upX{;zQsN@ylS7_!V%3_*HO}I11KBdGWsi z_7T4c4iLWu4iX;+hlt+;o5b&fBgCh`QDVIq@#lE)4+Q&&hkygb!@xn}5#SKBQ-cnLT{yc8TI4ubUyy!e-ceZ(ul0piu*AaOl7M7$1c65j=m5Z?=q z5{JP0gc1M7KS{Nuqs;)&n@@g#7NxDXs7o(49FXM!Wdv%pc}09f~V@y`YO zi06X?#0$Vd;xcfEcrn-{UILB~F9k=5gJ6A}7yojwk9Z|GK)f0pB(4XCh}VHl;=8~R z;(NhS;t*KB*o*%Gu#b2nI6%A!93{MzXFaBzY2~L zN5T4~Ui@!>eZ+5q1H^BEgT%+dA>#MICh_~=2=OU!lvtmG_$PSr4+Q&&hkygb!@xn} z5#SKs z#XlG9Bc2Zq5HA1+iOawt;>BQ-cnLT{yc8TI4ubW|z4(`deZ(ul0piu*AaOl7M7$1c z65j=m5Z?=q5{JP06<+)gfPKUp!2#k;;2`l9aEN#-*d%@!93kEgjuM+-{Yo$X$G|?~ zUEl!mZg7zJNpOgG57;Dr3LGKc4~`N?!1^RF{%65H;^)8t;upX{;zQsN@ylS7_!V%3 z_*HO}I11LU^5TC3>?3{?93XxR93(yt4iUcxHi_Q{M~F{>qs01L#GmiQKM?FA9s&*! z4+95@M}R}bBf%!|XmEsh3^+>c1M3A|{Nuqs;)&n@@g#7NxDXs7o(49FXM!Wdv%pc} z09Y^d;-3rl5zhw)h!=o^#AVe0Q-nHf&;{xz(L|I;1Kavuu1$dI6}M~93?iv z`cyCe$G|?~UEl!mZg7zJNpOgG57;Dr3LGKc4~`N?!1^>V{%65H;^)8t;upX{;zQsN z@ylS7_!V%3_*HO}I11LMd-1;k_7T4c4iLWu4iX;+hlt+;o5b&fBgCh`QDPnU2K6E@ z{()d0@epu;co;ZHJOUgd9tk#yM}s58W57{jA6TE^#XlbGBc2Ej5KjUJi3`CY;%Q)$ zcqTYPJPRBp4uJKUUi@>xKH~Y{0PzBFkhlySB3=wOiI;#Q#7n_Z;viW6h!_8Iu#b2p zI6%A_93-v>hltmKP2#)25#oEnQQ{C-|EL%L17IKVMsR?56F5k`1so#Y3O0!!21kgu zgQLVISpS$8|6^bu@h)(HcsDpm{3JL;ya#L&KLw5u?*~VTBVc`&7yq+hAMtbG0Pzdp zAn_q^i1=l&N&E^pLi{Q?N*o32S9|fl0rnBU2@Vjy1r8D)2ZxB?1DnL}gCoSJz)_dm z;_m;q4Iha6gFemir)~HUaDaFiI7mDK93mbGHi<`rBgA9CQDPrh&+y_K5B3pH1P6#G zfrG?_;1KaNut_`<93h?sjuHpJy6(k47wjXR4-OD700)W7z#-zrV3T+WI6}M>93>8d z^-M4RM6gWz(<9?u?<;6b`>?0lm4iFCm2Z=|3L&PJ&Ch=%+gm?@%O6&vcgT462gMGvk z!2#k);2?1!I7B=RY!c4|M~G*Eqr?HQKE#WEF4#vr9~>ZF01gtDfkVWL!6xw%aD;d% zI7%D@>qEWxmxF!8E5QNc)!-m;Jvcei$4f-VTlun_&GcFaF2CKH^>A0P${ckoZY(hI#;5=X%L zFfaaR!9L>WzyabHz(L|e;1Kc4V3YV2aD@0(aFjR-*3b6he*^3zeiIxZehVBVJ`N5M zzXvvn-v>vCPl2PvI_?MR*m$7Q=YoC2^T7e)1>hiY88}3|7;L(X z`9C;9yc8TI4ubV_ocJ;S2m6Ruf&;{>!9n7BaEN#v*d)FS93j3J93>8c^>e-W9{~G^ zH-ZDio4`ThE#MIGRX@gA^A{1iAs zydNATj)3)%Ui{C3eZx<8Ch;ra2=S}nC~*|5kMiPw1MDMy6C5CZ z3mhaq4h|8&2R4b{2SE}9|lK=w}Yd^CRo47i~ljOk9Ze2K)f3qBz_VcBHjZw ziJt;Ti1&k|#1XJQ#*6=1u#fmTaDey)aFF;AI7Iw1*d%@h93g%c93_r|^|4<3Z-9No zZ-N8FZ-Ilv$H5`u_rNCc```%iDR7in$NfM(*NcB3*hf4B93UPB4ib+5hlodlP2$nu z2=N$jl-LK>^St=SgMGvk!2#k);2?1!I7B=RY!c4|M~G*Eqr?HQ?(^cG3-%Gu2M353 zfP=(k;1Ka*ut~fG93fr`juHpK`ZzECE}9|lK=w}Yd^CRo43i~ljOk9Ze2K)f3qBz_Vc zBHjZwiJt;Ti1&k|#1XJQ-i!ZPu#fmTaDey)aFF;AI7Iw1*d%@h93g%c93_r|^-I0@ z-vIlF-vkGU-vS4TkAp+R?}1I?_rVe3Q{X7Ej{AZ71TX%9U?1@iaDaFiI7mDK93mbG zHi<`rBgA9CQDPrhzs!q&JlID(5gZ_%1P&4xfl3~B=YoC2 z^T7e)1>hiY88}3|7;F+R0Y`|Jf}_MiuztB0|8lU8cqKSMyc!%Nt_O#R*MUvqyTB3R zd%;oS5Lmy$i~j+zk9Z?EK)eYYB;EoJ5pM;X#1Dfb#M{A9ViT-i>BavT*hjnz93b8e z4iY~J4iWDGo5W9nBgFf`QQ`<#pX9~=EZAqUAJ4~WP1kn#HSIg+zjJz&JXcqxYc2RD zMoV6Jnjz2KY51FWp=QYQcX^uj{MAL;U+`|+Mb2}0c;DZ#yq(u}+;{r)cXG9>Uw;4e z=}~gN*iq-o`8ZE(<$X~6C1vM>*$?J@F#AhYn)Y4CP5^cS-?_FU1Ur7)&dIy?tLOgS z9;$WV{lhJl=DHdF3~j3N`#-MjXoBDBcev2F^1ag!y?FZcL%CX0S*1B`#@j>v9VhRO zsAmP8=LaQh#x-+0wA@0Y8RshB@4vQVHhzom;tOnyI$f;2*gi<>XdmS7&@_Js*pR%5 z(Fb_?$-94`{0yJ1b%f9McZ}7>{;?@jTO_~3qo9q#-*fQy9Lt_|&)9|9h1x=dyS07L z*G4i`zSAbCCWjXd z+|dT!6?d$-X_8?MXe#^5Ur#?I>D8K0SL}Rue35(b-bx; zHvZG`-|@HM>-Vzug}+y_Z|nfApbGJ7TGNO-;a_;1&&bg3lh=yxli$tbbOUA4VdA%h zpY1aa9e*Erg7dg6!crDEKJ?USyyB&41l~8TY-y+`;fetrVhicsmvN8(OYOhcpVW|+ z+)ieM!?Y9@LI~`laTIYoweW4Bs@M|&J`_;vijA@ z(+`>WD{)o9m!FF-Qje%!ga^SFgwb+a@6mIIMkZp+}1O2 z?l(&I!N1{Q#I5X zG3x5X@VE>^M|}!ZUTA>#Wn?uS+9~A(eSgR4hoJ2Wqb%h*2>wONuVh~f@*)@IIaKl* z`J;v5C-Pe5)lJuQT&-z;oc-QuRW4sgSSc4g|BU+i%udu3>zY20h5F*PGjC^R?F#SE zj12ga*M1>ek7OBg>}aOG3+HbceH`|oDVt4J*aC!AX@)NOQXIa>@FA|xzz0WfN1DXf#n74& zu08XdT~~{S-*>)y`l0j1R~&CfT%Sc88OXc5O6|ow{8e=g8n*@NI`2S!;hfo$$KwVY zZ=-F|5Pt^ZH^CA|NUnAKUWWQ3=~#-m#R(WL(Pf%Xj#%sqcKeg72tb7cQF|LquyGri}UlmYo zXO*F?^x%45`R?~x~?n5 z|8c{OP3%8d{J%)z!SBjN8cE}9QklL@&WZ!>@?%I_{eqZDDqJ1 zme~6>u01@0e{M6v2tO`q#P1{UOYEyMJ^)61!yG=6ZTtqu-7-abe}m&*9T;x>3hW+- zy`Lkz*#95cm%s9#Tjn@l^}CsaJD2(T4C5H`l>1VZKSu@`bDjKY9fNCakn;bRk_Y(x zJ;aZ9cbpqOFvwVh^fs^83_r?PM|)FuqVx4osiSW|{nv0^hOf#{ZE*8dvpc>m*T>~x z*lE6ecE{}~SF|sOD!a(;lEaNif9a+XD z%m=`BI>)kJjq+q(4@S5lj!*Fa96pY8oXw1M4+V?Q=fh|DEB|@d!(e-?w$~jOOMjJ% zx*NV4<3!YDX|K$y{T;7K-hi9&dpGKuwCy}b@Jm~=Bg42G^+2ux6Tc;{p>mzz_n(pX z=cziIVbmjxl#S{G4p`wbBpiMpK{)AuRsQ0<--5-?A8?M8gyXpGYLMReKDbiGGETTkt6#kt;Zz@d z0M`bNIeqX9xgMceHm$Iw2&?*9=-f_jhYyZ^8fjAJ!G{3a+d)Wc_)@eBm(K3cezEQ) z^&4rq4d)z1zKA{z*P!Z8VE2pot@>#xEBNyW{D|+TM_T3gM;>2foIrnpb8doNxh@Ag zWz;%WUCWZs+f<)3*tko4g6*|(?crm}R`&g^&v|J-7WjPkd$69kHXM8XHR2cIK6LUkkPI6Gb zG9G@k76C#_ah%He|}`oBYWTv{IJ@(vBsOMB3qnPKE%oG0_1Fn(+JE#u}Z;D@TWqWQHKe~5l5BWuf{JS}`2 z;T{xC*R~w;fhB+1Bwjo0W%$kKX5zeY2xn$yJ{Z7xnK)1QX0!{P$`^fT#=!dzRoU?@ zMZC`W%{V^~=W94#sqASFs`dlzAfNLSjBm!G{4xjKf2dm7$$ap7>2vLNNrTSk|60z6 z=ES)V=gW9a+N4ax<=FTy=n~%#U`vkiIp4C6?ZGh#BmcQ=G3pKS-)@hs`M;C@GUu1D z&6t~i4r77XbM1M$TXxpFjm!g@5vbH&^h=rHH|D=DVh-y5mhwaTXJibd^2&`ZuVpBs zOdOYT2qRxL9B+m#sS7gZFF`uu&sTL6W92H;tsO{D<+-@l@Hh8dd%c0yCp4^S;LULu zJ6ZlI+lWJan=N%*bg+z(#a0kD*1VhvNfj>|Czk+7dZywV1!x!HQoTkD#8 zdtt`oYHlX$DMvKpXXp=5_HC+90!tqME6QAs%|f}!x=0<`Mx?JT59zxM=~Hb6u5mT4 zrgW*b8r1W>(#IUoj2-xmG#o;|QZ`QJX5h0S|`>W;zusbC|TJJ+z z!#MUA851J>Ik5LM+OBi#x+wmRuCe!+)>?C|x@-S->+Yb>9+GxuDQxQl<+uJ|Gs5KI+)Nyk zwnXabo9H7l?6A)xem%$Ei`=#fik6vWW3z7D7P}Ubo-G+v??@$2zW$8b`Whhrw@9p#`pPO=3y;}tJJ=%8 z>p_j55nlS-B@*T;Ykg4a{>yT$;<_;LyZI`Ahm1c&FF{^3Ls#`5WrM!?keol?K3~rH z2=X9|bEHl;V@@sOEa~^1>uNe|xYrfJUuEZ`92dJ^Lfmp)%|JSY<-fPpb@i-02KcVK z*8B8At>ft%@!w5I-ww^V2IoqC@i?v09@|Wm{9CUZqr_K)l`&p;p))rhTr^z$&i3!il(jqP1J3mCQ(BgP-x;}< zHgJHY4Z>QaYCrwChO`Y+;k$L6KX0e=du}>q%y?E8X&bE42H&&&9r_3JUs86p3zglm zuHBz>%0I2CsVw}2ru;ygfjO{dOqOd7f7QAv@>hOmWLbHYcaQc+_=%={xmm3bOa7>M z+AaUh8jw_UHpy(*!?+a{5~4 z*jt+hV{JQAGoa&qf5%VIMmTNK2)SnLvXpvTrPg_L<6NZ29H*ncU$9Hsq(`MM0zY8Y z{jw>N?k51 zFU{0O@7jmI&hfW#{9GKb!trg;i?p0wpTb|*F|eL&XdhhjlJ=*2UOHW!`@Ez{(=dL% zPU}$p<8>%2p~+Ni(jNGQrbstXy$ zw{T4A0`jNq9Q>8z=fDr?n<|k;Y5y;gG8|`}_W|05<{ijO@KKaOqqKYYJrL)pc0i3~ z^3;6yaipWud>1yq1e26SILggK%{Yqs zC3$!p`JnO@>G>_fdF7B{{08jKC1LMZV5grFyDwRGcfBSyhRQuUXT0+Lgmb-S^c&JX z<;t}$b2^+O{gWy4vp>RD{5^U15AC)kv5hcnNZo5f{z!kNt}*=9u=cL<3O1E(91CB7 z@h{?&b9^dJ#CL(eV>se+bn(TDGj2{eR@*lhX>|HL8MBKm>sr6a!1!hc#`QCvw&tyE z8SQ9?Fz5WFlrglcrM_Q{>lXdm?CAYvCVUzr?F_gX>kCNtzR@;Uo!M08L-~!g`I*7i z@eyD@?w9I6ycS~|e@E4iK3{g09VSm~!k@u5zxFy9@tzCq-QG;cWgPXEoQv=um22fANE_mNQPK zdG#PJKhMUTf0VZGFVb(yI09v(@)Uo&%hN17Pv4a5HZD(pV*AuAc{<9!??*N}dHRga zDo=6z2jKWT-D`(Yc?y5NW3!W|EjBxO`Yk8Cou@mU@MoxeJoKo;N31dm3n$6*f5)+| z^4zd({Z__9cAl@b^SrE6o_{BHT!?&bMqM;! z{V(co1;%?)=b_2(W?Ykx+x(#9z1_!W_;C*vWnK?kat&%jQICpsV~O+?=+{jAmhs%1 z;^%tZSR{J2Zna~TKCF-H#t|GdcVyt2L0BA<^}BjI-1o&tgxRm!P2JclT&x>U3FDkI zkr!w)|I+?B?;d)SS_jpQ8L*+o6Bz%RJEYG&XV*dT1HX6SeDsH9Xw>iZcsOMMT*W(HW*_oHY-aGtawAE4aLOx+NcI)4Hzzg2xl8s4$lsqe>Z&a>qO|j9XZJ~c z+#`95uwTZpX812<`>yJ*Fd(tMfTL%IrMm^%=%+W|ZF@@W+ieQ#S_VxcE5=^-})If7U$Yr*>Z#x8{Vtb^~mn zuC-)@ajk&W`Kk_UMhC8W?w8*M%kk4V$F;BeV{4v&^6vkPX%pm{Kz-P({q$OCcU$tb zaq8OpoYegTavf^BK9Ksp9`ysP!uYK)K0C}W4;p_4H>qvJ`{g?tj@V&B_ zbXSnwf5q<$&32l2#zC9z4Nq-)p)04i*w$^Us?Y) zcVtR`guQte8_g&Ov9(>U8R%?ls$&cK8)5@tUzfi~qbl!eTsvSj=i6ajM`20Vdfj+M z!a+NXzcUf1Y7-CW#xKBy4j<8tHtmT9W zHgkQZ@qOlT=>Nf9o0n;PhmRNQMhi32bp`U@wO6ehJ7bRP#-q$T&>n%M?c9xYyKSgi zEBd+JM*mlA8@fJm8#)bkB)_B${U&~!h+9~dhuwz0DE%|E^%C}gHTJ0!4ZlaA{>b&F z+EDOa2qSH%b{^{F5i4vB!b*7yOB?!{)H(cKBle`;QX6_N>Z_D@7}tl>hJH-y2*Ujs z;bX4t^;X&EArC%>vf^vtTSq&$NmX&zSuyq1aUtT7x%>!J`cZTKS;`LsO`rQ^!xMT{1`@dhvN5h$VYYUXJ#5-v&R!y>sD>3 zj6b9=8!q`f&TB(2Lwk`ZpMQ+=FPHe-uvQ!Tb>v6FHq`cWAI?+j+s=7*8~RW;=Y13B zsdi%=wHvS@?Z#%QC(u=%+ilRLsAsATf__-)C&Ioge^GZ9Ng9iF;}BT28|$sQvqkcx zS~q^G!s*6y`0KU{J9Oh&un%c=+7THayY0dejQ7FrSPXf&AAWgj5}C#x9Cxqlak}v& zGyIwazue=+81M7(YTbB@c|F=~uw3sg@H_Up|CxQ=Tl)#+{`4cLhcah%?rEPcroBwg zI{c0-<9x}3^=kh?OJ3{67{6s1JJt9m%NT8sTh#T9Fd@V*dv5MWI@MT7>h*yvBMUav z_)pFUi*FB!4g9_nVe6#c;Qsz8>93F2arrM%`vtxr;h>$i$1^G}@GTM-&RM~6jT>zI z88%d0az0q%x<_o__fmxYxWrXG*f=6_6|4P{NEgCFtCw)lUgx;L*GpVDzJTLeKNuTs zopj-Ru*6j*Ht@R`VWp1=?-*thu$G+jMBEl-m)ki$at2r_2FOhq4fdtP9JU-&ECJ zW*ApPbK2Gt_%7|L_%GvA8GlII`jq&F^WMR6iK7rcPKHl%o~(sSnEyl=e2U1^5dJ8_ z$7ncL%D+AHyj_moj`L+rLDDMsjlQY!@7!H-e@5la5vzZeW39^POk)hfsB5PQ^>m!& z@3+NQ{QjcyRX1)xd0&X|a&N^M4{flI&Bd|va7@zqMHQb{FW$x4-pRY2c4(hF{*Zij z_QWi~Im#yT@h1F!7x^H0x(I2LJW=z!!`F7~l)55oZb4Tw3 z`ehtG5x=ZM z&1i3?qb|vutSWBK?X)$g*UNqd+9QQNSJvbLVSBF)erv7Ti&!h_$QayoXtupaN!l0L z6C6-1`xLOghjTI!rWhL98MRm8Y2=YStKgh>l|7cl^@aTi-uYiao;c^Cy?O8}sB`K* zkUS?OKESq$qte<>^J|oy(%~EHH%nc$eMWugkn(Wa+jiuEa|~_%<~+175@rF;vGy<6 zX{BS=;+V9}(jH2i@D9qI({qdXO~-Vk-^r6@I9J*==Xh*ec+Rd`yL=a*yrnJ79AxF4 zr2l8A^HT12qkbmxIS;lo5Z=kx!6^5`u=7hipCx|kIOh1F?2z5 z?0vBJ%sOMPBbxDji5p>5UTK#aCba9M&g1Wuh(DZZ-Ip6FbsxWFylUg0LLXw z&%+1#EB{&bz}jPc@@}s$RQsOb(?=!!nRXtxB9EaxFMSjKep}L7oMB9sFi4{+hsp_9 zv%&hjgu&lOB}_HSJu}1jf?TioeU)8DG0V;hsks$^FZZOL+W!3CG>* zSDxFFb!#be?K0#4z3v^$l`%dYy94o@pyL_n2i)Viml;2!W4W?6<(!LS%W>`>__#c) z?WMM(Qf0U-Su<4O_v!@(7BcDGme$u+`H*q88b=SBjqgPM6nm< z__WK6M{IiEP1P?rpC+;W}qg9 znVS`hTWOMtpytOi7uM?2*UCaIH$}eB*L~mno;$NM%liF2e(`uL_uskaoO{l>=l*@) zDKwt6rWe~-%=}_HH20F-1GCuIgZM`0ZLU92z7xI$Z|=HKCVHl?QT8uergn@2Ms@cE zpY%O}5L=ugI)7J`aQE8M0^212=FUsz2?g6*yzeADr&wTzjIvPwY52*QtJP z=5o~gEIN>H6Rsk%RM|G!FZBZ5H3CoJIE!Os*V_^%FWosS2o@d29W414&6`wjEA`Ya zrG;;X@!R9qK4<;6@}hRtKGWa8T0y==ZPNV!&cyrPA%3)mAYSQHyQELxh5=*dkE#b; zkvYV5^2>w?%9NMVcbDK1kJ_Vf_zG`uSA}|&=l;ID)xLviN9;eX%pv_eB|hS9f!k*%LgGGxwWm_dm@13!5XJN<0BQ?mZJvC%9LxdD*w) zJLNkN)_eqbSylzAUUjV)iI9FHm+&cP2g7PLi z_d5r8(+|Z1d}-P8xcU${~LkBA~S2XlfQ^Qrr0>Am9e5rh>@lRHE zJ}$N4`t8wN&&}0+o<1m^tS$Jw{Tcky>8q4Sdey!|B1Erd@4@KTpI>uVN^7}nTkr%t z^=X_8F0%Sn9NwkhN}YZK9r$z;P6w(3F7f|HuU&5`FJ)zrp32$_deGPopVC1cy!!a> zqYZ`+>eP*}@+E$}C*LlOW0pVB+6FBr=fGZA^62_d?~nIt^J{L+**Z1w=gjGkoOl;ASn zMF{E|Q$a8nAHG<0CJzf12k6G^UC+vOYVO2ZWyL3{-;!kRFmmJD6t*l)Oyry9x#}-L zePU0AZFRv+aB(lGRPU^T>V2hDPvfFr?-a{#n)^aAzn4rh{EBb!Y-o1{F>SL>_Qyy& z&T3rbUgLa!{B*RyvqJZqBE7gV(7T6-Ol1$Brhh1;Z-ke_n~A3q;i_#eKM~&I;`#FZ zj-i^rQg>V%Yl&pEM>7f=cjL{rfbZ_w4m0D>bZpFxH{_A z*PI9+5pRSS056%~OU?KFPXe#GusJIQz6sqYsCy8yHvSeH*8JG{A<=k; z`L zkK!9e_rp=#(EX@|QQfvE-lR638^w*R&X3~kyR;@Qh~mq+?~}O5!YF=86kjObE{ozV z;$bQAEbA%FS+G~LUL`G`cBaNt;>Tm>+*9iurM|p?xoQxYcQ7{MS7g)q1-Z_q^V^P9 zo&E9E21avN>L@;WX|VVd?kKAt$KSYu_aN}9&XL%No5${tZD}4f)!L8ot!3X0tcmwx z`47uQ|=()bB`g=Gt5rZd3p3iu&I?)0gVPA(YFfUqgGfPB$>Rt47`=wU;nZ zzs_>ro2(1}qOpVc3d)RMV*NCpzNow%;57RdljX+~A?;igoQ9bP@!IhcBvZJ?&f{-MdXsWS?C6N*x|5 z?`6@toASG(^4#9=1dRmx(0FHH)4)>xq}CpH0DDIS8)!{UT=$Ej`D$G_M?80PM%RlR z(Z_E!_V;RUhWiVO6aG+qQhu-eW|Dp0WL^negU8 z&;mB5McmM`c~xmqw%F(i8aF`W2EywIuN#EV46mjz{WI*8=5w+lBSU*Wiws-u7t*w&}t`aADGrg?VA7_(;{wY z*}O_xY3-v>HVImc;o~RqH%D#6&hyKlw~TNJ;gW&rwrDO2Y)qH9p=2}FpR0dtv zOXD@39V)u?qkqemGm(wIARkBk3i^S30)1wR(tig={va>9?Gfz%09(jLcaF41GW%8P zN92uFKO*cAZs7l*5Pzn=qq73>eG2|62Os(FAYAUoNaAL^(HKeG_*xANaEWxPH2oIZpJW;k%AdbzQ#!6%LSt`QgPAM7%3QyRT?9S8~hq0i8pEd z(-iEmWC#z@6AZpl9qF9?suNWlxg#z^8#qN_2IxM`QhNaBWnjgiV1PK}Yu zS6pKxaf4H1ByrP5jgiDnTQo)zZ_@auF_QR3jrSTO1uyw%j1;_ZYK$aq=xU5CCc`@U z&5So@kQZt^A-ov>w&B7=G)7+7(i!+;BzW={E;nPO>d)_?j$kxKQb%z&Miwa7m^azd z(tKZj_Y8&j*AEiD2YWE{yym*_bkc%f0KCb|)P<+P%drk7TNj=T%uz1gUKgH7I-aW) zNdrH{m3P;L$NS~Iq$wZa%A1qn(SCU{86N4Uvx)F9(xpA>WOxYav95kL86N1vw2kJvEM!M6^l~qa&HTk){irK`!i*KKCv^nU_aJL ztX0_WJHqI-uFub}BR_xPZ2V5H^)cxbI>J8@ShJr**}eFi0(4SKCyxu3?l-R~2pXG4 z24hpKh4R0x&O?4{eW&Ia`QK7caF5q_Zg{D_a}09MZ-r<0Qkih&UotR_;;{fd;8zqL zrwreP3}&j_w2Ar!;(5xnw%Vk(lw2FVx>{Go`L^vZJx|-RRob47+J24N-kbo#XLfQA^ zyMdFRZYN5I1&%y{O#S*Tu8@ZkFx6_?6BAap~9S z$ihhPR|?045sszRa3Gtn3kPjJ#M9AYV0|5ZnshZCU5b2)&^Nk)hYs*bH*Z(F$cNV_ zobA!xnZ~ZMPxY%&nja|s;b8Hd8vCokWAEyV;5j}2Qhle+0rD@=Ug)U3!YSR$_GP4|&gNcI9d!E#bno^Qh3gOG$9@{U{M}CjzXf>F?|~N+ z#{REln>&}-^|rS^rf=2VeuefM4paZ?EuB;BqkaRuLNXDqkxgWXQ~o{0v&6@-=TE!< zOb2B}@=TutSBu(8`VQGKWor~RC&Jqmg5x%YN$L>BV+8QpZ;9JVy_@|s^>6Ug;JMCE zgYO#BlEJOMEE;vr;J!ECy&V3J+U?2VY559hOAhCGayZwM!@?SJm?&J?MEFVgk{r%c z2(FJSgho~&co!1-a`+f&Uk)Gf)71Z@KNs6XnH);{rDk6dTj&Xb z%?Z|q9a+|-=r#a{`lY{?0!Hi2&51VFkmxyyy=%Vl0rjc&MEHPYl`(T8-U(qnP2T%7 zP7@x6-L9XOX3R~5_Xw_t-4NG(rxrVw-=X|oSD!Ov#`kwoX7fx|X1KpEKiJG#oVYueOZ|bo3^d4Z(zs9f zy3u!C*r#xnJ)Z;bdW&-|ye+Ha9O&TEInZ+X3h*2VKI5a2(<^a{u{qMQx`t$r;*2DmX8O? z7tPJ0X>7#pJ@4K|E~Rcw8|jK{doJZmp3wd6OvD+;Bd!(f)LP8sijxLpT@We{?6{SnS-~+n$T=PNyxwt&aT$GfMrQSZb8Gl#b8UWD5J zd*GMx4XXIlJV)~c4u|5s{U<3Gz3uQ~>;U?YlNZ|oygd%>I1SE+Y@Mg5BYe?1$&HQn z9z)(HLR;e{_cSYsrMNJK*O;$r^cb zHXTl;y{EKp4|0)`cmb4r$5aZqXsG^@Y~5<{Sll+H<@N zoP{KNFvK@Cx?cgLAQt7e%jFuJaq)uC45=Z!HdsB;az*jSyKmE@T>hTqsR8Ta3SeFAr)k?S!6!cENrr^_i~6;@>s)c}sJUWqX>Ry4>MN}|;M1NC z+t8QnSG>8qJ5!(y{weW#ih6I4^43m=enj^d_RO^Z9o7FJ=g6LpFlVUWMi=fr>dqyl zw>yphX^+m|4hO%P$B;I9?22@IH@G@7*n`VYO*6V&Bb_%#b&tZfJCd>)TemHO$*V5B z$8~d2{&!Tjs4;=}JK(L*4Gd}7#97#m;(rlLH!w-wV**Cwm*BQ4u6)w-1xxq`3;TP8 z*+e+@_ne(0(>B^9`}h574tWb^I4~cT-^}p7iG`QHsR|Fw&fo32;WWWFC&K>_7ygrL z!2|P8U``Y~?f<)lKfV?`FncC{&)2yase-lD<`?YC`RrLa8_-byH& zQ6DyaqL>J8BrjeU^(MmWNymMlI}xrdO9$cAej3_W`ROEgX8d$B-~apR4EKNhbT(o9 zM@+9h5ne*M`pxCcH6t5#cOy&lCUX~o`%eY#&3EutQl9(RTaXQR5;_@I)&-Z%;m#HB z)6t*MRoQz!#-GC1pETYFe23mM&5Spq`8lgQ?FVj_pPQWSdky=k>XT{9Qw~S!$WZn# z9A^DO{3LK}usC%7OMh9{)b@^W<+C;LW%+)=;p=)w*g;zOj6D%o|GA?IzWwmc1={~b zi&wUp9)u7q$A?*-RKJzS@-5Ax0bK@ZyRnu{IHWgY?@_9R}MQbDK9 zr_-AVKkU;XEjoXu&&tRA8alH@Cs`MMz`^B?HtQ|HJtsKIHUOu&mgc!9I=J*ZvBPK_ z+bTHXKLYLu;7ng_W_?FmcpjjR$;taIrEy62cC;Ss*SGM!%t^)? z`2Zsu{x^NQ60S%4TDIKSTKbr&u&)?6Ve8Q)z&4I{EV^jM|H-6o=W5SMs za8v#+@D?oYMKy5ybFR`@8_f^henH3RXigCIpJT$hF}W>EX#a-CxeL3AzlZqi=Drf& zn&meTC;#o@cT>7keP8_~{}6YHm_wx4yVKZOc$_-~{B}nRzunQq-LodUH!Xhh;LJZ{ z*WlpRceMF#>eK$+)fYecZd(_g@{ij%JQaxNH$?Yqr*elguf2~nZ>MR0Te7wB(sy9d zTYmBPfx4H){HTz@c4>o|b7C**&zEYRRAhWp8R-VwuNIs6lg53$%UBmCY(J7L9z%u& z>U8unK4PnGPwfKXB>gnwyZZ2x3aR_Fne!&X%}PT<`+@N~0r*GAkH;R>6`@%b4z-|x2r-0!#T_z>-ge4TsyrusW?x)(0Gg+}g6O6E$_A9Sz#Rke+` ztTcB23tz$;jGd*s-2L(OFsBG|$3}AtzWv)yI>YyT9pGOLPVFza`{pG)B_FJBWHqKH zemckSpf|^tmaPM-z&>1pU0eoxclljW?vPi$&vhI9c|&_*rs2Em*l?8k9(Ns-P6RVU z-AmXKfL9=D(6mufltj0H{!4ADK}>%%XHr$w?+4bU3md{y+NOM<;)MZTy4t} zY4CHGLT@+~k?(5i-^P0Rd&ug~qGRtoXl%$~CqJQ|DBf1Zm*<$j{0Ba^S=*k6-U{vb zmSQv?=+*jz_%DIE4}U8D5^d1uPK8;|UW(2yg@0(||BSwgyLZXV`(ea=fg{Qj-~M_=ZBSW3{AT$c`M08ry{D3kn?{(u=XHsE#NYk$nHPfV!gSx? zuuuKI)_BHq^$q{NP+H>*FcT@uF9N=o{bAELBOhB>1U<)cN8Z)U-&5h6!=s>e+G5W zAe=`yZxC_>RRc zPI#Pnru+o)j7{7mKY(rCppZWBEy6-JvZYsqkN6SPiTAXNq^T?ac`WiW^SSOs_;z6Y zG3o8V#QPh`x^RELyqPrRv2TN~z5H_c+l%rTA3ThrTr?*_vwZA&qWo^Q9lLp(uT#H; zz}+e39`A@fbQRF4*ML(Aq>rA3)JPn-*_X4=zCsBvcUAZP&_ zw~M%;W%H`iqHKlQb*jz--5E#eJ4Uls#~!pVp?&IG(3Q6jw5}<5m)R?NkomP>E|D&i ziEu4G%AAd3cdr}$<5!4lUwDPa^$hzhgl^0?_eRM(SM`DW1UlD!v1tD^5&j==U=u5& z{nMy#eL;BIIS&AKPuW8eUzJRR8Fb#&6XxDxDNp5)@;X-=l84QpdatSv?szDq~m@GUD>z4&o|YtpG<@YTN>|#M%*vG z@9uVW?@_v|Lw_t3?Y-XO2&0o3)bD5?L3>ANF2Oh1(H@-EQ~B-)=1gSOkp+gdfk{Vt zn{Q!;f@8zuNpmKsaoN3fH(v8I#C)E&sgz-6XqmaOp$CUjug)c4zd| zJ0jG1aXvYMH3N0f;otCodE$?oHtDzVg0(+Y`yGzlq2c}DxYXfDHk$cizI%AsL0mX~ zFC4^wE*!Sa3GJoxj>>JcP4C4B?@><^&qncaqLGQ>4~wVfDBkiE@njVLB6Rba#_&i} z-pIZwe3R~io_@O%8WdF zBE3HiuN}Q3!#rgL!&}|j5S$&|`VpR$7Cy>9Ilf^8>{d;{AS!FCBY zOFm_VcD1c;?GnLe$Rppt<|Ei23AUMh$_m8@_9DS1$s<4FH-cSj_-%w=V3QH7?hABu zlh=!GWcTUg4ZT%{U-Bs{pnvgjrs0>oqJ_;zu-6-Y$!`a?H^Qs=f!;RCXDw`Sg2C}M zV3l{AWDfjHg~i5jhQjW~@SO^K8^h@e;qx5|X~#5$nUUe)3bP}_sS4XihKDLFjtmc0 z*gZ0QyTaa)VY5Q?(xec*O(yKfSly;hU_KW8rqr+H*RYKOW$&VYVv#EE7qYOWZzK*QrHTUkA{&!VI{(<1p{NCWnzdORy_3rRZ+5hwWrWG)~l5=qs zG7r|Ci!beH$9_piI*#o3Aft;B*x0DBL4A_&DO+!=EgKFk-8JawR(sM;#VEg|2!uR?AO6j?RXq7ay+g6SBj?cS)a_Vd1d~ z+nKw9tB$sfL4M9pX)kU(zOB-a=e54D`WDO`@SyW>^Lu~f7r^UemNR}0IW-RpUllKe zmm1kY6S&#XOu|C~88z`uCHqy#BC+C4@=V*k^WB8*P+Gd`yethz)1C=-Pp4&j>nc?9d2P)4F51$*TynT51)IjCM;o+kL zm3I#he>qTj@9^+v1C=w*{Ai%^Ufz4bo@>f4J0iSop!(S6tpk-8M}*(9|O zWB6G=-Q5^|#!vS)hM)4&$&ukF{B-lk@Ekv#85y4Kr?Vr&GyQb?$nZmcx;Qdy_0!!W z!w>lB-jU(^{B&|uc#@xP9%cN;`&@f7qr!JnKHH_UjJKqZaq0F^;W2*w;;8T_((&0( z_oz_*V~T@s9u`g^U0U1k5gz2%?;RB$KssJSWcCOr`Q^>@e?N`y`7UEtNngXlv3_}W zSh$Ce4?i-ZtbC7fsGn|U?C{gYVd0zj{TP4uu<$>Ax_4OkFO3Vy;o&QOe)I6~AAUMB zJp8Ml&N4Ug)9t+1=%MEInirq7**-Nt-pM})ue%iBkU z5Burji0~JrHTG=5he!Lt+!yX<+%Rt-+Ha(JU&4RuPGjb~8MoZHog2n@+8nKywPt5M z)%qdLv&cIHxyEpIy`y*kkHR(tYsNv~p50m+2QQV+CvW2xLhS)SPx`2aCuQ*n#>|&e zv%+5LRgRm>wJx)`9NZTe_jO)U39IqMKl{?!FX!>DaYnxp@N#l5^9}aJ@U}|7699}G ze~@YEt_yAXl$EpYAL+hT^roQuotFM`E@)q!{b7Bx-veI9dk0_ML$HzQbqNX^5xy=#KCDkzi5y; zsceI%m$ho|D65y{RxhT#oCnC>6HjOzV)G15?=Ac`^(o4(f@Xk> z_#W82w{Qr2RPzVxxzl?Nve|HR&^b>snh^OVBNy!Z6X>XLbGmaSx-@xRv%-x}M7W?G z>AIrMM{J#Tug-k(u0%h+Y$|u}6a0P|Jeczb zEYpABQa`qcQg1;FaG|+UU*p%P){`_JJvL z{c<1j!cuNXI${{u0vCvDOQbj@|vgb@BPK#trE}<3a~G;6NH;+)2p5aD z=sbC4q6=Lu^Z&lO}f|U0C(nzqh)&8Q&(` zNNUfHu-AU;zSVr|-q~gSfqazqFtxvspTYj`jFvu)|M^)_Jby6Z!7cJ<{0(An7}(fm zh#T5Aud2;Z7M%z19vys~i+{~ePw?B`xmK-H^0Qiu4!bmWrcHv`OXFCkA-t9TYWDt` z8zNszp4y`Mht_Ka^7YpG1nflnwfX5S<_>KeKGL6K@pk(Za~7*PB6VKJPBdRferA0q zTK_>l8^|+ntHM)$GV+*AIFWGTAmjpUEEnQNE;g^KTqt`EJ#3I3Xt(sF`fA_Bv}f{x z{084crSA^|t#^UjhRrtR*{cQaCgdR7X-=pwMD@o>cI^$}2juIrR&Jz)_c>%=AWt^- z2($~x%kW9wed;^F-7hJ}(_Q#=#q#E|-|k>kY^Eq66t){pc3s58eEc-=P6!Pir44zh33Y<`UXk0H<_#K#H>t^it1y z=|Q#UIBfB_NcY^$o4+q@HMZ-Wd8lt`Jun-27$1`w5@uN!<)+`@)ZBbkL4w4K+pSt-8gcAtI5sn)~-vd_dHDe}mW79UT zYTu*mP1z^3^P{0Lny`_uaS*hCjcE}#v}|5gT9oyQmX+%^F}iA%kX{fcY%EcUoF3z_x$cH*onqP?X&4^xYe}VjEC8VFr-dAX10?C zR`_prFvW)ODj%ksw1v6W!SptS%X}F6@De{wAKyhkis>X9!iy;vo#n!zy*+;WdgqdE z@3&;4JtXr6DEC=(#Nhv&;`>dl5nTU2U47op38tvO{uk#2IuFp?MK-uVK6Q|Df*bKW)#d~TF?aIk1Uf&E zpC9a;;9uyVa!&9n^7(Ji33MLd<^+Ym`(?2T5yB@t?hy987 zA@GD5bfXF)j~Zj~onF2F=2-j~{m~zb ze}&!$I~G3xY_+kt(~g5~EZ#`F-B|ns>cnGYdxN=ur7=?L1p`xT2ygRYx=DjiFn2qc z-iGiNABO&Oqo1ZO3H=fyTVa*EH3{>#C*%bn5y;0Li%>;JGZ+^`*++$ z*0;MFx1nXn$__59U9g;KgpCP=`xA;wyr2HHC`-R_^ zy(KLicRWu6ZnU)-y)CHoV!bb^-<;Ln#3k}s#6P4yOa5xsD`qWtWoa##XDzs4TB1|G zS5)X_-(lMF&ROTDj-?Mpa|X&3=e^p+qjv3@Z{`!=`kZ{-A*>InQ&*SUvcFkV)rIC= z+vfUkqv5q4->7dM>ci8;OSV3I!0k4Gm(M=xtg^b=} zs9SqDIxBR;ANg7%egpX^K2afX`zoaV1cl(;S8WF8IHjBG!@Wp%wAWK#do&Tw%Qk_t z2u|{a^D@B_AF2?z#wcd_6#^s}PjZG?hPG!IaY5QHEEpurz&!Fy0 zk`uW9rVu_}RESPL8QHh@Jv%%ihXc@y=@YcI2Uu(@O4H`w`{_)5_>7;Sv*m+2PrJer<8C2T2?hE*KzE!&DPKFB= z_9nvx3N!WL#}qok-~ihXE^+x@KbSbkesH+OJyjvPI8-5aaXb9uqo*9{?6a5*H6M@9 z@Q}qMVEumZHb0%I5BK)dS<+TUWBmN)`fv>S@p(>reK^c7FV=_kei}Ihej1+Npsg_< z@P1ju``V$%wY-0n?g_YWNOZ1e&Nfer?r|(X6oaM|IaK z%#enjXnarUB)L_IRf=cs$(Z&2V|(J(Sti`vbfSe>Xl?cZY6+ zwv+1=$)Z@2>rVs&-v=tcmwYSNdzDXn<%^crkn3dG5HMep{HS}SLU4V}%Aj58Zr*nx z?aOt!mBk8ZI=Nov%L`nW_-SxP^YKLZ89yJ~pRu@ff1tWtqi+!19w9PXr{8+GNW61) zYGjrg9_pLWC7OTm{in&}{6=9iN7;1F=UK~``GUPWaXm8Otep8hds_!H9vw_*zp>Q3 zcEE48ffbC|CnuiJ+Q;Tq-SegFXz>dl?)%NW)=io-?~az{Vvd&PF`{Mjs?wrN=M9pF zxnIF}n%8)X9qg7CurV#-hL+8%N{g~LWh-YyXEMlp{@RDk8SfP3&Ejp~Pm!m6R`ogU z$!Z^~ts-yd68F1;g?MeLxq|k3(`xT^dS?$^v5vhQ{Ms+dDeTsnRj{@XU3L_Qvc4Ir z_2N4B-QyDJnEc_tW)27KGq63;FK8|cPTFejkuI#@M=&|TKwt7G2v3InFL3>U`DCG) z{XNoJH%&2qjWY87ET2#KBMZM?VX`4S9J(62Oxtz0%;H_4^7aVtjl!D+FKOW&@9>iM zgzyrsvhcTByc0aUdHB`7qx#TC@H4t&uCr`+9ymre(a!!Kax?pUMeR{iPM$mWTY;P; z+X7{Q@iWL>>_oIh`F2p5o!eRrlIJd5<+t~|^6m|x!B5Y8hV&vA#H`QuMY_rT6YFS_p`{5r2V zST?2ma@=Xb7GG>(9(;iMFLnPHnh`F(4JQ4h#q}X@DL?3H495x9`P?49m7Kxvj|ivp zxdH1b`C9PT%ig%Jr+a#$)dZ~uOY2cbE8zQ1Xm!fI1q;t3)UUyDlI#!s|BbtDk80e2 z=l{ky@Iz>=M<#0P9mqj;65pdE?|U}yUgQ_eFQR*MyOQHe-+pssaQS63ZCN$H?D5Jwe4`Dn zLoJ<8icT{6&ixYFsrc8iC+*X#4N2%KtXIe!1sSbt%8xPUqe^Rxc+APiJF7Q$Sty^Z zH?P3?xk{W4SFmUJW!bpJ^*|L|<^1ny`0vAytKZIme~p!S=@LGDnVhSBHk$m=?=klu zzQOrWfq2k%O>55=R)1Dw?J4Hoe0e?j>q>p+_2ycn?{!UG5f9H;9`=?DdxuCiE1QC* zumc+JrA-}tV`Ih(%G`Lt-f-&_GiD5rWSJXl?x);}?LxEcy~ecc0~`mzkGX?p?cgVt zuI^UI_i8U*a4&#Y`|vH_wRejq&~Co@Sbn(idFz6%vEfUSNqZf?M>0G1F=qeLwJjKD zZ|DmMM zi05-H&mFGKsqx%{o!$3CwfRzbxisQ+8+OzwUMb6;O8%*Ya|!1Tf>&T;UWprCZC+Jg zDf_H=m0adPV-8`4Ff#~Rz{a$Q8(KE6DlN)%UnZ7I+jwIOldN1mDqUt`U9L-L>_R3V zk}mZ-BKG$wTDZTnSn{DPKa2cXgvSvcHwd1Ajd><+c(!>}d8W+FC#8!S(3n9uopAaf zXaO73B5r8eysES)8|`#44I0y+HI;DcAZP&_(;{wY*}ST>D0{{4S3Qv~9$+pbzsDm! z6ZhEns6>#0@Q*SCtlJ zKNhWJ(3k*?354Sa#|?rOurV#-hL+8%N{g~vL@PUndAfzq5GUOvUm_nuS~$($Qhxi^ zu1Ea0z!sYEqXcJs5I77k!eQe|SMrgQFS6xdfS2|3pLXxK|GGLV~p-z zSU<0|&qmD?diD;@{v_$sCF^U@{uf$ZB#D!bbwOIN=5HWf zP{+X;UD&kIr&m^07q(pI0kIDZCK!CrF)vZSn}~k)(^|o`m2p#houbKo#@3|fP_&b= zZe5~nLa6rs1X)6FQHt^0J~@~e}~26&rehO zcC&YMSfJdry_-1cSe~SvoNDdc7~7_fgER89X@k=%t13@h{-DOs-Vx!+@ToVH#JAoR zdWQK*M{$JqoS2(TU>`1eZ)UsTnt`M2dCipw?_v(5x_2n-CC!*5nCE{{J~z79>ggWj z=H&ArJ~O`v+glXb-a=!0o^0BQ7d_dKc5NO|Hq>!&Mm9EWWaE`pm5nW5BH3U=8`WP@ z`xtz)Z5wj5A+nc!cw=vN-%{D-d9>ras2v;i`<_{Nr_9jI5GNh$n6%?>z;;l_!I^g0 zw880>Rc(hYf3Mon?8!7qoOH|!X-8u~UZ~^XqW&AD4KH3?@*$890)cpR`T)Nyc8--*%&r&m_BEw=n+ z*?AHAPRH%UNyog9b~Fa$g*px{vQ>{4udFIBwtTaA$wJ@xlnimwF)ySYjRAR~j)RNr z&Ev%@tICTlH~k+y@+~uWX&S?KsBg44a=sb8HMWs4t0T)BVDc@geHr7Ux4$+4r?Z4Ax*ly2}5*WH-Ags+);8Q!`gU+X#T|LASAJoyG!SA^>^ z;lei+D7%|_dBQJgj{&$P3cDM_FDUFKE#H)B3_quIwlTa|VS8h^SYeU0%E5J8ce$U_ zdSCW@slLBJ4hhKtd%aTQPZl4MiS`=cYh_c8yPH*KmqeExsN#dkqeUf&1 z8c?6qad1(e_4MhLRn@00pDEko?T*;adH170I_8D6qcI>a)Nyc8pNrB_pDUGB<;B+3 z`M2K8itW6eIO&)d(vHS}yimu%MSafW#Vf1Ii!FZ{{jbl??%QeR9FgBojFaDoC#Saz zandn=q#fM>`J;}5i~6C*pI26uKU==Z*eCRze@YT>_IM%fXbi{;bsSt|Lmn?)Syf(a z`S-;O@6*J30sM~cKo;fXo=-p>n`zqslsND?<8^3G2f&eKLhej9S0ZLvB$SpR+VpC{x5v^dgQFN z+DpuD<^TUisD1Ba;a_^ve&7F+r~AStT^tdGsC(K|ft2O}&_}fC z#1?*UW47v4w5>(+u#5Sw;Nq6PJ>-usBz|E_-xB$?MZ{fuibKNhim&z|%q52~uC??X zVdiAk&e|r1Q}89)3qraI*F0er0wO>~Cy$HSLt$ zoGrV7XN>`pbpgI*+jpujooBwaTxs?;&THvwhmLF}742=TOlWU|{4d%3wkkHOy!F7G z2%SPR-(eD8Y~joHFtn$X92&k$ZRtf9z;=*+g!y$x_YlqPSDJkT$}}H;(!g;qoU$i0 z2Pa%5T~mImLh7!P9Y8NX51r2=JeBa&L430atYlz(I`M?|_H3T?=ku-hsq8EAm-wLj zkeh#g^FAx*dawOAX7%w$|q$^-w*DV7T?^&w1^v8Hm@oz%J%YQV)qb!k4>q6d|PX-to6~z z;Xz~qp0Y6{Hr={P!_C-mN0xgzlo@^W z>~HdC;&ap|4LQytCZk#BI>_31&vg1(s!fuKq$E7`u$o@bXLXqq&H_b+>EQ%b3^5A0H*X z&=<^ng>rK)jh>9ZI0O42zDxcReeG13ZRvYmVa97;GjY;!`$#*!YsuEMk2(&{w9lqZ z`@FKM?X%@yq;2cZK)=ixFJ@jRU$B@^Yog<6o7$*(<^|-%>!>1W{6M_^>mCxG>zDTq z3ETX1a%k8Fe9GZ#9vXhwFV74O=lkj2q2XzMI@u7OCcl#%8otlZZyy?-KA?gjX1UZ*4h0i7`A8?q%ku{NAI+fz#l>4pla#{Bz*tfrm_fLU22-uQLYs38me=r!hE9h^7 zfqMg;3=(O_d}u0s4O=nxTM6Hm3Y&l*0=)W(!}Vd{2k2v)&3)YUQ6C#J7{0hG*Wl>ThaH{I z7yneAO6Fk8?9=zof?Cfbs_ial? za~<;SexNO<%#6El@SY8OF*VCyOJ8ej+};7Kgp1Sd;DzUH@VtF=(AfiD+u&Q{xQUNW zcVa)CyV@-u-5#ItsJem+4tf8z!5f!s?ZFmJ`Zwuod+_@`vPt@B8*ATf$Rw!i+0ul6 zRvQfs$`ijCbGk`GN8i_*_5*9mfor-VH`egb9r57a<@3kJg!XLUTe1QFraU~=4NLR8 zH9@!(yU901?+HcYWOTRQ)$duB{-L>3u6Afn`9|ATbNAh)m!_{ZZ!cQ>rks21jviyw zwYqy=I1V1BGyW_MZq|5J(4BOb=DQ(NKFyZ%{g5elY2HUL-yVxL?ZK`;!g_zo%^Gx=IVjFmQAS z;S1o>rOOxYFT61CiM{v>p^OjG|wPN#g z)h84B{)_x)e=6S_*`MB5dGiaaP8)aPR@BPBb)QR{kpFfrv3{9$tG)79E9CPP-(0`= zaP8aaJ_Tn51>K!UO$;AoZ!p9Cg1Vu()@P6-GF};W z->k{yH)c9|m4_=Cz-(Chq%_ z(T)5&^OQa}2F3EX{^k<&rm+ovTNOGv`grKmMo;ceYAsv|o%=9e|IQz4-I#|iwrk9V z-)*!(c4Oiio5UY|MB{~{%esT{nYY9<{qtR3JT`gr0p!CPh<*>9ua?d6V!JSY_FrZV z_F(y1&f$@5f@j-LqIHY0w-?NOiu3$GY>YW$QTmMjd_Gy{C}ijXYELulNIO zc{cCm=)Hj=xSwS{ra6=TqB&DE7d7y|pVJqOo%S1B>mz=o_0kN+C203-`-+)wo~-$& z(#4gnZ6~b#Y-%a5@-YqtONY!0E}J}$x2`*Dm0vzG*BUIXo0yW%5p34>$y-J*K2-Lp z{*51(e=3}p?(CS6?z6vfKNJ1UG=28!#Ntm@fo;NvY)dAlRIPJh&>w%c3VxqH<+X6m zVXxe@aq=y#`Jd;_H#fuB8&_WH#mifJj`@ivmr?Vy=B-v%w{+CJVEb2E(@O&*ll)o0 zEf_$yXVfn5C);_zo;3ijQ)`#^!!;LJ>HP6)TZ3^?d;96@jQ;X8b>>{v+LMUt^wS%4 zYRTBt-MX?h71W?NuRpyq`=PuR?lQPjcY}xWLE&MH?`M0yJyBi@&w6~Vv5&=9ws!s0 z?^FBE<9A3R+o>q4Te|POAQjnPY(MfNbMT$m`(LzAN!;|a4Ea7UnceV8c`aVe+gCm= zXf)x+o4SHM6{??_wp8l}vus-`%KG&K=1PTG1K942+U5OhcRH}l$!hv5<{7oh`{B|$ z+;97g-N=#hT5>GIGkrIBC?6Ca=0yYAAmxL?Gj%t3C?5z<={tTqZ?)rz{5NZ!>x>h%q0{*u2Y=%Zh3!7uN~uOObj zX-oRz+jr!zB%ZrvOWX3bJJLbd4%SA@V{YBjwa1zrJ$0m)DSi2t{PjQC5$sQTiPG0^ zNk6t`N6@@+hki362(H?a9^J8{?d(lE^ji>26Ss8Tc<+v`yPw$67WC}UZ$YG9yK77O z51V(i)stSt{0bhm=IEIm@x%o6#q%f8H_6x9CI2|~_c-l!<16>yO}f_jy2-*8&br@&YNIx~|#QLvR7yna}-a}RBRigo1 zEgG+L*GMv|I@Wea@~JHA=VLYi*FAt=c%XK9KRN#x*bM{VT3@@oAFjL6S=QRjsvTpK}%S!Ki*sT~fc6oMS$=u4GSQH+=lK3Ln+<3S3Qk)!^3;5-)IX z3Gdy5#0y+4ydTk>Hz(^|tF{EM-L=E@ed+A`e535j^UvS3C3W?>9jWb)?Z|!M)g7s& zUCf)$+?u~5(ziM1tAI&cm<0Z;fIkbCCT>n^|Lb<_>5dxqU#wo%&;C~dR~*1*Z?0Y5 z&t`7`cGUp5uB%<%57#xo-d>|^E2@|EYulB;tr&ox_S)tB_*n+*l>^{fQoFn#u1meX zz3ZB?v17nKesdLDjqUErcuW$Gn*AIcwd~m0XwRmhkk9dD-uzY4~@Oj=K`2?;OpH=ml9VA}hYU0H% zjs7a;67hUvK{N)vrTNBL*!+STWAYi*%leJU^ME^}WJkVz7`>cYyS$(M%msGd0JyYg zQlq>dt_-ke)o9zS>Sg`fb{ufC2HLBe5xSDoW z%^%GjBwpa&65ct3#0%V8!kZZ+Uf|vm-Wh|$3*1}6JAIINfqP4MrwtM>aBm6k)Is6} zZg+ThF4?v(Bu*9{ebkF+Ip3*MeeE`q4gE=<> zcQ3eK;QZ*1oD1oDot}epI;VVsvn0;!f))7KnecqN+w4MIe$!XKBjjQO&%1e`XsLNx_S6&-=2a*Sm@jjIDbL8vIVw3Of#j7k&*#uYS z?eVLui?T7U%sVqsSr28}LpC_Q^AnW?y!XO^z=3!J5QpraypPgbL& z+oXTpgWw!p`-M%M1)6&cr*+lrtkJ%F>&7O2$fpg4`X5R3x57sEpjFZ5)`l$ms zufGPL>6bCb3(z}y{W@bdZ@r{De|#S}Sr^LoCUdUX3(p%^2l6|fvXRp#r-H@jj}PW= zWP`9NcWijThuiMw8`Lr9Jbb{4bTIkp0yt6?`}qO;`RqA%Rx7Kz*AJgxhpyn;PPwVI zXVG2WnbY1euFA5yrF+i{Hb>_;@woxGE_Jvz-`v`k{l155YzbFIS>4jH^MaG3^Bo`8 zx!{tV5}XOci&#XJx*| z5U(AoYy826#LWDLmYLP$mmiG$s9PpG(Xc+n&~Sdm`VJ#gXgitB9)!%qxA|TYxtMu9 zxO(x2PL^5p8C;gVA0Ij8J-WZ<6>dz@zlOCY?0C8o$bK(7;1;?dnR^9}0%_e1(mj?U{PMf>o#zB~b7!N= zZ>T>|?(oKZOn{zrYw7VLmJz-2!8;b|+jUEOB=HvJQ%ckBx%p;nwYh|^13bcUvFH%S z?IAAwj$T?gSSuPiPA%1s;hCQUzo%dd#OZ^v9F2ZROJ8Q(usk0uo@uY{tS@waQfoTrPwCS-dIcc0#-I^xaBn)!8zfA5?PoaEJF+u`$`uaH$g{FDp7)4_hwarPJR zFTT^I`*QLNMfx`P-rl?inGwGQAEJINU8kgL@g&Y_ku_J?*-{48)wmd+4?Yc?*v2PmqG%h$8jlr5PVB?#b4hzE9(NhYW za&q=$T!y;^+^Z{a_ek!wa38{b+<|aQf0pk8zen~BhnqHw@6TzRs)c)g5I#Cc+{?lJ z`3l^Vu#=j&PvbuB0JyQcosRFOWNyoX5^mkU?3<@Cv=-mz2H~oHxXbSz%3j?)%gXzh z0l#V0;FXu%hh&|)k@;BDC3E_k_)RzZZG1nn63--KQ|^AFy{lchW@9Wp`A7Ob2S3ux zSk0PD?bA5g7R-7YKi!9&#&e&A>RXKOFJlw$qMn(zUWGrg`QMUUrU5?{y<>m$(H8^x z8gOjWxC!nQa|08nT>R&!`rl{W8U4-#@2HzQl%fx=;x()v^c#B(0cCs#ApF7coDJyjjT5&9e_gtD zS2neYxkOnW_quqmT5}eCbMM8un`-hi^RSjCYl`HpmQ3bg^YFrW)0dNMucZE!_;&J-)*OO4)=l)|BBAbtDt|k_7k#7V zT;z?Quf*TnkRD1}aATM+x_hJ>XjAmI&69SHRr-go77KLCRokOtJF)3Bp>~{JK(-OI@+46U60*3+jq9z(@?tS>wMG8 z@ayi;nr|AxEuVHHyvSe5hSct_AT!r~{~qnmCH9*wXG`$y@T|Z$ykfsmyoz|P4*5$i z&^W#{2Qzy=2grV*drU<>`v+z3spg*#X6B!d-r;Yf&MWYydI|PGn7||Wslw+$y&Y}AJO;dT{aE;v+yeWlw1XSCcZ$wcYeOFpBntj*LDEs>>{=SbZ5u8 zzVTXLLU&w!6dnVAtVN+Uey!f7cnscDel{S^%$=NxTnLkmc-!=N0coGchgnp}#IBSGH^PL58^@k7BzpK$!9b@D4 z4Xsb*H-k4a*SUQ-$0smmpEChWPZHRFd}%AND;%8Pe$mf+{jajDsfG^xekk2a|67p1 ziKF}VtS^1t|BmrNdie`|Mlkv-*GZ{w%I^GqMERBl=+OBd<5$R=JSH%HC&)8rg{Jz{ z|2V(YmI%MCu?IN$D2+QCnHvT}a$D|)2Yn|j9-4yumOA?5Z#Aa@cAR_`q49wVp)tm@ z;Zf-CW!jMWq`Npzw4Zwba*$63EGJOzy<^54)s0Gy_O7nvV2--znFKJ!2{4y&&7o5*~B>TGA9 z#?srHW^4WNm~Uy(rEr-!5b*%NrFI>_yh*<84ece7Ke;EE zSz1qc{n^!>vBs_6o{^jl?Of}9*s65LMxn`TV*PTD z&3nw|J%!E$6WFm%a+l5mV1FqXc<4eNn(NF^x;qGe!T9z&`08gDsj205(KaMJ=iXfF z@v;l*-ABET@Mq=?q<=_!4g6s9Q&j!~(z0Kc-LY*Z;Z5z?h7aK_x+#CL{8!`mCd=O~ z!qXP{xSa#Qf5XB*jLoQ>N1zweFX2V&2iLxZud%m6d4e+;@$w7gp|UTjKI!jU*lIkP zyC~Gv_#;29-|RAenR-tTz}qJ*oZ8{$Rc0SlxZndmr>#VQ+AI8_xe6}=ZX-`MK<}Pd7B|p<2<_+X0U6f$FvCErV=O` zA38@Vkgsn9BmTxuxf2_R#{o09h~6?O8x!H78o%Q4*gF`b2?O>qzDoPCofqXJ7vdvN zvF&iY`#8~;nO9EcduWZ#jLT6thW80F_Kdqb`qtE)dHdsUVL_zteLVUfxAb$+FDyvU z)LiU0b{^z-aO0%=wsk+sP5*9>Y(oCZ|Bl+{S$61n+qoqAc2es|_N~o(Hmu**6d4Ov zYYvn}*4PB!e6QJr9TrGypP~buurJ*A9r!jrhWtOTjdT#tXUN-b^NvJ@k^MYm*WA_V z%BGWJf;G2k-$=Aci}szO4If2IUwNzPx9@X4j{JwJ(0|b8jj5ntCJ*Be_m0OS`3EN- z2Zt{rtigWwRbQB9T`~>-v_?6m!#jy{1*P9 zgScCxonmeLA!Mt1pQi7=DnCyAGswY=t>8584-)>2a?Z_|1MS6}(~O~Oj|Zn2=O|CG zmeM|~p$(mTz^lHpi9TcUfEi}x^eg4je_lnlZtm&ZtNO|)U`lpaK6jc;8*7z41fPZ{ zaNKWw+gS3d!5bff-{@ez?CLCn#}aJh<6gFFU$ow8bbRqpu`@venXs%SgHY@p9=kMg_{v=;-_dJhNTz#mWK8YRw z8M(P}NxlV~th0jg8c%k{?`3&@+07AY%P?^HYl{EHN2oujT}`eJkRDpu2gpmql)-LQ&wxBtW(~0XVyX!ly+_x(y z!+*h}8#AWWuq6j8d58b%nElE$}=_yUe%- zj?2N1pI=wk#rN`=M7TN|{dRT%AA5#;xXzBLb0f5aF#-1ng7GnK_Pn0OE+V_EGT!0W z%p9oVy&YffMu(pNi}l`Cq4(6Z@Bs8ayQKF{r*}6N_1lZ2eFTc?)FU$9| z!FN1f8=I`Y7W+K)935w0@fvtc|244V#hfR3Yx-!v4q0NeO%?s}DQv;lWA%Pn+qU+U zEukl8TQ^BY6}IKp57LLTtySpP*;WpJ@wqd+ zw!z@cy(6sqcCPhSi_@&#ICo|3vgnX#y@LNbyxf0L=K$)gv~{%JVxNvX6Wq7))>&!R zStb5z>;5PBA7A%pR?&SYc}_k~_g8(tqGR zmhM+U$LT(RhibZi5WnO57uUZu?pE9H{I;E2$UjcTZv6euRme(noV&mo_kA;0cYWXP zkEq{z>jB-HT+bXu^B=RP4(?&#aK5BN_AWk+ABo=9aeVrIKzq=d*SBasV8SZXjK{|XocjlFv}_fRDXr6$56s7qgPFs? zpUI~^f|0JU)fdlU>`>gntL$j(zceQ(&9U6rr}IdMQ#f?~f}S@AZPwm2qnjPw(=y{t}%p-(I5Q`}@+I!Opwv+{oY8E26)W%&NeF|L^n>LlYUxM$e{yl)( z4WUJ}KH8 z_s0*}&~3ogveikZU%B)Ca$B9|tu^M`@!?tcuZ648*iLm^2O3+fUbZhdP2Yvrlb|V{ z@BN-nr;6=L57>~?iQcM`p4fMYLVK=k&VK|8&ct}i2&I#FTn?s1<01axa%?*j92cHb zpKJY%>BEs9=8dV+`-=^%bq$Uv+=lOvEtouLE8NyV9`sEfXG89dD(8dE-h%a!<2W~G z{`z7bpC?_un=we?Qo_i8yRo388|U*h-``T^Ybw@Y#UOf97oG%rhQ_z{Xzle6`9I`W z>g%PtdEk$yAGf>8KZZNPm%sRO~}5J3E6il0H?V2NFMX_>dv;~ zF%jO(8^l&#N%^wgQeLloU3auL%1eI0YcK8{r>NhUAL*TC18C|6tLgSD@u{-=5xpR|Yc9{i{LYqSUC-h?jiFBhRFX9KpstP7(4 zvd-=mtjy%cgmv_nAIYvp({}7!wsL5V{$SR1z$ChYP;vP>g-*||-#9pzU+Mo3*Vs$@ zq}LK}<~-BzhTr=~CVxb@o%76ZTHd0)kLWy8G84a3kwX$0DXuv*NkQprCR*{(gCSr_yiDc@dJ?*Qt3(5r`SZ&_}~oU<%{`3=z?wCwc*@Zx0W zT51@d_t(lbkKsc!$vu`}Zz|3BLQCG^|>SNs2##uDt>w}0B*I^Ej;7idd0`yXxX z|Eq)p*}q$(N?xDXo$h$Y{ebbg)>oWgqs}N#cd>ou-%+kR>dmI!pS*g~-B_!;hiw~t z-8~QA)pWO0`Y+Sn0QQTV_LQzYTW+#8QPO?M-#S@rq+U|>=qs#WI=^CljO#OHeb?+a z?NxQZ-S5jj?!Q_a7H;jgsGYTOM|+t4$5M}PyEi1hpK`dFGkl)-z_`clo^KEL&pq6e z85exqep}sLTJfX(dC8G=+RnePF0WH2N5)I%16{sL`?%fwsi^so^g}!4TPx)^P~CcD z?&@j{QLR3EC{s!}BLCplQCg=JGx(YIXg+qNboj>X>oh*{9%=P=AIt)&Qd)y$FA6mF0+f$xu4lT^7nb#u@CL>Fm}A6z9RdW5%lpL zfw@o7t-dMyQ2U`*_`mFZ34B!5+5Vk?fCUu=H&Al}3dj}^6vH|Yc2HoF)~z)ONq|U5 z!X!}VwzMMu=XvirGw0sinG6X1 z`u}~+PrUb@_1)k1yyx6I!2d=Ao|uYt*#C3b&5KpP;~7h#>}lPfbYOqb;m0ZZH=(~i zpK++_ze@KfJ=h=gs7ld)Gy4BP&A*$z4D+wh{V!1cO9RmhQ}kbt{^M2ukJx{Rk{{56 z_aZ-kgZwCZJqUVHe%5>BCy4ir2C8vt_54L@{@OrvWQzG?n7@bW_j3h5;}4;Ksp@}a zAlg4g|K;d^jOyR0`WNW_vsM2Uf#^vo`bW^8xkJ+JA@paA%C*O3DCa%@x%ZO|+pc1( zY?Z^~-aKs=_vYn_H4^MdBhR()JKE4S#IC0~{ugUKi4W~VqD%53p*FO8XdA%0m@)qD z#qtS~!$T}9W8o~MGZxNoEmm=305-fYbSJfab^NcnUqZWfm$9xr7EpQ>9_bOuQ zCfaiJVN6Y0e1*Plx~R2$KGyPB!zb;s|J;i*nY#(O^SP1G1~qruvr20HOPGFD48 zl@Gm|vLpTq{t!I$^E;WWqk(_od*6`ZeO_ZEaw0gdp})^f`D&tE@$83^uO?$X8MM`R z0dU=ddYflLu2g42m}6mX1?!t8_(s}Lj=>xnJJ)v@y zT!&C5%sdfnq4TYNloM~-jf8Bicgg(b{Nmx!B|RgdN#JiX)>G%7^c;XqpMU&Jt%(U^ zVoXC`W$j}3jAqqGLW3zY@C}=wzdA-u^sxpdbWiu`hCV%&oay|k?C-!HAWtoOJlpu) zC-p3JVqPE5fh?TMpsuSr$VYG2{tjdlWe-!Xz6YBm_v{CLc2hQobHMk=7+AC8-g%c0 z-!d%7;X9seujb%8%yHCv=a4*e8W@=0B@A0ywSL$}>}*WZ68(9Wh4SM)Yi8Ohp1ES1t6;a&*&B1+ZpNZ9%smcrqiv(k zNlA{-hy@4_V*TC!!!-r(Vf#NsKl%hYw}G1YiTC|565SG|2FiDe3ExV@hp0e_lGr1E_>*+j&So&`3%ol8+o?_ zb1-km+@%Hm)B7Lt{t0MtBj{l6MQkN4*+;+YfqTf`Zt?YKzXbJy8_H(`Hz&`pbHe`T zrw;=?^jV(OkZaL9^ym97e6RjQ&{Xz0QT79oCkuNr&Y2FL`v^dOv`s2OJRmqg-d==^ zYuL%xA(%(EL(CxOL&jjdEZPjT=eZ2pabB}=Hlr3XnFIgN)S0&=Kgp*6asiAZ?@@Nn z%Nx+al^>=YOO80rc@DkSqYUu?GAMG1 zdhQ!erQY%z61k3UHbI`^ePBEA%zd;~=yN4#Dr++K!Sg>c2HWs`%D&X2LGWLYx32*c z`>@Sl&?bsH!gUVHX`_D$*c>_3XD^%ckLRJZ-rY)lITo^p7*^wV3(AVK9A19}xFohl zo9C#b^kcYRX0*Zk47*KPNZ5Ig4VjESx__?HH;$!s_hHz0t-FJKba!VgsgtJeo{N5_ z?mlR=)4H3TMt84D&@8>~b|HL#_dx6Ja_~;;r`LZzwlZ1g(uuZ8C!iatZAEk5mqT9m zMR%LUo%EZqkBloW13#&=njR+XVw<4nhk|AzYqWvT-6WoF*z}psFTe}gPsSYo03X?x za(yM};FhuG{dK=T8~wh9{F?nV4NafdQW@#1?Td*c?fM45t8wxhN7GO5iwd5=?L+YV zBJe`P+zBx6#IweCLNk1mHr=%Q=fR)o`OUfXc^%3t^+AtIdXS#{=SLT`ll{p|?QJG@ zv=X*c+tG252WoJC3h5mXiqJ4bVY+J+JBv9P%B_Y&PRS*$Y*^fa{Hzijg%vw@FS+$MVU!Oxx8(+URpL zjy-pc4LB|bZ$*ZH8^<088A|#arc!dNSu0pDmvPXa9T1&EFCJC`U&`;8{w- z(hhb*a032^;GS?%M|d8La&;VZ#(_o0KOR`r{Qq#-I=BBN4L^p=@+vu4=PELaTD z`|2#`StVSrhmT9F@$r0`(p?+6ZTcp!&;81XiL}p?eRgBr{1(O#`UCdgk&mF?WQ=dF zn;U3PFz>#uoAsR9r*n+%;EUj<@+#CDc}{`*O4u6{_gcrcKfEQui{`pRIan(4@6j3P zA@xe<4Bh}N8uy2YBXI93ya7y#m(r^`Ba(XrB4u>r|<>`z9V#I?N6Pi+QnT!ZsG(`dkxpevq7;F+?%Ovx7S)9M}I z+73K4o%~`}k)cxOd=~RoG2|DltXP;i-x$j818;@?sAGSYsq^ZRcT3`&JOk~*8U0oO zC!K#_?zC9m)6b2TsQ2_?PuAoYpBSAP#_xZE9*oH^0&Sc1LB7@7X9@beg+7{okAe>! zU{gq&fhq>&c;j)e#|y#^byRKsOql|0t~d^JcnzSv?w_+~Zy|Za zx+D*@y-~KGa;)}qEaXeR2OXvj)O-81*LGXS9) zegGS!&wdF{n-ov=`P`<%qFI<{JLMX*3?Qa#3Pf(kn8y1l2L0*p(JqK_#ooZa@>^bv zLwLSS=D<4wkm=8J5_5EwcLd%t=AeIIzkPjAKx7E;7N?<$-~bxyym6}Un53ea(7t^n zG{G~6ACKZ_H=Cen7rt_bACt{9yc z58$lb`U(79^cD7;;xlQBin%9+w&%h>X zjrm?+ew^RV)OG>iW<1mwD}d)ijAxFWv#50^8Go}HAK&nVEzA4oU2Yh1I#@^fwfh3? z#OA0uV|^e~z^OZM3K0K3!QH#ihI(Wf_7AefcUnV7D0)4heIE88y)pLUubppKXqvyL z#`FGmxQ?fEF6h7DXV^z#SMcXe^<0ct-i!~mUW*>0y`LkiPT@hW9zPu??Mexb!<%> z>E~cir_?7c9fOtR6E^9$}k7p zv5dZ$u<`j+&_>G9H!%*{aIcegQpfikn>kAGdUrR}vp>IY%l#+nSCYQX^8&Kx4S6FD zal(Q2?0*?-!g#`hafvtKEM+~~21yIF3lJ`)OIetgFrw})!Y8<+E&IiB)=T3AxZl$I z-U(V_9jC_;x?mkA{ngl-c5_HORiBJhwrJ7P5An4^6~aIoVm zB7+&LEYG)xM7tdy2?e3s#Di^P#25YaI$z=kJbQB|@JGA1LSLGgpH|<0Q z{(YHm5Fdi!AV}#*t^h#&@KIm#j1LD;iGHPG|*L5?W!rB<#v|m^m2mU-RQI z*qx+oJ8iXHrJUSD`+;kwYxK)8USd6key>3m$OrC^`q^L&r^bcwGdJd2jvSRPH~qQh zBl82~W>>2WeCA{3815JumK`70@mcUi_oYlXv|9Nhl@LEtR51OERo&_T$i@Zz5#p^GhzNcu=QFO^gioXi2Y67P3RHxxwJ`?JHT=y^^Rkx@0DnH znp|gmJ@1Bi_R@*_8-oC}qSpgXj`s}Onz#=z z;8@SR2jp-rcqOz`=XFQ}i!_)58nnW1h2-ORM=<8C#J%mrht471r7XucvhgpQ^W&K` znQqYJWz5ZY5`-VXH|l}qFawriO%wPoO_Spl|GYF2{uDX<>8Sa$3h->pidGUv+!L05 zdhS+%=;OeV^r%s^qn?p(e}>%9W)f$i2jJ|PiXL-Gk5<6DmEb)?=rQ%7PC^fqW1a^5 z`|x(sqm{rQ`8p*d3ov&(v|V=v##zO2(0*I1=nBw*&xF~rZJT}1VSNF6tZnU6pm|b{ zjkv(jA=yKLjVEsxlh=UbLBK3y0C&dn>%nLF?ic*W@xWW#-W~}$srUK~I(c;_HVSzL zjNOX1cNFps7>hpqQ2Gy)^IH_qd3-w(SMHyHMr?D6#46BF`L;%iy(HMLTIfCFryaLp zJUsJ$6a1ErrL=7XUJ`S9eT}Y{c&<3*xf7UB1Pp!Zsa_AG2U7RWJTfqP+hbS$t9_c|7M z2{hHQz&Oy}FBahbnT`bpkUuIGxDI{aR{d|rn6yzHAp6u0GZxshFtyLz{!@%+_{`0) zEnfSnedZR-Ep{I^K>N%;GaiPWd zXuu&p0{of@z6kAF@R@Ir*FHWo#QA-E=5HYXrq7)2fhA_Z;^#9LDgJqRZu-nx%^&46 zMIL>8=2E~;dgKBIlP^~oFbF;1Gx>g$FFiay^D3c7bDw#*(4)D}yan{|^O>8t4$Hu2 z-T+uB$6OPPz`eE?dyH>jHamCV@tJ#JYh(-`pE*|RJL)K}4xJpIdAdO-ug;|MnZscd z)A`IY;*Ir3OFr{r(3yUc=LxmXoDUjnd$-^A+rXFcoahvA*Xy(C8QSl^W@Nuxw#frP zW4%7>2ij6Cap4Zei`7Pcnn;gUaY}LOX?rEDIfIZt6o7{)E z8a6o}bo1(`w#mIPhv<=ElOMU&(ZdqdnKtrld?(rpuS%VDOap9ypG~H| znKpSW<}`IbV$A7hlea5ao8bq1DA&J3y|&4})i5cWd>DD@S;q`l<8|}kej&@vxE=iZ z31IiNK~orWCf6~g#NEdxe~K~vZ1OaWYue|| zgwe+)w+1Y<$$vn-$(Jq$4B947N<|NkP0kj2G`GnQ2|b$IEypN^NZ6(*5@;xCVXGZ-F!hg{QKLa0S;rHkGj>!eee)2mfG2~llL$~1j zJ*T6ba^(HK&j;WczwdJv@Jrg+=IcYkTLZX{6&xM-ymV*Ys|mSB>I~PrGi82fUykcR zw3qjMeCY>Xe6D>z)?e)3WSqYYVys8$uTjSw@{z(1y|-iJNfCRHo_A0`FlLe;s*RC< z)9g>~C{mW;b!Ii6i=6BK*jdXwS zGu!X$J_%v_$%=m`*pmE`-+}~ulo_7=F!yB#LuQ5U;G@p3vYq%H$OG#n*UJ9rOPPsd z&8>N^X$afp%2kktlD9x^RMXe1@2?s3+Ou$NGHLccbv0_Auw& zaQYygMZuxy&TRh-`Nw<>+CM401k6~EkYAJ~z$f`?+RX-zgF5o`74VxepU#aZeTIR% z`V9xJcWm%N=7G)TIVX;j!!roZT67V^e+-hTT0KEFd3!0&@3VMywh zrayIqIy2fhdneD}1z~#yvM2HjnqVy7J!$VZZhygeg|Jahk#DqozHxgWWJbmXeR*!o zCeGMbBMoWCwU6Mv)_c}n2HtsfSj(BnGi@ODH!+_7I~KGhINPLU(krW3fOiYV=muO< zwbOk~yP(gUip|0KO&h;O>1Xe0qmYBew;UXL@|qmahdliTESM(YD?Hx_RYxdXrP8sM1y`Juxr+NIt`5po8vKAu!poh0Xmb6@G z9upVZ5G}*5a>$`?Iq>k7Q&t!sddp>e@LHEA@HeZOZ2BJ^5>>J1KJZzX5G1W9ejvewOdsoDMnmlc8ofrFoCMFXUCfKX)YjpI%qp zh;rsp2umEXzTW4a$~G{a<~pi7WCSq(0<=)RHWV9`m3K6KJZQx- ziF7h`GR~FBabA{j#y`aM-MW0vjXI3i&?+nX1HzA7k0ZxFZyvuDo4S{@H)4+< z8~qg=j;vs8*bOb~rv1_F&j2q7w_p2~WC#8H z8AuQL@yXO01UncA5_ z7(>IC(0zSgNZFYiiSItznIwKbehj)iA3XBflS;EJ1AFoe*AdE|{GK}F#O^8WiH-41 zd(v6Sg`Yh+9dyz5Ng~tCI5Pv1N!! zvL(Jr9b2_EVk_62`w^e~%oCs7py-`0KA8geO}p{5diFb05TT4_7@zzWZIg6q*+v`+ zUT7P!6>_6(#9Wm7*@$Q1FWol6KgK8^%=@VOZhpJR-Kb@3l@%Qe82nY=f2)U5lO3d3i+G(?R9*kmcR2QF(KkM<~0^`53YM4`X%P) zcg5nkr`%T?^e4id*}88|LtSrE$f?`z2gi2vG(24%E_1L`b1n~u4#aa-beU{ zVTVka)a&=HKJ)9bah0`|em474hFAOqyuj7MZ_+k_?p%+6r-hBADaw;L`@~5GjQz-e zJX`Cn=ud@KdN0LIt5U(mf4`?WUEF=Ce;)$IBus|iG4x0BpnxMr`vsY&tQ`tG>GR%U z8Lm-7Z*<;OWE$^Bv40-s(YnA~xyX6!jOb{-(`w;2TKJw)ahvFp(;}f`#HZqWmB3qa zALx&De5i&r#C;EAteEL3ZW$aN(~G!9C?s&PpiS`{gqw7ln2K#CyCC zAU9#!8LgkXT~be-+&_N5c76Cv|%S@*G_fa`tGAqj^&9+f;jV$q+|Hh^Z$+IeVkYzwsb3~(oC zp?`}Kdnq&akrqcAwD=#zN5XeW3R={|7G**U`lDtz>h(VNEI^B!88c)+iywmi>EzCHLf=ranus!}u`jB<|qIOcHY*nRYl z{ed&>F~^dM4cWI7UhO$FEWpBYI zYWu2Xgz}+nMWXCat)5!{cGF*Ux%s`uuP?}Y=x>ZghFH7fgFvTTYxgRw?e1e*aL6Fx&obzQHmF`mhn?O@1c0Bgst*ws1yO&2ld?jKz zzxajzm*>o$##Q2%3&sE@`bX#c!wEdTw4RDRynlGme`q{ThwN)SUNP}#RhXH+G->L}bA(DEo*9Tk|3{<|sQhT#n=M#K~Ofx)=v=Pb zX3INJ(BChidlIuk_bkM$oj@-$X59!nm^RylCjgsIUE^7sq+TWQBOKR&e%j}W%|sb} z#Zt_}dht)Zs&gFtEmp3xSob^hm3RmDFB`tVi|a*b=cO0@M6>T^09NrE@QE5;6Q3-F z58r)+{d-8aNyIH?Yzo>v1D`0k!6w?y8g8?N-#Z2iy4|1@K=fXn>@U*J4Ii`{C`mMzr^_f#|yX$ zecnj&;#pk={%BsT(Pg&;eEm9oCV9bJ$!pN7RQz}!ey~M;0M?lkBB7@htU0)v{5XuT zgKis*u}prvY?LSH;pI8$fjRC}bF{F%xqPRCBDv943!={){{tZH(2N zM~@}Xhp4Tw9kmUNdH8Jgh;A*!ch86e`f$C-YnkdB%Wj<)%O0DTT?4&34*W*l zuT`60;YxnSx}r_jv3ZLF*7BUD3GVO5@mp^`b=*^kUjaD(*b3jCbNpxQ63GF*8>>b6 zCG=gurByBYob(OgU)DL1&~o~0jQuU_b5=Xp zZS>E5b74TE|JOnu9d9Q=tsS3vHjXvpM zTml$pD;R(6fpLR^@e*UK(Om!|=xYrQ&af zdkw~)K|TWCGGnZCaE&<^{$IiM1mU93L;umB-CYW<8OB(PkG2ZWJrM)rMh7MNYU%pjzS!B(h zsOHNu+N7Ir3FiBwn(r)QKJp#PDw4a7+L2&nDXwP=> zX`s&(l<|z*6vdBx#g9=a&sY2yrTBqo7eDYEN8Nek2kXd>VgCJuAH&#wjALUspXLYd z$q(weWt{I^{~mG;khMkwuXHr(c77!EQ$?fJxQahR|HYuu=iG-*d1d`Q*j;F zXnyrqz%Pk^fUkq}_u1f21$Xpk{QD$v z$Ng+$ER84as{A%qDfWxd?}|3iS>VICHL+)ey1%22`G6NNmb0e%mHM`d%1wFJ7k5J@ z1Q+C95^!62?ii|RWOT_;d|#PxBTi~dIYtcn5$p~7n)C>g9>-!o+Q1<(t~a*9xLLy@ zp-yVt#hM=IPkPKFJ#gRM7%K?Ae?UF~j)y4ExX#r$d=;3l^SHDXF~-WM|B&MVUdCf5 zD?Rpu`A}mX1@juqfq9KuYi-U4%v%BTPQc2s8DE+4SWvZs4BKmdGh5Sdr_nzhEDggWA;#w%bD#msV&HnBg5_7nScK&o(hqdixi#v*`%5tf z%HHs_U8HDzwxad57*phBmp|NVjs9uj9uW!Ms^D&Az#RuHpFtl9%l*b!g!?=NOCQ2w z!2f}Q{SHri`93h{lFjy`;B)zVii-X}(0Ht|=0`AgHe@G;EAx@0CF2RPEsz`7&c>fA z*{}@V*SuP2%$pA9C8MzZR5)*f9GY_Tl)`zAF&1$?0q}GsOu)Cqs7p7`tC(k`n&(kt zo(-Vw?P{L0jj=cn?aTRy2a>sBe|y3BO!$xdZ32d3u@_oLKF=>cEc$8?@~2?=Wj3G zQSj1UJeY>>d#+LAK*!U;+-*!G^xuG$b5HcZJXpc}u`w25rmUZfe_}5TSwBI^`X4;) zMb2S^(8wuT`aAtgE%KG21CZjw* zW{gFgO<7-U)TNte4Cc8~&GQA|*7Ixu?|!f5Sz(ODc_{0T7_x5RZ_mPKnyp_O;4392 z$C|a<)quGb-WNcu!S(7+@L}Cv#A7I9Jn^QI?d)e@7maV^tjBydV2K%RhkzgJUbGt5 zA(mdZE-P={Bk&1ooV9wK_07j&AKk78#(0u)t@=G|^gEStjw-)Tmv6V`&rz_LYqa$p zlWn>GCi=M^aTMp;>%5QAgm2aL#a!_hgN?;^+!2c05f95+5qT4qMU1j7m_yIKRL|XH z&9Cs8yNPYlKL$DojX(>=GYi#RO~`v&btfUd2(-5@%`QL=xwjsBr&`w+XIbqn4euk$ z#vAl3(=fbb%{S?Cp)OBBm-yfJiVkYLtwAe7_MlZRyltb*#Cw7s`@Ma`dqf6!KbRrj zJv0m+ylq{c25<2X`UIX_}8DkZL z?~L6)&Vqeg2^}i7&XqOKIwfzENhN!cc2?m)(go{D$lkk=I~xBIx#PF^x8*N9x5D*1 z>ze+Q^^`6%d9fY7LU2eSgS0I%^tY_ido63$y@W$#5pYo!%hcSE#fL3@4hCmH4_{u;Y(!!{edB0$rYh2BSfP*h)u6) z;i@}*_CvmtjW{Rm`u>qYo|6~qmc;pbu7wG79xCY_>WIADyW zWjGHqOc>TrPQqZdD>8Rx6MfBHp)v{)uL^&(TnhklIeQ!>$;*&JZb^9Tj4j%e2 zz86_eL7({F_7<+YE$Jgp(e2l|eMvELKYFfPh#TO%ny~_M3cCCl-4`%?L_4i;>8Q&A zgP%<=R_o#CK*LOI$CAoOXoaHtc?R7#fbO3%ZinqyWsIfi{$T*V53*nx@wj}S2{yzm zV|(pm_+4!3j`hu6(VI!AJ2eB@CjT}6T>5>R_?sEXW5b7Goq=;i5zgnM_pc&G5qfxd z`jReB!PCY6fXriTKi%vJ*f|$|r)GX%taT}ekuy@u;qe9@Y30zsi(|$HS?A_JRw=Io z?BbKA0!PV}7(6nKx`y|+-0Dfu+W=DHP0*{;(4Z$S)C znydNd=Q@Vpkgd8IN%c_Az6{Um#Z0{A9d*G5Tn{{;Ad**&ZCe*5xU1;CE*97$57l zo-wxT@)Tp_K+b*y92fUMj1IZC@taVwE38I|`&z(zTnfCg57Y?1Ja?Y5g|$~TvxUo5 zUTJwEuLK|TYJDU$9cvZhbPW2KKImcEXV}7GW2|Dx6XU4c4c*i0RWB_WpNaiRK?laW z+GkEM=1@M<7v7l{BcG<={rJixyjx(qf2`mgY>cJhtt7lk#v;@Sp)=?Xxe@su=IGKE z_!`MmlODVu0{;ZvrJmZ1`_NDEtUdNC8e`Lfd$3>8_#*wZieq#fxt{aDmN7=i36`9c zgLM>pjs zS>BQ5W`Ecd^q<1E!&N-ac5QTj)L(6k8yn{FX-cks51I>4lkvGPpI*HTzpnlToT?3w-vw=tV z-iC9^8n7_KZ1afnnzo-=oUHejLw5Gh1f1BlikHGi`5cXxXMc)zj zECcq%fcec7uqWO}!dYh8Thc#{bIe`fmv|;ympyfb;wRet5%4*`^@B71fU|+^!DG_K z;jKRB@9;L&98uVIbB;TVIX3ysQ34kB4(J`_D+u`tr{ItL zUY+_*+kw)JFRA0-5%!A|>=D4u@4IC|Uw^4&AgLdov+vnfUy{T27q5o&jezlyfguNdIS&{EN<6dZlJ%6zu!yvz-l->_Z zVLwpsItzg3INH*Fv9H(<##I@NBlcrRa5vxEq5a_diZHqL_5-^AgY8n73^ zv*;1j_kr)H-xAny_FZ7l#hil_?71mmN4*PnJjc;?Az^17&oST(eJ0}w?0u59?=P_R zluhBUg7`>fUQuRN!{1J|T~aDRglHr4gc2;&QWyG+LzrXKS=1@j2H&Wszx z&hmT$>}?L(ZNa+h1Hh%@2iN)UwwM#`wqULR&e`en-+Hce^mrA%!QGILm=tjjZO*nGLXN zwV9r^FwgfPFS?JmH?*7PTFzAtd(YTa+TcA3+xYgVX+yPcm&0B&heBO)#nyOtw@AGc zk1;;C@J@UVzfpj9;>Ylv_*h$cC;n65L>ZO1+Gh;d@D|>QcfY4H8t`P}JHmVyU*^Sc zio52u+ski?zoX_20N-HVxLCH@mz~J65M-nCk^F$ zyO3|WM_>96j?s#~$EOeJ*wm_(yz^RwIF{>ww#Qmk`mqmuY$MA6w{wO6OU68@;b#2a zBHZ}ixQ6@6OyNc!6YhcE1#ajHsa?iAfFVzk3M z5Q*`A7?ANKp9lFZ0N!SvNMsLZYee>(cZinf$NEPbrbI#mD0{%`Ug)NrAx8fI@_E;D zj|TTaW2|DpkN6{3Z(m3ImE-dOn=_w2w~M)XZub-JnI&M(p5KdS`!&pq+Q{#N zS!G>LbHTq6^X)w(FSb#^|DuBbd6d7X;D27hk7pPBc#flP7~y9f;eXn{pTPe#+V36b zz|Z+K{J1Cl30Og2e68sy!f6#B?}qmYj9&zJpHT2Vpx|AF@&^>Ws}#I=cEOA1IO>id zysRUl{RG}pw7){V-@y4a zytpU4hnRA*XWdbh6P{HPJH0;7vD5ZY#wN@2BZHzd508X4A=XRE(NT^ZRi5CMqawic zN9?5(VIJ5b{=Sa*`75(9X5?MDM;YTS#u_n{^FD`pM^&!z-bi1CvUs9RaQ8UkUy3r6 z)7Ir+j7-LKXyVKj|D$@(05B%#LU>JmU!?TJki7tS8_UPGk_>};oQQQf;0MYt~lUCUq|Qi z9as{$t7kX<$~JrPUxo9CCjKh}#5FI5@&x{+QigX#4E#}+z#rua{8hONf0V@&ZE!vu zaeM`Spt9c?84up$HMzo9^ChePtM~WR|5Bq zQYPQONyt0O61cB&?nT~JxeIrc#S?At`^v=qN|d48g*%?pjVZiGKemYj&IIlX)&%Yv zcF6vog&(T-Hj--yy{`GyA{Do{cfQ@E))Sl6I%YH1G2P_7@H|<^yoi{Mv77J)eYkEB zoTUtW!Id`5n-4;}J(9S2#O=J_(#p|&Z~h3en$tJs7)zRs(S)(M z4xfVc3&#$03^uM0UKIk&y`5*wk2cn#;+xrS18ft^!MnzQb@XYjHjxr(6TlvBvduAx zHr%Hq>`hmeJO#V^$y=_mlI`{#ud7nx<({K+0+xNn$&Gg!Slc@MVvJ21FbGq#y>*96 zZb5&SLgj6;E!X!#?<<9^>|YE3sTR$(lxjG zMjFFwDRs-W#v8H6F#at-Z#NIW`@#h=Fq=oJ_vQ>m9yvcnt~&<@xBFZMi}FL ztjEKivN@hBzk=Vvfxf25uQ)X1xApY;ifgMi3jrtS%KOgrx43pfZid$YaB|k)&TrAJ z>J;4lHvA{&kb3pqDDG>K?m3J**pK_%ys{j966N@A2lMYqo~WFHTAPamb4yW2qrnICnObG8BBoh$d-*$2O~Kpbfs`1~bt#4};CQvA-&o5)EJ*JimU zJ$iW861)r5%YycfBd{^m0$OZSYsCzk9>|O5n zT@HG&E%28yL{5k=<%DBUPJo}t37*G6=LBECosbiSgQmlF;3&95u8AAJGfq6k4#EG- zWSk9~q}#J@F6ijqcTUCxl+TTzr}o`mJJ=aGq^h%Gc-NRbqRiv>&rT%1ytf0pPeWK?81N0kKeB>tHV10TR;o)ZVU2UTLI%}E3hmF z@jP-_bp-0;5#7+Uys<$p-m62 z3xCCSUij=Jd~Ld}2(=mTSeWNao6+y&Bz!x+zBhFBoF~IgZ0Dt4uOxh%uDj)^5By;3 zn}luC-|h@u-RH^hFZ^H|kc4gg$xA|Q!jFYF`oT6h3EQmGz_X{rNJ!VkXTN%(rS2G7oTD15&ke3U83-u;lh@sC~=+O+YN@MrZtv?I^J+fyKWZRY$m z)TZ6q@D8@~(vEZiZ(l>Z9+Q`aHobLU_(Qhy${uANvbPknH~#C}Lz~WcIs8ku^YWH5 zrQloLeo1KOYcGUf_k)i-Q}E5Yeo5%+iLvm@e(;el8oteUhkA5=G5oY2e3W?wUz-Ih zL$m(zQurZ1_$X5fzD+OP5$ZAgzVJ#v_{cW}-==@ydjPZ6hHv$Qk2+^4IH{(D=RU!V?e|HPc<<3;4<@ zSDWt*_4uDP;Sp??LjDe+T%CMFX!X;Ng$J>nSN_Nkz_$bKt`1xiTK&?x@TqL)l|Q8) zgzxH!_k=dx_Dne24?fC^hVSb;LYo#o6h7JyK1Dym*QWBi(5w$$3AguykMgYGYjfjm zp`9N*9}f7zr}#_wHvQ(N(5z1PhyR9HtQq~NZwkKgd#?)hD115mDcgDJr{s_Dt?qa= zoxZs7*8${w>>i>8JFA@bzd5J2~{l@H>9+QC<{$tB0=$&HC`A z@N0hXDg71tt-L8T{;O5t7yaO)JZt#gzCLvIt!u+i`oTxKKtEm+{(>$`UkP`;)Q5iL z8{iuU`P=!Im7($F4~CCqJ1_kxSJ00gY*%+x=<4vZ;kIn&r61)U@ZAqO_h@@dXx7c^ z!h0_9p&#j@;G1>kve2wY9u0rVcHY>4d{gj^zxj^P)mxqnf5diP_$XHjzSTF~8``;N zUHFe|=Y@}Qui<-PS?KCDYr?^Wi)E;G7~50Fb%Xgk0PDFHMH_1(@~Vihr9IYK z&Q*=M$QToK0mQ_yQR+RaA!_e_h_fcy$oE!I$6Nx}Ph2B6pe~Lz;hPsb>psjQXOSMS z!!zpe{!inf%)<p+HDp3G~jurl{IB+tE}D+02ZBZX!b2ob*6wYH=ti+^XBqFO z2KlX2%1cMAyTR8i*py1HiP_fV8`gEPT;w-?=bRA_r4|#wt)eG}?e?=3BA} zAMWW7c?M=3)?vqz=g=vB_rY2eToK=9MJY41lQ}5ML0Pxfk$1trw^l)(8mc4Y-?A~m zKy=K2VBR+HaSY}e6B!z1d?{m5hPlSW7|b0V#`g!I({lk6$8W$r;rS=&g8SdYzqAJ& zlB)oG^oOk9hW1j1Ht(sjKcI|t{O9B?URU{Al{aPn7qs~j=U4ds{#`29w7k}tn>sZP zW68Lve*w0IWAU9ZUG}Uh+sOGPH==XX&n3QHlHkEctWh!MbmD^hT^Oq#bGc!B)1BLf zY{$ETK9FnVzUOaYj<73o3hL}a37#oy*7{-5C7@|AfU|S43A<6gCI@9>@H{4VTJ%fN zADt&!C$QO&oh=w=$6o5z2ANmq^0u_M8n^N8L#Signv04~han{cLT&pN>c@A%n$*LTrl zYMU*2G2{*7u#XLctj1N)_xqs7_xH3KS9cfsBj*@wr2UHzf^EtTHjW4HOZaYWADO>> zth>zLKHf9fI1GH1oGaP`w)V`WLr>MdftC?N4)<~Hg@|`j&3(c_p8E#uA*7mnPx#>C zJqh}lYVJQ|Ft^B1TW8O0G;|~9^9j3ig3g{a+bE!*R_F!nL9L(GNpopr)aKfML* z1F-Xu_uV)v7~KjRmxX88p|Z_E+cR_FfrC<|xy^@dJEs zLtq?$Z`5}OyqH^H!QA3+ktb~`LSEEcj{NGDBGH-NzV*n@rmJ_=VZRP@HL9}7zT**& z3sS&wfr7)Yo^Yh8chwP&j&iP`QI${jos+PflLD4=6fA!Age6VAtB$bP3Kpl&WZyXn zOF;@)3KT4U^@Jr&y{nF}Kt6;=PM^t~lRl_vn$_4y8J-qtw012_=B7iQ2TO#Axe9c13iHsP>H&e>;zZ?W2; zKWLN#*-YJE>iqk6K!5NwWnaMDY+mBt8gzt@BQCV74i4P6!#X19h5tCh)_ffH?X2P7 zPyCNl@b54FC!jz0x6k+^7m~mqI^ytPKkz?U!@r;S_fqihFaCYepYoSV_Wbxp{v+p> zz#qCNG6Y|+&-@>(;ondE&s6a5FaE>PpZc>8_#^(3-ww(&4m(%JVQbp{Ck~_RDBt&= zILwIQNEhY9RD9-&!(PyF8058uSZuRzEar|M5SwHai+!Fl7Nh@mbOvqqITrJW(H%Ra zhEZ@y#15%pOvVoBVN~+y6N~x7N!*ihZE83LpA6t6KAF%mbu8u&C-Kh&PQfPwIEhat za4I=I5V2ULHx^4J|I{C@IT(`&-E}->?|VGPc*qrx+21i9^Y(8c9&`C)Pv2DjIBh)U z83QqC+IS4~+i(1v$79RK1ika8!JqM1(jPbPn+pHb@mTXQ(&5i|jPy_P->1LCD*o|o z^LWgse;WK5k5T>{J!{@K75=H?vF2l>!=LdO>A%1DH;>1B=1+q^<1xzLKH#t8G5vk6 z#6BO+tjV67Pmb71o1aBUf8(w81&+Bbht2*F?Hli?TY! zCLs4b0lEK<)_jZmHM~DA`TMfmdkP|ll7*{}$1JP`LE zed~ixZif4IzV*eb9&2;Kr})Sy_PKViLtbwn=8j?SjX6L5z9j5pUH~~IKoS`h?xlhP1*O>vn zPeA+4{U_iM$31Dwb@?Os{-jM>V$EI;+=yctuH3tcVJvHU>!{8@@eH@hO)d9}hZ;EU zDB1UD-+=L3b$GQ4uQL~Ab+xd!*+AJ~K1+=W+Anj)Oyo8o*Zwh-j6H0B#!mR)HL2n* z8GFcnjP2+X#`TZYh%afl3c0>goWn0BzR2YX-rx=Mbh?c-z@ani-ZVHx?|;S20bgU9QpSdG_GmQkODdd+W+ zF-E;Jkl;ID#W#2xV^f@cc>ER=TYleRcxqIt>_7?70=>BG7CO%=IVekEX!c^?-g^e}`%_pe%Ii*jS z-hxl4N$nF(qaOXc`-CRE^9tR})GsVE{KCb+k2a$mS8XS>4X6Gaeqqf)@(XJYqF>m2 zko*G1-3Pw_{(M*8(5D685S^FaH~jhYq;I&6GM&yhD0qec-`PJT*D~?{HUD7Cnl@>h zB_?TZ9}lX3@WSw~_79%<_rX6r;_?rTa^}YL537#F8Iw+kAz&xveMqbgZbA+hGA3&U ziT8P5Y{_p!NqNMY5C2rSlDr*9?=Q(S}K8z8k;OwI7yc*`07_!=_{~pdwa4w$7;9Q5L zn#-^e>E`Rx!h9cpqhemfsaSU%NjU9AR##aslIGw~GkRccD6}f>7)9*58}hR|mWwqU z;O4t@v0Sx=Ls?=CH%P7FJaw{$LtSDGm#Q7ta4ocxH5}T=8V+!oYdDkUanL%JuhwXI zmu?;QSxM_W()t{SSKT|T?-Ij(9Q4TQu%QdDqZb7rngLmB#licWj_#P8u{vYWQvUO- zNkBuP8F}5H<_Kbr?)*)H12K=`<7BS6h{MTG;KlLf9(al@D%>B2UI@Nwz1j!w(XGQ? zb3ew6XoEg8DCe@S1g?oS3)UZu^|&6@`C7#BpXXpL!nQWn0kUqFdS{K;4fR~Bus(=- z=4Huy^qVZ>;JH~F@Z1`19hGQ3AAijijJ_ovKBREaT#>+2F&}v0SEaVSd>jVwYL@pI?0(sWs^G4MtJI5 zlnqaN;vWEQOr7Zl+QcCvIp87m;PXOJF z)6UtCZPt$R0Xa*yJFcL4n|1|=-6yBxlNW!{E3IST{r{G{{Ksxtb7{9+cI>~CdAD)U zC(>v*Z98Nz2D{xb&G6ZVALSW=q+QedF^(P4whunWW%pof)}&fLIrcG;BZEzX4TTOQ z)*?>cGGYH5yOm~L1K3^pt~55^6?1?-Y4cY6mLq7B1N#@ixz*%*C@(m%CeIY(TLuj{ z^o+t?0q8+D)M@TGUckKP48`W9nV*ulc3?30aH z6X((cc#neXVfju1^00)9-&TQ*XPeyt#FD(?U5MQ`T!i@2EbAHEJq>4fStc+4pV>w{Hx?6y!6MWfWS?f;Wra@ ze?9-;!SNRSO|yL1KFDB{uP;p4-V((x`Tv6rtTp6qfOQQ2!#eft#29{GgZS`0h8TQ2zimi6ZbdG{ zdc-OKPu5uW@aR+6$KhI$G?*|Oas+$;>yfgjqi{Qn?-*jNSWltJ4APABZ))p&lZf|= z|LXWv-Uq)2TA*)iiq**9m>|7kQzDI{NH44%Wqj1}9^d_%f7o91B|QMgzYXmGpU@6) zr=ne{LA$?0eq6W#f1N{Mu7mG@v<59m=Q!ST{4?9DHgV9FbZB}#NzV)$57Syl{NX2Wi2`z5ySFQ5nK z!?+gp3-`=R$v4R-Bx#;a9wCPzI)-{V+W>lgN*M7hJVN^<{oOn|U-QV-#(&KJRUQGC z1djl#=Fu_ak%>R(1U;Og&mr94qgTf(z3Q1#uZ(vJbD<-BK-XOG0x|jq{vK@~oLy7z zj*iB&$hO>J^3uBAr}pYq zU2`h``r5sE4gCI8d-!nsv>|xr82qbQP&cQ#YEb`yr{$J9_qj6{)Ros(%K-i7IIS$J z${snw9$@$AVOQD1N7#b~oaxk5RFziNmzC#+OKK|0E~>4r%B`rXD-TzdROZ%HUovn& zas;P+ZCy2fhSRc6sVs8}JpVssz-i|WP4z#t&8-PnmjcxKimF-Fc%D~Lih}Ag>`}_! zP<6G{RbOV6Rn*S4`&HVDW{1mb?4D!!SW#A9RaY^qqCDJlQOPB9?Vby3!T{esdC=wf zjbr%>tLxN?s`^WDUtcx1s``>DtDh?GcS%Lv96UI1%`2a$;5w^(USV}ziQvkn162F^ zx>-WG*)!)#$t9{BD3|q1{Lkhc$>97CZE|(HN{4W#DDvfAa>*sRwdLW9%fq=fB~|5> zxn(7FCAk+5?q4Hw_Z#eV?01H9{XW|E8$BC%54Fb>_M6Twtm!_duC9hxr&H^n%|fNz_AeVQLYdx=kBDzFXgJb=7mrtMJqfu%VTVtS(lfwVF@b;=bQ_8_O2U>Spu*(7Rf@ zAN1f{d-}A3Lc3pWf!(iPX?0Zy|sWwzaKqTS7umCA~lqd}x@HU_|d zr;Y3}P{t!2sdH-F{TeSZ7{+nPD2y(utEfo`l2cw*aj`0?sh?R{QHoy%FRv>?sRb6v z-=wOGODZeM?3$8r$-Ht{qT2paUXh|ax3sdPwl>#tT3OavkW~sszcGc%4 zuCFSYSy@idYs>B0>REL*g{iMyQ(0bul5lw?8z(C4lBzPhF1)}lnO%Yj1YC=fTpg~s ztfa1@x~gbiiBc=eXC4{N)xN*o&*7?Nl~Yi7Ir(wd5Jd07Gib3U`be=qAh%;!K{S?A~z+>2mvKK_3W{;`%edJO(i@oBu- zatQuS#J{ugkEV5;x*}$fd&Kb;eyd0Na~$?3FDM)&Dt!sBlrsLKpW&4bm*Xw09XhRV z?a*3fhQtmQh0EvHm)F)&)@xv&aj`1+Km65B!ym6=uZj{L5xQLmV^2MC+EuWX*0X9Y zmR2qr6hP#rLgL0%iG3Ntb_=^fkHiM?33jA!H#%P$CH*|7EcwX#>RMgtw4YZ}nk+!Q zGF4U3Pgi+G6@ag-6xb6LXkSrVR8e#B8DbPGYE%tvApd%lS6y5YuCAH~B#Y>K1!~J0 zZR){LduE+2%}Lip3PzRx<6`;8D2U8<|b>WiI@|h*2PLzOO6|WA@E2%3gzqGWx zhN`WaTMpMzUs6$7US@-fFbS%wO?t4K(f*=pXD&wy0KZ{>IHrnk9Fv4+saLw|%rO+h zDjVylG$QS5qAZMg01el~XL8q5INuRaDJ}RqLk-0UI$JaUCpNeYmvTX5;?a z_$BO|U0DLF-e1o*v%aFT4w9pv5l+;A5>~AuWcj7#@Dkt`OniTOjWO9=+TqK6Y+Dg~o-Ttz6zEG4w+Vg-7-+E1%_gDhbc79bQ&ZT33Xn&dB6?NXHH4co?g` zvRnobr>jS^OG~P%s_X2T5bAj~b>yNiJT3L7k%kSZwQFn2ODiB5wF~CWtgd9}a!ReX zB)B`JR%}K8ng#CiimI~mOZ(T%frC-1N~mU4*TWF0ze2+YyL~9go^;CE4O@vgMfI0p zefq@ddOSzZ5vySRfMVN`6GuLq6{3?!9~-)C3FH*|Sp>+7YD>cvHFe(SUZ>h_R7O`h zqHkB1BJ_lm*q6*HudJD+eoBhKjoKd6e(k@;XRKNqwc`b}b1rlelncC4Wi%qGn4gDet8u zGFdO~v5Iz)5v%HMC8z^yg2=DThW+8+k08IA)-0k;;-9h=G#kzm-zhfmAsixySQnS6 zQL+AETnq;bL6aV!s}nhkSf0gNiY43WdfakV%tQnv7fqDhI{j3Q^mMA#2o6BT_`Xdr zI8-u8P=$sE4{=Q{f(czzXVS&FEow#&?UPfi+Y}io)o@0Y)z0D&<1t3$U(yb!;GQ!$ zll7wAO_(OdXZR=i({+YvLEH})elZsYb#t_%Xf{3yYRk1NU{ulzzQ445IPw{^ZVF&1 zhcnO4>mJw@zhUQ1H*bIOxt%|4-1+pay_|;~%YV|>Dd63Xo0janbq)F}I(hp)7~gTv zZMwfar07rdHPIL{?Yi;#9XI_{O?mBYyW$V_8jw5iR9Vl_J{2*Lg*Q{}ZoGEqJy-te z`sa2=7Q-a$T6fEiyYBt$r8@`o-?8*3yPj$M({k| z_IC!^ao>}>p1I#aA9=5$QY@<^hGWsrwM##`o=03 z+~3=8LU|n%lwj6~5%%1(>lx{=K!Ih=sw|mZTV(gJ=T54sJ7e&8JW7?TJW*;{lWIqY z!zBweSco5BiR|Gv^D#yJOZwMVTqa{<-U8fWUgRrF;Q{OH-Zm?%XZ0!sQJdof1#$9~ zkhs#hI$wD)tQOYfLyOKTf@`ltL|w#ayr@L7fU?diotuvwR%u1u0(+!AKp{4%)=9l; zKxdUy))KJF@{3_VWs0KyE+7?fQ`z8DANVc_!>5EbTsf|R;A{X}UAbLYT{B!p7}@L8 zej_WYgjfV=ADdfs;1LtNUY6C`IV0?%qN@5zNt;<-rb|Dv+7X`wt8?a*)P}07>xvY8 z{7@%fs>&}JQ$K4K!ePtX4T9<6jEUB$1 z1s^Em;BEm{n^<;+>r3lw;wBD7RyPXX9Awz1QwPT}N29sw2Vb~cd;wtNUqx;I>E-2f z9r^EBg~f*R=Ps{HQ9dh0`47A43imh*b+KpO3limAOx0lRS4ZX!xOCuv@%SU0&GPp! zchzN}j*waM4z);gX4FVk(koHYyRThaS2lDgIaO45 zs(k^o=XRpn9$_2f=7;MNHN(t)XjWN`rDJ_nZN=;=1Sx<($2Ug*3FTGg;fm6+%3cj0 zrx5F#oUpGgASb37*~0{2e+C#jRHOx3P+l^x*QBbN`a0*nhsM@~8ZI58MKf0mqz2Rv zvkPSHt3h=W+BNZW0_u+1S^|cGJt}I6e z&C#J|@}HTS{jZV41N@b>&ySckAEM2jAsn{ABOA~Lk@<1 zIQ~5aL;Bh?;h{4NHvSKLl7q*W!fGe-^6+O`ece9Cf;xh!KY?S~n1NQ@{;mrcvO;@=FK1q(p1& zcW0d&AUE?8^5Y6s{oCw!oK}nUT}7$wRWq48ADrd-91Ir`F7IVZT93Z2g`K8}*~AU2 zn-vC+d?e4OXkUa;Lk<8ao(M{bU~g00{e~NZ;~R<43RojrD4v8j5U)6rcmKNEV+tygXBj5=&>;r+NWB6SqA$QUmb53DxWo6 zQy&!c*7cG!j;&iD&tMExfNX>0gH*mjw@P5*(-w86>g(l)@Y@{QEqrdZ%hV32jG>|R z(1HJ~2~9Hl3Kf}Ruuic;(tm!*o}}1FlU_zwQ##Un)r1;nAoPUqfase6sX`L;KtjEF za>ph2zC>gCCj}DwY)tkEKcuLU4RtbpG$PHBayVy{JJvcwh#eL6I-CQcpU%$1f7Ek- z5(Ec>AI@%)D{}gey>x4ouj*4n%Bpd27H3KIaeXs=Kb;5mdm$#k#>qjlTi;E^Gl(J# z#4_*)jv1YV{WWeZnN-Q3^LXq*Is>Taqvg4s_#k0^M3x+Eg&{F)}X$>=B zF^RN~kUD|-xcT)ZmET1noJih@6aIJAf`Vs=j$TIWBU!lPEgN!E%d2MB&4Ean7#<|S zO2Fz}#u)fyVB$Q&w?Cc7Rh9i;)S`VORXz_P957=43*;*U+3~|E15bgo80Fu=QS1xd z{Wkm4tsbjuoH*fvAAJ}5_U~cY92v=<<4Ol*z}18```6g93?Ry%1E;96ar@=BM&k{y znz~-ego~*^zFXzz$dN2_^{wD^483!Z$*k53*#mp_g8s?7zEYr>*-{)raOKc5NlS&x zXH{I9Dnnup?i-M_M23G&R^Lo#NDRriCi8Vonta7~;Q!E}a!fCy9E>Oa$V3+okRQ>( zuc-+KDN@-_tPZv(@y5RgMz0VIsbsI=r*y2QlVkrMs+Vg~qw#@hXr4mPT>sMSKN!UO z7pY{y!l$`(U@z$PJ@}j4W@)>^9ejEn2dDjWHLzx z_LgUHMbDWuPo#Dd>8Jx|>$T%WR`HC%MSce<(?#5fHYc&;K@2uEU}{eij{-79rMEqO z!5DrFrD$|PQ9)s7(%BP?Ppdfkj`sVW4**UF4I@SHeUg}c#K2ew8Pj;}(Sehp%)QHj z$kz~$gT|eVQMn!Bbrt^c1*KyAI~)J-4RF4QY<0pv{nr{dhvMJi_{VoFZot3H{+##s zRa^D;S!O-)sxCl75rOV0s;K`6#lgWG^EoZ_yR*4yt*mJmc`FJ?7Dd3 z5MCQ@=`s)3;EuZoG~l}Yq@TU?KCXj%d_A{Az7=U2vS8!rd@J^wrRNO3INy5X?Ah}+ zJd$sXh!$@@cT0ZohG#E5@rk3yPXBD{``f!r8GG}u&ua6|=aI30oxJ7stbWgqedeCu zF37+1a^#-daQ6<)>Yqbpa0wI#w|W7JNHjL*N@BjVCO%Y27Wc} z;~Qdm=Z!pd{K;$DUHjQjipC#Xb;z^Nu3a(y?w^nEwr%wr<6ABI&Bu3b{&xJ5vtAFE zj2Sdxo4w$j-AB!t(6!>6{I$p4JK@RrF};8FU++#haq`_?U9+U^#QT4^@rzSSM@;E0jafByX3@slEr>I z-`aV?3olF_S@rwM%4a^G{9ixseC;(`PM$LQo8vF~==k%eJTzkZ)`8QPPw8~l_VY)U z{cOrP#rJ%1*5$jW%v;;KD*n6Nsbjx@N~a#Z=F1Z=+%V{hh5nbE-68MR8!G&@#LKACpMx}{s6AD1)zirk(bo^spw zrx*S6^BJFgbN%$L_nfif=s&EVK67mAzu$TE*V7k%|B&Yb7xbPn@B15%+;VC0j5m4@ z{=xCL-7@2{(>FZw$CrLSqjJN!Z?!+h3cdbC|5tA8IyjW~_zUyG-6}$_pLgn^N6cLr zI`KD+&bx@+r?;$BvGjS7n(|_ZMyUmHl+DOZN@sJyPCvo!$LH z760oyo*(JnqI7-Fi@qPa7r$-QEIYAgk5ZjxCN{J0)8nGSwRN@hmh{M0oo#E@{!EW% zO=5e+h5qO<#H4iW=k;bi1K*UZHTHsM&(s%T?@DB??isS@)aSa*lX|}EYdJQiqr%PG zN^5bo0gc_xwA9KNc4(B_k-CAlkFRfZtEY@OdpF^>o0Wr;f61T4d!4M);Por_R=qxq z`L?O1(!bZvMdM2}a@f@?vBLIYM_)Yd)gr<$K7B*E-klRFeBLs@V{ePfX5XDA%;-JU zB*3OpnS;H<8}unK@NRnV8JTXars!4gv$1I-Rp`7PeL{*fJ=Cf}NS~F@;=8pEJ=v$3 z@qriax?lQq-1?-tGN``0S-Z>2BP<5E2i$Ww;xui!d!MeQUR8W}!F}S`4%d7yYW3~u z@}OYHY-QgOY0I5bn+@+9zpHP9s38%3$5q^K+cPk^Z>a%~3yz2=+;2xfM-xYdeZQOc zJQ{Q_;MXs))FJ=v1-JE^*7JGU?oJQ;?Ox&1&}u>H{(a)oCeHA6>Oban-|`znruMJB zugSZo<@WXe(7&+v!~4(szd3lZbJuqzFtrGIbe=f z(xjli<^#8B1+9J>Gk9RK)>Czr>T3rE8Nch_?Q_z=O)p;AY_%vb=*ottt-DTZGU(*2 z-QLR<`3|xey0(q~zQ{q@_AT9dq}~~{&a3WMD^ml{v!_1!wX$*a^xtRJ)v)zM&mO69 zLwc>)?P;xVo%;3Y6VFzQTHPMEv;1Jgv96u2-|jRx{al7YP}`YLvMKPX}@#Q(33@m?d}v6l^Sb5toOiMx5_{F8}^~e zrD6|_w-3AN7~wVFHFcPNkA#-Hh8YfDaoG7@uA-JnpA2%N;R|Ke!YQ{Ml~yCGP?DY=uwGf zM_gJFnl@^hdqQB`=?cDA8c#{;UeU$3RdA@|YvUka*X^gv)T(>P_oYg?cGTE+z9sK# zovgXF#^@e~4(5%Px{dbkwIu8CfrX==+?w=2X>odV%7O}IPi21{-R;A=wPUgyj7k2| zYJH*O1INtGP|ZC*a^;vE=09FfZhvV^(f$Er7j)7Ydue>1B(ulXW2d{0Q5;?4Gd9WZ zT=fH~>&Gg(PTcXt?dI6on|rP>dRNrXFyYJn(c>Ka%1`p^e)-xszwlL8Urlej!*7uK zl*JzXM}Bt4$4cS3JMr|c^+<33z4e4M5GYTV6i z=bckq8jlZY9^qD{o9p;Sof6(Y9XfaXkpYv2KRkJSe3>aB8|vTrG`@Feqxs{n)S2Kr z^JfEB-To75SsV>D9Jp*k^wjjztD7WDa1Vd3v~QO^;rl6rY6)*F{9`}4)@`zFsQ)Ij z?fZh4h4~Nd*WTAE;F`bo7x%=Ll?qLqVd*!m^kch;<`G*PITsrrt21Op4f5H zgd*pZDHDC8<_*b=C^;#lXM@0|h1*OTwbRyP-u%gv2K&?qu(sJVX=s2xlyEL1ry34e-cep=d>f=84?K^MZF!k=3 zB%>#;x27&$q)4k&uh_K1cV>+2(xm0Ix{GUEc$zSNnx$WqUDe4^(;^aH&-#@3Xxg~k zKLhH|E*(YZegjkvbM2p?9j56W9v3%W@hGQ z7G{=aN;4}nYcm@&TeHUIX6EMR7Uq`bN^>i7YjYcOTl2;iW)|ia78aHkN((CsYYQ6- zTZ_h)W|rob7M7NlN=qwCYfBqTTg%2uGo`uGLTRZ~Dy@{(N*krEvayw!mARFLm8F%^ z%F4>x%Erpps&Rp#>U3hrm?M= zt+}m*t);Ef*2>n}*2dP>wsB()u`#=E%%&T&T4NRk>lo=p+*tHu{PE@+rajtqczjfM zGvl9S=fASL+_(MQmRA}y9o&6;wD+2att&Ruy;AYng|lnUmCN?s(K#jVSmvGyYW26-fz039e=QrvYrx|y9+dZ^>hcdvDLNyw~DD~?XB>uWOM`@==C zPh-~|eYjaQ#V`0u|83u{tlhe3nXls^?=mHhe|xVt?|`}a;O;e}FGWq8oOJeai`502 z)~^?D;GTAU>DXbbO-!~Izo0eqZ$iME-~0;RN=(95YxdG9=nx0cs1t< zwGR~SoKXJ$#Pw;eF{jR~?Xz@tpV`g&u1Gl3%_?DE z#L6G;G4+-|=^wHB=(;u!YKAnvr#!WGll!_=8^^3U`{nYnm#*E8ryP9jQ2k@MPnp4+ z{N1mwy===7`esSdHj)z zq4o#fE!;8RG^pohgLhSv>Y6Wk`83jCZJ2)O`k!kJp4ZK2I`EEhov2w?woK~R!7;GG z{U}}gv}(r}sU7<@3LU!8AvPp5tej=?wn|T8mfounerLkN!y}Kcc1IV?4~tB zD_CoT)={n7S|7EFY1h_nsqL;kL3^ool=eC8N7|X%Wp&JToOOoiOxIbfb3i9a=e15j zU1Qy*y4`hsb?57D(mkPjSNF4S2}M1IUTyBbOy{1|3kG2Wej`A{Y<|Oh%Mx0FD@Yk0 zeJ-GNVNFxM{Kp@D_iWBD`pI3I^UoHVUysmkI#VL0{CbF3>N+LAif1o%iE^frsiD+1DwUF( z(viAF4Wu?xA1Fy)%2RbHe=3-oNrh8~s3gjpI!dKb)2QjxDrz5fnQBMfqsCKfsQpwt z)sy;88Bk`F3-z8VN>!%h7fKAHj!-wL0n|n+o2o$7r>0PIsHN0SDxH#mX*9Kr+C{yj zj42N)g8E67rTE=^nyl*@96=qVPEitTKBp|G9@G!23?%`M19h6}M?I#RQEjR7ls$EV z>O;MuOey(QKJvnNoVrVmqt;P}sT-6h6-B+H>QPgv8B`b*M?It_Q46WLR3NpMIzXMI z+^82+4N45yO}(OOQlqFnR07qCIzzcqnN(G(8a0U8L}gHgsKQhwssS~DT1`b$7pXSX z1*$c5g=$Ydpe9oDsoB&LYAf}GYEHGJ&Qe{dPgHTL6lG2IrruI@smas=Duh}@&7)>f zDryV$kt#-&qZ(5ksQXj^6-2G1Hc(lV5oJR8P%+dwsw?${(x&qM$TKAWE%#Z6sz}MP z%C==4xei{GB_+ox_d=JFd#0dLDLEe{CC5IUdPg}^^8D#ha_zrS!zekflhhRI6jhlL z7rs-6sM^$Usw0&~ZKlRhYpC;-+#7LlFx7~XYgB-$MwOr*QF7j;DDh|pCHKmT5*O}L z3#k>9T;qGxQA+Mb5_Or9>s^|1r>;>SD0`|6HIuTXhEVsZEXs^JP8Fo&85l_IqiRx* zsZ~^UDwvXI!+?@!d?e*Wou)QY2dFAkOR5RwP1T`XsUOrVsu^{ds!vU%icn>!wbVMQ zA9aE1L?u(=)l=#dHI~{!^`v%FZq!!Fh035_Q8y_+sx!5VdP#|+nBPU;0^O5LKy zQIn|GR9UJh^_l8LU8MR`(Nrk4jS>eUsA-fw^@SQm$uk{KwWHFh+f*y6D%F6Bq{dU? z$U!QRIzowGy{Vs+2epTqO*N&uQtK%nYCF}Ps!N4YE2+KI9BMH&fI3E{P?=O5wVb*_ zU7~EL7E~YVD%Fi@Oa)PPlmoSa8bP(C45@llIMtWxL4BmIQ#&X}swB0PxR^<8Oof>rpi;rs6kW+6-b?+zEZwaA!<;-gvlSh zN1ZX#9$G*9){gNPW}Kh$qgUJaJCir;4vidh;$YUYTGlPTCMq{Q{V@8rc^SpI8mG&z zD6pnbsldmtYwsObc&2H^N+WwmbZvTbU)?8-g1TMRdSjfqr_b<<#~QbHcDs`FZu`$x z9W7_I{;o64#cti!E*tF^Ee*5%9CzSuH3RjhYnSv(r}tm!ST26c)MlQ2D~$D+-Z`au zy~V+8-o_d}=vVY!szs9`?FyQ>pSm73aLMwtiO&;kMl86|X`4@vj8}sv?b18!U$ddn z=9sfD?)z2kaCH7W-{$*=IO%>VKESHyq1h)Zhd=tZs^qXc3sq~Y%z1gad!2_N>*p3b z{;Z{!!|DblqFs-a?OJ&F4KwRaw#V%^hEM-=>u11|#xvG7QqNH?Y~>jL;N?Zv$tsJ@ zr)o5N>%6>Z%O9sZ=`~zGq|P0az2p0AXg6a1)tY0zees;_wW@4#qnv)iSuyIrBZG40_cLOz-c}W!kn1``Z_5@O09;W#>cRMWi}GabJ)m5D#ni(SZq|A<%y3ZmYWh<)4H|SdC?Me5>IoCxE|@&|ORVfN-GfP~|%sh0pwRN@)9eR06{rWvBWM@Aog4_RW$BvF`#*a^Lec{5Q zuo*L|+Mho^cg~b4Kg#_0@$Pl6UVG2CZM(4O`}aT7ckXl;nw-2ZYr}^AuXgWleLOU@ z#@EQmZY#!&N$Yds#M)B_4~{d+%6hbG*|KKCYt?e@ZEfx2(4xguov&X%cTP;4>8@0I zWo+8iMfvpURlN@%zAqa+`tjJ?w|8_iHy_xdOqqK63WZ+RbLW;1u2G{+ztg9~>Xa{U zwtmG5n-T>I6xqLKO{2nv3Kh35Rmx;-U|^+Yj~_p*_4@UtVYO>_y0mxiv~}agy{c5W zaH;T_Gb{Uo{D7M`4_@B4Z_4Dl zb-Oly^5p*LMveNd3<@g!xm&l*URSRkLM1KJH*ZdQ7#rJH&CGm~uxHQ2H+}jXI5vEE za+`}67u-2^Y-oqZjmJD_-(HpM?7ZrQn_G1ID_3TDCnX)J_wL=9r`xx8F8lN6n=`Fi z&A!#K2A? zHe_$y*!P6Jec-%Biwf*qy0k@HSXebDTU%en=g%J=#>EXfdEmf=ad+?T9#pMbOEUul z&xGfF`yQ@ep@PNov16Y_czB#xHGO)8tDQTCPfJPJIHY=Y z$Eo$|x$aoJxarK`;G!4Wvi>MGWt?YqzvW!GiieCMHg4?(VUDPn}vf^7{3o@1mkwZ5}u0=&_*C=Nt1G()55BN)(xi-+yLPoLucv2l&~1?;pK=$dJU_PELyp=;{{K{_^E(sp7?rHVznYs=t-h@b5i)?n*jzXxNh3 zv&*@iJh{4d<;qPCg@@O+ee~!~>2Kd&?_0I1zGcah)sGAtcFp(Boo#a$E-bV~rLsJ* zc5Q>|RjSx6oinG5@ynOz+g-jqN8P>qc7OigW+pv+xM@*Hi0-%b>w5*xom;qEv0@eP z9zQo-b?9FS{ln4U5B(dWe*^U2i~euX|0nu?LH~~EKLGtNqyJ>|Z;t+>(SIfS ze@1^V@gMz7(cc68tD=7b`oBT{W9Z)o{qLZE2lRh{{>kY70{z>gzc>2VL;t7fUl#q( zp#LrO?~eW|^uL1s2IyZ9{imbVD`K>vB@zZ3oA(BBFD73lvE z{ZFF*IP@Qc{$}W35&es!{|NMVMt?)}w?h9$=>G`)H=uta`Zq@Zp6LG({b!(mTl9}Y z|L5pmAN`l3e+2rkLjSAiKMnndp#N0#-+}%!(fMq6Vd-M`d3E(@#xSb=e;WGtMgNiL{|@~(qyJX)k3@eP^q-IZ7U9sQT0zcKo^Lw_~;`=kFP^k0Pj z-_So0{mY^MUGz6c|Fh^Hi~gt4KN|ffpnpm9zli>s=zkOao1p(7^bbJ)3Fto={Xe3= zH~No4|1;=shW^^O`e&j4GW4&7{?_Q<0{y?De=-&nXH=@5i`Y%HNrRX1q{jke;xg!(0?HMFG2t1=%0rE6Vd-U`X`{j4f>Bj{{`rO1N}Rp|2FjZLH{1; zpMn0b(0?%cPeT7)=&y(Vhtc03{cECsL-aR7|IO$hgZ^jH{{{NrM}I%`uZsR1(Elj< z&qx1x=WC-m1v|1aoY9Q_BNzZLrTME^tRKO6l|qJL%d4@dt;=>HA< zSD}AN^dE-)chG+!`m4}?E&5kM|2gRY68$fue|Pk+gZ>ZEKLq{PqyJp=FNXfd(f=9x zw?uz0^mjo2)#%><{Y#*KH2S-u{}J>ri~fVqKLGtFp#Nm_|A_wH=sybm&!E2<`fH>A zQ1q{l{@Lh%3;lPX|9JGjfc`Vk|2+CnLH{4<-wXZQqW^pJ---Uo=)VE|ccXtO`bVPw z81z4Z{s+-N3;ma&e=YR4M*kM*{}ufc(O-%Fo6!F$`hP(G(dd60{ms$84Eih3{~Y?) zK>yR|UmpEepnn1MUxWUI(7zP=2crLD^nZ>1wb6es`j12Z!stH}{Y}xoBKlWC|B>k5 z8~r2DzbpDTMgN=VzYqQEqW=^0Z-o9q=-&nPIME_&x z-x&SdqrWryyP^LT^iM+ncj&(z{ePl=EA;P({+8%J3;kQ8|9AA)LH}v!?}Gkz=)VsA zx1xU+^xug7_UOL|{gr;OX#nU{-x1B z9sT>G|4Q_CME`Q=ACLZ9(0?lWH$#6<^zV!Q70`by`g@@NboB3x{we5R9sTQ}|6=qH zM*lYG{}%mY(ccjLAE19f^e>A3_s~BT{VmYH3Hld7|90qK5dBTi-yQu=q5pOCk3#=} z=)VO0m!p3g`cFjv=jfk+{x;}80{s`D{|)r-g#O#m-v|ABpnnGXze4}P=syYlccH%? z`X5GrfAp`3{teOJ2>myse+>GcMgJG*e;@t*(7!7BcR>H6=szF*=b^tZ`Zq`a{pddg z{hiQX7yZAWe{u95fc{qK-xK{0q5o|3KZ*X8(LWshAEEy@^k0SkCDDHv`rkqSh3KzB z|F!5}1^wrs|4a10jQ-uxzYh98ME?-qjTD`|6Bqbh^}g z=jj_)wpnoY`*rW<{TJyj4_#X3^`sHU@Am4tyur+4$=9p-xz!z(+RicI{KDaLPmS}R zxG=-Jx!dadqYu?S(D~7^ayx_H4~a59AJgRWl=AH#4w_+J@u`PpmzAxjbsLyE!E;a0 zF7G2o5!16(_4U5Hc{%x3$ZEZ4j?V+t!Eckh*0?@)$77uiV|p&C`lQ~D?n4UIdA(Qn z)c!_mCZCzTNzdl`$2oP(_HH^}59`)Y8PWaI28&1Y3XFUGC9-k)%%a=hdDs_RH@aUJgONwS&#TtF zS;6}&b`@xTzs=ES9rrJAT~eTL{OD211r_QtE*>U7S5;W~^61UPR`zozgx?u@wB?KF z;Q4b4+4iaM{NCGyiEWDntbbXeWx)9=LEq#v#LU@&g`M8iw}|pBS*6pM-abFo#j|?Y z&MHym#;#_y8#l1_yi=k`waxBcS6%z8ZPL)%WY5qd`X!$CTy|~6+nDerYb$j=b0Tnq zams*U9VYZHTd|Z;k;BU#Kdl#WqE%{jyUOJ%l`2}SaPghz=6^i2^+1D!ZB-0sRrt`; zxzM=XNA9nFo8>WWKu+xmMER`>qk|>z?b-D7^9~v%M+lU1t|ddUA4tPuYF- zS0D1-_QOe4vbyKXw(%jETPwDBA934sN{@GoEvujF)NanntG0KtH(Us@E}FdR%Jk2h z>pYmWsn)`ScO#GQY>^h$#I)E_f0wn1{f7^1Gu8D}=>=b2#NJ-q?`z|RpLd_0aV)U! zx`1U#!?rcJF=}6_npc{xj!M`vrM$t&L7^`uA8C58uz#&f8RdJ8)IPW-ee0UeGY5}c z{%q)20an+u^DD&>l+UJKaTaRj=TzK89n*q^vckhifH?Ps6o|$UX*%L!HSlPsN z^4ol2MrL%<;?y#?w^+7crTFmmZn{HpmrK(ceOXd&!Of22J4U~2<=Sz3;zPxXv?u$o zhF1z6?i^dy^yIWIgWD)x^$Y836{0=XZ_bZ~4n=?Vt~_*K*K7BdTJ|t_S-S1b5g`L^ zt(m01-e#fUw_bDYO22+vd)sBJ^8*gPd1~~?Zcp1uy&hf*Z87#^_lcJc%OAHnH}=(~ zuig4=&+2bDs@cQY_ZBS*fBTK6#+NqSfrXH=m zwef>}`g);hF}D|=?BO{e=;CDG0~R;@+o-8|YsZ{pX>782U$`zXke_L;uF;za9MxqJJ0k|Bn96(0>>Dw?Y5?=wAT+N1?wO z{Y}t+CHg0#{{-|uivGdqZ;Sr-(7!GEzeNA@;y?P&M1Lprk3#=W=>HS_d!T<6^skNn zp6Fi<{jZ{b6ZGGM{w2`=8v2K$e`oaHfd0eKzbyI}LI0=d-wORJqkmEK---T*&_4nF zXQ96{`X536Ec9Q8{&DDk2K|-jKOX&4(BBLF2ciE3^fy3%d-OL(|0(GI4gE`^{|NN& zfc~G*KOOy((BB9BSEK(A^skQo@#tR>{cofHJM=$?{wL8t8~v@({|froLH}ClABp~H z=wA%|*P{PG^go6EFVMdq`hQ0MW9T1%{@c)hANn^%|1Ic068$Hme_`~`K!0uY--`Z& z(SIuX>!JTM^uL4tm(f2N{cocGZuB=te>3zyf&Mn=zZw0b(Z3A(w@3f4=wBTD8=?OK z^zVrNuIT>|{hy$JCG?L)|7qy2K>xnzKNkHPqJMAn?~4AG=wBNBL(qQ``Y%NPx#<5E z{jJdd4f@-me=qcJf<G|2X=;LjOMKZ;1Z0(SJJnH%EU{^zV=UW6)n8{pX{<1Nx6f z{|D$FivEkye*pUXqQ4*dw?zLf=sy_!BhkMf`g@~)YxH+Re_izdf&L@Ve?0m-qyH`R zzli>h=)V#Dd!v6P^e>M7C(wT!`oBm2mFWK+{rjVTDEd!A|6b@n6aA~9|1k7VK>xYu zKN0<#qyK31?~MLC(LW0Po1lMt^fyO;OZ1ShI{Lpu|8?kZfd2E)zaaV-K>wrYzX1LFqJJ{_mqGuZ=>HP^TcLkA`nN>? z`RLyV{okU05%e#C{#C?(^q-CXZ_wWt{l}oc8vSk2{|5RuM*lnLzZv~q(Z3=34@Ljy z=)VH}m!SU{^fyNT4(MMI{STvmJ@ijS|8nSG4E@ic|5o(hhW-`MzYzN0M}H6WuZ8}f z(Z4DBPeuQ_=zkXdGtvJ8`lq1(EcCCA{w2}h1pS+#zXkfQNB>6XZ-)L|(fho6#W;Ye<$?6ivAnWzbN`oNB;-tzYzV8qkkCsFGc@E^lyXyrO`hY z{lB9BZuAdC|7Gak0R2m$|7!G~g8qZh{|Nf~qknnyKZyQo(0?TQKSTd3=>G!!*P_2Q z`WHt30QBFB{x#5Fh5kd(KMwsbpnnqj-$wsc=zkaeUC{pv`rkzVX!P%h{tEQpkN&~v zUlskkp#LlMw?cnE^mjo2%IJR${d=H)Tl61*{`%-|i2ioyUmN|;qyJO%--G@S(SI!Z zUq*i$^uL7u+tGg%`rkwUPv{?y{z~+>NB`64UlaY?q5pF9*GB(+=%0rEC(%C${STnO zJNoxT{|xkZK>u>+UmN{*pnn+p|3LpI=x>Jp^Z-oBN=-&qYucQAW^k0hpBhbGq`X57oKlD#U|MTd73jG(NzZ?1=LjOnTAB_IS z=zkggAEJLn^zVZH-O%3?{k_qDI{NFOzZ3emM*j!sKN92???YL z=x>Amb9S@gex{u9x^5c&^8|GVg40R5HdzZv}-qW>rKpNIZm(0?ZSd!YYl z^dE`-)zJSw`Zq`aXXx*W{_*Hv5dB@ye--*4MSpwrzk~iS(0?xaS3v&+^bbJ)mgq0v zLZF5If#_c!{Y#>MZ}cyY{ z`dgxZJM_0j{|o4!jQ*d|e-ip1ME{-W-vs^r(Z4_XPeuO)=zkmi8>9be^zV!QN$7tA z{cECs6#AD(|4{UAivG3GzZd$iLI2L^za0J5=pTpvWzhdQ`j0~Yb?6_B{*mb40{u6k z{|5B$g#I(oe=+)RL4O7Mr=$NR^k0Jh9nt?C`fo@773hBz{fDE!Df$ma|93m$_Z$a+zIZ){=Q%=0TZ>WX_RUS7s`i`6XwNyh7$Q z$qrjN^%967iCVCY(wT=$rbW43Yo2CUYFTa=6cBu zWS-;kx*}PG%xRK6$gC_gzs!GA#=E75i-9?J|pwGJ=}2}Xv!!GbGPg?(BKeKX!!n0UULd)NhRnz^qf6E!xrk&FGJi`xBe{X(C6dL+Tr4xRWGj+| zNPZyMmgEAGkx1qwnUdsFl08U{AQ^zn-!iXDt|EDrWI&Q1NERZQh~zJlB}wKWnT_N{ zlB3D|Eg6|)JCa9979=@@}2UqmnE{GCJ{JWTSLgq}FEoBap`C9S;nMGy(mbpRZ8kxmp#+128W=ENkWUiDsL*`wXonJ1(_FRPL^y#=3dDa@-hmUtz}-9*;M9w z$qi(lllfAz2$|C)dyrXKW`3FfBuA0CO!5qwv1BHdd0*ysnH$kxPjU#!5hO#9Tp(|L zlsQi3Tba)!gOGedvJ9ESC5w>xP4XF;*ClU|3`1sTnT;iTkhxiM44KE#zog99l6%M; zDD$q&!ZLqK&LVk@%zZK|%FHUWtYkWp-NMj}~*yoQTUL_fjQnTBKsl4nVFB-xhaFOr!_UMG2pWK@!cNJc0A%SIGG9wRAhW2<-!eDITqCoX%$PD4$?PaIlFXGdXUM!Ov$M=5GONi< zB=eojYcl`Ij3@J|%w961$vh&nqs;s=x652Evy04HGSACAC^M1FIWp_YOeHhFGnUMxGVjaWE_0*Io03CFjvyHV`v1(E zA7ze{`BvsL$si_%ow$s}ZMmmEa$8<~e?4wt+@audlXB)^bM zNAd>AFC;&ZEJS8o$#EpBkz7Es1IbZjR+TwivLTs!CHs&pN#914A(Dwm{vuhDWDb(q zNM0m4n#|vlkx8~Ad6Z;9l0!(YB6)*kV3L8z>@PFAqY%4{ifkj&ST56CPk^S8_mGS|o~CNrkYMKU|ej3jfV%o#H8%Iqw&iOgy; z6Ulrh^P0?mGULg-Dzlf&Xflt;>?kw8%RSFFAwc z6*8wub|CYc{i$c!a3sm%K_x69lp^QPnwk|RilAh|%^ z{3vsr%(pV1Nd_VLf@B#ohf5YA^PA)|GOtVCAQ^_t&N3TI_8@b!mB?H!bG6L914A(Dwm{vuhDWDb(q zNM0m4n#|vlkx8~Ad6Z;9l0!(YB6)*kV3L8z>@PFAnQXL zjO_y}S^M1PJNg^-iyL%yrPc7{eS9b1nI-b-kn=|y=u$elC4ZM?d4J^Z1t|Htf|9RG z<^7Sb%g>w0GU<~ZDNBde@*_0A{^b0$eOsq??K?Pk?9|z%OV@6$-Fx(O>(zU}z(Jma zhYa-^Hr(51#K=*;qsNT(8#jJ}|HMhZe^$SKBiWF4CEcc8{P2rDJ9}@Lr4HinN@bQh z3I@Pr7!NaH1~jQauT_@X4w^t$7!3p93#fv2*7O;(9NNKD+bnemT-+o}9S?)-*dA21 z$Wkk-@pEzZS!z#c+cHZX3Ikv~)NhrgPKTk;usZu|ouzh%ZZHT2wxJKkw#`y!Km(^N zwNVXz&Z!-J(4YhRh2LNdbaBp7r@%O*V zn*3~Cw=A_IT-B3)_zgxvyS`i}s2sp{`N1O>XbK|-ab8f*lj{Oqp-FA_KbYf#(L*>s z7~sWuz!NYXCJg7g)qy@)Y9F|D6xRg?`f^;b{Ai8~+K*v-{BV}BAIAgT#$~DFU^+~P zX5+bT{6JOE1daphP2@PB(`5Ds{bsTqm@3}!npa4zQsgXeKxFe-%eGUaFe z7Sabp7qdOMbt&6}#>?0qbb;wGZ8_Jg0bgI8rFMpKYuGM~U(0#G#8A!yCWrAHKvg)` ziyz`jgRanY9nTYVgOM-@Cc@y2^uxuQIB%X_<1O^Vtub6jXmEh@hfxPP4yYB!ahS2) zqZ|kHIK_71`}6d{fD2jbG~rdY#}9%D#r_rjX7Ta<f{9QC z(_kFTgtwuIE!Q79Ll-Bm2efU+c|k9j3`1ZhjD{wSd2XOHbZ<{T41_AU6-GhB4y*?q zVI~ZLCQUd`=nRvgA2fF6e#?J%=J=qe3-=iYL!+iVC(se5Lm!w4gP>^__5)Qg8Aic$ z_yQW*@f>x<4_F5}LLcY@L!k=Bz$j?ijq3=bpnfxs1DZfBSMC$M4Siq+R6&F8+*fE1 z)1VL3Z_e>SC3Nh;^8uq_2-NDy{elKC5t_j?=nnN;aNVF12Dx$H;8qv_zrjf8*o*a` z2h4y$P~V={ZK#AVdb1w1?Zf`yZ5RX7+}RJb@5}XY;QB*5=+=*Zm%YD-41WFbbLt<~~DrmN{~Ap$UwI&M*Ob!f!AH>W^T3XfTTPp$SZb0i#*J9oKUV_Z!xM&QJyYU?dEI zW@EX&&>kj2UzhZRRKcY2oIg}dVE?c@%!Ep4(g81_ zD~yE!@CA&7<^9<&RKg7C0*#!xuh0?3Lw9%^`azG0JV(%Y66--Hm;wEven*ZQDq$>i zhcBQXG?>hK&=kf%510spU>aNnGhq}o?8I{mP2tu6wgZi(aJ^s(jDw1)tOu1a6S_f@ z&b-b;XBY`RVImBHFJL4zoW_2kJeKV_^VHgOM;{8OICDFK2(y73%kkB<$01SdE zm-v;&%9ic1qgg!6|s$epVgD+q*G~CE>!xPZdgXd&3 z>p{g9&KD-ZNcr!rTqkG()1VL3AAp}w3AaLL=(>&dpl=lG!BsF3dhX=D!XT(Wkn0YW zFd4eTZ!iej?czCtaWDm@K*b=Ab2rZ)^n{Ku82Z3isDddl4t|4a&^DU$f=)g`qGR#=}gQ4o!};e;>9BO`!{PgevF*U!36lVfmBnA3DJ_=m9ff z05loFexWmrgMKg>hCts_JTFiMQ{XqK7|H!O&HkV*bc8O@9s0u{7y?6K5{!o?XE4S^oRPRxld3D6QMgyhkj81JjV@_F0dZ-yvTD7w?d;aEQdgCvb285l z^nl6GAEv`PH#n~eTwiDh-$OsBca#0WSQrlzVTybmW@LR(Pu$~pq33;$7b>2z9#p<$J!q27^_;}(+fVx8ZRif) zLqDjW$^M`*jDcP-1%^PyWL`hAI8GP^J)vDT`+)&44%(`@{xAdT2XMc&)M_P+gYGZ| z`azYpS{*81*HNqE;bNE$-4$xJ$rPS%Xa^&pD@=p|@I6#PtpaLw6f}m(Pzlqa6I4v) zx))TdO<@9bg~5ebA3Eu&)sgb`!fJIQw1XMY7aC3DdO}AS2i;*B^n?CII9`|t<6%%y z_6P0s)oQ~)wqK0ng)g8dv@fn!hrqb9YIO`WE2mbcKtGrPoy)7$`qOzGfhN$S0_(#B z=nB(d0Mz1VFe9NOOoSdV0|r2&85|FEghrLrY9HtbgWzo#1!Ii3J}?bt!o#|vGd^DM3l^n>v*6q*|Ib*NR1benT(l z4&57ZJTQRYCJ_TuVHz|xr+*I1p%ShV25~&l8R}VZTu=!EpeIzpU>F4zmYf$1gqbiC zns6NcO7_e4;-DY=21B8d755EB!W3xQg7sjW6W1}A{dVFy!uPIRN9f+4{lU}$YIPj+ z@Z`8*9Mqr7^@U1kJed7KEibMgbn~VkmiOWLfJ$gMkJl$!Qeabi%b;6|3x665Pq(&r=#+T`@L{6G45JJX8D>vJe> z9Ht1@btqQZVVO>+2lub%>{zy^qE66f2DKb=wntw(&2D8Gf8@C1s08}T%L4iwi{~s) z{y&z>cGGj0hd}zx!U~2LV7nfFZ`V+p`(Tu%w$14?c4oV>zA4L{S?-# zTA)b_c{bFX*D{KqYQLG(G%XUy?%eu3Iy2H_>RF*>b(cS>IrefHmeU4Q#9 zz3@V`{c-)}IwaHQL!T#qm+RFt_juA-9?SB~oaG*2`r$=cKj8OXa*o?jm-m(LxzGLY z=Y)qeR}R*fGS`QrS{0_%e#Nw|LzN+FPgrfKl}Rq-4{b& z`JY*8-#`0$t3EYH2~dB@ZC@AZuoIeoGY*U_MD?sZhM z+>iBhm*=hD=I{00>GP%U@8`vj<-sif+uM-b<#Oz@zgD^Xi)48a{r}}jV($9?=7|ZL z&Y&-V?K$S`C(pZo++U*ty#CXtd0qSc95^gf{C>TXV|Ao2mOg#iUhW^+k2}kwSng7g z|8OYo8&*8L7|)yT67AntO8SE6OV-X-yUDu0_tWinSI+YmMV~^4&#NUheGxigCgC+? zg8_W4l5O2Qug2b_X)}VUFnOf$mh_U^`o`J%7&MrZ_KZw%sHO``n)RfIW5Qgy!)Ut z@UOl|`jU;Z)q(h%@A~M3{neL9pIOyxwbq~8o06|h<820g2F858%sIZi{ks0$XHNKDkeE^cj@p-*I!!!?+LkNgPRLxev?Da;{f&Nr&Pm zN`|e{316whUC^0btg6F<`^9B(L`jV+tfM{2g?N%r0EN~*eXOljDerTDlCQ}tCAKs2FIxKV28KQXym&J<}wEy~@*zpzkcUM_2NVDDvJ%{3{ zdSM~j;q$dMYtGZwnV?x!z7nCQSx?3K@_E95*D1xEob}jnsv^ueyn|+~_BzLMR$_64 zW`Ap{%3H`9(1nuxS2{{*bc7nD}Z_UALzN~3XJ+3dupTYi4RR8t)B=7m>b&Zc$ zxu3)A=rbz9HKnf(`}<>G(d>)uhtJaNOQ)5b(C@vxKh6uG+z)yGkEy}uC^_E2n&Zv8 z1`f;gRE2c-M3kKGb6*Jii!;gk*VjQgt{AqPPG6jC*EHuk*z-)f@Jzad%_|ZKE*r z>+tLKj0gAE2LidS&h**U`E~riULW(nkV~H*eLnR0{Ixx|zqS`jUo?HWpF{KRQ-^$Q zn$OW3E9@heu9ck7gUckkT3 zb@N8@_2QcAqOaijs>gB3b+OZIC*S?j>G|v7;JD>H?6}Wy^?!Mg_xa}^9(dAso4(xF zsk}a(&R>^Lv%L`dOiX#dWdB^}ynX9*$=4|RjiJxE0o(hlFaHKj=}X~!RyE}NjO2A> z`4HLW3GJ}*;pH?>M_E-FotM9EwDjxqzGh^R^EpkiOVgis&2fXbhumkCR?YRYqu-f+ zdH+|;)AYaB4yzJgS+jj3RVCeyyfNlHNwR$(`aEp1)ZgTI&S?6Rw8JWfSJ3pASC!NG zk=L%d9-+m^Z<_x5=YDw|iY-E+xzdo;)GN?!o)|C;xf-+h0~ zE3&-+`h4j#{&RczKK18(9*LySi@rKJeFie8$a%*VKyB5WKy95#Ik!{Oqv=jnvl-2OmG}RkmRV{oc^+owY%g!7 z;(bt6Oox#t_oB%4Q0VcQ%`r=zD*Y-=f8I55kZYn7^Q&F6eI@-0zQ0dCA1Z=1{drD1 zEGwugz~{VQmt3xcrvdL>^snOYT)Vv9LHVB>IgSvvYvh#kexqos*>2uzK+gLGuUUB; zmg^QrzaigOm@4I4l@idtvQrZy#N1xAm#c5v0wRqNI8c5z4;#$>5HPzknfLl&3WC+yN+CQd5O%~ zrt~G#=TYw8{gdO&l+PUZ%V?T^s(Cl%&S z77h#74iC$D{6n=>YqfP$xi^xpMspJUk@XW9Gjwn zra#{^$h+_F$2#Zr&kz^r&zt{q^Rhp3JUNaaw(CxxN{-{{AbAe+e{R>|ZvEP_^vnCd zd|$47?os^E^#5@k-{k8zp+8NIgYVll%<(4gby%lmz84_r(=Vf?Wj`Y4esT=It_9cO z*BglJU&;Hj@yOi!epj>q#d))^!!mKr2ku9|Hc3ASJ9uu_ZOdFU4{Ik@}Hku zeDa;1<~ql7+#Y-%vJ3t6=G~M0ADc9N>GTED=k#Y^=X|H9=`$=#u0@|5JKM{`r{`sKXH8(+M4ScU5NW!C~Q*rM9s1I z>$>Y|4n~$Q&U-&K;eFhY@5k1B&am;6W6FD-Y$vDbMn+Ecov%|7dx+VD(nJ0ks$IzcLpYM~F?aMb8{N>BI-|d>$ zsWkc*FW~#9W&1ig{W@XB;Z-%~Tt&B1zU|BFs3GsyDNDGnTmZ#K&GE>m#=rt$F5#V7 zr{%Ivs*cN@S9H+5&zdqn@Sd7GiJQpzm*@RrHQ)cu-}9eK{-9m<6Tp5J)2Ai-dGv4l zIrM+-CyxENMErY>Dc38F{YalCPe{tS?>b?%!fR^I-$ducU)Rf!_fIpvuiaRe<9hth z{`kN89qBjToTW~X`=O<|zQ3MX?w`)czxw^?kKK~nU*Ny{Bk6b9##|}eFQVzs|9YcC z0G{`FXzsVX|NC#x?brSHeiN>bq9X4zJNW*5w$J^_b4Z6z!g;!Bwrg4n-*)DHPE6KZ zm-o71&BL2%uA`lNO#I_G-03%r=KJ#Hbv^gF%lA1{XMVnZ&HF$7#(Q~PFGqjw`#=#~ zC=wQ_9lkN={eOeDYQ47Z>jF7u2lC}q%?AMSJCXGx`8fjlKXOu()tq-y0X{zz2mPZ@KmNVOls@zX? z-}dYLKlb_kkA3p<2LH?X%FiQ6U+#R7cVGUPFXXzU(`R&t*Kz*NeaY)1*ZB3AYp%0l zWj>G4XPncg`FBqqpFieO*`6JJhWgp+tvP*p_m0fz_x9vId-A@kqEB)SMRCph)Crxi zI^nf7&txrCO`T4~s`6n(av0gJitWY}q7s(9j{z76Xtn0`u@Itb}V;gdG7O+cfNl-PqL0DeJc9oS&-}B zj@Kty9>DUgEU%NhTyhP$jw+VNvYf|N`t!E?kNhN#zNr7`lYOS~dKy8WJAYRk=>L!F zLO%KHx=6nv@2iRQzmWbFn*KlT`S^VOa=ec82N~pCN5wQvf8H_wy7$uOW6X?4pZq=F zdHwOcYR;cN`MIe7>WiaK_G{1P*j}E`|9G8E))O?&H2-3PoLAD=k*US z~U!CWiKIcF8`$xVf`%=;uK%eBQ zobMmcM!rZ@wxT(2Px`g^cSG?#U;lvouT!%B5c<66%f0V;=kt%}BZfZD|Di91J`eir zbM~9>o;&|~YnA(|sKNU>eFiyw2J-z5at!iwtMc!i|8>6{bJzFESwHW6K=W@2J#t-< z{rJ!q%l3F2>sHlsDNk&CRMMXDKEV@xqQBhG*VPR31l8S1)(QUey^7nc6 zdY;4C>+A*8wR?Zx*Xx{@&e^=rXFuy%&-%0ft-Y46$6;>yw8u%8-v8}}KEB-%FVMq4&h^NlN} zPx=<ksgp{`TdHL-`C`f8(V|DDd>jRpj$$^1Cs8$Z-_4kSGqZu;(83a zR`?fJpYVKSIo?!8<`B{lT}P7NG>^m(bW|6pp3!lX_Rj{O_w>sZm;GaPlrZT^ zpj&{hQ|Y8#CY;|zpbKJL6Taf}=Q`ezKQw`(qoL01i(lmkC^ zaNVaNAC@3)h3*8l?S~JLPjbAtlE#5Nn0B}8JR$Egw7ZK|9ydn*2ce5X$L-U3UQIj# z-Ugn9ZHhCES0;bkm^Uge{g7)sC+P;Do522sN+;#v8V};+AG#UnCfqt#|L5c%=L@aR zcpzl8QQ2VEFCeg7qJ@+;rHf2{8#OzG!9K>O1+r7z=hi@+&;JH_GB zc2e*FEY>m7K4IvJUXZwd3wR-TrP@F4njz(#(lJu}`p}T-bMgKUw(WB&;`17oq$r&U z*qeba1KaU&qWNT|z$1!FyFvfEeC<#Brx&0;y(n>inujI>ydz=%khtl@iRP(^dh8zt zkATCCc;6G^+`;>EdB*M&^?|ozf4x5$0q+La>&q1L3QlkTW>t<0;FHjoFuxw0;%je1 zI|L6aF7d&oYpfU@T^@9Wmtg!c&Jz${>E~%QNlZxgg3zVs#oDFVL%fk?KHgEhTcFE= zj<)0L70ug|3C{h$)N9xIGR0^F$N4nqg4nj#?Thyb_as)8v%BO%7cbac%1iDdd+aD^|;@-6m52#(&88k0d z`DLp9+HZ9968G3SEBQk6Dv^%ciF1F0;?f5m2B&T0SL#E;dC>%P)zGaJXG&)gx}Zlo z={x8c9cyReBl3@=;_7XHb)1fKenNK3!5gtZ%sYNw%k3@<-T}`3$L=T6yincXv@i1I z^sBVp1>WVs2f#Zuj?h|TINp=}d2HL4UQ4#(`qHjbd{iofkYTtoJy?K{r^^zx?Tf7az(-rsffV&7HXEjyJ?FYhk!eC)qk zd5=Js2i%an}h$&{aXFod-gPQ>VCkIs;ti$X{HAf6JZgmyjzT_bcw3HFw+rzp=e z(6uewi?zE&@J{gfxKJ6=@xKGyX-5~r+ZEao`B=0Y<(0p>zhUT#p<69}9nb}!)9Yy; zcsY1(LVn%-(gf?`>nY`N4!mO7PbUtP-zDU?3f#%>^;fT&-@F&&*jNy&@6rdj>M^Ak zgf0s@_ILUE2Rv8f6vsC3LhvqZBj#e)>059S8N0tZ09_w+0i~1r;ksTRdsEO2LWg1| zd(y5Gu9KFa8-;GQ`j+z&9PgIX6?+NkmP0oV-GoXv?jFifS3QqH7ugo8zmkuH>-Qe$ zTA`~@_Tu7#OYpdMGX`BhbSve9%6kFF{XXa-*tTC&8Xp_<9n0s!lpNX0MgyCLex-U# zI-19A3c4!VaXH=z=OZ-FTiSMa|3ZGXp+Jc{e*Li|-iD`NL$Ff7?8e{IXw)^3eQp@{i*!ajM7mA;&LCD|Q?sT?y~c?N-u>ol{csl+Ot4MPaWQ z+xB^my|_B!eojJmdti42dfFFpm9$;$jdWwMI|?1u6Z`vn;^P;)M^Cy1*qeur#<#lL z+47J@(q+C3^*Dg@YNeC>&^RO4d43Uei#uY^8^k}?^9HgPhAw9(=3&5gTt0Bx5HqLl ze?vD6U3|Ym`}dJQ%Usfll>wQheuw7qBb}cB&Zp@8XWF0U^_v6_DlYrSXUEaeynjp3 zwYYVz`({9weYt+rKu1}R? z?E3t+xWJP=yBN>A3s*kxp!nuNcLKUBY*T#Y{#6NcG~@X4b@h+5e+0TE?5~$^8+iKe zSbr$>EulXgfX)vc$5V6(?O_VK9O(QOKFjUKHGYTuSt>>!2OYQD*!2d@%NSTrN6I%L z9h$eX5V|>)j`)Mq?d5t+@uhhk7op?#nxr3hz1Kwk(7cb?FNwt=<2tUtec;959A8NX zedF@=G^H~ET{(2L9hYy{`4gqH2wfR;di_hkQl*1#Gk*VsatJ^d#Qxl`N_-Of)hg)P zm;H%7UyFiwfv;4K^c&4T+6`R<+xD|)#LLH)gyRC`YXWwsppVZN@j37b@c4R5e941T z>}bAB(g&~|7e`lpCLPVEIlZita&hS>A7LDy=b&?*uk3O15!dg#pRbT#U9cN@X`;Ua z9-RCbW8Q;p_#0;%L*U}h6BeMGgtBi&%>!$L4`ADViL<}-U3_02Js7{$Mf;Ay zPOD}o2Yd#+1-uyBaXuv6hqiGl)CC>n@w$X@5_!-KuEAaqx^d{TQyqJ)abvAhzRV>; z{dH!LApNHO<>zyzFiw|W*Iv_o`A0U%9ZOa#cL6|2y}jB&naKx=~vn5=p5!yl7hHdB zVx0QG8^Ht6An$Vi;5yDwxzYT>Ezo5voz$;{>mr(enC#I$aj|rrC(v)%xOEqSuEcH6 z)h|+fX&&l!=vuLD{}DHrmXAR^-s+ZEe@%Aj_&*K3pY-2x^x|vm9F5}9gY+k%E5tTz zX2?8DL*OUCD-@UGQ0!WVFr_mCT~_((`LuBhG!r^L4-|W@>o>BO2VM5EJ@GjqZZr=w zrMpr&Q2uD1=5*+au}%5ghx0w+UEl$5{XAv>JP%y&pT|77lYi!``C*siyxPN$Z16G< zKLX&z9)6U2aPlL}yk6x~;)@#&%je0I?+)l%VK-0dV&f3{z`MX#$_MQ;QG$919lhsg zzZw_C+Ivw~#Lk9PoR?rXa-BP#Tm3vjO4Tr&CeaDbNrxu(0tud?B7Mgbj8@FxJY}muZJ(- zq=}DLHhA+Yb}9W5=pxYh)&7!xtS=)>x(IZ%j)I@S^6@`G*9Bb_bacFn)49rt?2WKa zi?i#vKzW*jt{(REeq+hNGtsen>GKSn=2h4Au;Dmnc8QZbH4Hbp!m+ ztyHfl-?Xkkd2r==K{{G@Ah?Q-d~JcQ3cC3G(EeS_`FJPw)YYGpZUnjt?C-~RTsbE6 zr*qJit)k0>iS!$AJooS?hxuytsvNo@>`%VM`Qz%x=(i|zgV2SQ&RBnXpj&{Bj{{PE z3H4%(b@6ee{LFZ8N`G;UcC%lp(kENUPwYG*06q`DN)#8r66#eIboLD^*DJE!23`Iu z6YD__^OgD`%Fh^dh1h>39i_7XU9px9ar?$ZPUj(X{3m@golk}?r5MO{qnalSk8EV1 z3)!LdEZT2OJZ}$!(|SUriH~CocnkPy^}G+d##MA=djh(!mJabb@M>^BwkdsiF0uq( z4^DXnmvVI7kD&PGRHE-*-e20ItDQOdhmO+EQ1+bX8pOlkw60P-C%Y}+LGVJQ7rP11 zY5UkN>%`9l9j)6Eg*}R={gW3_p2TLX?;w8`VUP0dSLw#$k&fdd#hJMMKG*)Syi%+J z(9!;5&UJ>PbG2cb2ukV&<&)MMS*nbkH;YdR=gl3^)!&rRu5jLrTb6vzOa2c*N9#>- zzYx1lp!G|-z_XyCa;E1q*?@ z>*ZJiJ`FwPHLe~d^m`HLrl2cVI=QaGbAI>x8(q+ygs#eM&-HwP(i(v-@8;F}(K+bq zpsdTo9dPXuv309`+}?bH`gT5;_%@w#ydcE_P_g+8v{y58%ceCE6g=SNkm-={2xt{6J< z1@^>GSAR2aUL}kduY#@~x|P~3 zrPBso6?B~M*n7=A;8AdnQ|$Ti5O^zi2admS{-?yxFGTyyKsQ&7_mVVS{EJDXvun`q z4r09)O@|s6yRk)WIuFO!@mjnGxdHq9?jlNiFm4{9)Hz@5Fzv*-3U&+cK)!73ynM_} z-3Xl+ru5sOD?W;KP*PmFl;LHW)4vTM{j?_BZ`^|PTb%SqeM-%310$+^%)oB* zYZL1qt%DT>k8g+M2d#@me6{kTb+hUr^^9_vUB7c=v+OS?Fh{|A&6|@6aznU;MhY*(YD~YVm$8^u?5*tKDFTMBH=sAaujf zjVPV;&#v)KWUmFf@&{Je^+7iaUD$08=S|CVw359E=(5{Z)=_&~gsu`gzp@u=*Xc;0 z8hluB@%xUrYUtGK0CXpy8*}T9=(;NCI$j?;zn9~R>%2Vx8*Mla_dqw{L%n|D0*Y;` z%oBAb%~$$hI?bUKSK8eF!+!o7-TfftZwkB^oa=Gy{lNwB67bcI&zW^NFM%#c<O&c%Ngh@p^NY5h%YkNkDuv>(62$?rTmoRXTo(_0JHc)}{^thdD4lhpr& z_C)LFb*@22>+AVDaQ%dK64(Ev#J`YBaisP4O5XanXREOtr$cAqt~a#qVhePYkl8n! z7q5+t;Us&se%}CeEoqLu!PNKed}Igau1d{LiJ2rFt?y^OYvuRpDgIID%AqU4HnjtK z4~=*SIIW{O&79VqAl?T~>t_xyZ(u$GJ^_v|$4({lDS+&5Hz)sSJ;4Y#_X|?KuImWW z(Rzc`YtR)z7le-UA?-8aK3EvK#x>aMfG)fS-4Jw@&{e2>$6e#N$A!;8*Sd<1;%ncI z@#E0tDtoc>$!zdp@GiwAe+lFAOQ6en2F=&C**JKj3uC5X>~w|MX+@E&kJ9>&He(|VPI;9O3z^Kn|gavYp|qIN6SEwr8` zt)tqacwAmE?j!EJh}OHz{lw}zS`V{j4LVvcb8-zjT2IqLeORsCrys_#0rf7Z@*(vT z*G2MlP4!0s==`vU?!?va(E6N>;8VQ+G47AT;CZMQ8QgED;011qVGDRp56+`baqP%D zd#RsT?p7ULA9SOSB0aUg#FrLih&2eZHvyg1A1hDs-*x|ibc@i@db*3)jypcw8)wd` zr`b48PD1CWdRl%aX~jp(>y(1}w_H#2V5%JY(T}L}Pdneyi?6P8RZ7;${}K2CeK7&| zoVT$PYSQ&Vm;TY%{WfV=3HJjhpeu%s%h}jY7NN^TeJxSxI>#SMC;ce)2QN}w(n%cu z3|(-!9f;0#{X*@o3c3pD+LgWh>dk2P*p7ma65ohF@Ju1KIt9O|5B=VS-*X?wb&33b zaQS!glh$4B9>w|Mdi=5cOd|Dl+I8g&bZuWn`Dwb>YdX6Tb#VgYthIXBt<}Rk=th5t z>(3Ndx+#yuRUGm!2wnD*xUZ(^-mB?apo{)w^>Xclt{=LU%9YZkb!2;>E5kP8BIS}W z@5dqjbOTE2fz^*RT~(a2A6o|FoGi--PuZoRWU@z42v*^r}; zYX`UtTy8c_J7~KTY5Zd4{84=xfG!I<+K&G``>gNc92fFo<*X=4S`|&?8Bmpc1H@99t0%^bMYOpCsM5CZv`5KH zdmz;s^$q#(#|%4&0%=dN3hjv$tJsFIM%scy1Mt@u7(o%Gpqj#BX-)>t*0Vb{kUInG zvB;C%wI0^&w)N21SmR_UC1Ytl@|Ur&-a3(D&#brFQvF!#Y%aAu1-tcZP_Cur+J(rs zWAu=6v*#46+@46Ys%YQllG?)Z}_f z3Y$o3c0DCExWP%PcLODLVm*@T_hEwZ#dUV;25Xk{@bWw=mw??$q3v@*iwfB5b1I|H zo=K(Z)@q|l*e6o0@f5qurjYd8R&OfJwvKqFs|sEOcc_g#`*49TC&wQ5`3mhpUusEu zk1w^}Zuh04?x3elu?MN@U=w@a2mS1EA6Yp!0OS;LMQaMyWkl^mRT-edC7YM|{MGh& zN@^p@Gj-S(nNRVdrsHfm)t*YVPLPc4bt-!=&9%`Wi|lFCY1)@bCbtQ8?>O=rg-fbz?A~%}T5VtnlnoYI4ZL7;x zP5muCKTH9D++@!nflc=)r82>vK!+T^&7PquOQ9;$PdIw5`^4>euJx;5$IEEVd_oRQ-ME z9TELTiv9_X{@PT9G{;_qJ0chYonr%upd5_O>sb89;sn2@W*;o;BY@OY{!CF{n_iV5_*3+W^REqO+AT6RI4x$U8mghrC zIABTXt}57)OO+wtMw=Ss8nHh>0|(HB&9Armo%TJBI=ulc!$x=Bi;kFj#|)4;+aB0} zrNzmog>~s5-RtcM^q}kQkqub>Dih@7dQ}VZsqqJ>*`Xw0qT1lxx#*Rq&PCCVWTG*Tor^*k@C}_yJ?um#P6O<|bFGDxw61fl z4!D|W^{3ix=UPLl_To9#XsV3~h9}VTokOPzW9Q(&WDlKVEumRvQl=I%ksZ6|9BT^O zXIb;MJ#!W{B23raway+n%Nkf`51eI5RnOHyF?tmuE?l{NlOS6~Gvc{b4DQst13u#pgSy(IG?l>E( z9og9KI>jD1+v+{V?m63FF~}0$l?|<+L~d{WMQ&vdjjNKlnkQ1 zIXegUw;IP%ut2dQTk_DjdVNtG2HLrql;l#;7E-xK?b!{~8gOXs_fN$lKgbSRb77$ejQyL?MNG|4GWB|E!&1J0K> z*yCx|+y;9%&1z4h(}Vsrby9Mmqyts7GbM9q6Uwv%{i1ERZl(e}v55)|E3);iv%5B1 zA@pWbZsueZn153jd}H(L|yi?H3lVQw9MUf6`%PZ2m!L}uGlGW*i4Npb~s z#W%nyK@~KiCS?2iqvd$@%HpOs;T4aU>keFv1^ot zRy0U75F1^t4QptkSR{;+sdvYDw9L3?7tre(VX*7jS z_;4!)JtNB8XZNPkIWxLe^iC-9PMATvObI(K{6jlkO#M@kj=RltBGZZU2Rgr`Q%`i7 zCw<6Bg=2lq!kfur9>uSa&M+z{@7*{d!O1;NQJh}58|PdOL43w-I!PUNVgk)vijDZR zrc!*+Q%u|FceAiP&i(b4+$sz)e3vcwdN0Sh^Qf=$a&!mz)0bIY z>rZCjkKVKG_N~_N+4jlHtg*B0`OB=uvllkthouYc*2}RBt9@cC7S*+9w^|c^dweSv z&b5cPS||N>|5i-4Xm@YLREV>uVu!)YRf5Ng@m;;(n>i0%+DYHUd2}>E&xUG^K5b!r zE1|A5>IDbVY;=OdY1I9VY+Oji57V3Mk@Kt*n`|t`(T*ISad0=JhWh zhjAW%zAv)^@~Ysl&o|~v!)dY;M08U0;q*QB#BZzZyV~#1`qE04%O`%@$pyu^$gaS_ zJm$x;=HWHQ4nCYg?KxsM``UbI=zP1Wj&Sx={+=66S~==gWZBmaF+}Dzr~NMXVf4Z z;&3+NgbGzV=$vG(oSv#%r!?Db92Hnw>912udN#Fgp+mis%4zxb+y&P7`o6QLF0d9a zP%r~WYdmIh&e}&))PdOXvM`8I%1-CJ$d_FiCDd!5XoY|JF^S<5zlK(Zf5_5;a&AlVNj`+;OX@Lcr+mU@hAHFom`RW5wg(^ABl`JmzVQRXKM zzgrI}yIK6LtYaNw-pFO%s{JnMcQWrc{65Tl-0=G}^LfMXCz&%L0KHt(HhCY7-W|O8BH&DY0<}@js?oczVy{ z4cr{-xAC|HH(zJT^L;m8Z^^g-H{W2%_yRY_qQX25!Ob^XGXBENPl-LZbMsTJ(d*Q4 z%FWX)8Moo)o2+6U&*A2qt+{h#M8@(jsn!;&Vw)Gwurm9+_-U4m4_UTiAA(#xbN~^hT#AKhyFW_mFCxWzGIAHi9IUM5=XmYThw4yxjL3v|Y zVke-L`@b0MJOb{s63a#MHKoZHy?GfcdC_@@6G)No~v<*Prk;n{-aaw&5x;ae<2sJ=sPui z%%1|cKce>=c&C968u+wTVnZKEw@*t~czKm~etTZ!&9iRx=0yfxVc^{c-W>9_-*4cB)!zD`f%h8t zq=C;FczKO?y3GdOYv3aWK5gKEgWl<8*Lw4E18+3&E(4!3@Zzv{x?uzF&^Vnw(fLyA zsA|7IP}Me8Z%=A^skgax-sv_Pc#nY(8@PR&+y1EI*Cwk(>z}{D{#Slc`Tu*(|KSF= z9r3^Hc5far@J<7tH1Jsi&$z=o-DU&tH1G)nUo!CYhtvldd!ABlU1j(+giT@XR~C z(=9geuz}Y%dE4pG_(}GEoUfG zoWG*njQN>;+^zo=>#HY}{y#MRh^Ci(jo;Q0wliz7dC$CRJVnxEOPb?YS#B?exl@%wPPPWJmh zr|eH^BAo!+k~J3-CQNlh~yrb^vIjf%D~$UeAvKe4LtJ=-u{#sc(s8y8hDR^4;c82fuAt&#ddH1)86FGtKZ`0 z|Ht|)xi4=eAK`*-sbI3P~(U2;w{A|>ys*952^6P;ykSB`&mEE$Jys; zaTx0KPIunGmkd1X9o}{#2Hs=f69&Fy;92kVPB&oSod(`-;3EcJ@-N=$+Fjl}&%mn; zyw$*a41B`C7Y#h)L2o}x47}dJTMWF@!21lm<6YkVSnu}cqXs^2;91??c8UzV(!kpd ze9*ur4E&^l=RD-?XV}1d41CbQ#|(VNz)u=@`g^?n3>tW|fe#t@w1F=e_|(JR{+u-M z%pPxjiGfE9yxqVD47}^T-sz4S_`HE126x8xBYqpA2aY71GhiuZ9ib(0|q{A;JJNn`+1mN{Nvu`yYvaSUdpBLlioaH;GG8EYv3~mUO4ETZk2(z z8~CDu+n@5bpKag)1Ftvm9s>`4+B@BdfwvjBf5_WTnSs|E_@IGL8~B2OcYns)p8*43 zF!21xyzMj^c*hgo`XK{v{j5Q6;8O;^VBq%WyzOTjc)-BR4Loe%)`)kyxdvWl;GG6O zY~T|He$v4EKJV@4=%_cJH}Ih^8uSL9_9bt9g@HF4c!z-(e%af8@T=au#lXwH=B=+c z@E!vnHt-n(_m6w0+iT#X27c1O%f9YyKVslL20m)wGX|dh4exaG47|j^s|>u^z=sWd z+`wlI{G@?bf73f(9R}X7@jvkSK;`??`9P0$UOF=2ZGTGRy_?l}Mek>oo%NcX$!~ev z34Ys~=X}STmm7G!fp;2spMg&p_@aTgPkQ?~V&Ibo-uzu}J6#4oW#ID$Zhz0)evW~c z7kYikz`G56*uduu-2R2P|Ct7!W8hT=-eur} z20m%vCp2ELMV;q#zh9l_yhl6F8JctFYeNQ~7x8-p!`l6^vR}IOa=)d+z=sTc!obUZ zA5B}QQ{;+{h8u&>A&-#tG z{h)z28hF-k-FCXUzMbq<_48q^zIAJQsc*9ep1$C=FZw(KFE{W71JC%KxBX%R&;Px* zzQn+*3_Sf0-ga^fe9FKV47}%rxBWo_pE7X!8E-pz23~I9T?Rg6;939Soo>Lu!v@~2 z@zm2)e^~fIRX;nl{xfsYo$j|-Ki=o9@B5Qm|L)V3{mMs`{;it*vOl}^wPz@O>qnG6 zCq*@w?RY_zo`cMPSn)2+&ivopc6?_l{Vz5aIa=RledVXU{Y+c$*1zX0 z_Gd_Ou|Mh)4=^% z-uh|-&pyXnKW*Se=X&dV4SdwVo6qyMQk*e9pk_3%&ix zGVnYDUoh|izqkF!MczE`dER`%!2Qqn)&~u|-N1Vdd|cy)uwaAJ|L}OfpK!+^?WHQm z?ayCWKmMo0cO5$=D1o4*+^T$4ZjA5(mr*1ttw=+=v!2?Ni`b?Yz1 z0vlw1c0lo`wfy$xyY*jSee)+2e~+fmEO6_^pGpJI-{!5KG4PV@-ueLpA2;wh1J4b3 z+b=QjY6BlMaBGLR{agbdG4Sr4-gd?fd~lbye$v2)3JrP#U(ong%s)W$9?a#c<8>j| z+gq8-IySOii#l$eW>stRZz!HPFGn-%_~Pd4z%X6sqx74sYVG~982^qtU&31rJoia& zeeRFFd7*(@Kk?QNKjqEqf9l1vtYU4R5aoZCRp!aJSlQe0VnW>K2Z)>3yb#LHCaY6R zSKdPx&c$fS`^6^=?-vV~q@^Ed;dP62K4jnv2A);!j*r+cGw_Ik&l`C7I&b^4241+^ zt^bHqKeMa~E|+4?u(S)C^Hs(CtK2SRT_?R=)O)szW&0A(QD-C5>elqqUWB{b%N8r| z5qCZ5$3jah)suchJrS$8Oa)y-!vEdP9|EW2j;xEOx1&Z+f3{e`_jA5Dp0lvCQvL7N{Fi=GxL*IQ z3)MUW!c}~5GR2&ewFaGhonxgjmv$=ke}v;B`Y&;Qg_rRU63-%D7fz*TS>I*7tQV)p zr`a<;nO3jHr9TlaeyViPpM20A&)*v2Df!BJ1D7NFFLu(k`Myl^M~OZ|)Bnkd=N3!n zN}o92R4M}EH4hcJaD~<=IOeH?d$V{ z9ddZ$d{$A`%XxzM)8TNWtCgdQf8soE4?_Pu_#?;177749A7L)zr6&}&rbOSY&iBfh z|0nqAu+uS+;*e(Mndd9}GLKCj^A|Ih=jMLq2bs(Jq%iAzMw!d_`FZB=L z=1(z~=Y9FiH)6p&(#!j~Vt+R{ZfzxeDp@c0Y5lC2U@q^uh@JmpF5}n(tiJ?1Qo8cq zgv8+hbKRc@n9I23O13l4T;{`xa`U_BB4uC3(@MG_<}z+7n{TVlgL~!ojOQska{n30 zIiEIgFa0lBFY_BnoQt2Y>O(Y`;_Sc39pWpPm%mUsBIVTpPVtd>6>vH1d6Ayyr`>LFvkS6XH)3a~YTAXZ?fVRL`?st@0)1{sGp@{m))? z7_e@-SlO3xU#)ECA?7mw10E|lpG9!5IH$cp*^%dWqQ8c@jC<*4J7dh{eQ>D{yI!dL zFWzfANm`7{?a^xc-&%D~WSA>t)=U z`2Qp3GOiod()paTmE*wgr$m_F%Us?s7XN3M%XOC6S!6Eb`K6uuFIVHbJ~ZK^|rzZ4D1DVIwW7dv&#<^4y5*ZK4?mvJ}}=hPj_j?6o^behs%%v|oDOMV|? zF7ts%et*kc-aB66{;+bVvM(AhH9O>oj z+#=?wg-S2?8!MO}1t)#|E7fr)Lxst((#iQAW$%lH$x?R=j95|ysZzf{lsW6Wj#9EtN7 zxL3MAV!e#d_p=>80#5Z%=MBu|{TGSnH^9B@|AO`UaWLnl%D&92Im78*&s@fPlrw)T za~Urxe*P4k%DqQBU-}E{B{pyiO&Pf<$aJ6PWRuK%X1bqH|LY_a%D%} zcaZWr&Rm{5%k$;jVx^aH(b;V0jo{>e^_;4=7M~Y=jP)`;O7uT=^t^N2*9q3kd+t&X zFTO&hE93e7yw4$UukyN|^)e5Dq}#z<=Chh+J0ElG;Jp3M%KrtQ`r-Sm*N?Nmy~8`* zGp|&B%6sk-&%NMYepVUyXW5QCHC8%!l|r9Y5Ruh+~KUi2E7^7fl?tzGLA3&h}+~j1J!E zs#4`=@jbRf+LXK2tqza3thVn3C;w%>5os?^81xgY*Uu+kdX=&x^B&}IQg<_#`2?hX zKEYf+pFH(yWk<$)h(Cvz>*tN_%;i113{Ll(Vu#t{P#(6Lj#WS%b3 zZ(}aw5XM;lF6J_Bt%&(FbD4)k>d8sw^8T;buf0y?tGvv1EG)8}yTHjG8P71m?Ru8E zetdu4KBd>MH^a>3Jt?U_z076)hhk3myUb;NMe#rDdSzecw~Vm9g}Kbv?PvZ7_~};S zeD!^-m-ia7SpO|Y&ws>y%{u&4Yy3y5!)Q@qvi`<=s9pj8dbQKJ6)K+c+(+ue3mhJ2 zm^X{TDb6xpH;?&i*v`_MRk|{-*ZT~1KFfAw+-VEj`5AM0|2Knq*hjO{Cw}Z#kpGFF zv%$$vnU5u(C08?-_qoLWo0-cz7*an!XRtrZdYQjY`olB#t8``lr5;YVnYqk6UBU5x z4>(;XX7O{SN!GuY^)h~`llkA6%Xn-lugn7~UH$mJow>}@DCKn{^N|{LqAhkF0H=PV z^^d9?$GIK7-{I^;+}GDwKlK7-N80Hx*uIR{D&u`N1eKpME?wqTyPCPopCfE{pXp>`|L&R|1-?xJz1$&S5_+f@_wtt`NPa*9O)q2*?yDK>-Xi3F_-s! z#LqG2GVWrY?Oc7cvLo|Ni9h!-mwB-Ltp71{nU}Sh`KxYG^{V~1s$R+UT?;t1QyF(B z_53O3@_xGH_vKZ}PLPjls2-uJN|2XwbIMF2_^hvEjaZ9MNg=>NxgjloZ=?y2uR#M!d&KY6@E&M zveQ+e?BuI2>mr9+37-mZvM=)^N!+@b>(_C=VJ`2vOMTvb&^y0%;ACIM(MkD^Fqip1 z^Vzd?wU+gMJU5qdETX>}oZ@3$Z#&9%*5AZ@?k_5%v+B!w9XO>c-!5^x zCamC5}b3bz#mo9Pmy6{MQ}&y`q0%jOsAWCQT;5v|f7TsRdRec;&-zQj$)Blz zSB5*7U&VTvUsU>y$5~%|u{+(LuwKT|iT}Imz3a)f;8b3+{s_Y8eC}s1^Y)9Kj~ncK zoAolTzKrcW!(7Ijl{5D>c>8l6xU^-KI$ycqhJ-Gj_!9<4%-Ll<*-Ut9G52JY1#K5gJj4##=*CslmLRQ0m_5!Eha++F`hmGB;L z%CEc^BmRE^+$-HD9gh2-?@;#JIR3w8-g}$klCS>;C;R$+)I*28>&d;~Uix2wpN93k zdigm;8prJf>t#MDiQB7=C_D0Azm#J;xL5sr7wcu+hcdQ5!CXIX{))NG!-H%(pR130 z$1My_`I7fx;1-`S#Q6M%Z2r6 zce4Fgs|q6h!#;4YazA9?_c?Yr^KoC@Y+v3>8sy({n!N4r0jK!LIOKZPKVr~-ne~%A z4p#jC19KUtUCnl0aZK5l^%O+^2IlgfZx8GL3!LJd@nYK{Q_M4>9JlwYxJkJmVJ`0# z=CJ+?%w-;aN%!~6WxgFL-z$%M$L9ucijRKWe}uWri<`meJ_YXQ?f9?1uwK^Z5`WIW zOXWB4aaB*c)R(o};qeyKb~!ltBkLJSd#PeOOWJkT-K>}OL8{o~Ck*yK&vtC~C!h5{ zXFKvftn@P_cPoG7y+w)V$C%4}l2R^TVJ_>%w6Ogx&B~6vKO*sIV=nK-gjxSPaEUo1 z=j(5*m-)ua`Nuo&QTAnC7qS0d<}&|b5$nIfT;`<||2N#L?8vzH3f3QEF7H>0{a-Pc zdD^ACE`GHt_ZfcvCH4!zslS!=6K%F%?CANAxUU0j$Nr+KIPII%k9UJpJ<-S8^fK4) z8~9q3Ugouy*j@-O+i}TSTUjsj*yr<)QRXtgSr+pr!Kqyj$c<{Q=Ot0Q>{(}3w z+v7_OJP1ztm35}7*v=Eo^?LYy<}$yx_<6?t%8tBWFZp^oa~aoN$o8*eF7Nx7Fn>RD znXg0a|CYHvKI**JsC4E15sAZH%;kN2DT{}}DV{BNsz6qAy8X;M-lVwL`8@M*wc_%g z!UWsb>*uMh${+ncTRwAHrvQICp9XAD{AE3k3g+Kn-u-jcZ~K}52Auq_=j%tCEBZOF zRd!^a1UX*c$6VfH>S8-5nag{%gUs7rr|ihOInutq#9Y=P5`OUmN-y)oX0x6B%w>K~ zc^-6vxxAMbW_@LwI^UD`9X`SQVQ`9%th*%jYKghb4=?3=?(3Ca-e>W%oi~Cz{eyNs z_Ab`TdyAc{|1)!$hfwO_tKOjO%lnm*-*)D*j+?Zj1?I92R1Vv}<&ECQyW`-evnKxQ z%?AFM!%wxwuCX0?G4J#RaEhDm=WoF2zUZJferC~NKds%{pB!+qpL2_QsBFWPk2wz09vD@tk0;kLO%;?C{QUUmM<}($&vn>zT{^%w@dKTbawehy%>Gy;<4O zk4KL%*Pr)2#a!m)9%Vb1y+zrP`AdtLznZ!J-25@-vTlyV?bpop`^lR+lzsiU+{#?$ zlb3e!2zW-^y0q+$HNbjVFG=)&V&2E)*v~ucc&kcR)`-osqxJI!N$;oFp6*3I-Y ze;IR`&o;<>l)1dW8DM_;+m#(z&s4@47BZK4OT_-|%w;|tsaM0`PCuz#_l>h&)@^K6 z)ztbObD1Ar{8@(%f$F);gDUO15ZtSsUc-8MA5hBoQRaGlzQSBT&i;(K%%>6N_&oLw z@AHbUf|H*zpL~?{XS~zf&pe0Y`fgYSpn~=Lz{!rhZ=T8g9_BKCpy>OV%X^EtthfI~ z*^znJB%XIOmvthfyk@~q<9*}5PVZ9XSoSCGPkidvo#0OY^BvXyNV-Rv>-UA;$#xd* zQ+CQZk!P7#eL!(JPF?VzvM=ih2Ux!c{PcJSdHW#iW&XV-<}=J?Ju+!8zIQ46vi@&A z>z@zqW&fqDmv!Q#J@z_!yhq8`eHk2|$5^kQXD%_<%lC!v_D**@PcoPH84KC|(`;v)+hZB?^oLct`gog{GoSyWinE{fuLUQ6%5Qha=UuFqdEvzW zC2%PtHsO4o+N13Be@5Anc2U6mWR6N#{Hy?X+T%lt7mGj4WnRQg=09`n@E>tse`LMP zgDU0w{P!yRGH>%R$GM8R%nKy_9Uc6UFxSNnPg>t%l9V)p492K}6YpJY2S zFEp~_eC~Lk@<--vmN-1YT-GIQW&MkKRs8kmafRSc|GZ7bIh*x2F_(FjCzwA4PW54o z#{qUTKgoLix<2Rq%Dz5c=n!+6cWaE}|AG%Fy{ro$_39XNnRi<9`)|x;o-3()z7Hxp zvR*(w`+1zXK5lJOpURhghpL|y?9YYZ6dzf)PU^|c%w?W2Kii*VF7qC=q^TdS`;f9D z^R5@M{?p9$=Vj|2ReJsW^AhH=&QS^5c?EM>7hdAf1Wx4@=J6gjQvtRkIj~}}ToXSNXKY2NG zeO$q7naljrQlHa4qU^{z?`@p!rOfsF39kXCc$UnldeXT;?eHOo#|4ZxzsPoE9$=}@ zIUiN&$~qJktp6Z$S(jPr`45@v$M=)qbp4gV<5JSt&R@Zu+79h z{S0%N2V2s;Y{0v`t^=p|%Q|9W{}%>*%Ey&nA1{3#bD4*voZH=pn9F?EQr|w!T-NtY zXZ;+wSG}@u;ZJ_b`b1)8*{zC3jC|8&+YPje4|SD z064`>=5@oLt8hPljNoyp~U z{gCqWL>0F?)*l2XKPP$qMnCf(FqicagrENzrI+*N^+Z zV=n6|1~}dF$CRA_KL?C3uLgJG`LN2br27u$vhJ$H?bFO) zakH{t#k_b}>17_^eCBsBm-$|1nE#5otRE`n^@=CdeSi$@{qdW?DPQ{i(jn&h{l+P9 zigPxXqa43}#d=x)QQ}$lS!G|=r<1rvnajFene6}jn9F>#(tdx&T-K47IAnfK+1JN4 z9cM1{D3-DPZ!(v49Hjndk9fx?7o6e~IH<~Hp6$E?oZ>KkSk<>7^|Lj=dYM;E^7RYm zvVK!G+u8hiWnb1cko?}nT-MpiV0|BRS*J#OsIsHKPkKAJSAX8ZdVRd{mzm4@nBwP4zo_i% z?{AJVpW^q|q#yXymy})~=ll!ivW}9}hkajGdi_1V`J-P8&i6jhg0g|+nLKc z?-HLSaIZL={uO0M=BE{XE_0dhwBJ&nR^~D+F+|T+en9Djl zlI~lndG+yurJ{GguSDO5y=CV$i#QE#Y z_2bu{*nYKkK9lw>WnUjJu?yTQZufzEmDhu8M?W9?1ap}Wxr+1k-v&DyzODR`dFG|Q zWrNf8h0HU?x0$RE>t$Z(Fx%hx9c4#9?$j`s`Sis8{o18_S40G zaO!8;`8kf%tJkx>oaZy|W&L-V%X+m^UKh+zzRt1qd6O;y_o_elfKz@W+V$Gcnd@=B z>M5ny??b*8ocy<*Rqbf(Y~|<&Sug8G<+1-iW-jxNW;4(Ksj}ne_w__y0#1I)x@J-? zA7S2otBU6=+j+{d!+*qmJtKDBrt}i$^M2+XpL}piSJqW2VOwp?$M}A=@CTX8x`8sk z(0Q}Ujy`_-QgE+wxr+5NZ}Tvx`%&ilydqy@KHQ-4)yewPey-A$^$sL%w}5-4JLGU2 z@3e7)Q*7r1KmW2-F}0-1`7h;u{)np2 z`t^P(>-Fp9N14k!z?E#@hGSHYR=Kh-`~~1tZ#%xNG9=fPSF>K$kt}9At;}T|zyR~# zGw%;6`x5_)f2q=yd89{K|7Pa0uCc`7$INA&sUFr}`zvKfpNDD$+^ZbF<8X{)(dJ3` zJ=>9WG^*JCi=S5Z^>M;q1^2Q)&HA>VtNdoNoptldj;u!}d@J*EzJAPM{e#S9y()>r z)65s!-S&_ETG^L%ZAAY==K6bW2Y;jVvhK6w_bbe0osL}g=k(ut9~UkLr}`u7RY|&U z0;l>j_G;U)Amc9H&w9Oo{=K8;#Nxiv7L3%?Cayf`oPKm-XT^0<$35=glqG$e3$LWx)M^3f!{0pGH-U2{p?^a>p}RLe}%a| z&MN&6%8q^=x)q$x^JSjlakf*-`oV5hPqLYJvOYE+vQPc6|Aew1yie(iSpQk@4Bn3a z`YP*Xp6V|C@w{i09sT%qCv#cfR_fIg%w^q;e6}+yoY(b`^1AdtyvNZMgHw5Repi)O zP*poC1Wxs{yh`Ouo*RCEc^=O*Kg#K@|D)3D+>CbiTOm7?X#VW7QOq2ml*f~ z1HZ$-?>F#wI2`@$q#X;5?3A3LI%=LCu{3oTCd6lJH_JNIf-)~Pz_4fZ-aIfRhjgB7U)_7dBw3k|N%2(BEReTyb zzu#sq>(LZ5{}c0(<4P~juU~);!7JS&hvWIa_T23Xa7tIdzH2tvX=A;t6Ds}fC&B5s zHu#w8c;r0f2dvkh|F*1C>FVnF;-1sj#Vi%)nbxPRCKPU-6RKRbj^ zx#Rgw@QZL>C-b7OWB;dEFYEY7JH7oBm2U4r)t_XpS3A4`oYK|L8~d5ddPWk5EvG8I zKCjDu=JsvMPkA2t2j&xeT#$0P6%EIU^DmXYh5dg3oZ{L2dlg8(`q}Ddy?!3PaTDni zKeZ0W{k^-Dos(?mE^taWsNKitWiIQ|O5FZtu>X?H>_4xYBlZ6w=KA}6OW=CUrc_;bdYs$7B*RW4DkCocjg`!=U5{b3Vxecqc#nP)tr z(v|u%%RKUR#U*Z+ou%x{y1iomoy_(3M}7iM^=IrXWyjjAGW=KOb3ai2&$0g(oUQE3 zI(ltelzxhNK9{@n+of4bFYASkayU^btyJN#&dB!m=Yf>eu z%u9IQ6=|mrF`xXN@<;eznd`^d3;oKzKCkl2nCsVfe+8#{p7)?C*t|5A;WIB%cJy%t zw=!@3uJT9vr8hIz$L)NGxvZNh%eme1Joc0Ou@a8+*O(VyrtAlqKf_!f*B5@ivNKqq z^wREL5AO6EODbPQTyFG`cRjftT-pdH>3kgmr}`u7Qi=b^nb-4k5UHQ{3+MCIN;dWx=5xI& zzY*qVy+GL?eYZQF2f@Aip9dU{^VQF*49R$jkFuSi?<+gf@BKIHE8na1^&E%n7pn3a ztW}_d``bKl%I`GWk$k<8d9~ILf0DUA-^FRU%8ovN<92YbI9~(qmES4@Z)W@YxczT1 zpVh7l@?PX^|0ZzCudFLA_4!fe`n)Tn20PzpeLbHzOF#C)OO$-F_GZd&xdU-1{#%R13={%}v8N>^WR<=V>>PkU1NBmHDI zxK}xTjP?3>(H}C`$1UvIN_H?$=@(S}?Bx1+9XQ2dv{j{B#c>#7o?EB5#Q!&r9TvrX zrChG;=+A2|WiIPVOZ~r>xjr7?qI~7&*nZ{bAp2PWPJZh1&wq(|j`m*FADL(KeTiQ7 zbzgyZJnsZ|>aBKs|EQzKarq{79@4o{8TvZwXPQ)e%Gv(2%x7P%xExA>jvjG4qWq7Xq7pvJe5hZ=d4l8oHnyYR&;Kvx zHjnq4VE;n_m99Ro?`xUs*ZX74^?Car*rDv`*RylXBU~=h4_~s=I}Rn_Uj5+>;1nNy ze!urJuX;+Ai`38maO~qa^-dL^V)bQh+NJF1^JZ*gUa!4xdpmP|-HnI9z1sCK>qj3@ z{z#m&3%&LE;FK@@e5Q(d9$yDZ|MO1A4&EOLsSKfecRn9ry{w-u_4d@=%Dz6n=tk!H z`z&?LW&LPB*OMoh>+?fi|6*m|`iRPJ9_!!4JmYV)+4~Z2{UHN? zA2`LckiIdd#cF2mA=f?XRqF=I8Yi=WRva>6U<#{n_`cd_~y)2Mqczvwmn! z>AP6}(w8bbdD{8*2bt^d#ozlf@(1%fak&TB&RfCBAAMc0-!h-QRHa+Y`qN&{>0YS% zlTqe(GuPKC_#tzB9+iW|%8ovt;X9a*aepqy!Ot_-`?ne9`uih2S19`l<1p`{*J!&y-Mk`c>I;L_gd!J)5=fbk29Z~Qe66N>+<8r znl6tt9K8HcZSCbXHBGhkI|G+B)rIS8qL)P)jvQ~kJg~KJ>yFE}Ze9Kl@|}UpYn!4C zTaQ{f%~owqBoey2uI7%=ZAboR29LlX>>i6AzwNfIwN@x}&7S?C^1TOwp^z1>YpOfc za4cHa6p9`W)kcmUsXJ!ngdqwa4IPRcJy;V7g`-ECj)iKD!>6N%8zXhmy71PWJ9q8y z7KLtWIMNWRX=m_!BJ_B2Ifq!gdsSJy=k zM4K9p94fubnAd+!(mSuHt*vW}l3zP-Y-&K<9T(w|lQZSjm8h@y;lQ@gfk3FVl)ibh zwDjQqVCa9@=R$;Q?~&-v!2f=`ro+*`L|s{e#q+0a~i`wAtyoZ@nOJrJ#_ zy~C@HZ?8DoNbRB`7&`o1+1yi5T7i1FlD&UQV@0z*Sl3i~B>cZ$AbSt&K>OQ&U}tFm zwfhd_hp0)S`IW8^(tkz>3JO#VuEY`R$cj<;r=-3k)?Jp@9XS-OU(ti*YPxFwrgz|a zKYd_(i2g$txErN^{lVMoYNKmxBF|ykg-H7fI;gKP>F4Yyh4w}fqC@{m6>d4;&soZA zuW$c^#OW~p_qF$(u7l_Py4%q6QD=2+?b`a&=PYi!uLy_Pd+IX|>AEa z__Z7fgE$hx>fhxnx_O*-6^7_PZ0;zpyQ?mOZXbu!l};F!BeNFA=n(xJI=0erdD*cw zpC#|wU+0{@msZ?$c#WoUoN!KJOODnaKa49N%H2P9hE9EcK|w`L)3G|75!clm-dldV zQ&VBFzP8%ZWRW*F;pSJGkaEhPcAn&JjfHaeeBFA2R4< z9xZrn7dbne@Hv57IcoH4LeU{PbX`;q_>e-C#{Rlv$0O0w{lWau{*}YLoU6a1E&Lb8ggS&~uxc|AWk^X5(B}{Xd@I%j9ABkE@^t_@-qlago|^-#c5%O*Ps{(ONBp&&j{g%5US)O70Xjij@pSa|m2cKA z_j^(bJ4=t$9t}J9wsoHm6ol}flj54s0loX<(AqAGz3SE4Pm|Vk2Qp9)3LT5qL>p?I zCn60sk%oKm*U=-Pnj_&*xbB$qbbiInd+b~qF)pobOC*0$Kz_SjZ{wgS){h#@>L;vIIzwsys~3T5j6a(Hw3ZtQxj zzTvj0^GswH{%XQw7SY@gOZ+r3WtMJF)F-xqRf4dtzyXj~xxwqmD%CEJ@_%;NAn#-F40Mcm*}a3YFZn?~3d8?m?FJQBiNZ`s&jC zx2q4aYY#+s-i14gxD9gP*nt}SgS%|oLf0V>b+vbdB6UYf591aRNe@Kv!z&%F6Pe{5 zO172WMT%V&sy5=8*YRk5=x|MK=osCrJwOqq{R=3(p=eW0L-ZJKPwk=yiZxAjbdw7G z>tR-H5A8n)QC(f=2=xL7qJhSmhNivO9yo@(4gn69a< z3)SP%V(9phhC6Ylf6ab`d|QFXXmr=6vg#yx^?p4QUEw<_M{L}JIu z!jgtV4N+Vmm8yF`J4zbvxvG(D9ImLXXu$mx3Mv{w!M5W^YMSl|9jQB16QyRpZAT*> zoTGM!qDRqO=(pRBAHhNF=n+J}w6rz?lXQ46SLVn{%|E_*?nd)GR2Rho_gGz1G`@v~ z8jqqGM^|oE`*9KiMhAwt)#cv!Ky()}h~Xx6dutJF zt?NZUg$vXD_+dLL6P-s8}bdlqIc0{<+ukv+7M)U&p z0-+HSQVafpL1NKjHDZOt2(<)@`2$#h#G)4n&2CsARzo-6cYg2RefZkG2C^#C5w`E^ z_c)L5eCM2}CkRfck<|=D7AqKgLyFfscg`v#EGBr`iH(YjX(XepFvJM=HFh6 z#OP;MWay=5WClLL^^;_}_=wehvv`2KyTT69Po$5Y8iA8Lnkf9mhcU(k3RFEoks`mC z4W80W>^hDi$8cZ}sgXE)^LfU$f(6+6*UFX#?6frO!z$g;WU_8RC zFP|l9MLR}+rrz`~ShVmlBn1W^P-6}ihUhx&zfSwFWFbN}oY~PfgOR35!;yY- zJ)A;E^&KPXcA4HTkJD^9oSxqVEO&K^d9GJ?|aR*T01QQA2LZ;pZ_CkHAP>xS94=1`G!Zp_LX(q6@u((x>x)-mal2|Jw@kPBlk93rrP-pw@gm19U9og}LTpsz8!o?Ecd#3_64skfjzILwO(ma0c;MO&o{#F#!dMt;d#P?_u51z-BFX&DmtQt83zPsq8>PHp=EW z$8+5S%7I}k@PKcP*B^R;IiW3-$dNG!u9kEF3exYCL2wdjn=8=$ywJw|m6|V#+J_LXmq=()fk*-noKx>Ab{@`@ zAUW%1KN_zV`flo*0^{$ff{X=KGGujC5a@vEBa=^X!v$s*@bz$ex`%i;Yp)jrtT%a1 zw>+9PMOrEd-B*AX?b&j&oB*wQXZP2L1Qa-U3M&H~CfFF!FpMRLtrxKvY6?*(X3%m_ zG2F*#h!z79m51-1-2br5{c=&qq2bfW>^Q{s|oigQ~#>p#;xFI zf+LveR@$A9#!sn^34N0(FF?hG_!}cx+-v6oWiK)j;@-4Z5uRi1SYNu&$55xp0%5=m zBUoV4H@lq=#w`6)3ET&LcF89L)I;0T8gm+H2-HsGZ)#?xK0FjjZ9rYMPD**>jqW*NY|7 z6LBB8;4j$cVrqKAxhb)B-XE`R=!ioEiawAvM1D9LJ3;u|C0j<35L55Jw^%5jSmwW>~p=f5bxhJC}c$Q3JbR0RfBC@@CoGChK~ zbPp4Kjs)I#D0cTMjDU12%o+$u`_u9IH66PVl5mmkHtC6LS#hxl^?xTYQ-t~NW+OAm zQs@CC$Dxrb^@=Yqa;zgX#1-An8toqwhQCapvC>TMx);8J#7HW zu-Kt?LO{$@T~nfI;@#|pTJ6m~fVq_-;HCD0m5dJeqcroz-Z1={NFg{B;rxKNY+O}K z$T?%|Z#W%fnNBP3AO@=3XiDF*Fpmo$!s5l(8Ecy|gcwN!U*Ba_Dh?>7ui`c%c<}Rz zpj_=^^t0vXom|lNOBJLtgHWL4h)%xOKDrsB)`_A#E!Ovl$u6f4U@oER0TMm6x8kJ8 z79v$$NgL#Sg;Z5CN&K)btdjNOLw>9=nN7v@^~n#+I?;g`DT_xl- zMTpd-@TF~21|px{B)w8P(w#a77nq6`l7HA6P8HJJ2M&t+E5bo{un0YA081ygBdP>s z5C$tZu%fpLi**xeQF*ZpSkZ|! z0V}t%<>if)GQNEE5Ua4bg@6aZ_s zn7c-E7M+)!%yn(nx`+9es^pXNOt`Hnb||**dH{mgO2{9Mb^*d1^;-;+HweO>VFFn= z1~>n$i33+f<3td#az=jCl`0=idwKqZ!~nt1djv!sZcFfAC!x#|Y^-$D!hMRd*>|U^ z^|!wQ+6H|ty;^O+t>kKEse8~-V{09V_17SEZh-eN9nRIlFOQlUBs&A~#x{|7dyW!< zEjGO%V5Q?!7vM&G=Zkdtuom~CV$l-EaYGuCxP;=T2bMSVhG`rKrGaUM#rFshB7sRU z5IPi}(i1beG-O{0ilnTY`U20c)3#&{+y*F>>jA;0-Vh+(Iyp6*NW<>}5dmT2R=b|CoG@_H|k3ruqhb=R1iE7Qr5!h5+Grb;>x z()l|i0`W;4ZxRRWiqS!`F$-6LH8^ziOj!jiP{;2vb`@lb__1%x zHOLmj591uK(^>R%tcCLpD*3Gr%+YR5T5l9W^a@V%2W7qTs$8~e2Ek4q0R9#B_$Qwr z$>HUzZkoI}!MAW{MX(;;J`vE^>{?0_!j+o3*=D669DYgLm^^ljOK!WWN6*@d7aXcp zx$<%P+ElcIMXPDFwGp*UW$54r3h1yL@=a!fAFAAGlPGevWx*$B8j4Q`nF*y({af4@ z#wwON-Z0kp#blv!zuH}B9Zps}O58ssN(g0cRA#~T(vg#uUh+hIQyvrc76qGBN8-e~ zOSrZFaMEJX^;^IgJ(Gx4rnQvASTjk99x;aP!p6^Zu92_qsOx%$H@>rfKkhNP4NchzanM^q(1p^eXGMM_{ zmKz`iU)WWpEOpX@1*)-biBs=Quk5eK-xbCA6a@g)Y{6f?)yZYNskc}~YHUh{ zyA}H63gH+m2<2t(k45^xuT0RM*k%whDcFgkE*z%44SS4{G2f#;tx330#C*%Q*utjp zua=p7lkj+d$uRFqgiDjwa5VxeS27z$9M;xs#rZ)$gm44SY&f#NZ%L6AM+ z;bXsU!ZURq3CtUYJ^}0&rBZLd(t>?y_jnk|6emHXOLjCH&xXtA2<(zvzT3%kEU35$ zEy>-7u?mrNFjwnBfjO*!EaJn3LYfS?CR@jf+Sg-~k!;O+OXtwWnCG(>W{0>n0voQ% zjXw|Z^780uWc&3qMNWY3y&TU6poNqC@=dn-64ov2b~sP|emq5?2=EOJklr~}gv&<_ z)eAg^?SM6@vefx>nohHc{2|G&*d)6UXQUXUCC=pS`2qxP`yJAvEy=B|le1Oj1j);G zw&R7dl|dhBoq;$$TA<(u%8DbzZ9bwI{oeD5VvBRAYZilAksU>g|1!wT`b2YGrz4cweQ z)Z&*dL^4N*IqGT)``>EHDFx(fX!jC{4dN|+a%6U=2+Vkl&e|YNx@9q ze>^zQ*Tg+w-{+jfMAE6gqDP*m)WNM+p{=hp{$%X}Y62(%MaslvZdM1}igb6Q@Yeo@ z-x*fBtF58@(o1@c2$8K3`IteXGh06%6n}>8lJRmqOV)D1Qa;BrQ?=ejlk6K+*QP=t z8nL-LMCa^-G+^MZ5J)@u25S|SBf?O6OV*Wk&!<4O(;E;QRZvP%#++_yYF)$L7iy%x zXByReCrAa5LWp|IXSUSMtbtE(|f6y}qK(@+L!G`=06(Cgdj;Ete?l4a1+x0Ab+Ekm#- zuYC1V5V+Z0!9cI8RLMOZ8|V6M&Tnz+2311IOQz;!ckNt zhvfWA8GjV`c*PPTD41i=yjm4Rj7x`kww1V`YfOHm<%Hpp!kB>Av7z^EX3>O_r7}wX zWW8rqxif_m%mH@`u4p-xtw00+`5CA6@e5+y9be2r&$5b0`jiIb`!ac<{xK0zIt8`(UyvA17P=# z_T#jSwUL{k&c>A&Sq?F0cUsxt*)EZ_ohU}9iARW`*M`})KKp{q^)HZCq zY7h`XKd~M98K7&z_LKrF2uQXp-Uux)?AO}@F02!17rKaE$R*_!eC6dH-}apvb8@{Y zDbOpv-%{$f>l#4GN!}BGF3fv^hwSA&$!`Sg*cz-mTY?y$B1ifQ z>6OJ{i!Xn$v@6t<#M+q5QN+=otOg;sM4by6xU049`yv|C)2oGG8FG@|4>3Kx=g$}O zyYywo7u;q2{6>NbHo9STS3bN|<|))g>AeV1V4SD`IYac>nX0Rn56)qxt+$OhbujOu zv5IRzVll#-0-dGPsevph1Oz#%G6pIdY8#bm zS7+_uUBGCdRhWXd;YWeJv8BP_MeI_n92a(oaNMhi+aT90(6viuW4PHwy7w`%%-W=g!4l~AViGa@t()qTFuf2 z@S7p7P$TA%9w7dYWER9TJQ2JPzV(!1{a7^yP4lFb`U)OChSBgYFPI!#K7;Y)fM(xL zAF}%}8A{=B-Vinkfk8R;;Qgp#H=cqFR#n~I8`Y_>J1~s3IrB!s)3}4LG~38~#;|}y zy#h1s9>(s#h+$N9>rAJHikax$ezOjQ--i%J=F8PxDyXGYsfjm%uFUhpJ)6}+)Re}C zc}d!IdBRZ`({tjg)O92aKqo^2d6yHL>QdF!SPS!UAQ&|bLBb6tkydIzH0uj-1S;}4 zAz%9l!l+`fD5TEZ92`&(hCEC8J#s@)$w&twJ0|)#v*w9 zJ)Ig7_X!WNTn9wtMv4H^qlzhzypwjmgte@|-ZS9+^s6DB8a=_)-B{VQLoIuw)k@W4 zU3po@r(jKKWK1G&%LMcyS!2z!&^?EU5jqM(~CF&(tMST`?%mAA-B9pfr zv}sUBZqDRym6p7e6C+9;ruYY8%k3>v%^ug!Wm$17s7+s7xG3x+;nk(blt)XYIlm_9 z&@%F&*w_xvm^R4k3v-5%2L6&H*%GZd5IY3QfaRDGoV)BijfZ_mzv<5Dxm?~x%2gQW z;K~~d-bOlVEG|e9X+h0L_OYO*5DE~m*y-Gg)PkG?+=3=PT4V8KHAa7nY@JEs0a*w( zY%+l#?AZ0D>U9X()D==&fi^V;Q#$k%uu&NY+{mdG7ztP;H@R2~6_dTmItc*=rd@1Q zKboNie1W(svgU9bHf0BqfF-g(&_RLC6RNRq6}RiXWug}p+MyN}=wktn2_?BvB#v}t zFem*R6B`XR3Fc^dch{_~?e*Qpb^Ht2wOz^*LMGn_ub562*X3(CNzTZ!BGc!_aHRZ^ zYD!)O1#-^5&ghGy{4xOg?8m~S_#(Sw$0!HBHfS?lh|NR|A<;5l1vR_)lRP1Y=u08?B0dA);Mi>d$Z8XP zbU9lBu0@9D33{CA?4SZtii-A!BfJD{P1PX=KEv`P@zr5coT6MmgO(NcY*e4J9=xE< z{-i~V94No|PKpz>r*W6dov7F~NCl96ub7wdIKUy5OZ~b zSRMD!=W|BWY=R6qy6H5?O8G_k1a;gR+H|XWtOj!_5umhhAm=$f{e0cCT7{rZ4en}L z3?INepEd3qGhJ}G9T8Z@g%&UNvH*a0oiZt?n}c|uwiRS3bYstvg3>J8}E#+%h(fwv^>O<(lDw_i_gQHyZMcC2J6 z*mN=}8!*A>YS~~BkO&!}8KHFwHp*&nN3>aB&`ik8ss|!Y;?oU9uVK4lpCR(S7u_(2 z-})H>=HYE*MERL`+eAj7c8g2xY%s8;x2T^F4G~;VoyxN_lCHEVMmPoJKm*$g4sIro2~8DMT$AN; zR3VV4qK6G%B2;B@Ksj*^G}mk|Q(&AoRFGecG z0XElglCiGgh?~d1m96aFB74S=0Gvvgqa~OM7D#feY$?|D=?!Nyl@jmIVBqQ2wdZO~BCJSOY{4v{BpWx4 z9ivMrRiHJYDZ@<#;D5_DI(Eazk1-&5nJP9hs0v6dFR0gMm)MSRHq)HtOG3|@6C+u} zo||$DQXD_ix7bRxqb;TAgmwXYRHLp^t4s1(Z?_r%nfVp+Iy^s{Fb<29(dtNduyQAtqb$liPI?x54rug5JOcH}op`ioY z|52J@-E;tZNxe)A9(t!E=8awJ_lv<>l6)Yn@Ymw8ND7w}?GP~#A2IBh{0A*e8yH_* zRe>R;vM3X}qg2UOi8x5cjg|G94N!}QXNVx>SkI=NY)nmOkXW>>iL|v-p)*1)2@Du* zM@V+MVsHo)Or3)&@d?pr*c13egTu#GI#<9#uKJ~u7BRpFxYDot6}?cGz^yDwsh-)3sIQZ zGq_DahHB#L>OR^BSVl$ty&DeSCuc4#@T++T**2I<1FX2|H2A|A>heQ^Nagluv>94S z(ye9|rnh8s=*~=Iu-t#yLe+h(Iq^gms!Rnbn}Rw=P9K@Q~(b5QGs4J}ht$S6TOVz7Py+4c9ET1%d&!61!ZqM4g-j!wM-)o&(yT*Vp#gpMKM64xxS~ zn+osq?d*K1iD(_$Z2XZ<$DfRmIn3Rl;sNA48~Laxl4exwPyq{IRhs>~F8FrBPOgra zV*8p3-h|oFx_FCToXo-XOqGZ( zlu<>oGjG3HgII^3_pWsYgAxH_L!VvJsUO%*X3IRCyJXnB^Ln##t zZBZ2Qi9AA+bwPGWY6+@jx(h1OCT>Fwvb#dC59`BuAQLVYn(6N3af9-sZ7bpVm_j}@ z8|C4uiY1r@LcfzC3Ponh?JVj+Ta+z>zy`ha73z76BTh+ef6G}X%>i!p(yG*Iw0ZLo zCbgsewm?CyVrCfa=aw_AGv#uS19_YL@IIDu6}g3u#%w!7iwtRdxld*m;}0~C8hNJHYXfH9Fudby;LQ)!=3%2i|+7f_i@wHt`H!t zg$OIbBM|>sXU8g&E!0wiASj^d@@v$gx?X&G^8v98Byt}1W^*`ziPBYyeiJ*D^hsdR zoW4WJZSTYZE7;tqGylET4-wbGo9xh5s0p_-`b4-rST_wSjuXSLPgt)dMdPLq9>d!j zE7e)azJ1}c@j@<|88mM|nu;eTi84N46*Y)z%;`Y{>;@Fml!0x#uaP&yG>|SU@?ECM z>6Ju9`m76sVhwlQ-dl8Gzf3RfJ3wkLQ&g_)gOt#Pp^H;?K(S#vb`Zu28mu%p*U7QT zs6b_H6HKcLfO3|%Xfb|MXA^bYq=nH!A=0?92q<^{2L05&SNktD*0Kp7(BIL0KO1{Z^_kg1DA zRWe5e{IEHs5V$$G(N$8_yc^5ludw2cD3{Yi)`F_ahET|K(Rub8x}*@Zgj*;fFho08 z){RqFpb&y9Ndp(Cjh*ZASMP+{QHD-P7snm#>1G6h8e!iTE$3##Bv{l^NTr9E2?jqo zmLLRMg0k$U?14n3IR8cV!ZkLM@)rs^O)lxcwbpaEn$J`u8VYzy0`}8df z+UU&Y%DpA5Eb{>Fsx@S+WE88!f02^HwR>2Cih_76>p`J#Au8>pJ!vuiISN^fr50-6 z5gv=6XQ+I%@tX}kfRlv)KU_jY_Qp@c@e+md*W(phqOQoX;5JiVFmGb2B?ZF}=5r>p zarCU+)Y5bw0Q@YzE$lB<{aCuOu2NF>#I2;6SEZ>$h6F9$Y&4=tnF)T_Esz<=cc-dL zS6y9)iTf7gnyE1Pp^Vue|DeEsNm>TgA-}X6uwi6Vj|bUvigpobA|~q%GqP)6Ek^uI zt46ox52M@VS|k{ENfd%*VcEm(%8ep~aflHj5;mYS8MW0^bQ?{X8mK;v%A8!LR*qc0 z0hMNZOBJP`*RbVQV%{3FRZQJPnYyp3vdK?T8$u$g#-OEZso!S;gjUWu<3Xo=v;c&5 za|AOtNe%AfB}BV?EO8VK`t_!gp57j-`izR6`+7&;{hjV=!MWmT+&?yS!37qPEYKn;M)rz;3r0?IyTPfnp3HLX+V zMX)t1(4Nf(0(3#e8Qf%Asp)yR_3e7&{6ZEg|J9wiNf{QekQ~sAI&cUrfoEw!Q<{ja z&pr#9c=%E*h;Gz1nxWrxE@us2PhdODLIrH+M(Z#JK_1X(5F$b%hN`<}Ng5q7oxxiX zA85L+-S$)GnY-a=TEvhFRbKJCDO=Oi;+CKCeRO3mqiYf^BX zi1F2#kDPYfSXYz0dhI1&B0h1wV5q`HDQe=>kpEg#C6_wjnF)&p|B`H55cL$+fm}L> zzhjr9efez3(Nwv7Po{m3P!$xek*Bo+ zAp9I09GdYcaXnMmJ`LN|_;JjPMvE``B!(haM0L9vA!d?a{?;4`i9y!n)v{t@`8Rkr zj*#9$B9F~u>}?#y^?D5cyfY%J`F&>6Yv3sNk4QO%4-H5w;HPreT%Rw{O0AwR$G3_nz4}DcVS2Gx_TiZ z>b$ugPD9i1cA4HTkJAh_ozJzK+@(yuT~g+Pd=FNR6s_#P)qp^WpsVZkG{9TF&Bo&o z@%BVq$|1li9%iLtAfqrD*yhEkxD^uR2!N^@g^I}##?d`oKx#}suF_0bdGl6ZNMV5G zldl%h!$TRdoDUSLaw3pzx?8d@j{N+kQ%Mpr=V@ZDd`8kXwP0#jR&&F2sZ7Ev^yyP5 zY(YXY6FJ}ow-mL**E8FrFc{`9(j_`-*<3rfgaRg`yTyimNlFH6F%%<47!aoMtB1*S z^h2gAD#5bn1lEd>xQA=dSs{2&*@5RWL601Mt%^=y2+W$MNh2GJU}d^q4JK=_d|_i0 zdbE7EL4VdZ>$W`*p%7oL*DJLsIUgRMqA}uEsZm>0mw_-;|9d(n#Zugn<>=;AYdQ7tE6a zTqOI=O-qBr!B?^v(op(H81#Rzm zqF91;Jf)N8ndJx!b3LO1pRcYdRD(lbzhe+v0s@menq;zrBGbelO_YzHk5?05Hj;-V z%2tgZYBB>CyUlXZA^7b32o5n|W66P2qO{(WE2F0?E_pT7-xZL^DBu zn+&mI^m(K)Aa{>7433a_$GNn+QQ6DRWd-monX*u|n+Su71c`Cg$xT*CxO;2N+%keL zm>v7%XOpuw>f&~$%7ZhaASoyu7652vHh{LLAX#8EVSOhlLLyul{&ov=M_s&dn-Fh( zB?89^Gs@m`(eiCpyq&7JmS$30Llkzt(K8Ubbw?s#Z-yhgy%G_0A}DZEu*iWD0BJY+ z!ds+KodvSpOp6jYtoXII7(fKCIL+g>sP|S91-2r|==%q8W?T6d7Cbi!pK#YWUt`O2 z?b1NSb!deFM*|X$48;Q^{$w#+hc%CIcfK%;Z!Zg)%2z*%v?G!Yd)SR2g>1Cx8VH3Q zmM#-aC8>P|)hga3)-I;Yz+m8qug7<2HVi$N>>4_t!=0SkQ)(X_piFXZ-!#{u#Dhgz zCGP5)%2BJxSnX)sZl|@W;)J^_P7AK))@Jm`+fQaK;AU?nV$C+&svTCL->Th-=3O44 zuM2Ngkye;g39DkbdPeD9xNyDUR9E1s0p}`zz6PGl40wijW1BMAa)I`#yD+!W!=628 zEe;RdsZW%W5LX4Trom#{;9&2C0-rjTEtCWCHgwh>kfPer8?}w{!5eFRorqhP-A(4{ zhcU~#D}!GWa&^!iHv^s`lJM5VWJn??l~bpq$?Yxr1LJ$D8XT$3_ zQFPu2^7HY1ci>LB7RJGKlpUD~TJ0hI&39z%Eyf^U2PrHT^P|i!!`OmFqP-CR3e9rL zRoMyU>0YxoD+4b&!`Ikk0Un~ig3kypqiB6$;j0#E#DUi8RiE`8@}?+YegiZy5)^%< z2i!(Vm%0UDr)4L3a&|RlM_EA*v6Bh`H1N5wrJ!L=Yl98UjdvJ94?y!p(Du5-<`?zX zbL?&`PZE_OfWkx?@|G39xXKQ!l3S!pU6@9^w4u`F`W|a$M9HH&LU3_g*(8>?1|`sK z3c^VYMrR~rFj3kaq-%n0@Nj^}BNg`Tq=)fC;dGHa7}du?Ds6IScz_?vDQq52!yzZX zoR-^*Qc_JE@psbx_+5ttKEO4G45CV?T>+ozvV;Sp!?IMm^U?SzT|X?5%=_+}Y{_MT zk64d@zi}H0nWm|=YNXa7_FKEpU7$SstPK@0Dli%7nm%=+ic}MX93IhQAnuEdX^-g} zcnh#+jL+Hym7s9xc=$m=B|C(KAg=DQ(m?ckJIU2e*-uIkI>Gcyc!7lIq^rwJV;x`x zW1lQOB8mSFzZ0`qZusaBfFQZLNzGFg=WfoUkp^dnkP#f`w{7H~7ule8GE zn+=93tuDPIJaj%VmFogEO#$?Ecxc$6gbo5x-SX>uWAX7negWfF9CfZz6fkgOMozt1 zi=4(L7&#t?6~Rl@h@<7;iz>IIOoaO$MT;(PVUtHghXahl>X^ za5X{%s~oy_nDTTgD_sXGHehj^B_zw)kS09vz8jdE2Y zN89kXafZ>LR|K)tLUIH#AYxdm#V9ilJwFj zw7RQg0Zd6%U9hrd2#Z>vrYpZwK0+IY`Iw3un4RYKAk5DL;8&~t8DC7Qc}Q9Q|O%#w0R1QY>~faFc~i2*-Y78 z)-O{%ihx}+UJ~~x@-eoZ>~@KM|6k;LTj9B1mohGLqra-hRETJz<9p94&XvA~6}%ox zo~WBsdt+yllNJdfS|xh{#xjLuvZO7O0rM!K5a1XcDJIbA;y)i-W1cNwGQrmPb0N0Y z4X`!&&0=fjT2(X@+dK$D+|p2LJdas|#%je7mhK*`*iNo?c8wYF>kl15Y@By9FJ;zE zbA$TQr?ed80w?bo`a;XQ8_1Ds!ma6-Wl2|#=$1muiuThqfbqLsD_6?aR^H&SL^P&&k%AXl8uvg1>_zK`2C1DTl>pr zo^DcAgIi@KgAKZ_B5wdZ!pNJiW}2m7dCQ5~%*89VT_6SuJ;c5}dpTtJG|u0~tx7i* zzNi$c(m%30tkCVSzz~MXSFuS48{Gt0yahGCkb=Z*psU)vq+4*sx;c>CScy>VVY^=z za)#69k(c~!D?Kn+2$vxyR_d9$5Vu(WCjB6$SYk))O|irUj2)OshzkE!X7s}=;nT>C zRk>HqeJ>67 ze!=->6-9y7u|QxVsi4pe;-`|=mA_i4&03p)<1pl~*0fG%cWcfWbpnc^MG|_rParoq z?)Isr#DJ6nk&w7RF>oC4Y+}4At}>lL7YU~&e37B2?&gr@~ZnH}(mD-R^p<)|{J$#*kz3rGR61 z7^Mxj&PHY78wB_{S92JlBmNJ4a{4(+EKDKv2!(j+c&k)ZIn=4xHkJ<4hvN)KcCXE_ zf`;pKF_(&J+;*n&ft#G|BaME!G4CPlj@ZxtG9h_4$M(eKsF<*z>L_-9uCQ65;SpZK z|M*c&4xEnOwGnOA-Uf}xQ7-YJu@#ABLLu&zL&E%(r9V^wX+gKgQW05K7cv~DyIm1p zw+kr<$6eY2#85@>Rdpdc9ALk{Lz@^&+@Xvt368k;DZrI|Au<1=m0G@_LK0J{!ADsg zvWBEaA)3G$a}F6u&?(NG!>zj^pygxZt#t)skmzU66%7CW7cslgYO-V1P%X`6R@!d> z`A$cRkMk*N93$F~NWZ2pIo?F0DoGV8P~pc$y0;?x*L7=k;-}&0#HnjHOO&RlpJ~I+ z8S-OL5DzBR{d&FZEwZ(sF1ch|U4LW`@9o?)|Auj>92DxRy%&7_!U|3+ZtHj!lzs~| zba@zfFF>7%#xxns#)#vnzEcT%CXiQlu6tEt| zgQR4+Oq!NU1ROyFksg(Y0vszVV+qP?+8&6dY|=6zeDl#ZAAKl8RTbHAhh$Co<}XtS z38>+!WKGz_tSA;vk$5|j*;?rDaGRK%mu~g>he#;Af`eJrbQ#&WrOzqr4MZj&%(=Qm z!FBCiJ3brK1Bb$({{`r(S#VB4T5a11ps&o(uWr7Dr`^06YW670OIii z*$`5R_R`f+j=+(IjpKqmWsSUfH<$P&%7%|OQB?bs3(>3W5Eg>zXYdgFtjY^)`T@N0v0(C86vO1IdXtnO?sk~D~VM|?~ym6rlo-%k)u$8Lc)Q`%O%WRKRh zA#6^%n4u;myJ?np1W1iBt}d`+cvE9jjkPTyG=%fP?%(k z*X$-v#e}6)uW9gXJR@KlkGDgez%oW9uR(V@9d-e*Gb-MDizLVQh==#sg%v9oXgzUd zRD_&q4lQWDC4Q-|9UI&3`IIn&;51@Wo*>wzig`#&HoClR_dB(ss>|n3_f8sS&!jQs zOxW725WqzoM9dZhl8day1r_nR&9d2FX_~lU`lI>BD6dk4K-A(nV#BRg(C@)DMl#Rt$von1|x2g4-Z_Qi3iW5r#LOwIR{Pp}JE21I4`(ef% z{Q7lz^5R(ZMmh3|;x|$wQ!HVODR#QSxm*pIsuByurC?N`y>tWARS-Fn>LJ>sC&x6J zhRcU^GK&4DM&-uVXlBN8T3&4s^#rB5AbD9*+KfA6f0Nd_3kNU{ zn7<9Sd0)6#EYj$62~{a%wK?TE+zm8n{0aO58*3WpY6xXLT677mSKN_o5-H~M1v;Q= zt}3jIiB$8kK8JYvIxZ>o4&*rFO~dCv9Rgz&9|jLU$dFxvgrXb-y&+12-Daqq^^|NI z_%h265@5HKm@vDw9taty!!(h%T!Er44NBuQ=6`KjFdACaMlsXKeSEW|JnM)^XEvDkpcj}ir^J`wge5`3l zye}F;9j#|_tWD37OxcpVK@M${%?bZ@+7rI_C4+5h3_TB^=ux9noOWm^$&s{%-vIkt zEuN*U(W8LcM;SYU>c#JHrP_~v$R}sW_JVB!^pp291)O8y7Wi&i8@|V~MgCz1u>+F= zNqecZJG~#Q*jG8Kr1OHggMf#@kqr6zinHbQR3At)bwu`M&1ot=NSiFpC7w$ngk%k_ zrV|YLa!gSsHKa;A*0HcuY&ITAd7py$Fx8KDV8(gMF9EeeB(7&v;7 zJU);?fUfi%L-S1CENAlbKAiF<$F?yckNcFm2sZZF8mSku7 zn*JrlXRGmazytJ8%jx>)#biFgzw7Z6{)NVv;IoC0V_uB!)7up^M0!8MvF10Nbcni= zVi6{{BUMvOoC?>Id`sd-{Fz@|VmH`t=1~|JSd6wJ^v34}H9O{cC?meeu`y z_rIrK&C3OT_znCu#~=M&{fU14`9l59>+&4t`0wkleWKqt$N&7FsSnKSH@~NENPj>4 z?da$;y#Fij{=ffk)Hlp)@2{!@&HbC}nAflI@h`pO@Bf`T-n{;yI{YZUfBp8+2tV*M zbH4BWu{z$o{t+h!f6Vpc=bz&<9{-A8!k>TpAJp;Y^^dO9`#k<{;ctBW6W-$ye+7T} z=l*Z1J29`nqu#dv@>>3%48Qot|3n{eUOzeazkPJ{H}tRO^&dsY|NIMeee*iKFe9T5 zH1}`L`j4aI|G_^|cW7SCb0Lro7ew|&-~;6^jGTmKmC-XC+EdHvt`%s>98KTrbWr$12V z`Mz1$`W)ssbIM=C!FX~UIsW&f_s!}2*XS1I zi2rP^|L@fas7Bu5Ce8huZAI~H?p=LT{rky(RrmiJpPDPF_ru?gj()4;`1gNLUFo-? J1EOD#{tx~``W^rP literal 0 HcmV?d00001 diff --git a/class/PluginLoader.x86_64.Python3.12.so b/class/PluginLoader.x86_64.Python3.12.so new file mode 100644 index 0000000000000000000000000000000000000000..575355097b141927c3ba4ac5dd86938a85c0a5a9 GIT binary patch literal 498128 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 +# Author: hwliang a # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -18,22 +18,20 @@ import binascii import hashlib import base64 import json +import shutil import time import os import sys - os.chdir('/www/server/panel') if not 'class/' in sys.path: - sys.path.insert(0, 'class/') + sys.path.insert(0,'class/') import http_requests as requests - -requests.DEFAULT_TYPE = 'curl' import public try: import OpenSSL except: - public.ExecShell("btpip install -I pyOpenSSL") + public.ExecShell("btpip install pyopenssl") import OpenSSL try: import dns.resolver @@ -41,6 +39,14 @@ except: public.ExecShell("btpip install dnspython") import dns.resolver +#### +# auth to 格式说明 +# 旧版 auth to 格式: +# 文件验证:/www/server/xxxx/xxxx +# DNS->手动:dns DNS->api: CloudFlareDns|XXXXXXXX|XXXXXXXXX +# +# 新版 auth to 格式: +# DNS->手动:dns DNS->api: dns#@api class acme_v2: _url = None @@ -50,11 +56,11 @@ class acme_v2: _bits = 2048 _acme_timeout = 30 _dns_class = None - _user_agent = "BTPanel" + _user_agent = "BaoTa/1.0 (+https://www.aapanel.com)" _replay_nonce = None _verify = False _digest = "sha256" - _max_check_num = 5 + _max_check_num = 15 _wait_time = 5 _mod_index = {True: "Staging", False: "Production"} _debug = False @@ -62,15 +68,121 @@ class acme_v2: _dnsapi_file = 'config/dns_api.json' _save_path = 'vhost/letsencrypt' _conf_file = 'config/letsencrypt.json' - _stop_rp_file = '{}/data/stop_rp_when_renew_ssl.pl'.format(public.get_panel_path()) - _by_panel = None + _conf_file_v2 = 'config/letsencrypt_v2.json' + _request_type = 'curl' def __init__(self): + if not os.path.exists(self._conf_file_v2) and os.path.exists(self._conf_file): + shutil.copyfile(self._conf_file, self._conf_file_v2) if self._debug: self._url = 'https://acme-staging-v02.api.letsencrypt.org/directory' else: self._url = 'https://acme-v02.api.letsencrypt.org/directory' self._config = self.read_config() + self._nginx_cache_file_auth = {} + self._can_use_lua = None + self._well_known_check_cache = {} + + def can_use_lua_module(self): + if self._can_use_lua is None: + # 查询lua_module 不为空 + self._can_use_lua = public.ExecShell("nginx -V 2>&1 |grep lua_nginx_module")[0].strip() != '' + return self._can_use_lua + + # 返回是否能通过lua 做了文件验证处理, 如果返回True,则表示可以处理了验证文件, 不再走之前的 if 验证方式 + def can_use_lua_for_site(self, site_name: str, site_type: str): + if self._can_use_lua is None: + # 查询lua_module 不为空 + self._can_use_lua = public.ExecShell("nginx -V 2>&1 |grep lua_nginx_module")[0].strip() != '' + + if not self._can_use_lua: + return False + + if site_type.lower() in ("php", "proxy"): + prefix = "" + else: + prefix = site_type.lower() + "_" + + ng_file = "{}/nginx/{}{}.conf".format(public.get_vhost_path(), prefix, site_name) + ng_data = public.readFile(ng_file) + if not ng_data: + return False + + rep_well_known = re.compile( + r"(#.*\n)?\s*include\s+/www/server/panel/vhost/nginx/well-known/.*\.conf;.*\n(#.*\n)?" + r"(.*\n)*?\s*#error_page 404/404\.html;" + ) # 匹配一下引入的外部配置文件,同时保证这个配置在SSL配置之前, 这样避免路由匹配问题 + + if rep_well_known.search(ng_data): + lua_file = "{}/nginx/well-known/{}.conf".format(public.get_vhost_path(), site_name) + lua_data = public.readFile(lua_file) + if not lua_data or "set_by_lua_block $well_known" not in lua_data: + return False + else: + return True + else: + return False + + # 返回是否能通过if 判断方式做了文件验证处理, 如果返回True,则表示可以 + @staticmethod + def can_use_if_for_file_check(site_name: str, site_type: str): + if site_type.lower() in ("php", "proxy"): + prefix = "" + else: + prefix = site_type.lower() + "_" + + # if 方式的文件验证必须是可重载的情况 + if public.checkWebConfig() is not True: + return False + + ng_file = "{}/nginx/{}{}.conf".format(public.get_vhost_path(), prefix, site_name) + ng_data = public.readFile(ng_file) + if not ng_data: + return False + + rep_well_known = re.compile( + r"(#.*\n)?\s*include\s+/www/server/panel/vhost/nginx/well-known/.*\.conf;.*\n(#.*\n)?" + r"(.*\n)*?\s*#error_page 404/404\.html;" + ) # 匹配一下引入的外部配置文件,同时保证这个配置在SSL配置之前, 这样避免路由匹配问题 + + if rep_well_known.search(ng_data): + return True + else: + return False + + # 返回配置文件是否支持使用普通的文件验证 + @staticmethod + def can_use_base_file_check(site_name: str, site_type: str): + if site_type.lower() in ("php", "proxy", "wp", "wp2"): + prefix = "" + else: + prefix = site_type.lower() + "_" + + webserver = public.get_webserver() + if webserver == "nginx": + ng_file = "{}/nginx/{}{}.conf".format(public.get_vhost_path(), prefix, site_name) + ng_data = public.readFile(ng_file) + if not ng_data: + return False + rep_well_known = re.compile(r"location\s+([=~^]*\s*)?/?\\?\.well-known/?\s*{") + if rep_well_known.search(ng_data): + return True + else: + return False + elif webserver == "apache" and prefix: # PHP 不用检查 + ap_file = "{}/apache/{}{}.conf".format(public.get_vhost_path(), prefix, site_name) + ap_data = public.readFile(ap_file) + if not ap_data: + return False + rep_well_known_list = [ + re.compile(r"\s+Alias\s+/\.well-known/\s+\S+\s+"), + re.compile(r"\s*ProxyPass\s+/\.well-known/\s+!", re.M), + ] + for rep_well_known in rep_well_known_list: + if rep_well_known.search(ap_data): + return True + return False + return True # 取接口目录 def get_apis(self): @@ -86,17 +198,13 @@ class acme_v2: return self._apis # 尝试从云端获取 - res = requests.get(self._url, verify=False) + res = requests.get(self._url,s_type=self._request_type) if not res.status_code in [200, 201]: result = res.json() if "type" in result: if result['type'] == 'urn:acme:error:serverInternal': raise Exception(public.get_msg_gettext( 'Service shutdown or internal error due to maintenance, check [ https://letsencrypt.status.io ] see for more details.')) - if not os.path.exists('/www/server/panel/data/http_type.pl'): - public.writeFile('/www/server/panel/data/http_type.pl', 'python') - self.get_apis() - return self._apis raise Exception(res.content) s_body = res.json() self._apis = {} @@ -110,7 +218,7 @@ class acme_v2: self._config['apis'][api_index] = {} self._config['apis'][api_index]['directory'] = self._apis self._config['apis'][api_index]['expires'] = time.time() + \ - 86400 # 24小时后过期 + 86400 # 24小时后过期 self.save_config() return self._apis @@ -254,11 +362,19 @@ class acme_v2: return domains domain_list = [] for domain in domains: - rootDoamin = self.extract_zone(domain)[0] - if not rootDoamin in domain_list: - domain_list.append(rootDoamin) - if not "*." + rootDoamin in domain_list: - domain_list.append("*." + rootDoamin) + root, zone = self.extract_zone(domain) + tmp_list = zone.rsplit(".", 1) + if len(tmp_list) == 1: + if root not in domain_list: + domain_list.append(root) + if not "*." + root in domain_list: + domain_list.append("*." + root) + else: + new_root = "{}.{}".format(tmp_list[1], root) + if new_root not in domain_list: + domain_list.append(new_root) + if not "*." + new_root in domain_list: + domain_list.append("*." + new_root) return domain_list # 构造域名列表 @@ -306,7 +422,7 @@ class acme_v2: # 请求创建订单 res = self.acme_request(self._apis['newOrder'], payload) - if not res.status_code in [201]: # 如果创建失败 + if not res.status_code in [201,200]: # 如果创建失败 e_body = res.json() if 'type' in e_body: # 如果随机数失效 @@ -321,16 +437,16 @@ class acme_v2: self.get_kid() self.get_nonce(force=True) res = self.acme_request(self._apis['newOrder'], payload) - if not res.status_code in [201]: + if not res.status_code in [201,200]: a_auth = res.json() ret_title = self.get_error(str(a_auth)) raise StopIteration( - "{} >>>> {}".format( - ret_title, - json.dumps(a_auth) + "{0} >>>> {1}".format( + ret_title, + json.dumps(a_auth) + ) ) - ) # 返回验证地址和验证 s_json = res.json() @@ -340,7 +456,7 @@ class acme_v2: index = self.save_order(s_json, index) return index - def get_site_run_path_byid(self, site_id): + def get_site_run_path_byid(self,site_id): ''' @name 通过site_id获取网站运行目录 @author hwliang @@ -364,7 +480,7 @@ class acme_v2: else: return False - def get_site_run_path(self, domains): + def get_site_run_path(self,domains): ''' @name 通过域名列表获取网站运行目录 @author hwliang @@ -373,7 +489,7 @@ class acme_v2: ''' site_id = 0 for domain in domains: - site_id = public.M('domain').where("name=?", domain).getField('pid') + site_id = public.M('domain').where("name=?",domain).getField('pid') if site_id: break if not site_id: return None @@ -393,7 +509,7 @@ class acme_v2: site_run_path = self.get_site_run_path(self._config['orders'][index]['domains']) if site_run_path: self._config['orders'][index]['auth_to'] = site_run_path - # 清理旧验证 + #清理旧验证 self.claer_auth_file(index) auths = [] @@ -421,8 +537,9 @@ class acme_v2: identifier_auth['expires'] = s_body['expires'] identifier_auth['auth_to'] = self._config['orders'][index]['auth_to'] identifier_auth['type'] = self._config['orders'][index]['auth_type'] + # 设置验证信息 - self.set_auth_info(identifier_auth) + self.set_auth_info(identifier_auth, index=index) auths.append(identifier_auth) self._config['orders'][index]['auths'] = auths self.save_config() @@ -435,9 +552,9 @@ class acme_v2: self._replay_nonce = replay_nonce # 设置验证信息 - def set_auth_info(self, identifier_auth): + def set_auth_info(self, identifier_auth, index=None): - # 从云端验证 + #从云端验证 if not self.cloud_check_domain(identifier_auth['domain']): self.err = "Cloud verification failed!" @@ -448,39 +565,87 @@ class acme_v2: # 是否文件验证 if identifier_auth['type'] in ['http', 'tls']: self.write_auth_file( - identifier_auth['auth_to'], identifier_auth['token'], identifier_auth['acme_keyauthorization']) + identifier_auth['auth_to'], identifier_auth['token'], identifier_auth['acme_keyauthorization'], index) else: # dnsapi验证 self.create_dns_record( identifier_auth['auth_to'], identifier_auth['domain'], identifier_auth['auth_value']) - # 从云端验证域名是否可访问 - def cloud_check_domain(self, domain): + #从云端验证域名是否可访问 + def cloud_check_domain(self,domain): try: - result = requests.post('https://www.aapanel.com/api/panel/checkDomain', {"domain": domain, "ssl": 1}).json() + result = requests.post('https://www.aapanel.com/api/panel/checkDomain',{"domain":domain,"ssl":1},s_type=self._request_type).json() return result['status'] - except: - return False + except: return False - # 清理验证文件 - def claer_auth_file(self, index): - if not self._config['orders'][index]['auth_type'] in ['http', 'tls']: + + #清理验证文件 + def claer_auth_file(self,index): + if not self._config['orders'][index]['auth_type'] in ['http','tls']: return True acme_path = '{}/.well-known/acme-challenge'.format(self._config['orders'][index]['auth_to']) + acme_path = acme_path.replace("//",'/') write_log(public.get_msg_gettext('|-Verify the dir:{}', (acme_path,))) if os.path.exists(acme_path): public.ExecShell("rm -f {}/*".format(acme_path)) + acme_path = '/www/server/stop/.well-known/acme-challenge' if os.path.exists(acme_path): public.ExecShell("rm -f {}/*".format(acme_path)) + def change_well_known_mod(self, path_dir: str): + path_dir = path_dir.rstrip("/") + if not os.path.isdir(path_dir): + return False + if path_dir in self._well_known_check_cache: + return True + else: + self._well_known_check_cache[path_dir] = True + import stat + + try: + import pwd + uid_data = pwd.getpwnam("www") + uid = uid_data.pw_uid + gid = uid_data.pw_gid + except: + return + + # 逐级给最低访问权限 + while path_dir != "/": + path_dir_stat = os.stat(path_dir) + if path_dir_stat.st_uid == 0 and uid != 0: + old_mod = stat.S_IMODE(path_dir_stat.st_mode) + if not old_mod & (1 << 3): + os.chmod(path_dir, old_mod + (1 << 3)) # chmod g+x + if path_dir_stat.st_uid == uid: + old_mod = stat.S_IMODE(path_dir_stat.st_mode) + if not old_mod & (1 << 6): + os.chmod(path_dir, old_mod + (1 << 6)) # chmod u+x + elif path_dir_stat.st_gid == gid: + old_mod = stat.S_IMODE(path_dir_stat.st_mode) + if not old_mod & (1 << 3): + os.chmod(path_dir, old_mod + (1 << 6)) # chmod g+x + elif path_dir_stat.st_uid != uid or path_dir_stat.st_gid != gid: + old_mod = stat.S_IMODE(path_dir_stat.st_mode) + if not old_mod & 1: + os.chmod(path_dir, old_mod+1) # chmod o+x + path_dir = os.path.dirname(path_dir) + # 写验证文件 - def write_auth_file(self, auth_to, token, acme_keyauthorization): + def write_auth_file(self, auth_to, token, acme_keyauthorization, index): + if public.get_webserver() == "nginx": + # 如果是nginx尝试使用配置文件进行验证 + self.write_ngin_authx_file(auth_to, token, acme_keyauthorization, index) + + # 尝试写文件进行验证 try: acme_path = '{}/.well-known/acme-challenge'.format(auth_to) + acme_path = acme_path.replace("//",'/') if not os.path.exists(acme_path): os.makedirs(acme_path) public.set_own(acme_path, 'www') + self.change_well_known_mod(acme_path) wellknown_path = '{}/{}'.format(acme_path, token) public.writeFile(wellknown_path, acme_keyauthorization) public.set_own(wellknown_path, 'www') @@ -489,8 +654,9 @@ class acme_v2: if not os.path.exists(acme_path): os.makedirs(acme_path) public.set_own(acme_path, 'www') - wellknown_path = '{}/{}'.format(acme_path, token) - public.writeFile(wellknown_path, acme_keyauthorization) + self.change_well_known_mod(acme_path) + wellknown_path = '{}/{}'.format(acme_path,token) + public.writeFile(wellknown_path,acme_keyauthorization) public.set_own(wellknown_path, 'www') return True except: @@ -498,19 +664,150 @@ class acme_v2: print(err) raise Exception(public.get_msg_gettext('Writing verification file failed: {}', (err,))) + def write_ngin_authx_file(self, auth_to, token, acme_keyauthorization, index): + site_name, project_type = self.get_site_name_by_domains(self._config["orders"][index]["domains"]) + if site_name is None: + return + + if self.can_use_lua_for_site(site_name, project_type): + return + + if project_type.lower() in ("php", "proxy"): + nginx_conf_path = "{}/vhost/nginx/{}.conf".format(public.get_panel_path(), site_name) + else: + nginx_conf_path = "{}/vhost/nginx/{}_{}.conf".format(public.get_panel_path(), project_type.lower(), site_name) + nginx_conf = public.readFile(nginx_conf_path) + if nginx_conf is False: + return + + file_check_config_path = "/www/server/panel/vhost/nginx/well-known/{}.conf".format(site_name) + if not os.path.exists("/www/server/panel/vhost/nginx/well-known"): + os.makedirs("/www/server/panel/vhost/nginx/well-known", 0o755) + + # 如果主配置中,没有引用则尝试添加,添加失败就跳出 + if not re.search(r"\s*include\s+/www/server/panel/vhost/nginx/well-known/.*\.conf;", nginx_conf, re.M): + ssl_line = re.search(r"(#.*\n\s*)?#error_page 404/404\.html;", nginx_conf) + if ssl_line is None: + return + default_cert_apply_check = ( + "#CERT-APPLY-CHECK--START\n" + " # Configuration related to file verification for SSL certificate application - Do not delete\n" + " include /www/server/panel/vhost/nginx/well-known/{}.conf;\n" + " #CERT-APPLY-CHECK--END\n " + ).format(site_name) + if not os.path.exists(file_check_config_path): + public.writeFile(file_check_config_path, "") + + new_conf = nginx_conf.replace(ssl_line.group(), default_cert_apply_check + ssl_line.group(), 1) + public.writeFile(nginx_conf_path, new_conf) + isError = public.checkWebConfig() + if isError is not True: + public.writeFile(nginx_conf_path, nginx_conf) + return + + # 如果主配置有引用, 不再检测位置关系,因为不能保证用户的自定义配置的优先级, 直接进行文件验证的 lua 方式和 if 方式的尝试 + if self.can_use_lua_module(): + self.write_lua_file_for_site(file_check_config_path) + return + + # 开始尝试if 验证方式 + if auth_to not in self._nginx_cache_file_auth: + self._nginx_cache_file_auth[auth_to] = [] + + self._nginx_cache_file_auth[auth_to].append((token, acme_keyauthorization)) + + tmp_data = [] + for token, acme_key in self._nginx_cache_file_auth[auth_to]: + tmp_data.append(( + 'if ($request_uri ~ "^/\\.well-known/acme-challenge/{}.*"){{\n' + ' return 200 "{}";\n' + '}}\n' + ).format(token, acme_key)) + + public.writeFile(file_check_config_path, "\n".join(tmp_data)) + isError = public.checkWebConfig() + if isError is True: + public.serviceReload() + else: + public.writeFile(file_check_config_path, "") + + @staticmethod + def write_lua_file_for_site(file_check_config_path: str): + old_data = public.readFile(file_check_config_path) + if isinstance(old_data, str) and "set_by_lua_block $well_known" in old_data: + return + + lua_file_data = r""" +set $well_known ''; +if ( $uri ~ "^/.well-known/" ) { + set_by_lua_block $well_known { + --get path + local m,err = ngx.re.match(ngx.var.uri,"/.well-known/(.*)","isjo") + -- If the path matches + if m then + -- Splicing file path + local filename = ngx.var.document_root .. m[0] + -- Determine if the file path is legal + if not ngx.re.find(m[1],"\\\\./","isjo") then + -- Determine if the file exists + local is_exists = io.open(filename, "r") + if not is_exists then + -- Java project? + filename = "/www/wwwroot/java_node_ssl" .. m[0] + end + -- release + if is_exists then is_exists:close() end + -- read file + local fp = io.open(filename,'r') + if fp then + local file_body = fp:read("*a") + fp:close() + if file_body then + ngx.header['content-type'] = 'text/plain' + return file_body + end + end + end + end + return "" + } +} + +if ( $well_known != "" ) { + return 200 $well_known; +} +""" + public.writeFile(file_check_config_path, lua_file_data) + isError = public.checkWebConfig() + if isError is True: + public.serviceReload() + else: + public.writeFile(file_check_config_path, old_data) + # 解析域名 def create_dns_record(self, auth_to, domain, dns_value): # 如果为手动解析 if auth_to == 'dns' or auth_to.find('|') == -1: return None - if not self._dns_class: - import panelDnsapi - dns_name, key, secret = self.get_dnsapi(auth_to) - self._dns_class = getattr(panelDnsapi, dns_name)(key, secret) + + import panelDnsapi + dns_name, key, secret = self.get_dnsapi(auth_to) + self._dns_class = getattr(panelDnsapi, dns_name)(key, secret) self._dns_class.create_dns_record(public.de_punycode(domain), dns_value) self._dns_domains.append({"domain": domain, "dns_value": dns_value}) + return - # 解析DNSAPI信息 + # # 如果为手动解析 + # if auth_to == 'dns' : + # return None + + # from panelDnsapi import DnsMager + + # self._dns_class = DnsMager().get_dns_obj_by_domain(domain) + # self._dns_class.create_dns_record(public.de_punycode(domain), dns_value) + # self._dns_domains.append({"domain": domain, "dns_value": dns_value}) + + # 解析DNSAPI信息 # 不再使用的 def get_dnsapi(self, auth_to): tmp = auth_to.split('|') dns_name = tmp[0] @@ -543,12 +840,14 @@ class acme_v2: public.de_punycode(dns_info['domain']), dns_info['dns_value']) except: pass - # 验证域名 def auth_domain(self, index): - if not index in self._config['orders']: + if index not in self._config['orders']: raise Exception(public.get_msg_gettext('The specified order does not exist!')) + if "auths" not in self._config['orders'][index]: + raise Exception(public.get_msg_gettext('Order verification information is missing, please try reapplying!')) + # 开始验证 for auth in self._config['orders'][index]['auths']: res = self.check_auth_status(auth['url']) # 检查是否需要验证 @@ -565,7 +864,7 @@ class acme_v2: # 检查验证结果 for i in range(len(self._config['orders'][index]['auths'])): self.check_auth_status(self._config['orders'][index]['auths'][i]['url'], [ - 'valid', 'invalid']) + 'valid', 'invalid']) self._config['orders'][index]['status'] = 'valid' # 检查验证状态 @@ -596,7 +895,7 @@ class acme_v2: except: ret_title = str(a_auth) raise StopIteration( - "{} >>>> {}".format( + "{0} >>>> {1}".format( ret_title, json.dumps(a_auth) ) @@ -627,6 +926,9 @@ class acme_v2: elif error.find("The domain name belongs") >= 0: return public.get_msg_gettext( 'The domain name does not belong to this DNS service provider, please make sure the domain name is filled in correctly.') + + elif error.find("domains in the last 168 hours") != -1 and error.find("Error creating new order") != -1: + return public.get_msg_gettext("Issuance failed, the root domain name of domain name %s exceeds the maximum weekly issuance limit!" % re.findall(r"hours:\s+(.+?),", error)) elif error.find('login token ID is invalid') >= 0: return public.get_msg_gettext('DNS server connection failed, please check if the key is correct.') elif error.find('Error getting validation data') != -1: @@ -702,7 +1004,7 @@ class acme_v2: # 发送验证请求 def respond_to_challenge(self, auth): - payload = {"keyAuthorization": "{}".format( + payload = {"keyAuthorization": "{0}".format( auth['acme_keyauthorization'])} respond_to_challenge_response = self.acme_request( auth['dns_challenge_url'], payload) @@ -754,14 +1056,14 @@ class acme_v2: cert['cert_timeout'] = self.get_cert_timeout(cert['cert']) cert['private_key'] = self._config['orders'][index]['private_key'] cert['domains'] = self._config['orders'][index]['domains'] - del (self._config['orders'][index]['private_key']) - del (self._config['orders'][index]['auths']) - del (self._config['orders'][index]['expires']) - del (self._config['orders'][index]['authorizations']) - del (self._config['orders'][index]['finalize']) - del (self._config['orders'][index]['identifiers']) + del(self._config['orders'][index]['private_key']) + del(self._config['orders'][index]['auths']) + del(self._config['orders'][index]['expires']) + del(self._config['orders'][index]['authorizations']) + del(self._config['orders'][index]['finalize']) + del(self._config['orders'][index]['identifiers']) if 'cert' in self._config['orders'][index]: - del (self._config['orders'][index]['cert']) + del(self._config['orders'][index]['cert']) self._config['orders'][index]['status'] = 'valid' self._config['orders'][index]['cert_timeout'] = cert['cert_timeout'] domain_name = self._config['orders'][index]['domains'][0] @@ -775,6 +1077,9 @@ class acme_v2: # 保存证书到文件 def save_cert(self, cert, index): try: + from ssl_manage import SSLManger + SSLManger().save_by_data(cert['cert'] + cert['root'], cert['private_key']) + domain_name = self._config['orders'][index]['domains'][0] path = self._config['orders'][index]['save_path'] if not os.path.exists(path): @@ -789,8 +1094,13 @@ class acme_v2: public.writeFile(path + "/root_cert.csr", cert['root']) # 转为IIS证书 - pfx_buffer = self.dump_pkcs12( - cert['private_key'], cert['cert'] + cert['root'], cert['root'], domain_name) + try: + pfx_buffer = self.dump_pkcs12( + cert['private_key'], cert['cert'] + cert['root'], cert['root'], domain_name) + except: + import ssl_info + pfx_buffer = ssl_info.ssl_info().dump_pkcs12_new( + cert['private_key'], cert['cert'] + cert['root'], cert['root'], domain_name) public.writeFile(path + "/fullchain.pfx", pfx_buffer, 'wb+') ps = '''Document description: @@ -810,21 +1120,22 @@ fullchain.pem Paste into certificate input box write_log(public.get_error_info()) # 通过域名获取网站名称 - def get_site_name_by_domains(self, domains): + def get_site_name_by_domains(self,domains): sql = public.M('domain') site_sql = public.M('sites') - siteName = None + siteName, project_type = None, None for domain in domains: - pid = sql.where('name=?', domain).getField('pid') + pid = sql.where('name=?',domain).getField('pid') if pid: - siteName = site_sql.where('id=?', pid).getField('name') + site_data = site_sql.where('id=?', pid).field('name,project_type').find() + siteName, project_type = site_data["name"], site_data["project_type"] break - return siteName + return siteName, project_type # 替换服务器上的同域名同品牌证书 def sub_all_cert(self, key_file, pem_file): cert_init = self.get_cert_init(pem_file) # 获取新证书的基本信息 - paths = ['/www/server/panel/vhost/cert', '/www/server/panel/vhost/ssl', '/www/server/panel'] + paths = ['/www/server/panel/vhost/cert', '/www/server/panel/vhost/ssl','/www/server/panel'] is_panel = False for path in paths: if not os.path.exists(path): @@ -836,25 +1147,18 @@ fullchain.pem Paste into certificate input box to_info = to_path + '/info.json' # 判断目标证书是否存在 if not os.path.exists(to_pem_file): - if p_name not in ['ssl']: continue + if not p_name in ['ssl']: continue to_pem_file = to_path + '/certificate.pem' to_key_file = to_path + '/privateKey.pem' if not os.path.exists(to_pem_file): continue - # _by_panel None 时通过面板请求时不即使续签面板证书也不重启面板以免导致后续请求出错 - if not os.path.exists('{}/data/ssl.pl'.format(public.get_panel_path())): - continue - if not self._by_panel: - is_panel = True # 获取目标证书的基本信息 to_cert_init = self.get_cert_init(to_pem_file) # 判断证书品牌是否一致 try: - if to_cert_init['issuer'] != cert_init['issuer'] and to_cert_init['issuer'].find( - "Let's Encrypt") == -1 and to_cert_init['issuer'] != 'R3': + if to_cert_init['issuer'] != cert_init['issuer'] and to_cert_init['issuer'].find("Let's Encrypt") == -1 and to_cert_init['issuer'] != 'R3': continue - except: - continue + except: continue # 判断目标证书的到期时间是否较早 if to_cert_init['notAfter'] > cert_init['notAfter']: continue @@ -877,24 +1181,27 @@ fullchain.pem Paste into certificate input box write_log(public.get_msg_gettext( '|-Detected that the certificate under {} overlaps with the certificate of this application and has an earlier expiration time, and has been replaced with a new certificate!', (to_path,))) + if path == paths[-1]: is_panel = True + # 重载web服务 public.serviceReload() - #if is_panel: public.restart_panel() + # if is_panel: public.restart_panel() # 检查指定证书是否在订单列表 def check_order_exists(self, pem_file): try: cert_init = self.get_cert_init(pem_file) - if not cert_init: return None + if not cert_init: + return None + if not (cert_init['issuer'].find("Let's Encrypt") != -1 or cert_init['issuer'] == 'R3'): + return None for index in self._config['orders'].keys(): if not 'save_path' in self._config['orders'][index]: continue for domain in self._config['orders'][index]['domains']: if domain in cert_init['dns']: return index - if cert_init['issuer'].find("Let's Encrypt") != -1 or cert_init['issuer'] == 'R3': - return pem_file - return None + return pem_file except: return None @@ -907,13 +1214,10 @@ fullchain.pem Paste into certificate input box cert_init = self.get_cert_init(args.pem_file) if not cert_init: return public.return_msg_gettext(False, 'Certificate information acquisition failed!') - api_path = './config/dns_api.json' - api_init = './config/dns_api_init.json' - if not os.path.exists(api_path): - if os.path.exists(api_init): - import shutil - shutil.copyfile(api_init, api_path) - cert_init['dnsapi'] = json.loads(public.readFile(self._dnsapi_file)) + try: + cert_init['dnsapi'] = json.loads(public.readFile(self._dnsapi_file)) + except: + cert_init['dnsapi'] = [] return cert_init # 获取指定证书基本信息 @@ -959,8 +1263,7 @@ fullchain.pem Paste into certificate input box else: result['subject'] = result['dns'][0] return result - except: - return None + except: return None # 转换时间 def strf_date(self, sdate): @@ -969,11 +1272,13 @@ fullchain.pem Paste into certificate input box # 证书转为DER def dump_der(self, cert_path): cert = OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, public.readFile(cert_path + '/cert.csr')) + OpenSSL.crypto.FILETYPE_PEM, public.readFile(cert_path+'/cert.csr')) return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert) # 证书转为pkcs12 def dump_pkcs12(self, key_pem=None, cert_pem=None, ca_pem=None, friendly_name=None): + # from cryptography.hazmat.primitives.serialization import pkcs12 + p12 = OpenSSL.crypto.PKCS12() if cert_pem: p12.set_certificate(OpenSSL.crypto.load_certificate( @@ -989,7 +1294,7 @@ fullchain.pem Paste into certificate input box return p12.export() # 拆分根证书 - def split_ca_data(self, cert): + def split_ca_data(self,cert): sp_key = '-----END CERTIFICATE-----\n' datas = cert.split(sp_key) return {"cert": datas[0] + sp_key, "root": sp_key.join(datas[1:])} @@ -1039,11 +1344,11 @@ fullchain.pem Paste into certificate input box X509Req = OpenSSL.crypto.X509Req() X509Req.get_subject().CN = domain_name if domain_alt_names: - SAN = "DNS:{}, ".format(domain_name).encode("utf8") + ", ".join( + SAN = "DNS:{0}, ".format(domain_name).encode("utf8") + ", ".join( "DNS:" + i for i in domain_alt_names ).encode("utf8") else: - SAN = "DNS:{}".format(domain_name).encode("utf8") + SAN = "DNS:{0}".format(domain_name).encode("utf8") X509Req.add_extensions( [ @@ -1072,7 +1377,7 @@ fullchain.pem Paste into certificate input box acme_thumbprint = self.calculate_safe_base64( hashlib.sha256(acme_header_jwk_json.encode("utf8")).digest() ) - acme_keyauthorization = "{}.{}".format(token, acme_thumbprint) + acme_keyauthorization = "{0}.{1}".format(token, acme_thumbprint) base64_of_acme_keyauthorization = self.calculate_safe_base64( hashlib.sha256(acme_keyauthorization.encode("utf8")).digest() ) @@ -1152,7 +1457,7 @@ fullchain.pem Paste into certificate input box # 获取kid def get_kid(self, force=False): - # 如果配置文件中不存在kid或force = True时则重新注册新的acme帐户 + #如果配置文件中不存在kid或force = True时则重新注册新的acme帐户 if not 'account' in self._config: self._config['account'] = {} k = self._mod_index[self._debug] @@ -1169,13 +1474,13 @@ fullchain.pem Paste into certificate input box # 注册acme帐户 def register(self, existing=False): if not 'email' in self._config: - self._config['email'] = 'demo@bt.cn' + self._config['email'] = 'demo@aapanel.com' if existing: payload = {"onlyReturnExisting": True} elif self._config['email']: payload = { "termsOfServiceAgreed": True, - "contact": ["mailto:{}".format(self._config['email'])], + "contact": ["mailto:{0}".format(self._config['email'])], } else: payload = {"termsOfServiceAgreed": True} @@ -1199,15 +1504,15 @@ fullchain.pem Paste into certificate input box protected = self.get_acme_header(url) protected64 = self.calculate_safe_base64(json.dumps(protected)) signature = self.sign_message( - message="{}.{}".format(protected64, payload64)) # bytes + message="{0}.{1}".format(protected64, payload64)) # bytes signature64 = self.calculate_safe_base64(signature) # str data = json.dumps( {"protected": protected64, "payload": payload64, - "signature": signature64} + "signature": signature64} ) headers.update({"Content-Type": "application/jose+json"}) response = requests.post( - url, data=data.encode("utf8"), timeout=self._acme_timeout, headers=headers, verify=self._verify + url, data=data.encode("utf8"), timeout=self._acme_timeout, headers=headers, verify=self._verify,s_type=self._request_type ) # 更新随机数 self.update_replay_nonce(response) @@ -1241,7 +1546,8 @@ fullchain.pem Paste into certificate input box self._apis['newNonce'], timeout=self._acme_timeout, headers=headers, - verify=self._verify + verify=self._verify, + s_type=self._request_type ) self._replay_nonce = response.headers["Replay-Nonce"] return self._replay_nonce @@ -1260,7 +1566,7 @@ fullchain.pem Paste into certificate input box public_key_public_numbers = private_key.public_key().public_numbers() exponent = "{0:x}".format(public_key_public_numbers.e) - exponent = "0{}".format(exponent) if len( + exponent = "0{0}".format(exponent) if len( exponent) % 2 else exponent modulus = "{0:x}".format(public_key_public_numbers.n) jwk = { @@ -1320,7 +1626,7 @@ fullchain.pem Paste into certificate input box # 写配置文件 def save_config(self): - fp = open(self._conf_file, 'w+') + fp = open(self._conf_file_v2, 'w+') fcntl.flock(fp, fcntl.LOCK_EX) # 加锁 fp.write(json.dumps(self._config)) fcntl.flock(fp, fcntl.LOCK_UN) # 解锁 @@ -1329,16 +1635,16 @@ fullchain.pem Paste into certificate input box # 读配置文件 def read_config(self): - if not os.path.exists(self._conf_file): + if not os.path.exists(self._conf_file_v2): self._config['orders'] = {} self._config['account'] = {} self._config['apis'] = {} - self._config['email'] = public.M('config').where('id=?', (1,)).getField('email') - if self._config['email'] in ['287962566@qq.com']: + self._config['email'] = public.M('config').where('id=?',(1,)).getField('email') + if self._config['email'] in [public.en_hexb('4d6a67334f5459794e545932514846784c6d4e7662513d3d')]: self._config['email'] = None self.save_config() return self._config - tmp_config = public.readFile(self._conf_file) + tmp_config = public.readFile(self._conf_file_v2) if not tmp_config: return self._config try: @@ -1356,8 +1662,6 @@ fullchain.pem Paste into certificate input box index = None if 'index' in args: index = args['index'] - if 'auto_wildcard' in args: - self._auto_wildcard = 1 if not index: # 判断是否只想验证域名 write_log(public.get_msg_gettext('|-Creating order..')) index = self.create_order(domains, auth_type, auth_to) @@ -1389,223 +1693,121 @@ fullchain.pem Paste into certificate input box # 申请证书 - api def apply_cert_api(self, args): - # 在面板点击申请证书时不要重启面板以防后续请求出错 - self._by_panel = True - # 是否为指定站点 - if public.M('sites').where('id=? and project_type=?', (args.id, 'Java')).count(): - project_info = public.M('sites').where('id=?', (args.id,)).getField('project_config') - try: - project_info = json.loads(project_info) - if not 'ssl_path' in project_info: - return public.return_msg_gettext(False, - 'There is a problem with the current Java project configuration file, please rebuild') - if not os.path.exists(project_info['ssl_path']): - os.makedirs(project_info['ssl_path']) - path = project_info['ssl_path'] - args.auth_to = path - check_result = self.check_auth_env(args) - if check_result: return check_result + """ + @name 申请证书 + @param domains: list 域名列表 + @param auth_type: str 认证方式 + @param auth_to: str 认证路径 + @param auto_wildcard: str 是否自动组合泛域名 + """ + if not 'id' in args: + return public.return_msg_gettext(False,'Website ID cannot be empty!') - if args.auto_wildcard == '1': - self._auto_wildcard = True - self.turnon_redirect_proxy_httptohttps(args) - return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to) + if 'auto_wildcard' in args and args.auto_wildcard == '1': + self._auto_wildcard = True + + find = public.M('sites').where('id=?', (args.id,)).find() + if not find: + return public.return_msg_gettext(False, "Website lost, unable to continue applying for certificate") + + if args.auth_type in ['http', 'tls']: + if not self.can_use_base_file_check(find["name"], find["project_type"]): + webserver: str = public.get_webserver() + msg = "The service ({}) configuration file of the current project has been modified and does not support file verification. Please choose another method or restore the configuration file".format(webserver.title()) + if webserver != 'nginx': + return public.return_msg_gettext(False, msg) + # nginx 检测其他两种方案的可行性 + if not self.can_use_lua_for_site(find["name"], find["project_type"]) and \ + not self.can_use_if_for_file_check(find["name"], find["project_type"]): + + return public.return_msg_gettext(False, msg) + else: + return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to) + + # 是否为指定站点 + count = public.M('sites').where( + 'id=? and project_type in (?,?,?,?)', + (args.id, 'Java', 'Go', 'Other', "Python") + ).count() + if count: + try: + project_info = json.loads(find['project_config']) + if 'ssl_path' not in project_info: + ssl_path = '/www/wwwroot/java_node_ssl' + else: + ssl_path = project_info['ssl_path'] + if not os.path.exists(ssl_path): + os.makedirs(ssl_path) + + args.auth_to = ssl_path except: - return public.return_msg_gettext(False, - 'There is a problem with the current Java project configuration file, please rebuild') - finally: - self.turnon_redirect_proxy_httptohttps(args) + return public.return_msg_gettext(False, 'There is an issue with the current project configuration file, please rebuild it') else: if re.match(r"^\d+$", args.auth_to): import panelSite - path = public.M('sites').where('id=?', (args.id,)).getField('path') - args.auth_to = path + '/' + panelSite.panelSite().GetRunPath(args) + args.auth_to = find['path'] + '/' + panelSite.panelSite().GetRunPath(args) args.auth_to = args.auth_to.replace("//", "/") if args.auth_to[-1] == '/': args.auth_to = args.auth_to[:-1] if not os.path.exists(args.auth_to): - self.turnon_redirect_proxy_httptohttps(args) - return public.return_msg_gettext(False, - 'Invalid site directory, please check if the specified site exists!') + return public.return_msg_gettext(False, 'Invalid site directory, please check if the specified site exists!') - check_result = self.check_auth_env(args, check=True) - if check_result: return check_result - if args.auto_wildcard == '1': - self._auto_wildcard = True - res = self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to) - if os.path.exists(self._stop_rp_file): - self.turnon_redirect_proxy_httptohttps(args) - return res + # 检查认证环境 + check_result = self.check_auth_env(args) + if check_result: + return check_result - def turnon_redirect_proxy_httptohttps(self, args): - import panelSite - s = panelSite.panelSite() - if not 'siteName' in args: - args.siteName = public.M('sites').where('id=?', (args.id,)).getField('name') - args.sitename = args.siteName - self.turnon_redirect(args, s) - self.turnon_proxy(args, s) - self.turnon_httptohttps(args, s) - public.serviceReload() - - def turnon_httptohttps(self, args, s): - conf_file = '{}/data/stop_httptohttps.pl'.format(public.get_panel_path()) - if os.path.exists(conf_file): - write_log('|-Turning on http to https') - s.HttpToHttps(args) - try: - os.remove(conf_file) - except: - pass - - def turnon_proxy(self, args, s): - conf_file = '{}/data/stop_p_tmp.pl'.format(public.get_panel_path()) - if not os.path.exists(conf_file): - return - write_log('|-Turning on proxy') - conf = json.loads(public.readFile(conf_file)) - data = s.GetProxyList(args) - for x in data: - if x['sitename'] not in conf: - continue - if x['proxyname'] not in conf[x['sitename']]: - continue - args.type = 1 - args.advanced = x['advanced'] - args.cache = x['cache'] - args.cachetime = x['cachetime'] - args.proxydir = x['proxydir'] - args.proxyname = x['proxyname'] - args.proxysite = x['proxysite'] - args.sitename = x['sitename'] - args.subfilter = json.dumps(x['subfilter']) - args.todomain = x['todomain'] - s.ModifyProxy(args) - try: - os.remove(conf_file) - except: - pass - - def turnon_redirect(self, args, s): - conf_file = '{}/data/stop_r_tmp.pl'.format(public.get_panel_path()) - if not os.path.exists(conf_file): - return - write_log('|-Turning on redirection') - conf = json.loads(public.readFile(conf_file)) - data = s.GetRedirectList(args) - for x in data: - if x['sitename'] not in conf: - continue - if x['redirectname'] not in conf[x['sitename']]: - continue - args.type = 1 - args.sitename = x['sitename'] - args.holdpath = x['holdpath'] - args.redirectname = x['redirectname'] - args.redirecttype = x['redirecttype'] - args.domainorpath = x['domainorpath'] - args.redirectpath = x['redirectpath'] - args.redirectdomain = json.dumps(x['redirectdomain']) - args.tourl = x['tourl'] - s.ModifyRedirect(args) - try: - os.remove(conf_file) - except: - pass + return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to) # 检查认证环境 - def check_auth_env(self, args, check=None): - if not check: - return + def check_auth_env(self,args): for domain in json.loads(args.domains): if public.checkIp(domain): continue - if domain.find('*.') != -1 and args.auth_type in ['http', 'tls']: - raise public.return_msg_gettext(False, - 'Pan domain names cannot apply for a certificate using [File Verification]!') + if domain.find('*.') != -1 and args.auth_type in ['http','tls']: + return public.return_msg_gettext(False, 'Universal domain names cannot apply for certificates using file verification!') + + data = public.M('sites').where('id=?', (args.id,)).find() + if not data: + return public.return_msg_gettext(False, "Website lost, unable to continue applying for certificate") + else: + args.siteName = data['name'] + site_type = data["project_type"] + + use_nginx_conf_to_auth = False + if args.auth_type in ['http', 'tls'] and public.get_webserver() == "nginx": # nginx 在lua验证和可重启的 + if self.can_use_lua_for_site(args.siteName, site_type): + use_nginx_conf_to_auth = True + else: + if self.can_use_if_for_file_check(args.siteName, site_type): + use_nginx_conf_to_auth = True + import panelSite s = panelSite.panelSite() - if args.auth_type in ['http', 'tls']: + if args.auth_type in ['http', 'tls'] and use_nginx_conf_to_auth is False: try: - rp_conf = public.readFile(self._stop_rp_file) - try: - if rp_conf: - rp_conf = json.loads(rp_conf) - except: - write_log('|-Failed to parse configuration file') - if not 'siteName' in args: - args.siteName = public.M('sites').where('id=?', (args.id,)).getField('name') args.sitename = args.siteName data = s.GetRedirectList(args) # 检查重定向是否开启 if type(data) == list: - redirect_tmp = {args.sitename: []} for x in data: - if rp_conf and x['sitename'] in rp_conf: - if str(x['type']) == '0': - continue - args.type = 0 - args.sitename = x['sitename'] - args.holdpath = x['holdpath'] - args.redirectname = x['redirectname'] - args.redirecttype = x['redirecttype'] - args.domainorpath = x['domainorpath'] - args.redirectpath = x['redirectpath'] - args.redirectdomain = json.dumps(x['redirectdomain']) - args.tourl = x['tourl'] - args.notreload = True - write_log("|- Turning off redirection {}".format(args.redirectname)) - s.ModifyRedirect(args) - redirect_tmp[args.sitename].append(x['redirectname']) - else: - if x['type']: return public.return_msg_gettext(False, + if x['type']: return public.return_msg_gettext(False, 'Your site has 301 Redirect on,Please turn it off first!') - if redirect_tmp[args.sitename]: - public.writeFile('{}/data/stop_r_tmp.pl'.format(public.get_panel_path()), - json.dumps(redirect_tmp)) data = s.GetProxyList(args) - # 检查反向代理是否开启 - if type(data) == list: - proxy_tmp = {args.sitename: []} - for x in data: - if rp_conf and x['sitename'] in rp_conf: - if str(x['type']) == '0': - continue - args.type = 0 - args.advanced = x['advanced'] - args.cache = x['cache'] - args.cachetime = x['cachetime'] - args.proxydir = x['proxydir'] - args.proxyname = x['proxyname'] - args.proxysite = x['proxysite'] - args.sitename = x['sitename'] - args.subfilter = json.dumps(x['subfilter']) - args.todomain = x['todomain'] - args.notreload = True - s.ModifyProxy(args) - write_log("|- Turning off proxy {}".format(args.proxyname)) - proxy_tmp[args.sitename].append(x['proxyname']) - else: - if x['type']: return public.return_msg_gettext(False, - 'Sites with reverse proxy turned on cannot apply for SSL!') - if proxy_tmp[args.sitename]: - public.writeFile('{}/data/stop_p_tmp.pl'.format(public.get_panel_path()), json.dumps(proxy_tmp)) + # # 检查反向代理是否开启 + # if type(data) == list: + # for x in data: + # if x['type']: return public.return_msg_gettext(False, + # 'Sites with reverse proxy turned on cannot apply for SSL!') # 检查旧重定向是否开启 data = s.Get301Status(args) if data['status']: return public.return_msg_gettext(False, 'The website has been redirected, please close it before applying!') - # 判断是否强制HTTPS + #判断是否强制HTTPS if s.IsToHttps(args.siteName): - if os.path.exists(self._stop_rp_file): - if rp_conf and args.siteName in rp_conf: - write_log("|- Turning off http to https") - s.CloseToHttps(args) - public.writeFile('{}/data/stop_httptohttps.pl'.format(public.get_panel_path()), '') - else: - return public.return_msg_gettext(False, + return public.return_msg_gettext(False, 'After configuring Force HTTPS, you cannot use [File Verification] to apply for a certificate!') - public.serviceReload() except: return False else: @@ -1617,39 +1819,61 @@ fullchain.pem Paste into certificate input box # DNS手动验证 def apply_dns_auth(self, args): + if not hasattr(args, "index") or not args.index: + return public.return_msg_gettext(False, "Incomplete parameter information, no index parameter [index]") return self.apply_cert([], auth_type='dns', auth_to='dns', index=args.index) - # 创建计划任务 + + #创建计划任务 def set_crond(self): try: echo = public.md5(public.md5('renew_lets_ssl_bt')) - cron_id = public.M('crontab').where('echo=?', (echo,)).getField('id') + find = public.M('crontab').where('echo=?',(echo,)).find() + cron_id = find['id'] if find else None import crontab + import random args_obj = public.dict_obj() if not cron_id: cronPath = public.GetConfigValue('setup_path') + '/cron/' + echo shell = '{} -u /www/server/panel/class/acme_v2.py --renew=1'.format(sys.executable) - public.writeFile(cronPath, shell) - args_obj.id = public.M('crontab').add( - 'name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress', - ("Renew Let's Encrypt Certificate", 'day', '', '0', '10', echo, - time.strftime('%Y-%m-%d %X', time.localtime()), 0, '', 'localhost', 'toShell', '', shell, '')) + public.writeFile(cronPath,shell) + + # 使用随机时间 + hour = random.randint(0, 23) + minute = random.randint(1, 59) + args_obj.id = public.M('crontab').add('name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress',("Renew Let's Encrypt Certificate",'day','',hour,minute,echo,time.strftime('%Y-%m-%d %X',time.localtime()),0,'','localhost','toShell','',shell,'')) crontab.crontab().set_cron_status(args_obj) else: + # 检查任务如果是0点10分执行,改为随机时间 + if find['where_hour'] == 0 and find['where_minute'] == 10: + # print('修改任务时间') + # 使用随机时间 + hour = random.randint(0, 23) + minute = random.randint(1, 59) + public.M('crontab').where('id=?',(cron_id,)).save('where_hour,where_minute,status',(hour,minute,0)) + + # 停用任务 + args_obj.id = cron_id + crontab.crontab().set_cron_status(args_obj) + + # 启用任务 + public.M('crontab').where('id=?',(cron_id,)).setField('status',1) + crontab.crontab().set_cron_status(args_obj) + cron_path = public.get_cron_path() if os.path.exists(cron_path): cron_s = public.readFile(cron_path) if cron_s.find(echo) == -1: - public.M('crontab').where('echo=?', (echo,)).setField('status', 0) + public.M('crontab').where('id=?',(cron_id,)).setField('status',0) args_obj.id = cron_id crontab.crontab().set_cron_status(args_obj) - except: - pass + except:pass + # 获取当前正在使用此证书的网站目录 - def get_ssl_used_site(self, save_path): - pkey_file = '{}/privkey.pem'.format(save_path) + def get_ssl_used_site(self,save_path): + pkey_file = '{}/privkey.pem'.format(save_path) pkey = public.readFile(pkey_file) if not pkey: return False cert_paths = 'vhost/cert' @@ -1657,19 +1881,32 @@ fullchain.pem Paste into certificate input box args = public.dict_obj() args.siteName = '' for c_name in os.listdir(cert_paths): - skey_file = '{}/{}/privkey.pem'.format(cert_paths, c_name) + skey_file = '{}/{}/privkey.pem'.format(cert_paths,c_name) skey = public.readFile(skey_file) if not skey: continue if skey == pkey: args.siteName = c_name + site_info = public.M('sites').where('name=?', c_name).find() + if not site_info or isinstance(site_info, str): + return False + if site_info["project_type"] not in ("PHP", "proxy"): + if not os.path.isdir(site_info["path"]): + return os.path.dirname(site_info["path"]) + else: + return site_info["path"] + run_path = panelSite.panelSite().GetRunPath(args) - if not run_path: continue - sitePath = public.M('sites').where('name=?', c_name).getField('path') - if not sitePath: continue - to_path = "{}/{}".format(sitePath, run_path) + if not run_path: + continue + sitePath = public.M('sites').where('name=?',c_name).getField('path') + if not sitePath: + continue + to_path = "{}/{}".format(sitePath,run_path) + to_path = to_path.replace("//", "/") return to_path return False + def get_site_id(self, domains): site_ids = [] for domain in domains: @@ -1686,28 +1923,13 @@ fullchain.pem Paste into certificate input box return False return site_ids[0] - def get_site_runpath(self, domains): - site_id = self.get_site_id(domains) - if not site_id: - return False - import panelSite - from collections import namedtuple - ps = panelSite.panelSite() - # 构造一个类 - get = namedtuple("get", ["id"]) - get.id = site_id - site_path = public.M('sites').where('id=?', (get.id,)).field('path').select()[0]['path'] - runpath = ps.GetRunPath(get) - return site_path + runpath - def find_site_stopped(self, domains): site_id = self.get_site_id(domains) if not site_id: return False site_status = public.M('sites').where('id=?', (site_id,)).field('status').select()[0]['status'] return site_status - - def get_index(self, domains): + def get_index(self,domains): ''' @name 获取标识 @author hwliang<2022-02-10> @@ -1719,6 +1941,7 @@ fullchain.pem Paste into certificate input box identifiers.append({"type": 'dns', "value": domain_name}) return public.md5(json.dumps(identifiers)) + # 续签同品牌其它证书 def renew_cert_other(self): ''' @@ -1729,33 +1952,31 @@ fullchain.pem Paste into certificate input box cert_path = "{}/vhost/cert".format(public.get_panel_path()) if not os.path.exists(cert_path): return new_time = time.time() + (86400 * 30) - n = 0 + n=0 if not 'orders' in self._config: self._config['orders'] = {} import panelSite siteObj = panelSite.panelSite() args = public.dict_obj() for siteName in os.listdir(cert_path): try: - cert_file = '{}/{}/fullchain.pem'.format(cert_path, siteName) - if not os.path.exists(cert_file): continue # 无证书文件 - siteInfo = public.M('sites').where('name=?', siteName).find() - if not siteInfo: continue # 无网站信息 + cert_file = '{}/{}/fullchain.pem'.format(cert_path,siteName) + if not os.path.exists(cert_file): continue # 无证书文件 + siteInfo = public.M('sites').where('name=?',siteName).find() + if not siteInfo: continue # 无网站信息 cert_init = self.get_cert_init(cert_file) - if not cert_init: continue # 无法获取证书 - end_time = time.mktime(time.strptime(cert_init['notAfter'], '%Y-%m-%d')) - if end_time > new_time: continue # 未到期 + if not cert_init: continue # 无法获取证书 + end_time = time.mktime(time.strptime(cert_init['notAfter'],'%Y-%m-%d')) + if end_time > new_time: continue # 未到期 try: - if not cert_init['issuer'] in ['R3', "Let's Encrypt"] and cert_init['issuer'].find( - "Let's Encrypt") == -1: - continue # 非同品牌证书 - except: - continue + if not cert_init['issuer'] in ['R3',"Let's Encrypt"] and cert_init['issuer'].find("Let's Encrypt") == -1: + continue # 非同品牌证书 + except: continue - if isinstance(cert_init['dns'], str): cert_init['dns'] = [cert_init['dns']] + if isinstance(cert_init['dns'],str): cert_init['dns'] = [cert_init['dns']] index = self.get_index(cert_init['dns']) - if index in self._config['orders'].keys(): continue # 已在订单列表 + if index in self._config['orders'].keys(): continue # 已在订单列表 - n += 1 + n+=1 write_log("|-Renewing additional certificate {}, domain name:{}..".format(n, cert_init['subject'])) write_log("|-Creating order..") args.id = siteInfo['id'] @@ -1765,12 +1986,12 @@ fullchain.pem Paste into certificate input box else: path = siteInfo['path'] - self.renew_cert_to(cert_init['dns'], 'http', path.replace('//', '/')) + self.renew_cert_to(cert_init['dns'],'http',path.replace('//','/')) except: - write_log("|-Renewal failed:") + write_log("|-Renewal failed:") # 关闭强制https - def close_httptohttps(self, siteName): + def close_httptohttps(self,siteName): try: if not siteName: siteName @@ -1786,7 +2007,7 @@ fullchain.pem Paste into certificate input box return False # 恢复强制https - def rep_httptohttps(self, siteName): + def rep_httptohttps(self,siteName): try: if not siteName: return False import panelSite @@ -1799,13 +2020,14 @@ fullchain.pem Paste into certificate input box except: return False - def renew_cert_to(self, domains, auth_type, auth_to, index=None): + + def renew_cert_to(self,domains,auth_type,auth_to,index = None): siteName = None cert = {} if os.path.exists(auth_to): - if public.M('sites').where('path=?', auth_to).count() == 1: - site_id = public.M('sites').where('path=?', auth_to).getField('id') - siteName = public.M('sites').where('path=?', auth_to).getField('name') + if public.M('sites').where('path=?',auth_to).count() == 1: + site_id = public.M('sites').where('path=?',auth_to).getField('id') + siteName = public.M('sites').where('path=?',auth_to).getField('name') import panelSite siteObj = panelSite.panelSite() args = public.dict_obj() @@ -1813,16 +2035,23 @@ fullchain.pem Paste into certificate input box runPath = siteObj.GetRunPath(args) if runPath and not runPath in ['/']: path = auth_to + '/' + runPath - if os.path.exists(path): auth_to = path.replace('//', '/') + if os.path.exists(path): auth_to = path.replace('//','/') else: - siteName = self.get_site_name_by_domains(domains) + siteName, _ = self.get_site_name_by_domains(domains) + + isError = public.checkWebConfig() + if isError is not True and public.get_webserver() == "nginx": + write_log("|- The certificate uses the file verification method, but currently it cannot overload the nginx server configuration file and can only skip renewal.") + write_log("|- The error message in the configuration file is as follows:") + write_log(isError) + is_rep = self.close_httptohttps(siteName) try: index = self.create_order( domains, auth_type, - auth_to.replace('//', '/'), + auth_to.replace('//','/'), index ) @@ -1847,6 +2076,7 @@ fullchain.pem Paste into certificate input box cert['msg'] = 'Renewed successfully!' write_log("|-Renewed successfully!!") except Exception as e: + if str(e).find('please try again later') == -1: # 受其它证书影响和连接CA失败的的不记录重试次数 if index: # 设置下次重试时间 @@ -1865,6 +2095,7 @@ fullchain.pem Paste into certificate input box write_log("-" * 70) return cert + # 续签证书 def renew_cert(self, index): write_log("", "wb+") @@ -1873,9 +2104,7 @@ fullchain.pem Paste into certificate input box if index: if type(index) != str: index = index.index - # 在面板点击申请证书时不要重启面板以防后续请求出错 - self._by_panel = True - if index not in self._config['orders']: + if not index in self._config['orders']: raise Exception( public.get_msg_gettext('The specified order number does not exist and cannot be renewed!')) order_index.append(index) @@ -1889,45 +2118,50 @@ fullchain.pem Paste into certificate input box self._config['orders'][i]['cert_timeout'] = self._config['orders'][i]['cert']['cert_timeout'] if not 'cert_timeout' in self._config['orders'][i]: self._config['orders'][i]['cert_timeout'] = int(time.time()) - if self._config['orders'][i]['cert_timeout'] > s_time or self._config['orders'][i][ - 'auth_to'] == 'dns': + if self._config['orders'][i]['cert_timeout'] > s_time or self._config['orders'][i]['auth_to'] == 'dns': continue if self.find_site_stopped(self._config['orders'][i]['domains']) == '0': write_log("|-The website has been suspended, skip certificate renewal!") continue # 已删除的网站直接跳过续签 - if self._config['orders'][i]['auth_to'].find('|') == -1 and self._config['orders'][i][ - 'auth_to'].find('/') != -1: - #if not os.path.exists(self._config['orders'][i]['auth_to']): - # ^——这个不能判断网站已被删除,但文件夹未删除的问题 + is_file_check = (self._config['orders'][i]['auth_to'].find('|') == -1 or + not self._config['orders'][i]['auth_to'].startswith("dns") + ) and self._config['orders'][i]['auth_to'].find('/') != -1 + if is_file_check: + # if not os.path.exists(self._config['orders'][i]['auth_to']): + # ^^^^^^^^^^——————————这个不能判断网站已被删除的情况下,文件夹未删除时的问题 _auth_to = self.get_ssl_used_site(self._config['orders'][i]['save_path']) - if not _auth_to: continue + if not _auth_to: + continue # 域名不存在? for domain in self._config['orders'][i]['domains']: - if domain.find('*') != -1: break - if not public.M('domain').where("name=?", (domain,)).count() and not public.M( - 'binding').where("domain=?", domain).count(): + if domain.find('*') != -1: + break + if not public.M('domain').where("name=?",(domain,)).count() and not public.M('binding').where("domain=?",domain).count(): _auth_to = None - write_log("|-Skip deleted domains:{}".format(self._config['orders'][i]['domains'])) + write_log("|-Skip deleted domain names: {}".format(self._config['orders'][i]['domains'])) if not _auth_to: continue self._config['orders'][i]['auth_to'] = _auth_to + # 检查网站域名是否存在 + if not public.M('domain').where('`name` IN ({})'.format(', '.join(map(lambda x: "'{}'".format(x), filter(lambda x: x.find('*') < 0, self._config['orders'][i]['domains'])))), ()).count(): + write_log("|-Skip deleted domain names: {}".format(self._config['orders'][i]['domains'])) + continue + # 是否到了允许重试的时间 if 'next_retry_time' in self._config['orders'][i]: timeout = self._config['orders'][i]['next_retry_time'] - int(time.time()) if timeout > 0: - write_log( - '|-The domain name skipped this time: {}, because the last renewal failed, you still need to wait {} hours and try again'.format( - self._config['orders'][i]['domains'], int(timeout / 60 / 60))) + write_log('|-Skipping domain name: {} this time, due to last renewal failure, we need to wait {} hours before trying again'.format(self._config['orders'][i]['domains'],int(timeout / 60 / 60))) continue # # 是否到了最大重试次数 # if 'retry_count' in self._config['orders'][i]: # if self._config['orders'][i]['retry_count'] >= 5: - # write_log('|-Skip the domain name this time: {}, this certificate will not be renewed due to failure to renew 5 times in a row, (The number of errors will be reset after manual renewal and success)'.format(self._config['orders'][i]['domains'])) + # write_log('|-本次跳过域名:{},因连续5次续签失败,不再续签此证书(可尝试手动续签此证书,成功后错误次数将被重置)'.format(self._config['orders'][i]['domains'])) # continue # 加入到续签订单 @@ -1938,52 +2172,24 @@ fullchain.pem Paste into certificate input box self.renew_cert_other() write_log("|-All tasks have been processed!") return - write_log( - public.get_msg_gettext('|-A total of {} certificates need to be renewed', (str(len(order_index)),))) + write_log("|-A total of {} certificates need to be renewed".format(len(order_index))) n = 0 self.get_apis() cert = None - args = public.to_dict_obj({}) for index in order_index: - args.domains = json.dumps(self._config['orders'][index]['domains']) - args.auth_type = self._config['orders'][index]['auth_type'] - args.auth_to = self._config['orders'][index]['auth_to'] - sitename = args.auth_to.split('/')[-1] - if not sitename: - sitename = self._config['orders'][index]['auth_to'].split('/')[-2] - args.siteName = sitename - write_log('|-Renew the visa certificate and start checking the environment') - self.check_auth_env(args, check=True) n += 1 - domains = _test_domains(self._config['orders'][index]['domains'], self._config['orders'][index]['auth_to'],self._config['orders'][index]['auth_type']) if len(domains) == 0: - write_log("|-The domain name under the {} certificate is not used (the domain name is: [%s]) and has been skipped.".format(n, ",".join(self._config['orders'][index]['domains']))) + write_log("|-The domain names under the {} certificate are all unused (these domains are: [%s]) and have been skipped.".format(n, ",".join(self._config['orders'][index]['domains']))) continue else: self._config['orders'][index]['domains'] = domains write_log(public.get_msg_gettext('|-Renewing certificate number of {},domain: {}..', - (str(n), str(self._config['orders'][index]['domains'])))) + (n, str(self._config['orders'][index]['domains'])))) write_log(public.get_msg_gettext('|-Creating order..')) - - cert = self.renew_cert_to(self._config['orders'][index]['domains'], - self._config['orders'][index]['auth_type'], - self._config['orders'][index]['auth_to'], index) - - # write_log(public.get_msg_gettext('|-Renewing certificate number of {},domain: {}..', - # (str(n), str(self._config['orders'][index]['domains'])))) - # write_log(public.get_msg_gettext('|-Creating order..')) - # cert = self.renew_cert_to(self._config['orders'][index]['domains'], - # self._config['orders'][index]['auth_type'], - # self._config['orders'][index]['auth_to'], index) - - # aapanel 用 - try: - self.turnon_redirect_proxy_httptohttps(args) - except: - pass - + cert = self.renew_cert_to(self._config['orders'][index]['domains'],self._config['orders'][index]['auth_type'],self._config['orders'][index]['auth_to'],index) return cert + except Exception as ex: self.remove_dns_record() ex = str(ex) @@ -1999,7 +2205,7 @@ fullchain.pem Paste into certificate input box def _test_domains(domains, auth_to, auth_type): # 检查站点域名变更情况, 若有删除域名,则在续签时,删除已经不使用的域名,再执行续签任务 # 是dns验证的跳过 - if auth_to.find("|") != -1: + if auth_to.find("|") != -1 or auth_to.startswith("dns#@"): return domains # 是泛域名的跳过 for domain in domains: @@ -2017,6 +2223,7 @@ def _test_domains(domains, auth_to, auth_type): if bool(site_id) and str(site_id).isdigit(): site_domains = [i["name"] for i in sql.where('pid=?',(site_id,)).field("name").select()] else: + # 全都查询不到,认为这个站点已经被删除 return [] del_domains = list(set(domains) - set(site_domains)) @@ -2043,10 +2250,9 @@ def write_log(log_str, mode="ab+"): f.close() return True - +# todo:兼容控制台,目前不兼容 if __name__ == "__main__": import argparse - p = argparse.ArgumentParser(usage=public.get_msg_gettext( 'Required parameters: --domain list of domain names, multiple separated by commas!')) p.add_argument('--domain', default=None, @@ -2164,7 +2370,6 @@ if __name__ == "__main__": write_log("=" * 65) write_log(public.get_msg_gettext('|-Certificate obtained successfully!')) write_log("=" * 65) - write_log(public.get_msg_gettext('Certified Domain Name: {}', (','.join(cert['domains']),))) write_log( public.get_msg_gettext('Certificate expiration time: {}', (public.format_date(times=cert['cert_timeout']),))) write_log(public.get_msg_gettext('Certificate saved at: {}/', (cert['save_path'],))) diff --git a/class/ajax.py b/class/ajax.py index 3c8acbef..cdf7f8ee 100755 --- a/class/ajax.py +++ b/class/ajax.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- from flask import session,request @@ -794,19 +794,19 @@ class ajax: if not os.path.exists(filename): return public.return_msg_gettext(False,'Requested PHP version does NOT exist!') phpini = public.readFile(filename) data = {} - rep = "disable_functions\s*=\s{0,1}(.*)\n" + rep = "disable_functions\\s*=\\s{0,1}(.*)\n" tmp = re.search(rep,phpini) if tmp: data['disable_functions'] = tmp.groups()[0] - rep = "upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)" + rep = r"upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)" tmp = re.search(rep,phpini) if tmp: data['max'] = tmp.groups()[0] - rep = u"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + rep = u"\n;*\\s*cgi\\.fix_pathinfo\\s*=\\s*([0-9]+)\\s*\n" tmp = re.search(rep,phpini) if tmp: if tmp.groups()[0] == '0': @@ -1048,7 +1048,7 @@ class ajax: #AUTH_END """ # nginx配置文件 - ssl_conf = """server + ssl_conf = r"""server { listen 887 ssl; server_name phpmyadmin; @@ -1095,7 +1095,7 @@ class ajax: #AUTH_END """ # apache配置 - ssl_conf = '''Listen 887 + ssl_conf = r'''Listen 887 ServerAdmin webmaster@example.com DocumentRoot "/www/server/phpmyadmin" @@ -1134,6 +1134,13 @@ class ajax: '''.format(public.get_php_proxy(v["ext"]["phpversion"],'apache'),auth) public.writeFile("/www/server/panel/vhost/apache/phpmyadmin.conf", ssl_conf) + + import firewalls + fw = firewalls.firewalls() + fw.AddAcceptPort(public.to_dict_obj({ + 'port': '887', + 'ps': public.get_msg_gettext('New phpMyAdmin SSL Port'), + })) else: if os.path.exists("/www/server/panel/vhost/nginx/phpmyadmin.conf"): os.remove("/www/server/panel/vhost/nginx/phpmyadmin.conf") @@ -1331,9 +1338,9 @@ class ajax: conf = public.readFile('/etc/init.d/memcached') result = {} result['bind'] = re.search('IP=(.+)',conf).groups()[0] - result['port'] = int(re.search('PORT=(\d+)',conf).groups()[0]) - result['maxconn'] = int(re.search('MAXCONN=(\d+)',conf).groups()[0]) - result['cachesize'] = int(re.search('CACHESIZE=(\d+)',conf).groups()[0]) + result['port'] = int(re.search(r'PORT=(\d+)',conf).groups()[0]) + result['maxconn'] = int(re.search(r'MAXCONN=(\d+)',conf).groups()[0]) + result['cachesize'] = int(re.search(r'CACHESIZE=(\d+)',conf).groups()[0]) tn = telnetlib.Telnet(result['bind'],result['port']) tn.write(b"stats\n") tn.write(b"quit\n") @@ -1358,9 +1365,9 @@ class ajax: confFile = '/etc/init.d/memcached' conf = public.readFile(confFile) conf = re.sub('IP=.+','IP='+get.ip,conf) - conf = re.sub('PORT=\d+','PORT='+get.port,conf) - conf = re.sub('MAXCONN=\d+','MAXCONN='+get.maxconn,conf) - conf = re.sub('CACHESIZE=\d+','CACHESIZE='+get.cachesize,conf) + conf = re.sub(r'PORT=\d+','PORT='+get.port,conf) + conf = re.sub(r'MAXCONN=\d+','MAXCONN='+get.maxconn,conf) + conf = re.sub(r'CACHESIZE=\d+','CACHESIZE='+get.cachesize,conf) public.writeFile(confFile,conf) public.ExecShell(confFile + ' reload') return public.return_msg_gettext(True,'Setup successfully!') @@ -1369,8 +1376,8 @@ class ajax: def GetRedisStatus(self,get): import re c = public.readFile('/www/server/redis/redis.conf') - port = re.findall('\n\s*port\s+(\d+)',c)[0] - password = re.findall('\n\s*requirepass\s+(.+)',c) + port = re.findall('\n\\s*port\\s+(\\d+)',c)[0] + password = re.findall('\n\\s*requirepass\\s+(.+)',c) if password: password = ' -a ' + password[0] else: @@ -1431,113 +1438,9 @@ class ajax: if not os.path.exists(get.path): return public.return_msg_gettext(False,'Log file does NOT exist!') return public.returnMsg(True,public.xsssec(public.GetNumLines(get.path,1000))) - def get_pd(self,get): - from BTPanel import cache - tmp = -1 - try: - import panelPlugin - # get = public.dict_obj() - # get.init = 1 - tmp1 = panelPlugin.panelPlugin().get_cloud_list(get) - except: - tmp1 = None - if tmp1: - tmp = tmp1[public.to_string([112, 114, 111])] - ltd = tmp1.get('ltd', -1) - else: - ltd = -1 - tmp4 = cache.get(public.to_string([112, 95, 116, 111, 107, 101, 110])) - if tmp4: - tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4 - if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1') - tmp = public.readFile(tmp_f) - if tmp: tmp = int(tmp) - if not ltd: ltd = -1 - if tmp == None: tmp = -1 - if ltd < 1: - if ltd == -2: - tmp3 = public.to_string( - [60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, 100, - 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, - 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, - 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, - 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, - 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, - 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, - 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, 82, 69, 78, 69, 87, - 60, 47, 97, - 62, 60, 47, 115, 112, 97, 110, 62]) - elif tmp == -1: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, - 116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, 111, 110, 99, 108, 105, 99, - 107, - 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, - 114, - 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107, - 32, 116, 111, 32, - 103, 101, 116, 32, 80, 82, 79, 34, 62, 20813, 36153, 29256, 60, 47, 115, 112, - 97, 110, 62]) - elif tmp == -2: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, - 112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, - 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, - 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, - 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, - 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399, - 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, - 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, - 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114, - 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97, - 110, 62]) - if tmp >= 0 and ltd in [-1, -2]: - if tmp == 0: - - tmp2 = public.to_string([76, 105, 102, 101, 116, 105, 109, 101]) - tmp3 = public.to_string( - [60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, 114, - 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 60, 115, 112, 97, 110, 32, 115, - 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, - 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, - 98, 111, 108, 100, 59, 34, 62, 123, 48, 125, 60, 47, 115, 112, 97, 110, 62, 60, - 47, 115, 112, 97, 110, 62]).format(tmp2) - else: - tmp2 = time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(tmp)) - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, - 112, 114, 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, - 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, - 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, - 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, - 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, - 48, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, - 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, - 99, - 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, - 95, - 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, - 47, 115, 112, 97, 110, 62]).format(tmp2) - else: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, - 114, 111, 45, 103, 114, 97, 121, 34, 32, 111, 110, 99, 108, 105, 99, 107, - 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 112, - 114, 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, - 107, 32, 116, - 111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 70, 82, - 69, 69, 60, 47, 115, 112, 97, 110, 62]) - else: - tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, - 100, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, 97, 110, 32, 115, - 116, - 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, - 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, - 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, - 112, 120, 34, 62, 123, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, - 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, - 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, - 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, - 112, 97, 110, 62]).format( - time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(ltd))) - - return tmp3, tmp, ltd + # 获取授权信息 + def get_pd(self, get): + return public.get_pd(get) #检查用户绑定是否正确 def check_user_auth(self,get): diff --git a/class/apache.py b/class/apache.py index 2d4a34f3..3abb64f7 100644 --- a/class/apache.py +++ b/class/apache.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -52,7 +52,7 @@ class apache: data = {} # 计算启动时间 - Uptime = re.search("ServerUptimeSeconds:\s+(.*)",result) + Uptime = re.search(r"ServerUptimeSeconds:\s+(.*)",result) if not Uptime: return public.return_msg_gettext(False, "Get worker Uptime False") Uptime = int(Uptime.group(1)) @@ -63,11 +63,11 @@ class apache: min = math.floor(min - (days * 60 * 24) - (hours * 60)) #格式化重启时间 - restarttime = re.search("RestartTime:\s+(.*)",result) + restarttime = re.search(r"RestartTime:\s+(.*)",result) if not restarttime: return public.return_msg_gettext(False, "Get worker Restart Time False") restarttime = restarttime.group(1) - rep = "\w+,\s([\w-]+)\s([\d\:]+)\s\w+" + rep = r"\w+,\s([\w-]+)\s([\d\:]+)\s\w+" date = re.search(rep,restarttime) if not date: return public.return_msg_gettext(False, "Get worker date False") @@ -85,28 +85,28 @@ class apache: date = date.split("-") date = "%s-%s-%s" % (date[2],date[1],date[0]) - reqpersec = re.search("ReqPerSec:\s+(.*)", result) + reqpersec = re.search(r"ReqPerSec:\s+(.*)", result) if not reqpersec: return public.return_msg_gettext(False, "Get worker reqpersec False") reqpersec = reqpersec.group(1) - if re.match("^\.", reqpersec): + if re.match(r"^\.", reqpersec): reqpersec = "%s%s" % (0,reqpersec) data["RestartTime"] = "%s %s" % (date,timedetail) data["UpTime"] = "%s day %s hour %s minute" % (str(int(days)),str(int(hours)),str(int(min))) - total_acc = re.search("Total Accesses:\s+(\d+)",result) + total_acc = re.search(r"Total Accesses:\s+(\d+)",result) if not total_acc: return public.return_msg_gettext(False, "Get worker TotalAccesses False") data["TotalAccesses"] = total_acc.group(1) - total_kb = re.search("Total kBytes:\s+(\d+)",result) + total_kb = re.search(r"Total kBytes:\s+(\d+)",result) if not total_kb: return public.return_msg_gettext(False, "Get worker TotalKBytes False") data["TotalKBytes"] = total_kb.group(1) data["ReqPerSec"] = round(float(reqpersec), 2) - busywork = re.search("BusyWorkers:\s+(\d+)",result) + busywork = re.search(r"BusyWorkers:\s+(\d+)",result) if not busywork: return public.return_msg_gettext(False, "Get worker BusyWorkers False") data["BusyWorkers"] = busywork.group(1) - idlework = re.search("IdleWorkers:\s+(\d+)",result) + idlework = re.search(r"IdleWorkers:\s+(\d+)",result) if not idlework: return public.return_msg_gettext(False, "Get worker IdleWorkers False") data["IdleWorkers"] = idlework.group(1) @@ -119,7 +119,7 @@ class apache: apachempmcontent = public.readFile(self.apachempmfile) if not "mpm_event_module" in apachempmcontent: return public.returnMsg(False,"mpm_event_module conf not found or /www/server/apache/conf/extra/httpd-mpm.conf is empty") - apachempmcontent = re.search("\(\n|.)+?\",apachempmcontent).group() + apachempmcontent = re.search(r"\(\n|.)+?\",apachempmcontent).group() ps = ["%s,%s" % (public.get_msg_gettext('Second'),public.get_msg_gettext('Request timeout')), public.get_msg_gettext('Keep alive'), "%s,%s" % (public.get_msg_gettext('Second'),public.get_msg_gettext('Connection timeout')), @@ -131,7 +131,7 @@ class apache: conflist = [] n = 0 for i in gets: - rep = "(%s)\s+(\w+)" % i + rep = r"(%s)\s+(\w+)" % i k = re.search(rep, apachedefaultcontent) if not k: return public.return_msg_gettext(False, "Get Key {} False",(i,)) @@ -154,7 +154,7 @@ class apache: gets = ["StartServers","MaxSpareThreads","MinSpareThreads","ThreadsPerChild","MaxRequestWorkers","MaxConnectionsPerChild"] n = 0 for i in gets: - rep = "(%s)\s+(\w+)" % i + rep = r"(%s)\s+(\w+)" % i k = re.search(rep, apachempmcontent) if not k: return public.return_msg_gettext(False, "Get Key {} False",(i,)) @@ -175,7 +175,7 @@ class apache: if not "mpm_event_module" in apachempmcontent: return public.return_msg_gettext(False,"mpm_event_module conf not found or /www/server/apache/conf/extra/httpd-mpm.conf is empty") conflist = [] - getdict = get.__dict__ + getdict = get.get_items() for i in getdict.keys(): if i != "__module__" and i != "__doc__" and i != "data" and i != "args" and i != "action": getpost = { @@ -189,11 +189,11 @@ class apache: return public.return_msg_gettext(False, 'Parameter ERROR!') else: print(c["value"]) - if not re.search("\d+", c["value"]): + if not re.search(r"\d+", c["value"]): print(c["name"],c["value"]) return public.return_msg_gettext(False, 'Parameter ERROR!') - rep = "%s\s+\w+" % c["name"] + rep = r"%s\s+\w+" % c["name"] if re.search(rep,apachedefaultcontent): newconf = "%s %s" % (c["name"],c["value"]) apachedefaultcontent = re.sub(rep,newconf,apachedefaultcontent) @@ -214,7 +214,7 @@ class apache: def add_httpd_access_log_format(self,args): ''' @name 添加httpd日志格式 - @author zhwen + @author zhwen @param log_format 需要设置的日志格式["$server_name","$remote_addr","-"....] @param log_format_name @param act 操作方式 add/edit @@ -244,13 +244,13 @@ class apache: def del_httpd_access_log_format(self,args): ''' @name 删除日志格式 - @author zhwen + @author zhwen @param log_format_name ''' conf = public.readFile(self.httpdconf) if not conf: return public.return_msg_gettext(False, 'Configuration file not exist') - reg = '\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name) + reg = r'\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name) conf = re.sub(reg,'',conf) self._del_format_log_of_website(args.log_format_name) public.writeFile(self.httpdconf,conf) @@ -270,7 +270,7 @@ class apache: if not site_format_log_status[s]: continue website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(s) - format_exist_reg = 'CustomLog\s+"/www.*"\s+{}'.format(log_format_name) + format_exist_reg = r'CustomLog\s+"/www.*"\s+{}'.format(log_format_name) conf = public.readFile(website_conf_file) if not conf:continue if not re.search(format_exist_reg,conf):continue @@ -323,7 +323,7 @@ class apache: format_name = [i.split('LOG_FORMAT_BEGIN_')[-1] for i in data] format_log = {} for i in format_name: - format_reg = "#LOG_FORMAT_BEGIN_{n}(\n|.)+LogFormat\s+\'(.*)\'\s+{n}".format(n=i) + format_reg = r"#LOG_FORMAT_BEGIN_{n}(\n|.)+LogFormat\s+\'(.*)\'\s+{n}".format(n=i) tmp = re.search(format_reg,conf) if not tmp: continue @@ -336,7 +336,7 @@ class apache: def set_httpd_format_log_to_website(self,args): ''' @name 设置网站日志格式 - @author zhwen + @author zhwen @param sites aaa.com,bbb.com @param log_format_name ''' @@ -344,13 +344,13 @@ class apache: sites = loads(args.sites) try: all_site = public.M('sites').field('name').select() - reg = 'CustomLog\s+"/www.*{}\s*'.format(args.log_format_name) + reg = r'CustomLog\s+"/www.*{}\s*'.format(args.log_format_name) for site in all_site: website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(site['name']) conf = public.readFile(website_conf_file) if not conf: return public.return_msg_gettext(False, 'Configuration file not exist') - format_exist_reg = '(CustomLog\s+"/www.*\_log).*' + format_exist_reg = r'(CustomLog\s+"/www.*\_log).*' access_log = re.search(format_exist_reg, conf).groups()[0] + '" ' + args.log_format_name if site['name'] not in sites and re.search(format_exist_reg,conf): access_log = ' '.join(access_log.split()[:-1]) diff --git a/class/backup_bak.py b/class/backup_bak.py index 32aa117b..42e98757 100644 --- a/class/backup_bak.py +++ b/class/backup_bak.py @@ -1,8 +1,8 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: 1249648969@qq.com # | 主控 备份 @@ -283,7 +283,7 @@ class backup_bak: public.ExecShell("sed -i '/password=/d' /etc/my.cnf") if act: mycnf = public.readFile('/etc/my.cnf'); - rep = "\[mysqldump\]\nuser=root" + rep = "\\[mysqldump\\]\nuser=root" sea = "[mysqldump]\n" subStr = sea + "user=root\npassword=\"" + root + "\"\n"; mycnf = mycnf.replace(sea,subStr) diff --git a/class/cachelib/simple.py b/class/cachelib/simple.py index f07c52d4..83ec8e39 100644 --- a/class/cachelib/simple.py +++ b/class/cachelib/simple.py @@ -47,6 +47,9 @@ class SimpleCache(BaseCache): __session_key = 'BT_:' __session_basedir = '/www/server/panel/data/session' + __SHM_PREFIX = 'SHM_:' + __SHM_BASEDIR = '/dev/shm/aap-shm' + def __init__(self, threshold=500, default_timeout=300): BaseCache.__init__(self, default_timeout) self._cache = {} @@ -109,6 +112,15 @@ class SimpleCache(BaseCache): def get(self, key): if not isinstance(key,str): return None + + try: + # 优先从shm中查找 + _shm_val = self.__get_shm(key) + + if _shm_val is not None: + return _shm_val + except: pass + try: expires, value = self._cache[key] if expires == 0 or expires > time(): @@ -125,6 +137,12 @@ class SimpleCache(BaseCache): if value_type not in type_list: return False + try: + # 优先写入shm + if self.__set_shm(key, value, timeout): + return True + except: pass + # 过期清理 expires = self._normalize_timeout(timeout) self._prune() @@ -148,6 +166,12 @@ class SimpleCache(BaseCache): if value_type not in type_list: return False + try: + # 优先写入shm + if self.__add_shm(key, value, timeout): + return True + except: pass + expires = self._normalize_timeout(timeout) self._prune() try: @@ -162,11 +186,23 @@ class SimpleCache(BaseCache): return True def delete(self, key): + try: + # 优先删除shm + if self.__del_shm(key): + return True + except: pass + result = self._cache.pop(key, None) is not None self.del_session_by_file(key) return result def has(self, key): + try: + # 优先shm + if self.__has_shm(key): + return True + except: pass + try: expires, value = self._cache[key] return expires == 0 or expires > time() @@ -174,7 +210,6 @@ class SimpleCache(BaseCache): if self.get_session_by_file(key): return True return False - def get_expire_time(self, key): try: expires, value = self._cache[key] @@ -194,3 +229,119 @@ class SimpleCache(BaseCache): m.update(strings.encode('utf-8')) return m.hexdigest() + def __set_shm(self, key, value, timeout=None): + ''' + @name 尝试将缓存写入shm目录 + @author Zhj<2022-10-08> + @param key 键名 + @param value 值 + @param timeout 存活时间/秒 + @return bool + ''' + if key[:5] != self.__SHM_PREFIX: + return False + + self.__makesure_shm_basedir() + + expires = struct.pack('f', self._normalize_timeout(timeout)) + filename = '/'.join((self.__SHM_BASEDIR, self.md5(key))) + with open(filename, 'wb') as fp: + fp.write(expires + pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + os.chmod(filename, 384) + + return True + + def __get_shm(self, key): + ''' + @name 尝试从shm目录下读取缓存 + @author Zhj<2022-10-08> + @param key 键名 + @return mixed|None + ''' + if key[:5] != self.__SHM_PREFIX: + return None + + self.__makesure_shm_basedir() + + filename = '/'.join((self.__SHM_BASEDIR, self.md5(key))) + if not os.path.exists(filename): return None + + with open(filename, 'rb') as fp: + _val = fp.read() + + expires = struct.unpack('f', _val[:4])[0] + + # 过期 删除缓存文件 + if expires > 0 and expires <= time(): + os.remove(filename) + return None + + return pickle.loads(_val[4:]) + + def __del_shm(self, key): + ''' + @name 删除shm目录下的缓存 + @author Zhj<2022-10-08> + @param key 键名 + @return bool + ''' + if key[:5] != self.__SHM_PREFIX: + return False + + self.__makesure_shm_basedir() + + filename = '/'.join((self.__SHM_BASEDIR, self.md5(key))) + if os.path.exists(filename): + os.remove(filename) + + return True + + def __has_shm(self, key): + ''' + @name 检查shm目录下的缓存是否存在 + @author Zhj<2022-10-08> + @param key 键名 + @return bool + ''' + if key[:5] != self.__SHM_PREFIX: + return False + + self.__makesure_shm_basedir() + + filename = '/'.join((self.__SHM_BASEDIR, self.md5(key))) + if not os.path.exists(filename): return False + + # 获取缓存过期时间 + with open(filename, 'rb') as fp: + expires = struct.unpack('f', fp.read(4))[0] + + # 过期 删除缓存文件 + if expires > 0 and expires <= time(): + os.remove(filename) + return False + + return True + + def __add_shm(self, key, value, timeout=None): + ''' + @name 尝试添加缓存到shm目录下 + @author Zhj<2022-10-08> + @param key 键名 + @param value 值 + @param timeout 缓存存活时间/秒 + @return bool + ''' + if self.__has_shm(key): + return False + + return self.__set_shm(key, value, timeout) + + def __makesure_shm_basedir(self): + ''' + @name 确保shm下的缓存目录存在 + @author Zhj<2022-10-08> + @return void + ''' + if not os.path.exists(self.__SHM_BASEDIR): + os.makedirs(self.__SHM_BASEDIR, 384) + diff --git a/class/common.py b/class/common.py index 4e382409..58a4de6a 100755 --- a/class/common.py +++ b/class/common.py @@ -1,12 +1,12 @@ -#coding: utf-8 +# coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- -from BTPanel import session, cache , request, redirect, g,abort +from BTPanel import session, cache , request, redirect, g,abort, Response from datetime import datetime from public import dict_obj import os @@ -21,13 +21,13 @@ class panelSetup: panel_path = public.get_panel_path() if os.getcwd() != panel_path: os.chdir(panel_path) - g.ua = request.headers.get('User-Agent','') + g.ua = request.headers.get('User-Agent', '') if g.ua: ua = g.ua.lower() if ua.find('spider') != -1 or g.ua.find('bot') != -1: return abort(403) - g.version = '6.8.37' + g.version = '7.0.6' g.title = public.GetConfigValue('title') g.uri = request.path g.debug = os.path.exists('data/debug.pl') @@ -55,7 +55,6 @@ class panelSetup: self.other_import() return None - def other_import(self): g.o = public.readFile('data/o.pl') g.other_css = [] @@ -71,7 +70,6 @@ class panelSetup: if os.path.exists(js_file): g.other_js.append('/static/other/{}'.format(js_name)) - class panelAdmin(panelSetup): setupPath = '/www/server' @@ -82,6 +80,7 @@ class panelAdmin(panelSetup): return result result = self.check_login() if result: + # public.print_log("local 2登录检查返回 {}".format(result)) return result result = self.setSession() if result: @@ -100,7 +99,8 @@ class panelAdmin(panelSetup): if request.method == 'GET': g.menus = public.get_menus() g.yaer = datetime.now().year - session["top_tips"] = public.get_msg_gettext("The current IE browser version is too low to display some features, please use another browser. Or if you use a browser developed by a Chinese company, please switch to Extreme Mode!") + session["top_tips"] = public.get_msg_gettext( + "The current IE browser version is too low to display some features, please use another browser. Or if you use a browser developed by a Chinese company, please switch to Extreme Mode!") session["bt_help"] = public.get_msg_gettext("For Support|Suggestions, please visit the aaPanel Forum") session["download"] = public.get_msg_gettext("Downloading:") if not 'brand' in session: @@ -121,7 +121,7 @@ class panelAdmin(panelSetup): # 检查Web服务器类型 def checkWebType(self): - #if request.method == 'GET': + # if request.method == 'GET': if not 'webserver' in session: if os.path.exists('/usr/local/lsws/bin/lswsctrl'): session['webserver'] = 'openlitespeed' @@ -130,11 +130,12 @@ class panelAdmin(panelSetup): else: session['webserver'] = 'nginx' if not 'webversion' in session: - if os.path.exists(self.setupPath+'/'+session['webserver']+'/version.pl'): - session['webversion'] = public.ReadFile(self.setupPath+'/'+session['webserver']+'/version.pl').strip() + if os.path.exists(self.setupPath + '/' + session['webserver'] + '/version.pl'): + session['webversion'] = public.ReadFile( + self.setupPath + '/' + session['webserver'] + '/version.pl').strip() if not 'phpmyadminDir' in session: - filename = self.setupPath+'/data/phpmyadminDirName.pl' + filename = self.setupPath + '/data/phpmyadminDirName.pl' if os.path.exists(filename): session['phpmyadminDir'] = public.ReadFile(filename).strip() return False @@ -144,15 +145,38 @@ class panelAdmin(panelSetup): if os.path.exists('data/close.pl'): return redirect('/close') + # 跳转到登录页面 + def to_login(self, url, msg=" Login has expired, please log in again"): + x_http_token = request.headers.get('X-Http-Token', '') + if x_http_token: + # 如果是ajax请求 + # res = {"status": False, "msg": msg, "redirect": url} + # 修改为通用返回方式 + res = { + "status": -1, + "timestamp": int(time.time()), + "message": { + # "result": "successfully added!", + "msg": msg, + "redirect": url + } + } + + # public.print_log("有x_http_token res --{}".format(res)) + return Response(json.dumps(res), content_type='application/json') + + # public.print_log("无x_http_token 跳转 --{}".format(url)) + return redirect(url) + # 检查登录 - def check_login(self): + def check_login1(self): try: api_check = True g.api_request = False if not 'login' in session: api_check = self.get_sk() if api_check: - if not isinstance(api_check,dict): + if not isinstance(api_check, dict): if public.get_admin_path() == '/login': return redirect('/login?err=1') return api_check @@ -178,12 +202,12 @@ class panelAdmin(panelSetup): if api_check: now_time = time.time() - session_timeout = session.get('session_timeout',0) + session_timeout = session.get('session_timeout', 0) if session_timeout < now_time and session_timeout != 0: session.clear() return redirect(public.get_admin_path()) - login_token = session.get('login_token','') + login_token = session.get('login_token', '') if login_token: if login_token != public.get_login_token_auth(): session.clear() @@ -201,51 +225,120 @@ class panelAdmin(panelSetup): self.check_session() except: - # public.print_log(public.get_error_info()) + public.print_log(public.get_error_info()) session.clear() public.print_error() + return redirect('/login?id=2') + # 检查登录 + def check_login(self): + try: + api_check = True + g.api_request = False + if not 'login' in session: + api_check = self.get_sk() + if api_check: + # if not isinstance(api_check, dict): + # if public.get_admin_path() == '/login': + # return redirect('/login?err=1') + return api_check + g.api_request = True + else: + if session['login'] == False: + session.clear() + return self.to_login(public.get_admin_path()) + + if 'tmp_login_expire' in session: + s_file = 'data/session/{}'.format(session['tmp_login_id']) + if session['tmp_login_expire'] < time.time(): + session.clear() + if os.path.exists(s_file): os.remove(s_file) + return self.to_login(public.get_admin_path(), 'The temporary login has expired, please log in again') + if not os.path.exists(s_file): + session.clear() + return self.to_login(public.get_admin_path(),'The temporary login has expired, please log in again') + + # 检查客户端hash -- 不要删除 + if not public.check_client_hash(): + session.clear() + return self.to_login(public.get_admin_path(),'Client verification failed, please log in again') + + if api_check: + now_time = time.time() + session_timeout = session.get('session_timeout', 0) + if session_timeout < now_time and session_timeout != 0: + session.clear() + return self.to_login(public.get_admin_path(),"Login session has expired, please log in again") + + login_token = session.get('login_token', '') + if login_token: + if login_token != public.get_login_token_auth(): + session.clear() + return self.to_login(public.get_admin_path(),'Login verification failed, please log in again') + + # if api_check: + # filename = 'data/sess_files/' + public.get_sess_key() + # if not os.path.exists(filename): + # session.clear() + # return redirect(public.get_admin_path()) + + # 标记新的会话过期时间 + self.check_session() + + except: + public.print_log(public.get_error_info()) + session.clear() + public.print_error() + public.print_log("except Login has expired, please log in again") + return self.to_login('/login',' Login has expired, please log in again') + + def check_session(self): white_list = ['/favicon.ico', '/system?action=GetNetWork'] if g.uri in white_list: return session['session_timeout'] = time.time() + public.get_session_timeout() - - # 获取sk def get_sk(self): save_path = '/www/server/panel/config/api.json' if not os.path.exists(save_path): - return public.redirect_to_login() + return public.redirect_to_login(None) + # return self.to_login(public.get_admin_path(), "Login session has expired, please log in again") try: api_config = json.loads(public.ReadFile(save_path)) except: os.remove(save_path) - return public.redirect_to_login() + return public.redirect_to_login(None) + # return self.to_login(public.get_admin_path(), "Login session has expired, please log in again") if not api_config['open']: - return public.redirect_to_login() + return public.redirect_to_login(None) + # return self.to_login(public.get_admin_path(), "Login session has expired, please log in again") from BTPanel import get_input get = get_input() client_ip = public.GetClientIp() + if not 'client_bind_token' in get: if not 'request_token' in get or not 'request_time' in get: - return public.redirect_to_login() + return public.redirect_to_login(None) + # return self.to_login(public.get_admin_path(), "Login session has expired, please log in again") num_key = client_ip + '_api' if not public.get_error_num(num_key, 20): - return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour') + return public.returnJson(False, '20 consecutive verification failures, prohibited for 1 hour') - if not public.is_api_limit_ip(api_config['limit_addr'], client_ip): # client_ip in api_config['limit_addr']: + if not public.is_api_limit_ip(api_config['limit_addr'], + client_ip): # client_ip in api_config['limit_addr']: public.set_error_num(num_key) - return public.returnJson(False,'%s[' % public.get_msg_gettext("20 consecutive verification failures, prohibited for 1 hour")+client_ip+']') + return public.returnJson(False, '%s[' % public.get_msg_gettext( + "IP validation failed, your access IP is") + client_ip + ']') else: num_key = client_ip + '_app' - if not public.get_error_num(num_key,20): - return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour') + if not public.get_error_num(num_key, 20): + return public.returnJson(False, '20 consecutive verification failures, prohibited for 1 hour') a_file = '/dev/shm/' + get.client_bind_token if not public.path_safe_check(get.client_bind_token): @@ -256,8 +349,8 @@ class panelAdmin(panelSetup): import panelApi if not panelApi.panelApi().get_app_find(get.client_bind_token): public.set_error_num(num_key) - return public.returnJson(False,'Unbound device') - public.writeFile(a_file,'') + return public.returnJson(False, 'Unbound device') + public.writeFile(a_file, '') if not 'key' in api_config: public.set_error_num(num_key) @@ -275,17 +368,23 @@ class panelAdmin(panelSetup): g.aes_key = api_config['key'] request_token = public.md5(get.request_time + api_config['token']) if get.request_token == request_token: - public.set_error_num(num_key,True) + public.set_error_num(num_key, True) return False public.set_error_num(num_key) - return public.returnJson(False,'Secret key verification failed') + return public.returnJson(False, 'Secret key verification failed') # 检查系统配置 def checkConfig(self): if not 'config' in session: session['config'] = public.M('config').where("id=?", ('1',)).field( 'webserver,sites_path,backup_path,status,mysql_root').find() - if not 'email' in session['config']: + + # 4.29 修复config可能是空列表导致赋值不上的问题 + if not session['config']: + session['config'] = {} + + # if not 'email' in session['config']: + if session['config'] and not 'email' in session['config']: session['config']['email'] = public.M( 'users').where("id=?", ('1',)).getField('email') if not 'address' in session: @@ -310,8 +409,7 @@ class panelAdmin(panelSetup): session['server_os'] = tmp return False - - def get_osname(self,i_file): + def get_osname(self, i_file): ''' @name 从指定文件中获取系统名称 @author hwliang<2021-04-07> diff --git a/class/config.py b/class/config.py index 6f38691e..d5c8ead8 100755 --- a/class/config.py +++ b/class/config.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import base64 import public,re,os,nginx,apache,json,time,ols @@ -510,7 +510,8 @@ class config: reg = r"^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$" if not re.match(reg, get.domain): return public.return_msg_gettext(False,'Format of primary domain is incorrect') if get.address: - if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", get.address): + from public.regexplib import match_ipv4, match_ipv6 + if not match_ipv4.match(get.address) and not match_ipv6.match(get.address) : return public.return_msg_gettext(False, 'Please set the correct Server IP') oldPort = public.GetHost(True) if not 'port' in get: @@ -1307,7 +1308,7 @@ class config: passwd = get.passwd if g != "files": iprep = r"(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})" - rep_domain = "^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$" + rep_domain = r"^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$" if not re.search(iprep, ip) and not re.search(rep_domain, ip): if ip != "localhost": return public.returnMsg(False, 'Please enter the correct [domain or IP]!') @@ -2441,7 +2442,7 @@ class config: if not 'status_code' in get: return public.return_msg_gettext(False,'Parameter ERROR!') - if re.match("^\d+$", get.status_code): + if re.match(r"^\d+$", get.status_code): status_code = int(get.status_code) if status_code != 0: if status_code < 100 or status_code > 999: @@ -2878,7 +2879,7 @@ class config: } # request发送post请求并指定form_data参数 res = public.httpPost(url, data) - # public.print_log("获取问卷@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ {}".format(res)) + try: res = json.loads(res) except: @@ -3162,7 +3163,7 @@ class config: "USER_AGENT": public.xsssec(request.headers.get('User-Agent')), # 客户端连接信息 "ERROR_INFO": error, # 错误信息 "PACK_TIME": public.readFile("/www/server/panel/config/update_time.pl") if os.path.exists("/www/server/panel/config/update_time.pl") else public.getDate(), # 打包时间 - "TYPE": 1, + "TYPE": 101, "ERROR_ID": "{}_{}".format(error.split("\n")[0].strip(),get.get("uri", "")) } pkey = public.Md5(error_infos["ERROR_INFO"]) @@ -3170,12 +3171,9 @@ class config: # 提交 if not public.cache_get(pkey): try: - public.run_thread(public.httpPost("https://api.bt.cn/bt_error/index.php", error_infos)) + public.run_thread(public.httpPost("https://geterror.aapanel.com/bt_error/index.php", error_infos)) public.cache_set(pkey, 1, 1800) except Exception as e: pass return public.returnMsg(True, "OK") - - - diff --git a/class/crontab.py b/class/crontab.py index ec0d3e70..24afce80 100644 --- a/class/crontab.py +++ b/class/crontab.py @@ -1,13 +1,13 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import public,db,os,time,re, json -from BTPanel import session,cache +from BTPanel import session, cache class crontab: field = 'id,name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sName,sBody,sType,urladdress' field += ",save_local,notice,notice_channel" @@ -401,6 +401,7 @@ class crontab: id = get['id'] find = public.M('crontab').where("id=?",(id,)).field('name,echo').find() if not find: return public.return_msg_gettext(False, 'The specified task does not exist!') + if not self.remove_for_crond(find['echo']): return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!') cronPath = public.GetConfigValue('setup_path') + '/cron' sfile = cronPath + '/' + find['echo'] diff --git a/class/crontab_ssl.py b/class/crontab_ssl.py index 35f9a379..172f13df 100644 --- a/class/crontab_ssl.py +++ b/class/crontab_ssl.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: <290070744@qq.com> +# Author: <290070744@aapanel.com> # ------------------------------------------------------------------- # ------------------------------ diff --git a/class/data.py b/class/data.py index e57b6533..54eeb092 100644 --- a/class/data.py +++ b/class/data.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import sys,os,re,time if not 'class/' in sys.path: @@ -223,7 +223,6 @@ class data: * @return Json page.分页数 , count.总行数 data.取回的数据 ''' def getData(self,get): - import one_key_wp # # net_flow_type = { # # "total_flow": "总流量", # # "7_day_total_flow": "近7天流量", @@ -262,14 +261,12 @@ class data: # # get.order = 'id desc' # # # net_flow_dict["order_type"] = order_type # # public.writeFile(net_flow_json_file, json.dumps(net_flow_dict)) - # 如果网站列表包含 rname 字段排序 先检查表内是否有 rname字段 if hasattr(get, "order") and get.table == 'sites': if get.order.startswith('rname'): data = public.M('sites').find() if 'rname' not in data.keys(): public.M('sites').execute("ALTER TABLE 'sites' ADD 'rname' text DEFAULT ''", ()) - table = get.table data = self.GetSql(get) SQL = public.M(table) @@ -331,6 +328,7 @@ class data: data['data'][i]['attack'] = self.get_analysis(get,data['data'][i]) data['data'][i]['project_type'] = SQL.table('sites').where('id=?',(data['data'][i]['id'])).field('project_type').find()['project_type'] if data['data'][i]['project_type'] == 'WP': + import one_key_wp data['data'][i]['cache_status'] = one_key_wp.one_key_wp().get_cache_status(data['data'][i]['id']) if not data['data'][i]['status'] in ['0','1',0,1]: data['data'][i]['status'] = '1' diff --git a/class/database.py b/class/database.py index c4c950ed..b8b0c1af 100644 --- a/class/database.py +++ b/class/database.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------ @@ -418,7 +418,7 @@ ssl-key=/www/server/mysql/mysql-test/std_data/server-key.pem reg = "ssl-ca=/www.*\n.*\n.*server-key.pem\n" conf = re.sub(reg, "", conf) if os.path.exists('/www/server/mysql/mysql-test/std_data/server-cert.pem'): - conf = re.sub('\[mysqld\]', '[mysqld]\nskip_ssl', conf) + conf = re.sub(r'\[mysqld\]', '[mysqld]\nskip_ssl', conf) public.writeFile(conf_file, conf) return public.return_msg_gettext(True, 'Setup successfully!') # create_ssl = None @@ -429,7 +429,7 @@ ssl-key=/www/server/mysql/mysql-test/std_data/server-key.pem # if create_ssl: # self._create_mysql_ssl() if "ssl-ca" not in conf: - conf = re.sub('\[mysqld\]', '[mysqld]' + ssl_original_path, conf) + conf = re.sub(r'\[mysqld\]', '[mysqld]' + ssl_original_path, conf) conf = re.sub('skip_ssl\n', '', conf) public.writeFile(conf_file, conf) # public.ExecShell('chown mysql.mysql /www/server/data/*.pem') @@ -792,7 +792,7 @@ SetLink password = get['password'].strip() try: if not password: return public.return_msg_gettext(False, 'Root password cannot be empty') - rep = "^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$" + rep = r"^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$" if not re.match(rep, password): return public.return_msg_gettext(False, 'Database password cannot contain special characters!') self.sid = get.get('sid/d', 0) @@ -864,7 +864,7 @@ SetLink db_find = public.M('databases').where('id=?', (id,)).find() name = db_find['name'] - rep = "^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$" + rep = r"^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$" if not re.match(rep, newpassword): return public.return_msg_gettext(False, 'Database password cannot contain special characters!') # 修改MYSQL @@ -1252,7 +1252,7 @@ SetLink ps = public.get_msg_gettext('Test Database') # XSS filter - if not re.match("^[\w+\.-]+$", value[0]): continue + if not re.match(r"^[\w+\.-]+$", value[0]): continue addTime = time.strftime('%Y-%m-%d %X', time.localtime()) @@ -1317,9 +1317,9 @@ SetLink public.CheckMyCnf() myfile = '/etc/my.cnf' mycnf = public.readFile(myfile) - rep = "datadir\s*=\s*(.+)\n" + rep = "datadir\\s*=\\s*(.+)\n" data['datadir'] = re.search(rep, mycnf).groups()[0] - rep = "port\s*=\s*([0-9]+)\s*\n" + rep = "port\\s*=\\s*([0-9]+)\\s*\n" data['port'] = re.search(rep, mycnf).groups()[0] except: data['datadir'] = '/www/server/data' @@ -1338,7 +1338,7 @@ SetLink 'The same as the current storage directory, file cannot be moved!') public.ExecShell('/etc/init.d/mysqld stop') - public.ExecShell('\cp -arf ' + mysqlInfo['datadir'] + '/* ' + get.datadir + '/') + public.ExecShell(r'\cp -arf ' + mysqlInfo['datadir'] + '/* ' + get.datadir + '/') public.ExecShell('chown -R mysql.mysql ' + get.datadir) public.ExecShell('chmod -R 755 ' + get.datadir) public.ExecShell('rm -f ' + get.datadir + '/*.pid') @@ -1365,7 +1365,7 @@ SetLink def SetMySQLPort(self, get): myfile = '/etc/my.cnf' mycnf = public.readFile(myfile) - rep = r"port\s*=\s*([0-9]+)\s*\n" + rep = "port\\s*=\\s*([0-9]+)\\s*\n" mycnf = re.sub(rep, 'port = ' + get.port + '\n', mycnf) public.writeFile(myfile, mycnf) public.ExecShell('/etc/init.d/mysqld restart') @@ -1429,19 +1429,19 @@ SetLink mysql_cnf = re.sub(r"\nlog-bin", "\n#log-bin", mysql_cnf) mysql_cnf = re.sub(r"\nbinlog_format", "\n#binlog_format", mysql_cnf) if not is_off_bin_log: - if re.search("\n#\s*skip-log-bin", mysql_cnf): - mysql_cnf = re.sub("\n#\s*skip-log-bin", "\nskip-log-bin", mysql_cnf) + if re.search("\n#\\s*skip-log-bin", mysql_cnf): + mysql_cnf = re.sub("\n#\\s*skip-log-bin", "\nskip-log-bin", mysql_cnf) else: - mysql_cnf = re.sub("\n#\s*log-bin", "\nskip-log-bin\n#log-bin", mysql_cnf) + mysql_cnf = re.sub("\n#\\s*log-bin", "\nskip-log-bin\n#log-bin", mysql_cnf) # public.ExecShell("rm -f {}/mysql-bin.*".format(mysql_data_dir)) else: # 开启 binlog 日志 - if re.search("\n#\s*log-bin", mysql_cnf): - mysql_cnf = re.sub("\n#\s*log-bin", "\nlog-bin", mysql_cnf) + if re.search("\n#\\s*log-bin", mysql_cnf): + mysql_cnf = re.sub("\n#\\s*log-bin", "\nlog-bin", mysql_cnf) else: mysql_cnf = re.sub("[mysqld]", "[mysqld]\nlog-bin=mysql-bin", mysql_cnf) - if re.search("\n#\s*binlog_format", mysql_cnf): - mysql_cnf = re.sub(r"\n#\s*binlog_format", "\nbinlog_format", mysql_cnf) + if re.search("\n#\\s*binlog_format", mysql_cnf): + mysql_cnf = re.sub("\n#\\s*binlog_format", "\nbinlog_format", mysql_cnf) else: mysql_cnf = re.sub("[mysqld]", "[mysqld]\nbinlog_format=mixed", mysql_cnf) diff --git a/class/databaseModel/base.py b/class/databaseModel/base.py index 2afe6118..d48fbd16 100644 --- a/class/databaseModel/base.py +++ b/class/databaseModel/base.py @@ -178,7 +178,7 @@ class databaseBase: if data_name in checks or len(data_name) < 1: return public.returnMsg(False,'Database name is invalid!'); - reg = "^\w+$" + reg = r"^\w+$" if not re.match(reg, data_name): return public.returnMsg(False,'DATABASE_NAME_ERR_T') diff --git a/class/databaseModel/mongodbModel.py b/class/databaseModel/mongodbModel.py index 0280c750..6e1e4171 100644 --- a/class/databaseModel/mongodbModel.py +++ b/class/databaseModel/mongodbModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #角色说明: #read:允许用户读取指定数据库 @@ -105,7 +105,7 @@ class panelMongoDB(): conf = self.get_config(None) for opt in options: - tmp = re.findall(opt + ":\s+(.+)",conf) + tmp = re.findall(opt + r":\s+(.+)",conf) if not tmp: continue; data[opt] = tmp[0] @@ -177,9 +177,9 @@ class main(databaseBase): conf = public.readFile(self.__conf_path) if status: - conf = re.sub('authorization\s*\:\s*disabled','authorization: enabled',conf) + conf = re.sub(r'authorization\s*\:\s*disabled','authorization: enabled',conf) else: - conf = re.sub('authorization\s*\:\s*enabled','authorization: disabled',conf) + conf = re.sub(r'authorization\s*\:\s*enabled','authorization: disabled',conf) public.writeFile(self.__conf_path,conf) self.restart_services() @@ -201,7 +201,7 @@ class main(databaseBase): if status: if hasattr(get,'password'): password = get['password'].strip() - if not password or not re.search("^[\w@\.]+$", password): + if not password or not re.search(r"^[\w@\.]+$", password): return public.return_msg_gettext(False, 'Database password cannot be empty or have special characters!') # if re.search('[\u4e00-\u9fa5]',password): @@ -670,7 +670,7 @@ class main(databaseBase): try: if not newpassword: return public.returnMsg(False, 'Modify the failure,The database[' + username + ']password cannot be empty.'); - if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0: + if len(re.search(r"^[\w@\.]+$", newpassword).groups()) > 0: return public.returnMsg(False, 'The database password cannot be empty or contain special characters') if re.search('[\u4e00-\u9fa5]',newpassword): diff --git a/class/databaseModel/pgsqlModel.py b/class/databaseModel/pgsqlModel.py index 259c5584..f983128e 100644 --- a/class/databaseModel/pgsqlModel.py +++ b/class/databaseModel/pgsqlModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hezhihong +# Author: hezhihong #------------------------------------------------------------------- #------------------------------ @@ -154,7 +154,7 @@ class main(databaseBase,panelPgsql): if not self.__soft_path:self.__soft_path='{}/pgsql'.format(public.get_setup_path()) conf = public.readFile('{}/data/postgresql.conf'.format(self.__soft_path)) for opt in options: - tmp = re.findall("\s+" +opt + "\s*=\s*(.+)#",conf) + tmp = re.findall(r"\s+" +opt + r"\s*=\s*(.+)#",conf) if not tmp: continue; data[opt] = tmp[0].strip() if opt == 'listen_addresses': @@ -223,7 +223,8 @@ class main(databaseBase,panelPgsql): @return list ''' check_result = os.system('/www/server/pgsql/bin/psql --version') - if check_result !=0 and not public.M('database_servers').where('db_type=?','pgsql').count():return [] + if check_result !=0 and not public.M('database_servers').where('db_type=?','pgsql').count(): + return [] return self.GetBaseCloudServer(args) @@ -302,7 +303,7 @@ class main(databaseBase,panelPgsql): id = get['id'] find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,accept,ps,addtime,db_type,conn_config,sid,type').find(); if not find: return public.returnMsg(False,'The specified database does not exist.') - + name = get['name'] username = find['username'] @@ -323,15 +324,15 @@ class main(databaseBase,panelPgsql): find = public.M('databases').where("id=?",(id,)).find() if not find: return public.returnMsg(False,'Database does not exist!') - + if not find['password'].strip(): return public.returnMsg(False,'The database password is empty. Set the password first.') - + sql_dump = '{}/bin/pg_dump'.format(self.__soft_path) # return sql_dump if not os.path.isfile(sql_dump): return public.returnMsg(False,'Lack of backup tools, please first through the software store PGSQL manager!') - + back_path = session['config']['backup_path'] + '/database/pgsql/' # return back_path if not os.path.exists(back_path): os.makedirs(back_path) @@ -358,7 +359,7 @@ class main(databaseBase,panelPgsql): if os.path.getsize(backupName) < 2048: return public.returnMsg(True, 'The backup file size is smaller than 2Kb. Check the backup integrity.') - else: + else: return public.returnMsg(True, 'BACKUP_SUCCESS') def DelBackup(self,args): @@ -397,7 +398,7 @@ class main(databaseBase,panelPgsql): ext = tmp[len(tmp) -1] if ext not in exts: return public.returnMsg(False, 'DATABASE_INPUT_ERR_FORMAT') - + sql_dump = '{}/bin/psql'.format(self.__soft_path) if not os.path.exists(sql_dump): return public.returnMsg(False,'Lack of recovery tool, please use software management to install PGSQL!') diff --git a/class/databaseModel/redisModel.py b/class/databaseModel/redisModel.py index b7fd8fb9..64a9fef7 100644 --- a/class/databaseModel/redisModel.py +++ b/class/databaseModel/redisModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- # sqlite模型 @@ -76,7 +76,7 @@ class panelRedisDB(): keys = ["bind","port","timeout","maxclients","databases","requirepass","maxmemory"] for k in keys: v = "" - rep = "\n%s\s+(.+)" % k + rep = "\n%s\\s+(.+)" % k group = re.search(rep,redis_conf) if not group: if k == "maxmemory": @@ -102,6 +102,7 @@ class main(databaseBase): pass + def GetCloudServer(self,args): ''' @name 获取远程服务器列表 @@ -109,6 +110,7 @@ class main(databaseBase): @return list ''' return self.GetBaseCloudServer(args) + # return public.return_message(0, 0, self.GetBaseCloudServer(args)) def AddCloudServer(self,args): @@ -373,7 +375,8 @@ class main(databaseBase): nlist = [] cloud_list = {} - for x in self.GetCloudServer({'type':'redis'}): cloud_list['id-' + str(x['id'])] = x + for x in self.GetCloudServer({'type':'redis'}): + cloud_list['id-' + str(x['id'])] = x path = session['config']['backup_path'] + '/database/redis/' if not os.path.exists(path): os.makedirs(path) diff --git a/class/databaseModel/sqliteModel.py b/class/databaseModel/sqliteModel.py index bfd3191d..00ee1955 100644 --- a/class/databaseModel/sqliteModel.py +++ b/class/databaseModel/sqliteModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ diff --git a/class/databaseModel/sqlserverModel.py b/class/databaseModel/sqlserverModel.py index 4e5b05ad..b3054d62 100644 --- a/class/databaseModel/sqlserverModel.py +++ b/class/databaseModel/sqlserverModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -87,6 +87,8 @@ class main(databaseBase): ''' @添加远程数据库 ''' + + return self.AddBaseCloudServer(args) def RemoveCloudServer(self,args): @@ -107,17 +109,18 @@ class main(databaseBase): """ res = self.add_base_database(args) - if not res['status']: return res + if not res['status']: + return res data_name = res['data_name'] username = res['username'] password = res['data_pwd'] - if re.match("^\d+",data_name): + if re.match(r"^\d+",data_name): return public.returnMsg(False,'SQLServer databases cannot start with numbers!') reg_count = 0 - regs = ['[a-z]','[A-Z]','\W','[0-9]'] + regs = ['[a-z]','[A-Z]',r'\W','[0-9]'] for x in regs: if re.search(x,password): reg_count += 1 @@ -361,7 +364,7 @@ class main(databaseBase): try: if not newpassword: return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.'); - if len(re.search("^[\w@\.]+$", newpassword).groups()) > 0: + if len(re.search(r"^[\w@\.]+$", newpassword).groups()) > 0: return public.returnMsg(False, 'The database password cannot be empty or contain special characters') except : return public.returnMsg(False, 'The database password cannot be empty or contain special characters') @@ -402,7 +405,7 @@ class main(databaseBase): try: if not password: return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.') - if len(re.search("^[\w@\.]+$", password).groups()) > 0: + if len(re.search(r"^[\w@\.]+$", password).groups()) > 0: return public.returnMsg(False, 'saThe password cannot be empty or have special symbols') except : return public.returnMsg(False, 'saThe password cannot be empty or have special symbols') diff --git a/class/datatool.py b/class/datatool.py index d67383f5..3f99f81b 100644 --- a/class/datatool.py +++ b/class/datatool.py @@ -1,8 +1,8 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: 1249648969@qq.com # ------------------------------------------------------------------- diff --git a/class/db.py b/class/db.py index f1fc0ace..3efc9529 100644 --- a/class/db.py +++ b/class/db.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import sqlite3 @@ -390,3 +390,7 @@ class Sql(): except: pass + def db(self,name): + # 设置数据库名称,用于判断数据库文件 + self.__DB_NAME = name + return self diff --git a/class/db_mysql.py b/class/db_mysql.py index 5531ecd0..5fbc2c05 100644 --- a/class/db_mysql.py +++ b/class/db_mysql.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import re,os,sys,public,json diff --git a/class/downloadFile.py b/class/downloadFile.py index 28b8cdd3..b8e5da7c 100644 --- a/class/downloadFile.py +++ b/class/downloadFile.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import os,sys,public,json,time class downloadFile: diff --git a/class/file_execute_deny.py b/class/file_execute_deny.py index 50aa8a18..677f0b56 100644 --- a/class/file_execute_deny.py +++ b/class/file_execute_deny.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2020 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zhwen +# Author: zhwen #------------------------------------------------------------------- #------------------------------ @@ -24,7 +24,7 @@ class FileExecuteDeny: def get_file_deny(self,args): ''' # 添加某个网站禁止运行PHP - author: zhwen + author: zhwen :param args: website 网站名 str :return: ''' @@ -51,7 +51,7 @@ class FileExecuteDeny: deny_name.append(tmp[-1]) result = [] for i in deny_name: - reg = '#BEGIN_DENY_{}\n\s*location\s*\~\*\s*\^(.*)\.\*.*\((.*)\)\$'.format(i.replace("|","\|")) + reg = '#BEGIN_DENY_{}\n\\s*location\\s*\\~\\*\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i.replace("|",r"\|")) re_tmp = re.search(reg,conf) if re_tmp: deny_directory = re_tmp.groups()[0] @@ -73,7 +73,7 @@ class FileExecuteDeny: deny_name.append(tmp[-1]) result = [] for i in deny_name: - reg = '#BEGIN_DENY_{}\n\s* + author: zhwen :param args: website 网站名 str :param args: deny_name 规则名称 str :param args: suffix 禁止访问的后续名 str @@ -145,7 +145,7 @@ class FileExecuteDeny: if not conf: return False if not dir and not suffix: - reg = '\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name) + reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name) conf = re.sub(reg,'',conf) else: if dir[0] != '/':dir = '/'+dir @@ -168,12 +168,12 @@ class FileExecuteDeny: if not conf: return False if not dir and not suffix: - reg = '\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name) + reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name) conf = re.sub(reg,'',conf) else: if dir[0] != '/':dir = '/'+dir if dir[-1] != '/':dir = dir+'/' - new = ''' + new = r''' #BEGIN_DENY_{n} Order allow,deny @@ -183,7 +183,7 @@ class FileExecuteDeny: '''.format(n=name,d=dir,s=suffix) if '#BEGIN_DENY_{}'.format(name) in conf: return True - conf = re.sub('#DENY\s*FILES',new+'\n #DENY FILES',conf) + conf = re.sub(r'#DENY\s*FILES',new+'\n #DENY FILES',conf) public.writeFile(self.ap_website_conf,conf) return True @@ -192,17 +192,17 @@ class FileExecuteDeny: if not conf: return False if not dir and not suffix: - reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\s*'.format(n=name) + reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\\s*'.format(n=name) conf = re.sub(reg,'',conf) else: - new = ''' + new = r''' #BEGIN_DENY_{n} rules RewriteRule ^{d}.*\.({s})$ - [F,L] #END_DENY_{n} '''.format(n=name,d=dir,s=suffix) if '#BEGIN_DENY_{}'.format(name) in conf: return True - conf = re.sub('autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf) + conf = re.sub(r'autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf) public.writeFile(self.ols_website_conf,conf) return True @@ -210,7 +210,7 @@ class FileExecuteDeny: def del_file_deny(self,args): ''' # 添加某个网站禁止运行PHP - author: zhwen + author: zhwen :param args: website 网站名 str :param args: deny_name 规则名称 str :return: diff --git a/class/files.py b/class/files.py index a29344d3..9a1e914c 100644 --- a/class/files.py +++ b/class/files.py @@ -1,11 +1,11 @@ #!/usr/bin/env python #coding:utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- from base64 import b64encode import sys @@ -124,7 +124,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path) filename = public.get_vhost_path() + '/nginx/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = '\s*root\s+(.+);' + rep = r'\s*root\s+(.+);' tmp1 = re.search(rep, conf) if tmp1: path = tmp1.groups()[0] @@ -132,7 +132,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path) filename = public.get_vhost_path() + '/apache/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = '\s*DocumentRoot\s*"(.+)"\s*\n' + rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n' tmp1 = re.search(rep, conf) if tmp1: path = tmp1.groups()[0] @@ -398,9 +398,86 @@ session.save_handler = files'''.format(path, sess_path, sess_path) for m in ms.keys(): filename = filename.replace(m,ms[m]) return filename + def files_list(self, path, search=None, my_sort='off', reverse=False): + ''' + @name 遍历目录,并获取全量文件信息列表 + @param path 目录路径 + @param search 搜索关键词 + @param my_sort 排序字段 + @param reverse 是否降序 + @return tuple (int,list) + ''' + + nlist = [] + count = 0 + + # 文件不存在 + if not os.path.exists(path): + return count, nlist + + sort_key = -1 + if my_sort == 'off': # 不排序 + sort_key = -1 + elif my_sort == 'name': # 按文件名排序 + sort_key = 0 + elif my_sort == 'size': # 按文件大小排序 + sort_key = 1 + elif my_sort == 'mtime': # 按修改时间排序 + sort_key = 2 + elif my_sort == 'accept': # 按文件权限排序 + sort_key = 3 + elif my_sort == 'user': # 按文件所有者排序 + sort_key = 4 + + with os.scandir(path) as it: + try: + for entry in it: + # 是否搜索 + if search: + if entry.name.lower().find(search) == -1: + continue + + # 是否需要获取文件信息 + sort_val = 0 + if sort_key == 0 or sort_key == -1: + # 通过文件名或不排序时,不获取文件信息 + sort_val = 0 + else: + try: + fstat = entry.stat() + if sort_key == 1: + sort_val = fstat.st_size + elif sort_key == 2: + sort_val = fstat.st_mtime + elif sort_key == 3: + sort_val = fstat.st_mode + elif sort_key == 4: + sort_val = fstat.st_uid + except: + pass + + nlist.append((entry.name, sort_val)) + + # 计数 + count += 1 + except: + pass + + if sort_key == 0: + # 按文件名排序 + nlist = sorted(nlist, key=lambda x: x[0], reverse=reverse) + elif sort_key > 0: + # 按指定字段排序 + nlist = sorted(nlist, key=lambda x: x[1], reverse=reverse) + else: + # 否则文件数量小于10000时,按文件名排序 + if count < 10000: + nlist = sorted(nlist, key=lambda x: x[0], reverse=reverse) + + return count, nlist # 取文件/目录列表 - def GetDir(self, get): + def GetDir(self, get: public.dict_obj): if not hasattr(get, 'path'): # return public.returnMsg(False,'错误的参数!') get.path = public.get_site_path() #'/www/wwwroot' @@ -425,7 +502,6 @@ session.save_handler = files'''.format(path, sess_path, sess_path) if not os.path.isdir(get.path): return public.return_msg_gettext(False,'This is not a directory') - import pwd dirnames = [] filenames = [] @@ -441,7 +517,16 @@ session.save_handler = files'''.format(path, sess_path, sess_path) # 实例化分页类 page = page.Page() info = {} - info['count'] = self.GetFilesCount(get.path, search) + + if not hasattr(get, 'reverse'): get.reverse = 'False' + if not hasattr(get, 'sort'): get.sort = 'off' + reverse = bool(get.reverse) + if get.reverse == 'False': + reverse = False + + info['count'], _nlist = self.files_list(get.path, search, my_sort=get.sort, reverse=reverse) + # 改1 + # info['count'] = self.GetFilesCount(get.path, search) info['row'] = 500 if 'disk' in get: if get.disk == 'true': info['row'] = 2000 @@ -472,13 +557,18 @@ session.save_handler = files'''.format(path, sess_path, sess_path) data['STORE'] = self.get_files_store(None) data['FILE_RECYCLE'] = os.path.exists('data/recycle_bin.pl') - if not hasattr(get, 'reverse'): get.reverse = 'False' - if not hasattr(get, 'sort'): get.sort = 'name' - reverse = bool(get.reverse) - if get.reverse == 'False': - reverse = False - for file_info in self.__list_dir(get.path, get.sort, reverse): - filename = os.path.join(get.path, file_info[0]) + # if info['count'] >= 200 and not os.path.exists('data/max_files_sort.pl'): + # get.reverse = 'False' + # reverse = False + # get.sort = '' + + # _nlist = self.__default_list_dir(get.path,page.SHIFT,page.ROW) + # data['SORT'] = 0 + # else: + # _nlist = self.__list_dir(get.path, get.sort, reverse) + + for file_info in _nlist: + if search: if file_info[0].lower().find(search) == -1: continue @@ -487,19 +577,25 @@ session.save_handler = files'''.format(path, sess_path, sess_path) break if i < page.SHIFT: continue - if not os.path.exists(filename) and not os.path.islink(filename): continue - file_info = self.__format_stat(filename, get.path) - if not file_info: continue - favorite = self.__check_favorite(filename, data['STORE']) - r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str( - file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\ - + self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\ - + favorite+';'+self.__check_share(filename) - if os.path.isdir(filename): - dirnames.append(r_file) - else: - filenames.append(r_file) - n += 1 + + try: + fname = file_info[0].encode('unicode_escape').decode("unicode_escape") + filename = os.path.join(get.path, fname) + if not os.path.exists(filename) and not os.path.islink(filename): continue + file_info = self.__format_stat(filename, get.path) + if not file_info: continue + favorite = self.__check_favorite(filename, data['STORE']) + r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str( + file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\ + + self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\ + + favorite+';'+self.__check_share(filename) + if os.path.isdir(filename): + dirnames.append(r_file) + else: + filenames.append(r_file) + n += 1 + except: + continue data['DIR'] = dirnames data['FILES'] = filenames @@ -536,8 +632,77 @@ session.save_handler = files'''.format(path, sess_path, sess_path) data['search_history'] = public.get_search_history('files','get_list') public.set_dir_history('files','GetDirList',data['PATH']) + # 2023-3-6,增加融入企业级防篡改 + data = self._check_tamper(data) + data = self._get_bt_sync_status_old(data) return data + # ——————————————————— + # 融合企业级防篡改 | + # ——————————————————— + + # 防篡改:获取文件是否在保护列表中 + + def _check_tamper(self, data): + try: + import PluginLoader + except: + return {} + args = public.dict_obj() + args.client_ip = public.GetClientIp() + args.fun = "check_dir_safe" + args.s = "check_dir_safe" + args.file_data = { + "base_path": data['PATH'], + "dirs": [i.split(";", 1)[0] for i in data["DIR"]], + "files": [i.split(";", 1)[0] for i in data["FILES"]] + } + data["tamper_data"] = PluginLoader.plugin_run("tamper_core", "check_dir_safe", args) + + return data + + + + # 获取文件同步状态 + @staticmethod + def _get_bt_sync_status_old(data): + config_file = "{}/plugin/rsync/config4.json".format(public.get_panel_path()) + if not os.path.exists(config_file): + data["bt_sync"] = {} + return data + try: + conf = json.loads(public.readFile(config_file)) + except json.JSONDecodeError: + data["bt_sync"] = {} + return data + + dirs = [data['PATH'] + "/" + i.split(";", 1)[0] for i in data["DIR"]] + res = [{} for _ in range(len(dirs))] + for idx, d in enumerate(dirs): + for value in conf.get("modules", []): + if value.get("path", "").rstrip("/") == d: + res[idx] = { + "type": "modules", + "name": value.get("name", ""), + "status": value.get("recv_status", True), + "path": d, + } + + for idx, d in enumerate(dirs): + for value in conf.get("senders", []): + if value.get("source", "").rstrip("/") == d: + target = value.get("target_list", [{}])[0] + res[idx] = { + "type": "senders", + "name": target.get("name", ""), + "status": target.get("status", True), + "path": d, + } + + data["bt_sync"] = res + return data + + def get_file_ps(self,filename): ''' @@ -548,13 +713,17 @@ session.save_handler = files'''.format(path, sess_path, sess_path) ''' ps_path = public.get_panel_path() + '/data/files_ps' - f_key1 = '/'.join((ps_path,public.md5(filename))) - if os.path.exists(f_key1): - return public.readFile(f_key1) + try: + f_key1 = '/'.join((ps_path,public.md5(filename))) - f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename)))) - if os.path.exists(f_key2): - return public.readFile(f_key2) + if os.path.exists(f_key1): + return public.readFile(f_key1) + + f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename)))) + if os.path.exists(f_key2): + return public.readFile(f_key2) + except: + pass pss = { '/www/server/data':'MySQL data storage directory!', @@ -600,10 +769,17 @@ session.save_handler = files'''.format(path, sess_path, sess_path) '/usr/local/sbin': 'System script directory', '/usr/local/bin': 'System script directory' } - if filename in pss: return "PS:" + pss[filename] - if not self.recycle_list: self.recycle_list = public.get_recycle_bin_list() - if filename + '/' in self.recycle_list: return 'PS: Recycle Bin Directory' + if str(filename).endswith(".bt_split_json"): + return "PS: Split the recovery profile" + if str(filename).endswith(".bt_split"): + return "PS: Split unit file" + if filename in pss: return "PS:" + pss[filename] + try: + if not self.recycle_list: self.recycle_list = public.get_recycle_bin_list() + except: + pass + if filename + '/' in self.recycle_list:'PS: Recycle Bin Directory' if filename in self.recycle_list: return 'PS: Recycle Bin Directory' return '' @@ -930,8 +1106,7 @@ session.save_handler = files'''.format(path, sess_path, sess_path) public.WriteLog('TYPE_FILE', 'Successfully deleted directory [{}]!', (get.path,)) self.remove_file_ps(get) return public.return_msg_gettext(True, ' Successfully deleted directory!') - except Exception as e: - public.print_log("DeleteDir error info :{}".format(e)) + except: return public.return_msg_gettext(False, 'Failed to delete directory!') # 删除 空目录 @@ -1256,9 +1431,9 @@ session.save_handler = files'''.format(path, sess_path, sess_path) public.write_log_gettext('File manager','[{}] renamed to [{}]',(get.sfile,get.dfile)) return public.return_msg_gettext(True,'Successfully renamed!') else: - public.write_log_gettext('File manager', 'Database moved!', + public.write_log_gettext('File manager', 'File moved!', (get.sfile, get.dfile)) - return public.return_msg_gettext(True, 'Database moved!') + return public.return_msg_gettext(True, 'File moved!') except: return public.return_msg_gettext(False, 'Failed to move file!') @@ -1795,11 +1970,11 @@ session.save_handler = files'''.format(path, sess_path, sess_path) return self.GetDirSize(get) # 批量操作 - def SetBatchData(self, get): + def SetBatchData(self, get: public.dict_obj): if sys.version_info[0] == 2: get.path = get.path.encode('utf-8') if get.type == '1' or get.type == '2': - session['selected'] = get + session['selected'] = get.get_items() return public.return_msg_gettext(True, 'Successfully marked, please click Paste All button in the target directory!') elif get.type == '3': for key in json.loads(get.data): @@ -2428,7 +2603,7 @@ cd %s pdata['password'] = get.password if len(pdata['password']) < 4 and len(pdata['password']) > 0: return public.return_msg_gettext(False,'The length of the extracted password cannot be less than 4 digits') - if not re.match('^\w+$',pdata['password']): + if not re.match(r'^\w+$',pdata['password']): return public.return_msg_gettext(False,'The password only supports a combination of uppercase and lowercase letters and numbers') if 'ps' in get: pdata['ps'] = get.ps @@ -2455,7 +2630,7 @@ cd %s pdata['token'] += "." + exts[-1] if len(pdata['password']) < 4 and len(pdata['password']) > 0: return public.return_msg_gettext(False,' Please do not enter the following special characters [ ~ ` / = ]') - if not re.match('^\w+$',pdata['password']) and pdata['password']: + if not re.match(r'^\w+$',pdata['password']) and pdata['password']: return public.return_msg_gettext(False,'The password only supports a combination of uppercase and lowercase letters and numbers') #更新 or 插入 token = public.M(my_table).where('filename=?',(get.filename,)).getField('token') @@ -3254,7 +3429,7 @@ CREATE TABLE index_tb( def restore_website(self,args): """ @name 恢复站点文件 - @author zhwen + @author zhwen @parma file_name 备份得文件名 @parma site_id 网站id """ @@ -3265,8 +3440,8 @@ CREATE TABLE index_tb( def get_progress(self,args): """ @name 获取进度日志 - @author zhwen + @author zhwen """ import panel_restore pr=panel_restore.panel_restore() - return pr.get_progress(args) \ No newline at end of file + return pr.get_progress(args) diff --git a/class/filesModel/downModel.py b/class/filesModel/downModel.py index b875fe0c..7f6c06e9 100644 --- a/class/filesModel/downModel.py +++ b/class/filesModel/downModel.py @@ -1,46 +1,46 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# 上传文件至oss -#------------------------------ -from filesModel.base import filesBase -import public - -class main(filesBase): - - - def __init__(self): - pass - - - def get_oss_objects(self,get): - """ - @name 获取可上传的对象存储 - """ - return self.get_all_objects(get) - - - def download_file(self,get): - """ - @name 下载文件 - @param get - file:文件路径 - """ - - info = self.get_soft_find(get.name) - if not info['setup']: - return public.returnMsg(False,'未安装[{}]插件'.format(info['title'])) - - import panelTask - task_obj = panelTask.bt_task() - task_obj.create_task('下载文件', 1, get.url, get.path + '/' + get.filename) - public.set_module_logs('files_down_to_file', 'download_file', 1) - public.WriteLog('TYPE_FILE', '从 [{}] 下载文件 [{}] 到 {}'.format(info['title'],get.filename,get.path)) - return public.returnMsg(True, 'FILE_DOANLOAD') - +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +kEPo6lgxLBQavG1DxRrlnEK5KohxBShsQIeNCGHkEP4= +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +XIfdJ79nObMM+vyAmKbTmw== +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9bJD7OGVYIbkQwcqio3pMkEQ5Tepk1KAKJBdik81BOHNSVFwrMdl2gFGwZvvpYDQ +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P5ZvVL4YjAgUVrqKtQwmJSm1F1KbG0/rNY/6xTjx3EA3 +Z8UsPk1Q7HtwjRd4g01ryw== +CAhn8dRUItEbEErp4w+lX9Z2hX2+feDN0U2wxQk1jf6XkRmrNeGBO3gU/TXqpTeF +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +vHI6ym+jnn06PgL99476zxO+uTTrKbsFCbogJPwJML2TG1a7NmZkzv05uK3L/Tc8 +Z8UsPk1Q7HtwjRd4g01ryw== +n3tXewq2IYqiwFREjJjsxZJ3F7EUu5p3TiQaoXfFThA= +ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk= +PRG/YRzXVD8iVR3bzEcUnsWMvR86r5liyWslQeT39Bs= +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +AN33R+yjfqqAkAsFnmP/r/WGRgGkDEv8Fnc8kjQsJ7fRnV1BKZAu/huNYnIc6I01 +cbvASHjpysrsjdY5RctXmG9wund6qoaXAfnCgO+o1nY= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXHKWKUVVa/rOrR3+fgEd7tp63QHMzA34qukyTQgDFJrLVYVJL/Ye3kNmAIiExi//aTWiBsWXWTJE2/bLQwR3ie +1u+XjG/2+GSQRv6EzCaWRQ== +q4nc/jwATOMUyfSjLibfEelnou8pLYpfebj3/LvnkGg= +7fBvPEAf72Uv1YEDXbbQ3CN3MWrD+3vkr2VJCCzzEPRhwccGf00/aNA1BGgdy8WH +7fBvPEAf72Uv1YEDXbbQ3JX8fOX6IsaFNTXjLCv8fl9nBsjACyGde1V2jqcLtcHMgHmQHXLhOkH7ECCy4Qan/Eap/d5B8ETngKGRLc1dnrFJxLrR59XTXqj5a3KAPh6f +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs8XJaZJ50IVd+4cfwZkMesXHuMlT+97TT2qjLWtQKOwIMYvrQOoIlZ+noM34BKXXbE= +2+RceSNjD2iq0sRzepMyse3K/6KDWrRQWf3bA9DlS0ntu9Qw70LcA2SU6TO7qbj7+BZ00tLn7oRDAJhOp7GBQJ9AdWUuTKC0qj4H0sUep4AFfY3QegcrH7wS+cAOaYcaYNB7YC/AjQj4DFsev5mqOa3aXW4VcIMS3jP/SRer5vo= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOHo1JRV4mvyfjFAJfZA0Y7fzjR8HN7n40vj/TKQkGxagQ== +1u+XjG/2+GSQRv6EzCaWRQ== diff --git a/class/filesModel/gzModel.py b/class/filesModel/gzModel.py index 6cb7cd7e..fb0b9a83 100644 --- a/class/filesModel/gzModel.py +++ b/class/filesModel/gzModel.py @@ -1,342 +1,342 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# -#------------------------------ - -import os,sys,re -from filesModel.base import filesBase -import public,json -import tarfile,shutil,gzip - -class main(filesBase): - - def __init__(self): - pass - - - def __check_zipfile(self,sfile,is_close = False): - ''' - @name 检查文件是否为zip文件 - @param sfile 文件路径 - @return bool - ''' - - pass - - def get_zip_files(self,args): - ''' - @name 获取压缩包内文件列表 - @param args['path'] 压缩包路径 - @return list - ''' - sfile = args.sfile - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - if not tarfile.is_tarfile(sfile): - return public.returnMsg(False,'Not a valid tar.gz archive file') - - zip_file = tarfile.open(sfile) - data = {} - for item in zip_file.getmembers(): - - sub_data = data - f_name = self.__get_zip_filename(item) - - f_dirs = f_name.split('/') - for d in f_dirs: - if not d: continue - if not d in sub_data: - if d == f_name[-len(d):]: - - sub_data[d] = { - 'file_size': item.size, - 'filename':d, - 'fullpath':f_name, - 'date_time': public.format_date(times=item.mtime), - 'is_dir': 0 - } - if item.isdir(): - sub_data[d]['is_dir'] = 1 - else: - sub_data[d] = {} - sub_data = sub_data[d] - - return data - - - def get_fileinfo_by(self,args): - ''' - @name 获取压缩包内文件信息 - @param args['path'] 压缩包路径 - @param args['filename'] 文件名 - @return dict - ''' - - sfile = args.sfile - filename = args.filename - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - tmp_path = '{}/tmp/{}'.format(public.get_panel_path(),public.md5(sfile + filename)) - result = {} - result['status'] = True - result['data'] = '' - with tarfile.open(sfile,'r') as zip_file: - try: - zip_file.extract(filename,tmp_path) - result['data'] = public.readFile('{}/{}'.format(tmp_path,filename)) - except:pass - try: - public.rmdir(tmp_path) - except:pass - return result - - def delete_zip_file(self,args): - ''' - @name 删除压缩包内文件 - @param args['path'] 压缩包路径 - @param args['filenames'] 文件名列表,数组格式 - @return dict - ''' - sfile = args.sfile - filenames = args.filenames - - if not tarfile.is_tarfile(sfile): - return public.returnMsg(False,'Not a valid tar.gz archive file') - - tmp_path = self.__unzip_tmp_path(sfile) - if not tmp_path: return public.returnMsg(False,'Failed edit!') - - #组装原有的文件 - s_list = [] - src_list = {} - public.get_file_list(tmp_path,s_list) - for f in s_list: - if not os.path.isfile(f): continue - src_file = f.replace(tmp_path,'').strip('/') - if src_file in filenames: - continue - src_list[src_file] = f - - with tarfile.open(sfile,'w') as new_zfile: - try: - for src_file in src_list: - new_zfile.add(src_list[src_file],src_file) - except: - shutil.rmtree(tmp_path, True) - return public.returnMsg(False,'Failed delete file,error:' + public.get_error_info()) - - shutil.rmtree(tmp_path, True) - return public.returnMsg(True,'Compressed package file modified successfully') - - - def write_zip_file(self,args): - ''' - @name 写入压缩包内文件 - @param args['path'] 压缩包路径 - @param args['filename'] 文件名 - @param args['data'] 写入数据 - @return dict - ''' - - sfile = args.sfile - filename = args.filename - data = args.data - - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - tmp_path = self.__unzip_tmp_path(sfile) - if not tmp_path: return public.returnMsg(False,'Failed edit!') - public.writeFile('{}/{}'.format(tmp_path,filename),data) - - #组装原有的文件 - s_list = [] - src_list = {} - public.get_file_list(tmp_path,s_list) - for f in s_list: - if os.path.isdir(f): - continue - src_file = f.replace(tmp_path,'').strip('/') - if src_file in src_list: - continue - src_list[src_file] = f - - with tarfile.open(sfile,'w') as new_zfile: - try: - for src_file in src_list: - new_zfile.add(src_list[src_file],src_file) - except: - shutil.rmtree(tmp_path, True) - return public.returnMsg(False,'Failed modify file,error:' + public.get_error_info()) - - shutil.rmtree(tmp_path, True) - return public.returnMsg(True,'Compressed package file modified successfully') - - - - def extract_byfiles(self,args): - """ - @name 解压部分文件 - @param args['path'] 压缩包路径 - @param args['extract_path'] 解压路径 - @param args['filenames'] 文件名列表,数组格式 - """ - sfile = args.sfile - filenames = args.filenames - extract_path = args.extract_path - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - if not os.path.exists(extract_path): - os.makedirs(extract_path,384) - - tmp_path = '{}/tmp/{}'.format(public.get_panel_path(),public.md5(public.GetRandomString(32))) - if not os.path.exists(tmp_path): - os.makedirs(tmp_path,384) - - with tarfile.open(sfile) as zip_file: - try: - m_list = {} - - f_infos = zip_file.getmembers() - for item in f_infos: - filename = self.__get_zip_filename(item) - - if filename in filenames: - spath = os.path.join(tmp_path,filename).strip('/') - if item.isdir(): - m_list[spath] = [] - else: - if not 'other' in m_list: - m_list['other'] = [] - - dir_key = os.path.dirname(spath) - info = {'src':spath,'dst':'{}/{}'.format(extract_path, filename.strip('/'))} - if dir_key in m_list: - info['dst'] = '{}/{}'.format(extract_path,'/'.join(filename.split('/')[1:])) - s_path = os.path.dirname(info['dst']) - if not os.path.exists(s_path): os.makedirs(s_path,384) - - m_list[dir_key].append(info) - else: - m_list['other'].append(info) - - s_path = os.path.dirname(info['dst']) - if not os.path.exists(s_path): os.makedirs(s_path, 384) - zip_file.extract(filename.strip('/'),tmp_path) - - for key in m_list: - try: - for info in m_list[key]: - if os.getenv('BT_PANEL'): - shutil.copyfile(info['src'],info['dst']) - else: - shutil.copyfile('/' + info['src'],'/' + info['dst']) - except: - pass - shutil.rmtree(tmp_path, True) - except: - return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info()) - return public.returnMsg(True,'File was decompressed successfully') - - def __unzip_tmp_path(self,sfile): - ''' - @name 获取临时解压路径 - @param sfile 压缩包路径 - @return str - ''' - tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32))) - with tarfile.open(sfile) as zip_file: - try: - zip_file.extractall(tmp_path) - except: return False - - return tmp_path - - - def add_zip_file(self,args): - ''' - @name 添加文件到压缩包 - @param args['r_path'] 跟路径 - @param args['filename'] 文件名 - @param args['f_list'] 写入数据 - @return dict - ''' - - sfile = args.sfile - r_path = args.r_path - f_list = args.f_list - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - tmp_path = self.__unzip_tmp_path(sfile) - if not tmp_path: return public.returnMsg(False,'Failed edit!') - - #组装新添加的文件 - src_list = {} - for fname in f_list: - if os.path.isdir(fname): - s_list = [] - public.get_file_list(fname,s_list) - - for f in s_list: - if os.path.isdir(f): - continue - src_file = '{}/{}{}'.format(r_path,os.path.basename(fname),f.replace(fname,'')).replace('//','/') - src_list[src_file] = f - else: - src_file = '{}/{}'.format(r_path, os.path.basename(fname)).replace('//','/') - src_list[src_file] = fname - - #组装原有的文件 - s_list = [] - public.get_file_list(tmp_path,s_list) - for f in s_list: - if os.path.isdir(f): - continue - src_file = f.replace(tmp_path,'').strip('/') - if src_file in src_list: - continue - src_list[src_file] = f - - with tarfile.open(sfile,'w') as new_zfile: - try: - for src_file in src_list: - new_zfile.add(src_list[src_file],src_file) - except: - shutil.rmtree(tmp_path, True) - return public.returnMsg(False,'Failed add file,error:' + public.get_error_info()) - - shutil.rmtree(tmp_path, True) - return public.returnMsg(True,'Compressed package file modified successfully') - - - - def __get_zip_filename(self,item): - ''' - @name 获取压缩包文件名 - @param item 压缩包文件对象 - @return string - ''' - filename = item.name - try: - filename = item.name.encode('cp437').decode('gbk') - except:pass - if item.isdir(): - filename += '/' - return filename - - - - - - +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +Hf+bBTgRv9pVfall8ODpvg== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +1u+XjG/2+GSQRv6EzCaWRQ== +K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU= +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +hUVhKZtvg+FKessuieMZGkP/5FS8EfJsx7HCvhsTdvM= +y9P3+4KXYacR8m2lL04bxyxkWpCOoKKwyE9jOO9xzDU= +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhLTeka+iJoxis1kUHdyggy+yXh3Xc8xsyir/5NbKP+Sj41j2UHgL27dZFRsmbh/P+Q== +KZTmaJLp+FU9X93j5Tpqtw== +0LFyB+q/4AUBkCrX53xYJedFoNZHYELHOLu/R25oBrqozNv9g917+/ANMSb6X+WR +yqM179SzW9HnBHozkU+IlaI6kf6Fax7Db/qE4wQMDFqfwVE+4rG3+IJ9S3c+p3Wf +akHibliG6j9uvBEg96vRehUZmhX3a+KyYm/Obf/H764= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +x41zVQ6DCtWXndDIq7U8L+5quM3ARMCnVm34NUX7l/TJzZZ100EvdUXHXI2cFH4H +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmme9ACxsySMBZr2gsEQniKL +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018= +KZTmaJLp+FU9X93j5Tpqtw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +sV2BMz3qoSZgP7XLsV0u8Ey5TTZbfTeRXmE8Lnkq8ecwI8LY+wsKkeHx5JQmHjbJ +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWW4zkUYYb5UAUX8zS+IvdqmDjQdLy6c68fWPpLBj0d0GY6NLmGX6bobgiWRy9bUsk= +1u+XjG/2+GSQRv6EzCaWRQ== +1VHvxkEcO4espNMpWHmnH39LdUi6HdEZyAx9T9sPSkCOt9wcLgOersS/HmOi2Xel +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +ei/1A+2pKZSasjYF/t4qooATwcryDpeJQCtosnRGcgHSsp9sawkQJUO/Q/xMUnXL +1u+XjG/2+GSQRv6EzCaWRQ== +4nsuLyRybkZg8C3fJCsRPXDWwLMccXwEpFBdFK0JOvg= +ZFD6pnBshb3mpu7xvvuMLemrazA4ssXDDuW5SINX4ZLsvLIwaJ4YyDANze64OGqUEj7NCJs+t+zkcUskHr+gqw== +1u+XjG/2+GSQRv6EzCaWRQ== +g5xFPP9LKBd4uktrRYzGf+p1NdL+k6huN6CrzYXY3fPVQzhtKStMTpRTZpRqmhGR +wlLHv6kT3Q/RmtMBN4nDAbqo0o2IA7mU8WVNqZvoB5I= +VmVrGQo2zRokW/ZuO9bN6+qRbBr2ygG3RGcAIOk727ylzODmV8VRqZ4YKvduSUCu +VmVrGQo2zRokW/ZuO9bN62psx0nYMGJaQ1S1BUtE6l8lgsopQJ3Qf5syQpAasAAf +VmVrGQo2zRokW/ZuO9bN69Woqo49I0Fga24Nj55N9ZL81I98txu56TEAiJ0hswK9 +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh/5aA4AiEej6zodAVzZFzS0 +VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn8lOmX2nmjCcfYXY14ohcOqo/JjN+Uf5m+lGfErLLeSsw== +VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn92m0FWAHkcFEifdcSefZI3 +VmVrGQo2zRokW/ZuO9bN6xQU/k6xhXMbxscs7Gix8nNqUkjAknQH3LrGVvAyjYtQ +VmVrGQo2zRokW/ZuO9bN6xOutEc9teLv333RLphPxTSZMBPTZ5nEHEAZCqisJAYdi0Te13nq3FDKoUwPWRFVhcbFGXs/hncVFmPVZNUBUZY= +VmVrGQo2zRokW/ZuO9bN6wv73IH9kSzXKMUmUPacSk1KDsdrjROzTN1kzduuP0l3 +VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc= +VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLtxlUb0X9CVsj27gKH7yxkc +VmVrGQo2zRokW/ZuO9bN6/kiIWH8hJ6dJOo/fnNj9VLcBHtqpOZ/Rc4AjXcEm1edHKkbtiFAlXrzE+rfp/AeMQ== +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh80w0dM3z0phdMS+lT6j/xo +VmVrGQo2zRokW/ZuO9bN65abAZ39yZLyKCl4nd6eov4HdKauw4+toNc5YoXvsAeW +1u+XjG/2+GSQRv6EzCaWRQ== ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrRvdf2ev6P6vInKuFAO8eQfewP9/G67aA1dZDLRwjari +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmlP+1p+XjT1dvUjFUZHsqC6 +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEAKAgQMZx8+NhDilcUt2veW2ndED3g1z4Otqk5QlUsRCjvCITtLgXlADwlPZpuWSgD +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI +YSlit6erKXylrjSZnvyW8CCccEStX4fvFvduMtCGi54= +D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYRo9qF9iYlR/oIixOO4YW9r5sQV57F10J/zF5jBaEFdKw== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6+8scbwlFXmHwUL6N0BWPj6dlHHDEIlkodgo1A0usjGyHhQdtr/CZJG5zDxIr66oew== +VmVrGQo2zRokW/ZuO9bN65rMxzVrOInziG0YF1uvpqBp8ZS5jWaYG3vnBxPtXcWWa9TIx0uiiZmKhmv6T3Z+yciaFXeGtzFwR28zuXWWSf6Fvx4Mh4aXTcAi9R5RGv8f +M0ZySqkmhuHCw6olbCKv992x+E1VMoPx+GJL+VjXXxc= +b4OJVZe8QyIpjuTpKXDL9A== +xWoGNWjKGPfI4gq8aHoTfL6V0pkYKbeBvdfg5x1FYR8uFPtJ2x9MfdlNdGBKn+Bi +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +6JhaWzGWadTJ3yzCk9OQNgkLy/m1ahTUuEy+6FiV49HNwHkAN2ZMxFTsufeIA0ig +KZTmaJLp+FU9X93j5Tpqtw== +dar6N0tUAb9rU8/aX1b3tGumQ1hhfk8O6qr6gmiQ1+eBvZwFkpECn+RP444+i510 +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y= +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au +1u+XjG/2+GSQRv6EzCaWRQ== +sV2BMz3qoSZgP7XLsV0u8Ey5TTZbfTeRXmE8Lnkq8ecwI8LY+wsKkeHx5JQmHjbJ +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWW4zkUYYb5UAUX8zS+IvdqmDjQdLy6c68fWPpLBj0d0GY6NLmGX6bobgiWRy9bUsk= +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP/1JxkOTIR85eefsT8+ZE4aqE0v0U76615IyJqY5UKTuMtUbLm26g8btb6vlq0a/Rg== +sV2BMz3qoSZgP7XLsV0u8FnjXWiNqL5EgZ3mkCPlBXRqd4GmOGu47uyTs1R79OxGn8j+RZZ1XwLWsKhwXR/uRAfKx5AH0WN2iitz8d+Ezds= +1u+XjG/2+GSQRv6EzCaWRQ== +/Bf0c5l+J3LcMje0y2hT6xexfB3PDHGLzFprRXh5sZU= +gCKKzKpjyg80fGP7iU1CosQydbZEVmkhqpAlx45K/ms= +HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU= +b0AcCU56KvRpMcnxfbDRg2RaKdDnbQt+ar3yTu4ASKKZrazBALhe/1DkqoflPq4v +QOiTGJqcvhOfGILFspJCgFitD1klQoY+1I4jtCCP/XI= +1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmfmpa/q0AQoQFAFtjS1Nskr +JBX7lPwBHKhyRzgJY9OZ2ID8CtKJxnNl/B8H+N4MrEOtqpoairx3dp0kQkKQXLr/i2uDaqSXVIfJ6RfDnJqDlA== +J1UGlXkKhKXD36OrXXhN8J2xIJzaLSbkMLWZBVYhIzKIhtDAsZ6oAssWKRx5BzNP +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +JBX7lPwBHKhyRzgJY9OZ2KI6iis8uoA2jLORIv/XFCtWDyuLfsIYrBQwTzzUuSVO +1u+XjG/2+GSQRv6EzCaWRQ== +D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYQlAG5xlrK2k9RIx8apVXk+fYR83glUbhc3FJlByrwJkQ== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6xHuLV+pwxqKLEVIaJlVY1mV5wixDkrlkeQ/46GnHjPJ +VmVrGQo2zRokW/ZuO9bN69ftsX0HT5bqVOOMRQ1dnH6J2cD5LXLKGBEYYVmwvLbOnW/+ho3f4pS5gGHbjP7S6A== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTLBRO8Ka4sxejzogHbwTSQCRjmbMCS45yRDKakJA+6lygvtX8/AEkLox/HgNQ0WReKAZvStGcdGPMqn6vgZv5Gu5msyaDAcqtS9MyGyGb9+g== +1u+XjG/2+GSQRv6EzCaWRQ== +oCwMuj/95ZXy+xntYImc0fRuDbZyEvkaVbFAkuMpm9yWIfTaDuSAdU/L7ROHPmO1 +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +O2CqINTuIULxX7Gu5GskT/Nm15YFovaVXDj8ylNfQi3d2Qbp7uZYhyvrRE1b06DK +KZTmaJLp+FU9X93j5Tpqtw== +t987AUZxQR72IPRcAqvQ/tuxrpVopv9MhEHHX4HHOFnF6WZD8PVxYK60YbaBpzoa +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +c1XU6vxl7MsQKGo3r4amMgGxGyqG90Nz+St+6838nV5Hy3ZAmDi9Ecxx2gyS4yZO +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +XKWK9LfYDT5w2LEWWxwkc6l2k+0eByijzzjX3gaj5k8= +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP/1JxkOTIR85eefsT8+ZE4aqE0v0U76615IyJqY5UKTuMtUbLm26g8btb6vlq0a/Rg== +sV2BMz3qoSZgP7XLsV0u8FnjXWiNqL5EgZ3mkCPlBXRqd4GmOGu47uyTs1R79OxGn8j+RZZ1XwLWsKhwXR/uRAfKx5AH0WN2iitz8d+Ezds= +IhuisKim47k91RVt8z8qtAZ7rSCYljkiJH9HzGQ5OPx8FxzBv8dhM3FQlWygMwgCxPuj/L0zzaJH7M9JLtcNyBQAqW+52jDxmeGRDtqtSp8= +1u+XjG/2+GSQRv6EzCaWRQ== +/Bf0c5l+J3LcMje0y2hT6xexfB3PDHGLzFprRXh5sZU= +gCKKzKpjyg80fGP7iU1CosQydbZEVmkhqpAlx45K/ms= +HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU= +b0AcCU56KvRpMcnxfbDRg2RaKdDnbQt+ar3yTu4ASKKZrazBALhe/1DkqoflPq4v +QOiTGJqcvhOfGILFspJCgFitD1klQoY+1I4jtCCP/XI= +O3CUgrw2GJfB+mDjH5+NdkjK1xGXzP/MBLULODELp6gwPy9qqsLvy+O0nI8WaT4C +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +JBX7lPwBHKhyRzgJY9OZ2ID8CtKJxnNl/B8H+N4MrEOtqpoairx3dp0kQkKQXLr/i2uDaqSXVIfJ6RfDnJqDlA== +J1UGlXkKhKXD36OrXXhN8JAqE/N8+U09yjtfA8xA8yW9SYS4/U88+D8eMNmNqLL6 +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +JBX7lPwBHKhyRzgJY9OZ2KI6iis8uoA2jLORIv/XFCtWDyuLfsIYrBQwTzzUuSVO +1u+XjG/2+GSQRv6EzCaWRQ== +D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYQlAG5xlrK2k9RIx8apVXk+fYR83glUbhc3FJlByrwJkQ== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6xHuLV+pwxqKLEVIaJlVY1mV5wixDkrlkeQ/46GnHjPJ +VmVrGQo2zRokW/ZuO9bN69ftsX0HT5bqVOOMRQ1dnH6J2cD5LXLKGBEYYVmwvLbOnW/+ho3f4pS5gGHbjP7S6A== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTLBRO8Ka4sxejzogHbwTSQBD0QaFQ6mH2ykhpeatV5MFSAvHLSQE3mXQCKlo9Vmyj7/c2aTE5OzSuRlIiCt76fbCvV4M9DgyzOc6QS8FnjVg== +1u+XjG/2+GSQRv6EzCaWRQ== +oCwMuj/95ZXy+xntYImc0fRuDbZyEvkaVbFAkuMpm9yWIfTaDuSAdU/L7ROHPmO1 +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +D5nlraEpeGmKL9lxoq591txjX2tGuFl4D7WJZ/91lnMQ6WZrDXvU2okqtgoaqWSS +Z8UsPk1Q7HtwjRd4g01ryw== +VcOpbkIFMz59VlIHGus1M+TodC/Ia97E/RilXZKKBR29V7X/RlCXQzuWibEOEJfo +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMjvzqCkQbmHuG+orSe7QiThO3NRqf8lDLQklcqf6u2x4yWOeax5QyyroyrE443cDvw== +c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y= +Z8UsPk1Q7HtwjRd4g01ryw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au +77Mf9+1xBvZiUeNTtU/s60Kyn+UEu/OgKFt23E+wd8BiTmgbWFTzjcyPW/imPN9p +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYmSt8oPS+2lwNla/wCY62SBZ7HAsGrtjJH7dzi+5hXSr +NqhMbY8+EQI8zhTG6Zh2I5csQ+/eG6egKX2sKZJStJZk02bOBi20A8UHGNy6sybt +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEAKAgQMZx8+NhDilcUt2veWwUPocpK+8JugxVoHXyHBSRb28kLWdpmZTTf3StM9Y2XDpZ02OI3BeuyFvxLweVX9w== +wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG +NqhMbY8+EQI8zhTG6Zh2I0BiMnuwr8mLUTT6rraGmzoW2r/izQNInq2UmHNhRbv+ +1u+XjG/2+GSQRv6EzCaWRQ== +D1uzgsuXIKL/yxNsPwFM0bn1b8lc4rkM7jKkoTPs67jZgYEdG3TOZllO4+vZp0ZM +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN61lflwobhmxel33RwHEAzsw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67AsrXUPIcMWmsNJ6skMzcKKCpn1+SzebzO6hgphZ4RaLaD/MDC8FwcBZZJFOSWJmA== +VmVrGQo2zRokW/ZuO9bN66NLXvUSFhBjtRrUbZsPQNOrrRiOaX/5u3wb0Rfqy+pv +VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe +VmVrGQo2zRokW/ZuO9bN61ODJTwuVdYa2e77kV4UyyiKC6xmDTYLpzmUEL6iaKIotT6XRfjdpuPhvefHRzC+7lCYolxcDYktKmTY39wnf+k= +VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLtxlUb0X9CVsj27gKH7yxkc +VmVrGQo2zRokW/ZuO9bN655297gigIv+4Ufy0bubbx7ffTJUoURR64bKe908x4ap +VmVrGQo2zRokW/ZuO9bN62cJZxyhxghVn78AgYWoneU= +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbdSOgZt1gFP6QuzR+PijaPfkXI6n5+LDNetR25SN0GXgg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkns33MIHJ07fAs3bEPA9prEegTrYMIIXPrzC4jD9xKXsA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+VE/nQEHJOLSC1DnxvG5TiVhVg6gVuqdGuJQhlv12yWuKoI97jUkrKrXkDjch0nxA== +VmVrGQo2zRokW/ZuO9bN66Stm18E9cFphVzr4DVvkp0eMQP2KdIRCCvQSRwvi/ogRLfVX0ez0AMMWvL6BswFfzkKtdTc18Qoh/tgpwV2o+KvhObTvNhFYenGcSOQ2IfJ/VBsqInH+jm5934kDPyo5Q== +VmVrGQo2zRokW/ZuO9bN6zsPJOFHzvayEpAPvGiJ0lAWXca0TugmxSi8hINZZvoHQ1Y8EIw5ZU/3qx6IfU7mzw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmgImK713UKKRnRLyQBV8gyjNySqhD1ER71FyIggw6WuTb4tOz+zOmUJvBAa9I7hKop6pGwEoB2371+83RgakZhyjMJWosIoeLReEb5GcqHNA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkrikeB3PyYnRyHWKvzXceKTTsB9Sbgf9uM/v0isHHFakzQxpTSrdsYuODmqyQGE3Q= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknz2So+gFu+BijKlvPtsNKCIi0RkX1BFpd3GhHKBMdoB6DedipwufkrnDIRLB493gRtMiXCiHSsTBNmIXqRl2RK +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkqG04WXvd0zYTVxLMDUCWknQtobuOLFvBEBrZieoy/MQ== +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk6a/inNeVFTn9NarE6P/3LG5P9KtqHegfYsmaf6Actxw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yaURb+J41vJInDMdNBqADJBPzFwU1DWEhzV7Igw3NGvcy7cQ4IWtwYwzl4xxnS4q7aao1GGQ/lpjefMWmVC5io= +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbfaLzRNSm7AkFFrFp+7ESwXtG1lg7SuOb/Fvk+nDWM/LTeUYMfTnzndr1z2COlogy5E3igzgZFBhcAjJ6avABsS +VmVrGQo2zRokW/ZuO9bN67FJHoI0w8vvrPd3RbddRrD2Em+ygcQFnOdLaXNs8ffOLcZZMjCFzoLC6LOvXRJel9lRMBQd5MAQelNetUjr7a0= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN65aFRI029wWEhVZc+cvXTRpzda0UlKTw9izNui/xkgdD +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6ylpzdD3c1E8cbuPHoSW5RFJzxWCEvXq+6grREYT2q2eRpfKq4CUkXaSg9PBb07Brg== +VmVrGQo2zRokW/ZuO9bN65qgzI0QqVLALq/Lz0RhHgVj4W6v2iAhy3bpNtZbOHJ8qR+FfOTDkpmrnn3Bg7loWg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfko6jVO83Qfz+7nZDbM61ZOu8HuZ+ZNu6hMvpayaQR5SAc= +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfkosj3Q/Uyc4ih1OKYxi2Aoat/XPMKk6ZFw9dS97mdyNCW7sbOTUzQ7vKis/hh/FWBl +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTcB3+DRCAF+pezmRjCID0udaZbU9+Bp25aQzLB8QDwQjJOciYA4ZMGenxD3oVSnPjhf/xssdSo8p33UNNP+7LnwEXEbYt+fXn2F5oiKjC6Gw== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOF2h+KCvqR3zTaYTe9GwY3cxsP+/BBmVOckz/EpSQTZmvBKIrz2PIwNuNhzK8OCOI4= +1u+XjG/2+GSQRv6EzCaWRQ== +nfBQrFU5Tl0d1VcHhIJkAP9Tx9PwP8cRBjb6FOkkDsaLG6R4uDQDi4xcdtmOXEqB +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P3euInP55YyecjBWhnEnlB7Lw5dMNDV3iHSK0kwD9rha +yqM179SzW9HnBHozkU+IlV+BVKi+rIq6p9thxVddK2wK0J7yGt1J4fq2cywdhxRL +akHibliG6j9uvBEg96vRemf0tAA5Rm6aB+lqKw8WFi0= +KZTmaJLp+FU9X93j5Tpqtw== +IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEA3MxUp7pEuHhp8IfRMvwD6SZPogdTtHhT5unY8iIrUiU6GaMq6uosuXq8hjxV9nUgwa2iqC1n6Ziz6tiDdPy1mw== +D1uzgsuXIKL/yxNsPwFM0bn1b8lc4rkM7jKkoTPs67jZgYEdG3TOZllO4+vZp0ZM +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6+8scbwlFXmHwUL6N0BWPj5rk9N0kB/j2zz5GqVVZxsc +M0ZySqkmhuHCw6olbCKv9xWw7HeiAhxWgwzOj+uORvYUGO2Wun1zPlqkYizDiyWM +1u+XjG/2+GSQRv6EzCaWRQ== +00qsNOHBbp9Q+MTSOB/jV1gEYraNSaFlJL1NYP7mKBg= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +mObL0pW76xtWwwYeubgb11DPfOo/O10pxYUkfDIVCjm8OCRNT0T/bbX1vgDt/X1W +KZTmaJLp+FU9X93j5Tpqtw== +H7d2MP+WgR9k+129/mRN7ij0WzXIk6KGh6j7E1fgHISsYBh1T2OGWCH8QUwPOkcn +c1XU6vxl7MsQKGo3r4amMvdbawOnt/oBPSHXSrswDVS5Xue1Y7sB0F2vPYt641B/ +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +c1XU6vxl7MsQKGo3r4amMqf9zivp0Qb6thhCnxaiYdHXwUNLWpYP828px7Gieygp +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +g8q6WqkWsk4SUh3d5nzGDIJanaAux29CDdQxdeG/k+U= +Vyh8kMpaq2Q+odra9mDJ3Acz3kaMgWRroPCWRIzYRMY= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP/1JxkOTIR85eefsT8+ZE4aqE0v0U76615IyJqY5UKTuMtUbLm26g8btb6vlq0a/Rg== +sV2BMz3qoSZgP7XLsV0u8FnjXWiNqL5EgZ3mkCPlBXRqd4GmOGu47uyTs1R79OxGn8j+RZZ1XwLWsKhwXR/uRAfKx5AH0WN2iitz8d+Ezds= +1u+XjG/2+GSQRv6EzCaWRQ== +OKNarVhdXTeaHF7smpo1Fsg5Z6GS4ctUP7SwO9m7ADsrK59qTlLvB8EsnHNRomb5 +HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU= +mZ9rrP32sP7RJKYMPGILD+78ba2l0mR6sa16eW97WcI= +O3CUgrw2GJfB+mDjH5+NdlB3aQOXnKkk0z1WAt9Jmo/evqziM8DhkT0YrYGb4COh +VmVrGQo2zRokW/ZuO9bN67lx9HSk8eNN2hBCO4HpKRk= +VmVrGQo2zRokW/ZuO9bN69ZjyuXvRyGNy9kZJ0BSUPrXcl5zPRPxpUUNlqBFMfTepJuwObNjyv5s1Bwxw+0zHA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yiPlSW1IkAq8PnO6zy+iP3jJCfd1bhtOH5rekZFEjXi +VmVrGQo2zRokW/ZuO9bN6yIV96dETdHvOTzc1eoxsu7+haDOTWWWdxKhfAdfjn09 +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +VmVrGQo2zRokW/ZuO9bN60+BaOrNhyGitXORdqvsMmwFtJw9x+0nfzpYbP/xMELoydQeOelN/AlsLE8govEqlMQrfUl1PeNk2rX8fh0ZElrak4Q4jLc+GGiQ0DLMMpnH/1J5GKH49RP7rJbs5dxgJ4tOonH1clyp6d9GTs2dKrA= +VmVrGQo2zRokW/ZuO9bN60jWe2OHiR8czCXypGRfxf3KT4lPFL0wUA7wcV6fbrUq +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN62YWnBw/cibruWabHzPuqaAcn7VyIMN4YMuZ3S9CW09ENvrO91BA4ldYfyyQpiR6Cy5R4Fei8M2XzZTUjvQbS6iXg3tLmWav6AtitMV2FQP4 +VmVrGQo2zRokW/ZuO9bN608vqClPKbLpxLXcA30riX215gq1D0BrebU6pLhAhTm2 +1u+XjG/2+GSQRv6EzCaWRQ== +/Bf0c5l+J3LcMje0y2hT6xexfB3PDHGLzFprRXh5sZU= +gCKKzKpjyg80fGP7iU1CosQydbZEVmkhqpAlx45K/ms= +b0AcCU56KvRpMcnxfbDRg2RaKdDnbQt+ar3yTu4ASKKZrazBALhe/1DkqoflPq4v +QOiTGJqcvhOfGILFspJCgFitD1klQoY+1I4jtCCP/XI= +O3CUgrw2GJfB+mDjH5+NdkjK1xGXzP/MBLULODELp6gwPy9qqsLvy+O0nI8WaT4C +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +JBX7lPwBHKhyRzgJY9OZ2ID8CtKJxnNl/B8H+N4MrEOtqpoairx3dp0kQkKQXLr/i2uDaqSXVIfJ6RfDnJqDlA== +J1UGlXkKhKXD36OrXXhN8JAqE/N8+U09yjtfA8xA8yW9SYS4/U88+D8eMNmNqLL6 +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +JBX7lPwBHKhyRzgJY9OZ2KI6iis8uoA2jLORIv/XFCtWDyuLfsIYrBQwTzzUuSVO +1u+XjG/2+GSQRv6EzCaWRQ== +D1uzgsuXIKL/yxNsPwFM0QIV7+TILj/tblfnXjJ0GYQlAG5xlrK2k9RIx8apVXk+fYR83glUbhc3FJlByrwJkQ== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6xHuLV+pwxqKLEVIaJlVY1mV5wixDkrlkeQ/46GnHjPJ +VmVrGQo2zRokW/ZuO9bN69ftsX0HT5bqVOOMRQ1dnH6J2cD5LXLKGBEYYVmwvLbOnW/+ho3f4pS5gGHbjP7S6A== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTLBRO8Ka4sxejzogHbwTSQIMBV8oz+JZw1lVuk9zsKubda9eiR1++L545YBfEjatGypdQPztU2DAF6OXEg3W2a3ltwtcgo9+IKhfYuqJESBw== +1u+XjG/2+GSQRv6EzCaWRQ== +oCwMuj/95ZXy+xntYImc0fRuDbZyEvkaVbFAkuMpm9yWIfTaDuSAdU/L7ROHPmO1 +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmA0y/dvI19s9e1BRFCkk/QzH4UG1JBu620e7CQwjbQOOUvlgFJz4yLxls8aSEEo +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P+yZLrQY+X/LEpSBMw7xwfahIuTImHO5EcUQN9HQmgqX +LikBEoRjt8wwWzUZNw+jJqDVmHo0aqmO8fx4adbRXm90KhRgBH5ZfsJyfEAx+4JR +akHibliG6j9uvBEg96vRei563dWr9knc+JrDspjHc/A= +KZTmaJLp+FU9X93j5Tpqtw== +BIa0ibHnqUboaK9kYHYO4k/Xks2d0kb8EqwPCudb2sE= +b4OJVZe8QyIpjuTpKXDL9A== +PRG/YRzXVD8iVR3bzEcUnu1TFK0huFhoat7c0/vfEZdQ5ejFvZhOUgdVFA1LA3t54/DjYCmHvkQ18q4IM7keqQ== +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +6XDh38s4HWrzA9ChHtrpYV0DWWNc3chcp0di+Spm+pg= +PRG/YRzXVD8iVR3bzEcUnpfl+XcdHX8i0iNThoY4QPk= +5wsVwKLrIjIWqeTNpSVp9SZAE79lgehXxNsrtfaN8Wk= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== diff --git a/class/filesModel/logsModel.py b/class/filesModel/logsModel.py index 03592c88..8391b550 100644 --- a/class/filesModel/logsModel.py +++ b/class/filesModel/logsModel.py @@ -1,289 +1,289 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# -#------------------------------ -import os,sys,re -from filesModel.base import filesBase -import public,files,json,time - -from BTPanel import cache - -class main(filesBase): - - - __objs = ['bos'] - def __init__(self): - pass - - - def get_logs_info(self,get): - """ - @查看日志 - @param get - limit:每页显示条数 - file:日志文件 - """ - p = 1 - limit = 200 - search = None - file = get.file - if 'limit' in get: limit = int(get.limit) - if 'p' in get: limit = int(get.p) - if 'search' in get: search = get.search - - if not os.path.exists(file): - return public.returnMsg(False,'Please specify file!') - - res = {} - res['status'] = True - res['data'] = self.GetNumLines(file,limit,p,search) - - res['md5'] = public.md5(res['data']) - res['limit'] = limit - - if not cache.get(file+'_logs_info'): - public.set_module_logs('files_get_logs_info','get_logs_info') - cache.set(file+'_logs_info','1',86400) - return res - - - def set_log_split(self,get): - """ - @name 文件切割 - @param filename 文件路径 - @param stype 切割类型 day:按天切割 size:按大小切割 - @param size 切割大小(stype=size必传) - """ - filename = get.filename - stype = get.stype - limit = int(get.limit) - if not stype in ['day','size']: - return public.returnMsg(False,'Cut type passing error.') - - if not os.path.exists(filename): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - if limit < 3: - return public.returnMsg(False,'The number of reserved copies cannot be less than 3.') - - data = {'type':stype,'limit':limit,'addtime':int(time.time())} - if stype == 'size': - size = int(get.size) - if size < 1024: - return public.returnMsg(False,'Cut size cannot be empty.') - data['size'] = size - - public.set_split_logs(filename,1,data) - - return public.returnMsg(True,'successfully set.') - - - def get_log_split(self,get): - """ - @name 获取文件切割信息 - @param filename 文件路径 - """ - data = {} - sfile = '{}/data/cutting_log.json'.format(public.get_panel_path()) - if os.path.exists(sfile): - try: - data = json.loads(public.readFile(sfile)) - except:pass - - return data - - - def get_file_ext(self,filename): - """ - @name 获取文件扩展名 - @param filename - """ - ss_exts = ['.tar.gz','.tar.bz2','.tar.bz'] - for s in ss_exts: - e_len = len(s) - f_len = len(filename) - if f_len < e_len: continue - if filename[-e_len:] == s: - return filename[:-e_len] ,s - if filename.find('.') == -1: return filename,'' - return os.path.splitext(filename) - - def copy_file_to(self, get): - """ - @name 创建文件副本 - @param get - @return - """ - - sfile = get.sfile - if not os.path.exists(sfile): - return public.returnMsg(False, 'FILE_NOT_EXISTS') - - spath,ext = sfile,'' - if os.path.isfile(get.sfile): - spath,ext = self.get_file_ext(sfile) - - # public.print_log(spath) - for x in range(1,1000): - dfile = '{} - copy ({}){}'.format(spath,x,ext) - if not os.path.exists(dfile): - break - - get.dfile = dfile - f_obj = files.files() - if os.path.isdir(get.sfile): - public.WriteLog("File manager","Create copy of the directory [{}]".format(sfile)) - return f_obj.CopyDir(get) - - import shutil - try: - shutil.copyfile(get.sfile, get.dfile) - public.WriteLog('TYPE_FILE', 'FILE_COPY_SUCCESS', - (get.sfile, get.dfile)) - try: - stat = os.stat(get.sfile) - os.chmod(get.dfile,stat.st_mode) - os.chown(get.dfile, stat.st_uid, stat.st_gid) - except:pass - public.WriteLog("File manager","Create copy of the file[{}]".format(sfile)) - return public.returnMsg(True, 'FILE_COPY_SUCCESS') - except: - return public.returnMsg(False, 'FILE_COPY_ERR') - - - def set_topping_status(self,get): - """ - @name 设置文件或目录置顶 - @param get - file:文件路径 - type:置顶类型 - """ - sfile = get.sfile - status = int(get.status) - if not os.path.exists(sfile): - import html - sfile = html.unescape(sfile) - if not os.path.exists(sfile): - return public.returnMsg(False, 'File or directory does not exist.') - - - data = {} - conf_file = '{}/data/toping.json'.format(public.get_panel_path()) - try : - if os.path.exists(conf_file): - data = json.loads(public.readFile(conf_file)) - except:pass - - if sfile in data: del data[sfile] - - if status: - data[sfile] = status - public.writeFile(conf_file, json.dumps(data)) - public.set_module_logs('files_set_topping_status','set_topping_status') - public.WriteLog("File manager","Modify [{}] top status".format(sfile)) - return public.returnMsg(True, 'Successful set.') - - - def GetNumLines(self,path, num, p=1,search = None): - """ - @name 取文件指定尾行数 - @param path 文件路径 - @param num 取尾行数 - @param p 当前页 - @param search 搜索关键字 - @return list - """ - pyVersion = sys.version_info[0] - max_len = 1024 * 128 - try: - from html import escape - if not os.path.exists(path): return "" - start_line = (p - 1) * num - count = start_line + num - fp = open(path, 'rb') - - buf = "" - fp.seek(-1, 2) - if fp.read(1) == "\n": fp.seek(-1, 2) - data = [] - total_len = 0 - b = True - n = 0 - - for i in range(count): - while True: - newline_pos = str.rfind(str(buf), "\n") - - pos = fp.tell() - if newline_pos != -1: - if n >= start_line: - line = buf[newline_pos + 1:] - - is_res = True - if search: - is_res = False - if line.find(search) >= 0 or re.search(search,line): - is_res = True - - if is_res: - line_len = len(line) - total_len += line_len - sp_len = total_len - max_len - if sp_len > 0: - line = line[sp_len:] - try: - data.insert(0, escape(line)) - except: - pass - buf = buf[:newline_pos] - n += 1 - break - else: - if pos == 0: - b = False - break - to_read = min(4096, pos) - fp.seek(-to_read, 1) - t_buf = fp.read(to_read) - if pyVersion == 3: - try: - if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8',errors='ignore') - except: - try: - if type(t_buf) == bytes: t_buf = t_buf.decode('gbk',errors='ignore') - except: - t_buf = str(t_buf) - buf = t_buf + buf - fp.seek(-to_read, 1) - if pos - to_read == 0: - buf = "\n" + buf - if total_len >= max_len: break - if not b: break - fp.close() - result = "\n".join(data) - - if not result: raise Exception('null') - except: - result = '' - if len(result) > max_len: - result = result[-max_len:] - - try: - try: - result = json.dumps(result) - return json.loads(result).strip() - except: - if pyVersion == 2: - result = result.decode('utf8', errors='ignore') - else: - result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore") - return result.strip() - except: - return "" \ No newline at end of file +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +Hf+bBTgRv9pVfall8ODpvg== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU= +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +9ewEGhR02rX1CYy9TbJShdc5zOrGhv/95JJVBawwTJI= +1u+XjG/2+GSQRv6EzCaWRQ== +QtywOSt2jU7XEVQfd46hXhlGuDAcY6mFXPkzqinx4hA= +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +P0xFZRxBgzs6EB2PV1P1Ga8KWkeEOjCa2HwBgtCLnkI= +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +/tIKPI7pxbgiB5DGE+JcX8inA0z7UW6nw0fpA/nuJtJ30NtW1IQQsUdAjwgJ+5zC +Z8UsPk1Q7HtwjRd4g01ryw== +0fJLHIpaUEUEURheAjqV+C5PbuPsfeNtAPgfcFiqP5w= +ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk= +zxNKIz/Dwj9Harw3dUNEQJhfwzP9e0g+QEHF20IF9QloJJn499jB5lN74CMN7xKF +PRG/YRzXVD8iVR3bzEcUnoM4TQf6qaKLuCt8gJ8NfkQ= +Z8UsPk1Q7HtwjRd4g01ryw== +y+v+I4rQ8oQ+Y894tAwqPQ== +p6i8iK7HDbvmLpoFrhNyv2ns9UjKsYvBLT40sf6Ap28= +Nxw10FAZUk+m0nadC1hdyy162NCelWBcYGf8TIFoMWw= +gw7rFE+f9bVF+DtSprQxBWIvcXcHzAaIYdK9TCMnZFs= +VIHA31zdnuwVlvUv3VOlYVRNGNAMqDxg7g3P55Yig7XGJ0frgqBbhflLu9pZDsGtG7PWJFzYnbAhv0HasJD0eg== +mcQwVxjUw79rwGN6NopbQ6kcPmfht+aHpy4N4vzTb9DbhhPn/FiUW/rz0u7pkKGy +X/Lp4PsbbXulceSXUvGUpDfqLabbqgG4MZE+Cdy4FwlQTZqFS7MLKRTLDMwaDvILIRxQybc5sD1eU9BOwIwsyw== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJjwdSnpWE4PzbAMHxIUoYAb +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUZFcA4o3wUnGm4YjdorXMeGmKOMmT0MYEr6v6Dtj7fXTQgHn4plKdmzjTMeVlEm5A= +1u+XjG/2+GSQRv6EzCaWRQ== +QeR/vJltSrUa36sdgLk/IfL4q9jp7Kh5fPIA7nHg0hc= +qkpuPeWx54kZb1C5WIa62tKtrwMAUE7xRV1hPFjc0nk= +NUDHKeYglPKQjXCsTjqHkDpPG4Ff4llamNWSKelBxqKmBtoeDOPkBptxNl9ae6xEQqyUrJT9AnSRTpSh7aXAbg== +1u+XjG/2+GSQRv6EzCaWRQ== +ezgwiw1QpepwAFNSsnvQoOJN6u8DqbLWz8qgHsO6aqB6RHHe5HToLp0+UqRpP8fb +z+VhFG0r6WY/HBghnJVPENgMUP6uURPQG10QAuXe9q8= +1u+XjG/2+GSQRv6EzCaWRQ== +o3kxTO4RwuXO7GZ9wpXG+Wq35U1WRWDZvf7dXlWNmacOOTCJLfQ4KEfT1r5nSH7h +xWoGNWjKGPfI4gq8aHoTfEof00xZubKgl7GPVFGSDVd7TSf0tyOZVdADGcIojOkDe5MWF/mzQs4D73pMqY5dZvgzuA9ObZjdKCQqlGE/ZFc= +7jSw4GB3eBRbmC1MzdtYH2nxYtjMgz8klBoQVhVoidWwT8r1LlO9NvZUaeVEEEdTW81nQdiNwJibED1dKYQBkw== +ruTTNFTdswRs4Mc+srnRkxKrfMU4HqZxGxt1RxHe024= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0J28z5Afxj/RLIBl6j3pAgNq/oTIsu+AgqPjVKJEPvv9NLcDpzlB17tpU0lSBAVA +Z8UsPk1Q7HtwjRd4g01ryw== +HoOSkwUjMH/v4OzYfSLZHLuEmVzOc0Jcac6Q7EBdZLI= +wOl7lRII4ZgqRTj9M//0b0Hmiipql0KqOySDsulvH0Su8cx+3vVrHwSELz95Nt4O +yqM179SzW9HnBHozkU+IlZmT/v3wYMBi43GV3/6zTQaofEdG+x7npWVs++h4r59tCne/rOxDxzdL+ulBEDhdXzPWQJHGam+A14zKSyFK8tY= +yqM179SzW9HnBHozkU+IlSh6plWR6D4Za/srbzCBndvZ2MLXINNMDiNewLwvNACvbuwcvh5NzzpKTCtz6o++Uw== +Z8UsPk1Q7HtwjRd4g01ryw== +BIa0ibHnqUboaK9kYHYO4tix8ZPIpY4+eXNZQL6TNBEr37K1gp2hFIgZaVuDpKry +wUvHMMqfJEAtxgmf7WcojEsJZ/q+trcw4QZczEaX1wc= +p6i8iK7HDbvmLpoFrhNyv+jCXQ+9uuqm20drf0JQzoo= +uTEK8Ng11d3ix2pA+DD/aUSvw2QhKfq2muw9OeWLUPkFXMoY8H1+WYM2ZgNetwFM +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVoIIr4UQdxImXSR4+nP5rPMN4durEeX4gdmJz+ASa5AAk5l7XFK6IcVVpMUMCMJmI= +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJh6sAOIfkvF5oIqf+7bT65s +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +ksF2R1E+pQ3tpguSadwptSjHYbVoil2NE4uVOms8YOU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWjtdfz4jUHOisd7iLe9SzEDuNihpqofFUHBGmLmre0CFX801z657OYko3yu9pDZ2cMrn7XCoaQHi8TBS6BfnnXRgmoO+AsowNhXyROWGVmDQ== +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpMWEt1OR4iOGZbYxwMVSGXaSRClwJ7H8/1TEbkxkYcSscQPjgl6VzrZ1rA8bbkzLPGLtGrShGu4zlnLbt+BiXXQ= +j9BWZnPkz9pNgv1SXK/8DQ0oIRVkw0dZu+9bbZ9Z2fk= +N5/4MVITy7EOJTlZiwcB9agKfbGr368xpyc0rhumQZEuHxhitKliD0GwkptReLUf +J1UGlXkKhKXD36OrXXhN8NU53NrHG2SKWIiwAUeD9JY= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrQorUkV/0Z4czhLJwFbLtQgDiot4dt7MCR+IyRl/JXPGwvD97k+zv3alg0zn9SqxNU= +NhnIR3Ilo4H2su9/cTNo/Nhao8jRz1mpKHWzTHDhdaWoB5q4jnCTxvFKzgSknYQc +1u+XjG/2+GSQRv6EzCaWRQ== +Ic1WBp01nLyRxbqJnsu2p0K5AVX4XdWNyelWOIV6Ri/Zcn0aKD/Dw9aXBHAKiMi1 +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOEns7mun1htPlaJ9Dut7srZPs8zLx2mnV8J2BNAbKqHDQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawEUyGCXGDdulnbl2dYZOPGVzG5m1Glq7FFUvGH6sdkQl +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P4RgpdUyasZdSn+oHMc2tG5Ug0ojdlQR9cuRD1kYLMhs +wOl7lRII4ZgqRTj9M//0b0Hmiipql0KqOySDsulvH0Su8cx+3vVrHwSELz95Nt4O +Z8UsPk1Q7HtwjRd4g01ryw== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +YdUmdqAC07VhbSt1h6KCDOQhlWQ9jG4k+MaSOV9j4FHOiSK3ETa4U5I3fxOq7tLF/PYmB1e7iFQcBIVDEiWJ0hG8ZfezZD+o0fXjWq7Tix8= +WHkOzVx7seuLmxs5Hu+l/sD6YH+8F+kQZFoPxa6Xx38LU1lUEpTxHz0FS+pwLXBn +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN63t2MnnwkYHotYsrWH9vRICglWvh1CtLK/YdWGbWd1o9f1tzSxswHQJ7jeusy4I/wA== +M0ZySqkmhuHCw6olbCKv992x+E1VMoPx+GJL+VjXXxc= +1u+XjG/2+GSQRv6EzCaWRQ== ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrcZEDr9DJDb9XjoXgIX///kKtoJQ30XXawDWakYG93Yo +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/Px0qyBBTm/Di4HeEkA6LeK0wkzA9oyOBq1r4iQdNVJFx +wOl7lRII4ZgqRTj9M//0b258VwcrBXb9+d6gAS2zZVE= +Z8UsPk1Q7HtwjRd4g01ryw== +YPfxe0G+MGXmXAi6U4BEkvyb2XevHxdc8FMThKGhtzEMysaWtPfSjbuwkZA3pYeOFKhCSFYG+dkVrsVae0IfFA== +AJ6YSai03eOKVxBS+s+A+4Cy+lk/ZeoSfixZxH7jcBI= +h6wp9SZ6w+zAVENITafY7hRljTrwoJCPY0O2fxDuqa0= +ieyNFTb+50j7HJWl7nbiRb4GCc6WHw87gawZDTQSQBAN4boyX2ECQxLm67quRnkg +gZ7BoIAABc+I8g/rYzq+fxkznXPQP3aR2IC/o8MbAWFRcDH+nxQt+h5tN0pl/k/R +gZ7BoIAABc+I8g/rYzq+fxpmPvrlSDpm2/4GV4ZL52N+f3aoouaz0DKjSpxvecRv +VmVrGQo2zRokW/ZuO9bN608SN+rl/c82Fx3gEheLtEPWenSoQZ0ErgcGs7SYPEIE +fE3zmgbfhO9ZwEda5GgwD2xm2m7B0YFtqnQCEsPic9MwVxNnLGn04EN6FjhytfIHBDJPSIwM3+fK2/ZA7HX2Eg== +9uF3R/OfNUGVQlxbTNUS55dJWWqKoFe+2B8l4N6Jc2pEZCkxDuC97C1dDY8PNwf/ +1u+XjG/2+GSQRv6EzCaWRQ== +x22JLf5+9uUTDUdpgwgZ9O83E1bfgaVhykmWdJ+nZXJQ9kCBq7GuTG8v1IJzJD5h +Z8UsPk1Q7HtwjRd4g01ryw== +dar6N0tUAb9rU8/aX1b3tPPCrmt+A77OlLLc0hq85JPAMBfiIm5+TeqFwN72RdG8 +ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk= +3AcPLYoMMn4rxWOUMA1wJdOmdYprhThTGxGyDnWq840= +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDCoYsQLlWzDGUd9QUR41IHk= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW+2pW4n/W6qWQ7fvKEZcw6lORQHv34KyeA811LNUCOgw== +1u+XjG/2+GSQRv6EzCaWRQ== +gZq1mJvnqxTNLPjSpQ6bfKeb6fxkOx/+dElrFnEieCM= +WHkOzVx7seuLmxs5Hu+l/v5tNYogyNy2bAogm7myZaPcxZDZSm5ntWmgTrqmpiFB +JlxIfINDpIN74VOPKABZWw1pDjtDv1WACEXIhyxtaYCMs+iDnrosPIR34X2MwwO9+dspbEyZ5RinZUNx6/2rAA== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +NnnRSiFB8Vtr5PXra2GmCcwzujRQX5ogRv1ZA//+TDFXEzK++s5GANm+fB4wZ9b1 +T6Eqs+Bi2aLwH9GlPE5AbHo5JCADBnfOf3YzkqXbPUf7ww5fPYa7xY2H8XgXCiUfUway+41gmA7KI8VTyYCtUQ== +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033YGIXZ5LurDt9cgLUto0D+F +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +1u+XjG/2+GSQRv6EzCaWRQ== +a/jv9CzWHWz0h350oJLqa5TTL4zXej8PqERC4mSIOhc= +e6XLp4vcZ1lGimtmqXOZYQEh7CM4Q/OMFSck6/Q+gT0= +WHkOzVx7seuLmxs5Hu+l/vyBEzeQThILDQKrzavtBosY4yeDSrIgmjG8rDYDt7jf +xWoGNWjKGPfI4gq8aHoTfLS/lZvfoJjLfC/marPQVLnKMOw4Z2B4PM+0VW7zItzI/6l7PfcB1fpqeh4CVpOVaAVtXUo8+KKsnSCrzxMrCgqvl15+qXkkBzeyMR1jFwGa +L0eUthVnpkGsmKFAX6d+uEWqqcpDPrCTTj+Voj6q7co3v+1bz5rI1ebO+u/PYVII +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +b4OJVZe8QyIpjuTpKXDL9A== +CUHO82G5MDpSEiqhNImyK/VmwjDkiSuGC7ZIskUb7gD65hKsx8U/XeYWPTotCZKW34T742v7dfkWr7P/TSMaHw== +xWoGNWjKGPfI4gq8aHoTfAsoDN3ZmNarxbTXL1u4oucttlV0ayU3aRT17FqHoXJQDIiBBbSbAmeppasRVhu84Q== +VmVrGQo2zRokW/ZuO9bN62lH7iQuGMYTOSVMNmHFiRJ/Sw4G+R4bn28hJN4qoweYFOMRMpD0ZDoWuodT/c5B7g== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6/hmmducS1PDoVjLNYIkh7yyxYJu3Yggs3y3xm90fY6f +VmVrGQo2zRokW/ZuO9bN6/+pUnGYHu9kYzSmExtaF9xg/93u33jl028+UV8/J7U0fgjyTfXwYVHPB8cx9YfLBw== +VmVrGQo2zRokW/ZuO9bN62pu9LJ9Byu1GJDSPyIAK5YfjdHOvgBbuNyy8GkgPt06Hy9Wawft7wQQemmjSGO9aA== +M0ZySqkmhuHCw6olbCKv992x+E1VMoPx+GJL+VjXXxc= +xWoGNWjKGPfI4gq8aHoTfLS/lZvfoJjLfC/marPQVLnKMOw4Z2B4PM+0VW7zItzIvXmsV0xjGZqPof0yohbnqV2ZK6WX/JTtF6Rsc2nFkVdqaK9KQFfwsn1rzq0uaEPH +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXAb0WFcZv4HhfILL3G9ms3qCgcSR8133ZQaREk3oJxSA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW+2pW4n/W6qWQ7fvKEZcw6nnotqX+Ka5wYNWQOIeQDvg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +y0vAsW15ZVdHlspkX2eEyTnrhgxq9w3tH5fYg0+3IYTw+fzqXDNLLSILTETgFx1f +Z8UsPk1Q7HtwjRd4g01ryw== +DzneBA92dU5V88ETMYFQ5zTOuyBrABt5a1RgPheterisyI8z8hB6+Xmqaxszc5OF +ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk= +PRG/YRzXVD8iVR3bzEcUnsWMvR86r5liyWslQeT39Bs= +SadDvAEU7JBQjzqB/CmwTKt23QO8pBBBQpkoyF+cMuk= +Z8UsPk1Q7HtwjRd4g01ryw== +YdUmdqAC07VhbSt1h6KCDCoYsQLlWzDGUd9QUR41IHk= +2qQ1+UjgiO2OZgsc3Luuhjl38aA5wDGtKWrWNIfYtrBlHRw9FCPi00BufKOnIQnC +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +p9oVsBgcyiBMjDb8CcPOqB5miGjA7k/rR8HoMrfCBh8= +HiVf4YuHR4gw4rFUUeRvK5DcsI9Cfqv2qLzm9sb8unefn5AgzArrWdtqSFcj8Px9 +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033abQzol1FA4tuwFJraDvx2U +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSo1ZAQ1FHnoPT0QBpT+y//yTdMi/PIio5xW01+SMmEPbzHTSWH2AcPKinWHgjPuzZic3mlhIGEMpw0WGyqlHdR +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +96gahBYKYgdoVkNtss7VEPgCInqEgx8v+sRhs1WPpZm2HnAMO4XvsEY1xpE0BXSYmvq/MLRD+JYKbipt7HmjYeBg/h3NBuTXdf9BwGtIArY= +SY9CIZHY29IN9pslG93gZg== +O3CUgrw2GJfB+mDjH5+NdtbkdTJdRhl+51HFBnR6kaH4/6DbnYid4U0U1SHEYx1n +VmVrGQo2zRokW/ZuO9bN63t2MnnwkYHotYsrWH9vRICglWvh1CtLK/YdWGbWd1o9uxYVCWxZnh7Y3ENahYaMPQ== +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +1u+XjG/2+GSQRv6EzCaWRQ== +P8KbMTjfuhARpOL8NoffNq+3uZDJUpCiwqTVP/jFFTbQzIztXfbek7BVBaXkRncL +1u+XjG/2+GSQRv6EzCaWRQ== +UkQQaGHg2Aow1Gtc9bKBau0euapoYTSumuYNb1Ly8sk= +NhnIR3Ilo4H2su9/cTNo/LBal3Bb69aVfp/VrSaJBVopEyKAKnVV8FGgN9p834dg +IhuisKim47k91RVt8z8qtJyWLVz+KjhPuZWupljEG6xxx85dlt92sHkP0dH3RDilXI/lJNqFeQKeKpcIDtD+IA== +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs8AGlXEXTs+FitgdECPA9/lrzW8srVC0vHK9JhZxKlpY1OTyVkakThiIe7k7Hc/3yZ2FjjNKgggOZKRyD6HAzHN +2+RceSNjD2iq0sRzepMysZEtAjuWiKl7QaPq1reOZ3L3mfen1JvnD6MPvKsBpyOKkDYBCw5cZs24QV3TsSDbQcyqIrPAseuaKXpl/OArmSc= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOERocNC1mWlogqCdtng5segh8OjwBnGkbvs4sXKYd75Mw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +kkhBrrZfa9Lz2ARtxqQ9WISHe4SzqYX3OdpNmbR5L/VBHTenO90IETRFsXol/Jpv6kfKAQ8LAS+ijYGG5x08+Q== +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIllG8L0Za7F6wo/ffbPvOV8mvOIi5W6Px2rfj6NO9ykAT +eeFiPlXUI0Fos82HYztSmZyutxdoUP9KbXnyyGYMqohKcgTCAEZmONzPn/3V/XeU +bWa5NLmMcRfP7/pL4TxwVSNHz9M1H68NEGnEXfZWNdlCgk9i6Y3xP89ubu7Qieu3 +eeFiPlXUI0Fos82HYztSmZz5nVydpyguF0WdL2JyDxQ= +yqM179SzW9HnBHozkU+IlRXsPW+yWAUAvWc+nnoZsRTppRoZDJSnb51AHNRYCykW +akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018= +Z8UsPk1Q7HtwjRd4g01ryw== +UxWVPzm6Eag/KO1azIHUdx+idAdS+rfPARzR88tVbgYdpuz2ULTF+2vmnBiei7OW +Lb802TYIO8cTPX7RtIYGZ+/BDmfzYBEomwEqSDkd3VI= +b4OJVZe8QyIpjuTpKXDL9A== +I5csN8e3J/KIFkMa5t+SJATQ2qGW0wOfRPtcA5bj1z8nKyxnKr/hfHKXbqiTXRny +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033ZLNQ2XZF4KsFXQ0AJ186dTqy0tFe9yVaa5Nvi4GzvRQQ== +UiubnxD7y7+jD/CLXy/lIzUv+EQIUnxp2+tRaByCudDYzFPn13pFELSDNAgNf0aY +wI4EEWzdSnynhqFxMCzFmvtQa54ffiTU8OKIuM+mUtbRQW9n1JFAWLuXCST3voAI +GL/yLvMwjflJNfDPl5ASjfzFHnOGqpWC8vWJqX+sGuU7UB9p8iAxBS67EStoox2D +1u+XjG/2+GSQRv6EzCaWRQ== +P5fFy0VqKNQcixZC6/gotxklJeUYBp6qUkCM0szi5po= +3HDkd2BFYLPk4K73U5D+BYdx+YPBx8edbX2mUEZUhcw= +gZ7BoIAABc+I8g/rYzq+fw0QRhyOskLowTpBMxzNkkXJfHhElpIZe5wsCdqDFCZvGpC72uDw/Cu+D1FuT4o1ew== +NhnIR3Ilo4H2su9/cTNo/AxXtCXBAtO/DWPyG38GkWo= +SXXsDW1aLF5ljPyJUoRc7t2uV1Ea/KDPsubfQRA1/g0= +i/p1vQ7ugXGBvwbjECApz/Cr9YvVAiEjIabMp4aMpkk= +zo+FVrlHkUeZ8fJX4PVhAH4W08MspSsE1ssI18UNPaA= +1u+XjG/2+GSQRv6EzCaWRQ== +wlLHv6kT3Q/RmtMBN4nDAaQcqH83V4KNJSetgkXN2mpJpLhuyLAaUNt+tKkzaLSI +VmVrGQo2zRokW/ZuO9bN632VflaKbWaV9Wa8IOB78Qk= +VmVrGQo2zRokW/ZuO9bN608eo5JjqlJJ+hYuirBCFcmf4cKrfv149X3GuxINE1xDDJnAj+I17VZiW+JCiwfRwA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67FMFupuL9NnhZUvLdV1qH1jDx/7tB6qL1SG7Pqfwsin +VmVrGQo2zRokW/ZuO9bN65qbK6/I8bncNNV3Fr84lp9CCx8VYIApMdX4h8SHWyk2 +VmVrGQo2zRokW/ZuO9bN6zEL40rbK+o6j49cNVbzKqOsExNYZ+R8lUnZEGfiuhXY +VmVrGQo2zRokW/ZuO9bN6+EP/UfvFvq8hDmkfhk2A+qnnPSHBLYJiyMbFizN9aWEoBeZFZoLhXy50+Ts9Ur1/A== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62OM8cyvjkpIHunHzlmyApZDgMPN1CpEHj7RB29QdSFu +VmVrGQo2zRokW/ZuO9bN6zDu/0cPxO8iIAxo/MyAOyPtJNDgANrLqcOpy3L5oY1k +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklSomvH/VIQjQOrkHPd2BPf +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklTf9Kt2VbDsiUekTyaR+Eqep0h0uyOXB1iMKSg430G3P3HOaEPs8YtIeaBmiEO8lwzs03jh8/eQSyBAoQdtToH +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkI9vh2RQuknkZgs1Cqb07GeK7Ac8sa2K/nX9m0GTqvYw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69jzSpHadiEPTz774Uy5h5Nmsum61eVUSkdR5LKSFtye +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknaeZX5axwNGbFayb5AUV0mdbRkjhMqcaUp+9h1iRD5Jw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklp8xpCS2Qa6hQCt5qnOrYkkwQkeIQCwQPcHEA9pzUHDg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklt0syFuEeIYE3//UfiwS/lcyJ5NAN5s2jGe4Vc5xHKeQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmzh7YNQJ+XcGeFsDcaBjSz +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknMDNdxIBFQcIw0RGPfFMjZjfh19zPlQyTadlR6rEMpug== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklDS7vlaZKw8aBp1UPbgthY +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmbQcBvdDeaM0tbHayRHb0Q5oDsO40qheoE5NHoJ7eApdm6gYH0+XlN/FV3HdO5VZw= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmO4Xe0u+mz5wM5yC6Q+Zhs +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmpDk5452BsZTuBSuOpo5yN +VmVrGQo2zRokW/ZuO9bN69IgslanOR1qBWJ7u9f8kApHl7m/3x/YUX5TmvRhoWWrhrEvRs3e6Vo03iY4FZoGJQ== +VmVrGQo2zRokW/ZuO9bN6yAU/fW49cRu9QdgvjUnDRs= +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN6w7SLxNKr+6YhBMvtqYtIS5B39SOQ9awYMdHGNN7g2HF +VmVrGQo2zRokW/ZuO9bN61ftGV3vPtB2WjF6YrN/0jNHYRNif5kQHZWzQEGp4Mrw +VmVrGQo2zRokW/ZuO9bN68i9AQUAyLa5Izsu2k/9DPtGLI71suphKNgMpp1nHjp3 +VmVrGQo2zRokW/ZuO9bN60FmyZq9YoZep5W4V0xLs2VJw4EwR4WqghfwBKZHKkzFW5QCRYRsMStsmunHScuj3A== +VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C +VmVrGQo2zRokW/ZuO9bN67c57efC+E7a6G1EN/4VXAUA5IVAU8G/FAp8AnNhV+8pDSYVAZUG09Nqr+7c8CWyvg== +VmVrGQo2zRokW/ZuO9bN6x9SPCVoe4PZoqZ6VqYN93GZvfLrMfeSjpwDGE6Mez3z +VmVrGQo2zRokW/ZuO9bN64ApqCXcODvD57AL9oh19dsQTwsNrJDaw5mMC3iBkcYF +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn5CJpDak47Y7NTLN+SVG+JjtSLj0+GTdSHjBiYnUk9ZehN+mSf/Rh2eO5fYqxzSaFaMrHoUfHlS3DLFMFIxzNoSLKwxQP2/fXAWKceACqodA== +VmVrGQo2zRokW/ZuO9bN69/FjVndCR4PL8NhQnFk2GePSUVga2CqkdtTIa+/EIW/ +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklDS7vlaZKw8aBp1UPbgthY +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmhycRnROwAaTF+9pqngLGtjJZz0E57qXffjBTU8zR5ws/8WNuh6zqeTDPmSR/EwdRf/kscgnRpkyfDhI32MBlHgF5oG/UTLtRJTD7kKhRtdQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmO4Xe0u+mz5wM5yC6Q+Zhs +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknbl+0QAy/9I0gZXTmE2tktHVvxDseM0i9LrUUswPEB0g== +VmVrGQo2zRokW/ZuO9bN6yZnMkqRvl1W1z1JA43ooyLsysqnBW0cSn//FgR0VFUn +VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C +VmVrGQo2zRokW/ZuO9bN66U3/6225IbLyIW0FVhZRNEyLZurMXbjR98/i0DXR7zf +VmVrGQo2zRokW/ZuO9bN6xaf2vdYGOU85+dskGN5Zti8/wz7YWu3y/jTkefW+oHw +VmVrGQo2zRokW/ZuO9bN6xkyZls2bS2c0K0h3aqZRdZrjes6+0hX3Wr6T+0mnqitWU1y3v2I2Wt4PYH0XBjT6w== +VmVrGQo2zRokW/ZuO9bN62zrDNonCkxA5BeQHJAPUOvVeRTu47AJZhJxjTJXUg6D +QPiJZBcZo3Zu5kDhrrGzbI3GQxTKN1tlSYOd4wqk4Qk= +o6QhOIN2Sc4SHELnst17uYG57hDyRaN+v5d46f3m9TtvuM1edZME3sYQm1fwKXSE +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTM+LVbDQYpuIwwcEKVLExe6MC8BJb9x5uoyJJlFncEM6rY6CjjYEoI1lGYiKE1nBng== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +o6QhOIN2Sc4SHELnst17uf4l+kb1P+/cnojpDP8CeKM= +ACEEdgL/kNbx7RaZcSRxZClHBggmOTwuoukB6MgtasIiEFle6fCvd15573nsLbI9 +VmVrGQo2zRokW/ZuO9bN67Pocv3BMn87TAB+S8aqZRSL3g30PRHmEkGas7a6CXo4 +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN61efkqHAT/xspGlYy7KhUnGfupbfN6kadJV1FzZSIvpa +VmVrGQo2zRokW/ZuO9bN6+b0SyPGp1VLLfFreWS32CdvJQ0lilsNKGrnKkyex2l6ZoEN1nB9tzGcgrcEFt4mWg== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6zpJcGQDok2bzZbwZ5+2z7FMKwUs1r6SjvZQDsl9E9LF +VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgEYDxdXHdYYYJAWxRhWjvkMMBYOgdKbm+/g/JVB0KXlVgkWFQ0d19yx2HQHDjijpFM= +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgGfllFXAtMFaX/YohJyQyfhG9xy/eo7UEqOCihMHvYuSYyU9x8O35sNRwdldlOqO7VLthK8cTiezk0qcNGLbtsxNx/TEJZE7IDz5uyfUbO2Fw== +L0eUthVnpkGsmKFAX6d+uA6wgjrZ2VgEjfvSiyQz6oysGP6R543HWJTxMrxe6FdC +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uGu0U7q5yxq3pNh7Qp1zdP4= diff --git a/class/filesModel/rarModel.py b/class/filesModel/rarModel.py index 923894b2..7521525b 100644 --- a/class/filesModel/rarModel.py +++ b/class/filesModel/rarModel.py @@ -1,243 +1,243 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# -#------------------------------ - -import os,sys,re -from filesModel.base import filesBase -import public,json -import zipfile,shutil -try: - from unrar import rarfile -except: - os.system('btpip install unrar') - from unrar import rarfile - - -class main(filesBase): - - def __init__(self): - pass - - - def __check_zipfile(self,sfile,is_close = False): - ''' - @name 检查文件是否为zip文件 - @param sfile 文件路径 - @return bool - ''' - - zip_file = None - try: - zip_file = rarfile.RarFile(sfile) - except:pass - - if is_close and zip_file: - zip_file.close() - - return zip_file - - def get_zip_files(self,args): - ''' - @name 获取压缩包内文件列表 - @param args['path'] 压缩包路径 - @return list - ''' - sfile = args.sfile - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - zip_file = self.__check_zipfile(sfile) - if not zip_file: - return public.returnMsg(False,'NOT_ZIP_FILE') - - data = {} - for item in zip_file.infolist(): - - sub_data = data - f_name = self.__get_zip_filename(item) - - f_dirs = f_name.split('/') - for d in f_dirs: - if not d: continue - if not d in sub_data: - if d == f_name[-len(d):]: - tmps = item.date_time - - sub_data[d] = { - 'file_size': item.file_size, - 'compress_size': item.compress_size, - 'filename':d, - 'fullpath':f_name, - 'date_time': public.to_date(times = '{}-{}-{} {}:{}:{}'.format(tmps[0],tmps[1],tmps[2],tmps[3],tmps[4],tmps[5])), - 'is_dir': 0 - } - if item.flag_bits == 32: - sub_data[d]['is_dir'] = 1 - else: - sub_data[d] = {} - sub_data = sub_data[d] - - return data - - - def get_fileinfo_by(self,args): - ''' - @name 获取压缩包内文件信息 - @param args['path'] 压缩包路径 - @param args['filename'] 文件名 - @return dict - ''' - - sfile = args.sfile - filename = args.filename - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - result = {} - result['status'] = True - result['data'] = '' - with rarfile.RarFile(sfile,'r') as zip_file: - for item in zip_file.infolist(): - z_filename = self.__get_zip_filename(item) - if z_filename == filename: - - buff = zip_file.read(item.filename) - encoding,srcBody = public.decode_data(buff) - result['encoding'] = encoding - result['data'] = srcBody - break - return result - - def delete_zip_file(self,args): - ''' - @name 删除压缩包内文件 - @param args['path'] 压缩包路径 - @param args['filenames'] 文件名列表,数组格式 - @return dict - ''' - sfile = args.sfile - filenames = args.filenames - - return public.returnMsg(False,'RAR archive files do not support file deletion') - - def write_zip_file(self,args): - ''' - @name 写入压缩包内文件 - @param args['path'] 压缩包路径 - @param args['filename'] 文件名 - @param args['data'] 写入数据 - @return dict - ''' - - sfile = args.sfile - filename = args.filename - data = args.data - return public.returnMsg(False,'RAR archive does not support this function!') - - def extract_byfiles(self,args): - """ - @name 解压部分文件 - @param args['path'] 压缩包路径 - @param args['extract_path'] 解压路径 - @param args['filenames'] 文件名列表,数组格式 - """ - sfile = args.sfile - filenames = args.filenames - extract_path = args.extract_path - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - if not os.path.exists(extract_path): - os.makedirs(extract_path,384) - - tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32))) - if not os.path.exists(tmp_path): - os.makedirs(tmp_path,384) - - with rarfile.RarFile(sfile) as zip_file: - try: - m_list = {} - - f_infos = zip_file.infolist() - f_infos = sorted(f_infos,key=lambda x:x.filename) - for item in f_infos: - filename = self.__get_zip_filename(item) - - if filename in filenames: - spath = os.path.join(tmp_path,filename).strip('/') - if item.flag_bits == 32: - m_list[spath] = [] - else: - if not 'other' in m_list: - m_list['other'] = [] - - dir_key = os.path.dirname(spath) - info = {'src':spath,'dst':'{}/{}'.format(extract_path,os.path.basename(spath))} - if dir_key in m_list: - info['dst'] = '{}/{}'.format(extract_path,'/'.join(filename.split('/')[1:])) - s_path = os.path.dirname(info['dst']) - if not os.path.exists(s_path): os.makedirs(s_path,384) - - m_list[dir_key].append(info) - else: - m_list['other'].append(info) - zip_file.extract(filename.strip('/').replace('/','\\'),tmp_path) - for key in m_list: - try: - # if key != 'other': - # dir_name = '{}/{}'.format(extract_path,os.path.basename(key)) - # if not os.path.exists(dir_name): os.makedirs(dir_name,384) - - for info in m_list[key]: - shutil.copyfile(info['src'],info['dst']) - except:pass - - shutil.rmtree(tmp_path, True) - except: - return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info()) - return public.returnMsg(True,'The file was decompressed successfully') - - def add_zip_file(self,args): - ''' - @name 添加文件到压缩包 - @param args['r_path'] 跟路径 - @param args['filename'] 文件名 - @param args['f_list'] 写入数据 - @return dict - ''' - - sfile = args.sfile - r_path = args.r_path - f_list = args.f_list - return public.returnMsg(False,'RAR archive does not support this function!') - - - - def __get_zip_filename(self,item): - ''' - @name 获取压缩包文件名 - @param item 压缩包文件对象 - @return string - ''' - filename = item.filename - try: - filename = item.filename.encode('cp437').decode('gbk') - except:pass - if item.flag_bits == 32: - filename += '/' - - return filename.replace('\\','/') - - - - - - +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +Hf+bBTgRv9pVfall8ODpvg== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +1u+XjG/2+GSQRv6EzCaWRQ== +K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU= +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +hUVhKZtvg+FKessuieMZGkP/5FS8EfJsx7HCvhsTdvM= +KbrESL0Rr2jC6vOleUlueMUgBS8ubHMHEf/dewOmXjc= +f3AsbhS4PaN1B4cUkY9T+A== +7slA6OC5VJa1dhNh0P/HjCGJmXVn2u3xkCMlnoSxSoA= +MMZtIZZuuuWy0CLBzKekTg== +0cnYXLPu1n3c2VaQ3tVc+NKPyveC0ERGp532EiLQZU5gtpnLWSMk9qn8OtVlUlvH +7slA6OC5VJa1dhNh0P/HjCGJmXVn2u3xkCMlnoSxSoA= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhLTeka+iJoxis1kUHdyggy+yXh3Xc8xsyir/5NbKP+Sj41j2UHgL27dZFRsmbh/P+Q== +KZTmaJLp+FU9X93j5Tpqtw== +0LFyB+q/4AUBkCrX53xYJedFoNZHYELHOLu/R25oBrqozNv9g917+/ANMSb6X+WR +yqM179SzW9HnBHozkU+IlaI6kf6Fax7Db/qE4wQMDFqfwVE+4rG3+IJ9S3c+p3Wf +akHibliG6j9uvBEg96vRehUZmhX3a+KyYm/Obf/H764= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +1VHvxkEcO4espNMpWHmnH1EgArAJXS2MAairjwNzV9c= +b4OJVZe8QyIpjuTpKXDL9A== +wQn8ViRVbUL8OLSIgBD0IAeRdK41R3m/PKt9hFbBzr88nFZgyZVILp1bIMP5rZUJ +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +1u+XjG/2+GSQRv6EzCaWRQ== +l+sL2BKY+z3yDMAsWI51b39VWekdKmbfrOabwvVHTbGfiUobxgPhHW2OIZyeGUJ9 +wQn8ViRVbUL8OLSIgBD0IO53Ya1+ckG2IX47+ykaGJU= +1u+XjG/2+GSQRv6EzCaWRQ== +MxWDzXEQjj6mgdfarjHFtAA6unJa5l9pTo0P8ga3JDU= +1u+XjG/2+GSQRv6EzCaWRQ== +x41zVQ6DCtWXndDIq7U8L+5quM3ARMCnVm34NUX7l/TJzZZ100EvdUXHXI2cFH4H +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmme9ACxsySMBZr2gsEQniKL +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018= +KZTmaJLp+FU9X93j5Tpqtw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +1VHvxkEcO4espNMpWHmnHy+YX2/U7Ip4Uqt3suxNhindpukzgQ1aM+PshcW/1dV1 +LuO0LAjc92aYQU4tSUEvi4x4NJPWR/eKWjRfKMSRL8o= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmV/41TA4054wrqxXx/Q1i8cmtFJNrxypDZNiWIVVjiYLA== +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +ei/1A+2pKZSasjYF/t4qojBnh7Bi8Fen8Fcf8mKjT9IuWg8ZCZGedub8Eqjf8qUh +1u+XjG/2+GSQRv6EzCaWRQ== +4nsuLyRybkZg8C3fJCsRPXDWwLMccXwEpFBdFK0JOvg= +ZFD6pnBshb3mpu7xvvuMLemrazA4ssXDDuW5SINX4ZLsvLIwaJ4YyDANze64OGqUEj7NCJs+t+zkcUskHr+gqw== +1u+XjG/2+GSQRv6EzCaWRQ== +g5xFPP9LKBd4uktrRYzGf+p1NdL+k6huN6CrzYXY3fPVQzhtKStMTpRTZpRqmhGR +wlLHv6kT3Q/RmtMBN4nDAbqo0o2IA7mU8WVNqZvoB5I= +VmVrGQo2zRokW/ZuO9bN6+qRbBr2ygG3RGcAIOk727ylzODmV8VRqZ4YKvduSUCu +VmVrGQo2zRokW/ZuO9bN62psx0nYMGJaQ1S1BUtE6l8lgsopQJ3Qf5syQpAasAAf +VmVrGQo2zRokW/ZuO9bN69Woqo49I0Fga24Nj55N9ZL81I98txu56TEAiJ0hswK9 +VmVrGQo2zRokW/ZuO9bN61e1I6gZaVwmbMEHc7jd9wFEHpeilcxXyCkd5bmWx/yK +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh/5aA4AiEej6zodAVzZFzS0 +VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn8LNRE5UkGbTG1UxYqNO1dyU2pay/FWt6r8mJiKZ4UU/g== +VmVrGQo2zRokW/ZuO9bN64KhHtDRs0SmcOS1WVIbDs4jrgwkpIqneJssR9hHHlJ3UJd9tZzLLz6C+UQLUJWWGbgxXo0UEEapI14RLzluAUk= +VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn92m0FWAHkcFEifdcSefZI3 +VmVrGQo2zRokW/ZuO9bN6xQU/k6xhXMbxscs7Gix8nNqUkjAknQH3LrGVvAyjYtQ +VmVrGQo2zRokW/ZuO9bN6xOutEc9teLv333RLphPxTSZMBPTZ5nEHEAZCqisJAYdx733inB6pPVPHnS53cxkslRzdzhPLD4mmySs0XcaZo8Yb7xMuMGGlmbahCRF6eC3yiiMtXXp0R7BViTpiJ04fqWtzkElISMu7z4pkkwr23TjUMjTjCelUG9Lwgmt+EiK +VmVrGQo2zRokW/ZuO9bN6wv73IH9kSzXKMUmUPacSk1KDsdrjROzTN1kzduuP0l3 +VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc= +VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvjL/YDIWVk4lhx62fOdGpioi6WGXoNG40BaJlgn48VjQ== +VmVrGQo2zRokW/ZuO9bN6/kiIWH8hJ6dJOo/fnNj9VLcBHtqpOZ/Rc4AjXcEm1edHKkbtiFAlXrzE+rfp/AeMQ== +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh80w0dM3z0phdMS+lT6j/xo +VmVrGQo2zRokW/ZuO9bN65abAZ39yZLyKCl4nd6eov4HdKauw4+toNc5YoXvsAeW +1u+XjG/2+GSQRv6EzCaWRQ== ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrRvdf2ev6P6vInKuFAO8eQfewP9/G67aA1dZDLRwjari +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmlP+1p+XjT1dvUjFUZHsqC6 +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI +YSlit6erKXylrjSZnvyW8CCccEStX4fvFvduMtCGi54= +d2Ww7zhXROgo6Qq8ml3vsgVOxob/5TKf0KTWSmheRnqYA/wEs2DzrY8Dpm7Fq44lbdZ2N/U6naG4DD7karinEw== +wlLHv6kT3Q/RmtMBN4nDAdAMeIPpwO0Pgawv937x0Zw4iwEVDD1EIlJA37GxF8mZ +VmVrGQo2zRokW/ZuO9bN64/tCOCyKu41lsl+3BVlFMtjVa1DXtvHbVFGS5luzEIFtQp9eHGpneVvKz65gzXCXQ== +VmVrGQo2zRokW/ZuO9bN69Z4WTbfjflDn+PHJe4HyeBCMRIQIdsxtS/SkQUyTK46 +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64IUBVOpZieySJUi/5Xp7IP9i2hr//841YnIEqNDctKsA4PCQR7FmaZMQHs7tdC+NQ== +VmVrGQo2zRokW/ZuO9bN63KtgQxYoQMLiw/swXgEzq6F5dbUK15QUQFhJ/fqYDb2bGgnr3J8HSW6FkIPXw/QAPGbSGA6euyj6sY6jr9uD88= +VmVrGQo2zRokW/ZuO9bN60+Gj1vDNaFgHQa9qY+WYQ/+8quDVQ7mW+ZFMiCB6dK3zTXaMhAFlgEsY2XTlgH4hQ== +VmVrGQo2zRokW/ZuO9bN60dNkfoHcjUpCVMrBt5WSA9PCuYzYl0QLyyWqlMopPgY +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +6JhaWzGWadTJ3yzCk9OQNgkLy/m1ahTUuEy+6FiV49HNwHkAN2ZMxFTsufeIA0ig +KZTmaJLp+FU9X93j5Tpqtw== +dar6N0tUAb9rU8/aX1b3tGumQ1hhfk8O6qr6gmiQ1+eBvZwFkpECn+RP444+i510 +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y= +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGJEf1Y6VNXDjyNkW1vEjRDaMkNWHl1CoN0rBoEBVHY67QP37rmJsbUJ2x6ftwGYtZYSf3Kk0VjMvB+nFonZq5o +1u+XjG/2+GSQRv6EzCaWRQ== +O2CqINTuIULxX7Gu5GskT/Nm15YFovaVXDj8ylNfQi3d2Qbp7uZYhyvrRE1b06DK +KZTmaJLp+FU9X93j5Tpqtw== +t987AUZxQR72IPRcAqvQ/tuxrpVopv9MhEHHX4HHOFnF6WZD8PVxYK60YbaBpzoa +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +c1XU6vxl7MsQKGo3r4amMgGxGyqG90Nz+St+6838nV5Hy3ZAmDi9Ecxx2gyS4yZO +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +XKWK9LfYDT5w2LEWWxwkc6l2k+0eByijzzjX3gaj5k8= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGJEf1Y6VNXDjyNkW1vEjRD4/QxBrfqq90D/lFkSa20blyxshNDPqg3WrN+80ZCLiv67pSFMB4Brq3qmKK1ZCax +1u+XjG/2+GSQRv6EzCaWRQ== +D5nlraEpeGmKL9lxoq591txjX2tGuFl4D7WJZ/91lnMQ6WZrDXvU2okqtgoaqWSS +Z8UsPk1Q7HtwjRd4g01ryw== +VcOpbkIFMz59VlIHGus1M+TodC/Ia97E/RilXZKKBR29V7X/RlCXQzuWibEOEJfo +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMjvzqCkQbmHuG+orSe7QiThO3NRqf8lDLQklcqf6u2x4yWOeax5QyyroyrE443cDvw== +c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y= +Z8UsPk1Q7HtwjRd4g01ryw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au +77Mf9+1xBvZiUeNTtU/s60Kyn+UEu/OgKFt23E+wd8BiTmgbWFTzjcyPW/imPN9p +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYmSt8oPS+2lwNla/wCY62SBZ7HAsGrtjJH7dzi+5hXSr +NqhMbY8+EQI8zhTG6Zh2I5csQ+/eG6egKX2sKZJStJZk02bOBi20A8UHGNy6sybt +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEA3MxUp7pEuHhp8IfRMvwD6SZPogdTtHhT5unY8iIrUiU6GaMq6uosuXq8hjxV9nUgwa2iqC1n6Ziz6tiDdPy1mw== +wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG +NqhMbY8+EQI8zhTG6Zh2I0BiMnuwr8mLUTT6rraGmzoW2r/izQNInq2UmHNhRbv+ +1u+XjG/2+GSQRv6EzCaWRQ== +d2Ww7zhXROgo6Qq8ml3vsgVOxob/5TKf0KTWSmheRnquRJLMVVVt2mgyaZFKv044NjiViQAAPm85lrCa2jWZ2Q== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN61lflwobhmxel33RwHEAzsw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67AsrXUPIcMWmsNJ6skMzcKlx905HWgBAV8CDz0otLpU +VmVrGQo2zRokW/ZuO9bN64dz5HIueoVr5d2RnbNUIG5nWBmTbO/nPqZZ/WWusF8B2fWzSHjB5SEduhXLHWwpbFrG7MHMtEZh0rgim/Nvx4Q= +VmVrGQo2zRokW/ZuO9bN66NLXvUSFhBjtRrUbZsPQNOrrRiOaX/5u3wb0Rfqy+pv +VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe +VmVrGQo2zRokW/ZuO9bN61ODJTwuVdYa2e77kV4UyyiKC6xmDTYLpzmUEL6iaKIotT6XRfjdpuPhvefHRzC+7lCYolxcDYktKmTY39wnf+k= +VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvjL/YDIWVk4lhx62fOdGpioi6WGXoNG40BaJlgn48VjQ== +VmVrGQo2zRokW/ZuO9bN655297gigIv+4Ufy0bubbx7ffTJUoURR64bKe908x4ap +VmVrGQo2zRokW/ZuO9bN62cJZxyhxghVn78AgYWoneU= +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbdSOgZt1gFP6QuzR+PijaPfkXI6n5+LDNetR25SN0GXgg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkns33MIHJ07fAs3bEPA9prEegTrYMIIXPrzC4jD9xKXsA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+VE/nQEHJOLSC1DnxvG5TiVhVg6gVuqdGuJQhlv12yWuKoI97jUkrKrXkDjch0nxA== +VmVrGQo2zRokW/ZuO9bN66Stm18E9cFphVzr4DVvkp0eMQP2KdIRCCvQSRwvi/ogRLfVX0ez0AMMWvL6BswFfzkKtdTc18Qoh/tgpwV2o+ISRqfA+Zum4wzK337qVQa8uzWtdDX/DDCp8lweCdFZUw== +VmVrGQo2zRokW/ZuO9bN6zsPJOFHzvayEpAPvGiJ0lAWXca0TugmxSi8hINZZvoHQ1Y8EIw5ZU/3qx6IfU7mzw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmgImK713UKKRnRLyQBV8gyjNySqhD1ER71FyIggw6WuTb4tOz+zOmUJvBAa9I7hKop6pGwEoB2371+83RgakZhyjMJWosIoeLReEb5GcqHNA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkrikeB3PyYnRyHWKvzXceKTTsB9Sbgf9uM/v0isHHFakzQxpTSrdsYuODmqyQGE3Q= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknz2So+gFu+BijKlvPtsNKCIi0RkX1BFpd3GhHKBMdoB6DedipwufkrnDIRLB493gRtMiXCiHSsTBNmIXqRl2RK +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkqG04WXvd0zYTVxLMDUCWknQtobuOLFvBEBrZieoy/MQ== +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk6a/inNeVFTn9NarE6P/3LG5P9KtqHegfYsmaf6Actxw== +VmVrGQo2zRokW/ZuO9bN67FJHoI0w8vvrPd3RbddRrD2Em+ygcQFnOdLaXNs8ffOckKjgP3gK8cG/TTtMkpW3DL4StNcG/wbkijZS1ecn2uSboPx9VWF3PIGue3h88Yd +VmVrGQo2zRokW/ZuO9bN65aFRI029wWEhVZc+cvXTRpzda0UlKTw9izNui/xkgdD +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6yfyDpG+MDWCM+I7xrm6yMNHAsN4/aIr7LH++fbWW227 +VmVrGQo2zRokW/ZuO9bN61RP3YbpVjqNga0la0+UFBO8any5NddZFrxwPV3C0M24EIcWBMjLzVvJvQ44t1iabGq/xokqYehL60ehDQbn9KxCN13a+8iVaZY1iG866Qqj +VmVrGQo2zRokW/ZuO9bN62eRNBkbzSoIoWrmVfjVYrLET/E/3Y6o68HoLFuelGsQJP2af8rBXdv1FclI3SNTly4xPKYQA8oVfyimWnwTn5ty/z6nec9yo4C3p/VcdNxW +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6ylpzdD3c1E8cbuPHoSW5RFJzxWCEvXq+6grREYT2q2eRpfKq4CUkXaSg9PBb07Brg== +VmVrGQo2zRokW/ZuO9bN6zS5woMmxkovFr1YPmCwVjomnwjs8yJd8vJ968XXd0m0pndaSK8+qOGykyY0WQyhZ6POo4IRkaJlDhmmPU4hQ+8= +VmVrGQo2zRokW/ZuO9bN643JcJR2kB926iqWdGz5U79RHIfKNGRE8o6dVdz5ZS7F +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTcB3+DRCAF+pezmRjCID0udaZbU9+Bp25aQzLB8QDwQjJOciYA4ZMGenxD3oVSnPjhf/xssdSo8p33UNNP+7LnwEXEbYt+fXn2F5oiKjC6Gw== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGez0gsOf0QeJW43sHDXr/9AmAy18p0NZvV4WTviIGdP9rl3wBU2+XWJNGEpDCn6tY= +1u+XjG/2+GSQRv6EzCaWRQ== +mObL0pW76xtWwwYeubgb11DPfOo/O10pxYUkfDIVCjm8OCRNT0T/bbX1vgDt/X1W +KZTmaJLp+FU9X93j5Tpqtw== +H7d2MP+WgR9k+129/mRN7ij0WzXIk6KGh6j7E1fgHISsYBh1T2OGWCH8QUwPOkcn +c1XU6vxl7MsQKGo3r4amMvdbawOnt/oBPSHXSrswDVS5Xue1Y7sB0F2vPYt641B/ +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +c1XU6vxl7MsQKGo3r4amMqf9zivp0Qb6thhCnxaiYdHXwUNLWpYP828px7Gieygp +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +g8q6WqkWsk4SUh3d5nzGDIJanaAux29CDdQxdeG/k+U= +Vyh8kMpaq2Q+odra9mDJ3Acz3kaMgWRroPCWRIzYRMY= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGJEf1Y6VNXDjyNkW1vEjRD4/QxBrfqq90D/lFkSa20blyxshNDPqg3WrN+80ZCLiv67pSFMB4Brq3qmKK1ZCax +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmA0y/dvI19s9e1BRFCkk/QzH4UG1JBu620e7CQwjbQOOUvlgFJz4yLxls8aSEEo +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P+yZLrQY+X/LEpSBMw7xwfahIuTImHO5EcUQN9HQmgqX +LikBEoRjt8wwWzUZNw+jJqDVmHo0aqmO8fx4adbRXm90KhRgBH5ZfsJyfEAx+4JR +akHibliG6j9uvBEg96vRei563dWr9knc+JrDspjHc/A= +KZTmaJLp+FU9X93j5Tpqtw== +BIa0ibHnqUboaK9kYHYO4qFxMPyUysXdjYA1FqqhK4UMv4PoX1vehXtIZKdqRHbG +b4OJVZe8QyIpjuTpKXDL9A== +PRG/YRzXVD8iVR3bzEcUnlkfSwESl/MBfBcCyRS23XAS+yqB8BD2Fws8ohDlx4dxPS4gXd1cpWdmGRHNT+D6KSwXWu5PYPEs39ffK4i+qWM= +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +6XDh38s4HWrzA9ChHtrpYfoDrFj6al2d9qt+zDQaqfaH1G08yJkGG2cvEJO5JVMs +PRG/YRzXVD8iVR3bzEcUngb7XqhS9BqrxYAnnSw8i/s= +1u+XjG/2+GSQRv6EzCaWRQ== +5wsVwKLrIjIWqeTNpSVp9UQ8Pi6XZeLfFubLS2iQfjkZO92jH5vXc8qvq/7p/iKy +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== diff --git a/class/filesModel/searchModel.py b/class/filesModel/searchModel.py index cdb03db1..d3775b98 100644 --- a/class/filesModel/searchModel.py +++ b/class/filesModel/searchModel.py @@ -1,307 +1,307 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# -#------------------------------ - -import os, re -from filesModel.base import filesBase -import public, json -from html import escape - - -class main(filesBase): - - __s_class = [] - - def __init__(self): - for i in range(1, 100): - self.__s_class.append('f-s-%s' % i) - - def get_search_status(self, get): - """ - @name 验证是否可用 - """ - return public.returnMsg(True, '1') - - def get_search_result(self, get): - """ - @name 搜索文件 - @param get - path:搜索路径 - search:搜索关键字 - limit:每页显示条数 - p:页码 - """ - result = {} - is_dir = 0 - search = [] - - if not 'ext' in get: get.ext = '*' - if 'search' in get: search = get.search - if 'is_dir' in get: is_dir = get.is_dir - - public.set_module_logs('searchModel', 'get_search_result') - if not is_dir: - if len(search) == 0: - return public.returnMsg(False, 'Please enter search keywords!') - - if not os.path.exists(get.path): - return public.returnMsg(False, 'Search directory does not exist!') - - slist = self.get_search_files(get) - if is_dir: return slist - num = 0 - total_num = len(slist) - if slist: public.writeSpeed('files_search', num, total_num) - for sfile in slist: - data = self.__check_file_contents(sfile, search) - if data: - result[sfile] = data - num += 1 - public.writeSpeed('files_search', num, total_num) - progress = int(public.getSpeed()['progress']) - if '_ws' in get: - get._ws.send( - public.getJson({ - "end": False if progress < 100 else True, - "ws_callback": get.ws_callback, - "file": sfile, - "progress": progress, - "total": total_num, - "num": num, - "type": "get_search_result" - })) - if not slist and '_ws' in get: - get._ws.send( - public.getJson({ - "end": True, - "ws_callback": get.ws_callback, - "file": '', - "progress": 100, - "total": 0, - "num": 0, - "type": "get_search_result" - })) - return result - - def get_search_files(self, get): - """ - @name 搜索文件 - @param get - path:搜索路径 - search:搜索关键字 - """ - - data = {} - - data['is_sub'] = 0 - if 'is_sub' in get: - data['is_sub'] = int(get.is_sub) - - data['ext'] = [] - for ext in get.ext.split(','): - if ext: data['ext'].append(ext) - - data['s_time'] = 0 - data['e_time'] = 4070880000 - if 's_time' in get: - data['s_time'] = int(get.s_time) - if 'e_time' in get: - data['e_time'] = int(get.e_time) - - data['min_size'] = 0 - data['max_size'] = 1024 * 1024 * 10 - if 'min_size' in get: - data['min_size'] = int(get.min_size) - - if 'max_size' in get: - data['max_size'] = int(get.max_size) - - data['names'] = [] - if 'names' in get: - data['names'] = get.names - - flist = [] - self.__get_file_list(get.path, data, flist) - - return flist - - def __check_file_contents(self, sfile, contents): - """ - @name 验证文件内容 - @param sfile:文件路径 - @param contents:文件内容 - """ - n = 1 - result = {} - try: - for line in open(sfile, 'rb'): - try: - if type(line) == bytes: line = line.decode('utf-8') - except: - line = str(line) - - rep_list = {} - _line = escape(line) - p = 0 - for txt in contents: - if not txt: continue - p += 1 - txt = escape(txt) - if line.find(txt) >= 0: - _line = self.__replace_contents( - _line, txt, p, rep_list) - else: - tmp = re.search('(' + txt + ')', _line, flags=re.I) - if tmp: - _line = self.__replace_contents( - _line, - tmp.groups()[0], p, rep_list) - - for key in rep_list: - # public.print_log(json.dumps(rep_list)) - result[n] = _line.replace(key, rep_list[key]) - n += 1 - except: - pass - return result - - # line = line.replace("BT_SEARCH".format(p), ) - def __replace_contents(self, line, txt, p, rep_list): - """ - @name 替换文件内容 - @param line:文件内容 - @param txt:替换内容 - @param p:替换位置 - """ - n_data = 'BT_SEARCH{}'.format(p) - line = line.replace(txt, n_data) - rep_list[n_data] = "{}".format( - self.__s_class[p - 1], txt) - return line - - def __get_file_list(self, path, data, flist): - """ - @name 获取文件列表 - @param path:文件路径 - @param ext:文件类型 - @param s_time:开始时间 - @param e_time:结束时间 - @param min_size:最小文件大小 - @param max_size:最大文件大小 - @param flist:返回文件列表 - """ - - exts, s_time, e_time, min_size, max_size, names = data['ext'], data[ - 's_time'], data['e_time'], data['min_size'], data[ - 'max_size'], data['names'] - - for name in os.listdir(path): - sfile = os.path.join(path, name) - - if os.path.isdir(sfile): - if not data['is_sub']: continue - - self.__get_file_list(sfile, data, flist) - else: - - #第一步:验证文件名 - if not self.__check_filename(sfile=sfile, names=names): - continue - - #第二步:验证后缀 - if not self.__check_ext(sfile=sfile, exts=exts): - continue - - #第三步:验证时间 - if not self.__check_time( - sfile=sfile, s_time=s_time, e_time=e_time): - continue - - #第四步:验证大小 - if not self.__check_size( - sfile=sfile, min_size=min_size, max_size=max_size): - continue - - flist.append(sfile) - - def __check_filename(self, sfile, names): - """ - @name 验证文件名 - @param sfile:文件路径 - @param names:文件名 - """ - try: - if len(names) == 0: return True - - filename = os.path.basename(sfile) - for name in names: - if filename.find(name) >= 0: - return True - try: - if re.search(name, filename): - return True - except: - pass - except: - pass - return False - - def __check_ext(self, sfile, exts): - """ - @name 验证文件后缀 - @param sfile:文件路径 - @param exts:文件类型 - """ - try: - if "*" in exts: - return True - - spath, ext = os.path.splitext(sfile) - if ext: - if ext[1:] in exts: - return True - else: - if 'no_ext' in exts: - return True - except: - pass - return False - - def __check_time(self, sfile, s_time, e_time): - """ - @name 验证文件时间 - @param sfile:文件路径 - @param s_time:开始时间 - @param e_time:结束时间 - """ - try: - st_time = int(os.stat(sfile).st_mtime) - if st_time >= s_time and st_time <= e_time: - return True - except: - pass - return False - - def __check_size(self, sfile, min_size, max_size): - """ - @name 验证文件大小 - @param sfile:文件路径 - @param min_size:最小文件大小 - @param max_size:最大文件大小 - """ - try: - f_size = os.path.getsize(sfile) - - if f_size >= min_size and f_size <= max_size: - return True - except: - pass - return False +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +Hf+bBTgRv9pVfall8ODpvg== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +1u+XjG/2+GSQRv6EzCaWRQ== +xO7Wpp8LRZfjlLUey8VU9w== +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +Ji07I4FXLA9HlL84f+xIzwVBYHb2UR2JW88XmYqsjV8= +FpGNKKrJzpuJOv3iM0R0pQ5MvNLfEMlC/N/hQ9UzGqI= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +TRR4OWAtNW2TMCr31HujQ786ngB4F46vN339GISDrdk= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +9zgnmC3+5N9XK5/NRaR4SCWZ3UikXYFnKKlpz6ENZEzWUZDKLOFleg2+OHdqWpsE +32pdC9DD05OE2l0oXazDFOXcNRPwrc0ZAJ7EyyAKw9gZBJ6iOJyN12yiwNtvG/Etj4AUGv20tGUTP756+m4Fmg== +1u+XjG/2+GSQRv6EzCaWRQ== +U28wv7XxIHeRSkqXJHXzR3dYOl05WSZCrwQLHtq7mE+upUObOxjAVH+D2wuzBbzd +Z8UsPk1Q7HtwjRd4g01ryw== +zZQPiCGCqSDO70yiwDNUrtYZbEcZaFTWzu76ev6QF+qbXnZjxFPpMEvGatyQPWPw +Z8UsPk1Q7HtwjRd4g01ryw== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOFR1+D7xHQXnSFNKqEFQ40g +1u+XjG/2+GSQRv6EzCaWRQ== +U28wv7XxIHeRSkqXJHXzR/CzOXT9fdhh18KnqL31WmtG7mgmXX+KEsVaJ9DtI5Om +Z8UsPk1Q7HtwjRd4g01ryw== +3f+yEXMJpDOPrez5ap+fN71X0yVW9y+W39rIT+wSpcE= +ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk= +9uZN8r0CKVAbfzGtFDuvLwhOKNflpKM2ZwfKcLjWwCQ= +s2s/1ED0ebJ2NmFlE8kk0/nWNx9HXYglwLmrDDl0SJVEejxjmWbqF4tItLVHJaXz +zxNKIz/Dwj9Harw3dUNEQJhfwzP9e0g+QEHF20IF9QloJJn499jB5lN74CMN7xKF +sziRj7t6JsRtk9qEmKAeGc8r59oagHkoEVKlJap65zo= +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +EAqIF4KpaXVDvs4DDT5V96KZSat3hIJqR6dNVUxhnWo= +Nxw10FAZUk+m0nadC1hdyzNerAYqJLcLvHGGVkpkJIM= +1u+XjG/2+GSQRv6EzCaWRQ== +g9LTwGsiGd7fGczReaq0piwhORjgGOZyOpIjDfyNOXCPNawCc9H7QjiYk9PnEc8K +X/Lp4PsbbXulceSXUvGUpDfqLabbqgG4MZE+Cdy4FwlQTZqFS7MLKRTLDMwaDvILIRxQybc5sD1eU9BOwIwsyw== +R4Jn97/68Irdow38EVIRhDowEdscaTuWxhIXOfqZKT3o/TXmpW6PH4gqeWelHNA33aMiyCRKeO0D7gfs1V70Hg== +1u+XjG/2+GSQRv6EzCaWRQ== +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs/lMRSVWryD1gVdrE+N///hctbXY1m9aLHS8lUrVSnFP5O6PHEXfEjsMokMZJkUfD0= +cbvASHjpysrsjdY5RctXmJXaYs34E4tMRz7722FvpDk= +ACEEdgL/kNbx7RaZcSRxZD3JieACM+9p2ggEO8B4CeOCL6Z+tjHPmOoWvsHltK8C +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSo1ZAQ1FHnoPT0QBpT+y//Ad3DLkc/FyKmgGPF5OiRLoeUNVCXj1GWzYsscvnb2SZxU4zI4hEOYLKQ5kvjDFyS +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYvriJFohZH5kYuBlhocmFiOq4wyQewMJhJy8xbRXglbx +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUaLU2ERbIduh0en1Wj1B8Tfd95oiMHCmcX2jALSwoq6RyVU7vVavWtQJUE9viKOUw= +1u+XjG/2+GSQRv6EzCaWRQ== +2/eGB4mNeTC2Lw/I1/B9jClbFP06By8Q6TQXjVRMC5aisNpbx4ptA/UYjC7wxXvn +DUkWJc4dP0F0T3fzlo5ovg+i5a7jjY0MEmCx8kfq2jK+QHBdk0bbr2AxZ3cQqlrr +Djb8zu0gG2jt979SRwXXjOvVYuDaNNQCNmI1OvvoD70= +H72sLFEMWD5UKG0XW5F6nGJKzW/LoRFoOz84reF2UCk= +2RF1tdl20VItbDNfdL2RkNTBOOe8pYfatuolPTwnN8LGADdQl54uUvT1DVF9e+iGV4cafz7X+gg6KtVrt/TXCPI9NWJqgOr1pzq/i1AhR10= +6Kva1wVgBZWnKH8KBbNuqu0I1u+AyN34C41R+ayvUyA= +NhnIR3Ilo4H2su9/cTNo/Fny68/2PqdXcpcFyZsnflsKTqzPOMITq9Y6CpuGnJ1XbcsahDP9y+wRMXSx0waR3Q== +Dlyx6ZDFdkn72YIxkC3bo99d0wqqUWkm5oggFt3pXuw= +VmVrGQo2zRokW/ZuO9bN6wd/KBNDReWGUABD3WNwVs62xWSEQgqeqad2Q73LEQLC +7aT066WRx/cZGdWRKj2TgzQp9AN+Q9kHfKqypeMmdG4= +xWoGNWjKGPfI4gq8aHoTfPXeKqcetXNDNllPDdGzxJIYVQirFuRW+EjhBVgYVjVDRD56A0SEtVDAKN9yNygMzg== +jlXY8vCef9GOpgUZXz0mf9DfhxAODMs6lMdvzBIM/MW1Aq1rMVSrJYB723qLrfih3YWjEp+EB+NTwJh1y6k0Yg== +84Vymj90Wzn5yYuvz1pUuNMKvUAWFp7QE+2Yweu4868= +VmVrGQo2zRokW/ZuO9bN6wEyB2Guh40Pcugoyh0QBDk= +VmVrGQo2zRokW/ZuO9bN66AnqHISKFWgaxa6i1eykZnjUYRjoLmSwgSMUPuifCRX +VmVrGQo2zRokW/ZuO9bN62/0JMqSTjLVEyn+Ej8AxwUnCoc7c66mKn79grgEeCW4lCCimbVM8qjmKRX089HBXQZTubrP4Dlhm4eMwMPvv3E= +VmVrGQo2zRokW/ZuO9bN6+E4b2xiC6ZHjT1m1FpW3M+cImaNbhy13Fhkj9q6wpbsV6pyGO3VUL36gOhAJ+Q9bA== +VmVrGQo2zRokW/ZuO9bN69SEQTu8KN9su1QzyegRt9I0k+TINrUN1jOdFTWatw/A +VmVrGQo2zRokW/ZuO9bN691C3HCcq309X4zk3aYIPPcffxT7OcwObFcDmavXvPD0 +VmVrGQo2zRokW/ZuO9bN65RsFGHNPy4PRufoKux2OhO78Lnr5z+7zzZGg1YcYuSV +VmVrGQo2zRokW/ZuO9bN6x5oB/WlUCb6mmC2LMPppfSCyk+XA4K4GFn/hUbHBSy5 +VmVrGQo2zRokW/ZuO9bN67Ff1ABLPDzqsQSzl/a49rnaHEAmRXtCEzOMwE/sYpXvJqC4BUWuPJeNx91EHEOGRg== +VmVrGQo2zRokW/ZuO9bN6198WF1cuKQrC1Fe1gF45Yc= +uTEK8Ng11d3ix2pA+DD/aX0ELDXTMxMbOqDs4xOCbbtjTvaikhc80beKBuWMfrmy +OO/dMlOTxBikYSnUTxYxGjU1adFuYtvDicPSrTmmd94= +VmVrGQo2zRokW/ZuO9bN6xA57qdw8/aOkwkb2GLtNsM1Gxzw/Ohom0z8ykGCiYSk +VmVrGQo2zRokW/ZuO9bN68s4dlWAY+dRAs2Ywb0QeE1L2XlwGJiaW7efF6E8T2X4 +VmVrGQo2zRokW/ZuO9bN61fiC4FaUq8z+fKBUks6tGgZfs6Lma5kU54uQlswGIQVxfzffKkvm1lDl5rcXHMFFA== +VmVrGQo2zRokW/ZuO9bN6725gn6AE4KlYGXCgdldfTHvVePxt81VDJii+UIRV9So +VmVrGQo2zRokW/ZuO9bN6xP6//zUY+lIi+gH0ZiMAHxrWVK37wh0Dd0rIYDgHxyh +VmVrGQo2zRokW/ZuO9bN65Vu8PVmTF+4Dzea/bvMcEVOXNQxxERI+pswNZxPLgGG +VmVrGQo2zRokW/ZuO9bN66+Mk4GZKdNMe7NCKER0yRk= +VmVrGQo2zRokW/ZuO9bN6+3m7PBc9EdU7lP2yNhVBRJqxygYFe0FGSSnC9/qF3UeaMY1MoDseyFI9vmbOD5IgQ== +VmVrGQo2zRokW/ZuO9bN64vo1FYdzVPQhcC0bXObVxs= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +U28wv7XxIHeRSkqXJHXzR0xpTOufJNSwabqZ3Q3ilnxCv5GA0Lu1QixaJfBttSFg +Z8UsPk1Q7HtwjRd4g01ryw== +3f+yEXMJpDOPrez5ap+fN71X0yVW9y+W39rIT+wSpcE= +ho/Q+jrWDtBeg9J9ZTnKi3+/Efbvadpf+qoH75NuGwk= +9uZN8r0CKVAbfzGtFDuvLwhOKNflpKM2ZwfKcLjWwCQ= +s2s/1ED0ebJ2NmFlE8kk0/nWNx9HXYglwLmrDDl0SJVEejxjmWbqF4tItLVHJaXz +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +1u+XjG/2+GSQRv6EzCaWRQ== +1zXpiGOddO9f/7P9ia16hLkuEytAkOvat/hMfHQ4YOA= +TlcB8lXx3B+v8kN8QpOFdfk9CF2LWs/6yNZPbaaVR70= +NhnIR3Ilo4H2su9/cTNo/ADljVwvrJpst190+kbx61cg2ptEynXXqaFn0MK3A4Gi +1u+XjG/2+GSQRv6EzCaWRQ== +UVXwiL1XIF6sRJ+E2PuG4USOrEdsFvbOxW2jp01zP+M= +APhqKflZUhmgRCCocHNfF8cWLKWpGdW2cBFVB8esfaebcV8Bm5icApbL9u/SmCTp +YA93D7bVXAwMe11nxv6BP/vWRwUT7PwlT8ufhs3UDnY7vkYUBvQW6UL1K9e7/UlQ +1u+XjG/2+GSQRv6EzCaWRQ== +LOnQOFqU6PCy7YYVSPmkA0/gTFRU2H0d6T51/GWKhnQ= +3KmFFXpaFcYe2PH7o19+Yp8xAODhI/vWpqDGX4Xxav/TPftCcwlJ2GSdqZRIVGoo +R8K/Ndjw+sVUFzGFOCCgwyEgZIFazHPxG2rsbkC4Mic= +NhnIR3Ilo4H2su9/cTNo/FK+qNAG19wxGrf6AaX5vF2MYvQpe0FpvROJQN48CKmo +vdsIgxKEnw7wmXARhJ0Oc8wVF+V4koy8W2Mh858ZTYc= +NhnIR3Ilo4H2su9/cTNo/ILZghLpbr5j/IZD/K8v92HhdXliUZ6LIaLGw2w1580v +1u+XjG/2+GSQRv6EzCaWRQ== +4lN0LWBzLjgquvZLEoGU4VcBfkPRXV3AgETwRHgwx0A= +CaVVGVWiTY/j3V1bwWG579Qd9AhVS9Fosq6m4Brd0nDzkmY9JVeKm8YUIUMgLZgj +ww1k/N0gWysBJXBpYKUCHSY84nqsRk88NU0ho54XVr4= +NhnIR3Ilo4H2su9/cTNo/PIphezILghaR2B05A8VC0vv8lXu5MHRtSnDBC+dvcGcG17D53H9BitReg2GU8a9Tw== +1u+XjG/2+GSQRv6EzCaWRQ== +m4a/CtpjJ/nMVgLJWRwZXXRLpxxOovbcYxRSxZCtA6Y= +NhnIR3Ilo4H2su9/cTNo/Gu9KOwC8SCkdYU8skYoE5Jmz6ywJ/AKKeZKyRVis9YBz+4SGikFp/xZOPqocMHyyQ== +1u+XjG/2+GSQRv6EzCaWRQ== +CQqNVWmwukkhI4nkdKzMzwBsOQ+4HbTh/BOPpsEZGY8= +bpVtspxTF2OToZILoJ1fkG7rqYhfXtulnikcgwpk7TI= +NhnIR3Ilo4H2su9/cTNo/HvYNwc7FmYMnVorYtJsgM7mPAX2E472V3DWt24sC8XW +1u+XjG/2+GSQRv6EzCaWRQ== +RAcgdll7MZc9X2PbkeTvEzNyiOpuxHuwyFCiwlCwtBo= +TcUpsBX6+7VrOYYb7aAQxi+cMqvbjHfbkHkvbweeOrI08YeWNRwxkPzTCz4gVzYxXfh+l8lWk7g+IZkT/aj/bQ== +1u+XjG/2+GSQRv6EzCaWRQ== +5wsVwKLrIjIWqeTNpSVp9X49wYIz3gKTqq7jnocq95Q= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhAwjLxzOvQmNis0aVGbhWo1EM3d+uiOHD4+CHNQr7OvntVC01SFgrSaWBjwpxSspMw== +Z8UsPk1Q7HtwjRd4g01ryw== +zZQPiCGCqSDO70yiwDNUrrH8Q4B8051PjWqxvRD/6G+6mZgTSaRF/+5UQJBSfsOu +yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W +FFgrrvXvjrcz4wZcb8tZVMfl7bl/3so2ckZ8r41azARzhi5rZ6V/H3mosyOtQ6kI +Z8UsPk1Q7HtwjRd4g01ryw== +eUSWcSfVc151c+2QfqQTIQ== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +b4OJVZe8QyIpjuTpKXDL9A== +wlLHv6kT3Q/RmtMBN4nDAd5ePG9boZZ1puX9EApi7waJ3F0rdpZHQ+QUGogqxN5a +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN684Ix5sFjdtWSuy9M2APYQQ2cloQs45uAs7g4ZITcnIAjpBDTpZ3BrLNJFEVqJ2ojctFGKKm/95hVbuFjYtJPeM= +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN67Ap1Q6VmWnNlLMMqeDi80Doeyvhi/U8GBPp5HHWZTzv +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6/n7n61n7R9/9XTW7LSvopM= +VmVrGQo2zRokW/ZuO9bN6/WTynACEFT8sdNdNup1kalU+7hlVh1zylFr4591OIGa +VmVrGQo2zRokW/ZuO9bN6zPS0z48efxTbmV0uLaJMFk= +VmVrGQo2zRokW/ZuO9bN60IbS8l3rE5TrRrNdfxL+kX3Pzn+FDHqKXiYJg1KgmXF +VmVrGQo2zRokW/ZuO9bN6wAx5J8iv3RazhpXmncqnrreXiCOGawHQMNMjUs2cCVN +VmVrGQo2zRokW/ZuO9bN67Y8U6ALWboELuY6YE4ad78= +VmVrGQo2zRokW/ZuO9bN61Rjjiw/60bucMm5EHZzOcK7qzv28rP7g4n1B0Ie7pOW +VmVrGQo2zRokW/ZuO9bN6w5+dIR4Ypp9Zft93+s5eE5/yPjP1yhzf5/NmchfJr6N +VmVrGQo2zRokW/ZuO9bN623LqJlCkyXYwwCXSySz5FR7MI/uUVbwFRZ9X3HsyodA5+hN+HhvBEXoBRCHeMLs1w== +VmVrGQo2zRokW/ZuO9bN6/TV8HYrXfBS0+5RislOy6/23foxSNNR4dF+wZRb5KjO9N7VJVsNrJmtWvnUeaVjrQ== +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN6x97Jv5sxc/lnlSc+X41DCbYX0L0DMlJm79khasZsmeDQfkyT4p1zevxzJpYBx1DNhDBrlR5/iGnPwmNZ1OvWwg= +VmVrGQo2zRokW/ZuO9bN6ywyqAt0WUYk0wz2RaTbkeMb61E3V3fbNF4IaFu+N7qh +VmVrGQo2zRokW/ZuO9bN6/TV8HYrXfBS0+5RislOy6+U+Hq91CX8KUds7Nh1ZOU8nWqPG53E9RBQ5qeqTEsFmw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkt5RqaKznfoAozF2brEPS6 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmb7MAq4qksNIzY1Eh1Jjt9/P2MbO42SPrEoJqJGO8pbA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN628UWklFrPzPT9a+yjbUprIuuMwW0I9DnLu6OGQ2TxNu +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+Z1kfdIPo4MYDXeXMYYWIYQQ4mrc936ju682bxyn+TqEoaQoeHhJgY1bOPJ6ik4QTAOiG9gx9IRwXfuCJtr3Ko= +VmVrGQo2zRokW/ZuO9bN66gjfjrYMn+hEmToUiACtV8= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +qx/2dn1Vpqs4SJaPnWiGqCi/4MihcwwKbYGKhk4naTAD8DbuTmd7DIaIioL9KkvbIcxbbjYAhP1Y/OUfj1mnGA== +segQHXNRrJyLOuWc8JKC3tmNT7tyg3IVKLwZ3sHcdnRU1rBJqs6RBi6qg5fjkef3qFq/cbKaLNb11SHQv37cDw== +Z8UsPk1Q7HtwjRd4g01ryw== +K0FOgK5mh0B5pEUVGx4wW2bJABs6gWPmt6Q9o3r8p6lGdFGrw5pIphcDNNDI4Pxe +bIts5UhOLbX+5vrkUi2fEpgbk+KLUNd8ANhOjt2CA8S7qBtqBqrK7rtUY4QlsSDu +St8jsnBszzKBMqXhq/bixnxiRXhroGzTpl2GmFXSnRkSxzNQsMXQGk8FOXJV6Y5r +eeFiPlXUI0Fos82HYztSmaHvIBfKy6OirpgreAVZg6s= +Z8UsPk1Q7HtwjRd4g01ryw== +4m1ZiBOyILBTSHQf//vyC0su8pHbpnlRDppIig8J5ysXeSTJP68Ri95UQiepxvh4 +WNTuVs53ESvy99Z9NIHHsQczMhznKv/xDqWjNZ9BqOGfNIc1eIJDK8jae1LRmist +sYeCd0P8QwDZsXD2zsYtpwOQuFBJwQCwG7+G1aTUb7MU2KmH7QjCjojJgGohPv0RKMRm2E/yyqe7h2CjSXfM2VbeNDXk2k3QOsue23kQf9w= +32pdC9DD05OE2l0oXazDFNsU+u4bMtJzvbtUKGSOKeSn9WvZrYyoyjfX7mEYUbe3 +lGY+HGJ/DlLufZHWWlj7U5mXaKcrxjwMdkooNruC2og= +1u+XjG/2+GSQRv6EzCaWRQ== +UNfQheD+zBxMB/8PVcAnPhJH0dABCf0LFLwey6F+PqLQAVJFMjnbXXReUGDMDEqKiYFfTP5la91cu1Qzw3WaLg== +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P2UruqssNk4NnKN+47U1SH36F2BxurMzko0B0uYv45I9 +eeFiPlXUI0Fos82HYztSmSfbblmQRmW8NByKq0O3ixRZii1ssvDqpHtqdGo6blGV +8ehs2Cqsj3GIU4YbS+kqMopjyYZACPJP3McICcxQ7QYvefmSdk7wQxP2oL97Q4vQ +yqM179SzW9HnBHozkU+IlY0McOeVypQ/EYlVcJDFe0GlWr6WqLSH8YZJQ3Zl80pE +8ehs2Cqsj3GIU4YbS+kqMpJNmQ5fFIbSdkmF260fowevNNXJ45tpcmfBJiEEZGw6 +NjU4zQ5DRyOHitK6CHLOgHN7CZ8aB+Mb5HQXfP+yO3ibTcrsbI4BMbGHj3qA0+h0 +NjU4zQ5DRyOHitK6CHLOgAOFCV3qwh/1iA/qlJwZgoGUD8smj9/Bg0H/hh9Oqg6m +wOl7lRII4ZgqRTj9M//0bzhomxn10Rjo41lvXMmPQezzGWk3GyeNMpopZ/PorkQi +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +D9SFxmTjfXKVjBp30+qz3QUFQ0c/jfdN4cWLcYJvWW8cGcuYOqW+zTq/dn4uaKX5XJmBCPK7U+oFvA5D8u9eQEuYG8CwsAxp5IrQkGPd5mY= +MQu48JkHralDtNXBUYl7lV0bLxYB+cVYGPW5I5H4uBmKn+X1vLJuCeoRB5MaCEB5bkAaA21+lVtVi8XnA5uCfg== +VmVrGQo2zRokW/ZuO9bN63kQ7naUTUtv0ESWGx9rzIn/1UUCp4+jfPXGiX14BdBQ +1u+XjG/2+GSQRv6EzCaWRQ== +hxgdAHD9AaIGGBvLsEuyB8PrRl5j0RLWnVrakTTrC+wYlY21ONwMz70K497xaqGJ +HiVf4YuHR4gw4rFUUeRvK+5LSyMNmU1h13+cLC9tnC/y8cnqYfMBKdGAnQDTVZHb +1u+XjG/2+GSQRv6EzCaWRQ== +O3CUgrw2GJfB+mDjH5+NdjDdFbdZzMNFPWLs5nIbjTo1SpgjDweH2KPbFtbB1QDS +VmVrGQo2zRokW/ZuO9bN69uA6kygxk2cG8IYgdG9Pr0+kDHcaDqpPo1aFOqQ6eZcFBrY15aCLHK16jKxHqaXiw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wcnkGR1Qyn1s8+CW5+MNtzRTbQfpmPYWYZ1Zix4ol4Or3JWa7MNuH8FVEl+/LFhBA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6ydgd98GwzWUciEurhqUbl5zFKBgTvp0P9ayAL5RaMdP +VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNq5a5scduUEPHPFtX2wTOnBqbjEynLbHNU89K1bs5Z0IYpgdoxUyCGS8wiq0EgiinY= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69NIyxsu9wL3ButjWi8Vd9WFqs0F/cbYpifzswbVj1XE +VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNqDts0LCwhPoG9qpww/bbUj9JcO334yYaFKIcL8wQwB6/PsreYi8O27v2MuAOqvCx0= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6zLSeF3gHkc70v8wH1lH7V6izJDceK8v1VqgXLP7PPFh +VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNrIogcyFIfNvtgKGel1Eosz +VmVrGQo2zRokW/ZuO9bN6wJqlk/efd+QgKBb45lQkEUJURJ6AXySYv+T873/0906R7ZDY/OqM+pHGZhlC5mTCLRAh2YSqtgcguEgy7g+lDE= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61W5fU8yZndDfCoUS7iF29y1q1oDpOjeucxethGOXiRJ +VmVrGQo2zRokW/ZuO9bN65a4OFYGJXxFiOGSJivVxNoJZTLcbrqpw1/MqZAUh2h1 +VmVrGQo2zRokW/ZuO9bN6wJqlk/efd+QgKBb45lQkEU/1xIS7OZ8UVIpUgSlst3vnPzuyqFcv172ahiYDGVFzqHNYalk2K2+ZiH00yJrmFg= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6zHe1AdyxhaUooAL+hUKQDTEHrKgluuXbnSBJQrzZUMq +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhFEcylaBQH0IUk+OusBE3my0xp9xwF5QGoWXAy5mxBYi +Z8UsPk1Q7HtwjRd4g01ryw== +zZQPiCGCqSDO70yiwDNUrvU3PfyPPxNosxB0yTLMifo= +yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W +bWa5NLmMcRfP7/pL4TxwVVPLg0xyCs7rLJn7q0oXZoI= +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +ACEEdgL/kNbx7RaZcSRxZATKpSMTQ/cWmiIi7MZqRD3iHlJkfgQKP2ZXgOycctOR +1u+XjG/2+GSQRv6EzCaWRQ== +PRG/YRzXVD8iVR3bzEcUnm+txOUxEzRjM53h5sTJkBazFR7SuI0t8ZpzuPGzrZlm +wlLHv6kT3Q/RmtMBN4nDAaa/4pYRsvL7819NLBZ1Jsk= +VmVrGQo2zRokW/ZuO9bN6w1AyfvV+/TKcjbtKlFZIXTurM+4yW7LDThX3d58hOXr +VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN66vfklQEmoLc3ecqJIXu87NMH8vbZoK4tpkEq8BGvWvlPTpczQ026/C1jKPPSYdvzg== +VmVrGQo2zRokW/ZuO9bN66SC7guo6F2BUtjo1cx7Weub6mMMOadJGmLmKcw75lpb +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhORIPeJCp4wWVvEEOwBGJbMQ9B8g68lVqaXG7/c8sCgL +Z8UsPk1Q7HtwjRd4g01ryw== +zZQPiCGCqSDO70yiwDNUrn/Ahs5WLg1NPCie3zGShu1/wSHjnFYLODLD25lQA5x1 +yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W +8ehs2Cqsj3GIU4YbS+kqMtuucPAdJ1i3hn/BkXRR/b8uF7yzHSGAxk7z1KZFvBTC +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +PWKGbNEPxxxpGpe8THO242QunNNWnvbBCn+iC2oElyc= +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +1u+XjG/2+GSQRv6EzCaWRQ== +JlxIfINDpIN74VOPKABZW2ra44tSxs0ZLHEM/U3b4xykot9XDTMkG244y4YCVtPy2qfh3Sl0bgJRhkuNbAnsLw== +YA93D7bVXAwMe11nxv6BP/8iLYI7Aa2oVDOTO+f6qRg= +VmVrGQo2zRokW/ZuO9bN66Hq/Y+eYT69h50gLi7IyQOOipSIQ7Q/7rFMIt1AIZov +VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN69tIcUnkZWsvMTtwyCmn6vvN9UwZdc3KUcaB2lz6Du2A +VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhDsDXpgsqOpbKETjou9WOZPcc1elPxQh9cS821Ok3bVbCXSJ8u7r7Ro1XiEX76I/iw== +Z8UsPk1Q7HtwjRd4g01ryw== +zZQPiCGCqSDO70yiwDNUrj777l5BTo9eCcYE6KEhGN3qR2uRts9okQ1lptsdQWtg +yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W +yqM179SzW9HnBHozkU+IlY0McOeVypQ/EYlVcJDFe0GlWr6WqLSH8YZJQ3Zl80pE +8ehs2Cqsj3GIU4YbS+kqMpJNmQ5fFIbSdkmF260fowevNNXJ45tpcmfBJiEEZGw6 +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +jPjjou9VNwkRtlQvS7IMwT61DiJZ1n7ABVvC8lImM1GTyercqKulHtnPhz5LMb0WCoJOixq+LHAXxH4az57sJQ== +J1UGlXkKhKXD36OrXXhN8LEiN+9Y+rPtU094/lpWCJkzL91nJbwePJFLFNxcU7a0SlIO5OZ3MwAWum4zIjkPgg== +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhLHqHL4jLTDrGAIPR+WsUzXEFIbEmBGSwvvQLutL0dFcpH0AUPQoXrj49knrv9+d+g== +Z8UsPk1Q7HtwjRd4g01ryw== +zZQPiCGCqSDO70yiwDNUrm7HG9OhPLBhZEBfEtqJxcB/kpanL2zAPOvr56+W9KHv +yqM179SzW9HnBHozkU+IlX5lAmWuzrOJA3YvC3xIfCIyjZshemB/XWvy+C/nSc8W +NjU4zQ5DRyOHitK6CHLOgHN7CZ8aB+Mb5HQXfP+yO3ibTcrsbI4BMbGHj3qA0+h0 +NjU4zQ5DRyOHitK6CHLOgAOFCV3qwh/1iA/qlJwZgoGUD8smj9/Bg0H/hh9Oqg6m +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +YdTM5cWYAsOq9968GjmEIn458PkySNd5GOJg2QjTSgkgpn8VUITEbXvbBG+yQJ0k +1u+XjG/2+GSQRv6EzCaWRQ== +gZ7BoIAABc+I8g/rYzq+f9fH+SgIZsS8Qfv+e480JkPCt3tQj/PL+v5Tjao1wdEQSyGpe2xx/8OpBGm95SqEpA== +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= diff --git a/class/filesModel/sizeModel.py b/class/filesModel/sizeModel.py index 6aa155ce..40449cfc 100644 --- a/class/filesModel/sizeModel.py +++ b/class/filesModel/sizeModel.py @@ -1,287 +1,287 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- -import copy -import random -# 获取目录大小 -#------------------------------ -import sys, os -import json, os, time, re - -import public -from filesModel.base import filesBase - -panelPath = '/www/server/panel' -os.chdir(panelPath) - - -class main(filesBase): - _exe_cmd = 'ncdu' - # 扫描历史 - log_path = '{}/data/scan/'.format(public.get_panel_path()) - # 缓存 - cache_file = '{}/config/scan_disk_cache.json'.format(public.get_panel_path()) - - def __init__(self): - self.is_use = False - if os.path.isdir("{}/plugin/disk_analysis".format(public.get_panel_path())): - self.is_use = True - if not os.path.exists(self.log_path): - os.makedirs(self.log_path) - if not os.path.exists(self.cache_file): - public.writeFile(self.cache_file,"{}") - if os.getenv('BT_PANEL'): - self._exe_cmd = '{}/plugin/disk_analysis/ncdu'.format(panelPath) - - def get_path_size(self, get): - """ - @name 根据排除目录获取路径的总大小 - @param path 目标路径 - """ - if self.is_use is False: - return {"code": 404, "status": False, "msg": 'Please install [Disk analysis] first !'} - path = get.path - is_refresh = get.is_refresh == "true" - - real_path_dict = {} # 软连接处理 - temp_path_list = [] - for path in str(path).split(","): - r_path = os.path.realpath(path) - if r_path != path: - real_path_dict[r_path] = path - path = r_path - if path != "/": path = str(path).rstrip("/") - temp_path_list.append(path) - - try: - cache_data = json.loads(public.readFile(self.cache_file)) - except: - cache_data = {} - - result = {} - - path_list = [] - if is_refresh is True: - path_list = temp_path_list - else: - for path in temp_path_list: - if cache_data.get(path) is not None: - result[path] = cache_data.get(path) - else: - path_list.append(path) - - if path_list: - scan_path = path_list[0] - if os.path.isfile(scan_path): - scan_path = os.path.split(scan_path)[0] - for path in path_list[1:]: - while True: - if path.startswith(scan_path): - break - scan_path = os.path.split(scan_path)[0] - import string - code = "".join(random.sample(string.ascii_letters + string.digits, 8)) - result_file = '{}{}'.format(self.log_path, f"temp_scan_size_{code}") - scan_time = int(time.time()) - exec_shell = "{} '{}' -o '{}' ".format(self._exe_cmd, scan_path, result_file).replace('\\', '/').replace('//','/') - public.ExecShell(exec_shell) - scan_result = self.__get_log_size(result_file, path_list, scan_time, cache_data) - os.remove(result_file) - result.update(scan_result) - public.writeFile(self.cache_file, json.dumps(cache_data)) - for r_path, path in real_path_dict.items(): - result[path] = result[r_path] - del result[r_path] - return result - - @classmethod - def __get_log_size(cls, log_file, path_list, scan_time, cache_data): - """ - @name 获取文件或目录大小 - @param log_file 日志文件 - """ - result = {} - for path in path_list: - result[path] = None - data = public.readFile(log_file) - data = json.loads(data) - data = data[-1] - root_path = data[0]["name"] - if root_path in path_list: - result[root_path] = data - else: - cls.__get_sub_size(data[1:], root_path, path_list, result) - for path,info in result.items(): - if info is None: - continue - if isinstance(info, dict): - info["type"] = 0 - info["asize"] = info.get("asize", 0) - info["dsize"] = info.get("dsize", 0) - info["dir_num"] = 0 - info["file_num"] = 0 - info["total_asize"] = info.get("asize", 0) - info["total_dsize"] = info.get("dsize", 0) - info["stime"] = scan_time - cls.__get_stat(path, info) - cache_data[path] = info - else: - cls.__get_dirs_size(info) - cls.__get_stat(path, info[0]) - result[path] = info[0] - result[path]["stime"] = scan_time - cache_data[path] = result[path] - return result - - @classmethod - def __get_sub_size(cls, data, root_path, path_list, result): - """ - @name 获取子目录数据 - @param id int 记录id - @param path string 目录 - """ - if len(path_list) == 0: return - for val in data: - if isinstance(val, list): - sfile = f"{root_path}/{val[0]['name']}".replace('\\', '/').replace('//', '/') - if sfile in path_list: - result[sfile] = val - path_list.remove(sfile) - if len(val) > 1: - cls.__get_sub_size(val[1:], sfile, path_list, result) - elif isinstance(val, dict): - sfile = f"{root_path}/{val['name']}".replace('\\', '/').replace('//', '/') - if sfile in path_list: - result[sfile] = val - path_list.remove(sfile) - - @classmethod - def __get_dirs_size(cls, dirs_list): - """ - @param info 目录信息 - @param result 结果 - """ - dir_info = dirs_list[0] - dir_info["type"] = 1 - dir_info["asize"] = dir_info.get("asize", 0) - dir_info["dsize"] = dir_info.get("dsize", 0) - dir_info["dirs"] = 0 - dir_info["files"] = 0 - dir_info["dir_num"] = 0 - dir_info["file_num"] = 0 - dir_info["total_asize"] = dir_info.get("asize", 0) - dir_info["total_dsize"] = dir_info.get("dsize", 0) - for info in dirs_list[1:]: - if isinstance(info, list): # 目录 - dir_info["dirs"] += 1 - dir_info["dir_num"] += 1 - cls.__get_dirs_size(info) - temp_info = info[0] - dir_info["dir_num"] += temp_info["dir_num"] - dir_info["file_num"] += temp_info["file_num"] - dir_info["total_asize"] += temp_info["total_asize"] - dir_info["total_dsize"] += temp_info["total_dsize"] - else: - if info.get("excluded") == "pattern": - continue - dir_info["files"] += 1 - dir_info["file_num"] += 1 - if info.get("asize") is None: info["asize"] = 0 - if info.get("dsize") is None: info["dsize"] = 0 - info["type"] = 0 - dir_info["total_asize"] += info["asize"] - dir_info["total_dsize"] += info["dsize"] - - @classmethod - def __get_stat(cls, path, info): - if not os.path.exists(path): - info["accept"] = None - info["user"] = None - info["mtime"] = "--" - info["ps"] = None - return - stat_file = os.stat(path) - - info["accept"] = oct(stat_file.st_mode)[-3:] - import pwd - try: - info["user"] = pwd.getpwuid(stat_file.st_uid).pw_name - except: - info["user"] = str(stat_file.st_uid) - info["atime"] = int(stat_file.st_atime) - info["ctime"] = int(stat_file.st_ctime) - info["mtime"] = int(stat_file.st_mtime) - info["ps"] = cls.get_file_ps(path) - - @classmethod - def get_file_ps(cls,filename): - ''' - @name 获取文件或目录备注 - @author hwliang<2020-10-22> - @param filename 文件或目录全路径 - @return string - ''' - - ps_path = public.get_panel_path() + '/data/files_ps' - f_key1 = '/'.join((ps_path,public.md5(filename))) - if os.path.exists(f_key1): - return public.readFile(f_key1) - - f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename)))) - if os.path.exists(f_key2): - return public.readFile(f_key2) - - pss = { - '/www/server/data': 'This is the default data directory of the MySQL database, please do not delete it!', - '/www/server/mysql': 'MySQL program directory', - '/www/server/redis': 'Redis program directory', - '/www/server/mongodb': 'MongoDB program directory', - '/www/server/nvm': 'PM2/NVM/NPM program directory', - '/www/server/pass': 'Website BasicAuth authentication password storage directory', - '/www/server/speed': 'Website acceleration data directory', - '/www/server/docker': 'Docker plugin and data directory', - '/www/server/total': 'Website monitoring report data directory', - '/www/server/btwaf': 'WAF firewall data directory', - '/www/server/pure-ftpd': 'ftp program directory', - '/www/server/phpmyadmin': 'phpMyAdmin program directory', - '/www/server/rar': 'rar expansion library directory, will lose support for RAR compressed files after deletion', - '/www/server/stop': 'The website deactivates the page directory, please do not delete it!', - '/www/server/nginx': 'Nginx program directory', - '/www/server/apache': 'Apache program directory', - '/www/server/cron': 'Scheduled task script and log directory', - '/www/server/php': 'PHP directory, all PHP version interpreters are in this directory', - '/www/server/tomcat': 'Tomcat program directory', - '/www/php_session': 'PHP-SESSION isolation directory', - '/www/server/panel': 'aaPanel program directory', - '/proc': 'system process directory', - '/dev': 'system device directory', - '/sys': 'system call directory', - '/tmp': 'system temporary file directory', - '/var/log': 'System log directory', - '/var/run': 'System running log directory', - '/var/spool': 'system queue directory', - '/var/lock': 'system lock directory', - '/var/mail': 'system mail directory', - '/mnt': 'System mount directory', - '/media': 'System multimedia directory', - '/dev/shm': 'system shared memory directory', - '/lib': 'system dynamic library directory', - '/lib64': 'system dynamic library directory', - '/lib32': 'system dynamic library directory', - '/usr/lib': 'system dynamic library directory', - '/usr/lib64': 'system dynamic library directory', - '/usr/local/lib': 'system dynamic library directory', - '/usr/local/lib64': 'system dynamic library directory', - '/usr/local/libexec': 'system dynamic library directory', - '/usr/local/sbin': 'System script directory', - '/usr/local/bin': 'System script directory' - - - } - if filename in pss: return "PS:" + pss[filename] - return None \ No newline at end of file +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +2unYDUJ7AQka0zNDbcMACQ== +PDg4EOFKN+dK2UrpwNWHLw== +svfNqiZI0zsAZVvJZhVKxDnsn+e2c8eSwsWdNJ9ryew= +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +Sm95VfslcvrZUo7dRR9Ibg== +1in3eluyrI5kuyCXT4wxGR0BSfUj5K16kanLx7m3x4E= +1u+XjG/2+GSQRv6EzCaWRQ== +XIfdJ79nObMM+vyAmKbTmw== +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +1u+XjG/2+GSQRv6EzCaWRQ== +VD9zfcC25A/8QOaHOhC/08+yyCMXeBqRf4/ga7jFrTkT7C7mOGjvtRvT5O1nKry9 +P8yT4+bsOs+HQJp87m1F/reOtBiqflci0dOv1dBJ5CE= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +02raNMOqWaNt7xMESPC3lwgVxysXSXHuDQdM35j2P78= +20lLzEXEs+ueAd+bOAB+91r8jzaQl96Rr4UooYVAhek= +9UNCW/IYJm3y0yDOfWJu1hDSEjrLqqinLz9ENpiz7Bp7U6FvhbSHpw2fG7yfwZm2o8XmdX7QFCP6PAgeC3qwLA== +5C9WmrmzTMoEG9WnQlXcow== +7dl/3jjStHv3YKwNaOrbP+Oo5xU5m7MdajxYoJc85S92TEuqkIbRI+ohiQ6vWWtZzZM2CO+G/Tt7tZ9oQFyeGBfi+jfxFGgmnYMxeUi1JaJixQo/7XHqj5ninApCrSrr +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +OV1bY3WjlWolVVGpf7K0XuXsoXQHr87ax8nvHq6xsHk= +WHkOzVx7seuLmxs5Hu+l/t+vqO/0m+n9UCf5LD77H/SwGtkYMMmsF4cVmuhhatEVLh7WBABwVLMyOLWUBB+RadwWiICXHtE1kzcSxcgplF89E4ZHUf3MK6MmlUz6ew99 +32pdC9DD05OE2l0oXazDFBSGVTBsjpcBhCs4oxG+0yc= +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9jMBZ/1Gnw5H1SVochJBBx+ +NqhMbY8+EQI8zhTG6Zh2I8eIwd9vy9oCMj+HlVpAR/l7CFW4GiuliNJFtFnBA50C +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9iDQP8kO4z+Nh9k9xWY+COM/KxrWvWZ4Fal5fq021KjFA== +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+nolHQ42C9PtbiSn1F6OlCtRmnEtWURclz1HQwAElCBzw== +NHEVGZTYwXqY5vJLoK2Wy88Jwk3YGlNccZ+9V9zZnU+eb80uFKXdlJTW19PQSswv +32pdC9DD05OE2l0oXazDFCUyxYReX+Pd+b7Itml48JbsM7EEkGoW/LLDCJHqhTgLDQkOzUwuHM/Ow28K/8sDHIyBKBfp8UiP7Spf6jDWw6w= +1u+XjG/2+GSQRv6EzCaWRQ== +doXPckCdzE7wjT67BB6bRaf5yMsyg97qMshMoQ7p5vyoCIqIGhDYBeIX7pK+6SIf +Z8UsPk1Q7HtwjRd4g01ryw== +2e9aPy7fN0yHEJSt/vm4HRBZEsq1/zwflCBuSjOe7lpc8+97QgE8TmPh9W3s4bjTFqkssBBzUB3kSkpHlHBB8A== +eeFiPlXUI0Fos82HYztSmdKUUr+D7SPt33f2BsZZi0+Z/4tXriyUkCyekpFOTLlk +Z8UsPk1Q7HtwjRd4g01ryw== +MzOj4ZiBOJDNVP8vi/lFQNbYGt1FVk3Is6YVV6wzt87cMUguwHtxV3/Xy4LTsg2e +L0eUthVnpkGsmKFAX6d+uEP20LPBDczIBUCH+eDLR4XWtYJ6VE0yMGgVZeHE55+Q/qVjwg2ycnFdmwmE1t4pQK7rMwd5XeH6SKf0j0LJj1rcjuZWiUfgbRgMQOHfmWeVoHXJd8Gdujzjmu8XVaU3Yg== +5zgvKM85O5RcGwttJ2rWIPdwxXoXLnOjmhL6DYLhvpA= +R7vEIxyjiiVKR30PQVdBXTc3ZCpAzCM1mvOBtDg5HBdxFY32n+U4Vd4HmL3xGJ0J +1u+XjG/2+GSQRv6EzCaWRQ== +ziQs1GyPUQ7zPh2xP+c4C9jWdniwpr48mYVqFpshpfPIMeXmT5sEphPHgd7xjv51 +ygXqV8JYtXUVMKjwSB6Bx/HL/6ZwOKweTtEx9fTOy34= +RDZxR0eU/0YxwhYtovyGeVmHi5rJUJIHwjySNLmeLsS/YgiTjCQe80TqXYEeRodv +diZ40h9D+5PatJtYF1tU0duwNTXVBazmSgO2LE1bzRxihfbnQEKkYw7WpzZBa6M4 +UbJuac0dxLiN+5ocS3w2vVbjWbzmARN1Q6nZRO+hN/U= +VmVrGQo2zRokW/ZuO9bN66bxma0YBg8TGr7zrmdMIaysrwI2tOjz2KGqNWCW4fyv +VmVrGQo2zRokW/ZuO9bN64fCWnvBG95BbHi3cQzmuAc= +n/Vg/an64od1JqfYD8zjtnTgUj5eucdPqy9I4MCTvJdvKMaMV+njWEGl8CrMjCbman94UdMu8eZchGcaRr1oYw== +A7tBmpCaSLjSrSFXFx4rD5DetX8LfpTB33kFqMYK0Od6ZH8Gs/Y11F09RX46+ICL +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +7jSw4GB3eBRbmC1MzdtYH7AtgesWi6JQAvIQf6OzE3nseJy0QnGK6G6JTZ8m4DdlPAzSNTALqHevuq2kA19SCpPCP9OhqpP+gq0PaqyDu7o= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +7jSw4GB3eBRbmC1MzdtYH8hFhUduGUWsUH3mRmUlIWM= +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +1u+XjG/2+GSQRv6EzCaWRQ== +7S/W1w5l8hNVTCb+pjBU6IQBp7oTWR1btD/WKY+MFgg= +nI4TFpKsgYpjAeaQko6KTiS9GERA58xL2uQkIWwz+8U= +9uZN8r0CKVAbfzGtFDuvLxswrINnjWvMwuMvv7mQFLJ/lDrB0Yzi1jeGmHGjb9sx +l2FJPs4YkAmmok1ulDRuSA== +wlLHv6kT3Q/RmtMBN4nDAf5jouZDDJebuZx0e+GqD0kRo3kF/7jgrUa9LjtTIi7k +VmVrGQo2zRokW/ZuO9bN63WXNO21aDfpqHWqTMbiAspwMoEKToJ9evkuxTBcHidMf81BXfmdf+zT9d8k7rZRQQ== +VmVrGQo2zRokW/ZuO9bN6xFqwj56iFFIvII+sEi91i3uqenZ1RukB/iwA5+BqIZ4sn4SaQnBu9P1A4Q+K1sZtQ== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN6zviB9IGvmdqavzf213hyy5ybw9UFAPNCxKWXRAzML9G +1u+XjG/2+GSQRv6EzCaWRQ== +WJAfjl0WrD5Jtca2LHLz1bmB1KYomdySnhMTj+CfBFQ= +X9N2KsRYiOx20Z7QjlcKwPlYTqzzndgDM6lpBv04BpdP5Pg3w1osXEPQndKx0rQg +O3CUgrw2GJfB+mDjH5+NdjXyt5QeWCsfAO3Xba+4ChG/q7Y+X5y6P+nc7wDmMC42 +VmVrGQo2zRokW/ZuO9bN62VgOORuMBXqkUitwmHVIrDnse5ewM1R/I5hzCpTRGjRPjKi5jw4W/gnKcBGD9YBwg== +wlLHv6kT3Q/RmtMBN4nDAb3TgOhh+jcLpHwNqZ402ohizeRChhJH0mnfBvfdrTFZ +VmVrGQo2zRokW/ZuO9bN632VflaKbWaV9Wa8IOB78Qk= +VmVrGQo2zRokW/ZuO9bN6423Hrz4GlH+nD6sJ78NwiKP8lp8FUcPhY9VQRWbCG1fwt6D/Suz+3Mw4fD7HRaoaw== +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +VmVrGQo2zRokW/ZuO9bN69ZI8jvRenlEUWjnEq2NCPyDOWUWUPrjde05ZcnIjqs3pWRoX/dFNNNs2i/oTDcoYA== +p9oVsBgcyiBMjDb8CcPOqP1vx7YoaEO9HCXBZO2TNso= +Ds0U5vkLcQbSiWM0OBg+t4r9+nkADB6+pTfs6BnR2ZxqURGwpq1kta+TPw+SFYxBQFmX+7Si0OBWn9a+xTaiWnluFS3e3tzgqB5tisWnRARhFcyQwPYfaZhQ4qC+koZE +o6QhOIN2Sc4SHELnst17uev8ILgDsE8F6Lr4r3FiO5kX4kPxG5GWPpKuy8MG+3Pp5ak0QKFbZROCqcYHAtfrS5iit3HYIQ4qteW8Mhs5eEUKNbIOTh/MQvOi25rbe0W/ +X9N2KsRYiOx20Z7QjlcKwJ/jTfZo3KZRc7Jt0Yxl4gCUzaVNOZtVdc33LG13QSPt +dAlD/BDiyhUNR82MoKY54wOQA9Y9TRrid6IAzqKFFYlKwy9skQ3yRe3T15WYhesi1YWjlAGeJABXbkoJuF6IIk7XVJH7co5nkqJxZSD49Qxzny6hNig6oVk/d6uY6w1QCaRfrBs5nTcMZExJVAB7WmDhGKieI8G2bZn9lROj5A4= +xWoGNWjKGPfI4gq8aHoTfM/GWNK1ohgau/P6Yy5hh4NxJd/Z/S8GTdLTsA9JjQHV +X9N2KsRYiOx20Z7QjlcKwI4qbj7KfnKOrIFv7Z4lLtGUYNfIhUqPnWQkQbUEaS3fINZ8F83Vu3A+GF1Unidx3ROcHOnR+dfswdftnV7DDxk2JxMxGEO/rdyz4HhDx1HR +gC7pmugJ3/w5trYRkDiKMZu2feMB3hGfqRh2TQebwmSMQbtzCVrS+b4WQ4vQR9dB +o6QhOIN2Sc4SHELnst17uZEi6P73uFYiKT8ZfdKaq74uZIFYFu8J6CF85QIftAeI +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+kuoHiBgr/0Fv8qNCoSuQiv1gF3L0eu9kJUZmaEmUpN5x4xDhiRRncANufGyvnI5q4= +5eMaCO4xAK2QW4QfX+0YtKbMF1jS3cxAAkXneuE7di9ABuvCVnZ4SdZbeTDi2qoaSmKkDI5m7fh2F112P2pzKA== +o6QhOIN2Sc4SHELnst17udxbOsHgV61axCR3P3wfex8g46ZP1Sdx4i+CYHMTr6rl +pZu1Giln7AYpCOrLbI27d9Gmita51EFD9OUkFL56AbY= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +k7Mgm/6UlihxNIWiDi7/FQWsPKdojVfNRBOtHAgYqheCYcsU4cUBPL/6I95dDtlT9USt45h9FK4d7rl/Yz+V2cXy5SkFT71kfnrU3MZE6nE= +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P3oJY/mwDRD7KWNhsy6/Y2mb4RNPwLuEAcMxo+t3fDjM +bIts5UhOLbX+5vrkUi2fEgVzsPbCG1s61jTt5yW6ACsP1ywsg+01aUDIv4DP01qd +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +RDZxR0eU/0YxwhYtovyGeVX2L0QGVwLQX8TIwLObEVs= +o6QhOIN2Sc4SHELnst17uQC18YJWaV/6CEkrqfg0jS20MlFgiNUSShqPpoyfxdJZ +HNr5/wGY+7coHgowTe94Ll4RZO1qTURSl37kSeltNLP9Vb6jtwiW6YkMWV99p4O4 +gEJ/mwYAYmzZtF+bOEtqdkC8SHP+P7fOYSe7UxEq8rmitn+SghIfUQayzs5YBbRY +nWJfxfGKXK3M33nhNtnE4rhyN+G2GEkqT49HwMRlHEY= +Vh41Si6Af7eSg+/W8dfTM7yg2G8U6j8xv+R+xKztdB06IZseCUPYnwY/NdWIvEVs +emk1Y/Al74+4sZ82aqgV0lhGJemXCaG1r29Gpu+zhWmouexK5AYktd5VzhuOMg2z +o6QhOIN2Sc4SHELnst17ufzdxJH6EiDMn2kcCw/f00XLHkMwjtUD6soxHwPjfI56 +l2FJPs4YkAmmok1ulDRuSA== ++Zjg8k4ZNVjB46NJnSn8ErakeJYqLEw9DhX8QKCz44c1fatMLaKfX4kADtdWsVo0m3DBdTfktt2+zvPnSnSwlvIi2oJRgthe7eFBbm0/pMs= +RDZxR0eU/0YxwhYtovyGeVzRqxe/x+r1aScdsXNjbZEDJM/n9+iJeJ6fjq5jg85x +1D18KWr2hdVsBcdZx1OaPQlScmRtGt3pyfQ0+O6DoOA= +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1D18KWr2hdVsBcdZx1OaPWpmi5eWzbYhJFCY/NY8C/Oa7c/XoIMLf5OfmxNwBC4p +VmVrGQo2zRokW/ZuO9bN69Xj6ooDVrRi+Qx0VVbxxuGwXblBGt+UMa3iQsUS+MLX +VmVrGQo2zRokW/ZuO9bN6z0Wv8dJZfITZaz7/LBnekh8eKiykXRkJuTroXNsk4sHsjMzx3ehYnBiRsLxQ3l1Ew== +VmVrGQo2zRokW/ZuO9bN65vHbxdhaeboWCi24FUvfUKxwpfWxuzCii3Vfl81U5n04eCJ/I9xuyAFs+a+utQoLA== +VmVrGQo2zRokW/ZuO9bN66hhLXcZ90iz+YURPNpCdCsAm2RnkTlvB3/dCAYY43QH +VmVrGQo2zRokW/ZuO9bN68uGmvNP6wueRXu6UnefD+wH+Yr2ww3IH83h4hC1VuyV +VmVrGQo2zRokW/ZuO9bN61sfi9NDWbb3IEw7V243XdGQhqhFSYbmb3HIkuCdE2VLLMjOBsOxdHyxz3Pp+dE5gA== +VmVrGQo2zRokW/ZuO9bN6/jrSn0BMJUjTRmq3s4EygRutAv5minFbaCDNSpGOnuGOGbAGxzYhr5FfgbC2LAzaA== +VmVrGQo2zRokW/ZuO9bN66KELFqGTvIFlqiTgxQshiEJtYy+NLL3drj2rKA5FLSA +VmVrGQo2zRokW/ZuO9bN69qUnlBYp99FW3NU5/GK+PzoHh9gUCjYvnxOqd2JqPw/ +VmVrGQo2zRokW/ZuO9bN69yVdWZ+lX5d/HxJ8n7Oe9lsFn7ww5+jazR8dqP2+S/V +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6+bUoxQpnmE0JVwLD8EwzQ/H7dsGXPHJ9cqQZTkojMQ+ +VmVrGQo2zRokW/ZuO9bN69qUnlBYp99FW3NU5/GK+PyS8CBUEJbhmFgtsc9tZNZa +VmVrGQo2zRokW/ZuO9bN6+EOVn5y2jsFMxK6Zwn+wS/mDi05KQxr36Au3RyQiAZt +VmVrGQo2zRokW/ZuO9bN645GzHl1E1lX49ZzqICVdNd+qq6XjowUV36TmrdcDLlJRAtaa5meR0QErawnLRsIbw== +VmVrGQo2zRokW/ZuO9bN69yVdWZ+lX5d/HxJ8n7Oe9kengQKSCiIkCs6PDQVWcCGpn3+rLZxeaOALBnRhwQmjw== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +Zj8NVVe0emTHliAvlpCAerp3iS8Ldzanll9i5zjVEzHQg8mhLB3GjOH9WUhexCJRTK10ie4Li3ku0ivs8PwncfL0CsJ9wuEyiySAnQ+YkT4= +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/PxQtnmghDNkLHWxwLL5Pfd80TPKT3Fq0L6v1zn15TQIi +LikBEoRjt8wwWzUZNw+jJgWwZ72bx/3Bg25s1ZrnmbQ= +eeFiPlXUI0Fos82HYztSmQX4Tn/GgRQusZFpFlvVnwGj3wnNVeLP8HbDx9FBGXTK +Z8UsPk1Q7HtwjRd4g01ryw== +ERFPcpFJ5x6x1iUKDQh3uuNBeWAGk63K78XGpIjhm2+rBpfY1aEG51pD1dP3DCm1 +mDvBoIQgD/LTzVDzjae4UJqJvNLzaOzwnnIlpmmAT9w= +1D18KWr2hdVsBcdZx1OaPS9U/g0ilrDQencOJy/eayhgybOUx8AzmnF0FRkjYK4+ +VmVrGQo2zRokW/ZuO9bN68DMZ7Jr2bE3tmVz/lTLfwFI/TgA0E/RsREqTEyLRZTtM7CfvidC3a+UoB6eKaAipGC8lyEDiDjU2OcuQXtivS1abYS5vnbaL+qjgg9xvxZW +VmVrGQo2zRokW/ZuO9bN6zAeYkrMwd7SuWC1RgnERpd/CtxQ8iNGHrTJa/V2vhqa +VmVrGQo2zRokW/ZuO9bN64O9/l1Cw+LCUDbA/A3sef0aZa1fkxQn/BGfLBYcyIz6 +VmVrGQo2zRokW/ZuO9bN60zutLkqghuDP33toq5yXb5QoAWEq42gUIT7CcbtapHA +VmVrGQo2zRokW/ZuO9bN615f1QfyDTLI7xBDK4WokeoZ/LXykw7EJtsaIC8dNCUB +VmVrGQo2zRokW/ZuO9bN60/t3y383iQehg4Rf7JHddlsYUFMmzhkXzzyUyBqcUubPvH2W0j6argePbntySyUzU3AV893eEQa7Yym533kWfk= +F9Sftf1PoN5P+lgvc+r10GOWQ9m5fvKDb6g+UBNuAqJgppHlIonewfzaqeYuks8R +VmVrGQo2zRokW/ZuO9bN68DMZ7Jr2bE3tmVz/lTLfwGYa8KblejDwqMGy0HS7E4EgxqdWnOJ4dMlPPf1V/WbB45LajcY2+ifZj2xLXa9OfFHv0/Gwu03ahXrFxuCROiu +VmVrGQo2zRokW/ZuO9bN6zAeYkrMwd7SuWC1RgnERpd/CtxQ8iNGHrTJa/V2vhqa +VmVrGQo2zRokW/ZuO9bN64O9/l1Cw+LCUDbA/A3sef0aZa1fkxQn/BGfLBYcyIz6 +VmVrGQo2zRokW/ZuO9bN60zutLkqghuDP33toq5yXb5QoAWEq42gUIT7CcbtapHA +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +uvcMgENha6hOb5ExkFYzxifcHMl1I6jrVqfUC4eNgIoj4KJOBKY4nAmoO9a/O2O4 +Z8UsPk1Q7HtwjRd4g01ryw== +LikBEoRjt8wwWzUZNw+jJnTofnNhv2W4ginDjUT/BhyY1a6MiWTuSLmwGyGxJyoG +OOMIt/WiNRdlMd3fGCaVnH8L1KXFwR+AJLHaxenwu6I= +Z8UsPk1Q7HtwjRd4g01ryw== +mLDy5md6q2DkMFpvLtFlrbgr2CZgO+U3exRgHQl+WcSH91VecYi0XBGIdUBcdsZg +mLDy5md6q2DkMFpvLtFlrQOIllMJ+wGUeIFDucuWhfM= +mLDy5md6q2DkMFpvLtFlrU7D4Mf1ddl1j/aFZsBH/L00FFBrPDB3T673ZTtvJXi7T4ttNDvQwxI44e41cn9A6A== +mLDy5md6q2DkMFpvLtFlravQqYs0c/bzbQZd9QN3+nxowiHk/rK8+h8Eqv5Ky8lOiVVSR1FIS/LaTiW4is7mBg== +mLDy5md6q2DkMFpvLtFlrbzw3oheqXbD2qwAZMcuA6Q= +mLDy5md6q2DkMFpvLtFlrWGBrB/jcgDQIi0rEIX5vN8= +mLDy5md6q2DkMFpvLtFlrZ1FI9J4zyIUAMeW/0xk+lP3z9xCfVcSg+XRPXHC2cuC +mLDy5md6q2DkMFpvLtFlrZCAR5DUc2ey3Q5f9VfOQLDhu05EqWt2hD1A7vDKSpvg +mLDy5md6q2DkMFpvLtFlrVE4nQqlZKqeRHuhcjYG9cBcbIb6mWq6nCQfXdYqVLBuaNiK7wnpdzENXVQGO4xxag== +mLDy5md6q2DkMFpvLtFlrQsEeGnN4vdParE9CDgiAK4tpdIWqozh7FV+BqtNBoddCimScVPthPo5boSZ2aCWKQ== +6P8mKfouOK1hwXy3MrrCzRw2UxbgP+Mth9DRZFMbu9aRbVUdtXbkT66ZlvG5+Djo +1D18KWr2hdVsBcdZx1OaPWpmi5eWzbYhJFCY/NY8C/MQbhQT7DlATgfnJmvUUqXY/dtM6WhYa2J1aR47iGCprQ== +VmVrGQo2zRokW/ZuO9bN6wPqF0gJwvcSllqTssBN3IQFplxd+VVRZRZSvCVDzHK4 +VmVrGQo2zRokW/ZuO9bN61DdfsWGlVNr7xf50OuAfKCWMv/w8v4ixbracqKZTk/4 +VmVrGQo2zRokW/ZuO9bN6+bUoxQpnmE0JVwLD8EwzQ/H7dsGXPHJ9cqQZTkojMQ+ +VmVrGQo2zRokW/ZuO9bN62MG6pIvkYn7nEiOjeoc64MrwokZLOOcTxaO8jmQiVHe +VmVrGQo2zRokW/ZuO9bN61DdfsWGlVNr7xf50OuAfKAMMGpuitrqnk3bkbSIR8tK74tenFOnrkxVhSzwMR4nkQ== +VmVrGQo2zRokW/ZuO9bN6/Ly/04eT22h1xyl4dSvQSMNqp7Qyw2t5SG7+K+1JJ0tG30ZCAYaAQLvLpQPeNMYHQ== +VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncDdczYfwtpU5zW2MXjhy/z24GxeAx3QYpL0FUnYH9XQhhs1p0qlJik4yELSlk3g+Jw= +VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncB97WLhqixtZQpLQVJjO8F9rmnhbOS/OmY6IJG+17UdTrJrlEUKFErdk4ikKnb3BL8= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN65CBdpRIOtptu///2YRyptDJuSvuNBDGWMFm3MShUYhGV2dg4J7PVbUNMjSG7+hBZw== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN6xRaRh2h2yn72BLBhrbk40FNmSd9pOkjInUT3PKeP00k +VmVrGQo2zRokW/ZuO9bN6/Ly/04eT22h1xyl4dSvQSNU5MWz1FGNCr4JsFoMFY0M +VmVrGQo2zRokW/ZuO9bN60HvClbwMpABlSn73UhadQPjjrFMVzyd6bmtZhfVjiZU0huAoInXX/Q18uefitNtu7dcNYig6Kw0BYoxUhMMT0E= +VmVrGQo2zRokW/ZuO9bN61igX6t9WmvQd//qUjyBL6Batd2BDC4WPD9BlNf7ZSqbQhMB7M2MNINGZFx+z9nGKVdGMF8NhqzIBLeQIOPk9kQ= +VmVrGQo2zRokW/ZuO9bN69Xj6ooDVrRi+Qx0VVbxxuGwXblBGt+UMa3iQsUS+MLX +VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncBha4twiqqqB+Lkuo23an3nEDeccqba1p3sRs3HYCm9Zw== +VmVrGQo2zRokW/ZuO9bN6+BIYCVAZKyRqKArWI1vncBg1CWm6ODKEO48OETuF/c/M7gx4RLnKkvGw4yWkxfmUw== +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +6tCwW2TgXgdok4IQCfIwE64+sANSz5E9miXDw6QJ0r+y7ra3wf2izxuhjiJCK0VR +wgR07xfoapmx6eEnFHXXYo4se8cEG3Kpe+KryARC+58c1jICgdZ/yfiiPJOB8osn +iZaIsa48RXfE+uf/yF/rQB5Pyu2LLCSwLBPzS3UQNqB6gGSsuwVrKFDeT7S7ipIY +iZaIsa48RXfE+uf/yF/rQJ1gz5XqVdLjUgxnZLiZ0kxneHQUMBy4uvSK/N8oOaAN +iZaIsa48RXfE+uf/yF/rQNi9uyLdvBZCesZvX8KcrGbG22Xqw+geSPuZqdcR8y/X +iZaIsa48RXfE+uf/yF/rQGg6DytIW0WefmXAsCDYL0s= +L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk= +45hn3FAK6/Q5+BKz66I4FYGZrfj46julOMe5ClqgRDsgDKqc5g62TlKmklK79LSu +1u+XjG/2+GSQRv6EzCaWRQ== +a3HCOwVwgUUsIXy0NI30N1OnTRGwPCMCQnd24LPbvMgw/R3ae9al5MVMPI50MW70reHrwrOjYxRGfyTEKtkxrw== +q4nc/jwATOMUyfSjLibfEQ+nVsCkuVXEdAcBKpgjqkY= +b4OJVZe8QyIpjuTpKXDL9A== +iZaIsa48RXfE+uf/yF/rQOvVhu/ngkyor/C5n/UzkBlCx3epFa1yEIpBPWIrtKEsgWftLDpGjIPx7n5zFnTscEhgReIowtD1ib7ZaN1X1YU= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +iZaIsa48RXfE+uf/yF/rQAZ+EA0c51yFjsX9CPHlh/5ktj0ZrD7uCaj/wgnzHlOCeQCMjWvzIk/DQlZByMiq+Q== +XoHMeaTuJPZ1ZwkBYGxPGNO/fZdFrQeGdb4hk4VRpV0VW7fg9Snb4rb537T74XCuh+3WZ9XaioboPMJTeFhjmA== +RCRD0rTmvzRnTOGXlaehMNmLKF59VFrNXo8fzNysbhTBKccco5cEPB8/dJX6dFqJWYqn10Vvxc/CzhwbE8EJnA== +I9ZqFYKrj9zbehR1kVWMoO1GI6M1qqtG2eBy4D/nldc6VNZO14SHN/sd2voQ0WEXWYIgTZTr4rDtrGXNBr4IBQ== +p50nNlFbWWSM5Jl8TEsk4TnLz+h+3cBHymV4PzApmXUNqbCVGjhE1qvOMYMtCX6R +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +J24jXQQti6wQv5pCoF2ZrcLSPgZELT2A2fhuhSpqDo6BY87dEmAzodysqNgyKvzt +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOYzeYsCbBz/1birJj/JpatlFkFxq27b1MCr3POwO46E7P +YThduOXrtrzATLnwayQWnaA2rNH65m9eJvXKw5GWAAUtcZdLSGJWwaU999R64Cgx +7JQQDTi0T2idhFjao4jEpTcu0vyrf72ks6Zg9LXjcLTZ8NE7V7tyehMaTKgkL7Idboxtgmjsd+qtDVj49PH4pQ== +h2Amc56dmaRo3inGZxN9N3ZLtg8BK5nYtLYDA42Z8AY= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YPE3JL0PMUUnZO0HIdJJMQIGfUXEkt1C3jRrwzATAL//GACiXsL3H69yk+yUtmy1bN8AwykNHE9d/OHOYjevUg== +DarJ6ebtq241LBXqUNSmb8Hia5oTaY/pb+ay91woMws0FeL7ToyJ9YGj7DCzO+EoR/kGoHazwB7LUQuT5rt+Tw== +WHkOzVx7seuLmxs5Hu+l/kOzZi2ouy/XqACgrRboMuAkPXB/7WhDgtDQxPBuCGk/ +L0eUthVnpkGsmKFAX6d+uDbeqTXIh0y0jrTQHkGxk00ztM8l4dexLnzgjvrsNYuo +1u+XjG/2+GSQRv6EzCaWRQ== +xRFNEm+nIUjh4cyIzan96kWy6IA9PfznyfSNsYXjhcyekoNWgDiMNKfLwh7IzzFp6unM4EoEl230CfvpYsKmgM609XzA4rC8JK4FwC/3GZM= +WHkOzVx7seuLmxs5Hu+l/tfRUTtAad8Cb/6s2BEj2MF5sacS9liWC0L9lFX0T/8D +L0eUthVnpkGsmKFAX6d+uDbeqTXIh0y0jrTQHkGxk02YaDJD923/KMmkdZImosxQ +1u+XjG/2+GSQRv6EzCaWRQ== +uqDej/AgJHcP58Evn06kx+BRYuP+Rv/P3yyKDCiEank= +M6xDbqnqlGC4Hwvm9iO9WpYNmQzB5Emv8bNh+GjwifhyGxJaxrM3Nu55hGkrzp3Na0UgKMMZhQFDdB1qBnzZM4GXlShJqy10Xh8Ng3hvM6XmMaUNRpE1sspYrkvl/ok8txPZkGD8+6CimGoeq/gHc3IooOAxEh16uaQ9V4oWzew= +M6xDbqnqlGC4Hwvm9iO9WqiWh7KxdPf1TCqsUz96BP2LPZ5/6OAavVK3NQ5oRvG2WRCLP8CLNwYIKWfO7LKcMQ== +M6xDbqnqlGC4Hwvm9iO9Wscmcj6WdKWpwKUcX7+6wD9qlaetybTVnC7NRyEMXQaUruJxuLP7EQajaE97ybYV+A== +M6xDbqnqlGC4Hwvm9iO9WmrzpW7xqvFOkXRrquG/LdmkWI44+PZZb3h8jfRCzfXDDJpmmDVFpMslk/lOV8ORKMKf4lFQfW6IN3gOezMVNdk= +M6xDbqnqlGC4Hwvm9iO9Wrluw3FaFu0rQlI5v3W4Gahe5M3IH/7altsfw4aQIzFTFEp+kqHOFqW9zknlolTgr2aDwLbq3LeRkrF/cccTrDo= +M6xDbqnqlGC4Hwvm9iO9Wui7tfyp3EhrtndI0VQ+0bDRrlG5yESM9XqTcbJlcOwgCZixtz2l/jHsCLjZop31bNpXX4KYhl+/OzIp1VoECupI8G0NECKhXZGxwFP67OGP +M6xDbqnqlGC4Hwvm9iO9WoTR3NAG7MGBrcMrYbTxwvg2d5zTs9QiVoeexs7vgrp1tLfB/Bf9Au9wHlmv00K5zdOfVB4vDxKGSGToV0vyVNU= +M6xDbqnqlGC4Hwvm9iO9Wsk/YBXEvbh9Uvmnh0QJSiFhxQ69X5Iboy1Wk54I9i1KGX689Qf7r00dKSrjZWYdjLOCaQVypVZpz26m19HQouI= +M6xDbqnqlGC4Hwvm9iO9WkK3xOAmzw0wgWg759KvLhH+T5S/PN6qHRHRQ9T3LKPDzTOSNkmC8BZsFn/KJ+t2aLi6nBteKpQt56QmNdl4dqs= +M6xDbqnqlGC4Hwvm9iO9WicgU1xjseoMDlsA3ET1//BCkdCo5I3potyMnxOy8NaEhbkT/s1OThM0Dsk3VBA/QNrh8++5Afxt13QBvjjNDXI= +M6xDbqnqlGC4Hwvm9iO9WgMokrgL8MxEJjEYatsrUV+m4TFjPvxx1gKKyfrM5ibGqjVXQhxhwIodZHTc6JZeUg== +M6xDbqnqlGC4Hwvm9iO9Wr1kU9VI/yudDc/eHU/ewRmVwLQ8jsS8G8r/7F9bWy1TnWfhsRCr+7KbyaufVfaXfcW7CUaFOxd/zmAVrMdCGCc= +M6xDbqnqlGC4Hwvm9iO9WgnywlPbX5KSPk43KpzNKONPq30CaJS4nUs0mMkP5+hbSivbSe4CLkF0KKyRNsQATD8ju8dPAj+f107ThzNYRGhbxWww+AVK7muD42wODMJWmw/m4J/JHWAWU3/ibv9MTKX/YkfOX0bmEcp9LHx2Qxw= +M6xDbqnqlGC4Hwvm9iO9WmJCBrWMX5jYxKbKR4/u41xMqvIPvD3LZfDUhKcoaKuCpfnCZ+EipXVt8I72Nc/dYrkGyEFkdOLMBXhB0XKMLR6STMFXt34k9atd9Qw9x9lNnqTZZXN2lErP5TZWivV35w== +M6xDbqnqlGC4Hwvm9iO9Wqld6rg2Lx98CYBge5Z6YW6hSCzeqqkJgURG552IJQtYStVrim5S05TrSOKTWH7I0Q== +M6xDbqnqlGC4Hwvm9iO9WtVd4iOTlcBudzv96785RaYKs1fhppqqdhgtHLqtXL7XvQgtx4SyuONpafq5eZA/ig== +M6xDbqnqlGC4Hwvm9iO9WgHNFBZvpw4Ykrar5o7UITLqUwDKmFnO9r5qPTi6XzxGGIRw44iLmxNRh6SGGLj5FfIqITod7iAWgEPz9f8TRmg= +M6xDbqnqlGC4Hwvm9iO9WuZW+fuUZB8WpAoWyO5L3TVNxM59XEoFvdP/J5Mq7mp3CFXj+BrERDZdtku4jEnITQzW/Dt7uaUWhWDMFwB/e23rItPvfJnVza4yTyKuwcrG09UsVKbA37u7vX9Q8N+YMQ== +M6xDbqnqlGC4Hwvm9iO9Wve61oMllAKK7aIpkSBv6ZwWnrC4aacWBYjk6HyqSYHL3Hq0XfzMZLHvhQDh2db3gQ== +M6xDbqnqlGC4Hwvm9iO9WoVS+2ab/4yGLNZfC++bUsOORjTX6oF3vbSWEKIYHfC+YN+4SdEA/Th8xOpeytLoDDgQyYgcj2oY5OlwO/zmBYo= +M6xDbqnqlGC4Hwvm9iO9WjKEpRdLTg7WVPHPtUqJqomzwXwNg8PGbqA/U5P5s2Ph9e4kTSbmMaVhig1rkgK80A== +ThMMHJmbSWymjnzPL8su8sBHn5UE8cpNu9WLRZiCzbQKCkCL03sgs+DNRoTYeavL9RZ2I8MdhFqyZIgb2dUmGw== +7sp6hnTw2OtZMRbJTXg9rCuLplGGh0CaZ+h81UyYXoTfxQz5dfr9h/PfGPK3/x1X +V3FyDbaPyt/GrdnaVxxGP5QP34MBqfVhEo9rUYClIQ8GQn13ZWbZ1M6lAwu32F6B +aTu3nEEJ7aNZbzv6PgxsAMIq5Ov2mq1jFSdTHQbA4AjQAxNj/KCefFN/tFo+IuhQiYS29bChBFdJoonMS2dVyA== +UaZ+UhuuDk5ogKtL0+DbtF7ZZMj2uXAUIl9TJ345Nxmn7StJaC+PsiBVbPv50MBUIixchc/TaeXRoVZZIe8nmg== +UaZ+UhuuDk5ogKtL0+DbtO8lJySRwyixd8VH5w6ddE5sq42a0K/FaZh7d6UNKygbk02lvC2lEd8zp1b07VqucA== +UaZ+UhuuDk5ogKtL0+DbtEs4/ygchHdZq2caT5P7ZAY/kfYVA9rE+SmVnD6+LpWybbHzyvSm6PZuNWfaxF7GPw== +UaZ+UhuuDk5ogKtL0+DbtMpwh8AvPkdmTABoemWOBnMsvPbmXsy2D3mw7ujaSN+uVEXDHvmeZaWIoPTaqnsQVw== +UaZ+UhuuDk5ogKtL0+DbtN/ExXZO6r+1Hcx6QYA2uMFerjP1PMKvvs4KUFhJZHFNY9UPjmpmdJT54wPfO3Unnw== +hQ7Zur2CVZat0LHXIoOqQEKciq0mRp3cUUC7gBemh69qrx4U+SnMEWhzDrNvYhgm +OcTtZEQzjmqOEoLAu9WIDBnMum7894CWxyFLkwGu5IwtyjTHaWA2GKONXV4r9vTbg7+1p4tBESO9qkBU74HTEw== +7sp6hnTw2OtZMRbJTXg9rCpKNGJwajbjYG7HLQCEEEXdDjiGpseZYp9Crn852qiTgxwj5jsQKnu7vZaHhMdVCw== +aeUTuKRk4Cv0a6/hj5z/Azbob5vVUjA8C9zwLz131NFQ9+8QpUnpNMjZSkEDyzqvNJOEE7EErl0S1qQyJp0S5A== +aeUTuKRk4Cv0a6/hj5z/A0ufTREaztlpquv0pmAqO4eQBh/OIVHNrMu+G8nKcxX6NRI3YvUe7O2X95abzQ0iTA== +aeUTuKRk4Cv0a6/hj5z/A66msItV6S9si1UO/Jz4XEtSerCl4q49UQ9M8vbWbqsSRL7I2Hi6gUZNJBpgAByHeg== +Yh5IC9IfOdzCK3e5XDQpq7zb2TsOXHn+3J5O2euHIXfq/mGF5OhOVbuFMPjXnQ+M0Dg5Ohnqz2pi0eTNKIzU3g== +Yh5IC9IfOdzCK3e5XDQpq+2odTci4K1zbNd8NJCQULk+QCw5/atmY2M9xvTShAOrmRgT9J1cZGUyLM7hRG6T9Q== +Yh5IC9IfOdzCK3e5XDQpqxA3qd3TcYSVTzrAlmiWJldxDn1jD1ym4yTsgSajOLNbDK+wnMHISwB8YRnXcqofniSCYnGaRqMvj3MURlsXp1w= +Yh5IC9IfOdzCK3e5XDQpq4DKOluGK9CtCgb/1zx0eeCNnM5UGmgXLCeepHrrYTn7VFMw8Fm+mwdLVExOl2tjpZOppE4njUrAe4tqn0RXteY= +Yh5IC9IfOdzCK3e5XDQpq5Ifj6xfZnDox3MD5utXFuixhN8RmZlHKUvwYnDADhnNXPn0TzBaUVp8y3F1IayBhZc9me9kQSHvBQ7soWODUbs= +Yh5IC9IfOdzCK3e5XDQpqzRhjv/5t8mv7bNNVXSZA0n4tEX8ATMW6aOFzRYG1NzH8pTBJQ/1EU81Zjuy4E0Uaw== +Yh5IC9IfOdzCK3e5XDQpq5aQfSbh2TPEVX96QEtEYaDiuEyvpOigBao3+w795EB9JATH6Wr2GIoRnqzXQgdrIA== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +661hZf7vhUQ+50okfwfTXw== +fE3zmgbfhO9ZwEda5GgwD0R+fpJ7JqroVfr4B5FO/AhYM3WK1zBVVzIzdn4lt3dirmMTx0U2aKsHZMW89EKgpg== +aPdLEV6HY2SBr9CPW/3NlfBbk//L4rv3UKu5v766u0A= diff --git a/class/filesModel/uploadModel.py b/class/filesModel/uploadModel.py index 916957cc..8eb83e32 100644 --- a/class/filesModel/uploadModel.py +++ b/class/filesModel/uploadModel.py @@ -1,170 +1,170 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# 上传文件至oss -#------------------------------ -import os -from filesModel.base import filesBase -import public,smtplib - -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart -from email.utils import formataddr - -class main(filesBase): - - - def __init__(self): - pass - - - def get_oss_objects(self,get): - """ - @name 获取可上传的对象存储 - """ - return self.get_all_objects(get) - - - - def get_file_list(self,get): - """ - @name 获取可上传的对象存储 - """ - return self.get_base_objects(get) - - - - def check_email_config(self,get): - """ - @name 检测邮箱是否配置 - """ - import config - - c_obj = config.config() - mail_config = c_obj.get_msg_configs(get)['mail'] - - return mail_config - - def send_to_email(self,get): - """ - @name 发送文件到邮件 - @flist list 文件列表 - @msg string 邮件正文 - @to string 邮件接收人,多个逗号隔开 - """ - - import config - c_obj = config.config() - - try: - mail_config = c_obj.get_msg_configs(get)['mail']['data'] - if not mail_config : - return public.returnMsg(False,'未正确配置邮箱信息。') - - if not mail_config['send']['qq_mail']: - return public.returnMsg(False,'未正确配置邮箱信息。') - except: - return public.returnMsg(False,'未正确配置邮箱信息。') - - msg = get.msg - receive_list = get.to_email.split(',') - if len(receive_list) <= 0: - return public.returnMsg(False,'发送失败,接收者不能为空.') - - - #附件文件 - flist = [] - if 'flist' in get: flist = get.flist - - result = {} - result['status'] = True - result['list'] = {} - for email in receive_list: - slist = {} - try: - data = MIMEMultipart() - data['From'] = formataddr([mail_config['send']['qq_mail'], mail_config['send']['qq_mail']]) - data['To'] = formataddr([mail_config['send']['qq_mail'], email.strip()]) - data['Subject'] = '宝塔面板消息通知' - if int(mail_config['send']['port']) == 465: - server = smtplib.SMTP_SSL(str(mail_config['send']['hosts']), str(mail_config['send']['port'])) - else: - server = smtplib.SMTP(str(mail_config['send']['hosts']), str(mail_config['send']['port'])) - - data.attach(MIMEText(msg, 'html', 'utf-8')) - - slist['error'] = {} - #添加附件 - for filename in flist: - if not os.path.exists(filename): - slist['error'][filename] = '文件不存在' - continue - - #超过50M无法发送 - if os.path.getsize(filename) > 50 * 1024 *1024: - slist['error'][filename] = '文件大于50M' - continue - - #中文无法发送 - if public.check_chinese(filename): - slist['error'][filename] = '文件名包含中文,发送失败.' - continue - - att1 = MIMEText(open(filename, 'rb').read(), 'base64', 'utf-8') - att1["Content-Type"] = 'application/octet-stream' - att1["Content-Disposition"] = 'attachment; filename="' + os.path.basename(filename) + '"' - data.attach(att1) - - server.login(mail_config['send']['qq_mail'], mail_config['send']['qq_stmp_pwd']) - server.sendmail(mail_config['send']['qq_mail'], [email.strip(), ], data.as_string()) - server.quit() - slist['status'] = True - except : - slist = '发送失败,' + public.get_error_info() - - result['list'][email] = slist - public.set_module_logs('files_send_to_email', 'send_to_email', 1) - return result - - - - def upload_file(self,args): - """ - @name 上传文件到指定的对象存储 - """ - - name = args.name - filename = args.filename - bucket = args.object_name.rstrip('/') - - if not os.path.exists(filename): - return public.returnMsg(False,'FILE_NOT_EXIST') - - info = self.get_soft_find(name) - if not info['setup']: - return public.returnMsg(False,'未安装[{}]插件'.format(info['title'])) - - sfile = '{path}/plugin/{name}/{name}_main.py'.format(path=public.get_panel_path(),name=name) - if public.readFile(sfile).find('upload_to') == -1: - return public.returnMsg(False,'暂不支持该操作,请将[{}]插件升级到最新版'.format(info['title'])) - - #创建任务 - import panelTask - task_obj = panelTask.bt_task() - msg = '上传文件{}到{}'.format(filename,info['title']) - exec_shell = 'btpython -u {spath} upload_to {file} {bucket}/{filename}'.format(spath=sfile,file=filename,bucket=bucket,filename=os.path.basename(filename)) - task_obj.create_task(msg, 0, exec_shell) - - public.set_module_logs('files_upload_to_file', 'upload_file', 1) - public.WriteLog('TYPE_FILE', msg) - return public.returnMsg(True, '已添加到上传队列.') - - - - +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +kEPo6lgxLBQavG1DxRrlnEK5KohxBShsQIeNCGHkEP4= +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +dTLQ8Wr3wPn7w6pte1B1YA== +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +bPP96Leq4/6bN59hBCaYlI8pXwmlcWoHnMhS0JRYMuI= +1u+XjG/2+GSQRv6EzCaWRQ== +NbgOlQKOVjgcHXr2EWkJqQWzgWH6Vq8RJ/AUSzSss8IVUY2HzghyE71ZaXPBTLGv +NbgOlQKOVjgcHXr2EWkJqeTgbDhQ7kNNY/wz8y2GqwZTfVZH1snrz7MBnfOT91tS +E9KwiiXGE37hdLvk/mY+UIznMbuZ/AbAoo9N1G0YvoLz61bIHX5Q0KQJryQ9TSuJ +1u+XjG/2+GSQRv6EzCaWRQ== +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9bJD7OGVYIbkQwcqio3pMkEQ5Tepk1KAKJBdik81BOHNSVFwrMdl2gFGwZvvpYDQ +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P5ZvVL4YjAgUVrqKtQwmJSm1F1KbG0/rNY/6xTjx3EA3 +Z8UsPk1Q7HtwjRd4g01ryw== +lt0ONPc5ulpyg8e5cXCrlcNbN4AJdkj3/jMJ6CSe6OnifSM9+lA1O2PeRoAR/q0t +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrcF30x6jIElrdtnLwh7xjCTUby5LexzwAUmLxkZ6OZp9 +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P5ZvVL4YjAgUVrqKtQwmJSm1F1KbG0/rNY/6xTjx3EA3 +Z8UsPk1Q7HtwjRd4g01ryw== +CAhn8dRUItEbEErp4w+lX9NUcXl4jGRSijSFq7galTkLvLnalDlhEukOn7KLDmPy +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +mz/fdzicrSUPVLp6o7PwCSAYjWkN2zpyX6N0H3XQ+Re/2H8C5nEqNaf5oXzlLd5r +Z8UsPk1Q7HtwjRd4g01ryw== +0LFyB+q/4AUBkCrX53xYJZNkGwqwopNQ7HR7+KY/2yjfRImCHWTpijZdrdo/d8l8 +Z8UsPk1Q7HtwjRd4g01ryw== +KbLSYqE/CR9TL3ZELtBVg8UiZep5zt9moZHOJ5ffCHo= +1u+XjG/2+GSQRv6EzCaWRQ== +iTSUWsaHz8xtfxXo170oO/0vr9reANk6j9ykbM5WiCYFw3OvMYMGyJ3KgPGMrF0H +PXCtqxQPPqk7Lcqhz/3AoGaqmPHjN4rP7NSesY70x/92ifCLZ8Okvt9nT27s6jtrmbW9pJXGLgLNf+3meuER5A== +1u+XjG/2+GSQRv6EzCaWRQ== +FEZSdYhAV26PfOeirhfda9fPkPkpgcLIU1bSxCG9qHs= +1u+XjG/2+GSQRv6EzCaWRQ== +DJtqiO8w/dKi7HwLds9peNr7IF6LPtUssSL3IuNOYGLlHjOwFmfwe9OK3nxxRRgN +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIliYA3yrJMlMcLCMVYxV0uObw5q3Q+Zn4+NYyZBHBqFEE ++kvn6XYS2Dcvt6Ptafb+egXt7PQiX4xuk1EJQthbw8LHHFksw4XlK/hEZYEN6MEz +Nl6FAwmBymcnkmQ2v1saoWguYo5PAJb10t78p9wNmQ967hJsaE/VyX2nuWwy3sK2 +JEQfGov5ldbBWaI3aZWXDNAOlBZZiYaTkM41OkhTw7DxlEAMKtlD3FDpetB/Ra/HrMZWBT2ISI5K4C7FovZIWg== +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +KbLSYqE/CR9TL3ZELtBVg8UiZep5zt9moZHOJ5ffCHo= +iTSUWsaHz8xtfxXo170oO/0vr9reANk6j9ykbM5WiCYFw3OvMYMGyJ3KgPGMrF0H +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +HNqEbxya75q97PmRdMU8Fq0RpPGtQqmyQcXNysvYcIzmzwtTL2CB6nQWsuCPipUki6fsBjosP5fyCX/lXfWtP2aHIN8g6JRrW4TZbYQNKv4= +1V2v8QerKOmubvSxgB4eTATuLP/YkzXuFS9s9v29gfLs7HzcbbjmTiso+zmsCZ3S +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrRQLiV7W1lz/PImX//PWNLkFIuv7VUgU3+a6PwWzgXXaB4nsglac/VuhWclPIvVlgOIAaNg2YXbEjwkZqivaweq +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTJ841yfu7l5arGk2XF/kO8mJfg3iuq4dIggjGJ90dwx8t4CE+4HLudLp33Sh9joOLg== +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrRQLiV7W1lz/PImX//PWNLkFIuv7VUgU3+a6PwWzgXXaB4nsglac/VuhWclPIvVlgOIAaNg2YXbEjwkZqivaweq +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWg3MlyzvlsDpJ2tFZppFlDIgMeiVDQ2HRCla7COtNJPjhTSrDI29qVP4xDeSndmdE= +1u+XjG/2+GSQRv6EzCaWRQ== +uIl0RcuWgRCPZivR9HE5K6P0KAPh0+c0Ci+5o0mjZKg= +KHVPPgRdeAM1K9qeWQ6AiqCJ1c6DyhB9VCKPe+SB8o/S5eT66VcxZqE1Abf23JR5 +gApoKqs53F0hB+0nTxFBUK0sDmJFt2pcRuqhtiHPJ+voEfz/6grsNrnqbl6KL3aq +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVf/86mCDVHuaSE/lwim7QrFi5TEaeF6XshC8H9LAxRJInVUSAqtWixvfFY8aKz+m1a1RsFEpCSFGYL1Q+qZONa +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +gHZvnjbK0+c2DWpdjJTIKjYmDJs6pOpneqykoeukYiI= +RAcgdll7MZc9X2PbkeTvEzNyiOpuxHuwyFCiwlCwtBo= +NgkU9z71J5Awi50+35yVrVFRMlqzLfxgW8oRH+8xszId0swAa3xYf+cQPC3GoRX8 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI +YSlit6erKXylrjSZnvyW8Dke2RKJMhjXyxnQzpVZgOM= +Ys4BPYSOoHeIx+ql4jJylD2VLnaOQOBr+k3Ytq5owd23kxsNazPnVuATSOUovBrq +bWoUv86VLMtyRQlo14Ahu3xfaGKmZUw7MPoURoMwHGI= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN68uMQcDaeKFBnSol/Upw31Zbihi4vGENQHlL+dj6DCvT +VmVrGQo2zRokW/ZuO9bN61HRAGek/auc8b4JvIZlfzGQ/b8xNkzKFp1KEGLxgbuwAYkeWczxvXCQWc0loQzSHyq5aP4vAsBP8KIObacZsHW2l0eK/68p8LwM/KD04HbjIFDKWSdnqDmGI5E/oEv2+A== +VmVrGQo2zRokW/ZuO9bN6/0hFCJypSh6x2UIBkV7VvXsU+cO8B7S9xx1VVZWBeu0eYDfkAI5IGvcY5NDn0MqTPYuqy5Bl3zY8pT547cBMFbo51+sa93m7/eHEKuy5GXE +VmVrGQo2zRokW/ZuO9bN6wHsvvxh4guxHr80r9yaINtJrvRK6UdvNL8JUA1IT/spxWK1U/dlzvPRDq60WZSm+w== +VmVrGQo2zRokW/ZuO9bN6xd9hVVPG/da3jOneq2RZCcce997vOo+rQywLRgLWiWuNr25+4z1FY+siXTO3eViyQ== +VmVrGQo2zRokW/ZuO9bN6/CxiXZULdhjyOp1jTEufd+jVXQuLPUTInFNcnpN1I4wKnR+4QeoyJLsqr/3r41c5otdZB5U+KnTPi6U0yHupXO5fT2t9XsGdz3dQ6SMRN8yg9FL1qHp4ThJ4cz/lg4X6o4uGI+UWHWKQ4ipM/V7GcM= +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN6/CxiXZULdhjyOp1jTEufd8fOViMKvV/4EBLt4qPjTM3P8imZTOW0FDMijb9jSuw0FVDpqK7f2sMpzWe9pEkHJdj7JuaHPwQym1UOAXnaGko///EYYbAEydMFZn+m+korg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN63WsPkEtKdp9rQFEV0mc0Bp0wD1zMIehj9MyHLROd0aILFdAc3MAmaemnxPAUiPekw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6zsXuunPbx71ngX6BaQenPNbA+Mg7i1nazF95o2HQtU5 +VmVrGQo2zRokW/ZuO9bN6xTkk9WeCniuLJmLBef2m+U= +VmVrGQo2zRokW/ZuO9bN65Mk3nfYCJEpzMz8EnuHOsgreh18KDp4UtHG8fOCTUhO +VmVrGQo2zRokW/ZuO9bN62oRed6hCMosT7UP35UqsiKbQbJoCKHWLyJn/Vx814Ro/Ee5KiD6gqXP3nh6dppi/w== +VmVrGQo2zRokW/ZuO9bN6waLqc54ChZODKGJgS1pLojVY2evr/6W6eipUfB4kUV4tIbbM82aET5PVAdbZXXdUO/Z4wJ1V7uPGDDu8Cb4YmQ= +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6x8dPCIFym0qQ4B6l0auqv3VSU3SG/i9eUcnpJrb1L+X +VmVrGQo2zRokW/ZuO9bN6ziVGg/SZJQ2a2ljdz3XhcVn97lFqgG8ziUZ47BfMFG9UXbT2kS6y93cABsoh/aDUSuKrmA11kGt+hTSze7FVyk= +VmVrGQo2zRokW/ZuO9bN6waLqc54ChZODKGJgS1pLojVY2evr/6W6eipUfB4kUV4jBljvu51ZnW8hS8WXSpu7pv1/kMj8Ak/oIB5g6oKubA= +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+Hp+GvFtSASnTbptRcIg2lSnsLRR4J22UX96CUCq+KR +VmVrGQo2zRokW/ZuO9bN64qagL+89Aq4rR/hcuQ6/QaZLgJxtVesokbhCUzfxCwfKcyVGoV3zyER2vrKLj8C4A== +VmVrGQo2zRokW/ZuO9bN6waLqc54ChZODKGJgS1pLojVY2evr/6W6eipUfB4kUV40d40RaKmxMWpfhvj8ZyruQ/Q/AdiPvG5CMZanDD6YE98WLACskd36bpmVVS7VPAS +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xUIvvF7vKpIldg0eDQ2GLdixkmDxxbU+OXh2/259dgakzCXh2oDJ0AfTKNrUbZ5d31ZrBNmK8mELWMQzPcKOzPm5KFJw1Mu/Hkb1SkNQs1d +VmVrGQo2zRokW/ZuO9bN69P4p4urRz6LNH8T7D85tUmvT6fU8K2BiEON885R0KsYZZwTue89GlNh5Rj+HJ/0l8p8Xk8+ZEyH9zTSoSVrZuM= +VmVrGQo2zRokW/ZuO9bN69P4p4urRz6LNH8T7D85tUnJY2h08qZtVSMViVJ80aOFEB7NOmEnSXBDfiqhTqlpxqE7hd3sxipptIDNtiux052BIE9Ab59zGW7HA4ampTWIfcrnofD4U94ciAzYjH7cMQ== +VmVrGQo2zRokW/ZuO9bN68SNBDbgryQsl/pB0VGbLvqWKmuDEMs3IPB8IEm2OG3x +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+UgxYwTsTpiHdnLypOQ/kZoFZH3aRRa3jGRdR795Y1mA6vXFA8gZ85TG3MqCavIWvAJnmCjVt23ZkwLlEM33Qocr5vG2sgJeT2xmYYZ/iICcOfrgbcyhaRW17sPNkIRlw== +VmVrGQo2zRokW/ZuO9bN6yYgZ33nPjoy4tOZmNaX7mQaiu/N7s/Mw1Ei0aMstGjWDlGg9LgiJMyZKxaW8bZyIjtyH8MUjBVG9J4w3mR65uirBBRQcKVp1rVnwRuAvlAOObnzQPL0B/HA/xlJ0/WDfA== +VmVrGQo2zRokW/ZuO9bN60vhRuPY/LJZF8HROzC0dFI= +VmVrGQo2zRokW/ZuO9bN6/6qy3lEE/QMSwWTRtFJoaMcGV0W2eYWCTCr8Hkveve9 +M0ZySqkmhuHCw6olbCKv99p0Om1sGOCLOPcvWBGiKm4= +VmVrGQo2zRokW/ZuO9bN6wgdH+0DZQge5QIlnRbbyGVIwpFYFAZXcG4ciFkbD8xGTtzX6dDhJzsy6JEW1M67sQDPORs3az4NjR88iM8Lcnk= +1u+XjG/2+GSQRv6EzCaWRQ== +o6QhOIN2Sc4SHELnst17uQbQrK2Z34ZMKBCsEar9NNzRcJW4KMeQ4yhsPKTJaw1j +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs9jY9CEkJ+mtN1RRkcQDmMZqWpTNzdr+Hy3p75kNCSdGSik+k6MbKPBA27LVqGIPTg= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +jGPLgFRMa/gLI5pn0C8oszLqa5isI224m2QOfckbxLELIVt69gRzqdxrFQlcGXIa +Z8UsPk1Q7HtwjRd4g01ryw== +n3tXewq2IYqiwFREjJjsxQv5FBS89YO3jVo+BgMdPGUBLkZx5+ZpOpx7LWu6PFKo8b9SqRNn6c5JZVzlsIkp+A== +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +xsCYviGksjEQaKpL56on/X7AnEB6uKwmFpBflqwZHQ4= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +a7gbuXC0s2yINkxKhLLBokQAN48UxYxa6jCVIMQ9FeJoGd91ZEhhcfiymqUdYGqf +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJh6sAOIfkvF5oIqf+7bT65s +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQ/iPzQ+uFBITVsVoGpItD5Q== +1u+XjG/2+GSQRv6EzCaWRQ== +AN33R+yjfqqAkAsFnmP/r/WGRgGkDEv8Fnc8kjQsJ7c32mbK3YgEYOi8MYXpOqJy +cbvASHjpysrsjdY5RctXmG9wund6qoaXAfnCgO+o1nY= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXHKWKUVVa/rOrR3+fgEd7tp63QHMzA34qukyTQgDFJrLVYVJL/Ye3kNmAIiExi//aTWiBsWXWTJE2/bLQwR3ie +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDGWrMrUVsu+cay2sf0G/q6n9TVdK0jEcTi9z8Mi4uhlIHBQoCZCJNLutARg3AcC9GZkJHoO5OtM5wGrnT579QQ/33LGKQp33LdowTPKOj8mg+5XvwqiamHNJ4b6qkc35ZA== +TQRmC0vOvJ1P+lfyS3Os4fdA9rnIPjSDuS8zcTDjbSEVtc81+Y4AUEIsuAgPRMzVhLwEUrXGM6DCeZ1rhkVK8w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUzJgj0ZVdf8DkqZrnrpLRzKvaQBgq7m7DBF8Wb/aWuxeFqQz2lv84Je+M0d4O8q9Ux2G7KTGW8VqvvCK6513pJrAuADi3mTtgVX9Y0JiEdnx9iCVypIiCmLG0BR48sQeM= +1u+XjG/2+GSQRv6EzCaWRQ== +y9uLKqgxuwiJJuaISH8LNlJR2o2Cr2uNLeYPxMDYl/0= +q4nc/jwATOMUyfSjLibfEelnou8pLYpfebj3/LvnkGg= +7fBvPEAf72Uv1YEDXbbQ3CN3MWrD+3vkr2VJCCzzEPRhwccGf00/aNA1BGgdy8WH +59Mj0OVU6q9AFARXxvxfN1dW2G3zJI+4zsdXTWh4XwFG18UDXnpLmLIfLIZ41jcpe1yBIt+f+JDrMrCbBNf/9Xy836/xgiba8x96DZU3Wlg= +pcGeDqO6jWjADfc8Oyd0b2PTNAiWtNjPy+4ZuJym09tcrllO4bi9TB9YXcFHDN8ulWbcZDzkd3vYSjeW6cb+7a07iV7nvIzEjupMBYIINGsixBBRFVjl2m3gGdMptbNGHA9dcrK5ewH6POHu2UFt2t23FwcWAqOHzR3sL67HAErpzje2T7IZlttmY5tQ/RPJ4QDTGGJ3YSogtm9/Z9ZSs7wMVRGX8M2Eia5bIBLZuxk= +7fBvPEAf72Uv1YEDXbbQ3Kl5asOP7Hk9Z+oJF57QC0c//DIVOxrgJlRJ07hlZuLdNPXnVpbKF/o/zidPewH4xQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs8ZqmZBUF1OtUXbF0cnfKWhyLN47Y9VJ4gMAghwAahsZHCVXCB6/L26wmeLJb9nC4o= +2+RceSNjD2iq0sRzepMyse3K/6KDWrRQWf3bA9DlS0lC8P1AVP/ryfVnqkju1FOT +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOEvWExeU8Y5S6VzOcEFIF/zQLgeURpx14jzr1s57LjK94c4cdiNmekvORipQcUMeV4= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== diff --git a/class/filesModel/zipModel.py b/class/filesModel/zipModel.py index 9542fa4f..4ecf80a7 100644 --- a/class/filesModel/zipModel.py +++ b/class/filesModel/zipModel.py @@ -1,327 +1,327 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# -#------------------------------ - -import os,sys,re -from filesModel.base import filesBase -import public,json -import zipfile,shutil -from pathlib import Path -class main(filesBase): - - def __init__(self): - pass - - - def __check_zipfile(self,sfile,is_close = False): - ''' - @name 检查文件是否为zip文件 - @param sfile 文件路径 - @return bool - ''' - - zip_file = None - try: - zip_file = zipfile.ZipFile(sfile) - except:pass - - if is_close and zip_file: - zip_file.close() - - return zip_file - - def get_zip_files(self,args): - ''' - @name 获取压缩包内文件列表 - @param args['path'] 压缩包路径 - @return list - ''' - sfile = args.sfile - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - zip_file = self.__check_zipfile(sfile) - if not zip_file: - return public.returnMsg(False,'NOT_ZIP_FILE') - - data = {} - for item in zip_file.infolist(): - sub_data = data - f_name = self.__get_zip_filename(item) - - f_dirs = f_name.split('/') - - d_idx = 0 - for d in f_dirs: - if not d: continue - if not d in sub_data: - if d == f_name[-len(d):] and d_idx == len(f_dirs) - 1: - tmps = item.date_time - sub_data[d] = { - 'file_size': item.file_size, - 'compress_size': item.compress_size, - 'compress_type': item.compress_type, - 'filename':d, - 'fullpath':f_name, - 'date_time': public.to_date(times = '{}-{}-{} {}:{}:{}'.format(tmps[0],tmps[1],tmps[2],tmps[3],tmps[4],tmps[5])), - 'is_dir': 0 - } - if item.is_dir(): - sub_data[d]['is_dir'] = 1 - else: - sub_data[d] = {} - d_idx += 1 - sub_data = sub_data[d] - - zip_file.close() - return data - - - def get_fileinfo_by(self,args): - ''' - @name 获取压缩包内文件信息 - @param args['path'] 压缩包路径 - @param args['filename'] 文件名 - @return dict - ''' - - sfile = args.sfile - filename = args.filename - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - result = {} - result['status'] = True - result['data'] = '' - with zipfile.ZipFile(sfile,'r') as zip_file: - for item in zip_file.infolist(): - z_filename = self.__get_zip_filename(item) - if z_filename == filename: - - buff = zip_file.read(item.filename) - encoding,srcBody = public.decode_data(buff) - result['encoding'] = encoding - result['data'] = srcBody - break - return result - - def delete_zip_file(self,args): - ''' - @name 删除压缩包内文件 - @param args['path'] 压缩包路径 - @param args['filenames'] 文件名列表,数组格式 - @return dict - ''' - sfile = args.sfile - filenames = args.filenames - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - with zipfile.ZipFile(sfile,'r') as zip_file: - with zipfile.ZipFile(sfile + '.tmp','w',zipfile.ZIP_DEFLATED) as new_zfile: - for item in zip_file.infolist(): - filename = self.__get_zip_filename(item) - - if filename in filenames: - continue - src_name = item.filename - item.filename = filename - new_zfile.writestr(item,zip_file.read(src_name)) - shutil.move(sfile + '.tmp',sfile) - return public.returnMsg(True,'File deleted successfully') - - def write_zip_file(self,args): - ''' - @name 写入压缩包内文件 - @param args['path'] 压缩包路径 - @param args['filename'] 文件名 - @param args['data'] 写入数据 - @return dict - ''' - - sfile = args.sfile - filename = args.filename - data = args.data - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - with zipfile.ZipFile(sfile,'r') as zip_file: - with zipfile.ZipFile(sfile + '.tmp','w',zipfile.ZIP_DEFLATED) as new_zfile: - for item in zip_file.infolist(): - z_filename = self.__get_zip_filename(item) - if z_filename == filename: - continue - - new_zfile.writestr(item,zip_file.read(item.filename)) - new_zfile.writestr(filename, data=data, compress_type=zipfile.ZIP_DEFLATED) - - shutil.move(sfile + '.tmp',sfile) - return public.returnMsg(True,'File written successfully') - - - def extract_byfiles(self,args): - """ - @name 解压部分文件 - @param args['path'] 压缩包路径 - @param args['extract_path'] 解压路径 - @param args['filenames'] 文件名列表,数组格式 - """ - - zip_path = '' - if 'zip_path' in args: zip_path = args.zip_path - sfile = args.sfile - filenames = args.filenames - extract_path = args.extract_path - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - if not os.path.exists(extract_path): - os.makedirs(extract_path,384) - - tmp_path = '{}/tmp/{}'.format(public.get_soft_path(),public.md5(public.GetRandomString(32))) - if not os.path.exists(tmp_path): - os.makedirs(tmp_path,384) - - with zipfile.ZipFile(sfile) as zip_file: - try: - m_list = {} - for item in zip_file.infolist(): - filename = self.__get_zip_filename(item) - - if filename in filenames: - spath = os.path.join(tmp_path,filename).strip('/') - if item.is_dir(): - m_list[spath] = [] - else: - if not 'other' in m_list: - m_list['other'] = [] - - dir_key = os.path.dirname(spath) - info = {'src':spath,'dst':'{}/{}'.format(extract_path,filename.strip('/'))} - if zip_path: - info['dst'] = '{}/{}'.format(extract_path,filename.replace(zip_path,'').strip('/')) - - s_path = os.path.dirname(info['dst']) - if not os.path.exists(s_path): os.makedirs(s_path,384) - - if dir_key in m_list: - m_list[dir_key].append(info) - else: - m_list['other'].append(info) - - item.filename = filename - zip_file.extract(item,tmp_path) - - for key in m_list: - try: - for info in m_list[key]: - if os.getenv('BT_PANEL'): - shutil.copyfile(info['src'],info['dst']) - else: - shutil.copyfile('/' + info['src'],'/' + info['dst']) - except: - pass - - shutil.rmtree(tmp_path, True) - except: - return public.returnMsg(False,'Decompression failed,error:' + public.get_error_info()) - return public.returnMsg(True,'The file was decompressed successfully') - - def add_zip_file(self,args): - ''' - @name 添加文件到压缩包 - @param args['r_path'] 跟路径 - @param args['filename'] 文件名 - @param args['f_list'] 写入数据 - @return dict - ''' - - sfile = args.sfile - r_path = args.r_path - f_list = args.f_list - if not os.path.exists(sfile): - return public.returnMsg(False,'FILE_NOT_EXISTS') - - #追加原路径 - src_list = {} - for fname in f_list: - if os.path.isdir(fname): - s_list = [] - public.get_file_list(fname,s_list) - - for f in s_list: - if os.path.isdir(f): - continue - src_file = '{}/{}{}'.format(r_path,os.path.basename(fname),f.replace(fname,'')) - src_list[src_file] = f - else: - src_file = r_path + '/' + os.path.basename(fname) - src_list[src_file] = fname - - tmp_path = sfile + '.tmp' - if os.path.exists(tmp_path): - os.remove(tmp_path) - - with zipfile.ZipFile(sfile,'r') as zip_file: - with zipfile.ZipFile(tmp_path,'w',zipfile.ZIP_DEFLATED) as new_zfile: - try: - #过滤旧文件 - for item in zip_file.namelist(): - if item in src_list: - continue - new_zfile.writestr(item,zip_file.read(item)) - - #追加新文件 - for src_file in src_list: - new_zfile.write(src_list[src_file],src_file) - except: - return public.returnMsg(False,'Failed add file,error:' + public.get_error_info()) - - shutil.move(tmp_path,sfile) - return public.returnMsg(True,'Compressed package file modified successfully') - - - # def __get_zip_filename(self,item): - # ''' - # @name 获取压缩包文件名 - # @param item 压缩包文件对象 - # @return string - # ''' - # path = item.filename - # try: - # path_name = path.decode('utf-8') - # except: - # path_name = path.encode('cp437').decode('gbk') - # path_name = path_name.encode('utf-8').decode('utf-8') - # return path_name - - - - - def __get_zip_filename(self,item): - ''' - @name 获取压缩包文件名 - @param item 压缩包文件对象 - @return string - ''' - - - filename = item.filename - try: - filename = item.filename.encode('cp437').decode('gbk') - except:pass - return filename - - - - - - +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +Hf+bBTgRv9pVfall8ODpvg== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +1u+XjG/2+GSQRv6EzCaWRQ== +K3QJU/hV27C5FAVoZ+avNNMfDMygeonfI5TFnvUHjhU= +X9BNoFOwiQAnMPqEk6FnbzU/oA+chL/aEAzGH80mBrNNxRMBdiGPqgDUeLF6rer2 +hUVhKZtvg+FKessuieMZGkP/5FS8EfJsx7HCvhsTdvM= +KbrESL0Rr2jC6vOleUlueMUgBS8ubHMHEf/dewOmXjc= +yUFFATi+ZVxTJRU9zfoit8QIFboIm1z3wG2Pz/1LIy0= +CjJS7KrzKM18mlLqJK9jKTB717Vvrubu5ix9tb047ao= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhLTeka+iJoxis1kUHdyggy+yXh3Xc8xsyir/5NbKP+Sj41j2UHgL27dZFRsmbh/P+Q== +KZTmaJLp+FU9X93j5Tpqtw== +0LFyB+q/4AUBkCrX53xYJedFoNZHYELHOLu/R25oBrqozNv9g917+/ANMSb6X+WR +yqM179SzW9HnBHozkU+IlaI6kf6Fax7Db/qE4wQMDFqfwVE+4rG3+IJ9S3c+p3Wf +akHibliG6j9uvBEg96vRehUZmhX3a+KyYm/Obf/H764= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +1VHvxkEcO4espNMpWHmnH1EgArAJXS2MAairjwNzV9c= +b4OJVZe8QyIpjuTpKXDL9A== +wQn8ViRVbUL8OLSIgBD0ID21VtRI2rJC6jqthFugWvWVef56vW1JZ4xiXoQ3GDDo +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +1u+XjG/2+GSQRv6EzCaWRQ== +l+sL2BKY+z3yDMAsWI51b39VWekdKmbfrOabwvVHTbGfiUobxgPhHW2OIZyeGUJ9 +wQn8ViRVbUL8OLSIgBD0IO53Ya1+ckG2IX47+ykaGJU= +1u+XjG/2+GSQRv6EzCaWRQ== +MxWDzXEQjj6mgdfarjHFtAA6unJa5l9pTo0P8ga3JDU= +1u+XjG/2+GSQRv6EzCaWRQ== +x41zVQ6DCtWXndDIq7U8L+5quM3ARMCnVm34NUX7l/TJzZZ100EvdUXHXI2cFH4H +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmme9ACxsySMBZr2gsEQniKL +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +akHibliG6j9uvBEg96vRer/Fg5zTlM8oj8hSAsYq018= +KZTmaJLp+FU9X93j5Tpqtw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +1VHvxkEcO4espNMpWHmnHy+YX2/U7Ip4Uqt3suxNhindpukzgQ1aM+PshcW/1dV1 +LuO0LAjc92aYQU4tSUEvi4x4NJPWR/eKWjRfKMSRL8o= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmV/41TA4054wrqxXx/Q1i8cmtFJNrxypDZNiWIVVjiYLA== +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +ei/1A+2pKZSasjYF/t4qojBnh7Bi8Fen8Fcf8mKjT9IuWg8ZCZGedub8Eqjf8qUh +4nsuLyRybkZg8C3fJCsRPXDWwLMccXwEpFBdFK0JOvg= +ZFD6pnBshb3mpu7xvvuMLemrazA4ssXDDuW5SINX4ZLsvLIwaJ4YyDANze64OGqUEj7NCJs+t+zkcUskHr+gqw== +1u+XjG/2+GSQRv6EzCaWRQ== +g5xFPP9LKBd4uktrRYzGf+p1NdL+k6huN6CrzYXY3fPVQzhtKStMTpRTZpRqmhGR +1u+XjG/2+GSQRv6EzCaWRQ== +wkESzX/Mo7zNrX6wCsHIMgfYkIJy+Dcvs4tK//49fTc= +wlLHv6kT3Q/RmtMBN4nDAbqo0o2IA7mU8WVNqZvoB5I= +VmVrGQo2zRokW/ZuO9bN6+qRbBr2ygG3RGcAIOk727ylzODmV8VRqZ4YKvduSUCu +VmVrGQo2zRokW/ZuO9bN62psx0nYMGJaQ1S1BUtE6l8lgsopQJ3Qf5syQpAasAAf +VmVrGQo2zRokW/ZuO9bN69Woqo49I0Fga24Nj55N9ZL6pyjjGewhvRJVBn8x2mvRw+9QouFIpzWY1J2Ub+ZqxCSNlCNDuqqN6YMHyK4kxCg= +VmVrGQo2zRokW/ZuO9bN61e1I6gZaVwmbMEHc7jd9wFEHpeilcxXyCkd5bmWx/yK +VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh/5aA4AiEej6zodAVzZFzS0 +VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn8LNRE5UkGbTG1UxYqNO1dyU2pay/FWt6r8mJiKZ4UU/g== +VmVrGQo2zRokW/ZuO9bN64KhHtDRs0SmcOS1WVIbDs4jrgwkpIqneJssR9hHHlJ3UJd9tZzLLz6C+UQLUJWWGbgxXo0UEEapI14RLzluAUk= +VmVrGQo2zRokW/ZuO9bN64KhHtDRs0SmcOS1WVIbDs6tF3Tt2+gNwTBcUtu+YYQKSt4+UQPUctZ0/+hfCqv3GxDub0pA8qeNN8AxvOE+5lk= +VmVrGQo2zRokW/ZuO9bN6xT3wQu8/ZV37F/D/EekYn92m0FWAHkcFEifdcSefZI3 +VmVrGQo2zRokW/ZuO9bN6xQU/k6xhXMbxscs7Gix8nNqUkjAknQH3LrGVvAyjYtQ +VmVrGQo2zRokW/ZuO9bN6xOutEc9teLv333RLphPxTSZMBPTZ5nEHEAZCqisJAYdx733inB6pPVPHnS53cxkslRzdzhPLD4mmySs0XcaZo8Yb7xMuMGGlmbahCRF6eC3yiiMtXXp0R7BViTpiJ04fqWtzkElISMu7z4pkkwr23TjUMjTjCelUG9Lwgmt+EiK +VmVrGQo2zRokW/ZuO9bN6wv73IH9kSzXKMUmUPacSk1KDsdrjROzTN1kzduuP0l3 +VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc= +VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvxN3810q5P2uCqBbefHbrK +VmVrGQo2zRokW/ZuO9bN6/kiIWH8hJ6dJOo/fnNj9VLcBHtqpOZ/Rc4AjXcEm1edHKkbtiFAlXrzE+rfp/AeMQ== +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN69+qNrAfkaoVno2zP/PIMh80w0dM3z0phdMS+lT6j/xo +VmVrGQo2zRokW/ZuO9bN65rWaakWr3uiXA345yT9jOU= +VmVrGQo2zRokW/ZuO9bN65abAZ39yZLyKCl4nd6eov4HdKauw4+toNc5YoXvsAeW +1u+XjG/2+GSQRv6EzCaWRQ== +1VHvxkEcO4espNMpWHmnHx0/qyT1YxdKfIVIaA4qitI= ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrRvdf2ev6P6vInKuFAO8eQfewP9/G67aA1dZDLRwjari +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P7D7U0y87ntgFdQV5REShmlP+1p+XjT1dvUjFUZHsqC6 +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +YSlit6erKXylrjSZnvyW8I3FYH6DLcfYpMAlko6lQqVBBd1eDeDGaujrHWIOJkcI +YSlit6erKXylrjSZnvyW8CCccEStX4fvFvduMtCGi54= +6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA== +wlLHv6kT3Q/RmtMBN4nDAdAMeIPpwO0Pgawv937x0Zw4iwEVDD1EIlJA37GxF8mZ +VmVrGQo2zRokW/ZuO9bN64/tCOCyKu41lsl+3BVlFMtjVa1DXtvHbVFGS5luzEIFtQp9eHGpneVvKz65gzXCXQ== +VmVrGQo2zRokW/ZuO9bN69Z4WTbfjflDn+PHJe4HyeBCMRIQIdsxtS/SkQUyTK46 +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64IUBVOpZieySJUi/5Xp7IP9i2hr//841YnIEqNDctKsA4PCQR7FmaZMQHs7tdC+NQ== +VmVrGQo2zRokW/ZuO9bN63KtgQxYoQMLiw/swXgEzq6F5dbUK15QUQFhJ/fqYDb2bGgnr3J8HSW6FkIPXw/QAPGbSGA6euyj6sY6jr9uD88= +VmVrGQo2zRokW/ZuO9bN60+Gj1vDNaFgHQa9qY+WYQ/+8quDVQ7mW+ZFMiCB6dK3zTXaMhAFlgEsY2XTlgH4hQ== +VmVrGQo2zRokW/ZuO9bN60dNkfoHcjUpCVMrBt5WSA9PCuYzYl0QLyyWqlMopPgY +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +6JhaWzGWadTJ3yzCk9OQNgkLy/m1ahTUuEy+6FiV49HNwHkAN2ZMxFTsufeIA0ig +KZTmaJLp+FU9X93j5Tpqtw== +dar6N0tUAb9rU8/aX1b3tGumQ1hhfk8O6qr6gmiQ1+eBvZwFkpECn+RP444+i510 +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y= +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA== +1hs1FfIPISXmd7TJ/r+Vl5VOoCywAZyr7zSPYceKQIcrfmFscSLitBH8rHfUTRshPd3qsDNW2TEFVB/BherqRUPAAaKNdUCadxkxouQ603/UPQa2mdlVSAGn2R+CvNrg +VmVrGQo2zRokW/ZuO9bN650CvvO4YLL+rKCQnmVaPF2En1jKNRAIMYaPmWw4DTM2qNuk40r70LOd5/jqlUfMYw== +VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +VmVrGQo2zRokW/ZuO9bN6xrJVNh48JOJRHCQYI4xAH6seRNi5O7iARUSsCPjTbt2 +VmVrGQo2zRokW/ZuO9bN6/GmqY1U6uFDyEbXCwKITV7VrKjU57dBsJG4vNk6MQiq +VmVrGQo2zRokW/ZuO9bN657WYPgEitoQAm574YXmF9lUB867LK9es4C6vHCx401D0rMP+Q/+dVAUlvxCaivz4QRggUPK+VUMIqZGEHb1ljU= +u8vW5y2SRttIHP9hhx+fqksl4XiiUx+QjIOk2+hITXxAagiDFp3uE2PeHg2zFP6D +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOH6nbHs2WDQfzsBPtsBHumfOcU290Wam9UwXjcW8zT/eaFb4gRZNo5rRuNz1TQ6cJU= +1u+XjG/2+GSQRv6EzCaWRQ== +O2CqINTuIULxX7Gu5GskT/Nm15YFovaVXDj8ylNfQi3d2Qbp7uZYhyvrRE1b06DK +KZTmaJLp+FU9X93j5Tpqtw== +t987AUZxQR72IPRcAqvQ/tuxrpVopv9MhEHHX4HHOFnF6WZD8PVxYK60YbaBpzoa +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +c1XU6vxl7MsQKGo3r4amMgGxGyqG90Nz+St+6838nV5Hy3ZAmDi9Ecxx2gyS4yZO +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4um2nnLtPSMsHb7nUhh0kKQ6CdlvwTSeB4h8LEiuwG9t +XKWK9LfYDT5w2LEWWxwkc6l2k+0eByijzzjX3gaj5k8= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA== +1hs1FfIPISXmd7TJ/r+Vl5VOoCywAZyr7zSPYceKQIcrfmFscSLitBH8rHfUTRshPd3qsDNW2TEFVB/BherqRUPAAaKNdUCadxkxouQ603/UPQa2mdlVSAGn2R+CvNrg +VmVrGQo2zRokW/ZuO9bN650CvvO4YLL+rKCQnmVaPF2En1jKNRAIMYaPmWw4DTM2qNuk40r70LOd5/jqlUfMYw== +VmVrGQo2zRokW/ZuO9bN6xIOVX/jmHApY1vYi7p62efnJA3RwDkPnY0p8K402cIDtEidZgORRD2w1kwOy8ktqQ== +VmVrGQo2zRokW/ZuO9bN655gCVY8shnXyf3BTAhOCEseA+FmLUdf2zTZRuj4Gshw +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN657WYPgEitoQAm574YXmF9lUB867LK9es4C6vHCx401DoXSgI87+dv1IXTMOs5pogfgyGxM8q3ngzmVKGd4oLv0= +VmVrGQo2zRokW/ZuO9bN61dxACaOXPUs+kKrrvH8m6V6uiphjbaKMJwgGptqRuCR9Cj1NHc2Yj+FdsrtTHaQoM49Sy0pv/Om5EdLdtFFjJ5lg/dZmMsubirsi45j9AT4 +1u+XjG/2+GSQRv6EzCaWRQ== +u8vW5y2SRttIHP9hhx+fqksl4XiiUx+QjIOk2+hITXxAagiDFp3uE2PeHg2zFP6D +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOFPUBPOYID993hmow4PfQhItCgegxeWNdVD4M5OUfiqV7yqGqj3L0yy/roD2hGd6ho= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +D5nlraEpeGmKL9lxoq591txjX2tGuFl4D7WJZ/91lnMQ6WZrDXvU2okqtgoaqWSS +Z8UsPk1Q7HtwjRd4g01ryw== +VcOpbkIFMz59VlIHGus1M+TodC/Ia97E/RilXZKKBR29V7X/RlCXQzuWibEOEJfo +c1XU6vxl7MsQKGo3r4amMsSWef3BumXlCWI6QCsp6pMACoHb/XNQfx2cpKV7J/MH +c1XU6vxl7MsQKGo3r4amMjvzqCkQbmHuG+orSe7QiThO3NRqf8lDLQklcqf6u2x4yWOeax5QyyroyrE443cDvw== +c1XU6vxl7MsQKGo3r4amMlEDkaPg52QqeaL1e16aTiZGdP9MF1i1K/F0icaNF/FRZY8CjFaji8Ecm2WEpXZbMiyduIw57U2lMT6kzLqch3Y= +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +0NowFhvxcpa35RgS0fkY9ejQNksclvfpv75ehCpzJxQ= +rTZ4GehPWBQfXf82D1FIzy9S8wkwIsZ3K9dIGaM8SqbP43GsEZhJZUm22nJ+2gPqOOxkmco8qaHhW3SBF16WUg== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +BIa0ibHnqUboaK9kYHYO4kqIZZgv1yXQHpqfJ4aNJ54bUNUYTYq/0A+dhHby52Au +77Mf9+1xBvZiUeNTtU/s60Kyn+UEu/OgKFt23E+wd8BiTmgbWFTzjcyPW/imPN9p +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYmSt8oPS+2lwNla/wCY62SBZ7HAsGrtjJH7dzi+5hXSr +NqhMbY8+EQI8zhTG6Zh2I5csQ+/eG6egKX2sKZJStJZk02bOBi20A8UHGNy6sybt +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP263Ug7hf7sm5bqxRn1G7j8CZM6KmOHJcbEZNXo2mkEA3MxUp7pEuHhp8IfRMvwD6SZPogdTtHhT5unY8iIrUiU6GaMq6uosuXq8hjxV9nUgwa2iqC1n6Ziz6tiDdPy1mw== +wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG +NqhMbY8+EQI8zhTG6Zh2I0BiMnuwr8mLUTT6rraGmzoW2r/izQNInq2UmHNhRbv+ +1u+XjG/2+GSQRv6EzCaWRQ== +6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82iJ2tHfDbjNfVtFa8KsTVYDHjxHgDB1l5TRkSasuLGs5g== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN61lflwobhmxel33RwHEAzsw= +VmVrGQo2zRokW/ZuO9bN650CvvO4YLL+rKCQnmVaPF2En1jKNRAIMYaPmWw4DTM2qNuk40r70LOd5/jqlUfMYw== +VmVrGQo2zRokW/ZuO9bN6wsF8dCAuyXrReoI3w9Yj8ejyyRZrswtbKGtDKu6ywDGElhkS5s5y7BVqVo+FqDhvA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wOXhaCTOJSjUCWUfjx+YyYNeSRhAnvmOzc5j0wt6pWe +VmVrGQo2zRokW/ZuO9bN61ODJTwuVdYa2e77kV4UyyiKC6xmDTYLpzmUEL6iaKIotT6XRfjdpuPhvefHRzC+7lCYolxcDYktKmTY39wnf+k= +VmVrGQo2zRokW/ZuO9bN63p1oaV/8/rZOHP3bxdBsLvxN3810q5P2uCqBbefHbrK +VmVrGQo2zRokW/ZuO9bN655297gigIv+4Ufy0bubbx7ffTJUoURR64bKe908x4ap +VmVrGQo2zRokW/ZuO9bN62cJZxyhxghVn78AgYWoneU= +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbdSOgZt1gFP6QuzR+PijaPfkXI6n5+LDNetR25SN0GXgg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkns33MIHJ07fAs3bEPA9prEegTrYMIIXPrzC4jD9xKXsA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+VE/nQEHJOLSC1DnxvG5TiVhVg6gVuqdGuJQhlv12yWuKoI97jUkrKrXkDjch0nxA== +VmVrGQo2zRokW/ZuO9bN66Stm18E9cFphVzr4DVvkp0eMQP2KdIRCCvQSRwvi/ogRLfVX0ez0AMMWvL6BswFfzkKtdTc18Qoh/tgpwV2o+Kht3IBHRneKuZIpe6ZGcaJTVPTytE4Bqe8/Zl36QIobQ== +VmVrGQo2zRokW/ZuO9bN6xSxF3eqlbsmTCkJFu+pH46xsXmb4t0jdQQcKfcGkEaM +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmgImK713UKKRnRLyQBV8gyjNySqhD1ER71FyIggw6WuQR8Ewb2c166ROgirDfRPbjt18mJAcMOqb3NN1zON9EtMy2glRMsNpgCk7fMSHxR7K6cqkD4EtrpnRF/O2N/bUo= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yaURb+J41vJInDMdNBqADJBPzFwU1DWEhzV7Igw3NGvcy7cQ4IWtwYwzl4xxnS4q7aao1GGQ/lpjefMWmVC5io= +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbfaLzRNSm7AkFFrFp+7ESwXtG1lg7SuOb/Fvk+nDWM/LdSTEbaou9dv1kw9992cO1wzuimxPOSJKkAKKU5hd+6f +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6zsPJOFHzvayEpAPvGiJ0lAWXca0TugmxSi8hINZZvoHQ1Y8EIw5ZU/3qx6IfU7mzw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkqG04WXvd0zYTVxLMDUCWknQtobuOLFvBEBrZieoy/MQ== +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk6a/inNeVFTn9NarE6P/3LG5P9KtqHegfYsmaf6Actxw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN68jWU1VqsROJtSXOaVTpeKWZDZ536km13XBycpNTV7Nhu/w7ITcYY+83JHaD4GV4Ww== +VmVrGQo2zRokW/ZuO9bN67FJHoI0w8vvrPd3RbddRrDBThH0/mxaArrt9hDAJ1SFmBLkHrD34oMhy7mWtOnyYg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN65aFRI029wWEhVZc+cvXTRpzda0UlKTw9izNui/xkgdD +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6ylpzdD3c1E8cbuPHoSW5RFJzxWCEvXq+6grREYT2q2eRpfKq4CUkXaSg9PBb07Brg== +VmVrGQo2zRokW/ZuO9bN65qgzI0QqVLALq/Lz0RhHgVj4W6v2iAhy3bpNtZbOHJ8qR+FfOTDkpmrnn3Bg7loWg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfko6jVO83Qfz+7nZDbM61ZOu8HuZ+ZNu6hMvpayaQR5SAc= +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNd/k4iuNp2bbqaDmaAfkosj3Q/Uyc4ih1OKYxi2Aoat/XPMKk6ZFw9dS97mdyNCW7sbOTUzQ7vKis/hh/FWBl +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1/Vu+G41TBzTVvAU2KLSeCv +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTcB3+DRCAF+pezmRjCID0udaZbU9+Bp25aQzLB8QDwQjJOciYA4ZMGenxD3oVSnPjhf/xssdSo8p33UNNP+7LnwEXEbYt+fXn2F5oiKjC6Gw== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGez0gsOf0QeJW43sHDXr/9AmAy18p0NZvV4WTviIGdP9rl3wBU2+XWJNGEpDCn6tY= +1u+XjG/2+GSQRv6EzCaWRQ== +mObL0pW76xtWwwYeubgb11DPfOo/O10pxYUkfDIVCjm8OCRNT0T/bbX1vgDt/X1W +KZTmaJLp+FU9X93j5Tpqtw== +H7d2MP+WgR9k+129/mRN7ij0WzXIk6KGh6j7E1fgHISsYBh1T2OGWCH8QUwPOkcn +c1XU6vxl7MsQKGo3r4amMvdbawOnt/oBPSHXSrswDVS5Xue1Y7sB0F2vPYt641B/ +c1XU6vxl7MsQKGo3r4amMpPnl/Q0nxs4Ul/v+JmLHipRvS1iO3+JhmWzafMyTI0p +c1XU6vxl7MsQKGo3r4amMqf9zivp0Qb6thhCnxaiYdHXwUNLWpYP828px7Gieygp +akHibliG6j9uvBEg96vResIm2pvfeBRQxNU2J0BQeUc= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +YdUmdqAC07VhbSt1h6KCDASPIXTwTSkv8cm4ToJ+lpo= +g8q6WqkWsk4SUh3d5nzGDIJanaAux29CDdQxdeG/k+U= +Vyh8kMpaq2Q+odra9mDJ3Acz3kaMgWRroPCWRIzYRMY= +wgR07xfoapmx6eEnFHXXYmnjxw2aWD9jyym26bSe8wdUky12ZBuFKfAUxS++Pix0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW/27EoqfC0X59kkH7imoHQb/TQW5074PHVFRKqgD5aSA== +1u+XjG/2+GSQRv6EzCaWRQ== +nZKk1bbhIicHthktrQ3ySmJb3nXruK0WPj1O/nD1Op8= +HI5OOAOTfAAnGE3XBxyr2vdUoaKdz9Yp4Em51oFqNhU= +mZ9rrP32sP7RJKYMPGILD+78ba2l0mR6sa16eW97WcI= +O3CUgrw2GJfB+mDjH5+NdlB3aQOXnKkk0z1WAt9Jmo/evqziM8DhkT0YrYGb4COh +VmVrGQo2zRokW/ZuO9bN67lx9HSk8eNN2hBCO4HpKRk= +VmVrGQo2zRokW/ZuO9bN69ZjyuXvRyGNy9kZJ0BSUPrXcl5zPRPxpUUNlqBFMfTepJuwObNjyv5s1Bwxw+0zHA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yiPlSW1IkAq8PnO6zy+iP3jJCfd1bhtOH5rekZFEjXi +VmVrGQo2zRokW/ZuO9bN6yIV96dETdHvOTzc1eoxsu7+haDOTWWWdxKhfAdfjn09 +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNuRprwNE+dzFP36dxj7lYWb +VmVrGQo2zRokW/ZuO9bN60+BaOrNhyGitXORdqvsMmwFtJw9x+0nfzpYbP/xMELoydQeOelN/AlsLE8govEqlMQrfUl1PeNk2rX8fh0ZElrak4Q4jLc+GGiQ0DLMMpnHYH/V4LeOzo0DpkYIcND3BA== +VmVrGQo2zRokW/ZuO9bN60jWe2OHiR8czCXypGRfxf3KT4lPFL0wUA7wcV6fbrUq +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6xYvvPJ68rqA+nqtjvksoecuaI1I6HrJYyQuBkEcxg38GjzrZVp9q6JUUtfh0kiUIfnKe6wTljkMngNv5DLroHo= +VmVrGQo2zRokW/ZuO9bN608vqClPKbLpxLXcA30riX215gq1D0BrebU6pLhAhTm2 +1u+XjG/2+GSQRv6EzCaWRQ== +IZj4TYq2HlScfSdRkI+QP1DXXDz3reXEAHM/ysmIbguf7VfJ/dsB+EE4b/l5XAWj +WHkOzVx7seuLmxs5Hu+l/tg64oMMngzQdC6c5uFYkJSfhDT5Ar0GKZd268ltWLHQ +gC7pmugJ3/w5trYRkDiKMWt651l7qmaQDKAvk9X5I0aA2tjC5DMB6fPkHfzLvY+S +1u+XjG/2+GSQRv6EzCaWRQ== +6Vd1Vz4KEbM/6qVZBGLAGuJhapKEU8TBPzOQYI4q82gnJ7Pu1Da4cxfKJ3J95+e7Z75/5e7XrUkjQFagToxKXA== +1hs1FfIPISXmd7TJ/r+Vl5VOoCywAZyr7zSPYceKQIdJVXu7JYnBUb+iQtMPqcsojEatZy5pDdbDBezG5DA1qLdz7Wh2R5B0piUodv86TpthC/OIBMMyceg05hLT05OZ +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN63uaPsI9SGIYFo4qu6fQZHL9GGcTmbqkN3kKfV4iG89B +VmVrGQo2zRokW/ZuO9bN6364+6D+dT33Zk25tJjqlVgtoL29ZI9bEfBKQ6cfJNwStfozG9mThVS88sNzbEnfeg== +VmVrGQo2zRokW/ZuO9bN69uIuNlhHDMeqLOAnCu5uPWT8evwqXAwcfDwsmWoxBJB +VmVrGQo2zRokW/ZuO9bN6yMdhcWuddd6xv9F64cBYywreWSVvaR5B3yfWI7OYuMP +VmVrGQo2zRokW/ZuO9bN60D33f2rnzf/qOtpO3Ttt+1LbZoGyDvOeulSnp2R0ilck0EERjIpT/ktNM4srJBraTngKI5B04/LBl3323wp0nE= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69ypGNbHyZMpYxMtQ3L+IMga3+inL7nbSVVsRnlPcc0U +VmVrGQo2zRokW/ZuO9bN64Ads2ODFtiVcSlOcVp7F8BTH9nWa2Xc95X7mNQD1kCu +VmVrGQo2zRokW/ZuO9bN60D33f2rnzf/qOtpO3Ttt+12NIPaRofJCp377XaLKfiybd59Ihi7pD1c3XhLcDanb1TM1C1QH4BPTOHZUBJeWHk= +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUikKCfZfG5oV8DWImIHip5MlpGXTmv9J1XV3V25fcmp5aa8KtmY5GAfonB0YuJLBm3Qyg6ipnG5U8MSK0eJ5IXtPW102IBhcOrIDStubnpSgQ== +1u+XjG/2+GSQRv6EzCaWRQ== +u8vW5y2SRttIHP9hhx+fqmx0hSbFOYRP5+aWkTy93ZFnIE/WAAXjKYo8eEgVBv85 +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGZIAyEpuVq0D9UpB4o0dY00eifTWefY3mmHrggp7bGLkFRUvNVINpyXDZbESEgd4Pe4G8AvpwDNkWSLrOyNGKY +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Y9+a9leOyTyP83EF6Ql2IDgu816zIZ8AFaS4gCHUHX0nvp4HPB59n314/EkGNhAM +dZw+CO0w3i/S02Fo8A/bXw== +Z9W5TG7rzK3ZhbnWjdfJ37W7cUH0ZUZ4etA9G1eKq/5LlOl/dP9tIF16dAZSZX24 +kjVV/zwZdFNcXb0/4WIo43yY3WhctfxqYlJeIJT/Zt4raVptLNgdCJ85x2QCj7en +v9zi26p7nzNT8ihOFEIGAPHAc1CmBd9eN42Km9GL48E= +dZw+CO0w3i/S02Fo8A/bXw== +OMrLNahybxYaZATa0EbsUraPl9ZQXb7EqnmkiDHNafM= +GMvSy/NpCRnN8GukyNJXGQ== +ZurH5ZOEBjKhuKKsOJuaZcaZjwgsQXIB71JtU6gEl5anuc8gN8IUF/VRflvF49gf +tb7N17ccXzCvhV9hZH479H/o3ElcTFJ2wxaYkBX0qHE= +ZurH5ZOEBjKhuKKsOJuaZT9OROQzGwZwPFsfOlMOqlW/n1s9EF2CXY7nIzE2BeWQUoQiVGUxtmYToskldhUSJg== +ZurH5ZOEBjKhuKKsOJuaZZOj4gtB0ZybAayCdpeA+rzvR2xX3QPBG1G+d2mABcg83+eEtNBqMdpVGWRkdFdjju+Fp/5HLdmKRV5bbO3vpgI= +xF/vKN+xQbwK2H7vBii2bVr0r4cs4Y9Wc0/WOSJt5a4= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmA0y/dvI19s9e1BRFCkk/QzH4UG1JBu620e7CQwjbQOOUvlgFJz4yLxls8aSEEo +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P+yZLrQY+X/LEpSBMw7xwfahIuTImHO5EcUQN9HQmgqX +LikBEoRjt8wwWzUZNw+jJqDVmHo0aqmO8fx4adbRXm90KhRgBH5ZfsJyfEAx+4JR +akHibliG6j9uvBEg96vRei563dWr9knc+JrDspjHc/A= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +BIa0ibHnqUboaK9kYHYO4qFxMPyUysXdjYA1FqqhK4UMv4PoX1vehXtIZKdqRHbG +b4OJVZe8QyIpjuTpKXDL9A== +PRG/YRzXVD8iVR3bzEcUnlkfSwESl/MBfBcCyRS23XAS+yqB8BD2Fws8ohDlx4dxPS4gXd1cpWdmGRHNT+D6KSwXWu5PYPEs39ffK4i+qWM= +usrsES3VVkgHW6tBbEkpHTeLztsByVaJibbFv8kSFIM= +5wsVwKLrIjIWqeTNpSVp9SZAE79lgehXxNsrtfaN8Wk= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== diff --git a/class/firewallModel/app/appBase.py b/class/firewallModel/app/appBase.py new file mode 100644 index 00000000..3a991af8 --- /dev/null +++ b/class/firewallModel/app/appBase.py @@ -0,0 +1,78 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - 底层基类 +# ------------------------------ + +import sys +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public + + +class Base(object): + + def __init__(self): + pass + + # 2024/3/22 下午 3:18 通用返回 + def _result(self, status: bool, msg: str) -> dict: + ''' + @name 通用返回 + @author wzz <2024/3/22 下午 3:19> + @param status: True/False + msg: 提示信息 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return {"status": status, "msg": msg} + + # 2024/3/22 下午 4:55 检查是否设置了net.ipv4.ip_forward = 1,没有则设置 + def check_ip_forward(self) -> dict: + ''' + @name 检查是否设置了net.ipv4.ip_forward = 1,没有则设置 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("sysctl net.ipv4.ip_forward") + if "net.ipv4.ip_forward = 1" not in stdout: + # 2024/3/22 下午 4:56 永久设置 + stdout, stderr = public.ExecShell("echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf") + if stderr: + return self._result(False, "设置net.ipv4.ip_forward失败, err: {}".format(stderr)) + + stdout, stderr = public.ExecShell("sysctl -p") + if stderr: + return self._result(False, "设置net.ipv4.ip_forward失败, err: {}".format(stderr)) + return self._result(True, "设置net.ipv4.ip_forward成功") + return self._result(True, "net.ipv4.ip_forward已经设置") + + # 2024/3/18 上午 11:35 处理192.168.1.100-192.168.1.200这种ip范围 + # 返回192.168.1.100,192.168.1.101,192.168.1...,192.168.1.200列表 + def handle_ip_range(self, ip): + ''' + @name 处理192.168.1.100-192.168.1.200这种ip范围的ip列表 + @author wzz <2024/3/19 下午 4:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + ip_range = ip.split("-") + ip_start = ip_range[0] + ip_end = ip_range[1] + ip_start = ip_start.split(".") + ip_end = ip_end.split(".") + ip_start = [int(i) for i in ip_start] + ip_end = [int(i) for i in ip_end] + ip_list = [] + for i in range(ip_start[0], ip_end[0] + 1): + for j in range(ip_start[1], ip_end[1] + 1): + for k in range(ip_start[2], ip_end[2] + 1): + for l in range(ip_start[3], ip_end[3] + 1): + ip_list.append("{}.{}.{}.{}".format(i, j, k, l)) + return ip_list diff --git a/class/firewallModel/app/firewalld.py b/class/firewallModel/app/firewalld.py new file mode 100644 index 00000000..7e796a81 --- /dev/null +++ b/class/firewallModel/app/firewalld.py @@ -0,0 +1,750 @@ +#!/www/server/panel/pyenv/bin/python3.7 +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - firewalld封装库 +# ------------------------------ + +import os +import sys + + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +from firewallModel.app.appBase import Base + + +class Firewalld(Base): + def __init__(self): + super().__init__() + self.cmd_str = self._set_cmd_str() + + def _set_cmd_str(self) -> str: + return "firewall-cmd" + + # 2024/3/20 下午 12:00 获取防火墙状态 + def status(self) -> bool: + ''' + @name 获取防火墙状态 + @author wzz <2024/3/20 下午 12:01> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + stdout, stderr = public.ExecShell("systemctl is-active firewalld") + if "not running" in stdout: + return False + return True + except Exception as e: + return False + + # 2024/3/20 下午 12:00 获取防火墙版本号 + def version(self) -> str: + ''' + @name 获取防火墙版本号 + @author wzz <2024/3/20 下午 12:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("firewall-cmd --version") + if "FirewallD is not running" in stdout: + return "Firewalld 没有启动,请先启动再试" + if stderr: + return "获取firewalld版本失败, err: {}".format(stderr) + return stdout.strip() + + # 2024/3/20 下午 12:08 启动防火墙 + def start(self) -> dict: + ''' + @name 启动防火墙 + @author wzz <2024/3/20 下午 12:08> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("systemctl start firewalld") + if stderr: + return self._result(False, "启动防火墙失败, err: {}".format(stderr)) + return self._result(True, "启动防火墙成功") + + # 2024/3/20 下午 12:10 停止防火墙 + def stop(self) -> dict: + ''' + @name 停止防火墙 + @author wzz <2024/3/20 下午 12:10> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("systemctl stop firewalld") + if stderr: + return self._result(False, "停止防火墙失败, err: {}".format(stderr)) + return self._result(True, "停止防火墙成功") + + # 2024/3/20 下午 12:11 重启防火墙 + def restart(self) -> dict: + ''' + @name 重启防火墙 + @author wzz <2024/3/20 下午 12:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("systemctl restart firewalld") + if stderr: + return self._result(False, "重启防火墙失败, err: {}".format(stderr)) + return self._result(True, "重启防火墙成功") + + # 2024/3/20 下午 12:11 重载防火墙 + def reload(self) -> dict: + ''' + @name 重载防火墙 + @author wzz <2024/3/20 下午 12:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("firewall-cmd --reload") + if stderr: + return self._result(False, "重载防火墙失败, err: {}".format(stderr)) + return self._result(True, "重载防火墙成功") + + # 2024/3/20 下午 12:12 获取所有防火墙端口列表 + def list_port(self) -> list: + ''' + @name 获取所有防火墙端口列表 + @author wzz <2024/3/20 下午 12:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["ports"] + self.list_output_port() + + # 2024/3/20 下午 12:12 获取防火墙端口INPUT列表 + def list_input_port(self) -> list: + ''' + @name 获取防火墙端口列表 + @author wzz <2024/3/20 下午 12:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["ports"] + + # 2024/3/22 上午 11:28 获取所有OUTPUT的direct 端口规则 + def list_output_port(self) -> list: + ''' + @name 获取所有OUTPUT的direct 端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + list_direct_rules = self.parse_direct_xml()["ports"] + datas = [] + for rule in list_direct_rules: + if rule.get("Chain") == "OUTPUT": + datas.append(rule) + return datas + + # 2024/3/20 下午 12:21 获取防火墙的rule的ip规则列表 + def list_address(self) -> list: + ''' + @name 获取防火墙的rule的ip规则列表 + @author wzz <2024/3/20 下午 2:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["rules"] + self.list_output_address() + + # 2024/3/20 下午 12:21 获取防火墙的rule input的ip规则列表 + def list_input_address(self) -> list: + ''' + @name 获取防火墙的rule input的ip规则列表 + @author wzz <2024/3/20 下午 2:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["rules"] + + # 2024/3/22 下午 4:07 获取所有OUTPUT的direct ip规则 + def list_output_address(self) -> list: + ''' + @name 获取所有OUTPUT的direct ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + list_direct_rules = self.parse_direct_xml()["rules"] + datas = [] + for rule in list_direct_rules: + if rule.get("Chain") == "OUTPUT": + datas.append(rule) + return datas + + # 2024/3/20 下午 5:34 添加或删除防火墙端口 + def input_port(self, info: dict, operation: str) -> dict: + ''' + @name 添加或删除防火墙端口 + @author wzz <2024/3/20 下午 5:34> + @param info:{"Port": args[2], "Protocol": args[3]} + operation: add/remove + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + # 2024/3/25 下午 6:00 处理tcp/udp双协议的端口 + if info['Protocol'].find("/") != -1: + stdout, stderr = public.ExecShell( + "{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent" + .format( + cmd_str=self.cmd_str, + operation=operation, + port=info['Port'], + prot="tcp" + ) + ) + if stderr: + return self._result(False, "设置端口失败, err: {}".format(stderr)) + stdout, stderr = public.ExecShell( + "{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent" + .format( + cmd_str=self.cmd_str, + operation=operation, + port=info['Port'], + prot="udp" + ) + ) + if stderr: + return self._result(False, "设置端口失败, err: {}".format(stderr)) + else: + # 2024/3/25 下午 6:00 处理单协议的端口 + stdout, stderr = public.ExecShell( + "{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent" + .format( + cmd_str=self.cmd_str, + operation=operation, + port=info['Port'], + prot=info['Protocol'] + ) + ) + if stderr: + return self._result(False, "设置端口失败, err: {}".format(stderr)) + return self._result(True, "设置入站端口成功") + + # 2024/3/20 下午 6:02 设置output的防火墙端口规则 + def output_port(self, info: dict, operation: str) -> dict: + ''' + @name 设置output的防火墙端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + if info['Strategy'] == "accept": + info['Strategy'] = "ACCEPT" + elif info['Strategy'] == "drop": + info['Strategy'] = "DROP" + elif info['Strategy'] == "reject": + info['Strategy'] = "REJECT" + else: + return self._result(False, "不支持的策略: {}".format(info['Strategy'])) + + info['Port'] = info['Port'].replace("-", ":") + + if "/" in info['Protocol']: + info['Protocol'] = info['Protocol'].split("/") + for pp in info['Protocol']: + if not pp in ["tcp", "udp"]: + return self._result(False, "设置出站端口失败, err: 协议不支持 {}".format(pp)) + + stdout, stderr = public.ExecShell( + "{cmd_str} --permanent --direct --{operation}-rule ipv4 filter OUTPUT {priority} -p {prot} --dport {port} -j {strategy}" + .format( + cmd_str=self.cmd_str, + operation=operation, + priority=info['Priority'], + prot=pp, + port=info['Port'], + strategy=info['Strategy'] + ) + ) + if stderr: + return self._result(False, "设置出站端口失败, err: {}".format(stderr)) + else: + stdout, stderr = public.ExecShell( + "{cmd_str} --permanent --direct --{operation}-rule ipv4 filter OUTPUT {priority} -p {prot} --dport {port} -j {strategy}" + .format( + cmd_str=self.cmd_str, + operation=operation, + priority=info['Priority'], + prot=info['Protocol'], + port=info['Port'], + strategy=info['Strategy'] + ) + ) + + if stderr: + return self._result(False, "设置出站端口失败, err: {}".format(stderr)) + + return self._result(True, "设置出站端口成功") + + def set_rich_rule(self, info: dict, operation: str) -> dict: + ''' + @name 添加或删除复杂规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + rule_str = "rule family={}".format(info['Family'].lower()) + if "Address" in info and info["Address"] != "all": + rule_str += " source address={}".format(info['Address']) + if info.get("Port"): + rule_str += " port port={}".format(info['Port']) + if info.get("Protocol"): + rule_str += " protocol={}".format(info['Protocol']) + rule_str += " {}".format(info['Strategy']) + + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-rich-rule='{}' --permanent" + .format(self.cmd_str, operation, rule_str)) + + if stderr: + return self._result(False, "设置规则:{} 失败, err: {}".format(operation, rule_str, stderr)) + return self._result(True, "设置规则成功".format(operation)) + + # 2024/3/22 上午 11:35 添加或删除复杂规则 + def rich_rules(self, info: dict, operation: str) -> dict: + ''' + @name 添加或删除复杂规则 + @author wzz <2024/3/22 上午 11:35> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的规则操作: {}".format(operation)) + + if "Protocol" in info and info["Protocol"] == "all": + info["Protocol"] = "tcp/udp" + + if "Protocol" in info and info['Protocol'].find("/") != -1: + result_list = [] + for protocol in info['Protocol'].split("/"): + info['Protocol'] = protocol + result_list.append(self.set_rich_rule(info, operation)) + + return {"status": True, "msg": result_list} + else: + return self.set_rich_rule(info, operation) + + # 2024/3/24 下午 10:43 设置output的防火墙ip规则 + def output_rich_rules(self, info: dict, operation: str) -> dict: + ''' + @name 设置output的防火墙ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + if info['Strategy'] == "accept": + info['Strategy'] = "ACCEPT" + elif info['Strategy'] == "drop": + info['Strategy'] = "DROP" + elif info['Strategy'] == "reject": + info['Strategy'] = "REJECT" + else: + return self._result(False, "不支持的策略: {}".format(info['Strategy'])) + + rich_rules = self.cmd_str + " --permanent --direct --{0}-rule ipv4 filter OUTPUT".format(operation) + if "Priority" in info: + rich_rules += " {}".format(info["Priority"]) + if "Address" in info: + rich_rules += " -d {}".format(info["Address"]) + if "Protocol" in info: + rich_rules += " -p {}".format(info["Protocol"]) + if "Port" in info: + info["Port"] = info["Port"].replace("-", ":") + rich_rules += " --dport {}".format(info["Port"]) + if "Strategy" in info: + rich_rules += " -j {}".format(info["Strategy"]) + + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + return self._result(False, "设置出站地址失败, err: {}".format(stderr)) + if "NOT_ENABLED" in stderr: + return self._result(False, "规则不存在") + return self._result(True, "设置出站地址成功") + + # 2024/3/22 下午 12:22 解析public区域的防火墙规则 + def parse_public_zone(self) -> dict: + ''' + @name 解析public区域的防火墙规则 + @author wzz <2024/3/22 下午 12:22> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"services": services, "ports": ports, "rules": rules} rules是ip规则 + ''' + try: + import xml.etree.ElementTree as ET + file_path = "/etc/firewalld/zones/public.xml" + if not os.path.exists(file_path): + return {"services": [], "ports": [], "rules": [], "forward_ports": []} + + services = [] + ports = [] + rules = [] + forward_ports = [] + + tree = ET.parse(file_path) + root = tree.getroot() + + for elem in root: + # 2024/3/22 下午 3:01 服务规则 + if elem.tag == "service": + services.append(elem.attrib['name']) + # 2024/3/22 下午 3:01 端口规则 + elif elem.tag == "port": + port = { + "Protocol": elem.attrib["protocol"], + "Port": elem.attrib["port"], + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "INPUT", + } + ports.append(port) + # 2024/3/22 下午 3:01 复杂的规则配置 + elif elem.tag == "rule": + rule = {"Family": elem.attrib["family"] if "family" in elem.attrib else "ipv4"} + for subelem in elem: + rule["Strategy"] = "accept" + if subelem.tag == "source": + if "address" in subelem.attrib: + rule["Address"] = subelem.attrib["address"] + else: + continue + rule["Address"] = "all" if rule["Address"] == "Anywhere" else rule["Address"] + elif subelem.tag == "port": + rule["port"] = {"protocol": subelem.attrib["protocol"], "port": subelem.attrib["port"]} + elif subelem.tag == "drop": + rule["Strategy"] = "drop" + elif subelem.tag == "accept": + rule["Strategy"] = "accept" + elif subelem.tag == "forward-port": + rule["forward-port"] = { + "protocol": subelem.attrib["protocol"], + "S_Port": subelem.attrib["port"], + "T_Address": subelem.attrib["to-addr"], + "T_Port": subelem.attrib["to-port"], + } + + # 2024/3/22 下午 3:02 如果端口在里面,就放到端口规则列表中,否则就是ip规则 + if "port" in rule: + ports.append({ + "Protocol": rule["port"]["protocol"] if "protocol" in rule else "tcp", + "Port": rule["port"]["port"], + "Strategy": rule["Strategy"] if "Strategy" in rule else "accept", + "Family": rule["Family"] if "Family" in rule else "ipv4", + "Address": rule["Address"] if "Address" in rule else "all", + "Chain": "INPUT", + }) + # 2024/3/25 下午 5:01 处理带源ip的端口转发规则 + elif "forward-port" in rule: + forward_ports.append({ + "type": "port_forward", + "number": len(forward_ports) + 1, + "Protocol": rule["forward-port"]["protocol"], + "S_Address": rule["Address"], + "S_Port": rule["forward-port"]["S_Port"], + "T_Address": rule["forward-port"]["T_Address"], + "T_Port": rule["forward-port"]["T_Port"], + }) + else: + if "Address" not in rule: + continue + + rule["Chain"] = "INPUT" + rules.append(rule) + # 2024/3/25 下午 2:57 端口转发规则 + elif elem.tag == "forward-port": + port = { + "type": "port_forward", + "number": len(forward_ports) + 1, + "Protocol": elem.attrib["protocol"] if "protocol" in elem.attrib else "tcp", + "S_Address": "", + "S_Port": elem.attrib["port"] if "port" in elem.attrib else "", + "T_Address": elem.attrib["to-addr"] if "to-addr" in elem.attrib else "", + "T_Port": elem.attrib["to-port"] if "to-port" in elem.attrib else "", + } + forward_ports.append(port) + + return {"services": services, "ports": ports, "rules": rules, "forward_ports": forward_ports} + except Exception as e: + return {"services": [], "ports": [], "rules": [], "forward_ports": []} + + # 2024/3/22 下午 2:32 解析direct.xml的防火墙规则 + def parse_direct_xml(self) -> dict: + ''' + @name 解析direct.xml的防火墙规则 + @author wzz <2024/3/22 下午 2:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + try: + import xml.etree.ElementTree as ET + file_path = "/etc/firewalld/direct.xml" + if not os.path.exists(file_path): + return {"ports": [], "rules": []} + + ports = [] + rules = [] + + tree = ET.parse(file_path) + root = tree.getroot() + + for elem in root: + if elem.tag == "rule": + protocol = "tcp" + port = "" + strategy = "" + address = "" + + elem_t = elem.text.split(" ") + # 2024/3/22 下午 4:14 解析 Options 得到端口,策略,地址,协议 + for i in elem_t: + if i == "-p": + protocol = elem_t[elem_t.index(i) + 1] # 如果找到匹配项,结果为索引+1的值,-p tcp,值为tcp + elif i == "--dport": + port = elem_t[elem_t.index(i) + 1] + elif i == "-j": + strategy = elem_t[elem_t.index(i) + 1] + elif i == "-d": + address = elem_t[elem_t.index(i) + 1] + + rule = { + "Family": elem.attrib["ipv"], + "Chain": elem.attrib["chain"], + "Strategy": strategy.lower(), + "Address": address if address != "" else "all", + # "Options": elem.text + } + + # 2024/3/22 下午 4:13 如果端口不为空,就是端口规则 + if port != "": + rule["Port"] = port + rule["Protocol"] = protocol + + ports.append(rule) + # 2024/3/22 下午 4:14 如果端口为空,就是ip规则 + else: + rules.append(rule) + + return {"ports": ports, "rules": rules} + except Exception as e: + return {"ports": [], "rules": []} + + # 2024/3/22 下午 4:54 检查是否开启了masquerade,没有则开启 + def check_masquerade(self) -> dict: + ''' + @name 检查是否开启了masquerade,没有则开启 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("firewall-cmd --query-masquerade") + if "no" in stdout: + stdout, stderr = public.ExecShell("firewall-cmd --add-masquerade") + if stderr: + return self._result(False, "开启masquerade失败, err: {}".format(stderr)) + return self._result(True, "开启masquerade成功") + return self._result(True, "masquerade已经开启") + + # 2024/3/22 下午 4:57 设置端口转发 + def port_forward(self, info: dict, operation: str) -> dict: + ''' + @name 设置端口转发 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + if operation == "add": + check_masquerade = self.check_masquerade() + if not check_masquerade["status"]: + return check_masquerade + + # 2024/3/25 下午 6:07 处理有源地址的情况 + if "S_Address" in info and info["S_Address"] != "": + # 2024/3/25 下午 6:05 处理tcp/udp双协议的情况 + if info['Protocol'].find("/") != -1: + rich_rules = self.cmd_str + " --zone=public" + rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"tcp\" to-port=\"{4}\" to-addr=\"{5}\"' --permanent".format( + operation, + info['Family'], + info['S_Address'], + info['S_Port'], + info['T_Port'], + info['T_Address'], + ) + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + rich_rules = self.cmd_str + " --zone=public" + rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"udp\" to-port=\"{4}\" to-addr=\"{5}\"' --permanent".format( + operation, + info['Family'], + info['S_Address'], + info['S_Port'], + info['T_Port'], + info['T_Address'], + ) + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + # 2024/3/25 下午 6:05 处理单协议的情况 + else: + rich_rules = self.cmd_str + " --zone=public" + rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"{4}\" to-port=\"{5}\" to-addr=\"{6}\"'".format( + operation, + info['Family'], + info['S_Address'], + info['S_Port'], + info['Protocol'], + info['T_Port'], + info['T_Address'], + ) + rich_rules += " --permanent" + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + # 2024/3/25 下午 6:08 处理没有源地址的情况 + else: + # 2024/3/25 下午 6:05 处理tcp/udp双协议的情况 + if info['Protocol'].find("/") != -1: + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent" + .format(self.cmd_str, operation, info['S_Port'], "udp", info['T_Port'], info['T_Address']) + ) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent" + .format(self.cmd_str, operation, info['S_Port'], "tcp", info['T_Port'], info['T_Address']) + ) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + # 2024/3/25 下午 6:09 处理单协议的情况 + else: + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent" + .format(self.cmd_str, operation, info['S_Port'], info['Protocol'], info['T_Port'], info['T_Address']) + ) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + return self._result(True, "设置端口转发成功") + + # 2024/3/25 下午 2:37 获取所有端口转发规则 + def list_port_forward(self) -> list: + ''' + @name 获取所有端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + return self.parse_public_zone()["forward_ports"] + + +if __name__ == '__main__': + args = sys.argv + firewall = Firewalld() + Firewalld_status = firewall.status() + if len(args) < 2: + print("Welcome to the Firewalld command-line interface!") + print("Firewall status is :", Firewalld_status) + print("Firewall version: ", firewall.version()) + if Firewalld_status == "not running": + print("Firewalld未启动,请启动Firewalld后再执行命令!") + print("启动命令: start") + print() + sys.exit(1) + print("Usage: ") + print("status: Get the status of the firewall.") + print("version: Get the version of the firewall.") + print("start: Start the firewall.") + print("stop: Stop the firewall.") + print("restart: Restart the firewall.") + print("reload: Reload the firewall.") + print("list_input_port: List all input ports.") + print("list_input_address: List all input address rules.") + print("input_port: Add or remove input port.") + print("output_port: Add or remove output port.") + print("list_output_port: List all output ports.") + print("list_output_address: List all output address rules.") + print("rich_rules: Add or remove rich rules.") + sys.exit(1) + + if args[1] == "status": + print("Firewall status is :", Firewalld_status) + elif args[1] == "version": + print("Firewall version: ", firewall.version()) + elif args[1] == "start": + print(firewall.start()) + elif args[1] == "stop": + print(firewall.stop()) + elif args[1] == "restart": + print(firewall.restart()) + elif args[1] == "reload": + print(firewall.reload()) + elif args[1] == "list_input_port": + print(firewall.list_input_port()) + elif args[1] == "list_input_address": + print(firewall.list_input_address()) + elif args[1] == "input_port": + if len(args) < 4: + print("Usage: input_port Port Protocol") + sys.exit(1) + print(firewall.input_port({"Port": args[2], "Protocol": args[3]}, args[4])) + elif args[1] == "output_port": + if len(args) < 6: + print("Usage: output_port Port Protocol Strategy Priority") + sys.exit(1) + print(firewall.output_port({"Port": args[2], "Protocol": args[3], "Strategy": args[4], "Priority": args[5]}, args[6])) + elif args[1] == "list_output_port": + print(firewall.list_output_port()) + elif args[1] == "list_output_address": + print(firewall.list_output_address()) + elif args[1] == "rich_rules": + if len(args) < 4: + print("Usage: rich_rules Family Address Port Protocol Strategy") + sys.exit(1) + print(firewall.rich_rules({"Family": args[2], "Address": args[3], "Port": args[4], "Protocol": args[5], "Strategy": args[6]}, args[7])) + elif args[1] == "output_rich_rules": + if len(args) < 6: + print("Usage: output_rich_rules Family Address Port Protocol Strategy Priority") + sys.exit(1) + print(firewall.output_rich_rules({"Family": args[2], "Address": args[3], "Port": args[4], "Protocol": args[5], "Strategy": args[6], "Priority": args[7]}, args[8])) + elif args[1] == "port_forward": + if len(args) < 7: + print("Usage: port_forward Port Protocol ToPort ToAddr") + sys.exit(1) + print(firewall.port_forward({"Port": args[2], "Protocol": args[3], "ToPort": args[4], "ToAddr": args[5]}, args[6])) + else: + print("Command not found!") + sys.exit(1) + diff --git a/class/firewallModel/app/iptables.py b/class/firewallModel/app/iptables.py new file mode 100644 index 00000000..3a49fd8d --- /dev/null +++ b/class/firewallModel/app/iptables.py @@ -0,0 +1,939 @@ +#!/www/server/panel/pyenv/bin/python3.7 +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - iptables封装库 +# ------------------------------ + +import re +import subprocess +import os +import sys + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +from firewallModel.app.appBase import Base + + +# import re + +class Iptables(Base): + def __init__(self): + self.cmd_str = self._set_cmd_str() + self.protocol = { + "6": "tcp", + "17": "udp", + "0": "all" + } + + def _set_cmd_str(self): + return "iptables" + + # 2024/3/19 下午 5:00 获取系统防火墙的运行状态 + def status(self): + ''' + @name 获取系统防火墙的运行状态 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return "running" + + # 2024/3/19 下午 5:00 获取系统防火墙的版本号 + def version(self): + ''' + @name 获取系统防火墙的版本号 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = public.ExecShell("iptables -v 2>&1|awk '{print $2}'|head -1")[0].replace("\n", "") + if result == "": + return "未知的iptables版本" + return result + except Exception as e: + return "未知版本" + + # 2024/3/19 下午 5:00 启动防火墙 + def start(self): + ''' + @name 启动防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持设置状态") + + # 2024/3/19 下午 5:00 停止防火墙 + def stop(self): + ''' + @name 停止防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持停止") + + # 2024/3/19 下午 4:59 重启防火墙 + def restart(self): + ''' + @name 重启防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持重启") + + # 2024/3/19 下午 4:59 重载防火墙 + def reload(self): + ''' + @name 重载防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持重载") + + # 2024/3/19 下午 3:36 检查表名是否合法 + def check_table_name(self, table_name): + ''' + @name 检查表名是否合法 + @param "table_name": "filter/nat/mangle/raw/security" + @return dict{"status":True/False,"msg":"提示信息"} + ''' + table_names = ['filter', 'nat', 'mangle', 'raw', 'security'] + if table_name not in table_names: + return False + return True + + # 2024/3/19 下午 3:55 解析规则列表输出,返回规则列表字典 + def parse_rules(self, stdout): + ''' + @name 解析规则列表输出,返回规则列表字典 + @author wzz <2024/3/19 下午 3:53> + 字段含义: + "number": 规则编号,对应规则在链中的顺序。 + "chain": 规则所属的链的名称。 + "pkts": 规则匹配的数据包数量。 + "bytes": 规则匹配的数据包字节数。 + "target": 规则的目标动作,表示数据包匹配到该规则后应该执行的操作。 + "prot": 规则适用的协议类型。 + "opt": 规则的选项,包括规则中使用的匹配条件或特定选项。 + "in": 规则匹配的数据包的输入接口。 + "out": 规则匹配的数据包的输出接口。 + "source": 规则匹配的数据包的源地址。 + "destination": 规则匹配的数据包的目标地址。 + "options": 规则的其他选项或说明,通常是规则中的注释或附加信息。 + + protocol(port协议头中数字对应的协议类型): + 0: 表示所有协议 + 1: ICMP(Internet 控制消息协议) + 6: TCP(传输控制协议) + 17: UDP(用户数据报协议) + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + lines = stdout.strip().split('\n') + rules = [] + current_chain = None + for line in lines: + if line.startswith("Chain"): + current_chain = line.split()[1] + elif (line.startswith("target") or line.strip() == "" or "source" in line or + "Warning: iptables-legacy tables present" in line): + # 过滤表头,空行,警告 + continue + else: + rule_info = line.split() + rule = { + "number": rule_info[0], + "chain": current_chain, + "pkts": rule_info[1], + "bytes": rule_info[2], + "target": rule_info[3], + "prot": rule_info[4], + "opt": rule_info[5], + "in": rule_info[6], + "out": rule_info[7], + "source": rule_info[8], + "destination": rule_info[9], + "options": " ".join(rule_info[10:]).strip() + } + rules.append(rule) + return rules + + # 2024/3/19 下午 3:02 列出指定表的指定链的规则 + def list_rules(self, parm): + ''' + @name 列出指定表的指定链的规则 + @author wzz <2024/3/19 下午 3:02> + @param + @return + ''' + try: + if not self.check_table_name(parm['table']): + return "错误: 不支持的表名." + stdout = subprocess.check_output( + [self.cmd_str, '-t', parm['table'], '-L', parm['chain_name'], '-nv', '--line-numbers'], + stderr=subprocess.STDOUT, universal_newlines=True + ) + return self.parse_rules(stdout) + except Exception as e: + return [] + + # 2024/4/29 下午12:16 列出iptables中所有INPUT和OUTPUT的端口规则 + def list_port(self): + ''' + @name 列出iptables中所有INPUT和OUTPUT的端口规则 + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "INPUT" + }] + ''' + try: + list_port = self.list_input_port() + self.list_output_port() + for i in list_port: + i["Strategy"] = i["Strategy"].lower() + return list_port + except Exception as e: + return [] + + # 2024/4/29 下午2:39 列出防火墙中所有的INPUT端口规则 + def list_input_port(self): + ''' + @name 列出防火墙中所有的INPUT端口规则 + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "INPUT" + }] + ''' + try: + list_port = self.get_chain_port("INPUT") + for i in list_port: + i["Strategy"] = i["Strategy"].lower() + return list_port + except Exception as e: + return [] + + # 2024/4/29 下午2:39 列出防火墙中所有的OUTPUT端口规则 + def list_output_port(self): + ''' + @name 列出防火墙中所有的OUTPUT端口规则 + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "OUTPUT" + }] + ''' + try: + list_port = self.get_chain_port("OUTPUT") + for i in list_port: + i["Strategy"] = i["Strategy"].lower() + return list_port + except Exception as e: + return [] + + # 2024/4/29 下午3:28 根据链来获取端口规则,暂时只支持INPUT/OUTPUT链 + def get_chain_port(self, chain): + ''' + @name 根据链来获取端口规则 + @author wzz <2024/4/29 下午3:29> + @param chain = INPUT/OUTPUT + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "OUTPUT" + }] + ''' + if chain not in ["INPUT", "OUTPUT"]: + return [] + + try: + stdout = self.get_chain_data(chain) + if stdout == "": + return [] + + lines = stdout.strip().split('\n') + rules = [] + for line in lines: + if line.startswith("Chain"): + continue + if not "dpt:" in line and not "multiport sports" in line: + continue + rule_info = line.split() + if rule_info[0] == "num": + continue + if not rule_info[3] in ["ACCEPT", "DROP", "REJECT"]: + continue + if not rule_info[4] in self.protocol: + continue + if not "dpt" in rule_info[-1] and not "-" in rule_info[-1] and not ":" in rule_info[-1]: + continue + + if ":" in rule_info[-1] and not "dpt" in rule_info[-1]: + Port = rule_info[-1] + elif "-" in rule_info[-1]: + Port = rule_info[-5].split(":")[1] + else: + Port = rule_info[-1].split(":")[1] + + if "source IP range" in line and "multiport sports" in line: + Address = rule_info[-4] + elif not "0.0.0.0/0" in rule_info[8]: + Address = rule_info[8] + elif "-" in rule_info[-1]: + Address = rule_info[-1] + else: + Address = "all" + + rule = { + "Protocol": self.protocol[rule_info[4]], + "Port": Port, + "Strategy": rule_info[3], + "Family": "ipv4", + "Address": Address, + "Chain": chain, + } + rules.append(rule) + + return rules + except Exception as e: + return [] + + # 2024/4/29 下午3:28 根据链来获取IP规则,暂时只支持INPUT/OUTPUT链 + def get_chain_ip(self, chain): + ''' + @name 根据链来获取端口规则 + @author wzz <2024/4/29 下午3:29> + @param chain = INPUT/OUTPUT + @return [ + { + "Family": "ipv4", + "Address": "192.168.1.190", + "Strategy": "accept", + "Chain": "INPUT" + } + ] + ''' + if chain not in ["INPUT", "OUTPUT"]: + return [] + + try: + stdout = self.get_chain_data(chain) + if stdout == "": + return [] + + lines = stdout.strip().split('\n') + rules = [] + for line in lines: + if line.startswith("Chain"): + continue + if "dpt:" in line or "multiport sports" in line: + continue + rule_info = line.split() + if rule_info[0] == "num": + continue + if not rule_info[3] in ["ACCEPT", "DROP", "REJECT"]: + continue + if not rule_info[4] in self.protocol: + continue + + Address = "" + if not "0.0.0.0/0" in rule_info[8]: + Address = rule_info[8] + elif "0.0.0.0/0" in rule_info[8] and "-" in rule_info[-1]: + Address = rule_info[-1] + + if Address == "": + continue + + rule = { + "Family": "ipv4", + "Address": Address, + "Strategy": rule_info[3], + "Chain": chain, + } + rules.append(rule) + + return rules + except Exception as e: + return [] + + # 2024/4/29 下午4:01 获取指定链的数据 + def get_chain_data(self, chain): + ''' + @name 获取指定链的数据 + @author wzz <2024/4/29 下午4:01> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + cmd = "{} -t filter -L {} -nv --line-numbers".format(self.cmd_str, chain) + stdout, stderr = public.ExecShell(cmd) + return stdout + except Exception as e: + return "" + + # 2024/4/29 下午2:46 列出防火墙中所有的INPUT和OUTPUT的ip规则 + def list_address(self): + ''' + @name 列出防火墙中所有的ip规则 + @author wzz <2024/4/29 下午2:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return + ''' + try: + list_address = self.get_chain_ip("INPUT") + self.get_chain_ip("OUTPUT") + for i in list_address: + i["Strategy"] = i["Strategy"].lower() + return list_address + except Exception as e: + return [] + + # 2024/4/29 下午2:48 列出防火墙中所有input的ip规则 + def list_input_address(self): + ''' + @name 列出防火墙中所有input的ip规则 + @author wzz <2024/4/29 下午2:48> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + list_address = self.get_chain_ip("INPUT") + for i in list_address: + i["Strategy"] = i["Strategy"].lower() + return list_address + except Exception as e: + return [] + + # 2024/4/29 下午2:49 列出防火墙中所有output的ip规则 + def list_output_address(self): + ''' + @name 列出防火墙中所有output的ip规则 + @author wzz <2024/4/29 下午2:49> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + list_address = self.get_chain_ip("OUTPUT") + for i in list_address: + i["Strategy"] = i["Strategy"].lower() + return list_address + except Exception as e: + return [] + + # 2024/4/29 下午2:49 添加INPUT端口规则 + def input_port(self, info, operation): + ''' + @name 添加INPUT端口规则 + @author wzz <2024/4/29 下午2:50> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return self.set_chain_port(info, operation, "INPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午2:50 设置output端口策略 + def output_port(self, info, operation): + ''' + @name 设置output端口策略 + @author wzz <2024/4/29 下午2:50> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return self.set_chain_port(info, operation, "OUTPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午4:49 添加/删除指定链的端口规则 + def set_chain_port(self, info, operation, chain): + ''' + @name 添加/删除指定链的端口规则 + @author wzz <2024/4/29 下午4:49> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not chain in ["INPUT", "OUTPUT"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的链类型")) + + if info['Protocol'] not in ["tcp", "udp"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的协议类型")) + if info["Strategy"] == "accept": + info["Strategy"] = "ACCEPT" + elif info["Strategy"] == "drop": + info["Strategy"] = "DROP" + elif info["Strategy"] == "reject": + info["Strategy"] = "REJECT" + else: + return self._result(False, "设置端口规则失败:{}".format("不支持的策略类型")) + + if operation == "add": + operation = "-I" + elif operation == "remove": + operation = "-D" + + rule = "{} -t filter {} {} -p {} --dport {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Strategy'] + ) + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "设置端口规则失败:{}".format(stderr)) + return self._result(True, "设置端口规则成功") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午5:01 添加/删除指定链的复杂端口规则 + def set_chain_rich_port(self, info, operation, chain): + ''' + @name 添加/删除指定链的复杂端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not chain in ["INPUT", "OUTPUT"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的链类型")) + + if "Address" in info and info["Address"] == "": + info["Address"] = "all" + if "Address" in info and public.is_ipv6(info['Address']): + return self._result(False, "设置端口规则失败:{}".format("不支持的IPV6地址")) + + if info['Protocol'] not in ["tcp", "udp"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的协议类型")) + if info["Strategy"] == "accept": + info["Strategy"] = "ACCEPT" + elif info["Strategy"] == "drop": + info["Strategy"] = "DROP" + elif info["Strategy"] == "reject": + info["Strategy"] = "REJECT" + else: + return self._result(False, "设置端口规则失败:{}".format("不支持的策略类型")) + + if operation == "add": + operation = "-I" + elif operation == "remove": + operation = "-D" + + info['Port'] = info['Port'].replace("-", ":") + info["Address"] = info["Address"].replace(":", "-") + if ":" in info['Port'] or "-" in info['Port']: + if ":" in info["Address"] or "-" in info["Address"]: + # iptables -t filter -I INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -p tcp -m multiport --sports 8000:9000 -j ACCEPT + rule = "{} -t filter {} {} -m iprange --src-range {} -p {} -m multiport --sports {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Address'], + info['Protocol'], + info['Port'], + info['Strategy'] + ) + else: + # iptables -t filter -I INPUT -p tcp -m multiport --sports 8000:9000 -s 192.168.1.100 -j ACCEPT + rule = "{} -t filter {} {} -p {} -m multiport --sports {} -s {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Address'], + info['Strategy'] + ) + else: + if ":" in info["Address"] or "-" in info["Address"]: + # iptables -t filter -I OUTPUT -p tcp --dport 22333 -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT + rule = "{} -t filter {} {} -p {} --dport {} -m iprange --src-range {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Address'], + info['Strategy'] + ) + else: + # iptables -t filter -I OUTPUT -p tcp --dport 22333 -s 192.168.1.0/24 -j ACCEPT + rule = "{} -t filter {} {} -p {} --dport {} -s {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Address'], + info['Strategy'] + ) + + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "设置端口规则失败:{}".format(stderr)) + return self._result(True, "设置端口规则成功") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午5:01 添加/删除指定链的复杂ip规则 + def set_chain_rich_ip(self, info, operation, chain): + ''' + @name 添加/删除指定链的复杂ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not chain in ["INPUT", "OUTPUT"]: + return self._result(False, "设置规则失败:{}".format("不支持的链类型")) + + if "Address" in info and info["Address"] == "": + info["Address"] = "all" + if "Address" in info and public.is_ipv6(info['Address']): + return self._result(False, "设置规则失败:{}".format("不支持的IPV6地址")) + + if info["Strategy"] == "accept": + info["Strategy"] = "ACCEPT" + elif info["Strategy"] == "drop": + info["Strategy"] = "DROP" + elif info["Strategy"] == "reject": + info["Strategy"] = "REJECT" + else: + return self._result(False, "设置规则失败:{}".format("不支持的策略类型")) + + if operation == "add": + operation = "-I" + elif operation == "remove": + operation = "-D" + + if ":" in info["Address"] or "-" in info["Address"]: + # iptables -t filter -I INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT + rule = "{} -t filter {} {} -m iprange --src-range {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Address'], + info['Strategy'] + ) + else: + # iptables -t filter -I INPUT -s 192.168.1.100 -j ACCEPT + rule = "{} -t filter {} {} -s {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Address'], + info['Strategy'] + ) + + + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "设置规则失败:{}".format(stderr)) + return self._result(True, "设置规则成功") + except Exception as e: + return self._result(False, "设置规则失败:{}".format(str(e))) + + # 2024/4/29 下午2:51 INPUT复杂一些的规则管理 + def rich_rules(self, info, operation): + ''' + @name + @author wzz <2024/4/29 下午2:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if "Priority" in info and not "Port" in info: + return self.set_chain_rich_ip(info, operation, "INPUT") + else: + return self.set_chain_rich_port(info, operation, "INPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午2:52 OUTPUT复杂一些的规则管理 + def output_rich_rules(self, info, operation): + ''' + @name OUTPUT复杂一些的规则管理 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if "Priority" in info and not "Port" in info: + return self.set_chain_rich_ip(info, operation, "OUTPUT") + else: + return self.set_chain_rich_port(info, operation, "OUTPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/3/19 下午 3:03 清空指定链中的所有规则 + def flush_chain(self, chain_name): + ''' + @name 清空指定链中的所有规则 + @author wzz <2024/3/19 下午 3:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + subprocess.check_output( + [self.cmd_str, '-F', chain_name], stderr=subprocess.STDOUT, universal_newlines=True + ) + return chain_name + " chain flushed successfully." + except Exception as e: + return "Failed to flush " + chain_name + " chain." + + # 2024/3/19 下午 3:03 获取当前系统中可用的链的名称列表 + def get_chain_names(self, parm): + ''' + @name 获取当前系统中可用的链的名称列表 + @author wzz <2024/3/19 下午 3:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not self.check_table_name(parm['table']): + return "错误: 不支持的表名." + stdout = subprocess.check_output( + [self.cmd_str, '-t', parm['table'], '-L'], stderr=subprocess.STDOUT, universal_newlines=True + ) + chain_names = re.findall(r"Chain\s([A-Z]+)", stdout) + return chain_names + except Exception as e: + return [] + + # 2024/3/19 下午 3:17 构造端口转发规则,然后调用insert_rule方法插入规则 + def port_forward(self, info, operation): + ''' + @name 构造端口转发规则,然后调用insert_rule方法插入规则 + @param "info": { + "Protocol": "tcp/udp", + "S_Port": "80", + "T_Address": "0.0.0.0/0", + "T_Port": "8080" + } + @param "operation": "add" or "remove" + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + rule = " -p {}".format(info['Protocol']) + + if "S_Address" in info and info['S_Address'] != "": + rule += " -s {}".format(info['S_Address']) + + rule += " --dport {0} -j DNAT --to-destination {1}:{2}".format( + info['S_Port'], + info['T_Address'], + info['T_Port'], + ) + + parm = { + "table": "nat", + "chain_name": "PREROUTING", + "rule": rule + } + if operation not in ["add", "remove"]: + return "请输入正确的操作类型. (add/remove)" + + if operation == "add": + parm['type'] = "-I" + elif operation == "remove": + parm['type'] = "-D" + return self.rule_manage(parm) + except Exception as e: + return self._result(False, "设置端口转发规则失败:{}".format(str(e))) + + # 2024/3/19 下午 3:03 在指定链中管理规则 + def rule_manage(self, parm): + ''' + @name 在指定链中管理规则 + @author wzz <2024/3/19 下午 3:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not self.check_table_name(parm['table']): + return self._result(False, "不支持的表名{}".format(parm['table'])) + + rule = "{} -t {} {} {} {}".format( + self.cmd_str, parm['table'], parm['type'], parm['chain_name'], parm['rule'] + ) + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "规则设置失败:{}".format(stderr)) + + return self._result(True, "规则设置成功") + except Exception as e: + return self._result(False, "规则设置失败: {}".format(str(e))) + + # 2024/4/29 下午5:55 获取所有端口转发列表 + def list_port_forward(self): + ''' + @name 获取所有端口转发列表 + @author wzz <2024/4/29 下午5:55> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.get_nat_prerouting_rules() + + # 2024/3/19 下午 4:00 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回 + def get_nat_prerouting_rules(self): + ''' + @name 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + port_forward_rules = self.list_rules({"table": "nat", "chain_name": "PREROUTING"}) + rules = [] + for rule in port_forward_rules: + if rule["target"] != "DNAT": continue + options = rule["options"].split(' ') + + protocol = "TCP" + if rule["prot"] == "6" or rule["prot"] == "tcp": + protocol = "TCP" + elif rule["prot"] == "17" or rule["prot"] == "udp": + protocol = "UDP" + elif rule["prot"] == "0" or rule["prot"] == "all": + protocol = "TCP/UDP" + rules.append({ + "type": "port_forward", + "number": rule["number"], + "S_Address": rule["source"], + "S_Port": options[1].split("dpt:")[1], + "T_Address": options[2].split("to:")[1].split(":")[0], + "T_Port": options[2].split("to:")[1].split(":")[1], + "Protocol": protocol.lower() + }) + return rules + except Exception as e: + return [] + + # 2024/4/29 下午2:43 格式化输出json + def format_json(self, data): + ''' + @name 格式化输出json + @param "data": json数据 + @return json字符串 + ''' + import json + from pygments import highlight, lexers, formatters + + formatted_json = json.dumps(data, indent=3) + colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), + formatters.TerminalFormatter()) + return colorful_json + + +if __name__ == '__main__': + args = sys.argv + firewall = Iptables() + if len(args) < 2: + print("Welcome to the iptables command-line interface!") + print() + print("Available options:") + print("list_rules: list_rules ") + print("flush_chain: flush_chain
                                    ") + print("get_chain_names: get_chain_names
                                    ") + print("port_forward: port_forward ") + print("get_nat_prerouting_rules: get_nat_prerouting_rules") + print() + sys.exit(1) + if args[1] == "list_rules": + # firewall.list_rules({"table": args[2], "chain_name": args[3]}) + print(firewall.list_rules({"table": args[2], "chain_name": args[3]})) + elif args[1] == "flush_chain": + print(firewall.flush_chain(args[2])) + elif args[1] == "get_chain_names": + table = args[2] if len(args) > 2 else "filter" + print(firewall.get_chain_names({"table": table})) + elif args[1] == "port_forward": + if len(args) < 8: + print("传参使用方法: port_forward ") + sys.exit(1) + + info = { + "S_Address": args[2], + "S_Port": args[3], + "T_Address": args[4], + "T_Port": args[5], + "Protocol": args[6] + } + print(firewall.port_forward(info, args[7])) + elif args[1] == "get_nat_prerouting_rules": + import json + from pygments import highlight, lexers, formatters + + formatted_json = json.dumps(firewall.get_nat_prerouting_rules(), indent=3) + colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), + formatters.TerminalFormatter()) + print(colorful_json) + # print(firewall.get_nat_prerouting_rules()) + elif args[1] == "list_port": + print(firewall.format_json(firewall.list_port())) + elif args[1] == "list_input_port": + print(firewall.format_json(firewall.list_input_port())) + elif args[1] == "list_output_port": + print(firewall.format_json(firewall.list_output_port())) + elif args[1] == "list_address": + print(firewall.format_json(firewall.list_address())) + elif args[1] == "list_input_address": + print(firewall.format_json(firewall.list_input_address())) + elif args[1] == "list_output_address": + print(firewall.format_json(firewall.list_output_address())) + elif args[1] == "input_port": + info = { + "Protocol": args[2], + "Port": args[3], + "Strategy": args[4] + } + print(firewall.input_port(info, args[5])) + elif args[1] == "output_port": + info = { + "Protocol": args[2], + "Port": args[3], + "Strategy": args[4] + } + print(firewall.output_port(info, args[5])) + elif args[1] == "rich_rules": + info = { + "Protocol": args[2], + "Port": args[3], + "Address": args[4], + "Strategy": args[5] + } + print(firewall.rich_rules(info, args[6])) + elif args[1] == "output_rich_rules": + info = { + "Protocol": args[2], + "Port": args[3], + "Address": args[4], + "Strategy": args[5] + } + print(firewall.output_rich_rules(info, args[6])) + else: + print("不支持的传参: " + args[1]) + sys.exit(1) diff --git a/class/firewallModel/app/ufw.py b/class/firewallModel/app/ufw.py new file mode 100644 index 00000000..dfc8717c --- /dev/null +++ b/class/firewallModel/app/ufw.py @@ -0,0 +1,729 @@ +#!/www/server/panel/pyenv/bin/python3.7 +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - ufw封装库 +# ------------------------------ + +import subprocess +import os +import sys +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +# import re +from firewallModel.app.appBase import Base + +class Ufw(Base): + def __init__(self): + self.cmd_str = self._set_cmd_str() + + def _set_cmd_str(self): + return "ufw" + + # 2024/3/19 下午 5:00 获取系统防火墙的运行状态 + def status(self): + ''' + @name 获取系统防火墙的运行状态 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run([self.cmd_str, "status"], capture_output=True, text=True, check=True) + if "Status: active" in result.stdout: + return "running" + elif "状态: 激活" in result.stdout: + return "running" + else: + return "not running" + except subprocess.CalledProcessError: + return "not running" + except Exception as e: + return "not running" + + # 2024/3/19 下午 5:00 获取系统防火墙的版本号 + def version(self): + ''' + @name 获取系统防火墙的版本号 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run([self.cmd_str, "version"], capture_output=True, text=True, check=True) + info = result.stdout.replace("\n", "") + return info.replace("ufw ", "") + except Exception as e: + return "未知版本" + + # 2024/3/19 下午 5:00 启动防火墙 + def start(self): + ''' + @name 启动防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + stdout, stderr = public.ExecShell("echo y | {} enable".format(self.cmd_str)) + if stderr: + return self._result(False, "启动防火墙失败:{}".format(stderr)) + return self._result(True, "启动防火墙成功") + except Exception as e: + return self._result(False, "启动防火墙失败:{}".format(str(e))) + + # 2024/3/19 下午 5:00 停止防火墙 + def stop(self): + ''' + @name 停止防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + stdout, stderr = public.ExecShell("{} disable".format(self.cmd_str)) + if stderr: + return self._result(False, "停止防火墙失败:{}".format(stderr)) + return self._result(True, "停止防火墙成功") + except Exception as e: + return self._result(False, "停止防火墙失败:{}".format(str(e))) + + # 2024/3/19 下午 4:59 重启防火墙 + def restart(self): + ''' + @name 重启防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + self.stop() + self.start() + except Exception as e: + return self._result(False, "重启防火墙失败:{}".format(str(e))) + + # 2024/3/19 下午 4:59 重载防火墙 + def reload(self): + ''' + @name 重载防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + subprocess.run([self.cmd_str, "reload"], check=True, stdout=subprocess.PIPE) + except Exception as e: + return self._result(False, "重载防火墙失败:{}".format(str(e))) + + # 2024/3/19 上午 10:39 列出防火墙中所有端口规则 + def list_port(self): + ''' + @name 列出防火墙中所有端口规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "port") + if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]: + item_fire["Port"] = item_fire["Port"].replace(":", "-") + item_fire["Address"] = "all" if item_fire["Address"] == "Anywhere" else item_fire["Address"] + + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有input端口规则 + def list_input_port(self): + ''' + @name 列出防火墙中所有input端口规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "port") + if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]: + item_fire["Port"] = item_fire["Port"].replace(":", "-") + + if item_fire["Chain"] == "INPUT": + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有output端口规则 + def list_output_port(self): + ''' + @name 列出防火墙中所有output端口规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "port") + if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]: + item_fire["Port"] = item_fire["Port"].replace(":", "-") + + if item_fire["Chain"] == "OUTPUT": + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有的ip规则 + def list_address(self): + ''' + @name 列出防火墙中所有的ip规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "address") + if "Port" in item_fire: continue + if item_fire.get("Address"): + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有input的ip规则 + def list_input_address(self): + ''' + @name 列出防火墙中所有input的ip规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + if " IN" not in line: + continue + item_fire = self._load_info(line, "address") + if "Port" in item_fire: continue + if item_fire.get("Address"): + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有output的ip规则 + def list_output_address(self): + ''' + @name 列出防火墙中所有output的ip规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + if " OUT" not in line: + continue + item_fire = self._load_info(line, "address") + if "Port" in item_fire: continue + if item_fire.get("Address"): + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 下午 4:59 添加端口规则 + def input_port(self, info, operation): + ''' + @name 添加端口规则 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + + if info["Port"].find('-') != -1: + info["Port"] = info["Port"].replace('-', ':') + + if operation == "add": + if info['Protocol'].find("/") != -1: + rich_rule = self.cmd_str + " insert 1 {} {}".format(info['Strategy'], info['Port']) + stdout, stderr = public.ExecShell(rich_rule) + elif info['Protocol'] == "tcp/udp": + stdout, stderr = public.ExecShell(self.cmd_str + " allow " + info['Port']) + else: + stdout, stderr = public.ExecShell(self.cmd_str + " allow " + info['Port'] + "/" + info['Protocol']) + else: + if info['Protocol'].find("/") != -1: + rich_rule = "{} delete {} {}".format(self.cmd_str, info['Strategy'], info['Port']) + stdout, stderr = public.ExecShell(rich_rule) + elif info['Protocol'] == "tcp/udp": + stdout, stderr = public.ExecShell(self.cmd_str + " delete allow " + info['Port']) + else: + stdout, stderr = public.ExecShell(self.cmd_str + " delete allow " + info['Port'] + "/" + info['Protocol']) + + if stderr: + if "setlocale" in stderr: + return self._result(True, "设置端口规则成功") + return self._result(False, "设置端口规则失败:{}".format(stderr)) + + return self._result(True, "设置端口规则成功") + + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置端口规则成功") + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/3/24 下午 11:28 设置output端口策略 + def output_port(self, info, operation): + ''' + @name 设置output端口策略 + @param info: 端口号 + @param operation: 操作 + @return None + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + + if operation == "add": + if info['Protocol'].find('/') != -1: + cmd = "{} {} out {}".format(self.cmd_str, info['Strategy'], info['Port']) + else: + cmd = "{} {} out {}/{}".format(self.cmd_str, info['Strategy'], info['Port'], info['Protocol']) + else: + if info['Protocol'].find('/') != -1: + cmd = "{} delete {} out {}".format(self.cmd_str, info['Strategy'], info['Port']) + else: + cmd = "{} delete {} out {}/{}".format(self.cmd_str, info['Strategy'], info['Port'], info['Protocol']) + stdout, stderr = public.ExecShell(cmd) + if stderr: + if "setlocale" in stderr: + return self._result(True, "设置端口规则成功") + return self._result(False, "设置output端口规则失败:{}".format(stderr)) + return self._result(True, "设置output端口规则成功") + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置output端口规则成功") + return self._result(False, "设置output端口规则失败:{}".format(str(e))) + + # 2024/3/19 下午 4:58 复杂一些的规则管理 + def rich_rules(self, info, operation): + ''' + @name 复杂一些的规则管理 + @author wzz <2024/3/19 下午 4:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + else: + return self._result(False, "未知的策略参数:{}".format(info["Strategy"])) + + rule_str = "{} insert 1 {} ".format(self.cmd_str, info["Strategy"]) + if "Address" in info and public.is_ipv6(info['Address']): + rule_str = "{} {} ".format(self.cmd_str, info["Strategy"]) + if operation == "remove": + rule_str = "{} delete {} ".format(self.cmd_str, info["Strategy"]) + + if "Address" in info and info['Address'] != "all": + rule_str += "from {} ".format(info['Address']) + if len(info.get("Protocol", "")) != 0 and "/" not in info['Protocol']: + rule_str += "proto {} ".format(info['Protocol']) + if len(info.get("Port", "")) != 0: + rule_str += "to any port {} ".format(info['Port']) + stdout, stderr = public.ExecShell(rule_str) + if stderr: + if "Rule added" in stdout or "Rule deleted" in stdout or "Rule updated" in stdout or "Rule inserted" in stdout or "Skipping adding existing rule" in stdout: + return self._result(True, "设置规则成功") + if "setlocale" in stderr: + return self._result(True, "设置规则成功") + return self._result(False, "规则设置失败:{}".format(stderr)) + return self._result(True, "设置规则成功") + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置规则成功") + return self._result(False, "规则设置失败:{}".format(e)) + + # 2024/3/24 下午 11:29 设置output rich_rules + def output_rich_rules(self, info, operation): + ''' + @name 设置output rich_rules + @param info: 规则 + @param operation: 操作 + @return None + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + else: + return self._result(False, "未知的策略: {}".format(info["Strategy"])) + + rule_str = "{} insert 1 {} ".format(self.cmd_str, info["Strategy"]) + if "Address" in info and public.is_ipv6(info['Address']): + rule_str = "{} {} ".format(self.cmd_str, info["Strategy"]) + if operation == "remove": + rule_str = "{} delete {} ".format(self.cmd_str, info["Strategy"]) + + if len(info.get("Address", "")) != 0: + rule_str += "out from {} ".format(info['Address']) + if len(info.get("Protocol", "")) != 0: + rule_str += "proto {} ".format(info['Protocol']) + if len(info.get("Port", "")) != 0: + rule_str += "to any port {} ".format(info['Port']) + stdout, stderr = public.ExecShell(rule_str) + if stderr: + if "Rule added" in stdout or "Rule deleted" in stdout or "Rule updated" in stdout or "Rule inserted" in stdout or "Skipping adding existing rule" in stdout: + return self._result(True, "设置output规则成功") + if "setlocale" in stderr: + return self._result(True, "设置规则成功") + return self._result(False, "outpu规则设置失败:{}".format(stderr)) + return self._result(True, "设置output规则成功") + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置output规则成功") + return self._result(False, "outpu规则设置失败:{}".format(e)) + + # 2024/3/19 下午 5:01 解析防火墙规则信息,返回字典格式数据,用于添加或删除防火墙规则 + def _load_info(self, line, fire_type): + ''' + @name 解析防火墙规则信息,返回字典格式数据,用于添加或删除防火墙规则 + @author wzz <2024/3/19 上午 10:38> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + fields = line.split() + item_info = {} + if "LIMIT" in line or "ALLOW FWD" in line: + return item_info + if len(fields) < 4: + return item_info + if fields[0] == "Anywhere" and fire_type != "port": + item_info["Strategy"] = "drop" + + if fields[1] != "(v6)": + if fields[1] == "ALLOW": + item_info["Strategy"] = "accept" + if fields[2] == "IN": + item_info["Chain"] = "INPUT" + elif fields[2] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[3] + item_info["Family"] = "ipv4" + else: + if fields[2] == "ALLOW": + item_info["Strategy"] = "accept" + if fields[3] == "IN": + item_info["Chain"] = "INPUT" + elif fields[3] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[4] + item_info["Family"] = "ipv6" + + return item_info + + if "/" in fields[0]: + item_info["Port"] = fields[0].split("/")[0] + item_info["Protocol"] = fields[0].split("/")[1] + else: + item_info["Port"] = fields[0] + item_info["Protocol"] = "tcp/udp" + + if "v6" in fields[1]: + item_info["Family"] = "ipv6" + if fields[2] == "ALLOW": + item_info["Strategy"] = "accept" + else: + item_info["Strategy"] = "drop" + + if fields[3] == "IN": + item_info["Chain"] = "INPUT" + elif fields[3] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[4] + + else: + item_info["Family"] = "ipv4" if ":" not in fields[3] else "ipv6" + + if fields[1] == "ALLOW": + item_info["Strategy"] = "accept" + else: + item_info["Strategy"] = "drop" + + if fields[2] == "IN": + item_info["Chain"] = "INPUT" + elif fields[2] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[3] + + return item_info + + # 2024/3/25 下午 2:29 设置端口转发 + def port_forward(self, info, operation): + ''' + @name 设置端口转发 + @param port: 端口号 + @param ip: ip地址 + @param operation: 操作 + @return None + ''' + from firewallModel.app.iptables import Iptables + self.firewall = Iptables() + return self.firewall.port_forward(info, operation) + + # 2024/3/25 下午 2:34 获取所有端口转发列表 + def list_port_forward(self): + ''' + @name 获取所有端口转发列表 + @return None + ''' + from firewallModel.app.iptables import Iptables + self.firewall = Iptables() + return self.firewall.get_nat_prerouting_rules() + + +if __name__ == '__main__': + args = sys.argv + firewall = Ufw() + ufw_status = firewall.status() + if len(args) < 2: + print("Welcome to the UFW (Uncomplicated Firewall) command-line interface!") + print("Firewall status is :", ufw_status) + print("Firewall version: ", firewall.version()) + if ufw_status == "not running": + print("ufw未启动,请启动ufw后再执行命令!") + print("启动命令: start") + print() + sys.exit(1) + print() + print("Available options:") + print("1. Check Firewall Status: status") + print("2. Check Firewall Version: version") + print("3. Start Firewall: start") + print("4. Stop Firewall: stop") + print("5. Restart Firewall: restart") + print("6. Reload Firewall: reload") + print("7. List All Ports: list_port") + print("8. List All IP Addresses: list_address") + print("9. Add Port: add_port ") + print("10. Remove Port: remove_port ") + print("11. Add Port Rule: add_port_rule
                                    ") + print("12. Remove Port Rule: remove_port_rule
                                    ") + print("13. Add IP Rule: add_ip_rule
                                    ") + print("14. Remove IP Rule: remove_ip_rule
                                    ") + print() + sys.exit(1) + if args[1] == "status": + print(firewall.status()) + elif args[1] == "version": + print(firewall.version()) + elif args[1] == "start": + error = firewall.start() + if error: + print(f"Error: {error}") + else: + print("Firewall started successfully.") + elif args[1] == "stop": + error = firewall.stop() + if error: + print(f"Error: {error}") + else: + print("Firewall stopped successfully.") + elif args[1] == "restart": + error = firewall.restart() + if error: + print(f"Error: {error}") + else: + print("Firewall restarted successfully.") + elif args[1] == "reload": + error = firewall.reload() + if error: + print(f"Error: {error}") + else: + print("Firewall reloaded successfully.") + elif args[1] == "list_input_port": + ports = firewall.list_input_port() + for p in ports: + print(p) + elif args[1] == "list_output_port": + ports = firewall.list_output_port() + for p in ports: + print(p) + elif args[1] == "list_input_address": + addresses = firewall.list_input_address() + for a in addresses: + print(a) + elif args[1] == "list_output_address": + addresses = firewall.list_output_address() + for a in addresses: + print(a) + elif args[1] == "add_port": + port = args[2] + protocol = args[3] + error = firewall.input_port(f"{port}/{protocol}", "allow") + if error: + print(f"Error: {error}") + else: + print(f"Port {port}/{protocol} added successfully.") + elif args[1] == "remove_port": + port = args[2] + protocol = args[3] + error = firewall.input_port(f"{port}/{protocol}", "remove") + if error: + print(f"Error: {error}") + else: + print(f"Port {port}/{protocol} removed successfully.") + elif args[1] == "add_port_rule": + address = args[2] + port = args[3] + protocol = args[4] + strategy = args[5] + operation = args[6] + error = firewall.rich_rules( + {"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule added successfully.") + elif args[1] == "remove_port_rule": + address = args[2] + port = args[3] + protocol = args[4] + strategy = args[5] + operation = args[6] + error = firewall.rich_rules( + {"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule removed successfully.") + elif args[1] == "add_ip_rule": + address = args[2] + strategy = args[3] + operation = args[4] + error = firewall.rich_rules( + {"Address": address, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule added successfully.") + elif args[1] == "remove_ip_rule": + address = args[2] + strategy = args[3] + operation = args[4] + error = firewall.rich_rules( + {"Address": address, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule removed successfully.") + elif args[1] == "output_port": + port = args[2] + operation = args[3] + error = firewall.output_port(port, operation) + if error: + print(f"Error: {error}") + else: + print(f"Output port {port} {operation} successfully.") + elif args[1] == "output_rich_rules": + address = args[2] + port = args[3] + protocol = args[4] + strategy = args[5] + operation = args[6] + error = firewall.output_rich_rules( + {"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Output rich rule added successfully.") + else: + print("Invalid args") + sys.exit(1) + diff --git a/class/firewallModel/comModel.py b/class/firewallModel/comModel.py new file mode 100644 index 00000000..89154fa2 --- /dev/null +++ b/class/firewallModel/comModel.py @@ -0,0 +1,1718 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import json +import re +import time +import os + +# ------------------------------ +# 系统防火墙模型 - 业务接口类 +# ------------------------------ +import public +from firewallModel.firewallBase import Base + + +class main(Base): + + def __init__(self): + super().__init__() + + # 2024/3/14 下午 12:01 获取防火墙状态信息 + def get_firewall_info(self, get): + """ + @name 获取防火墙统计 + """ + data = {} + data['port'] = len(self.firewall.list_port()) + data['ip'] = len(self.firewall.list_address()) + data['trans'] = len(self.firewall.list_port_forward()) + data['country'] = public.M('firewall_country').count() + + isPing = True + try: + file = '/etc/sysctl.conf' + conf = public.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)\n" + tmp = re.search(rep, conf).groups(0)[0] + if tmp == '1': isPing = False + except: + isPing = True + + data['ping'] = isPing + return data + + # 2024/3/26 下午 3:40 获取防火墙状态 + def get_status(self, get): + ''' + @name 获取防火墙状态 + @author wzz <2024/3/26 下午 3:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.get_firewall_status() + + # 2024/3/26 下午 3:42 设置防火墙状态 + def set_status(self, get): + ''' + @name 设置防火墙状态 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.status = get.get('status/s', '1') + if get.status not in ['0', '1']: + return public.returnMsg(False, '参数错误') + + if get.status == '1': + return self.firewall.start() + else: + return self.firewall.stop() + + # 2024/5/13 下午3:50 检查指定端口是否已经存在,如果存在则返回False,否则返回True + def check_port_exist(self, get): + ''' + @name 检查指定端口是否已经存在,如果存在则返回False,否则返回True + @author wzz <2024/5/13 下午3:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + port_rules_list = self.port_rules_list(get) + for item in port_rules_list: + if item["Port"] == get.port and item["Address"] == get.address and item["Protocol"] == get.protocol and \ + item["Strategy"] == get.strategy and item["Chain"] == get.chain: + return False + + return True + + # 2024/5/13 下午4:13 检查指定ip规则是否已经存在,如果存在则返回False,否则返回True + def check_ip_exist(self, get): + ''' + @name 检查指定ip规则是否已经存在,如果存在则返回False,否则返回True + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + ip_rules_list = self.ip_rules_list(get) + for item in ip_rules_list: + if item["Address"] == get.address and item["Strategy"] == get.strategy and item["Chain"] == get.chain: + return False + + return True + + # 2024/5/13 下午4:17 检查指定端口转发规则是否已经存在,如果存在则返回False,否则返回True + def check_forward_exist(self, get): + ''' + @name 检查指定端口转发规则是否已经存在,如果存在则返回False,否则返回True + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + forward_rules_list = self.port_forward_list(get) + for item in forward_rules_list: + if item["S_Address"] == get.S_Address and item["S_Port"] == get.S_Port and item["T_Address"] == get.T_Address and \ + item["T_Port"] == get.T_Port: + return False + + return True + + # 2024/3/26 下午 6:09 从数据库中获取端口规则列表 + def get_port_db(self, get): + ''' + @name 从数据库中获取端口规则列表 + @author wzz <2024/3/26 下午 6:13> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + where = '1=1' + sql = public.M('firewall_new') + data = sql.where(where, ()).select() + + domain_sql = public.M('firewall_domain') + domain_data = domain_sql.where(where, ()).select() + for i in range(len(data)): + if not "ports" in data[i]: + data[i]['status'] = -1 + continue + + if "brief" in data[i]: + data[i]['brief'] = public.xssdecode(data[i]['brief']) + + if not "chain" in data[i]: + data[i]['chain'] = "INPUT" + if "chain" in data[i] and data[i]['chain'] == "": + data[i]['chain'] = "INPUT" + + for j in range(len(domain_data)): + if "domain" in domain_data[j] and data[i]['address'] in domain_data[j]['domain']: + data[i]['domain'] = domain_data[j]['domain'] + break + + return data + except Exception as e: + return [] + + # 2024/3/26 下午 11:53 从数据库中获取ip规则列表 + def get_ip_db(self, get): + ''' + @name 从数据库中获取ip规则列表 + @author wzz <2024/3/26 下午 11:53> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + where = '1=1' + sql = public.M('firewall_ip') + + ip_data = sql.where(where, ()).select() + + for i_data in ip_data: + if "brief" in i_data: + i_data['brief'] = public.xssdecode(i_data['brief']) + if not "chain" in i_data: + i_data['chain'] = "INPUT" + if "chain" in i_data and i_data['chain'] == "": + i_data['chain'] = "INPUT" + + return ip_data + except Exception as e: + return [] + + # 2024/3/26 下午 11:58 从数据库中获取端口转发规则列表 + def get_forward_db(self, get): + ''' + @name 从数据库中获取端口转发规则列表 + @author wzz <2024/3/26 下午 11:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + where = '1=1' + sql = public.M('firewall_forward') + + if hasattr(get, 'query'): + where = " S_Address like '%{search}%' or S_Port like '%{search}%' or T_Address like '%{search}%' or T_Port like '%{search}%'".format( + search=get.query + ) + res = sql.where(where, ()).select() + if type(res) != list: + return [] + return res + except Exception as e: + return [] + + # 2024/3/26 下午 10:46 构造端口规则返回数据 + def structure_port_return_data(self, list_port, rule_db, query): + ''' + @name 构造返回数据 + @author wzz <2024/3/26 下午 10:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + new_list = [] + for j in range(len(list_port)): + list_port[j]['id'] = 0 + list_port[j]['sid'] = 0 + list_port[j]['brief'] = "" + list_port[j]['domain'] = "" + if (list_port[j]['Port'].find(":") != -1 or list_port[j]['Port'].find("-") != -1 or + list_port[j]['Port'].find("/") != -1 or list_port[j]['Port'].find(".") != -1): + list_port[j]['status'] = 1 + else: + try: + if not ":" in list_port[j]['Port'] or not "-" in list_port[j]['Port']: + list_port[j]['status'] = self.CheckPort(int(list_port[j]['Port']), list_port[j]['Protocol']) + else: + list_port[j]['status'] = -1 + except: + list_port[j]['status'] = -1 + + if "Chain" in list_port[j] and list_port[j]['Chain'] == "OUTPUT": + list_port[j]['status'] = -1 + + list_port[j]['addtime'] = "0000-00-00 00:00:00" + + list_port[j]['Port'] = list_port[j]['Port'].replace(":", "-") + for i in range(len(rule_db)): + if (rule_db[i]['ports'] == list_port[j]['Port'] and + rule_db[i]['protocol'] == list_port[j]['Protocol'] and + rule_db[i]['address'].lower() == list_port[j]['Address'].lower() and + rule_db[i]['types'] == list_port[j]['Strategy'] and + rule_db[i]['chain'] == list_port[j]['Chain']): + list_port[j]['id'] = rule_db[i]['id'] + list_port[j]['sid'] = rule_db[i]['sid'] + list_port[j]['brief'] = rule_db[i]['brief'] + list_port[j]['addtime'] = rule_db[i]['addtime'] + + if "domain" in rule_db[i]: + list_port[j]['domain'] = rule_db[i]['domain'] + + break + + if query != "": + if query in list_port[j]['Port'] or query in list_port[j]['brief'] or query in list_port[j]['Address']: + new_list.append(list_port[j]) + + if len(new_list) > 0 or query != "": + return sorted(new_list, key=lambda x: x['addtime'], reverse=True) + + return sorted(list_port, key=lambda x: x['addtime'], reverse=True) + + # 2024/3/27 上午 12:01 构造ip规则返回数据 + def structure_ip_return_data(self, list_ip, rule_db, query): + ''' + @name 构造ip规则返回数据 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + new_list = [] + for j in range(len(list_ip)): + list_ip[j]['id'] = 0 + list_ip[j]['sid'] = 0 + list_ip[j]['brief'] = "" + list_ip[j]['domain'] = "" + list_ip[j]['addtime'] = "0000-00-00 00:00:00" + + for i in range(len(rule_db)): + if (rule_db[i]['address'] == list_ip[j]['Address'] and + rule_db[i]['types'] == list_ip[j]['Strategy'] and + rule_db[i]['chain'] == list_ip[j]['Chain']): + list_ip[j]['id'] = rule_db[i]['id'] + list_ip[j]['sid'] = rule_db[i]['sid'] + list_ip[j]['brief'] = rule_db[i]['brief'] + list_ip[j]['addtime'] = rule_db[i]['addtime'] + + if "domain" in rule_db[i]: + list_ip[j]['domain'] = rule_db[i]['domain'] + + break + if query != "": + if query in list_ip[j]['brief'] or query in list_ip[j]['Address']: + new_list.append(list_ip[j]) + + if len(new_list) > 0 or query != "": + return public.return_area(sorted(new_list, key=lambda x: x['addtime'], reverse=True), "Address") + + return public.return_area(sorted(list_ip, key=lambda x: x['addtime'], reverse=True), "Address") + + # 2024/3/27 上午 12:11 构造端口转发规则返回数据 + def structure_forward_return_data(self, list_forward, rule_db, query): + ''' + @name 构造端口转发规则返回数据 + @author wzz <2024/3/27 上午 12:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + new_list = [] + for j in range(len(list_forward)): + list_forward[j]['id'] = 0 + list_forward[j]['brief'] = "" + list_forward[j]['addtime'] = "0000-00-00 00:00:00" + + for i in range(len(rule_db)): + if (rule_db[i]['T_Address'] == list_forward[j]['T_Address'] and + rule_db[i]['S_Port'] == list_forward[j]['S_Port'] and + rule_db[i]['T_Port'] == list_forward[j]['T_Port']): + list_forward[j]['id'] = rule_db[i]['id'] + list_forward[j]['brief'] = rule_db[i]['brief'] + list_forward[j]['addtime'] = rule_db[i]['addtime'] + break + + if query != "": + if (query in list_forward[j]['brief'] or query in list_forward[j]['S_Address'] or query in + list_forward[j]['S_Port'] or + query in list_forward[j]['T_Address'] or query in list_forward[j]['T_Port']): + new_list.append(list_forward[j]) + + if len(new_list) > 0 or query != "": + return sorted(new_list, key=lambda x: x['addtime'], reverse=True) + + return sorted(list_forward, key=lambda x: x['addtime'], reverse=True) + + # 2024/3/25 上午 11:05 获取所有端口规则列表 + def port_rules_list(self, get): + ''' + @name 获取所有端口规则列表 + @author wzz <2024/3/25 上午 11:06> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + get.chain = get.get('chain/s', 'ALL') + get.query = get.get('query/s', '') + + rule_db = self.get_port_db(get) + + if get.chain == "INPUT": + list_port = self.firewall.list_input_port() + elif get.chain == "OUTPUT": + list_port = self.firewall.list_output_port() + else: + list_port = self.firewall.list_port() + + return self.structure_port_return_data(list_port, rule_db, query=get.query) + + # 2024/3/26 下午 3:17 导出规则 + def export_rules(self, get): + ''' + @name 导出规则 + @author wzz <2024/3/26 下午 3:17> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.rule = get.get('rule/s', 'port') + if get.rule == "port": + return self.export_port_rules(get) + elif get.rule == "ip": + return self.export_ip_rules(get) + else: + return self.export_port_forward(get) + + # 2024/3/26 下午 3:18 导入规则 + def import_rules(self, get): + ''' + @name 导入规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置导入规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再导入规则!') + + get.rule = get.get('rule/s', 'port') + if get.rule == "port": + return self.import_port_rules(get) + elif get.rule == "ip": + return self.import_ip_rules(get) + else: + return self.import_port_forward(get) + + # 2024/3/26 下午 2:38 导出所有端口规则 + def export_port_rules(self, get): + ''' + @name 导出所有端口规则 + @author wzz <2024/3/26 下午 2:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.chain = get.get('chain/s', 'all') + + if get.chain == "INPUT": + file_name = "input_port_rules_{}".format(int(time.time())) + elif get.chain == "OUTPUT": + file_name = "output_port_rules_{}".format(int(time.time())) + else: + file_name = "port_rules_{}".format(int(time.time())) + + data = self.port_rules_list(get) + if not data: + return public.returnMsg(False, '没有规则无法导出') + if not os.path.exists(self.config_path): + os.makedirs(self.config_path, exist_ok=True) + file_path = "{}/{}.json".format(self.config_path, file_name) + + public.writeFile(file_path, public.GetJson(data)) + public.WriteLog("系统防火墙", "导出端口规则") + return public.returnMsg(True, file_path) + + # 2024/3/26 下午 2:41 导入端口规则 + def import_port_rules(self, get): + ''' + @name 导入端口规则 + @author wzz <2024/3/26 下午 2:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.file = get.get('file/s', '') + + if not get.file: + return public.returnMsg(False, '文件不能为空') + + if not os.path.exists(get.file): + return public.returnMsg(False, '文件不存在') + + try: + data = public.readFile(get.file) + if "|" in data and not "{" in data: + get.rule_name = "port_rule" + return self.import_rules_old(get) + + data = json.loads(data) + # 2024/4/10 下午2:51 反转数据 + data.reverse() + except: + return public.returnMsg(False, '文件内容异常或格式错误') + + args = public.dict_obj() + for item in data: + args.operation = 'add' + args.protocol = item['Protocol'] + args.port = item['Port'] + args.strategy = item['Strategy'] + args.chain = item['Chain'] + args.address = item.get('Address', 'all') + args.brief = item.get('brief', '') + args.reload = "0" + + self.set_port_rule(args) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '导入成功') + + # 2024/5/14 上午10:27 调用旧的导入规则方法 + def import_rules_old(self, get): + ''' + @name 调用旧的导入规则方法 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + from safeModel.firewallModel import main as firewall + firewall_obj = firewall() + get.file_name = get.file.split("/")[-1] + firewall_obj.import_rules(get) + + return public.returnMsg(True, '导入成功') + except Exception as e: + return public.returnMsg(False, str(e)) + + # 2024/3/26 上午 9:30 处理多个ip以换行的方式添加/删除 + def set_nline_port_ip(self, get): + ''' + @name 处理多个ip以换行的方式添加/删除 + @author wzz <2024/3/26 上午 9:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split("\n") + failed_list = [] + for addr in address: + if not public.checkIp(addr): + return public.returnMsg(False, '目标地址格式错误') + + get.address = addr + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + # if self._isFirewalld: + # self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:30 处理多个ip以逗号的方式添加/删除 + def set_tline_port_ip(self, get): + ''' + @name 处理多个ip以逗号的方式添加 + @author wzz <2024/3/26 上午 9:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split(",") + failed_list = [] + for addr in address: + if not public.checkIp(addr): + return public.returnMsg(False, '目标地址格式错误') + + get.address = addr + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + # if self._isFirewalld: + # self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:34 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + def set_range_port_ip(self, get): + ''' + @name 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + @author wzz <2024/3/26 上午 9:35> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = self.firewall.handle_ip_range(get.address) + failed_list = [] + for addr in address: + get.address = addr + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + # if self._isFirewalld: + # self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/25 下午 6:27 设置端口规则 + def set_port_rule(self, get): + ''' + @name 设置端口规则 + @author wzz <2024/3/25 下午 6:28> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置端口规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.address = get.get('address/s', 'all') + get.port = get.get('port/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + get.reload = get.get('reload/s', "1") + get.brief = get.get('brief/s', '') + + if get.address == "Anywhere" or get.address == "": + get.address = "all" + + if get.protocol == "all": + get.protocol = "tcp/udp" + + if get.port == "": + return public.returnMsg(False, '目标端口不能为空') + + if get.address != "all" and "," in get.address: + import copy + args = copy.deepcopy(get) + address_list = get.address.split(",") + for address in address_list: + args.address = address + result = self.more_prot_rule(args) + if not result['status']: + return result + if get.address != "all" and "\n" in get.address: + import copy + args = copy.deepcopy(get) + address_list = get.address.split("\n") + for address in address_list: + args.address = address + result = self.more_prot_rule(args) + if not result['status']: + return result + else: + result = self.more_prot_rule(get) + if not result['status']: + return result + + if self._isFirewalld and get.reload == "1": + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/29 下午 4:04 处理多个ip的端口规则情况 + def more_prot_rule(self, get): + ''' + @name 处理多个ip的端口规则情况 + @author wzz <2024/3/29 下午 4:02> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.port.find(",") != -1: + import copy + args = copy.deepcopy(get) + port_list = get.port.split(",") + for port in port_list: + args.port = port + result = self.exec_port_rule(args) + if not result['status']: + return result + return public.returnMsg(True, '设置成功') + else: + return self.exec_port_rule(get) + + # 2024/3/28 下午 6:29 执行端口设置 + def exec_port_rule(self, get): + ''' + @name 执行端口设置 + @author wzz <2024/3/28 下午 6:29> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add" and not self.check_port_exist(get): + return public.returnMsg(False, '端口{}已存在,请勿重复添加'.format(get.port)) + + self.set_port_db(get) + + # 2024/3/25 下午 8:23 处理多个ip的情况,例如出现每行一个ip + if get.address != "all" and "\n" in get.address: + return self.set_nline_port_ip(get) + elif get.address != "all" and "-" in get.address: + return self.set_range_port_ip(get) + elif get.address != "all" and "," in get.address: + return self.set_tline_port_ip(get) + elif get.address != "all" and "/" in get.address: + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + elif get.address != "all" and not public.checkIp(get.address) and not public.is_ipv6(get.address): + return public.returnMsg(False, '指定IP地址格式错误') + else: + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + return result + + # 2024/5/14 上午10:40 前置检测ip是否合法 + def check_ips(self, get): + ''' + @name 前置检测ip是否合法 + @author wzz <2024/5/14 上午10:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.address != "all" and "\n" in get.address: + address = get.address.split("\n") + for addr in address: + if addr != "all" and not public.checkIp(addr) and not public.is_ipv6(addr): + return public.returnMsg(False, '指定IP地址格式错误') + elif get.address != "all" and "," in get.address: + address = get.address.split(",") + for addr in address: + if addr != "all" and not public.checkIp(addr) and not public.is_ipv6(addr): + return public.returnMsg(False, '指定IP地址格式错误') + else: + if get.address != "all" and not public.checkIp(get.address) and not public.is_ipv6(get.address): + return public.returnMsg(False, '指定IP地址格式错误') + + return public.returnMsg(True, 'ok') + + # 2024/3/27 上午 9:37 修改端口规则 + def modify_port_rule(self, get): + ''' + @name 修改端口规则 + @author wzz <2024/3/27 上午 9:38> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + if "address" in get.new_data: + get.address = get.new_data['address'] + if not self.check_ips(get)["status"]: + return public.returnMsg(False, '修改后的指定IP地址格式错误') + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.port = get.old_data['Port'] + args1.protocol = get.old_data['Protocol'] + args1.address = get.old_data['Address'] + args1.strategy = get.old_data['Strategy'] + args1.chain = get.old_data['Chain'] + args1.id = get.old_data['id'] + args1.sid = get.old_data['sid'] + args1.reload = "0" + self.set_port_rule(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + args2.port = get.new_data['port'] + args2.protocol = get.new_data['protocol'] + args2.address = get.new_data['address'] if "address" in get.new_data else "all" + args2.strategy = get.new_data['strategy'] + args2.chain = get.new_data['chain'] + args2.brief = get.new_data['brief'] if 'brief' in get.new_data else "" + args2.reload = "1" + self.set_port_rule(args2) + + return public.returnMsg(True, '修改成功') + + # 2024/3/27 下午 4:03 修改域名端口规则 + def modify_domain_port_rule(self, get): + ''' + @name 修改域名端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + address = self.get_a_ip(get.new_data['domain']) + + if address == "": + return public.returnMsg(False, '域名: 【{}】解析失败'.format(get.domain)) + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.port = get.old_data['Port'] + args1.protocol = get.old_data['Protocol'] + args1.address = get.old_data['Address'] + args1.strategy = get.old_data['Strategy'] + args1.chain = get.old_data['Chain'] + args1.id = get.old_data['id'] + args1.sid = get.old_data['sid'] + self.set_port_rule(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + args2.port = get.new_data['port'] + args2.protocol = get.new_data['protocol'] + args2.address = address + args2.strategy = get.new_data['strategy'] + args2.chain = get.new_data['chain'] + args2.brief = get.new_data['brief'] if "brief" in get.new_data else "" + args2.domain = get.new_data['domain'] + self.set_port_rule(args2) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '修改成功') + + # 2024/3/26 下午 5:10 设置域名端口规则 + def set_domain_port_rule(self, get): + ''' + @name 设置域名端口规则 + @author wzz <2024/3/26 下午 5:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.domain = get.get('domain/s', '') + get.port = get.get('port/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + + if get.domain == "": + return public.returnMsg(False, '目标域名不能为空') + + if get.port == "": + return public.returnMsg(False, '目标端口不能为空') + + if not public.is_domain(get.domain): + return public.returnMsg(False, '目标域名格式错误') + + if "|" in get.domain: + get.domain = get.domain.split("|")[0] + + address = self.get_a_ip(get.domain) + + if address == "": + return public.returnMsg(False, '域名: 【{}】解析失败'.format(get.domain)) + + get.address = address + self.set_port_db(get) + + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if result['status'] and self._isFirewalld: + self.firewall.reload() + + return result + + # 2024/5/13 下午4:46 添加端口规则到指定数据库 + def add_port_db(self, get, protocol, addtime, domain): + ''' + @name 添加端口规则到指定数据库 + @author wzz <2024/5/13 下午4:46> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + add_sid = public.M('firewall_new').add( + 'ports,brief,protocol,address,types,addtime,domain,sid,chain', + ( + get.port, public.xsssec(get.brief), protocol, get.address, get.strategy, addtime, domain, 0, + get.chain) + ) + + if get.domain != "": + domain_sid = public.M('firewall_domain').add( + 'types,domain,port,address,brief,addtime,sid,protocol,domain_total', + (get.strategy, domain, get.port, get.address, public.xsssec(get.brief), + addtime, add_sid, get.protocol, get.domain) + ) + public.M('firewall_new').where("id=?", (add_sid,)).save('sid', domain_sid) + self.check_resolve_crontab() + + # 2024/5/13 下午4:49 从指定数据库删除端口规则 + def remove_port_db(self, get, protocol, addtime, domain): + ''' + @name 从指定数据库删除端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + public.M('firewall_new').where("ports=? and protocol=? and address=? and types=? and chain=?", ( + get.port, protocol, get.address, get.strategy, get.chain + )).delete() + get.domain = get.get('domain/s', '') + if get.domain != "": + public.M('firewall_domain').where("domain=?", (get.domain)).delete() + + self.remove_resolve_crontab() + + # 2024/3/26 下午 5:52 添加/删除数据库的端口规则 + def set_port_db(self, get): + ''' + @name 添加/删除数据库的端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add": + # 检测端口是否已经添加过 + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=? and chain=?', + (get.port, get.address, get.protocol, get.strategy, get.chain) + ).find() + + if not query_result: + get.domain = get.get('domain/s', '') + domain = "{}|{}".format(get.domain, get.address) if get.domain != "" else "" + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + + if get.protocol == "tcp/udp" and self._isFirewalld: + self.add_port_db(get, "tcp", addtime, domain) + self.add_port_db(get, "udp", addtime, domain) + else: + self.add_port_db(get, get.protocol, addtime, domain) + else: + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=? and chain=?', + (get.port, get.address, get.protocol, get.strategy, get.chain) + ).find() + if query_result: + if get.protocol == "tcp/udp" and self._isFirewalld: + self.remove_port_db(get, "tcp", query_result['addtime'], query_result['domain']) + self.remove_port_db(get, "udp", query_result['addtime'], query_result['domain']) + else: + self.remove_port_db(get, get.protocol, query_result['addtime'], query_result['domain']) + + # 2024/3/26 下午 11:23 添加/删除数据库的ip规则 + def set_ip_db(self, get): + ''' + @name 添加/删除数据库的ip规则 + @author wzz <2024/3/26 下午 11:24> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add": + get.domain = get.get('domain/s', '') + domain = "{}|{}".format(get.domain, get.address) if get.domain != "" else "" + query_result = public.M('firewall_ip').where("address=? and types=? and domain=? and chain=?", + (get.address, get.strategy, domain, get.chain)).find() + + if not query_result: + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + self._add_sid = public.M('firewall_ip').add( + 'address,types,brief,addtime,domain,sid,chain', + (get.address, get.strategy, public.xsssec(get.brief), addtime, domain, 0, get.chain) + ) + + if get.domain != "": + domain_sid = public.M('firewall_domain').add( + 'types,domain,port,address,brief,addtime,sid,protocol,domain_total', + (get.strategy, domain, '', get.address, public.xsssec(get.brief), addtime, self._add_sid, '', + get.domain) + ) + public.M('firewall_ip').where("id=?", (self._add_sid,)).save('sid', domain_sid) + self.check_resolve_crontab() + else: + get.address = get.get("address/s", '') + get.strategy = get.get("strategy/s", '') + if get.address == "": + return public.returnMsg(False, '请传入id') + public.M('firewall_ip').where("address=? and types=? and chain=?", (get.address, get.strategy, get.chain)).delete() + get.domain = get.get('domain/s', '') + if get.domain != "": + public.M('firewall_domain').where("domain=?", (get.domain)).delete() + + self.remove_resolve_crontab() + + # 2024/3/26 下午 11:40 添加/删除数据库的端口转发规则 + def set_forward_db(self, get): + ''' + @name 添加/删除数据库的端口转发规则 + @author wzz <2024/3/26 下午 11:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add": + query_result = public.M('firewall_forward').where("S_Port=? and T_Address=? and T_Port=? and Protocol=?", ( + get.S_Port, get.T_Address, get.T_Port, get.protocol + )).find() + + if not query_result: + get.brief = get.get('brief/s', '') + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + self._add_sid = public.M('firewall_forward').add( + 'S_Port,T_Address,T_Port,Protocol,Family,addtime,brief', + (get.S_Port, get.T_Address, get.T_Port, get.protocol, "ipv4", addtime, get.brief) + ) + else: + public.M('firewall_forward').where( + "S_Port=? and T_Address=? and T_Port=? and Protocol=?", + (get.S_Port, get.T_Address, get.T_Port, get.protocol) + ).delete() + + # 2024/3/25 下午 6:55 入站端口规则 + def input_port(self, get): + ''' + @name 入站端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 dabao + @return list[dict{}...] + ''' + info = { + "Port": get.port, + "Protocol": get.protocol.lower(), + "Strategy": get.strategy.lower(), + "Family": "ipv4", + } + + if ":" in get.address: + info["Family"] = "ipv6" + + if get.address != "all": + info["Address"] = get.address + result = self.firewall.rich_rules(info=info, operation=get.operation) + elif get.address == "all" and get.strategy == "drop": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.input_port(info=info, operation=get.operation) + + return result + + # 2024/3/25 下午 6:56 出站端口规则 + def output_port(self, get): + ''' + @name 出站端口规则 + @author wzz <2024/3/25 下午 6:56> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + info = { + "Port": get.port, + "Protocol": get.protocol.lower(), + "Strategy": get.strategy.lower(), + "Priority": "0", + "Family": "ipv4", + } + + if ":" in get.address: + info["Family"] = "ipv6" + + if get.address != "all": + info["Address"] = get.address + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_port(info=info, operation=get.operation) + + return result + + # 2024/3/26 上午 9:40 处理多个ip的情况,例如出现每行一个ip + def set_nline_ip_rule(self, get): + ''' + @name 处理多个ip的情况,例如出现每行一个ip + @author wzz <2024/3/26 上午 9:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split("\n") + failed_list = [] + import copy + for addr in address: + if not public.is_ipv4(addr) and not public.is_ipv6(addr): + continue + + args = copy.deepcopy(get) + args.address = addr + args.family = "ipv4" if ":" not in addr else "ipv6" + + if self.check_is_user_ip(args): + continue + + if get.operation == "add" and not self.check_ip_exist(args): + continue + + self.set_ip_db(args) + + info = { + "Address": args.address, + "Family": args.family, + "Strategy": args.strategy, + "Priority": args.priority, + } + + if args.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=args.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=args.operation) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:40 处理多个ip的情况,例如出现逗号隔开ip + def set_tline_ip_rule(self, get): + ''' + @name 处理多个ip的情况,例如出现逗号隔开ip + @author wzz <2024/3/26 上午 9:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split(",") + failed_list = [] + import copy + for addr in address: + if not public.checkIp(addr): + return public.returnMsg(False, '目标地址格式错误') + + args = copy.deepcopy(get) + args.address = addr + + if self.check_is_user_ip(args): + continue + + if get.operation == "add" and not self.check_ip_exist(args): + continue + + self.set_ip_db(args) + + info = { + "Address": args.address, + "Family": args.family, + "Strategy": args.strategy, + "Priority": args.priority, + } + + if args.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=args.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=args.operation) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:43 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + def set_range_ip_rule(self, get): + ''' + @name 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + @author wzz <2024/3/26 上午 9:43> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = self.firewall.handle_ip_range(get.address) + failed_list = [] + import copy + for addr in address: + args = copy.deepcopy(get) + args.address = addr + + if self.check_is_user_ip(args): + continue + + if get.operation == "add" and not self.check_ip_exist(args): + continue + + self.set_ip_db(args) + + info = { + "Address": args.address, + "Family": args.family, + "Strategy": args.strategy, + "Priority": args.priority, + } + + if args.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=args.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=args.operation) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:46 设置带掩码的ip段 + def set_mask_ip_rule(self, get): + ''' + @name 设置带掩码的ip段 + @author wzz <2024/3/26 上午 9:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add" and not self.check_ip_exist(get): + return public.returnMsg(False, '目标地址{}已存在,请勿重复添加'.format(get.address)) + + self.set_ip_db(get) + info = { + "Address": get.address, + "Family": get.family, + "Strategy": get.strategy, + "Priority": get.priority, + } + + if get.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld: + self.firewall.reload() + + return result + + # 2024/4/9 下午11:31 检查是否会自己的ip,如果是则返回 + def check_is_user_ip(self, get): + ''' + @name + @author wzz <2024/4/9 下午11:31> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/3/26 下午 11:27 处理用户当前的远程ip,如果添加的ip与此ip一直则返回 + try: + from flask import request + user_ip = request.remote_addr + if user_ip in get.address: + return True + return False + except: + return False + + # 2024/3/25 下午 7:16 设置ip规则 + def set_ip_rule(self, get): + ''' + @name 设置ip规则 + @author wzz <2024/3/25 下午 7:16> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.address = get.get('address/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + get.family = get.get('family/s', 'ipv4') + get.priority = get.get('priority/s', '0') + get.reload = get.get('reload/s', "1") + + if get.address == "": + return public.returnMsg(False, '目标ip不能为空') + + # 2024/3/25 下午 8:23 处理多个ip的情况,例如出现每行一个ip + if get.address != "all" and "\n" in get.address: + return self.set_nline_ip_rule(get) + elif get.address != "all" and "-" in get.address: + return self.set_range_ip_rule(get) + elif get.address != "all" and "/" in get.address: + return self.set_mask_ip_rule(get) + elif get.address != "all" and "," in get.address: + return self.set_tline_ip_rule(get) + elif get.address != "all" and not public.checkIp(get.address) and not public.is_ipv6(get.address): + return public.returnMsg(False, '目标地址格式错误') + else: + if get.operation == "add" and get.strategy == "drop": + if self.check_is_user_ip(get): + return public.returnMsg(False, '不能添加自己的ip') + + if get.operation == "add" and not self.check_ip_exist(get): + return public.returnMsg(False, '目标地址{}已存在,请勿重复添加'.format(get.address)) + + self.set_ip_db(get) + + info = { + "Address": get.address, + "Family": get.family, + "Strategy": get.strategy.lower(), + "Priority": get.priority.lower(), + } + + if get.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld and get.reload == "1": + self.firewall.reload() + + return result + + # 2024/3/27 上午 9:44 修改ip规则 + def modify_ip_rule(self, get): + ''' + @name 修改ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.address = get.old_data['Address'] + args1.strategy = get.old_data['Strategy'] + args1.family = get.old_data['Family'] + args1.chain = get.old_data['Chain'] + args1.id = get.old_data['id'] + args1.sid = get.old_data['sid'] + args1.reload = "0" + self.set_ip_rule(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + args2.address = get.new_data['address'] + args2.strategy = get.new_data['strategy'] + args2.family = get.new_data['family'] + args2.chain = get.new_data['chain'] + args2.brief = get.new_data['brief'] + args2.reload = "0" + self.set_ip_rule(args2) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '修改成功') + + # 2024/3/26 下午 5:18 设置域名ip规则 + def set_domain_ip_rule(self, get): + ''' + @name 设置域名ip规则 + @author wzz <2024/3/26 下午 5:19> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.domain = get.get('domain/s', '') + get.port = get.get('port/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + + if get.domain == "": + return public.returnMsg(False, '目标域名不能为空') + + if get.port == "": + return public.returnMsg(False, '目标端口不能为空') + + if not public.is_domain(get.domain): + return public.returnMsg(False, '目标域名格式错误') + + address = self.get_a_ip(get.domain) + + if address == "": + return public.returnMsg(False, '域名: 【{}】解析失败'.format(get.domain)) + + if not public.checkIp(get.address): + return public.returnMsg(False, '目标地址格式错误') + + info = { + "Address": address, + "Family": get.family, + "Strategy": get.strategy.lower(), + "Priority": get.priority.lower(), + } + if get.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld: + self.firewall.reload() + + return result + + # 2024/3/25 上午 11:18 获取所有ip规则列表 + def ip_rules_list(self, get): + ''' + @name 获取所有ip规则列表 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + get.chain = get.get('chain/s', 'all') + get.query = get.get('query/s', '') + ip_db = self.get_ip_db(get) + + if get.chain == "INPUT": + list_address = self.firewall.list_input_address() + elif get.chain == "OUTPUT": + list_address = self.firewall.list_output_address() + else: + list_address = self.firewall.list_address() + + return self.structure_ip_return_data(list_address, ip_db, query=get.query) + + # 2024/3/26 下午 3:03 导出所有ip规则 + def export_ip_rules(self, get): + ''' + @name 导出所有ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.chain = get.get('chain/s', 'all') + + if get.chain == "INPUT": + file_name = "input_ip_rules_{}".format(int(time.time())) + elif get.chain == "OUTPUT": + file_name = "output_ip_rules_{}".format(int(time.time())) + else: + file_name = "ip_rules_{}".format(int(time.time())) + + data = self.ip_rules_list(get) + if not data: + return public.returnMsg(False, '没有规则无法导出') + + file_path = "{}/{}.json".format(self.config_path, file_name) + + public.writeFile(file_path, public.GetJson(data)) + public.WriteLog("系统防火墙", "导出ip规则") + return public.returnMsg(True, file_path) + + # 2024/3/26 下午 3:05 导入ip规则 + def import_ip_rules(self, get): + ''' + @name 导入ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.file = get.get('file/s', '') + + if not get.file: + return public.returnMsg(False, '文件不能为空') + + if not os.path.exists(get.file): + return public.returnMsg(False, '文件不存在') + + try: + data = public.readFile(get.file) + if "|" in data and not "{" in data: + get.rule_name = "ip_rule" + return self.import_rules_old(get) + + data = json.loads(data) + # 2024/4/10 下午2:51 反转数据 + data.reverse() + except: + return public.returnMsg(False, '文件内容异常或格式错误') + + args = public.dict_obj() + for item in data: + args.operation = 'add' + args.address = item['Address'] + args.strategy = item['Strategy'] + args.chain = item['Chain'] + args.family = item['Family'] + args.brief = item['brief'] + args.reload = "0" + + self.set_ip_rule(args) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '导入成功') + + # 2024/3/25 下午 2:34 获取端口转发列表 + def port_forward_list(self, get): + ''' + @name 获取端口转发列表 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + get.query = get.get('query/s', '') + list_port_forward = self.firewall.list_port_forward() + forward_db = self.get_forward_db(get) + if type(list_port_forward) == list and type(forward_db) == list: + return self.structure_forward_return_data(list_port_forward, forward_db, query=get.query) + return [] + + # 2024/3/26 下午 3:08 导出所有端口转发规则 + def export_port_forward(self, get): + ''' + @name 导出所有端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + data = self.firewall.list_port_forward() + if not data: + return public.returnMsg(False, '没有规则无法导出') + file_name = "port_forward_{}".format(int(time.time())) + file_path = "{}/{}.json".format(self.config_path, file_name) + + public.writeFile(file_path, public.GetJson(data)) + public.WriteLog("系统防火墙", "导出端口转发规则") + return public.returnMsg(True, file_path) + + # 2024/3/26 下午 3:10 导入端口转发规则 + def import_port_forward(self, get): + ''' + @name 导入端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.file = get.get('file/s', '') + + if not get.file: + return public.returnMsg(False, '文件不能为空') + + if not os.path.exists(get.file): + return public.returnMsg(False, '文件不存在') + + try: + data = public.readFile(get.file) + if "|" in data and not "{" in data: + get.rule_name = "trans_rule" + return self.import_rules_old(get) + + data = json.loads(data) + # 2024/4/10 下午2:51 反转数据 + data.reverse() + except: + return public.returnMsg(False, '文件内容异常或格式错误') + + args = public.dict_obj() + for item in data: + args.operation = 'add' + args.protocol = item['Protocol'] + args.S_Address = item['S_Address'] + args.S_Port = item['S_Port'] + args.T_Address = item['T_Address'] + args.T_Port = item['T_Port'] + args.reload = "0" + + self.set_port_forward(args) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '导入成功') + + # 2024/3/25 下午 3:43 设置端口转发 + def set_port_forward(self, get): + ''' + @name 设置端口转发 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.S_Address = get.get('S_Address/s', '') + get.S_Port = get.get('S_Port/s', '') + get.T_Address = get.get("T_Address/s", '') + get.T_Port = get.get("T_Port/s", '') + get.reload = get.get('reload/s', "1") + + if get.S_Port == "": + return public.returnMsg(False, '源端口不能为空') + # if get.T_Address == "": + # return public.returnMsg(False, '目标地址不能为空') + if get.T_Port == "": + return public.returnMsg(False, '目标端口不能为空') + + # 2024/3/25 下午 5:49 前置检测 + if get.operation == "add": + check_ip_forward = self.firewall.check_ip_forward() + if not check_ip_forward["status"]: + return check_ip_forward + + if not self.check_forward_exist(get): + return public.returnMsg(False, '端口转发规则已存在,请勿重复添加') + + self.set_forward_db(get) + + # 2024/3/25 下午 5:50 构造传参,调用底层方法设置端口转发 + if get.protocol == "tcp/udp" or get.protocol == "all": + info = { + "Family": "ipv4", + "Protocol": "tcp", + "S_Address": get.S_Address, + "S_Port": get.S_Port, + "T_Address": get.T_Address, + "T_Port": get.T_Port, + } + result = self.firewall.port_forward(info=info, operation=get.operation) + if not result['status']: + return result + + info = { + "Family": "ipv4", + "Protocol": "udp", + "S_Address": get.S_Address, + "S_Port": get.S_Port, + "T_Address": get.T_Address, + "T_Port": get.T_Port, + } + result = self.firewall.port_forward(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld and get.reload == "1": + self.firewall.reload() + else: + info = { + "Family": "ipv4", + "Protocol": get.protocol, + "S_Address": get.S_Address, + "S_Port": get.S_Port, + "T_Address": get.T_Address, + "T_Port": get.T_Port, + } + result = self.firewall.port_forward(info=info, operation=get.operation) + + # 2024/3/25 下午 5:50 如果设置成功才重载防火墙 + if result['status'] and self._isFirewalld and get.reload == "1": + self.firewall.reload() + + return result + + # 2024/3/27 上午 9:44 修改端口转发规则 + def modify_forward_rule(self, get): + ''' + @name 修改端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.S_Address = get.old_data['S_Address'] + args1.S_Port = get.old_data['S_Port'] + args1.T_Address = get.old_data['T_Address'] + args1.T_Port = get.old_data['T_Port'] + args1.Protocol = get.old_data['Protocol'] + args1.id = get.old_data['id'] + args1.reload = "0" + self.set_port_forward(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + # args2.S_Address = get.new_data['S_Address'] + args2.S_Port = get.new_data['S_Port'] + args2.T_Address = get.new_data['T_Address'] + args2.T_Port = get.new_data['T_Port'] + args2.Protocol = get.new_data['protocol'] + args2.brief = get.new_data['brief'] + args2.reload = "0" + self.set_port_forward(args2) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '修改成功') diff --git a/class/firewallModel/firewallBase.py b/class/firewallModel/firewallBase.py new file mode 100644 index 00000000..97f83843 --- /dev/null +++ b/class/firewallModel/firewallBase.py @@ -0,0 +1,220 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - 基类 +# ------------------------------ + +import os +import re +from typing import Dict, Union, Any +from xml.etree.ElementTree import ElementTree + +import public + + +class Base(object): + + def __init__(self): + self.config_path = "{}/class/firewallModel/config".format(public.get_panel_path()) + self._isUfw = False + self._isFirewalld = False + self._isIptables = False + if os.path.exists('/usr/sbin/ufw'): + self._isUfw = True + from firewallModel.app.ufw import Ufw + self.firewall = Ufw() + elif os.path.exists('/usr/sbin/firewalld'): + self._isFirewalld = True + from firewallModel.app.firewalld import Firewalld + self.firewall = Firewalld() + elif not self._isUfw and not self._isFirewalld: + self._isIptables = True + from firewallModel.app.iptables import Iptables + self.firewall = Iptables() + _months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', + 'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + + # 2024/3/14 上午 11:27 获取防火墙运行状态 + def get_firewall_status(self) -> bool: + ''' + @name 获取防火墙运行状态 + @author wzz <2024/3/14 上午 11:27> + @param + @return bool True/False + ''' + if self._isUfw: + res = public.ExecShell("systemctl is-active ufw")[0] + if res == "active": return True + res = public.ExecShell("systemctl list-units | grep ufw")[0] + if res.find('active running') != -1: return True + res = public.ExecShell('/lib/ufw/ufw-init status')[0] + if res.find("Firewall is not running") != -1: return False + res = public.ExecShell('ufw status verbose')[0] + if res.find('inactive') != -1: return False + return True + if self._isFirewalld: + res = public.ExecShell("ps -ef|grep firewalld|grep -v grep")[0] + if res: return True + res = public.ExecShell("systemctl is-active firewalld")[0] + if res == "active": return True + res = public.ExecShell("systemctl list-units | grep firewalld")[0] + if res.find('active running') != -1: return True + return False + else: + res = public.ExecShell("/etc/init.d/iptables status")[0] + if res.find('not running') != -1: return False + res = public.ExecShell("systemctl is-active iptables")[0] + if res == "active": return True + return True + + # 2024/3/14 上午 11:30 设置禁ping + def set_ping(self, get) -> dict: + ''' + @name 设置禁ping + @author wzz <2024/3/14 上午 11:31> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.status = get.get("status", "1") + get.status = str(get.status) if str(get.status) in ['0', '1'] else '1' + + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + rep = r"net\.ipv4\.icmp_echo.*" + conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + get.status + "\n", conf) + else: + conf += "\nnet.ipv4.icmp_echo_ignore_all=" + get.status + "\n" + + if public.writeFile(filename, conf): + public.ExecShell('sysctl -p') + return public.returnMsg(True, 'SUCCESS') + else: + return public.returnMsg( + False, + '错误:设置失败,sysctl.conf不可写!
                                    ' + '1、如果安装了[宝塔系统加固],请先关闭
                                    ' + '2、如果安装了云锁,请关闭[系统加固]功能
                                    ' + '3、如果安装了安全狗,请关闭[系统防护]功能
                                    ' + '4、如果使用了其它安全软件,请先卸载
                                    ' + ) + + # 2024/3/14 上午 11:37 获取网站日志目录的大小 + def get_www_logs_size(self, get) -> Dict[str, Union[str, Any]]: + ''' + @name 获取网站日志目录的大小 + @author wzz <2024/3/14 上午 11:37> + @param + @return dict{"status":True/False,"msg":"提示信息"} + ''' + path_size = public.get_size_total("/www/wwwlogs") + if not path_size: + return {"log_path": "/www/wwwlogs", "size": "0B"} + + return {"log_path": "/www/wwwlogs", "size": public.to_size(path_size["/www/wwwlogs"])} + + # 2024/3/25 上午 10:50 获取防火墙类型,firewall或ufw + def _get_firewall_type(self) -> str: + ''' + @name 获取防火墙类型,firewall或ufw + @return str firewall/ufw + ''' + import os + if os.path.exists('/usr/sbin/ufw'): + return 'ufw' + if os.path.exists('/usr/sbin/firewalld'): + return 'firewall' + return 'iptables' + + # 2024/3/26 下午 5:01 获取指定域名的A记录 + def get_a_ip(self, domain: str) -> str: + ''' + @name 获取指定域名的A记录 + @param domain: 域名 + @return str + ''' + try: + import socket + return socket.gethostbyname(domain) + except Exception as e: + return "" + + # 2024/3/26 下午 5:40 检查是否已添加计划任务,如果没有则添加 + def check_resolve_crontab(self): + ''' + @name 检查是否已添加计划任务 + @author wzz <2024/3/26 下午 5:41> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + python_path = "{}/pyenv/bin/python".format(public.get_panel_path()) + + if not public.M('crontab').where('name=?', ('[勿删]系统防火墙域名解析检测任务',)).count(): + cmd = '{} {}'.format(python_path, '/www/server/panel/script/firewall_domain.py') + args = {"name": "[勿删]系统防火墙域名解析检测任务", "type": 'minute-n', "where1": '5', "hour": '', + "minute": '', "sName": "", + "sType": 'toShell', "notice": '', "notice_channel": '', "save": '', "save_local": '1', + "backupTo": '', "sBody": cmd, + "urladdress": ''} + import crontab + res = crontab.crontab().AddCrontab(args) + if res and "id" in res.keys(): + return True + return False + return True + + # 2024/3/26 下午 11:37 当没有域名解析时,删除域名解析的计划任务 + def remove_resolve_crontab(self): + ''' + @name 当没有域名解析时,删除域名解析的计划任务 + @author wzz <2024/3/26 下午 11:37> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + if not public.M('firewall_domain').count(): + pdata = public.M('crontab').where('name=?', '[勿删]系统防火墙域名解析检测任务').select() + if pdata: + import crontab + for i in pdata: + args = {"id": i['id']} + crontab.crontab().DelCrontab(args) + + # 2024/3/26 下午 6:22 端口扫描 + def CheckPort(self, port, protocol): + ''' + @name 端口扫描 + @author wzz <2024/3/26 下午 6:22> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + import socket + localIP = '127.0.0.1' + temp = {} + temp['port'] = port + temp['local'] = True + + try: + if 'tcp' in protocol.lower(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(0.01) + s.connect((localIP, port)) + s.close() + if 'udp' in protocol.lower(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0.01) + s.sendto(b'', (localIP, port)) + s.close() + except: + temp['local'] = False + + result = 0 + if temp['local']: result += 2 + return result diff --git a/class/firewall_new.py b/class/firewall_new.py index 426ea322..3fe29ef4 100644 --- a/class/firewall_new.py +++ b/class/firewall_new.py @@ -1,8 +1,8 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x5 +# | aaPanel x5 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: 1249648969@qq.com # +------------------------------------------------------------------- @@ -138,7 +138,7 @@ class firewalls: def AddDropAddress(self,get): import time import re - rep = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" if not re.search(rep,get.port): return public.return_msg_gettext(False,'IP address youve entered is illegal!'); address = get.port if public.M('firewall').where("port=?",(address,)).count() > 0: return public.return_msg_gettext(False,'The IP exists in block list, no need to repeat processing!') @@ -189,7 +189,7 @@ class firewalls: def AddAcceptPort(self,get): flag=False import re - rep = "^\d{1,5}(:\d{1,5})?$" + rep = r"^\d{1,5}(:\d{1,5})?$" if not re.search(rep,get.port): return public.return_msg_gettext(False,'Port range is incorrect!'); import time port = get.port @@ -297,7 +297,7 @@ class firewalls: filename = '/etc/sysctl.conf' conf = public.readFile(filename) if conf.find('net.ipv4.icmp_echo') != -1: - rep = u"net\.ipv4\.icmp_echo.*" + rep = r"net\.ipv4\.icmp_echo.*" conf = re.sub(rep,'net.ipv4.icmp_echo_ignore_all='+get.status,conf) else: conf += "\nnet.ipv4.icmp_echo_ignore_all="+get.status @@ -320,7 +320,7 @@ class firewalls: file = '/etc/ssh/sshd_config' conf = public.readFile(file) - rep = "#*Port\s+([0-9]+)\s*\n" + rep = "#*Port\\s+([0-9]+)\\s*\n" conf = re.sub(rep, "Port "+port+"\n", conf) public.writeFile(file,conf) @@ -345,7 +345,7 @@ class firewalls: def GetSshInfo(self,get): file = '/etc/ssh/sshd_config' conf = public.readFile(file) - rep = "#*Port\s+([0-9]+)\s*\n" + rep = "#*Port\\s+([0-9]+)\\s*\n" port = re.search(rep,conf).groups(0)[0] import system panelsys = system.system(); @@ -367,7 +367,7 @@ class firewalls: try: file = '/etc/sysctl.conf' conf = public.readFile(file) - rep = "#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" tmp = re.search(rep,conf).groups(0)[0] if tmp == '1': isPing = False except: @@ -397,11 +397,11 @@ class firewalls: flag = False import re # 判断端口是否正确 - rep = "^\d{1,5}(:\d{1,5})?$" + rep = r"^\d{1,5}(:\d{1,5})?$" if not re.search(rep, get.port): return public.return_msg_gettext(False, 'Port range is incorrect! should be between 100-65535'); # 判断IP是否正确 - rep2 = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" if not re.search(rep2, get.address): return public.return_msg_gettext(False, 'IP address is illegal!'); import time ports = get.port diff --git a/class/firewalld.py b/class/firewalld.py index 125cf45a..0c8fb111 100644 --- a/class/firewalld.py +++ b/class/firewalld.py @@ -1,9 +1,9 @@ #!/usr/bin/env python # coding:utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: 1249648969@qq.com, # +------------------------------------------------------------------- diff --git a/class/firewalls.py b/class/firewalls.py index b417e24d..57232920 100644 --- a/class/firewalls.py +++ b/class/firewalls.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import sys,os,public,re,firewalld,time @@ -310,15 +310,13 @@ class firewalls: #改远程端口 def SetSshPort(self,get): + port = get.port - if not port: - return public.return_msg_gettext(False, 'Port range must be between 22 and 65535!') - try: - if int(port) < 22 or int(port) > 65535: return public.return_msg_gettext(False,'Port range must be between 22 and 65535!') - except: - return public.return_msg_gettext(False, 'Port range must be between 22 and 65535!') + ports = ['21','25','80','443','8080','888','8888','7800'] - if port in ports: return public.return_msg_gettext(False,'Do NOT use common default port!') + if port in ports: + return public.return_msg_gettext(False,'Do NOT use common default port!') + # return public.return_message(-1, 0, 'Do NOT use common default port!') file = '/etc/ssh/sshd_config' conf = public.readFile(file) @@ -343,6 +341,7 @@ class firewalls: public.M('firewall').add('port,ps,addtime',(port,'SSH remote service',time.strftime('%Y-%m-%d %X',time.localtime()))) public.WriteLog("TYPE_FIREWALL", "FIREWALL_SSH_PORT",(port,)) return public.return_msg_gettext(True,'Setup successfully!') + # return public.return_message(0, 0, 'Setup successfully!') #取SSH信息 def GetSshInfo(self,get): diff --git a/class/ftp.py b/class/ftp.py index 0f8f1a7c..b048b3a1 100644 --- a/class/ftp.py +++ b/class/ftp.py @@ -1,10 +1,10 @@ #coding: utf-8 # + ------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # + ------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # + ------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # + ------------------------------------------------------------------- import public,db,re,os,firewalls try: @@ -24,13 +24,13 @@ class ftp: import files,time fileObj=files.files() if get['ftp_username'].strip().find(' ') != -1: return public.returnMsg(False,'Username cannot contain spaces') - if re.search("\W+",get['ftp_username']): return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')} + if re.search(r"\W+",get['ftp_username']): return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')} if len(get['ftp_username']) < 3: return {'status':False,'code':501,'msg':public.get_msg_gettext('Username is illegal, cannot be less than 3 characters!')} if not fileObj.CheckDir(get['path']): return {'status':False,'code':501,'msg':public.get_msg_gettext('System critical directory cannot be used as FTP directory!')} if public.M('ftps').where('name=?',(get.ftp_username.strip(),)).count(): return public.return_msg_gettext(False,'User [{}] exists!',(get.ftp_username,)) username = get['ftp_username'].strip() - if re.search("[\/\\\:\*\?\"\'\<\>\|]+",username): - return public.return_msg_gettext(False,"Name cannot contain /\:*?\"<>| symbol") + if re.search("[\\/\\\\:\\*\\?\"\'\\<\\>\\|]+",username): + return public.return_msg_gettext(False,"Name cannot contain /\\:*?\"<>| symbol") password = get['ftp_password'].strip() if len(password) < 6: return public.return_msg_gettext(False, 'Password must be at least [{}] characters',("6",)) get.path = get['path'].replace(' ','') @@ -122,7 +122,7 @@ class ftp: if int(port) < 1 or int(port) > 65535: return public.return_msg_gettext(False,'Port range is incorrect!') file = '/www/server/pure-ftpd/etc/pure-ftpd.conf' conf = public.readFile(file) - rep = u"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)" + rep = u"\n#?\\s*Bind\\s+[0-9]+\\.[0-9]+\\.[0-9]+\\.+[0-9]+,([0-9]+)" #preg_match(rep,conf,tmp) conf = re.sub(rep,"\nBind 0.0.0.0," + port,conf) public.writeFile(file,conf) diff --git a/class/ftplog.py b/class/ftplog.py index fd08b633..0077aa4b 100644 --- a/class/ftplog.py +++ b/class/ftplog.py @@ -1,8 +1,8 @@ #coding: utf-8 # + ------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # + ------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # + ------------------------------------------------------------------- # | Author: hezhihong <272267659@@qq.cn> # + ------------------------------------------------------------------- @@ -30,6 +30,8 @@ month_list = { class ftplog: + __AUTH_MSG =public.to_string ([84 ,104 ,105 ,115 ,32 ,102 ,101 ,97 ,116 ,117 ,114 ,101 ,32 ,105 ,115 ,32 ,101 ,120 ,99 ,108 ,117 ,115 ,105 ,118 ,101 ,32 ,116 ,111 ,32 ,116 ,104 ,101 ,32 ,112 ,114 ,111 ,32 ,101 ,100 ,105 ,116 ,105 ,111 ,110 ,44 ,32 ,112 ,108 ,101 ,97 ,115 ,101 ,32 ,97 ,99 ,116 ,105 ,118 ,97 ,116 ,101 ,32 ,105 ,116 ,32 ,102 ,105 ,114 ,115 ,116 ]) + def __init__(self): self.__messages_file = "/var/log/" self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/' @@ -75,128 +77,44 @@ class ftplog: reverse=False) return file_name_list + def __check_auth(self): + from plugin_auth_v2 import Plugin as Plugin + plugin_obj = Plugin(False) + plugin_list = plugin_obj.get_plugin_list() + import PluginLoader + self.__IS_PRO_MEMBER = PluginLoader.get_auth_state() > 0 + return int(plugin_list["pro"]) > time.time() or self.__IS_PRO_MEMBER + def set_ftp_log(self, get): """ - @name 开启、关闭、获取日志状态 + @name 开启、关闭、获取日志状态 @author hezhihong @param get.exec_name 执行的动作 """ + if not self.__check_auth(): + return public.returnMsg(False, self.__AUTH_MSG) if not hasattr(get, 'exec_name'): return public.returnMsg(False, 'The parameter is incorrect!') - conf_path = '/etc/rsyslog.conf' - conf = public.readFile(conf_path) - import re - search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log" - search_str_two = "ftp.none" - rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n' - result = re.search(search_str, conf) + ftp_file='/etc/rsyslog.d/pure-ftpd.conf' + write_string = '\nftp.*\t\t-/var/log/pure-ftpd.log\n' #获取日志状态 if get.exec_name == 'getlog': - if result: - return_result = 'start' + if os.path.exists(ftp_file): + return public.returnMsg(True, 'start') else: - return_result = 'stop' - return public.returnMsg(True, return_result) - #开启日志审计 + return public.returnMsg(True, 'stop') + + # 开启日志审计 elif get.exec_name == 'start': - # 兼容之前开启,会将配置文件搞坏 - if conf.count('ftp.nonenftp') > 5: - conf = ''' -# /etc/rsyslog.conf configuration file for rsyslog -# -# For more information install rsyslog-doc and see -# /usr/share/doc/rsyslog-doc/html/configuration/index.html -# -# Default logging rules can be found in /etc/rsyslog.d/50-default.conf - - -################# -#### MODULES #### -################# - -module(load="imuxsock") # provides support for local system logging -#module(load="immark") # provides --MARK-- message capability - -# provides UDP syslog reception -#module(load="imudp") -#input(type="imudp" port="514") - -# provides TCP syslog reception -#module(load="imtcp") -#input(type="imtcp" port="514") - -# provides kernel logging support and enable non-kernel klog messages -module(load="imklog" permitnonkernelfacility="on") - -########################### -#### GLOBAL DIRECTIVES #### -########################### - -# -# Use traditional timestamp format. -# To enable high precision timestamps, comment out the following line. -# -$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat - -# Filter duplicated messages -$RepeatedMsgReduction on - -# -# Set the default permissions for all log files. -# -$FileOwner syslog -$FileGroup adm -$FileCreateMode 0640 -$DirCreateMode 0755 -$Umask 0022 -$PrivDropToUser syslog -$PrivDropToGroup syslog - - - -# -# Where to place spool and state files -# -$WorkDirectory /var/spool/rsyslog - -# -# Include all config files in /etc/rsyslog.d/ -# -$IncludeConfig /etc/rsyslog.d/*.conf - - - - ''' - public.writeFile(conf_path, conf) - return self.set_ftp_log(get) - if '*.info;mail.none;authpriv.none;' not in conf: - conf += '\n*.info;mail.none;authpriv.none;cron.none /var/log/messages\n' - if result: - conf = conf.replace(search_str, rep_str) - else: - conf += rep_str - #禁止ftp日志写入/var/log/messages - - d_conf = conf[conf.rfind('info;'):] - d_conf = d_conf[:d_conf.find('/')] - s_conf = d_conf.replace(',', ';') - if s_conf.find(search_str_two) == -1: - str_index = s_conf.rfind(';') - s_conf = s_conf[:str_index + - 1] + search_str_two + s_conf[str_index + 1:] - conf = conf.replace(d_conf, s_conf) + public.writeFile(ftp_file, write_string) self.add_crontab() - #关闭日志审计 + # 关闭日志审计 elif get.exec_name == 'stop': - if result: - conf = re.sub(search_str, '', conf) - #取消禁止ftp日志写入/var/log/messages - if conf.find(search_str_two) != -1: - conf = conf.replace(search_str_two, '') - for i in [';;', ',,', ';,', ',;']: - if conf.find(i) != -1: conf = conf.replace(i, '') + if os.path.exists(ftp_file): + os.remove(ftp_file) + if os.path.exists(ftp_file): + return public.returnMsg(False, 'failed to close the log') self.del_crontab() - public.writeFile(conf_path, conf) public.ExecShell('systemctl restart rsyslog') return public.returnMsg(True, 'successfully set') @@ -404,6 +322,7 @@ $IncludeConfig /etc/rsyslog.d/*.conf if search_str not in line: continue tmp_v = line.split(search_str) + if len(tmp_v[0].strip().split())<3:continue hostname = tmp_v[0].strip().split()[3].strip() action_time = tmp_v[0].replace(hostname, '').strip() action_info['time'] = self.get_format_time(action_time) diff --git a/class/http_requests.py b/class/http_requests.py index 9c168ba1..fe8632e6 100644 --- a/class/http_requests.py +++ b/class/http_requests.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- # +------------------------------------------------------------------- @@ -440,13 +440,19 @@ exit($header."\r\n\r\n".json_encode($body)); match = re.search("(.|\n)+\r\n\r\n",req) if not match: return req,{},0 tmp = match.group().split("\r\n") - i = 0 - if tmp[i].find('Continue') != -1: i+=1 - if not tmp[i]: i+=1 - try: - status_code = int(tmp[i].split()[1]) - except: - status_code = 0 + + status_code = 0 + + from public.regexplib import search_http_response_status_line + + for i in range(len(tmp) - 1): + m = search_http_response_status_line.search(tmp[i]) + if not m: + continue + + status_code = int(m.group(1)) + break + body = req.replace(match.group(),'') return body,tmp,status_code diff --git a/class/jobs.py b/class/jobs.py index 91bfb5c6..b264df58 100755 --- a/class/jobs.py +++ b/class/jobs.py @@ -1,15 +1,16 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import time,public,db,os,sys,json,re,shutil os.chdir('/www/server/panel') def control_init(): + update_py312() public.chdck_salt() clear_other_files() sql_pacth() @@ -21,6 +22,7 @@ def control_init(): clean_max_log('/root/.pm2/pm2.log',1024*1024*20) remove_tty1() clean_hook_log() + acme_crond_reinit() run_new() clean_max_log('/www/server/cron',1024*1024*5,20) clean_max_log("/www/server/panel/plugin/webhook/script",1024*1024*1) @@ -31,7 +33,7 @@ def control_init(): set_pma_access() # public.set_open_basedir() clear_fastcgi_safe() - update_py37() + # update_py37() run_script() set_php_cli_env() check_enable_php() @@ -45,6 +47,7 @@ def control_init(): #hide_docker() rep_pyenv_link() rm_apache_cgi_test() + uninstall_pip_lxml() def rm_apache_cgi_test(): ''' @@ -56,6 +59,32 @@ def rm_apache_cgi_test(): if os.path.exists(test_cgi_file): os.remove(test_cgi_file) +def uninstall_pip_lxml(): + ''' + @name 如果lxml版本大于5.0.0卸载它,解决部分机器 import bs4 程序崩溃的问题 + ''' + try: + import lxml + if lxml.__version__ > '5.0.0': + public.ExecShell('/www/server/panel/pyenv/bin/pip3 uninstall lxml -y') + except: + pass + +def acme_crond_reinit(): + ''' + @name 修复acme定时任务 + @return void + ''' + try: + lets_config_file = os.path.join(public.get_panel_path(),'config/letsencrypt_v2.json') + if not os.path.exists(lets_config_file): return + + import acme_v2 + acme_v2.acme_v2().set_crond() + except: + pass + + def rep_pyenv_link(): ''' @name 修复pyenv环境软链 @@ -156,7 +185,7 @@ def clear_other_files(): shutil.copyfile(src_file,init_file) if os.path.getsize(init_file) < 10: public.ExecShell("chattr -i " + init_file) - public.ExecShell("\cp -arf %s %s" % (src_file,init_file)) + public.ExecShell(r"\cp -arf %s %s" % (src_file,init_file)) public.ExecShell("chmod +x %s" % init_file) except:pass public.writeFile('/var/bt_setupPath.conf','/www') @@ -484,7 +513,7 @@ def set_php_cli_env(): if not os.path.exists(php_fpm) and os.path.exists(php_fpm_src): os.symlink(php_fpm_src,php_fpm) if not os.path.exists(php_pecl) and os.path.exists(php_pecl_src): os.symlink(php_pecl_src,php_pecl) if not os.path.exists(php_pear) and os.path.exists(php_pear_src): os.symlink(php_pear_src,php_pear) - public.ExecShell("\cp -f {} {}".format(php_ini,php_cli_ini)) # 每次复制新的php.ini到php-cli.ini + public.ExecShell(r"\cp -f {} {}".format(php_ini,php_cli_ini)) # 每次复制新的php.ini到php-cli.ini public.ExecShell('sed -i "/disable_functions/d" {}'.format(php_cli_ini)) # 清理禁用函数 bashrc_body += "alias php{}='php{} -c {}'\n".format(php_version,php_version,php_cli_ini) # 设置别名 else: @@ -507,7 +536,7 @@ def check_enable_php(): for php_v in php_versions: ngx_php_conf = public.get_setup_path() + '/nginx/conf/enable-php-{}.conf'.format(php_v) if os.path.exists(ngx_php_conf): continue - enable_conf = ''' + enable_conf = r''' location ~ [^/]\.php(/|$) {{ try_files $uri =404; @@ -549,7 +578,7 @@ def run_script(): if not os.path.exists(script_info['script_file']) \ or script_info['script_file'].find('/www/server/panel/plugin/') != 0 \ - or not re.match('^\w+$',script_info['script_file']): + or not re.match(r'^\w+$',script_info['script_file']): os.remove(script_conf_file) if os.path.exists(exec_log_file): os.remove(exec_log_file) continue @@ -594,6 +623,7 @@ def files_set_mode(): ["/www/backup","","root",600,True], ["/www/wwwlogs","","www",700,True], ["/www/enterprise_backup","","root",600,True], + ["/www/server/panel/webserver", "", "root", 755, False], ["/www/server/cron","","root",700,True], ["/www/server/cron","/*.log","root",600,True], ["/www/server/stop","","root",755,True], @@ -602,7 +632,7 @@ def files_set_mode(): ["/www/server/panel/class","","root",600,True], ["/www/server/panel/data","","root",600,True], ["/www/server/panel/plugin","","root",600,False], - ["/www/server/panel/BTPanel","","root",600,True], + ["/www/server/panel/BTPanel","","root",755,True], ["/www/server/panel/vhost","","root",600,True], ["/www/server/panel/rewrite","","root",600,True], ["/www/server/panel/config","","root",600,True], @@ -617,6 +647,7 @@ def files_set_mode(): ["/www/server/panel/BT-Panel","","root",700,False], ["/www/server/panel/BT-Task","","root",700,False], ["/www/server/panel","/*.py","root",600,False], + ["/www/server/panel","","root",755,False], ["/dev/shm/session.db","","root",600,False], ["/dev/shm/session_py3","","root",600,True], ["/dev/shm/session_py2","","root",600,True], @@ -706,7 +737,7 @@ def set_pma_access(): -#尝试升级到独立环境 +#尝试升级到独立环境3.7 def update_py37(): pyenv='/www/server/panel/pyenv/bin/python3' pyenv_exists='/www/server/panel/data/pyenv_exists.pl' @@ -716,6 +747,16 @@ def update_py37(): public.writeFile(pyenv_exists,'True') return True +#尝试升级到独立环境3.12 +def update_py312(): + pyenv='/www/server/panel/pyenv/bin/python3.12' + if os.path.exists(pyenv): return False + download_url = public.get_url() + only_update_pyenv312 = '/tmp/only_update_pyenv312.pl' + public.writeFile(only_update_pyenv312, 'True') + public.ExecShell("nohup curl -k {}/install/update_7.x_en.sh|bash &>/tmp/panelUpdate.pl &".format(download_url)) + return True + def test_ping(): _f = '/www/server/panel/data/ping_token.pl' if os.path.exists(_f): os.remove(_f) @@ -855,7 +896,7 @@ def disable_putenv(fun_name): if os.path.exists(is_set_disable): return True php_vs = public.get_php_versions() php_ini = "/www/server/php/{0}/etc/php.ini" - rep = "disable_functions\s*=\s*.*" + rep = r"disable_functions\s*=\s*.*" for pv in php_vs: php_ini_path = php_ini.format(pv) if not os.path.exists(php_ini_path): continue diff --git a/class/log_analysis.py b/class/log_analysis.py index 3c6fd646..83b29e95 100644 --- a/class/log_analysis.py +++ b/class/log_analysis.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: lkq +# | Author: lkq # | # | 日志分析工具 # +------------------------------------------------------------------- @@ -21,7 +21,7 @@ class log_analysis: def __init__(self): if not os.path.exists(self.path + '/log/'): os.makedirs(self.path + '/log/') if not os.path.exists(self.log_analysis_path): - log_analysis_data = '''help(){ + log_analysis_data = r'''help(){ echo "Usage: ./action.sh [options] [FILE] [OUTFILE] " echo "Options:" echo "xxx.sh san_log [FILE] Get the log list with the keywords xss|sql|mingsense information|php code execution in the successful request [OUTFILE] 11" @@ -231,7 +231,7 @@ echo "[*] shut down" result['is_status'] = True else: result['is_status'] = False - if os.path.exists(speed+".time"): + if os.path.exists(speed+".time") and os.path.getsize(speed+".time") > 0: time_data, start_time, status = public.ReadFile(self.path + '/log/' + log_path + ".time").split("[]") if status == '1' or start_time==1: result['time']=time_data @@ -257,4 +257,4 @@ echo "[*] shut down" type_list = ['xss', 'sql', 'san', 'php', 'ip', 'url'] if get.type not in type_list: return public.ReturnMsg(False, 'Type mismatch') if not os.path.exists(speed + get.type + '.log'): return public.ReturnMsg(False, 'Record does not exist') - return self.get_log_count(speed + get.type + '.log', is_body=True) \ No newline at end of file + return self.get_log_count(speed + get.type + '.log', is_body=True) diff --git a/class/logsModel/ftpModel.py b/class/logsModel/ftpModel.py index 1fee7fef..c6d09304 100644 --- a/class/logsModel/ftpModel.py +++ b/class/logsModel/ftpModel.py @@ -1,469 +1,452 @@ -#coding: utf-8 -# + ------------------------------------------------------------------- -# | 宝塔Linux面板 -# + ------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. -# + ------------------------------------------------------------------- -# | Author: hezhihong <272267659@@qq.cn> -# + ------------------------------------------------------------------- -import public, os, time -from logsModel.base import logsBase - -try: - from BTPanel import session -except: - pass -#英文转月份缩写 -month_list = { - "Jan": "1", - "Feb": "2", - "Mar": "3", - "Apr": "4", - "May": "5", - "Jun": "6", - "Jul": "7", - "Aug": "8", - "Sept": "9", - "Sep": "9", - "Oct": "10", - "Nov": "11", - "Dec": "12" -} - - -class main(logsBase): - def __init__(self): - self.__messages_file = "/var/log/" - self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/' - if not os.path.isdir(self.__ftp_backup_path): - public.ExecShell('mkdir -p {}'.format(self.__ftp_backup_path)) - self.__script_py = public.get_panel_path() + '/script/ftplogs_cut.py' - - def get_file_list(self, path, is_bakcup=False): - """ - @name 取所有messages日志文件 - @param path: 日志文件路径 - @return: 返回日志文件列表 - """ - files = os.listdir(path) - if is_bakcup: - file_name_list = [{ - "file": "/var/log/pure-ftpd.log", - "time": int(time.time()) - }] - else: - file_name_list = [] - for i in files: - tmp_dict = {} - if not i: continue - file_path = path + i - tmp_dict['file'] = file_path - if is_bakcup: - if os.path.isfile(file_path) and i.find('pure-ftpd.log') != -1: - tmp_dict['time'] = int( - public.to_date( - times=os.path.basename(file_path).split('_')[0] + - ' 00:00:00')) - file_name_list.append(tmp_dict) - else: - if os.path.isfile(file_path) and i.find('messages') != -1: - tmp_dict['time'] = int( - public.to_date( - times=os.path.basename(file_path).split('-')[1] + - ' 00:00:00')) - file_name_list.append(tmp_dict) - file_name_list = sorted(file_name_list, - key=lambda x: x['time'], - reverse=False) - return file_name_list - - def set_ftp_log(self, get): - """ - @name 开启、关闭、获取日志状态 - @author hezhihong - @param get.exec_name 执行的动作 - """ - if not hasattr(get, 'exec_name'): - return public.returnMsg(False, 'The parameter is incorrect!') - conf_path = '/etc/rsyslog.conf' - conf = public.readFile(conf_path) - import re - search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log" - search_str_two = "ftp.none" - rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n' - result = re.search(search_str, conf) - #获取日志状态 - if get.exec_name == 'getlog': - if result: - return_result = 'start' - else: - return_result = 'stop' - return public.returnMsg(True, return_result) - #开启日志审计 - elif get.exec_name == 'start': - if result: - conf = conf.replace(search_str, rep_str) - else: - conf += rep_str - #禁止ftp日志写入/var/log/messages - - d_conf = conf[conf.rfind('info;'):] - d_conf = d_conf[:d_conf.find('/')] - s_conf = d_conf.replace(',', ';') - if s_conf.find(search_str_two) == -1: - str_index = s_conf.rfind(';') - s_conf = s_conf[:str_index + - 1] + search_str_two + s_conf[str_index + 1:] - conf = conf.replace(d_conf, s_conf) - self.add_crontab() - #关闭日志审计 - elif get.exec_name == 'stop': - if result: - conf = re.sub(search_str, '', conf) - #取消禁止ftp日志写入/var/log/messages - if conf.find(search_str_two) != -1: - conf = conf.replace(search_str_two, '') - for i in [';;', ',,', ';,', ',;']: - if conf.find(i) != -1: conf = conf.replace(i, '') - self.del_crontab() - public.writeFile(conf_path, conf) - public.ExecShell('systemctl restart rsyslog') - return public.returnMsg(True, 'successfully set') - - def get_format_time(self, englist_time): - """ - @name 时间英文转换 - """ - chinanese_time = '' - try: - for i in month_list.keys(): - if i in englist_time: - tmp_time = englist_time.replace(i, month_list[i]) - tmp_time = tmp_time.split() - chinanese_time = '{}-{} {}'.format(tmp_time[0], tmp_time[1], - tmp_time[2]) - break - return chinanese_time - except: - return chinanese_time - - def get_login_log(self, get): - """ - @name 取登录日志 - @author hezhihong - @param get.user_name ftp用户名 - return - """ - - search_str = 'pure-ftpd:' - search_str2 = 'pure-ftpd[' - if not hasattr(get, 'user_name'): - return public.returnMsg(False, 'The parameter is incorrect!') - args = public.dict_obj() - args.exec_name = 'getlog' - file_name = self.__ftp_backup_path - is_backup = True - if self.set_ftp_log(get) == 'stop': - file_name = self.__messages_file - is_backup = False - file_list = self.get_file_list(file_name, is_backup) - data = [] - sortid = 0 - tmp_dict = {} - login_all = [] - for file in file_list: - - if not os.path.isfile(file['file']): continue - conf = public.readFile(file['file']) - lines = conf.split('\n') - for line in lines: - if not line: continue - login_info = {} - if search_str not in line and search_str2 not in line: - continue - tmp_value = ' is now logged in' - info = line[:line.find(search_str)].strip() - if not info: - info = line[:line.find(search_str2)].strip() - hostname = info.split()[-1] - exec_time = info.split(hostname)[0].strip() - exec_time = self.get_format_time(exec_time) - ip = line[line.find('(') + 1:line.find(')')].split('@')[1] - - #取登录成功日志 - if tmp_value in line: - user = line.split(tmp_value)[0].strip().split()[-1] - if user == '?' or user != get.user_name: continue - dict_index = '{}__{}'.format(user, ip) - if dict_index not in tmp_dict: - tmp_dict[dict_index] = [] - tmp_dict[dict_index].append(exec_time) - - #取登出日志 - tmp_value = '[INFO] Logout.' - tmp_value_two = 'Timeout - try typing a little faster next time' - if tmp_value in line or tmp_value_two in line: - user = line[line.find('(') + - 1:line.find(')')].split('@')[0] - if user == '?' or user != get.user_name: continue - dict_index = '{}__{}'.format(user, ip) - try: - login_info['out_time'] = exec_time - login_info['in_time'] = tmp_dict[dict_index][0] - login_info['user'] = user - login_info['ip'] = ip - login_info['status'] = 'Success' #0为登录失败,1为登录成功 - login_info['sortid'] = sortid - login_all.append(login_info) - tmp_dict[dict_index] = [] - sortid += 1 - except: - pass - #取登录失败日志 - tmp_value = 'Authentication failed for user' - if tmp_value in line: - user = line.split(tmp_value)[-1].replace('[', '').replace( - ']', '').strip() - if user == '?' or user != get.user_name: continue - login_info['user'] = user - login_info['ip'] = ip - login_info['status'] = 'Failure' #0为登录失败,1为登录成功 - login_info['in_time'] = exec_time - login_info['out_time'] = exec_time - login_info['sortid'] = sortid - login_all.append(login_info) - sortid += 1 - - if tmp_dict: - for item in tmp_dict.keys(): - if not tmp_dict[item]: continue - info = { - "status": "login successful", - "in_time": tmp_dict[item][0], - "out_time": "connecting", - "user": item.split('__')[0], - "ip": item.split('__')[1], - "sortid": sortid - } - sortid += 1 - login_all.append(info) - #搜索过滤 - if login_all and 'search' in get and get.search and get.search.strip(): - for info in login_all: - try: - search_str = str(get.search).strip().lower() - # public.writeFile('/tmp/aa.aa', get.search) - if info['ip'].find(search_str) != -1 or info['user'].lower( - ).find(search_str) != -1 or info['status'].find( - search_str) != -1 or info['in_time'].find( - search_str) != -1: - data.append(info) - elif info['out_time'] and info['out_time'].find( - search_str) != -1: - data.append(info) - except: - pass - else: - for info2 in login_all: - data.append(info2) - - data = sorted(data, key=lambda x: x['sortid'], reverse=True) - return self.get_page(data, get) - - def get_page(self, data, get): - """ - @name 取分页 - @author hezhihong - @param data 需要分页的数据 list - @param get.p 第几页 - @return 指定分页数据 - """ - # 包含分页类 - import page - # 实例化分页类 - page = page.Page() - - info = {} - info['count'] = len(data) - info['row'] = 10 - info['p'] = 1 - if hasattr(get, 'p'): - info['p'] = int(get['p']) - info['uri'] = {} - info['return_js'] = '' - # 获取分页数据 - result = {} - result['page'] = page.GetPage(info, limit='1,2,3,4,5,8') - n = 0 - result['data'] = [] - for i in range(info['count']): - if n >= page.ROW: break - if i < page.SHIFT: continue - n += 1 - result['data'].append(data[i]) - return result - - def get_action_log(self, get): - """ - @name 取操作日志 - @author hezhihong - @param get.user_name ftp用户名 - return {"upload":[],"download":[],"rename":[],"delete":[]} - """ - search_str = 'pure-ftpd:' - args = public.dict_obj() - args.exec_name = 'getlog' - file_name = self.__ftp_backup_path - is_backup = True - if self.set_ftp_log(get) == 'stop': - file_name = self.__messages_file - is_backup = False - file_list = self.get_file_list(file_name, is_backup) - if not hasattr(get, 'user_name'): - return public.returnMsg(False, 'The parameter is incorrect!') - data = [] - tmp_data = [] - sortid = 0 - for file in file_list: - if not os.path.isfile(file['file']): continue - conf = public.readFile(file['file']) - lines = conf.split('\n') - for line in lines: - if not line: continue - action_info = {} - if search_str not in line: continue - - tmp_v = line.split(search_str) - hostname = tmp_v[0].strip().split()[3].strip() - action_time = tmp_v[0].replace(hostname, '').strip() - action_info['time'] = self.get_format_time(action_time) - - upload_value = ' uploaded ' - download_value = ' downloaded ' - rename_value = 'successfully renamed or moved:' - delete_value = ' Deleted ' - ip = line[line.find('(') + 1:line.find(')')].split('@')[1] - action_info['ip'] = ip - action_info['type'] = '' - #取操作用户 - user = '' - if upload_value in line or download_value in line or rename_value in line or delete_value in line: - user = line[line.find('(') + - 1:line.find(')')].split('@')[0] - action_info['sortid'] = sortid - sortid = sortid + 1 - if not user or user != get.user_name: continue - #取上传日志 - if (get.type == 'all' - or get.type == 'upload') and upload_value in line: - line_list = line.split() - upload_index = line_list.index('uploaded') - # action_info['file'] = line_list[upload_index - 1].replace( - # '//', '/') - action_info['file'] = line[line.find(']') + - 1:line.rfind('(')].replace( - 'uploaded', - '').replace('//', - '/').strip() - action_info['type'] = 'upload' - tmp_data.append(action_info) - #取下载日志 - if (get.type == 'all' - or get.type == 'download') and download_value in line: - line_list = line.split() - upload_index = line_list.index('downloaded') - action_info['file'] = line_list[upload_index - 1].replace( - '//', '/') - action_info['type'] = 'download' - tmp_data.append(action_info) - #取重命名日志 - if (get.type == 'all' - or get.type == 'rename') and rename_value in line: - action_info['file'] = line.split(rename_value)[1].replace( - '->', 'Renamed to').strip().replace('//', '/') - action_info['type'] = 'rename' - tmp_data.append(action_info) - #取删除日志 - if (get.type == 'all' - or get.type == 'delete') and delete_value in line: - action_info['file'] = line.split()[-1].strip().replace( - '//', '/') - action_info['type'] = 'delete' - tmp_data.append(action_info) - # f.close - #搜索过滤 - if tmp_data and 'search' in get and get.search and get.search.strip(): - for info in tmp_data: - search_str = str(get.search).strip().lower() - if info['ip'].find(search_str) != -1 or info['file'].lower( - ).find(search_str) != -1 or info['type'].find( - search_str) != -1 or info['time'].find( - search_str) != -1 or get.user_name.lower().find( - search_str) != -1: - data.append(info) - else: - for info2 in tmp_data: - data.append(info2) - data = sorted(data, key=lambda x: x['sortid'], reverse=True) - return self.get_page(data, get) - - def del_crontab(self): - """ - @name 删除项目定时清理任务 - @auther hezhihong<2022-10-31> - @return - """ - cron_name = '[Do not delete] FTP audit log cutting task' - cron_path = public.GetConfigValue('setup_path') + '/cron/' - cron_list = public.M('crontab').where("name=?", (cron_name, )).select() - if cron_list: - for i in cron_list: - if not i: continue - cron_echo = public.M('crontab').where( - "id=?", (i['id'], )).getField('echo') - args = {"id": i['id']} - import crontab - crontab.crontab().DelCrontab(args) - del_cron_file = cron_path + cron_echo - public.ExecShell( - "crontab -u root -l| grep -v '{}'|crontab -u root -". - format(del_cron_file)) - - def add_crontab(self): - """ - @name 构造日志切割任务 - """ - python_path = '' - try: - python_path = public.ExecShell('which btpython')[0].strip("\n") - except: - try: - python_path = public.ExecShell('which python')[0].strip("\n") - except: - pass - if not python_path: return False - if not public.M('crontab').where('name=?', - ('[Do not delete] FTP audit log cutting task', )).count(): - cmd = '{} {}'.format(python_path, self.__script_py) - args = { - "name": "[Do not delete] FTP audit log cutting task", - "type": 'day', - "where1": '', - "hour": '0', - "minute": '1', - "sName": "", - "sType": 'toShell', - "notice": '0', - "notice_channel": '', - "save": '', - "save_local": '1', - "backupTo": '', - "sBody": cmd, - "urladdress": '' - } - import crontab - res = crontab.crontab().AddCrontab(args) - if res and "id" in res.keys(): - return True - return False - return True +QRASP55VO/1DQ98p1csw9A== +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +5G1X0WJyak7IcriKROwRfg== +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +ZuLVraRZUj2RhJbyALJsxNMuhZe1AKXjIZKX1s/bahYi6PnNCfy1Qv0MKdv1oxSXYGJY2PXP7NLNatPW4VSB/zJjWGw5HqJLpeYo/MseDrI= +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +mlbcpvT0kUVOtns2yTGydDt4+07xDvuvRqS/JgkUon3SrcxUlm2Z4rG4Pa9PwOq+ +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +iOUsBGgOHEqzdAdQgfAp4trfzBztc5RmfOjqgmGickU= +XQTKBPclCgMYfJTNC1Qwx9JOU5PR8V/GzRhXc9amMLuHdAcID+TXUZzz7SdDuJA9 +1u+XjG/2+GSQRv6EzCaWRQ== +f3AsbhS4PaN1B4cUkY9T+A== +vlNl0s/hLJ4MLdvF5a1XATBpmRe09mwg70GHdHdh7mTmKAaerhorUT+w+dAajOtb +MMZtIZZuuuWy0CLBzKekTg== +i8F7pUrlHPBNsIux2MgfYA== +hAZTEkbxoxowQJisJSulNX6xYijfsoIV2aWlEpje85k= +Fzf1WPeHT+phfW8FPaBhNw== +iwW5Y10vLm5bol1PMh2K5PcgF/7PR7VaxhpNPLVQXiQ= +iiFBxU1BEtsymTSqJJqljqN1RY1RLiUspBdLrvd0CW0= +ldCxpJy7GYU9Z3wc/nhJ/te4XvT+sJUYSl50ZnBKy3w= +CrJuPSINqYUMS/s5z4adcU1LJqJ2Sojj3C2wjmLuRsM= +Psg6WqdUeMsPYvW+jnYn0evEHl+1XoE/rnCfo+c6WdA= +1z8iprMiHgI6LYZftXGjKDYPI7FVqZWCQ09oWcM3Pag= +o1ftBjL5WX5IAh9xWVdqd9wdMBvoYRdNfl+IAvPWFp8= +QUUBWS4HdgaVs9J11lpeBKBH5NkT6WbMMMnumMF/7Aw= +tfRWYnUvHUwnwQYB+ywLpslVnsH3tMXHAPD/cYoTpT4= +AtAHlguqAHnQsUP0meqLH3VU1D/0sLjQMR/6J/fyRbM= +7MgotmZFCo6+Kg3s1/uhRtwRAi7VKRh0cRfONXMb3yI= +CvGG8CijcRlYyvz5dGomfhXk6DzjYPOBtu63/Veri24= +/PTfuW9bPdAz/6cUQf+E55cP43jnvaceBCRBneohYbA= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Tz7Nk5H535VRrRyTrsKtQL8WpBb8+0Czjnl0ON67RWc= +gSinJxQ2Dl02IUzuK5alezKMVZMklWE7tWiUg2329L/n+lRYCQ9UCFbErLWlw54SDdzf2aW7xcFo9f6yWsFr+Gmxv7QW7ILh8TsYOZ9znNp1PFfZW6D80dgQ9cC19CuCsN/nORHFj+ZzOKgvaEAfFiLoGt0ZpN5vpGxumpW0gpcWn6x7Ldft+bCe8OO/4vq4eE9RKaqjTgoUu0FLyAKLMsUrm9spdqinKdfS+0njcmvjNfpj5WOsg4REfjqMdCjT+eYrQYJpSEhzTFpPXaGYrdCg7TnFabrpVoJ3F5uhjQdkKGWqXHHHV/nbuQtIHnCtT8dwjmznuv5sSlpRCVqsgj9kw0VHrZYCN0PnqZ18QBTTb6aQzq2vcHCtyHbfnTgZYpXapiGZtR0S/U7DzfJCjnAyqRZxt4/wKHbAGYy8PQ8OUSXjFZJkxujMHXXUoxC2tVM7rRO1UyMTyERrP/FjuBzgj8YxTjyaxV4GnCT02jD45RbtdAK6txhyTaDZXcGS +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +NRG8WiBknxy8LZ7uM7Rtwjpor9jB6CRSN8s/tRh0bCGCT3O3yIYveV/kC3whqCOj +g+9Bvcv6QScOWqD1KWI83lDWKDEvFH24UumsH9uh9SSEzyoQuAQyJOm+k2nYRQYDLlXqKegz+5H8/A6yDuU4hTssV9YA2l/qH0zdlTP5IOI= +wgR07xfoapmx6eEnFHXXYsrtutdMRxYffYBJpExAFLL3LFn2eCspav2L+PBokDEhe88eat5Ab0fx9+MVZjZtHQ== +xWoGNWjKGPfI4gq8aHoTfOgPIu3QGZOJ5qEujzh3pzi1Iz2/AHJ8GvmS3bUx5254nOirctK/k6BFZA2v8ayK/EDVY6Wbr2d7jYxq8UAugp4= +uRzTWU2MnopGqqEdWeT6ZU1hlM1Dw8+qKY/ieJj+NsUqvH0CTNguPTeX+Laan8WHKtl7c/+1YA4rF+Fa3pfogwkGJNEhZb49H+rX+WZ7S1s= +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrQbQF1j1bF4/QmmX1CwZUVEjmug7a9JAroEXBBTfhYssEYHy8XwASHc+rw3shghfWA== +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIlhC5ovOIKOhQYI+YFVzTxDyBspL/Zz/EA3ekwqZLC22t +eeFiPlXUI0Fos82HYztSmTsAYhvcpYqGtZuapEgzpCBypVgTjJ6W0KJC4ipcw897 +WuRvIBaPNRsVGEMoM4NSTioH6DPOvYqQxMaOqX/7OKFY0pBl49Sar+3IeBmADhmj +Z8UsPk1Q7HtwjRd4g01ryw== +N5xldEoitd9eWUThCvdOTB6LOXhNE9TOsjnkpc/9SMN2L8WJXh22t578ODk/HhdL +oxiSpIWwD4wuBub3KU1/cF5fchu1Vqpk+nU9bn11yzo= +PRG/YRzXVD8iVR3bzEcUnjHro+U+4TucaVNI9ygPWuVU3EqLuxmqZkTk2CyNK7O/ +VmVrGQo2zRokW/ZuO9bN68uCgzKuG5LF3dEm8osmV0HPcWbbdr6DBOymTEvfp0P/ftdl4LLYfLwuPpwsYazhEA== +VmVrGQo2zRokW/ZuO9bN68sblSJDKz0OqxMt0bHs/NUCob2DYRF35HVY0+LpxHBb +iDFAoKi0IJZ7t2pJuBivrw== +l2FJPs4YkAmmok1ulDRuSA== +PRG/YRzXVD8iVR3bzEcUnt6EfhtcL4GrcBoio2oUko9nPAoPP0N4kWpuhhh+oYdV +9zgnmC3+5N9XK5/NRaR4SMVNup0N5fm76yGD/GNlNnU= +sDlCNtGYD6nPzg4cZ+6YAIVaGRKK1htDJsqNdoebWm8= +1V2v8QerKOmubvSxgB4eTCXRIGj4wwBQxqmi+7HBYgg= +PRG/YRzXVD8iVR3bzEcUntHGmZ5qTlWv7ywh0UaUxJzXifYQX4pUcW7z5PPhLeh5 +sDlCNtGYD6nPzg4cZ+6YAOK/4Cv++bqYgS9TyEF32cuA5AzQdBq8lyKWQabFTagT +1D18KWr2hdVsBcdZx1OaPWkr8ytK7qVWaaza8e1vU6k= +VmVrGQo2zRokW/ZuO9bN66Vh5+Ci920JkrzQsTHn083JJPLf45hGeTPWU0PgjupO+yaDkxvgzGWeJhaeVem9Wq+8oeyKDNsYcPKXT94yeZ0UymKFveKN91eF38ApXJp2 +VmVrGQo2zRokW/ZuO9bN625bd4BNT0j+VcP1nTIY6XaDTRy5+5AWHUZPakkiglNT +VmVrGQo2zRokW/ZuO9bN69TcL00if5hvJ8+IR0VhUuvoRwQz2rzPWjBJm1Mk3z5a +VmVrGQo2zRokW/ZuO9bN62AVcq3plEcj4rxrsvQ9pPDU08M8azrqNxOOot3ghWHctbl7KH0YZqLimlQWQDerlojzSftx5vWNNRCgaO4RyF8= +VmVrGQo2zRokW/ZuO9bN6xD6PoCXoaoICnYYUV03lnrKjmQB9MEOOIg0j4Z9zWge +VmVrGQo2zRokW/ZuO9bN6/wMRobeZASkZ7UgaRv/zbQEquXghr8pcxoQVAx9J5G/e9b3CtH5KoqK0sb3R99OsA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66Vh5+Ci920JkrzQsTHn083JJPLf45hGeTPWU0PgjupOJvco2FZSOBWgiEqCcFM7Q/vW4PqCZLGYaAAy6qf2tko= +VmVrGQo2zRokW/ZuO9bN625bd4BNT0j+VcP1nTIY6XaDTRy5+5AWHUZPakkiglNT +VmVrGQo2zRokW/ZuO9bN69TcL00if5hvJ8+IR0VhUuvoRwQz2rzPWjBJm1Mk3z5a +VmVrGQo2zRokW/ZuO9bN62AVcq3plEcj4rxrsvQ9pPDU08M8azrqNxOOot3ghWHctbl7KH0YZqLimlQWQDerlpd+8N/8ymIPDrStsyiyEo4= +VmVrGQo2zRokW/ZuO9bN6xD6PoCXoaoICnYYUV03lnrKjmQB9MEOOIg0j4Z9zWge +VmVrGQo2zRokW/ZuO9bN6/wMRobeZASkZ7UgaRv/zbQEquXghr8pcxoQVAx9J5G/e9b3CtH5KoqK0sb3R99OsA== +ojv9MyHa70Aio7SDrWcjwSYYqgfeHLgPvpjtVJfUmX0s6OSNU+WrIf9mNVPQ5TfqPavTidvffxVkcshQNOCsRA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklNjZdFug/ngOpMcvCKxEBrAN68NaDKAtp2GLglVerAbQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkllTuznFui81R4CASaEKJI9 +5wsVwKLrIjIWqeTNpSVp9YtfZPXXv5krNmxeXAjq+NQ= +1u+XjG/2+GSQRv6EzCaWRQ== +JKgfL+COM6qpIwe0Wm0zY0qrBwJPhJivgZdbq2ek/MNyEskG/o3RxQuD1murI70P +Z8UsPk1Q7HtwjRd4g01ryw== +9JiWXiRNWDTg+XstQ0v3tAQjzhKVhg9yH4FpL7cmNHMby1dZvf+KLKXDFxiL/GyHwt+mo7J9Eh26MMHXk7dZ7A== +vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg= +ho/Q+jrWDtBeg9J9ZTnKizg5CYSqIH+8noUWvD9u7YuJKKfD4Zw1m+s2/8nfOg5c +Z8UsPk1Q7HtwjRd4g01ryw== +fLETQ22HuDIh4ibW1C50WajryFpGmdAvdbylaFXR0PYbVChIKwUBviTdPnbJl3rp +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVtxbXBpGlFIU9ZBe0L9nDB3cJqXtkVNxiOuaaYNBuGag== +c0jePRxtTVZYop75Q5JCFonnV+X8W6zX0oXQ+sQuZtHIAMgLt2jbmRWY4GrPgmiW +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW7Akukb+j0lqloIv/j7uSM4oazJqFVNfgaZWfRnkezm/n/qRAFeZXm0pd4Y650TvY= +Mmz9tB5RaVIWQerkiDs2BePvXl4dNWSjl+vJFVGrvpac5kzICL/kBoClsURQxOPYTWXamhuvoo8Pwhw/Asx8vw== +R14ZYm6mELJXNFBxHSBttdYfQPT7lYz3sNsgm+2cAVEVZqqsWoLADHy1tzgaDLwVuLUQU00i5jhUkarevZBgbw== +jpkMvZxe0AU7LK72hOoSV9O2+nkRjDR2qPsiciba+P0= +SSb4svay1O2En913Q8HWTI5XiKI6KpCz9PP4OKkiVgIZ9PAiv190X/Q6cvCmIvEn +O3CUgrw2GJfB+mDjH5+NdgM4cG182DjbaaqgGRQTDbbHFEBxIQHWpD3HPFUKruz8 +VmVrGQo2zRokW/ZuO9bN62aET6XweFEf8bCs0QU19kFV77Lm2Kxx3MCAPMJIlJSp +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN62aET6XweFEf8bCs0QU19kF1ezkxxQftj/4sG8qLYubO +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVhrSS9BSOA1ZLru6nt/7dm75/vFI6+H/XoIqtvVBWgGA== +13vYlKuO6A3n1KjBvCLS2cZOnSAYizlRW6NVt55Nvpg= +Rc/EJuEm/I9INM8fD5RDm8V6NM+R609UAWBHMDxIGH1KB0feE4DfP+R5GNLxg7AM +xWoGNWjKGPfI4gq8aHoTfBMUl5q05yLZWa9BqWZ+hHXNzfrvrNJv7lUhLv58UG50furKaKDp9/7/vwDMn7xgPA== +32pdC9DD05OE2l0oXazDFBLSw6QGkeD2EftJ95N/ZFU= +9Ky7E5Jxg7bFh45xr+PtLAQzRENCwucwjn+oXpXkeQE= +Rc/EJuEm/I9INM8fD5RDm8V6NM+R609UAWBHMDxIGH3FuQzm6WS6LLN0bq9R/qzA +O3CUgrw2GJfB+mDjH5+NdgM4cG182DjbaaqgGRQTDbbHFEBxIQHWpD3HPFUKruz8 +VmVrGQo2zRokW/ZuO9bN6zRl0iz3kGEFiV+wGJc1Rx+/HiOt/vX0mu63k3Gx19QQ +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmq0nitHFfMxYtPtCzV22AE +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUikKCfZfG5oV8DWImIHip5MshUySi7lDaZb63pdnAbPrhvWTvaNXMG/2J1IdORcGPQ= +32pdC9DD05OE2l0oXazDFFXAy1MI/uPhakkzCdBdnMA= +4+wKB+Tel1iow8au3cOIJaTLjj3pysITCcJJ7ec43Sg8lqI/J7o8CtRAAtYI0AOu3JqoT8kI2t89ug8sZ1eCiQ== +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOHsyhzYJSgj0XYfDyZD/qpmTBlCMqtpJ0emRNzsjaERyA== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +f+rqXRH5RpetwpbV8cB3LNb4H/TF90wkaKs48d1M7L+avSymddn7mKYz0NItjsSS +Z8UsPk1Q7HtwjRd4g01ryw== +6Jpks4lpI/jb7zQY9ZwZK1zkFQHFLO1SftfqDQPgq1EBsFoAqyniZa+4+xJKzZfT +Z8UsPk1Q7HtwjRd4g01ryw== +IOQ7R1QbtHtN/omAE4BWfAjZgPJASxuTDruVD3whaNc= +b4OJVZe8QyIpjuTpKXDL9A== +wlLHv6kT3Q/RmtMBN4nDAQ8YSs8Zt0gDBRyWHhM8ed+GGdWzPmL+jZ6S+F5YV+22 +VmVrGQo2zRokW/ZuO9bN6xjRZ/jgghl2/5XRKMIuEuFfgv1283QcodM/1ZOCBV+E +VmVrGQo2zRokW/ZuO9bN6x+Y6lBOlB/5zr7yqielcHbAt1j/vhhHLiR0XLFVJ4MYIB7qEVFJCIX6M64zazFiQeEMWR6HA8xoXyo6nAZJhlk= +VmVrGQo2zRokW/ZuO9bN6zKW75ke8QGqFd99GlEdhOmQAXdMhXbfkO0SVCSKXZhAgrRq5E4FlbZVLyjCi8XIzw== +VmVrGQo2zRokW/ZuO9bN6/VA0tcNP0B0yWPLZItZlkbpytNLRqAe/ddM0lgyhD/+AVdhq9Y31ZAG+aChEtUpGjK+qk8XXC1AHcYIovJNZ+PRowAmFi0HJrFUfIQN6E7Q +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpXsF9xMLoel3FaVnpw+fNuSwrGC7RSUXTIrW2j2yH61A= +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +L0eUthVnpkGsmKFAX6d+uK/AWodUm3mcxHH2qymBAgItW7Ziboyd2OX6uMRxQUgs +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uK/AWodUm3mcxHH2qymBAgItW7Ziboyd2OX6uMRxQUgs +1u+XjG/2+GSQRv6EzCaWRQ== +69Uik37T733MFTHEnugne9sIK0q/BrAEinbmIsH25IyzJWj8GWaHG6Tu8rx0i8YI +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIluXzbJqLPwB2hMZiB6idlsw= +vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg= +ho/Q+jrWDtBeg9J9ZTnKi7Z8oMuCLRtnjC35Cw+4aVDyKydByU+COt6lumOoVZyj +1xJ0clp+39cOWW64WGs9Tw== +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +avTFp0LCGrKbHzgXWCq/pYahaBNfx164yVsSAVTJDN4lWK0zoj6Eh3CajVgk0hPL +avTFp0LCGrKbHzgXWCq/pSoIEj7PXeiBI2BkNQsojArLvnQoeeQAiRG4S9Ely2YX +c0jePRxtTVZYop75Q5JCFsMjGu62TZDSljeQjMtCShNsz0Y9SFIv08dPVdTjI95b +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW7Akukb+j0lqloIv/j7uSM4oazJqFVNfgaZWfRnkezm/n/qRAFeZXm0pd4Y650TvY= +TzinVBSdupkFZuXDgmwYfiSs5GTHKq13dTr2kdVzFPy0Jh42rAak6FUbANl5whZO +W7OoNBP/IWbYzUOvpg/slY2NVhp515LKHRWJIoHB+V62OipmiB60YwRsLUoQ2Yhe +ojv9MyHa70Aio7SDrWcjwQ0VSHrTkMcwddE36Wz7lCz16K6ytw4GpaCto2OYzTO/ +IfOrW6dR51K0HWsQz64KwMZLSub5xEzT/cQoJpZiUus= +MzOj4ZiBOJDNVP8vi/lFQIcd3YW2BaDb4kSPJzNVOdlx1Fa3HoeBVOYpiRWE7XX1 +PRG/YRzXVD8iVR3bzEcUno4Z8XHjrzDgKJNn9oJou7lkrRw3h7on47id8HDtKTRy +j7S8zldDFIkYlZuEh7uh1S+jR/V5xbpfKOdCgELD/V8= +yqOVJCkAGOhqcQYBOo7yqReCj9ixiaIB5tdlZ21DAUB9HlVyjl18y9Mfwf2BKrxO1mdZmBSOas0Z9YsNQ75XTg== +NYuttixIfaRvF0QtYZhNf933obSmTd0Sfv1RLv5CWew= +jg/rCd1ltkosbKN5ZG2fjyfZDh9bj6LoXpZWnO+Jm3I= +kTToPYSGc065HLDqbjV0HFp+qk2oCg3rcMPzGznvj4s= +UsNFlHPXrsQOqjn4RzHaA2d+Dsggsz8fegt1C0el50M= +m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw== +k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wAfHx0c0egKY2YtT74Sqa68k3g5TvkiAzu1NRNlkcoBZw== +rtgmVFvoY2d3wTFk50ruC6zmYmoALMFLQOc+xc+vXmJzN60uRcu12WAXC28NrZ2x +wlLHv6kT3Q/RmtMBN4nDAesM/ZGOfx7RLjx/t+u4GQE= +VmVrGQo2zRokW/ZuO9bN692Dxe6/pJb49NcEGwdYFHp1ykWA3uXz8Dyrp2BHgNzY +VmVrGQo2zRokW/ZuO9bN65W0SAM+Lz+ixIA1yfBIlyIfbjLiw8kUEWgRJErCtT/B +VmVrGQo2zRokW/ZuO9bN63sbeay11c+Azj5VEIm5hpE6JlT9cIzf6HYvRvtfkHtUzfa7mLb2YRp06Fq3uXR7IgrEOFQK+h9IpdXLW6HxD8Q= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN68NGdAuCULKcdnQ9QIPhYF2d+4WoU/gSRVS6rllapIH/JcXSlRMgq/Lahda1FjioQA== +VmVrGQo2zRokW/ZuO9bN6/D3pD6pMXGNmGG8sGEgUx6XUzf02GbZTCaXui9nRWWO8tkj2O9evUoVTjlZbcp3bw== +VmVrGQo2zRokW/ZuO9bN656ysh/dA/L9/oKKFnGsliQ= +VmVrGQo2zRokW/ZuO9bN62VJEYtP1Byl1VdEwj5br8q1Cxt1YWn0/LGOVI2rmkKmLTqDuNfg3z4w6nFv1fa0OQ6VY4XRiSyXGr0pXrL72no= +VmVrGQo2zRokW/ZuO9bN682I1WfoM5O31Eu4y7eErmS1UbchPX+RbJH8okRw6uaf +VmVrGQo2zRokW/ZuO9bN6wGhkWn/nbtYgnRrfABp3Wx1AshDUYpYVsgVW1JY2KGZFsqdt6fYlLewntOhfc3+yQ== +VmVrGQo2zRokW/ZuO9bN61QRx6pmjScEC6Mg8X6jMiYZxM1o8NnnHqaeAW/oXr0H2CGH5XEtUznATSrKZ2lZEQ== +VmVrGQo2zRokW/ZuO9bN69ytvi1kzpknGdq50rt6Uk+ubFehg+T5L/Wn9jlHITZARQHXl2+Us7ChTWXRkBLidjmBIhghC83F2q0QovPWPAU= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+WmmMTtHxp83MPGxzXMHzRsy/BAZUo3PX2S6H3yS5eG +VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKB/zu4YnIm31wuOcKLBoPXS +VmVrGQo2zRokW/ZuO9bN63uBz94NTvSB4ZlhYbDfvddgnVY5f5JKOORH7Tw2EYEWgIaiQrzxYa4sTWMTgESGdzp52DXDe+CM2icfFsZP8BQ= +VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw= +VmVrGQo2zRokW/ZuO9bN61tUoNETw2Gi1D/Pq4qmNf5zhx/EQVRufI8LJkPSSi0Ih/xPpxJTJaW6aikR8po+Gw== +VmVrGQo2zRokW/ZuO9bN60TADvNmgaySXS1m1WJNuPeBE1HygRWbriOlU7U2Ssu3klRVzuCKqRzIyX8uoMGm2Q== +VmVrGQo2zRokW/ZuO9bN6+NxpNK9XYUauw2abZQW3LIA6UmYzalir4YjKFLEIVPk8tEmxjCF+UBrUqEASEZ7LQ== +VmVrGQo2zRokW/ZuO9bN69V5vRMZphCh9ZgEve1d0/wJCCaWNPSysYTVRsjVkHV1kaWCUu2teK5jcDOFuBzPQg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62Ywq26Utqu0XTnaZ+HQKopdamM5KbwMRVVnvfRPuEM1 +VmVrGQo2zRokW/ZuO9bN69qq0Yi+mSeaf9labGycI4pPobf7Rk2EV5UV2RidC6jX +VmVrGQo2zRokW/ZuO9bN63BfTcQ4tGBocvZdkD6/kO+b2BWi71GtoR82tq7ZzboaxwEKuBjLI8hR7w2Vz8MwvmfTeIGeWGGODE9Xm7plcChd9CESFtAhCbsVbUZfRATe +VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKD5/dQ70v3ovug8BVS13b+a04fDuz4GqTVSudpfL0PZ3Q== +VmVrGQo2zRokW/ZuO9bN6/JsORMmbxegVkakcQYSuOtPUInnHZyjGgz46mtOv7qSxkpfnDglI1Zm24CHtbp9uw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknACUIF0SHq3Wk7UPJPdvLnBytT0bs3KMa3b9ztxfxoAOuFAPEC6llaTPvEl5nkxFo= +VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw= +VmVrGQo2zRokW/ZuO9bN61tUoNETw2Gi1D/Pq4qmNf5zhx/EQVRufI8LJkPSSi0Ih/xPpxJTJaW6aikR8po+Gw== +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6/zCWmOuloAo6ldSTiaAbum79T3ZyCN2uTGYeWaRk8ZDw== +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6996q3aNX+Q/j+AZP8Qvo1SsqECC6pGPNuHt7GBg6OuU7Z1kO5CyxL0yZW1h1ERJGY= +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH69ya1Zc1M+KUYAJsz6AJ8F9YFPnDyZrQbgW0mxsmgLccg== +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH69tjxVx99Q6mXAvsEky2j6E +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6+u5zuOiB74Y40ABQeHY7iFbajQdIlVw8YH0S9x9fm/0gGxi5N3i0IL4ZOQgrP8+OX40wVL+NzPVrUtKnd1s0s9 +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH693/pJPvxX08JK6/fU1NpPhvWQO/SYGDQsMBTlZKO7NUA== +VmVrGQo2zRokW/ZuO9bN65UApnVIJ5/vPXwprQ1J61CB61rainG+jx/I7BmKhaXNbQAK6argkLKn8aYOSqMyZg== +VmVrGQo2zRokW/ZuO9bN6+NxpNK9XYUauw2abZQW3LIA6UmYzalir4YjKFLEIVPk8tEmxjCF+UBrUqEASEZ7LQ== +VmVrGQo2zRokW/ZuO9bN64RgViN5TsYlk5QzRuGR7Q/OP4DOltXpxjAehZXCVn5t +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +VmVrGQo2zRokW/ZuO9bN69wvhLovBGRbT42dQt1px1AK/igYwlUjVrS2AJALzGdr +VmVrGQo2zRokW/ZuO9bN6xfwiyo+r5sBtLLY/yC2JQuHAqyNIUWdS8mL+/TK0tDkDYK6sWUkPNuSjWFfywjVYg== +VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKB/zu4YnIm31wuOcKLBoPXS +VmVrGQo2zRokW/ZuO9bN63uBz94NTvSB4ZlhYbDfvddgnVY5f5JKOORH7Tw2EYEWfecK1ZYcY5aRkTVMuXTf4H0ornquPXZt6sNbcV9pvdE= +VmVrGQo2zRokW/ZuO9bN69fMKIOt3Fz+VdjDCrbU0LCl+bFI5Faw9xdIi1QHkSiy +VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw= +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLwPXg3jnnr3ZgO5MqTOchYl +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLymwotzxdwFuDwNOf7s1SnK +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLxqHw0Wm+3Oz89atXNvpO+3MXp3Qtmegk6NHmWrFwnYkOvDHfuz5NaWnAG6esuIjESLWMw8mumKWR+OGLFQatxn +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLyUNjqRPrStY0n7cNwmxO8c2/xYLBixqHfcg+3JdMiYfw== +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLx3KKJ5e9ptEgMTxJyuDzDUvQprgIXqnEOJyY8Wjzx/7g== +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLzg8EOHacHSOV2zn2Ehx3MZFJJGKychYjDMMiIIfaMNUg== +VmVrGQo2zRokW/ZuO9bN6zSaI2hF+nruhyzPFkleUq8kEqJLrAmChIOBKZkxcqwkGxR20IThZlGA1Dw2fFbmvg== +VmVrGQo2zRokW/ZuO9bN61yX1Dmgg5/3/nXVEduWvGRjAxUjUGE7S9oN2liQJclq +1u+XjG/2+GSQRv6EzCaWRQ== +M8WJLh8/G3HKYOcF0o/j+ZydjE/y4s9kCBsZH9Ydiys= +wlLHv6kT3Q/RmtMBN4nDAR8Bo0ERF5FTL0peq/6tsQxyyboaug1HqBM4U72iapnH +VmVrGQo2zRokW/ZuO9bN68F9oo0dUBAgf3zfH+UjZ2vBnQt9FOIIe/Mpc+VcY0hPyb7Y4hQhLdZIwPaG+7+qSw== +VmVrGQo2zRokW/ZuO9bN6wO+YLo8joUjMSrMMYsU2gc= +VmVrGQo2zRokW/ZuO9bN6wxqWDjyux4j5kWMbM/YT04A7rFtRY3ZvKwNmal8z607JZ0N1ek63JQAbp1/rMmDdg== +VmVrGQo2zRokW/ZuO9bN66+oTs/ylSo2IwYwivOcQ5QBeGO3/qhUXmuWRmyY22oicnIskjhngFIDfDK5fq2/pg== +VmVrGQo2zRokW/ZuO9bN6wPyViMJrLgq6QE06AJQ/mAxMxhT6z5+FEPokBQMNnem +VmVrGQo2zRokW/ZuO9bN61Ayy1rssK205MlM60bu6RKTG0Yfp2Lha8/dhaOOY7W18jbXMCbohsZ+i3OCAo0UDg== +VmVrGQo2zRokW/ZuO9bN63oDITMAygWGYYDyry55YDO/Rs3iFoOYCQPRB0Vo6QHG +VmVrGQo2zRokW/ZuO9bN65wPRQBNUUIF+K895b5fOrPxf0bXsfEkNdHCrolD4R0g +VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE= +VmVrGQo2zRokW/ZuO9bN63D/Hc2z6pQlInT4BP7h+Iw= +VmVrGQo2zRokW/ZuO9bN6yUmZOZSHWWMW4cj+zuxv+kqaCT5BWcHH/D3f8p49LGM +zvzyAjCZxViDIIg5wIBcfv3Z+YCuz/oELxYEQudPAfA= +/jVx2Sfk8Nv4tQDKNVrh5YQDDOyw39n94XpeN+Fc18Kb/ocx6XA91E4DLjnGHUVOp/hG38KiBHnsJF7rC+FrGh3/Y7Yjh+e73kBNop061ts4ALDF136iqb3NjiK6eB/P +wlLHv6kT3Q/RmtMBN4nDARhCtgHyXaIoGDNPdzxanK1C9qqxbuBKdK1MCBW9e3yd +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN65v9BdTfHDX5h5JoIA/XwnnexMAistxqcEkO+FfI33OD8/CZEhqElXeJdGaAFQ+8dlBsrteF3JVNER2SAEya4ro= +VmVrGQo2zRokW/ZuO9bN60wy8tZH9NZmPUZU3ymx7Rp230YC0uhO/IEmXkv95OC+kYR59ne0kzYbTAnbjiDQU/tfbdzA12veXVBzv74WMwI= +VmVrGQo2zRokW/ZuO9bN6w1QwjDxqY23oBLfy+yRgv/pNEUynCImsZYJyFdRKiY3f9n/tdlMbWaxhorqdeT5IvbcJCzD8QfNSTYnlePbg2dOcteS5jjxFSQ3VLx5bqO0 +VmVrGQo2zRokW/ZuO9bN6xDmLuIA9/MQHFSh5NeXQIQ+yxAa1KwUsFahRjuiZ8Mht4CYtcVKfJe+264KUvItRwPdoTCmQExpQVyY7LXmw64= +VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbVpMoEipT3erObxJsWCoytEOGK/hbak+7Shcx1wGKtjvE9kvRNNO0FTZ9ViojU52Bs= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkdh44SEE/n2JvhNVaLLGFkAT0r5NcHasfhHQIuEbaS/w== +VmVrGQo2zRokW/ZuO9bN6yC3ZNj/jGmx0c5FxYA8mMYFovl3/pDr9xKbkD9+awkq +VmVrGQo2zRokW/ZuO9bN6y282QljYt9u/aACctfJxlfOZSOWj+ka4WVOhE7+gYPHIAVL1wqdRYXpc242KdGWJ/V8BbFe5Mk6POcVdzCcc/E= +VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbUT/1fI7DpxplEbSq3o7wy5 +VmVrGQo2zRokW/ZuO9bN6yC3ZNj/jGmx0c5FxYA8mMYFovl3/pDr9xKbkD9+awkq +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc= +l2FJPs4YkAmmok1ulDRuSA== +wlLHv6kT3Q/RmtMBN4nDAU4MoUw37WOeYAqXUx8nKXV08D/vyeLYo268Q6TQAkcy +VmVrGQo2zRokW/ZuO9bN68rwZzz6qT+BRwAM0w/VgNam6voCMhL/LyfmhkE+4WOq +1u+XjG/2+GSQRv6EzCaWRQ== +YabJCT93a3/IohXaIkr3oSIBZqGhFAEcGH7Ca/SaLLaG+fOp90QxSi85tpj3adUdyNOydRy2vjmM402XZpsX3+nkFnHf2XtWx5xSFzCScjM= +CAhn8dRUItEbEErp4w+lX6BuaW4lR2EIOU/Z+PBe2bh+we0izGB82w9NP/FQvC5X +1u+XjG/2+GSQRv6EzCaWRQ== +oSOaxL22S+/P+2LLtATJSAaArdQ7fQZ1yguM5QAidIUD9pZGaNnuSKZ/6l+iYdvR +Z8UsPk1Q7HtwjRd4g01ryw== +hpr9H7QXrORqNT7P49jOY2MKXv3knmxSuBVQsYMFcyo= +YThduOXrtrzATLnwayQWnd8n5YD62KOD6YsiYJw+RAk= +7JQQDTi0T2idhFjao4jEpWRxFagB+y/O50vW7a0bZYhQWe9QbLzaMInaqQiRp7EGliGMBr4A0vhy+GCOuLQR2g== +7JQQDTi0T2idhFjao4jEpVM5kDnYAXzeJH6AUYrQZAHIQ4Zi/CV9839Nkf/Sc8cE +h2Amc56dmaRo3inGZxN9N4t4UB6ZIYdgw3I8UrLsiK9g6KbQ85/a9qF4mH08k4ge +rErGIG0jHuEUhPzM/Cvn7eDAiXr3299blfSBm1Bfub0= +B3iNup9pdvLGx6CAo6KC6KE/v+ibyUyH8Yud04Lz810= +q4nc/jwATOMUyfSjLibfEenY5RFzfDAlBFkea5rX8Ic= +nhEO6jGYTLTxNd7tfL8haGb6dIE98SqR0mAOJokWbaA= +2gqFvhn8R1IfPGeV2rMswbHUHFXVUzGBLmhsu02wzqE= +1u+XjG/2+GSQRv6EzCaWRQ== +C3JSV38/w2nvM3I7TZ5+4epkOMkaWYvoOErk6ygROHs= +to+v3MAswOY2NqesdSm5E+gSyJU0y7Xs7zqrUefMUuCWwwmO2rJrPN7egFiRQVKU +vJtKpUSg5Ukl4nJbF6QjQJ7EE1taDQMnZThgUYKjS1A= +m64JmVQ8ZxnJzJGTbbSLFgYYjwLvqe5k1J852jWCJ20= +GDxnwLueKeYNT0/dIp4TaMZIrCOXt9Fadk7QEvmi3LY= +iZaIsa48RXfE+uf/yF/rQBFFBTbLDCVgmOpzzd9fNSoU5yqcis6eMa2AhGtSBk42 +8+kEPw2kbWRhVZANSTiFn91S4x0IfqnYB21Go0MrYHk= +ed2GXKkXmwU2EQk+6MUOb5wVZ0JLOm9I7uChKGZVytg= +ojcUvW+Z2ehEJ6yMJpmY+4qFMPDKWboTeo1Y4LE86Gs= +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +YSlit6erKXylrjSZnvyW8KJQ5WBrDhjuDH3szUt3rF4TtLL3u+2vhmfGxql7dElVlZ+FltHCrJLtURHR876Z+5QJwoIiDk96EuxXOkKMTdk= +DaK4moDemtv24P20mOaUzQ== +YSlit6erKXylrjSZnvyW8F3ElaLeR/Rlppv/IAE6pDs= +9zgnmC3+5N9XK5/NRaR4SOWz2y/YEwWVWSZltrwvNrSfE3PSPwQMDu0BXDhBm14C +1V2v8QerKOmubvSxgB4eTCxJBqBlZI1ptPijcUHCDz5sYoASyVMieGEeAAQXWrck +1D18KWr2hdVsBcdZx1OaPfHgcwFUa7nBQO7bfH5tm74XTDXVMzBwLoLCrGgXhe0X +fqq/Y0gPbKQeEXzqLGS0q/J+017f+uH388qCfSeGlh8= +o6QhOIN2Sc4SHELnst17uYigovIGLGw9Sk8MSoNStgLGPEqp/JNtpdjWEUUK4pQG +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +lkFGPJ6keZ1Jd10HSdN0i5ZJfFnQV/nGEGCTaGt06s58zYz056g6djTo00sC9scb +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIlhrCEedAYfpQnUktuEYoFlw= +vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg= +ho/Q+jrWDtBeg9J9ZTnKi7Z8oMuCLRtnjC35Cw+4aVDyKydByU+COt6lumOoVZyj +AWid6G8ypXqQe9XxPPYED6EG/d+wAZpLmHn10ofRnJm2zuVMuvU9qhZPIJsbg007g9BmQxGLRAPjJyKkcrVzW+hTwBZcS8FDXPlLD0f/q/4= +Z8UsPk1Q7HtwjRd4g01ryw== +avTFp0LCGrKbHzgXWCq/pYahaBNfx164yVsSAVTJDN4lWK0zoj6Eh3CajVgk0hPL +TzinVBSdupkFZuXDgmwYfiSs5GTHKq13dTr2kdVzFPy0Jh42rAak6FUbANl5whZO +W7OoNBP/IWbYzUOvpg/slY2NVhp515LKHRWJIoHB+V62OipmiB60YwRsLUoQ2Yhe +ojv9MyHa70Aio7SDrWcjwQ0VSHrTkMcwddE36Wz7lCz16K6ytw4GpaCto2OYzTO/ +IfOrW6dR51K0HWsQz64KwMZLSub5xEzT/cQoJpZiUus= +MzOj4ZiBOJDNVP8vi/lFQIcd3YW2BaDb4kSPJzNVOdlx1Fa3HoeBVOYpiRWE7XX1 +PRG/YRzXVD8iVR3bzEcUno4Z8XHjrzDgKJNn9oJou7lkrRw3h7on47id8HDtKTRy +j7S8zldDFIkYlZuEh7uh1S+jR/V5xbpfKOdCgELD/V8= +yqOVJCkAGOhqcQYBOo7yqReCj9ixiaIB5tdlZ21DAUB9HlVyjl18y9Mfwf2BKrxO1mdZmBSOas0Z9YsNQ75XTg== +c0jePRxtTVZYop75Q5JCFsMjGu62TZDSljeQjMtCShNsz0Y9SFIv08dPVdTjI95b +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmW7Akukb+j0lqloIv/j7uSM4oazJqFVNfgaZWfRnkezm/n/qRAFeZXm0pd4Y650TvY= +NYuttixIfaRvF0QtYZhNf933obSmTd0Sfv1RLv5CWew= +z7RR81hY6MAA2voH8Z2gcSMl4BzicmRRwwcx6alhVes= +jg/rCd1ltkosbKN5ZG2fjyfZDh9bj6LoXpZWnO+Jm3I= +m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s= +1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw== +k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wAfHx0c0egKY2YtT74Sqa68k3g5TvkiAzu1NRNlkcoBZw== +rtgmVFvoY2d3wTFk50ruC6zmYmoALMFLQOc+xc+vXmJzN60uRcu12WAXC28NrZ2x +wlLHv6kT3Q/RmtMBN4nDAesM/ZGOfx7RLjx/t+u4GQE= +VmVrGQo2zRokW/ZuO9bN692Dxe6/pJb49NcEGwdYFHp1ykWA3uXz8Dyrp2BHgNzY +VmVrGQo2zRokW/ZuO9bN69YWkPGheqxTevWr4koVetNO2nXtSSYd+VohbeMtpTJM +VmVrGQo2zRokW/ZuO9bN63sbeay11c+Azj5VEIm5hpFeadOrAd8WGDCLEKR35dm2edAnPxHas9j394knOwJ8NA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69nNx2wsmuj5rj8dMZU/ic5BLawNF/f6lthvaeVh/Ls+ +VmVrGQo2zRokW/ZuO9bN69Fm+oKgY4oZ96N2lUN0xCYIR65p/6S+rx79lvSqg2I47knqkaZTvrsnLR+tEwCTnA== +VmVrGQo2zRokW/ZuO9bN6+qkEKPDxNwYFnkXN8TuQuYkotp65nYQ0fa3zEFof2zYnFi/LEybvPyjh48Z1idmyjl0NtdcsjLBkBJ13a8Xf98= +VmVrGQo2zRokW/ZuO9bN6wm11lyKsRHgTY7hsLdIWagcAA4Fqj10A0nlHnCL31hRaLimdtsSr1rF3vfwAkIE9UNe1Oq6AC93jtsLEBybVC4= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69fMYEokQlwk/9w9fdzOny6b4sFM5OW1KAT1oeECeG7J +VmVrGQo2zRokW/ZuO9bN6x9kSHAfsYQbQ95eUk47zCSU++ESIivk4zGTm21DHKkHEqyRv+yZpHFD0LXHT4m58A== +VmVrGQo2zRokW/ZuO9bN68s9bgaf6E2UUkkykIzwZHpz3P+5wRaqeeTuq1ofqFvFKnCBTa5TbqmgBM/RmZJPNAUu69T5cyQoQbj9yc2u8n0= +VmVrGQo2zRokW/ZuO9bN65llTn0pe1t030hd6gBN+zBd3IDBl9SSuthuY26B8/Pu +VmVrGQo2zRokW/ZuO9bN69ytvi1kzpknGdq50rt6Uk+ubFehg+T5L/Wn9jlHITZARQHXl2+Us7ChTWXRkBLidjmBIhghC83F2q0QovPWPAU= +VmVrGQo2zRokW/ZuO9bN62HF7Hu1aUGvgMj3NxvOaP7lJ1t3oULkhfzDpf5obr4i +VmVrGQo2zRokW/ZuO9bN6yfJ0tQlmRWb/s+hHO4yegbY9OZJu7Pz1YpWO35g7fWo +VmVrGQo2zRokW/ZuO9bN63coKIwm0os5grqf/FNTOQs15MAmmbu+Svk4r/hr3Mwt +VmVrGQo2zRokW/ZuO9bN633IXjOdT/W54PqLLNSn1Vg= +VmVrGQo2zRokW/ZuO9bN60VxqaV005HoUzQg7og2uQzzkGvTFfQImHgCDcOTreTvBj0gdrkAAkZ1tFN5kmALjU7kVG7jii3XUUaGJzXU1Dm0brYcNqsnFgqAbRd+hLig734vVILUrNX+DvQbOCpPeCtyQ8YEDj2QHDc0gf1laMU= +VmVrGQo2zRokW/ZuO9bN6/JsORMmbxegVkakcQYSuOtPUInnHZyjGgz46mtOv7qSxkpfnDglI1Zm24CHtbp9uw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknACUIF0SHq3Wk7UPJPdvLnBytT0bs3KMa3b9ztxfxoAOuFAPEC6llaTPvEl5nkxFo= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH13yvcZl75di1EfS2yW7gx+JTVNJQF0HOMQVAegRfGv1eA== +VmVrGQo2zRokW/ZuO9bN64OaX2n92Ly7KbozWq7a7t04nFfOkcL8NsC84xQi78iQ +VmVrGQo2zRokW/ZuO9bN6+a8J14HUH3L5V5KblyMysRYdesKR+fGd24F6C9j1l7wwo/p5lCfK41p1tvTNJzD/Q== +VmVrGQo2zRokW/ZuO9bN6yZNwtKaS0KLSStVEVMnEqjMcCKBADGrWUZZOFSQE2G8 +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9bqZ+RlmOwSQwtD/IjfE4TEXKm1zXESIuOIjor2vta6yx+sUjFgCbNTyLF8DtvW/9/Y= +VmVrGQo2zRokW/ZuO9bN61EVRyU6dsJr2EqyMVPbztJnTc0M4smwvC4e37Qh7Ms8 +VmVrGQo2zRokW/ZuO9bN67E2Rg2DTyty5YPAV4Joqwwqw9NcbRGp5GK4uyK1s8dkEGH9RxcsHHlq/24VKap3dQ== +VmVrGQo2zRokW/ZuO9bN6xRDOkMGch8OU8UdKKuA98T78S/RtIEO+vPGquNUPYP8j7Hqi5x7cOT1rq7lCPTbFRrq+ZOiuhVZC6d2uE6b5Y4RMkjy5kdxgYDR8lyh08y6 +VmVrGQo2zRokW/ZuO9bN6x+CQDkpZHTSzVJB0vr6/owqo2rDPdhhBctC47HwoQJI +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12i/zuDq/sxwawfAUxLzsmyvkFQXYvSK+lgmw3Dfruf8oUMKRDLCW0YoRSFNGJnVRA= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmJ6/ykoozm7YfC70113tbsRK8vQxRXP6m1Lk5Pq4lJ71yrM2OyBQ2a20YT6ATnjGs= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehp008Hpwo+KIS41Bq+plw28g== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpCUt6sS2ZM7/SgZvw2/D7KN2T4F8mibCUg/Vu5XXI0q8= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehp369EMxzMKwrpZ1Fsc6Rzihs0R+CDz1UtQsB7s3e2X1Y= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12q9ECvLOWcUe9cfTPHyuxucjjrMsKMpYA6OLWCOLB2Qw== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +VmVrGQo2zRokW/ZuO9bN68uqYuUF7HS6ZsAigA5dAhhdPk0G+p/b9PgaumDuRt89 +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9bpaS2w5sSDIfS2hUtvDW/ZPwVwLAcDR+Kc98dntjxh559OJdKONTmZolY5AjSf4KMU= +VmVrGQo2zRokW/ZuO9bN61EVRyU6dsJr2EqyMVPbztJnTc0M4smwvC4e37Qh7Ms8 +VmVrGQo2zRokW/ZuO9bN67E2Rg2DTyty5YPAV4Joqwwqw9NcbRGp5GK4uyK1s8dkfYflZHE0nkkYN92Iz4DvtrB7pbXI8nJ1B+v8kBcTL9k= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH10h4UbnbgTDNlKt0H8bnNKHH19OWWvR1gRk3zXeKMJ+R8jqCu7/rIb/rNBOG+zFbIM= +VmVrGQo2zRokW/ZuO9bN6wRsqCOcYvLI1Bp/40+s8jeuGD+dib11yOYzha+J15yw +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH11DboSZV8b8syS3y20nGfXmJMrA3OSI5dV3PIVmDHiETg== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +VmVrGQo2zRokW/ZuO9bN66uuxBZGV0E5w7uGfD+hzU4AmPi0rGaOz7BvwaEoiXgK +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9brMUFxnvPbybqGRmv07gv34NbyJ44u8fMbC6Sq1JkAEaNjiyTMCoEHy6fKKN/tEOOE= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH112Cwgr1gup7jex1yHReVWxiyvsKVsK3znbe1ecdgFhbIZqxwirWCHagb7ADC5VmGM= +VmVrGQo2zRokW/ZuO9bN6yc8qBfgAsioQiqSI3s7WIFM5rHpAQSYfkhRNfXBk2JBPvIvjRzS10i3GMc0J2bxTrSvtMX11tg5EOzI+VjAy9E= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12W5piSXwXcBTxZsgj+fR5PRj+yKlsuMRhvQaQszRXFiA== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +VmVrGQo2zRokW/ZuO9bN69jndazZ1JZSETYUFSRjpTYuSd0gU4R+ef5NLXuDtL3Q +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN661ybCBlxEvwailf4eTD9bqkLLlR3IpZgSDiX5ChzlR6NnjTJlDwKlsxY/AdyE1faRiMQGjhE0oX8Ie0uAI0rvI= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH112Cwgr1gup7jex1yHReVWxP38Ud5r+2JgPLAafH7N4i6SpRZsaYMAGNiTp9HaQsd4= +VmVrGQo2zRokW/ZuO9bN6wRsqCOcYvLI1Bp/40+s8jeuGD+dib11yOYzha+J15yw +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH13ETFhTDjpNg4UjDJ/ZX/5m5v/P88Ser/GIltB+CyiPFA== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +SS3eUI6O4Am57+zsCenaFkHmh+pwM+FJ/cut0UtoxJ0= +zvzyAjCZxViDIIg5wIBcfv3Z+YCuz/oELxYEQudPAfA= +M8WJLh8/G3HKYOcF0o/j+UKQix7E5QZpJV0Tfc/XRq/AKN5kkYE+IMwFcAyVsz2M5wHJAN89+nJ1b7GSY68O6925BBXUxFwqdflokenOUZU= +wlLHv6kT3Q/RmtMBN4nDAZf8v50ttctcXvJzAQ4D3y4VerbRFUVUK+NjcpNFPsa+ +VmVrGQo2zRokW/ZuO9bN60s8cEnb0+qXY0vQh1/7elp8TJKn/o/Cr8ab/L4KpzVyx+zZfHPJlblcJVM2evPA8Q== +VmVrGQo2zRokW/ZuO9bN62wFNZfsNF2gO4y5vOS6dGtOhTIohM0/9iaTTdt4VAkwr3U4JR6xB2mGWrp5KaqxgOX+iIeHDUU+B09yhkuBbeQ= +VmVrGQo2zRokW/ZuO9bN6zJHGvDJmOJJYHHdXzByxKsyeKpya/o0dns6NEjvZ9FhBqfG56Ge8FxUmH1UPM8WYg== +VmVrGQo2zRokW/ZuO9bN641hlBSX1Vyx3lcgSR68G0pIVXm8+RXV4rlYHPfD1CE2Ifl6RiQnkn3hT82X+tXXDQim3jnyNRBLGfZDXhejLG8= +VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbVpMoEipT3erObxJsWCoytE96eId0MgNxxUF/+ybzG/f+gvdjS4IHHuGSQJeLB3dqc= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkdh44SEE/n2JvhNVaLLGFkAT0r5NcHasfhHQIuEbaS/w== +VmVrGQo2zRokW/ZuO9bN67WLIC8ccIPl/iWyXLvwqc6z89/fim1Zup64yhd9bw8m +l2FJPs4YkAmmok1ulDRuSA== +wlLHv6kT3Q/RmtMBN4nDATXcKKq3YicYjT+ilqwnePASEk6UJ4kb2FJ4sCzHQMkb +VmVrGQo2zRokW/ZuO9bN68rwZzz6qT+BRwAM0w/VgNam6voCMhL/LyfmhkE+4WOq +YabJCT93a3/IohXaIkr3oSIBZqGhFAEcGH7Ca/SaLLaG+fOp90QxSi85tpj3adUdyNOydRy2vjmM402XZpsX3+nkFnHf2XtWx5xSFzCScjM= +CAhn8dRUItEbEErp4w+lX6BuaW4lR2EIOU/Z+PBe2bh+we0izGB82w9NP/FQvC5X +1u+XjG/2+GSQRv6EzCaWRQ== +VEyEXJTNTVvV1TGW5V2yEMzjBDxvV9j1MdPjo5gCe0Q= +Z8UsPk1Q7HtwjRd4g01ryw== +dar6N0tUAb9rU8/aX1b3tI5lWSbcXQgKxiMzJeLasCmdJgT+u3YcH7fkhDzLb+1X +9BuKX7c+cBUFGH/xEQqKwDJwv6pCDrQbGrc/aEaAxEizXN1tDiWRXfYhvFOYGiCD +3AcPLYoMMn4rxWOUMA1wJdOmdYprhThTGxGyDnWq840= +Z8UsPk1Q7HtwjRd4g01ryw== +90/F6C3nHF3UrQ/Er80CwwMY0Oi3JHbGAYQNb/w2V49c1F+wL9hoGKNjAjwN6pWeeHbxvhz6JqsdWpYj5f3cjKeHXg+8Lul7hoesFngMXfE= +g9lRvj200ApJT2I/NDdmaNS2bs03/Yy1N4LL8JCPrSEGtYUIbFGqz+9Nvsx/WW8DAPlJEAOAh1mN1Q2H5srR9aN9BcyfgbXU677udPpvmJs= +AC/CEOsuoGTyXbh39EUlAxckdwNHSoPmK9UbpHaeRy5+9wXscQv0OinnRdatf4o5vPdsr8lUrlC4beVwlerQ34wo2xApt+NOGbWFjuh6p10OnBiFzXIsBBGS2CKsc8ur +HBkACpLXsHV9FQoDE2PMYwSntxSga34Rul8QLEjV96M= +wlLHv6kT3Q/RmtMBN4nDAeWjc9eUr6obACCnQsg7vueRPByWE2DayjQ0xFllv1tW +VmVrGQo2zRokW/ZuO9bN65VlXQyBOMicxNyTkfSbDVVHhvBq22DM+S7/byvVxhpE +VmVrGQo2zRokW/ZuO9bN6yititPhxMgpb/nuYcU2bQxlVrgi32/YGL7BfDcYBS6ZrhF8DKeSq4/xyWh1l402NQ== +VmVrGQo2zRokW/ZuO9bN60VWeNWzP5MtK6lHtmFWfiOlcDBQw592yq0YxYnQPyb2cY8haKBhRx3ogIkozyLzGA== +VmVrGQo2zRokW/ZuO9bN61P2OVHnBm22rHl5VcwespdSVFvlh6jWmhNQGuWaiPCn +VmVrGQo2zRokW/ZuO9bN6/C3rj8+QHPPw24V/a4Ay7A= +VmVrGQo2zRokW/ZuO9bN61MqA9WsfKOg/tExmVwPpl7QnDnpNlS+CkeEkfTa9kINLQXgJYeYvwm13Q5goIzeLg== +VmVrGQo2zRokW/ZuO9bN632LGHZY9uic2w89Yj9RNWLrcjnK57CYRX2bXewhAHx7FVtzwI0ZOgY/T/V/6qaQlw== +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjakEuF8eNiysMFwArESQEwY +VmVrGQo2zRokW/ZuO9bN6yg8DfkEZba0oHLbAQabDs+4/JxGHZrKdgs6bMyBPLRMxwPtWLarWOkPgDMMkW2/RUdxOLVZk3+IQpAGkMdAX/s= +VmVrGQo2zRokW/ZuO9bN62dh+FzI+VGLu7ulc+ItFAbFXemabBEE/LbP+46UYm60 +1u+XjG/2+GSQRv6EzCaWRQ== +JEnsSsP7ogTJg1OXQ2YdoeqaOYYcnxFN3j3ktV+Kpps= +Z8UsPk1Q7HtwjRd4g01ryw== +8xWkPvEiREBWC3F3wdDkfHfKi35MXWHSVRhGdhf5R5T9XB9HNeEomkbAWdDKXpBP +Z8UsPk1Q7HtwjRd4g01ryw== +tsXNkoeF46BtVmFZ50qzKkC9C9rJjI8DYYqtZ5sTRNk= +b4OJVZe8QyIpjuTpKXDL9A== +cjZRE5RYY9TGg8THaNbKeimi7CKPJrP0YrozyGJJ6po8VG8Og36dzm29TqPtykmq3ShDIRxTK5G7NnirQ35JIwA7rlx3Yk1mP0YkpIDY3Ec= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN60gMmPI+qllGkwxOA9qbMmMB1m4JoBAOFYQ6YXkMo1VXquwSJ3IOKilGLS5oWPYO5OYeeloJXPkrrYqwK73A4D8= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN64/n5EL8V7CIqhp9FPzHEuQ= +W3A5Dt+8AxWSKsTYeXZoL1vLKDueV7P0Ut7HIASfZlp4D0IrBt2bwp8YHjJXXxXp +W3A5Dt+8AxWSKsTYeXZoLzB4TUiScS3D8yuLKBmm3AwWtYcxV6f46K0T0Egvi5WKkEtlHJveHdb2egjl/+1LDg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkknASfUzt1VKHSynjO/3GCHs8NYZ0lBfA3YkEhN9KGBNrviqTxIwFkI8b8tfQOhuu8e1qX/wTQkIHS4gFt6ErXtomVQ+AVI6XYbacH6pVLAEQ== +3ihEq+EcGY3EkKhgSscgbxPe6xF3X8bYDWyX0qt6BtkDpzyqs5cxV+fqGAxgw4n0F0ocWo63+pTEB2B02sSu997O+t/1VUp8dx53F4oaaq8= +1S1xjYS0s+Ph1PyvECx3QMdMFDZ5B3+BEK8kyCfI1RM= +VmVrGQo2zRokW/ZuO9bN67pnuem2Cn+Z/63FRYUcFkLoOrSR0T8WP6DifYG5JOXMPR4AFoHBPiKvg8hnpKw+PCDgxml+g92wfGPN8zZ2WJk= +VmVrGQo2zRokW/ZuO9bN64Ga+02XCL+A28NNybmlSnI= +VmVrGQo2zRokW/ZuO9bN65a8NYx2zqK2mFD4wjj1HmQ= +VmVrGQo2zRokW/ZuO9bN61jw9zMkfm0nvQi9tveiWV0= +VmVrGQo2zRokW/ZuO9bN6zYzNJFTIYiz/a08bSUNc5M= +VmVrGQo2zRokW/ZuO9bN6ztX+cJqy4yqv63JnfA/mqI= +VmVrGQo2zRokW/ZuO9bN60jLKcJEMj36X6NZvvwxF/XvqrmJF3W3RArdDHrkgZZ6 +VmVrGQo2zRokW/ZuO9bN6zskyREk1B9SpIIVpfE36zU= +VmVrGQo2zRokW/ZuO9bN63leNhHVk6qBYEqAElCrxOXVPHIpJ8U4k7mou4tbIR/7 +VmVrGQo2zRokW/ZuO9bN68tloVdndHDn6gJZRayJG+k= +VmVrGQo2zRokW/ZuO9bN617eR9WvYqaE7y/ppCzNL1570CCRJc668VdVufs+tRvC +VmVrGQo2zRokW/ZuO9bN6x59qEj9owkguOGj8QrHQSLi1Q/1C75rARTXu4TGt8DP +VmVrGQo2zRokW/ZuO9bN60Bq4PDCei1vxH18OtSGKCg= +VmVrGQo2zRokW/ZuO9bN67biU+BUR7/d7BkQj4RgpT8E7BfCa3N4KcxGo7ivT2Hg +YH231WTDnzQG3bFilBiqIg== +p9oVsBgcyiBMjDb8CcPOqP2sLQ/zn5Dzo5lCtrgxqTQ= +lOh2GtzHjjMM8E9J40AuOcPN2eZCahfvScF49QHmlNXYE5PQWq+WYPigt3dG9DzvOjcs8WVNkHQ6P1pZps2Ehg== +UbJuac0dxLiN+5ocS3w2vTpUyzdtCQ6EQJg8V3275Z+ztU2/bGujfta5s3IG3yXD +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= diff --git a/class/logsModel/panelModel.py b/class/logsModel/panelModel.py index 5af8d160..4f9c9986 100644 --- a/class/logsModel/panelModel.py +++ b/class/logsModel/panelModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: cjxin +# Author: cjxin #------------------------------------------------------------------- #------------------------------ @@ -198,7 +198,7 @@ class main(logsBase): try: if self.find_line_str(_line,search): #根据用户名查找 - if username and not re.search('-\s+({})\s+\('.format(username),_line): + if username and not re.search(r'-\s+({})\s+\('.format(username),_line): continue find_idx += 1 diff --git a/class/logsModel/siteModel.py b/class/logsModel/siteModel.py index cb7221a5..7013e17f 100644 --- a/class/logsModel/siteModel.py +++ b/class/logsModel/siteModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: cjxin +# Author: cjxin #------------------------------------------------------------------- #------------------------------ diff --git a/class/monitor.py b/class/monitor.py index 84c1c484..fb7e1117 100644 --- a/class/monitor.py +++ b/class/monitor.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: wzjie +# | Author: wzjie # +------------------------------------------------------------------- import os diff --git a/class/monitorModel/process_managementModel.py b/class/monitorModel/process_managementModel.py index 53a243c0..8cc08f5b 100644 --- a/class/monitorModel/process_managementModel.py +++ b/class/monitorModel/process_managementModel.py @@ -1,556 +1,556 @@ -import json -import os -import time -import traceback -import re -import psutil - -import public -from monitorModel.base import monitorBase -from pluginAuth import Plugin - - -class main(monitorBase): - setupPath = '/www/server' - __panel_path = '/www/server/panel/class/monitorModel' - __data_path = os.path.join(__panel_path, 'data') - cpu_old_path = os.path.join(__data_path, 'cpu_old.json') - disk_read_old_path = os.path.join(__data_path, 'disk_read_old.json') - disk_write_old_path = os.path.join(__data_path, 'disk_write_old.json') - old_net_path = os.path.join(__data_path, 'network_old.json') - old_disk_path = os.path.join(__data_path, 'disk_old.json') - old_site_path = os.path.join(__data_path, 'site_old.json') - nethogs_out = os.path.join(__data_path, 'process_flow.log') - - disk_write_new_info = {} - disk_write_old_info = {} - disk_read_new_info = {} - disk_read_old_info = {} - cpu_new_info = {} - old_disk_info = {} - new_disk_info = {} - cpu_old_info = {} - log_path = { - "mongod": "/www/server/mongodb/log/config.log", - "nginx": "/www/wwwlogs/nginx_error.log", - "httpd": "/www/wwwlogs/nginx_error.log", - "mysqld": "/www/server/data/mysql-slow.log", - } - - pids = None - __cpu_time = None - panel_pid = None - task_pid = None - processPs = { - 'bioset': '用于处理块设备上的I/O请求的进程', - 'BT-MonitorAgent': '面板程序的进程', - 'rngd': '一个熵守护的进程', - 'master': '用于管理和协调子进程的活动的进程', - 'irqbalance': '一个IRQ平衡守护的进程', - 'rhsmcertd': '主要用于管理Red Hat订阅证书,并维护系统的订阅状态的进程', - 'auditd': '是Linux审计系统中用户空间的一个组的进程', - 'chronyd': '调整内核中运行的系统时钟和时钟服务器同步的进程', - 'qmgr': 'PBS管理器的进程', - 'oneavd': '面板微步木马检测的进程', - 'postgres': 'PostgreSQL数据库的进程', - 'grep': '一个命令行工具的进程', - 'lsof': '一个命令行工具的进程', - 'containerd-shim-runc-v2': 'Docker容器的一个组件的进程', - 'pickup': '用于监听Unix域套接字的进程', - 'cleanup': '邮件传输代理(MTA)中的一个组件的进程', - 'trivial-rewrite': '邮件传输代理(MTA)中的一个组件的进程', - 'containerd': 'docker依赖服务的进程', - 'redis-server': 'redis服务的进程', - 'rcu_sched': 'linux系统rcu机制服务的进程', - 'jsvc': '面板tomcat服务的进程', - 'oneav': '面板微步木马检测的进程', - 'mysqld': 'MySQL服务的进程', - 'php-fpm': 'PHP的子进程', - 'php-cgi': 'PHP-CGI的进程', - 'nginx': 'Nginx服务的进程', - 'httpd': 'Apache服务的进程', - 'sshd': 'SSH服务的进程', - 'pure-ftpd': 'FTP服务的进程', - 'sftp-server': 'SFTP服务的进程', - 'mysqld_safe': 'MySQL服务的进程', - 'firewalld': '防火墙服务的进程', - 'BT-Panel': '宝塔面板-主的进程', - 'BT-Task': '宝塔面板-后台任务的进程', - 'NetworkManager': '网络管理服务的进程', - 'svlogd': '日志守护的进程', - 'memcached': 'Memcached缓存器的进程', - 'gunicorn': "宝塔面板的进程", - "BTPanel": '宝塔面板的进程', - 'baota_coll': "堡塔云控-主控端的进程", - 'baota_client': "堡塔云控-被控端的进程", - 'node': 'Node.js程序的进程', - 'supervisord': 'Supervisor的进程', - 'rsyslogd': 'rsyslog日志服务的进程', - 'crond': '计划任务服务的进程', - 'cron': '计划任务服务的进程', - 'rsync': 'rsync文件同步的进程', - 'ntpd': '网络时间同步服务的进程', - 'rpc.mountd': 'NFS网络文件系统挂载服务的进程', - 'sendmail': 'sendmail邮件服务的进程', - 'postfix': 'postfix邮件服务的进程', - 'npm': 'Node.js NPM管理器的进程', - 'PM2': 'Node.js PM2进程管理器的进程', - 'htop': 'htop进程监控软件的进程', - 'btpython': '宝塔面板-独立Python环境的进程', - 'btappmanagerd': '宝塔应用管理器插件的进程', - 'dockerd': 'Docker容器管理器的进程', - 'docker-proxy': 'Docker容器管理器的进程', - 'docker-registry': 'Docker容器管理器的进程', - 'docker-distribution': 'Docker容器管理器的进程', - 'docker-network': 'Docker容器管理器的进程', - 'docker-volume': 'Docker容器管理器的进程', - 'docker-swarm': 'Docker容器管理器的进程', - 'docker-systemd': 'Docker容器管理器的进程', - 'docker-containerd': 'Docker容器管理器的进程', - 'docker-containerd-shim': 'Docker容器管理器的进程', - 'docker-runc': 'Docker容器管理器的进程', - 'docker-init': 'Docker容器管理器的进程', - 'docker-init-systemd': 'Docker容器管理器的进程', - 'docker-init-upstart': 'Docker容器管理器的进程', - 'docker-init-sysvinit': 'Docker容器管理器的进程', - 'docker-init-openrc': 'Docker容器管理器的进程', - 'docker-init-runit': 'Docker容器管理器的进程', - 'docker-init-systemd-resolved': 'Docker容器管理器的进程', - 'rpcbind': 'NFS网络文件系统服务的进程', - 'dbus-daemon': 'D-Bus消息总线守护的进程', - 'systemd-logind': '登录管理器的进程', - 'systemd-journald': 'Systemd日志管理服务的进程', - 'systemd-udevd': '系统设备管理服务的进程', - 'systemd-timedated': '系统时间日期服务的进程', - 'systemd-timesyncd': '系统时间同步服务的进程', - 'systemd-resolved': '系统DNS解析服务的进程', - 'systemd-hostnamed': '系统主机名服务的进程', - 'systemd-networkd': '系统网络管理服务的进程', - 'systemd-resolvconf': '系统DNS解析服务的进程', - 'systemd-local-resolv': '系统DNS解析服务的进程', - 'systemd-sysctl': '系统系统参数服务的进程', - 'systemd-modules-load': '系统模块加载服务的进程', - 'systemd-modules-restore': '系统模块恢复服务的进程', - 'agetty': 'TTY登陆验证程序的进程', - 'sendmail-mta': 'MTA邮件传送代理的进程', - '(sd-pam)': '可插入认证模块的进程', - 'polkitd': '授权管理服务的进程', - 'mongod': 'MongoDB数据库服务的进程', - 'mongodb': 'MongoDB数据库服务的进程', - 'mongodb-mms-monitor': 'MongoDB数据库服务的进程', - 'mongodb-mms-backup': 'MongoDB数据库服务的进程', - 'mongodb-mms-restore': 'MongoDB数据库服务的进程', - 'mongodb-mms-agent': 'MongoDB数据库服务的进程', - 'mongodb-mms-analytics': 'MongoDB数据库服务的进程', - 'mongodb-mms-tools': 'MongoDB数据库服务的进程', - 'mongodb-mms-backup-agent': 'MongoDB数据库服务的进程', - 'mongodb-mms-backup-tools': 'MongoDB数据库服务的进程', - 'mongodb-mms-restore-agent': 'MongoDB数据库服务的进程', - 'mongodb-mms-restore-tools': 'MongoDB数据库服务的进程', - 'mongodb-mms-analytics-agent': 'MongoDB数据库服务的进程', - 'mongodb-mms-analytics-tools': 'MongoDB数据库服务的进程', - 'dhclient': 'DHCP协议客户端的进程', - 'dhcpcd': 'DHCP协议客户端的进程', - 'dhcpd': 'DHCP服务器的进程', - 'isc-dhcp-server': 'DHCP服务器的进程', - 'isc-dhcp-server6': 'DHCP服务器的进程', - 'dhcp6c': 'DHCP服务器的进程', - 'dhcpcd': 'DHCP服务器的进程', - 'dhcpd': 'DHCP服务器的进程', - 'avahi-daemon': 'Zeroconf守护的进程', - 'login': '登录的进程', - 'systemd': '系统管理服务的进程', - 'systemd-sysv': '系统管理服务的进程', - 'systemd-journal-gateway': '系统管理服务的进程', - 'systemd-journal-remote': '系统管理服务的进程', - 'systemd-journal-upload': '系统管理服务的进程', - 'systemd-networkd': '系统网络管理服务的进程', - 'rpc.idmapd': 'NFS网络文件系统相关服务的进程', - 'cupsd': '打印服务的进程', - 'cups-browsed': '打印服务的进程', - 'sh': 'shell的进程', - 'php': 'PHP CLI模式的进程', - 'blkmapd': 'NFS映射服务的进程', - 'lsyncd': '文件同步服务的进程', - 'sleep': '延迟的进程', - } - - def __init__(self): - if not os.path.isdir(self.__data_path): - os.makedirs(self.__data_path, 384) - plugin_obj = Plugin(False) - plugin_list = plugin_obj.get_plugin_list() - public.print_log(plugin_list['ltd']) - ped = int(plugin_list['ltd']) > time.time() - if ped: - self.add_nethogs_task() - - def specific_resource_load_type(self, get): - """ - 查询具体资源类型负载 - :param get: None - :return: 资源占用字典 - """ - try: - plugin_obj = Plugin(False) - plugin_list = plugin_obj.get_plugin_list() - public.print_log(plugin_list['ltd']) - ped = int(plugin_list['ltd']) > time.time() - if not ped: return {'status': False, 'msg': "该功能为企业版专享!"} - infos = {} - load_avg = os.getloadavg() - infos['info'] = {} - infos['info']['physical_cpu'] = psutil.cpu_count(logical=False) - infos['info']['logical_cpu'] = psutil.cpu_count(logical=True) - c_tmp = public.readFile('/proc/cpuinfo') - d_tmp = re.findall("physical id.+", c_tmp) - cpuW = len(set(d_tmp)) - infos['info']['cpu_name'] = public.getCpuType() + " * {}".format(cpuW) - infos['info']['num_phys_cores'] = cpuW - infos['info']['load_avg'] = {"1": load_avg[0], "5": load_avg[1], "15": load_avg[2]} - infos['info']['active_processes'] = len( - [p for p in psutil.process_iter() if p.status() == psutil.STATUS_RUNNING]) - infos['info']['total_processes'] = len(psutil.pids()) - cpu_percent = self.get_process_cpu(get) - cpu_proc = cpu_percent["process_list"] - mem = self.get_mem_info() - infos['CPU_percentage_of_load'] = cpu_percent["info"]["cpu"] - infos['percentage_of_memory_usage'] = round(mem['memRealUsed'] / mem['memTotal'] * 100, 2) - infos['CPU_high_occupancy_software_list'] = {} - for i in range(5): - try: - infos['CPU_high_occupancy_software_list'][i] = {"name": cpu_proc[i]['name'], - 'pid': cpu_proc[i]['pid'], - 'cpu_percent': cpu_proc[i]['cpu_percent'], - 'proc_survive': cpu_proc[i]['proc_survive']} - except: - pass - b = [] - for i, j in infos['CPU_high_occupancy_software_list'].items(): - cpu_info = {'proc_name': infos['CPU_high_occupancy_software_list'][i]['name'], - 'pid': infos['CPU_high_occupancy_software_list'][i]['pid'], - 'cpu_percent': str(infos['CPU_high_occupancy_software_list'][i]['cpu_percent']) + "%"} - cpu_info['explain'], cpu_info['num_threads'], cpu_info['exe_path'], cpu_info['cwd_path'], cpu_info[ - 'important'], cpu_info['proc_survive'] = self.__process_analysis( - infos['CPU_high_occupancy_software_list'][i]['pid']) - b.append(cpu_info) - infos['CPU_high_occupancy_software_list'] = b - infos["memory_high_occupancy_software_list"] = self.__use_mem_list() - c = [] - for i, j in infos["memory_high_occupancy_software_list"].items(): - mem_info = {'proc_name': i, 'pid': infos['memory_high_occupancy_software_list'][i]['pid'], - "memory_usage": infos["memory_high_occupancy_software_list"][i]['memory_usage']} - mem_info['explain'], mem_info['num_threads'], mem_info['exe_path'], mem_info['cwd_path'], mem_info[ - 'important'], mem_info['proc_survive'] = self.__process_analysis( - infos["memory_high_occupancy_software_list"][i]['pid']) - c.append(mem_info) - infos["memory_high_occupancy_software_list"] = c - return infos - except: - public.print_log(traceback.format_exc()) - - # 按cpu资源获取进程列表 - def get_process_cpu(self, get): - self.pids = psutil.pids() - process_list = [] - if type(self.cpu_new_info) != dict: self.cpu_new_info = {} - self.cpu_new_info['cpu_time'] = self.get_cpu_time() - self.cpu_new_info['time'] = time.time() - - if 'sort' not in get: get.sort = 'cpu_percent' - get.reverse = bool(int(get.reverse)) if 'reverse' in get else True - info = {} - info['activity'] = 0 - info['cpu'] = 0.00 - status_ps = {'sleeping': '睡眠', 'running': '活动'} - limit = 1000 - for pid in self.pids: - tmp = {} - try: - p = psutil.Process(pid) - except: - continue - with p.oneshot(): - p_cpus = p.cpu_times() - p_state = p.status() - if p_state == 'running': info['activity'] += 1 - if p_state in status_ps: - p_state = status_ps[p_state] - else: - continue - tmp['exe'] = p.exe() - timestamp = time.time() - p.create_time() - time_info = {} - time_info["天"] = int(timestamp // (24 * 3600)) - time_info["小时"] = int((timestamp - time_info['天'] * 24 * 3600) // 3600) - time_info["分钟"] = int((timestamp - time_info['天'] * 24 * 3600 - time_info['小时'] * 3600) // 60) - ll = [str(v) + k for k, v in time_info.items() if v != 0] - tmp['proc_survive'] = ''.join(ll) - tmp['name'] = p.name() - tmp['pid'] = pid - tmp['ppid'] = p.ppid() - # tmp['create_time'] = int(p.create_time()) - tmp['status'] = p_state - tmp['user'] = p.username() - tmp['cpu_percent'] = self.get_cpu_percent(str(pid), p_cpus, self.cpu_new_info['cpu_time']) - tmp['threads'] = p.num_threads() - tmp['ps'] = self.get_process_ps(tmp['name'], pid) - if tmp['cpu_percent'] > 100: tmp['cpu_percent'] = 0.1 - info['cpu'] += tmp['cpu_percent'] - process_list.append(tmp) - limit -= 1 - if limit <= 0: break - del p - del tmp - public.writeFile(self.cpu_old_path, json.dumps(self.cpu_new_info)) - # process_list = self.handle_process_list(process_list) - process_list = sorted(process_list, key=lambda x: x[get.sort], reverse=get.reverse) - info['load_average'] = self.get_load_average() - data = {} - data['process_list'] = process_list[:10] - info['cpu'] = round(info['cpu'], 2) - data['info'] = info - return data - - # 获取负载 - def get_load_average(self, get=None): - b = public.ExecShell("uptime")[0].replace(',', '') - c = b.split() - data = {} - data['1'] = float(c[-3]) - data['5'] = float(c[-2]) - data['15'] = float(c[-1]) - return data - - # 获取总的cpu时间 - def get_cpu_time(self, get=None): - if self.__cpu_time: return self.__cpu_time - self.__cpu_time = 0.00 - s = psutil.cpu_times() - self.__cpu_time = s.user + s.system + s.nice + s.idle - return self.__cpu_time - - # 获取进程cpu利用率 - def get_cpu_percent(self, pid, cpu_times, cpu_time): - self.get_cpu_old() - percent = 0.00 - process_cpu_time = self.get_process_cpu_time(cpu_times) - if not self.cpu_old_info: self.cpu_old_info = {} - if pid not in self.cpu_old_info: - self.cpu_new_info[pid] = {} - self.cpu_new_info[pid]['cpu_time'] = process_cpu_time - return percent - percent = round(100.00 * (process_cpu_time - self.cpu_old_info[pid]['cpu_time']) / ( - cpu_time - self.cpu_old_info['cpu_time']), 2) - self.cpu_new_info[pid] = {} - self.cpu_new_info[pid]['cpu_time'] = process_cpu_time - if percent > 0: return percent - return 0.00 - - # 获取信息,如果存在返回true,不存在读取gson后存在true:不存在flase - def get_cpu_old(self): - if self.cpu_old_info: return True - if not os.path.exists(self.cpu_old_path): return False - data = public.readFile(self.cpu_old_path) - if not data: return False - data = json.loads(data) - if not data: return False - self.cpu_old_info = data - del data - return True - - # 获取进程占用的cpu时间 - def get_process_cpu_time(self, cpu_times): - cpu_time = 0.00 - for s in cpu_times: cpu_time += s - return cpu_time - - def get_process_ps(self, name, pid): - if name in self.processPs: return self.processPs[name] - - # 增加使用nethogs收集进程流量定时任务 - def add_nethogs_task(self, get=None): - # self.add_process_white('nethogs') - import crontab - if public.M('crontab').where('name=?', u'[勿删]资源管理器-获取进程流量').count(): - return public.returnMsg(True, '定时任务已存在!') - - s_body = '''ps -ef | grep nethogs | grep -v grep | awk '{print $2}' | xargs kill 2>/dev/null -count=0 -while [ $count -lt 2 ] -do - count=$(($count+1)) - /usr/sbin/nethogs -t -a -d 2 -c 5 > %s 2>/dev/null - if [[ $count == 2 ]];then - exit - else - sleep 20 - fi -done''' % self.nethogs_out - - p = crontab.crontab() - args = { - "name": u'[勿删]资源管理器-获取进程流量', - "type": 'minute-n', - "where1": 5, - "hour": '', - "minute": '', - "week": '', - "sType": "toShell", - "sName": "", - "backupTo": "", - "save": '', - "sBody": s_body, - "urladdress": "undefined" - } - p.AddCrontab(args) - return public.returnMsg(True, '设置成功!') - - # 获取内存情况 - def get_mem_info(self, get=None): - mem = psutil.virtual_memory() - memInfo = {'memTotal': int(mem.total / 1024 / 1024), 'memFree': int(mem.free / 1024 / 1024), - 'memBuffers': int(mem.buffers / 1024 / 1024), 'memCached': int(mem.cached / 1024 / 1024)} - memInfo['memRealUsed'] = memInfo['memTotal'] - memInfo['memFree'] - memInfo['memBuffers'] - memInfo['memCached'] - return memInfo - - def __use_mem_list(self): - processes = [] - for proc in psutil.process_iter(): - try: - # 获取进程详细信息 - pinfo = proc.as_dict(attrs=['pid', 'name', 'memory_info']) - # 添加到进程列表 - processes.append(pinfo) - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - pass - processes = sorted(processes, key=lambda p: p['memory_info'].rss, reverse=True) - l = {} - mem_total = psutil.virtual_memory().total - for p in processes: - l[p['name']] = {'pid': p['pid'], - 'memory_usage': '%.2f' % (int(p['memory_info'].rss) / int(mem_total) * 100) + "%"} - if len(l) >= 5: - break - return l - - # 常用软件分析 - def __process_analysis(self, pid): - process = psutil.Process(pid) - important = 0 - explain = self.processPs.get(process.name(), - '未知程序的进程') - if 'BT-Panel' == process.name() or 'BT-Task' == process.name(): - important = 1 - num_threads = process.num_threads() - exe_path = process.exe() - cwd_path = process.cwd() - timestamp = time.time() - process.create_time() - time_info = {} - time_info["天"] = int(timestamp // (24 * 3600)) - time_info["小时"] = int((timestamp - time_info['天'] * 24 * 3600) // 3600) - time_info["分钟"] = int((timestamp - time_info['天'] * 24 * 3600 - time_info['小时'] * 3600) // 60) - ll = [str(v) + k for k, v in time_info.items() if v != 0] - pro_time = ''.join(ll) - if ''.join(ll) == '': - pro_time = '小于1分钟' - return explain, num_threads, exe_path, cwd_path, important, pro_time - - def kill_process_all(self, get): - pid = int(get.pid) - if pid < 30: return public.returnMsg(False, '不能结束系统关键进程!') - if pid not in psutil.pids(): return public.returnMsg(False, '指定进程不存在!') - p = psutil.Process(pid) - if self.is_panel_process(pid): return public.returnMsg(False, '不能结束面板服务进程') - p.kill() - return self.kill_process_tree_all(pid) - - # 结束进程树 - def kill_process_tree_all(self, pid): - if pid < 30: return public.returnMsg(True, '已结束此进程树!') - if self.is_panel_process(pid): return public.returnMsg(False, '不能结束面板服务进程') - try: - if pid not in psutil.pids(): public.returnMsg(True, '已结束此进程树!') - p = psutil.Process(pid) - ppid = p.ppid() - name = p.name() - p.kill() - public.ExecShell('pkill -9 ' + name) - if name.find('php-') != -1: - public.ExecShell("rm -f /tmp/php-cgi-*.sock") - elif name.find('mysql') != -1: - public.ExecShell("rm -f /tmp/mysql.sock") - elif name.find('mongod') != -1: - public.ExecShell("rm -f /tmp/mongod*.sock") - self.kill_process_lower(pid) - if ppid: return self.kill_process_all(ppid) - except: - pass - return public.returnMsg(True, '已结束此进程树!') - - def kill_process_lower(self, pid): - pids = psutil.pids() - for lpid in pids: - if lpid < 30: continue - if self.is_panel_process(lpid): continue - p = psutil.Process(lpid) - ppid = p.ppid() - if ppid == pid: - p.kill() - return self.kill_process_lower(lpid) - return True - - # 判断是否是面板进程 - def is_panel_process(self, pid): - if not self.panel_pid: - self.panel_pid = os.getpid() - if pid == self.panel_pid: return True - if not self.task_pid: - try: - self.task_pid = int( - public.ExecShell("ps aux | grep 'python task.py'|grep -v grep|head -n1|awk '{print $2}'")[0]) - except: - self.task_pid = -1 - if pid == self.task_pid: return True - return False - - def __get_number_of_processes(self): - import psutil - from collections import Counter - ll = [] - processes = psutil.process_iter() - process_names = [process.name() for process in processes] - process_item = Counter(process_names) - process_item = dict(sorted(process_item.items(), key=lambda item: item[1], reverse=True)[:5]) - for key, value in process_item.items(): - procs = {'proc_name': key, 'proc_description': '此进程的进程数有' + str(value) + '个,进程是{}'.format( - self.processPs.get(key, "未知进程"))} - ll.append(procs) - return ll - - def process_description(self, get): - try: - updatas = json.loads(get.information_collection) - data = json.loads(public.readFile('/www/server/panel/class/monitorModel/common_process.json')) - data.update(updatas) - public.writeFile('/www/server/panel/class/monitorModel/common_process.json', json.dumps(data)) - return public.returnMsg(True, "进程添加成功") - except: - return public.returnMsg(False, "进程添加失败") - - def universal(self, get): - method = { - "题目1": "遇到未知进程解决办法。", - "1.1": "观察进程可执行目录和运行目录,是否与BT、项目名、常用软件相关,若与项目或者系统相关的进程且占用资源不大可不管。", - "1.2": "去‘百度’上搜索进程名,查看进程的归属,以及是否有害。ps:https://www.baidu.com", - "1.3": "咨询项目开发人员,看此进程是否由部署的项目所创建,若是,可加入到常见进程列表中。", - "1.4": "实在判断不了进程的性质,可到宝塔论坛发帖求助.ps:https://www.bt.cn/bbs/portal.php", - "1.5": "对进程做出详细的判断后,无用且占用资源较高,可关闭该进程。", - "1.6": "若占用资源较多的是使用当中的软件或项目,则可以尝试适当的优化,比如mysql优化、适当限制php的并发等。", - "题目2": "内存,cpu使用率不高,但负载很高解决办法", - "2.1": "负载高低还与线程数量、IO使用率、服务器本身有联系,可查看线程数量以及磁盘使用情况进行综合判断", - "2.2": "若本身服务器的配置较低,可以适当的考虑升级服务器配置", - "2.3": "若是遭受到网络攻击,也可导致服务器的负载偏高,可以开启宝塔防火墙以及安全插件进行防护。", - "2.4": "若服务器使用的是云服务器,也可能是服务器商家限制,可以咨询一下服务器商家的客服。" - } - return method +piG6BsMF31u4R4iA6R4SRA== +dTLQ8Wr3wPn7w6pte1B1YA== +SbQQ5SqO9QrBwZ9ObpMYLw== +aB1r/Hl41eeDYHjvKNsDN+fwXnHZ5cKnjNQNkvt811Q= +wiYVtfO/yzajW1Tv5z7hyA== +bbgfjwWjzPNOxnsxTb5hzQ== +1u+XjG/2+GSQRv6EzCaWRQ== +XIfdJ79nObMM+vyAmKbTmw== +DjrnV2ugcXpCQCRtr1AhjGZ1uAagxBZY4p+tagUZ+g789PFU3G5RrAfrJSwrmwJC +VEQLjjua2Z0/nl0SqvlgpQZHKj35t4rRyTE0nOHE87Y= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +GC18aI/weGPndT9J1gQkOXLINKNA+KXP+Dhe1VxZyWA= +HuLSKhYFyga2gCsAbUvtZHLP0BAfxDGimJZciagBXbE= +i5Pjb3wFWjgWhO/Oy7348x0KCcyi6IVF8qqP7v32njhzG+8BdTBQyqlCg3CRdkP+hkP1ySPJM/rBG9E6T++rIg== +rdSLxsl+Jo+9wGF1wR0xFfkiBqqHhxwN5kl0Lj1eqZJZBJ0Un+czbpqSSOCnL00rgrs3FzTKbr1MIAbXu6UVGg== +0SQnak5isFUZ7qBrHG5nONf0/iIYxo+ossW+sIeJPVqc06D+NOpjaE6Uqq4RDZ6D+X1V5oSWDCNufz1Z5NsruQ== +QoXjlU/yrq3LjBi536E1lAtUbyzoaPGqmdMscHtc82Eqnx3xaK7ND//Aee6pjueUoSkSd7Kn/GTgusZOZkazWr735TNoBy8E2i+TeT5QpBw= +EnTcTKmPyFkaGN9DqDzaSceq8uDBrg212ADWAEQ4IZ7RrbWtCNgCxsKxhOwBdNTIiDKGNrYM1guBNGIg+GP4oH9ldbTHxPoem41Xx3gcVH4= +VdrjM3tESbDpF9uC89FIBP9Gd1akXGqOvVf2yxPgOSm6+6n45/Opv1Gr2Z6wLcSiweXQCxy9SU/Ps8OhjacAOibd0MvWgOEF/Qj8Hg1OpD4= +23L+yCPrnnJuqxnn47seC6mWE/7wPUK89o8BQ4gkIOBjwQKKbOoe6bUJw6g30fgZLJKIywqZgDEqKLNb6Wk4ZA== +ls1D8gXPZvKpYcbhjFq/angELE3042eHnGYIIdOc3EqVbEv8X3JJ8d4kjYJRVOzknDfBdJPlavOFcLuc1C3RlQ== +Ucs0nOwLNKLYeguD5Ho6We236+pzwCXfjIf8zCbNBPUj8Trs/3tdQqbzpyL2y/A3nGNz3pdXfnPIeWgvZPJdJCQ3oPUcIKLTYx+hNA8fP3A= +1u+XjG/2+GSQRv6EzCaWRQ== +PdlH+lu7jNu2OvYS8I8SIGv8UV7r+RCWsVIOF9/nLLA= +EnTcTKmPyFkaGN9DqDzaSeIxT+jPdlkzsjUgVGeo67M= +lfXDVmh5N4vkcCpP16rRNU+nS75ACFRPief37RRqVEQ= +QoXjlU/yrq3LjBi536E1lNTH28NopdOB61QQlHyGFcA= +UrqM+fZEnAnh676kBTiBqQ2F5qpH1BZ72+KLbvQGDMU= +zKCrIlbP/DDhVHiZ3fIxklV8gb38KK+GbBXLALoFwPQ= +VoVkMP9tW2A1crYS1JPcez/T5g9qr04wEneJMF3RRlA= +40d0Kjq9eFveZcVdlpFWHqsMLb6Cbgq596PvxkzyPPs= +KIZi5iGAZ1ERVsYyf9tRFc+B9Yz08eywa9GtzLEWwb0= +CCWp04qZQAG0xvBg/GxA1ac8M6aDoxCupSpEbINrd14SIsQMCX2yg35fZKfSZ45bUlwsnEKO6CphPTsCe870BQ== +ELPZs21xKKoOnovAKJosLe8y3drsV3LCJ9rBZ59KMXaSIKv2MS1cInuGvowyQR6/da+MfD6aJgPi0OIyjKl/TA== +0aJdVaqIprJqIF2ACVD434ZUgkgWJjA1UrDXUzIX7hLHfRqO7xfTfRdmPwLbMjnBsyI29qYoF8SnX188Ei+AEA== +cEDtw6uMb2/A6mWG+mGficxASjA1PsBLQqmyk6avgrA5NU4ovgWemvxpF4tbZuYyAvelvkrb1my6fx7rBEPUgA== +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +oKVaGzjnS0ibnrCRJ6VhOXjlPFbbLUbXwFxCuM1lBik= +x4aYGEXHUiyydN0TvGTj6vQYATZwfiFdhjHTFnA8Gao= +C/AADmj7KKNzSBTrmgjwHtkMmAZdvtiecPcfgdxsdp4= +Oonoanjo57cFaJw3N8v3+Re7+iV0uPfFwq4o6iaSCBM= +sTBwU4/6vbNIKaonWCsmPxBuizocf0lQLoMzCcsQdOE= +GQ8bP5FSB8U92LYKcbOqVOyusqkt7x3hnzxKCHCGHFuYPxYJaktAZ3ui01vGLpRoDf0dBr+MRE3SP4hUL7XxWSh/k6f2maS9LSwwdicF4yc= +6XekAdRuN93speBaqeGdSwFHDCPkTtGFh21Nhkd6RlbVsrd3+42TNXjDmQXYItc0eG3VUBTA0arBcuR6ynpbKw== +Ct7YEe25Op6jhkRit6HXSS9R9HrQIjqhCqSX+ee73JHfZ5J55br5lF3uB0DPPA1Y +hTUqYvcYvtmpKOewm481/MQZEgItO4vOyj5qmO8Is2I/FUdYUR2LRXEA0bvcem68tGf2rMuDkJVgN87keG95dDdbsSJwMmpyoBKp89kGpgU= +QP9mdDvW8SzUpgVgx/NNyykpcPN+PRmA76dtTDgOphdIcANFGc9Lc1H+IPwlHbnCF8ZWbhFJxGOC3NwCQqRKWA== +mtgwn5Yb9q2LR0oKaA1GDD6dKVcIfEsUGTnL1x7Se6F27uWBDZjB1zpIV4BB9SuvOACzeafKG/8RCnLDgL7hUDMaSrphxgNVLKIhSNMuEA4rIhdIKVhFskcIN6ZauI9DdHrx/ohao8XSb7Bms2iSWA== +/P08j/NQ3V/6NH/K2OPqHUeruiobFj0/uXohCUqtHCIqf67tmCphQozpUid3/pMXZJJZ4WV+sOtmU5DRKL34w5AK3PWk4OrqzNNs6S/4bmc= +DFIcnybg9Q4e8lvNk2V2upP6Hr7xhNXqIhviOYpVB3raB8ZoZWqooeaOFlPW97CvXdi5l6SG8hM8ae5YhafZMnviwbNiJgG36bYj9aMJvfHfuerZTUunqjWjKtVf1gxT +fBUMMACbLfIUiv22DBztC2X8HTxvBDwI2uJvKztXFjHgPgMkx1KLn/iS/Rz90Vtg +KpJ00HDVp573NWedwG3qjGqRAe30y85buGnJhVevfJm3oQkLHz3hrV90wpkkyGnTYy5MOm/pSRF/WrydQlXKCA== +NYEZAf0CPJOvudyjBgyMzPr9RZ7nBiZlHyvHZ6psZ4EgokJyAlzdjdnp920Q7b6kGI48rfekYgiiDo2uvYO3pA== ++wBlhY7T5u3USVjT9x1efKnQWlx/oGnzkkf6zFBt9Afs7O/ns5xfaaJBL+Zm0S0nHdqHmJuae5ZknLenRHyJxA== +vX3aw/VpCn2kdUdQ401ThgqbPZjOUtBDCyHHc0hFIKQ9KgriBJ9/gPZNfcuSLe7A2jFLkMQ0ox50NmmrzT9/nA== +Rwr5WRRWJc49eH5/ngaIBiWYeyNcMjokcXjDWWMdBpQrqGeet+DbXOd6PKMpitDQQvpS+fsW0LpUBhz3AC+l9VeeNNbYYP06g1AXgsuzzyk= +ZUOeDkg+GlcyKcw8Uhn9GhzBXyPqNYdX9wx0uRWoOFBRu7BPoEbD8g0jpM4NMA0W6fNiH+9CmstbuuqaxZjuXw== +ogK1joGXApEMwUMZDKuBPRM0Z0BG5Gd1D6LTT02x0YXw1YVPNRIHfaMphOIQmn7D9kr2yVIL0bSBQVkRyhGoKAnDYDm6Y3vMUpTTHyRhFAE= +kHKnlmRHLSoRZ3FlW7VxUDkE445yMx1gpDyqtMBOugd88lX0F6rketv+5EQ5rk/ua73CnzFhtiUf3HI6EwhucJ5x6HlVFX/lyIdLjt14Wsz5TohcQ1yNJJkHvVWTWiXV +Rwr5WRRWJc49eH5/ngaIBmd/jgM0o9xcXjNmkQ7ojlWHqQYbvRSksD+YYI3UqYt5arsC7JF9ywvKPiwi14GCzQ== +UymC8Fo8j5Vfm3c3mvSqVCadgEL91qmjAufL18gREC8zFOvraqdOTFkx4Q8QR8DaHyZKqWK/befuh7X9PMbVIw== +Cqu/R4uxTK+Rjxynhwj8CbkMvLquN/WmcJqBhmSX+Uav1ikgqpF9c2lfpNakMq4U1f6TY8HJd3+YcM7ncuENlw== +hsSLeDBq70w4vj6GUYkiYbh4J1qeIJqUq989eIhWYj85DR2PV0lMqymDGJmf2ZL0 +yoNfTnPxkT2ArtuDpRb26Xft0K/cNfiaz3RRuiM2MAI6zFyefakcodovpWSvFro3cGtk5GVoSq0peR7Zow/RFw== +PEQnpcHCCkNy3Q7rsdi/TDlLh1hH5jGLBD/P8NhI1yeXk5rBZmq2rPIvTOtE1IQM +XCHGYowgcWiXCPE3uFJ0ZbmCVeK+z7+QiyULQeM/Mfqgvd2ITOeeUUunR/W0eUgw +ijJqnkItaijg8Xxs/K8JAja7EaqPc7KCGoNyxafnXX1GfQLiO7hMfC4trRt8GBCh +U31Q5qsC0ZXwyq8Zvtv34/Y4w020wjEmrhhBg6B3ag9Peb7eoOVQ2RtXr9peyeoc +Uma9eTOXwECuwd3yDyjkccjKNPBHZO90NiSH1qcJQ1x/tFFJNp7yhVpfbDkTfOHg +fKhgtgnW1lEA4yy3gPB948NQpKwJr7zKV5C2horyhdjwskODl2bNwS6dipXYQ7PM +i2sw6+ZuogTNbw/oNKxMgXWiqY+aZvoMLpccLvBZI5aaY3QlTOwrb7QPOGk54xJA +F2DWu3kKh2SE9esMcSzIGZs4RKyGxgWAnrhrGFCFhyYGj+sUGXNUG/dippyVFmDY +oDtnF93s1JkfTVQ9UzfoipWioLUWstRD7jomiUg61SVBA++lOLLh5NS+jaLBu7OC +JPxDl53ivISpEUZ+1kQXQdwn6NDhZRhnvq4Kk4Dg1F78sf4e2VcDCafHiSYcmh4FNR3mcNE3togApnOLUomA2Q== +RrtdXHpDf8ftCQ4plQxw0Oh0rdfx53ARjXk+r8csJmUThuK0+0oUUUsGp3YckosrrXeo2LoNn5c6UqyBUWQ+CA== +c33sbT/GTUpquVFIngMO885zLxbCXVnvexYBDVOqU+iMb6nbaP4TrFnF0sZQpz5CiHdLyhtvzXNx8TUkLN9lXQ== +D7Xe+CnrKuTWLm74s5CdUP8kGk2RBzR4MwDZOP8HMBWM7vrwiBc7rfg1YuRvnLkp080JZrF6wd/Cjd7buGH3fw== +t607yNsl+Xt4IUZUKCdg10Hshd8XtxOblG6lAjn8lTH8FIQPvzkvdbqtLIVpeg2o +v1YvZLHufhLKFxIHRhqo1yVklxc3rJlgV5c1oxuurRWLcATM374XavSj+WtB5S7v5MspC01i6B4GEzgYnQWGLg== +O0mtTdlcYfDttKJPtVyrjWTsesoi2Yrh7TXd3he1E61o5u2D65rQDBCUAXYd/EsJ +Zs0AOqbSh/pxVla9Y6PkDEgfzwwsnZGC1KlACL5BI7yFhlcNAgIImY+NzajllBSA +eP9y1UpA8RKgKDUIdVctv8RYlCX5DX3ByYiFFPcsYMtfq2Hi6ygMIqz08ehknPq3kLbHBezWrZBjAPN5AvD6Dg== +eP9y1UpA8RKgKDUIdVctvwNj3jgyiyWh1YuqZQrMAkQkCjQFc/kByE3s0UrgVKYvxwbAOHxQG9JHjNJ07hSSEQ== +jlyatDnTvJkXgFSCTGo3tiGAMuSVHfuYvRETjlSvHGXrQDWFYrXS1QRqEORJ8isz +ECQx4hhql9YPrq7JsTDQaMrs8kiVlfn9Z8Q2Ifc0RUwzgtJ7FSS6YU4fGZoI+vTM +yUEvl2lmwCONcgsqq7zSbw+XXPkspwcz/0kaT5QF/6UXREdbTeC16ytf0AHP41KXTYT8aY2W8cVMXH4kf/nrZg== +u+YxRTYN8EJVeuURXtdcZv02D0KVvAStvANxEWCv9sf8Fm7jHpRdK1cQJTfqlVewQQb0soY1dkSd+EZKffT1SA== +AS9+AURcEG1BucPfCwzeHUkiiH/X9fPuUC8GLr00X65+TGusfO5LkBvlfu3iuth5 +o4QTqehA3AlJXkkcJZDUJj+Jzlgd8GAcYeL5PjckJKlACYIxVuJH3hlfpsyx1yOn +GH1HhyXKGfEMcZXYE219kNP5r+5V3l7u36q6KqB5CiGuj4WsUy67R41L7Dob2OTrscp4iTsL/eajIwP8P3twQg== +yL3V7JujJ3KF9f8XtukZsaEMjPxcpgiq3vTVqhL0re8ldG7x2wrWgut0XubluxOT8r8dMH1ueJ8JX2jFRbadq8jpJ0cSaQBow1cKZJbOIik= +Optb+ZZvcgzfoopw/H9ohTDk0ctvD8SKkfapSkLhlCbvQoYhDSlRZhjkNHBWVOmHOTyBUsvtZudPrsD8P4eShQ== +1Gt/p2zFJnwB8mRUNG4E25SVCJpZa9Jrtsib+8dSFPyCHRw/LA63qa2Ty7rzSKElPcZ7W+EtSbNpbzcFW4dHVA== +iQWcT+NnjKoF31XI9cw2YeoDvOh09cumpPHE3C6spMUKCU88YaIWvJc71nUHOV2TXitxGG/DTyVD6PrE3XoQlg== +IXYtIlQBIioKIAehYu+n3FZcrjiXCvJKfLpalDl+PAQqgDVfnM4v6ryxYGPhPquxlXjsxuh067VFhK7JA7Zm2A== +Fic+PF+8pyenEu8liCSk1ZGYJrf34OrxiQ/kSbZJf+w+WgdvDRsp3GynkcX5kvRpktiMsGDDQdldXlj3E+K9Iw== +XjBVJ2fDvhqj13zHSPhNq2bYXFR0+8Rj5YB3l6Zn8SuOfSLeh8LvRfi+okfPlg/Bldv5dQDk/TV1buG3oe/830ywdWKkgu7VvUpmCA2B9Bk= +mb5ArhJqY8U7Nhj3n51LRtWAGnitzRz6hRGBLHH3PfObqyyzPGD9ukhudhkGOEDquhrpyhRpigzbUoXmpN0Yp5BmZ23NRuLlmZSsIsRip7M= +eIAbDBBMnbQNURiQ/SQSj2OL9VTyEv4fx+MVb1toHyJoIXhKC9eqW368GSpEcq6wJkbA88+fkXsxOh6XvN87yA== +WMryAG7s9FKjtjrG4x+ATG/yGGAOpQ5Mu9Sw79MAMt60bIv7/nLjmvJhjeSHaYYGXC3AGr/NvpTvnYMS68tGnQ== +WMryAG7s9FKjtjrG4x+ATMr19LOPxyT30q6tXv+Ai3a2P7Cg7P2d0oqeko7smZPj07DwtrlzOgQRtH9War0TKw== +WMryAG7s9FKjtjrG4x+ATA1XxevnK+neTT4KR3WqQSsCRausMSrVxJIXPabGNYO5PGcgayhgtwVFCqBih65mzvRqg6yx+WU5qUn2dAFfJRM= +WMryAG7s9FKjtjrG4x+ATK/LBrspDdnvHPnyOH0X43Md+CHKEHD9MqfCnJZTWL7LDFbPXj6YxDlhGpfsWqSxIw== +WMryAG7s9FKjtjrG4x+ATIKWbV5uWpBQjHCbzC21ThGHCMrZ6/rqrTk/OZoTkW+TeGdfnhdLs504olThDPMVnQ== +WMryAG7s9FKjtjrG4x+ATMO+vflXSDy9uzArXKqz4Djtq7h0GBwNWgH3OH8NAYsunB5E5RsFoqVFU20mRdSyvQ== +WMryAG7s9FKjtjrG4x+ATKE3uOlGXG/ai7M+pRzUU3joyAGlrn9Xvw6rBO6Xt4RBMPqFM6sjtJQmELC32ZuPXA== +WMryAG7s9FKjtjrG4x+ATEI+NCcj39CbYNvbfjuI2r+Ca/kX+1V80nr4TNaHpeCLoDAztMXxfGIc4BGt2SEslQ== +WMryAG7s9FKjtjrG4x+ATM/EbaHuaIT2sybliDIfD7g5G0nrrtSXGitConozYNm1l5RlLE4IBxGMfEcTooYzvg4ZYfhEGo9BLutg/FDb9Pc= +WMryAG7s9FKjtjrG4x+ATCBp2NIRXySRT6QN665odL+FnoUTyKwi/Wof1FoE1i392rQ00v+Tj8JZY5q2WPYv8Q== +WMryAG7s9FKjtjrG4x+ATOpEoOjVwgJZnZq4QnYnom2telS+0ITfvtKzBcFGGjCOV0m+zvWTM1tiOGcnY7fo4A== +WMryAG7s9FKjtjrG4x+ATOcQ0lVkg1tu+xrgjlmF/syK5hWuiwO4HT1xF72fAH+HjNqKw1apci6P+Hrri+xHVya0hQBeyzP98+MViRv4YNQ= +WMryAG7s9FKjtjrG4x+ATKjZ3EBOshBqZzeSpOExlRpSJt8xScVUTN4XvIHfKhd8ThyGKISNelgnZ2T0ecpc+caVfc2yPlPf+ytGzTm+Rr8= +WMryAG7s9FKjtjrG4x+ATKqF6F/kMLWl7MmcuE4WEnOv9/pLTI6TIBlDkNHgIKZazk8Qj02OmN45aR75YotAQuwAXfD/u/S7O3ycI3UW+10= +WMryAG7s9FKjtjrG4x+ATEVYhjgt49n9T8GXTStDP96GUI/P1L3eerDdknnuGDYUwJKd7RpThJ4f6pNtKs0w2HazqyMbkmNDGqgyfFso3eg= +WMryAG7s9FKjtjrG4x+ATNN83k65N7TcpEhdUSdXIr+isOFgGyPHGd2J7uhQdjgz5pavR1hfPZm0NRzMtdCX1A== +WMryAG7s9FKjtjrG4x+ATMgv+MtjO7FtemUMEZb9FoiEY65thzTYGMd2DG88RPHV8qAYx6yMo9mtPPDigDaASeWIkt1rj6bveGXNnTEXllI= +cW6VfR5aZO4LK69BZsjV9UpTvkEwgCSbRhjScMiqRO0iWxW2KX4PIEVOpNAm1myH/iDckSaXxf8xpidXGbBZxQ== +qXz/P6tVzdUn0oeNwT5P5KbO6OhS5xA3Kmz/5XUFu/I1cVsE+5O1VhCyUHpZYfJT2pwHBNSBtzJ1Oq3xwjq1TA== +u9mvWPj2GQyeh4Qfg/3JkwVeHSQF1+4KWUXsEhiLbU2vguDvPbovjWRLj2zZUnOaB07bXePGlviviNg7AQvIdg== +u9mvWPj2GQyeh4Qfg/3Jk+tGURZ6qNJ2J+xWOI0Bn5CuMg5ytf9jNjlGFFpw4DGNLT9Wn9gBrbElVifp/pMH7YmIpsuqe2hc92tY3h9kAyU= +u9mvWPj2GQyeh4Qfg/3Jk2lThgawC4szMX2s/bx+aPMNiE4iMYB7wv2LuUJhg/FQKp02IokgaklB9sZxvq7ydw== +u9mvWPj2GQyeh4Qfg/3JkxYsRJytDV0UNBwwmqkjyn2ijGJMGLLpbXK72x8n/yg+O/+N2LoAJgZnh/xBFoQ8n0DHcwQbom6c95k7ps/GkCI= +u9mvWPj2GQyeh4Qfg/3Jk0OHZ7VjmYhZ7lnCmXEc3X3qtCMnqW/RQ9yS0JYIUmbme9mqsaJHWcY3poE6o/ne/cFUb2Oaz58fELEHG4XrsXM= +u9mvWPj2GQyeh4Qfg/3Jk0Z0U1kJeRDilIw3Kjupxgsrjia4ZIHtR0AIKrEIV6ne2s1SX/XFyqNVeXyu5Jn2/Q== +u9mvWPj2GQyeh4Qfg/3JkzP+lseasJtzUpa7Yj1DRMuUfcg+Hp5N5bfdDY0coeTH5Yq4C4w5O3SwZ7qhWXyy2g== +u9mvWPj2GQyeh4Qfg/3JkwXiSGsxe0w3LmGE8blrG9SReSPhQPVdsWHzUalY1ICjL+4EAPt5q5mx/7PfkcC/9APSJRHysqtB4ndtQUZJ5zQ= +u9mvWPj2GQyeh4Qfg/3Jk68W6b7rhQM4E+OcfhL6eem4t4asg8Ztp99XWwTH3rpMjYl45f+sOtHSL5Cv5VLL5WZ57M07rU9bPcjMKl1KHZs= +u9mvWPj2GQyeh4Qfg/3Jk6XzYErLQRL1wLbMINJRJVqQbO8phW1756ARycM9dbmHdlT6CMnpn9Eq6iGf0i9DCwy0T1XVqO657kI1KicL/T4= +u9mvWPj2GQyeh4Qfg/3Jk8u9aLlj97rhmCOXufwoHkDGOKvcNwCIaXf+yqS9+xn5uBZKay3rI9gpQ+YFNhrq5g== +u9mvWPj2GQyeh4Qfg/3JkxI3FsCFY/d+zw1RFtJNfsM9Y0JZY0zwu9KposEwLxbTO32jVkmuEGIEEKBVUuMxaYo+cgfuNmpwnrjU4xSgT/I= +u9mvWPj2GQyeh4Qfg/3Jk/4Xxo+6RU8qO11KZpuw8BgsqdDGhk9wiSWyfFvNp37zCApmBXC9CJZoCupKL93HmITFPlMzrW3Li4DMPZz4Q9w= +k8mQATL2QGzdHVb/ZqU0sdv0TlLp/CuYNtJgvarcxrImVKie8q/K2IP8APf+DytIv2EALQvbMEAWFPj+uLnoWQ== +Optb+ZZvcgzfoopw/H9ohU8tljhlXj21QQ+gRCOyX1OySc3/UttfSVVyX+Hit9Pl9lD9hpdd6+eDlDZvY/BY/A== +O0H12djxOPpYv3DI+nktXWNVtMvswMeYWU8pNnbQ1ARb5mEFsfbugUsGgqjyPIOsW/YXoqzwQYaBeQI/PX0k6w== +L2qllRaufT5w7PPs814S6jdO0T9dO6I03d4q9MOWqvJghGaC80NN1STq02T+i7NHzjVHQQqYXvwj03kWVZgvAw== +GCa+LjTuIcQFdoFSHacKrqfvmlQ6RuQhQzYlgtCK+Mbnx/xc9twHveEqaKLHjAqo+w6O3hTC4YAiLHFMrgjbLg== +tp5g+osoOV/pmK+tBcXKV3TEvJQnuWktOx35NAOLpVu5SeTQu2OUIKD2RCNQx4glgboDAtP2DmgDNx4AolsMJw== +tp5g+osoOV/pmK+tBcXKV9hLL8C8BEn9jkAQJ+zdPFATD08P4lTbwWzYHe6aZg7yLI/VSVuK2O5VWMd8noAQfRXxVAXihxg6tU9xMNxLz3w= +tp5g+osoOV/pmK+tBcXKV2aqyPF8qSL8lCeArzgYEhIuNLRWWta9ukfuU7Y879WpQ1///S3UQo97mRN5zqPy7/rDvia0pu0R5bob1Kog1oc= +tp5g+osoOV/pmK+tBcXKV+3nkvNSPZsmRCiIYaYDGbq7/4/fjilKmBrEKu4acUGrPw8BUkgJVWOg2PjrIr+zlVmmSMIuRdGwqTFasCKm88M= +tp5g+osoOV/pmK+tBcXKV8dcfRVV+3iCHtG9QUDz4uEX0PDI/QNVkJ8bhWwwu2OMK2KsJGf31NGgAdgO7PLYfZnLSPJTSbwNoKyAHIz/MOE= +tp5g+osoOV/pmK+tBcXKV+0kNyfVosEvu1R5eIdu4mp9RD8DVTwhInq+ZEC40TCsm9+wOl+JMFsCdo3+EAZ+d0zW4kjUy4PwBARjXscN7SM= +tp5g+osoOV/pmK+tBcXKVxaDwti7mWG6/L3BiJN0oGE+KsXn/VMGG10NyH0u8rFe1dt8uv6dJo7PhgTzgrjyUlC9yHxZBApt2/aSMtTQqM0= +tp5g+osoOV/pmK+tBcXKV23o/6FCCKq253U3hcS90I3OV6BKTo40KNpqERe4dqM0oeQ/LOt4q/CkWNfeZohxR03nPBZYx9oU2tYFaER5lx0= +tp5g+osoOV/pmK+tBcXKV5NkfIfu6lyMg9b8khIrQtZYsmf2xFO3Ll8iCOqEXhtb/HpJFbeWFHSTz8k3hcMZC+u6JTgdRO77eYTfziE8/M0= +tp5g+osoOV/pmK+tBcXKVzTZueI/mphDci1n7i4FvjFgNHcEAtd0j9fvOK5z5vBciy6n+sOJ9At9zuT0m3/KxZJFG0yGmBOBOXfAGhK8zjY= +tp5g+osoOV/pmK+tBcXKV/R1y1G15U2L/+mO+1q9xsmO7UYo8wUqdZnPiNsqikyMiCweoAPY/CPiU165YCDP+/OzVGULF9IYxTp3EmqR0QU= +tp5g+osoOV/pmK+tBcXKV1nrdSo4U5sImCiOzub/YfeCySVZJlO5XwlzdKY9IUlsJg1Qw1flNH1tnMaUPEx1pjQu7NWQLnE/+Hdbo95/rm8= +tp5g+osoOV/pmK+tBcXKV2XIfCmcFG05EPSUV1i+BCwrJcHy4HfpZCdiXtwn6xgjIIrMpOOiDx3z6iet/EaSPTQ7yIIhqXN1BviRqHNRqfA= +iaAodRlbbDbdxCarNwkcQUTO0nhGXrSlD7BKY9U/+h66Pp7oK8CK+2g0VusNn/WL988GCPaGLufuUmZ5X3BNxg== +phX6+xCiuQFZuok+RnQpZC0EWCXYMWhejnYj1yT8kSxBypjvfxeKkMPpyP6TyBU8+DJYIEw+o7fuNkVcGJ/2rA== +6dJZtsb8CUPLf6XAX0fHy6aa+F/Hy0+FSnai7BAvMbkZ1kngZkQmNufaUhYOzTH/ +lpMeOmzCLPmsgqJkG7PE0nFddPBnF9WHa9x+DYoD7F9QIcTbi73discSSa9t5LcsFXGXXmts8HnluacE0HmiZw== +lpMeOmzCLPmsgqJkG7PE0uNRxY/2Wd1eN+U5+P3pylI9OATxz5ysUrFFxQIoidMewDxl+pTFBs+ufGWBKUMFNA== +dDQ2U1xB29TjkFiRc3OY91fq42wVlRDJBslIUC5VOwebrdzYaqYfQhKRsb3Cgza3 +phX6+xCiuQFZuok+RnQpZMyP4ejz8dP5Rv1WDyPhhA/mTZrpMNtoDSAGhVA8OACk +6dJZtsb8CUPLf6XAX0fHy6aa+F/Hy0+FSnai7BAvMbkZ1kngZkQmNufaUhYOzTH/ +glsVHMdFb/IeJsxHuz5fO2qoCX0bHv8KxFpiL7f44o8f9OvgYRSRBzbUzk0UvWRhmVbMLLBc2ny1lzSl+dOPNA== +LllQdNg6CwTfXdYMyq0nDvlbmqRSNt2wHU7z0L1BKHuJFK9vEEUqpeoN10MSEIuV +u9mvWPj2GQyeh4Qfg/3Jk2ZO6P8hGnyy/zY+jhpPTExHxZm4Bj8vU8VgMgQAcdRKiWx0v7Qs2ZqBBehqzjEtXA== +u9mvWPj2GQyeh4Qfg/3Jk8ZDTBQks0/zArhCOqlMX9YSyNH+xfsBRCf33b/2BtXsLd83FYHzqGKVviPkeMIqfA== +u9mvWPj2GQyeh4Qfg/3Jk5Bfw3Qg1JNky6Qzkq0rIuCvd4nlwBcmQ2rEWWvb3pqt4Qc+DiJMDyQTJgQN4r1Qk2y6JuH4MTZbH7fgmmkXQ1Y= +u9mvWPj2GQyeh4Qfg/3Jk0o90/9mCvvXcHLwVxWpPy0cI1q/hR/kcgRbTv4vHEQMSvjtaEXD8qxqjBxMFE/FVfRQxFURW8Y6QO5hEjEQ8KM= +u9mvWPj2GQyeh4Qfg/3Jk0n0O4lJoUim6Eqj3aXcB98U+jsNkYpIN3VNqIbVV8o468MIUuo8ThVmrXfllG/hehHWfYgFxFvK9krwdaJfFWM= +u9mvWPj2GQyeh4Qfg/3JkwXiSGsxe0w3LmGE8blrG9SReSPhQPVdsWHzUalY1ICjL+4EAPt5q5mx/7PfkcC/9APSJRHysqtB4ndtQUZJ5zQ= +nrGCUtZXO8lhMUzpsw7flX5OFTgCrwZmIp7z4/aubyjFV1DrMaZGkwbq+0TXi4/HWwgBhq5Eu8bpgtFbLSvCGc2vHCb1vWe1wQ0pmNMfV/s= +OlA3Ms276mZTcqwKAwXvu56bhPdCtw5GGNVjM1oM2e+M5sRPVjVsfUNxcKyhB0vz +8p4c/a5VsUSqHSeB2usqZ4qruwtfsgO+2SoHERyfupq1ztB9EMTtVlBuFntiG7U97LE2j6LU5+xJirtn924K+A== +G9T1Nfsl7hOYPUDmJtp6nN22GmHI7VyiriEhZPJCJWNPjQPq4CNx87iIuy3wK0gK +AdPVIE5vJ65wZA9MH/IxkPFx17bD/WkfQCRLJN1PaYLkEFnLaqeffLEWDXiLQRJZ +2Poxxdy9o8AvAQ8T2zP50KH3rg6/ouFD/nYQU/f+IIp8ActTeg0uR4fevWWvfjth +6I/H1Zpit1TSo4efV6bxh+EAqFiiiS0FnW9oFr2u/GGPf4gC8A5mP6Sem+ISyA4J5tO3CbUIrmqOfzZcuUOqLg== +ztzuq6nAH2ogL/nP4kxvN69SRiKraaWyWBK/ip93LYc95fIJZuN0J2GAk0DYtX9u +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +wgR07xfoapmx6eEnFHXXYsrtutdMRxYffYBJpExAFLKvOOsdc0KV0E0yQAAQBZkqO2dMmc/KvncCFdasMp/nIQ== +NqhMbY8+EQI8zhTG6Zh2I9n9ZL/a0DhWUREDi0vuSmk/BBGIzgfE/zwz/+PpUEnm +ET0y6SUMPE2Ml4dS8viRTJixJpYvSUNaJEQ3wgnwmDR/Dl0Tv2W4MPgVxUJMaUl1 +/VraJuu2EmN1fJjV8ELbStoy+NvUd5C4U3l5SFxwBYqHlGghTAj+KvzQIxH+57GjdeVm46KwnUg95Vg08AS+OA== +fLETQ22HuDIh4ibW1C50WVHnbZXsGAhArGdfK3tcG1pK3MbEtlt4g1YMg//GB50t +Nc90b6Vm+KhgeJLTsdGO0k1J8yfJ4oYcbdpVDV0CZaqGvHTkW3ri/kIS4tuhLtXq/yA4+EzCs7wLomarmDvHow== +8v88LHwZLe8C6EIyeMyGCHmIdCyFG+evh5a94fiR+mg= +32pdC9DD05OE2l0oXazDFBeY6+wg6WQKfWFWUO0P6kUhOidDEfWd/HzYk7lVbEtQ +1u+XjG/2+GSQRv6EzCaWRQ== +flULj/LdXe4ShFGOST+HfgiEQqJL0kKKn6iUffraNcUfW9gxdwW9wmntTAcmF7KVOzRDVCHXtZHOS4D78Zob1Q== +Z8UsPk1Q7HtwjRd4g01ryw== +v4wZTNeqSt7hKjjs5rimNMLgdpIKpbxvujNwNW9pnkHVQ8lv2VVqQHVVc0GPFdfP +6/YdOldN7ILWufE3TpQIbCIbF/kDgCBlZUKNWM/prTU= +Ys4bozvJBgtB7q16qcvtBxfJUhKmEI4H3T9j7f29CdE7rNQ1+w5tPWDUNgUfBW/n +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +8eMNwOFVbk4AtLbMoBZ3WTJylC8gGuKXwsoa33uaVOilo+eTav4x4B/CBPvAjTgn +8eMNwOFVbk4AtLbMoBZ3WXGvCdkUE1lCv740F/AYpZTVXZWXz/0Lz1ev0SYqBf0gjG5iXFTmyoAlt7NDxvLnEA== +xWoGNWjKGPfI4gq8aHoTfNkqombeCH/8EMbOTha72rQFp5IL2Vny2x86v+62gwW+6tyAPvX761EuG/sZKbYPlA== +PqAs8ZYMxXN3H+LYNsNggUUCDiwhZ5kF7Kc0dxYPIfvvLyQAP717ErA0zvIWk7YEplmqjfbDzWRoNOKyjvnrdQ== +1V2v8QerKOmubvSxgB4eTELr4a2ZreHX5suyB5yxG62fHomWaQJE4fHrHlvISVn5FPy+5D7AmhPQ7W8szu3lxBPfmtavK+0V1LcZFVr6D4cBm4eSd2et/WWZXL0AJZ6H +iZaIsa48RXfE+uf/yF/rQHaH39lOhItuA9XlMsZGwHM= +PxQyEFNgEUrjep/ov+AWf+oM8xywLq7k8twOsAgmDnhwxbYPjvM1HR7ZmztlWsVP +iZaIsa48RXfE+uf/yF/rQFDamMuGYoHcHvig6IR7L5g= +iZaIsa48RXfE+uf/yF/rQBHzBiyCImLvDpqO+6hTyP3m9lHPXPQIIotfEF+lox/PQnwFku2M550Yae3EjKfdwSo2zaLVsBrXdy2HzGwErWc= +iZaIsa48RXfE+uf/yF/rQH2dIzSy3gkyzLeqVCbm3eTH9zmgrK/v9VFjQHkrschdvsu31TnW9AJ98XajAcXV/xJabWM9DvIsHVJ0epK6pbw= +vlw44yX3fCVt6eRmFa/S8+zcDVCNlbkZPwywv2/TkEdjzIX1a7+Qv5GdodWIWjhtmXWPTEcqpjmiAgwl6lc01A== +e1fY1YJpbz170V5bcvidMMj8zZEMpvaxNEwOhd5zodzg9VC9MnufwNdaYU25e3Ap0TjTYIXJp3aftiAH9jPWNw== +S1U+ozOPAZj3pbrAwiivwV+OvqAz1d1sZB0EPsOnolf+IN6OHYpFNOp6+2SjXYmV +iZaIsa48RXfE+uf/yF/rQDuGVEzsmF3CtJPekR383wxEc8OumBnbu+FZpS1sbexmU4IRN0hBGAisopPlzdzamXnnzur1Wji8UNl30wxAtMET/A4MwljXqYiRMkHmVxJs +iZaIsa48RXfE+uf/yF/rQBHEMyc95ROne+WTG39WCVaS+cXX9BOUlzXxE0w6ot+Oc3VNAQ+DhKgGIvQZHJ5c6g== +iZaIsa48RXfE+uf/yF/rQO/ErLeTILJ36v76UKS54Jw2UX3OGfKAG7yS6qfIxugHyp2S/OL5tJKEWjUVuW4IUuzQc20IvoIJtKzwy4FpzeobJXfdFFF6CmRLQ8jKXRiVYQeTwTiaZHk9oJGEGYazRQ== +iZaIsa48RXfE+uf/yF/rQFvnSgMMK7IUZnw+ay1bgOmW6qSuUZQTz2zXyezTI5+d7Fsrlel7B7kVccTcSm9Pfg== +VmVrGQo2zRokW/ZuO9bN698MHz+PRq+TRlhzLxtWGMygpukHf5d24j778t/zhHGidh9OMwub9c9vsi9i+zZN4Dri9QnMhFeT7rUsrCUJo5TwdvrO6yF6zrAu1/LNEjX/ +iZaIsa48RXfE+uf/yF/rQBogmq+jEWWC54y/H6MlNh4rp0+JMOgYT2mZY+TYp75FCm5W06pUVqWJPDAukRPl+ZRIueWws/l752pV//pYffU= +cdjMZGd+oj+E1aBgYuadXWtaC7aSClO5q/k5YLsaTcSQLEvmsyDwCAB+ds3GneYkZe6H1FsAUlpWz82XtXdOog== +cdjMZGd+oj+E1aBgYuadXaukekpOTL5WA5BiDC9vCxPuawlZz6j7AAZlEbdZdLQ48OZE5ekQYbVCOAhL/BJpig== +2YIp4yADnjMGtY1lwBdIGuJOMZTFEHseAqpUotVOIPwfj4SeqVivEGJskF8OkEcx +iZaIsa48RXfE+uf/yF/rQANiUB6WFTzoHZ3OwjBwtM2/j5761QTS3AJCOB+qIm5mr/5/eVreUywu46shO608Fxk3JiSLg1QkYCinrxbBW8I= +iZaIsa48RXfE+uf/yF/rQKIqqcSbvyJ6nKs1nu85+fMRjwAg8Y6Nh993Kne9OE8bYXDQ+LuRcreEaUiZDN8aOyIbEKMTvERwYtJMKUES04k8EwtY/LwkeYK3mxxvwexvEDmRXbKbEGBLl52UPJVJfw== +iZaIsa48RXfE+uf/yF/rQJ2YbiBFucc3UQ/ZX8J50oEXgugXrduKe8SAiE/mA2k+T3N5ZO8BVpH8YAKvC4ZR0Q== +wlLHv6kT3Q/RmtMBN4nDAe8+imtRBuiJubS60XFrMJg= +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN6yl77/0MnObCK5XlSs9DWF8eqyYhe1FWHc8xaEa0H0urV7ygkURnv6BiALfKwfgNY7qBO4kuhmbf8nJ1oKsf+gyy3EG6rLIgfnG4sQZKYsDRT9AFAoDMtdIUBl7ovtqbuQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw5bx4JTsUXc9GQD7jUveTF+h1GxK2r9hBJbi4mkX/JP2 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw3p30izicGP0/3QQynjVV8QfCMdvBT/iicXrGxILnxT7Flbhb0PBJRFV0JDAAWvxHQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw+sgN+NoknzWuDmsXkDIpaqQj2Lw0lrCOo1L/C3blJaAgKXMvFjXCS584QOcKTtHOfudZ11Mj38X+gn040NpusM= +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc= +i/p1vQ7ugXGBvwbjECApz6tQ3fYSsRgetdpGSwwJdY0= +wlLHv6kT3Q/RmtMBN4nDAfKuWKTPnQzOUo8b39dYynKTIm9yduqnJKjcYCIOYNl2Ve+9X84vGlhWQ4dv1oTdSWR22oAktUUFZmgLi+55bm4= +VmVrGQo2zRokW/ZuO9bN6+zshBZO9d61HkhcnvN87E/esTkTggViecgSKvZl2/jilsnJj15aKiHc7zivB4GiRjN+hiLn89qackKeknKgOAkBsBvnq+NL8RWD/NHkUD0E +VmVrGQo2zRokW/ZuO9bN67EO2w8TNG1LHiP/tGFb6V2Gt2yQRtjzazeNr74m0atzaPdvLFX7XyVVRswklh1k9b4jQHovg+TYl7Ff9JiJYPcw7bsPs4ffMxTkD62b1LTR +VmVrGQo2zRokW/ZuO9bN6x1lQigDbibIoTlVRYg/sjBbtNt2NXe9ly6pYDchoxSFm8eCigCwEbYkrmTqSpR66Px+l9G0RWse6wD1iaMwlGH17ZZODY6LHJwXbnnoehfY3dgu5gHZ1QEdRO3HBSEC/BmkcWwlcqCj5HLhmnA7jVY= +VmVrGQo2zRokW/ZuO9bN65ZXaaTN9OdZLZFqE1+KS6RnzprQpjuF686uW9l1a1Xa7mf8nAsb5dvwf2nXb6MyyeGLjC6K1kWHmAxqB0N5Lhqbqh8cjSw9EakEYrt1pQmzU6bWNZ792Zty9yar13+Cvp95ctSpW2nDI1vJq91wABs= +VmVrGQo2zRokW/ZuO9bN67sdU0BGc9v1NKjQ+sFJyyAVqCbBH9iZ1H+7Xb4dU4gwDiJcnbDr43Qq212mGJoiC2DMD5ackhh4eB3W7YaQThFl4no6PKCk63hukqrWGYjX +VmVrGQo2zRokW/ZuO9bN6yl77/0MnObCK5XlSs9DWF8eqyYhe1FWHc8xaEa0H0urV7ygkURnv6BiALfKwfgNYyI+kia9//G28k57ZB6qNEQ= +VmVrGQo2zRokW/ZuO9bN68m4dRJxwc9vmoYO9XfNu8JoAL0ANoHBA6wRLssRJG9d +iZaIsa48RXfE+uf/yF/rQJ2YbiBFucc3UQ/ZX8J50oEXgugXrduKe8SAiE/mA2k+ZzCTJlHCGGr+o1zyX0J7dQ== +iZaIsa48RXfE+uf/yF/rQEW/HnjzbDWg5QiOMKFgUs166OxnL4KP5EwHDMXQoCQJRRha1BVSLbU7v0IGpgEo3Ow9rETT+gtPN+XqbTD9O4VSY9LLGQrxzpdHx4ypB9/+ +PMWbmPRKnSEEQmuoINHGWsR4WdhOdaCB8ldWDbzQF20= +wlLHv6kT3Q/RmtMBN4nDAVAwwjXMi7bsf2Urn5Fa7BXRsDUkBXTBYPEpaEfSPA3CViaW+eHPpTOr1sqaxfsUEJXr0r8RinHLoMwf+yDuYSs= +VmVrGQo2zRokW/ZuO9bN64Jqehn36bioUDDk5ORDx1UfKJPWwYobkwrB7mW4yC0E/yspyDpCiSv62gKc5GAi9T+y34+JwM/Ix71XqqYfqk6wuSlZIytENGwwf/qssogjxCLORgiVriYpcnIi5PgXEg== +VmVrGQo2zRokW/ZuO9bN69lOsOGGAluHtoQsF4JTTD91qLyShUeY2zrHp66doa7mrqRGy6XrHMPUUlJQ9jQMJN0k5EJO95P+i0RrsO6IfE9KsdjGbXe06Ke4++h4/3UVjycijEWFMnGV6uvGaxuhCw== +VmVrGQo2zRokW/ZuO9bN66Bx+QcaotFFc25usOdeBnbTRU45vVMOgan2bt8crvVKWdMPA2l5zQM2ZMjHmie4Ostb2ZlRvEpt7614r2AV4HLy37BZMk+x2Nk/Kbj+WVTFsFKkwVRGLBsFJ6yqXmiY71otfHLUQGgM8V8B/4spf6Y= +VmVrGQo2zRokW/ZuO9bN67sdU0BGc9v1NKjQ+sFJyyCMLjq+xWwbZ8c+ddHAwB7BKoukMsMxYs2sswO22RXml2KSsLww/KZ/8G2d/WiAx/rgyHeWDCW9UJufFHa7xinD +VmVrGQo2zRokW/ZuO9bN62cg3MFjF3Xh1e0EF0hJpnU2g0qslcYflC7df6LH5ycv96oJb3iOdLAMnhFiSSXOjfbj/n1Y70ip284H5cNfF+k= +VmVrGQo2zRokW/ZuO9bN66RJ3wohQFXs+QBlSfWZpaf0OWtwRJ6BrDTWIqv5NVGm +iZaIsa48RXfE+uf/yF/rQEW/HnjzbDWg5QiOMKFgUs166OxnL4KP5EwHDMXQoCQJDHEHPrnpFeN1JnNSnouOcA== +L0eUthVnpkGsmKFAX6d+uJTdC3GdscKd99Ans+Bqi+E= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +xWoGNWjKGPfI4gq8aHoTfBVO0rtzJGwPNNCAKcvoQ7YGnBVlRRzBkPuuYdnd1drISaN/5vpFnf0ao1Hkw6ztew== +1u+XjG/2+GSQRv6EzCaWRQ== +piplqnZD2qKjxqq5BxcYpBpGRwKsiHJxwLqnhEe2D48YtmIu0TblS7JCQluznId9 +uCeQtkgRBtN0HiqPTvhZHeALjPy6nS+/Py9QQjy3Ju9z9JZP7KSWkptweIa2bYE2 +cgFNMoW/KsgkKsovM6n6KO+C6KjDPWoUE/hR0PgyXA7qIltJH6LvNkqkyka0kjkn +v4Cu02hZRDsW+85CLRBC581P6PmAWmaUk3n76dbnkVw= +NmkgFF4ZsyEHu9KVT53La4WGMwIVFvT8dzZ/QJmAvrfSZPlY+eRgJ/IGmWYM3DiSmAIvy2CFU0T3w56mhC3RAurFjyBbAOsoZluPEh0M/sY= +YoCN/YJ3FGpKuVn0NXtXTv3XCu2B9Ep5OJG8o8tyakhU5BRvAk6zZXlPRk9etr/09ajT8+g6VldNtH2UYJBlUw== +YoCN/YJ3FGpKuVn0NXtXTsWIqvly5o9YazCVgAEKZW0PTnKV1DWqLu0lRYHDz/YcsBnA7SOwZuYWWRON2HScNg== +1u+XjG/2+GSQRv6EzCaWRQ== +PyAFsma1q0hLhwE/7yEwvlLDizBd59bTOK0yaWmk5wKN2EpZqyKHG2wdBEZW3kAlb+ZRqbiaA2kRmKB/ClkTYA== +DB6Dm0fkcUc76J51zO5BsTwaw9bnJ1hMDPyvzSAiR99N1sM5KJNB3yakje8btu8/QBHP8RxgWzfFi8SXTQxdwc2MFwLBmPC8Kwdv5Qni9ns= +C3JSV38/w2nvM3I7TZ5+4epkOMkaWYvoOErk6ygROHs= +UaGDE9swb13AVV1HxfNyqi1KJFeHDs773v7JZyf+ME4= +mbg4a7OqVLuFyb4lBv/8ihC29C2cveBqDqu2E6PhaRE= +meZhlBQhcKhD0kZGoC7z20E7608oJ67KyHB8wGHG8NCp7pjQf+LhcA7qy6D0G1bFxtX29fKaijtpwJqI9cAO0v9A2SbJKZ6YEwsGeas8a+0= +p6i8iK7HDbvmLpoFrhNyvxdkpuCzYcAgEgoD8gO9QRA= +rYd5Q6Wra4KSueZqSRRqN4gDfkYJFjFLoq8Y/uFOPjk= +0aOe1PRcA3iTQeyd7fu/UT3OH8TgnUZIetc0kIThlMY= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN63igchzsu5zTaq9IXvBjrNVT0Ypem/6Ctem4lX8VHAu6 +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1hs1FfIPISXmd7TJ/r+Vl/cQigT5kdrpKyW4rHdGgtQ= +VmVrGQo2zRokW/ZuO9bN60XiiQBl5FEkQ6DHRSd+Tw111DT04Hh8GF8qhcK0KSh/ +VmVrGQo2zRokW/ZuO9bN65hNsUFyX6poB60L31EhX7HkJSxkQfY10wTx/PiHvRMW +VmVrGQo2zRokW/ZuO9bN65s61AIgyJ8ql62IoneEj2fAfjFSAde2JX230qlgjnH2vV81My82sV2kTGC+8zV2TQ== +VmVrGQo2zRokW/ZuO9bN60EOQAX3/UH5DkusEZxHTXPrNTCzuXya4iloKJBnudc0 +VmVrGQo2zRokW/ZuO9bN689+A3i/MUSda6G7q0qItIjO89bRKTthTU14pzjp1wZx/IYyafQwuUcVBEAoyeksHA== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN67Ta1/lbp6yqDJ2hGru87rzFFPN0VimnlC4upIFz9Ysl +VmVrGQo2zRokW/ZuO9bN69twiVqU/YruzaZ31Ozd8kyG92yD5lEmoQmdiZOEP8KmMgIvjkFJw9Dw8H1UfQ7mow== +VmVrGQo2zRokW/ZuO9bN6wMQRwD6rXhLUXMdURcL5Ag= +VmVrGQo2zRokW/ZuO9bN63dWRPw8YokoZZBoOkV+KLyGghp0raIHV9VjtnANG9KIUctvuM1jrb2u+oAvO+PyNX/pN1YjyTM2QU+QQeyrSQs= +VmVrGQo2zRokW/ZuO9bN68YCI3L+9iFlHcXIBMaDhSoBHAzOkS/E3fCGnDSizJXU2WZoltpBbK3cP92X2enr2wtzMw7JWe0lgiVXiazz4T/Z4bUwnAkwMMGjZJNpyPjM +VmVrGQo2zRokW/ZuO9bN6xdWa+gbDteV6y2dDYzvdHXNdvwv6O4BWZ9K2JMaaIkBryl3NjlyCMPeat24ub9YK6v5Aanri0Vw3GBZzV334T23bfdQVyLiqBefgdn1Bbn/k2+E2Jdx6KrEuRr17CCy7q0GnxQtnc2syvPtkXQBtoY= +VmVrGQo2zRokW/ZuO9bN6wNy3dMSKzw1+znxXLOAZLh5FEFcOrAX3cntmkTItZyKKVGgHnm92b7h7ork2RuLWUmLbdVgqwxu7Wl8pXJk83I= +VmVrGQo2zRokW/ZuO9bN6wRTb++fBMmvLx2k6/MEJBys2MeDsfiTOU27PQGTtFwGzoxQQOeDpqoXCiSV9odxKw== +VmVrGQo2zRokW/ZuO9bN65OUJfJvgNPZipZ9GCOTw7gT/VBpBNqR6eHMzDCD+qxc +VmVrGQo2zRokW/ZuO9bN6/6DN1Rj8W+FylDHNZarY8GmZYJIJsOXkIFPArsJz88a +VmVrGQo2zRokW/ZuO9bN60b5Dlj8qSCLiYdFYQye1vxhQfzXLk5iO7kCBpmwj0d3 +VmVrGQo2zRokW/ZuO9bN66Vj2MTf6PwKatUHlMDNJnAflwnI/tv9py6bv+AQb/3Mh1ASIGKNXpINw5P13Fnriw== +VmVrGQo2zRokW/ZuO9bN6zJK7VoTu9IkzGKsOyFHUih3nKOsBegITZ98OzfLK1Ug +VmVrGQo2zRokW/ZuO9bN6wragKme4d+RqlA7wSuKiun6Xqgs7hKkILO2biA4qzho +VmVrGQo2zRokW/ZuO9bN6924SEXfGwykaPMoxSMlsEcQAwBCGiNnpl4AiLwthDY0ogr81kYcskyrJDT5/Q87vD/ybRw7C5VoTHeL2G9HfhQinJgPrUBEI1svbLGHdxDN1m3m+AH9xwXwqMSg3chqEg== +VmVrGQo2zRokW/ZuO9bN67yPtu0N927f4HIlE8Q/3KEjZKgRfvyBEFqTjZArZjS6wWjCk37Cjc7Be61nCtFfog== +VmVrGQo2zRokW/ZuO9bN67oV8rbNeQxyRr5iXWNRT7jCGot63P1U0aZqnfo9iER3g0jsNJ4bdPmlVkmSovfUKEE8j1q098bPnAUmTwYv2U4= +VmVrGQo2zRokW/ZuO9bN61XvIee7O+pzg0zO1do+jmn7+YnxEi9X2xYlEBv1SXfIIfgDFycnDEX3zYsNHvW2uWPjibLCY7dmVLtuDnx9lOI= +VmVrGQo2zRokW/ZuO9bN6y82k0/7L8OC4IRsil2RtO1tWsTIDg48NsS7LlukbCaWEQq1DKn4luZ24BCSmDqhgg== +kJxoTVctk+bGjMYgKxsjjUpPRSfC+WsPwpMn5FGcCWkNpbjYvwc8oT1ZalJ4jAtf +zxNKIz/Dwj9Harw3dUNEQET0V05gcgtC1p+K2Ihkd0Q= +ACEEdgL/kNbx7RaZcSRxZGgMaBrP/U1Wsj+kqixOX3U0XMejpRw/rYT5i4ihGDYP +pZu1Giln7AYpCOrLbI27dxkWuQIzVUpxHeSHCtkSzeY= +pZu1Giln7AYpCOrLbI27d9FNbDQGKqlmV3/n549EBNY= +IhuisKim47k91RVt8z8qtCsumyIVSOp8xbUu5duXman+sOE6qwlPIuSaUbeysSjj1/coIZqlKQRM13ITsP3JTZKUmxD+QBryE7TKwJbeYvU= +DMKsax7BkTxm29PYxP4a3tCEAD0O5XmbLDroN8+cKNkbya4muwQehRDf9xiCNtXi5mN/bz7SmZm6fjy6PG4iu68u9ENCgu/bVtrtkv80Tvw= +v4Cu02hZRDsW+85CLRBC52OF7Q2oFtJnu5QwKIMu0BP97gowulUFTCsZQKMleZfSYIjeGZBAO1CNNX4e5jvdjNKUV0vrbVg3URKLSmZQu9pDsE/+LHEfAr1t4b9ExGJ+ +lfQMhYa2PjTGY/59jLRb898KNkZ/P9VvrG4wbt0MN94SnvQg6MxkeddSbK4PThoOt6FGGqNr+N4GXY+i/CDWzA== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +aEZ3meBRg3VU8t9WienCaIcMPcHcv2YGqbGshoEf2+BjZfFHOMPLysvgDh5kRgilD8P3YgAMi/iJ67yNmlvr2w== +mbg4a7OqVLuFyb4lBv/8ijnDf2TRJk/x5VtAuD8Lei+9UFA+RJ6F6QToHUji16bw +XisXz5DCD82wMRFAyU2YnBIQChWrgJ4+9q/ztStU2Rc= ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +2STnKtQfadtZuOOXKAyG97fXc39ov+eVBLi4v0OMsFg= +RGF+I9YWwlm/IovqReh8KzDsktfMik+IEctOJHKJWNfLhJfd7lO0N+PqYXq68hXW +wa6Uqxieufh+16mIo2wUWZ1mtKb/vzs433ud/yv/qeNzIjpgHyPB2b9qHji9uppV6FpACIOY4mogpGhOh+bSYg== +kFmg/dBFMjlvuWIcUrXktJM3uaPpOUszNWRf97JB6UU= +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +c0BvLCajy/2XMKFpBZTl+Iu/SkO0dvuX1US9n740cfboKQmRZYgu55JFFv3bWRzs +xM38J/T7qHoyh2Dm7AS1PeofVv7nUvjVw8J08A7tPoHKI8AfdUEMMT36J2TuAkXa +DjJQKue4V6m4p8kNUg01bEfz/+OabzCmVKUlV+DraSJuEtoAE53HRREuE0nz/mi5 ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +8asfyBO1JetUFwe4WyYO6Y7zXLxXmxgBzDu5xRXkg3A= +JkzYefpr/Q8/FMT+PiabUnsjVaeV4SXIGL0ZfwaksKSkUz13n6Mkp8jdbSsukdJu +MzOj4ZiBOJDNVP8vi/lFQAuCz8rMEK3CmFN5hj1hieddwhYaM3nQrsLOT48yBJI/oO/+O9N0Sgl2RvuU7kpn7w== +k92oDIqD3u/zM2W83z4PpVdM7SBGKtVUm1Jd80kknUc= +gyj0tuy4SMPFYgH4w+8wBY3IXPM0uWG/GrgGCA34VZw= +k92oDIqD3u/zM2W83z4Ppf0NYHDpAym7jFb0+rpC0vUFZlWzp5yZj+qpxxcuNbrk6Vcu1GyjgOQWSHm+7DcKEQ== +CAhn8dRUItEbEErp4w+lX7mnZWJD/TsN9WfGAjJJW1U= +1u+XjG/2+GSQRv6EzCaWRQ== +dq8ytOoyRUv/BeomAnpfcgN82WObFNw9OWpp+ashMco= +JkzYefpr/Q8/FMT+PiabUsLDCIfcYA9Gq0gv0IiQYerPKih0OKW4CHu8oewW1bF89ddkSdnMNn9sQNfx6cbWWA== +pTLYmNVSpbV89ewuJgN8s5DQJklspf+KlgaHmhcv0nA= +e1rLLbcLg7HKd8syQ0gecaIghAO5DHIkRtrdHrEdJec= +v4Cu02hZRDsW+85CLRBC56CB7hEAmjGZ+HomKkZKk+ti0sMqpAvIK7hwFc1+6mkIIxY3pr099j+UqOBi9ZahczUxI7FqRqgGWZSM6Xxqdbg= +uTEK8Ng11d3ix2pA+DD/aVQ101h9KjleSwZ0SYcRXd3eEzJzX3PloA8wBw8sN35krPKr/MyJpFx412GjefpG0Q== +uIsEpyb/BmTr4pwkQVVUf0RwjhiJQJf/hK04tdATmnh19x2HCfTqNvTq2rqHc5Gq +32pdC9DD05OE2l0oXazDFAdV2J9n5ffNkz5f36qCNytDp0iqq6ihW0KAr5+/dls1 +32pdC9DD05OE2l0oXazDFAdV2J9n5ffNkz5f36qCNyvubBPOn8LTVbiMDS+ibP+zvdPiwG7MZt2aBqQCUoiqByNRY4+XgGEEEtqLhpLRFzk= +L0eUthVnpkGsmKFAX6d+uOiK47GeV9U0IUhNuINx/fs= +e1rLLbcLg7HKd8syQ0gecfbZMbyjfRdQUOPnf5zIbTPr+V4LqLNiJCjGESBeFgYCEsvn7CupaoEzKXkwj2CZJsK7E0mq6ZE14nq0QU0PXcR3In/aUlH4AeU8sl7MkPKn +VmVrGQo2zRokW/ZuO9bN6/6UgeiotxsmEebrhxlDDLc8nC6Lwpn76wNAK3afDRnhMLV2RBSQaGynOLl28c/t+A== +YoCN/YJ3FGpKuVn0NXtXTgdEcSPVapklApkwW0i70PxTxKlglILzchhWiOPKZgOd +YoCN/YJ3FGpKuVn0NXtXToix6MqFL/QoVtdwEBkml6COQw8TpndvWpwBpvKNQMmTnxpUY/oF7+S3o40O72xFIA== +qxpZEz7yGh2snp/cK+0Ipk2Qv4TuHjOzatUb8F4u/nzLmmMsGUaOag81kxmq7Mlj ++jcV4UADuSsKDRH/jIi95dA2FW0TSUBn0rCfhGruLSE= +1u+XjG/2+GSQRv6EzCaWRQ== +glzTAHn8hffaBHtijzYL5UK18RwV0frcOEbqdQEdG+Pt92+be5zhscNFhL/eAiR9Tc711rcl7gLGuXJ+jzgMU+aJxXXBy1+OdZ5rcxvu5Fekj6TIakCimT7wMqEJ0BifU67RUtePcHtqliChztpO0A== +JkzYefpr/Q8/FMT+PiabUtwVJbCg1UHVw6o0ZG8VQbs= +MzOj4ZiBOJDNVP8vi/lFQAcd/8FSbCN3RDG7aQCgrl3NnBpIPZP1CuuiXlKwBhUw +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9gIulSKHdQPn1rt3eTIMEsl38gs8lF57/xlhqgd9ITkeg== +HNr5/wGY+7coHgowTe94LlheFV7l4nvszHiASjC/wO5DiwIYi0sK4qdLmDmUfgwDgE7PElyE7dR0LMY6vNGFCg== +vxZRPtvgnx9GXrXB4G/UM0PDJAHnJfAH1EU1B/RGx3flsrprR4JXQdk2AZEllj6S +gEJ/mwYAYmzZtF+bOEtqdkC8SHP+P7fOYSe7UxEq8rmitn+SghIfUQayzs5YBbRY +vxZRPtvgnx9GXrXB4G/UM0PDJAHnJfAH1EU1B/RGx3flsrprR4JXQdk2AZEllj6S +YoCN/YJ3FGpKuVn0NXtXTnef+ICKXNxnrkPoLNoM6bkjgMdyZRztWKGvv67a690e +vUSdI+D7GGPa55xHnexsw+oNL/ikozXaKZGm51a6QXc= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +dq8ytOoyRUv/BeomAnpfcvejXPvE7NiytOq8XOUMnvml0KjFGpiByzoIln/KufdW +uCeQtkgRBtN0HiqPTvhZHcPnx9uQcC8BFwZstE/shHLyfSjSJNhoIYesa6Py6Tvp +7V1Z5ICwJM9j1fYg0cpccfMkJlFKvknmjzfobvmEo2Q= +AJ6YSai03eOKVxBS+s+A+/x4xEbw8mjQ70I4HsvrlCiUwZbEJ9HrEH9cZywfcLSS +auZi0vV8Th1M5xUz1rxru4RteY+AtM3Jk2k0FxXVoO8= +1u+XjG/2+GSQRv6EzCaWRQ== +uCeQtkgRBtN0HiqPTvhZHRHF1iwllZSPFSMptoCh8TWuJckkTFe3/T7PShD60DdF +mOVDVEmzgX6HnqY4rlOl8Zf58jGyWJmgY+rfwoP7Nv05pvO42TLHLIh+VtAUs/DRVclc15+ZQbtHBFMvjOH4+w== +1u+XjG/2+GSQRv6EzCaWRQ== +bxxa5unxulFhVIdgYf2zPoytXjzesmqgb/je+IsUbJ6JAnsRAoeAvcznZO+AYYoOKvJU0MVF4EHkDTvwO3xckQ== +0TsdrJvJroIDiAiNTYLCm/OphXNS2tzCddUWOSuFG0kG0hMQku2PUAi1ctdkxSUc +99nPav/MjT1hGMzUpnNFmmALRQAqua4s0cHovmRI5xqw0Ofo7PqaOiLu3iG2GYZ1 +KbLSYqE/CR9TL3ZELtBVgxmWrwAXvlf+ZAt0WDpGZ0g= +TQRmC0vOvJ1P+lfyS3Os4RfKsX8MHWdsIncvcLq3grDmjzVmBg58wHqSjU0RBeX1aaOFThv+gxFDdjCIpUMu5/gCMPc8mAb3StkoBMislhZ6tOMK+rAXGThdJP8neMaM/NQdddCrnGqi3mjbf1dTHw== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUiGFQjtZHsM+K9kWsIdYhz6dBHCdfNBfcOUe5Em2j4R8+VSeT0UUYoi1LUHVP8q+o= +1u+XjG/2+GSQRv6EzCaWRQ== +ZPttXNTCGDK18VzoR8DzYKyka11GnDaWIOOQV28/TIXqzJRwd+oVPa9D5jLMJ3az8mUKvRlRSJMZkBfK19w0h+r4ZtAuH86gZfkH0Tx/+KydTvaOG7bIgsn7F2KqkC3cCZfprRednK2p0WuLxxKOyg== +HJa3BH4TkRX/a5B9uAn8vA== +zfLEQOz1g23nqtChgBMTbhyiGXGhWhAY2+qwZtKU2zs= +ypyfLFF6HqkDeg1sXel/og== +w3rdXVV9x6u3KSOstnEsm4PLlBhDTI+8FdH5E2Z7OE0= +IwoYJLA2ek7vWcssU2p6YbPP0gHTLA4HmzobpfTCWYhvHXK7lgr2+CDhRu7uMpje93SAESWlp6DbIzT7VQNW/w== +jKpcG7quCcerLli2mJ39vBolbg2notAcBVKYoG/EO8o= +2SlvqX/WQojOSKqcazOiBA== +cBMX/M+ODyL+hH/po9GjAA== +EL5E2qD00QeSF0FNMBo12pQWeIyq9cNwgIZqd4VEy2c= +ZVauRl8efRxWkkXsUWoKPg== +HfHZ0x6IhQ8gr8sh9V0YAAATEqIM95DuxsRArsnn8ys= +1u+XjG/2+GSQRv6EzCaWRQ== +ydM+flUAneoazE7ph9Hof3rbIj+sluwHYaxCWsNBFhE= +P5FSiL1+Pqt0jNjlHNtw3BfOokQdNQF4so/qFjceHA8= +YnAdY30Lg/9fjxdn+Sc57MTWQhZBfGNMp2YwNaHbwexla5xKL0PxQdFUmaw2L63qX8NsU0BzZj1gDBPhy4JSbPxUVe2xocQ2RpGbAoz4W5k= +h4lKXT0d0p8lHVvwNQMn+LKoo6D0qtuqywubpeGhgjP1wmiYwY0D/HtlehtN3aCW +4xkKxP0XIvHb1D4sJsHh2gZjH1Vo4xijw1ZC+9Rm+a0= +NzUVwIScy4hrAjo551BWpxLBVrqGQSD9yBLKdRgxYx0= +ft4O+SUgqJFurYX9lapEio+F4WkORg7Yc5dRgf0GAKI= +CDLUY0WWS1opA7rPYYGAHqCEWLmN7ESaQ3cddP6FtEs= +D3tDGThV5uK73D09MDbkHD3JqWj3V3TF8FWWecx67z6FQM8aWFJD78O2J9r7oGW+ +fFIb+ccq5lrZDdMOZKeSYxPj8RxEZloL9mf43eMdLFg= +DV35hS1I55lzc8Rx3j2gKq3qpS4DGEY60WsG/QAoTXw= +tbVOTg+NfLlTyWxlgN7BTrKZG0+dC3Pjrp6KzKvtS4A= +u/w7lYvOA4sXyLYt/Ceg+dy7vbWqHKAUinXlMI+xkz0= +8NeoLWUN/HbOvEj4zYku64g6MsBHB3pIxUY9k/g46pm3Nw8eblpWcMTKpfvJRflp +661hZf7vhUQ+50okfwfTXw== +o+/LpOe0noTt0q9uY7caTYSaVB9I1gwyK4s4NWQ7NUA= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOERIoWZY07F1DaLs8KSF2NE1WKeF83y1sHol2N4IutXXg== +1u+XjG/2+GSQRv6EzCaWRQ== +sDEbfywocoesN89hhGJfbn+Up/JgL1Qg2VHfdBaVSh8= +bdMrmpuFQ7yM1zhu+h/eLpcnNtalyd8b0N7ueBW8qxJW2cs9cWz74j8D2CGsBGSf +7ruGyOsuKMUu2VJjD7qOMeaAkd8lbFxmFgrVLxFs0oPIjIIPvkx0ZFyMjf6oRSyt +w4I7mVFZGDKiBbcb41eQkxM4nnk+e7mIaguT0WGkkVtz+XhNeocu4Dwlis2SGs+uEVlFlJ1TTROvWNVda6V51XiWTQVNeeq+mwE7orSeGPlP88A2APkdQ7WXcpGsRZSeQTuqKraUSJATMjQccjEKUw== +VmVrGQo2zRokW/ZuO9bN646KfasAt/rPZ4Mzei/x9X4PK6kawVkqvHaQdE9ItYIrN0R1SNQyDXs9q/cle8yTU7duHnPCfZk7g+UKQZaDYdoQ1egSYJLdZGNCNlpNm7lOjvo7TlphOadcZr0WUZLKDg== +a3jbUXPmyR9Vb7Xvsw0w8VcvNLwUIPucqyBxSewCjvpNGf4qVC9bC/Kq8Ub7+IjB541pebM9XViq+TAyzXBMtnoEC9wqaxeFrEhoP73Ucb8zxTbkd8fs/i4yUk10fuvtF4yZG4ou3qTgQ9i1iaCFJRjDi7GvIiufx6zM8N7OwL4= +FEZSdYhAV26PfOeirhfda8xXkIgxZCfPmuVck1Top9Y= +1u+XjG/2+GSQRv6EzCaWRQ== +dPf7g+b79gcCgal9DX4pOJHyAfVR/z7s6ET0GpBby7E= +0OsCC+YpIPphU/YpLoF2Kzv8rZ15USoyP9EDEDNoe4E= +lNoJsK2hWEGOaXNpZFMEKYAMKxXzTQglIlFqVZ/qv90ift2NxK3uK30zwQMR/g2E +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN68kG9x3f+w5aFN8e4/3SB7hYxvzV8Iokc8k375ZmlVH5 +VmVrGQo2zRokW/ZuO9bN69n2IH9Vunz7hXYmWIVxfwPXrXJCVKsHyYzEby8GZS9u0PX7VZIVelGtG52pT513uo3a3aGreyDkMtDppIaQp1w= +VmVrGQo2zRokW/ZuO9bN64wauWr3TvqFolPCIg7VtlOeTNs/abgaVrpCuUHCHrpv +VmVrGQo2zRokW/ZuO9bN6xoSQ3etHchjGzx9GgAoZUY1EPK486h/g4GcxqyTNCwQ +M0ZySqkmhuHCw6olbCKv97KlG7pHnkVH5PzxCv3SOm8hBQ62OlY+Ty/yX79KJJrdNu+CHQd3CPIkR67gsrZxGblpwz5ac1JVqyFJQsp3MvnS9K1ZYGSHoLMHlhFGV3zU +VmVrGQo2zRokW/ZuO9bN64/n5EL8V7CIqhp9FPzHEuQ= +0OsCC+YpIPphU/YpLoF2K6FQb9eutkS0W+twNd64F+O1GZYjvNnJ0aTvS0PBP4BFzU9cR9pgLcItJ3/8oa4lp9NS50ZiYATfufhv/OY4HvuGJ1DtfPl2w5Pl47TRv+1Q +nOflcriAnpFPxVtzU4aISA== +xUiJWUyMvL3eKTICfxM7FHlF8+ynsJSeQ8zvbn8P6ldzdd+1cSzmI5LPDCIBQ0j1BMY3iQTiHHXKXbMlTNzbIg== +4lm0ybc3aVxLaNQODc9aBanfiyaNlwa5Y83cl2wzTJM= +GJNoiDRZuv2t3D6WjWIl+qITVcmlxn4NEeTza92NOQJaBcd0iFalRE3MrJWhGMGx +VmVrGQo2zRokW/ZuO9bN63BJBJEXm+rc5N8L1OHvM55cAO+Ku+qj4muqvW20+pVMp1wYb7OOSX3teOhakn3wUJeQSJL3mZUL2AZMqu0/OdPAAjhra4OMDXmn6v6Poq55Oql860T4eKYpMhnUuGUa3A== +ACEEdgL/kNbx7RaZcSRxZL+LzF2dVZa5hwngNS0Drfw= +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +lGY+HGJ/DlLufZHWWlj7U7tQubWSU1u4v7fNn+PU7V0= +1u+XjG/2+GSQRv6EzCaWRQ== +N2ewPkaF4vBztOl5yvdzXb12XWHEkfuTMQQ/S/IatrU= +DyDHCcahxG8x9eot9BhGGoaSx73c+e9U0Tp+L9OcbF/XbjTh88pSzp7kgiuYM1B2 +7ui7GKVu+hA1zn3iOmo+va7LW68AgDWItaWEVH6a7oq2MogBYQqB9a0ERfgAfeqp ++4NbF4lLQ35m5UiB1/0oM4hG33zvxiKsPHbFeRE//WI= +ErYWeNPhZeZLxfLfOGSlIroR342Y4yDE+5gd4kGeGi+Cxk34mAtGJIhUD+XL5o6JTvUKzpV2ggBzciaY2dSBkA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl6FOpSEGBrcIkh+/0LAnZWFUAa1eWAXdpgj9j9poO/lw== +xljVybfujf1U6lUBrfLztokiw0p3LjxCtcQVbdFJCh55nLdKbOwDf5U9mlEssvrw9UuksdylMWv6nUbn2eAV3yXUyFjGRXqSEHmnN9KQFEI= +p9oVsBgcyiBMjDb8CcPOqN8UTrGCDDQu68V59h0pkaI= +L5I5tdTeebwyRuKjqW19YSTNGjiTv77wwjwChdXruX5PCPlB01g/8mzPNbcCV4Ej +JXk5JLKM/1JqEbD+ffdNPhq/wJlzDLI+uOyyhRBAqdkc8glvKXYuquXWB3Up9JjK +rybirQdlAeHusp/N7GM0sKNwBki4q4kuOQGfi5/04eredJsBktFstkBd+UVmjZ93 +/VHZ8wcOZ8J40IbgQT8a2I6Oa7H1i8uh0gQGQdXA45vcac/EKnJbhl/dTCny6O14DZIAcyGMJrA3ITncE4RtoQ== +XUWmzg0XlHIPZ8hF18QkYN0Aztefgf1QQvjhvSQd5GE= +XUWmzg0XlHIPZ8hF18QkYC7r88uJMe9MygkEWgedRMbFHgDUTHr50CWK0NfmfpFCc6v2SCxA+oYHMSSmCYEOQg== +XUWmzg0XlHIPZ8hF18QkYF7IYI93/oShp2QkjWEeQ6Ylogc+jUYWb7HAIj/dGE0fQYvQvYXUxQRCOx7MQgdI/IR1Y3tGV9ak6FKSK7TxiFkwQPI6UjElsQAwX6TenXLq +XUWmzg0XlHIPZ8hF18QkYHN/4/UmmyCivB+qOWB/viXjMw2wutt9QBKK/B8cA/K+4lFQn9SaCsqotrw0lofRsyLyy7wkeHruuK8v22leO67Nc2KhqApN6oCYo+Pi0nM/f9QBmbzkNPV3VfiLJiCN4/pIseXM4tngIvCXd8ZM5Kw= +jU/UoCJH//JN+eJXlaFsvzU8G2xBEArehtnC0GEU3kba88wtG61nlrgiA1vZDQqQbOZZmS5jYHUfwpgNaCOdal4h0gBZdfVDpEG4TnwcSco= +LjE79g4isZOsOnkgBUQGVvXGCw/Wz+rnAp975tiAu3U= +1WJNrGwGz37apsG1QXCNVX6sT/6pveI0wT7pr1N/pmU= +0kmS0E/2gTphgwOZpwAE2xWwxocpcu6AhSCb7aTGMvUAH0AwAt/+sZO/gEL4yiLF +4uuueBhid9aJeYpW9TknMTKFhi7c1ZuCDkAynCPWLEE4f5zCoL+aW6sgf4jEjnJZkRv4BgsIwv5h+Nn8Ll5k4uJ3HJUccyzNzNMSKu4HZ3E= +1u+XjG/2+GSQRv6EzCaWRQ== +1zukBHMguVb5WvA8m8HQjPjAhqCqUt1LhX5PoNgDmCwZWDEDFPUr61tKw2fBGv8i +R2NB8DN3pOWXcW6vM0ClmEMoF71ke+0fKsaH+rPi+7Q= +Gbp0us/o/RYxNCpypte8wkDs+L9ibC0lB6ODeIGeBUP1ypIAedJqMAUiTZngRtyPG7/dM83k04CPmAFPULQl+BfCYLWRfSKljUfitGeJbyO49RScFIauKLN77UJb69nA +uIsEpyb/BmTr4pwkQVVUf4sU8Bq17UUXFXa/GD5UzvsLsnfQOtQoQPl3At3HFxd9/jb+Yo4rOUkT5964mXVmAiL4B94ZUQ2ZYbYDeUMpDvSd6WZhArMZhfRTWFLLnLhM +9AUqsxjSX9s1jg3GUS+NB5ufD2BIObuhk4izDv68g49BKR0/lv0Y5YYfBQ98x9ip +MzOj4ZiBOJDNVP8vi/lFQHphDQLizMa0vztkd1r5mDy3IP/1XcaniiKAbPj57C+GJJ3I3CaODTQcpMe9YJWXTdxmyXbeuTmk1k/H/vMUQZdYd39tKeEk9fc7cJ5GDQYxYBbkxgZV9sHPPW7wy7uurg== +4QQ/zTZZdnopElj3JQHNa9PygZa4ZCO9taMSrns8R7Q= +CAhn8dRUItEbEErp4w+lX55YSYfu77vGi60UCUzYYfp8S9MgKw/toqSe6FMs2M1v +1u+XjG/2+GSQRv6EzCaWRQ== +1XSH7jd9+l3kxs8yJf4VMU+0Br69HiFgxoemVlEviZQ= +1zukBHMguVb5WvA8m8HQjOPEJCJ9+AzZ9DJkuqA/jaCsGXgslAqdvnFUngarGbeG +Gbp0us/o/RYxNCpypte8wkDs+L9ibC0lB6ODeIGeBUMOi+Fkj8eNjbINDM9sx2l63D4tJlRS3ZzbvYtl64FLqNsgxiYtNOdd98lUkskaJlI= +MzOj4ZiBOJDNVP8vi/lFQHphDQLizMa0vztkd1r5mDy3IP/1XcaniiKAbPj57C+GJJ3I3CaODTQcpMe9YJWXTdxmyXbeuTmk1k/H/vMUQZdYd39tKeEk9fc7cJ5GDQYxYBbkxgZV9sHPPW7wy7uurg== +b4OJVZe8QyIpjuTpKXDL9A== +n/Vg/an64od1JqfYD8zjtsZlIrtOVreSEto4JRdo972fXSPXC9glbleBdvuvJyBc1FrgvcEbB4s3L7BZSj5T3/i8V/IrVnojvAizmFbsfXyL5wPDxuQqWfNYU+/A8JC/ +iUEDlyYF71b358pIL4FKL4uQouNP3knkritXeMCFqD6RcAocvu7FrPVBCb4SQVlH +JomK3KjQVhm7axi7Z5yMpzSzedclpQ0EPWf4a5slcp0= +ynmt6S+38NUiCOOHWGlzRziAza4a5udNX5tgg2hzQ1k= +MIOAnnldxAFaiMyLbIEH0zOpkRUjLKhsZU8SfkygKU8= +xWoGNWjKGPfI4gq8aHoTfCbeqO6O7OYgG1AC8Y9kBQW0BOAdclPvoPuB3UjdhN0TRKx8JizDm/NQEXrzbcwHmA== +1V2v8QerKOmubvSxgB4eTDu/hxOotI87lxS6veME3Crs2k0u4BS95hFjA9PZArPg +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjaVq63HF67baKhTwF5Wt9FBPiGnoli/Hkn+AIFnauccJA== +F9Sftf1PoN5P+lgvc+r10OJ80Vg6iPphyrOoh/5x4bwMegQKx1lxJt2/4i1MN1Vk +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjY9DrK1RwfWAFBMl0E5L7BGp6Et4s9ptYJscetnOWBhjg== +F9Sftf1PoN5P+lgvc+r10GxubuveiTDZyIVny6m+75S9YktzS7qqSq3X2ySJI1VX +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjYzzneH/VPa53Tze+qkheMyIZg41dkiAdfgOiX7zd+woA== +32pdC9DD05OE2l0oXazDFNZg85sdZ06B0olaVOjcsr2UCUkKahTATxT0kBv7/uMo +n/Vg/an64od1JqfYD8zjtvTGvwYcoCTsdR9KI4oJEXjHrKKo/lK6NN3u1IyRnVmJxAC02FuWxa549NUc0lWSZQ== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOGOyBnf2jbqVHlcfZ4m8qhump3EwFJZCmZalXR+GylZU7Im9Vmh/sqbHznArZthWWo= +1u+XjG/2+GSQRv6EzCaWRQ== +1zukBHMguVb5WvA8m8HQjDdAZWNCBhb/LRFKU6AoQAPnDsBbWhpceRgHN0OMdoTg +XBwzd37QS0Dep4YK0TJlr1JCCGVIs7DD9eBryOC6u50= +mX7fgLhy/TJmbe7Pm0t6D4rgTdc0/jMdS8+hY9YrX9k= +ACEEdgL/kNbx7RaZcSRxZC+bHLMedAa4IlPYPErxwbQbb2bCimUzVAoWiGPSZjhG +J1UGlXkKhKXD36OrXXhN8DRNLBzSnpnR9zem4Hl46aYT4DaL8TwLah64w6PefGG35pZu+O411OAUqUyJw60qew== +iUEDlyYF71b358pIL4FKL042CE27KtREMjdeW1oXex7KFuFnXXodSBFBSaoKtMYZ +JomK3KjQVhm7axi7Z5yMpzSzedclpQ0EPWf4a5slcp0= +n/Vg/an64od1JqfYD8zjtgR84pQoEprp4i1Z7jZ8hf4= +VmVrGQo2zRokW/ZuO9bN6xBSBKdKa/mCsbRch2EchZY= +VmVrGQo2zRokW/ZuO9bN64/3v10sr+z8K0riZpxmj3dhtt9I77P5EN2qjRUN9+HZNXT5BzWZZUZQ1+4tzdGNlQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +7CWMhMuDAW1zoceP42HFI7JkOskde8GnIX1Kh6Fk5FtQiq0ZdpGNGcR5RDi7Pioy +ORph4c0b9qayU8qN/Z+ZPvufkaK4ZP6Z5pDSWcXufAEkqWlcZmYJvpUATBsncavX +uTEK8Ng11d3ix2pA+DD/aZwn7g4EisNaVgVRLiZl0yo= +32pdC9DD05OE2l0oXazDFJkxspwkL8t0IItwxjfnC8nUjRvvtzFDV48AlYBtpE1m +4gO44pHRPczUcmpB4bg0pnQGVtS7ogyMz4MAeBHcMtAxe2eF9/DiCFsLtpuKiL7B +uTEK8Ng11d3ix2pA+DD/aQrOlBP0FfiwhlnNZOyjgoo= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6+Iz01H3FqtnAgb7mhzu25zCCKQUPgasOEYO93p0OIwe +VmVrGQo2zRokW/ZuO9bN65rD4nI0MfwzlsBII0cdPaa77aMSTcf8piJKRZe3l5hL4FCHh2Pq7Gs/Wmj2RyJLsNkLZy9ia0AMqcf6dGGGEbVe0xvfm155GJTo9663VjJzFiKsJi7SJETzvnTYIwcltfuO8Uiv6IhgN87wNdwKZck= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6+Iz01H3FqtnAgb7mhzu25zjxGKQtsXlvqkagztbW1AG +4gO44pHRPczUcmpB4bg0pklZgQJXm0A2Am+4pJfHwxIxdfNIgu3xCJb1c+nvhf0I +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +SIVhMVja35cNhEoyyFHyU5ueFbyMySbyi9mnmmhpxVU8hpsEV8BTtrKQwQni3PGF +q4nc/jwATOMUyfSjLibfEW5DCAn6VBbRQRnaOjh7eA0= +WA9gm/QP+j5PVCbJRTSxYwp3kGb4cMqf+qwJUWVEEPnmqCBjvCtbmGcOopjB+jck +UBbQHMxDYmM54VXbqfW20S22f4jK6BUOrIYt632vyLE= +0OsCC+YpIPphU/YpLoF2K65mO2fOEgg9BDORJsJnsukfPkOuC1C9HL5KjkD4ozjN +v4Cu02hZRDsW+85CLRBC5+QhixIrX1sjRbc+k4FIGXQqvaBnxkvwl7N6zF1jmyIx+QaQW6LMymcXOKxyLELGjoDaY5dHZslh+zvOJ3nOF4o= +v4Cu02hZRDsW+85CLRBC55x7KgPFiTIBJuK7Bj8UUn6kT88eAAMKmh4eP+KGfruG +v4Cu02hZRDsW+85CLRBC52M44iq5ZlKYvAT50T1VVUxbP+lYYf2HLRnTkcnOv/7RLTiXmmYbKFrtds9nTvmC78/4+GyXpy//qMRxrz1Dp81yXvKLN7kSquN/lGm0CbyIuiiLCxeHe7ZblRuSW5w/ew== +tWSLqD1lvEP9Rpp6I27PpvXrraKAQUvuzpkWxLOkdYVAxT6x12Fs+vuA/HccQbq+SMpYqdMtX58R+H1Pdla69g== +kJxoTVctk+bGjMYgKxsjjSmeY05Z0U8C6JFZmjUMMCSxIzLqQNB1ftjf5Kn92nPVt550MwxW8IyDCqJ4Q9eC3FQUIBmzYkMvbBDJNih80rnEO1NVUGYABtrZq/94OTpqywj+vwL3UkXPUL3+35U/jA8nAEIOSPnzN1fJFjnTR3Mx+gPRivpe27HbNFgHbZPg +VmVrGQo2zRokW/ZuO9bN679dY2ryxqp4ouOaU1fOIulOqWd5KUkmwSOGfFCgfWHwgteTq1jrO4iW0E88BvDnPw== +f58SBztD4UCgJ/6kPztVo29CIZDUF3sUhAF1SPuV/14= +lGY+HGJ/DlLufZHWWlj7U9tymVCSNLDh3vOFZaRUBuc= +1u+XjG/2+GSQRv6EzCaWRQ== +nfkJ3QMSmeMN6/r/kF1fGVfrMpks76Z9i1HsARq3TTrMiMLoOh4ZCWV7E4Vz9LdZ +b4OJVZe8QyIpjuTpKXDL9A== +HUxaWbdFNk9pZ+7x3i/kLN34rJMFESnZNqgFnOTtmncZ9MojyT4ik/ZLHKgOBvG5jYiwtWKmCbv5/tg2EykmXA== +NhnIR3Ilo4H2su9/cTNo/Ps3vYSOiOY6esn1xeoWmAlrdRFFnm+r4q+F9O7KjdciOkRcYwkz4orBueV5Pha+Cse7WK7UEu4tjn7yjq09/pc+cqG1kgwWKp3q5L/lI5vjJjeYoh0VOhmY1zWisiB2GA== +NhnIR3Ilo4H2su9/cTNo/IbXc6jmjXpdg3/b5Q0M//5RfYowE2A9IKSdANsXKONc +xWoGNWjKGPfI4gq8aHoTfDMe4+Xx9fgEfvyQdRt49XptGFFda7+W0fuqWp8j+ScVv1QIofgDM/c9givgAZheB/DaAF8Pow88H+SBfdPZZ4jcj49xI9n48vcxq/0T0GE740KKNSWuK4oLY4A7yJGVoQ== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmVQbPz0K1B+JfSV5r/HF6jqLH8cmj7UBpEkhkC5vwSdJiX6F5/J3ugOxIGdW0OYNc0= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU15HDjUiZ7JjFyYpKbXtItUI7uFAiW+e3188BVvbtoYsMsrFB5QSqjIo1lExvyACE= +1u+XjG/2+GSQRv6EzCaWRQ== +KiWYwpJCvVh7pD2s6nbEY7wXqZOCFGVUVZew5Gnpwt8= +1FfqRJIaTv+Hy0UdnrEpXkay2ZR4P6JEneOylPoQjT4= +zuZzq5dzvL2MRNAsfG0GjGS3XRmeqy+aZeovGa0ddfmPuMT372mCGqv5UCgCM70U96xDK5ggO1vY7bxu4j7xZw== +UV6WV80R3POE+S2K3cNp7P8Mv8jJnUdC1uM4InSlYRdgWjmsFAY1Yv6SAghLmt+Z1uNYJTE0FCHSaq2WCw+hAD1VlStivegR9nS5eYiwWTbHvruE50WCRVePmr2csL9FBcZyhOMdNkh6yyet8BjdTWVSVE2DvEeOPGZweOjUuVfdyssacYMY8kpmMeAXJCQZzEKf8FtQyhxu4jwtGrWL0zOkZ0wc31ErPE5IJA9N7PaGOrVXr0cAuoe41Ln59cR8 +Z6/7wkt3BMqq5Y+jFo+XDteIB3YuTrio4xDrY/2mxFGTQQw8rj+Pf/07PQWC6gVc4/AgxP21txHQ55SSLduaLntVy0GJ2oZ1QThqCJYi/ufkZzyq/RuE2mhC9XeesVnY7KYQNLHsRETfFbW9qQwBcou327Gu6KyBZX0FCiCKdOkrgi08pT1UhEfkPl2xtl6n +OlFvTKqdeKF9w1tvvZyIDIlrvmeU2j5RIq361TZK32CoUU7Sp0y2L69Ua/cywMdp+xGru68zvCs9UX+9aSDAWdcgeqHTt075jI3L1avZ+IJzUumjKqoMSy/0Gz0O2WXL/A4k0Uy0LdNUuoGLQnik3CKOXgkYz4ehwQy6SXtjp1oxsqwM9s9nWL2zXBq84gip +gIITPXcfsbW14W9koRVg6+ks1MbgMFupDxmIAXQD1+wQRYZtxytn4ZO0xf8FvXU7v6FMbDhOVAMvZ2gNlztbwouh64TAdrICnyRUdjS9gKDjFXle/XzV7PYyDITAX8Ncgj+hmjXsFIkGWeOBwcvW/CVhFE7DKlDfxEh8ey+9cbo= +AE2D69YCySZnqC/KuUyd6AGsybxWLngSTsMfLA1YPCDQ8OFTsaFWXrQdusJJC4BgaslHwTfLZ4zb+ogwukgpSIAIhczFRgh/FEDYi0hg+gD2GhIBJ0qhSVsEPRLkD8eLCfeGs6UeU/NnAjJ9oDU1cg== +q86vJ1Gk5zoQJkWiDC3EDHW4ie7aSVy+iRJ3e5uBreLs6ZQtRFcPVSGF/trh4arUs8mDj8ng6qc2rwvI6mN7jTnI71Exksa2VyI54IXv4nv+BB+4yTO6aodPKVdzify2IBKNqfXxRXk6ilDU1vHO2rkx95moNbUc6kyrj8IbUqd60QYXhI8CjvMxq7HH1Q3MzHDUyw6fbHDq7qnF+Rv3lo9GFL9SrjekFvd0SOebGZI= +zuZzq5dzvL2MRNAsfG0GjKackRzN4eqfmNCRlFdHKtoBdzMtMyHhousMEQBwm83oe/xgM3D+/pZgpf1SjYBf60uCeaj83bT8cJail/HfXaCx3s3PPxj8nCUhsIV2rZth +VwBeeHn0i7Rn77yuYYaoYAGyL2u8AE+N89j694UfCTBSuvToVWInwIjwpY3O5TTlXMYhYkXrEoWyIlLdm7CYSHbtthxJ8Pj0kfd4KtUDR0Aecm8yBUCIL4LcpHPJTU5bWz/wO1BA+kb2c6vsNVsZnA5YkucErGu/sIb5b9FARGMgb2gbl/S1DHuet5hagyTl0HWyCrmt9gWiJJCZWlFaMBajwj0n5Hz3Mfd0v5n+jEo= +LDoWyYEVft11g2fMaUoWVhpfUwmL6xqf/f8HKcv/obIjZ0iSUxIWeEALc64BWdSPh2owyFLaPfUr6VN+kAteQbvubruXVrEfzmIiKpMrJ3KJUhLMFh4qb9B9mZbeDsjWMF47MT2oithWcDJ/Gz/RJA== +qzAWBJSCd8usbTDm9R1x/MXzPL0m0Ud34Ai1Rm7vYQS0Z/5HPh7Ulgw2tiTRxDZoyNlyAAsEZLXIOjGA8iawFQLTD8emXrTP9qRXpa6atJPleFgDNTG9RcjjeaHO0zVFO3yo4ivEgRqjHAW8Ia5LYGC64Ot0c0E9ei+Ff9nFBWM886RF+jVAdZonG3r+IoU/R8wlmHi0CJufxZVaAzkMHw== +gDAZChPAe4zQXRH7y194sqbv3RpQagsxOjI/3tEGN2kNVglD/S6+7T+d1UEDnLy3FbRxzTJc4aRq7ttywGcL75jdKodi/K4ANqywo6BII1NpkgMWQPyhWLLkMtLdIJiG04maEg0DkaXb2mWJtdKQ7BI4wJ1cxITvgvzlLe4c9yc+hOc1qKaBJXezPFVrYafL +661hZf7vhUQ+50okfwfTXw== +FEZSdYhAV26PfOeirhfda8qGB+2DE0ehPq4imkXwDHE= diff --git a/class/msg/dingding_msg.py b/class/msg/dingding_msg.py index 033138eb..504b07da 100644 --- a/class/msg/dingding_msg.py +++ b/class/msg/dingding_msg.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # | 消息通道邮箱模块 # +------------------------------------------------------------------- diff --git a/class/msg/feishu_msg.py b/class/msg/feishu_msg.py index 2a489478..c71877fc 100644 --- a/class/msg/feishu_msg.py +++ b/class/msg/feishu_msg.py @@ -1,8 +1,8 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: lx # | 消息通道飞书通知模块 diff --git a/class/msg/mail_msg.py b/class/msg/mail_msg.py index 488ab061..9f14bf93 100644 --- a/class/msg/mail_msg.py +++ b/class/msg/mail_msg.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # | 消息通道邮箱模块 # +------------------------------------------------------------------- diff --git a/class/msg/sms_msg.py b/class/msg/sms_msg.py index 13653c54..ee0396e1 100644 --- a/class/msg/sms_msg.py +++ b/class/msg/sms_msg.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # | 消息通道邮箱模块 # +------------------------------------------------------------------- diff --git a/class/msg/tg_msg.py b/class/msg/tg_msg.py index 4df51fe2..d0c8f9c6 100644 --- a/class/msg/tg_msg.py +++ b/class/msg/tg_msg.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: jose +# | Author: jose # | 消息通道电报模块 # +------------------------------------------------------------------- diff --git a/class/msg/weixin_msg.py b/class/msg/weixin_msg.py index ded85e7e..c001347a 100644 --- a/class/msg/weixin_msg.py +++ b/class/msg/weixin_msg.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # | 消息通道邮箱模块 # | 常用功能 diff --git a/class/msg/wx_account_msg.py b/class/msg/wx_account_msg.py index 9f1a0cc0..5d25e681 100644 --- a/class/msg/wx_account_msg.py +++ b/class/msg/wx_account_msg.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # | 消息通道邮箱模块 # +------------------------------------------------------------------- diff --git a/class/nginx.py b/class/nginx.py index 44bfcf22..c96361f1 100644 --- a/class/nginx.py +++ b/class/nginx.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -38,7 +38,7 @@ class nginx: gets = ["worker_processes","worker_connections","keepalive_timeout","gzip","gzip_min_length","gzip_comp_level","client_max_body_size","server_names_hash_bucket_size","client_header_buffer_size"] n = 0 for i in gets: - rep = "(%s)\s+(\w+)" % i + rep = r"(%s)\s+(\w+)" % i k = re.search(rep, ngconfcontent) if not k: return public.return_msg_gettext(False,"Get key {} False".format(k)) @@ -64,7 +64,7 @@ class nginx: gets = ["client_body_buffer_size"] n = 0 for i in gets: - rep = "(%s)\s+(\w+)" % i + rep = r"(%s)\s+(\w+)" % i k = re.search(rep, proxycontent) if not k: return public.return_msg_gettext(False,"Get key {} False".format(k)) @@ -88,14 +88,14 @@ class nginx: n+=1 return conflist - def SetNginxValue(self,get): + def SetNginxValue(self, get: public.dict_obj): ngconfcontent = public.readFile(self.nginxconf) proxycontent = public.readFile(self.proxyfile) if public.get_webserver() == 'nginx': shutil.copyfile(self.nginxconf, '/tmp/ng_file_bk.conf') shutil.copyfile(self.proxyfile, '/tmp/proxyfile_bk.conf') conflist = [] - getdict = get.__dict__ + getdict = get.get_items() for i in getdict.keys(): if i != "__module__" and i != "__doc__" and i != "data" and i != "args" and i != "action": getpost = { @@ -105,13 +105,13 @@ class nginx: conflist.append(getpost) for c in conflist: - rep = "%s\s+[^kKmMgG\;\n]+" % c["name"] + rep = r"%s\s+[^kKmMgG\;\n]+" % c["name"] if c["name"] == "worker_processes" or c["name"] == "gzip": - if not re.search("auto|on|off|\d+", c["value"]): - return public.return_msg_gettext(False, 'Parameter ERROR!') + if not re.search(r"auto|on|off|\d+", c["value"]): + return public.return_msg_gettext(False, 'Parameter ERROR! -1') else: - if not re.search("\d+", c["value"]): - return public.return_msg_gettext(False, 'Parameter ERROR!') + if not re.search(r"\d+", c["value"]): + return public.return_msg_gettext(False, 'Parameter ERROR! -2') if re.search(rep,ngconfcontent): newconf = "%s %s" % (c["name"],c["value"]) ngconfcontent = re.sub(rep,newconf,ngconfcontent) @@ -132,7 +132,7 @@ class nginx: def add_nginx_access_log_format(self,args): ''' @name 添加日志格式 - @author zhwen + @author zhwen @param log_format 需要设置的日志格式["$server_name","$remote_addr","-"....] @param log_format_name @param act 操作方式 add/edit @@ -151,7 +151,7 @@ class nginx: conf = public.readFile(self.nginxconf) if not conf: return public.return_msg_gettext(False,'Nginx configuration file does not exist!') - reg = 'http(\n|\s)+{' + reg = r'http(\n|\s)+{' conf = re.sub(reg,'http\n\t{'+data,conf) public.writeFile(self.nginxconf,conf) public.serviceReload() @@ -162,14 +162,14 @@ class nginx: def del_nginx_access_log_format(self,args): ''' @name 删除日志格式 - @author zhwen + @author zhwen @param log_format_name ''' log_format_name = args.log_format_name conf = public.readFile(self.nginxconf) if not conf: return public.return_msg_gettext(False, 'Nginx configuration file does not exist!') - reg = '\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name) + reg = r'\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name) conf = re.sub(reg,'',conf) self._del_format_log_of_website(log_format_name) public.writeFile(self.nginxconf,conf) @@ -227,7 +227,7 @@ class nginx: format_name = [i.split('LOG_FORMAT_BEGIN_')[-1] for i in data] format_log = {} for i in format_name: - format_reg = "#LOG_FORMAT_BEGIN_{n}(\n|.)+log_format\s+{n}\s*(.*);".format(n=i) + format_reg = r"#LOG_FORMAT_BEGIN_{n}(\n|.)+log_format\s+{n}\s*(.*);".format(n=i) tmp = re.search(format_reg,conf) if not tmp: continue @@ -240,7 +240,7 @@ class nginx: def set_format_log_to_website(self,args): ''' @name 设置日志格式 - @author zhwen + @author zhwen @param sites aaa.com,bbb.com @param log_format_name ''' @@ -248,13 +248,13 @@ class nginx: sites = loads(args.sites) try: all_site = public.M('sites').field('name').select() - reg = 'access_log\s+/www.*{}\s*;'.format(args.log_format_name) + reg = r'access_log\s+/www.*{}\s*;'.format(args.log_format_name) for site in all_site: website_conf_file = '/www/server/panel/vhost/nginx/{}.conf'.format(site['name']) conf = public.readFile(website_conf_file) if not conf: return public.return_msg_gettext(False, 'Nginx configuration file does not exist!') - format_exist_reg = '(access_log\s+/www.*\.log).*;' + format_exist_reg = r'(access_log\s+/www.*\.log).*;' access_log = self.get_nginx_access_log(conf) if not access_log: continue @@ -272,7 +272,7 @@ class nginx: def get_nginx_access_log(self,nginx_conf): try: - reg = 'access_log\s+(.*\.log)' + reg = r'access_log\s+(.*\.log)' log_path = re.findall(reg, nginx_conf) if not log_path: return False @@ -309,7 +309,7 @@ class nginx: if not site_format_log_status[s]: continue website_conf_file = '/www/server/panel/vhost/nginx/{}.conf'.format(s) - format_exist_reg = 'access_log\s+/www.*\.log\s+{};'.format(log_format_name) + format_exist_reg = r'access_log\s+/www.*\.log\s+{};'.format(log_format_name) conf = public.readFile(website_conf_file) if not conf:continue if not re.search(format_exist_reg,conf):continue diff --git a/class/ols.py b/class/ols.py index 8fb22f7c..cb0364e7 100644 --- a/class/ols.py +++ b/class/ols.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: zhwwen +# Author: zhwwen # ------------------------------------------------------------------- # # ------------------------------ @@ -26,7 +26,7 @@ class ols: conf = public.readFile(self._main_conf_path) data = {} for k in keys: - rep = '{}\s+(\w+)'.format(k) + rep = r'{}\s+(\w+)'.format(k) tmp = re.search(rep,conf) if tmp: data[k] = tmp.groups(1) @@ -43,7 +43,7 @@ class ols: conf = public.readFile(self._main_conf_path) data = json.loads(get.array) for k in data: - rep = '{}\s+(\w+)'.format(k) + rep = r'{}\s+(\w+)'.format(k) tmp = re.search(rep,conf) if tmp: conf = re.sub('{}.*'.format(k),'{} {}'.format(k,data[k]),conf) @@ -58,24 +58,24 @@ class ols: data = {} sitename = public.M('sites').where("id=?", (get.id,)).getField('name') conf = public.readFile(self._detail_conf_path.format(sitename)) - rep = 'expiresByType\s+(.*)' + rep = r'expiresByType\s+(.*)' tmp = re.search(rep,conf) if tmp: tmp = tmp.groups(1)[0].split(',') for i in tmp: if 'image' in i: - rep = 'image\/\*=A(\d+)' + rep = r'image\/\*=A(\d+)' data['image'] = re.search(rep,conf).groups(1)[0] print(data['image']) if 'css' in i: - rep = 'css=A(\d+)' + rep = r'css=A(\d+)' data['css'] = re.search(rep, conf).groups(1)[0] if 'javascript' in i: - rep = 'javascript=A(\d+)' + rep = r'javascript=A(\d+)' data['javascript'] = re.search(rep, conf).groups(1)[0] if 'font' in i: - rep = 'font.*=A(\d+)' + rep = r'font.*=A(\d+)' data['font'] = re.search(rep, conf).groups(1)[0] return data @@ -92,12 +92,12 @@ class ols: print(self._detail_conf_path.format(sitename)) values = json.loads(get.values) for k in values: - rep = '{}/[\*\w+=]=A\d+'.format(k) + rep = r'{}/[\*\w+=]=A\d+'.format(k) old_cache = re.search(rep,conf) if not old_cache: continue old_cache = old_cache.group() - new_cache = re.sub('\d+',values[k],old_cache) + new_cache = re.sub(r'\d+',values[k],old_cache) conf = conf.replace(old_cache,new_cache) public.writeFile(self._detail_conf_path.format(sitename),conf) public.serviceReload() @@ -127,13 +127,13 @@ class ols: if tmp: # 获取排除文件 tmp = tmp.group() - rep = '#\s*excluding.*\n.*\((.*)\)' + rep = r'#\s*excluding.*\n.*\((.*)\)' tmp = re.search(rep,tmp) if tmp: data['exclude_file'] = [i+'.php' for i in tmp.groups(1)[0].split('|')] print(data) # 获取缓存时间 - rep = 'max-age=(\d+)' + rep = r'max-age=(\d+)' tmp = re.search(rep,conf) if tmp: data['maxage'] = tmp.groups(1)[0] @@ -154,7 +154,7 @@ class ols: conf = self.get_private_cache_conf(get.id)[0] file = self.get_private_cache_conf(get.id)[1] if 'BTLSCACHE_BEGIN' not in conf: - confstr = """#######################BTLSCACHE_BEGIN####################### + confstr = r"""#######################BTLSCACHE_BEGIN####################### RewriteEngine on @@ -206,10 +206,10 @@ RewriteRule (.*\.php)?$ - [E=Cache-Control:private] for file in get.exclude_file.split('\n'): exclude_files.append(file.split('.')[0]) exclude_file = "|".join(exclude_files) - old_exc_rep = 'RewriteCond\s+\%\{REQUEST_URI\}\s+\!/\(.*\)\\\.php\$' - new_exc = 'RewriteCond %{REQUEST_URI} !/(' + exclude_file + ')\.php$' + old_exc_rep = r'RewriteCond\s+\%\{REQUEST_URI\}\s+\!/\(.*\)\\\.php\$' + new_exc = 'RewriteCond %{REQUEST_URI} !/(' + exclude_file + r')\.php$' bt_conf = re.sub(old_exc_rep,new_exc,bt_conf) - old_max_age_rep = 'max-age=\d+' + old_max_age_rep = r'max-age=\d+' new_max_age = 'max-age={}'.format(int(get.max_age)) bt_conf = re.sub(old_max_age_rep,new_max_age,bt_conf) conf = re.sub(bt_conf_rep,bt_conf,conf) @@ -247,11 +247,11 @@ RewriteRule (.*\.php)?$ - [E=Cache-Control:private] conf = public.readFile(path) ap_path = '/www/server/panel/vhost/apache/{}.conf'.format(s['sitename']) ap_conf = public.readFile(ap_path) - tmp = re.search('ServerName\s+SSL\.(.*)',ap_conf) + tmp = re.search(r'ServerName\s+SSL\.(.*)',ap_conf) s['phpv'] = re.search(phpv_reg,conf).groups(1)[0] s['rundir'] = re.search(rundir_reg.format(s),conf).groups(1)[0] s['ssl_domain'] =tmp.groups(1)[0] if tmp else None - s['port'] = re.search('listen\s+(\d+);',conf).groups(1)[0] + s['port'] = re.search(r'listen\s+(\d+);',conf).groups(1)[0] return siteinfo def _make_args(self): diff --git a/class/one_key_wp.py b/class/one_key_wp.py index 2eba30e8..d9ca1c7d 100644 --- a/class/one_key_wp.py +++ b/class/one_key_wp.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: zhwen +# | Author: zhwen # +------------------------------------------------------------------- import os import time @@ -73,7 +73,7 @@ class one_key_wp: else: self.get_plugin_page() resp = self.__plugin_page_content - _ajax_nonce_rep = '"ajax_nonce\\\"\:\\\"(\w+)' + _ajax_nonce_rep = '"ajax_nonce\\\"\\:\\\"(\\w+)' rex = re.search(_ajax_nonce_rep, resp) if rex: self.__ajax_nonce = rex.group(1) @@ -130,7 +130,7 @@ class one_key_wp: # hosts = public.readFile('/etc/hosts') # if not hosts: # return False - # if not re.search('127.0.0.1\s+{}'.format(self.__domain),hosts): + # if not re.search(r'127.0.0.1\s+{}'.format(self.__domain),hosts): # public.writeFile('/etc/hosts','\n127.0.0.1 {}'.format(self.__domain),'a+') self.__session_resp = retry( @@ -270,7 +270,7 @@ class one_key_wp: hosts = public.readFile('/etc/hosts') if not hosts: return False - if not re.search('127.0.0.1\s+{}'.format(values['site_name']), hosts): + if not re.search(r'127.0.0.1\s+{}'.format(values['site_name']), hosts): public.writeFile('/etc/hosts', '\n127.0.0.1 {}'.format(values['site_name']), 'a+') self.write_logs("|-Start initializing Wordpress...") @@ -362,7 +362,7 @@ class one_key_wp: def get_update_wp_nonce(self): url = "http://{}/wp-admin/update-core.php".format(self.__domain) # res = self.action_plugin_get(url) - _wp_nonce_rep = '"_wpnonce\\\"\svalue=\\\"(\w+)' + _wp_nonce_rep = '"_wpnonce\\\"\\svalue=\\\"(\\w+)' # rex = re.search(_wp_nonce_rep,res) # if rex: # self.__wp_nonce = rex.group(1) @@ -414,7 +414,7 @@ class one_key_wp: def get_smart_http_expire_form_nonce(self, url): public.writeFile('/tmp/2', str(self.action_plugin_get(url))) - smart_http_expire_form_nonce = '"smart_http_expire_form_nonce\\\"\svalue=\\\"(\w+)' + smart_http_expire_form_nonce = '"smart_http_expire_form_nonce\\\"\\svalue=\\\"(\\w+)' return self.get_wp_nonce(self.action_plugin_get(url), smart_http_expire_form_nonce) def set_nginx_helper(self, values): @@ -557,7 +557,7 @@ class one_key_wp: conf_file = "{}/wp-includes/version.php".format(path) conf = public.readFile(conf_file) try: - version = re.search('\$wp_version\s*=\s*[\'\"]{1}([\d\.]*)[\'\"]{1}', conf).groups(1)[0] + version = re.search('\\$wp_version\\s*=\\s*[\'\"]{1}([\\d\\.]*)[\'\"]{1}', conf).groups(1)[0] except: version = "00" return version @@ -569,7 +569,7 @@ class one_key_wp: 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36' } result = requests.get(url, headers=headers) - result = re.search('Download\s+WordPress\s+([\d\.]+)', result.text) + result = re.search(r'Download\s+WordPress\s+([\d\.]+)', result.text) if result: return result.group(1) return "00" @@ -793,13 +793,13 @@ class one_key_wp: rep_domain = r"^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$" values = {} if hasattr(args, 'd_id'): - if re.search('\d+', args.d_id): + if re.search(r'\d+', args.d_id): values["d_id"] = args.d_id else: return public.return_msg_gettext(False, "Please check if the [{}] format is correct For example: {}", ("d_id", "99")) if hasattr(args, 's_id'): - if re.search('\d+', args.s_id): + if re.search(r'\d+', args.s_id): values["s_id"] = args.s_id else: return public.return_msg_gettext(False, "Please check if the [{}] format is correct For example: {}", @@ -1218,7 +1218,7 @@ class optimize_db: class fast_cgi: def get_fastcgi_conf(self, version): - conf = """ + conf = r""" set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; @@ -1294,7 +1294,7 @@ class fast_cgi: one_key_wp().write_logs("|-Nginx FastCgi cache configuration already exists") print("Nginx FastCgi cache configuration already exists") return public.return_msg_gettext(True, "Nginx FastCgi cache configuration already exists") - rep = "http\s*\n\s*{" + rep = "http\\s*\n\\s*{" content = re.sub(rep, "http\n\t{" + conf, content) public.writeFile(conf_path, content) @@ -1328,7 +1328,7 @@ class fast_cgi: print("Nginx init FastCgi cache configuration already exists") return public.return_msg_gettext(True, "Nginx init FastCgi cache configuration already exists") # content_init = re.sub(r"\$NGINX_BIN -c \$CONFIGFILE", + conf2, content_init) - rep2 = "\$NGINX_BIN -c \$CONFIGFILE" + rep2 = r"\$NGINX_BIN -c \$CONFIGFILE" content_init = re.sub(rep2, conf2 + " $NGINX_BIN -c $CONFIGFILE", content_init) public.writeFile(init_path, content_init) @@ -1366,7 +1366,7 @@ class fast_cgi: print("FastCgi configuration does not exist in website configuration") one_key_wp().write_logs("|-FastCgi configuration does not exist in website configuration, skip") return public.return_msg_gettext(False, "FastCgi configuration does not exist in website configuration") - rep = "include\s+enable-php-{}-wpfastcgi.conf;".format(version) + rep = r"include\s+enable-php-{}-wpfastcgi.conf;".format(version) conf = re.sub(rep, "include enable-php-{}.conf;".format(version), conf) else: fastcgi_conf = "include enable-php-{}-wpfastcgi.conf;".format(version) @@ -1375,7 +1375,7 @@ class fast_cgi: "|-The FastCgi configuration already exists in the website configuration, skip it") return public.return_msg_gettext(True, "The FastCgi configuration already exists in the website configuration") - rep = "include\s+enable-php-{}.conf;".format(version) + rep = r"include\s+enable-php-{}.conf;".format(version) conf = re.sub(rep, fastcgi_conf, conf) public.writeFile(conf_path, conf) conf_pass = public.checkWebConfig() diff --git a/class/page.py b/class/page.py index e8ebf7a3..fecddd26 100644 --- a/class/page.py +++ b/class/page.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import math,string,public,re @@ -200,8 +200,8 @@ class Page(): def __SetUri(self,request_uri): #构造URI try: - request_uri = re.sub("&p=\d+",'&',request_uri) - request_uri = re.sub("\?p=\d+",'?',request_uri) + request_uri = re.sub(r"&p=\d+",'&',request_uri) + request_uri = re.sub(r"\?p=\d+",'?',request_uri) if request_uri.find('&') == -1: if request_uri[-1] != '?': request_uri += '?' else: diff --git a/class/panelApi.py b/class/panelApi.py index 9d6a5b8f..823922a9 100644 --- a/class/panelApi.py +++ b/class/panelApi.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import public,os,json,time class panelApi: diff --git a/class/panelAuth.py b/class/panelAuth.py index 142059e7..9765bb0a 100644 --- a/class/panelAuth.py +++ b/class/panelAuth.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2019 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -60,8 +60,8 @@ class panelAuth: def check_serverid(self,get): if get.serverid != self.create_serverid(get): return False return True - # 旧接口 没有永久版 - def get_plugin_price2(self, get): + + def get_plugin_price(self, get): try: userPath = 'data/userInfo.json' if not 'pluginName' in get and not 'product_id' in get: return public.return_msg_gettext(False,'Parameter ERROR!') @@ -71,7 +71,7 @@ class panelAuth: params['product_id'] = self.get_plugin_info(get.pluginName)['id'] else: params['product_id'] = get.product_id - data = self.send_cloud('{}/api/product/prices'.format(self.__official_url), params) + data = self.send_cloud('{}/api/product/pricesV3'.format(self.__official_url), params) if not data: return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!') if not data['success']: @@ -82,61 +82,7 @@ class panelAuth: except: del(session['get_product_list']) return public.return_msg_gettext(False,'Syncing information, please try again!\n {}',(public.get_error_info(),)) - - # 获取永久版信息 - def get_plugin_price3(self, get): - try: - userPath = 'data/userInfo.json' - if not 'pluginName' in get and not 'product_id' in get: - return public.return_msg_gettext(False,'Parameter ERROR!') - if not os.path.exists(userPath): - return public.return_msg_gettext(False,'Please login with account first') - params = {} - if not hasattr(get,'product_id'): - params['product_id'] = self.get_plugin_info(get.pluginName)['id'] - else: - params['product_id'] = get.product_id - - data = self.send_cloud('{}/api/product/pricesV2'.format(self.__official_url), params) - - if not data: - return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!') - if not data['success']: - return public.return_msg_gettext(False,data['msg']) - return data['res'] - except: - # del(session['get_product_list']) - return public.return_msg_gettext(False,'Syncing information, please try again!\n {}',(public.get_error_info(),)) - - # 获取价格列表 新增多机购买 - def get_plugin_price(self, get): - try: - userPath = 'data/userInfo.json' - if not 'pluginName' in get and not 'product_id' in get: - return public.return_msg_gettext(False, 'Parameter ERROR!') - if not os.path.exists(userPath): - return public.return_msg_gettext(False, 'Please login with account first') - params = {} - if not hasattr(get, 'product_id'): - params['product_id'] = self.get_plugin_info(get.pluginName)['id'] - else: - params['product_id'] = get.product_id - - data = self.send_cloud('{}/api/product/pricesV3'.format(self.__official_url), params) - - if not data: - return public.return_msg_gettext(False, 'Please log in to your aaPanel account on the panel first!') - if not data['success']: - return public.return_msg_gettext(False, data['msg']) - return data['res'] - except Exception as ex: - public.print_log("获取价格报错 {}".format(ex)) - # del(session['get_product_list']) - return public.return_msg_gettext(False, 'Syncing information, please try again!\n {}', - (public.get_error_info(),)) - - - + def get_plugin_info(self,pluginName): data = self.get_business_plugin(None) if not data: return None @@ -183,7 +129,6 @@ class panelAuth: return public.return_msg_gettext(False, data['res']) return data['res'] - def get_stripe_session_id(self,get): params = {} @@ -413,8 +358,7 @@ class panelAuth: data = self.send_cloud('{}/api/user/productAuthorizes'.format(self.__official_url), params) if not data: return [] - if not data['success']: - return [] + if not data['success']: return [] data = data['res'] # return [i for i in data['list'] if i['status'] != 'activated' and get.pid == i['product_id']] res = list() @@ -444,7 +388,7 @@ class panelAuth: if hasattr(get,'coupon_id') and get.pay_channel == '10': params['coupon_id'] = get.coupon_id data = self.send_cloud('{}/api/authorize/product/renew'.format(self.__official_url), params) - # public.print_log('############ 续费接口 {}'.format(data)) + if not data['success']: data['res'] = 'Invalid authorize OR authorize not found!' return data diff --git a/class/panelBackup.py b/class/panelBackup.py index a5e98f8b..6e21c50c 100644 --- a/class/panelBackup.py +++ b/class/panelBackup.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------ @@ -422,10 +422,30 @@ class backup: def backup_site(self, siteName, save=3, exclude=[], echo_id=None): try: self.echo_start() - find = public.M('sites').where('name=?', (siteName,)).field('id,path').find() + find = public.M('sites').where('name=?', (siteName,)).field('id,path,project_type').find() public.print_log(find) - if not find: + if not find or not isinstance(find, dict): raise Exception(' The directory for does not exist') + + # Wordpress + if find['project_type'] == 'WP2': + import PluginLoader + if PluginLoader.get_auth_state() < 1: + self.echo_info(public.get_msg_gettext('Authorization is invalid or expired')) + return None + + try: + from wp_toolkit import wpbackup + bak_info = wpbackup(find['id']).backup_full_get_data() + self.echo_info(public.get_msg_gettext('Backup wordpress [{}] successfully', (siteName,))) + self.echo_end() + return bak_info.bak_file + except Exception as e: + public.print_error() + self.echo_info(str(e)) + self.echo_end() + return None + spath = find['path'] pid = find['id'] fname = 'web_{}_{}.tar.gz'.format(siteName, public.format_date("%Y%m%d_%H%M%S")) diff --git a/class/panelController.py b/class/panelController.py index 0c6a9d56..de08d879 100755 --- a/class/panelController.py +++ b/class/panelController.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -32,8 +32,8 @@ class Controller: if args['mod_name'] in ['base']: return public.return_status_code(1000,'错误的调用!') public.exists_args('def_name,mod_name',args) if args['def_name'].find('__') != -1: return public.return_status_code(1000,'调用的方法名称中不能包含“__”字符') - if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'调用的模块名称中不能包含\w以外的字符') - if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'调用的方法名称中不能包含\w以外的字符') + if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'调用的模块名称中不能包含\w以外的字符') + if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'调用的方法名称中不能包含\w以外的字符') except: return public.get_error_object() # 参数处理 diff --git a/class/panelDatabaseController.py b/class/panelDatabaseController.py index be1cf3c3..ffe87b59 100644 --- a/class/panelDatabaseController.py +++ b/class/panelDatabaseController.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -32,8 +32,8 @@ class DatabaseController: if args['mod_name'] in ['base']: return public.return_status_code(1000,'Bad call!') public.exists_args('def_name,mod_name',args) if args['def_name'].find('__') != -1: return public.return_status_code(1000,'The called method name cannot contain the "__" character') - if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w') - if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w') + if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w') except: return public.get_error_object() # 参数处理 diff --git a/class/panelDefense.py b/class/panelDefense.py index 63e1b031..a8e3943d 100644 --- a/class/panelDefense.py +++ b/class/panelDefense.py @@ -1,8 +1,8 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: hwliang # +------------------------------------------------------------------- diff --git a/class/panelDnsapi.py b/class/panelDnsapi.py index bd1d3337..06e28701 100644 --- a/class/panelDnsapi.py +++ b/class/panelDnsapi.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # +------------------------------------------------------------------- import public,os,sys,json,time,random import requests diff --git a/class/panelHttpProxy.py b/class/panelHttpProxy.py index a9df1f2e..34b32b53 100644 --- a/class/panelHttpProxy.py +++ b/class/panelHttpProxy.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -29,9 +29,9 @@ class HttpProxy: ''' headers = {} for h in p_res.headers.keys(): - if h in ['Content-Encoding','Transfer-Encoding']: continue + if h in ['content-encoding', 'Content-Encoding', 'transfer-encoding', 'Transfer-Encoding']: continue headers[h] = p_res.headers[h] - if h in ['Location']: + if h in ['location', 'Location']: if headers[h].find('phpmyadmin_') != -1: if not self._pma_path: diff --git a/class/panelLets.py b/class/panelLets.py index 8d431a4e..61abfa4f 100644 --- a/class/panelLets.py +++ b/class/panelLets.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # +------------------------------------------------------------------- import os,sys,json,time,re setup_path = '/www/server/panel' @@ -763,7 +763,7 @@ class panelLets: tlist = [] for siteName in old_list: if not siteName in cron_list: tlist.append(siteName) - print(public.get_msg_gettext('|-[{}] Not expired or the site does not use the Let\s Encrypt certificate.',(','.join(tlist),))) + print(public.get_msg_gettext(r'|-[{}] Not expired or the site does not use the Let\s Encrypt certificate.',(','.join(tlist),))) print(public.get_msg_gettext('|-{} Waiting for renewal [{}].',(time.strftime('%Y-%m-%d %X',time.localtime()),str(len(cron_list))))) sucess_list = [] diff --git a/class/panelMessage.py b/class/panelMessage.py index 0cabed94..67465e49 100644 --- a/class/panelMessage.py +++ b/class/panelMessage.py @@ -1,8 +1,8 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: hwliang <2020-05-18> # +------------------------------------------------------------------- diff --git a/class/panelModel/backupModel.py b/class/panelModel/backupModel.py index d234c2b1..e681e5e9 100644 --- a/class/panelModel/backupModel.py +++ b/class/panelModel/backupModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: cjxin +# Author: cjxin #------------------------------------------------------------------- # 备份 diff --git a/class/panelModel/base.py b/class/panelModel/base.py index 0738f632..ccbceed5 100644 --- a/class/panelModel/base.py +++ b/class/panelModel/base.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: cjxin +# Author: cjxin #------------------------------------------------------------------- # 面板其他模型新增功能 diff --git a/class/panelModel/publicModel.py b/class/panelModel/publicModel.py new file mode 100644 index 00000000..b659d64c --- /dev/null +++ b/class/panelModel/publicModel.py @@ -0,0 +1,274 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: cjxin +# ------------------------------------------------------------------- + +# 备份 +# ------------------------------ +import os, sys, re, json, shutil, psutil, time +from panelModel.base import panelBase +import public, config, panelTask + +try: + from BTPanel import cache +except:pass + +class main(panelBase): + __table = 'task_list' + # public.check_database_field("ssl_data.db","ssl_info") + task_obj = panelTask.bt_task() + + def __init__(self): + pass + + + """ + @name 获取面板日志 + """ + def get_update_logs(self,get): + try: + + skey = 'panel_update_logs' + res = cache.get(skey) + if res: return res + + res = public.httpPost('https://www.bt.cn/Api/getUpdateLogs?type=Linux',{}) + + start_index = res.find('(') + 1 + end_index = res.rfind(')') + json_data = res[start_index:end_index] + + res = json.loads(json_data) + cache.set(skey,res,60) + except: + res = [] + + return res + + def get_public_config(self, args): + """ + @name 获取公共配置 + """ + public.print_log("error 3366666 原始方法: ") + _config_obj = config.config() + data = _config_obj.get_config(args) + + data['task_list'] = self.task_obj.get_task_lists(args) + data['task_count'] = public.M('tasks').where("status!=?", ('1',)).count() + data['get_pd'] = self.get_pd(args) + data['ipv6'] = '' + if _config_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + + if data['get_pd'] and data['get_pd'][2] != -1: + time_diff = (data['get_pd'][2]-int(time.time())) % (365*86400) + data['active_pro_time'] = int(time.time()) - (365*86400 - time_diff) + else: + data['active_pro_time'] = 0 + data['status_code'] = _config_obj.get_not_auth_status() + if os.path.exists('/www/server/panel/config/api.json'): + try: + res = json.loads(public.readFile('/www/server/panel/config/api.json')) + data['api'] = 'checked' if res['open'] else '' + except: + public.ExecShell('rm -f /www/server/panel/config/api.json') + data['api'] = '' + else: + data['api'] = '' + return data + + def get_pd(self, get): + from BTPanel import cache + tmp = -1 + try: + import panelPlugin + # get = public.dict_obj() + # get.init = 1 + tmp1 = panelPlugin.panelPlugin().get_cloud_list(get) + except: + tmp1 = None + if tmp1: + tmp = tmp1[public.to_string([112, 114, 111])] + ltd = tmp1.get('ltd', -1) + else: + ltd = -1 + tmp4 = cache.get( + public.to_string([112, 95, 116, 111, 107, 101, 110])) + if tmp4: + tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4 + if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1') + tmp = public.readFile(tmp_f) + if tmp: tmp = int(tmp) + if not ltd: ltd = -1 + if tmp == None: tmp = -1 + if ltd < 1: + if ltd == -2: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 108, 116, 100, 45, 103, 114, 97, 121, 34, 62, 60, + 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, + 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, + 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, + 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, + 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, + 24050, 36807, 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, + 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, + 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, + 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116, + 97, 95, 108, 116, 100, 40, 41, 34, 62, 32493, 36153, 60, + 47, 97, 62, 60, 47, 115, 112, 97, 110, 62 + ]) + elif tmp == -1: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, + 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, + 111, 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 99, 111, + 109, 109, 101, 114, 99, 105, 97, 108, 95, 118, 105, 101, + 119, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, + 28857, 20987, 21319, 32423, 21040, 21830, 19994, 29256, 34, + 62, 20813, 36153, 29256, 60, 47, 115, 112, 97, 110, 62 + ]) + elif tmp == -2: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60, + 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, + 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, + 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, + 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, + 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, + 24050, 36807, 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, + 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, + 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, + 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116, + 97, 95, 112, 114, 111, 40, 41, 34, 62, 32493, 36153, 60, + 47, 97, 62, 60, 47, 115, 112, 97, 110, 62 + ]) + if tmp >= 0 and ltd in [-1, -2]: + if tmp == 0: + tmp2 = public.to_string([27704, 20037, 25480, 26435]) + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, + 34, 98, 116, 112, 114, 111, 34, 62, 123, 48, 125, 60, + 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, + 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, + 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, + 104, 116, 58, 32, 98, 111, 108, 100, 59, 34, 62, 123, + 49, 125, 60, 47, 115, 112, 97, 110, 62, 60, 47, 115, + 112, 97, 110, 62 + ]).format( + public.to_string([21040, 26399, 26102, 38388, 65306]), + tmp2) + else: + tmp2 = time.strftime( + public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), + time.localtime(tmp)) + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, + 34, 98, 116, 112, 114, 111, 34, 62, 21040, 26399, + 26102, 38388, 65306, 60, 115, 112, 97, 110, 32, 115, + 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, + 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, + 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, + 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, + 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, 48, + 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, + 108, 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107, + 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, + 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, + 116, 97, 95, 112, 114, 111, 40, 41, 34, 62, 32493, + 36153, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, 62 + ]).format(tmp2) + else: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 108, 116, 100, 45, 103, 114, 97, 121, 34, 32, 111, + 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, + 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116, + 100, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, + 28857, 20987, 21319, 32423, 21040, 20225, 19994, 29256, 34, + 62, 20813, 36153, 29256, 60, 47, 115, 112, 97, 110, 62 + ]) + else: + tmp3 = public.to_string([ + 60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, + 116, 108, 116, 100, 34, 62, 21040, 26399, 26102, 38388, 65306, + 60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, + 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, + 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, + 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, + 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, 125, 60, 47, + 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, + 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, + 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 117, + 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, + 32493, 36153, 60, 47, 97, 62, 60, 47, 115, 112, 97, 110, 62 + ]).format( + time.strftime( + public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), + time.localtime(ltd))) + + return tmp3, tmp, ltd + + @staticmethod + def set_backup_path(get): + try: + backup_path = get.backup_path.strip().rstrip("/") + except AttributeError: + return public.returnMsg(False, "参数错误") + + if not os.path.exists(backup_path): + return public.returnMsg(False, "指定目录不存在") + + if backup_path[-1] == "/": + backup_path = backup_path[:-1] + + import files + try: + from BTPanel import session + except: + session = None + fs = files.files() + + if not fs.CheckDir(get.backup_path): + return public.returnMsg(False, '不能使用系统关键目录作为默认备份目录') + if session is not None: + session['config']['backup_path'] = os.path.join('/', backup_path) + db_backup = backup_path + '/database' + site_backup = backup_path + '/site' + + if not os.path.exists(db_backup): + try: + os.makedirs(db_backup, 384) + except: + public.ExecShell('mkdir -p ' + db_backup) + + if not os.path.exists(site_backup): + try: + os.makedirs(site_backup, 384) + except: + public.ExecShell('mkdir -p ' + site_backup) + + public.M('config').where("id=?", ('1',)).save('backup_path', (get.backup_path,)) + public.WriteLog('TYPE_PANEL', 'PANEL_SET_SUCCESS', (get.backup_path,)) + + public.restart_panel() + return public.returnMsg(True, "设置成功") + + def get_soft_status(self,get): + if not hasattr(get,'name'): return public.returnMsg(False,'参数错误') + name = get.name.strip() + if name == 'sqlite': + return public.returnMsg(True,'符合!') + if os.path.exists('/www/server/{}'.format(name)) and len(os.listdir('/www/server/{}'.format(name))) > 2: + return public.returnMsg(True,'符合!') + if name == ['mysql','pgsql','sqlserver','mongodb','redis']: + count = public.M('database_servers').where("LOWER(db_type)=LOWER(?)", (name,)).count() + if count > 0: return public.returnMsg(True,'符合!') + return public.returnMsg(False,'不符合!') \ No newline at end of file diff --git a/class/panelMssql.py b/class/panelMssql.py index 2e1ac68d..307a2c5e 100644 --- a/class/panelMssql.py +++ b/class/panelMssql.py @@ -2,9 +2,9 @@ # +------------------------------------------------------------------- # | 宝塔Windows面板 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # +------------------------------------------------------------------- import re,os,sys,public @@ -43,7 +43,8 @@ class panelMssql: try: import pymssql except : - os.system("btpip install pymssql==2.1.4") + os.system("btpip install pymssql==2.3.0") + # os.system("btpip install pymssql==2.1.4") import pymssql @@ -53,17 +54,29 @@ class panelMssql: self.__DB_PORT = self.get_port() try: - - if self.__DB_CLOUD: - self.__DB_CONN = pymssql.connect(server = self.__DB_HOST, port= str(self.__DB_PORT),user=self.__DB_USER,password=self.__DB_PASS,database = None,login_timeout = 30,timeout = 0,autocommit = True) + + if self.__DB_CLOUD: + try: + self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), + user=self.__DB_USER, + password=self.__DB_PASS, database=None, login_timeout=30, + timeout=0, autocommit=True, charset="CP936", tds_version='7.0') + except: + self.__DB_ERR = 'Failed to connect to database! Check that the remote database information is correct' + return False + # self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), + # user=self.__DB_USER, + # password=self.__DB_PASS, database=None, login_timeout=30, + # timeout=0, autocommit=True, charset="CP936", tds_version='7.0') else: - self.__DB_CONN = pymssql.connect(server = self.__DB_HOST, port= str(self.__DB_PORT),login_timeout = 30,timeout = 0,autocommit = True) - self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。 - self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。 + self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), login_timeout=30, + timeout=0, autocommit=True, charset="CP936", tds_version='7.0') + self.__DB_CUR = self.__DB_CONN.cursor() # 将数据库连接信息,赋值给cur。 + self.__DB_CUR = self.__DB_CONN.cursor() # 将数据库连接信息,赋值给cur。 if self.__DB_CUR: return True else: - self.__DB_ERR = '连接数据库失败,请检查是否安装SQL Server' + self.__DB_ERR = 'Failed to connect to the database, please check whether SQL Server is installed' return False except Exception as ex: self.__DB_ERR = public.get_error_info() diff --git a/class/panelMysql.py b/class/panelMysql.py index bfb23517..f89d11cf 100644 --- a/class/panelMysql.py +++ b/class/panelMysql.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import re,os,sys,public diff --git a/class/panelPHP.py b/class/panelPHP.py index f8723422..e3fe0652 100644 --- a/class/panelPHP.py +++ b/class/panelPHP.py @@ -1,10 +1,10 @@ #coding:utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- # +------------------------------------------------------------------- @@ -199,18 +199,18 @@ class panelPHP: # if o_pma_root: # if not os.path.exists(pma_root): # os.makedirs(pma_root) -# public.ExecShell("\cp -arf {}/* {}/".format(o_pma_root,pma_root)) +# public.ExecShell(r"\cp -arf {}/* {}/".format(o_pma_root,pma_root)) # public.ExecShell("chown -R www:www {}".format(pma_root)) # public.ExecShell("chmod -R 700 {}".format(pma_root)) -# public.ExecShell("\cp -arf {} {}".format(pma_version_f1,pma_version_f2)) +# public.ExecShell(r"\cp -arf {} {}".format(pma_version_f1,pma_version_f2)) # index = public.readFile(pma_root + '/index.php') # if index: # if index.find("use PhpMyAdmin\\Util") != -1: # resp = "use PhpMyAdmin\\Util;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');" # index = index.replace("use PhpMyAdmin\\Util;",resp) -# elif index.find("use PMA\libraries\LanguageManager;") != -1: -# resp = "use PMA\libraries\LanguageManager;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');" -# index = index.replace("use PMA\libraries\LanguageManager;",resp) +# elif index.find(r"use PMA\libraries\LanguageManager;") != -1: +# resp = "use PMA\\libraries\\LanguageManager;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');" +# index = index.replace(r"use PMA\libraries\LanguageManager;",resp) # elif index.find("require_once 'libraries/common.inc.php';") != -1: # resp = "if(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');\nrequire_once 'libraries/common.inc.php';" # index = index.replace("require_once 'libraries/common.inc.php';",resp) @@ -364,7 +364,7 @@ class panelPHP: # dst_path = '/www/server/adminer' # if os.path.exists(src_path): # if not os.path.exists(dst_path): os.makedirs(dst_path) -# public.ExecShell("\cp -arf {}/* {}/".format(src_path, dst_path)) +# public.ExecShell(r"\cp -arf {}/* {}/".format(src_path, dst_path)) # public.ExecShell("chown -R www:www {}".format(dst_path)) # public.ExecShell("chmod -R 700 {}".format(dst_path)) # public.ExecShell("rm -rf {}".format(src_path)) diff --git a/class/panelPlugin.py b/class/panelPlugin.py index e1e38394..faef308f 100644 --- a/class/panelPlugin.py +++ b/class/panelPlugin.py @@ -1,36 +1,83 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- -import public,os,sys,json,time,psutil,py_compile,re -from BTPanel import session,cache,send_file +import public +import os +import sys +import json +import time +import psutil +import re +import shutil +import requests +from BTPanel import session, cache, send_file + + if sys.version_info[0] == 3: from importlib import reload + + class mget: pass + + class panelPlugin: - __isTable = None - __install_path = None - __tasks = None __list = 'data/list.json' __type = 'data/type.json' __index = 'config/index.json' __link = 'config/link.json' - __product_list = None - __plugin_list = None - __exists_names = {} - __plugin_s_list = [] + __official_url = 'https://www.aapanel.com' # __official_url = 'http://dev.aapanel.com' - pids = None - ROWS = 15 def __init__(self): + self.__isTable = None + self.__tasks = None + self.__product_list = None + self.__plugin_list = None + self.__exists_names = {} + self.__plugin_s_list = [] + self.__plugin_info = None + self.__plugin_name = None + self.__plugin_object = None + self.__plugin_list = None + self.__panel_path = '/www/server/panel' + self.__plugin_path = self.__panel_path + '/plugin/' + self.__plugin_save_file = self.__panel_path + '/data/plugin_bin.pl' + self.__api_root_url = self.__official_url + '/api' + self.__api_url = self.__api_root_url + '/panel/get_plugin_list' + self.__download_url = self.__api_root_url + '/panel/download_plugin' + self.__download_d_main_url = self.__api_root_url + '/panel/download_plugin_main' + self._check_url = self.__api_root_url + '/panel/get_soft_list_status' + self._unbinding_url = self.__api_root_url + '/panel/get_unbinding' + self.__tmp_path = self.__panel_path + '/temp/' + self.__plugin_timeout = 3600 + self.__is_php = False + self.__install_opt = 'i' + self.__pid = 0 + self.__path_error = self.__panel_path + '/data/error_pl.pl' + self.__error_html = '/www/server/panel/BTPanel/templates/default/block_error.html' + self.__sub_rules = [] + self.__replace_rule = [] + + self.pids = None + self.ROWS = 15 + self.__install_path = '/www/server/panel/plugin' - #检查依赖 + if not self.__tasks: + try: + self.__tasks = public.M('tasks').where("status!=?", ('1',)).field('status,name').select() + except: + self.__tasks = [] + + if not os.path.exists(self.__tmp_path): + os.makedirs(self.__tmp_path, 0o755) + + # 检查依赖 def check_deps(self,get): cacheKey = 'plugin_lib_list' if not 'force' in get: @@ -79,11 +126,11 @@ class panelPlugin: return True #检查依赖 - def check_dependnet(self,dependnet): - if not dependnet: return True - dependnets = dependnet.split(',') + def check_dependent(self,dependent): + if not dependent: return True + dependents = dependent.split(',') status = True - for dep in dependnets: + for dep in dependents: if not dep: continue if dep.find('|') != -1: names = dep.split('|') @@ -137,7 +184,7 @@ class panelPlugin: if not self.check_mutex(pluginInfo['mutex']): return public.return_msg_gettext(False, 'Please uninstall [{}] first', (self.mutex_title,)) if not hasattr(get, 'id'): - if not self.check_dependnet(pluginInfo['dependent']): return public.return_msg_gettext(False, 'Depends on the following software, please install [{}] first', + if not self.check_dependent(pluginInfo['dependent']): return public.return_msg_gettext(False, 'Depends on the following software, please install [{}] first', (pluginInfo['dependent'],)) if 'version' in get: for versionInfo in pluginInfo['versions']: @@ -153,7 +200,32 @@ class panelPlugin: m_ps = {0: "All", 1: "Centos", 2: "Ubuntu/Debian"} return public.return_msg_gettext(False, 'Only supports [{}] system',(m_ps[int(versionInfo['os_limit'])],)) if not hasattr(get, 'id'): - if not self.check_dependnet(versionInfo['dependent']): return public.return_msg_gettext(False,'Depend on the following software, please install first [{}]',(versionInfo['dependent'],)) + if not self.check_dependent(versionInfo['dependent']): return public.return_msg_gettext(False,'Depend on the following software, please install first [{}]',(versionInfo['dependent'],)) + + # 获取插件安装包下载进度 + def get_download_speed(self, get): + ''' + @name 获取插件下载进度 + @author hwliang<2021-06-25> + @param plugin_name 插件名称 + @return dict + ''' + result = self.__get_download_speed(get.plugin_name) + return result + + # 取消下载 + def close_install(self, get): + ''' + @name 取消指定插件安装过程 + @author hwliang<2021-07-07> + @param plugin_name 插件名称 + @return void + ''' + plugin_name = get.plugin_name.strip() + tmp_path = '{}/{}'.format(self.__tmp_path, plugin_name) + if os.path.exists(tmp_path): shutil.rmtree(tmp_path) + return public.returnMsg(False, '安装过程已取消!') + #安装插件 def install_plugin(self,get): str1 = public.get_msg_gettext("System critical directory is not writable!") @@ -173,6 +245,7 @@ class panelPlugin: return check_result if pluginInfo['name'] in ['dns_manager','mail_sys']: pluginInfo['type'] = 5 + if pluginInfo['type'] != 5: result = self.install_sync(pluginInfo,get) else: @@ -209,16 +282,21 @@ class panelPlugin: if os.path.exists(pluginInfo['install_checks']): update =pluginInfo['versions'][0]['version_msg'] return self.update_zip(None,toFile,update) else: - download_url = public.get_url() + '/install/plugin/' + pluginInfo['name'] + '_en/install.sh' - toFile = '/tmp/%s.sh' % pluginInfo['name'] - public.downloadFile(download_url,toFile) - self.set_pyenv(toFile) - public.ExecShell('/bin/bash ' + toFile + ' install &> /tmp/panelShell.pl') - if os.path.exists(pluginInfo['install_checks']): - public.write_log_gettext('Installer','Successfully installed plugin [{}]',(pluginInfo['title'],)) - if os.path.exists(toFile): os.remove(toFile) - return public.return_msg_gettext(True,'Installation succeeded!') - return public.return_msg_gettext(False,'Installation failed') + # download_url = public.get_url() + '/install/plugin/' + pluginInfo['name'] + '_en/install.sh' + # toFile = '/tmp/%s.sh' % pluginInfo['name'] + # public.downloadFile(download_url,toFile) + # self.set_pyenv(toFile) + # public.ExecShell('/bin/bash ' + toFile + ' install &> /tmp/panelShell.pl') + # if os.path.exists(pluginInfo['install_checks']): + # public.write_log_gettext('Installer','Successfully installed plugin [{}]',(pluginInfo['title'],)) + # if os.path.exists(toFile): os.remove(toFile) + # return public.return_msg_gettext(True,'Installation succeeded!') + # return public.return_msg_gettext(False,'Installation failed') + + if hasattr(get, 'min_version'): + get.version += '.' + get.min_version + + return self.__install_plugin(pluginInfo['name'], get.version) # 设置Python环境变量 def set_pyenv(self, filename): @@ -235,7 +313,11 @@ class panelPlugin: #异步安装 def install_async(self,pluginInfo,get): - mtype = 'install'; + # # 只取主版本号与子版本号,忽略修订号 + # if 'version' in get: + # get.version = '.'.join(str(get.version).split('.')[:2]) + + mtype = 'install' mmsg = public.get_msg_gettext('Install') if hasattr(get, 'upgrade'): mtype = 'update' @@ -267,6 +349,11 @@ class panelPlugin: execstr = "cd /www/server/panel/install && /bin/bash install_soft.sh {} {} {} {} {}".format( get.type, mtype, get.sName, get.version, ols_execstr) if get.sName == "phpmyadmin": + # 当面板开启SSL时,标记下次打开phpmyadmin时需要设置SSL + if os.path.exists('{}/data/ssl.pl'.format(public.get_panel_path())): + with open('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path()), 'w') as fp: + fp.write('1') + execstr += "&> /tmp/panelExec.log" if public.get_webserver() == 'openlitespeed': execstr += " && sleep 1 && /usr/local/lsws/bin/lswsctrl restart" @@ -322,74 +409,45 @@ class panelPlugin: return public.return_msg_gettext(True,"Uninstallation succeeded") #从云端取列表 - def get_cloud_list(self,get=None): - lcoalTmp = 'data/plugin.json' - softList = None - listTmp = public.readFile(lcoalTmp) - force_refresh = 0 - try: - if listTmp: softList = json.loads(listTmp) - if 'success' in softList and not softList['success']: - if os.path.exists(lcoalTmp): os.remove(lcoalTmp) - force_refresh = 1 - except: - if os.path.exists(lcoalTmp): os.remove(lcoalTmp) + def get_cloud_list(self, get=None): + force = 0 + if get and hasattr(get, 'force'): + force = int(get.force) + + if 'focre_cloud' in session: + if session['focre_cloud']: + force = 1 + session['focre_cloud'] = False + + if 'init_cloud' not in session: + force = 1 + session['init_cloud'] = True + + softList = public.load_soft_list(True if force == 1 else False) if get and 'init' in get: if softList: if 'success' not in softList: return softList - focre = 0 - if hasattr(get,'force'): focre = int(get.force) - if session: - if 'focre_cloud' in session: - if session['focre_cloud']: - focre = 1 - session['focre_cloud'] = False - - if not 'init_cloud' in session: - focre = 1 - session['init_cloud'] = True - if force_refresh == 1: - focre = 1 - if not softList or focre > 0: - self.clean_panel_log() - # cloudUrl = 'https://console.aapanel.com/api/panel/get_soft_list' - cloudUrl = '{}/api/panel/getSoftList'.format(self.__official_url) - import panelAuth - import requests - pdata = panelAuth.panelAuth().create_serverid(None) - # listTmp = public.httpPost(cloudUrl,pdata,6) - url_headers={} - if 'token' in pdata: - url_headers = {"authorization": "bt {}".format(pdata['token'])} - pdata['environment_info'] = json.dumps(public.fetch_env_info()) - - # listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers,verify=False,timeout=10) - # listTmp=listTmp.json() - - try: - listTmp = requests.post(cloudUrl, params=pdata, headers=url_headers, verify=False, timeout=10) - listTmp.raise_for_status() # 检查请求是否成功,如果不成功会抛出异常 - listTmp = listTmp.json() - except: - listTmp = False - - if listTmp is False: - listTmp = public.readFile(lcoalTmp) - try: - softList = listTmp - except: - pass - if softList: public.writeFile(lcoalTmp,json.dumps(softList)) + if force > 0: public.ExecShell('rm -f /tmp/bmac_*') public.run_thread(self.getCloudPHPExt) # 专业版和企业版到期提醒,aaPanel目前没有先注释 # self.expire_msg(softList) + try: - public.writeFile("/tmp/" + cache.get('p_token'),str(softList['pro'])) - except:pass + p_token = cache.get('p_token') + + if p_token is None: + p_token = 'bmac_' + public.Md5(public.get_mac_address()) + cache.set('p_token', p_token) + + public.writeFile("/tmp/" + p_token, str(softList['pro'])) + public.writeFile('/tmp/{}.time'.format(p_token), str(int(time.time()))) + except: + pass + sType = 0 try: if hasattr(get,'type'): sType = int(get['type']) @@ -414,14 +472,13 @@ class panelPlugin: sType = 0 except:pass - - try: - softList = json.loads(softList) - except: - pass + # 扫描本地插件并追加到软件列表中 softList['list'] = self.get_local_plugin(softList['list']) - softList['list'] = self.get_types(softList['list'],sType) - if hasattr(get,'query'): + + # 软件列表分类处理 + softList['list'] = self.get_types(softList['list'], sType) + + if hasattr(get, 'query'): if get.query: get.query = get.query.lower() tmpList = [] @@ -431,11 +488,11 @@ class panelPlugin: softInfo['ps'].lower().find(get.query) != -1: tmpList.append(softInfo) softList['list'] = tmpList + for softInfo in softList['list']: if 'uninsatll_checks' not in softInfo: softInfo['uninsatll_checks'] = softInfo['uninstall_checks'] - # if not softList['list']: - # if os.path.exists(lcoalTmp): os.remove(lcoalTmp) + return softList #取提醒标记 @@ -656,7 +713,7 @@ class panelPlugin: "s_version": "0", "manager_version": "1", "c_manager_version": "1", - "dependnet": "", + "dependent": "", "mutex": "", "install_checks": "/www/server/panel/plugin/" + info['name'], "uninsatll_checks": "/www/server/panel/plugin/" + info['name'], @@ -666,7 +723,7 @@ class panelPlugin: { "m_version": m_version[0], "version": m_version[1], - "dependnet": "", + "dependent": "", "mem_limit": 32, "cpu_limit": 1, "os_limit": 0, @@ -756,20 +813,14 @@ class panelPlugin: return softList #取首页软件列表 - def get_index_list(self,get=None): + def get_index_list(self, get=None): softList = self.get_cloud_list(get)['list'] if not softList: get.force = 1 softList = self.get_cloud_list(get)['list'] if not softList: return public.return_msg_gettext(False,'Failed to get software list ({})',"401") softList = self.set_coexist(softList) - # # 只取应用名 - # lista = [i['name'] for i in softList] - # - # public.print_log("23454566777^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^_______________________________" - # "列表 {}".format(lista)) - if not os.path.exists(self.__index): - public.writeFile(self.__index,'[]') + if not os.path.exists(self.__index): public.writeFile(self.__index,'[]') try: indexList = json.loads(public.ReadFile(self.__index)) except Exception: @@ -1021,11 +1072,18 @@ class panelPlugin: if softInfo['name'] == sName: if sName == 'phpmyadmin': + # 检查是否需要开启SSL + if os.path.exists('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path())) and os.path.exists('{}/phpmyadmin'.format(public.get_setup_path())): + os.remove('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path())) + from ajax_v2 import ajax + ajax().set_phpmyadmin_ssl(public.to_dict_obj({'v': '1'})) + from BTPanel import get_phpmyadmin_dir pmd = get_phpmyadmin_dir() softInfo['ext'] = self.getPHPMyAdminStatus() if softInfo['ext'] and pmd: - softInfo['ext']['url'] = 'http://' + public.GetHost() + ':'+ pmd[1] + '/' + pmd[0] + port = softInfo['ext']['ssl_port'] if softInfo['ext'].get('ssl_enabled', False) else pmd[1] + softInfo['ext']['url'] = 'http' + ('s' if softInfo['ext'].get('ssl_enabled', False) else '') + '://' + public.GetHost() + ':'+ port + '/' + pmd[0] if "php-" in sName: v = softInfo["versions"][0]["m_version"] v1 = v.replace(".", "") @@ -1036,6 +1094,7 @@ class panelPlugin: else: softInfo["php_ini"] = "/www/server/php/{}/etc/php.ini".format(v1) return self.check_status(softInfo) + return False @@ -1181,7 +1240,13 @@ class panelPlugin: page = page.Page() info = {} info['count'] = len(data) - info['row'] = self.ROWS + info['row'] = self.ROWS + if hasattr(get,'row'): + try: + info['row'] = int(get['row']) + except: + info['row'] = self.ROWS + info['p'] = 1 if hasattr(get,'p'): try: @@ -1637,7 +1702,7 @@ class panelPlugin: if os.path.exists('/www/server/apache/bin/httpd'): v1 = session.get('httpdv') if not v1: - v1 = public.ExecShell("/www/server/apache/bin/httpd -v|grep Apache|awk '{print $3}'|sed 's/Apache\///'")[0].strip(); + v1 = public.ExecShell(r"/www/server/apache/bin/httpd -v|grep Apache|awk '{print $3}'|sed 's/Apache\///'")[0].strip(); session['httpdv'] = v1 #if name == 'mysql': # if os.path.exists('/www/server/mysql/bin/mysql'): v1 = public.ExecShell("mysql -V|awk '{print $5}'|sed 's/,//'")[0].strip(); @@ -1749,6 +1814,8 @@ class panelPlugin: pstatus = False phpversion = "54" phpport = '888' + ssl_port = '887' + ssl_enabled = False if os.path.exists(configFile): conf = public.readFile(configFile) rep = r"listen\s+([0-9]+)\s*;" @@ -1756,6 +1823,15 @@ class panelPlugin: if rtmp: phpport = rtmp.groups()[0] + # SSL配置文件查看 + ssl_config_file = '{}/vhost/nginx/phpmyadmin.conf'.format(public.get_panel_path()) + if os.path.exists(ssl_config_file) and os.path.getsize(ssl_config_file) > 10: + tmps = public.readFile(ssl_config_file) + m = re.search(r"listen\s*(\d+)", tmps) + if m is not None: + ssl_enabled = True + ssl_port = m.group(1) + if conf.find('AUTH_START') != -1: pauth = True if conf.find(setupPath + '/stop') == -1: pstatus = True configFile = setupPath + '/nginx/conf/enable-php.conf' @@ -1786,8 +1862,19 @@ class panelPlugin: rtmp = re.search(rep,conf) if rtmp: phpport = rtmp.groups()[0] + + # SSL配置文件查看 + ssl_config_file = '{}/vhost/apache/phpmyadmin.conf'.format(public.get_panel_path()) + if os.path.exists(ssl_config_file) and os.path.getsize(ssl_config_file) > 10: + tmps = public.readFile(ssl_config_file) + m = re.search(r"Listen\s*(\d+)", tmps) + if m is not None: + ssl_enabled = True + ssl_port = m.group(1) + if conf.find('AUTH_START') != -1: pauth = True if conf.find('/www/server/stop') == -1: pstatus = True + if os.path.exists('/usr/local/lsws/bin/lswsctrl'): result = self._get_ols_myphpadmin_info() if result: @@ -1808,7 +1895,9 @@ class panelPlugin: tmp['run'] = pstatus tmp['phpversion'] = phpversion + tmp['ssl_enabled'] = ssl_enabled tmp['port'] = phpport + tmp['ssl_port'] = ssl_port tmp['auth'] = pauth except Exception as ex: tmp['status'] = False @@ -1819,18 +1908,18 @@ class panelPlugin: filename = "/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf" conf = public.readFile(filename) if not conf:return False - reg = '/usr/local/lsws/lsphp(\d+)/bin/lsphp' + reg = r'/usr/local/lsws/lsphp(\d+)/bin/lsphp' php_v = re.search(reg,conf) phpversion = '73' phpport = '888' if php_v: - phpversion = php_v.groups(1) + phpversion = php_v.group(1) filename = '/www/server/panel/vhost/openlitespeed/listen/888.conf' conf = public.readFile(filename) - reg = 'address\s+\*\:(\d+)' + reg = r'address\s+\*\:(\d+)' php_port = re.search(reg,conf) if php_port: - phpport = php_port.groups(1) + phpport = php_port.group(1) pauth = False pstatus = False if conf.find('/www/server/stop') == -1: pstatus = True @@ -2164,43 +2253,12 @@ class panelPlugin: return find['title'] - - #请求插件事件 - def a(self,get): - if not hasattr(get,'name'): return public.return_msg_gettext(False,'Input name of plugin!') + def a(self, get): try: - if not public.path_safe_check("%s/%s" % (get.name,get.s)): return public.return_msg_gettext(False,'Requested method [{}] does not exist!') - path = self.__install_path + '/' + get.name - - # aaa = path + '/'+get.name+'_main.py' - # public.print_log("@@@@@@@@@@@@@@@@@aaa {}".format(aaa)) - - if not os.path.exists(path + '/'+get.name+'_main.py'): - if os.path.exists(path+'/index.php'): - # bbb = path+'/index.php' - # public.print_log("@@@@@@@@@@@@@@@@@bbb {}".format(bbb)) - - - import panelPHP - return panelPHP.panelPHP(get.name).exec_php_script(get) - return public.return_msg_gettext(False,'This plugin does NOT have extend function!') - - if not self.check_accept(get):return public.return_msg_gettext(False,"You did not purchase [ {} ] or the authorization has expired", (self.get_title_byname(get),)) - public.package_path_append(path) - plugin_main = __import__(get.name+'_main') - try: - reload(plugin_main) - except: pass - - pluginObject = eval('plugin_main.' + get.name + '_main()') - if not hasattr(pluginObject,get.s): return public.return_msg_gettext(False,'Requested method [{}] does not exist!',(get.s,)) - execStr = 'pluginObject.' + get.s + '(get)' - return eval(execStr) + return public.run_plugin(get.name, get.s, get) except: - import traceback - errorMsg = traceback.format_exc() - public.writeFile('logs/done.log',errorMsg) - return public.return_msg_gettext(False,'%s:
                                    %s ' % (public.get_msg_gettext('Sorry, something went wrong'),errorMsg.replace('\n','
                                    '))) + return public.get_error_object(None, plugin_name=get.name) + #上传插件包 def update_zip(self,get = None,tmp_file = None, update = False): tmp_path = '/www/server/panel/temp' @@ -2266,7 +2324,7 @@ class panelPlugin: if not os.path.exists(get.tmp_path): return public.return_msg_gettext(False,'TEM_FILE_NOT_EXIST!') plugin_path = '/www/server/panel/plugin/' + get.plugin_name if not os.path.exists(plugin_path): os.makedirs(plugin_path) - public.ExecShell("\cp -a -r " + get.tmp_path + '/* ' + plugin_path + '/') + public.ExecShell(r"\cp -a -r " + get.tmp_path + '/* ' + plugin_path + '/') public.ExecShell('chmod -R 600 ' + plugin_path) self.set_pyenv(plugin_path + '/install.sh') public.ExecShell('cd ' + plugin_path + ' && bash install.sh install &> /tmp/panelShell.pl') @@ -2384,4 +2442,403 @@ class panelPlugin: config_data.append(args_name) public.writeFile(config_file,"\n".join(config_data)) public.write_log_gettext('Software manager','Setup software: Custom compilation parameters for {} are configured as: {}'.format(get.name,config_data)) - return public.return_msg_gettext(True,'Setup successfully!') \ No newline at end of file + return public.return_msg_gettext(True,'Setup successfully!') + + # 安装插件 + def __install_plugin(self, upgrade_plugin_name, upgrade_version=None): + ''' + @name 安装指定插件 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版 + @return dict + ''' + self.__plugin_name = upgrade_plugin_name + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: + raise public.PanelError('指定插件不存在,无法安装!') + if not plugin_info['versions']: + raise public.PanelError('指定插件当前未发布版本信息,请稍候再安装!') + if not upgrade_version: + upgrade_version = '{}.{}'.format( + plugin_info['versions'][0]['m_version'], + plugin_info['versions'][0]['version']) + filename = self.__download_plugin(upgrade_plugin_name, upgrade_version) + # 如果下载失败 + if isinstance(filename, dict): + return filename + return self.__unpackup_plugin(filename) + + # 修复插件 + def __repair_plugin(self, upgrade_plugin_name, upgrade_version=None): + ''' + @name 修复指定插件 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版 + @return dict + ''' + self.__install_opt = 'r' + return self.__install_plugin(upgrade_plugin_name, upgrade_version) + + # 升级插件版本 + def __upgrade_plugin(self, upgrade_plugin_name, upgrade_version=None): + ''' + @name 升级到指定版本 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版 + @return dict + ''' + self.__install_opt = 'u' + return self.__install_plugin(upgrade_plugin_name, upgrade_version) + + # 获取插件信息 + def __get_plugin_info(self, upgrade_plugin_name): + ''' + @name 获取插件信息 + @author hwliang<2021-06-15> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + plugin_info_file = '{}/{}/info.json'.format(self.__plugin_path, + upgrade_plugin_name) + if not os.path.exists(plugin_info_file): return {} + info_body = self.__read_file(plugin_info_file) + if not info_body: return {} + plugin_info = json.loads(info_body) + return plugin_info + + # 获取插件最近1条更新日志 + def __get_update_msg(self, upgrade_plugin_name, upgrade_version): + ''' + @name 检查指定插件版本更新日志 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 + @return string + ''' + plugin_update_msg = '' + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: return plugin_update_msg + for _version_info in plugin_info['versions']: + l_version = '{}.{}'.format(_version_info['m_version'], + _version_info['version']) + if l_version == upgrade_version: + plugin_update_msg = _version_info['update_msg'] + break + return plugin_update_msg + + # 获取插件最近10条更新日志 + def __get_plugin_upgrades(self, upgrade_plugin_name): + ''' + @name 检查指定插件最近10条更新日志 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @return list + ''' + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: return [] + + try: + upgrade_list = public.httpPost( + self.__api_root_url + '/down/get_update_msg', + {'soft_id': plugin_info['id']}) + return json.loads(upgrade_list) + except: + return [] + + # 获取指定软件信息 + def __get_plugin_find(self, upgrade_plugin_name=None): + ''' + @name 获取指定软件信息 + @author hwliang<2021-06-15> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + self.__ensure_plugin_list_obtained(True) + + for p_data_info in self.__plugin_list['list']: + if p_data_info['name'] == upgrade_plugin_name: + upgrade_plugin_name = p_data_info['name'] + return p_data_info + + # 如果不在插件列表中 + return self.__get_plugin_info(upgrade_plugin_name) + + # 检查插件依赖 + def __check_dependent(self, upgrade_plugin_name): + ''' + @name 检查指定插件的依赖安装情况 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: return {} + if not plugin_info['dependent']: return {} + deployment_list = {} + for dependent_plu_name in plugin_info['dependent'].split(','): + p_info = self.__get_plugin_find(dependent_plu_name) + if not p_info: continue + deployment_list[dependent_plu_name] = os.path.exists( + p_info['install_checks']) + return deployment_list + + # 读取指定文件 + def __read_file(self, filename, open_mode='r'): + ''' + @name 读取指定文件 + @author hwliang<2021-06-16> + @param filename 文件名 + @param mode 打开模式, 默认: r + @return bytes or string + ''' + f_object = open(filename, mode=open_mode) + file_body = f_object.read() + f_object.close() + return file_body + + # 解包插件压缩包 + def __unpackup_plugin(self, tmp_file): + ''' + @name 解包插件包 + @author hwliang<2021-06-21> + @param tmp_file 下载好的保存路径,从self.download_plugin方法中获取 + @return dict + ''' + if type(tmp_file) == dict: + return tmp_file + + if "false" in tmp_file or "错误" in tmp_file: + return json.loads(tmp_file) + + s_tmp_path = self.__tmp_path + if not os.path.exists(s_tmp_path): + os.makedirs(s_tmp_path, mode=384) + + if tmp_file: + if not os.path.exists(tmp_file): + return public.returnMsg(False, '文件下载失败!') + import panelTask as plu_panelTask + plu_panelTask.bt_task()._unzip(tmp_file, s_tmp_path, '', + '/dev/null') + if os.path.exists(tmp_file): + os.remove(tmp_file) + + s_tmp_path = os.path.join(s_tmp_path, self.__plugin_name) + + p_info = os.path.join(s_tmp_path, 'info.json') + if not os.path.exists(p_info): + d_path = None + for plugin_df in os.walk(s_tmp_path): + if len(plugin_df[2]) < 3: continue + if not 'info.json' in plugin_df[2]: continue + if not 'install.sh' in plugin_df[2]: continue + if not os.path.exists(plugin_df[0] + '/info.json'): continue + d_path = plugin_df[0] + if d_path: + s_tmp_path = d_path + p_info = s_tmp_path + '/info.json' + try: + try: + plugin_data_info = json.loads(public.ReadFile(p_info)) + except: + plugin_data_info = json.loads(self.__read_file(p_info)) + + plugin_data_info['size'] = public.get_path_size(s_tmp_path) + if not 'author' in plugin_data_info: + plugin_data_info['author'] = 'aapanel' + if not 'home' in plugin_data_info: + plugin_data_info['home'] = 'https://www.aapanel.com' + + p_info_file = self.__plugin_path + plugin_data_info[ + 'name'] + '/info.json' + plugin_data_info['old_version'] = '0' + plugin_data_info['tmp_path'] = s_tmp_path + if os.path.exists(p_info_file): + try: + old_info = json.loads(public.ReadFile(p_info_file)) + plugin_data_info['old_version'] = old_info['versions'] + except: + pass + except: + public.ExecShell("rm -rf " + s_tmp_path + '/*') + return public.get_error_object(plugin_name=self.__plugin_name) + plugin_data_info['install_opt'] = self.__install_opt + plugin_data_info['dependent'] = self.__check_dependent(plugin_data_info['name']) + plugin_data_info['update_msg'] = self.__get_update_msg( + plugin_data_info['name'], plugin_data_info['versions']) + not_check = self.not_cpu_or_bit(plugin_data_info) + if not_check: + if os.path.exists(s_tmp_path): shutil.rmtree(s_tmp_path) + return not_check + + return plugin_data_info + + # 检测是否为不支持的平台和系统位数 + def not_cpu_or_bit(self, plugin_data_info): + ''' + @name 检测是否为不支持的平台和系统位数 + @author hwliang<2021-07-07> + @param plugin_data_info 插件信息数据 + @return dict or None + ''' + if 'not_os_bit' in plugin_data_info: + if public.get_sysbit() == int(plugin_data_info['not_os_bit']): + return public.returnMsg( + False, + '该应用不支持{}位系统'.format(plugin_data_info['not_os_bit'])) + if 'not_cpu_type' in plugin_data_info: + if not plugin_data_info['not_cpu_type']: return None + machine = os.uname().machine + for c_type in plugin_data_info['not_cpu_type']: + c_type = c_type.lower() + result = public.returnMsg( + False, '该应用不支持{}平台,{}'.format(c_type, machine)) + if c_type in ['arm', 'aarch64', 'aarch']: + if machine in ['aarch64', 'aarch']: + return result + elif c_type in ['mips', 'mips64', 'mips64el']: + if machine.find('mips') != -1: + return result + elif c_type in ['x86', 'x86-64']: + if machine in ['x86', 'x86-64']: + return result + return None + + # 下载插件安装包 + def __download_plugin(self, upgrade_plugin_name, upgrade_version): + ''' + @name 下载插件包 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 + @return string 保存路径 + ''' + + pkey = '{}_pre'.format(upgrade_plugin_name) + pdata = public.get_user_info() + pdata['name'] = upgrade_plugin_name + pdata['version'] = upgrade_version + pdata['os'] = 'Linux' + pdata['environment_info'] = json.dumps(public.fetch_env_info(), ensure_ascii=False) + filename = '{}/{}.zip'.format(self.__tmp_path, upgrade_plugin_name) + if not os.path.exists(self.__tmp_path): + os.makedirs(self.__tmp_path, 384) + + if not cache.get(pkey): + import config, socket + import requests.packages.urllib3.util.connection as urllib3_conn + _ip_type = config.config().get_request_iptype() + old_family = urllib3_conn.allowed_gai_family + if _ip_type == 'ipv4': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + elif _ip_type == 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + try: + download_res = requests.post( + self.__download_url, + pdata, + headers=public.get_requests_headers(), + timeout=(60, 1800), + stream=True) + except Exception as ex: + str_ex = str(ex) + if 'Name or service not known' in str_ex: + return public.returnMsg(False, + '下载软件包时DNS解析失败,请检查服务器网络配置是否正常:

                                    Error: Name or service not known

                                    ') + elif 'Failed to establish a new connection' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Failed to establish a new connection

                                    ') + elif 'Read timed out' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Read timed out

                                    ') + elif 'Connection refused' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Connection refused

                                    ') + elif 'Remote end closed connection without response' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Remote end closed connection without response

                                    ') + else: + return public.returnMsg(False, '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: {}

                                    '.format(str_ex)) + finally: + urllib3_conn.allowed_gai_family = old_family + + try: + headers_total_size = int(download_res.headers['File-size']) + except: + try: + return json.loads(download_res.text).json() + except: + if download_res.text.find('') != -1: + raise public.PanelError( + public.error_conn_cloud(download_res.text)) + raise public.PanelError(download_res.text) + + res_down_size = 0 + res_chunk_size = 8192 + last_time = time.time() + with open(filename, 'wb+') as with_res_f: + try: + for download_chunk in download_res.iter_content( + chunk_size=res_chunk_size): + if download_chunk: + with_res_f.write(download_chunk) + speed_last_size = len(download_chunk) + res_down_size += speed_last_size + res_start_time = time.time() + res_timeout = (res_start_time - last_time) + res_sec_speed = int(res_down_size / res_timeout) + pre_text = '{}/{}/{}'.format(res_down_size, + headers_total_size, + res_sec_speed) + cache.set(pkey, pre_text, 3600) + + except Exception as ex: + ex_str = str(ex) + if "Read timed out" in ex_str: + return public.returnMsg(False, '软件包下载超时,请重试: {}'.format(ex_str)) + if "No space left on device" in ex_str: + return public.returnMsg(False, "磁盘空间不足,请先清理后再试!") + finally: + with_res_f.close() + if cache.get(pkey): cache.delete(pkey) + + if public.FileMd5(filename) != download_res.headers['Content-md5']: + return public.returnMsg(False, '软件包校验失败,请更新软件列表,并重试') + else: + while True: + time.sleep(1) + if not cache.get(pkey): break + return '' + return filename + + # 获取插件安装包下载进度 + def __get_download_speed(self, upgrade_plugin_name): + ''' + @name 取插件下载进度 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + pkey = '{}_pre'.format(upgrade_plugin_name) + pre_text = cache.get(pkey) + if not pre_text: + return public.returnMsg(False, '指定进度信息不存在!') + result = {"status": True} + pre_tmp = pre_text.split('/') + result['down_size'], result['total_size'] = (int(pre_tmp[0]), + int(pre_tmp[1])) + result['down_pre'] = round( + result['down_size'] / result['total_size'] * 100, 1) + result['sec_speed'] = int(float(pre_tmp[2])) + result['need_time'] = int( + (result['total_size'] - result['down_size']) / result['sec_speed']) + return result + + + # 获取插件与授权信息 + def __ensure_plugin_list_obtained(self, force: bool = False): + if force or not self.__plugin_list: + self.__plugin_list = public.load_soft_list(force) diff --git a/class/panelPmd.py b/class/panelPmd.py index 197831d1..f38037da 100644 --- a/class/panelPmd.py +++ b/class/panelPmd.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2019 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- import public,os from BTPanel import request,abort,send_file diff --git a/class/panelProjectController.py b/class/panelProjectController.py index e0053547..395038f8 100644 --- a/class/panelProjectController.py +++ b/class/panelProjectController.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -32,8 +32,8 @@ class ProjectController: if args['mod_name'] in ['base']: return public.return_status_code(1000,'wrong call!') public.exists_args('def_name,mod_name',args) if args['def_name'].find('__') != -1: return public.return_status_code(1000,'Called method name cannot contain [ __ ] characters') - if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w') - if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w') + if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w') except: return public.get_error_object() diff --git a/class/panelPush.py b/class/panelPush.py index 2a7d73a6..01199ae7 100644 --- a/class/panelPush.py +++ b/class/panelPush.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://www.bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # | 消息推送管理 # | 对外方法 get_modules_list、install_module、uninstall_module、get_module_template、set_push_config、get_push_config、del_push_config diff --git a/class/panelRedirect.py b/class/panelRedirect.py index 09f4894c..a564f2a6 100644 --- a/class/panelRedirect.py +++ b/class/panelRedirect.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2018 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -21,7 +21,7 @@ class panelRedirect: #匹配目标URL的域名并返回 def GetToDomain(self,tourl): if tourl: - rep = "https?://([\w\-\.]+)" + rep = r"https?://([\w\-\.]+)" tu = re.search(rep, tourl) return tu.group(1) @@ -105,7 +105,7 @@ class panelRedirect: if os.path.exists(ng_file): ng_conf = public.readFile(ng_file) if not p_conf: - rep = "#SSL-END(\n|.)*\/redirect\/.*\*.conf;" + rep = "#SSL-END(\n|.)*\\/redirect\\/.*\\*.conf;" ng_conf = re.sub(rep, '#SSL-END', ng_conf) public.writeFile(ng_file, ng_conf) return @@ -114,13 +114,13 @@ class panelRedirect: sitenamelist.append(i["sitename"]) if get.sitename in sitenamelist: - rep = "include.*\/redirect\/.*\*.conf;" + rep = r"include.*\/redirect\/.*\*.conf;" if not re.search(rep,ng_conf): ng_conf = ng_conf.replace("#SSL-END","#SSL-END\n\t%s\n\t" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + "include " + ng_redirectfile + ";") public.writeFile(ng_file,ng_conf) else: - rep = "#SSL-END(\n|.)*\/redirect\/.*\*.conf;" + rep = "#SSL-END(\n|.)*\\/redirect\\/.*\\*.conf;" ng_conf = re.sub(rep,'#SSL-END',ng_conf) public.writeFile(ng_file, ng_conf) @@ -134,18 +134,18 @@ class panelRedirect: if os.path.exists(ap_file): ap_conf = public.readFile(ap_file) if p_conf == "[]": - rep = "\n*%s\n+\s+IncludeOptiona[\s\w\/\.\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') ap_conf = re.sub(rep, '', ap_conf) public.writeFile(ap_file, ap_conf) return if sitename in p_conf: - rep = "%s(\n|.)+IncludeOptional.*\/redirect\/.*conf" % public.get_msg_gettext('#referenced redirect rule') + rep = "%s(\n|.)+IncludeOptional.*\\/redirect\\/.*conf" % public.get_msg_gettext('#referenced redirect rule') rep1 = "combined" if not re.search(rep,ap_conf): ap_conf = ap_conf.replace(rep1, rep1 + "\n\t%s" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') +"\n\tIncludeOptional " + ap_redirectfile) public.writeFile(ap_file,ap_conf) else: - rep = "\n*%s\n+\s+IncludeOptiona[\s\w\/\.\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') ap_conf = re.sub(rep,'', ap_conf) public.writeFile(ap_file, ap_conf) @@ -167,7 +167,7 @@ class panelRedirect: if self.__CheckRedirect(get.sitename,get.redirectname,is_error_page): return public.return_msg_gettext(False, 'Specified redirect name already exists') #检测目标URL格式 - rep = "http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" if 'tourl' in get and not re.match(rep, get.tourl): return public.returnMsg(False, 'Target URL format is wrong %s' + get.tourl) @@ -180,12 +180,12 @@ class panelRedirect: else: if not get.redirectpath: return public.return_msg_gettext(False, 'Please enter redirected path') - #repte = "[\?\=\[\]\)\(\*\&\^\%\$\#\@\!\~\`{\}\>\<\,\',\"]+" + #repte = "[\\?\\=\\[\\]\\)\\(\\*\\&\\^\\%\\$\\#\\@\\!\\~\\`{\\}\\>\\<\\,\',\"]+" # 检测路径格式 if "/" not in get.redirectpath: return public.return_msg_gettext(False, 'Path format is incorrect, the format is /xxx') #if re.search(repte, get.redirectpath): - # return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]") + # return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]") #检测域名是否已经存在配置文件 repeatdomain = self.__CheckRepeatDomain(get,action) if repeatdomain: @@ -384,7 +384,7 @@ class panelRedirect: """ if self.__firsturl:redirect_path=self.__firsturl add_type=',R={}]'.format(str(r_type)) - add_str='#REWRITE-START\n\n RewriteEngine on\n RewriteCond %\{REQUEST_FILENAME\} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . '+redirect_path+' [L'+add_type+'\n\n#REWRITE-END' + add_str='#REWRITE-START\n\n RewriteEngine on\n RewriteCond %\\{REQUEST_FILENAME\\} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . '+redirect_path+' [L'+add_type+'\n\n#REWRITE-END' redirectname_md5 = self.__calc_md5(redirectname) file_path= "%s/panel/vhost/apache/redirect/%s" % (self.setupPath,site_name) public.ExecShell("mkdir -p %s" % file_path) @@ -620,9 +620,9 @@ class panelRedirect: old_conf = public.readFile(conf_path) rep ="" if i == "nginx": - rep += "#301-START\n+[\s\w\:\/\.\;\$]+#301-END" + rep += "#301-START\n+[\\s\\w\\:\\/\\.\\;\\$]+#301-END" if i == "apache": - rep += "#301-START[\n\<\>\w\.\s\^\*\$\/\[\]\(\)\:\,\=]+#301-END" + rep += "#301-START[\n\\<\\>\\w\\.\\s\\^\\*\\$\\/\\[\\]\\(\\)\\:\\,\\=]+#301-END" conf = re.sub(rep, "", old_conf) public.writeFile(conf_path, conf) public.serviceReload() diff --git a/class/panelRun.py b/class/panelRun.py index 692b957f..78508bfb 100644 --- a/class/panelRun.py +++ b/class/panelRun.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ diff --git a/class/panelSSL.py b/class/panelSSL.py index 16eb14e4..d0ee40e0 100644 --- a/class/panelSSL.py +++ b/class/panelSSL.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------ @@ -34,7 +34,7 @@ class panelSSL: __BINDURL = 'https://www.aapanel.com/api/user' # 获取token 获取官网token - # __BINDURL = 'https:///dev.aapanel.com/api/user' # 获取token 获取官网token + # __BINDURL = 'http://dev.aapanel.com/api/user' # 获取token 获取官网token __CODEURL = 'https://api.bt.cn/Auth/GetBindCode' # 获取绑定验证码 __UPATH = 'data/userInfo.json' @@ -66,7 +66,6 @@ class panelSSL: else: self.__userInfo = {} - # public.print_log('初始化 !!!!!!!!!!!!!!!!!!!用户信息: {}'.format(self.__userInfo)) try: if self.__userInfo: # 记录里没有这两个key @@ -141,7 +140,7 @@ class panelSSL: # public.print_log("写入用户信息 @@@@222 {}".format(self.__APIURL + '/user/login')) result = json.loads(rtmp) # public.print_log("写入用户信息 @@@@ {}".format(rtmp)) - public.print_log("写入用户信息 @@@@ {}".format(result)) + # public.print_log("写入用户信息 @@@@ {}".format(result)) if result['success']: bind = 'data/bind.pl' if os.path.exists(bind): os.remove(bind) @@ -1056,7 +1055,7 @@ class panelSSL: if cert_list and all_domain: for cert in cert_list: d_cert = '' - if re.match("^\*\..*", cert): + if re.match(r"^\*\..*", cert): d_cert = cert.replace('*.', '') for domain in all_domain: if cert == domain: diff --git a/class/panelSafe.py b/class/panelSafe.py index 745ac227..1656bc55 100644 --- a/class/panelSafe.py +++ b/class/panelSafe.py @@ -14,26 +14,26 @@ class safe: ev = public.GetMsg("EXPLOITABLE_VULNERABILITIES") dc = public.GetMsg("DANGEROUS_CITATION") rulelist = [ - {'msg':get_post_ev,'level':danger,'code':'(\$_(GET|POST|REQUEST)\[.{0,15}\]\s{0,10}\(\s{0,10}\$_(GET|POST|REQUEST)\[.{0,15}\]\))'}, - {'msg':one_word_th,'level':high_risk,'code':'((eval|assert)(\s|\n)*\((\s|\n)*\$_(POST|GET|REQUEST)\[.{0,15}\]\))'}, - {'msg':one_word_th,'level':high_risk,'code':'(eval(\s|\n)*\(base64_decode(\s|\n)*\((.|\n){1,200})'}, - {'msg':webshell,'level':danger,'code':'(function\_exists\s*\(\s*[\'|\"](shell\_exec|system|popen|exec|proc\_open|passthru)+[\'|\"]\s*\))'}, - {'msg':webshell,'level':danger,'code':'((exec|shell\_exec|passthru)+\s*\(\s*\$\_(\w+)\[(.*)\]\s*\))'}, - {'msg':ev,'level':danger,'code':'(\$(\w+)\s*\(\s.chr\(\d+\)\))'}, - {'msg':webshell,'level':danger,'code':'(\$(\w+)\s*\$\{(.*)\})'}, - {'msg':get_post_cookie_ev,'level':danger,'code':'(\$(\w+)\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\s*\))'}, - {'msg':get_post_cookie_ev,'level':danger,'code':'(\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\(\s*\$(.*)\))'}, - {'msg':webshell,'level':danger,'code':'(\$\_\=(.*)\$\_)'}, - {'msg':webshell,'level':danger,'code':'(\$(.*)\s*\((.*)\/e(.*)\,\s*\$\_(.*)\,(.*)\))'}, - {'msg':webshell,'level':danger,'code':'(new com\s*\(\s*[\'|\"]shell(.*)[\'|\"]\s*\))'}, - {'msg':webshell,'level':danger,'code':'(echo\s*curl\_exec\s*\(\s*\$(\w+)\s*\))'}, - {'msg':public.GetMsg("HAZARDOUS_FILE_OPERATION_VULNERABILITIES"),'level':high_risk,'code':'((fopen|fwrite|fputs|file\_put\_contents)+\s*\((.*)\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\](.*)\))'}, - {'msg':public.GetMsg("DANGEROUS_UPLOAD_VULNERABILITIES"),'level':danger,'code':'(\(\s*\$\_FILES\[(.*)\]\[(.*)\]\s*\,\s*\$\_(GET|POST|REQUEST)+\[(.*)\]\[(.*)\]\s*\))'}, - {'msg':dc,'level':high_risk,'code':'(\$\_(\w+)(.*)(eval|assert|include|require|include\_once|require\_once)+\s*\(\s*\$(\w+)\s*\))'}, - {'msg':dc,'level':high_risk,'code':'((include|require|include\_once|require\_once)+\s*\(\s*[\'|\"](\w+)\.(jpg|gif|ico|bmp|png|txt|zip|rar|htm|css|js)+[\'|\"]\s*\))'}, - {'msg':ev,'level':danger,'code':'(eval\s*\(\s*\(\s*\$\$(\w+))'}, - {'msg':one_word_th,'level':high_risk,'code':'((eval|assert|include|require|include\_once|require\_once|array\_map|array\_walk)+\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER|SESSION)+\[(.*)\]\s*\))'}, - {'msg':one_word_th,'level':danger,'code':'(preg\_replace\s*\((.*)\(base64\_decode\(\$)'} + {'msg':get_post_ev,'level':danger,'code':r'(\$_(GET|POST|REQUEST)\[.{0,15}\]\s{0,10}\(\s{0,10}\$_(GET|POST|REQUEST)\[.{0,15}\]\))'}, + {'msg':one_word_th,'level':high_risk,'code':'((eval|assert)(\\s|\n)*\\((\\s|\n)*\\$_(POST|GET|REQUEST)\\[.{0,15}\\]\\))'}, + {'msg':one_word_th,'level':high_risk,'code':'(eval(\\s|\n)*\\(base64_decode(\\s|\n)*\\((.|\n){1,200})'}, + {'msg':webshell,'level':danger,'code':'(function\\_exists\\s*\\(\\s*[\'|\"](shell\\_exec|system|popen|exec|proc\\_open|passthru)+[\'|\"]\\s*\\))'}, + {'msg':webshell,'level':danger,'code':r'((exec|shell\_exec|passthru)+\s*\(\s*\$\_(\w+)\[(.*)\]\s*\))'}, + {'msg':ev,'level':danger,'code':r'(\$(\w+)\s*\(\s.chr\(\d+\)\))'}, + {'msg':webshell,'level':danger,'code':r'(\$(\w+)\s*\$\{(.*)\})'}, + {'msg':get_post_cookie_ev,'level':danger,'code':r'(\$(\w+)\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\s*\))'}, + {'msg':get_post_cookie_ev,'level':danger,'code':r'(\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\]\(\s*\$(.*)\))'}, + {'msg':webshell,'level':danger,'code':r'(\$\_\=(.*)\$\_)'}, + {'msg':webshell,'level':danger,'code':r'(\$(.*)\s*\((.*)\/e(.*)\,\s*\$\_(.*)\,(.*)\))'}, + {'msg':webshell,'level':danger,'code':'(new com\\s*\\(\\s*[\'|\"]shell(.*)[\'|\"]\\s*\\))'}, + {'msg':webshell,'level':danger,'code':r'(echo\s*curl\_exec\s*\(\s*\$(\w+)\s*\))'}, + {'msg':public.GetMsg("HAZARDOUS_FILE_OPERATION_VULNERABILITIES"),'level':high_risk,'code':r'((fopen|fwrite|fputs|file\_put\_contents)+\s*\((.*)\$\_(GET|POST|REQUEST|COOKIE|SERVER)+\[(.*)\](.*)\))'}, + {'msg':public.GetMsg("DANGEROUS_UPLOAD_VULNERABILITIES"),'level':danger,'code':r'(\(\s*\$\_FILES\[(.*)\]\[(.*)\]\s*\,\s*\$\_(GET|POST|REQUEST)+\[(.*)\]\[(.*)\]\s*\))'}, + {'msg':dc,'level':high_risk,'code':r'(\$\_(\w+)(.*)(eval|assert|include|require|include\_once|require\_once)+\s*\(\s*\$(\w+)\s*\))'}, + {'msg':dc,'level':high_risk,'code':'((include|require|include\\_once|require\\_once)+\\s*\\(\\s*[\'|\"](\\w+)\\.(jpg|gif|ico|bmp|png|txt|zip|rar|htm|css|js)+[\'|\"]\\s*\\))'}, + {'msg':ev,'level':danger,'code':r'(eval\s*\(\s*\(\s*\$\$(\w+))'}, + {'msg':one_word_th,'level':high_risk,'code':r'((eval|assert|include|require|include\_once|require\_once|array\_map|array\_walk)+\s*\(\s*\$\_(GET|POST|REQUEST|COOKIE|SERVER|SESSION)+\[(.*)\]\s*\))'}, + {'msg':one_word_th,'level':danger,'code':r'(preg\_replace\s*\((.*)\(base64\_decode\(\$)'} ] ruleFile = '/www/server/panel/data/ruleList.conf'; @@ -101,7 +101,7 @@ class safe: def checkPHPINI(self): setupPath = '/www/server'; phps = public.get_php_versions() - rep = "disable_functions\s*=\s*(.+)\n" + rep = "disable_functions\\s*=\\s*(.+)\n" defs = ['passthru','exec','system','chroot','chgrp','chown','shell_exec','popen','ini_alter','ini_restore','dl','openlog','syslog','readlink','symlink','popepassthru'] data = [] for phpv in phps: diff --git a/class/panelSafeController.py b/class/panelSafeController.py index 8495dc91..bac8c8bf 100644 --- a/class/panelSafeController.py +++ b/class/panelSafeController.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -32,8 +32,8 @@ class SafeController: if args['mod_name'] in ['base']: return public.return_status_code(1000,'wrong call!') public.exists_args('def_name,mod_name',args) if args['def_name'].find('__') != -1: return public.return_status_code(1000,'The called method name cannot contain the "__" character') - if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w') - if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,'The called module name cannot contain characters other than \w') + if not re.match(r"^\w+$",args['mod_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + if not re.match(r"^\w+$",args['def_name']): return public.return_status_code(1000,r'The called module name cannot contain characters other than \w') except: return public.get_error_object() # 参数处理 diff --git a/class/panelSearch.py b/class/panelSearch.py index 10cb22ec..6e76552f 100644 --- a/class/panelSearch.py +++ b/class/panelSearch.py @@ -2,7 +2,7 @@ # +------------------------------------------------------------------- # | version :1.0 # +------------------------------------------------------------------- -# | Author: 梁凯强 <1249648969@qq.com> +# | Author: 梁凯强 <1249648969@aapanel.com> # +------------------------------------------------------------------- # | 快速检索 # +-------------------------------------------------------------------- diff --git a/class/panelSite.py b/class/panelSite.py index bd832078..6bb91bb9 100644 --- a/class/panelSite.py +++ b/class/panelSite.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------ @@ -55,6 +55,8 @@ class panelSite(panelRedirect): if os.path.exists(self.apache_conf_bak): os.remove(self.apache_conf_bak) self.is_ipv6 = os.path.exists(self.setupPath + '/panel/data/ipv6.pl') sys.setrecursionlimit(1000000) + self._proxy_path = '/www/server/proxy_project' + self._proxy_config_path = self._proxy_path + '/sites' # 默认配置文件 def check_default(self): @@ -141,7 +143,7 @@ class panelSite(panelRedirect): ''' % (public.get_php_proxy(self.phpVersion, 'apache'),) apaOpt = 'Require all granted' - conf = '''%s + conf = r'''%s ServerAdmin webmaster@example.com DocumentRoot "%s" ServerName %s.%s @@ -182,7 +184,7 @@ class panelSite(panelRedirect): listen_ipv6 = '' if self.is_ipv6: listen_ipv6 = "\n listen [::]:%s;" % self.sitePort - conf = '''server + conf = r'''server {{ listen {listen_port};{listen_ipv6} server_name {site_name}; @@ -806,7 +808,14 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) 'success': del_successfully} # 删除站点 - def DeleteSite(self, get, multiple=None): + def DeleteSite(self, get: public.dict_obj, multiple=None): + # 请求参数校验 + get.validate([ + public.validate.Param('id').Require().Integer(), + public.validate.Param('webname').Require().SafePath(), + public.validate.Param('path').Integer(), + ], [public.validate.trim_filter()]) + proxyconf = self.__read_config(self.__proxyfile) id = get.id if public.M('sites').where('id=?', (id,)).count() < 1: return public.return_msg_gettext(False, 'Specified site does NOT exist') @@ -964,7 +973,7 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) conf = public.readFile(file_name) if not conf: continue - map_rep = 'map\s+{}.*'.format(sitename) + map_rep = r'map\s+{}.*'.format(sitename) conf = re.sub(map_rep, '', conf) if "map" not in conf: public.ExecShell('rm -f {}*'.format(file_name)) @@ -1069,7 +1078,7 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) return public.return_msg_gettext(False,'Domain name format is incorrect!') # 判断域名格式 - reg = "^([\w\-\*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$" + reg = r"^([\w\-\*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$" if not re.match(reg, get.domain): return public.return_msg_gettext(False, 'Format of domain is invalid!') # 获取自定义端口 @@ -1133,7 +1142,7 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) # 添加openlitespeed 80端口监听 def openlitespeed_set_80_domain(self, get, conf): - rep = 'map\s+{}.*'.format(get.webname) + rep = r'map\s+{}.*'.format(get.webname) domains = get.webname.strip().split(',') if conf: map_tmp = re.search(rep, conf) @@ -1145,12 +1154,12 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) conf = re.sub(rep, new_map, conf) else: map_tmp = '\tmap\t{d} {d}\n'.format(d=domains[0]) - listen_rep = "secure\s*0" + listen_rep = r"secure\s*0" conf = re.sub(listen_rep, "secure 0\n" + map_tmp, conf) return conf else: - rep_default = 'listener\s+Default\{(\n|[\s\w\*\:\#\.\,])*' + rep_default = 'listener\\s+Default\\{(\n|[\\s\\w\\*\\:\\#\\.\\,])*' tmp = re.search(rep_default, conf) # domains = get.webname.strip().split(',') if tmp: @@ -1177,7 +1186,7 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) pass if listen_conf: # 添加域名 - rep = 'map\s+{}.*'.format(get.webname) + rep = r'map\s+{}.*'.format(get.webname) map_tmp = re.search(rep, listen_conf) if map_tmp: map_tmp = map_tmp.group() @@ -1188,7 +1197,7 @@ set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) else: domains = get.webname.strip().split(',') map_tmp = '\tmap\t{d} {d}'.format(d=domains[0]) - listen_rep = "secure\s*0" + listen_rep = r"secure\s*0" listen_conf = re.sub(listen_rep, "secure 0\n" + map_tmp, listen_conf) else: listen_conf = """ @@ -1277,7 +1286,7 @@ listener Default%s{ apaOpt = "Order allow,deny\n\t\tAllow from all" else: vName = "" - # rep = "php-cgi-([0-9]{2,3})\.sock" + # rep = r"php-cgi-([0-9]{2,3})\.sock" # version = re.search(rep,conf).groups()[0] version = public.get_php_version_conf(conf) if len(version) < 2: return public.return_msg_gettext(False, 'Failed to get PHP version!') @@ -1289,7 +1298,7 @@ listener Default%s{ ''' % (public.get_php_proxy(version, 'apache'),) apaOpt = 'Require all granted' - newconf = ''' + newconf = r''' ServerAdmin webmaster@example.com DocumentRoot "%s" ServerName %s.%s @@ -1397,12 +1406,12 @@ listener Default%s{ rep = r"\n*(.|\n)*" tmp = re.search(rep, conf).group() - rep1 = "ServerAlias\s+(.+)\n" + rep1 = "ServerAlias\\s+(.+)\n" tmp1 = re.findall(rep1, tmp) tmp2 = tmp1[0].split(' ') if len(tmp2) < 2: conf = re.sub(rep, '', conf) - rep = "NameVirtualHost.+\:" + port + "\n" + rep = r"NameVirtualHost.+\:" + port + "\n" conf = re.sub(rep, '', conf) else: newServerName = tmp.replace(' ' + get['domain'] + "\n", "\n") @@ -1432,7 +1441,7 @@ listener Default%s{ if os.path.isdir(file_name): continue conf = public.readFile(file_name) - map_rep = 'map\s+{}\s+(.*)'.format(get.webname) + map_rep = r'map\s+{}\s+(.*)'.format(get.webname) domains = re.search(map_rep, conf) if domains: domains = domains.group(1).split(',') @@ -1640,7 +1649,7 @@ listener Default%s{ if not apis[i]['data']: continue for j in range(len(apis[i]['data'])): if apis[i]['data'][j]['value']: continue - match = re.search(apis[i]['data'][j]['key'] + "\s*=\s*'(.+)'", account) + match = re.search(apis[i]['data'][j]['key'] + r"\s*=\s*'(.+)'", account) if match: apis[i]['data'][j]['value'] = match.groups()[0] if apis[i]['data'][j]['value']: is_write = True if is_write: public.writeFile('./config/dns_api.json', json.dumps(apis)) @@ -1695,7 +1704,7 @@ listener Default%s{ def GetFormatSSLResult(self, result): try: import re - rep = "\s*Domain:.+\n\s+Type:.+\n\s+Detail:.+" + rep = "\\s*Domain:.+\n\\s+Type:.+\n\\s+Detail:.+" tmps = re.findall(rep, result) statusList = [] @@ -1716,16 +1725,16 @@ listener Default%s{ def get_tls13(self): nginx_bin = '/www/server/nginx/sbin/nginx' nginx_v = public.ExecShell(nginx_bin + ' -V 2>&1')[0] - nginx_v_re = re.findall("nginx/(\d\.\d+).+OpenSSL\s+(\d\.\d+)",nginx_v,re.DOTALL) + nginx_v_re = re.findall(r"nginx/(\d\.\d+).+OpenSSL\s+(\d\.\d+)",nginx_v,re.DOTALL) if nginx_v_re: if nginx_v_re[0][0] in ['1.8','1.9','1.7','1.6','1.5','1.4']: return '' if float(nginx_v_re[0][0]) >= 1.15 and float(nginx_v_re[0][-1]) >= 1.1: return ' TLSv1.3' else: - _v = re.search('nginx/1\.1(5|6|7|8|9).\d',nginx_v) + _v = re.search(r'nginx/1\.1(5|6|7|8|9).\d',nginx_v) if not _v: - _v = re.search('nginx/1\.2\d\.\d',nginx_v) + _v = re.search(r'nginx/1\.2\d\.\d',nginx_v) openssl_v = public.ExecShell(nginx_bin + ' -V 2>&1|grep OpenSSL')[0].find('OpenSSL 1.1.') != -1 if _v and openssl_v: return ' TLSv1.3' @@ -1733,7 +1742,7 @@ listener Default%s{ # 获取apache反向代理 def get_apache_proxy(self, conf): - rep = "\n*#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid\n+\s+IncludeOptiona.*" + rep = "\n*#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid\n+\\s+IncludeOptiona.*" proxy = re.search(rep, conf) if proxy: return proxy.group() @@ -1795,7 +1804,7 @@ listener SSL443 { """ else: - rep = 'listener\s*SSL443\s*{' + rep = r'listener\s*SSL443\s*{' map = '\n map {s} {s}'.format(s=siteName) conf = re.sub(rep, 'listener SSL443 {' + map, conf) domain = ",".join(self._get_site_domains(siteName)) @@ -1808,6 +1817,21 @@ listener SSL443 { if ap_static_security: return ap_static_security.group() return '' + + def write_json_conf(self, siteName,status): + conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=siteName + ) + try: + proxy_json_conf = json.loads(public.readFile(conf_path)) + proxy_json_conf['ssl_info']['ssl_status']=status + #将proxy_json_conf 写入文件 + public.WriteFile + except Exception as e: + proxy_json_conf = {} + + return public.return_message(0,0,proxy_json_conf) # 添加SSL配置 def SetSSLConf(self, get): @@ -1857,7 +1881,7 @@ listener SSL443 { conf = re.sub(r"\s+\#SSL\-END","\n\t\t#SSL-END",conf) # 添加端口 - rep = "listen.*[\s:]+(\d+).*;" + rep = r"listen.*[\s:]+(\d+).*;" tmp = re.findall(rep, conf) if not public.inArray(tmp, '443'): listen_re = re.search(rep,conf) @@ -1925,7 +1949,7 @@ listener SSL443 { ''' % (public.get_php_proxy(version, 'apache'),) apaOpt = 'Require all granted' - sslStr = '''%s + sslStr = r'''%s ServerAdmin webmaster@example.com DocumentRoot "%s" ServerName SSL.%s @@ -2071,16 +2095,16 @@ listener SSL443 { file = self.setupPath + '/panel/vhost/nginx/node_'+siteName+'.conf' conf = public.readFile(file) if conf: - rep = "\n\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" conf = re.sub(rep, '', conf) - rep = "\s+if.+server_port.+\n.+\n\s+\s*}" + rep = "\\s+if.+server_port.+\n.+\n\\s+\\s*}" conf = re.sub(rep, '', conf) public.writeFile(file, conf) file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' conf = public.readFile(file) if conf: - rep = "\n\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" conf = re.sub(rep, '', conf) public.writeFile(file, conf) # OLS @@ -2111,41 +2135,41 @@ listener SSL443 { file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf' conf = public.readFile(file) if conf: - rep = "\n\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" conf = re.sub(rep, '', conf) - rep = "\s+ssl_certificate\s+.+;\s+ssl_certificate_key\s+.+;" + rep = r"\s+ssl_certificate\s+.+;\s+ssl_certificate_key\s+.+;" conf = re.sub(rep, '', conf) - rep = "\s+ssl_protocols\s+.+;\n" + rep = "\\s+ssl_protocols\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_ciphers\s+.+;\n" + rep = "\\s+ssl_ciphers\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_prefer_server_ciphers\s+.+;\n" + rep = "\\s+ssl_prefer_server_ciphers\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_session_cache\s+.+;\n" + rep = "\\s+ssl_session_cache\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_session_timeout\s+.+;\n" + rep = "\\s+ssl_session_timeout\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_ecdh_curve\s+.+;\n" + rep = "\\s+ssl_ecdh_curve\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_session_tickets\s+.+;\n" + rep = "\\s+ssl_session_tickets\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_stapling\s+.+;\n" + rep = "\\s+ssl_stapling\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl_stapling_verify\s+.+;\n" + rep = "\\s+ssl_stapling_verify\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+add_header\s+.+;\n" + rep = "\\s+add_header\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+add_header\s+.+;\n" + rep = "\\s+add_header\\s+.+;\n" conf = re.sub(rep, '', conf) - rep = "\s+ssl\s+on;" + rep = r"\s+ssl\s+on;" conf = re.sub(rep, '', conf) - rep = "\s+error_page\s497.+;" + rep = r"\s+error_page\s497.+;" conf = re.sub(rep, '', conf) - rep = "\s+if.+server_port.+\n.+\n\s+\s*}" + rep = "\\s+if.+server_port.+\n.+\n\\s+\\s*}" conf = re.sub(rep, '', conf) - rep = "\s+listen\s+443.*;" + rep = r"\s+listen\s+443.*;" conf = re.sub(rep, '', conf) - rep = "\s+listen\s+\[::\]:443.*;" + rep = r"\s+listen\s+\[::\]:443.*;" conf = re.sub(rep, '', conf) public.writeFile(file, conf) @@ -2154,9 +2178,9 @@ listener SSL443 { file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf' conf = public.readFile(file) if conf: - rep = "\n(.|\n)*<\/VirtualHost>" + rep = "\n(.|\n)*<\\/VirtualHost>" conf = re.sub(rep, '', conf) - rep = "\n\s*#HTTP_TO_HTTPS_START(.|\n){1,250}#HTTP_TO_HTTPS_END" + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,250}#HTTP_TO_HTTPS_END" conf = re.sub(rep, '', conf) rep = "NameVirtualHost *:443\n" conf = conf.replace(rep, '') @@ -2188,7 +2212,7 @@ listener SSL443 { file = "/www/server/panel/vhost/openlitespeed/listen/443.conf" conf = public.readFile(file) if conf: - rep = '\n\s*map\s*{}.*'.format(sitename) + rep = '\n\\s*map\\s*{}.*'.format(sitename) conf = re.sub(rep, '', conf) if not "map " in conf: public.ExecShell('rm -f {}*'.format(file)) @@ -2380,7 +2404,7 @@ listener SSL443 { file = self.setupPath + '/panel/vhost/openlitespeed/' + get.name + '.conf' conf = public.readFile(file) if conf: - rep = 'vhRoot\s*{}'.format(Path) + rep = r'vhRoot\s*{}'.format(Path) new_content = 'vhRoot {}'.format(sitePath) conf = re.sub(rep, new_content, conf) public.writeFile(file, conf) @@ -2402,7 +2426,7 @@ listener SSL443 { if not conf: return False try: - really_path = re.search('root\s+(.*);', conf).group(1) + really_path = re.search(r'root\s+(.*);', conf).group(1) tmp = stop_path + '/' + really_path.replace(website_path + '/', '') public.ExecShell('mkdir {t} && ln -s {s}/index.html {t}/index.html'.format(t=tmp, s=stop_path)) except: @@ -2455,7 +2479,7 @@ listener SSL443 { file = self.setupPath + '/panel/vhost/openlitespeed/' + get.name + '.conf' conf = public.readFile(file) if conf: - rep = 'vhRoot\s*{}'.format(sitePath) + rep = r'vhRoot\s*{}'.format(sitePath) new_content = 'vhRoot {}'.format(path) conf = re.sub(rep, new_content, conf) public.writeFile(file, conf) @@ -2478,17 +2502,17 @@ listener SSL443 { data = {} conf = public.readFile(filename) try: - rep = "\s+limit_conn\s+perserver\s+([0-9]+);" + rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" tmp = re.search(rep, conf).groups() data['perserver'] = int(tmp[0]) # IP并发限制 - rep = "\s+limit_conn\s+perip\s+([0-9]+);" + rep = r"\s+limit_conn\s+perip\s+([0-9]+);" tmp = re.search(rep, conf).groups() data['perip'] = int(tmp[0]) # 请求并发限制 - rep = "\s+limit_rate\s+([0-9]+)\w+;" + rep = r"\s+limit_rate\s+([0-9]+)\w+;" tmp = re.search(rep, conf).groups() data['limit_rate'] = int(tmp[0]) except: @@ -2525,15 +2549,15 @@ listener SSL443 { if (conf.find('limit_conn perserver') != -1): # 替换总并发 - rep = "limit_conn\s+perserver\s+([0-9]+);" + rep = r"limit_conn\s+perserver\s+([0-9]+);" conf = re.sub(rep, perserver, conf) # 替换IP并发限制 - rep = "limit_conn\s+perip\s+([0-9]+);" + rep = r"limit_conn\s+perip\s+([0-9]+);" conf = re.sub(rep, perip, conf) # 替换请求流量限制 - rep = "limit_rate\s+([0-9]+)\w+;" + rep = r"limit_rate\s+([0-9]+)\w+;" conf = re.sub(rep, limit_rate, conf) else: conf = conf.replace('#error_page 404/404.html;', @@ -2559,15 +2583,15 @@ listener SSL443 { filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' conf = public.readFile(filename) # 清理总并发 - rep = "\s+limit_conn\s+perserver\s+([0-9]+);" + rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" conf = re.sub(rep, '', conf) # 清理IP并发限制 - rep = "\s+limit_conn\s+perip\s+([0-9]+);" + rep = r"\s+limit_conn\s+perip\s+([0-9]+);" conf = re.sub(rep, '', conf) # 清理请求流量限制 - rep = "\s+limit_rate\s+([0-9]+)\w+;" + rep = r"\s+limit_rate\s+([0-9]+)\w+;" conf = re.sub(rep, '', conf) public.writeFile(filename, conf) public.serviceReload() @@ -2597,9 +2621,9 @@ listener SSL443 { result['status'] = False result['url'] = "http://" return result - rep = "return\s+301\s+((http|https)\://.+);" + rep = r"return\s+301\s+((http|https)\://.+);" arr = re.search(rep, conf).groups()[0] - rep = "'\^(([\w-]+\.)+[\w-]+)'" + rep = r"'\^(([\w-]+\.)+[\w-]+)'" tmp = re.search(rep, conf) src = '' if tmp: src = tmp.groups()[0] @@ -2611,9 +2635,9 @@ listener SSL443 { result['status'] = False result['url'] = "http://" return result - rep = "RewriteRule\s+.+\s+((http|https)\://.+)\s+\[" + rep = r"RewriteRule\s+.+\s+((http|https)\://.+)\s+\[" arr = re.search(rep, conf).groups()[0] - rep = "\^((\w+\.)+\w+)\s+\[NC" + rep = r"\^((\w+\.)+\w+)\s+\[NC" tmp = re.search(rep, conf) src = '' if tmp: src = tmp.groups()[0] @@ -2626,9 +2650,9 @@ listener SSL443 { result['status'] = False result['url'] = "http://" return result - rep = "RewriteRule\s+.+\s+((http|https)\://.+)\s+\[" + rep = r"RewriteRule\s+.+\s+((http|https)\://.+)\s+\[" arr = re.search(rep, conf).groups()[0] - rep = "\^((\w+\.)+\w+)\s+\[NC" + rep = r"\^((\w+\.)+\w+)\s+\[NC" tmp = re.search(rep, conf) src = '' if tmp: src = tmp.groups()[0] @@ -2650,7 +2674,7 @@ listener SSL443 { srcDomain = get.srcDomain toDomain = get.toDomain type = get.type - rep = "(http|https)\://.+" + rep = r"(http|https)\://.+" if not re.match(rep, toDomain): return public.return_msg_gettext(False, 'URL address is invalid!') # nginx @@ -2664,7 +2688,7 @@ listener SSL443 { if type == '1': mconf = mconf.replace("#error_page 404/404.html;", "#error_page 404/404.html;\n" + conf301) else: - rep = "\s+#301-START(.|\n){1,300}#301-END" + rep = "\\s+#301-START(.|\n){1,300}#301-END" mconf = re.sub(rep, '', mconf) public.writeFile(filename, mconf) @@ -2680,7 +2704,7 @@ listener SSL443 { rep = "combined" mconf = mconf.replace(rep, rep + "\n\t" + conf301) else: - rep = "\n\s+#301-START(.|\n){1,300}#301-END\n*" + rep = "\n\\s+#301-START(.|\n){1,300}#301-END\n*" mconf = re.sub(rep, '\n\n', mconf, 1) mconf = re.sub(rep, '\n\n', mconf, 1) @@ -2775,7 +2799,7 @@ listener SSL443 { if not hasattr(get, 'dirName'): public.return_msg_gettext(False, 'Directory cannot be empty!') dirName = get.dirName - reg = "^([\w\-\*]{1,100}\.){1,4}([\w\-]{1,100}|[\w\-]{1,100}\.[\w\-]{1,100})$" + reg = r"^([\w\-\*]{1,100}\.){1,4}([\w\-]{1,100}|[\w\-]{1,100}\.[\w\-]{1,100})$" if not re.match(reg, domain): return public.return_msg_gettext(False, 'Format of primary domain is incorrect') siteInfo = public.M('sites').where("id=?",(id,)).field('id,path,name').find() @@ -2804,16 +2828,16 @@ listener SSL443 { listen_ipv6 = '' if self.is_ipv6: listen_ipv6 = "\n listen [::]:%s;" % port try: - rep = "enable-php-(\w{2,5})\.conf" + rep = r"enable-php-(\w{2,5})\.conf" tmp = re.search(rep,conf) if not tmp: - rep = "enable-php-(\d+-wpfastcgi).conf" + rep = r"enable-php-(\d+-wpfastcgi).conf" tmp = re.search(rep, conf) except: return public.returnMsg(False,"Get enable php config failed!") tmp = tmp.groups() version = tmp[0] - bindingConf = ''' + bindingConf = r''' #BINDING-%s-START server { @@ -2871,7 +2895,7 @@ server phpConfig = "" apaOpt = "Order allow,deny\n\t\tAllow from all" else: - # rep = "php-cgi-([0-9]{2,3})\.sock" + # rep = r"php-cgi-([0-9]{2,3})\.sock" # tmp = re.search(rep,conf).groups() # version = tmp[0] version = public.get_php_version_conf(conf) @@ -2883,8 +2907,9 @@ server ''' % (public.get_php_proxy(version, 'apache'),) apaOpt = 'Require all granted' - bindingConf = ''' -\n#BINDING-%s-START + bindingConf = r''' + +#BINDING-%s-START ServerAdmin webmaster@example.com DocumentRoot "%s" @@ -2925,7 +2950,7 @@ server listen_file = self.setupPath + "/panel/vhost/openlitespeed/listen/80.conf" listen_conf = public.readFile(listen_file) if listen_conf: - rep = 'secure\s*0' + rep = r'secure\s*0' map = '\tmap {}_{} {}'.format(siteInfo['name'], dirName, domain) listen_conf = re.sub(rep, 'secure 0\n' + map, listen_conf) public.writeFile(listen_file, listen_conf) @@ -2978,7 +3003,7 @@ server filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' conf = public.readFile(filename) if conf: - rep = "\s*.+BINDING-" + binding['domain'] + "-START(.|\n)+BINDING-" + binding['domain'] + "-END" + rep = r"\s*.+BINDING-" + binding['domain'] + "-START(.|\n)+BINDING-" + binding['domain'] + "-END" conf = re.sub(rep, '', conf) public.writeFile(filename, conf) @@ -2986,14 +3011,14 @@ server filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' conf = public.readFile(filename) if conf: - rep = "\s*.+BINDING-" + binding['domain'] + "-START(.|\n)+BINDING-" + binding['domain'] + "-END" + rep = r"\s*.+BINDING-" + binding['domain'] + "-START(.|\n)+BINDING-" + binding['domain'] + "-END" conf = re.sub(rep, '', conf) public.writeFile(filename, conf) # openlitespeed filename = self.setupPath + '/panel/vhost/openlitespeed/' + siteName + '.conf' conf = public.readFile(filename) - rep = "#SUBDIR\s*{s}_{d}\s*START(\n|.)+#SUBDIR\s*{s}_{d}\s*END".format(s=siteName, d=binding['path']) + rep = "#SUBDIR\\s*{s}_{d}\\s*START(\n|.)+#SUBDIR\\s*{s}_{d}\\s*END".format(s=siteName, d=binding['path']) if conf: conf = re.sub(rep, '', conf) public.writeFile(filename, conf) @@ -3006,7 +3031,7 @@ server listen_file = self.setupPath + "/panel/vhost/openlitespeed/listen/80.conf" listen_conf = public.readFile(listen_file) if listen_conf: - map_reg = '\s*map\s*{}_{}.*'.format(siteName, binding['path']) + map_reg = r'\s*map\s*{}_{}.*'.format(siteName, binding['path']) listen_conf = re.sub(map_reg, '', listen_conf) public.writeFile(listen_file, listen_conf) # 清理detail文件 @@ -3074,11 +3099,11 @@ server conf = public.readFile(file) if conf == False: return public.return_msg_gettext(False, 'Configuration file not exist') if public.get_webserver() == 'nginx': - rep = "\s+index\s+(.+);" + rep = r"\s+index\s+(.+);" elif public.get_webserver() == 'apache': - rep = "DirectoryIndex\s+(.+)\n" + rep = "DirectoryIndex\\s+(.+)\n" else: - rep = "indexFiles\s+(.+)\n" + rep = "indexFiles\\s+(.+)\n" if re.search(rep, conf): tmp = re.search(rep, conf).groups() if public.get_webserver() == 'openlitespeed': @@ -3105,7 +3130,7 @@ server file = self.setupPath + '/panel/vhost/nginx/' + Name + '.conf' conf = public.readFile(file) if conf: - rep = "\s+index\s+.+;" + rep = r"\s+index\s+.+;" conf = re.sub(rep, "\n\tindex " + Index_L + ";", conf) public.writeFile(file, conf) @@ -3113,7 +3138,7 @@ server file = self.setupPath + '/panel/vhost/apache/' + Name + '.conf' conf = public.readFile(file) if conf: - rep = "DirectoryIndex\s+.+\n" + rep = "DirectoryIndex\\s+.+\n" conf = re.sub(rep, 'DirectoryIndex ' + Index_L + "\n", conf) public.writeFile(file, conf) @@ -3121,7 +3146,7 @@ server file = self.setupPath + '/panel/vhost/openlitespeed/detail/' + Name + '.conf' conf = public.readFile(file) if conf: - rep = "indexFiles\s+.+\n" + rep = "indexFiles\\s+.+\n" Index = Index.split(',') Index = [i for i in Index if i] Index = ",".join(Index) @@ -3155,9 +3180,9 @@ server file = self.setupPath + '/panel/vhost/apache/' + Name + '.conf' conf = public.readFile(file) if conf: - rep = "DocumentRoot\s+.+\n" + rep = "DocumentRoot\\s+.+\n" conf = re.sub(rep, 'DocumentRoot "' + Path + '"\n', conf) - rep = "\n", conf) public.writeFile(file, conf) @@ -3328,7 +3353,7 @@ server if not public.check_tcp(other_tmp[0],int(other_tmp[1])): return public.return_msg_gettext(False,'Unable to connect to [{}], please check whether the machine can connect to the target server'.format(get.other)) - other_conf = '''location ~ [^/]\.php(/|$) + other_conf = r'''location ~ [^/]\.php(/|$) {{ try_files $uri =404; fastcgi_pass {}; @@ -3338,19 +3363,19 @@ server }}'''.format(get.other) public.writeFile(other_rep,other_conf) conf = conf.replace(other_rep,dst) - rep = "include\s+enable-php-(\w{2,5})\.conf" + rep = r"include\s+enable-php-(\w{2,5})\.conf" tmp = re.search(rep,conf) if tmp: conf = conf.replace(tmp.group(),'include ' + dst) - elif re.search("enable-php-\d+-wpfastcgi.conf",conf): + elif re.search(r"enable-php-\d+-wpfastcgi.conf",conf): dst = 'enable-php-{}-wpfastcgi.conf'.format(version) conf = conf.replace(other_rep,dst) - rep = "enable-php-\d+-wpfastcgi.conf" + rep = r"enable-php-\d+-wpfastcgi.conf" tmp = re.search(rep, conf) if tmp:conf = conf.replace(tmp.group(),dst) else: dst = 'enable-php-'+version+'.conf' conf = conf.replace(other_rep,dst) - rep = "enable-php-(\w{2,5})\.conf" + rep = r"enable-php-(\w{2,5})\.conf" tmp = re.search(rep,conf) if tmp: conf = conf.replace(tmp.group(),dst) public.writeFile(file,conf) @@ -3372,7 +3397,7 @@ server file = self.setupPath + '/panel/vhost/apache/'+siteName+'.conf' conf = public.readFile(file) if conf and version != 'other': - rep = "(unix:/tmp/php-cgi-(\w{2,5})\.sock\|fcgi://localhost|fcgi://127.0.0.1:\d+)" + rep = r"(unix:/tmp/php-cgi-(\w{2,5})\.sock\|fcgi://localhost|fcgi://127.0.0.1:\d+)" tmp = re.search(rep,conf).group() conf = conf.replace(tmp,public.get_php_proxy(version,'apache')) public.writeFile(file,conf) @@ -3381,7 +3406,7 @@ server file = self.setupPath + '/panel/vhost/openlitespeed/detail/'+siteName+'.conf' conf = public.readFile(file) if conf: - rep = 'lsphp\d+' + rep = r'lsphp\d+' tmp = re.search(rep, conf) if tmp: conf = conf.replace(tmp.group(), 'lsphp' + version) @@ -3456,7 +3481,7 @@ server return public.return_msg_gettext(True, 'Base directory turned off!') if conf and "session.save_path" in conf: - rep = "session.save_path\s*=\s*(.*)" + rep = r"session.save_path\s*=\s*(.*)" s_path = re.search(rep, conf).groups(1)[0] public.writeFile(filename, conf + '\nopen_basedir={}/:/tmp/:{}'.format(path, s_path)) else: @@ -3478,12 +3503,12 @@ server c = public.readFile(f) if not c: return False if f: - rep = '\nphp_admin_value\s*open_basedir.*' + rep = '\nphp_admin_value\\s*open_basedir.*' result = re.search(rep, c) s = 'on' if not result: s = 'off' - rep = '\n#php_admin_value\s*open_basedir.*' + rep = '\n#php_admin_value\\s*open_basedir.*' result = re.search(rep, c) result = result.group() if s == 'on': @@ -3525,8 +3550,8 @@ server if os.path.exists(conf_path): old_conf = public.readFile(conf_path) rep = "(#PROXY-START(\n|.)+#PROXY-END)" - url_rep = "proxy_pass (.*);|ProxyPass\s/\s(.*)|Host\s(.*);" - host_rep = "Host\s(.*);" + url_rep = r"proxy_pass (.*);|ProxyPass\s/\s(.*)|Host\s(.*);" + host_rep = r"Host\s(.*);" if re.search(rep, old_conf): # 构造代理配置 if w == "nginx": @@ -3656,7 +3681,7 @@ server def __CheckUrl(self, get): sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.settimeout(5) - rep = "(https?)://([\w\.\-]+):?([\d]+)?" + rep = r"(https?)://([\w\.\-]+):?([\d]+)?" h = re.search(rep, get.proxysite).group(1) d = re.search(rep, get.proxysite).group(2) try: @@ -3699,16 +3724,16 @@ server except: return public.return_msg_gettext(False, 'Please enter number') - rep = "http(s)?\:\/\/" - # repd = "http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + rep = r"http(s)?\:\/\/" + # repd = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" tod = "[a-zA-Z]+$" - repte = "[\?\=\[\]\)\(\*\&\^\%\$\#\@\!\~\`{\}\>\<\,\',\"]+" + repte = "[\\?\\=\\[\\]\\)\\(\\*\\&\\^\\%\\$\\#\\@\\!\\~\\`{\\}\\>\\<\\,\',\"]+" # 检测代理目录格式 if re.search(repte, get.proxydir): - return public.return_msg_gettext(False, "PROXY_DIR_ERR", ("?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]",)) + return public.return_msg_gettext(False, "PROXY_DIR_ERR", ("?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]",)) # 检测发送域名格式 if get.todomain: - if re.search("[\}\{\#\;\"\']+",get.todomain): + if re.search("[\\}\\{\\#\\;\"\']+",get.todomain): return public.return_msg_gettext(False, 'Sent Domain format error :'+get.todomain+'
                                    The following special characters cannot exist [ } { # ; \" \' ] ') if public.get_webserver() != 'openlitespeed' and not get.todomain: get.todomain = "$host" @@ -3717,7 +3742,7 @@ server if not re.match(rep, get.proxysite): return public.return_msg_gettext(False, 'Sent domain format ERROR {}', (get.proxysite,)) if re.search(repte, get.proxysite): - return public.return_msg_gettext(False, "PROXY_URL_ERR", ("?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',\"]",)) + return public.return_msg_gettext(False, "PROXY_URL_ERR", ("?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]",)) # 检测目标url是否可用 # if re.match(repd, get.proxysite): # if self.__CheckUrl(get): @@ -3752,9 +3777,9 @@ server self.CheckProxy(get) ng_conf = public.readFile(ng_file) if not p_conf: - # rep = "%s[\w\s\~\/\(\)\.\*\{\}\;\$\n\#]+.{1,66}[\s\w\/\*\.\;]+include enable-php-" % public.GetMsg( + # rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.{1,66}[\\s\\w\\/\\*\\.\\;]+include enable-php-" % public.GetMsg( # "CLEAR_CACHE") - rep = "%s[\w\s\~\/\(\)\.\*\{\}\;\$\n\#]+.*\n.*" % public.get_msg_gettext("#Clear cache") + rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.*\n.*" % public.get_msg_gettext("#Clear cache") # ng_conf = re.sub(rep, 'include enable-php-', ng_conf) ng_conf = re.sub(rep, '', ng_conf) oldconf = '''location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ @@ -3770,7 +3795,7 @@ server access_log /dev/null; }''' if "(gif|jpg|jpeg|png|bmp|swf)$" not in ng_conf: - ng_conf = re.sub('access_log\s*/www', oldconf + "\n\taccess_log /www",ng_conf) + ng_conf = re.sub(r'access_log\s*/www', oldconf + "\n\taccess_log /www",ng_conf) public.writeFile(ng_file, ng_conf) return sitenamelist = [] @@ -3778,9 +3803,9 @@ server sitenamelist.append(i["sitename"]) if get.sitename in sitenamelist: - rep = "include.*\/proxy\/.*\*.conf;" + rep = r"include.*\/proxy\/.*\*.conf;" if not re.search(rep, ng_conf): - rep = "location.+\(gif[\w\|\$\(\)\n\{\}\s\;\/\~\.\*\\\\\?]+access_log\s+/" + rep = "location.+\\(gif[\\w\\|\\$\\(\\)\n\\{\\}\\s\\;\\/\\~\\.\\*\\\\\\?]+access_log\\s+/" ng_conf = re.sub(rep, 'access_log /', ng_conf) ng_conf = ng_conf.replace("include enable-php-", "%s\n" % public.get_msg_gettext( "#Clear cache") + cureCache + "\n\t%s\n\t" % public.get_msg_gettext( @@ -3788,9 +3813,9 @@ server public.writeFile(ng_file, ng_conf) else: - # rep = "%s[\w\s\~\/\(\)\.\*\{\}\;\$\n\#]+.{1,66}[\s\w\/\*\.\;]+include enable-php-" % public.GetMsg( + # rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.{1,66}[\\s\\w\\/\\*\\.\\;]+include enable-php-" % public.GetMsg( # "CLEAR_CACHE") - rep = "%s[\w\s\~\/\(\)\.\*\{\}\;\$\n\#]+.*\n.*" % public.get_msg_gettext("#Clear cache") + rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.*\n.*" % public.get_msg_gettext("#Clear cache") # ng_conf = re.sub(rep, 'include enable-php-', ng_conf) ng_conf = re.sub(rep,'',ng_conf) oldconf = '''location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ @@ -3806,7 +3831,7 @@ server access_log /dev/null; }''' if "(gif|jpg|jpeg|png|bmp|swf)$" not in ng_conf: - ng_conf = re.sub('access_log\s*/www', oldconf + "\n\taccess_log /www",ng_conf) + ng_conf = re.sub(r'access_log\s*/www', oldconf + "\n\taccess_log /www",ng_conf) public.writeFile(ng_file, ng_conf) # 设置apache配置 @@ -3821,20 +3846,20 @@ server if os.path.exists(ap_file): ap_conf = public.readFile(ap_file) if p_conf == "[]": - rep = "\n*%s\n+\s+IncludeOptiona[\s\w\/\.\*]+" % public.get_msg_gettext('#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') ap_conf = re.sub(rep, '', ap_conf) public.writeFile(ap_file, ap_conf) return if sitename in p_conf: - rep = "combined(\n|.)+IncludeOptional.*\/proxy\/.*conf" + rep = "combined(\n|.)+IncludeOptional.*\\/proxy\\/.*conf" rep1 = "combined" if not re.search(rep, ap_conf): ap_conf = ap_conf.replace(rep1, rep1 + "\n\t%s\n\t" % public.get_msg_gettext( '#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') + "\n\tIncludeOptional " + ap_proxyfile) public.writeFile(ap_file, ap_conf) else: - # rep = "\n*#引用反向代理(\n|.)+IncludeOptional.*\/proxy\/.*conf" - rep = "\n*%s\n+\s+IncludeOptiona[\s\w\/\.\*]+" % public.get_msg_gettext('#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') + # rep = "\n*#引用反向代理(\n|.)+IncludeOptional.*\\/proxy\\/.*conf" + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') ap_conf = re.sub(rep, '', ap_conf) public.writeFile(ap_file, ap_conf) @@ -3877,7 +3902,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] # vhost文件 vhostpath = "%s/panel/vhost/nginx/%s.conf" % (self.setupPath, get.sitename) - rep = "location\s+/[\n\s]+{" + rep = "location\\s+/[\n\\s]+{" for i in [rewriteconfpath, nginxconfpath, vhostpath]: conf = public.readFile(i) @@ -3954,12 +3979,12 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] # 检查是否存在#Set Nginx Cache def check_annotate(self, data): - rep = "\n\s*#Set\s*Nginx\s*Cache" + rep = "\n\\s*#Set\\s*Nginx\\s*Cache" if re.search(rep, data): return True def old_proxy_conf(self,conf,ng_conf_file,get): - rep = 'location\s*\~\*.*gif\|png\|jpg\|css\|js\|woff\|woff2\)\$' + rep = r'location\s*\~\*.*gif\|png\|jpg\|css\|js\|woff\|woff2\)\$' if not re.search(rep,conf): return conf @@ -4005,23 +4030,23 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] # 如果代理URL后缀带有URI则删除URI,正则匹配不支持proxypass处带有uri php_pass_proxy = get.proxysite if get.proxysite[-1] == '/' or get.proxysite.count('/') > 2 or '?' in get.proxysite: - php_pass_proxy = re.search('(https?\:\/\/[\w\.]+)', get.proxysite).group(0) - ng_conf = re.sub("location\s+[\^\~]*\s?%s" % conf[i]["proxydir"], "location ^~ " + get.proxydir, ng_conf) - ng_conf = re.sub("proxy_pass\s+%s" % conf[i]["proxysite"], "proxy_pass " + get.proxysite, ng_conf) - ng_conf = re.sub("location\s+\~\*\s+\\\.\(php.*\n\{\s*proxy_pass\s+%s.*" % (php_pass_proxy), - "location ~* \.(php|jsp|cgi|asp|aspx)$\n{\n\tproxy_pass %s;" % php_pass_proxy,ng_conf) - ng_conf = re.sub("location\s+\~\*\s+\\\.\(gif.*\n\{\s*proxy_pass\s+%s.*" % (php_pass_proxy), - "location ~* \.(gif|png|jpg|css|js|woff|woff2)$\n{\n\tproxy_pass %s;" % php_pass_proxy,ng_conf) + php_pass_proxy = re.search(r'(https?\:\/\/[\w\.]+)', get.proxysite).group(0) + ng_conf = re.sub(r"location\s+[\^\~]*\s?%s" % conf[i]["proxydir"], "location ^~ " + get.proxydir, ng_conf) + ng_conf = re.sub(r"proxy_pass\s+%s" % conf[i]["proxysite"], "proxy_pass " + get.proxysite, ng_conf) + ng_conf = re.sub("location\\s+\\~\\*\\s+\\\\.\\(php.*\n\\{\\s*proxy_pass\\s+%s.*" % (php_pass_proxy), + "location ~* \\.(php|jsp|cgi|asp|aspx)$\n{\n\tproxy_pass %s;" % php_pass_proxy,ng_conf) + ng_conf = re.sub("location\\s+\\~\\*\\s+\\\\.\\(gif.*\n\\{\\s*proxy_pass\\s+%s.*" % (php_pass_proxy), + "location ~* \\.(gif|png|jpg|css|js|woff|woff2)$\n{\n\tproxy_pass %s;" % php_pass_proxy,ng_conf) backslash = "" if "Host $host" in ng_conf: backslash = "\\" - ng_conf = re.sub("\sHost\s+%s" % backslash + conf[i]["todomain"], " Host " + get.todomain, ng_conf) + ng_conf = re.sub(r"\sHost\s+%s" % backslash + conf[i]["todomain"], " Host " + get.todomain, ng_conf) cache_rep = r"proxy_cache_valid\s+200\s+304\s+301\s+302\s+\d+m;((\n|.)+expires\s+\d+m;)*" if int(get.cache) == 1: if re.search(cache_rep, ng_conf): - expires_rep = "\{\n\s+expires\s+12h;" + expires_rep = "\\{\n\\s+expires\\s+12h;" ng_conf = re.sub(expires_rep, "{", ng_conf) ng_conf = re.sub(cache_rep, "proxy_cache_valid 200 304 301 302 {0}m;".format(get.cachetime), ng_conf) @@ -4031,7 +4056,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] # proxy_cache cache_one; # proxy_cache_key $host$uri$is_args$args; # proxy_cache_valid 200 304 301 302 %sm;""" % (get.cachetime) - ng_cache = """ + ng_cache = r""" if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) { expires 1m; @@ -4041,16 +4066,16 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] proxy_cache_key $host$uri$is_args$args; proxy_cache_valid 200 304 301 302 %sm;""" % (get.cachetime) if self.check_annotate(ng_conf): - cache_rep = '\n\s*#Set\s*Nginx\s*Cache(.|\n)*no-cache;\s*\n*\s*\}' + cache_rep = '\n\\s*#Set\\s*Nginx\\s*Cache(.|\n)*no-cache;\\s*\n*\\s*\\}' ng_conf = re.sub(cache_rep, '\n\t#Set Nginx Cache\n' + ng_cache, ng_conf) else: - # cache_rep = '#proxy_set_header\s+Connection\s+"upgrade";' + # cache_rep = r'#proxy_set_header\s+Connection\s+"upgrade";' cache_rep = r"proxy_set_header\s+REMOTE-HOST\s+\$remote_addr;" ng_conf = re.sub(cache_rep, r"\n\tproxy_set_header\s+REMOTE-HOST\s+\$remote_addr;\n\t#Set Nginx Cache" + ng_cache, ng_conf) else: - no_cache = """ + no_cache = r""" #Set Nginx Cache set $static_file%s 0; if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) @@ -4081,7 +4106,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] subfilter = json.loads(get.subfilter) if str(conf[i]["subfilter"]) != str(subfilter) or ng_conf.find('sub_filter_once') == -1: if re.search(sub_rep, ng_conf): - sub_rep = "\s+proxy_set_header\s+Accept-Encoding(.|\n)+off;" + sub_rep = "\\s+proxy_set_header\\s+Accept-Encoding(.|\n)+off;" ng_conf = re.sub(sub_rep, "", ng_conf) # 构造替换字符串 @@ -4102,21 +4127,21 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] ng_sub_filter = ng_sub_filter % (ng_subdata) else: ng_sub_filter = '' - sub_rep = '#Set\s+Nginx\s+Cache' + sub_rep = r'#Set\s+Nginx\s+Cache' ng_conf = re.sub(sub_rep, '#Set Nginx Cache\n' + ng_sub_filter, ng_conf) # 修改apache配置 ap_conf = public.readFile(ap_conf_file) - ap_conf = re.sub("ProxyPass\s+%s\s+%s" % (conf[i]["proxydir"], conf[i]["proxysite"]), + ap_conf = re.sub(r"ProxyPass\s+%s\s+%s" % (conf[i]["proxydir"], conf[i]["proxysite"]), "ProxyPass %s %s" % (get.proxydir, get.proxysite), ap_conf) - ap_conf = re.sub("ProxyPassReverse\s+%s\s+%s" % (conf[i]["proxydir"], conf[i]["proxysite"]), + ap_conf = re.sub(r"ProxyPassReverse\s+%s\s+%s" % (conf[i]["proxydir"], conf[i]["proxysite"]), "ProxyPassReverse %s %s" % (get.proxydir, get.proxysite), ap_conf) # 修改OLS配置 p = "{p}/panel/vhost/openlitespeed/proxy/{s}/{n}_{s}.conf".format(p=self.setupPath, n=proxyname_md5, s=get.sitename) c = public.readFile(p) if c: - rep = 'address\s+(.*)' + rep = r'address\s+(.*)' new_proxysite = 'address\t{}'.format(get.proxysite) c = re.sub(rep, new_proxysite, c) public.writeFile(p, c) @@ -4124,7 +4149,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] # p = "{p}/panel/vhost/openlitespeed/proxy/{s}/urlrewrite/{n}_{s}.conf".format(p=self.setupPath,n=proxyname_md5,s=get.sitename) c = public.readFile(ols_conf_file) if c: - rep = 'RewriteRule\s*\^{}\(\.\*\)\$\s+http://{}/\$1\s*\[P,E=Proxy-Host:{}\]'.format( + rep = r'RewriteRule\s*\^{}\(\.\*\)\$\s+http://{}/\$1\s*\[P,E=Proxy-Host:{}\]'.format( conf[i]["proxydir"], get.proxyname, conf[i]["todomain"]) new_content = 'RewriteRule ^{}(.*)$ http://{}/$1 [P,E=Proxy-Host:{}]'.format(get.proxydir, get.proxyname, @@ -4184,7 +4209,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] # 构造清理缓存连接 # 构造缓存配置 - ng_cache = """ + ng_cache = r""" if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) { expires 1m; @@ -4193,7 +4218,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] proxy_cache cache_one; proxy_cache_key $host$uri$is_args$args; proxy_cache_valid 200 304 301 302 %sm;""" % (cachetime) - no_cache = """ + no_cache = r""" set $static_file%s 0; if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) { @@ -4204,7 +4229,7 @@ RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] { add_header Cache-Control no-cache; }""" % (random_string,random_string,random_string) - # rep = "(https?://[\w\.]+)" + # rep = r"(https?://[\w\.]+)" # proxysite1 = re.search(rep,get.proxysite).group(1) ng_proxy = ''' #PROXY-START%s @@ -4259,7 +4284,7 @@ location %s # 如果代理URL后缀带有URI则删除URI,正则匹配不支持proxypass处带有uri # php_pass_proxy = get.proxysite # if get.proxysite[-1] == '/' or get.proxysite.count('/') > 2 or '?' in get.proxysite: - # php_pass_proxy = re.search('(https?\:\/\/[\w\.]+)', get.proxysite).group(0) + # php_pass_proxy = re.search(r'(https?\:\/\/[\w\.]+)', get.proxysite).group(0) if advanced == 1: if proxydir[-1] != '/': proxydir = '{}/'.format(proxydir) @@ -4359,7 +4384,7 @@ location %s file = self.setupPath + "/nginx/conf/nginx.conf" conf = public.readFile(file) if (conf.find('include proxy.conf;') == -1): - rep = "include\s+mime.types;" + rep = r"include\s+mime.types;" conf = re.sub(rep, "include mime.types;\n\tinclude proxy.conf;", conf) public.writeFile(file, conf) @@ -4530,13 +4555,13 @@ location %s if name.find('.') == -1: return conf = public.readFile(filename) # 取域名 - rep = "server_name\s+(.+);" + rep = r"server_name\s+(.+);" tmp = re.search(rep, conf) if not tmp: return domains = tmp.groups()[0].split(' ') # 取根目录 - rep = "root\s+(.+);" + rep = r"root\s+(.+);" tmp = re.search(rep, conf) if not tmp: return path = tmp.groups()[0] @@ -4552,13 +4577,13 @@ location %s conf = public.readFile(filename) # 取域名 - rep = "ServerAlias\s+(.+)\n" + rep = "ServerAlias\\s+(.+)\n" tmp = re.search(rep, conf) if not tmp: return domains = tmp.groups()[0].split(' ') # 取根目录 - rep = u"DocumentRoot\s+\"(.+)\"\n" + rep = u"DocumentRoot\\s+\"(.+)\"\n" tmp = re.search(rep, conf) if not tmp: return path = tmp.groups()[0] @@ -4612,7 +4637,7 @@ location %s if conf.find(rep) != -1: conf = conf.replace(rep, "/dev/null") else: - # conf = re.sub('}\n\s+access_log\s+off', '}\n\taccess_log ' + rep, conf) + # conf = re.sub('}\n\\s+access_log\\s+off', '}\n\taccess_log ' + rep, conf) conf = conf.replace('access_log /dev/null', 'access_log ' + rep) public.writeFile(filename, conf) @@ -4620,12 +4645,12 @@ location %s filename = public.GetConfigValue('setup_path') + '/panel/vhost/openlitespeed/detail/' + get.name + '.conf' conf = public.readFile(filename) if conf: - rep = "\nerrorlog(.|\n)*compressArchive\s*1\s*\n}" + rep = "\nerrorlog(.|\n)*compressArchive\\s*1\\s*\n}" tmp = re.search(rep, conf) s = 'on' if not tmp: s = 'off' - rep = "\n#errorlog(.|\n)*compressArchive\s*1\s*\n#}" + rep = "\n#errorlog(.|\n)*compressArchive\\s*1\\s*\n#}" tmp = re.search(rep, conf) tmp = tmp.group() if tmp: @@ -4652,7 +4677,7 @@ location %s conf = public.readFile(filename) if not conf: return True if conf.find('#ErrorLog') != -1: return False - #if re.search("}\n*\s*access_log\s+off", conf): + #if re.search("}\n*\\s*access_log\\s+off", conf): if conf.find("access_log /dev/null") != -1: return False if re.search('\n#accesslog', conf): return False @@ -4748,7 +4773,7 @@ location %s if os.path.exists(get.configFile): conf = public.readFile(get.configFile) - rep = "\n\s*#AUTH_START(.|\n){1,200}#AUTH_END" + rep = "\n\\s*#AUTH_START(.|\n){1,200}#AUTH_END" conf = re.sub(rep, '', conf) public.writeFile(get.configFile, conf) @@ -4759,7 +4784,7 @@ location %s if os.path.exists(get.configFile): conf = public.readFile(get.configFile) - rep = "\n\s*#AUTH_START(.|\n){1,200}#AUTH_END" + rep = "\n\\s*#AUTH_START(.|\n){1,200}#AUTH_END" conf = re.sub(rep, '', conf) conf = conf.replace(' #Require all granted', " Require all granted") public.writeFile(get.configFile, conf) @@ -4772,7 +4797,7 @@ location %s siteName = get.siteName name = siteName.replace('.', '_') - rep = "^(\d{1,3}\.){3,3}\d{1,3}$" + rep = r"^(\d{1,3}\.){3,3}\d{1,3}$" if re.match(rep, siteName): return public.return_msg_gettext(False, 'ERROR, primary domain cannot be IP address!') # nginx @@ -4780,7 +4805,7 @@ location %s if os.path.exists(filename): conf = public.readFile(filename) if conf.find('#TOMCAT-START') != -1: return self.CloseTomcat(get) - tomcatConf = '''#TOMCAT-START + tomcatConf = r'''#TOMCAT-START location / { proxy_pass "http://%s:8080"; @@ -4843,7 +4868,7 @@ location %s filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = "\s*#TOMCAT-START(.|\n)+#TOMCAT-END" + rep = "\\s*#TOMCAT-START(.|\n)+#TOMCAT-END" conf = re.sub(rep, '', conf) public.writeFile(filename, conf) @@ -4851,7 +4876,7 @@ location %s filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = "\s*#TOMCAT-START(.|\n)+#TOMCAT-END" + rep = "\\s*#TOMCAT-START(.|\n)+#TOMCAT-END" conf = re.sub(rep, '', conf) public.writeFile(filename, conf) public.ExecShell('rm -rf ' + self.setupPath + '/panel/vhost/tomcat/' + name) @@ -4876,7 +4901,7 @@ location %s filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = '\s*root\s+(.+);' + rep = r'\s*root\s+(.+);' path = re.search(rep, conf) if not path: return public.return_msg_gettext(False, 'Get Site run path false') @@ -4885,7 +4910,7 @@ location %s filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = '\s*DocumentRoot\s*"(.+)"\s*\n' + rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n' path = re.search(rep, conf) if not path: return public.return_msg_gettext(False, 'Get Site run path false') @@ -4894,7 +4919,7 @@ location %s filename = self.setupPath + '/panel/vhost/openlitespeed/' + siteName + '.conf' if os.path.exists(filename): conf = public.readFile(filename) - rep = "vhRoot\s*(.*)" + rep = r"vhRoot\s*(.*)" path = re.search(rep, conf) if not path: return public.return_msg_gettext(False, 'Get Site run path false') @@ -4936,7 +4961,7 @@ location %s if os.path.exists(filename): conf = public.readFile(filename) if conf: - rep = '\s*root\s+(.+);' + rep = r'\s*root\s+(.+);' tmp = re.search(rep,conf) if tmp: path = tmp.groups()[0] @@ -4948,7 +4973,7 @@ location %s if os.path.exists(filename): conf = public.readFile(filename) if conf: - rep = '\s*DocumentRoot\s*"(.+)"\s*\n' + rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n' tmp = re.search(rep,conf) if tmp: path = tmp.groups()[0] @@ -4974,11 +4999,11 @@ location %s ols_conf = public.readFile(ols_conf_file) if not ols_conf: return - reg = '#VHOST\s*{s}\s*START(.|\n)+#VHOST\s*{s}\s*END'.format(s=sitename) + reg = '#VHOST\\s*{s}\\s*START(.|\n)+#VHOST\\s*{s}\\s*END'.format(s=sitename) tmp = re.search(reg, ols_conf) if not tmp: return - reg = "vhRoot\s*(.*)" + reg = r"vhRoot\s*(.*)" # tmp = re.search(reg,tmp.group()) # if not tmp: # return @@ -5002,13 +5027,13 @@ location %s path = self.setupPath + '/panel/vhost/nginx/' + defaultSite + '.conf' if os.path.exists(path): conf = public.readFile(path) - rep = "listen\s+80.+;" + rep = r"listen\s+80.+;" conf = re.sub(rep, 'listen 80;', conf, 1) - rep = "listen\s+\[::\]:80.+;" + rep = r"listen\s+\[::\]:80.+;" conf = re.sub(rep, 'listen [::]:80;', conf, 1) - rep = "listen\s+443.+;" + rep = r"listen\s+443.+;" conf = re.sub(rep, 'listen 443 ssl' + http2 + ';', conf, 1) - rep = "listen\s+\[::\]:443.+;" + rep = r"listen\s+\[::\]:443.+;" conf = re.sub(rep, 'listen [::]:443 ssl' + http2 + ';', conf, 1) public.writeFile(path, conf) @@ -5035,13 +5060,13 @@ location %s path = self.setupPath + '/panel/vhost/nginx/' + get.name + '.conf' if os.path.exists(path): conf = public.readFile(path) - rep = "listen\s+80\s*;" + rep = r"listen\s+80\s*;" conf = re.sub(rep, 'listen 80 default_server;', conf, 1) - rep = "listen\s+\[::\]:80\s*;" + rep = r"listen\s+\[::\]:80\s*;" conf = re.sub(rep, 'listen [::]:80 default_server;', conf, 1) - rep = "listen\s+443\s*ssl\s*\w*\s*;" + rep = r"listen\s+443\s*ssl\s*\w*\s*;" conf = re.sub(rep, 'listen 443 ssl' + http2 + ' default_server;', conf, 1) - rep = "listen\s+\[::\]:443\s*ssl\s*\w*\s*;" + rep = r"listen\s+\[::\]:443\s*ssl\s*\w*\s*;" conf = re.sub(rep, 'listen [::]:443 ssl' + http2 + ' default_server;', conf, 1) public.writeFile(path, conf) @@ -5142,12 +5167,12 @@ location %s if conf.find('SECURITY-START') != -1: rep = "#SECURITY-START(\n|.)+#SECURITY-END" tmp = re.search(rep, conf).group() - data['fix'] = re.search("\(.+\)\$", tmp).group().replace('(', '').replace(')$', '').replace('|', ',') + data['fix'] = re.search(r"\(.+\)\$", tmp).group().replace('(', '').replace(')$', '').replace('|', ',') try: data['domains'] = ','.join( - list(set(re.search("valid_referers\s+none\s+blocked\s+(.+);\n", tmp).groups()[0].split()))) + list(set(re.search("valid_referers\\s+none\\s+blocked\\s+(.+);\n", tmp).groups()[0].split()))) except: - data['domains'] = ','.join(list(set(re.search("valid_referers\s+(.+);\n", tmp).groups()[0].split()))) + data['domains'] = ','.join(list(set(re.search("valid_referers\\s+(.+);\n", tmp).groups()[0].split()))) data['status'] = True data['none'] = tmp.find('none blocked') != -1 try: @@ -5187,10 +5212,10 @@ location %s if conf.find('SECURITY-START') != -1: # 先替换域名部分,防止域名过多导致替换失败 - rep = "\s+valid_referers.+" + rep = r"\s+valid_referers.+" conf = re.sub(rep,'',conf) # 再替换配置部分 - rep = "\s+#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" + rep = "\\s+#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" conf = re.sub(rep,'\n',conf) public.write_log_gettext('Site manager', "Hotlink Protection for site [{}] disabled!", (get.name,)) else: @@ -5203,7 +5228,7 @@ location %s if get.return_rule[0] != '/': return public.return_msg_gettext(False, 'Response resources should use URI path or HTTP status code, such as: /test.png or 404') return_rule = 'rewrite /.* {} break'.format(get.return_rule) - rconf = '''%s + rconf = r'''%s location ~ .*\.(%s)$ { expires 30d; @@ -5216,7 +5241,7 @@ location %s #SECURITY-END include enable-php-''' % (public.get_msg_gettext('#SECURITY-START Hotlink protection configuration'), get.fix.strip().replace(',', '|'), get.domains.strip().replace(',', ' '), return_rule) - conf = re.sub("include\s+enable-php-", rconf, conf) + conf = re.sub(r"include\s+enable-php-", rconf, conf) public.write_log_gettext('Site manager', "Hotlink Protection for site [{}] enabled!", (get.name,)) public.writeFile(file, conf) @@ -5262,7 +5287,7 @@ location %s os.makedirs(cond_dir) file = cond_dir + get.name + '.conf' if get.status == '1': - conf = """ + conf = r""" RewriteCond %{HTTP_REFERER} !^$ RewriteCond %{HTTP_REFERER} !BTDOMAIN_NAME [NC] RewriteRule \.(BTPFILE)$ /404.html [R,NC] @@ -5270,7 +5295,7 @@ RewriteRule \.(BTPFILE)$ /404.html [R,NC] conf = conf.replace('BTDOMAIN_NAME', get.domains.replace(',', ' ')).replace('BTPFILE', get.fix.replace(',', '|')) else: - conf = """ + conf = r""" RewriteCond %{HTTP_REFERER} !BTDOMAIN_NAME [NC] RewriteRule \.(BTPFILE)$ /404.html [R,NC] """ diff --git a/class/panelSiteController.py b/class/panelSiteController.py index ef725f00..54d7ef27 100644 --- a/class/panelSiteController.py +++ b/class/panelSiteController.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ diff --git a/class/panelTask.py b/class/panelTask.py index 9904a49c..89d676de 100644 --- a/class/panelTask.py +++ b/class/panelTask.py @@ -1,10 +1,10 @@ #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2019-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2019-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------ @@ -369,7 +369,7 @@ class bt_task: self.install_rar() pass_opt = '-p-' if password: - password = password.replace("&","\&").replace('"','\"') + password = password.replace("&",r"\&").replace('"','\"') pass_opt = '-p"{}"'.format(password) public.ExecShell(rar_file + ' x '+ pass_opt +' -u -y "' + sfile + '" "' + dfile + '" &> ' + log_file) @@ -571,7 +571,7 @@ class bt_task: public.ExecShell("sed -i '/password=/d' " + my_cnf) if act: mycnf = public.readFile(my_cnf) - rep = "\[mysqldump\]\nuser=root" + rep = "\\[mysqldump\\]\nuser=root" sea = "[mysqldump]\n" subStr = sea + "user=root\npassword=\"" + root + "\"\n" mycnf = mycnf.replace(sea, subStr) diff --git a/class/panelVideo.py b/class/panelVideo.py index 22bad697..3298d85c 100644 --- a/class/panelVideo.py +++ b/class/panelVideo.py @@ -1,8 +1,8 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: hwliang <2020-05-18> # +------------------------------------------------------------------- diff --git a/class/panelWaf.py b/class/panelWaf.py index b76b3f71..ba03af41 100644 --- a/class/panelWaf.py +++ b/class/panelWaf.py @@ -1,6 +1,6 @@ #!/usr/bin/python #coding: utf-8 -# Author: lkqiang +# Author: lkqiang # panelWaf.py # code: 面板基础安全类 # +------------------------------------------------------------------- diff --git a/class/panelWarning.py b/class/panelWarning.py index 2399b3a8..47e6edd8 100644 --- a/class/panelWarning.py +++ b/class/panelWarning.py @@ -1,8 +1,8 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: hwliang <2020-08-04> # +------------------------------------------------------------------- @@ -227,7 +227,7 @@ class panelWarning: if not os.path.exists(self.new_vul_list): if zip_file != '': downfile = self.__path+'/'+zip_file - public.downloadFile("/safe_warning/{}".format(public.get_url(),zip_file), downfile) + public.downloadFile("{}/safe_warning/{}".format(public.get_url(), zip_file), downfile) o, e = public.ExecShell("unzip -o {} -d {}".format(downfile, self.__path)) # 解压报错 if e != "": @@ -1433,9 +1433,9 @@ if __name__ == "__main__": # #coding: utf-8 # # +------------------------------------------------------------------- -# # | 宝塔Linux面板 +# # | aaPanel # # +------------------------------------------------------------------- -# # | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# # | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # # +------------------------------------------------------------------- # # | Author: hwliang <2020-08-04> # # +------------------------------------------------------------------- diff --git a/class/panel_php_run.php b/class/panel_php_run.php index 3babd4bd..9885b037 100644 --- a/class/panel_php_run.php +++ b/class/panel_php_run.php @@ -1,10 +1,10 @@ +// | Author: hwliang // +------------------------------------------------------------------- // +------------------------------------------------------------------- diff --git a/class/panel_restore.py b/class/panel_restore.py index 485b3f3b..adf4df60 100644 --- a/class/panel_restore.py +++ b/class/panel_restore.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: zhwwen +# Author: zhwwen # ------------------------------------------------------------------- # # ------------------------------ @@ -127,7 +127,7 @@ class panel_restore: def restore_website_backup(self,args): """ @name 恢复站点文件 - @author zhwen + @author zhwen @parma file_name 备份得文件名 @parma site_id 网站id """ @@ -171,7 +171,7 @@ class panel_restore: def get_progress(self, get): """ @name 获取进度日志 - @author zhwen + @author zhwen """ # result = public.GetNumLines(self._progress_file, 20) result = public.ExecShell('tail -n 20 {}'.format(self._progress_file))[0] @@ -183,7 +183,7 @@ class panel_restore: def restore_db_backup(self,args): """ @name 恢复站点文件 - @author zhwen + @author zhwen @parma file_name 备份得文件名 /www/backup/database/db_test_com_20200817_112722.sql.gz|Google Drive|db_test_com_20200817_112722.sql.gz @parma obj_name 数据库名 """ diff --git a/class/panel_search.py b/class/panel_search.py index 2b820413..84f74d0b 100644 --- a/class/panel_search.py +++ b/class/panel_search.py @@ -2,7 +2,7 @@ # +------------------------------------------------------------------- # | version :1.0 # +------------------------------------------------------------------- -# | Author: 梁凯强 <1249648969@qq.com> +# | Author: 梁凯强 <1249648969@aapanel.com> # +------------------------------------------------------------------- # | 快速检索 # +-------------------------------------------------------------------- diff --git a/class/panel_telegram_bot.py b/class/panel_telegram_bot.py index 6c447970..c9b3b4e6 100644 --- a/class/panel_telegram_bot.py +++ b/class/panel_telegram_bot.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: zhw +# | Author: zhw # +------------------------------------------------------------------- import public @@ -59,12 +59,42 @@ class panel_telegram_bot: # 使用tg机器人发送消息 def send_by_tg_bot(self,content,parse_mode=None): + """ + @author hezhihong + """ + "parse_mode 消息格式 html/markdown/markdownv2" - content = self.process_character(content) + + # content = self.process_character(content) conf = self.get_tg_conf() try: - bot = telegram.Bot(conf['bot_token']) - result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2") - return result + result= self.send_message(conf['bot_token'],conf['my_id'],content) + if not result['status']: + return False + return True except: - return False \ No newline at end of file + return False + + + def send_message(self, bot_token, chat_id, msg): + """ + tg发送信息 + @msg 消息正文 + @author hezhihong + """ + msg = self.process_character(msg) + url = 'https://api.telegram.org/bot{}/sendMessage'.format(bot_token) + data = { + 'chat_id': chat_id, + 'text': msg, + 'parse_mode':'MarkdownV2', + } + try: + import requests + response = requests.post(url, json=data) + if response.status_code == 200: + return public.returnMsg(True,0,response.json()) + else: + return public.returnMsg(False,json.loads(response.text)) + except Exception as e: + return public.returnMsg(False,str(e)) diff --git a/class/password.py b/class/password.py index c0d78e2f..68b43a6d 100644 --- a/class/password.py +++ b/class/password.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: lkqiang +# | Author: lkqiang # +------------------------------------------------------------------- # +-------------------------------------------------------------------- # | 密码管理 @@ -88,7 +88,7 @@ class password: # 开启密码登陆 def SetPassword(self, get): - ssh_password = '\n#?PasswordAuthentication\s\w+' + ssh_password = '\n#?PasswordAuthentication\\s\\w+' file = public.readFile('/etc/ssh/sshd_config') if len(re.findall(ssh_password, file)) == 0: file_result = file + '\nPasswordAuthentication yes' @@ -114,15 +114,15 @@ class password: public.ExecShell("ssh-keygen -t %s -P '' -f ~/.ssh/id_rsa |echo y" % type) if os.path.exists(file[0]): public.ExecShell('cat %s >%s && chmod 600 %s' % (file[0], file[-1], file[-1])) - rec = '\n#?RSAAuthentication\s\w+' - rec2 = '\n#?PubkeyAuthentication\s\w+' + rec = '\n#?RSAAuthentication\\s\\w+' + rec2 = '\n#?PubkeyAuthentication\\s\\w+' file = public.readFile('/etc/ssh/sshd_config') if len(re.findall(rec, file)) == 0: file = file + '\nRSAAuthentication yes' if len(re.findall(rec2, file)) == 0: file = file + '\nPubkeyAuthentication yes' file_ssh = re.sub(rec, '\nRSAAuthentication yes', file) file_result = re.sub(rec2, '\nPubkeyAuthentication yes', file_ssh) if ssh == 'no': - ssh_password = '\n#?PasswordAuthentication\s\w+' + ssh_password = '\n#?PasswordAuthentication\\s\\w+' if len(re.findall(ssh_password, file_result)) == 0: file_result = file_result + '\nPasswordAuthentication no' else: @@ -137,8 +137,8 @@ class password: # 关闭sshkey def StopKey(self, get): file = ['/root/.ssh/id_rsa.pub', '/root/.ssh/id_rsa', '/root/.ssh/authorized_keys'] - rec = '\n#?RSAAuthentication\s\w+' - rec2 = '\n#?PubkeyAuthentication\s\w+' + rec = '\n#?RSAAuthentication\\s\\w+' + rec2 = '\n#?PubkeyAuthentication\\s\\w+' file = public.readFile('/etc/ssh/sshd_config') file_ssh = re.sub(rec, '\n#RSAAuthentication no', file) file_result = re.sub(rec2, '\n#PubkeyAuthentication no', file_ssh) @@ -151,9 +151,9 @@ class password: def GetConfig(self, get): result = {} file = public.readFile('/etc/ssh/sshd_config') - rec = '\n#?RSAAuthentication\s\w+' - pubkey = '\n#?PubkeyAuthentication\s\w+' - ssh_password = '\nPasswordAuthentication\s\w+' + rec = '\n#?RSAAuthentication\\s\\w+' + pubkey = '\n#?PubkeyAuthentication\\s\\w+' + ssh_password = '\nPasswordAuthentication\\s\\w+' ret = re.findall(ssh_password, file) if not ret: result['password'] = 'no' @@ -183,7 +183,7 @@ class password: # 关闭密码方式 def StopPassword(self, get): file = public.readFile('/etc/ssh/sshd_config') - ssh_password = '\n#?PasswordAuthentication\s\w+' + ssh_password = '\n#?PasswordAuthentication\\s\\w+' file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file) self.Wirte('/etc/ssh/sshd_config', file_result) self.RestartSsh() diff --git a/class/pay.py b/class/pay.py index 1af3d111..11d98a44 100644 --- a/class/pay.py +++ b/class/pay.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2019 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- # # ┏┓ ┏┓ diff --git a/class/php_execute_deny.py b/class/php_execute_deny.py index bb5b191a..60609f91 100644 --- a/class/php_execute_deny.py +++ b/class/php_execute_deny.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2020 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zhwen +# Author: zhwen #------------------------------------------------------------------- #------------------------------ @@ -24,7 +24,7 @@ class PhpExecuteDeny: def get_php_deny(self,args): ''' # 添加某个网站禁止运行PHP - author: zhwen + author: zhwen :param args: website 网站名 str :return: ''' @@ -45,7 +45,7 @@ class PhpExecuteDeny: deny_name = [i.split('_')[-1] for i in data] result = [] for i in deny_name: - reg = '#BEGIN_DENY_{}\n\s*location\s*\~\*\s*\^(.*)\.\*.*\((.*)\)\$'.format(i) + reg = '#BEGIN_DENY_{}\n\\s*location\\s*\\~\\*\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i) deny_directory = re.search(reg,conf).groups()[0] deny_suffix = re.search(reg,conf).groups()[1] result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix}) @@ -59,7 +59,7 @@ class PhpExecuteDeny: deny_name = [i.split('_')[-1] for i in data] result = [] for i in deny_name: - reg = '#BEGIN_DENY_{}\n\s* + author: zhwen :param args: website 网站名 str :param args: deny_name 规则名称 str :param args: suffix 禁止访问的后续名 str @@ -122,7 +122,7 @@ class PhpExecuteDeny: if not conf: return False if not dir and not suffix: - reg = '\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name) + reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name) conf = re.sub(reg,'',conf) else: new = ''' @@ -143,10 +143,10 @@ class PhpExecuteDeny: if not conf: return False if not dir and not suffix: - reg = '\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name) + reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name) conf = re.sub(reg,'',conf) else: - new = ''' + new = r''' #BEGIN_DENY_{n} Order allow,deny @@ -156,7 +156,7 @@ class PhpExecuteDeny: '''.format(n=name,d=dir,s=suffix) if '#BEGIN_DENY_{}'.format(name) in conf: return True - conf = re.sub('#DENY\s*FILES',new+'\n #DENY FILES',conf) + conf = re.sub(r'#DENY\s*FILES',new+'\n #DENY FILES',conf) public.writeFile(self.ap_website_conf,conf) return True @@ -165,17 +165,17 @@ class PhpExecuteDeny: if not conf: return False if not dir and not suffix: - reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\s*'.format(n=name) + reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\\s*'.format(n=name) conf = re.sub(reg,'',conf) else: - new = ''' + new = r''' #BEGIN_DENY_{n} rules RewriteRule ^{d}.*\.({s})$ - [F,L] #END_DENY_{n} '''.format(n=name,d=dir,s=suffix) if '#BEGIN_DENY_{}'.format(name) in conf: return True - conf = re.sub('autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf) + conf = re.sub(r'autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf) public.writeFile(self.ols_website_conf,conf) return True @@ -183,7 +183,7 @@ class PhpExecuteDeny: def del_php_deny(self,args): ''' # 添加某个网站禁止运行PHP - author: zhwen + author: zhwen :param args: website 网站名 str :param args: deny_name 规则名称 str :return: diff --git a/class/plugin_deployment.py b/class/plugin_deployment.py index 10f32f1b..1eb40cde 100644 --- a/class/plugin_deployment.py +++ b/class/plugin_deployment.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- #+-------------------------------------------------------------------- @@ -309,7 +309,7 @@ class plugin_deployment: if type(pinfo['enable_functions']) == str : pinfo['enable_functions'] = pinfo['enable_functions'].strip().split(',') php_f = public.GetConfigValue('setup_path') + '/php/' + php_version + '/etc/php.ini' php_c = public.readFile(php_f) - rep = "disable_functions\s*=\s{0,1}(.*)\n" + rep = "disable_functions\\s*=\\s{0,1}(.*)\n" tmp = re.search(rep,php_c).groups() disable_functions = tmp[0].split(',') for fun in pinfo['enable_functions']: @@ -441,7 +441,7 @@ class plugin_deployment: os.remove(p_info) i_ndex_html = path + '/index.html' if os.path.exists(i_ndex_html): os.remove(i_ndex_html) - if not self.copy_to(p_tmp,path): public.ExecShell(("\cp -arf " + p_tmp + '/. ' + path + '/').replace('//','/')) + if not self.copy_to(p_tmp,path): public.ExecShell((r"\cp -arf " + p_tmp + '/. ' + path + '/').replace('//','/')) except: pass public.ExecShell("rm -rf " + self.__tmp + '/*') return p_config diff --git a/class/process_task.py b/class/process_task.py index 7ecc9794..ca38f65c 100644 --- a/class/process_task.py +++ b/class/process_task.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- #-------------------------------- diff --git a/class/projectModel/bt_docker/dk_compose.py b/class/projectModel/bt_docker/dk_compose.py index eedf3a4e..21723a85 100644 --- a/class/projectModel/bt_docker/dk_compose.py +++ b/class/projectModel/bt_docker/dk_compose.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ @@ -128,7 +128,7 @@ class main :#line:22 import re #line:293 import projectModel .bt_docker .dk_container as dc #line:294 OO00OOOOO0O0OOOOO =[]#line:295 - O0OOOO000O00OOO0O =re .findall ("container_name\s*:\s*[\"\']+(.*)[\'\"]",OO0OOOO0O00OO0OO0 )#line:296 + O0OOOO000O00OOO0O =re .findall ("container_name\\s*:\\s*[\"\']+(.*)[\'\"]",OO0OOOO0O00OO0OO0 )#line:296 O0O0OO0O00OOO0O00 =dc .main ().get_list (OOO0OO00OO0OO00OO )#line:297 if not O0O0OO0O00OOO0O00 ["status"]:#line:298 return public .return_msg_gettext (False ,"Error getting container list!")#line:299 @@ -138,7 +138,7 @@ class main :#line:22 OO00OOOOO0O0OOOOO .append (O0OOO0O0OO0O0OO00 ['name'])#line:303 if OO00OOOOO0O0OOOOO :#line:304 return public .return_msg_gettext (False ,"The container name in the template:
                                    [{}] already exists!".format (", ".join (OO00OOOOO0O0OOOOO )))#line:305 - OO0000O0OO00OO0O0 ="(\d+):\d+"#line:307 + OO0000O0OO00OO0O0 =r"(\d+):\d+"#line:307 O00O00OOOO0OO00O0 =re .findall (OO0000O0OO00OO0O0 ,OO0OOOO0O00OO0OO0 )#line:308 for O0OOO00O00O0OOOOO in O00O00OOOO0OO00O0 :#line:309 if dp .check_socket (O0OOO00O00O0OOOOO ):#line:310 diff --git a/class/projectModel/bt_docker/dk_container.py b/class/projectModel/bt_docker/dk_container.py index 1c10d29f..ff1cce07 100644 --- a/class/projectModel/bt_docker/dk_container.py +++ b/class/projectModel/bt_docker/dk_container.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_host.py b/class/projectModel/bt_docker/dk_host.py index be52a4be..e1db7cf2 100644 --- a/class/projectModel/bt_docker/dk_host.py +++ b/class/projectModel/bt_docker/dk_host.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw # ------------------------------------------------------------------- # ------------------------------ diff --git a/class/projectModel/bt_docker/dk_image.py b/class/projectModel/bt_docker/dk_image.py index 38f8a06d..6bd4c953 100644 --- a/class/projectModel/bt_docker/dk_image.py +++ b/class/projectModel/bt_docker/dk_image.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_monitor.py b/class/projectModel/bt_docker/dk_monitor.py index c29757e2..5f884a1e 100644 --- a/class/projectModel/bt_docker/dk_monitor.py +++ b/class/projectModel/bt_docker/dk_monitor.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_network.py b/class/projectModel/bt_docker/dk_network.py index b542a74e..cd6088bc 100644 --- a/class/projectModel/bt_docker/dk_network.py +++ b/class/projectModel/bt_docker/dk_network.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_public.py b/class/projectModel/bt_docker/dk_public.py index 9ead8ba9..beb2c462 100644 --- a/class/projectModel/bt_docker/dk_public.py +++ b/class/projectModel/bt_docker/dk_public.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ @@ -42,7 +42,7 @@ def docker_client_low (url ="unix:///var/run/docker.sock"):#line:25 def get_cpu_count ():#line:39 import re #line:40 OOO00O00O0000O00O =open ('/proc/cpuinfo','r').read ()#line:41 - OO00O0000OO000OOO ="processor\s*:"#line:42 + OO00O0000OO000OOO =r"processor\s*:"#line:42 OO0OOO0O00O0O0OOO =re .findall (OO00O0000OO000OOO ,OOO00O00O0000O00O )#line:43 if not OO0OOO0O00O0O0OOO :#line:44 return 0 #line:45 diff --git a/class/projectModel/bt_docker/dk_registry.py b/class/projectModel/bt_docker/dk_registry.py index b2f83bd1..8a787c15 100644 --- a/class/projectModel/bt_docker/dk_registry.py +++ b/class/projectModel/bt_docker/dk_registry.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_screen.py b/class/projectModel/bt_docker/dk_screen.py index be2f8165..688ce9d4 100644 --- a/class/projectModel/bt_docker/dk_screen.py +++ b/class/projectModel/bt_docker/dk_screen.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_setup.py b/class/projectModel/bt_docker/dk_setup.py index 44aded50..5d3afc3e 100644 --- a/class/projectModel/bt_docker/dk_setup.py +++ b/class/projectModel/bt_docker/dk_setup.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ @@ -41,7 +41,7 @@ class main :#line:1 O00OO000O0OO0000O ="SAVE={}".format (OOO00O0000000OO0O )#line:36 public .writeFile (OOOO0O0OO00O00O00 ,O00OO000O0OO0000O )#line:37 return public .returnMsg (True ,"Set up successfully!")#line:38 - O00OO000O0OO0000O =re .sub ("SAVE\s*=\s*\d+","SAVE={}".format (OOO00O0000000OO0O ),O00OO000O0OO0000O )#line:39 + O00OO000O0OO0000O =re .sub (r"SAVE\s*=\s*\d+","SAVE={}".format (OOO00O0000000OO0O ),O00OO000O0OO0000O )#line:39 public .writeFile (OOOO0O0OO00O00O00 ,O00OO000O0OO0000O )#line:40 dp .write_log ("Set the monitoring time to [] days!".format (OOO00O0000000OO0O ))#line:41 return public .returnMsg (True ,"Set up successfully!")#line:42 diff --git a/class/projectModel/bt_docker/dk_status.py b/class/projectModel/bt_docker/dk_status.py index 0d23e631..b0a1ed1b 100644 --- a/class/projectModel/bt_docker/dk_status.py +++ b/class/projectModel/bt_docker/dk_status.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/bt_docker/dk_volume.py b/class/projectModel/bt_docker/dk_volume.py index 951b1a02..ded652ee 100644 --- a/class/projectModel/bt_docker/dk_volume.py +++ b/class/projectModel/bt_docker/dk_volume.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw #------------------------------------------------------------------- #------------------------------ diff --git a/class/projectModel/dockerModel.py b/class/projectModel/dockerModel.py index 35a02184..6a79438b 100644 --- a/class/projectModel/dockerModel.py +++ b/class/projectModel/dockerModel.py @@ -1,10 +1,10 @@ # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: zouhw +# Author: zouhw # ------------------------------------------------------------------- # ------------------------------ @@ -27,8 +27,8 @@ class main :#line:15 if OO0O0O000O00O00O0 ['mod_name']in ['base']:return public .return_status_code (1000 ,'Wrong call!')#line:43 public .exists_args ('def_name,mod_name',OO0O0O000O00O00O0 )#line:44 if OO0O0O000O00O00O0 ['def_name'].find ('__')!=-1 :return public .return_status_code (1000 ,'The called method name cannot contain the "__" characterrong call!')#line:45 - if not re .match (r"^\w+$",OO0O0O000O00O00O0 ['mod_name']):return public .return_status_code (1000 ,'The called module name cannot contain characters other than \w')#line:46 - if not re .match (r"^\w+$",OO0O0O000O00O00O0 ['def_name']):return public .return_status_code (1000 ,'The called module name cannot contain characters other than \w')#line:47 + if not re .match (r"^\w+$",OO0O0O000O00O00O0 ['mod_name']):return public .return_status_code (1000 ,r'The called module name cannot contain characters other than \w')#line:46 + if not re .match (r"^\w+$",OO0O0O000O00O00O0 ['def_name']):return public .return_status_code (1000 ,r'The called module name cannot contain characters other than \w')#line:47 except :#line:48 return public .get_error_object ()#line:49 O0OOO0O0O00O00O0O ="dk_{}".format (OO0O0O000O00O00O0 ['mod_name'].strip ())#line:51 diff --git a/class/projectModel/nodejsModel.py b/class/projectModel/nodejsModel.py index fb2d93d0..ba552046 100644 --- a/class/projectModel/nodejsModel.py +++ b/class/projectModel/nodejsModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -464,7 +464,7 @@ export PATH return public.return_error('Please install nodejs version manager first') project_name = get.project_name.strip() - if not re.match("^\w+$",project_name): + if not re.match(r"^\w+$",project_name): return public.return_error('The project name format is incorrect and supports letters, numbers, underscores, and expressions: ^[0-9A-Za-z_]$') if public.M('sites').where('name=?',(get.project_name,)).count(): diff --git a/class/projectModel/quotaModel.py b/class/projectModel/quotaModel.py index 92452283..2168f9e6 100644 --- a/class/projectModel/quotaModel.py +++ b/class/projectModel/quotaModel.py @@ -1,304 +1,304 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: hwliang -#------------------------------------------------------------------- - -#------------------------------ -# 磁盘配额管理 -#------------------------------ -import os ,public ,psutil ,json ,time ,re -from projectModel .base import projectBase -class main (projectBase ): - xfs_quota ="xfs_quota" - __O0O000000OO000000 ='{}/config/quota.json'.format (public .get_panel_path ()) - __O00OOOO00OO0OO0OO ='{}/config/mysql_quota.json'.format (public .get_panel_path ()) - __O0O00000000OOO0O0 =public .to_string ([84 ,104 ,105 ,115 ,32 ,102 ,101 ,97 ,116 ,117 ,114 ,101 ,32 ,105 ,115 ,32 ,101 ,120 ,99 ,108 ,117 ,115 ,105 ,118 ,101 ,32 ,116 ,111 ,32 ,116 ,104 ,101 ,32 ,112 ,114 ,111 ,32 ,101 ,100 ,105 ,116 ,105 ,111 ,110 ,44 ,32 ,112 ,108 ,101 ,97 ,115 ,101 ,32 ,97 ,99 ,116 ,105 ,118 ,97 ,116 ,101 ,32 ,105 ,116 ,32 ,102 ,105 ,114 ,115 ,116 ]) - def __init__ (OOO0000OO0OO0O00O ): - _O0OOO0O00O0O0O000 ='{}/data/quota_install.pl'.format (public .get_panel_path ()) - if not os .path .exists (_O0OOO0O00O0O0O000 ): - O0OO0OOO0OO0O0O00 ='/usr/sbin/xfs_quota' - if not os .path .exists (O0OO0OOO0OO0O0O00 ): - if os .path .exists ('/usr/bin/apt-get'): - public .ExecShell ('nohup apt-get install xfsprogs -y > /dev/null &') - else : - public .ExecShell ('nohup yum install xfsprogs -y > /dev/null &') - public .writeFile (_O0OOO0O00O0O0O000 ,'True') - if os .path .exists ("/sbin/xfs_quota"): - OOO0000OO0OO0O00O .xfs_quota ="/sbin/xfs_quota" - def __OO00OO00O0000O0OO (O00OOO0000000000O ,args =None ): - "" - OO00000OO0O00O0O0 =[] - for OO00O0OO0O0000OOO in psutil .disk_partitions (): - if OO00O0OO0O0000OOO .fstype =='xfs': - OO00000OO0O00O0O0 .append ((OO00O0OO0O0000OOO .mountpoint ,OO00O0OO0O0000OOO .device ,psutil .disk_usage (OO00O0OO0O0000OOO .mountpoint ).free ,OO00O0OO0O0000OOO .opts .split (','))) - return OO00000OO0O00O0O0 - def __O00OOOO0OO00O0OO0 (O000O00O0O0000O00 ,args =None ): - "" - return O000O00O0O0000O00 .__O000O0O0000OOOOOO (args .path ) - def __O000000O0O0000OOO (O00OOO00000OOOO00 ,O000O00O00O00OOOO ): - "" - O0OOO000OO000OOO0 =O00OOO00000OOOO00 .__OO00OO00O0000O0OO () - OO0OO000OO00O0OO0 =None - for O0O0OOOO00O00OOO0 in O0OOO000OO000OOO0 : - if O0O0OOOO00O00OOO0 [0 ]=="/": - OO0OO000OO00O0OO0 =O0O0OOOO00O00OOO0 - if O000O00O00O00OOOO .find (O0O0OOOO00O00OOO0 [0 ]+'/')==0 : - if not 'prjquota'in O0O0OOOO00O00OOO0 [3 ]: - return O0O0OOOO00O00OOO0 - return O0O0OOOO00O00OOO0 [1 ] - if OO0OO000OO00O0OO0 and O000O00O00O00OOOO .find (OO0OO000OO00O0OO0 [0 ])==0 : - if not 'prjquota'in OO0OO000OO00O0OO0 [3 ]: - return OO0OO000OO00O0OO0 - return OO0OO000OO00O0OO0 [1 ] - return '' - def __O000O0O0000OOOOOO (OO0OOO00OO000000O ,O0O0O0OOOOO0OOO0O ): - "" - if not os .path .exists (O0O0O0OOOOO0OOO0O ):return -1 - if not os .path .isdir (O0O0O0OOOOO0OOO0O ):return -2 - OO0O0OO0O0000000O =OO0OOO00OO000000O .__OO00OO00O0000O0OO () - O0O00OO00O0O0O00O =None - for O0OO0O0O00O00OO00 in OO0O0OO0O0000000O : - if O0OO0O0O00O00OO00 [0 ]=="/": - O0O00OO00O0O0O00O =O0OO0O0O00O00OO00 - if O0O0O0OOOOO0OOO0O .find (O0OO0O0O00O00OO00 [0 ]+'/')==0 : - return O0OO0O0O00O00OO00 [2 ]/1024 /1024 - if O0O00OO00O0O0O00O and O0O0O0OOOOO0OOO0O .find (O0O00OO00O0O0O00O [0 ])==0 : - return O0O00OO00O0O0O00O [2 ]/1024 /1024 - return -3 - def get_quota_path_list (OOOO000OO000O000O ,args =None ,get_path =None ): - "" - if not os .path .exists (OOOO000OO000O000O .__O0O000000OO000000 ): - public .writeFile (OOOO000OO000O000O .__O0O000000OO000000 ,'[]') - O00OO00OO000O0OO0 =json .loads (public .readFile (OOOO000OO000O000O .__O0O000000OO000000 )) - OO00OOO0O00OOOO00 =[] - for O0OOOO0O0O000O0O0 in O00OO00OO000O0OO0 : - if not os .path .exists (O0OOOO0O0O000O0O0 ['path'])or not os .path .isdir (O0OOOO0O0O000O0O0 ['path'])or os .path .islink (O0OOOO0O0O000O0O0 ['path']):continue - if get_path : - if O0OOOO0O0O000O0O0 ['path']==get_path : - OOO00OO0OOO0O0OO0 =psutil .disk_usage (O0OOOO0O0O000O0O0 ['path']) - O0OOOO0O0O000O0O0 ['used']=OOO00OO0OOO0O0OO0 .used - O0OOOO0O0O000O0O0 ['free']=OOO00OO0OOO0O0OO0 .free - return O0OOOO0O0O000O0O0 - else : - continue - OOO00OO0OOO0O0OO0 =psutil .disk_usage (O0OOOO0O0O000O0O0 ['path']) - O0OOOO0O0O000O0O0 ['used']=OOO00OO0OOO0O0OO0 .used - O0OOOO0O0O000O0O0 ['free']=OOO00OO0OOO0O0OO0 .free - OO00OOO0O00OOOO00 .append (O0OOOO0O0O000O0O0 ) - if get_path : - return {'size':0 ,'used':0 ,'free':0 } - if len (OO00OOO0O00OOOO00 )!=len (O00OO00OO000O0OO0 ): - public .writeFile (OOOO000OO000O000O .__O0O000000OO000000 ,json .dumps (OO00OOO0O00OOOO00 )) - return O00OO00OO000O0OO0 - def get_quota_mysql_list (OO000O0O00OOOOO0O ,args =None ,get_name =None ): - "" - if not os .path .exists (OO000O0O00OOOOO0O .__O00OOOO00OO0OO0OO ): - public .writeFile (OO000O0O00OOOOO0O .__O00OOOO00OO0OO0OO ,'[]') - OO00OO0000O0O0OOO =json .loads (public .readFile (OO000O0O00OOOOO0O .__O00OOOO00OO0OO0OO )) - O00O00OOO0OOO00OO =[] - OOOO000000OO0O0O0 =public .M ('databases') - for OOO0OO0O000O0O00O in OO00OO0000O0O0OOO : - if get_name : - if OOO0OO0O000O0O00O ['db_name']==get_name : - OOO0OO0O000O0O00O ['used']=OOO0OO0O000O0O00O ['used']=int (public .get_database_size_by_name (OOO0OO0O000O0O00O ['db_name'])) - _O0OO00O000O0OOO00 =OOO0OO0O000O0O00O ['size']*1024 *1024 - if (OOO0OO0O000O0O00O ['used']>_O0OO00O000O0OOO00 and OOO0OO0O000O0O00O ['insert_accept'])or (OOO0OO0O000O0O00O ['used']<_O0OO00O000O0OOO00 and not OOO0OO0O000O0O00O ['insert_accept']): - OO000O0O00OOOOO0O .mysql_quota_check () - return OOO0OO0O000O0O00O - else : - if OOOO000000OO0O0O0 .where ('name=?',OOO0OO0O000O0O00O ['db_name']).count (): - if args :OOO0OO0O000O0O00O ['used']=int (public .get_database_size_by_name (OOO0OO0O000O0O00O ['db_name'])) - O00O00OOO0OOO00OO .append (OOO0OO0O000O0O00O ) - OOOO000000OO0O0O0 .close () - if get_name : - return {'size':0 ,'used':0 } - if len (O00O00OOO0OOO00OO )!=len (OO00OO0000O0O0OOO ): - public .writeFile (OO000O0O00OOOOO0O .__O00OOOO00OO0OO0OO ,json .dumps (O00O00OOO0OOO00OO )) - return O00O00OOO0OOO00OO - def __OOO000O00000O0OO0 (OOOOOOOO0OOO000OO ,OOO0000OOO00O000O ,OO0OOO0000000OOO0 ,O0OOO0OO00O00O0OO ,OOOO00000OOOO0OO0 ): - "" - O0OO0O00O000O00O0 =OOO0000OOO00O000O .execute ("REVOKE ALL PRIVILEGES ON `{}`.* FROM '{}'@'{}';".format (O0OOO0OO00O00O0OO ,OO0OOO0000000OOO0 ,OOOO00000OOOO0OO0 )) - if O0OO0O00O000O00O0 :raise public .PanelError ('Failed to remove insert permission for database user:{}'.format (O0OO0O00O000O00O0 )) - O0OO0O00O000O00O0 =OOO0000OOO00O000O .execute ("GRANT SELECT, DELETE, CREATE, DROP, REFERENCES, INDEX, CREATE TEMPORARY TABLES, LOCK TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON `{}`.* TO '{}'@'{}';".format (O0OOO0OO00O00O0OO ,OO0OOO0000000OOO0 ,OOOO00000OOOO0OO0 )) - if O0OO0O00O000O00O0 :raise public .PanelError ('Failed to remove insert permission for database user:{}'.format (O0OO0O00O000O00O0 )) - OOO0000OOO00O000O .execute ("FLUSH PRIVILEGES;") - return True - def __O000O000O0OOOOOOO (OO00OOO00OO0OOO00 ,O000OOOO0OO0O00OO ,O0OOO0OOO0OO00O00 ,O00OOO0OO0OO0OOOO ,OOOOOOOO0OO00O000 ): - "" - OOO0O000OOOOO0O00 =O000OOOO0OO0O00OO .execute ("REVOKE ALL PRIVILEGES ON `{}`.* FROM '{}'@'{}';".format (O00OOO0OO0OO0OOOO ,O0OOO0OOO0OO00O00 ,OOOOOOOO0OO00O000 )) - if OOO0O000OOOOO0O00 :raise public .PanelError ('Failed to restore insert privileges for database user:{}'.format (OOO0O000OOOOO0O00 )) - OOO0O000OOOOO0O00 =O000OOOO0OO0O00OO .execute ("GRANT ALL PRIVILEGES ON `{}`.* TO '{}'@'{}';".format (O00OOO0OO0OO0OOOO ,O0OOO0OOO0OO00O00 ,OOOOOOOO0OO00O000 )) - if OOO0O000OOOOO0O00 :raise public .PanelError ('Failed to restore insert privileges for database user:{}'.format (OOO0O000OOOOO0O00 )) - O000OOOO0OO0O00OO .execute ("FLUSH PRIVILEGES;") - return True - def mysql_quota_service (O00000OO00O0O0000 ): - "" - while 1 : - time .sleep (600 ) - O00000OO00O0O0000 .mysql_quota_check () - def __O0OO00O00O0OOO0OO (O0OO0O0000OOO0O0O ,OO0O0OOOOO0O00O0O ): - try : - if type (OO0O0OOOOO0O00O0O )!=list and type (OO0O0OOOOO0O00O0O )!=str :OO0O0OOOOO0O00O0O =list (OO0O0OOOOO0O00O0O ) - return OO0O0OOOOO0O00O0O - except :return [] - def mysql_quota_check (OO0O0OOO00O0OO00O ): - "" - if not OO0O0OOO00O0OO00O .__OO000OOO0OO0O00OO ():return public .returnMsg (False ,OO0O0OOO00O0OO00O .__O0O00000000OOO0O0 ) - O00000OOO00OO000O =OO0O0OOO00O0OO00O .get_quota_mysql_list () - for OO0O0O0O0OO00OOO0 in O00000OOO00OO000O : - try : - if OO0O0O0O0OO00OOO0 ['size']<1 : - if not OO0O0O0O0OO00OOO0 ['insert_accept']: - OO0O0OOO00O0OO00O .__O000O000O0OOOOOOO (O0OOOO000OOO0O0OO ,O0000O00OO0O00O0O ,OO0O0O0O0OO00OOO0 ['db_name'],O000O00O0000OOO0O [0 ]) - OO0O0O0O0OO00OOO0 ['insert_accept']=True - public .WriteLog ('Quota','Database [{}] quota has been closed, restore insert privileges'.format (OO0O0O0O0OO00OOO0 ['db_name'])) - continue - O000O000O00000O0O =public .get_database_size_by_name (OO0O0O0O0OO00OOO0 ['db_name'])/1024 /1024 - O0000O00OO0O00O0O =public .M ('databases').where ('name=?',(OO0O0O0O0OO00OOO0 ['db_name'],)).getField ('username') - O0OOOO000OOO0O0OO =public .get_mysql_obj (OO0O0O0O0OO00OOO0 ['db_name']) - O0O0OOO0O0O00OOOO =OO0O0OOO00O0OO00O .__O0OO00O00O0OOO0OO (O0OOOO000OOO0O0OO .query ("select Host from mysql.user where User='"+O0000O00OO0O00O0O +"'")) - if O000O000O00000O0O time .time () - def modify_mysql_quota (OO00OO0OOOO00O000 ,OOOO00O0OO000OOOO ): - "" - if not OO00OO0OOOO00O000 .__OO000OOO0OO0O00OO ():return public .returnMsg (False ,OO00OO0OOOO00O000 .__O0O00000000OOO0O0 ) - if not os .path .exists (OO00OO0OOOO00O000 .__O00OOOO00OO0OO0OO ): - public .writeFile (OO00OO0OOOO00O000 .__O00OOOO00OO0OO0OO ,'[]') - if not re .match (r"^\d+$",OOOO00O0OO000OOOO .size ):return public .returnMsg (False ,'Quota size must be an integer!') - OO0O0OOOO00000OOO =int (OOOO00O0OO000OOOO ['size']) - OO0OO0OO000O0O00O =OOOO00O0OO000OOOO .db_name .strip () - O0OOOO0OOOO0O0000 =json .loads (public .readFile (OO00OO0OOOO00O000 .__O00OOOO00OO0OO0OO )) - O0O00O0O000OOO0O0 =False - for OO00O0000O0OOOOO0 in O0OOOO0OOOO0O0000 : - if OO00O0000O0OOOOO0 ['db_name']==OO0OO0OO000O0O00O : - OO00O0000O0OOOOO0 ['size']=OO0O0OOOO00000OOO - O0O00O0O000OOO0O0 =True - break - if O0O00O0O000OOO0O0 : - public .writeFile (OO00OO0OOOO00O000 .__O00OOOO00OO0OO0OO ,json .dumps (O0OOOO0OOOO0O0000 )) - public .WriteLog ('Quota','Modify the quota limit of database [{db_name}] to: {size}MB'.format (db_name =OO0OO0OO000O0O00O ,size =OO0O0OOOO00000OOO )) - OO00OO0OOOO00O000 .mysql_quota_check () - return public .returnMsg (True ,'Successfully modified') - return OO00OO0OOOO00O000 .__O0000OO00OO00O0O0 (OOOO00O0OO000OOOO ) - def __O0OOOO00O00000OOO (O0OO0O0O00O0OO00O ,O0O0O0O0O000O0OOO ): - "" - O0OOO0OO0O0000O00 =[] - OO0000OO0OO00O0O0 =public .ExecShell ("{xfs_quota} -x -c report {mountpoint}|awk '{{print $1}}'|grep '#'".format (xfs_quota =O0OO0O0O00O0OO00O .xfs_quota ,mountpoint =O0O0O0O0O000O0OOO ))[0 ] - if not OO0000OO0OO00O0O0 :return O0OOO0OO0O0000O00 - for O0OO0O00OO0O0OO0O in OO0000OO0OO00O0O0 .split ('\n'): - if O0OO0O00OO0O0OO0O :O0OOO0OO0O0000O00 .append (int (O0OO0O00OO0O0OO0O .split ('#')[-1 ])) - return O0OOO0OO0O0000O00 - def __OO00O000OOOOOOO00 (O0OOO0O00OOOO0OOO ,OO0000O0OOOOOOO0O ,OOO0000OO0O0000OO ): - "" - O00O00OO0O000O000 =1001 - if not OO0000O0OOOOOOO0O :return O00O00OO0O000O000 - O00O00OO0O000O000 =OO0000O0OOOOOOO0O [-1 ]['id']+1 - O0OO00OO00O00O000 =sorted (O0OOO0O00OOOO0OOO .__O0OOOO00O00000OOO (OOO0000OO0O0000OO )) - if O0OO00OO00O00O000 : - if O0OO00OO00O00O000 [-1 ]>O00O00OO0O000O000 : - O00O00OO0O000O000 =O0OO00OO00O00O000 [-1 ]+1 - return O00O00OO0O000O000 - def __OOOO0O000OOOO00O0 (OO0OO00O0O0000O0O ,O000O0OOOO0OOO00O ): - "" - if not OO0OO00O0O0000O0O .__OO000OOO0OO0O00OO ():return public .returnMsg (False ,OO0OO00O0O0000O0O .__O0O00000000OOO0O0 ) - OOOO000000O00OO00 =O000O0OOOO0OOO00O .path .strip () - OO000OOO0000OO00O =int (O000O0OOOO0OOO00O .size ) - if not os .path .exists (OOOO000000O00OO00 ):return public .returnMsg (False ,'The specified directory does not exist') - if os .path .isfile (OOOO000000O00OO00 ):return public .returnMsg (False ,'this is not a valid directory!') - if os .path .islink (OOOO000000O00OO00 ):return public .returnMsg (False ,'The specified directory is a soft link!') - OOOOO000OO0000OOO =OO0OO00O0O0000O0O .get_quota_path_list () - for O00O0OOOO0O00O000 in OOOOO000OO0000OOO : - if O00O0OOOO0O00O000 ['path']==OOOO000000O00OO00 :return public .returnMsg (False ,'The specified directory has already set a quota!') - OO000O0OO0000OO00 =OO0OO00O0O0000O0O .__O000O0O0000OOOOOO (OOOO000000O00OO00 ) - if OO000O0OO0000OO00 ==-3 :return public .returnMsg (False ,'The partition where the specified directory is located is not an XFS partition and does not support directory quotas!') - if OO000O0OO0000OO00 ==-2 :return public .returnMsg (False ,'this is not a valid directory!') - if OO000O0OO0000OO00 ==-1 :return public .returnMsg (False ,'The specified directory does not exist!') - if OO000OOO0000OO00O >OO000O0OO0000OO00 :return public .returnMsg (False ,'Insufficient quota capacity available for the specified disk!') - OOO0O0O0O0O000O0O =OO0OO00O0O0000O0O .__O000000O0O0000OOO (OOOO000000O00OO00 ) - if not OOO0O0O0O0O000O0O :return public .returnMsg (False ,'The specified directory is not in the xfs disk partition!') - if isinstance (OOO0O0O0O0O000O0O ,tuple ):return public .returnMsg (False ,'The directory quota function is not enabled for this xfs partition, please increase the [prjquota] parameter when mounting the partition

                                    /etc/fstab File configuration example:

                                    {mountpoint}       {path}           xfs             defaults,prjquota       0 0

                                    Note: You need to remount the partition or reboot the server to take effect


                                    The setup is completed and the error still occurs, please refer to
                                    How to enable disk quota for root directory'.format (mountpoint =OOO0O0O0O0O000O0O [1 ],path =OOO0O0O0O0O000O0O [0 ])) - O0000OO000OO00O0O =OO0OO00O0O0000O0O .__OO00O000OOOOOOO00 (OOOOO000OO0000OOO ,OOO0O0O0O0O000O0O ) - OO0O0O0O0OOOOO00O =public .ExecShell ("{xfs_quota} -x -c 'project -s -p {path} {quota_id}'".format (path =OOOO000000O00OO00 ,quota_id =O0000OO000OO00O0O ,xfs_quota =OO0OO00O0O0000O0O .xfs_quota )) - if OO0O0O0O0OOOOO00O [1 ]:return public .returnMsg (False ,OO0O0O0O0OOOOO00O [1 ]) - OO0O0O0O0OOOOO00O =public .ExecShell ("{xfs_quota} -x -c 'limit -p bhard={size}m {quota_id}' {mountpoint}".format (quota_id =O0000OO000OO00O0O ,size =OO000OOO0000OO00O ,mountpoint =OOO0O0O0O0O000O0O ,xfs_quota =OO0OO00O0O0000O0O .xfs_quota )) - if OO0O0O0O0OOOOO00O [1 ]:return public .returnMsg (False ,OO0O0O0O0OOOOO00O [1 ]) - OOOOO000OO0000OOO .append ({'path':O000O0OOOO0OOO00O .path ,'size':OO000OOO0000OO00O ,'id':O0000OO000OO00O0O }) - public .writeFile (OO0OO00O0O0000O0O .__O0O000000OO000000 ,json .dumps (OOOOO000OO0000OOO )) - public .WriteLog ('Quota','The quota limit for creating directory [{path}] is: {size}MB'.format (path =OOOO000000O00OO00 ,size =OO000OOO0000OO00O )) - return public .returnMsg (True ,'Added successfully') - def modify_path_quota (O0O0OOOOO0OOO0OOO ,OO0O0000000OO0OO0 ): - "" - if not O0O0OOOOO0OOO0OOO .__OO000OOO0OO0O00OO ():return public .returnMsg (False ,O0O0OOOOO0OOO0OOO .__O0O00000000OOO0O0 ) - O00O0O00OOO0O0O0O =OO0O0000000OO0OO0 .path .strip () - if not re .match (r"^\d+$",OO0O0000000OO0OO0 .size ):return public .returnMsg (False ,'Quota size must be an integer!') - O0O00O0OOO0O0O0OO =int (OO0O0000000OO0OO0 .size ) - if not os .path .exists (O00O0O00OOO0O0O0O ):return public .returnMsg (False ,'The specified directory does not exist') - if os .path .isfile (O00O0O00OOO0O0O0O ):return public .returnMsg (False ,'This is not a valid directory!') - if os .path .islink (O00O0O00OOO0O0O0O ):return public .returnMsg (False ,'The specified directory is a soft link!') - OO0O00O00OO0000OO =O0O0OOOOO0OOO0OOO .get_quota_path_list () - OOOO0OO0OOOOO0OOO =0 - for OOO00000OOOOO0OOO in OO0O00O00OO0000OO : - if OOO00000OOOOO0OOO ['path']==O00O0O00OOO0O0O0O : - OOOO0OO0OOOOO0OOO =OOO00000OOOOO0OOO ['id'] - break - if not OOOO0OO0OOOOO0OOO :return O0O0OOOOO0OOO0OOO .__OOOO0O000OOOO00O0 (OO0O0000000OO0OO0 ) - O0O0O00OOOOOO00O0 =O0O0OOOOO0OOO0OOO .__O000O0O0000OOOOOO (O00O0O00OOO0O0O0O ) - if O0O0O00OOOOOO00O0 ==-3 :return public .returnMsg (False ,'The partition where the specified directory is located is not an XFS partition, and directory quotas are not supported!') - if O0O0O00OOOOOO00O0 ==-2 :return public .returnMsg (False ,'This is not a valid directory!') - if O0O0O00OOOOOO00O0 ==-1 :return public .returnMsg (False ,'The specified directory does not exist!') - if O0O00O0OOO0O0O0OO >O0O0O00OOOOOO00O0 :return public .returnMsg (False ,'Insufficient quota capacity available for the specified disk!') - OOO0OO0O0000O0OO0 =O0O0OOOOO0OOO0OOO .__O000000O0O0000OOO (O00O0O00OOO0O0O0O ) - if not OOO0OO0O0000O0OO0 :return public .returnMsg (False ,'The specified directory is not in the xfs disk partition!') - if isinstance (OOO0OO0O0000O0OO0 ,tuple ):return public .returnMsg (False ,'The directory quota function is not enabled for this xfs partition, please increase the [prjquota] parameter when mounting the partition

                                    /etc/fstab File configuration example:

                                    {mountpoint}       {path}           xfs             defaults,prjquota       0 0

                                    Note: After the configuration is complete, you need to remount the partition or restart the server to take effect

                                    '.format (mountpoint =OOO0OO0O0000O0OO0 [1 ],path =OOO0OO0O0000O0OO0 [0 ])) - OO00O0OOO00OOOO0O =public .ExecShell ("{xfs_quota} -x -c 'project -s -p {path} {quota_id}'".format (path =O00O0O00OOO0O0O0O ,quota_id =OOOO0OO0OOOOO0OOO ,xfs_quota =O0O0OOOOO0OOO0OOO .xfs_quota )) - if OO00O0OOO00OOOO0O [1 ]:return public .returnMsg (False ,OO00O0OOO00OOOO0O [1 ]) - OO00O0OOO00OOOO0O =public .ExecShell ("{xfs_quota} -x -c 'limit -p bhard={size}m {quota_id}' {mountpoint}".format (quota_id =OOOO0OO0OOOOO0OOO ,size =O0O00O0OOO0O0O0OO ,mountpoint =OOO0OO0O0000O0OO0 ,xfs_quota =O0O0OOOOO0OOO0OOO .xfs_quota )) - if OO00O0OOO00OOOO0O [1 ]:return public .returnMsg (False ,OO00O0OOO00OOOO0O [1 ]) - for OOO00000OOOOO0OOO in OO0O00O00OO0000OO : - if OOO00000OOOOO0OOO ['path']==O00O0O00OOO0O0O0O : - OOO00000OOOOO0OOO ['size']=O0O00O0OOO0O0O0OO - break - public .writeFile (O0O0OOOOO0OOO0OOO .__O0O000000OO000000 ,json .dumps (OO0O00O00OO0000OO )) - public .WriteLog ('Quota','Modify the quota limit of directory [{path}] to:{size}MB'.format (path =O00O0O00OOO0O0O0O ,size =O0O00O0OOO0O0O0OO )) - return public .returnMsg (True ,'Successfully modified') +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfoMbif5SSuhU+ED0z8LQ0uRP+8tAmQ/sx0zSKtQtV+DZAsSdFCQjtILVBd7FvDmtwc6THtaa0pvFowRX+Q+yEWI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +PEKPgJeDDCLnL9UcS39EYQ0oxS0YHncLtyklh46m5EBkNzGCoSotSCFeO4A9wJZj +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +Z71ucSR75ppLp8TcGPXjF3pMbrAmHfUi73meeu5lk5g= +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 ++aWWMzKsJO+aM6tUDo7l22gaN/EfQwY9pVk1jkrGHubq5BMU7tPycF7U4lfA+PnV +NyzlU2YOTFNZFPd7RK11YaUw7ZYsBQH6PJOb5a5HaopbJMLTygrp/eIUmAMGN6Od +s+bHc/90a3lxFTPvWEu+yQ4hdETfvFmr96jd16JVhvU= +a4WDhREt5YBbYTTI0+NOTlxdCabhZWLzl3dzPJCL2ns= +H4fJMREGje1RhDKY1u71WkFaRhA2/BXughjkfaZp1RetweG/KCGDvlR75NN9AWxqG7njwjkItebPDjCvvD0ScUXpMVMO8Q7fKdfHqxQvhc0Qo+XFxK2fsZP0OPVxNa30 +agP6Y0IXgzGYLtZLVcCCZ/0T599xXC8rG/FDWEro2DxfJQNabVUQQfe30RgJll2GWgYLMccciONnZHIUS3Z5qWHn2s7WlDckd+TogFxnamkRlczRvd0o9wecbthheLVo +I3U+Q+NUzPWPEuq1msm2jktlvsiPJjAhTZasqurt+e02ugu/S01xy+8g1TfxXg2xBw7u6ZrdCIKv6h8lGzSaB2XtdEhETr84GetEdGL9JNROgGPC7MQ7C+tI+RwFXBStB1/PylFqV2Y5eewvjz6yzB0KARJjv+PUr3VN5C1ldweqNOD6WfVOtgYZlJOd/RKai2gm1Xe/1qHEsrMqBKfzR0r88ShNiOdlXNPN5Q2Kc+9CXEnQQ5w/3oU63fxP1wk3R02xH78wobUwoMnSpGoiOFRVJcXce2/cO/j80Cr9xp/Bq36qcHrFNfxxLOo4NoQeITeD12nvkWwSaNgmpkti9tZqh3rrD0bz/KPjsePqDqaSBQuy8Z+bZn9RP82p+6QscQO8YmQ8zBEN9H1QouaZMs033/PTbriEeQYkdZyPQmaWKMjODPdHjGM3AvUXrex5lNXifEog6jVp9jpCvLXiQWKOl6vCXdhA6N09FEV5+hc23vD2eelgSUOeS/CVWnQn +9GxZpCRwMRDPejWR2Vvf+BGs2TNDawMKvXw7wkGFKycwYV+KrGJBLOWE5ZKcXjfZ +hqlhGn9T+umTkW8M7AGCyf53pAfC7nEQD4HNVdEJQZLIkmHZl8U57qY1DeVTGYl+nUTy7H49ZrUNzIpKIAr9zZIIyN/9N+wehqLU+wEqAEsnKcNuw/Damkm1gJmpUF/q +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YwSw+qPU1x9H7h5sO7rqpwAq9aEd9/ZDyB7sxwH9WQaDw== +TP9zhKzoqP2ZyB/hk4eVp4PgFV6qyVcVcXyg9pUYTH+fe1NKLaf5spkcfWXgRsYJtnS4hcBQTfomcVa3NGK2kw== +1V2v8QerKOmubvSxgB4eTBhWeQzn26oNLaz4XtiKd3AdDQBUXiqXQwhmu5WlhFn60xMYWLG5JhZsJWdFpyqQ7A== +VmVrGQo2zRokW/ZuO9bN6w0DuXITxu54bKEKszNtCJvIZ0v/QRc2IeaSOENH3l9T90Rkz5x6Un2VmLIOzr8dwA== +VmVrGQo2zRokW/ZuO9bN65gGPMYTOZHI9ZQoxsnmP7KLEtSB0GLS5Se79m+TG2t9135KBsD2gO3FZdgx0O9OKmGhezJAz/k4uf64WYthAg9CvHenbGCdg+rCL1QdVGZO +VmVrGQo2zRokW/ZuO9bN63uze4Lnmw99IoHHLSLuFlE= +VmVrGQo2zRokW/ZuO9bN65gGPMYTOZHI9ZQoxsnmP7JlthBTUL7R5HqxLcjsSXIrwynkyFYqgigXe0guno1SD0MxRQkoinrozEUjd2i76KQ2FdVaMOaG6NgLEeroNbqY +xWoGNWjKGPfI4gq8aHoTfCMtL0f3r/mGFy+NqqYlupPtN4LM6Lh/S65ElrzIM2mEFR4wbzAXWHZrnhVLLLAO0g== +ut2tmPKYoa0Hz0cuVsUj3tMSGh1ko3z0+MlUSmSyMwhIb1zJinXvIFDxLfLhTecS/1MzMv2RHX+TvRordfgZoA== +x2LOCSeSzacmh81ySUhNRyMnvRWiL5oO6Qgq1srO26J5oIdOF2MB2jFyE3kJu+XCO3xi5v0zS0Bck5oPwGmqdg== +5nKwQL/1xmQIH02KLn2uCz4YF8qrIN87bFE85HkLc/i7k2M794x2Yq0LfVLigc6ht6OQMPr12G5ehRdntmVxAA== +cr04jPiHcZs6CKfXkpLJpg== +k2Zl28bOLN38qJfNGTaCZ6rMM0mTyJFOy3hLy7u57NY= +yfhcGVf2iYc+Ztxqwy9MUIKrmif2LWU+zznzn9UpLXI/s+FPgY2+Hxtx2sMMpbTlTne9WXFpqgJJ6gPk5Wav9w== +2wtz2EJ9gcVOn6ULRb1bGIe07hjE+74SGaG0w588uApK/2t5XjeyKC8OdX+1C8HjQ+IfmVBZwTDF0pqgdxhjkQ== +VmVrGQo2zRokW/ZuO9bN6/lqCmGJfh2uNEvtBuw26X8w+5enulKar/ZSvLijxaNb/dPvHp6PgHlhD6KLaQ5LS4HdQeIvfc9DPai/jd4+mwsBcN1vPq7nE76tV7UQ7HVwLJrb0xura69ySsodzPM1j9yiS4WA+QFYcOiZSLVhiLqfCr9XBVFfJx44SoYr2facnIdAeqw2baAk/6b5GJTW0bSxIFm9YutewCUa95yzA1QYUb+WlbdF5fohXe/pPENEyoNRGxO1JUaGh6nK9g7YnA== +KQe2cpNa8FkcC9SS/NMi8CpV7JOOSbLjDX5EVV0mMeZBU6EHWzQukb4HO6oqSoSn +GI0tVzKIRtvBR9LqilOl4U0EMIK6j0pKu8mjRYtcrdG7dZ269LI7frBhzviGc3tyjRVIj7Ajag15BCDZNPPiCQ== +cr04jPiHcZs6CKfXkpLJpg== +KQe2cpNa8FkcC9SS/NMi8G0P9GY6qNLNYbwjnq7t4XZr0KjhChiSWxjHizc34qaJqim4buwZW/fRlFpl/CdD8LcP/CgbTeIlzjwrd8jnfS4= +UrJP/jKni+bt3Mjt/LR2/JixjGffYHjGQSIIYHxxdbx6x+W3qD4ODikQky2/dTb0FJxL5jKdPUwgMlZ4WeWfOn9MU6Yj+i91j0dzx6dZUvM= +cr04jPiHcZs6CKfXkpLJpg== +JLLU9+nVY1JUMr5gL/q4iFLnR8rZZKgJ3OwXpvTNq3REjghdQYFTNphtNqdZRRnh+6KIUHLhYejxdcDDAMpidcvFYi6gVlk0eEAx627Syho= +h5FvPAwG81WNEoTw22EDzkR3HrRLEnwFXkWthPsBXv5FxJO4EU4fGo07ytQwj7zA +H4vRWcZMdeGTkaux2duG85KYo6NtqzfaByFi2mNPcgBBHwoC77PGMeI/lbIfKdFGFKByKlbEQzoPmxasTNih9g== +2wtz2EJ9gcVOn6ULRb1bGDs+vEyftqMNg0ecms5Hu3Ns+s3h9c+vz6ytuh+HVVQR +VmVrGQo2zRokW/ZuO9bN64dGT517FFoHJ0TEwdOhtfnc+CAijAojPGqiIIwkNeAf+ikp4vrFkNt/evEciFGybQ== +2wtz2EJ9gcVOn6ULRb1bGNqBvm70dZ7HtlEosE/Nvm4WGWQyuwgvUYRCgxKPD3IXayptF1loB8Wt5TBeELqccO+40wL0pvNylzYrUHw6JBE= +VmVrGQo2zRokW/ZuO9bN6x4A95AfXu9BB2SGVN+xyx0QdXN/K+kB5RwpHBtFUdMco7LWTmbDJvRzHVtYD+qEVQ== +VmVrGQo2zRokW/ZuO9bN67ezeGNjgojkyMvJrElaSuLp1VoIPcnvjrt1egmDrxsT +VmVrGQo2zRokW/ZuO9bN62PFnbgPIhp3A8iSXWSsu5IPoEqGgdDHCzJcdTWYiFwo +T09EhIon/1QoA8oqVWmf9YDvrNcJ40cqakka+PgK4WIoJfqJFeo+lh65ATFyUEMyF0Y/+zwyDgyEpxfdJC1T9s7UhkpoJHbxUBQZk4Fz00RqB4T9ckqgL8prAZIi3eST +1V2v8QerKOmubvSxgB4eTBy6rynLclEPfDHFwfFdGkrqZYjti0bOFlAKQAZdWvRRBTq4Tw3TkA7zqlFYV3GEGw== +VmVrGQo2zRokW/ZuO9bN631PXfqa0WsPxY3cPKsjN++sf3pPhl4PmY0D+iiZJP6K +L0eUthVnpkGsmKFAX6d+uKiZA2NxmniTgCRjkzRry5tHeci7yRgrIj81Qcc56UWP +4hdB7RJambPsQ0dtPl5R2R2cbNN2oigWOYu+sXAe6NE= +Z8EhB+ghYGoKD9XY4aLecICNYZH3MI+WkCe55V3zbmE92inO+KpNM/U7zaoDuAPEuoRzraAIHccyyiUiZq0KCn4SIx0pgaa5qCV5DyiUzg8= +cr04jPiHcZs6CKfXkpLJpg== +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YyVGBH6aNnzPjcK4RvfX4hfYve9n5OSl18xA0qaFTGh/FvjgivZL+gAYkkYN7wuGkE= +wgR07xfoapmx6eEnFHXXYpiq7dd9WD1xT5fQ5Vehci+ppnSgw3Uh/4UFjdGtp4C7cvhGp5bS426UifixsSPqnA== ++rSqPmR0H16Evq8k1dmRlB4xIbvaYG+O11hO82myw91Ydmx3LodFeccWDl136pyS+nAQAVxikLOkQvSOY9kJQAL4dTpsdxfBRhjl8YNkSe0= +YY+NVOVvcj0xKLmCGeWNx2BestjgssDXb997jOzwt9Aq32N7lFRvKaEk5Fl/ha6S +tqyC1MJ6XiYh+GD0a1kac5dSvZffsX+eThfLrN6YbFmpi1kaiU7RgZCp0rtM29PiZ3vWLi+ljJuWig8NiQ+4bg== +2wtz2EJ9gcVOn6ULRb1bGKZE1Zcf2OrvDEjNWK0WFBUgbaWhDaV89uXGofJoOVRr +VmVrGQo2zRokW/ZuO9bN601Odt2qBpBvUnMq3yF58HwhhNM7lVHcWsw71hZ1B8rwe2VgzE/Ygfs/ES/u0phTJg== +2wtz2EJ9gcVOn6ULRb1bGE3fWxG5qHjknC55f8ermFzus/o/QnSnnp1lNx6tgemIWgwHLhJMu5mRfNxKVC4tGUagweF1aVyTeSeOgr2pV0E= +VmVrGQo2zRokW/ZuO9bN6/3050nTbENCuAHMJP8rPTyx4Hr09t3MP1sylZ2k9TWT1Z/y9kt5XpMdcbz6I/4KRQ== +1cV64uRoDtvn/9K8xFmBBX9ZdUkOEyIMST38LHOp7fSKs2ImPRP0uyopElCHPi7HMVolZMF8O0XYdhXxBgTZgEqtCxmaQbs6wC+46PgWeNDCrmBUCzHdmv+1vOWqpreU +L0eUthVnpkGsmKFAX6d+uKJozLiMP5XT821uPNJ8RPjEdXEmGnrZWY/p8GPQZsgGWBxCipm0x1x/vOwPH25IOA== +9LvBrxm/uKypfgjBGJGYtcBuhWDF5ev4FlLJBPqtxn4= +rqA8uevZ84obkmE4+PiALpn7E9+AzYGlYfGJdMimSUSjnNUvkpCHlBESha0byLU3ObnMbt+xpHSu7mkeF29b/TGgdBW9AIpou7zfLCAKfCg= +cr04jPiHcZs6CKfXkpLJpg== +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YwJt+VY8UEHNTi++WTqHhGFcQLxNOcaxrVugGzPuGlKxDpuTXtsBiJx6HKGQdLz9CA= +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWMqK7Soh3XPs5AU7ubhT3vOqORY+HJbq5ao42T+oXB2TxMpFXIxfI/VHCXdH5U/gk8= +poFDBYdcnIoac22Rpx3wOFqmecX/dbM0cwOE9JcdOUxhOvVL/xyrkb8pvE3XuSq/AF6vTbgqAOzaclvnzUP78zNBCUHIe9viMRGZD64lExwdRQ37I52ZP24wl2h+HEYnkzG37tN6AaS9pxgDJLGXWA== +9Vq3hrd3Q3VGJ7tCv0+KbRjjCn/SrIsjP/N9ZQ31H8s= +tqyC1MJ6XiYh+GD0a1kac+C23p8m5Q8Czgkq36h7kR9597Aftw1MKkys8jKCT8dDNm0OhJLEvszQGKdpR/tUZg== +1V2v8QerKOmubvSxgB4eTBhWeQzn26oNLaz4XtiKd3DBM6qqjMAtodeg9N3wCm9Gq2yeZjXnN4HfG2ObtojCyuFuu4uJxjeY08uVocnJAWzulcdvmrB+9jE1iVskGH1AwKoc/O1U4u7wqM1+ViPfz4Nc7TcCBSHiHSfnNKMbMSgFTRrNcK1VUSeA+xqt+7Gps9biwxLJJkfYwpud7rzzLTIQYO3dgrSEgNopaKP1wHQ= +89eRayX3G8tCX3FDU5IcjcI7XW0AOTd7+zV/GdfWl4M= +VmVrGQo2zRokW/ZuO9bN69cX0oe78riTFf2DR0iSouZv8VJVkm4PXJpjAZ8dMu85ue9EvVlif7D0Hfkl6Hg6wA== +VmVrGQo2zRokW/ZuO9bN6x2UBXrziXsE1YruI8yEIdpEkt5u9hKDhDnaxZI1tKV/1MiYuJ9V5LAPFKhhY6L1DPpcIKwtbw6ZwveQkIvw4MONL6VbKw7Qe3GYzo57G9ez +VmVrGQo2zRokW/ZuO9bN63aR3YocYJrJURmHuOn6NQyL2YNwJCHOOO4dVXSO1OrOu57rWNUOaY/g9Z4DxYYIAw+FuROKSt7S7gbygqMsdJM= +VmVrGQo2zRokW/ZuO9bN63aR3YocYJrJURmHuOn6NQxt5CXBImKEYcRbajp+HfFQgcG4V6mNlUB9TfRtQuR8trry1sH16bjkYZEVMTwcB6w= +VmVrGQo2zRokW/ZuO9bN66TcS6o6l70yqYAEaLGRTwyJxvl18TfQzpu1GNA+7HjH +VmVrGQo2zRokW/ZuO9bN63uze4Lnmw99IoHHLSLuFlE= +VmVrGQo2zRokW/ZuO9bN6zhHFvchcMiaUYiV8FEKyco= +x2LOCSeSzacmh81ySUhNR9w5+XreI5pXDQTDZ2h9wlRlZm0n1nilVwIkZR412HMxDvSJoPfkBBhn4MVgLI7ors/3tML7Yd+6oLX9YHq7J8c= +TP9zhKzoqP2ZyB/hk4eVp5dwJ3AjKE3bduAuqPgvqjLT78U+Dqd5CSCjq+oD1f0ZsvntMFuytKQk/UqvAUPWuxQYcRKHE4yIrWtNgj+/1e4= +TP9zhKzoqP2ZyB/hk4eVp5dwJ3AjKE3bduAuqPgvqjJAEyYSNUdFNS6Lh4D94nUUUjoRv+eD3l2E/Q+p9vL5xMs2E2yAFDG+aCuGb5VjBn4= +UTxJ4jWO6dHQO8pTjqkurgCMD/1jMHNO7c5IGagQ/I2QGV7PCdztnRenkMZGlh9+0iLnOQuFMDxZCa1M85F7cg== +XX9hoBCxjod6pWwbTwYew9PA9JlQNG5VtJfkL0XPTA4= +L0eUthVnpkGsmKFAX6d+uOSGjw9k4+ECXaFBolnRCxXrkljYR7gvNUjTs+cnRe7RhvtQwTDG80WvhjGs0l3aVw== +GrDu9Oao7I2P1akJgF09eDtLRscASUn24HyLWqaEGOoLFd3Pyl1FDaBsa3nmHXJlV6TKQFpWc3mjwlMasU2QuQ== +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWMqK7Soh3XPs5AU7ubhT3vOqORY+HJbq5ao42T+oXB2T+dPZSHoCsu8Rvh//+BFVlqkbhy5clkrm5xd5xWnatOtX/i00VnO9Du8rLpc5vQP+Q== +KQe2cpNa8FkcC9SS/NMi8BFvFUJwV+XI/wdURlfBJWAKHDHHyrOU8iCWTLbvkh9K +rqA8uevZ84obkmE4+PiALkqLDY5CT7nUo6WhuYTmudfvPnqJdzDNw7liPPnKRWgCH5aS6+1HbVYGvtufo0BSXKqCmMResFIg8Yd1Urarz6E= +cr04jPiHcZs6CKfXkpLJpg== +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YxTcmv/VfxPFlecQ2Xnotqt2IE52L3gC7dCe4SN49+Z4J0rlz5DNFH0zxI1SMhseJ0= +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWNJQB7DVYymMOWmjTgS4eEyfy+l4kioIRjPlfjna0D49HQWKrqNczuGOV2UnBCSpJk= +npCRXd+WLSmlk7JiNtxtm9gbYh/Jik1FIDXG9IEelYlUIDsISM+xNL1kwJmj6EVRBZZmSfp4awYJxDtGhLbmSnyPSsssdTNdjVh8miFF4Jn/qor6xUfnuQIA6ZtKmpvNSOx1PBvgevr8PiuUrmOT+w== +6r4ECPT4o7OwHqIcEmFYzlEbZOlulJHLZOuh0/zLs3M= +XmWcjbejqXjijvSDMMApGon2xDjBspqtUFw+zce5TQt7ub7089lB2nVxtLvJitZEjjV+Ih920BeA+wbO6kaybA== +8qTxQznEcsQMgFW26Dqik8Cp05AEJsEUSbVdG+IW7IfjrtNpbPkvFbMSrg7fTklWMTS1MlUC5TsFldxvdJejTw== +89eRayX3G8tCX3FDU5IcjYVpWBUTJe+6dnMemLr9pO4= +VmVrGQo2zRokW/ZuO9bN6w6POkpptsbLbDcwAs1qGzyVhhDiOEGExsweZmFRMA6uit7+z8HqafOBm+f1YAnPFg== +VmVrGQo2zRokW/ZuO9bN63pkTmxnYm4QxVHRndiusm21eM9HuqziUJfSxSPrf2PAsXwHDxb8ftSlHSZHnsh24GzGc87Dpn2aHIxSb/ojqvUGBW4iJF9d9xK+17b+BpD5Rw+OA6q5DJ7d0o4S/u9IHjFW8HcRAUi/mlM43BsUCJh/jk3jWn71snZbWPixdQAZzjNUireEtL4ye5p5yYA8qA== +VmVrGQo2zRokW/ZuO9bN6xatn4pQf6Qylq3DHGXCyT1/gtrxi1hEFizOSLgOoO67H2dMVxci0va3+/BS4a24dQs62DYcvihjvbPqFll4EGU= +VmVrGQo2zRokW/ZuO9bN65da9+cSQosYOCAUllk+pAhHrZNpey+rOYfxZixSyiq9e/vKx2xioCH8T35uXU2iNeWRXpdbAL8h4Lkvzght0U/Ts2rWOX49CLOXyoRKCpwcy8TuX5rPw+VOr42ds1YAPLFlCCg21RSbk7iu+fTC50sk182IVPHmk1sBhgAs9M+bs1klsfK7JJlGrew/cX8A5sVVCSyoYIFcZI1fqs8NZdj8zjoyXLLEDzm6KRkc5qrvSI29znlAPdb0TUzeex0G1Q== +VmVrGQo2zRokW/ZuO9bN65mTvxewxktldzaTIh0ruseAf79XDFvSH+mS/WNQRDu58Jt2SHiNf3FiIIVL9XD6qksS8Pi50Znk2FgPbjr91GE= +VmVrGQo2zRokW/ZuO9bN67BmtbRZu7DbWxJyGfNqdp2DE5GklxTMrqrBNoUflNgJ +sobCGpmMf4/g7+HpPqBjC6JAoG0FhLZpTt8WF4LoCdc= +VmVrGQo2zRokW/ZuO9bN6ycgigIJ0vi/BKT5iDenSw2GswKcw3nZbhDFVP46d9mwIKxz8tsOD+PKOJ6VxbkS+Doa2QcmIKejv2R92AsreFRo1/svGGmZidNFI7nhWcXh +VmVrGQo2zRokW/ZuO9bN6+wwJYTJogYbw0PvRmy4lbbxRYV2gypJbeoLmseomGGpvhP1RsTU2N4c3EIRExz7t0+QX7ho+8KK9sCVUVaQujq9zlh3iv0vxDOxrHxVoWL0vPQy6OANUn3YB+xIhGV3YCx+4Xg4wSM30SDGMDNrxRAq7ROxx1v4Nz9VcDQJnf3l +VmVrGQo2zRokW/ZuO9bN68W7kg6GMlDfsyj9oqABfICnFRJY4e3Ft7u0FDEeTLb+Tttpv2vgL2k8tL9C6IQypvRiZXCeHsld0iOwrv5oWUk= +XmWcjbejqXjijvSDMMApGqg66JQONXEM/znZP+hhHbvJqwnVLtBpSTFW1C8zjERP +f9qliXFiuzgVqdx2xlu28laTsVt0v0AcLDLGFGh4ei4= +L0eUthVnpkGsmKFAX6d+uOSGjw9k4+ECXaFBolnRCxVXWqXRfRFrlYkhQXdVympE +GrDu9Oao7I2P1akJgF09eIPmKz1Z36SfVzbX5EtIVBSJLXuLhRL9bU8hNSIy2ELM7gHJn+xHvxDr0YymbaxYQw== +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWNJQB7DVYymMOWmjTgS4eEyfy+l4kioIRjPlfjna0D49BiYt9QMwyQ4CP42XHTRd2Eae4u3klBtdxzhjxB2tp/fpTXxrSMhqObBE8An2UfNyw== +KQe2cpNa8FkcC9SS/NMi8I+ywyBJxxH3fLZbImuOz741MPpebUEOYQ++4urBwfNp +v88P/mqO0QCsEtt5FQxQSW16A/YQqwPQpq1pkQgukNC1qdmOmTbKBeiUamM5fkCyU04u6JgeNbqszxMDnR3gWGQltPPE/JiBntG4yiEJOhFE81/yTcpkQzySU1/dU0WkEn1B6J44c2f3ZUeAWZFpMwGnZPjHe4mEty4JAddSTnY= +cr04jPiHcZs6CKfXkpLJpg== +4ILYwbt7Vw8dM6uHbkq0vJKFyJOT6EmZCVi5TPscIMcKGdQWeHEhTG56vR6HfHecSYcaTVmYfDlfVruxO4vUILrx4YEZmPUd2JfQRcEJJDSvMDclu/6DXDm3D0QaCRqiqvrLHQOib2Vy31zzzYp3RQ4H8P1U8KU0rqQvuCGHL2xnlpGCfIwSNh8v/VjP8cnigUeJ+QAptPmy2DVB/2BwNPs8k2ztWHWvl9bc+7p1e7Q= +4IH4b6ywJwYs95/tFiL8lfnVM8cGC/TcQN6wOHKJkru7qKTJqyg+NRdMZZjqWBn3szh6PMq7v8cN3fvQg3xbZuKkD7c/LeXguV4QIOtOzQmInXNic0zqAbqxWrq8yun8i/kQI6c1s/Em6ebGCLvt3rsKACzVCUV/nEuuE364wWwIqGjKqWfzxiUAA73uNvIn +4ILYwbt7Vw8dM6uHbkq0vJKFyJOT6EmZCVi5TPscIMcKGdQWeHEhTG56vR6HfHec6/zuyKD4TNiS7umIUQ1eh4mx53BbRpEGDorZQsRxUIdr/Yovh7zG9leoCSODHOex0/be1Sr+cfB5VTJKpjqus0DNc2HZNsgg0ccmxlW6HR6l5lCNTGQD1izZ3oo6Vc3Zvd0NHBGxCrhCEGjroOZL/qmPNTCSc79RhJ/im8x06v42AlgXJtJmMDVKBFY2Ki9kxzkecSlRQld/40PT4PjR5E3/NtH1pOL/to5r7Lfk+/hRui6SIs2FvUkBA6/kIiO4UP5/F0C8bD8DS+HXSEi1JuUaz9t3t6e44P8Q70xu+QOk840HWTXQU9mxACc5PsyDVQ4GOY0Ph0hF72l/O9pPph7Da737jtPgBihJKejdB9cyNG1scb2M0BPnYqR55Hpp +4IH4b6ywJwYs95/tFiL8lfnVM8cGC/TcQN6wOHKJkru7qKTJqyg+NRdMZZjqWBn3szh6PMq7v8cN3fvQg3xbZuKkD7c/LeXguV4QIOtOzQmInXNic0zqAbqxWrq8yun8i/kQI6c1s/Em6ebGCLvt3rsKACzVCUV/nEuuE364wWwIqGjKqWfzxiUAA73uNvIn +GCWEveFNOjCMyiiJnvM3cj/vlLOzJEJeXu4Wc4MzTT9m/lr3kKqDifw8VnjgCbmzEVvoSNnOlUy5clRfnyQg1A== +guSZID0bFQuDFoWO2uxAJsjFYYfXNGsMOQ7vVBnR2Zc= +Z8EhB+ghYGoKD9XY4aLecBk1+N/K2p7q6loOj5hO/AY/OY2IChkLDqVzJEzXKrKk5Dhqc8kgf7n8kn3bYhf6mSyxjI41IXmkSnhG277VRrK9M6jwUqP0hzg+3dxRvJy/5bLZrUrlKLLAqdOB1DfhD8hInusaFJKOPu0wf70s1wc= +cr04jPiHcZs6CKfXkpLJpg== +tf1Oq1wYbINEkyucXtee1cMgJMhHf1FsUsP2LRuuwug1o39iAStOUDwI0RHQuoo1DY6kPK9KpPHWiGmnoKUpnaFv5CqtlCnc9iB52wcO11EZ+gBlPecinoTKWp2vNyQXL+jk+xFCOP33oQlYIz0BugeM/Xs8WmaxUp+YAPU/27ZVp5rbBob67xkA4H0ahEQtvyWXuaWjvb6vC+TD1+mEEZfT4zwsKh1vwbvDluhn5ng= +wyvncB/3/67oBT3vhbMVkleD3cwd9I6ljdxbr1LJWaBalpGbxaXxP/vvYmQVlTq6AXqr+pB5OJA9v+pwNTkRIUjhIPP3l7NehRhk/YXgDF5I6D1WciLFei3jd03xok7qhRg/Cr5NRwF3IWBai3fmz9CCYlyMGnypU8zhPd57O3J+PYMqQcD4Zcup/TfAGEaN0P5evWJFcQLIOV9zlKJ7QQ== +tf1Oq1wYbINEkyucXtee1cMgJMhHf1FsUsP2LRuuwug1o39iAStOUDwI0RHQuoo11dIkX7kqHzlkFxHSyD6tnXs+xhWWWFAgjgE6iZVyEvVxS3hDQ1kZ7mZO69wsx7qXMiK++axVuM7kDei6Uc8fdG2Ij3V95ndg3NGhF5c8Ve+M43nLzx5EEadj3AIL988up2jz6IXskD13juE3HHLxstJO7++UKrO3Fph0H0YgADU= +wyvncB/3/67oBT3vhbMVkleD3cwd9I6ljdxbr1LJWaBalpGbxaXxP/vvYmQVlTq6AXqr+pB5OJA9v+pwNTkRIUjhIPP3l7NehRhk/YXgDF5I6D1WciLFei3jd03xok7qhRg/Cr5NRwF3IWBai3fmz9CCYlyMGnypU8zhPd57O3J+PYMqQcD4Zcup/TfAGEaN0P5evWJFcQLIOV9zlKJ7QQ== +BsAbgDLrhQj2uYn3yuDnJfTmJeo1JkS+iXr7NiMr2ZGVgoDFcakJ59CcFxnHHC81E4rjC26F25zO4kie0jx4Nw== +guSZID0bFQuDFoWO2uxAJsjFYYfXNGsMOQ7vVBnR2Zc= +QWxBDaw3VAaL036sX4hCHMARERUPqvMRF5uWXT0klt4M8uDrT3wrTUNGrzlsT3SHynX/h4n6qICxWvRyD6/zjg== +cr04jPiHcZs6CKfXkpLJpg== +CjeKMqexO85OxFkNTLsfVe7SITCOKaZp1LGMwqVNe4A= +R65WfMhum3uW9cFR5COFLNNgF+xCIveKPUqFxBGX9W8= +wbI88h5UWpmLfMPZm1GHGsVMKwcuDJu56WFkvk/fR2TtwNrgHZQRoxBog1rgNcsJMOWUeXIf2/8hfo8KTaG/XQ== +JGZY1C2+nS9wZYWgd+gvh082R8I97omw8iSHX8Km7LmoB4CSGmPMOwkUtKpjByEk5vVvPtxTZbu3mWhXtEeW6uJ1VBaEFl9c7YBx5ZTGNMA= +SY9CIZHY29IN9pslG93gZg== +pVMCrZ7PAAHymZ70WROm/xN7uc37HHCphJNP6wBWNFFkfCrTO3/zWdRXL0JcM+29WG0o7tsUYtBK+YT4qlbtoHBEcSL+oYQx8umm33tHtvxlOnew1yGrW+9tUb/3mIzCXV+bwMsE5oXss2lYpTO3GNQd55Kh2tKBwIfi983jfE5WfGpdHOOq3NWmcvgx1IQ9 +L0eUthVnpkGsmKFAX6d+uM37yNPNIFu5Sy9mvfTDYR40t895iJsTBk2DH1n3Un8e +TTmoxB5HuP9wmwf8x1QYvrn+3uJVMIWBGgMruE5M1BU= +QWxBDaw3VAaL036sX4hCHMx8zffAVdfqNc5+di1I5xBdsKxDZKivqDomKrgNbiQV42S8BdMCjk5yRAW6CwZB8g== +cr04jPiHcZs6CKfXkpLJpg== +fnnuZD0QAdxvV2PveMWL8FOZqBJQkO5fkDyErcdv3sHBCTMsJBafZRCNzddg0fI312AFmwseKma+xsssgq5CNQAjmSvAJCGuc0eXbxvMQOdAMf1OlFFfed4k7hRAM3At/KtVPJ5SelXm8whoB/FEOXQ3qFgH/HpWgizR1mjK/e6ou5uOv4f39VcQKtXRn2Jd +3eIpXgbBonaoheCPx8AFY6umFI8slLWq/upaa1Uk2jpSVyMvPJ4mcBuZLgraO+q9GQedAucc4xrSwIqyV/B6gCTFpRYHtmfOoqOLaWkwYvc= +4/4+Wm3Rsh2mkp+PCxs2s6VV4mJNPEuvR7w91QHZ557aHaU/Cb+THTNE07rfYLXxrdZDRZPikp+SWMJYE4H3Ig== +dID/HuTFI39RGoF22OwzKyXop25iN/6Cgmas1ryG/r0= +VmVrGQo2zRokW/ZuO9bN6yIQVC/ANBR72Zf+NN0mt0yltbzDkWCCV7TF+F4d/yUWRi3IdQCvPfYZK+rRnnypTQ== +VmVrGQo2zRokW/ZuO9bN60bK2UgxkvkWzml09hTN25JfpBLx03bYbZyT1mbcJLoM9IlxgO6bebtiUCLu238r87hHo/BkL3I8dZPhAAdHYzQ= +VmVrGQo2zRokW/ZuO9bN69XWNCdW2VwaznplNhiELZFhveDssDscfLsEq3UYW8Iw4usKj8FTxO0dSaukGj8cnv8+vtmaKEgZ0bN/GMY3GQDbfxr0gYg6NdbH2kpceCeziSX72rUP2GIAP/ebCA3AjE8/vyUHOlZUHuT08r4QEqVOFV6VX0kqtotk/D+bdiQoW4FGuVaT65K0qS+w4pQd/A== +VmVrGQo2zRokW/ZuO9bN61k54EIrXJ5YDEJYX7KVSMQJb2yB2/tKePc0AJNb7scfmBx3ZMDkULLl85vjDrKPDsRbYCCYbmQ4JM/pYb5gQtg= +VmVrGQo2zRokW/ZuO9bN6+4h+8nsMn2hxb5D0Q6u6aGCqIPaFsLj6fC1TahQFgsNH4kuSQ+lduIVprP5UXJaS/58J7CKl19/ZhwZDgV8O6QU8iHj73adX3xjmEJZkf6Qrfvc431ZzHai1xsHjSFJgmjI+xwBn+vfmSyEbXYaPyIavlSyj91iOeD8FqpN4irSOGSrBibcPvxEkbNEBWdfzw== +VmVrGQo2zRokW/ZuO9bN68wHZtGYQdFVE6PwKTgvaNtYzBFUcYX3fyY/6M9/dXzj +VmVrGQo2zRokW/ZuO9bN60GIVQb1dVeSTDjJFx4iZ8K1YL/MjcMgl0IDPsbhoWcM7yQ9EOiw907XJNWeGVgujqehYoLtC9EudSNuKe99+CWrrATAnvnn66j4nlkv3EcPJUeAgTtZo77f5zqZOUsiGesKkQIGJJ6zd/3K7c2P48s= +VmVrGQo2zRokW/ZuO9bN6/o4zScwQoTUi+tr1czRySSTBNwD3hHM9vC0XVvu0iF/uL9dBtNk/w6BB5ctKdmT5CcwaDY3ncMWkTQB13YuBkvaC3/J+X6jTdOr7PDuX2PSynf0+gGLE/QAUz3VFC0aHU1OhnUAoA9NI005K47zs9vF8K4GOfiRNWxWRaEo61bK +VmVrGQo2zRokW/ZuO9bN6y0EoUp3iWRH9OmX39lPFAedKJOagVu1hc7h3Pe4GIyhE4KZU3Jx0OYEExfBCrAKqagP9yem43/r6C4T8+tDACodjJey9cFkvf//RgViLJEY +VmVrGQo2zRokW/ZuO9bN696Qz5c9i0uv9aeGTx01h1hC3aUrUxbV7GyHYuGQp68o9ge64GJUGHt5FQuweqvNs7qVTiNgWwYE10vLvinIRsMYIM/myPIdDv/AqzuwHVzOOae1yPP1GqFmx4OoiOna/EGkKRt3Z8kNd3k4/rs4wwzo61sa5wcv2NX4XAwqnj1CEvFdWPd/bPz5yy3bIgVjgSZPiSvSqJXgxQGFENnWTi4= +VmVrGQo2zRokW/ZuO9bN67tgFk4kAxPV2NXuFIabsssnHcnxv5PUS7w7nqdNDlZmad2xlF197WUG6enWS1ddeO2lu2wARsOMM3olmtoDpSE= +VmVrGQo2zRokW/ZuO9bN60bK2UgxkvkWzml09hTN25JfpBLx03bYbZyT1mbcJLoM9IlxgO6bebtiUCLu238r87hHo/BkL3I8dZPhAAdHYzQ= +VmVrGQo2zRokW/ZuO9bN68mt+2TzTj2DszDWHbL/Vx+mrPtVnbW+SD1pBk7cc7rsaJwQNo4QU7LoHvOicKqoaUNdoG5e+AzVGaXRW1n1Nqg= +VmVrGQo2zRokW/ZuO9bN67wpbXh8GxRtxxUdyv9+tcnQYOPzlH126m/rftVMv5cRl/hQkptryk/t8Gtg8ajJsb8lwpAI2nvgWtGpl8TpQ+K9nHJFRXKol1tZrc9/ipu8kaOocsRDzkPn1olW6CDnbALkALXUpIrfjvDKrygdIdVqX4xoNpvqmFtSkD/wGtSGVW8x0++ZkFMLlzwBleSXIofOOFMqbdm8kP/g/2Q9cnM= +VmVrGQo2zRokW/ZuO9bN61k54EIrXJ5YDEJYX7KVSMQJb2yB2/tKePc0AJNb7scfmBx3ZMDkULLl85vjDrKPDsRbYCCYbmQ4JM/pYb5gQtg= +VmVrGQo2zRokW/ZuO9bN6+4h+8nsMn2hxb5D0Q6u6aGCqIPaFsLj6fC1TahQFgsNc8McXaMNkcQsuyQNX9sTxBDIVJELewreELhbA7ExKAhYHdGr0vUfJ656qPdz61pEMCuI1svU236YOtcqeeWw9hqHZiYtOwsjiw0R7ymDlNoj5gwm+V1FBszZXgoQ/hN6BxqCiqLFSweS/VzkreCfWAHbQNqrDsEDcEKlGn64ivJUmuj9zyfGuDZOIHE/SGBUK2SlyF9yV95YTadwSATEnw== +VmVrGQo2zRokW/ZuO9bN6/AO4eShpSZK4uRSltMUWur6UmoAJ3uYmBh6Hb4u8+QZuLnJtd07ycHQVITOJweo3B01crh9ZOyXk+RD4zH9h3dxaFHiZPGzCPB05djMX/pZ +VmVrGQo2zRokW/ZuO9bN6zhHFvchcMiaUYiV8FEKyco= +VmVrGQo2zRokW/ZuO9bN6yIQVC/ANBR72Zf+NN0mt0w5vOuC994hsp0d+uCS6/55EZLchuUQTeEJwheE06nZPQ== +VmVrGQo2zRokW/ZuO9bN6z1vYAYiDcZJ2goOT3IWOLIPUh30Y1JucFlnMHb1teNZ16yXgRYuOnoJWnC9DJL+4Zj/1ue9Mx4yCXqgEarMjmo= +VmVrGQo2zRokW/ZuO9bN69XWNCdW2VwaznplNhiELZEzh9jk6I0q3rmZW4u4dtEB5X/k82/6F0+ZL0tohkmBOkIUMP4Pr0PSa2rrEIkJpyeCRFXjMs6Jo7fo5NVOVuWlM5YByI4VbPYwOI9kC8H+zNIdR0QyalN2tczBM2yXnr+zlXuJh0AhmzcDReqsIiJHe8wIa9fruyQvytkiCOU0rw== +VmVrGQo2zRokW/ZuO9bN60CwpxhzHd5Tsu3VuWdEIGbC5Eco8WcHvSLIMGZrC4J8HM7Mhe6VWR6L/Sg/mo+5Ug== +VmVrGQo2zRokW/ZuO9bN60+OYD9Lp6v7rDnBJufgyu0dtNrYkL8H7zhFQ+vQrlqhvCE6NtoBH6ioyORLFJjNRF9SGfkFntthQsprIkcvCtCK/JhjIZowEZYnAX2rXG2bsXd3yGimQl5GilD4VC9gNoyJDrUbIX84KmcwTFd8/LRErn07610YcZNUf1UtMXy9vd3qDn0Q/AVxLd1KCmzh4jDf+6iTl3HBqq6d9N7CJeNtaOYmfKxXO3OYOtgspRP3 +VmVrGQo2zRokW/ZuO9bN66jTdv1hTu+4fFiv1YBwZue7lv6Aufd7luwy7B+0aC0dbzx6VRSrf3fzINUzlq8XMNkGKWBWvxEGkmN/9UAOa6dKPNyUHrHmxW5rH1tExZwQ +M0ZySqkmhuHCw6olbCKv99p0Om1sGOCLOPcvWBGiKm4= +VmVrGQo2zRokW/ZuO9bN6/IJQworJPCLdiBBDQz9p5RwSTJBcPPTw2MHkE7h2GTjhvnVFr+q/CK5r4h+EYXiyQ== +iwvjn39WsWBJqN2zZdgrYJ/brbUvLQT8xFC2K/CMfMpSLWjSyTEJEwqWm0w226ndjFtOnyhoGwu9FfNq6tbV24x74jC3NVVQJ/zXWM50PNKF5ZjzeY6CK05/XMakHiEDBjCNUSVpX7NFuVqFBQoXFQ== +dKwZPlLLroeAXoKoRvFRMPkQmp49Bc6BILmWMD0egRbJufQJJVHeUyhmpinBOzpu7lZkSTQPR3sLPWAb1XrOdBks43Ja3veKdkPBsYEtP+4= +cr04jPiHcZs6CKfXkpLJpg== +fnnuZD0QAdxvV2PveMWL8GG3YPzBkIz9Ea9idqA0hv7KzY3K3PuUh9EVgCFxsnuQ948EJfpPabGHe6RQ1WLXqx3Sw1oJIpfRNJqpmc8NXbiDy0sb/I/Fx6BFCgQNeSfWkMOO46FZGiOPxWAfogF5t95B5JeJd/sxMl1fwroGSOQGzaf7YZCSCW9ezDLi1u3T +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YxTYMkXt8S0Nah7w8r9TF300puk+OnsqWzfPRMs3Mbi7zOrp3cqhM0qeqORamoAJBM= +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWNGuMooTRErT71B83/M8nD4aRWyyME7sQDlOs9XPKbRnkn/lNp5zeuNE5AF5qUcgYw= +XmWcjbejqXjijvSDMMApGpsWw+e7nEJCTIvfvDqrkKXboOR4vzQVwubyAa+M5U2ai7wdT1soKMjQ5oH6v9r1qQ== +7iJfWS/hi94/vke2djffqmjBjwDgzRyWSoEw1Q3z3GT4V7Je3CZ1mm5s206h0RWJN8GlLviN43HL/e8mdYfKB8xyWTX906NaD7r9udlkATM= +WTPaI3KC72IatMBY2cUf8DhINsfbwu2z6BUbAhOsimUwCOTh2mByfFmolL6LGlAzznx9YHBnysnjX5w3K0Aa4lU42GWBOTkXRGOsJGciABerQH0fIN3Cc0Ha2WQAmXrtVkuiFi7WSacqVGM92Wz0RA== +yfhcGVf2iYc+Ztxqwy9MUGl2MlVdixWiPkpdkDIaDTR2fL2bGJ0eG/T3S3mFK3I1yjUKdBI/7/TjB+C0Q1TF7A== +2wtz2EJ9gcVOn6ULRb1bGHs/k+FAu74ujznevW9tehQxxh+/BQiuLboLlLsvhekSf7bU2kLTTL5K33sDNiX0trhbG01wTa4j5HspvJM+CjY= +VmVrGQo2zRokW/ZuO9bN62zAi72PGE+cjoOBruJcs80PAY9N3iH7ke6TUqsi1sSy8D9ganeo6V2zi4SBqW2bdOnBzTgDx5DTrEDZzIkiR6xafY1QNC1k21Wx2o1UPd5i +WTPaI3KC72IatMBY2cUf8GcB7Gs9lcHGWbUjG0C9vSs70Ygjj7wUoEbxnNP2i9zrzgxlxVIkk0U+7ydFZHSoEzXRRuiyI8zt4O/5cZ2lMz2J5pBt8vqrB5dxECjlX6ZE0SioSpP6KutnXcCf1ehJKTCWzRi2xhcBQ0FshyUTLQg= +iwvjn39WsWBJqN2zZdgrYIdzLsQ825kgPBf5VIBdj3uoMjS29MAx3RxOuWvlnXiosQ7KPIpTLW0MUcNlsu/ulyBkoOXWnXGiVxFPDjplOPmOZH25r/PenV+QVxeQxMCQfVpOT+W5VTimeAhxNV8PQw== +iwvjn39WsWBJqN2zZdgrYJapEN/AKnY2zLbcAIzyrBBU1RXGfgWqmb+6/1Y8honwoq8vQFNWL5QXG2xnJPe0LBPl/ycfTdBL8st+Ep1mgsYzpmdsWR6INN9Bz4yFZESMs7wbMHG0SzC3fg0QgFp086x0icWfwhosFLdQWu1ZDy/D1nu3zd6GRjYEiiID4hWyqMGLVMlVhlgucIyj8rJvGGu4F9qYL8XJEbUPtev2y4Q= +j950dDFgNocMT/8FQS6TYJHV9Kyoac68bruP5hOu5qmaOOkbdmWW75ouQPYNKTGSSa2ykeEdUHfzKTkJdx/Ccg== +96orka/uERLyRst14azQwifX4NgxGrWxr0Kq7A5Rg2HnATx3FBLn85SCdMGIOm1E0hlma+GxHkgZc93eWmwVjA== +bqFhodE9GHP09Cm0jqHUfmkdtpV9kuGe/MGw5ZaaoCp81F/rxUszv1KfXRZWtOlE3la7p6gT6SCyEt8mcZzS2A== +q4nc/jwATOMUyfSjLibfEbtbU6LFt5q4ewa9MuGiJMo= +jsE17/UlKDH9XrhQJJowi15/y5gn5lH8c7Gqcnf3Esr7fxvcWtKirtM5TT2hHcoLOOiNTfJ5a7izuUZ8eDTQRw== +jsE17/UlKDH9XrhQJJowi9yxaM1QuCYxVef9FbIoMZ+eKsxoKPzwQ/wwh+qx24Hi +AaCYXSBoNzzxOXBdbJWxCPghQQ+zxvQLtEy2BM3tYqHuDq6yKijsnOS5h2YQlWHI00dhyYcLovqVLkdh0Dy8fMQOez3M0K6+uE8weXP9Ap5DYQqbnNlIhatzkP9KpuAj +jceVTICIbdeQFZqTJKGutSjbVQ97l0vRfz6aCyyExd8BAD7ORBXAStcGQntGkH9VeVHgeQxgUAn9i5xQBjtmtg== +w07xSMqKvzErqU8Z4gNDycTDkv3aRFmhNSvV3Fxu24BqPo3ptCHykzMf9nG/CGMxQaiucRiWiW3Flz8o49yWP5RB3d68MNFb5mogSyWvDQA= +cr04jPiHcZs6CKfXkpLJpg== +fnnuZD0QAdxvV2PveMWL8CSqz+/2LSHUFrClcf5BdQrTv1c0XJfhDdpDKKPo0z0b14+xocsVY8vCZj/69cLwfoiB1FEWbmyYbrlCddxFGEsCX5QCEQvSd95BTrhq1nlZCNY9ga7YYTrfSnw+46RbFP+ovv8K0iZCj+u7EFMHJ/IMXwLlnh3z3jdyNDu7TFNo +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YxDlh4Xxnj94+dsoExgikc543ZsABYd7Ls6YBhmFxb45wha5ZOThRb9RHRh4ve9w0I= +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWO/PpcmZKW2bAAbmrDnw1SCP4uaE0ZYA6R3BnA+nWDqAy1mL3cUFeM8frCNXgKcWp0= +ncUn2TP8ZQ4fZMBYk+CUrbejaOCU8cNhJKyCBZWCqq1sY02wl0mD872qKy8RvJgnAbv7KCxyej8W9Q+U+qS+nMiBgdyd4po5Df4qqDwKnLOe/UUDOA1H47u9gqHf8WHl6B1PCla277lpLOkFu7GkR/mj98MnPUXQEt54mzwtDe71lGzby4KJ9Ma/n9OkWQnE +A4iM5LeR0j/aNdRd2ACS4ny8WZLXU05tQMpWxiCT9JuuAqjT43MG9lWrq5R0d6qpJwthsL4nn9qcoduCQeoOlw== +5REqqEJCYV1z7x5neHLGFDGAviuWIaE/TYt4LSZwDiMXm/53mOKPZnbzemXU4+GZEUcMNcyvWJGIOUQ5RIkIIpC70r0TF2jbHjMwqOoUldc= +3DnhOSKd6qzCIAY6ANKkgJYd0P/22B0l1MZyem52ut9bERvo0PX/T68xhBrjCbLsd45eA+op38niJOuRPomiESt7gvsqdzn5rexbsh2MuoA0EOCYOneAtVYTW8225GFO+VCWMzhTlT1SXl7twpHYnQ== +R6vHhJwn2j7IZ8/oBXXPlT1VLHYksWD6DKGcKNO8kyDSYJaiXzcsBEJQzsggxUu3 +yfhcGVf2iYc+Ztxqwy9MUFkJFa9YS88pkqSw34PcNf4lRXLRKL7BLErDKD8oJEUa8Is5OQTKCIYC80QzJUv+ow== +2wtz2EJ9gcVOn6ULRb1bGC0c4KGSGGqyU0mFPA7k9/zMuX9TZw/BP1yH6IjAzrb0UW4BCVFU5p42K5W86y/mOUKYixfq2yNN6gEZO27U3aI= +VmVrGQo2zRokW/ZuO9bN62alG5rsG3QEZzQ33/QBje3tmhSRMsf9NUXv9udZovEkoqejbYgifjLzt4+lAxk98Q== +VmVrGQo2zRokW/ZuO9bN61IpWQW8LyV5vGxMqJtLAMG4GidKSYVzYqLlwKnkYGh3 +VmVrGQo2zRokW/ZuO9bN66B2cWRLetUcWMU+aw+SZbQ= +1cV64uRoDtvn/9K8xFmBBdHLi7PMpkqFYqY7BMozZdA= +xWoGNWjKGPfI4gq8aHoTfPboh14laZFSUPEgtjSKUWO/PpcmZKW2bAAbmrDnw1SCP4uaE0ZYA6R3BnA+nWDqA8JKb1hHUBhmO1q9RpqWkO65x899CtNFF6+cegZlLYKatosIHzYd1sSurD0D440aNw== +xWoGNWjKGPfI4gq8aHoTfPSftyw23aWBm0SalP6bSZ5uFgWb+TdmXVXjEBOfs4SIpa/FkCphz9Tm5ZgfbL0jqHLzP164gR1SD2C8+k5o8kpAwYIJv84ufmqQyFKhd1DZCis68M8tRgaB6ZXGCaNrCkbBbv1rEFnJ3jwN4L9DvSeA0NBccCpd8oVxmdplQ24eWXMmssJ+SewTSv3wCcPzXui4VvS88RJY8GZHoEgTtE4= +UTxJ4jWO6dHQO8pTjqkurrjGYhp2vBYcOlWBI2li3lQTi9a9ylfr2bPqUUBeRL9Lp7KgysTqDJQBOFjwXnAT7A== +L0eUthVnpkGsmKFAX6d+uG2V4ODQQv2yL6nL1etYQzYzTVLBKVfwwGviW9wMWGbPvgdbWuIi8fuBLcL8k+2CkM8xJKi/40nY+5NrgF6Um8o= +KQe2cpNa8FkcC9SS/NMi8CwZjefuYFdgIVltRQ6d/OEFpkMvPQIEkY8jmyRMDoerHhdkzdV+Z4+gMSYNTzKt6xu+JQBCWWXi1l89v4epCfY= ++oS0XSKoqGXJre3Q/1v1BlvSlGCveCkN/Z8cmI+VqTrpmsmHgDb9KkDKC6ZVLS20UIZPb95vU7cgxrlJ21Bc2ar9RFcrYmzQRZ8SjSyJJ84= +cr04jPiHcZs6CKfXkpLJpg== +nCZEvlHDeEU72IAmy9hXaHLXXyBaHBxe+RST4mPX/1s= +JsM1B8QNr50RvpRbP1YnPuFx5C4ai7Wud51VQmeOJhtTbi2K1dw95XnWrJpGmome1iYmoZa6Oc4Q6EBuZoNV+fX+bUk82IAFMAM49zKO3cBTybz8r0H5oeeZD2PG2vz9YvwAvdCggT5OaK2dzb9qtkyl9EQWU/5TuiTRRLK9wPBcfCiv8BBTrzpbXxUyEJkAlKS7V+HzybJN3Z1X6AT+PKpq6HL9uxam8gsZOZHM2/HTQ3g4puzMy372Km4u6jdZortgp3ZYADH65j98Z05y5Q== +fnnuZD0QAdxvV2PveMWL8ChOX9d1DU+esVC4Z+TLQn0LGt4/lA9B1nK95LwQUMwDyBIvMUEQynEPvkKTsUPmBA== +tqyC1MJ6XiYh+GD0a1kaczblP9d+7MbugTMefIcU5knRIIUxS/2Wcv7v0aIZ+2c9FzydBTSgZglAaMKN7kCI/+weTX/7RdLptz4gfoDeOVA= +2wtz2EJ9gcVOn6ULRb1bGJbVRPKXqKnqQ6mDrr8yTxwFA9NUMvAWHGQpE/FJKpCSlbP5Zg80d4pY2gBPsk7i8ozLuZO1ozYdjzrgJw2U5OUEKIiU1L82Uq2rBkRVdg0FCh+0rlh1SAarvhCyIPm4eg== +KQe2cpNa8FkcC9SS/NMi8MCNQ/S9KLk1s7xLXNfFHdSzUR+lpVwNBVZYrPMpnyol +oV6EeCQGmWRpCjav7p0xHU9dTzjHsDMOBcmP4S9Cd0Sm/BfUaDvQq6+oeGDChPW7TipeycZUvdKZKFIi70R6ce9xK8Y1ET3qsvaYpUbml/t0t7fn8Z81Ufs3uMSqjXwn +cr04jPiHcZs6CKfXkpLJpg== +6r4ECPT4o7OwHqIcEmFYzuoVsBFhqfPJAaTURpnU6VwScF3eicqvf7HjZ11RI4NP +fnnuZD0QAdxvV2PveMWL8AuKdHixQT66Gc00Gg1vJS6+GO0xBdztvdUhCI5Ge5N555Yujja5lRVAwH+nvKDo0g== +6r4ECPT4o7OwHqIcEmFYzheDtvHlqty/0KdCgKl0ylz/1KEPPjnNCy+Qa3YCQV4ej0URjhxYyA78RnBZQa+b7w== +KkG36smI6M1sDeGTVVD+qSP00dlWGnbBakrMMitnwAHUW1Dpt7HTXNcTRJlaQP8kip102wRtA7YBJRxVLYKDJU8D8qE4aeWEhK4dD0Pl6fVL3wtOaxsjGuUlU3uOMl80q62tcnhnh6ZpLjlBbduGpw== +4IH4b6ywJwYs95/tFiL8lY1GyBaN9rJuTeVxWOfmJ6U= +2wtz2EJ9gcVOn6ULRb1bGM8IQ2dJE0FhTsv41jhWplnZWgQQDp3w/UtYWI2rgs6eeyYGfdf/TyqbhqX4p71ooQ== +VmVrGQo2zRokW/ZuO9bN64SEuusKjyuHabj781H+tb1pbX8IQ6fviKyAJ4bIwo6f1MEOhr9OA2EPLA0laTC+bQ== +KQe2cpNa8FkcC9SS/NMi8EFKVhLSE9F1GFAOmsqhoz0nSuR5P8d9BeQpjUGmCKYl +PUlTSkp7cRpcs3JwkQy2WnJTeXdbhFjvuSced9pxt6YzgizBmOcyjfpim0HV2MrFzan/z6jOh7I71bA4NdyyuRUOhVQJVxU+8UkcayIiIfY= +cr04jPiHcZs6CKfXkpLJpg== +fnnuZD0QAdxvV2PveMWL8JrP786E2VWv6OEVjvqfqal1F0tGEw4g6I+m/uUhNjRha+6HO1sT2iO4abtaK4OE1Vy7eO++Khxe/SuLdIKHyJcUbwrJ8UwpyBO+PNDZ52RrEVi8iM258cNgoOb0YAZQfN7kK2V3iyKgd/GnMPZXMTL4JzhUkcRyggyZfDNgDqT2 +XmWcjbejqXjijvSDMMApGmQVBFK+3Fu7ffDX8KPyOyo5Q0Ti2ZZ2YMkf71vb/W909tViVHOCaDHTsuIQl/bZlA== +HyB13FOAEbSI2KAABytWG1lJdYY/yjhXtGuYXK23U9uctTZthkax3pqCq+KZWhToqgQK7rULc+rIh5RV3cT5QA== +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1Yx/jb+vZHJt3XPEP0sYLVm5vGGxbG9PnkJrq0RBLRyPIHfCkYLqlrQewmKTeUugXsTUbZM0+Bq0egdvyjbtYB3RDS3E8+1Of5ENemfFFdPgZTU4bGrWNIhvEpJReOGDokjH++X8r7oe9AMTBA/AQnDw +ut2tmPKYoa0Hz0cuVsUj3qcXVeFDr/mvvHeu4hSOYIh9yLoIX9tq9Y5O/hZBb0tffbu4/T77YQFbF6NFadqzOjISdrhc269PQWx3OJ7ouTW/1PToQZX1+XjOsQbRA6FVDFry8tD5zNbLBVLnSpaRBYyncHoM/p0AK6AxDDXFQ0s= +ut2tmPKYoa0Hz0cuVsUj3sGyPnE6nUmywzlUF1eIcgMYiYMpPOa4pahwX32jHUUbAHbImsTgL9Rk7Q5kcJIpqFEt3V081Pokv5C1DXYg8pqGB9PcgYc/0ZKPNh5eLzBu+Y2u8nxkPk+kysiE08xWJ/yoGtaacIJybZbO95Rb254= +CTDlCq/Yx3kXyawzsUjqPn4DoEOXY94GKmJFYozlJ3MZVnazWs2aCAAG7WaeGpIc1rom6k73cpvAMPH+Q//OZpGilDx8wm+pDjOdrv5qwBM= +qgvPLYf8+6pituXauG2lHnpUgYTRBVrfogZrz8+a40teOzfvu+2n8c/F+VGRE5UBE+95iEDTw4AWhkst55N38A== +2wtz2EJ9gcVOn6ULRb1bGC1vdhobTwHQicpopnpHvZykrJyTIAmAai0BhcHk+7gzy73WaZ2MEopQp2fV5AohLeqaAoV6nx/xVq+kH9sAGKaH4qZ78s4pETjbffhT67+2hs20W5A4KJiH5y3gyd7fe6NKlulpdwtFzGl88VxNvpEEnITo4gbXmnmsr8ygIv8n3g91w2QrdIzkQn0ufjy3bw== +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 +yYOGmvXvGt/cRLLVkO4eKqa+hM3PuRZfD7b8rMYq/UT5o+JnFaIJw/yVufWRJ3GYE6JJV8UyWObkwgX0nomex4N7hMwH0p/mvytxHRM0LsXoHlzxAgZ68TlBXX9wMqO+ +NREvnNIDTttHan/FJweGj/msBpf/p1aKu8LZw94+KUqSn1c+F+iZlZgS/VL7fr/48X68sN6vyPH3CMR6/5oTo6pzILMLc9S2Abfp3x11erswXy+6aKQxkAOV3JNDJy/u4bISLULyNHieYZYXNxvYW/Dtf+nQ/d3q+8B6w+EKAiJUPNmPW8Mx5AGhPkKWWMQsz7ioFJ2CQjBvlTk/2hfy1MbIqtmWyx3r6g2Vvg1+UQ4ptd3QByZwW46+nwoFVVe7u8OdItBRe7okhek+hhTpiYfdw7mDXnQfcpCHAC7y4BjGXo0CurX7AjM06m7n6D1EA7tHc6gqHSOm5/cGUTKXig== +yYOGmvXvGt/cRLLVkO4eKqa+hM3PuRZfD7b8rMYq/UT5o+JnFaIJw/yVufWRJ3GYE6JJV8UyWObkwgX0nomex4N7hMwH0p/mvytxHRM0LsXoHlzxAgZ68TlBXX9wMqO+ +CTDlCq/Yx3kXyawzsUjqPj/1UgvtLUwbSocBDSTGdlQdGlfuxYW1zktAmMlWajLEJlycf3QM6d3m2/8WV/7qap+0+9bbcfKO31Ha3+gfc0v/wa//RxuB/CTGHWThqgBLKsNaADG5PBVOlQNWxwKSNUS+55fKYqn7UbOGJUsBt08= +iwvjn39WsWBJqN2zZdgrYH73io9UFYsVQIpyJHGNPolTowZ3ES6qaIlYZbd0+9c8k/3/kVFrQkiBM1cR8ZRiGoxZK4ShV6FY0bULRqyn93CV5P1j8chjs7oBotB9I/BtcUB3JYzsnV+37ppDY6xcDg== +iwvjn39WsWBJqN2zZdgrYJapEN/AKnY2zLbcAIzyrBBU1RXGfgWqmb+6/1Y8honwoq8vQFNWL5QXG2xnJPe0LK1Ma+Uxp4BV2qI3ukXHtQYKXoJzDPI9GCy7FvK4NRqoVIY2d3UNJZ6JrGvDp8Itv8UtbTtOKRZzMGesfAFfpr8M2F7b1sde3FoHIbX53bIajKUCfv6EdR2qn5YyhmO/0Q== +96orka/uERLyRst14azQwifX4NgxGrWxr0Kq7A5Rg2HnATx3FBLn85SCdMGIOm1E0hlma+GxHkgZc93eWmwVjA== +6we+bQ+BNhpl74ICwbzDD6SJhA5UxkUBfCZmu1pDNmRCVuoiUdtV/LjGUcclBb9x1lO73/pbYlp7Qn7laP/ALybE3LW0N3KSwXGpr6c9FY4= +cr04jPiHcZs6CKfXkpLJpg== +fnnuZD0QAdxvV2PveMWL8FnhE/Qbu1TUcwt3ycoXzAb63R8Bw+Kjhj/s+Ugn6uM2VYtJ5GD3NvbvtZB/8CpMknz6mUrg1bAfFvVuTUch8Fa1XKdHthZX+SiOeT190cbuzf1PMxPOpzbt2lZhBmtWnV5tc/shXzdOMbVzBF3CVbwpT3MP6rpratp4mnHz3RuO +SE5xU/Z0JSLRg+BF0JZlQ351DCSneiU0Ro4ul8s6YQ0+e9ClxvBwl8Z8jDAAh4Pw29EBMojpWeiQDQQ3je1JBQ== +ncUn2TP8ZQ4fZMBYk+CUrbejaOCU8cNhJKyCBZWCqq3ZxbWlzmOTv+kBIJsZsjljcT1q3oO0cOKLsE5F65U+psAkCkXFw1+rnPdHpl45RtMkIcHFBbTnubK1Yw2j5SiSMTQaANcjGIXkA5pbNDFojl1kkOkaPHt6uO54XZwjB9PlZ8XdctMBp+wpTD06crdB +R6vHhJwn2j7IZ8/oBXXPlciOa3G4bauQKWbklU12JP0CWdxdVaZIoNzpWfrmbe5i5gswcTWd6DqXeLXmYLSydw== +wgR07xfoapmx6eEnFHXXYv6sj3jsGK5jp+XNGADM1YwZ3X8erO8tF+9rF+ud3CmG2DzTMUCy19ltKZJBZVeJ2G3b7Y/tUzeVbHaaGaNkyQryOXyVCJelyU3NO9pBJXZwU4csk3bxLN74sMPi17loDmo4qNbgIL5dTT7IV6iCpcJf45etCMNP7ojLR/iLdfFt +ut2tmPKYoa0Hz0cuVsUj3l2A/S9h8YCNZoTpXi3WyNmbvxKV9hRgROXsqHqZmvm0sTMN+4iSaKr2yT/X+fgBC4YDIVZ1uBFHcV7UgCMzOjI2lx9+St+VE5ivldn9lPHV3rb5YkHv11D4TUNLk3DCWm7kTSih4pt3xjcF9tbSGek= +ut2tmPKYoa0Hz0cuVsUj3iSUOfYfZogjpziC8PUGLSE9MjZBw4GuEUF0vb4KA4CRuxc4tpvehR3tbZvfDCzpxJksbJGmNWFIh7s9WlNnDc+qD7wQpCT3NbfBgRmblirSNOVpyIgLflRUGrvOFeOG9zFm3PjGlOZ00Vv3qWkURL8= +oF+52jOqU38B3IbYFCrd4N3GYJGn+k9zqnS5QNsGjaRnqfzqGwgeL+xZ/I7WiPbOLIGF8wwQthufZuWJLZ8TV8PVM+kG0CKewnf99F2dx5s= +p4mlb5HXUU40EYWFAnjifyb31sUX+0BXv/YW9j2K+gI= +8qTxQznEcsQMgFW26Dqik37xPQpnAepb+3VTmXSvTHkBXZvjIZwIqQeUzzujDsnU8/MAPAbj2oDa9K3AqwPTFQ== +2wtz2EJ9gcVOn6ULRb1bGDulJXIx4y56v5IQGpDjYFZ136nD/ZkugeoB6wP9mFEheXByEOtBdpbVts7POYAC1Q== +VmVrGQo2zRokW/ZuO9bN60HnLn+7qb3BEYgduIkYBusMNq2qQ2m7PTCE41vqIcK/Cewvi49S3FU5bVmsIU/TRg== +VmVrGQo2zRokW/ZuO9bN66B2cWRLetUcWMU+aw+SZbQ= +fnnuZD0QAdxvV2PveMWL8GzimDATsb9PCxhUZX36WPceYPd8+C+DZiTdw0oWYeDzPAREthfc0Ab+8X6ybl3VEmmQvWGs9zAYVneC2Hy2egkTCe1Ob78xdnB0S18HF60ZIM1v7YQAQ8UB+CC4cZs7fQ== +AaCYXSBoNzzxOXBdbJWxCI9qK/O2dxQuwFCqR22/ccmrRlWPB13BOQzWf+aWkGS+2+eb+G2UGJ030Ldkc/TRa6QakPcSXSNOI3gUmbZsFE56ZNHp130Cn7JwRwpSNvyF +Igyj+TKK+I9SELYdi+D74wrFQ1TlNAWhWGzDssoXLbghxc1cj/mwrtUBlQ6VRLTuoSRRar3R+eQrcrTodxnolxO11AHYzchAkrT97uXxfomkpOzQL5Tmlw5oI/hsnk0R3C+lgPstP5/4bDc90+iRQT3cMxKnuQv1rM9z6oW2nPBssswhEAPdwxcmplZu4C3JnPxdZByVx6FbRN2Jgg4Y1CUhjhiIBddhAyjAv1YWNpTM8o5GlDeqrvWm2293br1F +Igyj+TKK+I9SELYdi+D74wrFQ1TlNAWhWGzDssoXLbjeoHs/8Mje8tsYdNHFBscqwQug2YW9a6jBvQV2kMslbbKaz2EQLgEjn6frBGO4TfxHRuA1CCfJmWabVkIQMFxrCJix3c+0lMgO1+8zPIjvrg== +Igyj+TKK+I9SELYdi+D74wrFQ1TlNAWhWGzDssoXLbiZex8VHIziY50edMtwBriCkbhwV2pZ9K3FDuHuJkHYxeLxgByj1SOSg2vK7OLlpEhM2jDZRUpXVynPlw8R1bP7ZLPJI+hMXZWTM2qTMVW4Bg== +1cV64uRoDtvn/9K8xFmBBSyIwLLAyZtDmWjNCGSK35oV7Lln1obVdrK86aC2P/5d2IugIo65luFTN3ITzWeIAojASipLuDAxR0vKGzse+0ZM659ojBqm1tuvWsd/P5Hs8sc9RblCikWUy4X3c3w5R9/jB0lLIJ44vKAW2Z/MoB4f+A5ULgHqCz5/JSrpUeMfl/E8YVza9ME/7/ufufeoNA== +/wrjOJbIgR3iizz9ZfsYFV1S0QEoNDYDB0tPgYYyunCzrR8+mwbHh2ZHlRO+dgFF1df50fLmTkW5bI44PHknwmKwbKoYj8Y9Uyhuh9nMKzL8yi5sZyw9SFrwXay2nHnR +fnnuZD0QAdxvV2PveMWL8DHcrBrw7XfIoGItmS/X1LRvez55+VhH4ORJHoks3tPiigwsWjzGIQ1+EqGmE/pzsSG8L1Qo4r3de+ZwnWu4x5aYGJBCnAtBWXOAjrBM7bRdQX4XYwovgtmfpuO6eZBDlcrPlsAE0RxjrJ/E/BsdBTRGuen7gVcgLjFTZvWMO4Fa +oacxXEoPih9NKsd5qduwcLPkuMecFSQLme+U3RYD4k6vG2vB69hZeZTj75RiG2qUS2vKp6FFekTp22dTzpmqrhqwF3gZSut9skRswVAOd+ey+p8hkk31WVViZKUsvFoZcQzf5qFZ5bMTM/a7hA53WqJlzxxMkdA2km9UpRf8a2WRvbBa6KD/jTkeYiZl/nHyHjmA2tqxhDLike6xDAbA2AQYYTbkiirIVH9/A+P08TmTNHCZUryZAOMBqpffFuhKa4uoh4D0WBbjEPrFdoiEz3ocw5bKhTwdKD2RQWcIFihi+UnmV2ApR3o26gPYgi6HRSeiKrZEwhcp5m+whI7lnng5/abuOGGbabfAagwFRP8HkguHuJ2vfjmYtJiVXkgrpjYSJsDZHFwhgfPxj9XqVRNzREoMxO0S/b0FC90Z6w0r1eMRmzBF5N2Zn/4JOzfvVymJ+MEoKHZyQjjwZIIOXp4KFKnmvUZ9q4C//bBvwEaBQJ6Qteg7RZ0QQkafEG/NBncU1vOMCYFIZTOG/ym+H39xC51Omll5vt64U6rtJO7lsIO+WKd9WZAUYATPv1J1g1aRzsJycWilmD+jWXpI23ZcbmmTf4VctIpeCNZwnm+aQm7J30HLgU2PnkluZdq744bQ4GCZQVgHkLM++NGVUmDB97Rx/4FIZ9i83MNYpndJPYOe2yYnQ5vThEHFeanLo115DYYct09qBrRVGeM9hxfN7UP1MWDKFeAbZF5flQE= +nPNZk+Xvg1c6GZFzrp1IbMVt0EpHjdQMD/KiHv3lEICoTqIbAmvUQ+gVypzCdBB8Gs0AbP+Zh9pMLpuFBMJVhrRL3RKTfSSRfVHFNiMAjiuK48g7qWvs2jmegZXfmVGc+gXJ9imIODTMBbyPmgyrR0qZFdlSN4+ocHLB0gjsQGmIRDYaJNrNAnS4USkTNOXVJ+CnobHbEUrDvenXe4GcGwb6lwpI0j1vr6tVraqgemOc+yVhnFDBFX0eaTQPMUsDqeqEbxv/r/mhMEfOmRGhUA== +wu62rpGT52ZTxeUUs2GeuGgAZsk/tzh/isMkRRQuL7nq3eaiw1Qb9M0+Qc8KElN+7mEiLyeftYZBbZDOnzNciRm5TdJahLGV1drbGS1rn8dSqfam6zfdyDZ+0qHR+jnX +nPNZk+Xvg1c6GZFzrp1IbMVt0EpHjdQMD/KiHv3lEICoTqIbAmvUQ+gVypzCdBB8Gs0AbP+Zh9pMLpuFBMJVhlpvc3VMIX/ToG+YV/rNHgA6KSwDvFN3sEa/wtjnlB7Gwj6zOH2itskLF4HrIvxMLthUmGUfGpnVqtHOpKzBOhMdQMK3ECYTLkqJgqQIjZkxcxvKH33BB8iIKHhbL/7VeuzthB99YJSe3mQ0vuX2vvcBZTIlX/khXl8eo63Qod7ozf7o47f41YKmUdwsO/JMXjeiD0NMGOFxwR4AREJ50Invb5AF4D9pp2kJTvEUlKGD+L4b4hSYjM37qFKD1zXD1A== +wu62rpGT52ZTxeUUs2GeuGgAZsk/tzh/isMkRRQuL7nq3eaiw1Qb9M0+Qc8KElN+7mEiLyeftYZBbZDOnzNciRm5TdJahLGV1drbGS1rn8dSqfam6zfdyDZ+0qHR+jnX +8qTxQznEcsQMgFW26Dqik37xPQpnAepb+3VTmXSvTHkBXZvjIZwIqQeUzzujDsnU8/MAPAbj2oDa9K3AqwPTFQ== +2wtz2EJ9gcVOn6ULRb1bGDulJXIx4y56v5IQGpDjYFZ136nD/ZkugeoB6wP9mFEheXByEOtBdpbVts7POYAC1Q== +VmVrGQo2zRokW/ZuO9bN65CLqRUEWJ03MnVfT48m2QyxHgYbgNM+mUWp/RHlScL707Hwd5hAXrcCQuNOHzWRmw== +VmVrGQo2zRokW/ZuO9bN66B2cWRLetUcWMU+aw+SZbQ= +iwvjn39WsWBJqN2zZdgrYG5NBDXw/UERaMa5qn6uxHIlNsNh7PeHEY5YZ4B/EHd4pgIscKVIIX7ioUSholIaIyLKidrEOF20CDJX/iz34wBbW9zWqoCbzoD6tzk7oYnY1Lb5dkZX86F/rOGlcmjsIQ== +iwvjn39WsWBJqN2zZdgrYJapEN/AKnY2zLbcAIzyrBAAFOSsyeU1IHqhRAAJmxek18L8hhYV0nclwoNTInf9BxJ1mKeAbWt9n4txx96fjdNoTICL7WaSPRAd+PHZ9Rckg0kFG02IFUMdzt0r5RApu6mM0z1QqDZmZc2kp00MWUTfdnwFkK6MLDk+Drcqk5fnWaio6MEpLq2BY4/d9cl99Q== +96orka/uERLyRst14azQwifX4NgxGrWxr0Kq7A5Rg2GgtWLCloikT548er10NVqyOU/URIs3U0nX+iki5Rj91UImIzSUQ3706pgDdxepy+E= diff --git a/class/public/PluginLoader.py b/class/public/PluginLoader.py new file mode 100644 index 00000000..4c401d2e --- /dev/null +++ b/class/public/PluginLoader.py @@ -0,0 +1,24 @@ +from .exceptions import NoAuthorizationException, HintException + + +def get_module(filename: str): + import PluginLoader + + module_obj = PluginLoader.get_module(filename) + + if not module_obj: + raise Exception('importError: {}'.format(filename)) + + if isinstance(module_obj, dict): + if 'msg' not in module_obj: + raise Exception('importError: {}'.format(filename)) + + if module_obj['msg'] == 'Sorry. This feature is professional member only.': + raise NoAuthorizationException(module_obj['msg']) + + if str(module_obj['msg']).find('Traceback ') > -1: + raise HintException(module_obj['msg']) + + raise Exception('importError: {}\n{}'.format(filename, module_obj['msg'])) + + return module_obj diff --git a/class/public/__init__.py b/class/public/__init__.py new file mode 100644 index 00000000..c99c507b --- /dev/null +++ b/class/public/__init__.py @@ -0,0 +1,20 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +# -------------------------------- +# 宝塔公共库 +# -------------------------------- + +from .common import * +from .exceptions import * + + +def is_bind(): + # if not os.path.exists('{}/data/bind.pl'.format(get_panel_path())): return True + return not not get_user_info() diff --git a/class/public/common.py b/class/public/common.py new file mode 100644 index 00000000..6b022b52 --- /dev/null +++ b/class/public/common.py @@ -0,0 +1,8277 @@ +# 公共模块 +# @author Zhj<2024/06/15> +import contextlib +import json, os, sys, time, re, socket, importlib, binascii, base64, io, string, psutil +import gettext +import typing +import werkzeug.datastructures +from .validate import Param, trim_filter +from .regexplib import match_ipv4, match_ipv6, match_class_private_property, match_safe_path, match_based_host, \ + find_url_root +from .tools import is_number + + +import collections + +# Common structures +aap_t_simple_result = collections.namedtuple('aap_t_simple_result', ['success', 'msg']) +aap_t_mysql_dump_info = collections.namedtuple('aap_t_mysql_dump_info', ['db_name', 'file', 'dump_time']) + + +es = gettext.translation('en', localedir='/www/server/panel/BTPanel/static/language', languages=['en']) +es.install() +_ = es.gettext + +_LAN_PUBLIC = None +_LAN_LOG = None +_LAN_TEMPLATE = None + +if sys.version_info[0] == 2: + reload(sys) + sys.setdefaultencoding('utf8') +else: + from importlib import reload + + +def M(table): + """ + @name 访问面板数据库 + @author hwliang + @table 被访问的表名(必需) + @return db.Sql object + + ps: 默认访问data/default.db + """ + import db + with db.Sql() as sql: + # sql = db.Sql() + return sql.table(table) + + +# 连接MYSQL数据库 +def MysqlConn(db_name: typing.Optional[str] = None, db_user: str = 'root', db_pwd: typing.Optional[str] = None, db_host: str = 'localhost'): + from panel_mysql_v2 import PanelMysqlWithContext + return PanelMysqlWithContext(db_name, db_user, db_pwd, db_host) + + +def HttpGet(url, timeout=6, headers={}): + """ + @name 发送GET请求 + @author hwliang + @url 被请求的URL地址(必需) + @timeout 超时时间默认60秒 + @return string + """ + if url.find('GetAuthToken') == -1: + if is_local(): return False + # rep_home_host() + import http_requests + res = http_requests.get(url, timeout=timeout, headers=headers, verify=False) + if res.status_code == 0: + if headers: return False + s_body = res.text + return s_body + s_body = res.text + del res + return s_body + + +def http_get_home(url, timeout, ex): + """ + @name Get方式使用优选节点访问官网 + @author hwliang + @param url 当前官网URL地址 + @param timeout 用于测试超时时间 + @param ex 上一次错误的响应内容 + @return string 响应内容 + + 如果已经是优选节点,将直接返回ex + """ + try: + home = 'www.bt.cn' + if url.find(home) == -1: return ex + hosts_file = "config/hosts.json" + if not os.path.exists(hosts_file): return ex + hosts = json.loads(readFile(hosts_file)) + headers = {"host": home} + for host in hosts: + new_url = url.replace(home, host) + res = HttpGet(new_url, timeout, headers) + if res: + writeFile("data/home_host.pl", host) + # set_home_host(host) + return res + return ex + except: + return ex + + +# def set_home_host(host): +# """ +# @name 设置官网hosts +# @author hwliang +# @param host IP地址 +# @return void +# """ +# ExecShell('sed -i "/www.bt.cn/d" /etc/hosts') +# ExecShell("echo '' >> /etc/hosts") +# ExecShell("echo '%s www.bt.cn' >> /etc/hosts" % host) +# ExecShell(r'sed -i "/^\s*$/d" /etc/hosts') + +def httpGet(url, timeout=6): + return HttpGet(url, timeout) + + +def HttpPost(url, data, timeout=6, headers={}): + """ + 发送POST请求 + @url 被请求的URL地址(必需) + @data POST参数,可以是字符串或字典(必需) + @timeout 超时时间默认60秒 + return string + """ + if url.find('GetAuthToken') == -1: + if is_local(): return False + # rep_home_host() + import http_requests + res = http_requests.post(url, data=data, timeout=timeout, headers=headers) + if res.status_code == 0: + if headers: return False + s_body = res.text + return s_body + s_body = res.text + return s_body + + +def httpPost(url, data, headers={}, timeout=6): + """ + @name 发送POST请求 + @author hwliang + @param url 被请求的URL地址(必需) + @param data POST参数,可以是字符串或字典(必需) + @param timeout 超时时间默认60秒 + @return string + """ + return HttpPost(url, data, timeout, headers) + + +def check_home(): + return True + + +def Md5(strings): + """ + @name 生成MD5 + @author hwliang + @param strings 要被处理的字符串 + @return string(32) + """ + if type(strings) != bytes: + strings = strings.encode() + import hashlib + m = hashlib.md5() + m.update(strings) + return m.hexdigest() + + +def md5(strings): + return Md5(strings) + + +def FileMd5(filename): + """ + @name 生成文件的MD5 + @author hwliang + @param filename 文件名 + @return string(32) or False + """ + if not os.path.isfile(filename): return False + import hashlib + my_hash = hashlib.md5() + f = open(filename, 'rb') + while True: + b = f.read(8096) + if not b: + break + my_hash.update(b) + f.close() + return my_hash.hexdigest() + + +def GetRandomString(length): + """ + @name 取随机字符串 + @author hwliang + @param length 要获取的长度 + @return string(length) + """ + from random import Random + strings = '' + chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789' + chrlen = len(chars) - 1 + random = Random() + for i in range(length): + strings += chars[random.randint(0, chrlen)] + return strings + + +def ReturnJson(status, msg, args=()): + """ + @name 取通用Json返回 + @author hwliang + @param status 返回状态 + @param msg 返回消息 + @return string(json) + """ + # return GetJson(ReturnMsg(status, msg, args)) + return GetJson(return_msg_gettext(status, msg, args)) + + +def returnJson(status, msg, args=()): + """ + @name 取通用Json返回 + @author hwliang + @param status 返回状态 + @param msg 返回消息 + @return string(json) + """ + return ReturnJson(status, msg, args) + + +def ReturnMsg(status, msg, args=()): + """ + @name 取通用dict返回 + @author hwliang + @param status 返回状态 + @param msg 返回消息 + @return dict {"status":bool,"msg":string} + """ + try: + log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json')) + except: + log_message = {} + keys = log_message.keys() + if type(msg) == str: + if msg in keys: + msg = log_message[msg] + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + msg = msg.replace(rep, args[i]) + return {'status': status, 'msg': msg} + + +def return_msg_gettext(status, msg, args=()): + """ + @name 取通用dict返回 + @author hwliang + @date 2022.9.20 + """ + msg = gettext_msg(msg, args) + return {'status': status, 'msg': msg} + + +def returnMsg(status, msg, args=()): + """ + @name 取通用dict返回 + @author hwliang + @param status 返回状态 + @param msg 返回消息 + @return dict {"status":bool,"msg":string} + """ + return ReturnMsg(status, msg, args) + + +def return_message(status, types, message, args=(), play="", requests=()): + """ + @name 统一请求响应函数 + @author hezhihong + @param status 返回状态 + @param message 返回消息 + @return dict {"status":0/-1,"message":any}/下载对象 + """ + from flask import g + g.return_message = True + # 非文件下载 + if types == 0: + return_message = {'status': status, "timestamp": int(time.time()), "message": {}} + try: + log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json')) + except: + log_message = {} + keys = log_message.keys() + if type(message) == str: + if message in keys: + message = log_message[message] + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + message = message.replace(rep, args[i]) + return_message["message"]["result"] = message + elif type(message) == int: + return_message["message"]["result"] = message + elif type(message) == bool: + return_message["message"]["result"] = message + elif type(message) == float: + return_message["message"]["result"] = message + elif type(message) == dict: + return_message["message"] = message + elif type(message) == list: + return_message["message"] = message + elif type(message) == tuple: + return_message["message"] = message + else: + try: + return_message["message"] = message + except: + return_message["message"] = {} + return return_message + # # 文件下载 + # elif types == 1: + # # from flask import requests as requests + # if play == 'true': + # import panelVideo + # # start, end = panelVideo.get_range(requests) + # # return panelVideo.partial_response(filename, start, end) + # else: + # mimetype = "application/octet-stream" + # extName = filename.split('.')[-1] + # if extName in ['png', 'gif', 'jpeg', 'jpg']: mimetype = None + # public.WriteLog("TYPE_FILE", 'FILE_DOWNLOAD', + # (filename, public.GetClientIp())) + # return send_file(filename, + # mimetype=mimetype, + # as_attachment=True, + # etag=True, + # conditional=True, + # download_name=os.path.basename(filename), + # max_age=0) + + # html响应对象 + elif types == 2: + return_message = {'status': status, "timestamp": int(time.time()), "message": {}} + if type(message) == str: + return_message["message"]["result"] = message + return return_message + + +# V2版本的成功响应函数 +def success_v2(res, format_args=()): + """ + @name V2版本的成功响应函数 + @author Zhj<2024-06-05> + @param res 响应数据 + @param format_args 响应文本提示时的format参数 + @return dict + """ + # 对文本响应做多语言转换处理 + if isinstance(res, str): + res = gettext_msg(res, format_args) + + return return_message(0, 0, res) + + +# V2版本的失败响应函数 +def fail_v2(res, format_args=()): + """ + @name V2版本的失败响应函数 + @author Zhj<2024-06-05> + @param res 响应数据 + @param format_args 响应文本提示时的format参数 + @return dict + """ + # 对文本响应做多语言转换处理 + if isinstance(res, str): + res = gettext_msg(res, format_args) + + return return_message(-1, 0, res) + + +def GetFileMode(filename): + """ + @name 取文件权限字符串 + @author hwliang + @param filename 文件全路径 + @return string 如:644/777/755 + """ + stat = os.stat(filename) + accept = str(oct(stat.st_mode)[-3:]) + return accept + + +def get_mode_and_user(path): + '''取文件或目录权限信息''' + import pwd + data = {} + if not os.path.exists(path): return None + stat = os.stat(path) + data['mode'] = str(oct(stat.st_mode)[-3:]) + try: + data['user'] = pwd.getpwuid(stat.st_uid).pw_name + except: + data['user'] = str(stat.st_uid) + return data + + +class ijson: + def loads(self, data): + return json.loads(data) + + def dumps(self, data): + try: + try: + return json.dumps(data) + except: + return json.dumps(data, ensure_ascii=False) + except: + return json.dumps({'status': False, 'msg': "wrong response: %s" % str(data)}) + + +def GetJson(data): + """ + 将对象转换为JSON + @data 被转换的对象(dict/list/str/int...) + """ + if data == bytes: data = data.decode('utf-8') + ijson_obj = ijson() + data = ijson_obj.dumps(data) + del (ijson_obj) + return data + + +def getJson(data): + return GetJson(data) + + +def gettext_msg(msg, args=()): + try: + msg = _(msg).format(*args) + except: + pass + finally: + return msg + + +def write_log_gettext(type, logmsg, args=(), not_web=False): + # 写日志 + logmsg = gettext_msg(logmsg, args) + try: + import time, db, json + username = 'system' + uid = 1 + tmp_msg = '' + if not not_web: + try: + from BTPanel import session + if 'username' in session: + username = session['username'] + uid = session['uid'] + if session.get('debug') == 1: return + except: + pass + sql = db.Sql() + mDate = time.strftime('%Y-%m-%d %X', time.localtime()) + data = (uid, username, _(type), xssencode2(logmsg + tmp_msg), mDate) + result = sql.table('logs').add('uid,username,type,log,addtime', data) + except: + pass + + +def WriteLog(type, logMsg, args=(), not_web=False): + # 写日志 + try: + import time, db, json + username = 'system' + uid = 1 + tmp_msg = '' + if not not_web: + try: + from BTPanel import session + if 'username' in session: + username = session['username'] + uid = session['uid'] + if session.get('debug') == 1: return + except: + pass + global _LAN_LOG + if not _LAN_LOG: + _LAN_LOG = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/log.json')) + keys = _LAN_LOG.keys() + if logMsg in keys: + logMsg = _LAN_LOG[logMsg] + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + logMsg = logMsg.replace(rep, args[i]) + if type in keys: type = _LAN_LOG[type] + + try: + if 'login_address' in session: + logMsg = '{} {}'.format(session['login_address'], logMsg) + except: + pass + + sql = db.Sql() + mDate = time.strftime('%Y-%m-%d %X', time.localtime()) + data = (uid, username, type, logMsg + tmp_msg, mDate) + result = sql.table('logs').add('uid,username,type,log,addtime', data) + return result + except: + return None + + +def GetLanguage(): + ''' + 取语言 + ''' + return GetConfigValue("language") + + +def get_language(): + return GetLanguage() + + +def GetConfigValue(key): + ''' + 取配置值 + ''' + config = GetConfig() + if not key in config.keys(): + if key == 'download': return 'http://node.aapanel.com' + return None + return config[key] + + +def SetConfigValue(key, value): + config = GetConfig() + config[key] = value + WriteConfig(config) + + +def GetConfig(): + ''' + 取所有配置项 + ''' + path = "config/config.json" + if not os.path.exists(path): return {} + f_body = ReadFile(path) + if not f_body: return {} + return json.loads(f_body) + + +def WriteConfig(config): + path = "config/config.json" + WriteFile(path, json.dumps(config)) + + +def GetLan(key): + """ + 取提示消息 + """ + global _LAN_TEMPLATE + if not _LAN_TEMPLATE: + _LAN_TEMPLATE = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/template.json')) + keys = _LAN_TEMPLATE.keys() + msg = None + if key in keys: + msg = _LAN_TEMPLATE[key] + return msg + + +def getLan(key): + return GetLan(key) + + +def GetMsg(key, args=()): + try: + global _LAN_PUBLIC + if not _LAN_PUBLIC: + _LAN_PUBLIC = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json')) + keys = _LAN_PUBLIC.keys() + msg = None + if key in keys: + msg = _LAN_PUBLIC[key] + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + msg = msg.replace(rep, args[i]) + return msg + except: + return key + + +def get_msg_gettext(msg, args=()): + return gettext_msg(msg, args) + + +def getMsg(key, args=()): + return GetMsg(key, args) + + +# 获取Web服务器 +def GetWebServer(): + if os.path.exists('{}/apache/bin/apachectl'.format(get_setup_path())): + webserver = 'apache' + elif os.path.exists('/usr/local/lsws/bin/lswsctrl'): + webserver = 'openlitespeed' + else: + webserver = 'nginx' + return webserver + + +def get_webserver(): + return GetWebServer() + + +def ServiceReload(): + # 重载Web服务配置 + if os.path.exists('{}/nginx/sbin/nginx'.format(get_setup_path())): + result = ExecShell('/etc/init.d/nginx reload') + if result[1].find('nginx.pid') != -1: + ExecShell('pkill -9 nginx && sleep 1') + ExecShell('/etc/init.d/nginx start') + elif os.path.exists('{}/apache/bin/apachectl'.format(get_setup_path())): + result = ExecShell('/etc/init.d/httpd reload') + else: + result = ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl restart') + return result + + +def serviceReload(): + return ServiceReload() + + +def get_preexec_fn(run_user): + ''' + @name 获取指定执行用户预处理函数 + @author hwliang<2021-08-19> + @param run_user 运行用户 + @return 预处理函数 + ''' + import pwd + pid = pwd.getpwnam(run_user) + uid = pid.pw_uid + gid = pid.pw_gid + + def _exec_rn(): + os.setgid(gid) + os.setuid(uid) + + return _exec_rn + + +def ExecShell(cmdstring, timeout=None, shell=True, cwd=None, env=None, user=None): + ''' + @name 执行命令 + @author hwliang<2021-08-19> + @param cmdstring 命令 [必传] + @param timeout 超时时间 + @param shell 是否通过shell运行 + @param cwd 进入的目录 + @param env 环境变量 + @param user 执行用户名 + @return 命令执行结果 + ''' + a = '' + e = '' + import subprocess, tempfile + preexec_fn = None + tmp_dir = '/dev/shm' + if user: + preexec_fn = get_preexec_fn(user) + tmp_dir = '/tmp' + try: + rx = md5(cmdstring) + succ_f = tempfile.SpooledTemporaryFile(max_size=4096, mode='wb+', suffix='_succ', prefix='btex_' + rx, + dir=tmp_dir) + err_f = tempfile.SpooledTemporaryFile(max_size=4096, mode='wb+', suffix='_err', prefix='btex_' + rx, + dir=tmp_dir) + sub = subprocess.Popen(cmdstring, close_fds=True, shell=shell, bufsize=128, stdout=succ_f, stderr=err_f, + cwd=cwd, env=env, preexec_fn=preexec_fn) + if timeout: + s = 0 + d = 0.01 + while sub.poll() == None: + time.sleep(d) + s += d + if s >= timeout: + if not err_f.closed: err_f.close() + if not succ_f.closed: succ_f.close() + return 'Timed out' + else: + sub.wait() + + err_f.seek(0) + succ_f.seek(0) + a = succ_f.read() + e = err_f.read() + if not err_f.closed: err_f.close() + if not succ_f.closed: succ_f.close() + except: + return '', get_error_info() + try: + # 编码修正 + if type(a) == bytes: a = a.decode('utf-8') + if type(e) == bytes: e = e.decode('utf-8') + except: + a = str(a) + e = str(e) + + return a, e + + +def GetLocalIp(): + # 取本地外网IP + try: + filename = 'data/iplist.txt' + ipaddress = readFile(filename) + if not ipaddress: + url = 'https://ifconfig.me/ip' + m_str = HttpGet(url) + if isinstance(m_str, bytes): + ipaddress = match_ipv4.match(m_str.decode('utf-8')).group(0) + else: + ipaddress = match_ipv4.match(m_str).group(0) + WriteFile(filename, ipaddress) + c_ip = check_ip(ipaddress) + if not c_ip: return GetHost() + return ipaddress + except Exception as e: + try: + url = 'https://www.aapanel.com/api/common/getClientIP' + ipaddress = HttpGet(url) + WriteFile(filename, ipaddress) + return ipaddress + except: + return GetHost() + + +def is_ipv4(ip): + ''' + @name 是否是IPV4地址 + @author hwliang + @param ip IP地址 + @return True/False + ''' + # 验证基本格式 + if not match_ipv4.match(ip): + return False + + # 验证每个段是否在合理范围 + try: + socket.inet_pton(socket.AF_INET, ip) + except AttributeError: + try: + socket.inet_aton(ip) + except socket.error: + return False + except socket.error: + return False + return True + + +def is_ipv6(ip): + ''' + @name 是否为IPv6地址 + @author hwliang + @param ip 地址 + @return True/False + ''' + # 验证基本格式 + if not match_ipv6.match(ip): + return False + + # 验证IPv6地址 + try: + socket.inet_pton(socket.AF_INET6, ip) + except socket.error: + return False + return True + + +def check_ip(ip): + return is_ipv4(ip) or is_ipv6(ip) + + +def GetHost(port=False): + from flask import request + host_tmp = request.headers.get('host') + + # 验证基本格式 + if host_tmp: + if not match_based_host.match(host_tmp): + host_tmp = '' + + if not host_tmp: + if request.url_root: + tmp = find_url_root.findall(request.url_root) + if tmp: host_tmp = tmp[0][1] + if not host_tmp: + host_tmp = '127.0.0.1:' + readFile('data/port.pl').strip() + try: + if host_tmp.find(':') == -1: host_tmp += ':80' + except: + host_tmp = "127.0.0.1:7800" + h = host_tmp.split(':') + if port: return h[-1] + return ':'.join(h[0:-1]) + + +def GetClientIp(): + from flask import request + ipaddr = request.remote_addr.replace('::ffff:', '') + if not check_ip(ipaddr): return 'Unknown IP address' + return ipaddr + + +def get_remote_port(): + ''' + @name 获取客户端端口号 + @return int + ''' + from flask import request + port = request.headers.get('X-Real-Port', '0') + if port == '0': port = request.environ.get('REMOTE_PORT') + return str(port) + + +def get_client_ip(): + return GetClientIp() + + +def phpReload(version): + # 重载PHP配置 + import os + if os.path.exists(get_setup_path() + '/php/' + version + '/libphp5.so'): + ExecShell('/etc/init.d/httpd reload') + else: + ExecShell('/etc/init.d/php-fpm-' + version + ' reload') + ExecShell("/etc/init.d/php-fpm-{} start".format(version)) + + +def get_timeout(url, timeout=3): + try: + start = time.time() + result = int(httpGet(url, timeout)) + return result, int((time.time() - start) * 1000 - 500) + except: + return 0, False + + +def get_url(timeout=0.5): + return 'https://node.aapanel.com' + + import json + try: + pkey = 'node_url' + node_url = cache_get(pkey) + if node_url: return node_url + nodeFile = 'data/node.json' + node_list = json.loads(readFile(nodeFile)) + mnode1 = [] + mnode2 = [] + mnode3 = [] + new_node_list = {} + for node in node_list: + node['net'], node['ping'] = get_timeout( + node['protocol'] + node['address'] + ':' + node['port'] + '/net_test', 1) + new_node_list[node['address']] = node['ping'] + if not node['ping']: continue + if node['ping'] < 100: # 当响应时间<100ms且可用带宽大于1500KB时 + if node['net'] > 1500: + mnode1.append(node) + elif node['net'] > 1000: + mnode3.append(node) + else: + if node['net'] > 1000: # 当响应时间>=100ms且可用带宽大于1000KB时 + mnode2.append(node) + if node['ping'] < 100: + if node['net'] > 3000: break # 有节点可用带宽大于3000时,不再检查其它节点 + if mnode1: # 优选低延迟高带宽 + mnode = sorted(mnode1, key=lambda x: x['net'], reverse=True) + elif mnode3: # 备选低延迟,中等带宽 + mnode = sorted(mnode3, key=lambda x: x['net'], reverse=True) + else: # 终选中等延迟,中等带宽 + mnode = sorted(mnode2, key=lambda x: x['ping'], reverse=False) + + if not mnode: return 'https://node.aapanel.com' + + new_node_keys = new_node_list.keys() + for i in range(len(node_list)): + if node_list[i]['address'] in new_node_keys: + node_list[i]['ping'] = new_node_list[node_list[i]['address']] + else: + node_list[i]['ping'] = 500 + + new_node_list = sorted(node_list, key=lambda x: x['ping'], reverse=False) + writeFile(nodeFile, json.dumps(new_node_list)) + node_url = mnode[0]['protocol'] + mnode[0]['address'] + ':' + mnode[0]['port'] + cache_set(pkey, node_url, 86400) + return node_url + except: + return 'https://node.aapanel.com' + + +# 过滤输入 +def checkInput(data): + if not data: return data + if type(data) != str: return data + checkList = [ + {'d': '<', 'r': '<'}, + {'d': '>', 'r': '>'}, + {'d': '\'', 'r': '‘'}, + {'d': '"', 'r': '“'}, + {'d': '&', 'r': '&'}, + {'d': '#', 'r': '#'}, + {'d': '<', 'r': '<'} + ] + for v in checkList: + data = data.replace(v['d'], v['r']) + return data + + +# 取文件指定尾行数 +def GetNumLines(path, num: int, p=1): + if not os.path.exists(path): return "" + if not is_number(num): + return "" + + pyVersion = sys.version_info[0] + max_len = 1024 * 1024 * 10 + try: + + start_line = (p - 1) * num + count = start_line + num + fp = open(path, 'rb') + buf = "" + fp.seek(-1, 2) + if fp.read(1) == "\n": fp.seek(-1, 2) + data = [] + total_len = 0 + b = True + n = 0 + for i in range(count): + while True: + newline_pos = str.rfind(str(buf), "\n") + pos = fp.tell() + if newline_pos != -1: + if n >= start_line: + line = buf[newline_pos + 1:] + line_len = len(line) + total_len += line_len + sp_len = total_len - max_len + if sp_len > 0: + line = line[sp_len:] + try: + data.insert(0, line) + except: + pass + buf = buf[:newline_pos] + n += 1 + break + else: + if pos == 0: + b = False + break + to_read = min(4096, pos) + fp.seek(-to_read, 1) + t_buf = fp.read(to_read) + if pyVersion == 3: + t_buf = t_buf.decode('utf-8', errors='ignore') + + buf = t_buf + buf + fp.seek(-to_read, 1) + if pos - to_read == 0: + buf = "\n" + buf + if total_len >= max_len: break + if not b: break + fp.close() + result = "\n".join(data) + except: + if re.match(r"[`\$\&\;]+", path): return "" + result = ExecShell("tail -n {} {}".format(num, path))[0] + if len(result) > max_len: + result = result[-max_len:] + + try: + try: + result = json.dumps(result) + return json.loads(result).strip() + except: + if pyVersion == 2: + result = result.decode('utf8', errors='ignore') + else: + result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore") + return result.strip() + except: + return "" + + +# 验证证书 +def CheckCert(certPath='ssl/certificate.pem'): + try: + return get_cert_data(certPath) + except: + openssl = '/usr/local/openssl/bin/openssl' + if not os.path.exists(openssl): openssl = 'openssl' + certPem = readFile(certPath) + s = "\n-----BEGIN CERTIFICATE-----" + tmp = certPem.strip().split(s) + res = True + for tmp1 in tmp: + if tmp1.find('-----BEGIN CERTIFICATE-----') == -1: tmp1 = s + tmp1 + writeFile(certPath, tmp1) + result = ExecShell(openssl + " x509 -in " + certPath + " -noout -subject") + if result[1].find('-bash:') != -1: res = True + if len(result[1]) > 2: res = False + if result[0].find('error:') != -1: res = False + return res + + +# 获取面板地址 +def getPanelAddr(): + from flask import request + protocol = 'https://' if os.path.exists("data/ssl.pl") else 'http://' + return protocol + request.headers.get('host') + + +# 字节单位转换 +def to_size(size): + if not size: return '0.00 b' + size = float(size) + d = ('b', 'KB', 'MB', 'GB', 'TB') + s = d[0] + for b in d: + if size < 1024: return ("%.2f" % size) + ' ' + b + size = size / 1024 + s = b + return ("%.2f" % size) + ' ' + b + + +def checkCode(code, outime=120): + # 校验验证码 + from BTPanel import session, cache + try: + codeStr = cache.get('codeStr') + cache.delete('codeStr') + if not codeStr: + session['login_error'] = GetMsg('CODE_TIMEOUT') + return False + + if md5(code.lower()) != codeStr: + session['login_error'] = GetMsg('CODE_ERR') + return False + return True + except: + session['login_error'] = GetMsg('CODE_NOT_EXISTS') + return False + + +# 写进度 +def writeSpeed(title, used, total, speed=0): + import json + if not title: + data = {'title': None, 'progress': 0, 'total': 0, 'used': 0, 'speed': 0} + else: + try: + progress = int((100.0 * used / total)) + except: + progress = 0 + data = {'title': title, 'progress': progress, 'total': total, 'used': used, 'speed': speed} + writeFile('/tmp/panelSpeed.pl', json.dumps(data)) + return True + + +# 取进度 +def getSpeed(): + import json + data = readFile('/tmp/panelSpeed.pl') + if not data: + data = json.dumps({'title': None, 'progress': 0, 'total': 0, 'used': 0, 'speed': 0}) + writeFile('/tmp/panelSpeed.pl', data) + return json.loads(data) + + +def get_requests_headers(): + return {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "BT-Panel"} + + +def downloadFile(url, filename): + try: + if sys.version_info[0] == 2: + import requests + headers = { + 'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'} + r = requests.get(url, headers=headers, verify=False) + with open(filename, "wb") as f: + f.write(r.content) + else: + import urllib.request + import ssl + ssl._create_default_https_context = ssl._create_unverified_context + opener = urllib.request.build_opener() + opener.addheaders = [('User-agent', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36')] + urllib.request.install_opener(opener) + urllib.request.urlretrieve(url, filename=filename) + except: + ExecShell("wget -O --no-check-certificate {} {}".format(filename, url)) + + +def exists_args(args, get): + ''' + @name 检查参数是否存在 + @author hwliang<2021-06-08> + @param args 参数列表 允许是列表或字符串 + @param get 参数对像 + @return bool 都存在返回True,否则抛出KeyError异常 + ''' + if type(args) == str: + args = args.split(',') + for arg in args: + if not arg in get: + raise KeyError('Required parameters are missing:{}'.format(arg)) + return True + + +def get_error_info(): + import traceback + errorMsg = traceback.format_exc() + return errorMsg + + +def get_plugin_replace_rules(): + ''' + @name 获取插件文件内容替换规则 + @author hwliang<2021-06-28> + @return list + ''' + return [ + { + "find": "[PATH]", + "replace": "[PATH]" + } + ] + + +def get_plugin_title(plugin_name): + ''' + @name 获取插件标题 + @author hwliang<2021-06-24> + @param plugin_name 插件名称 + @return string + ''' + + info_file = '{}/{}/info.json'.format(get_plugin_path(), plugin_name) + try: + return json.loads(readFile(info_file))['title'] + except: + return plugin_name + + +def get_error_object(plugin_title=None, plugin_name=None): + ''' + @name 获取格式化错误响应对像 + @author hwliang<2021-06-21> + @return Resp + ''' + if not plugin_title: plugin_title = get_plugin_title(plugin_name) + try: + from BTPanel import request, Resp + is_cli = False + except: + is_cli = True + + if is_cli: + raise get_error_info() + ss = '''404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. + +During handling of the above exception, another exception occurred:''' + error_info = get_error_info().strip().split(ss)[-1].strip() + request_info = '''REQUEST_DATE: {request_date} + PAN_VERSION: {panel_version} + OS_VERSION: {os_version} + REMOTE_ADDR: {remote_addr} + REQUEST_URI: {method} {full_path} +REQUEST_FORM: {request_form} + USER_AGENT: {user_agent}'''.format( + request_date=getDate(), + remote_addr=GetClientIp(), + method=request.method, + full_path=url_encode(xsssec(request.full_path)), + request_form=xsssec(str(request.form.to_dict())), + user_agent=xsssec(request.headers.get('User-Agent')), + panel_version=version(), + os_version=get_os_version() + ) + + result = readFile('{}/BTPanel/templates/default/plugin_error.html'.format(get_panel_path())).format( + plugin_name=plugin_title, + request_info=request_info, + error_title=error_info.split("\n")[-1], + error_msg=error_info + ) + return Resp(result, 500) + + +# 搜索数据中是否存在 +def inArray(arrays, searchStr): + for key in arrays: + if key == searchStr: return True + + return False + + +# 格式化指定时间戳 +def format_date(format="%Y-%m-%d %H:%M:%S", times=None): + if not times: times = int(time.time()) + time_local = time.localtime(times) + return time.strftime(format, time_local) + + +# # 检查Web服务器配置文件是否有错误 +# def checkWebConfig(): +# f1 = '{}/'.format(get_vhost_path()) +# f2 = '{}/'.format(get_plugin_path()) +# setup_path = get_setup_path() +# if not os.path.exists(f2 + 'btwaf'): +# f3 = f1 + 'nginx/btwaf.conf' +# if os.path.exists(f3): os.remove(f3) +# # if not os.path.exists(f2 + 'btwaf_httpd'): +# # f3 = f1 + 'apache/btwaf.conf' +# # if os.path.exists(f3): os.remove(f3) +# +# if not os.path.exists(f2 + 'total'): +# f3 = f1 + 'apache/total.conf' +# if os.path.exists(f3): os.remove(f3) +# f3 = f1 + 'nginx/total.conf' +# if os.path.exists(f3): os.remove(f3) +# else: +# if os.path.exists(setup_path + '/apache/modules/mod_lua.so'): +# writeFile(f1 + 'apache/btwaf.conf', 'LoadModule lua_module modules/mod_lua.so') +# writeFile(f1 + 'apache/total.conf', 'LuaHookLog {}/total/httpd_log.lua run_logs'.format(setup_path)) +# else: +# f3 = f1 + 'apache/total.conf' +# if os.path.exists(f3): os.remove(f3) +# +# if get_webserver() == 'nginx': +# result = ExecShell( +# "ulimit -n 8192 ; {setup_path}/nginx/sbin/nginx -t -c {setup_path}/nginx/conf/nginx.conf".format( +# setup_path=setup_path)) +# searchStr = 'successful' +# elif get_webserver() == 'apache': +# # else: +# result = ExecShell("ulimit -n 8192 ; {setup_path}/apache/bin/apachectl -t".format(setup_path=setup_path)) +# searchStr = 'Syntax OK' +# else: +# result = ["1", "1"] +# searchStr = "1" +# if result[1].find(searchStr) == -1: +# WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR', (result[1],)) +# return result[1] +# return True + + +# 获取nginx版本,没有获取到版本时返回None,获取到时,返回一个3位长度的列表,如[1, 25, 1], 表示1.25.1版本 +def nginx_version(): + out, _ = ExecShell("/www/server/nginx/sbin/nginx -V 2>&1 | grep 'version'") + out: str = out.strip() + if not out: + return None + + rep_ver = re.compile(r"nginx\s+version.*/(?P\d+\.\d+(\.\d+)*)") + res = rep_ver.search(out) + if not res: + return None + ver = res.group("ver") + ver_list = [int(i) for i in ver.split(".")] + if len(ver_list) < 3: + ver_list.extend([0] * (3 - len(ver_list))) + if len(ver_list) > 3: + ver_list = ver_list[:3] + return ver_list + + +def is_change_nginx_http2() -> bool: + nginx_ver = nginx_version() + if not nginx_ver: + return False + + if nginx_ver >= [1, 25, 1]: + return True + + return False + + +def is_change_nginx_old_http2() -> bool: + nginx_ver = nginx_version() + if not nginx_ver: + return False + if nginx_ver < [1, 25, 1]: + return True + + return False + + +def is_nginx_http3(): + return ExecShell("nginx -V 2>&1| grep 'http_v3_module'")[0].strip() != '' + + +def remove_nginx_quic(): + nginx_file_path = "/www/server/panel/vhost/nginx" + for i in os.listdir(nginx_file_path): + if not i.endswith(".conf"): + continue + nginx_file = os.path.join(nginx_file_path, i) + remove_nginx_server_quic(nginx_file) + + +def remove_nginx_server_quic(nginx_file: str): + if not os.path.isfile(nginx_file): + return + data = ReadFile(nginx_file) + if not isinstance(data, str): + return + + rep_listen_quic = re.compile(r"\s*listen\s+.*quic;", re.M) + if not rep_listen_quic.search(data): + return + + new_conf = rep_listen_quic.sub('', data) + writeFile(nginx_file, new_conf) + + +def change_nginx_http2(): + import os + nginx_file_path = "/www/server/panel/vhost/nginx" + for i in os.listdir(nginx_file_path): + if not i.endswith(".conf"): + continue + nginx_file = os.path.join(nginx_file_path, i) + change_nginx_server_http2(nginx_file) + + +def change_nginx_server_http2(nginx_file: str): + if not os.path.isfile(nginx_file): + return + data = ReadFile(nginx_file) + rep_listen = re.compile(r"\s*listen\s+[\[\]:]*([0-9]+).*;[^\n]*\n", re.M) + + conf_list = [] + start_idx, last_listen_idx = 0, -1 + for tmp in rep_listen.finditer(data): + listen_str = tmp.group() + if "http2" in listen_str: + listen_str = listen_str.replace("http2", "") + last_listen_idx = len(conf_list) + 2 + + conf_list.append(data[start_idx:tmp.start()]) + conf_list.append(listen_str) + start_idx = tmp.end() + + conf_list.append(data[start_idx:]) + if last_listen_idx > 0: + conf_list.insert(last_listen_idx, " http2 on;\n") + + new_conf = "".join(conf_list) + writeFile(nginx_file, new_conf) + + +def is_change_nginx_old_http2() -> bool: + nginx_ver = nginx_version() + if not nginx_ver: + return False + if nginx_ver < [1, 25, 1]: + return True + + return False + + +def change_nginx_old_http2(): + nginx_file_path = "/www/server/panel/vhost/nginx" + for i in os.listdir(nginx_file_path): + if not i.endswith(".conf"): + continue + nginx_file = os.path.join(nginx_file_path, i) + change_nginx_server_old_http2(nginx_file) + + +def read_file_lines_range(filename, start_line: int, end_line: int): + """ + 读取文件指定行数范围内容 + + Args: + filename: 文件名 + start_line: 开始行号 + end_line: 结束行号 + + Returns: + list: 指定行数范围内容列表 + """ + try: + with open(filename, 'r') as f: + lines = f.readlines() + return "".join(lines[start_line:end_line]) + except: + return "获取文件内容报错了" + + +def change_nginx_server_old_http2(nginx_file: str): + if not os.path.isfile(nginx_file): + return + data = ReadFile(nginx_file) + if not isinstance(data, str): + return + + rep_http2_on = re.compile(r"\s*http2\s+on;[^\n]*\n", re.M) + if not rep_http2_on.search(data): + return + else: + data = rep_http2_on.sub("\n", data) + + rep_listen = re.compile(r"\s*listen\s+[\[\]:]*443.*;[^\n]*\n", re.M) + conf_list = [] + start_idx = 0 + for tmp in rep_listen.finditer(data): + listen_str = tmp.group() + conf_list.append(data[start_idx:tmp.start()]) + conf_list.append(listen_str.replace(";", " http2;")) + start_idx = tmp.end() + + conf_list.append(data[start_idx:]) + new_conf = "".join(conf_list) + writeFile(nginx_file, new_conf) + + +# 错误收集适配 +# 检查Web服务器配置文件是否有错误 +def checkWebConfig(repair_num=2): + f1 = '{}/'.format(get_vhost_path()) + f2 = '{}/'.format(get_plugin_path()) + setup_path = get_setup_path() + if not os.path.exists(f2 + 'btwaf'): + f3 = f1 + 'nginx/btwaf.conf' + if os.path.exists(f3): os.remove(f3) + # if not os.path.exists(f2 + 'btwaf_httpd'): + # f3 = f1 + 'apache/btwaf.conf' + # if os.path.exists(f3): os.remove(f3) + + if not os.path.exists(f2 + 'total'): + f3 = f1 + 'apache/total.conf' + if os.path.exists(f3): os.remove(f3) + f3 = f1 + 'nginx/total.conf' + if os.path.exists(f3): os.remove(f3) + else: + if os.path.exists(setup_path + '/apache/modules/mod_lua.so'): + writeFile(f1 + 'apache/btwaf.conf', 'LoadModule lua_module modules/mod_lua.so') + writeFile(f1 + 'apache/total.conf', 'LuaHookLog {}/total/httpd_log.lua run_logs'.format(setup_path)) + else: + f3 = f1 + 'apache/total.conf' + if os.path.exists(f3): os.remove(f3) + + web_s = get_webserver() + if web_s == 'nginx': + result = ExecShell( + "ulimit -n 8192 ; {setup_path}/nginx/sbin/nginx -t -c {setup_path}/nginx/conf/nginx.conf".format( + setup_path=setup_path)) + writeFile('/tmp/nginx_new.conf', readFile('/www/server/nginx/conf/nginx.conf')) + print_log('checkWebConfig--result:{}'.format(result)) + searchStr = 'successful' + nginx_version = ExecShell("{}/nginx/sbin/nginx -v".format(setup_path)) + print_log('nginx') + version_info = nginx_version[1] + elif web_s == 'apache': + print_log('apache') + # else: + result = ExecShell("ulimit -n 8192 ; {setup_path}/apache/bin/apachectl -t".format(setup_path=setup_path)) + searchStr = 'Syntax OK' + apache_version = ExecShell("{}/apache/bin/httpd -v".format(setup_path)) + version_info = apache_version[1] + else: + print_log('other') + result = ["1", "1"] + searchStr = "1" + version_info = "Unknow" + print_log('checkWebConfig--result1:{}'.format(result)) + if result[1].find( + 'the "listen ... http2" directive is deprecated, use the "http2" directive instead') != -1 and web_s == "nginx" and is_change_nginx_http2(): + if repair_num > 0: + repair_num -= 1 + change_nginx_http2() + print_log('nginx----1') + return checkWebConfig(repair_num) + + if result[1].find(searchStr) == -1: + if result[1].find( + '[emerg] unknown directive "http2"') != -1 and web_s == "nginx" and is_change_nginx_old_http2(): + if repair_num > 0: + repair_num -= 1 + change_nginx_old_http2() + print_log('nginx----2') + return checkWebConfig(repair_num) + + if result[1].find('[emerg] invalid parameter "quic" in') != -1 and web_s == "nginx" and not is_nginx_http3(): + if repair_num > 0: + repair_num -= 1 + remove_nginx_quic() + print_log('nginx----3') + return checkWebConfig(repair_num) + + WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR', (result[1],)) + try: + match = re.search(r"in (.*):(\d+)", result[1]) + if match: + err_infos = read_file_lines_range(match.group(1), int(match.group(2)) - 5, + int(match.group(2)) + 5) if int( + match.group(2)) >= 5 else read_file_lines_range(match.group(1), 1, int(match.group(2))) + err_collect("{} \n 报错信息: \n {} \n 版本信息:{} \n报错文件路径:{}:{}".format( + err_infos, result[1], version_info, match.group(1), match.group(2)), + 0, result[1].split("\n")[0].strip()) + except Exception as e: + err_collect(result[1], 0, result[1].split("\n")[0].strip()) + print_log('nginx----4') + return result[1] + print_log('nginx----5') + return True + + +def err_collect(error_info, type, error_id): + ''' + @error_info 错误信息 + @type 错误类型 + @error_id 错误ID + ''' + + from flask import redirect, request, Response + _form = request.form.to_dict() + if 'username' in _form: _form['username'] = '******' + if 'password' in _form: _form['password'] = '******' + if 'phone' in _form: _form['phone'] = '******' + + # 错误信息 + error_infos = { + "REQUEST_DATE": getDate(), # 请求时间 + "PANEL_VERSION": version(), # 面板版本 + "OS_VERSION": get_os_version(), # 操作系统版本 + "REMOTE_ADDR": GetClientIp(), # 请求IP + "REQUEST_URI": request.method + request.full_path, # 请求URI + "REQUEST_FORM": xsssec(str(_form)), # 请求表单 + "USER_AGENT": xsssec(request.headers.get('User-Agent')), # 客户端连接信息 + "ERROR_INFO": error_info, # 错误信息 + "PACK_TIME": readFile("/www/server/panel/config/update_time.pl") if os.path.exists( + "/www/server/panel/config/update_time.pl") else getDate(), # 打包时间 + "TYPE": type, + "ERROR_ID": error_id, + } + pkey = Md5(error_infos["ERROR_ID"]) + + # 提交异常报告 + if not cache_get(pkey): + try: + run_thread(httpPost, ("https://geterror.aapanel.com/bt_error/index.php", error_infos)) + cache_set(pkey, 1, 1800) + except Exception as e: + pass # 错误信息 + + +############################### 错误收集适配 ^^上方 + + +# 检查是否为IPv4地址 +def checkIp(ip): + if match_ipv4.match(ip): + return True + else: + return False + + +# 检查端口是否合法 +def checkPort(port): + if not is_number(port): return False + ports = ['21', '25', '443', '8080', '888', '8888', '8443', '7800'] + if port in ports: return False + intport = int(port) + if intport < 1 or intport > 65535: return False + return True + + +# 字符串取中间 +def getStrBetween(startStr, endStr, srcStr): + start = srcStr.find(startStr) + if start == -1: return None + end = srcStr.find(endStr) + if end == -1: return None + return srcStr[start + 1:end] + + +# 取CPU类型 +def getCpuType(): + cpuinfo = open('/proc/cpuinfo', 'r').read() + rep = r"model\s+name\s+:\s+(.+)" + tmp = re.search(rep, cpuinfo, re.I) + cpuType = '' + if tmp: + cpuType = tmp.groups()[0] + else: + cpuinfo = ExecShell('LANG="en_US.UTF-8" && lscpu')[0] + rep = r"Model\s+name:\s+(.+)" + tmp = re.search(rep, cpuinfo, re.I) + if tmp: cpuType = tmp.groups()[0] + return cpuType + + +# 检查是否允许重启 +def IsRestart(): + num = M('tasks').where('status!=?', ('1',)).count() + if num > 0: return False + return True + + +# 加密密码字符 +def hasPwd(password): + import crypt + return crypt.crypt(password, password) + + +def getDate(format='%Y-%m-%d %X'): + # 取格式时间 + return time.strftime(format, time.localtime()) + + +# 处理MySQL配置文件 +def CheckMyCnf(): + import os + confFile = '/etc/my.cnf' + if os.path.exists(confFile): + conf = readFile(confFile) + if conf.find('[mysqld]') != -1: return True + versionFile = get_setup_path() + '/mysql/version.pl' + if not os.path.exists(versionFile): return False + + versions = ['5.1', '5.5', '5.6', '5.7', '8.0', 'AliSQL'] + version = readFile(versionFile) + for key in versions: + if key in version: + version = key + break + + shellStr = ''' +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH + +# CF='node.aapanel.com' +# HK='download.bt.cn' +# HK2='103.224.251.67' +# US='128.1.164.196' +# sleep 0.5; +# CN_PING=`ping -c 1 -w 1 $CF|grep time=|awk '{print $7}'|sed "s/time=//"` +# HK_PING=`ping -c 1 -w 1 $HK|grep time=|awk '{print $7}'|sed "s/time=//"` +# HK2_PING=`ping -c 1 -w 1 $HK2|grep time=|awk '{print $7}'|sed "s/time=//"` +# US_PING=`ping -c 1 -w 1 $US|grep time=|awk '{print $7}'|sed "s/time=//"` +# +# echo "$HK_PING $HK" > ping.pl +# echo "$HK2_PING $HK2" >> ping.pl +# echo "$US_PING $US" >> ping.pl +# echo "$CF_PING $CF" >> ping.pl +# nodeAddr=`sort -V ping.pl|sed -n '1p'|awk '{print $2}'` +# if [ "$nodeAddr" == "" ];then +# nodeAddr=$CF +# fi + +Download_Url=https://node.aapanel.com + + +MySQL_Opt() +{ + MemTotal=`free -m | grep Mem | awk '{print $2}'` + if [[ ${MemTotal} -gt 1024 && ${MemTotal} -lt 2048 ]]; then + sed -i "s#^key_buffer_size.*#key_buffer_size = 32M#" /etc/my.cnf + sed -i "s#^table_open_cache.*#table_open_cache = 128#" /etc/my.cnf + sed -i "s#^sort_buffer_size.*#sort_buffer_size = 768K#" /etc/my.cnf + sed -i "s#^read_buffer_size.*#read_buffer_size = 768K#" /etc/my.cnf + sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 8M#" /etc/my.cnf + sed -i "s#^thread_cache_size.*#thread_cache_size = 16#" /etc/my.cnf + sed -i "s#^query_cache_size.*#query_cache_size = 16M#" /etc/my.cnf + sed -i "s#^tmp_table_size.*#tmp_table_size = 32M#" /etc/my.cnf + sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 128M#" /etc/my.cnf + sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 32M#" /etc/my.cnf + elif [[ ${MemTotal} -ge 2048 && ${MemTotal} -lt 4096 ]]; then + sed -i "s#^key_buffer_size.*#key_buffer_size = 64M#" /etc/my.cnf + sed -i "s#^table_open_cache.*#table_open_cache = 256#" /etc/my.cnf + sed -i "s#^sort_buffer_size.*#sort_buffer_size = 1M#" /etc/my.cnf + sed -i "s#^read_buffer_size.*#read_buffer_size = 1M#" /etc/my.cnf + sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 16M#" /etc/my.cnf + sed -i "s#^thread_cache_size.*#thread_cache_size = 32#" /etc/my.cnf + sed -i "s#^query_cache_size.*#query_cache_size = 32M#" /etc/my.cnf + sed -i "s#^tmp_table_size.*#tmp_table_size = 64M#" /etc/my.cnf + sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 256M#" /etc/my.cnf + sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 64M#" /etc/my.cnf + elif [[ ${MemTotal} -ge 4096 && ${MemTotal} -lt 8192 ]]; then + sed -i "s#^key_buffer_size.*#key_buffer_size = 128M#" /etc/my.cnf + sed -i "s#^table_open_cache.*#table_open_cache = 512#" /etc/my.cnf + sed -i "s#^sort_buffer_size.*#sort_buffer_size = 2M#" /etc/my.cnf + sed -i "s#^read_buffer_size.*#read_buffer_size = 2M#" /etc/my.cnf + sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 32M#" /etc/my.cnf + sed -i "s#^thread_cache_size.*#thread_cache_size = 64#" /etc/my.cnf + sed -i "s#^query_cache_size.*#query_cache_size = 64M#" /etc/my.cnf + sed -i "s#^tmp_table_size.*#tmp_table_size = 64M#" /etc/my.cnf + sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 512M#" /etc/my.cnf + sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 128M#" /etc/my.cnf + elif [[ ${MemTotal} -ge 8192 && ${MemTotal} -lt 16384 ]]; then + sed -i "s#^key_buffer_size.*#key_buffer_size = 256M#" /etc/my.cnf + sed -i "s#^table_open_cache.*#table_open_cache = 1024#" /etc/my.cnf + sed -i "s#^sort_buffer_size.*#sort_buffer_size = 4M#" /etc/my.cnf + sed -i "s#^read_buffer_size.*#read_buffer_size = 4M#" /etc/my.cnf + sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 64M#" /etc/my.cnf + sed -i "s#^thread_cache_size.*#thread_cache_size = 128#" /etc/my.cnf + sed -i "s#^query_cache_size.*#query_cache_size = 128M#" /etc/my.cnf + sed -i "s#^tmp_table_size.*#tmp_table_size = 128M#" /etc/my.cnf + sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 1024M#" /etc/my.cnf + sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 256M#" /etc/my.cnf + elif [[ ${MemTotal} -ge 16384 && ${MemTotal} -lt 32768 ]]; then + sed -i "s#^key_buffer_size.*#key_buffer_size = 512M#" /etc/my.cnf + sed -i "s#^table_open_cache.*#table_open_cache = 2048#" /etc/my.cnf + sed -i "s#^sort_buffer_size.*#sort_buffer_size = 8M#" /etc/my.cnf + sed -i "s#^read_buffer_size.*#read_buffer_size = 8M#" /etc/my.cnf + sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 128M#" /etc/my.cnf + sed -i "s#^thread_cache_size.*#thread_cache_size = 256#" /etc/my.cnf + sed -i "s#^query_cache_size.*#query_cache_size = 256M#" /etc/my.cnf + sed -i "s#^tmp_table_size.*#tmp_table_size = 256M#" /etc/my.cnf + sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 2048M#" /etc/my.cnf + sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 512M#" /etc/my.cnf + elif [[ ${MemTotal} -ge 32768 ]]; then + sed -i "s#^key_buffer_size.*#key_buffer_size = 1024M#" /etc/my.cnf + sed -i "s#^table_open_cache.*#table_open_cache = 4096#" /etc/my.cnf + sed -i "s#^sort_buffer_size.*#sort_buffer_size = 16M#" /etc/my.cnf + sed -i "s#^read_buffer_size.*#read_buffer_size = 16M#" /etc/my.cnf + sed -i "s#^myisam_sort_buffer_size.*#myisam_sort_buffer_size = 256M#" /etc/my.cnf + sed -i "s#^thread_cache_size.*#thread_cache_size = 512#" /etc/my.cnf + sed -i "s#^query_cache_size.*#query_cache_size = 512M#" /etc/my.cnf + sed -i "s#^tmp_table_size.*#tmp_table_size = 512M#" /etc/my.cnf + sed -i "s#^innodb_buffer_pool_size.*#innodb_buffer_pool_size = 4096M#" /etc/my.cnf + sed -i "s#^innodb_log_file_size.*#innodb_log_file_size = 1024M#" /etc/my.cnf + fi +} + +wget -O /etc/my.cnf $Download_Url/install/conf/mysql-%s.conf -T 5 +chmod 644 /etc/my.cnf +MySQL_Opt +''' % (version,) + ExecShell(shellStr) + # 判断是否迁移目录 + if os.path.exists('data/datadir.pl'): + newPath = readFile('data/datadir.pl') + if os.path.exists(newPath): + mycnf = readFile('/etc/my.cnf') + mycnf = mycnf.replace('/www/server/data', newPath) + writeFile('/etc/my.cnf', mycnf) + WriteLog('TYPE_SOFE', 'MYSQL_CHECK_ERR') + return True + + +def GetSSHPort(): + try: + file = '/etc/ssh/sshd_config' + conf = ReadFile(file) + rep = r"#*Port\s+([0-9]+)\s*\n" + port = re.search(rep, conf).groups(0)[0] + return int(port) + except: + return 22 + + +def get_sshd_port(): + ''' + @name 获取sshd端口 + @author hwliang + @return int + ''' + # 先尝试从进程中获取当前实际的监听端口 + sshd_port = 22 + is_ok = 0 + pid = get_sshd_pid_of_pidfile() + if not pid: pid = get_sshd_pid_of_binfile() + if pid: + try: + import psutil + p = psutil.Process(pid) + for conn in p.connections(): + if conn.status == 'LISTEN': + sshd_port = conn.laddr[1] + is_ok = 1 + break + except: + pass + + # 如果从进程获取失败,则尝试从配置文件获取 + if not is_ok: sshd_port = GetSSHPort() + + return sshd_port + + +def get_sshd_pid_of_pidfile(): + ''' + @name 通过PID文件获取SSH状态 + @author hwliang + @return int 0:关闭 pid:开启 + ''' + sshd_pid_list = ['/run/sshd.pid', '/var/run/sshd.pid', '/run/ssh.pid', '/var/run/ssh.pid'] + sshd_pid_file = None + for spid_file in sshd_pid_list: + if os.path.exists(spid_file): + sshd_pid_file = spid_file + break + + if sshd_pid_file: + sshd_pid = readFile(sshd_pid_file) + if not sshd_pid: return 0 + try: + sshd_pid = int(sshd_pid) + if not sshd_pid: return 0 + if pid_exists(sshd_pid): + return sshd_pid + except: + pass + return 0 + + +def get_sshd_pid_of_binfile(): + ''' + @name 通过执行文件获取SSH状态 + @author hwliang + @return int 进程pid + ''' + sshd_bin_list = ['/usr/sbin/sshd', '/usr/bin/sshd', '/usr/sbin/ssh', '/usr/bin/ssh'] + sshd_bin = None + pid = 0 + for sbin in sshd_bin_list: + if os.path.exists(sbin): + sshd_bin = sbin + break + + if sshd_bin: + pid = get_process_pid(sshd_bin.split('/')[-1], sshd_bin, '-D') + + return pid + + +def GetSSHStatus(): + ''' + @name 获取SSH状态 + @author hwliang + @return bool + ''' + if get_sshd_pid_of_pidfile(): + return True + elif get_sshd_pid_of_binfile(): + return True + return False + + +def get_sshd_status(): + ''' + @name 获取SSH状态 + @author hwliang + @return bool + ''' + return GetSSHStatus() + + +# 检查端口是否合法 +def CheckPort(port, other=None): + if type(port) == str: port = int(port) + if port < 1 or port > 65535: return False + if other: + checks = [22, 20, 21, 8888, 3306, 11211, 888, 25, 7800] + if port in checks: return False + return True + + +# 获取Token +def GetToken(): + try: + from json import loads + tokenFile = 'data/token.json' + if not os.path.exists(tokenFile): return False + token = loads(readFile(tokenFile)) + return token + except: + return False + + +def to_btint(string): + m_list = [] + for s in string: + m_list.append(ord(s)) + return m_list + + +def load_module(pluginCode): + from imp import new_module + from BTPanel import cache + p_tk = 'data/%s' % md5(pluginCode + get_uuid()) + pluginInfo = None + skey = md5(pluginCode + 'code') + if cache: pluginInfo = cache.get(skey) + if not pluginInfo: + import panelAuth + pdata = panelAuth.panelAuth().create_serverid(None) + pdata['pid'] = pluginCode + url = GetConfigValue('home') + '/api/panel/get_py_module' + pluginTmp = httpPost(url, pdata) + try: + pluginInfo = json.loads(pluginTmp) + except: + if not os.path.exists(p_tk): return False + pluginInfo = json.loads(ReadFile(p_tk)) + if pluginInfo['status'] == False: return False + WriteFile(p_tk, json.dumps(pluginInfo)) + os.chmod(p_tk, 384) + if cache: cache.set(skey, pluginInfo, 1800) + + mod = sys.modules.setdefault(pluginCode, new_module(pluginCode)) + code = compile(pluginInfo['msg'].encode('utf-8'), pluginCode, 'exec') + mod.__file__ = pluginCode + mod.__package__ = '' + exec(code, mod.__dict__) + return mod + + +# 解密数据 +def auth_decode(data): + token = GetToken() + # 是否有生成Token + if not token: return returnMsg(False, 'REQUEST_ERR') + + # 校验access_key是否正确 + if token['access_key'] != data['btauth_key']: return returnMsg(False, 'REQUEST_ERR') + + # 解码数据 + import binascii, hashlib, urllib, hmac, json + tdata = binascii.unhexlify(data['data']) + + # 校验signature是否正确 + signature = binascii.hexlify(hmac.new(token['secret_key'], tdata, digestmod=hashlib.sha256).digest()) + if signature != data['signature']: return returnMsg(False, 'REQUEST_ERR') + + # 返回 + return json.loads(urllib.unquote(tdata)) + + +# 数据加密 +def auth_encode(data): + token = GetToken() + pdata = {} + + # 是否有生成Token + if not token: return returnMsg(False, 'REQUEST_ERR') + + # 生成signature + import binascii, hashlib, urllib, hmac, json + tdata = urllib.quote(json.dumps(data)) + # 公式 hex(hmac_sha256(data)) + pdata['signature'] = binascii.hexlify(hmac.new(token['secret_key'], tdata, digestmod=hashlib.sha256).digest()) + + # 加密数据 + pdata['btauth_key'] = token['access_key'] + pdata['data'] = binascii.hexlify(tdata) + pdata['timestamp'] = time.time() + + # 返回 + return pdata + + +# 检查Token +def checkToken(get): + tempFile = 'data/tempToken.json' + if not os.path.exists(tempFile): return False + import json, time + tempToken = json.loads(readFile(tempFile)) + if time.time() > tempToken['timeout']: return False + if get.token != tempToken['token']: return False + return True + + +# 获取识别码 +def get_uuid(): + import uuid + return uuid.UUID(int=uuid.getnode()).hex[-12:] + + +# 取计算机名 +def get_hostname(): + import socket + return socket.gethostname() + + +# 取mysql datadir +def get_datadir(): + mycnf_file = '/etc/my.cnf' + if not os.path.exists(mycnf_file): return '' + mycnf = readFile(mycnf_file) + import re + tmp = re.findall(r"datadir\s*=\s*(.+)", mycnf) + if not tmp: return '' + return tmp[0] + + +# 进程是否存在 +def process_exists(pname, exe=None, cmdline=None): + try: + import psutil + pids = psutil.pids() + for pid in pids: + try: + p = psutil.Process(pid) + if p.name() == pname: + if not exe and not cmdline: + return True + else: + if exe: + if p.exe() == exe: + return True + if cmdline: + if cmdline in p.cmdline(): return True + except: + pass + + return False + except: + return True + + +def get_process_pid(pname, exe=None, cmdline=None): + ''' + @name 通过进程名获取进程PID + @author hwliang + @param pname 进程名 + @param exe 进程路径 + @param cmdline 进程任意命令行参数 + @return int 返回进程PID + ''' + import psutil + pids = psutil.pids() + for pid in pids: + try: + p = psutil.Process(pid) + if p.name() == pname: + if not exe and not cmdline: + return pid + else: + if exe: + if p.exe() == exe: + if not cmdline: + return pid + return 0 + if cmdline: + if cmdline in p.cmdline(): return pid + except: + pass + return 0 + + +# pid是否存在 +def pid_exists(pid): + if os.path.exists('/proc/{}/exe'.format(pid)): + return True + return False + + +# 重启面板 +def restart_panel(): + import system + return system.system().ReWeb(None) + + +# 获取mac +def get_mac_address(): + import uuid + mac = uuid.UUID(int=uuid.getnode()).hex[-12:] + return ":".join([mac[e:e + 2] for e in range(0, 11, 2)]) + + +# 转码 +def to_string(lites): + if type(lites) != list: lites = [lites] + m_str = '' + for mu in lites: + if sys.version_info[0] == 2: + m_str += unichr(mu).encode('utf-8') + else: + m_str += chr(mu) + return m_str + + +# 解码 +def to_ord(string): + o = [] + for s in string: + o.append(ord(s)) + return o + + +# xss 防御 +def xssencode(text): + try: + from cgi import html + list = ['`', '~', '&', '#', '/', '*', '$', '@', '<', '>', '\"', '\'', ';', '%', ',', '.', '\\u'] + ret = [] + for i in text: + if i in list: + i = '' + ret.append(i) + str_convert = ''.join(ret) + text2 = html.escape(str_convert, quote=True) + return text2 + except: + return text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') + + +def html_decode(text): + ''' + @name HTML解码 + @author hwliang + @param text 要解码的HTML + @return string 返回解码后的HTML + ''' + try: + from cgi import html + text2 = html.unescape(text) + return text2 + except: + return text + + +def html_encode(text): + ''' + @name HTML编码 + @author hwliang + @param text 要编码的HTML + @return string 返回编码后的HTML + ''' + try: + from cgi import html + text2 = html.escape(text) + return text2 + except: + return text + + +# xss 防御 +def xsssec(text): + return text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') + + +# xss 防御 +def xsssec2(text): + return text.replace('<', '<').replace('>', '>') + + +# xss version +def xss_version(text): + try: + if not text or not isinstance(text, str): return text + text = text.strip() + list = ['`', '~', '&', '#', '/', '*', '$', '@', '<', '>', '\"', '\'', ';', '%', ',', '\\u'] + ret = [] + for i in text: + if i in list: + i = '' + ret.append(i) + str_convert = ''.join(ret) + return str_convert + except: + return text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') + + +# 获取数据库配置信息 +def get_mysql_info(): + data = {} + try: + CheckMyCnf() + myfile = '/etc/my.cnf' + mycnf = readFile(myfile) + rep = r"datadir\s*=\s*(.+)\n" + data['datadir'] = re.search(rep, mycnf).groups()[0] + rep = r"port\s*=\s*([0-9]+)\s*\n" + data['port'] = re.search(rep, mycnf).groups()[0] + except: + data['datadir'] = '/www/server/data' + data['port'] = '3306' + return data + + +# xss 防御 +def xssencode2(text): + try: + from cgi import html + text2 = html.escape(text, quote=True) + return text2 + except: + return text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') + + +# 取缓存 +def cache_get(key, default=None): + from BTPanel import cache + + res = cache.get(key) + + if res is None: + return default + + return res + + +def add_security_logs(type, log, is_ip=True): + try: + if is_ip: + from flask import request + log = GetClientIp() + ":" + str(request.environ.get('REMOTE_PORT')) + log + M('security').add('type,log,addtime', (type, log, time.strftime('%Y-%m-%d %X', time.localtime()))) + except: + pass + + +# 设置缓存 +def cache_set(key, value, timeout=None): + from BTPanel import cache + if value == 'check': + admin_path = "/www/server/panel/data/admin_path.pl" + path = ReadFile(admin_path) + if path and len(path) > 3: + if not cache.get(GetClientIp() + 'admin_path_info'): + add_security_logs("Security entrance correct", "Successfully accessed the security entrance") + cache.set(GetClientIp() + 'admin_path_info', 1, 60) + return cache.set(key, value, timeout) + + +# 删除缓存 +def cache_remove(key): + from BTPanel import cache + return cache.delete(key) + + +# 取session值 +def sess_get(key): + from BTPanel import session + if key in session: return session[key] + return None + + +# 设置或修改session值 +def sess_set(key, value): + from BTPanel import session + session[key] = value + return True + + +# 删除指定session值 +def sess_remove(key): + from BTPanel import session + if key in session: del (session[key]) + return True + + +# 构造分页 +def get_page(count, p=1, rows=12, callback='', result='1,2,3,4,5,8'): + import page + try: + from BTPanel import request + uri = url_encode(request.full_path) + except: + uri = '' + page = page.Page() + info = {'count': count, 'row': rows, 'p': p, 'return_js': callback, 'uri': uri} + data = {'page': page.GetPage(info, result), 'shift': str(page.SHIFT), 'row': str(page.ROW)} + return data + + +# 取面板版本 +def version(): + try: + comm = ReadFile('{}/common.py'.format(get_class_path())) + return re.search(r"g\.version\s*=\s*'(\d+\.\d+\.\d+)'", comm).groups()[0] + except: + return get_panel_version() + + +def get_panel_version(): + comm = ReadFile('{}/common.py'.format(get_class_path())) + s_key = 'g.version = ' + s_len = len(s_key) + s_leff = comm.find(s_key) + s_len + version = comm[s_leff:s_leff + 10].strip().strip("'") + return version + + +def get_os_version(): + ''' + @name 取操作系统版本 + @author hwliang<2021-08-07> + @return string + ''' + p_file = '/etc/.productinfo' + if os.path.exists(p_file): + s_tmp = readFile(p_file).split("\n") + if s_tmp[0].find('Kylin') != -1 and len(s_tmp) > 1: + version = s_tmp[0] + ' ' + s_tmp[1].split('/')[0].strip() + else: + version = readFile('/etc/redhat-release') + if not version: + version = readFile('/etc/issue').strip().split("\n")[0].replace('\\n', '').replace(r'\l', '').strip() + else: + version = version.replace('release ', '').replace('Linux', '').replace('(Core)', '').strip() + v_info = sys.version_info + try: + version = "{} {}(Py{}.{}.{})".format(version, os.uname().machine, v_info.major, v_info.minor, v_info.micro) + except: + version = "{} (Py{}.{}.{})".format(version, v_info.major, v_info.minor, v_info.micro) + return xsssec(version) + + +# 取文件或目录大小 +def get_path_size(path, exclude=[]): + """根据排除目录获取路径的总大小 + + :path 目标路径 + :exclude 排除路径单个字符串或者多个列表。匹配路径是基于path的相对路径,规则是 + tar命令的--exclude规则的子集。 + """ + import fnmatch + if not os.path.exists(path): return 0 + if os.path.isfile(path): return os.path.getsize(path) + if type(exclude) != type([]): + exclude = [exclude] + + path = path[0:-1] if path[-1] == "/" else path + path = os.path.normcase(path) + # print("path:"+ path) + # print("exclude:"+ str(exclude)) + _exclude = exclude[0:] + for i, e in enumerate(_exclude): + if not e.startswith(path): + basename = os.path.basename(path) + if not e.startswith(basename): + exclude.append(os.path.join(path, e)) + else: + new_exc = e.replace(basename + "/", "") + new_exc = os.path.join(path, new_exc) + exclude.append(new_exc) + + # print(exclude) + total_size = 0 + count = 0 + for root, dirs, files in os.walk(path, topdown=True): + # filter path + for exc in exclude: + for d in dirs: + sub_dir = os.path.normcase(root + os.path.sep + d) + if fnmatch.fnmatch(sub_dir, exc) or d == exc: + # print("排除目录:"+sub_dir) + dirs.remove(d) + count += 1 + for f in files: + to_exclude = False + count += 1 + filename = os.path.normcase(root + os.path.sep + f) + if not os.path.exists(filename): continue + if os.path.islink(filename): continue + # filter file + norm_filename = os.path.normcase(filename) + for fexc in exclude: + if fnmatch.fnmatch(norm_filename, fexc) or fexc == f: + to_exclude = True + # print("排除文件:"+norm_filename) + break + if to_exclude: + continue + total_size += os.path.getsize(filename) + return total_size + + +# 写关键请求日志 +def write_request_log(reques=None): + try: + from BTPanel import request, g, session + if session.get('debug') == 1: return + log_path = '{}/logs/request'.format(get_panel_path()) + log_file = getDate(format='%Y-%m-%d') + '.json' + if not os.path.exists(log_path): os.makedirs(log_path) + + log_data = [] + log_data.append(getDate()) + log_data.append(GetClientIp() + ':' + get_remote_port()) + log_data.append(request.method) + log_data.append(request.full_path) + log_data.append(request.headers.get('User-Agent')) + if request.method == 'POST': + args = request.form.to_dict() + for k in args.keys(): + if k.find('pass') != -1 or k.find('user') != -1: + args[k] = '******' + if len(args[k]) > 4096: + args[k] = args[k][0:1024] + " -- >4096" + log_data.append(str(args)) + else: + log_data.append('{}') + log_data.append(int((time.time() - g.request_time) * 1000)) + log_data.append(g.response.status_code) + log_data.append(g.response.content_length) + log_data.append(g.response.headers.get('Content-Type')) + log_data.append(request.headers.get('Host')) + log_data.append(str(reques)) + log_msg = json.dumps(log_data) + "\n" + WriteFile(log_path + '/' + log_file, log_msg, 'a+') + rep_sys_path() + except: + pass + + +# 重载模块 +def mod_reload(mode): + if not mode: return False + try: + if sys.version_info[0] == 2: + reload(mode) + else: + import imp + imp.reload(mode) + return True + except: + return False + + +# 设置权限 +def set_mode(filename, mode): + if not os.path.exists(filename): return False + mode = int(str(mode), 8) + try: + os.chmod(filename, mode) + except: + return False + return True + + +def create_linux_user(user, group): + ''' + @name 创建系统用户 + @author hwliang<2022-01-15> + @param user 用户名 + @param group 所属组 + @return bool + ''' + ExecShell("groupadd {}".format(group)) + ExecShell('useradd -s /sbin/nologin -g {} {}'.format(user, group)) + return True + + +# 设置用户组 +def set_own(filename, user, group=None): + if not os.path.exists(filename): return False + from pwd import getpwnam + try: + user_info = getpwnam(user) + user = user_info.pw_uid + if group: + user_info = getpwnam(group) + group = user_info.pw_gid + except: + if user == 'www': create_linux_user(user, group) + # 如果指定用户或组不存在,则使用www + try: + user_info = getpwnam('www') + except: + create_linux_user(user, group) + user_info = getpwnam('www') + user = user_info.pw_uid + group = user_info.pw_gid + os.chown(filename, user, group) + return True + + +# 校验路径安全 +def path_safe_check(path, force=True): + if len(path) > 256: return False + checks = ['..', './', '\\', '%', '$', '^', '&', '*', '~', '"', "'", ';', '|', '{', '}', '`'] + for c in checks: + if path.find(c) != -1: return False + if force: + if not match_safe_path.match(path): return False + return True + + +# 取数据库字符集 +def get_database_character(db_name): + try: + db_obj = get_mysql_obj(db_name) + tmp = db_obj.query("show create database `%s`" % db_name.strip()) + c_type = str(re.findall(r"SET\s+([\w\d-]+)\s", tmp[0][1])[0]) + c_types = ['utf8', 'utf-8', 'gbk', 'big5', 'utf8mb4'] + if not c_type.lower() in c_types: return 'utf8' + return c_type + except: + return 'utf8' + + +# 取mysql数据库对象 +def get_mysql_obj(db_name): + is_cloud_db = False + if db_name: + db_find = M('databases').where("name=?", db_name).find() + if db_find['sid']: + return get_mysql_obj_by_sid(db_find['sid']) + is_cloud_db = db_find['db_type'] in ['1', 1] + if is_cloud_db: + import db_mysql + db_obj = db_mysql.panelMysql() + conn_config = json.loads(db_find['conn_config']) + try: + db_obj = db_obj.set_host(conn_config['db_host'], conn_config['db_port'], conn_config['db_name'], + conn_config['db_user'], conn_config['db_password']) + except Exception as e: + raise PanelError(GetMySQLError(e)) + else: + import panelMysql + db_obj = panelMysql.panelMysql() + return db_obj + + +# 取mysql数据库对像 By sid +def get_mysql_obj_by_sid(sid=0, conn_config=None): + if sid in ['0', '']: sid = 0 + if sid: + if not conn_config: conn_config = M('database_servers').where("id=?", sid).find() + import db_mysql + db_obj = db_mysql.panelMysql() + try: + db_obj = db_obj.set_host(conn_config['db_host'], conn_config['db_port'], None, conn_config['db_user'], + conn_config['db_password']) + except Exception as e: + raise PanelError(GetMySQLError(e)) + else: + import panelMysql + db_obj = panelMysql.panelMysql() + return db_obj + + +def GetMySQLError(e): + res = '' + if e.args[0] == 1045: + res = get_msg_gettext('Database username or password is wrong!') + if e.args[0] == 1049: + res = get_msg_gettext('database does not exist!') + if e.args[0] == 1044: + res = get_msg_gettext('No permission, or the specified database does not exist!') + if e.args[0] == 1062: + res = get_msg_gettext('Database already exists!') + if e.args[0] == 1146: + res = get_msg_gettext('Table does not exist!') + if e.args[0] == 2003: + res = get_msg_gettext('Database server connection failed!') + if e.args[0] == 1142: + res = get_msg_gettext('Insufficient user rights!') + if res: + res = res + "
                                    " + str(e) + "
                                    " + else: + res = str(e) + return res + + +def get_database_codestr(codeing): + wheres = { + 'utf8': 'utf8_general_ci', + 'utf8mb4': 'utf8mb4_general_ci', + 'gbk': 'gbk_chinese_ci', + 'big5': 'big5_chinese_ci' + } + return wheres[codeing] + + +def get_database_size(name=None): + """ + @获取数据库大小 + """ + data = {} + try: + mysql_obj = get_mysql_obj(name) + tables = mysql_obj.query( + "select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES group by table_schema") + if type(tables) == list: + for x in tables: + if len(x) < 2: continue + if x[1] == None: continue + data[x[0]] = int(x[1]) + except: + return data + return data + + +def get_database_size_by_name(name): + """ + @获取数据库大小 + """ + data = 0 + try: + mysql_obj = get_mysql_obj(name) + tables = mysql_obj.query( + "select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES WHERE table_schema='{}' group by table_schema".format( + name)) + data = tables[0][1] + if not data: data = 0 + except: + return data + return data + + +def get_database_size_by_id(id): + """ + @获取数据库大小 + """ + data = 0 + try: + name = M('databases').where('id=?', id).getField('name') + mysql_obj = get_mysql_obj(name) + tables = mysql_obj.query( + "select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES WHERE table_schema='{}' group by table_schema".format( + name)) + data = tables[0][1] + if not data: data = 0 + except: + return data + return data + + +def en_punycode(domain): + if sys.version_info[0] == 2: + domain = domain.encode('utf8') + tmp = domain.split('.') + newdomain = '' + for dkey in tmp: + if dkey == '*': continue + # 匹配非ascii字符 + match = re.search(u"[\x80-\xff]+", dkey) + if not match: match = re.search(u"[\u4e00-\u9fa5]+", dkey) + if not match: + newdomain += dkey + '.' + else: + if sys.version_info[0] == 2: + newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' + else: + newdomain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' + if tmp[0] == '*': newdomain = "*." + newdomain + return newdomain[0:-1] + + +# punycode 转中文 +def de_punycode(domain): + tmp = domain.split('.') + newdomain = '' + for dkey in tmp: + if dkey.find('xn--') >= 0: + newdomain += dkey.replace('xn--', '').encode('utf-8').decode('punycode') + '.' + else: + newdomain += dkey + '.' + return newdomain[0:-1] + + +# 取计划任务文件路径 +def get_cron_path(): + u_file = '/var/spool/cron/crontabs/root' + if not os.path.exists(u_file): + file = '/var/spool/cron/root' + else: + file = u_file + return file + + +# 加密字符串 +def en_crypt(key, strings): + try: + if type(strings) != bytes: strings = strings.encode('utf-8') + from cryptography.fernet import Fernet + f = Fernet(key) + result = f.encrypt(strings) + return result.decode('utf-8') + except: + # print(get_error_info()) + return strings + + +# 解密字符串 +def de_crypt(key, strings): + try: + if type(strings) != bytes: strings = strings.decode('utf-8') + from cryptography.fernet import Fernet + f = Fernet(key) + result = f.decrypt(strings).decode('utf-8') + return result + except: + # print(get_error_info()) + return strings + + +# 获取IP限制列表 +def get_limit_ip(): + iplong_list = [] + try: + ip_file = 'data/limitip.conf' + if not os.path.exists(ip_file): return iplong_list + + from BTPanel import cache + ikey = 'limit_ip' + iplong_list = cache.get(ikey) + if iplong_list: return iplong_list + + iplong_list = [] + iplist = ReadFile(ip_file) + if not iplist: return iplong_list + iplist = iplist.strip() + for limit_ip in iplist.split(','): + if not limit_ip: continue + limit_ip = limit_ip.split('-') + iplong = {} + iplong['min'] = ip2long(limit_ip[0]) + if len(limit_ip) > 1: + iplong['max'] = ip2long(limit_ip[1]) + else: + iplong['max'] = iplong['min'] + iplong_list.append(iplong) + + cache.set(ikey, iplong_list, 3600) + except: + pass + return iplong_list + + +def is_api_limit_ip(ip_list, client_ip): + ''' + @name 判断IP是否在限制列表中 + @author hwliang<2022-02-10> + @param ip_list 限制IP列表 + @param client_ip 客户端IP + @return bool + ''' + iplong_list = [] + for limit_ip in ip_list: + if not limit_ip: continue + if limit_ip in ['*', 'all', '0.0.0.0', '0.0.0.0/0', '0.0.0.0/24', '0.0.0.0/32']: return True + limit_ip = limit_ip.split('-') + iplong = {} + iplong['min'] = ip2long(limit_ip[0]) + if len(limit_ip) > 1: + iplong['max'] = ip2long(limit_ip[1]) + else: + iplong['max'] = iplong['min'] + iplong_list.append(iplong) + + client_ip_long = ip2long(client_ip) + for limit_ip in iplong_list: + if client_ip_long >= limit_ip['min'] and client_ip_long <= limit_ip['max']: + return True + return False + + +# 检查IP白名单 +def check_ip_panel(): + iplong_list = get_limit_ip() + if not iplong_list: return False + client_ip = GetClientIp() + if client_ip in ['127.0.0.1', 'localhost', '::1']: return False + client_ip_long = ip2long(client_ip) + for limit_ip in iplong_list: + if client_ip_long >= limit_ip['min'] and client_ip_long <= limit_ip['max']: + return False + + # errorStr = ReadFile('./BTPanel/templates/' + GetConfigValue('template') + '/error2.html') + # try: + # errorStr = errorStr.format(getMsg('PAGE_ERR_TITLE'),getMsg('PAGE_ERR_IP_H1'),getMsg('PAGE_ERR_IP_P1',(GetClientIp(),)),getMsg('PAGE_ERR_IP_P2'),getMsg('PAGE_ERR_IP_P3'),getMsg('NAME'),getMsg('PAGE_ERR_HELP')) + # except IndexError:pass + # return error_not_login(errorStr,True) + return error_403(None) + + +# 检查面板域名 +def check_domain_panel(): + tmp = GetHost() + domain = ReadFile('data/domain.conf') + if domain: + client_ip = GetClientIp() + if client_ip in ['127.0.0.1', 'localhost', '::1']: return False + if tmp.strip().lower() != domain.strip().lower(): + if check_client_info(): + try: + from flask import render_template + return render_template('error2.html') + except: + pass + + return error_403(None) + return False + + +# 是否离线模式 +def is_local(): + s_file = '{}/data/not_network.pl'.format(get_panel_path()) + return os.path.exists(s_file) + + +# 自动备份面板数据 +def auto_backup_panel(): + try: + panel_paeh = get_panel_path() + paths = panel_paeh + '/data/not_auto_backup.pl' + if os.path.exists(paths): return False + b_path = '{}/panel'.format(get_backup_path()) + day_date = format_date('%Y-%m-%d') + backup_path = b_path + '/' + day_date + backup_file = backup_path + '.zip' + if os.path.exists(backup_path) or os.path.exists(backup_file): return True + ignore_default = '' + ignore_system = '' + max_size = 100 * 1024 * 1024 + if os.path.getsize('{}/data/default.db'.format(panel_paeh)) > max_size: + ignore_default = 'default.db' + if os.path.getsize('{}/data/system.db'.format(panel_paeh)) > max_size: + ignore_system = 'system.db' + os.makedirs(backup_path, 384) + import shutil + shutil.copytree(panel_paeh + '/data', backup_path + '/data', + ignore=shutil.ignore_patterns(ignore_system, ignore_default)) + shutil.copytree(panel_paeh + '/config', backup_path + '/config') + shutil.copytree(panel_paeh + '/vhost', backup_path + '/vhost') + ExecShell("cd {} && zip {} -r {}/".format(b_path, backup_file, day_date)) + ExecShell("chmod -R 600 {path};chown -R root.root {path}".format(path=backup_file)) + if os.path.exists(backup_path): shutil.rmtree(backup_path) + + time_now = time.time() - (86400 * 30) + for f in os.listdir(b_path): + if f.endswith(".zip") and time.mktime(time.strptime(f, "%Y-%m-%d.zip")) < time_now: + path = b_path + '/' + f + b_file = path # + '.zip' + # if os.path.exists(path): + # shutil.rmtree(path) + if os.path.exists(b_file): + os.remove(b_file) + set_php_cli_env() + except: + pass + + +def set_php_cli_env(): + ''' + @name 重新设置php-cli.ini配置 + ''' + import jobs + jobs.set_php_cli_env() + + +# 检查端口状态 +def check_port_stat(port, localIP='127.0.0.1'): + import socket + temp = {} + temp['port'] = port + temp['local'] = True + try: + s = socket.socket() + s.settimeout(0.15) + s.connect((localIP, port)) + s.close() + except: + temp['local'] = False + + result = 0 + if temp['local']: result += 2 + return result + + +# 同步时间 +def sync_date(): + tip_file = "/dev/shm/last_sync_time.pl" + s_time = int(time.time()) + try: + if os.path.exists(tip_file): + if s_time - int(readFile(tip_file)) < 60: return False + os.remove(tip_file) + time_str = HttpGet(GetConfigValue('home') + '/api/index/get_time') + new_time = int(time_str) + time_arr = time.localtime(new_time) + date_str = time.strftime("%Y-%m-%d %H:%M:%S", time_arr) + ExecShell('date -s "%s"' % date_str) + writeFile(tip_file, str(s_time)) + return True + except: + if os.path.exists(tip_file): os.remove(tip_file) + return False + + +# 重载模块 +def reload_mod(mod_name=None): + # 是否重载指定模块 + modules = [] + if mod_name: + if type(mod_name) == str: + mod_names = mod_name.split(',') + + for mod_name in mod_names: + if mod_name in sys.modules: + print(mod_name) + try: + if sys.version_info[0] == 2: + reload(sys.modules[mod_name]) + else: + importlib.reload(sys.modules[mod_name]) + modules.append([mod_name, True]) + except: + modules.append([mod_name, False]) + else: + modules.append([mod_name, False]) + return modules + + # 重载所有模块 + for mod_name in sys.modules.keys(): + if mod_name in ['BTPanel']: continue + f = getattr(sys.modules[mod_name], '__file__', None) + if f: + try: + if f.find('panel/') == -1: continue + if sys.version_info[0] == 2: + reload(sys.modules[mod_name]) + else: + importlib.reload(sys.modules[mod_name]) + modules.append([mod_name, True]) + except: + modules.append([mod_name, False]) + return modules + + +def de_hexb(data): + if sys.version_info[0] != 2: + if type(data) == str: data = data.encode('utf-8') + pdata = base64.b64encode(data) + if sys.version_info[0] != 2: + if type(pdata) == str: pdata = pdata.encode('utf-8') + return binascii.hexlify(pdata) + + +def en_hexb(data): + if sys.version_info[0] != 2: + if type(data) == str: data = data.encode('utf-8') + result = base64.b64decode(binascii.unhexlify(data)) + if type(result) != str: result = result.decode('utf-8') + return result + + +# def upload_file_url(filename): +# try: +# if os.path.exists(filename): +# data = ExecShell('/usr/bin/curl https://scanner.baidu.com/enqueue -F archive=@%s' % filename) +# data = json.loads(data[0]) +# time.sleep(1) +# import requests +# default_headers = { +# 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36' +# } +# data_list = requests.get(url=data['url'], headers=default_headers, verify=False) +# return (data_list.json()) +# else: +# return False +# except: +# return False + +# 直接请求到PHP-FPM +# version php版本 +# uri 请求uri +# filename 要执行的php文件 +# args 请求参数 +# method 请求方式 +def request_php(version, uri, document_root, method='GET', pdata=b''): + import panelPHP + if type(pdata) == dict: pdata = url_encode(pdata) + fpm_address = get_fpm_address(version) + p = panelPHP.FPM(fpm_address, document_root) + result = p.load_url_public(uri, pdata, method) + return result + + +def get_fpm_address(php_version, bind=False): + ''' + @name 获取FPM请求地址 + @author hwliang<2020-10-23> + @param php_version string PHP版本 + @return tuple or string + ''' + fpm_address = '/tmp/php-cgi-{}.sock'.format(php_version) + php_fpm_file = '{}/php/{}/etc/php-fpm.conf'.format(get_setup_path(), php_version) + try: + fpm_conf = readFile(php_fpm_file) + tmp = re.findall(r"listen\s*=\s*(.+)", fpm_conf) + if not tmp: return fpm_address + if tmp[0].find('sock') != -1: return fpm_address + if tmp[0].find(':') != -1: + listen_tmp = tmp[0].split(':') + if bind: + fpm_address = (listen_tmp[0], int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(listen_tmp[1])) + else: + fpm_address = ('127.0.0.1', int(tmp[0])) + return fpm_address + except: + return fpm_address + + +def get_php_proxy(php_version, webserver='nginx'): + ''' + @name 获取PHP代理地址 + @author hwliang<2020-10-24> + @param php_version string php版本 (52|53|54|55|56|70|71|72|73|74) + @param webserver string web服务器类型 (nginx|apache|ols) + return string + ''' + php_address = get_fpm_address(php_version) + if isinstance(php_address, str): + if webserver == 'nginx': + return 'unix:{}'.format(php_address) + elif webserver == 'apache': + return 'unix:{}|fcgi://localhost'.format(php_address) + else: + if webserver == 'nginx': + return '{}:{}'.format(php_address[0], php_address[1]) + elif webserver == 'apache': + return 'fcgi://{}:{}'.format(php_address[0], php_address[1]) + + +def get_php_version_conf(conf): + ''' + @name 从指定配置文件获取PHP版本 + @author hwliang<2020-10-24> + @param conf string 配置文件内容 + @return string + ''' + if not conf: return '00' + if conf.find('enable-php-') != -1: + rep = r"enable-php-(\w{2,5})[-\w]*\.conf" + tmp = re.findall(rep, conf) + if not tmp: return '00' + elif conf.find('/usr/local/lsws/lsphp') != -1: + rep = r"path\s*/usr/local/lsws/lsphp(\d+)/bin/lsphp" + tmp = re.findall(rep, conf) + if not tmp: return '00' + else: + rep = r"php-cgi-([0-9]{2,3})\.sock" + tmp = re.findall(rep, conf) + if not tmp: + rep = r'\d+\.\d+\.\d+\.\d+:10(\d{2,2})1' + tmp = re.findall(rep, conf) + if not tmp: + return '00' + return tmp[0] + + +def get_site_php_version(siteName): + ''' + @name 获取指定网站当前使用的PHP版本 + @author hwliang<2020-10-24> + @param siteName string 网站名称 + @return string + ''' + web_server = get_webserver() + vhost_path = get_vhost_path() + conf = readFile(vhost_path + '/' + web_server + '/' + siteName + '.conf') + if web_server == 'openlitespeed': + conf = readFile(vhost_path + '/' + web_server + '/detail/' + siteName + '.conf') + return get_php_version_conf(conf) + + +def check_tcp(ip, port): + ''' + @name 使用TCP的方式检测指定IP:端口是否能连接 + @author hwliang<2021-06-01> + @param ip IP地址 + @param port 端口 + @return bool + ''' + import socket + try: + s = socket.socket() + s.settimeout(5) + s.connect((ip.strip(), int(port))) + s.close() + except: + return False + return True + + +def sub_php_address(conf_file, rep, tsub, php_version): + ''' + @name 替换新的PHP配置到配置文件 + @author hwliang<2020-10-24> + @param conf_file string 配置文件全路径 + @param rep string 用于查找目标替换内容的正则表达式 + @param tsub string 新的内容 + @param php_version string 指定PHP版本 + @return bool + ''' + if not os.path.isfile(conf_file): return False + if not os.path.exists(conf_file): return False + conf = readFile(conf_file) + if not conf: return False + # if conf.find('#PHP') == -1 and conf.find('pathinfo.conf') == -1: return False + if conf_file.split('-')[-1].find(php_version + ".") != 0: + phpv = get_php_version_conf(conf) + if phpv != php_version: return False + + tmp = re.search(rep, conf) + if not tmp: return False + if tmp.group() == tsub: return False + conf = conf.replace(tmp.group(), tsub) # re.sub(rep,php_proxy,conf) + writeFile(conf_file, conf) + return True + + +def sync_all_address(): + ''' + @name 同步所有PHP版本配置到配置文件 + @author hwliang<2020-10-24> + @return void + ''' + php_versions = get_php_versions() + for phpv in php_versions: + sync_php_address(phpv) + + +def sync_php_address(php_version): + ''' + @name 同步PHP版本配置到所有配置文件 + @author hwliang<2020-10-24> + @param php_version string PHP版本 + @return void + ''' + if not os.path.exists('{}/php/{}/bin/php'.format(get_setup_path(), php_version)): # 指定PHP版本是否安装 + return False + ngx_rep = r"(unix:/tmp/php-cgi.*\.sock|\d+\.\d+\.\d+\.\d+:\d+)" + apa_rep = r"(unix:/tmp/php-cgi.*\.sock\|fcgi://localhost|fcgi://\d+\.\d+\.\d+\.\d+:\d+)" + ngx_proxy = get_php_proxy(php_version, 'nginx') + apa_proxy = get_php_proxy(php_version, 'apache') + is_write = False + + # nginx的PHP配置文件 + nginx_conf_path = '{}/nginx/conf'.format(get_setup_path()) + + if os.path.exists(nginx_conf_path): + for f_name in os.listdir(nginx_conf_path): + if f_name.find('enable-php') != -1: + conf_file = '/'.join((nginx_conf_path, f_name)) + if sub_php_address(conf_file, ngx_rep, ngx_proxy, php_version): + is_write = True + # nginx的phpmyadmin + # conf_file = '/www/server/nginx/conf/nginx.conf' + # if os.path.exists(conf_file): + # if sub_php_address(conf_file,ngx_rep,ngx_proxy,php_version): + # is_write = True + + # apache的网站配置文件 + apache_conf_path = '{}/apache'.format(get_vhost_path()) + if os.path.exists(apache_conf_path): + for f_name in os.listdir(apache_conf_path): + conf_file = '/'.join((apache_conf_path, f_name)) + if sub_php_address(conf_file, apa_rep, apa_proxy, php_version): + is_write = True + # apache的phpmyadmin + conf_file = '{}/apache/conf/extra/httpd-vhosts.conf'.format(get_setup_path()) + if os.path.exists(conf_file): + if sub_php_address(conf_file, apa_rep, apa_proxy, php_version): + is_write = True + + if is_write: serviceReload() + return True + + +def url_encode(data): + if type(data) != str: return data + if sys.version_info[0] != 2: + import urllib.parse + pdata = urllib.parse.quote(data) + else: + import urllib + pdata = urllib.urlencode(data) + return pdata + + +def url_decode(data): + if type(data) != str: return data + if sys.version_info[0] != 2: + import urllib.parse + pdata = urllib.parse.unquote(data) + else: + import urllib + pdata = urllib.urldecode(data) + return pdata + + +def unicode_encode(data): + try: + if sys.version_info[0] == 2: + result = unicode(data, errors='ignore') + else: + result = data.encode('utf8', errors='ignore') + return result + except: + return data + + +def unicode_decode(data, charset='utf8'): + try: + if sys.version_info[0] == 2: + result = unicode(data, errors='ignore') + else: + result = data.decode('utf8', errors='ignore') + return result + except: + return data + + +def import_cdn_plugin(): + plugin_path = 'plugin/static_cdn' + if not os.path.exists(plugin_path): return True + try: + import static_cdn_main + except: + package_path_append(plugin_path) + import static_cdn_main + + +def get_cdn_hosts(): + try: + if import_cdn_plugin(): return [] + import static_cdn_main + return static_cdn_main.static_cdn_main().get_hosts(None) + except: + return [] + + +def get_cdn_url(): + try: + if os.path.exists('plugin/static_cdn/not_open.pl'): + return False + from BTPanel import cache + cdn_url = cache.get('cdn_url') + if cdn_url: return cdn_url + if import_cdn_plugin(): return False + import static_cdn_main + cdn_url = static_cdn_main.static_cdn_main().get_url(None) + cache.set('cdn_url', cdn_url, 3) + return cdn_url + except: + return False + + +def set_cdn_url(cdn_url): + if not cdn_url: return False + import_cdn_plugin() + get = dict_obj() + get.cdn_url = cdn_url + import static_cdn_main + static_cdn_main.static_cdn_main().set_url(get) + return True + + +def get_python_bin(): + bin_file = '{}/pyenv/bin/python3'.format(get_panel_path()) + if os.path.exists(bin_file): + return bin_file + return '/usr/bin/python' + + +def get_pip_bin(): + bin_file = '{}/pyenv/bin/pip'.format(get_panel_path()) + if os.path.exists(bin_file): + return bin_file + return '/usr/bin/pip' + + +def aes_encrypt(data, key): + import panelAes + if sys.version_info[0] == 2: + aes_obj = panelAes.aescrypt_py2(key) + return aes_obj.aesencrypt(data) + else: + aes_obj = panelAes.aescrypt_py3(key) + return aes_obj.aesencrypt(data) + + +def aes_decrypt(data, key): + import panelAes + if sys.version_info[0] == 2: + aes_obj = panelAes.aescrypt_py2(key) + return aes_obj.aesdecrypt(data) + else: + aes_obj = panelAes.aescrypt_py3(key) + return aes_obj.aesdecrypt(data) + + +# 清理大日志文件 +def clean_max_log(log_file, max_size=100, old_line=100): + if not os.path.exists(log_file): return False + max_size = 1024 * 1024 * max_size + if os.path.getsize(log_file) > max_size: + try: + old_body = GetNumLines(log_file, old_line) + writeFile(log_file, old_body) + except: + print(get_error_info()) + + +# 获取证书哈希 +def get_cert_data(path): + import panelSSL + get = dict_obj() + get.certPath = path + data = panelSSL.panelSSL().GetCertName(get) + return data + + +# 获取系统发行版 +def get_linux_distribution(): + distribution = 'ubuntu' + redhat_file = '/etc/redhat-release' + if os.path.exists(redhat_file): + try: + tmp = readFile(redhat_file).split()[3][0] + distribution = 'centos{}'.format(tmp) + except: + distribution = 'centos7' + return distribution + + +def long2ip(ips): + ''' + @name 将整数转换为IP地址 + @author hwliang<2020-06-11> + @param ips string(ip地址整数) + @return ipv4 + ''' + i1 = int(ips / (2 ** 24)) + i2 = int((ips - i1 * (2 ** 24)) / (2 ** 16)) + i3 = int(((ips - i1 * (2 ** 24)) - i2 * (2 ** 16)) / (2 ** 8)) + i4 = int(((ips - i1 * (2 ** 24)) - i2 * (2 ** 16)) - i3 * (2 ** 8)) + return "{}.{}.{}.{}".format(i1, i2, i3, i4) + + +def ip2long(ip): + ''' + @name 将IP地址转换为整数 + @author hwliang<2020-06-11> + @param ip string(ipv4) + @return long + ''' + ips = ip.split('.') + if len(ips) != 4: return 0 + iplong = 2 ** 24 * int(ips[0]) + 2 ** 16 * int(ips[1]) + 2 ** 8 * int(ips[2]) + int(ips[3]) + return iplong + + +def is_local_ip(ip): + ''' + @name 判断是否为本地(内网)IP地址 + @author hwliang<2021-03-26> + @param ip string(ipv4) + @return bool + ''' + patt = r"^(192\.168|127|10|172\.(16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))\." + if re.match(patt, ip): return True + return False + + +# 获取debug日志 +def get_debug_log(): + from BTPanel import request + return GetClientIp() + ':' + str(request.environ.get('REMOTE_PORT')) + '|' + str( + int(time.time())) + '|' + get_error_info() + + +# 获取sessionid +def get_session_id(): + from BTPanel import request, app + session_id = request.cookies.get(app.config['SESSION_COOKIE_NAME'], '') + if not re.findall(r"^([\w\.-]{64,64})$", session_id): + return GetRandomString(64) + return session_id + + +# 尝试自动恢复面板数据库 +def rep_default_db(): + db_path = '{}/data/'.format(get_panel_path()) + db_file = db_path + 'default.db' + db_tmp_backup = db_path + 'default_' + format_date("%Y%m%d_%H%M%S") + ".db" + + panel_backup = '{}/panel'.format(get_backup_path()) + bak_list = os.listdir(panel_backup) + if not bak_list: return False + bak_list = sorted(bak_list, reverse=True) + db_bak_file = '' + for d_name in bak_list: + db_bak_file = panel_backup + '/' + d_name + '/data/default.db' + if not os.path.exists(db_bak_file): continue + if os.path.getsize(db_bak_file) < 17408: continue + break + + if not db_bak_file: return False + ExecShell(r"\cp -arf {} {}".format(db_file, db_tmp_backup)) + ExecShell(r"\cp -arf {} {}".format(db_bak_file, db_file)) + return True + + +def chdck_salt(): + ''' + @name 检查所有用户密码是否加盐,若没有则自动加上 + @author hwliang<2020-07-08> + @return void + ''' + + if not M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'users', '%salt%')).count(): + M('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT", ()) + u_list = M('users').where('salt is NULL', ()).field('id,username,password,salt').select() + if isinstance(u_list, str): + if u_list.find('no such table: users') != -1: + rep_default_db() + if not M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'users', '%salt%')).count(): + M('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT", ()) + u_list = M('users').where('salt is NULL', ()).field('id,username,password,salt').select() + + for u_info in u_list: + salt = GetRandomString(12) # 12位随机 + pdata = {} + pdata['password'] = md5(md5(u_info['password'] + '_bt.cn') + salt) + pdata['salt'] = salt + M('users').where('id=?', (u_info['id'],)).update(pdata) + + +def get_login_token(): + token_s = readFile('{}/data/login_token.pl'.format(get_panel_path())) + if not token_s: return GetRandomString(32) + return token_s + + +def get_sess_key(): + return md5(get_login_token() + get_csrf_sess_html_token_value()) + + +def password_salt(password, username=None, uid=None): + ''' + @name 为指定密码加盐 + @author hwliang<2020-07-08> + @param password string(被md5加密一次的密码) + @param username string(用户名) 可选 + @param uid int(uid) 可选 + @return string + ''' + chdck_salt() + if not uid: + if not username: + raise Exception('username或uid必需传一项') + uid = M('users').where('username=?', (username,)).getField('id') + salt = M('users').where('id=?', (uid,)).getField('salt') + return md5(md5(password + '_bt.cn') + salt) + + +# 备份配置文件 +def back_file(file, act=None): + """ + @name 备份配置文件 + @author zhwen + @param file 需要备份的文件 + @param act 如果存在,则备份一份作为默认配置 + """ + file_type = "_bak" + if act: + file_type = "_def" + ExecShell("/usr/bin/cp -p {0} {1}".format(file, file + file_type)) + + +# 还原配置文件 +def restore_file(file, act=None): + """ + @name 还原配置文件 + @author zhwen + @param file 需要还原的文件 + @param act 如果存在,则还原默认配置 + """ + file_type = "_bak" + if act: + file_type = "_def" + ExecShell("/usr/bin/cp -p {1} {0}".format(file, file + file_type)) + + +def package_path_append(path): + if not path in sys.path: + sys.path.insert(0, path) + + +def rep_sys_path(): + sys_path = [] + for p in sys.path: + if p in sys_path: continue + sys_path.append(p) + sys.path = sys_path + + +def get_ssh_port(): + ''' + @name 获取本机SSH端口 + @author hwliang<2020-08-07> + @return int + ''' + s_file = '/etc/ssh/sshd_config' + conf = readFile(s_file) + if not conf: conf = '' + port_all = re.findall(r".*Port\s+[0-9]+", conf) + ssh_port = 22 + for p in port_all: + rep = r"^\s*Port\s+([0-9]+)\s*" + tmp1 = re.findall(rep, p) + if tmp1: + ssh_port = int(tmp1[0]) + + return ssh_port + + +def set_error_num(key, empty=False, expire=3600): + ''' + @name 设置失败次数(每调用一次+1) + @author hwliang<2020-08-21> + @param key 索引 + @param empty 是否清空计数 + @param expire 计数器生命周期(秒) + @return bool + ''' + from BTPanel import cache + key = md5(key) + num = cache.get(key) + if not num: + num = 0 + else: + if empty: + cache.delete(key) + return True + cache.set(key, num + 1, expire) + return True + + +def get_error_num(key, limit=False): + ''' + @name 获取失败次数 + @author hwliang<2020-08-21> + @param key 索引 + @param limit 如果为False,则直接返回失败次数,否则与失败次数比较,若大于失败次数返回True,否则返回False + @return int or bool + ''' + from BTPanel import cache + key = md5(key) + num = cache.get(key) + if not num: num = 0 + if not limit: + return num + if limit > num: + return True + return False + + +def get_menus(): + ''' + @name 获取菜单列表 + @author hwliang<2020-08-31> + @return list + ''' + from BTPanel import session + data = json.loads(ReadFile('config/menu.json')) + hide_menu = ReadFile('config/hide_menu.json') + debug = session.get('debug') + if hide_menu: + hide_menu = json.loads(hide_menu) + show_menu = [] + for i in range(len(data)): + if data[i]['id'] in hide_menu: continue + if data[i]['id'] == "memuAxterm": + if debug: continue + show_menu.append(data[i]) + data = show_menu + del (hide_menu) + del (show_menu) + menus = sorted(data, key=lambda x: x['sort']) + return menus + + +# 取CURL路径 +def get_curl_bin(): + ''' + @name 取CURL执行路径 + @author hwliang<2020-09-01> + @return string + ''' + c_bin = ['/usr/local/curl2/bin/curl', '/usr/local/curl/bin/curl', '/usr/bin/curl'] + for cb in c_bin: + if os.path.exists(cb): return cb + return 'curl' + + +# 设置防跨站配置 +def set_open_basedir(): + try: + fastcgi_file = '{}/nginx/conf/fastcgi.conf'.format(get_setup_path()) + + if os.path.exists(fastcgi_file): + fastcgi_body = readFile(fastcgi_file) + if fastcgi_body.find('bt_safe_dir') == -1: + fastcgi_body = fastcgi_body + "\n" + 'fastcgi_param PHP_ADMIN_VALUE "$bt_safe_dir=$bt_safe_open";' + writeFile(fastcgi_file, fastcgi_body) + + proxy_file = '{}/nginx/conf/proxy.conf'.format(get_setup_path()) + if os.path.exists(proxy_file): + proxy_body = readFile(proxy_file) + if proxy_body.find('bt_safe_dir') == -1: + proxy_body = proxy_body + "\n" + '''map "baota_dir" $bt_safe_dir { + default "baota_dir"; +} +map "baota_open" $bt_safe_open { + default "baota_open"; +} ''' + writeFile(proxy_file, proxy_body) + + open_basedir_path = '{}/open_basedir/nginx'.format(get_vhost_path()) + if not os.path.exists(open_basedir_path): + os.makedirs(open_basedir_path, 384) + + site_list = M('sites').field('id,name,path').select() + for site_info in site_list: + set_site_open_basedir_nginx(site_info['name']) + except: + return + + +# 处理指定站点的防跨站配置 for Nginx +def set_site_open_basedir_nginx(siteName): + try: + return + open_basedir_path = '/www/server/panel/vhost/open_basedir/nginx' + if not os.path.exists(open_basedir_path): + os.makedirs(open_basedir_path, 384) + config_file = '/www/server/panel/vhost/nginx/{}.conf'.format(siteName) + open_basedir_file = "/".join( + (open_basedir_path, '{}.conf'.format(siteName)) + ) + if not os.path.exists(config_file): return + if not os.path.exists(open_basedir_file): + writeFile(open_basedir_file, '') + config_body = readFile(config_file) + if config_body.find(open_basedir_path) == -1: + config_body = config_body.replace("include enable-php", + "include {};\n\t\tinclude enable-php".format(open_basedir_file)) + writeFile(config_file, config_body) + + root_path = re.findall(r"root\s+(.+);", config_body)[0] + if not root_path: return + userini_file = root_path + '/.user.ini' + if not os.path.exists(userini_file): + writeFile(open_basedir_file, '') + return + userini_body = readFile(userini_file) + if not userini_body: return + if userini_body.find('open_basedir') == -1: + writeFile(open_basedir_file, '') + return + + open_basedir_conf = re.findall(r"open_basedir=(.+)", userini_body) + if not open_basedir_conf: return + open_basedir_conf = open_basedir_conf[0] + open_basedir_body = '''set $bt_safe_dir "open_basedir"; +set $bt_safe_open "{}";'''.format(open_basedir_conf) + writeFile(open_basedir_file, open_basedir_body) + except: + return + + +def run_thread(fun, args=(), daemon=False): + ''' + @name 使用线程执行指定方法 + @author hwliang<2020-10-27> + @param fun {def} 函数对像 + @param args {tuple} 参数元组 + @param daemon {bool} 是否守护线程 + @return bool + ''' + import threading + p = threading.Thread(target=fun, args=args) + p.setDaemon(daemon) + p.start() + return True + + +def check_domain_cloud(domain): + run_thread(cloud_check_domain, (domain,)) + + +def count_wp(): + run_thread(httpPost('http://brandnew.aapanel.com/api/setupCount/setupWP', {})) + + +def cloud_check_domain(domain): + ''' + @name 从云端验证域名的可访问性,并将结果保存到文件 + @author hwliang<2020-12-10> + @param domain {string} 被验证的域名 + @return void + ''' + try: + check_domain_path = '{}/data/check_domain/'.format(get_panel_path()) + if not os.path.exists(check_domain_path): + os.makedirs(check_domain_path, 384) + pdata = get_user_info() + pdata['domain'] = domain + result = httpPost('https://www.aapanel.com/api/panel/checkDomain', {"domain": domain}) + cd_file = check_domain_path + domain + '.pl' + writeFile(cd_file, result) + except: + pass + + +def get_mac_address(): + import uuid + mac = uuid.UUID(int=uuid.getnode()).hex[-12:] + return ":".join([mac[e:e + 2] for e in range(0, 11, 2)]) + + +def get_user_info(): + user_file = '{}/data/userInfo.json'.format(get_panel_path()) + if not os.path.exists(user_file): return {} + userInfo = {} + try: + userTmp = json.loads(readFile(user_file)) + userInfo['uid'] = userTmp['id'] + userInfo['username'] = userTmp['username'] + userInfo['serverid'] = userTmp['serverid'] + userInfo['oem'] = get_oem_name() + userInfo['o'] = userInfo['oem'] + except: + pass + return userInfo + + +def send_file(data, fname='', mimetype=''): + ''' + @name 以文件流的形式返回 + @author heliang<2020-10-27> + @param data {bytes|string} 文件数据或路径 + @param mimetype {string} 文件类型 + @param fname {string} 文件名 + @return Response + ''' + d_type = type(data) + from io import BytesIO, StringIO + from flask import send_file as send_to + if d_type == bytes: + fp = BytesIO(data) + else: + if len(data) < 128: + if os.path.exists(data): + fp = data + if not fname: + fname = os.path.basename(fname) + else: + fp = StringIO(data) + else: + fp = StringIO(data) + + if not mimetype: mimetype = "application/octet-stream" + if not fname: fname = 'doan.txt' + + import flask + if flask.__version__ < "2.1.0": + return send_to(fp, + mimetype=mimetype, + as_attachment=True, + add_etags=True, + conditional=True, + attachment_filename=fname, + cache_timeout=0) + else: + return send_to(fp, + mimetype=mimetype, + as_attachment=True, + etag=True, + conditional=True, + download_name=fname, + max_age=0) + + +def gen_password(length=8, chars=string.ascii_letters + string.digits): + from random import choice + return ''.join([choice(chars) for i in range(length)]) + + +def get_ipaddress(): + ''' + @name 获取本机IP地址 + @author hwliang<2020-11-24> + @return list + ''' + ipa_tmp = ExecShell( + "ip a |grep inet|grep -v inet6|grep -v 127.0.0.1|grep -v 'inet 192.168.'|grep -v 'inet 10.'|awk '{print $2}'|sed 's#/[0-9]*##g'")[ + 0].strip() + iplist = ipa_tmp.split('\n') + return iplist + + +def get_oem_name(): + ''' + @name 获取OEM名称 + @author hwliang<2021-03-24> + @return string + ''' + oem = '' + oem_file = '{}/data/o.pl'.format(get_panel_path()) + if os.path.exists(oem_file): + oem = readFile(oem_file) + if oem: oem = oem.strip() + return oem + + +def get_pdata(): + ''' + @name 构造POST基础参数 + @author hwliang<2021-03-24> + @return dict + ''' + import panelAuth + pdata = panelAuth.panelAuth().create_serverid(None) + pdata['oem'] = get_oem_name() + return pdata + + +# 刷新授权信息 +def refresh_pd(): + from BTPanel import cache + + try: + p_token = cache.get('p_token') + + if p_token is None: + p_token = 'bmac_' + public.Md5(public.get_mac_address()) + cache.set('p_token', p_token) + + softList = load_soft_list(False) + + public.writeFile("/tmp/" + p_token, str(softList['pro'])) + public.writeFile('/tmp/{}.time'.format(p_token), str(int(time.time()))) + except: + pass + + +# 获取授权信息 +def get_pd(args=None): + """ + @name 获取授权信息 + @param args: + @return tuple[html, pro, ltd] + """ + from BTPanel import cache + + # 专业版到期时间 -1.过期 0.永久授权 >0.到期时间 + pro = -1 + + # 企业版到期时间 -1.过期 0.永久授权 >0.到期时间 + ltd = -1 + + # HTML文本 + htm = '' + + # 获取当前时间 + cur_time = int(time.time()) + + p_token = cache.get('p_token') + + if p_token is None: + p_token = 'bmac_' + Md5(get_mac_address()) + cache.set('p_token', p_token) + + tmp_f = '/tmp/' + p_token + p_token_time_f = '/tmp/{}.time'.format(p_token) + + # 检查缓存是否失效 + if not os.path.exists(tmp_f) or not os.path.exists(p_token_time_f) or int(readFile(p_token_time_f).strip()) + 86400 <= cur_time: + # 检查用户是否登录,登录后才获取授权信息 + userinfo_f = '{}/data/userInfo.json'.format(get_panel_path()) + if os.path.exists(userinfo_f) and os.path.getsize(userinfo_f) > 10: + # 缓存失效时重新获取授权信息 + plugin_list = load_soft_list() + + if isinstance(plugin_list, dict): + pro = plugin_list.get('pro', -1) + # ltd = plugin_list.get('ltd', -1) + + writeFile(tmp_f, str(pro), 'w') + writeFile(p_token_time_f, str(cur_time), 'w') + + tmp = readFile(tmp_f) + if tmp: + pro = int(tmp) + + if ltd < 1: + if ltd == -2: + htm = 'EXPIREDRENEW' + elif pro == -1: + htm = 'FREE' + elif pro == -2: + htm = 'EXPIREDRENEW' + if pro >= 0 and ltd in [-1, -2]: + if pro == 0: + tmp2 = 'Lifetime' + htm = 'Expire:{0}'.format( + tmp2) + else: + tmp2 = time.strftime('%Y-%m-%d', time.localtime(pro)) + htm = 'Expire: {0}RENEW'.format( + tmp2) + else: + htm = 'FREE' + else: + htm = 'Expire: {}RENEW'.format( + time.strftime('%Y-%m-%d', time.localtime(ltd))) + + return htm, pro, ltd + + +# 名称输入系列化 +def xssdecode(text): + try: + cs = {""": '"', """: '"', "'": "'", "'": "'"} + for c in cs.keys(): + text = text.replace(c, cs[c]) + + str_convert = text + if sys.version_info[0] == 3: + import html + text2 = html.unescape(str_convert) + else: + text2 = cgi.unescape(str_convert) + return text2 + except: + return text + + +def fetch_disk_SN(): + r, e = ExecShell("fdisk -l |grep 'Disk identifier' |awk {'print $3'}") + if r: + return r.split('\n')[0] + + +def fetch_cpu_ID(): + r, e = ExecShell("cat /proc/cpuinfo|grep microcode|awk -F':' '{print $2}'") + if r: + return r.split('\n')[0] + + +def get_platform(): + import platform + return platform.platform() + + +def get_memory(): + import psutil + return psutil.virtual_memory().total + + +def fetch_env_info(): + import panelAuth + userInfo = panelAuth.panelAuth().create_serverid(None) + try: + return {'ip': GetLocalIp(), + 'is_ipv6': 0, + 'os': get_platform(), + 'mac': get_mac_address(), + 'hdid': fetch_disk_SN(), + 'ramid': get_memory(), + 'cpuid': fetch_cpu_ID(), + 'server_name': get_hostname(), + 'install_code': userInfo['server_id'] + } + except: + return {} + + +def arequests(method, url, data=None, timeout=3): + import threading + if method == 'post': + method = httpPost + else: + method = httpGet + threading.Thread(target=method, args=(url, data, timeout)).start() + + +# 取通用对象 +re_key_match = re.compile(r'^[\w\s\[\]\-.]+$') +re_key_match2 = re.compile(r'^\.?__[\w\s[\]\-]+__\.?$') +key_filter_list = ['get', 'set', 'get_items', 'exists', '__contains__', '__setitem__', '__getitem__', '__delitem__', + '__delattr__', '__setattr__', '__getattr__', '__class__', 'get_file'] + + +class dict_obj: + def __init__(self): + # 存放数据 + self.__store = {} + + # 检测数据是否经过校验 + self.__validated = set() + + def __contains__(self, key): + return hasattr(self, key) + + def __setitem__(self, key, value): + if key in key_filter_list: + raise ValueError("wrong field name") + + if not re_key_match.match(key) or re_key_match2.match(key): + raise ValueError("wrong field name") + + self.__store[key] = value + + def __getitem__(self, key): + return getattr(self, key) + + def __delitem__(self, key): + delattr(self, key) + + def __delattr__(self, key): + delattr(self, key) + + def __setattr__(self, key, value): + if match_class_private_property.match(key): + object.__setattr__(self, key, value) + return + + self.__store[key] = value + + def __getattr__(self, key): + if key in self.__store: + # 未经过校验的数据不允许获取 + # if key not in self.__validated: + # raise ValueError('参数值获取失败:参数 {} 尚未通过校验,请先调用 validate() 完成校验后再尝试重新获取参数值'.format(key)) + return self.__store[key] + + raise AttributeError('\'{}\' object has no attribute \'{}\''.format(self.__class__.__name__, key)) + + @property + def __dict__(self): + return self.__store + + def get_items(self): + return self.__store + + def validate(self, validate_rules: typing.List[Param], filters: typing.List[callable] = (trim_filter(),)) -> None: + """ + @name 验证请求参数 + @param validate_rules: list[validate.Param] 参数验证规则 + @param filters: list[callable] 参数过滤器 + @raise Error + """ + filters = list(filters) + + for v in validate_rules: + v.do_validate(self.__store) + + if v.name in self.__store: + self.__store[v.name] = v.do_filter(self.__store[v.name], filters) + + self.__validated.add(v.name) + + def exists(self, keys): + return exists_args(keys, self) + + def set(self, key, value): + if not isinstance(value, str) or not isinstance(key, str): return False + if key in key_filter_list: + raise ValueError("wrong field name") + if not re_key_match.match(key) or re_key_match2.match(key): + raise ValueError("wrong field name") + return setattr(self, key, value) + + def get(self, key, default='', format='', limit=[]): + ''' + @name 获取指定参数 + @param key 参数名称,允许在/后面限制参数格式,请参考参数值格式(format) + @param default 默认值,默认空字符串 + @param format 参数值格式(int|str|port|float|json|xss|path|url|ip|ipv4|ipv6|letter|mail|phone|正则表达式|>1|<1|=1),默认为空 + @param limit 限制参数值内容 + @param return mixed + ''' + if key.find('/') != -1: + key, format = key.split('/') + result = getattr(self, key, default) + if isinstance(result, str): result = result.strip() + if format: + if format in ['str', 'string', 's']: + result = str(result) + elif format in ['int', 'd']: + try: + result = int(result) + except: + raise ValueError("Parameters: {}, requires int type data".format(key)) + elif format in ['float', 'f']: + try: + result = float(result) + except: + raise ValueError("Parameters: {}, float type data required".format(key)) + elif format in ['json', 'j']: + try: + result = json.loads(result) + except: + raise ValueError("Parameters: {}, requires JSON string".format(key)) + elif format in ['xss', 'x']: + result = xssencode(result) + elif format in ['path', 'p']: + if not path_safe_check(result): + raise ValueError("Parameters: {}, the correct path format is required".format(key)) + result = result.replace('//', '/') + elif format in ['url', 'u']: + regex = re.compile( + r'^(?:http|ftp)s?://' + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' + r'localhost|' + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' + r'(?::\d+)?' + r'(?:/?|[/?]\S+)$', re.IGNORECASE) + if not re.match(regex, result): + raise ValueError('Parameters: {}, the correct URL format is required'.format(key)) + elif format in ['ip', 'ipaddr', 'i', 'ipv4', 'ipv6']: + if format == 'ipv4': + if not is_ipv4(result): + raise ValueError('Parameters: {}, the correct ipv4 address is required'.format(key)) + elif format == 'ipv6': + if not is_ipv6(result): + raise ValueError('Parameters: {}, the correct ipv6 address is required'.format(key)) + else: + if not is_ipv4(result) and not is_ipv6(result): + raise ValueError('Parameters: {}, the correct ipv4/ipv6 address is required'.format(key)) + elif format in ['w', 'letter']: + if not re.match(r'^\w+$', result): + raise ValueError( + 'Parameters: {}, the requirement can only be composed of English letters'.format(key)) + elif format in ['email', 'mail', 'm']: + if not re.match(r'^.+@(\[?)[a-zA-Z0-9\-.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(]?)$', result): + raise ValueError("Parameters: {}, the correct email address format is required".format(key)) + elif format in ['phone', 'mobile', 'm']: + if not re.match(r"^1[3-9]\d{9}$", result): + raise ValueError("Parameters: {}, mobile phone number format required".format(key)) + elif format in ['port']: + result_port = int(result) + if result_port > 65535 or result_port < 0: + raise ValueError("Parameters: {}, the required port number is 0-65535".format(key)) + result = result_port + elif re.match(r"^[<>=]\d+$", result): + operator = format[0] + length = int(format[1:].strip()) + result_len = len(result) + error_obj = ValueError("Parameters: {}, the required length is {}".format(key, format)) + if operator == '=': + if result_len != length: + raise error_obj + elif operator == '>': + if result_len < length: + raise error_obj + else: + if result_len > length: + raise error_obj + elif format[0] in ['^', '(', '[', '\\', '.'] or format[-1] in ['$', ')', ']', '+', '}']: + if not re.match(format, result): + raise ValueError("The format of the specified parameter is incorrect, {}:{}".format(key, format)) + + if limit: + if not result in limit: + raise ValueError("The specified parameter value range is incorrect, {}:{}".format(key, limit)) + return result + + def get_file(self, key: str) -> werkzeug.datastructures.FileStorage: + """ + @name 获取上传文件对象 + @param key: str 参数名 + @return: werkzeug.datastructures.FileStorage + """ + if 'FILES' not in self.__store or key not in self.__store['FILES']: + raise ValueError('not found file with param name {}'.format(key)) + + return self.__store['FILES'][key] + + +# 实例化定目录下的所有模块 +class get_modules: + + def __contains__(self, key): + return self.get_attr(key) + + def __setitem__(self, key, value): + setattr(self, key, value) + + def get_attr(self, key): + ''' + 尝试获取模块,若为字符串,则尝试实例化模块,否则直接返回模块对像 + ''' + res = getattr(self, key) + if isinstance(res, str): + try: + tmp_obj = __import__(key) + reload(tmp_obj) + setattr(self, key, tmp_obj) + return tmp_obj + except: + raise Exception(get_error_info()) + return res + + def __getitem__(self, key): + return self.get_attr(key) + + def __delitem__(self, key): + delattr(self, key) + + def __delattr__(self, key): + delattr(self, key) + + def get_items(self): + return self + + def __init__(self, path="class", limit=None): + ''' + @name 加载指定目录下的模块 + @author hwliang<2020-08-03> + @param path 指定目录,可指定绝对目录,也可指定相对于/www/server/panel的相对目录 默认加载class目录 + @param limit 指定限定加载的模块名称,默认加载path目录下的所有模块 + @param object + + @example + p = get_modules('class') + if 'public' in p: + md5_str = p.public.md5('test') + md5_str = p['public'].md5('test') + md5_str = getattr(p['public'],'md5')('test') + else: + print(p.__dict__) + ''' + os.chdir(get_panel_path()) + exp_files = ['__init__.py', '__pycache__'] + if not path in sys.path: + sys.path.insert(0, path) + for fname in os.listdir(path): + if fname in exp_files: continue + filename = '/'.join([path, fname]) + if os.path.isfile(filename): + if not fname[-3:] in ['.py', '.so']: continue + mod_name = fname[:-3] + else: + c_file = '/'.join((filename, '__init__.py')) + if not os.path.exists(c_file): + continue + mod_name = fname + + if limit: + if not isinstance(limit, list) and not isinstance(limit, tuple): + limit = (limit,) + if not mod_name in limit: + continue + + setattr(self, mod_name, mod_name) + + +# 检查App和小程序的绑定 +def check_app(check='app'): + path = get_panel_path() + '/' + if check == 'app': + try: + if not os.path.exists("/www/server/panel/plugin/btapp/btapp_main.py"): return False + if not os.path.exists(path + 'config/api.json'): return False + if os.path.exists(path + 'config/api.json'): + btapp_info = json.loads(readFile(path + 'config/api.json')) + if not btapp_info['open']: return False + if not 'apps' in btapp_info: return False + if not btapp_info['apps']: return False + return True + return False + except: + return False + elif check == 'app_bind': + if not cache_get(Md5(os.uname().version)): return False + if not os.path.exists("/www/server/panel/plugin/btapp/btapp_main.py"): return False + if not os.path.exists(path + 'config/api.json'): return False + btapp_info = json.loads(readFile(path + 'config/api.json')) + if not btapp_info: return False + if not btapp_info['open']: return False + return True + elif check == 'wxapp': + if not os.path.exists(path + 'plugin/app/user.json'): return False + app_info = json.loads(readFile(path + 'plugin/app/user.json')) + if not app_info: return False + return True + + +# #宝塔邮件报警 +# def send_mail(title,body,is_logs=False,is_type="aapanel login reminder"): +# if is_logs: +# try: +# import send_mail +# send_mail22 = send_mail.send_mail() +# tongdao = send_mail22.get_settings() +# if tongdao['user_mail']['mail_list']==0:return False +# if not tongdao['user_mail']['info']: return False +# if len(tongdao['user_mail']['mail_list'])==1: +# send_mail=tongdao['user_mail']['mail_list'][0] +# send_mail22.qq_smtp_send(send_mail, title=title, body=body) +# else: +# send_mail22.qq_smtp_send(tongdao['user_mail']['mail_list'], title=title, body=body) +# if is_logs: +# WriteLog2(is_type, body) +# except: +# return False +# else: +# try: +# import send_mail +# send_mail22 = send_mail.send_mail() +# tongdao = send_mail22.get_settings() +# if tongdao['user_mail']['mail_list'] == 0: return False +# if not tongdao['user_mail']['info']: return False +# if len(tongdao['user_mail']['mail_list']) == 1: +# send_mail = tongdao['user_mail']['mail_list'][0] +# return send_mail22.qq_smtp_send(send_mail, title=title, body=body) +# else: +# return send_mail22.qq_smtp_send(tongdao['user_mail']['mail_list'], title=title, body=body) +# except: +# return False + +# 宝塔邮件报警 +def send_mail(title, body, is_logs=False, is_type="aapanel login reminder"): + try: + import panelPush + msg_data = { + "msg": body.replace("\n", "
                                    "), + "title": title + } + if is_logs: WriteLog2(is_type, body) + return panelPush.panelPush().push_message_immediately({"mail": msg_data}) + except Exception as ex: + return returnMsg(False, 'Failed to send: {}'.format(ex)) + + +# 发送钉钉告警 +def send_dingding(body, is_logs=False, is_type="aapanel login reminder"): + try: + import panelPush + if is_logs: WriteLog2(is_type, body) + return panelPush.panelPush().push_message_immediately({"dingding": {"msg": body}}) + except Exception as ex: + return returnMsg(False, 'Failed to send: {}'.format(ex)) + + +# 发送微信告警 +def send_weixin(body, is_logs=False, is_type="aapanel login reminder"): + try: + import panelPush + if is_logs: WriteLog2(is_type, body) + return panelPush.panelPush().push_message_immediately({"weixin": {"msg": body}}) + except Exception as ex: + return returnMsg(False, 'Failed to send: {}'.format(ex)) + + +# 发送飞书告警 +def send_feishu(body, is_logs=False, is_type="aapanel login reminder"): + try: + import panelPush + if is_logs: WriteLog2(is_type, body) + return panelPush.panelPush().push_message_immediately({"feishu": {"msg": body}}) + except Exception as ex: + return returnMsg(False, 'Failed to send: {}'.format(ex)) + + +# 发送除短信以外的所有告警通道 +def send_all(body, title=None): + try: + import panelPush + msg_dict = {"msg": body} + msg_all = { + "feishu": msg_dict, + "weixin": msg_dict, + "dingding": msg_dict, + "mail": { + "msg": body.replace("\n", "
                                    "), + "title": title + } + } + return panelPush.panelPush().push_message_immediately(msg_all) + except Exception as ex: + return returnMsg(False, 'Failed to send: {}'.format(ex)) + + +# 获取服务器IP +def get_ip(): + iplist_file = '{}/data/iplist.txt'.format(get_panel_path()) + if os.path.exists(iplist_file): + data = ReadFile(iplist_file) + return data.strip() + else: + return '127.0.0.1' + + +# 获取服务器内网Ip +def get_local_ip(): + try: + ret = ExecShell( + r"ip addr | grep -E -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -E -v \"^127\.|^255\.|^0\.\" | head -n 1") + local_ip = ret[0].strip() + return local_ip + except: + return '127.0.0.1' + + +def create_logs(): + import db + sql = db.Sql() + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'logs2')).count(): + csql = '''CREATE TABLE `logs2` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `type` TEXT, + `log` TEXT, + `addtime` TEXT +, uid integer DEFAULT '1', username TEXT DEFAULT 'system')''' + sql.execute(csql, ()) + + +def WriteLog2(type, logMsg, args=(), not_web=False): + import db + create_logs() + username = 'system' + uid = 1 + tmp_msg = '' + sql = db.Sql() + mDate = time.strftime('%Y-%m-%d %X', time.localtime()) + data = (uid, username, type, logMsg + tmp_msg, mDate) + result = sql.table('logs2').add('uid,username,type,log,addtime', data) + + +def check_ip_white(path, ip): + if os.path.exists(path): + try: + path_json = json.loads(ReadFile(path)) + except: + WriteFile(path, '[]') + return False + if ip in path_json: + return True + else: + return False + else: + return False + + +def check_login_area(login_ip, login_type='panel'): + """ + @name 检测登录地区 + @login_type 登录类型 panel:宝塔面板登录, ssh:ssh登录 + """ + + login_ip_area = '' + ip_info = get_ips_area([login_ip]) + + if 'status' in ip_info: + login_ip_area = '****(Pro exclusive)' + else: + ip_info = ip_info[login_ip] + if not 'city' in ip_info: + login_ip_area = ip_info['info'] + + data = {} + status = False + sfile = '{}/data/{}_login_area.pl'.format(get_panel_path(), login_type) + s_conf = '{}/data/{}_login_area.json'.format(get_panel_path(), login_type) + if os.path.exists(sfile): + status = True + + data = {} + try: + data = json.loads(readFile(s_conf)) + except: + pass + + if not login_ip_area and 'city' in ip_info: + city = ip_info['city'] + login_ip_area = ip_info['info'] + + if not city in data: + data[city] = 0 + + if data[city] < 3: + login_ip_area += '(异地)' + data[city] += 1 + + writeFile(s_conf, json.dumps(data)) + + data['login_ip_area'] = login_ip_area + return status, data + + +def get_free_ips_area(ips): + ''' + @name 免费IP库 获取ip地址所在地 + @author cjxin + @param ips + @return list + ''' + import PluginLoader + args = dict_obj() + args.model_index = 'safe' + args.ips = ips + res = PluginLoader.module_run("freeip", "get_ip_area", args) + return res + + +def get_free_ip_info(address): + ''' + @name 免费IP库 获取ip地址所在地 + @param ip + @return dict + ''' + ip = address.split(':')[0] + if not is_ipv4(ip): + return {'info': 'unknow'} + if is_local_ip(ip): + return {'info': 'intranet', 'local': True} + + ip_info = {} + sfile = '{}/data/ip_area.json'.format(get_panel_path()) + try: + ip_info = json.loads(readFile(sfile)) + except: + pass + + if ip in ip_info: + return ip_info[ip] + try: + param = get_user_info() + param['ip'] = address + res = json.loads(httpPost('https://www.bt.cn/api/ip/info', param)) + + if address in res: + info = res[address] + ip_info[ip] = info + ip_info[ip]['info'] = '{} {} {} {}'.format(info['carrier'], info['country'], info['province'], + info['city']).strip() + ip_info[ip]['ip'] = ip + writeFile(sfile, json.dumps(ip_info)) + return res[address] + except: + pass + + return {'info': 'Unknown'} + + +# 使用免费IP库获取IP地区 +def free_login_area(login_ip, login_type='panel'): + """ + @name 使用免费IP库获取IP地区 + @login_type 登录类型 panel:宝塔面板登录, ssh:ssh登录 + """ + # 判断是否开启免费IP库 + if os.path.exists('{}/data/{}_login_area.pl'.format(get_panel_path(), 'btpanel')): + return False, {} + login_ip_area = '' + ip_info = get_free_ips_area([login_ip]) + if not login_ip in ip_info: + return False, {} + + ip_info = ip_info[login_ip] + if not 'city' in ip_info: + login_ip_area = ip_info['info'] + status = True + s_conf = '{}/data/{}_login_area.json'.format(get_panel_path(), login_type) + data = {} + try: + data = json.loads(readFile(s_conf)) + except: + pass + if not login_ip_area and 'city' in ip_info: + city = ip_info['city'] + login_ip_area = ip_info['info'] + if len(city) >= 1 and not city in data: + data[city] = 0 + if data[city] < 3: + if city == 'Local': + login_ip_area += '(Intranet)' + else: + login_ip_area += '(Abnormal login)' + data[city] += 1 + writeFile(s_conf, json.dumps(data)) + data['login_ip_area'] = login_ip_area + return status, data + + +# 登陆告警 +def login_send_body(is_type, username, login_ip, port): + send_type = "" + panel_path = get_panel_path() + login_send_type_conf = "/www/server/panel/data/panel_login_send.pl" + if os.path.exists(login_send_type_conf): + send_type = ReadFile(login_send_type_conf).strip() + else: + # 兼容之前的 + if os.path.exists("/www/server/panel/data/login_send_type.pl"): + send_type = readFile("/www/server/panel/data/login_send_type.pl") + else: + if os.path.exists('/www/server/panel/data/login_send_mail.pl'): + send_type = "mail" + if os.path.exists('/www/server/panel/data/login_send_dingding.pl'): + send_type = "dingding" + + # 增加异地登录告警 + server_ip_area = login_ip + ":" + port + login_aera_status, login_aera = free_login_area(login_ip=server_ip_area, login_type='panel') + if login_aera_status: + login_ip_area = ">Location:" + login_aera['login_ip_area'] + # 如果存在归属地则修改日志内容 + time.sleep(0.2) + if cache_get(server_ip_area): + id = cache_get(server_ip_area) + logs = M("logs").where("id=?", id).getField("log") + data = M("logs").where("id=?", id).setField("log", logs + login_ip_area) + else: + login_ip_area = '' + + add_security_logs('login successful', server_ip_area + login_ip_area, False) + if not send_type: + return False + + object = init_msg(send_type.strip()) + if not object: return + + send_login_white = '{}/data/send_login_white.json'.format(panel_path) + if check_ip_white(send_login_white, login_ip): + return False + + if send_type == 'sms': + data = {} + data['ip'] = get_server_ip() + data['local_ip'] = get_network_ip() + # 不加内网IP,否则短信模板参数长度超过限制 + ip = "{}(外)".format(data['ip']) + sm_args = {'name': '[' + ip + ']', 'time': time.strftime('%Y-%m-%d %X', time.localtime()), + 'type': '[' + is_type + ']', + 'user': username} + rdata = object.send_msg('login_panel', check_sms_argv(sm_args)) + else: + if login_ip_area: + plist = [ + ">Login type:" + is_type, + ">Account:" + username, + ">IP address:" + login_ip + ":" + port, + login_ip_area, + ">Login status:Success" + ] + else: + plist = [ + ">Login type:" + is_type, + ">Account:" + username, + ">IP address:" + login_ip + ":" + port, + ">Login status:Success" + ] + info = get_push_info("Panel Login Alert", plist) + object.push_data(info) + + # if send_type == "dingding": + # msg = "#### 堡塔登录提醒\n\n > 服务器 :"+get_ip()+"\n\n > 登录方式:"+is_type+"\n\n > 登录账号:"+username+"\n\n > 登录IP:"+login_ip+":"+port+"\n\n > 登录时间:"+time.strftime('%Y-%m-%d %X',time.localtime())+'\n\n > 登录状态: 成功' + # send_dingding(msg, False) + # elif send_type == "weixin": + # msg = "#### 堡塔登录提醒\n\n > 服务器 :"+get_ip()+"\n\n > 登录方式:"+is_type+"\n\n > 登录账号:"+username+"\n\n > 登录IP:"+login_ip+":"+port+"\n\n > 登录时间:"+time.strftime('%Y-%m-%d %X',time.localtime())+'\n\n > 登录状态: 成功' + # send_weixin(msg, False) + # elif send_type == "feishu": + # msg = "堡塔登录提醒\n > 服务器 :"+get_ip()+"\n > 登录方式:"+is_type+"\n > 登录账号:"+username+"\n > 登录IP:"+login_ip+":"+port+"\n > 登录时间:"+time.strftime('%Y-%m-%d %X',time.localtime())+'\n > 登录状态: 成功' + # send_feishu(msg, False) + + +# 普通模式下调用发送消息【设置登陆告警后的设置】 +# title= 发送的title +# body= 发送的body +# is_logs= 是否记录日志 +# is_type=发送告警的类型 +def send_to_body(title, body, is_logs=False, is_type="aaPanel email alert"): + login_send_mail = "{}/data/login_send_mail.pl".format(get_panel_path()) + login_send_dingding = "{}/data/login_send_dingding.pl".format(get_panel_path()) + if os.path.exists(login_send_mail): + if is_logs: + send_mail(title, body, True, is_type) + send_mail(title, body) + + if os.path.exists(login_send_dingding): + if is_logs: + send_dingding(body, True, is_type) + send_dingding(body) + + +# 普通发送消息 +# send_type= ["mail","dingding"] +# title =发送的头 +# body= 发送消息的内容 +def send_body_words(send_type, title, body): + if send_type == 'mail': + return send_mail(title, body) + if send_type == 'dingding': + return send_dingding(body) + + +def return_is_send_info(): + import send_mail + send_mail22 = send_mail.send_mail() + tongdao = send_mail22.get_settings() + ret = {} + ret['mail'] = tongdao['user_mail']['user_name'] + ret['dingding'] = tongdao['dingding']['dingding'] + return ret + + +def get_sys_path(): + ''' + @name 关键目录 + @author hwliang<2021-06-11> + @return tuple + ''' + a = ['/www', '/usr', '/', '/dev', '/home', '/media', '/mnt', '/opt', '/tmp', '/var'] + c = ['/www/.Recycle_bin/', '/www/backup/', '/www/php_session/', '/www/wwwlogs/', '/www/server/', '/etc/', '/usr/', + '/var/', '/boot/', '/proc/', '/sys/', '/tmp/', '/root/', '/lib/', '/bin/', '/sbin/', '/run/', '/lib64/', + '/lib32/', '/srv/'] + return a, c + + +def check_site_path(site_path): + ''' + @name 检查网站根目录是否为系统关键目录 + @author hwliang<2021-05-31> + @param site_path 网站根目录全路径 + @return bool + ''' + try: + if site_path in ['/', '/usr', '/dev', '/home', '/media', '/mnt', '/opt', '/tmp', '/var']: + return False + whites = ['/www/server/tomcat', '/www/server/stop', '/www/server/phpmyadmin'] + for w in whites: + if site_path.find(w) == 0: return True + a, error_paths = get_sys_path() + site_path = site_path.strip() + if site_path[-1] == '/': site_path = site_path[:-1] + if site_path in a: + return False + site_path += '/' + for ep in error_paths: + if site_path.find(ep) == 0: return False + return True + except: + return False + + +def is_debug(): + debug_file = "{}/data/debug.pl".format(get_panel_path()) + return os.path.exists(debug_file) + + +class PanelError(Exception): + ''' + @name 宝塔通用异常对像 + @author hwliang<2021-06-25> + ''' + + def __init__(self, value): + self.value = value + + def __str__(self): + return ("An error occurred while the panel was running: {}".format(str(self.value))) + + +def sys_path_append(path): + ''' + @name 追加引用路径 + @author hwliang<2021-07-07> + @param path 路径 + @return void + ''' + try: + if not path in sys.path: + sys.path.insert(0, path) + except: + pass + + +def get_sysbit(): + ''' + @name 获取操作系统位数 + @author hwliang<2021-07-07> + @return int 32 or 64 + ''' + import struct + return struct.calcsize('P') * 8 + + +def get_plugin_path(plugin_name=None): + ''' + @name 取指定插件目录 + @author hwliang<2021-07-14> + @param plugin_name 插件名称 不传则返回插件根目录 + @return string + ''' + + root_path = "{}/plugin".format(get_panel_path()) + if not plugin_name: return root_path + return "{}/{}".format(root_path, plugin_name) + + +def get_class_path(): + ''' + @name 取类库所在路径 + @author hwliang<2021-07-14> + @return string + ''' + return "{}/class".format(get_panel_path()) + + +def decode_data(srcBody): + """ + 遍历解码字符串 + """ + arrs = ['utf-8', 'GBK', 'ANSI', 'BIG5'] + for encoding in arrs: + try: + data = srcBody.decode(encoding) + return encoding, data + except: + pass + return False, None + + +def get_logs_path(): + ''' + @name 取日志目录 + @author hwliang<2021-07-14> + @return string + ''' + return '/www/wwwlogs' + + +def get_vhost_path(): + ''' + @name 取虚拟主机目录 + @author hwliang<2021-08-14> + @return string + ''' + return '{}/vhost'.format(get_panel_path()) + + +def get_backup_path(): + ''' + @name 取备份目录 + @author hwliang<2021-07-14> + @return string + ''' + default_backup_path = '/www/backup' + backup_path = M('config').where("id=?", (1,)).getField('backup_path') + if not backup_path: return default_backup_path + if os.path.exists(backup_path): return backup_path + return default_backup_path + + +def get_site_path(): + ''' + @name 取站点默认存储目录 + @author hwliang<2021-07-14> + @return string + ''' + default_site_path = '/www/wwwroot' + site_path = M('config').where("id=?", (1,)).getField('sites_path') + if not site_path: return default_site_path + if os.path.exists(site_path): return site_path + return default_site_path + + +def read_config(config_name, ext_name='json'): + ''' + @name 读取指定配置文件 + @author hwliang<2021-07-14> + @param config_name 配置文件名称(不含扩展名) + @param ext_name 配置文件扩展名,默认为json + @return string 如果发生错误,将抛出PanelError异常 + ''' + config_file = "{}/config/{}.{}".format(get_panel_path(), config_name, ext_name) + if not os.path.exists(config_file): + raise PanelError('The specified configuration file {} does not exist'.format(config_name)) + + config_str = readFile(config_file) + if ext_name == 'json': + try: + config_body = json.loads(config_str) + except Exception as ex: + raise PanelError('Configuration files are not standard parsable JSON content!\n{}'.format(ex)) + return config_body + return config_str + + +def save_config(config_name, config_body, ext_name='json'): + ''' + @name 保存配置文件 + @author hwliang<2021-07-14> + @param config_name 配置文件名称(不含扩展名) + @param config_body 被保存的内容, ext_name为json,请传入可解析为json的参数类型,如list,dict,int,str等 + @param ext_name 配置文件扩展名,默认为json + @return string 如果发生错误,将抛出PanelError异常 + ''' + + config_file = "{}/config/{}.{}".format(get_panel_path(), config_name, ext_name) + if ext_name == 'json': + try: + config_body = json.dumps(config_body) + except Exception as ex: + raise PanelError('The configuration content cannot be converted to json format!\n{}'.format(ex)) + + return writeFile(config_file, config_body) + + +def get_config_value(config_name, key, default='', ext_name='json'): + ''' + @name 获取指定配置文件的指定配置项 + @author hwliang<2021-07-14> + @param config_name 配置文件名称(不含扩展名) + @param key 配置项 + @param default 获不存在则返回的默认值,默认为空字符串 + @param ext_name 配置文件扩展名,默认为json + @return mixed 如果发生错误,将抛出PanelError异常 + ''' + config_data = read_config(config_name, ext_name) + return config_data.get(key, default) + + +def set_config_value(config_name, key, value, ext_name='json'): + ''' + @name 设置指定配置文件的指定配置项 + @author hwliang<2021-07-14> + @param config_name 配置文件名称(不含扩展名) + @param key 配置项 + @param value 配置值 + @param ext_name 配置文件扩展名,默认为json + @return mixed 如果发生错误,将抛出PanelError异常 + ''' + config_data = read_config(config_name, ext_name) + config_data[key] = value + return save_config(config_name, config_data, ext_name) + + +def return_data(status, data={}, status_code=None, error_msg=None): + ''' + @name 格式化响应内容 + @author hwliang<2021-07-14> + @param status 状态 + @param data 响应数据 + @param status_code 状态码 + @param error_msg 错误消息内容 + @return dict + + ''' + if status_code == None: + status_code = 1 if status else 0 + if error_msg == None: + error_msg = '' if status else 'unknown error' + + result = { + 'status': status, + "status_code": status_code, + 'error_msg': str(error_msg), + 'data': data + } + return result + + +def return_error(error_msg, status_code=-1, data=[]): + ''' + @name 格式化错误响应内容 + @author hwliang<2021-07-15> + @param error_msg 错误消息 + @param status_code 状态码,默认为-1 + @param data 响应数据 + @return dict + ''' + if not data: data = error_msg + return return_data(False, data, status_code, str(error_msg)) + + +def error(error_msg, status_code=-1, data=[]): + ''' + @name 格式化错误响应内容 + @author hwliang<2021-07-15> + @param error_msg 错误消息 + @param status_code 状态码,默认为-1 + @param data 响应数据 + @return dict + ''' + if not data: data = error_msg + return return_error(error_msg, status_code, data) + + +def success(data=[], status_code=1, error_msg=''): + ''' + @name 格式化成功响应内容 + @author hwliang<2021-07-15> + @param data 响应数据 + @param status_code 状态码,默认为0 + @return dict + ''' + return return_data(True, data, status_code, error_msg) + + +def return_status_code(status_code, format_body, data=[]): + ''' + @name 按状态码返回 + @author hwliang<2021-07-15> + @param status_code 状态码 + @param format_body 错误内容 + @param data 响应数据 + @return dict + ''' + error_msg = get_config_value('status_code', str(status_code)) + if not error_msg: raise PanelError('invalid status_code') + return return_data(error_msg[0], data, status_code, error_msg[1].format(format_body)) + + +def to_dict_obj(data: dict) -> dict_obj: + ''' + @name 将dict转换为dict_obj + @author hwliang<2021-07-15> + @param data 要被转换的数据 + @return dict_obj + ''' + if not isinstance(data, dict): + raise PanelError('parameter error: only support transform dict to dict_obj.') + pdata = dict_obj() + for key in data.keys(): + pdata[key] = data[key] + return pdata + + +def get_script_object(filename): + ''' + @name 从脚本文件获取对像 + @author hwliang<2021-07-19> + @param filename 文件名 + @return object + ''' + _obj = sys.modules.get(filename, None) + if _obj: return _obj + from types import ModuleType + _obj = sys.modules.setdefault(filename, ModuleType(filename)) + _code = readFile(filename) + _code_object = compile(_code, filename, 'exec') + _obj.__file__ = filename + _obj.__package__ = '' + exec(_code_object, _obj.__dict__) + return _obj + + +def check_hooks(): + ''' + @name 自动注册HOOK + @author hwliang<2021-07-19> + @return void + ''' + hooks_path = '{}/hooks'.format(get_panel_path()) + if not os.path.exists(hooks_path): + return + for hook_name in os.listdir(hooks_path): + if hook_name[-3:] != '.py': continue + filename = os.path.join(hooks_path, hook_name) + _obj = get_script_object(filename) + _main = getattr(_obj, 'main', None) + if not _main: continue + _main() + + +def register_hook(hook_index, hook_def): + ''' + @name 注册HOOK + @author hwliang<2021-07-15> + @param hook_index HOOK位置 + @param hook_def HOOK函数对像 + @return void + ''' + from BTPanel import hooks + hook_keys = hooks.keys() + if not hook_index in hook_keys: + hooks[hook_index] = [] + if not hook_def in hooks[hook_index]: + hooks[hook_index].append(hook_def) + + +def exec_hook(hook_index, data): + r''' + @name 执行HOOk + @author hwliang<2021-07-15> + @param hook_index HOOK索引位置,格式限制:^\w+$ + @param data 运行数据 + @return mixed + ''' + + from BTPanel import hooks + hook_keys = hooks.keys() + if not hook_index in hook_keys: + return data + + for hook_def in hooks[hook_index]: + data = hook_def(data) + return data + + +def get_hook_index(mod_name, def_name): + ''' + @name 获取HOOK位置 + @author hwliang<2021-07-19> + @param mod_name 模块名称 + @param def_name 方法名称 + @return tuple + ''' + mod_name = mod_name.upper() + def_name = def_name.upper() + last_index = '{}_{}_LAST'.format(mod_name, def_name) + end_index = '{}_{}_END'.format(mod_name, def_name) + return last_index, end_index + + +def flush_plugin_list(): + ''' + @name 刷新插件列表 + @author hwliang<2021-07-22> + @return bool + ''' + skey = 'TNaMJdG3mDHKRS6Y' + from BTPanel import cache + if cache.get(skey): cache.delete(skey) + load_soft_list() + return True + + +def get_session_timeout(): + ''' + @name 获取session过期时间 + @author hwliang<2021-07-28> + @return int + ''' + from BTPanel import cache + skey = 'session_timeout' + session_timeout = cache.get(skey) + if not session_timeout == None: return session_timeout + + sess_out_path = '{}/data/session_timeout.pl'.format(get_panel_path()) + session_timeout = 86400 + if not os.path.exists(sess_out_path): + return session_timeout + try: + session_timeout = int(readFile(sess_out_path)) + except: + session_timeout = 86400 + cache.set(skey, session_timeout, 3600) + return session_timeout + + +def get_login_token_auth(): + ''' + @name 获取登录token + @author hwliang<2021-07-28> + @return string + ''' + from BTPanel import cache + skey = 'login_token' + login_token = cache.get(skey) + if not login_token == None: return login_token + + login_token_file = '{}/data/login_token.pl'.format(get_panel_path()) + login_token = '1234567890' + if not os.path.exists(login_token_file): + return login_token + login_token = readFile(login_token_file) + cache.set(skey, login_token, 3600) + return login_token + + +def listen_ipv6(): + ''' + @name 是否监听ipv6 + @author hwliang<2021-08-12> + @return bool + ''' + ipv6_file = '{}/data/ipv6.pl'.format(get_panel_path()) + return os.path.exists(ipv6_file) + + +def get_panel_log_file(): + ''' + @name 获取panel日志文件 + @author hwliang<2021-08-12> + @return string + ''' + return "{}/logs/error.log".format(get_panel_path()) + + +def print_log(_info, _level='DEBUG'): + ''' + @name 写入日志 + @author hwliang<2021-08-12> + @param _info 要写入到日志文件的信息 + @param _level 日志级别 + @return void + ''' + if type(_info) == dict: + _info = json.dumps(_info) + log_body = "[{}][{}] - {}\n".format(format_date(), _level.upper(), _info) + return WriteFile(get_panel_log_file(), log_body, 'a+') + + +def print_error(): + ''' + @name 打印错误信息到日志文件 + @author hwliang + @return void + ''' + print_log(get_error_info(), 'ERROR') + + +def to_date(format="%Y-%m-%d %H:%M:%S", times=None): + ''' + @name 格式时间转时间戳 + @author hwliang<2021-08-17> + @param format 时间格式 + @param times 时间 + @return int + ''' + if times: + if isinstance(times, int): return times + if isinstance(times, float): return int(times) + if is_number(times): return int(times) + else: + return 0 + ts = time.strptime(times, format) + return time.mktime(ts) + + +def get_glibc_version(): + ''' + @name 获取glibc版本 + @author hwliang<2021-08-17> + @return string + ''' + try: + cmd_result = ExecShell("ldd --version")[0] + if not cmd_result: return '' + glibc_version = cmd_result.split("\n")[0].split()[-1] + except: + return '' + return glibc_version + + +def is_apache_nginx(): + ''' + @name 是否是apache或nginx + @author hwliang<2021-08-17> + @return bool + ''' + setup_path = get_setup_path() + return os.path.exists(setup_path + '/apache') or os.path.exists(setup_path + '/nginx') + + +def error_not_login(e=None, _src=None): + ''' + @name 未登录时且未输入正确的安全入口时的响应 + @author hwliang<2021-12-16> + @return Response + ''' + from BTPanel import Response, render_template, redirect, request + client_status = check_client_info() + x_http_token = request.headers.get('x-http-token') + if client_status == 1: + if x_http_token: + # result = {"status": False, "code": -8888, "redirect": get_admin_path(), + # "msg": "The current login session has been invalid, please login again!"} + + # 修改为aapanel通用返回方式 + result = { + "status": -1, + "timestamp": int(time.time()), + "message": { + "msg": "The current login session has been invalid, please login again!", + "redirect": get_admin_path() + } + } + + return Response(json.dumps(result), mimetype='application/json', status=200) + return redirect(get_admin_path()) + elif client_status == 2: + if x_http_token: + # result = {"status": False, "code": -8888, "redirect": "/login", + # "msg": "The current login session has been invalid, please login again!"} + + # 修改为aapanel通用返回方式 + result = { + "status": -1, + "timestamp": int(time.time()), + "message": { + "msg": "The current login session has been invalid, please login again!", + "redirect": "/login" + } + } + + return Response(json.dumps(result), mimetype='application/json', status=200) + return render_template('autherr.html') + + try: + abort_code = read_config('abort') + if not abort_code in [None, 1, 0, '0', '1']: + if abort_code == 404: return error_404(e) + if abort_code == 403: return error_403(e) + return Response(status=int(abort_code)) + except: + pass + + if e in ['/login']: + return redirect(e) + + if _src: + return e + else: + return error_404(e) + + +def error_403(e): + from BTPanel import Response, session + # if not session.get('login',None): return error_not_login() + errorStr = ''' +403 Forbidden + +

                                    403 Forbidden

                                    +
                                    nginx
                                    + +''' + headers = { + "Content-Type": "text/html" + } + return Response(errorStr, status=403, headers=headers) + + +def error_404(e): + from BTPanel import Response, session + # if not session.get('login',None): return error_not_login() + errorStr = ''' +404 Not Found + +

                                    404 Not Found

                                    +
                                    nginx
                                    + +''' + headers = { + "Content-Type": "text/html" + } + return Response(errorStr, status=404, headers=headers) + + +def error_401(e): + from BTPanel import Response, session + # if not session.get('login',None): return error_not_login() + errorStr = ''' +401 Unauthorized + +

                                    401 Unauthorized

                                    +
                                    You must enter a valid login ID and password to access this page.
                                    + +''' + headers = { + "Content-Type": "text/html" + } + return Response(errorStr, status=401, headers=headers) + + +def get_password_config(): + ''' + @name 获取密码安全配置 + @author hwliang<2021-10-18> + @return int + ''' + import config + return config.config().get_password_config(None) + + +def password_expire_check(): + ''' + @name 密码过期检查 + @author hwliang<2021-10-18> + @return bool + ''' + p_config = get_password_config() + if p_config['expire'] == 0: return True + if time.time() > p_config['expire_time']: return False + return True + + +def stop_status_mvore(): + flag = False + try: + nginx_path = '/www/server/panel/vhost/nginx/btwaf.conf' + if os.path.exists(nginx_path): + ExecShell('mv %s %s.bak' % (nginx_path, nginx_path)) + flag = True + nginx_path = '/www/server/panel/vhost/nginx/free_waf.conf' + if os.path.exists(nginx_path): + ExecShell('mv %s %s.bak' % (nginx_path, nginx_path)) + flag = True + apache_path = '/www/server/panel/vhost/apache/btwaf.conf' + if os.path.exists(apache_path): + ExecShell('chattr -i %s && mv %s %s.bak' % (apache_path, apache_path, apache_path)) + flag = True + if flag: + serviceReload() + except: + pass + + +def is_error_path(): + if os.path.exists("/www/server/panel/data/error_pl.pl"): + stop_status_mvore() + return True + return False + + +def get_php_versions(reverse=False): + ''' + @name 取PHP版本列表 + @author hwliang<2021-12-16> + @param reverse 是否降序 + @return list + ''' + _file = get_panel_path() + '/config/php_versions.json' + if os.path.exists(_file): + version_list = json.loads(readFile(_file)) + else: + version_list = ['52', '53', '54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83', '84'] + + return sorted(version_list, reverse=reverse) + + +def get_full_session_file(): + ''' + @name 获取临时SESSION文件 + @author hwliang<2021-12-28> + @return string + ''' + from BTPanel import app + full_session_key = app.config['SESSION_KEY_PREFIX'] + get_session_id() + sess_path = get_panel_path() + '/data/session/' + return sess_path + '/' + md5(full_session_key) + + +def install_mysql_client(): + ''' + @name 安装mysql客户端 + @author hwliang<2022-01-14> + @return void + ''' + if os.path.exists('/usr/bin/yum'): + os.system("yum install mariadb -y") + if not os.path.exists('/usr/bin/mysql'): + os.system("yum reinstall mariadb -y") + elif os.path.exists('/usr/bin/apt-get'): + os.system('apt-get install mariadb-client -y') + if not os.path.exists('/usr/bin/mysql'): + os.system('apt-get reinstall mariadb-client* -y') + + +def get_mysqldump_bin(): + ''' + @name 获取mysqldump路径 + @author hwliang<2022-01-14> + @return string + ''' + bin_files = [ + '{}/mysql/bin/mysqldump'.format(get_setup_path()), + '/usr/bin/mysqldump', + '/usr/local/bin/mysqldump', + '/usr/sbin/mysqldump', + '/usr/local/sbin/mysqldump' + ] + + for bin_file in bin_files: + if os.path.exists(bin_file): + return bin_file + + install_mysql_client() + + for bin_file in bin_files: + if os.path.exists(bin_file): + return bin_file + + return bin_files[0] + + +def get_mysql_bin(): + ''' + @name 获取mysql路径 + @author hwliang<2022-01-14> + @return string + ''' + bin_files = [ + '{}/mysql/bin/mysql'.format(get_setup_path()), + '/usr/bin/mysql', + '/usr/local/bin/mysql', + '/usr/sbin/mysql', + '/usr/local/sbin/mysql' + ] + + for bin_file in bin_files: + if os.path.exists(bin_file): + return bin_file + + install_mysql_client() + + for bin_file in bin_files: + if os.path.exists(bin_file): + return bin_file + return bin_files[0] + + +def error_conn_cloud(text): + ''' + @name 连接云端失败 + @author hwliang<2021-12-18> + @return void + ''' + code_msg = '' + if text.find("502 Bad Gateway") != -1: + code_msg = '502 Bad Gateway' + if text.find("504 Bad Gateway") != -1: + code_msg = '504 Bad Gateway' + elif text.find("Connection refused") != -1: + code_msg = 'Connection refused' + elif text.find("Connection timed out") != -1: + code_msg = 'Connection timed out' + elif text.find("Connection reset by peer") != -1: + code_msg = 'Connection reset by peer' + elif text.find("Name or service not known") != -1: + code_msg = 'Name or service not known' + elif text.find("No route to host") != -1: + code_msg = 'No route to host' + elif text.find("No such file or directory") != -1: + code_msg = 'No such file or directory' + elif text.find("404 Not Found") != -1: + code_msg = '404 Not Found' + elif text.find("403 Forbidden") != -1: + code_msg = '403 Forbidden' + elif text.find("401 Unauthorized") != -1: + code_msg = '401 Unauthorized' + elif text.find("400 Bad Request") != -1: + code_msg = '400 Bad Request' + elif text.find("Remote end closed connection without response") != -1: + code_msg = 'Remote end closed connection' + err_template_file = '{}/BTPanel/templates/default/error_connect.html'.format(get_panel_path()) + msg = readFile(err_template_file) + msg = msg.format(code=code_msg) + return PanelError(msg) + + +def get_mountpoint_list(): + ''' + @name 获取挂载点列表 + @author hwliang<2021-12-18> + @return list + ''' + import psutil + mount_list = [] + for mount in psutil.disk_partitions(): + mountpoint = mount.mountpoint if mount.mountpoint[-1] == '/' else mount.mountpoint + '/' + mount_list.append(mountpoint) + # 根据挂载点字符长度排序 + mount_list.sort(key=lambda i: len(i), reverse=True) + return mount_list + + +def get_path_in_mountpoint(path): + ''' + @name 获取文件或目录目录所在挂载点 + @author hwliang<2022-03-30> + @param path 文件或目录路径 + @return string + ''' + # 判断是否是绝对路径 + if path.find('./') != -1 or path[0] != '/': raise PanelError("cannot use relative path") + if not path: raise PanelError("path cannot be empty") + + # 在目录尾加/ + if os.path.isdir(path): + path = path if path[-1] == '/' else path + '/' + + # 匹配挂载点 + mount_list = get_mountpoint_list() + for mountpoint in mount_list: + if path.startswith(mountpoint): + return mountpoint + + # 没有匹配到挂载点 + return '/' + + +def get_recycle_bin_path(path): + ''' + @name 获取指定文件或目录的回收站路径 + @author hwliang<2022-03-30> + @param path 文件或目录路径 + @return string + ''' + mountpoint = get_path_in_mountpoint(path) + recycle_bin_path = '{}/.Recycle_bin/'.format(mountpoint) + try: + if not os.path.exists(recycle_bin_path): + os.mkdir(recycle_bin_path, 384) + except: + return '/www/.Recycle_bin/' + return recycle_bin_path + + +def get_recycle_bin_list(): + ''' + @name 获取回收站列表 + @author hwliang<2022-03-30> + @return list + ''' + # 旧的回收站重命名为.Recycle_bin + default_path = '/www/.Recycle_bin' + default_path_src = '/www/Recycle_bin' + if os.path.exists(default_path_src) and not os.path.exists(default_path): + try: + os.rename(default_path_src, default_path) + except: + ExecShell("mv {} {}".format(default_path_src, default_path)) + + if not os.path.exists(default_path): + os.makedirs(default_path, 384) + + # 获取回收站列表 + recycle_bin_list = [] + mtime_list = [] # 修改时间 + for mountpoint in get_mountpoint_list(): + recycle_bin_path = '{}.Recycle_bin/'.format(mountpoint) + + try: + if not os.path.exists(recycle_bin_path): + os.mkdir(recycle_bin_path, 384) + if not os.path.exists(recycle_bin_path): continue + mtime = os.path.getmtime(recycle_bin_path) + if mtime in mtime_list: continue # 通过修改时间去重 + mtime_list.append(mtime) + recycle_bin_list.append(recycle_bin_path) + except: + continue + + # 包含默认回收站路径? + if not default_path + '/' in recycle_bin_list: + recycle_bin_list.append(default_path + '/') + + return recycle_bin_list + + +def check_password(password): + """ + 密码强度: + 0 弱 + 1 中 + 2 强 + """ + l = 0 + low = False + up = False + symbol = False + digit = False + p_len = len(password) + if p_len < 8: + return l + for i in password: + if i.islower(): + low = True + if i.isupper(): + up = True + if i in ['~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '-', '=', '+', '<', '>', ',', '.', '/', + '"', '|', '\\', "'", '?']: + symbol = True + if i.isdigit(): + digit = True + # 判断重复出现 + tmp = len(set([i for i in password])) + if tmp >= 2: + if low and up and symbol and digit: + l = 2 + if p_len >= 11: + l = 1 + return l + + +def set_module_logs(mod_name, fun_name, count=1): + """ + @模块使用次数 + @mod_name 模块名称 + @fun_name 函数名 + """ + import datetime + data = {} + path = '{}/data/mod_log.json'.format(get_panel_path()) + if os.path.exists(path): + try: + data = json.loads(readFile(path)) + except: + pass + + if type(data) != dict: + data = {} + + key = datetime.datetime.now().strftime("%Y-%m-%d") + if not key in data: data[key] = {} + + if not mod_name in data[key]: + data[key][mod_name] = {} + + if not fun_name in data[key][mod_name]: + data[key][mod_name][fun_name] = 0 + + data[key][mod_name][fun_name] += count + + writeFile(path, json.dumps(data)) + return True + + +headers_filter_rules = None + + +def filter_headers(): + ''' + @name 过滤请求头 + @author hwliang<2021-12-18> + @return dict + ''' + global headers_filter_rules + + # 预编译过滤规则 + if not headers_filter_rules: + headers_filter_rules = { + 'host': re.compile(r'^[\w\.\-\:]+$'), + 'accept': re.compile(r'^[\w\s\.\-\*\/\,\=\;\+]+$'), + 'accept-encoding': re.compile(r'^[\w\s\.\-\*\/\,]+$'), + 'accept-language': re.compile(r'^[\w\s\.\-\*\/\,\=\:\;]+$'), + 'cache-control': re.compile(r'^[\w\s\.\-\=\;]+$'), + 'connection': re.compile(r'^[\w\s\.\-]+$'), + 'content-length': re.compile(r'^[\d]+$'), + 'cookie': re.compile(r'^[\w\s\=\%\+\&\;\:\@\$\,\.\-\_\*\/\?\!\~\#]+$'), + 'origin': re.compile(r'^(http|https)://[\w\.\-\?\=\&\/\:]+$'), + 'pragma': re.compile(r'^[\w\s\.\-]+$'), + 'referer': re.compile(r'^(http|https)://[\w\.\-\?\=\&\/\:\%\#\~\!\*\+\@]+$'), + 'user-agent': re.compile(r'^[\w\s\.\-\*\/\,\(\)\=\+\;\:\@\$\,\.\-\_\~\#]+$'), + 'x-cookie-token': re.compile(r'^\w+$'), + 'x-http-token': re.compile(r'^\w+$'), + 'X-KL-Ajax-Request': re.compile(r'^\w+$'), + 'X-Requested-With': re.compile(r'^\w+$') + } + from flask import request + headers = request.headers + skeys = headers_filter_rules.keys() + + for k in skeys: + v = headers.get(k, None) + if not v: continue + if not headers_filter_rules[k].match(v): + return False + return True + + +def trim(data): + """ + @去除所有空格 + """ + return data.replace(' ', '').strip() + + +def get_os(_os='windows'): + """ + @验证系统版本 + """ + src_os = 'windows' + if os.path.exists('/www/server/panel'): src_os = 'linux' + if src_os == _os: return True + return False + + +def get_file_list(path, flist): + """ + 递归获取目录所有文件列表 + @path 目录路径 + @flist 返回文件列表 + """ + if os.path.exists(path): + files = os.listdir(path) + flist.append(path) + for file in files: + if os.path.isdir(path + '/' + file): + get_file_list(path + '/' + file, flist) + else: + flist.append(path + '/' + file) + + +def writeFile2(filename, s_body, mode='w+'): + """ + 写入字节文件内容 + @filename 文件名 + @s_body 欲写入的内容 + + """ + try: + fp = open(filename, mode); + fp.write(s_body) + fp.close() + return True + except: + return False + + +def check_obj_upgrade(_obj, filename=None): + ''' + @name 检查指定模块是否修改 + @author hwliang + @param 文件名 + @param 模块对象 + @return void + ''' + + # 引用缓存 + try: + from BTPanel import cache + except: + return + + # 是否传递文件名? + if not filename: + filename = _obj.__file__ + + # 获取文件修改时间 + skey = "obj_up_{}".format(md5(filename)) + mtime = os.path.getmtime(filename) # 当前 + old_mtime = cache.get(skey) # 旧的 + + # 直接设置当前修改时间 + if not old_mtime: + cache.set(skey, mtime) + return + + # 检查是否修改 + if old_mtime == mtime: + return + + # 重新加载模块 + import importlib + importlib.reload(_obj) + cache.set(skey, mtime) + + +def version_to_tuple(version): + ''' + @name 将版本号转为元组 + @version 字符串版本号 + @return 元组版本号 + ''' + if not version: + return () + if not isinstance(version, str): + return version + version = re.sub(r"[^\.\d]+", "", version) + version = version.split('.') + version = tuple(map(int, version)) + return version + + +def set_search_history(mod_name, key, val): + """ + @保存搜索历史 + @mod_name 模块名称 + @key 关键字 + @val string 搜索内容 + """ + if not val: return False + + max = 10 + p_file = get_panel_path() + m_file = p_file + '/data/search.limit' + d_file = p_file + '/data/search.json' + try: + sdata = int(readFile(m_file)) + if sdata: max = sdata + except: + pass + + result = {} + try: + result = json.loads(readFile(d_file)) + except: + pass + + if not mod_name in result: result[mod_name] = {} + if not key in result[mod_name]: result[mod_name][key] = [] + + n_list = [] + for item in result[mod_name][key]: + if item['val'].strip() != val.strip(): n_list.append(item) + + n_list.append({'val': val, 'time': int(time.time())}) + + result[mod_name][key] = n_list[len(n_list) - max:] + + writeFile(d_file, json.dumps(result)) + return True + + +def get_search_history(mod_name, key): + """ + @获取搜索历史 + @mod_name string 模块名称 + @key string 关键字 + """ + print(mod_name, key) + result = [] + d_file = get_panel_path() + '/data/search.json' + try: + result = json.loads(readFile(d_file))[mod_name][key] + except: + pass + + result = sorted(result, key=lambda x: x['time'], reverse=True) + + return result + + +def set_dir_history(mod_name, key, val): + """ + @设置目录打开历史 + @mod_name string 模块名称 + @key string 函数名 + @val string 路径 + """ + if not val: return False + + max = 10 + result = {} + d_file = get_panel_path() + '/data/dir_history.json' + try: + result = json.loads(readFile(d_file)) + except: + pass + + if not mod_name in result: result[mod_name] = {} + if not key in result[mod_name]: result[mod_name][key] = [] + + data = result[mod_name][key] + for info in data: + if val.find(info['val']) >= 0: + if time.time() - info['time'] < 15: + data.remove(info) + + data.append({'val': val, 'time': int(time.time())}) + result[mod_name][key] = data[0:max] + writeFile(d_file, json.dumps(result)) + return True + + +def get_dir_history(mod_name, key): + """ + @获取目录打开历史 + @mod_name string 模块名称 + @key string 关键字 + """ + result = [] + d_file = get_panel_path() + '/data/dir_history.json' + try: + result = json.loads(readFile(d_file))[mod_name][key] + except: + pass + return result + + +def get_run_pip(): + pass + + +def install_pip(shell): + """ + @name 安装pip模块 + @author cjxin + @param shell 安装命令 + """ + if get_os('windows'): + os.system(get_run_pip(shell.replace('pip', '[PIP]'))) + else: + os.system(shell.replace('pip', 'btpip')) + + +def is_domain(domain): + """ + @验证是否域名 + """ + reg = r"^([\w\-\*]{1,100}\.){1,10}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$"; + if re.match(reg, domain): return True + return False + + +def init_msg(module): + """ + 初始化消息通道 + @module 消息通道模块名称 + """ + import os, sys + if not os.path.exists('class/msg'): os.makedirs('class/msg') + panelPath = get_panel_path() + + sfile = 'class/msg/{}_msg.py'.format(module) + if not os.path.exists(sfile): return False + sys.path.insert(0, "{}/class/msg".format(panelPath)) + + msg_main = __import__('{}_msg'.format(module)) + try: + mod_reload(msg_main) + except: + pass + return eval('msg_main.{}_msg()'.format(module)) + + +def push_argv(msg): + """ + @处理短信参数,否则会被拦截 + """ + if is_ipv4(msg): + tmp1 = msg.split('.') + msg = '{}.***.***.{}'.format(tmp1[0], tmp1[3]) + else: + if is_domain(msg): + msg = msg.replace('.', '_') + return msg + + +def check_sms_argv(data): + """ + @批量处理短信参数,否则会被拦截 + """ + for key in data: + val = data[key] + if type(val) == str: + data[key] = push_argv(val) + return data + + +""" +@获取推送ip +""" + + +def get_push_address(): + ip = push_argv(GetLocalIp()) + return ip + + +def get_ips_area(ips): + ''' + @name 获取ip地址所在地 + @author cjxin + @param ips + @return list + ''' + import PluginLoader + + args = dict_obj() + args.model_index = 'safe' + args.ips = ips + + res = PluginLoader.module_run("ips", "get_ip_area", args) + return res + + +def return_area(result, key): + """ + @name 格式化返回带IP归属地的数组 + @param result 数据数组 + @param key ip所在字段 + @return list + """ + tmps = [] + for data in result: + data['area'] = '' + tmps.append(data[key]) + + res = get_ips_area(tmps) + if 'status' in res: return result + + for data in result: + if data[key] in res: + data['area'] = res[data[key]] + return result + + +def get_network_ip(): + """ + @name 获取本机ip + @return string + """ + + import socket + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + ip = s.getsockname()[0] + return ip + finally: + s.close() + return '127.0.0.1' + + +def get_server_ip(): + """ + @获取服务器外网ip + """ + + user_file = '{}/data/userInfo.json'.format(get_panel_path()) + if os.path.exists(user_file): + try: + userTmp = json.loads(readFile(user_file)) + return userTmp['address'] + except: + pass + + return GetLocalIp() + + +def get_push_info(title, slist=[]): + """ + @name 获取推送信息 + @param title 推送标题 + @param slist 推送追加的列表 + 如:slist = ['>发送内容:xxx'] + @return dict + """ + data = {} + data['title'] = title + data['ip'] = get_server_ip() + + data['local_ip'] = get_network_ip() + data['time'] = format_date() + data['server_name'] = GetConfigValue('title') + + dlist = [ + "#### {}".format(data['title']), + ">ServerHost: " + data['server_name'], + ">IP Address: {}(Internet) {}(Internal)".format(data['ip'], data['local_ip']), + ">Send Time: " + data['time'] + ] + dlist.extend(slist) + msg = "\n\n".join(dlist) + data['msg'] = msg + data['list'] = dlist + return data + + +def write_push_log(module, msg, res): + """ + @name 写推送日志 + @module string 模块名称 + @msg string 消息内容 + @res dict 推送结果 + """ + user = '' + for key in res: + status = 'Success' + if res[key] == 0: status = 'Fail' + user += '[ {}:{} ] '.format(key, status) + + if not user: user = '[ Default ] ' + try: + msg_obj = init_msg(module) + if msg_obj: module = msg_obj.get_version_info(None)['title'] + except: + pass + + log = 'Title:[{}],method to informe:[{}],recipient:{}'.format(xsssec(msg), module, user) + WriteLog('Alarm notification', log) + return True + + +def push_msg(module, data): + """ + @name 推送消息 + @param module 模块名称 + @param msg 消息内容 + @return dict + """ + msg_obj = init_msg(module) + if not msg_obj: + returnMsg(False, 'Module {} does not exist!'.format(module)) + + res = msg_obj.push_data(data) + return res + + +def check_chinese(data): + """ + @name 判断字符串是否包含中文 + """ + if re.search(u'[\u4e00-\u9fa5]', data): + return True + return False + + +def is_ssl(): + ''' + @name 是否开启SSL + @author hwliang + @return bool + ''' + return os.path.exists(get_panel_path() + '/data/ssl.pl') + + +def get_cookie(key, default=None): + ''' + @name 获取指定Cookie值 + @author hwliang + @param key Cookie键 + @param default 默认值 + @return str + ''' + from flask import request + return request.cookies.get(key, default) + + +def get_csrf_cookie_token_key(): + ''' + @name 获取CSRF Cookie Key + @author hwliang + @return string + ''' + if is_ssl(): + token_key = 'request_token' + else: + token_key = 'request_token' + return token_key + + +def get_csrf_cookie_token_value(): + ''' + @name 获取CSRF Cookie Value + @author hwliang + @return string + ''' + token_key = get_csrf_cookie_token_key() + return get_cookie(token_key) + + +def get_csrf_html_token_key(): + ''' + @name 获取CSRF HTML Key + @author hwliang + @return string + ''' + if is_ssl(): + token_key = 'request_token_head' + else: + token_key = 'request_token_head' + return token_key + + +def get_csrf_html_token_value(): + ''' + @name 获取CSRF HTML Value + @author hwliang + @return string + ''' + token_key = get_csrf_html_token_key() + return get_cookie(token_key) + + +def get_csrf_sess_html_token_value(): + ''' + @name 从SESSION获取CSRF HTML value + @author hwliang + @return string + ''' + from flask import session + return session.get(get_csrf_html_token_key(), "") + + +def get_csrf_sess_cookie_token_value(): + ''' + @name 从SESSION获取CSRF Cookie value + @author hwliang + @return string + ''' + from flask import session + return session.get(get_csrf_cookie_token_key(), "") + + +def get_sys_install_bin(): + ''' + @name 获取系统包管理器命令 + @author hwliang + @return string + ''' + install_bins = ['/usr/bin/yum', '/usr/bin/apt-get', '/usr/bin/dnf'] + for bin in install_bins: + if os.path.exists(bin): + return bin + return '' + + +def get_firewall_status(): + ''' + @name 获取系统防火墙状态 + @author hwliang + @return int 0.关闭 1.开启 -1.未安装 + ''' + import psutil + firewall_files = {'/usr/sbin/firewalld': "pid", '/usr/bin/firewalld': "pid", + '/usr/sbin/ufw': "/usr/sbin/ufw status|grep 'Status: active'", + '/sbin/ufw': "/sbin/ufw status |grep 'Status: active'", + '/usr/sbin/iptables': "service iptables status|grep 'Chain INPUT'"} + for f in firewall_files.keys(): + if not os.path.exists(f): continue + _cmd = firewall_files[f] + if _cmd != "pid": + res = ExecShell(_cmd) + if res[0].strip(): + return 1 + else: + return 0 + for pid in psutil.pids(): + try: + p = psutil.Process(pid) + if f in p.cmdline(): + return 1 + except: + pass + return 0 + return -1 + + +def get_panel_port(): + ''' + @name 获取面板端口 + @author hwliang + @return int + ''' + port_file = '{}/data/port.pl'.format(get_panel_path()) + if not os.path.exists(port_file): + return 8888 + try: + return int(readFile(port_file)) + except: + return 8888 + + +def install_sys_firewall(): + ''' + @name 安装系统防火墙 + @author hwliang + @return bool + ''' + if get_firewall_status() != -1: return True + + install_bin = get_sys_install_bin() + if not install_bin: return False + if install_bin.find('apt-get') != -1: + ExecShell("{} install -y ufw".format(install_bin)) + if get_firewall_status() != -1: + _cmd = '''ufw allow 20/tcp +ufw allow 21/tcp +ufw allow 22/tcp +ufw allow 80/tcp +ufw allow 443/tcp +ufw allow ${panelPort}/tcp +ufw allow ${sshPort}/tcp +ufw allow 39000:40000/tcp +ufw_status=`ufw status` +echo y|ufw enable +ufw default deny +ufw reload +'''.format(panelPort=get_panel_port(), sshPort=get_ssh_port()) + ExecShell(_cmd) + elif install_bin.find('yum') != -1 or install_bin.find('dnf') != -1: + ExecShell("{} install -y firewalld".format(install_bin)) + if get_firewall_status() != -1: + _cmd = '''systemctl enable firewalld + systemctl start firewalld + firewall-cmd --set-default-zone=public > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=20/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=21/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=22/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=80/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=443/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port={panelPort}/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port={sshPort}/tcp > /dev/null 2>&1 + firewall-cmd --permanent --zone=public --add-port=39000-40000/tcp > /dev/null 2>&1 + firewall-cmd --reload +'''.format(panelPort=get_panel_port(), sshPort=get_ssh_port()) + ExecShell(_cmd) + + return False + + +def check_firewall_rule(port): + ''' + @name 检测防火墙是否已经添加规则 + @author cjxin + @param port int 端口号 + ''' + + args = dict_obj() + args.model_index = 'safe' + args.port = port + + import PluginLoader + res = PluginLoader.module_run("firewall", "check_firewall_rule", args) + return res + + +def add_firewall_rule(port, protocol='tcp', types='accept', address='0.0.0.0/0', brief=None): + """ + @name 添加防火墙规则 + @author cjxin + @param port int 端口号 + @param protocol string 协议类型 tcp udp + @param types string 添加类型 accept reject + @param address string 地址 + @param brief string 描述 + """ + + args = dict_obj() + args.model_index = 'safe' + args.port = port + args.protocol = protocol + args.types = types + args.address = address + args.brief = brief + if not brief: args.brief = str(port) + + import PluginLoader + res = PluginLoader.module_run("firewall", "create_rules", args) + return res + + +def del_firewall_rule(port, protocol='tcp', types='accept', address='0.0.0.0/0'): + ''' + @name 删除防火墙规则 + @author cjxin + @param port int 端口号 + @param protocol str 协议 + @param types str 类型 + @param address str 地址 + ''' + + args = dict_obj() + args.model_index = 'safe' + args.port = port + args.protocol = protocol + args.types = types + args.address = address + import PluginLoader + res = PluginLoader.module_run("firewall", "remove_rules", args) + return res + + +def is_aarch(): + ''' + @name 是否是arm架构 + @author hwliang + @return bool + ''' + uname = None + if hasattr(os, 'uname'): uname = os.uname() + aarch_list = ['aarch64', 'aarch'] + try: + return uname.machine in aarch_list + except: + if uname: + return uname[-1] in aarch_list + return False + + +def is_process_exists_by_cmdline(_cmd): + ''' + @name 根据命令行参数查找进程是否存在 + @author hwliang + @param _cmd 命令行 + @return bool + ''' + if isinstance(_cmd, str): + _cmd = [_cmd] + if not isinstance(_cmd, list): + return False + for pid in psutil.pids(): + try: + p = psutil.Process(pid) + cmd_line = p.cmdline() + for _c in _cmd: + if _c in cmd_line: + return True + except: + continue + return False + + +def is_process_exists_by_exe(_exe): + ''' + @name 根据执行文件路径查找进程是否存在 + @author hwliang + @param _exe 命令行 + @return bool + ''' + if isinstance(_exe, str): + _exe = [_exe] + if not isinstance(_exe, list): + return False + + for process in psutil.process_iter(): + try: + _exe_bin = process.exe() + for _e in _exe: + if _exe_bin.find(_e) != -1: return True + except: + continue + return False + + +def is_process_exists_by_name(_name): + ''' + @name 根据进程名查找进程是否存在 + @author hwliang + @param _name 命令行 + @return bool + ''' + if isinstance(_name, str): + _name = [_name] + if not isinstance(_name, list): + return False + for pid in psutil.pids(): + try: + p = psutil.Process(pid) + name = p.name() + for _n in _name: + if name == _n: return True + except: + continue + return False + + +def is_mysql_process_exists(): + ''' + @name 检查mysql进程是否存在 + @author hwliang + @return bool + ''' + _exe = ['server/mysql/bin/mysqld_safe', 'server/mysql/bin/mariadbd', 'server/mysql/bin/mysqld'] + return is_process_exists_by_exe(_exe) + + +def is_redis_process_exists(): + ''' + @name 检查redis进程是否存在 + @author hwliang + @return bool + ''' + _exe = ['server/redis/src/redis-server'] + return is_process_exists_by_exe(_exe) + + +def is_pure_ftpd_process_exists(): + ''' + @name 检查pure-ftpd进程是否存在 + @author hwliang + @return bool + ''' + _exe = ['server/pure-ftpd/sbin/pure-ftpd'] + return is_process_exists_by_exe(_exe) + + +def is_php_fpm_process_exists(name): + ''' + @name 检查php-fpm进程是否存在 + @author hwliang + @return bool + ''' + _php_version = name.split('-')[-1] + _exe = ['server/php/{}/sbin/php-fpm'.format(_php_version)] + return is_process_exists_by_exe(_exe) + + +def is_nginx_process_exists(): + ''' + @name 检查nginx进程是否存在 + @author hwliang + @return bool + ''' + _exe = ('server/nginx/sbin/nginx', 'server/nginx/nginx/sbin/nginx') + for i in _exe: + result = is_process_exists_by_exe(i) + if result: return result + return False + + +def is_httpd_process_exists(): + ''' + @name 检查httpd进程是否存在 + @author hwliang + @return bool + ''' + _exe = ['server/apache/bin/httpd'] + return is_process_exists_by_exe(_exe) + + +def is_memcached_process_exists(): + ''' + @name 检查memcached进程是否存在 + @author hwliang + @return bool + ''' + _exe = ['/usr/local/memcached/bin/memcached'] + return is_process_exists_by_exe(_exe) + + +def is_mongodb_process_exists(): + ''' + @name 检查mongodb进程是否存在 + @author hwliang + @return bool + ''' + _exe = ['server/mongodb/bin/mongod'] + return is_process_exists_by_exe(_exe) + + +def check_auth_ip(): + """ + @name 检测api和www的服务器ip是否一致 + @auther cjxin 2022-09-13 + @return bool + """ + import http_requests + result = {'www': '', 'api': ''} + res = http_requests.post('https://www.bt.cn/api/getIpAddress', data={}, timeout=5, headers={}) + if res.status_code == 200: + result['www'] = res.text + + res1 = http_requests.post('https://api.bt.cn/api/getIpAddress', data={}, timeout=5, headers={}) + if res1.status_code == 200: + result['api'] = res1.text + + return result + + +def set_func(key, count=0): + """ + 设置指定key的操作时间 + @key 面板访问函数 + """ + path = 'data/func.json' + data = {} + try: + data = json.loads(readFile(path)) + except: + pass + if not key in data: + data[key] = {} + data[key]['time'] = 0 + data[key]['count'] = 0 + data[key]['time'] = int(time.time()) + + if count > 0: + data[key]['count'] = count + else: + data[key]['count'] += 1 + writeFile(path, json.dumps(data)) + + +def get_func(key): + """ + 获取指定功能的操作时间 + @key 面板函数 + """ + path = 'data/func.json' + ret = {} + ret['count'] = 0 + ret['time'] = 0 + if not os.path.exists(path): return ret + data = {} + try: + data = json.loads(readFile(path)) + except: + pass + if not key in data: return ret + return data[key] + + +def set_cache_func(key, info): + """ + 设置指定key的操作时间 + @key 缓存的key函数 + """ + data = {} + path = '{}/data/cache_func.json'.format(get_panel_path()) + + try: + data = json.loads(readFile(path)) + except: + pass + if not key in data: data[key] = {} + + data[key]['time'] = int(time.time()) + data[key]['data'] = info + + writeFile(path, json.dumps(data)) + + +def get_cache_func(key): + """ + 获取指定功能的操作时间 + @key 缓存的key函数 + """ + + ret = {} + data = {} + ret['data'] = '' + ret['time'] = 0 + path = '{}/data/cache_func.json'.format(get_panel_path()) + if not os.path.exists(path): + return ret + try: + data = json.loads(readFile(path)) + except: + pass + if not key in data: return ret + return data[key] + + +def set_split_logs(path, status=1, info=None): + """ + @name 添加日志切割 + @path 日志路径, + @data dict + { + 'type':'day/size' + 'limit': 180,保留份数 + 'size': 日志超过多少进行切割,type=size时生效 + 'callback': 回调命令,部分日志切割后需要重启服务 + } + """ + + data = {} + sfile = '{}/data/cutting_log.json'.format(get_panel_path()) + if os.path.exists(sfile): + try: + data = json.loads(readFile(sfile)) + except: + pass + + if path in data: + del data[path] + + if status: + if not info: + return False + if not 'type' in info or not 'limit' in info: + return False + data[path] = info + writeFile(sfile, json.dumps(data)) + + # 计划任务切割 + echo = md5(md5('set_split_logs')) + find = M('crontab').where('echo=?', (echo,)).find() + + try: + import crontab + args_obj = dict_obj() + + if not find: + cronPath = GetConfigValue('setup_path') + '/cron/' + echo + shell = '{} -u /www/server/panel/script/logSplit.py'.format(sys.executable) + writeFile(cronPath, shell) + + args_obj.id = M('crontab').add( + 'name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress', + ("[删除]切割日志文件", 'minute-n', '10', '0', '0', echo, time.strftime('%Y-%m-%d %X', time.localtime()), 0, '', + 'localhost', 'toShell', '', shell, '')) + crontab.crontab().set_cron_status(args_obj) + else: + cron_path = get_cron_path() + if os.path.exists(cron_path): + cron_s = readFile(cron_path) + if cron_s.find(echo) == -1: + M('crontab').where('echo=?', (echo,)).setField('status', 0) + args_obj.id = find['id'] + crontab.crontab().set_cron_status(args_obj) + return True + except: + pass + + return False + + +def get_admin_path(): + ''' + @name 取安全入口 + @author hwliang + @return string + ''' + login_path = '/login' + path = '{}/data/admin_path.pl'.format(get_panel_path()) + if not os.path.exists(path): return login_path + admin_path = readFile(path) + if not admin_path: return login_path + admin_path = admin_path.strip() + if admin_path in ['', '/']: + return login_path + if admin_path[-1] == '/': admin_path = admin_path[:-1] + return admin_path + + +def get_improvement(): + ''' + @name 获取用户体验改进计划状态 + @author hwliang + @return bool + ''' + tip_file = '{}/data/improvement.pl'.format(get_panel_path()) + tip_file_set = '{}/data/is_set_improvement.pl'.format(get_panel_path()) + if not os.path.exists(tip_file_set): + return True + return os.path.exists(tip_file) + + +def is_spider(): + ''' + @name 判断是否为爬虫 + @return bool + ''' + from BTPanel import request + import panelDefense + p = panelDefense.bot_safe() + return not p.spider(request.headers.get('User-Agent'), request.remote_addr) + + +# def get_rsa_public_key_file(): +# ''' +# @name 获取RSA公钥文件路径 +# @author hwliang +# @return str +# ''' +# return '{}/data/rsa_public_key.pem'.format(get_panel_path()) + +# def get_rsa_private_key_file(): +# ''' +# @name 获取RSA私钥文件路径 +# @author hwliang +# @return str +# ''' +# return '{}/data/rsa_private_key.pem'.format(get_panel_path()) + + +def get_rsa_public_key(): + ''' + @name 获取RSA公钥内容 + @author hwliang + @return str + ''' + from BTPanel import session + pub_key = 'rsa_public_key' + public_key = session.get(pub_key) + if not public_key: + create_rsa_key() + public_key = session.get(pub_key) + return public_key + + # path = get_rsa_public_key_file() + # if not os.path.exists(path): create_rsa_key() + # if not os.path.exists(path): return '' + # return readFile(path) + + +def get_rsa_private_key(): + ''' + @name 获取RSA私钥内容 + @author hwliang + @return str + ''' + from BTPanel import session + prv_key = 'rsa_private_key' + private_key = session.get(prv_key) + if not private_key: + create_rsa_key() + private_key = session.get(prv_key) + return private_key + + +def create_rsa_key(): + ''' + @name 创建RSA密钥 + @author hwliang + @return bool + ''' + try: + # private_key_file = get_rsa_private_key_file() + # public_key_file = get_rsa_public_key_file() + # if os.path.exists(private_key_file) and os.path.exists(public_key_file): return True + from BTPanel import session + pub_key = 'rsa_public_key' + prv_key = 'rsa_private_key' + if pub_key in session and prv_key in session: + return True + try: + from Crypto.PublicKey import RSA + key = RSA.generate(1024) + private_key = key.exportKey("PEM") + public_key = key.publickey().exportKey("PEM") + except: + is_re_install = '{}/data/pycryptodome_re_install.pl'.format(get_panel_path()) + if not os.path.exists(is_re_install): + os.system("nohup btpip install pycryptodome -I &> /dev/null &") + writeFile(is_re_install, 'True') + + priv_pem = '/tmp/private.pem' + pub_pem = '/tmp/public.pem' + ExecShell("openssl genrsa -out {} 1024".format(priv_pem)) + ExecShell("openssl rsa -pubout -in {} -out {}".format(priv_pem, pub_pem)) + if not os.path.exists(priv_pem) or not os.path.exists(pub_pem): + return False + + private_key = readFile(priv_pem, 'rb') + public_key = readFile(pub_pem, 'rb') + + if os.path.exists(priv_pem): os.remove(priv_pem) + if os.path.exists(pub_pem): os.remove(pub_pem) + + session[pub_key] = public_key.decode('utf-8').replace("\n", "") + session[prv_key] = private_key.decode('utf-8') + + # writeFile(private_key_file,private_key,'wb+') + # writeFile(public_key_file,public_key,'wb+') + return True + except: + print_log(get_error_info()) + return False + + +def rsa_encrypt(data): + ''' + @name RSA加密数据 + @param data str 要加密的数据 + @return str + ''' + # 分片长度 1024 / 8 - 11 = 117 + split_length = 117 + try: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs + + # 初始化RSA加密对象 + public_key = get_rsa_public_key() + cipher_public = Cipher_pkcs.new(RSA.importKey(public_key)) + + # 分片加密 + data = data.encode('utf-8') + encrypted_arr = [] + for i in range(0, len(data), split_length): + d = data[i:i + split_length] + encrypted_data = cipher_public.encrypt(d) + encrypted_base64 = base64.b64encode(encrypted_data).decode() + encrypted_arr.append(encrypted_base64) + + # 用换行符拼接 + return "\n".join(encrypted_arr) + except: + print_log(get_error_info()) + return '' + + +def rsa_decrypt(data): + ''' + @name RSA解密数据 + @param data str 要解密的数据 + @return str + ''' + try: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs + + # 初始化RSA解密对象 + private_key = get_rsa_private_key() + cipher_private = Cipher_pkcs.new(RSA.importKey(private_key)) + + # 分片解密 + decrypted_str = b"" + for d in data.split("\n"): + if not d: continue + res = base64.b64decode(d) + if not res: continue + decrypted_data = cipher_private.decrypt(res, None) + decrypted_str += decrypted_data + return decrypted_str.decode('utf-8') + except: + print_log(get_error_info()) + return '' + + +def rsa_encrypt_for_private_key(data): + ''' + @name RSA私钥加密数据 + @author hwliang + @param data str 要加密的数据 + @return str + ''' + # 分片长度 1024 / 8 - 11 = 117 + split_length = 117 + try: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs + + # 初始化RSA加密对象 + private_key = get_rsa_private_key() + cipher_private = Cipher_pkcs.new(RSA.importKey(private_key)) + + # 分片加密 + data = data.encode('utf-8') + encrypted_arr = [] + for i in range(0, len(data), split_length): + d = data[i:i + split_length] + encrypted_data = cipher_private.encrypt(d) + encrypted_base64 = base64.b64encode(encrypted_data).decode() + encrypted_arr.append(encrypted_base64) + + # 用换行符拼接 + return "\n".join(encrypted_arr) + except: + print_log(get_error_info()) + return '' + + +def get_client_hash(): + ''' + @name 获取客户端HASH + @author hwliang + @return str + ''' + from flask import session, request + is_tmp_login = session.get('tmp_login') + if is_tmp_login: + client_hash = md5(request.remote_addr) + else: + skey = 'client_ips' + ckey = 'client_sync_count' + client_ips = session.get(skey, []) + client_sync_count = session.get(ckey, 0) + + # 是否唯一IP + if len(client_ips) <= 1: + # 唯一IP连续访问次数超过100次,使用IP+UA生成HASH + r_max = 101 + if client_sync_count >= r_max - 1: + client_hash = md5(request.remote_addr) + if client_sync_count < r_max: + session['client_hash'] = client_hash + client_sync_count += 1 + session[ckey] = client_sync_count + return client_hash + + # 记录IP + if not request.remote_addr in client_ips: + client_ips.append(request.remote_addr) + session[skey] = client_ips + + # 记录访问次数 + client_sync_count += 1 + session[ckey] = client_sync_count + + # 非唯一IP,使用UA生成HASH + client_hash = md5('') + + return client_hash + + +def check_client_hash(): + ''' + @name 验证客户端HASH + @author hwliang + @return bool + ''' + # 是否关闭验证 + not_tip = '{}/data/not_check_ip.pl'.format(get_panel_path()) + if os.path.exists(not_tip): return True + from BTPanel import session, request + # 如果未开启SSL,不验证 + if request.scheme == 'https': return True + skey = 'client_hash' + client_hash = get_client_hash() + if not skey in session: + session[skey] = client_hash + return True + + if session[skey] != client_hash: + WriteLog('User login', 'Client HASH verification failed, has been forced to log out!') + return False + return True + + +def shell_quote(cmd): + ''' + @name shell转义 + @author hwliang + @param cmd str 要转义的命令 + @return str + ''' + if not cmd: return '' + if isinstance(cmd, bytes): cmd = cmd.decode('utf-8') + if not isinstance(cmd, str): return cmd + try: + import shlex + return shlex.quote(cmd) + except: + try: + import pipes + return pipes.quote(cmd) + except: + return cmd + + +def get_div(div): + sql = M('sqlite_master') + if not sql.where('type=? AND name=? AND sql LIKE ?', ('table', 'div_list', '%div%')).count(): + sql_str = '''CREATE TABLE IF NOT EXISTS `div_list` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`div` TEXT +)''' + sql.execute(sql_str) + my_div = sql.table('div_list').where('id=1', ()).getField('div') + if not my_div: + sql.table('div_list').insert({'div': div}) + my_div = div + return my_div + + +def set_tasks_run(data): + ''' + @name 设置运行时间 + @param data dict 数据 + @param data.type int 类型 1:面板 2:插件 + @param data.time int 执行时间(必传) + @param data.name str 插件名称、模块名称(必传) + @param data.title str 插件中文名(必传) + @param data.fun str 执行方法(必传) + @param data.args dict 参数 + + ''' + spath = '{}/data/tasks'.format(get_panel_path()) + if not os.path.exists(spath): os.makedirs(spath, 384) + + task_file = '{}/{}'.format(spath, md5(str(time.time()))) + writeFile(task_file, json.dumps(data)) + return returnMsg(True, task_file) + + +def Get_ip_info(get_speed=False, get_user=True): + ''' + 获取bt官网ip归属地列表 + @author wzz + @return: list[dict{}] + ''' + host_list = json.loads(readFile("config/hosts_dict.json")) + print("host_list: ", host_list) + + # 推荐,一般,较差,不推荐,不测速时,ipv6,用户服务器IP + level = (1, 2, 3, 4, 5, 6, 0) + user_server_ipaddress = [] + if get_user: + user_server_ipaddress = get_user_server_ipaddress(host_list, level) + bt_host = get_bt_hosts(get_speed, host_list, level) + ips_result = user_server_ipaddress + bt_host + if ips_result: return ips_result + + +def get_user_server_ipaddress(host_list, level): + ''' + 获取服务器公网ip归属地信息 + @param host_list: host列表 + @param level: 等级元组 + @return: + ''' + ips_result = [] + headers = {"host": "www.bt.cn"} + for host in host_list: + try: + new_url = "https://{}/Api/getIpAddress".format(host["ip"]) + m_str = HttpGet(new_url, 1, headers=headers) + ipaddress = re.search(r"^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$", m_str).group(0) + s_ip_info = get_free_ip_info("{}".format(ipaddress)) + if "ip" in s_ip_info.keys(): + s_ip_info["info"] = "本服务器公网IP归属地信息" + s_ip_info["level"] = level[-1] + ips_result.append(s_ip_info) + return ips_result + except: + continue + if not ips_result: + return [{'continent': '', 'country': '未知地区', 'province': '', 'city': '', 'region': '', 'carrier': '', + 'division': '', 'en_country': '', 'en_short_code': '', 'longitude': '', + 'latitude': '', 'info': '本服务器公网IP归属地信息', 'ip': GetLocalIp(), 'level': 0}] + + +def get_bt_hosts(get_speed, host_list, level): + ''' + 获取bt官网ip归属地列表 + @param get_speed: 是否测速 + @param host_list: 传host列表 + @param level: 传等级元组 + @return: + ''' + ips_result = [] + + for ip in host_list: + ipv6 = { + "continent": "", + "country": "", + "province": "", + "city": "ipv6 地址", + "region": "", + "carrier": "", + "division": "", + "en_country": "", + "en_short_code": "", + "longitude": "", + "latitude": "", + "info": "该节点为ipv6地址,若服务器无ipv6请勿选择!", + "ip": "", + "level": None + } + try: + # 获取节点响应延迟 + if get_speed: n_net, n_ping = get_timeout("https://{}".format(ip["ip"]) + ':80/net_test', 1) + if not is_ipv4(ip["ip"]): + ipv6['ip'] = ip["ip"] + # ipv6地址默认一般推荐 + ipv6['level'] = level[-2] + if get_speed: ipv6['speed'] = "" + ips_result.append(ipv6) + continue + ip_result = get_free_ip_info("{}".format(ip["ip"])) + if "ip" in ip_result.keys(): + ip_result['level'] = level[-3] + if get_speed: + if int(n_ping) < 100: ip_result['level'] = level[0] + if 100 < int(n_ping) < 500: ip_result['level'] = level[1] + if int(n_ping) > 500: ip_result['level'] = level[2] + ip_result["speed"] = n_ping + 500 + ips_result.append(ip_result) + continue + if "info" in ip_result.keys(): + if ip_result["info"] == "未知归属地": + ipv6['ip'] = ip["ip"] + ipv6["city"] = ip["area"] + ipv6['level'] = level[1] + if get_speed: ipv6['speed'] = "" + ipv6["info"] = "节点无法测速,请选择离您服务器最近的尝试!" + ips_result.append(ipv6) + except: + continue + if len(ips_result) < 2 and ips_result[-1]["city"] == "ipv6 地址": ips_result.pop(-1) + return ips_result + + +def set_home_host2(host): + """ + @name 设置官网hosts + @author wzz + @param host IP地址 + @return void + """ + msg = "请尝试点击【清理旧节点】,如果仍然不行,请联系堡塔运维! https://www.bt.cn/bbs" + www_set = ExecShell("echo \"{} www.bt.cn\" >> /etc/hosts".format(host)) + api_set = ExecShell("echo \"{} api.bt.cn\" >> /etc/hosts".format(host)) + if not www_set[1] and not api_set[1]: return returnMsg(True, "节点设置成功") + if www_set[1]: return returnMsg(False, "节点设置失败: {}, {}".format(www_set[1], msg)) + if api_set[1]: return returnMsg(False, "节点设置失败: {}, {}".format(api_set[1], msg)) + return returnMsg(False, "节点设置失败: {}".format(msg)) + + +def Clean_bt_host(): + ''' + 删除bt.cn相关的hosts绑定信息 + @author wzz + @return: + ''' + check_hosts = ExecShell("grep \"bt.cn\" /etc/hosts") + if check_hosts[0]: + result = ExecShell("sed -i \"/bt.cn/d\" /etc/hosts") + if result[1]: return returnMsg(False, "旧节点清理失败: {}".format(result[1])) + return returnMsg(True, "旧节点已清理") + return returnMsg(True, "hosts没有绑定旧节点无需清理") + + +def Set_bt_host(ip=None): + ''' + 设置bt官网(www && api)指定hosts节点 + @author wzz + @param get: 手动设置 get.ip 官网传ip地址,从public.Get_ip_info方法获取 | 自动设置 + @return: + ''' + Clean_bt_host() + + if ip: return set_home_host2(ip) + # 如果不传ip则自动设置 + ips_info = Get_ip_info(get_user=False) + headers = {"host": "www.bt.cn"} + for host in ips_info: + new_url = "https://{}".format(host['ip']) + res = HttpGet(new_url, 1, headers=headers) + if res: + writeFile("{}/data/home_host.pl".format(get_panel_path()), host["ip"]) + result = set_home_host2(host["ip"]) + if result["status"]: + return returnMsg(True, "已自动选择为{}{}的最优节点,运营商是: {}" + .format(host['province'], host['city'], host['carrier'])) + return returnMsg(False, "自动选择节点失败,请尝试手动设置") + + +def set_ownership(directory, user): + ''' + 设置指定目录及目录下所有文件、子目录所属为user + @param directory: + @param user: + @return: + ''' + import pwd + uid = pwd.getpwnam(user).pw_uid + gid = pwd.getpwnam(user).pw_gid + + os.chown(directory, uid, gid) + + for root, dirs, files in os.walk(directory): + for d in dirs: + dir_path = os.path.join(root, d) + os.chown(dir_path, uid, gid) + for f in files: + file_path = os.path.join(root, f) + os.chown(file_path, uid, gid) + + +def set_permissions(directory, permissions): + ''' + 设置指定目录及目录下所有文件、子目录权限为permissions + @param directory: + @param permissions: 传八进制,如0o755 + @return: + ''' + os.chmod(directory, permissions) + + for root, dirs, files in os.walk(directory): + for d in dirs: + dir_path = os.path.join(root, d) + os.chmod(dir_path, permissions) + for f in files: + file_path = os.path.join(root, f) + os.chmod(file_path, permissions) + + +def check_ssl_verify(certPath='ssl/ca.pem'): + ''' + 校验面板设置SSL双向认证证书格式 + @param certPath: + @return: + ''' + if "crl.pem" in certPath: + certKey = readFile(certPath) + if "-----BEGIN X509 CRL-----" not in certKey: + return False + return True + + res = False + openssl = '/usr/local/openssl/bin/openssl' + if not os.path.exists(openssl): openssl = 'openssl' + certPem = readFile(certPath) + if "-----BEGIN CERTIFICATE-----" in certPem and "Certificate" in certPem: + result = ExecShell(openssl + " x509 -in " + certPath + " -noout -subject") + res = True + if len(result[1]) > 2: res = False + if result[0].find('error:') != -1: res = False + return res + + +def is_write_file(): + ''' + @name 测试是否能写入文件 + @return void + ''' + test_file = '/etc/init.d/bt_10000100.pl' + writeFile(test_file, 'True') + if os.path.exists(test_file): + if readFile(test_file) == 'True': + os.remove(test_file) + return True + os.remove(test_file) + return False + + +def stop_syssafe(): + ''' + @name 临时停用系统加固 + @return bool + ''' + # 检测是否可写 + ret = is_write_file() + is_stop_syssafe_file = '{}/data/is_stop_syssafe.pl'.format(get_panel_path()) + # 如果不可写,则尝试停用系统加固 + if not ret: + syssafe_path = get_plugin_path('syssafe') + if os.path.exists(syssafe_path): + writeFile(is_stop_syssafe_file, 'True') + ExecShell("/etc/init.d/bt_syssafe stop") + + # 停用系统加固后再检测一次 + ret = is_write_file() + return ret + + return ret + + +def start_syssafe(): + ''' + @name 恢复系统加固的运行状态 + @return void + ''' + is_stop_syssafe_file = '{}/data/is_stop_syssafe.pl'.format(get_panel_path()) + if os.path.exists(is_stop_syssafe_file): + ExecShell("/etc/init.d/bt_syssafe start") + if os.path.exists(is_stop_syssafe_file): + os.remove(is_stop_syssafe_file) + + +def check_sys_write(): + ''' + @name 检查关键目录是否可写 + @return bool + ''' + return stop_syssafe() + + +def get_root_domain(domain_name): + ''' + @name 根据域名查询根域名和记录值 + @author cjxin<2020-12-17> + @param domain {string} 被验证的根域名 + @return void + ''' + top_domain_list = ['.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn', + '.gov.cn', '.gs.cn', '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn', + '.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', '.jl.cn', '.js.cn', '.jx.cn', + '.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn', '.cn.com'] + old_domain_name = domain_name + top_domain = "." + ".".join(domain_name.rsplit('.')[-2:]) + new_top_domain = "." + top_domain.replace(".", "") + is_tow_top = False + if top_domain in top_domain_list: + is_tow_top = True + domain_name = domain_name[:-len(top_domain)] + new_top_domain + + if domain_name.count(".") > 1: + zone, middle, last = domain_name.rsplit(".", 2) + if is_tow_top: + last = top_domain[1:] + root = ".".join([middle, last]) + else: + zone = "" + root = old_domain_name + return root, zone + + +def check_area_panel(): + ''' + @name: 检查地区限制 + @return: + ''' + import contextlib + areas_dict = get_limit_area() + + # 关闭状态直接返回false + if areas_dict["limit_area_status"] == "false": return False + # 2024/1/3 下午 2:23 兼容配置文件如果为空或者地区为空或配置文件异常,则直接跳过验证 + if len(areas_dict["limit_area"]) == 0: return False + if len(areas_dict["limit_area"]["city"]) == 0: + if "province" in areas_dict["limit_area"] and len(areas_dict["limit_area"]["province"]) == 0: + if "country" in areas_dict["limit_area"] and len(areas_dict["limit_area"]["country"]) == 0: + return False + + client_ip = GetClientIp() + # 本地访问直接返回false + if client_ip in ['127.0.0.1', 'localhost', '::1']: return False + ip_area_dict = get_ip_location(client_ip) + # 没有查询到地区返回false,内网地址直接返回false + if not ip_area_dict: return False + if ip_area_dict.raw["country"]["country"] == "Internal network address": return False + try: + error_str = "

                                    {}

                                    {}
                                    {}
                                    {}".format( + getMsg('PAGE_ERR_IP_AREA_H1'), + getMsg('PAGE_ERR_IP_AREA_P1', ("{} {} {}".format( + ip_area_dict.raw["country"]["country"], + ip_area_dict.raw["country"]["province"], + ip_area_dict.raw["country"]["city"] + ),)), + getMsg('PAGE_ERR_IP_AREA_P2'), + getMsg('PAGE_ERR_IP_AREA_P3') + ) + + # 仅允许allow列表中的地区,其他地区都不可以访问 + if areas_dict["limit_type"] == "allow": + for city in areas_dict["limit_area"]["city"]: + if len(ip_area_dict.raw["country"]["city"].strip()) == 0: + break + + if ip_area_dict.raw["country"]["city"].strip() in city["name"]: + return False + + for province in areas_dict["limit_area"]["province"]: + if len(ip_area_dict.raw["country"]["province"].strip()) == 0: + break + + if ip_area_dict.raw["country"]["province"].strip() in province["name"]: + return False + + for country in areas_dict["limit_area"]["country"]: + if len(ip_area_dict.raw["country"]["country"].strip()) == 0: + break + + if ip_area_dict.raw["country"]["country"].strip() in country["name"]: + return False + + return error_str + + # 仅禁止deny列表中的地区,其他地区都可以访问 + if areas_dict["limit_type"] == "deny": + for city in areas_dict["limit_area"]["city"]: + if len(ip_area_dict.raw["country"]["city"].strip()) == 0: + break + + if ip_area_dict.raw["country"]["city"].strip() in city["name"]: + return error_str + + for province in areas_dict["limit_area"]["province"]: + if len(ip_area_dict.raw["country"]["province"].strip()) == 0: + break + + if ip_area_dict.raw["country"]["province"].strip() in province["name"]: + return error_str + + for country in areas_dict["limit_area"]["country"]: + if len(ip_area_dict.raw["country"]["country"].strip()) == 0: + break + + if ip_area_dict.raw["country"]["country"].strip() in country["name"]: + return error_str + except: + import traceback + print(traceback.format_exc()) + return False + + +def get_ip_location(ip_address): + ''' + 获取ip地址的地理位置 + @param ip_address: + @return: + ''' + try: + from geoip2 import database + except: + ExecShell("{}/pyenv/bin/pip install -U pip".format(get_panel_path())) + ExecShell("{}/pyenv/bin/pip install geoip2".format(get_panel_path())) + writeFile('data/restart.pl', 'True') + from geoip2 import database + + data_path = '{}/config/GeoLite2-City.mmdb'.format(get_panel_path()) + reader = database.Reader(data_path) + response = reader.city(ip_address) + reader.close() + return response + + +def get_limit_area(): + ''' + 获取地区限制列表 + @return: + ''' + empty_content = { + "limit_area": { + "city": [], + "province": [], + "country": [] + }, + "limit_area_status": "false", + "limit_type": "deny" + } + try: + areas_file = 'data/limit_area.json' + if not os.path.exists(areas_file): return empty_content + + try: + areas_dict = json.loads(ReadFile(areas_file)) + except json.decoder.JSONDecodeError: + return empty_content + + return areas_dict + except: + public.get_error_info() + return empty_content + + +# 密码复杂度验证 +def check_password_safe(password: str) -> bool: + ''' + @name 密码复杂度验证 + @param password(string) 密码 + @return bool + ''' + # 是否检测密码复杂度 + + # 密码长度验证 + if len(password) < 8: return False + + num = 0 + # 密码是否包含数字 + if re.search(r'[0-9]+', password): num += 1 + # 密码是否包含小写字母 + if re.search(r'[a-z]+', password): num += 1 + # 密码是否包含大写字母 + if re.search(r'[A-Z]+', password): num += 1 + # 密码是否包含特殊字符 + if re.search(r'[^\w\s]+', password): num += 1 + # 密码是否包含以上任意3种组合 + if num < 3: return False + return True + + +def show_menu(menu_id, status): + """ + 设置显示隐藏菜单 + :param menu_id: 菜单id + :param status: 0 | 1 + :return: + """ + + show_menu_file = '/www/server/panel/config/show_menu.json' + hide_menu_file = '/www/server/panel/config/hide_menu.json' + defanlt_data = ['memuA', 'memuAsite', 'memuAftp', 'memuAdatabase', 'memuDocker', 'memuAcontrol', 'memuAfirewall', + 'memuAfiles', 'memuAlogs', 'memuAxterm', 'memuAcrontab', 'memuAsoft', + 'memuAconfig', 'dologin'] + show_menu_data = defanlt_data + if os.path.exists(show_menu_file): + show_menu_data = json.loads(ReadFile(show_menu_file)) + + # 获取之前设置的隐藏页面 + try: + if os.path.exists(hide_menu_file): + hide_menu = ReadFile(hide_menu_file) + show_menu_data = [i for i in show_menu_data if i not in hide_menu] + ExecShell("rm -rf {}".format(hide_menu_file)) + WriteFile(show_menu_file, json.dumps(show_menu_data)) + except: + pass + if status == 1 and menu_id not in show_menu_data: + show_menu_data.append(menu_id) + elif status == 0 and menu_id in show_menu_data: + show_menu_data.remove(menu_id) + WriteFile(show_menu_file, json.dumps(show_menu_data)) + return returnMsg(True, 'Success') + + +def task_service_status(): + ''' + @name 检查后台任务服务状态 + @return bool + ''' + pid_file = '{}/logs/task.pid'.format(get_panel_path()) + if not os.path.exists(pid_file): return False + pid = readFile(pid_file) + if not pid: return False + if not os.path.exists('/proc/{}'.format(pid)): return False + return True + + +def reload_panel(): + ''' + @name 重载面板 + @return void + ''' + + # 重载面板 + WriteFile('{}/data/reload.pl'.format(get_panel_path()), 'True') + if not task_service_status(): + ExecShell("bash {}/init.sh start") + + +# 2024/1/24 上午 10:38 通用响应对象 +def returnResult(code=0, status=True, msg="OK", data=None, timestamp=None, args=None): + ''' + 通用响应对象 + @param code: 0:成功 1:失败 2:警告 ... + @param status: + @param msg: 只传msg,不传需要前端处理的数据 + @param data: 只传需要前端处理的数据 + @param timestamp: 秒级时间戳 + @return: + + 使用示例: + 成功:return dp.returnResult(data=data) + 失败:return dp.returnResult(code=1, status=False, msg="获取失败!", data=[]) + 失败:return dp.returnResult(code=1, status=False, msg="获取失败!") + 警告:return dp.returnResult(code=2, status=False, msg="警告,xxxxxxxxxxx!") + ... + ''' + import time + if timestamp is None: + timestamp = int(time.time()) + + log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json')) + keys = log_message.keys() + if type(msg) == str: + if msg in keys: + msg = log_message[msg] + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + msg = msg.replace(rep, args[i]) + + return { + "code": code, + "status": status, + "msg": msg, + "data": data, + "timestamp": timestamp + } + + +# 2024/1/24 上午 11:56 取指定模型目录 +def get_mod_path(mod_name=None): + ''' + @name 取指定插件目录 + @author hwliang<2021-07-14> + @param mod_name 模型名称 不传则返回模型根目录 + @return string + ''' + + root_path = "{}/mod/project".format(get_panel_path()) + if not mod_name: return root_path + return "{}/{}".format(root_path, mod_name) + + +def get_client_info_db_obj(): + ''' + @name 获取客户端信息数据库对象 + @return object + ''' + db_path = '{}/data/db'.format(get_panel_path()) + db_file = '{}/client_info.db'.format(db_path) + if not os.path.exists(db_path): os.makedirs(db_path, 384) + db_obj = M('') + db_obj._Sql__DB_FILE = db_file + # 如果数据库文件不存在则创建 + if not os.path.exists(db_file): + db_obj.execute('''CREATE TABLE client_info ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + remote_addr VARCHAR(50) NOT NULL, + remote_port INTEGER DEFAULT 0, + session_id VARCHAR(32) NOT NULL, + user_agent TEXT NOT NULL, + login_time INTEGER DEFAULT 0 + )''') + + # 创建索引 + db_obj.execute('CREATE INDEX client_ip_index ON client_info(client_ip)') + db_obj.execute('CREATE INDEX session_id_index ON client_info(session_id)') + db_obj.execute('CREATE INDEX login_time_index ON client_info(login_time)') + return db_obj + + +def record_client_info(): + ''' + @name 记录客户端信息 + @return void + ''' + from flask import request + from BTPanel import cache + db_obj = get_client_info_db_obj() + remote_addr = GetClientIp() + user_agent = request.headers.get('User-Agent', '') + pdata = { + 'remote_addr': remote_addr, + 'remote_port': request.environ.get('REMOTE_PORT'), + 'session_id': md5(remote_addr + user_agent), + 'user_agent': user_agent, + 'login_time': int(time.time()) + } + db_obj.table('client_info').insert(pdata) + db_obj.close() + + # 设置缓存 + cache.set('last_client_session_id', pdata['session_id'], 86400 * 2) + + +def check_client_info(): + ''' + @name 检查客户端信息 + @return int 0:陌生IP,1:上次登录的IP且UA一致,2:近30天内登录过的IP + ''' + from flask import request + from BTPanel import cache + remote_addr = GetClientIp() + # remote_addr = request.environ.get('REMOTE_ADDR', '0.0.0.0') + # 如果是本地访问或为未来IP则当作陌生IP + if remote_addr in ['0.0.0.0', '127.0.0.1', '::1', '::']: + return 0 + user_agent = request.headers.get('User-Agent', '') + # 如果UA不是浏览器则当作陌生IP + if user_agent.find('Mozilla') == -1: + return 0 + + session_id = md5(remote_addr + user_agent) + + if cache.get('last_client_session_id') == session_id: + return 1 + + db_obj = get_client_info_db_obj() + if not db_obj: + return 0 + + last_login_info = db_obj.table('client_info').order('id desc').field('remote_addr,session_id,login_time').find() + if not last_login_info: + return 0 + + # 如果上次登录的IP且UA一致 + now_time = int(time.time()) + if last_login_info['session_id'] == session_id: + s_time = now_time - last_login_info['login_time'] + if s_time < (86400 * 2): + cache.set('last_client_session_id', session_id, 86400 * 2 - s_time) + return 1 + if s_time < (86400 * 30): + return 2 + return 0 + + # 如果近30天内登录过的IP + if remote_addr == last_login_info['remote_addr'] and now_time - last_login_info['login_time'] < 2592000: + return 2 + if db_obj.table('client_info').where('remote_addr=?', remote_addr).count(): + return 2 + + # 陌生IP + return 0 + + +def redirect_to_login(default_callback_def=None): + ''' + @name 重定向到登录页面 + @return void + ''' + from flask import redirect, request, Response + client_status = check_client_info() + + # 获取请求头 + x_http_token = request.headers.get('x-http-token', '') + if client_status == 0: + if default_callback_def: + return default_callback_def(None) + # print_log("redirect_to_login 方法{1}") 登录过期会进入 + return error_404(None) + elif client_status == 1: + if x_http_token: + # result = {"status": False, "code": -8888, "redirect": get_admin_path(), + # "msg": "The current login session has been invalid, please login again!"} + # 修改为aapanel通用返回方式 + result = { + "status": -1, + "timestamp": int(time.time()), + "message": { + "msg": "The current login session has been invalid, please login again!", + "redirect": get_admin_path() + } + } + + return Response(json.dumps(result), mimetype='application/json', status=200) + return redirect(get_admin_path()) + elif client_status == 2: + if x_http_token: + # result = {"status": False, "code": -8888, "redirect": "/login", + # "msg": "The current login session has been invalid, please login again!"} + + # 修改为aapanel通用返回方式 + result = { + "status": -1, + "timestamp": int(time.time()), + "message": { + "msg": "The current login session has been invalid, please login again!", + "redirect": "/login" + } + } + + return Response(json.dumps(result), mimetype='application/json', status=200) + return redirect('/login') + + if default_callback_def: + return default_callback_def(None) + return error_404(None) + + +def ws_send(data: str): + try: + if '/www/server/panel' not in sys.path: + sys.path.insert(0, '/www/server/panel') + from BTPanel import WS_OBJ + ws_obj = {i: j for i, j in WS_OBJ.items() if j['timeout'] > int(time.time())} + if ws_obj == {}: return False + for i, j in ws_obj.items(): + j['ws_obj'].send(data) + return True + except: + return False + + +def get_plugin_info(upgrade_plugin_name): + ''' + @name 获取插件信息 + @author hwliang<2021-06-15> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + plugin_path = get_plugin_path() + plugin_info_file = '{}/{}/info.json'.format(plugin_path, upgrade_plugin_name) + if not os.path.exists(plugin_info_file): return {} + info_body = readFile(plugin_info_file) + if not info_body: return {} + plugin_info = json.loads(info_body) + return plugin_info + + +def get_plugin_find(upgrade_plugin_name=None): + ''' + @name 获取指定软件信息 + @author hwliang<2021-06-15> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + plugin_list_data = load_soft_list() + + for p_data_info in plugin_list_data['list']: + if p_data_info['name'] == upgrade_plugin_name: + # upgrade_plugin_name = p_data_info['name'] + return p_data_info + + return get_plugin_info(upgrade_plugin_name) + + +def get_plugin_value(plugin_name, key): + ''' + @name 获取插件配置值 + @author hwliang + @param plugin_name 插件名称 + @param key 字段名 + @return mixed + ''' + plugin_info = get_plugin_find(plugin_name) + return plugin_info.get(key, None) + + +def get_plugin_pid(plugin_name): + ''' + @name 获取指定插件的pid + @author hwliang<2021-06-15> + @param plugin_name 插件名称 + @return string + ''' + plugin_info = get_plugin_find(plugin_name) + if not plugin_info: return 0 + if 'pid' in plugin_info: + return plugin_info['pid'] + return 0 + + +# 下载插件主文件 +def download_main(upgrade_plugin_name, upgrade_version): + ''' + @name 下载插件主程序文件 + @author hwliang<2021-06-25> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 + @return void + ''' + import requests, shutil + plugin_path = get_plugin_path() + tmp_path = '{}/temp'.format(get_panel_path()) + + if not os.path.exists(tmp_path): + os.makedirs(tmp_path, 0o755) + + download_d_main_url = '{}/api/panel/download_plugin_main'.format(OfficialApiBase()) + pdata = get_user_info() + pdata['name'] = upgrade_plugin_name + pdata['version'] = upgrade_version + pdata['os'] = 'Linux' + pdata['environment_info'] = json.dumps(fetch_env_info(), ensure_ascii=False) + import config, socket + import requests.packages.urllib3.util.connection as urllib3_conn + _ip_type = config.config().get_request_iptype() + old_family = urllib3_conn.allowed_gai_family + if _ip_type == 'ipv4': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + elif _ip_type == 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + try: + download_res = requests.post(download_d_main_url, pdata, timeout=30, headers=get_requests_headers()) + + print_log(pdata) + print_log(download_res.content) + except Exception as ex: + raise PanelError(error_conn_cloud(str(ex))) + finally: + urllib3_conn.allowed_gai_family = old_family + + # 下载失败提示文本处理 + if download_res.status_code != 200: + try: + raise PanelError(download_res.json().get('res', 'download plugin source code error')) + except PanelError: + raise + except: + raise PanelError('download plugin source code error') + + filename = '{}/{}.py'.format(tmp_path, upgrade_plugin_name) + with open(filename, 'wb+') as save_script_f: + save_script_f.write(download_res.content) + save_script_f.close() + if md5(download_res.content) != download_res.headers.get('Content-md5'): + raise PanelError('Package file Hash verification failed.') + dst_file = '{plugin_path}/{plugin_name}/{plugin_name}_main.py'.format(plugin_path=plugin_path, + plugin_name=upgrade_plugin_name) + shutil.copyfile(filename, dst_file) + if os.path.exists(filename): os.remove(filename) + WriteLog('Software manager', + "Plugin [{}] was corrupted, try automatic repair.".format(get_plugin_info(upgrade_plugin_name)['title'])) + + +# 重新下载插件主文件 +def re_download_main(plugin_name, plugin_path=None): + if not plugin_path: + plugin_path = get_panel_path() + '/plugin/' + plugin_name + + plugin_file = '{plugin_path}/{name}/{name}_main.py'.format(plugin_path=plugin_path, name=plugin_name) + plugin_info = get_plugin_info(plugin_name) + if 'versions' in plugin_info: + version = plugin_info['versions'] + download_main(plugin_name, version) + plugin_body = readFile(plugin_file, 'rb') + return plugin_body + + return b'' + + +# 运行插件API +def run_plugin(plugin_name: str, def_name: str, args: dict_obj): + import PluginLoader + res = PluginLoader.plugin_run(plugin_name, def_name, args) + if isinstance(res, dict): + if 'status' in res and res['status'] == False and 'msg' in res: + if isinstance(res['msg'], str): + if res['msg'].find('Traceback ') != -1: + raise PanelError(res['msg']) + return res + +def run_plugin_v2(plugin_name: str, def_name: str, args: dict_obj): + import PluginLoader + res = PluginLoader.plugin_run(plugin_name, def_name, args) + # print_log(res) + if isinstance(res, dict): + if 'status' in res and res['status'] == False and 'msg' in res: + if isinstance(res['msg'], str): + if res['msg'].find('Traceback ') != -1: + raise PanelError(res['msg']) + if isinstance(res, dict): + if 'status' in res and 'msg' in res: + status = 0 if res['status'] else -1 + # 改返回 + res = return_message(status, 0, res['msg']) + else: + # 改返回 + res = return_message(0, 0, res) + if isinstance(res, list): + res = return_message(0, 0, res) + return res +# 加载插件列表与授权列表 +def load_soft_list(force: bool = True): + local_cache_file = '{}/data/plugin_bin.pl'.format(get_panel_path()) + + if force or not os.path.exists(local_cache_file) or os.path.getsize(local_cache_file) < 10: + cloudUrl = '{}/api/panel/getSoftListEn'.format(OfficialApiBase()) + import panelAuth + import requests + pdata = panelAuth.panelAuth().create_serverid(None) + url_headers = {} + if 'token' in pdata: + url_headers = {"authorization": "bt {}".format(pdata['token'])} + pdata['environment_info'] = json.dumps(fetch_env_info()) + + resp = requests.post(cloudUrl, params=pdata, headers=url_headers, verify=False, timeout=10) + + # 请求成功后将授权密文信息写入本地文件 + if resp.status_code == 200: + with open(local_cache_file, 'w') as fp: + fp.write(resp.text) + + import PluginLoader + + if force: + if hasattr(PluginLoader, 'parse_plugin_list'): + PluginLoader.parse_plugin_list(1) + else: + import importlib + importlib.reload(PluginLoader) + + plugin_list_data = PluginLoader.get_plugin_list(0) + + if not isinstance(plugin_list_data, dict): + raise PanelError('Sorry. failed to load soft list. please check the network and try again later.') + + if 'status' in plugin_list_data and 'msg' in plugin_list_data and plugin_list_data['status'] == False: + raise PanelError(str(plugin_list_data['msg'])) + + return plugin_list_data + + +# 官网API根地址 +def OfficialApiBase(): + return 'https://www.aapanel.com' + + +# 获取安装路径 +def get_setup_path(): + ''' + @name 获取安装路径 + @author hwliang<2021-07-22> + @return string + ''' + return '/www/server' + + +# 获取面板根目录 +def get_panel_path(): + ''' + @name 取面板根目录 + @author hwliang<2021-07-14> + @return string + ''' + return '{}/panel'.format(get_setup_path()) + + +# 读取文件 +def ReadFile(filename, mode='r'): + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): return False + fp = None + try: + fp = open(filename, mode) + f_body = fp.read() + except Exception as ex: + if sys.version_info[0] != 2: + try: + fp = open(filename, mode, encoding="utf-8", errors='ignore') + f_body = fp.read() + except: + fp = open(filename, mode, encoding="GBK", errors='ignore') + f_body = fp.read() + else: + return False + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +# 读取文件 +def readFile(filename, mode='r'): + ''' + @name 读取指定文件数据 + @author hwliang<2021-06-09> + @param filename 文件名 + @param mode 文件打开模式,默认r + @return string or bytes or False 如果返回False则说明读取失败 + ''' + return ReadFile(filename, mode) + + +# 写入文件 +def WriteFile(filename, s_body, mode='w+'): + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +# 写入文件 +def writeFile(filename, s_body, mode='w+'): + ''' + @name 写入到指定文件 + @author hwliang<2021-06-09> + @param filename 文件名 + @param s_boey 被写入的内容,字节或字符串 + @param mode 文件打开模式,默认w+ + @return bool + ''' + return WriteFile(filename, s_body, mode) + + +# 创建临时目录 +def make_panel_tmp_path() -> str: + tmp_path = '{}/temp/tmp_{}_{}'.format(get_panel_path(), int(time.time()), GetRandomString(32)) + if not os.path.exists(tmp_path): + os.makedirs(tmp_path, 0o755) + return tmp_path + + +# 创建临时目录(使用上下文管理器) +@contextlib.contextmanager +def make_panel_tmp_path_with_context(): + tmp_path = make_panel_tmp_path() + + import shutil + + try: + yield tmp_path + finally: + # 删除临时目录 + shutil.rmtree(tmp_path) + + diff --git a/class/public/exceptions.py b/class/public/exceptions.py new file mode 100644 index 00000000..5901e7a2 --- /dev/null +++ b/class/public/exceptions.py @@ -0,0 +1,11 @@ +# 异常类 +# @author Zhj<2024/06/27> + +# 提示类异常 会正常响应 +class HintException(Exception): + pass + + +# 无授权异常 +class NoAuthorizationException(HintException): + pass diff --git a/class/public/mysqlmgr.py b/class/public/mysqlmgr.py new file mode 100644 index 00000000..cb440242 --- /dev/null +++ b/class/public/mysqlmgr.py @@ -0,0 +1,136 @@ +# MySQL管理公共模块 +# @author Zhj<2024/07/01> +import os +import json +import time +import typing + +from .common import M, aap_t_simple_result, aap_t_mysql_dump_info, to_dict_obj, get_msg_gettext, get_mysqldump_bin, MysqlConn, get_database_character, ExecShell +from .exceptions import HintException + + +# 备份MySQL数据库 +def backup(database_id: int) -> aap_t_simple_result: + from database_v2 import database + data = database().ToBackup(to_dict_obj({'id': database_id})) + + if int(data.get('status', 0)) != 0: + return aap_t_simple_result(False, data.get('message', {})['result']) + + return aap_t_simple_result(True, M('backup').where('type = 1 and pid=?', (database_id,)).order('id desc').getField('filename')) + + +# 还原MySQL数据库 +def restore(db_name: str, bak_file: str) -> aap_t_simple_result: + from database_v2 import database + data = database().InputSql(to_dict_obj({'name': db_name, 'file': bak_file})) + return aap_t_simple_result(int(data.get('status', 0)) == 0, data.get('message', {})['result']) + + +# 删除MySQL数据库备份文件 +def del_bak(bak_file: str) -> aap_t_simple_result: + # aapanel内部备份 + bak_id_dict = M('backup').where('`type`=1 and `filename`=?', (bak_file,)).field('id').find() + + if isinstance(bak_id_dict, dict): + from database_v2 import database + data = database().DelBackup(to_dict_obj({'id': int(bak_id_dict['id'])})) + return aap_t_simple_result(int(data.get('status', 0)) == 0, data.get('message', {})['result']) + + # 其它途径备份 + if not os.path.exists(bak_file): + return aap_t_simple_result(False, get_msg_gettext('File not exists')) + + os.remove(bak_file) + + return aap_t_simple_result(True, get_msg_gettext('Remove backup successfully')) + + +# 数据库导出 +def dumpsql_with_aap(database_id: int, backup_path: typing.Optional[str] = None) -> aap_t_mysql_dump_info: + import shlex + db_find = M('databases').where("id=?", (database_id,)).find() + + if not isinstance(db_find, dict): + raise HintException(get_msg_gettext('Table {} has been corrupted', ('databases',))) + + if backup_path is None: + backup_path_tmp = M('config').order('`id` desc').limit(1).field('backup_path').find() + + if not isinstance(backup_path_tmp, dict): + raise HintException(get_msg_gettext('Table {} has been corrupted', ('config',))) + + backup_path = os.path.join(str(backup_path_tmp['backup_path']), 'database') + + name = db_find['name'] + fileName = name + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.sql.gz' + backupName = os.path.join(backup_path, fileName) + mysqldump_bin = get_mysqldump_bin() + + from database_v2 import database + database_obj = database() + + if db_find['db_type'] in ['0', 0]: + # 本地数据库 + # 测试数据库连接 + with MysqlConn() as conn: + conn.execute("show databases") + + root = M('config').where('id=?', (1,)).getField('mysql_root') + if not os.path.exists(backup_path): + os.makedirs(backup_path, 0o600) + + if not database_obj.mypass(True, root): + raise HintException(get_msg_gettext("Database configuration file failed to get checked, please check " + "if MySQL configuration file exists [/etc/my.cnf]")) + + try: + password = M('config').where('id=?', (1,)).getField('mysql_root') + if not password: + raise HintException(get_msg_gettext("Database password cannot be empty")) + + password = shlex.quote(str(password)) + os.environ["MYSQL_PWD"] = password + ExecShell(mysqldump_bin + " -R -E --triggers=false --default-character-set=" + get_database_character(name) + " --force --opt \"" + name + "\" -u root -p" + password + " | gzip > " + backupName) + finally: + os.environ["MYSQL_PWD"] = "" + + database_obj.mypass(False, root) + + elif db_find['db_type'] in ['1', 1]: + # 远程数据库 + try: + conn_config = json.loads(db_find['conn_config']) + res = database_obj.CheckCloudDatabase(conn_config) + if isinstance(res, dict): + raise HintException(res.get('msg', get_msg_gettext('Cannot connect to remote MySQL'))) + + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = password + ExecShell(mysqldump_bin + " -h " + conn_config['db_host'] + " -P " + str(int(conn_config['db_port'])) + " -R -E --triggers=false --default-character-set=" + get_database_character(name) + " --force --opt \"" + str(db_find['name']) + "\" -u " + str(conn_config['db_user']) + " -p" + password + " | gzip > " + backupName) + finally: + os.environ["MYSQL_PWD"] = "" + + elif db_find['db_type'] in ['2', 2]: + try: + conn_config = M('database_servers').where('id=?', db_find['sid']).find() + res = database_obj.CheckCloudDatabase(conn_config) + if isinstance(res, dict): + raise HintException(res.get('msg', get_msg_gettext('Cannot connect to remote MySQL'))) + + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = password + ExecShell(mysqldump_bin + " -h " + conn_config['db_host'] + " -P " + str(int(conn_config['db_port'])) + " -R -E --triggers=false --default-character-set=" + get_database_character(name) + " --force --opt \"" + str(db_find['name']) + "\" -u " + str(conn_config['db_user']) + " -p" + str(conn_config['db_password']) + " | gzip > " + backupName) + finally: + os.environ["MYSQL_PWD"] = "" + + else: + raise HintException(get_msg_gettext("Unsupported database type")) + + if not os.path.exists(backupName): + raise HintException(get_msg_gettext("Backup error")) + + # # 将备份信息添加到数据库中 + # bak_id = M('backup').add('type,name,pid,filename,size,addtime', (1, fileName, id, backupName, 0, time.strftime('%Y-%m-%d %X', time.localtime()))) + + return aap_t_mysql_dump_info(db_name=str(db_find['name']), file=backupName, dump_time=int(time.time())) diff --git a/class/public/regexplib.py b/class/public/regexplib.py new file mode 100644 index 00000000..8dea51f6 --- /dev/null +++ b/class/public/regexplib.py @@ -0,0 +1,29 @@ +import re + +# 匹配IP地址 +match_ipv4 = re.compile(r'^(?:(?:25[0-5]|(?:2[0-4]|1?\d)?\d)\.){3}(?:25[0-5]|(?:2[0-4]|1?\d)?\d)$') +match_ipv6 = re.compile(r'^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4})|(?:(?:[0-9a-fA-F]{1,4}:){1,7}:)|(?:(?:[0-9a-fA-F]{1,4}:){6}:[0-9a-fA-F]{1,4})|(?:(?:[0-9a-fA-F]{1,4}:){5}(?::[0-9a-fA-F]{1,4}){1,2})|(?:(?:[0-9a-fA-F]{1,4}:){4}(?::[0-9a-fA-F]{1,4}){1,3})|(?:(?:[0-9a-fA-F]{1,4}:){3}(?::[0-9a-fA-F]{1,4}){1,4})|(?:(?:[0-9a-fA-F]{1,4}:){2}(?::[0-9a-fA-F]{1,4}){1,5})|(?:(?:[0-9a-fA-F]{1,4}:){1}(?::[0-9a-fA-F]{1,4}){1,6})|(?::(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))') + +# 安全文件路径 +match_safe_path = re.compile(r'^[\w\s./\-]*$') + +# 匹配类私有属性名称 +match_class_private_property = re.compile(r'^(?:_\w+)?__\w+') + +# HOST基本格式 +match_based_host = re.compile(r'^[\w.:\-]+$') + +# 抓取URL根路径 +find_url_root = re.compile(r'(https|http)://([\w:.\-]+)', re.IGNORECASE) + +# 匹配首条PHP Fatal error信息 +search_php_first_fatal_error = re.compile(r'PHP Fatal error: \s*([^\r\n]+)') + +# 匹配HTTP响应报文中的状态行 (Status Line) +search_http_response_status_line = re.compile(r'HTTP/\d+(?:\.\d+)? (\d{1,3}) \S*') + +# 通用版本号格式验证 major.minor[.patch]/主版本.子版本[.修订号] +match_general_version_format = re.compile(r'^\d+(?:\.\d+){1,2}$') + +# md5格式验证 +match_md5_format = re.compile(r'^[a-fA-F0-9]{32}$') diff --git a/class/public/tools.py b/class/public/tools.py new file mode 100644 index 00000000..acd1ac21 --- /dev/null +++ b/class/public/tools.py @@ -0,0 +1,35 @@ +import typing + + +def my_pipe(val: any, fs: typing.List[callable]) -> any: + """ + 管道数据过滤函数 + @param val: any + @param fs: callable 数据过滤函数 + @return: any + """ + from functools import reduce + return reduce(lambda x, y: y(x), fs, val) + + +def is_number(s) -> bool: + """ + @name 判断输入参数是否一个数字 + @author Zhj<2022-07-18> + @param s 输入参数 + @return bool + """ + try: + float(s) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(s) + return True + except (TypeError, ValueError): + pass + + return False diff --git a/class/public/translations.py b/class/public/translations.py new file mode 100644 index 00000000..2bd53eeb --- /dev/null +++ b/class/public/translations.py @@ -0,0 +1,24 @@ +# Language translations +import os +import glob +import json +import public + +translations = {} + + +# Load translations +def load_translations(): + if len(translations.keys()) > 0: + return translations + + scan_pattern = '{}/BTPanel/static/vite/lang/*/*.json'.format(public.get_panel_path()) + + for path in glob.glob(scan_pattern): + lan = os.path.basename(os.path.dirname(path)) + if lan not in translations: + translations[lan] = {} + with open(path, 'r') as fp: + translations[lan].update(json.loads(fp.read())) + + return translations diff --git a/class/public/validate.py b/class/public/validate.py new file mode 100644 index 00000000..92d48a32 --- /dev/null +++ b/class/public/validate.py @@ -0,0 +1,1253 @@ +import re +import json +import socket +import os +import typing +from .regexplib import match_ipv4, match_ipv6, match_safe_path, match_based_host +from .exceptions import HintException + + +class Param: + __VALIDATE_OPTS = [ + '>', + '<', + '>=', + '<=', + '=', + 'in', + 'not in', + ] + + def __init__(self, name: str): + self.name: str = name + self.__validate_rules: typing.List[_ValidateRule] = [] + self.__filters: typing.List[callable] = [] + + # 验证器 Begin -----> + + def Require(self): + """ + 必选参数 + @return: self + """ + self.__validate_rules.append(_RequireValidation(self.name)) + return self + + def Date(self): + """ + 日期字符串 + @return: self + """ + self.__validate_rules.append(_DateValidation(self.name)) + return self + + def Timestamp(self): + """ + Unix时间戳 + @return: self + """ + self.__validate_rules.append(_TimestampValidation(self.name)) + return self + + def Url(self): + """ + URL + @return: self + """ + self.__validate_rules.append(_UrlValidation(self.name)) + return self + + def Ip(self): + """ + IP地址 + @return: self + """ + self.__validate_rules.append(_IpValidation(self.name)) + return self + + def Ipv4(self): + """ + IPv4地址 + @return: self + """ + self.__validate_rules.append(_Ipv4Validation(self.name)) + return self + + def Ipv6(self): + """ + IPv6地址 + @return: self + """ + self.__validate_rules.append(_Ipv6Validation(self.name)) + return self + + def Host(self): + """ + 主机地址(可以包含端口号) + @return: self + """ + self.__validate_rules.append(_HostValidation(self.name)) + return self + + def Port(self): + """ + 端口号 + @return: self + """ + return self.Integer('between', [1, 65535]) + + def Json(self): + """ + JSON字符串 + @return: self + """ + self.__validate_rules.append(_JsonValidation(self.name)) + return self + + def Array(self): + """ + JSON-Array字符串 + @return: self + """ + self.__validate_rules.append(_ArrayValidation(self.name)) + return self + + def Object(self): + """ + JSON-Object字符串 + @return: self + """ + self.__validate_rules.append(_ObjectValidation(self.name)) + return self + + def List(self): + """ + 限制参数数据类型:list + @return: self + """ + self.__validate_rules.append(_ListValidation(self.name)) + return self + + def Tuple(self): + """ + 限制参数数据类型:tuple + @return: self + """ + self.__validate_rules.append(_TupleValidation(self.name)) + return self + + def Dict(self): + """ + 限制参数数据类型:dict + @return: self + """ + self.__validate_rules.append(_DictValidation(self.name)) + return self + + def Bool(self): + """ + 布尔值或boolean字符串 true/false + @return: self + """ + self.__validate_rules.append(_BoolValidation(self.name)) + return self + + def String(self, opt: typing.Optional[str] = None, length_or_list: typing.Optional[typing.Union[int, typing.List[typing.Union[int, str]]]] = None): + """ + 字符串 + @param opt: str 运算符 + @param length_or_list: int|list[int|str]|None 字符串长度或字符串集合 + @return: self + """ + self.__validate_rules.append(_StringValidation(self.name, opt, length_or_list)) + return self + + def Number(self, opt: typing.Optional[str] = None, num: typing.Optional[typing.Union[int, float, typing.List[typing.Union[int, float]]]] = None): + """ + 数值 + @param opt: str 运算符 + @param num: int 数值大小 + @return: self + """ + self.__validate_rules.append(_NumberValidation(self.name, opt, num)) + return self + + def Integer(self, opt: typing.Optional[str] = None, num: typing.Optional[typing.Union[typing.Union[int, typing.List[int]]]] = None): + """ + 整数 + @param opt: str 运算符 + @param num: int 数值大小 + @return: self + """ + self.__validate_rules.append(_IntegerValidation(self.name, opt, num)) + return self + + def Float(self, opt: typing.Optional[str] = None, num: typing.Optional[typing.Union[int, float, typing.List[typing.Union[int, float]]]] = None): + """ + 浮点数 + @param opt: str 运算符 + @param num: int 数值大小 + @return: self + """ + self.__validate_rules.append(_FloatValidation(self.name, opt, num)) + return self + + def Alpha(self, opt: typing.Optional[str] = None, length_or_list: typing.Optional[typing.Union[int, typing.List[typing.Union[int, str]]]] = None): + """ + 纯字母 + @param opt: str 运算符 + @param length_or_list: int|list[int|str]|None 字符串长度或字符串集合 + @return: self + """ + self.__validate_rules.append(_AlphaValidation(self.name, opt, length_or_list)) + return self + + def Alphanum(self, opt: typing.Optional[str] = None, length_or_list: typing.Optional[typing.Union[int, typing.List[typing.Union[int, str]]]] = None): + """ + 字母+数字 + @param opt: str 运算符 + @param length_or_list: int|list[int|str]|None 字符串长度或字符串集合 + @return: self + """ + self.__validate_rules.append(_AlphanumValidation(self.name, opt, length_or_list)) + return self + + def Mobile(self): + """ + (中国)手机号码 + @return: self + """ + self.__validate_rules.append(_MobileValidation(self.name)) + return self + + def Email(self): + """ + 邮箱地址 + @return: self + """ + self.__validate_rules.append(_EmailValidation(self.name)) + return self + + def Regexp(self, exp: str): + """ + 正则表达式 + @param exp: str 正则表达式 + @return: self + """ + self.__validate_rules.append(_RegexpValidation(self.name, exp)) + return self + + def File(self): + """ + 文件上传 + @return: self + """ + self.__validate_rules.append(_FileValidation(self.name)) + return self + + def Size(self, opt: typing.Optional[str] = None, size: typing.Optional[typing.Union[int, typing.List[int]]] = None): + """ + 上传文件大小 + @param opt: str 运算符 + @param size: int 上传文件大小bytes + @return: self + """ + self.__validate_rules.append(_SizeValidation(self.name, opt, size)) + return self + + def Mime(self, opt: typing.Optional[str] = None, mime_type: typing.Optional[typing.Union[str, typing.List[str]]] = None): + """ + 上传文件Mimetype + @param opt: str 运算符 + @param mime_type: str 上传文件Mimetype + @return: self + """ + self.__validate_rules.append(_MimeValidation(self.name, opt, mime_type)) + return self + + def Ext(self, opt: typing.Optional[str] = None, ext: typing.Optional[typing.Union[str, typing.List[str]]] = None): + """ + 上传文件后缀名 + @param opt: str 运算符 + @param ext: str 上传文件后缀名 + @return: self + """ + self.__validate_rules.append(_ExtValidation(self.name, opt, ext)) + return self + + def SafePath(self): + """ + 文件路径 + @return: self + """ + self.__validate_rules.append(_SafePathValidation(self.name)) + return self + + # <------- 验证器 End + + # 过滤器 Begin ------> + + def Trim(self): + """ + 去除字符串两端空白字符 + @return: self + """ + self.__filters.append(lambda x: str(x).strip()) + return self + + def Xss(self): + """ + XSS过滤 + @return: self + """ + self.__filters.append(_xssencode) + return self + + def Filter(self, f: callable): + """ + 自定义参数过滤器 + @param f: callable func(x: any) -> any + @return: self + """ + self.__filters.append(f) + return self + + # <------- 过滤器 End + + def do_validate(self, args: dict): + """ + 执行验证器 + @param args: dict 请求参数列表 + @return: self + """ + for v in self.__validate_rules: + v.validate(args) + + return self + + def do_filter(self, val, extra_filters: typing.List[callable] = []) -> any: + """ + 执行参数过滤器 + @param val: any + @param extra_filters: list[callable] + @return: any + """ + from functools import reduce + return reduce(lambda x, y: y(x), extra_filters + self.__filters, val) + + +class _ValidateRule: + def validate(self, args: dict): + raise NotImplementedError('method validate() not implemented.') + + +class _RequireValidation(_ValidateRule): + """ + 必选参数验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} is required' + + def validate(self, args: dict): + if self.name in args: + return + + if 'FILES' in args and self.name in args['FILES']: + return + + raise HintException(self.errmsg.format(self.name)) + + +class _DateValidation(_ValidateRule): + """ + 日期字符串验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid datetime' + + def validate(self, args: dict): + if self.name not in args: + return + + if re.match(r'^(?:\d{2}-\d{2}-\d{2}|\d{2}/\d{2}/\d{2})(?: \d{2}:\d{2}(?::\d{2})?)?$', + str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _TimestampValidation(_ValidateRule): + """ + Unix时间戳验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid timestamp' + + def validate(self, args: dict): + if self.name not in args: + return + + if re.match(r'^\d{10}$', str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _UrlValidation(_ValidateRule): + """ + URL地址验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid URL' + + def validate(self, args: dict): + if self.name not in args: + return + + regex_obj = re.compile( + r'^(?:http|ftp)s?://' + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' + r'localhost|' + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' + r'(?::\d+)?' + r'(?:/?|[/?]\S+)$', re.IGNORECASE) + + if regex_obj.match(str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _IpValidation(_ValidateRule): + """ + IP地址验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid IP' + + def validate(self, args: dict): + if self.name not in args: + return + + ipstr = str(args[self.name]).strip() + + if _is_ipv4(ipstr) or _is_ipv6(ipstr): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _Ipv4Validation(_ValidateRule): + """ + IPv4地址验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid IPv4' + + def validate(self, args: dict): + if self.name not in args: + return + + if _is_ipv4(str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _Ipv6Validation(_ValidateRule): + """ + IPv6地址验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid IPv4' + + def validate(self, args: dict): + if self.name not in args: + return + + if _is_ipv6(str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _HostValidation(_ValidateRule): + """ + 主机地址验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid HOST' + + def validate(self, args: dict): + if self.name not in args: + return + + if match_based_host.match(str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _JsonValidation(_ValidateRule): + """ + JSON字符串验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid JSON' + + def validate(self, args: dict): + if self.name not in args: + return + + try: + json.loads(str(args[self.name]).strip()) + return + except: + pass + + raise HintException(self.errmsg.format(self.name)) + + +class _ArrayValidation(_ValidateRule): + """ + JSON-Array字符串验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid JSON Array' + + def validate(self, args: dict): + if self.name not in args: + return + + try: + obj = json.loads(str(args[self.name]).strip()) + + if isinstance(obj, list): + return + except: + pass + + raise HintException(self.errmsg.format(self.name)) + + +class _ObjectValidation(_ValidateRule): + """ + JSON-Object字符串验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid JSON Object' + + def validate(self, args: dict): + if self.name not in args: + return + + try: + obj = json.loads(str(args[self.name]).strip()) + + if isinstance(obj, dict): + return + except: + pass + + raise HintException(self.errmsg.format(self.name)) + + +class _BoolValidation(_ValidateRule): + """ + bool字符串验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} must be bool' + + def validate(self, args: dict): + if self.name not in args: + return + + val = args[self.name] + + if isinstance(val, bool) or re.match(r'^true|false$', str(args[self.name]).strip(), re.IGNORECASE): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _ListValidation(_ValidateRule): + """ + list数据类型验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} must be list' + + def validate(self, args: dict): + if self.name not in args: + return + + if isinstance(args[self.name], list): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _TupleValidation(_ValidateRule): + """ + tuple数据类型验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} must be tuple' + + def validate(self, args: dict): + if self.name not in args: + return + + if isinstance(args[self.name], tuple): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _DictValidation(_ValidateRule): + """ + dict数据类型验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} must be dict' + + def validate(self, args: dict): + if self.name not in args: + return + + if isinstance(args[self.name], dict): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _OperationHelper: + """ + 运算辅助类 + """ + + def __init__(self, name: str, opt: typing.Optional[str], operand: typing.Optional[typing.Union[str, int, float, typing.List[typing.Union[str, int, float]]]], data_type): + self.name: str = name + self.opt = opt + self.operand = operand + self.data_type = data_type + self._opt_check() + self._data_type_check() + + def do(self, val: typing.Union[str, int, float], data_type=None): + val = str(val).strip() + + if data_type is not None: + self.data_type = data_type + self._data_type_check() + + if self.opt is None: + return + + if self.operand is None: + return + + if self.opt == '=': + self._eq(self._calc_num(val)) + elif self.opt == '>': + self._gt(self._calc_num(val)) + elif self.opt == '>=': + self._gte(self._calc_num(val)) + elif self.opt == '<': + self._lt(self._calc_num(val)) + elif self.opt == '<=': + self._lte(self._calc_num(val)) + elif self.opt == 'between': + self._between(self._calc_num(val)) + elif self.opt == 'in': + self._in(self.data_type(val)) + elif self.opt == 'not in': + self._not_in(self.data_type(val)) + + def _calc_num(self, val: str) -> typing.Union[int, float]: + if self.data_type is str: + return len(val) + + return self.data_type(val) + + def _opt_check(self): + if self.opt is None: + return + + self.opt = self.opt.lower() + + if self.operand is None: + return + + if self.opt in ['=', '>', '<', '>=', '<='] and not isinstance(self.operand, int): + raise HintException('当运算符opt是 \'{}\' 时,运算数只能是int类型或float类型,当前类型 {}'.format(self.opt, type(self.operand))) + + if self.opt in ['in', 'not in', 'between'] and not isinstance(self.operand, list): + raise HintException('当运算符opt是 \'{}\' 时,运算数只能是list类型,当前类型 {}'.format(self.opt, type(self.operand))) + + def _data_type_check(self): + if self.data_type is str: + return + + if self.data_type is int: + return + + if self.data_type is float: + return + + raise HintException('data_type只能是str、int、float 当前 {}'.format(self.data_type)) + + def _eq(self, num: typing.Union[int, float]): + if num == self.operand: + return + + raise HintException( + '{}{} must equal {}'.format(self.name, ' length' if isinstance(self.data_type, str) else '', self.operand)) + + def _gt(self, num: typing.Union[int, float]): + if num > self.operand: + return + + raise HintException( + '{}{} must greater than {}'.format(self.name, ' length' if isinstance(self.data_type, str) else '', + self.operand)) + + def _gte(self, num: typing.Union[int, float]): + if num >= self.operand: + return + + raise HintException( + '{}{} must greater than or equal {}'.format(self.name, ' length' if isinstance(self.data_type, str) else '', + self.operand)) + + def _lt(self, num: typing.Union[int, float]): + if num < self.operand: + return + + raise HintException( + '{}{} must less than {}'.format(self.name, ' length' if isinstance(self.data_type, str) else '', + self.operand)) + + def _lte(self, num: typing.Union[int, float]): + if num <= self.operand: + return + + raise HintException( + '{}{} must less than or equal {}'.format(self.name, ' length' if isinstance(self.data_type, str) else '', + self.operand)) + + def _between(self, num: typing.Union[int, float]): + if len(self.operand) != 2: + raise HintException('当运算符opt是 \'between\' 时,运算数只能是list类型,并且list的长度只能是2,当前list长度 {}'.format(len(self.operand))) + + if num >= self.operand[0] and num <= self.operand[1]: + return + + raise HintException( + '{}{} must between {} and {}'.format(self.name, ' length' if isinstance(self.data_type, str) else '', + self.operand[0], self.operand[1])) + + def _in(self, item: typing.Union[int, float, str]): + if len(self.operand) < 1: + raise HintException('当运算符opt是 \'{}\' 时,运算数只能是list类型,并且list的长度必须大于0,当前list长度 0'.format(self.opt)) + + if item in self.operand: + return + + raise HintException('{} must in {}'.format(self.name, self.operand)) + + def _not_in(self, item: typing.Union[int, float, str]): + if len(self.operand) < 1: + raise HintException('当运算符opt是 \'{}\' 时,运算数只能是list类型,并且list的长度必须大于0,当前list长度 0'.format(self.opt)) + + if item in self.operand: + raise HintException('{} must not in {}'.format(self.name, self.operand)) + + +class _StringValidation(_ValidateRule): + """ + 字符串验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, v: typing.Optional[typing.Union[int, typing.List[typing.Union[int, str]]]] = None): + self.name: str = name + self.errmsg: str = '{} must be string' + self.op = _OperationHelper(name, opt, v, str) + + def validate(self, args: dict): + if self.name not in args: + return + + s = args[self.name] + + if isinstance(s, str): + self.op.do(s) + return + + raise HintException(self.errmsg.format(self.name)) + + +class _NumberValidation(_ValidateRule): + """ + 数字验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, num: typing.Optional[typing.Union[int, float, typing.List[typing.Union[int, float]]]] = None): + self.name: str = name + self.errmsg: str = '{} must be number' + self.op = _OperationHelper(name, opt, num, float) + + def validate(self, args: dict): + if self.name not in args: + return + + num = args[self.name] + + if _is_number(num): + self.op.do(num, _get_number_data_type(num)) + return + + raise HintException(self.errmsg.format(self.name)) + + +class _IntegerValidation(_ValidateRule): + """ + 整数验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, num: typing.Optional[typing.Union[int, typing.List[int]]] = None): + self.name: str = name + self.errmsg: str = '{} must be integer' + self.op = _OperationHelper(name, opt, num, int) + + def validate(self, args: dict): + if self.name not in args: + return + + num = args[self.name] + + if _is_int(num): + self.op.do(num) + return + + raise HintException(self.errmsg.format(self.name)) + + +class _FloatValidation(_ValidateRule): + """ + 浮点数验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, num: typing.Optional[typing.Union[float, typing.List[float]]] = None): + self.name: str = name + self.errmsg: str = '{} must be float' + self.op = _OperationHelper(name, opt, num, float) + + def validate(self, args: dict): + if self.name not in args: + return + + num = args[self.name] + + if _is_float(num): + self.op.do(num) + return + + raise HintException(self.errmsg.format(self.name)) + + +class _AlphaValidation(_ValidateRule): + """ + 纯字母验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, v: typing.Optional[typing.Union[int, typing.List[typing.Union[int, str]]]] = None): + self.name: str = name + self.errmsg: str = '{} must be alpha' + self.op = _OperationHelper(name, opt, v, str) + + def validate(self, args: dict): + if self.name not in args: + return + + s = str(args[self.name]).strip() + + if re.match(r'^[a-zA-Z]+$', s): + self.op.do(s) + return + + raise HintException(self.errmsg.format(self.name)) + + +class _AlphanumValidation(_ValidateRule): + """ + 字母数字验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, v: typing.Optional[typing.Union[int, typing.List[typing.Union[int, str]]]] = None): + self.name: str = name + self.errmsg: str = '{} must be alphanum' + self.op = _OperationHelper(name, opt, v, str) + + def validate(self, args: dict): + if self.name not in args: + return + + s = str(args[self.name]).strip() + + if re.match(r'^[a-zA-Z0-9]+$', s): + self.op.do(s) + return + + raise HintException(self.errmsg.format(self.name)) + + +class _MobileValidation(_ValidateRule): + """ + (中国)手机号码验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid mobile' + + def validate(self, args: dict): + if self.name not in args: + return + + s = str(args[self.name]).strip() + + if re.match(r'^1[3-9]\d{9}$', s): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _EmailValidation(_ValidateRule): + """ + 邮箱地址验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid email' + + def validate(self, args: dict): + if self.name not in args: + return + + s = str(args[self.name]).strip() + + if re.match(r'^.+@(\[?)[a-zA-Z0-9\-.]+\.(?:[a-zA-Z]{2,}|\d{1,3})\1$', s): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _RegexpValidation(_ValidateRule): + """ + 正则表达式验证类 + """ + + def __init__(self, name: str, regexp: str): + self.name: str = name + self.errmsg: str = '{} not success verified by regexp' + self.regexp: str = regexp + + def validate(self, args: dict): + if self.name not in args: + return + + s = str(args[self.name]).strip() + + if re.match(self.regexp, s): + return + + raise HintException(self.errmsg.format(self.name)) + + +class _FileValidation(_ValidateRule): + """ + 文件上传验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg: str = '{} not valid file' + + def validate(self, args: dict): + if 'FILES' in args and self.name in args['FILES']: + return + + raise HintException(self.errmsg.format(self.name)) + + +class _SizeValidation(_ValidateRule): + """ + 文件大小验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, size: typing.Optional[typing.Union[int, typing.List[int]]] = None): + self.name: str = name + self.op = _OperationHelper(name, opt, size, int) + + def validate(self, args: dict): + if 'FILES' not in args or self.name not in args['FILES']: + return + + self.op.do(args['FILES'][self.name].content_length) + + +class _MimeValidation(_ValidateRule): + """ + 文件mimetype验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, mime_type: typing.Optional[typing.Union[str, typing.List[str]]] = None): + self.name: str = name + self.op = _OperationHelper(name, opt, mime_type, str) + + def validate(self, args: dict): + if 'FILES' not in args or self.name not in args['FILES']: + return + + self.op.do(args['FILES'][self.name].mimetype) + + +class _ExtValidation(_ValidateRule): + """ + 文件后缀名验证类 + """ + + def __init__(self, name: str, opt: typing.Optional[str] = None, ext: typing.Optional[typing.Union[str, typing.List[str]]] = None): + self.name: str = name + self.op = _OperationHelper(name, opt, ext, str) + + def validate(self, args: dict): + if 'FILES' not in args or self.name not in args['FILES']: + return + + f = args['FILES'][self.name] + + self.op.do(os.path.splitext(f.filename)[-1]) + + +class _SafePathValidation(_ValidateRule): + """ + 文件路径名验证类 + """ + + def __init__(self, name: str): + self.name: str = name + self.errmsg = '{} not safe path' + + def validate(self, args: dict): + if self.name not in args: + return + + if _is_safe_path(str(args[self.name]).strip()): + return + + raise HintException(self.errmsg.format(self.name)) + + +def trim_filter() -> callable: + """ + 获取Trim参数过滤器 + @return: callable + """ + return lambda x: str(x).strip() + + +def xss_filter() -> callable: + """ + 获取XSS参数过滤器 + @return: callable + """ + return _xssencode + + +def _is_ipv4(ip: str) -> bool: + ''' + @name 是否是IPV4地址 + @author hwliang + @param ip IP地址 + @return True/False + ''' + # 验证基本格式 + if not match_ipv4.match(ip): + return False + + # 验证每个段是否在合理范围 + try: + socket.inet_pton(socket.AF_INET, ip) + except AttributeError: + try: + socket.inet_aton(ip) + except socket.error: + return False + except socket.error: + return False + return True + + +def _is_ipv6(ip: str) -> bool: + ''' + @name 是否为IPv6地址 + @author hwliang + @param ip 地址 + @return True/False + ''' + # 验证基本格式 + if not match_ipv6.match(ip): + return False + + # 验证IPv6地址 + try: + socket.inet_pton(socket.AF_INET6, ip) + except socket.error: + return False + return True + + +def _xssencode(text: str) -> str: + """ + XSS过滤 + @param text: str + @return bool + """ + try: + from cgi import html + list = ['`', '~', '&', '#', '/', '*', '$', '@', '<', '>', '\"', '\'', ';', '%', ',', '.', '\\u'] + ret = [] + for i in text: + if i in list: + i = '' + ret.append(i) + str_convert = ''.join(ret) + text2 = html.escape(str_convert, quote=True) + return text2 + except: + return text.replace('&', '&').replace('"', '"').replace('<', '<').replace('>', '>') + + +def _is_safe_path(path: str, force: bool = True) -> bool: + """ + 文件路径过滤 + @param path: str + @param force: bool + @return: bool + """ + if len(path) > 256: + return False + + checks = ['..', './', '\\', '%', '$', '^', '&', '*', '~', '"', "'", ';', '|', '{', '}', '`'] + + for c in checks: + if path.find(c) > -1: + return False + + if force: + if not match_safe_path.match(path): + return False + + return True + + +def _is_number(s) -> bool: + """ + @name 判断输入参数是否一个数字 + @author Zhj<2022-07-18> + @param s 输入参数 + @return bool + """ + try: + float(s) + return True + except ValueError: + pass + + try: + import unicodedata + unicodedata.numeric(s) + return True + except (TypeError, ValueError): + pass + + return False + + +def _is_int(s) -> bool: + """ + 判断输入是否是整数 + @param s: any + @return bool + """ + try: + int(s) + return True + except ValueError: + pass + + return False + + +def _is_float(s) -> bool: + """ + 判断输入是否是浮点数 + @param s: any + @return bool + """ + try: + float(s) + return True + except ValueError: + pass + + return False + + +def _get_number_data_type(s): + """ + 获取数字的数据类型 + @param s 输入参数 + @return int|float + """ + try: + int(s) + return int + except ValueError: + pass + + return float diff --git a/class/public/websitemgr.py b/class/public/websitemgr.py new file mode 100644 index 00000000..8a8912f0 --- /dev/null +++ b/class/public/websitemgr.py @@ -0,0 +1,181 @@ +# 网站管理公共模块 +# @author Zhj<2024/06/15> +import os +import re +import json +import typing + +import public +from .common import M, aap_t_simple_result, to_dict_obj, dict_obj, get_msg_gettext, get_setup_path, readFile +from .exceptions import HintException + + +import collections + + +# 简单网站信息 +aap_t_simple_site_info = collections.namedtuple('aap_t_simple_site_info', ['site_id', 'database_id']) + + +# 获取当前部署的Web服务器 +def get_webserver(): + if os.path.exists('{}/apache/bin/apachectl'.format(get_setup_path())): + webserver = 'apache' + elif os.path.exists('/usr/local/lsws/bin/lswsctrl'): + webserver = 'openlitespeed' + else: + webserver = 'nginx' + return webserver + + +# 查询网站对应的PHP版本 +def get_site_php_version(siteName: str) -> str: + try: + webserver = get_webserver() + setup_path = get_setup_path() + + conf = readFile( + '{setup_path}/panel/vhost/{webserver}/{siteName}.conf'.format(setup_path=setup_path, webserver=webserver, + siteName=siteName)) + if webserver == 'openlitespeed': + conf = readFile(setup_path + '/panel/vhost/' + webserver + '/detail/' + siteName + '.conf') + if webserver == 'nginx': + rep = r"enable-php-(\w{2,5})[-\w]*\.conf" + elif webserver == 'apache': + rep = r"php-cgi-(\w{2,5})\.sock" + else: + rep = r"path\s*/usr/local/lsws/lsphp(\d+)/bin/lsphp" + + tmp = re.search(rep, conf).groups() + + if tmp[0] == '00': + return 'Static' + + if tmp[0] == 'other': + return 'Other' + + return tmp[0][0] + '.' + tmp[0][1] + except: + return 'Static' + + +# 修复网站文件权限 +def fix_permissions(site_root_path_or_site_file: str) -> aap_t_simple_result: + """ + :param site_root_path_or_site_file: str 网站根目录或者单一网站文件 + :return: + """ + from files_v2 import files + data = files().fix_permissions(to_dict_obj({'path': site_root_path_or_site_file})) + + if int(data.get('status', 0)) != 0: + return aap_t_simple_result(False, data.get('msg', 'Failed to fix permission')) + + return aap_t_simple_result(True, data.get('msg', 'Fix permission successfully')) + + +# 备份网站文件 +def backup_files(site_id: int) -> aap_t_simple_result: + from panel_site_v2 import panelSite + data = panelSite().ToBackup(to_dict_obj({'id': site_id})) + + if int(data.get('status', 0)) != 0: + return aap_t_simple_result(False, data.get('message', {})['result']) + + return aap_t_simple_result(True, M('backup').where('type = 0 and pid=?', (site_id,)).order('id desc').getField('filename')) + + +# 还原网站文件 +def restore_files(site_id: int, bak_file: str) -> aap_t_simple_result: + from panel_restore_v2 import panel_restore + data = panel_restore().restore_website_backup(to_dict_obj({'site_id': site_id, 'file_name': os.path.basename(bak_file)})) + return aap_t_simple_result(int(data.get('status', 0)) == 0, data.get('message', {})['result']) + + +# 删除网站备份文件 +def del_bak(bak_file: str) -> aap_t_simple_result: + # aapanel内部备份 + bak_id_dict = M('backup').where('`type`=0 and `filename` = ?', (bak_file,)).field('id').find() + + if isinstance(bak_id_dict, dict): + from panel_site_v2 import panelSite + data = panelSite().DelBackup(to_dict_obj({'id': int(bak_id_dict['id'])})) + return aap_t_simple_result(int(data.get('status', 0)) == 0, data.get('message', {})['result']) + + # 其它途径备份 + if not os.path.exists(bak_file): + return aap_t_simple_result(False, get_msg_gettext('File not exists')) + + os.remove(bak_file) + + return aap_t_simple_result(True, get_msg_gettext('Remove backup successfully')) + + +# 创建PHP站点 +def create_php_site_with_mysql(domain: str, site_path: str, php_ver_short: str, db_user: str, db_pwd: str, another_domains: typing.List = ()) -> aap_t_simple_site_info: + """ + :param domain: str 网站主域名 + :param site_path: str 网站根目录(绝对路径) + :param php_ver_short: str PHP版本号缩写 54、74、80、81... + :param db_user: str 数据库用户名 + :param db_pwd: str 数据库用户密码 + :param another_domains: list 网站其它解析域名 + :return: aap_t_simple_site_info + """ + from panel_site_v2 import panelSite + data = panelSite().AddSite(to_dict_obj({ + 'webname': json.dumps({ + 'domain': domain, + 'domainlist': list(another_domains), + 'count': 0, + }), + 'type': 'PHP', + 'version': php_ver_short, + 'port': '80', + 'path': site_path, + 'sql': 'MySQL', + 'datauser': db_user, + 'datapassword': db_pwd, + 'codeing': 'utf8mb4', + 'ps': domain.replace('.', '_').replace('-', '_'), + })) + + if int(data.get('status', 0)) != 0: + raise HintException(data.get('message', {})['result']) + + data = data.get('message', {}) + + if int(data.get('databaseStatus', 0)) != 1: + raise HintException(public.get_msg_gettext('Database creation failed. Please check mysql running status and try again.')) + + return aap_t_simple_site_info(data['siteId'], data['d_id']) + + +# 删除站点 +def remove_site(site_id: int) -> public.aap_t_simple_result: + site_info = M('sites').where('`id` = ?', (site_id,)).field('name').find() + + if not isinstance(site_info, dict): + return public.aap_t_simple_result(False, public.get_msg_gettext('No found site-info with id {}'.format(site_id))) + + from panel_site_v2 import panelSite + data = panelSite().DeleteSite(to_dict_obj({ + 'id': site_id, + 'webname': site_info['name'], + 'ftp': '1', + 'path': '1', + 'database': '1', + })) + + return aap_t_simple_result(int(data.get('status', 0)) == 0, data.get('message', {})['result']) + + +# 获取可用的PHP版本列表 +def get_available_php_ver_shorts(without_static: bool = True) -> typing.List[str]: + from panel_site_v2 import panelSite + lst = panelSite().GetPHPVersion(to_dict_obj({}), False) + + if without_static: + lst = filter(lambda x: x['version'] != '00', lst) + + return list(map(lambda x: x['version'], lst)) diff --git a/class/push/base_push.py b/class/push/base_push.py index a3e1a91a..a3e49155 100644 --- a/class/push/base_push.py +++ b/class/push/base_push.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- # | Copyright (c) 2015-2020 宝塔软件(https://www.bt.cn) All rights reserved. # +------------------------------------------------------------------- -# | Author: baozi +# | Author: baozi # | Author: baozi # +------------------------------------------------------------------- import sys,os,re,json diff --git a/class/push/panel_push.py b/class/push/panel_push.py index 709ade3f..fe46517f 100644 --- a/class/push/panel_push.py +++ b/class/push/panel_push.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- # | Copyright (c) 2015-2020 宝塔软件(https://www.bt.cn) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # | Author: lx # +------------------------------------------------------------------- import sys,os,time,psutil,re @@ -93,7 +93,7 @@ class panel_push: return status elif name == 'mysql': res = public.ExecShell("service mysqld status") - if res and not re.search("not\s+running", res[0]): + if res and not re.search(r"not\s+running", res[0]): return True return False elif name == 'tomcat': diff --git a/class/push/site_push.py b/class/push/site_push.py index 3101f0c8..e907df27 100644 --- a/class/push/site_push.py +++ b/class/push/site_push.py @@ -4,7 +4,7 @@ # +------------------------------------------------------------------- # | Copyright (c) 2015-2020 宝塔软件(https://www.bt.cn) All rights reserved. # +------------------------------------------------------------------- -# | Author: 沐落 +# | Author: 沐落 # +------------------------------------------------------------------- import sys, os, time, json, re, psutil @@ -158,7 +158,7 @@ class site_push: return status elif name == 'mysql': res = public.ExecShell("service mysqld status") - if res and not re.search("not\s+running", res[0]): + if res and not re.search(r"not\s+running", res[0]): return True return False elif name == 'tomcat': diff --git a/class/push/tamper_push.py b/class/push/tamper_push.py index 15222214..de28d7bf 100644 --- a/class/push/tamper_push.py +++ b/class/push/tamper_push.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- # | Copyright (c) 2015-2020 宝塔软件(https://www.bt.cn) All rights reserved. # +------------------------------------------------------------------- -# | Author: baozi +# | Author: baozi # | Author: baozi # +------------------------------------------------------------------- import sys,os,re,json diff --git a/class/safeModel/base.py b/class/safeModel/base.py index ee3d0546..3af5d5b9 100644 --- a/class/safeModel/base.py +++ b/class/safeModel/base.py @@ -15,7 +15,7 @@ class safeBase: #转换时间格式 def to_date(self,date_str): - tmp = re.split('\s+',date_str) + tmp = re.split(r'\s+',date_str) if len(tmp) < 3: return date_str s_date = str(datetime.now().year) + '-' + self._months.get(tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] time_array = time.strptime(s_date, "%Y-%m-%d %H:%M:%S") diff --git a/class/safeModel/firewallModel.py b/class/safeModel/firewallModel.py index 56deb089..cf51c4e0 100644 --- a/class/safeModel/firewallModel.py +++ b/class/safeModel/firewallModel.py @@ -1,14 +1,14 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: hwliang -#------------------------------------------------------------------- +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- # 系统防火墙 -#------------------------------ +# ------------------------------ import sys, os, json, re, time, sqlite3 import contextlib import traceback @@ -23,7 +23,6 @@ import public class main(safeBase): - __isFirewalld = False __isUfw = False __firewall_obj = None @@ -133,32 +132,48 @@ class main(safeBase): def get_firewall_status(self): if self.__isUfw: res = public.ExecShell("systemctl is-active ufw")[0] - if res == "active": return True + if res == "active": + return True + res = public.ExecShell("systemctl list-units | grep ufw")[0] - if res.find('active running') != -1: return True + if res.find('active running') != -1: + return True + res = public.ExecShell('/lib/ufw/ufw-init status')[0] - if res.find("Firewall is not running") != -1: return False + if res.find("Firewall is not running") != -1: + return False + res = public.ExecShell('ufw status verbose')[0] - if res.find('inactive') != -1: return False + if res.find('inactive') != -1: + return False + return True if self.__isFirewalld: res = public.ExecShell("ps -ef|grep firewalld|grep -v grep")[0] - if res: return True + if res: + return True + res = public.ExecShell("systemctl is-active firewalld")[0] - if res == "active": return True + if res == "active": + return True + res = public.ExecShell("systemctl list-units | grep firewalld")[0] - if res.find('active running') != -1: return True + if res.find('active running') != -1: + return True return False + else: res = public.ExecShell("/etc/init.d/iptables status")[0] - if res.find('not running') != -1: return False + if res.find('not running') != -1: + return False + res = public.ExecShell("systemctl is-active iptables")[0] if res == "active": return True return True - def SetPing(self, get): + def SetPing(self, get): if get.status == '1': get.status = '0' else: @@ -189,6 +204,7 @@ class main(safeBase): '4. If you use other security software, please uninstall it first
                                    ' ) + # 服务状态控制 def firewall_admin(self, get): order = ['reload', 'restart', 'stop', 'start'] @@ -211,7 +227,7 @@ class main(safeBase): filename = '/etc/sysctl.conf' conf = public.readFile(filename) if conf.find('net.ipv4.icmp_echo') != -1: - public.ExecShell("sysctl -p") + public.ExecShell("sysctl -p") public.WriteLog("system firewall", "firewall {}".format(result[get.status])) return public.returnMsg(True, 'firewall has {}'.format(result[get.status])) if self.__isFirewalld: @@ -223,6 +239,7 @@ class main(safeBase): public.WriteLog("system firewall", "firewall {}".format(result[get.status])) return public.returnMsg(True, 'firewall has {}'.format(result[get.status])) + # 重载防火墙配置 def FirewallReload(self): if self.__isUfw: @@ -238,7 +255,8 @@ class main(safeBase): public.ExecShell('/etc/init.d/iptables save') public.ExecShell('/etc/init.d/iptables restart') - #端口扫描 + + # 端口扫描 def CheckPort(self, port, protocol): import socket localIP = '127.0.0.1' @@ -263,8 +281,10 @@ class main(safeBase): if temp['local']: result += 2 return result - # 查询入栈规则 + + # 查询入栈规则 def get_rules_list(self, args): + if self.__isFirewalld: self.__firewall_obj = firewalld() self.GetList() @@ -299,7 +319,7 @@ class main(safeBase): '-') != -1: d['status'] = -1 else: - d['status'] = self.CheckPort(int(_port), _protocol) + d['status'] = self.CheckPort(int(_port), _protocol) for i in res_data: if 'brief' in i: i['brief'] = public.xsssec(i['brief']) @@ -307,20 +327,22 @@ class main(safeBase): except: return [] + def check_firewall_rule(self, args): """ - @检测防火墙规则 - """ + @检测防火墙规则 + """ port = args['port'] - find = public.M('firewall_new').where('ports=?', (str(port), )).find() + find = public.M('firewall_new').where('ports=?', (str(port),)).find() if find: return True return False + # 端口检查 def check_port(self, port_list): - rep1 = "^\d{1,5}(:\d{1,5})?$" - # rep1 = '^[0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5]$' + rep1 = r"^\d{1,5}(:\d{1,5})?$" + # rep1 = r'^[0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5]$' for port in port_list: if port.find('-') != -1: ports = port.split('-') @@ -338,18 +360,19 @@ class main(safeBase): if not re.search(rep1, port): return public.returnMsg(False, 'PORT_CHECK_RANGE') + def parse_ip_interval(self, ip_str): """解析区间IP - author: lx - date: 2022/10/25 + author: lx + date: 2022/10/25 - Returns: - list : IP列表 - """ + Returns: + list : IP列表 + """ ips = [] try: - rep2 = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" searchor = re.compile(rep2) if ip_str.find("-") != -1: pre_ip, end_ip = ip_str.split("-") @@ -372,6 +395,7 @@ class main(safeBase): pass return ips + # 判断是否为ipv6网段 @staticmethod def is_ipv6_network_segment_or_ipv6_address(ip_datas: str) -> bool: @@ -385,17 +409,18 @@ class main(safeBase): return False return True + # 添加入栈规则 def create_rules2(self, get): ''' - get 里面 有 protocol port type address brief 五个参数 - protocol == ['tcp','udp'] - port = 端口 - types == [accept、drop] # 放行和禁止 - address 地址,允许放行的ip,如果全部就是:0.0.0.0/0;另外可以包含“,"或者"-" - 表示区间IP - brief 备注说明 - ''' + get 里面 有 protocol port type address brief 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types == [accept、drop] # 放行和禁止 + address 地址,允许放行的ip,如果全部就是:0.0.0.0/0;另外可以包含“,"或者"-" + 表示区间IP + brief 备注说明 + ''' protocol = get.protocol ports = get.ports.strip() types = get.types @@ -409,7 +434,7 @@ class main(safeBase): sources = [ sip.strip() for sip in address.split(",") if sip.strip() ] - rep2 = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" _ips = [] for source_ip in sources: if source_ip.find("-") != -1: @@ -440,17 +465,18 @@ class main(safeBase): for port in port_list: self.add_iptables_rule(source_ip, protocol, port, types) + # 添加入栈规则 def create_rules(self, get): ''' - get 里面 有 protocol port type address brief 五个参数 - protocol == ['tcp','udp'] - port = 端口 - types == [accept、drop] # 放行和禁止 - address 地址,允许放行的ip,如果全部就是:0.0.0.0/0;另外可以包含“,"或者"-" - 表示区间IP - brief 备注说明 - ''' + get 里面 有 protocol port type address brief 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types == [accept、drop] # 放行和禁止 + address 地址,允许放行的ip,如果全部就是:0.0.0.0/0;另外可以包含“,"或者"-" + 表示区间IP + brief 备注说明 + ''' protocol = get.protocol ports = get.ports.strip() types = get.types @@ -466,7 +492,7 @@ class main(safeBase): allow_ips = [] if address: sources = [sip.strip() for sip in address.split(",") if sip.strip()] - rep2 = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" _ips = [] for source_ip in sources: if source_ip.find("-") != -1: @@ -532,21 +558,25 @@ class main(safeBase): strategy = "accept" elif types == 'drop': strategy = "drop" - public.WriteLog("system firewall", "Add port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(protocol, ports, strategy, log_ip)) + public.WriteLog("system firewall", + "Add port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(protocol, ports, strategy, log_ip)) # 如果有忽略的端口,返回忽略的端口 if ignore_list: - return public.returnMsg(True, 'Added successfully, {} The same rule exists for the port and has been skipped'.format(', '.join(ignore_list))) + return public.returnMsg(True, + 'Added successfully, {} The same rule exists for the port and has been skipped'.format( + ', '.join(ignore_list))) return public.returnMsg(True, 'ADD_SUCCESS') + # 删除入栈规则 def remove_rules(self, get): ''' - get 里面有 id protocol port type address 五个参数 - protocol == ['tcp','udp'] - port = 端口 - types == [accept、drop] # 放行和禁止 - address 地址,允许放行的ip - ''' + get 里面有 id protocol port type address 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types == [accept、drop] # 放行和禁止 + address 地址,允许放行的ip + ''' # 检测是否开启防火墙 hezhihong if not self.get_firewall_status(): return public.returnMsg(False, 'Please enable the firewall before proceeding.') @@ -557,7 +587,7 @@ class main(safeBase): ports = get.ports types = get.types self._del_firewall_rules(address, protocol, ports, types) - public.M('firewall_new').where("id=?", (id, )).delete() + public.M('firewall_new').where("id=?", (id,)).delete() self.FirewallReload() if not get.address: log_ip = "All IPs" @@ -567,18 +597,21 @@ class main(safeBase): strategy = "accept" elif types == 'drop': strategy = "drop" - public.WriteLog("system firewall", "Delete port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(get.protocol, get.ports, types, log_ip)) + public.WriteLog("system firewall", + "Delete port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(get.protocol, get.ports, types, + log_ip)) return public.returnMsg(True, 'DEL_SUCCESS') + # 修改入栈规则 def modify_rules(self, get, addtime=None): ''' - get 里面有 id protocol port type address 五个参数 - protocol == ['tcp','udp'] - port = 端口 - types==['reject','accept'] # 放行和禁止 - address 地址,允许放行的ip,如果全部就是:0.0.0.0/0 - ''' + get 里面有 id protocol port type address 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types==['reject','accept'] # 放行和禁止 + address 地址,允许放行的ip,如果全部就是:0.0.0.0/0 + ''' # 检测是否开启防火墙 hezhihong if not self.get_firewall_status(): return public.returnMsg(False, 'Please enable the firewall before proceeding.') @@ -592,10 +625,10 @@ class main(safeBase): domain_total = domain.split('|')[0] sid = 0 if 'sid' not in get else get.sid if address: - rep = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" if not re.search(rep, get.source) and self.is_ipv6_network_segment_or_ipv6_address(get.source): return public.returnMsg(False, 'FIREWALL_IP_FORMAT') - data = public.M('firewall_new').where('id=?', (id, )).field( + data = public.M('firewall_new').where('id=?', (id,)).field( 'id,address,protocol,ports,types,brief,addtime,domain' ).find() if data: @@ -613,10 +646,10 @@ class main(safeBase): 'ports': ports, 'types': types, 'brief': brief, - 'addtime': addtime, - 'sid': sid, - 'domain': domain - } + 'addtime': addtime, + 'sid': sid, + 'domain': domain + } ) if domain: public.M('firewall_domain').where("id=?", (sid,)).save( @@ -635,8 +668,11 @@ class main(safeBase): strategy = "accept" elif get.types == 'drop': strategy = "drop" - public.WriteLog("system firewall", "修改端口规则: 协议:{}, 端口:{}, 策略:{}, IP:{}".format(get.protocol, get.ports.strip(), get.types, log_ip)) - return public.returnMsg(True, '操作成功') + public.WriteLog("system firewall", + "Modify port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(get.protocol, get.ports.strip(), get.types, + log_ip)) + return public.returnMsg(True, 'ADD_SUCCESS') + # firewall端口规则添加 def add_firewall_rule(self, address, protocol, ports, types): @@ -700,6 +736,7 @@ class main(safeBase): % (address, protocol, ports, types)) return True + # firewall端口规则删除 def del_firewall_rule(self, address, protocol, ports, types): if not address: @@ -766,6 +803,7 @@ class main(safeBase): % (address, protocol, ports, types)) return True + # firewall端口规则编辑 def edit_firewall_rule(self, _address, _protocol, _port, _type, address, protocol, ports, types): @@ -893,6 +931,7 @@ class main(safeBase): % (address, protocol, ports, types)) return True + # ufw 端口规则添加 def add_ufw_rule(self, address, protocol, ports, types): rule = "allow" if types == "accept" else "deny" @@ -912,6 +951,7 @@ class main(safeBase): public.ExecShell( 'ufw ' + rule + ' proto ' + protocol + ' from ' + address + ' to any port ' + ports + '') + # ufw 端口规则删除 def del_ufw_rule(self, address, protocol, ports, types): rule = "allow" if types == "accept" else "deny" @@ -933,6 +973,7 @@ class main(safeBase): ) self.update_panel_data(ports) + # ufw 端口规则修改 def edit_ufw_rule(self, _address, _protocol, _port, _type, address, protocol, ports, types): @@ -971,6 +1012,7 @@ class main(safeBase): 'ufw ' + rules + ' proto ' + protocol + ' from ' + address + ' to any port ' + ports + '' ) + # iptables端口规则添加 def add_iptables_rule(self, address, protocol, ports, types): rule = "ACCEPT" if types == "accept" else "DROP" @@ -1000,6 +1042,7 @@ class main(safeBase): rule + '') return True + # iptables端口规则删除 def del_iptables_rule(self, address, protocol, ports, types): rule = "ACCEPT" if types == "accept" else "DROP" @@ -1029,6 +1072,7 @@ class main(safeBase): rule + '') return True + # iptables端口规则编辑 def edit_iptables_rule(self, _address, _protocol, _port, _type, address, protocol, ports, types): @@ -1084,9 +1128,11 @@ class main(safeBase): rule2 + '') return True + # 修改面板数据 def update_panel_data(self, ports): - res = public.M('firewall').where("port=?", (ports, )).delete() + res = public.M('firewall').where("port=?", (ports,)).delete() + # 查询IP规则 def get_ip_rules_list(self, args): @@ -1110,11 +1156,12 @@ class main(safeBase): data['data'] = public.return_area(data['data'], 'address') return data + def check_a_ip(self, address): """ - @name 检测A记录是否为域名 - @author hezhihong - """ + @name 检测A记录是否为域名 + @author hezhihong + """ if address: if public.is_ipv4(address) or public.is_ipv6(address): return address @@ -1123,15 +1170,16 @@ class main(safeBase): if public.is_domain(address): return self.get_a_ip(address) return address + def get_a_ip(self, hostname): ''' - @name 检测主机名是否有A记录 - @author hezhihong - :param hostname: - :return: - ''' + @name 检测主机名是否有A记录 + @author hezhihong + :param hostname: + :return: + ''' if not self.install_dnspython(): - return public.returnMsg(False, '请先安装dnspython模块') + return public.returnMsg(False, 'Please install dnspython module first: btpip install dnspython') import dns.resolver # 尝试3次 a_ip = [] @@ -1167,11 +1215,12 @@ class main(safeBase): a_ip.remove(i2) return a_ip + def install_dnspython(self): """ - @name 安装dnspython模块 - @author hezhihong - """ + @name 安装dnspython模块 + @author hezhihong + """ # 检测dns解析 try: import dns.resolver @@ -1187,11 +1236,12 @@ class main(safeBase): except: return False + def del_domain_ip(self, args): """ - @name 删除域名设置 - @author hezhihong - """ + @name 删除域名设置 + @author hezhihong + """ if 'id' not in args or not args.id or 'sid' not in args: return public.returnMsg(False, 'Parameter error') @@ -1210,7 +1260,8 @@ class main(safeBase): # 当没有域名解析时,删除计划任务 if not public.M('firewall_domain').count(): - pdata = public.M('crontab').where('name=?', '[Do not delete] System firewall domain name resolution detection task').select() + pdata = public.M('crontab').where('name=?', + '[Do not delete] System firewall domain name resolution detection task').select() if pdata: for i in pdata: args = {"id": i['id']} @@ -1219,11 +1270,12 @@ class main(safeBase): return public.returnMsg(True, 'successfully deleted') + def add_crontab(self): """ - @name 构造日志切割任务 - @author hezhihong - """ + @name 构造日志切割任务 + @author hezhihong + """ python_path = '' try: python_path = public.ExecShell('which btpython')[0].strip("\n") @@ -1233,9 +1285,11 @@ class main(safeBase): except: pass if not python_path: return False - if not public.M('crontab').where('name=?', ('[Do not delete] System firewall domain name resolution detection task',)).count(): + if not public.M('crontab').where('name=?', ( + '[Do not delete] System firewall domain name resolution detection task',)).count(): cmd = '{} {}'.format(python_path, '/www/server/panel/script/firewall_domain.py') - args = {"name": "[Do not delete] System firewall domain name resolution detection task", "type": 'minute-n', "where1": '5', "hour": '', + args = {"name": "[Do not delete] System firewall domain name resolution detection task", "type": 'minute-n', + "where1": '5', "hour": '', "minute": '', "sName": "", "sType": 'toShell', "notice": '', "notice_channel": '', "save": '', "save_local": '1', "backupTo": '', "sBody": cmd, @@ -1247,6 +1301,7 @@ class main(safeBase): return False return True + def __check_auth(self): try: from pluginAuth import Plugin @@ -1258,11 +1313,12 @@ class main(safeBase): except: return False + def set_domain_ip2(self, args): """ - @name 设置域名规则 - @author hezhihong - """ + @name 设置域名规则 + @author hezhihong + """ pay = self.__check_auth() if not pay: return public.returnMsg(False, 'Current features are exclusive to the professional version') if not args.domain: return public.returnMsg(False, 'Please enter domain name') @@ -1278,7 +1334,8 @@ class main(safeBase): args.source = ip if ports: if public.is_ipv6(ip): - return public.returnMsg(False, 'The domain name is resolved to an IPv6 address and port rules are not supported.') + return public.returnMsg(False, + 'The domain name is resolved to an IPv6 address and port rules are not supported.') self.create_rules2(args) # 添加IP规则 else: @@ -1287,11 +1344,12 @@ class main(safeBase): return public.returnMsg(True, 'Domain name {} resolution added successfully'.format(args.domain)) + def set_domain_ip(self, args): """ - @name 设置域名规则 - @author hezhihong - """ + @name 设置域名规则 + @author hezhihong + """ pay = self.__check_auth() if not pay: return public.returnMsg(False, 'Current features are exclusive to the professional version') if not args.domain: return public.returnMsg(False, 'Please enter domain name') @@ -1305,7 +1363,8 @@ class main(safeBase): a_ip = [self.check_a_ip(a_ip[0])] # return a_ip if not a_ip: - return public.returnMsg(False, 'The domain name resolution has not been resolved or the resolution has not taken effect. If it has been resolved, please try again after 10 minutes.') + return public.returnMsg(False, + 'The domain name resolution has not been resolved or the resolution has not taken effect. If it has been resolved, please try again after 10 minutes.') if public.M('firewall_domain').where("domain=? and types=? and port=? and protocol=?", (args.domain, args.types, ports, protocol,)).count(): return public.returnMsg(False, 'Domain name {} already exists'.format(args.domain)) @@ -1319,7 +1378,8 @@ class main(safeBase): args.source = ip if ports: if public.is_ipv6(ip): - return public.returnMsg(False, 'The domain name is resolved to an IPv6 address and port rules are not supported.') + return public.returnMsg(False, + 'The domain name is resolved to an IPv6 address and port rules are not supported.') self.create_rules(args) # 添加IP规则 else: @@ -1328,11 +1388,12 @@ class main(safeBase): return public.returnMsg(True, 'Domain name {} resolution added successfully'.format(args.domain)) + def modify_domain_ip(self, args): """ - @name 修改域名规则(当修改为指定域名或从指定域名修改为其他时,需要调用此方法) - @name hezhihong - """ + @name 修改域名规则(当修改为指定域名或从指定域名修改为其他时,需要调用此方法) + @name hezhihong + """ pay = self.__check_auth() if not pay: return public.returnMsg(False, 'Current features are exclusive to the professional version') @@ -1393,9 +1454,10 @@ class main(safeBase): modify_args.domain = pdata['domain'] return self.modify_ip_rules(modify_args) + # IP地址检测 def check_ip(self, address_list): - rep = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" for address in address_list: address = address.split('/')[0] if address.find('-') != -1: @@ -1416,6 +1478,7 @@ class main(safeBase): if not re.search(rep, address) and not public.is_ipv6(address): return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + # 获取IP范围 def get_ip(self, address): result = [] @@ -1428,6 +1491,7 @@ class main(safeBase): result.append(head_s_ip + str(num + int(s_ips[-1]))) return result + def handle_firewall_ip(self, address, types): ip_list = self.get_ip(address) if isinstance(ip_list, dict): @@ -1450,6 +1514,7 @@ class main(safeBase): 'firewall-cmd --permanent --zone=public --add-rich-rule=\'rule source ipset="' + address + '" ' + types + '\'') + def handle_ufw_ip(self, address, types): ip_list = self.get_ip(address) if isinstance(ip_list, dict): @@ -1460,6 +1525,7 @@ class main(safeBase): public.ExecShell('iptables -I INPUT -m set --match-set ' + address + ' src -j ' + types.upper()) + # 检查IP地址是否在范围内 def ip_in_range(self, ip, ip_range): import ipaddress @@ -1493,7 +1559,8 @@ class main(safeBase): return result # 先处理用户的IP地址 - old_login_ip = public.M('firewall_ip').where("brief=?", ("IP that allows users to log in",)).field('id, address').select() + old_login_ip = public.M('firewall_ip').where("brief=?", ("IP that allows users to log in",)).field( + 'id, address').select() # public.print_log('############ ip接口user_ip {}'.format(user_ip)) for ip_range in address_list: @@ -1511,20 +1578,18 @@ class main(safeBase): # 然后处理其他的IP地址 for address in address_list: - self.add_rule(address, original_types, brief, domain, domain_total) self.FirewallReload() - public.WriteLog("system firewall", "Add IP rules: IP: {}, policy: {}".format(_address, original_types)) return public.returnMsg(True, 'ADD_SUCCESS') + # 添加单个IP规则 def add_rule(self, address, types, brief, domain, domain_total): if public.M('firewall_ip').where("address=? and types=? and domain=?", (address, types, domain)).count() > 0: - return if self.__isUfw: # public.print_log('############ ip接口 1') @@ -1631,11 +1696,12 @@ class main(safeBase): else: public.ExecShell('iptables -D INPUT -s ' + address + ' -j ' + types.upper()) - public.M('firewall_ip').where("id=?", (id, )).delete() + public.M('firewall_ip').where("id=?", (id,)).delete() self.update_panel_data(address) # 删除面板自带防火墙的表数据 self.FirewallReload() return public.returnMsg(True, 'All IP rules have been removed.') + # 删除IP规则 def remove_ip_rules(self, get): id = get.id @@ -1685,10 +1751,10 @@ class main(safeBase): else: public.ExecShell('iptables -D INPUT -s ' + address + ' -j ' + types.upper()) - public.M('firewall_ip').where("id=?", (id, )).delete() + public.M('firewall_ip').where("id=?", (id,)).delete() self.update_panel_data(address) # 删除面板自带防火墙的表数据 self.FirewallReload() - strategy= '' + strategy = '' if get.types == 'accept': strategy = "accept" elif get.types == 'drop': @@ -1696,6 +1762,7 @@ class main(safeBase): public.WriteLog("system firewall", "Delete IP rules: IP:{}, policy:{}".format(get.address, strategy)) return public.returnMsg(True, 'DEL_SUCCESS') + # 修改IP规则 def modify_ip_rules(self, get): id = get.id @@ -1710,7 +1777,7 @@ class main(safeBase): if result: return result data = public.M('firewall_ip').where( - 'id=?', (id, )).field('id,address,types,brief,addtime').find() + 'id=?', (id,)).field('id,address,types,brief,addtime').find() _address = data.get("address", "") _type = data.get("types", "") if self.__isUfw: @@ -1813,9 +1880,12 @@ class main(safeBase): strategy = "accept" elif get.types == 'drop': strategy = "drop" - public.WriteLog("system firewall", "修改规则, IP:{}, 策略:{} -> IP:{}, 策略:{}".format(_address, old_strategy, get.address.strip(), get.types)) + public.WriteLog("system firewall", + "修改规则, IP:{}, 策略:{} -> IP:{}, 策略:{}".format(_address, old_strategy, get.address.strip(), + get.types)) return public.returnMsg(True, 'Successful operation') + # 查看端口转发状态 def trans_status(self): content = dict() @@ -1829,6 +1899,7 @@ class main(safeBase): fw.write(json.dumps(content)) return True + # 查询端口转发 def get_forward_list(self, args): result = self.trans_status() @@ -1850,18 +1921,19 @@ class main(safeBase): data['shift'], data['row'])).order('addtime desc').select() return data + # 添加端口转发 def create_forward(self, get): s_port = get.s_ports.strip() # 起始端口 d_port = get.d_ports.strip() # 目的端口 d_ip = get.d_address.strip() # 目的ip protocol = get.protocol - rep1 = "^\d{1,5}(:\d{1,5})?$" + rep1 = r"^\d{1,5}(:\d{1,5})?$" if not re.search(rep1, s_port): return public.returnMsg(False, 'PORT_CHECK_RANGE') if not re.search(rep1, d_port): return public.returnMsg(False, 'PORT_CHECK_RANGE') - rep = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" if d_ip: if not re.search(rep, get.d_address) and not public.is_ipv6( get.d_address): @@ -1869,7 +1941,7 @@ class main(safeBase): if d_ip in ["127.0.0.1", "localhost"]: d_ip = "" if public.M('firewall_trans').where("start_port=?", - (s_port, )).count() > 0: + (s_port,)).count() > 0: return public.returnMsg(False, 'This port already exists, please do not add it again!') if self.__isUfw: content = self.ufw_handle_add(s_port, d_port, d_ip, protocol) @@ -1884,9 +1956,13 @@ class main(safeBase): 'start_port, ended_ip, ended_port, protocol, addtime', (s_port, d_ip, d_port, protocol, addtime)) self.FirewallReload() - public.WriteLog("system firewall", "Add port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {}".format(s_port, d_port, d_ip)) + public.WriteLog("system firewall", + "Add port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {}".format(s_port, + d_port, + d_ip)) return public.returnMsg(True, 'ADD_SUCCESS') + # 删除端口转发 def remove_forward(self, get): id = get.id @@ -1902,11 +1978,14 @@ class main(safeBase): self.firewall_handle_del(s_port, d_port, d_ip, protocol) else: self.iptables_handle_del(s_port, d_port, d_ip, protocol) - public.M('firewall_trans').where("id=?", (id, )).delete() + public.M('firewall_trans').where("id=?", (id,)).delete() self.FirewallReload() - public.WriteLog("system firewall", "Delete port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {}".format(s_port, d_port, d_ip)) + public.WriteLog("system firewall", + "Delete port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {}".format( + s_port, d_port, d_ip)) return public.returnMsg(True, 'DEL_SUCCESS') + # 修改端口转发 def modify_forward(self, get): id = get.id @@ -1914,19 +1993,19 @@ class main(safeBase): d_port = get.d_ports.strip() d_ip = get.d_address.strip() pool = get.protocol - rep1 = "^\d{1,5}(:\d{1,5})?$" + rep1 = r"^\d{1,5}(:\d{1,5})?$" if not re.search(rep1, s_port): return public.returnMsg(False, 'PORT_CHECK_RANGE') if not re.search(rep1, d_port): return public.returnMsg(False, 'PORT_CHECK_RANGE') - data = public.M('firewall_trans').where('id=?', (id, )).field( + data = public.M('firewall_trans').where('id=?', (id,)).field( 'id,start_port,ended_ip,ended_port,protocol,addtime').find() start_port = data.get("start_port", "") ended_ip = data.get("ended_ip", "") ended_port = data.get("ended_port", "") protocol = data.get("protocol", "") if d_ip: - rep = "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" if not re.search(rep, get.d_address) and not public.is_ipv6( get.d_address): return public.returnMsg(False, 'FIREWALL_IP_FORMAT') @@ -1950,9 +2029,12 @@ class main(safeBase): public.M('firewall_trans').where('id=?', id).update( {'start_port': s_port, "ended_ip": d_ip, "ended_port": d_port, "protocol": pool}) self.FirewallReload() - public.WriteLog("system firewall", "Modify port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {} -> Start port: {}, Destination port: {}, Destination IP: {}".format(start_port, ended_port, ended_ip, s_port, d_port, d_ip)) + public.WriteLog("system firewall", + "Modify port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {} -> Start port: {}, Destination port: {}, Destination IP: {}".format( + start_port, ended_port, ended_ip, s_port, d_port, d_ip)) return public.returnMsg(True, 'Successful operation.') + # 处理ufw的端口转发添加 def ufw_handle_add(self, s_port, d_port, d_ip, protocol): content = self.get_profile(self._ufw_before) @@ -1976,6 +2058,7 @@ class main(safeBase): array.insert(result + 1, _string) return '\n'.join(array) + # 处理ufw的端口转发删除 def ufw_handle_del(self, s_port, d_port, d_ip, protocol): content = self.get_profile(self._ufw_before) @@ -1989,6 +2072,7 @@ class main(safeBase): content = content.replace(_string, "") return content + # 处理ufw的端口转发修改 def ufw_handle_update(self, start_port, ended_ip, ended_port, protocol, s_port, d_ip, d_port, pool): @@ -2010,6 +2094,7 @@ class main(safeBase): content = content.replace(s_string, d_string) return content + # 处理firewall的端口转发添加 def firewall_handle_add(self, s_port, d_port, d_ip, protocol): if protocol.find('/') != -1: @@ -2025,6 +2110,7 @@ class main(safeBase): cmd = "firewall-cmd --permanent --zone=public --add-forward-port=port=" + s_port + ":proto=" + protocol + ":toaddr=" + d_ip + ":toport=" + d_port + "" public.ExecShell(cmd) + # 处理firewall的端口转发删除 def firewall_handle_del(self, s_port, d_port, d_ip, protocol): if protocol.find('/') != -1: @@ -2042,6 +2128,7 @@ class main(safeBase): + s_port + ":proto=" + protocol + ":toaddr=" + d_ip + ":toport=" + d_port + "") + # 处理firewall的端口转发修改 def firewall_handle_update(self, start_port, ended_ip, ended_port, protocol, s_port, d_ip, d_port, pool): @@ -2074,6 +2161,7 @@ class main(safeBase): + s_port + ":proto=" + pool + ":toaddr=" + d_ip + ":toport=" + d_port + "") + # 处理iptables的端口转发添加 def iptables_handle_add(self, s_port, d_port, d_ip, protocol): if d_ip == "": @@ -2104,6 +2192,7 @@ class main(safeBase): public.ExecShell("iptables -t nat -A POSTROUTING -j MASQUERADE") return True + # 处理iptables的端口转发删除 def iptables_handle_del(self, s_port, d_port, d_ip, protocol): if d_ip == "": @@ -2131,6 +2220,7 @@ class main(safeBase): public.ExecShell("iptables -t nat -D POSTROUTING -j MASQUERADE") return True + # 处理iptables的端口转发删除 def iptables_handle_update(self, start_port, ended_ip, ended_port, protocol, s_port, d_ip, d_port, pool): @@ -2182,6 +2272,7 @@ class main(safeBase): public.ExecShell("iptables -t nat -A POSTROUTING -j MASQUERADE") return True + # 开启端口转发 def open_forward(self): if self.__isUfw: @@ -2207,6 +2298,7 @@ class main(safeBase): self.FirewallReload() return True + # 开启或关闭端口转发 def open_close_forward(self, get): if not get.status in ["open", "close"]: @@ -2242,11 +2334,12 @@ class main(safeBase): public.ExecShell('sysctl -p /etc/sysctl.conf') return public.returnMsg(True, "Turn off port forwarding") + def get_host_ip(self): """ - 查询本机ip地址 - :return: - """ + 查询本机ip地址 + :return: + """ try: import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -2257,6 +2350,7 @@ class main(safeBase): return ip + def load_white_list(self): try: if not self._white_list: @@ -2272,6 +2366,7 @@ class main(safeBase): public.WriteLog("firewall", "Failed to load whitelist!") return [] + def verify_ip(self, ip_entry): """检查规则IP是否和内网IP重叠""" try: @@ -2304,6 +2399,7 @@ class main(safeBase): except: return False + def handle_firewall_country(self, brief, ip_list, types, port_list): try: public.ExecShell( @@ -2333,6 +2429,7 @@ class main(safeBase): except Exception as e: return {"status": "error", "msg": e} + def handle_ufw_country(self, brief, ip_list, types, port_list): tmp_path = '/tmp/firewall_tmp.sh' tmp_file = open(tmp_path, 'w') @@ -2353,9 +2450,9 @@ class main(safeBase): public.ExecShell('iptables -I INPUT -m set --match-set ' + brief + ' src -j ' + types.upper()) + # 查询区域规则 def get_country_list(self, args): - p = 1 limit = 15 if 'p' in args: p = args.p @@ -2374,6 +2471,7 @@ class main(safeBase): data['shift'], data['row'])).order('addtime desc').select() return data + def create_countrys(self, get): try: if not hasattr(get, 'country'): @@ -2411,13 +2509,14 @@ class main(safeBase): print(traceback.format_exc()) return public.returnMsg(False, 'Add failed') + # 添加区域规则 def create_country(self, get, is_mutil=False, _ips_paths=None): brief = get.brief types = get.types # types in [accept, drop] ports = get.ports country = get.country - rep = "^\d{1,5}(:\d{1,5})?$" + rep = r"^\d{1,5}(:\d{1,5})?$" port_list = [] # 检测该区域是否已添加过全部端口规则 hezhihong @@ -2485,9 +2584,11 @@ class main(safeBase): # elif get.types == 'drop': # strategy = "drop" public.print_log('############ 地区接口5'.format()) - public.WriteLog("system firewall", "Add regional rules: Region:{}, Policy:{}, Port:{}".format(get.country, get.types, log_port)) + public.WriteLog("system firewall", + "Add regional rules: Region:{}, Policy:{}, Port:{}".format(get.country, get.types, log_port)) return public.returnMsg(True, 'ADD_SUCCESS') + # 删除区域规则 def remove_country(self, get): id = get.id @@ -2498,7 +2599,7 @@ class main(safeBase): reload = True if "not_reload" in get: reload = get.not_reload.lower() == "true" - public.M('firewall_country').where("id=?", (id, )).delete() + public.M('firewall_country').where("id=?", (id,)).delete() if self.__isUfw: if not ports: public.ExecShell('iptables -D INPUT -m set --match-set ' + @@ -2508,7 +2609,7 @@ class main(safeBase): brief + ' src -p tcp --destination-port ' + ports + ' -j ' + types.upper()) if not public.M('firewall_country').where("country=?", - (country, )).count() > 0: + (country,)).count() > 0: public.ExecShell('ipset destroy ' + brief) else: if self.__isFirewalld: @@ -2522,7 +2623,7 @@ class main(safeBase): + brief + '" port port="' + ports + '" protocol=tcp ' + types + '\'') if not public.M('firewall_country').where( - "country=?", (country, )).count() > 0: + "country=?", (country,)).count() > 0: public.ExecShell( 'firewall-cmd --permanent --zone=public --delete-ipset=' + brief) @@ -2536,7 +2637,7 @@ class main(safeBase): ' src -p tcp --destination-port ' + ports + ' -j ' + types.upper()) if not public.M('firewall_country').where( - "country=?", (country, )).count() > 0: + "country=?", (country,)).count() > 0: public.ExecShell('ipset destroy ' + brief) if reload: get.status = "restart" @@ -2550,9 +2651,11 @@ class main(safeBase): strategy = "accept" elif get.types == 'drop': strategy = 'drop' - public.WriteLog("system firewall", "Delete zone rules: Region:{}, Policy:{}, Port:{}".format(get.country, strategy, log_port)) + public.WriteLog("system firewall", + "Delete zone rules: Region:{}, Policy:{}, Port:{}".format(get.country, strategy, log_port)) return public.returnMsg(True, 'DEL_SUCCESS') + # 编辑区域规则 def modify_country(self, get): # 2022/11/24 修复编辑地区端口规则问题 lx @@ -2562,7 +2665,7 @@ class main(safeBase): # country = get.country data = public.M('firewall_country').where( 'id=?', - (id, )).field('id,country,types,brief,ports,addtime').find() + (id,)).field('id,country,types,brief,ports,addtime').find() ori_get = public.dict_obj() ori_get.id = id ori_get.types = data.get("types", "") @@ -2577,6 +2680,7 @@ class main(safeBase): return public.returnMsg(True, "Successful operation") return public.returnMsg(False, "operation failed") + # 获取服务端列表:centos def GetList(self): try: @@ -2615,6 +2719,7 @@ class main(safeBase): file = open('error.txt', 'w') return public.returnMsg(False, e) + # 获取服务端列表:ufw def get_ufw_list(self): data = public.M('firewall').field('id,port,ps,addtime').select() @@ -2638,6 +2743,7 @@ class main(safeBase): except: pass + # 检查数据库是否存在 def check_db_exists(self, ports, address, types): if ports: @@ -2653,6 +2759,7 @@ class main(safeBase): if dt["address"] == address and dt["types"] == types: return dt return False + def check_trans_data(self, ports): data = public.M('firewall_trans').field( 'id,start_port,ended_ip,ended_port,protocol,addtime').select() @@ -2660,6 +2767,7 @@ class main(safeBase): if dt['start_port'] == ports: return dt return False + # 规则导出:服务器 def export_rules(self, get): rule_name = get.rule_name @@ -2689,9 +2797,10 @@ class main(safeBase): write_string += str(i[v]) + "|" write_string += '\n' public.writeFile(filename, write_string) - public.WriteLog("system firewall", "导出端口规则") + public.WriteLog("system firewall", "Export port rules") return public.returnMsg(True, filename) + # 规则导出:本地 def get_file(self, args): filename = args.filename @@ -2703,6 +2812,7 @@ class main(safeBase): attachment_filename=os.path.basename(filename), cache_timeout=0) + # 规则导入:json def import_rules(self, get): try: @@ -2788,12 +2898,15 @@ class main(safeBase): not_pay_list = ("
                                    " + "-" * 20 + "
                                    ").join(not_pay_list) return public.ReturnMsg( result["status"], - "{}
                                    The designated domain name function is exclusive to the Enterprise Edition, and the following rules are not imported:
                                    {}".format(result["msg"], not_pay_list) + "{}
                                    The designated domain name function is exclusive to the Enterprise Edition, and the following rules are not imported:
                                    {}".format( + result["msg"], not_pay_list) ) public.WriteLog("system firewall", "Import port rules") return public.ReturnMsg(result["status"], result["msg"]) except Exception: - return public.ReturnMsg(False, "The import failed. The format of the rules is wrong. Please try again according to the format of the export rules!") + return public.ReturnMsg(False, + "The import failed. The format of the rules is wrong. Please try again according to the format of the export rules!") + # 处理规则导入,读取json文件内容 def hand_import_rules(self, rule_name, data_list): @@ -2802,7 +2915,7 @@ class main(safeBase): if rule_name == "port_rule": table_head = ["id", "protocol", "ports", "types", "address", "brief", "addtime", "domain", ] for data in data_list: - #兼容一行一条规则格式文件导入 hezhihong + # 兼容一行一条规则格式文件导入 hezhihong try: data = json.loads(data) except: @@ -2891,25 +3004,26 @@ class main(safeBase): return {"status": False, "msg": "Import failed!"} return {"status": True, "msg": "Imported successfully!"} + def get_countrys(self, get): result = [] content = self.get_profile(self._country_path) result = json.loads(content) - result = sorted(result, key=lambda x : x['CH'], reverse=True); + result = sorted(result, key=lambda x: x['CH'], reverse=True); if isinstance(result, list): result.insert(0, {"CH": "Except China", "brief": "OTHER"}) return result + # 读取配置文件 def get_profile(self, path): - if not os.path.exists(path): b_path = os.path.dirname(path) if not os.path.exists(b_path): os.makedirs(b_path) if path in [ - self._ips_path, self._country_path, self._white_list_file + self._ips_path, self._country_path, self._white_list_file ]: public.downloadFile( 'https://download.bt.cn/install/lib/{}'.format( @@ -2920,22 +3034,26 @@ class main(safeBase): content = fr.read() return content + # 保存配置文件 def save_profile(self, path, data): with open(path, "w") as fw: fw.write(data) + # 读取配置文件 def update_profile(self, path): import files f = files.files() return f.GetFileBody(path) + # 获取端口规则列表 def get_port_rules(self, get): rule_list = public.M('firewall_new').order("id desc").select() return public.returnMsg(True, rule_list) + # 整理配置文件格式 def format(self, em, level=0): i = "\n" + level * " " @@ -2949,6 +3067,7 @@ class main(safeBase): if level and (not em.tail or not em.tail.strip()): em.tail = i + def check_table(self): if public.M('sqlite_master').where('type=? AND name=?', ('table', 'firewall_new')).count(): @@ -2956,13 +3075,14 @@ class main(safeBase): 'type=? AND name=?', ('table', 'firewall_ip')).count(): if public.M('sqlite_master').where( 'type=? AND name=?', - ('table', 'firewall_trans')).count(): + ('table', 'firewall_trans')).count(): if public.M('sqlite_master').where( 'type=? AND name=?', - ('table', 'firewall_country')).count(): + ('table', 'firewall_country')).count(): return True return Sqlite() + def delete_service(self): if self.__isUfw: public.ExecShell('ufw delete allow ssh') @@ -2975,6 +3095,7 @@ class main(safeBase): pass return True + # 获取系统类型(具体到哪个版本) def get_os_info(self): tmp = {"osname": "", "version": ""} @@ -3000,14 +3121,15 @@ class main(safeBase): public.ExecShell("systemctl restart firewalld") return True + # 新加代码----- start def sync_must_ports(self, get): ''' - 同步必须放行的端口 - @param get: - @return: - ''' + 同步必须放行的端口 + @param get: + @return: + ''' protocol = "tcp" ports = get.ports.strip() print(ports) @@ -3055,11 +3177,12 @@ class main(safeBase): print(traceback.format_exc()) return public.returnMsg(False, 'ADD_ERROR') + def _get_webserver(self): ''' - 获取web服务器类型 - @return: - ''' + 获取web服务器类型 + @return: + ''' webserver = '' if os.path.exists('/www/server/nginx/sbin/nginx'): webserver = 'nginx' @@ -3069,13 +3192,14 @@ class main(safeBase): webserver = 'lswsctrl' return webserver + def get_port_info(self, get): ''' - 获取面板防火墙关键服务端口放行状态信息 - 判断服务是否存在,能读取文件就读取文件,配置文件不大不会影响性能,这种方式能最大缩短接口响应时间,公网测试80ms - @param get: - @return: - ''' + 获取面板防火墙关键服务端口放行状态信息 + 判断服务是否存在,能读取文件就读取文件,配置文件不大不会影响性能,这种方式能最大缩短接口响应时间,公网测试80ms + @param get: + @return: + ''' ports_list = [] result_list = [{"name": "FTP passive port", "status": 0, "port": "39000-40000"}] @@ -3113,13 +3237,14 @@ class main(safeBase): return self._get_firewall_port_status(ports_list, result_list) return {} + def _get_firewall_port_status(self, ports_list, result_list): ''' - 获取firewalld防火墙端口状态 - @param ports_list: - @param result_list: - @return: - ''' + 获取firewalld防火墙端口状态 + @param ports_list: + @param result_list: + @return: + ''' with contextlib.suppress(Exception): _firewalld_ports, _ = self.__firewall_obj.GetAcceptPortList() # print("_firewalld_ports: ", _firewalld_ports) @@ -3131,13 +3256,14 @@ class main(safeBase): break return result_list + def _get_ufw_port_status(self, ports_list, result_list): ''' - 获取ufw防火墙端口状态 - @param ports_list: - @param result_list: - @return: - ''' + 获取ufw防火墙端口状态 + @param ports_list: + @param result_list: + @return: + ''' with contextlib.suppress(Exception): rules_result = self._get_ufw_port_info() # print("rules_result: ", rules_result) @@ -3154,11 +3280,12 @@ class main(safeBase): if not ports_set: break return result_list + def _get_ufw_port_info(self): ''' - 获取ufw防火墙端口信息 - @return: - ''' + 获取ufw防火墙端口信息 + @return: + ''' with open('/etc/ufw/user.rules', 'r') as f: content = f.read() start_index = content.find('### RULES ###') @@ -3182,15 +3309,16 @@ class main(safeBase): rules = [dict(item) for item in unique_set] return rules + @staticmethod - def get_listening_processes(get): + def get_listening_processes(get: public.dict_obj): ''' - 获取指定端口的进程信息 - @param get: - @return: - ''' - print("get.__dict__.keys(): ", get.__dict__.keys()) - if 'port' not in get.__dict__.keys(): return public.returnMsg(False, 'Parameter passing error, please pass the port field') + 获取指定端口的进程信息 + @param get: + @return: + ''' + if 'port' not in get.get_items().keys(): return public.returnMsg(False, + 'Parameter passing error, please pass the port field') if len(get.port) == 0: return public.returnMsg(False, 'Port cannot be empty') if get.port.find('-') != -1 or get.port.find(':') != -1: return public.returnMsg(False, 'Range ports not supported') if not get.port.isdigit(): return public.returnMsg(False, 'Port must be numeric') @@ -3215,11 +3343,12 @@ class main(safeBase): "process_cmd": process_cmd } + def get_diff_panel_firewall_rules(self, get): ''' - 对比面板防火墙规则数据库和防火墙配置文件,取出差异的规则 - @return: - ''' + 对比面板防火墙规则数据库和防火墙配置文件,取出差异的规则 + @return: + ''' # 获取面板防火墙规则数据库 panel_firewall_rules = self.get_panel_firewall_rules() # 获取防火墙配置文件 @@ -3228,32 +3357,35 @@ class main(safeBase): diff_rules = self._get_diff_rules(panel_firewall_rules, firewall_rules) return diff_rules + def get_panel_firewall_rules(self): ''' - 获取面板防火墙规则数据库 - @return: - ''' + 获取面板防火墙规则数据库 + @return: + ''' all_ports = public.M('firewall_new').field('protocol,ports,types,address').order('addtime desc').select() unique_set = set(tuple(sorted(item.items())) for item in all_ports) new_ports = [dict(item) for item in unique_set] return new_ports + def get_sys_firewall_rules(self): ''' - 获取防火墙配置文件 - @return: - ''' + 获取防火墙配置文件 + @return: + ''' if self.__isUfw: return self._get_ufw_port_info() if self.__isFirewalld: return self.__firewall_obj.recombine_rules() return [] + def _diff_dict_list(self, list1, list2): ''' - 比较两个dict类型的list,返回list1中有,而list2中没有的元素 - @param list1: - @param list2: - @return: - ''' + 比较两个dict类型的list,返回list1中有,而list2中没有的元素 + @param list1: + @param list2: + @return: + ''' list1_not_in_list2 = [] for item1 in list1: found = False @@ -3268,13 +3400,14 @@ class main(safeBase): if not found: list1_not_in_list2.append(item1) return list1_not_in_list2 + def _get_diff_rules(self, panel_firewall_rules, firewall_rules): ''' - 取出差异的规则 - @param panel_firewall_rules: - @param firewall_rules: - @return: - ''' + 取出差异的规则 + @param panel_firewall_rules: + @param firewall_rules: + @return: + ''' firewall_diff_rules_name = 'firewall_diff_rules' firewall_diff_rules = {} if os.path.isfile("config/{}.json".format(firewall_diff_rules_name)): @@ -3309,16 +3442,17 @@ class main(safeBase): public.save_config(firewall_diff_rules_name, firewall_diff_rules) return firewall_diff_rules + def exclude_diff_rules(self, get): ''' - 排除firewall_diff_rules的规则,并写入配置文件 - @param get: - @return: - ''' + 排除firewall_diff_rules的规则,并写入配置文件 + @param get: + @return: + ''' try: - panel_excludes = get.panel_exclude if "panel_exclude" in get.__dict__.keys() else {} - sys_excludes = get.sys_exclude if "sys_exclude" in get.__dict__.keys() else {} - status = get.status if "status" in get.__dict__.keys() else {} + panel_excludes = get.panel_exclude if "panel_exclude" in get.get_items().keys() else {} + sys_excludes = get.sys_exclude if "sys_exclude" in get.get_items().keys() else {} + status = get.status if "status" in get.get_items().keys() else {} # print("panel_excludes: ", panel_excludes) if status == 'add': @@ -3329,13 +3463,14 @@ class main(safeBase): # print(e) return public.returnMsg(False, 'Ignore rule failed,{}!'.format(e)) + def _add_exclude(self, panel_excludes, sys_excludes): ''' - 添加排除规则 - @param panel_exclude: - @param sys_exclude: - @return: - ''' + 添加排除规则 + @param panel_exclude: + @param sys_exclude: + @return: + ''' firewall_diff_rules_name = 'firewall_diff_rules' firewall_diff_rules = {} if os.path.isfile("config/{}.json".format(firewall_diff_rules_name)): @@ -3357,13 +3492,14 @@ class main(safeBase): public.save_config(firewall_diff_rules_name, firewall_diff_rules) return public.returnMsg(True, 'Ignore rules successfully!') + def _del_exclude(self, panel_excludes, sys_excludes): ''' - 删除排除规则 - @param panel_excludes: - @param sys_excludes: - @return: - ''' + 删除排除规则 + @param panel_excludes: + @param sys_excludes: + @return: + ''' firewall_diff_rules_name = 'firewall_diff_rules' firewall_diff_rules = {} if os.path.isfile("config/{}.json".format(firewall_diff_rules_name)): @@ -3384,15 +3520,16 @@ class main(safeBase): public.save_config(firewall_diff_rules_name, firewall_diff_rules) return public.returnMsg(True, 'Cancel ignore rule successfully!') + def _add_firewall_rules(self, source_ip, protocol, port, types): ''' - 添加防火墙规则 - @param source_ip: - @param protocol: - @param port: - @param types: - @return: - ''' + 添加防火墙规则 + @param source_ip: + @param protocol: + @param port: + @param types: + @return: + ''' if self.__isUfw: if port.find('-') != -1: port = port.replace('-', ':') @@ -3404,15 +3541,16 @@ class main(safeBase): else: self.add_iptables_rule(source_ip, protocol, port, types) + def _del_firewall_rules(self, source_ip, protocol, port, types): ''' - 删除防火墙规则 - @param source_ip: - @param protocol: - @param port: - @param types: - @return: - ''' + 删除防火墙规则 + @param source_ip: + @param protocol: + @param port: + @param types: + @return: + ''' if self.__isUfw: self.del_ufw_rule(source_ip, protocol, port, types) elif self.__isFirewalld: @@ -3420,19 +3558,20 @@ class main(safeBase): else: self.del_iptables_rule(source_ip, protocol, port, types) + def _modify_firewall_rules(self, address, protocol, port, type, source_ip, source_protocol, ports, types): ''' - 修改防火墙规则1 - @param address: - @param protocol: - @param port: - @param type: - @param source_ip: - @param source_protocol: - @param ports: - @param types: - @return: - ''' + 修改防火墙规则1 + @param address: + @param protocol: + @param port: + @param type: + @param source_ip: + @param source_protocol: + @param ports: + @param types: + @return: + ''' if self.__isUfw: self.edit_ufw_rule(address, protocol, port, type, source_ip, source_protocol, ports, types) elif self.__isFirewalld: @@ -3440,14 +3579,15 @@ class main(safeBase): else: self.edit_iptables_rule(address, protocol, port, type, source_ip, source_protocol, ports, types) + # 新加代码----- end # 端口防扫描 --- start def _get_server_lists_scan(self): """ - @name 获取服务器常用端口 - @return: - """ + @name 获取服务器常用端口 + @return: + """ return { "sshd": "{}".format(public.get_sshd_port()), "mysql": "{}".format(public.get_mysql_info()["port"]), @@ -3456,12 +3596,13 @@ class main(safeBase): "postfix": "25,465,587", } + def get_anti_scan_logs(self, get): """ - @name 获取防扫描日志 - @param get: - @return: - """ + @name 获取防扫描日志 + @param get: + @return: + """ get = public.dict_obj() server_lists = self._get_server_lists_scan() result_dict = { @@ -3486,11 +3627,12 @@ class main(safeBase): return result_dict + def get_anti_scan_status(self, get): """ - @name 获取端口防扫描 - @return: - """ + @name 获取端口防扫描 + @return: + """ plugin_path = "/www/server/panel/plugin/fail2ban" result_data = {"status": 0, "installed": 1} if not os.path.exists("{}".format(plugin_path)): @@ -3522,6 +3664,7 @@ class main(safeBase): return result_data + def set_anti_scan_status(self, get): """ @name 设置常用端口防扫描 @@ -3582,6 +3725,7 @@ class main(safeBase): public.WriteLog("Port Scanning Prevention", "[Security]-[System Firewall]-[Set Port Scanning Prevention]") return public.returnMsg(True, "Setup successful!") + def del_ban_ip(self, get): """ 删除封锁IP @@ -3598,7 +3742,7 @@ class main(safeBase): return public.returnMsg(True, "Unlocked successfully") -# 端口放扫描 --- end111 +# 端口防扫描 --- end111 class firewalld: @@ -3809,7 +3953,6 @@ class Sqlite(): "addtime" TEXT DEFAULT '');''') public.M('').execute('CREATE INDEX firewall_domain_addr ON firewall_domain (domain);') - # 修复之前已经创建的 firewall_domain 表无 domain_total 字段的问题 create_table_str = public.M('firewall_new').table('sqlite_master').where( 'type=? AND name=?', ('table', 'firewall_new')).getField('sql') @@ -3858,4 +4001,4 @@ sql = """ END; """ s = Sqlite() -s.create_trigger(sql) \ No newline at end of file +s.create_trigger(sql) diff --git a/class/safeModel/freeipModel.py b/class/safeModel/freeipModel.py index c00471e2..edc4545e 100644 --- a/class/safeModel/freeipModel.py +++ b/class/safeModel/freeipModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: cjxin +# Author: cjxin #------------------------------------------------------------------- # 免费IP库 diff --git a/class/safeModel/ipsModel.py b/class/safeModel/ipsModel.py index a5d28ee4..2d6963f3 100644 --- a/class/safeModel/ipsModel.py +++ b/class/safeModel/ipsModel.py @@ -1,118 +1,118 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: cjxin -#------------------------------------------------------------------- - -# 商用IP库 -#------------------------------ -import os,re,json,time -from safeModel.base import safeBase -import public - - -class main(safeBase): - - _sfile = '{}/data/ip_area.json'.format(public.get_panel_path()) - def __init__(self): - try: - self.user_info = public.get_user_info() - except: - self.user_info = None - - def get_ip_area(self,get): - """ - @获取IP地址所在地 - @param get: dict/array - """ - ips = get['ips'] - arrs,result = [],{} - for ip in ips: - info = {} - res = self.__check_ip_area(ip) - if res: - if type(res) == str: - info['info'] = res - else: - info = res - result[ip] = info - else: - arrs.append(ip) - - if len(arrs) > 0: - data = self.__get_cloud_ip_info(arrs) - for ip in data: - result[ip] = data[ip] - return result - - def __check_ip_area(self,ip): - """ - @检查IP地址所在地 - @param ip: - """ - - if not public.is_ipv4(ip): - return 'Unknown' - if public.is_local_ip(ip): - return 'Intranet' - - data = self.get_ip_area_cache() - if ip in data: - return data[ip] - return False - - - def __get_cloud_ip_info(self,ips): - """ - @获取IP地址所在地 - @param ips: - """ - result = {} - try: - - data = {} - data['ip'] = ','.join(ips) - data['uid'] = self.user_info['uid'] - data["serverid"]=self.user_info["serverid"] - res = public.httpPost('https://www.bt.cn/api/panel/get_ip_info',data) - res = json.loads(res) - - data = self.get_ip_area_cache() - for key in res: - if not public.is_ipv4(key): continue - - info = res[key] - if not res[key]['city'].strip() and not res[key]['continent'].strip(): - info = {'info':'Intranet'} - else: - info['info'] = '{} {} {} {}'.format(info['carrier'],info['country'],info['province'],info['city']).strip() - - data[key] = info - result[key] = info - self.set_ip_area_cache(data) - except: pass - return result - - - def get_ip_area_cache(self): - """ - @获取IP地址所在地 - @param get: - """ - data = {} - try: - data = json.loads(public.readFile(self._sfile)) - except: - public.writeFile(self._sfile,json.dumps({})) - return data - - def set_ip_area_cache(self,data): - """ - @设置IP地址所在地 - @param data: - """ - public.writeFile(self._sfile,json.dumps(data)) - return True \ No newline at end of file +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +rUv3hasppUA6pOugYcQZyA== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +iEprTHe70MI/8Rhzd8EK+QnVQn6aZyZ8dBODiF5pySg= +1w/Bux0eSLh4laruMV+kEu4fb+1yCwxQ0hOHwrLNXXRaWZyv1p2zipoavmFan6NZ +XIfdJ79nObMM+vyAmKbTmw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +hbDorme8BqzUEmeXCAWmYa52umm3PBCcfSvATzTG02w= +1u+XjG/2+GSQRv6EzCaWRQ== +giysigZ4bhExZB4emZkX3a9fe6PGGMNE9ZZnk7xPqW8QW2Ij1yNFf5XEj5pPPlMwdhA6dIcO2ZX23qJszvnq1fcl6NckihyZ+Uf83rbYjeQ= +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +b4OJVZe8QyIpjuTpKXDL9A== +32pdC9DD05OE2l0oXazDFI+hlwIzzkFTo7tZH3axSGoKNgh7EevAzaB+FO2ZLVS/maKOtFAjIeKErPBj7dsfmg== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +32pdC9DD05OE2l0oXazDFOQO8mg+tgs322u5NSxm1hnE+dYVd/hkfCsat3ixeRAk +1u+XjG/2+GSQRv6EzCaWRQ== +Fq/4dKD9ssEKrNEHrSuygxu04P7BPF8PboiRMtr4FMM= +Z8UsPk1Q7HtwjRd4g01ryw== +KNy3a/ETENhyr8ymSkKe9l1RX/SenKsGMtRp3/Nl0CxdpK2w2cyo43SfPqzKitUD +ho/Q+jrWDtBeg9J9ZTnKi7MMSdcDWkuDSRbkOa0slC0= +Z8UsPk1Q7HtwjRd4g01ryw== +1aJ5h9ef6qScYRSEXHxMz/JV+hrqnP7g6CgzmGbTA34= +CH5utP+NORdjI2nqATw4gJ30bQaw4oV4TkWtZlCiO9A= +lbIj6ug3LX3xS019kmbRSTcfm4XASPCYnVO8MD2z14s= +iZaIsa48RXfE+uf/yF/rQD9SK8CJ49+yPAMIKmMZPD4= +lOh2GtzHjjMM8E9J40AuOc+/vc1yhUL+xJx/Mivlb25pg5HBJ1HJ91Rfq30bJq9S +UbJuac0dxLiN+5ocS3w2vRp92UK1M7Voei4ApHZyTgY= +VmVrGQo2zRokW/ZuO9bN69BDpCzHoJtTjTgNlAOe5sUQUaSnZ1Z1hl5M9Ym+7tCG +VmVrGQo2zRokW/ZuO9bN61V3/TpT/zrd2QvdUtMgGKRq8f0CD0RNnZ4rTqSInsSj +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN657tYrblt+z3q06zgiQZcYA= +VmVrGQo2zRokW/ZuO9bN66aFKP1IdEjWKaMooH0pyYReSxgrF6gfc+R+CrZAggrY +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN69KBqHrgU1XKz6C1sxuKNo2/9c/EcmFt/gfo+iaOu2+K +1u+XjG/2+GSQRv6EzCaWRQ== +n+FHKmWLbioNpC38yMj4WmiXmXqyLuzKUmIAM1Ft5eM= +NhnIR3Ilo4H2su9/cTNo/MME7jTfPzQcknP67wyItNzcCacoNjdvCuiW0x8udsSO/edftRARPJ4xwm1wQrVT2A== +wlLHv6kT3Q/RmtMBN4nDATwL/9Oa0sOvSxwVwQfq99U= +VmVrGQo2zRokW/ZuO9bN68Jg91voF9Ce3nf2o3e+jkofqu9SSuRXcnpG7smrmLLo +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhMRKs67DB9ZOIiBDrB02YSQctSNS1aqQlPvqVprQ2WDG +Z8UsPk1Q7HtwjRd4g01ryw== +fXZfnC6cxxgiN+onu+xJ1wUhirDdf4kByrL5vhQHFnCYKlzy/eVgdjzy55WiEFTz +LikBEoRjt8wwWzUZNw+jJgsad0LKWFNZK+YO+SfR6UU= +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +W3A5Dt+8AxWSKsTYeXZoL+tHA8UsZgQuAUkDUOPJ9p9irMcLKjcsuwS2q6lHXAKR +L0eUthVnpkGsmKFAX6d+uOlJx8vHVMiotg8sk086vHQ= +TQRmC0vOvJ1P+lfyS3Os4X1xcTKiQ06lXIJEwRwWbq4ieECEmB4BmDhbitv1CsLg +L0eUthVnpkGsmKFAX6d+uLqKlmsh+FpVhvWy0B6mjhM= +1u+XjG/2+GSQRv6EzCaWRQ== +YabJCT93a3/IohXaIkr3oS6/BpCE446GJgDFFvo/yjjYAG9E6229ssUIWmf519v1 +hBWY30cr8RshUe3F5HK1cRZ9KmhsMz0lNR6pLxg9eoM= +L0eUthVnpkGsmKFAX6d+uFd45MXPp4WlUdlXMx7fhE8= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +avrdBvQ7h/91pEC4PcvqMSR2TJ0t+8EMPSsnARb+XVHHkBrQO1HsCg6QcbTZvZ2W +Z8UsPk1Q7HtwjRd4g01ryw== +KNy3a/ETENhyr8ymSkKe9l1RX/SenKsGMtRp3/Nl0CxdpK2w2cyo43SfPqzKitUD +LikBEoRjt8wwWzUZNw+jJpxJaQXGUN8g/Hr0khQ9A1Q= +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +b4OJVZe8QyIpjuTpKXDL9A== +1u+XjG/2+GSQRv6EzCaWRQ== +NhnIR3Ilo4H2su9/cTNo/F9IByyW2CHxX2+3PSbgZmE= +NhnIR3Ilo4H2su9/cTNo/JEWIdEQUX/MlMcOaFZ8tGgjCfBcJNHhmedart7PDRhF +NhnIR3Ilo4H2su9/cTNo/E4e/PRk3DEtgyjsUDeOpSOwGpZehbHYaLN6kP4SrhN+PFbXCI6E8Z+8865ULB7IOw== +NhnIR3Ilo4H2su9/cTNo/C/mkLay+Rx5WJjwGcYV7pSktyrOzy78W2NcadJ49lO1rNrUNnnzcDMri5NWEH6lGQ== +lOh2GtzHjjMM8E9J40AuOVVeyoh6rSy98QMdDBotkjU+CpzMJCQMfJrCkE4uEcQAgmW0jhOnZHI5mtglpnoAuAijuP+2lAOsmb6+b+801pIjLK0hXXo2u4rmG7jkFs3R +lOh2GtzHjjMM8E9J40AuOeMCiZGQsphriaBgvIdFiyOx9f7gw874ih7YoSVMDwrD +1u+XjG/2+GSQRv6EzCaWRQ== +NhnIR3Ilo4H2su9/cTNo/A5SzFv4xHkReni2nLCW9wf/+pP3k795kKoMZAQXwXD6 +wlLHv6kT3Q/RmtMBN4nDASCRBoPIX3WHiVt75UTjznI= +VmVrGQo2zRokW/ZuO9bN63WVsNPEwwprYYkAUqx5H2WQM74qCCEzrB3qJy/Mpn1CTpqeZGmRvqbYKmI/uSdTZw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61LJQniFugoi6x2ujjnJM7tldXKimSA8e3cVKdvY80W2 +VmVrGQo2zRokW/ZuO9bN64CPlXy2HmOeksJNoNOmCTRN2iWrExIMUdvTGpj/NNWq+ALT/bwKyDsjcfkGB9eQcrQ3H8UC+ywFIkXx32JcLCKuolLfVZKLqPTsQHTpYF9P +VmVrGQo2zRokW/ZuO9bN6yDw6frMtVHHQaPzsZSKK0aES6D4Kud9zFd68X/jO6wJ +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN61V3/TpT/zrd2QvdUtMgGKQFi9VDtwL4Vc83iegeMAft36zpJ+t/eeWtmA4Eamfb77jKlCL/2MWCO4n+tmFfKcs5bo8I5tFr5Qu31v4FEN0K0c0Au7xRuJmPMknxTzJ1AQ4L8Z8QbLuQ3EJTDAgG6zw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wKjnvaTwlMeHWSuJ/EAxZ8Z1KtkmFIaU/d9KNNMifcN +VmVrGQo2zRokW/ZuO9bN62ExR0OHxzNY3oC4Mfi694VYJyilbRPMd6JDlCBDdbCe +32pdC9DD05OE2l0oXazDFLdmyVEUr33LQ7qI5CZQN57igfVF4gan9s5C0Jnc3Ki1 +6ZPJI/HSoc4xA2zncU65FpfH+eGtPlOYNU1YCnnAis8= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Fq/4dKD9ssEKrNEHrSuyg9mm2ubmmEvtKER/ZGhfGKuM0YKPX8D/je6PexvVvgfs +Z8UsPk1Q7HtwjRd4g01ryw== +KNy3a/ETENhyr8ymSkKe9l1RX/SenKsGMtRp3/Nl0CxdpK2w2cyo43SfPqzKitUD +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +Z8UsPk1Q7HtwjRd4g01ryw== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +b4OJVZe8QyIpjuTpKXDL9A== +NhnIR3Ilo4H2su9/cTNo/Ps3vYSOiOY6esn1xeoWmAlmLwmaOoQ9T7lnCb6TRVZ67prVtxc96eRcwG0EieQAHA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+nhmNgO2Yqkfbla+bgPzSp/ulULnjCxA+skRNBtWTg0BA== ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +0jp9NuG0oWLkMfJ/MsYOmfu7WkSnZjHksXr6BBYLZSUOzHDXl9XjsxiUKNVbrHEG +Z8UsPk1Q7HtwjRd4g01ryw== ++kAMkUztiJJUlfW5H0qbp+x6TgEEQKPe4CNld5ATADH0jjvxLnFDBqMEQxxyT+xg +vPgTNKnHCM+ykPDJGfC5NIdfn4GuPGcMgxOHmcFE4sA= +Z8UsPk1Q7HtwjRd4g01ryw== +IhuisKim47k91RVt8z8qtK4Hi8QoBKGCIdLYuWbuvVULY/yxOc14YsJd+ymvu1M2ua6mSguBgb6gPVMjpVflaQ== +guSZID0bFQuDFoWO2uxAJhef0KHvUEISzfxiK9WGidA= diff --git a/class/safeModel/sshModel.py b/class/safeModel/sshModel.py index 1354f415..2676473e 100644 --- a/class/safeModel/sshModel.py +++ b/class/safeModel/sshModel.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- # ssh信息 @@ -43,6 +43,7 @@ class main(safeBase): result['error'] = int(public.ExecShell("journalctl -u ssh --no-pager |grep -a 'Failed password for' |grep -v 'invalid' |wc -l")[0]) + int(public.ExecShell("journalctl -u ssh --no-pager|grep -a 'Connection closed by authenticating user' |grep -a 'preauth' |wc -l")[0]) result['success'] = int(public.ExecShell("journalctl -u ssh --no-pager|grep -a 'Accepted' |wc -l")[0]) return result + # return public.return_message(0, 0, result) data = self.get_ssh_cache() for sfile in self.get_ssh_log_files(None): for stype in result.keys(): @@ -69,10 +70,11 @@ class main(safeBase): result[stype] += count self.set_ssh_cache(data) return result + # return public.return_message(0, 0, result) def get_ssh_cache(self): """ - @获取换成ssh记录 + @获取缓存ssh记录 """ file = '{}/data/ssh_cache.json'.format(public.get_panel_path()) if not os.path.exists(file): diff --git a/class/safeModel/syslogModel.py b/class/safeModel/syslogModel.py index 6dada622..8a1d876a 100644 --- a/class/safeModel/syslogModel.py +++ b/class/safeModel/syslogModel.py @@ -1,939 +1,993 @@ -#coding: utf-8 -#------------------------------------------------------------------- -# 宝塔Linux面板 -#------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. -#------------------------------------------------------------------- -# Author: hwliang -#------------------------------------------------------------------- - -# 系统日志 -#------------------------------ -import datetime -import json -import os -import re -import sys -import time - -import public -from safeModel.base import safeBase - - -class main(safeBase): - - def __init__(self): - ssh_cache_path = "{}/data/ssh".format(public.get_panel_path()) - if not os.path.exists(ssh_cache_path): os.makedirs(ssh_cache_path,384) - - - #*********************************************** start ssh收费模块 ****************************************************** - - def get_ssh_list(self,get): - """ - @获取SSH登录 - @param get: - count :数量 - """ - select_pl = ['Accepted', 'Failed password for'] - if hasattr(get, 'select'): - if get.select == "Accepted": - select_pl = ['Accepted'] - elif get.select == "Failed": - select_pl = ['Failed password for'] - - p = 1 - count = 20 - - if 'count' in get: count = int(get['count']) - if 'p' in get: p = int(get['p']) - - result = [] - min,max = (p -1) * count, p * count - - log_list = self.get_log_byfile(self.get_ssh_log_files(get)[0], min, max, - self.__get_search_list(get, select_pl)) - for log in log_list: - data = self.get_ssh_log_line(log['log'],log['time']) - if not data: continue - - result.append(data) - - if p < 1000000: public.set_module_logs('ssh_log','get_ssh_list') - return public.return_area(result,'address') - - - - def get_ssh_error(self,get): - """ - @获取SSH错误次数 - @param get: - count :数量 - """ - p = 1 - count = 20 - if 'count' in get: count = int(get['count']) - if 'p' in get: p = int(get['p']) - - result = [] - min,max = (p -1) * count, p * count - - log_list = self.get_log_byfile(self.get_ssh_log_files(get)[0],min,max,self.__get_search_list(get,['Failed password for'])) - - for log in log_list: - data = self.get_ssh_log_line(log['log'],log['time']) - if not data:continue - result.append(data) - - if p < 1000000: public.set_module_logs('ssh_log','get_ssh_list') - return public.return_area(result,'address') - - def get_ssh_success(self,get): - """ - @获取SSH登录成功次数 - @param get: - count :数量 - """ - p = 1 - count = 20 - if 'count' in get: count = int(get['count']) - if 'p' in get: p = int(get['p']) - - result = [] - min,max = (p -1) * count,p * count - - log_list = self.get_log_byfile(self.get_ssh_log_files(get)[0],min,max,self.__get_search_list(get,['Accepted'])) - - for log in log_list: - data = self.get_ssh_log_line(log['log'],log['time']) - if not data: continue - - result.append(data) - if p < 1000000: public.set_module_logs('ssh_log','get_ssh_list') - return public.return_area(result,'address') - - - def __get_search_list(self,get,slist): - """ - @组合搜索条件 - return list 查询组合 - status 1 增加查询条件 - """ - res = [] - if 'search' in get and get['search'].strip(): - search = get['search'].strip() - for info in slist: - res.append(info +'&' + search) - if len(res) == 0: - return search - return res - return slist - - - def get_ssh_log_line(self,log,log_time): - ''' - @name 获取ssh日志行 - @param log 日志行 - @param log_time 前一条记录的日志时间 - ''' - tmps = log.replace(' ',' ').split(' ') - if len(tmps) < 3: - return False - - data = {} - data['time'] = log_time - if log.find('closed by authenticating user') != -1: - data['user'] = tmps[10] - data['address'] = tmps[11] - data['port'] = tmps[13] - else: - data['user'] = tmps[8] - data['address'] = tmps[10] - data['port'] = tmps[12] - - data['status'] = 0 - if log.find('Accepted') >= 0: - data['status'] = 1 - return data - - #*********************************************** end ssh收费模块 ****************************************************** - - - - def get_curr_log_file(self,filename,search,min,max,log_list): - """ - @name 获取当前日志文件 - @param filename: 日志文件名 - @param search: 搜索条件 - @param min: 最小值 - @param max: 最大值 - @param log_list: 匹配的日志列表 - """ - - log_list.clear() - limit_max = '| tail -n {}'.format(max) - if search[0].find('&') >= 0: limit_max = '' - - shells = [ - "cat {}|grep -a 'Failed password for' |grep -v 'invalid' {} ".format(filename,limit_max), #登录失败 - "cat {}|grep -a 'Accepted' {}".format(filename,limit_max), - "cat {}|grep -E 'Failed password for|Accepted|Connection closed by authenticating user' |grep -v 'invalid' {} ".format(filename,limit_max), - "cat {}|grep -a 'Connection closed by authenticating user' |grep -a 'preauth' {} ".format(filename, - limit_max) - # 登录失败 - ] - if filename == 'journalctl': - shells = [ - "journalctl -u ssh --no-pager|grep -a 'Failed password for' |grep -v 'invalid' {} ".format(limit_max), - # 登录失败 - "journalctl -u ssh --no-pager|grep -a 'Accepted' {}".format(limit_max), - "journalctl -u ssh --no-pager|grep -E 'Failed password for|Accepted|Connection closed by authenticating user' |grep -v 'invalid' {} ".format( - limit_max), - "journalctl -u ssh --no-pager|grep -a 'Connection closed by authenticating user' |grep -a 'preauth' {} ".format( - limit_max) - # 登录失败 - ] - if len(search) == 1: - if search[0].find('Accepted') >= 0: - shells = [shells[1]] - else: - shells = [shells[0],shells[3]] - else: - shells = [shells[2]] - - result = [] - for shell in shells: - - res = public.ExecShell(shell)[0].strip().split('\n') - res.reverse() - for log in res: - result.append(log) - - find_idx = 0 - log_time = 0 - limit = max - min - for log in result: - log_time = self.get_log_pre_time(filename,log,log_time) - if len(log_list) >= limit: - break - - if self.__find_line_str(log,search): - - find_idx += 1 - if find_idx > min: - log = public.xssencode2(log.replace(' ',' ')) - log_list.append({'log':log,'time':log_time}) - - return find_idx - - - def get_sys_datetime(self,pre_time,log_time): - """ - @name 对比日志时间,日志时间保存年份,日志时间比前一条时间大,则判断为上一年, - @param pre_time 上一条日志时间 - @param log_time 当前日志时间 - @return 当前日志时间(校准年份) - """ - - if type(log_time) == str: - log_time = self.__get_to_date(log_time) - - if type(pre_time) == str: - pre_time = self.__get_to_date(pre_time) - - if (log_time > pre_time and pre_time > 0) or log_time >= time.time(): - d = datetime.datetime.strptime(public.format_date(times =log_time), "%Y-%m-%d %H:%M:%S") - n_date = self.__get_to_date('{}-{}-{} {}:{}:{}'.format(d.year-1,d.month,d.day,d.hour,d.minute,d.second)) - return public.format_date(times=n_date) - if type(log_time) == int: - return public.format_date(times=log_time) - return log_time - - - - def get_log_pre_time(self,log_file,_line,pre_time): - """ - @name 计算上次日志时间 - @param log_file 日志文件 - @param _line 日志行 - @param pre_time 上次日志时间 - @auther cjxin - """ - log_time = 0 - if _line[:3] in self._months: - log_time = self.to_date4(_line[:16].strip()) - elif _line[:2] in ['19','20','21','22']: - log_time = _line[:19].strip() - elif log_file.find('alternatives') >= 0: - _tmp = _line.split(": ") - _last = _tmp[0].split(" ") - log_time = ' '.join(_last[1:]).strip() - - log_time = self.get_sys_datetime(pre_time,log_time) - return log_time - - - def get_user(self): - ''' - @name 获取系统用户名 - :return: - ''' - pass_file = public.readFile("/etc/passwd") - pass_file = pass_file.split('\n') - user_list = [] - for p in pass_file: - p = p.split(':', 1) - user_list.append(p[0]) - return user_list - - - def get_log_byfile(self,sfile,min_num,max_num,search = None): - """ - @name 获取日志文件的日志 - @param sfile:日志文件 - @param min_num:起始行数 - @param max_num:结束行数 - @param search:搜索关键字 - """ - log_list = [] - h_find = None - #获取归档文件列表 - for info in self.get_sys_logfiles(None): - if info['log_file'] == sfile: - h_find = info - break - if os.path.exists('/etc/debian_version'): - version = public.readFile('/etc/debian_version').strip() - if 'bookworm' in version or 'jammy' in version or 'impish' in version: - version = 12 - else: - try: - version = float(version) - except: - version = 11 - if version >= 12: - h_find = {'log_file': 'journalctl', "list": [], 'uptime': time.time(), 'title': '授权日志', - 'size': 10000} - if not h_find: - return log_list - - #获取遍历文件列表 - file_list = [h_find['log_file']] - for info in h_find['list']: - file_list.append(info['log_file']) - - find_idx = 0 - log_time = 0 - limit = max_num - min_num - user_list = self.get_user() - - for filename in file_list: - #处理最新文件 - - if filename in ['/var/log/secure', '/var/log/auth.log', 'journalctl'] and search: - find_idx = self.get_curr_log_file(filename,search,min_num,max_num,log_list) - continue - - p = 0 #分页计数器 - next_file = False - sfile = filename - if filename[-3:] in ['.gz','.xz']: sfile = sfile[:-3] - - check_file,is_cache = self.__check_other_search(filename,search) - if check_file: - - cache_path = '{}/data/ssh/{}{}'.format(public.get_panel_path(),os.path.basename(sfile),check_file) - if not os.path.exists(cache_path): - self.__set_ssh_log(filename,check_file) - filename = cache_path - - #数据不够,则解压归档文件进行查询 - if filename[-3:] in ['.gz','.xz']: - public.ExecShell("gunzip -c " + filename + " > " + filename[:-3]) - filename = filename[:-3] - - while not next_file: - if not os.path.exists(filename): continue # 文件不存在? - if len(log_list) >= limit or os.path.getsize(filename) == 0: - break - p += 1 - - #public.print_log('读取文件:{},第{}页'.format(filename,p)) - #每次读取10000行,不足10000行跳转下个文件 - result = self.GetNumLines(filename,10001,p).split("\n") - if len(result) < 10000: - next_file = True - - result.reverse() - for _line in result: - if not _line.strip(): continue - - log_time = self.get_log_pre_time(filename,_line,log_time) - #处理搜索关键词 - is_search = False - if self.__find_line_str(_line,search): - is_search = True - - #读取数量超过最大值,跳出 - if len(log_list) >= limit: - break - - if is_search: - find_idx += 1 - if find_idx > min_num: - _line = public.xssencode2(_line.replace(' ',' ')) - # 解决SSH登录日志搜索ip或用户名不准确的情况 - if type(search) == list and len(search[0].split("&")) > 1: - rep_str = search[0].split("&") - rep = "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" - re_result = re.search(rep, _line).group() - if rep_str[1] in re_result: - log_list.append({'log': _line, 'time': log_time}) - elif rep_str[1] in user_list: - log_list.append({'log': _line, 'time': log_time}) - else: - log_list.append({'log':_line,'time':log_time}) - - return log_list - - def __set_ssh_log(self,filename,check_file): - """ - @name 缓存SSH登录日志 - @param filename 缓存文件 - @param check_file 文件类型 - """ - - cache_path = '{}/data/ssh/{}{}'.format(public.get_panel_path(),os.path.basename(filename),check_file) - - if check_file == '_success': - if filename == "journalctl": - shell = "journalctl -u ssh --no-pager|grep -a 'Accepted'" - else: - shell = "cat {}|grep -a 'Accepted'".format(filename) - elif check_file == '_error': - if filename == "journalctl": - shell = "journalctl -u ssh --no-pager|grep -E 'Failed password for|Connection closed by authenticating user' |grep -v 'invalid'" - else: - shell = "cat {}|grep -E 'Failed password for|Connection closed by authenticating user' |grep -v 'invalid'".format( - filename) - else: - if filename == "journalctl": - shell = "journalctl -u ssh --no-pager|grep -E 'Failed password for|Accepted|Connection closed by authenticating user' |grep -v 'invalid'" - else: - shell = "cat {}|grep -E 'Failed password for|Accepted|Connection closed by authenticating user' |grep -v 'invalid'".format(filename) - - if not os.path.exists(cache_path): - res = public.ExecShell(shell)[0] - public.writeFile(cache_path,res) - return True - - def __check_other_search(self,filename,search): - """ - @检测是否需要缓存ssh登录日志 - @filename 文件名 - @search 搜索关键字 - """ - if not search: return False,False - - if filename.find('secure-') >= 0 or filename.find('auth.log.') >= 0: - res = search - if type(search) == list: - res = ' '.join(search) - - is_cache = False - if res.find('&') == -1: is_cache = True - - if len(search) == 2: - return '_all',is_cache - if search[0].find('Accepted') >= 0: - return '_success',is_cache - return '_error',is_cache - return False,False - - def __find_line_str(self,__line,find_str): - """ - @ 批量搜索文件 - @ __line 文件行 - @ find_str 搜索关键字 - """ - if type(find_str) == list: - if len(find_str) == 0: - return True - for search in find_str: - if self.__find_str(__line,search.strip()): - return True - return False - else: - if find_str: - return self.__find_str(__line,find_str.strip()) - return True - - def __find_str(self,_line,find_str): - """ - @查找关键词 - @_line 文件行 - @find_str 搜索关键字 - """ - is_num = 0 - slist = find_str.split("&") - for search in slist: - if search == 'Failed password for': - #兼容多个系统的登录失败 - if _line.find(search) >= 0 and _line.find('invalid') == -1: - is_num += 1 - elif _line.find('Connection closed by authenticating user') >= 0 and _line.find('preauth') >= 0: #debian系统使用宝塔SSH终端登录失败 - is_num += 1 - else: - if _line.find(search) >= 0: - is_num += 1 - - if is_num == len(slist): - return True - return False - - - def get_log_title(self,log_name): - ''' - @name 获取日志标题 - @author hwliang<2021-09-03> - @param log_name 日志名称 - @return 日志标题 - ''' - log_name = log_name.replace('.1','') - if log_name in ['auth.log','secure'] or log_name.find('auth.') == 0: - return 'Authorization log' - if log_name in ['dmesg'] or log_name.find('dmesg') == 0: - return 'kernel buffer log' - if log_name in ['syslog'] or log_name.find('syslog') == 0: - return 'System warning/error log' - if log_name in ['btmp']: - return 'failed login record' - if log_name in ['utmp','wtmp']: - return 'Logon and restart records' - if log_name in ['lastlog']: - return 'User last logged in' - if log_name in ['yum.log']: - return 'yum package manager log' - if log_name in ['anaconda.log']: - return 'Anaconda log' - if log_name in ['dpkg.log']: - return 'dpkg package manager log' - if log_name in ['daemon.log']: - return 'System background daemon log' - if log_name in ['boot.log']: - return 'Boot oog' - if log_name in ['kern.log']: - return 'Kern log' - if log_name in ['maillog','mail.log']: - return 'Mail log' - if log_name.find('Xorg') == 0: - return 'Xorg log' - if log_name in ['cron.log']: - return 'Scheduled task log' - if log_name in ['alternatives.log']: - return 'Update alternate information' - if log_name in ['debug']: - return 'Debug log' - if log_name.find('apt') == 0: - return 'apt-get related logs' - if log_name.find('installer') == 0: - return 'System installation related logs' - if log_name in ['messages']: - return 'Comprehensive log' - return '{} log'.format(log_name.split('.')[0]) - - - def get_history_filename(self,filepath): - ''' - @name 获取归档文件名称 - @filepath 文件路径 - ''' - log_name = os.path.basename(filepath) - - #归档压缩文件,auth.log.1.gz - if filepath[-3:] in ['.gz','.xz']: - log_file = filepath[:-3] - if os.path.exists(log_file): - return False - log_name = os.path.basename(log_file) - - #处理auth.log-20221024 - if re.search('-(\d{8})',log_name): - arrs = log_name.split('-') - arrs = arrs[0 : len(arrs)-1] - return '-'.join(arrs) - #处理auth.log.1 - if re.search('.\d{1,10}$',log_name): - arrs = log_name.split('.') - arrs = arrs[0 : len(arrs)-1] - return '.'.join(arrs) - return log_name - - - - def get_sys_logfiles(self,get): - ''' - @name 获取系统日志文件列表 - @author hwliang<2021-09-02> - @param get - @return list - ''' - res = {} - log_dir = '/var/log' - for log_file in os.listdir(log_dir): - if log_file in ['.','..','faillog','fontconfig.log','unattended-upgrades','tallylog']: continue - - filename = os.path.join(log_dir,log_file) - if os.path.isfile(filename): - #归档文件原名 - log_name = self.get_history_filename(filename) - if not log_name: continue - - if not log_name in res: - filepath = os.path.join(log_dir,log_name) - if not os.path.exists(filepath): continue - - res[log_name] = { - 'name':log_name, - 'log_file':filepath, - 'size':os.path.getsize(filepath), - 'title': self.get_log_title(log_name), - 'uptime': os.path.getmtime(filepath), - 'list':[] - } - - if log_name != log_file: - res[log_name]['list'].append({ - 'name':log_file, - 'size':os.path.getsize(filename), - 'uptime': os.path.getmtime(filename), - 'log_file':filename - }) - else: - for next_name in os.listdir(filename): - next_file = os.path.join(filename,next_name) - if not os.path.isfile(next_file): continue - - log_name = self.get_history_filename(next_file) - if not log_name: continue - - if not log_name in res: - - filepath = os.path.join(filename,log_name) - if not os.path.exists(filepath): continue - - res[log_name] = { - 'name':log_name, - 'log_file':filepath, - 'size':os.path.getsize(filepath), - 'title': self.get_log_title(log_name), - 'uptime': os.path.getmtime(filepath), - 'list':[] - } - - if log_name != next_name: - res[log_name]['list'].append({ - 'name':next_name, - 'size':os.path.getsize(next_file), - 'uptime': os.path.getmtime(next_file), - 'log_file':next_file - }) - - - log_files = [] - for key in res: - res[key]['list'] = sorted(res[key]['list'],key=lambda x:x['name'],reverse=True) - log_files.append(res[key]) - log_files = sorted(log_files,key=lambda x:x['name'],reverse=True) - return log_files - - - def get_lastlog(self,get): - ''' - @name 获取lastlog日志 - @author hwliang<2021-09-02> - @param get - @return list - ''' - cmd = '''LANG=en_US.UTF-8 -lastlog|grep -v Username''' - result = public.ExecShell(cmd) - lastlog_list = [] - - p = 1 - count = 20 - if 'count' in get: count = int(get['count']) - if 'p' in get: p = int(get['p']) - - search = '' - if 'search' in get: - search = get.search - - idx = 0 - min,max = (p -1) * count, p * count - for _line in result[0].split("\n"): - if not _line: continue - if search and _line.find(search) == -1: continue - - _line = public.xssencode2(_line) - tmp = {} - sp_arr = _line.split() - tmp['User'] = sp_arr[0] - # tmp['_line'] = _line - if _line.find('Never logged in') != -1: - tmp['last login time'] = '0' - tmp['Last login source'] = '-' - tmp['Last login port'] = '-' - - else: - tmp['last login time'] = sp_arr[2] - tmp['Last login source'] = sp_arr[1] - tmp['Last login port'] = self.to_date2(' '.join(sp_arr[3:])) - - if idx >= min and idx < max: - lastlog_list.append(tmp) - - idx += 1 - lastlog_list = sorted(lastlog_list,key=lambda x:x['Last login port'],reverse=True) - for i in range(len(lastlog_list)): - if lastlog_list[i]['Last login port'] == '0': lastlog_list[i]['Last login port'] = 'never logged in' - return lastlog_list - - - def get_last(self,get): - ''' - @name 获取用户会话日志 - @author hwliang<2021-09-02> - @param get - @return list - ''' - cmd = '''LANG=en_US.UTF-8 -last -n 1000 -x -f {}|grep -v 127.0.0.1|grep -v " begins"'''.format(get.log_name) - result = public.ExecShell(cmd) - lastlog_list = [] - - search = '' - if 'search' in get: - search = get.search - - p = 1 - count = 20 - if 'count' in get: count = int(get['count']) - if 'p' in get: p = int(get['p']) - - idx = 0 - min,max = (p -1) * count, p * count - - for _line in result[0].split("\n"): - if not _line: continue - if search and _line.find(search) == -1: continue - - _line = public.xssencode2(_line) - tmp = {} - sp_arr = _line.split() - tmp['User'] = sp_arr[0] - if sp_arr[0] == 'runlevel': - tmp['Source'] = sp_arr[4] - tmp['Port'] = ' '.join(sp_arr[1:4]) - tmp['Time'] = self.to_date3(' '.join(sp_arr[5:])) + ' ' +' '.join(sp_arr[-2:]) - elif sp_arr[0] in ['reboot','shutdown']: - tmp['Source'] = sp_arr[3] - tmp['Port'] = ' '.join(sp_arr[1:3]) - if sp_arr[-3] == '-': - tmp['Time'] = self.to_date3(' '.join(sp_arr[4:])) + ' ' +' '.join(sp_arr[-3:]) - else: - tmp['Time'] = self.to_date3(' '.join(sp_arr[4:])) + ' ' +' '.join(sp_arr[-2:]) - elif sp_arr[1] in ['tty1','tty','tty2','tty3','hvc0','hvc1','hvc2'] or len(sp_arr) == 9: - tmp['Source'] = '' - tmp['Port'] = sp_arr[1] - tmp['Time'] = self.to_date3(' '.join(sp_arr[2:])) + ' ' +' '.join(sp_arr[-3:]) - else: - tmp['Source'] = sp_arr[2] - tmp['Port'] = sp_arr[1] - tmp['Time'] = self.to_date3(' '.join(sp_arr[3:])) + ' ' +' '.join(sp_arr[-3:]) - if idx >= min and idx < max: - lastlog_list.append(tmp) - idx += 1 - # lastlog_list = sorted(lastlog_list,key=lambda x:x['时间'],reverse=True) - return lastlog_list - - - - def __get_to_date(self,times): - """ - 日期转时间戳 - """ - try: - return int(time.mktime(time.strptime(times, "%Y-%m-%d %H:%M:%S"))) - except: - try: - return int(time.mktime(time.strptime(times, "%Y/%m/%d %H:%M:%S"))) - except: - return 0 - - - - def get_sys_log(self,get): - ''' - @name 获取指定系统日志 - @author hwliang<2021-09-02> - @param get - @return list - ''' - - log_file = get.log_name - - p,limit,search = 1,5,'' - if 'p' in get: p = int(get.p) - if 'limit' in get: limit = int(get.limit) - if 'search' in get: search = get.search - - sfile_name = os.path.basename(get.log_name) - if sfile_name in ['wtmp','btmp','utmp'] : - return self.get_last(get) - - if sfile_name in ['lastlog']: - return self.get_lastlog(get) - - if get.log_name.find('sa/sa') >= 0: - if get.log_name.find('sa/sar') == -1: - return public.xssencode2(public.ExecShell("sar -f /var/log/{}".format(get.log_name))[0]) - - is_string = True - - result = [] - min,max = (p-1) * limit , p * limit #最小值,最大值 - log_list = self.get_log_byfile(log_file,min,max,search) - - for info in log_list: - _line = info['log'] - if _line[:3] in self._months: - _tmps = _line.split(' ') - _msg = ' '.join(_tmps[3:]) - _tmp = _msg.split(": ") - _act = '' - if len(_tmp) > 1: - _act = _tmp[0] - _msg = _tmp[1] - else: - _msg = _tmp[0] - _line = { "Time": info['time'], "Role":_act, "Even":_msg } - is_string = False - elif _line[:2] in ['19','20','21','22']: - _msg = _line[19:] - _tmp = _msg.split(" ") - _act = _tmp[1] - _msg = ' '.join(_tmp[2:]) - _line = { "Time":info['time'], "Role":_act, "Even":_msg } - is_string = False - elif log_file.find('alternatives') >= 0: - _tmp = _line.split(": ") - _last = _tmp[0].split(" ") - _act = _last[0] - _msg = ' '.join(_tmp[1:]) - _line = { "Time":info['time'], "Role":_act, "Even":_msg } - is_string = False - else: - if not is_string: - if type(_line) != dict: continue - - result.append(_line) - - public.set_module_logs('sys_log','get_sys_log') - try: - _string = [] - _dict = [] - _list = [] - for _line in result: - if isinstance(_line,str): - _string.append(_line.strip()) - elif isinstance(_line,dict): - _dict.append(_line) - elif isinstance(_line,list): - _list.append(_line) - else: - continue - _str_len = len(_string) - _dict_len = len(_dict) - _list_len = len(_list) - if _str_len >= _dict_len + _list_len: - return _string - elif _dict_len >= _str_len + _list_len: - return _dict - else: - return _list - except: - return '\n'.join(result) - - - - #取文件指定尾行数 - def GetNumLines(self,path,num,p=1): - pyVersion = sys.version_info[0] - max_len = 1024*1024 * 2 - try: - from cgi import html - if not os.path.exists(path): return "" - start_line = (p - 1) * num - count = start_line + num - fp = open(path,'rb') - buf = "" - fp.seek(-1, 2) - if fp.read(1) == "\n": fp.seek(-1, 2) - data = [] - total_len = 0 - b = True - n = 0 - for i in range(count): - while True: - newline_pos = str.rfind(str(buf), "\n") - pos = fp.tell() - if newline_pos != -1: - if n >= start_line: - line = buf[newline_pos + 1:] - line_len = len(line) - total_len += line_len - sp_len = total_len - max_len - if sp_len > 0: - line = line[sp_len:] - try: - data.insert(0,line) - except: pass - buf = buf[:newline_pos] - n += 1 - break - else: - if pos == 0: - b = False - break - to_read = min(4096, pos) - fp.seek(-to_read, 1) - t_buf = fp.read(to_read) - if pyVersion == 3: - t_buf = t_buf.decode('utf-8') - - buf = t_buf + buf - fp.seek(-to_read, 1) - if pos - to_read == 0: - buf = "\n" + buf - if total_len >= max_len: break - if not b: break - fp.close() - result = "\n".join(data) - if not result: raise Exception('null') - except: - result = public.ExecShell("tail -n {} {}".format(num,path))[0] - if len(result) > max_len: - result = result[-max_len:] - - try: - try: - result = json.dumps(result) - return json.loads(result).strip() - except: - if pyVersion == 2: - result = result.decode('utf8',errors='ignore') - else: - result = result.encode('utf-8',errors='ignore').decode("utf-8",errors="ignore") - return result.strip() - except: return "" \ No newline at end of file +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +PEKPgJeDDCLnL9UcS39EYQ0oxS0YHncLtyklh46m5EBkNzGCoSotSCFeO4A9wJZj +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +izOKcEjLnmyxxrod80R5Ow== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +9/AEQy6sYem8wkQNCggJi4i9kWw0XQ381/dYG6HKfZo= +piG6BsMF31u4R4iA6R4SRA== +dTLQ8Wr3wPn7w6pte1B1YA== +wiYVtfO/yzajW1Tv5z7hyA== +bDrBrYFBEwTqHDQlSf5Wqw== +SbQQ5SqO9QrBwZ9ObpMYLw== +1u+XjG/2+GSQRv6EzCaWRQ== +XIfdJ79nObMM+vyAmKbTmw== +1w/Bux0eSLh4laruMV+kEu4fb+1yCwxQ0hOHwrLNXXRaWZyv1p2zipoavmFan6NZ +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +hbDorme8BqzUEmeXCAWmYa52umm3PBCcfSvATzTG02w= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +hxPPiT2HRNyGCUbYWjiGemMsho9zzoERNvAKutOOCEWVhC1sbjDeebh+SGn5u8GmkL2g41VpzeVyv9m/J//MuDXVnCmww1qdZncqaHpwyg8= +wgR07xfoapmx6eEnFHXXYlFDtUVt+yJ+tU8VHk3FJTvnJUFpUNld/t84QFPEYKTpJU156tin3Vc241jPdNebVm6HK/Ydm5jv2Mb3PtLSJC4= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0feE7ECax+/ZCUSs3gQ/3AaKzCTzDvnrH+EOl140p3Ejvkj/qRKNVgZY6MNTd6YR+5IenuNCFRCqo568dB1SxUvcHBcdz25PdNciWuU0T5T0+kclUUtQxfNacEShYTanibNFhUnMe4HHLWOUhn13GDCqcyIADTbNu8ZEuFklZzsT8l2mK3j3yk9iI88+Tz8U +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2UpJqD2VXFNDkddRfcdBjn5Xo11cfHVg4MHTU1ECnQkBR +Z8UsPk1Q7HtwjRd4g01ryw== +wa8O1mcGGxpEvPPRX3ikmvykeeKhIH9HZybe0SWYg6M= +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +wI4EEWzdSnynhqFxMCzFmo1bZxLdYghu7tPjNNa83Yw= +Z8UsPk1Q7HtwjRd4g01ryw== +SvL+08tQNi0qJOHjInKZY6RndgMQo6zUlLYIZu8iQNU261HNxXIEnmCJLHd/cAn682cyzkJMzokqlyn5ZLB0wQ== +GDxnwLueKeYNT0/dIp4TaA2EgKkJaFhgdpXqI89CP9JxudfN4YO5Bip/xZCOAyUl +89eRayX3G8tCX3FDU5IcjV0+/dsDUitdv1ScoFSstoWJGpFlcQ4x5TphzsFb2IQj +VmVrGQo2zRokW/ZuO9bN66izANLHguDX6h0ZMylPTZjSNwJjUmKceY0nGArlnnjq +F9Sftf1PoN5P+lgvc+r10NBQ12btoHUSbewJsWZIkLA4htiDy2/7V46D3DsQdHGR +VmVrGQo2zRokW/ZuO9bN61Go4AfJcv9+G+6Yze5rakTqOEVt8xxc04JEb0WvhXiiyOuFu/FvRpO928WIX0wTxw== +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +1u+XjG/2+GSQRv6EzCaWRQ== +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P3lRQWSpZzKNexozsWlQSmt +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidZcC3xryH5q1zGGaoBvvKTdC1AgWcdepqNrVhCT6tqt7k+JxPMwVGPnSqxhvLgv1DQhFmLlBc8XKdIY0AICqQQB +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmzHeTpF9D/4KEYPN1ABlSXLB+bxGaCCDvXrlgokvNEu+QTyZ0OkNbOjawGeCOhOAY= +1u+XjG/2+GSQRv6EzCaWRQ== +IyCWxP6fYDO4E5XteRNwiDXKEE7gzrOdM346GEQCZO8= +wlLHv6kT3Q/RmtMBN4nDAddbPv2l9qdO18ddV+SHYW6sapFy5qC4Sj9YrQq+XZS6 +VmVrGQo2zRokW/ZuO9bN64+cUfXVRogUENm4c6ZYTPLju4UDx+l86nzBCmwvVd0uwMYnjNJoL6js1zgp7gkYPENBFYuO7E5Nvxpmku1cGUU= +VmVrGQo2zRokW/ZuO9bN66z/+UOEROJ3qlfGXBlB86SWuPnq0AXG2phBzwXE7v1v +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN68lNxH5zpt5sybOcTXtNnpfItHRxcKV1GN9wgLL6Y/1R +1u+XjG/2+GSQRv6EzCaWRQ== ++kSw/JERkIrPRLyCm/D7AdGS0jaXYQYSNkJ6PDaS5kKRDw+QnaOx8ss81acdnppHhvRGi5fhyW54Z5gPLzuFGbO8BuY6dL1EqHs8DpgsB0c= +96orka/uERLyRst14azQwkEOOUsX1Ai0XqkbdTxnOingAid639l5vKz82fyzWZRhvWwi6CRkoGDeVwcGakosUg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2UpCUU06JrEEfNymvLzRwUVgF/R4iaOcaI2S3O3N7OAwl +Z8UsPk1Q7HtwjRd4g01ryw== +wa8O1mcGGxpEvPPRX3ikmjl4u4bTOurjxSyetFOSLpQ= +5Ch5Cy/OpWh+1WaF8Su/eDwl93O5klNCtuxXaiypyoA= +wI4EEWzdSnynhqFxMCzFmo1bZxLdYghu7tPjNNa83Yw= +Z8UsPk1Q7HtwjRd4g01ryw== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P1DHXJ/TJdbmPH2ckSraWgG +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidZcC3xryH5q1zGGaoBvvKTdC1AgWcdepqNrVhCT6tqt7pCgU8fcpxsvRgulBE1NZu9SSmus2oz4HzvvEcXgYZbN/5tlDq8RdAY5WQkwLWPTq/dVzZBv6z2B8FhigMKfcMnFomnMj1iyhyb10pjIliz/ +1u+XjG/2+GSQRv6EzCaWRQ== +esnxVrQTJckUcXi8Vt8zdyY4i7Esn3+rv1ghcUNo4q0= +NhnIR3Ilo4H2su9/cTNo/L0HJQSj7xDirJmInN4ogA7N4utqCGavJw/zWl3BqVBOcu0yh4310yAJbcS5M5y7KLT8b6U9cXXn+DOi/El+juU= +1V2v8QerKOmubvSxgB4eTBzsiAfEZawGIv/PJDVZk8DLw/veVTQK3qL0m1VX1XdQ +o6QhOIN2Sc4SHELnst17ubkalL7sSCEY8nh1Aau3kAESZfNThdx/u6aA+dfb26kB +1u+XjG/2+GSQRv6EzCaWRQ== ++kSw/JERkIrPRLyCm/D7AdGS0jaXYQYSNkJ6PDaS5kKRDw+QnaOx8ss81acdnppHhvRGi5fhyW54Z5gPLzuFGbO8BuY6dL1EqHs8DpgsB0c= +96orka/uERLyRst14azQwkEOOUsX1Ai0XqkbdTxnOingAid639l5vKz82fyzWZRhvWwi6CRkoGDeVwcGakosUg== +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2UtYsmXWasld7EcVPoh9dGjGt+2Xjb/ezeoPiUvh51Scc +Z8UsPk1Q7HtwjRd4g01ryw== +wa8O1mcGGxpEvPPRX3ikmj1BsUFLbf8jUkWlQ6+15/GeyFemD4bVxaphwo/ntI75 +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +wI4EEWzdSnynhqFxMCzFmo1bZxLdYghu7tPjNNa83Yw= +Z8UsPk1Q7HtwjRd4g01ryw== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcOuWMW7q96BiVbhOlHj8k0LS+GLTgLVoUNjFkoOOd6Q/ +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidZcC3xryH5q1zGGaoBvvKTdC1AgWcdepqNrVhCT6tqt7pCgU8fcpxsvRgulBE1NZu9SSmus2oz4HzvvEcXgYZbNsAyiUdVbe7Hjmg9ET03bHboNn3w+ITWBFzpEUndWW90= +1u+XjG/2+GSQRv6EzCaWRQ== +esnxVrQTJckUcXi8Vt8zdyY4i7Esn3+rv1ghcUNo4q0= +NhnIR3Ilo4H2su9/cTNo/L0HJQSj7xDirJmInN4ogA7N4utqCGavJw/zWl3BqVBOcu0yh4310yAJbcS5M5y7KLT8b6U9cXXn+DOi/El+juU= +1V2v8QerKOmubvSxgB4eTCJqrBg4vZgNAdzlsRAbXfGCflRUO5Z4sXw8H0VTfzWe +1u+XjG/2+GSQRv6EzCaWRQ== +o6QhOIN2Sc4SHELnst17ubkalL7sSCEY8nh1Aau3kAESZfNThdx/u6aA+dfb26kB ++kSw/JERkIrPRLyCm/D7AdGS0jaXYQYSNkJ6PDaS5kKRDw+QnaOx8ss81acdnppHhvRGi5fhyW54Z5gPLzuFGbO8BuY6dL1EqHs8DpgsB0c= +96orka/uERLyRst14azQwkEOOUsX1Ai0XqkbdTxnOingAid639l5vKz82fyzWZRhvWwi6CRkoGDeVwcGakosUg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +gn9R9ypc7EQr3UZGxM7R2AzpBmjpt//gk3VbIVrmsWPUgmmJziYMAJ3SBavXHHpg +Z8UsPk1Q7HtwjRd4g01ryw== +IafCwgGxn6pY+tyyf7k+k1uPwgoyKLoGy1T2IHFPUrs= +lGY+HGJ/DlLufZHWWlj7U8JUjESN387GevZrf0/dwpoTGJutrsojcsEElOQwuKhR +ZCo7BKJgBzBoR5QyWRMODvuiXuF1YyjhIRa499f9oeDFOwCSNimfzEv6xwOiYENc +Z8UsPk1Q7HtwjRd4g01ryw== +NYUPEpAMYcAgMU1lywXh+APRTYQ7bT0FxrwWNK9r+kE= +X/Lp4PsbbXulceSXUvGUpAixZi3IzpQX5iLz2HxFdWwS5A14Kie1TLFHz4QUExEMrjBXCKSQ2mS07fFxKBoIRQ== +s2s/1ED0ebJ2NmFlE8kk05gdRKkjvH+bphLoBZkO6vMkQmee56NmkDMz3ewSmOSg +wlLHv6kT3Q/RmtMBN4nDASYMmJRC8P+40dq2mnWeVhc= +VmVrGQo2zRokW/ZuO9bN6+vO0T5LxpdIZ6hmr5+BXMwvx1h4H3tu9h3Lw+gtPHVT +ACEEdgL/kNbx7RaZcSRxZKy0zW5JN3zc9G0K673Kt8E= +VmVrGQo2zRokW/ZuO9bN6+CU0Uzm+BLGwlKn+xqu8O0= +L0eUthVnpkGsmKFAX6d+uOW5BVuM+7tcrCiEwvU64kY= +CAhn8dRUItEbEErp4w+lX3cOtVXg8rs8OR96pWoJZvY= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2Uq12VTZnVAgLNUPRVpboscSkdq/rWesDEOFnnwfIdOM2 +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY8qDyry3eeYEnSUnFHqFOEdqLpNRCL3M5LaBhSQtDro7 +7JQQDTi0T2idhFjao4jEpWyyzmLHEngKhI6rkPYC+OEQ3v3507uiPdl6O4aO+4ER +7JQQDTi0T2idhFjao4jEpV/go6iO1qzsLRmLYXsC2awnSWcIsvPYVwnYvfMjZaDtow5+WSfBeFo+pvL7hARzXDxRko1EzJaJ+fi1oy8D1/8= +KZTmaJLp+FU9X93j5Tpqtw== +CRgPeWAvMGMS2vfLmB+XqAPNkWJygDEUlCyJRk0QVvtGXcHrIivyKjsAViAs3OCtr/wdXA9sspltDH7P0695hw== +/R3t0fIiNxFa1+EM4KdcC/Tq9YNQxX2rnc682C9+fe8= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +gmymfXJ+E4e4NAtQtupPJnNetLAHixU3GutHyzvm5FjbdeuSmaUynxh9ANB6mVzZ +/2yBdrTJDE3TFtcSLCcx/9sJSd4kIdV7hBv4DW/k5kQjLCSiOqb/ooMNKbVZJjgekfdghInCCdI6qQz/jIgZgA== +NhnIR3Ilo4H2su9/cTNo/FhhB6MZ5HlL8ilxek9dnzUbA0zrRzxd/m81iyHyj8Jn +NhnIR3Ilo4H2su9/cTNo/KlYN/bsxuWzFF+nWhu3hBR2BWGqsiqwtBlTpURXFcpL +NhnIR3Ilo4H2su9/cTNo/G1hu91GucoyUk5CAa7Se62ja+Iodh3ArUEpNw9/IIap +l2FJPs4YkAmmok1ulDRuSA== +NhnIR3Ilo4H2su9/cTNo/FhhB6MZ5HlL8ilxek9dnzWFsroGkpAbYhhbP16I8llP +NhnIR3Ilo4H2su9/cTNo/KlYN/bsxuWzFF+nWhu3hBQYlkX5gl9hxYgaKXgpyNbH +NhnIR3Ilo4H2su9/cTNo/G1hu91GucoyUk5CAa7Se62jqdhNgRiCY2VhbppcAkgq +1u+XjG/2+GSQRv6EzCaWRQ== +eDDKQ1PxEOXD40WdPkjAt+4gIw5LFJ2JffPLp1i0RM0= +/2yBdrTJDE3TFtcSLCcx/7i0c5WFgpYA4Y1T5vIUHVO5oUJ3yHw6al3zQOD6XGLm +NhnIR3Ilo4H2su9/cTNo/B0KkXyoVKsrbyorgJnjAt4= ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +0feE7ECax+/ZCUSs3gQ/3AaKzCTzDvnrH+EOl140p3Ejvkj/qRKNVgZY6MNTd6YRirlilOXAKg9O2fXCfHGfBtMDPeLErFs86v0ZeSLWbiUlxSp8Q6JZIdSDtsOukEmI9CkgD9cr1YKi5zNr/8OAB6nq4n5Nx0RnnUUavr/JMa8eGXsXCu3G6OAO8lGrXUZm +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +ISoywpaLHuQBxWIUflU2WE6goDl6H+SMTlvAvhjEOunmRWoDI71YFjPGTq2PREVfgI/0kDXgAM1iC59/IdE0zUqUlwg/visqR/ji4CUCNls= +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P2neUS+qyt4fxBbg7aS5U8ZVSEGgWJcksomV0iOXfr3S +wOl7lRII4ZgqRTj9M//0b1qhFBAHPMvcKJHX8xF2dd3LFwNZYH3pF0t/6DGxSkRF +yqM179SzW9HnBHozkU+IlZLJm7366WyNnx7ZMsu9exUEQG5/9gzkoWdK3lm2KNSi +NjU4zQ5DRyOHitK6CHLOgNNxRKGkyV3kGqu2T3oe3wk= +NjU4zQ5DRyOHitK6CHLOgMJ4vze7G/BcNjyu30dz0dI= +bIts5UhOLbX+5vrkUi2fEhpPnL7BGuZkarYaOytlgoinj9jnlLh+lL3x0ZV5fo0E +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTafshnKFviV+idW9zO4AP3io= +1P+dKgYYLCQ5O0NZwxSOaJcUBjFbQdzF9CNcdagyAI18a21hAHVeicWVY9WgSU2K +zrSEP+cwIY0LGAkn1FqokI6RwnSJpjctW3UvricDZLxItXuL73USA+6pRr2HvpJaC7RiR4yb1PJQkbGlH0YRWQ== +1u+XjG/2+GSQRv6EzCaWRQ== ++xLLwGkVmjlAlAN2wYY0FOnVwRrXqNlRBemygIVo5xo= +p0E//cFR8EOf13yDhOlObMD3Yuxj4bTt6EnfF43vdnLk7mp9E28enjNVoyEj00PVBw63PNtJHh7nYjCP57FGPqozYy2TzONryzbzwkoUMNwvUkxvlVRrGo2rxAnE/g0V4Cmmkw+dKL0nhwQooy8bnVmuDHamveEqMpiknfK8NBA= +p0E//cFR8EOf13yDhOlObP3FQjpN4YYsA4YJPXooDzrxOF7DYxRlXeKipAKSaevnHWB9P8oJq6upWDy/MUOvLA2G18HPNK28orPv/JxpaCU= +p0E//cFR8EOf13yDhOlObJwu0OCz0PycRy7pO+A7qycPBfJfS4wcckld5TKXEJcQ8LTGY/BgOJ8/Ey+ycvnkaPNCF8x8ePPtPFV33M0BgGR33MbsHGevwAuVMKg7zRUDIXwgrTQUFIdujw+lXXU2GmtNOEJjkhHjhR2dXDsYQl+qxS0SKUpVIYzDr1eRI12GUQFYJwR76xewQvSowM7l2Q== +p0E//cFR8EOf13yDhOlObNQNgN99cJOreDX6FzaHhxv3V4pef0BAT3QaZNKMIUivldoeOBbHNkFVO4BMgqzu4fDHYcR1BKL6eBdwt3OBu1kVdmgx3s4fTZ8LAlEPq2BRzuN4F8ZeuA1ppvWiDhACcEHyX6Pz6yKUiqxmQn0qung= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw58tqRCidnUtuhiV/SQek1vZ6sNmdinwpH5+NOEy1mNl8yusqwsRmAnJlzr3FKfnstUDfM7vtqOWitJUQd0uNl0= +tmi7c6vWl6N6sdrwujX3GReSPhJ5ssuXUZtdOTkPGXc= +4E0YNxQKZtqIoiEqkAJApA== +fE3zmgbfhO9ZwEda5GgwDzwSc+DUx0Ss08sG6egCcJP+XUG+DzlYynYbT/JVkU7P +EsliW4ECGA8kkEJ0BUSIKZ+6kvYYxaoa29sn6RE+30Q= +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUqoJYM2k3wpGIGNfe4ZVylRY2CmgGV6o3LCAp54cduWSIwMrRMTIErqM/4y21/qV2s2Bxs/+ycPzzZdeNzsz9/q4sZS2NAVCp5rDAV2WNLaY= +VmVrGQo2zRokW/ZuO9bN6xCL2jO48J3n3RHpsxsFW0A= +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUtD4Dy7F2C5KfRvcNVwIaIQRcd7Fy2ZXHLWGZJPl8MX9RBwYra8rs4CAKfeGMN3Bb +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUgC86euXJOlFyHZyqDAOLUAdeaHFM4IYtg18Uequ7wafHVvZue+VX8ISJjUr1sqwFRVRgsWgqIDu5NviNkV3pauDs1a5D7tn5bIpmJcr1KCm6cN6DLgXTjktT05vWK1srWo2mj1pylV4R46zpVCmFGA== +VmVrGQo2zRokW/ZuO9bN66Op6JyhixbDicezrxsJdmvmw/JvaaHtlFkTsLFyz3qi +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUv3HBNQdTfHHC76HdkzkeKhcn8l+7nqNJm8fOH+30rrfwQzm8hP4An/vuWNJxgVfdBK/7rxGOROS+FKukjRDAA7v4+O6TFSTWLNyfZEFdSHAACyn19p4G4SaBK+kNp7Y4 +VmVrGQo2zRokW/ZuO9bN63hl7oGUPd8q+ErnISkgMco= +VmVrGQo2zRokW/ZuO9bN6xCL2jO48J3n3RHpsxsFW0A= +4E0YNxQKZtqIoiEqkAJApA== +AaV0Q13NpbJz1j/27jY/ubg6CfL/9D/rk9oHG6vv7Tk= +J1UGlXkKhKXD36OrXXhN8PbGS4CCkC/QcSnejunC4s1HNXfKIR7pGHCoZHkqc2al9dG3/LRVuqxK1/lHGulhpw== +VmVrGQo2zRokW/ZuO9bN64ywnuovLuNhCiDoP2wQAWzHwT2J7ZgEwYI2DHVR0db7 +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN64ywnuovLuNhCiDoP2wQAWxQoOqVjvwf024i/wGnySdC +l2FJPs4YkAmmok1ulDRuSA== +EsliW4ECGA8kkEJ0BUSIKVKU5W04dIhCWYbq1NBglGIbd3FwxA+IXISbsRb5KU8x +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +6xO+ubEwkQTp/D6ATXPZaR9RFvPZlxoFFcMoTEBWzdY= +1u+XjG/2+GSQRv6EzCaWRQ== +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGjZZ/Q9S9r5t3lPlsYorgw68Y18oFZuov4IXxgtwIU+fyOxgNLXV9uwsqyK3UQIuC0= +T0lwjDK5GymcJe/LbHEB+5LTXLLonGWNuuTQfjCTPqg= +wlLHv6kT3Q/RmtMBN4nDAfGIYwX5xDRj7uztmlOephI= +VmVrGQo2zRokW/ZuO9bN615pZCvbchosnTxjAGDSTuu5F/3dP6AEc2sI/MY5xwN3 +1u+XjG/2+GSQRv6EzCaWRQ== +YnP/gQVEpWES+RQ7J7RJRlUivABjbdmquJjPlRdBH7s= +Wd7OXFQxn2HMHBqE448Pi6S8+/GBA34d3QZMmVea2Tk= +p6i8iK7HDbvmLpoFrhNyvxqRSTuyLOZi037vACDsfa8= +esnxVrQTJckUcXi8Vt8zdytTNOUxGpVlE+9gz0v5AKo= +topgAunkM89n84cH9D9pHIJ0dN6Gk7b/C+bsk/3fJRquhc7fUjMhZYvLbsfG2DMWzaSd6qLjQSoUeAO/UtXhP1Yel7kB7XwKvSI9CIFJqS8= +ACEEdgL/kNbx7RaZcSRxZPvmvyPIJl0tNCG2z5oQFSMF3Rtz2Zl3Uf+ip6mRNkxm +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +1u+XjG/2+GSQRv6EzCaWRQ== +J1UGlXkKhKXD36OrXXhN8DolGRp6NkzBMOOoPdzZSROBDCr+gph8JmqTmJ+BwzEcAyCmQfvb6v5GI6hFw38sPQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+M80RvQb5VsoyLkSgRiqcM= +VmVrGQo2zRokW/ZuO9bN61RJ7HPvuDQyY/7jnqZlstvr14lD1Nsc1GBMciUP56h8 +VmVrGQo2zRokW/ZuO9bN67mWWDHj8CSfv92pyS9sLERjFtkVPnpdPLZSP2NPs1gXeeQZjNsawPLfcnlKBwAakNgFVuo2Yd5N4wsDtWM07aE= +VmVrGQo2zRokW/ZuO9bN68V03MBz2tWTRGBIaN8QcnWa3+54ie/uRDPpsaNfYNUhfJWkoMWEjN141ojxjPQz7iUbeWa1gJ4fwE2A4QfkFyY= +1u+XjG/2+GSQRv6EzCaWRQ== +5wsVwKLrIjIWqeTNpSVp9QDHrlhrdh8VDOVcdAVCwxo= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +QdTjTxbShl+8mrARISGoAew+M6wRwESTJ4GivNwwWTIyDaIjNgge4ZpF0WBvDJqZRgXN2RgjWkE0y98VXQbEpQ== +Z8UsPk1Q7HtwjRd4g01ryw== +EauAFvhEfQAlaCCYJmuW5eV4DJOW7GNwqk+bbKrKpSaH1MvmX7D3DvPHyH3oySvOnRXE5AlMLgwUDhfDZANse1idoOMd3rJKanSLH7Uc123RRwtjA5eu4NckUBCieqKNHkfIlUn5nFjMA1ICSzTCQsv5pY+TV/1yO9BG5IofZXg= +eeFiPlXUI0Fos82HYztSmaNe9in2SzXpHGr5hBhAinnNHyxMDiBuvbazGbrnCMky +bIts5UhOLbX+5vrkUi2fEhOK0qYcUWJPnPtVafbmigUdzXiHWAONlKw9KDOwOlPM +akHibliG6j9uvBEg96vRelg6b5pjOK0azc0J/Ev8D6EPHt5rKichbRoGlTycmehx+Th3o7fyS7lnrCT4ZXS1gA== +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +NmkgFF4ZsyEHu9KVT53La1lz7XNNET0A9zdhd9gFT9fBSLnem4K6iQOWTy9x8XwI +topgAunkM89n84cH9D9pHITAncWu+e82byWKRCcayzEuvdX6re3IAygjiK0dtvwH5pfePRD6LD7p9ALfCp41GA== +1u+XjG/2+GSQRv6EzCaWRQ== +NmkgFF4ZsyEHu9KVT53La1eD1XgGKKkTpXLEHn6AsToySvZkHOsdmdS0PUJccZzo +sVk6QS4Pf3ntiMkl4LqRRdNSXsQEDaTnzm0yHVvLcNydLD7JFUCuBLvtEtInfM9cLHKhemuIRU5S45GYI+zKfw== +1u+XjG/2+GSQRv6EzCaWRQ== +JkdoXgqzqMYpkKh82pPju9o4/urRwxg/lFTkhxBEG5hgOJ5vGO/GPYvjcLXE248o1JQGij+4m8UEtIcbHbGSRRVhEZVih+RN/xjwceaGFpM= +mgV9MrvUK0fssdmv3ApVy7k8mJdR/B+7eG8ccjV2Ab788JJLQjLAWgAvu87PYMIxrNY3ii6KXngsif22LUSmMDbdoMq8P1ubKINGjlYCA7gTuIGpBMZsNYzFMa5NWPLopAgMwMESku+TbkFwChLHaw== +K9fxAkAAgWE70Il8AJdd4rdBcU0my9+uNHCVuDkF28NZN3ATTy62g9PIqSFIMzetO7VcoZ3+2dLF7JWJz2PwmrKOhy7hLolj7vwIWSqWgW1E/24aK4BeKO/DZ6lIwCW2E0Ly26J/TZrEe4nSAsvnhCF2/Sb4A2swT7NQC9kADyg= +L0eUthVnpkGsmKFAX6d+uCiYQic7T+3704A5xRjtHHV7D1K7sA7PU5Lx63H+wuEdqWGBURjxs/fUqKDjPX8aHg== +NmkgFF4ZsyEHu9KVT53La25kspERjj3Kd1WtR5F3DjUeyufiu9OPGHQHCZOJDZd/ +L0eUthVnpkGsmKFAX6d+uCiYQic7T+3704A5xRjtHHULvPeJ8pPVUmfUVgaJGNCIp4sjsG53ijiz9EipY86UOQ== +lGY+HGJ/DlLufZHWWlj7U/wgW3T0y9TOw/q89uQqE+c= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawFxAQnjo2U7M7PxwwYRzZjsaB1ggdhAibLBZPcfdEwOx2KWulak8d6g87sjhm9UYeQ== +Z8UsPk1Q7HtwjRd4g01ryw== +DzneBA92dU5V88ETMYFQ57uX0+Soy2svl62wHi59Ele/p/Fr3qr6/65PhVKxF6Ph +bIts5UhOLbX+5vrkUi2fEuV05t7kiBXke4QQt4fGEIi1pyM37lJMRHRDxQmK3gOZ +Tn3rOfE+s6NRy9yVv10cExsGK3PlpvQ/21Omt6hqVOjP06IQhzjsmDO6W8MidErE +eeFiPlXUI0Fos82HYztSmXW23eSyH2bXlOYpYhyb1RKLhaGx2IoCJa24JWsHrcMAg8yR7zX1oWHc14AqlOrK0g== +9BuKX7c+cBUFGH/xEQqKwAu6MbJNm8+b4qD0aAxmgDU= +Z8UsPk1Q7HtwjRd4g01ryw== +Wd7OXFQxn2HMHBqE448Pi6S8+/GBA34d3QZMmVea2Tk= +LxCLgek/xo8kXqFx9hyUVJPL3TFbh8u2ikhOGSAV0rg85G9Y5wYNCPljvLZlShFM +topgAunkM89n84cH9D9pHIV4dJ5fXtVEdOnz8/oeQYqujphF6DcnuFFxsQW+tGqMts90IYJNxLKB0izTwoEhvQ== +MzoJ1HANqAion0JXNkOKLkUXCYWzU8bckEHdk0zTAVCxXpXc/N2/2C4s2HOzit+j8Cu9wnFj9h55D4ASfhbhaA== +topgAunkM89n84cH9D9pHIjK3O9J9W+DroJme0vc0dxLsI4ZMqq+7wxYZZro3x0X +T8ztCulMhN50eAMwonrN+mnIYb5fDs3vnEBct26rKaEcORGfRo9zgegCK5TefOmJQqk4fO89rFCV3QP2Wzahfg== +bo0XlOt+6S78LT3IsBdT1dEroBAtDg8pr1Pybq8zhQJAu/VoI80oqogQomMUUlPM +v9uxr4HcPPk5UqMUuJoEoB7nY2X+tzb9PZyPToR8I6vD2vPuybQkfI0RMQ5SpdHZ +topgAunkM89n84cH9D9pHLhIfpf4IEOslMiXh31h17WTVnRZP8H/e9ND9t9DHG3EOcgqF1/s/qPfl2fGKEz3rA== +1u+XjG/2+GSQRv6EzCaWRQ== +Wd7OXFQxn2HMHBqE448Pi+XpPmaTTyeqpJxLnY2yRin/EZIgzqJ3CViWAhoEpU6erObfPycKdvcVekGvCE4Fyw== +lGY+HGJ/DlLufZHWWlj7U/wgW3T0y9TOw/q89uQqE+c= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +xzdWmbiM4p5/aGFV8YqP4XLLyrbPzLln0rwZDoXcnKc= +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P4+OZYX1oZ6IDO9Bb8L9jOTF/Bt2J4quQTw6RN85Bumk +Ys4bozvJBgtB7q16qcvtB0RluR7RwJXaqe7bJfwI9hU= +KZTmaJLp+FU9X93j5Tpqtw== +eW+3TRHX+r8wYGmvO/rbmQKRB+qqZDGER9PE45Hh6DhKWFPeFSKD/CBlJ+rZhJXE/6NzadTbUrA2WD4xQ/NtZg== +eW+3TRHX+r8wYGmvO/rbmQA+K0RVUGWoC1b+gP2mkFaXSKIZdVNEI2dyM5Nc324r +m00MPwDBB9sb6xb6db18s1+VrxrV3/MYAhac4NSUZUk= +4lm0ybc3aVxLaNQODc9aBcT0vlFE00Wx+6lONISyLb4= +iUEDlyYF71b358pIL4FKLxKByAvC7nSDmBVkTcIWC+9RPGSBJZRzJ2JFx9aDM6YT +zxz/Z9yjFalbpeH0OEk06wb+y3G26TwFK0BePbfiFxswJa+xbCabv6iKd6mKX8sM +zrSvdYautDwgfoprAQtW7Mh3DupVdGrOOgUnOp6J1n0= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawHo0YqPGy4jvJqzaJIsqw8aht/a5La2JHCiBs7MPYkJNnkttfrNlz3AZpwB+QDSF06CacZvErF4UHWUUepAZDW4= +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P5s4ZWJ5nCZ67hdzagwPZfnK+vAzOOBUPTQOeTz/eFRP +yqM179SzW9HnBHozkU+IlS7E9Jat0KMy3qoE1n5uiMAJ7/ZYgCvtzu/Qi+/1mAim +NjU4zQ5DRyOHitK6CHLOgMz13P9khDg6jBdTbmByKXOddMHKhILqXmmCVMC8sS3X +NjU4zQ5DRyOHitK6CHLOgKqZU0dP/fC3Nw6diKT/uI3N4y0eHljA4CIE4JRl3a/I +yqM179SzW9HnBHozkU+IlZ+sLT0JkWbe6QF8HumAIuHDqd+ZqJq4nJMu+r1QY06/ +Z8UsPk1Q7HtwjRd4g01ryw== +XEKtPwhadR+kpKQN/+UTaW/KEaZ8xQsb0J4R4+wy+qw= +F3HmfyKnf+xR8iqi6kYc0jeDpsR33ZKtlDiexNRZYLA= +3hCm6yDF8QSQ/yEbAtwtdmUoSfAgsymzLlnjfiQPPgQUC7w3DY9Wc7Bn+2+99jPC +6P8mKfouOK1hwXy3MrrCzRBPdWeF7NFSQT8rGlPCjGB/+XWHqqTbqSpG6ZavymMALgzEpY4QOg7MZRCal+ePJw== +1D18KWr2hdVsBcdZx1OaPddn9pCR79IrJK89K1q817H5A2Tia2JPGCrrHgOkp8RV +VmVrGQo2zRokW/ZuO9bN67r54/S5K8MNluBiVrjXDNs= +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +MzOj4ZiBOJDNVP8vi/lFQDc/aaaueRS943aPBbBVTruOJ0ScKqVF1VjeXVd3nD3y +Znf5RlJ6zVewwmUeSxfIGsO2yacXhB2H+eI6Ejvle2oa4C44s8dSYzlT06bYrJ7jLO6BjHZXLU70mL78XNfc/MgsennnOw8awpTQyz9lgKi9M4NOYpOpkfu9y2pjpDXxj1+2/9JZ5FGVbAjvUWVbI+MnYHew7WdlkltXdIcw7ds= +c0jePRxtTVZYop75Q5JCFsHjToEqjqqXfvyUun7oxD4= +L0eUthVnpkGsmKFAX6d+uHvX7jXsjikZBMo/rKrjiBw= +1u+XjG/2+GSQRv6EzCaWRQ== +RpR4QysGv9QPcAuUlmZB/ldqYJzcdfQBNZ1Th5SM6QO+POEY2DGleYbCe5l6UfNu +yqOVJCkAGOhqcQYBOo7yqQoTxFeI3v/YH5mpkidrW9HyNTAJRD02Ymur9pCntQEY +6P8mKfouOK1hwXy3MrrCzYZqDmfnLEVSCjSn5KIdRmc4dkXAA/1uvzDl3l1PUWtt +PRG/YRzXVD8iVR3bzEcUnllZ+cz9udLBY+J74ckAM+cZ2hSzyyfq/FybzwKXxMpO +1u+XjG/2+GSQRv6EzCaWRQ== +YnP/gQVEpWES+RQ7J7RJRlUivABjbdmquJjPlRdBH7s= +Wd7OXFQxn2HMHBqE448Pi6S8+/GBA34d3QZMmVea2Tk= +p6i8iK7HDbvmLpoFrhNyv9D601DxXdSOTjmgiyd3Nqr1byx7HtjSclWDwuzUoQV7 +m00MPwDBB9sb6xb6db18swVJnbf7H4WMvnIczhPQZ+0aVjp7owaSvIW84fWof7uc +1u+XjG/2+GSQRv6EzCaWRQ== +m5EI3+BXxkQEYKYaxORD+g/7lNafasyk405EhIyuvnNqv6n1h2jC986RGfvXjJkv +50HSHdrYmraajnW93vvabzwR6Glv3j7i9vFV1sPcZ7KgQtnqa8GX9KUqxMUlXQ3m +1u+XjG/2+GSQRv6EzCaWRQ== +gZ7BoIAABc+I8g/rYzq+f81Dacxy6eezCrTC1ekZwPEW9OSpvgIrYa0KGCOwwYXcTIWHKwChc+OzuGx8c8CYxqz7F329o74YYNCcaYwSM9V4eKMtsG8h6eORGPf5+1XV +VmVrGQo2zRokW/ZuO9bN66Yu9w4eCdqZ8p4k0NS0g/mr6t4fHQqW9//3beDBilCZmROWIZ1UbGT0bwXRWX+ibj7PTXWgbRivwGWChIIkZpWmZxWQE8b/GcSOusQIDbAt +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1u+XjG/2+GSQRv6EzCaWRQ== +iUEDlyYF71b358pIL4FKLzQOhNOG5YF9nS6QdrhVDB90W++YzeWFyqsFYbjeCsHl +3puL9F6TU32sbZzfbgCef8z8OvYCxvmTlQzeetx7rGA= +HiVf4YuHR4gw4rFUUeRvKw8qvbDmX+tfMbpiIaNfsno= +gZ7BoIAABc+I8g/rYzq+f+9HHgvBGOxBt37ysBPwI13uPk52FI6uvFjvWgHf7Jko6NUiSbwIYvUdo44a+xTir+OvHDqhK3py8D4bbLHhU7E= +1u+XjG/2+GSQRv6EzCaWRQ== +uWQGv+LShBH8u8S26pv5nPnM8ff4ZGji1vcSAAFUnfWxWXseAysF38MM+4hJ5dUZiFZbZax0zk7JjR3dSb08FV21UNIPkNYIE3W2R6DJCOc= +ltIZHHUgft++nMUXBsBki6T1wXBeAb/L2jS/24mdBRE= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69uRnj8/r8BaXEMGSBfrjESJouhp3OTlYGDThMnU5TB6K0SkOsqpVqbZDNGFz5szCw/AgnLXsS7/vstntyyZyJXLi5GL2bMqOI9UfzQHPSFIzK98I+cwwynqIM0yyc5o2IXjcbGmabsIRO4LkX3gb0k= +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvOMI4vw48TaUq4aRLYrmsXaJb+h7/y2WxDdspRfKEXnQw== +VmVrGQo2zRokW/ZuO9bN6/jFu99KJ+MquaMXJxls+pzVuGNN+Sl79RauAvg76B1wsGLU4dxJTOlUT9dMZ1w1ug== +VmVrGQo2zRokW/ZuO9bN60SggNWucpg/S6RxW0/YnaA1skwCA0ZpXLeYREWmU+BS +1u+XjG/2+GSQRv6EzCaWRQ== +c3KN//OVINDLfTVXAO/Lbk2Hd5TU1RVmBdDgvw8QHupIMpn754UzwYg885IkGmJJnOM8kM3rDIg7hTLxdJmlTw== +gZ7BoIAABc+I8g/rYzq+f+9HHgvBGOxBt37ysBPwI135+k2vlF9wrhYiCZmHN8IE +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjYKmRtsBhPWippExvZOW0/LJEaf8OI42VUCkcmkYiDNBmW4Tnaa50/2shkMsiFA/ejpSEFIqAkBQ53j0BA08zWZ +VmVrGQo2zRokW/ZuO9bN69z/Qlonskd1/GLdnc2k+VvhZQyzqI/fgAtkLK8iM1Hn +1u+XjG/2+GSQRv6EzCaWRQ== +jGJZZqYXcDE83UImRHGU4lffa63XK0BuEvGrV/OrPdOAgULF7Tuf5t89bW0q2hMA +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvOvKbZ3tEv+5Q+mTyYmHi9v32uKyu6CDih1wbEzENWW8Q== +VmVrGQo2zRokW/ZuO9bN63K0zvLacibwYj8m7wsfeqyyOyT4etluzfxFxaI5NIERaBBVwUw/TC8TfLKh0did/A== +VmVrGQo2zRokW/ZuO9bN62BySiMpDH9w/XNtJREDLzDV1N7g8dL9ueDFUmh6GeUDDFVqtUWhLZ39rABIECio6GXmpVIpQAKy8dcZ2qCvhpg= +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +VmVrGQo2zRokW/ZuO9bN6+g5JNrv8rEbMLqUNj28Cdo= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69C6A2K67LgD1eJvP3N6AWwUURoCkGGIg7SORUpL6Seh8a2ykGijyqDWzo86U586lwaKy2+VvF67QGk3R/UXPiXiVzWtIisu9t+GqCz0r6aF +VmVrGQo2zRokW/ZuO9bN6xigJm8qa4WpsQUsklfpWpWMUosdOYpkkLOs84FK2jeGjVjPCXRS43orE0WYC4dCfwQQxhdDG3yLsvZlftXvV0o= +VmVrGQo2zRokW/ZuO9bN63a0R/S59cuVV4xXEauJnWRz3xaZ2HgPpWq/a72oFYZVDKhEoXsn2iHq60nHoc8QreeySx+sbJT56yXV0YT5mB8= +VmVrGQo2zRokW/ZuO9bN6z6oXkNlIbiWTEz21chkjli+NoZjhxN9ORJwLCGQEnpn +VmVrGQo2zRokW/ZuO9bN62SJgGjT/WmMIIxZ/ruMwWWisTQI+r0CB+ZPSVt0T7SS +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6/x9mVEQ39E9CmRqJ+FlnL9EqemW9t23C9OdbGoCQ8cM +VmVrGQo2zRokW/ZuO9bN64w6dpups4cXvLDqooOrnJyvr6Av+x6T4G801ox+KJHC +VmVrGQo2zRokW/ZuO9bN60tueB5yS1mXpf65ThW2BRcr02wDxH+Ue+yMjEpkVNPJk/hP4oWqVTQE7JZlCIIrmA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69Hh4k/KN5p4WHNMxzu2tx2JIw6LXMR5NzQxKIVS864yY10isQvmwGVQ2DlSTPoctMbfzw5uVWHhvpOBeLt9pKI= +VmVrGQo2zRokW/ZuO9bN6wJmwpIiDUqk/W5Xvci/WyF4XF2UWEAYoz09NC+em7kI +VmVrGQo2zRokW/ZuO9bN6+LmY8Wp+WHHBK/N/eg16Q15+pIZBGb6sScWJhvIma2k +VmVrGQo2zRokW/ZuO9bN62L/LKqacP9lunLs/sZVgAVfljLX6fxmEJSvrO4zXddcQ4NElU+FCAcEPT2EZ880Cw== +VmVrGQo2zRokW/ZuO9bN66YYIRANoQmJRNKXv2xg3pCaJdGg4CQVst48mAlOjitX +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN631Ij+DuOT66h6R4m3bVN3TwDZnJGvuPIa0DLVLfOxX/JTIYUqYRpbQ52ElbP7VAkg== +VmVrGQo2zRokW/ZuO9bN66PmE0xfRAboNx2TeC9P7J4NhzSV+m0EkNzefjR+mpgN +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6y49h0FPe0mXa/MRz9rhxplV7doJhsM1E7TfB0kuuQSh +VmVrGQo2zRokW/ZuO9bN6xwhOaVQWDpfnMF6LA0XCFstMJg50MMXLnnkQUEa/wNn +VmVrGQo2zRokW/ZuO9bN68sDl/nq5E1fo4CRxhlKiT6ewilZZCn5nRtj5+srI5B2 +VmVrGQo2zRokW/ZuO9bN6/TV8HYrXfBS0+5RislOy68JgSfzi3n359kKXZzzkBVKkRF/lY4XEsYndx7+YjTP0jz72pF5WNcrFfW3cbITPNg= +VmVrGQo2zRokW/ZuO9bN619JyKoLhHxa0i2TABYxWfIzBj4xXUmyUuFPMVlHNfolzXARazZQnnKzxz8Qgx2TFd/VDzNwQ8p8VUgiV3DidQAni8mtUG7OUvdH9rqfLaPy +VmVrGQo2zRokW/ZuO9bN6x5O5549z42ZbPZdzwy2tzg5ykzCAsDBEFgAKnqr/bsWy+wofZoh6groBJA7PTdK/0btaorXdA/sR+o+7A1E5hLNkjWbWomUfcFC39spL93x +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkllVUyblmrBXx4KTxlxVkh4bcW2se2uqmrpRH3UsBE2hQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklScjVEbWucpHFf4jnfOHB52z9dx42c56L6XC4wp2g2ruIrt2feNZhOUsfn6mmpqAU= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklOzDQU2UmHOWsnNbrLjDmizGDPlPu+SoalSQx/sN2EQIT/3N3nh5LNd2fZegmgZDo= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklS6OX30XitSf1zWXDoSBdzuvFrWI1gEiyE0eWnthSNpA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkngkTGQ8tITagcno1If0T633KRDqrO43O4ckFcdiv4j57M7qt2UPUTmNG9R+ArOTMhYo2cTmli5bm6RYaKnxaZE +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkyr3u3tmRRdTjbXFN3bPPu9wRFkGc8MqoonARkL67arQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkngkTGQ8tITagcno1If0T633KRDqrO43O4ckFcdiv4j57M7qt2UPUTmNG9R+ArOTMhYo2cTmli5bm6RYaKnxaZE +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklEcvH4r4MfmtfJUGH+dpI5pxEL8+TV2kZl/s7yEkRSBkV6R2mDWz6QJPXIhWnq/4s= +1u+XjG/2+GSQRv6EzCaWRQ== +lGY+HGJ/DlLufZHWWlj7U0N/DF45+V15HT5Nu+KAqWE= +X/N4cXLQiqE5H/UsPg7N6H39QsvvRJt1vu7ZPXzjPbs= +WHkOzVx7seuLmxs5Hu+l/mrLDQ6BBXRseSNCn19UXP3v5lC2O2w45xLgjCqNwLEY +EiP/K054tbB05pkitbcV38EFfoKmw9zT8E8ybvzd9nORsP53TL7HxHzZVMv+d/I4CDSAjMP4VR6rzvujLxO+Zg== +EiP/K054tbB05pkitbcV3wfMjZ/GVvL48TFa+UH0AR0= +8/sla5spc9gZfiuUDEb2iHAhv7KgE/gh3O5zT4rZATU= +yTSwdlYSxlm840GOKG0jBNReEOgHRyBZMZj7qfJDVM8= +wlLHv6kT3Q/RmtMBN4nDAa1kCyeDPbBUXj1x4murSfY= +VmVrGQo2zRokW/ZuO9bN67tOtPUCVHilnHLpGSFH8/lE0zEZ1of6jPE4GNF1YsreLF88QhiYDrDpqY0GhYtYZg== +VmVrGQo2zRokW/ZuO9bN6wws8n1WvlPG4f5+ctB/4Wkw73gLVVi46HbD4YepM87BK9AtBo3X96VBGJabZEJGcWnaGrOIgSVP+1G9XJuOItw4Z3CBoIUDDL57ry88UL0Y +VmVrGQo2zRokW/ZuO9bN67tOtPUCVHilnHLpGSFH8/kVcZoulWP34aHJD6jlKFoM +VmVrGQo2zRokW/ZuO9bN62iPo1XY0vYrVL4PRMJ5H6bWKRzMN2ZPY8rDUIAMybuc +VmVrGQo2zRokW/ZuO9bN6zE8q/SdlXIvI0x0m8La72coiNnSoO6pKX5F0hOobfV+bRuzyAeOWi4FR3XbqMUOYA== +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN65sE0dfavTgZrT8VHJw1CbtVGdXJNjwPzVktduaNysJ/ +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +oJQSCA4LmnTse1ofrS7jTxSxJllCPFpdE5ycy6XFstzmvMPRParED6juI7yImDCRQ2mIYnHQP22rwfersmNpHiTQorKYISnL1hv5Snt1uxMyhP/Dkw3FjUFnb3ozCCH5ti6v+RsOhn/keCLsS+Rsvg== +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +aH8osMxZgIfRkNShGiaQ8KeostDT+ZPsWsXljTu4fBJd1fM2igANyxWLADUf8Q/PaQu53yzgmI+X5rE72QX5HA== +Z8UsPk1Q7HtwjRd4g01ryw== +8+pJybctMsfvqVW7hafi21j617S4ZJBcnVSvovNJf2fofEHOzMbs09NxooOPyRza +wOl7lRII4ZgqRTj9M//0b3Na+XUNXsGthFHJZn+iwk9DLm8ehZxPVZsuck2aDMbz +FFgrrvXvjrcz4wZcb8tZVBfFxJJtUNj66FBgYGeUBuNe1QYojsfdMMTfmUWbDp4P +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +0yVj5tN/frSXacyX2PgyeT5E+HbZCBESO3Xe8mtOrLxt4iljR9cXdbATNOKhHq7wMxj1yRwJ7OaljkHeJlnzK3LEcWydZBw++GRPav2xpkPrnDffgdhIqRhLBkni7ydMmI2eX/TuJRps99MKfJtUAA== +1u+XjG/2+GSQRv6EzCaWRQ== +PWLW/WTodw3tqQQSBBHInw4g49GUi/PQoXrJnMOKMcv3Wp6VztQYqiLzaEcDdgSs +gZ7BoIAABc+I8g/rYzq+f/E+IDZ6NsXsUDbHValdfzb/Kruq/Vjk28atdn7C+Siu +VmVrGQo2zRokW/ZuO9bN6/8yGXI8x1DvWcxmMbVGl2/d+UnadBjumrSvrXqCFvxL/dQ1IF3GPQDGm1Za9My9dqpvvZxqoUl6zH2mT03wugk= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66QEbsrD511ragqq/ntAN9ip4Cl7MQ5ucFmM7oBrE7CwtFofMzsbhBUBYHWjnO4+Gm4RaVlUCL2nZMQ4ZlUMHk4= +gwLNi9yZl3Cgb1vmhIukcDiwvybm4po7xCREzBIXUUWMhzq1uZtLMFENJQeoM6bf +gZ7BoIAABc+I8g/rYzq+f/E+IDZ6NsXsUDbHValdfzb/Kruq/Vjk28atdn7C+Siu +VmVrGQo2zRokW/ZuO9bN6/8yGXI8x1DvWcxmMbVGl2/d+UnadBjumrSvrXqCFvxL05RBhHMkSHCThcMnvcrtBL755t4Y28qLxX95H9SOw5MIMfwwm+AX7jFrLSlPFL7z/LG23Z7Qi0ZBkt5yi3J065WfKpBp7ljnujnyRI5KxS3WLULIWKJfkqhy13iwfEXTWU5aH1Cdj3ES1NC50RTweA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66QEbsrD511ragqq/ntAN9gn6I8xS/ec2ORcznjf8NbtWmOguhtVlMEA8/tuxJYlc+EcZYOpOz/OOJi7ykbx5GuqFl+UVDS0tpV5JPDnbAvDoPyg5AP415q+Nhiu9uo2uVlZvJcu7WWtFPCoNTx+1lGiW3wClWt7AZYCeza9v3va +VmVrGQo2zRokW/ZuO9bN63oVhYTQuQYZZogmVe/TzMw= +l2FJPs4YkAmmok1ulDRuSA== +gZ7BoIAABc+I8g/rYzq+f/E+IDZ6NsXsUDbHValdfzb/Kruq/Vjk28atdn7C+Siu +VmVrGQo2zRokW/ZuO9bN6/8yGXI8x1DvWcxmMbVGl2/d+UnadBjumrSvrXqCFvxL05RBhHMkSHCThcMnvcrtBL755t4Y28qLxX95H9SOw5NBHzoK2EBm81zr5nBdZfIqY9xIQsE1T6+1rIBc9RtxWrAgPHnUSyFwCGVIYz6lZga1UtCe6E+ND5HnA71abf4+8Mx/rNnpalgInue9MBDnlQ== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66QEbsrD511ragqq/ntAN9gn6I8xS/ec2ORcznjf8Nbtv6k9SWAza517BnGnZCkv2xN0WdZ4ZLw0aPWE+30+q6fOg28LzTGXOUhu+Ve6HvJaj4cxoQ4JYcacpAzNMdlTCWLZM+Uptv/BbJhem6UisCnkHFC8acpX/dMFcmgzOKuIaIG9LX5bT7btboBMh7V/7w== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYmirComgoMouw9fWv+9wQLbQn3IxZ0+fHgSwwrVEz19D +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGiaE95pK7CRTqvzzc1f82PP +xWoGNWjKGPfI4gq8aHoTfMo8UWPcZxcLStbRn3vuZvrvRuO8gwYogoCldXZi6VGk +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhOcA9ux5Auf73oocslB7sDeWu0fDEoiafZo2yMYtO6chT1HFJerygYO/wT5DIOkSKA== +Z8UsPk1Q7HtwjRd4g01ryw== +JLWwEPnWK0L+CzhaEIjbQU4GwWA558QpsjOq+5nz4DgxhiCXwzz0jGRGKxh8dUTODJr+s6+Ah0R9doWb6zPLQQ== +L5bpcfZyn5Pc75WQ8ineZfusv0+lxTTZjtC4b06AF3zKUPd2R/NTwAVMY8WVnZ7X +EhfxP26BarXubxBeyusn3NfNOtb3HPBGFTHMHZMXSqvtCtihhBnDLGI6VsAxQd4V +Z8UsPk1Q7HtwjRd4g01ryw== +uTEK8Ng11d3ix2pA+DD/aXy+DXgD07P/dsANZ49KBIoQ9DDyAcW8u95okz+vDaVb +1u+XjG/2+GSQRv6EzCaWRQ== +fE3zmgbfhO9ZwEda5GgwD/RsUOXfY9pzpW9xRZl2fW0PbW4wjRnTUOggbHQB//acwA0Dv9aDYg0PNWy2YecNQfNF05hJabP1q+/C4qoAENQ= +lOh2GtzHjjMM8E9J40AuOZ7zZZM0vaCjhH3uRcP/NrM= +pVMCrZ7PAAHymZ70WROm/67KCrp/ppOHMaNTwq4h7pocNe5jswE5ov4hxkNlgfBC +VmVrGQo2zRokW/ZuO9bN63ZBl+ZLQVwzJ8aZTH8bazNsMnfZ+bm+E1rvhx3AfI2H +1u+XjG/2+GSQRv6EzCaWRQ== +CmFznH7ArXzhNsFBIGOkr5Vsg/ycHXuLeeSShlENgnA= +UbJuac0dxLiN+5ocS3w2vfHOkOj7T66O79wy+D+TgtoNfPdzdywGX8tpg28oiCYnv83pW/YxuQwN1apN//aLdw== +1u+XjG/2+GSQRv6EzCaWRQ== +ACEEdgL/kNbx7RaZcSRxZENcLifoZmLdDLAaOT7aBW7RjTwy4Q7KIvPTg8m/9qBC +VmVrGQo2zRokW/ZuO9bN62YaWBnXKkSoB0qO9pLfxSSPe4WYW+NQbuQnofC6bhb9 +J1UGlXkKhKXD36OrXXhN8PbGS4CCkC/QcSnejunC4s1HNXfKIR7pGHCoZHkqc2al9dG3/LRVuqxK1/lHGulhpw== +VmVrGQo2zRokW/ZuO9bN6/d2CLxxt41IFX+A1fbH83na68t62fa3cjMWGKFnkaMR +L0eUthVnpkGsmKFAX6d+uPmf6IqlOWYOMRLeTkcrllRN4/sFJgvvL5z1UEeHE8+W +VMfVrQporTLzVAs+KagENgnYKzmIrs4+BBVOAKxaFac= +1u+XjG/2+GSQRv6EzCaWRQ== +rKj1u6Uot58r2z9vVGpkYPs6jYzSiEQNQ0Vlqh8zH1eUMBI5lpqMlLhl/4lO3nQu +Z8UsPk1Q7HtwjRd4g01ryw== +ewqdfdhwKtr+fTAdDLdXxNoKltdD5CW97QCrf+yOioE= +Sc8e9jvbHiy5hwiy0HtAoOvGcehWBaW/6Q54bzic4ooH1vds49RieexSGCVDRfXT +1B9Eo1v10Ho41P86Qsxs+Rf3h6kOJxgKbqyJXd2SQDCZaqaZocZ40EQpaNjgDGb2 +Z8UsPk1Q7HtwjRd4g01ryw== +NmkgFF4ZsyEHu9KVT53La24afMe7Hpz+AvRb0pPLT5Y+DtSbSZQ9wXQJ9PdlRpbh +ACEEdgL/kNbx7RaZcSRxZDbs+G2PAEacg2XavjihjHkGDr+yC4TGqKna/D6MU25t +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +wlLHv6kT3Q/RmtMBN4nDAcHzCoHA7fx8ge824KyD6j2RGkLqKD0SoqmJdGVHetKh +VmVrGQo2zRokW/ZuO9bN6za89G+hm2DHEo1o7nC89kWHww92mvNp+cT0E6zNNzmXwifWbhsAF7H7xfRYH1Tcsg== +VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +l2FJPs4YkAmmok1ulDRuSA== +gZ7BoIAABc+I8g/rYzq+f+vaPyTBeRc/jgIGrjGoV1I= +VmVrGQo2zRokW/ZuO9bN64Vu/H9alPnu3iLUUUBoSK+kyhoBM510sPwyNCX8Yr+ofuNO9OIf8TJMlD7EUz7RELcHLIPBGPTTiECymRVomBM= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +vF0ZftWrAGfdsoFfHqmHDJuEsO4R/FX2ga77ewUw7Y11IGYw63nwjmoZNzNj628o +Z8UsPk1Q7HtwjRd4g01ryw== +GlXLieqbP3oJrHHHl1oqk5GfuHjv+M1uY5kQiwNKOz0= +cCPEDb5F9Qd4JMGAsR7UfZfXQMkZOQ9q6AipKLgN9qA= +wzhSc3VGUoAQ+07oznSZPLPT5vU/alKRgCd2xATHoIdgjG3VPCXSpthiTEqckgdX +Z8UsPk1Q7HtwjRd4g01ryw== +rLwQ5yQ9bNuG44la3oQ+3afvJa6cgkEkDaPubWhiBa8= +2/eGB4mNeTC2Lw/I1/B9jHReQG3Sf6fhE0IH64g/YfeE/Ws6fbXaJX/LLa/JASMj +9L3jIB734YKv4S9AqEY35Kh1jnwS0FJtLaDJkqWU7Zs= +J1UGlXkKhKXD36OrXXhN8A/6KQQIEyEAY+cUx85urfskVuM/oTrnEBFsI3zXMuwQtyvGFxwsnrTAlyx/0hytcw== +VmVrGQo2zRokW/ZuO9bN68dKk9G97rhJCB3VEv0S3qjWifbCFeCN1sSryrl3vtTYdLbCLZieWWRVdoajo7IOpA== +VmVrGQo2zRokW/ZuO9bN69r44+V6JJX/2NLSGlXYr1YD313HJ7qeRebm2/AdMhL11t96l/ZN3bscCjY1669x54LQrad8RoESQEFGNJ7tr8I= +VmVrGQo2zRokW/ZuO9bN62MvivPt6ucjDY6Y4UwXjRGX+MgrdU7vOEwe6NO1KSXK +VmVrGQo2zRokW/ZuO9bN60Sd7dFtgik0J339YqGp+LqOWzrYRhuwK3n/DwPxquLs+N36KFtUHhWKI9VFqVraYlnKaqKXuvwu19LED1GmG2Kkna5Ve0z1QySEyTbnYwpj3mq3A5LUCr6VTgwESp9FYrceQzV/js0BIa16dwTapHE06PsyAeRntvguLENP7jvaYu2A79wYGT81tc7XklvAEdronju9kPx+7a+fzFUwV5U= +VmVrGQo2zRokW/ZuO9bN62MvivPt6ucjDY6Y4UwXjRGX+MgrdU7vOEwe6NO1KSXK +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN69r44+V6JJX/2NLSGlXYr1Z5XOqt9InPZIx/uA7fZePS +VmVrGQo2zRokW/ZuO9bN62MvivPt6ucjDY6Y4UwXjRGX+MgrdU7vOEwe6NO1KSXK +1u+XjG/2+GSQRv6EzCaWRQ== +sVwPh1y7MInnwEDK61kL2ZgurqcOtElG452IbPDSARaEvm4o7F6Vljqg3s4Vb98N +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawEfi7RDXlvnIiPUmGf6XyPurYR5IRGH6Jhoi1u8dhC6C +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY3e5Z5xEtbMc4JrlUL6rftaZUmLPHCH16nEnJiDRycKT +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQG9CJ/mCl9wdy9aVT0qKaa8 +7JQQDTi0T2idhFjao4jEpdluONpwOUUVh7y/o1Lzsm4a2FL3SufObmrWF3NyQ11G7uUUREDf5s1H4nVGAyEIiQ== +h2Amc56dmaRo3inGZxN9N0Mh3da6J2tf8CU4p/n8Ca2A7uHSizxBYlbR4VDvVrH8 +KZTmaJLp+FU9X93j5Tpqtw== +W08jtqcK2gqkconJfg+WHLJlYDaZnYglU+gPmeaeJz0dXPQc4GmpJ18lTWeF8Jvc +IsslvnYVqGkrqD0qU5QGYf5J/O0JjEzsRDI+7c68kAPbhQeof0lnZloVKmPJ0/OPwERg1AgDmZ5jUeNQ6jfaldScBwiDIsOdWtI9+ChcDxI= +L0eUthVnpkGsmKFAX6d+uK14dMm1+GhG8c2gpiVyR+E1Z2/jkCwp6Os25PRP8RSk +IsslvnYVqGkrqD0qU5QGYbCFSj6VHiK8jGEp4Z7SGzCVaeuBljBtpL3ckAqxImzQ1d5MvnFkYYWoGsfPJ/WPv8swi2RH+jUjIGOKkl3usaE= +L0eUthVnpkGsmKFAX6d+uH51QgM5kWHywxiE6Ty0F6BCQoJXs5zaJJqYGuXoD81B +IsslvnYVqGkrqD0qU5QGYcjXjwdbef/ruiHWGoRx3+tWAsJVYvcBpbkV5AJPt5MOcPZrXMgXPIOVr93qjXogTtDc9vraJd/N0pYe5+Z4eNM= +L0eUthVnpkGsmKFAX6d+uM+i6uIzzgNIsrvcJ0xQsTE06WRnqMHo4Q1g/bkpbg6d +IsslvnYVqGkrqD0qU5QGYbP084sfOENgLhN/XMGReZg0zyyRimxRdtJtSm6Sqdya +L0eUthVnpkGsmKFAX6d+uDQtcaiDMT/if2pHlZtAF6w7EuP6p/BouaL69QDJbMlX +IsslvnYVqGkrqD0qU5QGYbrfmZ+lxyGmJzabndV+bycc7U0Wl5SH5mS4H3CFQFF5 +L0eUthVnpkGsmKFAX6d+uEpYxmDfNJv/38PnR3IuvUzG7TueShUPZjSOSr1mkV90 +IsslvnYVqGkrqD0qU5QGYdNGxot71haXjS99OqNqqQ074sGxXHCxg0T7gUwpV061 +L0eUthVnpkGsmKFAX6d+uBEBFHibxFuwHX5Xun9xifvXFUxIe1hqpBHRh1Lw72g9 +IsslvnYVqGkrqD0qU5QGYacx+GIk+O8p/nDetEHOBxNO8G66dVdD3XUsN1D4L+DX +L0eUthVnpkGsmKFAX6d+uDKFjFlH66scjoKkr0HS3PwzqDQpIUplT5Z0WdNifCR8 +IsslvnYVqGkrqD0qU5QGYTZbaTDAr7WzW3U05y7JjCJE+JWxxlyG4/oGHZQRzxpM +L0eUthVnpkGsmKFAX6d+uKR3GJL+5DYQD9ef2JvARGDOf7TUitZF3uJDvhf4SXd6 +IsslvnYVqGkrqD0qU5QGYT4PypAZWVuTa3yx/tngA8BLwrQemYJ/HPN/7QEqxYRG +L0eUthVnpkGsmKFAX6d+uOhvQuK51ItTe89peTAEMXOQ5smjp/DG2mf1guds/omG +IsslvnYVqGkrqD0qU5QGYRnGdaaoC1M1g7ZAadH1EZcM7x8K1ujx/VKQzwSiV3gl +L0eUthVnpkGsmKFAX6d+uJ+E0oqu/x++zQJcSBL0VOekQHmZVACJJPl8TTRzVI8m9M3y+IGr3/7RTyRMpjpcig== +IsslvnYVqGkrqD0qU5QGYZTJr0cGLieJcZu1a1iY/RUTgK39OfOzDT+/AdeI2wCL +L0eUthVnpkGsmKFAX6d+uH07G4h7QHuhkW15eHMk0eA= +IsslvnYVqGkrqD0qU5QGYQn8o/Js3mP8t+fBdcR0TgAwd00zhld6rOJZQ6+yFxbe +L0eUthVnpkGsmKFAX6d+uIXRRpClQCuJqr/OY3HUOQs= +IsslvnYVqGkrqD0qU5QGYQheScjm8xnwil+c9CUIBAIfDVr4aEwxf02axHs55dTj +L0eUthVnpkGsmKFAX6d+uAX3W7tCeHuM0iu6taGk0Ik= +IsslvnYVqGkrqD0qU5QGYUyoMIjJLmAF5ojMLgMpyaE41ZKkM8Gjl3APdZx0Hj59 +L0eUthVnpkGsmKFAX6d+uEmIBdMpCCkqkvpD1pJK/Fc= +IsslvnYVqGkrqD0qU5QGYRNYwVLAovvSjrixyYk8aT36EarzpP9AgYTB5zNgeLWC +L0eUthVnpkGsmKFAX6d+uJfb3o2MxlDhM0IFL0qo2QevpUkPYKMcpvvjGL4e+KUJ +IsslvnYVqGkrqD0qU5QGYXSqTc4qTS7hPO4iPJv++5lyNhEn9/t3xBtXLeIofwMp +L0eUthVnpkGsmKFAX6d+uLOTVM8QXQomM/ScmIi0Aurqqbx0I74fDjhRNAJFYEPJ3pYwtdrIJSDHigUgbAk2dg== +IsslvnYVqGkrqD0qU5QGYXG6NlsXd/YqSaoeZGsOge0O+VwQiRXmNu6YtlNYN82K +L0eUthVnpkGsmKFAX6d+uK/VpHdm7KMAlOrmSjRBOrc= +IsslvnYVqGkrqD0qU5QGYU5Lk4PmA/HmYwjmHWabZ6hHV55xwbkRmwNx/z168Oti +L0eUthVnpkGsmKFAX6d+uHf7H9Z69Fy6EtLuPiv9GmjWSmDXIF8makwzDG3As2Xu +IsslvnYVqGkrqD0qU5QGYZszVUnULbYAsg6SI1v40Ie3ai1tISljWyUaHEuTyTeq +L0eUthVnpkGsmKFAX6d+uOMCsU8cjJLqb/iyzhRPI/ckwr99ZMBbGMnD7MK89CzJiEYNWtqeqQk216KxbOgGvQ== +IsslvnYVqGkrqD0qU5QGYQAmKpTYece2KpTt3d1NBCNYX8Ap/8fRHVUzkhz0ko4H +L0eUthVnpkGsmKFAX6d+uG8+ZBh4qlZRJvEzt5lBDV+PtTrj7mpKSjT2XGDtk0Vh +4hdB7RJambPsQ0dtPl5R2akq0BODLVZotsDNh2wSunpksKRZQGda2QfFL3yC7r9FNUGWj4cTVasPp4knqjntXg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +21/52BX1a38vl6/d4QTVP0hoUi6PwJEXFQ6wphCzPr6fXDo3Z5Vg+a7TcMA5dHOO +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P/pyukZf+uIGpurCH+0HOPz4Yr/Z3jIpQQmOXWHeoYrG +ZtsV8vaHEXutlQxVeZ819plsRMEMfUI0BG6ieJYZQ0SZ5n31ETTM1BuyD7ejcndH +KZTmaJLp+FU9X93j5Tpqtw== +W08jtqcK2gqkconJfg+WHMWCpDQbDVBXW3mpkNRn3lIXAExnTItYo1kq5iRG4GcR +1u+XjG/2+GSQRv6EzCaWRQ== +CTVkyTnZxhWMnQdl2TPfNtZypvunYlImqx0NqMHaPXRBRXlCxO+cXNezSDuvkCTx +TADZGw0D6PLYGUHKmgeZL4HNEC8FTcIShYtTIXIxyo+0tdoxkJP4HbuHSul3u35j +topgAunkM89n84cH9D9pHAfyyGx8htIfqZCXE8ucjJj/OUSO2V7ozSUs83ljlvy2 +O3CUgrw2GJfB+mDjH5+NdkkEd9P97hGK1RTeRfwhysCroE1sFZzj1cl2Wj01k4MM +VmVrGQo2zRokW/ZuO9bN65x96E+pC4TupOHurj34PBY= +topgAunkM89n84cH9D9pHKjRNdYyRqpYiqgVsH37hnqzQCcFnmPRP7x+3/JqeKZ+haxhWTIMsEjUYMkHZbb60w== +1u+XjG/2+GSQRv6EzCaWRQ== +YcIT2RppFBANuzyiUKo/XbqbC0hnpSADZ2Xd7Kwfuo91ygsOr2hFjcoPoo/9iE1B +mEOKzB0uJs35kZrdrKxIkCclaJRcU34BXh5nvgXkUmhXqzXNaToiOYCIt8Xptl1d +W/Rdcl/m9EziNRa9XlvfZH5kAFMc6PQ4eF7Rsh/gbQLJ9bIjLmL0JAuq36SAsgMj +W/Rdcl/m9EziNRa9XlvfZCdb6qvEMYvH5wnxbbWCd1+DR0Cf3qqNnJRCqdPpXwVK +L0eUthVnpkGsmKFAX6d+uMzHH+Rw5vYjEEqS9kb+ft733Qn8E+MZcbFtFKTonvDe +YcIT2RppFBANuzyiUKo/XfLqIAjoBYCaWlb87AUyhxA= +mEOKzB0uJs35kZrdrKxIkAwLXKX1c/aY0fGJeKgMKfhPtxAt6BH1o9t+1mdRbz8z +W/Rdcl/m9EziNRa9XlvfZH5kAFMc6PQ4eF7Rsh/gbQKgUj6yNAchV8t2eLfKtDtG +W/Rdcl/m9EziNRa9XlvfZCdb6qvEMYvH5wnxbbWCd1+DR0Cf3qqNnJRCqdPpXwVK +L0eUthVnpkGsmKFAX6d+uPK1shPIFD6x9kjjo2oaS3fd8Xa6knSu/TMUZumMZllv +lGY+HGJ/DlLufZHWWlj7U5YwItAjxeXXjqfky42+km8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +QdTjTxbShl+8mrARISGoAcUhgZRi1ym8p5oZAX1ZOiFX408x5lL2opLCXVnt/vA2 +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY933z5nYV5e5Vw9OGrr8WfRmBaVCVOfV3FtWbmWGq0nhK9Hbx3/RcbihQUwUjoECJw== +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +QeR/vJltSrUa36sdgLk/IfL4q9jp7Kh5fPIA7nHg0hc= +f1uTQJeAsYOuzXSplbSkIbPpvJXETldnXXS37p1dj3s= +h9U6jl4cZ4C22C1wpKu8ve1yUcQ+dIn1vMmQcAZ0Z0w= +MzOj4ZiBOJDNVP8vi/lFQDc/aaaueRS943aPBbBVTruOJ0ScKqVF1VjeXVd3nD3y +ynmt6S+38NUiCOOHWGlzR4HdkOhFQMEHtI5N9m9UmAHvhYD65+zL3Xa+TZcF+A7JkuvH7bgBenSEp3R5N7mEEVopWYSftAoMBIzCFFUM8wBcyO+Zn8uwyrC/eV4OLSQ1eHRk79nfwEq8G5g7LP9amaF8mveR7Yn2dtZqMe9UGBmE1kgEX+yal6xBpHRIT4lOwwPGunRAoGoHVO+jKBIPUW8TSA+jTQ+0yT0rehF06w8= +lfyVGovXk7iJwBH9N9PEdVdmkroBj+ONqDvmQCUdy6k= +VmVrGQo2zRokW/ZuO9bN69sw02l8wweyC+FH5l+wLv+Ee7lQUMZkk6JxPCn2KC+R +VmVrGQo2zRokW/ZuO9bN606ykrEkLL7rkIYoLIEGRKa/IO3yIPucaHCAbBRsYEvA +VmVrGQo2zRokW/ZuO9bN65fvpP+bM+ZO3sDwcWPsPc3jVHDm/dlBMFRHAuHLqEE8 +VmVrGQo2zRokW/ZuO9bN66iijmlBDGu/7d75ZWU/kWG35wZGP6R0A94kBvpc+n0R +YH231WTDnzQG3bFilBiqIg== +wlLHv6kT3Q/RmtMBN4nDAT6Yus/X/bPaDoTTmgVYHsPCgsnUT+b8w2rTsMRKLl+L +VmVrGQo2zRokW/ZuO9bN66EqyN/XX98hHc/stlewQpHbm+gnf5+GoPEYTHk/wm4E +VmVrGQo2zRokW/ZuO9bN648PngVnKTtx+x3sa9gCvVU= +VmVrGQo2zRokW/ZuO9bN66ld9vu6DiCgxopF+eL/NaAa8rqG7ls2TJquJKvUWI/k +VmVrGQo2zRokW/ZuO9bN6xO985T9pwQ7sN4pEaMuKffCIPEXjYOQ0lTzWJ5AJvHO5YrWawAA1po8HVlyajFBHA== +VmVrGQo2zRokW/ZuO9bN6/iiJ6ywWPFmc7rDzl3SAiaNlKRacu4gJ2ukVSgw5Xe0 +VmVrGQo2zRokW/ZuO9bN69SQQTccEgrQxwYplaazClQ6D2QNKN0egHCbyag6gxMojLSb3oPvSnAw7DYO1vRK+g== +VmVrGQo2zRokW/ZuO9bN6+dlyOY32uq88jvIbgRzKiDMm0ganjfVKY7FpygLryja +VmVrGQo2zRokW/ZuO9bN61peK/nN9U69DY3VRr6FObLOlhZnqahXz1HoUOLXn9G2 +VmVrGQo2zRokW/ZuO9bN6x9pxWmgkQy2kh90U0Cezas= +L0eUthVnpkGsmKFAX6d+uP5wwwCosax2ir0Hv1xHLf4= +Ugk5+soYHCBsUKBc634TSP5vAwrOjcBahLrsDvmb5FWZu1d3ORGf9BCh0nq1o7jN +ACEEdgL/kNbx7RaZcSRxZIZ7jqIFcKvTxbeHCZUGxKDJtaY2uw5Wg70BfPJBD9aWShCUnQQRQXXWR+PHAmREhq/pitnanpldXtISZM6BIHXSJKcThqhLjhdomc7tD9FE0gFGYn2hL/fyorY87ERXwg== ++Y0hdVhe4P6kxySw/AbSEZezH4XpLoMqZDEtodezEwe+sEK3EwdeWCG/rHzQHNjH2Djgten3Kp0mQBOVU4DrJjvX6vMnGqwK0A26Zj5Z7laJLYyp9s1ePS7FEvx/5h2q +ACEEdgL/kNbx7RaZcSRxZMBkaNKtMOvmOtY+mNP1/ddwTQFHX+uWMEbHiI2ZxpqLI5C5Pk/24zEzu7w1EJmeXQ== +IWUeBsODWZbVEdrzqAjEqtizWLn+FujeuQdgkB69tIMjQJNvecUOjPbaAE5iRYFq +bjpI32qzKze0nfrRYYQXmykS5Jf5w8l6vEzLuK/nTbZ+CVgh2EB5eMaTrJ3F/3hWmR3/6JZoH732hBosDRZY/A== +1u+XjG/2+GSQRv6EzCaWRQ== +PRG/YRzXVD8iVR3bzEcUnoHre2ym25NbVgAhCcm2A1hoiCeOLVi33wtvGBo3MOPE2Cb54XyHCFdK3+BCNa1Dpg== +O3CUgrw2GJfB+mDjH5+NdiRXh8cEweT1cRWwOW9QX5fh6Yaj08E7nEo3hPidINlo +VmVrGQo2zRokW/ZuO9bN65b6RdMyfA4zumGrUHCFg65RTBrSUTABEpsq9PJ4JCel +VmVrGQo2zRokW/ZuO9bN6w9tUPRz4/J0Cgrs+FV7Tr/YcWK0KgvLa4A8Zh6i8yg+uRoNBCckO5193pFh3dc5mA== +VmVrGQo2zRokW/ZuO9bN6+YGqEY/sG/2DL6NHBGgZFPn8HU0P0VRGqmcpSfpPR9W +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61YlA8R/kneqHpa4ue21nrXaE+mh5Y+vq6iFNzk+uuvg +VmVrGQo2zRokW/ZuO9bN69Ej+WUzR36sZsG0nGLhsTjFUTA171Py2jCOZGaWjhVUzTwgwMBDdiIDiZBt0M49pQ== +VmVrGQo2zRokW/ZuO9bN62oRed6hCMosT7UP35UqsiJ+neIey4O/p83yxE/Xz8Vq7FTWnyXgWBqC2Ofg/IQMLA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xv6VKf5v25Gv35Ngxt7+SxexyFaNW8qsenwXUokPM6b +VmVrGQo2zRokW/ZuO9bN64a61Rpqu4F50qZSKzf6C34rN8pqiwxpLduFB9njxLiv +VmVrGQo2zRokW/ZuO9bN6xO985T9pwQ7sN4pEaMuKfefQO3H0FO740MZBVb4uwOf +VmVrGQo2zRokW/ZuO9bN6xr/qvm+5ucCO1hkB5N9exATtIjyk7lQ3ExTkIcU12jOt9fONgTPPb03RIYeiIzThg== +VmVrGQo2zRokW/ZuO9bN69SQQTccEgrQxwYplaazClQ6D2QNKN0egHCbyag6gxMo/CPqg8bW9jLetQduGzVa8g== +VmVrGQo2zRokW/ZuO9bN6+dlyOY32uq88jvIbgRzKiD+c9puldgGvnigwOeCT81wJWkhhBMtEqqc8b+vZxO8tQ== +VmVrGQo2zRokW/ZuO9bN6/bbff3RPIfdJD9Tz8nKTkS8AWh7qG4PAFWqinn3GS+B +VmVrGQo2zRokW/ZuO9bN68cDhwAKTM2Cy3z7y8RSN78= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6463nYA6JOgSgcM3CVjNTJznMLYNsYHB/6aPvDLy4Q80 +VmVrGQo2zRokW/ZuO9bN6xv6VKf5v25Gv35Ngxt7+Sx0bNvv17oN8olvRoRNFEX7V2BUAqQk1w9AG+fHfA3mjg== +VmVrGQo2zRokW/ZuO9bN69dQDCddUjZfqhLWgtMTA+6lr1BUsQBWcHEfSvRE3879 +VmVrGQo2zRokW/ZuO9bN659sVHCRNKwi5EybWXGjIFHDG+wKjWhy7PLQCmhQOSV4291MUSOlSEESLCcvl7/Fng== +VmVrGQo2zRokW/ZuO9bN62+mfM1KxuYR9GMEOHSn12FMvAE6TgNv6jSr3+eIjIkftGBTCi5VuuIi6ta2B1ahrhcNQtCYZ8yLHfkFzehQCbU= +VmVrGQo2zRokW/ZuO9bN68xhA6nn3nm5MXR5GCHMXtcxC3eMgYv7+sQYRGoRMUKJ/NtYdiS6ZTF06NLLhQeZ3Q== +VmVrGQo2zRokW/ZuO9bN68M2OVEeH8lXEDh7ocUvFZ4= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN60ntial7L47tWO4rV4Icn7HpBltblYWBCieqT4UVgq9CFSyUKzN/U7U1y808EhW5Qw== +VmVrGQo2zRokW/ZuO9bN62SJgGjT/WmMIIxZ/ruMwWUGgOzwXkp7Yl61rDQTwCRFc6zTS2NxH91SNGo4fvq8c0/QMcscIcl8fl2DAYz9BEM= +VmVrGQo2zRokW/ZuO9bN62oRed6hCMosT7UP35UqsiK29Hs7QixVm5i7kdWxory/eb/Qqnm5/jUpYwUUUkLhDA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+6X3Z2WgPscDVJea7oFkkBvFOwA8ZIliXzYpwCO5w8mCjx0uV1YsGYftfAWvGpnZgdXDsovKcS9qBn/5dy9/jU= +VmVrGQo2zRokW/ZuO9bN60TFHropKQybmgi/Ewhnmc7xtekqzThyNJcyQykKtAL8 +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN60TFHropKQybmgi/Ewhnmc7PyGF61LfQUSq+4E3rbm1p +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yOPAnkhPEHUxQR8wIbPiwSTWlEsAD2rF3bnj5NhAv9XbaRnFLHata2faDtCqOdjdb1U7MCISF8/6F97G7mquUM= +VmVrGQo2zRokW/ZuO9bN6w3mLqzdYJP4YnG22gQyoLmoBk43np2N2P04jX+kASBb9gClLRXQLQzop3WjvHjIKKMsPzTJmkWildPSC3LXw1E= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6y7h5D3hwYRtgfvir5w8hacLALFT2aKPT+72WX0CphB7 +VmVrGQo2zRokW/ZuO9bN69dQDCddUjZfqhLWgtMTA+7x3DyGBidwiLe8ycxBOOYA +VmVrGQo2zRokW/ZuO9bN68xhA6nn3nm5MXR5GCHMXtfY9183Ko8rfO9qF7oaQasTgQyzsj3jWJAdaiSjnzCWMw== +VmVrGQo2zRokW/ZuO9bN659sVHCRNKwi5EybWXGjIFHDG+wKjWhy7PLQCmhQOSV4ZrV+dIrpw07fAyDbieibNw== +VmVrGQo2zRokW/ZuO9bN64E7kV0oe1KNCWtfO+2IzG2FPrJbLwlL61MXY6h19yCgwlN05/jodOhk+oUWPJqcNN6IBI7oumAfwTtn3AERBME= +VmVrGQo2zRokW/ZuO9bN62+mfM1KxuYR9GMEOHSn12FMvAE6TgNv6jSr3+eIjIkfOEwcPjcDLjN9tD0xweBslcAkKKcfqK+vUKrRm3X7Nl4= +VmVrGQo2zRokW/ZuO9bN64OojIhoKNJ1F73QGw4VuTsSq1t0rpx7OXXkl8uQ30KM +VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN60NuI+3rwmZPRXBatW5je5ashCp3DeAz4mwJFRsD2uIz +VmVrGQo2zRokW/ZuO9bN6y7h5D3hwYRtgfvir5w8hacofEZFxYSxFBNP4nrNMDKlU4v51PkdjGnN4kullijdzQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn3k4igWZGKmftHXEMWcn7kly21b/nYMgSJlcJhkvDi9w== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkliTSPh1fvkS/ScWR2yLc4+HTPJwBf81Hy/L5fQgnRrH7bNCsflDO0k2jj/2A1Vbck= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn84mKwOupbY4jKHqhGVSsSRWvM73UP+iiVN5SxtdLKWMg4JgXS/q/83v0PECO1Oxw= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklAMkk+cPHwsBtRXYD12TtxwEYLEKXTLpcd7/0JsjoVdw== +VmVrGQo2zRokW/ZuO9bN66XK99UTYPeH5P9OwsB1SDc= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +h9U6jl4cZ4C22C1wpKu8ve1yUcQ+dIn1vMmQcAZ0Z0w= +FBy3vCcAP87b/+M8ydgUJRF52I47z/TZBzA6Bymagn0= +xOhAHwpsatV+hYHn6t6A6bUPiUFFzlk01p4yvWK50jyy3U69zJPFTOkEngdeeh4dV2ZsmN/celU41tVeKyZCPKR9o4ij9I2DDit4hQ8yk8OvD6qwHkCfsNW43rtkacx8 +topgAunkM89n84cH9D9pHBHJGKogzkmjYZitDzWAv+GuAYoOOkSChVYahktCovv+ +h9U6jl4cZ4C22C1wpKu8vZOLikErPbvUJn108FkBgu9+60+OG57l8ccneiDXTrwOoz7y2s/Cah0Aj4t1RxHPgznaDgjVKZs3ALXpKjeJ0vo= +lGY+HGJ/DlLufZHWWlj7U6go50WaXphLgme8Jt6GCTs= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +IE+9Uft2qvVuoU6zfHciG3wzRZH5DHb0iYqeBG8ShAk= +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY1lJUaDlPvjJcgqNTD10CNjS+JFlHmmzAW7C9TIFGPZZ +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +2A27FrJel7LwQVQbnj4q4hlgih3qBk24vCQh3ZEzZH1D9Te7T5q1mDbPLZp0o0v6 +wXC0PXkDt+//2q1Gfu8pw1omD2s1DPC9/eHWLru0T7M= +/0ULpLqgTvInFD0r5hHANlt/A4HN48BaWWKSY1CtQvClxZHrVHBtcJRLbba3CP1w +9kMYV6mizqMbt4N+qTZtN4zB76eoGATkjA0lU5cQ2Ak= +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +Nxw10FAZUk+m0nadC1hdy7a2+IPpKTLzQ6uOO+E1Dbs= +X/Lp4PsbbXulceSXUvGUpEwg42OTOSx1VxHM46BBNX0= +s2s/1ED0ebJ2NmFlE8kk02x6FisgSkWfiVYBaeNNhGvko7bwZ1FD5aZ3vGnu9kSm +1u+XjG/2+GSQRv6EzCaWRQ== +p+qW8e8p1zrxOyU05vOA0oTKH5zqNz+PCgf62f9tApk= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P3lRQWSpZzKNexozsWlQSmt +FFvLZRifY4ofMFjBuFasFxWXCYrK9vqCc5dq5VE0iw6DSlYwafvxu5DYwNYrMthu +1V2v8QerKOmubvSxgB4eTBZTEixtIaCeLifMkliDnl1FSzgrzmlMEyvFnWK3RGd/ +J1UGlXkKhKXD36OrXXhN8BVlTjNvt2FG2CiPqdl199JaWrFGr3c4jWvQXS12vU6gFbGt23o5BdLlHOVX3j/o5Q== +1u+XjG/2+GSQRv6EzCaWRQ== +2J/cJdNHXZ2Qsa37Fo6/Z/gM9h4l3NzxQe1VHPUoNCj50z035sIRNuNbaAptxn44 +0aOe1PRcA3iTQeyd7fu/UT3OH8TgnUZIetc0kIThlMY= +V79F1HDTm8z6yGnQm/hDJr2dkX3jv5pKaOjk74dknTfkNo8du33ifDa8c3SEgcfl +mWtqqlZvPd5u+MDOxVnfq7x81G9ia1eZdDpax7kDimPICwqRAAp4SD95uu4DP+3q +ftwgBqNbwagslv/gBU4tpEE5GDJfsuYmSwXmJa3+o1fA58g3B92Kp8T+2Ht6OVkf +K/3TjoKXtlVKQEXdtPO0uifsTNFU0eFt8+2eD6sIbn2PmRNN2I5GHAjvsDqrxu3awe7s1IGxmpgEyF5PTdi4Wg== +VmVrGQo2zRokW/ZuO9bN6xz5iGlq9bNMsveN3G8E1JikmXOYVXY7T1MoO8bJd/lt +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P0b5qliwag+wNT+s/UEtf4f +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P3vHLK4yXJh/Y+S+1XhH9rt +1u+XjG/2+GSQRv6EzCaWRQ== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6xz5iGlq9bNMsveN3G8E1JisN1fUkIMjwSqaHMkv+2OFIPr5q4vWGJVqp1MZusIqwg== +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P1Oxzn1tUamy4I7kxYIUhQU3NM1tr9mAx/yIud930NXFA== +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P2Ei14aMB20i0YI/RCMc9vC587JhNV12B308q8WzisouLfqdSseWY0+wqtG9K74mqs= +1u+XjG/2+GSQRv6EzCaWRQ== +1D18KWr2hdVsBcdZx1OaPfvwrE7g7+awLCd9GHCg8UHyIrzitpsEab7Put5dPSX9 +VmVrGQo2zRokW/ZuO9bN654Optg5TGZB1gMGxxgScexFS1RR1Ks0UsELmW81E6uK +1u+XjG/2+GSQRv6EzCaWRQ== +o1bT2zrSxs12aPyNKpukk+RBjtOXgYwyhl9BlbuoPII= +9kMYV6mizqMbt4N+qTZtN73ymQjbbWCfVaP1lIPcs1OU1AQgg6+83fISm2I1XX/N50H2FN/cmR/jUdb8rfxdgjyYdsNSEsociL+R9aPXHUMPalIxO0ZQEAVocqvn4o/N +9zgnmC3+5N9XK5/NRaR4SIX5nH4iJWB9r8DAPkHCqMUc7KHT6GbFzm1/ZZDfEDnV +ACEEdgL/kNbx7RaZcSRxZNCtRTkFUngsUPEeQy/+UyWj7cI78bO+VYj4wVqG+FraCRyNCLzTSU5fw732DiiY4Bs6NS4JZHrotgQbzgu/NHg0XC2ZDt2Tdq3I1jVs24bycirX7+k1becWKOkHHvZ60ZXv1saJ5rczadDgDE9Qj+k= +lGY+HGJ/DlLufZHWWlj7UzT71dQgCsiwDOluCIjxD+k= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +IE+9Uft2qvVuoU6zfHciG5u6HyMWvuiuseR1J2M4pfs= +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY++vWRAhgONxDuLxgRuRo4NAs9c+F3KZcSg+B4cy6yyb +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +2A27FrJel7LwQVQbnj4q4hlgih3qBk24vCQh3ZEzZH1D9Te7T5q1mDbPLZp0o0v6 +/p6t8/yEODj2SpyYrFogNPdnJ598mYDI9OQG3SKrs2s0xEMb9jmts3FpPIdSfwUlsOkZH7GoGPHgLihjl0yD5koXY2E5+OCPEh6lFcT5FWGdmJWettOxpcoMW5CbJ+ym +/0ULpLqgTvInFD0r5hHANlt/A4HN48BaWWKSY1CtQvClxZHrVHBtcJRLbba3CP1w +9kMYV6mizqMbt4N+qTZtN4zB76eoGATkjA0lU5cQ2Ak= +1u+XjG/2+GSQRv6EzCaWRQ== +Nxw10FAZUk+m0nadC1hdy7a2+IPpKTLzQ6uOO+E1Dbs= +X/Lp4PsbbXulceSXUvGUpEwg42OTOSx1VxHM46BBNX0= +s2s/1ED0ebJ2NmFlE8kk02x6FisgSkWfiVYBaeNNhGvko7bwZ1FD5aZ3vGnu9kSm +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +p+qW8e8p1zrxOyU05vOA0oTKH5zqNz+PCgf62f9tApk= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P3lRQWSpZzKNexozsWlQSmt +1u+XjG/2+GSQRv6EzCaWRQ== +FFvLZRifY4ofMFjBuFasFxWXCYrK9vqCc5dq5VE0iw6DSlYwafvxu5DYwNYrMthu +1V2v8QerKOmubvSxgB4eTBZTEixtIaCeLifMkliDnl1FSzgrzmlMEyvFnWK3RGd/ +J1UGlXkKhKXD36OrXXhN8BVlTjNvt2FG2CiPqdl199JaWrFGr3c4jWvQXS12vU6gFbGt23o5BdLlHOVX3j/o5Q== +1u+XjG/2+GSQRv6EzCaWRQ== +2J/cJdNHXZ2Qsa37Fo6/Z/gM9h4l3NzxQe1VHPUoNCj50z035sIRNuNbaAptxn44 +0aOe1PRcA3iTQeyd7fu/UT3OH8TgnUZIetc0kIThlMY= +V79F1HDTm8z6yGnQm/hDJr2dkX3jv5pKaOjk74dknTfkNo8du33ifDa8c3SEgcfl +mWtqqlZvPd5u+MDOxVnfq7x81G9ia1eZdDpax7kDimPICwqRAAp4SD95uu4DP+3q +J1UGlXkKhKXD36OrXXhN8IJbvphSi+7vstAaJ7toEgm4zZMxFe/rKS4u9y2Ssvzh +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAcBxCAXlvfzQFBJgoPBTJbj +VmVrGQo2zRokW/ZuO9bN67iVlFAU8hAYMafjpVDx4oOy1GxC/YZz9MGGC5hXHifDIzzMbooqVq803bDkm/ITJg== +VmVrGQo2zRokW/ZuO9bN66Bl6UaWMaQpzwFzTULo/tY6jlTwP4gsV1QVwm+H2RE+ygziQ7qyYUv2jZGrZghIXhaI9IfXLM4+/7ZEaTQK0+JudXe43jFBuxq9uaQVanpR +F9Sftf1PoN5P+lgvc+r10DzCXpZBs2+NQLZrUg4+WkxCye8nWiOXw8mDAYUsbMktOZPiLEpeqFuRtHXGZ2OIbA== +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAeRwlwRNQErNfyGxNziVpfT +VmVrGQo2zRokW/ZuO9bN67iVlFAU8hAYMafjpVDx4oOy1GxC/YZz9MGGC5hXHifDCkvImVt+p1wORLo6xFoArA== +VmVrGQo2zRokW/ZuO9bN6xYoLnmVOS/ahrj5uuRdV0EWsnEizWRxlOrvM8wENlN4 +VmVrGQo2zRokW/ZuO9bN65e0KdmCuDXHyRDbH0mrOLKZuncKdgINFZSmlvo5RFWwygn3vSbFEX3KrOzxXciCHQemoYWznnH0DlDnPaok5SJghk4Gvzt1lP8Nd8xypH1gdpU29AuUlNmUasQNQYvk1Q== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65e0KdmCuDXHyRDbH0mrOLKZuncKdgINFZSmlvo5RFWwygn3vSbFEX3KrOzxXciCHQemoYWznnH0DlDnPaok5SLNfnGfLb1wbPgdBMy9o4VF+look+gYRrvZgUaelLl4lQ== +F9Sftf1PoN5P+lgvc+r10D7o64ZLbi78S/TgPxw/f4SOC++uRT8tIfgwb8Omo7vu6chE1yw4OMlsbZQVGt7v1YdICPBImfmbQa8l34QUTWP5nPueh8DOOl+YjipCgyfiah1QuO33ZZN9u1isdHNgaw== +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAdgqSKLQvYqIyYU7AJpR7fK +VmVrGQo2zRokW/ZuO9bN65X+HRZP26yr5JJcJjbroHgY2yQZCC2iz/EentJIOM14 +VmVrGQo2zRokW/ZuO9bN66Bl6UaWMaQpzwFzTULo/tY6jlTwP4gsV1QVwm+H2RE+U2dhdllU017qeNlwV/t3qMxoN3b6ZEwtypHeRVok5QLeq7jBhGNGeUsnEKGRoGiN +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAebii1mp4p3I7SDA1b7UujH +VmVrGQo2zRokW/ZuO9bN65X+HRZP26yr5JJcJjbroHgY2yQZCC2iz/EentJIOM14 +VmVrGQo2zRokW/ZuO9bN66Bl6UaWMaQpzwFzTULo/tY6jlTwP4gsV1QVwm+H2RE+0ESEOFCVWY3zpdLKkxogIjfbS+mp5BcT25dPwPqol/ewrCICKac8rraGy87WhRhdSJ6N6R7PX4AbXUWbAtjbLA== +1D18KWr2hdVsBcdZx1OaPfvwrE7g7+awLCd9GHCg8UHyIrzitpsEab7Put5dPSX9 +VmVrGQo2zRokW/ZuO9bN654Optg5TGZB1gMGxxgScexFS1RR1Ks0UsELmW81E6uK +o1bT2zrSxs12aPyNKpukk+RBjtOXgYwyhl9BlbuoPII= +axUGGc1VN8059OpAvKXjQWop4g5wEilwDML73fTR0aROShh1A/PpsX89P62Fxwd4hZKM+zVAPfmRsWcP03u9Ku3VD03o8PL+LDYwCELmIUqQ9SwDTtOHpMHkHqyUOUm1 +lGY+HGJ/DlLufZHWWlj7UzT71dQgCsiwDOluCIjxD+k= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +OtFYf3L1SCFz3eV8uxxLQDcWdIw+tWys4K3uAhWrSOIHz+Hf+pmJOLXaeG2mKqnJ +Z8UsPk1Q7HtwjRd4g01ryw== +x1Dv0cSaQmavN9ft+ex9jCSwM0+ru2NYHY8G/siatT0= +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +L0eUthVnpkGsmKFAX6d+uCuT47MPwULi2lpyJh4ryFDW9qwV9g5joBbVHwA9hMBSw4eJiwzIZ2bMkcNfWxzADeutkGRiY7X7EdjbySNl+rA= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN67Z4GyvH1s+PEZuudjntZcEEAEOgkqKv/lySCowLggPbbCf4J73tOz2c1J7SATG4GzNyDiMvQ17bky1qOfBwo79dgSMSwZ1eu3+WvIgk3sqo +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN64mARHpcJLoYD+9rQ7Ln85U= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +QdTjTxbShl+8mrARISGoAbKd/0Om5LDKQaniNR51X/Q= +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY6IrtSknVO/GcflctAZsVFrYCUa5MPG1XS1Xvxi+13Qa +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +h9U6jl4cZ4C22C1wpKu8vSM7bTtvy9o2g7Di3K7jTo2HJl2CG2yVZIJgod88bc9l +70InS28HLacTjXElRM5nzDgBR0IPsK1rd3U17Ol/cDLFLPbymx3eOxXlhbV+4Rah +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YK+xvdtabAjOdZa8rH/UawJ +VIHA31zdnuwVlvUv3VOlYVRNGNAMqDxg7g3P55Yig7XGJ0frgqBbhflLu9pZDsGtG7PWJFzYnbAhv0HasJD0eg== +X/Lp4PsbbXulceSXUvGUpDfqLabbqgG4MZE+Cdy4FwlQTZqFS7MLKRTLDMwaDvILIRxQybc5sD1eU9BOwIwsyw== +1u+XjG/2+GSQRv6EzCaWRQ== +3CRPlw3jrcmcxl55IvNWHzpHT+6v7kWYl0ApHHoxKMJoX/gB5r4x4K9yipZS1RbVmH3Bni4Adtio/jyOu7/Xky/SKbiStP0vIIU3iLQZKYxRFiMvP4KcugEoJmswVAtcNQNIl85wHb5F+uF64zFndw== +1SbkTSil8XzgXK/aUkDPBugDeezHMR05FNSDcP/zAI0EyOvkngQrNpe9Q/zEiKi6 +ynmt6S+38NUiCOOHWGlzR+5LF2yk1cZiM+CH3D6A1LQl/LBPT3dK/LV2jXWp4pRO +rZ6/dyVUzW/Y6/ismBQRsvai7eL5Elhq0TRluGLB1iQCjISRGPWdI/DC4yKUCuZH +ZZ53txuqZwjSqUUoQFkxA8vxD0O4EQP3Gj3bk9l6kkf75Nhh4D9nFVIyddS3RTiu +vp1kd8TYnGnnwiRkbX+cNGmevUM3IizQMssm2G2VjPQPgaBN9dNxHkp29IH1TdEq +1V2v8QerKOmubvSxgB4eTPtnzcT/cbp3ANG+lRJ84FblNIiML31gaOFnx0iDFfMHFprvq8suc76LP6Nv4rjsTid6VorF8J3rPdddQqu2ZO0= +VmVrGQo2zRokW/ZuO9bN6wYSJfAvINthaFx/9kAbIX4t4n7u7tZTTsBIGg2Qyp9A +VmVrGQo2zRokW/ZuO9bN62K+Pj/G4KSvFtRlZdyfBhiB0llpWvlaCQsNJWvpmIpnE8q8slXtmx/jGYLxEVGETG+NYh6Q/nXeUjy8GYFyxE7awePOtgMs7chfALOimNQRQEr9LgVtnt+NcdVKIVdxAA== +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUi3xlrmD1YKuD9vITYmwXw2LQFhXrmbjxoc7PIhRGtlxg== +VmVrGQo2zRokW/ZuO9bN64ontDfjzG02xckDeU0RX1dlOr0aDDh1ClJhaUJQQEzI +VmVrGQo2zRokW/ZuO9bN62K+Pj/G4KSvFtRlZdyfBhiB0llpWvlaCQsNJWvpmIpnE8q8slXtmx/jGYLxEVGETJ/m6IY4mJzQJ+V47Dnk665/atEpCgAH3VqlGooJcbGY +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUi3xlrmD1YKuD9vITYmwXw2LQFhXrmbjxoc7PIhRGtlxg== +VmVrGQo2zRokW/ZuO9bN61wyMShiXniwkoczPQZqKjfgIhCq708yG+xQp3DrRld/ +VmVrGQo2zRokW/ZuO9bN62K+Pj/G4KSvFtRlZdyfBhgNCj9Yj6N4yd2ZIBHnP0lzjzLtu4SleXbeNFqXOKNpEZmlBRA0F3hFmzJkw+OA2vIXkDIGIVhBQplDJFB/j1ZZ +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUi3xlrmD1YKuD9vITYmwXw2LQFhXrmbjxoc7PIhRGtlxg== +VmVrGQo2zRokW/ZuO9bN60mAu6yBx4d6kfvOGCLxZ3YAjG6cu/92rOpni9SYu3Uo +VmVrGQo2zRokW/ZuO9bN62K+Pj/G4KSvFtRlZdyfBhgNCj9Yj6N4yd2ZIBHnP0lzjzLtu4SleXbeNFqXOKNpEYCoUT2GLVT4RJj7Ye7vqiRw6J4Hh+4SHU4QbWrOzkiCsoppZ9Egg5/7bWlwfrFqBg== +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUi3xlrmD1YKuD9vITYmwXw2LQFhXrmbjxoc7PIhRGtlxg== +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGhZkrpt38uR3iX7EUOZ+eOR+aAqr73auptB295rHBA4QDmoJ45/cdwzvUGosMaFPoFRPmvLy3TrBNLtV7Ktn4sG8Ogrd11C48+dLen4KuB1kg== +L0eUthVnpkGsmKFAX6d+uMrw1kmlfAOxw9snwIOJEE5vzwAr5lC4+rbX2BqiWH/dpzzD2IlSKVcXrDtzOCu1+g== +HTOfkyRnnkvrq92C2ykyOPO4d7gErRgYL7aScZTpzxREn6lD4QDtxAH0B30SgN6E3F22VdO8GzELDUsICPiSeg== +P8KbMTjfuhARpOL8NoffNro45O1U8oEoZJkv8WBE9IqPq9Tpvqti2IlFB1FFEDKEayTHih7/O8HeyhvI7Rb1xQ== +L0eUthVnpkGsmKFAX6d+uFjjhUy+MCKFNT4NrCL5yNnhcA/pEoLZALqem+hbKHoY +1u+XjG/2+GSQRv6EzCaWRQ== +P8KbMTjfuhARpOL8NoffNom7jch4ZQTU5jfIo7wG8ScR4XnHaBOnM0U+2vh4JNta +L0eUthVnpkGsmKFAX6d+uFjjhUy+MCKFNT4NrCL5yNmWWojcHbeJQ61OMhTGFdlC +1u+XjG/2+GSQRv6EzCaWRQ== +i4c/4ocK9c+0kOv/NeukhzRCRIDY4b1IcwhCyS6fwKaxL1iz5YdtgQ8ur54xnSve +89eRayX3G8tCX3FDU5IcjcECGHtpMfMxvoO/SDLARSENffTD+JaXDY9RnayEnHLnlI8b7pgVZ6VRMA8KffpRHg== +VmVrGQo2zRokW/ZuO9bN67AUBZ3/XEKHpa/wOGbEOnmtIF6uAz0tBlf7rcOxUQPuytPu9bJw1t4kl86ncxKWUr6UFGd4R8GkpK1204AYmG4bJSHkhwXQJ4SNyep2Orx8zpIZXaQwin0CO9B7uaPk1A== +1u+XjG/2+GSQRv6EzCaWRQ== +swRuTHNu5hN+oxWHfH2uTRaafzF+WZloRON/s6FBnLM= +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcJ0dAhlHHuuDGrGW0z9/mG1YgeQhelMeN8fM9H05Q7bFoBNC//YD8IgCAMKgkLtISr7jJb8ysmAn68sYqB3zYKY= +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidaKrkdw//NbtiPyzZIdYJRwkO1XfoVngfneyEI34hiEIjZDJsEWIi+RYy+5PJ52y4M= +6P8mKfouOK1hwXy3MrrCzYNv1bj48gIxdX69IR058kk= +2J/cJdNHXZ2Qsa37Fo6/Z45HJGTQKJ2XarF9YWFlYi/NS7Y1/C8WI42zDRQS65sB +K/3TjoKXtlVKQEXdtPO0urKjWEFV9hqYUzZS4hx2t3vgmB4P0sEnPBB9+cxnHYt4 +VmVrGQo2zRokW/ZuO9bN6zPTlJYqiIwYIHWMLx6MVG4A/jqaX4tahdPmgshFSQG4 +VmVrGQo2zRokW/ZuO9bN60KbPALjpXkxdnMsVQY2qiQ2mn3irY/rZFqtKYoHLxRE +VmVrGQo2zRokW/ZuO9bN60Xvz4OmVOCjpkbTCNQIATsOCVbKnePG0cTDCTWdue9j +VmVrGQo2zRokW/ZuO9bN66guoSIjBQ2hp+h2iWlRadc= +VmVrGQo2zRokW/ZuO9bN6855zfg/ZnrwyfrD1ClJJctc8wQUc7t8LPNPEB5FCa4H +VmVrGQo2zRokW/ZuO9bN6yrrO27yfi+aG1SaNFn36A8CnO5yCexFXRxrEFBi6HH8 +VmVrGQo2zRokW/ZuO9bN6/m/X7phwv/rEzEofJRELW/qETCx/ewa2vv2I76GwEWB +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN6/m/X7phwv/rEzEofJRELW+ZhbjEfjOJN1QgOkdHcgfg +VmVrGQo2zRokW/ZuO9bN69t0x0NhvKGOHr2+EEBw8Jek/n491mR4LKuB/UuUgzMUy7YY4ELecU+vhof4LvKC/Bhkvz+bT+jSRSy/qFVu/F0= +VmVrGQo2zRokW/ZuO9bN6wIR+5HpCjcoa898tUp4mpJqO8Na+qweZg8vpjfBiLDS +F9Sftf1PoN5P+lgvc+r10HYBS8twYBi0U/DCh6ocj+X6RD0HNlSpwv37yho4nl4Ot2LOLOWFJbMnayi7wAK30w== +VmVrGQo2zRokW/ZuO9bN67yPVwtUvdC1tj4awelda/fkZ9dUXYCEUeCyL3Yrqyn/ +VmVrGQo2zRokW/ZuO9bN60Xvz4OmVOCjpkbTCNQIATsXOlREGrlEeF/zy2qslZ1j +VmVrGQo2zRokW/ZuO9bN67HiZr3Mu5Xx/pLDDclKRz8= +VmVrGQo2zRokW/ZuO9bN60KbPALjpXkxdnMsVQY2qiQyq2q2A/0gHrZHUNJvsm2k +VmVrGQo2zRokW/ZuO9bN69t0x0NhvKGOHr2+EEBw8JfSNIZvkvl8jIdEvRQwBwJ8qc6p773V8w3sbnJ7sNRCjGOJmoa2y21Lbs/qYLxw7ZE= +VmVrGQo2zRokW/ZuO9bN6wIR+5HpCjcoa898tUp4mpJqO8Na+qweZg8vpjfBiLDS +F9Sftf1PoN5P+lgvc+r10NgITD/y7z0I4C2wYBhOQf9kEr4e0+0VrZmNsbWDAMY531ZzaC8lUc4x5xBNRPYlTw== +VmVrGQo2zRokW/ZuO9bN6yH86FWoLic2FxcRUV4zebxf0/M2UvczoadL9jeQKWgM +VmVrGQo2zRokW/ZuO9bN69so/u5P+e4KbXGeD0Dm7to/lSyHyW7MicMJ0jDa2eXr +VmVrGQo2zRokW/ZuO9bN6xQjxHnk9JeJcV3ebvE1Z2ZFylERkWn2HmPyx+oo40NY +VmVrGQo2zRokW/ZuO9bN60KbPALjpXkxdnMsVQY2qiQQ1X8tQ+wf4ZCJUp0oMXLr +VmVrGQo2zRokW/ZuO9bN69t0x0NhvKGOHr2+EEBw8JfSNIZvkvl8jIdEvRQwBwJ8qc6p773V8w3sbnJ7sNRCjGOJmoa2y21Lbs/qYLxw7ZE= +VmVrGQo2zRokW/ZuO9bN6wIR+5HpCjcoa898tUp4mpJqO8Na+qweZg8vpjfBiLDS +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6zPiagaoQNUZ6Q8MukMCasPCfaBG6YJFzlL84vUM1kV8 +VmVrGQo2zRokW/ZuO9bN69eu9LBWEB6PDbGO+0TyWwBhVpoFqMPkT8HlHX8RMHrR+75Z9K8m2JUCWTBuR0Tk9Q== +1u+XjG/2+GSQRv6EzCaWRQ== +o6QhOIN2Sc4SHELnst17uXpnZ5EAUQuq8Epej7P3sTRveRgNRdPH82tKZu7g4Eiq +1u+XjG/2+GSQRv6EzCaWRQ== +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs+glmD/7BTlR5C7Ujg9dOtQxQc0uyS/kT/ILSqYhwQGNA== +b4OJVZe8QyIpjuTpKXDL9A== +KToqm5Or/ymuVO66QU/2pu7oKyhGYWQ+0pgKsF2sKsQ= +S76gqn57AVIGnu8xVYuZaO27N6yYhZ4GNoiBGQP9DKg= +K9/ExlRvl0KpaOPuc49gtg1nR3Bc0+g9UN7uwKhXbdM= +wlLHv6kT3Q/RmtMBN4nDAVircipW/U7HQDc2UPUyQ/vmRhc3HaKPFi1NtnisK6xX +VmVrGQo2zRokW/ZuO9bN64JOsgztOsHxYHKGCeX474Qcqbriq9LDc2GKDAaXpEva +VmVrGQo2zRokW/ZuO9bN6173lsXiCfzKN2E9+9sH2KYDQWUma1tCh1mCQLO6go9pew9ww96U7ujnre6SjyCuxQ== +VmVrGQo2zRokW/ZuO9bN6/EQSa+RVcntmzafI6w4hrXeQefCDpqhGzMMDJocinCp +VmVrGQo2zRokW/ZuO9bN64ViM2XLR1EOvB6UVgIoYuF8fdMuzlzZir170SdeoO1N +VmVrGQo2zRokW/ZuO9bN6/EQSa+RVcntmzafI6w4hrVZKx2YSK2fHbBecH4ladDU +VmVrGQo2zRokW/ZuO9bN66vpbXlJpZkCo8bNa8hfdTtD0OOBen8N9PlZvb6sHluL +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +KToqm5Or/ymuVO66QU/2puiKplVgTGTPZqNeAeKoYCRnGqCo0URmUW6QuDrFvxhR +S76gqn57AVIGnu8xVYuZaLg1MWy1KmN4+LZJN+yDmqCMssHrDpVNECVIttB2y5oe +K9/ExlRvl0KpaOPuc49gtoyfPotIPHp16XgJV6eRYyrbA+h8XwwZ0jIgIeiV5WJH +K/3TjoKXtlVKQEXdtPO0uuuN7F0BMuO/Q3cMv6UmDSFakb80AgleKsv9apABSHy8BJ9UVzoo8dLM78KhLfy4Pw== +VmVrGQo2zRokW/ZuO9bN65LhaN63q3ApjnzlgAtjYqw= +F9Sftf1PoN5P+lgvc+r10C1yj1HZBpntAx/i13/oYeYaK3wfHrLLbHW7duBhTqbF+VuvZwLaBegIbS5rvj5IKg== +VmVrGQo2zRokW/ZuO9bN65ZTH8ABglrqk9nDO7nL0rs= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN64De6ITZeavbNwRiqFkMysk= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uAST/oojvSe5vOTCDj9ZTSci5aesa7iYDqIo84xNyUjr +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9OQ4F/6xMvSNwNUGdXyQ9F1NqMgCz0f8DrpmEaOqG0E= +kkhBrrZfa9Lz2ARtxqQ9WI07s+twubQO6K+lkziI1UrpPBayzeeEXSMVuzem4Nzq +UxWVPzm6Eag/KO1azIHUdx+idAdS+rfPARzR88tVbgYdpuz2ULTF+2vmnBiei7OW +Lb802TYIO8cTPX7RtIYGZ+6eFx+9GqXwRvUIq98SK68Eo/uW/nIxVIKX8k1Y1OBk +b4OJVZe8QyIpjuTpKXDL9A== +I5csN8e3J/KIFkMa5t+SJCqL3g3fUtFpMCVgdY7uBLC2hDdwb73LgrQLVQ3aMtK6 +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033ZLNQ2XZF4KsFXQ0AJ186dTqy0tFe9yVaa5Nvi4GzvRQQ== +UiubnxD7y7+jD/CLXy/lIzUv+EQIUnxp2+tRaByCudDYzFPn13pFELSDNAgNf0aY +wI4EEWzdSnynhqFxMCzFmvtQa54ffiTU8OKIuM+mUtbRQW9n1JFAWLuXCST3voAI +GL/yLvMwjflJNfDPl5ASjQ2UrwSPDpUhyRkgeiXFg2U8KeI5JLJSQLyyHw7AauiQ +P5fFy0VqKNQcixZC6/gotxklJeUYBp6qUkCM0szi5po= +3HDkd2BFYLPk4K73U5D+BYdx+YPBx8edbX2mUEZUhcw= +gZ7BoIAABc+I8g/rYzq+fw0QRhyOskLowTpBMxzNkkXJfHhElpIZe5wsCdqDFCZvGpC72uDw/Cu+D1FuT4o1ew== +NhnIR3Ilo4H2su9/cTNo/AxXtCXBAtO/DWPyG38GkWo= +SXXsDW1aLF5ljPyJUoRc7t2uV1Ea/KDPsubfQRA1/g0= +i/p1vQ7ugXGBvwbjECApz/Cr9YvVAiEjIabMp4aMpkk= +zo+FVrlHkUeZ8fJX4PVhAH4W08MspSsE1ssI18UNPaA= +wlLHv6kT3Q/RmtMBN4nDAaQcqH83V4KNJSetgkXN2mpJpLhuyLAaUNt+tKkzaLSI +VmVrGQo2zRokW/ZuO9bN632VflaKbWaV9Wa8IOB78Qk= +VmVrGQo2zRokW/ZuO9bN608eo5JjqlJJ+hYuirBCFcmf4cKrfv149X3GuxINE1xDDJnAj+I17VZiW+JCiwfRwA== +VmVrGQo2zRokW/ZuO9bN67FMFupuL9NnhZUvLdV1qH1jDx/7tB6qL1SG7Pqfwsin +VmVrGQo2zRokW/ZuO9bN65qbK6/I8bncNNV3Fr84lp9CCx8VYIApMdX4h8SHWyk2 +VmVrGQo2zRokW/ZuO9bN6zEL40rbK+o6j49cNVbzKqOsExNYZ+R8lUnZEGfiuhXY +VmVrGQo2zRokW/ZuO9bN6+EP/UfvFvq8hDmkfhk2A+qnnPSHBLYJiyMbFizN9aWEoBeZFZoLhXy50+Ts9Ur1/A== +VmVrGQo2zRokW/ZuO9bN6+EP/UfvFvq8hDmkfhk2A+pmn/JN+SNJ6dRlNT3kh3N/3tDs7zMtD+kCYf9bfutOkQ== +VmVrGQo2zRokW/ZuO9bN60xwqMjDC3vPo6L4vEr/pBaJQkw/7oJUptDcMB6Fr5Cj4S+qQ9QZRL24FoS5lS3+qQ== +VmVrGQo2zRokW/ZuO9bN61G/tYB+vcuOiiaR8UbkscqXrSG+YeXqyTcHAiaZKnMP/R8AE4EFWhpiT+I3n+NNhw== +VmVrGQo2zRokW/ZuO9bN6zDu/0cPxO8iIAxo/MyAOyO3mi+GHsPcbGU9H8sA9FWz +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm5Kes26rz2vkjDRpZY9y8CG+MKLyqwcOLQaC8uFAcQhA== +VmVrGQo2zRokW/ZuO9bN64ApqCXcODvD57AL9oh19dsQTwsNrJDaw5mMC3iBkcYF +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmssm/VPICxxxORTX5OIKSeBFVbEgJs/oO6D+R5qX3b7w== +VmVrGQo2zRokW/ZuO9bN69/FjVndCR4PL8NhQnFk2Gew+8ZLbD2plI32rzUojOkk +VmVrGQo2zRokW/ZuO9bN69IgslanOR1qBWJ7u9f8kApHl7m/3x/YUX5TmvRhoWWrhrEvRs3e6Vo03iY4FZoGJQ== +VmVrGQo2zRokW/ZuO9bN6yAU/fW49cRu9QdgvjUnDRs= +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN6w7SLxNKr+6YhBMvtqYtIS5B39SOQ9awYMdHGNN7g2HF +VmVrGQo2zRokW/ZuO9bN61ftGV3vPtB2WjF6YrN/0jNHYRNif5kQHZWzQEGp4Mrw +VmVrGQo2zRokW/ZuO9bN68i9AQUAyLa5Izsu2k/9DPtGLI71suphKNgMpp1nHjp3 +VmVrGQo2zRokW/ZuO9bN60FmyZq9YoZep5W4V0xLs2VJw4EwR4WqghfwBKZHKkzFW5QCRYRsMStsmunHScuj3A== +VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C +VmVrGQo2zRokW/ZuO9bN67c57efC+E7a6G1EN/4VXAUA5IVAU8G/FAp8AnNhV+8pDSYVAZUG09Nqr+7c8CWyvg== +VmVrGQo2zRokW/ZuO9bN6x9SPCVoe4PZoqZ6VqYN93GZvfLrMfeSjpwDGE6Mez3z +VmVrGQo2zRokW/ZuO9bN6yf4ml6i++/GqH/wSBe2JUimuAJuO3PPz0XEqqC+kEwmq2P2CCy/D5JDk3aKmyunXg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yZnMkqRvl1W1z1JA43ooyLsysqnBW0cSn//FgR0VFUn +VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C +VmVrGQo2zRokW/ZuO9bN66U3/6225IbLyIW0FVhZRNEyLZurMXbjR98/i0DXR7zf +VmVrGQo2zRokW/ZuO9bN6xaf2vdYGOU85+dskGN5Zti8/wz7YWu3y/jTkefW+oHw +VmVrGQo2zRokW/ZuO9bN6xkyZls2bS2c0K0h3aqZRdZrjes6+0hX3Wr6T+0mnqitWU1y3v2I2Wt4PYH0XBjT6w== +VmVrGQo2zRokW/ZuO9bN62zrDNonCkxA5BeQHJAPUOvVeRTu47AJZhJxjTJXUg6D +QPiJZBcZo3Zu5kDhrrGzbI3GQxTKN1tlSYOd4wqk4Qk= +o6QhOIN2Sc4SHELnst17uYG57hDyRaN+v5d46f3m9TtvuM1edZME3sYQm1fwKXSE +1V2v8QerKOmubvSxgB4eTM+LVbDQYpuIwwcEKVLExe6MC8BJb9x5uoyJJlFncEM6rY6CjjYEoI1lGYiKE1nBng== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +o6QhOIN2Sc4SHELnst17ucZEhQfEUee5E6C4gEbgFRMehTYHPr1sbQ4k/qB7LJ8r1OhV8ece9Zkwgx68RDZgODP4asgDCZmEmIilOSuj+JI= +ACEEdgL/kNbx7RaZcSRxZClHBggmOTwuoukB6MgtasIiEFle6fCvd15573nsLbI9 +VmVrGQo2zRokW/ZuO9bN67Pocv3BMn87TAB+S8aqZRSL3g30PRHmEkGas7a6CXo4 +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN61efkqHAT/xspGlYy7KhUnGfupbfN6kadJV1FzZSIvpa +VmVrGQo2zRokW/ZuO9bN6+b0SyPGp1VLLfFreWS32CdvJQ0lilsNKGrnKkyex2l6ZoEN1nB9tzGcgrcEFt4mWg== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6zpJcGQDok2bzZbwZ5+2z7FMKwUs1r6SjvZQDsl9E9LF +VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgEYDxdXHdYYYJAWxRhWjvkMURIUYeBTgS8tgmIZ7YENDxgg1Zs4OdrlyVu+VDCE+oc= +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgGfllFXAtMFaX/YohJyQyfhm+vA31HUxmdUtlkayzAZUKQbFld81ekbiffgRsrQqQB1xVb1v/rxv/O8tBET/TaPCdwsyclhJhXcaG/6gaDkZg== +L0eUthVnpkGsmKFAX6d+uA6wgjrZ2VgEjfvSiyQz6oysGP6R543HWJTxMrxe6FdC +6ZPJI/HSoc4xA2zncU65FgoTZnuGr10wlo0fDu1cdoU= diff --git a/class/safe_warning/sw_alias_ls_rm.py b/class/safe_warning/sw_alias_ls_rm.py index 3ce06416..b1d3b31b 100644 --- a/class/safe_warning/sw_alias_ls_rm.py +++ b/class/safe_warning/sw_alias_ls_rm.py @@ -28,11 +28,11 @@ def check_run(): if not os.path.exists(cfile): return True, 'Risk-free' conf = public.readFile(cfile) - # rep1 = 'alias(\s*)ls(\s*)=(\s*)[\'\"]ls(\s*)-.*[alh].*[alh].*[alh]' + # rep1 = 'alias(\\s*)ls(\\s*)=(\\s*)[\'\"]ls(\\s*)-.*[alh].*[alh].*[alh]' # tmp1 = re.search(rep1, conf) # if not tmp1: # result_list.append('ls') - rep2 = 'alias(\s*)rm(\s*)=(\s*)[\'\"]rm(\s*)-.*[i?].*' + rep2 = 'alias(\\s*)rm(\\s*)=(\\s*)[\'\"]rm(\\s*)-.*[i?].*' tmp2 = re.search(rep2, conf) if not tmp2: result_list.append('rm') diff --git a/class/safe_warning/sw_audit_log_keep.py b/class/safe_warning/sw_audit_log_keep.py index 36c8e7a6..90d70b5f 100644 --- a/class/safe_warning/sw_audit_log_keep.py +++ b/class/safe_warning/sw_audit_log_keep.py @@ -23,7 +23,7 @@ def check_run(): return False, 'Risky,The auditd audit tool is not installed' result = public.ReadFile(cfile) # 默认是rotate,日志满了后循环日志,keep_logs会保留旧日志 - rep = 'max_log_file_action\s*=\s(.*)' + rep = r'max_log_file_action\s*=\s(.*)' tmp = re.search(rep, result) if tmp: if 'keep_logs'.lower() == tmp.group(1).lower(): diff --git a/class/safe_warning/sw_bashrc.py b/class/safe_warning/sw_bashrc.py index a6129703..ace926a0 100644 --- a/class/safe_warning/sw_bashrc.py +++ b/class/safe_warning/sw_bashrc.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_cshrc.py b/class/safe_warning/sw_cshrc.py index 24c681a0..ef7ce1a7 100644 --- a/class/safe_warning/sw_cshrc.py +++ b/class/safe_warning/sw_cshrc.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_cve_2019_5736.py b/class/safe_warning/sw_cve_2019_5736.py index 19ed04a8..f25b9599 100644 --- a/class/safe_warning/sw_cve_2019_5736.py +++ b/class/safe_warning/sw_cve_2019_5736.py @@ -24,7 +24,7 @@ def check_run(): docker = public.ExecShell("docker version --format=\'{{ .Server.Version }}\'")[0].strip() if 'command not found' in docker or 'Command not found' in docker: return True, 'Risk-free,docker is not installed' - if not re.search('\d+.\d+.\d+', docker): + if not re.search(r'\d+.\d+.\d+', docker): return True, 'Risk-free' docker = docker.split('.') if len(docker[0]) < 2: diff --git a/class/safe_warning/sw_cve_2021_4034.py b/class/safe_warning/sw_cve_2021_4034.py index 02bed4c7..37620303 100644 --- a/class/safe_warning/sw_cve_2021_4034.py +++ b/class/safe_warning/sw_cve_2021_4034.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -32,7 +32,7 @@ def check_run(): ''' @name CVE-2021-4034 polkit pkexec 本地提权漏洞检测 @time 2022-08-12 - @author lkq@bt.cn + @author lkq@aapanel.com ''' st = os.stat('/usr/bin/pkexec') diff --git a/class/safe_warning/sw_database_backup.py b/class/safe_warning/sw_database_backup.py index 45107b75..c88b3d48 100644 --- a/class/safe_warning/sw_database_backup.py +++ b/class/safe_warning/sw_database_backup.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_database_priv.py b/class/safe_warning/sw_database_priv.py index 98350b92..51657e5a 100644 --- a/class/safe_warning/sw_database_priv.py +++ b/class/safe_warning/sw_database_priv.py @@ -1,9 +1,9 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: linxiao # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_debug_mode.py b/class/safe_warning/sw_debug_mode.py index 2272c99d..d7e71b5a 100644 --- a/class/safe_warning/sw_debug_mode.py +++ b/class/safe_warning/sw_debug_mode.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_dir_mode.py b/class/safe_warning/sw_dir_mode.py index 69e5e8b8..c9199b86 100644 --- a/class/safe_warning/sw_dir_mode.py +++ b/class/safe_warning/sw_dir_mode.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_docker_api.py b/class/safe_warning/sw_docker_api.py index 5d2b1f15..ee54217d 100644 --- a/class/safe_warning/sw_docker_api.py +++ b/class/safe_warning/sw_docker_api.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -45,7 +45,7 @@ def check_run(): ''' @name 面板登录告警是否开启 @time 2022-08-12 - @author lkq@bt.cn + @author lkq@aapanel.com ''' try: if os.path.exists("/lib/systemd/system/docker.service"): diff --git a/class/safe_warning/sw_file_lock.py b/class/safe_warning/sw_file_lock.py index 9a2f331e..7b7f5f8e 100644 --- a/class/safe_warning/sw_file_lock.py +++ b/class/safe_warning/sw_file_lock.py @@ -28,14 +28,14 @@ def check_run(): # 执行lsattr -l查看文件特殊属性,若存在特殊属性,则判断是否为“追加属性”,若为否,则加入到result_list,最终显示到面板中 for tl1 in tmp_list1: if not "Append_Only" in tl1: - log1 = re.search('.*?\s', tl1) + log1 = re.search(r'.*?\s', tl1) result_list.append(log1.group().strip()) result_str2 = public.ExecShell('lsattr -l /etc/passwd /etc/shadow /etc/group /etc/gshadow')[0].strip() tmp_list2 = result_str2.split('\n') # immutable判断是否为锁属性 for tl2 in tmp_list2: if not "Immutable" in tl2: - log2 = re.search('.*?\s', tl2) + log2 = re.search(r'.*?\s', tl2) result_list.append(log2.group().strip()) if result_list: return False, '以下文件未配置适当的底层属性:{}'.format('、'.join(result_list)) diff --git a/class/safe_warning/sw_file_mod.py b/class/safe_warning/sw_file_mod.py index a61babf0..3064a2c9 100644 --- a/class/safe_warning/sw_file_mod.py +++ b/class/safe_warning/sw_file_mod.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_files_recycle_bin.py b/class/safe_warning/sw_files_recycle_bin.py index 0a197ec8..1192ad7e 100644 --- a/class/safe_warning/sw_files_recycle_bin.py +++ b/class/safe_warning/sw_files_recycle_bin.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_firewall_open.py b/class/safe_warning/sw_firewall_open.py index 26058da7..c804a831 100644 --- a/class/safe_warning/sw_firewall_open.py +++ b/class/safe_warning/sw_firewall_open.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_ftp_login.py b/class/safe_warning/sw_ftp_login.py index dea78dda..b080de46 100644 --- a/class/safe_warning/sw_ftp_login.py +++ b/class/safe_warning/sw_ftp_login.py @@ -21,7 +21,7 @@ def check_run(): try: info_data = public.ReadFile('/www/server/pure-ftpd/etc/pure-ftpd.conf') if info_data: - if re.search('.*NoAnonymous\s*yes', info_data): + if re.search(r'.*NoAnonymous\s*yes', info_data): return True, 'Risk-free' else: return False, 'Currently pure-ftpd does not disable anonymous login, modify/add the value of NoAnonymous to yes in the [pure-ftpd.conf] file' diff --git a/class/safe_warning/sw_ftp_pass.py b/class/safe_warning/sw_ftp_pass.py index 4c0e524d..d8e4bf7e 100644 --- a/class/safe_warning/sw_ftp_pass.py +++ b/class/safe_warning/sw_ftp_pass.py @@ -1,9 +1,9 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: linxiao # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_kernel_space.py b/class/safe_warning/sw_kernel_space.py index 11507463..8b7cd6f0 100644 --- a/class/safe_warning/sw_kernel_space.py +++ b/class/safe_warning/sw_kernel_space.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_login_message.py b/class/safe_warning/sw_login_message.py index be981c0f..1061cedc 100644 --- a/class/safe_warning/sw_login_message.py +++ b/class/safe_warning/sw_login_message.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_login_user.py b/class/safe_warning/sw_login_user.py index 274bf5b9..c2189e41 100644 --- a/class/safe_warning/sw_login_user.py +++ b/class/safe_warning/sw_login_user.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_memcached_port.py b/class/safe_warning/sw_memcached_port.py index 1cefc107..df1f8899 100644 --- a/class/safe_warning/sw_memcached_port.py +++ b/class/safe_warning/sw_memcached_port.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_mysql_pass.py b/class/safe_warning/sw_mysql_pass.py index c57484ac..37756e82 100644 --- a/class/safe_warning/sw_mysql_pass.py +++ b/class/safe_warning/sw_mysql_pass.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -29,7 +29,7 @@ def check_run(): ''' @name Mysql 弱口令检测 @time 2022-08-12 - @author lkq@bt.cn + @author lkq@aapanel.com ''' pass_info = public.ReadFile("/www/server/panel/config/weak_pass.txt") if not pass_info: return True, 'Risk-free' diff --git a/class/safe_warning/sw_mysql_port.py b/class/safe_warning/sw_mysql_port.py index 06ac8688..f8b9637a 100644 --- a/class/safe_warning/sw_mysql_port.py +++ b/class/safe_warning/sw_mysql_port.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_mysql_priv.py b/class/safe_warning/sw_mysql_priv.py index 9b529c17..c76a584d 100644 --- a/class/safe_warning/sw_mysql_priv.py +++ b/class/safe_warning/sw_mysql_priv.py @@ -1,9 +1,9 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: linxiao # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_nginx_server.py b/class/safe_warning/sw_nginx_server.py index 13ba7615..969d765b 100644 --- a/class/safe_warning/sw_nginx_server.py +++ b/class/safe_warning/sw_nginx_server.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_panel_control.py b/class/safe_warning/sw_panel_control.py index 8de58d04..dc600fb7 100644 --- a/class/safe_warning/sw_panel_control.py +++ b/class/safe_warning/sw_panel_control.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -28,7 +28,7 @@ def check_run(): ''' @name 面板未开启监控 @time 2022-08-12 - @author lkq@bt.cn + @author lkq@aapanel.com ''' global _tips send_type = "" diff --git a/class/safe_warning/sw_panel_pass.py b/class/safe_warning/sw_panel_pass.py index 0fe48580..ec111b2c 100644 --- a/class/safe_warning/sw_panel_pass.py +++ b/class/safe_warning/sw_panel_pass.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_panel_path.py b/class/safe_warning/sw_panel_path.py index 5a1b52fc..e57fdc33 100644 --- a/class/safe_warning/sw_panel_path.py +++ b/class/safe_warning/sw_panel_path.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_panel_port.py b/class/safe_warning/sw_panel_port.py index 94129830..6a346b06 100644 --- a/class/safe_warning/sw_panel_port.py +++ b/class/safe_warning/sw_panel_port.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_panel_swing.py b/class/safe_warning/sw_panel_swing.py index 0d8ddb3f..e166914b 100644 --- a/class/safe_warning/sw_panel_swing.py +++ b/class/safe_warning/sw_panel_swing.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -28,7 +28,7 @@ def check_run(): ''' @name 面板登录告警是否开启 @time 2022-08-12 - @author lkq@bt.cn + @author lkq@aapanel.com ''' send_type = "" tip_files = ['panel_login_send.pl','login_send_type.pl','login_send_mail.pl','login_send_dingding.pl'] diff --git a/class/safe_warning/sw_php_disable_functions.py b/class/safe_warning/sw_php_disable_functions.py index 4e404ced..b4ffed7f 100644 --- a/class/safe_warning/sw_php_disable_functions.py +++ b/class/safe_warning/sw_php_disable_functions.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_php_expose.py b/class/safe_warning/sw_php_expose.py index de6d8932..7272dd11 100644 --- a/class/safe_warning/sw_php_expose.py +++ b/class/safe_warning/sw_php_expose.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_ping.py b/class/safe_warning/sw_ping.py index dc23d239..358cf7ad 100644 --- a/class/safe_warning/sw_ping.py +++ b/class/safe_warning/sw_ping.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_pingin.py b/class/safe_warning/sw_pingin.py index 5e0bb2dd..e6974f29 100644 --- a/class/safe_warning/sw_pingin.py +++ b/class/safe_warning/sw_pingin.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_redis_pass.py b/class/safe_warning/sw_redis_pass.py index 11ad179a..6c29b3ba 100644 --- a/class/safe_warning/sw_redis_pass.py +++ b/class/safe_warning/sw_redis_pass.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_redis_port.py b/class/safe_warning/sw_redis_port.py index f9372705..ab838433 100644 --- a/class/safe_warning/sw_redis_port.py +++ b/class/safe_warning/sw_redis_port.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_site_spath.py b/class/safe_warning/sw_site_spath.py index 152e74aa..cdc34c97 100644 --- a/class/safe_warning/sw_site_spath.py +++ b/class/safe_warning/sw_site_spath.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_site_ssl.py b/class/safe_warning/sw_site_ssl.py index caf5f0fc..1c52f7a3 100644 --- a/class/safe_warning/sw_site_ssl.py +++ b/class/safe_warning/sw_site_ssl.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_site_ssl_expire.py b/class/safe_warning/sw_site_ssl_expire.py index 34942f17..bbc2b3ee 100644 --- a/class/safe_warning/sw_site_ssl_expire.py +++ b/class/safe_warning/sw_site_ssl_expire.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_ssh_clientalive.py b/class/safe_warning/sw_ssh_clientalive.py index 84862d1d..51fe207e 100644 --- a/class/safe_warning/sw_ssh_clientalive.py +++ b/class/safe_warning/sw_ssh_clientalive.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -40,8 +40,8 @@ def check_run(): try: info_data=public.ReadFile('/etc/ssh/sshd_config') if info_data: - if re.search('ClientAliveInterval\s+\d+',info_data): - clientalive=re.findall('ClientAliveInterval\s+\d+',info_data)[0] + if re.search(r'ClientAliveInterval\s+\d+',info_data): + clientalive=re.findall(r'ClientAliveInterval\s+\d+',info_data)[0] #clientalive 需要大于600 小于900 if int(clientalive.split(' ')[1]) >= 600 and int(clientalive.split(' ')[1]) <= 900: return True,'Rick-free' diff --git a/class/safe_warning/sw_ssh_forward.py b/class/safe_warning/sw_ssh_forward.py index 90b76e8e..47c913e7 100644 --- a/class/safe_warning/sw_ssh_forward.py +++ b/class/safe_warning/sw_ssh_forward.py @@ -20,7 +20,7 @@ def check_run(): if not os.path.exists(conf): return True, 'Risk-free' result = public.ReadFile(conf) - rep = '.*?X11Forwarding\s*?yes' + rep = r'.*?X11Forwarding\s*?yes' tmp = re.search(rep, result) if tmp: if tmp.group()[0] == '#': diff --git a/class/safe_warning/sw_ssh_maxauth.py b/class/safe_warning/sw_ssh_maxauth.py index 5b3a03e1..65f2f40a 100644 --- a/class/safe_warning/sw_ssh_maxauth.py +++ b/class/safe_warning/sw_ssh_maxauth.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- @@ -40,8 +40,8 @@ def check_run(): try: info_data = public.ReadFile('/etc/ssh/sshd_config') if info_data: - if re.search('MaxAuthTries\s+\d+', info_data): - maxauth = re.findall('MaxAuthTries\s+\d+', info_data)[0] + if re.search(r'MaxAuthTries\s+\d+', info_data): + maxauth = re.findall(r'MaxAuthTries\s+\d+', info_data)[0] # max 需要大于3 小于6 if int(maxauth.split(' ')[1]) >= 3 and int(maxauth.split(' ')[1]) <= 6: return True, 'Rick-free' diff --git a/class/safe_warning/sw_ssh_minclass.py b/class/safe_warning/sw_ssh_minclass.py index c7ef2a58..f7790a9d 100644 --- a/class/safe_warning/sw_ssh_minclass.py +++ b/class/safe_warning/sw_ssh_minclass.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -34,7 +34,7 @@ def check_run(): p_file = '/etc/security/pwquality.conf' p_body = public.readFile(p_file) if not p_body: return True, 'Risk-free' - tmp = re.findall("\s*minclass\s+=\s+(.+)", p_body, re.M) + tmp = re.findall(r"\s*minclass\s+=\s+(.+)", p_body, re.M) if not tmp: return True, 'Risk-free' minlen = tmp[0].strip() if int(minlen) <3: diff --git a/class/safe_warning/sw_ssh_notpass.py b/class/safe_warning/sw_ssh_notpass.py index 512f9fc9..07257536 100644 --- a/class/safe_warning/sw_ssh_notpass.py +++ b/class/safe_warning/sw_ssh_notpass.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: lkq +# Author: lkq # ------------------------------------------------------------------- # Time: 2022-08-10 # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_ssh_passmax.py b/class/safe_warning/sw_ssh_passmax.py index ece46e5d..a1f008a0 100644 --- a/class/safe_warning/sw_ssh_passmax.py +++ b/class/safe_warning/sw_ssh_passmax.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -34,7 +34,7 @@ def check_run(): p_file = '/etc/login.defs' p_body = public.readFile(p_file) if not p_body: return True, 'Risk-free' - tmp = re.findall("\nPASS_MAX_DAYS\s+(.+)", p_body, re.M) + tmp = re.findall("\nPASS_MAX_DAYS\\s+(.+)", p_body, re.M) if not tmp: return True, 'Risk-free' maxdays = tmp[0].strip() #60-180之间 diff --git a/class/safe_warning/sw_ssh_passmin.py b/class/safe_warning/sw_ssh_passmin.py index 32347915..f7c0465a 100644 --- a/class/safe_warning/sw_ssh_passmin.py +++ b/class/safe_warning/sw_ssh_passmin.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -34,7 +34,7 @@ def check_run(): p_file = '/etc/login.defs' p_body = public.readFile(p_file) if not p_body: return True, 'Risk-free' - tmp = re.findall("\nPASS_MIN_DAYS\s+(.+)", p_body, re.M) + tmp = re.findall("\nPASS_MIN_DAYS\\s+(.+)", p_body, re.M) if not tmp: return True, 'Risk-free' maxdays = tmp[0].strip() #7-14 diff --git a/class/safe_warning/sw_ssh_port.py b/class/safe_warning/sw_ssh_port.py index 99aafbaf..6e0ef578 100644 --- a/class/safe_warning/sw_ssh_port.py +++ b/class/safe_warning/sw_ssh_port.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -49,7 +49,7 @@ def check_run(): version = public.readFile('/etc/redhat-release') if not version: - version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace('\l','').strip() + version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace(r'\l','').strip() else: version = version.replace('release ','').replace('Linux','').replace('(Core)','').strip() diff --git a/class/safe_warning/sw_ssh_root.py b/class/safe_warning/sw_ssh_root.py index 90da797d..e3898507 100644 --- a/class/safe_warning/sw_ssh_root.py +++ b/class/safe_warning/sw_ssh_root.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -36,7 +36,7 @@ def check_run(): try: info_data = public.ReadFile('/etc/ssh/sshd_config') if info_data: - if re.search('PermitRootLogin\s+no', info_data): + if re.search(r'PermitRootLogin\s+no', info_data): return True, 'Risk-free' else: return True, 'Risk-free' diff --git a/class/safe_warning/sw_ssh_security.py b/class/safe_warning/sw_ssh_security.py index d845af29..93bb76d9 100644 --- a/class/safe_warning/sw_ssh_security.py +++ b/class/safe_warning/sw_ssh_security.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- @@ -35,7 +35,7 @@ def check_run(): p_file = '/etc/security/pwquality.conf' p_body = public.readFile(p_file) if not p_body: return True, 'Risk-free' - tmp = re.findall("\s*minlen\s+=\s+(.+)", p_body, re.M) + tmp = re.findall(r"\s*minlen\s+=\s+(.+)", p_body, re.M) if not tmp: return True, 'Risk-free' minlen = tmp[0].strip() if int(minlen) < 9: diff --git a/class/safe_warning/sw_umask.py b/class/safe_warning/sw_umask.py index 6393083e..e1ce9284 100644 --- a/class/safe_warning/sw_umask.py +++ b/class/safe_warning/sw_umask.py @@ -1,11 +1,11 @@ #!/usr/bin/python # coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/safe_warning/sw_waf_install.py b/class/safe_warning/sw_waf_install.py index d1ea4cdd..c5008946 100644 --- a/class/safe_warning/sw_waf_install.py +++ b/class/safe_warning/sw_waf_install.py @@ -1,11 +1,11 @@ #!/usr/bin/python #coding: utf-8 # ------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel # ------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # ------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang # ------------------------------------------------------------------- # ------------------------------------------------------------------- diff --git a/class/san_baseline.py b/class/san_baseline.py index 0124e45a..01af82e3 100644 --- a/class/san_baseline.py +++ b/class/san_baseline.py @@ -327,7 +327,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s expose_php的值修改为Off中修改" % (php_path + i + '/etc/php.ini'), "repair": "expose_php = Off", "rule": [ - {"re": "\nexpose_php\s*=\s*(\w+)", "check": {"type": "string", "value": ['Off']}}] + {"re": "\nexpose_php\\s*=\\s*(\\w+)", "check": {"type": "string", "value": ['Off']}}] } if not self.check_san_baseline(php_data): ret.append(php_data) @@ -354,7 +354,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s 中 disable_functions= 修改成如下:" % (php_path + i + '/etc/php.ini'), "repair": "disable_functions = passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv", "rule": [ - {"re": "\ndisable_functions\s?=\s?(.+)", "check": {"type": "string", "value": [ + {"re": "\ndisable_functions\\s?=\\s?(.+)", "check": {"type": "string", "value": [ 'passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv']}}] } if not self.check_san_baseline(php_data): @@ -402,7 +402,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s expose_php的值修改为Off中修改" % (php_path + i + '/etc/php.ini'), "repair": "expose_php = Off", "rule": [ - {"re": "\nexpose_php\s*=\s*(\w+)", "check": {"type": "string", "value": ['Off']}}] + {"re": "\nexpose_php\\s*=\\s*(\\w+)", "check": {"type": "string", "value": ['Off']}}] } if not self.check_san_baseline(php_data): ret.append(php_data) @@ -422,7 +422,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s 中 disable_functions= 修改成如下:" % (php_path + i + '/etc/php.ini'), "repair": "disable_functions = passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv", "rule": [ - {"re": "\ndisable_functions\s?=\s?(.+)", "check": {"type": "string", "value": [ + {"re": "\ndisable_functions\\s?=\\s?(.+)", "check": {"type": "string", "value": [ 'passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv']}}] } if not self.check_san_baseline(php_data): @@ -460,7 +460,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s 中的监听IP设置为127.0.0.1 例如" % ('/www/server/redis/redis.conf'), "repair": "bind 127.0.0.1", "rule": [ - {"re": "\nbind\s*(.+)", "check": {"type": "string", "value": ['0.0.0.0']}}] + {"re": "\nbind\\s*(.+)", "check": {"type": "string", "value": ['0.0.0.0']}}] } if self.check_san_baseline(redis_server_ip): ret.append(redis_server_ip) @@ -478,7 +478,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s 中的为未设置密码 例如" % ('/www/server/redis/redis.conf'), "repair": "requirepass requirepassQWERQQQQQQQ", "rule": [ - {"re": "\nrequirepass\s*(.+)", "check": {"type": "string", "value": []}}] + {"re": "\nrequirepass\\s*(.+)", "check": {"type": "string", "value": []}}] } if not self.check_san_baseline(redis_server_not_pass): ret.append(redis_server_not_pass) @@ -496,7 +496,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s 中requirepass 设置为强密码" % ('/www/server/redis/redis.conf'), "repair": "requirepass requirepassQWERQQQQQQQ", "rule": [ - {"re": "\nrequirepass\s*(.+)", "check": {"type": "string", "value": ['123456', 'admin', 'damin888']}}] + {"re": "\nrequirepass\\s*(.+)", "check": {"type": "string", "value": ['123456', 'admin', 'damin888']}}] } if not self.check_san_baseline(redis_server_pass): ret.append(redis_server_pass) @@ -534,7 +534,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s 中的监听IP设置为127.0.0.1 例如" % ('/etc/init.d/memcached'), "repair": "IP=127.0.0.1", "rule": [ - {"re": "\nIP\s?=\s?(.+)", "check": {"type": "string", "value": ['0.0.0.0']}}] + {"re": "\nIP\\s?=\\s?(.+)", "check": {"type": "string", "value": ['0.0.0.0']}}] } if self.check_san_baseline(self.__repair['46']): ret.append(self.__repair['46']) @@ -629,7 +629,7 @@ class san_baseline: "name": "存在非root 的管理员用户(危险)", "ps": "除root以为的其他的UID为0的用户的应该删除。或者为其分配新的UID", "cmd": '''cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'|grep -v '^root$' ''', - "find": {"re": "\w+"} + "find": {"re": r"\w+"} } if not self.check_san_baseline(get_root_0): result.append(get_root_0) @@ -845,7 +845,7 @@ class san_baseline: tls = [] if os.path.exists('/www/server/panel/vhost/nginx/%s.conf' % siteName): ret = public.ReadFile('/www/server/panel/vhost/nginx/%s.conf' % siteName) - valuse = re.findall('ssl_protocols\s+(.+)', ret) + valuse = re.findall(r'ssl_protocols\s+(.+)', ret) print(valuse) if not valuse: return tls if not valuse[0]: return tls @@ -1071,7 +1071,7 @@ class san_baseline: "Suggestions": "加固建议, 在%s expose_php的值修改为Off中修改" % ('/www/server/nginx/conf/nginx.conf'), "repair": "expose_php = Off", "rule": [ - {"re": "server_tokens\s*(.+)", "check": {"type": "string", "value": ['off;']}}] + {"re": r"server_tokens\s*(.+)", "check": {"type": "string", "value": ['off;']}}] } if not self.check_san_baseline(Nginx_Get_version): ret.append(Nginx_Get_version) diff --git a/class/send_mail.py b/class/send_mail.py index 8e2180b6..2c930a60 100644 --- a/class/send_mail.py +++ b/class/send_mail.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: lkqiang +# | Author: lkqiang # +------------------------------------------------------------------- # +-------------------------------------------------------------------- # | 宝塔内置消息通道 @@ -181,9 +181,9 @@ class send_mail: opener = urllib2.urlopen(url) m_str = opener.read() if isinstance(m_str, bytes): - ipaddress = re.search('\d+.\d+.\d+.\d+', m_str.decode('utf-8')).group(0) + ipaddress = re.search(r'\d+.\d+.\d+.\d+', m_str.decode('utf-8')).group(0) else: - ipaddress = re.search('\d+.\d+.\d+.\d+', m_str).group(0) + ipaddress = re.search(r'\d+.\d+.\d+.\d+', m_str).group(0) public.WriteFile(filename, ipaddress) c_ip = public.check_ip(ipaddress) if not c_ip: diff --git a/class/send_to_user.py b/class/send_to_user.py index 3dcf9be6..f1daeb74 100644 --- a/class/send_to_user.py +++ b/class/send_to_user.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: lkq <1249648969@qq.com> +# | Author: lkq <1249648969@aapanel.com> # +------------------------------------------------------------------- # +-------------------------------------------------------------------- # | 告警消息队列 diff --git a/class/setPanelLets.py b/class/setPanelLets.py index 971baa51..859a12ea 100644 --- a/class/setPanelLets.py +++ b/class/setPanelLets.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: zhwen +# | Author: zhwen # +------------------------------------------------------------------- import os os.chdir("/www/server/panel") diff --git a/class/sewer/cli.py b/class/sewer/cli.py index 542f7b73..5d5d0c9f 100644 --- a/class/sewer/cli.py +++ b/class/sewer/cli.py @@ -8,7 +8,7 @@ from .config import ACME_DIRECTORY_URL_STAGING, ACME_DIRECTORY_URL_PRODUCTION def main(): - """ + r""" Usage: 1. To get a new certificate: CLOUDFLARE_EMAIL=example@example.com \ @@ -29,7 +29,7 @@ def main(): """ parser = argparse.ArgumentParser( prog="sewer", - description="""Sewer is a Let's Encrypt(ACME) client. + description=r"""Sewer is a Let's Encrypt(ACME) client. Example usage:: CLOUDFLARE_EMAIL=example@example.com \ CLOUDFLARE_API_KEY=api-key \ diff --git a/class/site_dir_auth.py b/class/site_dir_auth.py index c861b5d2..475cb078 100644 --- a/class/site_dir_auth.py +++ b/class/site_dir_auth.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zhwen +# Author: zhwen #------------------------------------------------------------------- #------------------------------ @@ -126,13 +126,13 @@ class SiteDirAuth: try: conf = public.readFile(self.setup_path + '/panel/vhost/'+public.get_webserver()+'/'+siteName+'.conf'); if public.get_webserver() == 'nginx': - rep = "enable-php-(\w{2,5})\.conf" + rep = r"enable-php-(\w{2,5})\.conf" tmp = re.search(rep,conf) if not tmp: - rep = "enable-php-(\d+-wpfastcgi).conf" + rep = r"enable-php-(\d+-wpfastcgi).conf" re.search(rep, conf) else: - rep = "php-cgi-(\w{2,5})\.sock" + rep = r"php-cgi-(\w{2,5})\.sock" tmp = re.search(rep,conf).groups() if tmp: return tmp[0] @@ -216,24 +216,24 @@ class SiteDirAuth: conf = public.readFile(file) if i == "apache": if act == "create": - rep = "IncludeOptional.*\/dir_auth\/.*conf(\n|.)+<\/VirtualHost>" + rep = "IncludeOptional.*\\/dir_auth\\/.*conf(\n|.)+<\\/VirtualHost>" rep1 = "" if not re.search(rep, conf): conf = conf.replace(rep1, "\n\t#Directory protection rules, do not manually delete\n\tIncludeOptional {}\n".format( dir_auth_file)) else: - rep = "\n*#Directory protection rules, do not manually delete\n+\s+IncludeOptional[\s\w\/\.\*]+" + rep = "\n*#Directory protection rules, do not manually delete\n+\\s+IncludeOptional[\\s\\w\\/\\.\\*]+" conf = re.sub(rep, '', conf) public.writeFile(file, conf) else: if act == "create": - rep = "#SSL-END(\n|.)+include.*\/dir_auth\/.*conf;" + rep = "#SSL-END(\n|.)+include.*\\/dir_auth\\/.*conf;" rep1 = "#SSL-END" if not re.search(rep,conf): conf = conf.replace(rep1, rep1 + "\n\t#Directory protection rules, do not manually delete\n\tinclude {};".format(dir_auth_file)) else: - rep = "\n*#Directory protection rules, do not manually delete\n+\s+include[\s\w\/\.\*]+;" + rep = "\n*#Directory protection rules, do not manually delete\n+\\s+include[\\s\\w\\/\\.\\*]+;" conf = re.sub(rep, '', conf) public.writeFile(file, conf) @@ -338,7 +338,7 @@ class SiteDirAuth: password = get.password.strip() if len(password) < 3: return public.returnMsg(False, 'Password cannot be less than 3 characters') - if re.search('\s', password): + if re.search(r'\s', password): return public.returnMsg(False, 'Password cannot contain spaces') values['password'] = password @@ -348,7 +348,7 @@ class SiteDirAuth: username = get.username.strip() if len(username) < 3: return public.returnMsg(False, 'Username cannot be less than 3 characters') - if re.search('\s', username): + if re.search(r'\s', username): return public.returnMsg(False, 'Username cannot contain spaces') values['username'] = username @@ -358,10 +358,10 @@ class SiteDirAuth: name = get.name.strip() if len(name) < 3: return public.returnMsg(False, 'Name cannot be less than 3 characters') - if re.search('\s', name): + if re.search(r'\s', name): return public.returnMsg(False, 'Name cannot contain spaces') - if re.search('[\/\"\'\!@#$%^&*()+={}\[\]\:\;\?><,./\\\]+', name): + if re.search('[\\/\"\'\\!@#$%^&*()+={}\\[\\]\\:\\;\\?><,./\\\\]+', name): return public.returnMsg(False, 'Name format must be [ aaa_bbb ]') values['name'] = name - return public.returnMsg(True, values) \ No newline at end of file + return public.returnMsg(True, values) diff --git a/class/sites.py b/class/sites.py index 1e3b3bf6..b25088da 100644 --- a/class/sites.py +++ b/class/sites.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ @@ -42,7 +42,7 @@ class sites: return self._generate_nginx_conf(pdata['siteName']) - ''' + r''' server { listen 80; @@ -142,7 +142,7 @@ server #检查域名格式是否不正确 def domain_format(self,domains): if type(domains) == str: domains = [domains] - reg = "^([\w\-\*]{1,100}\.){1,8}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$"; + reg = r"^([\w\-\*]{1,100}\.){1,8}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$"; for domain in domains: if not re.match(reg,domain): return domain return False diff --git a/class/ssh_authentication.py b/class/ssh_authentication.py index 4b77fc48..baaf87d1 100644 --- a/class/ssh_authentication.py +++ b/class/ssh_authentication.py @@ -2,7 +2,7 @@ # +------------------------------------------------------------------- # | version :1.0 # +------------------------------------------------------------------- -# | Author: 梁凯强 <1249648969@qq.com> +# | Author: 梁凯强 <1249648969@aapanel.com> # +------------------------------------------------------------------- # | SSH 双因子认证 # +-------------------------------------------------------------------- @@ -150,7 +150,7 @@ class ssh_authentication: #设置SSH应答模式 def set_ssh_login_user(self): - ssh_password = '\nChallengeResponseAuthentication\s\w+' + ssh_password = '\nChallengeResponseAuthentication\\s\\w+' file = public.readFile(self.__SSH_CONFIG) if isinstance(file, str): if len(re.findall(ssh_password, file)) == 0: @@ -164,7 +164,7 @@ class ssh_authentication: #关闭SSH应答模式 def close_ssh_login_user(self): file = public.readFile(self.__SSH_CONFIG) - ssh_password = '\nChallengeResponseAuthentication\s\w+' + ssh_password = '\nChallengeResponseAuthentication\\s\\w+' if isinstance(file, str): file_result = re.sub(ssh_password, '\nChallengeResponseAuthentication no', file) self.wirte(self.__SSH_CONFIG, file_result) @@ -174,7 +174,7 @@ class ssh_authentication: #查看SSH应答模式 def check_ssh_login_user(self): file = public.readFile(self.__SSH_CONFIG) - ssh_password = '\nChallengeResponseAuthentication\s\w+' + ssh_password = '\nChallengeResponseAuthentication\\s\\w+' if isinstance(file, str): ret = re.findall(ssh_password, file) if not ret: @@ -230,7 +230,7 @@ class ssh_authentication: 无参数传递 ''' file = public.readFile(self.__SSH_CONFIG) - ssh_password = '\nPasswordAuthentication\s\w+' + ssh_password = '\nPasswordAuthentication\\s\\w+' if isinstance(file, str): ret = re.findall(ssh_password, file) if not ret: diff --git a/class/ssh_security.py b/class/ssh_security.py index 7453da1a..ab7afb9f 100644 --- a/class/ssh_security.py +++ b/class/ssh_security.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: lkqiang +# Author: lkqiang #------------------------------------------------------------------- # SSH 安全类 #------------------------------ @@ -173,7 +173,7 @@ class ssh_security: def get_ssh_port(self): conf = public.readFile(self.__SSH_CONFIG) if not conf: conf = '' - rep = "#*Port\s+([0-9]+)\s*\n" + rep = r"#*Port\s+([0-9]+)\s*\n" tmp1 = re.search(rep,conf) port = '22' if tmp1: @@ -304,7 +304,7 @@ class ssh_security: def login_last(self): self.check_files() data=public.ExecShell('last -n 50') - data=re.findall("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0]) + data=re.findall(r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0]) if data>=1: data2=list(set(data)) for i in data2: @@ -316,7 +316,7 @@ class ssh_security: #获取ROOT当前登陆的IP def get_ip(self): data = public.ExecShell(''' who am i |awk ' {print $5 }' ''') - data = re.findall("(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0]) + data = re.findall(r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0]) return data def get_logs(self, get): @@ -421,7 +421,7 @@ class ssh_security: #监控状态 def get_jian(self,get): data = public.ReadFile(self.return_profile()) - #if re.search('{}\/www\/server\/panel\/class\/ssh_security.py\s+login'.format(".*python\s+"), data): + #if re.search(r'{}\/www\/server\/panel\/class\/ssh_security.py\s+login'.format(r".*python\s+"), data): if re.search('/www/server/panel/class/ssh_security.py login', data): return public.returnMsg(True, '1') else: @@ -432,7 +432,7 @@ class ssh_security: 开启密码登陆 get: 无需传递参数 ''' - ssh_password = '\n#?PasswordAuthentication\s\w+' + ssh_password = r'\n#?PasswordAuthentication\s\w+' file = public.readFile(self.__SSH_CONFIG) if not file: return public.returnMsg(False,'ERROR: sshd config configuration file does not exist, cannot continue!') if len(re.findall(ssh_password, file)) == 0: @@ -459,13 +459,13 @@ class ssh_security: file = ['/root/.ssh/id_{}.pub'.format(s_type), '/root/.ssh/id_{}'.format(s_type)] for i in file: if os.path.exists(i): - public.ExecShell('sed -i "\~$(cat %s)~d" %s' % (file[0], authorized_keys)) + public.ExecShell(r'sed -i "\~$(cat %s)~d" %s' % (file[0], authorized_keys)) os.remove(i) os.system("ssh-keygen -t {s_type} -P '' -f /root/.ssh/id_{s_type} |echo y".format(s_type = s_type)) if os.path.exists(file[0]): public.ExecShell('cat %s >> %s && chmod 600 %s' % (file[0], authorized_keys, authorized_keys)) - rec = '\n#?RSAAuthentication\s\w+' - rec2 = '\n#?PubkeyAuthentication\s\w+' + rec = r'\n#?RSAAuthentication\s\w+' + rec2 = r'\n#?PubkeyAuthentication\s\w+' file = public.readFile(self.__SSH_CONFIG) if not file: return public.returnMsg(False, 'ERROR: sshd config configuration file does not exist, cannot continue!') @@ -474,7 +474,7 @@ class ssh_security: file_ssh = re.sub(rec, '\nRSAAuthentication yes', file) file_result = re.sub(rec2, '\nPubkeyAuthentication yes', file_ssh) if ssh == 'no': - ssh_password = '\n#?PasswordAuthentication\s\w+' + ssh_password = r'\n#?PasswordAuthentication\s\w+' if len(re.findall(ssh_password, file_result)) == 0: file_result = file_result + '\nPasswordAuthentication no' else: @@ -592,8 +592,8 @@ class ssh_security: 无需参数传递 ''' is_ssh_status=self.GetSshInfo() - rec = '\n\s*#?\s*RSAAuthentication\s+\w+' - rec2 = '\n\s*#?\s*PubkeyAuthentication\s+\w+' + rec = r'\n\s*#?\s*RSAAuthentication\s+\w+' + rec2 = r'\n\s*#?\s*PubkeyAuthentication\s+\w+' file = public.readFile(self.__SSH_CONFIG) if not file: return public.returnMsg(False,'错误:sshd_config配置文件不存在,无法继续!') file_ssh = re.sub(rec, '\nRSAAuthentication no', file) @@ -693,7 +693,7 @@ class ssh_security: 开启密码登陆 get: 无需传递参数 ''' - ssh_password = '\n\s*PermitRootLogin\s+\w+' + ssh_password = r'\n\s*PermitRootLogin\s+\w+' file = public.readFile(self.__SSH_CONFIG) if len(re.findall(ssh_password, file)) == 0: file_result = file + '\nPermitRootLogin no' @@ -710,7 +710,7 @@ class ssh_security: 无参数传递 ''' file = public.readFile(self.__SSH_CONFIG) - ssh_password = '\n#?PasswordAuthentication\s\w+' + ssh_password = r'\n#?PasswordAuthentication\s\w+' file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file) self.wirte(self.__SSH_CONFIG, file_result) self.restart_ssh() diff --git a/class/ssh_terminal.py b/class/ssh_terminal.py index fbae21a9..c4f6b270 100644 --- a/class/ssh_terminal.py +++ b/class/ssh_terminal.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import json import time @@ -434,7 +434,7 @@ class ssh_terminal: ''' version = public.readFile('/etc/redhat-release') if not version: - version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace('\l','').strip() + version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace(r'\l','').strip() else: version = version.replace('release ','').replace('Linux','').replace('(Core)','').strip() return version diff --git a/class/ssl_info.py b/class/ssl_info.py new file mode 100644 index 00000000..3dea7c7a --- /dev/null +++ b/class/ssl_info.py @@ -0,0 +1,220 @@ + +import os,sys,re,time,subprocess + +panelPath = '/www/server/panel/' +os.chdir(panelPath) + +sys.path.insert(0, panelPath + "class/") +import public +from datetime import date, datetime + +is_openssl = True +try: + import OpenSSL +except: + is_openssl = False + + + +class ssl_info: + + def __init__(self) -> None: + pass + + + def create_key(self,bits=2048): + """ + @name 创建RSA密钥 + @param bits 密钥长度 + """ + if is_openssl: + + key = OpenSSL.crypto.PKey() + key.generate_key(OpenSSL.crypto.TYPE_RSA, bits) + private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) + return private_key + else: + tmp_pk_file = "/tmp/private_key_{}.pem".format(int(time.time())) + cmd = ["openssl", "genpkey", "-algorithm", "RSA", "-out", tmp_pk_file, "-pkeyopt", f"rsa_keygen_bits:{bits}"] + subprocess.run(cmd, check=True) + with open(tmp_pk_file, "r") as f: + private_key = f.read() + try: + os.remove("private_key.pem") + except: + pass + return private_key + + def load_ssl_info_by_data(self, pem_data: str): + if not isinstance(pem_data, (str, bytes)): + return None + + if is_openssl: + return self.__get_cert_info(pem_data) + + # 使用命令行解析 + pem_file = "/tmp/fullchain_{}.pem".format(int(time.time())) + public.writeFile(pem_file, pem_data) + res = public.ExecShell("openssl x509 -in {} -noout -text".format(pem_file))[0] + try: + result = {} + issuer_match = re.search(r"Issuer: (.*)", res) + if issuer_match: + data = {} + issuer = issuer_match.group(1) + for key, val in re.findall(r"(\w+\s*)=([^,]+)", issuer): + data[key.strip()] = val + + if "CN" in data: + result["issuer"] = data['CN'] + if "O" in data: + s = data['O'].encode().decode('unicode_escape') + result["issuer"] = bytes(s, 'latin1').decode('utf-8') + + validity_match = re.search(r"Not After\s*:\s*(.*)", res) + if validity_match: + not_after = validity_match.group(1) + dt_after = datetime.strptime(not_after, "%b %d %H:%M:%S %Y %Z") + result['notAfter'] = dt_after.strftime("%Y-%m-%d %H:%M:%S") + result['endtime'] = (dt_after - datetime.now()).days + else: + result['endtime'] = 0 + + validity_match = re.search(r"Not Before\s*:\s*(.*)", res) + if validity_match: + not_befoer = validity_match.group(1) + dt_befoer = datetime.strptime(not_befoer, "%b %d %H:%M:%S %Y %Z") + result['notBefore'] = dt_befoer.strftime("%Y-%m-%d %H:%M:%S") + + subject_match = re.search(r"Subject: (.*)", res) + if subject_match: + subject = subject_match.group(1) + for key, val in re.findall(r"(\w+\s*)=([^,]+)", subject): + if key.strip() == 'CN': + s = val.encode().decode('unicode_escape') + result["subject"] = bytes(s, 'latin1').decode('utf-8') + # 取可选名称 + result['dns'] = [] + dns_match = re.findall(r"DNS:([^\s,]+)", res) + for dns in dns_match: + result['dns'].append(dns) + except: + result = None + + if os.path.exists(pem_file): + os.remove(pem_file) + return result + + def load_ssl_info(self,pem_file): + """ + @name 获取证书详情 + """ + if not os.path.exists(pem_file): + return None + + pem_data = public.readFile(pem_file) + if not pem_data: + return None + return self.load_ssl_info_by_data(pem_data) + + def __get_cert_info(self,pem_data): + """ + @name 通过python的openssl模块获取证书信息 + @param pem_data 证书内容 + """ + result = {} + try: + x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem_data) + except: # 证书格式可能是错的,就没有办法读取证书内容 + return None + + 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 + if 'subject' in result: + result['dns'].append(result['subject']) + else: + result['subject'] = result['dns'][0] + result['endtime'] = int(int(time.mktime(time.strptime(result['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) + return result + + # 转换时间 + 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 dump_pkcs12_new(self, key_pem=None, cert_pem=None, ca_pem=None, friendly_name=""): + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.serialization.pkcs12 import serialize_key_and_certificates + from cryptography.x509 import load_pem_x509_certificate + + private_key = serialization.load_pem_private_key( + key_pem.encode(), + password=None, # 如果私钥有密码,请在此处提供密码 + backend=default_backend() + ) + + cert = load_pem_x509_certificate((cert_pem + ca_pem).encode(), default_backend()) + + # 将证书和私钥组合成PKCS12格式的文件 + p12 = serialize_key_and_certificates( + name=friendly_name.encode() if friendly_name else None, + key=private_key, + cert=cert, + encryption_algorithm=serialization.NoEncryption(), + cas=[load_pem_x509_certificate(ca_pem.encode(), default_backend())] + ) + return p12 + +#class ssl: + + + + + + + + diff --git a/class/ssl_manage.py b/class/ssl_manage.py index 55d57f64..722f82cb 100644 --- a/class/ssl_manage.py +++ b/class/ssl_manage.py @@ -1,8 +1,8 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- # | Author: baozi # +------------------------------------------------------------------- diff --git a/class/system.py b/class/system.py index 303207dd..1063d413 100644 --- a/class/system.py +++ b/class/system.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 x3 +# | aaPanel x3 # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import psutil,time,os,public,re,sys try: @@ -61,7 +61,7 @@ class system: try: if os.path.exists(configFile): conf = public.readFile(configFile) - rep = "listen\s+([0-9]+)\s*;" + rep = r"listen\s+([0-9]+)\s*;" rtmp = re.search(rep,conf) if rtmp: phpport = rtmp.groups()[0] @@ -70,7 +70,7 @@ class system: if conf.find(self.setupPath + '/stop') == -1: pstatus = True configFile = self.setupPath + '/nginx/conf/enable-php.conf' conf = public.readFile(configFile) - rep = "php-cgi-([0-9]+)\.sock" + rep = r"php-cgi-([0-9]+)\.sock" rtmp = re.search(rep,conf) if rtmp: phpversion = rtmp.groups()[0] @@ -85,11 +85,11 @@ class system: try: if os.path.exists(configFile): conf = public.readFile(configFile) - rep = "php-cgi-([0-9]+)\.sock" + rep = r"php-cgi-([0-9]+)\.sock" rtmp = re.search(rep,conf) if rtmp: phpversion = rtmp.groups()[0] - rep = "Listen\s+([0-9]+)\s*\n" + rep = "Listen\\s+([0-9]+)\\s*\n" rtmp = re.search(rep,conf) if rtmp: phpport = rtmp.groups()[0] @@ -105,12 +105,12 @@ class system: try: if os.path.exists(configFile): conf = public.readFile('/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf') - rep = "/usr/local/lsws/lsphp(\d+)/bin/lsphp" + rep = r"/usr/local/lsws/lsphp(\d+)/bin/lsphp" rtmp = re.search(rep,conf) if rtmp: phpversion = rtmp.groups()[0] conf = public.readFile('/www/server/panel/vhost/openlitespeed/listen/888.conf') - rep = "address\s+\*\:(\d+)" + rep = r"address\s+\*\:(\d+)" rtmp = re.search(rep,conf) if rtmp: phpport = rtmp.groups()[0] @@ -206,13 +206,13 @@ class system: phpfpm = public.readFile(file) data = {} try: - rep = "upload_max_filesize\s*=\s*([0-9]+)M" + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" tmp = re.search(rep,phpini).groups() data['max'] = tmp[0] except: data['max'] = '50' try: - rep = "request_terminate_timeout\s*=\s*([0-9]+)\n" + rep = "request_terminate_timeout\\s*=\\s*([0-9]+)\n" tmp = re.search(rep,phpfpm).groups() data['maxTime'] = tmp[0] except: @@ -564,6 +564,7 @@ class system: networkInfo['downPackets'] = 0 networkInfo['upPackets'] = 0 networkIo_list = psutil.net_io_counters(pernic = True) + for net_key in networkIo_list.keys(): networkIo = networkIo_list[net_key][:4] up_key = "{}_up".format(net_key) @@ -623,7 +624,9 @@ class system: networkInfo['database_total'] = public.M('databases').count() networkInfo['system'] = self.GetSystemVersion() networkInfo['installed'] = self.CheckInstalled() + import panelSSL + networkInfo['user_info'] = panelSSL.panelSSL().GetUserInfo(None) networkInfo['up'] = round(float(networkInfo['up']),2) networkInfo['down'] = round(float(networkInfo['down']),2) @@ -683,7 +686,7 @@ class system: #取网络流量信息 import time; pnet = public.readFile('/proc/net/dev') - rep = '([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)' + rep = r'([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)' pnetall = re.findall(rep,pnet) networkInfo = {} networkInfo['upTotal'] = networkInfo['downTotal'] = networkInfo['up'] = networkInfo['down'] = networkInfo['downPackets'] = networkInfo['upPackets'] = 0 @@ -777,7 +780,7 @@ class system: if not os.path.exists(mypath): return False public.set_mode(mypath,644) mycnf = public.readFile(mypath) - tmp = re.findall('datadir\s*=\s*(.+)',mycnf) + tmp = re.findall(r'datadir\s*=\s*(.+)',mycnf) if not tmp: return False datadir = tmp[0] @@ -988,12 +991,12 @@ class system: #修复面板 def RepPanel(self,get): public.writeFile('data/js_random.pl','1') - public.ExecShell("wget --no-check-certificate -O update.sh " + public.get_url() + "/install/update6_en.sh && bash update.sh") + public.ExecShell("wget --no-check-certificate -O update.sh " + public.get_url() + "/install/update_7.x_en.sh && bash update.sh") self.ReWeb(None) return True #升级到专业版 def UpdatePro(self,get): - public.ExecShell("wget --no-check-certificate -O update.sh " + public.get_url() + "/install/update6_en.sh && bash update.sh") + public.ExecShell("wget --no-check-certificate -O update.sh " + public.get_url() + "/install/update_7.x_en.sh && bash update.sh") self.ReWeb(None) return True diff --git a/class/tomcat.py b/class/tomcat.py index 4456e8bf..81439c8a 100644 --- a/class/tomcat.py +++ b/class/tomcat.py @@ -1,11 +1,11 @@ #!/usr/bin/env python #coding:utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- #------------------------------ diff --git a/class/tools.py b/class/tools.py index d925a170..e3082a9b 100644 --- a/class/tools.py +++ b/class/tools.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- #------------------------------ @@ -72,7 +72,7 @@ def set_panel_pwd(password,ncli = False): #设置数据库目录 def set_mysql_dir(path): - mysql_dir = '''#!/bin/bash + mysql_dir = r'''#!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH oldDir=`cat /etc/my.cnf |grep 'datadir'|awk '{print $3}'` @@ -459,7 +459,7 @@ def bt_cli(): return import re - rep = "^[\w@\._]+$" + rep = r"^[\w@\._]+$" if not re.match(rep, input_mysql): print(public.get_msg_gettext('|-ERROR, password cannot contain special characters')) return diff --git a/class/userRegister.py b/class/userRegister.py new file mode 100644 index 00000000..bcf51816 --- /dev/null +++ b/class/userRegister.py @@ -0,0 +1,174 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: lotk +# +------------------------------------------------------------------- + +import public,os,sys,db,time,json,re +from BTPanel import session,cache,json_header +from flask import request,redirect,g + +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 userRegister: + # __official_url = 'https://dev.aapanel.com' + # # __official_url = 'https://www.aapanel.com' + + def toRegister(self, post): + try: + # 参数检测 + if not hasattr(post, 'email') or not hasattr(post, 'password'): + return public.return_msg_gettext(False, 'User email or password cannot be empty!') + post.email = post.email.strip() + post.password = post.password.strip() + # 检测 email + emailformat = re.compile(r'[a-zA-Z0-9.-_+%]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+') + if not emailformat.search(post.email): + return public.return_msg_gettext(False, 'Please enter your vaild email') + + post.email = self.en_code_rsa(post.email) + post.password = self.en_code_rsa(post.password) + + params = {} + params['email'] = post.email + params['password'] = post.password + + env_info = self.fetch_env_info() + params['environment_info'] = json.dumps(env_info) + + params['install_code'] = env_info['install_code'] + except Exception as e: + return public.return_msg_gettext(False, "error info : {}".format(e)) + + + # 调用官网注册 + try: + + # public.print_log("传参2 {}".format(params)) + # sUrl = 'http://dev.aapanel.com/api/user/register_on_panel' + sUrl = 'https://www.aapanel.com/api/user/register_on_panel' + aa = public.httpPost(sUrl, params, timeout=60) + data = json.loads(aa) + + + if not data['success']: + if data['res'].startswith("[code: 400] Account is exists!"): + data['res'] = '[code: 400] Account is exists!' + + return public.return_msg_gettext(False, data['res']) + except Exception as e: + return public.return_msg_gettext(False, "error info2 : {}".format(e)) + + # 注册成功调用登录 + try: + self.getToken(post) + return public.return_msg_gettext(True, "Register successfully") + except Exception as e: + return public.return_msg_gettext(False, "error info6 : {}".format(e)) + + + + + # 绑定登录 + def getToken(self, get): + + rtmp = "" + data = {} + data['identification'] = get.email + data['password'] = get.password + data['from_panel'] = self.en_code_rsa('1') # 1 代表从面板登录 + try: + # APIURL1 = 'http://dev.aapanel.com/api/user/login' + APIURL1 = 'https://www.aapanel.com/api/user/login' + rtmp = public.httpPost(APIURL1, 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('data/userInfo.json', json.dumps(userinfo)) + + session['focre_cloud'] = True + return public.return_msg_gettext(True, 'Bind successfully') + + else: + return public.return_msg_gettext(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.return_msg_gettext(False, '%s
                                    %s' % ( + public.get_msg_gettext('Failed to connect server!'), str(rtmp))) + + + def get_cpuname(self): + return public.ExecShell("cat /proc/cpuinfo|grep 'model name'|cut -d : -f2")[0].strip() + + + def fetch_env_info(self,): + + # 获取机器码 + try: + userPath = 'data/userInfo.json' + if not os.path.exists(userPath): + s1 = public.get_mac_address() + public.get_hostname() + s2 = self.get_cpuname() + serverid = public.md5(s1) + public.md5(s2) + data1 = {} + data1['server_id'] = serverid + public.writeFile(userPath, json.dumps(data1)) + + tmp = public.readFile(userPath) + if len(tmp) < 2: + tmp = '{}' + data = json.loads(tmp) + + if not 'server_id' in data: + s1 = public.get_mac_address() + public.get_hostname() + s2 = self.get_cpuname() + serverid = public.md5(s1) + public.md5(s2) + data['server_id'] = serverid + public.writeFile(userPath, json.dumps(data)) + + server_id = data['server_id'] + except Exception as e: + return {} + + return {'ip': public.GetLocalIp(), + 'is_ipv6': 0, + 'os': public.get_platform(), + 'mac': public.get_mac_address(), + 'hdid': public.fetch_disk_SN(), + 'ramid': public.get_memory(), + 'cpuid': public.fetch_cpu_ID(), + 'server_name': public.get_hostname(), + 'install_code': server_id + } + + + # RSA 加密 + def en_code_rsa(self, data): + pk = public.readFile('data/public.key') + 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') + diff --git a/class/userlogin.py b/class/userlogin.py index d98fc168..9da8e1a6 100755 --- a/class/userlogin.py +++ b/class/userlogin.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import public,os,sys,db,time,json,re @@ -13,123 +13,9 @@ from flask import request,redirect,g class userlogin: limit_expire_time = 0 - def request_post(self,post): - if not hasattr(post, 'username') or not hasattr(post, 'password'): - return public.returnJson(False,'User name or password cannot be empty!'),json_header - self.error_num(False) - if self.limit_address('?') < 1: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header - post.username = post.username.strip() - format_error = 'Parameter format error' - - # 核验用户名密码格式 - post.username = public.rsa_decrypt(post.username) - - if len(post.username) != 32: - return public.returnMsg(False,format_error+"1"),json_header - post.password = public.rsa_decrypt(post.password) - if len(post.password) != 32: - return public.returnMsg(False,format_error+"2"),json_header - - if not re.match(r"^\w+$",post.username): return public.return_msg_gettext(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header - if not re.match(r"^\w+$",post.password): return public.return_msg_gettext(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header - last_login_token = session.get('last_login_token',None) - if not last_login_token: - public.WriteLog('TYPE_LOGIN','LOGIN_ERR_CODE',('****','****',public.GetClientIp())) - return public.returnJson(False,"Verification failed, please refresh the page and log in again!"),json_header - - public.chdck_salt() - sql = db.Sql() - userInfo = None - user_plugin_file = '{}/users_main.py'.format(public.get_plugin_path('users')) - if os.path.exists(user_plugin_file): - user_list = sql.table('users').field('id,username,password,salt').select() - for u_info in user_list: - if public.md5(public.md5(u_info['username'] + last_login_token)) == post.username: - userInfo = u_info - else: - userInfo = sql.table('users').where('id=?',1).field('id,username,password,salt').find() - - - if 'code' in session: - if session['code'] and not 'is_verify_password' in session: - if not hasattr(post, 'code'): return public.returnJson(False,'Verification code can not be empty!'),json_header - if not re.match(r"^\w+$",post.code): return public.returnJson(False,'Verification code is incorrect, please try again!'),json_header - if not public.checkCode(post.code): - public.write_log_gettext('Login','Verification code is incorrect, Username:{}, Verification Code:{}, Login IP:{}',('****','****',public.GetClientIp())) - return public.returnJson(False,'Verification code is incorrect, please try again!'),json_header - try: - if not userInfo: - public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp())) - num = self.limit_address('+') - if not num: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header - return public.returnJson(False,'wrong user name or password,please refresh the page and try again,You can retry {} more times'.format(num)),json_header - - if userInfo and not userInfo['salt']: - public.chdck_salt() - userInfo = sql.table('users').where('id=?',(userInfo['id'],)).field('id,username,password,salt').find() - - password = public.md5(post.password.strip() + userInfo['salt']) - s_username = public.md5(public.md5(userInfo['username'] + last_login_token)) - if s_username != post.username or userInfo['password'] != password: - public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) - num = self.limit_address('+') - if not num: return public.returnJson(False,'You failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header - return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header - _key_file = "/www/server/panel/data/two_step_auth.txt" - - # 密码过期检测 - if sys.path[0] != 'class/': sys.path.insert(0,'class/') - if not public.password_expire_check(): - session['password_expire'] = True - - #登陆告警 - #public.run_thread(public.login_send_body,("账号密码",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) - # public.login_send_body("账号密码",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))) - if hasattr(post,'vcode'): - if not re.match(r"^\d+$",post.vcode): return public.returnJson(False,'Incorrect format of verification code'),json_header - if self.limit_address('?',v="vcode") < 1: return public.returnJson(False,'You have failed verification many times, forbidden for 10 minutes'),json_header - import pyotp - secret_key = public.readFile(_key_file) - if not secret_key: - return public.returnJson(False, "Did not find the key, please close Google verification on the command line and trun on again"),json_header - t = pyotp.TOTP(secret_key) - result = t.verify(post.vcode) - if not result: - if public.sync_date(): result = t.verify(post.vcode) - if not result: - num = self.limit_address('++',v="vcode") - return public.returnJson(False, 'Invalid Verification code. You have [{}] times left to try!'.format(num)), json_header - now = int(time.time()) - # public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) - public.writeFile("/www/server/panel/data/dont_vcode_ip.txt",json.dumps({"client_ip":public.GetClientIp(),"add_time":now})) - self.limit_address('--',v="vcode") - self.set_cdn_host(post) - return self._set_login_session(userInfo) - - acc_client_ip = self.check_two_step_auth() - - if not os.path.exists(_key_file) or acc_client_ip: - public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) - self.set_cdn_host(post) - return self._set_login_session(userInfo) - self.limit_address('-') - session['is_verify_password'] = True - return "1" - except Exception as ex: - stringEx = str(ex) - if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1: - public.ExecShell("rm -f /tmp/sess_*") - public.ExecShell("rm -f /www/wwwlogs/*log") - public.ServiceReload() - return public.returnJson(False,'USER_INODE_ERR'),json_header - public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) - num = self.limit_address('+') - if not num: return public.returnJson(False,'You have failed to log in many times, please wait {} seconds and try again!'.format(int(self.limit_expire_time - time.time()))),json_header - return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header - - # 错误收集 适配 # def request_post(self,post): # if not hasattr(post, 'username') or not hasattr(post, 'password'): + # public.print_log('登录拦截 11') # return public.returnJson(False,'User name or password cannot be empty!'),json_header # self.error_num(False) # if self.limit_address('?') < 1: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header @@ -192,9 +78,6 @@ class userlogin: # return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header # _key_file = "/www/server/panel/data/two_step_auth.txt" # - # area_check = public.check_area_panel() - # if area_check: return area_check - # # # 密码过期检测 # if sys.path[0] != 'class/': sys.path.insert(0,'class/') # if not public.password_expire_check(): @@ -229,7 +112,7 @@ class userlogin: # if not os.path.exists(_key_file) or acc_client_ip: # public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) # self.set_cdn_host(post) - # return self._set_login_session(userInfo, acc_client_ip) + # return self._set_login_session(userInfo) # self.limit_address('-') # session['is_verify_password'] = True # return "1" @@ -242,51 +125,169 @@ class userlogin: # return public.returnJson(False,'USER_INODE_ERR'),json_header # public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) # num = self.limit_address('+') - # if not num: - # return public.returnJson(False,'You have failed to log in many times, please wait {} seconds and try again!'.format(int(self.limit_expire_time - time.time()))),json_header - # # return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header - # - # # 2024/1/3 下午 2:31 记录登录时捕捉不到合适的错误,记录到文件中易于排查 - # import traceback - # public.writeFile( - # '/www/server/panel/data/login_err.log', - # public.getDate() + '\n' + str(traceback.format_exc() + "\n"), - # mode='a+' - # ) - # - # # 提交错误登录信息 - # _form = request.form.to_dict() - # if 'username' in _form: _form['username'] = '******' - # if 'password' in _form: _form['password'] = '******' - # if 'phone' in _form: _form['phone'] = '******' - # - # # 错误信息 - # error_infos = { - # "REQUEST_DATE": public.getDate(), # 请求时间 - # "PANEL_VERSION": public.version(), # 面板版本 - # "OS_VERSION": public.get_os_version(), # 操作系统版本 - # "REMOTE_ADDR": public.GetClientIp(), # 请求IP - # "REQUEST_URI": request.method + request.full_path, # 请求URI - # "REQUEST_FORM": public.xsssec(str(_form)), # 请求表单 - # "USER_AGENT": public.xsssec(request.headers.get('User-Agent')), # 客户端连接信息 - # "ERROR_INFO": str(traceback.format_exc()), # 错误信息 - # "PACK_TIME": public.readFile("/www/server/panel/config/update_time.pl") if os.path.exists("/www/server/panel/config/update_time.pl") else public.getDate(), # 打包时间 - # "TYPE": 2, - # "ERROR_ID": str(ex) - # } - # pkey = public.Md5(error_infos["ERROR_INFO"]) - # - # # 提交 - # if not public.cache_get(pkey): - # try: - # public.run_thread(public.httpPost, ("https://api.bt.cn/bt_error/index.php", error_infos)) - # public.cache_set(pkey, 1, 1800) - # except Exception as e: - # pass - # - # return (public.returnJson( - # False, 'Login error, details:【{}】'.format(stringEx)), - # json_header) + # if not num: return public.returnJson(False,'You have failed to log in many times, please wait {} seconds and try again!'.format(int(self.limit_expire_time - time.time()))),json_header + # return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header + # 错误收集 适配 + def request_post(self,post): + if not hasattr(post, 'username') or not hasattr(post, 'password'): + return public.returnJson(False,'User name or password cannot be empty!'),json_header + self.error_num(False) + if self.limit_address('?') < 1: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header + post.username = post.username.strip() + format_error = 'Parameter format error' + + # 核验用户名密码格式 + post.username = public.rsa_decrypt(post.username) + + if len(post.username) != 32: + return public.returnMsg(False,format_error+"1"),json_header + post.password = public.rsa_decrypt(post.password) + if len(post.password) != 32: + return public.returnMsg(False,format_error+"2"),json_header + + if not re.match(r"^\w+$",post.username): return public.return_msg_gettext(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header + if not re.match(r"^\w+$",post.password): return public.return_msg_gettext(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header + last_login_token = session.get('last_login_token',None) + if not last_login_token: + public.WriteLog('TYPE_LOGIN','LOGIN_ERR_CODE',('****','****',public.GetClientIp())) + return public.returnJson(False,"Verification failed, please refresh the page and log in again!"),json_header + + public.chdck_salt() + sql = db.Sql() + userInfo = None + user_plugin_file = '{}/users_main.py'.format(public.get_plugin_path('users')) + if os.path.exists(user_plugin_file): + user_list = sql.table('users').field('id,username,password,salt').select() + for u_info in user_list: + if public.md5(public.md5(u_info['username'] + last_login_token)) == post.username: + userInfo = u_info + else: + userInfo = sql.table('users').where('id=?',1).field('id,username,password,salt').find() + + if 'code' in session: + if session['code'] and not 'is_verify_password' in session: + if not hasattr(post, 'code'): return public.returnJson(False,'Verification code can not be empty!'),json_header + if not re.match(r"^\w+$",post.code): return public.returnJson(False,'Verification code is incorrect, please try again!'),json_header + if not public.checkCode(post.code): + public.write_log_gettext('Login','Verification code is incorrect, Username:{}, Verification Code:{}, Login IP:{}',('****','****',public.GetClientIp())) + return public.returnJson(False,'Verification code is incorrect, please try again!'),json_header + + try: + if not userInfo: + public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp())) + num = self.limit_address('+') + if not num: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header + return public.returnJson(False,'wrong user name or password,please refresh the page and try again,You can retry {} more times'.format(num)),json_header + + if userInfo and not userInfo['salt']: + public.chdck_salt() + userInfo = sql.table('users').where('id=?',(userInfo['id'],)).field('id,username,password,salt').find() + + password = public.md5(post.password.strip() + userInfo['salt']) + s_username = public.md5(public.md5(userInfo['username'] + last_login_token)) + if s_username != post.username or userInfo['password'] != password: + public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) + num = self.limit_address('+') + if not num: return public.returnJson(False,'You failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header + return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header + _key_file = "/www/server/panel/data/two_step_auth.txt" + + area_check = public.check_area_panel() + if area_check: return area_check + + # 密码过期检测 + if sys.path[0] != 'class/': sys.path.insert(0,'class/') + if not public.password_expire_check(): + session['password_expire'] = True + + #登陆告警 + #public.run_thread(public.login_send_body,("账号密码",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) + # public.login_send_body("账号密码",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))) + if hasattr(post,'vcode'): + if not re.match(r"^\d+$",post.vcode): return public.returnJson(False,'Incorrect format of verification code'),json_header + if self.limit_address('?',v="vcode") < 1: return public.returnJson(False,'You have failed verification many times, forbidden for 10 minutes'),json_header + import pyotp + secret_key = public.readFile(_key_file) + if not secret_key: + return public.returnJson(False, "Did not find the key, please close Google verification on the command line and trun on again"),json_header + t = pyotp.TOTP(secret_key) + result = t.verify(post.vcode) + if not result: + if public.sync_date(): result = t.verify(post.vcode) + if not result: + num = self.limit_address('++',v="vcode") + return public.returnJson(False, 'Invalid Verification code. You have [{}] times left to try!'.format(num)), json_header + now = int(time.time()) + # public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) + public.writeFile("/www/server/panel/data/dont_vcode_ip.txt",json.dumps({"client_ip":public.GetClientIp(),"add_time":now})) + self.limit_address('--',v="vcode") + self.set_cdn_host(post) + return self._set_login_session(userInfo) + + acc_client_ip = self.check_two_step_auth() + + if not os.path.exists(_key_file) or acc_client_ip: + public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) + self.set_cdn_host(post) + return self._set_login_session(userInfo, acc_client_ip) + + self.limit_address('-') + session['is_verify_password'] = True + return "1" + except Exception as ex: + stringEx = str(ex) + if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1: + public.ExecShell("rm -f /tmp/sess_*") + public.ExecShell("rm -f /www/wwwlogs/*log") + public.ServiceReload() + return public.returnJson(False,'USER_INODE_ERR'),json_header + public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) + num = self.limit_address('+') + if not num: + return public.returnJson(False,'You have failed to log in many times, please wait {} seconds and try again!'.format(int(self.limit_expire_time - time.time()))),json_header + # return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header + + # 2024/1/3 下午 2:31 记录登录时捕捉不到合适的错误,记录到文件中易于排查 + import traceback + public.writeFile( + '/www/server/panel/data/login_err.log', + public.getDate() + '\n' + str(traceback.format_exc() + "\n"), + mode='a+' + ) + + # 提交错误登录信息 + _form = request.form.to_dict() + if 'username' in _form: _form['username'] = '******' + if 'password' in _form: _form['password'] = '******' + if 'phone' in _form: _form['phone'] = '******' + + # 错误信息 + error_infos = { + "REQUEST_DATE": public.getDate(), # 请求时间 + "PANEL_VERSION": public.version(), # 面板版本 + "OS_VERSION": public.get_os_version(), # 操作系统版本 + "REMOTE_ADDR": public.GetClientIp(), # 请求IP + "REQUEST_URI": request.method + request.full_path, # 请求URI + "REQUEST_FORM": public.xsssec(str(_form)), # 请求表单 + "USER_AGENT": public.xsssec(request.headers.get('User-Agent')), # 客户端连接信息 + "ERROR_INFO": str(traceback.format_exc()), # 错误信息 + "PACK_TIME": public.readFile("/www/server/panel/config/update_time.pl") if os.path.exists("/www/server/panel/config/update_time.pl") else public.getDate(), # 打包时间 + "TYPE": 100, + "ERROR_ID": str(ex) + } + pkey = public.Md5(error_infos["ERROR_INFO"]) + + # 提交 + if not public.cache_get(pkey): + try: + public.run_thread(public.httpPost, ("https://geterror.aapanel.com/bt_error/index.php", error_infos)) + public.cache_set(pkey, 1, 1800) + except Exception as e: + pass + + return (public.returnJson( + False, 'Login error, details:【{}】'.format(stringEx)), + json_header) def request_tmp(self,get): try: @@ -300,7 +301,6 @@ class userlogin: if not 'tmp_token' in data or not 'tmp_time' in data: return public.returnJson(False,'Verification failed'),json_header if (time.time() - data['tmp_time']) > 120: return public.returnJson(False,'Expired Token'),json_header if get.tmp_token != data['tmp_token']: return public.returnJson(False,'Invalid Token!'),json_header - userInfo = public.M('users').where("id=?",(1,)).field('id,username').find() session['login'] = True session['username'] = userInfo['username'] @@ -325,7 +325,7 @@ class userlogin: def request_temp(self,get): try: - if len(get.__dict__.keys()) > 2: return public.get_msg_gettext('Parameter ERROR!') + if len(get.get_items().keys()) > 2: return public.get_msg_gettext('Parameter ERROR!') if not hasattr(get,'tmp_token'): return public.get_msg_gettext('Parameter ERROR!') if len(get.tmp_token) != 48: return public.get_msg_gettext('Parameter ERROR!') if not re.match(r"^\w+$",get.tmp_token):return public.get_msg_gettext('Parameter ERROR!') @@ -516,7 +516,7 @@ class userlogin: # res = public.returnMsg(True,'LOGIN_SUCCESS') # 返回增加登录地区 res = public.returnMsg(True, 'LOGIN_SUCCESS') if not acc_client_ip else public.returnMsg(True, - 'Login success, your ip: [{}] has been dynamic password authentication, authentication free within 24 hours!'.format( + 'Login success, your ip: [{}] has been dynamic password authentication, authentication free within 24 hours!'.format( address)) res['login_time'] = time.time() diff --git a/class/vilidate.py b/class/vilidate.py index ed37f06e..c7e29e95 100644 --- a/class/vilidate.py +++ b/class/vilidate.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- # | Copyright (c) 2015-2099 宝塔(http://bt.cn) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import random, math diff --git a/class/webserver.py b/class/webserver.py new file mode 100644 index 00000000..a8ab83b2 --- /dev/null +++ b/class/webserver.py @@ -0,0 +1,429 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwl@bt.cn +# +------------------------------------------------------------------- +# +-------------------------------------------------------------------- +# | 前置web服务器控制器 +# +-------------------------------------------------------------------- + +import os +from sys import path as sys_path +from platform import machine + +# 设置运行目录 +os.chdir('/www/server/panel') + +# 添加自定义公共模块路径 +if not 'class/' in sys_path: + sys_path.insert(0,'class/') + +# 引入自定义公共模块 +import public + + +class webserver: + + + def __init__(self): + ''' + @name 初始化 + ''' + if not hasattr(public,'get_panel_path'): + self.__panel_path = '/www/server/panel' + else: + self.__panel_path = public.get_panel_path() + + + self.__webserver_bin = os.path.join(self.__panel_path,'webserver/sbin/webserver') # webserver二进制文件 + self.__webserver_conf = os.path.join(self.__panel_path,'webserver/conf/webserver.conf') # webserver配置文件 + self.__webserver_pid = os.path.join(self.__panel_path,'webserver/logs/webserver.pid') # webserver进程PID + self.__webserver_ctl = os.path.join(self.__panel_path,'script/webserver-ctl.sh') # webserver控制脚本 + self.__panel_port_file = os.path.join(self.__panel_path, 'data/port.pl') # 面板端口文件 + self.__ssl_key_file = os.path.join(self.__panel_path, 'ssl/privateKey.pem') # SSL私钥文件 + self.__ssl_crt_file = os.path.join(self.__panel_path,'ssl/certificate.pem') # SSL证书文件 + self.__is_ssl_file = os.path.join(self.__panel_path,'data/ssl.pl') # 是否开启SSL文件 + self.__examples_path = os.path.join(self.__panel_path, 'webserver/tpls') # 配置文件模板目录 + self.__webserver_conf_example = os.path.join(self.__examples_path ,'webserver.conf') # webserver配置文件模板 + self.__webserver_ssl_conf_example = os.path.join(self.__examples_path ,'webserver_ssl.conf') # webserver SSL配置文件模板 + self.__webserver_listen_conf_example = os.path.join(self.__examples_path ,'webserver_listen.conf') # webserver监听配置文件模板 + self.__webserver_listen_ssl_conf_example = os.path.join(self.__examples_path ,'webserver_listen_ssl.conf') # webserver SSL监听配置文件模板 + self.__default_port = 8888 # 默认端口 + self.__log_error = 'ERROR' + self.__log_debug = 'DEBUG' + self.__log_info = 'INFO' + self.__log_warning = 'WARNING' + + + def print_log(self,msg,level='INFO'): + ''' + @name 打印日志 + @param msg str 日志内容 + @param level str 日志级别 + @return void + ''' + if not hasattr(public,'print_log'): + print("[{}]".format(level),msg) + else: + public.print_log(msg,level) + + def exec_shell(self,cmd): + ''' + @name 执行shell命令 + @param cmd str shell命令 + @return tuple + ''' + res = public.ExecShell(cmd) + if isinstance(res,tuple): + return res + else: + return ('',res) + + + + + # 获取系统架构是X86还是ARM + def get_machine(self): + arch = machine() + return arch + + def bin_exists(self): + ''' + @name 判断webserver二进制文件是否存在 + @return bool + ''' + return os.path.exists(self.__webserver_bin) + + def conf_exists(self): + ''' + @name 判断webserver配置文件是否存在 + @return bool + ''' + return os.path.exists(self.__webserver_conf) + + def conf_example_exists(self): + ''' + @name 判断webserver配置文件模板是否存在 + @return bool + ''' + return os.path.exists(self.__webserver_conf_example) + + def pid_exists(self): + ''' + @name 判断webserver进程PID是否存在 + @return bool + ''' + return os.path.exists(self.__webserver_pid) + + def ctl_exists(self): + ''' + @name 判断webserver-ctl.sh是否存在 + @return bool + ''' + return os.path.exists(self.__webserver_ctl) + + def hasattr_public(self,defname): + ''' + @name 判断public模块是否存在 + @return bool + ''' + if hasattr(public,defname): + return True + + self.print_log('public.{} not found.'.format(defname),self.__log_error) + return False + + def process_status(self): + ''' + @name 判断webserver进程是否存在 + @return bool + ''' + if not self.pid_exists(): + return False + + res = self.exec_shell("bash {} status".format(self.__webserver_ctl)) + + if res[0].find('not running') != -1: + return False + return True + + def start(self): + ''' + @name 启动webserver + @return bool + ''' + if self.process_status(): + return False + + + + res = self.exec_shell("bash {} start".format(self.__webserver_ctl)) + if res[0].find('Failed') != -1: + self.print_log(res,self.__log_warning) + return False + + return True + + def stop(self): + ''' + @name 停止webserver + @return bool + ''' + if not self.process_status(): + return False + res = self.exec_shell("bash {} stop".format(self.__webserver_ctl)) + if res[0].find('Failed') != -1: + self.print_log(res,self.__log_warning) + return False + return True + + def restart(self): + ''' + @name 重启webserver + @return bool + ''' + res = self.exec_shell("bash {} restart".format(self.__webserver_ctl)) + if res[0].find('Failed') != -1: + self.print_log(res,self.__log_warning) + return False + return True + + def reload(self): + ''' + @name 重载webserver + @return bool + ''' + res = self.exec_shell("bash {} reload".format(self.__webserver_ctl)) + if res[0].find('Failed') != -1: + self.print_log(res,self.__log_warning) + return False + return True + + def get_status(self): + ''' + @name 获取webserver状态 + @return bool + ''' + return self.process_status() + + def configtest(self): + ''' + @name 测试配置文件是否正确 + @return bool + ''' + res = self.exec_shell("bash {} configtest".format(self.__webserver_ctl)) + result = "\n".join(res) + + if result.find('successful') != -1: + return True + + self.print_log(result,self.__log_warning) + return False + + def get_panel_port(self): + ''' + @name 获取面板端口 + @return int + ''' + + # 如果面板端口文件不存在,则使用默认端口 + if not os.path.exists(self.__panel_port_file): + return self.__default_port + + # 读取面板端口文件 + port = public.ReadFile(self.__panel_port_file) + if not port: + return self.__default_port + + try: + # 判断端口是否在1-65535之间 + port = int(port) + if port < 1 or port > 65535: + return self.__default_port + except: + return self.__default_port + + return port + + def is_ssl(self): + ''' + @name 是否开启了SSL + @return bool + ''' + if os.path.exists(self.__is_ssl_file): + # 如果开启了SSL,则判断SSL证书和私钥文件是否存在 + if os.path.exists(self.__ssl_key_file) and os.path.exists(self.__ssl_crt_file): + return True + return False + + def get_ssl_config(self): + ''' + @name 获取SSL配置 + @return dict + ''' + # 如果没有开启SSL,则返回空字符串 + if not self.is_ssl(): + return '' + + ssl_conf = public.ReadFile(self.__webserver_ssl_conf_example) + if not ssl_conf: + self.print_log('Ssl configuration file not found.',self.__log_warning) + return '' + + return ssl_conf + + def get_http3_header(self): + ''' + @name 获取HTTP3头部 + @return dict + ''' + http3_header = '' + + # 如果开启了SSL,则添加HTTP3头部 + if self.is_ssl(): + '''h3=":443"; ma=2592000,h3-29=":443"; ma=2592000''' + http3_header = "add_header Alt-Svc 'h3=\":{PORT}\"; ma=86400,h3-29=\":{PORT}\"; ma=86400';".format(PORT=self.get_panel_port()) + + return http3_header + + def get_listen_config(self): + ''' + @name 获取监听配置 + @return dict + ''' + default_listen = ''' + listen {PORT}; + listen [::]:{PORT}; +''' + # 是否开启了SSL + if self.is_ssl(): + + listen_conf = public.ReadFile(self.__webserver_listen_ssl_conf_example) + else: + listen_conf = public.ReadFile(self.__webserver_listen_conf_example) + + # 如果没有找到监听配置文件,则使用默认监听配置 + if not listen_conf: + listen_conf = default_listen + + # 替换面板端口 + port = self.get_panel_port() + listen_conf = listen_conf.format(PORT=port) + + return listen_conf + + + def create_conf(self): + ''' + @name 创建配置文件 + @return bool + ''' + if not self.conf_example_exists(): + self.print_log('Web server configuration file template not found.',self.__log_error) + return False + + # 读取配置文件模板 + config_example = public.ReadFile(self.__webserver_conf_example) + if not config_example: + self.print_log('Web server configuration file template read failed.',self.__log_error) + return False + + # 获取监听配置 + listen_conf = self.get_listen_config() + # 获取SSL配置 + ssl_conf = self.get_ssl_config() + # 获取HTTP3头部 + http3_header = self.get_http3_header() + + config = config_example.format(LISTEN=listen_conf, SSL_CONFIG=ssl_conf, HTTP3_HEADER=http3_header) + res = public.WriteFile(self.__webserver_conf, config) + if not res: + self.print_log("Write Web server configuration file failed",self.__log_error) + return False + + # 测试配置文件是否正确 + if not self.configtest(): + self.print_log('Web server configuration file test failed.',self.__log_error) + return False + + return True + + def chmod_static_files(self): + ''' + @name 设置静态文件权限 + @return bool + ''' + # 设置静态目录链权限 + res = self.exec_shell('chmod 755 /www /www/server /www/server/panel /www/server/panel/BTPanel') + if res[1] != '': + self.print_log('\n'.join(res),self.__log_warning) + return False + + # 递归设置静态文件权限 + res = self.exec_shell('chmod -R 755 /www/server/panel/BTPanel/static') + if res[1] != '': + self.print_log('\n'.join(res),self.__log_warning) + return False + + return True + + def run_webserver(self): + ''' + @name 启动web服务器 + @return bool 是否成功 + ''' + + try: + # 判断public模块是否符合要求 + public_function = ['ExecShell','ReadFile','WriteFile','get_error_info','get_panel_path','get_panel_port','print_log'] + for func in public_function: + if not self.hasattr_public(func): + return False + + # 判断webserver二进制文件是否存在,如果不存在则尝试调用下载脚本下载 + if not self.bin_exists(): + if os.path.exists(self.__webserver_ctl): + self.exec_shell("bash {} download".format(self.__webserver_ctl)) + self.print_log('Web server binary file not found.',self.__log_error) + return False + + # 判断cli工具脚本是否存在 + if not self.ctl_exists(): + self.print_log('Web server command line tool not found.',self.__log_error) + return False + + # 设置静态文件权限 + if not self.chmod_static_files(): + self.print_log('Web server static file permission setting failed.',self.__log_error) + return False + + # 创建配置文件 + if not self.create_conf(): + self.print_log('Web server configuration file creation failed.',self.__log_error) + return False + + # 检查webserver进程是否存在,如果存在则重载,否则启动 + if self.process_status(): + res = self.reload() + if not res: + self.print_log('Web server reload failed.',self.__log_warning) + else: + res = self.start() + # 检查进程是否正常运行 + if not res: + self.print_log('Web server start failed.',self.__log_error) + return False + except: + # 将错误信息写入到日志 + self.print_log(public.get_error_info(),self.__log_error) + return False + + return True + +if __name__ == '__main__': + + web = webserver() + print(web.run_webserver()) diff --git a/class/webshell_check.py b/class/webshell_check.py index efbfb59b..9d374585 100644 --- a/class/webshell_check.py +++ b/class/webshell_check.py @@ -1,10 +1,10 @@ # # coding: utf-8 # # +------------------------------------------------------------------- -# # | 宝塔Linux面板 x6 +# # | aaPanel x6 # # +------------------------------------------------------------------- -# # | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved. +# # | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. # # +------------------------------------------------------------------- -# # | Author: lkqiang +# # | Author: lkqiang # # +------------------------------------------------------------------- # # +-------------------------------------------------------------------- # # | 宝塔webshell 内置扫描 diff --git a/class/website_auto_index.py b/class/website_auto_index.py index b748e98d..854c871d 100644 --- a/class/website_auto_index.py +++ b/class/website_auto_index.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2020 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: zhwen +# Author: zhwen #------------------------------------------------------------------- #------------------------------ @@ -25,7 +25,7 @@ class website_auto_index: def get_auto_index(self, args): ''' # 添加某个网站禁止运行PHP - author: zhwen + author: zhwen :param args: website 网站名 str :return: ''' @@ -46,7 +46,7 @@ class website_auto_index: deny_name = [i.split('_')[-1] for i in data] result = [] for i in deny_name: - reg = '#BEGIN_AUTOINDEX_{}\n\s*location\s*\~\*\s*\^(.*)\.\*.*\((.*)\)\$'.format(i) + reg = '#BEGIN_AUTOINDEX_{}\n\\s*location\\s*\\~\\*\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i) deny_directory = re.search(reg, conf).groups()[0] deny_suffix = re.search(reg, conf).groups()[1] result.append({'name': i, 'dir': deny_directory, 'suffix': deny_suffix}) @@ -60,7 +60,7 @@ class website_auto_index: deny_name = [i.split('_')[-1] for i in data] result = [] for i in deny_name: - reg = '#BEGIN_AUTOINDEX_{}\n\s* + author: zhwen :param args: website 网站名 str :param args: index_name 规则名称 str :param args: dir 自动索引目录 str @@ -108,7 +108,7 @@ class website_auto_index: if not conf: return False if not dir: - reg = '\s*#BEGIN_AUTOINDEX_{n}\n(.|\n)*#END_AUTOINDEX_{n}\n'.format(n=name) + reg = '\\s*#BEGIN_AUTOINDEX_{n}\n(.|\n)*#END_AUTOINDEX_{n}\n'.format(n=name) conf = re.sub(reg, '', conf) else: new = ''' @@ -133,7 +133,7 @@ class website_auto_index: if not conf: return False if not dir: - reg = '\s*#BEGIN_AUTOINDEX_{n}\n(.|\n)*#END_AUTOINDEX_{n}'.format(n=name) + reg = '\\s*#BEGIN_AUTOINDEX_{n}\n(.|\n)*#END_AUTOINDEX_{n}'.format(n=name) conf = re.sub(reg, '', conf) else: new = ''' @@ -147,7 +147,7 @@ class website_auto_index: '''.format(n=name, d=dir) if '#BEGIN_AUTOINDEX_{}'.format(name) in conf: return True - conf = re.sub('#DENY\s*FILES', new + '\n #DENY FILES', conf) + conf = re.sub(r'#DENY\s*FILES', new + '\n #DENY FILES', conf) public.writeFile(self.ap_website_conf, conf) return True @@ -155,7 +155,7 @@ class website_auto_index: def del_auto_index(self, args): ''' # 添加某个网站禁止运行PHP - author: zhwen + author: zhwen :param args: website 网站名 str :param args: deny_name 规则名称 str :return: diff --git a/class/wxapp.py b/class/wxapp.py index 11b0d16e..890d62a1 100644 --- a/class/wxapp.py +++ b/class/wxapp.py @@ -1,10 +1,10 @@ # coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2019 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- import os import sys diff --git a/class_v2/acme_v3.py b/class_v2/acme_v3.py new file mode 100644 index 00000000..3f20ab47 --- /dev/null +++ b/class_v2/acme_v3.py @@ -0,0 +1,2208 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# ACME v2客户端 +# ------------------------------------------------------------------- +import re +import fcntl +import datetime +import binascii +import hashlib +import base64 +import json +import time +import os +import sys + +os.chdir('/www/server/panel') +if not 'class/' in sys.path: + sys.path.insert(0, 'class/') +import http_requests as requests +# + +requests.DEFAULT_TYPE = 'curl' +import public +from public.validate import Param +try: + import OpenSSL +except: + public.ExecShell("btpip install -I pyOpenSSL") + import OpenSSL +try: + import dns.resolver +except: + public.ExecShell("btpip install dnspython") + import dns.resolver + + +class acme_v2: + _url = None + _apis = None + _config = {} + _dns_domains = [] + _bits = 2048 + _acme_timeout = 30 + _dns_class = None + _user_agent = "BTPanel" + _replay_nonce = None + _verify = False + _digest = "sha256" + _max_check_num = 5 + _wait_time = 5 + _mod_index = {True: "Staging", False: "Production"} + _debug = False + _auto_wildcard = False + _dnsapi_file = 'config/dns_api.json' + _save_path = 'vhost/letsencrypt' + _conf_file = 'config/letsencrypt.json' + _stop_rp_file = '{}/data/stop_rp_when_renew_ssl.pl'.format(public.get_panel_path()) + _by_panel = None + + def __init__(self): + if self._debug: + self._url = 'https://acme-staging-v02.api.letsencrypt.org/directory' + else: + self._url = 'https://acme-v02.api.letsencrypt.org/directory' + self._config = self.read_config() + + # 取接口目录 + def get_apis(self): + if not self._apis: + # 尝试从配置文件中获取 + api_index = self._mod_index[self._debug] + if not 'apis' in self._config: + self._config['apis'] = {} + if api_index in self._config['apis']: + if 'expires' in self._config['apis'][api_index] and 'directory' in self._config['apis'][api_index]: + if time.time() < self._config['apis'][api_index]['expires']: + self._apis = self._config['apis'][api_index]['directory'] + return self._apis + + # 尝试从云端获取 + res = requests.get(self._url, verify=False) + if not res.status_code in [200, 201]: + result = res.json() + if "type" in result: + if result['type'] == 'urn:acme:error:serverInternal': + raise Exception(public.get_msg_gettext( + 'Service shutdown or internal error due to maintenance, check [ https://letsencrypt.status.io ] see for more details.')) + if not os.path.exists('/www/server/panel/data/http_type.pl'): + public.writeFile('/www/server/panel/data/http_type.pl', 'python') + self.get_apis() + return self._apis + raise Exception(res.content) + s_body = res.json() + self._apis = {} + self._apis['newAccount'] = s_body['newAccount'] + self._apis['newNonce'] = s_body['newNonce'] + self._apis['newOrder'] = s_body['newOrder'] + self._apis['revokeCert'] = s_body['revokeCert'] + self._apis['keyChange'] = s_body['keyChange'] + + # 保存到配置文件 + self._config['apis'][api_index] = {} + self._config['apis'][api_index]['directory'] = self._apis + self._config['apis'][api_index]['expires'] = time.time() + \ + 86400 # 24小时后过期 + self.save_config() + return self._apis + + # 获取帐户信息 + def get_account_info(self, args): + try: + if not 'account' in self._config: + return {} + k = self._mod_index[self._debug] + if not k in self._config['account']: + self.get_apis() + self.get_kid() + account = self._config['account'][k] + account['email'] = self._config['email'] + self.set_crond() + return public.return_message(0,0,account) + except Exception as ex: + return_message=public.return_msg_gettext(False, str(ex)) + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + # 设置帐户信息 + def set_account_info(self, args): + if not 'account' in self._config: + return public.return_msg_gettext(False, 'The specified account does not exist') + account = json.loads(args.account) + if 'email' in account: + self._config['email'] = account['email'] + del (account['email']) + self._config['account'][self._mod_index[self._debug]] = account + self.save_config() + return public.return_msg_gettext(True, 'Setup successfully!') + + # 获取订单列表 + def get_orders(self, args): + if not 'orders' in self._config: + return [] + s_orders = [] + for index in self._config['orders'].keys(): + tmp_order = self._config['orders'][index] + tmp_order['index'] = index + s_orders.append(tmp_order) + return s_orders + + # 删除订单 + def remove_order(self, args): + if not 'orders' in self._config: + return public.return_msg_gettext(False, 'The specified order does not exist!') + if not args.index in self._config['orders']: + return public.return_msg_gettext(False, 'The specified order does not exist!') + del (self._config['orders'][args.index]) + self.save_config() + return public.return_msg_gettext(True, 'Order deleted successfully!') + + # 取指定订单数据 + def get_order_find(self, args): + if not 'orders' in self._config: + return public.return_msg_gettext(False, 'The specified order does not exist!') + if not args.index in self._config['orders']: + return public.return_msg_gettext(False, 'The specified order does not exist!') + result = self._config['orders'][args.index] + result['cert'] = self.get_cert_info(args.index) + return result + + # 获取证书信息 + def get_cert_info(self, index): + cert = {} + path = self._config['orders'][index]['save_path'] + if not os.path.exists(path): + self.download_cert(index) + cert['private_key'] = public.readFile(path + "/privkey.pem") + cert['fullchain'] = public.readFile(path + "/fullchain.pem") + return cert + + # 更新证书压缩包 + def update_zip(self, args): + path = self._config['orders'][args.index]['save_path'] + if not os.path.exists(path): # 尝试重新下载证书 + self.download_cert(args.index) + if not os.path.exists(path): + return public.return_msg_gettext(False, 'Certificate read failed, directory does not exist!') + import panelTask + bt_task = panelTask.bt_task() + zip_file = path + '/cert.zip' + result = bt_task._zip(path, '.', path + '/cert.zip', '/dev/null', 'zip') + if not os.path.exists(zip_file): + return result + return public.return_msg_gettext(True, zip_file) + + # 吊销证书 + def revoke_order(self, index): + if type(index) != str: + index = index.index + if not index in self._config['orders']: + raise Exception(public.get_msg_gettext('The specified order does not exist!')) + cert_path = self._config['orders'][index]['save_path'] + if not os.path.exists(cert_path): + raise Exception(public.get_msg_gettext('No certificate found for the specified order!')) + cert = self.dump_der(cert_path) + if not cert: + raise Exception(public.get_msg_gettext('Certificate read failed!')) + payload = { + "certificate": self.calculate_safe_base64(cert), + "reason": 4 + } + res = self.acme_request(self._apis['revokeCert'], payload) + if res.status_code in [200, 201]: + if os.path.exists(cert_path): + public.ExecShell("rm -rf {}".format(cert_path)) + del (self._config['orders'][index]) + self.save_config() + return public.return_msg_gettext(True, "Certificate revoked!") + return res.json() + + # 取根域名和记录值 + def extract_zone(self, domain_name): + top_domain_list = public.readFile('{}/config/domain_root.txt'.format(public.get_panel_path())) + if top_domain_list: + top_domain_list = top_domain_list.strip().split('\n') + else: + top_domain_list = [] + old_domain_name = domain_name + top_domain = "." + ".".join(domain_name.rsplit('.')[-2:]) + new_top_domain = "." + top_domain.replace(".", "") + is_tow_top = False + if top_domain in top_domain_list: + is_tow_top = True + domain_name = domain_name[:-len(top_domain)] + new_top_domain + + if domain_name.count(".") > 1: + zone, middle, last = domain_name.rsplit(".", 2) + if is_tow_top: + last = top_domain[1:] + root = ".".join([middle, last]) + else: + zone = "" + root = old_domain_name + return root, zone + + # 自动构造通配符 + def auto_wildcard(self, domains): + if not domains: + return domains + domain_list = [] + for domain in domains: + rootDoamin = self.extract_zone(domain)[0] + if not rootDoamin in domain_list: + domain_list.append(rootDoamin) + if not "*." + rootDoamin in domain_list: + domain_list.append("*." + rootDoamin) + return domain_list + + # 构造域名列表 + def format_domains(self, domains): + if type(domains) != list: + return [] + # 是否自动构造通配符 + if self._auto_wildcard: + domains = self.auto_wildcard(domains) + wildcard = [] + tmp_domains = [] + for domain in domains: + domain = domain.strip() + if domain in tmp_domains: + continue + # 将通配符域名转为验证正则表达式 + f_index = domain.find("*.") + if f_index not in [-1, 0]: + continue + if f_index == 0: + wildcard.append(domain.replace( + "*", r"^[\w-]+").replace(".", r"\.")) + # 添加到申请列表 + tmp_domains.append(domain) + + # 处理通配符包含 + apply_domains = tmp_domains[:] + for domain in tmp_domains: + for w in wildcard: + if re.match(w, domain): + apply_domains.remove(domain) + + return apply_domains + + # 创建订单 + def create_order(self, domains, auth_type, auth_to, index=None): + domains = self.format_domains(domains) + if not domains: + raise Exception(public.get_msg_gettext('Need at least a domain name!')) + # 构造标识 + identifiers = [] + for domain_name in domains: + identifiers.append({"type": 'dns', "value": domain_name}) + payload = {"identifiers": identifiers} + + # 请求创建订单 + res = self.acme_request(self._apis['newOrder'], payload) + if not res.status_code in [201]: # 如果创建失败 + e_body = res.json() + if 'type' in e_body: + # 如果随机数失效 + if e_body['type'].find('error:badNonce') != -1: + self.get_nonce(force=True) + res = self.acme_request(self._apis['newOrder'], payload) + + # 如果帐户失效 + if e_body['detail'].find('KeyID header contained an invalid account URL') != -1: + k = self._mod_index[self._debug] + del (self._config['account'][k]) + self.get_kid() + self.get_nonce(force=True) + res = self.acme_request(self._apis['newOrder'], payload) + if not res.status_code in [201]: + a_auth = res.json() + + ret_title = self.get_error(str(a_auth)) + raise StopIteration( + "{} >>>> {}".format( + ret_title, + json.dumps(a_auth) + ) + ) + + # 返回验证地址和验证 + s_json = res.json() + s_json['auth_type'] = auth_type + s_json['domains'] = domains + s_json['auth_to'] = auth_to + index = self.save_order(s_json, index) + return index + + def get_site_run_path_byid(self, site_id): + ''' + @name 通过site_id获取网站运行目录 + @author hwliang + @param site_id 网站标识 + @return None or string + ''' + if public.M('sites').where('id=? and project_type=?', (site_id, 'PHP')).count() >= 1: + site_path = public.M('sites').where('id=?', site_id).getField('path') + if not site_path: return None + if not os.path.exists(site_path): return None + args = public.dict_obj() + args.id = site_id + import panelSite + run_path = panelSite.panelSite().GetRunPath(args) + if run_path in ['/']: run_path = '' + if run_path: + if run_path[0] == '/': run_path = run_path[1:] + site_run_path = os.path.join(site_path, run_path) + if not os.path.exists(site_run_path): return site_path + return site_run_path + else: + return False + + def get_site_run_path(self, domains): + ''' + @name 通过域名列表获取网站运行目录 + @author hwliang + @param domains 域名列表 + @return None or string + ''' + site_id = 0 + for domain in domains: + site_id = public.M('domain').where("name=?", domain).getField('pid') + if site_id: break + + if not site_id: return None + return self.get_site_run_path_byid(site_id) + + # 获取验证信息 + def get_auths(self, index): + if not index in self._config['orders']: + raise Exception(public.get_msg_gettext('The specified order does not exist!')) + + # 检查是否已经获取过授权信息 + if 'auths' in self._config['orders'][index]: + # 检查授权信息是否过期 + if time.time() < self._config['orders'][index]['auths'][0]['expires']: + return self._config['orders'][index]['auths'] + if self._config['orders'][index]['auth_type'] != 'dns': + site_run_path = self.get_site_run_path(self._config['orders'][index]['domains']) + if site_run_path: self._config['orders'][index]['auth_to'] = site_run_path + + # 清理旧验证 + self.claer_auth_file(index) + + auths = [] + for auth_url in self._config['orders'][index]['authorizations']: + res = self.acme_request(auth_url, "") + if res.status_code not in [200, 201]: + raise Exception("ACEM_AUTH_ERR", (res.json(),)) + + s_body = res.json() + if 'status' in s_body: + if s_body['status'] in ['invalid']: + raise Exception('ACME_INVALID_ORDER') + if s_body['status'] in ['valid']: # 跳过无需验证的域名 + continue + + s_body['expires'] = self.utc_to_time(s_body['expires']) + identifier_auth = self.get_identifier_auth(index, auth_url, s_body) + if not identifier_auth: + raise Exception('ACME_V_INFO_ERR') + + acme_keyauthorization, auth_value = self.get_keyauthorization( + identifier_auth['token']) + identifier_auth['acme_keyauthorization'] = acme_keyauthorization + identifier_auth['auth_value'] = auth_value + identifier_auth['expires'] = s_body['expires'] + identifier_auth['auth_to'] = self._config['orders'][index]['auth_to'] + identifier_auth['type'] = self._config['orders'][index]['auth_type'] + # 设置验证信息 + self.set_auth_info(identifier_auth) + auths.append(identifier_auth) + self._config['orders'][index]['auths'] = auths + self.save_config() + return auths + + # 更新随机数 + def update_replay_nonce(self, res): + replay_nonce = res.headers.get('Replay-Nonce') + if replay_nonce: + self._replay_nonce = replay_nonce + + # 设置验证信息 + def set_auth_info(self, identifier_auth): + + # 从云端验证 + if not self.cloud_check_domain(identifier_auth['domain']): + self.err = "Cloud verification failed!" + + # 是否手动验证DNS + if identifier_auth['auth_to'] == 'dns': + return None + + # 是否文件验证 + if identifier_auth['type'] in ['http', 'tls']: + self.write_auth_file( + identifier_auth['auth_to'], identifier_auth['token'], identifier_auth['acme_keyauthorization']) + else: + # dnsapi验证 + self.create_dns_record( + identifier_auth['auth_to'], identifier_auth['domain'], identifier_auth['auth_value']) + + # 从云端验证域名是否可访问 + def cloud_check_domain(self, domain): + try: + result = requests.post('https://www.aapanel.com/api/panel/checkDomain', {"domain": domain, "ssl": 1}).json() + return result['status'] + except: + return False + + # 清理验证文件 + def claer_auth_file(self, index): + if not self._config['orders'][index]['auth_type'] in ['http', 'tls']: + return True + acme_path = '{}/.well-known/acme-challenge'.format(self._config['orders'][index]['auth_to']) + write_log(public.get_msg_gettext('|-Verify the dir:{}', (acme_path,))) + if os.path.exists(acme_path): + public.ExecShell("rm -f {}/*".format(acme_path)) + acme_path = '/www/server/stop/.well-known/acme-challenge' + if os.path.exists(acme_path): + public.ExecShell("rm -f {}/*".format(acme_path)) + + # 写验证文件 + def write_auth_file(self, auth_to, token, acme_keyauthorization): + try: + acme_path = '{}/.well-known/acme-challenge'.format(auth_to) + if not os.path.exists(acme_path): + os.makedirs(acme_path) + public.set_own(acme_path, 'www') + wellknown_path = '{}/{}'.format(acme_path, token) + public.writeFile(wellknown_path, acme_keyauthorization) + public.set_own(wellknown_path, 'www') + + acme_path = '/www/server/stop/.well-known/acme-challenge' + if not os.path.exists(acme_path): + os.makedirs(acme_path) + public.set_own(acme_path, 'www') + wellknown_path = '{}/{}'.format(acme_path, token) + public.writeFile(wellknown_path, acme_keyauthorization) + public.set_own(wellknown_path, 'www') + return True + except: + err = public.get_error_info() + print(err) + raise Exception(public.get_msg_gettext('Writing verification file failed: {}', (err,))) + + # 解析域名 + def create_dns_record(self, auth_to, domain, dns_value): + # 如果为手动解析 + if auth_to == 'dns' or auth_to.find('|') == -1: + return None + if not self._dns_class: + import panelDnsapi + dns_name, key, secret = self.get_dnsapi(auth_to) + self._dns_class = getattr(panelDnsapi, dns_name)(key, secret) + self._dns_class.create_dns_record(public.de_punycode(domain), dns_value) + self._dns_domains.append({"domain": domain, "dns_value": dns_value}) + + # 解析DNSAPI信息 + def get_dnsapi(self, auth_to): + tmp = auth_to.split('|') + dns_name = tmp[0] + key = "None" + secret = "None" + if len(tmp) < 3: + try: + dnsapi_config = json.loads(public.readFile(self._dnsapi_file)) + for dc in dnsapi_config: + if dc['name'] != dns_name: + continue + if not dc['data']: + continue + key = dc['data'][0]['value'] + secret = dc['data'][1]['value'] + except: + raise Exception(public.get_msg_gettext('No valid DNSAPI key information found')) + else: + key = tmp[1] + secret = tmp[2] + return dns_name, key, secret + + # 删除域名解析 + def remove_dns_record(self): + if not self._dns_class: + return None + for dns_info in self._dns_domains: + try: + self._dns_class.delete_dns_record( + public.de_punycode(dns_info['domain']), dns_info['dns_value']) + except: + pass + + # 验证域名 + def auth_domain(self, index): + if not index in self._config['orders']: + raise Exception(public.get_msg_gettext('The specified order does not exist!')) + + # 开始验证 + for auth in self._config['orders'][index]['auths']: + res = self.check_auth_status(auth['url']) # 检查是否需要验证 + if res.json()['status'] == 'pending': + if auth['type'] == 'dns': # 尝试提前验证dns解析 + self.check_dns( + "_acme-challenge.{}".format( + auth['domain'].replace('*.', '')), + auth['auth_value'], + "TXT" + ) + self.respond_to_challenge(auth) + + # 检查验证结果 + for i in range(len(self._config['orders'][index]['auths'])): + self.check_auth_status(self._config['orders'][index]['auths'][i]['url'], [ + 'valid', 'invalid']) + self._config['orders'][index]['status'] = 'valid' + + # 检查验证状态 + def check_auth_status(self, url, desired_status=None): + desired_status = desired_status or ["pending", "valid", "invalid"] + number_of_checks = 0 + while True: + if desired_status == ['valid', 'invalid']: + write_log(public.get_msg_gettext('|-{} Query verification results..', (str(number_of_checks + 1),))) + time.sleep(self._wait_time) + check_authorization_status_response = self.acme_request(url, "") + a_auth = check_authorization_status_response.json() + authorization_status = a_auth["status"] + number_of_checks += 1 + if authorization_status in desired_status: + if authorization_status == "invalid": + write_log("|-" + public.get_msg_gettext('Verification failed')) + try: + if 'error' in a_auth['challenges'][0]: + ret_title = a_auth['challenges'][0]['error']['detail'] + elif 'error' in a_auth['challenges'][1]: + ret_title = a_auth['challenges'][1]['error']['detail'] + elif 'error' in a_auth['challenges'][2]: + ret_title = a_auth['challenges'][2]['error']['detail'] + else: + ret_title = str(a_auth) + ret_title = self.get_error(ret_title) + except: + ret_title = str(a_auth) + raise StopIteration( + "{} >>>> {}".format( + ret_title, + json.dumps(a_auth) + ) + ) + break + + if number_of_checks == self._max_check_num: + raise StopIteration( + public.get_msg_gettext( + 'Error: Attempted verification {} times. The maximum number of verifications is {}. The verification interval is {} seconds.', + ( + str(number_of_checks), + str(self._max_check_num), + str(self._wait_time) + ))) + if desired_status == ['valid', 'invalid']: + write_log(public.get_msg_gettext('|-Verification succeeded!')) + return check_authorization_status_response + + # 格式化错误输出 + def get_error(self, error): + write_log("error_result: " + str(error)) + if error.find("Max checks allowed") >= 0: + return public.get_msg_gettext( + 'CA cannot verify your domain name, please check if the domain name resolution is correct, or wait 5-10 minutes and try again.') + elif error.find("Max retries exceeded with") >= 0 or error.find('status_code=0 ') != -1: + return public.get_msg_gettext('CA server connection timed out, please try again later.') + elif error.find("The domain name belongs") >= 0: + return public.get_msg_gettext( + 'The domain name does not belong to this DNS service provider, please make sure the domain name is filled in correctly.') + elif error.find('login token ID is invalid') >= 0: + return public.get_msg_gettext('DNS server connection failed, please check if the key is correct.') + elif error.find('Error getting validation data') != -1: + return public.get_msg_gettext( + 'Data validation failed and the CA was unable to get the correct captcha from the authenticated connection.') + elif "too many certificates already issued for exact set of domains" in error: + return public.get_msg_gettext('Issuing failed, the domain {} has exceeded the limit of weekly reissues!', + (str(re.findall("exact set of domains: (.+):", error)),)) + elif "Error creating new account :: too many registrations for this IP" in error: + return public.get_msg_gettext( + 'Issuing failed, the current server IP has reached the limit of creating up to 10 accounts every 3 hours.') + elif "DNS problem: NXDOMAIN looking up A for" in error: + return public.get_msg_gettext( + 'Validation failed, domain name was not resolved, or resolution did not take effect!') + elif "Invalid response from" in error: + return public.get_msg_gettext( + 'Verification failed, domain name resolution error or verification URL cannot be accessed!') + elif error.find('TLS Web Server Authentication') != -1: + return public.get_msg_gettext('Connection to CA server failed, please try again later.') + elif error.find('Name does not end in a public suffix') != -1: + return public.get_msg_gettext('Unsupported domain name {}, please check the domain name is correct!', + (str(re.findall("Cannot issue for \"(.+)\":", error)),)) + elif error.find('No valid IP addresses found for') != -1: + return public.get_msg_gettext( + 'No resolution record was found for domain name {}, please check if the domain name resolution takes effect!', + (str(re.findall("No valid IP addresses found for (.+)", error)),)) + elif error.find('No TXT record found at') != -1: + return public.get_msg_gettext( + 'No valid TXT resolution record was found in the domain name {}, please check whether the TXT record is parsed correctly. If it is applied by DNSAPI, please try again in 10 minutes!', + (str(re.findall("No TXT record found at (.+)", error)),)) + elif error.find('Incorrect TXT record') != -1: + return public.get_msg_gettext( + 'A wrong TXT record was found on {}: {}, please check whether the TXT resolution is correct, if it is applied by DNSAPI, please try again in 10 minutes!', + (str(re.findall("found at (.+)", error)), str(re.findall("Incorrect TXT record \"(.+)\"", error)))) + elif error.find('Domain not under you or your user') != -1: + return public.get_msg_gettext( + 'This domain name does not exist under this dnspod account, adding resolution failed!') + elif error.find('SERVFAIL looking up TXT for') != -1: + return public.get_msg_gettext( + 'No valid TXT resolution record was found in the domain name {}, please check whether the TXT record is parsed correctly. If it is applied by DNSAPI, please try again in 10 minutes!', + (str(re.findall("looking up TXT for (.+)", error)),)) + elif error.find('Timeout during connect') != -1: + return public.get_msg_gettext( + 'The connection timed out and the CA server was unable to access your website!') + elif error.find("DNS problem: SERVFAIL looking up CAA for") != -1: + return public.get_msg_gettext( + 'Domain name {} is currently required to verify the CAA record, please parse the CAA record manually, or retry the application after 1 hour!', + (str(re.findall("looking up CAA for (.+)", error)),)) + elif error.find("Read timed out.") != -1: + return public.get_msg_gettext( + 'The verification timed out. Please check if the domain name is resolved correctly. If it is resolved correctly, the connection between the server and LetsEncrypt may be abnormal. Please try again later!') + elif error.find('Cannot issue for') != -1: + return public.get_msg_gettext( + 'Cannot issue a certificate for {}, cannot apply for a wildcard certificate with a domain name suffix directly!', + (str(re.findall(r'for\s+"(.+)"', error)),)) + elif error.find('too many failed authorizations recently'): + return public.get_msg_gettext( + 'The account has more than 5 failed orders within 1 hour, please wait 1 hour and try again!') + elif error.find("Error creating new order") != -1: + return public.get_msg_gettext('Order creation failed, please try again later!') + elif error.find("Too Many Requests") != -1: + return public.get_msg_gettext( + 'More than 5 verification failures in 1 hour, the application is temporarily banned, please try again later!') + elif error.find('HTTP Error 400: Bad Request') != -1: + return public.get_msg_gettext('CA server denied access, please try again later!') + elif error.find('Temporary failure in name resolution') != -1: + return public.get_msg_gettext( + 'The DNS of the server is faulty and the domain name cannot be resolved. Please use the Linux toolbox to check the DNS configuration') + elif error.find('Too Many Requests') != -1: + return public.get_msg_gettext('Too many requests for this domain name. Please try again 3 hours later') + else: + return error + + # 发送验证请求 + def respond_to_challenge(self, auth): + payload = {"keyAuthorization": "{}".format( + auth['acme_keyauthorization'])} + respond_to_challenge_response = self.acme_request( + auth['dns_challenge_url'], payload) + return respond_to_challenge_response + + # 发送CSR + def send_csr(self, index): + csr = self.create_csr(index) + payload = {"csr": self.calculate_safe_base64(csr)} + send_csr_response = self.acme_request( + url=self._config['orders'][index]['finalize'], payload=payload) + if send_csr_response.status_code not in [200, 201]: + if send_csr_response.status_code == 0: + raise ValueError( + "Error: [Connection reset by peer], the request process may be accidentally intercepted, if only this domain name cannot apply, then the domain name may be abnormal!") + raise ValueError( + "Error: Sending CSR: Response Status {status_code} Response:{response}".format( + status_code=send_csr_response.status_code, + response=send_csr_response.json(), + ) + ) + send_csr_response_json = send_csr_response.json() + certificate_url = send_csr_response_json["certificate"] + self._config['orders'][index]['certificate_url'] = certificate_url + self.save_config() + return certificate_url + + # 获取证书到期时间 + def get_cert_timeout(self, cret_data): + try: + x509 = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cret_data) + cert_timeout = bytes.decode(x509.get_notAfter())[:-1] + return int(time.mktime(time.strptime(cert_timeout, '%Y%m%d%H%M%S'))) + except: + return int(time.time() + (86400 * 90)) + + # 下载证书 + def download_cert(self, index): + res = self.acme_request( + self._config['orders'][index]['certificate_url'], "") + if res.status_code not in [200, 201]: + raise Exception(public.get_msg_gettext('Failed to download certificate: {}', (str(res.json()),))) + + pem_certificate = res.content + if type(pem_certificate) == bytes: + pem_certificate = pem_certificate.decode('utf-8') + cert = self.split_ca_data(pem_certificate) + cert['cert_timeout'] = self.get_cert_timeout(cert['cert']) + cert['private_key'] = self._config['orders'][index]['private_key'] + cert['domains'] = self._config['orders'][index]['domains'] + del (self._config['orders'][index]['private_key']) + del (self._config['orders'][index]['auths']) + del (self._config['orders'][index]['expires']) + del (self._config['orders'][index]['authorizations']) + del (self._config['orders'][index]['finalize']) + del (self._config['orders'][index]['identifiers']) + if 'cert' in self._config['orders'][index]: + del (self._config['orders'][index]['cert']) + self._config['orders'][index]['status'] = 'valid' + self._config['orders'][index]['cert_timeout'] = cert['cert_timeout'] + domain_name = self._config['orders'][index]['domains'][0] + self._config['orders'][index]['save_path'] = '{}/{}'.format( + self._save_path, domain_name) + cert['save_path'] = self._config['orders'][index]['save_path'] + self.save_config() + self.save_cert(cert, index) + return cert + + # 保存证书到文件 + def save_cert(self, cert, index): + try: + domain_name = self._config['orders'][index]['domains'][0] + path = self._config['orders'][index]['save_path'] + if not os.path.exists(path): + os.makedirs(path, 384) + + # 存储证书 + key_file = path + "/privkey.pem" + pem_file = path + "/fullchain.pem" + public.writeFile(key_file, cert['private_key']) + public.writeFile(pem_file, cert['cert'] + cert['root']) + public.writeFile(path + "/cert.csr", cert['cert']) + public.writeFile(path + "/root_cert.csr", cert['root']) + + # 转为IIS证书 + pfx_buffer = self.dump_pkcs12( + cert['private_key'], cert['cert'] + cert['root'], cert['root'], domain_name) + public.writeFile(path + "/fullchain.pfx", pfx_buffer, 'wb+') + + ps = '''Document description: +privkey.pem Certificate private key +fullchain.pem PEM format certificate with certificate chain (nginx/apache) +root_cert.csr Root certificate +cert.csr Domain name certificate +fullchain.pfx Certificate format for IIS + +How to use in the aaPanel: +privkey.pem Paste into the key entry box +fullchain.pem Paste into certificate input box +''' + public.writeFile(path + '/Description.txt', ps) + self.sub_all_cert(key_file, pem_file) + except: + write_log(public.get_error_info()) + + # 通过域名获取网站名称 + def get_site_name_by_domains(self, domains): + sql = public.M('domain') + site_sql = public.M('sites') + siteName = None + for domain in domains: + pid = sql.where('name=?', domain).getField('pid') + if pid: + siteName = site_sql.where('id=?', pid).getField('name') + break + return siteName + + # 替换服务器上的同域名同品牌证书 + def sub_all_cert(self, key_file, pem_file): + cert_init = self.get_cert_init(pem_file) # 获取新证书的基本信息 + paths = ['/www/server/panel/vhost/cert', '/www/server/panel/vhost/ssl', '/www/server/panel'] + is_panel = False + for path in paths: + if not os.path.exists(path): + continue + for p_name in os.listdir(path): + to_path = path + '/' + p_name + to_pem_file = to_path + '/fullchain.pem' + to_key_file = to_path + '/privkey.pem' + to_info = to_path + '/info.json' + # 判断目标证书是否存在 + if not os.path.exists(to_pem_file): + if p_name not in ['ssl']: continue + to_pem_file = to_path + '/certificate.pem' + to_key_file = to_path + '/privateKey.pem' + if not os.path.exists(to_pem_file): + continue + # _by_panel None 时通过面板请求时不即使续签面板证书也不重启面板以免导致后续请求出错 + if not os.path.exists('{}/data/ssl.pl'.format(public.get_panel_path())): + continue + if not self._by_panel: + is_panel = True + # 获取目标证书的基本信息 + to_cert_init = self.get_cert_init(to_pem_file) + # 判断证书品牌是否一致 + try: + if to_cert_init['issuer'] != cert_init['issuer'] and to_cert_init['issuer'].find( + "Let's Encrypt") == -1 and to_cert_init['issuer'] != 'R3': + continue + except: + continue + # 判断目标证书的到期时间是否较早 + if to_cert_init['notAfter'] > cert_init['notAfter']: + continue + # 判断认识名称是否一致 + if len(to_cert_init['dns']) != len(cert_init['dns']): + continue + is_copy = True + for domain in to_cert_init['dns']: + if not domain in cert_init['dns']: + is_copy = False + if not is_copy: + continue + + # 替换新的证书文件和基本信息 + public.writeFile( + to_pem_file, public.readFile(pem_file, 'rb'), 'wb') + public.writeFile( + to_key_file, public.readFile(key_file, 'rb'), 'wb') + public.writeFile(to_info, json.dumps(cert_init)) + write_log(public.get_msg_gettext( + '|-Detected that the certificate under {} overlaps with the certificate of this application and has an earlier expiration time, and has been replaced with a new certificate!', + (to_path,))) + # 重载web服务 + public.serviceReload() + #if is_panel: public.restart_panel() + + # 检查指定证书是否在订单列表 + def check_order_exists(self, pem_file): + try: + cert_init = self.get_cert_init(pem_file) + if not cert_init: return None + for index in self._config['orders'].keys(): + if not 'save_path' in self._config['orders'][index]: + continue + for domain in self._config['orders'][index]['domains']: + if domain in cert_init['dns']: + return index + if cert_init['issuer'].find("Let's Encrypt") != -1 or cert_init['issuer'] == 'R3': + return pem_file + return None + except: + return None + + # 取证书基本信息API + def get_cert_init_api(self, args): + if not os.path.exists(args.pem_file): + args.pem_file = 'vhost/cert/{}/fullchain.pem'.format(args.siteName) + if not os.path.exists(args.pem_file): + return public.return_msg_gettext(False, 'The specified certificate file does not exist!') + cert_init = self.get_cert_init(args.pem_file) + if not cert_init: + return public.return_msg_gettext(False, 'Certificate information acquisition failed!') + api_path = './config/dns_api.json' + api_init = './config/dns_api_init.json' + if not os.path.exists(api_path): + if os.path.exists(api_init): + import shutil + shutil.copyfile(api_init, api_path) + cert_init['dnsapi'] = json.loads(public.readFile(self._dnsapi_file)) + return cert_init + + # 获取指定证书基本信息 + def get_cert_init(self, pem_file): + if not os.path.exists(pem_file): + return None + try: + 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 + # 取到期时间 + 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: + 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')) + + # 证书转为DER + def dump_der(self, cert_path): + cert = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, public.readFile(cert_path + '/cert.csr')) + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, cert) + + # 证书转为pkcs12 + def dump_pkcs12(self, key_pem=None, cert_pem=None, ca_pem=None, friendly_name=None): + p12 = OpenSSL.crypto.PKCS12() + if cert_pem: + p12.set_certificate(OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert_pem.encode())) + if key_pem: + p12.set_privatekey(OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, key_pem.encode())) + if ca_pem: + p12.set_ca_certificates((OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, ca_pem.encode()),)) + if friendly_name: + p12.set_friendlyname(friendly_name.encode()) + return p12.export() + + # 拆分根证书 + def split_ca_data(self, cert): + sp_key = '-----END CERTIFICATE-----\n' + datas = cert.split(sp_key) + return {"cert": datas[0] + sp_key, "root": sp_key.join(datas[1:])} + + # 构造可选名称 + def get_alt_names(self, index): + domain_name = self._config['orders'][index]['domains'][0] + domain_alt_names = [] + if len(self._config['orders'][index]['domains']) > 1: + domain_alt_names = self._config['orders'][index]['domains'][1:] + return domain_name, domain_alt_names + + # 检查DNS记录 + def check_dns(self, domain, value, s_type='TXT'): + write_log(public.get_msg_gettext( + '|-Attempt to verify DNS records locally, domain name: {}, type: {} record value: {}', + (domain, s_type, value))) + time.sleep(10) + n = 0 + while n < 20: + n += 1 + try: + import dns.resolver + ns = dns.resolver.query(domain, s_type) + for j in ns.response.answer: + for i in j.items: + txt_value = i.to_text().replace('"', '').strip() + write_log( + public.get_msg_gettext('|-Number of verifications: {}, value: {}', (str(n), txt_value))) + if txt_value == value: + write_log(public.get_msg_gettext('|-Local authentication succeeded!')) + return True + except: + try: + import dns.resolver + except: + return False + time.sleep(3) + write_log(public.get_msg_gettext('|-Local authentication failed!')) + return True + + # 创建CSR + def create_csr(self, index): + if 'csr' in self._config['orders'][index]: + return self._config['orders']['csr'] + domain_name, domain_alt_names = self.get_alt_names(index) + X509Req = OpenSSL.crypto.X509Req() + X509Req.get_subject().CN = domain_name + if domain_alt_names: + SAN = "DNS:{}, ".format(domain_name).encode("utf8") + ", ".join( + "DNS:" + i for i in domain_alt_names + ).encode("utf8") + else: + SAN = "DNS:{}".format(domain_name).encode("utf8") + + X509Req.add_extensions( + [ + OpenSSL.crypto.X509Extension( + "subjectAltName".encode("utf8"), critical=False, value=SAN + ) + ] + ) + pk = OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, self.create_certificate_key( + index).encode() + ) + X509Req.set_pubkey(pk) + try: + X509Req.set_version(2) + except ValueError as e: # pyOpenSSL 新版本需要必须设置版本为0 + X509Req.set_version(0) + X509Req.sign(pk, self._digest) + return OpenSSL.crypto.dump_certificate_request(OpenSSL.crypto.FILETYPE_ASN1, X509Req) + + # 构造域名验证头和验证值 + def get_keyauthorization(self, token): + acme_header_jwk_json = json.dumps( + self.get_acme_header("GET_THUMBPRINT")["jwk"], sort_keys=True, separators=(",", ":") + ) + acme_thumbprint = self.calculate_safe_base64( + hashlib.sha256(acme_header_jwk_json.encode("utf8")).digest() + ) + acme_keyauthorization = "{}.{}".format(token, acme_thumbprint) + base64_of_acme_keyauthorization = self.calculate_safe_base64( + hashlib.sha256(acme_keyauthorization.encode("utf8")).digest() + ) + + return acme_keyauthorization, base64_of_acme_keyauthorization + + # 构造验证信息 + def get_identifier_auth(self, index, url, auth_info): + s_type = self.get_auth_type(index) + write_log(public.get_msg_gettext('|-Verification type: {}', (s_type,))) + domain = auth_info['identifier']['value'] + wildcard = False + # 处理通配符 + if 'wildcard' in auth_info: + wildcard = auth_info['wildcard'] + if wildcard: + domain = "*." + domain + + for auth in auth_info['challenges']: + if auth['type'] != s_type: + continue + identifier_auth = { + "domain": domain, + "url": url, + "wildcard": wildcard, + "token": auth['token'], + "dns_challenge_url": auth['url'], + } + return identifier_auth + return None + + # 获取域名验证方式 + def get_auth_type(self, index): + if not index in self._config['orders']: + raise Exception(public.get_msg_gettext('The specified order does not exist!')) + s_type = 'http-01' + if 'auth_type' in self._config['orders'][index]: + if self._config['orders'][index]['auth_type'] == 'dns': + s_type = 'dns-01' + elif self._config['orders'][index]['auth_type'] == 'tls': + s_type = 'tls-alpn-01' + else: + s_type = 'http-01' + return s_type + + # 保存订单 + def save_order(self, order_object, index): + if not 'orders' in self._config: + self._config['orders'] = {} + renew = False + if not index: + index = public.md5(json.dumps(order_object['identifiers'])) + else: + renew = True + order_object['certificate_url'] = self._config['orders'][index]['certificate_url'] + order_object['save_path'] = self._config['orders'][index]['save_path'] + + order_object['expires'] = self.utc_to_time(order_object['expires']) + self._config['orders'][index] = order_object + self._config['orders'][index]['index'] = index + if not renew: + self._config['orders'][index]['create_time'] = int(time.time()) + self._config['orders'][index]['renew_time'] = 0 + self.save_config() + return index + + # UTC时间转时间戳 + def utc_to_time(self, utc_string): + try: + utc_string = utc_string.split('.')[0] + utc_date = datetime.datetime.strptime( + utc_string, "%Y-%m-%dT%H:%M:%S") + # 按北京时间返回 + return int(time.mktime(utc_date.timetuple())) + (3600 * 8) + except: + return int(time.time() + 86400 * 7) + + # 获取kid + def get_kid(self, force=False): + # 如果配置文件中不存在kid或force = True时则重新注册新的acme帐户 + if not 'account' in self._config: + self._config['account'] = {} + k = self._mod_index[self._debug] + if not k in self._config['account']: + self._config['account'][k] = {} + + if not 'kid' in self._config['account'][k]: + self._config['account'][k]['kid'] = self.register() + self.save_config() + time.sleep(3) + self._config = self.read_config() + return self._config['account'][k]['kid'] + + # 注册acme帐户 + def register(self, existing=False): + if not 'email' in self._config: + self._config['email'] = 'demo@aapanel.com' + if existing: + payload = {"onlyReturnExisting": True} + elif self._config['email']: + payload = { + "termsOfServiceAgreed": True, + "contact": ["mailto:{}".format(self._config['email'])], + } + else: + payload = {"termsOfServiceAgreed": True} + + res = self.acme_request(url=self._apis['newAccount'], payload=payload) + + if res.status_code not in [201, 200, 409]: + raise Exception(public.get_msg_gettext('Registration for ACME account failed: {}', (str(res.json()),))) + kid = res.headers["Location"] + return kid + + # 请求到ACME接口 + def acme_request(self, url, payload): + headers = {"User-Agent": self._user_agent} + payload = self.stringfy_items(payload) + + if payload == "": + payload64 = payload + else: + payload64 = self.calculate_safe_base64(json.dumps(payload)) + protected = self.get_acme_header(url) + protected64 = self.calculate_safe_base64(json.dumps(protected)) + signature = self.sign_message( + message="{}.{}".format(protected64, payload64)) # bytes + signature64 = self.calculate_safe_base64(signature) # str + data = json.dumps( + {"protected": protected64, "payload": payload64, + "signature": signature64} + ) + headers.update({"Content-Type": "application/jose+json"}) + response = requests.post( + url, data=data.encode("utf8"), timeout=self._acme_timeout, headers=headers, verify=self._verify + ) + # 更新随机数 + self.update_replay_nonce(response) + return response + + # 计算signature + def sign_message(self, message): + pk = OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, self.get_account_key().encode()) + return OpenSSL.crypto.sign(pk, message.encode("utf8"), self._digest) + + # 系列化payload + def stringfy_items(self, payload): + if isinstance(payload, str): + return payload + + for k, v in payload.items(): + if isinstance(k, bytes): + k = k.decode("utf-8") + if isinstance(v, bytes): + v = v.decode("utf-8") + payload[k] = v + return payload + + # 获取随机数 + def get_nonce(self, force=False): + # 如果没有保存上一次的随机数或force=True时则重新获取新的随机数 + if not self._replay_nonce or force: + headers = {"User-Agent": self._user_agent} + response = requests.get( + self._apis['newNonce'], + timeout=self._acme_timeout, + headers=headers, + verify=self._verify + ) + self._replay_nonce = response.headers["Replay-Nonce"] + return self._replay_nonce + + # 获请ACME请求头 + def get_acme_header(self, url): + header = {"alg": "RS256", "nonce": self.get_nonce(), "url": url} + if url in [self._apis['newAccount'], 'GET_THUMBPRINT']: + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization + private_key = serialization.load_pem_private_key( + self.get_account_key().encode(), + password=None, + backend=default_backend(), + ) + public_key_public_numbers = private_key.public_key().public_numbers() + + exponent = "{0:x}".format(public_key_public_numbers.e) + exponent = "0{}".format(exponent) if len( + exponent) % 2 else exponent + modulus = "{0:x}".format(public_key_public_numbers.n) + jwk = { + "kty": "RSA", + "e": self.calculate_safe_base64(binascii.unhexlify(exponent)), + "n": self.calculate_safe_base64(binascii.unhexlify(modulus)), + } + header["jwk"] = jwk + else: + header["kid"] = self.get_kid() + return header + + # 转为无填充的Base64 + def calculate_safe_base64(self, un_encoded_data): + if sys.version_info[0] == 3: + if isinstance(un_encoded_data, str): + un_encoded_data = un_encoded_data.encode("utf8") + r = base64.urlsafe_b64encode(un_encoded_data).rstrip(b"=") + return r.decode("utf8") + + # 获用户取密钥对 + def get_account_key(self): + if not 'account' in self._config: + self._config['account'] = {} + k = self._mod_index[self._debug] + if not k in self._config['account']: + self._config['account'][k] = {} + + if not 'key' in self._config['account'][k]: + self._config['account'][k]['key'] = self.create_key() + if type(self._config['account'][k]['key']) == bytes: + self._config['account'][k]['key'] = self._config['account'][k]['key'].decode() + self.save_config() + return self._config['account'][k]['key'] + + # 获取证书密钥对 + def create_certificate_key(self, index): + # 判断是否已经创建private_key + if 'private_key' in self._config['orders'][index]: + return self._config['orders'][index]['private_key'] + # 创建新的私钥 + private_key = self.create_key() + if type(private_key) == bytes: + private_key = private_key.decode() + # 保存私钥到订单配置文件 + self._config['orders'][index]['private_key'] = private_key + self.save_config() + return private_key + + # 创建Key + def create_key(self, key_type=OpenSSL.crypto.TYPE_RSA): + key = OpenSSL.crypto.PKey() + key.generate_key(key_type, self._bits) + private_key = OpenSSL.crypto.dump_privatekey( + OpenSSL.crypto.FILETYPE_PEM, key) + return private_key + + # 写配置文件 + def save_config(self): + fp = open(self._conf_file, 'w+') + fcntl.flock(fp, fcntl.LOCK_EX) # 加锁 + fp.write(json.dumps(self._config)) + fcntl.flock(fp, fcntl.LOCK_UN) # 解锁 + fp.close() + return True + + # 读配置文件 + def read_config(self): + if not os.path.exists(self._conf_file): + self._config['orders'] = {} + self._config['account'] = {} + self._config['apis'] = {} + self._config['email'] = public.M('config').where('id=?', (1,)).getField('email') + if self._config['email'] in ['287962566@qq.com']: + self._config['email'] = None + self.save_config() + return self._config + tmp_config = public.readFile(self._conf_file) + if not tmp_config: + return self._config + try: + self._config = json.loads(tmp_config) + except: + self.save_config() + return self._config + return self._config + + # 申请证书 + def apply_cert(self, domains, auth_type='dns', auth_to='Dns_com|None|None', **args): + write_log("", "wb+") + try: + self.get_apis() + index = None + if 'index' in args: + index = args['index'] + if 'auto_wildcard' in args: + self._auto_wildcard = 1 + if not index: # 判断是否只想验证域名 + write_log(public.get_msg_gettext('|-Creating order..')) + index = self.create_order(domains, auth_type, auth_to) + write_log(public.get_msg_gettext('|-Getting verification information..')) + self.get_auths(index) + if auth_to == 'dns' and len(self._config['orders'][index]['auths']) > 0: + return public.return_message(0,0,self._config['orders'][index]) + write_log(public.get_msg_gettext('|-Verifying domain name..')) + self.auth_domain(index) + self.remove_dns_record() + write_log(public.get_msg_gettext('|-Sending CSR..')) + self.send_csr(index) + write_log(public.get_msg_gettext('|-Downloading certificate..')) + cert = self.download_cert(index) + cert['status'] = True + cert['msg'] = public.get_msg_gettext('Application successful!') + write_log(public.get_msg_gettext('|-Successful application, deploying to site..')) + return public.return_message(0,0,cert) + except Exception as ex: + self.remove_dns_record() + ex = str(ex) + if ex.find(">>>>") != -1: + msg = ex.split(">>>>") + msg[1] = json.loads(msg[1]) + else: + msg = ex + write_log(public.get_error_info()) + return_message=public.return_msg_gettext(False, msg) + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + # 申请证书 - api + def apply_cert_api(self, args): + # 校验参数 + try: + get = args + get.validate([ + Param('domains').Array(), + Param('auth_type').String(), + Param('auth_to').String(), + Param('auto_wildcard').Integer(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 在面板点击申请证书时不要重启面板以防后续请求出错 + self._by_panel = True + # 是否为指定站点 + if public.M('sites').where('id=? and project_type=?', (args.id, 'Java')).count(): + project_info = public.M('sites').where('id=?', (args.id,)).getField('project_config') + try: + project_info = json.loads(project_info) + if not 'ssl_path' in project_info: + return_message=public.return_msg_gettext(False, + 'There is a problem with the current Java project configuration file, please rebuild') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + if not os.path.exists(project_info['ssl_path']): + os.makedirs(project_info['ssl_path']) + path = project_info['ssl_path'] + args.auth_to = path + check_result = self.check_auth_env(args) + if check_result['message']['result'] !="": + return check_result + + if args.auto_wildcard == '1': + self._auto_wildcard = True + self.turnon_redirect_proxy_httptohttps(args) + return self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to) + except: + return public.return_message(-1,0, 'There is a problem with the current Java project configuration file, please rebuild') + finally: + self.turnon_redirect_proxy_httptohttps(args) + else: + if re.match(r"^\d+$", args.auth_to): + import panel_site_v2 as panelSite + path = public.M('sites').where('id=?', (args.id,)).getField('path') + args.auth_to = path + '/' + panelSite.panelSite().GetRunPath(args)['message']['result'] + args.auth_to = args.auth_to.replace("//", "/") + if args.auth_to[-1] == '/': + args.auth_to = args.auth_to[:-1] + + if not os.path.exists(args.auth_to): + self.turnon_redirect_proxy_httptohttps(args) + return public.return_message(-1,0, 'Invalid site directory, please check if the specified site exists!') + check_result = self.check_auth_env(args, check=True) + if check_result['message']['result'] !="": + return check_result + if args.auto_wildcard == '1': + self._auto_wildcard = True + res = self.apply_cert(json.loads(args.domains), args.auth_type, args.auth_to) + if os.path.exists(self._stop_rp_file): + self.turnon_redirect_proxy_httptohttps(args) + return res + + def turnon_redirect_proxy_httptohttps(self, args): + import panelSite + s = panelSite.panelSite() + if not 'siteName' in args: + args.siteName = public.M('sites').where('id=?', (args.id,)).getField('name') + args.sitename = args.siteName + self.turnon_redirect(args, s) + self.turnon_proxy(args, s) + self.turnon_httptohttps(args, s) + public.serviceReload() + + def turnon_httptohttps(self, args, s): + conf_file = '{}/data/stop_httptohttps.pl'.format(public.get_panel_path()) + if os.path.exists(conf_file): + write_log('|-Turning on http to https') + s.HttpToHttps(args) + try: + os.remove(conf_file) + except: + pass + + def turnon_proxy(self, args, s): + conf_file = '{}/data/stop_p_tmp.pl'.format(public.get_panel_path()) + if not os.path.exists(conf_file): + return + write_log('|-Turning on proxy') + conf = json.loads(public.readFile(conf_file)) + data = s.GetProxyList(args) + for x in data: + if x['sitename'] not in conf: + continue + if x['proxyname'] not in conf[x['sitename']]: + continue + args.type = 1 + args.advanced = x['advanced'] + args.cache = x['cache'] + args.cachetime = x['cachetime'] + args.proxydir = x['proxydir'] + args.proxyname = x['proxyname'] + args.proxysite = x['proxysite'] + args.sitename = x['sitename'] + args.subfilter = json.dumps(x['subfilter']) + args.todomain = x['todomain'] + s.ModifyProxy(args) + try: + os.remove(conf_file) + except: + pass + + def turnon_redirect(self, args, s): + conf_file = '{}/data/stop_r_tmp.pl'.format(public.get_panel_path()) + if not os.path.exists(conf_file): + return + write_log('|-Turning on redirection') + conf = json.loads(public.readFile(conf_file)) + data = s.GetRedirectList(args) + for x in data: + if x['sitename'] not in conf: + continue + if x['redirectname'] not in conf[x['sitename']]: + continue + args.type = 1 + args.sitename = x['sitename'] + args.holdpath = x['holdpath'] + args.redirectname = x['redirectname'] + args.redirecttype = x['redirecttype'] + args.domainorpath = x['domainorpath'] + args.redirectpath = x['redirectpath'] + args.redirectdomain = json.dumps(x['redirectdomain']) + args.tourl = x['tourl'] + s.ModifyRedirect(args) + try: + os.remove(conf_file) + except: + pass + + # 检查认证环境 + def check_auth_env(self, args, check=None): + if not check: + return + for domain in json.loads(args.domains): + if public.checkIp(domain): continue + if domain.find('*.') != -1 and args.auth_type in ['http', 'tls']: + return_message=public.return_msg_gettext(False, + 'Pan domain names cannot apply for a certificate using [File Verification]!') + del return_message['status'] + raise public.return_message(-1,0, return_message['msg']) + import panel_site_v2 as panelSite + s = panelSite.panelSite() + if args.auth_type in ['http', 'tls']: + try: + rp_conf = public.readFile(self._stop_rp_file) + try: + if rp_conf: + rp_conf = json.loads(rp_conf) + except: + write_log('|-Failed to parse configuration file') + if not 'siteName' in args: + args.siteName = public.M('sites').where('id=?', (args.id,)).getField('name') + args.sitename = args.siteName + data = s.GetRedirectList(args) + # 检查重定向是否开启 + if type(data) == list: + redirect_tmp = {args.sitename: []} + for x in data: + if rp_conf and x['sitename'] in rp_conf: + if str(x['type']) == '0': + continue + args.type = 0 + args.sitename = x['sitename'] + args.holdpath = x['holdpath'] + args.redirectname = x['redirectname'] + args.redirecttype = x['redirecttype'] + args.domainorpath = x['domainorpath'] + args.redirectpath = x['redirectpath'] + args.redirectdomain = json.dumps(x['redirectdomain']) + args.tourl = x['tourl'] + args.notreload = True + write_log("|- Turning off redirection {}".format(args.redirectname)) + s.ModifyRedirect(args) + redirect_tmp[args.sitename].append(x['redirectname']) + else: + if x['type']: return public.return_message(-1,0, + 'Your site has 301 Redirect on,Please turn it off first!') + if redirect_tmp[args.sitename]: + public.writeFile('{}/data/stop_r_tmp.pl'.format(public.get_panel_path()), + json.dumps(redirect_tmp)) + data = s.GetProxyList(args) + # 检查反向代理是否开启 + if type(data) == list: + proxy_tmp = {args.sitename: []} + for x in data: + if rp_conf and x['sitename'] in rp_conf: + if str(x['type']) == '0': + continue + args.type = 0 + args.advanced = x['advanced'] + args.cache = x['cache'] + args.cachetime = x['cachetime'] + args.proxydir = x['proxydir'] + args.proxyname = x['proxyname'] + args.proxysite = x['proxysite'] + args.sitename = x['sitename'] + args.subfilter = json.dumps(x['subfilter']) + args.todomain = x['todomain'] + args.notreload = True + s.ModifyProxy(args) + write_log("|- Turning off proxy {}".format(args.proxyname)) + proxy_tmp[args.sitename].append(x['proxyname']) + else: + if x['type']: return public.return_message(-1,0, + 'Sites with reverse proxy turned on cannot apply for SSL!') + if proxy_tmp[args.sitename]: + public.writeFile('{}/data/stop_p_tmp.pl'.format(public.get_panel_path()), json.dumps(proxy_tmp)) + # 检查旧重定向是否开启 + data = s.Get301Status(args) + if data['status']: + return public.return_message(-1,0, + 'The website has been redirected, please close it before applying!') + # 判断是否强制HTTPS + if s.IsToHttps(args.siteName): + if os.path.exists(self._stop_rp_file): + if rp_conf and args.siteName in rp_conf: + write_log("|- Turning off http to https") + s.CloseToHttps(args) + public.writeFile('{}/data/stop_httptohttps.pl'.format(public.get_panel_path()), '') + else: + return public.return_message(-1,0, + 'After configuring Force HTTPS, you cannot use [File Verification] to apply for a certificate!') + public.serviceReload() + except: + return public.return_message(-1,0,"") + else: + if args.auth_to.find('Dns_com') != -1: + if not os.path.exists('plugin/dns/dns_main.py'): + return public.return_message(-1,0, + 'Please go to the software store to install [cloud analysis], and complete the domain name NS binding.') + return public.return_message(-1,0,"") + + # DNS手动验证 + def apply_dns_auth(self, args): + # 校验参数 + try: + args.validate([ + Param('index').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + return self.apply_cert([], auth_type='dns', auth_to='dns', index=args.index) + + # 创建计划任务 + def set_crond(self): + try: + echo = public.md5(public.md5('renew_lets_ssl_bt')) + cron_id = public.M('crontab').where('echo=?', (echo,)).getField('id') + + import crontab + args_obj = public.dict_obj() + if not cron_id: + cronPath = public.GetConfigValue('setup_path') + '/cron/' + echo + shell = '{} -u /www/server/panel/class/acme_v2.py --renew=1'.format(sys.executable) + public.writeFile(cronPath, shell) + args_obj.id = public.M('crontab').add( + 'name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress', + ("Renew Let's Encrypt Certificate", 'day', '', '0', '10', echo, + time.strftime('%Y-%m-%d %X', time.localtime()), 0, '', 'localhost', 'toShell', '', shell, '')) + crontab.crontab().set_cron_status(args_obj) + else: + cron_path = public.get_cron_path() + if os.path.exists(cron_path): + cron_s = public.readFile(cron_path) + if cron_s.find(echo) == -1: + public.M('crontab').where('echo=?', (echo,)).setField('status', 0) + args_obj.id = cron_id + crontab.crontab().set_cron_status(args_obj) + except: + pass + + # 获取当前正在使用此证书的网站目录 + def get_ssl_used_site(self, save_path): + pkey_file = '{}/privkey.pem'.format(save_path) + pkey = public.readFile(pkey_file) + if not pkey: return False + cert_paths = 'vhost/cert' + import panelSite + args = public.dict_obj() + args.siteName = '' + for c_name in os.listdir(cert_paths): + skey_file = '{}/{}/privkey.pem'.format(cert_paths, c_name) + skey = public.readFile(skey_file) + if not skey: continue + if skey == pkey: + args.siteName = c_name + run_path = panelSite.panelSite().GetRunPath(args) + if not run_path: continue + sitePath = public.M('sites').where('name=?', c_name).getField('path') + if not sitePath: continue + to_path = "{}/{}".format(sitePath, run_path) + return to_path + return False + + def get_site_id(self, domains): + site_ids = [] + for domain in domains: + if '*' in domain: + continue + site_id = public.M('domain').where('name=?', (domain,)).field('pid').select() + if not site_id: + continue + site_ids.append(site_id[0]['pid']) + if not site_ids: + return False + site_ids = list(set(site_ids)) + if not len(site_ids) == 1: + return False + return site_ids[0] + + def get_site_runpath(self, domains): + site_id = self.get_site_id(domains) + if not site_id: + return False + import panelSite + from collections import namedtuple + ps = panelSite.panelSite() + # 构造一个类 + get = namedtuple("get", ["id"]) + get.id = site_id + site_path = public.M('sites').where('id=?', (get.id,)).field('path').select()[0]['path'] + runpath = ps.GetRunPath(get) + return site_path + runpath + + def find_site_stopped(self, domains): + site_id = self.get_site_id(domains) + if not site_id: + return False + site_status = public.M('sites').where('id=?', (site_id,)).field('status').select()[0]['status'] + return site_status + + def get_index(self, domains): + ''' + @name 获取标识 + @author hwliang<2022-02-10> + @param domains 域名列表 + @return string + ''' + identifiers = [] + for domain_name in domains: + identifiers.append({"type": 'dns', "value": domain_name}) + return public.md5(json.dumps(identifiers)) + + # 续签同品牌其它证书 + def renew_cert_other(self): + ''' + @name 续签同品牌其它证书 + @author hwliang<2022-02-10> + @return void + ''' + cert_path = "{}/vhost/cert".format(public.get_panel_path()) + if not os.path.exists(cert_path): return + new_time = time.time() + (86400 * 30) + n = 0 + if not 'orders' in self._config: self._config['orders'] = {} + import panelSite + siteObj = panelSite.panelSite() + args = public.dict_obj() + for siteName in os.listdir(cert_path): + try: + cert_file = '{}/{}/fullchain.pem'.format(cert_path, siteName) + if not os.path.exists(cert_file): continue # 无证书文件 + siteInfo = public.M('sites').where('name=?', siteName).find() + if not siteInfo: continue # 无网站信息 + cert_init = self.get_cert_init(cert_file) + if not cert_init: continue # 无法获取证书 + end_time = time.mktime(time.strptime(cert_init['notAfter'], '%Y-%m-%d')) + if end_time > new_time: continue # 未到期 + try: + if not cert_init['issuer'] in ['R3', "Let's Encrypt"] and cert_init['issuer'].find( + "Let's Encrypt") == -1: + continue # 非同品牌证书 + except: + continue + + if isinstance(cert_init['dns'], str): cert_init['dns'] = [cert_init['dns']] + index = self.get_index(cert_init['dns']) + if index in self._config['orders'].keys(): continue # 已在订单列表 + + n += 1 + write_log("|-Renewing additional certificate {}, domain name:{}..".format(n, cert_init['subject'])) + write_log("|-Creating order..") + args.id = siteInfo['id'] + runPath = siteObj.GetRunPath(args) + if runPath and not runPath in ['/']: + path = siteInfo['path'] + '/' + runPath + else: + path = siteInfo['path'] + + self.renew_cert_to(cert_init['dns'], 'http', path.replace('//', '/')) + except: + write_log("|-Renewal failed:") + + # 关闭强制https + def close_httptohttps(self, siteName): + try: + + if not siteName: siteName + import panelSite + site_obj = panelSite.panelSite() + if not site_obj.IsToHttps(siteName): + return False + get = public.dict_obj() + get.siteName = siteName + site_obj.CloseToHttps(get) + return True + except: + return False + + # 恢复强制https + def rep_httptohttps(self, siteName): + try: + if not siteName: return False + import panelSite + site_obj = panelSite.panelSite() + if not site_obj.IsToHttps(siteName): + get = public.dict_obj() + get.siteName = siteName + site_obj.HttpToHttps(get) + return True + except: + return False + + def renew_cert_to(self, domains, auth_type, auth_to, index=None): + siteName = None + cert = {} + if os.path.exists(auth_to): + if public.M('sites').where('path=?', auth_to).count() == 1: + site_id = public.M('sites').where('path=?', auth_to).getField('id') + siteName = public.M('sites').where('path=?', auth_to).getField('name') + import panelSite + siteObj = panelSite.panelSite() + args = public.dict_obj() + args.id = site_id + runPath = siteObj.GetRunPath(args) + if runPath and not runPath in ['/']: + path = auth_to + '/' + runPath + if os.path.exists(path): auth_to = path.replace('//', '/') + + else: + siteName = self.get_site_name_by_domains(domains) + is_rep = self.close_httptohttps(siteName) + try: + index = self.create_order( + domains, + auth_type, + auth_to.replace('//', '/'), + index + ) + + write_log("|-Getting verification information..") + self.get_auths(index) + write_log("|-Verifying domain name..") + self.auth_domain(index) + write_log("|-Sending CSR..") + self.remove_dns_record() + self.send_csr(index) + write_log("|-Downloading certificate..") + cert = self.download_cert(index) + self._config['orders'][index]['renew_time'] = int(time.time()) + + # 清理失败重试记录 + self._config['orders'][index]['retry_count'] = 0 + self._config['orders'][index]['next_retry_time'] = 0 + + # 保存证书配置 + self.save_config() + cert['status'] = True + cert['msg'] = 'Renewed successfully!' + write_log("|-Renewed successfully!!") + except Exception as e: + if str(e).find('please try again later') == -1: # 受其它证书影响和连接CA失败的的不记录重试次数 + if index: + # 设置下次重试时间 + self._config['orders'][index]['next_retry_time'] = int(time.time() + (86400 * 2)) + # 记录重试次数 + if not 'retry_count' in self._config['orders'][index].keys(): + self._config['orders'][index]['retry_count'] = 1 + self._config['orders'][index]['retry_count'] += 1 + # 保存证书配置 + self.save_config() + msg = str(e).split('>>>>')[0] + write_log("|-" + msg) + return public.returnMsg(False, msg) + finally: + if is_rep: self.rep_httptohttps(siteName) + write_log("-" * 70) + return cert + + # 续签证书 + def renew_cert(self, index): + write_log("", "wb+") + try: + order_index = [] + if index: + if type(index) != str: + index = index.index + # 在面板点击申请证书时不要重启面板以防后续请求出错 + self._by_panel = True + if index not in self._config['orders']: + raise Exception( + public.get_msg_gettext('The specified order number does not exist and cannot be renewed!')) + order_index.append(index) + else: + s_time = time.time() + (30 * 86400) + if not 'orders' in self._config: self._config['orders'] = {} + for i in self._config['orders'].keys(): + if not 'save_path' in self._config['orders'][i]: + continue + if 'cert' in self._config['orders'][i]: + self._config['orders'][i]['cert_timeout'] = self._config['orders'][i]['cert']['cert_timeout'] + if not 'cert_timeout' in self._config['orders'][i]: + self._config['orders'][i]['cert_timeout'] = int(time.time()) + if self._config['orders'][i]['cert_timeout'] > s_time or self._config['orders'][i][ + 'auth_to'] == 'dns': + continue + if self.find_site_stopped(self._config['orders'][i]['domains']) == '0': + write_log("|-The website has been suspended, skip certificate renewal!") + continue + + # 已删除的网站直接跳过续签 + if self._config['orders'][i]['auth_to'].find('|') == -1 and self._config['orders'][i][ + 'auth_to'].find('/') != -1: + #if not os.path.exists(self._config['orders'][i]['auth_to']): + # ^——这个不能判断网站已被删除,但文件夹未删除的问题 + _auth_to = self.get_ssl_used_site(self._config['orders'][i]['save_path']) + if not _auth_to: continue + + # 域名不存在? + for domain in self._config['orders'][i]['domains']: + if domain.find('*') != -1: break + if not public.M('domain').where("name=?", (domain,)).count() and not public.M( + 'binding').where("domain=?", domain).count(): + _auth_to = None + write_log("|-Skip deleted domains:{}".format(self._config['orders'][i]['domains'])) + if not _auth_to: continue + + self._config['orders'][i]['auth_to'] = _auth_to + + # 是否到了允许重试的时间 + if 'next_retry_time' in self._config['orders'][i]: + timeout = self._config['orders'][i]['next_retry_time'] - int(time.time()) + if timeout > 0: + write_log( + '|-The domain name skipped this time: {}, because the last renewal failed, you still need to wait {} hours and try again'.format( + self._config['orders'][i]['domains'], int(timeout / 60 / 60))) + continue + + # # 是否到了最大重试次数 + # if 'retry_count' in self._config['orders'][i]: + # if self._config['orders'][i]['retry_count'] >= 5: + # write_log('|-Skip the domain name this time: {}, this certificate will not be renewed due to failure to renew 5 times in a row, (The number of errors will be reset after manual renewal and success)'.format(self._config['orders'][i]['domains'])) + # continue + + # 加入到续签订单 + order_index.append(i) + if not order_index: + write_log(public.get_msg_gettext('|-No SSL certificate found within 30 days!')) + self.get_apis() + self.renew_cert_other() + write_log("|-All tasks have been processed!") + return public.return_message(0,0,'No SSL certificate found within 30 days!') + write_log( + public.get_msg_gettext('|-A total of {} certificates need to be renewed', (str(len(order_index)),))) + n = 0 + self.get_apis() + cert = None + args = public.to_dict_obj({}) + for index in order_index: + args.domains = json.dumps(self._config['orders'][index]['domains']) + args.auth_type = self._config['orders'][index]['auth_type'] + args.auth_to = self._config['orders'][index]['auth_to'] + sitename = args.auth_to.split('/')[-1] + if not sitename: + sitename = self._config['orders'][index]['auth_to'].split('/')[-2] + args.siteName = sitename + write_log('|-Renew the visa certificate and start checking the environment') + self.check_auth_env(args, check=True) + n += 1 + + domains = _test_domains(self._config['orders'][index]['domains'], self._config['orders'][index]['auth_to'],self._config['orders'][index]['auth_type']) + if len(domains) == 0: + write_log("|-The domain name under the {} certificate is not used (the domain name is: [%s]) and has been skipped.".format(n, ",".join(self._config['orders'][index]['domains']))) + continue + else: + self._config['orders'][index]['domains'] = domains + write_log(public.get_msg_gettext('|-Renewing certificate number of {},domain: {}..', + (str(n), str(self._config['orders'][index]['domains'])))) + write_log(public.get_msg_gettext('|-Creating order..')) + + cert = self.renew_cert_to(self._config['orders'][index]['domains'], + self._config['orders'][index]['auth_type'], + self._config['orders'][index]['auth_to'], index) + + # write_log(public.get_msg_gettext('|-Renewing certificate number of {},domain: {}..', + # (str(n), str(self._config['orders'][index]['domains'])))) + # write_log(public.get_msg_gettext('|-Creating order..')) + # cert = self.renew_cert_to(self._config['orders'][index]['domains'], + # self._config['orders'][index]['auth_type'], + # self._config['orders'][index]['auth_to'], index) + + # aapanel 用 + try: + self.turnon_redirect_proxy_httptohttps(args) + except: + pass + + return cert + except Exception as ex: + self.remove_dns_record() + ex = str(ex) + if ex.find(">>>>") != -1: + msg = ex.split(">>>>") + msg[1] = json.loads(msg[1]) + else: + msg = ex + write_log(public.get_error_info()) + return_message=public.return_msg_gettext(False, msg) + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + +def _test_domains(domains, auth_to, auth_type): + # 检查站点域名变更情况, 若有删除域名,则在续签时,删除已经不使用的域名,再执行续签任务 + # 是dns验证的跳过 + if auth_to.find("|") != -1: + return domains + # 是泛域名的跳过 + for domain in domains: + if domain.find("*.") != -1: + return domains + sql = public.M('domain') + site_sql = public.M('sites') + for domain in domains: + pid = sql.where('name=?', domain).getField('pid') + if pid and site_sql.where('id=?',pid).find(): + site_domains = [i["name"] for i in sql.where('pid=?',(pid,)).field("name").select()] + break + else: + site_id = site_sql.where('path=?', auth_to).getField('id') + if bool(site_id) and str(site_id).isdigit(): + site_domains = [i["name"] for i in sql.where('pid=?',(site_id,)).field("name").select()] + else: + return [] + + del_domains = list(set(domains) - set(site_domains)) + for i in del_domains: + domains.remove(i) + return domains + + +def echo_err(msg): + write_log("\033[31m=" * 65) + write_log("|-error: {}\033[0m".format(msg)) + exit() + + +# 写日志 +def write_log(log_str, mode="ab+"): + if __name__ == "__main__": + print(log_str) + return + _log_file = 'logs/letsencrypt.log' + f = open(_log_file, mode) + log_str += "\n" + f.write(log_str.encode('utf-8')) + f.close() + return True + + +if __name__ == "__main__": + import argparse + + p = argparse.ArgumentParser(usage=public.get_msg_gettext( + 'Required parameters: --domain list of domain names, multiple separated by commas!')) + p.add_argument('--domain', default=None, + help=public.get_msg_gettext('Please specify the domain name to apply for a certificate'), + dest="domains") + p.add_argument('--type', default=None, help=public.get_msg_gettext('Please specify verification type'), + dest="auth_type") + p.add_argument('--path', default=None, help=public.get_msg_gettext('Please specify the website document root'), + dest="path") + p.add_argument('--dnsapi', default=None, help=public.get_msg_gettext('Please specify DNSAPI'), dest="dnsapi") + p.add_argument('--dns_key', default=None, help=public.get_msg_gettext('Please specify DNSAPI key'), dest="key") + p.add_argument('--dns_secret', default=None, help=public.get_msg_gettext('Please specify DNSAPI secret'), + dest="secret") + p.add_argument('--index', default=None, help=public.get_msg_gettext('Specify the order index'), dest="index") + p.add_argument('--renew', default=None, help=public.get_msg_gettext('renew certificate'), dest="renew") + p.add_argument('--revoke', default=None, help=public.get_msg_gettext('Revoke certificate'), dest="revoke") + args = p.parse_args() + cert = None + if args.revoke: + if not args.index: + echo_err( + public.get_msg_gettext('Please enter the index of the order to be revoked in the --index parameter')) + p = acme_v2() + result = p.revoke_order(args.index) + write_log(result) + exit() + + if args.renew: + p = acme_v2() + p.renew_cert(args.index) + else: + try: + if not args.index: + if not args.domains: + echo_err(public.get_msg_gettext( + 'Please specify the domain name for which you want to apply for a certificate in the --domain parameter, multiple separated by commas (,)')) + if not args.auth_type in ['http', 'tls', 'dns']: + echo_err(public.get_msg_gettext( + 'Please specify the correct authentication type in the --type parameter, supporting dns and http')) + auth_to = '' + if args.auth_type in ['http', 'tls']: + if not args.path: + echo_err( + public.get_msg_gettext('Please specify the website document root in the --path parameter!')) + if not os.path.exists(args.path): + echo_err(public.get_msg_gettext('The specified site root does not exist, please check: {}', + (args.path,))) + auth_to = args.path + else: + if args.dnsapi == '0': + auth_to = 'dns' + else: + if not args.key: + echo_err(public.get_msg_gettext( + 'When applying using dnsapi, specify the dnsapi key in the --dns_key parameter!')) + if not args.secret: + echo_err(public.get_msg_gettext( + 'When applying using dnsapi, specify the secret of dnsapi in the --dns_secret parameter!')) + auth_to = "{}|{}|{}".format( + args.dnsapi, args.key, args.secret) + + domains = args.domains.strip().split(',') + p = acme_v2() + cert = p.apply_cert( + domains, auth_type=args.auth_type, auth_to=auth_to) + if args.dnsapi == '0': + acme_txt = '_acme-challenge.' + acme_caa = '1 issue letsencrypt.org' + write_log("=" * 65) + write_log("\033[32m" + public.get_msg_gettext( + '|-Manual order submission is successful, please resolve DNS records according to the following tips: ') + "\033[0m") + write_log("=" * 65) + write_log(public.get_msg_gettext('|-Order index: {}', (cert['index'],))) + write_log(public.get_msg_gettext('|-Retry the command') + ": ./acme_v2.py --index=\"{}\"".format( + cert['index'])) + write_log(public.get_msg_gettext( + '|-A total of \033[36m{}\033[0m domain name records need to be resolved.', + (len(cert['auths']),))) + for i in range(len(cert['auths'])): + write_log('-' * 70) + write_log(public.get_msg_gettext( + '|-The \033[36m{}\033[0m domain names are: {}, please resolve the following information: ', + (str(i + 1), cert['auths'][i]['domain']))) + write_log(public.get_msg_gettext( + '|-Record Type: TXT Record Name: \033[41m{}\033[0m Record Value: \033[41m{}\033 [0m [Required]', + (acme_txt + cert['auths'][i]['domain'].replace('*.', ''), cert['auths'][i]['auth_value']))) + write_log(public.get_msg_gettext( + '|-Record type: CAA Record name: \033[41m{}\033[0m Record value: \033[41m{}\033[0m [Optional]', + (cert['auths'][i]['domain'].replace('*.', ''), acme_caa))) + write_log('-' * 70) + input_data = "" + while input_data not in ['y', 'Y', 'n', 'N']: + input_msg = public.get_msg_gettext( + 'Please wait 2-3 minutes after completing the resolution and enter Y and press Enter to continue verifying the domain name: ') + if sys.version_info[0] == 2: + input_data = raw_input(input_msg) + else: + input_data = input(input_msg) + if input_data in ['n', 'N']: + write_log("=" * 65) + write_log(public.get_msg_gettext('|-The user abandons the application and exits the program!')) + exit() + cert = p.apply_cert( + [], auth_type=args.auth_type, auth_to='dns', index=cert['index']) + else: + # 重新验证 + p = acme_v2() + cert = p.apply_cert([], auth_type='dns', + auth_to='dns', index=args.index) + except Exception as ex: + write_log("|-{}".format(public.get_error_info())) + exit() + if not cert: + exit() + write_log("=" * 65) + write_log(public.get_msg_gettext('|-Certificate obtained successfully!')) + write_log("=" * 65) + write_log(public.get_msg_gettext('Certified Domain Name: {}', (','.join(cert['domains']),))) + write_log( + public.get_msg_gettext('Certificate expiration time: {}', (public.format_date(times=cert['cert_timeout']),))) + write_log(public.get_msg_gettext('Certificate saved at: {}/', (cert['save_path'],))) diff --git a/class_v2/ajax_v2.py b/class_v2/ajax_v2.py new file mode 100644 index 00000000..9315f9a5 --- /dev/null +++ b/class_v2/ajax_v2.py @@ -0,0 +1,1956 @@ + #coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +from flask import session,request +import public,os,json,time,apache,psutil +from public.validate import Param +class ajax: + __official_url = 'https://brandnew.aapanel.com' + + def GetApacheStatus(self,get): + a = apache.apache() + return a.GetApacheStatus() + + def GetProcessCpuPercent(self,i,process_cpu): + try: + pp = psutil.Process(i) + if pp.name() not in process_cpu.keys(): + process_cpu[pp.name()] = float(pp.cpu_percent(interval=0.01)) + process_cpu[pp.name()] += float(pp.cpu_percent(interval=0.01)) + except: + pass + def GetNginxStatus(self,get): + try: + if not os.path.exists('/www/server/nginx/sbin/nginx'): return public.return_msg_gettext(False,'Nginx is not install') + process_cpu = {} + worker = int(public.ExecShell("ps aux|grep nginx|grep 'worker process'|wc -l")[0])-1 + workermen = int(public.ExecShell("ps aux|grep nginx|grep 'worker process'|awk '{memsum+=$6};END {print memsum}'")[0]) / 1024 + for proc in psutil.process_iter(): + if proc.name() == "nginx": + self.GetProcessCpuPercent(proc.pid,process_cpu) + time.sleep(0.1) + #取Nginx负载状态 + self.CheckStatusConf() + result = public.httpGet('http://127.0.0.1/nginx_status') + is_curl = False + tmp = [] + if result: + tmp = result.split() + if len(tmp) < 15: is_curl = True + + if is_curl: + result = public.ExecShell( + 'curl http://127.0.0.1/nginx_status')[0] + tmp = result.split() + data = {} + if "request_time" in tmp: + data['accepts'] = tmp[8] + data['handled'] = tmp[9] + data['requests'] = tmp[10] + data['Reading'] = tmp[13] + data['Writing'] = tmp[15] + data['Waiting'] = tmp[17] + else: + data['accepts'] = tmp[9] + data['handled'] = tmp[7] + data['requests'] = tmp[8] + data['Reading'] = tmp[11] + data['Writing'] = tmp[13] + data['Waiting'] = tmp[15] + data['active'] = tmp[2] + data['worker'] = worker + data['workercpu'] = round(float(process_cpu["nginx"]), 2) + data['workermen'] = "%s%s" % (int(workermen), "MB") + return data + except Exception as ex: + public.write_log_gettext('Get Info','Nginx load status acquisition failed:{}',(ex,)) + return public.return_msg_gettext(False,'Data acquisition failed!') + + def GetPHPStatus(self,get): + #取指定PHP版本的负载状态 + try: + version = get.version + uri = "/phpfpm_"+version+"_status?json" + result = public.request_php(version,uri,'') + tmp = json.loads(result) + fTime = time.localtime(int(tmp['start time'])) + tmp['start time'] = time.strftime('%Y-%m-%d %H:%M:%S',fTime) + return tmp + except Exception as ex: + public.write_log_gettext('Get Info',"PHP load status acquisition failed: {}",(public.get_error_info(),)) + return public.return_msg_gettext(False,'PHP load status acquisition failed!') + + def CheckStatusConf(self): + if public.get_webserver() != 'nginx': return + filename = session['setupPath'] + '/panel/vhost/nginx/phpfpm_status.conf' + if os.path.exists(filename): + if public.ReadFile(filename).find('nginx_status')!=-1: return + + conf = '''server { + listen 80; + server_name 127.0.0.1; + allow 127.0.0.1; + location /nginx_status { + stub_status on; + access_log off; + } +}''' + public.writeFile(filename,conf) + public.serviceReload() + + + def GetTaskCount(self,get): + # 校验参数 + try: + get.validate([ + Param('action').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + #取任务数量 + return public.return_message(0,0,public.M('tasks').where("status!=?",('1',)).count()) + + def GetSoftList(self,get): + #取软件列表 + import json,os + tmp = public.readFile('data/softList.conf') + data = json.loads(tmp) + tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select() + for i in range(len(data)): + data[i]['check'] = public.GetConfigValue('root_path')+'/'+data[i]['check'] + for n in range(len(data[i]['versions'])): + #处理任务标记 + isTask = '1' + for task in tasks: + tmp = public.getStrBetween('[',']',task['name']) + if not tmp:continue + tmp1 = tmp.split('-') + if data[i]['name'] == 'PHP': + if tmp1[0].lower() == data[i]['name'].lower() and tmp1[1] == data[i]['versions'][n]['version']: isTask = task['status']; + else: + if tmp1[0].lower() == data[i]['name'].lower(): isTask = task['status'] + + #检查安装状态 + if data[i]['name'] == 'PHP': + data[i]['versions'][n]['task'] = isTask + checkFile = data[i]['check'].replace('VERSION',data[i]['versions'][n]['version'].replace('.','')) + else: + data[i]['task'] = isTask + version = public.readFile(public.GetConfigValue('root_path')+'/server/'+data[i]['name'].lower()+'/version.pl') + if not version:continue + if version.find(data[i]['versions'][n]['version']) == -1:continue + checkFile = data[i]['check'] + data[i]['versions'][n]['status'] = os.path.exists(checkFile) + return public.return_message(0,0,data) + + + def GetLibList(self,get): + #取插件列表 + import json,os + tmp = public.readFile('data/libList.conf') + data = json.loads(tmp) + for i in range(len(data)): + data[i]['status'] = self.CheckLibInstall(data[i]['check']) + data[i]['optstr'] = self.GetLibOpt(data[i]['status'], data[i]['opt']) + return data + + def CheckLibInstall(self,checks): + for cFile in checks: + if os.path.exists(cFile): return public.GetMsg('Already installed') + return public.GetMsg('Not installed') + + #取插件操作选项 + def GetLibOpt(self,status,libName): + optStr = '' + if status == public.GetMsg('Not installed'): + optStr = ''+public.GetMsg('Uninstallaton succeeded')+'' + else: + libConfig = public.GetMsg('Old configuration') + if(libName == 'beta'): libConfig = public.GetMsg('Beta tester profile') + + optStr = ''+libConfig+' | '+public.get_msg_gettext("Uninstallaton succeeded")+''; + return optStr + + #取插件AS + def GetQiniuAS(self,get): + filename = public.GetConfigValue('setup_path') + '/panel/data/'+get.name+'As.conf' + if not os.path.exists(filename): public.writeFile(filename,'') + data = {} + data['AS'] = public.readFile(filename).split('|') + data['info'] = self.GetLibInfo(get.name) + if len(data['AS']) < 3: + data['AS'] = ['','','',''] + return data + + + #设置插件AS + def SetQiniuAS(self,get): + info = self.GetLibInfo(get.name) + filename = public.GetConfigValue('setup_path') + '/panel/data/'+get.name+'As.conf' + conf = get.access_key.strip() + '|' + get.secret_key.strip() + '|' + get.bucket_name.strip() + '|' + get.bucket_domain.strip() + public.writeFile(filename,conf) + if not os.path.exists(filename): + return public.return_msg_gettext(False, 'write file failed!') + public.ExecShell("chmod 600 " + filename) + result = public.ExecShell(public.get_python_bin() + " " + public.GetConfigValue('setup_path') + "/panel/script/backup_"+get.name+".py list") + + if result[0].find("ERROR:") == -1: + public.write_log_gettext("Plugin manager","Set plugin [" +info['name']+ "]AS!") + return public.return_msg_gettext(True, 'Successfully set') + return public.return_msg_gettext(False,'ERROR: Unable to connect to the {} server, please check if the [AK/SK/Storage] setting is correct!',(info['name'],)) + + #设置内测 + def SetBeta(self,get): + data = {} + data['username'] = get.bbs_name + data['qq'] = get.qq + data['email'] = get.email + result = public.httpPost(public.GetConfigValue('home') + '/Api/LinuxBeta',data) + import json + data = json.loads(result) + if data['status']: + public.writeFile('data/beta.pl',get.bbs_name + '|' + get.qq + '|' + get.email) + return data + #取内测资格状态 + def GetBetaStatus(self,get): + try: + return public.readFile('data/beta.pl').strip() + except: + return 'False' + + + #获取指定插件信息 + def GetLibInfo(self,name): + import json + tmp = public.readFile('data/libList.conf') + data = json.loads(tmp) + for lib in data: + if name == lib['opt']: return lib + return False + + #获取文件列表 + def GetQiniuFileList(self,get): + try: + import json + result = public.ExecShell(public.get_python_bin() + " " + public.GetConfigValue('setup_path') + "/panel/script/backup_"+get.name+".py list") + return json.loads(result[0]) + except: + return public.return_msg_gettext(False, 'Failed to get the list, please check if the [AK/SK/Storage] setting is correct!') + + + + #取网络连接列表 + def GetNetWorkList(self,get): + import psutil + netstats = psutil.net_connections() + networkList = [] + for netstat in netstats: + tmp = {} + if netstat.type == 1: + tmp['type'] = 'tcp' + else: + tmp['type'] = 'udp' + tmp['family'] = netstat.family + tmp['laddr'] = netstat.laddr + tmp['raddr'] = netstat.raddr + tmp['status'] = netstat.status + p = psutil.Process(netstat.pid) + tmp['process'] = p.name() + tmp['pid'] = netstat.pid + networkList.append(tmp) + del (p) + del (tmp) + networkList = sorted(networkList, + key=lambda x: x['status'], + reverse=True) + return networkList + + #取进程列表 + def GetProcessList(self, get): + import psutil, pwd + Pids = psutil.pids() + + processList = [] + for pid in Pids: + try: + tmp = {} + p = psutil.Process(pid) + if p.exe() == "": continue + + tmp['name'] = p.name() + #进程名称 + if self.GoToProcess(tmp['name']): continue + + tmp['pid'] = pid + #进程标识 + tmp['status'] = p.status() + #进程状态 + tmp['user'] = p.username() + #执行用户 + cputimes = p.cpu_times() + tmp['cpu_percent'] = p.cpu_percent(0.1) + tmp['cpu_times'] = cputimes.user #进程占用的CPU时间 + tmp['memory_percent'] = round(p.memory_percent(), + 3) #进程占用的内存比例 + pio = p.io_counters() + tmp['io_write_bytes'] = pio.write_bytes #进程总共写入字节数 + tmp['io_read_bytes'] = pio.read_bytes #进程总共读取字节数 + tmp['threads'] = p.num_threads() #进程总线程数 + + processList.append(tmp) + del (p) + del (tmp) + except: + continue + import operator + processList = sorted(processList, + key=lambda x: x['memory_percent'], + reverse=True) + processList = sorted(processList, + key=lambda x: x['cpu_times'], + reverse=True) + return processList + + #结束指定进程 + def KillProcess(self, get): + #return public.returnMsg(False,'演示服务器,禁止此操作!'); + import psutil + p = psutil.Process(int(get.pid)) + name = p.name() + if name == 'python': return public.return_msg_gettext(False,'Error, cannot end task processes!') + + p.kill() + public.write_log_gettext('Task manager','Ended processes[{}][{}] Successfully!',(get.pid,name)) + return public.return_msg_gettext(True,'Ended processes[{}][{}] Successfully!',(get.pid,name)) + + def GoToProcess(self,name): + ps = ['sftp-server','login','nm-dispatcher','irqbalance','qmgr','wpa_supplicant','lvmetad','auditd','master','dbus-daemon','tapdisk','sshd','init','ksoftirqd','kworker','kmpathd','kmpath_handlerd','python','kdmflush','bioset','crond','kthreadd','migration','rcu_sched','kjournald','iptables','systemd','network','dhclient','systemd-journald','NetworkManager','systemd-logind','systemd-udevd','polkitd','tuned','rsyslogd'] + + for key in ps: + if key == name: return True + + return False + + + def GetNetWorkIo(self,get): + #取指定时间段的网络Io + data = public.M('network').dbfile('system').where( + "addtime>=? AND addtime<=?", (get.start, get.end) + ).field( + 'id,up,down,total_up,total_down,down_packets,up_packets,addtime' + ).order('id desc').select() + return self.ToAddtime(data, None) + + def GetDiskIo(self, get): + #取指定时间段的磁盘Io + __OPT_FIELD = "*" + tmp_cols = public.M('diskio').dbfile('system').query( + 'PRAGMA table_info(diskio)', ()) + cols = [] + for col in tmp_cols: + if len(col) > 2: cols.append('`' + col[1] + '`') + if len(cols) > 0: + cols.append("disk_top") + __OPT_FIELD = ','.join(cols) + data = public.M('diskio').dbfile('system').query( + "SELECT diskio.*,process_top_list.disk_top from diskio inner join process_top_list on diskio.addtime=process_top_list.addtime where diskio.addtime>={} AND diskio.addtime<={} ORDER BY diskio.addtime desc;" + .format(get.start, get.end), ()) + if isinstance(data, str) and data.find( + 'error: no such table: process_top_list') != -1: + return public.M('diskio').dbfile('system').where( + "addtime>=? AND addtime<=?", (get.start, get.end) + ).field( + 'id,read_count,write_count,read_bytes,write_bytes,read_time,write_time,addtime' + ).order('id asc').select() + try: + if __OPT_FIELD != "*": + fields = self.__format_field(__OPT_FIELD.split(',')) + tmp = [] + for row in data: + i = 0 + tmp1 = {} + for key in fields: + tmp1[key.strip('`')] = row[i] + i += 1 + tmp.append(tmp1) + del (tmp1) + data = tmp + except: + return [] + return self.ToAddtime(data, True, 'disk') + + + def __format_field(self,field): + import re + fields = [] + for key in field: + s_as = re.search(r'\s+as\s+',key,flags=re.IGNORECASE) + if s_as: + as_tip = s_as.group() + key = key.split(as_tip)[1] + fields.append(key) + return fields + + def GetCpuIo(self, get): + #取指定时间段的CpuIo + __OPT_FIELD = "*" + tmp_cols = public.M('cpuio').dbfile('system').query( + 'PRAGMA table_info(cpuio)', ()) + cols = [] + for col in tmp_cols: + if len(col) > 2: cols.append('`' + col[1] + '`') + if len(cols) > 0: + cols.append("cpu_top") + cols.append("memory_top") + __OPT_FIELD = ','.join(cols) + data = public.M('cpuio').dbfile('system').query( + "SELECT cpuio.*,process_top_list.cpu_top,process_top_list.memory_top from cpuio inner join process_top_list on cpuio.addtime=process_top_list.addtime where cpuio.addtime>={} AND cpuio.addtime<={} ORDER BY cpuio.addtime desc;" + .format(get.start, get.end), ()) + if isinstance(data, str) and data.find( + 'error: no such table: process_top_list') != -1: + return public.M('cpuio').dbfile('system').where( + "addtime>=? AND addtime<=?", + (get.start, get.end + )).field('id,pro,mem,addtime').order('id asc').select() + try: + if __OPT_FIELD != "*": + fields = self.__format_field(__OPT_FIELD.split(',')) + tmp = [] + for row in data: + i = 0 + tmp1 = {} + for key in fields: + tmp1[key.strip('`')] = row[i] + i += 1 + tmp.append(tmp1) + del (tmp1) + data = tmp + except: + return [] + return self.ToAddtime(data, True, 'cpu') + + def get_load_average(self, get): + __OPT_FIELD = "*" + tmp_cols = public.M('load_average').dbfile('system').query( + 'PRAGMA table_info(load_average)', ()) + cols = [] + for col in tmp_cols: + if len(col) > 2: cols.append('`' + col[1] + '`') + if len(cols) > 0: + cols.append("cpu_top") + __OPT_FIELD = ','.join(cols) + data = public.M('load_average').dbfile('system').query( + "SELECT load_average.*,process_top_list.cpu_top from load_average inner join process_top_list on load_average.addtime=process_top_list.addtime where load_average.addtime>={} AND load_average.addtime<={} ORDER BY load_average.addtime desc;" + .format(get.start, get.end), ()) + if isinstance(data, str) and data.find( + 'error: no such table: process_top_list') != -1: + return public.M('load_average').dbfile('system').where( + "addtime>=? AND addtime<=?", + (get.start, get.end)).field('id,pro,one,five,fifteen,addtime' + ).order('id asc').select() + try: + if __OPT_FIELD != "*": + fields = self.__format_field(__OPT_FIELD.split(',')) + tmp = [] + for row in data: + i = 0 + tmp1 = {} + for key in fields: + tmp1[key.strip('`')] = row[i] + i += 1 + tmp.append(tmp1) + del (tmp1) + data = tmp + except: + return [] + return self.ToAddtime(data, True, 'cpu') + + def get_process_tops(self, get): + ''' + @name 获取进程开销排行 + @author hwliang<2021-09-07> + @param get{ + start: int<开始时间> + end: int<结束时间> + } + @return list + ''' + data = public.M('process_tops').dbfile('system').where( + "addtime>=? AND addtime<=?", + (get.start, get.end + )).field('id,process_list,addtime').order('id asc').select() + return self.ToAddtime(data) + + def get_process_cpu_high(self, get): + ''' + @name 获取CPU占用高的进程列表 + @author hwliang<2021-09-07> + @param get{ + start: int<开始时间> + end: int<结束时间> + } + @return list + ''' + data = public.M('process_high_percent').dbfile('system').where( + "addtime>=? AND addtime<=?", (get.start, get.end)).field( + 'id,name,pid,cmdline,cpu_percent,memory,cpu_time_total,addtime' + ).order('id asc').select() + return self.ToAddtime(data) + + def ToAddtime(self, data, tomem=False, types=None): + import time + #格式化addtime列 + + if tomem: + import psutil + mPre = (psutil.virtual_memory().total / 1024 / 1024) / 100 + length = len(data) + he = 1 + if length > 100: he = 1 + if length > 1000: he = 3 + if length > 10000: he = 15 + if he == 1: + for i in range(length): + try: + if types: + key = '{}_top'.format(types) + if key in data[i]: + data[i][key] = json.loads(data[i][key]) + if 'memory_top' in data[i]: + data[i]['memory_top'] = json.loads( + data[i]['memory_top']) + data[i]['addtime'] = time.strftime( + '%m/%d %H:%M', + time.localtime(float(data[i]['addtime']))) + if 'process_list' in data[i]: + data[i]['process_list'] = json.loads( + data[i]['process_list']) + if tomem and data[i]['mem'] > 100: + data[i]['mem'] = data[i]['mem'] / mPre + if tomem in [None]: + if type(data[i]['down_packets']) == str: + data[i]['down_packets'] = json.loads( + data[i]['down_packets']) + data[i]['up_packets'] = json.loads( + data[i]['up_packets']) + except: + continue + return data + else: + count = 0 + tmp = [] + couns = 0 + for value in data: + if count < he: # 0 1 2 + count += 1 + #cpu大于60的时候,随机取 + if types == "cpu" and 'pro' in value and value['pro'] > 60: + couns += 1 + #he等于3 的时候 百分之50的概率取 当he等于15的时候 百分之33的概率取 + if (he == 3 + and couns % 2 == 0) or (he == 15 + and couns % 3 == 0): + if types: + key = '{}_top'.format(types) + if key in value: + value[key] = json.loads(value[key]) + if 'memory_top' in value: + value['memory_top'] = json.loads( + value['memory_top']) + value['addtime'] = time.strftime( + '%m/%d %H:%M', + time.localtime(float(value['addtime']))) + if tomem and 'mem' in value and value['mem'] > 100: + value['mem'] = value['mem'] / mPre + if tomem in [None]: + if type(value['down_packets']) == str: + value['down_packets'] = json.loads(value['down_packets']) + value['up_packets'] = json.loads(value['up_packets']) + tmp.append(value) + continue + try: + if types: + key='{}_top'.format(types) + if key in value: + value[key] = json.loads(value[key]) + if 'memory_top' in value: + value['memory_top'] = json.loads(value['memory_top']) + value['addtime'] = time.strftime('%m/%d %H:%M',time.localtime(float(value['addtime']))) + if tomem and 'mem' in value and value['mem'] > 100: value['mem'] = value['mem'] / mPre + if tomem in [None]: + if type(value['down_packets']) == str: + value['down_packets'] = json.loads(value['down_packets']) + value['up_packets'] = json.loads(value['up_packets']) + tmp.append(value) + count = 0 + except: continue + return tmp + + + + def GetInstalleds(self,softlist): + softs = '' + for soft in softlist['data']: + try: + for v in soft['versions']: + if v['status']: softs += soft['name'] + '-' + v['version'] + '|' + except: + pass + return softs + + + + #获取SSH爆破次数 + def get_ssh_intrusion(self): + fp = open('/var/log/secure','rb') + l = fp.readline() + intrusion_total = 0 + while l: + if l.find('Failed password for root') != -1: intrusion_total += 1 + l = fp.readline() + fp.close() + return intrusion_total + + #申请内测版 + def apple_beta(self,get): + try: + # userInfo = json.loads(public.ReadFile('data/userInfo.json')) + # p_data = {} + # p_data['uid'] = userInfo['uid'] + # p_data['access_key'] = userInfo['access_key'] + # p_data['username'] = userInfo['username'] + # result = public.HttpPost(public.GetConfigValue('home') + '/api/panel/apple_beta',p_data,5) + public.writeFile('/www/server/panel/data/is_beta.pl','true') + try: + return public.return_message(0,0,"Successful application!") + except: return public.return_message(-1,0,'Fail to connect to the server!') + except: return public.return_message(-1,0,'Please bind your account first!') + + def to_not_beta(self,get): + try: + # userInfo = json.loads(public.ReadFile('data/userInfo.json')) + # p_data = {} + # p_data['uid'] = userInfo['uid'] + # p_data['access_key'] = userInfo['access_key'] + # p_data['username'] = userInfo['username'] + # result = public.HttpPost(public.GetConfigValue('home') + '/api/panel/to_not_beta',p_data,5) + try: + beta_file = '/www/server/panel/data/is_beta.pl' + if os.path.exists(beta_file): + os.remove(beta_file) + return public.return_message(0,0, "Successful application!") + except: return public.return_message(-1,0,'Fail to connect to the server!') + except: return public.return_message(-1,0,'Please bind your account first!') + + def to_beta(self): + try: + userInfo = json.loads(public.ReadFile('data/userInfo.json')) + p_data = {} + p_data['uid'] = userInfo['uid'] + p_data['access_key'] = userInfo['access_key'] + p_data['username'] = userInfo['username'] + public.HttpPost(public.GetConfigValue('home') + '/api/panel/to_beta',p_data,5) + except: pass + + def get_uid(self): + try: + userInfo = json.loads(public.ReadFile('data/userInfo.json')) + return userInfo['uid'] + except: return 0 + + #获取最新的5条测试版更新日志 + def get_beta_logs(self,get): + try: + data = json.loads(public.HttpGet('{}/api/panel/getBetaVersionLogs'.format(self.__official_url))) + return public.return_message(0,0,data) + except: + return public.return_message(-1,0,'Fail to connect to the server!') + + def get_other_info(self): + other = {} + other['ds'] = [] + ds = public.M('domain').field('name').select() + for d in ds: + other['ds'].append(d['name']) + return ','.join(other['ds']) + + + + # 更新面板 + def UpdatePanel(self,get): + if 'check' in get: + # 校验参数 + try: + get.validate([ + Param('check').Bool(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + import json + conf_status = public.M('config').where("id=?",('1',)).field('status').find() + if int(session['config']['status']) == 0 and int(conf_status['status']) == 0: + public.arequests('get', '{}/api/setupCount/setupPanel?type=Linux'.format(self.__official_url)) + public.M('config').where("id=?",('1',)).setField('status',1) + + #取回远程版本信息 + if 'updateInfo' in session and hasattr(get,'check') == False: + updateInfo = session['updateInfo'] + else: + logs = public.get_debug_log() + import psutil,system,sys + mem = psutil.virtual_memory() + import panelPlugin + mplugin = panelPlugin.panelPlugin() + + mplugin.ROWS = 10000 + panelsys = system.system() + data = {} + data['ds'] = ''#self.get_other_info() + data['sites'] = str(public.M('sites').count()) + data['ftps'] = str(public.M('ftps').count()) + data['databases'] = str(public.M('databases').count()) + data['system'] = panelsys.GetSystemVersion() + '|' + str(mem.total / 1024 / 1024) + 'MB|' + str(public.getCpuType()) + '*' + str(psutil.cpu_count()) + '|' + str(public.get_webserver()) + '|' +session['version'] + data['system'] += '||'+self.GetInstalleds(mplugin.getPluginList(None)) + data['logs'] = logs + data['client'] = request.headers.get('User-Agent') + data['oem'] = '' + data['intrusion'] = 0 + data['uid'] = self.get_uid() + #msg = public.getMsg('Current version is stable version and already latest. Update cycle of stable version is generally 2 months,while developer version will update every Wednesday!'); + data['o'] = public.get_oem_name() + sUrl = '{}/api/panel/updateLinuxEn'.format(self.__official_url) + + updateInfoRaw = public.httpPost(sUrl, data, timeout=60) + + if not updateInfoRaw or len(updateInfoRaw) == 0: + return public.return_message(-1, 0, 'Failed to connect server! -1') + + try: + updateInfo = json.loads(updateInfoRaw) + except: + return public.return_message(-1, 0, 'Failed to connect server! -2') + + session['updateInfo'] = updateInfo + + # 判断是否测试版 + updateInfo['is_beta'] = 0 + + if os.path.exists('/www/server/panel/data/is_beta.pl'): + updateInfo['is_beta'] = 1 + + # 输出忽略的版本 + updateInfo['ignore'] = [] + no_path = '{}/data/no_update.pl'.format(public.get_panel_path()) + if os.path.exists(no_path): + try: + updateInfo['ignore'] = json.loads(public.readFile(no_path)) + except: + pass + + # 重启面板 默认开启系统监控 + public.writeFile('data/control.conf', '30') + + # 判断本地版本是否最新 + updateInfo['local_is_latest'] = False + + #检查是否需要升级 + if not hasattr(get,'toUpdate'): + if updateInfo['is_beta'] == 1: + if updateInfo['beta']['version'] == session['version']: + updateInfo['local_is_latest'] = True + else: + if updateInfo['version'] == session['version']: + updateInfo['local_is_latest'] = True + + return public.return_message(0, 0, updateInfo) + + + #是否执行升级程序 + if(updateInfo['force'] == True or hasattr(get,'toUpdate') == True or os.path.exists('data/autoUpdate.pl') == True): + if not public.IsRestart(): + return public.return_message(-1, 0, 'Please run the program when all install tasks finished!') + + if updateInfo['is_beta'] == 1: updateInfo['version'] = updateInfo['beta']['version'] + setupPath = public.GetConfigValue('setup_path') + uptype = 'update' + httpUrl = public.get_url() + if httpUrl: updateInfo['downUrl'] = httpUrl + '/install/' + uptype + '/LinuxPanel_EN-' + updateInfo['version'] + '.zip' + public.downloadFile(updateInfo['downUrl'],'panel.zip') + if os.path.getsize('panel.zip') < 1048576: return public.return_message(-1,0,'File download failed, please try again or update manually!') + public.ExecShell('unzip -o panel.zip -d ' + setupPath + '/') + # import compileall + + # 清除pycache编译缓存 + remove_py_caches = [ + '{}/__pycache__'.format(public.get_panel_path()), + '{}/class/__pycache__'.format(public.get_panel_path()), + '{}/class_v2/__pycache__'.format(public.get_panel_path()), + ] + + for pycache_dir in remove_py_caches: + if os.path.exists(pycache_dir): + os.system('rm -rf {}'.format(pycache_dir)) + + # if os.path.exists('/www/server/panel/runserver.py'): public.ExecShell('rm -f /www/server/panel/*.pyc') + # if os.path.exists('/www/server/panel/class/common.py'): public.ExecShell('rm -f /www/server/panel/class/*.pyc') + + if os.path.exists('panel.zip'):os.remove("panel.zip") + session['version'] = updateInfo['version'] + if 'getCloudPlugin' in session: del(session['getCloudPlugin']) + if updateInfo['is_beta'] == 1: self.to_beta() + public.ExecShell("/etc/init.d/bt start") + public.writeFile('data/restart.pl','True') + return public.return_message(0,0, public.gettext_msg('Successful to update to {}',(updateInfo['version'],))) + + public.ExecShell('rm -rf /www/server/phpinfo/*') + return public.return_message(0,0,updateInfo) + except Exception as ex: + return public.return_message(-1,0,public.get_error_info()) + # return public.return_message(-1,0,'Failed to connect server!') + + #检查是否安装任何 + def CheckInstalled(self,get): + checks = ['nginx','apache','php','pure-ftpd','mysql'] + import os + for name in checks: + filename = public.GetConfigValue('root_path') + "/server/" + name + if os.path.exists(filename): return True + return False + + + #取已安装软件列表 + def GetInstalled(self,get): + import system + data = system.system().GetConcifInfo() + return data + + #取PHP配置 + def GetPHPConfig(self,get): + import re,json + filename = public.GetConfigValue('setup_path') + '/php/' + get.version + '/etc/php.ini' + if public.get_webserver() == 'openlitespeed': + filename = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version,get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + filename = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + if not os.path.exists(filename): return public.return_msg_gettext(False,'Requested PHP version does NOT exist!') + phpini = public.readFile(filename) + data = {} + rep = "disable_functions\\s*=\\s{0,1}(.*)\n" + + tmp = re.search(rep,phpini) + if tmp: + data['disable_functions'] = tmp.groups()[0] + + rep = r"upload_max_filesize\s*=\s*([0-9]+)(M|m|K|k)" + + tmp = re.search(rep,phpini) + if tmp: + data['max'] = tmp.groups()[0] + + rep = u"\n;*\\s*cgi\\.fix_pathinfo\\s*=\\s*([0-9]+)\\s*\n" + tmp = re.search(rep,phpini) + if tmp: + if tmp.groups()[0] == '0': + data['pathinfo'] = False + else: + data['pathinfo'] = True + + self.getCloudPHPExt(get) + phplib = json.loads(public.readFile('data/phplib.conf')) + libs = [] + tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select() + phpini_ols = None + for lib in phplib: + lib['task'] = '1' + for task in tasks: + tmp = public.getStrBetween('[',']',task['name']) + if not tmp:continue + tmp1 = tmp.split('-') + if tmp1[0].lower() == lib['name'].lower(): + lib['task'] = task['status'] + lib['phpversions'] = [] + lib['phpversions'].append(tmp1[1]) + if public.get_webserver() == 'openlitespeed': + lib['status'] = False + get.php_version = "{}.{}".format(get.version[0],get.version[1]) + if not phpini_ols: + phpini_ols = self.php_info(get)['phpinfo']['modules'].lower() + phpini_ols = phpini_ols.split() + for i in phpini_ols: + if lib['check'][:-3].lower() == i : + lib['status'] = True + break + if "ioncube" in lib['check'][:-3].lower() and "ioncube" == i: + lib['status'] = True + break + else: + if phpini.find(lib['check']) == -1: + lib['status'] = False + else: + lib['status'] = True + + libs.append(lib) + + data['libs'] = libs + return data + + #获取PHP扩展 + def getCloudPHPExt(self,get): + try: + self._process_chinese_ext_description() + if 'php_ext' in session: return True + if not self._get_cloud_phplib(): + return False + session['php_ext'] = True + return True + except: + return False + + # 处理PHP插件变描述中文 + def _process_chinese_ext_description(self): + chinese = None + phplib = json.loads(public.readFile('data/phplib.conf')) + for p in phplib: + if "缓存器" in p['type']: + chinese = True + break + if chinese: + self._get_cloud_phplib() + + # 下载云端php扩展配置 + def _get_cloud_phplib(self): + if not session.get('download_url'): session['download_url'] = 'https://node.aapanel.com' + download_url = session['download_url'] + '/install/lib/phplib_en.json' + tstr = public.httpGet(download_url) + data = json.loads(tstr) + if not data: return False + public.writeFile('data/phplib.conf', json.dumps(data)) + return True + + #取PHPINFO信息 + def GetPHPInfo(self,get): + if public.get_webserver() == "openlitespeed": + shell_str = "/usr/local/lsws/lsphp{}/bin/php -i".format(get.version) + return public.ExecShell(shell_str)[0] + sPath = '/www/server/phpinfo' + if os.path.exists(sPath): + public.ExecShell("rm -rf " + sPath) + p_file = '/dev/shm/phpinfo.php' + public.writeFile(p_file,'') + phpinfo = public.request_php(get.version,'/phpinfo.php','/dev/shm') + if os.path.exists(p_file): os.remove(p_file) + return phpinfo.decode() + + #清理日志 + def delClose(self,get): + if not 'uid' in session: session['uid'] = 1 + if session['uid'] != 1: return public.return_msg_gettext(False,'Permission denied!') + if 'tmp_login_id' in session: + return public.return_msg_gettext(False,'Permission denied!') + + # 备份近100条日志 + new_bak = public.M('logs').limit('100').select() + if len(new_bak) > 3: + bak_file = '{}/data/logs.bak'.format(public.get_panel_path()) + public.writeFile(bak_file,json.dumps(new_bak)) + public.add_security_logs("清空日志", '清空所有日志条数为:{}'.format(public.M('logs').count())) + # 清空日志 + public.M('logs').where('id>?',(0,)).delete() + public.write_log_gettext('Panel setting','Panel Logs emptied!') + return public.return_msg_gettext(True,'Panel Logs emptied!') + + def __get_webserver_conffile(self): + webserver = public.get_webserver() + if webserver == 'nginx': + filename = public.GetConfigValue('setup_path') + '/nginx/conf/nginx.conf' + elif webserver == 'openlitespeed': + filename = public.GetConfigValue('setup_path') + "/panel/vhost/openlitespeed/detail/phpmyadmin.conf" + else: + filename = public.GetConfigValue('setup_path') + '/apache/conf/extra/httpd-vhosts.conf' + return filename + + # 获取phpmyadmin ssl配置 + def get_phpmyadmin_conf(self): + if public.get_webserver() == "nginx": + conf_file = "/www/server/panel/vhost/nginx/phpmyadmin.conf" + rep = r"listen\s*(\d+)" + else: + conf_file = "/www/server/panel/vhost/apache/phpmyadmin.conf" + rep = r"Listen\s*(\d+)" + return {"conf_file":conf_file,"rep":rep} + + # 设置phpmyadmin路径 + def set_phpmyadmin_session(self): + import re + conf_file = self.get_phpmyadmin_conf() + conf = public.readFile(conf_file["conf_file"]) + rep = conf_file["rep"] + if conf: + port = re.search(rep,conf).group(1) + if session['phpmyadminDir']: + path = session['phpmyadminDir'].split("/")[-1] + ip = public.GetHost() + session['phpmyadminDir'] = "https://{}:{}/{}".format(ip, port, path) + + # 获取phpmyadmin ssl状态 + def get_phpmyadmin_ssl(self,get): + import re + conf_file = self.get_phpmyadmin_conf() + conf = public.readFile(conf_file["conf_file"]) + rep = conf_file["rep"] + if conf: + port = re.search(rep, conf).group(1) + return public.success_v2({"status":True,"port":port}) + + return public.success_v2({"status":False,"port":""}) + + # 修改php ssl端口 + def change_phpmyadmin_ssl_port(self,get): + if public.get_webserver() == "openlitespeed": + return public.fail_v2('The current web server is openlitespeed. This function is not supported yet.') + + import re + try: + port = int(get.port) + if 1 > port > 65535: + return public.fail_v2('Port range is incorrect!') + except: + return public.fail_v2('Please enter the correct port number') + + for i in ["nginx","apache"]: + file = "/www/server/panel/vhost/{}/phpmyadmin.conf".format(i) + conf = public.readFile(file) + if not conf: + return public.fail_v2('Did not find the {} configuration file, please try to close the ssl port settings before opening',(i,)) + rulePort = ['80', '443', '21', '20', '8080', '8081', '8089', '11211', '6379'] + if get.port in rulePort: + return public.fail_v2('Please do NOT use the usual port as the phpMyAdmin port!') + + if i == "nginx": + if not os.path.exists("/www/server/panel/vhost/apache/phpmyadmin.conf"): + return public.fail_v2('Did not find the apache phpmyadmin ssl configuration file, please try to close the ssl port settings before opening') + + rep = r"listen\s*([0-9]+)\s*.*;" + oldPort = re.search(rep, conf) + if not oldPort: + return public.fail_v2('Did not detect the port that nginx phpmyadmin listens, please confirm whether the file has been manually modified.') + + oldPort = oldPort.groups()[0] + conf = re.sub(rep, 'listen ' + get.port + ' ssl;', conf) + else: + rep = r"Listen\s*([0-9]+)\s*\n" + oldPort = re.search(rep, conf) + if not oldPort: + return public.fail_v2('Did not detect the port that apache phpmyadmin listens, please confirm whether the file has been manually modified.') + + oldPort = oldPort.groups()[0] + conf = re.sub(rep, "Listen " + get.port + "\n", conf, 1) + rep = r"VirtualHost\s*\*:[0-9]+" + conf = re.sub(rep, "VirtualHost *:" + get.port, conf, 1) + if oldPort == get.port: + return public.fail_v2('Port [{}] is in use!',(get.port,)) + + public.writeFile(file, conf) + public.serviceReload() + if i=="apache": + import firewalls + # aapanel 使用 get_msg_gettext + get.ps = public.get_msg_gettext('New phpMyAdmin SSL Port') + fw = firewalls.firewalls() + fw.AddAcceptPort(get) + public.serviceReload() + public.write_log_gettext('Software manager', 'Modified access port to {} for phpMyAdmin!', (get.port,)) + get.id = public.M('firewall').where('port=?', (oldPort,)).getField('id') + get.port = oldPort + fw.DelAcceptPort(get) + + return public.success_v2('Setup successfully!') + + def _get_phpmyadmin_auth(self): + import re + nginx_conf = '/www/server/nginx/conf/nginx.conf' + reg = '#AUTH_START(.|\n)*#AUTH_END' + if os.path.exists(nginx_conf): + nginx_conf = public.readFile(nginx_conf) + auth_tmp = re.search(reg, nginx_conf) + if auth_tmp: + return True + apache_conf = '/www/server/apache/conf/extra/httpd-vhosts.conf' + if os.path.exists(apache_conf): + apache_conf = public.readFile(apache_conf) + auth_tmp = re.search(reg, apache_conf) + if auth_tmp: + return True + + # 设置phpmyadmin ssl + def set_phpmyadmin_ssl(self,get): + if public.get_webserver() == "openlitespeed": + return public.return_message(-1, 0, public.gettext_msg('The current web server is openlitespeed. This function is not supported yet.')) + + if not os.path.exists("/www/server/panel/ssl/certificate.pem"): + return public.return_message(-1, 0, public.gettext_msg('The panel certificate does not exist. Please apply for the panel certificate and try again.')) + + if get.v == "1": + # 获取auth信息 + auth = "" + if self._get_phpmyadmin_auth(): + auth = """ + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file /www/server/pass/phpmyadmin.pass; + #AUTH_END +""" + # nginx配置文件 + ssl_conf = r"""server + { + listen 887 ssl; + server_name phpmyadmin; + index index.html index.htm index.php; + root /www/server/phpmyadmin; + #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则 + #error_page 404/404.html; + ssl_certificate /www/server/panel/ssl/certificate.pem; + ssl_certificate_key /www/server/panel/ssl/privateKey.pem; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + error_page 497 https://$host$request_uri; + #SSL-END + %s + include enable-php.conf; + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + } + location ~ .*\.(js|css)?$ + { + expires 12h; + } + location ~ /\. + { + deny all; + } + access_log /www/wwwlogs/access.log; + }""" % auth + public.writeFile("/www/server/panel/vhost/nginx/phpmyadmin.conf",ssl_conf) + import panelPlugin + get.sName = "phpmyadmin" + v = panelPlugin.panelPlugin().get_soft_find(get) + if self._get_phpmyadmin_auth(): + auth = """ + #AUTH_START + AuthType basic + AuthName "Authorization " + AuthUserFile /www/server/pass/phpmyadmin.pass + Require user jose + #AUTH_END + """ + # apache配置 + ssl_conf = r'''Listen 887 + + ServerAdmin webmaster@example.com + DocumentRoot "/www/server/phpmyadmin" + ServerName 0b842aa5.phpmyadmin + ServerAlias phpmyadmin.com + #ErrorLog "/www/wwwlogs/BT_default_error.log" + #CustomLog "/www/wwwlogs/BT_default_access.log" combined + + #SSL + SSLEngine On + SSLCertificateFile /www/server/panel/ssl/certificate.pem + SSLCertificateKeyFile /www/server/panel/ssl/privateKey.pem + SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH + SSLProtocol All -SSLv2 -SSLv3 + SSLHonorCipherOrder On + + #PHP + + SetHandler "proxy:{}" + + + #DENY FILES + + Order allow,deny + Deny from all + + + #PATH + +{} + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Require all granted + DirectoryIndex index.php index.html index.htm default.php default.html default.htm + +'''.format(public.get_php_proxy(v["ext"]["phpversion"],'apache'),auth) + public.writeFile("/www/server/panel/vhost/apache/phpmyadmin.conf", ssl_conf) + + import firewalls + fw = firewalls.firewalls() + fw.AddAcceptPort(public.to_dict_obj({ + 'port': '887', + 'ps': public.get_msg_gettext('New phpMyAdmin SSL Port'), + })) + else: + if os.path.exists("/www/server/panel/vhost/nginx/phpmyadmin.conf"): + os.remove("/www/server/panel/vhost/nginx/phpmyadmin.conf") + if os.path.exists("/www/server/panel/vhost/apache/phpmyadmin.conf"): + os.remove("/www/server/panel/vhost/apache/phpmyadmin.conf") + public.serviceReload() + return public.return_message(0, 0, public.gettext_msg('Setup successfully!')) + public.serviceReload() + return public.return_message(0, 0, public.gettext_msg('Open successfully, please manually release phpmyadmin ssl port')) + + + #设置PHPMyAdmin + def setPHPMyAdmin(self,get): + import re + #try: + filename = self.__get_webserver_conffile() + if public.get_webserver() == 'openlitespeed': + filename = "/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf" + conf = public.readFile(filename) + + if not conf: + return public.fail_v2('Operation failed') + + if hasattr(get,'port'): + mainPort = public.readFile('data/port.pl').strip() + rulePort = ['80','443','21','20','8080','8081','8089','11211','6379'] + oldPort = "888" + if get.port in rulePort: + return public.fail_v2('Please do NOT use the usual port as the phpMyAdmin port!') + + if public.get_webserver() == 'nginx': + rep = r"listen\s+([0-9]+)\s*;" + oldPort = re.search(rep,conf).groups()[0] + conf = re.sub(rep,'listen ' + get.port + ';\n',conf) + elif public.get_webserver() == 'apache': + rep = r"Listen\s+([0-9]+)\s*\n" + oldPort = re.search(rep,conf).groups()[0] + conf = re.sub(rep,"Listen " + get.port + "\n",conf,1) + rep = r"VirtualHost\s+\*:[0-9]+" + conf = re.sub(rep,"VirtualHost *:" + get.port,conf,1) + else: + filename = '/www/server/panel/vhost/openlitespeed/listen/888.conf' + conf = public.readFile(filename) + reg = r"address\s+\*:(\d+)" + tmp = re.search(reg,conf) + if tmp: + oldPort = tmp.groups(1) + + ## 修复 openlitespeed 修改端口报错 + oldPort = oldPort[0] + + conf = re.sub(reg,"address *:{}".format(get.port),conf) + + if oldPort == get.port: + return public.fail_v2('Port [{}] is in use!', (get.port,)) + + public.writeFile(filename,conf) + import firewalls + get.ps = public.get_msg_gettext('New phpMyAdmin Port') + fw = firewalls.firewalls() + fw.AddAcceptPort(get) + public.serviceReload() + public.write_log_gettext('Software manager','Modified access port to {} for phpMyAdmin!',(get.port,)) + get.id = public.M('firewall').where('port=?',(oldPort,)).getField('id') + get.port = oldPort + fw.DelAcceptPort(get) + return public.success_v2('Setup successfully!') + + if hasattr(get,'phpversion'): + if public.get_webserver() == 'nginx': + filename = public.GetConfigValue('setup_path') + '/nginx/conf/enable-php.conf' + conf = public.readFile(filename) + rep = r"(unix:/tmp/php-cgi.*\.sock|127.0.0.1:\d+)" + conf = re.sub(rep,public.get_php_proxy(get.phpversion,'nginx'),conf,1) + elif public.get_webserver() == 'apache': + rep = r"(unix:/tmp/php-cgi.*\.sock\|fcgi://localhost|fcgi://127.0.0.1:\d+)" + conf = re.sub(rep,public.get_php_proxy(get.phpversion,'apache'),conf,1) + else: + reg = r'/usr/local/lsws/lsphp\d+/bin/lsphp' + conf = re.sub(reg,'/usr/local/lsws/lsphp{}/bin/lsphp'.format(get.phpversion),conf) + public.writeFile(filename,conf) + public.serviceReload() + public.write_log_gettext('Software manager','Modified PHP runtime version to PHP-{} for phpMyAdmin!',(get.phpversion,)) + + return public.success_v2('Setup successfully!') + + if hasattr(get,'password'): + import panel_site_v2 + if(get.password == 'close'): + return panel_site_v2.panelSite().CloseHasPwd(get) + else: + return panel_site_v2.panelSite().SetHasPwd(get) + + if hasattr(get,'status'): + pma_path = public.GetConfigValue('setup_path') + '/phpmyadmin' + stop_path = public.GetConfigValue('setup_path') + '/stop' + + + webserver = public.get_webserver() + if conf.find(stop_path) != -1: + conf = conf.replace(stop_path,pma_path) + msg = public.getMsg('START') + + if webserver == 'nginx': + sub_string = '''{}; + allow 127.0.0.1; + allow ::1; + deny all'''.format(pma_path) + if conf.find(sub_string) != -1: + conf = conf.replace(sub_string,pma_path) + msg = public.getMsg('START') + else: + conf = conf.replace(pma_path,sub_string) + msg = public.getMsg('STOP') + elif webserver == 'apache': + src_string = 'AllowOverride All' + sub_string = '''{} + Deny from all + Allow from 127.0.0.1 ::1 localhost'''.format(src_string,pma_path) + if conf.find(sub_string) != -1: + conf = conf.replace(sub_string,src_string) + msg = public.getMsg('START') + else: + conf = conf.replace(src_string,sub_string) + msg = public.getMsg('STOP') + else: + if conf.find(stop_path) != -1: + conf = conf.replace(stop_path,pma_path) + msg = public.getMsg('START') + else: + conf = conf.replace(pma_path,stop_path) + msg = public.getMsg('STOP') + + public.writeFile(filename,conf) + public.serviceReload() + public.write_log_gettext('Software manager','phpMyAdmin already {}!',(msg,)) + + return public.success_v2('phpMyAdmin already {}!', (msg,)) + #except: + #return public.returnMsg(False,'ERROR'); + + def ToPunycode(self,get): + import re + get.domain = get.domain.encode('utf8') + tmp = get.domain.split('.') + newdomain = '' + for dkey in tmp: + #匹配非ascii字符 + match = re.search(u"[\x80-\xff]+",dkey) + if not match: + newdomain += dkey + '.' + else: + newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' + + return newdomain[0:-1] + + #保存PHP排序 + def phpSort(self,get): + if public.writeFile('/www/server/php/sort.pl',get.ssort): return public.return_msg_gettext(True,'Setup successfully!') + return public.return_msg_gettext(False,'Operation failed') + + #获取广告代码 + def GetAd(self,get): + try: + return public.HttpGet(public.GetConfigValue('home') + '/Api/GetAD?name='+get.name + '&soc=' + get.soc) + except: + return '' + + #获取进度 + def GetSpeed(self,get): + return public.getSpeed() + + #检查登陆状态 + def CheckLogin(self,get): + return True + + #获取警告标识 + def GetWarning(self,get): + warningFile = 'data/warning.json' + if not os.path.exists(warningFile): return public.return_msg_gettext(False,'Warning list does NOT exist!') + import json,time; + wlist = json.loads(public.readFile(warningFile)) + wlist['time'] = int(time.time()) + return wlist + + #设置警告标识 + def SetWarning(self,get): + wlist = self.GetWarning(get) + id = int(get.id) + import time,json; + for i in xrange(len(wlist['data'])): + if wlist['data'][i]['id'] == id: + wlist['data'][i]['ignore_count'] += 1 + wlist['data'][i]['ignore_time'] = int(time.time()) + + warningFile = 'data/warning.json' + public.writeFile(warningFile,json.dumps(wlist)) + return public.return_msg_gettext(True,'Setup successfully!') + + #获取memcached状态 + def GetMemcachedStatus(self,get): + import telnetlib,re; + conf = public.readFile('/etc/init.d/memcached') + result = {} + result['bind'] = re.search('IP=(.+)',conf).groups()[0] + result['port'] = int(re.search(r'PORT=(\d+)',conf).groups()[0]) + result['maxconn'] = int(re.search(r'MAXCONN=(\d+)',conf).groups()[0]) + result['cachesize'] = int(re.search(r'CACHESIZE=(\d+)',conf).groups()[0]) + tn = telnetlib.Telnet(result['bind'],result['port']) + tn.write(b"stats\n") + tn.write(b"quit\n") + data = tn.read_all() + if type(data) == bytes: data = data.decode('utf-8') + data = data.replace('STAT','').replace('END','').split("\n") + res = ['cmd_get','get_hits','get_misses','limit_maxbytes','curr_items','bytes','evictions','limit_maxbytes','bytes_written','bytes_read','curr_connections']; + for d in data: + if len(d)<3: continue + t = d.split() + if not t[0] in res: continue + result[t[0]] = int(t[1]) + result['hit'] = 1 + if result['get_hits'] > 0 and result['cmd_get'] > 0: + result['hit'] = float(result['get_hits']) / float(result['cmd_get']) * 100 + + return result + + #设置memcached缓存大小 + def SetMemcachedCache(self,get): + import re + confFile = '/etc/init.d/memcached' + conf = public.readFile(confFile) + conf = re.sub('IP=.+','IP='+get.ip,conf) + conf = re.sub(r'PORT=\d+','PORT='+get.port,conf) + conf = re.sub(r'MAXCONN=\d+','MAXCONN='+get.maxconn,conf) + conf = re.sub(r'CACHESIZE=\d+','CACHESIZE='+get.cachesize,conf) + public.writeFile(confFile,conf) + public.ExecShell(confFile + ' reload') + return public.return_msg_gettext(True,'Setup successfully!') + + #取redis状态 + def GetRedisStatus(self,get): + import re + c = public.readFile('/www/server/redis/redis.conf') + port = re.findall('\n\\s*port\\s+(\\d+)',c)[0] + password = re.findall('\n\\s*requirepass\\s+(.+)',c) + if password: + password = ' -a ' + password[0] + else: + password = '' + data = public.ExecShell('/www/server/redis/src/redis-cli -p ' + port + password + ' info')[0]; + res = [ + 'tcp_port', + 'uptime_in_days', #已运行天数 + 'connected_clients', #连接的客户端数量 + 'used_memory', #Redis已分配的内存总量 + 'used_memory_rss', #Redis占用的系统内存总量 + 'used_memory_peak', #Redis所用内存的高峰值 + 'mem_fragmentation_ratio', #内存碎片比率 + 'total_connections_received',#运行以来连接过的客户端的总数量 + 'total_commands_processed', #运行以来执行过的命令的总数量 + 'instantaneous_ops_per_sec', #服务器每秒钟执行的命令数量 + 'keyspace_hits', #查找数据库键成功的次数 + 'keyspace_misses', #查找数据库键失败的次数 + 'latest_fork_usec' #最近一次 fork() 操作耗费的毫秒数 + ] + data = data.split("\n") + result = {} + for d in data: + if len(d)<3: continue + t = d.strip().split(':') + if not t[0] in res: continue + result[t[0]] = t[1] + return result + + #取PHP-FPM日志 + def GetFpmLogs(self,get): + import re + fpm_path = '/www/server/php/' + get.version + '/etc/php-fpm.conf' + if not os.path.exists(fpm_path): return public.return_msg_gettext(False,'Log file does NOT exist!') + fpm_conf = public.readFile(fpm_path) + log_tmp = re.findall(r"error_log\s*=\s*(.+)",fpm_conf) + if not log_tmp: return public.return_msg_gettext(False,'Log file does NOT exist!') + log_file = log_tmp[0].strip() + if log_file.find('var/log') == 0: + log_file = '/www/server/php/' +get.version + '/'+ log_file + return public.returnMsg(True,public.GetNumLines(log_file,1000)) + + #取PHP慢日志 + def GetFpmSlowLogs(self,get): + import re + fpm_path = '/www/server/php/' + get.version + '/etc/php-fpm.conf' + if not os.path.exists(fpm_path): return public.return_msg_gettext(False,'Log file does NOT exist!') + fpm_conf = public.readFile(fpm_path) + log_tmp = re.findall(r"slowlog\s*=\s*(.+)",fpm_conf) + if not log_tmp: return public.return_msg_gettext(False,'Log file does NOT exist!') + log_file = log_tmp[0].strip() + if log_file.find('var/log') == 0: + log_file = '/www/server/php/' +get.version + '/'+ log_file + return public.returnMsg(True,public.GetNumLines(log_file,1000)) + + #取指定日志 + def GetOpeLogs(self,get): + if not os.path.exists(get.path): return public.return_msg_gettext(False,'Log file does NOT exist!') + return public.returnMsg(True,public.xsssec(public.GetNumLines(get.path,1000))) + + def get_pd(self,get): + # 校验参数 + try: + get.validate([ + Param('status').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + from BTPanel import cache + tmp = -1 + try: + import panelPlugin + # get = public.dict_obj() + # get.init = 1 + tmp1 = panelPlugin.panelPlugin().get_cloud_list(get) + except: + tmp1 = None + if tmp1: + tmp = tmp1[public.to_string([112, 114, 111])] + ltd = tmp1.get('ltd', -1) + else: + ltd = -1 + tmp4 = cache.get(public.to_string([112, 95, 116, 111, 107, 101, 110])) + if tmp4: + tmp_f = public.to_string([47, 116, 109, 112, 47]) + tmp4 + if not os.path.exists(tmp_f): public.writeFile(tmp_f, '-1') + tmp = public.readFile(tmp_f) + if tmp: tmp = int(tmp) + if not ltd: ltd = -1 + if tmp == None: tmp = -1 + if ltd < 1: + if ltd == -2: + tmp3 = public.to_string( + [60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, 100, + 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, 115, 116, 121, 108, 101, + 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, + 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, + 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, + 26399, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, + 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, 34, 98, 116, 46, 115, 111, + 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 108, 116, 100, 40, 41, 34, 62, 82, 69, 78, 69, 87, + 60, 47, 97, + 62, 60, 47, 115, 112, 97, 110, 62]) + elif tmp == -1: + tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, + 116, 112, 114, 111, 45, 102, 114, 101, 101, 34, 32, 111, 110, 99, 108, 105, 99, + 107, + 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, + 114, + 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, 107, + 32, 116, 111, 32, + 103, 101, 116, 32, 80, 82, 79, 34, 62, 20813, 36153, 29256, 60, 47, 115, 112, + 97, 110, 62]) + elif tmp == -2: + tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, + 112, 114, 111, 45, 103, 114, 97, 121, 34, 62, 60, 115, 112, 97, 110, 32, + 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, + 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, + 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, + 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 24050, 36807, 26399, + 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, 115, 61, 34, + 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, 99, 107, 61, + 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, 112, 114, + 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, 112, 97, + 110, 62]) + if tmp >= 0 and ltd in [-1, -2]: + if tmp == 0: + tmp2 = public.to_string([27704, 20037, 25480, 26435]) + tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, + 112, 114, 111, 34, 62, 123, 48, 125, 60, 115, 112, 97, 110, 32, 115, 116, + 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, + 100, + 50, 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, + 58, 32, 98, 111, 108, 100, 59, 34, 62, 123, 49, 125, 60, 47, 115, + 112, 97, 110, 62, 60, 47, 115, 112, 97, 110, 62]).format( + public.to_string([21040, 26399, 26102, 38388, 65306]), tmp2) + else: + tmp2 = time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(tmp)) + tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, + 112, 114, 111, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, + 97, 110, 32, 115, 116, 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, + 58, 32, 35, 102, 99, 54, 100, 50, 54, 59, 102, 111, 110, 116, 45, 119, + 101, 105, 103, 104, 116, 58, 32, 98, 111, 108, 100, 59, 109, 97, 114, + 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, 112, 120, 34, 62, 123, + 48, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, 97, 115, + 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, + 99, + 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, + 95, + 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, + 47, 115, 112, 97, 110, 62]).format(tmp2) + else: + tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 112, + 114, 111, 45, 103, 114, 97, 121, 34, 32, 111, 110, 99, 108, 105, 99, 107, + 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 117, 112, 100, 97, 116, 97, 95, 112, + 114, 111, 40, 41, 34, 32, 116, 105, 116, 108, 101, 61, 34, 67, 108, 105, 99, + 107, 32, 116, + 111, 32, 103, 101, 116, 32, 80, 82, 79, 34, 62, 70, 82, + 69, 69, 60, 47, 115, 112, 97, 110, 62]) + else: + tmp3 = public.to_string([60, 115, 112, 97, 110, 32, 99, 108, 97, 115, 115, 61, 34, 98, 116, 108, 116, + 100, 34, 62, 69, 120, 112, 105, 114, 101, 58, 32, 60, 115, 112, 97, 110, 32, 115, + 116, + 121, 108, 101, 61, 34, 99, 111, 108, 111, 114, 58, 32, 35, 102, 99, 54, 100, 50, + 54, 59, 102, 111, 110, 116, 45, 119, 101, 105, 103, 104, 116, 58, 32, 98, 111, + 108, 100, 59, 109, 97, 114, 103, 105, 110, 45, 114, 105, 103, 104, 116, 58, 53, + 112, 120, 34, 62, 123, 125, 60, 47, 115, 112, 97, 110, 62, 60, 97, 32, 99, 108, + 97, 115, 115, 61, 34, 98, 116, 108, 105, 110, 107, 34, 32, 111, 110, 99, 108, 105, + 99, 107, 61, 34, 98, 116, 46, 115, 111, 102, 116, 46, 114, 101, 110, 101, 119, 95, + 112, 114, 111, 40, 41, 34, 62, 82, 69, 78, 69, 87, 60, 47, 97, 62, 60, 47, 115, + 112, 97, 110, 62]).format( + time.strftime(public.to_string([37, 89, 45, 37, 109, 45, 37, 100]), time.localtime(ltd))) + return_message={"bt_pro":tmp3,"time_stamp":tmp,"itd":ltd} + + return public.return_message(0,0,return_message) + + #检查用户绑定是否正确 + def check_user_auth(self,get): + # import requests + m_key = 'check_user_auth' + if m_key in session: return session[m_key] + u_path = 'data/userInfo.json' + try: + userInfo = json.loads(public.ReadFile(u_path)) + except: + if os.path.exists(u_path): os.remove(u_path) + return public.return_msg_gettext(False,'Account binding has expired, please re-bind on the [Settings] page!') + url_headers = {"authorization":"bt {}".format(userInfo['token'])} + # resp = requests.post('{}/api/user/verifyToken'.format(self.__official_url),headers=url_headers,verify=False) + resp = public.HttpPost.post('{}/api/user/verifyToken'.format(self.__official_url), headers=url_headers, verify=False) + resp = resp.json() + if not resp['success']: + if os.path.exists(u_path): os.remove(u_path) + return public.return_msg_gettext(False,'Account binding has expired, please re-bind on the [Settings] page!') + else: + session[m_key] = public.return_msg_gettext(True,'Binding is valid!') + return session[m_key] + + + #PHP探针 + def php_info(self,args): + php_version = args.php_version.replace('.','') + php_path = '/www/server/php/' + if public.get_webserver() == 'openlitespeed': + php_path = '/usr/local/lsws/lsphp' + php_bin = php_path + php_version + '/bin/php' + php_ini = php_path + php_version + '/etc/php.ini' + if not os.path.exists('/etc/redhat-release') and public.get_webserver() == 'openlitespeed': + php_ini = php_path + php_version + '/etc/php/'+args.php_version+'/litespeed/php.ini' + tmp = public.ExecShell(php_bin + ' -c {} /www/server/panel/class/php_info.php'.format(php_ini))[0] + if tmp.find('Warning: JIT is incompatible') != -1: + tmp = tmp.strip().split('\n')[-1] + result = json.loads(tmp) + result['phpinfo'] = {} + result['phpinfo']['php_version'] = result['php_version'] + result['phpinfo']['php_path'] = php_path + result['phpinfo']['php_bin'] = php_bin + result['phpinfo']['php_ini'] = php_ini + result['phpinfo']['modules'] = ' '.join(result['modules']) + result['phpinfo']['ini'] = result['ini'] + result['phpinfo']['keys'] = { "1cache": "Buffer", "2crypt": "Encryption and decryption library", "0db": "Database-driven", "4network": "Network Communication Library", "5io_string": "File and string processing libraries", "3photo":"Image processing library","6other":"Other third-party libraries"} + del(result['php_version']) + del(result['modules']) + del(result['ini']) + return result + + #取指定行 + def get_lines(self,args): + # 校验参数 + try: + args.validate([ + Param('filename').String(), + Param('num').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + if not os.path.exists(args.filename): return public.return_message(-1,0,'Logs emptied') + num = args.get('num/d',10) + s_body = public.GetNumLines(args.filename,num) + return public.return_message(0,0,s_body) + + def log_analysis(self,get): + public.set_module_logs('log_analysis', 'log_analysis', 1) + import log_analysis_v2 as log_analysis + log_analysis=log_analysis.log_analysis() + return log_analysis.log_analysis(get) + + + def speed_log(self,get): + import log_analysis_v2 as log_analysis + log_analysis=log_analysis.log_analysis() + return log_analysis.speed_log(get) + + + + def get_result(self,get): + # 校验参数 + try: + get.validate([ + Param('path').String(), + Param('action').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + import log_analysis_v2 as log_analysis + log_analysis=log_analysis.log_analysis() + return log_analysis.get_result(get) + + def get_detailed(self,get): + import log_analysis_v2 as log_analysis + log_analysis=log_analysis.log_analysis() + return log_analysis.get_detailed(get) + + def download_pay_type(self, path): + public.downloadFile(public.get_url() + '/install/lib/pay_type_en.json', path) + return True + + def get_pay_type(self, get): + """ + @name 获取推荐列表 + """ + #未传参,无需统一参数校验 + + + # spath = '{}/data/pay_type.json'.format(public.get_panel_path()) + # if not os.path.exists(spath): + # public.run_thread(self.download_pay_type,(spath,)) + # try: + # data = json.loads(public.readFile("data/pay_type.json")) + # except: + # public.run_thread(self.download_pay_type, (spath,)) + # data = {} + # + # import panelPlugin + # plu_panel = panelPlugin.panelPlugin() + # plugin_list = plu_panel.get_cloud_list() + # if not 'pro' in plugin_list: plugin_list['pro'] = -1 + # + # for item in data: + # if 'list' in item: + # item['list'] = self.__get_home_list(item['list'], item['type'], plugin_list, plu_panel) + # if item['type'] == 1: + # if len(item['list']) > 4: item['list'] = item['list'][:4] + # # if item['type'] == 0 and plugin_list['pro'] >= 0: + # # item['show'] = False + # + # return data + + spath = '{}/data/pay_type.json'.format(public.get_panel_path()) + if os.path.exists(spath) and os.path.getsize(spath) <= 0: + os.remove(spath) + + if not os.path.exists(spath): + public.run_thread(self.download_pay_type, (spath,)) + try: + data = json.loads(public.readFile("data/pay_type.json")) + except json.decoder.JSONDecodeError: + os.remove(spath) + public.run_thread(self.download_pay_type, (spath,)) + data = json.loads(public.readFile("data/pay_type.json")) + except Exception: + data = self.get_default_pay_type() + + import panelPlugin + plu_panel = panelPlugin.panelPlugin() + plugin_list = plu_panel.get_cloud_list() + if not 'pro' in plugin_list: plugin_list['pro'] = -1 + + for item in data: + if 'list' in item: + item['list'] = self.__get_home_list(item['list'], item['type'],plugin_list, plu_panel) + if item['type'] == 1: + if len(item['list']) > 4: item['list'] = item['list'][:4] + # if item['type'] == 0 and plugin_list['pro'] >= 0: + # item['show'] = False + return public.return_message(0,0,data) + + + @staticmethod + def get_default_pay_type(): + spath = '{}/data/default_pay_type.json'.format(public.get_panel_path()) + default = [{"type": -1}, {"type": -1}, {"type": -1}, {"type": -1}, + {"type": -1}, { + "type": 5, + "describe": "网站-设置推荐", + "show": True, + "list": [ + { + "title": "防火墙", + "name": "btwaf", + "pay": "46", + "pluginName": "Nginx网站防火墙", + "ps": "有效拦截SQL 注入、XSS跨站、恶意代码、网站挂马等常见攻击,过滤恶意访问,降低数据泄露的风险,保障网站的可用性。", + "preview": "https://www.bt.cn/new/product_nginx_firewall.html", + "dependent": "nginx", + "pluginType": "pro", + "eventList": [ + { + "event": "site_waf_config('$siteName')", + "version": "5.2.0" + } + ] + }, + { + "title": "防火墙", + "name": "btwaf_httpd", + "pay": "46", + "pluginName": "网站防火墙", + "ps": "有效拦截SQL 注入、XSS跨站、恶意代码、网站挂马等常见攻击,过滤恶意访问,降低数据泄露的风险,保障网站的可用性。", + "preview": "https://www.bt.cn/new/product_nginx_firewall.html", + "dependent": "apache", + "pluginType": "pro", + "eventList": [ + { + "event": "site_waf_config('$siteName')", + "version": "5.2.0" + } + ] + }, + { + "title": "统计", + "name": "total", + "pay": "47", + "pluginName": "网站监控报表", + "ps": "快速分析网站运行状况,实时精确统计网站流量、ip、uv、pv、请求、蜘蛛等数据,网站SEO优化利器", + "preview": "https://www.bt.cn/new/product_website_total.html", + "dependent": "apache", + "pluginType": "pro", + "eventList": [ + { + "event": "WebsiteReport('$siteName')", + "version": "5.0" + } + ] + }, + { + "title": "统计", + "name": "total", + "pay": "47", + "pluginName": "网站监控报表", + "ps": "快速分析网站运行状况,实时精确统计网站流量、ip、uv、pv、请求、蜘蛛等数据,网站SEO优化利器", + "preview": "https://www.bt.cn/new/product_website_total.html", + "dependent": "nginx", + "pluginType": "pro", + "eventList": [ + { + "event": "WebsiteReport('$siteName')", + "version": "5.0" + } + ] + } + ] + }, {"type": -1}, {"type": -1}] + if os.path.isfile(spath): + try: + res_data = json.loads(public.readFile(spath)) + if isinstance(res_data, list): + return res_data + except json.JSONDecodeError: + pass + # 再次出错时,保障网站列表可以展示 + return default + return default + + + + + def __get_home_list(self, sList, stype, plugin_list, plu_panel): + """ + @name 获取首页软件列表推荐 + """ + nList = [] + webserver = public.get_webserver() + for x in sList: + for plugin_info in plugin_list['list']: + if x['name'] == plugin_info['name']: + if not 'endtime' in plugin_info or plugin_info['endtime'] >= 0: + x['isBuy'] = True + is_check = False + if 'dependent' in x: + if x['dependent'] == webserver: is_check = True + else: + is_check = True + if is_check: + info = plu_panel.get_soft_find(x['name']) + if info: + if stype == 1: + # if plugin_list['pro'] >= 0: continue + if not info['setup']: + x['install'] = info['setup'] + nList.append(x) + else: + x['install'] = info['setup'] + nList.append(x) + return nList + + def ignore_version(self, get): + """ + @忽略版本更新 + :param version 忽略的版本号 + """ + # 校验参数 + try: + get.validate([ + Param('version').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + version = get.version + path = '{}/data/no_update.pl'.format(public.get_panel_path()) + try: + data = json.loads(public.readFile(path)) + except: + data = [] + + if not version in data: data.append(version) + + public.writeFile(path, json.dumps(data)) + try: + del (session['updateInfo']) + except: + pass + + return public.return_message(0,0, "Ignore success, this version will no longer be reminded to update.") diff --git a/class_v2/apache_v2.py b/class_v2/apache_v2.py new file mode 100644 index 00000000..4f0b536c --- /dev/null +++ b/class_v2/apache_v2.py @@ -0,0 +1,381 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +#------------------------------ +# Apache管理模块 +#------------------------------ +import public,os,re,shutil,math,psutil,time +from json import loads +os.chdir("/www/server/panel") + +class apache: + setupPath = '/www/server' + apachedefaultfile = "%s/apache/conf/extra/httpd-default.conf" % (setupPath) + apachempmfile = "%s/apache/conf/extra/httpd-mpm.conf" % (setupPath) + httpdconf = "%s/apache/conf/httpd.conf" % (setupPath) + + + def GetProcessCpuPercent(self,i,process_cpu): + try: + pp = psutil.Process(i) + if pp.name() not in process_cpu.keys(): + process_cpu[pp.name()] = float(pp.cpu_percent(interval=0.1)) + process_cpu[pp.name()] += float(pp.cpu_percent(interval=0.1)) + except: + pass + + def GetApacheStatus(self): + process_cpu = {} + apacheconf = "%s/apache/conf/httpd.conf" % (self.setupPath) + confcontent = public.readFile(apacheconf) + rep = "#Include conf/extra/httpd-info.conf" + if re.search(rep,confcontent): + confcontent = re.sub(rep,"Include conf/extra/httpd-info.conf",confcontent) + public.writeFile(apacheconf,confcontent) + public.serviceReload() + result = public.HttpGet('http://127.0.0.1/server-status?auto') + try: + workermen = int(public.ExecShell("ps aux|grep httpd|grep 'start'|awk '{memsum+=$6};END {print memsum}'")[0]) / 1024 + except: + return public.return_msg_gettext(False,"Get worker RAM False") + for proc in psutil.process_iter(): + if proc.name() == "httpd": + self.GetProcessCpuPercent(proc.pid,process_cpu) + time.sleep(0.5) + + data = {} + + # 计算启动时间 + Uptime = re.search(r"ServerUptimeSeconds:\s+(.*)",result) + if not Uptime: + return public.return_msg_gettext(False, "Get worker Uptime False") + Uptime = int(Uptime.group(1)) + min = Uptime / 60 + hours = min / 60 + days = math.floor(hours / 24) + hours = math.floor(hours - (days * 24)) + min = math.floor(min - (days * 60 * 24) - (hours * 60)) + + #格式化重启时间 + restarttime = re.search(r"RestartTime:\s+(.*)",result) + if not restarttime: + return public.return_msg_gettext(False, "Get worker Restart Time False") + restarttime = restarttime.group(1) + rep = r"\w+,\s([\w-]+)\s([\d\:]+)\s\w+" + date = re.search(rep,restarttime) + if not date: + return public.return_msg_gettext(False, "Get worker date False") + date = date.group(1) + timedetail = re.search(rep,restarttime) + if not timedetail: + return public.return_msg_gettext(False, "Get worker time detail False") + timedetail=timedetail.group(2) + monthen = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] + n = 0 + for m in monthen: + if m in date: + date = re.sub(m,str(n+1),date) + n+=1 + date = date.split("-") + date = "%s-%s-%s" % (date[2],date[1],date[0]) + + reqpersec = re.search(r"ReqPerSec:\s+(.*)", result) + if not reqpersec: + return public.return_msg_gettext(False, "Get worker reqpersec False") + reqpersec = reqpersec.group(1) + if re.match(r"^\.", reqpersec): + reqpersec = "%s%s" % (0,reqpersec) + data["RestartTime"] = "%s %s" % (date,timedetail) + data["UpTime"] = "%s day %s hour %s minute" % (str(int(days)),str(int(hours)),str(int(min))) + total_acc = re.search(r"Total Accesses:\s+(\d+)",result) + if not total_acc: + return public.return_msg_gettext(False, "Get worker TotalAccesses False") + data["TotalAccesses"] = total_acc.group(1) + total_kb = re.search(r"Total kBytes:\s+(\d+)",result) + if not total_kb: + return public.return_msg_gettext(False, "Get worker TotalKBytes False") + data["TotalKBytes"] = total_kb.group(1) + data["ReqPerSec"] = round(float(reqpersec), 2) + busywork = re.search(r"BusyWorkers:\s+(\d+)",result) + if not busywork: + return public.return_msg_gettext(False, "Get worker BusyWorkers False") + data["BusyWorkers"] = busywork.group(1) + idlework = re.search(r"IdleWorkers:\s+(\d+)",result) + if not idlework: + return public.return_msg_gettext(False, "Get worker IdleWorkers False") + data["IdleWorkers"] = idlework.group(1) + data["workercpu"] = round(float(process_cpu["httpd"]),2) + data["workermem"] = "%s%s" % (int(workermen),"MB") + return data + + def GetApacheValue(self): + apachedefaultcontent = public.readFile(self.apachedefaultfile) + apachempmcontent = public.readFile(self.apachempmfile) + if not "mpm_event_module" in apachempmcontent: + return public.returnMsg(False,"mpm_event_module conf not found or /www/server/apache/conf/extra/httpd-mpm.conf is empty") + apachempmcontent = re.search("\\(\n|.)+?\\",apachempmcontent).group() + ps = ["%s,%s" % (public.get_msg_gettext('Second'),public.get_msg_gettext('Request timeout')), + public.get_msg_gettext('Keep alive'), + "%s,%s" % (public.get_msg_gettext('Second'),public.get_msg_gettext('Connection timeout')), + public.get_msg_gettext('Max keep-alive requests per connection')] + gets = ["Timeout","KeepAlive","KeepAliveTimeout","MaxKeepAliveRequests"] + if public.get_webserver() == 'apache': + shutil.copyfile(self.apachedefaultfile, '/tmp/apdefault_file_bk.conf') + shutil.copyfile(self.apachempmfile, '/tmp/apmpm_file_bk.conf') + conflist = [] + n = 0 + for i in gets: + rep = r"(%s)\s+(\w+)" % i + k = re.search(rep, apachedefaultcontent) + if not k: + return public.return_msg_gettext(False, "Get Key {} False",(i,)) + k = k.group(1) + v = re.search(rep, apachedefaultcontent) + if not v: + return public.return_msg_gettext(False, "Get Value {} False",(v,)) + v = v.group(2) + psstr = ps[n] + kv = {"name":k,"value":v,"ps":psstr} + conflist.append(kv) + n += 1 + + ps = [public.get_msg_gettext('Default processes'), + public.get_msg_gettext('Maximum number of idle threads'), + public.get_msg_gettext('Minimum number of idle threads available to handle request spikes'), + public.get_msg_gettext('Number of threads created by each child process'), + public.get_msg_gettext('Maximum number of connections that will be processed simultaneously'), + public.get_msg_gettext('Limit on the number of connections that an individual child server will handle during its life')] + gets = ["StartServers","MaxSpareThreads","MinSpareThreads","ThreadsPerChild","MaxRequestWorkers","MaxConnectionsPerChild"] + n = 0 + for i in gets: + rep = r"(%s)\s+(\w+)" % i + k = re.search(rep, apachempmcontent) + if not k: + return public.return_msg_gettext(False, "Get Key {} False",(i,)) + k = k.group(1) + v = re.search(rep, apachempmcontent) + if not v: + return public.return_msg_gettext(False, "Get Value {} False",(v,)) + v = v.group(2) + psstr = ps[n] + kv = {"name": k, "value": v, "ps": psstr} + conflist.append(kv) + n += 1 + return(conflist) + + def SetApacheValue(self,get): + apachedefaultcontent = public.readFile(self.apachedefaultfile) + apachempmcontent = public.readFile(self.apachempmfile) + if not "mpm_event_module" in apachempmcontent: + return public.return_msg_gettext(False,"mpm_event_module conf not found or /www/server/apache/conf/extra/httpd-mpm.conf is empty") + conflist = [] + getdict = get.get_items() + for i in getdict.keys(): + if i != "__module__" and i != "__doc__" and i != "data" and i != "args" and i != "action": + getpost = { + "name": i, + "value": str(getdict[i]) + } + conflist.append(getpost) + for c in conflist: + if c["name"] == "KeepAlive": + if not re.search("on|off", c["value"]): + return public.return_msg_gettext(False, 'Parameter ERROR!') + else: + print(c["value"]) + if not re.search(r"\d+", c["value"]): + print(c["name"],c["value"]) + return public.return_msg_gettext(False, 'Parameter ERROR!') + + rep = r"%s\s+\w+" % c["name"] + if re.search(rep,apachedefaultcontent): + newconf = "%s %s" % (c["name"],c["value"]) + apachedefaultcontent = re.sub(rep,newconf,apachedefaultcontent) + elif re.search(rep,apachempmcontent): + newconf = "%s\t\t\t%s" % (c["name"], c["value"]) + apachempmcontent = re.sub(rep, newconf , apachempmcontent) + public.writeFile(self.apachedefaultfile,apachedefaultcontent) + public.writeFile(self.apachempmfile, apachempmcontent) + isError = public.checkWebConfig() + if (isError != True): + shutil.copyfile('/tmp/_file_bk.conf', self.apachedefaultfile) + shutil.copyfile('/tmp/proxyfile_bk.conf', self.apachempmfile) + return public.returnMsg(False, 'ERROR: %s
                                    ' % public.get_msg_gettext('Configuration ERROR') + isError.replace("\n", + '
                                    ') + '
                                    ') + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + def add_httpd_access_log_format(self,args): + ''' + @name 添加httpd日志格式 + @author zhwen + @param log_format 需要设置的日志格式["$server_name","$remote_addr","-"....] + @param log_format_name + @param act 操作方式 add/edit + ''' + try: + log_format = loads(args.log_format) + data = """ + #LOG_FORMAT_BEGIN_{n} + LogFormat '{c}' {n} + #LOG_FORMAT_END_{n} +""".format(n=args.log_format_name,c=' '.join(log_format)) + data = data.replace('%{User-agent}i','"%{User-agent}i"') + data = data.replace('%{Referer}i', '"%{Referer}i"') + if args.act == 'edit': + self.del_httpd_access_log_format(args) + conf = public.readFile(self.httpdconf) + if not conf: + return public.return_msg_gettext(False,'Configuration file not exist') + reg = '' + conf = re.sub(reg,''+data,conf) + public.writeFile(self.httpdconf,conf) + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + except: + return public.returnMsg(False, str(public.get_error_info())) + + def del_httpd_access_log_format(self,args): + ''' + @name 删除日志格式 + @author zhwen + @param log_format_name + ''' + conf = public.readFile(self.httpdconf) + if not conf: + return public.return_msg_gettext(False, 'Configuration file not exist') + reg = '\\s*#LOG_FORMAT_BEGIN_{n}(\n|.)+#LOG_FORMAT_END_{n}\n?'.format(n=args.log_format_name) + conf = re.sub(reg,'',conf) + self._del_format_log_of_website(args.log_format_name) + public.writeFile(self.httpdconf,conf) + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + def del_all_log_format(self,args): + all_format = self.get_httpd_access_log_format(args) + for i in all_format: + args.log_format_name = i + self.del_httpd_access_log_format(args) + + def _del_format_log_of_website(self,log_format_name): + site_format_log_status = self._get_format_log_to_website(log_format_name) + try: + for s in site_format_log_status.keys(): + if not site_format_log_status[s]: + continue + website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(s) + format_exist_reg = r'CustomLog\s+"/www.*"\s+{}'.format(log_format_name) + conf = public.readFile(website_conf_file) + if not conf:continue + if not re.search(format_exist_reg,conf):continue + access_log = re.search(format_exist_reg,conf).group().split() + access_log = access_log[0] + ' ' +access_log[1] + 'combined' + conf = re.sub(format_exist_reg,access_log,conf) + public.writeFile(website_conf_file,conf) + return True + except: + return False + + def get_httpd_access_log_format_parameter(self,args=None): + data = { + "%h":"Client's IP address", + "%r":"Request agreement", + "%t":"Request time", + "%>s":"http status code", + "%b":"Send data size", + "%{Referer}i":"http referer", + "%{User-agent}i":"http user agent", + "%{X-Forwarded-For}i":"The real ip of the client", + "%l":"Remote login name", + "%u":"Remote user", + "-":"-" + } + if hasattr(args,'log_format_name'): + site_list = self._get_format_log_to_website(args.log_format_name) + return {'site_list':site_list,'format_log':data} + else: + return data + + def _process_log_format(self,tmp): + log_tips = self.get_httpd_access_log_format_parameter() + data = [] + for t in tmp: + t = t.replace('\"','') + t = t.replace("'", "") + if t not in log_tips: + continue + data.append({t:log_tips[t]}) + return data + + def get_httpd_access_log_format(self,args=None): + try: + reg = "#LOG_FORMAT_BEGIN.*" + conf = public.readFile(self.httpdconf) + if not conf: + return public.return_msg_gettext(False, 'Configuration file not exist') + data = re.findall(reg,conf) + format_name = [i.split('LOG_FORMAT_BEGIN_')[-1] for i in data] + format_log = {} + for i in format_name: + format_reg = "#LOG_FORMAT_BEGIN_{n}(\n|.)+LogFormat\\s+\'(.*)\'\\s+{n}".format(n=i) + tmp = re.search(format_reg,conf) + if not tmp: + continue + tmp = tmp.groups()[1].split() + format_log[i] = self._process_log_format(tmp) + return format_log + except: + return public.returnMsg(False,public.get_error_info()) + + def set_httpd_format_log_to_website(self,args): + ''' + @name 设置网站日志格式 + @author zhwen + @param sites aaa.com,bbb.com + @param log_format_name + ''' + # sites = args.sites.split(',') + sites = loads(args.sites) + try: + all_site = public.M('sites').field('name').select() + reg = r'CustomLog\s+"/www.*{}\s*'.format(args.log_format_name) + for site in all_site: + website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(site['name']) + conf = public.readFile(website_conf_file) + if not conf: + return public.return_msg_gettext(False, 'Configuration file not exist') + format_exist_reg = r'(CustomLog\s+"/www.*\_log).*' + access_log = re.search(format_exist_reg, conf).groups()[0] + '" ' + args.log_format_name + if site['name'] not in sites and re.search(format_exist_reg,conf): + access_log = ' '.join(access_log.split()[:-1]) + conf = re.sub(reg, access_log, conf) + public.writeFile(website_conf_file,conf) + continue + conf = re.sub(format_exist_reg,access_log,conf) + public.writeFile(website_conf_file,conf) + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + except: + return public.returnMsg(False, str(public.get_error_info())) + + def _get_format_log_to_website(self,log_format_name): + tmp = public.M('sites').field('name').select() + reg = 'CustomLog.*{}'.format(log_format_name) + data = {} + for i in tmp: + website_conf_file = '/www/server/panel/vhost/apache/{}.conf'.format(i['name']) + conf = public.readFile(website_conf_file) + if not conf: + data[i['name']] = False + continue + if re.search(reg,conf): + data[i['name']] = True + else: + data[i['name']] = False + return data diff --git a/class_v2/backup_bak_v2.py b/class_v2/backup_bak_v2.py new file mode 100644 index 00000000..42e98757 --- /dev/null +++ b/class_v2/backup_bak_v2.py @@ -0,0 +1,617 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 1249648969@qq.com +# | 主控 备份 +# +--------------------------------------- +import sys, os +if sys.version_info[0] == 2: + reload(sys) + sys.setdefaultencoding('utf-8') +os.chdir('/www/server/panel') +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import time,hashlib,sys,os,json,requests,re,public,random,string,panelMysql,downloadFile +python_bin=public.get_python_bin() +class backup_bak: + _chek_site_file='/tmp/chekc_site.json' + _check_database = '/www/server/panel/data/check_database.json' + _check_site = '/www/server/panel/data/check_site_data.json' + _chekc_path='/www/server/panel/data/check_path_data.json' + _down_path='/www/server/panel/data/download_path_data.json' + _check_database_data=[] + _check_site_data=[] + _check_path_data=[] + _down_path_data=[] + #备份所有站点的进度 + _check_all_site = '/www/server/panel/data/check_site_data_all.json' + _check_site_all_data=[] + #备份所有数据库的进度 + _check_all_date = '/www/server/panel/data/check_date_data_all.json' + _check_date_all_data=[] + def __init__(self): + if not os.path.exists(self._check_all_site): + ret = [] + public.writeFile(self._check_all_site, json.dumps(ret)) + else: + ret = public.ReadFile(self._check_all_site) + self._check_site_all_data = json.loads(ret) + + if not os.path.exists(self._check_all_date): + ret = [] + public.writeFile(self._check_all_date, json.dumps(ret)) + else: + ret = public.ReadFile(self._check_all_date) + self._check_date_all_data = json.loads(ret) + if not os.path.exists('/www/backup/site_backup'): + public.ExecShell('mkdir /www/backup/site_backup -p') + if not os.path.exists('/www/backup/database_backup'): + public.ExecShell('mkdir /www/backup/database_backup') + if not os.path.exists(self._check_database): + ret = [] + public.writeFile(self._check_database, json.dumps(ret)) + else: + ret = public.ReadFile(self._check_database) + self._check_database_data = json.loads(ret) + if not os.path.exists(self._check_site): + ret = [] + public.writeFile(self._check_site, json.dumps(ret)) + else: + ret = public.ReadFile(self._check_site) + self._check_site_data = json.loads(ret) + if not os.path.exists(self._chekc_path): + ret = [] + public.writeFile(self._chekc_path, json.dumps(ret)) + else: + ret = public.ReadFile(self._chekc_path) + self._check_path_data = json.loads(ret) + + #下载所需要的 + if not os.path.exists(self._down_path): + ret = [] + public.writeFile(self._down_path, json.dumps(ret)) + else: + ret = public.ReadFile(self._down_path) + self._down_path_data = json.loads(ret) + + + #判断是否在_check_database_data 中 + def check_database_data(self,data,ret): + if len(data)==0:return False + for i in data: + if int(i['id']) == int(ret['id']): + return True + else: + return False + + def check_database_data2(self,data,ret): + if len(data)==0:return False + for i in data: + if i['id'] == ret['id']: + return True + else: + return False + + #写入_database_data到里面去 + def set_database_data(self,ret): + if len(self._check_database_data) == 0: + self._check_database_data.append(ret) + else: + if self.check_database_data(self._check_database_data,ret): + for i in self._check_database_data: + if int(i['id'])==int(ret['id']): + i['name'] = ret['name'] + i['path']=ret['path'] + i['status']=ret['status'] + else: + self._check_database_data.append(ret) + public.writeFile(self._check_database, json.dumps(self._check_database_data)) + return True + + #写入_site_data到里面去 + def set_site_data(self,ret): + if len(self._check_site_data) == 0: + self._check_site_data.append(ret) + else: + if self.check_database_data(self._check_site_data,ret): + for i in self._check_site_data: + if int(i['id']) == int(ret['id']): + i['name'] = ret['name'] + i['path']=ret['path'] + i['status']=ret['status'] + else: + self._check_site_data.append(ret) + public.writeFile(self._check_site, json.dumps(self._check_site_data)) + return True + + #写入_site_data到里面去 + def set_path_data(self,ret): + if len(self._check_path_data) == 0: + self._check_path_data.append(ret) + else: + if self.check_database_data2(self._check_path_data,ret): + for i in self._check_path_data: + if i['id']==ret['id']: + i['name'] = ret['name'] + i['path']=ret['path'] + i['status']=ret['status'] + else: + self._check_path_data.append(ret) + public.writeFile(self._chekc_path, json.dumps(self._check_path_data)) + return True + + # 显示所有网站信息 + def get_sites(self,get): + data= public.M('sites').field('id,name,path,status,ps,addtime,edate').select() + for i in data: + data2=self.GetSSL(i['name']) + i['ssl']=data2['status'] + if data2['status']: + i['time'] = data2['cert_data'] + else: + i['time'] =False + return data + + + def get_databases(self,get): + data= public.M('databases').field('id,name,username,password,accept,ps,addtime').select() + return data + + # 是否跳转到https + def IsToHttps(self, siteName): + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf'; + conf = public.readFile(file); + if conf: + if conf.find('HTTP_TO_HTTPS_START') != -1: return True; + if conf.find('$server_port !~ 443') != -1: return True; + return False; + + # 取SSL状态 + def GetSSL(self, siteName): + self.setupPath = '/www/server' + path = os.path.join('/www/server/panel/vhost/cert/', siteName) + if not os.path.isfile(os.path.join(path, "fullchain.pem")) and not os.path.isfile(os.path.join(path, "privkey.pem")): + path = os.path.join('/etc/letsencrypt/live/', siteName) + type = 0; + if os.path.exists(path + '/README'): type = 1; + if os.path.exists(path + '/partnerOrderId'): type = 2; + csrpath = path + "/fullchain.pem"; # 生成证书路径 + keypath = path + "/privkey.pem"; # 密钥文件路径 + key = public.readFile(keypath); + csr = public.readFile(csrpath); + file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/' + siteName + '.conf'; + conf = public.readFile(file); + keyText = 'SSLCertificateFile' + if public.get_webserver() == 'nginx': keyText = 'ssl_certificate'; + status = True + if not conf or conf.find(keyText) == -1: + status = False + type = -1 + toHttps = self.IsToHttps(siteName) + id = public.M('sites').where("name=?", (siteName,)).getField('id') + domains = public.M('domain').where("pid=?", (id,)).field('name').select() + cert_data= {} + if csr: + cert_data = self.GetCertName(csrpath) + email = public.M('users').where('id=?',(1,)).getField('email') + if email == '287962566@qq.com': email = '' + return {'status': status, 'cert_data':cert_data} + + #转换时间 + def strfToTime(self,sdate): + import time + return time.strftime('%Y-%m-%d',time.strptime(sdate,'%b %d %H:%M:%S %Y %Z')) + + #获取证书名称 + def GetCertName(self,certPath): + try: + openssl = '/usr/local/openssl/bin/openssl'; + if not os.path.exists(openssl): openssl = 'openssl'; + result = public.ExecShell(openssl + " x509 -in "+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 "+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_sites_or_ssl(self,get): + data= public.M('sites').field('id,name,path,status,ps,addtime,edate').select() + for i in data: + i['ssl']=self.GetSSL(i['name']) + return data + + + #backup_database + def backup_database(self,get): + if not public.M('databases').where("name=?",(get.name,)).count():return public.returnMsg(False,'The database does not exist') + id=public.M('databases').where("name=?", (get.name,)).getField('id') + if not id:return public.returnMsg(False,'The database does not exist') + if os.path.exists(self._chek_site_file): + return public.returnMsg(False, 'A backup task already exists in this time period. It is recommended that you choose another time period') + public.ExecShell( python_bin + ' /www/server/panel/class/backup_bak.py database %s &'%id) + return public.returnMsg(True,'OK') + + # backup_database + def backup_site(self, get): + if not public.M('sites').where("name=?", (get.name,)).count(): return public.returnMsg(False, "Website does not exist") + id = public.M('sites').where('name=?',(get.name,)).getField('id') + if not id:return public.returnMsg(False, "Website does not exist") + + #监测是否存在任务 + if os.path.exists(self._chek_site_file): + return public.returnMsg(False, 'A backup task already exists in this time period. It is recommended that you choose another time period') + + + public.ExecShell(python_bin+ ' /www/server/panel/class/backup_bak.py sites %s &' % id) + return public.returnMsg(True, 'OK') + + # backup_path + def backup_path_data(self, get): + if not os.path.exists(get.path):return public.returnMsg(False, "Directory does not exist") + public.ExecShell(python_bin+ ' /www/server/panel/class/backup_bak.py path %s &' % get.path) + return public.returnMsg(True, 'OK') + + #检测数据库执行错误 + def IsSqlError(self,mysqlMsg): + mysqlMsg=str(mysqlMsg) + if "MySQLdb" in mysqlMsg: return False + if "2002," in mysqlMsg or '2003,' in mysqlMsg: return False + if "using password:" in mysqlMsg: return False + if "Connection refused" in mysqlMsg: return False + if "1133" in mysqlMsg: return False + if "libmysqlclient" in mysqlMsg:return False + + #配置 + def mypass(self,act,root): + public.ExecShell("sed -i '/user=root/d' /etc/my.cnf") + public.ExecShell("sed -i '/password=/d' /etc/my.cnf") + if act: + mycnf = public.readFile('/etc/my.cnf'); + rep = "\\[mysqldump\\]\nuser=root" + sea = "[mysqldump]\n" + subStr = sea + "user=root\npassword=\"" + root + "\"\n"; + mycnf = mycnf.replace(sea,subStr) + if len(mycnf) > 100: public.writeFile('/etc/my.cnf',mycnf); + + def backup_database2(self,id): + if not public.M('databases').where("id=?", (id,)).count(): + ret = {} + ret['id'] = id + ret['name'] = False + ret['status'] = False + ret['path'] = False + ret['chekc']=False + self.set_site_data(ret) + return public.returnMsg(False, 'The database does not exist') + id=int(id) + # 添加到chekc_database 中 + ret={} + ret['id']=id + ret['name']=public.M('databases').where("id=?", (id,)).getField('name') + ret['status']=False + ret['path']=False + ret['chekc'] = True + self.set_database_data(ret) + if not os.path.exists(self._chek_site_file): public.ExecShell('touch %s' % self._chek_site_file) + path=self.backup_database_data(id) + os.remove(self._chek_site_file) + + ret['status'] = True + ret['path'] = path + self.set_database_data(ret) + + def backup_path_data2(self,path): + id=''.join(random.sample(string.ascii_letters + string.digits, 4)) + if not os.path.exists(path): + ret = {} + ret['id'] = id + ret['name'] = False + ret['status'] = False + ret['path'] = False + ret['chekc']=False + self.set_path_data(ret) + return public.returnMsg(False, "Directory does not exist") + # 添加到chekc_database 中 + ret={} + ret['id']=id + ret['name']=path + ret['status']=False + ret['path']=False + ret['chekc'] = True + self.set_path_data(ret) + path2=self.backup_path(path) + ret['status'] = True + ret['path'] = path2 + self.set_path_data(ret) + return True + + def backup_site2(self,id): + if not public.M('sites').where("id=?", (id,)).count(): + ret = {} + ret['id'] = id + ret['name'] = False + ret['status'] = False + ret['path'] = False + ret['chekc']=False + self.set_site_data(ret) + return public.returnMsg(False, "Site does not exist") + id=int(id) + # 添加到chekc_database 中 + ret={} + ret['id']=id + ret['name']=public.M('sites').where("id=?", (id,)).getField('name') + ret['status']=False + ret['path']=False + ret['chekc'] = True + self.set_site_data(ret) + if not os.path.exists(self._chek_site_file): public.ExecShell('touch %s' % self._chek_site_file) + path=self.backup_site_data(id) + os.remove(self._chek_site_file) + ret['status'] = True + ret['path'] = path + self.set_site_data(ret) + return True + + #备份数据库 + def backup_database_data(self,id): + result = panelMysql.panelMysql().execute("show databases") + isError =self.IsSqlError(result) + if isError: return isError + name = public.M('databases').where("id=?", (id,)).getField('name') + root = public.M('config').where('id=?', (1,)).getField('mysql_root') + if not os.path.exists('/www/server/panel/BTPanel/static' + '/database'): public.ExecShell( + 'mkdir -p ' + '/www/server/panel/BTPanel/static' + '/database'); + self.mypass(True, root) + path_id = ''.join(random.sample(string.ascii_letters + string.digits, 20)) + fileName = path_id+'DATA'+name + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.sql.gz' + backupName = '/www/server/panel/BTPanel/static'+ '/database/' + fileName + public.ExecShell("/www/server/mysql/bin/mysqldump --default-character-set=" + public.get_database_character( + name) + " --force --opt \"" + name + "\" | gzip > " + backupName) + if not os.path.exists(backupName): return public.returnMsg(False, 'BACKUP_ERROR') + self.mypass(False, root) + sql = public.M('backup') + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + sql.add('type,name,pid,filename,size,addtime', (1, fileName, id, backupName, 0, addTime)) + public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS", (name,)) + return backupName + + #备份网站 + def backup_site_data(self,id): + path_id = ''.join(random.sample(string.ascii_letters + string.digits, 20)) + find = public.M('sites').where("id=?",(id,)).field('name,path,id').find() + import time + fileName = path_id+'WEB'+find['name']+'_'+time.strftime('%Y%m%d_%H%M%S',time.localtime())+'.zip' + backupPath = '/www/server/panel/BTPanel/static'+ '/site' + zipName = backupPath + '/'+fileName + if not (os.path.exists(backupPath)): os.makedirs(backupPath) + tmps = '/tmp/panelExec.log' + execStr = "cd '" + find['path'] + "' && zip '" + zipName + "' -x .user.ini -r ./ > " + tmps + " 2>&1" + public.ExecShell(execStr) + sql = public.M('backup').add('type,name,pid,filename,size,addtime',(0,fileName,find['id'],zipName,0,public.getDate())) + public.WriteLog('TYPE_SITE', 'SITE_BACKUP_SUCCESS',(find['name'],)) + return zipName + + #备份目录 + def backup_path(self,path): + import time + path_id = ''.join(random.sample(string.ascii_letters + string.digits, 20)) + fileName =path_id+ path.replace('/','_')+'_'+time.strftime('%Y%m%d_%H%M%S',time.localtime())+'.zip' + backupPath = '/www/server/panel/BTPanel/static'+ '/path' + zipName = backupPath + '/'+fileName + if not (os.path.exists(backupPath)): os.makedirs(backupPath) + tmps = '/tmp/panelExec.log' + execStr = "cd '" + path + "' && zip '" + zipName + "' -x .user.ini -r ./ > " + tmps + " 2>&1" + public.ExecShell(execStr) + public.WriteLog('TYPE_FILE', 'Backup folder [ % s] succeeded'%path) + print(zipName) + return zipName + + #查看数据备份进度 + def get_database_progress(self,get): + id=get.id + for i in self._check_database_data: + if int(i['id'])==int(id): + return public.returnMsg(True, i) + else: + return public.returnMsg(False,'False') + + #查看网站备份进度 + def get_site_progress(self,get): + id=get.id + for i in self._check_site_data: + if int(i['id']) == int(id): + return public.returnMsg(True, i) + else: + return public.returnMsg(False, 'False') + + #查看网站备份进度 + def get_path_progress(self,get): + id=get.id + for i in self._check_path_data: + if i['id'] == id: + return public.returnMsg(True, i) + else: + return public.returnMsg(False, 'False') + + ###########文件下载 + # 判断是否在_check_database_data 中 + def check_down_data(self, data, ret): + if len(data) == 0: return False + for i in data: + if i['id'] == ret['id'] and i['type']==ret['type']: + return True + else: + return False + + def set_down_data(self, ret): + if len(self._down_path_data) == 0: + self._down_path_data.append(ret) + else: + if self.check_database_data(self._down_path_data, ret): + for i in self._down_path_data: + if i['id'] == ret['id'] and i['type']==ret['type']: + i['name'] = ret['name'] + i['url']=ret['url'] + i['filename']=ret['filename'] + i['status'] = ret['status'] + else: + self._down_path_data.append(ret) + public.writeFile(self._down_path, json.dumps(self._down_path_data)) + return True + + #下载对方的备份文件 + def download_path(self,get): + filename=get.filename + ret = {} + ret['type']=get.type + ret['id']=get.id + ret['name']=get.name + ret['url']=get.url + ret['filename']=filename + ret['status']=False + self.set_down_data(ret) + print(python_bin+ ' /www/server/panel/class/backup_bak.py down %s %s %s %s %s &'%(get.url,filename,get.type,get.id,get.name)) + public.ExecShell(python_bin+ ' /www/server/panel/class/backup_bak.py down %s %s %s %s %s &'%(get.url,filename,get.type,get.id,get.name)) + return True + + def down2(self,url,filename,type,id,name): + self.down(url,filename) + ret={} + ret['url'] = url + ret['type']=type + ret['id']=id + ret['name']=name + ret['filename'] = filename + ret['status']=True + self.set_down_data(ret) + + #测试下载 + def down(self,url,filename): + print(url) + print("Download to %s"%filename) + down=downloadFile.downloadFile() + ret=down.DownloadFile(url,filename) + print('Download completed') + return True + + #查看网站备份进度 + def get_down_progress(self,get): + id=get.id + type=get.type + for i in self._down_path_data: + if i['id'] == id and i['type']==type: + return public.returnMsg(True, i) + else: + return public.returnMsg(False, 'False') + + # backup_database + def backup_site_all(self, get): + #监测是否存在任务 + if os.path.exists(self._chek_site_file): + return public.returnMsg(False, 'A backup task already exists in this time period. It is recommended that you choose another time period') + + public.ExecShell(python_bin+ ' /www/server/panel/class/backup_bak.py sites_ALL 11 &') + return public.returnMsg(True, 'OK') + + def set_backup_all(self): + data = public.M('sites').field('id,name,path,status,ps,addtime,edate').select() + site_list = [] + #进度格式 总数量 当前数量 和返回的结果 + jindu={} + jindu['start_count']=len(data) + jindu['end_count']=0 + jindu['resulit']=site_list + public.writeFile(self._check_all_site, json.dumps(jindu)) + if not os.path.exists(self._chek_site_file): public.ExecShell('touch %s' % self._chek_site_file) + for i in data: + path = self.backup_site_data(i['id']) + if path: + resulit = {} + resulit['id'] = i['id'] + resulit['path'] = path + resulit['type'] = 'sites' + resulit['name']=i['name'] + jindu['resulit'].append(resulit) + jindu['end_count'] +=1 + print(jindu) + public.writeFile(self._check_all_site, json.dumps(jindu)) + os.remove(self._chek_site_file) + return site_list + + #查看网站备份进度 + def get_all_site_progress(self,get): + return self._check_site_all_data + + # backup_database + def backup_date_all(self, get): + if os.path.exists(self._chek_site_file): + return public.returnMsg(False, 'A backup task already exists in this time period. It is recommended that you choose another time period') + public.ExecShell(python_bin+ ' /www/server/panel/class/backup_bak.py database_ALL 11 &') + return public.returnMsg(True, 'OK') + + def backup_all_database(self): + data = public.M('databases').field('id,name,username,password,accept,ps,addtime').select() + site_list = [] + #进度格式 总数量 当前数量 和返回的结果 + jindu={} + jindu['start_count']=len(data) + jindu['end_count']=0 + jindu['resulit']=site_list + public.writeFile(self._check_all_date, json.dumps(jindu)) + if not os.path.exists(self._chek_site_file): public.ExecShell('touch %s' % self._chek_site_file) + for i in data: + path = self.backup_database_data(i['id']) + if path: + resulit = {} + resulit['id'] = i['id'] + resulit['path'] = path + resulit['type'] = 'sites' + resulit['name'] = i['name'] + jindu['resulit'].append(resulit) + jindu['end_count'] +=1 + print(jindu) + public.writeFile(self._check_all_date, json.dumps(jindu)) + os.remove(self._chek_site_file) + return site_list + + #查看网站备份进度 + def get_all_date_progress(self,get): + return self._check_date_all_data + +if __name__ == '__main__': + p = backup_bak() + ret = sys.argv[1] + type = sys.argv[2] + if ret =='sites': + p.backup_site2(type) + elif ret=='sites_ALL': + p.set_backup_all() + elif ret=='database_ALL': + p.backup_all_database() + elif ret=='database': + p.backup_database2(type) + elif ret=='path': + p.backup_path_data2(type) + elif ret=='down': + filename = sys.argv[3] + down_type=sys.argv[4] + down_id=sys.argv[5] + down_name=sys.argv[6] + p.down2(type,filename,down_type,down_id,down_name) + diff --git a/class_v2/btdockerModelV2/appModel.py b/class_v2/btdockerModelV2/appModel.py new file mode 100644 index 00000000..7fd3476d --- /dev/null +++ b/class_v2/btdockerModelV2/appModel.py @@ -0,0 +1,79 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 - Docker应用 +# ------------------------------ +import public +import os +import time +import json +import re +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + + def __init__(self): + pass + + # 2024/2/20 下午 4:31 获取/搜索docker应用的列表 + def get_app_list(self, get=None): + ''' + @name 获取docker应用的列表 + @author wzz <2024/2/20 下午 4:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + try: + from btdockerModelV2 import registryModel as dr + dr.main().registry_list(get) + + from panelPlugin import panelPlugin + pp = panelPlugin() + get.type = 10 + # get.type = 16 # dev docker + # get.type = 14 # www docker + get.force = get.force if "force" in get and get.force else 0 + if not hasattr(get, "query"): + get.query = "" + get.tojs = "soft.get_list" + # softList = pp.get_soft_list(get) + if get.query != "": + get.row = 1000 + softList = pp.get_soft_list(get) + softList['list'] = self.struct_list(softList['list']) + softList['list'] = pp.get_page(softList['list']['data'], get) + else: + + softList = pp.get_soft_list(get) + return public.return_message(0, 0, softList['list']) + except Exception as e: + # public.print_log("1111111111 进方法") + return public.return_message(-1, 0, e) + + # 2024/2/20 下午 4:47 处理云端软件列表,只需要list中type=13的数据 + def struct_list(self, softList: dict): + ''' + @name 处理云端软件列表,只需要list中type=13的数据 + @param softList: + @return: + ''' + new_list = [] + for i in softList['data']: + # if i['type'] == 14: # www docker + # if i['type'] == 16: # dev docker + if i['type'] == 10: + new_list.append(i) + + softList['data'] = new_list + + return softList diff --git a/class_v2/btdockerModelV2/backupModel.py b/class_v2/btdockerModelV2/backupModel.py new file mode 100644 index 00000000..98461fd7 --- /dev/null +++ b/class_v2/btdockerModelV2/backupModel.py @@ -0,0 +1,185 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import os +import time +import traceback +import gettext +_ = gettext.gettext + +# ------------------------------ +# Docker模型 +# ------------------------------ +import public +from btdockerModelV2 import containerModel as dc +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + + # 2023/12/22 上午 9:56 备份指定容器的mount v11olume + def backup_volume(self, get): + ''' + @name 备份指定容器的mount volume + @author wzz <2023/12/22 上午 11:19> + @param "data":{"container_id":"容器ID"} + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + # 校验参数 + try: + get.validate([ + Param('container_id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + client = dp.docker_client() + container = client.containers.get(get.container_id) + volume_list = container.attrs["Mounts"] + volume_list = [v["Source"] for v in volume_list] + + if not volume_list: + return public.return_message(-1, 0, _("There is no volume to back up")) + + backup_path = "/www/backup/btdocker/volumes/{}".format(container.name) + if not os.path.exists(backup_path): + os.makedirs(backup_path, 0o755) + + import subprocess + public.ExecShell("echo -n > {}".format(self._backup_log)) + + for v in volume_list: + backup_name = os.path.basename(v) + # 2023/12/22 上午 10:34 每个压缩包命名都用v的目录名,如果是文件则用文件名 + tar_name = "{}_{}_{}.tar.gz".format( + container.name, + backup_name, + time.strftime("%Y%m%d_%H%M%S", time.localtime()) + ) + backup_file = os.path.join(backup_path, tar_name) + source_path = os.path.dirname(v) + cmd = "cd {} && tar zcvf {} {}".format(source_path, backup_file, backup_name) + cmd = ("nohup echo 'To start backing up {} of container {}, it may take more than 1-5 minutes...' >> {};" + "{} >> {} 2>&1 &&" + "echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &" + .format( + container.name, + tar_name, + self._backup_log, + cmd, + self._backup_log, + self._backup_log, + self._backup_log, + )) + subprocess.Popen(cmd, shell=True) + + # 2023/12/22 下午 12:17 添加到数据库 + dp.sql('dk_backup').add( + 'type,name,container_id,container_name,filename,size,addtime', + (3, tar_name, container.id, container.name, backup_file, 0, time.strftime( + "%Y-%m-%d %H:%M:%S", time.localtime() + )) + ) + public.WriteLog("Docker module", "The {} of the backup container {} succeeds!".format(container.name, tar_name)) + + return public.return_message(0, 0, _("The backup task was created successfully.")) + except Exception as e: + print(traceback.format_exc()) + return public.return_message(-1, 0, _("Failed to create a backup task {}".format(str(e)))) + + # 2023/12/22 上午 11:23 获取指定容器的备份列表 + def get_backup_list(self, get): + ''' + @name 获取指定容器的备份列表 + @param "data":{"container_id":"容器ID"} + @return list[dict{"":""}] + ''' + # 校验参数 + try: + get.validate([ + Param('container_id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + # 2023/12/22 下午 12:24 从数据库中获取已备份的指定容器 + backup_list = dp.sql('dk_backup').where('container_id=?', (get.container_id,)).field( + 'name,container_id,container_name,filename,size,addtime' + ).select() + + for l in backup_list: + if not os.path.exists(l['filename']): + l['size'] = 0 + l['ps'] = 'file does not exist' + continue + + l['size'] = os.path.getsize(l['filename']) + l['ps'] = 'local backup' + + return public.return_message(0, 0, backup_list) + + except Exception as e: + print(traceback.format_exc()) + return public.return_message(0, 0, []) + + # 2023/12/22 下午 2:25 删除指定容器的备份 + def remove_backup(self, get): + ''' + @name 删除指定容器的备份 + @param "data":{"container_id":"容器ID","container_name":"容器名","name":"文件名"} + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + # 校验参数 + try: + get.validate([ + Param('container_id').Require().String(), + Param('container_name').Require().String(), + Param('name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + try: + # 2023/12/22 下午 2:26 从数据库中删除指定容器的备份 + dp.sql('dk_backup').where('container_id=? and name=?', (get.container_id, get.name)).delete() + + # 2023/12/22 下午 2:27 删除本地备份文件 + backup_path = "/www/backup/btdocker/volumes/{}".format(get.container_name) + file_path = os.path.join(backup_path, get.name) + if not os.path.exists(file_path): + return public.return_message(0, 0, _("successfully delete")) + os.remove(file_path) + return public.return_message(0, 0, _("successfully delete")) + except Exception as e: + print(traceback.format_exc()) + return public.return_message(-1, 0, _("{} Failed to delete the file, reason: {}".format(get.name, str(e)))) + + def get_pull_log(self, get): + """ + 获取镜像拉取日志,websocket + @param get: + @return: + """ + get.wsLogTitle = "Start container directory backup, please wait..." + get._log_path = self._backup_log + return self.get_ws_log(get) \ No newline at end of file diff --git a/class_v2/btdockerModelV2/composeModel.py b/class_v2/btdockerModelV2/composeModel.py new file mode 100644 index 00000000..4172a4fb --- /dev/null +++ b/class_v2/btdockerModelV2/composeModel.py @@ -0,0 +1,1044 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import json +import os +import time +import gettext +_ = gettext.gettext + +import public +from btdockerModelV2 import containerModel as dc +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + + +class main(dockerBase): + + def get_docker_compose_version(self): + try: + import subprocess + result = subprocess.run(["docker-compose", "version", "--short"], capture_output=True, text=True) + version_str = result.stdout.strip() + major, minor, patch = map(int, version_str.split('.')) + return major, minor, patch + except Exception as e: + print("Error:", e) + return None + + # 验证配置文件 + def check_conf(self, path): + # 2024/3/21 下午 5:58 检测path是否存在中文,如果有就false return + if public.check_chinese(path): + return public.returnMsg(False, "The file path cannot contain Chinese characters!") + + # 2024/3/20 下午 4:33 获取docker-compose的版本,如果大于v2.24.7则不需要检测,比如是v2.25,解决高版本的docker-compose + version = self.get_docker_compose_version() + if version and version > (2, 24, 7): + tmpfile = public.md5(path) + public.ExecShell(r"\cp -r {} /tmp/{}.yml".format(path, public.md5(path))) + public.ExecShell("sed -i '/version:/d' /tmp/{}.yml".format(tmpfile)) + path = "/tmp/{}.yml".format(tmpfile) + + shell = "/usr/bin/docker-compose -f {} config".format(path) + a, e = public.ExecShell(shell) + if e and "setlocale: LC_ALL: cannot change locale (en_US.UTF-8)" not in e: + return public.returnMsg(False, "Detection failed: {}".format(e)) + return public.returnMsg(True, "Detection passes!") + + # 用引导方式创建模板 + def add_template_gui(self, get): + """ + 用引导方式创建模板 + :param name 模板名 + :param description 模板描述 + :param data 模板内容 {"version":3,"services":{...}...} + :param get: + 模板文件参数: + version 2/3version + 2: 仅支持单机 + 3:支持单机和多机模式 + services: + 多个容器的集合 + 下一层执行服务名 + 如web1,服务名下面指定服务的变量 + web1: + build: . 基于dockerfile构建一个镜像 + image: nginx 服务所使用的镜像为nginx + container_name: "web" 容器名 + depends_on: 该服务在db服务启动后再启动 + - db + ports: + - "6061:80" 将容器的80端口映射到主机的6061端口 + networks: + - frontend 该容器所在的网络 + deploy: 指定与部署和运行服务相关的配置(在使用 swarm时才会生效) + replicas: 6 6个副本 + update_config: + parallelism: 2 + delay: 10s + restart_policy: + condition: on-failure + 其他详细描述可以参考 https://docs.docker.com/compose/compose-file/compose-file-v3 + :return: + """ + import yaml + path = "{}/template".format(self.compose_path) + file = "{}/{}.yaml".format(path, get.name) + if not os.path.exists(path): + os.makedirs(path) + data = json.loads(get.data) + yaml.dump(data, file) + + def get_template_kw(self, get): + data = { + "version": "", + "services": { + "server_name_str": { # 用户输入 + "build": { + "context": "str", + "dockerfile": "str", + "get": [], + "cache_from": [], + "labels": [], + "network": "str", + "shm_size": "str", + "target": "str" + }, + "cap_add": "", + "cap_drop": "", + "cgroup_parent": "str", + "command": "str", + "configs": { + "my_config_str": [] + }, + "container_name": "str", + "credential_spec": { + "file": "str", + "registry": "str" + }, + "depends_on": [], + "deploy": { + "endpoint_mode": "str", + "labels": { + "key": "value" + }, + "mode": "str", + "placement": [{"key": "value"}], + "max_replicas_per_node": "int", + "replicas": "int", + "resources": { + "limits": { + "cpus": "str", + "memory": "str", + }, + "reservations": { + "cpus": "str", + "memory": "str", + }, + "restart_policy": { + "condition": "str", + "delay": "str", + "max_attempts": "int", + "window": "str" + } + } + } + } + } + } + + # 创建项目配置文件 + def add_template(self, get): + """ + 添加一个模板文件 + :param name 模板名 + :param remark 模板描述 + :param data 模板内容 + :param get: + :return: + """ + import re + name = get.name + if not re.search(r"^[\w\.\-]+$", name): + return public.return_message(-1, 0, + "Template names cannot contain special characters; only letters, numbers, underscores, dots, and underscores are supported") + + template_list = self._template_list(get) + for template in template_list: + if name == template['name']: + return public.return_message(-1, 0, _("This template name already exists!")) + + path = "{}/{}/template".format(self.compose_path, name) + file = "{}/{}.yaml".format(path, name) + if not os.path.exists(path): + os.makedirs(path) + public.writeFile(file, get.data) + + check_res = self.check_conf(file) + if not check_res['status']: + if os.path.exists(file): + os.remove(file) + return public.return_message(-1, 0, check_res['msg']) + + pdata = { + "name": name, + "remark": public.xsssec(get.remark), + "path": file + } + dp.sql("templates").insert(pdata) + dp.write_log("Added template [{}] successfully!".format(name)) + public.set_module_logs('docker', 'add_template', 1) + return public.return_message(0, 0,_("Template added successfully!")) + + def edit_template(self, get): + """ + :param id 模板id + :param data 模板内容 + :param remark 模板描述 + :param get: + :return: + """ + template_info = dp.sql("templates").where("id=?", (get.id,)).find() + if not template_info: + return public.return_message(-1, 0, _("Did not change the template!")) + + if "data" not in get: + return public.return_message(-1, 0, + "Template content format error, please enter a valid docker-compose template!") + + if "version" not in get.data: + return public.return_message(-1, 0, + "Template content format error, please enter a valid docker-compose template!") + + public.writeFile(template_info['path'], get.data) + check_res = self.check_conf(template_info['path']) + if not check_res['status']: + return public.return_message(-1, 0,check_res['msg']) + pdata = { + "name": get.name, + "remark": public.xsssec(get.remark), + "path": template_info['path'] + } + dp.sql("templates").where("id=?", (get.id,)).update(pdata) + dp.write_log("Edit template [{}] successful!".format(template_info['name'])) + return public.return_message(0, 0, _("Modified template successfully!")) + + def get_template(self, get): + """ + id 模板ID + 获取模板内容 + :return: + """ + + # 校验参数 + try: + get.validate([ + Param('template_id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + template_info = dp.sql("templates").where("id=?", (get.template_id,)).find() + if not template_info: + return public.return_message(-1, 0, _("This template was not found!")) + + return public.return_message(0, 0, public.readFile(template_info['path'])) + + def template_list(self, get): + """ + 获取所有模板 + :param get: + :return: + """ + template = dp.sql("templates").select()[::-1] + if not isinstance(template, list): + template = [] + + return public.return_message(0, 0, template) + + # 内部调用 不改响应格式 + def _template_list(self, get): + """ + 获取所有模板 + :param get: + :return: + """ + template = dp.sql("templates").select()[::-1] + if not isinstance(template, list): + template = [] + + return template + + def remove_template(self, get): + """ + 删除模板 + :param template_id + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('template_id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + data = dp.sql("templates").where("id=?", (get.template_id,)).find() + if not data: + return public.return_message(-1, 0, _("This template was not found!")) + if os.path.exists(data['path']): + os.remove(data['path']) + dp.sql("templates").delete(id=get.template_id) + dp.write_log("Delete template [{}] successfully!".format(data['name'])) + return public.return_message(0, 0, _("successfully delete!")) + + def edit_project_remark(self, get): + """ + 编辑项目 + :param project_id 项目 + :param remark备注 + :param get: + :return: + """ + stacks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not stacks_info: + return public.returnMsg(False, "The item was not found!") + pdata = { + "remark": public.xsssec(get.remark) + } + dp.write_log("Comment for project [{}] changed successfully [{}] --> [{}]!".format(stacks_info['name'], + stacks_info['remark'], + public.xsssec(get.remark))) + dp.sql("stacks").where("id=?", (get.project_id,)).update(pdata) + + def edit_template_remark(self, get): + """ + 编辑项目 + :param templates_id 项目 + :param remark备注 + :param get: + :return: + """ + stacks_info = dp.sql("templates").where("id=?", (get.templates_id,)).find() + if not stacks_info: + return public.returnMsg(False, "The template was not found!") + pdata = { + "remark": public.xsssec(get.remark) + } + dp.write_log( + "Modify template [{}] Remark successful [{}] --> [{}]!".format(stacks_info['name'], stacks_info['remark'], + public.xsssec(get.remark))) + dp.sql("templates").where("id=?", (get.templates_id,)).update(pdata) + + def create_project_in_path(self, name, path): + shell = "cd {} && /usr/bin/docker-compose -p {} up -d &> {}".format("/".join(path.split("/")[:-1]), name, + self._log_path) + public.ExecShell(shell) + + def create_project_in_file(self, project_name, file): + project_path = "{}/{}".format(self.compose_path, project_name) + project_file = "{}/docker-compose.yaml".format(project_path) + if not os.path.exists(project_path): + os.makedirs(project_path) + template_content = public.readFile(file) + public.writeFile(project_file, template_content) + shell = "/usr/bin/docker-compose -p {} -f {} up -d &> {}".format(project_name, project_file, self._log_path) + public.ExecShell(shell) + + def check_project_container_name(self, template_data, get): + """ + 检测模板文件中的容器名是否已经存在 + :return: + """ + import re + data = [] + template_container_name = re.findall("container_name\\s*:\\s*[\"\']+(.*)[\'\"]", template_data) + # 调用容器列表接口 选择不改统一返回的 + container_list = dc.main()._get_list(get) + + container_list = container_list['container_list'] + for container in container_list: + if container['name'] in template_container_name: + data.append(container['name']) + if data: + return public.returnMsg(False, "The container name already exists!:
                                    [{}]".format(", ".join(data))) + # 获取模板所使用的端口 + rep = r"(\d+):\d+" + port_list = re.findall(rep, template_data) + for port in port_list: + if dp.check_socket(port): + return public.returnMsg(False, "This port [{}] is already used by other templates".format(port)) + + # 创建项目 + def create(self, get): + """ + :param project_name 项目名 + :param remark 描述 + :param template_id 模板ID + :param rags: + :return: + """ + # {"template_id": "13", "project_name": "dedf2f", "remark": "从本地添加asd"} + # 校验参数 + try: + get.validate([ + Param('template_id').Require().Integer(), + Param('project_name').Require().String().Xss(), + Param('remark').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + project_name = public.md5(get.project_name) + # if "template_id" not in get: + # return public.returnMsg(False, "Parameter error, please pass in template_id!") + template_id = get.template_id + template_info = dp.sql("templates").where("id=?", template_id).find() + if len(template_info) < 1: + return public.return_message(-1, 0, _("This template was not found, or file is corrupt!")) + + if not os.path.exists(template_info['path']): + return public.return_message(-1, 0, _("Template file does not exist")) + + template_exist = dp.sql("stacks").where("template_id=?", (template_id,)).find() + if template_exist: + return public.return_message(-1, 0, + "Template [{}] has been deployed by project: [{}], please change a template and try again!".format( + template_info['name'], template_exist['name'])) + + name_exist = self.check_project_container_name(public.readFile(template_info['path']), get) + if name_exist: + return public.return_message(-1, 0, name_exist['msg']) + + stacks_info = dp.sql("stacks").where("name=?", (project_name)).find() + if not stacks_info: + pdata = { + "name": public.xsssec(get.project_name), + "status": "1", + "path": template_info['path'], + "template_id": template_id, + "time": time.time(), + "remark": public.xsssec(get.remark) + } + dp.sql("stacks").insert(pdata) + else: + return public.return_message(-1, 0, _("The project name already exists!")) + + if template_info['add_in_path'] == 1: + self.create_project_in_path( + project_name, + template_info['path'] + ) + else: + self.create_project_in_file( + project_name, + template_info['path'] + ) + dp.write_log("Project [{}] deployed successfully!".format(project_name)) + public.set_module_logs('docker', 'add_project', 1) + return public.return_message(0, 0, _("Successful deployment!")) + except Exception as ex: + public.print_log(traceback.format_exc()) + return public.return_message(-1, 0, str(ex)) + + def compose_project_list(self, get): + """ + 获取所有已部署的项目列表 + @param get: + """ + compose_project = dp.sql("stacks").select() + # public.print_log("部署项目 {}".format(compose_project)) + try: + cmd_result = public.ExecShell("/usr/bin/docker-compose ls -a --format json")[0] + if "Segmentation fault" in cmd_result: + return public.returnMsg(False, "docker-compose is too low, please upgrade to the latest version!") + result = json.loads(cmd_result) + except: + result = [] + + for i in compose_project: + for j in result: + if public.md5(i['name']) in j['Name']: + i['run_status'] = j['Status'].split("(")[0].lower() + break + else: + i['run_status'] = "exited" + + return public.return_message(0, 0, compose_project) + + def project_container_count(self, get): + """ + 获取项目容器数量 + @param get: + @return: + """ + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + sk_container_list = sk_container.get_container() + + stacks_info = dp.sql("stacks").select() + net_info = [] + + for i in stacks_info: + count = 0 + for c in sk_container_list: + if public.md5(i['name']) in c["Names"][0].replace("/", ""): + count += 1 + continue + + if 'com.docker.compose.project' in c.keys(): + if public.md5(i['name']) in c['com.docker.compose.project.config_files']: + count += 1 + continue + + if public.md5(i['name']) in public.md5(c['com.docker.compose.project.config_files']): + count += 1 + continue + + if 'com.docker.compose.project' in c['Labels'].keys(): + if public.md5(i['name']) in c['Labels']['com.docker.compose.project.config_files']: + count += 1 + continue + + if public.md5(i['name']) in public.md5(c['Labels']['com.docker.compose.project.config_files']): + count += 1 + continue + + net_info.append(count) + + return public.return_message(0, 0, net_info) + + def get_compose_container(self, get): + """ + 目前仅支持本地 url: unix:///var/run/docker.sock + """ + # 校验参数 + try: + get.validate([ + Param('name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + sk_container_list = sk_container.get_container() + + project_container_list = [] + for c in sk_container_list: + if public.md5(get.name) in dp.rename(c["Names"][0].replace("/", "")): + project_container_list.append(dc.main().struct_container_list(c)) + continue + + if 'com.docker.compose.project' in c.keys(): + if public.md5(get.name) in c['com.docker.compose.project.config_files']: + project_container_list.append(dc.main().struct_container_list(c)) + + if public.md5(get.name) in public.md5(c['com.docker.compose.project.config_files']): + project_container_list.append(dc.main().struct_container_list(c)) + + if 'com.docker.compose.project' in c['Labels'].keys(): + if public.md5(get.name) in c['Labels']['com.docker.compose.project.config_files']: + project_container_list.append(dc.main().struct_container_list(c)) + + if public.md5(get.name) in public.md5(c['Labels']['com.docker.compose.project.config_files']): + project_container_list.append(dc.main().struct_container_list(c)) + + return public.return_message(0, 0, project_container_list) + + # 删除项目 + def remove(self, get): + """ + project_id 数据库记录的项目ID + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('project_id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.return_message(-1, 0, _("The project name was not found!")) + container_name = public.ExecShell("docker ps --format \"{{.Names}}\"") + if statcks_info['name'] in container_name[0]: + shell = f"/usr/bin/docker-compose -p {statcks_info['name']} -f {statcks_info['path']} down &> {self._log_path}" + else: + shell = f"/usr/bin/docker-compose -p {public.md5(statcks_info['name'])} -f" \ + f" {statcks_info['path']} down &> {self._log_path}" + public.ExecShell(shell) + dp.sql("stacks").delete(id=get.project_id) + dp.write_log("Delete project [{}] success!".format(statcks_info['name'])) + return public.return_message(0, 0, _("successfully delete!")) + + def prune(self, get): + """ + 删除所有没有容器的项目 + @param get: + @return: + """ + stacks_info = dp.sql("stacks").select() + container_name = public.ExecShell("docker ps --format \"{{.Names}}\"")[0] + container_name = container_name.split("\n") + for i in stacks_info: + # 2024/3/21 下午 6:26 如果i['name']在container_name[0]中,说明容器还在运行,不删除 + is_run = False + docker_name = public.ExecShell("grep 'container_name' {}".format(i["path"]))[0] + + for j in container_name: + if j == "": continue + if public.md5(i['name']) in j or j in docker_name: + is_run = True + break + + if is_run: continue + shell = "/usr/bin/docker-compose -f {} down &> {}".format(i['path'], self._log_path) + public.ExecShell(shell) + dp.sql("stacks").delete(id=i['id']) + dp.write_log("Cleanup project [{}] successful!".format(i['name'])) + return public.return_message(0, 0, _("Clean up successfully!")) + + + def set_compose_status(self, get): + """ + 设置项目状态 + @param get: + @return: + """ + try: + get.validate([ + + Param('status').Require().String('in', ['start', 'stop','restart','pause','unpause','kill']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if get.status == 'start': + data = self.start(get) + + elif get.status == 'stop': + data = self.stop(get) + elif get.status == 'restart': + data = self.restart(get) + elif get.status == 'pause': + data = self.pause(get) + elif get.status == 'unpause': + data = self.unpause(get) + else: + data = self.kill(get) + + if data["status"]: + return public.return_message(0, 0, data['msg']) + else: + return public.return_message(-1, 0, data['msg']) + + def kill(self, get): + """ + 强制停止项目 + @param get: + @return: + """ + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.returnMsg(False, "Project configuration not found!") + shell = "/usr/bin/docker-compose -f {} kill &> {}".format( + "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name'])), + self._log_path + ) + a, e = public.ExecShell(shell) + if e: + return public.returnMsg(False, "Stopping project failed: {}".format(e)) + dp.write_log("Stopping project [{}] succeeded".format(statcks_info['name'])) + return public.returnMsg(True, "Setup successful!") + + def stop(self, get): + """ + 停止项目 + project_id 数据库记录的项目ID + kill 强制停止项目 0/1 + :param get: + :return: + """ + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.returnMsg(False, "Project configuration not found!") + + shell = "/usr/bin/docker-compose -f {} stop &> {}".format( + "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name'])), + self._log_path + ) + a, e = public.ExecShell(shell) + if e: + return public.returnMsg(False, "Stopping project failed: {}".format(e)) + dp.write_log("Stopping project [{}] succeeded!".format(statcks_info['name'])) + return public.returnMsg(True, "Setup successful!") + + def start(self, get): + """ + 启动项目 + project_id 数据库记录的项目ID + :param get: + :return: + """ + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.returnMsg(False, "Project configuration not found!") + + shell = "/usr/bin/docker-compose -f {} start &> {}".format( + "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name'])), + self._log_path + ) + a, e = public.ExecShell(shell) + if e: + return public.returnMsg(False, "Failed to start project: {}".format(e)) + dp.write_log("Start project [{}] successful!".format(statcks_info['name'])) + return public.returnMsg(True, "Setup successful!") + + def restart(self, get): + """ + 拉取项目内需要的镜像 + project_id 数据库记录的项目ID + :param get: + :return: + """ + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.returnMsg(False, "Project configuration not found!") + + shell = "/usr/bin/docker-compose -f {} restart &> {}".format( + "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name'])), + self._log_path + ) + a, e = public.ExecShell(shell) + if e: + return public.returnMsg(False, "Failed to restart project: {}".format(e)) + dp.write_log("Restart project [{}] successfully!".format(statcks_info['name'])) + return public.returnMsg(True, "Successfully set!") + + def pull(self, get): + """ + 拉取模板内需要的镜像 + template_id 数据库记录的项目ID + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('template_id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + statcks_info = dp.sql("templates").where("id=?", (get.template_id,)).find() + if not statcks_info: + return public.return_message(0, 0, _("The template was not found!")) + + os.system( + "nohup /usr/bin/docker-compose -f {} pull >> {} 2>&1 " + "&& echo 'bt_successful' >> {} " + "|| echo 'bt_failed' >> {} &".format( + statcks_info['path'], + self._log_path, + self._log_path, + self._log_path, + )) + dp.write_log("The image inside the template [{}] was pulled successfully !".format(statcks_info['name'])) + return public.return_message(0, 0, _("Pull successfully!")) + + def pause(self, get): + """ + 暂停项目 + project_id 数据库记录的项目ID + :param get: + :return: + """ + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.returnMsg(False, "Project configuration not found!") + shell = "/usr/bin/docker-compose -f {} pause &> {}".format( + "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name'])), + self._log_path + ) + a, e = public.ExecShell(shell) + if e: + return public.returnMsg(False, "Failed to suspend project: {}".format(e)) + dp.write_log("Pause [{}] success!".format(statcks_info['name'])) + return public.returnMsg(True, "Successfully set!") + + def unpause(self, get): + """ + 取消暂停项目 + project_id 数据库记录的项目ID + :param get: + :return: + """ + statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find() + if not statcks_info: + return public.returnMsg(False, "Project configuration not found!") + shell = "/usr/bin/docker-compose -f {} unpause &> {}".format( + "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name'])), + self._log_path + ) + a, e = public.ExecShell(shell) + if e: + return public.returnMsg(False, "Failed to unpause project: {}".format(e)) + dp.write_log("Unpause [{}] success!".format(statcks_info['name'])) + return public.returnMsg(True, "Successfully set!") + + def scan_compose_file(self, path, data): + """ + 递归扫描目录下的compose文件 + :param path 需要扫描的目录 + :param data 需要返回的数据 一个字典 + :param get: + :return: + """ + file_list = os.listdir(path) + for file in file_list: + current_path = os.path.join(path, file) + # 判断是否是文件夹 + if os.path.isdir(current_path): + self.scan_compose_file(current_path, data) + else: + if file == "docker-compose.yaml" or file == "docker-compose.yam" or file == "docker-compose.yml": + if "/www/server/panel/data/compose" in current_path: + continue + data.append(current_path) + if ".yaml" in file or ".yam" in file or ".yml" in file: + if "/www/server/panel/data/compose" in current_path: + continue + data.append(current_path) + return data + + def get_compose_project(self, get): + """ + :param path 需要获取的路径 是一个目录 + :param sub_dir 扫描子目录 + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('path').Require().SafePath(), + Param('sub_dir').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + data = list() + suffix = ["yaml", "yam", "yml"] + if get.path == "/": + return public.return_message(-1, 0, _("Unable to scan the root directory")) + + if get.path[-1] == "/": + get.path = get.path[:-1] + if str(get.sub_dir) == "1": + res = self.scan_compose_file(get.path, data) + if not res: + res = [] + else: + tmp = list() + p_name_tmp = list() + for i in res: + if i.split(".")[1] not in suffix: + continue + + project_name = i.split("/")[-1].split(".")[0] + if project_name in p_name_tmp: + project_name = "{}_{}".format(project_name, i.split("/")[-2]) + + tmp_data = { + "project_name": project_name, + "conf_file": "/".join(i.split("/")), + "remark": "Add locally" + } + + tmp.append(tmp_data) + p_name_tmp.append(tmp_data['project_name']) + res = tmp + p_name_tmp.clear() + else: + yaml = "{}/docker-compose.yaml".format(get.path) + yam = "{}/docker-compose.yam".format(get.path) + yml = "{}/docker-compose.yml".format(get.path) + if os.path.exists(yaml): + res = [{ + "project_name": get.path.split("/")[-1], + "conf_file": yaml, + "remark": "Add locally" + }] + elif os.path.exists(yam): + res = [{ + "project_name": get.path.split("/")[-1], + "conf_file": yam, + "remark": "Add locally" + }] + elif os.path.exists(yml): + res = [{ + "project_name": get.path.split("/")[-1], + "conf_file": yml, + "remark": "Add locally" + }] + else: + res = list() + + if not os.path.isdir(get.path): + return public.return_message(0, 0, res) + + dir_list = os.listdir(get.path) + + for i in dir_list: + if i.rsplit(".")[-1] in suffix: + res.append({ + "project_name": i.rsplit(".")[0], + "conf_file": "/".join(get.path.split("/") + [i]), + "remark": "Add locally" + }) + + return public.return_message(0, 0, res) + + # 从现有目录中添加模板 + def add_template_in_path(self, get): + """ + :param template_list list [{"project_name":"pathtest_template","conf_file":"/www/dockerce/mysecent-project/docker-compose.yaml","remark":"描述描述"}] + :param get: + :return: + """ + + create_failed = dict() + create_successfully = dict() + for template in get.template_list: + path = template['conf_file'] + name = template['project_name'] + remark = template['remark'] + exists = self._template_list(get) + for i in exists: + if name == i['name']: + create_failed[name] = "Template already exists!" + continue + if not os.path.exists(path): + create_failed[name] = "This template was not found!" + continue + check_res = self.check_conf(path) + if not check_res['status']: + create_failed[name] = "Template validation failed, possibly malformed!" + continue + pdata = { + "name": name, + "remark": remark, + "path": path, + "add_in_path": 1 + } + dp.sql("templates").insert(pdata) + create_successfully[name] = "Template added successfully!" + + for i in create_failed: + if i in create_successfully: + del (create_successfully[i]) + else: + dp.write_log("Template added successfully from path [{}]!".format(i)) + if not create_failed and create_successfully: + # return {'status': True, 'msg': 'Template added successfully: [{}]'.format(','.join(create_successfully))} + return public.return_message(0, 0, + 'Template added successfully: [{}]'.format(','.join(create_successfully))) + elif not create_successfully and create_failed: + + # return {'status': False, + # 'msg': 'Failed to add template: template name already exists or is incorrectly formatted [{}],Use docker-compose -f [specify compose.yml file] config to check' + # .format(','.join(create_failed))} + + return public.return_message(-1, 0, + 'Failed to add template: template name already exists or is incorrectly formatted [{}],Use docker-compose -f [specify compose.yml file] config to check' + .format(','.join(create_failed))) + + # return {'status': False, 'msg': 'These templates succeed: [{}]
                                    These templates fail: the template name already exists or is incorrectly formatted [{}]'.format( + # ','.join(create_successfully), ','.join(create_failed))} + return public.return_message(-1, 0, + 'These templates succeed: [{}]
                                    These templates fail: the template name already exists or is incorrectly formatted [{}]'.format( + ','.join(create_successfully), ','.join(create_failed))) + + def get_pull_log(self, get): + """ + 获取镜像拉取日志,websocket + @param get: + @return: + """ + get.wsLogTitle = "Start to pull the template image, please wait..." + get._log_path = self._log_path + return self.get_ws_log(get) + + # 编辑项目 todo 根据删除适配 + def edit(self, get): + """ + :param project_id: 要编辑的项目的ID + :param project_name: 新的项目名 + :param remark: 新的描述 + :param template_id: 新的模板ID + :return: + """ + # {"project_id": 1, "template_id": 2, "project_name": "福达坊", "remark": ""} + # 校验参数 + try: + get.validate([ + Param('project_id').Require().Integer(), + Param('project_name').Require().String(), + Param('remark').String(), + Param('template_id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 删除旧的项目 + remove_result = self.remove(get) + # if not remove_result['status']: + # return public.return_message(-1, 0, _("Fail to modify!")) + + # 创建新的项目 + self.create(get) + return public.return_message(0, 0, _("Modify successfully!")) diff --git a/class_v2/btdockerModelV2/config/com_reg_mirror.json b/class_v2/btdockerModelV2/config/com_reg_mirror.json new file mode 100644 index 00000000..da28faa7 --- /dev/null +++ b/class_v2/btdockerModelV2/config/com_reg_mirror.json @@ -0,0 +1,3 @@ +{ + "https://docker.m.daocloud.io": "Third party image accelerator" +} \ No newline at end of file diff --git a/class_v2/btdockerModelV2/config/com_registry.json b/class_v2/btdockerModelV2/config/com_registry.json new file mode 100644 index 00000000..83c3bc3c --- /dev/null +++ b/class_v2/btdockerModelV2/config/com_registry.json @@ -0,0 +1,11 @@ +{ + "docker.io": "docker official mirror site", + "swr.cn-north-4.myhuaweicloud.com": "Huawei Cloud Mirror Station (North China-Beijing 4)", + "ccr.ccs.tencentyun.com": "Tencent Cloud Mirror Station", + "registry.cn-hongkong.aliyuncs.com": "Alibaba Cloud Mirror Station (Hong Kong)", + "registry.ap-southeast-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Singapore)", + "registry.us-west-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Silicon Valley, USA)", + "registry.eu-west-1.aliyuncs.com": "Alibaba Cloud Mirror Station (London, UK)", + "registry.eu-central-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Frankfurt, Germany)", + "registry.ap-northeast-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Japan)" +} \ No newline at end of file diff --git a/class_v2/btdockerModelV2/config/docker_hub_last_update.pl b/class_v2/btdockerModelV2/config/docker_hub_last_update.pl new file mode 100644 index 00000000..8d76e180 --- /dev/null +++ b/class_v2/btdockerModelV2/config/docker_hub_last_update.pl @@ -0,0 +1 @@ +1710901654 \ No newline at end of file diff --git a/class_v2/btdockerModelV2/config/docker_hub_repos.db b/class_v2/btdockerModelV2/config/docker_hub_repos.db new file mode 100644 index 0000000000000000000000000000000000000000..09f66fa119e29d3b82eaa3521aee23487dc8b54d GIT binary patch literal 112640 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 W^A<~$zlG`lbjnpb)TKqt|NH+^QHCu5 literal 0 HcmV?d00001 diff --git a/class_v2/btdockerModelV2/containerModel.py b/class_v2/btdockerModelV2/containerModel.py new file mode 100644 index 00000000..678a2811 --- /dev/null +++ b/class_v2/btdockerModelV2/containerModel.py @@ -0,0 +1,1743 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import json +import os +import traceback +import time + +import crontab +import docker.errors + +import gettext +_ = gettext.gettext +# ------------------------------ +# Docker模型 +# ------------------------------ +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + + +class main(dockerBase): + + def __init__(self): + super().__init__() + self.alter_table() + if public.M('sqlite_master').db('docker_log_split').where('type=? AND name=?', ('table', 'docker_log_split')).count(): + # if public.M('sqlite_master').where('type=? AND name=?',('table', 'docker_log_split')).count(): + p = crontab.crontab() + llist = p.GetCrontab(None) + add_crond = True + if type(llist) == list: + for i in llist: + if i['name'] == "[Do not delete] Docker log cuts": + add_crond = False + break + else: + add_crond = True + + if add_crond: + get = { + "name": "[Do not delete] Docker log cuts", + "type": "minute-n", + "where1": 5, + "hour": "", + "minute": "", + "week": "", + "sType": "toShell", + "sName": "", + "backupTo": "localhost", + "save": '', + "sBody": "btpython /www/server/panel/script/dk_log_split.py", + "urladdress": "undefined" + } + p.AddCrontab(get) + + def alter_table(self): + if not dp.sql('sqlite_master').db('container').where('type=? AND name=? AND sql LIKE ?', + ('table', 'container', '%sid%')).count(): + dp.sql('container').execute("alter TABLE container add container_name VARCHAR DEFAULT ''", ()) + + def docker_client(self, url): + # unix:///var/run/docker.sock + return dp.docker_client(url) + + def get_cmd_log(self, get): + """ + 获取命令运行容器的日志,websocket + @param get: + @return: + """ + get.wsLogTitle = "Please wait to execute the command..." + get._log_path = self._rCmd_log + return self.get_ws_log(get) + + def run_cmd(self, get): + """ + 命令行创建运行容器(docker run),需要做危险命令校验,存在危险命令则不执行 + @param get: + @return: + """ + try: + get.validate([ + Param('cmd').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + import re + # if not hasattr(get, 'cmd'): + # return public.return_message(-1, 0, 'cmd parameter error') + if "docker run" not in get.cmd: + return public.return_message(-1, 0, _('Only the docker run command can be executed')) + + danger_cmd = ['rm', 'rmi', 'kill', 'stop', 'pause', 'unpause', 'restart', 'update', 'exec', 'init', + 'shutdown', 'reboot', 'chmod', 'chown', 'dd', 'fdisk', 'killall', 'mkfs', 'mkswap', 'mount', + 'swapoff', 'swapon', 'umount', 'userdel', 'usermod', 'passwd', 'groupadd', 'groupdel', + 'groupmod', 'chpasswd', 'chage', 'usermod', 'useradd', 'userdel', 'pkill'] + + danger_symbol = ['&', '&&', '||', '|', ';'] + + for d in danger_cmd: + if get.cmd.startswith(d) or re.search(r'\s{}\s'.format(d), get.cmd): + return public.return_message(-1, 0, _( + 'Dangerous command exists: [{}],Execution is not allowed!'.format(d))) + + for d in danger_symbol: + if d in get.cmd: + return public.return_message(-1, 0, _( + 'There are danger symbols: [{}],Execution is not allowed!'.format(d))) + + os.system("echo -n > {}".format(self._rCmd_log)) + os.system("nohup {} >> {} 2>&1 && echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &".format( + get.cmd, + self._rCmd_log, + self._rCmd_log, + self._rCmd_log, + )) + + return public.return_message(0, 0, _("The command has been executed!")) + + # 添加容器 + def run(self, get): + """ + :param name:容器名 + :param image: 镜像 + :param publish_all_ports 暴露所有端口 1/0 + :param ports 暴露某些端口 {'1111/tcp': ('127.0.0.1', 1111)} + :param command 命令 + :param entrypoint 配置容器启动后执行的命令 + :param environment 环境变量 xxx=xxx 一行一条 + :param auto_remove 当容器进程退出时,在守护进程端启用自动移除容器。 0/1 + + :param get: + :return: + """ + # {"name": "cgroupg1", "image": "nginx:1.19.6", "publish_all_ports": "0", "ports": "", "network": "", + # "ip_address": "", "command": "", "entrypoint": "", "auto_remove": "0", "privileged": "0", + # "restart_policy": {"Name": ""}, "mem_reservation": "8855MB", "cpu_quota": 6, "mem_limit": "15555MB", + # "labels": "", "environment": ""} + + # 校验参数 + try: + get.validate([ + Param('name').Require().String(), + Param('image').Require().String(), + Param('publish_all_ports').Require().Integer(), + Param('command').String(), + Param('entrypoint').String(), + Param('environment').String(), + Param('auto_remove').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + config_path = "{}/config/name_map.json".format(public.get_panel_path()) + if not os.path.exists(config_path): + public.writeFile(config_path, json.dumps({})) + + if public.readFile(config_path) == '': + public.writeFile(config_path, json.dumps({})) + + # 2024/2/20 下午 3:21 如果检测到是中文的容器名,则自动转换为英文 + name_map = json.loads(public.readFile(config_path)) + import re + if re.findall(r"[\u4e00-\u9fa5]", get.name): + name_str = 'q18q' + public.GetRandomString(10).lower() + name_map[name_str] = get.name + get.name = name_str + public.writeFile(config_path, json.dumps(name_map)) + + cPorts = get.ports if "ports" in get and get.ports != "" else False + nPorts = {} + if not cPorts is False: + if ":" in cPorts.keys(): + return public.return_message(-1, 0, _( "The port format is wrong, this method is not supported!")) + if "-" in cPorts.keys(): + return public.return_message(-1, 0, _( "The port format is wrong, this method is not supported!")) + + for i in cPorts.keys(): + if cPorts[i] == "": continue + if dp.check_socket(cPorts[i]): + return public.return_message(-1, 0, _( + "Server port [{}] is occupied, please change to another port!".format( + cPorts[i]))) + + if "tcp/udp" in i: + cPort = i.split('/')[0] + nPorts[str(cPort) + "/tcp"] = cPorts[i] + nPorts[str(cPort) + "/udp"] = cPorts[i] + else: + nPorts[i] = cPorts[i] + del cPorts + + if "image" not in get or not get.image: + return public.return_message(-1, 0, _( + "Please pull the image you need")) + + if get.image == "" or "" in get.image: + return public.return_message(-1, 0, _( + "The image does not exist!")) + + mem_limit = get.mem_limit if "mem_limit" in get and get.mem_limit != "0" else None + if not mem_limit is None: + mem_limit_byte = dp.byte_conversion(get.mem_limit) + if mem_limit_byte > dp.get_mem_info(): + return public.return_message(-1, 0, _( "The memory quota has exceeded the available amount!")) + if mem_limit_byte < 6291456: + return public.return_message(-1, 0, _( "The memory quota cannot be less than 6MB!")) + + try: + if "force_pull" in get and get.force_pull == "0": + self.docker_client(self._url).images.get(get.image) + except docker.errors.ImageNotFound as e: + return public.return_message(-1, 0, _( + "Image [{}] does not exist, You can try [Forced Pull]!".format( + get.image))) + except docker.errors.APIError as e: + return public.return_message(-1, 0, _( + "Image [{}] does not exist, You can try [Forced Pull]!".format( + get.image))) + + # 2024/4/16 上午11:40 检查镜像是否存在并且处理镜像如果是非应用容器的情况 + try: + from btdockerModelV2.dockerSock import image + sk_image = image.dockerImage() + image_inspect = sk_image.inspect(get.image) + if type(image_inspect) != dict: + return public.return_message(-1, 0, _( "Image [{}] does not exist!".format(get.image))) + + if "Config" in image_inspect and "Cmd" in image_inspect["Config"]: + sh_list = ("bash", "sh", "dash", "/bin/sh", "/bin/bash", "/bin/dash") + if len(image_inspect["Config"]["Cmd"]) == 1 and image_inspect["Config"]["Cmd"][0] in sh_list: + get.tty = "1" + get.stdin_open = "1" + except Exception as e: + pass + + cpu_quota = get.cpu_quota if "cpu_quota" in get and get.cpu_quota != "0" else 0 + if int(cpu_quota) != 0: + cpu_quota = float(get.cpu_quota) * 100000 + + if int(cpu_quota) / 100000 > dp.get_cpu_count(): + return public.return_message(-1, 0, _( "cpu quota has exceeded available cores!")) + + df_restart_policy = {"Name": "unless-stopped", "MaximumRetryCount": 0} + restart_policy = get.restart_policy if "restart_policy" in get and get.restart_policy else df_restart_policy + if restart_policy['Name'] == "always": + restart_policy = {"Name": "always"} + + mem_reservation = get.mem_reservation if "mem_reservation" in get and get.mem_reservation != "" else None + # 2023/12/19 下午 3:08 检测如果小于6MB则报错 + if not mem_reservation is None and mem_reservation != "0": + mem_reservation_byte = dp.byte_conversion(mem_reservation) + if mem_reservation_byte < 6291456: + return public.return_message(-1, 0, _( "Memory reservation cannot be less than 6MB!")) + + network = get.network if "network" in get and get.network != "" else "bridge" + ip_address = get.ip_address if "ip_address" in get and get.ip_address != "" else None + + try: + res = self.docker_client(self._url).containers.run( + name=get.name, + image=get.image, + detach=True, + # cpuset_cpus=get.cpuset_cpus ,#指定容器使用的cpu个数 + tty=True if "tty" in get and get.tty == "1" else False, + stdin_open=True if "stdin_open" in get and get.stdin_open == "1" else False, + publish_all_ports=True if "publish_all_ports" in get and get.publish_all_ports != "0" else False, + ports=nPorts if len(nPorts) > 0 else None, + cpu_quota=int(cpu_quota) or 0, + mem_reservation=mem_reservation, # b,k,m,g + mem_limit=mem_limit, # b,k,m,g + restart_policy=restart_policy, + command=get.command if "command" in get and get.command != "" else None, + volume_driver=get.volume_driver if "volume_driver" in get and get.volume_driver != "" else None, + volumes=get.volumes if "volumes" in get and get.volumes != "" else None, + auto_remove=True if "auto_remove" in get and get.auto_remove != "0" else False, + privileged=True if "privileged" in get and get.privileged != "0" else False, + environment=dp.set_kv(get.environment), # "HOME=/value\nHOME11=value1" + labels=dp.set_kv(get.labels), # "key=value\nkey1=value1" + network=network, + ) + + except docker.errors.APIError as e: + if "invalid reference format" in str(e): + return public.return_message(-1, 0, _( + "The image name format is incorrect.such as:nginx:latest")) + if "failed to create task for container" in str(e) or "failed to create shim task" in str(e): + return public.return_message(-1, 0, _( "Container creation failed, details: {}!".format(str(e)))) + if "Minimum memory limit can not be less than memory reservation limit, see usage" in str(e): + return public.return_message(-1, 0, _( "The memory quota cannot be less than the memory reserve!")) + if "already exists in network bridge" in str(e): + return public.return_message(-1, 0, _( + "The container name or network bridge already exists. Please change the container name and try again!")) + if "No command specified" in str(e): + return public.return_message(-1, 0, _( + "There is no startup command in this image, please specify the container startup command!")) + if "permission denied" in str(e): + return public.return_message(-1, 0, _( "Permission exception! Details:{}".format(str(e)))) + if "Internal Server Error" in str(e): + return public.return_message(-1, 0, _( + "Container creation failed! Please restart the docker service at the appropriate time!")) + if "repository does not exist or may require 'docker login'" in str(e): + return public.return_message(-1, 0, _( + "Image [{}] does not exist!".format( + get.image))) + if "Minimum memory reservation allowed is 6MB" in str(e): + return public.return_message(-1, 0, _( "Memory reservation cannot be less than 6MB!")) + if "container to be able to reuse that name." in str(e): + return public.return_message(-1, 0, _( "Container name already exists!")) + if "Invalid container name" in str(e): + return public.return_message(-1, 0, _( "The container name is invalid")) + if "bind: address already in use" in str(e): + port = "" + for i in get.ports: + if ":{}:".format(get.ports[i]) in str(e): + port = get.ports[i] + get.id = get.name + self.del_container(get) + return public.return_message(-1, 0, _( "Server port {} in use! Change the other port".format(port))) + return public.return_message(-1, 0, _( 'Creation failure! {}'.format(public.get_error_info()))) + except Exception as a: + public.print_log(traceback.format_exc()) + self.del_container(get) + if "Read timed out" in str(a): + return public.return_message(-1, 0, _( + "The container creation failed and the connection to docker timed out!")) + return public.return_message(-1, 0, _( 'Container failed to run! {}'.format(str(a)))) + + if res: + # print(res.status) + # print(res.id) + # dk_config = self.docker_client(self._url).containers.get(res.id) + # print(dk_config.attrs['NetworkSettings']['Networks'][network]['IPAddress']) + # 将容器的ip改成用户指定的ip + pdata = { + "cpu_limit": str(get.cpu_quota), + "container_name": get.name + } + dp.sql('container').insert(pdata) + public.set_module_logs('docker', 'run_container', 1) + dp.write_log("Container creation [{}] successful!".format(get.name)) + + # 2024/2/26 下午 6:00 添加备注 + self.check_remark_table() + dp.sql('dk_container_remark').insert({ + "container_id": res.id, + "container_name": get.name, + "remark": public.xssencode2(get.remark), + "addtime": int(time.time()) + }) + + if not ip_address is None: + try: + self.docker_client(self._url).networks.get(network).disconnect(res.id) + self.docker_client(self._url).networks.get(network).connect(res.id, ipv4_address=ip_address) + # dk_config = self.docker_client(self._url).containers.get(res.id) + # print(dk_config.attrs['NetworkSettings']['Networks'][network]['IPAddress']) + # logs = res.logs(stdout=True, stderr=True) + # print(logs.decode()) + except docker.errors.APIError as e: + if "Invalid IPv4 address" in str(e): + return public.return_message(0, 0, _( + "Created successfully, but the IP [{}] is illegal and the IP has been automatically assigned.".format( + ip_address))) + + # 返回包含容器的id和name + return public.return_message(0, 0, { + "status": True, + "result": "Successfully created!", + "id": res.id, + "name": dp.rename(res.name), + }) + + # return public.returnMsg(True, "容器创建成功!") + return public.return_message(-1, 0, _( 'Creation failure!')) + + def upgrade_container(self, get): + """ + 更新正在运行的容器镜像(重建) + @param get: + @return: + """ + try: + if "id" not in get: + return public.return_message(-1, 0, _( "Container ID is abnormal")) + + container = self.docker_client(self._url).containers.get(get.id) + + old_container_config = self.save_container_config(container) + new_image = get.new_image if "new_image" in get and get.new_image else "latest" + if new_image is None: + return public.return_message(-1, 0, _( "The new image name cannot be empty!")) + + if "upgrade" in get and get.upgrade == "1": + get.new_image = "{}:{}".format(old_container_config["image"].split(':')[0], new_image) + + try: + if "force_pull" in get and get.force_pull == "1": + public.ExecShell("docker pull {}".format(get.new_image)) + except docker.errors.ImageNotFound as e: + return public.return_message(-1, 0, _( "The mirror does not exist!")) + except docker.errors.APIError as e: + return public.return_message(-1, 0, "The mirror does not exist!") + # except Exception as e: + # public.print_log(traceback.format_exc()) + # return public.return_message(-1, 0, e) + + + get.old_container_config = old_container_config + new_container_config = self.structure_new_container_conf(get) + if type(new_container_config) != dict: + return public.return_message(-1, 0, _( new_container_config['msg'])) + + container.stop() + container.remove() + + try: + new_container = self.docker_client(self._url).containers.create( + name=new_container_config["name"], + image=new_container_config["image"], + detach=new_container_config["detach"], + cpu_quota=new_container_config["cpu_quota"], + mem_limit=new_container_config["mem_limit"], + tty=new_container_config["tty"], + stdin_open=new_container_config["stdin_open"], + publish_all_ports=new_container_config["publish_all_ports"], + ports=new_container_config["ports"], + command=new_container_config["command"], + entrypoint=new_container_config["entrypoint"], + environment=new_container_config["environment"], + labels=new_container_config["labels"], + auto_remove=new_container_config["auto_remove"], + privileged=new_container_config["privileged"], + volumes=new_container_config["volumes"], + volume_driver=new_container_config["volume_driver"], + mem_reservation=new_container_config["mem_reservation"], + restart_policy=new_container_config["restart_policy"], + network=new_container_config["network"], + ) + except Exception as e: + public.print_log(traceback.format_exc()) + public.print_log("修改 创建新容器报错--") + + if "Read timed out" in str(e): + return public.return_message(-1, 0, _( + "Container editing failed and the connection to docker timed out.")) + # print(traceback.format_exc()) + return public.return_message(-1, 0, _( "Update failed!{}".format(str(e)))) + + new_container.start() + if not "upgrade" in get or get.upgrade != "1": + new_ip_address = get.new_ip_address if "new_ip_address" in get and get.new_ip_address else \ + old_container_config["ip_address"] + new_network = get.new_network if "new_network" in get and get.new_network else \ + old_container_config["network"] + + if new_network != "bridge": + try: + self.docker_client(self._url).networks.get(new_network).disconnect(new_container.id) + self.docker_client(self._url).networks.get(new_network).connect( + new_container.id, ipv4_address=new_ip_address + ) + + except docker.errors.APIError as e: + if ("user specified IP address is supported only when " + "connecting to networks with user configured subnets") in str(e): + self.docker_client(self._url).networks.get(new_network).connect(new_container.id) + return public.return_message(0, 0, _( + "Editing successful, [{}] has not specified a subnet, and the IP has been automatically assigned!" + .format(str(new_network)))) + + except Exception as e: + public.print_log(traceback.format_exc()) + self.docker_client(self._url).networks.get(new_network).connect(new_container.id) + print(traceback.format_exc()) + return public.return_message(0, 0, _( + "Editing successful, network [{}] setting failed, IP has been automatically assigned to you, error details: {}!" + .format(new_network, str(e)))) + + return public.return_message(0, 0, _("Update successfully!")) + except docker.errors.NotFound as e: + if "No such container" in str(e): + return public.return_message(-1, 0, _("Container does not exist!")) + return public.return_message(-1, 0, _("Update failed!{}".format(str(e)))) + except docker.errors.APIError as e: + if "No such container" in str(e): + return public.return_message(-1, 0, _("Container does not exist!")) + return public.return_message(-1, 0, _("Update failed!{}".format(str(e)))) + except Exception as a: + public.print_log(traceback.format_exc()) + if "Read timed out" in str(a): + return public.return_message(-1, 0, + _("Container editing failed and the connection to docker timed out.")) + return public.return_message(-1, 0, _("Update failed!{}".format(str(a)))) + + def save_container_config(self, container): + """ + 保存容器的配置信息 + """ + ip_address, network = None, None + if len(container.attrs['NetworkSettings']['Networks']) != 0: + Networks = container.attrs['NetworkSettings']['Networks'][list(container.attrs['NetworkSettings']['Networks'].keys())[0]] + ip_address = Networks['IPAddress'] + network = Networks['NetworkID'] + + container_config = { + "image": container.attrs['Config']['Image'], + "name": container.attrs['Name'], + "detach": True, + "cpu_quota": container.attrs['HostConfig']['CpuQuota'], + "mem_limit": container.attrs['HostConfig']['Memory'], + "tty": container.attrs['Config']['Tty'], + "stdin_open": container.attrs['Config']['OpenStdin'], + "publish_all_ports": container.attrs['HostConfig']['PublishAllPorts'], + "ports": container.attrs['NetworkSettings']['Ports'], + "command": container.attrs['Config']['Cmd'], + "entrypoint": container.attrs['Config']['Entrypoint'], + "environment": container.attrs['Config']['Env'], + "labels": container.attrs['Config']['Labels'], + "auto_remove": container.attrs['HostConfig']['AutoRemove'], + "privileged": container.attrs['HostConfig']['Privileged'], + "volumes": container.attrs['HostConfig']['Binds'], + "volume_driver": container.attrs['HostConfig']['VolumeDriver'], + "mem_reservation": container.attrs['HostConfig']['MemoryReservation'], + "restart_policy": container.attrs['HostConfig']['RestartPolicy'], + "network": network, + "ip_address": ip_address, + } + return container_config + + def structure_new_container_conf(self, get): + """ + 构造新的容器配置 + @param get: + @return: + """ + new_image = get.new_image if hasattr(get, "new_image") and get.new_image else get.old_container_config["image"] + new_name = get.new_name if hasattr(get, "new_name") and get.new_name else get.old_container_config["name"].replace("/", "") + new_cpu_quota = get.new_cpu_quota if hasattr(get, "new_cpu_quota") and get.new_cpu_quota != 0 else get.old_container_config["cpu_quota"] + if int(new_cpu_quota) != 0: + new_cpu_quota = float(new_cpu_quota) * 100000 + + if int(new_cpu_quota) / 100000 > dp.get_cpu_count(): + return public.returnMsg(False, _( "cpu quota has exceeded available cores!")) + + new_mem_limit = get.new_mem_limit if hasattr(get, "new_mem_limit") and get.new_mem_limit else get.old_container_config["mem_limit"] + new_tty = get.new_tty if hasattr(get, "new_tty") and get.new_tty else get.old_container_config["tty"] + new_stdin_open = get.new_stdin_open if hasattr(get, "new_stdin_open") and get.new_stdin_open else get.old_container_config["stdin_open"] + new_publish_all_ports = get.new_publish_all_ports if hasattr(get, "new_publish_all_ports") and get.new_publish_all_ports != '0' else get.old_container_config["publish_all_ports"] + new_ports = get.new_ports if hasattr(get, "new_ports") and get.new_ports else get.old_container_config["ports"] + new_command = get.new_command if hasattr(get, "new_command") else get.old_container_config["command"] + new_entrypoint = get.new_entrypoint if hasattr(get, "new_entrypoint") else get.old_container_config["entrypoint"] + new_environment = get.new_environment if hasattr(get, "new_environment") and get.new_environment != '' else get.old_container_config["environment"] + new_labels = get.new_labels if hasattr(get, "new_labels") and get.new_labels != '' else get.old_container_config["labels"] + new_auto_remove = True if hasattr(get, "new_auto_remove") and get.new_auto_remove != '0' else get.old_container_config["auto_remove"] + new_privileged = True if hasattr(get, "new_privileged") and get.new_privileged != '0' else get.old_container_config["privileged"] + new_volumes = get.new_volumes if hasattr(get, "new_volumes") and get.new_volumes else get.old_container_config["volumes"] + new_volume_driver = get.new_volume_driver if hasattr(get, "new_volume_driver") and get.new_volume_driver else get.old_container_config["volume_driver"] + new_mem_reservation = get.new_mem_reservation if hasattr(get, "new_mem_reservation") and get.new_mem_reservation else get.old_container_config["mem_reservation"] + new_restart_policy = get.new_restart_policy if hasattr(get, "new_restart_policy") and get.new_restart_policy else get.old_container_config["restart_policy"] + new_network = get.new_network if hasattr(get, "new_network") and get.new_network else get.old_container_config["network"] + + container_config = { + "image": new_image, + "name": new_name, + "detach": True, + "cpu_quota": int(new_cpu_quota), + "mem_limit": new_mem_limit, + "tty": new_tty, + "stdin_open": new_stdin_open, + "publish_all_ports": new_publish_all_ports, + "ports": new_ports, + "command": new_command, + "entrypoint": new_entrypoint, + "environment": dp.set_kv(new_environment) if type(new_environment) != list else new_environment, + "labels": dp.set_kv(new_labels) if type(new_labels) != dict else new_labels, + "auto_remove": new_auto_remove, + "privileged": new_privileged, + "volumes": new_volumes, + "volume_driver": new_volume_driver, + "mem_reservation": new_mem_reservation, + "restart_policy": new_restart_policy, + "network": new_network, + } + return container_config + + def commit(self, get): + """ + 保存为镜像 + :param repository 推送到的仓库 + :param tag 镜像标签 jose:v1 + :param message 提交的信息 + :param author 镜像作者 + :param changes + :param conf dict + :param path 导出路径 + :param name 导出文件名 + :param get: + :return: + """ + try: + if not hasattr(get, 'conf') or not get.conf: + get.conf = None + if get.repository == "docker.io": + get.repository = "" + + container = self.docker_client(self._url).containers.get(get.id) + container.commit( + repository=get.repository if "repository" in get else None, + tag=get.tag if "tag" in get else None, + message=get.message if "message" in get else None, + author=get.author if "author" in get else None, + # changes=get.changes if get.changes else None, + conf=get.conf + ) + dp.write_log("Submitted container [{}] as image [{}] successfully".format(container.attrs['Name'], get.tag)) + + if hasattr(get, "path") and get.path: + get.id = "{}:{}".format(get.repository, get.tag) + from btdockerModelV2 import imageModel as di + result = di.main().save(get) + if result['status']: + return public.return_message(0, 0, _("The image has been generated, and{}".format(result['msg']))) + return public.return_message(0, 0, result) + + return public.return_message(0, 0, _("submit successfully!")) + except Exception as e: + return public.return_message(-1, 0, _( "Submission Failed!{}".format(str(e)))) + + + def docker_shell(self, get): + """ + 容器执行命令 + :param get: + :return: + """ + # {"id": "b414fd6b5d5a1e36e3246dc8bb4b4518bd0adbcd62180abc0e146ccc4b8731db", "shell": "bash", "sudo_i": 1} + try: + get.validate([ + Param('id').Require().String(), + Param('shell').Require().String('in', ['bash', 'sh']), + # Param('sudo_i').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + try: + # if "id" not in get: + # return public.returnMsg(False, "The container ID is abnormal, please refresh the page and try again!") + + # shell_list = ('bash', 'sh') + # if "shell" not in get: + # return public.returnMsg(False, "Select the shell type!") + + # if get.shell not in shell_list: + # return public.returnMsg(False, "This shell is not supported-choose bash or sh!") + + cmd = 'docker container exec -it {} {}'.format(get.id, get.shell) + return public.return_message(0, 0, cmd) + except docker.errors.APIError as ex: + return public.return_message(-1, 0, _( 'Failed to get container')) + + def export(self, get): + """ + 导出容器为tar 没有导入方法,目前弃用 + :param get: + :return: + """ + from os import path as ospath + from os import makedirs as makedirs + try: + if "tar" in get.name: + file_name = '{}/{}'.format(get.path, get.name) + else: + file_name = '{}/{}.tar'.format(get.path, get.name) + if not ospath.exists(get.path): + makedirs(get.path) + public.writeFile(file_name, '') + f = open(file_name, 'wb') + container = self.docker_client(self._url).containers.get(get.id) + data = container.export() + for i in data: + f.write(i) + f.close() + return public.returnMsg(True, _( "Successfully exported to:{}".format(file_name))) + except: + return public.returnMsg(False, _( 'operation failure:' + str(public.get_error_info()))) + + def del_container(self, get): + """ + 删除指定容器 + @param get: + @return: + """ + # try: + # # 删除站点 + # a = public.M('sites').find() + # b = public.M('domain').find() + # + # # 删除数据库记录 + # c = dp.sql('dk_domain').find() + # d = dp.sql('dk_sites').find() + # except Exception as e: + # public.print_log(traceback.format_exc()) + # + # public.print_log("a| {}".format(a)) + # public.print_log("b| {}".format(b)) + # public.print_log("c| {}".format(c)) + # public.print_log("d| {}".format(d)) + + try: + get.validate([ + Param('id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + import sys + sys.path.insert(0, '/www/server/panel/class') + from btdockerModelV2.proxyModel import main + from panelSite import panelSite + try: + container = self.docker_client(self._url).containers.get(get.id) + config_path = "{}/config/name_map.json".format(public.get_panel_path()) + if not os.path.exists(config_path): + public.writeFile(config_path, json.dumps({})) + if public.readFile(config_path) == '': + public.writeFile(config_path, json.dumps({})) + config_data = json.loads(public.readFile(config_path)) + if container.name in config_data.keys(): + config_data.pop(container.name) + public.writeFile(config_path, json.dumps(config_data)) + container.remove(force=True) + dp.sql("cpu_stats").where("container_id=?", (get.id,)).delete() + dp.sql("io_stats").where("container_id=?", (get.id,)).delete() + dp.sql("mem_stats").where("container_id=?", (get.id,)).delete() + dp.sql("net_stats").where("container_id=?", (get.id,)).delete() + dp.sql("container").where("container_nam=?", (container.attrs['Name'])).delete() + dp.write_log("Delete container [{}] successfully!".format(container.attrs['Name'])) + get.container_id = get.id + # todo 此处导入注意适配 info返回已改 + info = main().get_proxy_info(get) + if info and 'name' in info and 'id' in info: + print(info['name']) + print(info['id']) + args = public.to_dict_obj({ + 'id': info['id'], + 'webname': info['name'] + + }) + panelSite().DeleteSite(args) + # domain_id = dp.sql('dk_domain').where('id=?', (info['id'],)).find() + + # 删除站点 改默认数据库 + # dp.sql('sites').where('name=?', (info['name'],)).delete() + # dp.sql('domain').where("name=?", (info['name'],)).delete() + public.M('sites').where('name=?', (info['name'],)).delete() + public.M('domain').where("name=?", (info['name'],)).delete() + + # 删除数据库记录 + dp.sql('dk_domain').where('id=?', (info['id'],)).delete() + dp.sql('dk_sites').where('container_id=?', (get.id,)).delete() + + return public.return_message(0, 0, _("Successfully deleted!")) + except Exception as e: + return public.return_message(-1, 0, _( "Delete failed! {}".format(str(e)))) + + # 设置容器状态 + def set_container_status(self, get): + """ + 设置容器状态 + @param get: + @return: + """ + try: + get.validate([ + Param('id').Require().String(), + Param('status').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + try: + container = self.docker_client(self._url).containers.get(get.id) + result = {"status": True, "msg": "Successfully set!"} + if get.status == "start": + result = self.start(get) + elif get.status == "stop": + result = self.stop(get) + elif get.status == "pause": + result = self.pause(get) + elif get.status == "unpause": + result = self.unpause(get) + elif get.status == "reload": + result = self.reload(get) + elif get.status == "kill": + container.kill() + else: + container.restart() + + try: + + if result['status']: + return public.return_message(0, 0,result['msg']) + else: + return public.return_message(-1, 0, result['msg']) + + + except: + return public.return_message(-1, 0, str(result['msg'])) + except Exception as e: + if "No such container" in str(e): + return public.return_message(-1, 0, _( "The container has been deleted!")) + if "port is already allocated" in str(e) or "address already in use" in str(e): + if "[::]" in str(e): + str_port = str(e).split("[::]:")[1].split(":")[0] + return public.return_message(-1, 0, _( "ipv6 server port [{}] is occupied!".format(str_port))) + else: + str_port = str(e).split("0.0.0.0")[1].split(":")[1].split(" ")[0] + return public.return_message(-1, 0, _( "ipv4 server port [{}] is occupied!".format(str_port))) + return public.return_message(-1, 0, _( "Setup failed! {}".format(str(e)))) + + # 停止容器 + def stop(self, get): + """ + 停止指定容器 (内部调用) + :param get: + :return: + """ + try: + get.status = "stop" + container = self.docker_client(self._url).containers.get(get.id) + container.stop() + time.sleep(1) + data = self.docker_client(self._url).containers.get(get.id) + if data.attrs['State']['Status'] != "exited": + return public.returnMsg(False, _( "Stop failing!")) + dp.write_log("Stopping container [{}] success!".format(data.attrs['Name'].replace('/', ''))) + return public.returnMsg(True, _("Stop succeeding!")) + except docker.errors.APIError as e: + if "is already paused" in str(e): + return public.returnMsg(False, _( "The container has paused.")) + if "No such container" in str(e): + return public.returnMsg(True, _( "Container stopped and deleted")) + return public.returnMsg(False, _( "Stop failing!{}".format(e))) + + def start(self, get): + """ + 启动指定容器 (内部调用) + :param get: + :return: + """ + try: + get.status = "start" + container = self.docker_client(self._url).containers.get(get.id) + container.start() + time.sleep(1) + data = self.docker_client(self._url).containers.get(get.id) + if data.attrs['State']['Status'] != "running": + return public.returnMsg(False, _( "boot failed!")) + dp.write_log("Starting container [{}] was successful!".format(data.attrs['Name'].replace('/', ''))) + return public.returnMsg(True, _( "starting success!")) + except docker.errors.APIError as e: + if "cannot start a paused container, try unpause instead" in str(e): + return self.unpause(get) + except Exception as a: + print(traceback.format_exc()) + raise Exception(a) + + def pause(self, get): + """ + 暂停此容器内的所有进程 (内部调用) + :param get: + :return: + """ + try: + get.status = "pause" + container = self.docker_client(self._url).containers.get(get.id) + container.pause() + time.sleep(1) + data = self.docker_client(self._url).containers.get(get.id) + if data.attrs['State']['Status'] != "paused": + return public.returnMsg(False, _( "Container pause failed!")) + dp.write_log("Pause container [{}] success!".format(data.attrs['Name'].replace('/', ''))) + return public.returnMsg(True, _( "Container pause successfully!")) + except docker.errors.APIError as e: + if "is already paused" in str(e): + return public.returnMsg(False, _( "The container has been suspended!")) + if "is not running" in str(e): + return public.returnMsg(False, _( "The container is not started and cannot be paused!")) + if "is not paused" in str(e): + return public.returnMsg(False, _( + "The container is not paused or has been deleted. Check if the container has the option to delete immediately after stopping!")) + return str(e) + except Exception as a: + print(traceback.format_exc()) + raise Exception(a) + + def unpause(self, get): + """ + 取消暂停该容器内的所有进程 (内部调用) + :param get: + :return: + """ + try: + get.status = "unpause" + container = self.docker_client(self._url).containers.get(get.id) + container.unpause() + time.sleep(1) + data = self.docker_client(self._url).containers.get(get.id) + if data.attrs['State']['Status'] != "running": + return public.returnMsg(False, _( "boot failed!")) + dp.write_log("Unpause container [{}] success!".format(data.attrs['Name'].replace('/', ''))) + return public.returnMsg(True, _( "The container unpaused successfully")) + except docker.errors.APIError as e: + if "is already paused" in str(e): + return public.returnMsg(False, _( "The container has paused.")) + if "is not running" in str(e): + return public.returnMsg(False, _( "The container is not started and cannot be paused!")) + if "is not paused" in str(e): + return public.returnMsg(False, _( + "The container is not paused or has been deleted. Check if the container has the option to delete immediately after stopping!")) + return str(e) + except Exception as a: + print(traceback.format_exc()) + raise Exception(a) + + def reload(self, get): + """ + 再次从服务器加载此对象并使用新数据更新 attrs (内部调用) + :param get: + :return: + """ + get.status = "reload" + container = self.docker_client(self._url).containers.get(get.id) + container.reload() + time.sleep(1) + data = self.docker_client(self._url).containers.get(get.id) + if data.attrs['State']['Status'] != "running": + return public.returnMsg(False, _( "boot failed!")) + dp.write_log("Reloading container [{}] succeeded!".format(data.attrs['Name'].replace('/', ''))) + return public.returnMsg(True, _( "The container was reloaded successfully!")) + + def restart(self, get): + """ + 重新启动这个容器。类似于 docker restart 命令 + :param get: + :return: + """ + try: + get.status = "restart" + container = self.docker_client(self._url).containers.get(get.id) + container.restart() + time.sleep(1) + data = self.docker_client(self._url).containers.get(get.id) + if data.attrs['State']['Status'] != "running": + return public.returnMsg(False, _( "boot failed!")) + dp.write_log("Restart container [{}] successfully!".format(data.attrs['Name'].replace('/', ''))) + return public.returnMsg(True, _( "Container restarts successfully!")) + except docker.errors.APIError as e: + if "container is marked for removal and cannot be started" in str(e): + return public.returnMsg(False, _( + "The container has been stopped and deleted because containers have the option to automatically delete when stopped")) + if "is already paused" in str(e): + return public.returnMsg(False, _( "The container has paused")) + return str(e) + + def get_container_ip(self, container_networks): + """ + 获取容器IP + @param container_networks: + @return: + """ + data = list() + for network in container_networks: + data.append(container_networks[network]['IPAddress']) + return data + + def get_container_path(self, detail): + """ + 获取容器路径 + @param detail: + @return: + """ + try: + import os + if not "GraphDriver" in detail: + return False + if "Data" not in detail["GraphDriver"]: + return False + if "MergedDir" not in detail["GraphDriver"]["Data"]: + return False + path = detail["GraphDriver"]["Data"]["MergedDir"] + if not os.path.exists(path): + return "" + return path + except: + return False + + def get_container_info(self, get): + """ + 获取容器信息 + @param get: + @return: + """ + try: + if "id" not in get or get.id == "": + return public.return_message(-1, 0, _( "The container ID is empty")) + + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + sk_container_info = sk_container.get_container_inspect(get.id) + + info_path = "/var/lib/docker/containers/{}/container_info.json".format(get.id) + public.writeFile(info_path, json.dumps(sk_container_info, indent=3)) + sk_container_info['container_info'] = info_path + return public.return_message(0, 0, sk_container_info) + except Exception as e: + if "No such container" in str(e): + return public.return_message(-1, 0, _( "Container does not exist!")) + return public.return_message(-1, 0, _( "Failed to get container information!{}".format(str(e)))) + + def struct_container_ports(self, ports): + """ + 构造容器ports + @param ports: + @return: + """ + data = dict() + for port in ports: + key = str(port["PrivatePort"]) + "/" + port["Type"] + if key not in data.keys(): + data[str(port["PrivatePort"]) + "/" + port["Type"]] = [{ + "HostIp": port["IP"], + "HostPort": str(port["PublicPort"]) + }] if "IP" in port else None + else: + data[str(port["PrivatePort"]) + "/" + port["Type"]].append({ + "HostIp": port["IP"], + "HostPort": str(port["PublicPort"]) + }) + return data + + def struct_container_list(self, container): + ''' + @name 构造容器列表 + @author wzz <2024/3/13 下午 5:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + tmp = { + "id": container["Id"], + "name": dp.rename(container['Names'][0].replace("/", "")), + "status": container["State"], + "image": container["Image"], + "created_time": container["Created"], + "ip": self.get_container_ip(container["NetworkSettings"]['Networks']), + "ports": self.struct_container_ports(container["Ports"]), + } + + return tmp + + # 2024/4/11 下午2:46 获取 merged 目录 + def get_container_merged(self, get): + ''' + @name 获取容器 merged 目录 + @author wzz <2024/4/11 下午2:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + get.id = get.get("id", "") + if get.id == "": + return public.return_message(-1, 0, _( "The container ID is empty")) + return public.return_message(0, 0, { + "path": public.ExecShell("docker inspect -f \"{{json .GraphDriver.Data.MergedDir}}\" " + get.id)[ + 0].strip().strip('"')}) + + except Exception as e: + return public.return_message(0, 0, {"path": ""}) + + # 2024/4/11 下午3:44 获取其他容器列表的数据 + def get_other_container_data(self, get): + ''' + @name 获取其他容器列表的数据 + @author wzz <2024/4/11 下午3:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + from btdockerModelV2.dockerSock import container + + sk_container = container.dockerContainer() + container_list = sk_container.get_container() + + data = [] + # 获取前先检测数据库是否存在 + self.check_remark_table() + dk_container_remark = dp.sql("dk_container_remark").select() + self.check_table_dk_backup() + dk_backup = dp.sql("dk_backup").select() + for sk_c in container_list: + try: + remark = [i['remark'] for i in dk_container_remark if i['container_id'] == sk_c["Id"]][0] + except Exception as e: + remark = "" + + + # 计算备份数量 + backup_count = 0 + for i in dk_backup: + if i['container_id'] == sk_c["Id"]: + backup_count += 1 + + data.append({ + "id": sk_c["Id"], + "name": dp.rename(sk_c['Names'][0].replace("/", "")), + "backup_count": backup_count, + "remark": remark, + }) + + return public.return_message(0, 0, data) + except Exception as e: + public.print_log(traceback.format_exc()) + return public.return_message(0, 0, []) + + # 获取容器列表 + def get_list(self, get): + """ + 获取所有容器列表 1 + :param get + :return: + """ + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + sk_container_list = sk_container.get_container() + + data = { + "online_cpus": dp.get_cpu_count(), + "mem_total": dp.get_mem_info(), + "container_list": [], + } + + container_detail = list() + grouped_by_status = dict() + for sk_c in sk_container_list: + struct_container = self.struct_container_list(sk_c) + status = struct_container['status'] + grouped_by_status.setdefault(status, []).append(struct_container) + container_detail.append(struct_container) + + data['grouped_by_status'] = grouped_by_status + data['container_list'] = sorted(container_detail, key=lambda x: x['created_time'], reverse=True) + return public.return_message(0, 0, data) + + def _get_list(self, get): + """ + 获取所有容器列表 1 + :param get + :return: + """ + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + sk_container_list = sk_container.get_container() + + data = { + "online_cpus": dp.get_cpu_count(), + "mem_total": dp.get_mem_info(), + "container_list": [], + } + + container_detail = list() + grouped_by_status = dict() + for sk_c in sk_container_list: + struct_container = self.struct_container_list(sk_c) + status = struct_container['status'] + grouped_by_status.setdefault(status, []).append(struct_container) + container_detail.append(struct_container) + + data['grouped_by_status'] = grouped_by_status + data['container_list'] = sorted(container_detail, key=lambda x: x['created_time'], reverse=True) + return data + + # 获取容器的attr + def get_container_attr(self, containers): + c_list = containers.list(all=True) + return [container_info.attrs for container_info in c_list] + + # 获取容器日志 + def get_logs(self, get): + """ + 获取指定容器日志 + :param get: + :return: + """ + # {"id": "4b270b6b6dd3cb50b4bc5c5c51ceade67de0754d39ad7a312d06e3fabf4d89b1","time_search": [1701273600, 1701829064]} + + res = { + "logs": "", + 'split_status': False, + 'split_type': 'day', + 'split_size': 1000, + 'split_hour': 2, + 'split_minute': 0, + 'save': '180' + } + + try: + container = self.docker_client(self._url).containers.get(get.id) + if hasattr(get, 'time_search') and get.time_search != '': + if not os.path.exists(container.attrs['LogPath']): + return public.return_message(-1, 0, "No container logging") + # return public.return_message(0, 0, None) + + time_search = json.loads(str(get.time_search)) + since = int(time_search[0]) + until = int(time_search[1]) + r_logs = container.logs(since=since, until=until).decode() + + else: + if not os.path.exists(container.attrs['LogPath']): + return public.return_message(-1, 0, "No container logging") + # return public.return_message(0, 0, None) + + size = os.stat(container.attrs['LogPath']).st_size + if size < 1048576: + r_logs = container.logs().decode() + else: + tail = int(get.tail) if "tail" in get else 3000 + r_logs = container.logs(tail=tail).decode() + + if hasattr(get, 'search') and get.search != '': + if get.search: + r_logs = r_logs.split("\n") + r_logs = [i for i in r_logs if get.search in i] + r_logs = "\n".join(r_logs) + + res['logs'] = r_logs + res['id'] = get.id + res['name'] = dp.rename(container.attrs['Name'][1:]) + res['logs_path'] = container.attrs['LogPath'] + res['size'] = os.stat(container.attrs['LogPath']).st_size + + if public.M('sqlite_master').db('docker_log_split').where('type=? AND name=?', + ('table', 'docker_log_split')).count(): + res['split_status'] = True if public.M('docker_log_split').where('pid=?', (get.id,)).count() else False + data = public.M('docker_log_split').where('pid=?', (get.id,)).select() + if data: + res['split_type'] = data[0]['split_type'] + res['split_size'] = data[0]['split_size'] + res['split_hour'] = data[0]['split_hour'] + res['split_minute'] = data[0]['split_minute'] + res['save'] = data[0]['save'] + else: + res['split_type'] = 'day' + res['split_size'] = 1000 + res['split_hour'] = 2 + res['split_minute'] = 0 + res['save'] = '180' + + return public.return_message(0, 0, res) + + except Exception: + return public.return_message(0, 0, res) + + def get_logs_all(self, get): + """ + 获取所有容器的日志 + @param get: + @return: + """ + try: + client = self.docker_client(self._url) + if not client: + return public.return_message(-1, 0, _( 'docker connection failed')) + containers = client.containers + clist = [i.attrs for i in containers.list(all=True)] + clist = [{'id': i['Id'], 'name': dp.rename(i['Name'][1:]), 'log_path': i['LogPath']} for i in clist] + for i in clist: + if os.path.exists(i['log_path']): + i['size'] = os.stat(i['log_path']).st_size + else: + i['size'] = 0 + return public.return_message(0, 0, clist) + except Exception as e: + return public.return_message(-1, 0, e) + + def docker_split(self, get): + """ + 设置容器日志切割 + @param get: + @return: + """ + # {"pid": "3f225d2b41bcadb1b4bb73dc7521a35d1933cb1e1caa8ac61bd53cf718b9910c", "type": "del"} + try: + get.validate([ + Param('pid').Require().String(), + Param('type').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + try: + client = self.docker_client(self._url) + if not client: + return public.return_message(-1, 0, _( 'docker connection failed')) + containers = client.containers + clist = [i.attrs for i in containers.list(all=True)] + name = [dp.rename(i['Name'][1:]) for i in clist if i['Id'] == get.pid] + if name: + name = name[0] + else: + name = '' + if not hasattr(get, 'type'): + return public.return_message(-1, 0, _( 'parameter error,Pass: type')) + if not public.M('sqlite_master').db('docker_log_split').where('type=? AND name=?', + ('table', 'docker_log_split')).count(): + public.M('docker_log_split').execute('''CREATE TABLE IF NOT EXISTS docker_log_split ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name text default '', + pid text default '', + log_path text default '', + split_type text default '', + split_size INTEGER default 0, + split_hour INTEGER default 2, + split_minute INTEGER default 0, + save INTEGER default 180)''', ()) + if get.type == 'add': + if "log_path" not in get or not get.log_path: + return public.return_message(-1, 0, _( + 'Container log directory does not exist, log cut cannot be set!')) + + if not (hasattr(get, 'pid') and hasattr(get, 'log_path') and + hasattr(get, 'split_type') and hasattr(get, 'split_size') and + hasattr(get, 'split_minute') and + hasattr(get, 'split_hour') and hasattr(get, 'save')): + return public.return_message(-1, 0, _( 'parameter error')) + data = { + 'name': name, + 'pid': get.pid, + 'log_path': get.log_path, + 'split_type': get.split_type, + 'split_size': get.split_size, + 'split_hour': get.split_hour, + 'split_minute': get.split_minute, + 'save': get.save + } + if public.M('docker_log_split').where('pid=?', (get.pid,)).count(): + id = public.M('docker_log_split').where('pid=?', (get.pid,)).select() + public.M('docker_log_split').delete(id[0]['id']) + public.M('docker_log_split').insert(data) + return public.return_message(0, 0, _("Opened successfully!")) + elif get.type == 'del': + id = public.M('docker_log_split').where('pid=?', (get.pid,)).getField('id') + public.M('docker_log_split').where('id=?', (id,)).delete() + return public.return_message(0, 0, _("Closed successfully!")) + except: + return public.return_message(-1, 0, traceback.format_exc()) + + def clear_log(self, get): + """ + 清空日志 + @param get: + @return: + """ + if not hasattr(get, 'log_path'): + return public.return_message(-1, 0, _( 'parameter error')) + if not os.path.exists(get.log_path): + return public.return_message(-1, 0, _( 'The log file does not exist')) + public.writeFile(get.log_path, '') + return public.return_message(0, 0, _("Log cleaning was successful!")) + + # 清理无用已停止未使用的容器 + def prune(self, get): + """ + :param get: + :return: + """ + try: + type = get.get("type/d", 0) + if type == 0: + res = self.docker_client(self._url).containers.prune() + if not res['ContainersDeleted']: + return public.return_message(-1, 0, _( "No useless containers!")) + dp.write_log("Delete useless containers successfully!") + return public.return_message(0, 0, _( "successfully deleted!")) + else: + import docker + client = docker.from_env() + containers = client.containers.list(all=True) + for container in containers: + container.remove(force=True) + dp.write_log("Delete useless containers successfully!") + return public.return_message(0, 0, _( "successfully deleted!")) + except Exception as e: + if "operation not permitted" in str(e): + return public.return_message(-1, 0, _( "Please turn off enterprise tamper protection before trying again!")) + return public.return_message(-1, 0, _( "failed to delete! {}".format(e))) + + + def update_restart_policy(self, get): + """ + 更新容器重启策略 + @param get: + @return: + """ + try: + get.validate([ + Param('id').Require().String(), + Param('restart_policy').Require(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + try: + # if "restart_policy" not in get: + # return public.returnMsg(False, "Parameter error, please pass in restart policy restart_policy!") + + container = self.docker_client(self._url).containers.get(get.id) + # 修复偶尔 go反序列化 报错 + if isinstance(get.restart_policy, str): + import json + restart_policy = get.restart_policy.replace("'", "\"") + restart_policy = json.loads(restart_policy) + else: + restart_policy = get.restart_policy + + container.update(restart_policy=restart_policy) + # container.update(restart_policy=get.restart_policy) + # container.update(restart_policy= json.dumps(get.restart_policy)) + dp.write_log("Update container [{}] Restart policy successful!".format(container.attrs['Name'])) + return public.return_message(0, 0, _("Update successfully!")) + # except docker.errors.APIError as e: + except Exception as e: + public.print_log(traceback.format_exc()) + return public.return_message(-1, 0, _( "Update failed! {}".format(e))) + + ''' + @name 重命名指定容器 + @author wzz <2023/12/1 下午 3:13> + @param 参数名<数据类型> 参数描述 + @return 数据类型 + ''' + + def rename_container(self, get): + """ + 重命名指定容器 + @param get: + @return: + """ + try: + get.validate([ + Param('id').Require().String(), + Param('name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + try: + # 2023/12/6 上午 10:54 容器未启动时,不允许重命名 + container = self.docker_client(self._url).containers.get(get.id) + if container.attrs['State']['Status'] != "running": + return public.return_message(-1, 0, _( "The container is not started and cannot be renamed!")) + config_path = "{}/config/name_map.json".format(public.get_panel_path()) + if not os.path.exists(config_path): + public.writeFile(config_path, json.dumps({})) + + if public.readFile(config_path) == '': + public.writeFile(config_path, json.dumps({})) + + name_map = json.loads(public.readFile(config_path)) + name_str = 'q18q' + public.GetRandomString(10).lower() + name_map[name_str] = get.name + get.name = name_str + public.writeFile(config_path, json.dumps(name_map)) + + container.rename(get.name) + dp.write_log("Renaming container [{}] succeeded!".format(get.name)) + return public.return_message(0, 0, _("Rename successfully!")) + except docker.errors.APIError as e: + return public.return_message(-1, 0, _( "Renaming failed! {}".format(e))) + + # 2024/2/23 上午 9:58 设置容器列表置顶 + def set_container_to_top(self, get): + """ + 设置容器列表置顶 + @param get: + @return: + """ + # {"type": "add", "container_name": "dk_wordpress-wordpress-1"} + + set_type = get.type if "type" in get else "" + container_name = get.container_name if "container_name" in get else None + + if set_type not in ['add', 'del']: return public.returnMsg(False, _( 'The type only supports add/del')) + if container_name is None: return public.returnMsg(False, _( 'Please select a container')) + + _conf_path = "{}/class_v2/btdockerModelV2/config/container_top.json".format(public.get_panel_path()) + if os.path.exists(_conf_path): + container_top_conf = json.loads(public.readFile(_conf_path)) + else: + container_top_conf = [] + + if set_type == "add": + container_top_conf = [i for i in container_top_conf if i != container_name] + container_top_conf.insert(0, container_name) + public.writeFile(_conf_path, json.dumps(container_top_conf)) + return public.return_message(0, 0, _('Set top successfully!')) + elif set_type == "del": + container_top_conf.remove(container_name) + public.writeFile(_conf_path, json.dumps(container_top_conf)) + return public.return_message(0, 0, _('Unpinned successfully!')) + + # 2024/2/23 上午 9:58 获取容器列表置顶 + def _get_container_to_top(self): + """ + 获取容器列表置顶 + @return: + """ + _conf_path = "{}/class_v2/btdockerModelV2/config/container_top.json".format(public.get_panel_path()) + if os.path.exists(_conf_path): + container_top_conf = json.loads(public.readFile(_conf_path)) + else: + container_top_conf = [] + + return container_top_conf + # 2024/3/13 下午 5:46 检查并创建备注的表 + def check_remark_table(self): + ''' + @name 检查并创建备注的表 + @author wzz <2024/2/26 下午 5:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if not dp.sql('sqlite_master').where('type=? AND name=?', + ('table', 'dk_container_remark')).count(): + dp.sql('dk_container_remark').execute( + "CREATE TABLE `dk_container_remark` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `container_id` TEXT, `container_name` TEXT, `remark` TEXT, `addtime` TEXT)", + () + ) + + + + + # 2024/2/26 下午 6:11 修改备注 + def set_container_remark(self, get): + """ + 设置容器备注 + @param get: + @return: + """ + + try: + get.validate([ + Param('id').Require().String(), + Param('remark').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + # if not hasattr(get, 'remark'): + # return public.returnMsg(False, 'Parameter error, Please pass in: remark!') + # if not hasattr(get, 'id'): + # return public.returnMsg(False, 'Parameter error, Please pass in: id!') + + container_id = get.id + container_remark = public.xssencode2(get.remark) + + if not dp.sql("dk_container_remark").where("container_id=?", (container_id,)).count(): + dp.sql("dk_container_remark").insert({ + "container_id": container_id, + "remark": container_remark, + }) + else: + dp.sql("dk_container_remark").where("container_id=?", (container_id,)).setField("remark", container_remark) + + return public.return_message(0, 0, _("Setup successful!")) + + # 2024/2/27 上午 11:03 通过cgroup获取所有容器的cpu和内存使用情况 + def get_all_stats(self, get): + """ + # 通过cgroup获取所有容器的cpu和内存使用情况 + @param get: + @return: + """ + if not hasattr(get, 'ws_callback'): + return public.returnMsg(False, _( 'Parameter error, Please pass in: ws_callback!')) + if not hasattr(get, '_ws'): + return public.returnMsg(False, _( 'Parameter error, Please pass in: _ws!')) + + from system import system + syst = system() + data = dict() + data["cpu_info"] = syst.GetCpuInfo() + data["mem_info"] = syst.GetMemInfo() + + get._ws.send(public.getJson( + { + "data": data, + "ws_callback": get.ws_callback, + "msg": "Start getting the cpu and memory usage of all containers!", + "status": True, + "end": False, + })) + + try: + # 获取所有容器列表 + container_list = self._get_list(get)["container_list"] + + while True: + docker_stats_result = public.ExecShell( + "docker stats --no-stream --format " + "'{{.ID}},{{.Name}},{{.CPUPerc}},{{.MemUsage}},{{.MemPerc}},{{.NetIO}},{{.BlockIO}},{{.PIDs}};'" + )[0] + + data["cpu_info"] = syst.GetCpuInfo() + data["mem_info"] = syst.GetMemInfo() + + if not docker_stats_result: + get._ws.send(public.getJson( + { + "data": {}, + "ws_callback": get.ws_callback, + "msg": "No running container resource information has been obtained yet!", + "status": True, + "end": True, + })) + return + + container_stats_data = list() + + for i in docker_stats_result.split(";"): + if not i: continue + tmp = i.strip().split(",") + if len(tmp) == 0: continue + if len(tmp) == 1 and not tmp[0]: continue + + # 获取容器使用的image + id = image = create_time = "" + + for cl in container_list: + if cl["name"] in dp.rename(tmp[1]): + id = cl["id"] + image = cl["image"] + create_time = cl["created_time"] + break + + container_stats_data.append({ + "id": id, + "name": dp.rename(tmp[1]), + "image": image, + "create_time": create_time, + "cpu_usage": tmp[2].strip("%"), + "mem_percent": tmp[4].strip("%"), + "mem_usage": { + "mem_usage": dp.byte_conversion(tmp[3].split("/")[0]), + "mem_limit": dp.byte_conversion(tmp[3].split("/")[1]), + }, + "net_io": { + "net_in": dp.byte_conversion(tmp[5].split("/")[0]), + "net_out": dp.byte_conversion(tmp[5].split("/")[1]), + }, + "block_io": { + "block_in": dp.byte_conversion(tmp[6].split("/")[0]), + "block_out": dp.byte_conversion(tmp[6].split("/")[1]), + }, + "pids": tmp[7], + }) + + data["container_stats_data"] = container_stats_data + data["container_count"] = len(container_stats_data) + end = False + msg = "Obtaining the cpu and memory usage of all containers was successful!" + if len(container_stats_data) == 0: + end = True + msg = "No running container resource information has been obtained yet!" + + get._ws.send(public.getJson( + { + "data": data, + "ws_callback": get.ws_callback, + "msg": msg, + "status": True, + "end": end, + })) + + time.sleep(0.1) + + except: + print(traceback.format_exc()) + get._ws.send(public.getJson( + { + "data": {}, + "ws_callback": get.ws_callback, + "msg": "Failed to get cpu and memory usage of all containers!", + "status": True, + "end": True, + })) + return + + def check_table_dk_backup(self): + ''' + @name 检查并创建表 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_backup')).count(): + dp.sql('dk_backup').execute( + "CREATE TABLE `dk_backup` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` INTEGER, `name` TEXT, `container_id` TEXT, `container_name` TEXT, `filename` TEXT, `size` INTEGER, `addtime` TEXT, `ps` STRING DEFAULT 'none', `cron_id` INTEGER DEFAULT 0 )", + () + ) diff --git a/class_v2/btdockerModelV2/dk_public.py b/class_v2/btdockerModelV2/dk_public.py new file mode 100644 index 00000000..73748bca --- /dev/null +++ b/class_v2/btdockerModelV2/dk_public.py @@ -0,0 +1,293 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +from datetime import datetime, timezone, timedelta +import json +import os + +import db +# from class_v2.dk_db import db +import public +from public.validate import Param + +# db_path = "/www/server/panel/data/db/docker.db" +db_path = "/www/server/panel/data/docker.db" + + +def check_db(): + if not os.path.exists(db_path) or os.path.getsize(db_path) == 0: + execstr = "wget -O {} {}/install/src/docker_en.db".format(db_path, public.get_url()) + public.ExecShell(execstr) + + +def sql(table): + check_db() + with db.Sql() as sql: + sql.dbfile(db_path) + return sql.table(table) + + +# 实例化docker +def docker_client(url="unix:///var/run/docker.sock"): + """ + 目前仅支持本地服务器 + :param url: unix:///var/run/docker.sock + :return: + """ + try: + import docker + except: + public.ExecShell("btpip install docker") + import docker + + try: + client = docker.DockerClient(base_url=url) + if client: + return client + return False + except: + return False + + +def docker_client_low(url="unix:///var/run/docker.sock"): + """ + docker 低级接口 + :param url: + :return: + """ + try: + import docker + except: + public.ExecShell("btpip install docker") + import docker + + try: + client = docker.APIClient(base_url=url) + return client + except docker.errors.DockerException: + return False + + +# 取CPU类型 +def get_cpu_count(): + import re + with open('/proc/cpuinfo', 'r') as f: + cpuinfo = f.read() + rep = r"processor\s*:" + tmp = re.findall(rep, cpuinfo) + if not tmp: + return 0 + return len(tmp) + + +def set_kv(kv_str): + """ + 将键值字符串转为对象 + :param data: + :return: + """ + if not kv_str: + return None + res = kv_str.split('\n') + data = dict() + for i in res: + i = i.strip() + if not i: + continue + if i.find('=') == -1: + continue + if i.find('=') > 1: + k, v = i.split('=', 1) + + data[k] = v + continue + + k, v = i.split('=') + data[k] = v + return data + + +def get_mem_info(): + # 取内存信息 + import psutil + mem = psutil.virtual_memory() + memInfo = int(mem.total) + return memInfo + + +def byte_conversion(data): + data = data.lower() # 将数据转换为小写字母形式 + if "gib" in data: + return float(data.replace('gib', '')) * 1024 * 1024 * 1024 + elif "mib" in data: + return float(data.replace('mib', '')) * 1024 * 1024 + elif "kib" in data: + return float(data.replace('kib', '')) * 1024 + elif "gb" in data: + return float(data.replace('gb', '')) * 1024 * 1024 * 1024 + elif "mb" in data: + return float(data.replace('mb', '')) * 1024 * 1024 + elif "kb" in data: + return float(data.replace('kb', '')) * 1024 + elif "b" in data: + return float(data.replace('b', '')) + else: + return False + + +def bytes_to_human_readable(bytes_num): + """ + 将字节数转换为人类可读的格式(KB、MB、GB等) + :param bytes_num: 字节数 + :return: 格式化后的字符串 xxx mb + """ + suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + index = 0 + while bytes_num >= 1024 and index < len(suffixes) - 1: + bytes_num /= 1024.0 + index += 1 + return "{:.2f} {}".format(bytes_num, suffixes[index]) + + +def log_docker(generator, task_name): + __log_path = '/tmp/dockertmp.log' + while True: + try: + output = generator.__next__() + try: + output = json.loads(output) + if 'status' in output: + output_str = "{}\n".format(output['status']) + public.writeFile(__log_path, output_str, 'a+') + except: + public.writeFile(__log_path, public.get_error_info(), 'a+') + if 'stream' in output: + output_str = output['stream'] + public.writeFile(__log_path, output_str, 'a+') + except StopIteration: + public.writeFile(__log_path, f'{task_name} complete.', 'a+') + break + except ValueError: + public.writeFile(__log_path, f'Error parsing output from {task_name}: {output}', 'a+') + except Exception as e: + public.writeFile(__log_path, f'Error from {task_name}: {e}', 'a+') + break + + +def docker_conf(): + """ + 解析docker配置文件 + KEY=VAULE + KEY1=VALUE1 + :return: + """ + docker_conf = public.readFile("{}/data/docker.conf".format(public.get_panel_path())) + if not docker_conf: + return {"SAVE": 30} + data = dict() + for i in docker_conf.split("\n"): + if not i: + continue + k, v = i.split("=") + if k == "SAVE": + v = int(v) + data[k] = v + return data + + +def get_process_id(pname, cmd_line): + import psutil + pids = psutil.pids() + for pid in pids: + try: + p = psutil.Process(pid) + if p.name() == pname and cmd_line in p.cmdline(): + return pid + except: + pass + return False + + +def write_log(log_data): + public.WriteLog("Docker module", log_data) + + +def check_socket(port): + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + location = ("127.0.0.1", int(port)) + result_of_check = s.connect_ex(location) + s.close() + if result_of_check == 0: + return True + else: + return False + + +def download_file(url, filename): + ''' + 下载方法 + @param url: + @param filename: + @return: + ''' + return public.ExecShell(f"wget -O {filename} {url} --no-check-certificate") + +def convert_timezone_str_to_iso8601(timestamp_str): + # 解析时间字符串为 datetime 对象 + dt = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S %z %Z') + + # 转换时区为 UTC + dt_utc = dt.astimezone(timezone.utc) + + # 格式化为 ISO 8601 格式 + iso8601_str = dt_utc.strftime('%Y-%m-%dT%H:%M:%S.%fZ') + + return iso8601_str + +def timestamp_to_string(timestamp): + # 将时间戳转换为 datetime 对象 + dt_object = datetime.fromtimestamp(timestamp) + # 格式化为字符串 + formatted_string = dt_object.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + return formatted_string + +def rename(name: str): + """ + 重命名容器名,兼容中文命名 + @param name: + @return: + """ + try: + if name[:4] != 'q18q': + return name + config_path = "{}/config/name_map.json".format(public.get_panel_path()) + config_data = json.loads(public.readFile(config_path)) + name_l = name.split('_') + if name_l[0] in config_data.keys(): + name_l[0] = config_data[name_l[0]] + return '_'.join(name_l) + except: + return name + +def convert_timezone_str_to_timestamp(timestamp_str: str): + import re + # 解析时间字符串为 2024-05-16T06:18:23.915547557-04:00 时间戳 + timestamp_str = re.sub(r'\.\d+', '', timestamp_str) + dt = datetime.strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S%z") + # 转换时区为 UTC + dt_utc = dt.astimezone(timezone.utc) + + # 转换为时间戳 + timestamp = dt_utc.timestamp() + + return timestamp diff --git a/class_v2/btdockerModelV2/dkgroupModel_.py b/class_v2/btdockerModelV2/dkgroupModel_.py new file mode 100644 index 00000000..a3c4b908 --- /dev/null +++ b/class_v2/btdockerModelV2/dkgroupModel_.py @@ -0,0 +1,459 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: zhengweibiao +# ------------------------------------------------------------------- + +# ------------------------------ +# 容器项目编排 +# ------------------------------ +import os, sys, re, json, shutil, psutil, time +import datetime +import public +from btdockerModelV2.containerModel import main as docker +import subprocess + + + + + +class main(): + + next_id = 1 # 将 next_id 设为类变量,而不是实例变量 + def __init__(self): + # self.next_id = 1 + pass + + def load_project_data(self): + json_file = "/www/server/panel/class_v2/btdockerModelV2/docker_project_groups.json" + try: + with open(json_file, 'r') as f: + return json.load(f) + except FileNotFoundError: + data = [] + with open(json_file, 'w') as f: + json.dump(data, f) + + return data + except Exception as e: + return None + + def write_to_json(self, data): + json_file = "/www/server/panel/class_v2/btdockerModelV2/docker_project_groups.json" + try: + with open(json_file, 'w') as f: + json.dump(data, f) + return True + except Exception as e: + print("写入失败!{}".format(e)) + return False + + def get_project_groups(self, get): + + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + + # 获取项目列表 + project_list = docker().get_list(get) + + # 更新每个项目组的状态 + for group in data: + # 检查项目排序是否为空 + if not group['order']: + group['status'] = 0 # 如果项目排序为空,状态为停止 + continue + + all_running = True # 假设所有的项目都在运行 + for project in group['projects']: + for p in project_list['container_list']: + if p['name'] == project['project_name']: + if p['status']!="running": # 如果项目没有运行 + all_running = False + break + if not all_running: + break + + if all_running: + group['status'] = 1 # 如果所有的项目都在运行,状态为启动 + else: + group['status'] = 0 + + return public.returnMsg(True, data) + + def get_group_data(self, get): + try: + data = self.load_project_data() + if data is None: + return None, public.returnMsg(False, "获取配置文件失败!") + + project_list = docker().get_list(get) + for group in data: + if group['id'] == int(get.id): + for project in group['projects']: + + print(project) + # print(project_list['container_list']) + for p in project_list['container_list']: + if p['name'] == project['project_name']: + print(3333) + print(p) + project['status'] = p['status'] + return group, None + return None, public.ReturnMsg(False, "项目不存在!") + + except Exception as e: + return None, public.returnMsg(False, "获取失败!" + str(e)) + + def get_project_details(self, get): + print(33333) + group, error_msg = self.get_group_data(get) + if error_msg: + return error_msg + + group['projects'].sort( + key=lambda x: group['order'].index(x['project_name'])) + return public.returnMsg(True, group['projects']) + + def get_project_group_details(self, get): + group, error_msg = self.get_group_data(get) + if error_msg: + return error_msg + return public.returnMsg(True, group) + + def add_project_group(self, get): + + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + + # 检查是否已存在 + for group in data: + if group['group_name'] == get.group_name: + return public.returnMsg(False, "项目已存在!") + + # 添加新的项目 + new_group = { + "id": self.next_id, # 使用 next_id 作为新的 id + "group_name": get.group_name, + # "status": 0, + "interval": 30, + "projects": [], + "order": [], + } + data.append(new_group) + + # 更新 next_id + + main.next_id += 1 + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + + return public.returnMsg(True, "项目添加成功!") + + except Exception as e: + + return public.returnMsg(False, "添加失败!" + str(e)) + + def edit_project_order(self, get): + + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + # 找到指定的项目组 + for group in data: + if group['id']==int(get.id): + new_order=get.order.split(',') + if sorted(new_order) != sorted(group['order']): + return public.returnMsg(False, "无效的容器顺序!") + group['order']=new_order + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + return public.returnMsg(True,"容器顺序修改成功!") + return public.returnMsg(False,"项目不存在!") + except Exception as e: + return public.returnMsg(False,"修改失败:"+str(e)) + + def edit_group_interval(self, get): + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + + + # 找到指定的项目 + for group in data: + if group['id'] == int(get.id): + # 检查新的项目顺序是否有效 + group['interval'] = get.interval + break + else: + return public.returnMsg(False, "项目不存在!") + + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + + return public.returnMsg(True, "项目时间间隔修改成功!") + except Exception as e: + return public.returnMsg(False, "修改失败!" + str(e)) + + def start_projects_in_order(self, get): + + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + project_list=docker().get_list(get)['container_list'] + + for group in data: + if group['id']==int(get.id): + # 如果pid存在并且进程仍在运行,那么就不允许用户再次启动项目 + if 'start_pid' in group and self.is_process_running(group['start_pid']): + return public.returnMsg(False,"正在依次启动容器中!") + + project_order=group['order'] + + running_projects=[project for project in project_order if self.is_project_running(project,project_list)] + + if running_projects and not get.get("force_stop",False): + return public.returnMsg(False,"以下容器正在运行:{}。是否允许先强制停止再启动?您也可以选择自己手动停止运行中的容器!".format(",".join(running_projects))) + + with open('/dev/null','w') as devnull: + process=subprocess.Popen(['btpython','/www/server/panel/script/set_docker_project_groups.py','--id',str(group['id']),"--action","start"],stdout=devnull,stderr=devnull) + + + + pid=process.pid + group['start_pid']=pid + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + + return public.returnMsg(True, "开始依次启动容器!") + except Exception as e: + return public.returnMsg(False, "启动失败!"+str(e)) + + def is_process_running(self,pid): + try: + os.kill(pid,0) + except OSError: + return False + else: + return True + + + def is_project_running(self,project_name,project_list): + for project in project_list: + if project['name']==project_name and project['status']=="running": + return True + return False + + def stop_projects_in_order(self, get): + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + for group in data: + if group['id']==int(get.id): + # 如果pid存在并且进程仍在运行,那么就不允许用户再次停stop_pid止项目 + if 'stop_pid' in group and self.is_process_running(group['stop_pid']): + return public.returnMsg(False,"正在依次停止容器中!") + with open("/dev/null","w") as devnull: + process=subprocess.Popen(['btpython','/www/server/panel/script/set_docker_project_groups.py','--id',str(group['id']),"--action","stop"],stdout=devnull,stderr=devnull) + + pid=process.pid + group['stop_pid']=pid + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + return public.returnMsg(True,"容器开始按顺序停止") + + except Exception as e: + return public.returnMsg(False, "停止失败!" + str(e)) + + + def start_group(self, args_id): + + try: + get = public.dict_obj() + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + # 找到指定的项目 + for group in data: + if group['id'] == int(args_id): + # 获取项目排序 + project_order = group['order'] + + # 停止所有项目 + for project_name in project_order: + container_id = None + for project in group['projects']: + if project['project_name'] == project_name: + container_id = project['project_id'] + break + if container_id: + # print() + # docker().set_container_status(public.dict_obj({ + # "id": container_id, + # "status": "stop" + # })) + print(33333333333333) + get.status = "stop" + get.id = container_id + docker().set_container_status(get) + # time.sleep(30) + # 依次启动项目 + for project_name in project_order: + container_id = None + for project in group['projects']: + if project['project_name'] == project_name: + container_id = project['project_id'] + break + if container_id: + # print() + # docker().set_container_status(public.dict_obj({ + # "id": container_id, + # "status": "stop" + # })) + get.status = "start" + get.id = container_id + start_result=docker().set_container_status(get) + + if not start_result['status']: + return start_result # 如果启动失败,立即返回错误信息 + + # 暂停指定的时间间隔 + time.sleep(int(group['interval'])) + if not self.is_process_running(group['pid']): + # 删除pid + del group['pid'] + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + except Exception as e: + print("启动失败!" + str(e)) + + + def stop_group(self, args_id): + + + try: + get = public.dict_obj() + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + # 找到指定的项目 + for group in data: + if group['id'] == int(args_id): + # 获取项目排序 + project_order = group['order'] + + # 停止所有项目 + for project_name in project_order: + container_id = None + for project in group['projects']: + if project['project_name'] == project_name: + container_id = project['project_id'] + break + if container_id: + get.status = "stop" + get.id = container_id + docker().set_container_status(get) + except Exception as e: + print("启动失败!" + str(e)) + + def modify_group_status(self, get): + group_ids=[int(id) for id in get.id.split(",")] + + for group_id in group_ids: + get.id=group_id + if get.status=="1": + return self.start_projects_in_order(get) + elif get.status=="0": + return self.stop_projects_in_order(get) + else: + return public.returnMsg(False,"无效的状态!") + + + + def add_project_to_group(self, get): + + try: + + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + # 检查项目是否已被其他项目组添加 + for group in data: + for project in group['projects']: + if project['project_name']==get.project_name: + return public.returnMsg(False,"容器 {} 已经被项目组 {} 添加了!".format(get.project_name,group['group_name'])) + for group in data: + if group['id']==int(get.id): + new_project={ + "project_id":get.project_id, + "project_name":get.project_name, + + + } + group['projects'].append(new_project) + group['order'].append(get.project_name) + break + + + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + + return public.returnMsg(True, "容器添加成功!") + except Exception as e: + return public.returnMsg(False, "添加失败!" + str(e)) + + def remove_project_from_group(self, get): + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + + # 将 get.project_name 分割成一个列表 + project_names=get.project_name.split(",") + + # 找到指定的项目 + for group in data: + if group['id']==int(get.id): + # 删除指定的项目组 + group['projects']=[project for project in group['projects'] if project['project_name'] not in project_names] + # 同时更新order列表,移除已删除的项目名称 + group['order'] = [project_name for project_name in group['order'] if project_name not in project_names] + break + else: + return public.returnMsg(False,"项目不存在!") + + # 将更新后的数据写回文件 + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + + return public.returnMsg(True, "容器删除成功!") + except Exception as e: + return public.returnMsg(False, "删除失败!" + str(e)) + + def delete_project_group(self, get): + + try: + data = self.load_project_data() + if data is None: + return public.returnMsg(False, "获取配置文件失败") + + # 将 get.id 分割成一个列表 + group_ids=[int(id) for id in get.id.split(",")] + # 找到并删除指定的项目 + data=[group for group in data if group['id'] not in group_ids] + # 将更新后的数据写回文件 + if not self.write_to_json(data): + return public.returnMsg(False, "写入失败!") + + return public.returnMsg(True, "项目删除成功!") + except Exception as e: + return public.returnMsg(False, "删除失败!" + str(e)) diff --git a/class_v2/btdockerModelV2/dockerBase.py b/class_v2/btdockerModelV2/dockerBase.py new file mode 100644 index 00000000..11260e67 --- /dev/null +++ b/class_v2/btdockerModelV2/dockerBase.py @@ -0,0 +1,117 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import public, os +from btdockerModelV2 import dk_public as dp + + +class dockerBase(object): + + def __init__(self): + # self._db_path = "/www/server/panel/data/db/docker.db" + self._db_path = "/www/server/panel/data/docker.db" + self._backup_log = '/tmp/backup.log' + self._log_path = '/tmp/dockertmp.log' + self._rCmd_log = '/tmp/dockerRun.log' + self._url = "unix:///var/run/docker.sock" + self.compose_path = "{}/data/compose".format(public.get_panel_path()) + self.aes_key = "btdockerModel_QWERAS" + self.moinitor_lock = "/tmp/bt_docker_monitor.lock" + + def get_ws_log(self, get): + """ + 获取日志,websocket + @param get: + @return: + """ + if not hasattr(get, "_ws"): + return True + + import time + sum = 0 + + with open(get._log_path, "r") as file: + position = file.tell() + get._ws.send("{}\r\n".format(get.wsLogTitle)) + + while True: + current_position = file.tell() + line = file.readline() + if current_position > position: + file.seek(position) + new_content = file.read(current_position - position) + if "nohup" not in new_content: + for i in new_content.split('\n'): + if i == "": continue + get._ws.send(i.strip("\n") + "\r\n") + + position = current_position + + if "bt_successful" in line: + get._ws.send("bt_successful\r\n") + del get._ws + break + elif "bt_failed" in line: + get._ws.send("bt_failed\r\n") + del get._ws + break + + if sum > 0: + sum = 0 + else: + sum += 1 + + if sum >= 6000: + get._ws.send("\r\nNo response for more than 10 minutes!\r\n") + break + + time.sleep(0.1) + + return True + + # 命令行创建 拉取容器 + def run_cmd(self, get): + """ + 命令行创建运行容器(docker run / docker pull),需要做危险命令校验,存在危险命令则不执行 + @param get: + @return: + """ + import re + if not hasattr(get, 'cmd'): + return public.return_message(-1, 0, _("Please pass in cmd")) + + if "docker run" not in get.cmd and "docker pull" not in get.cmd: + return public.return_message(-1, 0, _('Only docker run or docker pull commands can be executed')) + + danger_cmd = ['rm', 'rmi', 'kill', 'stop', 'pause', 'unpause', 'restart', 'update', 'exec', 'init', + 'shutdown', 'reboot', 'chmod', 'chown', 'dd', 'fdisk', 'killall', 'mkfs', 'mkswap', 'mount', + 'swapoff', 'swapon', 'umount', 'userdel', 'usermod', 'passwd', 'groupadd', 'groupdel', + 'groupmod', 'chpasswd', 'chage', 'usermod', 'useradd', 'userdel', 'pkill'] + + danger_symbol = ['&', '&&', '||', '|', ';'] + + for d in danger_cmd: + if get.cmd.startswith(d) or re.search(r'\s{}\s'.format(d), get.cmd): + return public.return_message(-1, 0, _( 'Dangerous command exists: [{}], execution is not allowed!'.format(d))) + + for d in danger_symbol: + if d in get.cmd: + return public.return_message(-1, 0, _( 'Dangerous symbol exists: [{}], execution is not allowed!'.format(d))) + + os.system("echo -n > {}".format(self._rCmd_log)) + os.system("nohup {} >> {} 2>&1 && echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &".format( + get.cmd, + self._rCmd_log, + self._rCmd_log, + self._rCmd_log, + )) + return public.return_message(0, 0, _("The command has been executed!")) diff --git a/class_v2/btdockerModelV2/dockerSock/__init__.py b/class_v2/btdockerModelV2/dockerSock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/class_v2/btdockerModelV2/dockerSock/container.py b/class_v2/btdockerModelV2/dockerSock/container.py new file mode 100644 index 00000000..bd78f054 --- /dev/null +++ b/class_v2/btdockerModelV2/dockerSock/container.py @@ -0,0 +1,48 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +# docker模型sock 封装库 容器库 +# ------------------------------------------------------------------- +import json + +import public +from btdockerModelV2.dockerSock.sockBase import base + + +class dockerContainer(base): + def __init__(self): + super(dockerContainer, self).__init__() + + # 2024/3/13 上午 11:20 获取所有容器列表 + def get_container(self): + ''' + @name 获取所有容器列表 + @author wzz <2024/3/13 上午 10:54> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/containers/json?all=1".format(self._sock, self.get_api_version()))[0]) + + except Exception as e: + print(public.get_error_info()) + return [] + + # 2024/3/28 下午 11:37 获取指定容器的inspect + def get_container_inspect(self, container_id: str): + ''' + @name 获取指定容器的inspect + @param container_id: 容器id + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/containers/{}/json" + .format(self._sock, self.get_api_version(), container_id))[0]) + except Exception as e: + print(public.get_error_info()) + return [] diff --git a/class_v2/btdockerModelV2/dockerSock/image.py b/class_v2/btdockerModelV2/dockerSock/image.py new file mode 100644 index 00000000..8215e872 --- /dev/null +++ b/class_v2/btdockerModelV2/dockerSock/image.py @@ -0,0 +1,80 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +# docker模型sock 封装库 镜像库 +# ------------------------------------------------------------------- +import json + +import public +from btdockerModelV2.dockerSock.sockBase import base + + +class dockerImage(base): + def __init__(self): + super(dockerImage, self).__init__() + + # 2024/3/13 上午 11:20 获取所有镜像列表 + def get_images(self): + ''' + @name 获取所有镜像列表 + @author wzz <2024/3/13 上午 10:54> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/json?all=1" + .format(self._sock, self.get_api_version()))[0]) + except Exception as e: + print(public.get_error_info()) + return [] + + # 2023/12/13 上午 11:08 镜像搜索 + def search(self, name): + ''' + @name 镜像搜索 + @author wzz <2023/12/13 下午 3:41> + @param 参数名<数据类型> 参数描述 + @return 数据类型 + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/search?term={}" + .format(self._sock, self.get_api_version(), name))[0],) + except Exception as e: + # if os.path.exists('data/debug.pl'): + # print(public.get_error_info()) + # public.print_log(public.get_error_info()) + return [] + + # 2024/4/1 下午 2:47 image load + def load_image(self, path): + ''' + @name 加载镜像 + @param path 镜像名称 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} -X POST http:/{}/images/load -H \"Content-Type: application/x-tar\" --data-binary @{}" + .format(self._sock, self.get_api_version(), path))[0]) + except Exception as e: + print(public.get_error_info()) + return False + + # 2024/4/16 上午11:39 获取指定image的inspect信息 + def inspect(self, image): + ''' + @name 获取指定image的inspect信息 + @param image 镜像名称 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/{}/json" + .format(self._sock, self.get_api_version(), image))[0]) + except Exception as e: + print(public.get_error_info()) + return {} + diff --git a/class_v2/btdockerModelV2/dockerSock/sockBase.py b/class_v2/btdockerModelV2/dockerSock/sockBase.py new file mode 100644 index 00000000..13c73dee --- /dev/null +++ b/class_v2/btdockerModelV2/dockerSock/sockBase.py @@ -0,0 +1,19 @@ +class dockerSock(object): + def __init__(self): + self._sock = "/var/run/docker.sock" + self._url = "unix://{}".format(self._sock) + self._api_version = "/127.0.0.1" + + def get_sock(self): + return self._sock + + def get_url(self): + return self._url + + def get_api_version(self): + return self._api_version + + +class base(dockerSock): + def __init__(self): + super(base, self).__init__() diff --git a/class_v2/btdockerModelV2/dockerSock/volume.py b/class_v2/btdockerModelV2/dockerSock/volume.py new file mode 100644 index 00000000..43f16dac --- /dev/null +++ b/class_v2/btdockerModelV2/dockerSock/volume.py @@ -0,0 +1,34 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +# docker模型sock 封装库 存储库 +# ------------------------------------------------------------------- +import json + +import public +from btdockerModelV2.dockerSock.sockBase import base + + +class dockerVolume(base): + def __init__(self): + super(dockerVolume, self).__init__() + + # 2024/3/13 上午 11:20 获取所有存储卷列表 + def get_volumes(self): + ''' + @name 获取所有存储卷列表 + @author wzz <2024/3/13 上午 10:54> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/volumes" + .format(self._sock, self.get_api_version()))[0]) + except Exception as e: + print(public.get_error_info()) + return [] diff --git a/class_v2/btdockerModelV2/host.py b/class_v2/btdockerModelV2/host.py new file mode 100644 index 00000000..3f22b457 --- /dev/null +++ b/class_v2/btdockerModelV2/host.py @@ -0,0 +1,60 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: zouhw +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import public +import dk_public as dp + +class main: + + # 获取docker主机列表 + def get_list(self,args=None): + info = dp.sql("hosts").select() + for i in info: + if dp.docker_client(i['url']): + i['status'] = True + else: + i['status'] = False + return info + + # 添加docker主机 + def add(self,args): + """ + :param url 连接主机的url + :param remark 主机备注 + :return: + """ + import time + host_lists = self.get_list() + for h in host_lists: + if h['url'] == args.url: + return public.returnMsg(False,"The host already exists!") + # 测试连接 + if not dp.docker_client(args.url): + return public.returnMsg(False,"Failed to connect to the server, please check if docker is started!") + pdata = { + "url": args.url, + "remark": public.xsssec(args.remark), + "time": int(time.time()) + } + dp.write_log("Add host [{}] successful!".format(args.url)) + dp.sql('hosts').insert(pdata) + return public.returnMsg(True,"Add docker host successfully!") + + def delete(self,args): + """ + :param id 连接主机id + :return: + """ + data = dp.sql('hosts').where('id=?',args(args.id,)).find() + dp.sql('hosts').delete(id=args.id) + dp.write_log("Delete host [{}] successful!".format(data['url'])) + return public.returnMsg(True,"Delete host successfully!") \ No newline at end of file diff --git a/class_v2/btdockerModelV2/imageModel.py b/class_v2/btdockerModelV2/imageModel.py new file mode 100644 index 00000000..e36d803a --- /dev/null +++ b/class_v2/btdockerModelV2/imageModel.py @@ -0,0 +1,733 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import os +import json +import traceback + +import docker.errors +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +import gettext +_ = gettext.gettext + +class main(dockerBase): + + def docker_client(self, url): + return dp.docker_client(url) + + # 导出 + def save(self, get): + """ + :param path 要镜像tar要存放的路径 + :param name 包名 + :param id 镜像 + :param + :param get: + :return: + """ + + # 校验参数 + try: + get.validate([ + Param('path').Require().SafePath(), + Param('name').Require().String(), + Param('id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + # if "name" not in get or get.name == "": + # return public.returnMsg(False, "Image name cannot be empty") + # if "path" not in get or get.path == "": + # return public.returnMsg(False, "Mirror path cannot be empty") + # if "id" not in get or get.id == "": + # return public.returnMsg(False, "Image ID cannot be empty") + + if "/" in get.name: + return public.return_message(-1, 0, _("The image name cannot contain /")) + + if "tar" in get.name: + filename = '{}/{}'.format(get.path, get.name) + else: + filename = '{}/{}.tar'.format(get.path, get.name) + + if not os.path.exists(get.path): os.makedirs(get.path) + + public.writeFile(filename, "") + with open(filename, 'wb') as f: + image = self.docker_client(self._url).images.get(get.id) + print(image) + for chunk in image.save(named=True): + f.write(chunk) + dp.write_log("Image [{}] exported to [{}] successfully".format(get.id, filename)) + return public.return_message(0, 0, "Successfully saved to:{}".format(filename)) + + except docker.errors.APIError as e: + if "empty export - not implemented" in str(e): + return public.return_message(-1, 0, "Cannot export image!") + return public.get_error_info() + except Exception as e: + if "Read timed out" in str(e): + return public.return_message(-1, 0, + "Exporting the image failed and the connection to docker timed out. Please try restarting docker and try again!") + return public.return_message(-1, 0, "Failed to export image!
                                    {}".format(e)) + + # 导入 + def load(self, get): + """ + :param path: 需要导入的镜像路径具体到文件名 + :param get: + :return: + """ + + # 校验参数 + try: + get.validate([ + Param('path').Require().SafePath(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + if "path" not in get and get.path == "": + return public.return_message(-1, 0, "Please enter the image path!") + + # 2023/12/20 下午 4:12 判断如果path后缀不为.tar则返回错误 + if not get.path.endswith(".tar"): + return public.return_message(-1, 0, "Failed to import the image. The file extension must be.tar!") + + from btdockerModelV2.dockerSock import image + sk_image = image.dockerImage() + sk_image.load_image(get.path) + + dp.write_log("Image [{}] imported successfully!".format(get.path)) + return public.return_message(0, 0, "Image import was successful!{}".format(get.path)) + except Exception as e: + if "Read timed out" in str(e): + return public.return_message(-1, 0, + "Exporting the image failed and the connection to docker timed out. Please try restarting docker and try again!") + if "no such file or directory" in str(e): + return public.return_message(-1, 0, + "The image import failed and the temporary directory of the container failed to be created. Please check whether the protection software has an interception record!") + return public.return_message(-1, 0, "Failed to import image!
                                    {}".format(e)) + + # 列出所有镜像 + def image_list(self, get): + """ + :param url + :param get: + :return: + """ + try: + from btdockerModelV2.dockerSock import image + sk_image = image.dockerImage() + sk_images_list = sk_image.get_images() + + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + container_list = sk_container.get_container() + # if not container_list: + # return public.return_message(0, 0, data) + + + data = list() + # public.print_log("data000 : {}".format(data)) + # public.print_log("sk_images_镜像列表 sk_images_list: {}".format(sk_images_list)) + for image in sk_images_list: + # public.print_log("image if111111: {}".format(image)) + # {'Containers': -1, 'Created': 1717026901, + # 'Id': 'sha256:4f67c83422ec747235357c04556616234e66fc3fa39cb4f40b2d4441ddd8f100', + # 'Labels': {'maintainer': 'NGINX Docker Maintainers '}, 'ParentId': '', + # 'RepoDigests': ['nginx@sha256:0f04e4f646a3f14bf31d8bc8d885b6c951fdcf42589d06845f64d18aec6a3c4d'], + # 'RepoTags': ['nginx:latest'], 'SharedSize': -1, 'Size': 187667860} + if image is None: + continue + + if image['RepoTags'] is not None and len(image['RepoTags']) != 0: + # public.print_log("data2 if111111: {}".format(data)) + for tag in image['RepoTags']: + tmp = { + "id": image['Id'], + "tags": tag, + "name": tag, + "digest": image['RepoDigests'][0].split("@")[1] if image['RepoDigests'] else "", + "time": image['Created'] if type(image['Created']) == int else None, + "size": image['Size'], + "created_at": image['Created'], + "used": 0, + "containers": [], + } + # public.print_log("tmp tmp tmp: {}".format(tmp)) + # {'id': 'sha256:4f67c83422ec747235357c04556616234e66fc3fa39cb4f40b2d4441ddd8f100', + # 'tags': 'nginx:latest', 'name': 'nginx:latest', + # 'digest': 'sha256:0f04e4f646a3f14bf31d8bc8d885b6c951fdcf42589d06845f64d18aec6a3c4d', + # 'time': 1717026901, 'size': 187667860, 'created_at': 1717026901, 'used': 0, 'containers': []} + # public.print_log("container_list if: {}".format(container_list)) + self.structure_images_list(container_list, tmp) + # public.print_log("data jhshs哈666666 if: {}".format(data)) + data.append(tmp) + # public.print_log("data2 if: {}".format(data)) + else: + # public.print_log("data2 if: {}".format(data)) + tmp = { + "id": image['Id'], + "tags": "", + "name": "", + "digest": image['RepoDigests'][0].split("@")[1] if image['RepoDigests'] else "", + "time": image['Created'] if type(image['Created']) == int else None, + "size": image['Size'], + "created_at": image['Created'], + "used": 0, + "containers": [], + } + + self.structure_images_list(container_list, tmp) + # public.print_log("data2333 if: {}".format(data)) + data.append(tmp) + # public.print_log("data2 else : {}".format(data)) + + + # public.print_log("data2kjefa : {}".format(type(data))) + return public.return_message(0, 0, data) + except Exception as ex: + import traceback + # public.print_log("尺码个| info: {}".format(ex)) + public.print_log(traceback.format_exc()) + return public.return_message(0, 0, data) + + def structure_images_list(self, container_list, image_info): + ''' + @name + @author wzz <2024/5/22 下午5:53> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + for container in container_list: + if image_info['id'] in container['ImageID']: + image_info['used'] = 1 + image_info['containers'].append({ + "container_id": container['Id'], + "container_name": dp.rename(container['Names'][0].replace("/", "")), + }) + except: + pass + + + def get_image_attr(self, images): + image = images.list() + return [i.attrs for i in image] + + def get_logs(self, get): + # 校验参数 + try: + get.validate([ + Param('logs_file').Require().SafePath(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import files + logs_file = get.logs_file + return public.return_message(0, 0, files.files().GetLastLine(logs_file, 20)) + + # 构建镜像 + def build(self, get): + """ + :param path dockerfile dir + :param pull 如果引用的镜像有更新自动拉取 + :param tag 标签 jose:v1 + :param data 在线编辑配置 + :param get: + :return: + """ + + # # 校验参数 + # try: + # get.validate([ + # Param('path').Require().SafePath(), + # ], [ + # public.validate.trim_filter(), + # ]) + # except Exception as ex: + # public.print_log("error info: {}".format(ex)) + # return public.return_message(-1, 0, str(ex)) + + public.writeFile(self._log_path, "Start building the image!") + if not hasattr(get, "pull"): + get.pull = False + + min_time = None + if hasattr(get, "data") and get.data: + min_time = public.format_date("%Y%m%d%H%M") + get.path = "/tmp/{}/Dockerfile".format(min_time) + os.makedirs("/tmp/{}".format(min_time), exist_ok=True) + public.writeFile(get.path, get.data) + + if not os.path.exists(get.path): + return public.return_message(-1, 0, "Please enter the correct DockerFile path!") + + try: + # 2024/1/18 下午 12:05 取get.path的目录 + get.path = os.path.dirname(get.path) + image_obj, generator = self.docker_client(self._url).images.build( + path=get.path, + pull=True if get.pull == "1" else False, + tag=get.tag, + forcerm=True + ) + + if min_time is not None: + public.ExecShell("rm -rf {}".format(get.path)) + + dp.log_docker(generator, "Docker Build tasks!") + dp.write_log("Build image [{}] successful!".format(get.tag)) + return public.return_message(0, 0, "Build image successfully!") + except docker.errors.BuildError as e: + if "TLS handshake timeout" in str(e): + return public.return_message(-1, 0, "Build failed, connection timed out") + return public.return_message(-1, 0, "Build failed! {}".format(e)) + except docker.errors.APIError as e: + if "Cannot locate specified Dockerfile" in str(e): + return public.return_message(-1, 0, "Build failed!The specified Dockerfile was not found") + return public.return_message(-1, 0, "Build failed!{}".format(e)) + except Exception as e: + return public.return_message(-1, 0, "Build failed!{}".format(e)) + + # 删除镜像 + def remove(self, get): + """ + :param url + :param id 镜像id + :param name 镜像tag + :force 0/1 强制删除镜像 + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('force').Require().Integer(), + Param('name').Require().String(), + Param('id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + from btdockerModelV2.dockerSock import image + sk_image = image.dockerImage() + image_inspect = sk_image.inspect(get.name) + if not image_inspect: + self.docker_client(self._url).images.remove(get.id) + else: + self.docker_client(self._url).images.remove(get.name) + + dp.write_log("Deletion of image【{}】successful!".format(get.name)) + return public.return_message(0, 0, "Mirror deleted successfully!") + + except docker.errors.ImageNotFound as e: + return public.return_message(-1, 0, "The delete failed and the image may not exist!") + + except docker.errors.APIError as e: + if "image is referenced in multiple repositories" in str(e): + return public.return_message(-1, 0, + "The image ID is used in more than one image, force the image to be deleted!") + if ("using its referenced image" in str(e) or + "image is being used by stopped container" in str(e) or + "image is being used by running container" in str(e)): + return public.return_message(-1, 0, + "The image is in use. Please delete the container before deleting the image!") + + return public.return_message(-1, 0, "Failed to delete image!
                                    {}".format(e)) + except Exception as e: + if "Read timed out" in str(e): + return public.return_message(-1, 0, + "Failed to delete image,The connection to docker timed out, please restart and try again!") + return public.return_message(-1, 0, "Failed to delete image!
                                    {}".format(e)) + + # 拉取指定仓库镜像 + def pull_from_some_registry(self, get): + """ + :param name 仓库名11 + :param url + :param image + :param get: + :return: + """ + if not hasattr(get, "_ws"): + return True + + from btdockerModelV2 import registryModel as dr + + try: + if get.name == "Docker public repository": + login = dr.main().login(self._url, "docker.io", None, None)['status'] + if not login: + get._ws.send( + "bt_failed, Login to the repository [docker.io] failed, please try to log in to this repository again!\r\n") + return login + + r_info = { + "url": "docker.io", + "username": None, + "password": None, + "namespace": "library" + } + else: + r_info = dr.main().registry_info(get.name) + r_info['username'] = public.aes_decrypt(r_info['username'], self.aes_key) + r_info['password'] = public.aes_decrypt(r_info['password'], self.aes_key) + login = dr.main().login(self._url, r_info['url'], r_info['username'], r_info['password'])['status'] + if not login: + get._ws.send("bt_failed, {}\r\n".format(login['msg'])) + return login + except Exception as e: + get._ws.send( + "bt_failed, Login to repository [{}] failed, please try to log in to this repository again!\r\n".format( + get.name)) + return public.returnMsg(False, + "bt_failed, Login to repository [{}] failed, please try to log in to this repository again!".format( + get.name)) + + get.username = r_info['username'] + get.password = r_info['password'] + get.registry = r_info['url'] + get.namespace = r_info['namespace'] + + # public.print_log('准备拉取镜像 123--') + + return self.pull(get) + + # 推送镜像到指定仓库 + def push(self, get): + """ + :param id 镜像ID + :param url 连接docker的url + :param tag 标签 镜像名+版本号v1 + :param name 仓库名 + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('tag').Require().String(), + Param('name').Require().String(), + Param('id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if "/" in get.tag: + return public.return_message(-1, 0, "The pushed image cannot contain [/], please use the following " + "format: image:v1 (image name: version)") + if ":" not in get.tag: + get.tag = "{}:latest".format(get.tag) + + public.writeFile(self._log_path, "Start pushing the image!\n") + + from btdockerModelV2 import registryModel as dr + r_info = dr.main().registry_info(get.name) + r_info['username'] = public.aes_decrypt(r_info['username'], self.aes_key) + r_info['password'] = public.aes_decrypt(r_info['password'], self.aes_key) + + if get.name == "docker official" and r_info['url'] == "docker.io": + public.writeFile(self._log_path, "The image cannot be pushed to the Docker public repository!\n") + return public.return_message(-1, 0, "Unable to push to Docker public repository!") + + try: + login = dr.main().login(self._url, r_info['url'], r_info['username'], r_info['password'])['status'] + if not login: + return public.return_message(-1, 0, "Repository [{}] Login failed!".format(r_info['url'])) + + auth_conf = { + "username": r_info['username'], + "password": r_info['password'], + "registry": r_info['url'] + } + # repository namespace/image + + repository = r_info['url'] + image = "{}/{}/{}".format(repository, r_info['namespace'], get.tag) + + self.tag(self._url, get.id, image) + ret = self.docker_client(self._url).images.push( + repository=image.split(":")[0], + tag=image.split(":")[1], + auth_config=auth_conf, + stream=True + ) + + dp.log_docker(ret, "Image push task") + # 删除自动打标签的镜像 + get.name = image + self.remove(get) + + except docker.errors.APIError as e: + if "invalid reference format" in str(e): + return public.return_message(-1, 0, "Push failed, image label error, please enter such as: v1.0.1") + if "denied: requested access to the resource is denied" in str(e): + return public.return_message(-1, 0, "Push failed, do not have permission to push to this repository!") + return public.return_message(-1, 0, "Push failure!{}".format(e)) + + dp.write_log("Image [{}] pushed successfully!".format(image)) + return public.return_message(0, 0, "Push successfully, mirror:{}".format(image)) + + def tag(self, url, image_id, tag): + """ + 为镜像打标签 + :param repository 仓库namespace/images + :param image_id: 镜像ID + :param tag: 镜像标签jose:v1 + :return: + """ + image = tag.split(":")[0] + tag_ver = tag.split(":")[1] + self.docker_client(url).images.get(image_id).tag( + repository=image, + tag=tag_ver + ) + return public.returnMsg(True, "Successfully set!") + + def pull(self, get): + """ + :param image + :param url + :param registry + :param username 拉取私有镜像时填写 1 + :param password 拉取私有镜像时填写 + :param get: + :return: + """ + get._ws.send("Pulling the image, please wait...\r\n") + + try: + get._ws.send("Pulling the image, please wait...\r\n") + import docker.errors + import time + time.sleep(0.1) + get._ws.send("Pull or search for images...\r\n") + try: + get.image = '{}:latest'.format(get.image) if ':' not in get.image else get.image + auth_data = { + "username": get.username, + "password": get.password, + "registry": get.registry if get.registry else None + } + auth_conf = auth_data if get.username else None + + if not hasattr(get, "tag"): get.tag = get.image.split(":")[-1] + + if get.registry != "docker.io": + get.image = "{}/{}/{}".format(get.registry, get.namespace, get.image) + + ret = dp.docker_client_low(self._url).pull( + repository=get.image.split(":")[0], + auth_config=auth_conf, + tag=get.tag, + stream=True + ) + + if not ret: + get._ws.send("bt_failed, pull failed!\r\n") + return + + while True: + try: + output = next(ret) + output = json.loads(output) + if 'status' in output: + output_str = output['status'] + get._ws.send(output_str + "\r\n") + time.sleep(0.1) + except StopIteration: + get._ws.send("bt_successful, Image pull [{}] successful\r\n".format(get.image)) + return public.returnMsg(True, "Image pulled successfully!") + except ValueError: + get._ws.send("bt_failed, Failed to pull image!\r\n") + return public.returnMsg(False, "Failed to pull image!") + + except docker.errors.ImageNotFound as e: + if "pull access denied for" in str(e): + get._ws.send( + "bt_failed, pull failed,The image does not exist, or the image may be a private image. You need to enter your dockerhub account password!\r\n") + return + get._ws.send("bt_failed, pull failed!{}\r\n".format(e)) + return + + except docker.errors.NotFound as e: + if "not found: manifest unknown" in str(e): + get._ws.send("bt_failed, pull failed,There is no such image in the repository!\r\n") + return + get._ws.send("bt_failed, pull failed!{}\r\n".format(e)) + return + + except docker.errors.APIError as e: + if "invalid tag format" in str(e): + get._ws.send("bt_failed, pull failed, The image format is wrong, such as: nginx:v 1!\r\n") + return + get._ws.send("bt_failed, pull failed!{}\r\n".format(e)) + return + + except Exception as e: + # public.print_log("拉取镜像 -- {}".format(e)) + public.print_log(traceback.format_exc()) + + # 拉取镜像 + def pull_high_api(self, get): + """ + :param image + :param url + :param registry + :param username 拉取私有镜像时填写 + :param password 拉取私有镜像时填写 + :param get: + :return: + """ + import docker.errors + try: + if ':' not in get.image: + get.image = '{}:latest'.format(get.image) + auth_data = { + "username": get.username, + "password": get.password, + "registry": get.registry if get.registry else None + } + + auth_conf = auth_data if get.username else None + + if get.registry != "docker.io": + get.image = "{}/{}/{}".format(get.registry, get.namespace, get.image) + + ret = self.docker_client(get.url).images.pull(repository=get.image, auth_config=auth_conf) + if ret: + return public.returnMsg(True, 'The image was pulled successfully.') + else: + return public.returnMsg(False, 'There may not be this mirror image.') + + except docker.errors.ImageNotFound as e: + if "pull access denied for" in str(e): + return public.returnMsg(False, + "Failed to pull the image, this is a private image, please enter the account password!") + return public.returnMsg(False, "Pull image failure

                                    Reason: {}".format(e)) + + def image_for_host(self, get): + """ + 获取镜像大小和获取镜像数量 + :param get: + :return: + """ + res = self.image_list(get) + if not res['status']: return res + + num = len(res['msg']['images_list']) + size = 0 + + for i in res['msg']['images_list']: + size += i['size'] + return public.returnMsg(True, {'num': num, 'size': size}) + + def prune(self, get): + """ + 删除无用的镜像 + :param get: + :return: + """ + dang_ling = True if "filters" in get and get.filters == "0" else False + + try: + res = self.docker_client(self._url).images.prune(filters={'dangling': dang_ling}) + + if not res['ImagesDeleted']: + return public.return_message(0, 0, "No useless images!") + + dp.write_log("Delete useless image successfully!") + return public.return_message(0, 0, "successfully delete!") + + except docker.errors.APIError as e: + return public.return_message(-1, 0, "failed to delete!{}".format(e)) + except Exception as e: + if error.find("Read timed out") != -1: + return public.return_message(-1, 0, + "Deletion of useless images failed and the connection to docker timed" + " out. Please try restarting the docker service and try again!") + return public.return_message(-1, 0, "failed to delete!{}".format(e)) + + # 2023/12/13 上午 11:08 镜像搜索 todo 关键字查询调用ws接口 暂时没查到 + def search(self, get): + ''' + @name 镜像搜索,docker hub官方镜像列表 + 从docker hub官方镜像列表获取最新排序镜像 + 数据库在/www/server/panel/class_v2/btdockerModelV2/config/docker_hub_repos.db + 每隔1个月从官网同步一次 + 脚本在/www/server/panel/class_v2/btdockerModelV2/script/syncreposdb.py + @author wzz <2023/12/13 下午 3:41> + @param 参数名<数据类型> 参数描述 + @return 数据类型 + ''' + try: + get.name = get.get("name/s", "") + if get.name == "": + # 2024/3/20 上午 10:10 如果get.name是空,则返回docker_hub_repos.db中results表的所有镜像 + import db, os + + sql = db.Sql() + sql.dbfile('{}/class_v2/btdockerModelV2/config/docker_hub_repos.db'.format(public.get_panel_path())) + # 2024/3/20 上午 10:24 按照star_count排序 + results = sql.table('results').field('name,description,star_count,is_official').order( + 'star_count desc').select() + + if not results: + return public.return_message(0, 0, []) + + return public.return_message(0, 0, results) + + from btdockerModelV2.dockerSock import image + sk_image = image.dockerImage() + + return public.return_message(0, 0, sk_image.search(get.name)) + except Exception as e: + # if os.path.exists('data/debug.pl'): + # print(public.get_error_info()) + public.print_log(public.get_error_info()) + return public.return_message(-1, 0, []) + + # 拉取容器日志 + def get_cmd_log(self, get): + """ + 拉取容器日志 + @param get: + @return: + """ + get.wsLogTitle = "Start executing the command, please wait..." + get._log_path = self._rCmd_log + return self.get_ws_log(get) diff --git a/class_v2/btdockerModelV2/monitorModel.py b/class_v2/btdockerModelV2/monitorModel.py new file mode 100644 index 00000000..010b755a --- /dev/null +++ b/class_v2/btdockerModelV2/monitorModel.py @@ -0,0 +1,129 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: zouhw +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import sys +import threading +import time + +sys.path.insert(0, "/www/server/panel/class_v2/") +sys.path.insert(1, "/www/server/panel/") +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2 import containerModel as dc +from btdockerModelV2 import statusModel as ds +from btdockerModelV2 import imageModel as di +from public.validate import Param + +class main: + __save_date = None + __day_sec = 86400 + + def __init__(self, save_date): + if not save_date: + self.__save_date = 30 + else: + self.__save_date = save_date + + def docker_client(self, url): + return dp.docker_client(url) + + def get_all_host_stats(self, fun): + """ + 获取所有主机信息并获取该主机下的容器状态 + :param fun: 需要调用的方法,用于获取并记录容器状态 + :return: + """ + hosts = dp.sql('hosts').select() + for i in hosts: + t = threading.Thread(target=fun, args=(i,)) + t.setDaemon(True) + t.start() + + # 获取所有docker容器的状态信息 + def container_status_for_all_hosts(self): + """ + 获取所有服务器的容器数量 + :return: + """ + # while True: + args = public.to_dict_obj({}) + container_list = dc.main().get_list(args) + for c in container_list['container_list']: + args.id = c['id'] + args.write = 1 + args.save_date = self.__save_date + ds.main().stats(args) + # time.sleep(60) + + # 获取所有服务器的容器数量 + def container_count(self): + # while True: + hosts = dp.sql('hosts').select() + n = 0 + for i in hosts: + args = public.to_dict_obj({}) + args.url = i['url'] + container_list = dc.main().get_list(args) + n += len(container_list) + pdata = { + "time": int(time.time()), + "container_count": n + } + expired = time.time() - (self.__save_date * self.__day_sec) + dp.sql("container_count").where("time +# ------------------------------------------------------------------- + +import docker.errors + +import gettext +_ = gettext.gettext +# ------------------------------ +# Docker模型 +# ------------------------------ +import public + +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + + def docker_client(self, url): + return dp.docker_client(url) + + def get_network_id(self, get): + """ + asdf + @param get: + @return: + """ + networks = self.docker_client(self._url).networks + network = networks.get(get.id) + return network.attrs + + def get_host_network(self, get): + """ + 获取服务器的docker网络 + :param get: + :return: + """ + try: + client = self.docker_client(self._url) + if not client: + return public.return_message(-1, 0, []) + + networks = client.networks + network_attr = self.get_network_attr(networks) + data = list() + + for attr in network_attr: + get.id = attr["Id"] + c_result = self.get_network_id(get) + subnet = "" + gateway = "" + if attr["IPAM"]["Config"]: + if "Subnet" in attr["IPAM"]["Config"][0]: + subnet = attr["IPAM"]["Config"][0]["Subnet"] + if "Gateway" in attr["IPAM"]["Config"][0]: + gateway = attr["IPAM"]["Config"][0]["Gateway"] + + tmp = { + "id": attr["Id"], + "name": attr["Name"], + "time": dp.convert_timezone_str_to_timestamp(attr["Created"]), + "driver": attr["Driver"], + "subnet": subnet, + "gateway": gateway, + "labels": attr["Labels"], + "used": 1 if c_result["Containers"] else 0, + "containers": c_result["Containers"], + } + data.append(tmp) + + return public.return_message(0, 0, sorted(data, key=lambda x: x['time'], reverse=True)) + except Exception as e: + err = str(e) + if "Connection reset by peer" in err: + return public.return_message(-1, 0, _( + "The docker service is running abnormally, please restart and try again!")) + return public.return_message(-1, 0, []) + + def get_network_attr(self, networks): + network = networks.list() + return [i.attrs for i in network] + + def add(self, get): + """ + :param name 网络名称 + :param driver bridge/ipvlan/macvlan/overlay + :param options Driver options as a key-value dictionary + :param subnet '124.42.0.0/16' + :param gateway '124.42.0.254' + :param iprange '124.42.0.0/24' + :param labels Map of labels to set on the network. Default None. + :param remarks 备注 + :param get: + :return: + """ + # {"name": "23sdff223f", "driver": "overlay", "options": "", "subnet": "192.168.13.0/24", + # "gateway": "192.168.13.1", "iprange": "192.168.13.0/24", "labels": ""} + # 校验参数 + try: + get.validate([ + Param('name').Require().String(), + Param('subnet').Require(), + Param('gateway').Require(), + Param('iprange').Require(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import docker + + ipam_pool = docker.types.IPAMPool( + subnet=get.subnet, + gateway=get.gateway, + iprange=get.iprange + ) + + ipam_config = docker.types.IPAMConfig( + pool_configs=[ipam_pool] + ) + + try: + self.docker_client(self._url).networks.create( + name=get.name, + options=dp.set_kv(get.options), + driver="bridge", + ipam=ipam_config, + labels=dp.set_kv(get.labels) + ) + except docker.errors.APIError as e: + print(str(e)) + if "failed to allocate gateway" in str(e): + return public.return_message(-1, 0, _( + "The gateway setting is wrong, Please enter a gateway that matches the subnet: {}".format( + get.subnet))) + if "invalid CIDR address" in str(e): + return public.return_message(-1, 0, _( + "Subnet address format error, please enter for example: 172.16.0.0/16")) + if "invalid Address SubPool" in str(e): + return public.return_message(-1, 0, _( + "IP range format error, please enter the appropriate IP range for this subnet:".format( + get.subnet))) + if "Pool overlaps with other one on this address space" in str(e): + return public.return_message(-1, 0, _( "IP range [{}] already exists!".format(get.subnet))) + return public.return_message(-1, 0, _( "Failed to add network! {}".format(str(e)))) + + dp.write_log("Added network [{}] [{}] successful!".format(get.name, get.iprange)) + return public.return_message(0, 0, _( "Added network successfully!")) + + def del_network(self, get): + """ + :param id + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + networks = self.docker_client(self._url).networks.get(get.id) + attrs = networks.attrs + if attrs['Name'] in ["bridge", "none"]: + return public.return_message(-1, 0, _( "The system default network cannot be deleted!")) + + networks.remove() + dp.write_log("Delete network [{}] successfully!".format(attrs['Name'])) + return public.return_message(0, 0, _( "successfully delete!")) + + except docker.errors.APIError as e: + if " has active endpoints" in str(e): + return public.return_message(-1, 0, _( "The network cannot be deleted while it is in use!")) + return public.return_message(-1, 0, _( "Delete failed! {}".format(str(e)))) + + def prune(self, get): + """ + 删除无用的网络 + :param get: + :return: + """ + try: + res = self.docker_client(self._url).networks.prune() + if not res['NetworksDeleted']: + return public.return_message(-1, 0, _( "There are no useless networks!")) + + dp.write_log("Delete useless network successfully!") + return public.return_message(0, 0, _( "successfully delete!")) + + except docker.errors.APIError as e: + return public.return_message(-1, 0, _( "Delete failed! {}".format(str(e)))) + + def disconnect(self, get): + """ + 断开某个容器的网络 + :param id + :param container_id + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('id').Require().String(), + Param('container_id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + get.id = get.get("id/s", "") + get.container_id = get.get("container_id/s", "") + if get.id == "": + return public.return_message(-1, 0, _( "Network ID cannot be empty")) + if get.container_id == "": + return public.return_message(-1, 0, _( "Container ID cannot be empty")) + + networks = self.docker_client(self._url).networks.get(get.id) + networks.disconnect(get.container_id) + dp.write_log("Network disconnection [{}] successful!".format(get.id)) + return public.return_message(0, 0, _( "Network disconnection was successful!")) + except docker.errors.APIError as e: + if "No such container" in str(e): + return public.return_message(-1, 0, _( "Container ID: {}, does not exist!".format(get.container_id))) + if "network" in str(e) and "Not Found" in str(e): + return public.return_message(-1, 0, _( "Network ID: {}, does not exist!".format(get.id))) + return public.return_message(-1, 0, _( "Network disconnection failed! {}".format(str(e)))) + + def connect(self, get): + """ + 连接到指定网络 + :param id + :param container_id + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('id').Require().String(), + Param('container_id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + networks = self.docker_client(self._url).networks.get(get.id) + networks.connect(get.container_id) + dp.write_log("Network connection [{}] successful!".format(get.id)) + return public.return_message(0, 0, _( "Network connection successful!")) + except docker.errors.APIError as e: + if "No such container" in str(e): + return public.return_message(-1, 0, _( "Container ID: {}, does not exist!".format(get.container_id))) + if "network" in str(e) and "Not Found" in str(e): + return public.return_message(-1, 0, _( "Network ID: {}, does not exist!".format(get.id))) + return public.return_message(-1, 0, _( "Failed to connect to network! {}".format(str(e)))) diff --git a/class_v2/btdockerModelV2/projectModel.py b/class_v2/btdockerModelV2/projectModel.py new file mode 100644 index 00000000..32221284 --- /dev/null +++ b/class_v2/btdockerModelV2/projectModel.py @@ -0,0 +1,540 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + + +import gettext +_ = gettext.gettext +# ------------------------------ +# Docker模型 +# ------------------------------ +import public +import os +import time +import json +import re +from btdockerModelV2 import dk_public as dp +from btdockerModelV2 import setupModel as ds +from btdockerModelV2 import volumeModel as dv +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + + compose_path = "{}/data/compose".format(public.get_panel_path()) + project_path = "/www/dk_project" + templates_path = "{}/templates".format(project_path) + config_path = "{}/config".format(public.get_panel_path()) + info_path = "{}/docker_project_info.json".format(config_path) + __first_pl = "{}/first.pl".format(project_path) + + def __init__(self): + self.log_file = "/tmp/dk_project_run.log" + self.docker_setup = ds.main() + if not os.path.exists(self.templates_path): os.system("mkdir -p {}".format(self.templates_path)) + self.compose_cmd = "/usr/bin/docker-compose" if self.docker_setup.check_docker_compose_service()[0] \ + else "/usr/local/bin/docker-compose" + + def __check_conf(self, filename): + ''' + 验证配置文件是否可执行 + @param filename: docker-compose.yml文件路劲 + @return: + ''' + return public.ExecShell("{} -f {} config".format(self.compose_cmd, filename)) + + def sync_item(self, get): + ''' + 同步官方可以一键部署的项目 + @param get: 空对象 + @return: + ''' + os.remove(self.info_path) + project_info = self._get_project_list(get) + failed_list = [] + successes_list = [] + for info in project_info: + if info["server_name"]: + down_project_yml = self.__download_project_yml(info["server_name"]) + if not down_project_yml["status"]: + failed_list.append(info["server_name"]) + continue + successes_list.append(info["server_name"]) + data = [{"successes": len(successes_list), "server_name": successes_list}, + {"failed": len(failed_list), "server_name": failed_list}] + return public.return_message(0, 0, data) + + def __first_sync_item(self, project_info): + ''' + 同步官方可以一键部署的项目 + @param get: 空对象 + @return: + ''' + failed_list = [] + successes_list = [] + for info in project_info: + if info["server_name"]: + down_project_yml = self.__download_project_yml(info["server_name"]) + if not down_project_yml["status"]: + failed_list.append(info["server_name"]) + continue + successes_list.append(info["server_name"]) + data = [{"successes": len(successes_list), "server_name": successes_list}, + {"failed": len(failed_list), "server_name": failed_list}] + return data + + def get_project_list(self, get): + ''' + 获取支持一键部署的项目列表 + @param get: + @return: + ''' + project_info = [] + try: + if not os.path.exists(self.info_path): + down_info = self.__download_info(self.info_path) + if not down_info["status"]: + return public.return_message(0, 0, project_info) + + project_info = json.loads(public.readFile(self.info_path)) + project_info.sort(key=lambda x: x["sort"]) + + if not os.path.exists(self.__first_pl): + sync_result = self.__first_sync_item(project_info) + for result in sync_result: + if result.get("successes") and result["successes"] <= 0: + return public.return_message(0, 0, project_info) + public.ExecShell("echo \"first\" > {}".format(self.__first_pl)) + + except Exception as e: + project_info = [] + + return public.return_message(0, 0, project_info) + + def _get_project_list(self, get): + ''' + 获取支持一键部署的项目列表 + @param get: + @return: + ''' + project_info = [] + try: + if not os.path.exists(self.info_path): + down_info = self.__download_info(self.info_path) + if not down_info["status"]: + return project_info + + project_info = json.loads(public.readFile(self.info_path)) + project_info.sort(key=lambda x: x["sort"]) + + if not os.path.exists(self.__first_pl): + sync_result = self.__first_sync_item(project_info) + for result in sync_result: + if result.get("successes") and result["successes"] <= 0: + return project_info + public.ExecShell("echo \"first\" > {}".format(self.__first_pl)) + + except Exception as e: + project_info = [] + + return project_info + + def __get_docker_status(self, args): + ''' + 获取docker安装和启动状态 + @param args: + @return: + ''' + return { + "installed": self.docker_setup.check_docker_compose_service(), + "service_status": self.docker_setup.get_service_status() + } + + def __download_info(self, info_path): + ''' + 下载版本信息: info.json + @param info_path: string info.json文件的路劲 + @return: + ''' + url = "{}/install/lib/docker_project/docker_project_info.json".format(public.get_url()) + dp.download_file(url, info_path) + if os.path.exists(info_path): + return public.returnMsg(True, "info.json is downloaded!") + return public.returnMsg(False, "The info.json download failed!") + + def __download_project_yml(self, server_name): + ''' + 下载指定项目压缩包 + @param server_name: string 模板名称,如nextcloud + @return: + ''' + try: + path = "{}/{}".format(self.templates_path, server_name) + filename = "{}/{}.tar.gz".format(self.templates_path, server_name) + compose_file = "{}/docker-compose.yml".format(path) + url = "{}/install/lib/docker_project/templates/{}.tar.gz".format(public.get_url(), server_name) + dp.download_file(url, filename) + if not os.path.exists(filename): + return public.returnMsg(False, "{} Download failed, please resync!".format(server_name)) + if os.path.getsize(filename) == 0: + os.remove(filename) + return public.returnMsg(False, "{} Download failed, please resync!".format(server_name)) + self.__tar_x_yml(server_name, path, filename) + if os.path.exists(compose_file): + check_conf = self.__check_conf(compose_file) + if check_conf[1]: + return public.returnMsg(False, "{}yml file test failed,{}".format(server_name, check_conf[1])) + return public.returnMsg(True, "{} Download completed!".format(server_name)) + except: + return public.returnMsg(False, "{} Download failed, please resync!".format(server_name)) + + def __tar_x_yml(self, server_name, path=None, filename=None): + ''' + 解压项目模板方法 + @param server_name: 模板名称,如nextcloud + @param path: 项目模板路劲,如/www/dk_project/templates/nextcloud + @param filename: 项目模板压缩包,如/www/dk_project/templates/nextcloud.tar.gz + @return: + ''' + tar_result = public.ExecShell("tar xvf {} -C {}".format(filename, self.templates_path)) + if tar_result[1]: + os.remove(path) + os.remove(filename) + return public.returnMsg(False, "{} Decompression failed".format(server_name)) + return public.returnMsg(True, "{} extracted successfully".format(server_name)) + + def create_project_volume(self, server_name, project_name, dir_names, volume_path): + ''' + 创建指定项目的数据存储卷 + @param volume_path: + @param project_name: string + @param dir_names: list [dir_name,dir_name,...] + @return: + ''' + args = public.dict_obj() + args.url = "unix:///var/run/docker.sock" + # volumes = dv.main().get_volume_list(args) + # {'status': True, 'msg': {'volume': [], 'installed': True, 'service_status': True}} + # if volumes['status']: + # volumes = volumes['msg']['volume'] + # else: + # volumes = list() + # volume的值,一个list: [] + for dir_name in dir_names: + # # 如果已经存在就跳过 + # for volume in volumes: + # if dir_name == volume["Name"]: + # continue + if volume_path == "": + path = "{}/projects/{}/data/{}".format(self.project_path, project_name, dir_name) + else: + path = "{}/data/{}".format(volume_path, dir_name) + is_mkdir = public.ExecShell("mkdir -p {}".format(path)) + if is_mkdir[1]: return public.returnMsg(False, "Directory creation failed for the following reasons: {}".format(is_mkdir[1])) + args.name = "{}_{}_{}".format(project_name, server_name, dir_name) + args.driver = "local" + args.driver_opts = {'type': 'none', 'device': path, 'o': 'bind'} + args.labels = {} + dv.main().add(args) + return public.returnMsg(True, "The storage volume has been created") + + def get_project(self, get): + ''' + 获取指定一键部署项目的配置信息 + @param get: get.server_name + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('server_name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + server_name = getattr(get, "server_name") + info_path = "{}/{}/conf.json".format(self.templates_path, server_name) + project_info = json.loads(public.readFile(info_path)) + volume_placeholder = "Default: {}/projects/ your project name /data/".format(self.project_path) + total_sum = len(project_info) + volume_path = {"id": total_sum + 1, "sort": total_sum + 1, "type": "string", + "key": "VOLUME_PATH", "value": "", "placeholder": volume_placeholder, + "ps": "Data storage directory"} + project_info.append(volume_path) + except: + project_info = [] + return public.return_message(0, 0, project_info) + + def _get_project(self, get): + ''' + 获取指定一键部署项目的配置信息 + @param get: get.server_name + @return: + ''' + try: + server_name = getattr(get, "server_name") + info_path = "{}/{}/conf.json".format(self.templates_path, server_name) + project_info = json.loads(public.readFile(info_path)) + volume_placeholder = "Default: {}/projects/ your project name /data/".format(self.project_path) + total_sum = len(project_info) + volume_path = {"id": total_sum + 1, "sort": total_sum + 1, "type": "string", + "key": "VOLUME_PATH", "value": "", "placeholder": volume_placeholder, + "ps": "Data storage directory"} + project_info.append(volume_path) + except: + project_info = [] + return project_info + + def __get_server_ps(self, project_conf, conf_key): + ''' + 获取对应服务名的标题 + @param project_conf: + @param conf_key: + @return: + ''' + get = public.dict_obj() + for conf in project_conf: + if conf["key"] == "SERVER_NAME": + get.server_name = conf["value"] + server_conf = self._get_project(get) + for server in server_conf: + if conf_key == server["key"]: + return server["ps"] + return conf_key + + def get_project_logs(self, get): + """ + 获取一键部署日志,websocket + @param get: + @return: + """ + get.wsLogTitle = "Please wait to execute the command..." + print(self.log_file) + get._log_path = self.log_file + return self.get_ws_log(get) + + def create_project(self, get): + ''' + 创建一键部署的项目 + @param get: + @return: + ''' + + # {"project_conf": [{"key": "PROJECT_NAME", "value": "sdfasdf"}, {"key": "PORT", "value": "8180"}, + # {"key": "DB_ROOT_PASS", "value": "bt_nextcloud"}, {"key": "DB_NAME", "value": "nextcloud"}, + # {"key": "DB_USER", "value": "nextcloud"}, {"key": "DB_PASS", "value": "bt_nextcloud"}, + # {"key": "VOLUME_PATH", "value": "/www/dk_project/projects/sdfasdf"}, + # {"key": "REMARK", "value": "SDFADSF"}, {"key": "SERVER_NAME", "value": "nextcloud"}, + # {"key": "VOLUMES", "value": ["nextcloud", "db"]}]} + + + # 校验参数 + try: + get.validate([ + Param('project_conf').Require().List(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_conf = getattr(get, "project_conf") + remark = "" + for conf in project_conf: + if conf["key"] != "REMARK" and type(conf["value"]) != list: + if re.search(r'\s', conf["value"]): + server_ps = self.__get_server_ps(project_conf, conf["key"]) + return public.return_message(-1, 0, _( "{} cannot contain Spaces".format(server_ps))) + if conf["key"] != "VOLUME_PATH" and conf["key"] != "REMARK": + if conf["value"] == "": + server_ps = self.__get_server_ps(project_conf, conf["key"]) + return public.return_message(-1, 0, _( "{} cannot be null!".format(server_ps))) + if conf["key"].upper() == "PROJECT_NAME": project_name = conf["value"].strip() + if conf["key"].upper() == "VOLUME_PATH": project_volume = conf["value"].strip() + if conf["key"].upper() == "SERVER_NAME": server_name = conf["value"].strip() + if conf["key"].upper() == "VOLUMES": # VOLUMES = list + volumes = conf["value"] + if conf["key"].upper() == "PORT": + if dp.check_socket(conf["value"]): + return public.return_message(-1, 0, _( "Server port [{}] is occupied, please change to another port!".format(conf['value']))) + project_port = conf["value"] + if conf["key"] == "REMARK": remark = conf["value"] + + config_path = "{}/config/name_map.json".format(public.get_panel_path()) + if not os.path.exists(config_path): + public.writeFile(config_path, json.dumps({})) + + if public.readFile(config_path) == '': + public.writeFile(config_path, json.dumps({})) + + name_map = json.loads(public.readFile(config_path)) + name_str = 'q18q' + public.GetRandomString(10).lower() + name_map[name_str] = project_name + project_name = name_str + public.writeFile(config_path, json.dumps(name_map)) + server_dir = "{}/{}".format(self.templates_path, server_name) + project_dir = "{}/projects/{}/{}_{}".format(self.project_path, project_name, project_name, server_name) + public.set_module_logs('docker_project', 'create_project', 1) + check_result = self.__create_dir(project_dir, project_name, server_name, server_dir) + # todo 修改返回内容 只取msg 测试是否取到 + if not check_result["status"]: + return public.return_message(-1, 0, check_result["msg"]) + + self.__write_config(project_dir, project_name, server_name, project_conf) + self.create_project_volume(server_name, project_name, volumes, project_volume) + run_result = self.__project_run(project_dir, project_name) + + if run_result["status"]: + self.__add_sql(project_dir, project_name, server_name, remark) + dp.write_log("One-click deployment project [{}] successful!".format(server_name)) + return public.return_message(-1, 0, self.__return_msg(project_port)) + return public.return_message(-1, 0, run_result) + # return public.return_message(-1, 0, run_result["msg"]) + + def __project_run(self, project_dir, project_name): + ''' + 运行项目 + @param project_dir: 项目运行目录 + @param server_name: 服务名称 + @return: + ''' + filename = "{}/docker-compose.yml".format(project_dir) + check_result = self.__check_conf(filename) + if check_result[1]: + return public.returnMsg(False, "Project startup failed {}".format(check_result[1])) + + public.ExecShell("echo -n > {}".format(self.log_file)) + public.ExecShell("nohup {} -f {}/docker-compose.yml up -d >> {} 2>&1 &&" + " echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &" + .format( + self.compose_cmd, + project_dir, + self.log_file, + self.log_file, + self.log_file + )) + return public.returnMsg(True, "Start creating the project") + + def __create_dir(self, project_dir, project_name, server_name, server_dir): + ''' + 创建项目目录 + @param project_dir: 项目目录 + @param project_name: 项目名称 + @param server_dir: 服务源目录 + @return: + ''' + if self.__check_repeat(project_dir, project_name, server_name): + return public.returnMsg(False, "{} already exists, please change the project name".format(project_name)) + mk_result = public.ExecShell("mkdir -p {}".format(project_dir)) + if mk_result[1]: return public.returnMsg(False, "User project directory failed to create,details: {}".format(mk_result[1])) + cp_result = public.ExecShell("cp -a {}/. {}/".format(server_dir, project_dir)) + if cp_result[1]: return public.returnMsg(False, "Failed to copy project directory. Details: {}".format(cp_result[1])) + return public.returnMsg(True, "") + + def __add_sql(self, project_dir, project_name, server_name, remark): + ''' + 添加项目到docker数据库中 + @param project_dir: 项目路劲 + @param project_name: 项目名称 + @return: + ''' + pdata = { + "name": public.xsssec("{}_{}".format(project_name, server_name)), + "status": "1", + "path": "{}/docker-compose.yml".format(project_dir), + "template_id": "", + "time": time.time(), + "remark": public.xsssec(remark) + } + dp.sql("stacks").insert(pdata) + + def __return_msg(self, project_port): + ''' + 创建成功后返回给用户的数据 + @param project_port: + @return: + ''' + server_ip = public.get_server_ip() + local_ip = public.GetLocalIp() + data = {"protocol": "http", "server_ip": server_ip, "local_ip": local_ip, + "port": project_port} + return public.returnMsg(True, data) + + def __check_repeat(self, project_dir, project_name, server_name): + ''' + 检查是否存在相同项目 + @param project_dir: 项目路劲 + @return: + ''' + # if os.path.exists(project_dir): + # return True + stacks_info = dp.sql("stacks").where("name=?", ("{}_{}".format(project_name, server_name),)).find() + if stacks_info: + return True + return False + + def __write_config(self, project_dir, project_name, server_name, project_conf): + ''' + 写配置文件 + @param project_dir: 用户项目目录 + @param project_name: 项目名称 + @param server_name: 服务名称,如nextcloud + @param project_conf: 新的配置文件内容 + @return: + ''' + old_env_path = "{}/{}/.env".format(self.templates_path, server_name) + new_env_path = "{}/.env".format(project_dir) + env_conf = "" + if not os.path.exists(old_env_path): + public.ExecShell("echo > {}".format(old_env_path)) + with open(old_env_path) as env: + lines = env.readlines() + # 取旧文件转字典 + old_dict = {} + for line in lines: + if "=" in line: + temp = line.split("=") + old_dict[temp[0]] = temp[1] + # 新数据转字典 + new_dict = {} + for conf in project_conf: + if conf["key"] == "VOLUME_PATH": + project_volume = conf["value"] + if "Default path" in project_volume: + conf["value"] = "{}/{}/data/".format(self.project_path, project_name) + continue + if conf["key"] == "VOLUMES": continue + new_dict[conf["key"].upper()] = conf["value"] + # 旧字典更新新字典的内容 + old_dict.update(new_dict) + # 拼接成新的环境变量文件 + for key, value in old_dict.items(): + env_conf += "{}={}\n".format(key, value.strip()) + public.writeFile(new_env_path, env_conf) + return True + + def sync_compose_template(self, server_name): + ''' + 同步模板到项目模板页面 + @param server_name: 模板名称 + @return: + ''' + data = dp.sql("templates").where("name=?", (server_name,)).find() + # if data: dp.sql("templates").delete(id=data["id"]) + if data: return + pdata = { + "name": server_name, + "remark": "aaPanel Docker Quick Deployment templates only [Do not delete them and use them separately to create projects]", + "path": "{}/{}/docker-compose.yml".format(self.templates_path, server_name) + } + dp.sql("templates").insert(pdata) + dp.write_log("Add template [{}] successful!".format(server_name)) diff --git a/class_v2/btdockerModelV2/proxyModel.py b/class_v2/btdockerModelV2/proxyModel.py new file mode 100644 index 00000000..268529e4 --- /dev/null +++ b/class_v2/btdockerModelV2/proxyModel.py @@ -0,0 +1,301 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import json +import os +import traceback +from datetime import datetime + +import gettext +_ = gettext.gettext +# 未处理关键字 + +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param +class main(dockerBase): + + # 2023/12/27 下午 2:56 创建容器反向代理 + def create_proxy(self, get): + ''' + @name 创建容器反向代理 + @author wzz <2023/12/27 下午 2:57> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + # 校验参数 + try: + get.validate([ + Param('domain').Require(), + Param('container_port').Require(), + Param('container_name').Require(), + Param('container_id').Require(), + Param('privateKey').Require(), + Param('certPem').Require(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + try: + if not (os.path.exists('/etc/init.d/nginx') or os.path.exists('/etc/init.d/httpd')): + return public.return_message(-1, 0, 'nginx or apache server was not detected, please install one first!') + + # if not hasattr(get, 'domain'): + # return public.return_message(-1, 0, 'parameter error') + # + # if not hasattr(get, 'container_port'): + # return public.return_message(-1, 0, 'parameter error') + + self.siteName = get.domain.strip() + self.check_table_dk_sites() + if dp.sql('dk_sites').where('container_id=?', (get.container_id,)).order('id desc').find(): + self.close_proxy(get) + # 2024/2/23 下午 12:05 如果其他地方有这个域名,则禁止添加 + newpid = public.M('domain').where("name=? and port=?", (self.siteName, 80)).getField('pid') + if newpid: + result = public.M('sites').where("id=? and ps!=?", + (newpid, 'Reverse proxy for the container [{}]'.format(get.container_name))).find() + if result: + return public.return_message(-1, 0, _( + 'Project Type [{}] Existing Domain: {}'.format(result['project_type'], + self.siteName))) + + self.container_port = get.container_port + if not dp.check_socket(self.container_port): + return public.return_message(-1, 0, _( "Server port [{}] is not used, please enter the port in use to reverse!".format(self.container_port))) + + self.sitePath = '/www/wwwroot/' + self.siteName + + from panelSite import panelSite + args = public.to_dict_obj({ + 'webname': '{"domain":"'+ self.siteName +'","domainlist":[],"count":0}', + 'type': 'docker', + 'port': "80", + 'ps': self.siteName, + 'path': self.sitePath, + 'type_id': 111, + 'version': "00", + 'ftp': False, + 'sql': False, + }) + panelSite().AddSite(args) + + args = public.to_dict_obj({ + 'type': 1, + 'proxyname': get.container_name + '_dk_proxy', + 'cachetime': 1, + 'proxydir': '/', + 'cache': 0, + 'subfilter': '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]', + 'sitename': self.siteName, + 'advanced': 0, + 'proxysite': 'http://127.0.0.1:' + self.container_port, + 'todomain': '$host', + }) + import projectModel.proxyModel as proxyModel + proxyModel = proxyModel.main() + proxyModel.CreateProxy(args) + + # 设置面板SSL + if hasattr(get, "privateKey") and hasattr(get, "certPem"): + args = public.to_dict_obj({ + 'type': '1', + 'siteName': self.siteName, + 'key': get.privateKey, + 'csr': get.certPem, + }) + panelSite().SetSSL(args) + + # 写入数据库 + newpid = public.M('domain').where("name=? and port=?", (self.siteName, 80)).getField('pid') + if newpid: + # 更新ps和project_type字段 + public.M('sites').where("id=?", (newpid,)).save('ps,project_type', ( + 'Reverse proxy for the container [{}]'.format(get.container_name), + 'proxy')) + + site_pid = dp.sql('dk_sites').add( + 'name,path,ps,addtime,container_id,container_name,container_port', + (self.siteName, self.sitePath, 'Reverse proxy for the container [{}]'.format(get.container_name), + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), get.container_id, get.container_name, self.container_port) + ) + if not site_pid: + return public.return_message(-1, 0, _( 'Add failure, database cannot be written!')) + # 检查数据库是否存在 + self.check_table_dk_domain() + domain_id = dp.sql('dk_domain').where('id=?', (site_pid,)).find() + if not domain_id: + dp.sql('dk_domain').add( + 'pid,name,addtime', + (site_pid, self.siteName, datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + ) + + return public.return_message(0, 0, _( 'successfully added!')) + except Exception as e: + return public.return_message(0, 0, _( 'Add failed, error {}!'.format(str(e)))) + + # 2024/1/2 下午 5:34 获取容器的反向代理信息 + def get_proxy_info(self, get): + ''' + @name 获取容器的反向代理信息 + @author wzz <2024/1/2 下午 5:34> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('container_id').Require(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + + try: + # if not hasattr(get, 'container_id'): + # return public.return_message(-1, 0, 'parameter error') + + container_id = get.container_id + self.check_table_dk_sites() + proxy_info = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find() + # 没找到表 + if isinstance(proxy_info, dict): + return public.return_message(-1, 0, proxy_info) + + + + path = '/www/server/panel/vhost/cert/' + proxy_info['name'] + csrpath = path + "/fullchain.pem" + keypath = path + "/privkey.pem" + if os.path.exists(csrpath) and os.path.exists(keypath): + try: + proxy_info['cert'] = public.readFile(csrpath) + proxy_info['key'] = public.readFile(keypath) + except: + proxy_info['cert'] = "" + proxy_info['key'] = "" + + if not proxy_info: + return public.return_message(-1, 0, _( 'No reverse proxy information was detected!')) + return public.return_message(0, 0, proxy_info) + except Exception as ex: + print(traceback.format_exc()) + public.print_log("error: {}".format(ex)) + return public.return_message(-1, 0, {}) + + # 2024/1/2 下午 5:43 关闭容器的反向代理 + def close_proxy(self, get): + ''' + @name 关闭容器的反向代理 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + # 校验参数 + try: + get.validate([ + Param('container_id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + try: + # if not hasattr(get, 'container_id'): + # return public.return_message(-1, 0, 'parameter error!') + + container_id = get.container_id + proxy_info = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find() + + if not proxy_info: + return public.return_message(-1, 0, _( 'No reverse proxy information was detected!')) + + newpid = public.M('domain').where("name=? and port=?", (proxy_info["name"], 80)).getField('pid') + if not newpid: + return public.return_message(-1, 0, _( 'No reverse proxy information was detected!')) + + result = public.M('sites').where("id=? and ps=?", (newpid, 'Reverse proxy for the container [{}]'.format(proxy_info["container_name"]))).find() + # 删除反向代理 + import projectModel.proxyModel as proxyModel + proxyModel = proxyModel.main() + + args = public.to_dict_obj({ + 'id': result['id'], + 'webname': proxy_info['name'], + 'type': 1, + }) + proxyModel.DeleteSite(args) + + # 删除站点 + public.M('sites').where("name=?", (proxy_info['name'],)).delete() + public.M('domain').where("name=?", (proxy_info['name'],)).delete() + + # 删除数据库记录 + dp.sql('dk_sites').where('container_id=?', (container_id,)).delete() + dp.sql('dk_domain').where('pid=?', (proxy_info['id'],)).delete() + + return public.return_message(0, 0, _( 'successfully delete!')) + except: + return traceback.format_exc() + + # 2024/1/2 下午 5:57 获取指定域名的证书内容 + def get_cert_info(self, get): + ''' + @name 获取指定域名的证书内容 + @author wzz <2024/1/2 下午 5:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not hasattr(get, 'cert_name'): return public.return_message(-1, 0, _( 'parameter error!')) + cert_name = get.cert_name + # 2024/1/3 下午 4:50 处理通配符域名,将*.spider.com替换成spider.com + if cert_name.startswith('*.'): + cert_name = cert_name.replace('*.', '') + if not os.path.exists('/www/server/panel/vhost/ssl/{}'.format(cert_name)): + return public.return_message(-1, 0, _( 'Certificate does not exist!')) + cert_data = {} + cert_data['cert_name'] = cert_name + cert_data['cert'] = public.readFile('/www/server/panel/vhost/ssl/{}/fullchain.pem'.format(cert_name)) + cert_data['key'] = public.readFile('/www/server/panel/vhost/ssl/{}/privkey.pem'.format(cert_name)) + cert_data['info'] = json.loads( + public.readFile('/www/server/panel/vhost/ssl/{}/info.json'.format(cert_name))) + return public.return_message(-1, 0, cert_data) + except: + return public.return_message(-1, 0, traceback.format_exc()) + + def check_table_dk_domain(self): + ''' + @name 检查并创建表 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_domain')).count(): + dp.sql('dk_domain').execute( + "CREATE TABLE `dk_backup` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `pid` INTEGER, `name` TEXT, `addtime` TEXT )", + () + ) + + def check_table_dk_sites(self): + ''' + @name 检查并创建表 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_sites')).count(): + dp.sql('dk_sites').execute( + "CREATE TABLE `dk_backup` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `path` TEXT, `status` TEXT DEFAULT 1, `ps` TEXT, `addtime` TEXT, `type_id` integer DEFAULT 111, `edate` integer DEFAULT '0000-00-00', `project_type` STRING DEFAULT 'dk_proxy', `container_id` TEXT DEFAULT '', `container_name` TEXT DEFAULT '', `container_port` TEXT DEFAULT '')", + () + ) \ No newline at end of file diff --git a/class_v2/btdockerModelV2/registryModel.py b/class_v2/btdockerModelV2/registryModel.py new file mode 100644 index 00000000..8ea1999c --- /dev/null +++ b/class_v2/btdockerModelV2/registryModel.py @@ -0,0 +1,293 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: zouhw +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import json + +import gettext +_ = gettext.gettext + +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + + def docker_client(self, url): + return dp.docker_client(url) + + def add(self, args): + """ + 添加仓库 + :param registry 仓库URL docker.io + :param name + :parma username + :parma password + :param namespace 仓库命名空间 + :param remark 备注 + :param args: + :return: + """ + # {"registry": "docker.io", "name": "wzznb", "username": "akaishuichi", "password": "xiuyi999..", + # "namespace": "akaishuichi", "remark": "wzz_docker_io"} + + # 校验参数 + try: + args.validate([ + Param('name').Require().String(), + Param('username').Require().String(), + Param('password').Require().String(), + Param('namespace').Require().String(), + Param('remark').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + # 验证登录 + if not args.registry: + args.registry = "docker.io" + res = self.login(self._url, args.registry, args.username, args.password) + if not res['status']: + return public.return_message(-1, 0, res) + r_list = self.registry_list(args) + if len(r_list) > 0: + for r in r_list: + if r['name'] == args.name: + return public.return_message(-1, 0, _( "The name already exists!

                                    name: {}".format(args.name))) + if r['username'] == args.username and args.registry == r['url']: + return public.return_message(-1, 0, _( "Repository information already exists!")) + pdata = { + "name": args.name, + "url": args.registry, + "namespace": args.namespace, + "username": public.aes_encrypt(args.username, self.aes_key), + "password": public.aes_encrypt(args.password, self.aes_key), + "remark": public.xsssec(args.remark) + } + dp.sql("registry").insert(pdata) + dp.write_log("Added repository [{}] [{}] success!".format(args.name, args.registry)) + return public.return_message(0, 0, _( "successfully added!")) + + def edit(self, args): + """ + 编辑仓库 + :param registry 仓库URL docker.io + :param id 仓库id + :parma username + :parma password + :param namespace + :param remark + :param args: + :return: + """ + + # 校验参数 + try: + args.validate([ + Param('id').Require().Integer(), + Param('username').Require().String(), + Param('password').Require().String(), + Param('namespace').Require().String(), + Param('remark').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 验证登录 + # if str(args.id) == "1": + # return public.return_message(-1, 0, "[Official Docker repository] Not editable!") + if not args.registry: + args.registry = "docker.io" + + # 2023/12/13 上午 11:40 处理加密的编辑 + try: + is_encrypt = False + res = self.login(self._url, args.registry, args.username, args.password) + if not res['status']: + res = self.login( + self._url, + args.registry, + public.aes_decrypt(args.username, self.aes_key), + public.aes_decrypt(args.password, self.aes_key) + ) + if not res['status']: + return public.return_message(-1, 0, res['msg']) + is_encrypt = True + except Exception as e: + if "binascii.Error: Incorrect padding" in str(e): + return public.return_message(-1, 0, _( + "Editing failed! Reason: Account password decryption failed! Please delete the repository and add it again")) + return public.return_message(-1, 0, _( "Editing failed! Reason:{}".format(e))) + + res = dp.sql("registry").where("id=?", (args.id,)).find() + if not res: + return public.return_message(-1, 0, _( "This repository could not be found")) + pdata = { + "name": args.name, + "url": args.registry, + "username": public.aes_encrypt(args.username, self.aes_key) if is_encrypt is False else args.username, + "password": public.aes_encrypt(args.password, self.aes_key) if is_encrypt is False else args.password, + "namespace": args.namespace, + "remark": args.remark + } + dp.sql("registry").where("id=?", (args.id,)).update(pdata) + dp.write_log("Edit repository [{}][{}] Success!".format(args.name, args.registry)) + return public.return_message(0, 0, _( "Edit success!")) + + def remove(self, args): + """ + 删除某个仓库 + :param id + :param rags: + :return: + """ + # 校验参数 + try: + args.validate([ + Param('id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # if str(args.id) == "1": + # return public.return_message(-1, 0, "[Official Docker repository] can not be removed!") + + data = dp.sql("registry").where("id=?", (args.id)).find() + + if len(data) < 1: + return public.return_message(0, 0, _( "Delete failed,The repository id may not exist!")) + + dp.sql("registry").where("id=?", (args.id,)).delete() + + dp.write_log("Delete repository [{}][{}] Success!".format(data['name'], data['url'])) + return public.return_message(0, 0, _( "Successfully deleted!")) + def registry_list(self, get): + """ + 获取仓库列表 + :return: + """ + + db_obj = dp.sql("registry") + # 2024/1/3 下午 6:00 检测数据库是否存在并且表健康 + search_result = db_obj.where('id=? or name=?', (1, "Docker public repository")).select() + + # if db_obj.ERR_INFO: + # return [] + + + if len(search_result) == 0: + dp.sql("registry").insert({ + "name": "Docker public repository", + "url": "docker.io", + "username": "", + "password": "", + "namespace": "", + "remark": "Docker public repository" + }) + if "error: no such table: registry" in search_result or len(search_result) == 0: + # public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db") + public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db") + dp.check_db() + + res = dp.sql("registry").select() + if not isinstance(res, list): + res = [] + return res + # 改返回 + def registry_listV2(self, get): + """ + 获取仓库列表 + :return: + """ + db_obj = dp.sql("registry") + # 2024/1/3 下午 6:00 检测数据库是否存在并且表健康 + search_result = db_obj.where('id=? or name=?', (1, "Docker public repository")).select() + # search_result = db_obj.where('id=? ', (1)).select() + # if db_obj.ERR_INFO: + # return public.return_message(0, 0, []) + + + if len(search_result) == 0: + dp.sql("registry").insert({ + "name": "Docker public repository", + "url": "docker.io", + "username": "", + "password": "", + "namespace": "", + "remark": "Docker public repository" + }) + if "error: no such table: registry" in search_result or len(search_result) == 0: + # public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db") + public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db") + dp.check_db() + + res = dp.sql("registry").select() + if not isinstance(res, list): + res = [] + + return public.return_message(0, 0, res) + + def get_com_registry(self, get): + """ + 获取常用仓库列表 + @param get: + @return: + """ + com_registry_file = "{}/class/btdockerModelV2/config/com_registry.json".format(public.get_panel_path()) + try: + com_registry = json.loads(public.readFile(com_registry_file)) + except: + com_registry = { + "docker.io": "Docker public repository", + "swr.cn-north-4.myhuaweicloud.com": "Huawei Cloud mirror station", + "ccr.ccs.tencentyun.com": "Tencent cloud mirror station", + "registry.cn-hangzhou.aliyuncs.com": "Alibaba Cloud Mirror Station (Hangzhou)" + } + + return public.return_message(0, 0, com_registry) + + def registry_info(self, name): + return dp.sql("registry").where("name=?", (name,)).find() + + def login(self, url, registry, username, password): + """ + 仓库登录测试 + :param args: + :return: + """ + import docker.errors + try: + res = self.docker_client(url).login( + registry=registry, + username=username, + password=password, + reauth=False + ) + return public.returnMsg(True, str(res)) + except docker.errors.APIError as e: + if "authentication required" in str(e): + return public.returnMsg(False, + "Login test failed! Reason: May be account password error, please check!") + if "unauthorized: incorrect username or password" in str(e): + return public.returnMsg(False, + "Login test failed! Reason: May be account password error, please check!") + return public.returnMsg(False, "Login test failed! Reason: {}".format(e)) diff --git a/class_v2/btdockerModelV2/screen.py b/class_v2/btdockerModelV2/screen.py new file mode 100644 index 00000000..552fb3ef --- /dev/null +++ b/class_v2/btdockerModelV2/screen.py @@ -0,0 +1,84 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: zouhw +#------------------------------------------------------------------- + +#------------------------------ +# Docker模型 +#------------------------------ +import dk_public as dp +import time + +class main: + + def get_status(self,args): + """ + start_time + stop_time + :param args: + :return: + """ + data = dict() + # 容器总数 + data['container_count'] = self.__get_container_count(args) + # 镜像信息,镜像总数,占用空间大小 + data['image_info'] = dp.sql("image_infos").where("time>=? and time<=?",(args.start_time,args.stop_time)).select() + # 主机信息 + data['host'] = len(dp.sql('hosts').select()) + # 1小时内容器占用资源前三平均值 + data['container_top'] = {"cpu":self.__get_cpu_avg(),"mem":self.__get_mem_avg()} + return data + + def __get_container_count(self,args): + count = dp.sql('container_count').where("time>=? and time<=?", (args.start_time, args.stop_time)).select() + if not count: + return 0 + return count[-1] + + def __get_mem_avg(self): + now = int(time.time()) + start_time = now - 3600 + data = dp.sql("mem_stats").where("time>=? and time<=?",(start_time,now)).select() + containers = list() + info = dict() + # 获取容器ID + for d in data: + containers.append(d['container_id']) + # 获取每个容器1小时内的cpu使用率总和 + containers = set(containers) + for c in containers: + num = 0 + usage = 0 + for d in data: + if d['container_id'] == c: + num += 1 + usage += float(d['usage']) + if num != 0: + info[c] = usage / num + return info + + def __get_cpu_avg(self): + now = int(time.time()) + start_time = now - 3600 + data = dp.sql("cpu_stats").where("time>=? and time<=?",(start_time,now)).select() + containers = list() + info = dict() + # 获取容器ID + for d in data: + containers.append(d['container_id']) + # 获取每个容器1小时内的cpu使用率总和 + containers = set(containers) + for c in containers: + num = 0 + cpu_usage = 0 + for d in data: + if d['container_id'] == c: + num += 1 + cpu_usage += float(0 if d['cpu_usage'] == '0.0' else d['cpu_usage']) + if num != 0: + info[c] = cpu_usage / num + return info \ No newline at end of file diff --git a/class_v2/btdockerModelV2/script/flush_plugin_.py b/class_v2/btdockerModelV2/script/flush_plugin_.py new file mode 100644 index 00000000..a10e9912 --- /dev/null +++ b/class_v2/btdockerModelV2/script/flush_plugin_.py @@ -0,0 +1,134 @@ +# coding: utf-8 +import sys, os + +os.chdir('/www/server/panel/') +sys.path.insert(0, "class/") +import PluginLoader +import public +import time + + +def clear_hosts(): + """ + @name 清理hosts文件中的bt.cn记录 + @return: + """ + remove = 0 + try: + import requests + requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) + + url = 'https://www.bt.cn/api/ip/info_json' + res = requests.post(url, verify=False) + + if res.status_code == 404: + remove = 1 + elif res.status_code == 200 or res.status_code == 400: + res = res.json() + if res != "[]": + remove = 1 + except: + result = public.ExecShell("curl -sS --connect-timeout 3 -m 60 -k https://www.bt.cn/api/ip/info_json")[0] + if result != "[]": + remove = 1 + + hosts_file = '/etc/hosts' + if remove == 1 and os.path.exists(hosts_file): + public.ExecShell('sed -i "/www.bt.cn/d" /etc/hosts') + +def flush_cache(): + ''' + @name 更新缓存 + @author hwliang + @return void + ''' + try: + # start_time = time.time() + res = PluginLoader.get_plugin_list(1) + spath = '{}/data/pay_type.json'.format(public.get_panel_path()) + public.downloadFile(public.get_url() + '/install/lib/pay_type.json', spath) + import plugin_deployment + plugin_deployment.plugin_deployment().GetCloudList(None) + + # timeout = time.time() - start_time + if 'ip' in res and res['ip']: + pass + else: + if isinstance(res, dict) and not 'msg' in res: res['msg'] = 'Connection failure!' + except: + pass + + +def flush_php_order_cache(): + """ + 更新软件商店php顺序缓存 + @return: + """ + spath = '{}/data/php_order.json'.format(public.get_panel_path()) + public.downloadFile(public.get_url() + '/install/lib/php_order.json', spath) + + +def flush_msg_json(): + """ + @name 更新消息json + """ + try: + spath = '{}/data/msg.json'.format(public.get_panel_path()) + public.downloadFile(public.get_url() + '/linux/panel/msg/msg.json', spath) + except: + pass + + +def flush_docker_project_info(): + ''' + @name 更新docker_project版本信息 + @author wzz + @return void + ''' + msg = "docker_projcet version information" + try: + # start_time = time.time() + res = PluginLoader.get_plugin_list(1) + config_path = f"{public.get_panel_path()}/config" + spath = f"{config_path}/docker_project_info.json" + url = "/install/lib/docker_project/docker_project_info.json" + public.downloadFile(f"{public.get_url()}{url}", spath) + import plugin_deployment + plugin_deployment.plugin_deployment().GetCloudList(None) + + # timeout = time.time() - start_time + if 'ip' in res and res['ip']: + pass + else: + if isinstance(res, dict) and not 'msg' in res: res['msg'] = 'Connection failure!' + except: + pass + + +# 2024/3/20 上午 11:09 更新docker_hub镜像排行数据 +def flush_docker_hub_repos(): + ''' + @name 更新docker_hub镜像排行数据 + @author wzz <2024/3/20 上午 11:09> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + public.ExecShell("/www/server/panel/pyenv/bin/python3 /www/server/panel/class_v2/btdockerModelV2/script/syncreposdb.py") + + +if __name__ == '__main__': + tip_date_tie = '/tmp/.fluah_time' + if os.path.exists(tip_date_tie): + last_time = int(public.readFile(tip_date_tie)) + timeout = time.time() - last_time + if timeout < 600: + print("Execution interval is too short, exit - {}!".format(timeout)) + sys.exit() + clear_hosts() + flush_cache() + flush_php_order_cache() + flush_msg_json() + flush_docker_project_info() + flush_docker_hub_repos() + + public.writeFile(tip_date_tie, str(int(time.time()))) diff --git a/class_v2/btdockerModelV2/script/syncreposdb.py b/class_v2/btdockerModelV2/script/syncreposdb.py new file mode 100644 index 00000000..e363f108 --- /dev/null +++ b/class_v2/btdockerModelV2/script/syncreposdb.py @@ -0,0 +1,92 @@ +#!/www/server/panel/pyenv/bin/python3 +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +# docker模型sock 封装库 镜像库 +# ------------------------------------------------------------------- +import os +import sys +import time + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public + +if "/www/server/panel/class_v2" not in sys.path: + sys.path.insert(0, "/www/server/panel/class_v2") +import btdockerModelV2.dk_public as dp + +db_file = '{}/class_v2/btdockerModelV2/config/docker_hub_repos.db'.format(public.get_panel_path()) +last_update_pl = "{}/class_v2/btdockerModelV2/config/docker_hub_last_update.pl".format(public.get_panel_path()) + + +# 2024/3/20 上午 9:47 获取docker hub最新的镜像排行数据 +def get_docker_hub_repos(): + ''' + @name 获取docker hub最新的镜像排行数据 + @author wzz <2024/3/20 上午 9:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + url = "{}/src/docker/docker_hub_repos.db".format(public.get_url()) + dp.download_file(url, db_file) + if not os.path.exists(db_file): + return public.returnMsg(False, "info.json download failed") + + # 写一个最后更新的标记文件,里面有时间戳 + public.writeFile(last_update_pl, str(int(time.time()))) + + return + except Exception as e: + if os.path.exists('data/debug.pl'): + print(public.get_error_info()) + public.print_log(public.get_error_info()) + + +# 2024/3/20 上午 9:34 如果当前时间减去这个时间戳大于30天,就执行 get_docker_hub_repos +def check_last_update(): + ''' + @name 如果当前时间减去这个时间戳大于30天,就执行 get_docker_hub_repos + @author wzz <2024/3/20 上午 9:46> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if os.path.exists(last_update_pl): + last_update_time = int(public.readFile(last_update_pl)) + if int(time.time()) - last_update_time > 2592000: + public.ExecShell("rm -f {}".format(db_file)) + public.ExecShell("rm -f {}".format(last_update_pl)) + get_docker_hub_repos() + + if os.path.exists(db_file) and (os.path.getsize(db_file) == 0 or os.path.getsize(db_file) < 80): + public.ExecShell("rm -f {}".format(db_file)) + public.ExecShell("rm -f {}".format(last_update_pl)) + get_docker_hub_repos() + + if not os.path.exists(db_file): + public.ExecShell("rm -f {}".format(last_update_pl)) + get_docker_hub_repos() + else: + if not os.path.exists(db_file): + get_docker_hub_repos() + + if os.path.exists(db_file) and (os.path.getsize(db_file) == 0 or os.path.getsize(db_file) < 80): + public.ExecShell("rm -f {}".format(db_file)) + public.ExecShell("rm -f {}".format(last_update_pl)) + get_docker_hub_repos() + except Exception as e: + public.ExecShell("rm -f {}".format(db_file)) + public.ExecShell("rm -f {}".format(last_update_pl)) + if os.path.exists('data/debug.pl'): + print(public.get_error_info()) + public.print_log(public.get_error_info()) + + +check_last_update() diff --git a/class_v2/btdockerModelV2/securityModel.py b/class_v2/btdockerModelV2/securityModel.py new file mode 100644 index 00000000..3fe5fcb0 --- /dev/null +++ b/class_v2/btdockerModelV2/securityModel.py @@ -0,0 +1,1058 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- +import fnmatch +import os, sys, time +from _stat import S_ISREG, S_ISDIR, S_ISLNK + +# ------------------------------ +# Docker安全检测 +# ------------------------------ +BASE_PATH = "/www/server/panel" +os.chdir(BASE_PATH) +sys.path.insert(0, "class/") +import public, re +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +def auto_progress(func): + """ + @name 自动增长进度条(装饰器) + @author lwh<2024-1-23> + """ + def wrapper(self, *args, **kwargs): + result = func(self, *args, **kwargs) + self.progress_percent += self.scan_percent + return result + return wrapper + + +class main(dockerBase): + progress_percent = 0 # 扫描进度条 + ids_percent = 0 + scan_percent = 0 + scan_score = 100 # 扫描分数 + progress_content = "Initializing scan..." # 扫描内容 + sys_ver = "" # 系统类型 + requirements_list = ["veinmind", "veinmind-common"] + docker_obj = "" # docker对象 + image_name = "" # 镜像名称 + send_time = "" # 记录发送时间 + start_time = "" # 开始时间 + + def short_string(self, text): + """ + @name 缩短字符串为40个字符 + """ + if len(text) <= 40: + return text + else: + return text[:37] + "..." + + def short_string1(self, text): + """ + @name 缩短字符串中间部分保留前后 + """ + if len(text) <= 30: + return text + else: + return text[:15] + "..." + text[-15:] + + def get_image_list(self, get): + """ + @name 获取镜像id列表 + @author lwh<2024-1-23> + @return + """ + public.print_log("Get the image id list") + if not hasattr(get, "_ws"): + return True + from btdockerModelV2 import imageModel + image_list = imageModel.main().image_list(get=public.dict_obj()) + # public.print_log(image_list) + get._ws.send(public.GetJson({"end": True, "image_list": image_list})) + + def send_image_ws(self, get, msg, detail="", repair="", status=1, end=False): + """ + @name 发送ws信息 + @author lwh<2024-01-23> + @param msg string 扫描内容 + @param status int 风险情况:1无风险,2告警,3危险 + @param repair string 修复方案 + @param end bool 是否结束 + """ + now_time = time.time() + # 判断间隔时间是否小于100ms + if now_time-self.send_time <= 0.1 and not end and status == 1: + return + self.send_time = now_time + # 根据风险情况进行扣分 + if status == 2: + score = 2 + elif status == 3: + score = 5 + else: + score = 0 + # msg:扫描内容,score:当前分数,type:类型docker/image + get._ws.send(public.GetJson({"end": end, "image_name": self.image_name, "status": status, "detail": detail, + "msg": msg, "repair": repair, "score": score, "type": "image"})) + + def send_docker_ws(self, get, msg, repair="", status=1, end=True): + """ + @name 发送ws信息 + @author lwh<2024-01-23> + @param msg string 扫描内容 + @param status int 风险情况:1无风险,2告警,3危险 + @param repair string 修复方案 + @param end bool 是否结束 + """ + # 根据风险情况进行扣分 + if status == 2: + score = 2 + elif status == 3: + score = 5 + else: + score = 0 + # msg:扫描内容,progress:扫描进度,score:当前分数 + get._ws.send(public.GetJson({"end": end, "image_name": self.image_name, "status": status, "msg": msg, "repair": repair, "score": score, "type": "docker"})) + + def reduce_core(self, score): + """ + @name 减少总分 + @author lwh<2024-01-23> + @param score int 需要减少的分数 + @return self.scan_score int 所剩分数 + """ + self.scan_score -= score + if self.scan_score < 0: + return 0 + return self.scan_score + + def image_safe_scan(self, get): + """ + @name 镜像安全扫描入口函数 + @author lwh@aapanel.com + @time 2024-01-22 + @param _ws + @return 返回服务器扫描项 + """ + public.set_module_logs('docker', 'image_safe_scan', 1) + if not hasattr(get, "_ws"): + return True + if not hasattr(get, "image_id"): + return True + # 获取检测镜像 + image_id = get.image_id + # 初始化时间 + self.send_time = time.time() + + # 初始化安装检测SDK + try: + from veinmind import docker + except Exception as e: + public.print_log("Importing veinmind failed:{}".format(e)) + # requirements_list = ["veinmind"] + self.send_image_ws(get, msg="The detection engine is being initialized. The first load may take a long time....", status=1) + shell_command = "btpip install --no-dependencies {}".format("veinmind") + public.ExecShell(shell_command) + sys_ver = public.get_os_version() + if "Ubuntu" in sys_ver or "Debian" in sys_ver: + public.WriteFile("/etc/apt/sources.list.d/libveinmind.list", + "deb [trusted=yes] https://download.veinmind.tech/libveinmind/apt/ ./") + self.send_image_ws(get, msg="Apt-get is being updated. The first load may take a long time....", status=1) + public.ExecShell("apt-get update") + time.sleep(1) + self.send_image_ws(get, msg="Detection engine being installed, first execution may take thousands of years...", status=1) + public.ExecShell("apt-get install -y libveinmind-dev") + time.sleep(1) + elif "CentOS" in sys_ver: + public.WriteFile("/etc/yum.repos.d/libveinmind.repo", """[libveinmind] +name=libVeinMind SDK yum repository +baseurl=https://download.veinmind.tech/libveinmind/yum/ +enabled=1 +gpgcheck=0""") + self.send_image_ws(get, msg="The yum cache is being updated. The first load may take a long time....", status=1) + public.ExecShell("yum makecache") + self.send_image_ws(get, msg="Detection engine being installed, first execution may take thousands of years...", status=1) + public.ExecShell("yum install -y libveinmind-devel") + else: + self.send_image_ws(get, msg="Unsupported system version {}".format(sys_ver), status=1) + return public.returnMsg(False, "Unsupported system version {}\nCurrently only supports Debian, Ubuntu, Centos".format(sys_ver)) + self.send_image_ws(get, msg="Checking libdl.so dependent libraries...", status=1) + result, err = public.ExecShell("whereis libdl.so") + result = result.strip().split(" ") + # public.print_log("The situation of libdl.so library:{}".format(result)) + if len(result) <= 1: + # public.print_log("Missing libdl.so library") + result, err = public.ExecShell("whereis libdl.so.2") + result = result.strip().split(" ") + # public.print_log("The situation of libdl.so.2 library:{}".format(result)) + if len(result) <= 1: + public.print_log("Missing libdl.so library,Requires libdl.so or libdl.so2 to be installed") + public.returnMsg(False, "Missing libdl.so library,Requires libdl.so or libdl.so2 to be installed") + else: + # 建立libdl.so软链接至libdl.so.2 + for lib in result[1:]: + ln_command = "ln -s {} {}".format(lib, lib[:-2]) + # public.print_log("Soft link in progress:{}".format(ln_command)) + public.ExecShell(ln_command) + from veinmind import docker + + # 开始检测 + # 获取docker对象 + docker_obj = docker.Docker() + # # 获取所有镜像id + # ids = docker_obj.list_image_ids() + # # 计算镜像进度占比 + # self.ids_percent = math.floor(100 / len(ids)) + # # 计算每个镜像扫描进度占比 + # self.scan_percent = math.floor(self.ids_percent / 3) + # 开始镜像检测 + # for key, id in enumerate(ids): + image = docker_obj.open_image_by_id(image_id=image_id) + # 获取ref镜像名 + refs = image.reporefs() + if len(refs) > 0: + self.image_name = refs[0] + else: + self.image_name = image.id() + public.print_log("Detecting:{}".format(self.image_name)) + self.send_image_ws(get, msg="Scanning {} exception history command".format(self.image_name)) + self.scan_history(get, image) + self.send_image_ws(get, msg="Scanning {} sensitive information".format(self.image_name)) + self.scan_sensitive(get, image) + self.send_image_ws(get, msg="Scanning {} backdoor".format(self.image_name)) + self.scan_backdoor(get, image) + self.send_image_ws(get, msg="Scanning {} container escapes") + self.scan_escape(get, image) + self.send_image_ws(get, msg="{}Scan completed".format(self.image_name), end=True) + + def scan_history(self, get, image): + """ + @name 异常历史命令 + @author lwh@aapanel.com + @time 2024-01-22 + """ + instruct_set = ( + "FROM", "CMD", "RUN", "LABEL", "MAINTAINER", "EXPOSE", "ENV", "ADD", "COPY", "ENTRYPOINT", "VOLUME", "USER", + "WORKDIR", "ARG", "ONBUILD", "STOPSIGNAL", "HEALTHCHECK", "SHELL") + rules = { + "rules": [{"description": "Miner Repo", "instruct": "RUN", "match": ".*(xmrig|ethminer|miner)\\.git.*"}, + {"description": "Unsafe Path", "instruct": "ENV", "match": "PATH=.*(|:)(/tmp|/dev/shm)"}]} + + ocispec = image.ocispec_v1() + if 'history' in ocispec.keys() and len(ocispec['history']) > 0: + for history in ocispec['history']: + if 'created_by' in history.keys(): + created_by = history['created_by'] + created_by_split = created_by.split("#(nop)") + if len(created_by_split) > 1: + command = "#(nop)".join(created_by_split[1:]) + command = command.lstrip() + command_split = command.split() + if len(command_split) == 2: + instruct = command_split[0] + command_content = command_split[1] + for r in rules["rules"]: + if r["instruct"] == instruct: + if re.match(r["match"], command_content): + self.send_image_ws(get, msg="Suspicious abnormal history command found", detail="It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.".format(self.short_string(command_content)), repair="1.It is recommended to check whether the command is required for normal business
                                    2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses.") + break + else: + instruct = command_split[0] + command_content = " ".join(command_split[1:]) + for r in rules["rules"]: + if r["instruct"] == instruct: + if re.match(r["match"], command_content): + self.send_image_ws(get, msg="Suspicious abnormal history command found", detail="It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.".format(self.short_string(command_content)), repair="1.It is recommended to check whether the command is required for normal business
                                    2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses.") + break + else: + command_split = created_by.split() + if command_split[0] in instruct_set: + for r in rules["rules"]: + if r["instruct"] == command_split[0]: + if re.match(r["match"], " ".join(command_split[1:])): + self.send_image_ws(get, msg="Suspicious abnormal history command found", detail="It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.".format(self.short_string(" ".join(command_split[1:]))), repair="1.It is recommended to check whether the command is required for normal business
                                    2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses.") + break + else: + for r in rules["rules"]: + if r["instruct"] == "RUN": + if re.match(r["match"], created_by): + self.send_image_ws(get, msg="Suspicious abnormal history command found", detail="It was found that the image has an abnormal historical command [{}], which may implant malware or code into the host system when the container is running, causing security risks.".format(self.short_string(created_by)), repair="1.It is recommended to check whether the command is required for normal business
                                    2.It is recommended to choose official and reliable infrastructure to avoid unnecessary losses.") + break + + def scan_sensitive(self, get, image): + """ + @name 扫描镜像敏感数据 + @author lwh<2024-1-22> + """ + # 敏感数据规则 + rules = {"whitelist": { + "paths": ["/usr/**", "/lib/**", "/lib32/**", "/bin/**", "/sbin/**", "/var/lib/**", "/var/log/**", + "**/node_modules/**/*.md", "**/node_modules/**/test/**", "**/service/iam/examples_test.go", + "**/grafana/public/build/*.js"]}, "rules": [ + {"id": 1, "name": "gitlab_personal_access_token", "description": "GitLab Personal Access Token", + "match": "glpat-[0-9a-zA-Z_\\-]{20}", "level": "high"}, + {"id": 2, "name": "AWS", "description": "AWS Access Token", "match": "AKIA[0-9A-Z]{16}", "level": "high"}, + {"id": 3, "name": "PKCS8 private key", "description": "PKCS8 private key", + "match": "-----BEGIN PRIVATE KEY-----", "level": "high"}, + {"id": 4, "name": "RSA private key", "description": "RSA private key", + "match": "-----BEGIN RSA PRIVATE KEY-----", "level": "high"}, + {"id": 5, "name": "SSH private key", "description": "SSH private key", + "match": "-----BEGIN OPENSSH PRIVATE KEY-----", "level": "high"}, + {"id": 6, "name": "PGP private key", "description": "PGP private key", + "match": "-----BEGIN PGP PRIVATE KEY BLOCK-----", "level": "high"}, + {"id": 7, "name": "Github Personal Access Token", "description": "Github Personal Access Token", + "match": "ghp_[0-9a-zA-Z]{36}", "level": "high"}, + {"id": 8, "name": "Github OAuth Access Token", "description": "Github OAuth Access Token", + "match": "gho_[0-9a-zA-Z]{36}", "level": "high"}, + {"id": 9, "name": "SSH (DSA) private key", "description": "SSH (DSA) private key", + "match": "-----BEGIN DSA PRIVATE KEY-----", "level": "high"}, + {"id": 10, "name": "SSH (EC) private key", "description": "SSH (EC) private key", + "match": "-----BEGIN EC PRIVATE KEY-----", "level": "high"}, + {"id": 11, "name": "Github App Token", "description": "Github App Token", + "match": "(ghu|ghs)_[0-9a-zA-Z]{36}", "level": "high"}, + {"id": 12, "name": "Github Refresh Token", "description": "Github Refresh Token", + "match": "ghr_[0-9a-zA-Z]{76}", "level": "high"}, + {"id": 13, "name": "Shopify shared secret", "description": "Shopify shared secret", + "match": "shpss_[a-fA-F0-9]{32}", "level": "high"}, + {"id": 14, "name": "Shopify access token", "description": "Shopify access token", + "match": "shpat_[a-fA-F0-9]{32}", "level": "high"}, + {"id": 15, "name": "Shopify custom app access token", "description": "Shopify custom app access token", + "match": "shpca_[a-fA-F0-9]{32}", "level": "high"}, + {"id": 16, "name": "Shopify private app access token", "description": "Shopify private app access token", + "match": "shppa_[a-fA-F0-9]{32}", "level": "high"}, + {"id": 17, "name": "Slack token", "description": "Slack token", "match": "xox[baprs]-([0-9a-zA-Z]{10,48})?", + "level": "high"}, + {"id": 18, "name": "Stripe", "description": "Stripe", "match": "(?i)(sk|pk)_(test|live)_[0-9a-z]{10,32}", + "level": "high"}, {"id": 19, "name": "PyPI upload token", "description": "PyPI upload token", + "match": "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9-_]{50,1000}", "level": "high"}, + {"id": 20, "name": "Google (GCP) Service-account", "description": "Google (GCP) Service-account", + "match": "\\\"type\\\": \\\"service_account\\\"", "level": "medium"}, + {"id": 21, "name": "Password in URL", "description": "Password in URL", + "match": "[a-zA-Z]{3,10}:\\/\\/[^$][^:@\\/\\n]{3,20}:[^$][^:@\\n\\/]{3,40}@.{1,100}", "level": "high"}, + {"id": 22, "name": "Heroku API Key", "description": "Heroku API Key", + "match": "(?i)(?:heroku)(?:[0-9a-z\\-_\\t .]{0,20})(?:[\\s|']|[\\s|\"]){0,3}(?:=|>|:=|\\|\\|:|<=|=>|:)(?:'|\\\"|\\s|=|\\x60){0,5}([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})(?:['|\\\"|\\n|\\r|\\s|\\x60]|$)", + "level": "high"}, {"id": 23, "name": "Slack Webhook", "description": "Slack Webhook", + "match": "https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8,12}/[a-zA-Z0-9_]{24}", + "level": "medium"}, + {"id": 24, "name": "Twilio API Key", "description": "Twilio API Key", "match": "SK[0-9a-fA-F]{32}", + "level": "high"}, {"id": 25, "name": "Age secret key", "description": "Age secret key", + "match": "AGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L]{58}", "level": "high"}, + {"id": 26, "name": "Facebook token", "description": "Facebook token", + "match": "(?i)(facebook[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-f0-9]{32})['\\\"]", + "level": "high"}, {"id": 27, "name": "Twitter token", "description": "Twitter token", + "match": "(?i)(twitter[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-f0-9]{35,44})['\\\"]", + "level": "high"}, + {"id": 28, "name": "Adobe Client ID (Oauth Web)", "description": "Adobe Client ID (Oauth Web)", + "match": "(?i)(adobe[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-f0-9]{32})['\\\"]", + "level": "medium"}, {"id": 29, "name": "Adobe Client Secret", "description": "Adobe Client Secret", + "match": "(p8e-)(?i)[a-z0-9]{32}", "level": "high"}, + {"id": 30, "name": "Alibaba AccessKey ID", "description": "Alibaba AccessKey ID", + "match": "(LTAI5t)(?i)[a-z0-9]{18}", "level": "medium", "lock": True}, + {"id": 31, "name": "Alibaba Secret Key", "description": "Alibaba Secret Key", + "match": "(?i)(alibaba[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{30})['\\\"]", + "level": "high"}, {"id": 32, "name": "Asana Client ID", "description": "Asana Client ID", + "match": "(?i)(asana[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([0-9]{16})['\\\"]", + "level": "medium"}, + {"id": 33, "name": "Asana Client Secret", "description": "Asana Client Secret", + "match": "(?i)(asana[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{32})['\\\"]", + "level": "high"}, {"id": 34, "name": "Atlassian API token", "description": "Atlassian API token", + "match": "(?i)(atlassian[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{24})['\\\"]", + "level": "high"}, + {"id": 35, "name": "Bitbucket client ID", "description": "Bitbucket client ID", + "match": "(?i)(bitbucket[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{32})['\\\"]", + "level": "medium"}, {"id": 36, "name": "Bitbucket client secret", "description": "Bitbucket client secret", + "match": "(?i)(bitbucket[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9_\\-]{64})['\\\"]", + "level": "high"}, + {"id": 37, "name": "Beamer API token", "description": "Beamer API token", + "match": "(?i)(beamer[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"](b_[a-z0-9=_\\-]{44})['\\\"]", + "level": "high"}, {"id": 38, "name": "Clojars API token", "description": "Clojars API token", + "match": "(CLOJARS_)(?i)[a-z0-9]{60}", "level": "high"}, + {"id": 39, "name": "Contentful delivery API token", "description": "Contentful delivery API token", + "match": "(?i)(contentful[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9\\-=_]{43})['\\\"]", + "level": "high"}, + {"id": 40, "name": "Contentful preview API token", "description": "Contentful preview API token", + "match": "(?i)(contentful[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9\\-=_]{43})['\\\"]", + "level": "high"}, {"id": 41, "name": "Databricks API token", "description": "Databricks API token", + "match": "dapi[a-h0-9]{32}", "level": "high"}, + {"id": 42, "name": "Discord API key", "description": "Discord API key", + "match": "(?i)(discord[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{64})['\\\"]", + "level": "high"}, {"id": 43, "name": "Discord client ID", "description": "Discord client ID", + "match": "(?i)(discord[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([0-9]{18})['\\\"]", + "level": "medium"}, + {"id": 44, "name": "Discord client secret", "description": "Discord client secret", + "match": "(?i)(discord[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9=_\\-]{32})['\\\"]", + "level": "high"}, {"id": 45, "name": "Doppler API token", "description": "Doppler API token", + "match": "['\\\"](dp\\.pt\\.)(?i)[a-z0-9]{43}['\\\"]", "level": "high"}, + {"id": 46, "name": "Dropbox API secret/key", "description": "Dropbox API secret/key", + "match": "(?i)(dropbox[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{15})['\\\"]", + "level": "high"}, + {"id": 47, "name": "Dropbox short lived API token", "description": "Dropbox short lived API token", + "match": "(?i)(dropbox[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"](sl\\.[a-z0-9\\-=_]{135})['\\\"]", + "level": "high"}, + {"id": 48, "name": "Dropbox long lived API token", "description": "Dropbox long lived API token", + "match": "(?i)(dropbox[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"][a-z0-9]{11}(AAAAAAAAAA)[a-z0-9\\-_=]{43}['\\\"]", + "level": "high"}, {"id": 49, "name": "Duffel API token", "description": "Duffel API token", + "match": "['\\\"]duffel_(test|live)_(?i)[a-z0-9_-]{43}['\\\"]", "level": "high"}, + {"id": 50, "name": "Dynatrace API token", "description": "Dynatrace API token", + "match": "['\\\"]dt0c01\\.(?i)[a-z0-9]{24}\\.[a-z0-9]{64}['\\\"]", "level": "high"}, + {"id": 51, "name": "EasyPost API token", "description": "EasyPost API token", + "match": "['\\\"]EZAK(?i)[a-z0-9]{54}['\\\"]", "level": "high"}, + {"id": 52, "name": "EasyPost test API token", "description": "EasyPost test API token", + "match": "['\\\"]EZTK(?i)[a-z0-9]{54}['\\\"]", "level": "high"}, + {"id": 53, "name": "Fastly API token", "description": "Fastly API token", + "match": "(?i)(fastly[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9\\-=_]{32})['\\\"]", + "level": "high"}, {"id": 54, "name": "Finicity client secret", "description": "Finicity client secret", + "match": "(?i)(finicity[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{20})['\\\"]", + "level": "high"}, + {"id": 55, "name": "Finicity API token", "description": "Finicity API token", + "match": "(?i)(finicity[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-f0-9]{32})['\\\"]", + "level": "high"}, {"id": 56, "name": "Flutterweave public key", "description": "Flutterweave public key", + "match": "(?i)FLWPUBK_TEST-[a-h0-9]{32}-X", "level": "medium"}, + {"id": 57, "name": "Flutterweave secret key", "description": "Flutterweave secret key", + "match": "(?i)FLWSECK_TEST-[a-h0-9]{32}-X", "level": "high"}, + {"id": 58, "name": "Flutterweave encrypted key", "description": "Flutterweave encrypted key", + "match": "FLWSECK_TEST[a-h0-9]{12}", "level": "high"}, + {"id": 59, "name": "Frame.io API token", "description": "Frame.io API token", + "match": "fio-u-(?i)[a-z0-9-_=]{64}", "level": "high"}, + {"id": 60, "name": "GoCardless API token", "description": "GoCardless API token", + "match": "['\\\"]live_(?i)[a-z0-9-_=]{40}['\\\"]", "level": "high"}, + {"id": 61, "name": "Grafana API token", "description": "Grafana API token", + "match": "['\\\"]eyJrIjoi(?i)[a-z0-9-_=]{72,92}['\\\"]", "level": "high"}, + {"id": 62, "name": "Hashicorp Terraform user/org API token", + "description": "Hashicorp Terraform user/org API token", + "match": "['\\\"](?i)[a-z0-9]{14}\\.atlasv1\\.[a-z0-9-_=]{60,70}['\\\"]", "level": "high"}, + {"id": 63, "name": "Hashicorp Vault batch token", "description": "Hashicorp Vault batch token", + "match": "b\\.AAAAAQ[0-9a-zA-Z_-]{156}", "level": "high"}, + {"id": 64, "name": "Hubspot API token", "description": "Hubspot API token", + "match": "(?i)(hubspot[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\\\"]", + "level": "high"}, {"id": 65, "name": "Intercom API token", "description": "Intercom API token", + "match": "(?i)(intercom[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9=_]{60})['\\\"]", + "level": "high"}, + {"id": 66, "name": "Intercom client secret/ID", "description": "Intercom client secret/ID", + "match": "(?i)(intercom[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\\\"]", + "level": "high"}, + {"id": 67, "name": "Ionic API token", "description": "Ionic API token", "match": "ion_(?i)[a-z0-9]{42}", + "level": "high"}, {"id": 68, "name": "Linear API token", "description": "Linear API token", + "match": "lin_api_(?i)[a-z0-9]{40}", "level": "high"}, + {"id": 69, "name": "Linear client secret/ID", "description": "Linear client secret/ID", + "match": "(?i)(linear[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-f0-9]{32})['\\\"]", + "level": "high"}, {"id": 70, "name": "Lob API Key", "description": "Lob API Key", + "match": "(?i)(lob[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]((live|test)_[a-f0-9]{35})['\\\"]", + "level": "high"}, + {"id": 71, "name": "Lob Publishable API Key", "description": "Lob Publishable API Key", + "match": "(?i)(lob[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]((test|live)_pub_[a-f0-9]{31})['\\\"]", + "level": "high"}, {"id": 72, "name": "Mailchimp API key", "description": "Mailchimp API key", + "match": "(?i)(mailchimp[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-f0-9]{32}-us20)['\\\"]", + "level": "high"}, + {"id": 73, "name": "Mailgun private API token", "description": "Mailgun private API token", + "match": "(?i)(mailgun[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"](key-[a-f0-9]{32})['\\\"]", + "level": "high"}, + {"id": 74, "name": "Mailgun public validation key", "description": "Mailgun public validation key", + "match": "(?i)(mailgun[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"](pubkey-[a-f0-9]{32})['\\\"]", + "level": "high"}, + {"id": 75, "name": "Mailgun webhook signing key", "description": "Mailgun webhook signing key", + "match": "(?i)(mailgun[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{32}-[a-h0-9]{8}-[a-h0-9]{8})['\\\"]", + "level": "high"}, {"id": 76, "name": "Mapbox API token", "description": "Mapbox API token", + "match": "(?i)(pk\\.[a-z0-9]{60}\\.[a-z0-9]{22})", "level": "high"}, + {"id": 77, "name": "messagebird-api-token", "description": "MessageBird API token", + "match": "(?i)(messagebird[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{25})['\\\"]", + "level": "high"}, + {"id": 78, "name": "MessageBird API client ID", "description": "MessageBird API client ID", + "match": "(?i)(messagebird[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-h0-9]{8}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{4}-[a-h0-9]{12})['\\\"]", + "level": "medium"}, {"id": 79, "name": "New Relic user API Key", "description": "New Relic user API Key", + "match": "['\\\"](NRAK-[A-Z0-9]{27})['\\\"]", "level": "high"}, + {"id": 80, "name": "New Relic user API ID", "description": "New Relic user API ID", + "match": "(?i)(newrelic[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([A-Z0-9]{64})['\\\"]", + "level": "medium"}, {"id": 81, "name": "New Relic ingest browser API token", + "description": "New Relic ingest browser API token", + "match": "['\\\"](NRJS-[a-f0-9]{19})['\\\"]", "level": "high"}, + {"id": 82, "name": "npm access token", "description": "npm access token", + "match": "['\\\"](npm_(?i)[a-z0-9]{36})['\\\"]", "level": "high"}, + {"id": 83, "name": "Planetscale password", "description": "Planetscale password", + "match": "pscale_pw_(?i)[a-z0-9\\-_\\.]{43}", "level": "high"}, + {"id": 84, "name": "Planetscale API token", "description": "Planetscale API token", + "match": "pscale_tkn_(?i)[a-z0-9\\-_\\.]{43}", "level": "high"}, + {"id": 85, "name": "Postman API token", "description": "Postman API token", + "match": "PMAK-(?i)[a-f0-9]{24}\\-[a-f0-9]{34}", "level": "high"}, + {"id": 86, "name": "Pulumi API token", "description": "Pulumi API token", "match": "pul-[a-f0-9]{40}", + "level": "high"}, {"id": 87, "name": "Rubygem API token", "description": "Rubygem API token", + "match": "rubygems_[a-f0-9]{48}", "level": "high"}, + {"id": 88, "name": "Sendgrid API token", "description": "Sendgrid API token", + "match": "SG\\.(?i)[a-z0-9_\\-\\.]{66}", "level": "high"}, + {"id": 89, "name": "Sendinblue API token", "description": "Sendinblue API token", + "match": "xkeysib-[a-f0-9]{64}\\-(?i)[a-z0-9]{16}", "level": "high"}, + {"id": 90, "name": "Shippo API token", "description": "Shippo API token", + "match": "shippo_(live|test)_[a-f0-9]{40}", "level": "high"}, + {"id": 91, "name": "Linkedin Client secret", "description": "Linkedin Client secret", + "match": "(?i)(linkedin[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z]{16})['\\\"]", + "level": "high"}, {"id": 92, "name": "Linkedin Client ID", "description": "Linkedin Client ID", + "match": "(?i)(linkedin[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{14})['\\\"]", + "level": "medium"}, + {"id": 93, "name": "Twitch API token", "description": "Twitch API token", + "match": "(?i)(twitch[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}['\\\"]([a-z0-9]{30})['\\\"]", + "level": "high"}, {"id": 94, "name": "Typeform API token", "description": "Typeform API token", + "match": "(?i)(typeform[a-z0-9_ .\\-,]{0,25})(=|>|:=|\\|\\|:|<=|=>|:).{0,5}(tfp_[a-z0-9\\-_\\.=]{59})", + "level": "high"}, + {"id": 95, "name": "Social Security Number", "description": "Social Security Number", + "match": "\\d{3}-\\d{2}-\\d{4}", "level": "low"}, + {"id": 96, "name": "Version Control File", "description": "Version Control File", + "filepath": ".*\\/\\.(git|svn)$", "level": "high"}, + {"id": 97, "name": "Config File", "description": "Config File", "filepath": ".*\\/config\\.ini$", + "level": "medium"}, + {"id": 99, "name": "Desktop Services Store", "description": "Desktop Services Store", + "filepath": " .*\\/\\.DS_Store$", "level": "low"}, + {"id": 100, "name": "MySQL client command history file", "description": "MySQL client command history file", + "filepath": ".*\\/\\.(mysql|psql|irb)_history$", "level": "low"}, + {"id": 101, "name": "Recon-ng web reconnaissance framework API key database", + "description": "Recon-ng web reconnaissance framework API key database", + "filepath": ".*\\/\\.recon-ng\\/keys\\.db$", "level": "medium"}, + {"id": 102, "name": "DBeaver SQL database manager configuration file", + "description": "DBeaver SQL database manager configuration file", + "filepath": ".*\\/\\.dbeaver-data-sources\\.xml$", "level": "low"}, + {"id": 103, "name": "S3cmd configuration file", "description": "S3cmd configuration file", + "filepath": ".*\\/\\.s3cfg$", "level": "low"}, + {"id": 104, "name": "Ruby On Rails secret token configuration file", + "description": "If the Rails secret token is known, it can allow for remote code execution. (http://www.exploit-db.com/exploits/27527/)", + "filepath": ".*\\/secret_token\\.rb$", "level": "high"}, {"id": 105, "name": "OmniAuth configuration file", + "description": "The OmniAuth configuration file might contain client application secrets.", + "filepath": ".*\\/omniauth\\.rb$", + "level": "high"}, + {"id": 106, "name": "Carrierwave configuration file", + "description": "Can contain credentials for online storage systems such as Amazon S3 and Google Storage.", + "filepath": ".*\\/carrierwave\\.rb$", "level": "high"}, + {"id": 107, "name": "Potential Ruby On Rails database configuration file", + "description": "Might contain database credentials.", "filepath": ".*\\/database\\.yml$", "level": "high"}, + {"id": 108, "name": "Django configuration file", + "description": "Might contain database credentials, online storage system credentials, secret keys, etc.", + "filepath": ".*\\/settings\\.py$", "level": "low"}, + {"id": 109, "name": "PHP configuration file", "description": "Might contain credentials and keys.", + "filepath": ".*\\/config(\\.inc)?\\.php$", "level": "low"}, + {"id": 110, "name": "Jenkins publish over SSH plugin file", + "description": "Jenkins publish over SSH plugin file", + "filepath": ".*\\/jenkins\\.plugins\\.publish_over_ssh\\.BapSshPublisherPlugin\\.xml$", "level": "high"}, + {"id": 111, "name": "Potential Jenkins credentials file", + "description": "Potential Jenkins credentials file", "filepath": ".*\\/credentials\\.xml$", + "level": "high"}, {"id": 112, "name": "Apache htpasswd file", "description": "Apache htpasswd file", + "filepath": ".*\\/\\.htpasswd$", "level": "low"}, + {"id": 113, "name": "Configuration file for auto-login process", + "description": "Might contain username and password.", "filepath": ".*\\/\\.(netrc|git-credentials)$", + "level": "high"}, {"id": 114, "name": "Potential MediaWiki configuration file", + "description": "Potential MediaWiki configuration file", + "filepath": ".*\\/LocalSettings\\.php$", "level": "high"}, + {"id": 115, "name": "Rubygems credentials file", + "description": "Might contain API key for a rubygems.org account.", + "filepath": ".*\\/\\.gem\\/credentials$", "level": "high"}, + {"id": 116, "name": "Potential MSBuild publish profile", "description": "Potential MSBuild publish profile", + "filepath": ".*\\/\\.pubxml(\\.user)?$", "level": "low"}, + {"id": 117, "name": "Potential Tencent Accesskey", "description": "Might contain Tencent Accesskey", + "match": "AKID(?i)[a-z0-9]{32}", "level": "high"}, + {"id": 118, "name": "Potential aws Accesskey", "description": "Might contain aws Accesskey", + "match": "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}", "level": "high"}, + {"id": 119, "name": "Potential UCloud Accesskey", "description": "Might contain UCloud Accesskey", + "match": "JDC_[A-z,0-9]{28}", "level": "high"}, + {"id": 120, "name": "JWT TOKEN", "description": "Might JWT Token ", + "match": "ey[0-9a-zA-Z]{30,34}\\.ey[0-9a-zA-Z-\\/_]{30,500}\\.[0-9a-zA-Z-\\/_]{10,200}={0,2}", + "level": "medium"}, {"id": 121, "name": "Google API", "description": "Might Google API Key ", + "match": "AIza[0-9A-Za-z\\-_]{35}", "level": "medium"}, + {"id": 122, "name": "gitlab_pipeline_trigger_token", "description": "GitLab Pipeline Trigger Token", + "match": "glptt-[0-9a-zA-Z_\\-]{20}", "level": "high"}, + {"id": 123, "name": "gitlab_runner_registration_token", "description": "GitLab Runner Registration Token", + "match": "GR1348941[0-9a-zA-Z_\\-]{20}", "level": "high"}, + {"id": 124, "name": "Flutterwave public key", "description": "Flutterwave public key", + "match": "FLWPUBK_TEST-(?i)[a-h0-9]{32}-X", "level": "high"}, + {"id": 125, "name": "Flutterwave secret key", "description": "Flutterwave secret key", + "match": "FLWSECK_TEST-(?i)[a-h0-9]{32}-X", "level": "high"}, + {"id": 126, "name": "Flutterwave encrypted key", "description": "Flutterwave encrypted key", + "match": "FLWSECK_TEST[a-h0-9]{12}", "level": "high"}, + {"id": 127, "name": "github-app-token", "description": "GitHub App Token", + "match": "(ghu|ghs)_[0-9a-zA-Z]{36}", "level": "high"}, + {"id": 128, "name": "github-fine-grained-pat", "description": "GitHub Fine-Grained Personal Access Token", + "match": "github_pat_[0-9a-zA-Z_]{82}", "level": "high"}, + {"id": 129, "name": "grafana-cloud-api-token", "description": "Grafana cloud api token", + "match": "glc_[A-Za-z0-9+/]{32,400}={0,2}", "level": "high"}, + {"id": 130, "name": "grafana-service-account-token", "description": "Grafana service account token", + "match": "glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}", "level": "high"}, + {"id": 131, "name": "prefect-api-token", "description": "Prefect API token", "match": "pnu_[a-z0-9]{36}", + "level": "medium"}]} + + # refs = image.reporefs() + # if len(refs) > 0: + # ref = refs[0] + # else: + # ref = image.id() + # public.print_log("start scan sensitive: " + ref) + + # 检测docker历史(另外有扫描项) + # ocispec = image.ocispec_v1() + # if 'history' in ocispec.keys() and len(ocispec['history']) > 0: + # for history in ocispec['history']: + # command_content = history['created_by'] + # report_rule_list = [] + # for r in rules["rules"]: + # # 正则选择 可以选择docker history的正则是由哪些模块检测 + # for i in ['env', 'match', 'filepath']: + # regexp_s = r.get(i) + # if not regexp_s: + # continue + # if re.search(regexp_s, command_content, re.IGNORECASE): + # self.send_image_ws(get, msg="镜像OCI存在敏感信息", detail=r["description"]+"镜像OCI存在敏感信息{}".format(command_content), repair="使用此镜像可能存在危险,建议更换同类型镜像或是部署容器时清理敏感信息", status=2) + # if not report_rule_list: + # continue + # 检测env环境变量 + # ocispec = image.ocispec_v1() + # if 'config' in ocispec.keys() and 'Env' in ocispec['config'].keys(): + # env_list = image.ocispec_v1()['config']['Env'] + # for env in env_list: + # env_split = env.split("=") + # if len(env_split) >= 2: + # for r in rules["rules"]: + # if "env" in r.keys(): + # env_regex = r["env"] + # if re.match(env_regex, env, re.IGNORECASE): + # self.send_image_ws(get, msg="镜像OCI存在敏感信息", detail=r["description"]+"\nenv存在敏感信息,可能会造成数据泄露", repair="使用该镜像部署容器时,及时修改默认密码或者鉴权值,防止被黑客利用入侵", status=2) + # break + # 排除大型项目 + large_dir = ["usr", "lib"] + sensitive_dirs = [] + try: + for filename in image.listdir("/"): + if filename not in large_dir: + sensitive_dirs.append("/"+filename) + except Exception as e: + public.print_log("Failed to obtain mirror directory{}".format(e)) + sensitive_dirs = ["/"] + + # 检测存在敏感数据的路径 + for sensitive_dir in sensitive_dirs: + for root, dirs, files in image.walk(sensitive_dir): + # 短暂停留0.004s,防止占用太高影响其他接口响应 + time.sleep(0.004) + # 遍历深度不超过3 + if len(root.split("/")) > 3: + self.send_image_ws(get, msg=self.short_string1("Scanning:{}".format(root))) + # public.print_log("跳过{}".format(root)) + continue + for dir in dirs: + try: + dirpath = os.path.join(root, dir) + self.send_image_ws(get, msg="Scanning:{}...".format(dirpath)) + # public.print_log("扫描目录{}".format(dirpath)) + # detect filepath or filename + for r in rules["rules"]: + if "filepath" in r.keys(): + filepath_match_regex = r["filepath"] + if re.match(filepath_match_regex, dirpath): + self.send_image_ws(get, msg="Sensitive directories found{}".format(dirpath), detail="The image exists in a sensitive directory:{}, may be used by attackers to steal sensitive data or source code, leading to further security issues.".format(dirpath), repair="1. Enter the container deployed using the image and delete the directory without affecting the business
                                    2. If it cannot be deleted, restrict access to the directory".format(dirpath), status=2) + break + except Exception as e: + public.print_log("Match sensitive information to catch exceptions{}".format(e)) + for filename in files: + try: + filepath = os.path.join(root, filename) + self.send_image_ws(get, msg="Scanning:{}...".format(filename)) + # public.print_log("扫描文件{}".format(filepath)) + # 跳过白名单 + whitelist = rules["whitelist"] + white_match = False + white_paths = whitelist["paths"] + for wp in white_paths: + if fnmatch.filter([filepath], wp): + white_match = True + break + if white_match: + continue + + try: + # 跳过非常规文件,超过10m + f_stat = image.stat(filepath) + if not S_ISREG(f_stat.st_mode): + continue + if f_stat.st_size > 10 * 1024 * 1024: + continue + + f = image.open(filepath, mode="rb") + f_content_byte = f.read() + except FileNotFoundError as e: + public.print_log("Error while traversing sensitive files:{}".format(e)) + continue + except BaseException as e: + public.print_log("Error while traversing sensitive files:{}".format(e)) + continue + # 检测文件路径及文件名 + match = False + for r in rules["rules"]: + if "filepath" in r.keys(): + filepath_match_regex = r["filepath"] + if re.match(filepath_match_regex, filepath): + match = True + self.send_image_ws(get, msg="Sensitive files found{}".format(filepath), detail=r["description"] + ":
                                    It was found that the image contains sensitive files {}, which may cause leakage.".format(filepath), repair="1. Enter the container deployed using the image, and it is recommended to delete the file if it does not affect the business
                                    2. If it cannot be deleted, restrict the access rights of the file
                                    3. Use a password to protect sensitive files from being easily accessed read", status=2) + break + if match: + continue + # chardet_guess = chardet.detect(f_content_byte[0:64]) + # if chardet_guess["encoding"] != None: + # try: + # f_content = f_content_byte.decode(chardet_guess["encoding"]) + # except: + # continue + # else: + # f_content = str(f_content_byte) + # mime_guess = magic.from_buffer(f_content_byte, mime=True) + # for r in rules["rules"]: + # # mime + # mime_find = False + # if "mime" in r.keys(): + # if r["mime"] == mime_guess: + # mime_find = True + # else: + # if mime_guess.startswith("text/"): + # mime_find = True + # if mime_find: + # if "match" in r.keys(): + # match = r["match"] + # if match.startswith("$contains:"): + # keyword = match.lstrip("$contains:") + # if keyword in f_content: + # file_stat = image.stat(filepath) + # self.send_image_ws(get, msg="镜像路径{}存在敏感信息".format(filepath), + # repair="建议使用该镜像部署容器后,检查该文件是否有用,无用则清理", + # status=2) + # + # else: + # if re.match(match, f_content): + # file_stat = image.stat(filepath) + # self.send_image_ws(get, msg="镜像路径{}存在敏感信息".format(filepath), + # repair="建议使用该镜像部署容器后,检查该文件是否有用,无用则清理", + # status=2) + except Exception as e: + public.print_log("Error while traversing sensitive files:{}".format(e)) + + def scan_backdoor(self, get, image): + """ + @name 扫描后门 + @author lwh<2024-1-23> + """ + backdoor_regex_list1 = [ + # reverse shell + r'''(nc|ncat|netcat)\b.*(-e|--exec|-c)\b.*?\b(ba|da|z|k|c|a|tc|fi|sc)?sh\b''', + r'''python\w*\b.*\bsocket\b.*?\bconnect\b.*?\bsubprocess\b.*?\bsend\b.*?\bstdout\b.*?\bread\b''', + r'''python\w*\b.*\bsocket\b.*?\bconnect\b.*?\bos\.dup2\b.*?\b(call|spawn|popen)\b\s*\([^)]+?\b(ba|da|z|k|c|a|tc|fi|sc)?sh\b''', + r'''(sh|bash|dash|zsh)\b.*-c\b.*?\becho\b.*?\bsocket\b.*?\bwhile\b.*?\bputs\b.*?\bflush\b.*?\|\s*tclsh\b''', + r'''(sh|bash|dash|zsh)\b.*-c\b.*?\btelnet\b.*?\|&?.*?\b(ba|da|z|k|c|a|tc|fi|sc)?sh\b.*?\|&?\s*telnet\b''', + r'''(sh|bash|dash|zsh)\b.*-c\b.*?\bcat\b.*?\|&?.*?\b(ba|da|z|k|c|a|tc|fi|sc)?sh\b.*?\|\s*(nc|ncat)\b''', + r'''(sh|bash|dash|zsh)\b.*sh\s+(-i)?\s*>&?\s*/dev/(tcp|udp)/.*?/\d+\s+0>&\s*(1|2)''', + ] + + def bashrc(): + """ + @name bashrc后门检测 + """ + backdoor_regex_list = [r'''alias\s+ssh=[\'\"]{0,1}strace''', r'''alias\s+sudo='''] + bashrc_dirs = ["/home", "/root"] + for bashrc_dir in bashrc_dirs: + for root, dirs, files in image.walk(bashrc_dir): + for file in files: + if re.match(r'''^\.[\w]*shrc$''', file): + filepath = os.path.join(root, file) + else: + continue + try: + f = image.open(filepath, mode="r") + f_content = f.read() + for backdoor_regex in backdoor_regex_list: + if re.search(backdoor_regex, f_content): + self.send_image_ws(get, msg="Found bashrc backdoor{}".format(filepath), detail="It was found that the image has bashrc backdoor: [{}], malicious code content:
                                    {}".format(filepath, self.short_string(f_content)), repair="1. Enter the container deployed using the image and delete the malicious code under the file
                                    2. Check whether the container has been invaded, and update the access token or account password of the business in the container
                                    3. It is recommended to replace the official image or Other trusted image deployment containers", status=3) + for backdoor_regex in backdoor_regex_list1: + if re.search(backdoor_regex, f_content): + self.send_image_ws(get, msg="Backdoor file found{}".format(filepath), detail="It was found that the bashrc backdoor file [{}] exists in the image, and the malicious code content is:
                                    {}".format(filepath, self.short_string(f_content)), repair="1. Enter the container deployed using the image and delete the malicious code under the file
                                    2. Check whether the container has been invaded, and update the access token or account password of the business in the container
                                    3. It is recommended to replace the official image or Other trusted image deployment containers", status=3) + except FileNotFoundError: + continue + except BaseException as e: + public.print_log(e) + + def crontab(): + """ + @name crontab后门检测 + """ + cron_list = ["/etc/crontab", "/etc/cron.hourly", "/etc/cron.daily", "/etc/cron.weekly", "/etc/cron.monthly", + "/etc/cron.d"] + environment_regex = r'''[a-zA-Z90-9]+\s*=\s*[^\s]+$''' + cron_regex = r'''((\d{1,2}|\*)\s+){5}[a-zA-Z0-9]+\s+(.*)''' + backdoor_regex_list = [ + # download + r'''^(wget|curl)\b''' + # mrig + r'''^([\w0-9]*mrig[\w0-9]*)\b''' + ] + + def detect_crontab_content(cron_f): + """ + @name 检查crontab内容 + """ + result_dict = {} + for line in cron_f.readlines(): + # preprocess + line = line.strip() + line = line.replace("\n", "") + # environment + if re.match(environment_regex, line): continue + m = re.match(cron_regex, line) + if m: + if len(m.groups()) == 3: + cmdline1 = m.group(3) + # for backdoor_regex in backdoor_regex_list: + # if re.search(backdoor_regex, cmdline1): + # result_dict[backdoor_regex] = cmdline1 + for backdoor_regex in backdoor_regex_list1: + if re.search(backdoor_regex, cmdline1): + self.send_image_ws(get, msg="cron backdoor discovered{}".format(filepath), + detail="It was found that the cron backdoor [{}] exists in the image, and the malicious code content is:
                                    {}".format( + filepath, self.short_string(cmdline1)), + repair="1. Enter the container deployed using the image and delete the malicious code under the file
                                    2. Check whether the container has been invaded, and update the access token or account password of the business in the container
                                    3. It is recommended to replace the official image or Other trusted image deployment containers", + status=3) + result_dict[backdoor_regex] = cmdline1 + else: continue + return result_dict + + for cron in cron_list: + try: + # filetype + cron_stat = image.stat(cron) + if S_ISDIR(cron_stat.st_mode): + for root, dirs, files in image.walk(cron): + for file in files: + filepath = os.path.join(root, file) + with image.open(filepath) as f: + result_dict = detect_crontab_content(f) + # if len(result_dict) > 0: + # for regex, cmdline in result_dict.items(): + # self.send_image_ws(get, msg="cron backdoor discovered{}".format(filepath), detail="镜像发现crontab后门:{}".format(filepath), repair="文件命中恶意特征{},建议删除此镜像,并及时排查相关使用该镜像部署的容器,删除文件中的恶意代码".format(cmdline), status=3) + elif S_ISREG(cron_stat.st_mode): + with image.open(cron) as f: + result_dict = detect_crontab_content(f) + # if len(result_dict) > 0: + # for regex, cmdline in result_dict.items(): + # self.send_image_ws(get, msg="cron backdoor discovered{}".format(cron), detail="发现crontab后门:{}".format(cron), + # repair="文件命中恶意特征{},建议删除此镜像,并及时排查相关使用该镜像部署的容器,删除文件中的恶意代码".format( + # cmdline), status=3) + except FileNotFoundError: + continue + + def service(): + """ + @name 服务后门 + """ + service_dir_list = ["/etc/systemd/system"] + for service_dir in service_dir_list: + for root, dirs, files in image.walk(service_dir): + for file in files: + try: + filepath = os.path.join(root, file) + f = image.open(filepath, mode="r") + f_content = f.read() + for backdoor_regex in backdoor_regex_list1: + if re.search(backdoor_regex, f_content): + self.send_image_ws(get, msg="Found system backdoor:{}".format(filepath), + detail="It was found that the systemd backdoor file [{}] exists in the image, and the malicious code content is:
                                    {}".format( + filepath, self.short_string(f_content)), + repair="1. Enter the container deployed using the image and delete the malicious code under the file
                                    2. Check whether the container has been invaded, and update the access token or account password of the business in the container
                                    3. It is recommended to replace the official image or Other trusted image deployment containers", + status=3) + except FileNotFoundError: + continue + except BaseException as e: + public.print_log(e) + + def sshd(): + """ + @name sshd软链接后门检测,支持检测常规软连接后门 + """ + rootok_list = ("su", "chsh", "chfn", "runuser") + sshd_dirs = ["/home", "/root", "/tmp"] + for sshd_dir in sshd_dirs: + for root, dirs, files in image.walk(sshd_dir): + for f in files: + try: + filepath = os.path.join(root, f) + f_lstat = image.lstat(filepath) + if S_ISLNK(f_lstat.st_mode): + f_link = image.evalsymlink(filepath) + f_exename = filepath.split("/")[-1] + f_link_exename = f_link.split("/")[-1] + if f_exename in rootok_list and f_link_exename == "sshd": + self.send_image_ws(get, msg="Found sshd backdoor{}".format(filepath), detail="Found the sshd soft link backdoor: {}, the file hits the malicious feature [exe={};link_file={}]".format(filepath, f_exename, f_link), + repair="1. Enter the container deployed using the image and delete the malicious code under the file
                                    2. Check whether the container has been invaded, and update the access token or account password of the business in the container
                                    3. It is recommended to replace the official image or Other trusted image deployment containers", status=3) + except FileNotFoundError: + continue + except BaseException as e: + public.print_log(e) + + def tcpwrapper(): + """ + @name tcpwrapper后门检测 + """ + wrapper_config_file_list = ['/etc/hosts.allow', '/etc/hosts.deny'] + for config_filepath in wrapper_config_file_list: + try: + with image.open(config_filepath, mode="r") as f: + f_content = f.read() + for backdoor_regex in backdoor_regex_list1: + if re.search(backdoor_regex, f_content): + self.send_image_ws(get, msg="Found the tcpwrapper backdoor{}".format(config_filepath), detail="It was found that the tcpwrapper backdoor file [{}] exists in the image, and the malicious code content is:
                                    {}".format(config_filepath, self.short_string(f_content)), + repair="1. Enter the container deployed using the image and delete the malicious code under the file
                                    2. Check whether the container has been invaded, and update the access token or account password of the business in the container
                                    3. It is recommended to replace the official image or Other trusted image deployment containers", status=3) + except FileNotFoundError: + continue + except BaseException as e: + public.print_log(e) + + # 执行后门检测函数 + bashrc() + crontab() + service() + sshd() + tcpwrapper() + + def scan_privilege_escalation(self, get, image): + """ + @name 提权风险 + @author lwh<2024-01-24> + """ + + def scan_escape(self, get, image): + """ + @name 逃逸风险 + @author lwh<2024-01-24> + """ + def sudoers(): + """ + @name sudo逃逸 + """ + sudo_regex = r"(\w{1,})\s\w{1,}=\(.*\)\s(.*)" + unsafe_sudo_files = ["wget", "find", "cat", "apt", "zip", "xxd", "time", "taskset", "git", "sed", "pip", "tmux", "scp", "perl", "bash", "less", "awk", "man", "vim", "env", "ftp"] + try: + with image.open("/etc/sudoers", mode="r") as f: + lines = f.readlines() + for line in lines: + line = line.strip() + if line.startswith("#"): + continue + matches = re.findall(sudo_regex, line) + if len(matches) == 1: + user, sudo_command = matches[0] + if user.lower() in ["admin", "sudo", "root"]: + continue + for unsafe_sudo_file in unsafe_sudo_files: + if unsafe_sudo_file in sudo_command.lower(): + self.send_image_ws(get, msg="Users found to be at risk of escape{}".format(user), detail="The username {} may complete container escape through the command [{}], allowing the attacker to obtain access rights to the host or other containers. Malicious content:
                                    {}".format(user, sudo_command, line), status=3, repair="1.建议删除镜像或不再使用
                                    2.若已有业务使用该镜像,则进入容器环境,删除/etc/sudoers文件内包含用户{}的内容".format(user)) + break + except Exception as e: + # public.print_log(e) + return + # 开始检测 + sudoers() + + def scan_log4j2(self, get, image): + """ + @name 扫描是否存在log4j漏洞 + @author lwh<2024-01-26> + """ + +def veinmind(): + from veinmind import docker + client = docker.Docker() + ids = client.list_image_ids() + # public.print_log(ids) + for id in ids: + image = client.open_image_by_id(id) + # public.print_log("image id: " + image.id()) + for ref in image.reporefs(): + public.print_log("image ref: " + ref) + for repo in image.repos(): + public.print_log("image repo: " + repo) + # public.print_log("image ocispec: " + str(image.ocispec_v1())) + + +if __name__ == '__main__': + # obj = main() + # get = public.dict_obj() + # obj.get_safe_scan(get=get) + # sudo_regex = r"(\w{1,})\s\w{1,}=\(.*\)\s(.*)" + # unsafe_sudo_files = ["wget", "find", "cat", "apt", "zip", "xxd", "time", "taskset", "git", "sed", "pip", "ed", + # "tmux", "scp", "perl", "bash", "less", "awk", "man", "vi", "vim", "env", "ftp", "all"] + # try: + # with open("/tmp/sudoers.test", "r") as f: + # lines = f.readlines() + # for line in lines: + # line = line.strip() + # if line.startswith("#"): + # continue + # matches = re.findall(sudo_regex, line) + # if len(matches) == 1: + # user, sudo_file = matches[0] + # if user.lower() in ["admin", "sudo", "root"]: + # continue + # print(user.lower(), sudo_file.lower()) + # for unsafe_sudo_file in unsafe_sudo_files: + # if unsafe_sudo_file in sudo_file.lower(): + # print("用户有问题:{}".format(user)) + # break + # except Exception as e: + # print(e) + # 初始化安装检测SDK + try: + from veinmind import docker + except Exception as e: + # public.print_log("Importing veinmind failed:{}".format(e)) + requirements_list = ["veinmind"] + shell_command = "btpip install --no-dependencies {}".format(" ".join(requirements_list)) + public.ExecShell(shell_command) + sys_ver = public.get_os_version() + # self.send_image_ws(get, msg="正在初始化检测引擎中,首次加载耗时较长...", status=1) + if "Ubuntu" in sys_ver or "Debian" in sys_ver: + public.WriteFile("/etc/apt/sources.list.d/libveinmind.list", + "deb [trusted=yes] https://download.veinmind.tech/libveinmind/apt/ ./") + # public.print_log("Updating apt-get") + public.ExecShell("apt-get update") + time.sleep(1) + public.ExecShell("apt-get install -y libveinmind-dev") + time.sleep(1) + elif "CentOS" in sys_ver: + public.WriteFile("/etc/yum.repos.d/libveinmind.repo", """[libveinmind] +name=libVeinMind SDK yum repository +baseurl=https://download.veinmind.tech/libveinmind/yum/ +enabled=1 +gpgcheck=0""") + public.ExecShell("yum makecache", timeout=10) + public.ExecShell("yum install -y libveinmind-devel", timeout=10) + else: + pass + # public.print_log("不支持的系统版本") + # return public.returnMsg(False, "不支持的系统版本") + # self.send_image_ws(get, msg="正在检查依赖库是否存在...", status=1) + result, err = public.ExecShell("whereis libdl.so") + result = result.strip().split(" ") + # public.print_log("libdl.so库的情况:{}".format(result)) + if len(result) <= 1: + # public.print_log("缺少libdl.so库") + result, err = public.ExecShell("whereis libdl.so.2") + result = result.strip().split(" ") + # public.print_log("libdl.so.2库的情况:{}".format(result)) + if len(result) <= 1: + # public.print_log("缺少libdl.so库,需要安装libdl.so或libdl.so2") + public.returnMsg(False, "The libdl.so library is missing, you need to install libdl.so or libdl.so2") + else: + # 建立libdl.so软链接至libdl.so.2 + for lib in result[1:]: + ln_command = "ln -s {} {}".format(lib, lib[:-2]) + public.print_log("Soft link in progress:{}".format(ln_command)) + public.ExecShell(ln_command) + from veinmind import docker + # public.print_log("执行成功") + + diff --git a/class_v2/btdockerModelV2/setupModel.py b/class_v2/btdockerModelV2/setupModel.py new file mode 100644 index 00000000..a46d3b5b --- /dev/null +++ b/class_v2/btdockerModelV2/setupModel.py @@ -0,0 +1,686 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# Docker模型 +# ------------------------------ +import gettext +_ = gettext.gettext + +import json +import os + +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + + + +class main(dockerBase): + def get_config(self, get): + """ + 获取设置配置信息 + @param get: + @return: + """ + check_docker_compose = self.check_docker_compose_service() + try: + installing = public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count() + if not installing: + installing = public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count() + except: + installing = 0 + + # if not os.path.exists("/www/server/panel/data/db/docker.db"): + # public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db") + + if not os.path.exists("/www/server/panel/data/docker.db"): + public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db") + + service_status = self.get_service_status() + if not service_status: + service_status = self.get_service_status() + + data = { + "service_status": service_status, + "docker_installed": self.check_docker_service(), + "docker_compose_installed": check_docker_compose[0], + "docker_compose_path": check_docker_compose[1], + "monitor_status": self.get_monitor_status(), + "monitor_save_date": dp.docker_conf()['SAVE'], + "daemon_path": "/etc/docker/daemon.json", + "installing": installing, + } + return public.return_message(0, 0, data) + + @staticmethod + def _get_com_registry_mirrors(): + """ + 获取常用加速配置 + @return: + """ + com_reg_mirror_file = "{}/class_v2/btdockerModelV2/config/com_reg_mirror.json".format(public.get_panel_path()) + try: + com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) + except: + com_reg_mirror = { + "https://docker.m.daocloud.io": "Third party image accelerator", + } + + return com_reg_mirror + + def set_monitor_save_date(self, get): + """ + :param save_date: int 例如30 表示 30天 + :param get: + :return: + """ + # 校验参数 + try: + get.validate([ + Param('save_date').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import re + conf_path = "{}/data/docker.conf".format(public.get_panel_path()) + docker_conf = public.readFile(conf_path) + try: + save_date = int(get.save_date) + except: + return public.return_message(-1, 0, _( "The monitoring save time needs to be a positive integer!")) + if save_date > 999: + return public.return_message(-1, 0, _( "Monitoring data cannot be retained for more than 999 days!")) + if not docker_conf: + docker_conf = "SAVE={}".format(save_date) + public.writeFile(conf_path, docker_conf) + return public.return_message(0, 0, _( "Successfully set!")) + docker_conf = re.sub(r"SAVE\s*=\s*\d+", "SAVE={}".format(save_date), + docker_conf) + public.writeFile(conf_path, docker_conf) + dp.write_log("et the monitoring time to [{}] days!".format(save_date)) + return public.return_message(0, 0, _( "Successfully set!")) + + def get_service_status(self): + sock = '/var/run/docker.pid' + if os.path.exists(sock): + try: + client = dp.docker_client() + if client: + return True + return False + except: + return False + else: + return False + + # docker服务状态设置 + def docker_service(self, get): + """ + :param act start/stop/restart + :param get: + :return: + """ + + import public + act_dict = {'start': 'start', 'stop': 'stop', 'restart': 'restart'} + if get.act not in act_dict: + return public.return_message(-1, 0, _( "There's no way to do that")) + exec_str = 'systemctl {} docker'.format(get.act) + if get.act == "stop": + exec_str += ";systemctl {} docker.socket".format(get.act) + stdout, stderr = public.ExecShell(exec_str) + if stderr and not "but it can still be activated by:\n docker.socket\n" in stderr: + dp.write_log("Setting the Docker service status to [{}] failed, failure reason:{}".format(act_dict[get.act], stderr)) + + jou_stdout, jou_stderr = public.ExecShell("journalctl -xe -u docker -n 100 --no-pager|grep libusranalyse.so") + if jou_stdout != "": + return public.return_message(-1, 0, _("Docker service setup failed, please turn off aapanel anti-intrusion and try again!")) + + return public.return_message(-1, 0, _("Setup failed! Reason for failure:{}".format(stderr))) + + if get.act != "stop": + service_status = self.get_service_status() + if not service_status: + import time + public.ExecShell("systemctl stop docker") + public.ExecShell("systemctl stop docker.socket") + time.sleep(1) + public.ExecShell("systemctl start docker") + + dp.write_log("Set the Docker service status to [{}]".format(act_dict[get.act])) + return public.return_message(0, 0, _("{} success".format(act_dict[get.act]))) + + # 获取加速配置 + def get_registry_mirrors(self, get): + """ + 获取镜像加速信息 + @param get: + @return: + """ + try: + if not os.path.exists('/etc/docker/daemon.json'): + reg_mirrors = [] + else: + conf = json.loads(public.readFile('/etc/docker/daemon.json')) + if "registry-mirrors" not in conf: + reg_mirrors = [] + else: + reg_mirrors = conf['registry-mirrors'] + except: + reg_mirrors = [] + + com_reg_mirrors = self._get_com_registry_mirrors() + + # return { + # "registry_mirrors": reg_mirrors, + # "com_reg_mirrors": com_reg_mirrors + # } + + data = { + "registry_mirrors": reg_mirrors, + "com_reg_mirrors": com_reg_mirrors + } + return public.return_message(0, 0, data) + + # 设置加速配置 + def set_registry_mirrors(self, get): + """ + :param registry_mirrors_address registry.docker-cn.com\nhub-mirror.c.163.com + :param get: + :return: + """ + # {"registry_mirrors_address": "https://wzz1sdf11nb.com", "remarks": ""} + # 校验参数 + try: + get.validate([ + Param('registry_mirrors_address').Require().String(), + Param('remarks').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not os.path.exists('/etc/docker/'): + os.makedirs('/etc/docker', 755, True) + + import re + try: + get.registry_mirrors_address = get.get("registry_mirrors_address/s", "") + conf = {} + if os.path.exists('/etc/docker/daemon.json'): + try: + conf = json.loads(public.readFile('/etc/docker/daemon.json')) + except Exception as e: + return public.return_message(-1, 0, _( "Global configuration file error, please check {}!".format(str(e)))) + + if not get.registry_mirrors_address.strip(): + if "registry-mirrors" in conf: + del (conf['registry-mirrors']) + else: + registry_mirrors = get.registry_mirrors_address.strip() + if registry_mirrors == "": + # 2024/4/16 下午12:10 双重保险 + if 'registry-mirrors' in conf: + del (conf['registry-mirrors']) + else: + if not re.search('https?://', registry_mirrors): + return public.return_message(-1, 0, _( 'Speedup address [{}] Format error
                                    Reference: https://mirror.ccs.tencentyun.com'.format(registry_mirrors))) + + conf['registry-mirrors'] = public.xsssec2(registry_mirrors) + if isinstance(conf['registry-mirrors'], str): + conf['registry-mirrors'] = [conf['registry-mirrors']] + + public.writeFile('/etc/docker/daemon.json', json.dumps(conf, indent=2)) + if get.registry_mirrors_address != "": + self.update_com_registry_mirrors(get) + + dp.write_log("Setup Docker acceleration successful!") + return public.return_message(0, 0, _( 'successfully set')) + + except: + return public.return_message(-1, 0, _('Setup failed! Failure reason :{}'.format(public.get_error_info()))) + + def update_com_registry_mirrors(self, get): + """ + 更新常用加速配置 + @param get: + @return: + """ + import time + com_reg_mirror_file = "{}/class_v2/btdockerModelV2/config/com_reg_mirror.json".format(public.get_panel_path()) + try: + com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) + except: + com_reg_mirror = { + "https://docker.m.daocloud.io": "Third party image accelerator", + } + + if get.registry_mirrors_address in com_reg_mirror: + return public.return_message(0, 0, _( "Successfully set!")) + + remarks = get.remarks if "remarks" in get and get.remarks != "" else ("Custom mirrors" + str(int(time.time()))) + + com_reg_mirror.update({"{}".format(get.registry_mirrors_address): remarks}) + public.writeFile(com_reg_mirror_file, json.dumps(com_reg_mirror, indent=2)) + dp.write_log("Updated common acceleration configuration successfully!") + return public.return_message(0, 0, _( "Update successfully!")) + + def del_com_registry_mirror(self, get): + """ + 删除常用加速配置 + @param get: + @return: + """ + com_reg_mirror_file = "{}/class_v2/btdockerModelV2/config/com_reg_mirror.json".format(public.get_panel_path()) + try: + com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file)) + except: + com_reg_mirror = { + "https://docker.m.daocloud.io": "Third party image accelerator", + } + + if get.registry_mirrors_address not in com_reg_mirror: + return public.return_message(0, 0, _( "successfully delete!")) + + del com_reg_mirror["{}".format(get.registry_mirrors_address)] + public.writeFile(com_reg_mirror_file, json.dumps(com_reg_mirror, indent=2)) + dp.write_log("Remove common acceleration configuration successfully!") + return public.return_message(0, 0, _( "successfully delete!")) + + def get_monitor_status(self): + """ + 获取docker监控状态 + @return: + """ + try: + from BTPanel import cache + except: + from cachelib import SimpleCache + cache = SimpleCache() + + skey = "docker_monitor_status" + result = cache.get(skey) + if isinstance(result, bool): + return result + + import psutil + is_monitor = False + for proc in psutil.process_iter(): + try: + pinfo = proc.as_dict(attrs=['pid', 'name']) + if "monitorModel.py" in pinfo['name']: + is_monitor = True + except psutil.NoSuchProcess: + pass + cache.set(skey, is_monitor, 86400) + return is_monitor + + def set_docker_monitor(self, get): + """ + 开启docker监控获取docker相取资源信息 + :param act: start/stop + :return: + """ + # 校验参数 + try: + get.validate([ + Param('act').Require().String('in', ['start', 'stop']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import time + python = "/www/server/panel/pyenv/bin/python" + if not os.path.exists(python): + python = "/www/server/panel/pyenv/bin/python3" + cmd_line = "/www/server/panel/class_v2/btdockerModelV2/monitorModel.py" + if get.act == "start": + self.stop_monitor(get) + if not os.path.exists(self.moinitor_lock): + public.writeFile(self.moinitor_lock, "1") + + shell = "nohup {} {} &".format(python, cmd_line) + public.ExecShell(shell) + time.sleep(1) + if self.get_monitor_status(): + dp.write_log("Docker started monitoring successfully!") + self.add_monitor_cron(get) + return public.return_message(0, 0, _( "Start monitoring successfully!")) + return public.return_message(-1, 0, _( "Failed to start monitoring!")) + else: + from BTPanel import cache + skey = "docker_monitor_status" + cache.set(skey, False) + + if os.path.exists(self.moinitor_lock): + os.remove(self.moinitor_lock) + + self.stop_monitor(get) + return public.return_message(0, 0, _( "Docker monitoring stopped successfully!")) + + # 2024/1/4 上午 9:32 停止容器监控进程 + def stop_monitor(self, get): + ''' + @name 名称/描述 + @param 参数名<数据类型> 参数描述 + @return 数据类型 + ''' + cmd_line = [ + "/www/server/panel/class_v2/btdockerModelV2/monitorModel.py", + "/www/server/panel/class/projectModel/bt_docker/dk_monitor.py" + ] + + for cmd in cmd_line: + in_pid = True + sum = 0 + while in_pid: + in_pid = False + pid = dp.get_process_id( + "python", + "{}".format(cmd)) + if pid: + in_pid = True + + if not pid: + pid = dp.get_process_id( + "python3", + "{}".format(cmd) + ) + if pid: + in_pid = True + public.ExecShell("kill -9 {}".format(pid)) + sum += 1 + if sum > 100: + break + + import os + + # 指定目录路径 + directory = "/www/server/cron/" + if not os.path.exists(directory): + os.makedirs(directory) + + # 遍历目录下的所有非.log结尾的文件 + for filename in os.listdir(directory): + if not filename.endswith(".log"): + filepath = os.path.join(directory, filename) + if os.path.isdir(filepath): + continue + # 检查文件内容是否包含 "monitorModel.py" + with open(filepath, 'r') as file: + content = file.read() + if "monitorModel.py" in content or "dk_monitor.py" in content: + # 删除原文件和对应的.log文件 + if os.path.exists(filepath): + os.remove(filepath) + if os.path.exists(os.path.join(directory, "{}.log".format(filename))): + os.remove(os.path.join(directory, "{}.log".format(filename))) + public.ExecShell("crontab -l | sed '/{}/d' | crontab -".format(filename)) + + dp.write_log("Docker monitoring stopped successfully!") + + public.M('crontab').where('name=?', ("[Do not delete] docker monitoring daemon",)).delete() + return public.returnMsg(True, "Docker monitoring stopped successfully!") + + # 2023/12/7 下午 6:24 创建计划任务,监听监控进程是否存在,如果不存在则添加 + def add_monitor_cron(self, get): + ''' + @name 名称/描述 + @author wzz <2023/12/7 下午 6:24> + @param 参数名<数据类型> 参数描述 + @return 数据类型 + ''' + try: + import crontab + if public.M('crontab').where('name', ("[Do not delete] docker monitoring daemon",)).count() == 0: + p = crontab.crontab() + llist = p.GetCrontab(None) + + if type(llist) == list: + for i in llist: + if i['name'] == '[Do not delete] docker monitoring daemon': + return + + get = { + "name": "[Do not delete] docker monitoring daemon", + "type": "minute-n", + "where1": 5, + "hour": "", + "minute": "", + "week": "", + "sType": "toShell", + "sName": "", + "backupTo": "localhost", + "save": '', + "sBody": """ +if [ -f {} ]; then + new_mt=`ps aux|grep monitorModel.py|grep -v grep` + old_mt=`ps aux|grep dk_monitor.py|grep -v grep` + + if [ -z "$new_mt" ] && [ -z "$old_mt" ]; then + nohup /www/server/panel/pyenv/bin/python /www/server/panel/class_v2/btdockerModelV2/monitorModel.py & + fi +fi + """.format(self.moinitor_lock), + "urladdress": "undefined" + } + p.AddCrontab(get) + except Exception as e: + return False + + def check_docker_compose_service(self): + """ + 检查docker-compose是否已经安装 + :return: + """ + docker_compose = "/usr/bin/docker-compose" + + docker_compose_path = "{}/class_v2/btdockerModelV2/config/docker_compose_path.pl".format(public.get_panel_path()) + if os.path.exists(docker_compose_path): + docker_compose = public.readFile(docker_compose_path).strip() + + if not os.path.exists(docker_compose): + # public.print_log("mwmwmwmwm 没文件") + dk_compose_list = ["/usr/libexec/docker/cli-plugins/docker-compose", "/usr/local/docker-compose"] + for i in dk_compose_list: + if os.path.exists(i): + public.ExecShell("ln -sf {} {}".format(i, "/usr/bin/docker-compose")) + break + + if not os.path.exists(docker_compose): + return False, "" + + return True, docker_compose + + def check_docker_service(self): + """ + 检查docker是否安装 + @return: + """ + docker = "/usr/bin/docker" + if not os.path.exists(docker): + return False + return True + + def set_docker_compose_path(self, get): + """ + 设置docker-compose的路径 + @param get: + @return: + """ + docker_compose_file = get.docker_compose_path if "docker_compose_path" in get else "" + if docker_compose_file == "": + return public.return_message(-1, 0, _( "docker-compose file path cannot be empty!")) + + if not os.path.exists(docker_compose_file): + return public.return_message(-1, 0, _( "docker-compose file does not exist!")) + + public.ExecShell("chmod +x {}".format(docker_compose_file)) + cmd_result = public.ExecShell("{} --version".format(docker_compose_file)) + if not cmd_result[0]: + return public.return_message(-1, 0, _( "docker-compose file is not executable or is not a docker-compose file!")) + + docker_compose_path = "{}/class_v2/btdockerModelV2/config/docker_compose_path.pl".format(public.get_panel_path()) + + public.writeFile(docker_compose_path, docker_compose_file) + dp.write_log("Set docker-compose path successfully!") + return public.return_message(0, 0, _( "Successfully set!")) + + def install_docker_program(self, get): + """ + 安装docker和docker-compose + :param get: + :return: + """ + import time + url = get.get("url/s", "") + type = get.get("type/d", 0) + + # 2024/3/28 上午 10:36 检测是否已存在安装任务 + if public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count(): + return public.return_message(-1, 0, _( "The installation task already exists, please do not add it again!")) + + mmsg = "Install Docker Service" + if type == 0 and url == "": + # 默认安装 + execstr = ("wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && " + "bash /tmp/docker_install.sh install ").format(public.get_url()) + elif type == 0 and url != "": + # 选择镜像源安装 + execstr = ("wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && " + "bash /tmp/docker_install.sh install {} ").format(public.get_url(), url.strip('"')) + else: + # 二进制安装 + execstr = "/bin/bash /www/server/panel/install/install_soft.sh 0 install docker_bin " + + public.M('tasks').add('id,name,type,status,addtime,execstr', + (None, mmsg, 'execshell', '0', + time.strftime('%Y-%m-%d %H:%M:%S'), execstr)) + public.httpPost( + public.GetConfigValue('home') + '/api/panel/plugin_total', { + "pid": "1111111", + 'p_name': "Docker commercial module" + }, 3) + return public.return_message(0, 0, _( "The installation task has been added to the queue!")) + + def repair_docker(self, get): + """ + 修复docker + @param get: + @return: + """ + import time + mmsg = "Repair Docker service" + execstr = "curl -fsSL https://get.docker.com -o /tmp/get-docker.sh && sed -i '/sleep 20/d' /tmp/get-docker.sh && /bin/bash /tmp/get-docker.sh" + public.M('tasks').add('id,name,type,status,addtime,execstr', + (None, mmsg, 'execshell', '0', + time.strftime('%Y-%m-%d %H:%M:%S'), execstr)) + public.httpPost( + public.GetConfigValue('home') + '/api/panel/plugin_total', { + "pid": "1111111", + 'p_name': "Docker commercial module" + }, 3) + return public.return_message(0, 0, _( "The repair task has been added to the queue!")) + + def get_daemon_json(self, get): + """ + 获取daemon.json配置信息 + @param get: + @return: + """ + daemon_json = "/etc/docker/daemon.json" + if not os.path.exists(daemon_json): + return public.return_message(0, 0, "") + + try: + return public.return_message(0, 0, json.loads(public.readFile(daemon_json))) + except Exception as e: + print(e) + return public.return_message(-1, 0, "") + + def save_daemon_json(self, get): + """ + 保存daemon.json配置信息,保存前备份,验证可以成功执行后再替换 + @param get: + @return: + """ + daemon_json = "/etc/docker/daemon.json" + if getattr(get, "daemon_json", "") == "": + public.ExecShell("rm -f {}".format(daemon_json)) + return public.return_message(0, 0, _( "Saved successfully!")) + + try: + conf = json.loads(get.daemon_json) + public.writeFile(daemon_json, json.dumps(conf, indent=2)) + dp.write_log("Save daemon.json configuration successfully!") + return public.return_message(0, 0, _( "Saved successfully!")) + except Exception as e: + public.print_log("err: {}".format(e)) + if "Expecting property name enclosed in double quotes" in str(e): + return public.return_message(-1, 0, _( "Saving failed, reason: daemon.json configuration file format error!")) + + return public.return_message(-1, 0, _( "Save failed, reason: {}".format(e))) + def uninstall_status(self, get): + """ + 检测docker是否可以卸载 + :param get: + :return: + """ + from btdockerModelV2 import containerModel + docker_list = containerModel.main().get_list(get) + from btdockerModelV2 import imageModel + images_list = imageModel.main().image_list(get) + if len(images_list) > 0 or len(docker_list["container_list"]) > 0: + return public.return_message(0, 0, {"status": False, + "msg": "Please manually delete all containers and images before uninstalling!"}) + return public.return_message(0, 0, "Allow uninstallation") + + def uninstall_status1(self, get): + """ + 检测docker是否可以卸载 + :param get: + :return: + """ + from btdockerModelV2 import containerModel + docker_list = containerModel.main().get_list(get) + from btdockerModelV2 import imageModel + images_list = imageModel.main().image_list(get) + if len(images_list) > 0 or len(docker_list["container_list"]) > 0: + return False + return True + def uninstall_docker_program(self, get): + """ + 卸载docker和docker-compose + :param get: + :return: + """ + type = get.get("type/d", 0) + if type == 0: + uninstall_status = self.uninstall_status1(get) + if not uninstall_status["status"]: + return public.return_message(-1, 0, _( "Please manually delete all containers and images before uninstalling!")) + + public.ExecShell( + "wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && bash /tmp/docker_install.sh uninstall" + .format(public.get_url() + )) + public.ExecShell("rm -rf /usr/bin/docker-compose") + + return public.return_message(0, 0, "Uninstall successfully!") diff --git a/class_v2/btdockerModelV2/statusModel.py b/class_v2/btdockerModelV2/statusModel.py new file mode 100644 index 00000000..08ba8c3e --- /dev/null +++ b/class_v2/btdockerModelV2/statusModel.py @@ -0,0 +1,258 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import gettext +_ = gettext.gettext + +import time + +# ------------------------------ +# Docker模型 +# ------------------------------ +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + __stats_tmp = dict() + __docker = None + + def docker_client(self, url): + if not self.__docker: + self.__docker = dp.docker_client(url) + return self.__docker + + def io_stats(self, stats, write=None): + drive_io = stats['blkio_stats']['io_service_bytes_recursive'] + if drive_io: + if len(drive_io) <= 2: + try: + now = drive_io[0]['value'] + self.__stats_tmp['read_total'] = now + except: + self.__stats_tmp['read_total'] = 0 + try: + now = drive_io[1]['value'] + self.__stats_tmp['write_total'] = now + except: + self.__stats_tmp['write_total'] = 0 + else: + try: + now = drive_io[0]['value'] + drive_io[2]['value'] + self.__stats_tmp['read_total'] = now + except: + self.__stats_tmp['read_total'] = 0 + try: + now = drive_io[1]['value'] + drive_io[3]['value'] + self.__stats_tmp['write_total'] = now + except: + self.__stats_tmp['write_total'] = 0 + if write: + self.__stats_tmp['container_id'] = stats['id'] + self.write_io(self.__stats_tmp) + + def net_stats(self, stats, cache, write=None): + try: + net_io = stats['networks']['eth0'] + net_io_old = cache['networks']['eth0'] + except: + self.__stats_tmp['rx_total'] = 0 + self.__stats_tmp['rx'] = 0 + self.__stats_tmp['tx_total'] = 0 + self.__stats_tmp['tx'] = 0 + if write: + self.__stats_tmp['container_id'] = stats['id'] + self.write_net(self.__stats_tmp) + return + time_now = stats["time"] + time_old = cache["time"] + try: + now = net_io["rx_bytes"] + self.__stats_tmp['rx_total'] = now + old = net_io_old["rx_bytes"] + self.__stats_tmp['rx'] = int((now - old) / (time_now - time_old)) + except: + self.__stats_tmp['rx_total'] = 0 + self.__stats_tmp['rx'] = 0 + try: + now = net_io["tx_bytes"] + old = net_io_old["tx_bytes"] + self.__stats_tmp['tx_total'] = now + self.__stats_tmp['tx'] = int((now - old) / (time_now - time_old)) + except: + self.__stats_tmp['tx_total'] = 0 + self.__stats_tmp['tx'] = 0 + if write: + self.__stats_tmp['container_id'] = stats['id'] + self.write_net(self.__stats_tmp) + # return data + + def mem_stats(self, stats, write=None): + mem = stats['memory_stats'] + try: + self.__stats_tmp['limit'] = mem['limit'] + self.__stats_tmp['usage_total'] = mem['usage'] + if 'cache' not in mem['stats']: + mem['stats']['cache'] = 0 + self.__stats_tmp['usage'] = mem['usage'] - mem['stats']['cache'] + self.__stats_tmp['cache'] = mem['stats']['cache'] + # data['mem_useage'] = round(mem['usage'] * 100 / data['limit'],2) + except: + # return public.get_error_info() + self.__stats_tmp['limit'] = 0 + self.__stats_tmp['usage'] = 0 + self.__stats_tmp['cache'] = 0 + self.__stats_tmp['usage_total'] = 0 + # data['mem_useage'] = 0 + if write: + self.__stats_tmp['container_id'] = stats['id'] + self.write_mem(self.__stats_tmp) + # return data + + def cpu_stats(self, stats, write=None): + # cpu_limit = dp.sql('container').where("c_id=?",(stats['id'],)).find() + # if cpu_limit: + # cpu_limit = cpu_limit['cpu_limit'] + # else: + # cpu_limit = 1 + try: + cpu = stats['cpu_stats']['cpu_usage']['total_usage'] - stats[ + 'precpu_stats']['cpu_usage']['total_usage'] + except: + cpu = 0 + try: + system = stats['cpu_stats']['system_cpu_usage'] - stats[ + 'precpu_stats']['system_cpu_usage'] + except: + system = 0 + try: + self.__stats_tmp['online_cpus'] = stats['cpu_stats']['online_cpus'] + except: + self.__stats_tmp['online_cpus'] = 0 + if cpu > 0 and system > 0: + self.__stats_tmp['cpu_usage'] = round( + (cpu / system) * 100 * self.__stats_tmp['online_cpus'], 2) + else: + self.__stats_tmp['cpu_usage'] = 0.0 + if write: + self.__stats_tmp['container_id'] = stats['id'] + self.write_cpu(self.__stats_tmp) + # return data + + def stats(self, args): + """ + 获取某个容器的cpu,内存,网络io,磁盘io. + :param url + :param id + :param args: + :return: + """ + # {"id": "d58097084d43324643efde5cc8d30643901c27366d35238801a2119509352ab7", "dk_status": "running"} + + try: + args.validate([ + Param('id').Require().String(), + Param('dk_status').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, ex) + try: + container = self.docker_client(self._url).containers.get(args.id) + stats = container.stats(decode=None, stream=False) + stats['time'] = time.time() + cache = public.cache_get('stats') + if not cache: + cache = stats + public.cache_set('stats', stats) + write = None + if hasattr(args, "write"): + write = args.write + self.__stats_tmp['expired'] = time.time() - (args.save_date * 86400) + stats['id'] = args.id + import json + from pygments import highlight, lexers, formatters + formatted_json = json.dumps(stats, indent=3) + colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), formatters.TerminalFormatter()) + print(colorful_json) + self.io_stats(stats, write) + self.net_stats(stats, cache, write) + self.cpu_stats(stats, write) + self.mem_stats(stats, write) + public.cache_set('stats', stats) + self.__stats_tmp['detail'] = stats + if 'dk_status' in args and args.dk_status != 'running': + self.__stats_tmp['read_total'] = 0 + self.__stats_tmp['write_total'] = 0 + return public.return_message(0, 0, self.__stats_tmp) + except Exception as ex: + if "No such container" in str(ex): + return public.return_message(-1, 0, _('The container does not exist, please refresh the browser and try again!')) + return public.return_message(-1, 0, _('Failed to get container status: ' + str(ex))) + + def top(self, get): + """ + 获取容器内进程信息 + @param get: + @return: + """ + container = self.docker_client(self._url).containers.get(get.id) + return public.return_message(0, 0, container.top()) + + def write_cpu(self, data): + pdata = { + "time": time.time(), + "cpu_usage": data['cpu_usage'], + "online_cpus": data['online_cpus'], + "container_id": data['container_id'] + } + dp.sql("cpu_stats").where("time +# ------------------------------------------------------------------- +import gettext +_ = gettext.gettext + +# ------------------------------ +# Docker模型 +# ------------------------------ +import docker.errors +import public +from btdockerModelV2 import dk_public as dp +from btdockerModelV2.dockerBase import dockerBase +from public.validate import Param + +class main(dockerBase): + + def docker_client(self, url): + return dp.docker_client(url) + + def get_volume_container_name(self, volume_detail, container_list): + ''' + 拼接对应的容器名与卷名 + @param volume_detail: 卷字典 + @param container_list: 容器详情列表 + @return: + ''' + try: + for container in container_list: + if not container['Mounts']: + continue + for mount in container['Mounts']: + if "Name" not in mount: + continue + if volume_detail['Name'] == mount['Name']: + volume_detail['container'] = container['Names'][0].replace("/", "") + if 'container' not in volume_detail: + volume_detail['container'] = '' + except: + volume_detail['container'] = '' + + return volume_detail + + def get_volume_list(self, args): + """ + :param self._url: 链接docker的URL + :return: + """ + try: + data = list() + from btdockerModelV2.dockerSock import volume + sk_volume = volume.dockerVolume() + volume_list = sk_volume.get_volumes() + + from btdockerModelV2.dockerSock import container + sk_container = container.dockerContainer() + container_list = sk_container.get_container() + + if "Volumes" in volume_list and type(volume_list["Volumes"]) == list: + for v in volume_list["Volumes"]: + data.append(self.get_volume_container_name(v, container_list)) + + return public.return_message(0, 0, sorted(data, key=lambda x: x['CreatedAt'], reverse=True)) + else: + return public.return_message(0, 0, []) + except Exception as e: + return public.return_message(-1, 0, []) + + def add(self, args): + """ + 添加一个卷 + :param name + :param driver local + :param driver_opts (dict) – Driver options as a key-value dictionary + :param labels str + :return: + """ + try: + args.driver_opts = args.get("driver_opts", "") + args.labels = args.get("labels", "") + if args.driver_opts != "": + args.driver_opts = dp.set_kv(args.driver_opts) + if args.labels != "": + args.labels = dp.set_kv(args.labels) + + if len(args.name) < 2: + return public.return_message(-1, 0, _( "Volume names can be no less than 2 characters long!")) + + self.docker_client(self._url).volumes.create( + name=args.name, + driver=args.driver, + driver_opts=args.driver_opts if args.driver_opts else None, + labels=args.labels if args.labels != "" else None + ) + dp.write_log("Add storage volume [{}] success!".format(args.name)) + return public.return_message(0, 0, _( "successfully added!")) + except docker.errors.APIError as e: + if "volume name is too short, names should be at least two alphanumeric characters" in str(e): + return public.return_message(-1, 0, _( "Volume names can be no less than 2 characters long!")) + if "volume name" in str(e): + return public.return_message(-1, 0, _( "Volume name already exists!")) + return public.return_message(-1, 0, _( "addition failed {}".format(e))) + + except Exception as e: + if "driver_opts must be a dictionary" in str(e): + return public.return_message(-1, 0, _( "Driver option tags must be dictionary/key-value pairs!")) + return public.return_message(-1, 0, _( "Add failed! {}".format(e))) + + def remove(self, args): + """ + 删除一个卷 + :param name volume name + :param args: + :return: + """ + + # 校验参数 + try: + args.validate([ + Param('name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + obj = self.docker_client(self._url).volumes.get(args.name) + obj.remove() + dp.write_log("Delete volume [{}] successful!".format(args.name)) + return public.return_message(0, 0, _( "successfully delete")) + + except docker.errors.APIError as e: + if "volume is in use" in str(e): + return public.return_message(-1, 0, _( "The storage volume is in use and cannot be deleted!")) + if "no such volume" in str(e): + return public.return_message(-1, 0, _( "The storage volume does not exist!")) + return public.return_message(-1, 0, _( "Delete failed! {}".format(e))) + + def prune(self, args): + """ + 删除无用的卷 + :param args: + :return: + """ + try: + res = self.docker_client(self._url).volumes.prune() + if not res['VolumesDeleted']: + return public.return_message(-1, 0, _( "No useless storage volumes!")) + + dp.write_log("Delete useless storage volume successfully!") + return public.return_message(0, 0, _( "successfully delete!")) + except docker.errors.APIError as e: + return public.return_message(-1, 0, _( "Delete failed! {}".format(e))) diff --git a/class_v2/common_v2.py b/class_v2/common_v2.py new file mode 100644 index 00000000..58087c2c --- /dev/null +++ b/class_v2/common_v2.py @@ -0,0 +1,324 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +from BTPanel import session, cache , request, redirect, g,abort +from datetime import datetime +from public import dict_obj +import os +import public +import json +import sys +import time + + +class panelSetup: + def init(self): + panel_path = public.get_panel_path() + if os.getcwd() != panel_path: os.chdir(panel_path) + + g.ua = request.headers.get('User-Agent','') + if g.ua: + ua = g.ua.lower() + if ua.find('spider') != -1 or g.ua.find('bot') != -1: + return abort(403) + + g.version = '6.8.36' + g.title = public.GetConfigValue('title') + g.uri = request.path + g.debug = os.path.exists('data/debug.pl') + g.pyversion = sys.version_info[0] + session['version'] = g.version + + if not public.get_improvement(): session['is_flush_soft_list'] = 1 + if request.method == 'GET': + if not g.debug: + g.cdn_url = public.get_cdn_url() + if not g.cdn_url: + g.cdn_url = '/static' + else: + g.cdn_url = '//' + g.cdn_url + '/' + g.version + else: + g.cdn_url = '/static' + session['title'] = g.title + + g.recycle_bin_open = 0 + if os.path.exists("data/recycle_bin.pl"): g.recycle_bin_open = 1 + + g.recycle_bin_db_open = 0 + if os.path.exists("data/recycle_bin_db.pl"): g.recycle_bin_db_open = 1 + g.is_aes = False + self.other_import() + return None + + + def other_import(self): + g.o = public.readFile('data/o.pl') + g.other_css = [] + g.other_js = [] + if g.o: + s_path = 'BTPanel/static/other/{}' + css_name = "css/{}.css".format(g.o) + css_file = s_path.format(css_name) + if os.path.exists(css_file): g.other_css.append('/static/other/{}'.format(css_name)) + + js_name = "js/{}.js".format(g.o) + js_file = s_path.format(js_name) + if os.path.exists(js_file): g.other_js.append('/static/other/{}'.format(js_name)) + + + +class panelAdmin(panelSetup): + setupPath = '/www/server' + + # 本地请求 + def local(self): + result = panelSetup().init() + if result: + return result + result = self.check_login() + if result: + return result + result = self.setSession() + if result: + return result + result = self.checkClose() + if result: + return result + result = self.checkWebType() + if result: + return result + result = self.checkConfig() + self.GetOS() + + # 设置基础Session + def setSession(self): + if request.method == 'GET': + g.menus = public.get_menus() + g.yaer = datetime.now().year + session["top_tips"] = public.get_msg_gettext("The current IE browser version is too low to display some features, please use another browser. Or if you use a browser developed by a Chinese company, please switch to Extreme Mode!") + session["bt_help"] = public.get_msg_gettext("For Support|Suggestions, please visit the aaPanel Forum") + session["download"] = public.get_msg_gettext("Downloading:") + if not 'brand' in session: + session['brand'] = public.GetConfigValue('brand') + session['product'] = public.GetConfigValue('product') + session['rootPath'] = '/www' + session['download_url'] = 'https://node.aapanel.com' + session['setupPath'] = session['rootPath'] + '/server' + session['logsPath'] = '/www/wwwlogs' + session['yaer'] = datetime.now().year + if not 'menu' in session: + session['menu'] = public.GetLan('menu') + if not 'lan' in session: + session['lan'] = public.GetLanguage() + if not 'home' in session: + session['home'] = 'https://www.aapanel.com' + return False + + # 检查Web服务器类型 + def checkWebType(self): + #if request.method == 'GET': + if not 'webserver' in session: + if os.path.exists('/usr/local/lsws/bin/lswsctrl'): + session['webserver'] = 'openlitespeed' + elif os.path.exists(self.setupPath + '/apache/bin/apachectl'): + session['webserver'] = 'apache' + else: + session['webserver'] = 'nginx' + if not 'webversion' in session: + if os.path.exists(self.setupPath+'/'+session['webserver']+'/version.pl'): + session['webversion'] = public.ReadFile(self.setupPath+'/'+session['webserver']+'/version.pl').strip() + + if not 'phpmyadminDir' in session: + filename = self.setupPath+'/data/phpmyadminDirName.pl' + if os.path.exists(filename): + session['phpmyadminDir'] = public.ReadFile(filename).strip() + return False + + # 检查面板是否关闭 + def checkClose(self): + if os.path.exists('data/close.pl'): + return redirect('/close') + + # 检查登录 + def check_login(self): + try: + api_check = True + g.api_request = False + if not 'login' in session: + api_check = self.get_sk() + if api_check: + if not isinstance(api_check,dict): + if public.get_admin_path() == '/login': + return redirect('/login?err=1') + return api_check + g.api_request = True + else: + if session['login'] == False: + session.clear() + return redirect(public.get_admin_path()) + + if 'tmp_login_expire' in session: + s_file = 'data/session/{}'.format(session['tmp_login_id']) + if session['tmp_login_expire'] < time.time(): + session.clear() + if os.path.exists(s_file): os.remove(s_file) + return redirect(public.get_admin_path()) + if not os.path.exists(s_file): + session.clear() + return redirect(public.get_admin_path()) + + if not public.check_client_hash(): + session.clear() + return redirect(public.get_admin_path()) + + if api_check: + now_time = time.time() + session_timeout = session.get('session_timeout',0) + if session_timeout < now_time and session_timeout != 0: + session.clear() + return redirect(public.get_admin_path()) + + login_token = session.get('login_token','') + if login_token: + if login_token != public.get_login_token_auth(): + session.clear() + return redirect(public.get_admin_path()) + + # if api_check: + # filename = 'data/sess_files/' + public.get_sess_key() + # if not os.path.exists(filename): + # session.clear() + # return redirect(public.get_admin_path()) + + # 标记新的会话过期时间 + # session['session_timeout'] = time.time() + public.get_session_timeout() + # 标记新的会话过期时间 + self.check_session() + + except: + # public.print_log(public.get_error_info()) + session.clear() + public.print_error() + return redirect('/login?id=2') + + def check_session(self): + white_list = ['/favicon.ico', '/system?action=GetNetWork'] + if g.uri in white_list: + return + session['session_timeout'] = time.time() + public.get_session_timeout() + + + + # 获取sk + def get_sk(self): + save_path = '/www/server/panel/config/api.json' + if not os.path.exists(save_path): + return public.redirect_to_login() + + try: + api_config = json.loads(public.ReadFile(save_path)) + except: + os.remove(save_path) + return public.redirect_to_login() + + if not api_config['open']: + return public.redirect_to_login() + from BTPanel import get_input + get = get_input() + client_ip = public.GetClientIp() + if not 'client_bind_token' in get: + if not 'request_token' in get or not 'request_time' in get: + return public.redirect_to_login() + + num_key = client_ip + '_api' + if not public.get_error_num(num_key, 20): + return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour') + + if not public.is_api_limit_ip(api_config['limit_addr'], client_ip): # client_ip in api_config['limit_addr']: + public.set_error_num(num_key) + return public.returnJson(False,'%s[' % public.get_msg_gettext("20 consecutive verification failures, prohibited for 1 hour")+client_ip+']') + else: + num_key = client_ip + '_app' + if not public.get_error_num(num_key,20): + return public.returnJson(False,'20 consecutive verification failures, prohibited for 1 hour') + a_file = '/dev/shm/' + get.client_bind_token + + if not public.path_safe_check(get.client_bind_token): + public.set_error_num(num_key) + return public.returnJson(False, 'illegal request') + + if not os.path.exists(a_file): + import panelApi + if not panelApi.panelApi().get_app_find(get.client_bind_token): + public.set_error_num(num_key) + return public.returnJson(False,'Unbound device') + public.writeFile(a_file,'') + + if not 'key' in api_config: + public.set_error_num(num_key) + return public.returnJson(False, 'Key verification failed') + if not 'form_data' in get: + public.set_error_num(num_key) + return public.returnJson(False, 'No form_data data found') + + g.form_data = json.loads(public.aes_decrypt(get.form_data, api_config['key'])) + + get = get_input() + if not 'request_token' in get or not 'request_time' in get: + return public.error_not_login('/login') + g.is_aes = True + g.aes_key = api_config['key'] + request_token = public.md5(get.request_time + api_config['token']) + if get.request_token == request_token: + public.set_error_num(num_key,True) + return False + public.set_error_num(num_key) + return public.returnJson(False,'Secret key verification failed') + + # 检查系统配置 + def checkConfig(self): + if not 'config' in session: + session['config'] = public.M('config').where("id=?", ('1',)).field( + 'webserver,sites_path,backup_path,status,mysql_root').find() + if not 'email' in session['config']: + session['config']['email'] = public.M( + 'users').where("id=?", ('1',)).getField('email') + if not 'address' in session: + session['address'] = public.GetLocalIp() + return False + + # 获取操作系统类型 + def GetOS(self): + if not 'server_os' in session: + tmp = {} + issue_file = '/etc/issue' + redhat_release = '/etc/redhat-release' + if os.path.exists(redhat_release): + tmp['x'] = 'RHEL' + tmp['osname'] = self.get_osname(redhat_release) + elif os.path.exists('/usr/bin/yum'): + tmp['x'] = 'RHEL' + tmp['osname'] = self.get_osname(issue_file) + elif os.path.exists(issue_file): + tmp['x'] = 'Debian' + tmp['osname'] = self.get_osname(issue_file) + session['server_os'] = tmp + return False + + + def get_osname(self,i_file): + ''' + @name 从指定文件中获取系统名称 + @author hwliang<2021-04-07> + @param i_file 指定文件全路径 + @return string + ''' + if not os.path.exists(i_file): return '' + issue_str = public.ReadFile(i_file).strip() + if issue_str: return issue_str.split()[0] + return '' diff --git a/class_v2/config_v2.py b/class_v2/config_v2.py new file mode 100644 index 00000000..55a97c27 --- /dev/null +++ b/class_v2/config_v2.py @@ -0,0 +1,3352 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel x3 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import base64 +import public,re,os,nginx,apache,json,time,ols +from public.validate import Param +try: + import pyotp +except: + public.ExecShell("pip install pyotp &") +try: + from BTPanel import session,admin_path_checks,g,request,cache + import send_mail +except:pass +class config: + _setup_path = "/www/server/panel" + _key_file = _setup_path+"/data/two_step_auth.txt" + _bk_key_file = _setup_path + "/data/bk_two_step_auth.txt" + _username_file = _setup_path + "/data/username.txt" + _core_fle_path = _setup_path + '/data/qrcode' + __mail_config = _setup_path+'/data/stmp_mail.json' + __mail_list_data = _setup_path+'/data/mail_list.json' + __dingding_config = _setup_path+'/data/dingding.json' + __mail_list = [] + __weixin_user = [] + + def __init__(self): + try: + self.mail = send_mail.send_mail() + if not os.path.exists(self.__mail_list_data): + ret = [] + public.writeFile(self.__mail_list_data, json.dumps(ret)) + else: + try: + mail_data = json.loads(public.ReadFile(self.__mail_list_data)) + self.__mail_list = mail_data + except: + ret = [] + public.writeFile(self.__mail_list_data, json.dumps(ret)) + except:pass + # 返回配置邮件地址 + def return_mail_list(self, get): + return public.return_msg_gettext(True, self.__mail_list) + + # 删除邮件接口 + def del_mail_list(self, get): + emial = get.email.strip() + if emial in self.__mail_list: + self.__mail_list.remove(emial) + public.writeFile(self.__mail_list_data, json.dumps(self.__mail_list)) + return public.return_msg_gettext(True, 'Successfully deleted') + else: + return public.return_msg_gettext(True, 'Email does not exist') + + def del_tg_info(self,get): + import panel_telegram_bot + return panel_telegram_bot.panel_telegram_bot().del_tg_bot(get) + + def set_tg_bot(self,get): + import panel_telegram_bot + return panel_telegram_bot.panel_telegram_bot().set_tg_bot(get) + + #添加接受邮件地址 + def add_mail_address(self, get): + if not hasattr(get, 'email'): return public.return_msg_gettext(False, 'Please input your email') + emailformat = re.compile(r'[a-zA-Z0-9.-_+%]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+') + if not emailformat.search(get.email): return public.return_msg_gettext(False, 'Please enter your vaild email') + # 测试发送邮件 + if get.email.strip() in self.__mail_list: return public.return_msg_gettext(True, 'Email already exists') + self.__mail_list.append(get.email.strip()) + public.writeFile(self.__mail_list_data, json.dumps(self.__mail_list)) + return public.return_msg_gettext(True, 'Setup successfully!') + + # 添加自定义邮箱地址 + def user_mail_send(self, get): + if not (hasattr(get, 'email') or hasattr(get, 'stmp_pwd') or hasattr(get, 'hosts') or hasattr(get, 'port')): + return public.return_msg_gettext(False, 'Please complete the information') + # 自定义邮件 + self.mail.qq_stmp_insert(get.email.strip(), get.stmp_pwd.strip(), get.hosts.strip(),get.port.strip()) + # 测试发送 + if self.mail.qq_smtp_send(get.email.strip(), public.get_msg_gettext('aaPanel Alert Test Email'), public.get_msg_gettext('aaPanel Alert Test Email')): + if not get.email.strip() in self.__mail_list: + self.__mail_list.append(get.email.strip()) + public.writeFile(self.__mail_list_data, json.dumps(self.__mail_list)) + return public.return_msg_gettext(True, 'Setup successfully!') + else: + ret = [] + public.writeFile(self.__mail_config, json.dumps(ret)) + return public.return_msg_gettext(False, 'Email sending failed, please check if the STMP password is correct or the hosts are correct') + + # 查看自定义邮箱配置 + def get_user_mail(self, get): + qq_mail_info = json.loads(public.ReadFile(self.__mail_config)) + if len(qq_mail_info) == 0: + return public.return_msg_gettext(False, 'No Data') + if not 'port' in qq_mail_info:qq_mail_info['port']=465 + return public.return_msg_gettext(True, qq_mail_info) + + #清空数据 + def set_empty(self,get): + type=get.type.strip() + if type=='dingding': + ret = [] + public.writeFile(self.__dingding_config, json.dumps(ret)) + return public.return_msg_gettext(True, 'Empty successfully') + else: + ret = [] + public.writeFile(self.__mail_config, json.dumps(ret)) + return public.return_msg_gettext(True, 'Empty successfully') + + + # 用户自定义邮件发送 + def user_stmp_mail_send(self, get): + if not (hasattr(get, 'email')): return public.return_msg_gettext(False, 'Please input your email') + emailformat = re.compile(r'[a-zA-Z0-9.-_+%]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+') + if not emailformat.search(get.email): return public.return_msg_gettext(False, 'Please enter your vaild email') + # 测试发送邮件 + if not get.email.strip() in self.__mail_list: return public.return_msg_gettext(True, 'The mailbox does not exist, please add it to the mailbox list') + if not (hasattr(get, 'title')): return public.return_msg_gettext(False, 'Please fill in the email title') + if not (hasattr(get, 'body')): return public.return_msg_gettext(False, 'Please enter the email content') + # 先判断是否存在stmp信息 + qq_mail_info = json.loads(public.ReadFile(self.__mail_config)) + if len(qq_mail_info) == 0: + return public.return_msg_gettext(False, 'STMP information was not found, please re-add custom mail STMP information in the settings') + if self.mail.qq_smtp_send(get.email.strip(), get.title.strip(), get.body): + # 发送成功 + return public.return_msg_gettext(True, 'Sent successfully') + else: + return public.return_msg_gettext(False, 'Failed to send') + + # 查看能使用的告警通道 + def get_settings(self, get): + sm = send_mail.send_mail() + return sm.get_settings() + + def get_settings2(self, get=None): + import panel_telegram_bot + tg = panel_telegram_bot.panel_telegram_bot() + tg = tg.get_tg_conf() + conf = self.get_settings(get) + conf['telegram'] = tg + return conf + + # 设置钉钉报警 + def set_dingding(self, get): + if not (hasattr(get, 'url') or hasattr(get, 'atall')): + return public.return_msg_gettext(False, 'Please complete the information') + if get.atall=='True' or get.atall=='1': + get.atall = 'True' + else: get.atall = 'False' + push_url = get.url.strip() + channel = "dingding" + if push_url.find("weixin.qq.com") != -1: + channel = "weixin" + msg = "" + try: + from panelMessage import panelMessage + pm = panelMessage() + if hasattr(pm, "init_msg_module"): + msg_module = pm.init_msg_module(channel) + if msg_module: + _res = msg_module.set_config(get) + if _res["status"]: + return _res + except Exception as e: + msg = str(e) + print("设置钉钉配置异常: {}".format(msg)) + if not msg: + return public.returnMsg(False, 'Add failed, please check if the URL is correct') + else: + return public.returnMsg(False, msg) + + # 查看钉钉 + def get_dingding(self, get): + sm = send_mail.send_mail() + return sm.get_dingding() + + # 使用钉钉发送消息 + def user_dingding_send(self, get): + qq_mail_info = json.loads(public.ReadFile(self.__dingding_config)) + if len(qq_mail_info) == 0: + return public.return_msg_gettext(False, 'The configuration information of the nails you configured was not found, please add in the settings') + if not (hasattr(get, 'content')): return public.return_msg_gettext(False, 'Please enter the data you need to send') + if self.mail.dingding_send(get.content): + return public.return_msg_gettext(True, 'Sent successfully') + else: + return public.return_msg_gettext(False, 'Failed to send') + + + def getPanelState(self,get): + return os.path.exists(self._setup_path+'/data/close.pl') + + def reload_session(self): + userInfo = public.M('users').where("id=?",(1,)).field('username,password').find() + token = public.Md5(userInfo['username'] + '/' + userInfo['password']) + public.writeFile(self._setup_path+'/data/login_token.pl',token) + skey = 'login_token' + cache.set(skey,token) + sess_path = 'data/sess_files' + if not os.path.exists(sess_path): + os.makedirs(sess_path,384) + self.clean_sess_files(sess_path) + sess_key = public.get_sess_key() + sess_file = os.path.join(sess_path,sess_key) + public.writeFile(sess_file,str(int(time.time()+86400))) + public.set_mode(sess_file,'600') + session['login_token'] = token + + def clean_sess_files(self,sess_path): + ''' + @name 清理过期的sess_file + @auther hwliang<2020-07-25> + @param sess_path(string) sess_files目录 + @return void + ''' + s_time = time.time() + for fname in os.listdir(sess_path): + try: + if len(fname) != 32: continue + sess_file = os.path.join(sess_path,fname) + if not os.path.isfile(sess_file): continue + sess_tmp = public.ReadFile(sess_file) + if not sess_tmp: + if os.path.exists(sess_file): + os.remove(sess_file) + if s_time > int(sess_tmp): + os.remove(sess_file) + except: + pass + + def get_password_safe_file(self): + ''' + @name 获取密码复杂度配置文件 + @auther hwliang<2021-10-18> + @return string + ''' + return public.get_panel_path() + '/data/check_password_safe.pl' + + def check_password_safe(self,password): + ''' + @name 密码复杂度验证 + @auther hwliang<2021-10-18> + @param password(string) 密码 + @return bool + ''' + # 是否检测密码复杂度 + is_check_file = self.get_password_safe_file() + if not os.path.exists(is_check_file): return True + + # 密码长度验证 + if len(password) < 8: return False + + num = 0 + # 密码是否包含数字 + if re.search(r'[0-9]+',password): num += 1 + # 密码是否包含小写字母 + if re.search(r'[a-z]+',password): num += 1 + # 密码是否包含大写字母 + if re.search(r'[A-Z]+',password): num += 1 + # 密码是否包含特殊字符 + if re.search(r'[^\w\s]+',password): num += 1 + # 密码是否包含以上任意3种组合 + if num < 3: return False + return True + + def set_password_safe(self,get): + ''' + @name 设置密码复杂度 + @auther hwliang<2021-10-18> + @param get(string) 参数 + @return dict + ''' + is_check_file = self.get_password_safe_file() + if os.path.exists(is_check_file): + os.remove(is_check_file) + public.WriteLog('TYPE_PANEL','Disable password complexity verification') + return public.returnMsg(True,'Password complexity verification is disabled') + else: + public.writeFile(is_check_file,'True') + public.WriteLog('TYPE_PANEL','Enable password complexity verification') + return public.returnMsg(True,'Password complexity verification has been enabled') + + def get_password_safe(self,get): + ''' + @name 获取密码复杂度 + @auther hwliang<2021-10-18> + @param get(string) 参数 + @return bool + ''' + is_check_file = self.get_password_safe_file() + return os.path.exists(is_check_file) + + + def get_password_expire_file(self): + ''' + @name 获取密码过期配置文件 + @auther hwliang<2021-10-18> + @return string + ''' + return public.get_panel_path() + '/data/password_expire.pl' + + + def set_password_expire(self,get): + ''' + @name 设置密码过期时间 + @auther hwliang<2021-10-18> + @param get{ + expire: int<密码过期时间> 单位:天, + } + @return dict + ''' + expire = int(get.expire) + expire_file = self.get_password_expire_file() + if expire <= 0: + if os.path.exists(expire_file): + os.remove(expire_file) + public.WriteLog('TYPE_PANEL','Disable password expiration authentication') + return public.returnMsg(True,'Password expiration authentication is disabled') + min_expire = 10 + max_expire = 365 * 5 + if expire < min_expire: return public.returnMsg(False,'The password expiration period cannot be less than {} days'.format(min_expire)) + if expire > max_expire: return public.returnMsg(False,'The password expiration period cannot be longer than {} days'.format(max_expire)) + + public.writeFile(self.get_password_expire_file(),str(expire)) + + if expire > 0: + expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl' + public.writeFile(expire_time_file,str(int(time.time()) + (expire * 86400))) + + public.WriteLog('TYPE_PANEL','Set the password expiration time to [{}] days'.format(expire)) + return public.returnMsg(True,'The password expiration time is set to [{}] days'.format(expire)) + + def setlastPassword(self, get): + public.add_security_logs("Change Password", "Successfully used last password!") + self.reload_session() + # 密码过期时间 + expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl' + if os.path.exists(expire_time_file): os.remove(expire_time_file) + self.get_password_config(None) + if session.get('password_expire', False): + session['password_expire'] = False + return public.returnMsg(True, 'USER_PASSWORD_SUCCESS') + + def get_password_config(self,get=None): + ''' + @name 获取密码配置 + @auther hwliang<2021-10-18> + @param get 参数 + @return dict{expire:int,expire_time:int,password_safe:bool} + ''' + expire_file = self.get_password_expire_file() + expire = 0 + expire_time=0 + if os.path.exists(expire_file): + expire = public.readFile(expire_file) + try: + expire = int(expire) + except: + expire = 0 + + # 检查密码过期时间文件是否存在 + expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl' + if not os.path.exists(expire_time_file) and expire > 0: + public.writeFile(expire_time_file,str(int(time.time()) + (expire * 86400))) + + expire_time = public.readFile(expire_time_file) + if expire_time: + expire_time = int(expire_time) + else: + expire_time = 0 + + data = {} + data['expire'] = expire + data['expire_time'] = expire_time + data['password_safe'] = self.get_password_safe(get) + data['ps'] = 'Password expiration configuration is not enabled. For your panel security, please consider enabling it!' + if data['expire_time']: + data['expire_day'] = int((expire_time - time.time()) / 86400) + if data['expire_day'] < 10: + if data['expire_day'] <= 0: + data['ps'] = 'Your password has expired. In case you fail to log in next time, please change your password immediately.' + else: + data['ps'] = "Your panel password will expire in {} days, in order not to affect your normal login, please change the password as soon as possible!".format(data['expire_day']) + else: + data['ps'] = "Your panel password has {} days left to expire!".format(data['expire_day']) + return data + + + def setPassword(self,get): + get.password1 = public.url_decode(public.rsa_decrypt(get.password1)) + get.password2 = public.url_decode(public.rsa_decrypt(get.password2)) + if get.password1 != get.password2: return public.return_msg_gettext(False,'The passwords entered twice are inconsistent, please try again!') + if len(get.password1) < 5: return public.return_msg_gettext(False,'Password cannot be less than 5 characters!') + if not self.check_password_safe(get.password1): return public.returnMsg(False,'The password must be at least eight characters in length and contain at least three combinations of digits, uppercase letters, lowercase letters, and special characters') + public.M('users').where("username=?",(session['username'],)).setField('password',public.password_salt(public.md5(get.password1.strip()),username=session['username'])) + public.write_log_gettext('Panel configuration','Successfully modified password for user [{0}]!',(session['username'],)) + self.reload_session() + + # 密码过期时间 + expire_time_file = public.get_panel_path() + '/data/password_expire_time.pl' + if os.path.exists(expire_time_file): os.remove(expire_time_file) + self.get_password_config(None) + if session.get('password_expire',False): + session['password_expire'] = False + return public.return_msg_gettext(True,'Setup successfully!') + + def setUsername(self,get): + get.username1 = public.url_decode(public.rsa_decrypt(get.username1)) + get.username2 = public.url_decode(public.rsa_decrypt(get.username2)) + if get.username1 != get.username2: return public.return_msg_gettext(False,'The usernames entered twice are inconsistent, plesea try again!') + if len(get.username1) < 3: return public.return_msg_gettext(False,'Username cannot be less than 3 characters') + public.M('users').where("username=?",(session['username'],)).setField('username',get.username1.strip()) + public.write_log_gettext('Panel configuration','Username is modified from [{}] to [{}]',(session['username'],get.username2)) + session['username'] = get.username1 + self.reload_session() + return public.return_msg_gettext(True,'Setup successfully!') + + #取用户列表 + def get_users(self,args): + data = public.M('users').field('id,username').select() + return data + + # 创建新用户 + def create_user(self,args): + args.username = public.url_decode(args.username) + args.password = public.url_decode(args.password) + if session['uid'] != 1: return public.return_msg_gettext(False,'Permission denied!') + if len(args.username) < 2: return public.return_msg_gettext(False,'User name must be at least 2 characters') + if len(args.password) < 8: return public.return_msg_gettext(False,'Password must be at least 8 characters') + pdata = { + "username": args.username.strip(), + "password": public.password_salt(public.md5(args.password.strip()),username=args.username.strip()) + } + + if(public.M('users').where('username=?',(pdata['username'],)).count()): + return public.return_msg_gettext(False,'The specified username already exists!') + + if(public.M('users').insert(pdata)): + public.write_log_gettext('User Management','Create new user {}',(pdata['username'],)) + return public.return_msg_gettext(True,'Create new user {} success!',(pdata['username'],)) + return public.return_msg_gettext(False,'Create new user failed!') + + # 删除用户 + def remove_user(self,args): + if session['uid'] != 1: return public.return_msg_gettext(False,'Permission denied!') + if int(args.id) == 1: return public.return_msg_gettext(False,'Cannot delete initial default user!') + username = public.M('users').where('id=?',(args.id,)).getField('username') + if not username: return public.return_msg_gettext(False,'The specified username not exists!') + if(public.M('users').where('id=?',(args.id,)).delete()): + public.write_log_gettext('User Management','Delete users [{}]',(username)) + return public.return_msg_gettext(True,'Delete user {} success!',(username,)) + return public.return_msg_gettext(False,'User deletion failed!') + + # 修改用户 + def modify_user(self,args): + if session['uid'] != 1: return public.return_msg_gettext(False,'Permission denied!') + username = public.M('users').where('id=?',(args.id,)).getField('username') + pdata = {} + if 'username' in args: + args.username = public.url_decode(args.username) + if len(args.username) < 2: return public.return_msg_gettext(False,'User name must be at least 2 characters') + pdata['username'] = args.username.strip() + + if 'password' in args: + if args.password: + args.password = public.url_decode(args.password) + if len(args.password) < 8: return public.return_msg_gettext(False,'Password must be at least 8 characters') + pdata['password'] = public.password_salt(public.md5(args.password.strip()),username=username) + + if(public.M('users').where('id=?',(args.id,)).update(pdata)): + public.write_log_gettext('User Management',"Edit user {}",(username,)) + return public.return_msg_gettext(True,'Setup successfully!') + return public.return_msg_gettext(False,'No changes submitted') + + def setPanel(self,get): + if not public.IsRestart(): return public.return_msg_gettext(False,'Please run the program when all install tasks finished!') + if 'limitip' in get: + if get.limitip.find('/') != -1: + return public.return_msg_gettext(False,'The authorized IP format is incorrect, and the subnet segment writing is not supported') + isReWeb = False + sess_out_path = 'data/session_timeout.pl' + if 'session_timeout' in get: + try: + session_timeout = int(get.session_timeout) + except: + return public.returnMsg(False,"Timeout must be an integer!") + s_time_tmp = public.readFile(sess_out_path) + if not s_time_tmp: s_time_tmp = '0' + if int(s_time_tmp) != session_timeout: + if session_timeout < 300 or session_timeout > 86400: return public.return_msg_gettext(False,'The timeout time needs to be between 300-86400') + public.writeFile(sess_out_path,str(session_timeout)) + isReWeb = True + else: + return public.returnMsg(False,'Timeout must be an integer!') + + workers_p = 'data/workers.pl' + if 'workers' in get: + workers = int(get.workers) + if int(public.readFile(workers_p)) != workers: + if workers < 1 or workers > 1024: return public.return_msg_gettext(False,public.get_msg_gettext('The number of panel threads should be between 1-1024')) + public.writeFile(workers_p,str(workers)) + isReWeb = True + + if get.domain: + if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", get.domain): return public.return_msg_gettext(False, 'Domain cannot bind ip address') + reg = r"^([\w\-\*]{1,100}\.){1,4}(\w{1,10}|\w{1,10}\.\w{1,10})$" + if not re.match(reg, get.domain): return public.return_msg_gettext(False,'Format of primary domain is incorrect') + if get.address: + if not re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", get.address): + return public.return_msg_gettext(False, 'Please set the correct Server IP') + oldPort = public.GetHost(True) + if not 'port' in get: + get.port = oldPort + newPort = get.port + if oldPort != get.port: + get.port = str(int(get.port)) + if self.IsOpen(get.port): + return public.return_msg_gettext(False,'Port [{}] is in use!',(get.port,)) + if int(get.port) >= 65535 or int(get.port) < 100: return public.return_msg_gettext(False,'Port range is incorrect! should be between 100-65535') + public.writeFile('data/port.pl',get.port) + import firewalls + get.ps = public.get_msg_gettext('New panel port') + fw = firewalls.firewalls() + fw.AddAcceptPort(get) + get.port = oldPort + get.id = public.M('firewall').where("port=?",(oldPort,)).getField('id') + fw.DelAcceptPort(get) + isReWeb = True + + if get.webname != session['title']: + session['title'] = public.xssencode2(get.webname) + public.SetConfigValue('title',public.xssencode2(get.webname)) + + limitip = public.readFile('data/limitip.conf') + if get.limitip != limitip: + public.writeFile('data/limitip.conf',get.limitip) + cache.set('limit_ip',[]) + + public.writeFile('data/domain.conf',public.xssencode2(get.domain).strip()) + public.writeFile('data/iplist.txt',get.address) + + import files + fs = files.files() + if not fs.CheckDir(get.backup_path): return public.returnMsg(False,'Cannot use system critical directory as default backup directory') + if not fs.CheckDir(get.sites_path): return public.returnMsg(False,'Cannot use system critical directory as default site directory') + public.M('config').where("id=?",('1',)).save('backup_path,sites_path',(get.backup_path,get.sites_path)) + session['config']['backup_path'] = os.path.join('/',get.backup_path) + session['config']['sites_path'] = os.path.join('/',get.sites_path) + db_backup = get.backup_path + '/database' + if not os.path.exists(db_backup): + try: + os.makedirs(db_backup,384) + except: + public.ExecShell('mkdir -p ' + db_backup) + site_backup = get.backup_path + '/site' + if not os.path.exists(site_backup): + try: + os.makedirs(site_backup,384) + except: + public.ExecShell('mkdir -p ' + site_backup) + + mhost = public.GetHost() + if get.domain.strip(): mhost = get.domain + data = {'uri':request.path,'host':mhost+':'+newPort,'status':True,'isReWeb':isReWeb,'msg':public.get_msg_gettext('Saved')} + public.write_log_gettext('Panel configuration','Set panel port [{}], domain [{}], default backup directory [{}], default site directory [{}], server IP [{}], authorized IP [{}]!',(newPort,get.domain,get.backup_path,get.sites_path,get.address,get.limitip)) + if isReWeb: public.restart_panel() + return data + + + def set_admin_path(self,get): + get.admin_path = public.rsa_decrypt(get.admin_path.strip()).strip() + if len(get.admin_path) < 6: return public.return_msg_gettext(False,'Security Entrance cannot be less than 6 characters!') + if get.admin_path in admin_path_checks: return public.return_msg_gettext(False,'This entrance has been used by the panel, please set another entrances!') + if not public.path_safe_check(get.admin_path) or get.admin_path[-1] == '.': return public.returnMsg(False,'Entrance address format is incorrect, e.g. /my_panel') + if get.admin_path[0] != '/': return public.return_msg_gettext(False,'Entrance address format is incorrect, e.g. /my_panel') + if get.admin_path.find("//") != -1: + return public.return_msg_gettext(False, 'Entrance address format is incorrect, e.g. /my_panel') + admin_path_file = 'data/admin_path.pl' + admin_path = '/' + if os.path.exists(admin_path_file): admin_path = public.readFile(admin_path_file).strip() + if get.admin_path != admin_path: + public.writeFile(admin_path_file,get.admin_path) + public.restart_panel() + return public.return_msg_gettext(True, 'Setup successfully!') + + + def setPathInfo(self,get): + #设置PATH_INFO + version = get.version + type = get.type + if public.get_webserver() == 'nginx': + path = public.GetConfigValue('setup_path')+'/nginx/conf/enable-php-'+version+'.conf' + conf = public.readFile(path) + rep = r"\s+#*include\s+pathinfo.conf;" + if type == 'on': + conf = re.sub(rep,'\n\t\t\tinclude pathinfo.conf;',conf) + else: + conf = re.sub(rep,'\n\t\t\t#include pathinfo.conf;',conf) + public.writeFile(path,conf) + public.serviceReload() + + path = public.GetConfigValue('setup_path')+'/php/'+version+'/etc/php.ini' + conf = public.readFile(path) + rep = r"\n*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + status = '0' + if type == 'on':status = '1' + conf = re.sub(rep,"\ncgi.fix_pathinfo = "+status+"\n",conf) + public.writeFile(path,conf) + public.write_log_gettext("PHP configuration", "Set PATH_INFO module to [{}] for PHP-{}!",(version,type)) + public.phpReload(version) + return public.return_msg_gettext(True,'Setup successfully!') + + + #设置文件上传大小限制 + def setPHPMaxSize(self,get): + version = get.version + max = get.max + if int(max) < 2: return public.return_msg_gettext(False,'Limit of upload size cannot be less than 2 MB') + #设置PHP + path = public.GetConfigValue('setup_path')+'/php/'+version+'/etc/php.ini' + ols_php_path = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version, get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + ols_php_path = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + for p in [path,ols_php_path]: + if not p: + continue + if not os.path.exists(p): + continue + conf = public.readFile(p) + rep = r"\nupload_max_filesize\s*=\s*[0-9]+M?m?" + conf = re.sub(rep,r'\nupload_max_filesize = '+max+'M',conf) + rep = r"\npost_max_size\s*=\s*[0-9]+M?m?" + conf = re.sub(rep,r'\npost_max_size = '+max+'M',conf) + public.writeFile(p,conf) + + if public.get_webserver() == 'nginx': + #设置Nginx + path = public.GetConfigValue('setup_path')+'/nginx/conf/nginx.conf' + conf = public.readFile(path) + rep = r"client_max_body_size\s+([0-9]+)m?M?" + tmp = re.search(rep,conf).groups() + if int(tmp[0]) < int(max): + conf = re.sub(rep,'client_max_body_size '+max+'m',conf) + public.writeFile(path,conf) + + public.serviceReload() + public.phpReload(version) + public.write_log_gettext("PHP configuration", "Set max upload size to [{} MB] for PHP-{}!",(version,max)) + return public.return_msg_gettext(True,'Setup successfully!') + + #设置禁用函数 + def setPHPDisable(self,get): + filename = public.GetConfigValue('setup_path') + '/php/' + get.version + '/etc/php.ini' + ols_php_path = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version, get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + ols_php_path = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + if not os.path.exists(filename): return public.return_msg_gettext(False,'Requested PHP version does NOT exist!') + for file in [filename,ols_php_path]: + if not os.path.exists(file): + continue + phpini = public.readFile(file) + rep = r"disable_functions\s*=\s*.*\n" + phpini = re.sub(rep, 'disable_functions = ' + get.disable_functions + "\n", phpini) + public.write_log_gettext('PHP configuration','Modified disabled function to [{}] for PHP-{}',(get.version,get.disable_functions)) + public.writeFile(file,phpini) + public.phpReload(get.version) + public.serviceReload() + return public.return_msg_gettext(True,'Setup successfully!') + + #设置PHP超时时间 + def setPHPMaxTime(self,get): + time = get.time + version = get.version + if int(time) < 30 or int(time) > 86400: return public.return_msg_gettext(False,'Please fill in the value between 30 and 86400!') + file = public.GetConfigValue('setup_path')+'/php/'+version+'/etc/php-fpm.conf' + conf = public.readFile(file) + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + conf = re.sub(rep,"request_terminate_timeout = "+time+"\n",conf) + public.writeFile(file,conf) + + file = '/www/server/php/'+version+'/etc/php.ini' + phpini = public.readFile(file) + rep = r"max_execution_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep,"max_execution_time = "+time+"\n",phpini) + rep = r"max_input_time\s*=\s*([0-9]+)\r?\n" + phpini = re.sub(rep,"max_input_time = "+time+"\n",phpini) + public.writeFile(file,phpini) + + if public.get_webserver() == 'nginx': + #设置Nginx + path = public.GetConfigValue('setup_path')+'/nginx/conf/nginx.conf' + conf = public.readFile(path) + rep = r"fastcgi_connect_timeout\s+([0-9]+);" + tmp = re.search(rep, conf).groups() + if int(tmp[0]) < int(time): + conf = re.sub(rep,'fastcgi_connect_timeout '+time+';',conf) + rep = r"fastcgi_send_timeout\s+([0-9]+);" + conf = re.sub(rep,'fastcgi_send_timeout '+time+';',conf) + rep = r"fastcgi_read_timeout\s+([0-9]+);" + conf = re.sub(rep,'fastcgi_read_timeout '+time+';',conf) + public.writeFile(path,conf) + + public.write_log_gettext("PHP configuration", "Set maximum time of script to [{} second] for PHP-{}!",(version,time)) + public.serviceReload() + public.phpReload(version) + return public.return_msg_gettext(True, 'Setup successfully!') + + + #取FPM设置 + def getFpmConfig(self,get): + version = get.version + file = public.GetConfigValue('setup_path')+"/php/"+version+"/etc/php-fpm.conf" + conf = public.readFile(file) + data = {} + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_children'] = tmp[0] + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['start_servers'] = tmp[0] + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['min_spare_servers'] = tmp[0] + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + tmp = re.search(rep, conf).groups() + data['max_spare_servers'] = tmp[0] + + rep = r"\s*pm\s*=\s*(\w+)\s*" + tmp = re.search(rep, conf).groups() + data['pm'] = tmp[0] + + rep = r"\s*listen.allowed_clients\s*=\s*([\w\.,/]+)\s*" + tmp = re.search(rep, conf).groups() + data['allowed'] = tmp[0] + + + data['unix'] = 'unix' + data['port'] = '' + data['bind'] = '/tmp/php-cgi-{}.sock'.format(version) + + fpm_address = public.get_fpm_address(version,True) + if not isinstance(fpm_address,str): + data['unix'] = 'tcp' + data['port'] = fpm_address[1] + data['bind'] = fpm_address[0] + + return data + + + #设置 + def setFpmConfig(self,get): + version = get.version + max_children = get.max_children + start_servers = get.start_servers + min_spare_servers = get.min_spare_servers + max_spare_servers = get.max_spare_servers + pm = get.pm + if not pm in ['static','dynamic','ondemand']: + return public.return_msg_gettext(False,'Wrong operating mode') + file = public.GetConfigValue('setup_path')+"/php/"+version+"/etc/php-fpm.conf" + conf = public.readFile(file) + + rep = r"\s*pm.max_children\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_children = "+max_children, conf) + + rep = r"\s*pm.start_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.start_servers = "+start_servers, conf) + + rep = r"\s*pm.min_spare_servers\s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.min_spare_servers = "+min_spare_servers, conf) + + rep = r"\s*pm.max_spare_servers \s*=\s*([0-9]+)\s*" + conf = re.sub(rep, "\npm.max_spare_servers = "+max_spare_servers+"\n", conf) + + rep = r"\s*pm\s*=\s*(\w+)\s*" + conf = re.sub(rep, "\npm = "+pm+"\n", conf) + if pm == 'ondemand': + if conf.find('listen.backlog = -1') != -1: + rep = r"\s*listen\.backlog\s*=\s*([0-9-]+)\s*" + conf = re.sub(rep, "\nlisten.backlog = 8192\n", conf) + + if get.listen == 'unix': + listen = '/tmp/php-cgi-{}.sock'.format(version) + else: + default_listen = '127.0.0.1:10{}1'.format(version) + if 'bind_port' in get: + if get.bind_port.find('sock') != -1: + listen = default_listen + else: + listen = get.bind_port + else: + listen = default_listen + + + rep = r'\s*listen\s*=\s*.+\s*' + conf = re.sub(rep, "\nlisten = "+listen+"\n", conf) + + if 'allowed' in get: + if not get.allowed: get.allowed = '127.0.0.1' + rep = r"\s*listen.allowed_clients\s*=\s*([\w\.,/]+)\s*" + conf = re.sub(rep, "\nlisten.allowed_clients = "+get.allowed+"\n", conf) + + public.writeFile(file,conf) + public.phpReload(version) + public.sync_php_address(version) + public.write_log_gettext("PHP configuration",'Set concurrency of PHP-{}, max_children={}, start_servers={}, min_spare_servers={}, max_spare_servers={}', (version,max_children,start_servers,min_spare_servers,max_spare_servers)) + return public.return_msg_gettext(True, 'Setup successfully!') + + #同步时间 + def syncDate(self,get): + """ + @name 同步时间 + @author hezhihong + """ + #取国际标准0时时间戳 + time_str = public.HttpGet(public.GetConfigValue('home') + '/api/index/get_time') + try: + new_time = int(time_str)-28800 + except: + return public.returnMsg(False,'Failed to connect to the time server!') + if not new_time: public.returnMsg(False,'Failed to connect to the time server!') + #取所在时区偏差秒数 + add_time='+0000' + try: + add_time=public.ExecShell('date +"%Y-%m-%d %H:%M:%S %Z %z"')[0].replace('\n','').strip().split()[-1] + print(add_time) + except:pass + add_1=False + if add_time[0]=='+': + add_1=True + add_v=int(add_time[1:-2])*3600+int(add_time[-2:])*60 + if add_1: + new_time+=add_v + else:new_time-=add_v + #设置所在时区时间 + date_str = public.format_date(times=new_time) + public.ExecShell('date -s "%s"' % date_str) + public.write_log_gettext("Panel configuration", 'Update Succeeded!') + return public.return_msg_gettext(True,'Setup successfully!') + + def IsOpen(self,port): + #检查端口是否占用 + import socket + s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) + try: + s.connect(('127.0.0.1',int(port))) + s.shutdown(2) + return True + except: + return False + + #设置是否开启监控 + def SetControl(self,get): + try: + if hasattr(get,'day'): + get.day = int(get.day) + get.day = str(get.day) + if(get.day < 1): return public.return_msg_gettext(False,'Number of saving days is illegal!') + except: + pass + + filename = 'data/control.conf' + if get.type == '1': + public.writeFile(filename,get.day) + public.write_log_gettext("Panel configuration",'Turned on monitory service, save for [{}] day!',(get.day,)) + elif get.type == '0': + if os.path.exists(filename): os.remove(filename) + public.write_log_gettext("Panel configuration", 'Monitor service turned off!') + elif get.type == 'del': + if not public.IsRestart(): return public.return_msg_gettext(False,'Please run the program when all install tasks finished!') + os.remove("data/system.db") + import db + sql = db.Sql() + sql.dbfile('system').create('system') + public.write_log_gettext("Panel configuration", 'Monitor service turned off!') + return public.return_msg_gettext(True,'Setup successfully!') + + else: + data = {} + if os.path.exists(filename): + try: + data['day'] = int(public.readFile(filename)) + except: + data['day'] = 30 + data['status'] = True + else: + data['day'] = 30 + data['status'] = False + return data + + return public.return_msg_gettext(True,'Successfully set') + + #关闭面板 + def ClosePanel(self,get): + filename = 'data/close.pl' + if os.path.exists(filename): + os.remove(filename) + return public.returnMsg(True, 'Setup successfully!') + public.writeFile(filename, 'True') + public.ExecShell("chmod 600 " + filename) + public.ExecShell("chown root.root " + filename) + return public.return_msg_gettext(True,'Setup successfully!') + + + #设置自动更新 + def AutoUpdatePanel(self,get): + #return public.returnMsg(False,'体验服务器,禁止修改!') + filename = 'data/autoUpdate.pl' + if os.path.exists(filename): + os.remove(filename) + else: + public.writeFile(filename,'True') + public.ExecShell("chmod 600 " + filename) + public.ExecShell("chown root.root " + filename) + return public.return_msg_gettext(True,'Setup successfully!') + + #设置二级密码 + def SetPanelLock(self,get): + path = 'data/lock' + if not os.path.exists(path): + public.ExecShell('mkdir ' + path) + public.ExecShell("chmod 600 " + path) + public.ExecShell("chown root.root " + path) + + keys = ['files','tasks','config'] + for name in keys: + filename = path + '/' + name + '.pl' + if hasattr(get,name): + public.writeFile(filename,'True') + else: + if os.path.exists(filename): os.remove(filename); + + #设置PHP守护程序 + def Set502(self,get): + filename = 'data/502Task.pl' + if os.path.exists(filename): + public.ExecShell('rm -f ' + filename) + else: + public.writeFile(filename,'True') + + return public.return_msg_gettext(True,'Setup successfully!') + + #设置模板 + def SetTemplates(self,get): + public.writeFile('data/templates.pl',get.templates) + return public.return_msg_gettext(True,'Setup successfully!') + + #设置面板SSL + def SetPanelSSL(self, get): + if not os.path.exists("/www/server/panel/ssl/"): + os.makedirs("/www/server/panel/ssl/") + if hasattr(get, "cert_type") and str(get.cert_type) == "2": + # rep_mail = r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?" + # if not re.search(rep_mail,get.email): + # return public.return_msg_gettext(False,'The E-Mail format is illegal') + import setPanelLets + sp = setPanelLets.setPanelLets() + sps = sp.set_lets(get) + return sps + else: + sslConf = self._setup_path + '/data/ssl.pl' + if os.path.exists(sslConf) and not 'cert_type' in get: + public.ExecShell('rm -f ' + sslConf + '&& rm -f /www/server/panel/ssl/*') + g.rm_ssl = True + return public.return_msg_gettext(True, 'SSL turned off,Please use http protocol to access the panel!') + else: + public.ExecShell('btpip install cffi') + public.ExecShell('btpip install cryptography') + public.ExecShell('btpip install pyOpenSSL') + if not 'cert_type' in get: + return public.returnMsg(False,'Please refresh the page and try again!') + if get.cert_type in [0,'0']: + result = self.SavePanelSSL(get) + if not result['status']: return result + public.writeFile(sslConf,'True') + public.writeFile('data/reload.pl','True') + try: + if not self.CreateSSL(): + return public.return_msg_gettext(False, + 'Error, unable to auto install pyOpenSSL!

                                    Plesea try to manually install: pip install pyOpenSSL

                                    ') + public.writeFile(sslConf, 'True') + except: + return public.return_msg_gettext(False, + 'Error, unable to auto install pyOpenSSL!

                                    Plesea try to manually install: pip install pyOpenSSL

                                    ') + return public.return_msg_gettext(True, + 'SSL is turned on, plesea use https protocol to access the panel!') + #自签证书 + # def CreateSSL(self): + # if os.path.exists('ssl/input.pl'): return True + # import OpenSSL + # key = OpenSSL.crypto.PKey() + # key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + # cert = OpenSSL.crypto.X509() + # cert.set_serial_number(0) + # cert.get_subject().CN = public.GetLocalIp() + # cert.set_issuer(cert.get_subject()) + # cert.gmtime_adj_notBefore( 0 ) + # cert.gmtime_adj_notAfter(86400 * 3650) + # cert.set_pubkey( key ) + # cert.sign( key, 'md5' ) + # cert_ca = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) + # private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) + # if len(cert_ca) > 100 and len(private_key) > 100: + # public.writeFile('ssl/certificate.pem',cert_ca,'wb+') + # public.writeFile('ssl/privateKey.pem',private_key,'wb+') + # return True + # return False + # 自签证书 + + def CreateSSL(self): + import base64 + userInfo = public.get_user_info() + if not userInfo: + userInfo['uid'] = 0 + userInfo['access_key'] = 'B' * 32 + domains = self.get_host_all() + pdata = { + "action": "get_domain_cert", + "company": "aapanel.com", + "domain": ','.join(domains), + "uid": userInfo['uid'], + "access_key": 'B' * 32, + "panel": 1 + } + cert_api = 'https://api.aapanel.com/aapanel_cert' + result = json.loads(public.httpPost(cert_api, {'data': json.dumps(pdata)})) + if 'status' in result: + if result['status']: + if os.path.exists('ssl/certificate.pem'): + os.remove('ssl/certificate.pem') + if os.path.exists('ssl/privateKey.pem'): + os.remove('ssl/privateKey.pem') + if os.path.exists('ssl/baota_root.pfx'): + os.remove('ssl/baota_root.pfx') + if os.path.exists('ssl/root_password.pl'): + os.remove('ssl/root_password.pl') + public.writeFile('ssl/certificate.pem', result['cert']) + public.writeFile('ssl/privateKey.pem', result['key']) + public.writeFile('ssl/baota_root.pfx', base64.b64decode(result['pfx']), 'wb+') + public.writeFile('ssl/root_password.pl', result['password']) + public.writeFile('data/ssl.pl', 'True') + # public.ExecShell("/etc/init.d/bt reload") + print('1') + return True + if os.path.exists('ssl/input.pl'): return True + import OpenSSL + key = OpenSSL.crypto.PKey() + key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + cert = OpenSSL.crypto.X509() + cert.set_serial_number(0) + cert.get_subject().CN = public.GetLocalIp() + cert.set_issuer(cert.get_subject()) + cert.gmtime_adj_notBefore( 0 ) + cert.gmtime_adj_notAfter(86400 * 3650) + cert.set_pubkey( key ) + cert.sign( key, 'md5' ) + cert_ca = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) + private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) + + if len(cert_ca) > 100 and len(private_key) > 100: + public.writeFile('ssl/certificate.pem',cert_ca,'wb+') + public.writeFile('ssl/privateKey.pem',private_key,'wb+') + return True + return False + + def get_ipaddress(self): + ''' + @name 获取本机IP地址 + @author hwliang<2020-11-24> + @return list + ''' + ipa_tmp = \ + public.ExecShell("ip a |grep inet|grep -v inet6|grep -v 127.0.0.1|awk '{print $2}'|sed 's#/[0-9]*##g'")[ + 0].strip() + iplist = ipa_tmp.split('\n') + return iplist + + def get_host_all(self): + local_ip = ['127.0.0.1', '::1', 'localhost'] + ip_list = [] + bind_ip = self.get_ipaddress() + + for ip in bind_ip: + ip = ip.strip() + if ip in local_ip: continue + if ip in ip_list: continue + ip_list.append(ip) + net_ip = public.httpGet("https://www.aapanel.com/api/common/getClientIP") + + if net_ip: + net_ip = net_ip.strip() + if not net_ip in ip_list: + ip_list.append(net_ip) + ip_list = [ip_list[-1], ip_list[0]] + return ip_list + #生成Token + def SetToken(self,get): + data = {} + data[''] = public.GetRandomString(24) + + #取面板列表 + def GetPanelList(self,get): + try: + data = public.M('panel').field('id,title,url,username,password,click,addtime').order('click desc').select() + if type(data) == str: data[111] + return public.return_message(0,0,data) + except: + sql = '''CREATE TABLE IF NOT EXISTS `panel` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `title` TEXT, + `url` TEXT, + `username` TEXT, + `password` TEXT, + `click` INTEGER, + `addtime` INTEGER +);''' + public.M('sites').execute(sql,()) + return public.return_message(0,0,[]) + + #添加面板资料 + def AddPanelInfo(self,get): + + #校验是还是重复 + isAdd = public.M('panel').where('title=? OR url=?',(get.title,get.url)).count() + if isAdd: return public.return_msg_gettext(False,'Notes or panel address duplicate!') + import time,json + isRe = public.M('panel').add('title,url,username,password,click,addtime',(public.xssencode2(get.title),public.xssencode2(get.url),public.xssencode2(get.username),get.password,0,int(time.time()))) + if isRe: return public.return_msg_gettext(True,'Setup successfully!') + return public.return_msg_gettext(False,'Failed to add') + + #修改面板资料 + def SetPanelInfo(self,get): + #校验是还是重复 + isSave = public.M('panel').where('(title=? OR url=?) AND id!=?',(get.title,get.url,get.id)).count() + if isSave: return public.return_msg_gettext(False,'Notes or panel address duplicate!') + import time,json + + #更新到数据库 + isRe = public.M('panel').where('id=?',(get.id,)).save('title,url,username,password',(get.title,get.url,get.username,get.password)) + if isRe: return public.return_msg_gettext(True,'Setup successfully!') + return public.return_msg_gettext(False,'Failed to modify') + + #删除面板资料 + def DelPanelInfo(self,get): + isExists = public.M('panel').where('id=?',(get.id,)).count() + if not isExists: return public.return_msg_gettext(False,'Requested panel info does NOT exist!') + public.M('panel').where('id=?',(get.id,)).delete() + return public.return_msg_gettext(True,'Successfully deleted') + + #点击计数 + def ClickPanelInfo(self,get): + click = public.M('panel').where('id=?',(get.id,)).getField('click') + public.M('panel').where('id=?',(get.id,)).setField('click',click+1) + return True + + #获取PHP配置参数 + def GetPHPConf(self,get): + gets = [ + {'name':'short_open_tag','type':1,'ps':public.get_msg_gettext('Short tag support')}, + {'name':'asp_tags','type':1,'ps':public.get_msg_gettext('ASP tag support')}, + {'name':'max_execution_time','type':2,'ps':public.get_msg_gettext('Max time of running script')}, + {'name':'max_input_time','type':2,'ps':public.get_msg_gettext('Max time of input')}, + {'name':'memory_limit','type':2,'ps':public.get_msg_gettext('Limit of script memory')}, + {'name':'post_max_size','type':2,'ps':public.get_msg_gettext('Max size of POST data')}, + {'name':'file_uploads','type':1,'ps':public.get_msg_gettext('Whether to allow upload file')}, + {'name':'upload_max_filesize','type':2,'ps':public.get_msg_gettext('Max size of upload file')}, + {'name':'max_file_uploads','type':2,'ps':public.get_msg_gettext('Max value of simultaneously upload file')}, + {'name':'default_socket_timeout','type':2,'ps':public.get_msg_gettext('Socket over time')}, + {'name':'error_reporting','type':3,'ps':public.get_msg_gettext('Level of error')}, + {'name':'display_errors','type':1,'ps':public.get_msg_gettext('Whether to output detailed error info')}, + {'name':'cgi.fix_pathinfo','type':0,'ps':public.get_msg_gettext('Whether to turn on pathinfo')}, + {'name':'date.timezone','type':3,'ps':public.get_msg_gettext('Timezone')} + ] + phpini_file = '/www/server/php/' + get.version + '/etc/php.ini' + if public.get_webserver() == 'openlitespeed': + phpini_file = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version, get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + phpini_file = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + phpini = public.readFile(phpini_file) + if not phpini: + return public.return_msg_gettext(False,"Error reading PHP configuration file, please try to reinstall this PHP!") + result = [] + for g in gets: + rep = g['name'] + r'\s*=\s*([0-9A-Za-z_&/ ~]+)(\s*;?|\r?\n)' + tmp = re.search(rep,phpini) + if not tmp: continue + g['value'] = tmp.groups()[0] + result.append(g) + + return result + + + def get_php_config(self,get): + #取PHP配置 + get.version = get.version.replace('.','') + file = session['setupPath'] + "/php/"+get.version+"/etc/php.ini" + if public.get_webserver() == 'openlitespeed': + file = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version, get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + file = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + phpini = public.readFile(file) + file = session['setupPath'] + "/php/"+get.version+"/etc/php-fpm.conf" + phpfpm = public.readFile(file) + data = {} + try: + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" + tmp = re.search(rep,phpini).groups() + data['max'] = tmp[0] + except: + data['max'] = '50' + try: + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + tmp = re.search(rep,phpfpm).groups() + data['maxTime'] = tmp[0] + except: + data['maxTime'] = 0 + + try: + rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + tmp = re.search(rep,phpini).groups() + + if tmp[0] == '1': + data['pathinfo'] = True + else: + data['pathinfo'] = False + except: + data['pathinfo'] = False + + return data + + #提交PHP配置参数 + def SetPHPConf(self,get): + gets = ['display_errors','cgi.fix_pathinfo','date.timezone','short_open_tag','asp_tags','max_execution_time','max_input_time','memory_limit','post_max_size','file_uploads','upload_max_filesize','max_file_uploads','default_socket_timeout','error_reporting'] + filename = '/www/server/php/' + get.version + '/etc/php.ini' + reload_str = '/etc/init.d/php-fpm-' + get.version + ' reload' + ols_php_path = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version, get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + ols_php_path = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + reload_ols_str = '/usr/local/lsws/bin/lswsctrl restart' + for p in [filename,ols_php_path]: + if not p: + continue + if not os.path.exists(p): + continue + phpini = public.readFile(p) + for g in gets: + try: + rep = g + r'\s*=\s*(.+)\r?\n' + val = g+' = ' + get[g] + '\n' + phpini = re.sub(rep,val,phpini) + except: continue + + public.writeFile(p,phpini) + public.ExecShell(reload_str) + public.ExecShell(reload_ols_str) + return public.return_msg_gettext(True,'Setup successfully!') + + + # 取Session缓存方式 + def GetSessionConf(self,get): + filename = '/www/server/php/' + get.version + '/etc/php.ini' + if public.get_webserver() == 'openlitespeed': + filename = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version,get.version[0],get.version[1]) + if os.path.exists('/etc/redhat-release'): + filename = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + phpini = public.readFile(filename) + rep = r'session.save_handler\s*=\s*([0-9A-Za-z_& ~]+)(\s*;?|\r?\n)' + save_handler = re.search(rep, phpini) + if save_handler: + save_handler = save_handler.group(1) + else: + save_handler = "files" + + reppath = r'\nsession.save_path\s*=\s*"tcp\:\/\/([\w\.]+):(\d+).*\r?\n' + passrep = r'\nsession.save_path\s*=\s*"tcp://[\w\.\?\:]+=(.*)"\r?\n' + memcached = r'\nsession.save_path\s*=\s*"([\w\.]+):(\d+)"' + save_path = re.search(reppath, phpini) + if not save_path: + save_path = re.search(memcached, phpini) + passwd = re.search(passrep, phpini) + port = "" + if passwd: + passwd = passwd.group(1) + else: + passwd = "" + if save_path: + port = save_path.group(2) + save_path = save_path.group(1) + + else: + save_path = "" + return {"save_handler": save_handler, "save_path": save_path, "passwd": passwd, "port": port} + + # 设置Session缓存方式 + def SetSessionConf(self, get): + import glob + g = get.save_handler + ip = get.ip.strip() + port = get.port + passwd = get.passwd + if g != "files": + iprep = r"(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})\.(2(5[0-5]{1}|[0-4]\d{1})|[0-1]?\d{1,2})" + rep_domain = r"^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$" + if not re.search(iprep, ip) and not re.search(rep_domain, ip): + if ip != "localhost": + return public.returnMsg(False, 'Please enter the correct [domain or IP]!') + try: + port = int(port) + if port >= 65535 or port < 1: + return public.return_msg_gettext(False, 'Port range is incorrect! should be between 100-65535') + except: + return public.return_msg_gettext(False, 'Port range is incorrect! should be between 100-65535') + prep = r"[\~\`\/\=]" + if re.search(prep, passwd): + return public.return_msg_gettext(False, 'Please do NOT enter the following special characters {}', ('" ~ ` / = "')) + filename = '/www/server/php/' + get.version + '/etc/php.ini' + filename_ols = None + ols_exist = os.path.exists("/usr/local/lsws/bin/lswsctrl") + if ols_exist: + filename_ols = '/usr/local/lsws/lsphp{}/etc/php/{}.{}/litespeed/php.ini'.format(get.version, get.version[0], + get.version[1]) + if os.path.exists('/etc/redhat-release'): + filename_ols = '/usr/local/lsws/lsphp' + get.version + '/etc/php.ini' + try: + ols_php_os_path = glob.glob("/usr/local/lsws/lsphp{}/lib/php/20*".format(get.version))[0] + except: + ols_php_os_path = None + if os.path.exists("/etc/redhat-release"): + ols_php_os_path = '/usr/local/lsws/lsphp{}/lib64/php/modules/'.format(get.version) + ols_so_list = os.listdir(ols_php_os_path) + else: + ols_so_list = [] + for f in [filename,filename_ols]: + if not f: + continue + phpini = public.readFile(f) + rep = r'session.save_handler\s*=\s*(.+)\r?\n' + val = r'session.save_handler = ' + g + '\n' + phpini = re.sub(rep, val, phpini) + if not ols_exist: + if g == "memcached": + if not re.search("memcached.so", phpini): + return public.return_msg_gettext(False, 'Please install the {} extension first.', (g,)) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip,port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + if g == "memcache": + if not re.search("memcache.so", phpini): + return public.return_msg_gettext(False, 'Please install the {} extension first.', (g,)) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s"\n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + if g == "redis": + if not re.search("redis.so", phpini): + return public.return_msg_gettext(False, 'Please install the {} extension first.', (g,)) + if passwd: + passwd = "?auth=" + passwd + else: + passwd = "" + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s%s"\n' % (ip, port, passwd) + res = re.search(rep, phpini) + if res: + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + if g == "files": + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "/tmp"\n' + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + else: + if g == "memcached": + if "memcached.so" not in ols_so_list: + return public.return_msg_gettext(False, 'Please install the {} extension first.', (g,)) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "%s:%s" \n' % (ip,port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + if g == "memcache": + if "memcache.so" not in ols_so_list: + return public.return_msg_gettext(False, 'Please install the {} extension first.', (g,)) + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s"\n' % (ip, port) + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + if g == "redis": + if "redis.so" not in ols_so_list: + return public.return_msg_gettext(False, 'Please install the {} extension first.', (g,)) + if passwd: + passwd = "?auth=" + passwd + else: + passwd = "" + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "tcp://%s:%s%s"\n' % (ip, port, passwd) + res = re.search(rep, phpini) + if res: + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + if g == "files": + rep = r'\nsession.save_path\s*=\s*(.+)\r?\n' + val = r'\nsession.save_path = "/tmp"\n' + if re.search(rep, phpini): + phpini = re.sub(rep, val, phpini) + else: + phpini = re.sub('\n;session.save_path = "/tmp"', '\n;session.save_path = "/tmp"' + val, phpini) + public.writeFile(f, phpini) + public.ExecShell('/etc/init.d/php-fpm-' + get.version + ' reload') + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + # 获取Session文件数量 + def GetSessionCount(self, get): + d=["/tmp","/www/php_session"] + + count = 0 + for i in d: + if not os.path.exists(i): public.ExecShell('mkdir -p %s'%i) + list = os.listdir(i) + for l in list: + if os.path.isdir(i+"/"+l): + l1 = os.listdir(i+"/"+l) + for ll in l1: + if "sess_" in ll: + count += 1 + continue + if "sess_" in l: + count += 1 + + s = "find /tmp -mtime +1 |grep 'sess_'|wc -l" + old_file = int(public.ExecShell(s)[0].split("\n")[0]) + + s = "find /www/php_session -mtime +1 |grep 'sess_'|wc -l" + old_file += int(public.ExecShell(s)[0].split("\n")[0]) + + return {"total":count,"oldfile":old_file} + + # 删除老文件 + def DelOldSession(self,get): + s = "find /tmp -mtime +1 |grep 'sess_'|xargs rm -f" + public.ExecShell(s) + s = "find /www/php_session -mtime +1 |grep 'sess_'|xargs rm -f" + public.ExecShell(s) + # s = "find /tmp -mtime +1 |grep 'sess_'|wc -l" + # old_file_conf = int(public.ExecShell(s)[0].split("\n")[0]) + old_file_conf = self.GetSessionCount(get)["oldfile"] + if old_file_conf == 0: + return public.return_msg_gettext(True, 'Successfully deleted') + else: + return public.return_msg_gettext(True, 'Failed to delete') + + #获取面板证书 + def GetPanelSSL(self,get): + cert = {} + key_file = 'ssl/privateKey.pem' + cert_file = 'ssl/certificate.pem' + if not os.path.exists(key_file): + self.CreateSSL() + cert['privateKey'] = public.readFile(key_file) + cert['certPem'] = public.readFile(cert_file) + cert['download_root'] = False + cert['info'] = {} + if not cert['privateKey']: + cert['privateKey'] = '' + cert['certPem'] = '' + else: + cert['info'] = public.get_cert_data(cert_file) + if not cert['info']: + self.CreateSSL() + cert['info'] = public.get_cert_data(cert_file) + if cert['info']: + if cert['info']['issuer'] == '宝塔面板': + if os.path.exists('ssl/baota_root.pfx'): + cert['download_root'] = True + cert['root_password'] = public.readFile('ssl/root_password.pl') + + + + cert['rep'] = os.path.exists('ssl/input.pl') + return cert + + #保存面板证书 + def SavePanelSSL(self,get): + keyPath = 'ssl/privateKey.pem' + certPath = 'ssl/certificate.pem' + checkCert = '/tmp/cert.pl' + ssl_pl = 'data/ssl.pl' + if not 'certPem' in get: return public.returnMsg(False,'The certPem parameter is missing!') + if not 'privateKey' in get: return public.returnMsg(False,'The privateKey parameter is missing!') + public.writeFile(checkCert,get.certPem) + if get.privateKey: + public.writeFile(keyPath,get.privateKey) + if get.certPem: + public.writeFile(certPath, get.certPem) + if not public.CheckCert(checkCert): return public.return_msg_gettext(False, 'Certificate ERROR, please check!') + public.writeFile('ssl/input.pl','True') + if os.path.exists(ssl_pl): public.writeFile('data/reload.pl','True') + return public.return_msg_gettext(True, 'Certificate saved!') + + # 获取ftp端口 + def get_ftp_port(self): + # 获取FTP端口 + if 'port' in session: return session['port'] + import re + try: + file = public.GetConfigValue('setup_path') + '/pure-ftpd/etc/pure-ftpd.conf' + conf = public.readFile(file) + rep = r"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)" + port = re.search(rep, conf).groups()[0] + except: + port = '21' + session['port'] = port + return port + + # #获取配置 + # def get_config(self,get): + # data = {} + # if 'config' in session: + # session['config']['distribution'] = public.get_linux_distribution() + # session['webserver'] = public.get_webserver() + # session['config']['webserver'] = session['webserver'] + # data = session['config'] + # if not data: + # data = public.M('config').where("id=?",('1',)).field('webserver,sites_path,backup_path,status,mysql_root').find() + # data['webserver'] = public.get_webserver() + # data['distribution'] = public.get_linux_distribution() + # data['request_iptype'] = self.get_request_iptype() + # data['request_type'] = self.get_request_type() + # # return data + # return public.return_message(0, 0, data) + + + def get_config(self, get): + public.print_log('v2-get_config') + import system_v2 as system + data = {} + data.update(system.system().GetConcifInfo()) + if 'config' in session: + session['config']['distribution'] = public.get_linux_distribution() + session['webserver'] = public.get_webserver() + session['config']['webserver'] = session['webserver'] + if "basic_auth" in session['config'].keys(): + session['config']['basic_auth'] = self.get_basic_auth_stat(None) + data = session['config'] + if not data: + data = public.M('config').where("id=?", ('1',)).field( + 'webserver,sites_path,backup_path,status,mysql_root').find() + data['webserver'] = public.get_webserver() + data['distribution'] = public.get_linux_distribution() + data['request_iptype'] = self.get_request_iptype() + data['request_type'] = self.get_request_type() + data['improvement'] = public.get_improvement() + data['isSetup'] = True + data['ftpPort'] = int(self.get_ftp_port()) + data['basic_auth'] = self.get_basic_auth_stat(None) + data['recycle_bin'] = os.path.exists("data/recycle_bin.pl") + if os.path.exists(public.GetConfigValue('setup_path') + '/nginx') == False \ + and os.path.exists(public.GetConfigValue('setup_path') + '/apache') == False \ + and os.path.exists('/usr/local/lsws/bin/lswsctrl') == False \ + and os.path.exists(public.GetConfigValue('setup_path') + '/php') == False: + # 查询安装过或者是有正在安装的任务就不显示推荐安装 + num = public.M('tasks').where("name like ? or name like ? or name like ? or name like ? or name like ?", ('%nginx%', '%apache%', '%php%', '%mysql%', '%pureftpd%')).count() + if not num: + data['isSetup'] = False + p_token = self.get_token(None) + + data['app'] = {'open': p_token['open'], 'apps': p_token['apps']} + + if 'api' not in data: data['api'] = 'checked' if p_token['open'] else '' + + import panel_ssl_v2 as panelSSL + data['user_info'] = panelSSL.panelSSL().GetUserInfo(None) + data['user'] = {} + try: + data['user']['username'] = public.get_user_info()['username'] + except: + pass + data['table_header'] = self.get_table_header(None) + data["debug"] = "checked" if self.get_debug() else "" + data["left_title"] = public.readFile("data/title.pl") if os.path.exists("data/title.pl") else "" + + from password import password + data["username"] = password().get_panel_username(get) + data["webname"] = public.GetConfigValue("title") + + sess_out_path = 'data/session_timeout.pl' + if not os.path.exists(sess_out_path): + public.writeFile(sess_out_path, '86400') + s_time_tmp = public.readFile(sess_out_path) + try: + s_time = int(s_time_tmp) + except: + public.writeFile(sess_out_path, '86400') + s_time = 86400 + + # data['session_timeout_source'] = s_time + data['session_timeout'] = s_time + data['SSL'] = os.path.exists('data/ssl.pl') + + #增加防篡改安装信息 + data['tamper_core']={} + data['tamper_core']['setup']=os.path.exists('/www/server/panel/plugin/tamper_core') + + return public.return_message(0,0,data) + + @staticmethod + def show_time_by_seconds(seconds: int) -> str: + if seconds // (60 * 60 * 24) >= 4: # 4天及以上的,使用天+小时展示 + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + if hours == 0: + return "%d天" % days + if minutes == 0: + return "%d天%d小时" % (days, hours) + hours = hours + minutes / 60 + return "%d天%.1f小时" % (days, hours) + + elif seconds // (60 * 60) >= 1: # 3天以内1小时以上,用小时+分钟表示 + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + if minutes == 0: + return "%d小时" % hours + return "%d小时%d分钟" % (hours, minutes) + else: # 1小时以内,用分钟表示 + minutes, seconds = divmod(seconds, 60) + return "%d分钟" % minutes + + + # 获取debug状态 + def get_debug(self): + debug_path = 'data/debug.pl' + return ((os.path.exists(debug_path) and (public.readFile(debug_path) == "True"))) + + + def get_table_header(self, get): + """获取表头""" + table_name = "PHP_Site" + if hasattr(get, "table_name"): + table_name = get.table_name + header_file = "{}/data/table_header_conf.json".format(public.get_panel_path()) + if not os.path.exists(header_file): + header_data = {table_name: ''} + else: + try: + header_data = json.loads(public.readFile(header_file)) + if not isinstance(header_data, dict): + header_data = {} + header_data = {table_name: header_data.get(table_name, '')} + except: + header_data = {table_name: ''} + + if header_data.get("PHP_Site", None): + try: + s_table = json.loads(header_data["PHP_Site"]) + for d in s_table: + if d["title"] == "流量": + if public.GetWebServer() == "openlitespeed": + d["disabled"] = True + d["value"] = False + else: + d["disabled"] = False + header_data["PHP_Site"] = json.dumps(s_table) + except: + pass + + return header_data + + + #取面板错误日志 + def get_error_logs(self,get): + return public.GetNumLines('logs/error.log',2000) + + def is_pro(self,get): + import panelAuth,json + pdata = panelAuth.panelAuth().create_serverid(None) + url = public.GetConfigValue('home') + '/api/panel/is_pro' + pluginTmp = public.httpPost(url,pdata) + pluginInfo = json.loads(pluginTmp) + return pluginInfo + + def get_token(self,get): + import panelApi + return panelApi.panelApi().get_token(get) + + def set_token(self,get): + import panelApi + return panelApi.panelApi().set_token(get) + + def get_tmp_token(self,get): + import panelApi + return panelApi.panelApi().get_tmp_token(get) + + def GetNginxValue(self,get): + n = nginx.nginx() + return n.GetNginxValue() + + def SetNginxValue(self,get): + n = nginx.nginx() + return n.SetNginxValue(get) + + def GetApacheValue(self,get): + a = apache.apache() + return a.GetApacheValue() + + def SetApacheValue(self,get): + a = apache.apache() + return a.SetApacheValue(get) + + def get_ols_value(self,get): + a = ols.ols() + return a.get_value(get) + + def set_ols_value(self,get): + a = ols.ols() + return a.set_value(get) + + def get_ols_private_cache(self,get): + a = ols.ols() + return a.get_private_cache(get) + + def get_ols_static_cache(self,get): + a = ols.ols() + return a.get_static_cache(get) + + def set_ols_static_cache(self,get): + a = ols.ols() + return a.set_static_cache(get) + + def switch_ols_private_cache(self,get): + a = ols.ols() + return a.switch_private_cache(get) + + def set_ols_private_cache(self,get): + a = ols.ols() + return a.set_private_cache(get) + + def get_ols_private_cache_status(self,get): + a = ols.ols() + return a.get_private_cache_status(get) + + def get_ipv6_listen(self,get): + return os.path.exists('data/ipv6.pl') + + def set_ipv6_status(self,get): + ipv6_file = 'data/ipv6.pl' + if self.get_ipv6_listen(get): + os.remove(ipv6_file) + public.write_log_gettext('Panel setting', 'Disable IPv6 compatibility of the panel!') + else: + public.writeFile(ipv6_file, 'True') + public.write_log_gettext('Panel setting', 'Enable IPv6 compatibility of the panel!') + public.restart_panel() + return public.return_msg_gettext(True, 'Setup successfully!') + + #自动补充CLI模式下的PHP版本 + def auto_cli_php_version(self,get): + import panel_site_v2 as panelSite + php_versions = panelSite.panelSite().GetPHPVersion(get,False) + php_bin_src = "/www/server/php/%s/bin/php" % php_versions[-1]['version'] + if not os.path.exists(php_bin_src): return public.return_message(-1,0,'PHP is not installed') + get.php_version = php_versions[-1]['version'] + self.set_cli_php_version(get) + return public.return_message(0,0,php_versions[-1]) + + #获取CLI模式下的PHP版本 + def get_cli_php_version(self,get): + php_bin = '/usr/bin/php' + if not os.path.exists(php_bin) or not os.path.islink(php_bin): return self.auto_cli_php_version(get) + link_re = os.readlink(php_bin) + if not os.path.exists(link_re): return self.auto_cli_php_version(get) + import panel_site_v2 as panelSite + php_versions = panelSite.panelSite().GetPHPVersion(get,False) + if len(php_versions)==0: + return public.return_message(-1,0,'Failed to get php version!') + del(php_versions[0]) + for v in php_versions: + if link_re.find(v['version']) != -1: return public.return_message(0,0,{"select":v,"versions":php_versions}) + return_message=self.auto_cli_php_version(get) + return public.return_message(0,0,{"select":return_message['message'],"versions":php_versions}) + + #设置CLI模式下的PHP版本 + def set_cli_php_version(self,get): + # 校验参数 + try: + get.validate([ + Param('php_version').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + php_bin = '/usr/bin/php' + php_bin_src = "/www/server/php/%s/bin/php" % get.php_version + php_ize = '/usr/bin/phpize' + php_ize_src = "/www/server/php/%s/bin/phpize" % get.php_version + php_fpm = '/usr/bin/php-fpm' + php_fpm_src = "/www/server/php/%s/sbin/php-fpm" % get.php_version + php_pecl = '/usr/bin/pecl' + php_pecl_src = "/www/server/php/%s/bin/pecl" % get.php_version + php_pear = '/usr/bin/pear' + php_pear_src = "/www/server/php/%s/bin/pear" % get.php_version + php_cli_ini = '/etc/php-cli.ini' + php_cli_ini_src = "/www/server/php/%s/etc/php-cli.ini" % get.php_version + if not os.path.exists(php_bin_src): return public.return_message(-1, 0,'Specified PHP version not installed') + is_chattr = public.ExecShell('lsattr /usr|grep /usr/bin')[0].find('-i-') + if is_chattr != -1: public.ExecShell('chattr -i /usr/bin') + public.ExecShell("rm -f " + php_bin + ' '+ php_ize + ' ' + php_fpm + ' ' + php_pecl + ' ' + php_pear + ' ' + php_cli_ini) + public.ExecShell("ln -sf %s %s" % (php_bin_src,php_bin)) + public.ExecShell("ln -sf %s %s" % (php_ize_src,php_ize)) + public.ExecShell("ln -sf %s %s" % (php_fpm_src,php_fpm)) + public.ExecShell("ln -sf %s %s" % (php_pecl_src,php_pecl)) + public.ExecShell("ln -sf %s %s" % (php_pear_src,php_pear)) + public.ExecShell("ln -sf %s %s" % (php_cli_ini_src,php_cli_ini)) + import jobs_v2 as jobs + jobs.set_php_cli_env() + if is_chattr != -1: public.ExecShell('chattr +i /usr/bin') + public.write_log_gettext('Panel settings','Set the PHP-CLI version to: {}',(get.php_version,)) + return public.return_message(0, 0,'Setup successfully!') + + + #获取BasicAuth状态 + def get_basic_auth_stat(self,get): + path = 'config/basic_auth.json' + is_install = True + result = {"basic_user":"","basic_pwd":"","open":False,"is_install":is_install} + if not os.path.exists(path): return result + try: + ba_conf = json.loads(public.readFile(path)) + except: + os.remove(path) + return result + ba_conf['is_install'] = is_install + return ba_conf + + #设置BasicAuth + def set_basic_auth(self,get): + is_open = False + if get.open == 'True': is_open = True + tips = '_bt.cn' + path = 'config/basic_auth.json' + ba_conf = None + if is_open: + if not get.basic_user.strip() or not get.basic_pwd.strip(): return public.returnMsg(False,'BasicAuth authentication username and password cannot be empty!') + if os.path.exists(path): + try: + ba_conf = json.loads(public.readFile(path)) + except: + os.remove(path) + + if not ba_conf: + ba_conf = {"basic_user":public.md5(get.basic_user.strip() + tips),"basic_pwd":public.md5(get.basic_pwd.strip() + tips),"open":is_open} + else: + if get.basic_user: ba_conf['basic_user'] = public.md5(get.basic_user.strip() + tips) + if get.basic_pwd: ba_conf['basic_pwd'] = public.md5(get.basic_pwd.strip() + tips) + ba_conf['open'] = is_open + + public.writeFile(path,json.dumps(ba_conf)) + os.chmod(path,384) + public.write_log_gettext('Panel settings','Set the BasicAuth status to: {}', (is_open,)) + public.add_security_logs('Panel settings',' Set the BasicAuth status to: %s' % is_open) + public.writeFile('data/reload.pl','True') + return public.return_msg_gettext(True,'Setup successfully!') + + # xss 防御 + def xsssec(self,text): + return text.replace('<', '<').replace('>', '>') + + #取面板运行日志 + def get_panel_error_logs(self,get): + filename = 'logs/error.log' + if not os.path.exists(filename): return public.return_msg_gettext(False,"Logs emptied") + result = public.GetNumLines(filename,2000) + return public.returnMsg(True,self.xsssec(result)) + + #清空面板运行日志 + def clean_panel_error_logs(self,get): + filename = 'logs/error.log' + public.writeFile(filename,'') + public.write_log_gettext('Panel settings','Clearing log info') + public.add_security_logs('Panel settings', 'Clearing log info') + return public.return_msg_gettext(True,'Cleared!') + + # 获取lets证书 + def get_cert_source(self,get): + import setPanelLets + sp = setPanelLets.setPanelLets() + spg = sp.get_cert_source() + return spg + + #设置debug模式 + def set_debug(self,get): + debug_path = 'data/debug.pl' + if os.path.exists(debug_path): + t_str = 'Close' + os.remove(debug_path) + else: + t_str = 'Open' + public.writeFile(debug_path,'True') + public.write_log_gettext('Panel configuration','{} Developer mode(DeBug)',(t_str,)) + public.restart_panel() + return public.return_msg_gettext(True,'Setup successfully!') + + + #设置离线模式 + def set_local(self,get): + d_path = 'data/not_network.pl' + if os.path.exists(d_path): + t_str = 'Close' + os.remove(d_path) + else: + t_str = 'Open' + public.writeFile(d_path,'True') + public.write_log_gettext('Panel configuration','{} Offline mode',(t_str,)) + return public.return_msg_gettext(True,'Setup successfully!') + + # 修改.user.ini文件 + def _edit_user_ini(self,file,s_conf,act,session_path): + public.ExecShell("chattr -i {}".format(file)) + conf = public.readFile(file) + if act == "1": + if "session.save_path" in conf: + return False + conf = conf + ":{}/".format(session_path) + conf = conf + "\n" + s_conf + else: + rep = "\n*session.save_path(.|\n)*files" + rep1 = ":{}".format(session_path) + conf = re.sub(rep,"",conf) + conf = re.sub(rep1,"",conf) + public.writeFile(file, conf) + public.ExecShell("chattr +i {}".format(file)) + + # 设置php_session存放到独立文件夹 + def set_php_session_path(self,get): + ''' + get.id site id + get.act 0/1 + :param get: + :return: + ''' + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + Param('act').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + if public.get_webserver() == 'openlitespeed': + return public.return_message(-1,0, 'The current web server is openlitespeed. This function is not supported yet.') + import panel_site_v2 + site_info = public.M('sites').where('id=?', (get.id,)).field('name,path').find() + session_path = "/www/php_session/{}".format(site_info["name"]) + if not os.path.exists(session_path): + os.makedirs(session_path) + public.ExecShell('chown www.www {}'.format(session_path)) + run_path = panel_site_v2.panelSite().GetSiteRunPath(get)['message']["runPath"] + user_ini_file = "{site_path}{run_path}/.user.ini".format(site_path=site_info["path"], run_path=run_path) + conf = "session.save_path={}/\nsession.save_handler = files".format(session_path) + if get.act == "1": + if not os.path.exists(user_ini_file): + public.writeFile(user_ini_file,conf) + public.ExecShell("chattr +i {}".format(user_ini_file)) + public.print_log("5") + return public.return_message(0,0,'Setup successfully!') + self._edit_user_ini(user_ini_file,conf,get.act,session_path) + return public.return_message(0,0, 'Setup successfully!') + else: + self._edit_user_ini(user_ini_file,conf,get.act,session_path) + return public.return_message(0,0, 'Setup successfully!') + + # 获取php_session是否存放到独立文件夹 + def get_php_session_path(self,get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import panelSite + site_info = public.M('sites').where('id=?', (get.id,)).field('name,path').find() + if site_info: + run_path = panelSite.panelSite().GetSiteRunPath(get)["runPath"] + user_ini_file = "{site_path}{run_path}/.user.ini".format(site_path=site_info["path"], run_path=run_path) + conf = public.readFile(user_ini_file) + if conf and "session.save_path" in conf: + return public.return_message(0,0,True) + return public.return_message(0,0,False) + + def _create_key(self): + get_token = pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator + public.writeFile(self._key_file,get_token) + username = self.get_random() + public.writeFile(self._username_file, username) + + def get_key(self,get): + key = public.readFile(self._key_file) + username = public.readFile(self._username_file) + if not key: + return public.return_msg_gettext(False, 'The key does not exist. Please turn on and try again.') + if not username: + return public.return_msg_gettext(False, 'The username does not exist. Please turn on and try again.') + return {"key":key,"username":username} + + def get_random(self): + import random + seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + sa = [] + for _ in range(8): + sa.append(random.choice(seed)) + salt = ''.join(sa) + return salt + + def set_two_step_auth(self,get): + if not hasattr(get,"act") or not get.act: + return public.return_msg_gettext(False, 'Please enter the operation mode') + if get.act == "1": + if not os.path.exists(self._core_fle_path): + os.makedirs(self._core_fle_path) + username = public.readFile(self._username_file) + if not os.path.exists(self._bk_key_file): + secret_key = public.readFile(self._key_file) + if not secret_key or not username: + self._create_key() + else: + os.rename(self._bk_key_file,self._key_file) + secret_key = public.readFile(self._key_file) + username = public.readFile(self._username_file) + local_ip = public.GetLocalIp() + if not secret_key: + return public.return_msg_gettext(False,"Failed to generate key or username. Please check if the hard disk space is insufficient or the directory cannot be written.[ {} ]",(self._setup_path+"/data/",)) + try: + try: + panel_name = json.loads(public.readFile(self._setup_path+'/config/config.json'))['title'] + except: + panel_name = 'aaPanel' + data = pyotp.totp.TOTP(secret_key).provisioning_uri(username, issuer_name='{}--{}'.format(panel_name,local_ip)) + public.writeFile(self._core_fle_path+'/qrcode.txt',str(data)) + return public.return_msg_gettext(True, 'Setup successfully!') + except Exception as e: + return public.return_msg_gettext(False, e) + else: + if os.path.exists(self._key_file): + os.rename(self._key_file,self._bk_key_file) + return public.return_msg_gettext(True, 'Setup successfully!') + + # 检测是否开启双因素验证 + def check_two_step(self,get): + secret_key = public.readFile(self._key_file) + if not secret_key: + return public.return_msg_gettext(False, 'Did not open Google authentication') + return public.return_msg_gettext(True, 'Google authentication has been turned on') + + # 读取二维码data + def get_qrcode_data(self,get): + data = public.readFile(self._core_fle_path + '/qrcode.txt') + if data: + return data + return public.return_msg_gettext(True, 'No QR code data, please re-open') + + # 设置是否云控打开 + def set_coll_open(self,get): + if not 'coll_show' in get: return public.return_msg_gettext(False,'Parameter ERROR!') + if get.coll_show == 'True': + session['tmp_login'] = True + else: + session['tmp_login'] = False + return public.return_msg_gettext(True,'Setup successfully!') + + + # 是否显示软件推荐 + def show_recommend(self,get): + pfile = 'data/not_recommend.pl' + if os.path.exists(pfile): + os.remove(pfile) + else: + public.writeFile(pfile,'True') + return public.return_msg_gettext(True,'Setup successfully!') + + # 获取菜单列表 + def get_menu_list(self, get): + ''' + @name 获取菜单列表 + @author hwliang<2020-08-31> + @param get + @return list + ''' + menu_file = 'config/menu.json' + hide_menu_file = 'config/hide_menu.json' + data = json.loads(public.ReadFile(menu_file)) + if not os.path.exists(hide_menu_file): + public.writeFile(hide_menu_file, '[]') + hide_menu = public.ReadFile(hide_menu_file) + if not hide_menu: + hide_menu = [] + else: + hide_menu = json.loads(hide_menu) + result = [] + for d in data: + tmp = {} + tmp['id'] = d['id'] + tmp['title'] = d['title'] + tmp['show'] = not d['id'] in hide_menu + tmp['sort'] = d['sort'] + result.append(tmp) + + menus = sorted(result, key=lambda x: x['sort']) + return menus + + # 设置隐藏菜单列表 + def set_hide_menu_list(self, get): + ''' + @name 设置隐藏菜单列表 + @author hwliang<2020-08-31> + @param get { + hide_list: json 所有不显示的菜单ID + } + @return dict + ''' + hide_menu_file = 'config/hide_menu.json' + not_hide_id = ["dologin", "memuAconfig", "memuAsoft", "memuA"] # 禁止隐藏的菜单 + + hide_list = json.loads(get.hide_list) + hide_menu = [] + for h in hide_list: + if h in not_hide_id: continue + hide_menu.append(h) + public.writeFile(hide_menu_file, json.dumps(hide_menu)) + public.write_log_gettext('Panel setting', 'Successfully modify the panel menu display list') + return public.return_msg_gettext(True, 'Setup successfully!') + + # 获取临时登录列表 + def get_temp_login(self, args): + ''' + @name 获取临时登录列表 + @author hwliang<2020-09-2> + @return dict + ''' + if 'tmp_login_expire' in session: return public.return_msg_gettext(False, 'Permission denied!') + public.M('temp_login').where('state=? and expire + @return dict + ''' + s_time = int(time.time()) + expire_time = get.expire_time if "expire_time" in get else s_time + 3600 + if 'tmp_login_expire' in session: return public.return_msg_gettext(False, 'Permission denied!') + public.M('temp_login').where('state=? and expire>?', (0, s_time)).delete() + token = public.GetRandomString(48) + salt = public.GetRandomString(12) + + pdata = { + 'token': public.md5(token + salt), + 'salt': salt, + 'state': 0, + 'login_time': 0, + 'login_addr': '', + 'expire': int(expire_time), + 'addtime': s_time + } + + if not public.M('temp_login').count(): + pdata['id'] = 101 + + if public.M('temp_login').insert(pdata): + public.write_log_gettext('Panel setting', 'Generate temporary connection, expiration time: {}',(public.format_date(times=pdata['expire']),)) + return {'status': True, 'msg': public.get_msg_gettext('Temporary login URL has been generated'), 'token': token, 'expire': pdata['expire']} + return public.return_msg_gettext(False, 'Failed to generate temporary login URL') + + # 删除临时登录 + def remove_temp_login(self, args): + ''' + @name 删除临时登录 + @author hwliang<2020-09-2> + @param args{ + id: int<临时登录ID> + } + @return dict + ''' + if 'tmp_login_expire' in session: return public.return_msg_gettext(False, 'Permission denied!') + id = int(args.id) + if public.M('temp_login').where('id=?', (id,)).delete(): + public.write_log_gettext('Panel setting', 'Delete temporary login URL') + return public.return_msg_gettext(True, 'Successfully deleted') + return public.return_msg_gettext(False, 'Failed to delete') + + # 强制弹出指定临时登录 + def clear_temp_login(self, args): + ''' + @name 强制登出 + @author hwliang<2020-09-2> + @param args{ + id: int<临时登录ID> + } + @return dict + ''' + if 'tmp_login_expire' in session: return public.return_msg_gettext(False, 'Permission denied!') + id = int(args.id) + s_file = 'data/session/{}'.format(id) + if os.path.exists(s_file): + os.remove(s_file) + public.write_log_gettext('Panel setting', 'Force logout of temporary users:{1}',(str(id),)) + return public.return_msg_gettext(True, 'Temporary user has been forcibly logged out:{}',(str(id),)) + public.return_msg_gettext(False, 'The specified user is not currently logged in!') + + # 查看临时授权操作日志 + def get_temp_login_logs(self, args): + ''' + @name 查看临时授权操作日志 + @author hwliang<2020-09-2> + @param args{ + id: int<临时登录ID> + } + @return dict + ''' + if 'tmp_login_expire' in session: return public.return_msg_gettext(False, 'Permission denied!') + id = int(args.id) + data = public.M('logs').where('uid=?', (id,)).order('id desc').select() + return data + + def add_nginx_access_log_format(self,args): + n = nginx.nginx() + return n.add_nginx_access_log_format(args) + + def del_nginx_access_log_format(self,args): + n = nginx.nginx() + return n.del_nginx_access_log_format(args) + + def get_nginx_access_log_format(self,args): + n = nginx.nginx() + return n.get_nginx_access_log_format(args) + + def set_format_log_to_website(self,args): + n = nginx.nginx() + return n.set_format_log_to_website(args) + + def get_nginx_access_log_format_parameter(self,args): + n = nginx.nginx() + return n.get_nginx_access_log_format_parameter(args) + + def add_httpd_access_log_format(self,args): + a = apache.apache() + return a.add_httpd_access_log_format(args) + + def del_httpd_access_log_format(self,args): + a = apache.apache() + return a.del_httpd_access_log_format(args) + + def get_httpd_access_log_format(self,args): + a = apache.apache() + return a.get_httpd_access_log_format(args) + + def set_httpd_format_log_to_website(self,args): + a = apache.apache() + return a.set_httpd_format_log_to_website(args) + + def get_httpd_access_log_format_parameter(self,args): + a = apache.apache() + return a.get_httpd_access_log_format_parameter(args) + + def get_file_deny(self,args): + import file_execute_deny_v2 + p = file_execute_deny_v2.FileExecuteDeny() + return p.get_file_deny(args) + + def set_file_deny(self,args): + import file_execute_deny_v2 + p = file_execute_deny_v2.FileExecuteDeny() + return p.set_file_deny(args) + + def del_file_deny(self,args): + import file_execute_deny_v2 + p = file_execute_deny_v2.FileExecuteDeny() + return p.del_file_deny(args) + + #查看告警 + def get_login_send(self, get): + send_type = "" + login_send_type_conf = "/www/server/panel/data/panel_login_send.pl" + if os.path.exists(login_send_type_conf): + send_type = public.ReadFile(login_send_type_conf).strip() + else: + + if os.path.exists("/www/server/panel/data/login_send_type.pl"): + send_type = public.readFile("/www/server/panel/data/login_send_type.pl") + else: + if os.path.exists('/www/server/panel/data/login_send_mail.pl'): + send_type = "mail" + if os.path.exists('/www/server/panel/data/login_send_dingding.pl'): + send_type = "dingding" + return public.returnMsg(True, send_type) + + + #取消告警 + def clear_login_send(self,get): + type = get.type.strip() + if type == 'mail': + if os.path.exists("/www/server/panel/data/login_send_mail.pl"): + os.remove("/www/server/panel/data/login_send_mail.pl") + elif type == 'dingding': + if os.path.exists("/www/server/panel/data/login_send_dingding.pl"): + os.remove("/www/server/panel/data/login_send_dingding.pl") + + login_send_type_conf = "/www/server/panel/data/login_send_type.pl" + if os.path.exists(login_send_type_conf): + os.remove(login_send_type_conf) + login_send_type_conf = "/www/server/panel/data/panel_login_send.pl" + if os.path.exists(login_send_type_conf): + os.remove(login_send_type_conf) + return public.returnMsg(True, 'Canceling the login alarm succeeded.!') + + + def get_login_area(self,get): + """ + @获取面板登录告警 + @return + login_status 是否开启面板登录告警 + login_area 是否开启面板异地登录告警 + """ + result = {} + result['login_status'] = self.get_login_send(get)['msg'] + + result['login_area'] = '' + sfile = '{}/data/panel_login_area.pl'.format(public.get_panel_path()) + if os.path.exists(sfile): + result['login_area_status'] = public.readFile(sfile) + return result + + + def set_login_area(self,get): + """ + @name 设置异地登录告警 + @param get + """ + sfile = '{}/data/panel_login_area.pl'.format(public.get_panel_path()) + set_type=get.type.strip() + obj = public.init_msg(set_type) + if not obj: + return public.returnMsg(False, "The alarm module is not installed.") + + public.writeFile(sfile, set_type) + return public.returnMsg(True, 'successfully set') + + def get_login_area_list(self,get): + """ + @name 获取面板常用地区登录 + + """ + data = {} + sfile = '{}/data/panel_login_area.json'.format(public.get_panel_path()) + try: + data = json.loads(public.readFile(sfile)) + except:pass + + result = [] + for key in data.keys(): + result.append({'area':key,'count':data[key]}) + + result = sorted(result, key=lambda x: x['count'], reverse=True) + return result + + def clear_login_list(self,get): + """ + @name 清理常用登录地区 + """ + sfile = '{}/data/panel_login_area.json'.format(public.get_panel_path()) + if os.path.exists(sfile): + os.remove(sfile) + return public.returnMsg(True,'Successful operation.') + + + + + + # def get_login_send(self,get): + # result={} + # import time + # time.sleep(0.01) + # if os.path.exists('/www/server/panel/data/login_send_mail.pl'): + # result['mail']=True + # else: + # result['mail']=False + # if os.path.exists('/www/server/panel/data/login_send_dingding.pl'): + # result['dingding']=True + # else: + # result['dingding']=False + # if result['mail'] or result['dingding']: + # return public.returnMsg(True, result) + # return public.returnMsg(False, result) + + #设置告警 + def set_login_send(self,get): + login_send_type_conf = "/www/server/panel/data/panel_login_send.pl" + + set_type=get.type.strip() + msg_configs = self.get_msg_configs(get) + if set_type not in msg_configs.keys(): + return public.returnMsg(False,'This send type is not supported') + _conf = msg_configs[set_type] + if "data" not in _conf or not _conf["data"]: + return public.returnMsg(False, "This channel is not configured, please select again.") + + from panelMessage import panelMessage + pm = panelMessage() + obj = pm.init_msg_module(set_type) + if not obj: + return public.returnMsg(False, "The message channel is not installed.") + + public.writeFile(login_send_type_conf, set_type) + return public.returnMsg(True, 'successfully set') + + # if type=='mail': + # if not os.path.exists("/www/server/panel/data/login_send_mail.pl"): + # os.mknod("/www/server/panel/data/login_send_mail.pl") + # if os.path.exists("/www/server/panel/data/login_send_dingding.pl"): + # os.remove("/www/server/panel/data/login_send_dingding.pl") + # return public.returnMsg(True, '设置成功') + # elif type=='dingding': + # if not os.path.exists("/www/server/panel/data/login_send_dingding.pl"): + # os.mknod("/www/server/panel/data/login_send_dingding.pl") + # if os.path.exists("/www/server/panel/data/login_send_mail.pl"): + # os.remove("/www/server/panel/data/login_send_mail.pl") + # return public.returnMsg(True, '设置成功') + # else: + # return public.returnMsg(False,'不支持该发送类型') + + + + #告警日志 + def get_login_log(self,get): + public.create_logs() + import page + page = page.Page() + count = public.M('logs2').where('type=?', (u'aapanel login reminder',)).field('log,addtime').count() + limit = 7 + info = {} + info['count'] = count + info['row'] = limit + info['p'] = 1 + if hasattr(get, 'p'): + info['p'] = int(get['p']) + info['uri'] = get + info['return_js'] = '' + if hasattr(get, 'tojs'): + info['return_js'] = get.tojs + data = {} + # 获取分页数据 + data['page'] = page.GetPage(info, '1,2,3,4,5,8') + data['data'] = public.M('logs2').where('type=?', (u'aapanel login reminder',)).field('log,addtime').order('id desc').limit( + str(page.SHIFT) + ',' + str(page.ROW)).field('log,addtime').select() + return data + + #白名单设置 + def login_ipwhite(self,get): + type=get.type + if type=='get': + return self.get_login_ipwhite(get) + if type=='add': + return self.add_login_ipwhite(get) + if type=='del': + return self.del_login_ipwhite(get) + if type=='clear': + return self.clear_login_ipwhite(get) + + #查看IP白名单 + def get_login_ipwhite(self,get): + try: + path='/www/server/panel/data/send_login_white.json' + ip_white=json.loads(public.ReadFile('/www/server/panel/data/send_login_white.json')) + if not ip_white:return public.return_msg_gettext(True, []) + return public.return_msg_gettext(True, ip_white) + except: + public.WriteFile(path, '[]') + return public.return_msg_gettext(True, []) + + def add_login_ipwhite(self,get): + ip=get.ip.strip() + try: + path = '/www/server/panel/data/send_login_white.json' + ip_white = json.loads(public.ReadFile('/www/server/panel/data/send_login_white.json')) + if not ip in ip_white: + ip_white.append(ip) + public.WriteFile(path, json.dumps(ip_white)) + return public.return_msg_gettext(True, "Setup successfully!") + except: + public.WriteFile(path, json.dumps([ip])) + return public.return_msg_gettext(True, "Setup successfully!") + + def del_login_ipwhite(self,get): + ip = get.ip.strip() + try: + path = '/www/server/panel/data/send_login_white.json' + ip_white = json.loads(public.ReadFile('/www/server/panel/data/send_login_white.json')) + if ip in ip_white: + ip_white.remove(ip) + public.WriteFile(path, json.dumps(ip_white)) + return public.return_msg_gettext(True, "Successfully deleted!") + except: + public.WriteFile(path, json.dumps([])) + return public.return_msg_gettext(True, "Successfully deleted!") + + def clear_login_ipwhite(self,get): + path = '/www/server/panel/data/send_login_white.json' + public.WriteFile(path, json.dumps([])) + return public.return_msg_gettext(True, "Successfully created") + + def get_panel_ssl_status(self,get): + import os + if os.path.exists(self._setup_path+'/data/ssl.pl'): + return public.return_message(0,0,'success') + return public.return_message(-1,0,'false') + + def set_ssl_verify(self,get): + """ + 设置双向认证 + """ + sslConf = 'data/ssl_verify_data.pl' + status = int(get.status) + if status: + if not os.path.exists('data/ssl.pl'): return public.returnMsg(False,'The panel SSL function needs to be enabled first!') + public.writeFile(sslConf,'True') + else: + if os.path.exists(sslConf): os.remove(sslConf) + if 'crl' in get and 'ca' in get: + crl = 'ssl/crl.pem' + ca = 'ssl/ca.pem' + if get.crl: + public.writeFile(crl,get.crl.strip()) + if get.ca: + public.writeFile(ca,get.ca.strip()) + return public.returnMsg(True,'The panel two-way authentication certificate has been saved!') + else: + msg = 'Enable' + if not status:msg = 'Disable' + return public.returnMsg(True,'Panel two-way authentication {} succeeded!'.format(msg)) + + def get_ssl_verify(self, get): + """ + 获取双向认证 + """ + result = {'status': False, 'ca': '', 'crl': ''} + sslConf = 'data/ssl_verify_data.pl' + if os.path.exists(sslConf): result['status'] = True + + ca = 'ssl/ca.pem' + crl = 'ssl/crl.pem' + if os.path.exists(crl): + result['crl'] = public.readFile(crl) + if os.path.exists(crl): + result['ca'] = public.readFile(ca) + return result + + + def set_not_auth_status(self,get): + ''' + @name 设置未认证时的响应状态 + @author hwliang<2021-12-16> + @param status_code 状态码 + @return dict + ''' + if not 'status_code' in get: + return public.return_msg_gettext(False,'Parameter ERROR!') + + if re.match(r"^\d+$", get.status_code): + status_code = int(get.status_code) + if status_code != 0: + if status_code < 100 or status_code > 999: + return public.return_msg_gettext(False,'Parameter ERROR!') + else: + return public.return_msg_gettext(False,'Parameter ERROR!') + + public.save_config('abort',get.status_code) + public.write_log_gettext('Panel configuration','Set the unauthorized response status to:{}'.format(get.status_code)) + return public.return_msg_gettext(True,'Setup successfully!') + + def get_not_auth_status(self): + ''' + @name 获取未认证时的响应状态 + @author hwliang<2021-12-16> + @return int + ''' + try: + status_code = int(public.read_config('abort')) + return status_code + except: + return 404 + + def get_request_iptype(self,get = None): + ''' + @name 获取云端请求线路 + @author hwliang<2022-02-09> + @return auto/ipv4/ipv6 + ''' + + v4_file = '{}/data/v4.pl'.format(public.get_panel_path()) + if not os.path.exists(v4_file): return 'auto' + iptype = public.readFile(v4_file).strip() + if not iptype: return 'auto' + if iptype == '-4': return 'ipv4' + return 'ipv6' + + def get_request_type(self,get= None): + ''' + @name 获取云端请求方式 + @author hwliang<2022-02-09> + @return python/curl/php + ''' + http_type_file = '{}/data/http_type.pl'.format(public.get_panel_path()) + if not os.path.exists(http_type_file): return 'python' + http_type = public.readFile(http_type_file).strip() + if not http_type: + os.remove(http_type_file) + return 'python' + return http_type + + def get_msg_configs(self,get): + """ + 获取消息通道配置列表 + """ + cpath = 'data/msg.json' + + #cpath = '{}/data/msg.json'.format(public.get_panel_path()) + example = 'config/examples/msg.example.json' + + if not os.path.exists(cpath) and os.path.exists(example): + import shutil + shutil.copy(example, cpath) + try: + # 配置文件异常处理 + json.loads(public.readFile(cpath)) + except: + if os.path.exists(cpath): os.remove(cpath) + data = {} + if os.path.exists(cpath): + msgs = json.loads(public.readFile(cpath)) + + for x in msgs: + x['data'] = {} + x['setup'] = False + x['info'] = False + key = x['name'] + try: + obj = public.init_msg(x['name']) + if obj: + x['setup'] = True + x['data'] = obj.get_config(None) + x['info'] = obj.get_version_info(None) + except : + pass + data[key] = x + return data + + def get_module_template(self,get): + """ + 获取模块模板 + """ + panelPath = public.get_panel_path() + module_name = get.module_name + sfile = '{}/class/msg/{}.html'.format(panelPath, module_name) + if not os.path.exists(sfile): + return public.returnMsg(False, 'The template file does not exist.') + + if module_name in ["sms"]: + + obj = public.init_msg(module_name) + if obj: + args = public.dict_obj() + args.reload = True + data = obj.get_config(args) + from flask import render_template_string + shtml = public.readFile(sfile) + return public.returnMsg(True, render_template_string(shtml, data=data)) + else: + shtml = public.readFile(sfile) + return public.returnMsg(True, shtml) + + + + def set_default_channel(self,get): + """ + 设置默认消息通道 + """ + default_channel_pl = "/www/server/panel/data/default_msg_channel.pl" + + new_channel = get.channel + default = False + if "default" in get: + _default = get.default + if not _default or _default in ["false"]: + default = False + else: + default = True + + ori_default_channel = "" + if os.path.exists(default_channel_pl): + ori_default_channel = public.readFile(ori_default_channel) + + if default: + # 设置为默认 + from panelMessage import panelMessage + pm = panelMessage() + obj = pm.init_msg_module(new_channel) + if not obj: return public.returnMsg(False, 'Setup failed, [{}] is not installed'.format(new_channel)) + + public.writeFile(default_channel_pl, new_channel) + if ori_default_channel: + return public.returnMsg(True, 'Successfully changed [{}] to [{}] panel default notification.'.format(ori_default_channel, new_channel)) + else: + return public.returnMsg(True, '[{}] has been set as the default notification.'.format(new_channel)) + else: + # 取消默认设置 + if os.path.exists(default_channel_pl): + os.remove(default_channel_pl) + return public.returnMsg(True, "[{}] has been removed as panel default notification.".format(new_channel)) + + def set_msg_config(self,get): + """ + 设置消息通道配置 + """ + from panelMessage import panelMessage + pm = panelMessage() + obj = pm.init_msg_module(get.name) + if not obj: return public.returnMsg(False, 'Setup failed, [{}] is not installed'.format(get.name)) + return obj.set_config(get) + + # def install_msg_module(self,get): + # """ + # 安装/更新消息通道模块 + # @name 需要安装的模块名称 + # """ + # module_name = "" + # try: + # module_name = get.name + # down_url = public.get_url() + # + # local_path = '{}/class/msg'.format(public.get_panel_path()) + # if not os.path.exists(local_path): os.makedirs(local_path) + # + # import panelTask + # task_obj = panelTask.bt_task() + # + # sfile1 = '{}/{}_msg.py'.format(local_path,module_name) + # down_url1 = '{}/linux/panel/msg/{}_msg.py'.format(down_url,module_name) + # + # sfile2 = '{}/class/msg/{}.html'.format(public.get_panel_path(),module_name) + # down_url2 = '{}/linux/panel/msg/{}.html'.format(down_url,module_name) + # + # public.WriteLog('Install module', 'Install [{}]'.format(module_name)) + # task_obj.create_task('Download file', 1, down_url1, sfile1) + # task_obj.create_task('Download file', 1, down_url2, sfile2) + # + # timeout = 0 + # is_install = False + # while timeout < 5: + # try: + # if os.path.exists(sfile1) and os.path.exists(sfile2): + # msg_obj = public.init_msg(module_name) + # if msg_obj and msg_obj.get_version_info: + # is_install = True + # break + # except: pass + # time.sleep(0.1) + # is_install = True + # + # if not is_install: + # return public.returnMsg(False, 'Failed to install [{}] module. Please check the network.'.format(module_name)) + # + # public.set_module_logs('msg_push', 'install_module', 1) + # return public.returnMsg(True, '[{}] Module is installed successfully.'.format(module_name)) + # except: + # pass + # return public.returnMsg(False, '[{}] Module installation failed.'.format(module_name)) + + def install_msg_module(self,get): + """ + aapanel 不与面板相同,不删除通道模块 + 安装/更新消息通道模块 + @name 需要安装的模块名称 + """ + module_name = "" + try: + module_name = get.name + + local_path = '{}/class/msg'.format(public.get_panel_path()) + if not os.path.exists(local_path): os.makedirs(local_path) + + sfile1 = '{}/{}_msg.py'.format(local_path,module_name) + + if os.path.exists(sfile1): + return public.returnMsg(True, '[{}] Module is installed successfully.'.format(module_name)) + + except: + return public.returnMsg(False, '[{}] Module installation failed.'.format(module_name)) + + + # def uninstall_msg_module(self,get): + # """ + # 卸载消息通道模块 + # @name 需要卸载的模块名称 + # @is_del 是否需要删除配置文件 + # """ + # module_name = get.name + # obj = public.init_msg(module_name) + # if 'is_del' in get: + # try: + # obj.uninstall() + # except:pass + # + # sfile = '{}/class/msg/{}_msg.py'.format(public.get_panel_path(),module_name) + # if os.path.exists(sfile): os.remove(sfile) + # + # # public.print_log(sfile) + # default_channel_pl = "{}/data/default_msg_channel.pl".format(public.get_panel_path()) + # default_channel = public.readFile(default_channel_pl) + # if default_channel and default_channel == module_name: + # os.remove(default_channel_pl) + # return public.returnMsg(True, '[{}] Module uninstallation succeeds'.format(module_name)) + + def uninstall_msg_module(self,get): + """ + aapanel 不与面板相同,不删除通道模块,只删除配置文件 + @module_name 是删除配置文件 + """ + module_name = get.name + + # sfile = '{}/class/msg/{}_msg.py'.format(public.get_panel_path(),module_name) + # if os.path.exists(sfile): os.remove(sfile) + + if module_name in ["dingding", "feishu", "weixin"]: + msg_conf_file = "{{}}/data/{}.json".format(module_name).format(public.get_panel_path()) + if os.path.exists(msg_conf_file): os.remove(msg_conf_file) + elif module_name == "mail": + for conf_file in ["stmp_mail", "mail_list"]: + msg_conf_file = "{}/data/{}.json".format(public.get_panel_path(), conf_file) + if os.path.exists(msg_conf_file): os.remove(msg_conf_file) + elif module_name == "tg": + msg_conf_file = "{}/data/tg_bot.json".format(public.get_panel_path()) + if os.path.exists(msg_conf_file): os.remove(msg_conf_file) + + + # public.print_log(sfile) + default_channel_pl = "{}/data/default_msg_channel.pl".format(public.get_panel_path()) + default_channel = public.readFile(default_channel_pl) + if default_channel and default_channel == module_name: + os.remove(default_channel_pl) + return public.returnMsg(True, '[{}] Module uninstallation succeeds'.format(module_name)) + + + def get_msg_fun(self,get): + """ + @获取消息模块指定方法 + @auther: cjxin + @date: 2022-08-16 + @param: get.module_name 消息模块名称(如:sms,weixin,dingding) + @param: get.fun_name 消息模块方法名称(如:send_sms,push_msg) + """ + module_name = get.module_name + fun_name = get.fun_name + + m_objs = public.init_msg(module_name) + if not m_objs: return public.returnMsg(False, 'Setup failed, [{}] is not installed'.format(module_name)) + + return getattr(m_objs,fun_name)(get) + + + def get_msg_configs_by(self,get): + """ + @name 获取单独消息通道配置 + @auther: cjxin + @date: 2022-08-16 + @param: get.name 消息模块名称(如:sms,weixin,dingding) + """ + name = get.name + res = {} + res['data'] = {} + res['setup'] = False + res['info'] = False + try: + obj = public.init_msg(name) + if obj: + res['setup'] = True + res['data'] = obj.get_config(None) + res['info'] = obj.get_version_info(None); + except: pass + return res + + + + def get_msg_push_list(self,get): + """ + @name 获取消息通道配置列表 + @auther: cjxin + @date: 2022-08-16 + """ + cpath = 'data/msg.json' + try: + if 'force' in get or not os.path.exists(cpath): + if not 'download_url' in session: session['download_url'] = public.get_url() + public.downloadFile('{}/linux/panel/msg/msg.json'.format(session['download_url']),cpath) + except : pass + + data = {} + if os.path.exists(cpath): + msgs = json.loads(public.readFile(cpath)) + for x in msgs: + x['setup'] = False + x['info'] = False + key = x['name'] + try: + obj = public.init_msg(x['name']) + if obj: + x['setup'] = True + x['info'] = obj.get_version_info(None) + except : + print(public.get_error_info()) + pass + data[key] = x + return data + + + # 检查是否提交过问卷 + def check_nps(self, get): + # 校验参数 + try: + get.validate([ + Param('product_type').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + if 'product_type' not in get: + return public.return_message(-1,0, 'Parameter error') + ikey = 'check_nps' + result = cache.get(ikey) + if result: + return public.return_message(0,0,result) + + url = "https://www.aapanel.com/api/panel/nps/check" + + data = { + 'product_type': get.get('product_type', 1), + #'server_id': user_info['server_id'], + } + + try: + user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path()))) + data['server_id'] = user_info['server_id'] + + except: + pass + res = public.httpPost(url, data) + try: + res = json.loads(res) + except: + pass + + # 连不上官网时使用默认数据 + if not isinstance(res, dict): + res = { + "nonce": 0, + "success": False, + "res": False + } + + + # 判断运行天数 + safe_day = 0 + cur_timestamp = int(time.time()) + # if os.path.exists("data/%s_nps_time.pl" % software_name): + if os.path.exists("/www/server/panel/data/panel_nps_time.pl"): + try: + # nps_time = float(public.ReadFile("/www/server/panel/data/panel_nps_time.pl")) + nps_time = float(public.ReadFile("data/panel_nps_time.pl")) + safe_day = int((cur_timestamp - nps_time) / 86400) + + except: + public.WriteFile("data/panel_nps_time.pl", "%s" % cur_timestamp) + else: + public.WriteFile("data/panel_nps_time.pl", "%s" % cur_timestamp) + + datas = {'nonce': res.get('nonce', 0), + 'success': res.get('success', False), + 'res': { + 'safe_day': safe_day, + 'is_submit': res.get('res', False) + }} + + cache.set(ikey, datas, 3600) + # if res['success']: + # return public.returnMsg(True, 'Questionnaire has been submitted') + + # return public.returnMsg(False, 'No questionnaire has been submitted') + return public.return_message(0,0,datas) + + + def get_nps_new(self, get): + """ + 获取问卷 + """ + # 校验参数 + try: + get.validate([ + Param('product_type').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + # 官网接口 需替换 + url = "https://www.aapanel.com/api/panel/nps/questions" + data = { + 'product_type': get.get('product_type', 1), + # 'action': "list", + # 'version': get.get('version', -1) + } + # request发送post请求并指定form_data参数 + res = public.httpPost(url, data) + # public.print_log("获取问卷@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ {}".format(res)) + try: + res = json.loads(res) + except: + pass + return public.return_message(0,0,res) + + except: + return public.return_message(-1,0,"Failed to obtain questionnaire") + + def write_nps_new(self, get): + ''' + @name nps 提交 + @param rate 评分 + @param feedback 反馈内容 + ''' + # 校验参数 + try: + get.validate([ + Param('questions').String(), + Param('product_type').Integer(), + Param('rate').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if 'product_type' not in get: + return public.return_message(-1,0, 'Parameter error') + + # if 'questions' not in get: + # public.returnMsg(False, '参数错误') + if 'rate' not in get: + return public.return_message(-1,0, 'Parameter error') + + # try: + # if not hasattr(get, 'software_name'): + # public.returnMsg(False, '参数错误') + + # software_name = get['software_name'] + # public.WriteFile("data/{}_nps.pl".format(software_name), "1") + + data = { + # 'action': "submit", + # 'uid': user_info['uid'], # 用户ID + # 'access_key': user_info['access_key'], # 用户密钥 + # 'is_paid': get['is_paid'], # 是否付费 + # 'phone_back': get['back_phone'], # 是否回访 + # 'feedback': get['feedback'] # 反馈内容 + # 'reason_tags': get['reason_tags'], # 问题标签 + 'rate': get.get('rate', 1), # 评分 1~10 + 'product_type': get.get('product_type', 1), # 产品类型 + #'server_id': user_info['server_id'], # 服务器ID + 'questions': get['questions'], # 问题列表 + 'panel_version': public.version(), # 面板版本 + + } + url_headers = { + # "authorization": "bt {}".format(user_info['token']) + } + + try: + user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path()))) + data[ 'server_id']= user_info['server_id'] + url_headers = { + "authorization": "bt {}".format(user_info['token']) + } + except: + pass + + url = 'https://www.aapanel.com/api/panel/nps/submit' + if not hasattr(get, 'questions'): + return public.return_message(-1,0, "questions Parameter error") + else: + try: + content = json.loads(get.questions) + for _, i in content.items(): + if len(i) > 512: + # public.ExecShell("rm -f data/{}_nps.pl".format(software_name)) + return public.return_message(-1,0, "The submitted text is too long, please adjust and resubmit (MAX: 512)") + except: + return public.return_message(-1,0, "questions Parameter error") + # if not hasattr(get, 'product_type'): + # return public.returnMsg(False, "参数错误") + # if not hasattr(get, 'rate'): + # return public.returnMsg(False, "参数错误") + # if not hasattr(get, 'reason_tags'): + # get['reason_tags'] = "1" + # if not hasattr(get, 'is_paid'): + # get['is_paid'] = 0 # 是否付费 + # if not hasattr(get, 'phone_back'): + # get.phone_back = 0 + # if not hasattr(get, 'phone_back'): + # get.feedback = "" + + res = public.httpPost(url, data=data, headers=url_headers) + try: + res = json.loads(res) + except: + pass + + # 连不上官网时使用默认数据 + if not isinstance(res, dict): + res = { + "nonce": 0, + "success": False, + "res": "The submission failed, please check to connect to the node" + } + + if res['success']: + return public.return_message(0,0, "Submitted successfully") + + return public.return_message(-1,0, res['res'] if 'res' in res else "The submission failed, please check to connect to the node") + + # Get all translations + def get_translations(self, args: public.dict_obj): + from BTPanel import load_translations + return load_translations() + + # # nps问卷 + # def stop_nps(self, get): + # if 'software_name' not in get: + # public.returnMsg(False, '参数错误') + # if get.software_name == "panel": + # self._stop_panel_nps() + # else: + # public.WriteFile("data/%s_nps.pl" % get.software_name, "") + # return public.returnMsg(True, '关闭成功') + + # def get_nps(self, get): + # if 'software_name' not in get: public.returnMsg(False, '参数错误') + # software_name = get.software_name + # if software_name == "panel": + # return self._get_panel_nps() + # data = {'safe_day': 0} + # # conf = self.get_config(None) + # # 判断运行天数 + # if os.path.exists("data/%s_nps_time.pl" % software_name): + # try: + # nps_time = float(public.ReadFile("data/%s_nps_time.pl" % software_name)) + # data['safe_day'] = int((time.time() - nps_time) / 86400) + # + # except: + # public.WriteFile("data/%s_nps_time.pl" % software_name, "%s" % time.time()) + # else: + # public.WriteFile("data/%s_nps_time.pl" % software_name, "%s" % time.time()) + # + # if not os.path.exists("data/%s_nps.pl" % software_name): + # # 如果安全运行天数大于5天 并且没有没有填写过nps的信息 + # data['nps'] = False + # else: + # data['nps'] = True + # return data + + # def write_nps(self, get): + # ''' + # @name nps 提交 + # @param rate 评分 + # @param feedback 反馈内容 + # + # ''' + # if 'product_type' not in get: public.returnMsg(False, '参数错误') + # if 'software_name' not in get: public.returnMsg(False, '参数错误') + # software_name = get.software_name + # product_type = get.product_type + # import json, requests + # api_url = 'https://www.bt.cn/api/v2/contact/nps/submit' + # user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path()))) + # if 'rate' not in get: + # return public.returnMsg(False, "参数错误") + # if 'feedback' not in get: + # get.feedback = "" + # if 'phone_back' not in get: + # get.phone_back = 0 + # else: + # if get.phone_back == 1: + # get.phone_back = 1 + # else: + # get.phone_back = 0 + # + # if 'questions' not in get: + # return public.returnMsg(False, "参数错误") + # + # try: + # get.questions = json.loads(get.questions) + # except: + # return public.returnMsg(False, "参数错误") + # + # data = { + # "uid": user_info['uid'], + # "access_key": user_info['access_key'], + # "server_id": user_info['server_id'], + # "product_type": product_type, + # "rate": get.rate, + # "feedback": get.feedback, + # "phone_back": get.phone_back, + # "questions": json.dumps(get.questions) + # } + # try: + # requests.post(api_url, data=data, timeout=10).json() + # if software_name == "panel": + # self._stop_panel_nps(is_complete=True) + # else: + # public.WriteFile("data/{}_nps.pl".format(software_name), "1") + # except: + # pass + # return public.returnMsg(True, "提交成功") + + + + + # @staticmethod + # def _get_panel_nps_data(): + # panel_path = public.get_panel_path() + # try: + # nps_file = "{}/data/btpanel_nps_data".format(panel_path) + # if os.path.exists(nps_file): + # with open(nps_file, mode="r") as fp: + # nps_data = json.load(fp) + # else: + # time_file = "{}/data/panel_nps_time.pl".format(panel_path) + # post_nps_done = "{}/data/panel_nps.pl".format(panel_path) + # if not os.path.exists(time_file): + # install_time = time.time() + # else: + # with open(time_file, mode="r") as fp: + # install_time = float(fp.read()) + # nps_data = { + # "time": install_time, + # "status": "complete" if os.path.exists(post_nps_done) else "waiting", + # "popup_count": 0 + # } + # + # with open(nps_file, mode="w") as fp: + # fp.write(json.dumps(nps_data)) + # except: + # nps_data = { + # "time": time.time(), + # "status": "waiting", + # "popup_count": 0 + # } + # + # return nps_data + # + # @staticmethod + # def _save_panel_nps_data(nps_data): + # panel_path = public.get_panel_path() + # nps_file = "{}/data/btpanel_nps_data".format(panel_path) + # with open(nps_file, mode="w") as fp: + # fp.write(json.dumps(nps_data)) + # + # def _get_panel_nps(self): + # nps_data = self._get_panel_nps_data() + # safe_day = int((time.time() - nps_data["time"]) / 86400) + # res = {'safe_day': safe_day} + # if nps_data["status"] == "complete": + # res["nps"] = False + # elif nps_data["status"] == "stopped": + # res["nps"] = False + # else: + # if safe_day >= 10: + # res["nps"] = True + # return res + # + # def _stop_panel_nps(self, is_complete: bool = False): + # nps_data = self._get_panel_nps_data() + # if is_complete: + # nps_data["status"] = "complete" + # else: + # nps_data["status"] = "stopped" + # + # self._save_panel_nps_data(nps_data) + diff --git a/class_v2/crontab_ssl_v2.py b/class_v2/crontab_ssl_v2.py new file mode 100644 index 00000000..172f13df --- /dev/null +++ b/class_v2/crontab_ssl_v2.py @@ -0,0 +1,58 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: <290070744@aapanel.com> +# ------------------------------------------------------------------- + +# ------------------------------ +# ssl自动续订定时任务脚本 +# ------------------------------ +import os, json, sys, time + +os.chdir("/www/server/panel") +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import public + +sys.path.append(".") +import panelSSL +import panelSite + + +class dict_obj: + def __contains__(self, key): + return getattr(self, key, None) + + def __setitem__(self, key, value): setattr(self, key, value) + + def __getitem__(self, key): return getattr(self, key, None) + + def __delitem__(self, key): delattr(self, key) + + def __delattr__(self, key): delattr(self, key) + + def get_items(self): return self + + +if __name__ == "__main__": + get = dict_obj() + obj = panelSSL.panelSSL() + CertList = obj.GetCertList(get) + cmd_list = json.loads(public.ReadFile("/www/server/panel/vhost/crontab.json")) + panelSite_=panelSite.panelSite() + for i in CertList: + timeArray = time.strptime(i['notAfter'], "%Y-%m-%d") + timestamp = time.mktime(timeArray) + if int(timestamp) - time.time() < 86400 * 30: # 如果证书到期时间小于多少天就续订 + subject = i['subject'] + for j in cmd_list: + if subject == j['siteName']: + cmd = j['cmd'] + public.ExecShell(cmd) + # 保存证书 + get.siteName=subject + result = panelSite_.save_cert(get) + public.serviceReload() diff --git a/class_v2/crontab_v2.py b/class_v2/crontab_v2.py new file mode 100644 index 00000000..b55718d0 --- /dev/null +++ b/class_v2/crontab_v2.py @@ -0,0 +1,564 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import public,db,os,time,re, json +from BTPanel import session,cache +class crontab: + field = 'id,name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sName,sBody,sType,urladdress' + field += ",save_local,notice,notice_channel" + #取计划任务列表 + def GetCrontab(self,get): + self.checkBackup() + self.__clean_log() + cront = public.M('crontab').order("id desc").field(self.field).select() + if type(cront) == str: + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'status' INTEGER DEFAULT 1",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save' INTEGER DEFAULT 3",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'backupTo' TEXT DEFAULT off",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sName' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sBody' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sType' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'urladdress' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save_local' INTEGER DEFAULT 0",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'notice' INTEGER DEFAULT 0",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'notice_channel' TEXT DEFAULT ''",()) + cront = public.M('crontab').order("id desc").field(self.field).select() + data=[] + for i in range(len(cront)): + tmp = {} + tmp=cront[i] + if cront[i]['type']=="day": + tmp['type']=public.get_msg_gettext('Per Day') + tmp['cycle']= public.get_msg_gettext('Per Day, run at {} Hour {} Min',(str(cront[i]['where_hour']),str(cront[i]['where_minute']))) + elif cront[i]['type']=="day-n": + tmp['type']=public.get_msg_gettext('Every {} Days',(str(cront[i]['where1']),)) + tmp['cycle']=public.get_msg_gettext('Every {} Days, run at {} Hour {} Min',(str(cront[i]['where1']),str(cront[i]['where_hour']),str(cront[i]['where_minute']))) + elif cront[i]['type']=="hour": + tmp['type']=public.get_msg_gettext('Per Hour') + tmp['cycle']=public.get_msg_gettext('Per Hour, run at {} Min',(str(cront[i]['where_minute']),)) + elif cront[i]['type']=="hour-n": + tmp['type']=public.get_msg_gettext('Every {} Hours',(str(cront[i]['where1']),)) + tmp['cycle']=public.get_msg_gettext('Every {} Hours, run at {} Min',(str(cront[i]['where1']),str(cront[i]['where_minute']))) + elif cront[i]['type']=="minute-n": + tmp['type']=public.get_msg_gettext('Every {} Minutes',(str(cront[i]['where1']),)) + tmp['cycle']=public.get_msg_gettext('Run Every {} Minutes',(str(cront[i]['where1']),)) + elif cront[i]['type']=="week": + tmp['type']=public.get_msg_gettext('Weekly') + if not cront[i]['where1']: cront[i]['where1'] = '0' + tmp['cycle']= public.get_msg_gettext('Every {}, run at {} Hour {} Min',(self.toWeek(int(cront[i]['where1'])),str(cront[i]['where_hour']),str(cront[i]['where_minute']))) + elif cront[i]['type']=="month": + tmp['type']=public.get_msg_gettext('Monthly') + tmp['cycle']=public.get_msg_gettext('Monthly, run on {}Day {} Hour {}Min',(str(cront[i]['where1']),str(cront[i]['where_hour']),str(cront[i]['where_minute']))) + + log_file = '/www/server/cron/{}.log'.format(tmp['echo']) + if os.path.exists(log_file): + tmp['addtime'] = self.get_last_exec_time(log_file) + data.append(tmp) + return data + + def get_backup_list(self, args): + ''' + @name 获取指定备份任务的备份文件列表 + @author hwliang + @param args 参数{ + cron_id 任务ID 必填 + p 页码 默认1 + rows 每页显示条数 默认10 + callback jsonp回调函数 默认为空 + } + @return { + page 分页HTML + data 数据列表 + } + ''' + + p = args.get('p/d', 1) + rows = args.get('rows/d', 10) + tojs = args.get('tojs/s', '') + callback = args.get('callback/s', '') if tojs else tojs + + cron_id = args.get('cron_id/d') + count = public.M('backup').where('cron_id=?', (cron_id,)).count() + data = public.get_page(count, p, rows, callback) + data['data'] = public.M('backup').where('cron_id=?', (cron_id,)).limit(data['row'], data['shift']).select() + return data + + def get_last_exec_time(self,log_file): + ''' + @name 获取上次执行时间 + @author hwliang + @param log_file 日志文件路径 + @return format_date + ''' + exec_date = '' + try: + log_body = public.GetNumLines(log_file,20) + if log_body: + log_arr = log_body.split('\n') + date_list = [] + for i in log_arr: + if i.find('★') != -1 and i.find('[') != -1 and i.find(']') != -1: + date_list.append(i) + if date_list: + exec_date = date_list[-1].split(']')[0].split('[')[1] + except: + pass + + finally: + if not exec_date: + exec_date = public.format_date(times=int(os.path.getmtime(log_file))) + return exec_date + + + #清理日志 + def __clean_log(self): + try: + log_file = '/www/server/cron' + if not os.path.exists(log_file): return False + for f in os.listdir(log_file): + if f[-4:] != '.log': continue + filename = log_file + '/' + f + if os.path.getsize(filename) < 1048576 /2: continue + tmp = public.GetNumLines(filename,100) + public.writeFile(filename,tmp) + except: + pass + + + #转换大写星期 + def toWeek(self,num): + wheres={ + 0 : public.get_msg_gettext('Sunday'), + 1 : public.get_msg_gettext('Monday'), + 2 : public.get_msg_gettext('Tuesday'), + 3 : public.get_msg_gettext('Wednesday'), + 4 : public.get_msg_gettext('Thursday'), + 5 : public.get_msg_gettext('Friday'), + 6 : public.get_msg_gettext('Saturday') + } + try: + return wheres[num] + except: + return '' + + #检查环境 + def checkBackup(self): + if cache.get('check_backup'): return None + + # 检查备份表是否正确 + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'backup','%cron_id%')).count(): + public.M('backup').execute("ALTER TABLE 'backup' ADD 'cron_id' INTEGER DEFAULT 0",()) + + #检查备份脚本是否存在 + filePath=public.GetConfigValue('setup_path')+'/panel/script/backup' + if not os.path.exists(filePath): + public.downloadFile(public.GetConfigValue('home') + '/linux/backup.sh',filePath) + #检查日志切割脚本是否存在 + filePath=public.GetConfigValue('setup_path')+'/panel/script/logsBackup' + if not os.path.exists(filePath): + public.downloadFile(public.GetConfigValue('home') + '/linux/logsBackup.py',filePath) + #检查计划任务服务状态 + import system + sm = system.system() + if os.path.exists('/etc/init.d/crond'): + if not public.process_exists('crond'): public.ExecShell('/etc/init.d/crond start') + elif os.path.exists('/etc/init.d/cron'): + if not public.process_exists('cron'): public.ExecShell('/etc/init.d/cron start') + elif os.path.exists('/usr/lib/systemd/system/crond.service'): + if not public.process_exists('crond'): public.ExecShell('systemctl start crond') + cache.set('check_backup',True,3600) + + + #设置计划任务状态 + def set_cron_status(self,get): + id = get['id'] + cronInfo = public.M('crontab').where('id=?',(id,)).field(self.field).find() + status_msg = ['Stop','Start'] + status = 1 + if cronInfo['status'] == status: + status = 0 + self.remove_for_crond(cronInfo['echo']) + else: + cronInfo['status'] = 1 + if not self.sync_to_crond(cronInfo): + return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!') + + public.M('crontab').where('id=?',(id,)).setField('status',status) + public.WriteLog('TYPE_CRON',"MODIFY_CRON_STATUS",(cronInfo['name'],str(status_msg[status]))) + return public.return_msg_gettext(True,'Setup successfully!') + + #修改计划任务 + def modify_crond(self,get): + if len(get['name'])<1: + return public.return_msg_gettext(False,'Name of task cannot be empty!') + id = get['id'] + cuonConfig,get,name = self.GetCrondCycle(get) + cronInfo = public.M('crontab').where('id=?',(id,)).field(self.field).find() + if not get['where1']: get['where1'] = get['week'] + del(cronInfo['id']) + del(cronInfo['addtime']) + cronInfo['name'] = get['name'] + cronInfo['type'] = get['type'] + cronInfo['where1'] = get['where1'] + cronInfo['where_hour'] = get['hour'] + cronInfo['where_minute'] = get['minute'] + cronInfo['save'] = get['save'] + cronInfo['backupTo'] = get['backupTo'] + cronInfo['sBody'] = get['sBody'] + cronInfo['urladdress'] = get['urladdress'] + columns = 'name,type,where1,where_hour,where_minute,save,backupTo,sBody,urladdress' + values = (get['name'],get['type'],get['where1'],get['hour'], + get['minute'],get['save'],get['backupTo'],get['sBody'] + ,get['urladdress']) + if 'save_local' in get: + columns += ",save_local, notice, notice_channel" + values = (get['name'],get['type'],get['where1'],get['hour'], + get['minute'],get['save'],get['backupTo'],get['sBody'], + get['urladdress'],get['save_local'],get["notice"], + get["notice_channel"]) + self.remove_for_crond(cronInfo['echo']) + if cronInfo['status'] == 0: return public.return_msg_gettext(False, 'The current task is Disable status, please open the task before modifying!') + if not self.sync_to_crond(cronInfo): + return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!') + public.M('crontab').where('id=?',(id,)).save(columns,values) + + public.WriteLog('TYPE_CRON',"MODIFY_CRON",(cronInfo['name'],)) + return public.return_msg_gettext(True,'Setup successfully!') + + + #获取指定任务数据 + def get_crond_find(self,get): + id = int(get.id) + data = public.M('crontab').where('id=?',(id,)).field(self.field).find() + return data + + #同步到crond + def sync_to_crond(self,cronInfo): + if not 'status' in cronInfo: return False + if 'where_hour' in cronInfo: + cronInfo['hour'] = cronInfo['where_hour'] + cronInfo['minute'] = cronInfo['where_minute'] + cronInfo['week'] = cronInfo['where1'] + cuonConfig,cronInfo,name = self.GetCrondCycle(cronInfo) + cronPath=public.GetConfigValue('setup_path')+'/cron' + cronName=self.GetShell(cronInfo) + if type(cronName) == dict: return cronName + #if cronInfo['status'] == 0: return False + cuonConfig += ' ' + cronPath+'/'+cronName+' >> '+ cronPath+'/'+cronName+'.log 2>&1' + wRes = self.WriteShell(cuonConfig) + if type(wRes) != bool: return False + self.CrondReload() + return True + + #添加计划任务 + def AddCrontab(self,get): + if len(get['name'])<1: + return public.return_msg_gettext(False,'Name of task cannot be empty!') + cuonConfig,get,name = self.GetCrondCycle(get) + cronPath=public.GetConfigValue('setup_path')+'/cron' + cronName=self.GetShell(get) + if type(cronName) == dict: return cronName + cuonConfig += ' ' + cronPath+'/'+cronName+' >> '+ cronPath+'/'+cronName+'.log 2>&1' + + wRes = self.WriteShell(cuonConfig) + if type(wRes) != bool: return wRes + self.CrondReload() + columns = 'name,type,where1,where_hour,where_minute,echo,addtime,\ + status,save,backupTo,sType,sName,sBody,urladdress' + values = (public.xssencode2(get['name']),get['type'],get['where1'],get['hour'], + get['minute'],cronName,time.strftime('%Y-%m-%d %X',time.localtime()), + 1,get['save'],get['backupTo'],get['sType'],get['sName'],get['sBody'], + get['urladdress']) + if "save_local" in get: + columns += ",save_local,notice,notice_channel" + values = (public.xssencode2(get['name']),get['type'],get['where1'],get['hour'], + get['minute'],cronName,time.strftime('%Y-%m-%d %X',time.localtime()), + 1,get['save'],get['backupTo'],get['sType'],get['sName'],get['sBody'], + get['urladdress'], get["save_local"], get['notice'], get['notice_channel']) + addData=public.M('crontab').add(columns,values) + public.add_security_logs('TYPE_CRON','Add Cron tasks ['+get['name']+'] success'+str(values)) + if type(addData) == str: + return public.return_msg_gettext(False, addData) + public.WriteLog('TYPE_CRON', 'Add Cron tasks [' + get['name'] + '] success') + if addData>0: + result = public.return_msg_gettext(True,'Setup successfully!') + result['id'] = addData + return result + return public.return_msg_gettext(False,'Failed to add') + + #构造周期 + def GetCrondCycle(self,params): + cuonConfig="" + name = "" + if params['type']=="day": + cuonConfig = self.GetDay(params) + name = public.get_msg_gettext('Per Day') + elif params['type']=="day-n": + cuonConfig = self.GetDay_N(params) + name = public.get_msg_gettext('Every {0} Days',(params['where1'],)) + elif params['type']=="hour": + cuonConfig = self.GetHour(params) + name = public.get_msg_gettext('Per Hour') + elif params['type']=="hour-n": + cuonConfig = self.GetHour_N(params) + name = public.get_msg_gettext('Per Hour') + elif params['type']=="minute-n": + cuonConfig = self.Minute_N(params) + elif params['type']=="week": + params['where1']=params['week'] + cuonConfig = self.Week(params) + elif params['type']=="month": + cuonConfig = self.Month(params) + return cuonConfig,params,name + + #取任务构造Day + def GetDay(self,param): + cuonConfig ="{} {} * * * ".format(param['minute'],param['hour']) + return cuonConfig + #取任务构造Day_n + def GetDay_N(self,param): + cuonConfig ="{} {} */{} * * ".format(param['minute'],param['hour'],param['where1']) + return cuonConfig + + #取任务构造Hour + def GetHour(self,param): + cuonConfig ="{} * * * * ".format(param['minute']) + return cuonConfig + + #取任务构造Hour-N + def GetHour_N(self,param): + cuonConfig ="{} */{} * * * ".format(param['minute'],param['where1']) + return cuonConfig + + #取任务构造Minute-N + def Minute_N(self,param): + cuonConfig ="*/{} * * * * ".format(param['where1']) + return cuonConfig + + #取任务构造week + def Week(self,param): + cuonConfig ="{} {} * * {}".format(param['minute'],param['hour'],param['week']) + return cuonConfig + + #取任务构造Month + def Month(self,param): + cuonConfig = "{} {} {} * * ".format(param['minute'],param['hour'],param['where1']) + return cuonConfig + + #取数据列表 + def GetDataList(self,get): + data = {} + if get['type'] == 'databases': + data['data'] = public.M(get['type']).where("type=?","MySQL").field('name,ps').select() + else: + data['data'] = public.M(get['type']).field('name,ps').select() + for i in data['data']: + if 'ps' in i: + i['ps'] = public.xsssec(i['ps']) + data['orderOpt'] = [] + import json + tmp = public.readFile('data/libList.conf') + if not tmp: return data + libs = json.loads(tmp) + for lib in libs: + if not 'opt' in lib: continue + filename = 'plugin/{}'.format(lib['opt']) + if not os.path.exists(filename): continue + tmp = {} + tmp['name'] = lib['name'] + tmp['value']= lib['opt'] + data['orderOpt'].append(tmp) + return data + + #取任务日志 + def GetLogs(self,get): + id = get['id'] + echo = public.M('crontab').where("id=?",(id,)).field('echo').find() + logFile = public.GetConfigValue('setup_path')+'/cron/'+echo['echo']+'.log' + if not os.path.exists(logFile):return public.return_msg_gettext(False, 'log is empty') + log = public.GetNumLines(logFile,2000) + return public.return_msg_gettext(True, log) + + #清理任务日志 + def DelLogs(self,get): + try: + id = get['id'] + echo = public.M('crontab').where("id=?",(id,)).getField('echo') + logFile = public.GetConfigValue('setup_path')+'/cron/'+echo+'.log' + os.remove(logFile) + return public.return_msg_gettext(True, 'Logs emptied') + except: + return public.return_msg_gettext(False, 'Failed to empty task logs!') + + #删除计划任务 + def DelCrontab(self,get): + try: + id = get['id'] + find = public.M('crontab').where("id=?",(id,)).field('name,echo').find() + if not find: return public.return_msg_gettext(False, 'The specified task does not exist!') + if not self.remove_for_crond(find['echo']): return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!') + cronPath = public.GetConfigValue('setup_path') + '/cron' + sfile = cronPath + '/' + find['echo'] + if os.path.exists(sfile): os.remove(sfile) + sfile = cronPath + '/' + find['echo'] + '.log' + if os.path.exists(sfile): os.remove(sfile) + + public.M('crontab').where("id=?",(id,)).delete() + public.add_security_logs("Delete cron", "Delete cron:" + find['name']) + public.WriteLog('TYPE_CRON', 'CRONTAB_DEL',(find['name'],)) + return public.return_msg_gettext(True, 'Successfully deleted') + except: + return public.return_msg_gettext(False, 'Failed to delete') + + #从crond删除 + def remove_for_crond(self,echo): + file = self.get_cron_file() + if not os.path.exists(file): + return False + conf=public.readFile(file) + if not conf: return False + if conf.find(str(echo)) == -1: return True + rep = ".+" + str(echo) + ".+\n" + conf = re.sub(rep, "", conf) + try: + if not public.writeFile(file,conf): return False + except: + return False + self.CrondReload() + return True + + #取执行脚本 + def GetShell(self,param): + #try: + type=param['sType'] + if not 'echo' in param: + cronName=public.md5(public.md5(str(time.time()) + '_bt')) + else: + cronName = param['echo'] + if type=='toFile': + shell=param.sFile + else : + head="#!/bin/bash\nPATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin\nexport PATH\n" + python_bin = "{} -u".format(public.get_python_bin()) + if public.get_webserver()=='nginx': + log='.log' + elif public.get_webserver()=='apache': + log = '-access_log' + else: + log = '_ols.access_log' + if type in ['site','path'] and param['sBody'] != 'undefined' and len(param['sBody']) > 1: + exports = param['sBody'].replace("\r\n","\n").replace("\n",",") + head += "BT_EXCLUDE=\"" + exports.strip() + "\"\nexport BT_EXCLUDE\n" + attach_param = " " + cronName + wheres={ + 'path': head + python_bin +" " + public.GetConfigValue('setup_path')+"/panel/script/backup.py path "+param['sName']+" "+str(param['save'])+attach_param, + 'site' : head +python_bin+ " " + public.GetConfigValue('setup_path')+"/panel/script/backup.py site "+param['sName']+" "+str(param['save'])+attach_param, + 'database': head +python_bin+ " " + public.GetConfigValue('setup_path')+"/panel/script/backup.py database "+param['sName']+" "+str(param['save'])+attach_param, + 'logs' : head +python_bin+ " " + public.GetConfigValue('setup_path')+"/panel/script/logsBackup "+param['sName']+log+" "+str(param['save']), + 'rememory' : head + "/bin/bash " + public.GetConfigValue('setup_path') + '/panel/script/rememory.sh', + 'webshell': head +python_bin+ " " + public.GetConfigValue('setup_path') + '/panel/class/webshell_check.py site ' + param['sName'] +' ' +param['urladdress'] + } + if param['backupTo'] != 'localhost': + cfile = public.GetConfigValue('setup_path') + "/panel/plugin/" + param['backupTo'] + "/" + param['backupTo'] + "_main.py" + if not os.path.exists(cfile): cfile = public.GetConfigValue('setup_path') + "/panel/script/backup_" + param['backupTo'] + ".py" + wheres={ + 'path': head + python_bin+" " + cfile + " path " + param['sName'] + " " + str(param['save'])+attach_param, + 'site' : head + python_bin+" " + cfile + " site " + param['sName'] + " " + str(param['save'])+attach_param, + 'database': head + python_bin+" " + cfile + " database " + param['sName'] + " " + str(param['save'])+attach_param, + 'logs' : head + python_bin+" " + public.GetConfigValue('setup_path')+"/panel/script/logsBackup "+param['sName']+log+" "+str(param['save']), + 'rememory' : head + "/bin/bash " + public.GetConfigValue('setup_path') + '/panel/script/rememory.sh', + 'webshell': head + python_bin+" " + public.GetConfigValue('setup_path') + '/panel/class/webshell_check.py site ' + param['sName'] +' ' +param['urladdress'] + } + + try: + shell=wheres[type] + except: + if type == 'toUrl': + shell = head + "curl -sS --connect-timeout 10 -m 3600 '" + param['urladdress']+"'" + else: + shell=head+param['sBody'].replace("\r\n","\n") + + shell += ''' +echo "----------------------------------------------------------------------------" +endDate=`date +"%Y-%m-%d %H:%M:%S"` +echo "★[$endDate] Successful" +echo "----------------------------------------------------------------------------" +''' + cronPath=public.GetConfigValue('setup_path')+'/cron' + if not os.path.exists(cronPath): public.ExecShell('mkdir -p ' + cronPath) + file = cronPath+'/' + cronName + public.writeFile(file,self.CheckScript(shell)) + public.ExecShell('chmod 750 ' + file) + return cronName + #except Exception as ex: + #return public.return_msg_gettext(False, 'Failed to write in file!' + str(ex)) + + #检查脚本 + def CheckScript(self,shell): + keys = ['shutdown','init 0','mkfs','passwd','chpasswd','--stdin','mkfs.ext','mke2fs'] + for key in keys: + shell = shell.replace(key,'[***]') + return shell + + #重载配置 + def CrondReload(self): + if os.path.exists('/etc/init.d/crond'): + public.ExecShell('/etc/init.d/crond reload') + elif os.path.exists('/etc/init.d/cron'): + public.ExecShell('service cron restart') + else: + public.ExecShell("systemctl reload crond") + + #将Shell脚本写到文件 + def WriteShell(self,config): + u_file = '/var/spool/cron/crontabs/root' + file = self.get_cron_file() + if not os.path.exists(file): public.writeFile(file,'') + conf = public.readFile(file) + if type(conf)==bool:return public.return_msg_gettext(False,'Failed to read file!') + conf += config + "\n" + if public.writeFile(file,conf): + if not os.path.exists(u_file): + public.ExecShell("chmod 600 '" + file + "' && chown root.root " + file) + else: + public.ExecShell("chmod 600 '" + file + "' && chown root.crontab " + file) + return True + return public.return_msg_gettext(False,'Unable to write to file, please check if system hardening is enabled!') + + #立即执行任务 + def StartTask(self,get): + echo = public.M('crontab').where('id=?',(get.id,)).getField('echo') + execstr = public.GetConfigValue('setup_path') + '/cron/' + echo + public.ExecShell('chmod +x ' + execstr) + public.ExecShell('nohup ' + execstr + ' >> ' + execstr + '.log 2>&1 &') + return public.return_msg_gettext(True,'Task has been executed!') + + #获取计划任务文件位置 + def get_cron_file(self): + u_path = '/var/spool/cron/crontabs' + u_file = u_path + '/root' + c_file = '/var/spool/cron/root' + cron_path = c_file + if not os.path.exists(u_path): + cron_path=c_file + + if os.path.exists("/usr/bin/apt-get"): + cron_path = u_file + elif os.path.exists('/usr/bin/yum'): + cron_path = c_file + + if cron_path == u_file: + if not os.path.exists(u_path): + os.makedirs(u_path,472) + public.ExecShell("chown root:crontab {}".format(u_path)) + if not os.path.exists(cron_path): + public.writeFile(cron_path,"") + return cron_path + + + diff --git a/class_v2/data_v2.py b/class_v2/data_v2.py new file mode 100644 index 00000000..646416fd --- /dev/null +++ b/class_v2/data_v2.py @@ -0,0 +1,892 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import sys,os,re,time +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import db,public,panelMysql +import json +import public +from public.validate import Param + +class data: + __ERROR_COUNT = 0 + #自定义排序字段 + __SORT_DATA = ['site_ssl','php_version','backup_count'] + DB_MySQL = None + web_server = None + setupPath = '/www/server' + siteorder_path = '/www/server/panel/data/siteorder.pl' + limit_path = '/www/server/panel/data/limit.pl' + + # 删除排序记录 + def del_sorted(self, get): + public.ExecShell("rm -rf {}".format(self.siteorder_path)) + return public.returnMsg(True, '清除排序成功!') + + + ''' + * 设置备注信息 + * @param String _GET['tab'] 数据库表名 + * @param String _GET['id'] 条件ID + * @return Bool + ''' + def setPs(self,get): + # 校验参数 + try: + get.validate([ + Param('table').Require().String().Xss(), + Param('ps').Require().String(), + Param('id').Require().Integer().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + get.ps = public.xssencode2(get.ps) + if public.M(get.table).where("id=?",(id,)).setField('ps',get.ps): + # return public.return_msg_gettext(True,'Setup successfully!') + return public.return_message(0, 0, "Setup successfully") + # return public.return_msg_gettext(False,'Failed to modify') + return public.return_message(-1, 0, 'Failed to modify') + + #端口扫描 + def CheckPort(self,port): + import socket + localIP = '127.0.0.1' + temp = {} + temp['port'] = port + temp['local'] = True + try: + s = socket.socket() + s.settimeout(0.01) + s.connect((localIP,port)) + s.close() + except: + temp['local'] = False + + result = 0 + if temp['local']: result +=2 + return result + + # 转换时间 + def strf_date(self, sdate): + return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S')) + + def get_cert_end(self,pem_file): + 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 + # 取到期时间 + 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: + result['subject'] = result['dns'][0] + return result + except: + return public.get_cert_data(pem_file) + + + def get_site_ssl_info(self,siteName): + try: + s_file = 'vhost/nginx/{}.conf'.format(siteName) + is_apache = False + if not os.path.exists(s_file): + s_file = 'vhost/apache/{}.conf'.format(siteName) + is_apache = True + + if not os.path.exists(s_file): + return -1 + + s_conf = public.readFile(s_file) + if not s_conf: return -1 + ssl_file = None + if is_apache: + if s_conf.find('SSLCertificateFile') == -1: + return -1 + s_tmp = re.findall(r"SSLCertificateFile\s+(.+\.pem)",s_conf) + if not s_tmp: return -1 + ssl_file = s_tmp[0] + else: + if s_conf.find('ssl_certificate') == -1: + return -1 + s_tmp = re.findall(r"ssl_certificate\s+(.+\.pem);",s_conf) + if not s_tmp: return -1 + ssl_file = s_tmp[0] + ssl_info = self.get_cert_end(ssl_file) + if not ssl_info: return -1 + ssl_info['endtime'] = int(int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) + return ssl_info + except: return -1 + #return "{}:{}".format(ssl_info['issuer'],ssl_info['notAfter']) + + # 查询网站对应的PHP版本 + def get_php_version(self,siteName): + try: + + if not self.web_server: + self.web_server = public.get_webserver() + + conf = public.readFile(self.setupPath + '/panel/vhost/'+self.web_server+'/'+siteName+'.conf') + if self.web_server == 'openlitespeed': + conf = public.readFile( + self.setupPath + '/panel/vhost/' + self.web_server + '/detail/' + siteName + '.conf') + if self.web_server == 'nginx': + rep = r"enable-php-(\w{2,5})[-\w]*\.conf" + elif self.web_server == 'apache': + rep = r"php-cgi-(\w{2,5})\.sock" + else: + rep = r"path\s*/usr/local/lsws/lsphp(\d+)/bin/lsphp" + tmp = re.search(rep,conf).groups() + if tmp[0] == '00': + return 'Static' + if tmp[0] == 'other': + return 'Other' + + return tmp[0][0] + '.' + tmp[0][1] + except: + return 'Static' + + def map_to_list(self,map_obj): + try: + if type(map_obj) != list and type(map_obj) != str: map_obj = list(map_obj) + return map_obj + except: return [] + + def get_database_size(self,databaseName): + try: + if not self.DB_MySQL:self.DB_MySQL = panelMysql.panelMysql() + db_size = self.map_to_list(self.DB_MySQL.query("select sum(DATA_LENGTH)+sum(INDEX_LENGTH) from information_schema.tables where table_schema='{}'".format(databaseName)))[0][0] + if not db_size: return 0 + return int(db_size) + except: + return 0 + + def get_site_quota(self,path): + ''' + @name 获取网站目录配额信息 + @author hwliang<2022-02-15> + @param path 网站目录 + @return dict + ''' + res = { + "used": 0, + "size": 0, + "quota_push": { + "size": 0, + "used": 0, + }, + "quota_storage": { + "size": 0, + "used": 0, + } + } + try: + from public.PluginLoader import get_module + quota_info = getattr(get_module('{}/class_v2/projectModelV2/quotaModel.py'.format(public.get_panel_path())), + 'main')().get_quota_path(path) + if isinstance(quota_info, dict): + res.update(quota_info) + res['size'] = int(quota_info['quota_push']['size']) + int(quota_info['quota_storage']['size']) + return res + return res + except: + from traceback import format_exc + public.print_log(format_exc()) + return res + #最新版本v2版本 + # try: + # from projectModelV2.quotaModel import main + # quota_info = main().get_quota_path(path) + # if isinstance(quota_info,dict): + # return quota_info + # return res + # except: return res + + def get_database_quota(self,db_name): + ''' + @name 获取网站目录配额信息 + @author hwliang<2022-02-15> + @param path 网站目录 + @return dict + ''' + res = { + "used": 0, + "size": 0, + "quota_push": { + "size": 0, + "used": 0, + }, + "quota_storage": { + "size": 0, + "used": 0, + } + } + try: + from public.PluginLoader import get_module + quota_info = getattr(get_module('{}/class_v2/projectModelV2/quotaModel.py'.format(public.get_panel_path())), + 'main')().get_quota_mysql(db_name) + if isinstance(quota_info, dict): + res.update(quota_info) + res['size'] = int(quota_info['quota_push']['size']) + int(quota_info['quota_storage']['size']) + return res + + return res + except: + return res + #最新版本v2版本 + # try: + # from projectModelV2.quotaModel import main + # quota_info = main().get_quota_mysql(db_name) + # if isinstance(quota_info,dict): + # return quota_info + # return res + # except: return res + + ''' + * 取数据列表 + * @param String _GET['tab'] 数据库表名 + * @param Int _GET['count'] 每页的数据行数 + * @param Int _GET['p'] 分页号 要取第几页数据 + * @return Json page.分页数 , count.总行数 data.取回的数据 + ''' + def getData(self, get): + + # 校验参数 + try: + get.validate([ + Param('table').Require().String().Xss(), + Param('search').String(), + Param('limit').Integer(), + Param('p').Integer(), + Param('type').String().Xss(), + Param('project_type').Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # # net_flow_type = { + # # "total_flow": "总流量", + # # "7_day_total_flow": "近7天流量", + # # "one_day_total_flow": "近1天流量", + # # "one_hour_total_flow": "近1小时流量" + # # } + # # net_flow_json_file = "/www/server/panel/plugin/total/panel_net_flow.json" + # + # if get.table == 'sites': + # if not hasattr(get, 'order'): + # if os.path.exists(self.siteorder_path): + # order = public.readFile(self.siteorder_path) + # if order.split(' ')[0] in self.__SORT_DATA: + # get.order = order + # + # if not hasattr(get, 'limit') or get.limit == '' or int(get.limit) == 0: + # try: + # if os.path.exists(self.limit_path): + # get.limit = int(public.readFile(self.limit_path)) + # else: + # get.limit = 20 + # except: + # get.limit = 20 + # if "order" in get: + # order = get.order + # if get.table == 'sites': + # public.writeFile(self.siteorder_path, order) + # # o_list = order.split(' ') + # # net_flow_dict = {} + # # order_type = None + # # if o_list[0].strip() in net_flow_type.keys(): + # # # net_flow_dict["flow_type"] = o_list[0].strip() + # # if len(o_list) > 1: + # # order_type = o_list[1].strip() + # # else: + # # get.order = 'id desc' + # # # net_flow_dict["order_type"] = order_type + # # public.writeFile(net_flow_json_file, json.dumps(net_flow_dict)) + # 如果网站列表包含 rname 字段排序 先检查表内是否有 rname字段 + if hasattr(get, "order") and get.table == 'sites': + if get.order.startswith('rname'): + data = public.M('sites').find() + if 'rname' not in data.keys(): + public.M('sites').execute("ALTER TABLE 'sites' ADD 'rname' text DEFAULT ''", ()) + table = get.table + data = self.GetSql(get) + SQL = public.M(table) + user_Data = self.get_user_power() + if user_Data != 'all' and table in ['sites', 'databases', 'ftps']: + data['data'] = [i for i in data['data'] if str(i['id']) in user_Data.get(table, [])] + + try: + # table = get.table + # data = self.GetSql(get) + # SQL = public.M(table) + if table == 'backup': + import os + backup_path = public.M('config').where('id=?',(1,)).getField('backup_path') + for i in range(len(data['data'])): + if data['data'][i]['size'] == 0: + if os.path.exists(data['data'][i]['filename']): + data['data'][i]['size'] = os.path.getsize(data['data'][i]['filename']) + else: + if not os.path.exists(data['data'][i]['filename']): + if (data['data'][i]['filename'].find('/www/') != -1 or data['data'][i]['filename'].find(backup_path) != -1) and data['data'][i]['filename'][0] == '/' and data['data'][i]['filename'].find('|') == -1: + data['data'][i]['size'] = 0 + data['data'][i]['ps'] = '文件不存在' + if data['data'][i]['ps'] in ['','无']: + if data['data'][i]['name'][:3] == 'db_' or (data['data'][i]['name'][:4] == 'web_' and data['data'][i]['name'][-7:] == '.tar.gz'): + data['data'][i]['ps'] = '自动备份' + else: + data['data'][i]['ps'] = '手动备份' + #判断本地文件是否存在,以确定能否下载 + data['data'][i]['local']=data['data'][i]['filename'].split('|')[0] + data['data'][i]['localexist']=0 if os.path.isfile(data['data'][i]['local']) else 1 + + elif table == 'sites' or table == 'databases': + type = '0' + if table == 'databases': + type = '1' + for i in range(len(data['data'])): + backup_count = 0 + try: + backup_count = SQL.table('backup').where("pid=? AND type=?",(data['data'][i]['id'],type)).count() + except: + pass + + data['data'][i]['backup_count'] = backup_count + if table == 'databases': data['data'][i]['conn_config'] = json.loads(data['data'][i]['conn_config']) + data['data'][i]['quota'] = self.get_database_quota(data['data'][i]['name']) + + if table == 'sites': + for i in range(len(data['data'])): + + data['data'][i]['domain'] = SQL.table('domain').where("pid=?",(data['data'][i]['id'],)).count() + # data['data'][i]['ssl'] = self.get_site_ssl_info(data['data'][i]['name']) + + ssl_info = self.get_site_ssl_info(data['data'][i]['name']) + data['data'][i]['ssl'] = ssl_info + data['data'][i]['site_ssl'] = ssl_info['endtime'] if ssl_info != -1 else -1 + + data['data'][i]['php_version'] = self.get_php_version(data['data'][i]['name']) + data['data'][i]['attack'] = self.get_analysis(get,data['data'][i]) + data['data'][i]['project_type'] = SQL.table('sites').where('id=?',(data['data'][i]['id'])).field('project_type').find()['project_type'] + if data['data'][i]['project_type'] in ['WP', 'WP2']: + import one_key_wp + one_key_wp_obj = one_key_wp.one_key_wp() + data['data'][i]['cache_status'] = one_key_wp_obj.get_cache_status(data['data'][i]['id']) + data['data'][i]['login_url'] = '/v2/wp/login/{}'.format(data['data'][i]['id']) + data['data'][i]['wp_version'] = one_key_wp_obj.get_wp_version(data['data'][i]['id']) + + if data['data'][i]['project_type'] == 'WP2': + from wp_toolkit import wpbackup + data['data'][i]['backup_count'] = wpbackup(data['data'][i]['id']).backup_count() + + if not data['data'][i]['status'] in ['0','1',0,1]: + data['data'][i]['status'] = '1' + data['data'][i]['quota'] = self.get_site_quota(data['data'][i]['path']) + site1 = SQL.table('sites').where('id=?', (data['data'][i]['id'])).find() + if hasattr(site1, 'rname'): + data['data'][i]['rname'] = \ + SQL.table('sites').where('id=?', (data['data'][i]['id'])).field('rname').find()['rname'] + if not data['data'][i].get('rname', ''): + data['data'][i]['rname'] = data['data'][i]['name'] + data["net_flow_info"] = {} + # try: + # net_flow_json_info = json.loads(public.readFile(net_flow_json_file)) + # data["net_flow_info"] = net_flow_json_info + # except Exception: + # data["net_flow_info"] = {} + + + elif table == 'firewall': + for i in range(len(data['data'])): + if data['data'][i]['port'].find(':') != -1 or data['data'][i]['port'].find('.') != -1 or data['data'][i]['port'].find('-') != -1: + data['data'][i]['status'] = -1 + else: + data['data'][i]['status'] = self.CheckPort(int(data['data'][i]['port'])) + + elif table == 'ftps': + for i in range(len(data['data'])): + data['data'][i]['quota'] = self.get_site_quota(data['data'][i]['path']) + + try: + for _find in data['data']: + _keys = _find.keys() + for _key in _keys: + _find[_key] = public.xsssec(_find[_key]) + except: + pass + + #返回 + res = self.get_sort_data(data) + return public.return_message(0, 0, res) + except: + res = public.get_error_info() + # return public.get_error_info() + return public.return_message(0, 0, res) + + def get_data_list(self, get): + + try: + self.check_and_add_stop_column() + if get.table == 'sites': + if not hasattr(get, 'order'): + if os.path.exists(self.siteorder_path): + order = public.readFile(self.siteorder_path) + if order.split(' ')[0] in self.__SORT_DATA: + get.order = order + else: + public.writeFile(self.siteorder_path, get.order) + if not hasattr(get, 'limit') or get.limit == '' or int(get.limit) == 0: + try: + if os.path.exists(self.limit_path): + get.limit = int(public.readFile(self.limit_path)) + else: + get.limit = 20 + except: + get.limit = 20 + else: + public.writeFile(self.limit_path, get.limit) + if not hasattr(get, 'order'): + get.order = 'addtime desc' + get = self._get_args(get) + try: + s_list = self.func_models(get, 'get_data_where') + except: + s_list = [] + + where_sql, params = self.get_where(get, s_list) + data = self.get_page_data(get, where_sql, params) + get.data_list = data['data'] + try: + data['data'] = self.func_models(get, 'get_data_list') + except : + print(traceback.format_exc()) + if get.table == 'sites': + if isinstance(data, dict): + file_path = os.path.join(public.get_panel_path(), "data/sort_list.json") + if os.path.exists(file_path): + sort_list_raw = public.readFile(file_path) + sort_list = json.loads(sort_list_raw) + sort_list_int = [int(item) for item in sort_list["list"]] + + for i in range(len(data['data'])): + if int(data['data'][i]['id']) in sort_list_int: + data['data'][i]['sort'] = 1 + else: + data['data'][i]['sort'] = 0 + + top_list = sort_list["list"] + if top_list: + top_list = top_list[::-1] + top_data = [item for item in data["data"] if str(item['id']) in top_list] + data1 = [item for item in data["data"] if str(item['id']) not in top_list] + top_data.sort(key=lambda x: top_list.index(str(x['id']))) + data['data'] = top_data + data1 + public.set_search_history(get.table, get.search_key, get.search) # 记录搜索历史 + # 字段排序 + data = self.get_sort_data(data) + if 'type_id' in get: + type_id=int(get['type_id']) + if type_id: + filtered_data = [] + target_type_id = type_id + # print(data['data']) + for item in data['data']: + if item.get('type_id') == target_type_id: + filtered_data.append(item) + data['data'] = filtered_data + if get.get("db_type",""): + if type_id < 0: + filtered_data = [] + target_type_id = type_id + for item in data['data']: + if item.get('type_id') == target_type_id: + filtered_data.append(item) + data['data'] = filtered_data + return data + except: + return traceback.format_exc() + + + # 获取用户权限列表 + def get_user_power(self, get=None): + user_Data = 'all' + try: + uid = session.get('uid') + if uid != 1 and uid: + plugin_path = '/www/server/panel/plugin/users' + if os.path.exists(plugin_path): + user_authority = os.path.join(plugin_path, 'authority') + if os.path.exists(user_authority): + if os.path.exists(os.path.join(user_authority, str(uid))): + try: + data = json.loads(self._decrypt(public.ReadFile(os.path.join(user_authority, str(uid))))) + if data['role'] == 'administrator': + user_Data = 'all' + else: + user_Data = json.loads(self._decrypt(public.ReadFile(os.path.join(user_authority, str(uid) + '.data')))) + except: + user_Data = {} + else: + user_Data = {} + except: + pass + return user_Data + + + def get_sort_data(self,data): + """ + @获取自定义排序数据 + @param data: 数据 + """ + if 'plist' in data: + plist = data['plist'] + o_list = plist['order'].split(' ') + + reverse = False + sort_key = o_list[0].strip() + + if o_list[1].strip() == 'desc': + reverse = True + + if sort_key in ['site_ssl']: + for info in data['data']: + if type(info['ssl']) == int: + info[sort_key] = info['ssl'] + else: + try: + info[sort_key] = info['ssl']['endtime'] + except : + info[sort_key] = '' + + data['data'] = sorted(data['data'],key=lambda x:x[sort_key],reverse=reverse) + data['data'] = data['data'][plist['shift'] : plist['row'] ] + return data + + ''' + * 取数据库行 + * @param String _GET['tab'] 数据库表名 + * @param Int _GET['id'] 索引ID + * @return Json + ''' + def getFind(self,get): + tableName = get.table + id = get.id + field = self.GetField(get.table) + SQL = public.M(tableName) + where = "id=?" + find = SQL.where(where,(id,)).field(field).find() + try: + _keys = find.keys() + for _key in _keys: + find[_key] = public.xsssec(find[_key]) + except: + pass + return find + + + ''' + * 取字段值 + * @param String _GET['tab'] 数据库表名 + * @param String _GET['key'] 字段 + * @param String _GET['id'] 条件ID + * @return String + ''' + def getKey(self,get): + + # 校验参数 + try: + get.validate([ + Param('table').Require().String().Xss(), + Param('key').Require().String().Xss(), + Param('id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + tableName = get.table + keyName = get.key + id = get.id + SQL = db.Sql().table(tableName) + where = "id=?" + retuls = SQL.where(where,(id,)).getField(keyName) + res = public.xsssec(retuls) + if type(res)==dict: + if res.get("message", None): + res = res.get("message") + if res.get("msg", None): + res = res.get("msg") + # return public.xsssec(retuls) + return public.return_message(0, 0, res) + + + ''' + * 获取数据与分页 + * @param string table 表 + * @param string where 查询条件 + * @param int limit 每页行数 + * @param mixed result 定义分页数据结构 + * @return array + ''' + def GetSql(self,get,result = '1,2,3,4,5,8'): + #判断前端是否传入参数 + order = 'id desc' + if hasattr(get,'order'): + # 验证参数格式 + if re.match(r"^[\w\s\-\.]+$",get.order): + order = get.order + + search_key = 'get_list' + limit = 20 + if hasattr(get,'limit'): + limit = int(get.limit) + if limit < 1: limit = 20 + + if hasattr(get,'result'): + # 验证参数格式 + if re.match(r"^[\d\,]+$",get.result): + result = get.result + + SQL = db.Sql() + data = {} + #取查询条件 + where = '' + search = '' + param = () + if hasattr(get,'search'): + search = get.search + if sys.version_info[0] == 2: get.search = get.search.encode('utf-8') + where,param = self.GetWhere(get.table,get.search) + if get.table == 'backup': + where += " and type='{}'".format(int(get.type)) + + if get.table == 'sites' and get.search: + conditions = '' + if '_' in get.search: + cs = '' + for i in get.search: + if i == '_': + cs += '/_' + else: + cs += i + get.search = cs + conditions = " escape '/'" + pid = SQL.table('domain').where("name LIKE ?{}".format(conditions),("%{}%".format(get.search),)).getField('pid') + if pid: + if where: + where += " or id=" + str(pid) + else: + where += "id=" + str(pid) + + if get.table == 'sites': + search_key = 'php' + + # 额外对 project_type 字段做处理 + if 'project_type' in get: + extra_where = "`project_type` = '{}'".format(get.project_type) + if where: + where = r"({}) AND {}".format(where, extra_where) + else: + where = extra_where + else: + extra_where = "`project_type` IN ('PHP', 'WP')" + if where: + where = r"({}) AND {}".format(where, extra_where) + else: + where = extra_where + + if hasattr(get,'type'): + if get.type != '-1': + where += " AND type_id={}".format(int(get.type)) + + if get.table == 'databases': + if hasattr(get,'db_type'): + if where: + where += " AND db_type='{}'".format(int(get.db_type)) + else: + where = "db_type='{}'".format(int(get.db_type)) + if hasattr(get,'sid'): + if where: + where += " AND sid='{}'".format(int(get.sid)) + else: + where = "sid='{}'".format(int(get.sid)) + + if where: + where += " and type='MySQL'" + else: + where = 'type = "MySQL"' + + field = self.GetField(get.table) + #实例化数据库对象 + + public.set_search_history(get.table,search_key,search) #记录搜索历史 + + #是否直接返回所有列表 + if hasattr(get,'list'): + data = SQL.table(get.table).where(where,param).field(field).order(order).select() + return data + + #取总行数 + count = SQL.table(get.table).where(where,param).count() + #get.uri = get + #包含分页类 + import page + #实例化分页类 + page = page.Page() + + info = {} + info['count'] = count + info['row'] = limit + + info['p'] = 1 + if hasattr(get,'p'): + info['p'] = int(get['p']) + if info['p'] <1: info['p'] = 1 + + try: + from flask import request + info['uri'] = public.url_encode(request.full_path) + except: + info['uri'] = '' + info['return_js'] = '' + if hasattr(get,'tojs'): + if re.match(r"^[\w\.\-]+$",get.tojs): + info['return_js'] = get.tojs + + data['where'] = where + + #获取分页数据 + data['page'] = page.GetPage(info,result) + #取出数据 + #data['data'] = SQL.table(get.table).where(where,param).order(order).field(field).limit(str(page.SHIFT)+','+str(page.ROW)).select() + + o_list = order.split(' ') + if o_list[0] in self.__SORT_DATA: + data['data'] = SQL.table(get.table).where(where,param).field(field).select() + data['plist'] = {'shift':page.SHIFT,'row':page.ROW,'order':order} + else: + data['data'] = SQL.table(get.table).where(where,param).order(order).field(field).limit(str(page.SHIFT)+','+str(page.ROW)).select() #取出数据 + + data['search_history'] = public.get_search_history(get.table,search_key) + + return data + + #获取条件 + def GetWhere(self,tableName,search): + if not search: return "",() + + if type(search) == bytes: search = search.encode('utf-8').strip() + try: + search = re.search(r"[\w\x80-\xff\.\_\-]+",search).group() + except: + return '',() + conditions = '' + if '_' in search: + cs = '' + for i in search: + if i == '_': + cs += '/_' + else: + cs += i + search = cs + conditions = " escape '/'" + wheres = { + 'sites': ("name LIKE ? OR ps LIKE ?{}".format(conditions), ('%' + search + '%', '%' + search + '%')), + 'ftps': ("name LIKE ? OR ps LIKE ?{}".format(conditions), ('%' + search + '%', '%' + search + '%')), + 'databases': ( + "(name LIKE ? {} OR ps LIKE ?{})".format(conditions, conditions), + ("%" + search + "%", "%" + search + "%")), + 'crontab': ("name LIKE ?{}".format(conditions), ('%' + (search) + '%')), + 'logs': ("username=? OR type LIKE ?{} OR log LIKE ?{}".format(conditions, conditions), + (search, '%' + search + '%', '%' + search + '%')), + 'backup' : ("pid=?",(search,)), + 'users' : ("id='?' OR username=?",(search,search)), + 'domain' : ("pid=? OR name=?",(search,search)), + 'tasks' : ("status=? OR type=?",(search,search)), + } + + # wheres = { + # 'sites' : ("name LIKE ? OR ps LIKE ?",('%'+search+'%','%'+search+'%')), + # 'ftps' : ("name LIKE ? OR ps LIKE ?",('%'+search+'%','%'+search+'%')), + # 'databases' : ("(name LIKE ? OR ps LIKE ?)",("%"+search+"%","%"+search+"%")), + # 'logs' : ("username=? OR type LIKE ? OR log LIKE ?",(search,'%'+search+'%','%'+search+'%')), + # 'backup' : ("pid=?",(search,)), + # 'users' : ("id='?' OR username=?",(search,search)), + # 'domain' : ("pid=? OR name=?",(search,search)), + # 'tasks' : ("status=? OR type=?",(search,search)), + # } + + try: + return wheres[tableName] + except: + return '',() + + # 获取返回的字段 + def GetField(self,tableName): + fields = { + 'sites' : "id,name,path,status,ps,addtime,edate", + 'ftps' : "id,pid,name,password,status,ps,addtime,path", + 'databases' : "id,sid,pid,name,username,password,accept,ps,addtime,db_type,conn_config", + 'logs' : "id,uid,username,type,log,addtime", + 'backup' : "id,pid,name,filename,addtime,size,ps", + 'users' : "id,username,phone,email,login_ip,login_time", + 'firewall' : "id,port,ps,addtime", + 'domain' : "id,pid,name,port,addtime", + 'tasks' : "id,name,type,status,addtime,start,end" + } + try: + return fields[tableName] + except: + return '' + + def get_analysis(self,get,i): + import log_analysis + get.path = '/www/wwwlogs/{}.log'.format(i['name']) + get.action = 'get_result' + data = log_analysis.log_analysis().get_result(get) + return int(data['php']) + int(data['san']) + int(data['sql']) + int(data['xss']) diff --git a/class_v2/databaseModelV2/__init__.py b/class_v2/databaseModelV2/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/class_v2/databaseModelV2/__init__.py @@ -0,0 +1 @@ + diff --git a/class_v2/databaseModelV2/base.py b/class_v2/databaseModelV2/base.py new file mode 100644 index 00000000..ded0dd21 --- /dev/null +++ b/class_v2/databaseModelV2/base.py @@ -0,0 +1,542 @@ +# coding: utf-8 +import os, sys, time, json + +panelPath = '/www/server/panel' +os.chdir(panelPath) +if not panelPath + "/class/" in sys.path: + sys.path.insert(0, panelPath + "/class/") +import public, re + + +class databaseBase: + + def get_base_list(self, args, sql_type='mysql'): + """ + @获取数据库列表 + @type:数据库类型,MySQL,SQLServer + """ + + search = '' + if 'search' in args: search = args['search'] + + SQL = public.M('databases'); + + where = "lower(type) = lower('{}')".format(sql_type) + if search: + where += "AND (name like '%{search}%' or ps like '%{search}%')".format(search=search) + + if 'db_type' in args: + where += " AND db_type='{}'".format(args['db_type']) + + if 'sid' in args: + where += " AND sid='{}'".format(args['sid']) + + order = "id desc" + if hasattr(args, 'order'): order = args.order + + info = {} + rdata = {} + + info['p'] = 1 + info['row'] = 20 + result = '1,2,3,4,5,8' + info['count'] = SQL.where(where, ()).count(); + + if hasattr(args, 'limit'): info['row'] = int(args.limit) + if hasattr(args, 'result'): result = args.result; + if hasattr(args, 'p'): info['p'] = int(args['p']) + + import page + # 实例化分页类 + page = page.Page(); + + info['uri'] = args + info['return_js'] = '' + if hasattr(args, 'tojs'): info['return_js'] = args.tojs + + rdata['where'] = where; + + # 获取分页数据 + rdata['page'] = page.GetPage(info, result) + # 取出数据 + rdata['data'] = SQL.where(where, ()).order(order).field( + 'id,sid,pid,name,username,password,accept,ps,addtime,type,db_type,conn_config').limit( + str(page.SHIFT) + ',' + str(page.ROW)).select() + + for sdata in rdata['data']: + sdata['backup_count'] = public.M('backup').where("pid=? AND type=1", (sdata['id'])).count() + + sdata['conn_config'] = json.loads(sdata['conn_config']) + return rdata; + + def get_databaseModel(self): + ''' + 获取数据库模型对象 + @db_type 数据库类型 + ''' + # from panelDatabaseController import DatabaseController + from panelDatabaseControllerV2 import DatabaseController + + project_obj = DatabaseController() + + return project_obj + + def get_average_num(self, slist): + """ + @批量删除获取平均值 + """ + count = len(slist) + limit_size = 1 * 1024 * 1024 + if count <= 0: return limit_size + + if len(slist) > 1: + slist = sorted(slist) + limit_size = int((slist[0] + slist[-1]) / 2 * 0.85) + return limit_size + + def get_database_size(self, ids, is_pid=False): + """ + 获取数据库大小 + """ + result = {} + p = self.get_databaseModel() + for id in ids: + if not is_pid: + x = public.M('databases').where('id=?', id).field('id,sid,pid,name,type,ps,addtime').find() + else: + x = public.M('databases').where('pid=?', id).field('id,sid,pid,name,type,ps,addtime').find() + if not x: continue + x['backup_count'] = public.M('backup').where("pid=? AND type=?", (x['id'], '1')).count() + if x['type'] == 'MySQL': + x['total'] = int(public.get_database_size_by_id(id)) + else: + try: + + get = public.dict_obj() + get['data'] = {'db_id': x['id']} + get['mod_name'] = x['type'].lower() + get['def_name'] = 'get_database_size_by_id' + + x['total'] = p.model(get) + except: + x['total'] = 0 + result[x['name']] = x + return result + + def check_base_del_data(self, get): + """ + @删除数据库前置检测 + """ + ids = json.loads(get.ids) + slist = {}; + result = []; + db_list_size = [] + db_data = self.get_database_size(ids) + + for key in db_data: + data = db_data[key] + if not data['id'] in ids: continue + + db_addtime = public.to_date(times=data['addtime']) + data['score'] = int(time.time() - db_addtime) + data['total'] + data['st_time'] = db_addtime + + if data['total'] > 0: db_list_size.append(data['total']) + result.append(data) + + slist['data'] = sorted(result, key=lambda x: x['score'], reverse=True) + slist['db_size'] = self.get_average_num(db_list_size) + return slist + + def get_test(self, args): + + p = self.get_databaseModel() + get = public.dict_obj() + get['data'] = {'db_id': 18} + get['mod_name'] = args['type'].lower() + get['def_name'] = 'get_database_size_by_id' + + return p.model(get) + + def add_base_database(self, get): + """ + @添加数据库前置检测 + @return username 用户名 + data_name 数据库名 + data_pwd:数据库密码 + """ + data_name = get['name'].strip().lower() + if self.check_recyclebin(data_name): + return public.returnMsg(False, + 'Database [' + data_name + '] is already in recycle bin, please restore from recycle bin!'); + + if len(data_name) > 16: + return public.returnMsg(False, 'DATABASE_NAME_LEN') + + if not hasattr(get, 'db_user'): get.db_user = data_name; + username = get.db_user.strip(); + checks = ['root', 'mysql', 'test', 'sys', 'panel_logs'] + if username in checks or len(username) < 1: + return public.returnMsg(False, 'Database username is invalid!'); + if data_name in checks or len(data_name) < 1: + return public.returnMsg(False, 'Database name is invalid!'); + + reg = r"^\w+$" + if not re.match(reg, data_name): + return public.returnMsg(False, 'DATABASE_NAME_ERR_T') + + data_pwd = get['password'] + if len(data_pwd) < 1: + data_pwd = public.md5(str(time.time()))[0:8] + + if public.M('databases').where("name=? or username=?", (data_name, username)).count(): + return public.returnMsg(False, 'DATABASE_NAME_EXISTS') + + res = { + 'data_name': data_name, + 'username': username, + 'data_pwd': data_pwd, + 'status': True + } + return res + + def delete_base_backup(self, get): + """ + @删除备份文件 + """ + + name = '' + id = get.id + where = "id=?" + filename = public.M('backup').where(where, (id,)).getField('filename') + if os.path.exists(filename): os.remove(filename) + + if filename == 'qiniu': + name = public.M('backup').where(where, (id,)).getField('name'); + + public.ExecShell(public.get_run_python("[PYTHON] " + public.GetConfigValue( + 'setup_path') + '/panel/script/backup_qiniu.py delete_file ' + name)) + public.M('backup').where(where, (id,)).delete() + public.WriteLog("TYPE_DATABASE", 'DATABASE_BACKUP_DEL_SUCCESS', (name, filename)) + public.return_message(0, 0, 'DEL_SUCCESS') + + # 检查是否在回收站 + def check_recyclebin(self, name): + try: + for n in os.listdir('{}/Recycle_bin'.format(public.get_soft_path())): + if n.find('BTDB_' + name + '_t_') != -1: return True; + return False; + except: + return False; + + # map to list + def map_to_list(self, map_obj): + try: + if type(map_obj) != list and type(map_obj) != str: map_obj = list(map_obj) + return map_obj + except: + return [] + + # ******************************************** 远程数据库 ******************************************/ + + def check_cloud_args(self, get, nlist=[]): + """ + 验证参数是否合法 + @get param + @args 参数列表 + """ + for key in nlist: + if not key in get: + return public.return_message(-1, 0, 'Parameter passing error, missing parameter {}!'.format(key)) + # return public.returnMsg(False, 'Parameter passing error, missing parameter {}!'.format(key)) + return True + + def check_cloud_database(self, args): + ''' + @检查远程数据库是否存在 + @conn_config param + ''' + + p = self.get_databaseModel() + + get = public.dict_obj() + # get['db_name'] = 'localhost' + get['data'] = args + get['mod_name'] = args['type'] + get['def_name'] = 'check_cloud_database_status' + # public.print_log("-------------------进入检测远程数据库是否存在: {}".format(p.model(get))) + return p.model(get) + + def AddBaseCloudServer(self, get): + """ + @name 添加远程服务器 + @author hwliang<2021-01-10> + @param db_host 服务器地址 + @param db_port 数据库端口 + @param db_user 用户名 + @param db_password 数据库密码 + @param db_ps 数据库备注 + @param type 数据库类型,mysql/sqlserver/sqlite + @return dict + """ + # mongodb {"db_host":"192.168.168.12","db_port":"27017","db_user":"root","db_password":"8thA5dgB8lr5ACfx","db_ps":"cecee","type":"mongodb"} + # sqlserver {"db_host":"192.168.1.23","db_port":"1433","db_user":"sa","db_password":"MfyDytnjXBTD8e6x","db_ps":"666","type":"sqlserver"} + + + arrs = ['db_host', 'db_port', 'db_user', 'db_password', 'db_ps', 'type'] + if get.type == 'redis': + arrs = ['db_host', 'db_port', 'db_password', 'db_ps', 'type'] + # try: + cRet = self.check_cloud_args(get, arrs) + if isinstance(cRet, dict): + return cRet + + # try: + get['db_name'] = None + try: + res = self.check_cloud_database(get) + except BaseException as ex: + # public.print_log("获取远程数据库状态00: {}".format(ex)) + return public.return_message(-1, 0, 'Database connection failed') + + + # # mongodb 远程检测有问题 暂时跳过检测 + # if get.type != 'mongodb': + # if res['message'].get('result', '') == '' or res['message'].get('result', '') == False: + # return public.return_message(-1, 0, "The remote database could not be connected") + # {'status': 0, 'timestamp': 1715394490, 'message': AttributeError("'str' object has no attribute 'command'")} + + # 检测数据库连接状态 + try: + if not isinstance(res['message'], dict): + return public.return_message(-1, 0, "The remote database could not be connected") + if res['message'].get('result', '') == '' or res['message'].get('result', '') == False: + return public.return_message(-1, 0, "The remote database could not be connected") + if res['status'] == -1: + return public.return_message(-1, 0, "The remote database could not be connected") + except Exception as e: + # public.print_log("获取远程数据库状态22: {}".format(e)) + return public.return_message(-1, 0, "The remote database could not be connected") + + if public.M('database_servers').where('db_host=? AND db_port=?', (get['db_host'], get['db_port'])).count(): + return public.return_message(-1, 0, 'The specified server already exists: [{}:{}]'.format(get['db_host'], + get['db_port'])) + get['db_port'] = int(get['db_port']) + pdata = { + 'db_host': get['db_host'], + 'db_port': int(get['db_port']), + 'db_user': get['db_user'], + 'db_password': get['db_password'], + 'db_type': get['type'], + 'ps': public.xssencode2(get['db_ps'].strip()), + 'addtime': int(time.time()) + } + result = public.M("database_servers").insert(pdata) + + if isinstance(result, int): + public.WriteLog('Database manager', 'Add remote MySQL server[{}:{}]'.format(get['db_host'], get['db_port'])) + # return public.returnMsg(True,'Added successfully!') + return public.return_message(0, 0, 'Added successfully!') + # return public.returnMsg(False,'Add failed: {}'.format(result)) + return public.return_message(0, 0, 'Add failed: {}'.format(result)) + # except Exception as ex: + # public.print_log("error info777: {}".format(ex)) + # return public.return_message(-1, 0, str(ex)) + + def GetBaseCloudServer(self, get): + ''' + @name 获取远程服务器列表 + @author hwliang<2021-01-10> + @return list + ''' + where = '1=1' + if 'type' in get: where = "db_type = '{}'".format(get['type']) + + data = public.M('database_servers').where(where, ()).select() + + if not isinstance(data, list): data = [] + + if get['type'] == 'mysql': + bt_mysql_bin = public.get_mysql_info()['path'] + '/bin/mysql.exe' + if os.path.exists(bt_mysql_bin): + data.insert(0, {'id': 0, 'db_host': '127.0.0.1', 'db_port': 3306, 'db_user': 'root', 'db_password': '', + 'ps': 'local server', 'addtime': 0, 'db_type': 'mysql'}) + elif get['type'] == 'sqlserver': + pass + elif get['type'] == 'mongodb': + if os.path.exists('/www/server/mongodb/bin'): + data.insert(0, {'id': 0, 'db_host': '127.0.0.1', 'db_port': 27017, 'db_user': 'root', 'db_password': '', + 'ps': 'local server', 'addtime': 0, 'db_type': 'mongodb'}) + elif get['type'] == 'redis': + if os.path.exists('/www/server/redis'): + data.insert(0, {'id': 0, 'db_host': '127.0.0.1', 'db_port': 6379, 'db_user': 'root', 'db_password': '', + 'ps': 'local server', 'addtime': 0, 'db_type': 'redis'}) + elif get['type'] == 'pgsql': + if os.path.exists('/www/server/pgsql'): + data.insert(0, + {'id': 0, 'db_host': '127.0.0.1', 'db_port': 5432, 'db_user': 'postgres', 'db_password': '', + 'ps': 'local server', 'addtime': 0, 'db_type': 'pgsql'}) + return data + + def RemoveBaseCloudServer(self, get): + ''' + @name 删除远程服务器 + @author hwliang<2021-01-10> + @param id 远程服务器ID + @return dict + ''' + + id = int(get.id) + if not id: + return public.return_message(-1, 0, 'Parameter passed error, please try again!') + db_find = public.M('database_servers').where('id=?', (id,)).find() + if not db_find: + return public.return_message(-1, 0, 'The specified remote server does not exist!') + public.M('databases').where('sid=?', id).delete() + result = public.M('database_servers').where('id=?', id).delete() + if isinstance(result, int): + public.WriteLog('Database manager', + 'Delete remote MySQL server [{}:{}]'.format(db_find['db_host'], int(db_find['db_port']))) + return public.return_message(0, 0, 'Successfully deleted!') + return public.return_message(0, 0, 'Successfully deleted: {}'.format(result)) + + def ModifyBaseCloudServer(self, get): + ''' + @name 修改远程服务器 + @author hwliang<2021-01-10> + @param id 远程服务器ID + @param db_host 服务器地址 + @param db_port 数据库端口 + @param db_user 用户名 + @param db_password 数据库密码 + @param db_ps 数据库备注 + @return dict + ''' + + arrs = ['db_host', 'db_port', 'db_user', 'db_password', 'db_ps', 'type'] + if get.type == 'redis': + arrs = ['db_host', 'db_port', 'db_password', 'db_ps', 'type'] + + cRet = self.check_cloud_args(get, arrs) + if isinstance(cRet, dict): + return cRet + # if not cRet['status']: + # return public.return_message(-1, 0,) + # return cRet + get['db_name'] = None + id = int(get.id) + get['db_port'] = int(get['db_port']) + db_find = public.M('database_servers').where('id=?', (id,)).find() + if not db_find: + return public.return_message(-1, 0, 'The specified remote server does not exist!') + _modify = False + if db_find['db_host'] != get['db_host'] or db_find['db_port'] != get['db_port']: + _modify = True + if public.M('database_servers').where('db_host=? AND db_port=?', (get['db_host'], get['db_port'])).count(): + return public.return_message(-1, 0, + 'The specified server already exists: [{}:{}]'.format(get['db_host'], + get['db_port'])) + + if db_find['db_user'] != get['db_user'] or db_find['db_password'] != get['db_password']: + _modify = True + _modify = True + + if _modify: + try: + res = self.check_cloud_database(get) + except BaseException as ex: + # public.print_log("获取远程数据库链接状态报错: {}".format(ex)) + return public.return_message(-1, 0, 'Database connection failed') + + if res['message'].get('result', '') == '' or res['message'].get('result', '') == False: + return public.return_message(-1, 0, "The remote database could not be connected") + + pdata = { + 'db_host': get['db_host'], + 'db_port': int(get['db_port']), + 'db_user': get['db_user'], + 'db_password': get['db_password'], + 'db_type': get['type'], + 'ps': public.xssencode2(get['db_ps'].strip()) + } + + result = public.M("database_servers").where('id=?', (id,)).update(pdata) + if isinstance(result, int): + public.WriteLog('Database manager', + 'Modify the remote MySQL server[{}:{}]'.format(get['db_host'], get['db_port'])) + + return public.return_message(0, 0, 'Successfully modified!') + return public.return_message(-1, 0, 'Fail to edit: {}'.format(result)) + + # 检测数据库执行错误 + def IsSqlError(self, mysqlMsg): + + if mysqlMsg: + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return public.return_message(-1, 0, 'DATABASE_ERR_MYSQLDB') + if "2002," in mysqlMsg: + return public.return_message(-1, 0, 'DATABASE_ERR_CONNECT') + if "2003," in mysqlMsg: + return public.return_message(-1, 0, + 'Database connection timed out, please check if the configuration is correct.') + if "1045," in mysqlMsg: + return public.return_message(-1, 0, 'MySQL password error.') + if "1040," in mysqlMsg: + return public.return_message(-1, 0, + 'Exceeded maximum number of connections, please try again later.') + if "1130," in mysqlMsg: + return public.return_message(-1, 0, + 'Database connection failed, please check whether the root user is authorized to access 127.0.0.1.') + if "using password:" in mysqlMsg: + return public.return_message(-1, 0, 'DATABASE_ERR_PASS') + if "Connection refused" in mysqlMsg: + return public.return_message(-1, 0, 'DATABASE_ERR_CONNECT') + if "1133" in mysqlMsg: + return public.return_message(-1, 0, 'DATABASE_ERR_NOT_EXISTS') + if "2005_login_error" == mysqlMsg: + return public.return_message(-1, 0, + 'The connection times out, please manually enable the TCP/IP function (Start Menu->SQL 2005->Configuration Tools->2005 Network Configuration->TCP/IP->Enable)') + if 'already exists' in mysqlMsg: + return public.return_message(-1, 0, + 'The specified database already exists, please do not add it repeatedly.') + if 'Cannot open backup device' in mysqlMsg: + return public.return_message(-1, 0, + 'The operation failed, the remote database does not support the operation.') + + if '1142' in mysqlMsg: + return public.return_message(-1, 0, 'Insufficient permissions, please use root user.') + + if "DB-Lib error message 20018" in mysqlMsg: + return public.return_message(-1, 0, + 'Create failed, SQL Server requires GUI support') + + return None + + # ******************************************** 数据库公用方法 ******************************************/ + + +if __name__ == "__main__": + # get = {} + # get['db_host'] = '192.168.1.37' + # get['db_port'] = '3306' + # get['db_user'] = 'root' + + # get['db_password'] = 'HLANEMJFRbPE7Ny2' + # get['db_ps'] = '2' + # get['type'] = 'mysql' + # bt = databaseBase() + # ret = bt.AddCloudServer(get) + # print(ret) + + get = {} + get['db_host'] = '192.168.66.73' + get['db_port'] = '1433' + get['db_user'] = 'sa' + + get['db_password'] = 'dPYi6Gt8GC7SL58C' + get['db_ps'] = '2' + get['type'] = 'sqlserver' + bt = databaseBase() + ret = bt.get_test(get) + print(ret) diff --git a/class_v2/databaseModelV2/mongodbModel.py b/class_v2/databaseModelV2/mongodbModel.py new file mode 100644 index 00000000..5edc43a9 --- /dev/null +++ b/class_v2/databaseModelV2/mongodbModel.py @@ -0,0 +1,933 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- +#角色说明: +#read:允许用户读取指定数据库 +#readWrite:允许用户读写指定数据库 +#dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile +#userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户 +#clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。 +#readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限 +#readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限 +#userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 +#dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。 +#root:只在admin数据库中可用。超级账号,超级权限 + +# sqlite模型 +#------------------------------ +import os,re,json,time +from databaseModelV2.base import databaseBase +import public +from public.validate import Param +try: + import pymongo +except: + public.ExecShell("btpip install pymongo") + import pymongo +try: + from BTPanel import session +except :pass + + +class panelMongoDB(): + + __DB_PASS = None + __DB_USER = None + __DB_PORT = 27017 + __DB_HOST = '127.0.0.1' + __DB_CONN = None + __DB_ERR = None + + __DB_CLOUD = None + def __init__(self): + self.__config = self.get_options(None) + + def __Conn(self,auth): + + if not self.__DB_CLOUD: + path = '{}/data/mongo.root'.format(public.get_panel_path()) + if os.path.exists(path): self.__DB_PASS = public.readFile(path) + self.__DB_PORT = int(self.__config['port']) + + try: + if not self.__DB_USER and auth: + self.__DB_USER = "root" + self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=self.__DB_PORT, username = self.__DB_USER, password=self.__DB_PASS) + self.__DB_CONN.admin.command({"listDatabases":1}) + return True + except : + try: + self.__DB_CONN = pymongo.MongoClient(host=self.__DB_HOST, port=self.__DB_PORT, username = self.__DB_USER, password=self.__DB_PASS) + self.__DB_CONN.admin.authenticate('root', self.__DB_PASS) + return True + except : + self.__DB_ERR = public.get_error_info() + return False + + + def get_db_obj(self,db_name = 'admin',auth=0): + """ + @获取连接对象 + """ + if not self.__Conn(auth): return self.__DB_ERR + + return self.__DB_CONN[db_name] + + def set_host(self,host,port,name,username,password,prefix = ''): + self.__DB_HOST = host + self.__DB_PORT = int(port) + self.__DB_NAME = name + if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME) + self.__DB_USER = str(username) + self._USER = str(username) + self.__DB_PASS = str(password) + self.__DB_PREFIX = prefix + self.__DB_CLOUD = 1 + return self + + + + #获取配置文件 + def get_config(self,get): + filename = '{}/mongodb/config.conf'.format(public.get_setup_path()) + if os.path.exists(filename): + return public.readFile(filename) + return "" + + #获取配置项 + def get_options(self,get): + options = ['port','bind_ip','logpath','dbpath','authorization'] + data = {} + conf = self.get_config(None) + + for opt in options: + tmp = re.findall(opt + r":\s+(.+)",conf) + if not tmp: continue; + data[opt] = tmp[0] + + if not 'authorization' in data:data['authorization'] = "disabled" + + # public.writeFile('/www/server/1.txt',json.dumps(data)) + return data + + +class main(databaseBase): + + __conf_path = '{}/mongodb/config.conf'.format(public.get_setup_path()) + def __init__(self): + pass + + + def get_list(self,args): + """ + @获取数据库列表 + @sql_type = sqlserver + """ + # {"table": "databases", "search": "", "limit": 20, "p": 1} + # 校验参数 + try: + args.validate([ + Param('table').Require().String().Xss(), + Param('search').String(), + Param('order').String().Xss(), + Param('limit').Integer(), + Param('p').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # return self.get_base_list(args, sql_type = 'mongodb') + return public.return_message(0, 0, self.get_base_list(args, sql_type = 'mongodb')) + + def GetCloudServer(self,args): + ''' + @name 获取远程服务器列表 + @author hwliang<2021-01-10> + @return list + ''' + # return self.GetBaseCloudServer(args) + return public.return_message(0, 0, self.GetBaseCloudServer(args)) + + + def AddCloudServer(self,args): + ''' + @添加远程数据库 + ''' + return self.AddBaseCloudServer(args) + # return public.return_message(0, 0, self.AddBaseCloudServer(args)) + + def RemoveCloudServer(self,args): + ''' + @删除远程数据库 + ''' + return self.RemoveBaseCloudServer(args) + # return public.return_message(0, 0, self.RemoveBaseCloudServer(args)) + + def ModifyCloudServer(self,args): + ''' + @修改远程数据库 + ''' + return self.ModifyBaseCloudServer(args) + # return public.return_message(0, 0, self.ModifyBaseCloudServer(args)) + + #获取数据库列表 + def exists_databases(self,get): + db_name = get + if type(get) != str:db_name = get['db_name'] + auth_status = self.get_local_auth(get) + db_obj = self.get_obj_by_sid(self.sid).get_db_obj('admin',auth=auth_status) + data = db_obj.command({"listDatabases":1}) + if 'databases' in data: + for x in data['databases']: + if x['name'] == db_name: + return True + return False + + def __set_auth_open(self,status): + """ + @设置数据库密码访问开关 + @状态 status:1 开启,2:关闭 + """ + + conf = public.readFile(self.__conf_path) + if status: + conf = re.sub(r'authorization\s*\:\s*disabled','authorization: enabled',conf) + else: + conf = re.sub(r'authorization\s*\:\s*enabled','authorization: disabled',conf) + + public.writeFile(self.__conf_path,conf) + self.restart_services() + + return True + + + def set_auth_status(self,get): + """ + @设置密码认证状态 + @status int 0:关闭,1:开启 + """ + + if not public.process_exists("mongod"): + # return public.returnMsg(False,"Mongodb service has not been started yet!") + return public.return_message(-1, 0, "Mongodb service has not been started yet!") + + status = int(get.status) + path = '{}/data/mongo.root'.format(public.get_panel_path()) + if status: + if hasattr(get,'password'): + password = get['password'].strip() + if not password or not re.search(r"^[\w@\.]+$", password): + # return public.return_msg_gettext(False, 'Database password cannot be empty or have special characters!') + return public.return_message(-1, 0, 'Database password cannot be empty or have special characters!') + + # if re.search('[\u4e00-\u9fa5]',password): + # return public.returnMsg(False,'Database password cannot be Chinese, please change the name!') + else: + password = public.GetRandomString(16) + self.__set_auth_open(0) + + _client = panelMongoDB().get_db_obj('admin') + try: + _client.command("dropUser", "root") + except : pass + + _client.command("createUser", "root", pwd=password, roles=[ + {'role':'root','db':'admin'}, + {'role':'clusterAdmin','db':'admin'}, + {'role':'readAnyDatabase','db':'admin'}, + {'role':'readWriteAnyDatabase','db':'admin'}, + {'role':'userAdminAnyDatabase','db':'admin'}, + {'role':'dbAdminAnyDatabase','db':'admin'}, + {'role':'userAdmin','db':'admin'}, + {'role':'dbAdmin','db':'admin'} + ]) + + self.__set_auth_open(1) + + public.writeFile(path,password) + else: + if os.path.exists(path): os.remove(path) + self.__set_auth_open(0) + + # return public.return_msg_gettext(True,'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + + def restart_services(self): + """ + @重启服务 + """ + public.ExecShell('/etc/init.d/mongodb restart') + return True + + def get_obj_by_sid(self,sid = 0,conn_config = None): + """ + @取mssql数据库对像 By sid + @sid 数据库分类,0:本地 + """ + if type(sid) == str: + try: + sid = int(sid) + except :sid = 0 + + if sid: + if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find() + db_obj = panelMongoDB() + + try: + db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password']) + except Exception as e: + raise public.PanelError(e) + else: + db_obj = panelMongoDB() + return db_obj + + + + def get_local_auth(self,get): + """ + @验证本地数据库是否需要密码 + """ + self.sid = get.get('sid/d',0) + if self.sid != 0: return True + + conf = panelMongoDB().get_options(None) + if conf['authorization'] == 'enabled': + return True + return False + + def AddDatabase(self,args): + """ + @添加数据库 + """ + + # 校验参数 + try: + args.validate([ + Param('sid').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # try: + # int(args.sid) + # except: + # return public.returnMsg(False, 'Database type sid needs int type!') + + + if not int(args.sid) and not public.process_exists("mongod"): + # return public.returnMsg(False,"Mongodb service has not been started yet!") + return public.return_message(-1, 0, "Mongodb service has not been started yet!") + + # try: + username = '' + password = '' + auth_status = self.get_local_auth(args) #auth为true时如果__DB_USER为空则将它赋值为 root,用于开启本地认证后数据库用户为空的情况 + data_name = args.name.strip() + # 检测重复添加 + find = public.M('databases').where("name=?", (data_name,)).find() + if find: + return public.return_message(-1, 0, 'Cannot be added repeatedly') + if not data_name: + # return public.returnMsg(False, "Database name cannot be empty!") + return public.return_message(-1, 0, "Database name cannot be empty!") + if auth_status: + res = self.add_base_database(args) + if not res['status']: + # return res + return public.return_message(-1, 0, res['msg']) + + data_name = res['data_name'] + username = res['username'] + password = res['data_pwd'] + else: + username = data_name + db_obj = self.get_obj_by_sid(self.sid).get_db_obj(data_name,auth=auth_status) + dtype = 'MongoDB' + if not hasattr(args,'ps'): args['ps'] = public.getMsg('INPUT_PS'); + addTime = time.strftime('%Y-%m-%d %X',time.localtime()) + + pid = 0 + if hasattr(args,'pid'): pid = args.pid + + if hasattr(args,'contact'): + site = public.M('sites').where("id=?",(args.contact,)).field('id,name').find() + if site: + pid = int(args.contact) + args['ps'] = site['name'] + + db_type = 0 + if self.sid: db_type = 2 + + db_obj.chat.insert_one({}) + if auth_status: + db_obj.command("createUser", username, pwd=password, roles=[{'role':'dbOwner','db':data_name},{'role':'userAdmin','db':data_name}]) + + public.set_module_logs('linux_mongodb','AddDatabase',1) + + #添加入SQLITE + public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type',(pid,self.sid,db_type,data_name,username,password,'127.0.0.1',args['ps'],addTime,dtype)) + public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS',(data_name,)) + # return public.returnMsg(True,'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + # except Exception as ex: + # public.print_log("error info66: {}".format(ex)) + # return public.return_message(-1, 0, str(ex)) + + def DeleteDatabase(self,args): + """ + @删除数据库 + """ + # 校验参数 + try: + args.validate([ + Param('id').Require().Integer(), + Param('name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = args['id'] + find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid,db_type').find() + if not find: + # return public.returnMsg(False,'The specified database does not exist.') + return public.return_message(-1, 0, 'The specified database does not exist.') + + + # try: + # int(find['sid']) + # except: + # return public.returnMsg(False, 'Database type sid needs int type!') + + + if not public.process_exists("mongod") and not int(find['sid']): + # return public.returnMsg(False,"Mongodb service has not been started yet!") + return public.return_message(-1, 0, "Mongodb service has not been started yet!") + + name = args['name'] + username = find['username'] + auth_status = self.get_local_auth(args) + db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(name,auth_status) + try: + db_obj.command("dropUser", username) + except : + pass + + db_obj.command('dropDatabase') + #删除SQLITE + public.M('databases').where("id=?",(id,)).delete() + public.WriteLog("Database manager", 'Successfully deleted!',(name,)) + # return public.returnMsg(True, 'Successfully deleted!') + return public.return_message(0, 0, 'Successfully deleted!') + + + def get_info_by_db_id(self,db_id): + """ + @获取数据库连接详情 + @db_id 数据库id + """ + find = public.M('databases').where("id=?" ,db_id).find() + if not find: return False + + data = { + 'db_host':'127.0.0.1', + 'db_port':int(panelMongoDB().get_options(None)['port']), + 'db_user':find['username'], + 'db_password':find['password'] + } + + if int(find['sid']): + conn_config = public.M('database_servers').where("id=?" ,find['sid']).find() + + data['db_host'] = conn_config['db_host'] + data['db_port'] = int(conn_config['db_port']) + + + return data + + #导入 + def InputSql(self,args): + + # 校验参数 + try: + args.validate([ + Param('file').SafePath(), # 文件路径 + Param('name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + name = args.name + file = args.file + # try: + if not os.path.exists(file): + # return public.returnMsg(False,'The import path does not exist!') + return public.return_message(-1, 0, 'The import path does not exist!') + if not os.path.isfile(file): + # return public.returnMsg(False,'Only importing compressed files is supported!') + return public.return_message(-1, 0, 'Only importing compressed files is supported!') + find = public.M('databases').where("name=? AND LOWER(type)=LOWER('MongoDB')",(name,)).find() + if not find: + # return public.returnMsg(False,'This database was not found!') + return public.return_message(-1, 0, 'This database was not found!') + + get = public.dict_obj() + get.sid = find['sid'] + if not public.process_exists("mongod") and not int(find['sid']): + # return public.returnMsg(False,"Mongodb service has not been started yet!") + return public.return_message(-1, 0, "Mongodb service has not been started yet!") + info = self.get_info_by_db_id(find['id']) + mongorestore_obj = '{}/mongodb/bin/mongorestore'.format(public.get_setup_path()) + mongoimport_obj = '{}/mongodb/bin/mongoimport'.format(public.get_setup_path()) + if not os.path.exists(mongorestore_obj): + # return public.returnMsg(False,'Lack of backup tools, please install MongoDB through [APP Store] first!') + return public.return_message(-1, 0, 'Lack of backup tools, please install MongoDB through [APP Store] first!') + + dir_tmp, file_tmp = os.path.split(file) + split_tmp = file_tmp.split(".") + ext = split_tmp[-1] + + ext_err = ".".join(split_tmp[1:]) + if len(split_tmp[1:]) == 2 and split_tmp[1] not in ['json', 'csv']: + # return public.returnMsg(False, f'.{ext_err} This file format is not currently supported!') + return public.return_message(-1, 0, f'.{ext_err} This file format is not currently supported!') + if ext not in ['json', 'csv', 'gz', 'zip']: + # return public.returnMsg(False, f'.{ext_err} This file format is not currently supported!') + return public.return_message(-1, 0, f'.{ext_err} This file format is not currently supported!') + + tmpFile = ".".join(split_tmp[:-1]) + isgzip = False + if ext != '': # gz zip + if tmpFile == '': + # return public.returnMsg(False, 'FILE_NOT_EXISTS', (tmpFile,)) + return public.return_message(-1, 0, 'FILE_NOT_EXISTS', (tmpFile,)) + isgzip = True + + # 面板默认备份路径 + backupPath = session['config']['backup_path'] + '/database' + input_path = os.path.join(backupPath, tmpFile) + # 备份文件的路径 + input_path2 = os.path.join(dir_tmp, tmpFile) + + if ext == 'zip': # zip + public.ExecShell("cd " + backupPath + " && unzip " + '"' + file + '"') + else: # gz + public.ExecShell("cd " + backupPath + " && tar zxf " + '"' + file + '"') + if not os.path.exists(input_path): + # 兼容从备份文件所在目录恢复 + if not os.path.exists(input_path2): + public.ExecShell("cd " + backupPath + " && gunzip -q " + '"' + file + '"') + else: + input_path = input_path2 + + if not os.path.exists(input_path) and os.path.isfile(input_path2): + input_path = input_path2 + else: + input_path = file + + if os.path.isdir(input_path): # zip,gz,bson + if self.get_local_auth(get): + for temp_file in os.listdir(input_path): + shell = f""" + {mongorestore_obj} \ + --host={info['db_host']} \ + --port={info['db_port']} \ + --db={find['name']} \ + --username={info['db_user']} \ + --password={info['db_password']} \ + --drop \ + {os.path.join(input_path, temp_file)} + """ + public.ExecShell(shell) + else: + for temp_file in os.listdir(input_path): + shell = f""" + {mongorestore_obj} \ + --host={info['db_host']} \ + --port={info['db_port']} \ + --db={find['name']} \ + --drop \ + {os.path.join(input_path, temp_file)} + """ + public.ExecShell(shell) + if isgzip is True: + public.ExecShell("rm -f " + input_path) + else:# json,csv + file_tmp = os.path.basename(input_path) + file_name = file_tmp.split(".")[0] + ext = file_tmp.split(".")[-1] + + if ext not in ["json","csv"]: + # return public.returnMsg(False, 'File format is incorrect!') + return public.return_message(-1, 0, 'File format is incorrect!') + + shell_txt = "" + if ext == "csv": + fp = open(input_path, "r") + fields_list = fp.readline() + fp.close() + shell_txt = f"--fields={fields_list}" + if self.get_local_auth(get): + shell = f""" + {mongoimport_obj} \ + --host={info['db_host']} \ + --port={info['db_port']} \ + --db={find['name']} \ + --username={info['db_user']} \ + --password={info['db_password']} \ + --collection={file_name} \ + --file={input_path} \ + --type={ext} \ + --drop + """ + else: + shell = f""" + {mongoimport_obj} \ + --host={info['db_host']} \ + --port={info['db_port']} \ + --db={find['name']} \ + --collection={file_name} \ + --file={input_path} \ + --type={ext} \ + --drop + """ + shell = f"{shell} {shell_txt}" + public.ExecShell(shell) + public.WriteLog("Database manager", 'Import database [{}] succeeded'.format(name)) + # return public.returnMsg(True, 'Successfully imported database!') + return public.return_message(0, 0, 'Successfully imported database!') + # except Exception as ex: + # public.print_log("error info66: {}".format(ex)) + # return public.return_message(-1, 0, str(ex)) + +####################### + def ToBackup(self,args): + """ + @备份数据库 id 数据库id + """ + + # 校验参数 + try: + args.validate([ + Param('id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = args['id'] + find = public.M('databases').where("id=? AND LOWER(type)=LOWER('MongoDB')",(id,)).find() + if not find: + # return public.returnMsg(False,'The specified database does not exist.') + return public.return_message(-1, 0, 'The specified database does not exist.') + + fileName = f"{find['name']}_mongodb_data_{time.strftime('%Y%m%d_%H%M%S',time.localtime())}" + backupName = session['config']['backup_path'] + '/database/mongodb/' + fileName + + spath = os.path.dirname(backupName) + if not os.path.exists(spath): os.makedirs(spath) + + get = public.dict_obj() + get.sid = find['sid'] + try: + sid = int(find['sid']) + except: + # return public.returnMsg(False, 'Database type sid needs int type!') + return public.return_message(-1, 0, 'Database type sid needs int type!') + + + if not public.process_exists("mongod") and not int(find['sid']): + # return public.returnMsg(False,"Mongodb service has not been started yet!") + return public.return_message(-1, 0, "Mongodb service has not been started yet!") + info = self.get_info_by_db_id(id) + + sql_dump = '{}/mongodb/bin/mongodump'.format(public.get_setup_path()) + if not os.path.exists(sql_dump): + # return public.returnMsg(False,'Lack of backup tools, please install MongoDB through [APP Store] first!') + return public.return_message(-1, 0, 'Lack of backup tools, please install MongoDB through [APP Store] first!') + + if self.get_local_auth(get): + if not info['db_password']: + # return public.returnMsg(False,'Password authentication has been enabled. The password cannot be empty when the database is backed up. Please set a password and try again!') + return public.return_message(-1, 0, 'Password authentication has been enabled. The password cannot be empty when the database is backed up. Please set a password and try again!') + shell = "{} -h {} --port {} -u {} -p {} -d {} -o {} ".format(sql_dump,info['db_host'],info['db_port'],info['db_user'],info['db_password'],find['name'] ,backupName) + else: + shell = "{} -h {} --port {} -d {} -o {} ".format(sql_dump,info['db_host'],info['db_port'],find['name'] ,backupName) + + ret = public.ExecShell(shell) + if not os.path.exists(backupName): + # return public.returnMsg(False,'Database backup failed, file does not exist'); + return public.return_message(-1, 0, 'Database backup failed, file does not exist'); + + + backupFile = f"{backupName}.zip" + public.ExecShell(f"cd {spath} && zip {backupFile} -r {fileName}") + fileName = f"{fileName}.zip" + public.M('backup').add('type,name,pid,filename,size,addtime',(1,fileName,id,backupFile,0,time.strftime('%Y-%m-%d %X',time.localtime()))) + public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS",(find['name'],)) + + public.ExecShell(f"rm -rf {backupName}") + if not os.path.exists(backupFile): + # return public.returnMsg(True, 'Backup failed,{}.'.format(ret[0])) + return public.return_message(0, 0, 'Backup failed,{}.'.format(ret[0])) + if os.path.getsize(backupFile) < 1: + # return public.returnMsg(True, 'The backup is executed successfully, the backup file is less than 1b, please check the backup integrity.') + return public.return_message(0, 0, 'The backup is executed successfully, the backup file is less than 1b, please check the backup integrity.') + else: + # return public.returnMsg(True, 'BACKUP_SUCCESS') + return public.return_message(0, 0, 'BACKUP_SUCCESS') + + def DelBackup(self,args): + """ + @删除备份文件 + """ + return self.delete_base_backup(args) + # return public.return_message(0, 0, self.delete_base_backup(args)) + + #同步数据库到服务器 + def SyncToDatabases(self,get): + type = int(get['type']) + n = 0 + sql = public.M('databases') + if type == 0: + data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('MongoDB',)).select() + + for value in data: + if value['db_type'] in ['1',1]: + continue # 跳过远程数据库 + result = self.ToDataBase(value) + if result == 1: n +=1 + else: + import json + data = json.loads(get.ids) + for value in data: + find = sql.where("id=?",(value,)).field('id,name,username,password,sid,db_type,accept,type').find() + result = self.ToDataBase(find) + if result == 1: n +=1 + if n == 1: + # return public.returnMsg(True, 'Synchronization succeeded') + return public.return_message(0, 0, 'Synchronization succeeded') + elif n == 0: + # return public.returnMsg(False,'Sync failed') + return public.return_message(-1, 0, 'No synchronized database') + # return public.returnMsg(True,'DATABASE_SYNC_SUCCESS',(str(n),)) + return public.return_message(0, 0, 'DATABASE_SYNC_SUCCESS {}'.format(n)) + + #添加到服务器 + def ToDataBase(self,find): + if find['username'] == 'bt_default': + # return 0 + return public.return_message(0, 0, 0) + if len(find['password']) < 3 : + find['username'] = find['name'] + find['password'] = public.md5(str(time.time()) + find['name'])[0:10] + public.M('databases').where("id=?",(find['id'],)).save('password,username',(find['password'],find['username'])) + + self.sid = find['sid'] + try: + int(find['sid']) + except: + return public.return_message(-1, 0, 'Database type sid needs int type!!') + # return public.returnMsg(False, 'Database type sid needs int type!!') + if not public.process_exists("mongod") and not int(find['sid']): + # return public.returnMsg(False,"Mongodb service has not been started yet!!") + return public.return_message(-1, 0, "Mongodb service has not been started yet!!") + + + get = public.dict_obj() + get.sid = self.sid + auth_status = self.get_local_auth(get) + if auth_status: + db_obj = self.get_obj_by_sid(self.sid).get_db_obj(find['name'], auth_status) + try: + db_obj.chat.insert_one({}) + db_obj.command("dropUser", find['username']) + except :pass + try: + db_obj.command("createUser", find['username'], pwd=find['password'], roles=[{'role':'dbOwner','db':find['name']},{'role':'userAdmin','db':find['name']}]) + except: + pass + # return 1 + return public.return_message(0, 0, 1) + + def SyncGetDatabases(self,get): + """ + @从服务器获取数据库 + """ + n = 0;s = 0; + db_type = 0 + self.sid = get.get('sid/d',0) + if self.sid: db_type = 2 + try: + int(get.sid) + except: + # return public.returnMsg(False, 'The database type SID requires an INT!') + return public.return_message(-1, 0, 'The database type SID requires an INT!') + if not public.process_exists("mongod") and not int(get.sid): + # return public.returnMsg(False,"The Mongodb service is not enabled!") + return public.return_message(-1, 0, "The Mongodb service is not enabled!") + auth_status = self.get_local_auth(get) + data = self.get_obj_by_sid(self.sid).get_db_obj('admin',auth=auth_status).command({"listDatabases":1}) + + sql = public.M('databases') + nameArr = ['information_schema','performance_schema','mysql','sys','master','model','msdb','tempdb','config','local','admin'] + for item in data['databases']: + dbname = item['name'] + if sql.where("name=?",(dbname,)).count(): continue + if not dbname in nameArr: + if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type',(dbname,dbname,'','',public.getMsg('INPUT_PS'),time.strftime('%Y-%m-%d %X',time.localtime()),'MongoDB',self.sid,db_type)): n +=1 + + # return public.returnMsg(True,'DATABASE_GET_SUCCESS',(str(n),)) + return public.return_message(0, 0, 'DATABASE_GET_SUCCESS {}'.format(n)) + + + def ResDatabasePassword(self,args): + """ + @修改用户密码 + """ + id = args['id'] + username = args['name'].strip() + newpassword = public.trim(args['password']) + + try: + if not newpassword: + # return public.returnMsg(False, 'Modify the failure,The database[' + username + ']password cannot be empty.'); + return public.return_message(-1, 0, 'Modify the failure,The database[' + username + ']password cannot be empty.'); + if len(re.search(r"^[\w@\.]+$", newpassword).groups()) > 0: + # return public.returnMsg(False, 'The database password cannot be empty or contain special characters') + return public.return_message(-1, 0, 'The database password cannot be empty or contain special characters') + + if re.search('[\u4e00-\u9fa5]',newpassword): + # return public.returnMsg(False,'Database password cannot be Chinese, please change the name!') + return public.return_message(-1, 0, 'Database password cannot be Chinese, please change the name!') + except : + # return public.returnMsg(False, 'The database password cannot be empty or contain special characters') + return public.return_message(-1, 0, 'The database password cannot be empty or contain special characters') + + find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid').find(); + if not find: + # return public.returnMsg(False, 'The modification failed because the specified database does not exist.'); + return public.return_message(-1, 0, 'The modification failed because the specified database does not exist.'); + + get = public.dict_obj() + get.sid = find['sid'] + try: + int(find['sid']) + except: + # return public.returnMsg(False, 'The database type SID requires an INT!') + return public.return_message(-1, 0, 'The database type SID requires an INT!') + if not public.process_exists("mongod") and not int(find['sid']): + # return public.returnMsg(False,"The Mongodb service is not enabled!") + return public.return_message(-1, 0, "The Mongodb service is not enabled!") + auth_status = self.get_local_auth(args) + if auth_status: + db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(username,auth=auth_status) + try: + print(db_obj.command("updateUser", username, pwd = newpassword)) + except : + print(db_obj.command("createUser", username, pwd=newpassword, roles=[{'role':'dbOwner','db':find['name']},{'role':'userAdmin','db':find['name']}])) + else: + # return public.returnMsg(False, 'Password access is not enabled for the database.') + return public.return_message(-1, 0, 'Password access is not enabled for the database.') + + #修改SQLITE + public.M('databases').where("id=?",(id,)).setField('password',newpassword) + + public.WriteLog("TYPE_DATABASE",'DATABASE_PASS_SUCCESS',(find['name'],)) + # return public.returnMsg(True,'DATABASE_PASS_SUCCESS',(find['name'],)) + return public.return_message(0, 0, 'DATABASE_PASS_SUCCESS',(find['name'],)) + + def get_root_pwd(self,args): + """ + @获取root密码 + """ + config = panelMongoDB().get_options(None) + sa_path = '{}/data/mongo.root'.format(public.get_panel_path()) + if os.path.exists(sa_path): + config['msg'] = public.readFile(sa_path) + else: + config['msg'] = '' + config['root'] = config['msg'] + # return config + return public.return_message(0, 0, config) + + def get_database_size_by_id(self, args): + """ + @获取数据库尺寸(批量删除验证) + @args json/int 数据库id + """ + # if not public.process_exists("mongod"): + # return public.returnMsg(False,"The Mongodb service is not enabled!") + total = 0 + db_id = args + if not isinstance(args, int): db_id = args['db_id'] + + find = public.M('databases').where('id=?', db_id).find() + try: + int(find['sid']) + except: + # return 0 + return public.return_message(0, 0, 0) + if not public.process_exists("mongod") and not int(find['sid']): + # return 0 + return public.return_message(0, 0, 0) + try: + auth_status = self.get_local_auth(args) + db_obj = self.get_obj_by_sid(find['sid']).get_db_obj(find['name'], auth=auth_status) + print(db_obj) + print(db_obj.stats()) + + total = tables[0][1] + if not total: total = 0 + except: + print(public.get_error_info()) + + # return total + return public.return_message(0, 0, total) + + def check_del_data(self,args): + """ + @删除数据库前置检测 + """ + # return self.check_base_del_data(args) + return public.return_message(0, 0, self.check_base_del_data(args)) + + + def check_cloud_database_status(self,conn_config): + """ + @检测远程数据库是否连接 + @conn_config 远程数据库配置,包含host port pwd等信息 + """ + try: + if not 'db_name' in conn_config: conn_config['db_name'] = None + sql_obj = panelMongoDB().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password']) + + db_obj = sql_obj.get_db_obj('admin') + data = db_obj.command({"listDatabases":1}) + + if 'databases' in data: + # return True + return public.return_message(0, 0, True) + # return False + return public.return_message(-1, 0, False) + except Exception as ex: + # return public.returnMsg(False,ex) + return public.return_message(0, 0, ex) diff --git a/class_v2/databaseModelV2/pgsqlModel.py b/class_v2/databaseModelV2/pgsqlModel.py new file mode 100644 index 00000000..65029070 --- /dev/null +++ b/class_v2/databaseModelV2/pgsqlModel.py @@ -0,0 +1,769 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hezhihong +# ------------------------------------------------------------------- + +# ------------------------------ +# postgresql模型 +# ------------------------------ +import os, re, json, time +from databaseModelV2.base import databaseBase +import public + +try: + from BTPanel import session +except: + pass +try: + import psycopg2 + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT +except: + pass + + +class panelPgsql: + __DB_PASS = None + __DB_USER = 'postgres' + __DB_PORT = 5432 + __DB_HOST = 'localhost' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + + __DB_CLOUD = 0 # 远程数据库 + + def __init__(self): + self.__DB_CLOUD = 0 + if self.__DB_USER == 'postgres' and self.__DB_HOST == 'localhost' and self.__DB_PASS == None: + tmp_args = public.dict_obj() + tmp_args.is_True = True + self.__DB_PASS = main().get_root_pwd(tmp_args) + + def set_host(self, host, port, name, username, password, prefix=''): + self.__DB_HOST = host + self.__DB_PORT = int(port) + self.__DB_NAME = name + if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME) + self.__DB_USER = str(username) + self._USER = str(username) + self.__DB_PASS = str(password) + self.__DB_PREFIX = prefix + self.__DB_CLOUD = 1 + return self + + def check_psycopg(self): + """ + @name检测依赖是否正常 + """ + try: + import psycopg2 + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + except: + os.system('btpip install psycopg2-binary') + try: + import psycopg2 + from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + except: + return False + return True + + # 连接MYSQL数据库 + def __Conn(self): + self.check_psycopg() + try: + import psycopg2 + except: + self.__DB_ERR = public.get_error_info() + return False + try: + if self.__DB_USER == 'postgres' and self.__DB_HOST == 'localhost': + if not self.__DB_PASS: + tmp_args = public.dict_obj() + try: + self.__DB_PASS == main().get_root_pwd(tmp_args)['msg'] + except: + pass + self.__DB_CONN = psycopg2.connect(user=self.__DB_USER, password=self.__DB_PASS, host=self.__DB_HOST, + port=self.__DB_PORT) + self.__DB_CONN.autocommit = True + self.__DB_CONN.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # <-- ADD THIS LINE + + self.__DB_CUR = self.__DB_CONN.cursor() + return True + except: + + self.__DB_ERR = public.get_error_info() + print(self.__DB_ERR) + return False + + def execute(self, sql): + # 执行SQL语句返回受影响行 + if not self.__Conn(): return self.__DB_ERR + try: + # print(sql) + result = self.__DB_CUR.execute(sql) + self.__DB_CONN.commit() + self.__Close() + return result + except Exception as ex: + + return ex + + def query(self, sql): + + # 执行SQL语句返回数据集 + if not self.__Conn(): return self.__DB_ERR + try: + self.__DB_CUR.execute(sql) + result = self.__DB_CUR.fetchall() + + data = list(map(list, result)) + self.__Close() + return data + except Exception as ex: + return ex + + # 关闭连接 + def __Close(self): + self.__DB_CUR.close() + self.__DB_CONN.close() + + +class main(databaseBase, panelPgsql): + __ser_name = None + __soft_path = '/www/server/pgsql' + __setup_path = '/www/server/panel/' + __dbuser_info_path = "{}plugin/pgsql_manager_dbuser_info.json".format(__setup_path) + __plugin_path = "{}plugin/pgsql_manager/".format(__setup_path) + + def __init__(self): + + s_path = public.get_setup_path() + v_info = public.readFile("{}/pgsql/version.pl".format(s_path)) + if v_info: + ver = v_info.split('.')[0] + self.__ser_name = 'postgresql-x64-{}'.format(ver) + self.__soft_path = '{}/pgsql/{}'.format(s_path) + + # 获取配置项 + def get_options(self, get): + data = {} + options = ['port', 'listen_addresses'] + if not self.__soft_path: self.__soft_path = '{}/pgsql'.format(public.get_setup_path()) + conf = public.readFile('{}/data/postgresql.conf'.format(self.__soft_path)) + for opt in options: + tmp = re.findall(r"\s+" + opt + r"\s*=\s*(.+)#", conf) + if not tmp: continue; + data[opt] = tmp[0].strip() + if opt == 'listen_addresses': + data[opt] = data[opt].replace('\'', '') + data['password'] = self.get_root_pwd(None)['msg'] + # return data + return public.return_message(0, 0, data) + + def get_list(self, args): + """ + @获取数据库列表 + @sql_type = pgsql + """ + # return self.get_base_list(args, sql_type = 'pgsql') + return public.return_message(0, 0, self.get_base_list(args, sql_type='pgsql')) + + def get_sql_obj_by_sid(self, sid=0, conn_config=None): + """ + @取pgsql数据库对像 By sid + @sid 数据库分类,0:本地 + """ + if type(sid) == str: + try: + sid = int(sid) + except: + sid = 0 + + if sid: + if not conn_config: conn_config = public.M('database_servers').where("id=?", sid).find() + db_obj = panelPgsql() + + try: + db_obj = db_obj.set_host(conn_config['db_host'], conn_config['db_port'], None, conn_config['db_user'], + conn_config['db_password']) + except Exception as e: + raise public.PanelError(e) + else: + db_obj = panelPgsql() + return db_obj + # return public.return_message(0, 0, db_obj) + + def get_sql_obj(self, db_name): + """ + @取pgsql数据库对象 + @db_name 数据库名称 + """ + is_cloud_db = False + if db_name: + db_find = public.M('databases').where("name=?", db_name).find() + if db_find['sid']: + # return self.get_sql_obj_by_sid(db_find['sid']) + return public.return_message(0, 0, self.get_sql_obj_by_sid(db_find['sid'])) + is_cloud_db = db_find['db_type'] in ['1', 1] + + if is_cloud_db: + + db_obj = panelPgsql() + conn_config = json.loads(db_find['conn_config']) + try: + db_obj = db_obj.set_host(conn_config['db_host'], conn_config['db_port'], conn_config['db_name'], + conn_config['db_user'], conn_config['db_password']) + except Exception as e: + raise public.PanelError(e) + else: + db_obj = panelPgsql() + # return db_obj + return public.return_message(0, 0, db_obj) + + def GetCloudServer(self, args): + ''' + @name 获取远程服务器列表 + @author hwliang<2021-01-10> + @return list + ''' + + check_result = os.system('/www/server/pgsql/bin/psql --version') + if check_result != 0 and not public.M('database_servers').where('db_type=?', 'pgsql').count(): + # return [] + return public.return_message(0, 0, []) + # return self.GetBaseCloudServer(args) + return public.return_message(0, 0, self.GetBaseCloudServer(args)) + + def AddCloudServer(self, args): + ''' + @添加远程数据库 + ''' + return self.AddBaseCloudServer(args) + # return public.return_message(0, 0, self.AddBaseCloudServer(args)) + + def RemoveCloudServer(self, args): + ''' + @删除远程数据库 + ''' + return self.RemoveBaseCloudServer(args) + # return public.return_message(0, 0, self.RemoveBaseCloudServer(args)) + + def ModifyCloudServer(self, args): + ''' + @修改远程数据库 + ''' + return self.ModifyBaseCloudServer(args) + + + def AddDatabase(self, args): + """ + @添加数据库 + """ + # try: + if not args.get('name/str', 0): + # rreturn public.returnMsg(False, 'Database name cannot be empty!') + return public.return_message(-1, 0, 'Database name cannot be empty!') + import re + test_str = re.search(r"\W", args.name) + if test_str != None: + # rreturn public.returnMsg(False, 'The database name cannot contain special characters') + return public.return_message(-1, 0, 'The database name cannot contain special characters') + res = self.add_base_database(args) + if not res['status']: + # return res + return public.return_message(-1, 0, res['msg']) + + data_name = res['data_name'] + username = res['username'] + password = res['data_pwd'] + try: + self.sid = int(args['sid']) + except: + self.sid = 0 + + dtype = 'PgSql' + + sql_obj = self.get_sql_obj_by_sid(self.sid) + result = sql_obj.execute("CREATE DATABASE {};".format(data_name)) + + isError = self.IsSqlError(result) + + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + + # 添加用户 + self.__CreateUsers(data_name, username, password, '127.0.0.1') + + if not hasattr(args, 'ps'): args['ps'] = public.getMsg('INPUT_PS'); + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + pid = 0 + if hasattr(args, 'pid'): pid = args.pid + + if hasattr(args, 'contact'): + site = public.M('sites').where("id=?", (args.contact,)).field('id,name').find() + if site: + pid = int(args.contact) + args['ps'] = site['name'] + + db_type = 0 + if self.sid: db_type = 2 + + public.set_module_logs('pgsql', 'AddDatabase', 1) + # 添加入SQLITE + public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type', ( + pid, self.sid, db_type, data_name, username, password, '127.0.0.1', args['ps'], addTime, dtype)) + public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS', (data_name,)) + # return public.returnMsg(True,'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + # except Exception as ex: + # public.print_log("error info55: {}".format(ex)) + # return public.return_message(-1, 0, str(ex)) + def DeleteDatabase(self, get): + """ + @删除数据库 + """ + id = get['id'] + find = public.M('databases').where("id=?", (id,)).field( + 'id,pid,name,username,password,accept,ps,addtime,db_type,conn_config,sid,type').find(); + if not find: + # return public.returnMsg(False,'The specified database does not exist.') + return public.return_message(-1, 0, 'The specified database does not exist.') + + name = get['name'] + username = find['username'] + + sql_obj = self.get_sql_obj_by_sid(find['sid']) + result = sql_obj.execute("drop database {};".format(name)) + sql_obj.execute("drop user {};".format(username)) + # 删除SQLITE + public.M('databases').where("id=?", (id,)).delete() + public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS', (name,)) + # return public.returnMsg(True, 'DEL_SUCCESS') + return public.return_message(0, 0, 'DEL_SUCCESS') + + def ToBackup(self, args): + """ + @备份数据库 id 数据库id + """ + id = args['id'] + + find = public.M('databases').where("id=?", (id,)).find() + if not find: + # return public.returnMsg(False,'Database does not exist!') + return public.return_message(-1, 0, 'Database does not exist!') + + if not find['password'].strip(): + # return public.returnMsg(False,'The database password is empty. Set the password first.') + return public.return_message(-1, 0, 'The database password is empty. Set the password first.') + + sql_dump = '{}/bin/pg_dump'.format(self.__soft_path) + # return sql_dump + if not os.path.isfile(sql_dump): + # return public.returnMsg(False,'Lack of backup tools, please first through the software store PGSQL manager!') + return public.return_message(-1, 0, + 'Lack of backup tools, please first through the software store PGSQL manager!') + + back_path = session['config']['backup_path'] + '/database/pgsql/' + # return back_path + if not os.path.exists(back_path): os.makedirs(back_path) + + fileName = find['name'] + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.sql' + + backupName = back_path + fileName + + if int(find['sid']): + info = self.get_info_by_db_id(id) + shell = '{} "host={} port={} user={} dbname={} password={}" > {}'.format(sql_dump, info['db_host'], + info['db_port'], info['db_user'], + find['name'], info['db_password'], + backupName) + else: + args_one = public.dict_obj() + port = self.get_port(args_one) + shell = '{} "host=127.0.0.1 port={} user={} dbname={} password={}" > {}'.format(sql_dump, port['data'], + find['username'], + find['name'], + find['password'], + backupName) + + ret = public.ExecShell(shell) + if not os.path.exists(backupName): + # return public.returnMsg(False,'BACKUP_ERROR'); + return public.return_message(-1, 0, 'BACKUP_ERROR') + + public.M('backup').add('type,name,pid,filename,size,addtime', + (1, fileName, id, backupName, 0, time.strftime('%Y-%m-%d %X', time.localtime()))) + public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS", (find['name'],)) + + if os.path.getsize(backupName) < 2048: + # return public.returnMsg(True, 'The backup file size is smaller than 2Kb. Check the backup integrity.') + return public.return_message(0, 0, 'The backup file size is smaller than 2Kb. Check the backup integrity.') + else: + # return public.returnMsg(True, 'BACKUP_SUCCESS') + return public.return_message(0, 0, 'BACKUP_SUCCESS') + + def DelBackup(self, args): + """ + @删除备份文件 + """ + return self.delete_base_backup(args) + # return public.return_message(0, 0, self.delete_base_backup(args)) + + def get_port(self, args): # 获取端口号 + str_shell = '''netstat -luntp|grep postgres|head -1|awk '{print $4}'|awk -F: '{print $NF}' ''' + try: + port = public.ExecShell(str_shell)[0] + if port.strip(): + return {'data': port.strip(), "status": True} + else: + return {'data': 5432, "status": False} + except: + return {'data': 5432, "status": False} + + # 导入 + def InputSql(self, get): + + name = get.name + file = get.file + # return name + + find = public.M('databases').where("name=?", (name,)).find() + if not find: + # return public.returnMsg(False,'Database does not exist!') + return public.return_message(-1, 0, 'Database does not exist!') + # return find + if not find['password'].strip(): + # return public.returnMsg(False,'The database password is empty. Set the password first.') + return public.return_message(-1, 0, 'The database password is empty. Set the password first.') + + tmp = file.split('.') + exts = ['sql'] + ext = tmp[len(tmp) - 1] + if ext not in exts: + # return public.returnMsg(False, 'DATABASE_INPUT_ERR_FORMAT') + return public.return_message(-1, 0, 'DATABASE_INPUT_ERR_FORMAT') + + sql_dump = '{}/bin/psql'.format(self.__soft_path) + if not os.path.exists(sql_dump): + # return public.returnMsg(False,'Lack of recovery tool, please use software management to install PGSQL!') + return public.return_message(-1, 0, + 'Lack of recovery tool, please use software management to install PGSQL!') + + if int(find['sid']): + info = self.get_info_by_db_id(find['id']) + shell = '{} "host={} port={} user={} dbname={} password={}" < {}'.format(sql_dump, info['db_host'], + info['db_port'], info['db_user'], + find['name'], info['db_password'], + file) + else: + args_one = public.dict_obj() + port = self.get_port(args_one) + shell = '{} "host=127.0.0.1 port={} user={} dbname={} password={}" < {}'.format(sql_dump, port['data'], + find['username'], + find['name'], + find['password'], file) + + ret = public.ExecShell(shell) + + public.WriteLog("TYPE_DATABASE", 'Description Succeeded in importing database [{}]'.format(name)) + # return public.returnMsg(True, 'DATABASE_INPUT_SUCCESS'); + return public.return_message(0, 0, 'DATABASE_INPUT_SUCCESS') + + def SyncToDatabases(self, get): + """ + @name同步数据库到服务器 + """ + tmp_type = int(get['type']) + n = 0 + sql = public.M('databases') + if tmp_type == 0: + where = "lower(type) = lower('pgsql')" + # data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('pgsql',)).select() + data = sql.field('id,name,username,password,accept,type,sid,db_type').where(where, ()).select() + print(data) + for value in data: + if value['db_type'] in ['1', 1]: + continue # 跳过远程数据库 + result = self.ToDataBase(value) + if result == 1: n += 1 + else: + import json + data = json.loads(get.ids) + for value in data: + find = sql.where("id=?", (value,)).field('id,name,username,password,sid,db_type,accept,type').find() + result = self.ToDataBase(find) + if result == 1: n += 1 + if n == 1: + # return public.returnMsg(True, 'Synchronization succeeded') + return public.return_message(0, 0, 'Synchronization succeeded') + elif n == 0: + # return public.returnMsg(False, 'Sync failed') + return public.return_message(-1, 0, 'Sync failed') + # return public.returnMsg(True, 'DATABASE_SYNC_SUCCESS', (str(n),)) + return public.return_message(0, 0, 'DATABASE_SYNC_SUCCESS {}'.format(n)) + + def ToDataBase(self, find): + """ + @name 添加到服务器 + """ + if find['username'] == 'bt_default': return 0 + if len(find['password']) < 3: + find['username'] = find['name'] + find['password'] = public.md5(str(time.time()) + find['name'])[0:10] + public.M('databases').where("id=?", (find['id'],)).save('password,username', + (find['password'], find['username'])) + + self.sid = find['sid'] + sql_obj = self.get_sql_obj_by_sid(self.sid) + result = sql_obj.execute("CREATE DATABASE {};".format(find['name'])) + isError = self.IsSqlError(result) + if isError != None and isError['status'] == False and isError[ + 'msg'] == 'The specified database already exists, please do not add it repeatedly.': return 1 + + self.__CreateUsers(find['name'], find['username'], find['password'], '127.0.0.1') + + return 1 + + def SyncGetDatabases(self, get): + """ + @name 从服务器获取数据库 + @param sid 0为本地数据库 1为远程数据库 + """ + n = 0; + s = 0; + db_type = 0 + self.sid = get.get('sid/d', 0) + if self.sid: db_type = 2 + + sql_obj = self.get_sql_obj_by_sid(self.sid) + data = sql_obj.query('SELECT datname FROM pg_database;') # select * from pg_database order by datname; + isError = self.IsSqlError(data) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + if type(data) == str: + # return public.returnMsg(False, data) + return public.return_message(-1, 0, data) + + sql = public.M('databases') + nameArr = ['information_schema', 'postgres', 'template1', 'template0', 'performance_schema', 'mysql', 'sys', + 'master', 'model', 'msdb', 'tempdb', 'ReportServerTempDB', 'YueMiao', 'ReportServer'] + for item in data: + + dbname = item[0] + + if sql.where("name=?", (dbname,)).count(): continue + if not dbname in nameArr: + if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type', ( + dbname, dbname, '', '', public.getMsg('INPUT_PS'), time.strftime('%Y-%m-%d %X', time.localtime()), + 'pgsql', self.sid, db_type)): n += 1 + + # return public.returnMsg(True, 'DATABASE_GET_SUCCESS', (str(n),)) + return public.return_message(0, 0, 'DATABASE_GET_SUCCESS {}'.format(n)) + + def ResDatabasePassword(self, args): + """ + @修改用户密码 + """ + id = args['id'] + username = args['name'].strip() + newpassword = public.trim(args['password']) + if not newpassword: + # return public.returnMsg(False, 'The database password cannot be empty.'); + return public.return_message(-1, 0, 'The database password cannot be empty') + + find = public.M('databases').where("id=?", (id,)).field( + 'id,pid,name,username,password,type,accept,ps,addtime,sid').find() + if not find: + # return public.returnMsg(False, 'Modify the failure,The specified database does not exist.'); + return public.return_message(-1, 0, 'Modify the failure,The specified database does not exist') + + sql_obj = self.get_sql_obj_by_sid(find['sid']) + result = sql_obj.execute("alter user {} with password '{}';".format(username, newpassword)) + isError = self.IsSqlError(result) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + + # 修改SQLITE + public.M('databases').where("id=?", (id,)).setField('password', newpassword) + + public.WriteLog("TYPE_DATABASE", 'DATABASE_PASS_SUCCESS', (find['name'],)) + # return public.returnMsg(True, 'DATABASE_PASS_SUCCESS', (find['name'],)) + return public.return_message(0, 0, 'DATABASE_PASS_SUCCESS {}'.format(find['name'],)) + + def get_root_pwd(self, args): + """ + @获取sa密码 + """ + check_result = os.system('/www/server/pgsql/bin/psql --version') + if check_result != 0: + # return public.returnMsg(False,'If PgSQL is not installed or started, install or start it first') + return public.return_message(-1, 0,'If PgSQL is not installed or started, install or start it first') + password = '' + path = '{}/data/postgresAS.json'.format(public.get_panel_path()) + if os.path.isfile(path): + try: + password = json.loads(public.readFile(path))['password'] + print('333333333') + print(password) + except: + pass + if 'is_True' in args and args.is_True: + # return password + return public.return_message(-1, 0, password) + # return public.returnMsg(True, password) + return public.return_message(0, 0, password) + + def set_root_pwd(self, args): + """ + @设置sa密码 + """ + password = public.trim(args['password']) + if len(password) < 8: + # return public.returnMsg(False, 'The password must not be less than 8 digits.') + return public.return_message(-1, 0, 'The password must not be less than 8 digits.') + check_result = os.system('/www/server/pgsql/bin/psql --version') + if check_result != 0: + # return public.returnMsg(False,'If PgSQL is not installed or started, install or start it first') + return public.return_message(-1, 0, 'If PgSQL is not installed or started, install or start it first') + sql_obj = self.get_sql_obj_by_sid('0') + data = sql_obj.query('SELECT datname FROM pg_database;') + isError = self.IsSqlError(data) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + + path = '{}/data/pg_hba.conf'.format(self.__soft_path) + p_path = '{}/data/postgresAS.json'.format(public.get_panel_path()) + if not os.path.isfile(path): + # return public.returnMsg(False,'{}File does not exist, please check the installation is complete!'.format(path)) + return public.return_message(-1, 0, '{}File does not exist, please check the installation is complete!'.format(path)) + src_conf = public.readFile(path) + add_conf = src_conf.replace('md5', 'trust') + # public.writeFile(path,public.readFile(path).replace('md5','trust')) + public.writeFile(path, add_conf) + + pg_obj = panelPgsql() + pg_obj.execute("ALTER USER postgres WITH PASSWORD '{}';".format(password)) + data = {"username": "postgres", "password": ""} + try: + data = json.loads(public.readFile(p_path)) + except: + pass + data['password'] = password + public.writeFile(p_path, json.dumps(data)) + public.writeFile(path, src_conf) + # return public.returnMsg(True, 'The administrator password is successfully changed. Procedure.') + return public.return_message(0, 0, 'The administrator password is successfully changed. Procedure.') + + def get_info_by_db_id(self, db_id): + """ + @获取数据库连接详情 + @db_id 数据库id + """ + # print(db_id,'111111111111') + find = public.M('databases').where("id=?", db_id).find() + # return find + if not find: return False + # print(find) + data = { + 'db_host': '127.0.0.1', + 'db_port': 5432, + 'db_user': find['username'], + 'db_password': find['password'] + } + + if int(find['sid']): + conn_config = public.M('database_servers').where("id=?", find['sid']).find() + + data['db_host'] = conn_config['db_host'] + data['db_port'] = int(conn_config['db_port']) + return data + + def get_database_size_by_id(self, args): + """ + @获取数据库尺寸(批量删除验证) + @args json/int 数据库id + """ + total = 0 + db_id = args + if not isinstance(args, int): db_id = args['db_id'] + + try: + name = public.M('databases').where('id=?', db_id).getField('name') + sql_obj = self.get_sql_obj(name) + tables = sql_obj.query( + "select name,size,type from sys.master_files where type=0 and name = '{}'".format(name)) + + total = tables[0][1] + if not total: total = 0 + except: + pass + + return total + + def check_del_data(self, args): + """ + @删除数据库前置检测 + """ + # return self.check_base_del_data(args) + return public.return_message(0, 0, self.check_base_del_data(args)) + + # 本地创建数据库 + def __CreateUsers(self, data_name, username, password, address): + """ + @创建数据库用户 + """ + sql_obj = self.get_sql_obj_by_sid(self.sid) + sql_obj.execute("CREATE USER {} WITH PASSWORD '{}';".format(username, password)) + sql_obj.execute("GRANT ALL PRIVILEGES ON DATABASE {} TO {};".format(data_name, username)) + return True + + def __get_db_list(self, sql_obj): + """ + 获取pgsql数据库列表 + """ + data = [] + ret = sql_obj.query('SELECT datname FROM pg_database;') + if type(ret) == list: + for x in ret: + data.append(x[0]) + return data + + def check_cloud_database_status(self, conn_config): + """ + @检测远程数据库是否连接 + @conn_config 远程数据库配置,包含host port pwd等信息 + """ + try: + + if not 'db_name' in conn_config: conn_config['db_name'] = None + sql_obj = panelPgsql().set_host(conn_config['db_host'], conn_config['db_port'], conn_config['db_name'], + conn_config['db_user'], conn_config['db_password']) + + data = sql_obj.query("SELECT datname FROM pg_database;") + if type(data) == str: + # return public.returnMsg(False,'Connecting to remote PGSQL fails. Perform the following operations to rectify the fault:
                                    1、The database port is correct and the firewall allows access
                                    2、Check whether the database account password is correct
                                    3、pg_hba.confWhether to add a client release record
                                    4、postgresql.conf Add listen_addresses to the correct server IP address.') + return public.return_message(-1, 0, 'Connecting to remote PGSQL fails. Perform the following operations to rectify the fault:
                                    1、The database port is correct and the firewall allows access
                                    2、Check whether the database account password is correct
                                    3、pg_hba.confWhether to add a client release record
                                    4、postgresql.conf Add listen_addresses to the correct server IP address.') + + if not conn_config['db_name']: + # return True + return public.return_message(0, 0, True) + for i in data: + if i[0] == conn_config['db_name']: + # return True + return public.return_message(0, 0, True) + # return public.returnMsg(False, 'The specified database does not exist!') + return public.return_message(-1, 0, 'The specified database does not exist!') + except Exception as ex: + + # return public.returnMsg(False, ex) + return public.return_message(-1, 0, ex) diff --git a/class_v2/databaseModelV2/redisModel.py b/class_v2/databaseModelV2/redisModel.py new file mode 100644 index 00000000..529e953b --- /dev/null +++ b/class_v2/databaseModelV2/redisModel.py @@ -0,0 +1,611 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +# sqlite模型 +#------------------------------ +import os,re,json,shutil,time +from databaseModelV2.base import databaseBase +import public +from public.validate import Param +try: + import redis +except: + public.ExecShell("btpip install redis") + import redis +try: + from BTPanel import session +except :pass + + +class panelRedisDB(): + + __DB_PASS = None + __DB_USER = None + __DB_PORT = 6379 + __DB_HOST = '127.0.0.1' + __DB_CONN = None + __DB_ERR = None + + __DB_CLOUD = None + def __init__(self): + self.__config = self.get_options(None) + + def redis_conn(self,db_idx = 0): + + if self.__DB_HOST in ['127.0.0.1','localhost']: + if not os.path.exists('/www/server/redis'): return False + + if not self.__DB_CLOUD: + self.__DB_PASS = self.__config['requirepass'] + self.__DB_PORT = int(self.__config['port']) + + try: + redis_pool = redis.ConnectionPool(host=self.__DB_HOST, port= self.__DB_PORT, password= self.__DB_PASS, db= db_idx) + self.__DB_CONN = redis.Redis(connection_pool= redis_pool) + return self.__DB_CONN + except : + self.__DB_ERR = public.get_error_info() + return False + + + def set_host(self,host,port,name,username,password,prefix = ''): + self.__DB_HOST = host + self.__DB_PORT = int(port) + self.__DB_NAME = name + if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME) + self.__DB_USER = str(username) + self._USER = str(username) + self.__DB_PASS = str(password) + self.__DB_PREFIX = prefix + self.__DB_CLOUD = 1 + return self + + + #获取配置项 + def get_options(self,get = None): + + result = {} + redis_conf = public.readFile("{}/redis/redis.conf".format(public.get_setup_path())) + if not redis_conf: return False + + keys = ["bind","port","timeout","maxclients","databases","requirepass","maxmemory"] + for k in keys: + v = "" + rep = "\n%s\\s+(.+)" % k + group = re.search(rep,redis_conf) + if not group: + if k == "maxmemory": + v = "0" + if k == "maxclients": + v = "10000" + if k == "requirepass": + v = "" + else: + if k == "maxmemory": + v = int(group.group(1)) / 1024 / 1024 + else: + v = group.group(1) + result[k] = v + return result + + + +class main(databaseBase): + + _db_max = 16 #最大redis数据库 + def __init__(self): + pass + + + def GetCloudServer(self,args): + ''' + @name 获取远程服务器列表 + @author hwliang<2021-01-10> + @return list + ''' + # # 校验参数 + # try: + # args.validate([ + # Param('type').Require().String('in', ['redis']), + # ], [ + # public.validate.trim_filter(), + # ]) + # except Exception as ex: + # public.print_log("error info: {}".format(ex)) + # return public.return_message(-1, 0, str(ex)) + # return self.GetBaseCloudServer(args) + return public.return_message(0, 0, self.GetBaseCloudServer(args)) + + + def AddCloudServer(self,args): + ''' + @添加远程数据库 + ''' + # {"db_host":"192.168.66.129","db_port":"6379","db_user":"root","db_password":"password1","db_ps":"192.168.66.129","type":"redis"} + # 校验参数 + try: + args.validate([ + + Param('db_host').Require().Ip(), + Param('db_port').Require().Number(">=", 1).Number("<=", 65535), + Param('db_user').Require().String().Xss(), + Param('db_password').Require().String().Xss(), + Param('db_ps').Require().String(), + Param('type').Require().String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + return self.AddBaseCloudServer(args) + # return public.return_message(0, 0, self.AddBaseCloudServer(args)) + + def RemoveCloudServer(self,args): + ''' + @删除远程数据库 + ''' + return self.RemoveBaseCloudServer(args) + # return public.return_message(0, 0, self.RemoveBaseCloudServer(args)) + + def ModifyCloudServer(self,args): + ''' + @修改远程数据库 + ''' + return self.ModifyBaseCloudServer(args) + + # return public.return_message(0, 0, self.ModifyBaseCloudServer(args)) + + def get_obj_by_sid(self,sid = 0,conn_config = None): + """ + @取mssql数据库对像 By sid + @sid 数据库分类,0:本地 + """ + if type(sid) == str: + try: + sid = int(sid) + except :sid = 0 + + if sid: + if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find() + db_obj = panelRedisDB() + + try: + db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password']) + except Exception as e: + raise public.PanelError(e) + else: + db_obj = panelRedisDB() + return db_obj + + + + def get_list(self,args): + """ + @获取数据库列表 + @sql_type = redis + """ + + # 校验参数 + try: + args.validate([ + Param('sid').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + result = [] + self.sid = args.get('sid/d',0) + for x in range(0,self._db_max): + + data = {} + data['id'] = x + data['name'] = 'DB{}'.format(x) + + + try: + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(x) + + data['keynum'] = redis_obj.dbsize() + if data['keynum'] > 0: + result.append(data) + except Exception as ex: + public.print_log("error info2: {}".format(ex)) + pass + + #result = sorted(result,key= lambda x:x['keynum'],reverse=True) + # return result + return public.return_message(0, 0, result) + + + def set_redis_val(self,args): + """ + @设置或修改指定值 + """ + # {"val":"bbbbbb12","endtime":"30","name":"aaaaa","db_idx":0,"sid":0} + # 校验参数 + try: + args.validate([ + + Param('db_idx').Require().Integer(), + Param('sid').Require().Integer(), + Param('name').Require().String().Xss(), # 键 + Param('val').Require().String().Xss(), # 值 + Param('endtime').Integer(), # 过期时间 + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self.sid = args.get('sid/d',0) + if not 'name' in args or not 'val' in args: + # return public.returnMsg(False,'Parameter passing error.') + return public.return_message(-1, 0, 'Parameter passing error') + + endtime = 0 + if 'endtime' in args : endtime = int(args.endtime) + + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(args.db_idx) + if endtime: + redis_obj.set(args.name, args.val, endtime) + else: + redis_obj.set(args.name, args.val) + public.set_module_logs('linux_redis','set_redis_val',1) + # return public.returnMsg(True,'Operation is successful.') + return public.return_message(0, 0, 'Operation is successful') + + def del_redis_val(self,args): + """ + @删除key值 + """ + # {"db_idx":0,"key":"qq","sid":0} + # 校验参数 + try: + args.validate([ + + Param('db_idx').Require().Integer(), + Param('sid').Require().Integer(), + Param('key').Require().String().Xss(), # 键 + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self.sid = args.get('sid/d',0) + if not 'key' in args: + # return public.returnMsg(False,'Parameter passing error.') + return public.return_message(-1, 0, 'Parameter passing error') + + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(args.db_idx) + redis_obj.delete(args.key) + + # return public.returnMsg(True,'Operation is successful.') + return public.return_message(0, 0, 'Operation is successful') + + + def clear_flushdb(self,args): + """ + 清空数据库 + @ids 清空数据库列表,不传则清空所有 + """ + # 校验参数 + try: + args.validate([ + + Param('ids').String(), # "ids":"[0,1]" + Param('sid').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self.sid = args.get('sid/d',0) + ids = json.loads(args.ids) + #ids = [] + if len(ids) == 0: + for x in range(0,self._db_max): + ids.append(x) + + for x in ids: + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(x) + redis_obj.flushdb() + + # return public.returnMsg(True,'Operation is successful.') + return public.return_message(0, 0, 'Operation is successful') + + def get_db_keylist(self,args): + """ + @获取指定数据库key集合 + """ + # 校验参数 + try: + args.validate([ + Param('db_type').Integer(), + Param('db_idx').Require().Integer(), + Param('limit').Integer(), + Param('p').Integer(), + Param('search').String(), + Param('tojs').String(), # 不知道 + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + search = '*' + if 'search' in args: search = "*" + args.search+"*" + db_idx = args.db_idx + self.sid = args.get('sid/d',0) + + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(db_idx) + try: + keylist = sorted(redis_obj.keys(search)) + except : + keylist = [] + + + info = {'p':1,'row':10,'count':len(keylist)} + + if hasattr(args,'limit'): info['row'] = int(args.limit) + if hasattr(args,'p'): info['p'] = int(args['p']) + + import page + #实例化分页类 + page = page.Page() + + info['uri'] = args + info['return_js'] = '' + if hasattr(args,'tojs'): + info['return_js'] = args.tojs + + slist = keylist[(info['p']-1) * info['row']:info['p'] * info['row']] + + rdata = {} + rdata['page'] = page.GetPage(info,'1,2,3,4,5,8') + rdata['where'] = '' + rdata['data'] = [] + + idx = 0 + for key in slist: + item = {} + try: + item['name'] = key.decode() + except: + item['name'] = str(key) + + item['endtime'] = redis_obj.ttl(key) + if item['endtime'] == -1: item['endtime'] = 0 + + item['type'] = redis_obj.type(key).decode() + + if item['type'] == 'string': + try: + item['val'] = redis_obj.get(key).decode() + except: + item['val'] = str(redis_obj.get(key)) + elif item['type'] == 'hash': + item['val'] = str(redis_obj.hgetall(key)) + elif item['type'] == 'list': + item['val'] = str(redis_obj.lrange(key, 0, -1)) + elif item['type'] == 'set': + item['val'] = str(redis_obj.smembers(key)) + elif item['type'] == 'zset': + item['val'] = str(redis_obj.zrange(key, 0, 1, withscores=True)) + else: + item['val'] = '' + try: + item['len'] = redis_obj.strlen(key) + except: + item['len'] = len(item['val']) + item['val'] = public.xsssec(item['val']) + item['name'] = public.xsssec(item['name']) + rdata['data'].append(item) + idx += 1 + # return rdata + return public.return_message(0, 0, rdata) + + + def ToBackup(self,args): + """ + @备份数据库 + """ + + # 校验参数 + try: + args.validate([ + Param('sid').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + self.sid = args.get('sid/d',0) + + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(0) + redis_obj.save() + + src_path = '{}/dump.rdb'.format(redis_obj.config_get()['dir']) + if not os.path.exists(src_path): + # return public.returnMsg(False,'BACKUP_ERROR') + return public.return_message(-1, 0, 'BACKUP_ERROR') + + backup_path = session['config']['backup_path'] + '/database/redis/' + if not os.path.exists(backup_path): os.makedirs(backup_path) + + fileName = backup_path + str(self.sid) + '_db_' + time.strftime('%Y%m%d_%H%M%S',time.localtime()) +'.rdb' + + shutil.copyfile(src_path,fileName) + if not os.path.exists(fileName): + # return public.returnMsg(False,'BACKUP_ERROR') + return public.return_message(-1, 0, 'BACKUP_ERROR') + + # return public.returnMsg(True, 'BACKUP_SUCCESS') + return public.return_message(0, 0, 'BACKUP_SUCCESS') + + except Exception as ex: + public.print_log("error info22: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + def DelBackup(self,args): + """ + @删除备份文件 + """ + # 校验参数 + try: + args.validate([ + Param('file').Require().SafePath(), + # Param('sid').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + file = args.file + + if os.path.exists(file): + os.remove(file) + + # return public.returnMsg(True, 'DEL_SUCCESS') + return public.return_message(0, 0, 'DEL_SUCCESS') + + def InputSql(self,get): + """ + @导入数据库 + """ + + # 校验参数 + try: + get.validate([ + Param('file').Require().SafePath(), + Param('sid').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + file = get.file + self.sid = get.get('sid/d',0) + + redis_obj = self.get_obj_by_sid(self.sid).redis_conn(0) + + rpath = redis_obj.config_get()['dir'] + dst_path = '{}/dump.rdb'.format(rpath) + public.ExecShell("/etc/init.d/redis stop") + if os.path.exists(dst_path): os.remove(dst_path) + shutil.copy2(file, dst_path) + public.ExecShell("chown redis.redis {dump} && chmod 644 {dump}".format(dump=dst_path)) + # self.restart_services() + public.ExecShell("/etc/init.d/redis start") + if os.path.exists(dst_path): + # return public.returnMsg(True, 'Restore Successful.') + return public.return_message(0, 0, 'Restore Successful.') + # return public.returnMsg(False, 'Restore failure.') + return public.return_message(-1, 0, 'Restore failure.') + + + def get_backup_list(self,get): + """ + @获取备份文件列表 + """ + # 校验参数 + try: + get.validate([ + Param('search').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + search = '' + if hasattr(get, 'search'): + search = get['search'].strip().lower() + + nlist = [] + cloud_list = {} + # try: + # 修改返回后接口适配 + listm = self.GetCloudServer({'type': 'redis'}) + + for x in listm['message']: + cloud_list['id-' + str(x['id'])] = x + + path = session['config']['backup_path'] + '/database/redis/' + if not os.path.exists(path): + os.makedirs(path) + # except Exception as ex: + # public.print_log("error 扥就: {}".format(ex)) + + for name in os.listdir(path): + if search: + if name.lower().find(search) == -1: continue; + + arrs = name.split('_') + + filepath = '{}/{}'.format(path,name).replace('//','/') + stat = os.stat(filepath) + + item = {} + item['name'] = name + item['filepath'] = filepath + item['size'] = stat.st_size + item['mtime'] = int(stat.st_mtime) + item['sid'] = arrs[0] + item['conn_config'] = cloud_list['id-' + str(arrs[0])] + + nlist.append(item) + if hasattr(get, 'sort'): + nlist = sorted(nlist, key=lambda data: data['mtime'], reverse=get["sort"] == "desc") + # return nlist + return public.return_message(0, 0, nlist) + + + + def restart_services(self): + """ + @重启服务 + """ + public.ExecShell('net stop redis') + public.ExecShell('net start redis') + return True + + + def check_cloud_database_status(self,conn_config): + """ + @检测远程数据库是否连接 + @conn_config 远程数据库配置,包含host port pwd等信息 + """ + try: + + sql_obj = panelRedisDB().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password']) + keynum = sql_obj.redis_conn(0).dbsize() + # return True + return public.return_message(0, 0, True) + except Exception as ex: + + # return public.returnMsg(False,ex) + return public.return_message(-1, 0, ex) diff --git a/class_v2/databaseModelV2/sqliteModel.py b/class_v2/databaseModelV2/sqliteModel.py new file mode 100644 index 00000000..5cbd3172 --- /dev/null +++ b/class_v2/databaseModelV2/sqliteModel.py @@ -0,0 +1,22 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +#------------------------------ +# sqlite模型 +#------------------------------ +import os,sys,re,json,shutil,psutil,time +from databaseModelV2.base import databaseBase +import public + + +class main(databaseBase): + + def get_list(self,args): + + return [] diff --git a/class_v2/databaseModelV2/sqlserverModel.py b/class_v2/databaseModelV2/sqlserverModel.py new file mode 100644 index 00000000..4a2603d2 --- /dev/null +++ b/class_v2/databaseModelV2/sqlserverModel.py @@ -0,0 +1,667 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +#------------------------------ +# sqlite模型 +#------------------------------ +import os,re,json,time +from databaseModelV2.base import databaseBase + + +# import public,panelMssql +import public +import panel_mssql_v2 as panelMssql + + +from public.validate import Param +try: + from BTPanel import session +except :pass + + +class main(databaseBase): + + def get_list(self,args): + """ + @获取数据库列表 + @sql_type = sqlserver + """ + # {"table":"databases","search":"","limit":"10","p":1,"order":"username desc"} + # 校验参数 + try: + args.validate([ + Param('table').Require().String().Xss(), + Param('search').String(), + Param('order').String().Xss(), + Param('limit').Integer(), + Param('p').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # return self.get_base_list(args, sql_type = 'sqlserver') + return public.return_message(0, 0, self.get_base_list(args, sql_type = 'sqlserver')) + + + def get_mssql_obj_by_sid(self,sid = 0,conn_config = None): + """ + @取mssql数据库对像 By sid + @sid 数据库分类,0:本地 + """ + if type(sid) == str: + try: + sid = int(sid) + except :sid = 0 + + if sid: + if not conn_config: conn_config = public.M('database_servers').where("id=?" ,sid).find() + db_obj = panelMssql.panelMssql() + + try: + db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password']) + except Exception as e: + raise public.PanelError(e) + else: + db_obj = panelMssql.panelMssql() + return db_obj + + def get_mssql_obj(self,db_name): + """ + @取mssql数据库对象 + @db_name 数据库名称 + """ + is_cloud_db = False + if db_name: + db_find = public.M('databases').where("name=?" ,db_name).find() + if db_find['sid']: + return self.get_mssql_obj_by_sid(db_find['sid']) + is_cloud_db = db_find['db_type'] in ['1',1] + + if is_cloud_db: + + db_obj = panelMssql.panelMssql() + conn_config = json.loads(db_find['conn_config']) + try: + db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password']) + except Exception as e: + raise public.PanelError(e) + else: + db_obj = panelMssql.panelMssql() + return db_obj + + def GetCloudServer(self,args): + ''' + @name 获取远程服务器列表 + @author hwliang<2021-01-10> + @return list + ''' + # {"type":"sqlserver"} + # 校验参数 + try: + args.validate([ + + Param('type').Require().String('in', ['sqlserver']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # return self.GetBaseCloudServer(args) + return public.return_message(0, 0, self.GetBaseCloudServer(args)) + + + def AddCloudServer(self,args): + ''' + @添加远程数据库 + ''' + + return self.AddBaseCloudServer(args) + # return public.return_message(0, 0, self.AddBaseCloudServer(args)) + + def RemoveCloudServer(self,args): + ''' + @删除远程数据库 + ''' + return self.RemoveBaseCloudServer(args) + # return public.return_message(0, 0, self.RemoveBaseCloudServer(args)) + + def ModifyCloudServer(self,args): + ''' + @修改远程数据库 + ''' + return self.ModifyBaseCloudServer(args) + # return public.return_message(0, 0, self.ModifyBaseCloudServer(args)) + + def AddDatabase(self,args): + """ + @添加数据库 + """ + # {"name":"test1","db_user":"test1","password":"nnMCp2ccJcBahJec","sid":"3","active":true,"ps":"test1","ssl":"REQUIRE SSL"} + # 校验参数 + try: + args.validate([ + Param('name').Require().String(), + Param('db_user').Require().String(), + Param('password').Require().String().Xss(), + Param('sid').Require().Integer(), + Param('active').Require().Bool(), + Param('ps').String(), + Param('ssl').String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + try: + res = self.add_base_database(args) + if not res['status']: + # return res + return public.return_message(-1, 0, res['msg']) + + data_name = res['data_name'] + username = res['username'] + password = res['data_pwd'] + + if re.match(r"^\d+",data_name): + # return public.returnMsg(False,'SQLServer databases cannot start with numbers!') + return public.return_message(-1, 0, 'SQLServer databases cannot start with numbers!') + + reg_count = 0 + regs = ['[a-z]','[A-Z]',r'\W','[0-9]'] + for x in regs: + if re.search(x,password): reg_count += 1 + + if len(password) < 8 or len(password) >128 or reg_count < 3 : + # return public.returnMsg(False,'SQLServer password complexity policy does not match, should be 8-128 characters, and contain any 3 of them in upper case, lower case, digits, special symbols!') + return public.return_message(-1, 0, 'SQLServer password complexity policy does not match, should be 8-128 ' + 'characters, and contain any 3 of them in upper case, lower case, ' + 'digits, special symbols!') + + try: + self.sid = int(args['sid']) + except : + self.sid = 0 + + dtype = 'SQLServer' + #添加SQLServer + mssql_obj = self.get_mssql_obj_by_sid(self.sid) + result = mssql_obj.execute("CREATE DATABASE %s" % data_name) + isError = self.IsSqlError(result) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + + mssql_obj.execute("DROP LOGIN %s" % username) + + #添加用户 + self.__CreateUsers(data_name,username,password,'127.0.0.1') + + if not hasattr(args,'ps'): args['ps'] = public.getMsg('INPUT_PS'); + addTime = time.strftime('%Y-%m-%d %X',time.localtime()) + + pid = 0 + if hasattr(args,'pid'): pid = args.pid + + if hasattr(args,'contact'): + site = public.M('sites').where("id=?",(args.contact,)).field('id,name').find() + if site: + pid = int(args.contact) + args['ps'] = site['name'] + + db_type = 0 + if self.sid: db_type = 2 + + public.set_module_logs('linux_sqlserver','AddDatabase',1) + #添加入SQLITE + public.M('databases').add('pid,sid,db_type,name,username,password,accept,ps,addtime,type',(pid,self.sid,db_type,data_name,username,password,'127.0.0.1',args['ps'],addTime,dtype)) + public.WriteLog("TYPE_DATABASE", 'DATABASE_ADD_SUCCESS',(data_name,)) + # return public.returnMsg(True,'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + def DeleteDatabase(self,args): + """ + @删除数据库 + """ + # {"id":128,"name":"失去了23"} + # 校验参数 + try: + args.validate([ + Param('name').Require().String(), + Param('id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = args['id'] + find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid,db_type').find(); + if not find: + # return public.returnMsg(False,'The specified database does not exist.') + return public.return_message(-1, 0, 'The specified database does not exist.') + + name = args['name'] + username = find['username'] + + mssql_obj = self.get_mssql_obj_by_sid(find['sid']) + mssql_obj.execute("ALTER DATABASE %s SET SINGLE_USER with ROLLBACK IMMEDIATE" % name) + result = mssql_obj.execute("DROP DATABASE %s" % name) + + if self.get_database_size_by_id(find['sid']): + isError = self.IsSqlError(result) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + + mssql_obj.execute("DROP LOGIN %s" % username) + + #删除SQLITE + public.M('databases').where("id=?",(id,)).delete() + public.WriteLog("TYPE_DATABASE", 'DATABASE_DEL_SUCCESS',(name,)) + # return public.returnMsg(True, 'DEL_SUCCESS') + return public.return_message(0, 0, 'DEL_SUCCESS') + + + + def ToBackup(self,args): + """ + @备份数据库 id 数据库id + """ + + + id = args['id'] + find = public.M('databases').where("id=?",(id,)).find() + if not find: + # return public.returnMsg(False,'Database does not exist!') + return public.return_message(-1, 0, 'Database does not exist!') + + self.CheckBackupPath(args) + + fileName = find['name'] + '_' + time.strftime('%Y%m%d_%H%M%S',time.localtime()) + '.bak' + backupName = session['config']['backup_path'] + '/database/sqlserver/' + fileName + + mssql_obj = self.get_mssql_obj_by_sid(find['sid']) + + if not int(find['sid']): + ret = mssql_obj.execute("backup database %s To disk='%s'" % (find['name'],backupName)) + isError=self.IsSqlError(ret) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + else: + #远程数据库 + # return public.returnMsg(False,'Operation failed. Remote database cannot be backed up.') + return public.return_message(-1, 0, 'Operation failed. Remote database cannot be backed up.') + + if not os.path.exists(backupName): + # return public.returnMsg(False,'BACKUP_ERROR') + return public.return_message(-1, 0, 'BACKUP_ERROR') + + public.M('backup').add('type,name,pid,filename,size,addtime',(1,fileName,id,backupName,0,time.strftime('%Y-%m-%d %X',time.localtime()))) + public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS",(find['name'],)) + + if os.path.getsize(backupName) < 2048: + # return public.returnMsg(True, 'The backup file size is smaller than 2Kb. Check the backup integrity.') + return public.return_message(0, 0, 'The backup file size is smaller than 2Kb. Check the backup integrity.') + else: + # return public.returnMsg(True, 'BACKUP_SUCCESS') + return public.return_message(0, 0, 'BACKUP_SUCCESS') + + def DelBackup(self,args): + """ + @删除备份文件 + """ + return self.delete_base_backup(args) + # return public.return_message(0, 0, self.delete_base_backup(args)) + + + #导入 + def InputSql(self,get): + # 校验参数 + try: + get.validate([ + Param('file').SafePath(), # 文件路径 + Param('name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + name = get.name + file = get.file + + find = public.M('databases').where("name=?",(name,)).find() + if not find: + # return public.returnMsg(False,'Database does not exist!') + return public.return_message(-1, 0, 'Database does not exist!') + + tmp = file.split('.') + exts = ['sql','zip','bak'] + ext = tmp[len(tmp) -1] + if ext not in exts: + # return public.returnMsg(False, 'DATABASE_INPUT_ERR_FORMAT') + return public.return_message(-1, 0, 'DATABASE_INPUT_ERR_FORMAT') + + backupPath = session['config']['backup_path'] + '/database' + + if ext == 'zip': + try: + fname = os.path.basename(file).replace('.zip','') + dst_path = backupPath + '/' +fname + if not os.path.exists(dst_path): os.makedirs(dst_path) + + public.unzip(file,dst_path) + for x in os.listdir(dst_path): + if x.find('bak') >= 0 or x.find('sql') >= 0: + file = dst_path + '/' + x + break + except : + # return public.returnMsg(False,'The import failed because the file is not a valid ZIP file.') + return public.return_message(-1, 0, 'The import failed because the file is not a valid ZIP file.') + + mssql_obj = self.get_mssql_obj_by_sid(find['sid']) + data = mssql_obj.query("use %s ;select filename from sysfiles" % find['name']) + + isError = self.IsSqlError(data) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + if type(data) == str: + # return public.returnMsg(False,data) + return public.return_message(-1, 0, data) + + mssql_obj.execute("ALTER DATABASE %s SET OFFLINE WITH ROLLBACK IMMEDIATE" % (find['name'])) + mssql_obj.execute("use master;restore database %s from disk='%s' with replace, MOVE N'%s' TO N'%s',MOVE N'%s_Log' TO N'%s' " % (find['name'],file,find['name'],data[0][0],find['name'],data[1][0])) + mssql_obj.execute("ALTER DATABASE %s SET ONLINE" % (find['name'])) + + public.WriteLog("TYPE_DATABASE", 'Description Succeeded in importing database [{}]'.format(name)) + # return public.returnMsg(True, 'DATABASE_INPUT_SUCCESS') + return public.return_message(0, 0, 'DATABASE_INPUT_SUCCESS') + + + #同步数据库到服务器 + def SyncToDatabases(self,get): + + # 校验? + + type = int(get['type']) + n = 0 + sql = public.M('databases') + if type == 0: + data = sql.field('id,name,username,password,accept,type,sid,db_type').where('type=?',('SQLServer',)).select() + + for value in data: + if value['db_type'] in ['1',1]: + continue # 跳过远程数据库 + result = self.ToDataBase(value) + if result == 1: n +=1 + else: + import json + data = json.loads(get.ids) + for value in data: + find = sql.where("id=?",(value,)).field('id,name,username,password,sid,db_type,accept,type').find() + result = self.ToDataBase(find) + if result == 1: n +=1 + + if n == 1: + # return public.returnMsg(True, 'Synchronization succeeded') + return public.return_message(0, 0, 'Synchronization succeeded') + + elif n == 0: + # return public.returnMsg(False,'Sync failed') + return public.return_message(-1, 0, 'Sync failed') + + # return public.returnMsg(True,'DATABASE_SYNC_SUCCESS',(str(n),)) + return public.return_message(0, 0, 'DATABASE_SYNC_SUCCESS'.format(str(n))) + + #添加到服务器 + def ToDataBase(self,find): + + if find['username'] == 'bt_default': + # return 0 + return public.return_message(0, 0, 0) + if len(find['password']) < 3 : + find['username'] = find['name'] + find['password'] = public.md5(str(time.time()) + find['name'])[0:10] + public.M('databases').where("id=?",(find['id'],)).save('password,username',(find['password'],find['username'])) + + self.sid = find['sid'] + mssql_obj = self.get_mssql_obj_by_sid(self.sid) + result = mssql_obj.execute("CREATE DATABASE %s" % find['name']) + isError = self.IsSqlError(result) + if isError != None and not 'already exists' in result: + # return -1 + return public.return_message(0, 0, -1) + + self.__CreateUsers(find['name'],find['username'],find['password'],'127.0.0.1') + + # return 1 + return public.return_message(0, 0, 1) + + + #从服务器获取数据库 + def SyncGetDatabases(self,get): + + n = 0 + s = 0 + db_type = 0 + self.sid = get.get('sid/d',0) + if self.sid: db_type = 2 + + mssql_obj = self.get_mssql_obj_by_sid(self.sid) + + data = mssql_obj.query('SELECT name FROM MASTER.DBO.SYSDATABASES ORDER BY name') + isError = self.IsSqlError(data) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + if type(data) == str: + # return public.returnMsg(False,data) + return public.return_message(-1, 0, data) + + sql = public.M('databases') + nameArr = ['information_schema','performance_schema','mysql','sys','master','model','msdb','tempdb','ReportServerTempDB','YueMiao','ReportServer'] + for item in data: + dbname = item[0] + if sql.where("name=?",(dbname,)).count(): continue + if not dbname in nameArr: + if sql.table('databases').add('name,username,password,accept,ps,addtime,type,sid,db_type',(dbname,dbname,'','',public.getMsg('INPUT_PS'),time.strftime('%Y-%m-%d %X',time.localtime()),'SQLServer',self.sid,db_type)): n +=1 + + # return public.returnMsg(True,'DATABASE_GET_SUCCESS',(str(n),)) + return public.return_message(0, 0, 'DATABASE_GET_SUCCESS'.format(n)) + + def ResDatabasePassword(self,args): + """ + @修改用户密码 + """ + # 校验参数 + try: + args.validate([ + Param('password').Require().String().Xss(), + Param('name').Require().String(), + Param('id').Require().Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + id = args['id'] + username = args['name'].strip() + newpassword = public.trim(args['password']) + + try: + # if not newpassword: + # return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.') + if len(re.search(r"^[\w@\.]+$", newpassword).groups()) > 0: + # return public.returnMsg(False, 'The database password cannot be empty or contain special characters') + return public.return_message(-1, 0, 'The database password cannot be empty or contain special characters') + except : + # return public.returnMsg(False, 'The database password cannot be empty or contain special characters') + return public.return_message(-1, 0, 'The database password cannot be empty or contain special characters') + + find = public.M('databases').where("id=?",(id,)).field('id,pid,name,username,password,type,accept,ps,addtime,sid').find() + if not find: + # return public.returnMsg(False, 'Modify the failure,The specified database does not exist.'); + return public.return_message(-1, 0, 'Modify the failure,The specified database does not exist.') + + mssql_obj = self.get_mssql_obj_by_sid(find['sid']) + mssql_obj.execute("EXEC sp_password NULL, '%s', '%s'" % (newpassword,username)) + + #修改SQLITE + public.M('databases').where("id=?",(id,)).setField('password',newpassword) + + public.WriteLog("TYPE_DATABASE",'DATABASE_PASS_SUCCESS',(find['name'],)) + # return public.returnMsg(True, 'DATABASE_PASS_SUCCESS',(find['name'],)) + return public.return_message(0, 0, 'DATABASE_PASS_SUCCESS'.format(find['name'])) + + + def get_root_pwd(self,args): + """ + @获取sa密码 + """ + mssql_obj = panelMssql.panelMssql() + ret = mssql_obj.get_sql_name() + if not ret : return public.returnMsg(False, 'The SQL Server is not installed or started. Install or start it first') + + sa_path = '{}/data/sa.pl'.format(public.get_panel_path()) + if os.path.exists(sa_path): + password = public.readFile(sa_path) + return public.returnMsg(True,password) + return public.returnMsg(True,'') + + + def set_root_pwd(self,args): + """ + @设置sa密码 + """ + password = public.trim(args['password']) + try: + if not password: + return public.returnMsg(False, 'The password of database [' + username + '] cannot be empty.') + if len(re.search(r"^[\w@\.]+$", password).groups()) > 0: + return public.returnMsg(False, 'saThe password cannot be empty or have special symbols') + except : + return public.returnMsg(False, 'saThe password cannot be empty or have special symbols') + + mssql_obj = panelMssql.panelMssql() + result = mssql_obj.execute("EXEC sp_password NULL, '%s', 'sa'" % password) + + isError = self.IsSqlError(result) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + + public.writeFile('data/sa.pl',password) + session['config']['mssql_sa'] = password + return public.returnMsg(True,'The password of sa is changed successfully.') + + + + def get_database_size_by_id(self,args): + """ + @获取数据库尺寸(批量删除验证) + @args json/int 数据库id + """ + total = 0 + db_id = args + if not isinstance(args,int): db_id = args['db_id'] + + try: + name = public.M('databases').where('id=?',db_id).getField('name') + mssql_obj = self.get_mssql_obj(name) + tables = mssql_obj.query("select name,size,type from sys.master_files where type=0 and name = '{}'".format(name)) + + total = tables[0][1] + if not total: total = 0 + except :pass + + return total + + def check_del_data(self,args): + """ + @删除数据库前置检测 + """ + # {"ids": "[128]"} + + # return self.check_base_del_data(args) + return public.return_message(0, 0, self.check_base_del_data(args)) + + #本地创建数据库 + def __CreateUsers(self,data_name,username,password,address): + """ + @创建数据库用户 + """ + mssql_obj = self.get_mssql_obj_by_sid(self.sid) + mssql_obj.execute("use %s create login %s with password ='%s' , default_database = %s" % (data_name,username,password,data_name)) + mssql_obj.execute("use %s create user %s for login %s with default_schema=dbo" % (data_name,username,username)) + mssql_obj.execute("use %s exec sp_addrolemember 'db_owner','%s'" % (data_name,data_name)) + mssql_obj.execute("ALTER DATABASE %s SET MULTI_USER" % data_name) + + + #检测备份目录并赋值权限(MSSQL需要Authenticated Users) + def CheckBackupPath(self,get): + backupFile = session['config']['backup_path'] + '/database/sqlserver' + if not os.path.exists(backupFile): + os.makedirs(backupFile) + get.filename = backupFile + get.user = 'Authenticated Users' + get.access = 2032127 + import files + files.files().SetFileAccess(get) + + def check_cloud_database_status(self,conn_config): + """ + @检测远程数据库是否连接 + @conn_config 远程数据库配置,包含host port pwd等信息 + """ + try: + + import panelMssql + if not 'db_name' in conn_config: conn_config['db_name'] = None + sql_obj = panelMssql.panelMssql().set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password']) + data = sql_obj.query("SELECT name FROM MASTER.DBO.SYSDATABASES ORDER BY name") + + isError = self.IsSqlError(data) + if isError != None: + return isError + # return public.return_message(-1, 0, isError) + if type(data) == str: + # return public.returnMsg(False,data) + return public.return_message(-1, 0, data) + + if not conn_config['db_name']: + # return True + return public.return_message(0, 0, True) + for i in data: + if i[0] == conn_config['db_name']: + # return True + return public.return_message(0, 0, True) + # return public.returnMsg(False,'The specified database does not exist!') + return public.return_message(-1, 0, 'The specified database does not exist!') + except Exception as ex: + + # return public.returnMsg(False,ex) + return public.return_message(-1, 0, ex) diff --git a/class_v2/database_v2.py b/class_v2/database_v2.py new file mode 100644 index 00000000..8d019258 --- /dev/null +++ b/class_v2/database_v2.py @@ -0,0 +1,2187 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------ +# 数据库管理类 +# ------------------------------ +import os +import time +import json +import re +import sys + +import public, db, panelMysql +import datatool_v2 as datatool +import db_mysql +from public.validate import Param + +class database(datatool.datatools): + _MYSQL_CNF = "/etc/my.cnf" + _DB_BACKUP_DIR = os.path.join(public.M("config").where("id=?", (1,)).getField("backup_path"), "database") + _MYSQL_BACKUP_DIR = os.path.join(_DB_BACKUP_DIR, "mysql") + _MYSQLDUMP_BIN = public.get_mysqldump_bin() + _MYSQL_BIN = public.get_mysql_bin() + + sqlite_connection = None + + def __init__(self): + if not os.path.exists(self._MYSQL_BACKUP_DIR): + os.makedirs(self._MYSQL_BACKUP_DIR) + + sid = 0 + + # mysql 在使用 + def AddCloudServer(self, get): + ''' + @name 添加远程服务器 + @author hwliang<2021-01-10> + @param db_host 服务器地址 + @param db_port 数据库端口 + @param db_user 用户名 + @param db_password 数据库密码 + @param db_ps 数据库备注 + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('db_host').Require().Ip(), + Param('db_port').Require().Number(">=", 1).Number("<=", 65535), + Param('db_user').Require().String().Xss(), + Param('db_password').Require().String().Xss(), + Param('db_ps').Require().String(), + Param('type').Require().String().Xss(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # if not hasattr(get, 'db_host'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_port'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_user'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_password'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_ps'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + get.db_name = None + res = self.CheckCloudDatabase(get) + if isinstance(res, dict): + # return res + info1 = res.get("msg", "Database unable to connect!") + return public.return_message(-1, 0, info1) + # return public.return_message(-1, 0, "Database unable to connect!") + if public.M('database_servers').where('db_host=? AND db_port=?', (get.db_host, get.db_port)).count(): + # return public.return_msg_gettext(False, 'The specified server already exists: [{}:{}]', (get.db_host, get.db_port)) + return public.return_message(-1, 0, 'The specified server already exists: [{}:{}]'.format(get.db_host, get.db_port)) + + get.db_port = int(get.db_port) + pdata = { + 'db_host': get.db_host, + 'db_port': get.db_port, + 'db_user': get.db_user, + 'db_password': get.db_password, + 'ps': public.xssencode2(get.db_ps.strip()), + 'addtime': int(time.time()) + } + + result = public.M("database_servers").insert(pdata) + + if isinstance(result, int): + public.write_log_gettext('Database manager', 'Add remote MySQL server [{}:{}]', (get.db_host, get.db_port)) + # return public.return_msg_gettext(True, 'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + # return public.return_msg_gettext(False, 'Add failed: {}', (result,)) + return public.return_message(-1, 0, 'Add failed: {}', (result,)) + + def GetCloudServer(self, get): + ''' + @name 获取远程服务器列表 + @author hwliang<2021-01-10> + @return list + ''' + data = public.M('database_servers').where("db_type=?", ("mysql",)).select() + bt_mysql_bin = '{}/mysql/bin/mysql'.format(public.get_setup_path()) + + if not isinstance(data, list): data = [] + if os.path.exists(bt_mysql_bin): + data.insert(0, {'id': 0, 'db_host': '127.0.0.1', 'db_port': 3306, 'db_user': 'root', 'db_password': '', + 'ps': 'LocalServer', 'addtime': 0}) + # return data + return public.return_message(0, 0, data) + + def RemoveCloudServer(self, get): + ''' + @name 删除远程服务器 + @author hwliang<2021-01-10> + @param id 远程服务器ID + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + id = int(get.id) + # if not id: + # return public.return_msg_gettext(False, 'Parameter ERROR!') + db_find = public.M('database_servers').where('id=?', (id,)).find() + if not db_find: + # return public.returnMsg(False, 'The specified server dose not exists!') + return public.return_message(-1, 0, 'The specified server dose not exists!') + public.M('databases').where('sid=?', id).delete() + result = public.M('database_servers').where('id=?', id).delete() + if isinstance(result, int): + public.WriteLog('Database manager', 'Delete the remote MySQL server [{}:{}]', + (db_find['db_host'], int(db_find['db_port']))) + # return public.return_msg_gettext(True, 'Successfully deleted!') + return public.return_message(0, 0, 'Successfully deleted!') + # return public.return_msg_gettext(False, 'Failed to delete: {}', (result,)) + return public.return_message(-1, 0, 'Failed to delete: {}', (result,)) + + def ModifyCloudServer(self, get): + ''' + @name 修改远程服务器 + @author hwliang<2021-01-10> + @param id 远程服务器ID + @param db_host 服务器地址 + @param db_port 数据库端口 + @param db_user 用户名 + @param db_password 数据库密码 + @param db_ps 数据库备注 + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('db_host').Require().Ip(), + Param('db_port').Require().Number(">=", 1).Number("<=", 65535), + Param('db_user').Require().String().Xss(), + Param('db_password').Require().String().Xss(), + Param('db_ps').Require().String(), + Param('id').Require().Integer().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + # if not hasattr(get, 'id'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_host'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_port'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_user'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_password'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_ps'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + + id = int(get.id) + get.db_port = int(get.db_port) + db_find = public.M('database_servers').where('id=?', (id,)).find() + if not db_find: + # return public.return_msg_gettext(False, 'The specified server dose not exists!') + return public.return_message(-1, 0, 'The specified server dose not exists!') + _modify = False + if db_find['db_host'] != get.db_host or db_find['db_port'] != get.db_port: + _modify = True + if public.M('database_servers').where('db_host=? AND db_port=?', (get.db_host, get.db_port)).count(): + # return public.return_msg_gettext(False, 'The specified server already exists: [{}:{}]',(get.db_host, get.db_port)) + return public.return_message(-1, 0, 'The specified server already exists: [{}:{}]'.format(get.db_host, get.db_port)) + + if db_find['db_user'] != get.db_user or db_find['db_password'] != get.db_password: + _modify = True + + if _modify: + res = self.CheckCloudDatabase(get) + if isinstance(res, dict): + # return res + info1 = res.get("msg", "Database unable to connect") + return public.return_message(-1, 0, info1) + + pdata = { + 'db_host': get.db_host, + 'db_port': get.db_port, + 'db_user': get.db_user, + 'db_password': get.db_password, + 'ps': public.xssencode2(get.db_ps.strip()) + } + + result = public.M("database_servers").where('id=?', (id,)).update(pdata) + if isinstance(result, int): + public.WriteLog('Database manager', 'Edit remote MySQL server [{}:{}]', (get.db_host, get.db_port)) + # return public.return_msg_gettext(True, 'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + # return public.return_msg_gettext(False, 'Fail to edit: {}', (result)) + return public.return_message(-1, 0, 'Fail to edit: {}'.format(result)) + + def AddCloudDatabase(self, get): + ''' + @name 添加远程数据库 + @author hwliang<2022-01-06> + @param db_host 服务器地址 + @param db_port 数据库端口 + @param db_user 用户名 + @param db_name 数据库名称 + @param db_password 数据库密码 + @param db_ps 数据库备注 + @return dict + ''' + + # 校验参数 + try: + get.validate([ + Param('db_host').Require().Ip(), + Param('db_port').Require().Number(">=", 1).Number("<=", 65535), + Param('db_user').Require().String(), + Param('db_password').Require().String(), + Param('db_name').Require().String(), + Param('db_ps').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # if not hasattr(get, 'db_host'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_port'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_user'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_name'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_password'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + # if not hasattr(get, 'db_ps'): + # return public.return_msg_gettext(False, 'Parameter ERROR!') + + # 检查数据库是否能连接 + res = self.CheckCloudDatabase(get) + if isinstance(res, dict): + # return res + info1 = res.get("msg", "Database unable to connect") + return public.return_message(-1, 0, info1) + + if public.M('databases').where('name=?', (get.db_name,)).count(): + # return public.return_msg_gettext(False, "A database with the same name already exists: [{}]",(get.db_name,)) + return public.return_msg_gettext(False, "A database with the same name already exists: [{}]".format(get.db_name)) + get.db_port = int(get.db_port) + conn_config = { + 'db_host': get.db_host, + 'db_port': get.db_port, + 'db_user': get.db_user, + 'db_password': get.db_password, + 'db_name': get.db_name + } + + pdata = { + 'name': get.db_name, + 'ps': get.db_ps, + 'conn_config': json.dumps(conn_config), + 'db_type': '1', + 'username': get.db_user, + 'password': get.db_password, + 'accept': '127.0.0.1', + 'addtime': time.strftime('%Y-%m-%d %X', time.localtime()), + 'pid': 0 + } + + result = public.M('databases').insert(pdata) + if isinstance(result, int): + public.write_log_gettext('Database manager', 'Add remote MySQL database [{}] successfully', (get.db_name,)) + # return public.return_msg_gettext(True, 'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + # return public.return_msg_gettext(False, 'Add failed: {}', (result,)) + return public.return_message(-1, 0, 'Add failed: {}', (result,)) + + def CheckCloudDatabase(self, conn_config): + ''' + @name 检查远程数据库信息是否正确 + @author hwliang<2022-01-06> + @param conn_config 连接信息 + db_host 服务器地址 + db_port 数据库端口 + db_user 用户名 + db_name 数据库名称 + db_password 数据库密码 + @return True / dict + ''' + try: + if not 'db_name' in conn_config: conn_config['db_name'] = None + mysql_obj = db_mysql.panelMysql() + mysql_obj.set_host(conn_config['db_host'], conn_config['db_port'], conn_config['db_name'], + conn_config['db_user'], conn_config['db_password']) + result = mysql_obj.query("show databases") + if isinstance(result, str): + if mysql_obj._ex: + return public.returnMsg(False, self.GetMySQLError(mysql_obj._ex)) + else: + return public.returnMsg(False, self.GetMySQLError(result)) + if not conn_config['db_name']: return True + for i in result: + if i[0] == conn_config['db_name']: + return True + return public.returnMsg(False, 'The specified database does not exist!') + except Exception as ex: + res = self.GetMySQLError(ex) + if not res: res = str(ex) + return public.returnMsg(False, res) + + def GetMySQLError(self, e): + if isinstance(e, str): + return e + res = '' + if e.args[0] == 1045: + res = public.gettext_msg("Wrong user name or password!") + if e.args[0] == 1049: + res = public.gettext_msg("Database does NOT exist!") + if e.args[0] == 1044: + res = public.gettext_msg("No access rights, or the database does not exist!") + if e.args[0] == 1062: + res = public.gettext_msg("Database exists!") + if e.args[0] == 1146: + res = public.gettext_msg('Database table does not exist!') + if e.args[0] == 2003: + res = public.gettext_msg('Fail to connect to the server!') + if res: + res = res + "
                                    " + str(e) + "
                                    " + else: + res = str(e) + return res + + # 检查mysql是否存在空用户密码 + def _check_empty_user_passwd(self): + mysql_obj = panelMysql.panelMysql() + mysql_obj.execute("delete from mysql.user where user='' and password=''") + + # 添加数据库 + def AddDatabase(self, get): + try: + # 校验参数 + try: + get.validate([ + # Param('name').Require().String('in', ['root', 'mysql', 'test', 'sys', 'panel_logs']), + # Param('db_user').Require().String('in', ['root', 'mysql', 'test', 'sys', 'panel_logs']), + Param('name').Require().String(), + Param('db_user').Require().String().Xss(), + Param('codeing').Require().String().Xss(), + Param('password').Require().String(), + # Param('dataAccess').Require().Ip(), + Param('sid').Require().Integer(), + Param('active').Require().Bool(), + Param('address').Require(), + Param('ps').Require().String(), + Param('ssl').String().Xss(), + Param('dtype').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # try: + self._check_empty_user_passwd() + ssl = "" + if hasattr(get, "ssl"): + ssl = get.ssl + if ssl == "REQUIRE SSL" and not self.check_mysql_ssl_status(get): + # return public.return_msg_gettext(False,'SSL is not enabled in the database, please open it in the Mysql manager first') + return public.return_message(-1, 0, "SSL is not enabled in the database, please open it in the Mysql manager first") + data_name = get['name'].strip().lower() + # if not data_name: + # return public.return_msg_gettext(False, 'The database name cannot be empty') + if self.CheckRecycleBin(data_name): + # return public.return_msg_gettext(False,'Database [{}] already at the recycle bin, please recover from the recycle bin!', + # (data_name,)) + return public.return_message(-1, 0, "Database [{}] already at the recycle bin, please recover from the recycle bin!".format(data_name)) + if len(data_name) > 64: + # return public.return_msg_gettext(False,'Database name cannot be more than 16 characters!') + return public.return_message(-1, 0, "Database name cannot be more than 16 characters") + reg = r"^[\w\.-]+$" + username = get.db_user.strip() + # if not username: + # return public.return_msg_gettext(False, 'The database user name cannot be empty') + if not re.match(reg, data_name): + # return public.return_msg_gettext(False,'Database name cannot contain special characters!') + return public.return_message(-1, 0, "Database name cannot contain special characters") + + if not re.match(reg, username): + # return public.return_msg_gettext(False, 'Database name is illegal!') + return public.return_message(-1, 0, "Database name is illegal") + if not hasattr(get, 'db_user'): + get.db_user = data_name + + checks = ['root', 'mysql', 'test', 'sys', 'panel_logs'] + if username in checks or len(username) < 1: + return public.return_msg_gettext(False, 'Database username is illegal!') + if data_name in checks or len(data_name) < 1: + return public.return_msg_gettext(False, 'Database name is illegal!') + data_pwd = get['password'] + if len(data_pwd) < 1: + data_pwd = public.md5(str(time.time()))[0:16] + + sql = public.M('databases') + if sql.where("name=?", (data_name)).count(): + # return public.return_msg_gettext(False, 'Database exists!') + return public.return_message(-1, 0, 'Database exists!') + if sql.where("username=?", (username)).count(): + # return public.return_msg_gettext(False,'The user name already exists. For security reasons, we do not allow one database user to manage multiple databases') + return public.return_message(-1, 0, 'The user name already exists. For security reasons, we do not allow ' + 'one database user to manage multiple databases') + address = get['address'].strip() + if address in ['', 'ip']: + # return public.return_msg_gettext(False,'If the access permission is [Specified IP], you need to enter the IP address!') + return public.return_message(-1, 0, 'If the access permission is [Specified IP], ' + 'you need to enter the IP address!') + + user = '是' + password = data_pwd + + codeing = get['codeing'] + + wheres = { + 'utf8': 'utf8_general_ci', + 'utf8mb4': 'utf8mb4_general_ci', + 'gbk': 'gbk_chinese_ci', + 'big5': 'big5_chinese_ci' + } + codeStr = wheres[codeing] + # 添加MYSQL + self.sid = get.get('sid/d', 0) + mysql_obj = public.get_mysql_obj_by_sid(self.sid) + if not mysql_obj: + # return public.returnMsg(False, 'Failed to connect to the specified database') + return public.return_message(-1, 0, 'Failed to connect to the specified database') + + # 从MySQL验证是否存在 + if self.database_exists_for_mysql(mysql_obj, data_name): + # return public.return_msg_gettext(False,'The specified database already exists in MySQL, please change the name!') + return public.return_message(-1, 0, 'The specified database already exists in MySQL,please change the name!') + + result = mysql_obj.execute( + "create database `" + data_name + "` DEFAULT CHARACTER SET " + codeing + " COLLATE " + codeStr) + + + isError = self.IsSqlError(result) + if isError != None: + # return isError + return public.return_message(-1, 0, isError) + mysql_obj.execute("drop user '" + username + "'@'localhost'") + for a in address.split(','): + mysql_obj.execute("drop user '" + username + "'@'" + a + "'") + self.__CreateUsers(data_name, username, password, address, ssl) + + if get['ps'] == '': get['ps'] = public.get_msg_gettext('Edit notes') + get['ps'] = public.xssencode2(get['ps']) + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + pid = 0 + if hasattr(get, 'pid'): pid = get.pid + # 添加入SQLITE + db_type = 0 + if self.sid: db_type = 2 + sql.add('pid,sid,db_type,name,username,password,accept,ps,addtime', + (pid, self.sid, db_type, data_name, username, password, address, get['ps'], addTime)) + public.write_log_gettext("Database manager", 'Successfully added database [{}]!', (data_name,)) + # return public.return_msg_gettext(True, 'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + # except Exception as ex: + # public.write_log_gettext("Database manager",'Failed to add database [{}]!, {}', (data_name,str(ex))) + # return public.return_msg_gettext(False,'Failed to add') + + except Exception as ex: + public.print_log("error info666: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 生成mysql证书 + def _create_mysql_ssl(self): + ip = public.readFile("/www/server/panel/data/iplist.txt") + openssl_command = """ +cd /www/server/data +openssl genrsa 2048 > ca-key.pem +openssl req -sha1 -new -x509 -nodes -subj "/C=CA/ST=CA/L=CA/O=CA/OU=CA/CN={ip}BT" -days 3650 -key ca-key.pem > ca.pem +openssl req -sha1 -newkey rsa:2048 -days 3650 -nodes -subj "/C=CA/ST=CA/L=CA/O=CA/OU=CA/CN={ip}" -keyout server-key.pem > server-req.pem +openssl rsa -in server-key.pem -out server-key.pem +openssl x509 -sha1 -req -in server-req.pem -days 3650 -CA ca.pem -CAkey ca-key.pem -set_serial 01 > server-cert.pem +openssl req -sha1 -newkey rsa:2048 -days 3650 -nodes -subj "/C=CA/ST=CA/L=CA/O=CA/OU=CA/CN={ip}" -keyout client-key.pem > client-req.pem +openssl rsa -in client-key.pem -out client-key.pem +openssl x509 -sha1 -req -in client-req.pem -days 3650 -CA ca.pem -CAkey ca-key.pem -set_serial 01 > client-cert.pem +zip -q ssl.zip client-cert.pem client-key.pem ca.pem +""".format(ip=ip) + public.ExecShell(openssl_command) + + # 写入mysqlssl到配置 + def write_ssl_to_mysql(self, get): + # ssl_conf = """ + # ssl-ca=/www/server/data/ca.pem + # ssl-cert=/www/server/data/server-cert.pem + # ssl-key=/www/server/data/server-key.pem + # """ + ssl_original_path = """ +ssl-ca=/www/server/mysql/mysql-test/std_data/cacert.pem +ssl-cert=/www/server/mysql/mysql-test/std_data//server-cert.pem +ssl-key=/www/server/mysql/mysql-test/std_data/server-key.pem +""" + conf_file = "/etc/my.cnf" + conf = public.readFile(conf_file) + if not conf: + return public.return_msg_gettext(False, 'Configuration file not exist') + if self.check_mysql_ssl_status(get): + reg = "ssl-ca=/www.*\n.*\n.*server-key.pem\n" + conf = re.sub(reg, "", conf) + if os.path.exists('/www/server/mysql/mysql-test/std_data/server-cert.pem'): + conf = re.sub(r'\[mysqld\]', '[mysqld]\nskip_ssl', conf) + public.writeFile(conf_file, conf) + return public.return_msg_gettext(True, 'Setup successfully!') + # create_ssl = None + # for i in ['5.5','5.6','10.1','10.2','10.3']: + # if i not in public.readFile('/www/server/mysql/version_check.pl'): + # continue + # create_ssl = True + # if create_ssl: + # self._create_mysql_ssl() + if "ssl-ca" not in conf: + conf = re.sub(r'\[mysqld\]', '[mysqld]' + ssl_original_path, conf) + conf = re.sub('skip_ssl\n', '', conf) + public.writeFile(conf_file, conf) + # public.ExecShell('chown mysql.mysql /www/server/data/*.pem') + return public.return_msg_gettext(True, 'Open successfully, take effect after manually restarting the database') + + # 检查mysqlssl状态 + def check_mysql_ssl_status(self, get): + mysql_obj = panelMysql.panelMysql() + result = mysql_obj.query("show variables like 'have_ssl';") + if not os.path.exists('/www/server/data/ssl.zip'): + if os.path.exists('/www/server/mysql/mysql-test/std_data/client-cert.pem'): + public.ExecShell( + "cd /www/server/mysql/mysql-test/std_data/ && zip -q /www/server/data/ssl.zip client-cert.pem client-key.pem cacert.pem") + try: + if result and result[0][1] == "YES": + return True + return False + except: + return False + + # 判断数据库是否存在—从MySQL + def database_exists_for_mysql(self, mysql_obj, dataName): + databases_tmp = self.map_to_list(mysql_obj.query('show databases')) + if not isinstance(databases_tmp, list): + return True + + for i in databases_tmp: + if i[0] == dataName: + return True + return False + + # 创建用户 + def __CreateUsers(self, dbname, username, password, address, ssl=None): + mysql_obj = public.get_mysql_obj_by_sid(self.sid) + if not mysql_obj: return public.returnMsg(False, 'Failed to connect to the specified database') + mysql_obj.execute("CREATE USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, password)) + result = mysql_obj.execute("grant all privileges on `%s`.* to `%s`@`localhost`" % (dbname, username)) + if str(result).find('1044') != -1: + mysql_obj.execute( + "grant SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,INDEX,ALTER,CREATE TEMPORARY TABLES,LOCK TABLES,EXECUTE,CREATE VIEW,SHOW VIEW,EVENT,TRIGGER on `%s`.* to `%s`@`localhost`" % ( + dbname, username)) + if not ssl: + mysql_obj.execute("update mysql.user set ssl_type='' where user='%s' and host='localhost'" % (username)) + for a in address.split(','): + mysql_obj.execute("CREATE USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, a, password)) + result = mysql_obj.execute("grant all privileges on `%s`.* to `%s`@`%s`" % (dbname, username, a)) + if str(result).find('1044') != -1: + mysql_obj.execute( + "grant SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,INDEX,ALTER,CREATE TEMPORARY TABLES,LOCK TABLES,EXECUTE,CREATE VIEW,SHOW VIEW,EVENT,TRIGGER on `%s`.* to `%s`@`%s` %s" % ( + dbname, username, a, ssl)) + mysql_obj.execute("flush privileges") + + # 检查是否在回收站 + def CheckRecycleBin(self, name): + try: + u_name = self.db_name_to_unicode(name) + for n in os.listdir('/www/.Recycle_bin'): + if n.find('BTDB_' + name + '_t_') != -1: return True + if n.find('BTDB_' + u_name + '_t_') != -1: return True + return False + except: + return False + + # 检测数据库执行错误 + def IsSqlError(self, mysqlMsg): + mysqlMsg = str(mysqlMsg) + if "MySQLdb" in mysqlMsg: + return 'MySQLdb component is missing!
                                    Please enter SSH and run the command: pip install mysql-python' + # return public.return_msg_gettext(False,'MySQLdb component is missing!
                                    Please enter SSH and run the command: pip install mysql-python') + if "2002," in mysqlMsg or '2003,' in mysqlMsg: + return 'ERROR to connect database, pls check database status!' + # return public.return_msg_gettext(False,'ERROR to connect database, pls check database status!') + if "using password:" in mysqlMsg: + return 'Mysql root or user password is incorrect, please try to reset!' + # return public.return_msg_gettext(False,'Mysql root or user password is incorrect, please try to reset!') + if "Connection refused" in mysqlMsg: + return 'ERROR to connect database, pls check database status!' + # return public.return_msg_gettext(False,'ERROR to connect database, pls check database status!') + if "1133" in mysqlMsg: + return 'Database user does NOT exist!' + # return public.return_msg_gettext(False, 'Database user does NOT exist!') + if "3679" in mysqlMsg: + return'Slave database deletion failed, data directory does not exist!' + # return public.returnMsg(False,'Slave database deletion failed, data directory does not exist!') + if "libmysqlclient" in mysqlMsg: + self.rep_lnk() + public.ExecShell("pip uninstall mysql-python -y") + public.ExecShell("pip install pymysql") + public.writeFile('data/restart.pl', 'True') + return 'Execution failed, attempted auto repair, please try again later!' + # return public.return_msg_gettext(False, 'Execution failed, attempted auto repair, please try again later!') + return None + + def rep_lnk(self): + shell_cmd = ''' +Setup_Path=/www/server/mysql +#删除软链 +DelLink() +{ + rm -f /usr/bin/mysql* + rm -f /usr/lib/libmysql* + rm -f /usr/lib64/libmysql* + rm -f /usr/bin/myisamchk + rm -f /usr/bin/mysqldump + rm -f /usr/bin/mysql + rm -f /usr/bin/mysqld_safe + rm -f /usr/bin/mysql_config +} +#设置软件链 +SetLink() +{ + ln -sf ${Setup_Path}/bin/mysql /usr/bin/mysql + ln -sf ${Setup_Path}/bin/mysqldump /usr/bin/mysqldump + ln -sf ${Setup_Path}/bin/myisamchk /usr/bin/myisamchk + ln -sf ${Setup_Path}/bin/mysqld_safe /usr/bin/mysqld_safe + ln -sf ${Setup_Path}/bin/mysqlcheck /usr/bin/mysqlcheck + ln -sf ${Setup_Path}/bin/mysql_config /usr/bin/mysql_config + + rm -f /usr/lib/libmysqlclient.so.16 + rm -f /usr/lib64/libmysqlclient.so.16 + rm -f /usr/lib/libmysqlclient.so.18 + rm -f /usr/lib64/libmysqlclient.so.18 + rm -f /usr/lib/libmysqlclient.so.20 + rm -f /usr/lib64/libmysqlclient.so.20 + rm -f /usr/lib/libmysqlclient.so.21 + rm -f /usr/lib64/libmysqlclient.so.21 + + if [ -f "${Setup_Path}/lib/libmysqlclient.so.18" ];then + ln -sf ${Setup_Path}/lib/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.20 + elif [ -f "${Setup_Path}/lib/mysql/libmysqlclient.so.18" ];then + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.18 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.18 /usr/lib64/libmysqlclient.so.20 + elif [ -f "${Setup_Path}/lib/libmysqlclient.so.16" ];then + ln -sf ${Setup_Path}/lib/libmysqlclient.so.16 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.16 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.16 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.16 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.16 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.16 /usr/lib64/libmysqlclient.so.20 + elif [ -f "${Setup_Path}/lib/mysql/libmysqlclient.so.16" ];then + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.16 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.16 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.16 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.16 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.16 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.16 /usr/lib64/libmysqlclient.so.20 + elif [ -f "${Setup_Path}/lib/libmysqlclient_r.so.16" ];then + ln -sf ${Setup_Path}/lib/libmysqlclient_r.so.16 /usr/lib/libmysqlclient_r.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient_r.so.16 /usr/lib64/libmysqlclient_r.so.16 + elif [ -f "${Setup_Path}/lib/mysql/libmysqlclient_r.so.16" ];then + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient_r.so.16 /usr/lib/libmysqlclient_r.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient_r.so.16 /usr/lib64/libmysqlclient_r.so.16 + elif [ -f "${Setup_Path}/lib/libmysqlclient.so.20" ];then + ln -sf ${Setup_Path}/lib/libmysqlclient.so.20 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.20 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.20 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.20 + elif [ -f "${Setup_Path}/lib/libmysqlclient.so.21" ];then + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib64/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib/libmysqlclient.so.21 + ln -sf ${Setup_Path}/lib/libmysqlclient.so.21 /usr/lib64/libmysqlclient.so.21 + elif [ -f "${Setup_Path}/lib/libmariadb.so.3" ]; then + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib64/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib/libmysqlclient.so.21 + ln -sf ${Setup_Path}/lib/libmariadb.so.3 /usr/lib64/libmysqlclient.so.21 + elif [ -f "${Setup_Path}/lib/mysql/libmysqlclient.so.20" ];then + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.20 /usr/lib/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.16 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.20 /usr/lib/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.18 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.20 /usr/lib/libmysqlclient.so.20 + ln -sf ${Setup_Path}/lib/mysql/libmysqlclient.so.20 /usr/lib64/libmysqlclient.so.20 + fi +} +DelLink +SetLink +''' + return public.ExecShell(shell_cmd) + + # 删除数据库 + def DeleteDatabase(self, get): + + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + Param('name').Require().String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # try: + id = get['id'] + name = get['name'] + find = public.M('databases').where("id=?", (id,)).field( + 'id,sid,pid,name,username,password,accept,ps,addtime,db_type').find() + if not find: + # return public.return_msg_gettext(False, 'Database [{}] does not exist!'.format(name)) + return public.return_message(-1, 0, 'Database [{}] does not exist!'.format(name)) + self.sid = find['sid'] + if find['db_type'] in ['0', 0] or self.sid: # 删除本地数据库 + if os.path.exists('data/recycle_bin_db.pl') and not self.sid: + res = self.DeleteToRecycleBin(name) + # return self.DeleteToRecycleBin(name) + info2 = res.get("msg", "Database moved to recycle bin") + return public.return_message(0, 0, info2) + accept = find['accept'] + username = find['username'] + # 删除MYSQL + mysql_obj = public.get_mysql_obj_by_sid(self.sid) + if not mysql_obj: + # return public.return_msg_gettext(False, 'Database [{}] connection failed'.format(name)) + return public.return_message(-1, 0, 'Database [{}] connection failed'.format(name)) + result = mysql_obj.execute("drop database `" + name + "`") + isError = self.IsSqlError(result) + if isError != None: + # return isError + return public.return_message(-1, 0, isError) + users = mysql_obj.query("select Host from mysql.user where User='" + username + "' AND Host!='localhost'") + mysql_obj.execute("drop user '" + username + "'@'localhost'") + for us in users: + mysql_obj.execute("drop user '" + username + "'@'" + us[0] + "'") + mysql_obj.execute("flush privileges") + # 删除SQLITE + public.M('databases').where("id=?", (id,)).delete() + public.write_log_gettext("Database manager", 'Successfully deleted database [{}]!', (name,)) + # return public.return_msg_gettext(True, 'Successfully deleted') + return public.return_message(0, 0, 'Successfully deleted') + # except Exception as ex: + # public.write_log_gettext("Database manager",'Failed to delete database [{}]!, {}',(get.name , str(ex))) + # return public.return_msg_gettext(False,'Failed to delete') + + def db_name_to_unicode(self, name): + ''' + @name 中文数据库名转换为Unicode编码 + @author hwliang<2021-12-20> + @param name 数据库名 + @return name Unicode编码的数据库名 + ''' + name = name.replace('.', '@002e') + name = name.replace('-', '@002d') + return name.encode("unicode_escape").replace(b"\\u", b"@").decode() + + # 删除数据库到回收站 + def DeleteToRecycleBin(self, name): + import json + data = public.M('databases').where("name=?", (name,)).field( + 'id,pid,name,username,password,accept,ps,addtime').find() + username = data['username'] + panelMysql.panelMysql().execute("drop user '" + username + "'@'localhost'") + users = panelMysql.panelMysql().query( + "select Host from mysql.user where User='" + username + "' AND Host!='localhost'") + if isinstance(users, str): + return public.return_msg_gettext(False, 'Delete failed, failed to connect to database!') + try: + for us in users: + panelMysql.panelMysql().execute("drop user '" + username + "'@'" + us[0] + "'") + except Exception: + pass + panelMysql.panelMysql().execute("flush privileges") + rPath = '/www/.Recycle_bin/' + data['rmtime'] = int(time.time()) + u_name = self.db_name_to_unicode(name) + rm_path = '{}/BTDB_{}_t_{}'.format(rPath, u_name, data['rmtime']) + if os.path.exists(rm_path): rm_path += '.1' + rm_config_file = '{}/config.json'.format(rm_path) + datadir = public.get_datadir() + + db_path = '{}/{}'.format(datadir, u_name) + if not os.path.exists(db_path): + return public.return_msg_gettext(False, 'The database data does not exist!') + + public.ExecShell("mv -f {} {}".format(db_path, rm_path)) + if not os.path.exists(rm_path): + return public.return_msg_gettext(False, 'Failed to move database data to the recycle bin!') + public.writeFile(rm_config_file, json.dumps(data)) + # public.writeFile(rPath + 'BTDB_' + name +'_t_' + str(time.time()),json.dumps(data)) + public.M('databases').where("name=?", (name,)).delete() + public.write_log_gettext("Database manager", 'Successfully deleted database [{}]!', (name,)) + return public.return_msg_gettext(True, 'Database moved to recycle bin!') + + # 永久删除数据库 + def DeleteTo(self, filename): + import json + if os.path.isfile(filename): + data = json.loads(public.readFile(filename)) + if public.M('databases').where("name=?", (data['name'],)).count(): + os.remove(filename) + return public.return_msg_gettext(True, 'Successfully deleted') + else: + if os.path.exists(filename): + data = json.loads(public.readFile(filename + '/config.json')) + else: + return public.returnMsg(False, 'Recycle Bin does not exist for this database!') + + db_obj = panelMysql.panelMysql() + if self.database_exists_for_mysql(db_obj, data['name']): + u_name = self.db_name_to_unicode(data['name']) + datadir = public.get_datadir() + db_path = '{}/{}'.format(datadir, u_name) + if not os.path.exists(db_path): + os.makedirs(db_path) + public.ExecShell("chown mysql:mysql {}".format(db_path)) + result = db_obj.execute("drop database `" + data['name'] + "`") + isError = self.IsSqlError(result) + if isError != None: return isError + db_obj.execute("drop user '" + data['username'] + "'@'localhost'") + users = db_obj.query( + "select Host from mysql.user where User='" + data['username'] + "' AND Host!='localhost'") + for us in users: + db_obj.execute("drop user '" + data['username'] + "'@'" + us[0] + "'") + db_obj.execute("flush privileges") + + if os.path.isfile(filename): + os.remove(filename) + else: + import shutil + shutil.rmtree(filename) + + try: + public.write_log_gettext("Database manager", 'Successfully deleted database [{}]!', (data['name'],)) + except: + pass + return public.return_msg_gettext(True, 'Successfully deleted') + + # 恢复数据库 + def RecycleDB(self, filename): + import json + _isdir = False + if os.path.isfile(filename): + data = json.loads(public.readFile(filename)) + else: + re_config_file = filename + '/config.json' + data = json.loads(public.readFile(re_config_file)) + u_name = self.db_name_to_unicode(data['name']) + db_path = "{}/{}".format(public.get_datadir(), u_name) + if os.path.exists(db_path): + return public.return_msg_gettext(False, + 'There is a database with the same name in the current database. To ensure data security, stop recovery!') + _isdir = True + + if public.M('databases').where("name=?", (data['name'],)).count(): + if not _isdir: os.remove(filename) + return public.return_msg_gettext(True, 'Database recovered!') + + if not _isdir: + os.remove(filename) + else: + public.ExecShell('mv -f {} {}'.format(filename, db_path)) + if not os.path.exists(db_path): + return public.return_msg_gettext(False, 'Data recovery failed!') + db_config_file = "{}/config.json".format(db_path) + if os.path.exists(db_config_file): os.remove(db_config_file) + + # 设置文件权限 + public.ExecShell("chown -R mysql:mysql {}".format(db_path)) + public.ExecShell("chmod -R 660 {}".format(db_path)) + public.ExecShell("chmod 700 {}".format(db_path)) + + self.__CreateUsers(data['name'], data['username'], data['password'], data['accept']) + public.M('databases').add('id,pid,name,username,password,accept,ps,addtime', ( + data['id'], data['pid'], data['name'], data['username'], data['password'], data['accept'], data['ps'], + data['addtime'])) + return public.return_msg_gettext(True, 'Database recovered!') + + # 设置ROOT密码 + def SetupPassword(self, get): + from BTPanel import session + + # 校验参数 + try: + get.validate([ + Param('password').Require().String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + password = get['password'].strip() + try: + # if not password: return public.return_msg_gettext(False, 'Root password cannot be empty') + # rep = r"^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$" + # if not re.match(rep, password): return public.return_msg_gettext(False, + # 'Database password cannot contain special characters!') + self.sid = get.get('sid/d', 0) + # 修改MYSQL + mysql_obj = public.get_mysql_obj_by_sid(self.sid) + if not mysql_obj: + # return public.returnMsg(False, 'Failed to connect to the specified database') + return public.return_message(-1, 0, "Failed to connect to the specified database") + + result = mysql_obj.query("show databases") + isError = self.IsSqlError(result) + is_modify = True + if isError != None and not self.sid: + # 尝试使用新密码 + public.M('config').where("id=?", (1,)).setField('mysql_root', password) + result = mysql_obj.query("show databases") + isError = self.IsSqlError(result) + if isError != None: + public.ExecShell( + "cd /www/server/panel && " + public.get_python_bin() + " tools.py root \"" + password + "\"") + is_modify = False + + if is_modify: + admin_user = 'root' + m_version = public.readFile(public.GetConfigValue('setup_path') + '/mysql/version.pl') + if self.sid: + admin_user = mysql_obj._USER + m_version = mysql_obj.query('select version();')[0][0] + + if m_version.find('5.7') == 0 or m_version.find('8.0') == 0: + accept = self.map_to_list( + mysql_obj.query("select Host from mysql.user where User='{}'".format(admin_user))) + for my_host in accept: + mysql_obj.execute( + "UPDATE mysql.user SET authentication_string='' WHERE User='{}' and Host='{}'".format( + admin_user, my_host[0])) + mysql_obj.execute( + "ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % (admin_user, my_host[0], password)) + elif m_version.find('10.5.') != -1 or m_version.find('10.4.') != -1: + accept = self.map_to_list( + mysql_obj.query("select Host from mysql.user where User='{}'".format(admin_user))) + for my_host in accept: + mysql_obj.execute( + "ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % (admin_user, my_host[0], password)) + else: + result = mysql_obj.execute( + "update mysql.user set Password=password('" + password + "') where User='{}'".format( + admin_user)) + mysql_obj.execute("flush privileges") + + msg = public.get_msg_gettext('Successfully modified root password!') + # 修改SQLITE + if self.sid: + public.M('database_servers').where('id=?', self.sid).setField('db_password', password) + public.write_log_gettext("Database manager", "Change the password of the remote MySQL server") + else: + public.M('config').where("id=?", (1,)).setField('mysql_root', password) + public.write_log_gettext("Database manager", 'Successfully modified root password!') + session['config']['mysql_root'] = password + # return public.return_msg_gettext(True, msg) + return public.return_message(0, 0, msg) + except Exception as ex: + # return public.return_msg_gettext(False, 'Failed to modify ' + str(ex)) + return public.return_message(-1, 0, 'Failed to modify : {}'.format(ex)) + + # 修改用户密码 + def ResDatabasePassword(self, get): + from BTPanel import session + + # 校验参数 + try: + get.validate([ + Param('name').Require().String(), + Param('id').Require().Integer(), + Param('password').Require().String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # try: + newpassword = get['password'] + username = get['name'] + id = get['id'] + if not newpassword: + # return public.return_msg_gettext(False, 'Database [{}] password cannot be empty',(username,)) + return public.return_message(-1, 0, 'Database [{}] password cannot be empty'.format(username)) + db_find = public.M('databases').where('id=?', (id,)).find() + name = db_find['name'] + + rep = r"^[\w@\.\?\-\_\>\<\~\!\#\$\%\^\&\*\(\)]+$" + if not re.match(rep, newpassword): + # return public.return_msg_gettext(False, 'Database password cannot contain special characters!') + return public.return_message(-1, 0, 'Database password cannot contain special characters!') + # 修改MYSQL + self.sid = db_find['sid'] + if self.sid and username == 'root': + # return public.returnMsg(False, 'Cannot change the root password of the remote database') + return public.return_message(-1, 0, 'Cannot change the root password of the remote database') + mysql_obj = public.get_mysql_obj_by_sid(self.sid) + if not mysql_obj: + # return public.returnMsg(False, 'Failed to connect to the specified database') + return public.return_message(-1, 0, 'Failed to connect to the specified database') + m_version = public.readFile(public.GetConfigValue('setup_path') + '/mysql/version.pl') + if self.sid: + m_version = mysql_obj.query('select version();')[0][0] + + if m_version.find('5.7') == 0 or m_version.find('8.0') == 0: + accept = self.map_to_list( + mysql_obj.query("select Host from mysql.user where User='" + name + "' AND Host!='localhost'")) + mysql_obj.execute("update mysql.user set authentication_string='' where User='" + username + "'") + result = mysql_obj.execute("ALTER USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, newpassword)) + for my_host in accept: + mysql_obj.execute("ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, my_host[0], newpassword)) + elif m_version.find('10.5.') != -1 or m_version.find('10.4.') != -1: + accept = self.map_to_list( + mysql_obj.query("select Host from mysql.user where User='" + name + "' AND Host!='localhost'")) + result = mysql_obj.execute("ALTER USER `%s`@`localhost` IDENTIFIED BY '%s'" % (username, newpassword)) + for my_host in accept: + mysql_obj.execute("ALTER USER `%s`@`%s` IDENTIFIED BY '%s'" % (username, my_host[0], newpassword)) + else: + result = mysql_obj.execute( + "update mysql.user set Password=password('" + newpassword + "') where User='" + username + "'") + + isError = self.IsSqlError(result) + if isError != None: + # return isError + return public.return_message(-1, 0, isError) + + mysql_obj.execute("flush privileges") + # if result==False: return public.return_msg_gettext(False,'Failed to modify, database user does not exist!') + # 修改SQLITE + if int(id) > 0: + public.M('databases').where("id=?", (id,)).setField('password', newpassword) + else: + public.M('config').where("id=?", (id,)).setField('mysql_root', newpassword) + session['config']['mysql_root'] = newpassword + + public.write_log_gettext("Database manager", 'Successfully modifyied password for database [{}]!', (name,)) + # return public.return_msg_gettext(True, 'Successfully modifyied password for database [{}]!', (name,)) + return public.return_message(0, 0, 'Successfully modifyied password for database [{}]!'.format(name)) + # except Exception as ex: + # import traceback + # public.write_log_gettext("Database manager", 'Failed to modify password for database [{}]!',(username,traceback.format_exc(limit=True).replace('\n','
                                    '))) + # return public.return_msg_gettext(False,'Failed to modify password for database [{}]!',(name,)) + + # 备份 + def ToBackup(self, get): + from BTPanel import session + + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # try: + import shlex + id = get['id'] + db_find = public.M('databases').where("id=?", (id,)).find() + name = db_find['name'] + fileName = name + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.sql.gz' + backupName = session['config']['backup_path'] + '/database/' + fileName + mysqldump_bin = public.get_mysqldump_bin() + if db_find['db_type'] in ['0', 0]: + # 本地数据库 + result = panelMysql.panelMysql().execute("show databases") + isError = self.IsSqlError(result) + if isError: + # return isError + return public.return_message(-1, 0, isError) + + root = public.M('config').where('id=?', (1,)).getField('mysql_root') + if not os.path.exists(session['config']['backup_path'] + '/database'): public.ExecShell( + 'mkdir -p ' + session['config']['backup_path'] + '/database') + if not self.mypass(True, root): + # return public.return_msg_gettext(False,'Database configuration file failed to get checked, please + # check if MySQL configuration file exists [/etc/my.cnf]') + return public.return_message(-1, 0, "Database configuration file failed to get checked, please check " + "if MySQL configuration file exists [/etc/my.cnf]") + try: + password = public.M('config').where('id=?', (1,)).getField('mysql_root') + if not password: + # return public.returnMsg(False, 'Database password cannot be empty') + return public.return_message(-1, 0, "Database password cannot be empty") + password = shlex.quote(str(password)) + os.environ["MYSQL_PWD"] = password + public.ExecShell( + mysqldump_bin + " -R -E --triggers=false --default-character-set=" + public.get_database_character( + name) + " --force --opt \"" + name + "\" -u root -p" + password + " | gzip > " + backupName) + except Exception as e: + raise + finally: + os.environ["MYSQL_PWD"] = "" + self.mypass(False, root) + elif db_find['db_type'] in ['1', 1]: + # 远程数据库 + try: + conn_config = json.loads(db_find['conn_config']) + res = self.CheckCloudDatabase(conn_config) + if isinstance(res, dict): + # return res + return public.return_message(0, 0, res) + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = password + public.ExecShell(mysqldump_bin + " -h " + conn_config['db_host'] + " -P " + str(int(conn_config[ + 'db_port'])) + " -R -E --triggers=false --default-character-set=" + public.get_database_character( + name) + " --force --opt \"" + str(db_find['name']) + "\" -u " + str( + conn_config['db_user']) + " -p" + password + " | gzip > " + backupName) + except Exception as e: + raise + finally: + os.environ["MYSQL_PWD"] = "" + elif db_find['db_type'] in ['2', 2]: + try: + conn_config = public.M('database_servers').where('id=?', db_find['sid']).find() + res = self.CheckCloudDatabase(conn_config) + if isinstance(res, dict): + # return res + return public.return_message(0, 0, res) + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = password + public.ExecShell(mysqldump_bin + " -h " + conn_config['db_host'] + " -P " + str(int(conn_config[ + 'db_port'])) + " -R -E --triggers=false --default-character-set=" + public.get_database_character( + name) + " --force --opt \"" + str(db_find['name']) + "\" -u " + str( + conn_config['db_user']) + " -p" + str(conn_config['db_password']) + " | gzip > " + backupName) + except Exception as e: + raise + finally: + os.environ["MYSQL_PWD"] = "" + else: + # return public.return_msg_gettext(False, 'Unsupported database type') + return public.return_message(-1, 0, "Unsupported database type") + + if not os.path.exists(backupName): + # return public.return_msg_gettext(False, 'Backup error!') + return public.return_message(-1, 0, "Backup error") + sql = public.M('backup') + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + sql.add('type,name,pid,filename,size,addtime', (1, fileName, id, backupName, 0, addTime)) + public.write_log_gettext("Database manager", "Backup database [{}] succeed!", (name,)) + # return public.return_msg_gettext(True, 'Backup Succeeded!') + return public.return_message(0, 0, "Backup Succeeded") + # except Exception as ex: + # public.write_log_gettext("数据库管理", "备份数据库[" + name + "]失败 => " + str(ex)) + # return public.return_msg_gettext(False,'备份失败!') + + # 删除备份文件 + def DelBackup(self, get): + + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + id = get.id + where = "id=?" + backup_info = public.M('backup').where(where, (id,)).find() + filename = backup_info['filename'] + if os.path.exists(filename): os.remove(filename) + db_name = '' + if filename == 'qiniu': + name = backup_info['name'] + public.ExecShell(public.get_python_bin() + " " + public.GetConfigValue( + 'setup_path') + '/panel/script/backup_qiniu.py delete_file ' + name) + public.M('backup').where(where, (id,)).delete() + # 取实际 + pid = backup_info['pid'] + db_name = public.M('databases').where('id=?', (pid,)).getField('name') + public.write_log_gettext("Database manager", 'Successfully deleted backup [{}] for database [{}]!', + (db_name, filename)) + # return public.return_msg_gettext(True, 'Successfully deleted') + return public.return_message(0, 0, "Successfully deleted") + except Exception as ex: + public.write_log_gettext("Database manager", 'Failed to delete backup [{}] for database [{}]! => {}', + (db_name, filename, str(ex))) + # return public.return_msg_gettext(False, 'Failed to delete') + return public.return_message(-1, 0, "Failed to delete") + + # 导入 + def InputSql(self, get): + from BTPanel import session + + # 校验参数 + try: + get.validate([ + Param('file').SafePath(), # 文件路径 + Param('name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + # try: + import shlex + name = get['name'] + file = get['file'] + if "|" in file: + file = file.split('|')[-1] + root = public.M('config').where('id=?', (1,)).getField('mysql_root') + tmp = file.split('.') + exts = ['sql', 'gz', 'zip'] + ext = tmp[len(tmp) - 1] + + if ext not in exts: + # return public.return_msg_gettext(False, 'Select sql/gz/zip file!') + return public.return_message(-1, 0, "Select sql/gz/zip file") + db_find = public.M('databases').where('name=?', name).find() + mysql_obj = public.get_mysql_obj_by_sid(db_find['sid']) + + if not mysql_obj: + # return public.returnMsg(False, 'Failed to connect to the specified database') + return public.return_message(-1, 0, "Failed to connect to the specified database") + result = mysql_obj.execute("show databases") + isError = self.IsSqlError(result) + if isError: + # return isError + return public.return_message(0, 0, isError) + isgzip = False + + mysql_bin = public.get_mysql_bin() + if ext != 'sql': + import panel_restore + tmp = file.split('/') + tmpFile = tmp[len(tmp) - 1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + # return tmpFile + backupPath = session['config']['backup_path'] + '/database' + if "|" in file: + panel_restore.panel_restore().restore_db_backup(get) + download_msg = panel_restore.panel_restore().restore_db_backup(get) + if not download_msg['status']: + # return download_msg + return public.return_message(0, 0, download_msg) + input_path = os.path.join(backupPath, tmpFile) + # 备份文件的路径 + input_path2 = os.path.join(os.path.dirname(file), tmpFile) + if ext == 'zip': + public.ExecShell("cd " + backupPath + " && unzip " + '"' + file + '"') + else: + public.ExecShell("cd " + backupPath + " && tar zxf " + '"' + file + '"') + if not os.path.exists(input_path): + # 兼容从备份文件所在目录恢复 + if not os.path.exists(input_path2): + public.ExecShell("cd " + backupPath + " && gunzip -q " + '"' + file + '"') + isgzip = True + else: + input_path = input_path2 + if not os.path.exists(input_path) or tmpFile == '': + if tmpFile and os.path.isfile(input_path2): + input_path = input_path2 + else: + # return public.return_msg_gettext(False, 'Configuration file not exist', (tmpFile,)) + return public.return_message(-1, 0, 'Configuration file not exist {}'.format(tmpFile)) + + try: + if db_find['db_type'] in ['0', 0]: + password = public.M('config').where('id=?', (1,)).getField('mysql_root') + password = shlex.quote(str(password)) + os.environ["MYSQL_PWD"] = str(password) + public.ExecShell(mysql_bin + " -uroot -p" + str( + password) + " --force \"" + name + "\" < " + '"' + input_path + '"') + elif db_find['db_type'] in ['1', 1]: + conn_config = json.loads(db_find['conn_config']) + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = str(password) + public.ExecShell(mysql_bin + " -h " + conn_config['db_host'] + " -P " + str( + int(conn_config['db_port'])) + " -u" + str(conn_config['db_user']) + " -p" + str( + password) + " --force \"" + name + "\" < " + '"' + input_path + '"') + elif db_find['db_type'] in ['2', 2]: + conn_config = public.M('database_servers').where('id=?', db_find['sid']).find() + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = str(password) + public.ExecShell(mysql_bin + " -h " + conn_config['db_host'] + " -P " + str( + int(conn_config['db_port'])) + " -u" + str(conn_config['db_user']) + " -p" + str( + password) + " --force \"" + name + "\" < " + '"' + input_path + '"') + except Exception as e: + raise + finally: + os.environ["MYSQL_PWD"] = "" + + if isgzip: + public.ExecShell('cd ' + os.path.dirname(input_path) + ' && gzip ' + file.split('/')[-1][:-3]) + else: + public.ExecShell("rm -f " + input_path) + else: + try: + if db_find['db_type'] in ['0', 0]: + password = public.M('config').where('id=?', (1,)).getField('mysql_root') + password = shlex.quote(str(password)) + os.environ["MYSQL_PWD"] = password + public.ExecShell( + mysql_bin + " -uroot -p" + password + " --force \"" + name + "\" < " + '"' + file + '"') + elif db_find['db_type'] in ['1', 1]: + conn_config = json.loads(db_find['conn_config']) + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = password + public.ExecShell(mysql_bin + " -h " + conn_config['db_host'] + " -P " + str( + int(conn_config['db_port'])) + " -u" + str(conn_config['db_user']) + " -p" + str( + password) + " --force \"" + name + "\" < " + '"' + file + '"') + elif db_find['db_type'] in ['2', 2]: + conn_config = public.M('database_servers').where('id=?', db_find['sid']).find() + password = shlex.quote(str(conn_config['db_password'])) + os.environ["MYSQL_PWD"] = password + public.ExecShell(mysql_bin + " -h " + conn_config['db_host'] + " -P " + str( + int(conn_config['db_port'])) + " -u" + str(conn_config['db_user']) + " -p" + str( + password) + " --force \"" + name + "\" < " + '"' + file + '"') + except Exception as e: + raise + finally: + os.environ["MYSQL_PWD"] = "" + + public.write_log_gettext("Database manager", 'Successfully imported database [{}]', (name,)) + # return public.return_msg_gettext(True, 'Successfully imported database!') + return public.return_message(0, 0, 'Successfully imported database!') + + + # 同步数据库到服务器 + def SyncToDatabases(self, get): + + # 校验参数 + try: + get.validate([ + Param('ids').Require(), # [67,66] + Param('type').Require().Integer(), # query传参 不确定.. + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # result = panelMysql.panelMysql().execute("show databases") + # isError=self.IsSqlError(result) + # if isError: return isError + type = int(get['type']) + n = 0 + sql = public.M('databases') + if type == 0: + data = sql.field('id,sid,name,username,password,accept,db_type').where("type='MySQL'", ()).select() + for value in data: + if value['db_type'] in ['1', 1]: + continue # 跳过远程数据库 + result = self.ToDataBase(value) + if result == 1: n += 1 + else: + import json + data = json.loads(get.ids) + for value in data: + find = sql.where("id=?", (value,)).field('id,sid,name,username,password,accept').find() + result = self.ToDataBase(find) + if result == 1: n += 1 + # 当只同步1个数据库时,不返回成功数量 + if n == 1: + # return public.returnMsg(True, 'Synchronization succeeded') + return public.return_message(0, 0, 'Synchronization succeeded') + elif n == 0: + # 失败 + # return public.returnMsg(False, 'Sync failed') + return public.return_message(-1, 0, 'Sync failed') + else: + # return public.return_msg_gettext(True, 'Sync {} database(s) from server!', (str(n),)) + return public.return_message(0, 0, 'Sync {} database(s) from server!'.format(str(n))) + + # 配置 + def mypass(self, act, password=None): + conf_file = '/etc/my.cnf' + conf_file_bak = '/etc/my.cnf.bak' + if os.path.getsize(conf_file) > 2: + public.writeFile(conf_file_bak, public.readFile(conf_file)) + public.set_mode(conf_file_bak, 600) + public.set_own(conf_file_bak, 'mysql') + elif os.path.getsize(conf_file_bak) > 2: + public.writeFile(conf_file, public.readFile(conf_file_bak)) + public.set_mode(conf_file, 600) + public.set_own(conf_file, 'mysql') + + public.ExecShell("sed -i '/user=root/d' {}".format(conf_file)) + public.ExecShell("sed -i '/password=/d' {}".format(conf_file)) + if act: + password = public.M('config').where('id=?', (1,)).getField('mysql_root') + mycnf = public.readFile(conf_file) + if not mycnf: return False + src_dump_re = r"\[mysqldump\][^.]" + sub_dump = "[mysqldump]\nuser=root\npassword=\"{}\"\n".format(password) + mycnf = re.sub(src_dump_re, sub_dump, mycnf) + if len(mycnf) > 100: public.writeFile(conf_file, mycnf) + return True + return True + + # 添加到服务器 + def ToDataBase(self, find): + # if find['username'] == 'bt_default': return 0 + if len(find['password']) < 3: + find['username'] = find['name'] + find['password'] = public.md5(str(time.time()) + find['name'])[0:10] + public.M('databases').where("id=?", (find['id'],)).save('password,username', + (find['password'], find['username'])) + self.sid = find['sid'] + mysql_obj = public.get_mysql_obj_by_sid(find['sid']) + if not mysql_obj: return public.returnMsg(False, 'Failed to connect to the specified database') + result = mysql_obj.execute("create database `" + find['name'] + "`") + if "using password:" in str(result): return -1 + if "Connection refused" in str(result): return -1 + + password = find['password'] + # if find['password']!="" and len(find['password']) > 20: + # password = find['password'] + + self.__CreateUsers(find['name'], find['username'], password, find['accept']) + return 1 + + # 从服务器获取数据库 + def SyncGetDatabases(self, get): + + # 校验参数 + try: + get.validate([ + Param('sid').Require().Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self.sid = get.get('sid/d', 0) + db_type = 0 + if self.sid: db_type = 2 + mysql_obj = public.get_mysql_obj_by_sid(self.sid) + if not mysql_obj: + # return public.returnMsg(False, 'Failed to connect to the specified database') + return public.return_message(-1, 0, 'Failed to connect to the specified database') + data = mysql_obj.query("show databases") + isError = self.IsSqlError(data) + if isError != None: + # return isError + return public.return_message(-1, 0, isError) + users = mysql_obj.query( + "select User,Host from mysql.user where User!='root' AND Host!='localhost' AND Host!=''") + + if type(users) == str: + # return public.returnMsg(False, users) + return public.return_message(-1, 0, users) + if type(users) != list: + # return public.returnMsg(False, public.GetMySQLError(users)) + return public.return_message(-1, 0, public.GetMySQLError(users)) + + sql = public.M('databases') + nameArr = ['information_schema', 'performance_schema', 'mysql', 'sys'] + n = 0 + for value in data: + b = False + for key in nameArr: + if value[0] == key: + b = True + break + if b: continue + if sql.where("name=?", (value[0],)).count(): continue + host = '127.0.0.1' + for user in users: + if value[0] == user[0]: + host = user[1] + break + + ps = public.get_msg_gettext('Edit notes') + if value[0] == 'test': + ps = public.get_msg_gettext('Test Database') + + # XSS filter + if not re.match(r"^[\w+\.-]+$", value[0]): continue + + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + + if sql.table('databases').add('name,sid,db_type,username,password,accept,ps,addtime', + (value[0], self.sid, db_type, value[0], '', host, ps, addTime)): n += 1 + + # return public.return_msg_gettext(True, 'Obtain {} database(s) from server!', (str(n),)) + return public.return_message(0, 0, 'Obtain {} database(s) from server!'.format(n)) + + # 获取数据库权限 + def GetDatabaseAccess(self, get): + + # 校验参数 + try: + get.validate([ + Param('name').Require().String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + name = get['name'] + db_name = public.M('databases').where('username=?', name).getField('name') + mysql_obj = public.get_mysql_obj(db_name) + users = mysql_obj.query("select Host from mysql.user where User='" + name + "' AND Host!='localhost'") + ssl_type = panelMysql.panelMysql().query("select ssl_type from mysql.user where User='%s'" % name) + isError = self.IsSqlError(users) + if isError != None: + # return isError + return public.return_message(-1, 0, isError) + users = self.map_to_list(users) + try: + if ssl_type: + ssl_type = ssl_type[0][0] + except: + ssl_type = "" + if len(users) < 1: + # return public.return_msg_gettext(True, {"permission": "127.0.0.1", "ssl": ssl_type}) + return public.return_message(0, 0, {"permission": "127.0.0.1", "ssl": ssl_type}) + + accs = [] + for c in users: + accs.append(c[0]) + userStr = ','.join(accs) + # return public.return_msg_gettext(True, {"permission": userStr, "ssl": ssl_type}) + return public.return_message(0, 0, {"permission": userStr, "ssl": ssl_type}) + + # 设置数据库权限 + def SetDatabaseAccess(self, get): + + # 校验参数 + try: + get.validate([ + Param('name').Require().String(), + # Param('dataAccess').String().Xss(), + # Param('address').String().Xss(), + Param('access').Require().String(), + Param('ssl').String().Xss(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + ssl = "" + if hasattr(get, 'ssl'): + ssl = get.ssl + if ssl == "REQUIRE SSL" and not self.check_mysql_ssl_status(get): + # return public.return_msg_gettext(False,'SSL is not enabled in the database, please open it in the Mysql manager first') + return public.return_message(-1, 0, 'SSL is not enabled in the database, please open it in the Mysql manager first') + name = get['name'] + db_find = public.M('databases').where('username=?', (name,)).find() + db_name = db_find['name'] + self.sid = db_find['sid'] + mysql_obj = public.get_mysql_obj(db_name) + access = get['access'].strip() + # if access in ['']: return public.return_msg_gettext(False, 'The IP address cannot be empty!') + password = public.M('databases').where("username=?", (name,)).getField('password') + result = mysql_obj.query("show databases") + isError = self.IsSqlError(result) + if isError != None: + # return isError + return public.return_message(-1, 0, isError) + users = mysql_obj.query("select Host from mysql.user where User='" + name + "' AND Host!='localhost'") + for us in users: + mysql_obj.execute("drop user '" + name + "'@'" + us[0] + "'") + self.__CreateUsers(db_name, name, password, access, ssl) + # return public.return_msg_gettext(True, 'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + + # 获取数据库配置信息 + def GetMySQLInfo(self, get): + data = {} + try: + public.CheckMyCnf() + myfile = '/etc/my.cnf' + mycnf = public.readFile(myfile) + rep = "datadir\\s*=\\s*(.+)\n" + data['datadir'] = re.search(rep, mycnf).groups()[0] + rep = "port\\s*=\\s*([0-9]+)\\s*\n" + data['port'] = re.search(rep, mycnf).groups()[0] + except: + data['datadir'] = '/www/server/data' + data['port'] = '3306' + # return data + return public.return_message(0, 0, data) + + # 修改数据库目录 + def SetDataDir(self, get): + if get.datadir[-1] == '/': get.datadir = get.datadir[0:-1] + if len(get.datadir) > 32: return public.return_msg_gettext(False, + 'The data directory length cannot exceed 32 bits') + # if not re.search(r"^[0-9A-Za-z_/\\]$+",get.datadir): return public.return_msg_gettext(False,'Special symbols cannot be included in the database path') + if not os.path.exists(get.datadir): public.ExecShell('mkdir -p ' + get.datadir) + mysqlInfo = self.GetMySQLInfo(get) + if mysqlInfo['datadir'] == get.datadir: return public.return_msg_gettext(False, + 'The same as the current storage directory, file cannot be moved!') + + public.ExecShell('/etc/init.d/mysqld stop') + public.ExecShell(r'\cp -arf ' + mysqlInfo['datadir'] + '/* ' + get.datadir + '/') + public.ExecShell('chown -R mysql.mysql ' + get.datadir) + public.ExecShell('chmod -R 755 ' + get.datadir) + public.ExecShell('rm -f ' + get.datadir + '/*.pid') + public.ExecShell('rm -f ' + get.datadir + '/*.err') + + public.CheckMyCnf() + myfile = '/etc/my.cnf' + mycnf = public.readFile(myfile) + public.writeFile('/etc/my_backup.cnf', mycnf) + mycnf = mycnf.replace(mysqlInfo['datadir'], get.datadir) + public.writeFile(myfile, mycnf) + public.ExecShell('/etc/init.d/mysqld start') + result = public.ExecShell('ps aux|grep mysqld|grep -v grep') + if len(result[0]) > 10: + public.writeFile('data/datadir.pl', get.datadir) + return public.return_msg_gettext(True, 'File moved!') + else: + public.ExecShell('pkill -9 mysqld') + public.writeFile(myfile, public.readFile('/etc/my_backup.cnf')) + public.ExecShell('/etc/init.d/mysqld start') + return public.return_msg_gettext(False, 'Failed to move file!') + + # 修改数据库端口 + def SetMySQLPort(self, get): + myfile = '/etc/my.cnf' + mycnf = public.readFile(myfile) + rep = "port\\s*=\\s*([0-9]+)\\s*\n" + mycnf = re.sub(rep, 'port = ' + get.port + '\n', mycnf) + public.writeFile(myfile, mycnf) + public.ExecShell('/etc/init.d/mysqld restart') + return public.return_msg_gettext(True, 'Setup successfully!') + + # 获取错误日志 + def GetErrorLog(self, get): + path = self.GetMySQLInfo(get)['datadir'] + filename = '' + for n in os.listdir(path): + if len(n) < 5: continue + if n[-3:] == 'err': + filename = path + '/' + n + break + if not os.path.exists(filename): return public.return_msg_gettext(False, 'Configuration file not exist') + if hasattr(get, 'close'): + public.writeFile(filename, '') + return public.return_msg_gettext(True, 'log is empty') + return public.GetNumLines(filename, 1000) + + # 二进制日志开关 + def BinLog(self, get): + status = getattr(get, "status", None) + mysql_cnf = public.readFile(self._MYSQL_CNF) + + if mysql_cnf.find('#log-bin=mysql-bin') != -1: + if hasattr(get, 'status'): return public.return_msg_gettext(False, '0') + + log_bin_status = re.search("\nlog-bin", mysql_cnf) + + is_off_bin_log = re.search("\nskip-log-bin", mysql_cnf) + bin_log_status = False + if log_bin_status and not is_off_bin_log: + bin_log_status = True + + mysql_data_dir = self.GetMySQLInfo(get)['datadir'] + if status is not None: + bin_log_total_size = 0 + mysql_bin_index = os.path.join(mysql_data_dir, "mysql-bin.index") + mysql_bin_index_content = public.readFile(mysql_bin_index) + if mysql_bin_index_content is not False: + for name in str(mysql_bin_index_content).strip().split("\n"): + bin_log_path = os.path.join(mysql_data_dir, os.path.basename(name)) + if os.path.isfile(bin_log_path): + bin_log_total_size += os.path.getsize(bin_log_path) + # return {"status": True, "msg": "ok", "data": {"binlog_status": bin_log_status, "size": bin_log_total_size}} + return public.return_msg_gettext(True, bin_log_total_size) + + if bin_log_status is True: # 关闭 binlog 日志 + master_slave_conf_1 = "/www/server/panel/plugin/masterslave/data.json" + master_slave_conf_2 = "/www/server/panel/plugin/mysql_replicate/config.json" + if os.path.exists(master_slave_conf_1): + # return {"status": False, "msg": "请先卸载【Mysql主从复制】插件后再关闭二进制日志!!", "data": {"binlog_status": bin_log_status}} + return public.return_msg_gettext(False, + 'Please uninstall the Mysql master-slave replication plugin before closing the binary log! !') + if os.path.exists(master_slave_conf_2): + # return {"status": False, "msg": "请先卸载【Mysql主从复制(重构版)】插件后再关闭二进制日志!!", "data": {"binlog_status": bin_log_status}} + return public.return_msg_gettext(False, + 'Please uninstall the Mysql master-slave replication plugin before closing the binary log! !') + if log_bin_status: + mysql_cnf = re.sub(r"\nlog-bin", "\n#log-bin", mysql_cnf) + mysql_cnf = re.sub(r"\nbinlog_format", "\n#binlog_format", mysql_cnf) + if not is_off_bin_log: + if re.search("\n#\\s*skip-log-bin", mysql_cnf): + mysql_cnf = re.sub("\n#\\s*skip-log-bin", "\nskip-log-bin", mysql_cnf) + else: + mysql_cnf = re.sub("\n#\\s*log-bin", "\nskip-log-bin\n#log-bin", mysql_cnf) + # public.ExecShell("rm -f {}/mysql-bin.*".format(mysql_data_dir)) + else: # 开启 binlog 日志 + if re.search("\n#\\s*log-bin", mysql_cnf): + mysql_cnf = re.sub("\n#\\s*log-bin", "\nlog-bin", mysql_cnf) + else: + mysql_cnf = re.sub("[mysqld]", "[mysqld]\nlog-bin=mysql-bin", mysql_cnf) + + if re.search("\n#\\s*binlog_format", mysql_cnf): + mysql_cnf = re.sub("\n#\\s*binlog_format", "\nbinlog_format", mysql_cnf) + else: + mysql_cnf = re.sub("[mysqld]", "[mysqld]\nbinlog_format=mixed", mysql_cnf) + + if is_off_bin_log: + mysql_cnf = re.sub("\nskip-log-bin", "\n#skip-log-bin", mysql_cnf) + + public.writeFile(self._MYSQL_CNF, mysql_cnf) + public.ExecShell('sync') + public.ExecShell('/etc/init.d/mysqld restart') + return {"status": True, "msg": "{} Binary log successful, Please refresh manually".format("Enable" if not bin_log_status else "Disable"), "data": {"binlog_status": not bin_log_status}} + + # 获取MySQL配置状态 + def GetDbStatus(self, get): + result = {} + data = self.map_to_list(panelMysql.panelMysql().query('show variables')) + gets = ['table_open_cache', 'thread_cache_size', 'query_cache_type', 'key_buffer_size', 'query_cache_size', + 'tmp_table_size', 'max_heap_table_size', 'innodb_buffer_pool_size', 'innodb_additional_mem_pool_size', + 'innodb_log_buffer_size', 'max_connections', 'sort_buffer_size', 'read_buffer_size', + 'read_rnd_buffer_size', 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + result['mem'] = {} + for d in data: + try: + for g in gets: + if d[0] == g: result['mem'][g] = d[1] + except: + continue + + if 'query_cache_type' in result['mem']: + if result['mem']['query_cache_type'] != 'ON': result['mem']['query_cache_size'] = '0' + return result + + # 设置MySQL配置参数 + def SetDbConf(self, get): + gets = ['key_buffer_size', 'query_cache_size', 'tmp_table_size', 'max_heap_table_size', + 'innodb_buffer_pool_size', 'innodb_log_buffer_size', 'max_connections', 'query_cache_type', + 'table_open_cache', 'thread_cache_size', 'sort_buffer_size', 'read_buffer_size', 'read_rnd_buffer_size', + 'join_buffer_size', 'thread_stack', 'binlog_cache_size'] + emptys = ['max_connections', 'query_cache_type', 'thread_cache_size', 'table_open_cache'] + mycnf = public.readFile('/etc/my.cnf') + n = 0 + m_version = public.readFile('/www/server/mysql/version.pl') + if not m_version: m_version = '' + for g in gets: + if m_version.find('8.') == 0 and g in ['query_cache_type', 'query_cache_size']: + n += 1 + continue + s = 'M' + if n > 5 and not g in ['key_buffer_size', 'query_cache_size', 'tmp_table_size', 'max_heap_table_size', + 'innodb_buffer_pool_size', 'innodb_log_buffer_size']: s = 'K' + if g in emptys: s = '' + if g in ['innodb_log_buffer_size']: + s = 'M' + if int(get[g]) < 8: + return public.return_msg_gettext(False, 'innodb_log_buffer_size cannot be less than 8MB') + + rep = r'\s*' + g + r'\s*=\s*\d+(M|K|k|m|G)?\n' + + c = g + ' = ' + get[g] + s + '\n' + if mycnf.find(g) != -1: + mycnf = re.sub(rep, '\n' + c, mycnf, 1) + else: + mycnf = mycnf.replace('[mysqld]\n', '[mysqld]\n' + c) + n += 1 + public.writeFile('/etc/my.cnf', mycnf) + return public.return_msg_gettext(True, 'Setup successfully!') + + # 获取MySQL运行状态 + def GetRunStatus(self, get): + import time + result = {} + data = panelMysql.panelMysql().query('show global status') + gets = ['Max_used_connections', 'Com_commit', 'Com_rollback', 'Questions', 'Innodb_buffer_pool_reads', + 'Innodb_buffer_pool_read_requests', 'Key_reads', 'Key_read_requests', 'Key_writes', + 'Key_write_requests', 'Qcache_hits', 'Qcache_inserts', 'Bytes_received', 'Bytes_sent', + 'Aborted_clients', 'Aborted_connects', 'Created_tmp_disk_tables', 'Created_tmp_tables', + 'Innodb_buffer_pool_pages_dirty', 'Opened_files', 'Open_tables', 'Opened_tables', 'Select_full_join', + 'Select_range_check', 'Sort_merge_passes', 'Table_locks_waited', 'Threads_cached', 'Threads_connected', + 'Threads_created', 'Threads_running', 'Connections', 'Uptime'] + try: + if data[0] == 1045: + return public.return_msg_gettext(False, 'MySQL password ERROR!') + for d in data: + for g in gets: + try: + if d[0] == g: result[g] = d[1] + except: + pass + except: + return public.return_msg_gettext(False, str(data)) + + if not 'Run' in result and result: + result['Run'] = int(time.time()) - int(result['Uptime']) + tmp = panelMysql.panelMysql().query('show master status') + try: + + result['File'] = tmp[0][0] + result['Position'] = tmp[0][1] + except: + result['File'] = 'OFF' + result['Position'] = 'OFF' + return result + + # 取慢日志 + def GetSlowLogs(self, get): + path = self.GetMySQLInfo(get)['datadir'] + '/mysql-slow.log' + if not os.path.exists(path): return public.return_msg_gettext(False, 'Log file does NOT exist!') + return public.return_msg_gettext(True, public.GetNumLines(path, 100)) + + # 获取binlog文件列表 + def GetMySQLBinlogs(self, get): + data_dir = self.GetMySQLInfo(get)["datadir"] + index_file = os.path.join(data_dir, "mysql-bin.index") + if not os.path.exists(index_file): return public.return_msg_gettext(False, 'Binlog is not enabled or binlog file does not exist!') + + text = public.readFile(index_file) + + rows = panelMysql.panelMysql().query("show master status") + + current_log = "" + if not isinstance(rows, list): + return public.return_msg_gettext(False, "Mysql status is abnormal!") + if len(rows) != 0: + current_log = rows[0][0] + + bin_log = [] + for item in text.split('\n'): + log_file = item.strip() + log_name = log_file.lstrip("./") + if not log_file: continue # 空行 + bin_log_path = os.path.join(data_dir, log_name) + if not os.path.isfile(bin_log_path): continue + st = os.stat(bin_log_path) + bin_log.append({ + "name": log_name, + "path": bin_log_path, + "size": st.st_size, + "last_modified": int(st.st_mtime), + "last_access": int(st.st_atime), + "current": current_log == log_name + }) + return {"status": True, "msg": "ok", "data": bin_log} + + def ClearMySQLBinlog(self, get): + if not hasattr(get, "days"): + return public.returnMsg(False, "Parameters are missing! days") + if not str(get.days).isdigit(): + return public.returnMsg(False, "Parameters are missing! days") + days = int(get.days) + if days < 7: return public.return_msg_gettext(False, 'To ensure data security, recent binlogs cannot be deleted!') + + rows = panelMysql.panelMysql().query("PURGE BINARY LOGS BEFORE DATE_SUB(NOW(), INTERVAL {days} DAY)".format(days=days)) + # public.print_log(rows) + # if rows: public.print_log(rows[0]) + + return public.return_msg_gettext(True, "Cleanup complete!") + + # 获取当前数据库信息 + def GetInfo(self, get): + # 校验参数 + try: + get.validate([ + Param('db_name').Require().String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + info = self.GetdataInfo(get) + # return info + return public.return_message(0, 0, info) + # if info: + # return info + # else: + # return public.return_msg_gettext(False, 'Failed to get databases') + + # 修复表信息 + def ReTable(self, get): + info = self.RepairTable(get) + if info: + # return public.return_msg_gettext(True, 'Successfully repaired!') + return public.return_message(0, 0, 'Successfully repaired!') + else: + # return public.return_msg_gettext(False, 'Failed to repair!') + return public.return_message(-1, 0, 'Failed to repair!') + + # 优化表 + def OpTable(self, get): + + + info = self.OptimizeTable(get) + if info: + # return public.return_msg_gettext(True, 'Successfully optimized!') + return public.return_message(0, 0, 'Successfully optimized!') + else: + # return public.return_msg_gettext(False, 'Failed to optimize or already optimized') + return public.return_message(-1, 0, 'Failed to optimize or already optimized') + + # 更改表引擎 + def AlTable(self, get): + + # 校验参数 + try: + get.validate([ + Param('db_name').Require().String(), + Param('tables').Require().String(), # ["wp_commentmeta"] + Param('table_type').Require().String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + info = self.AlterTable(get) + if info: + # return public.return_msg_gettext(True, 'Successfully changed') + return public.return_message(0, 0, 'Successfully changed') + else: + # return public.return_msg_gettext(False, 'Failed to change') + return public.return_message(-1, 0, 'Failed to change') + + def get_average_num(self, slist): + """ + @获取平均值 + """ + count = len(slist) + limit_size = 1 * 1024 * 1024 + if count <= 0: return limit_size + + if len(slist) > 1: + slist = sorted(slist) + limit_size = int((slist[0] + slist[-1]) / 2 * 0.85) + return limit_size + + def get_database_size(self, ids, is_pid=False): + """ + 获取数据库大小 + """ + result = {} + for id in ids: + if not is_pid: + x = public.M('databases').where('id=?', id).field('id,sid,pid,name,type,ps,addtime').find() + else: + x = public.M('databases').where('pid=?', id).field('id,sid,pid,name,ps,type,addtime').find() + if not x: continue + x['backup_count'] = public.M('backup').where("pid=? AND type=?", (x['id'], '1')).count() + if x['type'] == 'MySQL': + x['total'] = int(public.get_database_size_by_id(x['id'])) + else: + try: + from panelDatabaseController import DatabaseController + project_obj = DatabaseController() + + get = public.dict_obj() + get['data'] = {'db_id': x['id']} + get['mod_name'] = x['type'].lower() + get['def_name'] = 'get_database_size_by_id' + + x['total'] = project_obj.model(get) + except: + x['total'] = int(public.get_database_size_by_id(x['id'])) + result[x['name']] = x + return result + + def check_del_data(self, get): + """ + @删除数据库前置检测 + """ + + # 校验参数 + try: + get.validate([ + Param('ids').Require(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + ids = json.loads(get.ids) + slist = {} + result = [] + db_list_size = [] + db_data = self.get_database_size(ids) + for key in db_data: + data = db_data[key] + if not data['id'] in ids: continue + + db_addtime = public.to_date(times=data['addtime']) + data['score'] = int(time.time() - db_addtime) + data['total'] + data['st_time'] = db_addtime + + if data['total'] > 0: db_list_size.append(data['total']) + result.append(data) + + slist['data'] = sorted(result, key=lambda x: x['score'], reverse=True) + slist['db_size'] = self.get_average_num(db_list_size) + # return slist + return public.return_message(0, 0, slist) + + + # 获取备份文件 + def GetBackup(self, get): + + # 分页校验参数 + try: + get.validate([ + Param('limit').Integer(), + Param('p').Integer(), + Param('search').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + p = getattr(get, "p", 1) + limit = getattr(get, "limit", 10) + return_js = getattr(get, "return_js", "") + search = getattr(get, "search", None) + + # if not str(p).isdigit(): + # return public.returnMsg(False, "参数错误!p") + # if not str(limit).isdigit(): + # return public.returnMsg(False, "参数错误!limit") + + p = int(p) + limit = int(limit) + + ext_list = ["sql", "tar.gz", "gz", "zip"] + + backup_list = [] + + # 递归获取备份文件 + def get_dir_backup(backup_dir: str, backup_list: list, is_recursion: bool): + for name in os.listdir(backup_dir): + path = os.path.join(backup_dir, name) + if os.path.isdir(path) and name == "all_backup": continue # 跳过全部备份目录 + if os.path.isfile(path): + ext = name.split(".")[-1] + if ext.lower() not in ext_list: continue + if search is not None and search not in name: continue + + stat_file = os.stat(path) + path_data = { + "name": name, + "path": path, + "size": stat_file.st_size, + "mtime": int(stat_file.st_mtime), + "ctime": int(stat_file.st_ctime), + } + backup_list.append(path_data) + elif os.path.isdir(path) and is_recursion is True: + get_dir_backup(path, backup_list, is_recursion) + + get_dir_backup(self._MYSQL_BACKUP_DIR, backup_list, True) + get_dir_backup(self._DB_BACKUP_DIR, backup_list, False) + + try: + from flask import request + uri = public.url_encode(request.full_path) + except: + uri = '' + # 包含分页类 + import page + # 实例化分页类 + page = page.Page() + info = { + "p": p, + "count": len(backup_list), + "row": limit, + "return_js": return_js, + "uri": uri, + } + page_info = page.GetPage(info) + + start_idx = (int(p) - 1) * limit + end_idx = p * limit if p * limit < len(backup_list) else len(backup_list) + backup_list.sort(key=lambda data: data["mtime"], reverse=True) + backup_list = backup_list[start_idx:end_idx] + # return {"status": True, "msg": "OK", "data": backup_list, "page": page_info} + return public.return_message(0, 0, {"status": True, "msg": "OK", "data": backup_list, "page": page_info}) diff --git a/class_v2/datatool_v2.py b/class_v2/datatool_v2.py new file mode 100644 index 00000000..3f99f81b --- /dev/null +++ b/class_v2/datatool_v2.py @@ -0,0 +1,168 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: 1249648969@qq.com +# ------------------------------------------------------------------- + +# ------------------------------ +# 数据库工具类 +# ------------------------------ +import sys, os +os.chdir("/www/server/panel") +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import panelMysql +import re,json,public + +class datatools: + DB_MySQL = None + # 字节单位转换 + def ToSize(self, size): + ds = ['b', 'KB', 'MB', 'GB', 'TB'] + for d in ds: + if size < 1024: return ('%.2f' % size) + d + size = size / 1024 + return '0b' + + # 获取当前数据库信息 + def GetdataInfo(self,get): + ''' + 传递一个数据库名称即可 get.databases + ''' + + db_name=get.db_name + if not db_name:return False + if not self.DB_MySQL:self.DB_MySQL = public.get_mysql_obj(db_name) + if not self.DB_MySQL: return self.DB_MySQL + ret = {} + tables = self.map_to_list(self.DB_MySQL.query('show tables from `%s`' % db_name)) + if type(tables) == list: + try: + data = self.map_to_list(self.DB_MySQL.query("select sum(DATA_LENGTH)+sum(INDEX_LENGTH) from information_schema.tables where table_schema='%s'" % db_name))[0][0] + except: + data=0 + + if not data: data = 0 + ret['data_size'] = self.ToSize(data) + ret['database'] = db_name + + ret3 = [] + for i in tables: + if i == 1049: return public.return_msg_gettext(False,'Database does NOT exist!') + if type(i) == int: continue + table = self.map_to_list(self.DB_MySQL.query("show table status from `%s` where name = '%s'" % (db_name, i[0]))) + if not table: continue + try: + ret2 = {} + ret2['type']=table[0][1] + data_size = table[0][6] + ret2['rows_count'] = self.DB_MySQL.query("select count(*) from `{}`.`{}`".format(db_name,i[0]))[0][0] #table[0][4] 实时获取行数 @authow hwliang<2021-08-05> 修改 + ret2['collation'] = table[0][14] + ret2['data_size'] = self.ToSize(int(data_size)) + ret2['table_name'] = i[0] + ret3.append(ret2) + except: continue + ret['tables'] = (ret3) + return ret + + + + #修复表信息 + def RepairTable(self,get): + + ''' + POST: + db_name=web + tables=['web1','web2'] + ''' + db_name = get.db_name + tables = json.loads(get.tables) + if not db_name or not tables: return False + if not self.DB_MySQL:self.DB_MySQL = public.get_mysql_obj(db_name) + m_version = self.DB_MySQL.query('select version();')[0][0] + if m_version.find('5.1.')!=-1:return public.return_msg_gettext(False,"Nonsupport mysql5.1!") + mysql_table = self.map_to_list(self.DB_MySQL.query('show tables from `%s`' % db_name)) + ret=[] + if type(mysql_table)==list: + if len(mysql_table)>0: + for i in mysql_table: + for i2 in tables: + if i2==i[0]: + ret.append(i2) + if len(ret)>0: + for i in ret: + self.DB_MySQL.execute('REPAIR TABLE `%s`.`%s`'%(db_name,i)) + return True + return False + + + + #map to list + def map_to_list(self,map_obj): + try: + if type(map_obj) != list and type(map_obj) != str: map_obj = list(map_obj) + return map_obj + except: return [] + + + # 优化表 + def OptimizeTable(self,get): + ''' + POST: + db_name=web + tables=['web1','web2'] + ''' + + db_name = get.db_name + tables = json.loads(get.tables) + if not db_name or not tables: return False + if not self.DB_MySQL:self.DB_MySQL = public.get_mysql_obj(db_name) + mysql_table = self.map_to_list(self.DB_MySQL.query('show tables from `%s`' % db_name)) + ret=[] + if type(mysql_table) == list: + if len(mysql_table) > 0: + for i in mysql_table: + for i2 in tables: + if i2 == i[0]: + ret.append(i2) + if len(ret)>0: + for i in ret: + self.DB_MySQL.execute('OPTIMIZE table `%s`.`%s` ENGINE=MyISAM' % (db_name,i)) + return True + return False + + # 更改表引擎 + def AlterTable(self,get): + ''' + POST: + db_name=web + table_type=innodb + tables=['web1','web2'] + ''' + db_name = get.db_name + table_type = get.table_type + tables = json.loads(get.tables) + + if not db_name or not tables: return False + if not self.DB_MySQL:self.DB_MySQL = public.get_mysql_obj(db_name) + mysql_table = self.map_to_list(self.DB_MySQL.query('show tables from `%s`' % db_name)) + ret=[] + if type(mysql_table)==list: + if len(mysql_table)>0: + for i in mysql_table: + for i2 in tables: + if i2==i[0]: + ret.append(i2) + if len(ret)>0: + for i in ret: + self.DB_MySQL.execute('alter table `%s`.`%s` ENGINE=`%s`' % (db_name,i,table_type)) + return True + return False + + + #检查表 + def CheckTable(self,database,tables,*args,**kwargs): + pass diff --git a/class_v2/db_mysql_v2.py b/class_v2/db_mysql_v2.py new file mode 100644 index 00000000..91522d13 --- /dev/null +++ b/class_v2/db_mysql_v2.py @@ -0,0 +1,374 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +import re,os,sys,public,json +import pymysql + +class panelMysql: + __DB_PASS = None + __DB_USER = 'root' + __DB_NAME = None + __DB_PORT = 3306 + __DB_HOST = 'localhost' + __DB_PREFIX = '' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + __DB_TABLE = "" # 被操作的表名称 + __OPT_WHERE = "" # where条件 + __OPT_LIMIT = "" # limit条件 + __OPT_ORDER = "" # order条件 + __OPT_FIELD = "*" # field条件 + __OPT_PARAM = () # where值 + _USER = None + _ex = None + + def __init__(self): + pass + + def set_name(self,name): + self.__DB_NAME = str(name) + return self + + def set_prefix(self,prefix): + self.__DB_PREFIX = prefix + return self + + def set_host(self,host,port,name,username,password,prefix = ''): + self.__DB_HOST = host + self.__DB_PORT = int(port) + self.__DB_NAME = name + if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME) + self.__DB_USER = str(username) + self._USER = str(username) + self.__DB_PASS = str(password) + self.__DB_PREFIX = prefix + if not self.__GetConn(): return False + return self + + #连接MYSQL数据库 + def __GetConn(self): + try: + # print(self.__DB_HOST,self.__DB_PORT,self.__DB_NAME,self.__DB_USER,self.__DB_PASS) + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST,user=self.__DB_USER,passwd=str(self.__DB_PASS),db=self.__DB_NAME,port=self.__DB_PORT,connect_timeout=15,read_timeout=60,write_timeout=60) + except Exception as ex: + self.__DB_ERR = "error: " + str(ex) + self._ex = ex + print(ex) + if self.__DB_ERR.find("timed out") != -1 or self.__DB_ERR.find("is not allowed to connect") != -1: return False + try: + self.__DB_CONN = pymysql.connect(host=self.__DB_HOST,user=self.__DB_USER,passwd=str(self.__DB_PASS),db=self.__DB_NAME,port=self.__DB_PORT) + except Exception as ex: + self.__DB_ERR = "error: " + str(ex) + self._ex = ex + print(ex) + return False + self.__DB_CUR = self.__DB_CONN.cursor() + return True + + def table(self,table): + #设置表名 + self.__DB_TABLE = self.__DB_PREFIX + table + return self + + + def where(self,where,param): + #WHERE条件 + if where: + self.__OPT_WHERE = " WHERE " + where + self.__OPT_PARAM = self.__to_tuple(param) + return self + + def __to_tuple(self,param): + #将参数转换为tuple + if type(param) != tuple: + if type(param) == list: + param = tuple(param) + else: + param = (param,) + return param + + + def order(self,order): + #ORDER条件 + if len(order): + self.__OPT_ORDER = " ORDER BY "+order + return self + + + def limit(self,limit): + #LIMIT条件 + limit = str(limit) + if len(limit): + self.__OPT_LIMIT = " LIMIT "+ limit + return self + + + def field(self,field): + #FIELD条件 + if len(field): + self.__OPT_FIELD = field + return self + + + def select(self): + #查询数据集 + self.__GetConn() + if not self.__DB_CUR: return self.__DB_ERR + try: + self.__get_columns() + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + self.__OPT_WHERE + self.__OPT_ORDER + self.__OPT_LIMIT + self.__DB_CUR.execute(sql,self.__OPT_PARAM) + data = self.__DB_CUR.fetchall() + #构造字典系列 + if self.__OPT_FIELD != "*": + fields = self.__format_field(self.__OPT_FIELD.split(',')) + tmp = [] + for row in data: + i=0 + tmp1 = {} + for key in fields: + tmp1[key.strip('`')] = row[i] + i += 1 + tmp.append(tmp1) + del(tmp1) + data = tmp + del(tmp) + else: + #将元组转换成列表 + tmp = list(map(list, data)) + data = tmp + del(tmp) + self.__close() + return data + except Exception as ex: + self._ex = ex + return "error: " + str(ex) + + def get(self): + self.__get_columns() + return self.select() + + def __format_field(self, field): + import re + fields = [] + for key in field: + s_as = re.search(r'\s+as\s+', key, flags=re.IGNORECASE) + if s_as: + as_tip = s_as.group() + key = key.split(as_tip)[1] + fields.append(key) + return fields + + def __get_columns(self): + if self.__OPT_FIELD == '*': + tmp_cols = self.query( + "select COLUMN_NAME from information_schema.COLUMNS where table_name = '{}' and table_schema = '{}';" + .format(self.__DB_TABLE, self.__DB_NAME), False) + cols = [] + for col in tmp_cols: + cols.append('`' + col[0] + '`') + if len(cols) > 0: self.__OPT_FIELD = ','.join(cols) + + def getField(self, keyName): + #取回指定字段 + try: + result = self.field(keyName).select() + if len(result) != 0: + return result[0][keyName] + return result + except: + return None + + def setField(self, keyName, keyValue): + #更新指定字段 + return self.save(keyName, (keyValue, )) + + def find(self): + #取一行数据 + try: + result = self.limit("1").select() + if len(result) == 1: + return result[0] + return result + except: + return None + + def count(self): + #取行数 + key = "COUNT(*)" + data = self.field(key).select() + try: + return int(data[0][key]) + except: + return 0 + + def add(self, keys, param): + #插入数据 + self.__GetConn() + self.__DB_CONN.text_factory = str + try: + values = "" + for key in keys.split(','): + values += "%s," + values = values[0:len(values) - 1] + sql = "INSERT INTO " + self.__DB_TABLE + "(" + keys + ") " + "VALUES(" + values + ")" + self.__DB_CUR.execute(sql, self.__to_tuple(param)) + id = self.__DB_CUR.lastrowid + self.__close() + self.__DB_CONN.commit() + return id + except Exception as ex: + self._ex = ex + return "error: " + str(ex) + + #插入数据 + def insert(self, pdata): + if not pdata: return False + keys, param = self.__format_pdata(pdata) + return self.add(keys, param) + + #更新数据 + def update(self, pdata): + if not pdata: return False + keys, param = self.__format_pdata(pdata) + return self.save(keys, param) + + #构造数据 + def __format_pdata(self, pdata): + keys = pdata.keys() + keys_tmp = [] + for k in keys: + keys_tmp.append("`{}`".format(k)) + keys_str = ','.join(keys_tmp) + + param = [] + for k in keys: + #if pdata[k] == None: pdata[k] = '' + param.append(pdata[k]) + return keys_str, tuple(param) + + def addAll(self, keys, param): + #插入数据 + self.__GetConn() + self.__DB_CONN.text_factory = str + try: + values = "" + for key in keys.split(','): + values += "%s," + values = values[0:len(values) - 1] + sql = "INSERT INTO " + self.__DB_TABLE + "(" + keys + ") " + "VALUES(" + values + ")" + result = self.__DB_CUR.execute(sql, self.__to_tuple(param)) + return True + except Exception as ex: + self._ex = ex + return "error: " + str(ex) + + def commit(self): + self.__close() + self.__DB_CONN.commit() + + def save(self, keys, param): + #更新数据 + self.__GetConn() + self.__DB_CONN.text_factory = str + try: + opt = "" + for key in keys.split(','): + opt += key + "=%s," + opt = opt[0:len(opt) - 1] + sql = "UPDATE " + self.__DB_TABLE + " SET " + opt + self.__OPT_WHERE + + #处理拼接WHERE与UPDATE参数 + if param: + tmp = list(self.__to_tuple(param)) + for arg in self.__OPT_PARAM: + tmp.append(arg) + self.__OPT_PARAM = tuple(tmp) + self.__DB_CUR.execute(sql,self.__OPT_PARAM) + else: + self.__DB_CUR.execute(sql) + self.__close() + self.__DB_CONN.commit() + return self.__DB_CUR.rowcount + except Exception as ex: + self._ex = ex + return "error: " + str(ex) + + def delete(self, id=None): + #删除数据 + self.__GetConn() + try: + if id: + self.__OPT_WHERE = " WHERE id=%s" + self.__OPT_PARAM = (id, ) + sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE + self.__DB_CUR.execute(sql, self.__OPT_PARAM) + self.__close() + self.__DB_CONN.commit() + return self.__DB_CUR.rowcount + except Exception as ex: + return "error: " + str(ex) + + def execute(self,sql,param = ()): + #执行SQL语句返回受影响行 + if not self.__GetConn(): return self.__DB_ERR + try: + if param: + self.__OPT_PARAM = list(self.__to_tuple(param)) + result = self.__DB_CUR.execute(sql,self.__OPT_PARAM) + else: + result = self.__DB_CUR.execute(sql) + self.__DB_CONN.commit() + self.__close() + return result + except Exception as ex: + self._ex = ex + return ex + + + def query(self,sql,is_close=True,param=()): + #执行SQL语句返回数据集 + if not self.__GetConn(): return self.__DB_ERR + try: + if param: + self.__OPT_PARAM = list(self.__to_tuple(param)) + self.__DB_CUR.execute(sql,self.__OPT_PARAM) + else: + self.__DB_CUR.execute(sql) + result = self.__DB_CUR.fetchall() + #将元组转换成列表 + data = list(map(list,result)) + if is_close: self.__Close() + return data + except Exception as ex: + self._ex = ex + return ex + + + #关闭连接 + def __Close(self): + self.__DB_CUR.close() + self.__DB_CONN.close() + + def __close(self): + #清理条件属性 + self.__OPT_WHERE = "" + self.__OPT_FIELD = "*" + self.__OPT_ORDER = "" + self.__OPT_LIMIT = "" + self.__OPT_PARAM = () + + def close(self): + #释放资源 + try: + self.__DB_CUR.close() + self.__DB_CUR.close() + except: + pass diff --git a/class_v2/db_v2.py b/class_v2/db_v2.py new file mode 100644 index 00000000..2cf281ce --- /dev/null +++ b/class_v2/db_v2.py @@ -0,0 +1,392 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +import sqlite3 +import os,time,sys +os.chdir('/www/server/panel') +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import public + +class Sql(): + #------------------------------ + # 数据库操作类 For sqlite3 + #------------------------------ + __DB_FILE = None # 数据库文件 + __DB_CONN = None # 数据库连接对象 + __DB_TABLE = "" # 被操作的表名称 + __OPT_WHERE = "" # where条件 + __OPT_LIMIT = "" # limit条件 + __OPT_ORDER = "" # order条件 + __OPT_FIELD = "*" # field条件 + __OPT_PARAM = () # where值 + __LOCK = '/dev/shm/sqlite_lock.pl' + + def __init__(self): + self.__DB_FILE = 'data/default.db' + + def __enter__(self): + return self + + def __exit__(self,exc_type,exc_value,exc_trackback): + self.close() + + def __GetConn(self): + #取数据库对象 + try: + if self.__DB_CONN == None: + self.__DB_CONN = sqlite3.connect(self.__DB_FILE) + self.__DB_CONN.text_factory = str + except Exception as ex: + return "error: " + str(ex) + + def connect(self): + #连接数据库 + self.__GetConn() + return self + + def dbfile(self,name): + #设置数据库文件 + if name[0] == '/': + self.__DB_FILE = name + else: + self.__DB_FILE = 'data/' + name + '.db' + return self + + def table(self,table): + #设置表名 + self.__DB_TABLE = table + return self + + + def where(self,where,param): + #WHERE条件 + if where: + self.__OPT_WHERE = " WHERE " + where + self.__OPT_PARAM = self.__to_tuple(param) + return self + + def __to_tuple(self,param): + #将参数转换为tuple + if type(param) != tuple: + if type(param) == list: + param = tuple(param) + else: + param = (param,) + return param + + + def order(self,order): + #ORDER条件 + if len(order): + self.__OPT_ORDER = " ORDER BY "+order + return self + + + def limit(self,limit,offset = 0): + #LIMIT条件 + + if limit and not offset: + self.__OPT_LIMIT = " LIMIT {}".format(limit) + elif limit and offset: + self.__OPT_LIMIT = " LIMIT {},{}".format(offset,limit) + return self + + + def field(self,field): + #FIELD条件 + if len(field): + self.__OPT_FIELD = field + return self + + + def select(self): + #查询数据集 + self.__GetConn() + try: + self.__get_columns() + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + self.__OPT_WHERE + self.__OPT_ORDER + self.__OPT_LIMIT + result = self.__DB_CONN.execute(sql,self.__OPT_PARAM) + data = result.fetchall() + #构造字典系列 + if self.__OPT_FIELD != "*": + fields = self.__format_field(self.__OPT_FIELD.split(',')) + tmp = [] + for row in data: + i=0 + tmp1 = {} + for key in fields: + tmp1[key.strip('`')] = row[i] + i += 1 + tmp.append(tmp1) + del(tmp1) + data = tmp + del(tmp) + else: + #将元组转换成列表 + tmp = list(map(list,data)) + data = tmp + del(tmp) + self._close() + return data + except Exception as ex: + return "error: " + str(ex) + + def get(self): + self.__get_columns() + return self.select() + + def __format_field(self,field): + import re + fields = [] + for key in field: + s_as = re.search(r'\s+as\s+',key,flags=re.IGNORECASE) + if s_as: + as_tip = s_as.group() + key = key.split(as_tip)[1] + fields.append(key) + return fields + + def __get_columns(self): + if self.__OPT_FIELD == '*': + tmp_cols = self.query('PRAGMA table_info('+self.__DB_TABLE+')',()) + cols = [] + for col in tmp_cols: + if len(col) > 2: cols.append('`' + col[1] + '`') + if len(cols) > 0: self.__OPT_FIELD = ','.join(cols) + + def getField(self,keyName): + #取回指定字段 + try: + result = self.field(keyName).select() + if len(result) != 0: + return result[0][keyName] + return result + except: return None + + + def setField(self,keyName,keyValue): + #更新指定字段 + return self.save(keyName,(keyValue,)) + + + def find(self): + #取一行数据 + try: + result = self.limit("1").select() + if len(result) == 1: + return result[0] + return result + except:return None + + + def count(self): + #取行数 + key="COUNT(*)" + data = self.field(key).select() + try: + return int(data[0][key]) + except: + return 0 + + + def add(self,keys,param): + #插入数据 + self.write_lock() + self.__GetConn() + self.__DB_CONN.text_factory = str + try: + values="" + for key in keys.split(','): + values += "?," + values = values[0:len(values)-1] + sql = "INSERT INTO "+self.__DB_TABLE+"("+keys+") "+"VALUES("+values+")" + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + id = result.lastrowid + self._close() + self.__DB_CONN.commit() + self.rm_lock() + return id + except Exception as ex: + return "error: " + str(ex) + + #插入数据 + def insert(self,pdata): + if not pdata: return False + keys,param = self.__format_pdata(pdata) + return self.add(keys,param) + + #更新数据 + def update(self,pdata): + if not pdata: return False + keys,param = self.__format_pdata(pdata) + return self.save(keys,param) + + #构造数据 + def __format_pdata(self,pdata): + keys = pdata.keys() + keys_str = ','.join(keys) + param = [] + for k in keys: param.append(pdata[k]) + return keys_str,tuple(param) + + def addAll(self,keys,param): + #插入数据 + self.write_lock() + self.__GetConn() + self.__DB_CONN.text_factory = str + try: + values="" + for key in keys.split(','): + values += "?," + values = values[0:len(values)-1] + sql = "INSERT INTO "+self.__DB_TABLE+"("+keys+") "+"VALUES("+values+")" + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + self.rm_lock() + return True + except Exception as ex: + return "error: " + str(ex) + + def commit(self): + self._close() + self.__DB_CONN.commit() + + + def save(self,keys,param): + #更新数据 + self.write_lock() + self.__GetConn() + self.__DB_CONN.text_factory = str + try: + opt = "" + for key in keys.split(','): + opt += key + "=?," + opt = opt[0:len(opt)-1] + sql = "UPDATE " + self.__DB_TABLE + " SET " + opt+self.__OPT_WHERE + + #处理拼接WHERE与UPDATE参数 + tmp = list(self.__to_tuple(param)) + for arg in self.__OPT_PARAM: + tmp.append(arg) + self.__OPT_PARAM = tuple(tmp) + result = self.__DB_CONN.execute(sql,self.__OPT_PARAM) + self._close() + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + def delete(self,id=None): + #删除数据 + self.write_lock() + self.__GetConn() + try: + if id: + self.__OPT_WHERE = " WHERE id=?" + self.__OPT_PARAM = (id,) + sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE + result = self.__DB_CONN.execute(sql,self.__OPT_PARAM) + self._close() + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + + def execute(self,sql,param = ()): + #执行SQL语句返回受影响行 + self.write_lock() + self.__GetConn() + try: + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + #是否有锁 + def is_lock(self): + return + # n = 0 + # while os.path.exists(self.__LOCK): + # n+=1 + # if n > 100: + # self.rm_lock() + # break + # time.sleep(0.01) + #写锁 + def write_lock(self): + return + # self.is_lock() + # with open(self.__LOCK,'wb+') as f: + # f.close() + + #解锁 + def rm_lock(self): + return + # if os.path.exists(self.__LOCK): + # os.remove(self.__LOCK) + + def query(self,sql,param = ()): + #执行SQL语句返回数据集 + self.__GetConn() + try: + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + #将元组转换成列表 + data = list(map(list,result)) + return data + except Exception as ex: + return "error: " + str(ex) + + def create(self,name): + #创建数据表 + self.write_lock() + self.__GetConn() + script = public.readFile('data/' + name + '.sql') + result = self.__DB_CONN.executescript(script) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + + def fofile(self,filename): + #执行脚本 + self.write_lock() + self.__GetConn() + script = public.readFile(filename) + result = self.__DB_CONN.executescript(script) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + + def _close(self): + #清理条件属性 + self.__OPT_WHERE = "" + self.__OPT_FIELD = "*" + self.__OPT_ORDER = "" + self.__OPT_LIMIT = "" + self.__OPT_PARAM = () + + def is_connect(self): + #检查是否连接数据库 + if not self.__DB_CONN: + return False + return True + + + def close(self): + #释放资源 + try: + self.__DB_CONN.close() + self.__DB_CONN = None + except: + pass + diff --git a/class_v2/dk_db.py b/class_v2/dk_db.py new file mode 100644 index 00000000..9d6a1cea --- /dev/null +++ b/class_v2/dk_db.py @@ -0,0 +1,574 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import json +import os +import sys +import sqlite3 +import re + +os.chdir("/www/server/panel") +if not "class/" in sys.path: + sys.path.insert(0,"class/") + +import public + +class Sql(): + #------------------------------ + # 数据库操作类 For sqlite3 + #------------------------------ + __LOCK = "/dev/shm/sqlite_lock.pl" + __LAST_KEY = "BT-0x:" + __ENCRYPT_KEYS = ["password","salt","email","mysql_root","db_password"] + __DEFAULT_DB_PATH = os.path.join(public.get_panel_path(), "data/db/default.db") + + __DATABASES_PATH = os.path.join(public.get_panel_path(), "config/databases.json") + + + def __init__(self): + self.__DB_FILE = None # 数据库文件 + self.__DB_CONN = None # 数据库连接对象 + self.__DB_TABLE = "" # 被操作的表名称 + self.__OPT_WHERE = "" # where条件 + self.__OPT_LIMIT = "" # limit条件 + self.__OPT_ORDER = "" # order条件 + self.__OPT_FIELD = "*" # field条件 + self.__OPT_PARAM = () # where值 + self.__DB_NAME = "" # 数据库名称 + + self.__TB_INFO = {} # 表信息 + + def __enter__(self): + return self + + def __exit__(self,exc_type,exc_value,exc_trackback): + self.close() + + def __get_db_path(self): + if self.__DB_FILE is not None: + return + + if self.__DB_NAME: + # 有指定数据库名称的情况 + databases_dict = json.loads(public.readFile(self.__DATABASES_PATH)) + for db_name, tb_dict in databases_dict.items(): + if self.__DB_NAME in tb_dict: + self.__DB_FILE = os.path.join(public.get_panel_path(), "data/db", db_name) + self.__TB_INFO = tb_dict[self.__DB_NAME] + return + else: + # 未指定数据库名称的情况 + if not self.__DB_TABLE: + self.__DB_FILE = self.__DEFAULT_DB_PATH + return + try: + databases_dict = json.loads(public.readFile(self.__DATABASES_PATH)) + for db_name, tb_dict in databases_dict.items(): + if self.__DB_TABLE in tb_dict: + self.__DB_FILE = os.path.join(public.get_panel_path(), "data/db", db_name) + self.__TB_INFO = tb_dict[self.__DB_TABLE] + return + except Exception as err: + pass + + # 使用默认数据库 + self.__DB_FILE = self.__DEFAULT_DB_PATH + return + + def __GetConn(self): + #取数据库对象 + self.__get_db_path() + try: + if self.__DB_CONN == None: + if os.path.exists(self.__DB_FILE) and os.path.getsize(self.__DB_FILE) == 0: + os.remove(self.__DB_FILE) + self.__DB_CONN = sqlite3.connect(self.__DB_FILE) + self.__DB_CONN.text_factory = str + + if "sql" in self.__TB_INFO and self.__TB_INFO.get("sql"): + result = self.__DB_CONN.execute("SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?;", (self.__DB_TABLE,)) + if list(map(list,result))[0][0] == 0: + self.__DB_CONN.execute(self.__TB_INFO["sql"]) + except Exception as ex: + return "error: " + str(ex) + + def set_dbfile(self,path): + self.__DB_FILE = path + return self + + def connect(self): + #连接数据库 + self.__GetConn() + return self + + def dbfile(self,name: str): + if not name.endswith(".db"): # 兼容老的 + name += ".db" + #设置数据库文件 + self.__DB_FILE = os.path.join(public.get_panel_path(), "data", name) + return self + + def table(self,table): + #设置表名 + self.__DB_TABLE = table + return self + + def db(self,name): + # 设置数据库名称,用于判断数据库文件 + self.__DB_NAME = name + return self + + + def where(self,where,param): + #WHERE条件 + if where: + self.__OPT_WHERE = " WHERE " + where + self.__OPT_PARAM = self.__to_tuple(param) + return self + + def __to_tuple(self,param): + #将参数转换为tuple + if type(param) != tuple: + if type(param) == list: + param = tuple(param) + else: + param = (param,) + return param + + + def order(self,order): + #ORDER条件 + if len(order): + self.__OPT_ORDER = " ORDER BY "+order + return self + + + def limit(self,limit,offset = 0): + #LIMIT条件 + + if limit and not offset: + self.__OPT_LIMIT = " LIMIT {}".format(limit) + elif limit and offset: + self.__OPT_LIMIT = " LIMIT {},{}".format(offset,limit) + return self + + + def field(self,field): + #FIELD条件 + if len(field): + self.__OPT_FIELD = field + return self + + def query(self, sql, param=(), transfer_list=True): + # 执行SQL语句返回数据集 + self.__GetConn() + try: + result = self.__DB_CONN.execute(sql, self.__to_tuple(param)) + # self.log("result:" + str(result)) + if transfer_list: + # 将元组转换成列表 + data = list(map(list, result)) + else: + data = result + return data + except Exception as ex: + return "error: " + str(ex) + + def select(self, num=0): + # 查询数据集 + self.__GetConn() + try: + self.__get_columns() + sql = "SELECT " + self.__OPT_FIELD + " FROM " + self.__DB_TABLE + self.__OPT_WHERE + self.__OPT_ORDER + self.__OPT_LIMIT + result = self.__DB_CONN.execute(sql, self.__OPT_PARAM) + data = result.fetchall() + # 构造字典系列 + if self.__OPT_FIELD != "*": + fields = self.__format_field(self.__OPT_FIELD.split(',')) + tmp = [] + for row in data: + i = 0 + tmp1 = {} + for key in fields: + tmp1[key.strip('`')] = row[i] + i += 1 + tmp.append(tmp1) + del (tmp1) + data = tmp + del (tmp) + else: + # 将元组转换成列表 + tmp = list(map(list, data)) + data = tmp + del (tmp) + self._close() + data = self.de_crypt(data) + return data + except Exception as ex: + ex_str = str(ex) + re_column = re.compile(r"no such column:\s+(?P\S+)") + res = re_column.search(ex_str) + if res: + column = res.group("column") + if self.__TB_INFO: + for i in self.__TB_INFO.get("fields", []): + if isinstance(i, list) and len(i) == 6 and i[1] == column: + # 处理字符串类型默认值 + if i[2].lower() in ['text', 'varchar'] and i[4] == "": + i[4] = "''" + res = self.execute( + "ALTER TABLE {} ADD {} {} DEFAULT {}".format(self.__DB_TABLE, column, i[2], i[4])) + # 如果添加字段成功,且重试次数<3次,则重新查询 + if isinstance(res, int) and num < 3: + num += 1 + return self.select(num) + + if "disk I/O error" in ex_str: + return [] + + # 处理firewall_new表的异常 + if ("malformed database schema" in ex_str or "file is encrypted or is not a database" in ex_str) and self.__DB_TABLE in ['firewall_new']: + bak_file = "{}.{}".format(self.__DB_FILE, public.format_date("%Y%m%d_%H%M%S")) + public.ExecShell("mv -f {} {}".format(self.__DB_FILE, bak_file)) + return [] + + return [] + + def get(self): + self.__get_columns() + return self.select() + + def __format_field(self,field): + import re + fields = [] + for key in field: + s_as = re.search(r'\s+as\s+',key,flags=re.IGNORECASE) + if s_as: + as_tip = s_as.group() + key = key.split(as_tip)[1] + fields.append(key) + return fields + + def __get_columns(self): + if self.__OPT_FIELD == '*': + tmp_cols = self.query('PRAGMA table_info('+self.__DB_TABLE+')',()) + cols = [] + for col in tmp_cols: + if len(col) > 2: cols.append('`' + col[1] + '`') + if len(cols) > 0: self.__OPT_FIELD = ','.join(cols) + + def getField(self,keyName): + #取回指定字段 + try: + result = self.limit("1").field(keyName).select() + if len(result) != 0: + return result[0][keyName] + return result + except: return None + + + def setField(self,keyName,keyValue): + #更新指定字段 + return self.save(keyName,(keyValue,)) + + + def find(self): + #取一行数据 + try: + result = self.limit("1").select() + if len(result) == 1: + return result[0] + return result + except:return None + + + def count(self): + #取行数 + key="COUNT(*)" + data = self.field(key).select() + try: + return int(data[0][key]) + except: + return 0 + + + def add(self,keys,param): + #插入数据 + self.write_lock() + self.__GetConn() + self.__DB_CONN.text_factory = str + param = self.en_crypt(keys,param) + try: + values="" + for key in keys.split(','): + values += "?," + values = values[0:len(values)-1] + sql = "INSERT INTO "+self.__DB_TABLE+"("+keys+") "+"VALUES("+values+")" + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + id = result.lastrowid + self._close() + self.__DB_CONN.commit() + self.rm_lock() + return id + except Exception as ex: + raise public.PanelError("数据库插入出错:" + "error: " + str(ex)) + # return "error: " + str(ex) + + #插入数据 + def insert(self,pdata): + if not pdata: return False + keys,param = self.__format_pdata(pdata) + return self.add(keys,param) + + #更新数据 + def update(self,pdata): + if not pdata: return False + keys,param = self.__format_pdata(pdata) + return self.save(keys,param) + + #构造数据 + def __format_pdata(self,pdata): + keys = pdata.keys() + keys_str = ','.join(keys) + param = [] + for k in keys: param.append(pdata[k]) + return keys_str,tuple(param) + + def addAll(self,keys,param): + #插入数据 + self.write_lock() + self.__GetConn() + self.__DB_CONN.text_factory = str + param = self.en_crypt(keys,param) + try: + values="" + for key in keys.split(','): + values += "?," + values = values[0:len(values)-1] + sql = "INSERT INTO "+self.__DB_TABLE+"("+keys+") "+"VALUES("+values+")" + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + self.rm_lock() + return True + except Exception as ex: + return "error: " + str(ex) + + def commit(self): + self._close() + self.__DB_CONN.commit() + + def _encrypt(self,data): + import PluginLoader + # 加密数据 + if not isinstance(data,str): return data + if not data: return data + if data.startswith(self.__LAST_KEY): return data + + result = PluginLoader.db_encrypt(data) + if result['status'] == True: + return self.__LAST_KEY + result['msg'] + return data + + def _decrypt(self,data): + import PluginLoader + # 解密数据 + if not isinstance(data,str): return data + if not data: return data + if data.startswith(self.__LAST_KEY): + res = PluginLoader.db_decrypt(data[6:])['msg'] + return res + return data + + + def en_crypt(self,keys,param): + # 加密指定字段 + try: + if not param or not keys: return param + if not str(self.__DB_FILE).startswith(os.path.join(public.get_panel_path(), "data")): return param + + new_param = [] + keys_list = keys.split(',') + if isinstance(param,str): param = [param] + for i in range(len(keys_list)): + key = keys_list[i] + value = param[i] + if key in self.__ENCRYPT_KEYS: + value = self._encrypt(value) + new_param.append(value) + return tuple(new_param) + except: + return param + + def de_crypt(self,data): + # 解密字段 + try: + if not data: return data + if not str(self.__DB_FILE).startswith(os.path.join(public.get_panel_path(), "data")): return data + if isinstance(data,dict): + for key in data.keys(): + if not isinstance(data[key],str): continue + if data[key].startswith(self.__LAST_KEY): + data[key] = self._decrypt(data[key]) + elif isinstance(data,list): + for i in range(len(data)): + for key in data[i].keys(): + if not isinstance(data[i][key],str): continue + if data[i][key].startswith(self.__LAST_KEY): + data[i][key] = self._decrypt(data[i][key]) + elif isinstance(data,str): + if data.startswith(self.__LAST_KEY): + data = self._decrypt(data) + return data + except: + return data + + def save(self,keys,param): + #更新数据 + self.write_lock() + self.__GetConn() + self.__DB_CONN.text_factory = str + param = self.en_crypt(keys,param) + try: + opt = "" + for key in keys.split(','): + opt += key + "=?," + opt = opt[0:len(opt)-1] + sql = "UPDATE " + self.__DB_TABLE + " SET " + opt+self.__OPT_WHERE + + #处理拼接WHERE与UPDATE参数 + tmp = list(self.__to_tuple(param)) + for arg in self.__OPT_PARAM: + tmp.append(arg) + self.__OPT_PARAM = tuple(tmp) + result = self.__DB_CONN.execute(sql,self.__OPT_PARAM) + self._close() + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + raise public.PanelError("数据库保存出错:" + "error: " + str(ex)) + # return "error: " + str(ex) + + def delete(self,id=None): + #删除数据 + self.write_lock() + self.__GetConn() + try: + if id: + self.__OPT_WHERE = " WHERE id=?" + self.__OPT_PARAM = (id,) + sql = "DELETE FROM " + self.__DB_TABLE + self.__OPT_WHERE + result = self.__DB_CONN.execute(sql,self.__OPT_PARAM) + self._close() + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + + def execute(self,sql,param = ()): + #执行SQL语句返回受影响行 + self.write_lock() + self.__GetConn() + try: + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + def executemany(self, sql, param: list): + # 执行SQL语句返回受影响行 + self.write_lock() + self.__GetConn() + try: + result = self.__DB_CONN.executemany(sql, param) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + except Exception as ex: + return "error: " + str(ex) + + #是否有锁 + def is_lock(self): + return + # n = 0 + # while os.path.exists(self.__LOCK): + # n+=1 + # if n > 100: + # self.rm_lock() + # break + # time.sleep(0.01) + #写锁 + def write_lock(self): + return + # self.is_lock() + # with open(self.__LOCK,'wb+') as f: + # f.close() + + #解锁 + def rm_lock(self): + return + # if os.path.exists(self.__LOCK): + # os.remove(self.__LOCK) + + def query(self,sql,param = ()): + #执行SQL语句返回数据集 + self.__GetConn() + try: + result = self.__DB_CONN.execute(sql,self.__to_tuple(param)) + #将元组转换成列表 + data = list(map(list,result)) + return data + except Exception as ex: + return "error: " + str(ex) + + def create(self,name): + #创建数据表 + self.write_lock() + self.__GetConn() + script = public.readFile('data/' + name + '.sql') + result = self.__DB_CONN.executescript(script) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + + def fofile(self,filename): + #执行脚本 + self.write_lock() + self.__GetConn() + script = public.readFile(filename) + result = self.__DB_CONN.executescript(script) + self.__DB_CONN.commit() + self.rm_lock() + return result.rowcount + + def _close(self): + #清理条件属性 + self.__OPT_WHERE = "" + self.__OPT_FIELD = "*" + self.__OPT_ORDER = "" + self.__OPT_LIMIT = "" + self.__OPT_PARAM = () + + def is_connect(self): + #检查是否连接数据库 + if not self.__DB_CONN: + return False + return True + + + def close(self): + #释放资源 + try: + self.__DB_CONN.close() + self.__DB_CONN = None + except: + pass + diff --git a/class_v2/download_file_v2.py b/class_v2/download_file_v2.py new file mode 100644 index 00000000..b8e5da7c --- /dev/null +++ b/class_v2/download_file_v2.py @@ -0,0 +1,64 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import os,sys,public,json,time +class downloadFile: + logPath = 'data/speed.json' + timeoutCount = 0; + oldTime = 0; + writeTime = 0; + down_count = 0; + #下载文件 + def DownloadFile(self,url,filename): + try: + path = os.path.dirname(filename) + if not os.path.exists(path): os.makedirs(path) + import urllib,socket,ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except:pass + socket.setdefaulttimeout(30) + self.pre = 0; + self.oldTime = time.time(); + if sys.version_info[0] == 2: + urllib.urlretrieve(url,filename=filename,reporthook= self.DownloadHook) + else: + urllib.request.urlretrieve(url,filename=filename,reporthook= self.DownloadHook) + speed = self.GetSpeed() + speed['pre'] = 100; + speed['used'] = speed['total'] + self.WriteLogs(json.dumps(speed)); + except: + if self.timeoutCount > 5: return; + self.timeoutCount += 1 + time.sleep(5) + self.DownloadFile(url,filename) + + #下载文件进度回调 + def DownloadHook(self,count, blockSize, totalSize): + used = count * blockSize + pre1 = int((100.0 * used / totalSize)) + my_time = time.time() + if self.pre != pre1 or (my_time - self.writeTime) > 1: + dspeed = ((count -self.down_count) * blockSize) / (my_time - self.oldTime) + speed = {'name':public.GetMsg("DOWNLOAD_FILE"),'total':totalSize,'used':used,'pre':self.pre,'speed':dspeed} + self.WriteLogs(json.dumps(speed)) + self.pre = pre1 + self.writeTime = my_time + self.down_count = count + self.oldTime = my_time + + #取下载进度 + def GetSpeed(self): + speedLog = public.ReadFile(self.logPath) + if not speedLog: return {'name':public.GetMsg("DOWNLOAD_FILE"),'total':0,'used':0,'pre':0,'speed':0} + return json.loads(speedLog) + + #写输出日志 + def WriteLogs(self,logMsg): + public.WriteFile(self.logPath,logMsg) diff --git a/class_v2/fastcgi_client_two_v2.py b/class_v2/fastcgi_client_two_v2.py new file mode 100644 index 00000000..2322ea54 --- /dev/null +++ b/class_v2/fastcgi_client_two_v2.py @@ -0,0 +1,399 @@ +# Copyright (c) 2006 Allan Saddi +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. 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. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +# +# $Id$ +# +# Copyright (c) 2011 Vladimir Rusinov + +__author__ = 'Allan Saddi ' +__version__ = '$Revision$' +import sys +import select +import struct +import socket +import errno +import types + + + +__all__ = ['FCGIApp'] + +# Constants from the spec. +FCGI_LISTENSOCK_FILENO = 0 + +FCGI_HEADER_LEN = 8 + +FCGI_VERSION_1 = 1 + +FCGI_BEGIN_REQUEST = 1 +FCGI_ABORT_REQUEST = 2 +FCGI_END_REQUEST = 3 +FCGI_PARAMS = 4 +FCGI_STDIN = 5 +FCGI_STDOUT = 6 +FCGI_STDERR = 7 +FCGI_DATA = 8 +FCGI_GET_VALUES = 9 +FCGI_GET_VALUES_RESULT = 10 +FCGI_UNKNOWN_TYPE = 11 +FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE + +FCGI_NULL_REQUEST_ID = 0 + +FCGI_KEEP_CONN = 1 + +FCGI_RESPONDER = 1 +FCGI_AUTHORIZER = 2 +FCGI_FILTER = 3 + +FCGI_REQUEST_COMPLETE = 0 +FCGI_CANT_MPX_CONN = 1 +FCGI_OVERLOADED = 2 +FCGI_UNKNOWN_ROLE = 3 + +FCGI_MAX_CONNS = 'FCGI_MAX_CONNS' +FCGI_MAX_REQS = 'FCGI_MAX_REQS' +FCGI_MPXS_CONNS = 'FCGI_MPXS_CONNS' + +FCGI_Header = '!BBHHBx' +FCGI_BeginRequestBody = '!HB5x' +FCGI_EndRequestBody = '!LB3x' +FCGI_UnknownTypeBody = '!B7x' + +FCGI_BeginRequestBody_LEN = struct.calcsize(FCGI_BeginRequestBody) +FCGI_EndRequestBody_LEN = struct.calcsize(FCGI_EndRequestBody) +FCGI_UnknownTypeBody_LEN = struct.calcsize(FCGI_UnknownTypeBody) + +if __debug__: + import time + + # Set non-zero to write debug output to a file. + DEBUG = 0 + DEBUGLOG = '/www/server/panel/logs/fastcgi.log' + + def _debug(level, msg): + if DEBUG < level: + return + + try: + f = open(DEBUGLOG, 'a') + f.write('%sfcgi: %s\n' % (time.ctime()[4:-4], msg)) + f.close() + except: + pass + +def decode_pair(s, pos=0): + """ + Decodes a name/value pair. + + The number of bytes decoded as well as the name/value pair + are returned. + """ + nameLength = ord(s[pos]) + if nameLength & 128: + nameLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff + pos += 4 + else: + pos += 1 + + valueLength = ord(s[pos]) + if valueLength & 128: + valueLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff + pos += 4 + else: + pos += 1 + + name = s[pos:pos+nameLength] + pos += nameLength + value = s[pos:pos+valueLength] + pos += valueLength + + return (pos, (name, value)) + +def encode_pair(name, value): + """ + Encodes a name/value pair. + + The encoded string is returned. + """ + nameLength = len(name) + if nameLength < 128: + s = chr(nameLength).encode() + else: + s = struct.pack('!L', nameLength | 0x80000000) + + valueLength = len(value) + if valueLength < 128: + s += chr(valueLength).encode() + else: + s += struct.pack('!L', valueLength | 0x80000000) + + return s + name + value + +class Record(object): + """ + A FastCGI Record. + + Used for encoding/decoding records. + """ + def __init__(self, type=FCGI_UNKNOWN_TYPE, requestId=FCGI_NULL_REQUEST_ID): + self.version = FCGI_VERSION_1 + self.type = type + self.requestId = requestId + self.contentLength = 0 + self.paddingLength = 0 + self.contentData = '' + + def _recvall(sock, length): + """ + Attempts to receive length bytes from a socket, blocking if necessary. + (Socket may be blocking or non-blocking.) + """ + dataList = [] + recvLen = 0 + while length: + try: + data = sock.recv(length) + except socket.error as e: + if e[0] == errno.EAGAIN: + select.select([sock], [], []) + continue + else: + raise + if not data: # EOF + break + dataList.append(data) + dataLen = len(data) + recvLen += dataLen + length -= dataLen + return b''.join(dataList), recvLen + _recvall = staticmethod(_recvall) + + def read(self, sock): + """Read and decode a Record from a socket.""" + try: + header, length = self._recvall(sock, FCGI_HEADER_LEN) + except: + raise EOFError + + if length < FCGI_HEADER_LEN: + raise EOFError + + self.version, self.type, self.requestId, self.contentLength, \ + self.paddingLength = struct.unpack(FCGI_Header, header) + + if __debug__: _debug(9, 'read: fd = %d, type = %d, requestId = %d, ' + 'contentLength = %d' % + (sock.fileno(), self.type, self.requestId, + self.contentLength)) + + if self.contentLength: + try: + self.contentData, length = self._recvall(sock, + self.contentLength) + except: + raise EOFError + + if length < self.contentLength: + raise EOFError + + if self.paddingLength: + try: + self._recvall(sock, self.paddingLength) + except: + raise EOFError + + def _sendall(sock, data): + """ + Writes data to a socket and does not return until all the data is sent. + """ + length = len(data) + while length: + try: + sent = sock.send(data) + except socket.error as e: + if e[0] == errno.EAGAIN: + select.select([], [sock], []) + continue + else: + raise + data = data[sent:] + length -= sent + _sendall = staticmethod(_sendall) + + def write(self, sock): + """Encode and write a Record to a socket.""" + self.paddingLength = -self.contentLength & 7 + + if __debug__: _debug(9, 'write: fd = %d, type = %d, requestId = %d, ' + 'contentLength = %d' % + (sock.fileno(), self.type, self.requestId, + self.contentLength)) + + header = struct.pack(FCGI_Header, self.version, self.type, + self.requestId, self.contentLength, + self.paddingLength) + self._sendall(sock, header) + if self.contentLength: + self._sendall(sock, self.contentData) + if self.paddingLength: + self._sendall(sock, b'\x00'*self.paddingLength) + +class FCGIApp(object): + + def __init__(self, connect=None, host=None, port=None, filterEnviron=True): + if host is not None: + assert port is not None + connect=(host, port) + + self._connect = connect + self._filterEnviron = filterEnviron + + def __call__(self, environ, io, start_response=None): + # For sanity's sake, we don't care about FCGI_MPXS_CONN + # (connection multiplexing). For every request, we obtain a new + # transport socket, perform the request, then discard the socket. + # This is, I believe, how mod_fastcgi does things... + + sock = self._getConnection() + + # Since this is going to be the only request on this connection, + # set the request ID to 1. + requestId = 1 + + # Begin the request + rec = Record(FCGI_BEGIN_REQUEST, requestId) + rec.contentData = struct.pack(FCGI_BeginRequestBody, FCGI_RESPONDER, 0) + rec.contentLength = FCGI_BeginRequestBody_LEN + rec.write(sock) + + # Filter WSGI environ and send it as FCGI_PARAMS + if self._filterEnviron: + params = self._defaultFilterEnviron(environ) + else: + params = self._lightFilterEnviron(environ) + # TODO: Anything not from environ that needs to be sent also? + #return '200 OK',[],str(params),'' + self._fcgiParams(sock, requestId, params) + self._fcgiParams(sock, requestId, {}) + + # Transfer wsgi.input to FCGI_STDIN + content_length = int(environ.get('CONTENT_LENGTH') or 0) + s = '' + #io = StringIO(stdin) + while True: + if not io: break + chunk_size = min(content_length, 4096) + s = io.read(chunk_size) + content_length -= len(s) + rec = Record(FCGI_STDIN, requestId) + rec.contentData = s + rec.contentLength = len(s) + rec.write(sock) + if not s: break + # Empty FCGI_DATA stream + rec = Record(FCGI_DATA, requestId) + rec.write(sock) + return sock + + + def _getConnection(self): + if self._connect is not None: + # The simple case. Create a socket and connect to the + # application. + if isinstance(self._connect, str): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(self._connect) + elif hasattr(socket, 'create_connection'): + sock = socket.create_connection(self._connect) + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(self._connect) + return sock + + # To be done when I have more time... + raise NotImplementedError #, 'Launching and managing FastCGI programs not yet implemented' + + def _fcgiGetValues(self, sock, vars): + # Construct FCGI_GET_VALUES record + outrec = Record(FCGI_GET_VALUES) + data = [] + for name in vars: + data.append(encode_pair(name, '')) + data = ''.join(data) + outrec.contentData = data + outrec.contentLength = len(data) + outrec.write(sock) + + # Await response + inrec = Record() + inrec.read(sock) + result = {} + if inrec.type == FCGI_GET_VALUES_RESULT: + pos = 0 + while pos < inrec.contentLength: + pos, (name, value) = decode_pair(inrec.contentData, pos) + result[name] = value + return result + + def _fcgiParams(self, sock, requestId, params): + rec = Record(FCGI_PARAMS, requestId) + data = [] + for name,value in params.items(): + data.append(encode_pair(name.encode('latin-1'), value.encode('latin-1'))) + data = b''.join(data) + rec.contentData = data + rec.contentLength = len(data) + rec.write(sock) + + _environPrefixes = ['SERVER_', 'HTTP_', 'REQUEST_', 'REMOTE_', 'PATH_', + 'CONTENT_', 'DOCUMENT_', 'SCRIPT_'] + _environCopies = ['SCRIPT_NAME', 'QUERY_STRING', 'AUTH_TYPE'] + _environRenames = [] + + def _defaultFilterEnviron(self, environ): + result = {} + for n in environ.keys(): + iv = False + for p in self._environPrefixes: + if n.startswith(p): + result[n] = environ[n] + iv = True + if n in self._environCopies: + result[n] = environ[n] + iv = True + if n in self._environRenames: + result[self._environRenames[n]] = environ[n] + iv = True + if not iv: + result[n] = environ[n] + + return result + + def _lightFilterEnviron(self, environ): + result = {} + for n in environ.keys(): + if n.upper() == n: + result[n] = environ[n] + return result diff --git a/class_v2/fastcgi_client_v2.py b/class_v2/fastcgi_client_v2.py new file mode 100644 index 00000000..4940573c --- /dev/null +++ b/class_v2/fastcgi_client_v2.py @@ -0,0 +1,196 @@ +#!/usr/bin/python +# coding:utf-8 + +import socket +import random +import re +import sys + +class fastcgi_client: + __FCGI_VERSION = 1 + + __FCGI_ROLE_RESPONDER = 1 + __FCGI_ROLE_AUTHORIZER = 2 + __FCGI_ROLE_FILTER = 3 + + __FCGI_TYPE_BEGIN = 1 + __FCGI_TYPE_ABORT = 2 + __FCGI_TYPE_END = 3 + __FCGI_TYPE_PARAMS = 4 + __FCGI_TYPE_STDIN = 5 + __FCGI_TYPE_STDOUT = 6 + __FCGI_TYPE_STDERR = 7 + __FCGI_TYPE_DATA = 8 + __FCGI_TYPE_GETVALUES = 9 + __FCGI_TYPE_GETVALUES_RESULT = 10 + __FCGI_TYPE_UNKOWNTYPE = 11 + + __FCGI_HEADER_SIZE = 8 + + FCGI_STATE_SEND = 1 + FCGI_STATE_ERROR = 2 + FCGI_STATE_SUCCESS = 3 + + def __init__(self, host, port, timeout, keepalive): + self.host = host + self.port = port + self.timeout = timeout + if keepalive: + self.keepalive = 1 + else: + self.keepalive = 0 + self.sock = None + self.requests = dict() + + def __connect(self): + if self.port == None: + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + else: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.settimeout(self.timeout) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + if self.port != None: + self.sock.connect((self.host, int(self.port))) + else: + self.sock.connect(self.host) + except socket.error as msg: + self.sock.close() + self.sock = None + print(repr(msg)) + return False + return True + + def _chr(self,num): + if sys.version_info[0] == 3: + return chr(num).encode('latin1') + return chr(num) + + def _ord(self,sbody): + if sys.version_info[0] == 3: + return sbody + return ord(sbody) + + def __encodeFastCGIRecord(self, fcgi_type, content, requestid): + if type(content) == str: content = content.encode() + length = len(content) + + return self._chr(self.__FCGI_VERSION) \ + + self._chr(fcgi_type) \ + + self._chr((requestid >> 8) & 0xFF) \ + + self._chr(requestid & 0xFF) \ + + self._chr((length >> 8) & 0xFF) \ + + self._chr(length & 0xFF) \ + + self._chr(0) \ + + self._chr(0) \ + + content + + def __encodeNameValueParams(self, name, value): + nLen = len(str(name)) + vLen = len(str(value)) + record = b'' + if nLen < 128: + record += self._chr(nLen) + else: + record += self._chr((nLen >> 24) | 0x80) \ + + self._chr((nLen >> 16) & 0xFF) \ + + self._chr((nLen >> 8) & 0xFF) \ + + self._chr(nLen & 0xFF) + if vLen < 128: + record += self._chr(vLen) + else: + record += self._chr((vLen >> 24) | 0x80) \ + + self._chr((vLen >> 16) & 0xFF) \ + + self._chr((vLen >> 8) & 0xFF) \ + + self._chr(vLen & 0xFF) + return record + str(name).encode() + str(value).encode() + + def __decodeFastCGIHeader(self, stream): + header = dict() + header['version'] = self._ord(stream[0]) + header['type'] = self._ord(stream[1]) + header['requestId'] = (self._ord(stream[2]) << 8) + self._ord(stream[3]) + header['contentLength'] = (self._ord(stream[4]) << 8) + self._ord(stream[5]) + header['paddingLength'] = self._ord(stream[6]) + header['reserved'] = self._ord(stream[7]) + return header + + def __decodeFastCGIRecord(self): + header = self.sock.recv(int(self.__FCGI_HEADER_SIZE)) + if not header: + return False + else: + record = self.__decodeFastCGIHeader(header) + record['content'] = b'' + if 'contentLength' in record.keys(): + contentLength = int(record['contentLength']) + buffer = self.sock.recv(contentLength) + + while contentLength and buffer: + contentLength -= len(buffer) + record['content'] += buffer + if 'paddingLength' in record.keys(): + skiped = self.sock.recv(int(record['paddingLength'])) + return record + + def request(self, nameValuePairs={}, post=''): + if not self.__connect(): + raise Exception('Connection service failed, please check if the specified service is started!') + return + + requestId = random.randint(1, (1 << 16) - 1) + self.requests[requestId] = dict() + request = b"" + beginFCGIRecordContent = self._chr(0) \ + + self._chr(self.__FCGI_ROLE_RESPONDER) \ + + self._chr(self.keepalive) \ + + self._chr(0) * 5 + request += self.__encodeFastCGIRecord(self.__FCGI_TYPE_BEGIN, + beginFCGIRecordContent, requestId) + + paramsRecord = b'' + + if nameValuePairs: + v_items = sorted(nameValuePairs.items()) + for (name, value) in v_items: + paramsRecord += self.__encodeNameValueParams(name, value) + + + if paramsRecord: + request += self.__encodeFastCGIRecord(self.__FCGI_TYPE_PARAMS, paramsRecord, requestId) + + request += self.__encodeFastCGIRecord(self.__FCGI_TYPE_PARAMS, b'', requestId) + + if post: + request += self.__encodeFastCGIRecord(self.__FCGI_TYPE_STDIN, post, requestId) + request += self.__encodeFastCGIRecord(self.__FCGI_TYPE_STDIN, b'', requestId) + + self.sock.send(request) + self.requests[requestId]['state'] = self.FCGI_STATE_SEND + self.requests[requestId]['response'] = b'' + return self.__waitForResponse(requestId) + + def __waitForResponse(self, requestId): + while True: + response = self.__decodeFastCGIRecord() + if not response: + break + if response['type'] == self.__FCGI_TYPE_STDOUT \ + or response['type'] == self.__FCGI_TYPE_STDERR: + if response['type'] == self.__FCGI_TYPE_STDERR: + self.requests['state'] = self.FCGI_STATE_ERROR + if requestId == int(response['requestId']): + self.requests[requestId]['response'] += response['content'] + if response['type'] == self.FCGI_STATE_SUCCESS: + self.requests[requestId] + if self.requests[requestId]['response'].find(b'\r\n\r\n') != -1: + tmp = b"" + tmp2 = self.requests[requestId]['response'].split(b'\r\n\r\n') + for i in range(len(tmp2)): + if i == 0: continue + tmp += tmp2[i] + b'\r\n\r\n' + self.requests[requestId]['response'] = tmp.strip() + return self.requests[requestId]['response'] + + def __repr__(self): + return "fastcgi connect host:{} port:{}".format(self.host, self.port) diff --git a/class_v2/file_execute_deny_v2.py b/class_v2/file_execute_deny_v2.py new file mode 100644 index 00000000..4eab1dc3 --- /dev/null +++ b/class_v2/file_execute_deny_v2.py @@ -0,0 +1,279 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2020 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: zhwen +#------------------------------------------------------------------- + +#------------------------------ +# 禁止某个目录运行PHP +#------------------------------ +import public,re,os,json,shutil +from public.validate import Param + +class FileExecuteDeny: + + def _init_conf(self,website): + self.ng_website_conf = '/www/server/panel/vhost/nginx/{}.conf'.format(website) + self.ap_website_conf = '/www/server/panel/vhost/apache/{}.conf'.format(website) + self.ols_website_conf = '/www/server/panel/vhost/openlitespeed/detail/{}.conf'.format(website) + self.webserver = public.get_webserver() + + # 获取某个网站禁止运行的目录规则 + def get_file_deny(self,args): + ''' + # 添加某个网站禁止运行PHP + author: zhwen + :param args: website 网站名 str + :return: + ''' + # 校验参数 + try: + get=args + get.validate([ + Param('website').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self._init_conf(args.website) + if self.webserver == 'nginx': + data=self._get_nginx_file_deny() + elif self.webserver == 'apache': + data = self._get_apache_file_deny() + else: + data = self._get_ols_file_deny() + return public.return_message(0,0,data) + + def _get_nginx_file_deny(self): + conf = public.readFile(self.ng_website_conf) + if not conf: + return False + data = re.findall('BEGIN_DENY_.*',conf) + deny_name = [] + for i in data: + tmp = i.split('_') + if len(tmp) > 2: + deny_name.append('_'.join(tmp[2:])) + else: + deny_name.append(tmp[-1]) + result = [] + for i in deny_name: + reg = '#BEGIN_DENY_{}\n\\s*location\\s*\\~\\*\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i.replace("|",r"\|")) + re_tmp = re.search(reg,conf) + if re_tmp: + deny_directory = re_tmp.groups()[0] + deny_suffix = re_tmp.groups()[1] + result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix}) + return result + + def _get_apache_file_deny(self): + conf = public.readFile(self.ap_website_conf) + if not conf: + return False + data = re.findall('BEGIN_DENY_.*',conf) + deny_name = [] + for i in data: + tmp = i.split('_') + if len(tmp) > 2: + deny_name.append('_'.join(tmp[2:])) + else: + deny_name.append(tmp[-1]) + result = [] + for i in deny_name: + reg = '#BEGIN_DENY_{}\n\\s* 2: + deny_name.append('_'.join(tmp[2:])) + else: + deny_name.append(tmp[-1]) + result = [] + for i in deny_name: + reg = '#BEGIN_DENY_{}\n\\s*rules\\s*RewriteRule\\s*\\^(.*)\\.\\*.*\\((.*)\\)\\$'.format(i.replace("|",r"\|")) + deny_directory = re.search(reg, conf).groups()[0] + deny_suffix = re.search(reg,conf).groups()[1] + result.append({'name':i,'dir':deny_directory,'suffix':deny_suffix}) + return result + + def set_file_deny(self,args): + ''' + # 添加某个网站禁止运行PHP + author: zhwen + :param args: website 网站名 str + :param args: deny_name 规则名称 str + :param args: suffix 禁止访问的后续名 str + :param args: dir 禁止访问的目录 str + :param args: deny_name 规则名称 + :param args: act 操作方法 + :return: + ''' + # 校验参数 + try: + get=args + get.validate([ + Param('deny_name').String(), + Param('suffix').String(), + Param('dir').String(), + Param('act').String(), + Param('website').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + tmp = self._check_args(args) + if tmp: + return tmp + deny_name = args.deny_name + if not re.match(r"^\w+$",deny_name): return public.return_message(-1,0,'The rule name can only be composed of letters, numbers, and underscores!') + dir = args.dir + suffix = args.suffix + website = args.website + if suffix[-1] == "|": + suffix = suffix[:-1] + self._init_conf(website) + conf = public.readFile(self.ng_website_conf) + if not conf: + return public.return_message(-1,0,False) + data = re.findall('BEGIN_DENY_.*',conf) + exist_deny_name = [i.split('_')[-1] for i in data] + if args.act == 'edit': + if deny_name not in exist_deny_name: + return public.return_message(-1,0, 'The specify rule name is not exists! [ {} ]'.format(deny_name)) + self.del_file_deny(args) + else: + if deny_name in exist_deny_name: + return public.return_message(-1,0,'The specify rule name is already exists! [ {} ]'.format(deny_name)) + self._set_nginx_file_deny(deny_name,dir,suffix) + self._set_apache_file_deny(deny_name,dir,suffix) + self._set_ols_file_deny(deny_name,dir,suffix) + public.serviceReload() + return public.return_message(0,0,'Setup successfully!') + + def _set_nginx_file_deny(self,name,dir=None,suffix=None): + conf = public.readFile(self.ng_website_conf) + if not conf: + return False + if not dir and not suffix: + reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\n'.format(n=name) + conf = re.sub(reg,'',conf) + else: + if dir[0] != '/':dir = '/'+dir + if dir[-1] != '/':dir = dir+'/' + new = ''' + #BEGIN_DENY_%s + location ~* ^%s.*.(%s)$ { + deny all; + } + #END_DENY_%s +''' % (name,dir,suffix,name) + if '#BEGIN_DENY_{}\n'.format(name) in conf: + return True + conf = re.sub('#ERROR-PAGE-END','#ERROR-PAGE-END'+new,conf) + public.writeFile(self.ng_website_conf,conf) + return True + + def _set_apache_file_deny(self,name,dir=None,suffix=None): + conf = public.readFile(self.ap_website_conf) + if not conf: + return False + if not dir and not suffix: + reg = '\\s*#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}'.format(n=name) + conf = re.sub(reg,'',conf) + else: + if dir[0] != '/':dir = '/'+dir + if dir[-1] != '/':dir = dir+'/' + new = r''' + #BEGIN_DENY_{n} + + Order allow,deny + Deny from all + + #END_DENY_{n} +'''.format(n=name,d=dir,s=suffix) + if '#BEGIN_DENY_{}'.format(name) in conf: + return True + conf = re.sub(r'#DENY\s*FILES',new+'\n #DENY FILES',conf) + public.writeFile(self.ap_website_conf,conf) + return True + + def _set_ols_file_deny(self,name,dir=None,suffix=None): + conf = public.readFile(self.ols_website_conf) + if not conf: + return False + if not dir and not suffix: + reg = '#BEGIN_DENY_{n}\n(.|\n)*#END_DENY_{n}\\s*'.format(n=name) + conf = re.sub(reg,'',conf) + else: + new = r''' + #BEGIN_DENY_{n} + rules RewriteRule ^{d}.*\.({s})$ - [F,L] + #END_DENY_{n} +'''.format(n=name,d=dir,s=suffix) + if '#BEGIN_DENY_{}'.format(name) in conf: + return True + conf = re.sub(r'autoLoadHtaccess\s*1','autoLoadHtaccess 1'+new,conf) + public.writeFile(self.ols_website_conf,conf) + return True + + # 删除某个网站禁止运行PHP + def del_file_deny(self,args): + ''' + # 添加某个网站禁止运行PHP + author: zhwen + :param args: website 网站名 str + :param args: deny_name 规则名称 str + :return: + ''' + # 校验参数 + try: + get=args + get.validate([ + Param('deny_name').String(), + Param('website').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self._init_conf(args.website) + deny_name = args.deny_name + self._set_nginx_file_deny(deny_name) + self._set_apache_file_deny(deny_name) + self._set_ols_file_deny(deny_name) + public.serviceReload() + return public.return_message(0,0,'Successfully deleted!') + + # 检查传入参数 + def _check_args(self,args): + if hasattr(args,'deny_name'): + if len(args.deny_name) < 3: + return public.return_message(-1,0, 'Rule name needs to be greater than 3 bytes') + if hasattr(args,'suffix'): + if not args.suffix: + return public.return_message(-1,0, 'File suffix cannot be empty') + if hasattr(args,'dir'): + if not args.dir: + return public.return_message(-1,0, 'Directory cannot be empty') diff --git a/class_v2/files_v2.py b/class_v2/files_v2.py new file mode 100644 index 00000000..4e1c9174 --- /dev/null +++ b/class_v2/files_v2.py @@ -0,0 +1,3333 @@ +#!/usr/bin/env python +#coding:utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +from base64 import b64encode +import sys +import os +import public +import time +import json +import pwd +import cgi +import shutil +import re +import sqlite3 +from BTPanel import session, request +from public.validate import Param + + +class files: + run_path = None + path_permission_list = list() + path_permission_exclude_list = list() + file_permission_list = list() + sqlite_connection = None + download_list = None + download_is_rm = None + recycle_list = [] + download_token_list = None + # 检查敏感目录 + + def CheckDir(self, path): + path = path.replace('//', '/') + if path[-1:] == '/': + path = path[:-1] + + nDirs = ('', + '/', + '/*', + '/www', + '/root', + '/boot', + '/bin', + '/etc', + '/home', + '/dev', + '/sbin', + '/var', + '/usr', + '/tmp', + '/sys', + '/proc', + '/media', + '/mnt', + '/opt', + '/lib', + '/srv', + '/selinux', + '/www/server', + '/www/server/data', + '/www/.Recycle_bin', + public.GetConfigValue('logs_path'), + public.GetConfigValue('setup_path')) + + return not path in nDirs + + # 网站文件操作前置检测 + def site_path_check(self, get): + try: + if not 'site_id' in get: + return True + if not self.run_path: + self.run_path, self.path, self.site_name = self.GetSiteRunPath( + get.site_id) + if 'path' in get: + if get.path.find(self.path) != 0: + return False + if 'sfile' in get: + if get.sfile.find(self.path) != 0: + return False + if 'dfile' in get: + if get.dfile.find(self.path) != 0: + return False + return True + except: + return True + + # 网站目录后续安全处理 + def site_path_safe(self, get): + try: + if not 'site_id' in get: + return True + run_path, path, site_name = self.GetSiteRunPath(get.site_id) + if not os.path.exists(run_path): + os.makedirs(run_path) + ini_path = run_path + '/.user.ini' + if os.path.exists(ini_path): + return True + sess_path = '/www/php_session/%s' % site_name + if not os.path.exists(sess_path): + os.makedirs(sess_path) + ini_conf = '''open_basedir={}/:/tmp/:/proc/:{}/ +session.save_path={}/ +session.save_handler = files'''.format(path, sess_path, sess_path) + public.writeFile(ini_path, ini_conf) + public.ExecShell("chmod 644 %s" % ini_path) + public.ExecShell("chdir +i %s" % ini_path) + return True + except: + return False + + # 取当站点前运行目录 + def GetSiteRunPath(self, site_id): + try: + find = public.M('sites').where( + 'id=?', (site_id,)).field('path,name').find() + siteName = find['name'] + sitePath = find['path'] + if public.get_webserver() == 'nginx': + filename = public.get_vhost_path() + '/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] + else: + filename = public.get_vhost_path() + '/apache/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n' + tmp1 = re.search(rep, conf) + if tmp1: + path = tmp1.groups()[0] + return path, sitePath, siteName + except: + return sitePath, sitePath, siteName + + # 检测文件名 + def CheckFileName(self, filename): + nots = ['\\', '&', '*', '|', ';', '"', "'", '<', '>'] + if filename.find('/') != -1: + filename = filename.split('/')[-1] + for n in nots: + if n in filename: + return False + return True + + # 名称输出过滤 + def xssencode(self, text): + list = ['<', '>'] + ret = [] + for i in text: + if i in list: + i = '' + ret.append(i) + str_convert = ''.join(ret) + if sys.version_info[0] == 3: + import html + text2 = html.escape(str_convert, quote=True) + else: + text2 = cgi.escape(str_convert, quote=True) + + reps = {'&':'&'} + for rep in reps.keys(): + if text2.find(rep) != -1: text2 = text2.replace(rep,reps[rep]) + return text2 + + # 名称输入系列化 + def xssdecode(self,text): + try: + cs = {""":'"',"'":"'"} + for c in cs.keys(): + text = text.replace(c,cs[c]) + + str_convert = text + if sys.version_info[0] == 3: + import html + text2 = html.unescape(str_convert) + else: + text2 = cgi.unescape(str_convert) + return text2 + except: + return text + + # 上传文件 + def UploadFile(self, get): + from werkzeug.utils import secure_filename + from BTPanel import request + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if not os.path.exists(get.path): + os.makedirs(get.path) + f = request.files['zunfile'] + filename = os.path.join(get.path, f.filename) + if sys.version_info[0] == 2: + filename = filename.encode('utf-8') + s_path = get.path + if os.path.exists(filename): + s_path = filename + p_stat = os.stat(s_path) + f.save(filename) + os.chown(filename, p_stat.st_uid, p_stat.st_gid) + os.chmod(filename, p_stat.st_mode) + public.WriteLog('TYPE_FILE', 'FILE_UPLOAD_SUCCESS', + (filename, get['path'])) + return public.returnMsg(True, 'FILE_UPLOAD_SUCCESS') + + def f_name_check(self,filename): + ''' + @name 文件名检测2 + @author hwliang<2021-03-16> + @param filename 文件名 + @return bool + ''' + f_strs = [';','&','<','>'] + for fs in f_strs: + if filename.find(fs) != -1: + return False + return True + + # 上传前检查文件是否存在 + def upload_file_exists(self,args): + ''' + @name 上传前检查文件是否存在 + @author hwliang<2021-11-3> + @param filename 文件名 + @return dict + ''' + filename = args.filename.strip() + if not os.path.exists(filename): + return public.returnMsg(False,'File does not exist!') + file_info = {} + _stat = os.stat(filename) + file_info['size'] = _stat.st_size + file_info['mtime'] = int(_stat.st_mtime) + file_info['isfile'] = os.path.isfile(filename) + return public.returnMsg(True,file_info) + + + def get_real_len(self,string): + ''' + @name 获取含中文的字符串字精确长度 + @author hwliang<2021-11-3> + @param string + @return int + ''' + real_len = len(string) + for s in string: + if '\u2E80' <= s <= '\uFE4F': + real_len += 1 + return real_len + + # 上传文件2 + def upload(self, args): + if not 'f_name' in args: + args.f_name = request.form.get('f_name') + args.f_path = request.form.get('f_path') + args.f_size = request.form.get('f_size') + args.f_start = request.form.get('f_start') + + if sys.version_info[0] == 2: + args.f_name = args.f_name.encode('utf-8') + args.f_path = args.f_path.encode('utf-8') + + try: + if self.get_real_len(args.f_name) > 256: return public.return_msg_gettext(False,'The file name contains more than 256 bytes') + except: + pass + + if not self.f_name_check(args.f_name): return public.return_msg_gettext(False,'No special characters can be included in the file name!') + + if args.f_path == '/': + return public.return_msg_gettext(False,'Cannot upload files to the system document root!') + + if args.f_name.find('./') != -1 or args.f_path.find('./') != -1: + return public.return_msg_gettext(False, 'Wrong parameter') + + if not os.path.exists(args.f_path): + os.makedirs(args.f_path, 493) + if not 'dir_mode' in args or not 'file_mode' in args: + self.set_mode(args.f_path) + + save_path = os.path.join( + args.f_path, args.f_name + '.' + str(int(args.f_size)) + '.upload.tmp') + d_size = 0 + if os.path.exists(save_path): + d_size = os.path.getsize(save_path) + + if d_size != int(args.f_start): + return d_size + + try: + f = open(save_path, 'ab') + if 'b64_data' in args: + import base64 + b64_data = base64.b64decode(args.b64_data) + f.write(b64_data) + else: + upload_files = request.files.getlist("blob") + for tmp_f in upload_files: + f.write(tmp_f.read()) + f.close() + except Exception as ex: + ex = str(ex) + if ex.find('No space left on device') != -1: + return public.fail_v2('Not enough disk space') + + f_size = os.path.getsize(save_path) + if f_size != int(args.f_size): + return f_size + + new_name = os.path.join(args.f_path, args.f_name) + if os.path.exists(new_name): + if new_name.find('.user.ini') != -1: + public.ExecShell("chattr -i " + new_name) + try: + os.remove(new_name) + except: + public.ExecShell("rm -f %s" % new_name) + + os.renames(save_path, new_name) + if 'dir_mode' in args and 'file_mode' in args: + mode_tmp1 = args.dir_mode.split(',') + public.set_mode(args.f_path, mode_tmp1[0]) + public.set_own(args.f_path, mode_tmp1[1]) + mode_tmp2 = args.file_mode.split(',') + public.set_mode(new_name, mode_tmp2[0]) + public.set_own(new_name, mode_tmp2[1]) + + else: + self.set_mode(new_name) + + if new_name.find('.user.ini') != -1: + public.ExecShell("chattr +i " + new_name) + + public.write_log_gettext('File manager', 'Successfully uploaded [ {} ] !',(new_name,), + (args.f_name, args.f_path)) + + return public.success_v2('Successfully uploaded!') + + # 设置文件和目录权限 + def set_mode(self, path): + if path[-1] == '/': path = path[:-1] + s_path = os.path.dirname(path) + p_stat = os.stat(s_path) + os.chown(path,p_stat.st_uid,p_stat.st_gid) + if os.path.isfile(path): + os.chmod(path, 0o644) + else: + os.chmod(path,p_stat.st_mode) + + # 是否包含composer.json + def is_composer_json(self,path): + if os.path.exists(path + '/composer.json'): + return '1' + return '0' + + def __check_favorite(self,filepath,favorites_info): + for favorite in favorites_info: + if filepath == favorite['path']: + return '1' + return '0' + + def __get_topping_data(self): + """ + @获取置顶配置 + """ + data = {} + conf_file = '{}/data/toping.json'.format(public.get_panel_path()) + try : + if os.path.exists(conf_file): + data = json.loads(public.readFile(conf_file)) + except:pass + return data + + def __check_topping(self,filepath,top_info): + """ + @name 检测文件或者目录是否置顶 + @param filepath: 文件路径 + """ + if filepath in top_info: + return '1' + import html + filepath = html.unescape(filepath) + if filepath in top_info: + return '1' + return '0' + + + def __check_share(self,filename): + if self.download_token_list == None: + self.download_token_list = {} + my_table = 'download_token' + download_list = public.M(my_table).field('id,filename').select() + for k in download_list: + self.download_token_list[k['filename']] = k['id'] + + return str(self.download_token_list.get(filename,'0')) + + + def __filename_flater(self,filename): + ms = {";":""} + for m in ms.keys(): + filename = filename.replace(m,ms[m]) + return filename + + # 取文件/目录列表 + def GetDir(self, get): + if not hasattr(get, 'path'): + # return public.returnMsg(False,'错误的参数!') + get.path = public.get_site_path() #'/www/wwwroot' + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if get.path == '': + get.path = '/www' + + # 转换包含~的路径 + if get.path.find('~') != -1: + get.path = os.path.expanduser(get.path) + + get.path = self.xssdecode(get.path) + if not os.path.exists(get.path): + get.path = public.get_site_path() + #return public.ReturnMsg(False, '指定目录不存在!') + if os.path.basename(get.path) == '.Recycle_bin': + return public.return_message(-1,0,'Recovery failed!') + if not os.path.isdir(get.path): + get.path = os.path.dirname(get.path) + + if not os.path.isdir(get.path): + return public.return_message(-1,0,'This is not a directory') + + import pwd + dirnames = [] + filenames = [] + + search = None + if hasattr(get, 'search'): + search = get.search.strip().lower() + public.set_search_history('files','get_list',search) + if hasattr(get, 'all'): + return public.return_message(0,0,self.SearchFiles(get)) + + # 包含分页类 + import page + # 实例化分页类 + page = page.Page() + info = {} + info['count'] = self.GetFilesCount(get.path, search) + info['row'] = 500 + if 'disk' in get: + if get.disk == 'true': info['row'] = 2000 + if 'share' in get and get.share: + info['row'] = 5000 + info['p'] = 1 + if hasattr(get, 'p'): + try: + info['p'] = int(get['p']) + except: + info['p'] = 1 + + info['uri'] = {} + info['return_js'] = '' + if hasattr(get, 'tojs'): + info['return_js'] = get.tojs + if hasattr(get, 'showRow'): + info['row'] = int(get.showRow) + + # 获取分页数据 + data = {} + data['PAGE'] = page.GetPage(info, '1,2,3,4,5,6,7,8') + + i = 0 + n = 0 + + top_data = self.__get_topping_data() + data['STORE'] = self.get_files_store(None) + data['FILE_RECYCLE'] = os.path.exists('data/recycle_bin.pl') + + if not hasattr(get, 'reverse'): get.reverse = 'False' + if not hasattr(get, 'sort'): get.sort = 'name' + reverse = bool(get.reverse) + if get.reverse == 'False': + reverse = False + for file_info in self.__list_dir(get.path, get.sort, reverse): + filename = os.path.join(get.path, file_info[0]) + if search: + if file_info[0].lower().find(search) == -1: + continue + i += 1 + if n >= page.ROW: + break + if i < page.SHIFT: + continue + if not os.path.exists(filename) and not os.path.islink(filename): continue + file_info = self.__format_stat(filename, get.path) + if not file_info: continue + favorite = self.__check_favorite(filename, data['STORE']) + r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(file_info['mtime']) + ';' + str( + file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link']+';'\ + + self.get_download_id(filename) + ';' + self.is_composer_json(filename)+';'\ + + favorite+';'+self.__check_share(filename) + if os.path.isdir(filename): + dirnames.append(r_file) + else: + filenames.append(r_file) + n += 1 + + data['DIR'] = dirnames + data['FILES'] = filenames + data['PATH'] = str(get.path) + + #2022-07-29,增加置顶排序 + tmp_dirs = [] + for i in range(len(data['DIR'])): + filepath = os.path.join(data['PATH'] , data['DIR'][i].split(';')[0]) + toping = self.__check_topping(filepath,top_data) + info = data['DIR'][i] + ';' + self.get_file_ps(filepath)+';'+toping + if toping == '1': + tmp_dirs.insert(0, info) + else: + tmp_dirs.append(info) + + tmp_files = [] + for i in range(len(data['FILES'])): + filepath = os.path.join(data['PATH'] , data['FILES'][i].split(';')[0]) + toping = self.__check_topping(filepath,top_data) + info = data['FILES'][i] + ';' + self.get_file_ps(filepath)+';'+toping + if toping == '1': + tmp_files.insert(0, info) + else: + tmp_files.append(info) + data['DIR'] = tmp_dirs + data['FILES'] = tmp_files + + if hasattr(get, 'disk'): + import system + data['DISK'] = system.system().GetDiskInfo() + + data['dir_history'] = public.get_dir_history('files','GetDirList') + data['search_history'] = public.get_search_history('files','get_list') + public.set_dir_history('files','GetDirList',data['PATH']) + + return public.return_message(0,0,data) + + + def get_file_ps(self,filename): + ''' + @name 获取文件或目录备注 + @author hwliang<2020-10-22> + @param filename 文件或目录全路径 + @return string + ''' + + ps_path = public.get_panel_path() + '/data/files_ps' + f_key1 = '/'.join((ps_path,public.md5(filename))) + if os.path.exists(f_key1): + return public.readFile(f_key1) + + f_key2 = '/'.join((ps_path,public.md5(os.path.basename(filename)))) + if os.path.exists(f_key2): + return public.readFile(f_key2) + + pss = { + '/www/server/data':'MySQL data storage directory!', + '/www/server/mysql':'MySQL program directory', + '/www/server/redis':'Redis program directory', + '/www/server/mongodb':'MongoDB program directory', + '/www/server/nvm':'PM2/NVM/NPM program directory', + '/www/server/pass':'Website Basic Auth authentication password storage directory', + '/www/server/speed':'Website speed plugin directory', + '/www/server/docker':'Docker and data directory', + '/www/server/total':'Website Statistics Directory', + '/www/server/btwaf':'WAF directory', + '/www/server/pure-ftpd':'ftp program directory', + '/www/server/phpmyadmin':'phpMyAdmin program directory', + '/www/server/rar':'rar extension library directory, after deleting, it will lose support for RAR compressed files', + '/www/server/stop':'Website disabled page directory, please do not delete!', + '/www/server/nginx':'Nginx program directory', + '/www/server/apache':'Apache program directory', + '/www/server/cron':'Cron script and log directory', + '/www/server/php':'All interpreters of PHP versions are in this directory', + '/www/server/tomcat':'Tomcat program directory', + '/www/php_session':'PHP-SESSION Quarantine directory', + '/proc': 'System process directory', + '/dev': 'System Device Catalog', + '/sys': 'System call directory', + '/tmp': 'System temporary file directory', + '/var/log': 'System log directory', + '/var/run': 'System operation log directory', + '/var/spool': 'System queue directory', + '/var/lock': 'System lock directory', + '/var/mail': 'System mail directory', + '/mnt': 'System mount directory', + '/media': 'Multimedia catalog', + '/dev/shm': 'Shared memory directory', + '/lib': 'System dynamic library directory', + '/lib64': 'System dynamic library directory', + '/lib32': 'System dynamic library directory', + '/usr/lib': 'System dynamic library directory', + '/usr/lib64': 'System dynamic library directory', + '/usr/local/lib': 'System dynamic library directory', + '/usr/local/lib64': 'System dynamic library directory', + '/usr/local/libexec': 'System dynamic library directory', + '/usr/local/sbin': 'System script directory', + '/usr/local/bin': 'System script directory' + } + if filename in pss: return "PS:" + pss[filename] + + if not self.recycle_list: self.recycle_list = public.get_recycle_bin_list() + if filename + '/' in self.recycle_list: return 'PS: Recycle Bin Directory' + if filename in self.recycle_list: return 'PS: Recycle Bin Directory' + return '' + + + def set_file_ps(self,args): + ''' + @name 设置文件或目录备注 + @author hwliang<2020-10-22> + @param filename 文件或目录全路径 + @param ps_type 备注类型 0.完整路径 1.文件名称 + @param ps_body 备注内容 + @return dict + ''' + filename = args.filename.strip() + ps_type = int(args.ps_type) + ps_body = public.xssencode2(args.ps_body) + ps_path = public.get_panel_path() + '/data/files_ps' + if not os.path.exists(ps_path): + os.makedirs(ps_path,384) + if ps_type == 1: + f_name = os.path.basename(filename) + else: + f_name = filename + ps_key = public.md5(f_name) + + f_key = '/'.join((ps_path,ps_key)) + if ps_body: + public.writeFile(f_key,ps_body) + public.write_log_gettext('File manager','Set the file name [{}], notes: {}',(f_name,ps_body)) + else: + if os.path.exists(f_key): + os.remove(f_key) + public.write_log_gettext('File manager','Clear file notes [{}]',(f_name)) + return public.return_msg_gettext(True,'Setup successfully!') + + + + def check_file_sort(self,sort): + """ + @校验排序字段 + """ + slist = ['name','size','mtime','accept','user'] + if sort in slist: return sort + return 'name' + + def __list_dir(self, path, my_sort='name', reverse=False): + ''' + @name 获取文件列表,并排序 + @author hwliang<2020-08-01> + @param path 路径 + @param my_sort 排序字段 + @param reverse 是否降序 + @param list + ''' + if not os.path.exists(path): + return [] + py_v = sys.version_info[0] + tmp_files = [] + + for f_name in os.listdir(path): + try: + if py_v == 2: + f_name = f_name.encode('utf-8') + else: + f_name.encode('utf-8') + + #使用.join拼接效率更高 + filename = "/".join((path,f_name)) + sort_key = 1 + sort_val = None + if not os.path.islink(filename): + #此处直接做异常处理比先判断文件是否存在更高效 + if my_sort == 'name': + sort_key = 0 + elif my_sort == 'size': + sort_val = os.stat(filename).st_size + elif my_sort == 'mtime': + sort_val = os.stat(filename).st_mtime + elif my_sort == 'accept': + sort_val = os.stat(filename).st_mode + elif my_sort == 'user': + sort_val = os.stat(filename).st_uid + except Exception as err: + continue + #使用list[tuple]排序效率更高 + tmp_files.append((f_name,sort_val)) + try: + tmp_files = sorted(tmp_files, key=lambda x: x[sort_key], reverse=reverse) + except:pass + return tmp_files + + def __format_stat(self, filename, path): + try: + stat = self.__get_stat(filename, path) + if not stat: + return None + tmp_stat = stat.split(';') + file_info = {'name': self.xssencode(tmp_stat[0].replace('/', '')), 'size': int(tmp_stat[1]), 'mtime': int( + tmp_stat[2]), 'accept': int(tmp_stat[3]), 'user': tmp_stat[4], 'link': tmp_stat[5]} + return file_info + except: + return None + + def SearchFiles(self, get): + if not hasattr(get, 'path'): + get.path = public.get_site_path() + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if not os.path.exists(get.path): + get.path = '/www' + search = get.search.strip().lower() + my_dirs = [] + my_files = [] + count = 0 + max = 3000 + for d_list in os.walk(get.path): + if count >= max: + break + for d in d_list[1]: + if count >= max: + break + d = self.xssencode(d) + if d.lower().find(search) != -1: + filename = d_list[0] + '/' + d + if not os.path.exists(filename): + continue + my_dirs.append(self.__get_stat(filename, get.path)) + count += 1 + + for f in d_list[2]: + if count >= max: + break + f = self.xssencode(f) + if f.lower().find(search) != -1: + filename = d_list[0] + '/' + f + if not os.path.exists(filename): + continue + my_files.append(self.__get_stat(filename, get.path)) + count += 1 + data = {} + data['DIR'] = sorted(my_dirs) + data['FILES'] = sorted(my_files) + data['PATH'] = str(get.path) + data['PAGE'] = public.get_page( + len(my_dirs) + len(my_files), 1, max, 'GetFiles')['page'] + data['STORE'] = self.get_files_store(None) + return data + + def __get_stat(self, filename, path=None): + if os.path.islink(filename) and not os.path.exists(filename): + accept = "0" + mtime = "0" + user = "0" + size = "0" + else: + stat = os.stat(filename) + accept = str(oct(stat.st_mode)[-3:]) + mtime = str(int(stat.st_mtime)) + user = '' + try: + user = pwd.getpwuid(stat.st_uid).pw_name + except: + user = str(stat.st_uid) + size = str(stat.st_size) + link = '' + down_url = self.get_download_id(filename) + if os.path.islink(filename): + link = ' -> ' + os.readlink(filename) + tmp_path = (path + '/').replace('//', '/') + if path and tmp_path != '/': + filename = filename.replace(tmp_path, '',1) + favorite = self.__check_favorite(filename, self.get_files_store(None)) + return filename + ';' + size + ';' + mtime + ';' + accept + ';' + user + ';' + link+';'+ down_url+';'+ \ + self.is_composer_json(filename)+';'+favorite+';'+self.__check_share(filename) + + #获取指定目录下的所有视频或音频文件 + def get_videos(self,args): + path = args.path.strip() + v_data = [] + if not os.path.exists(path): return v_data + import mimetypes + for fname in os.listdir(path): + try: + filename = os.path.join(path,fname) + if not os.path.exists(filename): continue + if not os.path.isfile(filename): continue + v_tmp = {} + v_tmp['name'] = fname + v_tmp['type'] = mimetypes.guess_type(filename)[0] + v_tmp['size'] = os.path.getsize(filename) + if not v_tmp['type'].split('/')[0] in ['video']: + continue + v_data.append(v_tmp) + except:continue + return sorted(v_data,key=lambda x:x['name']) + + # 计算文件数量 + def GetFilesCount(self, path, search): + if os.path.isfile(path): + return 1 + if not os.path.exists(path): + return 0 + i = 0 + for name in os.listdir(path): + if search: + if name.lower().find(search) == -1: + continue + i += 1 + return i + + # 创建文件 + def CreateFile(self, get): + # 校验磁盘大小 + df_data = public.ExecShell("df -T | grep '/'")[0] + for data in str(df_data).split("\n"): + data_list = data.split() + if not data_list: continue + use_size = data_list[4] + size = data_list[5] + disk_path = data_list[6] + if int(use_size) < 1024 and str(size).rstrip("%") == "100" and disk_path in ["/","/www"]: + return public.return_msg_gettext(False, f"File creation failed! The disk is full! please clear the space first!") + + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8').strip() + try: + fname = os.path.basename(get.path).strip() + fpath = os.path.dirname(get.path).strip() + get.path = os.path.join(fpath,fname) + if get.path[-1] == '.': + return public.return_msg_gettext(False, 'It is not recommended to use [ . ] at the end of the file because there may be security risks') + if not self.CheckFileName(get.path): + return public.return_msg_gettext(False, 'File names can NOT contain special characters!') + if os.path.exists(get.path): + return public.return_msg_gettext(False, 'Requested file exists!') + path = os.path.dirname(get.path) + if not os.path.exists(path): + os.makedirs(path) + open(get.path, 'w+').close() + self.SetFileAccept(get.path) + public.write_log_gettext('File manager', 'Successfully created file [{}]!', (get.path,)) + return public.return_msg_gettext(True, 'Successfully created file!') + except: + return public.return_msg_gettext(False, 'Failed to create file!') + + #创建软链 + def CreateLink(self,get): + ''' + @name 创建软链接 + @author hwliang<2021-03-23> + @param get 源文件 + dfile 软链文件名 + }> + @return dict + ''' + if not get.dfile or get.dfile[-1] == "/": + return public.return_msg_gettext(False, + 'The specified soft link file name must contain the full path (full path)') + if not 'sfile' in get: return public.return_msg_gettext(False,'Parameter ERROR!') + if not os.path.exists(get.sfile): return public.return_msg_gettext(False,'Configuration file not exist') + if os.path.exists(get.dfile): return public.return_msg_gettext(False,'The specified soft link file name already exists') + l_name = os.path.basename(get.dfile) + if re.match(r"^[\w\-\.]+$", l_name) == None: return public.returnMsg(False, 'Link file name is illegal!') + if get.dfile[0] != '/': return public.return_msg_gettext(False,'The specified soft link file name must contain the full path (full path)') + public.ExecShell("ln -sf {} {}".format(get.sfile,get.dfile)) + if not os.path.exists(get.dfile): return public.return_msg_gettext(False,'Softlink file creation failed') + public.write_log_gettext('Firewall manager','Create softlink: {} -> {}',(get.dfile,get.sfile)) + return public.return_msg_gettext(True,'The softlink file was created successfully') + + + + # 创建目录 + def CreateDir(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8').strip() + try: + if get.path[-1] == '.': + return public.return_msg_gettext(False, 'It is not recommended to use [ . ] at the end of the directory, because there may be safety risks') + if not self.CheckFileName(get.path): + return public.return_msg_gettext(False, 'Directory names cannot contain special characters!') + if os.path.exists(get.path): + return public.return_msg_gettext(False, 'Requested directory exists!') + os.makedirs(get.path) + self.SetFileAccept(get.path) + public.write_log_gettext('File manager', 'Successfully created directory [ {} ]!', (get.path,)) + return public.return_msg_gettext(True, 'Successfully created directory!') + except: + return public.return_msg_gettext(False,'Failed to create directory!') + + #删除目录 + def DeleteDir(self,get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if os.path.basename(get.path) in ['Recycle_bin','.Recycle_bin']: + return public.return_msg_gettext(False,'Recovery failed!') + if not os.path.exists(get.path): + return public.return_msg_gettext(False, 'Requested directory does not exist') + + # 检查是否敏感目录 + if not self.CheckDir(get.path): + return public.return_msg_gettext(False, 'Editing this directory may cause service exceptions!') + + try: + # 检查是否存在.user.ini + # if os.path.exists(get.path+'/.user.ini'): + # public.ExecShell("chattr -i '"+get.path+"/.user.ini'") + public.ExecShell("chattr -R -i " + get.path) + if hasattr(get, 'empty'): + if not self.delete_empty(get.path): + return public.return_msg_gettext(False, 'Cannot delete non-empty directory!') + + if os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1: + if self.Mv_Recycle_bin(get): + self.site_path_safe(get) + self.remove_file_ps(get) + public.add_security_logs("Del dir","Delete directory: "+get.path) + return public.return_msg_gettext(True, 'Directory moved to recycle bin!') + + import shutil + shutil.rmtree(get.path) + self.site_path_safe(get) + public.add_security_logs("Del dir", "Delete directory: " + get.path) + public.WriteLog('TYPE_FILE', 'Successfully deleted directory [{}]!', (get.path,)) + self.remove_file_ps(get) + return public.return_msg_gettext(True, ' Successfully deleted directory!') + except: + return public.return_msg_gettext(False, 'Failed to delete directory!') + + # 删除 空目录 + def delete_empty(self, path): + if sys.version_info[0] == 2: + path = path.encode('utf-8') + if len(os.listdir(path)) > 0: + return False + return True + + # 删除文件 + def DeleteFile(self, get): + # 校验参数 + try: + get.validate([ + Param('path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if not os.path.exists(get.path)and not os.path.islink(get.path): + # return public.return_msg_gettext(False, 'Configuration file not exist') + return public.return_message(-1, 0,'Configuration file not exist') + + # 检查是否为.user.ini + if get.path.find('.user.ini') != -1: + public.ExecShell("chattr -i '"+get.path+"'") + try: + if os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1: + if self.Mv_Recycle_bin(get): + self.site_path_safe(get) + self.remove_file_ps(get) + public.add_security_logs("Del file", "Delete file: " + get.path) + return public.return_message(0, 0, 'File moved to recycle bin') + # return public.return_msg_gettext(True, 'File moved to recycle bin!') + os.remove(get.path) + self.site_path_safe(get) + public.write_log_gettext('File manager', 'Successfully permanent deleted file: [{}]!', (get.path,)) + public.add_security_logs("Del file", "Delete file: " + get.path) + self.remove_file_ps(get) + # return public.return_msg_gettext(True, 'Successfully deleted file!') + return public.return_message(0, 0, 'Successfully deleted file') + except: + # return public.return_msg_gettext(False, 'Failed to delete file!') + return public.return_message(-1, 0, 'Failed to delete file!') + + + def remove_file_ps(self,get): + ''' + @name 删除文件或目录的备注信息 + ''' + get.filename = get.path + get.ps_body = '' + get.ps_type = '0' + self.set_file_ps(get) + + # 移动到回收站 + def Mv_Recycle_bin(self, get): + rPath = public.get_recycle_bin_path(get.path) + rFile = os.path.join(rPath , get.path.replace('/', '_bt_') + '_t_' + str(time.time())) + try: + import shutil + shutil.move(get.path, rFile) + public.write_log_gettext('File manager', 'Successfully moved file [{}] to recycle bin!', (get.path,)) + return True + except: + public.write_log_gettext( + 'File manager', 'Failed to move file [{}] to recycle bin!', (get.path,)) + return False + + # 从回收站恢复 + def Re_Recycle_bin(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + get.path = public.html_decode(get.path).replace(';','') + dFile = get.path.replace('_bt_', '/').split('_t_')[0] + + # 检查所在回收站目录 + recycle_bin_list = public.get_recycle_bin_list() + _ok = False + for r_path in recycle_bin_list: + for r_file in os.listdir(r_path): + if get.path == r_file: + _ok = True + rPath = r_path + get.path = os.path.join(rPath , get.path) + break + if _ok: break + + if dFile.find('BTDB_') != -1: + import database + return database.database().RecycleDB(get.path) + try: + import shutil + if os.path.isdir(get.path) and os.path.exists(dFile): + shutil.move(dFile,dFile + "_{}.bak".format(public.format_date("%Y%m%d%H%M%S"))) + shutil.move(get.path, dFile) + public.write_log_gettext('File manager', 'Successfully recovered [{}] from recycle bin!', (dFile,)) + return public.return_msg_gettext(True, 'Recovery succeeded!') + except: + public.write_log_gettext('File manager', 'Failed to recover [{}] from recycle bin!', (dFile,)) + return public.return_msg_gettext(False, 'Recovery failed!') + + # 获取回收站信息 + def Get_Recycle_bin(self, get): + data = {} + data['dirs'] = [] + data['files'] = [] + data['status'] = os.path.exists('data/recycle_bin.pl') + data['status_db'] = os.path.exists('data/recycle_bin_db.pl') + recycle_bin_list = public.get_recycle_bin_list() + for rPath in recycle_bin_list: + if not os.path.exists(rPath): continue + for file in os.listdir(rPath): + try: + tmp = {} + fname = os.path.join(rPath , file) + if sys.version_info[0] == 2: + fname = fname.encode('utf-8') + else: + fname.encode('utf-8') + tmp1 = file.split('_bt_') + tmp2 = tmp1[len(tmp1)-1].split('_t_') + file = self.xssencode(file) + tmp['rname'] = file + tmp['dname'] = file.replace('_bt_', '/').split('_t_')[0] + if tmp['dname'].find('@') != -1: + tmp['dname'] = "BTDB_" + tmp['dname'][5:].replace('@',"\\u").encode().decode("unicode_escape") + tmp['name'] = tmp2[0] + tmp['time'] = int(float(tmp2[1])) + if os.path.islink(fname): + filePath = os.readlink(fname) + if os.path.exists(filePath): + tmp['size'] = os.path.getsize(filePath) + else: + tmp['size'] = 0 + else: + tmp['size'] = os.path.getsize(fname) + if os.path.isdir(fname): + if file[:5] == 'BTDB_': + tmp['size'] = public.get_path_size(fname) + data['dirs'].append(tmp) + else: + data['files'].append(tmp) + except: + continue + + data['dirs'] = sorted(data['dirs'],key = lambda x: x['time'],reverse=True) + data['files'] = sorted(data['files'],key = lambda x: x['time'],reverse=True) + # return data + return public.return_message(0, 0, data) + + # 彻底删除 + def Del_Recycle_bin(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + + get.path = public.html_decode(get.path).replace(';','') + + dFile = get.path.split('_t_')[0] + # 检查所在回收站目录 + recycle_bin_list = public.get_recycle_bin_list() + _ok = False + for r_path in recycle_bin_list: + for r_file in os.listdir(r_path): + if get.path == r_file: + _ok = True + rPath = r_path + filename = os.path.join(rPath , get.path) + break + if _ok: break + + + tfile = get.path.replace('_bt_', '/').split('_t_')[0] + if not _ok: return public.returnMsg(False, 'Error deleting file : {}', (tfile,)) + + if dFile.find('BTDB_') != -1: + import database + return database.database().DeleteTo(filename) + if not self.CheckDir(filename): + return public.return_msg_gettext(False, 'Never trouble troubles till troubles trouble you!') + + public.ExecShell('chattr -R -i ' + filename) + if os.path.isdir(filename): + import shutil + try: + shutil.rmtree(filename) + except: + public.ExecShell('chattr -R -a ' + filename) + public.ExecShell("rm -rf " + filename) + else: + try: + os.remove(filename) + except: + public.ExecShell("rm -f " + filename) + public.write_log_gettext('File manager', 'Parmanently deleted {} from recycle bin!', (tfile,)) + return public.return_msg_gettext(True, 'Parmanently deleted {} from recycle bin!', (tfile,)) + + # 清空回收站 + def Close_Recycle_bin(self, get): + + import database + import shutil + + recycle_bin_list = public.get_recycle_bin_list() + for rPath in recycle_bin_list: + public.ExecShell('chattr -R -i ' + rPath) + rlist = os.listdir(rPath) + i = 0 + l = len(rlist) + for name in rlist: + i += 1 + path = os.path.join(rPath , name) + public.writeSpeed(name, i, l) + if name.find('BTDB_') != -1: + database.database().DeleteTo(path) + continue + if os.path.isdir(path): + try: + shutil.rmtree(path) + except: + public.ExecShell('chattr -R -a ' + path) + public.ExecShell('rm -rf ' + path) + else: + try: + os.remove(path) + except: + public.ExecShell('rm -f ' + path) + + public.writeSpeed(None, 0, 0) + public.write_log_gettext('File manager', 'Recycle bin emptied!') + return public.return_msg_gettext(True, 'Recycle bin emptied!') + + # 回收站开关 + def Recycle_bin(self, get): + c = 'data/recycle_bin.pl' + if hasattr(get, 'db'): + c = 'data/recycle_bin_db.pl' + if os.path.exists(c): + os.remove(c) + public.write_log_gettext('File manager', 'Recycle bin feature turned off!') + return public.return_msg_gettext(True, 'Recycle bin feature turned off!') + else: + public.writeFile(c, 'True') + public.write_log_gettext('File manager', 'Recycle bin feature turned on!') + return public.return_msg_gettext(True, 'Recycle bin feature turned on!') + + # 复制文件 + def CopyFile(self, get): + if sys.version_info[0] == 2: + get.sfile = get.sfile.encode('utf-8') + get.dfile = get.dfile.encode('utf-8') + if get.dfile[-1] == '.': + return public.return_msg_gettext(False, 'It is not recommended to use [.] at the end of the file because there may be security risks') + if not os.path.exists(get.sfile): + return public.return_msg_gettext(False, 'Configuration file not exist') + + # if os.path.exists(get.dfile): + # return public.return_msg_gettext(False,'Requested file exists!') + + if os.path.isdir(get.sfile): + return self.CopyDir(get) + + import shutil + try: + shutil.copyfile(get.sfile, get.dfile) + public.write_log_gettext('File manager', 'Successfully copied file [{}] to [{}]!', + (get.sfile, get.dfile)) + stat = os.stat(get.sfile) + os.chmod(get.dfile,stat.st_mode) + os.chown(get.dfile, stat.st_uid, stat.st_gid) + return public.return_msg_gettext(True, 'Successfully copied file!') + except: + return public.return_msg_gettext(False, 'Failed to copy file!') + + # 复制文件夹 + def CopyDir(self, get): + if sys.version_info[0] == 2: + get.sfile = get.sfile.encode('utf-8') + get.dfile = get.dfile.encode('utf-8') + if get.dfile[-1] == '.': + return public.return_msg_gettext(False, 'It is not recommended to use [.] at the end of the directory, because there may be safety risks') + if not os.path.exists(get.sfile): + return public.return_msg_gettext(False, 'Requested directory does not exist') + + # if os.path.exists(get.dfile): + # return public.return_msg_gettext(False,'Requested directory exists!') + + # if not self.CheckDir(get.dfile): + # return public.return_msg_gettext(False,'Never trouble troubles till troubles trouble you!') + + try: + self.copytree(get.sfile, get.dfile) + stat = os.stat(get.sfile) + os.chmod(get.dfile,stat.st_mode) + os.chown(get.dfile, stat.st_uid, stat.st_gid) + public.write_log_gettext('File manager', 'Successfully copied directory!', + (get.sfile, get.dfile)) + return public.return_msg_gettext(True, 'Successfully copied directory!') + except: + return public.return_msg_gettext(False, 'Failed to copy directory!') + + # 移动文件或目录 + def MvFile(self, get): + if sys.version_info[0] == 2: + get.sfile = get.sfile.encode('utf-8') + get.dfile = get.dfile.encode('utf-8') + if get.dfile[-1] == '.': + return public.return_msg_gettext(False, 'It is not recommended to use [.] at the end of the file because there may be security risks') + if not self.CheckFileName(get.dfile): + return public.return_msg_gettext(False,'File names can NOT contain special characters!') + if os.path.basename(get.sfile) == '.Recycle_bin': + return public.return_msg_gettext(False,'Recovery failed!') + if not os.path.exists(get.sfile): + return public.return_msg_gettext(False, 'Configuration file not exist') + + if hasattr(get, 'rename'): + if os.path.exists(get.dfile): + return public.return_msg_gettext(False,'The target file name already exists!') + + if get.dfile[-1] == '/': + get.dfile = get.dfile[:-1] + + if get.dfile == get.sfile: + return public.return_msg_gettext(False,'Meaningless operation') + + if not self.CheckDir(get.sfile): + return public.return_msg_gettext(False,'Never trouble troubles till troubles trouble you!') + try: + self.move(get.sfile,get.dfile) + self.site_path_safe(get) + if hasattr(get,'rename'): + public.write_log_gettext('File manager','[{}] renamed to [{}]',(get.sfile,get.dfile)) + return public.return_msg_gettext(True,'Successfully renamed!') + else: + public.write_log_gettext('File manager', 'File moved!', + (get.sfile, get.dfile)) + return public.return_msg_gettext(True, 'File moved!') + except: + return public.return_msg_gettext(False, 'Failed to move file!') + + # 检查文件是否存在 + def CheckExistsFiles(self, get): + if sys.version_info[0] == 2: + get.dfile = get.dfile.encode('utf-8') + data = [] + filesx = [] + if not hasattr(get, 'filename'): + if not 'selected' in session: + return [] + filesx = json.loads(session['selected']['data']) + else: + filesx.append(get.filename) + + for fn in filesx: + if fn == '.': + continue + filename = get.dfile + '/' + fn + if os.path.exists(filename): + tmp = {} + stat = os.stat(filename) + tmp['filename'] = fn + tmp['size'] = os.path.getsize(filename) + tmp['mtime'] = str(int(stat.st_mtime)) + data.append(tmp) + return data + + # 取文件扩展名 + def __get_ext(self, filename): + tmp = filename.split('.') + return tmp[-1] + + # 获取文件内容 + def GetFileBody(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + + get.path = self.xssdecode(get.path) + + if get.path.find('/rewrite/null/') != -1: + webserver = public.get_webserver() + get.path = get.path.replace("/rewrite/null/", "/rewrite/{}/".format(webserver)) + if get.path.find('/vhost/null/') != -1: + webserver = public.get_webserver() + get.path = get.path.replace("/vhost/null/", "/vhost/{}/".format(webserver)) + + if not os.path.exists(get.path): + if get.path.find('rewrite') == -1: + return public.return_message(-1,0,'Configuration file not exist') + public.writeFile(get.path,'') + if self.__get_ext(get.path) in ['gz','zip','rar','exe','db','pdf','doc','xls','docx','xlsx','ppt','pptx','7z','bz2','png','gif','jpg','jpeg','bmp','icon','ico','pyc','class','so','pyd']: + return public.return_message(-1,0,'The file format does not support online editing!') + # if os.path.getsize(get.path) > 3145928: + # return public.return_msg_gettext(False,'Cannot edit files larger than 2MB online!') + if os.path.isdir(get.path): + return public.return_message(-1,0,'Writing verification file failed: {}') + + # 处理my.cnf为空的情况 + myconf_file = '/etc/my.cnf' + if get.path == myconf_file: + if os.path.getsize(myconf_file) < 10: + mycnf_file_bak = '/etc/my.cnf.bak' + if os.path.exists(mycnf_file_bak): + public.writeFile(myconf_file, public.readFile(mycnf_file_bak)) + + data = {} + data['status'] = True + data["only_read"] = False + data["size"] = os.path.getsize(get.path) + if data["size"] > 3145928: + try: + info_data=self.last_lines(get.path, 10000) + if info_data=="":return public.return_message(-1,0, u'The file encoding is not compatible, the file cannot be read correctly!') + data["data"]=info_data + data["only_read"]=True + except:return public.return_message(-1,0, u'The file encoding is not compatible, the file cannot be read correctly!') + else: + fp = open(get.path, 'rb') + if fp: + srcBody = fp.read() + fp.close() + try: + data['encoding'] = 'utf-8' + data['data'] = srcBody.decode(data['encoding']) + except: + try: + data['encoding'] = 'GBK' + data['data'] = srcBody.decode(data['encoding']) + except: + try: + data['encoding'] = 'BIG5' + data['data'] = srcBody.decode(data['encoding']) + except: + return public.return_message(-1,0, 'File encoding is not compatible and cannot be read correctly!') + else: + return public.return_message(-1,0,'Failed to open file, file may be occupied by other processes!') + if hasattr(get,'filename'): + get.path = get.filename + data['historys'] = self.get_history(get.path) + data['auto_save'] = self.get_auto_save(get.path) + data['st_mtime'] = str(int(os.stat(get.path).st_mtime)) + return_status = -1 + if data['status']: + return_status=0 + del data['status'] + return public.return_message(return_status,0,data) + + def last_lines(self,filename, lines=1): + block_size = 3145928 + block = '' + nl_count = 0 + start = 0 + fsock = open(filename, 'rU') + try: + fsock.seek(0, 2) + curpos = fsock.tell() + while (curpos > 0): + curpos -= (block_size + len(block)) + if curpos < 0: curpos = 0 + fsock.seek(curpos) + try: + block = fsock.read() + except: + continue + nl_count = block.count('\n') + if nl_count >= lines: break + for n in range(nl_count - lines + 1): + start = block.find('\n', start) + 1 + finally: + fsock.close() + return block[start:] + + + # 保存文件 + def SaveFileBody(self, get): + if not 'path' in get: + return public.return_message(-1,0,'[path] parameter cannot be empty!') + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + + if get.path.find('/rewrite/null/') != -1: + webserver = public.get_webserver() + get.path = get.path.replace("/rewrite/null/", "/rewrite/{}/".format(webserver)) + if get.path.find('/vhost/null/') != -1: + webserver = public.get_webserver() + get.path = get.path.replace("/vhost/null/", "/vhost/{}/".format(webserver)) + + if not os.path.exists(get.path): + if get.path.find('.htaccess') == -1: + return public.return_message(-1,0, 'Configuration file not exist') + elif os.path.getsize(get.path) > 3145928: + return public.return_message(-1,0, 'Files larger than 3MB cannot be edited online!') + nginx_conf_path = public.get_vhost_path() + '/nginx/' + if get.path.find(nginx_conf_path) != -1: + if get.data.find('#SSL-START') != -1 and get.data.find('#SSL-END') != -1: + if get.data.find('#error_page 404/404.html;') == -1: + str1 = public.get_msg_gettext('Failed to save the configuration file') + str2 = public.get_msg_gettext('Do not modify the 404 rule commented in the SSL config') + str3 = public.get_msg_gettext('To modify the 404 config, find the following config location') + + # return public.returnMsg(False,'Failed to save the configuration file:

                                    Do not modify the 404 rule commented in the SSL config

                                    To modify the 404 config, find the following config location:

                                    #ERROR-PAGE-START  Error page configuration, allowed to be commented
                                    ') + return public.return_message(-1,0,'{}:

                                    {}

                                    :

                                    #ERROR-PAGE-START  Error page configuration, allowed to be commented
                                    '.format(str1,str2,str3)) + + if 'st_mtime' in get: + st_mtime = str(int(os.stat(get.path).st_mtime)) + if st_mtime != get['st_mtime']: return public.return_message(-1,0,'Failed to save, {} file has been changed, please refresh the content and modify it again.'.format(get.path)) + + his_path = '/www/backup/file_history/' + if get.path.find(his_path) != -1: + return public.return_msg_gettext(False,'Cannot modify history copy directly!') + try: + if 'base64' in get: + import base64 + get.data = base64.b64decode(get.data) + isConf = -1 + if os.path.exists('/etc/init.d/nginx') or os.path.exists('/etc/init.d/httpd'): + isConf = get.path.find('nginx') + if isConf == -1: + isConf = get.path.find('apache') + if isConf == -1: + isConf = get.path.find('rewrite') + if isConf != -1: + public.ExecShell('\\cp -a '+get.path+' /tmp/backup.conf') + + data = get.data + if data == 'undefined': return public.return_message(-1,0,'Wrong file content, please save again!') + userini = False + if get.path.find('.user.ini') != -1: + userini = True + public.ExecShell('chattr -i ' + get.path) + + if get.path.find('/www/server/cron') != -1: + try: + import crontab + data = crontab.crontab().CheckScript(data) + except: + pass + + if get.encoding == 'ascii': + get.encoding = 'utf-8' + self.save_history(get.path) + try: + if sys.version_info[0] == 2: + data = data.encode(get.encoding, errors='ignore') + fp = open(get.path, 'w+') + else: + + data = data.encode(get.encoding , errors='ignore').decode(get.encoding) + fp = open(get.path, 'w+', encoding=get.encoding) + except: + fp = open(get.path, 'w+') + data = self.crlf_to_lf(data, get.path) + fp.write(data) + fp.close() + + if isConf != -1: + isError = public.checkWebConfig() + if isError != True: + public.ExecShell('\\cp -a /tmp/backup.conf '+get.path) + return public.return_message(-1,0, 'ERROR:
                                    '+isError.replace("\n", '
                                    ')+'
                                    ') + public.serviceReload() + + if userini: + public.ExecShell('chattr +i ' + get.path) + + public.write_log_gettext('File manager', 'Successfully saved file [{}]!', (get.path,)) + tmp_data = public.return_msg_gettext(True, 'Saved!') + data ={'msg': tmp_data['msg']} + data['historys'] = self.get_history(get.path) # 获取历史记录 + data['st_mtime'] = str(int(os.stat(get.path).st_mtime)) + return public.return_message(0,0,data) + except Exception as ex: + return public.return_message(-1,0, 'Save ERROR! {}' + str(ex)) + + def crlf_to_lf(self,data,filename): + ''' + @name 将CRLF转换为LF + @author hwliang + @param data 要转换的数据 + @param filename 文件名 + @return string + ''' + file_ext_name = os.path.splitext(filename)[-1] + if not file_ext_name: + if data.find('#!/bin/bash') == 0 or data.find('#!/bin/sh') == 0: + file_ext_name = '.sh' + elif data.find('#!/usr/bin/python') == 0 or data.find('import ') != -1: + file_ext_name = '.py' + elif data.find('#!/usr/bin/env node') == 0: + file_ext_name = '.js' + elif data.find('#!/usr/bin/env php') == 0 or data.find('= num: # 删除多余的副本 + if os.path.exists(rm_file): + os.remove(rm_file) + continue + # 写入新的副本 + if is_write: + public.writeFile( + save_path + '/' + str(int(time.time())), public.readFile(filename, 'rb'), 'wb') + except: + pass + + # 取历史副本 + def get_history(self, filename): + try: + save_path = ('/www/backup/file_history/' + + filename).replace('//', '/') + if not os.path.exists(save_path): + return [] + return sorted(os.listdir(save_path),reverse=True) + except: + return [] + + # 读取指定历史副本 + def read_history(self, args): + save_path = ('/www/backup/file_history/' + + args.filename).replace('//', '/') + args.path = save_path + '/' + args.history + return self.GetFileBody(args) + + # 恢复指定历史副本 + def re_history(self, args): + save_path = ('/www/backup/file_history/' + + args.filename).replace('//', '/') + args.path = save_path + '/' + args.history + if not os.path.exists(args.path): + return public.return_msg_gettext(False,'The specified historical copy does not exist!') + import shutil + shutil.copyfile(args.path, args.filename) + return self.GetFileBody(args) + + # 自动保存配置 + def auto_save_temp(self, args): + save_path = '/www/backup/file_auto_save/' + if not os.path.exists(save_path): + os.makedirs(save_path, 384) + filename = save_path + args.filename + if os.path.exists(filename): + f_md5 = public.FileMd5(filename) + s_md5 = public.md5(args.body) + if f_md5 == s_md5: + return public.return_msg_gettext(True,'Not Edit') + public.writeFile(filename,args.body) + return public.return_msg_gettext(True,'Automatically saved successfully!') + + # 取上一次自动保存的结果 + def get_auto_save_body(self, args): + save_path = '/www/backup/file_auto_save/' + args.path = save_path + args.filename + return self.GetFileBody(args) + + # 取自动保存结果 + def get_auto_save(self, filename): + try: + save_path = ('/www/backup/file_auto_save/' + + filename).replace('//', '/') + if not os.path.exists(save_path): + return None + return os.stat(save_path).st_mtime + except: + return None + + + def is_max_size(self,path,max_size,max_num=10000,total_size=0,total_num=0): + ''' + @name 是否超过最大大小 + @path 文件路径 + @max_size 最大大小 + @max_num 最大文件数量 + @return bool + ''' + if not os.path.exists(path) or not max_size: + return False,total_size,total_num + + # 是否为文件? + if os.path.isfile(path): + total_size = os.path.getsize(path) + total_num = 1 + if total_size > max_size: + return True,total_size,total_num + return False,total_size,total_num + + # 是否为目录? + for root, dirs, files in os.walk(path, topdown=True): + total_num += len(files) + total_num += len(dirs) + # 判断是否超过最大文件数量 + if total_num > max_num: + return True,total_size,total_num + + for f in files: + filename = os.path.normcase(root+os.path.sep+f) + if not os.path.exists(filename): continue + if os.path.islink(filename): continue + total_size += os.path.getsize(filename) + + # 判断是否超过最大大小 + if total_size > max_size: + return True,total_size,total_num + + return False,total_size,total_num + + + # 文件压缩 + def Zip(self, get): + if not 'z_type' in get: + get.z_type = 'rar' + + if get.z_type == 'rar': + if os.uname().machine != 'x86_64': + return public.return_msg_gettext(False,'RAR component does not support aarch 64 platform') + + import panelTask + task_obj = panelTask.bt_task() + max_size = 1024*1024*100 + max_num = 10000 + total_size = 0 + total_num = 0 + status = True + if not os.path.exists(os.path.dirname(get.dfile)): + os.makedirs(os.path.dirname(get.dfile)) + for file_name in get.sfile.split(','): + path = os.path.join(get.path,file_name) + status,total_size,total_num = self.is_max_size(path,max_size,max_num,total_size,total_num) + if not status: break + + # 如果被压缩目标小于100MB或文件数量少于1W个,则直接在主线程压缩 + if not status: + return task_obj._zip(get.path,get.sfile,get.dfile,'/tmp/zip.log',get.z_type) + + # 否则在后台线程压缩 + task_obj.create_task('压缩文件', 3, get.path, json.dumps( + {"sfile": get.sfile, "dfile": get.dfile, "z_type": get.z_type})) + public.WriteLog("TYPE_FILE", 'ZIP_SUCCESS', (get.sfile, get.dfile)) + return public.returnMsg(True, '已将压缩任务添加到消息队列!') + + # 文件解压 + def UnZip(self, get): + if get.sfile[-4:] == '.rar': + if os.uname().machine != 'x86_64': + return public.return_msg_gettext(False,'RAR component does not support aarch 64 platform') + import panelTask + if not 'password' in get: + get.password = '' + if not os.path.exists(get.sfile): + return public.returnMsg(False, 'The specified archive does not exist!') + if not os.path.exists(get.dfile): + os.makedirs(get.dfile) + zip_size = os.path.getsize(get.sfile) + task_obj = panelTask.bt_task() + if zip_size < 1024 * 1024 * 50: + return task_obj._unzip(get.sfile, get.dfile, get.password,"/tmp/unzip.log") + + task_obj.create_task(public.get_msg_gettext('Decompress the file'), 2, get.sfile, + json.dumps({"dfile": get.dfile, "password": get.password})) + public.write_log_gettext("File manager", 'Successfully uncompressed file from [{}] to [{}]!',(get.sfile,get.dfile)) + return public.return_msg_gettext(True,'Decompression task added to the message queue!') + + # 获取文件/目录 权限信息 + def GetFileAccess(self, get): + if sys.version_info[0] == 2: + get.filename = get.filename.encode('utf-8') + data = {} + try: + import pwd + stat = os.stat(get.filename) + data['chmod'] = str(oct(stat.st_mode)[-3:]) + data['chown'] = pwd.getpwuid(stat.st_uid).pw_name + except: + data['chmod'] = 644 + data['chown'] = 'www' + return data + + # 设置文件权限和所有者 + def SetFileAccess(self, get, all='-R'): + if sys.version_info[0] == 2: + get.filename = get.filename.encode('utf-8') + if 'all' in get: + if get.all == 'False': + all = '' + try: + if not self.CheckDir(get.filename): + return public.return_msg_gettext(False, 'Never trouble troubles till troubles trouble you!') + if not os.path.exists(get.filename): + return public.return_msg_gettext(False, 'Configuration file not exist') + public.ExecShell('chmod '+all+' '+get.access+" '"+get.filename+"'") + public.ExecShell('chown '+all+' '+get.user+':' + + get.user+" '"+get.filename+"'") + public.write_log_gettext('File manager', "Set [{}]'s permission to [{}] and authorized user to [{}]", + (get.filename, get.access, get.user)) + return public.return_msg_gettext(True, 'Setup successfully!') + except: + return public.return_msg_gettext(False, 'Failed to set') + + def SetFileAccept(self, filename): + public.ExecShell('chown -R www:www ' + filename) + if os.path.isfile(filename): + public.ExecShell('chmod -R 644 ' + filename) + else: + public.ExecShell('chmod -R 755 ' + filename) + + # 取目录大小 + + def GetDirSize(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + return public.to_size(public.get_path_size(get.path)) + + # 取目录大小2 + def get_path_size(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + data = {} + data['path'] = get.path + data['size'] = public.get_path_size(get.path) + return data + + def CloseLogs(self, get): + get.path = public.GetConfigValue('root_path') + public.ExecShell('rm -f '+public.GetConfigValue('logs_path')+'/*') + public.ExecShell('rm -rf '+public.GetConfigValue('logs_path')+'/history_backups/*') + public.ExecShell('rm -f '+public.GetConfigValue('logs_path')+'/pm2/*.log') + if public.get_webserver() == 'nginx': + public.ExecShell( + 'kill -USR1 `cat '+public.GetConfigValue('setup_path')+'/nginx/logs/nginx.pid`') + else: + public.ExecShell('/etc/init.d/httpd reload') + + public.write_log_gettext('File manager', 'Site Logs emptied!') + get.path = public.GetConfigValue('logs_path') + return self.GetDirSize(get) + + # 批量操作 + def SetBatchData(self, get): + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if get.type == '1' or get.type == '2': + session['selected'] = get + return public.return_msg_gettext(True, 'Successfully marked, please click Paste All button in the target directory!') + elif get.type == '3': + for key in json.loads(get.data): + try: + if sys.version_info[0] == 2: + key = key.encode('utf-8') + filename = get.path+'/'+key + if not self.CheckDir(filename): + return public.return_msg_gettext(False, 'Never trouble troubles till troubles trouble you!') + ret = ' -R ' + if 'all' in get: + if get.all == 'False': + ret = '' + public.ExecShell('chmod '+ret+get.access+" '"+filename+"'") + public.ExecShell('chown '+ret+get.user + + ':'+get.user+" '"+filename+"'") + except: + continue + public.write_log_gettext('File manager', 'Batch setting permission successful!') + return public.return_msg_gettext(True, 'Batch setting permission successful!') + else: + isRecyle = os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1 + path = get.path + get.data = json.loads(get.data) + l = len(get.data) + i = 0 + args = public.dict_obj() + for key in get.data: + try: + if sys.version_info[0] == 2: + key = key.encode('utf-8') + filename = path + '/'+key + get.path = filename + if not os.path.exists(filename): + continue + i += 1 + public.writeSpeed(key, i, l) + if os.path.isdir(filename): + if not self.CheckDir(filename): + return public.return_msg_gettext(False, 'Never trouble troubles till troubles trouble you!') + public.ExecShell("chattr -R -i " + filename) + if isRecyle: + self.Mv_Recycle_bin(get) + else: + shutil.rmtree(filename) + else: + if key == '.user.ini': + if l > 1: + continue + public.ExecShell('chattr -i ' + filename) + if isRecyle: + + self.Mv_Recycle_bin(get) + else: + os.remove(filename) + args.path = filename + self.remove_file_ps(args) + except: + continue + public.writeSpeed(None, 0, 0) + self.site_path_safe(get) + if not isRecyle: + public.write_log_gettext('File manager', 'Batch deleting successful!') + return public.return_msg_gettext(True, 'Batch deleting successful!') + else: + public.write_log_gettext('File manager', '{} files or directories have been moved to the recycle bin in batches'.format(i)) + return public.return_msg_gettext(True, '{} files or directories have been moved to the recycle bin in batches'.format(i)) + + # 批量粘贴 + def BatchPaste(self, get): + import shutil + if sys.version_info[0] == 2: + get.path = get.path.encode('utf-8') + if not self.CheckDir(get.path): + return public.return_msg_gettext(False,'Never trouble troubles till troubles trouble you!') + if not 'selected' in session: + return public.return_msg_gettext(False,'TCOPY_PRESS_ERR') + i = 0 + if not 'selected' in session: + return public.return_msg_gettext(False,'The operation failed, please re-operate') + myfiles = json.loads(session['selected']['data']) + l = len(myfiles) + if get.type == '1': + + for key in myfiles: + if sys.version_info[0] == 2: + sfile = session['selected']['path'] + \ + '/' + key.encode('utf-8') + dfile = get.path + '/' + key.encode('utf-8') + else: + sfile = session['selected']['path'] + '/' + key + dfile = get.path + '/' + key + + if os.path.commonpath([dfile, sfile]) == sfile: + return public.return_msg_gettext(False,'Wrong copy logic, from {} copy to {} has an inclusive relationship, there is an infinite loop copy risk!'.format(sfile,dfile)) + + for key in myfiles: + i += 1 + public.writeSpeed(key, i, l) + try: + if sys.version_info[0] == 2: + sfile = session['selected']['path'] + \ + '/' + key.encode('utf-8') + dfile = get.path + '/' + key.encode('utf-8') + else: + sfile = session['selected']['path'] + '/' + key + dfile = get.path + '/' + key + + if os.path.isdir(sfile): + self.copytree(sfile, dfile) + else: + shutil.copyfile(sfile, dfile) + stat = os.stat(sfile) + os.chown(dfile, stat.st_uid, stat.st_gid) + except: + continue + public.write_log_gettext('File manager','Batch copied from [{}] to [{}]',(session['selected']['path'],get.path)) + else: + for key in myfiles: + try: + i += 1 + public.writeSpeed(key, i, l) + if sys.version_info[0] == 2: + sfile = session['selected']['path'] + '/' + key.encode('utf-8') + dfile = get.path + '/' + key.encode('utf-8') + else: + sfile = session['selected']['path'] + '/' + key + dfile = get.path + '/' + key + self.move(sfile, dfile) + except: + continue + self.site_path_safe(get) + public.write_log_gettext('File manager','Batch moved from [{}] to [{}]',(session['selected']['path'],get.path)) + public.writeSpeed(None,0,0); + errorCount = len(myfiles) - i + del(session['selected']) + return public.return_msg_gettext(True,'Batch operating succeeded [{}], failed [{}]',(str(i),str(errorCount))) + + # 移动和重命名 + def move(self, sfile, dfile): + sfile = sfile.replace('//', '/') + dfile = dfile.replace('//', '/') + if sfile == dfile: + return False + if not os.path.exists(sfile): + return False + is_dir = os.path.isdir(sfile) + if not os.path.exists(dfile) or not is_dir: + if os.path.exists(dfile): + os.remove(dfile) + shutil.move(sfile, dfile) + else: + self.copytree(sfile, dfile) + if os.path.exists(sfile) and os.path.exists(dfile): + if is_dir: + shutil.rmtree(sfile) + else: + os.remove(sfile) + return True + + # 复制目录 + def copytree(self, sfile, dfile): + if sfile == dfile: + return False + if not os.path.exists(dfile): + os.makedirs(dfile) + for f_name in os.listdir(sfile): + if not f_name.strip(): continue + if f_name.find('./') != -1: continue + src_filename = (sfile + '/' + f_name).replace('//', '/') + dst_filename = (dfile + '/' + f_name).replace('//', '/') + mode_info = public.get_mode_and_user(src_filename) + if os.path.isdir(src_filename): + if not os.path.exists(dst_filename): + os.makedirs(dst_filename) + public.set_mode(dst_filename, mode_info['mode']) + public.set_own(dst_filename, mode_info['user']) + self.copytree(src_filename, dst_filename) + else: + try: + shutil.copy2(src_filename, dst_filename) + public.set_mode(dst_filename, mode_info['mode']) + public.set_own(dst_filename, mode_info['user']) + except: + pass + return True + + # 下载文件 + + def DownloadFile(self, get): + import panelTask + task_obj = panelTask.bt_task() + task_obj.create_task(public.get_msg_gettext('Download file'),1,get.url,get.path + '/' + get.filename) + #if sys.version_info[0] == 2: get.path = get.path.encode('utf-8'); + #import db,time + #isTask = '/tmp/panelTask.pl' + #execstr = get.url +'|bt|'+get.path+'/'+get.filename + #sql = db.Sql() + #sql.table('tasks').add('name,type,status,addtime,execstr',('下载文件['+get.filename+']','download','0',time.strftime('%Y-%m-%d %H:%M:%S'),execstr)) + # public.writeFile(isTask,'True') + # self.SetFileAccept(get.path+'/'+get.filename) + public.write_log_gettext('File manager', 'Downloaded file [{}] to [{}]', (get.url, get.path)) + return public.return_msg_gettext(True, 'Download task added into the queue!') + + # 添加安装任务 + def InstallSoft(self, get): + import db + import time + path = public.GetConfigValue('setup_path') + '/php' + if not os.path.exists(path): + public.ExecShell("mkdir -p " + path) + if session['server_os']['x'] != 'RHEL': + get.type = '3' + apacheVersion = 'false' + if public.get_webserver() == 'apache': + apacheVersion = public.readFile( + public.GetConfigValue('setup_path')+'/apache/version.pl') + public.writeFile('/var/bt_apacheVersion.pl', apacheVersion) + public.writeFile('/var/bt_setupPath.conf', + public.GetConfigValue('root_path')) + isTask = '/tmp/panelTask.pl' + execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \ + get.type + " install " + get.name + " " + get.version + if public.get_webserver() == "openlitespeed": + execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \ + get.type + " install " + get.name + "-ols " + get.version + sql = db.Sql() + if hasattr(get, 'id'): + id = get.id + else: + id = None + sql.table('tasks').add('id,name,type,status,addtime,execstr', (None, + 'Install ['+get.name+'-'+get.version+']', 'execshell', '0', time.strftime('%Y-%m-%d %H:%M:%S'), execstr)) + public.writeFile(isTask, 'True') + public.write_log_gettext('Installer', 'Download task added to queue!', (get.name, get.version)) + time.sleep(0.1) + return public.return_msg_gettext(True, 'Download task added to queue!') + + # 删除任务队列 + def RemoveTask(self, get): + try: + name = public.M('tasks').where('id=?', (get.id,)).getField('name') + status = public.M('tasks').where( + 'id=?', (get.id,)).getField('status') + public.M('tasks').delete(get.id) + if status == '-1': + public.ExecShell( + "kill `ps -ef |grep 'python panelSafe.pyc'|grep -v grep|grep -v panelExec|awk '{print $2}'`") + public.ExecShell( + "kill `ps -ef |grep 'install_soft.sh'|grep -v grep|grep -v panelExec|awk '{print $2}'`") + public.ExecShell( + "kill `ps aux | grep 'python task.pyc$'|awk '{print $2}'`") + public.ExecShell(''' +pids=`ps aux | grep 'sh'|grep -v grep|grep install|awk '{print $2}'` +arr=($pids) + +for p in ${arr[@]} +do + kill -9 $p +done + ''') + + public.ExecShell( + 'rm -f ' + name.replace('Scan dir [', '').replace(']', '') + '/scan.pl') + isTask = '/tmp/panelTask.pl' + public.writeFile(isTask, 'True') + public.ExecShell('/etc/init.d/bt start') + except: + public.ExecShell('/etc/init.d/bt start') + return public.return_msg_gettext(True, 'Task deleted') + + # 重新激活任务 + def ActionTask(self, get): + isTask = '/tmp/panelTask.pl' + public.writeFile(isTask, 'True') + return public.return_msg_gettext(True, 'Task queue activated') + + # 卸载软件 + def UninstallSoft(self, get): + public.writeFile('/var/bt_setupPath.conf', + public.GetConfigValue('root_path')) + get.type = '0' + if session['server_os']['x'] != 'RHEL': + get.type = '3' + if public.get_webserver() == "openlitespeed": + default_ext = ["bz2","calendar","sysvmsg","exif","imap","readline","sysvshm","xsl"] + if get.version == "73": + default_ext.append("opcache") + if not os.path.exists("/etc/redhat-release"): + default_ext.append("gmp") + default_ext.append("opcache") + if get.name.lower() in default_ext: + return public.return_msg_gettext(False, 'This extension is the default extension of OLS and cannot be uninstalled') + execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \ + get.type+" uninstall " + get.name.lower() + " " + get.version.replace('.', '') + if public.get_webserver() == "openlitespeed": + execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \ + get.type + " uninstall " + get.name.lower() + "-ols " + get.version.replace('.', '') + public.ExecShell(execstr) + public.write_log_gettext('Installer', 'Uninstallaton succeeded', + (get.name, get.version)) + return public.return_msg_gettext(True, 'Uninstallaton succeeded') + + # 取任务队列进度 + def GetTaskSpeed(self, get): + tempFile = '/tmp/panelExec.log' + #freshFile = '/tmp/panelFresh' + import db + find = db.Sql().table('tasks').where('status=? OR status=?',('-1','0')).field('id,type,name,execstr').find() + if(type(find) == str): + return public.return_msg_gettext(False,'Query error, {}',(find,)) + if not len(find): + return public.return_msg_gettext(False,'NO_TASK_AT_LINEUP',("-2",)) + isTask = '/tmp/panelTask.pl' + public.writeFile(isTask, 'True') + echoMsg = {} + echoMsg['name'] = find['name'] + echoMsg['execstr'] = find['execstr'] + if find['type'] == 'download': + try: + tmp = public.readFile(tempFile) + if len(tmp) < 10: + return public.return_msg_gettext(False,'NO_TASK_AT_LINEUP',("-3",)) + echoMsg['msg'] = json.loads(tmp) + echoMsg['isDownload'] = True + except: + db.Sql().table('tasks').where("id=?",(find['id'],)).save('status',('0',)) + return public.return_msg_gettext(False,'NO_TASK_AT_LINEUP',("-4",)) + else: + echoMsg['msg'] = self.GetLastLine(tempFile, 20) + echoMsg['isDownload'] = False + + echoMsg['task'] = public.M('tasks').where("status!=?", ('1',)).field( + 'id,status,name,type').order("id asc").select() + return echoMsg + + # 取执行日志 + def GetExecLog(self, get): + return self.GetLastLine('/tmp/panelExec.log', 100) + + # 读文件指定倒数行数 + def GetLastLine(self, inputfile, lineNum): + result = public.GetNumLines(inputfile, lineNum) + if len(result) < 1: + return public.get_msg_gettext('Loading...') + return result + + # 执行SHELL命令 + def ExecShell(self, get): + disabled = ['vi', 'vim', 'top', 'passwd', 'su'] + get.shell = get.shell.strip() + tmp = get.shell.split(' ') + if tmp[0] in disabled: + return public.return_msg_gettext(False, 'Sorry, [{}] command is NOT supported!', (tmp[0],)) + shellStr = '''#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +cd %s +%s +''' % (get.path, get.shell) + public.writeFile('/tmp/panelShell.sh', shellStr) + public.ExecShell( + 'nohup bash /tmp/panelShell.sh > /tmp/panelShell.pl 2>&1 &') + return public.return_msg_gettext(True, 'Command sent') + + # 取SHELL执行结果 + def GetExecShellMsg(self, get): + fileName = '/tmp/panelShell.pl' + if not os.path.exists(fileName): + return 'FILE_SHELL_EMPTY' + status = not public.process_exists('bash', None, '/tmp/panelShell.sh') + return public.return_msg_gettext(status, public.GetNumLines(fileName, 200)) + + # 文件搜索 + def GetSearch(self, get): + if not os.path.exists(get.path): + return public.return_msg_gettext(False, 'Requested directory does not exist') + return public.ExecShell("find "+get.path+" -name '*"+get.search+"*'") + + # 保存草稿 + def SaveTmpFile(self, get): + save_path = public.get_panel_path() + '/temp' + if not os.path.exists(save_path): + os.makedirs(save_path) + get.path = os.path.join(save_path,public.Md5(get.path) + '.tmp') + public.writeFile(get.path,get.body) + return public.return_msg_gettext(True,'Saved') + + # 获取草稿 + def GetTmpFile(self, get): + self.CleanOldTmpFile() + save_path = public.get_panel_path() + '/temp' + if not os.path.exists(save_path): + os.makedirs(save_path) + src_path = get.path + get.path = os.path.join(save_path,public.Md5(get.path) + '.tmp') + if not os.path.exists(get.path): + return public.return_msg_gettext(False,'No drafts available!') + data = self.GetFileInfo(get.path) + data['file'] = src_path + if 'rebody' in get: + data['body'] = public.readFile(get.path) + return data + + # 清除过期草稿 + def CleanOldTmpFile(self): + if 'clean_tmp_file' in session: + return True + save_path = public.get_panel_path() + '/temp' + max_time = 86400 * 30 + now_time = time.time() + for tmpFile in os.listdir(save_path): + filename = os.path.join(save_path, tmpFile) + fileInfo = self.GetFileInfo(filename) + if now_time - fileInfo['modify_time'] > max_time: + os.remove(filename) + session['clean_tmp_file'] = True + return True + + # 取指定文件信息 + def GetFileInfo(self, path): + if not os.path.exists(path): + return False + stat = os.stat(path) + fileInfo = {} + fileInfo['modify_time'] = int(stat.st_mtime) + fileInfo['size'] = os.path.getsize(path) + return fileInfo + + # 安装rar组件 + def install_rar(self, get): + unrar_file = public.get_setup_path() + '/rar/unrar' + rar_file = public.get_setup_path() + '/rar/rar' + bin_unrar = '/usr/local/bin/unrar' + bin_rar = '/usr/local/bin/rar' + if os.path.exists(unrar_file) and os.path.exists(bin_unrar): + try: + import rarfile + except: + public.ExecShell("pip install rarfile") + return True + + import platform + os_bit = '' + if platform.machine() == 'x86_64': + os_bit = '-x64' + download_url = public.get_url() + '/src/rarlinux'+os_bit+'-5.6.1.tar.gz' + + tmp_file = '/tmp/bt_rar.tar.gz' + public.ExecShell('wget -O ' + tmp_file + ' ' + download_url) + if os.path.exists(unrar_file): + public.ExecShell("rm -rf {}".format(rar_file)) + public.ExecShell("tar xvf " + tmp_file + ' -C {}'.format(public.get_setup_path())) + if os.path.exists(tmp_file): + os.remove(tmp_file) + if not os.path.exists(unrar_file): + return False + + if os.path.exists(bin_unrar): + os.remove(bin_unrar) + if os.path.exists(bin_rar): + os.remove(bin_rar) + + public.ExecShell('ln -sf ' + unrar_file + ' ' + bin_unrar) + public.ExecShell('ln -sf ' + rar_file + ' ' + bin_rar) + public.ExecShell("pip install rarfile") + # public.writeFile('data/restart.pl','True') + return True + + def get_store_data(self): + data = [] + path = 'data/file_store.json' + try: + if os.path.exists(path): + data = json.loads(public.readFile(path)) + except: + data = [] + if type(data) == dict: + result = [] + for key in data: + for path in data[key]: + result.append(path) + self.set_store_data(result) + return result + return data + + def set_store_data(self, data): + public.writeFile('data/file_store.json', json.dumps(data)) + return True + + # 获取收藏夹 + def get_files_store(self, get): + data = self.get_store_data() + result = [] + for path in data: + if type(path) == dict: + path = path['path'] + info = {'path': path, 'name': os.path.basename(path)} + if os.path.isdir(path): + info['type'] = 'dir' + else: + info['type'] = 'file' + result.append(info) + return result + + # 添加收藏夹 + def add_files_store(self, get): + path = get.path + if not os.path.exists(path): + return public.return_msg_gettext(False,'File or directory does not exist!') + data = self.get_store_data() + if path in data: + return public.return_msg_gettext(False,'Do not add it repeatedly!') + data.append(path) + self.set_store_data(data) + return public.return_msg_gettext(True,'Successfully added') + + #删除收藏夹 + def del_files_store(self,get): + path = get.path + data = self.get_store_data() + if not path in data: + is_go = False + for info in data: + if type(info) == dict: + if info['path'] == path: + path = info + is_go = True + break + if not is_go: + return public.return_msg_gettext(False,'This favorite object could not be found!') + data.remove(path) + if len(data) <= 0: + data = [] + self.set_store_data(data) + return public.return_msg_gettext(True,'Successfully deleted') + + # #单文件木马扫描 + # def file_webshell_check(self,get): + # if not 'filename' in get: return public.returnMsg(True, 'file does not exist!') + # import webshell_check + # if webshell_check.webshell_check().upload_file_url(get.filename.strip()): + # return public.returnMsg(False,'This file is webshell [ %s ]'%get.filename.strip().split('/')[-1]) + # else: + # return public.returnMsg(True, 'no risk') + # + # #目录扫描木马 + # def dir_webshell_check(self,get): + # if not 'path' in get: return public.returnMsg(False, 'Please enter a valid directory!') + # path=get.path.strip() + # if os.path.exists(path): + # #启动消息队列 + # exec_shell = public.get_python_bin() + ' /www/server/panel/class/webshell_check.py dir %s mail'%path + # task_name = "Scan Trojan files for directory %s"%path + # import panelTask + # task_obj = panelTask.bt_task() + # task_obj.create_task(task_name, 0, exec_shell) + # return public.returnMsg(True, 'Starting Trojan killing process. Details will be in the panel security log') + + # 获取下载地址列表 + def get_download_url_list(self, get): + my_table = 'download_token' + count = public.M(my_table).count() + + if not 'p' in get: + get.p = 1 + if not 'collback' in get: + get.collback = '' + data = public.get_page(count, int(get.p), 12, get.collback) + data['data'] = public.M(my_table).order('id desc').field( + 'id,filename,token,expire,ps,total,password,addtime').limit(data['shift'] + ',' + data['row']).select() + return data + + + #获取短列表 + def get_download_list(self): + if self.download_list != None: return self.download_list + my_table = 'download_token' + self.download_list = public.M(my_table).field('id,filename,expire').select() + if self.download_token_list == None: self.download_token_list = {} + m_time = time.time() + for d in self.download_list: + #清理过期和无效 + if self.download_is_rm: continue + if not os.path.exists(d['filename']) or m_time > d['expire']: + public.M(my_table).where('id=?',(d['id'],)).delete() + continue + self.download_token_list[d['filename']] = d['id'] + + #标记清理 + if not self.download_is_rm: + self.download_is_rm = True + + #获取id + def get_download_id(self,filename): + self.get_download_list() + return str(self.download_token_list.get(filename,'0')) + + # 获取指定下载地址 + def get_download_url_find(self, get): + if not 'id' in get: return public.return_msg_gettext(False, 'Wrong parameter') + id = int(get.id) + my_table = 'download_token' + data = public.M(my_table).where('id=?', (id,)).find() + if not data: return public.return_msg_gettext(False, 'The specified address does not exist!') + return data + + # 删除下载地址 + def remove_download_url(self, get): + if not 'id' in get: return public.return_msg_gettext(False, 'Wrong parameter') + id = int(get.id) + my_table = 'download_token' + public.M(my_table).where('id=?', (id,)).delete() + return public.return_msg_gettext(True, 'Successfully deleted!') + + # 修改下载地址 + def modify_download_url(self, get): + if not 'id' in get: return public.return_msg_gettext(False, 'Wrong parameter') + id = int(get.id) + my_table = 'download_token' + if not public.M(my_table).where('id=?', (id,)).count(): + return public.return_msg_gettext(False, 'The specified address does not exist!') + pdata = {} + if 'expire' in get: pdata['expire'] = get.expire + if 'password' in get: + pdata['password'] = get.password + if len(pdata['password']) < 4 and len(pdata['password']) > 0: + return public.return_msg_gettext(False,'The length of the extracted password cannot be less than 4 digits') + if not re.match(r'^\w+$',pdata['password']): + return public.return_msg_gettext(False,'The password only supports a combination of uppercase and lowercase letters and numbers') + + if 'ps' in get: pdata['ps'] = get.ps + public.M(my_table).where('id=?', (id,)).update(pdata) + return public.return_msg_gettext(True, 'Successfully modified') + + # 生成下载地址 + def create_download_url(self, get): + if not os.path.exists(get.filename): + return public.return_msg_gettext(False,'File or directory does not exist!') + my_table = 'download_token' + mtime = int(time.time()) + pdata = { + "filename": get.filename, #文件名 + "token": public.GetRandomString(12), #12位随机密钥,用于URL + "expire": mtime + (int(get.expire) * 3600), #过期时间 + "ps":get.ps, #备注 + "total":0, #下载计数 + "password":str(get.password), #提取密码 + "addtime": mtime #添加时间 + } + exts = os.path.basename(get.filename).split('.') + if len(exts) > 1: + pdata['token'] += "." + exts[-1] + if len(pdata['password']) < 4 and len(pdata['password']) > 0: + return public.return_msg_gettext(False,' Please do not enter the following special characters [ ~ ` / = ]') + if not re.match(r'^\w+$',pdata['password']) and pdata['password']: + return public.return_msg_gettext(False,'The password only supports a combination of uppercase and lowercase letters and numbers') + #更新 or 插入 + token = public.M(my_table).where('filename=?',(get.filename,)).getField('token') + if token: + return public.return_msg_gettext(False, 'Already shared!') + # pdata['token'] = token + # del(pdata['total']) + # public.M(my_table).where('token=?',(token,)).update(pdata) + else: + id = public.M(my_table).insert(pdata) + pdata['id'] = id + + return public.return_msg_gettext(True, pdata) + + + #取PHP-CLI执行命令 + def __get_php_bin(self,php_version=None): + php_vs = public.get_php_versions(True) + if php_version: + if php_version != 'auto': + if not php_version in php_vs: return '' + else: + php_version = None + + #判段兼容的PHP版本是否安装 + php_path = "/www/server/php/" + php_v = None + for pv in php_vs: + if php_version: + if php_version != pv: continue + php_bin = php_path + pv + "/bin/php" + if os.path.exists(php_bin): + php_v = pv + break + # 如果没安装直接返回False + if not php_v: return '' + #处理PHP-CLI-INI配置文件 + php_ini = '/www/server/panel/tmp/composer_php_cli_'+php_v+'.ini' + if not os.path.exists(php_ini): + # 如果不存在,则从PHP安装目录下复制一份 + src_php_ini = php_path + php_v + '/etc/php.ini' + import shutil + shutil.copy(src_php_ini, php_ini) + # 解除所有禁用函数 + php_ini_body = public.readFile(php_ini) + php_ini_body = re.sub(r"disable_functions\s*=.*", "disable_functions = ", php_ini_body) + public.writeFile(php_ini, php_ini_body) + return php_path + php_v + '/bin/php -c ' + php_ini + + # 执行git + def exec_git(self,get): + if get.git_action == 'option': + public.ExecShell("nohup {} &> /tmp/panelExec.pl &".format(get.giturl)) + else: + public.ExecShell("nohup git clone {} &> /tmp/panelExec.pl &".format(get.giturl)) + return public.return_msg_gettext(True,'Command has been sent!') + + # 安装composer + def get_composer_bin(self): + composer_bin = '/usr/bin/composer' + download_addr = 'wget -O {} {}/install/src/composer.phper -T 5'.format(composer_bin,public.get_url()) + if not os.path.exists(composer_bin): + public.ExecShell(download_addr) + elif os.path.getsize(composer_bin) < 100: + public.ExecShell(download_addr) + + public.ExecShell('chmod +x {}'.format(composer_bin)) + if not os.path.exists(composer_bin): + return False + return composer_bin + + # 执行composer + def exec_composer(self,get): + # 校验参数 + try: + get.validate([ + Param('php_version').String(), + Param('composer_args').String(), + Param('composer_cmd').String(), + Param('repo').String(), + Param('path').String(), + Param('user').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + #准备执行环境 + composer_bin = self.get_composer_bin() + if not composer_bin: + return public.return_message(-1,0,'No composer available!') + + #取执行PHP版本 + php_version = None + if 'php_version' in get: + php_version = get.php_version + php_bin = self.__get_php_bin(php_version) + if not php_bin: + return public.return_message(-1,0,'No available PHP version was found, or the specified PHP version was not installed!') + get.composer_cmd = get.composer_cmd.strip() + if get.composer_cmd == '': + if not os.path.exists(get.path + '/composer.json'): + return public.return_message(-1,0,'The composer.json configuration file was not found in the specified directory!') + log_file = '/tmp/composer.log' + user = '' + # del_cache = self._composer_user_home() + if 'user' in get: + user = 'sudo -u {} '.format(get.user) + if not os.path.exists('/usr/bin/sudo'): + if os.path.exists('/usr/bin/apt'): + public.ExecShell("apt install sudo -y > {}".format(log_file)) + else: + public.ExecShell("yum install sudo -y > {}".format(log_file)) + public.ExecShell("mkdir -p /home/www && chown -R www:www /home/www") + # del_cache = self._composer_user_home() + + #设置指定源 + if 'repo' in get: + if get.repo != 'repos.packagist': + public.ExecShell('export COMPOSER_HOME=/tmp && {}{} {} config -g repo.packagist composer {}'.format(user,php_bin,composer_bin,get.repo)) + else: + public.ExecShell('export COMPOSER_HOME=/tmp && {}{} {} config -g --unset repos.packagist'.format(user,php_bin,composer_bin)) + #执行composer命令 + if not get.composer_cmd: + composer_exec_str = '{} {} {} -vvv'.format(php_bin,composer_bin,get.composer_args) + else: + if get.composer_cmd.find('composer ') == 0 or get.composer_cmd.find('/usr/bin/composer ') == 0: + composer_cmd = get.composer_cmd.replace('composer ','').replace('/usr/bin/composer ','') + composer_exec_str = '{} {} {} -vvv'.format(php_bin,composer_bin,composer_cmd) + else: + composer_exec_str = '{} {} {} {} -vvv'.format(php_bin,composer_bin,get.composer_args,get.composer_cmd) + + if os.path.exists(log_file): os.remove(log_file) + public.ExecShell("cd {} && export COMPOSER_HOME=/tmp && {} nohup {} &> {} && echo 'BT-Exec-Completed' >> {} && rm -rf /home/www &".format(get.path,user,composer_exec_str,log_file,log_file)) + public.write_log_gettext('Composer',"Execute composer [{}] in the directory: [{}]",(get.path,get.composer_args)) + # del_cache() + return public.return_message(0,0,'Command has been sent!') + + # 取composer版本 + def get_composer_version(self,get): + composer_bin = self.get_composer_bin() + if not composer_bin: + return public.return_message(-1,0,'No composer available!') + + try: + bs = str(public.readFile(composer_bin,'rb')) + result = re.findall(r"const VERSION\s*=\s*.{0,2}'([\d\.]+)",bs)[0] + if not result: raise Exception('empty!') + except: + php_bin = self.__get_php_bin() + if not php_bin: return public.return_msg_gettext(False,'No available PHP version found!') + composer_exec_str = 'export COMPOSER_HOME=/tmp && ' + php_bin + ' ' + composer_bin +' --version 2>/dev/null|grep \'Composer version\'|awk \'{print $3}\'' + result = public.ExecShell(composer_exec_str)[0].strip() + data = public.return_message(0,0,result) + if 'path' in get: + import panel_site_v2 as panelSite + data['message']['php_versions'] = panelSite.panelSite().GetPHPVersion(get,False) + data['message']['comp_json'] = True + data['message']['comp_lock'] = False + if not os.path.exists(get.path + '/composer.json'): + data['message']['comp_json'] = public.get_msg_gettext('[Composer.json] configuration file is not found in the specified directory!') + if os.path.exists(get.path + '/composer.lock'): + data['message']['comp_lock'] = public.get_msg_gettext('[Composer.lock] file exists in the specified directory, please delete it before executing') + return data + + # 升级composer版本 + def update_composer(self,get): + # 校验参数 + try: + get.validate([ + Param('repo').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + composer_bin = self.get_composer_bin() + if not composer_bin: + return public.return_message(-1,0,'No composer available!') + php_bin = self.__get_php_bin() + if not php_bin: return public.return_message(-1,0,'No available PHP version found!') + #设置指定源 + # if 'repo' in get: + # if get.repo: + # public.ExecShell('{} {} config -g repo.packagist composer {}'.format(php_bin,composer_bin,get.repo)) + + version1 = self.get_composer_version(get)['message'] + composer_exec_str = 'export COMPOSER_HOME=/tmp && {} {} self-update -vvv'.format(php_bin,composer_bin) + public.ExecShell(composer_exec_str) + version2 = self.get_composer_version(get)['message'] + if version1 == version2: + msg = 'Currently the latest version, no upgrade required!' + else: + msg = 'Upgrade composer from {} to {}'.format(version1,version2) + public.write_log_gettext('Composer',msg) + return public.return_message(0,0,msg) + + # 计算文件HASH + def get_file_hash(self,args=None,filename=None): + if not filename: filename = args.filename + import hashlib + md5_obj = hashlib.md5() + sha1_obj = hashlib.sha1() + f = open(filename,'rb') + while True: + b = f.read(8096) + if not b : + break + md5_obj.update(b) + sha1_obj.update(b) + f.close() + return {'md5':md5_obj.hexdigest(),'sha1':sha1_obj.hexdigest()} + + + # 取历史副本 + def get_history_info(self, filename): + try: + save_path = ('/www/backup/file_history/' + + filename).replace('//', '/') + if not os.path.exists(save_path): + return [] + result = [] + for f in sorted(os.listdir(save_path)): + f_name = (save_path + '/' + f).replace('//', '/') + pdata = {} + pdata['md5'] = public.FileMd5(f_name) + f_stat = os.stat(f_name) + pdata['st_mtime'] = int(f) + pdata['st_size'] = f_stat.st_size + pdata['history_file'] = f_name + result.insert(0,pdata) + return sorted(result,key=lambda x:x['st_mtime'],reverse=True) + except: + return [] + + #获取文件扩展名 + def get_file_ext(self,filename): + ss_exts = ['tar.gz','tar.bz2','tar.bz'] + for s in ss_exts: + e_len = len(s) + f_len = len(filename) + if f_len < e_len: continue + if filename[-e_len:] == s: + return s + if filename.find('.') == -1: return '' + return filename.split('.')[-1] + + + # 取所属用户或组 + def get_mode_user(self,uid): + import pwd + try: + return pwd.getpwuid(uid).pw_name + except: + return uid + + # 取lsattr + def get_lsattr(self,filename): + if os.path.isfile(filename): + return public.ExecShell('lsattr {}'.format(filename))[0].split(' ')[0] + else: + s_name = os.path.basename(filename) + s_path = os.path.dirname(filename) + + try: + res = public.ExecShell('lsattr {}'.format(s_path))[0].strip() + for s in res.split('\n'): + if not s: continue + lsattr_info = s.split() + if not lsattr_info: continue + if filename == lsattr_info[1]: + return lsattr_info[0] + except: + raise public.PanelError(lsattr_info) + + return '--------------e----' + + + # 取指定文件属性 + def get_file_attribute(self,args): + filename = args.filename.strip() + if not os.path.exists(filename): + return public.return_msg_gettext(False,'File does not exist!') + attribute = {} + attribute['name'] = os.path.basename(filename) + attribute['path'] = os.path.dirname(filename) + f_stat = os.stat(filename) + attribute['st_atime'] = int(f_stat.st_atime) # 最后访问时间 + attribute['st_mtime'] = int(f_stat.st_mtime) # 最后修改时间 + attribute['st_ctime'] = int(f_stat.st_ctime) # 元数据修改时间/权限或数据者变更时间 + attribute['st_size'] = f_stat.st_size # 文件大小(bytes) + attribute['st_gid'] = f_stat.st_gid # 用户组id + attribute['st_uid'] = f_stat.st_uid # 用户id + attribute['st_nlink'] = f_stat.st_nlink # inode 的链接数 + attribute['st_ino'] = f_stat.st_ino # inode 的节点号 + attribute['st_mode'] = f_stat.st_mode # inode 保护模式 + attribute['st_dev'] = f_stat.st_dev # inode 驻留设备 + attribute['user'] = self.get_mode_user(f_stat.st_uid) # 所属用户 + attribute['group'] = self.get_mode_user(f_stat.st_gid) # 所属组 + attribute['mode'] = str(oct(f_stat.st_mode)[-3:]) # 文件权限号 + attribute['md5'] = public.get_msg_gettext('Do not count files or directories larger than 100MB') # 文件MD5 + attribute['sha1'] = public.get_msg_gettext('Do not count files or directories larger than 100MB') # 文件sha1 + attribute['lsattr'] = self.get_lsattr(filename) + attribute['is_dir'] = os.path.isdir(filename) # 是否为目录 + attribute['is_link'] = os.path.islink(filename) # 是否为链接文件 + if attribute['is_link']: + attribute['st_type'] = 'Link file' + elif attribute['is_dir']: + attribute['st_type'] = 'Dir' + else: + attribute['st_type'] = self.get_file_ext(filename) + attribute['history'] = [] + if f_stat.st_size < 104857600 and not attribute['is_dir']: + hash_info = self.get_file_hash(filename=filename) + attribute['md5'] = hash_info['md5'] + attribute['sha1'] = hash_info['sha1'] + attribute['history'] = self.get_history_info(filename) # 历史文件 + return attribute + + def files_search(self,args): + import panelSearch + adad=panelSearch.panelSearch() + return adad.get_search(args) + + + def files_replace(self,args): + import panelSearch + adad=panelSearch.panelSearch() + return adad.get_replace(args) + + def get_replace_logs(self,args): + import panelSearch + adad=panelSearch.panelSearch() + return adad.get_replace_logs(args) + + def get_path_images(self, path): + ''' + @name 获取目录的图片列表 + @param path 目录路径 + @return 图片列表 + ''' + image_list = [] + for fname in os.listdir(path): + if fname.split('.')[-1] in ['png', 'jpeg', 'gif', 'jpg', 'bmp', 'ico']: + image_list.append(fname) + return ','.join(image_list) + + def clear_thumbnail(self): + ''' + @name 清除过期的缩略图缓存 + @author hwliang + @return void + ''' + try: + from BTPanel import cache + except: + return + ikey = 'thumbnail_cache' + if cache.get(ikey): return + + cache_path = '{}/cache/thumbnail'.format(public.get_panel_path()) + if not os.path.exists(cache_path): return + expire_time = time.time() - (30 * 86400) # 30天前的文件 + for fname in os.listdir(cache_path): + filename =os.path.join(cache_path,fname) + if os.path.getctime(filename) < expire_time: + os.remove(filename) + + # 标记,每天清理一次 + cache.set(ikey,1,86400) + + + + + def get_images_resize(self,args): + ''' + @name 获取指定图片的缩略图 + @author hwliang<2022-03-02> + @param args{ + "path": "", 图片路径 + "files": xx.png,aaa.jpg, 文件名称(不包含目录路径),如果files=*,则返回该目录下的所有图片 + "width": 50, 宽 + "heigth:50, 高 + "return_type": "base64" // base64,file + } + @return base64编码的图片 or file + ''' + from PIL import Image + from base64 import b64encode + from io import BytesIO + if args.files == '*': + args.files = self.get_path_images(args.path) + + file_list = args.files.split(',') + + width = int(args.width) + height = int(args.height) + + cache_path = '{}/cache/thumbnail'.format(public.get_panel_path()) + if not os.path.exists(cache_path): os.makedirs(cache_path,384) + data = {} + _max_time = 3 # 最大处理时间 + _stime = time.time() + + # 清理过期的缩略图缓存 + self.clear_thumbnail() + + for fname in file_list: + try: + filename = os.path.join(args.path,fname) + f_size = os.path.getsize(filename) + cache_file = os.path.join(cache_path,public.md5("{}_{}_{}_{}".format(filename,width,height,f_size))) + if not os.path.exists(filename): + # 移除缓存文件 + if os.path.exists(cache_file): os.remove(cache_file) + continue + + # 有缩略图缓存的使用缓存 + if os.path.exists(cache_file): + data[fname] = public.readFile(cache_file) + continue + + # 超出最大处理时间直接跳过后续图片的处理,以免影响前端用户体验 + if time.time() - _stime > _max_time: + data[fname] = '' + continue + + im = Image.open(filename) + im.thumbnail((width,height)) + out = BytesIO() + im.save(out, im.format) + out.seek(0) + image_type = im.format.lower() + mimetype = 'image/{}'.format(image_type) + if args.return_type == 'base64': + b64_data = "data:{};base64,".format(mimetype) + b64encode(out.read()).decode('utf-8') + data[fname] = b64_data + out.close() + # 写缩略图缓存 + public.writeFile(cache_file,b64_data) + else: + from flask import send_file + return send_file(out, mimetype=mimetype, cache_timeout=0) + except: + data[fname] = '' + + return public.return_data(True,data) + + def set_rsync_data(self,data): + ''' + @name 写入rsync配置数据 + @author cjx + @param data 配置数据 + @return bool + ''' + public.writeFile('{}/data/file_rsync.json'.format(public.get_panel_path()),json.dumps(data)) + return True + + def get_rsync_data(self): + ''' + @name 获取文件同步配置 + @author cjx + @return dict + ''' + data = {} + path = '{}/data/file_rsync.json'.format(public.get_panel_path()) + try: + if os.path.exists(path): + data = json.loads(public.readFile(path)) + except : + data = {} + return data + + def add_files_rsync(self,get): + ''' + @name 添加数据同步标记 + @author cjx + ''' + path = get.path + s_type = get.s_type + + data = self.get_rsync_data() + if not path in data: data[path] = {} + + data[path][s_type] = 1 + + self.set_rsync_data(data) + return public.return_msg_gettext(True,'Added successfully!') + # 数据库对象 + def _get_sqlite_connect(self): + try: + if not self.sqlite_connection: + self.sqlite_connection = sqlite3.connect('data/file_permissions.db') + except Exception as ex: + return "error: " + str(ex) + + # 操作数据库 + def _operate_db(self,q_sql,permissions_tb=None): + try: + self._get_sqlite_connect() + c = self.sqlite_connection.cursor() + table = "index_tb" + if permissions_tb: + table = permissions_tb + sql_data = q_sql.replace("TB_NAME",table) + return c.execute(sql_data) + except: + self._create_index_tb() + self._operate_db(q_sql,permissions_tb) + + # 判断文件个数 + def _get_file_total(self,path,num,date): + n = 0 + for p in os.listdir(path): + full_path = path + "/" + p + if os.path.isfile(full_path): + if n == 0: + first_file = full_path + n+=1 + if n >= num: + self.path_permission_exclude_list.append(path) + f_p = public.get_mode_and_user(path) + data = {'path':first_file,'owner':f_p['user'],'mode':f_p['mode'],'type':'first_file','date':date} + self.path_permission_list.append(data) + return n + + # 创建权限表 + def _create_permissions_tb(self,tb_name): + self._get_sqlite_connect() + sql=""" +CREATE TABLE {}( + id INTEGER PRIMARY KEY AUTOINCREMENT, + path CHAR , + owner CHAR, + mode CHAR, + date CHAR, + type CHAR +);""".format(tb_name) + self.sqlite_connection.execute(sql) + + def _create_index_tb(self): + self._get_sqlite_connect() + sql = """ +CREATE TABLE index_tb( + id INTEGER PRIMARY KEY AUTOINCREMENT, + permissions_tb CHAR , + date CHAR, + remark CHAR, + first_path CHAR +);""" + self.sqlite_connection.execute(sql) + + # 获取权限表名 + def _get_permissions_tb_name(self,get_all_tb=None): + sql = 'select permissions_tb from TB_NAME' + data = self._operate_db(sql).fetchall() + exist_tb = [i[0] for i in data] + if get_all_tb: + return exist_tb + tb_names = ['p_tb'+str(x) for x in range(100)] + tb_name = [] + if exist_tb: + for n_tb in tb_names: + if n_tb not in exist_tb: + tb_name.append(n_tb) + break + if not tb_name: + tb_name.append(tb_names[0]) + self._create_permissions_tb(tb_name[0]) + return tb_name[0] + + # 写入索引表 + def _write_index_tb(self,remark,date,tb_name,path): + ins_sql = "INSERT INTO TB_NAME (remark,date,permissions_tb,first_path) VALUES ('{}', '{}', '{}','{}')".format(remark,date, tb_name,path) + self._operate_db(ins_sql) + self.sqlite_connection.commit() + + # 写入权限表 + def _write_permisssions_tb(self,tb_name): + p_p_l = self.path_permission_list + n = 0 + for p_p in p_p_l: + ins_sql = "INSERT INTO TB_NAME (path,owner,mode,date,type) VALUES ('{}', '{}', '{}','{}','{}')".format(p_p['path'], p_p['owner'], p_p['mode'],p_p['date'],p_p['type']) + self._operate_db(ins_sql,permissions_tb=tb_name) + n += 1 + if n >= 1000: + self.sqlite_connection.commit() + n = 0 + self.sqlite_connection.commit() + + # 备份路径权限 + def _back_path_permissions (self,path,date): + for p in os.listdir(path): + full_p = path + "/" + p + if os.path.isdir(full_p): + # 如果文件夹下数量大于500添加文件夹到排除列表,权限只记录第一个文件的权限 + if self._get_file_total(full_p,500,date): + permission_type = "exclude_dir" + else: + permission_type = "dir" + f_p = public.get_mode_and_user(full_p) + self.path_permission_list.append({'path':full_p,'owner':f_p['user'],'mode':f_p['mode'],'type':permission_type,'date':date}) + self._back_path_permissions(full_p,date) + continue + if path in self.path_permission_exclude_list: + continue + f_p = public.get_mode_and_user(full_p) + data = {'path':full_p,'owner':f_p['user'], 'mode':f_p['mode'], 'type':'file','date':date} + self.path_permission_list.append(data) + + # 备份目录权限 + def back_dir_perm(self,path,back_sub_dir,date,remark,tb_name): + print("开始备份目录权限 {}".format(path)) + self._write_index_tb(remark,date,tb_name,path) + f_p = public.get_mode_and_user(path) + data = {'path':path,'owner':f_p["user"],'mode':f_p['mode'],'date':date,'type':'dir'} + self.path_permission_list.append(data) + if back_sub_dir == "0": + self._write_permisssions_tb(tb_name) + return True + self._back_path_permissions(path, date) + self._write_permisssions_tb(tb_name) + + # 备份单个文件权限 + def back_single_file_perm(self,path,date,remark,tb_name): + print("开始备份文件权限 {}".format(path)) + self._write_index_tb(remark, date, tb_name,path) + f_p = public.get_mode_and_user(path) + data = {'path': path, 'owner': f_p["user"], 'mode': f_p['mode'], 'date': date, 'type': 'file'} + self.path_permission_list.append(data) + self._write_permisssions_tb(tb_name) + + # 备份权限 + def back_path_permissions(self,get): + back_limit = 100 + if self._get_total_back() >= back_limit: + return public.return_msg_gettext(False,"The number of backup versions has exceeded {} ,Please go to the upper right corner [Backup Permissions] to clean up the old backup before operating".format(back_limit)) + if not os.path.exists(get.path): + return public.return_msg_gettext(False,"Path is incorrect {}".format(get.path)) + path = get.path + back_sub_dir = get.back_sub_dir + remark = get.remark + self.path_permission_list = list() + # self.file_permission_list = list() + self.path_permission_exclude_list = list() + date = int(time.time()) + tb_name = self._get_permissions_tb_name() + try: + if os.path.isdir(path): + self.back_dir_perm(path,back_sub_dir,date,remark,tb_name) + else: + self.back_single_file_perm(path,date,remark,tb_name) + except Exception as e: + return public.return_msg_gettext(False,"Backup error {} ".format(e)) + finally: + self.sqlite_connection.commit() + self.sqlite_connection.close() + self.sqlite_connection=None + return public.return_msg_gettext(True,"Backup succeeded!") + + # 获取所有需要还原文件和文件夹 + def _get_restore_file(self,path): + for p in os.listdir(path): + full_p = path + "/" + p + if os.path.isdir(full_p): + self.file_permission_list.append(full_p) + self._get_restore_file(full_p) + continue + self.file_permission_list.append(full_p) + + # 直接递归还原目录下的文件权限 + def _recursive_restore_file_perm(self,path,p_i): + file_permissions = self._operate_db( + "SELECT owner,mode from TB_NAME where pid='{}' and type='{}'".format(p_i[0], 'first_file'), + 'file').fetchall() + f_p = file_permissions[0] + for i in os.listdir(path): + i = "{}/{}".format(path, i) + if os.path.isfile(i): + public.set_mode(i, f_p[1]) + public.set_own(i, f_p[0]) + + # 还原子目录权限 + def _restore_subdir_perm(self,path,date): + path_info = self._operate_db("SELECT id,owner,mode,type from TB_NAME where path='{}' and date='{}'".format(path,date), + 'path').fetchall() + p_i = path_info[0] + public.set_mode(path, p_i[2]) + public.set_own(path, p_i[1]) + if p_i[3] == "exclude_dir": + self._recursive_restore_file_perm(path,p_i) + file_permissions = self._operate_db("SELECT path,owner,mode from TB_NAME where pid='{}' and date='{}'".format(p_i[0],date), + 'file').fetchall() + if file_permissions: + for f in file_permissions: + if f[0] == ".user.ini": + continue + file_path = "{}/{}".format(path, f[0]) + public.set_mode(file_path, f[2]) + public.set_own(file_path, f[1]) + + # 还原目录权限 + def _restore_dir_perm(self,path_full,restore_sub_dir,date): + tb_name = self._operate_db("select permissions_tb from TB_NAME where date='{}'".format(date)).fetchall() + main_dir_data = self._operate_db("select path,owner,mode from TB_NAME where path='{}'".format(path_full),permissions_tb=tb_name[0][0]).fetchall() + if main_dir_data: + public.set_mode(main_dir_data[0][0], main_dir_data[0][2]) + public.set_own(main_dir_data[0][0], main_dir_data[0][1]) + if restore_sub_dir == "0": + public.return_msg_gettext(True, "Permission restored successfully") + self._get_restore_file(path_full) + if tb_name: + data = self._operate_db("select path,owner,mode from TB_NAME",permissions_tb=tb_name[0][0]).fetchall() + for d in data: + if '.user.ini' in d[0]: + continue + if d[0] in self.file_permission_list: + public.set_mode(d[0], d[2]) + public.set_own(d[0], d[1]) + return public.return_msg_gettext(True, "Permission restored successfully") + + # 还原单个文件权限 + def restore_single_file_perm(self,path_full,date): + tb_name = self._operate_db("select permissions_tb from TB_NAME where date='{}'".format(date)).fetchall() + main_dir_data = self._operate_db("select path,owner,mode from TB_NAME where path='{}'".format(path_full),permissions_tb=tb_name[0][0]).fetchall() + if main_dir_data: + public.set_mode(main_dir_data[0][0], main_dir_data[0][2]) + public.set_own(main_dir_data[0][0], main_dir_data[0][1]) + return public.return_msg_gettext(True, "Permission restored successfully") + return public.return_msg_gettext(False, "The file does not have backup permissions") + + + # 还原权限 + def restore_path_permissions(self,get): + self.file_permission_list = list() + path_full = get.path + restore_sub_dir = get.restore_sub_dir + date = get.date + try: + if os.path.isdir(path_full): + result = self._restore_dir_perm(path_full,restore_sub_dir,date) + else: + result = self.restore_single_file_perm(path_full,date) + return result + finally: + self.sqlite_connection.close() + self.sqlite_connection = None + + + def get_path_premissions(self,get): + path_full = get.path + result = [] + exist_tbs = self._get_permissions_tb_name(get_all_tb=True) + for tb_name in exist_tbs: + data = self._operate_db("select path,owner,mode,date from TB_NAME where path='{}'".format(path_full), + permissions_tb=tb_name).fetchall() + if not data and os.path.isdir(path_full) and path_full[-1] != "/": + path_full += "/" + data = self._operate_db( + "select path,owner,mode,date from TB_NAME where path='{}'".format(path_full), + permissions_tb=tb_name).fetchall() + if data: + index_data = self._operate_db("select id,remark from index_tb where permissions_tb='{}'".format(tb_name)).fetchall() + d_l = [] + for i in data[0]: + d_l.append(i) + if index_data: + d_l.append(index_data[0][1]) + d_l.append(index_data[0][0]) + result.append(d_l) + return sorted(result,key=lambda x:x[3],reverse=True) + + def del_path_premissions(self,get): + p_tb = self._operate_db("select permissions_tb from index_tb where id='{}'".format(get.id)).fetchall() + # 删除引导行 + self._operate_db("delete from index_tb where id='{}'".format(get.id)).fetchall() + if p_tb: + self._operate_db("drop table '{}'".format(p_tb[0][0])) + self.sqlite_connection.commit() + self.sqlite_connection.close() + return public.return_msg_gettext(True, "Successfully deleted!") + + # 获取所有备份 + def get_all_back(self,get): + data = self._operate_db('select id,remark,date,first_path from index_tb').fetchall() + return sorted(data,key=lambda x: x[2],reverse=True) + + def _get_total_back(self): + data = self._operate_db('select id from index_tb').fetchall() + return len(data) + + # 一键恢复默认权限 + def fix_permissions(self,get): + if not hasattr(get,"uid"): + import pwd + get.uid = pwd.getpwnam('www').pw_uid + get.gid = pwd.getpwnam('www').pw_gid + path = get.path + if os.path.isfile(path): + os.chown(path, get.uid, get.gid) + os.chmod(path, 0o644) + return public.return_msg_gettext(True, "Permission repair succeeded") + os.chown(path, get.uid, get.gid) + os.chmod(path, 0o755) + for file in os.listdir(path): + try: + filename = os.path.join(path,file) + os.chown(filename, get.uid, get.gid) + if os.path.isdir(filename): + os.chmod(filename, 0o755) + get.path = filename + self.fix_permissions(get) + continue + os.chmod(filename,0o644) + except: + print(public.get_error_info()) + return public.return_msg_gettext(True,"Permission repair succeeded") + + def restore_website(self,args): + """ + @name 恢复站点文件 + @author zhwen + @parma file_name 备份得文件名 + @parma site_id 网站id + """ + import panel_restore_v2 as panel_restore + pr=panel_restore.panel_restore() + return pr.restore_website_backup(args) + + def get_progress(self,args): + """ + @name 获取进度日志 + @author zhwen + """ + import panel_restore_v2 as panel_restore + pr=panel_restore.panel_restore() + return pr.get_progress(args) diff --git a/class_v2/firewallModelV2/app/appBase.py b/class_v2/firewallModelV2/app/appBase.py new file mode 100644 index 00000000..3a991af8 --- /dev/null +++ b/class_v2/firewallModelV2/app/appBase.py @@ -0,0 +1,78 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - 底层基类 +# ------------------------------ + +import sys +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public + + +class Base(object): + + def __init__(self): + pass + + # 2024/3/22 下午 3:18 通用返回 + def _result(self, status: bool, msg: str) -> dict: + ''' + @name 通用返回 + @author wzz <2024/3/22 下午 3:19> + @param status: True/False + msg: 提示信息 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return {"status": status, "msg": msg} + + # 2024/3/22 下午 4:55 检查是否设置了net.ipv4.ip_forward = 1,没有则设置 + def check_ip_forward(self) -> dict: + ''' + @name 检查是否设置了net.ipv4.ip_forward = 1,没有则设置 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("sysctl net.ipv4.ip_forward") + if "net.ipv4.ip_forward = 1" not in stdout: + # 2024/3/22 下午 4:56 永久设置 + stdout, stderr = public.ExecShell("echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf") + if stderr: + return self._result(False, "设置net.ipv4.ip_forward失败, err: {}".format(stderr)) + + stdout, stderr = public.ExecShell("sysctl -p") + if stderr: + return self._result(False, "设置net.ipv4.ip_forward失败, err: {}".format(stderr)) + return self._result(True, "设置net.ipv4.ip_forward成功") + return self._result(True, "net.ipv4.ip_forward已经设置") + + # 2024/3/18 上午 11:35 处理192.168.1.100-192.168.1.200这种ip范围 + # 返回192.168.1.100,192.168.1.101,192.168.1...,192.168.1.200列表 + def handle_ip_range(self, ip): + ''' + @name 处理192.168.1.100-192.168.1.200这种ip范围的ip列表 + @author wzz <2024/3/19 下午 4:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + ip_range = ip.split("-") + ip_start = ip_range[0] + ip_end = ip_range[1] + ip_start = ip_start.split(".") + ip_end = ip_end.split(".") + ip_start = [int(i) for i in ip_start] + ip_end = [int(i) for i in ip_end] + ip_list = [] + for i in range(ip_start[0], ip_end[0] + 1): + for j in range(ip_start[1], ip_end[1] + 1): + for k in range(ip_start[2], ip_end[2] + 1): + for l in range(ip_start[3], ip_end[3] + 1): + ip_list.append("{}.{}.{}.{}".format(i, j, k, l)) + return ip_list diff --git a/class_v2/firewallModelV2/app/firewalld.py b/class_v2/firewallModelV2/app/firewalld.py new file mode 100644 index 00000000..1e70c44a --- /dev/null +++ b/class_v2/firewallModelV2/app/firewalld.py @@ -0,0 +1,750 @@ +#!/www/server/panel/pyenv/bin/python3.7 +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - firewalld封装库 +# ------------------------------ + +import os +import sys + + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +from firewallModelV2.app.appBase import Base + + +class Firewalld(Base): + def __init__(self): + super().__init__() + self.cmd_str = self._set_cmd_str() + + def _set_cmd_str(self) -> str: + return "firewall-cmd" + + # 2024/3/20 下午 12:00 获取防火墙状态 + def status(self) -> bool: + ''' + @name 获取防火墙状态 + @author wzz <2024/3/20 下午 12:01> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + stdout, stderr = public.ExecShell("systemctl is-active firewalld") + if "not running" in stdout: + return False + return True + except Exception as e: + return False + + # 2024/3/20 下午 12:00 获取防火墙版本号 + def version(self) -> str: + ''' + @name 获取防火墙版本号 + @author wzz <2024/3/20 下午 12:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("firewall-cmd --version") + if "FirewallD is not running" in stdout: + return "Firewalld 没有启动,请先启动再试" + if stderr: + return "获取firewalld版本失败, err: {}".format(stderr) + return stdout.strip() + + # 2024/3/20 下午 12:08 启动防火墙 + def start(self) -> dict: + ''' + @name 启动防火墙 + @author wzz <2024/3/20 下午 12:08> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("systemctl start firewalld") + if stderr: + return self._result(False, "启动防火墙失败, err: {}".format(stderr)) + return self._result(True, "启动防火墙成功") + + # 2024/3/20 下午 12:10 停止防火墙 + def stop(self) -> dict: + ''' + @name 停止防火墙 + @author wzz <2024/3/20 下午 12:10> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("systemctl stop firewalld") + if stderr: + return self._result(False, "停止防火墙失败, err: {}".format(stderr)) + return self._result(True, "停止防火墙成功") + + # 2024/3/20 下午 12:11 重启防火墙 + def restart(self) -> dict: + ''' + @name 重启防火墙 + @author wzz <2024/3/20 下午 12:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("systemctl restart firewalld") + if stderr: + return self._result(False, "重启防火墙失败, err: {}".format(stderr)) + return self._result(True, "重启防火墙成功") + + # 2024/3/20 下午 12:11 重载防火墙 + def reload(self) -> dict: + ''' + @name 重载防火墙 + @author wzz <2024/3/20 下午 12:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("firewall-cmd --reload") + if stderr: + return self._result(False, "重载防火墙失败, err: {}".format(stderr)) + return self._result(True, "重载防火墙成功") + + # 2024/3/20 下午 12:12 获取所有防火墙端口列表 + def list_port(self) -> list: + ''' + @name 获取所有防火墙端口列表 + @author wzz <2024/3/20 下午 12:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["ports"] + self.list_output_port() + + # 2024/3/20 下午 12:12 获取防火墙端口INPUT列表 + def list_input_port(self) -> list: + ''' + @name 获取防火墙端口列表 + @author wzz <2024/3/20 下午 12:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["ports"] + + # 2024/3/22 上午 11:28 获取所有OUTPUT的direct 端口规则 + def list_output_port(self) -> list: + ''' + @name 获取所有OUTPUT的direct 端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + list_direct_rules = self.parse_direct_xml()["ports"] + datas = [] + for rule in list_direct_rules: + if rule.get("Chain") == "OUTPUT": + datas.append(rule) + return datas + + # 2024/3/20 下午 12:21 获取防火墙的rule的ip规则列表 + def list_address(self) -> list: + ''' + @name 获取防火墙的rule的ip规则列表 + @author wzz <2024/3/20 下午 2:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["rules"] + self.list_output_address() + + # 2024/3/20 下午 12:21 获取防火墙的rule input的ip规则列表 + def list_input_address(self) -> list: + ''' + @name 获取防火墙的rule input的ip规则列表 + @author wzz <2024/3/20 下午 2:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.parse_public_zone()["rules"] + + # 2024/3/22 下午 4:07 获取所有OUTPUT的direct ip规则 + def list_output_address(self) -> list: + ''' + @name 获取所有OUTPUT的direct ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + list_direct_rules = self.parse_direct_xml()["rules"] + datas = [] + for rule in list_direct_rules: + if rule.get("Chain") == "OUTPUT": + datas.append(rule) + return datas + + # 2024/3/20 下午 5:34 添加或删除防火墙端口 + def input_port(self, info: dict, operation: str) -> dict: + ''' + @name 添加或删除防火墙端口 + @author wzz <2024/3/20 下午 5:34> + @param info:{"Port": args[2], "Protocol": args[3]} + operation: add/remove + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + # 2024/3/25 下午 6:00 处理tcp/udp双协议的端口 + if info['Protocol'].find("/") != -1: + stdout, stderr = public.ExecShell( + "{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent" + .format( + cmd_str=self.cmd_str, + operation=operation, + port=info['Port'], + prot="tcp" + ) + ) + if stderr: + return self._result(False, "设置端口失败, err: {}".format(stderr)) + stdout, stderr = public.ExecShell( + "{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent" + .format( + cmd_str=self.cmd_str, + operation=operation, + port=info['Port'], + prot="udp" + ) + ) + if stderr: + return self._result(False, "设置端口失败, err: {}".format(stderr)) + else: + # 2024/3/25 下午 6:00 处理单协议的端口 + stdout, stderr = public.ExecShell( + "{cmd_str} --zone=public --{operation}-port={port}/{prot} --permanent" + .format( + cmd_str=self.cmd_str, + operation=operation, + port=info['Port'], + prot=info['Protocol'] + ) + ) + if stderr: + return self._result(False, "设置端口失败, err: {}".format(stderr)) + return self._result(True, "设置入站端口成功") + + # 2024/3/20 下午 6:02 设置output的防火墙端口规则 + def output_port(self, info: dict, operation: str) -> dict: + ''' + @name 设置output的防火墙端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + if info['Strategy'] == "accept": + info['Strategy'] = "ACCEPT" + elif info['Strategy'] == "drop": + info['Strategy'] = "DROP" + elif info['Strategy'] == "reject": + info['Strategy'] = "REJECT" + else: + return self._result(False, "不支持的策略: {}".format(info['Strategy'])) + + info['Port'] = info['Port'].replace("-", ":") + + if "/" in info['Protocol']: + info['Protocol'] = info['Protocol'].split("/") + for pp in info['Protocol']: + if not pp in ["tcp", "udp"]: + return self._result(False, "设置出站端口失败, err: 协议不支持 {}".format(pp)) + + stdout, stderr = public.ExecShell( + "{cmd_str} --permanent --direct --{operation}-rule ipv4 filter OUTPUT {priority} -p {prot} --dport {port} -j {strategy}" + .format( + cmd_str=self.cmd_str, + operation=operation, + priority=info['Priority'], + prot=pp, + port=info['Port'], + strategy=info['Strategy'] + ) + ) + if stderr: + return self._result(False, "设置出站端口失败, err: {}".format(stderr)) + else: + stdout, stderr = public.ExecShell( + "{cmd_str} --permanent --direct --{operation}-rule ipv4 filter OUTPUT {priority} -p {prot} --dport {port} -j {strategy}" + .format( + cmd_str=self.cmd_str, + operation=operation, + priority=info['Priority'], + prot=info['Protocol'], + port=info['Port'], + strategy=info['Strategy'] + ) + ) + + if stderr: + return self._result(False, "设置出站端口失败, err: {}".format(stderr)) + + return self._result(True, "设置出站端口成功") + + def set_rich_rule(self, info: dict, operation: str) -> dict: + ''' + @name 添加或删除复杂规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + rule_str = "rule family={}".format(info['Family'].lower()) + if "Address" in info and info["Address"] != "all": + rule_str += " source address={}".format(info['Address']) + if info.get("Port"): + rule_str += " port port={}".format(info['Port']) + if info.get("Protocol"): + rule_str += " protocol={}".format(info['Protocol']) + rule_str += " {}".format(info['Strategy']) + + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-rich-rule='{}' --permanent" + .format(self.cmd_str, operation, rule_str)) + + if stderr: + return self._result(False, "设置规则:{} 失败, err: {}".format(operation, rule_str, stderr)) + return self._result(True, "设置规则成功".format(operation)) + + # 2024/3/22 上午 11:35 添加或删除复杂规则 + def rich_rules(self, info: dict, operation: str) -> dict: + ''' + @name 添加或删除复杂规则 + @author wzz <2024/3/22 上午 11:35> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的规则操作: {}".format(operation)) + + if "Protocol" in info and info["Protocol"] == "all": + info["Protocol"] = "tcp/udp" + + if "Protocol" in info and info['Protocol'].find("/") != -1: + result_list = [] + for protocol in info['Protocol'].split("/"): + info['Protocol'] = protocol + result_list.append(self.set_rich_rule(info, operation)) + + return {"status": True, "msg": result_list} + else: + return self.set_rich_rule(info, operation) + + # 2024/3/24 下午 10:43 设置output的防火墙ip规则 + def output_rich_rules(self, info: dict, operation: str) -> dict: + ''' + @name 设置output的防火墙ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + if info['Strategy'] == "accept": + info['Strategy'] = "ACCEPT" + elif info['Strategy'] == "drop": + info['Strategy'] = "DROP" + elif info['Strategy'] == "reject": + info['Strategy'] = "REJECT" + else: + return self._result(False, "不支持的策略: {}".format(info['Strategy'])) + + rich_rules = self.cmd_str + " --permanent --direct --{0}-rule ipv4 filter OUTPUT".format(operation) + if "Priority" in info: + rich_rules += " {}".format(info["Priority"]) + if "Address" in info: + rich_rules += " -d {}".format(info["Address"]) + if "Protocol" in info: + rich_rules += " -p {}".format(info["Protocol"]) + if "Port" in info: + info["Port"] = info["Port"].replace("-", ":") + rich_rules += " --dport {}".format(info["Port"]) + if "Strategy" in info: + rich_rules += " -j {}".format(info["Strategy"]) + + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + return self._result(False, "设置出站地址失败, err: {}".format(stderr)) + if "NOT_ENABLED" in stderr: + return self._result(False, "规则不存在") + return self._result(True, "设置出站地址成功") + + # 2024/3/22 下午 12:22 解析public区域的防火墙规则 + def parse_public_zone(self) -> dict: + ''' + @name 解析public区域的防火墙规则 + @author wzz <2024/3/22 下午 12:22> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"services": services, "ports": ports, "rules": rules} rules是ip规则 + ''' + try: + import xml.etree.ElementTree as ET + file_path = "/etc/firewalld/zones/public.xml" + if not os.path.exists(file_path): + return {"services": [], "ports": [], "rules": [], "forward_ports": []} + + services = [] + ports = [] + rules = [] + forward_ports = [] + + tree = ET.parse(file_path) + root = tree.getroot() + + for elem in root: + # 2024/3/22 下午 3:01 服务规则 + if elem.tag == "service": + services.append(elem.attrib['name']) + # 2024/3/22 下午 3:01 端口规则 + elif elem.tag == "port": + port = { + "Protocol": elem.attrib["protocol"], + "Port": elem.attrib["port"], + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "INPUT", + } + ports.append(port) + # 2024/3/22 下午 3:01 复杂的规则配置 + elif elem.tag == "rule": + rule = {"Family": elem.attrib["family"] if "family" in elem.attrib else "ipv4"} + for subelem in elem: + rule["Strategy"] = "accept" + if subelem.tag == "source": + if "address" in subelem.attrib: + rule["Address"] = subelem.attrib["address"] + else: + continue + rule["Address"] = "all" if rule["Address"] == "Anywhere" else rule["Address"] + elif subelem.tag == "port": + rule["port"] = {"protocol": subelem.attrib["protocol"], "port": subelem.attrib["port"]} + elif subelem.tag == "drop": + rule["Strategy"] = "drop" + elif subelem.tag == "accept": + rule["Strategy"] = "accept" + elif subelem.tag == "forward-port": + rule["forward-port"] = { + "protocol": subelem.attrib["protocol"], + "S_Port": subelem.attrib["port"], + "T_Address": subelem.attrib["to-addr"], + "T_Port": subelem.attrib["to-port"], + } + + # 2024/3/22 下午 3:02 如果端口在里面,就放到端口规则列表中,否则就是ip规则 + if "port" in rule: + ports.append({ + "Protocol": rule["port"]["protocol"] if "protocol" in rule else "tcp", + "Port": rule["port"]["port"], + "Strategy": rule["Strategy"] if "Strategy" in rule else "accept", + "Family": rule["Family"] if "Family" in rule else "ipv4", + "Address": rule["Address"] if "Address" in rule else "all", + "Chain": "INPUT", + }) + # 2024/3/25 下午 5:01 处理带源ip的端口转发规则 + elif "forward-port" in rule: + forward_ports.append({ + "type": "port_forward", + "number": len(forward_ports) + 1, + "Protocol": rule["forward-port"]["protocol"], + "S_Address": rule["Address"], + "S_Port": rule["forward-port"]["S_Port"], + "T_Address": rule["forward-port"]["T_Address"], + "T_Port": rule["forward-port"]["T_Port"], + }) + else: + if "Address" not in rule: + continue + + rule["Chain"] = "INPUT" + rules.append(rule) + # 2024/3/25 下午 2:57 端口转发规则 + elif elem.tag == "forward-port": + port = { + "type": "port_forward", + "number": len(forward_ports) + 1, + "Protocol": elem.attrib["protocol"] if "protocol" in elem.attrib else "tcp", + "S_Address": "", + "S_Port": elem.attrib["port"] if "port" in elem.attrib else "", + "T_Address": elem.attrib["to-addr"] if "to-addr" in elem.attrib else "", + "T_Port": elem.attrib["to-port"] if "to-port" in elem.attrib else "", + } + forward_ports.append(port) + + return {"services": services, "ports": ports, "rules": rules, "forward_ports": forward_ports} + except Exception as e: + return {"services": [], "ports": [], "rules": [], "forward_ports": []} + + # 2024/3/22 下午 2:32 解析direct.xml的防火墙规则 + def parse_direct_xml(self) -> dict: + ''' + @name 解析direct.xml的防火墙规则 + @author wzz <2024/3/22 下午 2:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + try: + import xml.etree.ElementTree as ET + file_path = "/etc/firewalld/direct.xml" + if not os.path.exists(file_path): + return {"ports": [], "rules": []} + + ports = [] + rules = [] + + tree = ET.parse(file_path) + root = tree.getroot() + + for elem in root: + if elem.tag == "rule": + protocol = "tcp" + port = "" + strategy = "" + address = "" + + elem_t = elem.text.split(" ") + # 2024/3/22 下午 4:14 解析 Options 得到端口,策略,地址,协议 + for i in elem_t: + if i == "-p": + protocol = elem_t[elem_t.index(i) + 1] # 如果找到匹配项,结果为索引+1的值,-p tcp,值为tcp + elif i == "--dport": + port = elem_t[elem_t.index(i) + 1] + elif i == "-j": + strategy = elem_t[elem_t.index(i) + 1] + elif i == "-d": + address = elem_t[elem_t.index(i) + 1] + + rule = { + "Family": elem.attrib["ipv"], + "Chain": elem.attrib["chain"], + "Strategy": strategy.lower(), + "Address": address if address != "" else "all", + # "Options": elem.text + } + + # 2024/3/22 下午 4:13 如果端口不为空,就是端口规则 + if port != "": + rule["Port"] = port + rule["Protocol"] = protocol + + ports.append(rule) + # 2024/3/22 下午 4:14 如果端口为空,就是ip规则 + else: + rules.append(rule) + + return {"ports": ports, "rules": rules} + except Exception as e: + return {"ports": [], "rules": []} + + # 2024/3/22 下午 4:54 检查是否开启了masquerade,没有则开启 + def check_masquerade(self) -> dict: + ''' + @name 检查是否开启了masquerade,没有则开启 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + stdout, stderr = public.ExecShell("firewall-cmd --query-masquerade") + if "no" in stdout: + stdout, stderr = public.ExecShell("firewall-cmd --add-masquerade") + if stderr: + return self._result(False, "开启masquerade失败, err: {}".format(stderr)) + return self._result(True, "开启masquerade成功") + return self._result(True, "masquerade已经开启") + + # 2024/3/22 下午 4:57 设置端口转发 + def port_forward(self, info: dict, operation: str) -> dict: + ''' + @name 设置端口转发 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if operation not in ["add", "remove"]: + return self._result(False, "不支持的操作: {}".format(operation)) + + if operation == "add": + check_masquerade = self.check_masquerade() + if not check_masquerade["status"]: + return check_masquerade + + # 2024/3/25 下午 6:07 处理有源地址的情况 + if "S_Address" in info and info["S_Address"] != "": + # 2024/3/25 下午 6:05 处理tcp/udp双协议的情况 + if info['Protocol'].find("/") != -1: + rich_rules = self.cmd_str + " --zone=public" + rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"tcp\" to-port=\"{4}\" to-addr=\"{5}\"' --permanent".format( + operation, + info['Family'], + info['S_Address'], + info['S_Port'], + info['T_Port'], + info['T_Address'], + ) + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + rich_rules = self.cmd_str + " --zone=public" + rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"udp\" to-port=\"{4}\" to-addr=\"{5}\"' --permanent".format( + operation, + info['Family'], + info['S_Address'], + info['S_Port'], + info['T_Port'], + info['T_Address'], + ) + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + # 2024/3/25 下午 6:05 处理单协议的情况 + else: + rich_rules = self.cmd_str + " --zone=public" + rich_rules += " --{0}-rich-rule='rule family=\"{1}\" source address=\"{2}\" forward-port port=\"{3}\" protocol=\"{4}\" to-port=\"{5}\" to-addr=\"{6}\"'".format( + operation, + info['Family'], + info['S_Address'], + info['S_Port'], + info['Protocol'], + info['T_Port'], + info['T_Address'], + ) + rich_rules += " --permanent" + stdout, stderr = public.ExecShell(rich_rules) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + # 2024/3/25 下午 6:08 处理没有源地址的情况 + else: + # 2024/3/25 下午 6:05 处理tcp/udp双协议的情况 + if info['Protocol'].find("/") != -1: + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent" + .format(self.cmd_str, operation, info['S_Port'], "udp", info['T_Port'], info['T_Address']) + ) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent" + .format(self.cmd_str, operation, info['S_Port'], "tcp", info['T_Port'], info['T_Address']) + ) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + # 2024/3/25 下午 6:09 处理单协议的情况 + else: + stdout, stderr = public.ExecShell( + "{} --zone=public --{}-forward-port='port={}:proto={}:toport={}:toaddr={}' --permanent" + .format(self.cmd_str, operation, info['S_Port'], info['Protocol'], info['T_Port'], info['T_Address']) + ) + if "success" not in stdout and stderr: + if "ALREADY_ENABLED" in stderr: + return self._result(True, "端口转发规则已经存在") + return self._result(False, "设置端口转发失败, err: {}".format(stderr)) + + return self._result(True, "设置端口转发成功") + + # 2024/3/25 下午 2:37 获取所有端口转发规则 + def list_port_forward(self) -> list: + ''' + @name 获取所有端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + return self.parse_public_zone()["forward_ports"] + + +if __name__ == '__main__': + args = sys.argv + firewall = Firewalld() + Firewalld_status = firewall.status() + if len(args) < 2: + print("Welcome to the Firewalld command-line interface!") + print("Firewall status is :", Firewalld_status) + print("Firewall version: ", firewall.version()) + if Firewalld_status == "not running": + print("Firewalld未启动,请启动Firewalld后再执行命令!") + print("启动命令: start") + print() + sys.exit(1) + print("Usage: ") + print("status: Get the status of the firewall.") + print("version: Get the version of the firewall.") + print("start: Start the firewall.") + print("stop: Stop the firewall.") + print("restart: Restart the firewall.") + print("reload: Reload the firewall.") + print("list_input_port: List all input ports.") + print("list_input_address: List all input address rules.") + print("input_port: Add or remove input port.") + print("output_port: Add or remove output port.") + print("list_output_port: List all output ports.") + print("list_output_address: List all output address rules.") + print("rich_rules: Add or remove rich rules.") + sys.exit(1) + + if args[1] == "status": + print("Firewall status is :", Firewalld_status) + elif args[1] == "version": + print("Firewall version: ", firewall.version()) + elif args[1] == "start": + print(firewall.start()) + elif args[1] == "stop": + print(firewall.stop()) + elif args[1] == "restart": + print(firewall.restart()) + elif args[1] == "reload": + print(firewall.reload()) + elif args[1] == "list_input_port": + print(firewall.list_input_port()) + elif args[1] == "list_input_address": + print(firewall.list_input_address()) + elif args[1] == "input_port": + if len(args) < 4: + print("Usage: input_port Port Protocol") + sys.exit(1) + print(firewall.input_port({"Port": args[2], "Protocol": args[3]}, args[4])) + elif args[1] == "output_port": + if len(args) < 6: + print("Usage: output_port Port Protocol Strategy Priority") + sys.exit(1) + print(firewall.output_port({"Port": args[2], "Protocol": args[3], "Strategy": args[4], "Priority": args[5]}, args[6])) + elif args[1] == "list_output_port": + print(firewall.list_output_port()) + elif args[1] == "list_output_address": + print(firewall.list_output_address()) + elif args[1] == "rich_rules": + if len(args) < 4: + print("Usage: rich_rules Family Address Port Protocol Strategy") + sys.exit(1) + print(firewall.rich_rules({"Family": args[2], "Address": args[3], "Port": args[4], "Protocol": args[5], "Strategy": args[6]}, args[7])) + elif args[1] == "output_rich_rules": + if len(args) < 6: + print("Usage: output_rich_rules Family Address Port Protocol Strategy Priority") + sys.exit(1) + print(firewall.output_rich_rules({"Family": args[2], "Address": args[3], "Port": args[4], "Protocol": args[5], "Strategy": args[6], "Priority": args[7]}, args[8])) + elif args[1] == "port_forward": + if len(args) < 7: + print("Usage: port_forward Port Protocol ToPort ToAddr") + sys.exit(1) + print(firewall.port_forward({"Port": args[2], "Protocol": args[3], "ToPort": args[4], "ToAddr": args[5]}, args[6])) + else: + print("Command not found!") + sys.exit(1) + diff --git a/class_v2/firewallModelV2/app/iptables.py b/class_v2/firewallModelV2/app/iptables.py new file mode 100644 index 00000000..7ae89edc --- /dev/null +++ b/class_v2/firewallModelV2/app/iptables.py @@ -0,0 +1,939 @@ +#!/www/server/panel/pyenv/bin/python3.7 +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - iptables封装库 +# ------------------------------ + +import re +import subprocess +import os +import sys + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +from firewallModelV2.app.appBase import Base + + +# import re + +class Iptables(Base): + def __init__(self): + self.cmd_str = self._set_cmd_str() + self.protocol = { + "6": "tcp", + "17": "udp", + "0": "all" + } + + def _set_cmd_str(self): + return "iptables" + + # 2024/3/19 下午 5:00 获取系统防火墙的运行状态 + def status(self): + ''' + @name 获取系统防火墙的运行状态 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return "running" + + # 2024/3/19 下午 5:00 获取系统防火墙的版本号 + def version(self): + ''' + @name 获取系统防火墙的版本号 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = public.ExecShell("iptables -v 2>&1|awk '{print $2}'|head -1")[0].replace("\n", "") + if result == "": + return "未知的iptables版本" + return result + except Exception as e: + return "未知版本" + + # 2024/3/19 下午 5:00 启动防火墙 + def start(self): + ''' + @name 启动防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持设置状态") + + # 2024/3/19 下午 5:00 停止防火墙 + def stop(self): + ''' + @name 停止防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持停止") + + # 2024/3/19 下午 4:59 重启防火墙 + def restart(self): + ''' + @name 重启防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持重启") + + # 2024/3/19 下午 4:59 重载防火墙 + def reload(self): + ''' + @name 重载防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self._result(True, "当前系统防火墙为iptables,不支持重载") + + # 2024/3/19 下午 3:36 检查表名是否合法 + def check_table_name(self, table_name): + ''' + @name 检查表名是否合法 + @param "table_name": "filter/nat/mangle/raw/security" + @return dict{"status":True/False,"msg":"提示信息"} + ''' + table_names = ['filter', 'nat', 'mangle', 'raw', 'security'] + if table_name not in table_names: + return False + return True + + # 2024/3/19 下午 3:55 解析规则列表输出,返回规则列表字典 + def parse_rules(self, stdout): + ''' + @name 解析规则列表输出,返回规则列表字典 + @author wzz <2024/3/19 下午 3:53> + 字段含义: + "number": 规则编号,对应规则在链中的顺序。 + "chain": 规则所属的链的名称。 + "pkts": 规则匹配的数据包数量。 + "bytes": 规则匹配的数据包字节数。 + "target": 规则的目标动作,表示数据包匹配到该规则后应该执行的操作。 + "prot": 规则适用的协议类型。 + "opt": 规则的选项,包括规则中使用的匹配条件或特定选项。 + "in": 规则匹配的数据包的输入接口。 + "out": 规则匹配的数据包的输出接口。 + "source": 规则匹配的数据包的源地址。 + "destination": 规则匹配的数据包的目标地址。 + "options": 规则的其他选项或说明,通常是规则中的注释或附加信息。 + + protocol(port协议头中数字对应的协议类型): + 0: 表示所有协议 + 1: ICMP(Internet 控制消息协议) + 6: TCP(传输控制协议) + 17: UDP(用户数据报协议) + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + lines = stdout.strip().split('\n') + rules = [] + current_chain = None + for line in lines: + if line.startswith("Chain"): + current_chain = line.split()[1] + elif (line.startswith("target") or line.strip() == "" or "source" in line or + "Warning: iptables-legacy tables present" in line): + # 过滤表头,空行,警告 + continue + else: + rule_info = line.split() + rule = { + "number": rule_info[0], + "chain": current_chain, + "pkts": rule_info[1], + "bytes": rule_info[2], + "target": rule_info[3], + "prot": rule_info[4], + "opt": rule_info[5], + "in": rule_info[6], + "out": rule_info[7], + "source": rule_info[8], + "destination": rule_info[9], + "options": " ".join(rule_info[10:]).strip() + } + rules.append(rule) + return rules + + # 2024/3/19 下午 3:02 列出指定表的指定链的规则 + def list_rules(self, parm): + ''' + @name 列出指定表的指定链的规则 + @author wzz <2024/3/19 下午 3:02> + @param + @return + ''' + try: + if not self.check_table_name(parm['table']): + return "错误: 不支持的表名." + stdout = subprocess.check_output( + [self.cmd_str, '-t', parm['table'], '-L', parm['chain_name'], '-nv', '--line-numbers'], + stderr=subprocess.STDOUT, universal_newlines=True + ) + return self.parse_rules(stdout) + except Exception as e: + return [] + + # 2024/4/29 下午12:16 列出iptables中所有INPUT和OUTPUT的端口规则 + def list_port(self): + ''' + @name 列出iptables中所有INPUT和OUTPUT的端口规则 + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "INPUT" + }] + ''' + try: + list_port = self.list_input_port() + self.list_output_port() + for i in list_port: + i["Strategy"] = i["Strategy"].lower() + return list_port + except Exception as e: + return [] + + # 2024/4/29 下午2:39 列出防火墙中所有的INPUT端口规则 + def list_input_port(self): + ''' + @name 列出防火墙中所有的INPUT端口规则 + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "INPUT" + }] + ''' + try: + list_port = self.get_chain_port("INPUT") + for i in list_port: + i["Strategy"] = i["Strategy"].lower() + return list_port + except Exception as e: + return [] + + # 2024/4/29 下午2:39 列出防火墙中所有的OUTPUT端口规则 + def list_output_port(self): + ''' + @name 列出防火墙中所有的OUTPUT端口规则 + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "OUTPUT" + }] + ''' + try: + list_port = self.get_chain_port("OUTPUT") + for i in list_port: + i["Strategy"] = i["Strategy"].lower() + return list_port + except Exception as e: + return [] + + # 2024/4/29 下午3:28 根据链来获取端口规则,暂时只支持INPUT/OUTPUT链 + def get_chain_port(self, chain): + ''' + @name 根据链来获取端口规则 + @author wzz <2024/4/29 下午3:29> + @param chain = INPUT/OUTPUT + @return [{ + "Protocol": "tcp", + "Port": "8888", + "Strategy": "accept", + "Family": "ipv4", + "Address": "all", + "Chain": "OUTPUT" + }] + ''' + if chain not in ["INPUT", "OUTPUT"]: + return [] + + try: + stdout = self.get_chain_data(chain) + if stdout == "": + return [] + + lines = stdout.strip().split('\n') + rules = [] + for line in lines: + if line.startswith("Chain"): + continue + if not "dpt:" in line and not "multiport sports" in line: + continue + rule_info = line.split() + if rule_info[0] == "num": + continue + if not rule_info[3] in ["ACCEPT", "DROP", "REJECT"]: + continue + if not rule_info[4] in self.protocol: + continue + if not "dpt" in rule_info[-1] and not "-" in rule_info[-1] and not ":" in rule_info[-1]: + continue + + if ":" in rule_info[-1] and not "dpt" in rule_info[-1]: + Port = rule_info[-1] + elif "-" in rule_info[-1]: + Port = rule_info[-5].split(":")[1] + else: + Port = rule_info[-1].split(":")[1] + + if "source IP range" in line and "multiport sports" in line: + Address = rule_info[-4] + elif not "0.0.0.0/0" in rule_info[8]: + Address = rule_info[8] + elif "-" in rule_info[-1]: + Address = rule_info[-1] + else: + Address = "all" + + rule = { + "Protocol": self.protocol[rule_info[4]], + "Port": Port, + "Strategy": rule_info[3], + "Family": "ipv4", + "Address": Address, + "Chain": chain, + } + rules.append(rule) + + return rules + except Exception as e: + return [] + + # 2024/4/29 下午3:28 根据链来获取IP规则,暂时只支持INPUT/OUTPUT链 + def get_chain_ip(self, chain): + ''' + @name 根据链来获取端口规则 + @author wzz <2024/4/29 下午3:29> + @param chain = INPUT/OUTPUT + @return [ + { + "Family": "ipv4", + "Address": "192.168.1.190", + "Strategy": "accept", + "Chain": "INPUT" + } + ] + ''' + if chain not in ["INPUT", "OUTPUT"]: + return [] + + try: + stdout = self.get_chain_data(chain) + if stdout == "": + return [] + + lines = stdout.strip().split('\n') + rules = [] + for line in lines: + if line.startswith("Chain"): + continue + if "dpt:" in line or "multiport sports" in line: + continue + rule_info = line.split() + if rule_info[0] == "num": + continue + if not rule_info[3] in ["ACCEPT", "DROP", "REJECT"]: + continue + if not rule_info[4] in self.protocol: + continue + + Address = "" + if not "0.0.0.0/0" in rule_info[8]: + Address = rule_info[8] + elif "0.0.0.0/0" in rule_info[8] and "-" in rule_info[-1]: + Address = rule_info[-1] + + if Address == "": + continue + + rule = { + "Family": "ipv4", + "Address": Address, + "Strategy": rule_info[3], + "Chain": chain, + } + rules.append(rule) + + return rules + except Exception as e: + return [] + + # 2024/4/29 下午4:01 获取指定链的数据 + def get_chain_data(self, chain): + ''' + @name 获取指定链的数据 + @author wzz <2024/4/29 下午4:01> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + cmd = "{} -t filter -L {} -nv --line-numbers".format(self.cmd_str, chain) + stdout, stderr = public.ExecShell(cmd) + return stdout + except Exception as e: + return "" + + # 2024/4/29 下午2:46 列出防火墙中所有的INPUT和OUTPUT的ip规则 + def list_address(self): + ''' + @name 列出防火墙中所有的ip规则 + @author wzz <2024/4/29 下午2:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return + ''' + try: + list_address = self.get_chain_ip("INPUT") + self.get_chain_ip("OUTPUT") + for i in list_address: + i["Strategy"] = i["Strategy"].lower() + return list_address + except Exception as e: + return [] + + # 2024/4/29 下午2:48 列出防火墙中所有input的ip规则 + def list_input_address(self): + ''' + @name 列出防火墙中所有input的ip规则 + @author wzz <2024/4/29 下午2:48> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + list_address = self.get_chain_ip("INPUT") + for i in list_address: + i["Strategy"] = i["Strategy"].lower() + return list_address + except Exception as e: + return [] + + # 2024/4/29 下午2:49 列出防火墙中所有output的ip规则 + def list_output_address(self): + ''' + @name 列出防火墙中所有output的ip规则 + @author wzz <2024/4/29 下午2:49> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + list_address = self.get_chain_ip("OUTPUT") + for i in list_address: + i["Strategy"] = i["Strategy"].lower() + return list_address + except Exception as e: + return [] + + # 2024/4/29 下午2:49 添加INPUT端口规则 + def input_port(self, info, operation): + ''' + @name 添加INPUT端口规则 + @author wzz <2024/4/29 下午2:50> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return self.set_chain_port(info, operation, "INPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午2:50 设置output端口策略 + def output_port(self, info, operation): + ''' + @name 设置output端口策略 + @author wzz <2024/4/29 下午2:50> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + return self.set_chain_port(info, operation, "OUTPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午4:49 添加/删除指定链的端口规则 + def set_chain_port(self, info, operation, chain): + ''' + @name 添加/删除指定链的端口规则 + @author wzz <2024/4/29 下午4:49> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not chain in ["INPUT", "OUTPUT"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的链类型")) + + if info['Protocol'] not in ["tcp", "udp"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的协议类型")) + if info["Strategy"] == "accept": + info["Strategy"] = "ACCEPT" + elif info["Strategy"] == "drop": + info["Strategy"] = "DROP" + elif info["Strategy"] == "reject": + info["Strategy"] = "REJECT" + else: + return self._result(False, "设置端口规则失败:{}".format("不支持的策略类型")) + + if operation == "add": + operation = "-I" + elif operation == "remove": + operation = "-D" + + rule = "{} -t filter {} {} -p {} --dport {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Strategy'] + ) + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "设置端口规则失败:{}".format(stderr)) + return self._result(True, "设置端口规则成功") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午5:01 添加/删除指定链的复杂端口规则 + def set_chain_rich_port(self, info, operation, chain): + ''' + @name 添加/删除指定链的复杂端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not chain in ["INPUT", "OUTPUT"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的链类型")) + + if "Address" in info and info["Address"] == "": + info["Address"] = "all" + if "Address" in info and public.is_ipv6(info['Address']): + return self._result(False, "设置端口规则失败:{}".format("不支持的IPV6地址")) + + if info['Protocol'] not in ["tcp", "udp"]: + return self._result(False, "设置端口规则失败:{}".format("不支持的协议类型")) + if info["Strategy"] == "accept": + info["Strategy"] = "ACCEPT" + elif info["Strategy"] == "drop": + info["Strategy"] = "DROP" + elif info["Strategy"] == "reject": + info["Strategy"] = "REJECT" + else: + return self._result(False, "设置端口规则失败:{}".format("不支持的策略类型")) + + if operation == "add": + operation = "-I" + elif operation == "remove": + operation = "-D" + + info['Port'] = info['Port'].replace("-", ":") + info["Address"] = info["Address"].replace(":", "-") + if ":" in info['Port'] or "-" in info['Port']: + if ":" in info["Address"] or "-" in info["Address"]: + # iptables -t filter -I INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -p tcp -m multiport --sports 8000:9000 -j ACCEPT + rule = "{} -t filter {} {} -m iprange --src-range {} -p {} -m multiport --sports {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Address'], + info['Protocol'], + info['Port'], + info['Strategy'] + ) + else: + # iptables -t filter -I INPUT -p tcp -m multiport --sports 8000:9000 -s 192.168.1.100 -j ACCEPT + rule = "{} -t filter {} {} -p {} -m multiport --sports {} -s {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Address'], + info['Strategy'] + ) + else: + if ":" in info["Address"] or "-" in info["Address"]: + # iptables -t filter -I OUTPUT -p tcp --dport 22333 -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT + rule = "{} -t filter {} {} -p {} --dport {} -m iprange --src-range {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Address'], + info['Strategy'] + ) + else: + # iptables -t filter -I OUTPUT -p tcp --dport 22333 -s 192.168.1.0/24 -j ACCEPT + rule = "{} -t filter {} {} -p {} --dport {} -s {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Protocol'], + info['Port'], + info['Address'], + info['Strategy'] + ) + + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "设置端口规则失败:{}".format(stderr)) + return self._result(True, "设置端口规则成功") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午5:01 添加/删除指定链的复杂ip规则 + def set_chain_rich_ip(self, info, operation, chain): + ''' + @name 添加/删除指定链的复杂ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not chain in ["INPUT", "OUTPUT"]: + return self._result(False, "设置规则失败:{}".format("不支持的链类型")) + + if "Address" in info and info["Address"] == "": + info["Address"] = "all" + if "Address" in info and public.is_ipv6(info['Address']): + return self._result(False, "设置规则失败:{}".format("不支持的IPV6地址")) + + if info["Strategy"] == "accept": + info["Strategy"] = "ACCEPT" + elif info["Strategy"] == "drop": + info["Strategy"] = "DROP" + elif info["Strategy"] == "reject": + info["Strategy"] = "REJECT" + else: + return self._result(False, "设置规则失败:{}".format("不支持的策略类型")) + + if operation == "add": + operation = "-I" + elif operation == "remove": + operation = "-D" + + if ":" in info["Address"] or "-" in info["Address"]: + # iptables -t filter -I INPUT -m iprange --src-range 192.168.1.100-192.168.1.200 -j ACCEPT + rule = "{} -t filter {} {} -m iprange --src-range {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Address'], + info['Strategy'] + ) + else: + # iptables -t filter -I INPUT -s 192.168.1.100 -j ACCEPT + rule = "{} -t filter {} {} -s {} -j {}".format( + self.cmd_str, + operation, + chain, + info['Address'], + info['Strategy'] + ) + + + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "设置规则失败:{}".format(stderr)) + return self._result(True, "设置规则成功") + except Exception as e: + return self._result(False, "设置规则失败:{}".format(str(e))) + + # 2024/4/29 下午2:51 INPUT复杂一些的规则管理 + def rich_rules(self, info, operation): + ''' + @name + @author wzz <2024/4/29 下午2:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if "Priority" in info and not "Port" in info: + return self.set_chain_rich_ip(info, operation, "INPUT") + else: + return self.set_chain_rich_port(info, operation, "INPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/4/29 下午2:52 OUTPUT复杂一些的规则管理 + def output_rich_rules(self, info, operation): + ''' + @name OUTPUT复杂一些的规则管理 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if "Priority" in info and not "Port" in info: + return self.set_chain_rich_ip(info, operation, "OUTPUT") + else: + return self.set_chain_rich_port(info, operation, "OUTPUT") + except Exception as e: + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/3/19 下午 3:03 清空指定链中的所有规则 + def flush_chain(self, chain_name): + ''' + @name 清空指定链中的所有规则 + @author wzz <2024/3/19 下午 3:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + subprocess.check_output( + [self.cmd_str, '-F', chain_name], stderr=subprocess.STDOUT, universal_newlines=True + ) + return chain_name + " chain flushed successfully." + except Exception as e: + return "Failed to flush " + chain_name + " chain." + + # 2024/3/19 下午 3:03 获取当前系统中可用的链的名称列表 + def get_chain_names(self, parm): + ''' + @name 获取当前系统中可用的链的名称列表 + @author wzz <2024/3/19 下午 3:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not self.check_table_name(parm['table']): + return "错误: 不支持的表名." + stdout = subprocess.check_output( + [self.cmd_str, '-t', parm['table'], '-L'], stderr=subprocess.STDOUT, universal_newlines=True + ) + chain_names = re.findall(r"Chain\s([A-Z]+)", stdout) + return chain_names + except Exception as e: + return [] + + # 2024/3/19 下午 3:17 构造端口转发规则,然后调用insert_rule方法插入规则 + def port_forward(self, info, operation): + ''' + @name 构造端口转发规则,然后调用insert_rule方法插入规则 + @param "info": { + "Protocol": "tcp/udp", + "S_Port": "80", + "T_Address": "0.0.0.0/0", + "T_Port": "8080" + } + @param "operation": "add" or "remove" + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + rule = " -p {}".format(info['Protocol']) + + if "S_Address" in info and info['S_Address'] != "": + rule += " -s {}".format(info['S_Address']) + + rule += " --dport {0} -j DNAT --to-destination {1}:{2}".format( + info['S_Port'], + info['T_Address'], + info['T_Port'], + ) + + parm = { + "table": "nat", + "chain_name": "PREROUTING", + "rule": rule + } + if operation not in ["add", "remove"]: + return "请输入正确的操作类型. (add/remove)" + + if operation == "add": + parm['type'] = "-I" + elif operation == "remove": + parm['type'] = "-D" + return self.rule_manage(parm) + except Exception as e: + return self._result(False, "设置端口转发规则失败:{}".format(str(e))) + + # 2024/3/19 下午 3:03 在指定链中管理规则 + def rule_manage(self, parm): + ''' + @name 在指定链中管理规则 + @author wzz <2024/3/19 下午 3:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if not self.check_table_name(parm['table']): + return self._result(False, "不支持的表名{}".format(parm['table'])) + + rule = "{} -t {} {} {} {}".format( + self.cmd_str, parm['table'], parm['type'], parm['chain_name'], parm['rule'] + ) + stdout, stderr = public.ExecShell(rule) + if stderr: + return self._result(False, "规则设置失败:{}".format(stderr)) + + return self._result(True, "规则设置成功") + except Exception as e: + return self._result(False, "规则设置失败: {}".format(str(e))) + + # 2024/4/29 下午5:55 获取所有端口转发列表 + def list_port_forward(self): + ''' + @name 获取所有端口转发列表 + @author wzz <2024/4/29 下午5:55> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.get_nat_prerouting_rules() + + # 2024/3/19 下午 4:00 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回 + def get_nat_prerouting_rules(self): + ''' + @name 调用list_rules获取所有nat表中的PREROUTING链的规则(端口转发规则),并分析成字典返回 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + port_forward_rules = self.list_rules({"table": "nat", "chain_name": "PREROUTING"}) + rules = [] + for rule in port_forward_rules: + if rule["target"] != "DNAT": continue + options = rule["options"].split(' ') + + protocol = "TCP" + if rule["prot"] == "6" or rule["prot"] == "tcp": + protocol = "TCP" + elif rule["prot"] == "17" or rule["prot"] == "udp": + protocol = "UDP" + elif rule["prot"] == "0" or rule["prot"] == "all": + protocol = "TCP/UDP" + rules.append({ + "type": "port_forward", + "number": rule["number"], + "S_Address": rule["source"], + "S_Port": options[1].split("dpt:")[1], + "T_Address": options[2].split("to:")[1].split(":")[0], + "T_Port": options[2].split("to:")[1].split(":")[1], + "Protocol": protocol.lower() + }) + return rules + except Exception as e: + return [] + + # 2024/4/29 下午2:43 格式化输出json + def format_json(self, data): + ''' + @name 格式化输出json + @param "data": json数据 + @return json字符串 + ''' + import json + from pygments import highlight, lexers, formatters + + formatted_json = json.dumps(data, indent=3) + colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), + formatters.TerminalFormatter()) + return colorful_json + + +if __name__ == '__main__': + args = sys.argv + firewall = Iptables() + if len(args) < 2: + print("Welcome to the iptables command-line interface!") + print() + print("Available options:") + print("list_rules: list_rules
                                    ") + print("flush_chain: flush_chain
                                    ") + print("get_chain_names: get_chain_names
                                    ") + print("port_forward: port_forward ") + print("get_nat_prerouting_rules: get_nat_prerouting_rules") + print() + sys.exit(1) + if args[1] == "list_rules": + # firewall.list_rules({"table": args[2], "chain_name": args[3]}) + print(firewall.list_rules({"table": args[2], "chain_name": args[3]})) + elif args[1] == "flush_chain": + print(firewall.flush_chain(args[2])) + elif args[1] == "get_chain_names": + table = args[2] if len(args) > 2 else "filter" + print(firewall.get_chain_names({"table": table})) + elif args[1] == "port_forward": + if len(args) < 8: + print("传参使用方法: port_forward ") + sys.exit(1) + + info = { + "S_Address": args[2], + "S_Port": args[3], + "T_Address": args[4], + "T_Port": args[5], + "Protocol": args[6] + } + print(firewall.port_forward(info, args[7])) + elif args[1] == "get_nat_prerouting_rules": + import json + from pygments import highlight, lexers, formatters + + formatted_json = json.dumps(firewall.get_nat_prerouting_rules(), indent=3) + colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), + formatters.TerminalFormatter()) + print(colorful_json) + # print(firewall.get_nat_prerouting_rules()) + elif args[1] == "list_port": + print(firewall.format_json(firewall.list_port())) + elif args[1] == "list_input_port": + print(firewall.format_json(firewall.list_input_port())) + elif args[1] == "list_output_port": + print(firewall.format_json(firewall.list_output_port())) + elif args[1] == "list_address": + print(firewall.format_json(firewall.list_address())) + elif args[1] == "list_input_address": + print(firewall.format_json(firewall.list_input_address())) + elif args[1] == "list_output_address": + print(firewall.format_json(firewall.list_output_address())) + elif args[1] == "input_port": + info = { + "Protocol": args[2], + "Port": args[3], + "Strategy": args[4] + } + print(firewall.input_port(info, args[5])) + elif args[1] == "output_port": + info = { + "Protocol": args[2], + "Port": args[3], + "Strategy": args[4] + } + print(firewall.output_port(info, args[5])) + elif args[1] == "rich_rules": + info = { + "Protocol": args[2], + "Port": args[3], + "Address": args[4], + "Strategy": args[5] + } + print(firewall.rich_rules(info, args[6])) + elif args[1] == "output_rich_rules": + info = { + "Protocol": args[2], + "Port": args[3], + "Address": args[4], + "Strategy": args[5] + } + print(firewall.output_rich_rules(info, args[6])) + else: + print("不支持的传参: " + args[1]) + sys.exit(1) diff --git a/class_v2/firewallModelV2/app/ufw.py b/class_v2/firewallModelV2/app/ufw.py new file mode 100644 index 00000000..7d5b4f8f --- /dev/null +++ b/class_v2/firewallModelV2/app/ufw.py @@ -0,0 +1,729 @@ +#!/www/server/panel/pyenv/bin/python3.7 +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - ufw封装库 +# ------------------------------ + +import subprocess +import os +import sys +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +# import re +from firewallModelV2.app.appBase import Base + +class Ufw(Base): + def __init__(self): + self.cmd_str = self._set_cmd_str() + + def _set_cmd_str(self): + return "ufw" + + # 2024/3/19 下午 5:00 获取系统防火墙的运行状态 + def status(self): + ''' + @name 获取系统防火墙的运行状态 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run([self.cmd_str, "status"], capture_output=True, text=True, check=True) + if "Status: active" in result.stdout: + return "running" + elif "状态: 激活" in result.stdout: + return "running" + else: + return "not running" + except subprocess.CalledProcessError: + return "not running" + except Exception as e: + return "not running" + + # 2024/3/19 下午 5:00 获取系统防火墙的版本号 + def version(self): + ''' + @name 获取系统防火墙的版本号 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run([self.cmd_str, "version"], capture_output=True, text=True, check=True) + info = result.stdout.replace("\n", "") + return info.replace("ufw ", "") + except Exception as e: + return "未知版本" + + # 2024/3/19 下午 5:00 启动防火墙 + def start(self): + ''' + @name 启动防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + stdout, stderr = public.ExecShell("echo y | {} enable".format(self.cmd_str)) + if stderr: + return self._result(False, "启动防火墙失败:{}".format(stderr)) + return self._result(True, "启动防火墙成功") + except Exception as e: + return self._result(False, "启动防火墙失败:{}".format(str(e))) + + # 2024/3/19 下午 5:00 停止防火墙 + def stop(self): + ''' + @name 停止防火墙 + @author wzz <2024/3/19 下午 5:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + stdout, stderr = public.ExecShell("{} disable".format(self.cmd_str)) + if stderr: + return self._result(False, "停止防火墙失败:{}".format(stderr)) + return self._result(True, "停止防火墙成功") + except Exception as e: + return self._result(False, "停止防火墙失败:{}".format(str(e))) + + # 2024/3/19 下午 4:59 重启防火墙 + def restart(self): + ''' + @name 重启防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + self.stop() + self.start() + except Exception as e: + return self._result(False, "重启防火墙失败:{}".format(str(e))) + + # 2024/3/19 下午 4:59 重载防火墙 + def reload(self): + ''' + @name 重载防火墙 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + subprocess.run([self.cmd_str, "reload"], check=True, stdout=subprocess.PIPE) + except Exception as e: + return self._result(False, "重载防火墙失败:{}".format(str(e))) + + # 2024/3/19 上午 10:39 列出防火墙中所有端口规则 + def list_port(self): + ''' + @name 列出防火墙中所有端口规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "port") + if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]: + item_fire["Port"] = item_fire["Port"].replace(":", "-") + item_fire["Address"] = "all" if item_fire["Address"] == "Anywhere" else item_fire["Address"] + + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有input端口规则 + def list_input_port(self): + ''' + @name 列出防火墙中所有input端口规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "port") + if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]: + item_fire["Port"] = item_fire["Port"].replace(":", "-") + + if item_fire["Chain"] == "INPUT": + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有output端口规则 + def list_output_port(self): + ''' + @name 列出防火墙中所有output端口规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "port") + if item_fire.get("Port") and item_fire["Port"] != "Anywhere" and "." not in item_fire["Port"]: + item_fire["Port"] = item_fire["Port"].replace(":", "-") + + if item_fire["Chain"] == "OUTPUT": + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有的ip规则 + def list_address(self): + ''' + @name 列出防火墙中所有的ip规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + item_fire = self._load_info(line, "address") + if "Port" in item_fire: continue + if item_fire.get("Address"): + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有input的ip规则 + def list_input_address(self): + ''' + @name 列出防火墙中所有input的ip规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + if " IN" not in line: + continue + item_fire = self._load_info(line, "address") + if "Port" in item_fire: continue + if item_fire.get("Address"): + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 上午 10:39 列出防火墙中所有output的ip规则 + def list_output_address(self): + ''' + @name 列出防火墙中所有output的ip规则 + @author wzz <2024/3/19 上午 10:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + result = subprocess.run( + [self.cmd_str, "status", "verbose"], capture_output=True, text=True, check=True + ) + port_infos = result.stdout.split("\n") + datas = [] + is_start = False + for line in port_infos: + if line.startswith("-"): + is_start = True + continue + if not is_start: + continue + if " OUT" not in line: + continue + item_fire = self._load_info(line, "address") + if "Port" in item_fire: continue + if item_fire.get("Address"): + datas.append(item_fire) + return datas + except Exception as e: + return [] + + # 2024/3/19 下午 4:59 添加端口规则 + def input_port(self, info, operation): + ''' + @name 添加端口规则 + @author wzz <2024/3/19 下午 4:59> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + + if info["Port"].find('-') != -1: + info["Port"] = info["Port"].replace('-', ':') + + if operation == "add": + if info['Protocol'].find("/") != -1: + rich_rule = self.cmd_str + " insert 1 {} {}".format(info['Strategy'], info['Port']) + stdout, stderr = public.ExecShell(rich_rule) + elif info['Protocol'] == "tcp/udp": + stdout, stderr = public.ExecShell(self.cmd_str + " allow " + info['Port']) + else: + stdout, stderr = public.ExecShell(self.cmd_str + " allow " + info['Port'] + "/" + info['Protocol']) + else: + if info['Protocol'].find("/") != -1: + rich_rule = "{} delete {} {}".format(self.cmd_str, info['Strategy'], info['Port']) + stdout, stderr = public.ExecShell(rich_rule) + elif info['Protocol'] == "tcp/udp": + stdout, stderr = public.ExecShell(self.cmd_str + " delete allow " + info['Port']) + else: + stdout, stderr = public.ExecShell(self.cmd_str + " delete allow " + info['Port'] + "/" + info['Protocol']) + + if stderr: + if "setlocale" in stderr: + return self._result(True, "设置端口规则成功") + return self._result(False, "设置端口规则失败:{}".format(stderr)) + + return self._result(True, "设置端口规则成功") + + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置端口规则成功") + return self._result(False, "设置端口规则失败:{}".format(str(e))) + + # 2024/3/24 下午 11:28 设置output端口策略 + def output_port(self, info, operation): + ''' + @name 设置output端口策略 + @param info: 端口号 + @param operation: 操作 + @return None + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + + if operation == "add": + if info['Protocol'].find('/') != -1: + cmd = "{} {} out {}".format(self.cmd_str, info['Strategy'], info['Port']) + else: + cmd = "{} {} out {}/{}".format(self.cmd_str, info['Strategy'], info['Port'], info['Protocol']) + else: + if info['Protocol'].find('/') != -1: + cmd = "{} delete {} out {}".format(self.cmd_str, info['Strategy'], info['Port']) + else: + cmd = "{} delete {} out {}/{}".format(self.cmd_str, info['Strategy'], info['Port'], info['Protocol']) + stdout, stderr = public.ExecShell(cmd) + if stderr: + if "setlocale" in stderr: + return self._result(True, "设置端口规则成功") + return self._result(False, "设置output端口规则失败:{}".format(stderr)) + return self._result(True, "设置output端口规则成功") + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置output端口规则成功") + return self._result(False, "设置output端口规则失败:{}".format(str(e))) + + # 2024/3/19 下午 4:58 复杂一些的规则管理 + def rich_rules(self, info, operation): + ''' + @name 复杂一些的规则管理 + @author wzz <2024/3/19 下午 4:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + else: + return self._result(False, "未知的策略参数:{}".format(info["Strategy"])) + + rule_str = "{} insert 1 {} ".format(self.cmd_str, info["Strategy"]) + if "Address" in info and public.is_ipv6(info['Address']): + rule_str = "{} {} ".format(self.cmd_str, info["Strategy"]) + if operation == "remove": + rule_str = "{} delete {} ".format(self.cmd_str, info["Strategy"]) + + if "Address" in info and info['Address'] != "all": + rule_str += "from {} ".format(info['Address']) + if len(info.get("Protocol", "")) != 0 and "/" not in info['Protocol']: + rule_str += "proto {} ".format(info['Protocol']) + if len(info.get("Port", "")) != 0: + rule_str += "to any port {} ".format(info['Port']) + stdout, stderr = public.ExecShell(rule_str) + if stderr: + if "Rule added" in stdout or "Rule deleted" in stdout or "Rule updated" in stdout or "Rule inserted" in stdout or "Skipping adding existing rule" in stdout: + return self._result(True, "设置规则成功") + if "setlocale" in stderr: + return self._result(True, "设置规则成功") + return self._result(False, "规则设置失败:{}".format(stderr)) + return self._result(True, "设置规则成功") + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置规则成功") + return self._result(False, "规则设置失败:{}".format(e)) + + # 2024/3/24 下午 11:29 设置output rich_rules + def output_rich_rules(self, info, operation): + ''' + @name 设置output rich_rules + @param info: 规则 + @param operation: 操作 + @return None + ''' + try: + if info["Strategy"] == "accept": + info["Strategy"] = "allow" + elif info["Strategy"] == "drop": + info["Strategy"] = "deny" + else: + return self._result(False, "未知的策略: {}".format(info["Strategy"])) + + rule_str = "{} insert 1 {} ".format(self.cmd_str, info["Strategy"]) + if "Address" in info and public.is_ipv6(info['Address']): + rule_str = "{} {} ".format(self.cmd_str, info["Strategy"]) + if operation == "remove": + rule_str = "{} delete {} ".format(self.cmd_str, info["Strategy"]) + + if len(info.get("Address", "")) != 0: + rule_str += "out from {} ".format(info['Address']) + if len(info.get("Protocol", "")) != 0: + rule_str += "proto {} ".format(info['Protocol']) + if len(info.get("Port", "")) != 0: + rule_str += "to any port {} ".format(info['Port']) + stdout, stderr = public.ExecShell(rule_str) + if stderr: + if "Rule added" in stdout or "Rule deleted" in stdout or "Rule updated" in stdout or "Rule inserted" in stdout or "Skipping adding existing rule" in stdout: + return self._result(True, "设置output规则成功") + if "setlocale" in stderr: + return self._result(True, "设置规则成功") + return self._result(False, "outpu规则设置失败:{}".format(stderr)) + return self._result(True, "设置output规则成功") + except Exception as e: + if "setlocale" in str(e): + return self._result(True, "设置output规则成功") + return self._result(False, "outpu规则设置失败:{}".format(e)) + + # 2024/3/19 下午 5:01 解析防火墙规则信息,返回字典格式数据,用于添加或删除防火墙规则 + def _load_info(self, line, fire_type): + ''' + @name 解析防火墙规则信息,返回字典格式数据,用于添加或删除防火墙规则 + @author wzz <2024/3/19 上午 10:38> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + fields = line.split() + item_info = {} + if "LIMIT" in line or "ALLOW FWD" in line: + return item_info + if len(fields) < 4: + return item_info + if fields[0] == "Anywhere" and fire_type != "port": + item_info["Strategy"] = "drop" + + if fields[1] != "(v6)": + if fields[1] == "ALLOW": + item_info["Strategy"] = "accept" + if fields[2] == "IN": + item_info["Chain"] = "INPUT" + elif fields[2] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[3] + item_info["Family"] = "ipv4" + else: + if fields[2] == "ALLOW": + item_info["Strategy"] = "accept" + if fields[3] == "IN": + item_info["Chain"] = "INPUT" + elif fields[3] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[4] + item_info["Family"] = "ipv6" + + return item_info + + if "/" in fields[0]: + item_info["Port"] = fields[0].split("/")[0] + item_info["Protocol"] = fields[0].split("/")[1] + else: + item_info["Port"] = fields[0] + item_info["Protocol"] = "tcp/udp" + + if "v6" in fields[1]: + item_info["Family"] = "ipv6" + if fields[2] == "ALLOW": + item_info["Strategy"] = "accept" + else: + item_info["Strategy"] = "drop" + + if fields[3] == "IN": + item_info["Chain"] = "INPUT" + elif fields[3] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[4] + + else: + item_info["Family"] = "ipv4" if ":" not in fields[3] else "ipv6" + + if fields[1] == "ALLOW": + item_info["Strategy"] = "accept" + else: + item_info["Strategy"] = "drop" + + if fields[2] == "IN": + item_info["Chain"] = "INPUT" + elif fields[2] == "OUT": + item_info["Chain"] = "OUTPUT" + item_info["Address"] = fields[3] + + return item_info + + # 2024/3/25 下午 2:29 设置端口转发 + def port_forward(self, info, operation): + ''' + @name 设置端口转发 + @param port: 端口号 + @param ip: ip地址 + @param operation: 操作 + @return None + ''' + from firewallModel.app.iptables import Iptables + self.firewall = Iptables() + return self.firewall.port_forward(info, operation) + + # 2024/3/25 下午 2:34 获取所有端口转发列表 + def list_port_forward(self): + ''' + @name 获取所有端口转发列表 + @return None + ''' + from firewallModel.app.iptables import Iptables + self.firewall = Iptables() + return self.firewall.get_nat_prerouting_rules() + + +if __name__ == '__main__': + args = sys.argv + firewall = Ufw() + ufw_status = firewall.status() + if len(args) < 2: + print("Welcome to the UFW (Uncomplicated Firewall) command-line interface!") + print("Firewall status is :", ufw_status) + print("Firewall version: ", firewall.version()) + if ufw_status == "not running": + print("ufw未启动,请启动ufw后再执行命令!") + print("启动命令: start") + print() + sys.exit(1) + print() + print("Available options:") + print("1. Check Firewall Status: status") + print("2. Check Firewall Version: version") + print("3. Start Firewall: start") + print("4. Stop Firewall: stop") + print("5. Restart Firewall: restart") + print("6. Reload Firewall: reload") + print("7. List All Ports: list_port") + print("8. List All IP Addresses: list_address") + print("9. Add Port: add_port ") + print("10. Remove Port: remove_port ") + print("11. Add Port Rule: add_port_rule
                                    ") + print("12. Remove Port Rule: remove_port_rule
                                    ") + print("13. Add IP Rule: add_ip_rule
                                    ") + print("14. Remove IP Rule: remove_ip_rule
                                    ") + print() + sys.exit(1) + if args[1] == "status": + print(firewall.status()) + elif args[1] == "version": + print(firewall.version()) + elif args[1] == "start": + error = firewall.start() + if error: + print(f"Error: {error}") + else: + print("Firewall started successfully.") + elif args[1] == "stop": + error = firewall.stop() + if error: + print(f"Error: {error}") + else: + print("Firewall stopped successfully.") + elif args[1] == "restart": + error = firewall.restart() + if error: + print(f"Error: {error}") + else: + print("Firewall restarted successfully.") + elif args[1] == "reload": + error = firewall.reload() + if error: + print(f"Error: {error}") + else: + print("Firewall reloaded successfully.") + elif args[1] == "list_input_port": + ports = firewall.list_input_port() + for p in ports: + print(p) + elif args[1] == "list_output_port": + ports = firewall.list_output_port() + for p in ports: + print(p) + elif args[1] == "list_input_address": + addresses = firewall.list_input_address() + for a in addresses: + print(a) + elif args[1] == "list_output_address": + addresses = firewall.list_output_address() + for a in addresses: + print(a) + elif args[1] == "add_port": + port = args[2] + protocol = args[3] + error = firewall.input_port(f"{port}/{protocol}", "allow") + if error: + print(f"Error: {error}") + else: + print(f"Port {port}/{protocol} added successfully.") + elif args[1] == "remove_port": + port = args[2] + protocol = args[3] + error = firewall.input_port(f"{port}/{protocol}", "remove") + if error: + print(f"Error: {error}") + else: + print(f"Port {port}/{protocol} removed successfully.") + elif args[1] == "add_port_rule": + address = args[2] + port = args[3] + protocol = args[4] + strategy = args[5] + operation = args[6] + error = firewall.rich_rules( + {"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule added successfully.") + elif args[1] == "remove_port_rule": + address = args[2] + port = args[3] + protocol = args[4] + strategy = args[5] + operation = args[6] + error = firewall.rich_rules( + {"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule removed successfully.") + elif args[1] == "add_ip_rule": + address = args[2] + strategy = args[3] + operation = args[4] + error = firewall.rich_rules( + {"Address": address, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule added successfully.") + elif args[1] == "remove_ip_rule": + address = args[2] + strategy = args[3] + operation = args[4] + error = firewall.rich_rules( + {"Address": address, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Rich rule removed successfully.") + elif args[1] == "output_port": + port = args[2] + operation = args[3] + error = firewall.output_port(port, operation) + if error: + print(f"Error: {error}") + else: + print(f"Output port {port} {operation} successfully.") + elif args[1] == "output_rich_rules": + address = args[2] + port = args[3] + protocol = args[4] + strategy = args[5] + operation = args[6] + error = firewall.output_rich_rules( + {"Address": address, "Port": port, "Protocol": protocol, "Strategy": strategy}, operation) + if error: + print(f"Error: {error}") + else: + print("Output rich rule added successfully.") + else: + print("Invalid args") + sys.exit(1) + diff --git a/class_v2/firewallModelV2/comModel.py b/class_v2/firewallModelV2/comModel.py new file mode 100644 index 00000000..465833f8 --- /dev/null +++ b/class_v2/firewallModelV2/comModel.py @@ -0,0 +1,1718 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import json +import re +import time +import os + +# ------------------------------ +# 系统防火墙模型 - 业务接口类 +# ------------------------------ +import public +from firewallModelV2.firewallBase import Base + + +class main(Base): + + def __init__(self): + super().__init__() + + # 2024/3/14 下午 12:01 获取防火墙状态信息 + def get_firewall_info(self, get): + """ + @name 获取防火墙统计 + """ + data = {} + data['port'] = len(self.firewall.list_port()) + data['ip'] = len(self.firewall.list_address()) + data['trans'] = len(self.firewall.list_port_forward()) + data['country'] = public.M('firewall_country').count() + + isPing = True + try: + file = '/etc/sysctl.conf' + conf = public.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)\n" + tmp = re.search(rep, conf).groups(0)[0] + if tmp == '1': isPing = False + except: + isPing = True + + data['ping'] = isPing + return data + + # 2024/3/26 下午 3:40 获取防火墙状态 + def get_status(self, get): + ''' + @name 获取防火墙状态 + @author wzz <2024/3/26 下午 3:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return self.get_firewall_status() + + # 2024/3/26 下午 3:42 设置防火墙状态 + def set_status(self, get): + ''' + @name 设置防火墙状态 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.status = get.get('status/s', '1') + if get.status not in ['0', '1']: + return public.returnMsg(False, '参数错误') + + if get.status == '1': + return self.firewall.start() + else: + return self.firewall.stop() + + # 2024/5/13 下午3:50 检查指定端口是否已经存在,如果存在则返回False,否则返回True + def check_port_exist(self, get): + ''' + @name 检查指定端口是否已经存在,如果存在则返回False,否则返回True + @author wzz <2024/5/13 下午3:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + port_rules_list = self.port_rules_list(get) + for item in port_rules_list: + if item["Port"] == get.port and item["Address"] == get.address and item["Protocol"] == get.protocol and \ + item["Strategy"] == get.strategy and item["Chain"] == get.chain: + return False + + return True + + # 2024/5/13 下午4:13 检查指定ip规则是否已经存在,如果存在则返回False,否则返回True + def check_ip_exist(self, get): + ''' + @name 检查指定ip规则是否已经存在,如果存在则返回False,否则返回True + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + ip_rules_list = self.ip_rules_list(get) + for item in ip_rules_list: + if item["Address"] == get.address and item["Strategy"] == get.strategy and item["Chain"] == get.chain: + return False + + return True + + # 2024/5/13 下午4:17 检查指定端口转发规则是否已经存在,如果存在则返回False,否则返回True + def check_forward_exist(self, get): + ''' + @name 检查指定端口转发规则是否已经存在,如果存在则返回False,否则返回True + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + forward_rules_list = self.port_forward_list(get) + for item in forward_rules_list: + if item["S_Address"] == get.S_Address and item["S_Port"] == get.S_Port and item["T_Address"] == get.T_Address and \ + item["T_Port"] == get.T_Port: + return False + + return True + + # 2024/3/26 下午 6:09 从数据库中获取端口规则列表 + def get_port_db(self, get): + ''' + @name 从数据库中获取端口规则列表 + @author wzz <2024/3/26 下午 6:13> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + where = '1=1' + sql = public.M('firewall_new') + data = sql.where(where, ()).select() + + domain_sql = public.M('firewall_domain') + domain_data = domain_sql.where(where, ()).select() + for i in range(len(data)): + if not "ports" in data[i]: + data[i]['status'] = -1 + continue + + if "brief" in data[i]: + data[i]['brief'] = public.xssdecode(data[i]['brief']) + + if not "chain" in data[i]: + data[i]['chain'] = "INPUT" + if "chain" in data[i] and data[i]['chain'] == "": + data[i]['chain'] = "INPUT" + + for j in range(len(domain_data)): + if "domain" in domain_data[j] and data[i]['address'] in domain_data[j]['domain']: + data[i]['domain'] = domain_data[j]['domain'] + break + + return data + except Exception as e: + return [] + + # 2024/3/26 下午 11:53 从数据库中获取ip规则列表 + def get_ip_db(self, get): + ''' + @name 从数据库中获取ip规则列表 + @author wzz <2024/3/26 下午 11:53> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + where = '1=1' + sql = public.M('firewall_ip') + + ip_data = sql.where(where, ()).select() + + for i_data in ip_data: + if "brief" in i_data: + i_data['brief'] = public.xssdecode(i_data['brief']) + if not "chain" in i_data: + i_data['chain'] = "INPUT" + if "chain" in i_data and i_data['chain'] == "": + i_data['chain'] = "INPUT" + + return ip_data + except Exception as e: + return [] + + # 2024/3/26 下午 11:58 从数据库中获取端口转发规则列表 + def get_forward_db(self, get): + ''' + @name 从数据库中获取端口转发规则列表 + @author wzz <2024/3/26 下午 11:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + where = '1=1' + sql = public.M('firewall_forward') + + if hasattr(get, 'query'): + where = " S_Address like '%{search}%' or S_Port like '%{search}%' or T_Address like '%{search}%' or T_Port like '%{search}%'".format( + search=get.query + ) + res = sql.where(where, ()).select() + if type(res) != list: + return [] + return res + except Exception as e: + return [] + + # 2024/3/26 下午 10:46 构造端口规则返回数据 + def structure_port_return_data(self, list_port, rule_db, query): + ''' + @name 构造返回数据 + @author wzz <2024/3/26 下午 10:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + new_list = [] + for j in range(len(list_port)): + list_port[j]['id'] = 0 + list_port[j]['sid'] = 0 + list_port[j]['brief'] = "" + list_port[j]['domain'] = "" + if (list_port[j]['Port'].find(":") != -1 or list_port[j]['Port'].find("-") != -1 or + list_port[j]['Port'].find("/") != -1 or list_port[j]['Port'].find(".") != -1): + list_port[j]['status'] = 1 + else: + try: + if not ":" in list_port[j]['Port'] or not "-" in list_port[j]['Port']: + list_port[j]['status'] = self.CheckPort(int(list_port[j]['Port']), list_port[j]['Protocol']) + else: + list_port[j]['status'] = -1 + except: + list_port[j]['status'] = -1 + + if "Chain" in list_port[j] and list_port[j]['Chain'] == "OUTPUT": + list_port[j]['status'] = -1 + + list_port[j]['addtime'] = "0000-00-00 00:00:00" + + list_port[j]['Port'] = list_port[j]['Port'].replace(":", "-") + for i in range(len(rule_db)): + if (rule_db[i]['ports'] == list_port[j]['Port'] and + rule_db[i]['protocol'] == list_port[j]['Protocol'] and + rule_db[i]['address'].lower() == list_port[j]['Address'].lower() and + rule_db[i]['types'] == list_port[j]['Strategy'] and + rule_db[i]['chain'] == list_port[j]['Chain']): + list_port[j]['id'] = rule_db[i]['id'] + list_port[j]['sid'] = rule_db[i]['sid'] + list_port[j]['brief'] = rule_db[i]['brief'] + list_port[j]['addtime'] = rule_db[i]['addtime'] + + if "domain" in rule_db[i]: + list_port[j]['domain'] = rule_db[i]['domain'] + + break + + if query != "": + if query in list_port[j]['Port'] or query in list_port[j]['brief'] or query in list_port[j]['Address']: + new_list.append(list_port[j]) + + if len(new_list) > 0 or query != "": + return sorted(new_list, key=lambda x: x['addtime'], reverse=True) + + return sorted(list_port, key=lambda x: x['addtime'], reverse=True) + + # 2024/3/27 上午 12:01 构造ip规则返回数据 + def structure_ip_return_data(self, list_ip, rule_db, query): + ''' + @name 构造ip规则返回数据 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + new_list = [] + for j in range(len(list_ip)): + list_ip[j]['id'] = 0 + list_ip[j]['sid'] = 0 + list_ip[j]['brief'] = "" + list_ip[j]['domain'] = "" + list_ip[j]['addtime'] = "0000-00-00 00:00:00" + + for i in range(len(rule_db)): + if (rule_db[i]['address'] == list_ip[j]['Address'] and + rule_db[i]['types'] == list_ip[j]['Strategy'] and + rule_db[i]['chain'] == list_ip[j]['Chain']): + list_ip[j]['id'] = rule_db[i]['id'] + list_ip[j]['sid'] = rule_db[i]['sid'] + list_ip[j]['brief'] = rule_db[i]['brief'] + list_ip[j]['addtime'] = rule_db[i]['addtime'] + + if "domain" in rule_db[i]: + list_ip[j]['domain'] = rule_db[i]['domain'] + + break + if query != "": + if query in list_ip[j]['brief'] or query in list_ip[j]['Address']: + new_list.append(list_ip[j]) + + if len(new_list) > 0 or query != "": + return public.return_area(sorted(new_list, key=lambda x: x['addtime'], reverse=True), "Address") + + return public.return_area(sorted(list_ip, key=lambda x: x['addtime'], reverse=True), "Address") + + # 2024/3/27 上午 12:11 构造端口转发规则返回数据 + def structure_forward_return_data(self, list_forward, rule_db, query): + ''' + @name 构造端口转发规则返回数据 + @author wzz <2024/3/27 上午 12:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + new_list = [] + for j in range(len(list_forward)): + list_forward[j]['id'] = 0 + list_forward[j]['brief'] = "" + list_forward[j]['addtime'] = "0000-00-00 00:00:00" + + for i in range(len(rule_db)): + if (rule_db[i]['T_Address'] == list_forward[j]['T_Address'] and + rule_db[i]['S_Port'] == list_forward[j]['S_Port'] and + rule_db[i]['T_Port'] == list_forward[j]['T_Port']): + list_forward[j]['id'] = rule_db[i]['id'] + list_forward[j]['brief'] = rule_db[i]['brief'] + list_forward[j]['addtime'] = rule_db[i]['addtime'] + break + + if query != "": + if (query in list_forward[j]['brief'] or query in list_forward[j]['S_Address'] or query in + list_forward[j]['S_Port'] or + query in list_forward[j]['T_Address'] or query in list_forward[j]['T_Port']): + new_list.append(list_forward[j]) + + if len(new_list) > 0 or query != "": + return sorted(new_list, key=lambda x: x['addtime'], reverse=True) + + return sorted(list_forward, key=lambda x: x['addtime'], reverse=True) + + # 2024/3/25 上午 11:05 获取所有端口规则列表 + def port_rules_list(self, get): + ''' + @name 获取所有端口规则列表 + @author wzz <2024/3/25 上午 11:06> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + get.chain = get.get('chain/s', 'ALL') + get.query = get.get('query/s', '') + + rule_db = self.get_port_db(get) + + if get.chain == "INPUT": + list_port = self.firewall.list_input_port() + elif get.chain == "OUTPUT": + list_port = self.firewall.list_output_port() + else: + list_port = self.firewall.list_port() + + return self.structure_port_return_data(list_port, rule_db, query=get.query) + + # 2024/3/26 下午 3:17 导出规则 + def export_rules(self, get): + ''' + @name 导出规则 + @author wzz <2024/3/26 下午 3:17> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.rule = get.get('rule/s', 'port') + if get.rule == "port": + return self.export_port_rules(get) + elif get.rule == "ip": + return self.export_ip_rules(get) + else: + return self.export_port_forward(get) + + # 2024/3/26 下午 3:18 导入规则 + def import_rules(self, get): + ''' + @name 导入规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置导入规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再导入规则!') + + get.rule = get.get('rule/s', 'port') + if get.rule == "port": + return self.import_port_rules(get) + elif get.rule == "ip": + return self.import_ip_rules(get) + else: + return self.import_port_forward(get) + + # 2024/3/26 下午 2:38 导出所有端口规则 + def export_port_rules(self, get): + ''' + @name 导出所有端口规则 + @author wzz <2024/3/26 下午 2:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.chain = get.get('chain/s', 'all') + + if get.chain == "INPUT": + file_name = "input_port_rules_{}".format(int(time.time())) + elif get.chain == "OUTPUT": + file_name = "output_port_rules_{}".format(int(time.time())) + else: + file_name = "port_rules_{}".format(int(time.time())) + + data = self.port_rules_list(get) + if not data: + return public.returnMsg(False, '没有规则无法导出') + if not os.path.exists(self.config_path): + os.makedirs(self.config_path, exist_ok=True) + file_path = "{}/{}.json".format(self.config_path, file_name) + + public.writeFile(file_path, public.GetJson(data)) + public.WriteLog("系统防火墙", "导出端口规则") + return public.returnMsg(True, file_path) + + # 2024/3/26 下午 2:41 导入端口规则 + def import_port_rules(self, get): + ''' + @name 导入端口规则 + @author wzz <2024/3/26 下午 2:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.file = get.get('file/s', '') + + if not get.file: + return public.returnMsg(False, '文件不能为空') + + if not os.path.exists(get.file): + return public.returnMsg(False, '文件不存在') + + try: + data = public.readFile(get.file) + if "|" in data and not "{" in data: + get.rule_name = "port_rule" + return self.import_rules_old(get) + + data = json.loads(data) + # 2024/4/10 下午2:51 反转数据 + data.reverse() + except: + return public.returnMsg(False, '文件内容异常或格式错误') + + args = public.dict_obj() + for item in data: + args.operation = 'add' + args.protocol = item['Protocol'] + args.port = item['Port'] + args.strategy = item['Strategy'] + args.chain = item['Chain'] + args.address = item.get('Address', 'all') + args.brief = item.get('brief', '') + args.reload = "0" + + self.set_port_rule(args) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '导入成功') + + # 2024/5/14 上午10:27 调用旧的导入规则方法 + def import_rules_old(self, get): + ''' + @name 调用旧的导入规则方法 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + from safeModel.firewallModel import main as firewall + firewall_obj = firewall() + get.file_name = get.file.split("/")[-1] + firewall_obj.import_rules(get) + + return public.returnMsg(True, '导入成功') + except Exception as e: + return public.returnMsg(False, str(e)) + + # 2024/3/26 上午 9:30 处理多个ip以换行的方式添加/删除 + def set_nline_port_ip(self, get): + ''' + @name 处理多个ip以换行的方式添加/删除 + @author wzz <2024/3/26 上午 9:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split("\n") + failed_list = [] + for addr in address: + if not public.checkIp(addr): + return public.returnMsg(False, '目标地址格式错误') + + get.address = addr + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + # if self._isFirewalld: + # self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:30 处理多个ip以逗号的方式添加/删除 + def set_tline_port_ip(self, get): + ''' + @name 处理多个ip以逗号的方式添加 + @author wzz <2024/3/26 上午 9:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split(",") + failed_list = [] + for addr in address: + if not public.checkIp(addr): + return public.returnMsg(False, '目标地址格式错误') + + get.address = addr + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + # if self._isFirewalld: + # self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:34 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + def set_range_port_ip(self, get): + ''' + @name 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + @author wzz <2024/3/26 上午 9:35> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = self.firewall.handle_ip_range(get.address) + failed_list = [] + for addr in address: + get.address = addr + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + # if self._isFirewalld: + # self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/25 下午 6:27 设置端口规则 + def set_port_rule(self, get): + ''' + @name 设置端口规则 + @author wzz <2024/3/25 下午 6:28> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置端口规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.address = get.get('address/s', 'all') + get.port = get.get('port/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + get.reload = get.get('reload/s', "1") + get.brief = get.get('brief/s', '') + + if get.address == "Anywhere" or get.address == "": + get.address = "all" + + if get.protocol == "all": + get.protocol = "tcp/udp" + + if get.port == "": + return public.returnMsg(False, '目标端口不能为空') + + if get.address != "all" and "," in get.address: + import copy + args = copy.deepcopy(get) + address_list = get.address.split(",") + for address in address_list: + args.address = address + result = self.more_prot_rule(args) + if not result['status']: + return result + if get.address != "all" and "\n" in get.address: + import copy + args = copy.deepcopy(get) + address_list = get.address.split("\n") + for address in address_list: + args.address = address + result = self.more_prot_rule(args) + if not result['status']: + return result + else: + result = self.more_prot_rule(get) + if not result['status']: + return result + + if self._isFirewalld and get.reload == "1": + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/29 下午 4:04 处理多个ip的端口规则情况 + def more_prot_rule(self, get): + ''' + @name 处理多个ip的端口规则情况 + @author wzz <2024/3/29 下午 4:02> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.port.find(",") != -1: + import copy + args = copy.deepcopy(get) + port_list = get.port.split(",") + for port in port_list: + args.port = port + result = self.exec_port_rule(args) + if not result['status']: + return result + return public.returnMsg(True, '设置成功') + else: + return self.exec_port_rule(get) + + # 2024/3/28 下午 6:29 执行端口设置 + def exec_port_rule(self, get): + ''' + @name 执行端口设置 + @author wzz <2024/3/28 下午 6:29> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add" and not self.check_port_exist(get): + return public.returnMsg(False, '端口{}已存在,请勿重复添加'.format(get.port)) + + self.set_port_db(get) + + # 2024/3/25 下午 8:23 处理多个ip的情况,例如出现每行一个ip + if get.address != "all" and "\n" in get.address: + return self.set_nline_port_ip(get) + elif get.address != "all" and "-" in get.address: + return self.set_range_port_ip(get) + elif get.address != "all" and "," in get.address: + return self.set_tline_port_ip(get) + elif get.address != "all" and "/" in get.address: + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + elif get.address != "all" and not public.checkIp(get.address) and not public.is_ipv6(get.address): + return public.returnMsg(False, '指定IP地址格式错误') + else: + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + return result + + # 2024/5/14 上午10:40 前置检测ip是否合法 + def check_ips(self, get): + ''' + @name 前置检测ip是否合法 + @author wzz <2024/5/14 上午10:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.address != "all" and "\n" in get.address: + address = get.address.split("\n") + for addr in address: + if addr != "all" and not public.checkIp(addr) and not public.is_ipv6(addr): + return public.returnMsg(False, '指定IP地址格式错误') + elif get.address != "all" and "," in get.address: + address = get.address.split(",") + for addr in address: + if addr != "all" and not public.checkIp(addr) and not public.is_ipv6(addr): + return public.returnMsg(False, '指定IP地址格式错误') + else: + if get.address != "all" and not public.checkIp(get.address) and not public.is_ipv6(get.address): + return public.returnMsg(False, '指定IP地址格式错误') + + return public.returnMsg(True, 'ok') + + # 2024/3/27 上午 9:37 修改端口规则 + def modify_port_rule(self, get): + ''' + @name 修改端口规则 + @author wzz <2024/3/27 上午 9:38> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + if "address" in get.new_data: + get.address = get.new_data['address'] + if not self.check_ips(get)["status"]: + return public.returnMsg(False, '修改后的指定IP地址格式错误') + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.port = get.old_data['Port'] + args1.protocol = get.old_data['Protocol'] + args1.address = get.old_data['Address'] + args1.strategy = get.old_data['Strategy'] + args1.chain = get.old_data['Chain'] + args1.id = get.old_data['id'] + args1.sid = get.old_data['sid'] + args1.reload = "0" + self.set_port_rule(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + args2.port = get.new_data['port'] + args2.protocol = get.new_data['protocol'] + args2.address = get.new_data['address'] if "address" in get.new_data else "all" + args2.strategy = get.new_data['strategy'] + args2.chain = get.new_data['chain'] + args2.brief = get.new_data['brief'] if 'brief' in get.new_data else "" + args2.reload = "1" + self.set_port_rule(args2) + + return public.returnMsg(True, '修改成功') + + # 2024/3/27 下午 4:03 修改域名端口规则 + def modify_domain_port_rule(self, get): + ''' + @name 修改域名端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + address = self.get_a_ip(get.new_data['domain']) + + if address == "": + return public.returnMsg(False, '域名: 【{}】解析失败'.format(get.domain)) + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.port = get.old_data['Port'] + args1.protocol = get.old_data['Protocol'] + args1.address = get.old_data['Address'] + args1.strategy = get.old_data['Strategy'] + args1.chain = get.old_data['Chain'] + args1.id = get.old_data['id'] + args1.sid = get.old_data['sid'] + self.set_port_rule(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + args2.port = get.new_data['port'] + args2.protocol = get.new_data['protocol'] + args2.address = address + args2.strategy = get.new_data['strategy'] + args2.chain = get.new_data['chain'] + args2.brief = get.new_data['brief'] if "brief" in get.new_data else "" + args2.domain = get.new_data['domain'] + self.set_port_rule(args2) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '修改成功') + + # 2024/3/26 下午 5:10 设置域名端口规则 + def set_domain_port_rule(self, get): + ''' + @name 设置域名端口规则 + @author wzz <2024/3/26 下午 5:11> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.domain = get.get('domain/s', '') + get.port = get.get('port/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + + if get.domain == "": + return public.returnMsg(False, '目标域名不能为空') + + if get.port == "": + return public.returnMsg(False, '目标端口不能为空') + + if not public.is_domain(get.domain): + return public.returnMsg(False, '目标域名格式错误') + + if "|" in get.domain: + get.domain = get.domain.split("|")[0] + + address = self.get_a_ip(get.domain) + + if address == "": + return public.returnMsg(False, '域名: 【{}】解析失败'.format(get.domain)) + + get.address = address + self.set_port_db(get) + + if get.chain == "INPUT": + result = self.input_port(get) + else: + result = self.output_port(get) + + if result['status'] and self._isFirewalld: + self.firewall.reload() + + return result + + # 2024/5/13 下午4:46 添加端口规则到指定数据库 + def add_port_db(self, get, protocol, addtime, domain): + ''' + @name 添加端口规则到指定数据库 + @author wzz <2024/5/13 下午4:46> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + add_sid = public.M('firewall_new').add( + 'ports,brief,protocol,address,types,addtime,domain,sid,chain', + ( + get.port, public.xsssec(get.brief), protocol, get.address, get.strategy, addtime, domain, 0, + get.chain) + ) + + if get.domain != "": + domain_sid = public.M('firewall_domain').add( + 'types,domain,port,address,brief,addtime,sid,protocol,domain_total', + (get.strategy, domain, get.port, get.address, public.xsssec(get.brief), + addtime, add_sid, get.protocol, get.domain) + ) + public.M('firewall_new').where("id=?", (add_sid,)).save('sid', domain_sid) + self.check_resolve_crontab() + + # 2024/5/13 下午4:49 从指定数据库删除端口规则 + def remove_port_db(self, get, protocol, addtime, domain): + ''' + @name 从指定数据库删除端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + public.M('firewall_new').where("ports=? and protocol=? and address=? and types=? and chain=?", ( + get.port, protocol, get.address, get.strategy, get.chain + )).delete() + get.domain = get.get('domain/s', '') + if get.domain != "": + public.M('firewall_domain').where("domain=?", (get.domain)).delete() + + self.remove_resolve_crontab() + + # 2024/3/26 下午 5:52 添加/删除数据库的端口规则 + def set_port_db(self, get): + ''' + @name 添加/删除数据库的端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add": + # 检测端口是否已经添加过 + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=? and chain=?', + (get.port, get.address, get.protocol, get.strategy, get.chain) + ).find() + + if not query_result: + get.domain = get.get('domain/s', '') + domain = "{}|{}".format(get.domain, get.address) if get.domain != "" else "" + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + + if get.protocol == "tcp/udp" and self._isFirewalld: + self.add_port_db(get, "tcp", addtime, domain) + self.add_port_db(get, "udp", addtime, domain) + else: + self.add_port_db(get, get.protocol, addtime, domain) + else: + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=? and chain=?', + (get.port, get.address, get.protocol, get.strategy, get.chain) + ).find() + if query_result: + if get.protocol == "tcp/udp" and self._isFirewalld: + self.remove_port_db(get, "tcp", query_result['addtime'], query_result['domain']) + self.remove_port_db(get, "udp", query_result['addtime'], query_result['domain']) + else: + self.remove_port_db(get, get.protocol, query_result['addtime'], query_result['domain']) + + # 2024/3/26 下午 11:23 添加/删除数据库的ip规则 + def set_ip_db(self, get): + ''' + @name 添加/删除数据库的ip规则 + @author wzz <2024/3/26 下午 11:24> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add": + get.domain = get.get('domain/s', '') + domain = "{}|{}".format(get.domain, get.address) if get.domain != "" else "" + query_result = public.M('firewall_ip').where("address=? and types=? and domain=? and chain=?", + (get.address, get.strategy, domain, get.chain)).find() + + if not query_result: + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + self._add_sid = public.M('firewall_ip').add( + 'address,types,brief,addtime,domain,sid,chain', + (get.address, get.strategy, public.xsssec(get.brief), addtime, domain, 0, get.chain) + ) + + if get.domain != "": + domain_sid = public.M('firewall_domain').add( + 'types,domain,port,address,brief,addtime,sid,protocol,domain_total', + (get.strategy, domain, '', get.address, public.xsssec(get.brief), addtime, self._add_sid, '', + get.domain) + ) + public.M('firewall_ip').where("id=?", (self._add_sid,)).save('sid', domain_sid) + self.check_resolve_crontab() + else: + get.address = get.get("address/s", '') + get.strategy = get.get("strategy/s", '') + if get.address == "": + return public.returnMsg(False, '请传入id') + public.M('firewall_ip').where("address=? and types=? and chain=?", (get.address, get.strategy, get.chain)).delete() + get.domain = get.get('domain/s', '') + if get.domain != "": + public.M('firewall_domain').where("domain=?", (get.domain)).delete() + + self.remove_resolve_crontab() + + # 2024/3/26 下午 11:40 添加/删除数据库的端口转发规则 + def set_forward_db(self, get): + ''' + @name 添加/删除数据库的端口转发规则 + @author wzz <2024/3/26 下午 11:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add": + query_result = public.M('firewall_forward').where("S_Port=? and T_Address=? and T_Port=? and Protocol=?", ( + get.S_Port, get.T_Address, get.T_Port, get.protocol + )).find() + + if not query_result: + get.brief = get.get('brief/s', '') + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + self._add_sid = public.M('firewall_forward').add( + 'S_Port,T_Address,T_Port,Protocol,Family,addtime,brief', + (get.S_Port, get.T_Address, get.T_Port, get.protocol, "ipv4", addtime, get.brief) + ) + else: + public.M('firewall_forward').where( + "S_Port=? and T_Address=? and T_Port=? and Protocol=?", + (get.S_Port, get.T_Address, get.T_Port, get.protocol) + ).delete() + + # 2024/3/25 下午 6:55 入站端口规则 + def input_port(self, get): + ''' + @name 入站端口规则 + @param "data":{"参数名":""} <数据类型> 参数描述 dabao + @return list[dict{}...] + ''' + info = { + "Port": get.port, + "Protocol": get.protocol.lower(), + "Strategy": get.strategy.lower(), + "Family": "ipv4", + } + + if ":" in get.address: + info["Family"] = "ipv6" + + if get.address != "all": + info["Address"] = get.address + result = self.firewall.rich_rules(info=info, operation=get.operation) + elif get.address == "all" and get.strategy == "drop": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.input_port(info=info, operation=get.operation) + + return result + + # 2024/3/25 下午 6:56 出站端口规则 + def output_port(self, get): + ''' + @name 出站端口规则 + @author wzz <2024/3/25 下午 6:56> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + info = { + "Port": get.port, + "Protocol": get.protocol.lower(), + "Strategy": get.strategy.lower(), + "Priority": "0", + "Family": "ipv4", + } + + if ":" in get.address: + info["Family"] = "ipv6" + + if get.address != "all": + info["Address"] = get.address + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_port(info=info, operation=get.operation) + + return result + + # 2024/3/26 上午 9:40 处理多个ip的情况,例如出现每行一个ip + def set_nline_ip_rule(self, get): + ''' + @name 处理多个ip的情况,例如出现每行一个ip + @author wzz <2024/3/26 上午 9:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split("\n") + failed_list = [] + import copy + for addr in address: + if not public.is_ipv4(addr) and not public.is_ipv6(addr): + continue + + args = copy.deepcopy(get) + args.address = addr + args.family = "ipv4" if ":" not in addr else "ipv6" + + if self.check_is_user_ip(args): + continue + + if get.operation == "add" and not self.check_ip_exist(args): + continue + + self.set_ip_db(args) + + info = { + "Address": args.address, + "Family": args.family, + "Strategy": args.strategy, + "Priority": args.priority, + } + + if args.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=args.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=args.operation) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:40 处理多个ip的情况,例如出现逗号隔开ip + def set_tline_ip_rule(self, get): + ''' + @name 处理多个ip的情况,例如出现逗号隔开ip + @author wzz <2024/3/26 上午 9:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = get.address.split(",") + failed_list = [] + import copy + for addr in address: + if not public.checkIp(addr): + return public.returnMsg(False, '目标地址格式错误') + + args = copy.deepcopy(get) + args.address = addr + + if self.check_is_user_ip(args): + continue + + if get.operation == "add" and not self.check_ip_exist(args): + continue + + self.set_ip_db(args) + + info = { + "Address": args.address, + "Family": args.family, + "Strategy": args.strategy, + "Priority": args.priority, + } + + if args.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=args.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=args.operation) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:43 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + def set_range_ip_rule(self, get): + ''' + @name 处理192.168.1.10-192.168.1.20这种范围ip的添加/删除 + @author wzz <2024/3/26 上午 9:43> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + address = self.firewall.handle_ip_range(get.address) + failed_list = [] + import copy + for addr in address: + args = copy.deepcopy(get) + args.address = addr + + if self.check_is_user_ip(args): + continue + + if get.operation == "add" and not self.check_ip_exist(args): + continue + + self.set_ip_db(args) + + info = { + "Address": args.address, + "Family": args.family, + "Strategy": args.strategy, + "Priority": args.priority, + } + + if args.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=args.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=args.operation) + + if not result['status']: + failed_list.append({ + "address": addr, + "msg": result['msg'] + }) + if len(failed_list) > 0: + return public.returnMsg(True, '设置成功,以下规则设置失败:{}'.format(failed_list)) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '设置成功') + + # 2024/3/26 上午 9:46 设置带掩码的ip段 + def set_mask_ip_rule(self, get): + ''' + @name 设置带掩码的ip段 + @author wzz <2024/3/26 上午 9:47> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.operation == "add" and not self.check_ip_exist(get): + return public.returnMsg(False, '目标地址{}已存在,请勿重复添加'.format(get.address)) + + self.set_ip_db(get) + info = { + "Address": get.address, + "Family": get.family, + "Strategy": get.strategy, + "Priority": get.priority, + } + + if get.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld: + self.firewall.reload() + + return result + + # 2024/4/9 下午11:31 检查是否会自己的ip,如果是则返回 + def check_is_user_ip(self, get): + ''' + @name + @author wzz <2024/4/9 下午11:31> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/3/26 下午 11:27 处理用户当前的远程ip,如果添加的ip与此ip一直则返回 + try: + from flask import request + user_ip = request.remote_addr + if user_ip in get.address: + return True + return False + except: + return False + + # 2024/3/25 下午 7:16 设置ip规则 + def set_ip_rule(self, get): + ''' + @name 设置ip规则 + @author wzz <2024/3/25 下午 7:16> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.address = get.get('address/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + get.family = get.get('family/s', 'ipv4') + get.priority = get.get('priority/s', '0') + get.reload = get.get('reload/s', "1") + + if get.address == "": + return public.returnMsg(False, '目标ip不能为空') + + # 2024/3/25 下午 8:23 处理多个ip的情况,例如出现每行一个ip + if get.address != "all" and "\n" in get.address: + return self.set_nline_ip_rule(get) + elif get.address != "all" and "-" in get.address: + return self.set_range_ip_rule(get) + elif get.address != "all" and "/" in get.address: + return self.set_mask_ip_rule(get) + elif get.address != "all" and "," in get.address: + return self.set_tline_ip_rule(get) + elif get.address != "all" and not public.checkIp(get.address) and not public.is_ipv6(get.address): + return public.returnMsg(False, '目标地址格式错误') + else: + if get.operation == "add" and get.strategy == "drop": + if self.check_is_user_ip(get): + return public.returnMsg(False, '不能添加自己的ip') + + if get.operation == "add" and not self.check_ip_exist(get): + return public.returnMsg(False, '目标地址{}已存在,请勿重复添加'.format(get.address)) + + self.set_ip_db(get) + + info = { + "Address": get.address, + "Family": get.family, + "Strategy": get.strategy.lower(), + "Priority": get.priority.lower(), + } + + if get.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld and get.reload == "1": + self.firewall.reload() + + return result + + # 2024/3/27 上午 9:44 修改ip规则 + def modify_ip_rule(self, get): + ''' + @name 修改ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.address = get.old_data['Address'] + args1.strategy = get.old_data['Strategy'] + args1.family = get.old_data['Family'] + args1.chain = get.old_data['Chain'] + args1.id = get.old_data['id'] + args1.sid = get.old_data['sid'] + args1.reload = "0" + self.set_ip_rule(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + args2.address = get.new_data['address'] + args2.strategy = get.new_data['strategy'] + args2.family = get.new_data['family'] + args2.chain = get.new_data['chain'] + args2.brief = get.new_data['brief'] + args2.reload = "0" + self.set_ip_rule(args2) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '修改成功') + + # 2024/3/26 下午 5:18 设置域名ip规则 + def set_domain_ip_rule(self, get): + ''' + @name 设置域名ip规则 + @author wzz <2024/3/26 下午 5:19> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.domain = get.get('domain/s', '') + get.port = get.get('port/s', '') + get.strategy = get.get('strategy/s', 'accept') + get.chain = get.get('chain/s', 'INPUT') + + if get.domain == "": + return public.returnMsg(False, '目标域名不能为空') + + if get.port == "": + return public.returnMsg(False, '目标端口不能为空') + + if not public.is_domain(get.domain): + return public.returnMsg(False, '目标域名格式错误') + + address = self.get_a_ip(get.domain) + + if address == "": + return public.returnMsg(False, '域名: 【{}】解析失败'.format(get.domain)) + + if not public.checkIp(get.address): + return public.returnMsg(False, '目标地址格式错误') + + info = { + "Address": address, + "Family": get.family, + "Strategy": get.strategy.lower(), + "Priority": get.priority.lower(), + } + if get.chain == "INPUT": + result = self.firewall.rich_rules(info=info, operation=get.operation) + else: + result = self.firewall.output_rich_rules(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld: + self.firewall.reload() + + return result + + # 2024/3/25 上午 11:18 获取所有ip规则列表 + def ip_rules_list(self, get): + ''' + @name 获取所有ip规则列表 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + get.chain = get.get('chain/s', 'all') + get.query = get.get('query/s', '') + ip_db = self.get_ip_db(get) + + if get.chain == "INPUT": + list_address = self.firewall.list_input_address() + elif get.chain == "OUTPUT": + list_address = self.firewall.list_output_address() + else: + list_address = self.firewall.list_address() + + return self.structure_ip_return_data(list_address, ip_db, query=get.query) + + # 2024/3/26 下午 3:03 导出所有ip规则 + def export_ip_rules(self, get): + ''' + @name 导出所有ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.chain = get.get('chain/s', 'all') + + if get.chain == "INPUT": + file_name = "input_ip_rules_{}".format(int(time.time())) + elif get.chain == "OUTPUT": + file_name = "output_ip_rules_{}".format(int(time.time())) + else: + file_name = "ip_rules_{}".format(int(time.time())) + + data = self.ip_rules_list(get) + if not data: + return public.returnMsg(False, '没有规则无法导出') + + file_path = "{}/{}.json".format(self.config_path, file_name) + + public.writeFile(file_path, public.GetJson(data)) + public.WriteLog("系统防火墙", "导出ip规则") + return public.returnMsg(True, file_path) + + # 2024/3/26 下午 3:05 导入ip规则 + def import_ip_rules(self, get): + ''' + @name 导入ip规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.file = get.get('file/s', '') + + if not get.file: + return public.returnMsg(False, '文件不能为空') + + if not os.path.exists(get.file): + return public.returnMsg(False, '文件不存在') + + try: + data = public.readFile(get.file) + if "|" in data and not "{" in data: + get.rule_name = "ip_rule" + return self.import_rules_old(get) + + data = json.loads(data) + # 2024/4/10 下午2:51 反转数据 + data.reverse() + except: + return public.returnMsg(False, '文件内容异常或格式错误') + + args = public.dict_obj() + for item in data: + args.operation = 'add' + args.address = item['Address'] + args.strategy = item['Strategy'] + args.chain = item['Chain'] + args.family = item['Family'] + args.brief = item['brief'] + args.reload = "0" + + self.set_ip_rule(args) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '导入成功') + + # 2024/3/25 下午 2:34 获取端口转发列表 + def port_forward_list(self, get): + ''' + @name 获取端口转发列表 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return list[dict{}...] + ''' + get.query = get.get('query/s', '') + list_port_forward = self.firewall.list_port_forward() + forward_db = self.get_forward_db(get) + if type(list_port_forward) == list and type(forward_db) == list: + return self.structure_forward_return_data(list_port_forward, forward_db, query=get.query) + return [] + + # 2024/3/26 下午 3:08 导出所有端口转发规则 + def export_port_forward(self, get): + ''' + @name 导出所有端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + data = self.firewall.list_port_forward() + if not data: + return public.returnMsg(False, '没有规则无法导出') + file_name = "port_forward_{}".format(int(time.time())) + file_path = "{}/{}.json".format(self.config_path, file_name) + + public.writeFile(file_path, public.GetJson(data)) + public.WriteLog("系统防火墙", "导出端口转发规则") + return public.returnMsg(True, file_path) + + # 2024/3/26 下午 3:10 导入端口转发规则 + def import_port_forward(self, get): + ''' + @name 导入端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.file = get.get('file/s', '') + + if not get.file: + return public.returnMsg(False, '文件不能为空') + + if not os.path.exists(get.file): + return public.returnMsg(False, '文件不存在') + + try: + data = public.readFile(get.file) + if "|" in data and not "{" in data: + get.rule_name = "trans_rule" + return self.import_rules_old(get) + + data = json.loads(data) + # 2024/4/10 下午2:51 反转数据 + data.reverse() + except: + return public.returnMsg(False, '文件内容异常或格式错误') + + args = public.dict_obj() + for item in data: + args.operation = 'add' + args.protocol = item['Protocol'] + args.S_Address = item['S_Address'] + args.S_Port = item['S_Port'] + args.T_Address = item['T_Address'] + args.T_Port = item['T_Port'] + args.reload = "0" + + self.set_port_forward(args) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '导入成功') + + # 2024/3/25 下午 3:43 设置端口转发 + def set_port_forward(self, get): + ''' + @name 设置端口转发 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.operation = get.get('operation/s', 'add') + get.protocol = get.get('protocol/s', 'tcp') + get.S_Address = get.get('S_Address/s', '') + get.S_Port = get.get('S_Port/s', '') + get.T_Address = get.get("T_Address/s", '') + get.T_Port = get.get("T_Port/s", '') + get.reload = get.get('reload/s', "1") + + if get.S_Port == "": + return public.returnMsg(False, '源端口不能为空') + # if get.T_Address == "": + # return public.returnMsg(False, '目标地址不能为空') + if get.T_Port == "": + return public.returnMsg(False, '目标端口不能为空') + + # 2024/3/25 下午 5:49 前置检测 + if get.operation == "add": + check_ip_forward = self.firewall.check_ip_forward() + if not check_ip_forward["status"]: + return check_ip_forward + + if not self.check_forward_exist(get): + return public.returnMsg(False, '端口转发规则已存在,请勿重复添加') + + self.set_forward_db(get) + + # 2024/3/25 下午 5:50 构造传参,调用底层方法设置端口转发 + if get.protocol == "tcp/udp" or get.protocol == "all": + info = { + "Family": "ipv4", + "Protocol": "tcp", + "S_Address": get.S_Address, + "S_Port": get.S_Port, + "T_Address": get.T_Address, + "T_Port": get.T_Port, + } + result = self.firewall.port_forward(info=info, operation=get.operation) + if not result['status']: + return result + + info = { + "Family": "ipv4", + "Protocol": "udp", + "S_Address": get.S_Address, + "S_Port": get.S_Port, + "T_Address": get.T_Address, + "T_Port": get.T_Port, + } + result = self.firewall.port_forward(info=info, operation=get.operation) + + if result['status'] and self._isFirewalld and get.reload == "1": + self.firewall.reload() + else: + info = { + "Family": "ipv4", + "Protocol": get.protocol, + "S_Address": get.S_Address, + "S_Port": get.S_Port, + "T_Address": get.T_Address, + "T_Port": get.T_Port, + } + result = self.firewall.port_forward(info=info, operation=get.operation) + + # 2024/3/25 下午 5:50 如果设置成功才重载防火墙 + if result['status'] and self._isFirewalld and get.reload == "1": + self.firewall.reload() + + return result + + # 2024/3/27 上午 9:44 修改端口转发规则 + def modify_forward_rule(self, get): + ''' + @name 修改端口转发规则 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 2024/4/17 下午4:47 检查防火墙状态,如果未启动则不允许设置规则 + if not self.get_firewall_status(): + return public.returnMsg(False, '请先启动防火墙后再设置规则!') + + get.old_data = get.get('old_data/s', '') + get.new_data = get.get('new_data/s', '') + + if get.old_data == "": + return public.returnMsg(False, '请传入old_data') + + if get.new_data == "": + return public.returnMsg(False, '请传入new_data') + + get.old_data = json.loads(get.old_data) + get.new_data = json.loads(get.new_data) + + args1 = public.dict_obj() + args1.operation = 'remove' + args1.S_Address = get.old_data['S_Address'] + args1.S_Port = get.old_data['S_Port'] + args1.T_Address = get.old_data['T_Address'] + args1.T_Port = get.old_data['T_Port'] + args1.Protocol = get.old_data['Protocol'] + args1.id = get.old_data['id'] + args1.reload = "0" + self.set_port_forward(args1) + + args2 = public.dict_obj() + args2.operation = 'add' + # args2.S_Address = get.new_data['S_Address'] + args2.S_Port = get.new_data['S_Port'] + args2.T_Address = get.new_data['T_Address'] + args2.T_Port = get.new_data['T_Port'] + args2.Protocol = get.new_data['protocol'] + args2.brief = get.new_data['brief'] + args2.reload = "0" + self.set_port_forward(args2) + + if self._isFirewalld: + self.firewall.reload() + + return public.returnMsg(True, '修改成功') diff --git a/class_v2/firewallModelV2/firewallBase.py b/class_v2/firewallModelV2/firewallBase.py new file mode 100644 index 00000000..3e8d9e83 --- /dev/null +++ b/class_v2/firewallModelV2/firewallBase.py @@ -0,0 +1,220 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2014-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 系统防火墙模型 - 基类 +# ------------------------------ + +import os +import re +from typing import Dict, Union, Any +from xml.etree.ElementTree import ElementTree + +import public + + +class Base(object): + + def __init__(self): + self.config_path = "{}/class/firewallModelV2/config".format(public.get_panel_path()) + self._isUfw = False + self._isFirewalld = False + self._isIptables = False + if os.path.exists('/usr/sbin/ufw'): + self._isUfw = True + from firewallModelV2.app.ufw import Ufw + self.firewall = Ufw() + elif os.path.exists('/usr/sbin/firewalld'): + self._isFirewalld = True + from firewallModelV2.app.firewalld import Firewalld + self.firewall = Firewalld() + elif not self._isUfw and not self._isFirewalld: + self._isIptables = True + from firewallModelv2.app.iptables import Iptables + self.firewall = Iptables() + _months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', + 'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + + # 2024/3/14 上午 11:27 获取防火墙运行状态 + def get_firewall_status(self) -> bool: + ''' + @name 获取防火墙运行状态 + @author wzz <2024/3/14 上午 11:27> + @param + @return bool True/False + ''' + if self._isUfw: + res = public.ExecShell("systemctl is-active ufw")[0] + if res == "active": return True + res = public.ExecShell("systemctl list-units | grep ufw")[0] + if res.find('active running') != -1: return True + res = public.ExecShell('/lib/ufw/ufw-init status')[0] + if res.find("Firewall is not running") != -1: return False + res = public.ExecShell('ufw status verbose')[0] + if res.find('inactive') != -1: return False + return True + if self._isFirewalld: + res = public.ExecShell("ps -ef|grep firewalld|grep -v grep")[0] + if res: return True + res = public.ExecShell("systemctl is-active firewalld")[0] + if res == "active": return True + res = public.ExecShell("systemctl list-units | grep firewalld")[0] + if res.find('active running') != -1: return True + return False + else: + res = public.ExecShell("/etc/init.d/iptables status")[0] + if res.find('not running') != -1: return False + res = public.ExecShell("systemctl is-active iptables")[0] + if res == "active": return True + return True + + # 2024/3/14 上午 11:30 设置禁ping + def set_ping(self, get) -> dict: + ''' + @name 设置禁ping + @author wzz <2024/3/14 上午 11:31> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.status = get.get("status", "1") + get.status = str(get.status) if str(get.status) in ['0', '1'] else '1' + + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + rep = r"net\.ipv4\.icmp_echo.*" + conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + get.status + "\n", conf) + else: + conf += "\nnet.ipv4.icmp_echo_ignore_all=" + get.status + "\n" + + if public.writeFile(filename, conf): + public.ExecShell('sysctl -p') + return public.returnMsg(True, 'SUCCESS') + else: + return public.returnMsg( + False, + '错误:设置失败,sysctl.conf不可写!
                                    ' + '1、如果安装了[宝塔系统加固],请先关闭
                                    ' + '2、如果安装了云锁,请关闭[系统加固]功能
                                    ' + '3、如果安装了安全狗,请关闭[系统防护]功能
                                    ' + '4、如果使用了其它安全软件,请先卸载
                                    ' + ) + + # 2024/3/14 上午 11:37 获取网站日志目录的大小 + def get_www_logs_size(self, get) -> Dict[str, Union[str, Any]]: + ''' + @name 获取网站日志目录的大小 + @author wzz <2024/3/14 上午 11:37> + @param + @return dict{"status":True/False,"msg":"提示信息"} + ''' + path_size = public.get_size_total("/www/wwwlogs") + if not path_size: + return {"log_path": "/www/wwwlogs", "size": "0B"} + + return {"log_path": "/www/wwwlogs", "size": public.to_size(path_size["/www/wwwlogs"])} + + # 2024/3/25 上午 10:50 获取防火墙类型,firewall或ufw + def _get_firewall_type(self) -> str: + ''' + @name 获取防火墙类型,firewall或ufw + @return str firewall/ufw + ''' + import os + if os.path.exists('/usr/sbin/ufw'): + return 'ufw' + if os.path.exists('/usr/sbin/firewalld'): + return 'firewall' + return 'iptables' + + # 2024/3/26 下午 5:01 获取指定域名的A记录 + def get_a_ip(self, domain: str) -> str: + ''' + @name 获取指定域名的A记录 + @param domain: 域名 + @return str + ''' + try: + import socket + return socket.gethostbyname(domain) + except Exception as e: + return "" + + # 2024/3/26 下午 5:40 检查是否已添加计划任务,如果没有则添加 + def check_resolve_crontab(self): + ''' + @name 检查是否已添加计划任务 + @author wzz <2024/3/26 下午 5:41> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + python_path = "{}/pyenv/bin/python".format(public.get_panel_path()) + + if not public.M('crontab').where('name=?', ('[勿删]系统防火墙域名解析检测任务',)).count(): + cmd = '{} {}'.format(python_path, '/www/server/panel/script/firewall_domain.py') + args = {"name": "[勿删]系统防火墙域名解析检测任务", "type": 'minute-n', "where1": '5', "hour": '', + "minute": '', "sName": "", + "sType": 'toShell', "notice": '', "notice_channel": '', "save": '', "save_local": '1', + "backupTo": '', "sBody": cmd, + "urladdress": ''} + import crontab + res = crontab.crontab().AddCrontab(args) + if res and "id" in res.keys(): + return True + return False + return True + + # 2024/3/26 下午 11:37 当没有域名解析时,删除域名解析的计划任务 + def remove_resolve_crontab(self): + ''' + @name 当没有域名解析时,删除域名解析的计划任务 + @author wzz <2024/3/26 下午 11:37> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + + if not public.M('firewall_domain').count(): + pdata = public.M('crontab').where('name=?', '[勿删]系统防火墙域名解析检测任务').select() + if pdata: + import crontab + for i in pdata: + args = {"id": i['id']} + crontab.crontab().DelCrontab(args) + + # 2024/3/26 下午 6:22 端口扫描 + def CheckPort(self, port, protocol): + ''' + @name 端口扫描 + @author wzz <2024/3/26 下午 6:22> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + import socket + localIP = '127.0.0.1' + temp = {} + temp['port'] = port + temp['local'] = True + + try: + if 'tcp' in protocol.lower(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(0.01) + s.connect((localIP, port)) + s.close() + if 'udp' in protocol.lower(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0.01) + s.sendto(b'', (localIP, port)) + s.close() + except: + temp['local'] = False + + result = 0 + if temp['local']: result += 2 + return result diff --git a/class_v2/firewall_new_v2.py b/class_v2/firewall_new_v2.py new file mode 100644 index 00000000..3fe29ef4 --- /dev/null +++ b/class_v2/firewall_new_v2.py @@ -0,0 +1,494 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel x5 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 1249648969@qq.com +# +------------------------------------------------------------------- +import sys,os,public,re,firewalld,time + +if sys.version_info[0] == 2: + reload(sys) + sys.setdefaultencoding('utf-8') + +class firewalls: + __isFirewalld = False + __isUfw = False + __Obj = None + + + def __init__(self): + if os.path.exists('/usr/sbin/firewalld'): self.__isFirewalld = True + if os.path.exists('/usr/sbin/ufw'): self.__isUfw = True + public.M('firewall').execute("alter table firewall add ports TEXT;",()) + public.M('firewall').execute("alter table firewall add protocol TEXT;",()) + public.M('firewall').execute("alter table firewall add address_ip TEXT;",()) + public.M('firewall').execute("alter table firewall add types TEXT;",()) + #这里判断的是Centos7 的系统 + if self.__isFirewalld: + self.__Obj = firewalld.firewalld(); + # 获取列表信息 + self.GetList(); + + #获取服务端列表 + def GetList(self,get = None): + try: + data = {} + # 获取开放的端口 + data['ports'] = self.__Obj.GetAcceptPortList(); + #当前时间 + #'2018-10-11 14:36:40' + addtime = time.strftime('%Y-%m-%d %X',time.localtime()) + # + for i in range(len(data['ports'])): + # + tmp = self.CheckDbExists(data['ports'][i]['port'],data['ports'][i]['protocol']); + # | id | port | ps | addtime | ports | protocol | address_ip | types | + if not tmp: public.M('firewall').add('port,ps,addtime',(data['ports'][i]['port'],'',addtime)) + + data['iplist'] = self.__Obj.GetDropAddressList(); + + for i in range(len(data['iplist'])): + try: + tmp = self.CheckDbExists(data['iplist'][i]['address']); + if not tmp: public.M('firewall').add('port,ps,addtime',(data['iplist'][i]['address'],'',addtime)) + except: + return public.get_error_info() + + # 添加到firewalls 数据表中 + data['reject']=self.__Obj.GetrejectLIST() + + + for i in range(len(data['reject'])): + try: + tmp=self.CheckDbExists2(data['reject'][i]['protocol'], + data['reject'][i]['type'], + data['reject'][i]['port'], + data['reject'][i]['address']) + if not tmp:public.M('firewall').add('protocol,types,ports,address_ip,addtime', + (data['reject'][i]['protocol'], + data['reject'][i]['type'], + data['reject'][i]['port'], + data['reject'][i]['address'],addtime)) + except: + return public.get_error_info() + # 添加允许信息到firewalls 表中 + data['accept'] = self.__Obj.Getacceptlist() + #return data + for i in range(len(data['accept'])): + try: + tmp = self.CheckDbExists2(data['accept'][i]['protocol'], + data['accept'][i]['type'], + data['accept'][i]['port'], + data['accept'][i]['address']) + if not tmp: public.M('firewall').add('protocol,types,ports,address_ip,addtime', + (data['accept'][i]['protocol'], + data['accept'][i]['type'], + data['accept'][i]['port'], + data['accept'][i]['address'],addtime)) + except: + return public.get_error_info() + count = public.M('firewall').count(); + data = {} + data['page'] = public.get_page(count,int(get.p),12,get.collback) + data['data'] = public.M('firewall').limit(data['page']['shift'] + ',' + data['page']['row']).order('id desc').select() + for i in range(len(data['data'])): + if data['data'][i]['port'].find(':') != -1 or data['data'][i]['port'].find('.') != -1 or data['data'][i]['port'].find('-') != -1: + data['data'][i]['status'] = -1; + else: + data['data'][i]['status'] = public.check_port_stat(int(data['data'][i]['port'])); + + data['page'] = data['page']['page'] + return data + except Exception as ex: + return public.get_error_info() + + #检查数据库是否存在 + def CheckDbExists(self,port,type=None): + data = public.M('firewall').field('id,port,ps,addtime,types').select(); + return data + for dt in data: + if dt['port'] == port and dt['type'] == type: return dt; + return False; + + # 查看frewalls 数据库表中是否存在 + # | id | port | ps | addtime | ports | protocol | address_ip | types | + def CheckDbExists2(self,protocol,type,port,address): + data = public.M('firewall').field('protocol,types,ports,address_ip').select() + for dt in data: + if dt['ports'] == port and dt['protocol']==protocol and dt['types']==type and dt['address_ip']==address: return dt; + return False + + + + #重载防火墙配置 + def FirewallReload(self): + if self.__isUfw: + public.ExecShell('/usr/sbin/ufw reload') + return; + if self.__isFirewalld: + public.ExecShell('firewall-cmd --reload') + else: + public.ExecShell('/etc/init.d/iptables save') + public.ExecShell('/etc/init.d/iptables restart') + + + #添加屏蔽IP + def AddDropAddress(self,get): + import time + import re + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + if not re.search(rep,get.port): return public.return_msg_gettext(False,'IP address youve entered is illegal!'); + address = get.port + if public.M('firewall').where("port=?",(address,)).count() > 0: return public.return_msg_gettext(False,'The IP exists in block list, no need to repeat processing!') + if self.__isUfw: + public.ExecShell('ufw deny from ' + address + ' to any'); + else: + if self.__isFirewalld: + public.ExecShell('firewall-cmd --permanent --add-rich-rule=\'rule family=ipv4 source address="'+ address +'" drop\'') + ret=self.__Obj.CheckIpDrop(address) + if not ret: + self.__Obj.AddDropAddress(address) + + else: + public.ExecShell('iptables -I INPUT -s '+address+' -j DROP') + + public.write_log_gettext("Firewall manager", 'Successfully blocked IP [{}]!',(address,)) + addtime = time.strftime('%Y-%m-%d %X',time.localtime()) + public.M('firewall').add('port,ps,addtime',(address,get.ps,addtime)) + self.FirewallReload() + return public.return_msg_gettext(True,'Successfully added') + + + + + #删除IP屏蔽 + def DelDropAddress(self,get): + address = get.port + id = get.id + if self.__isUfw: + public.ExecShell('ufw delete deny from ' + address + ' to any'); + else: + if self.__isFirewalld: + public.ExecShell('firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv4 source address="'+ address +'" drop\'') + ret=self.__Obj.DelDropAddress(address) + if ret: + pass + else: + public.ExecShell('iptables -D INPUT -s '+address+' -j DROP') + + public.write_log_gettext("Firewall manager",'Unblocked IP [{}]!',(address,)) + public.M('firewall').where("id=?",(id,)).delete() + + self.FirewallReload(); + return public.return_msg_gettext(True,'Successfully deleted') + + + #添加放行端口 + def AddAcceptPort(self,get): + flag=False + import re + rep = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep,get.port): return public.return_msg_gettext(False,'Port range is incorrect!'); + import time + port = get.port + ps = get.ps + types=get.type + type_list=['tcp','udp'] + if types not in type_list:return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + notudps = ['80', '443', '8888', '888', '39000:40000', '21', '22'] + if port in notudps:flag=True + #return public.M('firewall').where("port=?", (port,)).count() + if types=='tcp': + if flag: + if public.M('firewall').where("port=?", (port,)).count() > 0: return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + else: + if public.M('firewall').where("port=? and type='tcp'",(port,)).count() > 0: return public.return_msg_gettext(False,'The port exists, no need to repeat the release!') + elif types=='udp': + if flag: + if public.M('firewall').where("port=?", (port,)).count() > 0: return public.return_msg_gettext( False, 'The port exists, no need to repeat the release!') + else: + if public.M('firewall').where("port=? and type='udp'", (port,)).count() > 0: return public.return_msg_gettext(False,'The port exists, no need to repeat the release!') + else: + return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + + if self.__isUfw: + if port in notudps: + public.ExecShell('ufw allow ' + port + '/tcp') + else: + public.ExecShell('ufw allow ' + port + '/'+type+''); + else: + if self.__isFirewalld: + port = port.replace(':','-') + if port in notudps: + public.ExecShell('firewall-cmd --permanent --zone=public --add-port=' + port + '/tcp') + else: + public.ExecShell('firewall-cmd --permanent --zone=public --add-port=' + port + '/' + types +'') + else: + if port in notudps: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + else: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m ' + types +' --dport ' + port + ' -j ACCEPT' ) + + public.write_log_gettext("Firewall manager", 'Successfully accepted port [{}]!',(port,)) + addtime = time.strftime('%Y-%m-%d %X',time.localtime()) + result = public.M('firewall').add('port,ps,addtime,types',(port,ps,addtime,types)) + #return result + self.FirewallReload() + return public.return_msg_gettext(True,'Setup successfully!') + + + #删除放行端口 + def DelAcceptPort(self,get): + port = get.port + id = get.id + types=get.type + type_list = ['tcp', 'udp'] + if not types in type_list: return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + try: + if(port == public.GetHost(True)): return public.return_msg_gettext(False,'Failed,cannot delete current port of the panel!') + if self.__isUfw: + public.ExecShell('ufw delete allow ' + port + '/' + types+ ''); + else: + if self.__isFirewalld: + public.ExecShell('firewall-cmd --permanent --zone=public --remove-port='+port+'/' + types + '') + else: + public.ExecShell('iptables -D INPUT -p tcp -m state --state NEW -m ' + types +' --dport '+port+' -j ACCEPT') + public.write_log_gettext("Firewall manager", 'Successfully deleted accepted port [{}] on firewall!',(port,)) + public.M('firewall').where("id=?",(id,)).delete() + + self.FirewallReload() + return public.return_msg_gettext(True,'Successfully deleted') + except: + return public.return_msg_gettext(False,'Failed to delete') + + + + #设置远程端口状态 + def SetSshStatus(self,get): + version = public.readFile('/etc/redhat-release') + if int(get['status'])==1: + msg = public.get_msg_gettext('SSH service turned off') + act = 'stop' + else: + msg = public.get_msg_gettext('SSH service turned on') + act = 'start' + + if not os.path.exists('/etc/redhat-release'): + public.ExecShell('service ssh ' + act); + elif version.find(' 7.') != -1: + public.ExecShell("systemctl "+act+" sshd.service") + else: + public.ExecShell("/etc/init.d/sshd "+act) + + public.write_log_gettext("Firewall manager", msg) + return public.return_msg_gettext(True,'Setup successfully!') + + + + + #设置ping + def SetPing(self,get): + if get.status == '1': + get.status = '0'; + else: + get.status = '1'; + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + rep = r"net\.ipv4\.icmp_echo.*" + conf = re.sub(rep,'net.ipv4.icmp_echo_ignore_all='+get.status,conf) + else: + conf += "\nnet.ipv4.icmp_echo_ignore_all="+get.status + + + public.writeFile(filename,conf) + public.ExecShell('sysctl -p') + return public.return_msg_gettext(True,'Setup successfully!') + + + + #改远程端口 + def SetSshPort(self,get): + #return public.returnMsg(False,'演示服务器,禁止此操作!'); + port = get.port + if int(port) < 22 or int(port) > 65535: return public.return_msg_gettext(False,'Port range must be between 22 and 65535!'); + ports = ['21','25','80','443','8080','888','8888']; + if port in ports: return public.return_msg_gettext(False,''); + + file = '/etc/ssh/sshd_config' + conf = public.readFile(file) + + rep = "#*Port\\s+([0-9]+)\\s*\n" + conf = re.sub(rep, "Port "+port+"\n", conf) + public.writeFile(file,conf) + + if self.__isFirewalld: + self.__Obj.AddAcceptPort(port); + public.ExecShell('setenforce 0'); + public.ExecShell('sed -i "s#SELINUX=enforcing#SELINUX=disabled#" /etc/selinux/config'); + public.ExecShell("systemctl restart sshd.service") + elif self.__isUfw: + public.ExecShell('ufw allow ' + port + '/tcp'); + public.ExecShell("service ssh restart") + else: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport '+port+' -j ACCEPT') + public.ExecShell("/etc/init.d/sshd restart") + + self.FirewallReload() + public.M('firewall').where("ps=?",(public.get_msg_gettext('SSH Server'),)).setField('port',port) + public.write_log_gettext("Firewall manager", "Successfully changed SSH port to [{}]!",(port,)) + return public.return_msg_gettext(True,'Setup successfully!') + + #取SSH信息 + def GetSshInfo(self,get): + file = '/etc/ssh/sshd_config' + conf = public.readFile(file) + rep = "#*Port\\s+([0-9]+)\\s*\n" + port = re.search(rep,conf).groups(0)[0] + import system + panelsys = system.system(); + + version = panelsys.GetSystemVersion(); + if os.path.exists('/usr/bin/apt-get'): + status = public.ExecShell("service ssh status | grep -P '(dead|stop)'") + else: + if version.find(' 7.') != -1: + status = public.ExecShell("systemctl status sshd.service | grep 'dead'") + else: + status = public.ExecShell("/etc/init.d/sshd status | grep -e 'stopped' -e '已停'") + + if len(status[0]) > 3: + status = False + else: + status = True + isPing = True + try: + file = '/etc/sysctl.conf' + conf = public.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" + tmp = re.search(rep,conf).groups(0)[0] + if tmp == '1': isPing = False + except: + isPing = True + + + + data = {} + data['port'] = port + data['status'] = status + data['ping'] = isPing + return data + + # 指定端口 放行IP + def AddSpecifiesIp(self, get): + ''' + get 里面 有 protocol type port address ps 五个参数 + protocol == ['tcp','udp'] + types==['reject','accept'] # 放行和禁止 + port = 端口 + address 地址 + :param get : + :return: + ''' + + # | ports | protocol | address_ip | types | + flag = False + import re + # 判断端口是否正确 + rep = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep, get.port): return public.return_msg_gettext(False, 'Port range is incorrect! should be between 100-65535'); + + # 判断IP是否正确 + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + if not re.search(rep2, get.address): return public.return_msg_gettext(False, 'IP address is illegal!'); + import time + ports = get.port + ps = get.ps + types = get.type + protocol=get.protocol + address_ip=get.address + + protocol_list = ['tcp', 'udp'] + type_list=['reject','accept'] + # 判断type类型是否正确 + + if types not in type_list:return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + # 判断protocol 类型是否正确 + + if protocol not in protocol_list: return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + + notudps = ['80', '443', '8888', '888', '39000:40000', '21', '22'] + if ports in notudps: flag = True + + # sql 查询 + #sql="select * from firewall where ports='%s' and address_ip='%s' and protocol='%s' and types='%s';" % (str(ports), str(address_ip), str(protocol), str(types)) + query_result = public.M('firewall').where('ports=? and address_ip=? and protocol=? and types=?',(ports, address_ip, protocol, types)).count() + # 这里大于0 表示存在 + if query_result > 0 : return public.return_msg_gettext(False,'The port exists, no need to repeat the release!') + + if self.__isUfw: + if type=='accept': + public.ExecShell('ufw allow proto '+ protocol +' from '+ address_ip+' to any port '+ ports +'') + + else: + public.ExecShell('ufw deny proto ' + protocol + ' from ' + address_ip + ' to any port ' + ports + '') + + else: + if self.__isFirewalld: + port = ports.replace(':', '-') + self.__Obj.Add_Port_IP(port=ports,address=address_ip,pool=protocol,type=types) + else: + if type == 'accept': + public.ExecShell('iptables -I INPUT -s '+ address_ip +' -p '+ protocol +' --dport '+ ports +' -j ACCEPT') + else: + public.ExecShell( + 'iptables -I INPUT -s ' + address_ip + ' -p ' + protocol + ' --dport ' + ports + ' -j DROP') + + + public.write_log_gettext("Firewall manager", 'Successfully accepted port [{}]!', (ports,)) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + result = public.M('firewall').add('protocol,types,port,address_ip,ps,addtime', (protocol,types,ports,address_ip,ps,addtime)) + self.FirewallReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + # 删除指定放行端口 + def DelSpecifiesIp(self, get): + ''' + get 里面 有 protocol type port address ps 五个参数 + protocol == ['tcp','udp'] + type==['reject','accept'] # 放行和禁止 + port = 端口 + address 地址 + :param get: + :return: + ''' + ports = get.port + types = get.type + protocol=get.protocol + address_ip=get.address + protocol_list = ['tcp', 'udp'] + id = get.id + if protocol not in protocol_list: return public.return_msg_gettext(False, 'Specified protocol does NOT exist!') + if self.__isUfw: + if type=='accept': + public.ExecShell('ufw delete allow proto ' + protocol + ' from ' + address_ip + ' to any port ' + ports + '') + else: + public.ExecShell('ufw delete deny proto ' + protocol + ' from ' + address_ip + ' to any port ' + ports + '') + else: + if self.__isFirewalld: + self.__Obj.Del_Port_IP(port=ports,address=address_ip,pool=protocol,type=types) + + else: + if type == 'accept': + public.ExecShell('iptables -D INPUT -s ' + address_ip + ' -p ' + protocol + ' --dport ' + ports + ' -j ACCEPT') + else: + public.ExecShell('iptables -D INPUT -s ' + address_ip + ' -p ' + protocol + ' --dport ' + ports + ' -j DROP') + public.write_log_gettext("Firewall manager", 'FIREWALL_DROP_PORT', (ports,)) + public.M('firewall').where("id=?", (id,)).delete() + + self.FirewallReload() + return public.return_msg_gettext(True, 'Successfully deleted') + + diff --git a/class_v2/firewalld_v2.py b/class_v2/firewalld_v2.py new file mode 100644 index 00000000..0c8fb111 --- /dev/null +++ b/class_v2/firewalld_v2.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# coding:utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 1249648969@qq.com, +# +------------------------------------------------------------------- +# Firewalld管理类 +# ------------------------------ +from xml.etree.ElementTree import ElementTree, Element +import os, public + +class firewalld: + __TREE = None + __ROOT = None + __CONF_FILE = '/etc/firewalld/zones/public.xml' + + # 初始化配置文件XML对象 + def __init__(self): + if self.__TREE: return + self.__TREE = ElementTree() + self.__TREE.parse(self.__CONF_FILE) + self.__ROOT = self.__TREE.getroot() + + + # 获取端口列表 + def GetAcceptPortList(self): + mlist = self.__ROOT.getchildren() + data = [] + for p in mlist: + if p.tag != 'port': continue + tmp = p.attrib + port = p.attrib['port'] + data.append(tmp) + return data + + # 添加端口放行 + def AddAcceptPort(self, port, pool='tcp'): + # 检查是否存在 + if self.CheckPortAccept(pool, port): return True + attr = {"protocol": pool, "port": port} + Port = Element("port", attr) + self.__ROOT.append(Port) + self.Save() + return True + + # 删除端口放行 + def DelAcceptPort(self, port, pool='tcp'): + # 检查是否存在 + if not self.CheckPortAccept(pool, port): return True + mlist = self.__ROOT.getchildren() + m = False + for p in mlist: + if p.tag != 'port': continue + if p.attrib['port'] == port: + self.__ROOT.remove(p) + m = True + if m: + self.Save() + return True + return False + + # 添加UDP端口放行 + def AddUpdPort(self, port, pool='udp'): + # 检查是否存在 + if self.CheckPortAccept(pool, port): return True + attr = {"protocol": pool, "port": port} + Port = Element("port", attr) + self.__ROOT.append(Port) + self.Save() + return True + + # 删除UDP端口放行 + def DelUdpPort(self, port, pool='udp'): + # 检查是否存在 + if not self.CheckPortAccept(pool, port): return True + mlist = self.__ROOT.getchildren() + m = False + for p in mlist: + if p.tag != 'port': continue + if p.attrib['port'] == port: + self.__ROOT.remove(p) + m = True + if m: + self.Save() + return True + return False + + # 检查端口是否已放行 + def CheckPortAccept(self, pool, port): + for p in self.GetAcceptPortList(): + if p['port'] == port and p['protocol']==pool: return True + return False + + + # 获取屏蔽IP列表 + def GetDropAddressList(self): + mlist = self.__ROOT.getchildren() + data = [] + for ip in mlist: + + if ip.tag != 'rule': continue + tmp = {} + ch = ip.getchildren() + a=None + for c in ch: + tmp['type']=None + if c.tag == 'drop': tmp['type'] = 'drop' + if c.tag == 'source': + + tmp['address']=c.attrib['address'] + if tmp['type']: + data.append(tmp) + return data + + # 获取 reject 信息 + def GetrejectLIST(self): + mlist = self.__ROOT.getchildren() + data = [] + for ip in mlist: + #print(ip) + if ip.tag != 'rule': continue + tmp = {} + ch = ip.getchildren() + a=None + flag = None + for c in ch: + tmp['type']=None + if c.tag == 'reject': tmp['type'] = 'reject' + if c.tag == 'source': + + tmp['address']=c.attrib['address'] + if c.tag =='port': + + tmp['protocol']=c.attrib['protocol'] + tmp['port']=c.attrib['port'] + if tmp['type']: + data.append(tmp) + return data + +# 获取 accept 信息 + + def Getacceptlist(self): + mlist = self.__ROOT.getchildren() + data = [] + for ip in mlist: + + if ip.tag != 'rule': continue + tmp = {} + ch = ip.getchildren() + a=None + flag = None + for c in ch: + tmp['type']=None + if c.tag == 'accept': tmp['type'] = 'accept' + if c.tag == 'source': + + tmp['address']=c.attrib['address'] + if c.tag =='port': + tmp['protocol']=c.attrib['protocol'] + tmp['port']=c.attrib['port'] + if tmp['type']: + data.append(tmp) + return data + + +# 获取所有信息 + def Get_All_Info(self): + data={} + data['drop_ip']=self.GetDropAddressList() + data['reject']=self.GetrejectLIST() + data['accept']=self.Getacceptlist() + return data + +# 判断是否存在 + def Chekc_info(self,port,address,pool,type): + data=self.Get_All_Info() + if type=='accept': + for i in data['accept']: + #print(i['address'], i['protocol'], i['port']) + if i['address']==address and i['protocol']==pool and i['port']==port: + return True + else: + return False + elif type=='reject': + for i in data['accept']: + # print(i['address'], i['protocol'], i['port']) + if i['address'] == address and i['protocol'] == pool and i['port'] == port: + return True + else: + return False + else: + return False + + def AddDropAddress(self, address): + # 检查是否存在 + if self.CheckIpDrop(address): return True + attr = {"family": 'ipv4'} + rule = Element("rule", attr) + attr = {"address": address} + source = Element("source", attr) + drop = Element("drop", {}) + rule.append(source) + rule.append(drop) + self.__ROOT.append(rule) + self.Save() + return 'OK' + + # 删除IP屏蔽 + def DelDropAddress(self, address): + # 检查是否存在 + if not self.CheckIpDrop(address): return True + mlist = self.__ROOT.getchildren() + for ip in mlist: + if ip.tag != 'rule': continue + ch = ip.getchildren() + for c in ch: + + if c.tag != 'source':continue + if c.attrib['address'] == address: + self.__ROOT.remove(ip) + self.Save() + return True + return False + + + +# 添加端口放行并且指定IP + def Add_Port_IP(self, port,address,pool,type): + if type=='accept': + # 判断是否存在 + if self.Chekc_info(port,address,pool,type): return True + attr = {"family": 'ipv4'} + rule = Element("rule", attr) + attr = {"address": address} + source = Element("source", attr) + attr={'port':str(port),'protocol':pool} + port_info=Element("port",attr) + accept = Element("accept", {}) + rule.append(source) + rule.append(port_info) + rule.append(accept) + self.__ROOT.append(rule) + self.Save() + return True + + elif type=='reject': + # 判断是否存在 + if self.Chekc_info(port,address,pool,type):return True + attr = {"family": 'ipv4'} + rule = Element("rule", attr) + attr = {"address": address} + source = Element("source", attr) + attr = {'port': str(port), 'protocol': pool} + port_info = Element("port", attr) + reject = Element("reject", {}) + rule.append(source) + rule.append(port_info) + rule.append(reject) + self.__ROOT.append(rule) + self.Save() + return True + else: + return False + + +# 删除指定端口的=。= + def Del_Port_IP(self, port,address,pool,type): + if type=='accept': + a = None + for i in self.__ROOT: + if i.tag == 'rule': + tmp = {} + for c in i.getchildren(): + tmp['type'] = None + if c.tag == 'accept': tmp['type'] = 'accept' + if c.tag == 'source': + tmp['address'] = c.attrib['address'] + if c.tag == 'port': + tmp['protocol'] = c.attrib['protocol'] + tmp['port'] = c.attrib['port'] + if tmp['type']: + if tmp['port'] == port and tmp['address'] == address and tmp['type'] == type and tmp['protocol'] == pool: + self.__ROOT.remove(i) + self.Save() + return True + + elif type=='reject': + for i in self.__ROOT: + if i.tag == 'rule': + tmp = {} + for c in i.getchildren(): + tmp['type'] = None + if c.tag == 'reject': tmp['type'] = 'reject' + if c.tag == 'source': + tmp['address'] = c.attrib['address'] + if c.tag == 'port': + tmp['protocol'] = c.attrib['protocol'] + tmp['port'] = c.attrib['port'] + if tmp['type']: + if tmp['port'] == port and tmp['address'] == address and tmp['type'] == type and tmp['protocol'] == pool: + self.__ROOT.remove(i) + self.Save() + return True + + # 检查IP是否已经屏蔽 + def CheckIpDrop(self, address): + for ip in self.GetDropAddressList(): + if ip['address'] == address: return True + return False + + # 取服务状态 + def GetServiceStatus(self): + import psutil + for pid in psutil.pids(): + if psutil.Process(pid).name() == 'firewalld': return True + return False + + # 服务控制 + def FirewalldService(self, type): + public.ExecShell('systemctl ' + type + ' firewalld.service') + return public.return_msg_gettext(True, 'Setup successfully!') + + # 保存配置 + def Save(self): + self.format(self.__ROOT) + self.__TREE.write(self.__CONF_FILE, 'utf-8') + public.ExecShell('firewall-cmd --reload') + + # 整理配置文件格式 + def format(self, em, level=0): + i = "\n" + level * " " + if len(em): + if not em.text or not em.text.strip(): + em.text = i + " " + for e in em: + self.format(e, level + 1) + if not e.tail or not e.tail.strip(): + e.tail = i + if level and (not em.tail or not em.tail.strip()): + em.tail = i + + diff --git a/class_v2/firewalls_v2.py b/class_v2/firewalls_v2.py new file mode 100644 index 00000000..5555070c --- /dev/null +++ b/class_v2/firewalls_v2.py @@ -0,0 +1,384 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel x3 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import sys, os, public, re, firewalld, time +from public.validate import Param + + +class firewalls: + __isFirewalld = False + __isUfw = False + __Obj = None + + def __init__(self): + if os.path.exists('/usr/sbin/firewalld'): self.__isFirewalld = True + self.__ufw = 'ufw' + if os.path.exists('/usr/sbin/ufw'): + self.__isUfw = True + self.__ufw = '/usr/sbin/ufw' + if self.__isFirewalld: + try: + self.__Obj = firewalld.firewalld() + self.GetList() + except: + pass + + # 获取服务端列表 + def GetList(self): + try: + data = {} + data['ports'] = self.__Obj.GetAcceptPortList() + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + for i in range(len(data['ports'])): + tmp = self.CheckDbExists(data['ports'][i]['port']) + if not tmp: public.M('firewall').add('port,ps,addtime', (data['ports'][i]['port'], '', addtime)) + + data['iplist'] = self.__Obj.GetDropAddressList() + for i in range(len(data['iplist'])): + try: + tmp = self.CheckDbExists(data['iplist'][i]['address']) + if not tmp: public.M('firewall').add('port,ps,addtime', (data['iplist'][i]['address'], '', addtime)) + except: + pass + except: + pass + + # 检查数据库是否存在 + def CheckDbExists(self, port): + data = public.M('firewall').field('id,port,ps,addtime').select() + for dt in data: + if dt['port'] == port: return dt + return False + + # 重载防火墙配置 + def FirewallReload(self): + if self.__isUfw: + public.ExecShell('/usr/sbin/ufw reload &') + return + if self.__isFirewalld: + public.ExecShell('firewall-cmd --reload &') + else: + public.ExecShell('/etc/init.d/iptables save &') + public.ExecShell('/etc/init.d/iptables restart &') + + # 取防火墙状态 + def CheckFirewallStatus(self): + # if self.__isUfw: + # res = public.ExecShell('ufw status verbose')[0] + # if res.find('inactive') != -1: return False + # return True + + # if self.__isFirewalld: + # res = public.ExecShell("systemctl status firewalld")[0] + # if res.find('active (running)') != -1: return True + # if res.find('disabled') != -1: return False + # if res.find('inactive (dead)') != -1: return False + # else: + # res = public.ExecShell("/etc/init.d/iptables status")[0] + # if res.find('not running') != -1: return False + # return True + return public.get_firewall_status() == 1 + + def SetFirewallStatus(self, get=None): + ''' + @name 设置系统防火墙状态 + @author hwliang<2022-01-13> + ''' + status = not self.CheckFirewallStatus() + status_msg = {False: 'Close', True: 'Open'} + if self.__isUfw: + if status: + public.ExecShell('echo y|{} enable'.format(self.__ufw)) + else: + public.ExecShell('echo y|{} disable'.format(self.__ufw)) + if self.__isFirewalld: + if status: + public.ExecShell('systemctl enable firewalld') + public.ExecShell('systemctl start firewalld') + else: + public.ExecShell('systemctl disable firewalld') + public.ExecShell('systemctl stop firewalld') + else: + if status: + public.ExecShell("chkconfig iptables on") + public.ExecShell('/etc/init.d/iptables start') + else: + public.ExecShell("chkconfig iptables off") + public.ExecShell('/etc/init.d/iptables stop') + public.write_log_gettext('Firewall manager', '{} system firewall!', (status_msg[status],)) + return public.return_msg_gettext(True, '{} system firewall!', (status_msg[status],)) + + # 添加屏蔽IP + def AddDropAddress(self, get): + if not self.CheckFirewallStatus(): return public.return_msg_gettext(False, 'The system firewall is not open') + import time + import re + ip_format = get.port.split('/')[0] + if not public.check_ip(ip_format): return public.return_msg_gettext(False, 'IP address you entered is illegal!') + if ip_format in ['0.0.0.0', '127.0.0.0', "::1"]: return public.return_msg_gettext(False, + 'Disabling this IP will cause your server to fail') + address = get.port + if public.M('firewall').where("port=?", (address,)).count() > 0: return public.return_msg_gettext(False, + 'The IP exists in block list, no need to repeat processing!') + if self.__isUfw: + if public.is_ipv6(ip_format): + public.ExecShell('{} deny from {} to any'.format(self.__ufw, address)) + else: + public.ExecShell('{} insert 1 deny from {} to any'.format(self.__ufw, address)) + else: + if self.__isFirewalld: + # self.__Obj.AddDropAddress(address) + if public.is_ipv6(ip_format): + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule=\'rule family=ipv6 source address="' + address + '" drop\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule=\'rule family=ipv4 source address="' + address + '" drop\'') + else: + if public.is_ipv6(ip_format): return public.return_msg_gettext(False, 'IP address is illegal!') + public.ExecShell('iptables -I INPUT -s ' + address + ' -j DROP') + + public.WriteLog("TYPE_FIREWALL", 'FIREWALL_DROP_IP', (address,)) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + public.M('firewall').add('port,ps,addtime', (address, get.ps, addtime)) + self.FirewallReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + # 删除IP屏蔽 + def DelDropAddress(self, get): + if not self.CheckFirewallStatus(): return public.return_msg_gettext(False, 'The system firewall is not open') + address = get.port + id = get.id + ip_format = get.port.split('/')[0] + if self.__isUfw: + public.ExecShell('{} delete deny from {} to any'.format(self.__ufw, address)) + else: + if self.__isFirewalld: + if public.is_ipv6(ip_format): + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv6 source address="' + address + '" drop\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv4 source address="' + address + '" drop\'') + else: + public.ExecShell('iptables -D INPUT -s ' + address + ' -j DROP') + + public.WriteLog("TYPE_FIREWALL", 'FIREWALL_ACCEPT_IP', (address,)) + public.M('firewall').where("id=?", (id,)).delete() + + self.FirewallReload() + return public.return_msg_gettext(True, 'Successfully deleted') + + # 添加放行端口 + def AddAcceptPort(self, get): + if not self.CheckFirewallStatus(): return public.return_msg_gettext(False, 'The system firewall is not open') + import re + src_port = get.port + get.port = get.port.replace('-', ':') + rep = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep, get.port): + return public.return_msg_gettext(False, 'Port range must be between 22 and 65535!') + + import time + port = get.port + ps = public.xssencode2(get.ps) + is_exists = public.M('firewall').where("port=? or port=?", (port, src_port)).count() + if is_exists: return public.return_msg_gettext(False, 'The port exists, no need to repeat the release!') + notudps = ['80', '443', '8888', '888', '39000:40000', '21', '22'] + if self.__isUfw: + a = public.ExecShell('{} allow {}/tcp'.format(self.__ufw, port)) + # public.writeFile('/tmp/2',str(a)) + if not port in notudps: public.ExecShell('{} allow {}/udp'.format(self.__ufw, port)) + else: + if self.__isFirewalld: + # self.__Obj.AddAcceptPort(port) + port = port.replace(':', '-') + public.ExecShell('firewall-cmd --permanent --zone=public --add-port=' + port + '/tcp') + if not port in notudps: public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + port + '/udp') + else: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + if not port in notudps: public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT') + public.WriteLog("TYPE_FIREWALL", 'FIREWALL_ACCEPT_PORT', (port,)) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + if not is_exists: public.M('firewall').add('port,ps,addtime', (port, ps, addtime)) + self.FirewallReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + # 添加放行端口 + def AddAcceptPortAll(self, port, ps): + if not self.CheckFirewallStatus(): return public.return_msg_gettext(False, 'The system firewall is not open') + import re + port = port.replace('-', ':') + rep = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep, port): + return False + if self.__isUfw: + public.ExecShell('{} allow {}/tcp'.format(self.__ufw, port)) + public.ExecShell('{} allow {}/udp'.format(self.__ufw, port)) + else: + if self.__isFirewalld: + port = port.replace(':', '-') + public.ExecShell('firewall-cmd --permanent --zone=public --add-port=' + port + '/tcp') + public.ExecShell('firewall-cmd --permanent --zone=public --add-port=' + port + '/udp') + else: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT') + return True + + # 删除放行端口 + def DelAcceptPort(self, get): + if not self.CheckFirewallStatus(): return public.return_msg_gettext(False, 'The system firewall is not open') + port = get.port + id = get.id + + if public.is_ipv6(port): return self.DelDropAddress(get) # 如果是ipv6地址,则调用DelDropAddress + + try: + if (port == public.GetHost(True) or port == public.readFile('data/port.pl').strip()): + return public.return_msg_gettext(False, 'Failed,cannot delete current port of the panel') + if self.__isUfw: + public.ExecShell('{} delete allow {}/tcp'.format(self.__ufw, port)) + public.ExecShell('{} delete allow {}/udp'.format(self.__ufw, port)) + else: + if self.__isFirewalld: + # self.__Obj.DelAcceptPort(port) + public.ExecShell('firewall-cmd --permanent --zone=public --remove-port=' + port + '/tcp') + public.ExecShell('firewall-cmd --permanent --zone=public --remove-port=' + port + '/udp') + else: + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m udp --dport ' + port + ' -j ACCEPT') + public.WriteLog("TYPE_FIREWALL", 'FIREWALL_DROP_PORT', (port,)) + public.M('firewall').where("id=?", (id,)).delete() + + self.FirewallReload() + return public.return_msg_gettext(True, 'Successfully deleted') + except: + return public.return_msg_gettext(False, 'Failed to delete') + + # 设置远程端口状态 + def SetSshStatus(self, get): + # version = public.readFile('/etc/redhat-release') + if int(get['status']) == 1: + msg = public.get_msg_gettext('SSH service turned off') + act = 'stop' + else: + msg = public.get_msg_gettext('SSH service turned on') + act = 'start' + + # if not os.path.exists('/etc/redhat-release'): + # public.ExecShell('service ssh ' + act) + # elif version.find(' 7.') != -1 or version.find(' 8.') != -1 or version.find('Fedora') != -1: + # public.ExecShell("systemctl "+act+" sshd") + # else: + # 全试一次? + public.ExecShell("/etc/init.d/sshd " + act) + public.ExecShell('service ssh ' + act) + public.ExecShell("systemctl " + act + " sshd") + public.ExecShell("systemctl " + act + " ssh") + if act in ['start'] and not public.get_sshd_status(): + msg = 'SSHD service failed to start' + public.WriteLog("TYPE_FIREWALL", msg) + return public.returnMsg(False, msg) + public.WriteLog("TYPE_FIREWALL", msg) + return public.return_msg_gettext(True, 'Setup successfully!') + + # 设置ping + def SetPing(self, get): + if get.status == '1': + get.status = '0' + else: + get.status = '1' + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + rep = r"net\.ipv4\.icmp_echo.*" + conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + get.status + "\n", conf) + else: + conf += "\nnet.ipv4.icmp_echo_ignore_all=" + get.status + "\n" + + if public.writeFile(filename, conf): + public.ExecShell('sysctl -p') + return public.returnMsg(True, 'SUCCESS') + else: + return public.returnMsg(False, + 'ERROR: setup failed, [sysctl.conf] not writable!
                                    1. If [System hardening] is installed, please close it first
                                    ') + + # 改远程端口 + def SetSshPort(self, get): + + # 校验参数 + try: + get.validate([ + Param('port').Require().Number(">=", 22).Number("<=", 65535).Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + port = get.port + + ports = ['21', '25', '80', '443', '8080', '888', '8888', '7800'] + if port in ports: + # return public.return_msg_gettext(False,'Do NOT use common default port!') + return public.return_message(-1, 0, 'Do NOT use common default port!') + file = '/etc/ssh/sshd_config' + conf = public.readFile(file) + + rep = r"#*Port\s+([0-9]+)\s*\n" + conf = re.sub(rep, "Port " + port + "\n", conf) + public.writeFile(file, conf) + + if self.__isFirewalld: + public.ExecShell('firewall-cmd --permanent --zone=public --add-port=' + port + '/tcp') + public.ExecShell('setenforce 0') + public.ExecShell('sed -i "s#SELINUX=enforcing#SELINUX=disabled#" /etc/selinux/config') + public.ExecShell("systemctl restart sshd.service") + elif self.__isUfw: + public.ExecShell('{} allow {}/tcp'.format(self.__ufw, port)) + public.ExecShell("service ssh restart") + else: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + port + ' -j ACCEPT') + public.ExecShell("/etc/init.d/sshd restart") + + self.FirewallReload() + public.M('firewall').where("ps=? or ps=? or port=?", + ('SSH remote management service', 'SSH remote service', port)).delete() + public.M('firewall').add('port,ps,addtime', + (port, 'SSH remote service', time.strftime('%Y-%m-%d %X', time.localtime()))) + public.WriteLog("TYPE_FIREWALL", "FIREWALL_SSH_PORT", (port,)) + # return public.return_msg_gettext(True,'Setup successfully!') + return public.return_message(0, 0, 'Setup successfully!') + + # 取SSH信息 + def GetSshInfo(self, get): + port = public.get_sshd_port() + status = public.get_sshd_status() + isPing = True + try: + file = '/etc/sysctl.conf' + conf = public.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" + tmp = re.search(rep, conf).groups(0)[0] + if tmp == '1': isPing = False + except: + isPing = True + + data = {} + data['port'] = port + data['status'] = status + data['ping'] = isPing + data['firewall_status'] = self.CheckFirewallStatus() + # return data + return public.return_message(0, 0, data) + diff --git a/class_v2/flask_compress_v2.py b/class_v2/flask_compress_v2.py new file mode 100644 index 00000000..698224c7 --- /dev/null +++ b/class_v2/flask_compress_v2.py @@ -0,0 +1,146 @@ +import sys,os +from gzip import GzipFile +from io import BytesIO + +from flask import request, current_app,session,Response,g,abort + + +if sys.version_info[:2] == (2, 6): + class GzipFile(GzipFile): + """ Backport of context manager support for python 2.6""" + def __enter__(self): + if self.fileobj is None: + raise ValueError("I/O operation on closed GzipFile object") + return self + + def __exit__(self, *args): + self.close() + + +class DictCache(object): + + def __init__(self): + self.data = {} + + def get(self, key): + return self.data.get(key) + + def set(self, key, value): + self.data[key] = value + + +class Compress(object): + """ + The Compress object allows your application to use Flask-Compress. + + When initialising a Compress object you may optionally provide your + :class:`flask.Flask` application object if it is ready. Otherwise, + you may provide it later by using the :meth:`init_app` method. + + :param app: optional :class:`flask.Flask` application object + :type app: :class:`flask.Flask` or None + """ + def __init__(self, app=None): + """ + An alternative way to pass your :class:`flask.Flask` application + object to Flask-Compress. :meth:`init_app` also takes care of some + default `settings`_. + + :param app: the :class:`flask.Flask` application object. + """ + self.app = app + if app is not None: + self.init_app(app) + + def init_app(self, app): + defaults = [ + ('COMPRESS_MIMETYPES', ['text/html', 'text/css', 'text/xml', + 'application/json', + 'application/javascript']), + ('COMPRESS_LEVEL', 9), + ('COMPRESS_MIN_SIZE', 500), + ('COMPRESS_CACHE_KEY', None), + ('COMPRESS_CACHE_BACKEND', None), + ('COMPRESS_REGISTER', True), + ] + + for k, v in defaults: + app.config.setdefault(k, v) + + backend = app.config['COMPRESS_CACHE_BACKEND'] + self.cache = backend() if backend else None + self.cache_key = app.config['COMPRESS_CACHE_KEY'] + + if (app.config['COMPRESS_REGISTER'] and + app.config['COMPRESS_MIMETYPES']): + app.after_request(self.after_request) + + def after_request(self, response): + app = self.app or current_app + accept_encoding = request.headers.get('Accept-Encoding', '') + response.headers['Server'] = 'nginx' + response.headers['Connection'] = 'keep-alive' + if not 'tmp_login' in session: + response.headers["X-Frame-Options"] = "SAMEORIGIN" + if 'dologin' in g and app.config['SSL']: + try: + for k,v in request.cookies.items(): + response.set_cookie(k,'',expires='Thu, 01-Jan-1970 00:00:00 GMT',path='/') + except: + pass + + if 'rm_ssl' in g: + import public + try: + for k,v in request.cookies.items(): + response.set_cookie(k,'',expires='Thu, 01-Jan-1970 00:00:00 GMT',path='/') + except: + pass + session_name = app.config['SESSION_COOKIE_NAME'] + session_id = public.get_session_id() + response.set_cookie(session_name,'',expires='Thu, 01-Jan-1970 00:00:00 GMT',path='/') + response.set_cookie(session_name, session_id, path='/', max_age=86400 * 30,httponly=True) + + request_token = request.cookies.get('request_token','') + if request_token: + response.set_cookie('request_token',request_token,path='/',max_age=86400 * 30) + + if (response.mimetype not in app.config['COMPRESS_MIMETYPES'] or + 'gzip' not in accept_encoding.lower() or + not 200 <= response.status_code < 300 or + (response.content_length is not None and + response.content_length < app.config['COMPRESS_MIN_SIZE']) or + 'Content-Encoding' in response.headers): + g.response = response + return response + + response.direct_passthrough = False + if self.cache: + key = self.cache_key(response) + gzip_content = self.cache.get(key) or self.compress(app, response) + self.cache.set(key, gzip_content) + else: + gzip_content = self.compress(app, response) + + response.set_data(gzip_content) + + response.headers['Content-Encoding'] = 'gzip' + response.headers['Content-Length'] = response.content_length + + vary = response.headers.get('Vary') + if vary: + if 'accept-encoding' not in vary.lower(): + response.headers['Vary'] = '{}, Accept-Encoding'.format(vary) + else: + response.headers['Vary'] = 'Accept-Encoding' + + g.response = response + return response + + def compress(self, app, response): + gzip_buffer = BytesIO() + with GzipFile(mode='wb', + compresslevel=app.config['COMPRESS_LEVEL'], + fileobj=gzip_buffer) as gzip_file: + gzip_file.write(response.get_data()) + return gzip_buffer.getvalue() diff --git a/class_v2/flask_sockets_v2.py b/class_v2/flask_sockets_v2.py new file mode 100644 index 00000000..ce6ac897 --- /dev/null +++ b/class_v2/flask_sockets_v2.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +from werkzeug.routing import Map, Rule +from werkzeug.exceptions import NotFound +from werkzeug.http import parse_cookie +from flask import request + + +# Monkeys are made for freedom. +try: + from geventwebsocket.gunicorn.workers import GeventWebSocketWorker as Worker + from geventwebsocket.handler import WebSocketHandler + from gunicorn.workers.ggevent import PyWSGIHandler + + import gevent +except ImportError: + pass + + +class SocketMiddleware(object): + + def __init__(self, wsgi_app, app, socket): + self.ws = socket + self.app = app + self.wsgi_app = wsgi_app + + def __call__(self, environ, start_response): + adapter = self.ws.url_map.bind_to_environ(environ) + try: + handler, values = adapter.match() + environment = environ['wsgi.websocket'] + cookie = None + if 'HTTP_COOKIE' in environ: + cookie = parse_cookie(environ['HTTP_COOKIE']) + + with self.app.app_context(): + with self.app.request_context(environ): + # add cookie to the request to have correct session handling + request.cookie = cookie + + handler(environment, **values) + return [] + except (NotFound, KeyError): + return self.wsgi_app(environ, start_response) + + +class Sockets(object): + + def __init__(self, app=None): + #: Compatibility with 'Flask' application. + #: The :class:`~werkzeug.routing.Map` for this instance. You can use + #: this to change the routing converters after the class was created + #: but before any routes are connected. + self.url_map = Map() + + #: Compatibility with 'Flask' application. + #: All the attached blueprints in a dictionary by name. Blueprints + #: can be attached multiple times so this dictionary does not tell + #: you how often they got attached. + self.blueprints = {} + self._blueprint_order = [] + + if app: + self.init_app(app) + + def init_app(self, app): + app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self) + + def route(self, rule, **options): + + def decorator(f): + endpoint = options.pop('endpoint', None) + self.add_url_rule(rule, endpoint, f, **options) + return f + return decorator + + def add_url_rule(self, rule, _, f, **options): + self.url_map.add(Rule(rule, endpoint=f)) + + def register_blueprint(self, blueprint, **options): + """ + Registers a blueprint for web sockets like for 'Flask' application. + + Decorator :meth:`~flask.app.setupmethod` is not applied, because it + requires ``debug`` and ``_got_first_request`` attributes to be defined. + """ + first_registration = False + + if blueprint.name in self.blueprints: + assert self.blueprints[blueprint.name] is blueprint, ( + 'A blueprint\'s name collision occurred between %r and ' + '%r. Both share the same name "%s". Blueprints that ' + 'are created on the fly need unique names.' + % (blueprint, self.blueprints[blueprint.name], blueprint.name)) + else: + self.blueprints[blueprint.name] = blueprint + self._blueprint_order.append(blueprint) + first_registration = True + + blueprint.register(self, options, first_registration) + + +# CLI sugar. +if ('Worker' in locals() and 'PyWSGIHandler' in locals() and + 'gevent' in locals()): + + class GunicornWebSocketHandler(PyWSGIHandler, WebSocketHandler): + def log_request(self): + if '101' not in self.status: + super(GunicornWebSocketHandler, self).log_request() + + Worker.wsgi_handler = GunicornWebSocketHandler + worker = Worker diff --git a/class_v2/ftp_log_v2.py b/class_v2/ftp_log_v2.py new file mode 100644 index 00000000..8d2ea31e --- /dev/null +++ b/class_v2/ftp_log_v2.py @@ -0,0 +1,563 @@ +#coding: utf-8 +# + ------------------------------------------------------------------- +# | aaPanel +# + ------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# + ------------------------------------------------------------------- +# | Author: hezhihong <272267659@@qq.cn> +# + ------------------------------------------------------------------- +import public, os, time +try: + from BTPanel import session +except: + pass +#英文转月份缩写 +month_list = { + "Jan": "1", + "Feb": "2", + "Mar": "3", + "Apr": "4", + "May": "5", + "Jun": "6", + "Jul": "7", + "Aug": "8", + "Sept": "9", + "Sep": "9", + "Oct": "10", + "Nov": "11", + "Dec": "12" +} + + +class ftplog: + + + def __init__(self): + self.__messages_file = "/var/log/" + self.__ftp_backup_path = public.get_backup_path() + '/pure-ftpd/' + if not os.path.isdir(self.__ftp_backup_path): + public.ExecShell('mkdir -p {}'.format(self.__ftp_backup_path)) + self.__script_py = public.get_panel_path() + '/script/ftplogs_cut.py' + self.__AUTH_MSG =public .to_string ([84 ,104 ,105 ,115 ,32 ,102 ,101 ,97 ,116 ,117 ,114 ,101 ,32 ,105 ,115 ,32 ,101 ,120 ,99 ,108 ,117 ,115 ,105 ,118 ,101 ,32 ,116 ,111 ,32 ,116 ,104 ,101 ,32 ,112 ,114 ,111 ,32 ,101 ,100 ,105 ,116 ,105 ,111 ,110 ,44 ,32 ,112 ,108 ,101 ,97 ,115 ,101 ,32 ,97 ,99 ,116 ,105 ,118 ,97 ,116 ,101 ,32 ,105 ,116 ,32 ,102 ,105 ,114 ,115 ,116 ]) + + + def __check_auth(cls): + from plugin_auth_v2 import Plugin as Plugin + plugin_obj = Plugin(False) + plugin_list = plugin_obj.get_plugin_list() + import PluginLoader + self.__IS_PRO_MEMBER = PluginLoader.get_auth_state() > 0 + return int(plugin_list["pro"]) > time.time() or self.__IS_PRO_MEMBER + + def get_file_list(self, path, is_bakcup=False): + """ + @name 取所有messages日志文件 + @param path: 日志文件路径 + @return: 返回日志文件列表 + """ + files = os.listdir(path) + if is_bakcup: + file_name_list = [{ + "file": "/var/log/pure-ftpd.log", + "time": int(time.time()) + }] + else: + file_name_list = [] + for i in files: + tmp_dict = {} + if not i: continue + file_path = path + i + tmp_dict['file'] = file_path + if is_bakcup: + if os.path.isfile(file_path) and i.find('pure-ftpd.log') != -1: + tmp_dict['time'] = int( + public.to_date( + times=os.path.basename(file_path).split('_')[0] + + ' 00:00:00')) + file_name_list.append(tmp_dict) + else: + if os.path.isfile(file_path) and i.find('messages') != -1: + tmp_dict['time'] = int( + public.to_date( + times=os.path.basename(file_path).split('-')[1] + + ' 00:00:00')) + file_name_list.append(tmp_dict) + file_name_list = sorted(file_name_list, + key=lambda x: x['time'], + reverse=False) + return file_name_list + + def set_ftp_log(self, get): + """ + @name 开启、关闭、获取日志状态 + @author hezhihong + @param get.exec_name 执行的动作 + """ + if not self.__check_auth(): + return public.return_message(-1,0, self.__AUTH_MSG) + if not hasattr(get, 'exec_name'): + return public.return_message(-1,0, 'The parameter is incorrect!') + conf_path = '/etc/rsyslog.conf' + conf = public.readFile(conf_path) + if not os.path.exists(conf_path): + return public.return_message(-1,0, 'The rsyslog configuration file does not exist!\nPlease check if rsyslog is installed or if /ect/rsyslog.cn exists!\nIf the debain system is not installed, please execute:apt-get install rsyslog\nPlease execute the Centos system:yum install rsyslog') + conf = public.readFile(conf_path) + import re + search_str = r"ftp\.\*.*\t*.*\t*.*-/var/log/pure-ftpd.log" + search_str_two = "ftp.none" + rep_str = '\nftp.*\t\t-/var/log/pure-ftpd.log\n' + result = re.search(search_str, conf) + #获取日志状态 + if get.exec_name == 'getlog': + if result: + return_result = 'start' + else: + return_result = 'stop' + return public.return_message(0,0, return_result) + #开启日志审计 + elif get.exec_name == 'start': + # 兼容之前开启,会将配置文件搞坏 + if conf.count('ftp.nonenftp') > 5: + conf = ''' +# /etc/rsyslog.conf configuration file for rsyslog +# +# For more information install rsyslog-doc and see +# /usr/share/doc/rsyslog-doc/html/configuration/index.html +# +# Default logging rules can be found in /etc/rsyslog.d/50-default.conf + + +################# +#### MODULES #### +################# + +module(load="imuxsock") # provides support for local system logging +#module(load="immark") # provides --MARK-- message capability + +# provides UDP syslog reception +#module(load="imudp") +#input(type="imudp" port="514") + +# provides TCP syslog reception +#module(load="imtcp") +#input(type="imtcp" port="514") + +# provides kernel logging support and enable non-kernel klog messages +module(load="imklog" permitnonkernelfacility="on") + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# Filter duplicated messages +$RepeatedMsgReduction on + +# +# Set the default permissions for all log files. +# +$FileOwner syslog +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 +$PrivDropToUser syslog +$PrivDropToGroup syslog + + + +# +# Where to place spool and state files +# +$WorkDirectory /var/spool/rsyslog + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + + + + ''' + public.writeFile(conf_path, conf) + return self.set_ftp_log(get) + if '*.info;mail.none;authpriv.none;' not in conf: + conf += '\n*.info;mail.none;authpriv.none;cron.none /var/log/messages\n' + if result: + conf = conf.replace(search_str, rep_str) + else: + conf += rep_str + #禁止ftp日志写入/var/log/messages + + d_conf = conf[conf.rfind('info;'):] + d_conf = d_conf[:d_conf.find('/')] + s_conf = d_conf.replace(',', ';') + if s_conf.find(search_str_two) == -1: + str_index = s_conf.rfind(';') + s_conf = s_conf[:str_index + + 1] + search_str_two + s_conf[str_index + 1:] + conf = conf.replace(d_conf, s_conf) + self.add_crontab() + #关闭日志审计 + elif get.exec_name == 'stop': + if result: + conf = re.sub(search_str, '', conf) + #取消禁止ftp日志写入/var/log/messages + if conf.find(search_str_two) != -1: + conf = conf.replace(search_str_two, '') + for i in [';;', ',,', ';,', ',;']: + if conf.find(i) != -1: conf = conf.replace(i, '') + self.del_crontab() + public.writeFile(conf_path, conf) + public.ExecShell('systemctl restart rsyslog') + return public.return_message(0,0, 'successfully set') + + def get_format_time(self, englist_time): + """ + @name 时间英文转换 + """ + chinanese_time = '' + try: + for i in month_list.keys(): + if i in englist_time: + tmp_time = englist_time.replace(i, month_list[i]) + tmp_time = tmp_time.split() + chinanese_time = '{}-{} {}'.format(tmp_time[0], tmp_time[1], + tmp_time[2]) + break + return chinanese_time + except: + return chinanese_time + + def get_login_log(self, get): + """ + @name 取登录日志 + @author hezhihong + @param get.user_name ftp用户名 + return + """ + if not self.__check_auth(): + return public.return_message(-1,0, self.__AUTH_MSG) + search_str = 'pure-ftpd:' + search_str2 = 'pure-ftpd[' + if not hasattr(get, 'user_name'): + return_message=public.returnMsg(False, 'The parameter is incorrect!') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + args = public.dict_obj() + args.exec_name = 'getlog' + file_name = self.__ftp_backup_path + is_backup = True + if self.set_ftp_log(get) == 'stop': + file_name = self.__messages_file + is_backup = False + file_list = self.get_file_list(file_name, is_backup) + data = [] + sortid = 0 + tmp_dict = {} + login_all = [] + for file in file_list: + + if not os.path.isfile(file['file']): continue + conf = public.readFile(file['file']) + lines = conf.split('\n') + for line in lines: + if not line: continue + login_info = {} + if search_str not in line and search_str2 not in line: + continue + tmp_value = ' is now logged in' + info = line[:line.find(search_str)].strip() + if not info: + info = line[:line.find(search_str2)].strip() + hostname = info.split()[-1] + exec_time = info.split(hostname)[0].strip() + exec_time = self.get_format_time(exec_time) + ip = line[line.find('(') + 1:line.find(')')].split('@')[1] + + #取登录成功日志 + if tmp_value in line: + user = line.split(tmp_value)[0].strip().split()[-1] + if user == '?' or user != get.user_name: continue + dict_index = '{}__{}'.format(user, ip) + if dict_index not in tmp_dict: + tmp_dict[dict_index] = [] + tmp_dict[dict_index].append(exec_time) + + #取登出日志 + tmp_value = '[INFO] Logout.' + tmp_value_two = 'Timeout - try typing a little faster next time' + if tmp_value in line or tmp_value_two in line: + user = line[line.find('(') + + 1:line.find(')')].split('@')[0] + if user == '?' or user != get.user_name: continue + dict_index = '{}__{}'.format(user, ip) + try: + login_info['out_time'] = exec_time + login_info['in_time'] = tmp_dict[dict_index][0] + login_info['user'] = user + login_info['ip'] = ip + login_info['status'] = 'Success' #0为登录失败,1为登录成功 + login_info['sortid'] = sortid + login_all.append(login_info) + tmp_dict[dict_index] = [] + sortid += 1 + except: + pass + #取登录失败日志 + tmp_value = 'Authentication failed for user' + if tmp_value in line: + user = line.split(tmp_value)[-1].replace('[', '').replace( + ']', '').strip() + if user == '?' or user != get.user_name: continue + login_info['user'] = user + login_info['ip'] = ip + login_info['status'] = 'Failure' #0为登录失败,1为登录成功 + login_info['in_time'] = exec_time + login_info['out_time'] = exec_time + login_info['sortid'] = sortid + login_all.append(login_info) + sortid += 1 + + if tmp_dict: + for item in tmp_dict.keys(): + if not tmp_dict[item]: continue + info = { + "status": "login successful", + "in_time": tmp_dict[item][0], + "out_time": "connecting", + "user": item.split('__')[0], + "ip": item.split('__')[1], + "sortid": sortid + } + sortid += 1 + login_all.append(info) + #搜索过滤 + if login_all and 'search' in get and get.search and get.search.strip(): + for info in login_all: + try: + search_str = str(get.search).strip().lower() + # public.writeFile('/tmp/aa.aa', get.search) + if info['ip'].find(search_str) != -1 or info['user'].lower( + ).find(search_str) != -1 or info['status'].find( + search_str) != -1 or info['in_time'].find( + search_str) != -1: + data.append(info) + elif info['out_time'] and info['out_time'].find( + search_str) != -1: + data.append(info) + except: + pass + else: + for info2 in login_all: + data.append(info2) + + data = sorted(data, key=lambda x: x['sortid'], reverse=True) + return self.get_page(data, get) + + def get_page(self, data, get): + """ + @name 取分页 + @author hezhihong + @param data 需要分页的数据 list + @param get.p 第几页 + @return 指定分页数据 + """ + # 包含分页类 + import page + # 实例化分页类 + page = page.Page() + + info = {} + info['count'] = len(data) + info['row'] = 10 + info['p'] = 1 + if hasattr(get, 'p'): + info['p'] = int(get['p']) + info['uri'] = {} + info['return_js'] = '' + # 获取分页数据 + result = {} + result['page'] = page.GetPage(info, limit='1,2,3,4,5,8') + n = 0 + result['data'] = [] + for i in range(info['count']): + if n >= page.ROW: break + if i < page.SHIFT: continue + n += 1 + result['data'].append(data[i]) + return public.return_message(0,0,result) + + def get_action_log(self, get): + """ + @name 取操作日志 + @author hezhihong + @param get.user_name ftp用户名 + return {"upload":[],"download":[],"rename":[],"delete":[]} + """ + if not self.__check_auth(): + return public.return_message(-1,0, self.__AUTH_MSG) + search_str = 'pure-ftpd:' + args = public.dict_obj() + args.exec_name = 'getlog' + file_name = self.__ftp_backup_path + is_backup = True + if self.set_ftp_log(get) == 'stop': + file_name = self.__messages_file + is_backup = False + file_list = self.get_file_list(file_name, is_backup) + if not hasattr(get, 'user_name'): + return_message=public.returnMsg(False, 'The parameter is incorrect!') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + data = [] + tmp_data = [] + sortid = 0 + for file in file_list: + if not os.path.isfile(file['file']): continue + conf = public.readFile(file['file']) + lines = conf.split('\n') + for line in lines: + if not line: continue + action_info = {} + if search_str not in line: continue + + tmp_v = line.split(search_str) + hostname = tmp_v[0].strip().split()[3].strip() + action_time = tmp_v[0].replace(hostname, '').strip() + action_info['time'] = self.get_format_time(action_time) + + upload_value = ' uploaded ' + download_value = ' downloaded ' + rename_value = 'successfully renamed or moved:' + delete_value = ' Deleted ' + ip = line[line.find('(') + 1:line.find(')')].split('@')[1] + action_info['ip'] = ip + action_info['type'] = '' + #取操作用户 + user = '' + if upload_value in line or download_value in line or rename_value in line or delete_value in line: + user = line[line.find('(') + + 1:line.find(')')].split('@')[0] + action_info['sortid'] = sortid + sortid = sortid + 1 + if not user or user != get.user_name: continue + #取上传日志 + if (get.type == 'all' + or get.type == 'upload') and upload_value in line: + line_list = line.split() + upload_index = line_list.index('uploaded') + # action_info['file'] = line_list[upload_index - 1].replace( + # '//', '/') + action_info['file'] = line[line.find(']') + + 1:line.rfind('(')].replace( + 'uploaded', + '').replace('//', + '/').strip() + action_info['type'] = 'upload' + tmp_data.append(action_info) + #取下载日志 + if (get.type == 'all' + or get.type == 'download') and download_value in line: + line_list = line.split() + upload_index = line_list.index('downloaded') + action_info['file'] = line_list[upload_index - 1].replace( + '//', '/') + action_info['type'] = 'download' + tmp_data.append(action_info) + #取重命名日志 + if (get.type == 'all' + or get.type == 'rename') and rename_value in line: + action_info['file'] = line.split(rename_value)[1].replace( + '->', 'Renamed to').strip().replace('//', '/') + action_info['type'] = 'rename' + tmp_data.append(action_info) + #取删除日志 + if (get.type == 'all' + or get.type == 'delete') and delete_value in line: + action_info['file'] = line.split()[-1].strip().replace( + '//', '/') + action_info['type'] = 'delete' + tmp_data.append(action_info) + # f.close + #搜索过滤 + if tmp_data and 'search' in get and get.search and get.search.strip(): + for info in tmp_data: + search_str = str(get.search).strip().lower() + if info['ip'].find(search_str) != -1 or info['file'].lower( + ).find(search_str) != -1 or info['type'].find( + search_str) != -1 or info['time'].find( + search_str) != -1 or get.user_name.lower().find( + search_str) != -1: + data.append(info) + else: + for info2 in tmp_data: + data.append(info2) + data = sorted(data, key=lambda x: x['sortid'], reverse=True) + return self.get_page(data, get) + + def del_crontab(self): + """ + @name 删除项目定时清理任务 + @auther hezhihong<2022-10-31> + @return + """ + cron_name = '[Do not delete] FTP audit log cutting task' + cron_path = public.GetConfigValue('setup_path') + '/cron/' + cron_list = public.M('crontab').where("name=?", (cron_name, )).select() + if cron_list: + for i in cron_list: + if not i: continue + cron_echo = public.M('crontab').where( + "id=?", (i['id'], )).getField('echo') + args = {"id": i['id']} + import crontab + crontab.crontab().DelCrontab(args) + del_cron_file = cron_path + cron_echo + public.ExecShell( + "crontab -u root -l| grep -v '{}'|crontab -u root -". + format(del_cron_file)) + + def add_crontab(self): + """ + @name 构造日志切割任务 + """ + python_path = '' + try: + python_path = public.ExecShell('which btpython')[0].strip("\n") + except: + try: + python_path = public.ExecShell('which python')[0].strip("\n") + except: + pass + if not python_path: return False + if not public.M('crontab').where('name=?', + ('[Do not delete] FTP audit log cutting task', )).count(): + cmd = '{} {}'.format(python_path, self.__script_py) + args = { + "name": "[Do not delete] FTP audit log cutting task", + "type": 'day', + "where1": '', + "hour": '0', + "minute": '1', + "sName": "", + "sType": 'toShell', + "notice": '0', + "notice_channel": '', + "save": '', + "save_local": '1', + "backupTo": '', + "sBody": cmd, + "urladdress": '' + } + import crontab + res = crontab.crontab().AddCrontab(args) + if res and "id" in res.keys(): + return True + return False + return True diff --git a/class_v2/ftp_v2.py b/class_v2/ftp_v2.py new file mode 100644 index 00000000..8a40e120 --- /dev/null +++ b/class_v2/ftp_v2.py @@ -0,0 +1,331 @@ +#coding: utf-8 +# + ------------------------------------------------------------------- +# | aaPanel +# + ------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# + ------------------------------------------------------------------- +# | Author: hwliang +# + ------------------------------------------------------------------- +import public,db,re,os,firewalls +import firewalls_v2 as firewalls +from public.validate import Param +try: + from BTPanel import session +except: pass +class ftp: + __runPath = None + + def __init__(self): + self.__runPath = '/www/server/pure-ftpd/bin' + + + #添加FTP + def AddUser(self,get): + # 校验参数 + try: + get.validate([ + Param('ftp_username').String(), + Param('ftp_password').String(), + Param('path').String(), + Param('ps').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + if not os.path.exists('/www/server/pure-ftpd/sbin/pure-ftpd'): + return_message=public.return_msg_gettext(False,'Please install the Pure-FTPd service in the software store first.') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + import files_v2,time + fileObj=files_v2.files() + if get['ftp_username'].strip().find(' ') != -1: + return_message=public.returnMsg(False,'Username cannot contain spaces') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + if re.search(r"\W+",get['ftp_username']): + return_message={'code':501,'msg':public.get_msg_gettext('Username is illegal, special characters are NOT allowed!')} + return public.return_message(-1,0, return_message) + if len(get['ftp_username']) < 3: + return_message={'code':501,'msg':public.get_msg_gettext('Username is illegal, cannot be less than 3 characters!')} + return public.return_message(-1,0, return_message) + if not fileObj.CheckDir(get['path']): + return_message={'code':501,'msg':public.get_msg_gettext('System critical directory cannot be used as FTP directory!')} + return public.return_message(-1,0, return_message) + if public.M('ftps').where('name=?',(get.ftp_username.strip(),)).count(): + return public.return_message(-1,0,'User [{}] exists!'.format(get.ftp_username)) + username = get['ftp_username'].strip() + if re.search("[\\/\\\\:\\*\\?\"\'\\<\\>\\|]+",username): + return_message=public.return_msg_gettext(False,"Name cannot contain /\\:*?\"<>| symbol") + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + password = get['ftp_password'].strip() + if len(password) < 6: + return_message=public.return_msg_gettext(False, 'Password must be at least [{}] characters',("6",)) + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + get.path = get['path'].replace(' ','') + get.path = get.path.replace("\\", "/") + fileObj.CreateDir(get) + public.ExecShell('chown www.www ' + get.path) + public.ExecShell(self.__runPath + '/pure-pw useradd "' + username + '" -u www -d ' + get.path + '< {}',(username,str(ex))) + return_message=public.return_msg_gettext(False,'Failed to add') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + #删除用户 + def DeleteUser(self,get): + # 校验参数 + try: + get.validate([ + Param('username').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + username = get['username'] + id = get['id'] + if public.M('ftps').where("id=? and name=?", (id,username, )).count()==0: + return_message=public.return_msg_gettext(False, 'DEL_ERROR') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + public.ExecShell(self.__runPath + '/pure-pw userdel "' + username + '"') + self.FtpReload() + public.M('ftps').where("id=?",(id,)).delete() + public.write_log_gettext('FTP manager', 'Successfully deleted FTP user[{}]!',(username,)) + return_message=public.return_msg_gettext(True, 'Successfully deleted') + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + except Exception as ex: + public.write_log_gettext('FTP manager', 'Faided to delete FTP user[{}]! => {}',(username,str(ex))) + return_message=public.return_msg_gettext(False,'Failed to delete') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + + #修改用户密码 + def SetUserPassword(self,get): + # 校验参数 + try: + get.validate([ + Param('ftp_username').String(), + Param('new_password').String(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + id = get['id'] + username = get['ftp_username'].strip() + password = get['new_password'].strip() + if public.M('ftps').where("id=? and name=?", (id,username, )).count()==0: + return_message=public.return_msg_gettext(False, 'DEL_ERROR') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + if len(password) < 6: + return_message=public.return_msg_gettext(False,'Password must be at least [{}] characters',("6",)) + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + public.ExecShell(self.__runPath + '/pure-pw passwd "' + username + '"< {}',(username,str(ex))) + return_message=public.return_msg_gettext(False,'Failed to modify') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + + #设置用户状态 + def SetStatus(self,get): + msg = public.get_msg_gettext('Turn off'); + if get.status != '0': msg = public.get_msg_gettext('Turn on'); + try: + id = get['id'] + username = get['username'] + status = get['status'] + if public.M('ftps').where("id=? and name=?", (id,username, )).count()==0: + return_message=public.return_msg_gettext(False, 'DEL_ERROR') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + if int(status)==0: + public.ExecShell(self.__runPath + '/pure-pw usermod "' + username + '" -r 1') + else: + public.ExecShell(self.__runPath + '/pure-pw usermod "' + username + "\" -r ''") + self.FtpReload() + public.M('ftps').where("id=?",(id,)).setField('status',status) + public.write_log_gettext('FTP manager','Successfully {} FTP user [{}]!', (msg,username)) + return_message=public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + except Exception as ex: + public.write_log_gettext('FTP manager','Failed to {} FTP user [{}]! => {}', (msg,username,str(ex))) + return_message=public.return_msg_gettext(False,'{} FTP user failed!',(msg,)) + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + ''' + * 设置FTP端口 + * @param Int _GET['port'] 端口号 + * @return bool + ''' + def setPort(self,get): + # 校验参数 + try: + get.validate([ + Param('port').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + port = get['port'].strip() + if not port: + return public.return_message(-1,0, 'Please enter an integer for the port') + if int(port) < 1 or int(port) > 65535: + return public.return_message(-1,0, 'Port range is incorrect!') + check_used = public.check_port_stat(int(port),public.GetLocalIp()) + if check_used == 2: + return public.return_message(-1,0, 'Port[{}] is used!'.format(str(port))) + data = public.ExecShell('lsof -i:' + str(port))[0] + if len(data) !=0:return public.return_message(-1,0, 'Port[{}] is used!'.format(str(port))) + file = '/www/server/pure-ftpd/etc/pure-ftpd.conf' + conf = public.readFile(file) + rep = u"\n#?\\s*Bind\\s+[0-9]+\\.[0-9]+\\.[0-9]+\\.+[0-9]+,([0-9]+)" + #preg_match(rep,conf,tmp) + conf = re.sub(rep,"\nBind 0.0.0.0," + port,conf) + public.writeFile(file,conf) + public.ExecShell('/etc/init.d/pure-ftpd restart') + public.write_log_gettext('FTP manager', "Successfully modified FTP port to [{}]!",(port,)) + #添加防火墙 + #data = ftpinfo(port=port,ps = 'FTP端口') + get.port=port + get.ps = public.get_msg_gettext('FTP port'); + firewalls.firewalls().AddAcceptPort(get) + session['port']=port + return public.return_message(0,0, 'Setup successfully!') + except Exception as ex: + public.write_log_gettext('FTP manager', 'Failed to modify FTP port! => {}',(str(ex),)) + return public.return_message(-1,0, 'Failed to modify') + + #重载配置 + def FtpReload(self): + public.ExecShell(self.__runPath + '/pure-pw mkdb /www/server/pure-ftpd/etc/pureftpd.pdb') + + def get_login_logs(self, get): + import ftp_log_v2 as ftplog + ftpobj = ftplog.ftplog() + return ftpobj.get_login_log(get) + def get_action_logs(self, get): + import ftp_log_v2 as ftplog + ftpobj = ftplog.ftplog() + return ftpobj.get_action_log(get) + + def set_ftp_logs(self, get): + import ftp_log_v2 as ftplog + ftpobj = ftplog.ftplog() + result = ftpobj.set_ftp_log(get) + return result + + #修改用户密码 + def set_user_home(self,get): + """ + change user home + id: ftp id + path: the new ftp user home + ftp_username: ftp username + migrate: migrate ftp user data to the new home + + """ + # 校验参数 + try: + get.validate([ + Param('ftp_username').String(), + Param('path').String(), + Param('id').Integer(), + Param('migrate').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + try: + id = get['id'] + path = get['path'] + username = get['ftp_username'] + # get the old path in the panel sqlite db + old_path = public.M("ftps").where("id=?",(id,)).getField('path') + # check the auth ftp user if exists + auth_conf_file = '/www/server/pure-ftpd/etc/pureftpd.passwd' + auth_conf = public.readFile(auth_conf_file) + if not auth_conf: + return_message=public.returnMsg(False,'FTP account has not been set up') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + # get the user specified conf + auth_conf_list = [i for i in auth_conf.split('\n')] + rep = '^{}:.*'.format(username) + macth_conf = [i for i in auth_conf_list if re.search(rep,i)] + if not macth_conf: + return_message=public.returnMsg(False, 'FTP account has not been set up1') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + if len(macth_conf) > 1: + return_message=public.returnMsg(False, 'Matching multiple configurations, this operation has been stopped!') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + if not os.path.exists(path): + os.makedirs(path) + public.ExecShell('chown www.www ' + path) + # replace the old path + result = macth_conf[0] + specified_user_conf = result.replace(old_path,path) + auth_conf = auth_conf.replace(result,specified_user_conf) + public.writeFile(auth_conf_file,auth_conf) + if get.migrate == '1': + public.ExecShell('cp -rp {}/* {}'.format(old_path,path)) + self.FtpReload() + public.M('ftps').where("id=?",(id,)).setField('path',path) + public.write_log_gettext('FTP manager', 'Successfully changed password for FTP user[{}]!',(path,)) + return_message=public.return_msg_gettext(True,'Setup successfully!') + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + except Exception as ex: + return public.get_error_info() + public.write_log_gettext('FTP manager', 'FTP_PASS_ERR',(path,str(ex))) + return_message=public.returnMsg(False,'EDIT_ERROR') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) diff --git a/class_v2/http_requests_v2.py b/class_v2/http_requests_v2.py new file mode 100644 index 00000000..f5ff530a --- /dev/null +++ b/class_v2/http_requests_v2.py @@ -0,0 +1,612 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +# +------------------------------------------------------------------- +# | 宝塔HTTP通信库 +# +------------------------------------------------------------------- +import os,sys,re +import ssl +import public +import json +import socket +import requests +import config +import requests.packages.urllib3.util.connection as urllib3_conn +from requests.packages.urllib3.exceptions import InsecureRequestWarning +requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + +class http: + _ip_type = None + def __init__(self): + self._ip_type = config.config().get_request_iptype() + + def get(self,url,timeout = 60,headers = {},verify = False,type = 'python'): + url = self.quote(url) + if type in ['python','src','php']: + old_family = urllib3_conn.allowed_gai_family + try: + # 默认使用IPv4 + if self._ip_type == 'ipv4': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + elif self._ip_type == 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + + result = requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify) + except Exception as ex: + # 可能使用了错误的family,尝试清除相关配置 + if(str(ex).find('Cannot assign requested address') != -1): + v_file = '{}/data/v4.pl'.format(public.get_panel_path()) + public.writeFile(v_file,'') + self._ip_type = 'auto' + + try: + # IPV6? + if self._ip_type != 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + result = requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify) + else: + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + result = requests.get(url,timeout=timeout,headers=get_headers(headers),verify=verify) + except: + # 使用CURL + result = self._get_curl(url,timeout,headers,verify) + finally: + urllib3_conn.allowed_gai_family = old_family + + elif type == 'curl': + result = self._get_curl(url,timeout,headers,verify) + if result.status_code == 0: + if self._ip_type == 'ipv4': + self._ip_type = 'ipv6' + elif self._ip_type == 'ipv6': + self._ip_type = 'ipv4' + else: + return result + result = self._get_curl(url,timeout,headers,verify) + if result.status_code != 0: + self.save_ip_type() + elif type == 'php': + result = self._get_php(url,timeout,headers,verify) + if result.status_code == 0: + if self._ip_type == 'ipv4': + self._ip_type = 'ipv6' + elif self._ip_type == 'ipv6': + self._ip_type = 'ipv4' + else: + return result + result = self._get_php(url,timeout,headers,verify) + if result.status_code != 0: + self.save_ip_type() + elif type == 'src': + if sys.version_info[0] == 2: + result = self._get_py2(url,timeout,headers,verify) + else: + result = self._get_py3(url,timeout,headers,verify) + return result + + def post(self,url,data,timeout = 60,headers = {},verify = False,type = 'python'): + url = self.quote(url) + if type in ['python','src','php']: + old_family = urllib3_conn.allowed_gai_family + try: + if self._ip_type == 'ipv4': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + elif self._ip_type == 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + + result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify) + except: + public.print_log(public.get_error_info()) + try: + # IPV6? + if self._ip_type != 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify) + else: + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + result = requests.post(url,data,timeout=timeout,headers=headers,verify=verify) + except: + # 使用CURL + result = self._post_curl(url,data,timeout,headers,verify) + urllib3_conn.allowed_gai_family = old_family + + elif type == 'curl': + result = self._post_curl(url,data,timeout,headers,verify) + if result.status_code == 0: + if self._ip_type == 'ipv4': + self._ip_type = 'ipv6' + elif self._ip_type == 'ipv6': + self._ip_type = 'ipv4' + else: + return result + result = self._post_curl(url,data,timeout,headers,verify) + + # 保存有效的请求IP类型 + if result.status_code != 0: + self.save_ip_type() + elif type == 'php': + result = self._post_php(url,data,timeout,headers,verify) + if result.status_code == 0: + if self._ip_type == 'ipv4': + self._ip_type = 'ipv6' + elif self._ip_type == 'ipv6': + self._ip_type = 'ipv4' + else: + return result + result = self._post_php(url,data,timeout,headers,verify) + if result.status_code != 0: + self.save_ip_type() + elif type == 'src': + if sys.version_info[0] == 2: + result = self._post_py2(url,data,timeout,headers,verify) + else: + result = self._post_py3(url,data,timeout,headers,verify) + return result + + + def save_ip_type(self): + v_file = '{}/data/v4.pl'.format(public.get_panel_path()) + v_body = 'auto' + if self._ip_type == 'ipv4': + v_body = '-4' + elif self._ip_type == 'ipv6': + v_body = '-6' + public.writeFile(v_file,v_body) + + + def download_file(self,url,filename,data = None,timeout = 1800,speed_file='/dev/shm/download_speed.pl'): + ''' + @name 下载文件 + @author hwliang<2021-07-08> + @param url 下载地址 + @param filename 保存路径 + @param data POST参数,不传则使用GET方法,否则使用POST方法 + @param timeout 超时时间,默认1800秒 + @param speed_file + ''' + import requests + from requests.packages.urllib3.exceptions import InsecureRequestWarning + requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + headers = public.get_requests_headers() + if data is None: + res = requests.get(url,headers=headers,timeout=timeout,stream=True) + else: + res = requests.post(url,data,headers=headers,timeout=timeout,stream=True) + with open(filename,"wb") as f: + for _chunk in res.iter_content(chunk_size=8192): + f.write(_chunk) + + + #POST请求 Python2 + def _post_py2(self,url,data,timeout,headers,verify): + import urllib2 + req = urllib2.Request(url, self._str_py_post(data,headers),headers = headers) + try: + if not verify: + context = ssl._create_unverified_context() + r_response = urllib2.urlopen(req,timeout = timeout,context = context) + else: + r_response = urllib2.urlopen(req,timeout = timeout) + except urllib2.HTTPError as err: + return response(str(err),err.code,[]) + except urllib2.URLError as err: + return response(str(err),0,[]) + return response(r_response.read(),r_response.getcode(),r_response.info().headers) + + #POST请求 Python3 + def _post_py3(self,url,data,timeout,headers,verify): + import urllib.request + req = urllib.request.Request(url, self._str_py_post(data,headers),headers = headers) + try: + if not verify: + context = ssl._create_unverified_context() + r_response = urllib.request.urlopen(req,timeout = timeout,context = context) + else: + r_response = urllib.request.urlopen(req,timeout = timeout) + except urllib.error.HTTPError as err: + return response(str(err),err.code,[]) + except urllib.error.URLError as err: + return response(str(err),0,[]) + r_body = r_response.read() + if type(r_body) == bytes: r_body = r_body.decode('utf-8') + return response(r_body,r_response.getcode(),r_response.getheaders()) + + #POST请求,通过CURL + def _post_curl(self,url,data,timeout,headers,verify): + headers_str = self._str_headers(headers) + pdata = self._str_post(data,headers_str) + _ssl_verify = '' + if not verify: _ssl_verify = ' -k' + result = public.ExecShell("{} -X POST -sS -i --connect-timeout {} {} {} '{}' 2>&1".format(self._curl_bin() + _ssl_verify,timeout,headers_str,pdata,url))[0] + r_body,r_headers,r_status_code = self._curl_format(result) + return response(r_body,r_status_code,r_headers) + + #POST请求,通过PHP + def _post_php(self,url,data,timeout,headers,verify): + php_version = self._get_php_version() + if not php_version: + raise Exception('No PHP version available!') + ip_type = '' + if self._ip_type == 'ipv6': + ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);' + elif self._ip_type == 'ipv4': + ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);' + tmp_file = '/dev/shm/http.php' + http_php = ''''''.format(ip_type = ip_type) + public.writeFile(tmp_file,http_php) + #if 'Content-Type' in headers: + # if headers['Content-Type'].find('application/json') != -1: + # data = json.dumps(pdata) + + data = json.dumps({"url":url,"timeout":timeout,"verify":verify,"headers":self._php_headers(headers),"data":data}) + if php_version in ['53']: + php_version = '/www/server/php/' + php_version + '/bin/php' + if php_version.find('/www/server/php') != -1: + result = public.ExecShell(php_version + ' ' + tmp_file + " --post='" + data + "'" )[0] + else: + result = public.request_php(php_version,'/http.php','/dev/shm','POST',{"data":data}) + if isinstance(result,bytes): result = result.decode('utf-8') + + if os.path.exists(tmp_file): os.remove(tmp_file) + r_body,r_headers,r_status_code = self._curl_format(result) + return response(json.loads(r_body),r_status_code,r_headers) + + + #GET请求 Python2 + def _get_py2(self,url,timeout,headers,verify): + import urllib2 + req = urllib2.Request(url, headers = headers) + try: + if not verify: + context = ssl._create_unverified_context() + r_response = urllib2.urlopen(req,timeout = timeout,context = context) + else: + r_response = urllib2.urlopen(req,timeout = timeout) + except urllib2.HTTPError as err: + return response(str(err),err.code,[]) + except urllib2.URLError as err: + return response(str(err),0,[]) + return response(r_response.read(),r_response.getcode(),r_response.info().headers) + + #URL转码 + def quote(self,url): + if url.find('[') == -1: return url + url_tmp = url.split('?') + if len(url_tmp) == 1: return url + url_last = url_tmp[0] + url_args = '?'.join(url_tmp[1:]) + if sys.version_info[0] == 2: + import urllib2 + url_args = urllib2.quote(url_args) + else: + import urllib.parse + url_args = urllib.parse.quote(url_args) + return url_last + '?' + url_args + + #GET请求 Python3 + def _get_py3(self,url,timeout,headers,verify): + import urllib.request + req = urllib.request.Request(url,headers = headers) + try: + if not verify: + context = ssl._create_unverified_context() + r_response = urllib.request.urlopen(req,timeout = timeout,context = context) + else: + r_response = urllib.request.urlopen(req,timeout = timeout) + except urllib.error.HTTPError as err: + return response(str(err),err.code,[]) + except urllib.error.URLError as err: + return response(str(err),0,[]) + r_body = r_response.read() + if type(r_body) == bytes: r_body = r_body.decode('utf-8') + return response(r_body,r_response.getcode(),r_response.getheaders()) + + #GET请求,通过CURL + def _get_curl(self,url,timeout,headers,verify): + headers_str = self._str_headers(headers) + _ssl_verify = '' + if not verify: _ssl_verify = ' -k' + result = public.ExecShell("{} -sS -i --connect-timeout {} {} {} 2>&1".format(self._curl_bin() + ' ' + str(_ssl_verify),timeout,headers_str,url))[0] + r_body,r_headers,r_status_code = self._curl_format(result) + return response(r_body,r_status_code,r_headers) + + #GET请求,通过PHP + def _get_php(self,url,timeout,headers,verify): + php_version = self._get_php_version() + if not php_version: + raise Exception('No PHP version available!') + ip_type = '' + if self._ip_type == 'ipv6': + ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);' + elif self._ip_type == 'ipv4': + ip_type = 'curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);' + tmp_file = '/dev/shm/http.php' + http_php = ''''''.format(ip_type=ip_type) + + public.writeFile(tmp_file,http_php) + data = json.dumps({"url":url,"timeout":timeout,"verify":verify,"headers":self._php_headers(headers)}) + if php_version in ['53']: + php_version = '/www/server/php/' + php_version + '/bin/php' + if php_version.find('/www/server/php') != -1: + result = public.ExecShell(php_version + ' ' + tmp_file + " --post='" + data + "'" )[0] + else: + result = public.request_php(php_version,'/http.php','/dev/shm','POST',{"data":data}) + if isinstance(result,bytes): result = result.decode('utf-8') + + if os.path.exists(tmp_file): os.remove(tmp_file) + r_body,r_headers,r_status_code = self._curl_format(result) + return response(json.loads(r_body).strip(),r_status_code,r_headers) + + + + #取可用的PHP版本 + def _get_php_version(self): + php_versions = public.get_php_versions() + php_versions = sorted(php_versions,reverse=True) + php_path = '/www/server/php/{}/sbin/php-fpm' + php_sock = '/tmp/php-cgi-{}.sock' + for pv in php_versions: + if not os.path.exists(php_path.format(pv)): continue + if not os.path.exists(php_sock.format(pv)): continue + return pv + php_bin = '/www/server/php/{}/bin/php' + for pv in php_versions: + pb = php_bin.format(pv) + if not os.path.exists(pb): continue + return pb + return None + + #取CURL路径 + def _curl_bin(self): + c_bin = ['/usr/local/curl2/bin/curl','/usr/local/curl/bin/curl','/usr/local/bin/curl','/usr/bin/curl'] + curl_bin = 'curl' + for cb in c_bin: + if os.path.exists(cb): curl_bin = cb + if self._ip_type != 'auto': + v4_file = '{}/data/v4.pl'.format(public.get_panel_path()) + v4_body = public.readFile(v4_file).strip() + if not self._ip_type in v4_body: + if self._ip_type == 'ipv4': + v4_body = '-4' + else: + v4_body = '-6' + curl_bin += ' {}'.format(v4_body) + return curl_bin + + #格式化CURL响应头 + def _curl_format(self,req): + match = re.search("(.|\n)+\r\n\r\n",req) + if not match: return req,{},0 + tmp = match.group().split("\r\n") + i = 0 + if tmp[i].find('Continue') != -1: i+=1 + if not tmp[i]: i+=1 + try: + status_code = int(tmp[i].split()[1]) + except: + status_code = 0 + body = req.replace(match.group(),'') + return body,tmp,status_code + + #构造适用于PHP的headers + def _php_headers(self,headers): + php_headers = [] + for h in headers.keys(): + php_headers.append('{}: {}'.format(h,headers[h])) + return php_headers + + #构造适用于CURL的headers + def _str_headers(self,headers): + str_headers = '' + for key in headers.keys(): + str_headers += " -H '{}: {}'".format(key,headers[key]) + return str_headers + + #构造适用于CURL的post参数 + def _str_post(self,pdata,headers): + str_pdata = '' + if headers.find('application/jose') != -1 \ + or headers.find('application/josn') != -1: + if type(pdata) == dict: + pdata = json.dumps(pdata) + if type(pdata) == bytes: + pdata = pdata.decode('utf-8') + str_pdata += " -d '{}'".format(pdata) + return str_pdata + + for key in pdata.keys(): + str_pdata += " -F '{}={}'".format(key ,pdata[key]) + return str_pdata + + #构造适用于python的post参数 + def _str_py_post(self,pdata,headers): + if 'Content-Type' in headers: + if headers['Content-Type'].find('application/jose') != -1 \ + or headers['Content-Type'].find('application/josn') != -1: + if type(pdata) == dict: + pdata = json.dumps(pdata) + if type(pdata) == str: + pdata = pdata.encode('utf-8') + return pdata + return public.url_encode(pdata) + +#响应头对象 +class http_headers: + def __contains__(self, key): + return getattr(self,key.lower(),None) + def __setitem__(self, key, value): setattr(self,key.lower(),value) + def __getitem__(self, key): return getattr(self,key.lower(),None) + def __delitem__(self,key): delattr(self,key.lower()) + def __delattr__(self, key): delattr(self,key.lower()) + def get(self,key): return getattr(self,key.lower(),None) + def get_items(self): return self + +#响应对象 +class response: + status_code = None + status = None + code = None + headers = {} + text = None + content = None + def __init__(self,body,status_code,headers): + self.text = body + self.content = body + self.status_code = status_code + self.status = status_code + self.code = status_code + self.headers = http_headers() + self.format_headers(headers) + + def format_headers(self,raw_headers): + raw = [] + for h in raw_headers: + if not h: continue + if type(h) == tuple: + raw.append(h[0] + ': ' + h[1]) + if len(h) < 2: continue + self.headers[h[0]] = h[1].strip() + else: + raw.append(h.strip()) + tmp = h.split(': ') + if len(tmp) < 2: continue + self.headers[tmp[0]] = tmp[1].strip() + self.headers.raw = '\r\n'.join(raw) + + def close(self): + self.text = None + self.content = None + self.status_code = None + self.status = None + self.code = None + self.headers = None + + #取格式化JSON响应 + def json(self): + try: + return json.loads(self.text) + except: + return self.text + +DEFAULT_HEADERS = {"Content-type":"application/x-www-form-urlencoded","User-Agent":"BT-Panel"} +s_types = ['python','php','curl','src'] +DEFAULT_TYPE = 'python' +__version__ = 1.0 + +#请请求方法 +def get_stype(s_type): + if not s_type: + s_type_file = '/www/server/panel/data/http_type.pl' + if os.path.exists(s_type_file): + tmp_type = public.readFile(s_type_file) + if tmp_type: + tmp_type = tmp_type.strip().lower() + if tmp_type in s_types: s_type = tmp_type + else: + s_type = s_type.lower() + if not s_type in s_types: s_type = DEFAULT_TYPE + if not s_type: s_type = DEFAULT_TYPE + return s_type + +#获取请求头 +def get_headers(headers): + if type(headers) != dict: headers = {} + #if not 'Content-type' in headers: + # headers['Content-type'] = DEFAULT_HEADERS['Content-type'] + if not 'User-Agent' in headers: + headers['User-Agent'] = DEFAULT_HEADERS['User-Agent'] + return headers + +def post(url,data = {},timeout = 60,headers = {},verify = False,s_type = None): + ''' + POST请求 + @param [url] string URL地址 + @parma [data] dict POST参数 + @param [timeout] int 超时时间 默认60秒 + @param [headers] dict 请求头 默认{"Content-type":"application/x-www-form-urlencoded","User-Agent":"BT-Panel"} + @param [verify] bool 是否验证ssl证书 默认False + @param [s_type] string 请求方法 默认python 可选:curl或php + ''' + p = http() + try: + return p.post(url,data,timeout,get_headers(headers),verify,get_stype(s_type)) + except: + raise Exception(public.get_error_info()) + +def get(url,timeout = 60,headers = {},verify = False,s_type = None): + ''' + GET请求 + @param [url] string URL地址 + @param [timeout] int 超时时间 默认60秒 + @param [headers] dict 请求头 默认{"Content-type":"application/x-www-form-urlencoded","User-Agent":"BT-Panel"} + @param [verify] bool 是否验证ssl证书 默认False + @param [s_type] string 请求方法 默认python 可选:curl或php + ''' + p = http() + try: + return p.get(url,timeout,get_headers(headers),verify,get_stype(s_type)) + except: + raise Exception(public.get_error_info()) + diff --git a/class_v2/jobs_v2.py b/class_v2/jobs_v2.py new file mode 100644 index 00000000..e92b40f3 --- /dev/null +++ b/class_v2/jobs_v2.py @@ -0,0 +1,927 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import time,public,db,os,sys,json,re,shutil +os.chdir('/www/server/panel') + +def control_init(): + public.chdck_salt() + clear_other_files() + sql_pacth() + #disable_putenv('putenv') + #clean_session() + #set_crond() + clean_max_log('/www/server/panel/plugin/rsync/lsyncd.log') + clean_max_log('/var/log/rsyncd.log',1024*1024*10) + clean_max_log('/root/.pm2/pm2.log',1024*1024*20) + remove_tty1() + clean_hook_log() + run_new() + clean_max_log('/www/server/cron',1024*1024*5,20) + clean_max_log("/www/server/panel/plugin/webhook/script",1024*1024*1) + #check_firewall() + check_dnsapi() + clean_php_log() + files_set_mode() + set_pma_access() + # public.set_open_basedir() + clear_fastcgi_safe() + update_py37() + run_script() + set_php_cli_env() + check_enable_php() + #sync_node_list() + check_default_curl_file() + null_html() + remove_other() + deb_bashrc() + upgrade_gevent() + upgrade_polkit() + #hide_docker() + rep_pyenv_link() + rm_apache_cgi_test() + +def rm_apache_cgi_test(): + ''' + @name 删除apache测试cgi文件 + @author hwliang + @return void + ''' + test_cgi_file = '/www/server/apache/cgi-bin/test-cgi' + if os.path.exists(test_cgi_file): + os.remove(test_cgi_file) + +def rep_pyenv_link(): + ''' + @name 修复pyenv环境软链 + @author hwliang + @return void + ''' + + pyenv_bin = '/www/server/panel/pyenv/bin/python3' + btpython_bin = '/usr/bin/btpython' + pip_bin = '/www/server/panel/pyenv/bin/pip3' + btpip_bin = '/usr/bin/btpip' + + # 检查btpython软链接 + if not os.path.exists(pyenv_bin): return + if not os.path.exists(btpython_bin): + public.ExecShell("ln -sf {} {}".format(pyenv_bin,btpython_bin)) + + # 检查btpip软链接 + if not os.path.exists(pip_bin): return + if not os.path.exists(btpip_bin): + public.ExecShell("ln -sf {} {}".format(pip_bin,btpip_bin)) + +def hide_docker(): + ''' + @name 隐藏docker菜单 + @author hwliang + @return void + ''' + tip_file = '{}/data/hide_docker.pl'.format(public.get_panel_path()) + if os.path.exists(tip_file): return + + # 正在使用docker-compose的用户不隐藏 + docker_compose = "/usr/bin/docker-compose" + if os.path.exists(docker_compose): return + + # 获取隐藏菜单配置 + menu_key = 'memuDocker' + hide_menu_json = public.read_config('hide_menu') + if not isinstance(hide_menu_json,list): + hide_menu_json = [] + if menu_key in hide_menu_json: return + + # 保存隐藏菜单配置 + hide_menu_json.append(menu_key) + public.save_config('hide_menu',hide_menu_json) + public.writeFile(tip_file,'True') + + + + + +def upgrade_polkit(): + ''' + @name 修复polkit提权漏洞(CVE-2021-4034) + @author hwliang + @return void + ''' + upgrade_log_file = '{}/logs/upgrade_polkit.log'.format(public.get_panel_path()) + tip_file = '{}/data/upgrade_polkit.pl'.format(public.get_panel_path()) + if os.path.exists(tip_file): return + os.system("nohup {} {}/script/polkit_upgrade.py &> {}".format(public.get_python_bin(),public.get_panel_path(),upgrade_log_file)) + +def clear_other_files(): + dirPath = '/www/server/phpmyadmin/pma' + if os.path.exists(dirPath): + public.ExecShell("rm -rf {}".format(dirPath)) + dirPath = '/www/server/nginx/waf' + if os.path.exists(dirPath): + public.ExecShell("rm -rf {}".format(dirPath)) + public.ExecShell("/etc/init.d/nginx reload") + public.ExecShell("/etc/init.d/nginx start") + + dirPath = '/www/server/adminer' + if os.path.exists(dirPath): + public.ExecShell("rm -rf {}".format(dirPath)) + + dirPath = '/www/server/panel/adminer' + if os.path.exists(dirPath): + public.ExecShell("rm -rf {}".format(dirPath)) + + filename = '/www/server/nginx/off' + if os.path.exists(filename): os.remove(filename) + filename = "{}/vhost/nginx/waf.conf".format(public.get_panel_path()) + if os.path.exists(filename): + os.remove(filename) + public.ExecShell("/etc/init.d/nginx reload") + public.ExecShell("/etc/init.d/nginx start") + c = public.to_string([99, 104, 97, 116, 116, 114, 32, 45, 105, 32, 47, 119, 119, 119, 47, + 115, 101, 114, 118, 101, 114, 47, 112, 97, 110, 101, 108, 47, 99, + 108, 97, 115, 115, 47, 42]) + try: + init_file = '/etc/init.d/bt' + src_file = '/www/server/panel/init.sh' + md51 = public.md5(init_file) + md52 = public.md5(src_file) + if md51 != md52: + import shutil + shutil.copyfile(src_file,init_file) + if os.path.getsize(init_file) < 10: + public.ExecShell("chattr -i " + init_file) + public.ExecShell(r"\cp -arf %s %s" % (src_file,init_file)) + public.ExecShell("chmod +x %s" % init_file) + except:pass + public.writeFile('/var/bt_setupPath.conf','/www') + public.ExecShell(c) + p_file = 'class/plugin2.so' + if os.path.exists(p_file): public.ExecShell("rm -f class/*.so") + public.ExecShell("chmod -R 600 /www/server/panel/data;chmod -R 600 /www/server/panel/config;chmod -R 700 /www/server/cron;chmod -R 600 /www/server/cron/*.log;chown -R root:root /www/server/panel/data;chown -R root:root /www/server/panel/config;chown -R root:root /www/server/phpmyadmin;chmod -R 755 /www/server/phpmyadmin") + if os.path.exists("/www/server/mysql"): + public.ExecShell("chown mysql:mysql /etc/my.cnf;chmod 600 /etc/my.cnf") + public.ExecShell("rm -rf /www/server/panel/temp/*") + stop_path = '/www/server/stop' + if not os.path.exists(stop_path): + os.makedirs(stop_path) + public.ExecShell("chown -R root:root {path};chmod -R 755 {path}".format(path=stop_path)) + public.ExecShell('chmod 755 /www;chmod 755 /www/server') + if os.path.exists('/www/server/phpmyadmin/pma'): + public.ExecShell("rm -rf /www/server/phpmyadmin/pma") + if os.path.exists("/www/server/adminer"): + public.ExecShell("rm -rf /www/server/adminer") + if os.path.exists("/www/server/panel/adminer"): + public.ExecShell("rm -rf /www/server/panel/adminer") + if os.path.exists('/dev/shm/session.db'): + os.remove('/dev/shm/session.db') + + node_service_bin = '/usr/bin/nodejs-service' + node_service_src = '/www/server/panel/script/nodejs-service.py' + if os.path.exists(node_service_src): public.ExecShell("chmod 700 " + node_service_src) + if not os.path.exists(node_service_bin): + if os.path.exists(node_service_src): + public.ExecShell("ln -sf {} {}".format(node_service_src,node_service_bin)) + + +def sql_pacth(): + sql = db.Sql().dbfile('system') + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'load_average')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `load_average` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`pro` REAL, +`one` REAL, +`five` REAL, +`fifteen` REAL, +`addtime` INTEGER +)''' + sql.execute(csql,()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%type_id%')).count(): + public.M('sites').execute("alter TABLE sites add type_id integer DEFAULT 0",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'database_servers','%db_type%')).count(): + public.M('databases').execute("alter TABLE database_servers add db_type REAL DEFAULT 'mysql'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%edate%')).count(): + public.M('sites').execute("alter TABLE sites add edate integer DEFAULT '0000-00-00'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%project_type%')).count(): + public.M('sites').execute("alter TABLE sites add project_type STRING DEFAULT 'PHP'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'sites','%project_config%')).count(): + public.M('sites').execute("alter TABLE sites add project_config STRING DEFAULT '{}'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'backup','%ps%')).count(): + public.M('backup').execute("alter TABLE backup add ps STRING DEFAULT 'No'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'databases','%db_type%')).count(): + public.M('databases').execute("alter TABLE databases add db_type integer DEFAULT '0'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'databases','%conn_config%')).count(): + public.M('databases').execute("alter TABLE databases add conn_config STRING DEFAULT '{}'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'databases','%sid%')).count(): + public.M('databases').execute("alter TABLE databases add sid integer DEFAULT 0",()) + + ndb = public.M('databases').order("id desc").field('id,pid,name,username,password,accept,ps,addtime,type').select() + if type(ndb) == str: public.M('databases').execute("alter TABLE databases add type TEXT DEFAULT MySQL",()) + + # 计划任务表处理 + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%status%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'status' INTEGER DEFAULT 1",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%save%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save' INTEGER DEFAULT 3",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%backupTo%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'backupTo' TEXT DEFAULT off",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%sName%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sName' TEXT",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%sBody%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sBody' TEXT",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%sType%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sType' TEXT",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%urladdress%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'urladdress' TEXT",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%save_local%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save_local' INTEGER DEFAULT 0",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%notice%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'notice' INTEGER DEFAULT 0",()) + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%notice_channel%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'notice_channel' TEXT DEFAULT ''",()) + + sql = db.Sql() + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'site_types')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `site_types` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`name` REAL, +`ps` REAL +)''' + + sql.execute(csql,()) + + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'download_token')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `download_token` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`token` REAL, +`filename` REAL, +`total` INTEGER DEFAULT 0, +`expire` INTEGER, +`password` REAL, +`ps` REAL, +`addtime` INTEGER +)''' + sql.execute(csql,()) + + + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'messages')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `messages` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`level` TEXT, +`msg` TEXT, +`state` INTEGER DEFAULT 0, +`expire` INTEGER, +`addtime` INTEGER +)''' + sql.execute(csql,()) + + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'temp_login')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `temp_login` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`token` REAL, +`salt` REAL, +`state` INTEGER, +`login_time` INTEGER, +`login_addr` REAL, +`logout_time` INTEGER, +`expire` INTEGER, +`addtime` INTEGER +)''' + sql.execute(csql,()) + + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'database_servers')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `database_servers` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`db_host` REAL, +`db_port` REAL, +`db_user` INTEGER, +`db_password` INTEGER, +`ps` REAL, +`addtime` INTEGER +)''' + sql.execute(csql,()) + + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'security')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `security` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `type` TEXT, + `log` TEXT, + `addtime` INTEGER DEFAULT 0 + )''' + sql.execute(csql, ()) + + + test_ping() + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'logs','%username%')).count(): + public.M('logs').execute("alter TABLE logs add uid integer DEFAULT '1'",()) + public.M('logs').execute("alter TABLE logs add username TEXT DEFAULT 'system'",()) + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'crontab','%status%')).count(): + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'status' INTEGER DEFAULT 1",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'save' INTEGER DEFAULT 3",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'backupTo' TEXT DEFAULT off",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sName' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sBody' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'sType' TEXT",()) + public.M('crontab').execute("ALTER TABLE 'crontab' ADD 'urladdress' TEXT",()) + + public.M('users').where('email=? or email=?',('287962566@qq.com','amw_287962566@qq.com')).setField('email','test@message.com') + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'users','%salt%')).count(): + public.M('users').execute("ALTER TABLE 'users' ADD 'salt' TEXT",()) + + + if not public.M('sqlite_master').where('type=? AND name=? AND sql LIKE ?', ('table', 'messages','%retry_num%')).count(): + public.M('messages').execute("alter TABLE messages add send integer DEFAULT 0",()) + public.M('messages').execute("alter TABLE messages add retry_num integer DEFAULT 0",()) + + +def upgrade_gevent(): + ''' + @name 升级gevent + @author hwliang + @return void + ''' + tip_file = '{}/data/upgrade_gevent.lock'.format(public.get_panel_path()) + upgrade_script_file = '{}/script/upgrade_gevent.sh'.format(public.get_panel_path()) + if os.path.exists(upgrade_script_file) and not os.path.exists(tip_file): + public.writeFile(tip_file,'1') + os.system("bash {}".format(upgrade_script_file)) + if os.path.exists(tip_file): os.remove(tip_file) + + +def deb_bashrc(): + ''' + @name 针对debian/ubuntu未调用bashrc导致的问题 + @author hwliang + @return void + ''' + bashrc = '/root/.bashrc' + bash_profile = '/root/.bash_profile' + apt_get = '/usr/bin/apt-get' + if not os.path.exists(apt_get): return + if not os.path.exists(bashrc): return + if not os.path.exists(bash_profile): return + + profile_body = public.readFile(bash_profile) + if not isinstance(profile_body,str): return + if profile_body.find('.bashrc') == -1: + public.writeFile(bash_profile,'source ~/.bashrc\n' + profile_body.strip() + "\n") + + + +def remove_other(): + rm_files = [ + "class/pluginAuth.so", + "class/pluginAuth.cpython-310-x86_64-linux-gnu.so", + "class/pluginAuth.cpython-310-aarch64-linux-gnu.so", + "class/pluginAuth.cpython-37m-i386-linux-gnu.so", + "class/pluginAuth.cpython-37m-loongarch64-linux-gnu.so", + "class/pluginAuth.cpython-37m-aarch64-linux-gnu.so", + "class/pluginAuth.cpython-37m-x86_64-linux-gnu.so", + "class/pluginAuth.cpython-37m.so", + "class/libAuth.loongarch64.so", + "class/libAuth.x86.so", + "class/libAuth.x86-64.so", + "class/libAuth.glibc-2.14.x86_64.so", + "class/libAuth.aarch64.so", + "script/check_files.py" + ] + + for f in rm_files: + if os.path.exists(f): + os.remove(f) + + + +def null_html(): + null_files = ['/www/server/nginx/html/index.html','/www/server/apache/htdocs/index.html','/www/server/panel/data/404.html'] + null_new_body=''' +404 Not Found + +

                                    404 Not Found

                                    +
                                    nginx
                                    + +''' + for null_file in null_files: + if not os.path.exists(null_file): continue + + null_body = public.readFile(null_file) + if not null_body: continue + if null_body.find('没有找到站点') != -1 or null_body.find('您请求的文件不存在') != -1: + public.writeFile(null_file,null_new_body) + + +def check_default_curl_file(): + default_file = '{}/data/default_curl.pl'.format(public.get_panel_path()) + if os.path.exists(default_file): + default_curl_body = public.readFile(default_file) + if default_curl_body: + public.WriteFile(default_file,default_curl_body.strip()) + +def set_wp_cache_dir(): + import one_key_wp + one_key_wp.fast_cgi().set_nginx_conf() + public.ExecShell("/etc/init.d/nginx restart") + +def set_php_cli_env(): + ''' + @name 设置php-cli环境变量 + @author hwliang<2021-09-07> + @return void + ''' + php_path = '/www/server/php' + bashrc = '/root/.bashrc' + if not os.path.exists(php_path): return + if not os.path.exists(bashrc): return + # 清理所有别名 + public.ExecShell('sed -i "/alias php/d" {}'.format(bashrc)) + bashrc_body = public.readFile(bashrc) + if not bashrc_body: return + + # 设置默认环境变量版本别名 + env_php_bin = '/usr/bin/php' + if os.path.exists(env_php_bin): + if os.path.islink(env_php_bin): + php_cli_ini = "/etc/php-cli.ini" + if os.path.exists(php_cli_ini): + bashrc_body += "alias php='php -c {}'\n".format(php_cli_ini) + + + # 设置所有已安装的PHP版本环境变量和别名 + php_versions_list = public.get_php_versions() + for php_version in php_versions_list: + php_ini = "{}/{}/etc/php.ini".format(php_path,php_version) + php_cli_ini = "{}/{}/etc/php-cli.ini".format(php_path,php_version) + env_php_bin = "/usr/bin/php{}".format(php_version) + php_bin = "{}/{}/bin/php".format(php_path,php_version) + php_ize = '/usr/bin/php{}-phpize'.format(php_version) + php_ize_src = "{}/{}/bin/phpize".format(php_path,php_version) + php_fpm = '/usr/bin/php{}-php-fpm'.format(php_version) + php_fpm_src = "{}/{}/sbin/php-fpm".format(php_path,php_version) + php_pecl = '/usr/bin/php{}-pecl'.format(php_version) + php_pecl_src = "{}/{}/bin/pecl".format(php_path,php_version) + php_pear = '/usr/bin/php{}-pear'.format(php_version) + php_pear_src = "{}/{}/bin/pear".format(php_path,php_version) + + if os.path.exists(php_bin): + # 设置每个版本的环境变量 + if not os.path.exists(env_php_bin): os.symlink(php_bin,env_php_bin) + if not os.path.exists(php_ize) and os.path.exists(php_ize_src): os.symlink(php_ize_src,php_ize) + if not os.path.exists(php_fpm) and os.path.exists(php_fpm_src): os.symlink(php_fpm_src,php_fpm) + if not os.path.exists(php_pecl) and os.path.exists(php_pecl_src): os.symlink(php_pecl_src,php_pecl) + if not os.path.exists(php_pear) and os.path.exists(php_pear_src): os.symlink(php_pear_src,php_pear) + public.ExecShell(r"\cp -f {} {}".format(php_ini,php_cli_ini)) # 每次复制新的php.ini到php-cli.ini + public.ExecShell('sed -i "/disable_functions/d" {}'.format(php_cli_ini)) # 清理禁用函数 + bashrc_body += "alias php{}='php{} -c {}'\n".format(php_version,php_version,php_cli_ini) # 设置别名 + else: + # 清理已卸载的环境变量 + if os.path.exists(env_php_bin): os.remove(env_php_bin) + if os.path.exists(php_ize): os.remove(php_ize) + if os.path.exists(php_fpm): os.remove(php_fpm) + if os.path.exists(php_pecl): os.remove(php_pecl) + if os.path.exists(php_pear): os.remove(php_pear) + public.writeFile(bashrc,bashrc_body) + + +def check_enable_php(): + ''' + @name 检查nginx下的php配置文件 + ''' + php_versions = public.get_php_versions() + ngx_php_conf = public.get_setup_path() + '/nginx/conf/enable-php-00.conf' + public.writeFile(ngx_php_conf,'') + for php_v in php_versions: + ngx_php_conf = public.get_setup_path() + '/nginx/conf/enable-php-{}.conf'.format(php_v) + if os.path.exists(ngx_php_conf): continue + enable_conf = r''' + location ~ [^/]\.php(/|$) + {{ + try_files $uri =404; + fastcgi_pass unix:/tmp/php-cgi-{}.sock; + fastcgi_index index.php; + include fastcgi.conf; + include pathinfo.conf; + }} + '''.format(php_v) + public.writeFile(ngx_php_conf,enable_conf) + + + +def write_run_script_log(_log,rn='\n'): + _log_file = '/www/server/panel/logs/run_script.log' + public.writeFile(_log_file,_log + rn,'a+') + + +def run_script(): + try: + os.system("{} {}/script/run_script.py".format(public.get_python_bin(),public.get_panel_path())) + run_tip = '/dev/shm/bt.pl' + if os.path.exists(run_tip): return + public.writeFile(run_tip,str(time.time())) + uptime = float(public.readFile('/proc/uptime').split()[0]) + if uptime > 1800: return + run_config ='/www/server/panel/data/run_config' + script_logs = '/www/server/panel/logs/script_logs' + if not os.path.exists(run_config): + os.makedirs(run_config,384) + if not os.path.exists(script_logs): + os.makedirs(script_logs,384) + + for sname in os.listdir(run_config): + script_conf_file = '{}/{}'.format(run_config,sname) + if not os.path.exists(script_conf_file): continue + script_info = json.loads(public.readFile(script_conf_file)) + exec_log_file = '{}/{}'.format(script_logs,sname) + + if not os.path.exists(script_info['script_file']) \ + or script_info['script_file'].find('/www/server/panel/plugin/') != 0 \ + or not re.match(r'^\w+$',script_info['script_file']): + os.remove(script_conf_file) + if os.path.exists(exec_log_file): os.remove(exec_log_file) + continue + + + if script_info['script_type'] == 'python': + _bin = public.get_python_bin() + elif script_info['script_type'] == 'bash': + _bin = '/usr/bin/bash' + if not os.path.exists(_bin): _bin = 'bash' + + exec_script = 'nohup {} {} &> {} &'.format(_bin,script_info['script_file'],exec_log_file) + public.ExecShell(exec_script) + script_info['last_time'] = time.time() + public.writeFile(script_conf_file,json.dumps(script_info)) + except: + pass + + +def clear_fastcgi_safe(): + try: + fastcgifile = '/www/server/nginx/conf/fastcgi.conf' + if os.path.exists(fastcgifile): + conf = public.readFile(fastcgifile) + if conf.find('bt_safe_open') != -1: + public.ExecShell('sed -i "/bt_safe_open/d" {}'.format(fastcgifile)) + public.ExecShell('/etc/init.d/nginx reload') + except: + pass + +#设置文件权限 +def files_set_mode(): + rr = {True:'-R',False:''} + m_paths = [ + ["/www/server/total","/*.lua","root",755,False], + ["/www/server/total","/*.json","root",755,False], + ["/www/server/total/logs","","www",755,True], + ["/www/server/total/total","","www",755,True], + ["/www/server/speed","/*.lua","root",755,False], + ["/www/server/speed/total","","www",755,True], + ["/www/server/btwaf","/*.lua","root",755,False], + ["/www/backup","","root",600,True], + ["/www/wwwlogs","","www",700,True], + ["/www/enterprise_backup","","root",600,True], + ["/www/server/cron","","root",700,True], + ["/www/server/cron","/*.log","root",600,True], + ["/www/server/stop","","root",755,True], + ["/www/server/redis","","redis",700,True], + ["/www/server/redis/redis.conf","","redis",600,False], + ["/www/server/panel/class","","root",600,True], + ["/www/server/panel/data","","root",600,True], + ["/www/server/panel/plugin","","root",600,False], + ["/www/server/panel/BTPanel","","root",600,True], + ["/www/server/panel/vhost","","root",600,True], + ["/www/server/panel/rewrite","","root",600,True], + ["/www/server/panel/config","","root",600,True], + ["/www/server/panel/backup","","root",600,True], + ["/www/server/panel/package","","root",600,True], + ["/www/server/panel/script","","root",700,True], + ["/www/server/panel/temp","","root",600,True], + ["/www/server/panel/tmp","","root",600,True], + ["/www/server/panel/ssl","","root",600,True], + ["/www/server/panel/install","","root",600,True], + ["/www/server/panel/logs","","root",600,True], + ["/www/server/panel/BT-Panel","","root",700,False], + ["/www/server/panel/BT-Task","","root",700,False], + ["/www/server/panel","/*.py","root",600,False], + ["/dev/shm/session.db","","root",600,False], + ["/dev/shm/session_py3","","root",600,True], + ["/dev/shm/session_py2","","root",600,True], + ["/www/server/phpmyadmin","","root",755,True], + ["/www/server/coll","","root",700,True], + ["/www/server/panel/init.sh","","root",600,False], + ["/www/server/panel/license.txt","","root",600,False], + ["/www/server/panel/requirements.txt","","root",600,False], + ["/www/server/panel/update.sh","","root",600,False], + ["/www/server/panel/default.pl","","root",600,False], + ["/www/server/panel/hooks","","root",600,True], + ["/www/server/panel/cache","","root",600,True], + ["/root","","root",550,False], + ["/root/.ssh","","root",700,False], + ["/root/.ssh/authorized_keys","","root",600,False], + ["/root/.ssh/id_rsa.pub","","root",644,False], + ["/root/.ssh/id_rsa","","root",600,False], + ["/root/.ssh/known_hosts","","root",644,False] + ] + + recycle_list = public.get_recycle_bin_list() + for recycle_path in recycle_list: + m_paths.append([recycle_path,'','root',600,True]) + + for m in m_paths: + if not os.path.exists(m[0]): continue + path = m[0] + m[1] + public.ExecShell("chown {R} {U}:{U} {P}".format(P=path,U=m[2],R=rr[m[4]])) + public.ExecShell("chmod {R} {M} {P}".format(P=path,M=m[3],R=rr[m[4]])) + if m[1]: + public.ExecShell("chown {U}:{U} {P}".format(P=m[0],U=m[2],R=rr[m[4]])) + public.ExecShell("chmod {M} {P}".format(P=m[0],M=m[3],R=rr[m[4]])) + + # 移除面板目录下所有文件的所属组、其它用户的写权限 + public.ExecShell("chmod -R go-w /www/server/panel") + +#获取PMA目录 +def get_pma_path(): + pma_path = '/www/server/phpmyadmin' + if not os.path.exists(pma_path): return False + for filename in os.listdir(pma_path): + filepath = pma_path + '/' + filename + if os.path.isdir(filepath): + if filename[0:10] == 'phpmyadmin': + return str(filepath) + return False + + +#处理phpmyadmin访问权限 +def set_pma_access(): + try: + pma_path = get_pma_path() + if not pma_path: return False + if not os.path.exists(pma_path): return False + pma_tmp = pma_path + '/tmp' + if not os.path.exists(pma_tmp): + os.makedirs(pma_tmp) + + nginx_file = '/www/server/nginx/conf/nginx.conf' + if os.path.exists(nginx_file): + nginx_conf = public.readFile(nginx_file) + if nginx_conf.find('/tmp/') == -1: + r_conf = '''/www/server/phpmyadmin; + location ~ /tmp/ { + return 403; + }''' + + nginx_conf = nginx_conf.replace('/www/server/phpmyadmin;',r_conf) + public.writeFile(nginx_file,nginx_conf) + public.serviceReload() + + apa_pma_tmp = pma_tmp + '/.htaccess' + if not os.path.exists(apa_pma_tmp): + r_conf = '''order allow,deny + deny from all''' + public.writeFile(apa_pma_tmp,r_conf) + public.set_mode(apa_pma_tmp,755) + public.set_own(apa_pma_tmp,'root') + + public.ExecShell("chmod -R 700 {}".format(pma_tmp)) + public.ExecShell("chown -R www:www {}".format(pma_tmp)) + return True + except: + return False + + + + + +#尝试升级到独立环境 +def update_py37(): + pyenv='/www/server/panel/pyenv/bin/python3' + pyenv_exists='/www/server/panel/data/pyenv_exists.pl' + if os.path.exists(pyenv) or os.path.exists(pyenv_exists): return False + download_url = public.get_url() + public.ExecShell("nohup curl {}/install/update_panel_en.sh|bash &>/tmp/panelUpdate.pl &".format(download_url)) + public.writeFile(pyenv_exists,'True') + return True + +def test_ping(): + _f = '/www/server/panel/data/ping_token.pl' + if os.path.exists(_f): os.remove(_f) + try: + import panelPing + panelPing.Test().create_token() + except: + pass + +#检查dnsapi +def check_dnsapi(): + dnsapi_file = 'config/dns_api.json' + tmp = public.readFile(dnsapi_file) + if not tmp: return False + dnsapi = json.loads(tmp) + if tmp.find('CloudFlare') == -1: + cloudflare = { + "ps": "Use CloudFlare's API interface to automatically parse and apply for SSL", + "title": "CloudFlare", + "data": [{ + "value": "", + "key": "SAVED_CF_MAIL", + "name": "E-Mail" + }, { + "value": "", + "key": "SAVED_CF_KEY", + "name": "API Key" + }], + "help": "CloudFlare Get in the background Global API Key", + "name": "CloudFlareDns" + } + dnsapi.insert(0,cloudflare) + check_names = {"dns_bt":"Dns_com","dns_dp":"DNSPodDns","dns_ali":"AliyunDns","dns_cx":"CloudxnsDns"} + for i in range(len(dnsapi)): + if dnsapi[i]['name'] in check_names: + dnsapi[i]['name'] = check_names[dnsapi[i]['name']] + + public.writeFile(dnsapi_file,json.dumps(dnsapi)) + return True + + + +#检测端口放行是否同步(仅firewalld) +def check_firewall(): + try: + if not os.path.exists('/usr/sbin/firewalld'): return False + data = public.M('firewall').field('port,ps').select() + import firewalld,firewalls + fs = firewalls.firewalls() + accept_ports = firewalld.firewalld().GetAcceptPortList() + + port_list = [] + for port_info in accept_ports: + if port_info['port'] in port_list: + continue + port_list.append(port_info['port']) + + n = 0 + for p in data: + if p['port'].find('.') != -1: + continue + if p['port'] in port_list: + continue + fs.AddAcceptPortAll(p['port'],p['ps']) + n+=1 + #重载 + if n: fs.FirewallReload() + except: + pass + + +#尝试启动新架构 +def run_new(): + try: + new_file = '/www/server/panel/data/new.pl' + port_file = '/www/server/panel/data/port.pl' + if os.path.exists(new_file): return False + if not os.path.exists(port_file): return False + port = public.readFile(port_file) + if not port: return False + cmd_line = public.ExecShell('lsof -P -i:{}|grep LISTEN|grep -v grep'.format(int(port)))[0] + if len(cmd_line) < 20: return False + if cmd_line.find('BT-Panel') != -1: return False + public.writeFile('/www/server/panel/data/restart.pl','True') + public.writeFile(new_file,'True') + return True + except: + return False + +#清理webhook日志 +def clean_hook_log(): + path = '/www/server/panel/plugin/webhook/script' + if not os.path.exists(path): return False + for name in os.listdir(path): + if name[-4:] != ".log": continue + clean_max_log(path+'/' + name,524288) + +#清理PHP日志 +def clean_php_log(): + path = '/www/server/php' + if not os.path.exists(path): return False + php_list=public.get_php_versions() + for name in os.listdir(path): + if name not in php_list:continue + filename = path +'/'+name + '/var/log/php-fpm.log' + if os.path.exists(filename): clean_max_log(filename) + filename = path +'/'+name + '/var/log/php-fpm-test.log' + if os.path.exists(filename): clean_max_log(filename) + filename = path +'/'+name + '/var/log/slow.log' + if os.path.exists(filename): clean_max_log(filename) + +#清理大日志 +def clean_max_log(log_file,max_size = 104857600,old_line = 100): + if not os.path.exists(log_file): return False + if os.path.getsize(log_file) > max_size: + try: + old_body = public.GetNumLines(log_file,old_line) + public.writeFile(log_file,old_body) + except: + print(public.get_error_info()) + +#删除tty1 +def remove_tty1(): + file_path = '/etc/systemd/system/getty@tty1.service' + if not os.path.exists(file_path): return False + if not os.path.islink(file_path): return False + if os.readlink(file_path) != '/dev/null': return False + try: + os.remove(file_path) + except:pass + + +#默认禁用指定PHP函数 +def disable_putenv(fun_name): + try: + is_set_disable = '/www/server/panel/data/disable_%s' % fun_name + if os.path.exists(is_set_disable): return True + php_vs = public.get_php_versions() + php_ini = "/www/server/php/{0}/etc/php.ini" + rep = r"disable_functions\s*=\s*.*" + for pv in php_vs: + php_ini_path = php_ini.format(pv) + if not os.path.exists(php_ini_path): continue + php_ini_body = public.readFile(php_ini_path) + tmp = re.search(rep,php_ini_body) + if not tmp: continue + disable_functions = tmp.group() + if disable_functions.find(fun_name) != -1: continue + print(disable_functions) + php_ini_body = php_ini_body.replace(disable_functions,disable_functions+',%s' % fun_name) + php_ini_body.find(fun_name) + public.writeFile(php_ini_path,php_ini_body) + public.phpReload(pv) + public.writeFile(is_set_disable,'True') + return True + except: return False + + +#创建计划任务 +def set_crond(): + try: + echo = public.md5(public.md5('renew_lets_ssl_bt')) + cron_id = public.M('crontab').where('echo=?',(echo,)).getField('id') + + import crontab + args_obj = public.dict_obj() + if not cron_id: + cronPath = public.GetConfigValue('setup_path') + '/cron/' + echo + shell = public.get_python_bin() + ' /www/server/panel/class/panelLets.py renew_lets_ssl' + public.writeFile(cronPath,shell) + args_obj.id = public.M('crontab').add('name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress',("Renew the Let's Encrypt certificate",'day','','0','10',echo,time.strftime('%Y-%m-%d %X',time.localtime()),0,'','localhost','toShell','',shell,'')) + crontab.crontab().set_cron_status(args_obj) + else: + cron_path = public.get_cron_path() + if os.path.exists(cron_path): + cron_s = public.readFile(cron_path) + if cron_s.find(echo) == -1: + public.M('crontab').where('echo=?',(echo,)).setField('status',0) + args_obj.id = cron_id + crontab.crontab().set_cron_status(args_obj) + except: + print(public.get_error_info()) + + +#清理多余的session文件 +def clean_session(): + try: + session_path = r'/dev/shm/session_py' + str(sys.version_info[0]) + if not os.path.exists(session_path): return False + now_time = time.time() + p_time = 86400 + old_state = False + for fname in os.listdir(session_path): + filename = os.path.join(session_path,fname) + if not os.path.exists(filename): continue + modify_time = os.path.getmtime(filename) + if (now_time - modify_time) > p_time: + old_state = True + break + if old_state: public.ExecShell("rm -f " + session_path + '/*') + return True + except:return False + + + +if __name__ == '__main__': + control_init() + + diff --git a/class_v2/letsencrypt_v2.py b/class_v2/letsencrypt_v2.py new file mode 100644 index 00000000..10b304ae --- /dev/null +++ b/class_v2/letsencrypt_v2.py @@ -0,0 +1,252 @@ +# coding: utf-8 +# The MIT License (MIT) +# +# Copyright (c) 2015 Daniel Roesler +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# Copyright Daniel Roesler, under MIT license, see LICENSE at github.com/diafygi/acme-tiny +import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging, requests + +try: + from urllib.request import urlopen, Request # 3 +except ImportError: + from urllib2 import urlopen, Request # 2 + +DEFAULT_CA = "https://acme-v02.api.letsencrypt.org" +# DEFAULT_CA = "https://acme-staging-v02.api.letsencrypt.org" +DEFAULT_DIRECTORY_URL = "https://acme-v02.api.letsencrypt.org/directory" # 正式 +# DEFAULT_DIRECTORY_URL = "https://acme-staging-v02.api.letsencrypt.org/directory " # 测试 + +LOGGER = logging.getLogger(__name__) +LOGGER.addHandler(logging.StreamHandler()) +LOGGER.setLevel(logging.INFO) + + +def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, disable_check=False, directory_url=DEFAULT_DIRECTORY_URL, contact=None): + directory, acct_headers, alg, jwk = None, None, None, None # global variables + + def _b64(b): + return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") + + def _cmd(cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"): + proc = subprocess.Popen(cmd_list, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate(cmd_input) + if proc.returncode != 0: + raise IOError("{0}\n{1}".format(err_msg, err)) + return out + + def _do_request(url, data=None, err_msg="Error", depth=0): + try: + resp = urlopen(Request(url, data=data, headers={"Content-Type": "application/jose+json", "User-Agent": "acme-tiny"})) + resp_data, code, headers = resp.read().decode("utf8"), resp.getcode(), resp.headers + except IOError as e: + resp_data = e.read().decode("utf8") if hasattr(e, "read") else str(e) + code, headers = getattr(e, "code", None), {} + try: + resp_data = json.loads(resp_data) + except ValueError: + pass + if depth < 100 and code == 400 and resp_data['type'] == "urn:ietf:params:acme:error:badNonce": + raise IndexError(resp_data) + if code not in [200, 201, 204]: + # print("{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}".format(err_msg, url, data, code)) + sys.exit(json.dumps(resp_data)) + return resp_data, code, headers + + def _send_signed_request(url, payload, err_msg, depth=0): + payload64 = _b64(json.dumps(payload).encode('utf8')) + new_nonce = _do_request(directory['newNonce'])[2]['Replay-Nonce'] + protected = {"url": url, "alg": alg, "nonce": new_nonce} + protected.update({"jwk": jwk} if acct_headers is None else {"kid": acct_headers['Location']}) + protected64 = _b64(json.dumps(protected).encode('utf8')) + protected_input = "{0}.{1}".format(protected64, payload64).encode('utf8') + out = _cmd(["openssl", "dgst", "-sha256", "-sign", account_key], stdin=subprocess.PIPE, cmd_input=protected_input, err_msg="OpenSSL Error") + data = json.dumps({"protected": protected64, "payload": payload64, "signature": _b64(out)}) + try: + return _do_request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth) + except IndexError: # retry bad nonces (they raise IndexError) + return _send_signed_request(url, payload, err_msg, depth=(depth + 1)) + + def _poll_until_not(url, pending_statuses, err_msg): + while True: + result, _, _ = _do_request(url, err_msg=err_msg) + if result['status'] in pending_statuses: + time.sleep(2) + continue + return result + + log.info("Parsing account key...") + out = _cmd(["openssl", "rsa", "-in", account_key, "-noout", "-text"], err_msg="OpenSSL Error") + pub_pattern = r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)" + pub_hex, pub_exp = re.search(pub_pattern, out.decode('utf8'), re.MULTILINE | re.DOTALL).groups() + pub_exp = "{0:x}".format(int(pub_exp)) + pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp + alg = "RS256" + jwk = { + "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))), + "kty": "RSA", + "n": _b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))), + } + accountkey_json = json.dumps(jwk, sort_keys=True, separators=(',', ':')) + thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) + + # find domains + log.info("Parsing CSR...") + out = _cmd(["openssl", "req", "-in", csr, "-noout", "-text"], err_msg="Error loading {0}".format(csr)) + domains = set([]) + common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8')) + if common_name is not None: + domains.add(common_name.group(1)) + subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL) + if subject_alt_names is not None: + for san in subject_alt_names.group(1).split(", "): + if san.startswith("DNS:"): + domains.add(san[4:]) + log.info("Found domains: {0}".format(", ".join(domains))) + + # get the ACME directory of urls + log.info("Getting directory...") + directory_url = CA + "/directory" if CA != DEFAULT_CA else directory_url # backwards compatibility with deprecated CA kwarg + directory, _, _ = _do_request(directory_url, err_msg="Error getting directory") + log.info("Directory found!") + + # create account, update contact details (if any), and set the global key identifier + log.info("Registering account...") + reg_payload = {"termsOfServiceAgreed": True} + account, code, acct_headers = _send_signed_request(directory['newAccount'], reg_payload, "Error registering") + log.info("Registered!" if code == 201 else "Already registered!") + if contact is not None: + account, _, _ = _send_signed_request(acct_headers['Location'], {"contact": contact}, "Error updating contact details") + log.info("Updated contact details:\n{0}".format("\n".join(account['contact']))) + + # create a new order + log.info("Creating new order...") + order_payload = {"identifiers": [{"type": "dns", "value": d} for d in domains]} + order, _, order_headers = _send_signed_request(directory['newOrder'], order_payload, "Error creating new order") + log.info("Order created!") + + # get the authorizations that need to be completed + for auth_url in order['authorizations']: + authorization, _, _ = _do_request(auth_url, err_msg="Error getting challenges") + domain = authorization['identifier']['value'] + log.info("Verifying {0}...".format(domain)) + + # find the http-01 challenge and write the challenge file + challenge = [c for c in authorization['challenges'] if c['type'] == "http-01"][0] + token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) + keyauthorization = "{0}.{1}".format(token, thumbprint) + wellknown_path = os.path.join(acme_dir, token) + with open(wellknown_path, "w") as wellknown_file: + wellknown_file.write(keyauthorization) + + # check that the file is in place + # try: + # wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format(domain, token) + # assert (disable_check or _do_request(wellknown_url)[0] == keyauthorization) + # except (AssertionError, ValueError) as e: + # os.remove(wellknown_path) + # raise ValueError("Wrote file to {0}, but couldn't download {1}: {2}".format(wellknown_path, wellknown_url, e)) + + # say the challenge is done + _send_signed_request(challenge['url'], {}, "Error submitting challenges: {0}".format(domain)) + authorization = _poll_until_not(auth_url, ["pending"], "Error checking challenge status for {0}".format(domain)) + if authorization['status'] != "valid": + public.WriteFile(os.path.join(path, "check_authorization_status_response"), json.dumps(authorization), mode="w") + print("Challenge did not pass for {0}".format(domain, )) + sys.exit(json.dumps(authorization)) + log.info("{0} verified!".format(domain)) + + # finalize the order with the csr + log.info("Signing certificate...") + csr_der = _cmd(["openssl", "req", "-in", csr, "-outform", "DER"], err_msg="DER Export Error") + _send_signed_request(order['finalize'], {"csr": _b64(csr_der)}, "Error finalizing order") + + # poll the order to monitor when it's done + order = _poll_until_not(order_headers['Location'], ["pending", "processing"], "Error checking order status") + if order['status'] != "valid": + raise ValueError("Order failed: {0}".format(order)) + + # download the certificate + certificate_pem, _, _ = _do_request(order['certificate'], err_msg="Certificate download failed") + log.info("Certificate signed!") + return certificate_pem + + +if __name__ == "__main__": # 文件验证调用脚本 + + os.chdir("/www/server/panel") + if not 'class/' in sys.path: + sys.path.insert(0,'class/') + import public + + data = json.loads(sys.argv[1]) + print (data) + sitedomain = data['siteName'] + path = data['path'] + public.ExecShell("mkdir -p {}".format(path)) + KEY_PREFIX = os.path.join(path, "privkey") + ACCOUNT_KEY = os.path.join(path, "letsencrypt-account.key") + DOMAIN_KEY = os.path.join(path, "privkey.pem") + DOMAIN_DIR = data['sitePath'] + DOMAINS = data['DOMAINS'] + DOMAIN_PEM = KEY_PREFIX + ".pem" + DOMAIN_CSR = KEY_PREFIX + ".csr" + DOMAIN_CRT = KEY_PREFIX + ".crt" + DOMAIN_CHAINED_CRT = os.path.join(path, "fullchain.pem") + if not os.path.isfile(ACCOUNT_KEY): + public.ExecShell('''openssl genrsa 4096 > "{}" '''.format(ACCOUNT_KEY)) + print ("Generate account key...") + if not os.path.isfile(DOMAIN_KEY): + public.ExecShell('''openssl genrsa 2048 > "{}" '''.format(DOMAIN_KEY)) + print ("Generate domain key...") + OPENSSL_CONF = "/etc/ssl/openssl.cnf" + if not os.path.isfile(OPENSSL_CONF): + OPENSSL_CONF = "/etc/pki/tls/openssl.cnf" + if not os.path.isfile(OPENSSL_CONF): + sys.exit(public.GetMsg("ACCEPT_SSL_ERR6")) + + DOMAIN_CSR_shell = '''openssl req -new -sha256 -key "{}" -subj "/" -reqexts SAN -config <(cat {} <(printf "[SAN]\\nsubjectAltName=%s" "{}")) > "{}" '''.format(DOMAIN_KEY, OPENSSL_CONF, DOMAINS, DOMAIN_CSR) + public.WriteFile(os.path.join(path, "DOMAIN_CSR_shell"), DOMAIN_CSR_shell, mode="w") + result = public.ExecShell('''cd {} && chmod +x DOMAIN_CSR_shell && bash DOMAIN_CSR_shell'''.format(path, )) + print ("Generate CSR...{}".format(DOMAIN_CSR)) + if result[1]: + sys.exit(result[1]) + if os.path.isfile(DOMAIN_CRT): + public.ExecShell('''mv "{}" "{}-OLD-$(date +%y%m%d-%H%M%S)" '''.format(DOMAIN_CRT, DOMAIN_CRT)) + + DOMAIN_DIR = os.path.join(DOMAIN_DIR, ".well-known/acme-challenge/") + public.ExecShell('''mkdir -p "{}" '''.format(DOMAIN_DIR)) + LOGGER.setLevel(LOGGER.level) + signed_crt = get_crt(ACCOUNT_KEY, DOMAIN_CSR, DOMAIN_DIR, ) ########## + public.WriteFile(DOMAIN_CRT, signed_crt, mode="w") + signed_pem_path = os.path.join(path, "lets-encrypt-x3-cross-signed.pem") + if not os.path.isfile(signed_pem_path): + req = requests.get(url="https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem", verify=False) + public.WriteFile(signed_pem_path, req.content, mode="w") + public.ExecShell(''' cd {} && cat "{}" lets-encrypt-x3-cross-signed.pem > "{}" '''.format(path, DOMAIN_CRT, DOMAIN_CHAINED_CRT)) + print ("New cert: {} has been generated".format(DOMAIN_CHAINED_CRT)) + # time.sleep(5) + # 重载Web服务配置 + if os.path.exists('/www/server/nginx/sbin/nginx'): + result = public.ExecShell('/etc/init.d/nginx reload') + if result[1].find('nginx.pid') != -1: + public.ExecShell('pkill -9 nginx && sleep 1'); + public.ExecShell('/etc/init.d/nginx start'); + else: + result = public.ExecShell('/etc/init.d/httpd reload') diff --git a/class_v2/log_analysis_v2.py b/class_v2/log_analysis_v2.py new file mode 100644 index 00000000..ceeead1e --- /dev/null +++ b/class_v2/log_analysis_v2.py @@ -0,0 +1,300 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: lkq +# | +# | 日志分析工具 +# +------------------------------------------------------------------- +import os +import time + +import public +from public.validate import Param + + +class log_analysis: + path = '/www/server/panel/script/' + log_analysis_path = '/www/server/panel/script/log_analysis.sh' + + def __init__(self): + if not os.path.exists(self.path + '/log/'): os.makedirs(self.path + '/log/') + if not os.path.exists(self.log_analysis_path): + log_analysis_data = r'''help(){ + echo "Usage: ./action.sh [options] [FILE] [OUTFILE] " + echo "Options:" + echo "xxx.sh san_log [FILE] Get the log list with the keywords xss|sql|mingsense information|php code execution in the successful request [OUTFILE] 11" + echo "xxx.sh san [FILE] Get list of logs with sql keyword in successful request [OUTFILE] 11 " +} + +if [ $# == 0 ] +then + help + exit +fi + +if [ ! -e $2 ] +then + echo -e "$2: log file does not exist" + exit +fi + +if [ ! -d "log" ] +then + mkdir log +fi + +echo "[*] Starting ..." + +if [ $1 == "san_log" ] +then + echo "1">./log/$3 + echo "Start getting xss cross-site scripting attack logs..." + + grep -E ' (200|302|301|500|444|403|304) ' $2 | grep -i -E "(javascript|data:|alert\(|onerror=|%3Cimg%20src=x%20on.+=|%3Cscript|%3Csvg/|%3Ciframe/|%3Cscript%3E).*?HTTP/1.1" >./log/$3xss.log + + echo "Analysis logs have been saved to./log/$3xss.log" + echo "Scan to attack count: "`cat ./log/$3xss.log |wc -l` + echo "20">./log/$3 + + + echo "Start getting sql injection attack logs..." + echo "Analysis logs have been saved to./log/$3sql.log" +grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(from.+?information_schema.+|select.+(from|limit)|union(.*?)select|extractvalue\(|case when|extractvalue\(|updatexml\(|sleep\().*?HTTP/1.1" > ./log/$3sql.log + echo "Scan to attack count: "`cat ./log/$3sql.log |wc -l` + echo "40">./log/$3 + + echo -e "Start getting related logs such as file traversal/code execution/scanner information/configuration files" + grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(\.\.|WEB-INF|/etc|\w\{1,6\}\.jsp |\w\{1,6\}\.php|\w+\.xml |\w+\.log |\w+\.swp |\w*\.git |\w*\.svn |\w+\.json |\w+\.ini |\w+\.inc |\w+\.rar |\w+\.gz |\w+\.tgz|\w+\.bak |/resin-doc).*?HTTP/1.1" >./log/$3san.log + echo "Analysis logs have been saved to./log/$3san.log" + echo "Scan to attack count: "`cat ./log/$3san.log |wc -l` + echo "50">./log/$3 + + + echo -e "Start getting the php code execution scan log" + grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(gopher://|php://|file://|phar://|dict://data://|eval\(|file_get_contents\(|phpinfo\(|require_once\(|copy\(|\_POST\[|file_put_contents\(|system\(|base64_decode\(|passthru\(|\/invokefunction\&|=call_user_func_array).*?HTTP/1.1" >./log/$3php.log + echo "Analysis logs have been saved to./log/$3php.log" + echo "Scan to attack count: "`cat ./log/$3php.log |wc -l` + echo "60">./log/$3 + + + echo -e "The number and value of the most visited ip is being counted" +# cat $2|awk -F" " '{print $1}'|sort|uniq -c|sort -nrk 1 -t' '|head -100 + awk '{print $1}' $2 |sort|uniq -c |sort -nr |head -100 >./log/$3ip.log + echo "80">./log/$3 + + + echo -e "The number and value of the url of the most visited request interface is being counted" + awk '{print $7}' $2 |sort|uniq -c |sort -nr |head -100 >./log/$3url.log + echo "100">./log/$3 + + +elif [ $1 == "san" ] +then + echo "1">./log/$3 + echo "Start getting xss cross-site scripting attack logs..." + grep -E ' (200|302|301|500|444|403|304) ' $2 | grep -i -E "(javascript|data:|alert\(|onerror=|%3Cimg%20src=x%20on.+=|%3Cscript|%3Csvg/|%3Ciframe/|%3Cscript%3E).*?HTTP/1.1" >./log/$3xss.log + echo "Analysis logs have been saved to./log/$3xss.log" + echo "Scan to attack count: "`cat ./log/$3xss.log |wc -l` + echo "20">./log/$3 + + echo "Start getting sql injection attack logs..." + echo "Analysis logs have been saved to./log/$3sql.log" +grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(from.+?information_schema.+|select.+(from|limit)|union(.*?)select|extractvalue\(|case when|extractvalue\(|updatexml\(|sleep\().*?HTTP/1.1" > ./log/$3sql.log + echo "Scan to attack count: "`cat ./log/$3sql.log |wc -l` + echo "40">./log/$3 + + echo -e "Start getting related logs such as file traversal/code execution/scanner information/configuration files" + grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(\.\.|WEB-INF|/etc|\w\{1,6\}\.jsp |\w\{1,6\}\.php|\w+\.xml |\w+\.log |\w+\.swp |\w*\.git |\w*\.svn |\w+\.json |\w+\.ini |\w+\.inc |\w+\.rar |\w+\.gz |\w+\.tgz|\w+\.bak |/resin-doc).*?HTTP/1.1" >./log/$3san.log + + echo "Analysis logs have been saved to./log/$3san.log" + echo "Scan to attack count: "`cat ./log/$3san.log |wc -l` + echo "60">./log/$3 + + echo -e "Start getting the php code execution scan log" + grep -E ' (200|302|301|500|444|403) ' $2 | grep -i -E "(gopher://|php://|file://|phar://|dict://data://|eval\(|file_get_contents\(|phpinfo\(|require_once\(|copy\(|\_POST\[|file_put_contents\(|system\(|base64_decode\(|passthru\(|\/invokefunction\&|=call_user_func_array).*?HTTP/1.1" >./log/$3php.log + echo "Analysis logs have been saved to./log/$3php.log" + echo "Scan to attack count: "`cat ./log/$3php.log |wc -l` + echo "100">./log/$3 + +else + help +fi + +echo "[*] shut down" +''' + public.WriteFile(self.log_analysis_path, log_analysis_data) + + def get_log_format(self, path): + ''' + @获取日志格式 + ''' + f = open(path, 'r') + data = None + for i in f: + data = i.split() + break + f.close() + if not data: return False + if not public.check_ip(data[0]): return False + if len(data) < 6: return False + return True + + def log_analysis(self, get): + ''' + 分析日志 + @param path:需要分析的日志 + @return 返回具体的分析结果 + @ 需要使用异步的方式进行扫描 + ''' + # 校验参数 + try: + get.validate([ + Param('action').String(), + Param('path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + path = get.path + log_path = public.Md5(path) + serverType = public.get_webserver() + if serverType == "nginx": + pass + elif serverType == 'apache': + #path = path.strip("-access_log") + '-access_log' + pass + elif serverType == 'openlitespeed': + # path = path.strip("_ols.access_log") + '_ols.access_log' + # return public.ReturnMsg(False, 'openlitespeed is not supported yet') + return public.fail_v2('openlitespeed is not supported yet') + + # public.print_log("path1:{}".format(path)) + # public.print_log("serverType:{}".format(serverType)) + + if not os.path.exists(path): return public.return_message(-1,0, 'No log file') + if os.path.getsize(path) > 9433107294: return public.return_message(-1,0, 'The log file is too large!') + if os.path.getsize(path) < 10: return public.return_message(-1,0, 'log is empty') + # public.print_log("log_path{}".format(log_path)) + # public.print_log("self.log_analysis_path{}".format(self.log_analysis_path)) + # public.print_log("path{}".format(path)) + if self.get_log_format(path): + public.ExecShell( + "cd %s && bash %s san_log %s %s &" % (self.path, self.log_analysis_path, path, log_path)) + else: + public.ExecShell("cd %s && bash %s san %s %s &" % (self.path, self.log_analysis_path, path, log_path)) + speed = self.path + '/log/' + log_path+".time" + public.WriteFile(speed,str(time.time())+"[]"+time.strftime('%Y-%m-%d %X',time.localtime())+"[]"+"0") + return public.return_message(0,0, 'Start scan successful') + + def speed_log(self, get): + ''' + 扫描进度 + @param path:扫描的日志文件 + @return 返回进度 + ''' + # 校验参数 + try: + get.validate([ + Param('path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + path = get.path.strip() + log_path = public.Md5(path) + speed = self.path + '/log/' + log_path + if os.path.getsize(speed) < 1: return public.return_message(-1,0, 'log is empty') + if not os.path.exists(speed): return public.return_message(-1,0, 'The directory was not scanned') + try: + data = public.ReadFile(speed) + data = int(data) + if data==100: + time_data,start_time,status=public.ReadFile(self.path + '/log/' + log_path+".time").split("[]") + public.WriteFile(speed+".time",str(time.time()-float(time_data)) + "[]" + start_time + "[]" + "1") + return public.return_message(0,0, data) + except: + return public.return_message(0,0, 0) + + def get_log_count(self, path, is_body=False): + count = 0 + if is_body: + if not os.path.exists(path): return '' + data = '' + with open(path, 'r') as f: + for i in f: + count += 1 + data = data.replace('<', '<').replace('>', '>') + i.replace('<', '<').replace('>', '>') + if count >= 300: break + return data + else: + if not os.path.exists(path): return count + with open(path, 'rb') as f: + for i in f: + count += 1 + return count + + def get_result(self, get): + ''' + 扫描结果 + @param path:扫描的日志文件 + @return 返回结果 + ''' + path = get.path.strip() + log_path = public.Md5(path) + speed = self.path + '/log/' + log_path + result = {} + if os.path.exists(speed): + result['is_status'] = True + else: + result['is_status'] = False + if os.path.exists(speed+".time"): + time_data, start_time, status = public.ReadFile(self.path + '/log/' + log_path + ".time").split("[]") + if status == '1' or start_time==1: + result['time']=time_data + result['start_time']=start_time + else: + result['time'] = "0" + result['start_time'] = "2022/2/22 22:22:22" + if 'time' not in result: + result['time'] = "0" + result['start_time'] = "2022/2/22 22:22:22" + result['xss'] = self.get_log_count(speed + 'xss.log') + result['sql'] = self.get_log_count(speed + 'sql.log') + result['san'] = self.get_log_count(speed + 'san.log') + result['php'] = self.get_log_count(speed + 'php.log') + result['ip'] = self.get_log_count(speed + 'ip.log') + result['url'] = self.get_log_count(speed + 'url.log') + return public.return_message(0,0,result) + + def get_detailed(self, get): + + # 校验参数 + try: + get.validate([ + Param('path').String(), + Param('type').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + path = get.path.strip() + log_path = public.Md5(path) + speed = self.path + '/log/' + log_path + type_list = ['xss', 'sql', 'san', 'php', 'ip', 'url'] + if get.type not in type_list: return public.return_message(-1,0, 'Type mismatch') + if not os.path.exists(speed + get.type + '.log'): return public.return_message(-1,0, 'Record does not exist') + return public.return_message(0,0,self.get_log_count(speed + get.type + '.log', is_body=True)) diff --git a/class_v2/logsModelV2/base.py b/class_v2/logsModelV2/base.py new file mode 100644 index 00000000..89ad5b5c --- /dev/null +++ b/class_v2/logsModelV2/base.py @@ -0,0 +1,141 @@ +#coding: utf-8 +import os,sys,time,json +panelPath = os.getenv('BT_PANEL') +if not panelPath: + panelPath = "/www/server/panel" +os.chdir(panelPath) +if not panelPath + "/class/" in sys.path: + sys.path.insert(0, panelPath + "/class/") +import public,re + +class logsBase: + + def __init__(self): + pass + + + def find_line_str(self,_line,search): + """ + @name 查找字符串 + """ + if search: + if _line.lower().find(search.lower()) != -1: + return True + else: + return True + return False + + + def return_line_area(self,logs_list,ip_list): + """ + @name 日志行返回归属地 + """ + if len(logs_list) <= 0: return logs_list + n_data = '\r\n'.join(logs_list) + res = public.get_ips_area(ip_list) + for ip in ip_list: + area = 'Unknown' + if 'status' in res: + area = '**** (Professional version exclusive)' + elif ip in res: + area = res[ip]['info'] + n_data = n_data.replace(ip,'{}({})'.format(ip,area)) + log_list = n_data.split('\r\n') + return log_list + + def GetNumLines(self,path, num, p=1,search = None): + """ + @name 取文件指定尾行数 + @param path 文件路径 + @param num 取尾行数 + @param p 当前页 + @param search 搜索关键字 + @return list + """ + pyVersion = sys.version_info[0] + max_len = 1024 * 128 * 1024 + try: + from html import escape + if not os.path.exists(path): return "" + start_line = (p - 1) * num + count = start_line + num + fp = open(path, 'rb') + + buf = "" + fp.seek(-1, 2) + if fp.read(1) == "\n": fp.seek(-1, 2) + data = [] + total_len = 0 + b = True + n = 0 + + for i in range(count): + while True: + newline_pos = str.rfind(str(buf), "\n") + + pos = fp.tell() + if newline_pos != -1: + if n >= start_line: + line = buf[newline_pos + 1:] + + is_res = True + if search: + is_res = False + if line.find(search) >= 0 or re.search(search,line): + is_res = True + + if is_res: + line_len = len(line) + total_len += line_len + sp_len = total_len - max_len + if sp_len > 0: + line = line[sp_len:] + try: + data.insert(0, escape(line)) + except: + pass + buf = buf[:newline_pos] + n += 1 + break + else: + if pos == 0: + b = False + break + to_read = min(4096, pos) + fp.seek(-to_read, 1) + t_buf = fp.read(to_read) + if pyVersion == 3: + try: + if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8',errors='ignore') + except: + try: + if type(t_buf) == bytes: t_buf = t_buf.decode('gbk',errors='ignore') + except: + t_buf = str(t_buf) + buf = t_buf + buf + fp.seek(-to_read, 1) + if pos - to_read == 0: + buf = "\n" + buf + if total_len >= max_len: break + if not b: break + fp.close() + result = "\n".join(data) + + if not result: raise Exception('null') + except: + result = '' + if len(result) > max_len: + result = result[-max_len:] + + try: + try: + result = json.dumps(result) + return json.loads(result).strip() + except: + if pyVersion == 2: + result = result.decode('utf8', errors='ignore') + else: + result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore") + return result.strip() + except: + return "" \ No newline at end of file diff --git a/class_v2/logsModelV2/ftpModel.py b/class_v2/logsModelV2/ftpModel.py new file mode 100644 index 00000000..1b3a187b --- /dev/null +++ b/class_v2/logsModelV2/ftpModel.py @@ -0,0 +1,944 @@ +QRASP55VO/1DQ98p1csw9A== +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +5G1X0WJyak7IcriKROwRfg== +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +ZuLVraRZUj2RhJbyALJsxNMuhZe1AKXjIZKX1s/bahYi6PnNCfy1Qv0MKdv1oxSXYGJY2PXP7NLNatPW4VSB/zJjWGw5HqJLpeYo/MseDrI= +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +mlbcpvT0kUVOtns2yTGydDt4+07xDvuvRqS/JgkUon3SrcxUlm2Z4rG4Pa9PwOq+ +PsDvd5tup7sspheXmdBjJ9/VPPpQm3Z93wJWBqKcwhPstZtP/mNaMKeE886qnOocqTecg7HmhY5phLEK5HEF6dZHSv0Wlj6b6xPrN6rZ/hs= +piG6BsMF31u4R4iA6R4SRA== +aB1r/Hl41eeDYHjvKNsDN+fwXnHZ5cKnjNQNkvt811Q= +bDrBrYFBEwTqHDQlSf5Wqw== +1u+XjG/2+GSQRv6EzCaWRQ== +3uNPAHzfs+GjC65Wj2Rt7Yr1FmDO/WvtwI9ACTTXaaw2ZZ+bL2BzJX0KRbZ2iXJ+ +Te0vaV2Be4O1NKtbTHTSEPuSBcLm0OszybVF5j5WyuMd3w1YS1N5di1Jdb7cS5FvWbPkfy+FymquFYYQfQohUw== +iOUsBGgOHEqzdAdQgfAp4trfzBztc5RmfOjqgmGickU= +dHeACZYMIKy/ZtmfGaPh9p/FhkuRMVl3Jcf2sj3yqruCd9kzPl26Pj9O4O5lNFVR +9/AEQy6sYem8wkQNCggJi4i9kWw0XQ381/dYG6HKfZo= +WDL4n6jeJGq0nSa2RyFPYQ== +rpy88ff5JnVuJqXSNlYf+CQ4VKHExseR2LosMAdYEeHGNm80vDZH+2U6FEAnuapk +1u+XjG/2+GSQRv6EzCaWRQ== +f3AsbhS4PaN1B4cUkY9T+A== +vlNl0s/hLJ4MLdvF5a1XATBpmRe09mwg70GHdHdh7mTmKAaerhorUT+w+dAajOtb +MMZtIZZuuuWy0CLBzKekTg== +i8F7pUrlHPBNsIux2MgfYA== +iCCluDm5uh0+pvdt+aoUoztPTvOMK08MeHVxMWJklzY= +Fzf1WPeHT+phfW8FPaBhNw== +iwW5Y10vLm5bol1PMh2K5PcgF/7PR7VaxhpNPLVQXiQ= +iiFBxU1BEtsymTSqJJqljqN1RY1RLiUspBdLrvd0CW0= +ldCxpJy7GYU9Z3wc/nhJ/te4XvT+sJUYSl50ZnBKy3w= +CrJuPSINqYUMS/s5z4adcU1LJqJ2Sojj3C2wjmLuRsM= +Psg6WqdUeMsPYvW+jnYn0evEHl+1XoE/rnCfo+c6WdA= +1z8iprMiHgI6LYZftXGjKDYPI7FVqZWCQ09oWcM3Pag= +o1ftBjL5WX5IAh9xWVdqd9wdMBvoYRdNfl+IAvPWFp8= +QUUBWS4HdgaVs9J11lpeBKBH5NkT6WbMMMnumMF/7Aw= +tfRWYnUvHUwnwQYB+ywLpslVnsH3tMXHAPD/cYoTpT4= +AtAHlguqAHnQsUP0meqLH3VU1D/0sLjQMR/6J/fyRbM= +7MgotmZFCo6+Kg3s1/uhRtwRAi7VKRh0cRfONXMb3yI= +CvGG8CijcRlYyvz5dGomfhXk6DzjYPOBtu63/Veri24= +/PTfuW9bPdAz/6cUQf+E55cP43jnvaceBCRBneohYbA= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Tz7Nk5H535VRrRyTrsKtQL8WpBb8+0Czjnl0ON67RWc= +mRiVQSow7SpouYK3AvqUX4InYpFE5p5We9mVkjhzafw5I8C/twUlpi8ApStCkWUBZkKyKJec53qHlBIhRtCHfMjSjGgd605iQCFzcbbvXtI= +OoCG74q49Zorh8P1XQMewTNdwuwJd3tr6im9hHqDZZMRAiA/OeCQww8qlXt4FxqlcaQ0SkBsgfpoB2Sw/8/fMJB9bSAlRc4jBK5o2FKH6qY= +gSinJxQ2Dl02IUzuK5alezKMVZMklWE7tWiUg2329L/n+lRYCQ9UCFbErLWlw54SDdzf2aW7xcFo9f6yWsFr+Gmxv7QW7ILh8TsYOZ9znNp1PFfZW6D80dgQ9cC19CuCsN/nORHFj+ZzOKgvaEAfFiLoGt0ZpN5vpGxumpW0gpcWn6x7Ldft+bCe8OO/4vq4eE9RKaqjTgoUu0FLyAKLMsUrm9spdqinKdfS+0njcmvjNfpj5WOsg4REfjqMdCjT+eYrQYJpSEhzTFpPXaGYrdCg7TnFabrpVoJ3F5uhjQdkKGWqXHHHV/nbuQtIHnCtT8dwjmznuv5sSlpRCVqsgj9kw0VHrZYCN0PnqZ18QBTTb6aQzq2vcHCtyHbfnTgZYpXapiGZtR0S/U7DzfJCjnAyqRZxt4/wKHbAGYy8PQ8OUSXjFZJkxujMHXXUoxC2tVM7rRO1UyMTyERrP/FjuBzgj8YxTjyaxV4GnCT02jD45RbtdAK6txhyTaDZXcGS +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +NRG8WiBknxy8LZ7uM7Rtwjpor9jB6CRSN8s/tRh0bCGCT3O3yIYveV/kC3whqCOj +g+9Bvcv6QScOWqD1KWI83lDWKDEvFH24UumsH9uh9SSEzyoQuAQyJOm+k2nYRQYDLlXqKegz+5H8/A6yDuU4hTssV9YA2l/qH0zdlTP5IOI= +wgR07xfoapmx6eEnFHXXYsrtutdMRxYffYBJpExAFLL3LFn2eCspav2L+PBokDEhe88eat5Ab0fx9+MVZjZtHQ== +xWoGNWjKGPfI4gq8aHoTfOgPIu3QGZOJ5qEujzh3pzi1Iz2/AHJ8GvmS3bUx5254nOirctK/k6BFZA2v8ayK/EDVY6Wbr2d7jYxq8UAugp4= +uRzTWU2MnopGqqEdWeT6ZU1hlM1Dw8+qKY/ieJj+NsUqvH0CTNguPTeX+Laan8WHKtl7c/+1YA4rF+Fa3pfogwkGJNEhZb49H+rX+WZ7S1s= +/9gfAufUZPDDG57T/QYpxdWPD8TwNFW420xTjTTQBkORDI+ZeKEPsfM5K898UU8g5dR87tixaipTGCcauRrVQA== +QKClQ4XOaAjfVBfstd1swg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +09UuO0tw3kJ5J618hVggcQ== +1G/K7RrekfrCzSxrR6/wTYNSQbc2uz66/PV17HDK7lM= +eLcpLeV/+9uVlQTwkhqIj38TutNj0RQi67eU2eN84NTyZDpBLoXYuINl8COZ7WDtjl8ei1UK7BD0ndqK+UnR8g== +blJaGg6qKrjd1Qn4XklpZzOOb0jMgt63VR2D5mQpOlZ27FWAgwjPVohLfSnV8Fu7 +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +SWfRwL0ogDREfMfCsQquWGscpySWDKBJMwbrv47jhc4UOdewFKdXnycq45J0VXW5 +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +09UuO0tw3kJ5J618hVggcQ== +6d8NLnHX3WuS3g79bJvyhHaf1Y2F1xBjvynu5mqktdM= +cfPwrhJYFivHRkEDhYTVNtiFIOmMRDgvlDsiWwA+DRgsj3+ceY+2hTcyXuBIIfOfx3eHGtbcHMXpTWbDr2J+FA== +ET0y6SUMPE2Ml4dS8viRTJixJpYvSUNaJEQ3wgnwmDR/Dl0Tv2W4MPgVxUJMaUl1 +/VraJuu2EmN1fJjV8ELbStoy+NvUd5C4U3l5SFxwBYqHlGghTAj+KvzQIxH+57GjdeVm46KwnUg95Vg08AS+OA== +n+vWyxFDQEoPBlVkcoK8wY9pKSF5GTVfw5dP9KWL9Mc= +CN5G72nnBtsOlOwm3ZnpgqNZTuTEeStLQSFZw0wzb8LzJ/PEqjYVJPHaVhayM0CWWr8fPDm29tfR2xcYUoXx1xDftTYxgmI84hfdErz0bYQ= +jceVTICIbdeQFZqTJKGutcwURrK8EHYrvAd7eDtpxsLSaZ1ywpAsxVBXsDtUMDU+I79b8EIcEmtjZIH+yBRJYWqbLUSaX4DmhfUdUw8L2eI= +1u+XjG/2+GSQRv6EzCaWRQ== +J24jXQQti6wQv5pCoF2ZrQbQF1j1bF4/QmmX1CwZUVEjmug7a9JAroEXBBTfhYssEYHy8XwASHc+rw3shghfWA== +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIlhC5ovOIKOhQYI+YFVzTxDyBspL/Zz/EA3ekwqZLC22t +eeFiPlXUI0Fos82HYztSmTsAYhvcpYqGtZuapEgzpCBypVgTjJ6W0KJC4ipcw897 +WuRvIBaPNRsVGEMoM4NSTioH6DPOvYqQxMaOqX/7OKFY0pBl49Sar+3IeBmADhmj +Z8UsPk1Q7HtwjRd4g01ryw== +N5xldEoitd9eWUThCvdOTB6LOXhNE9TOsjnkpc/9SMN2L8WJXh22t578ODk/HhdL +oxiSpIWwD4wuBub3KU1/cF5fchu1Vqpk+nU9bn11yzo= +PRG/YRzXVD8iVR3bzEcUnjHro+U+4TucaVNI9ygPWuVU3EqLuxmqZkTk2CyNK7O/ +VmVrGQo2zRokW/ZuO9bN68uCgzKuG5LF3dEm8osmV0HPcWbbdr6DBOymTEvfp0P/ftdl4LLYfLwuPpwsYazhEA== +VmVrGQo2zRokW/ZuO9bN68sblSJDKz0OqxMt0bHs/NUCob2DYRF35HVY0+LpxHBb +iDFAoKi0IJZ7t2pJuBivrw== +l2FJPs4YkAmmok1ulDRuSA== +PRG/YRzXVD8iVR3bzEcUnt6EfhtcL4GrcBoio2oUko9nPAoPP0N4kWpuhhh+oYdV +9zgnmC3+5N9XK5/NRaR4SMVNup0N5fm76yGD/GNlNnU= +sDlCNtGYD6nPzg4cZ+6YAIVaGRKK1htDJsqNdoebWm8= +1V2v8QerKOmubvSxgB4eTCXRIGj4wwBQxqmi+7HBYgg= +PRG/YRzXVD8iVR3bzEcUntHGmZ5qTlWv7ywh0UaUxJzXifYQX4pUcW7z5PPhLeh5 +sDlCNtGYD6nPzg4cZ+6YAOK/4Cv++bqYgS9TyEF32cuA5AzQdBq8lyKWQabFTagT +1D18KWr2hdVsBcdZx1OaPWkr8ytK7qVWaaza8e1vU6k= +VmVrGQo2zRokW/ZuO9bN66Vh5+Ci920JkrzQsTHn083JJPLf45hGeTPWU0PgjupO+yaDkxvgzGWeJhaeVem9Wq+8oeyKDNsYcPKXT94yeZ0UymKFveKN91eF38ApXJp2 +VmVrGQo2zRokW/ZuO9bN625bd4BNT0j+VcP1nTIY6XaDTRy5+5AWHUZPakkiglNT +VmVrGQo2zRokW/ZuO9bN69TcL00if5hvJ8+IR0VhUuvoRwQz2rzPWjBJm1Mk3z5a +VmVrGQo2zRokW/ZuO9bN62AVcq3plEcj4rxrsvQ9pPDU08M8azrqNxOOot3ghWHctbl7KH0YZqLimlQWQDerlojzSftx5vWNNRCgaO4RyF8= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkniyOkKSi5II/qGABbjSbB5nq48GQoMvYorQ05/VR43Qg== +VmVrGQo2zRokW/ZuO9bN6/wMRobeZASkZ7UgaRv/zbQEquXghr8pcxoQVAx9J5G/e9b3CtH5KoqK0sb3R99OsA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66Vh5+Ci920JkrzQsTHn083JJPLf45hGeTPWU0PgjupOJvco2FZSOBWgiEqCcFM7Q/vW4PqCZLGYaAAy6qf2tko= +VmVrGQo2zRokW/ZuO9bN625bd4BNT0j+VcP1nTIY6XaDTRy5+5AWHUZPakkiglNT +VmVrGQo2zRokW/ZuO9bN69TcL00if5hvJ8+IR0VhUuvoRwQz2rzPWjBJm1Mk3z5a +VmVrGQo2zRokW/ZuO9bN62AVcq3plEcj4rxrsvQ9pPDU08M8azrqNxOOot3ghWHctbl7KH0YZqLimlQWQDerlpd+8N/8ymIPDrStsyiyEo4= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkniyOkKSi5II/qGABbjSbB5nq48GQoMvYorQ05/VR43Qg== +VmVrGQo2zRokW/ZuO9bN6/wMRobeZASkZ7UgaRv/zbQEquXghr8pcxoQVAx9J5G/e9b3CtH5KoqK0sb3R99OsA== +ojv9MyHa70Aio7SDrWcjwSYYqgfeHLgPvpjtVJfUmX0s6OSNU+WrIf9mNVPQ5TfqPavTidvffxVkcshQNOCsRA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklNjZdFug/ngOpMcvCKxEBrAN68NaDKAtp2GLglVerAbQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkllTuznFui81R4CASaEKJI9 +5wsVwKLrIjIWqeTNpSVp9YtfZPXXv5krNmxeXAjq+NQ= +1u+XjG/2+GSQRv6EzCaWRQ== +JKgfL+COM6qpIwe0Wm0zY0qrBwJPhJivgZdbq2ek/MNyEskG/o3RxQuD1murI70P +Z8UsPk1Q7HtwjRd4g01ryw== +9JiWXiRNWDTg+XstQ0v3tAQjzhKVhg9yH4FpL7cmNHMby1dZvf+KLKXDFxiL/GyHwt+mo7J9Eh26MMHXk7dZ7A== +vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg= +ho/Q+jrWDtBeg9J9ZTnKizg5CYSqIH+8noUWvD9u7YuJKKfD4Zw1m+s2/8nfOg5c +Z8UsPk1Q7HtwjRd4g01ryw== +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +jEPV0YXDCqWCap/VHplvwQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN63+n53I33eehKLN5KvUF/njST1L3SsbP+AH0sXCoBqB8 +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +QKClQ4XOaAjfVBfstd1swg== +c0jePRxtTVZYop75Q5JCFonnV+X8W6zX0oXQ+sQuZtHIAMgLt2jbmRWY4GrPgmiW +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5hzukpfx2w2sPIWvGOQMWA5Yh7rnfJ/ffdahL7iA8laA/3JDQRVCFmm1vTiYAMYeY9 +mDRkPFp/j8KULnXRVcN0LwEwcSJVnbuC2hmp56pZJfTUXblgm3kKERqiCPQ5WxXb +Mmz9tB5RaVIWQerkiDs2BePvXl4dNWSjl+vJFVGrvpac5kzICL/kBoClsURQxOPYTWXamhuvoo8Pwhw/Asx8vw== +wgR07xfoapmx6eEnFHXXYtX+pH+0NWr1LYbDUaw1poloo/SQreGe/i/geKkMYIe0 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h66+MS1fyMgg4QbFqgaS5zM/xrhgXnbF7R0CfQQ1+NiPMSpG+gKOD6M2tp/y5za8ffjQSppN05X0LDqFQT9a5wOIgQJ/Yjmvbxvy5ZgSQkXThA61IOiX3O5DDhxAVwkrvF8psbn0BsLpymYmwMFV2qSiF5R3vp1Sxyl3hZW76qa52u/AlZqi2oGeEktYAA7i8pAzq77pBfMPsuV9ZAavvwmLmdr5nMrH4MCrkdZr1OBQqEOTFZy4Rtp8jRZoc9O3Ee1hevDInlv+qb3Eovojc9EXGFueUnVktB49vv6OTSgCbfCkr3GbC5cEOllSeNviaxJK8u8hN2MIBF/7ShdNspSVs88cblgg0AlY7k/1tkPo= +R14ZYm6mELJXNFBxHSBttdYfQPT7lYz3sNsgm+2cAVEVZqqsWoLADHy1tzgaDLwVuLUQU00i5jhUkarevZBgbw== +ojcUvW+Z2ehEJ6yMJpmY+2Z20VIYbVHcvJGptU0TEL4= +SSb4svay1O2En913Q8HWTI5XiKI6KpCz9PP4OKkiVgIZ9PAiv190X/Q6cvCmIvEn +O3CUgrw2GJfB+mDjH5+NdgM4cG182DjbaaqgGRQTDbbHFEBxIQHWpD3HPFUKruz8 +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTGW+VwmHBYIXijdBPs10gzVT12YFp5JZnl163bbcgFbA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTGW+VwmHBYIXijdBPs10gzN7zADUA2hNwQQDVNbVbGVg== +13vYlKuO6A3n1KjBvCLS2cZOnSAYizlRW6NVt55Nvpg= +Rc/EJuEm/I9INM8fD5RDm8V6NM+R609UAWBHMDxIGH1KB0feE4DfP+R5GNLxg7AM +xWoGNWjKGPfI4gq8aHoTfBMUl5q05yLZWa9BqWZ+hHXNzfrvrNJv7lUhLv58UG50furKaKDp9/7/vwDMn7xgPA== +32pdC9DD05OE2l0oXazDFBLSw6QGkeD2EftJ95N/ZFU= +9Ky7E5Jxg7bFh45xr+PtLAQzRENCwucwjn+oXpXkeQE= +Rc/EJuEm/I9INM8fD5RDm8V6NM+R609UAWBHMDxIGH3FuQzm6WS6LLN0bq9R/qzA +O3CUgrw2GJfB+mDjH5+NdgM4cG182DjbaaqgGRQTDbbHFEBxIQHWpD3HPFUKruz8 +VmVrGQo2zRokW/ZuO9bN6zRl0iz3kGEFiV+wGJc1Rx+/HiOt/vX0mu63k3Gx19QQ +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmq0nitHFfMxYtPtCzV22AE +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUj6vOCoGShzUiscvkZRGEAKA7uR2yfJ7pXV25tjsB1b0iTAkZkAH5X+kEWvVNPVuCdYOnp7yO4pDLE8Ou2gjvWF +32pdC9DD05OE2l0oXazDFFXAy1MI/uPhakkzCdBdnMA= +4+wKB+Tel1iow8au3cOIJaTLjj3pysITCcJJ7ec43Sg8lqI/J7o8CtRAAtYI0AOu3JqoT8kI2t89ug8sZ1eCiQ== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz7ZP5iIPcKimqUgUTHrSwkPOkVRNyKSBWUmtjLo6QNHEw== +1u+XjG/2+GSQRv6EzCaWRQ== +f+rqXRH5RpetwpbV8cB3LCQvH1cZGFXTjIrpGpfmiKB6I3CNNQHjXn+gKCasbU3WpehGERRGCkPUVijQbiz2hw== +42x4h97guh1rdm+jSDLx6WG71j+mb/kcr/EympinqGaL52MoXiK9EGabcAvWXPj/ +baycSy0dQ1Tu5e0z9Bp/lBxrTYjvAm+ccP8cFcEA8kK8O9TSDmsclixdCnVohLz9 +i9KLGXWhztWqpUW2TKI0t6Wh1fnUn6aXVqQKMbQ4Wwq24SjFcYWkCZoDKbV6MU6m +02RO8UnD41NXqUboGzyMNHX0JyR3Zt1dder6kkQ/0fvZhRxdM9NaaFhg8R9lJRcz +Q6ELX42Y0YS1qAvk/d/Oxw+XB1Oh4+oUsugYkWWAiYJVAc/gs1Z7+2AYi+OjKljk +UrD3vTXhRT2EnV+D/UAcyj4KlLaoTR0ZzVfFP7zcDh8qBFLqQVHnaVRpUcy3VNxf +fqNjaa/44JvPoTCcxz6RwKlIK34sd6QvNpIK085yp6fonLWQWHduUTCWz3l21/d0 +lNSpMXSSdsztjsXj5hxacANS7WSJ9fZBItR9FLQQCeRQ4sSSTGWKMGLiiXMpS2R/S9MpuAbK0NcchyUYHrjK0g== +1u+XjG/2+GSQRv6EzCaWRQ== +f+rqXRH5RpetwpbV8cB3LNb4H/TF90wkaKs48d1M7L+avSymddn7mKYz0NItjsSS +Z8UsPk1Q7HtwjRd4g01ryw== +6Jpks4lpI/jb7zQY9ZwZK1zkFQHFLO1SftfqDQPgq1EBsFoAqyniZa+4+xJKzZfT +Z8UsPk1Q7HtwjRd4g01ryw== +IOQ7R1QbtHtN/omAE4BWfAjZgPJASxuTDruVD3whaNc= +b4OJVZe8QyIpjuTpKXDL9A== +wlLHv6kT3Q/RmtMBN4nDAQ8YSs8Zt0gDBRyWHhM8ed+GGdWzPmL+jZ6S+F5YV+22 +VmVrGQo2zRokW/ZuO9bN6xjRZ/jgghl2/5XRKMIuEuFfgv1283QcodM/1ZOCBV+E +VmVrGQo2zRokW/ZuO9bN6x+Y6lBOlB/5zr7yqielcHbAt1j/vhhHLiR0XLFVJ4MYIB7qEVFJCIX6M64zazFiQeEMWR6HA8xoXyo6nAZJhlk= +VmVrGQo2zRokW/ZuO9bN6zKW75ke8QGqFd99GlEdhOmQAXdMhXbfkO0SVCSKXZhAgrRq5E4FlbZVLyjCi8XIzw== +VmVrGQo2zRokW/ZuO9bN6/VA0tcNP0B0yWPLZItZlkbpytNLRqAe/ddM0lgyhD/+AVdhq9Y31ZAG+aChEtUpGjK+qk8XXC1AHcYIovJNZ+PRowAmFi0HJrFUfIQN6E7Q +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpXsF9xMLoel3FaVnpw+fNuSwrGC7RSUXTIrW2j2yH61A= +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +ltIZHHUgft++nMUXBsBki41iWrVRqmYYsBZXvu1RVWtX5QNGu3kC2spvuS5BMSeI +VmVrGQo2zRokW/ZuO9bN61gUJX+sLdD5E9ziOqrlc35UesKXTRF076vwlWXX2xbP40+TL4fP4+HMWJmoIKgJCIlxfTkDq2jRuIwjBU2zhng= +L0eUthVnpkGsmKFAX6d+uK/AWodUm3mcxHH2qymBAgItW7Ziboyd2OX6uMRxQUgs +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +ltIZHHUgft++nMUXBsBki41iWrVRqmYYsBZXvu1RVWtX5QNGu3kC2spvuS5BMSeI +VmVrGQo2zRokW/ZuO9bN61gUJX+sLdD5E9ziOqrlc35UesKXTRF076vwlWXX2xbP40+TL4fP4+HMWJmoIKgJCIlxfTkDq2jRuIwjBU2zhng= +L0eUthVnpkGsmKFAX6d+uK/AWodUm3mcxHH2qymBAgItW7Ziboyd2OX6uMRxQUgs +1u+XjG/2+GSQRv6EzCaWRQ== +69Uik37T733MFTHEnugne9sIK0q/BrAEinbmIsH25IyzJWj8GWaHG6Tu8rx0i8YI +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIluXzbJqLPwB2hMZiB6idlsw= +vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg= +ho/Q+jrWDtBeg9J9ZTnKi7Z8oMuCLRtnjC35Cw+4aVDyKydByU+COt6lumOoVZyj +1xJ0clp+39cOWW64WGs9Tw== +Z8UsPk1Q7HtwjRd4g01ryw== +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +jEPV0YXDCqWCap/VHplvwQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6wYljL95v5S+KLH0XvYeRAu7jaBdeUshyw1Me0QcftpN +VmVrGQo2zRokW/ZuO9bN6+eQW79nEpaaEVxkQ5mhMLDYFz3gFM695f1GA6K12DxM +VmVrGQo2zRokW/ZuO9bN67F/Q5IDafbWxan46AKyqXhJkHTWPWMJlgqQQU22Q4EH +VmVrGQo2zRokW/ZuO9bN6/0cM2nc/QcV1pNvNaFvxCS9guiudKGmsn/LBMq+3sAL +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +avTFp0LCGrKbHzgXWCq/pYahaBNfx164yVsSAVTJDN4lWK0zoj6Eh3CajVgk0hPL +c0jePRxtTVZYop75Q5JCFsMjGu62TZDSljeQjMtCShNsz0Y9SFIv08dPVdTjI95b +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5hzukpfx2w2sPIWvGOQMWA5Yh7rnfJ/ffdahL7iA8laA/3JDQRVCFmm1vTiYAMYeY9 +TzinVBSdupkFZuXDgmwYfiSs5GTHKq13dTr2kdVzFPy0Jh42rAak6FUbANl5whZO +W7OoNBP/IWbYzUOvpg/slY2NVhp515LKHRWJIoHB+V62OipmiB60YwRsLUoQ2Yhe +ojv9MyHa70Aio7SDrWcjwQ0VSHrTkMcwddE36Wz7lCz16K6ytw4GpaCto2OYzTO/ +IfOrW6dR51K0HWsQz64KwMZLSub5xEzT/cQoJpZiUus= +MzOj4ZiBOJDNVP8vi/lFQIcd3YW2BaDb4kSPJzNVOdlx1Fa3HoeBVOYpiRWE7XX1 +PRG/YRzXVD8iVR3bzEcUno4Z8XHjrzDgKJNn9oJou7lkrRw3h7on47id8HDtKTRy +j7S8zldDFIkYlZuEh7uh1S+jR/V5xbpfKOdCgELD/V8= +yqOVJCkAGOhqcQYBOo7yqReCj9ixiaIB5tdlZ21DAUB9HlVyjl18y9Mfwf2BKrxO1mdZmBSOas0Z9YsNQ75XTg== +jg/rCd1ltkosbKN5ZG2fjyfZDh9bj6LoXpZWnO+Jm3I= +kTToPYSGc065HLDqbjV0HFp+qk2oCg3rcMPzGznvj4s= +UsNFlHPXrsQOqjn4RzHaA2d+Dsggsz8fegt1C0el50M= +m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw== +k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wAfHx0c0egKY2YtT74Sqa68k3g5TvkiAzu1NRNlkcoBZw== +rtgmVFvoY2d3wTFk50ruC6zmYmoALMFLQOc+xc+vXmJzN60uRcu12WAXC28NrZ2x +wlLHv6kT3Q/RmtMBN4nDAesM/ZGOfx7RLjx/t+u4GQE= +VmVrGQo2zRokW/ZuO9bN692Dxe6/pJb49NcEGwdYFHp1ykWA3uXz8Dyrp2BHgNzY +VmVrGQo2zRokW/ZuO9bN65W0SAM+Lz+ixIA1yfBIlyIfbjLiw8kUEWgRJErCtT/B +VmVrGQo2zRokW/ZuO9bN63sbeay11c+Azj5VEIm5hpFeadOrAd8WGDCLEKR35dm2edAnPxHas9j394knOwJ8NA== +VmVrGQo2zRokW/ZuO9bN68NGdAuCULKcdnQ9QIPhYF2d+4WoU/gSRVS6rllapIH/JcXSlRMgq/Lahda1FjioQA== +VmVrGQo2zRokW/ZuO9bN6/D3pD6pMXGNmGG8sGEgUx6XUzf02GbZTCaXui9nRWWO8tkj2O9evUoVTjlZbcp3bw== +VmVrGQo2zRokW/ZuO9bN682I1WfoM5O31Eu4y7eErmS1UbchPX+RbJH8okRw6uaf +VmVrGQo2zRokW/ZuO9bN6wGhkWn/nbtYgnRrfABp3Wx1AshDUYpYVsgVW1JY2KGZFsqdt6fYlLewntOhfc3+yQ== +VmVrGQo2zRokW/ZuO9bN61QRx6pmjScEC6Mg8X6jMiYZxM1o8NnnHqaeAW/oXr0H2CGH5XEtUznATSrKZ2lZEQ== +VmVrGQo2zRokW/ZuO9bN69ytvi1kzpknGdq50rt6Uk+ubFehg+T5L/Wn9jlHITZARQHXl2+Us7ChTWXRkBLidjmBIhghC83F2q0QovPWPAU= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64BzcfYorygF567wBnzhYgdXJpuZabV1BmxgnAUx5+Do +VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKB/zu4YnIm31wuOcKLBoPXS +VmVrGQo2zRokW/ZuO9bN63uBz94NTvSB4ZlhYbDfvddgnVY5f5JKOORH7Tw2EYEWgIaiQrzxYa4sTWMTgESGdzp52DXDe+CM2icfFsZP8BQ= +VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw= +VmVrGQo2zRokW/ZuO9bN61tUoNETw2Gi1D/Pq4qmNf5zhx/EQVRufI8LJkPSSi0Ih/xPpxJTJaW6aikR8po+Gw== +VmVrGQo2zRokW/ZuO9bN60TADvNmgaySXS1m1WJNuPeBE1HygRWbriOlU7U2Ssu3klRVzuCKqRzIyX8uoMGm2Q== +VmVrGQo2zRokW/ZuO9bN6+NxpNK9XYUauw2abZQW3LIA6UmYzalir4YjKFLEIVPk8tEmxjCF+UBrUqEASEZ7LQ== +VmVrGQo2zRokW/ZuO9bN69V5vRMZphCh9ZgEve1d0/wJCCaWNPSysYTVRsjVkHV1kaWCUu2teK5jcDOFuBzPQg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN65ql16YTxrqF/JGVZ1MkynHsDzpubnWpMd5UwEoUiIS0 +VmVrGQo2zRokW/ZuO9bN69qq0Yi+mSeaf9labGycI4pPobf7Rk2EV5UV2RidC6jX +VmVrGQo2zRokW/ZuO9bN63BfTcQ4tGBocvZdkD6/kO+b2BWi71GtoR82tq7ZzboaxwEKuBjLI8hR7w2Vz8MwvmfTeIGeWGGODE9Xm7plcChd9CESFtAhCbsVbUZfRATe +VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKD5/dQ70v3ovug8BVS13b+a04fDuz4GqTVSudpfL0PZ3Q== +VmVrGQo2zRokW/ZuO9bN6/JsORMmbxegVkakcQYSuOtPUInnHZyjGgz46mtOv7qSxkpfnDglI1Zm24CHtbp9uw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknACUIF0SHq3Wk7UPJPdvLnBytT0bs3KMa3b9ztxfxoAOuFAPEC6llaTPvEl5nkxFo= +VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw= +VmVrGQo2zRokW/ZuO9bN61tUoNETw2Gi1D/Pq4qmNf5zhx/EQVRufI8LJkPSSi0Ih/xPpxJTJaW6aikR8po+Gw== +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6/zCWmOuloAo6ldSTiaAbum79T3ZyCN2uTGYeWaRk8ZDw== +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6996q3aNX+Q/j+AZP8Qvo1SsqECC6pGPNuHt7GBg6OuU7Z1kO5CyxL0yZW1h1ERJGY= +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH69ya1Zc1M+KUYAJsz6AJ8F9YFPnDyZrQbgW0mxsmgLccg== +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH69tjxVx99Q6mXAvsEky2j6E +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH6+u5zuOiB74Y40ABQeHY7iFHo1pdDHleL7Y/5SOV2+67LgwV08qwlhzwYIvKN6ew2QPQs3KxxYBuYIuf6mUFE4LQcgTOHCghD9G2I43o+9JDw== +VmVrGQo2zRokW/ZuO9bN651w9MrkrO5sEgMcUVTfH693/pJPvxX08JK6/fU1NpPhvWQO/SYGDQsMBTlZKO7NUA== +VmVrGQo2zRokW/ZuO9bN65UApnVIJ5/vPXwprQ1J61CB61rainG+jx/I7BmKhaXNbQAK6argkLKn8aYOSqMyZg== +VmVrGQo2zRokW/ZuO9bN6+NxpNK9XYUauw2abZQW3LIA6UmYzalir4YjKFLEIVPk8tEmxjCF+UBrUqEASEZ7LQ== +VmVrGQo2zRokW/ZuO9bN64RgViN5TsYlk5QzRuGR7Q/OP4DOltXpxjAehZXCVn5t +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +VmVrGQo2zRokW/ZuO9bN6/PX3CxbCMUWnFIJe+bNqSPAMyzrThLalxkC2Swhbmfz +VmVrGQo2zRokW/ZuO9bN6xfwiyo+r5sBtLLY/yC2JQuHAqyNIUWdS8mL+/TK0tDkDYK6sWUkPNuSjWFfywjVYg== +VmVrGQo2zRokW/ZuO9bN6+lw59pqaZzeIqhj6WMFLKB/zu4YnIm31wuOcKLBoPXS +VmVrGQo2zRokW/ZuO9bN63uBz94NTvSB4ZlhYbDfvddgnVY5f5JKOORH7Tw2EYEWfecK1ZYcY5aRkTVMuXTf4H0ornquPXZt6sNbcV9pvdE= +VmVrGQo2zRokW/ZuO9bN69fMKIOt3Fz+VdjDCrbU0LCl+bFI5Faw9xdIi1QHkSiy +VmVrGQo2zRokW/ZuO9bN6yiRU09M/IrGNAMi1AT8LbUYEEDidCw2lDVT2RUEf/k9qsU1Wf5Nkpaqk5UUK1L+GYF2qfhogkOZK7QKKEBb1pw= +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLwPXg3jnnr3ZgO5MqTOchYl +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLymwotzxdwFuDwNOf7s1SnK +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLxqHw0Wm+3Oz89atXNvpO+3VpP8E3wH3+WyCZ1pBSAjAXfw0dWdNxBgtOHo9aHXAAp1ozGIrF4gPkJtrclfqSQW +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLyUNjqRPrStY0n7cNwmxO8c2/xYLBixqHfcg+3JdMiYfw== +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLx3KKJ5e9ptEgMTxJyuDzDUvQprgIXqnEOJyY8Wjzx/7g== +VmVrGQo2zRokW/ZuO9bN69q3A6NsFro7W8cTziR/HLzg8EOHacHSOV2zn2Ehx3MZFJJGKychYjDMMiIIfaMNUg== +VmVrGQo2zRokW/ZuO9bN6zSaI2hF+nruhyzPFkleUq8kEqJLrAmChIOBKZkxcqwkGxR20IThZlGA1Dw2fFbmvg== +VmVrGQo2zRokW/ZuO9bN61yX1Dmgg5/3/nXVEduWvGRjAxUjUGE7S9oN2liQJclq +1u+XjG/2+GSQRv6EzCaWRQ== +M8WJLh8/G3HKYOcF0o/j+ZydjE/y4s9kCBsZH9Ydiys= +wlLHv6kT3Q/RmtMBN4nDAR8Bo0ERF5FTL0peq/6tsQxyyboaug1HqBM4U72iapnH +VmVrGQo2zRokW/ZuO9bN68F9oo0dUBAgf3zfH+UjZ2vBnQt9FOIIe/Mpc+VcY0hPyb7Y4hQhLdZIwPaG+7+qSw== +VmVrGQo2zRokW/ZuO9bN6wO+YLo8joUjMSrMMYsU2gc= +VmVrGQo2zRokW/ZuO9bN6/pQKJ5vd2iaXyIKOVeQFX4bda0D69woaHuXDdZ+7XSF +VmVrGQo2zRokW/ZuO9bN66+oTs/ylSo2IwYwivOcQ5QBeGO3/qhUXmuWRmyY22oicnIskjhngFIDfDK5fq2/pg== +VmVrGQo2zRokW/ZuO9bN6wPyViMJrLgq6QE06AJQ/mAxMxhT6z5+FEPokBQMNnem +VmVrGQo2zRokW/ZuO9bN61Ayy1rssK205MlM60bu6RKTG0Yfp2Lha8/dhaOOY7W18jbXMCbohsZ+i3OCAo0UDg== +VmVrGQo2zRokW/ZuO9bN63oDITMAygWGYYDyry55YDO/Rs3iFoOYCQPRB0Vo6QHG +VmVrGQo2zRokW/ZuO9bN65wPRQBNUUIF+K895b5fOrPxf0bXsfEkNdHCrolD4R0g +VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE= +VmVrGQo2zRokW/ZuO9bN63D/Hc2z6pQlInT4BP7h+Iw= +VmVrGQo2zRokW/ZuO9bN6yUmZOZSHWWMW4cj+zuxv+kqaCT5BWcHH/D3f8p49LGM +1u+XjG/2+GSQRv6EzCaWRQ== +NYuttixIfaRvF0QtYZhNf933obSmTd0Sfv1RLv5CWew= +lcmg3fAsktA/DTr8lB5mRWYUQaXOV6Q27CwrOP6ime0= +/jVx2Sfk8Nv4tQDKNVrh5YQDDOyw39n94XpeN+Fc18Kb/ocx6XA91E4DLjnGHUVOp/hG38KiBHnsJF7rC+FrGh3/Y7Yjh+e73kBNop061ts4ALDF136iqb3NjiK6eB/P +wlLHv6kT3Q/RmtMBN4nDARhCtgHyXaIoGDNPdzxanK1C9qqxbuBKdK1MCBW9e3yd +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN65v9BdTfHDX5h5JoIA/XwnnexMAistxqcEkO+FfI33OD8/CZEhqElXeJdGaAFQ+8dlBsrteF3JVNER2SAEya4ro= +VmVrGQo2zRokW/ZuO9bN60wy8tZH9NZmPUZU3ymx7Rp230YC0uhO/IEmXkv95OC+kYR59ne0kzYbTAnbjiDQU/tfbdzA12veXVBzv74WMwI= +VmVrGQo2zRokW/ZuO9bN6w1QwjDxqY23oBLfy+yRgv/pNEUynCImsZYJyFdRKiY3f9n/tdlMbWaxhorqdeT5IvbcJCzD8QfNSTYnlePbg2dOcteS5jjxFSQ3VLx5bqO0 +VmVrGQo2zRokW/ZuO9bN6xDmLuIA9/MQHFSh5NeXQIQ+yxAa1KwUsFahRjuiZ8Mht4CYtcVKfJe+264KUvItRwPdoTCmQExpQVyY7LXmw64= +VmVrGQo2zRokW/ZuO9bN641hlBSX1Vyx3lcgSR68G0pIVXm8+RXV4rlYHPfD1CE2SXElOHuT06eUM55UWGQlyfgvRR7Tf1LTGZlpUWmDA0U= +VmVrGQo2zRokW/ZuO9bN641hlBSX1Vyx3lcgSR68G0q82MvnkjWxlMLct/yHsboi +VmVrGQo2zRokW/ZuO9bN6yC3ZNj/jGmx0c5FxYA8mMYFovl3/pDr9xKbkD9+awkq +VmVrGQo2zRokW/ZuO9bN6y282QljYt9u/aACctfJxlfOZSOWj+ka4WVOhE7+gYPHIAVL1wqdRYXpc242KdGWJ/V8BbFe5Mk6POcVdzCcc/E= +VmVrGQo2zRokW/ZuO9bN6zj/WJ/j3Gpxi/RO5HrCvbUT/1fI7DpxplEbSq3o7wy5 +VmVrGQo2zRokW/ZuO9bN6yC3ZNj/jGmx0c5FxYA8mMYFovl3/pDr9xKbkD9+awkq +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc= +l2FJPs4YkAmmok1ulDRuSA== +NhnIR3Ilo4H2su9/cTNo/OxBZFmIrDzrnRKQ7FDvUwU= +1u+XjG/2+GSQRv6EzCaWRQ== +YabJCT93a3/IohXaIkr3oSIBZqGhFAEcGH7Ca/SaLLaG+fOp90QxSi85tpj3adUdyNOydRy2vjmM402XZpsX3+nkFnHf2XtWx5xSFzCScjM= +CAhn8dRUItEbEErp4w+lX6BuaW4lR2EIOU/Z+PBe2bh+we0izGB82w9NP/FQvC5X +1u+XjG/2+GSQRv6EzCaWRQ== +oSOaxL22S+/P+2LLtATJSAaArdQ7fQZ1yguM5QAidIUD9pZGaNnuSKZ/6l+iYdvR +Z8UsPk1Q7HtwjRd4g01ryw== +hpr9H7QXrORqNT7P49jOY2MKXv3knmxSuBVQsYMFcyo= +YThduOXrtrzATLnwayQWnd8n5YD62KOD6YsiYJw+RAk= +7JQQDTi0T2idhFjao4jEpWRxFagB+y/O50vW7a0bZYhQWe9QbLzaMInaqQiRp7EGliGMBr4A0vhy+GCOuLQR2g== +7JQQDTi0T2idhFjao4jEpVM5kDnYAXzeJH6AUYrQZAHIQ4Zi/CV9839Nkf/Sc8cE +h2Amc56dmaRo3inGZxN9N4t4UB6ZIYdgw3I8UrLsiK9g6KbQ85/a9qF4mH08k4ge +rErGIG0jHuEUhPzM/Cvn7eDAiXr3299blfSBm1Bfub0= +B3iNup9pdvLGx6CAo6KC6KE/v+ibyUyH8Yud04Lz810= +q4nc/jwATOMUyfSjLibfEenY5RFzfDAlBFkea5rX8Ic= +nhEO6jGYTLTxNd7tfL8haGb6dIE98SqR0mAOJokWbaA= +2gqFvhn8R1IfPGeV2rMswbHUHFXVUzGBLmhsu02wzqE= +1u+XjG/2+GSQRv6EzCaWRQ== +C3JSV38/w2nvM3I7TZ5+4epkOMkaWYvoOErk6ygROHs= +to+v3MAswOY2NqesdSm5E+gSyJU0y7Xs7zqrUefMUuCWwwmO2rJrPN7egFiRQVKU +vJtKpUSg5Ukl4nJbF6QjQKmVnEERhZkbvQk1nyA3oiKLlcyO+IWzbyEkNMowLxjNh4A4GzAgdksU4IKrFkilFw== +m64JmVQ8ZxnJzJGTbbSLFgYYjwLvqe5k1J852jWCJ20= +GDxnwLueKeYNT0/dIp4TaMZIrCOXt9Fadk7QEvmi3LY= +iZaIsa48RXfE+uf/yF/rQBFFBTbLDCVgmOpzzd9fNSoU5yqcis6eMa2AhGtSBk42 +8+kEPw2kbWRhVZANSTiFn91S4x0IfqnYB21Go0MrYHk= +ed2GXKkXmwU2EQk+6MUOb5wVZ0JLOm9I7uChKGZVytg= +ojcUvW+Z2ehEJ6yMJpmY+4qFMPDKWboTeo1Y4LE86Gs= +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +YSlit6erKXylrjSZnvyW8KJQ5WBrDhjuDH3szUt3rF4TtLL3u+2vhmfGxql7dElVlZ+FltHCrJLtURHR876Z+5QJwoIiDk96EuxXOkKMTdk= +DaK4moDemtv24P20mOaUzQ== +YSlit6erKXylrjSZnvyW8F3ElaLeR/Rlppv/IAE6pDs= +9zgnmC3+5N9XK5/NRaR4SOWz2y/YEwWVWSZltrwvNrSfE3PSPwQMDu0BXDhBm14C +1V2v8QerKOmubvSxgB4eTCxJBqBlZI1ptPijcUHCDz5sYoASyVMieGEeAAQXWrck +1D18KWr2hdVsBcdZx1OaPfHgcwFUa7nBQO7bfH5tm74XTDXVMzBwLoLCrGgXhe0X +fqq/Y0gPbKQeEXzqLGS0q/J+017f+uH388qCfSeGlh8= +o6QhOIN2Sc4SHELnst17uYigovIGLGw9Sk8MSoNStgLGPEqp/JNtpdjWEUUK4pQG +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz5EhzAUCM9U/skPlFPtVQrio/gP457Hr1SiCbunQ3L5/g== +1u+XjG/2+GSQRv6EzCaWRQ== +lkFGPJ6keZ1Jd10HSdN0i5ZJfFnQV/nGEGCTaGt06s58zYz056g6djTo00sC9scb +Z8UsPk1Q7HtwjRd4g01ryw== +a1WCYQuoRJeK3mOCGHQIlhrCEedAYfpQnUktuEYoFlw= +vyYQukiyWticSXOwUY8MiznZqoLnnCC/RUpXDOGxEsg= +ho/Q+jrWDtBeg9J9ZTnKi7Z8oMuCLRtnjC35Cw+4aVDyKydByU+COt6lumOoVZyj +AWid6G8ypXqQe9XxPPYED6EG/d+wAZpLmHn10ofRnJm2zuVMuvU9qhZPIJsbg007g9BmQxGLRAPjJyKkcrVzW+hTwBZcS8FDXPlLD0f/q/4= +Z8UsPk1Q7HtwjRd4g01ryw== +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +jEPV0YXDCqWCap/VHplvwQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6wYljL95v5S+KLH0XvYeRAu7jaBdeUshyw1Me0QcftpN +VmVrGQo2zRokW/ZuO9bN6+eQW79nEpaaEVxkQ5mhMLDYFz3gFM695f1GA6K12DxM +VmVrGQo2zRokW/ZuO9bN67F/Q5IDafbWxan46AKyqXhJkHTWPWMJlgqQQU22Q4EH +VmVrGQo2zRokW/ZuO9bN6/0cM2nc/QcV1pNvNaFvxCS9guiudKGmsn/LBMq+3sAL +VmVrGQo2zRokW/ZuO9bN62GGKQt2S9DKmxGER0w1v26K7kSynx75p86xpYrJ5cQA +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +QKClQ4XOaAjfVBfstd1swg== +avTFp0LCGrKbHzgXWCq/pYahaBNfx164yVsSAVTJDN4lWK0zoj6Eh3CajVgk0hPL +TzinVBSdupkFZuXDgmwYfiSs5GTHKq13dTr2kdVzFPy0Jh42rAak6FUbANl5whZO +W7OoNBP/IWbYzUOvpg/slY2NVhp515LKHRWJIoHB+V62OipmiB60YwRsLUoQ2Yhe +ojv9MyHa70Aio7SDrWcjwQ0VSHrTkMcwddE36Wz7lCz16K6ytw4GpaCto2OYzTO/ +IfOrW6dR51K0HWsQz64KwMZLSub5xEzT/cQoJpZiUus= +MzOj4ZiBOJDNVP8vi/lFQIcd3YW2BaDb4kSPJzNVOdlx1Fa3HoeBVOYpiRWE7XX1 +PRG/YRzXVD8iVR3bzEcUno4Z8XHjrzDgKJNn9oJou7lkrRw3h7on47id8HDtKTRy +j7S8zldDFIkYlZuEh7uh1S+jR/V5xbpfKOdCgELD/V8= +yqOVJCkAGOhqcQYBOo7yqReCj9ixiaIB5tdlZ21DAUB9HlVyjl18y9Mfwf2BKrxO1mdZmBSOas0Z9YsNQ75XTg== +c0jePRxtTVZYop75Q5JCFsMjGu62TZDSljeQjMtCShNsz0Y9SFIv08dPVdTjI95b +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5hzukpfx2w2sPIWvGOQMWA5Yh7rnfJ/ffdahL7iA8laA/3JDQRVCFmm1vTiYAMYeY9 +NYuttixIfaRvF0QtYZhNf933obSmTd0Sfv1RLv5CWew= +z7RR81hY6MAA2voH8Z2gcSMl4BzicmRRwwcx6alhVes= +jg/rCd1ltkosbKN5ZG2fjyfZDh9bj6LoXpZWnO+Jm3I= +m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s= +1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw== +k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wAfHx0c0egKY2YtT74Sqa68k3g5TvkiAzu1NRNlkcoBZw== +rtgmVFvoY2d3wTFk50ruC6zmYmoALMFLQOc+xc+vXmJzN60uRcu12WAXC28NrZ2x +wlLHv6kT3Q/RmtMBN4nDAesM/ZGOfx7RLjx/t+u4GQE= +VmVrGQo2zRokW/ZuO9bN692Dxe6/pJb49NcEGwdYFHp1ykWA3uXz8Dyrp2BHgNzY +VmVrGQo2zRokW/ZuO9bN69YWkPGheqxTevWr4koVetNO2nXtSSYd+VohbeMtpTJM +VmVrGQo2zRokW/ZuO9bN63sbeay11c+Azj5VEIm5hpFeadOrAd8WGDCLEKR35dm2edAnPxHas9j394knOwJ8NA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69nNx2wsmuj5rj8dMZU/ic5BLawNF/f6lthvaeVh/Ls+ +VmVrGQo2zRokW/ZuO9bN6/VAHEhJNgGHVrhdK3YgRoi+tWQYazsG4HRXEstT1K7Lt512mtzbhlWYuZWMlSdePQ== +VmVrGQo2zRokW/ZuO9bN69Fm+oKgY4oZ96N2lUN0xCYIR65p/6S+rx79lvSqg2I47knqkaZTvrsnLR+tEwCTnA== +VmVrGQo2zRokW/ZuO9bN6+qkEKPDxNwYFnkXN8TuQuYkotp65nYQ0fa3zEFof2zYnFi/LEybvPyjh48Z1idmyjl0NtdcsjLBkBJ13a8Xf98= +VmVrGQo2zRokW/ZuO9bN6wm11lyKsRHgTY7hsLdIWagcAA4Fqj10A0nlHnCL31hRaLimdtsSr1rF3vfwAkIE9UNe1Oq6AC93jtsLEBybVC4= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69fMYEokQlwk/9w9fdzOny6b4sFM5OW1KAT1oeECeG7J +VmVrGQo2zRokW/ZuO9bN6x9kSHAfsYQbQ95eUk47zCSU++ESIivk4zGTm21DHKkHEqyRv+yZpHFD0LXHT4m58A== +VmVrGQo2zRokW/ZuO9bN68s9bgaf6E2UUkkykIzwZHpz3P+5wRaqeeTuq1ofqFvFKnCBTa5TbqmgBM/RmZJPNAUu69T5cyQoQbj9yc2u8n0= +VmVrGQo2zRokW/ZuO9bN65llTn0pe1t030hd6gBN+zBd3IDBl9SSuthuY26B8/Pu +VmVrGQo2zRokW/ZuO9bN69ytvi1kzpknGdq50rt6Uk+ubFehg+T5L/Wn9jlHITZARQHXl2+Us7ChTWXRkBLidjmBIhghC83F2q0QovPWPAU= +VmVrGQo2zRokW/ZuO9bN62HF7Hu1aUGvgMj3NxvOaP7lJ1t3oULkhfzDpf5obr4i +VmVrGQo2zRokW/ZuO9bN6yfJ0tQlmRWb/s+hHO4yegbY9OZJu7Pz1YpWO35g7fWo +VmVrGQo2zRokW/ZuO9bN63C5DuArfNVx+wMW+93hp1VACOhAd7D/DOq/RNVLMAyF +VmVrGQo2zRokW/ZuO9bN633IXjOdT/W54PqLLNSn1Vg= +VmVrGQo2zRokW/ZuO9bN60VxqaV005HoUzQg7og2uQzzkGvTFfQImHgCDcOTreTvBj0gdrkAAkZ1tFN5kmALjU7kVG7jii3XUUaGJzXU1Dm0brYcNqsnFgqAbRd+hLig734vVILUrNX+DvQbOCpPeCtyQ8YEDj2QHDc0gf1laMU= +VmVrGQo2zRokW/ZuO9bN6/JsORMmbxegVkakcQYSuOtPUInnHZyjGgz46mtOv7qSxkpfnDglI1Zm24CHtbp9uw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknACUIF0SHq3Wk7UPJPdvLnBytT0bs3KMa3b9ztxfxoAOuFAPEC6llaTPvEl5nkxFo= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH13yvcZl75di1EfS2yW7gx+JTVNJQF0HOMQVAegRfGv1eA== +VmVrGQo2zRokW/ZuO9bN64OaX2n92Ly7KbozWq7a7t04nFfOkcL8NsC84xQi78iQ +VmVrGQo2zRokW/ZuO9bN6+a8J14HUH3L5V5KblyMysRYdesKR+fGd24F6C9j1l7wwo/p5lCfK41p1tvTNJzD/Q== +VmVrGQo2zRokW/ZuO9bN665+22jFAKm6V1WzyfKiYmwgxIk3QP+n9ubhUrmwYA2KnMJwdt6DVyM18JjeA81Yfw== +VmVrGQo2zRokW/ZuO9bN65NCQtgmAYdDgDxZd9uDZM6S75/AmzU9dqV8tHszmbb1 +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN64eTzIW7j12tevyQMnZuf2zxhIzkuqiugSL/r1buE4TR9WwGt7Z0s13TO+PuATcNEKCIeOxuOdltONG4HDVneoo= +VmVrGQo2zRokW/ZuO9bN61EVRyU6dsJr2EqyMVPbztJnTc0M4smwvC4e37Qh7Ms8 +VmVrGQo2zRokW/ZuO9bN67E2Rg2DTyty5YPAV4Joqwwqw9NcbRGp5GK4uyK1s8dkEGH9RxcsHHlq/24VKap3dQ== +VmVrGQo2zRokW/ZuO9bN6xRDOkMGch8OU8UdKKuA98T78S/RtIEO+vPGquNUPYP8j7Hqi5x7cOT1rq7lCPTbFRrq+ZOiuhVZC6d2uE6b5Y4RMkjy5kdxgYDR8lyh08y6 +VmVrGQo2zRokW/ZuO9bN6x+CQDkpZHTSzVJB0vr6/owqo2rDPdhhBctC47HwoQJI +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12i/zuDq/sxwawfAUxLzsmyvkFQXYvSK+lgmw3Dfruf8oUMKRDLCW0YoRSFNGJnVRA= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmJ6/ykoozm7YfC70113tbsRK8vQxRXP6m1Lk5Pq4lJ71yrM2OyBQ2a20YT6ATnjGs= +VmVrGQo2zRokW/ZuO9bN6xC+pGEHNG3EGFN5iqJU139eI38FJ9/1MLDi82Qu88tq +VmVrGQo2zRokW/ZuO9bN6wJQGv5Qosk97LJRISI0TKDxn43J6bCNtR1cs8SqXBFf +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm/dQBBTn3j2CUDmofmN/zblP1j8uAa/rToM+BZYv+mQQ== +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12q9ECvLOWcUe9cfTPHyuxucjjrMsKMpYA6OLWCOLB2Qw== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +VmVrGQo2zRokW/ZuO9bN6xyEP5JNRRYJBSeITWT+YxVGp+KyALUy+hocs1rcLGph +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN64eTzIW7j12tevyQMnZuf2zts3XldX2gYW8pNbC1L6OsQ4ZtJ9ajE19kuRaiWyVAoryEYwu4R+F3k0WPtnoCKwM= +VmVrGQo2zRokW/ZuO9bN61EVRyU6dsJr2EqyMVPbztJnTc0M4smwvC4e37Qh7Ms8 +VmVrGQo2zRokW/ZuO9bN67E2Rg2DTyty5YPAV4Joqwwqw9NcbRGp5GK4uyK1s8dkfYflZHE0nkkYN92Iz4DvtrB7pbXI8nJ1B+v8kBcTL9k= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH10h4UbnbgTDNlKt0H8bnNKHH19OWWvR1gRk3zXeKMJ+R8jqCu7/rIb/rNBOG+zFbIM= +VmVrGQo2zRokW/ZuO9bN6wRsqCOcYvLI1Bp/40+s8jeuGD+dib11yOYzha+J15yw +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH11DboSZV8b8syS3y20nGfXmJMrA3OSI5dV3PIVmDHiETg== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +VmVrGQo2zRokW/ZuO9bN6xwsAbGNyT9xD+H83ZahXtoSQ21b2IcHlJ/tPpLLTF4v +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN64eTzIW7j12tevyQMnZuf2zLKCX4fndgv2+Eq27hqdJSfk45Deik8ix2YNh+TocfCoJRVsdPtE1WD0Mo4Zvkv9M= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH112Cwgr1gup7jex1yHReVWxiyvsKVsK3znbe1ecdgFhbIZqxwirWCHagb7ADC5VmGM= +VmVrGQo2zRokW/ZuO9bN6yc8qBfgAsioQiqSI3s7WIFM5rHpAQSYfkhRNfXBk2JBPvIvjRzS10i3GMc0J2bxTrSvtMX11tg5EOzI+VjAy9E= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH12W5piSXwXcBTxZsgj+fR5PRj+yKlsuMRhvQaQszRXFiA== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +VmVrGQo2zRokW/ZuO9bN66sauifJfWwug1b3TCSgxtryBhfJZF25UQuNkKl6tfv2 +VmVrGQo2zRokW/ZuO9bN6+3TErdL9P5BVvv77uxFNG6LDoAfDNrQcomm1VDCoez1 +VmVrGQo2zRokW/ZuO9bN64eTzIW7j12tevyQMnZuf2zNXuQbP0F84n0/jl6+SrzC+pRpm/QL0mHxDS5+IJAnFFA7CzSbYbo7CjkNhnp0xac= +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH112Cwgr1gup7jex1yHReVWxP38Ud5r+2JgPLAafH7N4i6SpRZsaYMAGNiTp9HaQsd4= +VmVrGQo2zRokW/ZuO9bN6wRsqCOcYvLI1Bp/40+s8jeuGD+dib11yOYzha+J15yw +VmVrGQo2zRokW/ZuO9bN6/HUq2I+boGDdrr74hWiH13ETFhTDjpNg4UjDJ/ZX/5m5v/P88Ser/GIltB+CyiPFA== +VmVrGQo2zRokW/ZuO9bN64rXDFbHdbWNMaZnXznjAwk28c0+eGFiwoRx33gWzY3PbndTr3mFKpviH1b0k1s0KA== +SS3eUI6O4Am57+zsCenaFkHmh+pwM+FJ/cut0UtoxJ0= +lcmg3fAsktA/DTr8lB5mRWYUQaXOV6Q27CwrOP6ime0= +M8WJLh8/G3HKYOcF0o/j+UKQix7E5QZpJV0Tfc/XRq/AKN5kkYE+IMwFcAyVsz2M5wHJAN89+nJ1b7GSY68O6925BBXUxFwqdflokenOUZU= +wlLHv6kT3Q/RmtMBN4nDAZf8v50ttctcXvJzAQ4D3y4VerbRFUVUK+NjcpNFPsa+ +VmVrGQo2zRokW/ZuO9bN60s8cEnb0+qXY0vQh1/7elp8TJKn/o/Cr8ab/L4KpzVyx+zZfHPJlblcJVM2evPA8Q== +VmVrGQo2zRokW/ZuO9bN62wFNZfsNF2gO4y5vOS6dGtOhTIohM0/9iaTTdt4VAkwr3U4JR6xB2mGWrp5KaqxgOX+iIeHDUU+B09yhkuBbeQ= +VmVrGQo2zRokW/ZuO9bN6zJHGvDJmOJJYHHdXzByxKsyeKpya/o0dns6NEjvZ9FhBqfG56Ge8FxUmH1UPM8WYg== +VmVrGQo2zRokW/ZuO9bN635qCNYj675bllz0Ek4r4iv4EKIq35DdzNNGBjl++xHimfkScnNymMQSXs5jgXMSbQ== +VmVrGQo2zRokW/ZuO9bN635qCNYj675bllz0Ek4r4iukMkEeqt6J9KEjusjtsVrPFiXBkj3IzHvRGwRNUQeLY/b/AFWdigY4P5oM+yVsvmc= +VmVrGQo2zRokW/ZuO9bN635qCNYj675bllz0Ek4r4iuMx7lDsumcY7b34j2hhF0B +VmVrGQo2zRokW/ZuO9bN67WLIC8ccIPl/iWyXLvwqc6z89/fim1Zup64yhd9bw8m +l2FJPs4YkAmmok1ulDRuSA== +wlLHv6kT3Q/RmtMBN4nDATXcKKq3YicYjT+ilqwnePASEk6UJ4kb2FJ4sCzHQMkb +VmVrGQo2zRokW/ZuO9bN68rwZzz6qT+BRwAM0w/VgNam6voCMhL/LyfmhkE+4WOq +YabJCT93a3/IohXaIkr3oSIBZqGhFAEcGH7Ca/SaLLaG+fOp90QxSi85tpj3adUdyNOydRy2vjmM402XZpsX3+nkFnHf2XtWx5xSFzCScjM= +CAhn8dRUItEbEErp4w+lX6BuaW4lR2EIOU/Z+PBe2bh+we0izGB82w9NP/FQvC5X +1u+XjG/2+GSQRv6EzCaWRQ== +VEyEXJTNTVvV1TGW5V2yEMzjBDxvV9j1MdPjo5gCe0Q= +Z8UsPk1Q7HtwjRd4g01ryw== +dar6N0tUAb9rU8/aX1b3tI5lWSbcXQgKxiMzJeLasCmdJgT+u3YcH7fkhDzLb+1X +9BuKX7c+cBUFGH/xEQqKwDJwv6pCDrQbGrc/aEaAxEizXN1tDiWRXfYhvFOYGiCD +3AcPLYoMMn4rxWOUMA1wJdOmdYprhThTGxGyDnWq840= +Z8UsPk1Q7HtwjRd4g01ryw== +90/F6C3nHF3UrQ/Er80CwwMY0Oi3JHbGAYQNb/w2V49c1F+wL9hoGKNjAjwN6pWeeHbxvhz6JqsdWpYj5f3cjKeHXg+8Lul7hoesFngMXfE= +g9lRvj200ApJT2I/NDdmaNS2bs03/Yy1N4LL8JCPrSEGtYUIbFGqz+9Nvsx/WW8DAPlJEAOAh1mN1Q2H5srR9aN9BcyfgbXU677udPpvmJs= +AC/CEOsuoGTyXbh39EUlAxckdwNHSoPmK9UbpHaeRy5+9wXscQv0OinnRdatf4o5vPdsr8lUrlC4beVwlerQ3y4hg9sa18InYNYeL8SziTc= +HBkACpLXsHV9FQoDE2PMYwSntxSga34Rul8QLEjV96M= +wlLHv6kT3Q/RmtMBN4nDAeWjc9eUr6obACCnQsg7vueRPByWE2DayjQ0xFllv1tW +VmVrGQo2zRokW/ZuO9bN65VlXQyBOMicxNyTkfSbDVVHhvBq22DM+S7/byvVxhpE +VmVrGQo2zRokW/ZuO9bN6yititPhxMgpb/nuYcU2bQxlVrgi32/YGL7BfDcYBS6ZrhF8DKeSq4/xyWh1l402NQ== +VmVrGQo2zRokW/ZuO9bN60VWeNWzP5MtK6lHtmFWfiNac7RqAQJc2+YM8lNzCLr14sOTSSb89TTi6tiF4b5Mog== +VmVrGQo2zRokW/ZuO9bN61P2OVHnBm22rHl5VcwespdSVFvlh6jWmhNQGuWaiPCn +VmVrGQo2zRokW/ZuO9bN6/C3rj8+QHPPw24V/a4Ay7A= +VmVrGQo2zRokW/ZuO9bN61MqA9WsfKOg/tExmVwPpl7QnDnpNlS+CkeEkfTa9kINLQXgJYeYvwm13Q5goIzeLg== +VmVrGQo2zRokW/ZuO9bN632LGHZY9uic2w89Yj9RNWLrcjnK57CYRX2bXewhAHx7FVtzwI0ZOgY/T/V/6qaQlw== +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjakEuF8eNiysMFwArESQEwY +VmVrGQo2zRokW/ZuO9bN6yg8DfkEZba0oHLbAQabDs+4/JxGHZrKdgs6bMyBPLRMxwPtWLarWOkPgDMMkW2/RUdxOLVZk3+IQpAGkMdAX/s= +VmVrGQo2zRokW/ZuO9bN62dh+FzI+VGLu7ulc+ItFAbFXemabBEE/LbP+46UYm60 +1u+XjG/2+GSQRv6EzCaWRQ== +JEnsSsP7ogTJg1OXQ2YdoeqaOYYcnxFN3j3ktV+Kpps= +Z8UsPk1Q7HtwjRd4g01ryw== +8xWkPvEiREBWC3F3wdDkfHfKi35MXWHSVRhGdhf5R5T9XB9HNeEomkbAWdDKXpBP +Z8UsPk1Q7HtwjRd4g01ryw== +tsXNkoeF46BtVmFZ50qzKkC9C9rJjI8DYYqtZ5sTRNk= +b4OJVZe8QyIpjuTpKXDL9A== +cjZRE5RYY9TGg8THaNbKeimi7CKPJrP0YrozyGJJ6po8VG8Og36dzm29TqPtykmq3ShDIRxTK5G7NnirQ35JIwA7rlx3Yk1mP0YkpIDY3Ec= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN60gMmPI+qllGkwxOA9qbMmMB1m4JoBAOFYQ6YXkMo1VXquwSJ3IOKilGLS5oWPYO5OYeeloJXPkrrYqwK73A4D8= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN64/n5EL8V7CIqhp9FPzHEuQ= +W3A5Dt+8AxWSKsTYeXZoL1vLKDueV7P0Ut7HIASfZlp4D0IrBt2bwp8YHjJXXxXp +W3A5Dt+8AxWSKsTYeXZoLzB4TUiScS3D8yuLKBmm3AwWtYcxV6f46K0T0Egvi5WKkEtlHJveHdb2egjl/+1LDg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkknASfUzt1VKHSynjO/3GCHs8NYZ0lBfA3YkEhN9KGBNrviqTxIwFkI8b8tfQOhuu9jUeb7lULfjfPkmMnUyufuDGrOr7IT6W005LdXSdC+eg== +3ihEq+EcGY3EkKhgSscgbxPe6xF3X8bYDWyX0qt6BtkDpzyqs5cxV+fqGAxgw4n0F0ocWo63+pTEB2B02sSu997O+t/1VUp8dx53F4oaaq8= +1S1xjYS0s+Ph1PyvECx3QMdMFDZ5B3+BEK8kyCfI1RM= +VmVrGQo2zRokW/ZuO9bN67pnuem2Cn+Z/63FRYUcFkLoOrSR0T8WP6DifYG5JOXMPR4AFoHBPiKvg8hnpKw+PCDgxml+g92wfGPN8zZ2WJk= +VmVrGQo2zRokW/ZuO9bN64Ga+02XCL+A28NNybmlSnI= +VmVrGQo2zRokW/ZuO9bN65a8NYx2zqK2mFD4wjj1HmQ= +VmVrGQo2zRokW/ZuO9bN61jw9zMkfm0nvQi9tveiWV0= +VmVrGQo2zRokW/ZuO9bN6zYzNJFTIYiz/a08bSUNc5M= +VmVrGQo2zRokW/ZuO9bN6ztX+cJqy4yqv63JnfA/mqI= +VmVrGQo2zRokW/ZuO9bN60jLKcJEMj36X6NZvvwxF/XvqrmJF3W3RArdDHrkgZZ6 +VmVrGQo2zRokW/ZuO9bN6zskyREk1B9SpIIVpfE36zU= +VmVrGQo2zRokW/ZuO9bN63leNhHVk6qBYEqAElCrxOXVPHIpJ8U4k7mou4tbIR/7 +VmVrGQo2zRokW/ZuO9bN68tloVdndHDn6gJZRayJG+k= +VmVrGQo2zRokW/ZuO9bN617eR9WvYqaE7y/ppCzNL1570CCRJc668VdVufs+tRvC +VmVrGQo2zRokW/ZuO9bN6x59qEj9owkguOGj8QrHQSLi1Q/1C75rARTXu4TGt8DP +VmVrGQo2zRokW/ZuO9bN60Bq4PDCei1vxH18OtSGKCg= +VmVrGQo2zRokW/ZuO9bN67biU+BUR7/d7BkQj4RgpT8E7BfCa3N4KcxGo7ivT2Hg +YH231WTDnzQG3bFilBiqIg== +p9oVsBgcyiBMjDb8CcPOqP2sLQ/zn5Dzo5lCtrgxqTQ= +lOh2GtzHjjMM8E9J40AuOcPN2eZCahfvScF49QHmlNXYE5PQWq+WYPigt3dG9DzvOjcs8WVNkHQ6P1pZps2Ehg== +UbJuac0dxLiN+5ocS3w2vTpUyzdtCQ6EQJg8V3275Z+ztU2/bGujfta5s3IG3yXD +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +PgCX5XeVQYjD7/hbTYlz4IaRp1HtU7nR0FQvcIMZh8A= +7HpfjuxaQDsQDQSHtCtrNK827fUfCR50hIlsuaTJjFjc8y25hOxPGEu/jrhlV8lb +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +jEPV0YXDCqWCap/VHplvwQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN67F/Q5IDafbWxan46AKyqXhJkHTWPWMJlgqQQU22Q4EH +VmVrGQo2zRokW/ZuO9bN63ZoACJPJn3hKH5qBGe9VjFfy9acDnoD4s5pyWTz4aPz +VmVrGQo2zRokW/ZuO9bN6zC77Yy5IdKUwUmbFPpcxkKmTXclfBLfED5Xnxr6+jid +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +jEPV0YXDCqWCap/VHplvwQ== +b4OJVZe8QyIpjuTpKXDL9A== +xWoGNWjKGPfI4gq8aHoTfEof00xZubKgl7GPVFGSDVesQ26Fk2cgdMVMyPXm96MnC15F03LmfmFV2XHuofNY+j5SUiwtk+iHsWhJ28gRAZY= +32pdC9DD05OE2l0oXazDFMcaSf/OvtPF5ddCbxkc2xQaF56RJsO2Xz5jcPdtIB1/eXGzpX+pojBjz/0LE9V2xthE0OS29+QUIDSCMj3fNAQ= +32pdC9DD05OE2l0oXazDFMFVya1MEFTy0BLA66iSpsVqQm6Ex0aKp6sk/uFMjOFRLXiyo8rgZB6BCUp1rgQxw9a9JiLERnLcmc0XUhcwFis= +1V2v8QerKOmubvSxgB4eTB+P6Y06+m1KdyBFjy0y0qtWT8ILPF26Krk+5/LzoulD +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSuY4P/AlnqUWfNJ3Nv+yymjs8C+eB+Hn8dWS7DzR5wCEVi+pzzp4DxubDifplcmTz8xv5V7wP5rejGKOKJFZz4 +uhuQgaMXnN5eYKoRWtj1+a5fnluADmFejrHKaJhU6U4= +IZ4I+7EmYS7/1XOeZ6CWDePsgkaWBxAo595vQq8IcgAmT8y6BMgbXaQOwT7ajJOyZ67cq/xz8LBemQ1aX2Z0Kw== +VmVrGQo2zRokW/ZuO9bN663zFPH6x6REddIs/KSm3NexAA0duB8e2h+avE01iaYd +VmVrGQo2zRokW/ZuO9bN62PWz3K051UrvlylqX+UdsKu5SkTaoIluiyKgMiWKP5VvUfaBzPRT+4LOTVj1DoKDMOGhOlRnJybfG0/JB3xdn8= +VmVrGQo2zRokW/ZuO9bN64iYMX+A77ebuxgWS4MZGWMQSPvTqssc411J6ON3u2s58fEiOjQ02BDwWoxRB1dSXD2N6clFtvfGUuHx+6QdDR8= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpIwympmloGxIdm3lnYC1zFlt82vhd3dIG++dF0cHPJrs= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpaXbDjWLbvZ1lZ0Zi+IId7fybt87VGRejtwKKf/BWJGU= +VmVrGQo2zRokW/ZuO9bN66AqdmrSo1oyJGbJyEFKlavIS98JVLZCvBKw35ReG7IZ +zxz/Z9yjFalbpeH0OEk069PytlF3G6u9Qh5uIBCLydc= +IZ4I+7EmYS7/1XOeZ6CWDdX50YjqkZaY4v6SVK3YYxL85Xnq8vaKhIBU5+04Vbwq1KwBOc3SUWrebxmVjCIdLg== +VmVrGQo2zRokW/ZuO9bN6zuBW3gGFHmzC3pxFkVqaOZGdVRDEqj5rzX7nMjOksm95bFAdKLGOw10hZTWVNauPw== +s2s/1ED0ebJ2NmFlE8kk041WeEOs0NQTR+NEuE/p1wycslBra4ABYHujZdQc0uDG +PRG/YRzXVD8iVR3bzEcUnhS+dVNLAvNDmBhimxHvlNA2HT82QC/02iXhdE3LqIS5 +j7S8zldDFIkYlZuEh7uh1VxkBYQtAkbaStfBbWx7fJQ= +6NhFwX8gQzuQfkiLZq050pZ+2q9zTxL4R2Rx3pYCY0lS6XAa7HxFkLPeWNSm74d/grgzrGZ4NTnGcsA2gwNf1g== +PRG/YRzXVD8iVR3bzEcUnoNc93WwzSDGZTijqo4XQYP1zB7OiFQUJ2sKTBOKiXHBegpq09XlVduN/zMZI9/IPoQkkQlIVRgE66uv5utA96s= +32pdC9DD05OE2l0oXazDFIv7Js51eMSeYiKPDoffdWw= +k1ktu/l6HFULD7Vyr8Cv0Dp/BLIuC7jc8lO034+ASSh12djXtIbpBUNxeWBNIvYRsNqqiKBD3YtyfeT5KRCKiA== +R65WfMhum3uW9cFR5COFLL9ubQ0tcpZY5jkkHBnHdce7XfcNd+pvaghvtup1k9r/CrjKQwHyNJqhVBp1y58t1lQ0AEceTHxF+sTa65mC4csmqqmEXH4lv6bNKaJSvJ14qOFHfzSjVyDM9A5KNHf2No7JEow0mX0clBmf2ktpRlk= +32pdC9DD05OE2l0oXazDFLmFDXu13Ku/x36VAfPGPP9FkvpnezPYZrhPB8PIy/uL +wlLHv6kT3Q/RmtMBN4nDAafzR3IJaSQy1F8fEUDT0KURDliq5QrdEI8/0cInAzfT +VmVrGQo2zRokW/ZuO9bN695D2NbK/khMekHONWycWzoTL3UetXnbY5ZBbnCaZGEmk8vkbkjI1J6NGFVtF3qVFg== +VmVrGQo2zRokW/ZuO9bN6wLuYcyNxaKhNZH2m+8aDPkLZsJ5l6OV6nPIyZ7rFMD3fDsuaY7RO6VkCnCbVznvMw== +VmVrGQo2zRokW/ZuO9bN69s+3Eo/hZMrKkYAXB3JkA2bQbWagpg3k5RV7ihsj0gHlQy5AeQPCIj7FGBMUa+4Gw== +VmVrGQo2zRokW/ZuO9bN6+qTSfToQVBLEY6zusk0ygqo9bxqE4YdXRCZUEfzNiWt +VmVrGQo2zRokW/ZuO9bN65N3QENvuGnjF5EmxEeDMzE= +VmVrGQo2zRokW/ZuO9bN6xmmokKCVOSzrVXzObj/KfAtUgdJPHRUFVWeGHuin00P +VmVrGQo2zRokW/ZuO9bN68Dhg+6et5k+hzf+4hfigxPO1gqzqETG/sLsufA1aXnk +VmVrGQo2zRokW/ZuO9bN6wWzNcvV4pRpB7aeiH46ZhuR7lLT0sXl4MncnelGql7ypmYCkufsO9/UWUOa5gKFFQAt0RFwBWsN3XLCnm+TI9k= +VmVrGQo2zRokW/ZuO9bN651a2HPSF4amB6pelKKGbxD/O3WChczakx9UCQPe9L4zYkn7rlilYYw4UiLgYLLHyQ== +VmVrGQo2zRokW/ZuO9bN6yMdhcWuddd6xv9F64cBYywreWSVvaR5B3yfWI7OYuMP +VmVrGQo2zRokW/ZuO9bN6/b5sfZq7QFdwQMtnNHleFw= +VmVrGQo2zRokW/ZuO9bN61gHm53bbaH+I9qyjWNPAxQviXRiEfcQJ9UaS/gMM+QlSDtja9XeaXcxbmOF5qba1Q== +VmVrGQo2zRokW/ZuO9bN6x88C1oSfUBmfAHykM+KeSlhAm0oCEAnYAW+tjlMsUZ6dHLLXaNm3S3cFG9r8INPpOyDrD1/0cYcdmgHJu+5hQc= +VmVrGQo2zRokW/ZuO9bN6x88C1oSfUBmfAHykM+KeSmsYnFTlaGSi3hItve9udXvXbK8kLkxk2JTeN5/083dIwusJTyrOp6WwhxmuVadK9w= +VmVrGQo2zRokW/ZuO9bN68QfdIZA1e8ZLlf8ttXmLYCnyNJDls9LvrZpXkKHipYZ +VmVrGQo2zRokW/ZuO9bN6x/CYIieetlN90tjay10F5n5/v1iJkArxYoRof65Hf/bWUKgONh6wM43I8Jpg8FgsHIngfteD7UBDsQdRIyf8yC4fMeAOMpse28Tp9jblFPR +VmVrGQo2zRokW/ZuO9bN60p/qfi9NG+nP+luzpqucTyAQc7Yj1OJYmKJzkVZar3zXH5sqe2agq9ywn0O/zrtYRUQXA47gxoRmDhJ3H/ABCQ= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xzedyD4hQTvOzNo8fw2FktCfIxZrB+ETZoJ/tb/OJ7i0JBBscPeHSmlMj1p3zH2kfzbA/hQSeT2zLO4guX6IJJgLJbg9nfzCvKwiJDpAx551vjLfPPT0dZezyB/sdZE2kmDhMKoqccpXkwE5uLmZ4sE04VcafV5qPD9LrfVTiy8 +VmVrGQo2zRokW/ZuO9bN65vXYqLcZgYh9RMZsMEc7swrBAM5P9dGK8NyKUQE8BGKAhHZEhZcOq8cWZX7jTyNTKobvqySbP0rFUEeAOBEQvAlfT0krQnboyw44hgEZFoq +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN63izF5cXEigq4rWf9EMGZs5lLUFVXHaMEfeD3NCR3MQrLCj9wxqx+wsboPDs62l97A== +VmVrGQo2zRokW/ZuO9bN6/OgSB8gHUPVPZkuldfhCBiEPWNxi725HjxzxtCaRkyPAgzszKiIBdm8EejILUwhg9QwAaRCNS9pDnB2qTnv/oY= +VmVrGQo2zRokW/ZuO9bN61LgRwreI9DApMQDnZReQ7qMEmgNW02YMDHoHy0bIU+UjUji4Y86/fXHb+PlFDf+jnE3sKazkvpT5ej343oVEAXLu50VQoO8PyS+uwRDWTQSlwN12A+ouOrf2+6c9fJbyJNho+jEgVaCrQjiuTM7+GpfZkrTHb9GDgyXeQWdvr5CO8mcOsfwgX1wX7iT+NBLGA== +VmVrGQo2zRokW/ZuO9bN65pQd/WGJO8ZjtfpO3veaj4G6ZwvVtL9gz0WgP48V+4qSrYN/FDj7+EvqNhODifyiQ== +VmVrGQo2zRokW/ZuO9bN68xSb0F38voLTC1ddFF97JS0RRXjJlMhVds+i1p+pz2HZN8oiYZHWB9YNjBJp2xjb4CwY39xev88q+d5fzQky9Q= +VmVrGQo2zRokW/ZuO9bN68frBhJmhmLxBkcwHpKMHIo1Fv+32NAA+36Mtj3NWFew +VmVrGQo2zRokW/ZuO9bN606yvVyUswKh4K9nEzByhRbgQyJvF+T8ZL/ywFKLuux0 +VmVrGQo2zRokW/ZuO9bN66E5j6Fo/6VCuayBfdOIhI4Wmi0iqcFoY31iRzq2/9PpOlmVnYLDJSLa5eJrIJHOdA== +VmVrGQo2zRokW/ZuO9bN64DqcAVCT1beyEqgB+oPWssh0gta/iPjagLMcIOG0ZM4 +VmVrGQo2zRokW/ZuO9bN66E5j6Fo/6VCuayBfdOIhI6/NAlvHtD8pmc2QQMFS1c0sVcxoDVQ1zW0Yt0PbfOw/w== +VmVrGQo2zRokW/ZuO9bN6ypica2CILJsrlhqe3QxnA5/+lrILTYwkPcVkl8kP/7Y +VmVrGQo2zRokW/ZuO9bN66E5j6Fo/6VCuayBfdOIhI7SNChuca/w+Phx/6pTsj9jxs00/c+KZkfeNaMXVitrqw== +VmVrGQo2zRokW/ZuO9bN65fHmo3CpQdkgu8QZ7/F879DDf6j0KBElFUfLCOPUFfLxXQ5L/C8yo/NSNn+6+ovkQ== +VmVrGQo2zRokW/ZuO9bN69TbvhtoIodcEOVjaAiMprVI0Jq4pPMYBUjl1/yLR2kpy0NEMJqN4n37N8mHjl/M9Q== +84Vymj90Wzn5yYuvz1pUuK0n98niayd9hUG4km8pAgba1Y6eGIcXGRzbcecfmD0p +VmVrGQo2zRokW/ZuO9bN6xbcZbx7XTfd0EiNQCQCRcGDvelsKcSn5NmNaZgXDjglenv6j2NQZQfINKTQZGqdHwp+S79x2vpNGCjrJsOmtN4= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXr1SI9n6q2YW3T0H5q5k8fTEg08mVqsagYJCw9AbddXw== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX+qgw1zzEimKnHnwQ99BpoMHjRxO1mk4jjepqQS4J6VtIpyoQeFSYTDCn6S0gG0vY= +1u+XjG/2+GSQRv6EzCaWRQ== +oi31VvltPlkmgAj8qrGsXUBJ5rMdCS0QXWQhphOGafI= +ssyiL0QCGR/Kitp0jM35AvSubxcgyb02wCxY63LeQ+FdXLXZELnkpYp0+wLaWOXX +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +WHkOzVx7seuLmxs5Hu+l/tP9Id4aXVd1mst3jTveQ+TSwCdYkYYi0xlII3ad+5Mx0bTivYaMa5qHmoef8q9aEg== +NhnIR3Ilo4H2su9/cTNo/Ps3vYSOiOY6esn1xeoWmAlmLwmaOoQ9T7lnCb6TRVZ6G8N2MyoNi7SMYnVlx7nRFc+zLLFF1NONcDlGDGB1zDU= +l2FJPs4YkAmmok1ulDRuSA== +NhnIR3Ilo4H2su9/cTNo/ASGhAbuY5KDc0IEMj7gTgg= +VmVrGQo2zRokW/ZuO9bN66hILXR9DK+oj0n6h42Gdmp68GKVo7yFyC0zdiW18fCa+eCJ494xGENCOPZtT5QGzg== +VmVrGQo2zRokW/ZuO9bN61UcHI3J5ut0LQ+Fq89sq2CxmCAw00K/80Gf5NGMfLiUPQzvxBOYvZuEZ+91m9rjMA== +VmVrGQo2zRokW/ZuO9bN65WJwaAJmfHPqle/KssJiahg597TU14l8xoyJhcweL0cLgHZ1ful0i9biMG05j4pK1ULcUOqxBNL65J6SG3EPEMuMtAbMDKEnGF4eeMF6OKFVOhWbtzYZ5l2KlKRIhBq9ivs220dKxL9knDJzmuKpunFe7voKpqtNUwZ8r/eh1hLQdNeWViD2kvL9h6NyBQcP6nPW7psPsoBVwnN1/LEO+E= +VmVrGQo2zRokW/ZuO9bN6/cVRWVNFm/JqWhVgEQHano9Nl8Qe1INoxj34Eq6ji7s +VmVrGQo2zRokW/ZuO9bN6xf87BYcEtEJusNj6B9nDB0fckAQ7JvTpEflmeNXOgGh +YH231WTDnzQG3bFilBiqIg== +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+mIkOb+TCfJvH/w7nenmNQi1zWK57JebtAOzfMq0wpoFDrQFeJwj7VybHSlxyiPcW0= +W3A5Dt+8AxWSKsTYeXZoLzB4TUiScS3D8yuLKBmm3AwWtYcxV6f46K0T0Egvi5WKC9b6LRk9h/rY0UvA4He6KcMMvs/jsWH0pUrPiH4ey5uAYIK2BkaGLpMljqgMIz+zO2OKSsR6gLqnpPrjUkLyAw== +NhnIR3Ilo4H2su9/cTNo/FjiH64JsDVcEniosnxmabur41H+EjLIMfw9X2mUqowY +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6oNJYJ3oKHQnTz+Hw7wWSQ +1u+XjG/2+GSQRv6EzCaWRQ== +GfUk7vHSvwxwwg1xGBF6csBhgNmta9ilREnJyj+FktE= +c1VtHOwUx3M8GzIh4FUOTb2GTYtBDI9RX12eWasBOyJaoVTrkag2GDqWWvjqaK3O +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +QKClQ4XOaAjfVBfstd1swg== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN623k/6o1+OQ+J2DsFBOlua2+5NYedbrk8T/8p8yP8t4ZOrDHOKTqndspNT7oxPTm5g== +VmVrGQo2zRokW/ZuO9bN6xobVLd08EJekv6ZAw5NA5B/Diz1jg7kDas/QJtXmbfK +VmVrGQo2zRokW/ZuO9bN6+h+LXnp38+SIcNeILtkDJK+LUWUXA9CApQpM6oXVdrL +VmVrGQo2zRokW/ZuO9bN68PyIKRWhjdxEk2m/qVBWymy9ZNeaIw0W1O67DTRPyNnoR+nnqXQv+HYTmswh6DgsA== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +09UuO0tw3kJ5J618hVggcQ== +QKClQ4XOaAjfVBfstd1swg== +b4OJVZe8QyIpjuTpKXDL9A== +k1ktu/l6HFULD7Vyr8Cv0NlPupiosgMrWrig5wXf07sHWsWP2ZG+eSRXSHwIyzscxCduO1qC9ihgl/nNN90yJ0k32I+kdYNgmVL7KZay7LA= +IZ4I+7EmYS7/1XOeZ6CWDWWl7RxKu6InaEBG7vU84eJ6NSVD41CUFN8EiDm838Z1 +VmVrGQo2zRokW/ZuO9bN62dCBfssvm/TGBXvEVjpXqinGtoatFJMGqyPDxGf4yCjghAxfayewdj6Sq2OfRf+QQ== +IZ4I+7EmYS7/1XOeZ6CWDeSjVgBoC/80Cp9mo7qHwRL0mH0LBS3N7X+ur5jGn2XW +VmVrGQo2zRokW/ZuO9bN66Yu9sKLLNl2nwapG4f4GMyi6ZnT5AwNHkF3+HYF+veHnYTI2lFnDPGtwmEvBiYNJQ== +IZ4I+7EmYS7/1XOeZ6CWDS3EsxYnGQoZ2XFiH/62GWDwC7JpmDhNC10oZSCsZH98 +VmVrGQo2zRokW/ZuO9bN68+EFwL05pc9iCB4ZPDwL90AL87PFeHdUCmDTwBU35QzUV8sUuZeBD9hQt6GEuKkXXOYUbDtAhti8agQ0HdlNPw= +IZ4I+7EmYS7/1XOeZ6CWDVYZUPAz6JpPin6tPCQAVitIBSokU7215gEpIqEeMgz1 +VmVrGQo2zRokW/ZuO9bN63JCOljOY+Ch2K7ShGCJuvItWI8TcexGXS34lSsKvEvQG4H4kN+uwTbL3RkCFA5Z3g== +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+mIkOb+TCfJvH/w7nenmNQi1zWK57JebtAOzfMq0wpoFGdgaOOSewO4tjZybH6SMo4= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXrx0RH74rzP+DMEHwpOwZ6d7bNaVWJEBrv6ZnZvyvHTAUE3baFmml5mUO5DXOnAC0= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5hqfprx+1n+iB1xwtsTjMm8w== +1u+XjG/2+GSQRv6EzCaWRQ== +XDQrbggyGtdxKf5Qd7CNDLXV5Zo0HKhXIYGnyxT8jhA= +mkMqvMwtS246VTZkcVlxvVf3KU+EyyXEdiPyP6rXAJeHK92ZpHtFB6ZvZBEzxQWX +D1+XxMqmQ9YZoTtzHI1TVzyBnQfcGjplIcZsNUXpw7Viqec4vJXheAV+I72d2S7Uu5RlufSa/1FqPxQw04YRZw== +n2Du0DA7YjNSpLacHFycz3CP7WVEm3aXjYa3hyjX/vUnN+KoeVTuPL1fYxpOnihl +Mo+0bvALEsKrelbErcxWpfi/5wYNk7Cl3tNmt+uBON43WNIz60wjKPX54mZYH3Hg0H2o3yZh/ula0ThdIMJ/ZQ== +Mo+0bvALEsKrelbErcxWpdx8fHv4ofXPZuoNIhLdJuneSTS752QV95WJYOSMPumWztWyMZs7Nk6qd1eygS1UOQ== +juJdqsykhOSvrjoodiEp93Hsa/iwzL5LF/9dyqXpyX2xlpLk1Gj9JMQOc2/aEr2512B5LUufYJ1XyixXjBNUVg== +fNA+s8KEa0Gy0us8PkktW1uakXpccvuyNacjHqe9a1de0PLA3Lfx/4DGb3djRrvuTxv67A3Rnwb5YHI2AqyV9Q== +PA17vimSRbG7tn861DNLBtjB0ul27mGcfjS4S+qBjlb5atkTuhrMpexda4gU00KeLPy+BMdCBGGYXVrJSLm5Uv2zSWtGjbvqwpb4Nr802Fo= +1u+XjG/2+GSQRv6EzCaWRQ== +3LUcH+0zR7ubnty5cezL7HZ9GeyvS5PSfD+Q6aJjeu4= +mkMqvMwtS246VTZkcVlxveIcYyurln9c0dwl21vdjnjzHM3c7FB74O4bP43aSx0W +r+NaLoMoC1qELSrv0h1ouJTsypANy3V82LmUeRvjKQQ2HDWmhdnv++dF6nRdMCPL +423QZ5M5u8A1qyMDnjgTGHUpzkEm8yvfqG5Gm+yIhST1aIqQ6OiHVHt6S0DWqHfg +n2Du0DA7YjNSpLacHFycz3CP7WVEm3aXjYa3hyjX/vUnN+KoeVTuPL1fYxpOnihl +Mo+0bvALEsKrelbErcxWpfi/5wYNk7Cl3tNmt+uBON43WNIz60wjKPX54mZYH3Hg0H2o3yZh/ula0ThdIMJ/ZQ== +Mo+0bvALEsKrelbErcxWpdx8fHv4ofXPZuoNIhLdJuneSTS752QV95WJYOSMPumWztWyMZs7Nk6qd1eygS1UOQ== +c5IxkYKUSbSDwct6vRlq5byNjttqWKrl6pQD7obLoFH1n4hQQN4L6J4qGdvXZrZuSIolg/PvyrrNN3cAwDRMsQ== +2NXrAbLln4pcE1BnFEF3EqFgB5lC7hgbEdSrJgVH2Q6M0nL8ocKtkoRKxCZrDP99 +L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk= +juJdqsykhOSvrjoodiEp93Hsa/iwzL5LF/9dyqXpyX2xlpLk1Gj9JMQOc2/aEr2512B5LUufYJ1XyixXjBNUVg== +fNA+s8KEa0Gy0us8PkktW1uakXpccvuyNacjHqe9a1de0PLA3Lfx/4DGb3djRrvuTxv67A3Rnwb5YHI2AqyV9Q== +PA17vimSRbG7tn861DNLBtjB0ul27mGcfjS4S+qBjlb5atkTuhrMpexda4gU00Ke5HBwV6PAc7oy61d8lCcXTDPCfj0jMO2dj03BONtTXjE= +1u+XjG/2+GSQRv6EzCaWRQ== +vqpebObTA425GjZtEgRzeKg1TcRBMIqhtwNxfjCXb3I= +mkMqvMwtS246VTZkcVlxvakn2CWC5fgOXjfig9t8ZOOKmCYcPWyiJrM2q1YTbzaY +n2Du0DA7YjNSpLacHFycz3CP7WVEm3aXjYa3hyjX/vUnN+KoeVTuPL1fYxpOnihl +Mo+0bvALEsKrelbErcxWpfi/5wYNk7Cl3tNmt+uBON43WNIz60wjKPX54mZYH3Hg0H2o3yZh/ula0ThdIMJ/ZQ== +Mo+0bvALEsKrelbErcxWpdx8fHv4ofXPZuoNIhLdJuneSTS752QV95WJYOSMPumWztWyMZs7Nk6qd1eygS1UOQ== +juJdqsykhOSvrjoodiEp93Hsa/iwzL5LF/9dyqXpyX2xlpLk1Gj9JMQOc2/aEr2512B5LUufYJ1XyixXjBNUVg== +fNA+s8KEa0Gy0us8PkktW1uakXpccvuyNacjHqe9a1de0PLA3Lfx/4DGb3djRrvuTxv67A3Rnwb5YHI2AqyV9Q== +b4OJVZe8QyIpjuTpKXDL9A== +sv7/Gn/EIUd1qd100SlAADmdMfLVzff/ds6lE3DYs60G0IRkQmqzuKCsitneaOUh42BtWRgy51WKl3wVIkqWTQ== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk= +cbvASHjpysrsjdY5RctXmFfq49X2GjJVQNVD9teGxwE= +cbvASHjpysrsjdY5RctXmN8xPh59X6dfQkAGafjIwQ/JO0RC5eZ+d8CYwUqZsrzYcHxd6s1DfQxzGCiFS5l7LQ== +oFRTJn+vQIW52ldmmXvX7SFq/R2PvVLOSjCgrER/+1ezS6CHmL/AryMKkk2VZX9TerI3Ejx/trvS3Q9Atgf/GLDC8QW1uxdpqhozEm0HSZo= +xt5C7mdEDz2/JDjO+ejBsP4W8f4f2ZIZVN5/SpPOOuQcRVeczva30Vo/fzex6J2uu0rGD3FHPjXzKGEoA+wfaK/lI3mo4/iO+G8+oG6gcVWMbZDoy6LXIywTQ/WT3A1tTlfYetI/Wa9mg9rSMNxyuRLZbOk+XDL18/+yxiYRVEY= +oFRTJn+vQIW52ldmmXvX7SFq/R2PvVLOSjCgrER/+1d/JzVXV9rI2vsjv+weSUCcmTye8Y8TItbmxBdGz92+BnDQx/jOcBYYAKZL5kL2oGY= +L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk= +PA17vimSRbG7tn861DNLBtjB0ul27mGcfjS4S+qBjlb5atkTuhrMpexda4gU00Ke3GjW1XqseD/MxG/WOJwzU76Gizg5wATyLx1EeZvwrQw= +1u+XjG/2+GSQRv6EzCaWRQ== +EZhRblE+jJIBo34uejixWP0r0RlXrUFCVz0+1fo2ZAE= +csoFqOxWYy7ouPTWoQaLdnaFuHtfElQV/4goJW5WhfA6dTubqpn/6XqsRAsueV2O +5ybqwXbYZHutgsWYoSLQvoL5cXDCA6u5W3fIXRKORs2FHNWAnv2UwAoj7p9pJP75 +n2Du0DA7YjNSpLacHFycz3CP7WVEm3aXjYa3hyjX/vUnN+KoeVTuPL1fYxpOnihl +Mo+0bvALEsKrelbErcxWpfi/5wYNk7Cl3tNmt+uBON43WNIz60wjKPX54mZYH3Hg0H2o3yZh/ula0ThdIMJ/ZQ== +Mo+0bvALEsKrelbErcxWpdx8fHv4ofXPZuoNIhLdJuneSTS752QV95WJYOSMPumWztWyMZs7Nk6qd1eygS1UOQ== +juJdqsykhOSvrjoodiEp93Hsa/iwzL5LF/9dyqXpyX2xlpLk1Gj9JMQOc2/aEr2512B5LUufYJ1XyixXjBNUVg== +fNA+s8KEa0Gy0us8PkktW1uakXpccvuyNacjHqe9a1de0PLA3Lfx/4DGb3djRrvuTxv67A3Rnwb5YHI2AqyV9Q== +GdtTSDJSToysp+tWyVstgx0+jVfPZGcCO1zjidU7WlfAOHspewxs3TY5YVdNSxzO784QsspYaBz30M0j4itOW6U4gZU1XhTPWC+X2w8FgS35mZOuXKy0tkCVdaqov93rTFynrx2cJtWxH/7+tsq2gA== +UFqj1DupeWmtd5xz8xEzn5FkHP+WNMugEY0ws9dpcPsEwXcOg3lrZPZWMRTcctBOu/99HuBLcHszBTcdNJStaQ== +PA17vimSRbG7tn861DNLBtjB0ul27mGcfjS4S+qBjlb5atkTuhrMpexda4gU00KetJ7YxbT2Ke2Ahg20sWE0zNiHKPKp1M85sMvsK0i1HOU= +1u+XjG/2+GSQRv6EzCaWRQ== +YJ4JGstLBrjsLhFvWMqqOs7ljD8z4xTG8gc9ZDuXoV0= +tRsGtRpyd3NaPukECjTZg3hNDScnsgtniY3jTn/FQkgb9AciCC6AWK90AROTouMfNfwogCllNtrvLYv/ohfuww== +b4OJVZe8QyIpjuTpKXDL9A== +p9oVsBgcyiBMjDb8CcPOqOqpzx/tm9sFWyAV0717NUAcEeYqb0APxYfdrJL2B82m +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +xWoGNWjKGPfI4gq8aHoTfGQ/0WcRe3RGVCTVpOXBGFfK9oq2wJG0ManURHPRgoR35wHRO73LgxbQYbAUvXjJbQ== +p9oVsBgcyiBMjDb8CcPOqOqpzx/tm9sFWyAV0717NUAcEeYqb0APxYfdrJL2B82m +d0CXrv/LF0oZZvxaIzp0YNe8V4naHoVuY5ZOhZwZAi+CU9TDUBed7auu54C+Ks5hv03XlcehOyt1wCWwHvl6xQ== +YabJCT93a3/IohXaIkr3oVnUkde6iwefyaZQxdFclFpk/CBhvdo2LD14OJ5a/aKYGqwtj5bEYBqakHdcLBRXFg== +vxZRPtvgnx9GXrXB4G/UMyf+Kd1+OunvObkPcMks4F8= +kTxSrTsHul564SUUCXqFGqnCBJ/pZnZuuEzwKq5lWTMnr/pICNQxZbpETQLymx28 +BPpXO4yg5jB/geTB0jxHP9wwlicJFHphvMFQKddQ4Rsq5BW04q+dIcl7qk/g+aEK +37cKTYNVSLcMbZNWkFyw/bkqfeV4AxxhISHXKAliyoVwea2SzWHXTJiiTNKCXTxF75HulVP5xL1nq+Qz2vJfOJeGEYdoF/AIaU93PAzvRalzzks/fjs5/0XEEDQviG2o +Ba8y5vDkcldg6GNO9PPGOWYojGh05hg3eE372iUV7B0= +g9QUrDNfkepVhzTckf1czKDis40wYHzT7NkITn2PT4puZ8csXgDB93OSOQMohJ8MU2FDNN0jsT2SjEy57u4rhlYCNIjjyvyfW+JsLiYK2SgrUj+w/yOD/IHdttHNynAiG7sUzTztXEF7SmiHDVvmS4DQE+Ug1u2/z0KozHT5mXrDVSAaNrU0YK8ICVH6HCzw +53pmrPesT9qAhqxIHDNU40O01bUmTAKPQq4gLvky8PY+lrS+4rOHZ7nXjUbuxI+Aogqr6+RdxQWgZTFOOSEycg== +/0ULpLqgTvInFD0r5hHANguwJe0DtrG/oJwRJkc/Bd4qZd8TpWmTyCFdjYXANp/k +txLhhK3LqdC7OTooW0Mlz4GFqz6LzbvDy99pHBQEQ40a2cY8nrJ6La4VhXvL3Boe4Awu7qH72hlAUcQbzktaOOAAlo5l6Rl2k4yw3ndEz2A= +2n6OKffnpctHA9iLBdLftCjTYTB0+MA2s3UeGpHkjmmd18Pz2OnNoeI3qHoxFA24 +32pdC9DD05OE2l0oXazDFAy/+2z+GSJU4Tvn6nr5ymNE8T4+MwqJlcj+UtdgrwnhPZFbP3Ie/lhKQDVRXFcQkNowU/FOvKAjvpveSX0UVePp39/hXQw80/Y+fNs5+291UZGnduOcOTprruxrXY+KlQTlTCwdMg9zWdhAmYdbeTbd+fddlpQOBFG4rlhvhDnXtkAQHgXgoduqV6NO+Zkv3g== +1u+XjG/2+GSQRv6EzCaWRQ== +i3q0rc3DNSm9lJpIhv//MavLcKoA5wmJ945KuJzmuHo= +ZJ2ZYiWQdJUCWkE//4r0NCfNnqY9G3Q73P2f4Aro4jqCGj/7LLVvGr332+QukBSaBYdWKH+pjGC/xyJBWz99kw== +hBWY30cr8RshUe3F5HK1ccg4rT1kIv0AJhkTC98pA0vrCtuVPwiWWBgWi7dSFi3S +DOZzACM3rMJz/5HoVkWhTAJRYjTgu8bbUR8ehtOm5U/d4TyAg0mg3WpauSeRow8Y +32pdC9DD05OE2l0oXazDFJZeI0y+qWsI0YMRMpHa7RTnKgfplydp2n5wgWdV/pIF0Ftd3ReUh5GWOp07BgnNu80faUNzKCfXokt9yQivK+mvyfjwfw7Ch9kMg6kj61x915ce/BnFvY5nAh2PhmFazdnDfH3RSEA0VEFEFACKroM= +l2FJPs4YkAmmok1ulDRuSA== +32pdC9DD05OE2l0oXazDFJU3nCYoYJj+nlJTs9Z5eqc8W0KCpCnIGzSYVI8k24tkdtksbXboLx73K9kAfePh8w== +pVMCrZ7PAAHymZ70WROm/0U0yYoo7XCh9SJpAqZIAe7JWLjXcOW5OehWzz8mGPHHLHmDnXP/8yOBcDuWqjRxug== +VmVrGQo2zRokW/ZuO9bN68p3CvferpZDfUPfIejFq4dgtDRbn9+EC+U13hcVN4xfDuCE2jMhnt6Ks7bjLO652A== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67qZ2KUV1f4tx00lgYmOjmGe5kSqLAQ88sGlFafMPtA6 +1u+XjG/2+GSQRv6EzCaWRQ== +aGle5statgVMNf7rj3OselC6TIMMyoWDroQ0LnUnNBIeqLqsE+/ACbWNOgr3XHbZ +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +jEPV0YXDCqWCap/VHplvwQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6+ugdaK72k5FDIN67ApiHeWC27mh8vSSvqLTkawZzG41 +VmVrGQo2zRokW/ZuO9bN62GGKQt2S9DKmxGER0w1v26K7kSynx75p86xpYrJ5cQA +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +jEPV0YXDCqWCap/VHplvwQ== +b4OJVZe8QyIpjuTpKXDL9A== +1V2v8QerKOmubvSxgB4eTB+P6Y06+m1KdyBFjy0y0qtDlVzyAzDndoVazuBea8ILSnWO8RuK5nvd0GXel7i7OVWCgkT7TP2NsLLVZ4mNqCA= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSo1ZAQ1FHnoPT0QBpT+y//EDQqEyU2+LLb0Qw50wZ849Ngf7YNt+KN1kLhyxEe4jPeWz9lyneG7AF4gK7wF4ng +89eRayX3G8tCX3FDU5IcjW6dZBKA/+IhDK3LhrTo6ym5cKSsa13rNj/ij10+KPkA +VmVrGQo2zRokW/ZuO9bN65BnQ8umChPy/sCxoyCmwA6LqaszzxrGnLukmnEqUv/u7z/U4QL/EzWxOm9svW+RH7G3eS2YkqUDqEJMrocMpMjyNUyyau2nVUKm9ERSLnjZisO5lnjNt+iGn0k1+yVZuw== +VmVrGQo2zRokW/ZuO9bN63A3xkU1lR5ZG1fBlpSh1c39T/3k+G4riKOjuqtHOnHMvvZvWkuOS51CC6qk4z5jnw== +89eRayX3G8tCX3FDU5IcjZio0hoGCtUO5KLqNdaB3UvURpBSOFsH1Ry2hhlpu2a3 +VmVrGQo2zRokW/ZuO9bN67wF+ldYt2W5ABEV4Jd5XnXJ5RbZPS+QKHdMwhNEDj31W6sIK4FsnIkLRJ3T5iAlNTtJ+RADG0QxCaWthy51S4e3LMPtYqvYmAIJIceoifGgj3qzjx5Mineh49VklXh8fw== +VmVrGQo2zRokW/ZuO9bN63A3xkU1lR5ZG1fBlpSh1c2HA0D8g9S9SMlaYHmeizpvWd1tnv8EXrkDAlHSfl2K/A== +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+mxGr/S7GRYgYWJrDIVeTUEC7H9MrmNwE6ENdkNQNBMlesKu21GOlwV6/lxOt+J9huukyIEOSNsWSANtpaf6kTA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXrx0RH74rzP+DMEHwpOwZ6d7bNaVWJEBrv6ZnZvyvHTAUE3baFmml5mUO5DXOnAC0= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5hqfprx+1n+iB1xwtsTjMm8w== +1u+XjG/2+GSQRv6EzCaWRQ== +vu8LS8RURN7sv+M4P2dSXuO5tOb5QA7mvTF0Q9xyHHoLcawyWgXdBSleKQcT19Gp +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +WHkOzVx7seuLmxs5Hu+l/gGhdEQDXfh3xxE5uPrZ9HkTaj8ei4z/I4Hs0fkWoW71FZtVCvpoLhrkg0DQhl0+zw== +NJQ+lum/oS33aJfKUum6ZKxq4v9IIG63s8wgAJpxyKKM3caX3PQBfK4hpVfelFefX0ha1REi5kkFRq4UVngkqz5Lga8eCOtZdzGW7Jx1e30= +PozHY517ojvTteRYsQkDRQ== +l2FJPs4YkAmmok1ulDRuSA== +NJQ+lum/oS33aJfKUum6ZGgG7m/Vgf6+MaLfXHjBYbG/boj/5Yf8rziyiMSNVNgO +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6t++CgSX8cAeNQyz47sn+GYtv0wtQ5PkRbw7visX9vww== +1u+XjG/2+GSQRv6EzCaWRQ== +fa6pp9hhUaHk9Koiu9s2ccIwXt1dcoJZB0wflEr0rwQ= +5GHtWZO1ZyuzhP6BpZ6HURY9BsEKvY5DDw7ChNy1rP2CD3rFiV2pVl9EEquVbu+K70cLSEgxArw1fmU6IVnzMw== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +m5EI3+BXxkQEYKYaxORD+qnAwk1i08GBypJtuz97N8s= +gZ7BoIAABc+I8g/rYzq+fz57S21B9Wyve1/Agtg7QmQj09si6ROVLmG2qVyyY9lL +1V2v8QerKOmubvSxgB4eTFLJVKokCn/n7cBzRCyjPmemCDNpavHplLKoYC5ZrVB11XPhYbqw8m0ksUK1fSNNJw== +1DFND+jtCuxjwXKqEjfnlD8JflvVZY16BAKNx0Ys8oh960PCawXn1DhrDIgKML2IvSaCy2FdK4vEawkKvbv+BQ== +1DFND+jtCuxjwXKqEjfnlFHSx+D7aFIV7RDIckCpWqiZCZOA+pgT/6kaWqauDZPr +wlLHv6kT3Q/RmtMBN4nDAe+nVsDSSjET98LLrywMOlg= +VmVrGQo2zRokW/ZuO9bN68umjQbqBp13Y8gxV9ie09H26ZhdYobd0e/IELC7PrX5Z4UEiAF2Lx0Kz2qNM9xtPg9hRCHPWmuRACx1FUjOq44= +VmVrGQo2zRokW/ZuO9bN61VUR3BEZJhw9HmBnyL9cM0= +VmVrGQo2zRokW/ZuO9bN68B6yBwdveCrEnL3eIPPl54HCMNcd7dZQ6V1Asu/4VOz +VmVrGQo2zRokW/ZuO9bN61m5Mxpu5OrMp8lEKzTstyW5t/Bxrf5FCuK09Q+Qt3Xb6zAMFKTKqMZ2bc8mvxMoYA== +VmVrGQo2zRokW/ZuO9bN635+naSBCfSoIFNU/mkWfEWvjx4qi995kng/6+ZHbkng+TzkNmb0iPG4cwuUVQI4ZKE+pWqvwN56kWvRb+8GUHM= +VmVrGQo2zRokW/ZuO9bN635+naSBCfSoIFNU/mkWfEXU6i2AICYZTQUe3CuP+KX8Hz39JbYJuMJN0Ey4/Kag6hZfrxZVopZx5Vy2glJNcGhvSsoSefu97SdpMTMYIKLD +VmVrGQo2zRokW/ZuO9bN6x3CEv6jZSVeTORx9EyCuuzE3c5Og66hLkfYnT65i/W/OHaxtXp/DcinMIa68QkDOAXzT1tpGn/G/1g004E8vTAvX10EUKwrhYAHwUOqjpUn7y/swzdApkXHDcJCO4M/ug== +VmVrGQo2zRokW/ZuO9bN6+qJ7ZwQdyiVmEJTiPQFIpKxvidM6dYKv4J64fba+Rv7 +VmVrGQo2zRokW/ZuO9bN6+GUsXdTMrUNGRoRF00pmbi60tgVb+d/2FD5rl281dqBNBRi5pQgGepTSMp/mK0IQb6K/t8paD6dYTpkLyU/ixxO4Yjcrsxdty7NT2h70NXG +VmVrGQo2zRokW/ZuO9bN6xEF47qKkxcGSQA8ZWEPDm4VsVyapvAq9bi5NYznw6hBoE8A28CROx2B7L7NPcqnMKPQSW9Q2meFjEGt590eZ8ikIT161qE/eWvIkfSbbh17 +VmVrGQo2zRokW/ZuO9bN6wdLYDV40oCO3TihSbGXJrZpMrfTemTStPcnvjmgnZdZNGjQQU3YFlc0N5lgKjM+sVfOqXPeo8ffNXtAXn8U6aw= +VmVrGQo2zRokW/ZuO9bN6zAPD3Xtk2YvfiqYLDohteTNRe52aljpAXhFzxSTpy34 +VmVrGQo2zRokW/ZuO9bN62u0ieXkBjpVkLjCpl9rmj10e3NBgGxfj0GYAQ/k87UY +VmVrGQo2zRokW/ZuO9bN67NTMXohKhLE0wOxn9dpXIG/8QEXIQaD4WsCbsRYT70j +VmVrGQo2zRokW/ZuO9bN61MEyHBjyzCNpNUpxJmXAqGns7E0FYe0ozkLUuME1Deh +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +8539W5My73ns3Q/u5rchhsyrOAomtnoexAKSg0NQa7s= +JNBOjio0wSMJSstABnh/zOiQQTikw5fWysUhLsbb18DmnkrHXpVy4xzmOQf8bph7 +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +QKClQ4XOaAjfVBfstd1swg== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN62PrQ58ZmOS+taM0eglCETGQ+8yO6ZBXlg+K406izWf3 +VmVrGQo2zRokW/ZuO9bN666LtvcJ+EKliohMh9tjJiFYAV0/gF3Crr6YBKyInU1P +VmVrGQo2zRokW/ZuO9bN6yLfMPEhyISD+REiXhi+kj5tXhWAiEhsZ3LNE2aBEOBX +VmVrGQo2zRokW/ZuO9bN6w0ttCFjDKrgfwVo/waQ6MI4G7C0z84Mo3p/n+iP2Czx +1u+XjG/2+GSQRv6EzCaWRQ== +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +09UuO0tw3kJ5J618hVggcQ== +QKClQ4XOaAjfVBfstd1swg== +b4OJVZe8QyIpjuTpKXDL9A== +1V2v8QerKOmubvSxgB4eTB+P6Y06+m1KdyBFjy0y0qt35NiD23nMDnboayOgm+qxRX16P6c61gdhdoE8cyqp434kBVVyEegJqMSq/9+j7qRYBzbpDTJclNP9AbLFWP/aYoHnZdjB3tUKsiTOYaWMRDDiKUJnEa0qsXpZMb2GdgjMPYZ2akb991jFm+56P1iO +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSuY4P/AlnqUWfNJ3Nv+yymjs8C+eB+Hn8dWS7DzR5wCEVi+pzzp4DxubDifplcmTz8xv5V7wP5rejGKOKJFZz4 +k1ktu/l6HFULD7Vyr8Cv0Dp/BLIuC7jc8lO034+ASSh12djXtIbpBUNxeWBNIvYRsNqqiKBD3YtyfeT5KRCKiA== +1D18KWr2hdVsBcdZx1OaPdYttuuMd+Wlneqh+fYf8tRS06YfvvjVykA0Ly3MBJZu +VmVrGQo2zRokW/ZuO9bN61b/OIj1n5I/egQV/irCI9ZF51EgBOVnJEOvqc/A4gWV +VmVrGQo2zRokW/ZuO9bN63OlqVFuSrE4LBwNUw3bkoDOavUodkFgOuITmFHLjrHA +VmVrGQo2zRokW/ZuO9bN61b/OIj1n5I/egQV/irCI9ZNKsuaBZPcCMLzgnnpuw9keZNTNp6iIMOAdW/dgsNpMefz9HvyioBehfZZagurPe9ROWnsKmrd366nsKzD2VyqHbZr56qX+EZbTpIgBMo8bfcma1+HcAM+UztJ8J2dcrZEO3hwMwyAZbNd+XHq/TJZ +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN60q1SD2rBZYBmqTssi+cBVTFolfijy7G1qUgw5+55fOlizRwtfrPbLo+QD4LQz/aXQ== +VmVrGQo2zRokW/ZuO9bN65ab2Motzh1/u4tD7gBJGx8gUZXzrg55jEQ36akEJ1ey +VmVrGQo2zRokW/ZuO9bN61b/OIj1n5I/egQV/irCI9YXQ8qfav5RO2NqR8STwJts +VmVrGQo2zRokW/ZuO9bN694CKy7hg/jAQggMQ3cflJfUCrGGd9dIzrTC0FxwfEf4 +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+mIkOb+TCfJvH/w7nenmNQi1zWK57JebtAOzfMq0wpoFGdgaOOSewO4tjZybH6SMo4= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXrx0RH74rzP+DMEHwpOwZ6d7bNaVWJEBrv6ZnZvyvHTAUE3baFmml5mUO5DXOnAC0= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5hqfprx+1n+iB1xwtsTjMm8w== +1u+XjG/2+GSQRv6EzCaWRQ== +VJqiSZUHAadJPm6IbO3XLPzVLLft+A1arsi3RybmnvA= +JEnsSsP7ogTJg1OXQ2YdoQg+5yBMz0QMX9uowke0S1ZHptfxrPs60KFAe5xogoyb +s0qLHwvmLw5nYPphiA6IN9NAVh47aVFuXfLEn+2tA/+IR0o4fvmeiO6qsseNVLAaEtREzItWxPogUFQTRVlGyQ== +W3A5Dt+8AxWSKsTYeXZoLzB4TUiScS3D8yuLKBmm3AwWtYcxV6f46K0T0Egvi5WKZ+IFtEVQSkI1R2/103kQoAd9RN+l2jYL5AuJdiBc8U4= +1S1xjYS0s+Ph1PyvECx3QMdMFDZ5B3+BEK8kyCfI1RM= +VmVrGQo2zRokW/ZuO9bN63KvuXi+Rau/UofskUTLsKQ= +VmVrGQo2zRokW/ZuO9bN63pkeY7E1wCFlSgT4To6rBxvA90+4fs1yFGrKqWxjhoI +VmVrGQo2zRokW/ZuO9bN69mxdR6fpagkC1QpuDN633U= +VmVrGQo2zRokW/ZuO9bN61jw9zMkfm0nvQi9tveiWV0= +VmVrGQo2zRokW/ZuO9bN67PbAfk7/vgNjya2BB9mgLo= +VmVrGQo2zRokW/ZuO9bN6ztX+cJqy4yqv63JnfA/mqI= +VmVrGQo2zRokW/ZuO9bN60jLKcJEMj36X6NZvvwxF/XvqrmJF3W3RArdDHrkgZZ6 +VmVrGQo2zRokW/ZuO9bN6zskyREk1B9SpIIVpfE36zU= +VmVrGQo2zRokW/ZuO9bN63leNhHVk6qBYEqAElCrxOXVPHIpJ8U4k7mou4tbIR/7 +VmVrGQo2zRokW/ZuO9bN68tloVdndHDn6gJZRayJG+k= +VmVrGQo2zRokW/ZuO9bN617eR9WvYqaE7y/ppCzNL1570CCRJc668VdVufs+tRvC +VmVrGQo2zRokW/ZuO9bN6x59qEj9owkguOGj8QrHQSLi1Q/1C75rARTXu4TGt8DP +VmVrGQo2zRokW/ZuO9bN6+3Qehls6zipcbnAYG52vS64qsWF7n58MJKONK4HivTE6Z0+CcayS8zXD9YppbFZ+/J2GrNDCfGEkmX6/AnIs/dP1CmXS8H+LlZmD+d0IrG3 +VmVrGQo2zRokW/ZuO9bN67biU+BUR7/d7BkQj4RgpT8E7BfCa3N4KcxGo7ivT2Hg +YH231WTDnzQG3bFilBiqIg== +lOh2GtzHjjMM8E9J40AuOcPN2eZCahfvScF49QHmlNXYE5PQWq+WYPigt3dG9DzvOjcs8WVNkHQ6P1pZps2Ehg== +UbJuac0dxLiN+5ocS3w2vTpUyzdtCQ6EQJg8V3275Z+ztU2/bGujfta5s3IG3yXD +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +aaMaQb4hNG5aY797TkrqCWfiH67nNDOngFCD9eKQBnU= +VEyEXJTNTVvV1TGW5V2yEJROi80UtMZ4Bfk9N1lPrUw= +s0qLHwvmLw5nYPphiA6IN9NAVh47aVFuXfLEn+2tA/+IR0o4fvmeiO6qsseNVLAaEtREzItWxPogUFQTRVlGyQ== +asVRHT1bqj8y+BH3ysf47YxzfxL+NexJMVBUNP6vrnufWCZJGD7MwYVGA8oeYsgFpKnSmDtvp3y/fiLSpk98IcZD1DZf0DjJ3SyzH+qyYjM= +P5FSiL1+Pqt0jNjlHNtw3PUsQ1HQjY3Zc9yP2+2FW8Y= +SHIgz36q1FYBLUgTHpJzWwvMCDfalwhI+vhlVkKru41zqEPlk9JS5loV/C9k/6Io +1u+XjG/2+GSQRv6EzCaWRQ== +hwrvMIGc4R8Rz+DcReSgt3VO4XbBq4/Dc26Csib6yT0= +uTEK8Ng11d3ix2pA+DD/aYq6iiRaSOVPA0xTeOGnO8KEq+qETa/PY3c3TUpiGOuA +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX4HVccALfPGDxT2Bl+uK54CjgC4dHORIP0UG1n3cGiVq2uzn3cFb+KWWE+PC2KoT4= +MzOj4ZiBOJDNVP8vi/lFQNI6cdfRYnHbX7jIkalGpxc= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmU8ccdG/y4bwpPH8ZZkAG5h0Y5XPh3OlhnUXvGEUQ2KWe5qKrCw4xrfZpU/bXhFhZBlnMW+ZvqnU4LLYDO0/H6M +3alpplMHMPiWRaABg96vtFa0DPujF6W95o8WfJVesvcYr94rSBkhGXG+3RqspvvAasMhJkqh0lRb4WSYwFcQ/w== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz7Jkrj3Ngyu2cYVDtwlmqFHAh1dGKymlbb0LFY4063fRnZ236zioiutx2EgqM7OqP4= diff --git a/class_v2/logsModelV2/panelModel.py b/class_v2/logsModelV2/panelModel.py new file mode 100644 index 00000000..4f9c9986 --- /dev/null +++ b/class_v2/logsModelV2/panelModel.py @@ -0,0 +1,321 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: cjxin +#------------------------------------------------------------------- + +#------------------------------ +# 面板日志类 +#------------------------------ + +import os,re,json,time +from logsModel.base import logsBase +import public,db +from html import unescape,escape + +class main(logsBase): + + def __init__(self): + pass + + + def get_logs_info(self,args): + ''' + @name 获取分类日志信息 + ''' + data = public.M('logs').query(''' + select type,count(id) as 'count' from logs + group by type + order by count(id) desc + ''') + result = [] + for arrs in data: + item = {} + if not arrs: continue + + item['count'] = arrs[1] + item['type'] = arrs[0] + result.append(item) + public.set_module_logs('get_logs_info','get_logs_info') + return result + + def get_logs_bytype(self,args): + """ + @name 根据类型获取日志 + @param args.type 日志类型 + """ + p,limit = 1,20 + if 'p' in args: p = int(args.p) + if 'limit' in args: limit = int(args.limit) + + stype = args.stype + search = '[' + str(args.search) + ']' + + where = "type=? and log like ? " + + count = public.M('logs').where(where,(stype,'%'+search+'%')).count() + data = public.get_page(count,p,limit) + data['data'] = public.M('logs').where(where,(stype,'%'+search+'%')).limit('{},{}'.format(data['shift'], data['row'])).order('id desc').select() + + return data + + + def __get_panel_dirs(self): + ''' + @name 获取面板日志目录 + ''' + dirs = [] + for filename in os.listdir('{}/logs/request'.format(public.get_panel_path())): + if filename.find('.json') != -1: + dirs.append(filename) + + dirs = sorted(dirs,reverse=True) + return dirs + + + + def get_panel_log(self,get): + """ + @name 获取面板日志 + """ + p,limit,search = 1,20,'' + if 'p' in get: p = int(get.p) + if 'limit' in get: limit = int(get.limit) + if 'search' in get: search = get.search + + find_idx = 0 + log_list = [] + dirs = self.__get_panel_dirs() + for filename in dirs: + log_path = '{}/logs/request/{}'.format(public.get_panel_path(),filename) + if not os.path.exists(log_path): #文件不存在 + continue + + if len(log_list) >= limit: + break + + p_num = 0 #分页计数器 + next_file = False + while not next_file: + if len(log_list) >= limit: + break + p_num += 1 + result = self.GetNumLines(log_path,10001,p_num).split('\r\n') + if len(result) < 10000: + next_file = True + result.reverse() + for _line in result: + if not _line: continue + if len(log_list) >= limit: + break + + try: + if self.find_line_str(_line,search): + find_idx += 1 + + if find_idx > (p-1) * limit: + + info = json.loads(unescape(_line)) + for key in info: + if isinstance(info[key],str): + info[key] = escape(info[key]) + + info['address'] = info['ip'].split(':')[0] + log_list.append(info) + except:pass + + return public.return_area(log_list,'address') + + def get_panel_error_logs(self,get): + ''' + @name 获取面板错误日志 + ''' + search = '' + if 'search' in get: + search = get.search + filename = '{}/logs/error.log'.format(public.get_panel_path()) + if not os.path.exists(filename): + return public.returnMsg(False,'No error log') + + res = {} + res['data'] = public.xssdecode(self.GetNumLines(filename,2000,1,search)) + res['data'].reverse() + return res + + + def __get_ftp_log_files(self,path): + """ + @name 获取FTP日志文件列表 + @param path 日志文件路径 + @return list + """ + file_list = [] + if os.path.exists(path): + for filename in os.listdir(path): + if filename.find('.log') == -1: continue + file_list.append('{}/{}'.format(path,filename)) + + file_list = sorted(file_list,reverse=True) + return file_list + + def get_ftp_logs(self,get): + """ + @name 获取ftp日志 + """ + + p,limit,search,username = 1,500,'','' + if 'p' in get: p = int(get.p) + if 'limit' in get: limit = int(get.limit) + if 'search' in get: search = get.search + if 'username' in get: username = get.username + + find_idx = 0 + ip_list = [] + log_list = [] + dirs = self.__get_ftp_log_files('{}/ftpServer/Logs'.format(public.get_soft_path())) + for log_path in dirs: + + if not os.path.exists(log_path): continue + if len(log_list) >= limit: break + + p_num = 0 #分页计数器 + next_file = False + while not next_file: + if len(log_list) >= limit: + break + p_num += 1 + result = self.GetNumLines(log_path,10001,p_num).split('\r\n') + if len(result) < 10000: + next_file = True + result.reverse() + for _line in result: + if not _line.strip(): continue + if len(log_list) >= limit: + break + try: + if self.find_line_str(_line,search): + #根据用户名查找 + if username and not re.search(r'-\s+({})\s+\('.format(username),_line): + continue + + find_idx += 1 + if find_idx > (p-1) * limit: + #获取ip归属地 + for _ip in public.get_line_ips(_line): + if not _ip in ip_list: ip_list.append(_ip) + + info = escape(_line) + log_list.append(info) + except:pass + + return self.return_line_area(log_list,ip_list) + + + #取慢日志 + def get_slow_logs(self,get): + ''' + @name 获取慢日志 + @get.search 搜索关键字 + ''' + search,p,limit = '',1,1000 + if 'search' in get: search = get.search + if 'limit' in get: limit = get.limit + + my_info = public.get_mysql_info() + if not my_info['datadir']: + return public.returnMsg(False,'MySQL is not installed!') + + path = my_info['datadir'] + '/mysql-slow.log' + if not os.path.exists(path): + return public.returnMsg(False,'Log file does not exist!') + # mysql慢日志有顺序问题,倒序显示不利于排查问题 + return public.returnMsg(True, public.xsssec(public.GetNumLines(path, limit))) + + # find_idx = 0 + # p_num = 0 #分页计数器 + # next_file = False + # log_list = [] + # while not next_file: + # if len(log_list) >= limit: + # break + # p_num += 1 + # result = self.GetNumLines(path,10001,p_num).replace('\r\n','\n').split('\n') + # if len(result) < 10000: + # next_file = True + # result.reverse() + + # for _line in result: + # if not _line: continue + # if len(log_list) >= limit: + # break + + # try: + # if self.find_line_str(_line,search): + # find_idx += 1 + # if find_idx > (p-1) * limit: + # info = escape(_line) + # log_list.append(info) + # except:pass + # return log_list + + def IP_geolocation(self, get): + ''' + @name 列出所有IP及其归属地 + @return list {ip: {ip: ip_address, operation_num: 12 ,info: 归属地}, ...] + ''' + + result = dict() + + data = public.M('logs').query(''' + select * from logs + ''') + for arrs in data: + if not arrs: continue + end = 0 + # 获得IP的尾后索引 + for ch in arrs[2]: + if ch.isnumeric() or ch == '.': + end += 1 + else: + break + + ip_addr = arrs[2][0:end] + + if ip_addr: + if result.get(ip_addr) != None: + result[ip_addr]["operation_num"] = result[ip_addr]["operation_num"] + 1 + else: + result[ip_addr] = {"ip":ip_addr,"operation_num":1, "info":None} + + return_list = [] + + for k in result: + info = public.get_free_ip_info(k) + result[k]["info"] = info["info"] + return_list.append(result[k]) + + return return_list + + def get_error_logs_by_search(self, args): + ''' + @name 根据搜索内容, 获取运行日志中的内容 + @args.search 匹配内容 + @return 匹配该内容的所有日志 + ''' + log_file_path = "{}/logs/error.log".format(public.get_panel_path()) + #return log_file_path + data = public.readFile(log_file_path) + if not data: + return None + data = data.split('\n') + result = [] + for line in data: + if args.search == None: + result.append(line) + elif args.search in line: + result.append(line) + + return result \ No newline at end of file diff --git a/class_v2/logsModelV2/siteModel.py b/class_v2/logsModelV2/siteModel.py new file mode 100644 index 00000000..7013e17f --- /dev/null +++ b/class_v2/logsModelV2/siteModel.py @@ -0,0 +1,110 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: cjxin +#------------------------------------------------------------------- + +#------------------------------ +# 面板日志类 +#------------------------------ + +import os,re,json,time +from logsModel.base import logsBase +import public,db +from html import unescape,escape + +class main(logsBase): + + def __init__(self): + self.serverType = public.get_webserver() + + + def __get_iis_log_files(self,path): + """ + @name 获取IIS日志文件列表 + @param path 日志文件路径 + @return list + """ + file_list = [] + if os.path.exists(path): + for filename in os.listdir(path): + if filename.find('.log') == -1: continue + file_list.append('{}/{}'.format(path,filename)) + + file_list = sorted(file_list,reverse=False) + return file_list + + def get_iis_logs(self,get): + """ + @name 获取IIS网站日志 + """ + + p,limit,search = 1,2000,'' + if 'p' in get: limit = int(get.p) + if 'limit' in get: limit = int(get.limit) + if 'search' in get: search = get.search + + import panelSite + site_obj = panelSite.panelSite() + data = site_obj.get_site_info(get.siteName) + if not data: + return public.returnMsg(False,'【{}】网站路径获取失败,请检查IIS是否存在此站点,如IIS不存在请通过面板删除此网站后重新创建.'.format(get.siteName)) + + log_path = '{}/wwwlogs/W3SVC{}'.format(public.get_soft_path(), data['id']) + file_list = self.__get_iis_log_files(log_path) + + find_idx = 0 + log_list = [] + for log_path in file_list: + if not os.path.exists(log_path): continue + if len(log_list) >= limit: break + + p_num = 0 #分页计数器 + next_file = False + while not next_file: + if len(log_list) >= limit: + break + p_num += 1 + result = self.GetNumLines(log_path,10001,p_num).split('\r\n') + if len(result) < 10000: + next_file = True + + for _line in result: + if not _line: continue + if len(log_list) >= limit: + break + + try: + if self.find_line_str(_line,search): + find_idx += 1 + if find_idx > (p-1) * limit: + info = escape(_line) + log_list.append(info) + except:pass + return log_list + + # 取网站日志 + def get_site_logs(self, get): + logPath = '' + if self.serverType == 'iis': + return self.get_iis_logs(get) + + elif self.serverType == 'apache': + logPath = self.setupPath + '/wwwlogs/' + get.siteName + '-access.log' + else: + logPath = self.setupPath + '/wwwlogs/' + get.siteName + '.log' + + data = {} + data['path'] = '' + data['path'] = os.path.dirname(logPath) + if os.path.exists(logPath): + data['status'] = True + data['msg'] = public.GetNumLines(logPath, 1000) + + return data + data['status'] = False + data['msg'] = 'log is empty' + return data diff --git a/class_v2/monitorModelV2/base.py b/class_v2/monitorModelV2/base.py new file mode 100644 index 00000000..b80b6a24 --- /dev/null +++ b/class_v2/monitorModelV2/base.py @@ -0,0 +1,16 @@ +# coding: utf-8 +import os, sys, time, json + +panelPath = '/www/server/panel' +os.chdir(panelPath) +if not panelPath + "/class/" in sys.path: + sys.path.insert(0, panelPath + "/class/") +if not panelPath + "/class_v2/" in sys.path: + sys.path.insert(0, panelPath + "/class_v2/") +import public, re + + +class monitorBase: + + def __init__(self): + pass diff --git a/class_v2/monitorModelV2/process_managementModel.py b/class_v2/monitorModelV2/process_managementModel.py new file mode 100644 index 00000000..31a4375f --- /dev/null +++ b/class_v2/monitorModelV2/process_managementModel.py @@ -0,0 +1,560 @@ +piG6BsMF31u4R4iA6R4SRA== +dTLQ8Wr3wPn7w6pte1B1YA== +SbQQ5SqO9QrBwZ9ObpMYLw== +aB1r/Hl41eeDYHjvKNsDN+fwXnHZ5cKnjNQNkvt811Q= +wiYVtfO/yzajW1Tv5z7hyA== +bbgfjwWjzPNOxnsxTb5hzQ== +1u+XjG/2+GSQRv6EzCaWRQ== +XIfdJ79nObMM+vyAmKbTmw== +DjrnV2ugcXpCQCRtr1AhjOqKcFaCI68w9rOW7RONBIZ5ibaGH1F5fN1ozytPQ6he +ic+a3PsSrb0KPm4AsF9vXtHZSKiYneN9Rh+9RnSNB9WK7HTAVn7HvWa3LZoeRP2P +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +GC18aI/weGPndT9J1gQkOXLINKNA+KXP+Dhe1VxZyWA= +HuLSKhYFyga2gCsAbUvtZHLP0BAfxDGimJZciagBXbE= +i5Pjb3wFWjgWhO/Oy7348x0KCcyi6IVF8qqP7v32njhzG+8BdTBQyqlCg3CRdkP+hkP1ySPJM/rBG9E6T++rIg== +rdSLxsl+Jo+9wGF1wR0xFfkiBqqHhxwN5kl0Lj1eqZJZBJ0Un+czbpqSSOCnL00rgrs3FzTKbr1MIAbXu6UVGg== +0SQnak5isFUZ7qBrHG5nONf0/iIYxo+ossW+sIeJPVqc06D+NOpjaE6Uqq4RDZ6D+X1V5oSWDCNufz1Z5NsruQ== +QoXjlU/yrq3LjBi536E1lAtUbyzoaPGqmdMscHtc82Eqnx3xaK7ND//Aee6pjueUoSkSd7Kn/GTgusZOZkazWr735TNoBy8E2i+TeT5QpBw= +EnTcTKmPyFkaGN9DqDzaSceq8uDBrg212ADWAEQ4IZ7RrbWtCNgCxsKxhOwBdNTIiDKGNrYM1guBNGIg+GP4oH9ldbTHxPoem41Xx3gcVH4= +VdrjM3tESbDpF9uC89FIBP9Gd1akXGqOvVf2yxPgOSm6+6n45/Opv1Gr2Z6wLcSiweXQCxy9SU/Ps8OhjacAOibd0MvWgOEF/Qj8Hg1OpD4= +23L+yCPrnnJuqxnn47seC6mWE/7wPUK89o8BQ4gkIOBjwQKKbOoe6bUJw6g30fgZLJKIywqZgDEqKLNb6Wk4ZA== +ls1D8gXPZvKpYcbhjFq/angELE3042eHnGYIIdOc3EqVbEv8X3JJ8d4kjYJRVOzknDfBdJPlavOFcLuc1C3RlQ== +Ucs0nOwLNKLYeguD5Ho6We236+pzwCXfjIf8zCbNBPUj8Trs/3tdQqbzpyL2y/A3nGNz3pdXfnPIeWgvZPJdJCQ3oPUcIKLTYx+hNA8fP3A= +1u+XjG/2+GSQRv6EzCaWRQ== +PdlH+lu7jNu2OvYS8I8SIGv8UV7r+RCWsVIOF9/nLLA= +EnTcTKmPyFkaGN9DqDzaSeIxT+jPdlkzsjUgVGeo67M= +lfXDVmh5N4vkcCpP16rRNU+nS75ACFRPief37RRqVEQ= +QoXjlU/yrq3LjBi536E1lNTH28NopdOB61QQlHyGFcA= +UrqM+fZEnAnh676kBTiBqQ2F5qpH1BZ72+KLbvQGDMU= +zKCrIlbP/DDhVHiZ3fIxklV8gb38KK+GbBXLALoFwPQ= +VoVkMP9tW2A1crYS1JPcez/T5g9qr04wEneJMF3RRlA= +40d0Kjq9eFveZcVdlpFWHqsMLb6Cbgq596PvxkzyPPs= +KIZi5iGAZ1ERVsYyf9tRFc+B9Yz08eywa9GtzLEWwb0= +CCWp04qZQAG0xvBg/GxA1ac8M6aDoxCupSpEbINrd14SIsQMCX2yg35fZKfSZ45bUlwsnEKO6CphPTsCe870BQ== +ELPZs21xKKoOnovAKJosLe8y3drsV3LCJ9rBZ59KMXaSIKv2MS1cInuGvowyQR6/da+MfD6aJgPi0OIyjKl/TA== +0aJdVaqIprJqIF2ACVD434ZUgkgWJjA1UrDXUzIX7hLHfRqO7xfTfRdmPwLbMjnBsyI29qYoF8SnX188Ei+AEA== +cEDtw6uMb2/A6mWG+mGficxASjA1PsBLQqmyk6avgrA5NU4ovgWemvxpF4tbZuYyAvelvkrb1my6fx7rBEPUgA== +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +oKVaGzjnS0ibnrCRJ6VhOXjlPFbbLUbXwFxCuM1lBik= +x4aYGEXHUiyydN0TvGTj6vQYATZwfiFdhjHTFnA8Gao= +C/AADmj7KKNzSBTrmgjwHtkMmAZdvtiecPcfgdxsdp4= +Oonoanjo57cFaJw3N8v3+Re7+iV0uPfFwq4o6iaSCBM= +sTBwU4/6vbNIKaonWCsmPxBuizocf0lQLoMzCcsQdOE= +GQ8bP5FSB8U92LYKcbOqVPtrzArbXQw92sj1bGuzJ1Lzc0ykynAZHg7W7lIh9VCyvuy1xx0xiuBt1I7P54NmCcm3dxPo0HOwwo7tFDhoPu+yzldqqdrWTSIRrCoxLw+5YpASeQRxYjEULaeTwkqePveTZpraNCJDzhOxUhZMIOjZFUfSB2xXKuI/iazrrLYM +6XekAdRuN93speBaqeGdS7mZ+YDSAF/1X41AUJfffwgDxVBNvGTIh/BqnzHblDVEvD5QZ86VvPZQOOmgg2lVbc/yUOnfJgQ4ct4qN8vJ9S61s4UZ77FCLYvI5zgjpBa3 +Ct7YEe25Op6jhkRit6HXSSZJ3/Z8e08T/asIJPAS+JBiQpX5IzKziQ+RX8qVdG0kcWgO+eRhWTxs+8ABKecvwkYRx2jE2A3stRu9WnJ1usM= +hTUqYvcYvtmpKOewm481/HtdVHS5tCgP3k907Eb+/iNqJkMKXLfNx7388XmgCPT4y5b8ijy8mCMYYFBgxS+Y0PsJa+5ddHmDK4MoaD7zkxy4lAUla3fx83H4XA/p6emPuqNg6mWcazm2Tg7vwmSRYY4Gs+oh7Bef0ZSfwvpMdC3pgSvu6bTZx+abnOM0VmGnPvQNxptUCE9WZugB2G2CUQ== +QP9mdDvW8SzUpgVgx/NNy+PQKhOQCI9rUmKBAXP10dQohDuTfGSFbX2wVW1lE8+KWgSYsWVlDUNEoE7fuKKjg9eahEWTIGX3v6Ox+oMhYA7IdJYsJfG+u28Xblk0g3t/GQsYgE32MZxhDOynNbR5eA== +mtgwn5Yb9q2LR0oKaA1GDK35p3socJhZP/ty80mesNBoCZh8/LZIyqglfgIVeQWfDoJMlSeA89++zDh+b/cl4PZWkjjhkhxGMA1+/4R9GeucHxc0N5RGTUp9GUl8NFzhnJBAbItJO/Ooj8Adr/iqems+7nd0AhmOz0XdBpC8EkQ5O9mFl+bYDPxKMWVcK7hX0T6spCqOX8IvONthb6z56zjLZcgoYBYY5IceJEA+013xOI22RXtRL8aaOIxsWDdST2Zffi+0qqgsXA9PZTH2RwdC+LofeuboPsUEQks5gH0SpcMO8vMKy3X4TwFGqAwJ +/P08j/NQ3V/6NH/K2OPqHb4UIWT5SuwzNgp0moMGPo7NaIPUXB2YYugHQeg5J3YZ41rI3+EMFYTXhZ48WK+JFY+L648GbWWN+n4J6sblnUk/Y3BWP1/or3NVwvGtDRpaVcJcTZuCsGIovzGUJeOpujRmFEqVHUnsTRCEyPhJ9jY6fPj16MVMwHSYMHq4tgBtWySlYT2STtRxbg7aJix8yg== +DFIcnybg9Q4e8lvNk2V2upGcuZPvzrdvtUWnQY2SrrrB76YW+dcib4zuKwU0cgd7PijQCOSs1xBFA63r8yKE+BZMpIJBPcQB0nAeBcdL8AnXfEZOX/dPIJkH+2T669dro6gre3MO8GntEgTBUobb04CGQo6Xbdhdjm2W2WlfXBBivBepOgNVICEyrBEWxJ6F3qViMPn/yj3zpF1putTvCtxD3OSaQQKlTQjwwDk87N5pAANpnAgmsTpnsGc5gwEWKsGT8CEY2q30YHoO9WZdvQ== +fBUMMACbLfIUiv22DBztC9D4tN7oztrdPKUY/nUgEKs8lLVTmCNXtzImPf+SyMdcxAqdceLgLMswKsNRoTwjEOVxK0+6kH3NoMKL8JR3Axw= +KpJ00HDVp573NWedwG3qjM4nIxeXBH1Xcj8oXm1JJju9zofOnBQAbjDel5Vf4V5+CCtWZ4rZ7L7oaIW7uZznMm5bEdo4WIfLcntIP0I0Rj6y+ldQhj+n3LowsUT4JgF2Re8UK7HV5d7IVq6F3Pok4g== +NYEZAf0CPJOvudyjBgyMzE2jwr20AEiuxvmw/forLgZERQ9CF6rwwXghLg/05yOWH7DsxQtiRV0jbRcghSMcamekmL3rjNcnlxlzHIz03L9Afg2mZCw2wpE1ng2uSMu2 ++wBlhY7T5u3USVjT9x1efC8TgTZeldB/Gm8A6jOghoftvOsZOwZkZ2waSc2ajCEu8/W+3Ne1P33gQgamZtXq0KRjdAlySMJ1LwafiTxCX/wcCGguMUTgIMT8pgJWKlP3 +vX3aw/VpCn2kdUdQ401ThnzVz9KSbiqiT6L++1PUtLCWNoHXu3hXQN1cFaNNoZLtxxPINpim31IYDqEJoBDph8kNEHythJKRmMo5Z0+V4K7hgquEbbLh6QkAZoWpM+de +Rwr5WRRWJc49eH5/ngaIBiWYeyNcMjokcXjDWWMdBpR9adsWuzQNUXNAKZOXfKil/IcpYdPvTbxEDZ3s0NmGyw1GIqb/xlAka2sQwmeAeAXxauBb3FooztVgs2YWUorEk7ZkyIFFLL9lDDIkexMk9Zip8Ezq0UfS4SDL/2+UqILPJNyweBJ2lb418UIVi8ei +ZUOeDkg+GlcyKcw8Uhn9Gkzo/f8t5Thp7tyqlTwTNGzDRXaXsNYqq58i15gSj/SXZmHhMPM8acC24U2Iz388I1TabZfzR3emEMt8tor2R5qDMGDRDt3uq51gLvSbi/o7pySbUdqo94mg6zOkQZZ4qQ== +ogK1joGXApEMwUMZDKuBPQ+K7io6hyKKF6eB/S2JjwxAs9ZmLoyXyu20uQSyV7LVv0TLo45EVujcWKH0qwbMzz7uVhKFhOvhBxY3r6NN3AQZeC4mZJpMD7Ita9zCqRrE2xRqPICGsrqOQCpgPf2mYLasUXusCU7X3XaczNkzeXgEPQEfDIGDzjdgoBDve0lD +kHKnlmRHLSoRZ3FlW7VxUDBfKAp9xAMQoasPRHDCJMzjQCpKGX5xlWdj28e19mbOuK412zaSn4fr8EiZ6CQkVPJV0JhCC6oXJ1VP5dzwVK6sJP2xnnrc1maP+2bSVCSJJW6XTQBnXN67lZ9csTc/55kThXgt8/WgNWfGnv7cNft7qMGh/uqDpiTxkTvtWFyfBLHpDodAuaAm3HZF81Kb9g== +Rwr5WRRWJc49eH5/ngaIBuv5m2Ve4B1fcLet+/1wo7DH0d23Qhw6EO8INtX4jGWqGtX5+lRj8NTlwImbeCDIR7whFKL4fiSpDcIWEbjfEvA3nU/uai0b+GjyPufitj7z +UymC8Fo8j5Vfm3c3mvSqVFlgOxkgngkb4zutUybie1eYRMKNM2R0Apu4INJTA+KsZzhEsS0Dg47oP2HXN636LPwOVEk3KNf6rD5qgihriEUcABpi+i4p6MgzO0aadCLO +Cqu/R4uxTK+Rjxynhwj8CfHKWpq32DKWeMBQre76RnzbGVSYpcv/9UNs/yZShnWykkMGuk7OIuRPKCi9fXAn91OtupWnmBE/tPrM6UCmBhOzozY6zSf4z7sHfK15NfQsadC4mwWFokseEn0i8rJBXv5XXYpBjiKMnnLxRTbzuKs= +GLJti+lpRHd7Oc2b5e+nlFAlPs/zP7tUhNYN+4uCpfDwFAjlXxr7u15rd1sMBPzQd4cew+cfFWW0cLAdoMsQO8scE0139HOrzP+lc1LQt+GdbK/albCj1Cwzxm6RjnEE +yoNfTnPxkT2ArtuDpRb26am0DA2k2Xon9SZObD9Px9u4mwT57Nln0TTGVdqJQcq+0cTeXnb3giLG+gqXnBuVYdQUPl01Vu+xBAOrY2dmrWA2ETuL8gZ9oDkUBfRCWk08pjDdAnBxSYBi/tFWtStR/Q== +PEQnpcHCCkNy3Q7rsdi/TK3Xxav7oi9sGAog4t8hQeSbJg1i8dcEVVLrHD/2FpX+Y3Y8NVyvTEw3MFgOjXg5Z3inmwUbEV9YzGRFNZiNldg= +XCHGYowgcWiXCPE3uFJ0ZSKOiHX8d0J3vy29XAMA1SeblILvIlSLkA1MisVOiIdR8trHQ7lcjcjeEYK312AhuA== +ijJqnkItaijg8Xxs/K8JAtEg/1CRLWdefN4APtezsqDTzlCt54L8MhtthOhfZBPCMGcLXf4L0yj4bf2nNSwz1U1/71jG7FqtWYPcN7DRP70= +U31Q5qsC0ZXwyq8Zvtv3463ts4GVBg3RkiE4S/DzaA6Kp8GeQBp7km2wFZXuGu4Qc1rF6tnaxDAWpyk6yZ9Z1V66e0iOMfXFOGVg9Z8gVVw= +Uma9eTOXwECuwd3yDyjkcVNVDqfGb+Xyc0AfsH/Lxdjqxri6EcuUN5gbD8qkevZ49ORG2Fny0UYggt129TzU3qVjI/LW9sdY3Tv95jg0aPg= +4mJjQ2Zr7TaCMSHEDH1cmNC9B6otCDNHWFtyq1OIjer6HF3uv5P3Ah3xQAtzeb0757eFH5uqqJ7+WetJ8IfyzFGIHdCnNSuAABkwXyZDQuc= +i2sw6+ZuogTNbw/oNKxMgeHb+1h6dsKyBoq229eIP+ootCEFRhDcvdvzh+nsZyWcKhEir+qfaM3KgbcBMkWYBfBY7N4WBJ7QD0HkqP4nK4I= +F2DWu3kKh2SE9esMcSzIGfJ+0XMuBwVtVj8iDPswkZPUxgGTPxq30Ixn/I28m1U3b9kMRMkkWS+JVyFDH5VnZiHOp4OhqAF14AOvwkU2gEw= +oDtnF93s1JkfTVQ9UzfoivqdVspjUgLvGtzFnOwO5TtqaeFScbiQcr9H8kA7Pghsvi5p67GLwNQUFk+jJObctRecC9oeWPUG1GQgK8oJXFA= +JPxDl53ivISpEUZ+1kQXQXMkwdTaHzfGE1gmcvUGrI1628Vukw8+Tr2vtKNIeQLhv6uPRQQcMjoLbM6J4+lctcZSACtSN4TMxHu8vTP8sq5cFtZfX6Hp1MA1/J/Ok0Tm +RrtdXHpDf8ftCQ4plQxw0HSkwvLurQVAdFqJ2vvEDrjKg9uE6YmWw40PaMMTfomE4srMPDhufk4eoHyPuBrvY4yoRW/aaI/Mic+SApmuiok= +c33sbT/GTUpquVFIngMO87dfZjzWcp4w+CxsZIom8hnCV4LQCVINvpXQKpQwscS46W+bs/X9FlMad7t7mrWHazRBqMMn1vYjX4+/MlIw5mp5jgZaKdUE6zxw3uBSIoS6Qfeod5AnUo1RuBAmOxzLYA== +D7Xe+CnrKuTWLm74s5CdULixGR1tSsjFREsLXI3w1fOCT9R1iAfD71fBMTYs8RQDd+578zqvHQbhLzp9wNKHV1QGVpIuu0XVQ3ZOfGrW331O57iqgWVKwNy0+TA8s4yn4uKBj/GHfqoW+P8hf2Y/iw== +t607yNsl+Xt4IUZUKCdg120PGcOsg1C5ytM/H738uXTHLSoefTvEFhchJxU6jCLjxj8wt8jNH4wUy8VleGCDcQa4Cqg3WtkSsjTogkIG8ss= +v1YvZLHufhLKFxIHRhqo121CaphAKXTzi+2OQ/Jt+2Ra8IbIbxhjYLnTbprhUVFVEdfSGSka+7HARRFgMLOuRNhwyux3UaQBPkoTNTFyYzs8tx7Lwn1ov8idcvuZqjoq +O0mtTdlcYfDttKJPtVyrjT7Vr7M+sSOE09EuesaodxvBQ0f1f82UIsSTbWj1oec7fJbrrisZPiNv85eApWnhpgY+dmjE1upeUmHSjs/TBhc= +Zs0AOqbSh/pxVla9Y6PkDHix7FEQa/lG9Xe9odgRvWBg/fLfX6CaHcjTCDy3Udru5iRaLK7p7tpRdesj8fy8uWL5VP9BHPjb2yo1d8NGp1w= +eP9y1UpA8RKgKDUIdVctvyfy3tDu4z1rJ5eEoNhYGLPUnbbTOnOP52gaVwcexczf78FiWjR/hUM/wR1ShNcBCLbP2anqHbSSEhBnTsDwBE4+v/swe5ginLtQblP8dcOiOJLgNPcLB7OsAiKkh0up1Xi4SR0Z+6UcqNd1oEffI2c= +eP9y1UpA8RKgKDUIdVctv5D2EaOJzqmMQgPuQUW0viADntFpL+OR+jyI4sc/yfJaa8WDEM9qhqyjn9UWgm3wq/x5eOVXdd4Zi0GnGHErWUxLRFycodvusOgBeFy1Rf6XkcY+hGJzZyc4wlLPAC30ZIzK2tsYRCgZw6AEQaZJ4OI= +V9s5Eo3Xcr3HGbb2kxKWGBy3TVUwgHQ0HlRWypwzK0rSJKWkUaHeJV60likc2x6BCRpPWJ866FM3o7ViuvFhsqjMOkmOE/VZUAuEZQ9ARsU= +ECQx4hhql9YPrq7JsTDQaKPFGEO85KpRVw7iAwFx+sHmIx2FBJIY08G4dmyeVsdrGj7nqklvYYxhdd8i3Q/8v/EFGpXLSkcBcocB9tuVIq0= +yUEvl2lmwCONcgsqq7zSb+bsBrBDVDADE4qAggOZKqRYnwiwh+tCkoX3N8tQQj94YtF9XFrFiR9XpmeMmcUpk3QRYUNp12NQuMrOacdrhR+hHHln1Vbai+2SZwxiSRg7 +u+YxRTYN8EJVeuURXtdcZmDUmLfeALtrsOJn6iGUQPr/JDY0TtUU3bhX3QGqIcnSPR6NQViOuU54/qL93hDwWxjZkkE2OTGd/2UbeTHyolfDsW4NEaMTKexhguTwoE0K +c2G+D5skO/+d+thjXmC1YUfVX+l7iWgX9T0c9PXin3gutxdDzXVeMcl6Xfle6alDePLg9NsXQLkUJC0M6bvsYEiWIwaq3GTGPdXDs7FE4G6jCNJxO1pLDWSmDyKgRSOS +o4QTqehA3AlJXkkcJZDUJiu8aX5WBwJ8DPaJ+OLwuZqAeqwI4DMcOEIwurUrckqZp8Koyjfn3RIHisWySq1U0fxdkjrKYgs2L5fMv8WV9mSvaPZnEqYOvHG6wsKTcKnQ +UyvkdV5qs24BbOkkeKQI33u6ArkTJBgz0Ss2onvlDERuqdFwTRUb4f3Q/WiESZUnLa4y03LnZMp/RftQIlV2m91QOExNYFrX0esZ7sDdO4o4i+eSqNEaLEvPr3qYBerNBCI2h9bIcHZWiANsdpfDdQ== +yL3V7JujJ3KF9f8XtukZsR1fBJIf7GJQ4YkaACLOGsVDiOxskKXNQYQxnTbWLPRsZrBuS1Fe3DcgHTUuWLfLd4Fw559n67DfeG4HwABfM3FUmI1EYGMPp7sZWL7qtyCWJR9N1mltg/8WrBdfNAnH0B54iJ7F8X1ViYcXicK0pWimFELauqZhgLeOdpcPNRwe +Optb+ZZvcgzfoopw/H9ohY+Pa2BmRt02gEQCo9AqlcpFc7GOIZhbYQBOxIyaube5WJCVuEAsrx3/gYzb9ShcHJcgPpkHCMuXnQHO1UVitunUWrMvslPVdSSUFmpC5DfP4X3WEmStNbYEXvjzYDHGLg== +1Gt/p2zFJnwB8mRUNG4E22OhcfaaQ89D89ZyKhAoJpPCFyqgu7edsDMIM1I2ucM19GOj4qe3GWawcR2f+t4Gfr65NyakK2HZYUs45NEU7Cs1wZjDferOyImlO/o257lZ +OTCrWmGpA4EdwTS+E16lD4VaAh7pDL09qwQdgri1dA3vQiC4xwH64veDLn1yvmMrl3yCRLP3RKYK4cmqbiNVYnshkRpd2zWGN1jp1vuDAIqJkJg/C4CLFI3XuEWKg+kg +VmUH3ezP1MwgHNKPAXaOFx0SCA2BhsoQPfuhCiEf9E12HPTU7Yj+DFZsX+NiywvPSXS+BcXWFmbMx+0GgNkdhnE+SLwvjVOBS0BKyT8PmjSEOzr6RajHGqnfKYHapBkOXCFmeWY5LSkmw5OLk1Tjzw== +7Cumgo3JknMVMx9d4IyXNkWblnKDeDeoxr61OsoOCUNKX+2st58f1PBkaCQ9Ia1jr5GDlka3sJiXlAFFGh9lFCdWTvGsamc67ufjc9RWDozPNbCPRW+/lI3g1nDP5hFX8SwW1CZKxWtn0YA7Oc9axQ== +XjBVJ2fDvhqj13zHSPhNq4a/VLCQNkDASpY02Xga/Ae3sqYAzA2RkYvcT0raXxa92r9qkRiMel4/QItFFGOOMnjeDSrcpJ8Q0n5tBEEiY4R5zixk2HZVh1msPMqP+9mXDLzDJNOp9CQlFUEkE5iBA12Z1Wv7CBU5FPs4OYFqeUs= +mb5ArhJqY8U7Nhj3n51LRgVpmRFD4NHWHZSW6JTOzw/7u4vN0xNGZ5J8d33/n4wpH8JeKbYZru9COcNPB1RTqNxypEzIetud0GaIspNozv1ypxW7aW0tK7Ag19G7wLSClxcTmV49pIapmSepFbCvPhS+KVgNY/2c1L9UrlmzyI0= +eIAbDBBMnbQNURiQ/SQSj0Yr6NsZ1YoYvEDZf2EiSRgF3TWp1Um5nhuZqvLgw4qhIJWe5FN/haGPgtqHNVGSbH/TsVHYtELdz+atH0h/Z1tt+jucbNVYhhmbWshgfFdaDXu1mHOdIShKRvFpFxldew== +WMryAG7s9FKjtjrG4x+ATNmu2wszZmtzysm6i3wgpZadOLO9/ZUSF6y6dLDxOuPnAVpKa6yN4xld2dDyKKgTmqoBJhqRirzqJbKQ+0gSuRTjfleFb4FsAJ6zCoJk3PeRvXAXbEOUrSCmz2KkGYUYRQ== +WMryAG7s9FKjtjrG4x+ATOQjM6HjHtlGa8M/Jtg/uZLKDhwMkQV0s9GUOWRJ1VWbYgF4Vr6cH8SXZJNnrKRk83C67UEKd1nA5OVrKD6ocgiWbmRdmzqpVefTgxsAfxMmBy8l2vVMAhYCOSCS7vRaCQ== +WMryAG7s9FKjtjrG4x+ATNrh7dTugRp+04uBk/GSVw5LASCW74N6Ga3pvnS/VXYDQsivFtV+i1iXncagRAYIvE5SuELzodmnMfYMx1kr7zk/VwVwQaxPVYYIB6xoP6bAA4pZv9tFlx44bFfZ909CoA== +WMryAG7s9FKjtjrG4x+ATBQQi98Oq52QOwHM1kpjILnLhFORzJ68hVlRhyTTS2dRrq+0ZNWIwi+JyFTeVO8jQ9lQVWJZZYW8qCZDuCe/Yt2oEln9gcCzKr+H/APNHS3P7buJdlKeDCPPxUGiq4u2jw== +WMryAG7s9FKjtjrG4x+ATHHxs27PI3yMHf+eKI5O6HCRJmN9NoPXQGGhg8es7ScHiOCC0XWzwWPhMklgPTG4GvnWwaTcKlVCZupsc6iVC5eqBfwJ24u6uwjs9i3Df8PU0ziVf0deWwiov3Ek3fmc6A== +WMryAG7s9FKjtjrG4x+ATC+CQvkTOYaW0xCvOF9tMTvIMNmF5CsiGspToLXf+IeN+nYOwz7p0lRnDzrwC2wP1zTmeLHQaHCR8qqKluHY/KcAskVdJx0nFTfbDZMUmTASMwwHdZH/fUO/ZaHzwiqrnA== +WMryAG7s9FKjtjrG4x+ATDHYw/qAF9L/WYO4Wh8/K/gzN0ezfEYg0FxSf87Z+bMoTc8J7Gvt0onDf+pP4CuWzo8G8DDB+ZLbmQFMCQv4Gut7bWkGzoXJQXfaohg17ya3ZofORP0LtpSMlo/BxzU7jg== +WMryAG7s9FKjtjrG4x+ATBVZ1OkNxU4zBg7teiYs5c/CaFnQC3a1I6m7faKgS8ZVA4yLRjDyYsAZpa7245PyergBBVwFFH6HmKuKQ7A1gGo/6DwUzhXPA8TxhxC1q88qpBAyzBBYSdYrgGfvuTqsXw== +WMryAG7s9FKjtjrG4x+ATM/EbaHuaIT2sybliDIfD7j6r35UTo7NYFZRlrVvQiHw1cQgcLXGhBK7zIArm1uZg3WRIVGiKaEsIyEwcMJeJq/RJRQV3VcpMObXSD5dZo3SvboG3XVZThQdLGhNFP/G0Q== +WMryAG7s9FKjtjrG4x+ATAdl4avdl+hip0KZ+75VAnrQV/Pk0SyMPFwSHkA5XpNjDbTgiwCWiqaKg83iNhOHBYJp2TE8YRnHnTbnVSy8t9gIA4dNzjFn4JXuClxUWXXkc/A0XSBNs/xDGhpWF+ndUQ== +WMryAG7s9FKjtjrG4x+ATKR++QNc0SJWmG2pcTkqP+Yym7W6h6lBj2/RCffuI3zv9foH1AtBZPly2+kYRUbEDAww2N8GU6gx1qAAPikD8k0z7RsRAqWIa+KkVRGLFrVVwgnB/MeND+82Pe0wXeOJUg== +WMryAG7s9FKjtjrG4x+ATCLs9tk9Jltu2ibqzlObVwNB4Guq8ibqEdeKnK2TyySOAO/Ine8V3xPDbhbNVEyzUe8PPbAbg8gAnSNTwNvK89I2AVbo6HZPOy6Jcl09MwHYSCaxASYteUjDGkjMsWrrXw== +WMryAG7s9FKjtjrG4x+ATB2qq2ymrxcxLsPkD+m3XrSI2N2+ZRJ3eD+s9eQDVamg+7njOyPJrh65dDsZQCCUNjjFu7OyKGRhcT6kCI+FJZMYtkEGHHz9+PMPfogU1f2BrYyjUOxBNbF/TUHeyw8Aog== +WMryAG7s9FKjtjrG4x+ATOEy7/q9poeP/FqVlweawPPrKCAMVQZ5h06PAZVXWMSiQkzeYW3YpSPAOjb3GOxHTgch5CvH0Xekog2zoMhn1y0pPvzbZYWxJ7MaD6edE4EKmtEKiA7Bjj04vrSAQRkw3g== +WMryAG7s9FKjtjrG4x+ATGhR+unI2h74qdTLP7c+xNjUIK5he6zXxFVALfstE1v2JJzL6k53U2c6Q80wk4pLrYKXWZ1LOF2CkF2zKg23Jhr3uuSLfIIjCKHSP1RQpfxP9XUoIAcx/o0T8ZbwM7BWlw== +WMryAG7s9FKjtjrG4x+ATDNt289XB4z5X6cf+rpuIYu/y1ZRboRfEAM8dlSkdMYEQi0k2LPMRZG49i+OikNXME9tAy3ZYFz/S6oMQGFxcdckiJT0s5pvDNiH/XmOKGU06+Es4PF80hhgFFBTw7qfQw== +WMryAG7s9FKjtjrG4x+ATMgv+MtjO7FtemUMEZb9FojV1JkUKbj9Krklg5Df0I/59ocr61fClFZvuselgezf35+GsHIg0XjP/D4Mn2m1v4TEgdWHtzjMoTCzy7uibFfDOFJZwoN4aDjnvjFQ1HS9BuRz9rAAbV4Us6lVHO1NCYQ= +cW6VfR5aZO4LK69BZsjV9crSLG9PIz+TGmuuhzZzbJsZziHU7ZnuAY6OK306VBJIfHpKpYF5XUgk6wzhq8A9yktWjNRT8Zwbl4ZCAtKQsASJ8ZeX2XJAc+MG5CLEMF46nleScxctrO6OMnDmQ4FKdQ== +qXz/P6tVzdUn0oeNwT5P5GUTOj15ONhltrAZDg3uz8+cz7lLpSBr4qzzaARhAhY8v/EQncx5cMJOfxhR2mUP+HPH38SryLW9z4LaaSx+R4s1oiOCUdTwDFOrqVxFSDvYM9X0PPXKvaAqxW+kvxtNoQ== +u9mvWPj2GQyeh4Qfg/3Jk0F+enxwpkcd4cVgUbKEjKPDyTaFz8NJ0DuyeWx8oGlVIXJdnlkiZWOv77XDEtAX0V2FHBnVWBnPEGmRbUQCTqEco3UPCD3t37w2dEgvYm2ZOKTlRmp46AQKp74G7mJ87g== +u9mvWPj2GQyeh4Qfg/3Jk4Kkm8Da//W0pSXkBnB3Z0ZucXeNfRaqEdskySzi8GxJe5iBfUt2r2xaJMX12K513CiekJa5Lqgyv7hpn80olxEtaElTf+pBKVS06dStqXtIdUv8sggqznqUh+SfMuIorNxfv8+vZZt/F6OhKfAM9uo= +u9mvWPj2GQyeh4Qfg/3Jk2Y7CLucDXrFQMDP/vNqUcFBBoU/SJ1Q6vFBo/3rdeq9SSz627vkZ2UQDP3TYS/hn32SmFoWHbs1ypYucLNWZeqEgrSdzjn+tExUhBbfKvLlhHruu7OPU8Y2ySWAqhUDP4SIiqRZbMaYRztquwx98F8= +u9mvWPj2GQyeh4Qfg/3Jk6ZzJZSqscDZOHsDwfXZl2numAbrtOPyz8uWbrx8vgIfflEMgWNlUL/XcUXMLvZBiTmcC4ihstUjUhoGvxKSxxYnTtRbCI+pN7SLgzFFGW84g4LuGQiqOqy/ds+Q7NxncZKnHsTyBV2TXa0gyzvNrYY= +u9mvWPj2GQyeh4Qfg/3Jk85Wg6xc4QdVkVFkKn7phwRwSBemcn7v/zYOEFY7ZR0rZrrhKYyCCjyPBxlMd52CxwixdtfGrltan6Q5N5JSTBGZVfYA89Hizmj9ixHpw6E1rEFmJQcujwZQF1qxZDtGN7AAzGh+UtVO6T9EaarsjI0= +u9mvWPj2GQyeh4Qfg/3Jk+XSamLEKdP1wc/owroCbvOt43VRnBmyr6pXnX1Wp4ugN8yY3TrOkzTqGUL1lMVEtc14uu5iIyHEwoL5fVQTA10uSoKAtFx4tXYUO9TGWbktWRWf9u+FibbSIiUxh4r4zQ== +u9mvWPj2GQyeh4Qfg/3Jk4p4JccEzwE701o1Hvtv8RLgf7s63UDhY3CdacNFIx+ma8rSIZd8+HOcxHvTi5JZh0RR94JdU2o9vHg2+Anw0noFsoo0B2TLPnzk/U58/tI7tL5X2cCSsrkiif7HYk+FDA== +u9mvWPj2GQyeh4Qfg/3JkyunHx5F8pN12FI8w8bGHc8PT0RJ1ik9rOvWz0nxCgMB95VK5Polaw18USNfn0UHraSRdJnNHBkkF5SbN2X9q+afOKw15+MFNFUel/uuowT8EtX09y1P8gBRS+vTMRblU3v4j7QT7RX7r8bqNa/+GnQ= +u9mvWPj2GQyeh4Qfg/3JkwFHURXL0F3lVUG4MYO4Z+CFpWMKpr9K6TnuTtqKlGcqNGGAtLqIlfLSUN0H9pE7u+Vs+zVGKT5y73ZBqYL2xRGhpyMxSg0E3VGxeHfL735hFitq9ZfsxG/GW5gwpLIycW2EehjIhuyMHCi1GQBZEYg= +u9mvWPj2GQyeh4Qfg/3Jk+/CDh9J0IZnC8hReRXw+Umz/8JuHSL0Q/BS68JLWdBapxPtKCsxS+pwpxh6YaW73Hys2MDHBM56ftFysTHtG+ueEqmyXYhqtgY3NBRrvV847Ep0rcZsXyRrxoqPOrsbwQZTOCpOoDuzwVx7DsCsvk8= +u9mvWPj2GQyeh4Qfg/3Jk7tH0NbWKnoRUzCSYmczsVc+1NMPu4Mp1UOV4QV9L9HJCQlvpe/JCSgaF9feNutG8SNjToJO79MFOIcr48Lgj9Ay9+tKedmp+Z+Uxq2cP4J0/Imu/PLctuO6THYMj/s8Nw== +u9mvWPj2GQyeh4Qfg/3Jk+l0QL47Qrb3z56wQfMVF6nZ4uT4S4ss9Bps56roEWQIR7rGMvhrpwlc60RJTjBbsmBFIDCVkhBzKJIZ64xjL7W/7WZNLXY4MdBoZZwp4LmigdpGnRJ9+KL7zizsydYEUNtkdsrtNuhlSRYYJFHxBz4= +u9mvWPj2GQyeh4Qfg/3Jk/4Xxo+6RU8qO11KZpuw8Bhw+58+RO/ozcnjeTnqhhAtpNc1cGEYiPseIBFUR1Nxil28Ixn0ELPDop0Y2KWOoVv/6gt4XTlPt/W1JZqZRA/vx8++Jcd6gdbe5vW5Oz8YU4dFv8CTdpX4bGyks6eB/pY= +k8mQATL2QGzdHVb/ZqU0sdhr/fHf9LEKT2RVauO7PV5k3kgwhTztgLNbkH+BmV4/Pk6g+EEKg987GEI4YDWznGUpHNPklJ5S4tL5CUpGGm07t2PS1TdV7LnXXLKs8IhmBDwb4J7iBFAadgcdI5mT6A== +Optb+ZZvcgzfoopw/H9ohV7P1ww7HNP/i6UvRFAyH4f1dGmnb4FXU9hOqrHZ2iWHVN0Vrk7rU3JK9Wt0VyVrf7HJJff9eRLYW8+yUQBzKhPANw8Wod2rxu8BgxCDiU9MeWTyk9ygFVXd1535Vk8f3g== +O0H12djxOPpYv3DI+nktXcfY8mj1r9TI5/bRMWfRk/UNsmywSrOU4V/gP4TFJYSVwwK6YHTB221bjhhmUyw0Q5oEDpemVFwY0Rh2E9m5GP/CJMw5yvKxbhBMTjbjx6KGMU1JjHwTVGyleGYqdzS2tQ== +L2qllRaufT5w7PPs814S6rwJrryp795HfIlSSWRZKcdHXSHyiLRqzXNxgEe7GX2Xjm7wdcKYfEI3CK97H+Sra2yYG7DkOyngECk6zNjiZpDdnAfoZ7/+fQw6pQw2ehfnXM9RtWtoXOH7ApaB5UErfQ== +GCa+LjTuIcQFdoFSHacKrr0BJRQ64ySoR/C/RpwFxIEaesCSIkPH3WLrkTMW8Eb9pq2iZ0SJ6bourSD1TtgY1DQKJTYq6/APe8XPoClmHkEt/UWlPbAMJUfcPP6UHlMlTE49dUn2DxKTK6zlAP8OLQ== +tp5g+osoOV/pmK+tBcXKV22d9HxcLMH+xTwms4BYy9dPBjrzfxOROpQlyteKmKpmFRUppTYemg73OD86JGUMTE4wMlQ29zo95Znb8JExYm7n0ZoEj1LmiY4jrSVYBndY1G0FuGYM2MS4sU9+AnS2EA== +tp5g+osoOV/pmK+tBcXKV8uUTAiPocxsNwpKfFrj78UoZext6dgqhXvLVuzQPgAHPnQrtRgunxQqXxaxV8NLmir+TM6FrDSKCl5e9m+VrCWQU68bjLgcX421o2gyTnPEllHMrG8UKEcrD5KlRX0+ow== +tp5g+osoOV/pmK+tBcXKV6neYTsy2CBToPppeuR+1qhIFV5ufM3BpqAdx9NcFGzk0tMll/JW77Kha5GiDhtwEy9i1DqVy69M5tGkQexGCFbPFupVmqqjur1qFTbudWD6Gj8SQx7IUKlsKtBsYl3Ecw== +tp5g+osoOV/pmK+tBcXKVyHhjlHhc+8OyciAEIPtMKdJ7km+qxLwPmF6uR/Il0KsOmvIw1AoPgUwPRfbhbRJuyp9zT144++FKrH5GhqFDifI53iHtun//ddkHe/XPCkhKPzRK4XiwWEiSTBo/O9fwA== +tp5g+osoOV/pmK+tBcXKVyncfbXigmOSKsGNbE35zxQBzcHPGmO7xa+kVKyGcGw/nhOnl9JFy3J990rE2dKhI7+7XZJm1qXa5hwMD/B5/hkz+UX8th/FK8rSKs60MzN6CRpd0S0Hn09u1oByLcNq+g== +tp5g+osoOV/pmK+tBcXKV+0kNyfVosEvu1R5eIdu4mqJK/C2Xiw3YPLIY9haorUwimJsC8qf7GwnP7/DHxkQ2lI/nEYqLq6DgTX2Q/RqiC7xdf1GnikUfqpyUgIOxtXD+VTiU+m72vsm/s2IYZw+xw== +tp5g+osoOV/pmK+tBcXKV7FjVUrcsdJ3A3YM+WnARMI6e/9yYrUwUm6z6teb4DrgH0C92Pw53k2cvXbizYZQIfKvQBC81hrDi5ccUcRtDX1LwpMM/AFM3k6uaPJtqxFX1wPn0/iN9aAnuWYtNmOKTw== +tp5g+osoOV/pmK+tBcXKV23o/6FCCKq253U3hcS90I0BKd7a+rj9vrM0erCmF5fgQ7zLdhTC66dGYxORDhOCqMIhyI1LiOSK0ZHLlQJODncmBSSJMs7dXXLYExcPtxigHv18uHuc+oBY+bDjt7yPOpBPbXJcxAYfmc4FlYcZ8hY= +tp5g+osoOV/pmK+tBcXKV5NkfIfu6lyMg9b8khIrQtYI6ae2yWEudVczO9o55hLgdwio62a0SsIZax5MvGJylvOVb7Nc4dbpebjU3WKZFfzgAoH9x7fLDIKfWb2Z0HDvRnf2X7ym2qSFdjFmOrgYu+VID5J9vsD4VpS0Qe+vsws= +tp5g+osoOV/pmK+tBcXKVzTZueI/mphDci1n7i4FvjEhHVFae1ypLGLSSkID9FE8Ao4KINj7K3pSaDsJGG7nun/o/67TR0+7U1SYfpjCUxmEt1wrRoq6Tirt/vPYpSjToZkzNLeq+IZPrRPaLa5OIepHlMWd+7xIJ5wr1UNNs/k= +tp5g+osoOV/pmK+tBcXKV/R1y1G15U2L/+mO+1q9xsmI6a3P2IWGD5pa0Carq/A/xwWpE2Md6t2Wv+rgc6EMwM9lR26uoTXBpPliXXqV76qlH0YV0vwRSueJUcbSuAPYLS8Am+AYoHoW3mCnXs656HIOH0Kum0/yjHIog8jdXj4= +tp5g+osoOV/pmK+tBcXKV1nrdSo4U5sImCiOzub/YfcLeDqpPXs4az+MWQ4dntNOBN/19PUoRylHR6T0T6sB3EtA75DvoY22xQvQQkXPOfCLAP5Mbp/zJcqEufJuAemYoOmRpMMYhwPMTByMXB2d8we1RyORED1IbXOLqOmAU+E= +tp5g+osoOV/pmK+tBcXKV2XIfCmcFG05EPSUV1i+BCzS5BPRJ6Esec+PamL+FyPsmGfClXKYu6XyF9pI/ZpSBfCwT7/XKlkUaqu33LTflkbevzZ0RKBlexQLCEvL+sL4cORr8od3FfAb5j5Ff+POj7CwkpttuJ/vhHuyxnNCwek= +iaAodRlbbDbdxCarNwkcQdoTVutsf6DDR9dYt9gT2Dt9YKBt9ktQmVkZryk55qSRBIQEKM2Zw4AProG46/+su20DlJ4Okv9gogPBQQ865pvc7aQ3BokSiJOuNGzMeDVW +phX6+xCiuQFZuok+RnQpZDtuXW6l0Z/fULj48AYMV8G65jxvIjADDr898dvf+QZUI9cVeQChYvXHZDFmVbJT7bZjk78T5FVOXnbjVL3lTWYUQd1JWLfy5Zvp+gy93rdh +6dJZtsb8CUPLf6XAX0fHyzOINiwDqD+a0wyfh6+clwJcKemZZWtrS24SqtRArpwsKByB5lq2v+zcUQh9chResg9HlDBoZeV4THSiKnI+jdw= +lpMeOmzCLPmsgqJkG7PE0uIgltFtjNk9joqoi/Sx5S7Z3Hx9I+2EOHepDdzOp0bXwM65kSbvZETscKym3dV9d+06XVZxbHvbKbxpli6UKzI= +lpMeOmzCLPmsgqJkG7PE0jWng/ZqcMX1cl5Z5Pm7O4/ZytOomuD8iSxW/o7XtMO5+i1+0HbeVVdrK3zTwVpP/JyiW+ZqL3FvwsktMy1ynd7a417nF56s5xndYxt4f9Yv +dDQ2U1xB29TjkFiRc3OY9/7W0ws1tGSg2m7m2Tqp9dg5UeSyYqzwRruFgovxcuV9aKft4FBBlInJ5drQZjAZAGcIPichSJAeo7NoE+61puY= +phX6+xCiuQFZuok+RnQpZBTHYsxYcds8EwQje3SixNq4j769utpwHMU+QxYxIPWMISACJ7key8eERNbKSxulVflk7w+0RYqm5PaHBvdgHnI= +6dJZtsb8CUPLf6XAX0fHyzOINiwDqD+a0wyfh6+clwJcKemZZWtrS24SqtRArpwsKByB5lq2v+zcUQh9chResg9HlDBoZeV4THSiKnI+jdw= +glsVHMdFb/IeJsxHuz5fO9rID0zbKTCYiMUut/6OYeDxaLt+nJl6yiZbzgy1GJ2W3MrTwFTfwF4MkwrPvP5EbtBwqbSZRh/yDZePmhERZag= +LllQdNg6CwTfXdYMyq0nDg4z5E0nl6cQ+JwOyb8yTWm/nkPMbxdYEXHyeqyqde00+c58w6eOh1dK5d8qz+QcUQ== +u9mvWPj2GQyeh4Qfg/3Jk2ASxmE3NWLfHbdsvZfzKIqbD9pcZOaSu8iu3+2su1dg/0qIvoQUPMDaWJ8UD801pfdPGOyh2Q8BAi8Rh4XZ3H6wElXcHWlU+mGMJ4V8Sp8T +u9mvWPj2GQyeh4Qfg/3Jk743lvh+RBZaS40NPQX0FlF4yKVTVZ+6x94ikzwHvd0gZXRB3p7eRD9lp7TGholSJMjz7hMFEEe/HbqLcRrRhq2l3kZgXCsxBUoLw5d5Go5L+5HBJqtKmTMS216uhbkZxw== +u9mvWPj2GQyeh4Qfg/3Jk5Bfw3Qg1JNky6Qzkq0rIuCg3Iyvai0TbwAmlCFeQxBEFUXT1LEkCCObL+IrndH+gRpzmUbK0B1+EQlDa9x7R1KOP0xEJT+eHg+lUpEbycTKIPaGT8owbEHpZWJegtvgnA== +u9mvWPj2GQyeh4Qfg/3Jk0o90/9mCvvXcHLwVxWpPy2TG4JAkDeHQ5tODo9lC/OrQTj2jD6Jj/TH5u8dwrbQWOntAXUqKDvKaZ/oD1yWRM2Jv8VgPwbGbABPMC9XRyKKmr3Pu4HV55Y0yc/ceXP2Ag== +u9mvWPj2GQyeh4Qfg/3Jk0n0O4lJoUim6Eqj3aXcB9+VClCq0cYi3SXapmP+GulURUxxmjT0HWW10/QL2IzHQprUFH17R13Ncnk04N7evinySbTyxkgvNH7TGXXg26EBUSQyNHCaYyvJGbThNuUMqg== +u9mvWPj2GQyeh4Qfg/3JkyunHx5F8pN12FI8w8bGHc8PT0RJ1ik9rOvWz0nxCgMB95VK5Polaw18USNfn0UHraSRdJnNHBkkF5SbN2X9q+afOKw15+MFNFUel/uuowT8EtX09y1P8gBRS+vTMRblU3v4j7QT7RX7r8bqNa/+GnQ= +nrGCUtZXO8lhMUzpsw7flZ91P8LuwTyQc7ctIpjtlcUqrrpkghMmRmtgIz0nrzEEfUfHH35Nsi0wHH9irMek/0ac4bUEQRanjgBZNDBP3rYTH8w7Ajd/zLhkn5Lrf3fEix0w9W0HBV/gva/a5Ouvwd0HYrV03O+i8wuB2Jd9v0Y= +OlA3Ms276mZTcqwKAwXvu5EFP6wUp6Tc8bHeRGfU0rVqmQmrCtwmip5nduVo4ES3udiIu0L7M/8b9MUtJmlbHDqsOzv5vyY0QIXUaQUTLZU= +8p4c/a5VsUSqHSeB2usqZycD37psYBNkNnPj1fiVTuiZUqK/y6FMbycxnb+zen50lRoX/YhV6BJMwFCovbBDPg7DKOmmpDGP9t0ikPTNI/auAuJJoqD86QvppAsnl4iY +KE24wbSLDn1spIcZwLIuO+B0IizzYj4xKt5JChRY72ns+cwTPeG6J6a3IwwqXFceMo+CrRvcoX8bXFoznahLhw== +8lApj2B7Plee0WvmT7TeQp+gJVvFAsIP/JC2C8RasjmndPKNXehKIrKd8oQFJw0AICRsi1vVOziDdgnInw7ZbLHXjmgxEaSanOmRWKVaW+k= +2Poxxdy9o8AvAQ8T2zP50IWtw/1ESBKlEtSbdOmYYsS6yr8YAomiJQwVP+k7Expq6Lu80jKOSPyRvX4XH91qbNTajCBICm9G6EG1rU4tidv1Ji5nLHskR1kxeeyV5JIc +6I/H1Zpit1TSo4efV6bxh+qs8BV2y9sBv9FEAqnH3/ZqxYg+NDJCpT8u2pIoCmXytfZhPXXeD48qT3QbAU0Q4SCBhinazuvJgDCcBAZiIOQEyeYbiY/bJzKU9DoAVQqI8Cdze4vBmc+XF+dN58qLLg== +ztzuq6nAH2ogL/nP4kxvNxCxLpdYh57+FTjjNfzNPkBbBxri/pKFEgGvP6KCQ5Vs4QwY2otmjZ4K4dauoTYR97j+ZR7tXAi9zwKJ35wbDbdQqiV+XNbMqJu8PXLXYQCH +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +wgR07xfoapmx6eEnFHXXYsrtutdMRxYffYBJpExAFLKvOOsdc0KV0E0yQAAQBZkqO2dMmc/KvncCFdasMp/nIQ== +NqhMbY8+EQI8zhTG6Zh2I9n9ZL/a0DhWUREDi0vuSmk/BBGIzgfE/zwz/+PpUEnm +ET0y6SUMPE2Ml4dS8viRTJixJpYvSUNaJEQ3wgnwmDR/Dl0Tv2W4MPgVxUJMaUl1 +/VraJuu2EmN1fJjV8ELbStoy+NvUd5C4U3l5SFxwBYqHlGghTAj+KvzQIxH+57GjdeVm46KwnUg95Vg08AS+OA== +2FZsmfvrIlaya7vy2+sJW88yP1VAikw28x/+NdM1QmN20rxHH2Otz0YftAcEhqUY +xqNkWRXtX9e6RRiV9vfsXA9iVWM3mvaHn9rgIJn1BG49SLTPQQBTTKok5G15ksiOdIjKd8r9S3mNXYXBl6TZmw== +Lzax8ZmAeSERlgYaqH59pNEOXo6zmxKJb0ycj+VARAM= +eV2S4UjiHsDr23JvNVNK3qnbFgIz/SjyIlu0VPUX/wyZB/X0ex78snhE4vu8fZjO +1u+XjG/2+GSQRv6EzCaWRQ== +flULj/LdXe4ShFGOST+HfgiEQqJL0kKKn6iUffraNcUfW9gxdwW9wmntTAcmF7KVOzRDVCHXtZHOS4D78Zob1Q== +Z8UsPk1Q7HtwjRd4g01ryw== +v4wZTNeqSt7hKjjs5rimNMLgdpIKpbxvujNwNW9pnkHVQ8lv2VVqQHVVc0GPFdfP +6/YdOldN7ILWufE3TpQIbCIbF/kDgCBlZUKNWM/prTU= +Ys4bozvJBgtB7q16qcvtBxfJUhKmEI4H3T9j7f29CdE7rNQ1+w5tPWDUNgUfBW/n +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +8eMNwOFVbk4AtLbMoBZ3WTJylC8gGuKXwsoa33uaVOilo+eTav4x4B/CBPvAjTgn +8eMNwOFVbk4AtLbMoBZ3WXGvCdkUE1lCv740F/AYpZTVXZWXz/0Lz1ev0SYqBf0gjG5iXFTmyoAlt7NDxvLnEA== +PV+X4fvCTN/6758fJ9HMv29tbB7O8rIYWMezR27hNavFPJ2Ycj6w0s2fyIPezxVu7xjb7HbbP069Lbz42fGYbg== +ajwXO+19f5mE2k+yWvJMuiZyUpxKU04yI0OuG4s8zNpqZTpRMGfgHQ5hHcdTx6rh8peLn6TF5/UaQ669kk5gaA== +jEPV0YXDCqWCap/VHplvwQ== +bjpI32qzKze0nfrRYYQXm47rkeYYZ9X+K8gA3uq13fQ= +VmVrGQo2zRokW/ZuO9bN67zLZiQVbo3RcQYl6c571ziqQyhgroqSx3gLauH91v+fFu+QAiRnaYq9E1VremwyiHQNgxowiLZvqJOjmX8oSehrQHybiy7vwg1hHHpsU74QN+VQ7upOre9cAdJI0sm7fCv6o+OcHXiE0M+D9Q/OO1C68jXt+dgqcW/XIVM+Oq4SNMnJ6bQVlZuo4m2jVD2I7KYC889jP/b9Muozz73cb6q6JEFuRmQO3t5Us55xVPsD6anKKmnION+bvukAj0IUOfxzKWFoRGzpBr3dOo7uPWTKMSe6PYqHDOo8SsaXIjK/moVXYmrU9qAsAIWUrXUUx6MqK5J2jzuOxNwAtvQjw0irPNvO74o2wLVALenBCx6Gx1zrJQNND8iW8Z3EF3A17kjBoqVWWP8CBft4xk5G5Pa9fh1PgjoTxIEtiPflF81bxIkXX92YUpPvO+NJ+BSoTTwn2UWrfgxFNkX395bF+Xtu8gGJhb8PcG368JiIVlPa +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN65d6SIfZo75FloW0FIxk6EHEMEM5zu3rfeKGq68emUxdc1vtMX03Al6g4yk1YI4aHnCDd0S0QTT/RMg/s2I1Kvw= +iZaIsa48RXfE+uf/yF/rQHaH39lOhItuA9XlMsZGwHM= +PxQyEFNgEUrjep/ov+AWf+oM8xywLq7k8twOsAgmDnhwxbYPjvM1HR7ZmztlWsVP +iZaIsa48RXfE+uf/yF/rQFDamMuGYoHcHvig6IR7L5g= +iZaIsa48RXfE+uf/yF/rQBHzBiyCImLvDpqO+6hTyP3m9lHPXPQIIotfEF+lox/PQnwFku2M550Yae3EjKfdwSo2zaLVsBrXdy2HzGwErWc= +iZaIsa48RXfE+uf/yF/rQH2dIzSy3gkyzLeqVCbm3eTH9zmgrK/v9VFjQHkrschdvsu31TnW9AJ98XajAcXV/xJabWM9DvIsHVJ0epK6pbw= +vlw44yX3fCVt6eRmFa/S8+zcDVCNlbkZPwywv2/TkEdjzIX1a7+Qv5GdodWIWjhtmXWPTEcqpjmiAgwl6lc01A== +e1fY1YJpbz170V5bcvidMMj8zZEMpvaxNEwOhd5zodzg9VC9MnufwNdaYU25e3Ap0TjTYIXJp3aftiAH9jPWNw== +S1U+ozOPAZj3pbrAwiivwV+OvqAz1d1sZB0EPsOnolf+IN6OHYpFNOp6+2SjXYmV +iZaIsa48RXfE+uf/yF/rQDuGVEzsmF3CtJPekR383wxEc8OumBnbu+FZpS1sbexmU4IRN0hBGAisopPlzdzamXnnzur1Wji8UNl30wxAtMET/A4MwljXqYiRMkHmVxJs +iZaIsa48RXfE+uf/yF/rQBHEMyc95ROne+WTG39WCVaS+cXX9BOUlzXxE0w6ot+Oc3VNAQ+DhKgGIvQZHJ5c6g== +iZaIsa48RXfE+uf/yF/rQO/ErLeTILJ36v76UKS54Jw2UX3OGfKAG7yS6qfIxugHyp2S/OL5tJKEWjUVuW4IUuzQc20IvoIJtKzwy4FpzeobJXfdFFF6CmRLQ8jKXRiVYQeTwTiaZHk9oJGEGYazRQ== +iZaIsa48RXfE+uf/yF/rQFvnSgMMK7IUZnw+ay1bgOmW6qSuUZQTz2zXyezTI5+d7Fsrlel7B7kVccTcSm9Pfg== +VmVrGQo2zRokW/ZuO9bN698MHz+PRq+TRlhzLxtWGMygpukHf5d24j778t/zhHGidh9OMwub9c9vsi9i+zZN4Dri9QnMhFeT7rUsrCUJo5TwdvrO6yF6zrAu1/LNEjX/ +iZaIsa48RXfE+uf/yF/rQBogmq+jEWWC54y/H6MlNh4rp0+JMOgYT2mZY+TYp75FCm5W06pUVqWJPDAukRPl+ZRIueWws/l752pV//pYffU= +cdjMZGd+oj+E1aBgYuadXWtaC7aSClO5q/k5YLsaTcSQLEvmsyDwCAB+ds3GneYkbwwp8J4mMiR1G2+0Ax7KAw== +cdjMZGd+oj+E1aBgYuadXaukekpOTL5WA5BiDC9vCxPuawlZz6j7AAZlEbdZdLQ48OZE5ekQYbVCOAhL/BJpig== +2YIp4yADnjMGtY1lwBdIGuJOMZTFEHseAqpUotVOIPw0VLHXqqJAqDo1N2tbNar0q8n9HgTNNBagFvdjmshZDw== +iZaIsa48RXfE+uf/yF/rQANiUB6WFTzoHZ3OwjBwtM2/j5761QTS3AJCOB+qIm5mr/5/eVreUywu46shO608Fxk3JiSLg1QkYCinrxbBW8I= +iZaIsa48RXfE+uf/yF/rQKIqqcSbvyJ6nKs1nu85+fMRjwAg8Y6Nh993Kne9OE8bYXDQ+LuRcreEaUiZDN8aOyIbEKMTvERwYtJMKUES04k8EwtY/LwkeYK3mxxvwexvEDmRXbKbEGBLl52UPJVJfw== +iZaIsa48RXfE+uf/yF/rQJ2YbiBFucc3UQ/ZX8J50oEXgugXrduKe8SAiE/mA2k+T3N5ZO8BVpH8YAKvC4ZR0Q== +wlLHv6kT3Q/RmtMBN4nDAe8+imtRBuiJubS60XFrMJg= +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN6yl77/0MnObCK5XlSs9DWF8eqyYhe1FWHc8xaEa0H0urV7ygkURnv6BiALfKwfgNY7qBO4kuhmbf8nJ1oKsf+gyy3EG6rLIgfnG4sQZKYsDRT9AFAoDMtdIUBl7ovtqbuQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw5bx4JTsUXc9GQD7jUveTF+h1GxK2r9hBJbi4mkX/JP2 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw3p30izicGP0/3QQynjVV8QfCMdvBT/iicXrGxILnxT7Flbhb0PBJRFV0JDAAWvxHQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw+sgN+NoknzWuDmsXkDIpaqQj2Lw0lrCOo1L/C3blJaAgKXMvFjXCS584QOcKTtHOfudZ11Mj38X+gn040NpusM= +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN6/VW4n84N+xmKyiHoJANXzc= +i/p1vQ7ugXGBvwbjECApz6tQ3fYSsRgetdpGSwwJdY0= +wlLHv6kT3Q/RmtMBN4nDAfKuWKTPnQzOUo8b39dYynKTIm9yduqnJKjcYCIOYNl2Ve+9X84vGlhWQ4dv1oTdSWR22oAktUUFZmgLi+55bm4= +VmVrGQo2zRokW/ZuO9bN6+zshBZO9d61HkhcnvN87E/esTkTggViecgSKvZl2/jilsnJj15aKiHc7zivB4GiRjN+hiLn89qackKeknKgOAkBsBvnq+NL8RWD/NHkUD0E +VmVrGQo2zRokW/ZuO9bN67EO2w8TNG1LHiP/tGFb6V2Gt2yQRtjzazeNr74m0atzaPdvLFX7XyVVRswklh1k9b4jQHovg+TYl7Ff9JiJYPcw7bsPs4ffMxTkD62b1LTR +VmVrGQo2zRokW/ZuO9bN6x1lQigDbibIoTlVRYg/sjBbtNt2NXe9ly6pYDchoxSFm8eCigCwEbYkrmTqSpR66Px+l9G0RWse6wD1iaMwlGH17ZZODY6LHJwXbnnoehfY3dgu5gHZ1QEdRO3HBSEC/BmkcWwlcqCj5HLhmnA7jVY= +VmVrGQo2zRokW/ZuO9bN65ZXaaTN9OdZLZFqE1+KS6RnzprQpjuF686uW9l1a1Xa7mf8nAsb5dvwf2nXb6MyyeGLjC6K1kWHmAxqB0N5Lhqbqh8cjSw9EakEYrt1pQmzU6bWNZ792Zty9yar13+Cvp95ctSpW2nDI1vJq91wABs= +VmVrGQo2zRokW/ZuO9bN67sdU0BGc9v1NKjQ+sFJyyAVqCbBH9iZ1H+7Xb4dU4gwDiJcnbDr43Qq212mGJoiC2DMD5ackhh4eB3W7YaQThFl4no6PKCk63hukqrWGYjX +VmVrGQo2zRokW/ZuO9bN6yl77/0MnObCK5XlSs9DWF8eqyYhe1FWHc8xaEa0H0urV7ygkURnv6BiALfKwfgNYyI+kia9//G28k57ZB6qNEQ= +VmVrGQo2zRokW/ZuO9bN68m4dRJxwc9vmoYO9XfNu8JoAL0ANoHBA6wRLssRJG9d +iZaIsa48RXfE+uf/yF/rQJ2YbiBFucc3UQ/ZX8J50oEXgugXrduKe8SAiE/mA2k+ZzCTJlHCGGr+o1zyX0J7dQ== +iZaIsa48RXfE+uf/yF/rQEW/HnjzbDWg5QiOMKFgUs166OxnL4KP5EwHDMXQoCQJRRha1BVSLbU7v0IGpgEo3Ow9rETT+gtPN+XqbTD9O4VSY9LLGQrxzpdHx4ypB9/+ +PMWbmPRKnSEEQmuoINHGWsR4WdhOdaCB8ldWDbzQF20= +wlLHv6kT3Q/RmtMBN4nDAVAwwjXMi7bsf2Urn5Fa7BXRsDUkBXTBYPEpaEfSPA3CViaW+eHPpTOr1sqaxfsUEJXr0r8RinHLoMwf+yDuYSs= +VmVrGQo2zRokW/ZuO9bN64Jqehn36bioUDDk5ORDx1UfKJPWwYobkwrB7mW4yC0E/yspyDpCiSv62gKc5GAi9T+y34+JwM/Ix71XqqYfqk6wuSlZIytENGwwf/qssogjxCLORgiVriYpcnIi5PgXEg== +VmVrGQo2zRokW/ZuO9bN69lOsOGGAluHtoQsF4JTTD91qLyShUeY2zrHp66doa7mrqRGy6XrHMPUUlJQ9jQMJN0k5EJO95P+i0RrsO6IfE9KsdjGbXe06Ke4++h4/3UVjycijEWFMnGV6uvGaxuhCw== +VmVrGQo2zRokW/ZuO9bN66Bx+QcaotFFc25usOdeBnbTRU45vVMOgan2bt8crvVKWdMPA2l5zQM2ZMjHmie4Ostb2ZlRvEpt7614r2AV4HLy37BZMk+x2Nk/Kbj+WVTFsFKkwVRGLBsFJ6yqXmiY71otfHLUQGgM8V8B/4spf6Y= +VmVrGQo2zRokW/ZuO9bN67sdU0BGc9v1NKjQ+sFJyyCMLjq+xWwbZ8c+ddHAwB7BKoukMsMxYs2sswO22RXml2KSsLww/KZ/8G2d/WiAx/rgyHeWDCW9UJufFHa7xinD +VmVrGQo2zRokW/ZuO9bN62cg3MFjF3Xh1e0EF0hJpnU2g0qslcYflC7df6LH5ycv96oJb3iOdLAMnhFiSSXOjfbj/n1Y70ip284H5cNfF+k= +VmVrGQo2zRokW/ZuO9bN66RJ3wohQFXs+QBlSfWZpaf0OWtwRJ6BrDTWIqv5NVGm +iZaIsa48RXfE+uf/yF/rQEW/HnjzbDWg5QiOMKFgUs166OxnL4KP5EwHDMXQoCQJDHEHPrnpFeN1JnNSnouOcA== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUTEOYWDoCep0UQjwSxVF0w9eBLN2NONNvbpF4CcDGkQA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +xWoGNWjKGPfI4gq8aHoTfBVO0rtzJGwPNNCAKcvoQ7YGnBVlRRzBkPuuYdnd1drISaN/5vpFnf0ao1Hkw6ztew== +1u+XjG/2+GSQRv6EzCaWRQ== +piplqnZD2qKjxqq5BxcYpBpGRwKsiHJxwLqnhEe2D48YtmIu0TblS7JCQluznId9 +uCeQtkgRBtN0HiqPTvhZHeALjPy6nS+/Py9QQjy3Ju9z9JZP7KSWkptweIa2bYE2 +cgFNMoW/KsgkKsovM6n6KO+C6KjDPWoUE/hR0PgyXA7qIltJH6LvNkqkyka0kjkn +v4Cu02hZRDsW+85CLRBC581P6PmAWmaUk3n76dbnkVw= +NmkgFF4ZsyEHu9KVT53La4WGMwIVFvT8dzZ/QJmAvrfSZPlY+eRgJ/IGmWYM3DiSmAIvy2CFU0T3w56mhC3RAurFjyBbAOsoZluPEh0M/sY= +YoCN/YJ3FGpKuVn0NXtXTv3XCu2B9Ep5OJG8o8tyakhU5BRvAk6zZXlPRk9etr/0bAiCaTT4Xnp28zI72QYmocOZkM9EMjn4wi9DBmAb0g6ipSHN3dmHucQQZtxoUbEu +YoCN/YJ3FGpKuVn0NXtXTsWIqvly5o9YazCVgAEKZW0PTnKV1DWqLu0lRYHDz/YcsBnA7SOwZuYWWRON2HScNg== +1u+XjG/2+GSQRv6EzCaWRQ== +PyAFsma1q0hLhwE/7yEwvlLDizBd59bTOK0yaWmk5wKN2EpZqyKHG2wdBEZW3kAlb+ZRqbiaA2kRmKB/ClkTYA== +DB6Dm0fkcUc76J51zO5BsTwaw9bnJ1hMDPyvzSAiR99N1sM5KJNB3yakje8btu8/QBHP8RxgWzfFi8SXTQxdwc2MFwLBmPC8Kwdv5Qni9ns= +C3JSV38/w2nvM3I7TZ5+4epkOMkaWYvoOErk6ygROHs= +UaGDE9swb13AVV1HxfNyqi1KJFeHDs773v7JZyf+ME4= +mbg4a7OqVLuFyb4lBv/8ihC29C2cveBqDqu2E6PhaRE= +meZhlBQhcKhD0kZGoC7z20E7608oJ67KyHB8wGHG8NC8h5uepI5cSAMadUxFvf4DOtyfgKSQxnHoEYtzKQUcloe3K8rw3Gke2CQzL8DcGYs= +p6i8iK7HDbvmLpoFrhNyvxdkpuCzYcAgEgoD8gO9QRA= +rYd5Q6Wra4KSueZqSRRqN4gDfkYJFjFLoq8Y/uFOPjk= +0aOe1PRcA3iTQeyd7fu/UT3OH8TgnUZIetc0kIThlMY= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN63igchzsu5zTaq9IXvBjrNVT0Ypem/6Ctem4lX8VHAu6 +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1hs1FfIPISXmd7TJ/r+Vl/cQigT5kdrpKyW4rHdGgtQ= +VmVrGQo2zRokW/ZuO9bN60XiiQBl5FEkQ6DHRSd+Tw111DT04Hh8GF8qhcK0KSh/ +VmVrGQo2zRokW/ZuO9bN65hNsUFyX6poB60L31EhX7HkJSxkQfY10wTx/PiHvRMW +VmVrGQo2zRokW/ZuO9bN65s61AIgyJ8ql62IoneEj2fAfjFSAde2JX230qlgjnH2vV81My82sV2kTGC+8zV2TQ== +VmVrGQo2zRokW/ZuO9bN60EOQAX3/UH5DkusEZxHTXPrNTCzuXya4iloKJBnudc0 +VmVrGQo2zRokW/ZuO9bN689+A3i/MUSda6G7q0qItIjO89bRKTthTU14pzjp1wZx/IYyafQwuUcVBEAoyeksHA== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN67Ta1/lbp6yqDJ2hGru87rzFFPN0VimnlC4upIFz9Ysl +VmVrGQo2zRokW/ZuO9bN69twiVqU/YruzaZ31Ozd8kyG92yD5lEmoQmdiZOEP8KmMgIvjkFJw9Dw8H1UfQ7mow== +VmVrGQo2zRokW/ZuO9bN6wMQRwD6rXhLUXMdURcL5Ag= +VmVrGQo2zRokW/ZuO9bN640QMRIb+NfIAnqkUpUM0UyM1UW5nFbYvKPvqsQY7qs494aS/4n+dnRqG6/WBFmxDv9kb9uX+yopcF1JYP/VDIE= +VmVrGQo2zRokW/ZuO9bN62xFfqul5F7aMWtovWOJ5tZP1bJgyqz4vpQ1vcu9ZeS7nO6J17Baoc3zlb7Qe8qElLM00WI2XDnSBgzQHmlHrIgvHTezk3aB1hm9hvb+Gv4T +VmVrGQo2zRokW/ZuO9bN68PIFqUsDNaIp++rUmuGFKn4o7M3pUhaRcA1ks5Adan7jnrB7ZtcIC8KAwdazqXsJQnRsz/VyPAD6sC3mSzpwPRfi2ghVLotdjoha+woBHHlkbOiZCUrVfc7uglqYXgMF9aodbyDf26K3EEkt1cx5+Q= +VmVrGQo2zRokW/ZuO9bN6wNy3dMSKzw1+znxXLOAZLh5FEFcOrAX3cntmkTItZyKKVGgHnm92b7h7ork2RuLWUmLbdVgqwxu7Wl8pXJk83I= +VmVrGQo2zRokW/ZuO9bN6wRTb++fBMmvLx2k6/MEJBys2MeDsfiTOU27PQGTtFwGzoxQQOeDpqoXCiSV9odxKw== +VmVrGQo2zRokW/ZuO9bN65OUJfJvgNPZipZ9GCOTw7gT/VBpBNqR6eHMzDCD+qxc +VmVrGQo2zRokW/ZuO9bN6/6DN1Rj8W+FylDHNZarY8GmZYJIJsOXkIFPArsJz88a +VmVrGQo2zRokW/ZuO9bN60b5Dlj8qSCLiYdFYQye1vxhQfzXLk5iO7kCBpmwj0d3 +VmVrGQo2zRokW/ZuO9bN66Vj2MTf6PwKatUHlMDNJnAflwnI/tv9py6bv+AQb/3Mh1ASIGKNXpINw5P13Fnriw== +VmVrGQo2zRokW/ZuO9bN6zJK7VoTu9IkzGKsOyFHUih3nKOsBegITZ98OzfLK1Ug +VmVrGQo2zRokW/ZuO9bN6wragKme4d+RqlA7wSuKiun6Xqgs7hKkILO2biA4qzho +VmVrGQo2zRokW/ZuO9bN6924SEXfGwykaPMoxSMlsEcQAwBCGiNnpl4AiLwthDY0ogr81kYcskyrJDT5/Q87vD/ybRw7C5VoTHeL2G9HfhQinJgPrUBEI1svbLGHdxDN1m3m+AH9xwXwqMSg3chqEg== +VmVrGQo2zRokW/ZuO9bN67yPtu0N927f4HIlE8Q/3KEjZKgRfvyBEFqTjZArZjS6wWjCk37Cjc7Be61nCtFfog== +VmVrGQo2zRokW/ZuO9bN67oV8rbNeQxyRr5iXWNRT7jCGot63P1U0aZqnfo9iER3g0jsNJ4bdPmlVkmSovfUKEE8j1q098bPnAUmTwYv2U4= +VmVrGQo2zRokW/ZuO9bN61XvIee7O+pzg0zO1do+jmn7+YnxEi9X2xYlEBv1SXfIIfgDFycnDEX3zYsNHvW2uWPjibLCY7dmVLtuDnx9lOI= +VmVrGQo2zRokW/ZuO9bN6y82k0/7L8OC4IRsil2RtO1tWsTIDg48NsS7LlukbCaWEQq1DKn4luZ24BCSmDqhgg== +kJxoTVctk+bGjMYgKxsjjUpPRSfC+WsPwpMn5FGcCWkNpbjYvwc8oT1ZalJ4jAtf +zxNKIz/Dwj9Harw3dUNEQET0V05gcgtC1p+K2Ihkd0Q= +ACEEdgL/kNbx7RaZcSRxZGgMaBrP/U1Wsj+kqixOX3U0XMejpRw/rYT5i4ihGDYP +pZu1Giln7AYpCOrLbI27dxkWuQIzVUpxHeSHCtkSzeY= +pZu1Giln7AYpCOrLbI27d9FNbDQGKqlmV3/n549EBNY= +IhuisKim47k91RVt8z8qtCsumyIVSOp8xbUu5duXman+sOE6qwlPIuSaUbeysSjj1/coIZqlKQRM13ITsP3JTZKUmxD+QBryE7TKwJbeYvU= +DMKsax7BkTxm29PYxP4a3tCEAD0O5XmbLDroN8+cKNkbya4muwQehRDf9xiCNtXi5mN/bz7SmZm6fjy6PG4iu68u9ENCgu/bVtrtkv80Tvw= +v4Cu02hZRDsW+85CLRBC52OF7Q2oFtJnu5QwKIMu0BP97gowulUFTCsZQKMleZfSYIjeGZBAO1CNNX4e5jvdjNKUV0vrbVg3URKLSmZQu9pDsE/+LHEfAr1t4b9ExGJ+ +lfQMhYa2PjTGY/59jLRb898KNkZ/P9VvrG4wbt0MN94SnvQg6MxkeddSbK4PThoOhH+CDexHx9A6J1shl1FHCjlWu2q0GlNlu+H1BQlcdUI= +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +aEZ3meBRg3VU8t9WienCaIcMPcHcv2YGqbGshoEf2+BjZfFHOMPLysvgDh5kRgilD8P3YgAMi/iJ67yNmlvr2w== +mbg4a7OqVLuFyb4lBv/8ijnDf2TRJk/x5VtAuD8Lei+9UFA+RJ6F6QToHUji16bw +XisXz5DCD82wMRFAyU2YnBIQChWrgJ4+9q/ztStU2Rc= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6oNJYJ3oKHQnTz+Hw7wWSQ +1u+XjG/2+GSQRv6EzCaWRQ== +2STnKtQfadtZuOOXKAyG97fXc39ov+eVBLi4v0OMsFg= +RGF+I9YWwlm/IovqReh8KzDsktfMik+IEctOJHKJWNfLhJfd7lO0N+PqYXq68hXW +wa6Uqxieufh+16mIo2wUWZ1mtKb/vzs433ud/yv/qeNzIjpgHyPB2b9qHji9uppV6FpACIOY4mogpGhOh+bSYg== +kFmg/dBFMjlvuWIcUrXktJM3uaPpOUszNWRf97JB6UU= +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +c0BvLCajy/2XMKFpBZTl+Iu/SkO0dvuX1US9n740cfboKQmRZYgu55JFFv3bWRzs +xM38J/T7qHoyh2Dm7AS1PeofVv7nUvjVw8J08A7tPoHKI8AfdUEMMT36J2TuAkXa +DjJQKue4V6m4p8kNUg01bEfz/+OabzCmVKUlV+DraSJuEtoAE53HRREuE0nz/mi5 +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6oNJYJ3oKHQnTz+Hw7wWSQ +1u+XjG/2+GSQRv6EzCaWRQ== +8asfyBO1JetUFwe4WyYO6Y7zXLxXmxgBzDu5xRXkg3A= +JkzYefpr/Q8/FMT+PiabUnsjVaeV4SXIGL0ZfwaksKSkUz13n6Mkp8jdbSsukdJu +MzOj4ZiBOJDNVP8vi/lFQAuCz8rMEK3CmFN5hj1hief/YV0qN2FHlXxmdMK9Ec5W1rvUke3pinD34K770i83m5kkbUBBkOOiDPjIkJrEKow= +k92oDIqD3u/zM2W83z4PpVdM7SBGKtVUm1Jd80kknUc= +gyj0tuy4SMPFYgH4w+8wBY3IXPM0uWG/GrgGCA34VZw= +k92oDIqD3u/zM2W83z4Ppf0NYHDpAym7jFb0+rpC0vUFZlWzp5yZj+qpxxcuNbrk6Vcu1GyjgOQWSHm+7DcKEQ== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz4GANtbDXFyZG+JtrIqxTR5VPNmuw63EUywvzLoQm4kQQ== +1u+XjG/2+GSQRv6EzCaWRQ== +dq8ytOoyRUv/BeomAnpfcgN82WObFNw9OWpp+ashMco= +JkzYefpr/Q8/FMT+PiabUsLDCIfcYA9Gq0gv0IiQYerPKih0OKW4CHu8oewW1bF89ddkSdnMNn9sQNfx6cbWWA== +pTLYmNVSpbV89ewuJgN8s5DQJklspf+KlgaHmhcv0nA= +e1rLLbcLg7HKd8syQ0gecaIghAO5DHIkRtrdHrEdJec= +v4Cu02hZRDsW+85CLRBC56CB7hEAmjGZ+HomKkZKk+ti0sMqpAvIK7hwFc1+6mkIIxY3pr099j+UqOBi9ZahczUxI7FqRqgGWZSM6Xxqdbg= +uTEK8Ng11d3ix2pA+DD/aVQ101h9KjleSwZ0SYcRXd3eEzJzX3PloA8wBw8sN35krPKr/MyJpFx412GjefpG0Q== +uIsEpyb/BmTr4pwkQVVUf0RwjhiJQJf/hK04tdATmnh19x2HCfTqNvTq2rqHc5Gq +32pdC9DD05OE2l0oXazDFAdV2J9n5ffNkz5f36qCNytDp0iqq6ihW0KAr5+/dls1 +32pdC9DD05OE2l0oXazDFAdV2J9n5ffNkz5f36qCNyvubBPOn8LTVbiMDS+ibP+zvdPiwG7MZt2aBqQCUoiqByNRY4+XgGEEEtqLhpLRFzk= +L0eUthVnpkGsmKFAX6d+uOiK47GeV9U0IUhNuINx/fs= +e1rLLbcLg7HKd8syQ0gecfbZMbyjfRdQUOPnf5zIbTPr+V4LqLNiJCjGESBeFgYCEsvn7CupaoEzKXkwj2CZJsK7E0mq6ZE14nq0QU0PXcR3In/aUlH4AeU8sl7MkPKn +VmVrGQo2zRokW/ZuO9bN6/6UgeiotxsmEebrhxlDDLc8nC6Lwpn76wNAK3afDRnhMLV2RBSQaGynOLl28c/t+A== +YoCN/YJ3FGpKuVn0NXtXTgdEcSPVapklApkwW0i70PxTxKlglILzchhWiOPKZgOd +YoCN/YJ3FGpKuVn0NXtXToix6MqFL/QoVtdwEBkml6COQw8TpndvWpwBpvKNQMmTnxpUY/oF7+S3o40O72xFIA== +qxpZEz7yGh2snp/cK+0Ipk2Qv4TuHjOzatUb8F4u/nzLmmMsGUaOag81kxmq7Mlj ++jcV4UADuSsKDRH/jIi95dA2FW0TSUBn0rCfhGruLSE= +1u+XjG/2+GSQRv6EzCaWRQ== +glzTAHn8hffaBHtijzYL5UK18RwV0frcOEbqdQEdG+Pt92+be5zhscNFhL/eAiR9Tc711rcl7gLGuXJ+jzgMU+aJxXXBy1+OdZ5rcxvu5Fekj6TIakCimT7wMqEJ0BifU67RUtePcHtqliChztpO0A== +JkzYefpr/Q8/FMT+PiabUtwVJbCg1UHVw6o0ZG8VQbs= +MzOj4ZiBOJDNVP8vi/lFQAcd/8FSbCN3RDG7aQCgrl3NnBpIPZP1CuuiXlKwBhUw +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9gIulSKHdQPn1rt3eTIMEsl38gs8lF57/xlhqgd9ITkeg== +HNr5/wGY+7coHgowTe94LlheFV7l4nvszHiASjC/wO5DiwIYi0sK4qdLmDmUfgwDgE7PElyE7dR0LMY6vNGFCg== +vxZRPtvgnx9GXrXB4G/UM0PDJAHnJfAH1EU1B/RGx3flsrprR4JXQdk2AZEllj6S +gEJ/mwYAYmzZtF+bOEtqdkC8SHP+P7fOYSe7UxEq8rmitn+SghIfUQayzs5YBbRY +vxZRPtvgnx9GXrXB4G/UM0PDJAHnJfAH1EU1B/RGx3flsrprR4JXQdk2AZEllj6S +YoCN/YJ3FGpKuVn0NXtXTnef+ICKXNxnrkPoLNoM6bkjgMdyZRztWKGvv67a690e +vUSdI+D7GGPa55xHnexsw+oNL/ikozXaKZGm51a6QXc= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +dq8ytOoyRUv/BeomAnpfcvejXPvE7NiytOq8XOUMnvml0KjFGpiByzoIln/KufdW +uCeQtkgRBtN0HiqPTvhZHcPnx9uQcC8BFwZstE/shHLyfSjSJNhoIYesa6Py6Tvp +7V1Z5ICwJM9j1fYg0cpccfMkJlFKvknmjzfobvmEo2Q= +AJ6YSai03eOKVxBS+s+A+/x4xEbw8mjQ70I4HsvrlCiUwZbEJ9HrEH9cZywfcLSS +auZi0vV8Th1M5xUz1rxru4RteY+AtM3Jk2k0FxXVoO8= +1u+XjG/2+GSQRv6EzCaWRQ== +uCeQtkgRBtN0HiqPTvhZHRHF1iwllZSPFSMptoCh8TWuJckkTFe3/T7PShD60DdF +mOVDVEmzgX6HnqY4rlOl8Zf58jGyWJmgY+rfwoP7Nv05pvO42TLHLIh+VtAUs/DRVclc15+ZQbtHBFMvjOH4+w== +1u+XjG/2+GSQRv6EzCaWRQ== +bxxa5unxulFhVIdgYf2zPoytXjzesmqgb/je+IsUbJ6JAnsRAoeAvcznZO+AYYoOKvJU0MVF4EHkDTvwO3xckQ== +0TsdrJvJroIDiAiNTYLCm/OphXNS2tzCddUWOSuFG0kG0hMQku2PUAi1ctdkxSUc +99nPav/MjT1hGMzUpnNFmmALRQAqua4s0cHovmRI5xqw0Ofo7PqaOiLu3iG2GYZ1 +KbLSYqE/CR9TL3ZELtBVgxmWrwAXvlf+ZAt0WDpGZ0g= +TQRmC0vOvJ1P+lfyS3Os4RfKsX8MHWdsIncvcLq3grDmjzVmBg58wHqSjU0RBeX1+yZNUjYibbjDYJ/2Zs4yH0t3SG/MIh4xPFPDALQI6FtMsmZN2Yf7RHU7vBbfL59WZll21jpCE6/0Kt6DwAwufw/X34CHO+/JlrRfRgCU4B8= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmV5FCFffpkRLBKsesx+UXBa9a/zzj7MC/CeJG+1iB07DF6gupL7r7q1OU6svPh0mYo= +1u+XjG/2+GSQRv6EzCaWRQ== +ZPttXNTCGDK18VzoR8DzYKyka11GnDaWIOOQV28/TIXqzJRwd+oVPa9D5jLMJ3az8mUKvRlRSJMZkBfK19w0h+r4ZtAuH86gZfkH0Tx/+KydTvaOG7bIgsn7F2KqkC3cCZfprRednK2p0WuLxxKOyg== +HJa3BH4TkRX/a5B9uAn8vA== +zfLEQOz1g23nqtChgBMTbhyiGXGhWhAY2+qwZtKU2zs= +ypyfLFF6HqkDeg1sXel/og== +w3rdXVV9x6u3KSOstnEsm4PLlBhDTI+8FdH5E2Z7OE0= +IwoYJLA2ek7vWcssU2p6YbPP0gHTLA4HmzobpfTCWYhvHXK7lgr2+CDhRu7uMpje93SAESWlp6DbIzT7VQNW/w== +jKpcG7quCcerLli2mJ39vBolbg2notAcBVKYoG/EO8o= +2SlvqX/WQojOSKqcazOiBA== +cBMX/M+ODyL+hH/po9GjAA== +EL5E2qD00QeSF0FNMBo12pQWeIyq9cNwgIZqd4VEy2c= +ZVauRl8efRxWkkXsUWoKPg== +HfHZ0x6IhQ8gr8sh9V0YAAATEqIM95DuxsRArsnn8ys= +1u+XjG/2+GSQRv6EzCaWRQ== +ydM+flUAneoazE7ph9Hof3rbIj+sluwHYaxCWsNBFhE= +P5FSiL1+Pqt0jNjlHNtw3BfOokQdNQF4so/qFjceHA8= +YnAdY30Lg/9fjxdn+Sc57PQiaDoqlnlto13gLyGNa80GKHELy59vY3a8LF3ptTbE8YxP/ayjze84gz3UubVSlu+mhbshLGLgU0vN/KTSqU0= +h4lKXT0d0p8lHVvwNQMn+LKoo6D0qtuqywubpeGhgjP1wmiYwY0D/HtlehtN3aCW +4xkKxP0XIvHb1D4sJsHh2gZjH1Vo4xijw1ZC+9Rm+a0= +NzUVwIScy4hrAjo551BWpxLBVrqGQSD9yBLKdRgxYx0= +ft4O+SUgqJFurYX9lapEio+F4WkORg7Yc5dRgf0GAKI= +CDLUY0WWS1opA7rPYYGAHqCEWLmN7ESaQ3cddP6FtEs= +D3tDGThV5uK73D09MDbkHD3JqWj3V3TF8FWWecx67z6FQM8aWFJD78O2J9r7oGW+ +fFIb+ccq5lrZDdMOZKeSYxPj8RxEZloL9mf43eMdLFg= +DV35hS1I55lzc8Rx3j2gKq3qpS4DGEY60WsG/QAoTXw= +tbVOTg+NfLlTyWxlgN7BTrKZG0+dC3Pjrp6KzKvtS4A= +u/w7lYvOA4sXyLYt/Ceg+dy7vbWqHKAUinXlMI+xkz0= +8NeoLWUN/HbOvEj4zYku64g6MsBHB3pIxUY9k/g46pm3Nw8eblpWcMTKpfvJRflp +661hZf7vhUQ+50okfwfTXw== +o+/LpOe0noTt0q9uY7caTYSaVB9I1gwyK4s4NWQ7NUA= +96orka/uERLyRst14azQwhCOqhTfcgFhXNAQS0hmuOFuEANGB6S6+TlaG45P8xM6AOLGkW5sS44kMoI+hpOgpg== +1u+XjG/2+GSQRv6EzCaWRQ== +sDEbfywocoesN89hhGJfbn+Up/JgL1Qg2VHfdBaVSh8= +bdMrmpuFQ7yM1zhu+h/eLpcnNtalyd8b0N7ueBW8qxJW2cs9cWz74j8D2CGsBGSf +7ruGyOsuKMUu2VJjD7qOMeaAkd8lbFxmFgrVLxFs0oPIjIIPvkx0ZFyMjf6oRSyt +w4I7mVFZGDKiBbcb41eQkxM4nnk+e7mIaguT0WGkkVtz+XhNeocu4Dwlis2SGs+uEVlFlJ1TTROvWNVda6V51XiWTQVNeeq+mwE7orSeGPlP88A2APkdQ7WXcpGsRZSeQTuqKraUSJATMjQccjEKUw== +VmVrGQo2zRokW/ZuO9bN646KfasAt/rPZ4Mzei/x9X4PK6kawVkqvHaQdE9ItYIrN0R1SNQyDXs9q/cle8yTU7duHnPCfZk7g+UKQZaDYdoQ1egSYJLdZGNCNlpNm7lOjvo7TlphOadcZr0WUZLKDg== +a3jbUXPmyR9Vb7Xvsw0w8VcvNLwUIPucqyBxSewCjvpNGf4qVC9bC/Kq8Ub7+IjB541pebM9XViq+TAyzXBMtnoEC9wqaxeFrEhoP73Ucb8zxTbkd8fs/i4yUk10fuvtF4yZG4ou3qTgQ9i1iaCFJRjDi7GvIiufx6zM8N7OwL4= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz40OtKZG76yI8EPfLXQ/ptOrhgtHi/2LckZEebSz/acKg== +1u+XjG/2+GSQRv6EzCaWRQ== +dPf7g+b79gcCgal9DX4pOJHyAfVR/z7s6ET0GpBby7E= +0OsCC+YpIPphU/YpLoF2Kzv8rZ15USoyP9EDEDNoe4E= +lNoJsK2hWEGOaXNpZFMEKYAMKxXzTQglIlFqVZ/qv90ift2NxK3uK30zwQMR/g2E +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN68kG9x3f+w5aFN8e4/3SB7hYxvzV8Iokc8k375ZmlVH5 +VmVrGQo2zRokW/ZuO9bN69n2IH9Vunz7hXYmWIVxfwPXrXJCVKsHyYzEby8GZS9u0PX7VZIVelGtG52pT513uo3a3aGreyDkMtDppIaQp1w= +VmVrGQo2zRokW/ZuO9bN64wauWr3TvqFolPCIg7VtlOeTNs/abgaVrpCuUHCHrpv +VmVrGQo2zRokW/ZuO9bN6xoSQ3etHchjGzx9GgAoZUY1EPK486h/g4GcxqyTNCwQ +M0ZySqkmhuHCw6olbCKv97KlG7pHnkVH5PzxCv3SOm8hBQ62OlY+Ty/yX79KJJrdNu+CHQd3CPIkR67gsrZxGblpwz5ac1JVqyFJQsp3MvnS9K1ZYGSHoLMHlhFGV3zU +VmVrGQo2zRokW/ZuO9bN64/n5EL8V7CIqhp9FPzHEuQ= +0OsCC+YpIPphU/YpLoF2K6FQb9eutkS0W+twNd64F+O1GZYjvNnJ0aTvS0PBP4BFzU9cR9pgLcItJ3/8oa4lp9NS50ZiYATfufhv/OY4HvuGJ1DtfPl2w5Pl47TRv+1Q +nOflcriAnpFPxVtzU4aISA== +xUiJWUyMvL3eKTICfxM7FHlF8+ynsJSeQ8zvbn8P6ldzdd+1cSzmI5LPDCIBQ0j1BMY3iQTiHHXKXbMlTNzbIg== +4lm0ybc3aVxLaNQODc9aBanfiyaNlwa5Y83cl2wzTJM= +GJNoiDRZuv2t3D6WjWIl+qITVcmlxn4NEeTza92NOQJaBcd0iFalRE3MrJWhGMGx +VmVrGQo2zRokW/ZuO9bN63BJBJEXm+rc5N8L1OHvM55cAO+Ku+qj4muqvW20+pVMp1wYb7OOSX3teOhakn3wUJeQSJL3mZUL2AZMqu0/OdPAAjhra4OMDXmn6v6Poq55Oql860T4eKYpMhnUuGUa3A== +ACEEdgL/kNbx7RaZcSRxZL+LzF2dVZa5hwngNS0Drfw= +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +lGY+HGJ/DlLufZHWWlj7U7tQubWSU1u4v7fNn+PU7V0= +1u+XjG/2+GSQRv6EzCaWRQ== +N2ewPkaF4vBztOl5yvdzXb12XWHEkfuTMQQ/S/IatrU= +DyDHCcahxG8x9eot9BhGGoaSx73c+e9U0Tp+L9OcbF/XbjTh88pSzp7kgiuYM1B2 +7ui7GKVu+hA1zn3iOmo+va7LW68AgDWItaWEVH6a7oq2MogBYQqB9a0ERfgAfeqp ++4NbF4lLQ35m5UiB1/0oM4hG33zvxiKsPHbFeRE//WI= +ErYWeNPhZeZLxfLfOGSlIroR342Y4yDE+5gd4kGeGi+Cxk34mAtGJIhUD+XL5o6JTvUKzpV2ggBzciaY2dSBkA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk0jB5v9uia9s/LvId4LpDPxheq4KAuxy30SMBaP0VoMyqtdF/x/wrhCqsqsW1LYHw= +xljVybfujf1U6lUBrfLztokiw0p3LjxCtcQVbdFJCh55nLdKbOwDf5U9mlEssvrw9UuksdylMWv6nUbn2eAV3902tdVMAzh0p972kCdgMT9YwNZR7uu+4w77+HGAahFEj9ce+vlHwr+dBIbvyCIAnQ== +p9oVsBgcyiBMjDb8CcPOqN8UTrGCDDQu68V59h0pkaI= +L5I5tdTeebwyRuKjqW19YSTNGjiTv77wwjwChdXruX5PCPlB01g/8mzPNbcCV4Ej +JXk5JLKM/1JqEbD+ffdNPhq/wJlzDLI+uOyyhRBAqdkc8glvKXYuquXWB3Up9JjK +rybirQdlAeHusp/N7GM0sKNwBki4q4kuOQGfi5/04eredJsBktFstkBd+UVmjZ93 +/VHZ8wcOZ8J40IbgQT8a2I6Oa7H1i8uh0gQGQdXA45vcac/EKnJbhl/dTCny6O14DZIAcyGMJrA3ITncE4RtoQ== +XUWmzg0XlHIPZ8hF18QkYN0Aztefgf1QQvjhvSQd5GE= +XUWmzg0XlHIPZ8hF18QkYAPejS/nuYQn78KM68umIdFJnVBP9H5IDJmlSsR5EqteYipZ+YiJplji/ek9bSPUaw== +XUWmzg0XlHIPZ8hF18QkYObeL8P52xvmTgwJHyUm/mfLNMDbo8CGOnJsVUQBIWQlfvvbM+AWxxqMYzuuII860l/8q20ZMBekthIoTIQYrDy1B8N0QDPw2xPUp8eNDncZ +XUWmzg0XlHIPZ8hF18QkYAIVEnTrHWvEYlMdWS6Jr8+JxDnush415VXf5xXFH5MfPlMYBwh5EK6N7tNxMd+x9rx7QHcf0/k2of4zBCX6BgG6dLOO3vmQ54XKDk9bQHsNX8oqmp8RafMghNfHp5m+hQ== +jU/UoCJH//JN+eJXlaFsvzU8G2xBEArehtnC0GEU3kba88wtG61nlrgiA1vZDQqQbOZZmS5jYHUfwpgNaCOdal4h0gBZdfVDpEG4TnwcSco= +LjE79g4isZOsOnkgBUQGVvXGCw/Wz+rnAp975tiAu3U= +1WJNrGwGz37apsG1QXCNVX6sT/6pveI0wT7pr1N/pmU= +0kmS0E/2gTphgwOZpwAE2yHpQG847xl/ajDfa5h5ju3vA+PsuXXfOQZPpYlHfc4h +4uuueBhid9aJeYpW9TknMTKFhi7c1ZuCDkAynCPWLEE4f5zCoL+aW6sgf4jEjnJZkRv4BgsIwv5h+Nn8Ll5k4uJ3HJUccyzNzNMSKu4HZ3E= +1u+XjG/2+GSQRv6EzCaWRQ== +1zukBHMguVb5WvA8m8HQjPjAhqCqUt1LhX5PoNgDmCwZWDEDFPUr61tKw2fBGv8i +R2NB8DN3pOWXcW6vM0ClmEMoF71ke+0fKsaH+rPi+7Q= +Gbp0us/o/RYxNCpypte8wkDs+L9ibC0lB6ODeIGeBUNxo67ovf1PiljiTsXxss/EimE7fTfi4tp69JeeIZ1JucqFL7uKZjRgXzm1wTNci/8roI+2d1T+pyUp5Kd86NfJBDaru4YN7mCFiZHuAPEnwQ== +uIsEpyb/BmTr4pwkQVVUf4sU8Bq17UUXFXa/GD5UzvsLsnfQOtQoQPl3At3HFxd9QPRV9DEKp1t8SX0Bk3fmnuQ1MvO6xYN3j3krrbtx9Ri7ySAONSAUfWtbyjvLcqIKqJ33t4d6RxsAm5zFneySw9JQvlUNGDV29LpQsznKjgQ= +9AUqsxjSX9s1jg3GUS+NB5ufD2BIObuhk4izDv68g49BKR0/lv0Y5YYfBQ98x9ip +MzOj4ZiBOJDNVP8vi/lFQHphDQLizMa0vztkd1r5mDy3IP/1XcaniiKAbPj57C+Gp3AnBwYczoXDbvTbbHXTgK3WupMUtAfvAVyFkTcwA8YkZlk8xc2iiHoJ7UnppvyxfNOSQa2GgQVTpBJnRQPsBI2y6/oyrjRnCa5IW1kASp0= +4QQ/zTZZdnopElj3JQHNa9PygZa4ZCO9taMSrns8R7Q= +CAhn8dRUItEbEErp4w+lX55YSYfu77vGi60UCUzYYfp8S9MgKw/toqSe6FMs2M1v +1u+XjG/2+GSQRv6EzCaWRQ== +1XSH7jd9+l3kxs8yJf4VMU+0Br69HiFgxoemVlEviZQ= +1zukBHMguVb5WvA8m8HQjOPEJCJ9+AzZ9DJkuqA/jaCsGXgslAqdvnFUngarGbeG +Gbp0us/o/RYxNCpypte8wkDs+L9ibC0lB6ODeIGeBUNxo67ovf1PiljiTsXxss/EuN2TsKprffsLq+piRlk9US4xVMZW13fxkFIp+7Nn7ogIvtGKczBlxvsKVf4Lmurq +MzOj4ZiBOJDNVP8vi/lFQHphDQLizMa0vztkd1r5mDy3IP/1XcaniiKAbPj57C+Gp3AnBwYczoXDbvTbbHXTgK3WupMUtAfvAVyFkTcwA8YkZlk8xc2iiHoJ7UnppvyxfNOSQa2GgQVTpBJnRQPsBI2y6/oyrjRnCa5IW1kASp0= +b4OJVZe8QyIpjuTpKXDL9A== +n/Vg/an64od1JqfYD8zjtsZlIrtOVreSEto4JRdo972fXSPXC9glbleBdvuvJyBcLXUEa9mxHCFwIk/EcNAsxka2/PVgSn4sRDhJcwPf9ZuU+pcTYk7yL9iStuLBH6WcOxYcU535Ts8JW22TXuRUZg== +iUEDlyYF71b358pIL4FKL4uQouNP3knkritXeMCFqD6RcAocvu7FrPVBCb4SQVlH +JomK3KjQVhm7axi7Z5yMpzSzedclpQ0EPWf4a5slcp0= +ynmt6S+38NUiCOOHWGlzRziAza4a5udNX5tgg2hzQ1k= +MIOAnnldxAFaiMyLbIEH0zOpkRUjLKhsZU8SfkygKU8= +xWoGNWjKGPfI4gq8aHoTfCbeqO6O7OYgG1AC8Y9kBQW0BOAdclPvoPuB3UjdhN0TRKx8JizDm/NQEXrzbcwHmA== +1V2v8QerKOmubvSxgB4eTDu/hxOotI87lxS6veME3Crs2k0u4BS95hFjA9PZArPg +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjaVq63HF67baKhTwF5Wt9FBPiGnoli/Hkn+AIFnauccJA== +F9Sftf1PoN5P+lgvc+r10OJ80Vg6iPphyrOoh/5x4bwMegQKx1lxJt2/4i1MN1Vk +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjY9DrK1RwfWAFBMl0E5L7BGp6Et4s9ptYJscetnOWBhjg== +F9Sftf1PoN5P+lgvc+r10GxubuveiTDZyIVny6m+75S9YktzS7qqSq3X2ySJI1VX +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjYzzneH/VPa53Tze+qkheMyIZg41dkiAdfgOiX7zd+woA== +32pdC9DD05OE2l0oXazDFNZg85sdZ06B0olaVOjcsr2UCUkKahTATxT0kBv7/uMo +n/Vg/an64od1JqfYD8zjtvTGvwYcoCTsdR9KI4oJEXjHrKKo/lK6NN3u1IyRnVmJxAC02FuWxa549NUc0lWSZQ== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6dHp7fYDGkVGjkCRmJH6NHpLEmoBoKZCqxn3HaNJebFni7TM0UOTkq3uitwYQRUL4= +1u+XjG/2+GSQRv6EzCaWRQ== +1zukBHMguVb5WvA8m8HQjDdAZWNCBhb/LRFKU6AoQAPnDsBbWhpceRgHN0OMdoTg +XBwzd37QS0Dep4YK0TJlr1JCCGVIs7DD9eBryOC6u50= +mX7fgLhy/TJmbe7Pm0t6D4rgTdc0/jMdS8+hY9YrX9k= +ACEEdgL/kNbx7RaZcSRxZC+bHLMedAa4IlPYPErxwbQbb2bCimUzVAoWiGPSZjhG +J1UGlXkKhKXD36OrXXhN8DRNLBzSnpnR9zem4Hl46aYT4DaL8TwLah64w6PefGG35pZu+O411OAUqUyJw60qew== +iUEDlyYF71b358pIL4FKL042CE27KtREMjdeW1oXex7KFuFnXXodSBFBSaoKtMYZ +JomK3KjQVhm7axi7Z5yMpzSzedclpQ0EPWf4a5slcp0= +n/Vg/an64od1JqfYD8zjtgR84pQoEprp4i1Z7jZ8hf4= +VmVrGQo2zRokW/ZuO9bN6xBSBKdKa/mCsbRch2EchZY= +VmVrGQo2zRokW/ZuO9bN64/3v10sr+z8K0riZpxmj3dhtt9I77P5EN2qjRUN9+HZNXT5BzWZZUZQ1+4tzdGNlQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +7CWMhMuDAW1zoceP42HFI7JkOskde8GnIX1Kh6Fk5FtQiq0ZdpGNGcR5RDi7Pioy +ORph4c0b9qayU8qN/Z+ZPvufkaK4ZP6Z5pDSWcXufAEkqWlcZmYJvpUATBsncavX +uTEK8Ng11d3ix2pA+DD/aZwn7g4EisNaVgVRLiZl0yo= +32pdC9DD05OE2l0oXazDFJkxspwkL8t0IItwxjfnC8nUjRvvtzFDV48AlYBtpE1m +4gO44pHRPczUcmpB4bg0pnQGVtS7ogyMz4MAeBHcMtAxe2eF9/DiCFsLtpuKiL7B +uTEK8Ng11d3ix2pA+DD/aQrOlBP0FfiwhlnNZOyjgoo= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6+Iz01H3FqtnAgb7mhzu25zCCKQUPgasOEYO93p0OIwe +VmVrGQo2zRokW/ZuO9bN65rD4nI0MfwzlsBII0cdPaa77aMSTcf8piJKRZe3l5hL4FCHh2Pq7Gs/Wmj2RyJLsNkLZy9ia0AMqcf6dGGGEbVe0xvfm155GJTo9663VjJzFiKsJi7SJETzvnTYIwcltfuO8Uiv6IhgN87wNdwKZck= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6+Iz01H3FqtnAgb7mhzu25zjxGKQtsXlvqkagztbW1AG +4gO44pHRPczUcmpB4bg0pklZgQJXm0A2Am+4pJfHwxIxdfNIgu3xCJb1c+nvhf0I +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +SIVhMVja35cNhEoyyFHyU5ueFbyMySbyi9mnmmhpxVU8hpsEV8BTtrKQwQni3PGF +q4nc/jwATOMUyfSjLibfEW5DCAn6VBbRQRnaOjh7eA0= +WA9gm/QP+j5PVCbJRTSxYwp3kGb4cMqf+qwJUWVEEPnmqCBjvCtbmGcOopjB+jck +UBbQHMxDYmM54VXbqfW20S22f4jK6BUOrIYt632vyLE= +0OsCC+YpIPphU/YpLoF2K65mO2fOEgg9BDORJsJnsukfPkOuC1C9HL5KjkD4ozjN +v4Cu02hZRDsW+85CLRBC5+QhixIrX1sjRbc+k4FIGXQqvaBnxkvwl7N6zF1jmyIx+QaQW6LMymcXOKxyLELGjoDaY5dHZslh+zvOJ3nOF4o= +v4Cu02hZRDsW+85CLRBC55x7KgPFiTIBJuK7Bj8UUn6kT88eAAMKmh4eP+KGfruG +v4Cu02hZRDsW+85CLRBC52M44iq5ZlKYvAT50T1VVUxbP+lYYf2HLRnTkcnOv/7RLTiXmmYbKFrtds9nTvmC78/4+GyXpy//qMRxrz1Dp81yXvKLN7kSquN/lGm0CbyIuiiLCxeHe7ZblRuSW5w/ew== +tWSLqD1lvEP9Rpp6I27PpvXrraKAQUvuzpkWxLOkdYVAxT6x12Fs+vuA/HccQbq+SMpYqdMtX58R+H1Pdla69g== +kJxoTVctk+bGjMYgKxsjjSmeY05Z0U8C6JFZmjUMMCSxIzLqQNB1ftjf5Kn92nPV0SE/2kEHSEkvBISrHEOc8olUZRsKyiScIOXAVmL8+AnZI2OJatRCbEYuUHSOXcH0UrWCb9M0odIJbfpL0b/Lw0Y3ebqDZzALGgeESF0E1IJoiAc76H5FHNtYFugXFh9i +VmVrGQo2zRokW/ZuO9bN679dY2ryxqp4ouOaU1fOIumFLB70f/WoAZf/2w7Cl1Z8ZnqXwm8+/EGNKMaSd44zNA== +f58SBztD4UCgJ/6kPztVo29CIZDUF3sUhAF1SPuV/14= +lGY+HGJ/DlLufZHWWlj7U9tymVCSNLDh3vOFZaRUBuc= +1u+XjG/2+GSQRv6EzCaWRQ== +nfkJ3QMSmeMN6/r/kF1fGVfrMpks76Z9i1HsARq3TTrMiMLoOh4ZCWV7E4Vz9LdZ +b4OJVZe8QyIpjuTpKXDL9A== +HUxaWbdFNk9pZ+7x3i/kLN34rJMFESnZNqgFnOTtmncZ9MojyT4ik/ZLHKgOBvG5jYiwtWKmCbv5/tg2EykmXA== +NhnIR3Ilo4H2su9/cTNo/Ps3vYSOiOY6esn1xeoWmAlrdRFFnm+r4q+F9O7KjdciOkRcYwkz4orBueV5Pha+Cse7WK7UEu4tjn7yjq09/pc+cqG1kgwWKp3q5L/lI5vjJjeYoh0VOhmY1zWisiB2GA== +NhnIR3Ilo4H2su9/cTNo/IbXc6jmjXpdg3/b5Q0M//5RfYowE2A9IKSdANsXKONc +xWoGNWjKGPfI4gq8aHoTfDMe4+Xx9fgEfvyQdRt49XptGFFda7+W0fuqWp8j+ScVv1QIofgDM/c9givgAZheB/DaAF8Pow88H+SBfdPZZ4jcj49xI9n48vcxq/0T0GE740KKNSWuK4oLY4A7yJGVoQ== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmV3FI++vVJXDLBceCG9F0SC+wvWb5HwFmxE4KbtErnpcH6kh83d0XIEyQijeUN9AQk= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXHkkiJnu/WJRPwK+C9Jo/kuW5qQduKUoG6ECAUaT3mOaQEo6tdlrSdJq8+xmfNVcM= +1u+XjG/2+GSQRv6EzCaWRQ== +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 +661hZf7vhUQ+50okfwfTXw== +FEZSdYhAV26PfOeirhfda8qGB+2DE0ehPq4imkXwDHE= diff --git a/class_v2/monitor_v2.py b/class_v2/monitor_v2.py new file mode 100644 index 00000000..fb7e1117 --- /dev/null +++ b/class_v2/monitor_v2.py @@ -0,0 +1,380 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: wzjie +# +------------------------------------------------------------------- + +import os +import json +import time +import datetime +import re +import sqlite3 + +import public + + +class Monitor: + def __init__(self): + pass + + def __get_file_json(self, filename): + try: + if not os.path.exists(filename): return {} + return json.loads(public.readFile(filename)) + except: + return {} + + def __get_file_nums(self, filepath): + if not os.path.exists(filepath): return 0 + + count = 0 + for index, line in enumerate(open(filepath, 'r')): + count += 1 + return count + + def _get_site_list(self): + sites = public.M('sites').where('status=?', (1,)).field('name').get() + return sites + + def _statuscode_distribute_site(self, site_name): + + try: + day_401 = 0 + day_500 = 0 + day_502 = 0 + day_503 = 0 + conn = None + ts = None + start_date, end_date = self.get_time_interval(time.localtime()) + select_sql = "select time/100 as time1, sum(status_401), sum(status_500), sum(status_502), sum(status_503) from request_stat where time between {} and {}"\ + .format(start_date, end_date) + + db_path = os.path.join("/www/server/total/", "logs/{}/logs.db".format(site_name)) + if os.path.isfile(db_path): + conn = sqlite3.connect(db_path) + ts = conn.cursor() + ts.execute(select_sql) + results = ts.fetchall() + + if type(results) == list: + for result in results: + time_key = str(result[0]) + day_401 = result[1] + day_500 = result[2] + day_502 = result[3] + day_503 = result[4] + except: + pass + finally: + if ts: + ts.close() + if conn: + conn.close() + + return day_401, day_500, day_502, day_503 + + def _statuscode_distribute_site_old(self, site_name): + today = time.strftime('%Y-%m-%d', time.localtime()) + path = '/www/server/total/total/' + site_name + '/request/' + today + '.json' + + day_401 = 0 + day_500 = 0 + day_502 = 0 + day_503 = 0 + if os.path.exists(path): + spdata = self.__get_file_json(path) + + for c in spdata.values(): + for d in c: + if '401' == d: day_401 += c['401'] or 0 + if '500' == d: day_500 += c['500'] or 0 + if '502' == d: day_502 += c['502'] or 0 + if '503' == d: day_503 += c['503'] or 0 + + return day_401, day_500, day_502, day_503 + + def _statuscode_distribute(self, args): + sites = self._get_site_list() + + count_401, count_500, count_502, count_503 = 0, 0, 0, 0 + for site in sites: + site_name = site['name'] + day_401, day_500, day_502, day_503 = self._statuscode_distribute_site(site_name) + day_401 = day_401 or 0 + day_500 = day_500 or 0 + day_502 = day_502 or 0 + day_503 = day_503 or 0 + count_401 += day_401 + count_500 += day_500 + count_502 += day_502 + count_503 += day_503 + return {'401': count_401, '500': count_500, '502': count_502, '503': count_503} + + # 获取mysql当天的慢查询数量 + def _get_slow_log_nums(self, args): + if not os.path.exists('/etc/my.cnf'): + return 0 + + ret = re.findall(r'datadir\s*=\s*(.+)', public.ReadFile('/etc/my.cnf')) + if not ret: + return 0 + filename = ret[0] + '/mysql-slow.log' + + if not os.path.exists(filename): + return 0 + + count = 0 + zero_point = int(time.time()) - int(time.time() - time.timezone) % 86400 + with open(filename) as f: + for line in f: + line = line.strip().lower() + if line.startswith('set timestamp='): + timestamp = int(line.split('=')[-1].strip(';')) + if timestamp >= zero_point: + count += 1 + return count + + # 判断字符串格式的时间是不是今天 + def _is_today(self, time_str): + try: + time_date = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S").date() + except: + try: + time_date = datetime.datetime.strptime(time_str, "%y%m%d %H:%M:%S").date() + except: + time_date = datetime.datetime.strptime(time_str, "%d-%b-%Y %H:%M:%S").date() + today = datetime.date.today() + if time_date == today: + return True + return False + + # PHP慢日志 + def _php_count(self, args): + result = 0 + if not os.path.exists('/www/server/php'): + return result + + for i in os.listdir('/www/server/php'): + if os.path.isdir('/www/server/php/' + i): + php_slow = '/www/server/php/' + i + '/var/log/slow.log' + if os.path.exists(php_slow): + php_info = open(php_slow, 'r') + for j in php_info.readlines(): + if re.search(r'\[\d+-\w+-\d+.+', j): + time_str = re.findall(r'\[\d+-\w+-\d+\s+\d+:\d+:\d+\]', j) + time_str = time_str[0].replace('[', '').replace(']', '') + if self._is_today(time_str): + result += 1 + else: + break + + return result + + # 获取当天cc攻击数 + def _get_cc_attack_num(self, args): + zero_point = int(time.time()) - int(time.time() - time.timezone) % 86400 + log_path = '/www/server/btwaf/drop_ip.log' + if not os.path.exists(log_path): return 0 + + num = 100 + log_body = public.GetNumLines(log_path, num).split('\n') + while True: + if len(log_body) < num: + break + if json.loads(log_body[0])[0] < zero_point: + break + else: + num += 100 + log_body = public.GetNumLines(log_path, num).split('\n') + + num = 0 + for line in log_body: + try: + item = json.loads(line) + if item[0] > zero_point and item[-1] == 'cc': + num += 1 + except: continue + + return num + + # 获取当天攻击总数 + def _get_attack_num(self, args): + today = time.strftime('%Y-%m-%d', time.localtime()) + sites = self._get_site_list() + + count = 0 + for site in sites: + file_path = '/www/wwwlogs/btwaf/{0}_{1}.log'.format(site['name'], today) + count += self.__get_file_nums(file_path) + return count + + def get_exception(self, args): + data = {'mysql_slow': self._get_slow_log_nums(args), 'php_slow': self._php_count(args), + 'attack_num': self._get_attack_num(args), 'cc_attack_num': self._get_cc_attack_num(args)} + statuscode_distribute = self._statuscode_distribute(args) + data.update(statuscode_distribute) + return data + + def get_spider(self, args): + request_data = {} + sites = public.M('sites').field('name').order("addtime").select(); + for site_info in sites: + ts = None + conn = None + try: + site_name = site_info["name"] + start_date, end_date = self.get_time_interval(time.localtime()) + select_sql = "select time, spider from request_stat where time between {} and {}"\ + .format(start_date, end_date) + + db_path = os.path.join("/www/server/total/", "logs/{}/logs.db".format(site_name)) + if not os.path.isfile(db_path): continue + conn = sqlite3.connect(db_path) + ts = conn.cursor() + ts.execute(select_sql) + results = ts.fetchall() + + if type(results) == list: + for result in results: + time_key = str(result[0]) + hour = time_key[len(time_key)-2:] + value = result[1] + if hour not in request_data: + request_data[hour] = value + else: + request_data[hour] += value + except: + pass + finally: + if ts: + ts.close() + if conn: + conn.close() + return request_data + + # 获取蜘蛛数量分布 + def get_spider_old(self, args): + today = time.strftime('%Y-%m-%d', time.localtime()) + sites = self._get_site_list() + + data = {} + for site in sites: + site_name = site['name'] + file_name = '/www/server/total/total/' + site_name + '/spider/' + today + '.json' + if not os.path.exists(file_name): continue + day_data = self.__get_file_json(file_name) + for s_data in day_data.values(): + for s_key in s_data.keys(): + if s_key not in data: + data[s_key] = s_data[s_key] + else: + data[s_key] += s_data[s_key] + return data + + # 获取负载和上行流量 + def load_and_up_flow(self, args): + import psutil + + load_five = float(os.getloadavg()[1]) + cpu_count = psutil.cpu_count() + + up_flow = 0 + data = public.M('network').dbfile('system').field('up').order('id desc').limit("5").get() + if len(data) == 5: + up_flow = round(sum([item['up'] for item in data]) / 5, 2) + + return {'load_five': load_five, 'cpu_count': cpu_count, 'up_flow': up_flow} + + def get_time_interval(self, local_time): + start = None + end = None + time_key_format = "%Y%m%d00" + start = int(time.strftime(time_key_format, local_time)) + time_key_format = "%Y%m%d23" + end = int(time.strftime(time_key_format, local_time)) + return start, end + + def get_request_count_by_hour(self, args): + # 获取站点每小时的请求数据 + request_data = {} + import sqlite3 + sites = public.M('sites').field('name').order("addtime").select(); + for site_info in sites: + ts = None + conn = None + try: + site_name = site_info["name"] + start_date, end_date = self.get_time_interval(time.localtime()) + select_sql = "select time, req from request_stat where time between {} and {}"\ + .format(start_date, end_date) + db_path = os.path.join("/www/server/total/", "logs/{}/logs.db".format(site_name)) + if not os.path.isfile(db_path): continue + conn = sqlite3.connect(db_path) + ts = conn.cursor() + ts.execute(select_sql) + results = ts.fetchall() + if type(results) == list: + for result in results: + time_key = str(result[0]) + hour = time_key[len(time_key)-2:] + value = result[1] + if hour not in request_data: + request_data[hour] = value + else: + request_data[hour] += value + except: pass + finally: + if ts: + ts.close() + if conn: + conn.close() + return request_data + + # 取每小时的请求数 + def get_request_count_by_hour_old(self, args): + today = time.strftime('%Y-%m-%d', time.localtime()) + + request_data = {} + sites = self._get_site_list() + for site in sites: + path = '/www/server/total/total/' + site['name'] + '/request/' + today + '.json' + if os.path.exists(path): + spdata = self.__get_file_json(path) + for hour, value in spdata.items(): + count = value.get('GET', 0) + value.get('POST', 0) + if hour not in request_data: + request_data[hour] = count + else: + request_data[hour] = request_data[hour] + count + + return request_data + + # 取服务器的请求数 + def _get_request_count(self, args): + request_data = self.get_request_count_by_hour(args) + return sum(request_data.values()) + + # 获取瞬时请求数和qps + def get_request_count_qps(self, args): + from BTPanel import cache + + cache_timeout = 86400 + + old_total_request = cache.get('old_total_request') + otime = cache.get("old_get_time") + if not old_total_request or not otime: + otime = time.time() + old_total_request = self._get_request_count(args) + time.sleep(2) + ntime = time.time() + new_total_request = self._get_request_count(args) + + qps = float(new_total_request - old_total_request) / (ntime - otime) + + cache.set('old_total_request', new_total_request, cache_timeout) + cache.set('old_get_time', ntime, cache_timeout) + return {'qps': qps, 'request_count': new_total_request} diff --git a/class_v2/one_key_wp_v2.py b/class_v2/one_key_wp_v2.py new file mode 100644 index 00000000..43882be2 --- /dev/null +++ b/class_v2/one_key_wp_v2.py @@ -0,0 +1,1842 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: zhwen +# +------------------------------------------------------------------- +import os +import time +import json +import sys +import public +import re +import requests +from bs4 import BeautifulSoup +import panel_mysql_v2 as panelMysql +from public.validate import Param + +# import wp-toolkit core +# from wp_toolkit import wp_version, wpmgr, wpfastcgi_cache + + +def get_mem(): + import psutil + mem = psutil.virtual_memory() + memInfo = {'memTotal': int(mem.total / 1024 / 1024), 'memFree': int(mem.free / 1024 / 1024), + 'memBuffers': int(mem.buffers / 1024 / 1024), 'memCached': int(mem.cached / 1024 / 1024)} + return memInfo['memTotal'] + + +class one_key_wp: + wp_package_url = 'https://wordpress.org/latest.zip' + # wp_package_url = 'http://download.bt.cn/install/package/wordpress-5.9.1.zip' + base_path = "/www/server/panel/" + wp_session_path = '{}/data/wp_session'.format(base_path) + package_zip = '{}/package/wp.zip'.format(base_path) + md5_file = '{}/package/md5'.format(base_path) + log_path = '/tmp/schedule.log' + __php_tmp_file = '/tmp/wp_tmp'.format(base_path) + panel_db = "/www/server/panel/data/default.db" + timeout_count = 0 + old_time = 0 + __wp_session = None + __session_resp = None + __ajax_nonce = None + __domain = None + __plugin_page_content = None + wp_user = None + wp_passwd = None + + def __init__(self): + self.create_wp_table() + if not self.__wp_session: + self.__wp_session = requests.Session() + + import PluginLoader + self.__IS_PRO_MEMBER = PluginLoader.get_auth_state() > 0 + + def get_headers(self): + return { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36', + 'Host': self.__domain + } + + def get_plugin_page(self): + _get_ajax_nonce_url = "http://{}/wp-admin/plugins.php".format(self.__domain) + self.__plugin_page_content = self.__wp_session.get(_get_ajax_nonce_url, headers=self.get_headers(), + verify=False).text + + def get_wp_nonce(self, content, rep): + rex = re.search(rep, content) + if rex: + return rex.group(1) + + def get_nonce(self): + resp = self.__session_resp + if resp != '1': + resp = resp.text + else: + self.get_plugin_page() + resp = self.__plugin_page_content + _ajax_nonce_rep = '"ajax_nonce\\\"\\:\\\"(\\w+)' + rex = re.search(_ajax_nonce_rep, resp) + if rex: + self.__ajax_nonce = rex.group(1) + + def __load_cookies(self, s_id): + self.__domain = self.get_wp_auth(s_id) + f = '{}/{}'.format(self.wp_session_path, self.__domain) + c = public.readFile(f) + if c: + mtime = os.path.getmtime(f) + expried = 86400 + if time.time() - mtime > expried: + print('cookies expired, re-login wordpress') + self.__login_wp() + else: + print('use local session') + self.__wp_session.cookies.update(json.loads(c)) + self.__session_resp = '1' + else: + print('No local cookies, login wordpress') + self.__login_wp() + # self.__login_wp() + # print('login success!') + try: + self.get_nonce() + except: + pass + if not self.__ajax_nonce: + self.__login_wp() + self.get_nonce() + self.get_plugin_page() + + def __save_cookies(self): + if not os.path.exists(self.wp_session_path): + os.mkdir(self.wp_session_path) + f = '{}/{}'.format(self.wp_session_path, self.__domain) + return public.writeFile(f, json.dumps(self.__wp_session.cookies.get_dict())) + + def __login_wp(self): + + wp_login = 'http://{}/wp-login.php'.format(self.__domain) + headers1 = {'Cookie': 'wordpress_test_cookie=WP Cookie check', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36', + # 'referer':"https://{}/wp-login.php?loggedout=true&wp_lang=en_US".format(self.__domain), + 'Host': self.__domain + } + datas = { + 'log': self.wp_user, + 'pwd': self.wp_passwd, + 'wp-submit': 'Log In', + # 'redirect_to': 'https://{}/wp-admin'.format(self.__domain), + 'testcookie': '1' + } + # hosts = public.readFile('/etc/hosts') + # if not hosts: + # return False + # if not re.search(r'127.0.0.1\s+{}'.format(self.__domain),hosts): + # public.writeFile('/etc/hosts','\n127.0.0.1 {}'.format(self.__domain),'a+') + + self.__session_resp = retry( + lambda: self.__wp_session.post(wp_login, headers=headers1, data=datas, verify=False)) + + # cookies持久化 + self.__save_cookies() + + # 写输出日志 + def write_logs(self, log_msg, clean=None): + if clean: + fp = open(self.log_path, 'w') + fp.write(log_msg) + fp.close() + fp = open(self.log_path, 'a+') + fp.write(log_msg + '\n') + fp.close() + + # 下载文件 + def download_file(self): + try: + path = os.path.dirname(self.package_zip) + if not os.path.exists(path): os.makedirs(path) + import urllib, socket, ssl + ssl._create_default_https_context = ssl._create_unverified_context + socket.setdefaulttimeout(10) + self.pre = 0 + self.old_time = time.time() + print("Download the installation package: {} --> {}".format(self.wp_package_url, self.package_zip)) + self.write_logs( + "|-Download the installation package: {} --> {}".format(self.wp_package_url, self.package_zip)) + if sys.version_info[0] == 2: + urllib.urlretrieve(self.wp_package_url, filename=self.package_zip, reporthook=self.download_hook) + else: + urllib.request.urlretrieve(self.wp_package_url, filename=self.package_zip, + reporthook=self.download_hook) + md5str = public.FileMd5(self.package_zip) + public.writeFile(self.md5_file, str(md5str)) + except: + print("Download error: {}".format(public.get_error_info())) + if self.timeout_count > 5: return; + self.timeout_count += 1 + time.sleep(5) + self.download_file() + + # 下载文件进度回调 + def download_hook(self, count, blockSize, totalSize): + used = count * blockSize + pre1 = int((100.0 * used / totalSize)) + if self.pre != pre1: + dspeed = used / (time.time() - self.old_time) + self.pre = pre1 + + def check_package(self): + # 检查本地包 + download = False + md5str = None + if os.path.exists(self.package_zip): + md5str = public.FileMd5(self.package_zip) + if os.path.exists(self.md5_file) and md5str: + if md5str != public.readFile(self.md5_file): + download = True + else: + download = True + return download + + def download_latest_package(self): + print("Start downloading the installation package...") + self.write_logs("|-Start downloading the installation package...") + + if os.path.exists(self.package_zip): + # 获取文件的最后修改时间 + modified_time = os.path.getmtime(self.package_zip) + # 计算当前时间与最后修改时间的差值 + time_diff = time.time() - modified_time + time2 = 30 * 24 * 60 * 60 + + if time_diff > time2: # 如果超过30天,则删除文件 + os.remove(self.package_zip) + os.remove(self.md5_file) + self.write_logs("|-Del package...") + + if not self.check_package(): + print("|-MD5 consistent, no need to download...") + return public.return_msg_gettext(True, 'MD5 consistent!') + self.download_file() + if not os.path.exists(self.package_zip): + self.write_logs("|-Download failed...") + print("Download failed...") + return public.return_msg_gettext(False, 'File download failed!') + return public.return_msg_gettext(True, "Download successfully") + + def unzip_package(self, site_path): + print("Start unzipping the installation package...") + self.write_logs("|-Start unzipping the installation package...") + public.ExecShell('unzip -o {} -d {}/'.format(self.package_zip, site_path)) + public.ExecShell('mv {}/wordpress/* {}'.format(site_path, site_path)) + os.removedirs("{}/wordpress/".format(site_path)) + print("Start setting up site permissions...") + self.write_logs("|-Start setting up site permissions...") + self.set_permission(site_path) + + def set_permission(self, site_path): + os.system('chmod -R 755 ' + site_path) + os.system('chown -R www.www ' + site_path) + + def set_urlrewrite(self, site_name, site_path): + webserver = public.get_webserver() + + if webserver == 'openlitespeed': + webserver = 'apache' + + swfile = '/www/server/panel/rewrite/{}/wordpress.conf'.format(webserver) + if os.path.exists(swfile): + rewriteConf = public.readFile(swfile) + + if webserver == 'nginx': + dwfile = '{}/vhost/rewrite/{}.conf'.format(self.base_path, site_name) + + else: + dwfile = '{}/.htaccess'.format(site_path) + + public.writeFile(dwfile, rewriteConf) + + + def __write_db(self, s_id, d_id, prefix, user_name, admin_password): + print("Inserting data...") + pdata = {"s_id": s_id, "d_id": d_id, "prefix": prefix, "user": user_name, "pass": admin_password} + public.M('wordpress_onekey').where('s_id=?', (s_id,)).field('s_id').find() + if public.M('wordpress_onekey').where('s_id=?', (s_id,)).field('s_id').find(): + print("Data already exists, update data...") + public.M('wordpress_onekey').where('s_id=?', (s_id,)).update(pdata) + return + print("Insert data...") + print("Insert data:{}".format(pdata)) + print("Result:{}".format(public.M('wordpress_onekey').insert(pdata))) + + def create_wp_table(self): + if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'wordpress_onekey')).count(): + public.M('').execute('''CREATE TABLE "wordpress_onekey" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "s_id" INTEGER DEFAULT '', + "d_id" INTEGER DEFAULT '', + "prefix" TEXT DEFAULT '', + "user" TEXT DEFAULT '', + "pass" TEXT DEFAULT '');''') + + def init_wp(self, values): + hosts = public.readFile('/etc/hosts') + if not hosts: + return False + if not re.search(r'127.0.0.1\s+{}'.format(values['site_name']), hosts): + public.writeFile('/etc/hosts', '\n127.0.0.1 {}'.format(values['site_name']), 'a+') + + self.write_logs("|-Start initializing Wordpress...") + + if self.__IS_PRO_MEMBER: + from wp_toolkit import wpmgr + + # 初始化WP管理类 + wpmgr_obj = wpmgr(values['s_id']) + + # 初始化WP网站配置 + self.write_logs("|-Start setup configurations...") + ok, msg = wpmgr_obj.setup_config(values['dbname'], values['db_user'], values['db_pwd'], 'localhost', values['prefix']) + self.write_logs('|-Setup config >>> {} {}'.format('OK' if ok else 'FAIL', msg)) + + if not ok: + raise Exception(msg) + + # 初始化WP网站信息 + self.write_logs("|-Start installations...") + ok, msg = wpmgr_obj.wp_install(values['weblog_title'], values['user_name'], values['admin_email'], values['admin_password'], values['language']) + self.write_logs('|-Installation {} {}'.format('OK' if ok else 'FAIL', msg)) + + if not ok: + raise Exception(msg) + else: + self.request_setup_0(values) + time.sleep(1) + if not self.request_setup_2(values): + return public.return_msg_gettext(False, + "The database connection is abnormal. Please check whether the root user authority or database configuration parameters are correct.") + time.sleep(1) + self.request_setup_3(values) + time.sleep(1) + self.request_setup_4(values) + time.sleep(1) + + # 配置伪静态规则 + self.set_urlrewrite(values['site_name'], values['site_path']) + + def request_setup_0(self, values): + self.write_logs("|-Start initializing Wordpress language...") + url = "http://{}/wp-admin/setup-config.php?step=0".format(values['site_name']) + param = { + "url": url, + "data": {"language": values['language']}, + "headers": {"Host": values['domain']} + } + # result = self.request_wp_api(param) + response = retry(lambda: self.request_wp_api(param)) + # public.print_log("开始检查--request_setup_0_result:{}".format(response)) + + def request_setup_2(self, values): + self.write_logs("|-Start initializing Wordpress config...") + url = "http://{}/wp-admin/setup-config.php?step=2".format(values['site_name']) + param = { + "url": url, + "headers": {"Host": values['domain']}, + "data": { + "dbname": values['dbname'], + "uname": values['db_user'], + "pwd": values['db_pwd'], + "dbhost": "localhost", + "prefix": values['prefix'], + "language": values['language'], + "submit": "Submit", + } + } + # result = self.request_wp_api(param) + response = retry(lambda: self.request_wp_api(param)) + if "install.php?language=ja": + return True + + def request_setup_3(self, values): + self.write_logs("|-Start initializing Wordpress install language...") + url = "http://{}/wp-admin/install.php?language={}".format(values['site_name'], values['language']) + param = { + "url": url, + "headers": {"Host": values['domain']}, + "data": { + "language": values['language'] + } + } + # self.request_wp_api(param) + response = retry(lambda: self.request_wp_api(param)) + + def request_setup_4(self, values): + self.write_logs("|-Start installing the wordpress program...") + url = "http://{}/wp-admin/install.php?step=2".format(values['site_name']) + param = { + "url": url, + "headers": {"Host": values['domain']}, + "data": { + "weblog_title": values['weblog_title'], + "user_name": values['user_name'], + "admin_password": values['admin_password'], + "admin_password2": values['admin_password'], + # "pw_weak": values['pw_weak'], # on/off + "admin_email": values['admin_email'], + # "Submit": "Install WordPress", + "language": values['language'] + } + } + # self.request_wp_api(param) + response = retry(lambda: self.request_wp_api(param)) + + def request_wp_api(self, param): + """需要指定域名得host为本机IP""" + resp = requests.post(param['url'], data=param['data'], headers=param['headers']) + # public.print_log("开始检查--request_wp_api:{}".format(resp)) + # public.print_log("开始检查--request_wp_api_resp.text:{}".format(resp.text)) + return resp.text + + def get_update_wp_nonce(self): + url = "http://{}/wp-admin/update-core.php".format(self.__domain) + # res = self.action_plugin_get(url) + _wp_nonce_rep = '"_wpnonce\\\"\\svalue=\\\"(\\w+)' + # rex = re.search(_wp_nonce_rep,res) + # if rex: + # self.__wp_nonce = rex.group(1) + + # return self.get_wp_nonce(self.action_plugin_get(url),_wp_nonce_rep) + response = retry(lambda: self.get_wp_nonce(self.action_plugin_get(url), _wp_nonce_rep)) + return response + + def action_plugin_post(self, param): + headers = self.get_headers() + if 'upgrade' in param['data']: + param['data']['_wpnonce'] = self.get_update_wp_nonce() + headers['referer'] = 'http://{}/wp-admin/update-core.php'.format(param['domain']) + + response = retry( + lambda: self.__wp_session.post(param['url'], data=param['data'], headers=headers, verify=False).text) + return response + # return self.__wp_session.post(param['url'],data=param['data'], headers=headers, verify=False).text + + def action_plugin_get(self, url, headers=None): + if not headers: + headers = self.get_headers() + # headers['Referer'] = "http://{}/wp-admin".format(self.__domain) + + response = retry(lambda: self.__wp_session.get(url, headers=headers, verify=False).text) + return response + # return self.__wp_session.get(url, headers=headers, verify=False).text + + def install_plugin(self, values): + self.write_logs("|-Start installing the [nginx-helper] plugin...") + self.__load_cookies(values['s_id']) + param = { + "url": "http://{}/wp-admin/admin-ajax.php".format(self.__domain), + "domain": values['domain'], + "data": { + "slug": values['slug'], # 插件名称 nginx-helper + "action": values['wp_action'], # action已经被面板占用 install-plugin + "_ajax_nonce": self.__ajax_nonce, + "_fs_nonce": "", + "username": "", + "password": "", + "connection_type": "", + "public_key": "", + "private_key": "" + } + } + res = self.action_plugin_post(param) + return res + + def get_smart_http_expire_form_nonce(self, url): + public.writeFile('/tmp/2', str(self.action_plugin_get(url))) + smart_http_expire_form_nonce = '"smart_http_expire_form_nonce\\\"\\svalue=\\\"(\\w+)' + return self.get_wp_nonce(self.action_plugin_get(url), smart_http_expire_form_nonce) + + def set_nginx_helper(self, values): + self.write_logs("|-Setting up [nginx-helper] plugin cache rules...") + self.__load_cookies(values['s_id']) + url = "http://{}/wp-admin/options-general.php?page=nginx".format(self.__domain) + param = { + "url": url, + "domain": values['domain'], + "data": { + "enable_purge": "1", + "is_submit": "1", + "cache_method": "enable_fastcgi", + "purge_method": "unlink_files", + "redis_hostname": "127.0.0.1", + "redis_port": "6379", + "redis_prefix": "nginx-cache", + "purge_homepage_on_edit": "1", + "purge_homepage_on_del": "1", + "purge_page_on_mod": "1", + "purge_page_on_new_comment": "1", + "purge_page_on_deleted_comment": "1", + "purge_archive_on_edit": "1", + "purge_archive_on_del": "1", + "purge_archive_on_new_comment": "1", + "purge_archive_on_deleted_comment": "1", + "purge_url": "", + "log_level": "INFO", + "log_filesize": "5", + "smart_http_expire_form_nonce": self.get_smart_http_expire_form_nonce(url), + "smart_http_expire_save": "Save All Changes" + } + } + return self.action_plugin_post(param) + + def act_nginx_helper_active(self, values): + self.write_logs("|-activating [nginx-helper] plugin...") + self.__load_cookies(values['s_id']) + values['active_id'] = "activate-nginx-helper" + self.get_plugin_page() + res = self.get_plugin_url(values['active_id'], self.__plugin_page_content) + url = 'http://{}/wp-admin/{data}'.format(self.__domain, + data=str(str(res[0]).split('"')[5])) + url = url.replace('amp;', '') + return self.action_plugin_get(url) + + def get_plugin_url(self, active_id, content): + # soup = BeautifulSoup(content) + soup = BeautifulSoup(content, features="html.parser") + res = soup.find_all(id=active_id) + return res + + def generate_wp_passwd(self, site_path, new_pass): + hash_password_code = public.readFile("{}/wp-includes/class-phpass.php".format(site_path)) + extra_code = """ + $passwordValue = "%s"; + $wp_hasher = new PasswordHash(8, TRUE); + $sigPassword = $wp_hasher->HashPassword($passwordValue); + $data = $wp_hasher->CheckPassword($passwordValue,$sigPassword); + if($data){ + echo 'True|'.$sigPassword;; + }else{ + echo 'False|'.$sigPassword;; + } + """ % new_pass + php_code = hash_password_code + extra_code + public.writeFile(self.__php_tmp_file, php_code) + a, e = public.ExecShell("php -f {}".format(self.__php_tmp_file)) + os.remove(self.__php_tmp_file) + res = a.split('|') + if res[0] == "True": + return public.return_message(0,0, res[1]) + return public.return_message(-1,0, 'Generated password detection failed!') + + def get_cache_status(self, s_id): + """ + s_id 网站id + """ + import data + site_info = public.M('sites').where('id=?', (s_id,)).field('name').find() + + if not isinstance(site_info, dict): + return False + + site_name = site_info['name'] + + # 获取WP站点绑定的PHP可执行文件 + from public import websitemgr + + php_v = websitemgr.get_site_php_version(site_name).replace('.', '') + + return fast_cgi().get_fast_cgi_status(site_name, php_v) + + ##############################对外接口—BEGIN############################## + def get_wp_username(self, get): + """ + s_id 网站ID + """ + # 校验参数 + try: + get.validate([ + Param('s_id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + values = self.check_param(get) + if values['status']==-1: + return values + values=values['message'] + db_info = public.M('wordpress_onekey').where('s_id=?', (values['s_id'],)).find() + db_name = public.M('databases').where('id=?', (db_info['d_id'],)).field('name').find() + if "name" not in db_name: + return public.return_message(-1,0, "The database of this wordpress was not found, this may be caused by the fact that you have manually deleted the database") + db_name = db_name['name'] + mysql_obj = panelMysql.panelMysql() + res = mysql_obj.query('select * from {}.{}users'.format( + db_name, db_info['prefix'])) + if hasattr(res, '__iter__'): + return public.return_message(0,0, [i[1] for i in res]) + else: + return public.return_message(-1,0, "Site database [{}] failed to query users, try setting the database for this site. Error: {}".format( + db_name, res)) + + def reset_wp_password(self, get): + """ + 重置wordpress用户密码 + s_id 网站ID + user 要重置的用户名 + new_pass + """ + # 校验参数 + try: + get.validate([ + Param('user').String(), + Param('new_pass').String(), + Param('s_id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + values = self.check_param(get) + if values['status']==-1: + return values + values=values['message'] + s_id = values['s_id'] + db_info = public.M('wordpress_onekey').where('s_id=?', (s_id,)).find() + db_name = public.M('databases').where('id=?', (db_info['d_id'],)).field('name').find()['name'] + path = public.M('sites').where('id=?', (s_id,)).field('path').find()['path'] + new_pass = values['new_pass'] + passwd = self.generate_wp_passwd(path, new_pass) + if passwd['status']==-1: + return passwd + + passwd = passwd['message'] + + if isinstance(passwd, dict): + passwd = passwd.get('result', None) + + if passwd is None: + return public.fail_v2('Reset password failed') + + mysql_obj = panelMysql.panelMysql() + sql = 'update {}.{}users set user_pass = "{}" where user_login = "{}"'.format( + db_name, db_info['prefix'], passwd, values['user']) + mysql_obj.execute(sql) + return public.return_message(0,0, "Password reset successful") + + # 获取WP可用版本列表 + def get_wp_available_versions(self, args: public.dict_obj): + # 校验参数 + try: + args.validate([ + Param('php_version_short').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + from wp_toolkit import wp_version + + versions = list(map(lambda x: { + 'locale': x['locale'], + 'version': x['version'], + 'php_version': x['php_version'], + 'mysql_version': x['mysql_version'], + }, wp_version().latest_versions())) + + if 'php_version_short' in args: + versions = list(filter(lambda x: int(args.php_version_short) >= int(''.join(str(x['php_version']).split('.')[:2])), versions)) + + return public.success_v2(versions) + + # 获取WP网站本地版本号 + def get_wp_version(self, s_id): + """获取wordpress本地版本 + s_id 网站id + """ + path = public.M('sites').where('id=?', (s_id,)).field('path').find()['path'] + conf_file = "{}/wp-includes/version.php".format(path) + conf = public.readFile(conf_file) + try: + version = re.search('\\$wp_version\\s*=\\s*[\'\"]{1}([\\d\\.]*)[\'\"]{1}', conf).groups(1)[0] + except: + version = "00" + return version + + # 获取WP已发布的最新版本号 + def get_wp_version_online(self): + """获取wordpress线上版本""" + url = "http://wordpress.org/download/" + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36' + } + result = requests.get(url, headers=headers) + result = re.search(r'Download\s+WordPress\s+([\d\.]+)', result.text) + if result: + return result.group(1) + return "00" + + # 检查WP是否有新版本可以更新 + def is_update(self, get): + """ + s_id 网站ID + """ + values = self.check_param(get) + if values['status']==-1: + return values + values=values['message'] + online_v = self.get_wp_version_online() + local_v = self.get_wp_version(values['s_id']) + update = False + if str(online_v) != str(local_v): + update = True + data = { + "online_v": online_v, + "local_v": local_v, + "update": update + } + return_message=public.return_msg_gettext(True, data) + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + + def purge_all_cache(self, get): + """ + 清理所有缓存 + """ + # 校验参数 + try: + get.validate([ + Param('s_id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if public.get_webserver() != "nginx": + return public.return_message(-1,0, "This feature currently only supports Nginx") + + if self.__IS_PRO_MEMBER: + from wp_toolkit import wpmgr + wpmgr(get.s_id).purge_cache_with_nginx_helper() + else: + cache_dir = "/dev/shm/nginx-cache/wp" + public.ExecShell("rm -rf {}/*".format(cache_dir)) + + return public.return_message(0,0,"Cleaned up successfully!") + + def set_fastcgi_cache(self, get): + """ + 设置缓存 + version php版本 + sitename 完整名 + act disable/enable 开启关闭缓存 + """ + # 校验参数 + try: + get.validate([ + Param('version').String(), + Param('sitename').String(), + Param('act').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + if public.get_webserver() != "nginx": + return public.return_message(-1,0, "This feature currently only supports Nginx") + values = self.check_param(get) + if values['status']==-1: + return values + values=values['message'] + if '.' in values['version']: + values['version'] = values['version'].replace('.', '') + + if self.__IS_PRO_MEMBER: + from wp_toolkit import wpfastcgi_cache + + ok, msg = wpfastcgi_cache().set_website_conf(values['version'], values['sitename'], values['act'], immediate=True) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + else: + return fast_cgi().set_website_conf(values['version'], values['sitename'], values['act']) + + # 使用网站ID查询WP站点域名 + def get_wp_auth(self, s_id): + domain = public.M('domain').where("pid=?", (s_id,)).field('name').find()['name'] + info = public.M('wordpress_onekey').where("s_id=?", (s_id,)).find() + self.wp_user = info['user'] + self.wp_passwd = info['pass'] + return domain + + # 更新WP版本 + def update_wp(self, args): + """更新wordpress版本 + s_id 网站ID + version 需要更新的版本 + """ + if self.__IS_PRO_MEMBER: + from wp_toolkit import wpmgr + + # 校验参数 + try: + args.validate([ + Param('s_id').Require().Integer('>', 0), + Param('version').Require().Regexp(r'^\d+(?:\.\d+)+$'), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + ok, msg = wpmgr(args.s_id).update_version(args.version) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Upgrade to {}'.format(msg)) + else: + self.__load_cookies(args.s_id) + param = { + "url": "http://{}/wp-admin/update-core.php?action=do-core-upgrade".format(self.__domain), + "domain": self.__domain, + "data": { + # "_wpnonce":get._wpnonce, + "_wp_http_referer": '/wp-admin/update-core.php', + "version": args.version, + "locale": "en_US", + "upgrade": "Update to version {}".format(args.version) + } + } + self.action_plugin_post(param) + return_message=public.return_msg_gettext(True, 'Updated successfully!') + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + + # 获取可用的语言列表 + def get_language(self, get=None): + language = { + "en": "English (United States)", + "af": "Afrikaans", + "am": "አማርኛ", + "ar": "العربية", + "ary": "العربية المغربية", + "as": "অসমীয়া", + "az": "Azərbaycan dili", + "azb": "گؤنئی آذربایجان", + "bel": "Беларуская мова", + "bg_BG": "Български", + "bn_BD": "বাংলা", + "bo": "བོད་ཡིག", + "bs_BA": "Bosanski", + "ca": "Català", + "ceb": "Cebuano", + "cs_CZ": "Čeština", + "cy": "Cymraeg", + "da_DK": "Dansk", + "de_CH_informal": "Deutsch (Schweiz, Du)", + "de_DE": "Deutsch", + "de_DE_formal": "Deutsch (Sie)", + "de_CH": "Deutsch (Schweiz)", + "de_AT": "Deutsch (Österreich)", + "dsb": "Dolnoserbšćina", + "dzo": "རྫོང་ཁ", + "el": "Ελληνικά", + "en_US": "English (United States)", + "en_NZ": "English (New Zealand)", + "en_AU": "English (Australia)", + "en_CA": "English (Canada)", + "en_GB": "English (UK)", + "en_ZA": "English (South Africa)", + "eo": "Esperanto", + "es_ES": "Español", + "es_EC": "Español de Ecuador", + "es_CO": "Español de Colombia", + "es_AR": "Español de Argentina", + "es_DO": "Español de República Dominicana", + "es_PE": "Español de Perú", + "es_CR": "Español de Costa Rica", + "es_UY": "Español de Uruguay", + "es_CL": "Español de Chile", + "es_PR": "Español de Puerto Rico", + "es_VE": "Español de Venezuela", + "es_GT": "Español de Guatemala", + "es_MX": "Español de México", + "et": "Eesti", + "eu": "Euskara", + "fa_IR": "فارسی", + "fa_AF": "(فارسی (افغانستان", + "fi": "Suomi", + "fr_FR": "Français", + "fr_BE": "Français de Belgique", + "fr_CA": "Français du Canada", + "fur": "Friulian", + "gd": "Gàidhlig", + "gl_ES": "Galego", + "gu": "ગુજરાતી", + "haz": "هزاره گی", + "he_IL": "עִבְרִית", + "hi_IN": "हिन्दी", + "hr": "Hrvatski", + "hsb": "Hornjoserbšćina", + "hu_HU": "Magyar", + "hy": "Հայերեն", + "id_ID": "Bahasa Indonesia", + "is_IS": "Íslenska", + "it_IT": "Italiano", + "ja": "日本語", + "jv_ID": "Basa Jawa", + "ka_GE": "ქართული", + "kab": "Taqbaylit", + "kk": "Қазақ тілі", + "km": "ភាសាខ្មែរ", + "kn": "ಕನ್ನಡ", + "ko_KR": "한국어", + "ckb": "كوردی‎", + "lo": "ພາສາລາວ", + "lt_LT": "Lietuvių kalba", + "lv": "Latviešu valoda", + "mk_MK": "Македонски јазик", + "ml_IN": "മലയാളം", + "mn": "Монгол", + "mr": "मराठी", + "ms_MY": "Bahasa Melayu", + "my_MM": "ဗမာစာ", + "nb_NO": "Norsk bokmål", + "ne_NP": "नेपाली", + "nl_NL_formal": "Nederlands (Formeel)", + "nl_NL": "Nederlands", + "nl_BE": "Nederlands (België)", + "nn_NO": "Norsk nynorsk", + "oci": "Occitan", + "pa_IN": "ਪੰਜਾਬੀ", + "pl_PL": "Polski", + "ps": "پښتو", + "pt_BR": "Português do Brasil", + "pt_AO": "Português de Angola", + "pt_PT_ao90": "Português (AO90)", + "pt_PT": "Português", + "rhg": "Ruáinga", + "ro_RO": "Română", + "ru_RU": "Русский", + "sah": "Сахалыы", + "snd": "سنڌي", + "si_LK": "සිංහල", + "sk_SK": "Slovenčina", + "skr": "سرائیکی", + "sl_SI": "Slovenščina", + "sq": "Shqip", + "sr_RS": "Српски језик", + "sv_SE": "Svenska", + "sw": "Kiswahili", + "szl": "Ślōnskŏ gŏdka", + "ta_IN": "தமிழ்", + "ta_LK": "தமிழ்", + "te": "తెలుగు", + "th": "ไทย", + "tl": "Tagalog", + "tr_TR": "Türkçe", + "tt_RU": "Татар теле", + "tah": "Reo Tahiti", + "ug_CN": "ئۇيغۇرچە", + "uk": "Українська", + "ur": "اردو", + "uz_UZ": "O‘zbekcha", + "vi": "Tiếng Việt", + "zh_TW": "繁體中文", + "zh_HK": "香港中文版 ", + "zh_CN": "简体中文", + } + return_message=public.return_msg_gettext(True, language) + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + + # 请求参数校验 + def check_param(self, args): + """ + @name 检测传入参数 + @author zhwen<2022-03-10> + """ + # 检查email格式 + rep_email = r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?" + # 检查域名格式 + rep_domain = r"^(?=^.{3,255}$)[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62}(\.[a-zA-Z0-9\_\-][a-zA-Z0-9\_\-]{0,62})+$" + values = {} + if hasattr(args, 'd_id'): + if re.search(r'\d+', args.d_id): + values["d_id"] = args.d_id + else: + return public.return_message(-1,0, "Please check if the [{}] format is correct For example: {}".format("d_id", "99")) + if hasattr(args, 's_id'): + if re.search(r'\d+', args.s_id): + values["s_id"] = args.s_id + else: + return public.return_message(-1,0, "Please check if the [{}] format is correct For example: {}".format("s_id", "99")) + if hasattr(args, 'language'): + if args.language in self.get_language()['message']: + values['language'] = args.language + else: + return public.return_message(-1,0, "Please check if the [{}] format is correct For example: {}".format("language", "en")) + if hasattr(args, 'domain'): + if re.search(rep_domain, args.domain): + values['domain'] = public.xssencode2(args.domain) + else: + return public.return_message(-1,0, "Please check if the [{}] format is correct For example: {}".format("domain", "aapanel.com")) + if hasattr(args, 'weblog_title'): + values['weblog_title'] = public.xssencode2(args.weblog_title) + if hasattr(args, 'user_name'): + values['user_name'] = public.xssencode2(args.user_name) + if hasattr(args, 'admin_password'): + values['admin_password'] = public.xssencode2(args.admin_password) + if hasattr(args, 'pw_weak'): + if args.pw_weak in ['on', 'off']: + values['pw_weak'] = args.pw_weak + else: + return public.return_message(-1,0, "Please check if the [{}] format is correct For example: {}".format("pw_weak", "on/off")) + if hasattr(args, 'admin_email'): + if re.search(rep_email, args.admin_email): + values['admin_email'] = public.xssencode2(args.admin_email) + else: + return public.return_message(-1,0, "Please check if the [{}] format is correct For example: {}".format("admin_email", "adimn@aapanel.com")) + if hasattr(args, 'prefix'): + values['prefix'] = public.xssencode2(args.prefix) + if hasattr(args, 'php_version'): + values['php_version'] = public.xssencode2(args.php_version) + if hasattr(args, 'enable_cache'): + values['enable_cache'] = public.xssencode2(args.enable_cache) + if hasattr(args, 'wp_action'): + values['slug'] = public.xssencode2(args.slug) + if hasattr(args, 'slug'): + values['wp_action'] = public.xssencode2(args.wp_action) + if hasattr(args, 'active_id'): + values['active_id'] = public.xssencode2(args.active_id) + if hasattr(args, 'new_pass'): + values['new_pass'] = public.xssencode2(args.new_pass) + if hasattr(args, 'user'): + values['user'] = public.xssencode2(args.user) + if hasattr(args, 'sitename'): + values['sitename'] = public.xssencode2(args.sitename) + if hasattr(args, 'act'): + values['act'] = public.xssencode2(args.act) + if hasattr(args, 'version'): + values['version'] = public.xssencode2(args.version) + return public.return_message(0,0, values) + + # 删除网站 + def del_site(self, get): + import panelSite + p = panelSite.panelSite() + site_info = public.M('sites').where('id=?', (get.s_id,)).find() + get.id = get.s_id + get.webname = site_info['name'] + get.ftp = "1" + get.database = "1" + get.path = "1" + p.DeleteSite(get) + + # 安装WP + def deploy_wp(self, get): + """ + d_id 数据据库ID + s_id 网站ID + language 部署wordpress后的语言 + weblog_title wordpress博客名 + user_name wordpress后台用户名 + admin_password 管理员密码 + admin_password2 + pw_weak 允许弱密码 + admin_email 管理员邮箱 + prefix wordpress数据表前缀 + php_version php版本 + enable_cache 开启缓存 + package_version Wordpress版本 + @name 部署wordpress + @author zhwen<2022-03-10> + """ + # 校验参数 + try: + get.validate([ + Param('domain').String().Host(), + Param('weblog_title').String(), + Param('language').String(), + Param('php_version').String(), + Param('user_name').String(), + Param('admin_email').String().Email(), + Param('prefix').String(), + Param('pw_weak').String(), + Param('admin_password').String(), + Param('enable_cache').Integer(), + Param('d_id').Integer(), + Param('s_id').Integer(), + Param('package_version').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + self.write_logs('', clean=True) + values = self.check_param(get) + if values['status']==-1: + # 删除自动创建的空白网站 + self.del_site(get) + return values + values=values['message'] + s_id = values['s_id'] # 网站ID + d_id = values['d_id'] # 数据库ID + prefix = values['prefix'] # 前缀 + site_info = public.M('sites').where('id=?', (s_id,)).find() + print("Get site info:\n ID: {}\npath: {}\n".format(s_id, site_info['path'])) + self.write_logs(""" + |-Get website information: + ID: {} + Path: {} + + """.format(s_id, site_info['path'])) + db_info = public.M('databases').where('id=?', (d_id,)).find() + + print("Get database information: \nID: {}\nDBName: {}\nUser: {}\nPassWD: {}".format( + d_id, db_info["name"], db_info["username"], db_info["password"])) + self.write_logs(""" + |-Get database information: + ID: {} + DBName: {} + User: {} + PassWD: {} + + """.format(d_id, db_info["name"], db_info["username"], db_info["password"])) + values['dbname'] = db_info["name"] + self.wp_user = values['user_name'] + self.wp_passwd = values['admin_password'] + values['db_user'] = db_info["username"] + values['db_pwd'] = db_info["password"] + values['site_path'] = site_info['path'] + values['site_name'] = site_info['name'] + + # 开始下载安装包 + if self.__IS_PRO_MEMBER: + from wp_toolkit import wp_version + + wp_version_obj = wp_version() + + if 'package_version' not in get or get.package_version is None: + get.package_version = wp_version_obj.latest_version()['version'] + + self.write_logs('|-Package version: {}'.format(get.package_version)) + + # 下载特定版本的安装包 + self.package_zip = wp_version_obj.download_package(get.package_version) + else: + # 下载最新版本的安装包 + self.download_latest_package() + + self.unzip_package(site_info['path']) + + # 优化PHP + res = optimize_php().optimize_php(get) + if not res['status']: + return public.return_message(-1,0,res) + + # 初始化wp + self.init_wp(values) + self.__write_db(s_id, d_id, prefix, get.user_name, get.admin_password) + + # 优化mysql + optimize_db().self_db_cache(get) + + # 设置fastcgi缓存 + if int(get.enable_cache) == 1 and public.get_webserver() == 'nginx': + if self.__IS_PRO_MEMBER: + from wp_toolkit import wpmgr, wpfastcgi_cache + + # 配置Nginx-fastcgi-cache + wpfastcgi_cache().set_fastcgi(values['site_path'], values['site_name'], values['php_version']) + + self.write_logs('|-WP Plugin nginx-helper installing...') + wpmgr(s_id).init_plugin_nginx_helper() + self.write_logs('|-WP Plugin nginx-helper installation succeeded') + else: + # 安装nginxHelper + # slug nginx-helper + values['slug'] = "nginx-helper" + values['wp_action'] = "install-plugin" + self.install_plugin(values) + self.act_nginx_helper_active(values) + + # 安装并启用nginx-helper插件 + self.set_nginx_helper(get) + + # 设置登录入口保护 + if int(get.get('enable_whl', 0)) == 1: + if self.__IS_PRO_MEMBER: + from wp_toolkit import wpmgr + + # 安装并启用wps-hide-login插件 + self.write_logs('|-WP Plugin wps-hide-login installing...') + wpmgr(s_id).init_plugin_wps_hide_login(get.get('whl_page', 'login'), get.get('whl_redirect_admin', '404')) + self.write_logs('|-WP Plugin wps-hide-login installation succeeded') + + public.ServiceReload() + + self.write_logs("\n\n\n|-Deployment was successful!") + + return public.return_message(0,0, "Deployment was successful!") + except: + self.del_site(get) + from traceback import format_exc + public.print_log(format_exc()) + return public.return_message(-1,0, "Deployment failed!") + + # 重新关联WP网站数据库 + def reset_wp_db(self, args): + """ + :param args db_name 数据库名 + :param args site_id 网站ID + """ + db_name = public.xssencode2(args.db_name) + try: + site_id = int(args.site_id) + except: + return public.return_message(-1,0, "Site ID must be numeric") + db_info = public.M("databases").where("name=?", (db_name,)).field('id').find() + if 'id' not in db_info: + return public.return_message(-1,0, "This database was not found!") + pdata = { + "d_id": db_info['id'] + } + public.M('wordpress_onekey').where("s_id=?", (site_id,)).update(pdata) + return public.return_message(0,0, "Setup successfully!") + + # 获取WP Toolkit配置信息 + def get_wp_configurations(self, args: public.dict_obj): + # 校验参数 + try: + args.validate([ + Param('s_id').Require().Integer('>', 0), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + from wp_toolkit import wpmgr + + wpmgr_obj = wpmgr(args.s_id) + + wp_local_version = wpmgr_obj.get_local_version() + wp_latest_version = wpmgr_obj.get_latest_version(available=True)['version'] + can_upgrade = wp_latest_version > wp_local_version + + wp_toolkit_config_data = wpmgr_obj.get_wp_toolkit_config_data() + + return public.success_v2({ + 'local_version': wp_local_version, + 'latest_version': wp_latest_version, + 'can_upgrade': can_upgrade, + 'language': wp_toolkit_config_data['locale'], + 'login_url': wp_toolkit_config_data['login_url'], + 'home_url': wp_toolkit_config_data['site_url'], + 'cache_enabled': self.get_cache_status(args.s_id), + 'admin_user': wp_toolkit_config_data['admin_info']['user_login'], + 'admin_email': wp_toolkit_config_data['admin_info']['user_email'], + 'whl_enabled': wp_toolkit_config_data['whl_config'].get('activated', False), + 'whl_page': wp_toolkit_config_data['whl_config'].get('whl_page', 'login'), + 'whl_redirect_admin': wp_toolkit_config_data['whl_config'].get('whl_redirect_admin', '404'), + }) + + # 保存WP Toolkit配置 + def save_wp_configurations(self, args: public.dict_obj): + # 校验参数 + try: + args.validate([ + Param('s_id').Require().Integer('>', 0), + Param('language').String('in', list(self.get_language(args)['message'].keys())), + Param('admin_password').String('>=', 8), + Param('admin_email').Email(), + Param('whl_enabled').Integer('in', [0, 1]), + Param('whl_page').SafePath(), + Param('whl_redirect_admin').SafePath(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + from wp_toolkit import wpmgr + + wpmgr_obj = wpmgr(args.s_id) + + # 更新语言 + if 'language' in args: + locale = wpmgr_obj.get_local_language() + if args.language != locale: + wpmgr_obj.update_language(args.language) + + # 写操作日志 + wpmgr.log_opt('Change language from [{}] to [{}] successfully', (locale, args.language)) + + # 更新管理员密码 + if 'admin_password' in args: + wpmgr_obj.set_admin_password(args.admin_password) + + # 写操作日志 + wpmgr.log_opt('Reset admin password successfully') + + # 更新管理员邮箱 + if 'admin_email' in args: + wpmgr_obj.set_admin_email(args.admin_email) + + # 写操作日志 + wpmgr.log_opt('Reset admin email to [{}] successfully', (args.admin_email,)) + + # 更新WPS-Hide-Login插件配置 + if 'whl_enabled' in args: + # 停用插件 + if int(args.whl_enabled) == 0: + wpmgr_obj.deactivate_plugins('wps-hide-login/wps-hide-login.php') + + # 写操作日志 + wpmgr.log_opt('Deactivate plugin [{}] successfully', ('WPS Hide Login',)) + + # 启用插件 + else: + wpmgr_obj.config_plugin_wps_hide_login(args.get('whl_page', 'login'), args.get('whl_redirect_admin', '404')) + + # 写操作日志 + wpmgr.log_opt('Activate plugin [{}] successfully', ('WPS Hide Login',)) + + return public.success_v2('Update successfully') + + +##############################对外接口-END############################## + +class optimize_php: + + def optimize_php(self, get): + get.version = get.php_version + # 安装缓存插件 + # self.install_ext(get) + # 根据主机性能调整参数 + return self.php_fpm(get) + + def install_ext(self, get): + exts = ['opcache'] + import files + mfile = files.files() + for ext in exts: + # print("开始安装php扩展 [{}]...".format(ext)) + + if ext == 'pathinfo': + import config + con = config.config() + get.version = get.php_version + get.type = 'on' + con.setPathInfo(get) + else: + get.name = ext + get.version = get.php_version + get.type = '1' + mfile.InstallSoft(get) + + # 优化phpfpm + def php_fpm(self, get): + one_key_wp().write_logs("|-Start tuning PHP FPM parameters...") + mem_total = int(get_mem()) + if mem_total <= 1024: + get.max_children = '30' + get.start_servers = '5' + get.min_spare_servers = '5' + get.max_spare_servers = '20' + elif 1024 < mem_total <= 2048: + get.max_children = '50' + get.start_servers = '5' + get.min_spare_servers = '5' + get.max_spare_servers = '30' + elif 2048 < mem_total <= 4098: + get.max_children = '80' + get.start_servers = '10' + get.min_spare_servers = '10' + get.max_spare_servers = '30' + elif 4098 < mem_total <= 8096: + get.max_children = '120' + get.start_servers = '10' + get.min_spare_servers = '10' + get.max_spare_servers = '30' + elif 8096 < mem_total <= 16192: + get.max_children = '200' + get.start_servers = '15' + get.min_spare_servers = '15' + get.max_spare_servers = '50' + elif 16192 < mem_total <= 32384: + get.max_children = '300' + get.start_servers = '20' + get.min_spare_servers = '20' + get.max_spare_servers = '50' + elif 32384 < mem_total: + get.max_children = '500' + get.start_servers = '20' + get.min_spare_servers = '20' + get.max_spare_servers = '50' + # get.version = self.version + import config + current_conf = config.config().getFpmConfig(get) + get.pm = current_conf['pm'] + get.listen = current_conf['unix'] + one_key_wp().write_logs(""" + ===================PHP FPM parameters======================= + + max_children: {} + start_servers: {} + min_spare_servers: {} + max_spare_servers: {} + Running mode: {} + Connection: {} + + ===================PHP FPM parameters======================= + + + """.format(get.max_children, get.start_servers, get.min_spare_servers, get.max_spare_servers, get.pm, get.listen)) + # self.backup_conf('/www/server/php/{}/etc/php-fpm.conf'.format(self.version)) + result = config.config().setFpmConfig(get) + if not result['status']: + one_key_wp().write_logs("|-PHP FPM Optimization failed: {}".format(result)) + return public.return_msg_gettext(False, "PHP FPM Optimization failed: {}", (result,)) + one_key_wp().write_logs("|-PHP FPM optimization succeeded") + return public.return_msg_gettext(True, "PHP FPM optimization succeeded") + + +class optimize_db: + + def self_db_cache(self, get): + one_key_wp().write_logs("|-Start optimizing Mysql") + mem_total = int(get_mem()) + if mem_total <= 2048: + get.key_buffer_size = '128' + get.query_cache_size = '64' + get.tmp_table_size = '64' + get.innodb_buffer_pool_size = '256' + get.innodb_log_buffer_size = '16' + get.sort_buffer_size = '768' + get.read_buffer_size = '768' + get.read_rnd_buffer_size = '512' + get.join_buffer_size = '1024' + get.thread_stack = '256' + get.binlog_cache_size = '64' + get.thread_cache_size = '64' + get.table_open_cache = '128' + get.max_connections = '100' + get.query_cache_type = '1' + get.max_heap_table_size = '64' + elif 2048 < mem_total <= 4096: + get.key_buffer_size = '256' + get.query_cache_size = '128' + get.tmp_table_size = '384' + get.innodb_buffer_pool_size = '384' + get.innodb_log_buffer_size = '16' + get.sort_buffer_size = '768' + get.read_buffer_size = '768' + get.read_rnd_buffer_size = '512' + get.join_buffer_size = '2048' + get.thread_stack = '256' + get.binlog_cache_size = '64' + get.thread_cache_size = '96' + get.table_open_cache = '192' + get.max_connections = '200' + get.query_cache_type = '1' + get.max_heap_table_size = '384' + elif 4096 < mem_total <= 8192: + get.key_buffer_size = '384' + get.query_cache_size = '192' + get.tmp_table_size = '512' + get.innodb_buffer_pool_size = '512' + get.innodb_log_buffer_size = '16' + get.sort_buffer_size = '1024' + get.read_buffer_size = '1024' + get.read_rnd_buffer_size = '768' + get.join_buffer_size = '2048' + get.thread_stack = '256' + get.binlog_cache_size = '128' + get.thread_cache_size = '128' + get.table_open_cache = '384' + get.max_connections = '300' + get.query_cache_type = '1' + get.max_heap_table_size = '512' + elif 8192 < mem_total <= 16384: + get.key_buffer_size = '512' + get.query_cache_size = '256' + get.tmp_table_size = '1024' + get.innodb_buffer_pool_size = '1024' + get.innodb_log_buffer_size = '16' + get.sort_buffer_size = '2048' + get.read_buffer_size = '2048' + get.read_rnd_buffer_size = '1024' + get.join_buffer_size = '4096' + get.thread_stack = '384' + get.binlog_cache_size = '192' + get.thread_cache_size = '192' + get.table_open_cache = '1024' + get.max_connections = '400' + get.query_cache_type = '1' + get.max_heap_table_size = '1024' + elif 16384 < mem_total <= 32768: + get.key_buffer_size = '1024' + get.query_cache_size = '384' + get.tmp_table_size = '2048' + get.innodb_buffer_pool_size = '4096' + get.innodb_log_buffer_size = '16' + get.sort_buffer_size = '4096' + get.read_buffer_size = '4096' + get.read_rnd_buffer_size = '2048' + get.join_buffer_size = '8192' + get.thread_stack = '512' + get.binlog_cache_size = '256' + get.thread_cache_size = '256' + get.table_open_cache = '2048' + get.max_connections = '500' + get.query_cache_type = '1' + get.max_heap_table_size = '2048' + elif 32768 < mem_total: + get.key_buffer_size = '2048' + get.query_cache_size = '500' + get.tmp_table_size = '4096' + get.innodb_buffer_pool_size = '8192' + get.innodb_log_buffer_size = '16' + get.sort_buffer_size = '8192' + get.read_buffer_size = '8192' + get.read_rnd_buffer_size = '4096' + get.join_buffer_size = '16384' + get.thread_stack = '1024' + get.binlog_cache_size = '512' + get.thread_cache_size = '512' + get.table_open_cache = '2048' + get.max_connections = '1000' + get.query_cache_type = '1' + get.max_heap_table_size = '4096' + one_key_wp().write_logs(""" + =====================Mysql parameters======================= + + key_buffer_size: {} + query_cache_size: {} + tmp_table_size: {} + innodb_buffer_pool_size: {} + innodb_log_buffer_size: {} + sort_buffer_size: {} + read_buffer_size: {} + read_rnd_buffer_size: {} + join_buffer_size: {} + thread_stack: {} + binlog_cache_size: {} + thread_cache_size: {} + table_open_cache: {} + max_connections: {} + query_cache_type: {} + max_heap_table_size: {} + + =====================Mysql parameters======================= + + """.format(get.key_buffer_size, get.query_cache_size, get.tmp_table_size, get.innodb_buffer_pool_size, + get.innodb_log_buffer_size, get.sort_buffer_size, get.read_buffer_size, get.read_rnd_buffer_size, + get.join_buffer_size, get.thread_stack, get.binlog_cache_size, get.thread_cache_size, get.table_open_cache, + get.max_connections, get.query_cache_type, get.max_heap_table_size)) + import database + result = database.database().SetDbConf(get) + if not result['status']: + one_key_wp().write_logs("|-Mysql optimization failed {}".format(result)) + return public.return_msg_gettext(False, "Mysql optimization failed {}", (result,)) + public.ExecShell("/etc/init.d/mysqld restart") + one_key_wp().write_logs("|-Mysql optimization succeeded") + return public.return_msg_gettext(True, "Mysql optimization succeeded") + + +# Nginx缓存加速WP站点 +class fast_cgi: + + def get_fastcgi_conf(self, version): + conf = r""" +set $skip_cache 0; + +if ($request_method = POST) { + set $skip_cache 1; +} + +if ($query_string != "") { + set $skip_cache 1; +} + +if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") { + set $skip_cache 1; +} + +if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { + set $skip_cache 1; +} + +location ~ (/[^/]+\.php)(/|$) { + if ( !-f $document_root$1 ) { + return 404; + } + + # try_files $uri =404; + fastcgi_pass unix:/tmp/php-cgi-%s.sock; + fastcgi_index index.php; + include fastcgi.conf; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + fastcgi_cache_bypass $skip_cache; + fastcgi_no_cache $skip_cache; + add_header X-Cache "$upstream_cache_status From $host"; + fastcgi_cache WORDPRESS; + add_header Cache-Control max-age=0; + add_header Nginx-Cache "$upstream_cache_status"; + add_header Last-Modified $date_gmt; + add_header X-Frame-Options SAMEORIGIN; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + etag on; + fastcgi_cache_valid 200 301 302 1d; +} + +location ~ /purge(/.*) { + allow 127.0.0.1; + deny all; + fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1"; +} +""" % version + return conf + + def get_fast_cgi_status(self, sitename, php_v): + if public.get_webserver() != "nginx": + return False + conf_path = "/www/server/panel/vhost/nginx/{}.conf".format(sitename) + content = public.readFile(conf_path) + fastcgi_conf = "include enable-php-{}-wpfastcgi.conf;".format(php_v) + # return conf_path,fastcgi_conf + if fastcgi_conf in content: + return True + return False + + def set_nginx_conf(self): + if not os.path.exists("/dev/shm/nginx-cache/wp"): + os.makedirs("/dev/shm/nginx-cache/wp") + one_key_wp().set_permission("/dev/shm/nginx-cache") + conf = """ + #AAPANEL_FASTCGI_CONF_BEGIN + fastcgi_cache_key "$scheme$request_method$host$request_uri"; + fastcgi_cache_path /dev/shm/nginx-cache/wp levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=1g; + fastcgi_cache_use_stale error timeout invalid_header http_500; + fastcgi_ignore_headers Cache-Control Expires Set-Cookie; + #AAPANEL_FASTCGI_CONF_END + """ + conf_path = "/www/server/nginx/conf/nginx.conf" + public.back_file(conf_path) + content = public.readFile(conf_path) + if not content: + return + if "#AAPANEL_FASTCGI_CONF_BEGIN" in content: + one_key_wp().write_logs("|-Nginx FastCgi cache configuration already exists") + print("Nginx FastCgi cache configuration already exists") + return public.return_msg_gettext(True, "Nginx FastCgi cache configuration already exists") + rep = "http\\s*\n\\s*{" + content = re.sub(rep, "http\n\t{" + conf, content) + public.writeFile(conf_path, content) + + # 如果配置出错恢复 + conf_pass = public.checkWebConfig() + if conf_pass != True: + public.restore_file(conf_path) + one_key_wp().write_logs("|-Nginx FastCgi configuration error! {}".format(conf_pass)) + print("Nginx FastCgi configuration error! {}".format(conf_pass)) + return public.return_msg_gettext(False, "Nginx FastCgi configuration error!") + one_key_wp().write_logs("|-Nginx FastCgi cache configuration complete...") + print("Nginx FastCgi cache configuration complete") + return public.return_msg_gettext(True, "Nginx FastCgi cache configuration complete") + + def set_nginx_init(self): + # if not os.path.exists("/dev/shm/nginx-cache/wp"): + # os.makedirs("/dev/shm/nginx-cache/wp") + # one_key_wp().set_permission("/dev/shm/nginx-cache") + conf2 = """ + #AAPANEL_FASTCGI_CONF_BEGIN + mkdir -p /dev/shm/nginx-cache/wp + #AAPANEL_FASTCGI_CONF_END + """ + init_path = "/etc/init.d/nginx" + public.back_file(init_path) + content_init = public.readFile(init_path) + if not content_init: + return + if "#AAPANEL_FASTCGI_CONF_BEGIN" in content_init: + one_key_wp().write_logs("|-Nginx init FastCgi cache configuration already exists") + print("Nginx init FastCgi cache configuration already exists") + return public.return_msg_gettext(True, "Nginx init FastCgi cache configuration already exists") + # content_init = re.sub(r"\$NGINX_BIN -c \$CONFIGFILE", + conf2, content_init) + rep2 = r"\$NGINX_BIN -c \$CONFIGFILE" + content_init = re.sub(rep2, conf2 + " $NGINX_BIN -c $CONFIGFILE", content_init) + public.writeFile(init_path, content_init) + + # 如果配置出错恢复 + public.ExecShell("/etc/init.d/nginx restart") + conf_pass = public.is_nginx_process_exists() + if conf_pass == False: + public.restore_file(init_path) + one_key_wp().write_logs("|-Nginx init FastCgi configuration error! {}".format(conf_pass)) + print("Nginx init FastCgi configuration error! {}".format(conf_pass)) + return public.return_msg_gettext(False, "Nginx init FastCgi configuration error!") + one_key_wp().write_logs("|-Nginx init FastCgi cache configuration complete...") + print("Nginx init FastCgi cache configuration complete") + return public.return_msg_gettext(True, "Nginx init FastCgi cache configuration complete") + + def set_fastcgi_php_conf(self, version): + conf_path = "/www/server/nginx/conf/enable-php-{}-wpfastcgi.conf".format(version) + if os.path.exists(conf_path): + one_key_wp().write_logs("|-Nginx FastCgi PHP configuration already exists...") + print("Nginx FastCgi PHP configuration already exists") + return True + public.writeFile(conf_path, self.get_fastcgi_conf(version)) + + def set_website_conf(self, version, sitename, act=None): + conf_path = "/www/server/panel/vhost/nginx/{}.conf".format(sitename) + public.back_file(conf_path) + conf = public.readFile(conf_path) + if not conf: + print("Website configuration file does not exist {}".format(conf_path)) + one_key_wp().write_logs("|-Website configuration file does not exist: {}".format(conf_path)) + return public.return_message(-1,0,False) + if act == 'disable': + fastcgi_conf = "include enable-php-{}-wpfastcgi.conf;".format(version) + if fastcgi_conf not in conf: + print("FastCgi configuration does not exist in website configuration") + one_key_wp().write_logs("|-FastCgi configuration does not exist in website configuration, skip") + return public.return_message(-1,0, "FastCgi configuration does not exist in website configuration") + rep = r"include\s+enable-php-{}-wpfastcgi.conf;".format(version) + conf = re.sub(rep, "include enable-php-{}.conf;".format(version), conf) + else: + fastcgi_conf = "include enable-php-{}-wpfastcgi.conf;".format(version) + if fastcgi_conf in conf: + one_key_wp().write_logs( + "|-The FastCgi configuration already exists in the website configuration, skip it") + return public.return_message(0,0, + "The FastCgi configuration already exists in the website configuration") + rep = r"include\s+enable-php-{}.conf;".format(version) + + one_key_wp().write_logs("|-Current configuration: {}".format(conf)) + one_key_wp().write_logs("|-Regular expression: {}".format(rep)) + + conf = re.sub(rep, fastcgi_conf, conf) + + one_key_wp().write_logs("|-Modified configuration: {}".format(conf)) + public.writeFile(conf_path, conf) + conf_pass = public.checkWebConfig() + if conf_pass != True: + public.restore_file(conf_path) + print("Website FastCgi configuration error {}".format(conf_pass)) + one_key_wp().write_logs("|-Website FastCgi configuration error: {}", (conf_pass,)) + return public.return_message(-1,0, "Website FastCgi configuration error!") + print("Website FastCgi configuration complete") + one_key_wp().write_logs("|-Website FastCgi configuration complete...") + return public.return_message(0,0, "Website FastCgi configuration complete") + + def set_fastcgi(self, values): + """ + get.version + get.name + """ + # 设置nginx启动文件 + self.set_nginx_init() + # 设置nginx全局配置 + self.set_nginx_conf() + # 设置fastcgi location + self.set_fastcgi_php_conf(values['php_version']) + # 设置网站配置文件 + self.set_website_conf(values['php_version'], values['site_name']) + # 设置wp的变量用于nginxhelper插件清理缓存 + self.set_wp_nginx_helper(values['site_path']) + # 设置userini允许访问 /dev/shm/nginx-cache/wp 目录 + self.set_userini(values['site_path']) + + def set_wp_nginx_helper(self, site_path): + cache_conf = """ + #AAPANEL_FASTCGICACHE_BEGIN + define('RT_WP_NGINX_HELPER_CACHE_PATH','/dev/shm/nginx-cache/wp'); + #AAPANEL_FASTCGICACHE_END + """ + conf_file = "{}/wp-config.php".format(site_path) + conf = public.readFile(conf_file) + if not conf: + print("Wordpress configuration file does not exist: {}".format(conf_file)) + one_key_wp().write_logs("|-Wordpress configuration file does not exist: {}".format(conf_file)) + return public.return_msg_gettext(False, "Wordpress configuration file does not exist: {}", (conf_file,)) + if re.search(r'''define\(\s*'RT_WP_NGINX_HELPER_CACHE_PATH'\s*,''', conf): + # if "RT_WP_NGINX_HELPER_CACHE_PATH" in conf: + one_key_wp().write_logs("|-Cache cleaning configuration already exists, skip") + print("Cache cleaning configuration already exists") + return + conf = conf.replace(" +#------------------------------------------------------------------- + +#------------------------------ +# 系统安全管理控制器 +#------------------------------ +import os,sys,public,json,re + +class Controller: + + + def __init__(self): + pass + + def model(self,args): + ''' + @name 调用指定项目模型 + @author hezhihong<2024-04-15> + @param args { + mod_name: string<模型名称> + def_name: string<方法名称> + data: JSON + } + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: + return_message=public.return_status_code(1000,'Bad call!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.exists_args('def_name,mod_name',args) + if args['def_name'].find('__') != -1: + return_message=public.return_status_code(1000,'The called method name cannot contain the "__" character') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['mod_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['def_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + except: + return public.return_message(-1,0, public.get_error_object()) + # 参数处理 + mod_name = "{}Model".format(args['mod_name'].strip()) + def_name = args['def_name'].strip() + model_index = None + if 'model_index' in args: + model_index = args['model_index'] + + if not hasattr(args,'data'): args.data = {} + if args.data: + if isinstance(args.data,str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.return_message(-1,0, public.get_error_object()) + else: + pdata = args.data + else: + pdata = args + + if isinstance(pdata,dict): + pdata = public.to_dict_obj(pdata) + + if not isinstance(pdata,public.dict_obj): + return public.return_message(-1,0, "The passed parameter is not a universal internal object") + + # 告诉加载器,要加载什么模块 + if model_index: pdata.model_index = model_index + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper()) + hook_result = public.exec_hook(hook_index,pdata) + if isinstance(hook_result,public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result,dict): + return public.return_message(-1,0, hook_result) # 响应具体错误信息 + elif isinstance(hook_result,bool): + if not hook_result: # 直接中断操作 + return_message=public.return_data(False,{},error_msg='Pre-HOOK interrupt operation') + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 调用处理方法 + # result = run_object(pdata) + import public.PluginLoader as plugin_loader + mod_file = '{}/class_v2/{}ModelV2/{}.py'.format(public.get_panel_path(),model_index,mod_name) + plugin_class = plugin_loader.get_module(mod_file) + class_string='main' + # if mod_name=='ftpModel': + # class_string='ftplog' + plugin_object = getattr(plugin_class,class_string)() + result = getattr(plugin_object,def_name)(pdata) + if isinstance(result,dict): + if 'status' in result and result['status'] == False and 'msg' in result: + if isinstance(result['msg'],str): + if result['msg'].find('Traceback ') != -1: + raise public.return_message(-1,0,public.PanelError(result['msg'])) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index,hook_data) + if isinstance(hook_result,dict): + result = hook_result['result'] + return result + + diff --git a/class_v2/panelDatabaseControllerV2.py b/class_v2/panelDatabaseControllerV2.py new file mode 100644 index 00000000..240dec59 --- /dev/null +++ b/class_v2/panelDatabaseControllerV2.py @@ -0,0 +1,128 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hezhihong<2024-04-15> +#------------------------------------------------------------------- + +#------------------------------ +# 数据库管理控制器 +#------------------------------ +import os,sys,public,json,re + +class DatabaseController: + + + def __init__(self): + pass + + def model(self,args): + ''' + @name 调用指定项目模型 + @author hezhihong<2024-04-15> + @param args { + mod_name: string<模型名称> + def_name: string<方法名称> + data: JSON + } + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: + return_message=public.return_status_code(1000,'Bad call!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.exists_args('def_name,mod_name',args) + if args['def_name'].find('__') != -1: + return_message=public.return_status_code(1000,'The called method name cannot contain the "__" character') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['mod_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['def_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + except: + return public.return_message(-1,0, public.get_error_object()) + # 参数处理 + module_name = args['mod_name'].strip() + mod_name = "{}Model".format(args['mod_name'].strip()) + def_name = args['def_name'].strip() + + # # 指定模型是否存在 + # mod_file = "{}/databaseModel/{}.py".format(public.get_class_path(),mod_name) + # if not os.path.exists(mod_file): + # return public.return_status_code(1003,mod_name) + # # 实例化 + # def_object = public.get_script_object(mod_file) + # if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name)) + # run_object = getattr(def_object.main(),def_name,None) + + # if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name)) + if not hasattr(args,'data'): args.data = {} + if args.data: + if isinstance(args.data,str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.return_message(-1,0,public.get_error_object()) + elif isinstance(args.data,dict): + pdata = public.to_dict_obj(args.data) + else: + pdata = args.data + else: + pdata = public.dict_obj() + + if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata) + pdata.model_index = 'database_v2' + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper()) + hook_result = public.exec_hook(hook_index,pdata) + if isinstance(hook_result,public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result,dict): + # return hook_result # 响应具体错误信息 + return public.return_message(-1,0, hook_result) + elif isinstance(hook_result,bool): + if not hook_result: # 直接中断操作 + return_message=public.return_data(False,{},error_msg='Pre-HOOK interrupt operation') + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 调用处理方法 + # result = run_object(pdata) + import public.PluginLoader as plugin_loader + mod_file = '{}/class_v2/databaseModelV2/{}.py'.format(public.get_panel_path(),mod_name) + plugin_class = plugin_loader.get_module(mod_file) + plugin_object = getattr(plugin_class,"main")() + result = getattr(plugin_object,def_name)(pdata) + + # public.print_log("-------------------控制器mode : {}".format(result)) + # if isinstance(result,dict): + # if 'status' in result and result['status'] == False and 'msg' in result: + # if isinstance(result['msg'],str): + # if result['msg'].find('Traceback ') != -1: + # raise public.PanelError(result['msg']) + if isinstance(result,dict): + if 'status' in result and result['status'] == -1 and 'message' in result and 'result' in result['message']: + if isinstance(result['message']['result'],str): + if result['message']['result'].find('Traceback ') != -1: + raise public.return_message(-1,0,public.PanelError(result['message']['result'])) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index,hook_data) + if isinstance(hook_result,dict): + result = hook_result['result'] + return result + + diff --git a/class_v2/panelDockerControllerV2.py b/class_v2/panelDockerControllerV2.py new file mode 100644 index 00000000..e8b1073c --- /dev/null +++ b/class_v2/panelDockerControllerV2.py @@ -0,0 +1,133 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hezhihong<2024-04-15> +#------------------------------------------------------------------- + +#------------------------------ +# docker模块控制器 +#------------------------------ +import os,sys,public,json,re + +class DockerController: + + + def __init__(self): + pass + + def model(self,args): + ''' + @name 调用指定项目模型 + @author hezhihong<2024-04-15> + @param args { + mod_name: string<模型名称> + def_name: string<方法名称> + data: JSON + } + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: + return_message=public.return_status_code(1000,'wrong call!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.exists_args('def_name,mod_name',args) + if args['def_name'].find('__') != -1: + return_message=public.return_status_code(1000,'Called method name cannot contain [ __ ] characters') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['mod_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['def_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + except: + return public.return_message(-1,0, public.get_error_object()) + + #静态html调用 + if 'stype' in args and args['stype'] == 'html': + from BTPanel import render_template_string + t_path_root = public.get_panel_path()+'/class/projectModel/templates/' + t_path = t_path_root + args['mod_name']+"_"+args['def_name'] + '.html' + if not os.path.exists(t_path): + return_message=public.return_status_code(1000,'The called template does not exist!'+t_path) + del return_message['status'] + return public.return_message(-1,0, return_message) + t_body = public.readFile(t_path) + public.return_message(0,0, "") + return render_template_string(t_body, data={}) + + # 参数处理 + module_name = args['mod_name'].strip() + mod_name = "{}Model".format(args['mod_name'].strip()) + def_name = args['def_name'].strip() + + # # 指定模型是否存在 + # mod_file = "{}/projectModel/{}.py".format(public.get_class_path(),mod_name) + # if not os.path.exists(mod_file): + # return public.return_status_code(1003,mod_name) + # # 实例化 + # def_object = public.get_script_object(mod_file) + # if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name)) + # run_object = getattr(def_object.main(),def_name,None) + # if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name)) + if not hasattr(args,'data'): args.data = {} + if args.data: + if isinstance(args.data,str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.return_message(-1,0,public.get_error_object()) + else: + pdata = args.data + else: + pdata = args + + if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata) + + pdata.model_index = 'docker_v2' + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper()) + hook_result = public.exec_hook(hook_index,pdata) + if isinstance(hook_result,public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result,dict): + return public.return_message(-1,0,hook_result) # 响应具体错误信息 + elif isinstance(hook_result,bool): + if not hook_result: # 直接中断操作 + return_message=public.return_data(False,{},error_msg='Pre-HOOK interrupt operation') + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 调用处理方法 + # result = run_object(pdata) + import public.PluginLoader as plugin_loader + mod_file = '{}/class_v2/btdockerModelV2/{}.py'.format(public.get_panel_path(),mod_name) + plugin_class = plugin_loader.get_module(mod_file) + plugin_object = getattr(plugin_class, "main")() + result = getattr(plugin_object,def_name)(pdata) + if isinstance(result,dict): + if 'status' in result and result['status'] == False and 'msg' in result: + if isinstance(result['msg'],str): + if result['msg'].find('Traceback ') != -1: + raise public.return_message(-1,0,public.PanelError(result['msg'])) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index,hook_data) + if isinstance(hook_result,dict): + result = hook_result['result'] + return result + + + diff --git a/class_v2/panelModControllerV2.py b/class_v2/panelModControllerV2.py new file mode 100644 index 00000000..05a682d7 --- /dev/null +++ b/class_v2/panelModControllerV2.py @@ -0,0 +1,117 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aapanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hezhihong +# ------------------------------------------------------------------- + +# ------------------------------ +# 网站模型管理控制器 +# ------------------------------ +import json +import public +import re + + +class Controller: + + def __init__(self): + pass + + def model(self, args): + ''' + @name 调用指定项目模型 + @author hezhihong + @param {"mod_name":"string<模型名称>","def_name":"string<方法名称>","data":JSON,} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: return public.return_status_code(1000, '错误的调用!') + public.exists_args('def_name,mod_name', args) + if args['def_name'].find('__') != -1: return public.return_status_code(1000, + '调用的方法名称中不能包含“__”字符') + if not re.match(r"^\w+$", args['mod_name']): return public.return_status_code(1000, + r'调用的模块名称中不能包含\w以外的字符') + if not re.match(r"^\w+$", args['def_name']): return public.return_status_code(1000, + r'调用的方法名称中不能包含\w以外的字符') + except: + return public.get_error_object() + # 参数处理 + module_name = args['mod_name'].strip() + sub_mod_name = args['sub_mod_name'].strip() + mod_name = "{}Mod".format(args['mod_name'].strip()) + def_name = args['def_name'].strip() + model_index = None + if 'model_index' in args: model_index = args['model_index'] + + if not hasattr(args, 'data'): args.data = {} + if args.data: + if isinstance(args.data, str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.get_error_object() + else: + pdata = args.data + else: + pdata = args + + if isinstance(pdata, dict): + pdata = public.to_dict_obj(pdata) + + if not isinstance(pdata, public.dict_obj): + return public.return_error("传递的参数不是通用的内部对象") + + # 告诉加载器,要加载什么模块 + if model_index: pdata.model_index = model_index + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(), def_name.upper()) + hook_result = public.exec_hook(hook_index, pdata) + if isinstance(hook_result, public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result, dict): + return hook_result # 响应具体错误信息 + elif isinstance(hook_result, bool): + if not hook_result: # 直接中断操作 + return public.return_data(False, {}, error_msg='前置HOOK中断操作') + + # 调用处理方法 + # result = run_object(pdata) + # import PluginLoader + + # public.print_log('mol--v2---module_fuile:{}'.format("{}/{}".format(module_name, sub_mod_name))) + # result = PluginLoader.module_run("{}/{}".format(module_name, sub_mod_name), def_name, pdata) + # public.print_log('mol--v2---result:{}'.format(result) + + import public.PluginLoader as plugin_loader + mod_file = '{}/class_v2/modModelV2/project/{}/comMod.py'.format(public.get_panel_path(),module_name) + public.print_log('mol--v2---mod_file:{}'.format(mod_file)) + plugin_class = plugin_loader.get_module(mod_file) + public.print_log('mol--v2---plugin_class:{}'.format(plugin_class)) + class_string='main' + # if mod_name=='ftpModel': + # class_string='ftplog' + plugin_object = getattr(plugin_class,class_string)() + public.print_log('mol--v2---plugin_object:{}'.format(plugin_object)) + result = getattr(plugin_object,def_name)(pdata) + public.print_log('mol--v2---result:{}'.format(result)) + + if isinstance(result, dict): + if 'status' in result and result['status'] == False and 'msg' in result: + if isinstance(result['msg'], str): + if result['msg'].find('Traceback ') != -1: + raise public.PanelError(result['msg']) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(), def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index, hook_data) + if isinstance(hook_result, dict): + result = hook_result['result'] + return result diff --git a/class_v2/panelModelV2/backupModel.py b/class_v2/panelModelV2/backupModel.py new file mode 100644 index 00000000..4731b9eb --- /dev/null +++ b/class_v2/panelModelV2/backupModel.py @@ -0,0 +1,83 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: cjxin +#------------------------------------------------------------------- + +# 备份 +#------------------------------ +import os,sys,re,json,shutil,psutil,time +from panelModelV2.base import panelBase +import public + +class main(panelBase): + + + def __init__(self): + pass + + + def get_site_backup_info(self,get): + """ + @获取网站是否开启计划任务备份 + @param get['site_id'] 网站id + @return + all : 开启全部网站备份 + info:计划任务详情 + """ + + id = get.id + find = public.M('sites').where("id=?",(id,)).find() + if not find: + return public.returnMsg(False,'找不到指定网站.') + + result = {} + result['all'] = 0 + result['info'] = False + result['status'] = True + data = public.M('crontab').where('sName=? and sType =?',(find['name'],'site')).order('id desc').select() + if len(data) > 0: + result['info'] = data[0] + + data = public.M('crontab').where('sName=? and sType =?',('ALL','site')).order('id desc').select() + if len(data) > 0: + result['info'] = data[0] + result['all'] = 1 + return result + + + def get_database_backup_info(self,get): + """ + @获取数据库是否开启计划任务备份 + @param get['site_id'] 数据库id + @return + all : 开启全部数据库备份 + info:计划任务详情 + """ + + id = get.id + find = public.M('databases').where("id=?",(id,)).find() + if not find: + return public.returnMsg(False,'找不到指定数据库.') + + result = {} + result['all'] = 0 + result['info'] = False + result['status'] = True + data = public.M('crontab').where('sName=? and sType =?',(find['name'],'database')).order('id desc').select() + if len(data) > 0: + result['info'] = data[0] + + data = public.M('crontab').where('sName=? and sType =?',('ALL','database')).order('id desc').select() + if len(data) > 0: + result['info'] = data[0] + result['all'] = 1 + return result + + + + + diff --git a/class_v2/panelModelV2/base.py b/class_v2/panelModelV2/base.py new file mode 100644 index 00000000..ccbceed5 --- /dev/null +++ b/class_v2/panelModelV2/base.py @@ -0,0 +1,21 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: cjxin +#------------------------------------------------------------------- + +# 面板其他模型新增功能 +#------------------------------ +import public,re,time,sys,os,json +from datetime import datetime + + + +class panelBase: + + + def __init__(self): + pass diff --git a/class_v2/panelModelV2/publicModel.py b/class_v2/panelModelV2/publicModel.py new file mode 100644 index 00000000..8e2b85b9 --- /dev/null +++ b/class_v2/panelModelV2/publicModel.py @@ -0,0 +1,280 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: cjxin +# ------------------------------------------------------------------- + +# 备份 +# ------------------------------ +import os, sys, re, json, shutil, psutil, time +from panelModelV2.base import panelBase +import public, panelTask +import config_v2 as config + +try: + from BTPanel import cache +except:pass + +class main(panelBase): + __table = 'task_list' + # public.check_database_field("ssl_data.db","ssl_info") + task_obj = panelTask.bt_task() + + def __init__(self): + pass + + + """ + @name 获取面板日志 + """ + def get_update_logs(self,get): + try: + + skey = 'panel_update_logs' + res = cache.get(skey) + if res: return res + + res = public.httpPost('https://www.bt.cn/Api/getUpdateLogs?type=Linux',{}) + + start_index = res.find('(') + 1 + end_index = res.rfind(')') + json_data = res[start_index:end_index] + + res = json.loads(json_data) + cache.set(skey,res,60) + except: + res = [] + + return res + + def get_public_config(self, args): + """ + @name 获取公共配置 + """ + # public.print_log("error 3366666: {}".format()) + _config_obj = config.config() + data = _config_obj.get_config(args)['message'] + + data['task_list'] = self.task_obj.get_task_lists(args) + data['task_count'] = public.M('tasks').where("status!=?", ('1',)).count() + data['get_pd'] = self.get_pd(args) + data['ipv6'] = '' + if _config_obj.get_ipv6_listen(None): data['ipv6'] = 'checked' + data['is_local'] = '' + if public.is_local(): data['is_local'] = 'checked' + + if data['get_pd'] and data['get_pd'][2] != -1: + time_diff = (data['get_pd'][2]-int(time.time())) % (365*86400) + data['active_pro_time'] = int(time.time()) - (365*86400 - time_diff) + else: + data['active_pro_time'] = 0 + data['status_code'] = _config_obj.get_not_auth_status() + if os.path.exists('/www/server/panel/config/api.json'): + try: + res = json.loads(public.readFile('/www/server/panel/config/api.json')) + data['api'] = 'checked' if res['open'] else '' + except: + public.ExecShell('rm -f /www/server/panel/config/api.json') + data['api'] = '' + else: + data['api'] = '' + return public.return_message(0,0,data) + + # 获取公共配置(精简版) + def get_public_config_simple(self, args): + """ + @name 获取公共配置(精简版) + @param args 请求参数 + @return dict + """ + data = {} + data['task_count'] = public.M('tasks').where("status!=?", ('1',)).count() + data['get_pd'] = self.get_pd(args) + + import panelSSL + data['user_info'] = panelSSL.panelSSL().GetUserInfo(None) + + import system_v2 as system + data['panel'] = system.system().GetPanelInfo() + + data["webname"] = public.GetConfigValue("title") + + data['menu_list'] = config.config().get_menu_list(args) + + data['install_finished'] = os.path.exists('{}/data/install_finished.mark'.format(public.get_panel_path())) + + return public.return_message(0, 0, data) + + # 获取常用软件安装状态 + def get_public_config_setup(self, args): + from BTPanel import session + if 'config' not in session: + session['config'] = public.M('config').where("id=?", ('1',)).field( + 'webserver,sites_path,backup_path,status,mysql_root').find() + data = session['config'] + + import system_v2 as system + data.update(system.system().GetConcifInfo()) + + if 'ftpPort' not in data: + # 获取FTP端口 + if 'port' not in session: + import re + try: + file = public.GetConfigValue('setup_path') + '/pure-ftpd/etc/pure-ftpd.conf' + conf = public.readFile(file) + rep = r"\n#?\s*Bind\s+[0-9]+\.[0-9]+\.[0-9]+\.+[0-9]+,([0-9]+)" + port = re.search(rep, conf).groups()[0] + except: + port = '21' + session['port'] = port + + data['ftpPort'] = session['port'] + + return public.return_message(0, 0, data) + + # 获取授权信息 + def get_pd(self, get): + return public.get_pd(get) + + @staticmethod + def set_backup_path(get): + try: + backup_path = get.backup_path.strip().rstrip("/") + except AttributeError: + return public.returnMsg(False, "参数错误") + + if not os.path.exists(backup_path): + return public.returnMsg(False, "指定目录不存在") + + if backup_path[-1] == "/": + backup_path = backup_path[:-1] + + import files + try: + from BTPanel import session + except: + session = None + fs = files.files() + + if not fs.CheckDir(get.backup_path): + return public.returnMsg(False, '不能使用系统关键目录作为默认备份目录') + if session is not None: + session['config']['backup_path'] = os.path.join('/', backup_path) + db_backup = backup_path + '/database' + site_backup = backup_path + '/site' + + if not os.path.exists(db_backup): + try: + os.makedirs(db_backup, 384) + except: + public.ExecShell('mkdir -p ' + db_backup) + + if not os.path.exists(site_backup): + try: + os.makedirs(site_backup, 384) + except: + public.ExecShell('mkdir -p ' + site_backup) + + public.M('config').where("id=?", ('1',)).save('backup_path', (get.backup_path,)) + public.WriteLog('TYPE_PANEL', 'PANEL_SET_SUCCESS', (get.backup_path,)) + + public.restart_panel() + return public.returnMsg(True, "设置成功") + + + def get_soft_status(self, get): + if not hasattr(get, 'name'): return public.return_message(-1, 0,'Parameter error') + s_status = False + status = False + setup = False + name = get.name.strip() + if name == 'web': name = public.get_webserver() + version = '' + if name == 'sqlite': + status = True + if name in ['mysql', 'pgsql', 'sqlserver', 'mongodb', 'redis']: + count = public.M('database_servers').where("LOWER(db_type)=LOWER(?)", (name,)).count() + if count > 0: status = True + if os.path.exists('/www/server/{}'.format(name)) and len(os.listdir('/www/server/{}'.format(name))) > 2: + if not public.M('tasks').where("name like ? and status == -1", + ('安装%{}%'.format(name.replace('-', '')),)).count() > 0: + status = True + setup = True + if name == 'openlitespeed': + status = os.path.exists('/usr/local/lsws/bin/lswsctrl') + setup = status + if status: + path_data = { + "nginx": "/www/server/nginx/logs/nginx.pid", + "mysql": "/www/server/data/localhost.localdomain.pid", + "apache": "/www/server/apache/logs/httpd.pid", + "pure-ftpd": "/var/run/pure-ftpd.pid", + "redis": "/www/server/redis/redis.pid", + "pgsql": "/www/server/pgsql/data_directory/postmaster.pid", + "openlitespeed": "/tmp/lshttpd/lshttpd.pid" + } + if name == 'mysql': + datadir = public.get_datadir() + if datadir: + path_data["mysql"] = "{}/{}.pid".format(datadir, public.get_hostname()) + + if name in path_data.keys(): + if os.path.exists(path_data[name]): + pid = public.readFile(path_data[name]) + if pid: + try: + psutil.Process(int(pid)) + s_status = True + except: + pass + else: + # 可能会存在用户修改主机名后 找不到pid文件的情况 + if name == 'mysql' and not s_status: + for proc in psutil.process_iter(): + if proc.name() == 'mysqld': + s_status = True + public.writeFile(path_data['mysql'], str(proc.pid)) + break + version_data = { + "nginx": '/www/server/nginx/version.pl', + "mysql": "/www/server/mysql/version.pl", + "pgsql": "/www/server/pgsql/data/PG_VERSION", + "apache": "/www/server/apache/version.pl", + "pure-ftpd": "/www/server/pure-ftpd/version.pl", + "openlitespeed": "/usr/local/lsws/VERSION" + } + if name in version_data.keys(): + if os.path.exists(version_data[name]): + version = public.readFile(version_data[name]).strip() + title_data = { + "nagix": "Nginx", + "mysql": "MySQL", + "pgsql": "PostgreSQL", + "mongodb": "MongoDB", + "redis": "Redis", + "apache": "Apache", + "openlitespeed": "OpenLiteSpeed", + "pure-ftpd": "Pure-FTPd", + } + s_version_data = { + 'mysql': 'mysqld', + 'apache': 'httpd', + } + + data = { + "status": status, + "s_status": s_status, + "msg": '', + "version": version, + "name": name.replace('-', ''), + "title": title_data.get(name, name), + "admin": os.path.exists('/www/server/panel/plugin/' + name), + "s_version": s_version_data.get(name, name), + "setup": setup + } + return public.return_message(0, 0,data) + diff --git a/class_v2/panelProjectControllerV2.py b/class_v2/panelProjectControllerV2.py new file mode 100644 index 00000000..a8d36141 --- /dev/null +++ b/class_v2/panelProjectControllerV2.py @@ -0,0 +1,136 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hezhihong<2024-04-15> +#------------------------------------------------------------------- + +#------------------------------ +# 项目管理控制器 +#------------------------------ +import os,sys,public,json,re + +class ProjectController: + + + def __init__(self): + pass + + def model(self,args): + ''' + @name 调用指定项目模型 + @author hezhihong<2024-04-15> + @param args { + mod_name: string<模型名称> + def_name: string<方法名称> + data: JSON + } + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: + return_message=public.return_status_code(1000,'wrong call!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.exists_args('def_name,mod_name',args) + if args['def_name'].find('__') != -1: + return_message=public.return_status_code(1000,'Called method name cannot contain [ __ ] characters') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['mod_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['def_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + except: + return public.return_message(-1,0,public.get_error_object()) + + #静态html调用 + if 'stype' in args and args['stype'] == 'html': + from BTPanel import render_template_string + t_path_root = public.get_panel_path()+'/class_v2/projectModelV2/templates/' + t_path = t_path_root + args['mod_name']+"_"+args['def_name'] + '.html' + if not os.path.exists(t_path): + return_message=public.return_status_code(1000,'The called template does not exist!'+t_path) + del return_message['status'] + return public.return_message(-1,0, return_message) + t_body = public.readFile(t_path) + public.return_message(0,0, "") + return render_template_string(t_body, data={}) + + # 参数处理 + module_name = args.mod_name.strip() + mod_name = "{}Model".format(args.mod_name.strip()) + def_name = args.def_name.strip() + # # 指定模型是否存在 + # mod_file = "{}/projectModel/{}.py".format(public.get_class_path(),mod_name) + # if not os.path.exists(mod_file): + # return public.return_status_code(1003,mod_name) + # # 实例化 + # def_object = public.get_script_object(mod_file) + # if not def_object: return public.return_status_code(1000,'没有找到{}模型'.format(mod_name)) + # run_object = getattr(def_object.main(),def_name,None) + # if not run_object: return public.return_status_code(1000,'没有在{}模型中找到{}方法'.format(mod_name,def_name)) + if not hasattr(args,'data'): args.data = {} + if args.data: + if isinstance(args.data,str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.return_message(-1,0,public.get_error_object()) + else: + pdata = args.data + else: + pdata = args + + if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata) + pdata.model_index = 'project_v2' + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper()) + hook_result = public.exec_hook(hook_index,pdata) + if isinstance(hook_result,public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result,dict): + return public.return_message(-1,0, hook_result) # 响应具体错误信息 + elif isinstance(hook_result,bool): + if not hook_result: # 直接中断操作 + return_message=public.return_data(False,{},error_msg='Pre-HOOK interrupt operation') + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 调用处理方法 + # result = run_object(pdata) + class_string='main' + import public.PluginLoader as plugin_loader + if module_name=='proxy': + class_string='Redirect' + mod_file = '{}/class_v2/projectModelV2/common/redirect.py'.format(public.get_panel_path()) + else: + mod_file = '{}/class_v2/projectModelV2/{}.py'.format(public.get_panel_path(),mod_name) + plugin_class = plugin_loader.get_module(mod_file) + plugin_object = getattr(plugin_class,class_string)() + result = getattr(plugin_object,def_name)(pdata) + if isinstance(result,dict): + if 'status' in result and result['status'] == False and 'msg' in result: + if isinstance(result['msg'],str): + if result['msg'].find('Traceback ') != -1: + raise public.return_message(-1,0,public.PanelError(result['msg'])) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index,hook_data) + if isinstance(hook_result,dict): + result = hook_result['result'] + return result + + + diff --git a/class_v2/panelSafeControllerV2.py b/class_v2/panelSafeControllerV2.py new file mode 100644 index 00000000..70a0d77f --- /dev/null +++ b/class_v2/panelSafeControllerV2.py @@ -0,0 +1,110 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hezhihong +#------------------------------------------------------------------- + +#------------------------------ +# 系统安全管理控制器 +#------------------------------ +import os,sys,public,json,re + +class SafeController: + + + def __init__(self): + pass + + def model(self,args): + ''' + @name 调用指定项目模型 + @author hezhihong<2024-04-15> + @param args { + mod_name: string<模型名称> + def_name: string<方法名称> + data: JSON + } + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: + return_message=public.return_status_code(1000,'wrong call!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.exists_args('def_name,mod_name',args) + if args['def_name'].find('__') != -1: + return_message=public.return_status_code(1000,'The called method name cannot contain the "__" character') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['mod_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not re.match(r"^\w+$",args['def_name']): + return_message=public.return_status_code(1000,r'The called module name cannot contain characters other than \w') + del return_message['status'] + return public.return_message(-1,0, return_message) + except: + return public.return_message(-1,0,public.get_error_object()) + # 参数处理 + module_name = args['mod_name'].strip() + mod_name = "{}Model".format(args['mod_name'].strip()) + def_name = args['def_name'].strip() + + if not hasattr(args,'data'): args.data = {} + if args.data: + if isinstance(args.data,str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.return_message(-1,0,public.get_error_object()) + elif isinstance(args.data,dict): + pdata = public.to_dict_obj(args.data) + else: + pdata = args.data + else: + pdata = public.dict_obj() + + if isinstance(pdata,dict): pdata = public.to_dict_obj(pdata) + pdata.model_index = 'safe_v2' + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(),def_name.upper()) + hook_result = public.exec_hook(hook_index,pdata) + if isinstance(hook_result,public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result,dict): + return public.return_message(-1,0,hook_result) # 响应具体错误信息 + elif isinstance(hook_result,bool): + if not hook_result: # 直接中断操作 + return_message=public.return_data(False,{},error_msg='Pre-HOOK interrupt operation') + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 调用处理方法 + # result = run_object(pdata) + import public.PluginLoader as plugin_loader + mod_file = '{}/class_v2/safeModelV2/{}.py'.format(public.get_panel_path(),mod_name) + plugin_class = plugin_loader.get_module(mod_file) + plugin_object = getattr(plugin_class,"main")() + result = getattr(plugin_object,def_name)(pdata) + if isinstance(result,dict): + if 'status' in result and result['status'] == False and 'msg' in result: + if isinstance(result['msg'],str): + if result['msg'].find('Traceback ') != -1: + raise public.return_message(-1,0, public.PanelError(result['msg'])) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(),def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index,hook_data) + if isinstance(hook_result,dict): + result = hook_result['result'] + return result + + diff --git a/class_v2/panel_api_v2.py b/class_v2/panel_api_v2.py new file mode 100644 index 00000000..f7b417ba --- /dev/null +++ b/class_v2/panel_api_v2.py @@ -0,0 +1,252 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import public,os,json,time +class panelApi: + save_path = '/www/server/panel/config/api.json' + timeout = 600 + max_bind = 5 + def get_token(self,get): + data = self.get_api_config() + if not 'key' in data: + data['key'] = public.GetRandomString(16) + public.writeFile(self.save_path,json.dumps(data)) + + if 'token_crypt' in data: + data['token'] = public.de_crypt(data['token'],data['token_crypt']) + else: + data['token'] = "***********************************" + + data['limit_addr'] = '\n'.join(data['limit_addr']) + data['bind'] = self.get_bind_token() + qrcode = (public.getPanelAddr() + "|" + data['token'] + "|" + data['key'] + '|' + data['bind']['token']+'|aapanel').encode('utf-8') + data['qrcode'] = public.base64.b64encode(qrcode).decode('utf-8') + data['apps'] = sorted(data['apps'],key=lambda x: x['time'],reverse=True) + del(data['key']) + return data + + + def login_for_app(self,get): + from BTPanel import cache + import uuid + tid = get.tid + if(len(tid) != 32): return public.return_msg_gettext(False,'Invalid login key1') + session_id = cache.get(tid) + if not session_id: return public.return_msg_gettext(False,'The specified key does not exist or has expired1') + if(len(session_id) != 64): return public.return_msg_gettext(False,'Invalid login key2') + try: + if not os.path.exists('/www/server/panel/data/app_login_check.pl'):return public.returnMsg(False,'Invalid login key3') + key, init_time, tid2, status = public.readFile('/www/server/panel/data/app_login_check.pl').split(':') + if session_id!=key:return public.returnMsg(False,'Invalid login key4') + if tid != tid2: return public.returnMsg(False, 'The specified key does not exist or has expired5') + if time.time() - float(init_time) > 60: + return public.returnMsg(False, 'QR code validity time expired6') + cache.set(session_id,public.md5(uuid.UUID(int=uuid.getnode()).hex),120) + import uuid + data = key + ':' + init_time + ':' + tid2 + ':' + uuid.UUID(int=uuid.getnode()).hex[-12:] + public.writeFile("/www/server/panel/data/app_login_check.pl", data) + return public.return_msg_gettext(True,'Scan code successfully, log in!') + except: + os.remove("/www/server/panel/data/app_login_check.pl") + return public.return_msg_gettext(False, 'Invalid login key') + + def get_api_config(self): + tmp = public.ReadFile(self.save_path) + if not tmp or not os.path.exists(self.save_path): + data = { "open":False, "token":"", "limit_addr":[] } + public.WriteFile(self.save_path,json.dumps(data)) + public.ExecShell("chmod 600 " + self.save_path) + tmp = public.ReadFile(self.save_path) + data = json.loads(tmp) + + is_save = False + if not 'binds' in data: + data['binds'] = [] + is_save = True + + if not 'apps' in data: + data['apps'] = [] + is_save = True + + data['binds'] = sorted(data['binds'],key=lambda x: x['time'],reverse=True) + if len(data['binds']) > 5: + data['binds'] = data['binds'][:5] + is_save = True + + if is_save: + self.save_api_config(data) + return data + + def save_api_config(self,data): + public.WriteFile(self.save_path,json.dumps(data)) + public.set_mode(self.save_path,'600') + return True + + def check_bind(self,args): + if not 'bind_token' in args or not 'client_brand' in args or not 'client_model' in args: + return 0 + if not args.client_brand or not args.client_model: + return 'Invalid device' + + bind = self.get_bind_token(args.bind_token) + if bind['token'] != args.bind_token: + return public.get_msg_gettext('The current QR code has expired, please refresh the page and rescan the code!') + + apps = self.get_apps() + if len(apps) >= self.max_bind: + return public.get_msg_gettext('This server is bound to a maximum of {} devices, which has reached the limit!',(self.max_bind,)) + + bind['status'] = 1 + bind['brand'] = args.client_brand + bind['model'] = args.client_model + self.set_bind_token(bind) + return 1 + + def get_bind_status(self,args): + if not public.cache_get(public.Md5(os.uname().version)): + public.cache_set(public.Md5(os.uname().version),1,60) + bind = self.get_bind_token(args.bind_token) + return bind + + def get_app_bind_status(self,args): + if not 'bind_token' in args: + return 0 + if self.get_app_find(args.bind_token): + return 1 + return 0 + + def set_bind_token(self,bind): + data = self.get_api_config() + is_save = False + for i in range(len(data['binds'])): + if data['binds'][i]['token'] == bind['token']: + data['binds'][i] = bind + is_save = True + break + if is_save: + self.save_api_config(data) + return True + + + def get_apps(self,args = None): + data = self.get_api_config() + return data['apps'] + + def get_app_find(self,bind_token): + apps = self.get_apps() + for s_app in apps: + if s_app['token'] == bind_token: + return s_app + return None + + def add_bind_app(self,args): + bind = self.get_bind_token(args.bind_token) + if bind['status'] == 0: + return public.return_msg_gettext(False,'Failed verification!') + apps = self.get_apps() + if len(apps) >= self.max_bind: + return public.return_msg_gettext(False,'A server allows up to {} device bindings!'.format(self.max_bind)) + + args.bind_app = args.bind_token + self.remove_bind_app(args) + data = self.get_api_config() + data['apps'].append(bind) + self.save_api_config(data) + self.remove_bind_token(args.bind_token) + return public.return_msg_gettext(True,'Bind successfully!') + + def remove_bind_token(self,bind_token): + data = self.get_api_config() + tmp_binds = [] + for s_bind in data['binds']: + if bind_token == s_bind['token']: + continue + tmp_binds.append(s_bind) + data['binds'] = tmp_binds + self.save_api_config(data) + + def remove_bind_app(self,args): + data = self.get_api_config() + tmp_apps = [] + for s_app in data['apps']: + if args.bind_app == s_app['token']: + continue + tmp_apps.append(s_app) + data['apps'] = tmp_apps + self.save_api_config(data) + s_file = '/dev/shm/{}'.format(args.bind_app) + if os.path.exists(s_file): + os.remove(s_file) + return public.return_msg_gettext(True,'Successfully deleted!') + + def get_bind_token(self,token = None): + data = self.get_api_config() + s_time = time.time() + binds = [] + bind = None + is_write = False + for i in range(len(data['binds'])): + if s_time - data['binds'][i]['time'] > self.timeout: + is_write = True + continue + binds.append(data['binds'][i]) + if token: + if token == data['binds'][i]['token']: + bind = data['binds'][i] + else: + if not bind: + bind = data['binds'][i] + if not bind: + if len(binds) > 0: + binds = sorted(binds,key=lambda x: x['time'],reverse=True) + bind = binds[0] + else: + bind = {"time":s_time,"token":public.GetRandomString(18),'status':0} + binds.append(bind) + is_write = True + + if is_write: + data['binds'] = binds + self.save_api_config(data) + return bind + + + def set_token(self,get): + if 'request_token' in get: return public.return_msg_gettext(False,'Cannot configure API through API interface') + data = self.get_api_config() + if get.t_type == '1': + token = public.GetRandomString(32) + data['token'] = public.md5(token) + data['token_crypt'] = public.en_crypt(data['token'],token).decode('utf-8') + public.write_log_gettext('API configuration','Regenerate API-Token') + public.add_security_logs('API configuration','Regenerate API-Token') + elif get.t_type == '2': + data['open'] = not data['open'] + stats = {True:'Open',False:'Close'} + if not 'token_crypt' in data: + token = public.GetRandomString(32) + data['token'] = public.md5(token) + data['token_crypt'] = public.en_crypt(data['token'],token).decode('utf-8') + public.write_log_gettext('API configuration','{} API interface',(stats[data['open']],)) + public.add_security_logs('API configuration', '{} API interface', (stats[data['open']],)) + token = stats[data['open']] + ' success!' + elif get.t_type == '3': + data['limit_addr'] = get.limit_addr.split('\n') + public.write_log_gettext('API configuration','Change IP limit to [{}]',(get.limit_addr,)) + public.add_security_logs('API configuration', 'Change IP limit to [{}]', (get.limit_addr,)) + token ='Saved successfully!' + self.save_api_config(data) + return public.return_msg_gettext(True,token) + + def get_tmp_token(self,get): + if not 'request_token' in get: return public.return_msg_gettext(False,'Temporary keys can only be obtained through the API interface') + data = self.get_api_config() + data['tmp_token'] = public.GetRandomString(64) + data['tmp_time'] = time.time() + self.save_api_config(data) + return public.return_msg_gettext(True,data['tmp_token']) diff --git a/class_v2/panel_auth_v2.py b/class_v2/panel_auth_v2.py new file mode 100644 index 00000000..07bb202d --- /dev/null +++ b/class_v2/panel_auth_v2.py @@ -0,0 +1,781 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------ +# AUTH验证接口 +# ------------------------------ + +import public, time, json, os, requests +from BTPanel import session, cache +from public.validate import Param + + +class panelAuth: + __request_url = None + __product_list_path = 'data/product_list.pl' + __product_bay_path = 'data/product_bay.pl' + __product_id = '100000011' + __official_url = 'https://www.aapanel.com' + # __official_url = 'http://dev.aapanel.com' + __failed_connect_server='Failed to connect to the server!' + + def create_serverid(self, get): + try: + userPath = 'data/userInfo.json' + if not os.path.exists(userPath): + return public.return_message(-1, 0, 'Please login with account first') + tmp = public.readFile(userPath) + if len(tmp) < 2: tmp = '{}' + data = json.loads(tmp) + data['uid'] = data['id'] + if not data: + return public.return_message(-1, 0, 'Please login with account first') + if not 'server_id' in data: + s1 = public.get_mac_address() + public.get_hostname() + s2 = self.get_cpuname() + serverid = public.md5(s1) + public.md5(s2) + data['server_id'] = serverid + public.writeFile(userPath, json.dumps(data)) + return data + except: + return public.return_message(-1, 0,'Please login with account first') + + + def create_plugin_other_order(self, get): + pdata = self.create_serverid(get) + pdata['pid'] = get.pid + pdata['cycle'] = get.cycle + p_url = public.GetConfigValue('home') + '/api/Pluginother/create_order' + if get.type == '1': + pdata['renew'] = 1 + p_url = public.GetConfigValue('home') + '/api/Pluginother/renew_order' + return public.return_message(0, 0, json.loads(public.httpPost(p_url,pdata))) + + def get_order_stat(self, get): + pdata = self.create_serverid(get) + pdata['order_id'] = get.oid + p_url = public.GetConfigValue('home') + '/api/Pluginother/order_stat' + if get.type == '1': p_url = public.GetConfigValue('home') + '/api/Pluginother/re_order_stat' + return public.return_message(0, 0, json.loads(public.httpPost(p_url,pdata))) + + def check_serverid(self, get): + if get.serverid != self.create_serverid(get): return public.return_message(-1,0,False) + return public.return_message(0,0,True) + + # 获取价格列表 新增多机购买 + def get_plugin_price(self, get): + # 校验参数 + try: + get.validate([ + Param('product_id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + userPath = 'data/userInfo.json' + if not 'pluginName' in get and not 'product_id' in get: return public.return_message(-1, 0,'Parameter ERROR!') + if not os.path.exists(userPath): return public.return_message(-1, 0,'Please login with account first') + params = {} + if not hasattr(get, 'product_id'): + params['product_id'] = self.get_plugin_info(get.pluginName)['id'] + else: + params['product_id'] = get.product_id + data = self.send_cloud('{}/api/product/pricesV3'.format(self.__official_url), params) + if data['status']==-1: + return data + data=data['message'] + if not data: + return public.return_message(-1, 0, 'Please log in to your aaPanel account on the panel first!') + if not data['success']: + return public.return_message(-1, 0,data['msg']) + # if len(data['res']) == 6: + # return data['res'][3:] + return public.return_message(0, 0,data['res']) + except: + del(session['get_product_list']) + return public.return_message(-1, 0,'Syncing information, please try again!\n {}',(public.get_error_info(),)) + + def get_plugin_info(self, pluginName): + data = self.get_business_plugin(None) + if data['status']==-1: return None + for d in data['message']: + if d['name'] == pluginName: return d + return None + + def get_plugin_list(self, get): + try: + if not session.get('get_product_bay') or not os.path.exists(self.__product_bay_path): + data = self.send_cloud('get_order_list_byuser', {}) + if data['status']==-1:return data + if data['message']: public.writeFile(self.__product_bay_path, json.dumps(data['message'])) + session['get_product_bay'] = True + data = json.loads(public.readFile(self.__product_bay_path)) + return public.return_message(0,0,data) + except: + return public.return_message(-1,0,None) + + def get_buy_code(self, get): + + # 校验参数 + try: + get.validate([ + Param('pid').Integer(), + Param('cycle').Integer(), + Param('source').Integer(), + Param('num').Integer(), + Param('charge_type').Integer(), + Param('src').Integer(), + Param('is_ipv6').Integer(), + Param('coupon_id').Integer(), + Param('cycle_unit').String(), + Param('pay_channel').String(), + Param('ip').String(), + Param('os').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + cycle = getattr(get, 'cycle', 1) + params = {} + params['cycle'] = cycle + + params['product_id'] = get.pid + params['src'] = 2 + params['trigger_entry'] = get.source + params['pay_channel'] = 2 + # 0.管理后台生成 1.Ping++ 2.Stripe 3.Paypal 10.抵扣券 + if hasattr(get, 'coupon_id'): + params['coupon_id'] = get.coupon_id + if hasattr(get, 'pay_channel'): + params['pay_channel'] = get.pay_channel + #优惠券检测 + if get.pay_channel==10 and not hasattr(get, 'coupon_id'): + return public.return_message(-1, 0, 'parameter error: coupon_id') + if hasattr(get, 'charge_type'): + params['charge_type'] = get.charge_type + if int(get.charge_type) == 1: + if not hasattr(get, 'cycle_unit'):return public.return_message(-1, 0, 'parameter error: cycle_unit') + params['cycle_unit'] = get.cycle_unit + + + env_info = public.fetch_env_info() + params['environment_info'] = json.dumps(env_info) + params['server_id'] = env_info['install_code'] + # 多机购买 数量 + if not hasattr(get, 'num'): + return public.return_message(-1, 0, 'parameter error: num') + params['num'] = get.num + + # 添加购买来源 + # params['source'] = get.source + + data = self.send_cloud('{}/api/order/product/create'.format(self.__official_url), params) + if data['status']==-1: + return data + return public.return_message(0, 0, data['message']['res']) + + def get_stripe_session_id(self, get): + + params = {} + if hasattr(get, 'order_no'): + params['order_no'] = get.order_no + if hasattr(get, 'order_id'): + params['order_id'] = get.order_id + + if hasattr(get, 'subscribe'): + params['subscribe'] = get.subscribe + + if not params.get('order_no', None) and not params.get('order_id', None): + return public.return_message(-1, 0,'parameter error') + + data = self.send_cloud('{}/api/order/product/pay'.format(self.__official_url), params) + session['focre_cloud'] = True + return public.return_message(0, 0, data['message']['res']) + + # paypal支付 + def get_paypal_session_id(self, get): + + params = {} + if hasattr(get, 'oid'): + params['oid'] = get.oid + + if not params.get('oid', None): + return public.return_message(-1, 0, 'parameter error') + + data = self.send_cloud('{}/api/paypal/create_order'.format(self.__official_url), params) + if data['status']==-1: + return data + session['focre_cloud'] = True + data=data['message'] + data2 = { + "status": data.get("success", False), + "res": data.get("res", ""), + "nonce": data.get("nonce", 0), + } + + return public.return_message(0, 0, data2) + + # paypal 支付确认 + def check_paypal_status(self, get): + + params = {} + if hasattr(get, 'paypal_order_id'): + params['paypal_order_id'] = get.paypal_order_id + + if not params.get('paypal_order_id', None): + return public.return_message(-1, 0,'parameter error') + + data = self.send_cloud('{}/api/paypal/capture_order'.format(self.__official_url), params) + data=data['message'] + status_code=-1 + status=data.get("success", False) + if status: + status_code=0 + # 刷新授权状态 + public.load_soft_list() + public.refresh_pd() + data2 = { + "res": data.get("res", ""), + "nonce": data.get("nonce", 0), + } + + return public.return_message(status_code, 0, data2) + + def check_pay_status(self, get): + params = {} + params['id'] = get.id + data = self.send_cloud('check_product_pays', params) + if data['status']==-1:return data + data=data['message'] + if not data: return public.return_message(-1, 0,'Fail to connect to the server!') + if data['status'] == True: + self.flush_pay_status(get) + if 'get_product_bay' in session: del (session['get_product_bay']) + return public.return_message(0, 0, data) + + def flush_pay_status(self, get): + if 'get_product_bay' in session: del (session['get_product_bay']) + data = self.get_plugin_list(get) + if data['status']==-1: return public.return_message(-1, 0,'Fail to connect to the server!') + return public.return_message(0, 0, 'Flush status success') + + def get_renew_code(self): + pass + + def check_renew_code(self): + pass + + def get_business_plugin(self, get): + try: + if not session.get('get_product_list') or not os.path.exists(self.__product_list_path): + data = self.send_cloud('{}/api/product/chargeProducts'.format(self.__official_url), {}) + if data['message']['success']: public.writeFile(self.__product_list_path, json.dumps(data['message']['res'])) + session['get_product_list'] = True + data = json.loads(public.readFile(self.__product_list_path)) + return public.return_message(0,0,data) + except: + return public.return_message(-1,0,None) + + def get_ad_list(self): + pass + + def check_plugin_end(self): + pass + + def get_re_order_status_plugin(self, get): + params = {} + params['pid'] = getattr(get, 'pid', 0) + data = self.send_cloud('get_re_order_status', params) + if data['status']==-1:return data + data=data['message'] + if not data: return public.return_message(-1, 0,'Fail to connect to the server!') + if data['status'] == True: + self.flush_pay_status(get) + if 'get_product_bay' in session: del (session['get_product_bay']) + return public.return_message(0, 0, data) + + def get_voucher_plugin(self, get): + # 校验参数 + try: + get.validate([ + Param('pid').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + params = {} + params['product_id'] = getattr(get, 'pid', 0) + params['status'] = '0' + data = self.send_cloud('{}/api/user/productVouchers'.format(self.__official_url), params) + if data['status']==-1:return data + if not data['message']: + return public.return_message(0, 0,[]) + return public.return_message(0, 0,data['message']['res']) + + def create_order_voucher_plugin(self, get): + # 校验参数 + try: + get.validate([ + Param('cycle_unit').String(), + Param('pid').Integer(), + Param('coupon_id').Integer(), + Param('cycle').Integer(), + Param('charge_type').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + cycle = getattr(get, 'cycle', '1') + params = {} + params['cycle'] = cycle + params['cycle_unit'] = get.cycle_unit + params['coupon_id'] = get.coupon_id + params['src'] = 2 + params['pay_channel'] = 10 + params['charge_type'] = get.charge_type + env_info = public.fetch_env_info() + params['environment_info'] = json.dumps(env_info) + params['server_id'] = env_info['install_code'] + data = self.send_cloud('{}/api/order/product/create'.format(self.__official_url), params) + session['focre_cloud'] = True + if data['message']['success']: + # 刷新授权状态 + public.load_soft_list() + public.refresh_pd() + return public.return_message(0, 0,'Activate successfully') + + return public.return_message(-1, 0, 'Activate failed') + + def send_cloud(self, cloudURL, params): + try: + userInfo = self.create_serverid(None) + if 'token' not in userInfo: + return public.return_message(-1,0,None) + url_headers = {"Content-Type": "application/json", + "authorization": "bt {}".format(userInfo['token']) + } + resp = requests.post(cloudURL, params=params, headers=url_headers) + resp_json = resp.json() + if resp.status_code != 200 or not resp_json.get('success', False): + # 当接口错误信息存在时,返回接口错误信息 + if isinstance(resp_json.get('res', None), str): + raise public.HintException(str(resp_json['res'])) + # return public.return_message(-1, 0, str(resp_json['res'])) + + # 否则统一返回连接服务器失败 + return public.return_message(-1, 0, self.__failed_connect_server) + # if not resp['res']: + # public.print_log('not res:') + # return public.return_message(-1,0,self.__failed_connect_server) + return public.return_message(0, 0, resp_json) + except public.HintException: + raise + except Exception: + return public.return_message(-1,0,self.__failed_connect_server) + + def send_cloud_v2(self, cloudURL, params): + try: + userInfo = self.create_serverid(None) + if 'token' not in userInfo: + return public.return_message(-1,0,None) + url_headers = {"Content-Type": "application/json", + "authorization": "bt {}".format(userInfo['token']) + } + resp = requests.post(cloudURL, params=params, headers=url_headers) + resp = resp.json() + if not resp or 'res' not in resp: + return public.return_message(-1,0,self.__failed_connect_server) + if not resp['res']: + return public.return_message(-1,0,None) + if resp['success'] == False: + return public.return_message(-1,0,self.__failed_connect_server) + return public.return_message(0,0,resp['res']) + except: + return public.return_message(-1,0,self.__failed_connect_server) + + + def send_cloud_v3(self, module, params): + userInfo = self.create_serverid(None); + if 'status' in userInfo: + params['uid'] = 0 + params['serverid'] = '' + else: + params['uid'] = userInfo['uid'] + params['serverid'] = userInfo['serverid'] + params['access_key'] = userInfo['access_key'] + params['os'] = 'Windows' + data = self.send_cloud_pro_2('obtain_coupons', params) + return data + + + def send_cloud_get(self, cloudURL, params): + try: + userInfo = self.create_serverid(None) + if 'token' not in userInfo: + return public.return_message(-1,0,None) + url_headers = {"Content-Type": "application/json", + "authorization": "bt {}".format(userInfo['token']) + } + resp = requests.get(cloudURL,headers=url_headers, stream=True).json() + if not resp or 'res' not in resp: return public.return_message(-1,0,self.__failed_connect_server) + if resp['success'] == False: + return public.return_message(-1,0,self.__failed_connect_server) + return public.return_message(0,0,resp['res']) + except: + return public.return_message(-1,0,self.__failed_connect_server) + + def send_cloud_pro(self, module, params): + try: + cloudURL = '{}/api/order/product/'.format(self.__official_url) + userInfo = self.create_serverid(None) + params['os'] = 'Linux' + if 'status' in userInfo: + params['server_id'] = '' + else: + params['server_id'] = userInfo['server_id'] + url_headers = {"authorization": "bt {}".format(userInfo['token'])} + resp = requests.post(cloudURL, params=params, headers=url_headers) + resp = resp.json()['res'] + if not resp: return None + return resp + except: + return None + + + def send_cloud_pro_2(self, module, params): + try: + cloudURL = '{}/api/user/{}'.format(self.__official_url,module) + userInfo = self.create_serverid(None) + params['os'] = 'Linux' + if 'status' in userInfo: + params['server_id'] = '' + else: + params['server_id'] = userInfo['server_id'] + url_headers = {"authorization": "bt {}".format(userInfo['token'])} + resp = requests.post(cloudURL, params=params, headers=url_headers) + resp = resp.json() + if not resp or 'res' not in resp: + return public.return_message(-1,0,self.__failed_connect_server) + if not resp['res']: return public.return_message(-1,0,self.__failed_connect_server) + if resp['success'] == False: + return public.return_message(-1,0,self.__failed_connect_server) + return public.return_message(0,0,resp['res']) + except: + return public.return_message(-1,0,self.__failed_connect_server) + + def get_voucher(self, get): + params = {} + params['product_id'] = self.__product_id + params['status'] = '0' + data = self.send_cloud_pro('get_voucher', params) + return data + + def get_order_status(self, get): + params = {} + data = self.send_cloud_pro('get_order_status', params) + return data + + def get_product_discount_by(self, get): + params = {} + data = self.send_cloud_pro('get_product_discount_by', params) + return data + + def get_re_order_status(self, get): + params = {} + data = self.send_cloud_pro('get_re_order_status', params) + return data + + def create_order_voucher(self, get): + code = getattr(get, 'code', '1') + params = {} + params['code'] = code + data = self.send_cloud_pro('create_order_voucher', params) + public.return_message(0, 0, data) + + def create_order(self, get): + cycle = getattr(get, 'cycle', '1') + params = {} + params['cycle'] = cycle + params['cycle'] = 'month' + params['product_id'] = 100000012 + params['src'] = 2 + params['pay_channel'] = 2 + params['charge_type'] = 1 + params['environment_info'] = json.dumps(public.fetch_env_info()) + data = self.send_cloud_pro('create', params) + return data + + def get_cpuname(self): + return public.ExecShell("cat /proc/cpuinfo|grep 'model name'|cut -d : -f2")[0].strip() + + def get_product_auth(self, get): + # 校验参数 + try: + get.validate([ + Param('pid').Integer(), + Param('page').Integer(), + Param('pageSize').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + params = {} + params['page'] = get.page if 'page' in get else 1 + params['pageSize'] = get.pageSize if 'pageSize' in get else 15 + data = self.send_cloud('{}/api/user/productAuthorizes'.format(self.__official_url), params) + if not data: + return public.return_message(0, 0,[]) + if 'success' not in data['message']: return public.return_message(0, 0,[]) + data = data['message']['res'] + # return [i for i in data['list'] if i['status'] != 'activated' and get.pid == i['product_id']] + res = list() + for i in data['list']: + if i['status'] != 'activated' and str(get.pid) == str(i['product_id']): + res.append(i) + return public.return_message(0, 0,res) + + def auth_activate(self, get): + params = {} + params['serial_no'] = get.serial_no + params['environment_info'] = json.dumps(public.fetch_env_info()) + data = self.send_cloud('{}/api/authorize/product/activate'.format(self.__official_url), params) + if 'success' not in data['message'] or not data['message']['success']: + return public.return_message(-1, 0,'Activate Failed') + session['focre_cloud'] = True + # 刷新授权状态 + public.load_soft_list() + public.refresh_pd() + return public.return_message(0, 0,'Activate successfully') + + def renew_product_auth(self, get): + params = {} + params['serial_no'] = get.serial_no + params['pay_channel'] = get.pay_channel + params['cycle'] = get.cycle + params['cycle_unit'] = get.cycle_unit + params['src'] = 2 + params['trigger_entry'] = get.source + params['environment_info'] = json.dumps(public.fetch_env_info()) + if hasattr(get, 'coupon_id') and get.pay_channel == '10': + params['coupon_id'] = get.coupon_id + data = self.send_cloud('{}/api/authorize/product/renew'.format(self.__official_url), params) + + if not data['message']['success']: + data['message']['res'] = 'Invalid authorize OR authorize not found!' + return public.return_message(-1, 0, data['message']['res']) + session['focre_cloud'] = True + # 使用抵扣券续费直接返回续费结果 + if get.pay_channel == '10': + if not data['message']['success']: + return public.return_message(-1, 0, 'Renew Failed') + # 刷新授权状态 + public.load_soft_list() + public.refresh_pd() + return public.return_message(0, 0, 'Renew successfully') + # 使用支付续费返回stripe的请求数据 + return public.return_message(0, 0,data['message']['res']) + + def free_trial(self, get): + """ + 每个账号有一次免费试用专业版15天的机会 + :return: + """ + params = {} + params['environment_info'] = json.dumps(public.fetch_env_info()) + data = self.send_cloud('{}/api/product/obtainProfessionalMemberFree'.format(self.__official_url), params) + session['focre_cloud'] = True + # 使用抵扣券续费直接返回续费结果 + if not data['message']['success']: + return public.return_message(-1, 0, 'Apply Failed') + return public.return_message(0, 0, 'Apply successfully') + + # 获取专业版特权信息 或插件信息? + def get_plugin_remarks(self, get): + # 校验参数 + try: + get.validate([ + Param('product_id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + if not hasattr(get, 'product_id'): + return public.return_message(-1, 0, 'product_id Parameter ERROR!') + product_id = get.product_id + + # ikey = 'plugin_remarks' + product_id + # if ikey in session: + # return session.get(ikey) + try: + url = '{}/api/panel/get_advantages/{}'.format(self.__official_url, product_id) + data = requests.get(url).json() + except: + return public.return_message(-1, 0, 'Failed to connect to the server!') + if not data: return public.return_message(-1, 0, 'Failed to connect to the server!') + # session[ikey] = data + return public.return_message(0,0,data) + + def res_request_error(self): + return public.return_message(-1,0, 'Interface request failed ({})!'.format(self.__request_url)) + + + def get_apply_copon(self, get): + """ + 领取优惠券 + @get.coupon 优惠券 + """ + # 校验参数 + try: + get.validate([ + Param('obtain_id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + params = {} + params['obtain_id']= get.obtain_id + cloudUrl=self.__official_url+'/api/user/obtain_coupons' + # data = self.send_cloud_v2('obtain_coupons', params) + data = self.send_cloud_v3(cloudUrl, params) + return data + + def get_ignore_time(self, get): + """ + 获取忽略时间 + @get.coupon 优惠券 + """ + try: + limit = int(public.readFile('data/ignore_coupon_time.pl')) + except: + limit = public.return_message(0,0,0) + + if limit == -100 or limit > time.time(): + return public.return_message(0,0,1) + return public.return_message(0,0,0) + + def get_coupon_list(self, get): + """ + 获取可用的优惠券 + @get.coupon 优惠券 + """ + params = {} + cloudUrl=self.__official_url+'/api/user/coupons' + data = self.send_cloud_get(cloudUrl, params) + return data + # if not data: + # if type(data) == list: + # return public.return_message(0,0,[]) + # return public.return_message(-1,0,self.res_request_error()) + # return public.return_message(0,0,data) + + def ignore_coupon_time(self, get): + """ + 获取优惠券 + @get.coupon 优惠券 + @get.limit_time 限制时间 永久 -100 + """ + if not hasattr(get, 'limit_time')or not get.limit_time: return public.return_message(-1,0, 'Missing parameter limit_time or parameter cannot be empty!') + limit_time = int(get.limit_time) + if limit_time < time.time() and limit_time > 0: + return public.return_message(-1,0, 'Time cannot be less than the current time!') + + public.writeFile('data/ignore_coupon_time.pl', str(limit_time)) + + msg = 'Ignoring success, coupon information will not be displayed in the future' + if limit_time > 0: msg = 'Ignoring success, coupon information will no longer be displayed to you before {}'.format(public.format_date(times=limit_time)) + return public.return_message(0,0, msg) + + def get_coupons(self, get): + """ + @name 获取可领取的优惠券列表 + @param uid 用户id + """ + # 用户是否忽略 + if self.get_ignore_time(get)['message']['result']: + return public.return_message(0,0,[]) + params = {} + cloudURL = self.__official_url+'/api/user/obtainable_coupons' + data = self.send_cloud_v2(cloudURL, params) + if not data: + return public.return_message(-1,0,None) + if data['status']==-1: + return data + return data + + + def get_all_coupons(self,get): + """ + @name 获取所有优惠券 + """ + #获取可领取的优惠券列表 + params = {} + cloudURL = self.__official_url+'/api/user/obtainable_coupons' + data = self.send_cloud_v2(cloudURL, params) + if not data['message']: + return public.return_message(-1,0,None) + if data['status']==0: + if data['message']['status'] ==1: + data['message']['interface_type']=1 + return data + else: + data['message']['interface_type']=2 + # 获取可用的优惠券列表 + params = {} + cloudUrl=self.__official_url+'/api/user/coupons' + tmp_data = self.send_cloud_get(cloudUrl, params) + data['message']['total']=0 + if isinstance(tmp_data['message'],list) and len(tmp_data['message'])>0: + data['message']['total']=len(tmp_data['message']) + data['message']['end_time']=tmp_data['message'][0]['end_time'] + return data + else: + # 接口请求失败,返回默认值 + return public.success_v2({ + 'status': 0, + 'obtain_id': 0, + 'end_time': 0, + 'type': 0, + 'coupons': [], + 'usable_coupon_num': 0, + }) + return data + + """ + @name 统一请求接口 + @param url 返回URL不是www.bt.cn,修改config/config.json的home字段 + """ + + def request_post(self, url, params): + params = {} + data = self.send_cloud_pro_2('obtainable_coupons', params) + return data diff --git a/class_v2/panel_dns_api_v2.py b/class_v2/panel_dns_api_v2.py new file mode 100644 index 00000000..06e28701 --- /dev/null +++ b/class_v2/panel_dns_api_v2.py @@ -0,0 +1,565 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 沐落 +# +------------------------------------------------------------------- +import public,os,sys,json,time,random +import requests +from OpenSSL import crypto +import sys, os +import time +import copy +import json +import base64 +import hashlib +import binascii +import urllib + +if sys.version_info[0] == 2: # python2 + import urlparse + from urlparse import urljoin + import urllib2 + import cryptography.hazmat + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.serialization +else: # python3 + from urllib.parse import urlparse + from urllib.parse import urljoin + import cryptography +import platform +import hmac +try: + import requests +except: + public.ExecShell('btpip install requests') + import requests +try: + import OpenSSL +except: + public.ExecShell('btpip install pyOpenSSL') + import OpenSSL +import random +import datetime +import logging +from hashlib import sha1 + +os.chdir("/www/server/panel") +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import public +caa_value = '0 issue "letsencrypt.org"' + + +def extract_zone(domain_name): + domain_name = domain_name.lstrip("*.") + top_domain_list = ['.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn', + '.gov.cn', '.gs.cn', '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn', + '.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', '.jl.cn', '.js.cn', '.jx.cn', + '.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn','.my.id'] + old_domain_name = domain_name + top_domain = "."+".".join(domain_name.rsplit('.')[-2:]) + new_top_domain = "." + top_domain.replace(".","") + is_tow_top = False + if top_domain in top_domain_list: + is_tow_top = True + domain_name = domain_name[:-len(top_domain)] + new_top_domain + + if domain_name.count(".") > 1: + zone, middle, last = domain_name.rsplit(".", 2) + acme_txt = "_acme-challenge.%s" % zone + if is_tow_top: last = top_domain[1:] + root = ".".join([middle, last]) + else: + zone = "" + root = old_domain_name + acme_txt = "_acme-challenge" + return root, zone, acme_txt + +class BaseDns(object): + def __init__(self): + self.dns_provider_name = self.__class__.__name__ + + def log_response(self, response): + try: + log_body = response.json() + except ValueError: + log_body = response.content + return log_body + + def create_dns_record(self, domain_name, domain_dns_value): + raise NotImplementedError("create_dns_record method must be implemented.") + + def delete_dns_record(self, domain_name, domain_dns_value): + raise NotImplementedError("delete_dns_record method must be implemented.") + +class DNSPodDns(BaseDns): + dns_provider_name = "dnspod" + _type = 0 # 0:lest 1:锐成 + def __init__(self, DNSPOD_ID, DNSPOD_API_KEY, DNSPOD_API_BASE_URL="https://dnsapi.cn/"): + self.DNSPOD_ID = DNSPOD_ID + self.DNSPOD_API_KEY = DNSPOD_API_KEY + self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + self.HTTP_TIMEOUT = 65 # seconds + self.DNSPOD_LOGIN = "{0},{1}".format(self.DNSPOD_ID, self.DNSPOD_API_KEY) + + if DNSPOD_API_BASE_URL[-1] != "/": + self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + "/" + else: + self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + super(DNSPodDns, self).__init__() + + def create_dns_record(self, domain_name, domain_dns_value): + domain_name,_,subd = extract_zone(domain_name) + if self._type == 1: + self.add_record(domain_name,subd.replace('_acme-challenge.',''),domain_dns_value,'CNAME') + else: + self.add_record(domain_name,subd,domain_dns_value,'TXT') + + + + def add_record(self,domain_name,subd,domain_dns_value,s_type): + url = urljoin(self.DNSPOD_API_BASE_URL, "Record.Create") + body = { + "record_type": s_type, + "domain": domain_name, + "sub_domain": subd, + "value": domain_dns_value, + "record_line_id": "0", + "format": "json", + "login_token": self.DNSPOD_LOGIN, + } + create_dnspod_dns_record_response = requests.post( + url, data=body, timeout=self.HTTP_TIMEOUT + ).json() + if create_dnspod_dns_record_response["status"]["code"] != "1": + raise ValueError( + "Error creating dnspod dns record: status_code={status_code} response={response}".format( + status_code=create_dnspod_dns_record_response["status"]["code"], + response=create_dnspod_dns_record_response["status"]["message"], + ) + ) + + + def remove_record(self,domain_name,subd,s_type): + url = urljoin(self.DNSPOD_API_BASE_URL, "Record.List") + rootdomain = domain_name + body = { + "login_token": self.DNSPOD_LOGIN, + "format": "json", + "domain": rootdomain, + "subdomain": subd, + "record_type": s_type, + } + list_dns_response = requests.post(url, data=body, timeout=self.HTTP_TIMEOUT).json() + for i in range(0, len(list_dns_response["records"])): + if list_dns_response["records"][i]['name'] != subd: + continue + rid = list_dns_response["records"][i]["id"] + urlr = urljoin(self.DNSPOD_API_BASE_URL, "Record.Remove") + bodyr = { + "login_token": self.DNSPOD_LOGIN, + "format": "json", + "domain": rootdomain, + "record_id": rid, + } + requests.post( + urlr, data=bodyr, timeout=self.HTTP_TIMEOUT + ).json() + + def delete_dns_record(self, domain_name, domain_dns_value): + try: + domain_name,_,subd = extract_zone(domain_name) + self.remove_record(domain_name,subd,'TXT') + self.remove_record(domain_name,'_acme-challenge','CNAME') + except: + pass + + + +class CloudFlareDns(BaseDns): + dns_provider_name = "cloudflare" + _type = 0 # 0:lest 1:锐成 + def __init__( + self, + CLOUDFLARE_EMAIL, + CLOUDFLARE_API_KEY, + CLOUDFLARE_API_BASE_URL="https://api.cloudflare.com/client/v4/", + ): + self.CLOUDFLARE_DNS_ZONE_ID = None + self.CLOUDFLARE_EMAIL = CLOUDFLARE_EMAIL + self.CLOUDFLARE_API_KEY = CLOUDFLARE_API_KEY + self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + self.HTTP_TIMEOUT = 65 # seconds + + try: + import urllib.parse as urlparse + except: + import urlparse + + if CLOUDFLARE_API_BASE_URL[-1] != "/": + self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + "/" + else: + self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + super(CloudFlareDns, self).__init__() + + def get_headers(self): + if os.path.exists('/www/server/panel/data/cf_limit_api.pl'): + headers = {"Authorization": "Bearer "+self.CLOUDFLARE_API_KEY} + else: + headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY} + return headers + + def find_dns_zone(self, domain_name): + url = urljoin(self.CLOUDFLARE_API_BASE_URL, "zones?status=active&name={0}".format(domain_name)) + headers = self.get_headers() + find_dns_zone_response = requests.get(url, headers=headers, timeout=self.HTTP_TIMEOUT) + if find_dns_zone_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=find_dns_zone_response.status_code, + response=self.log_response(find_dns_zone_response), + ) + ) + + result = find_dns_zone_response.json()["result"] + for i in result: + if i["name"] in domain_name: + setattr(self, "CLOUDFLARE_DNS_ZONE_ID", i["id"]) + if isinstance(self.CLOUDFLARE_DNS_ZONE_ID, type(None)): + raise ValueError( + "Error unable to get DNS zone for domain_name={domain_name}: status_code={status_code} response={response}".format( + domain_name=domain_name, + status_code=find_dns_zone_response.status_code, + response=self.log_response(find_dns_zone_response), + ) + ) + + def add_record(self,domain_name,value,s_type): + url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records".format(self.CLOUDFLARE_DNS_ZONE_ID), + ) + # if '_' in self.CLOUDFLARE_API_KEY or '-' in self.CLOUDFLARE_API_KEY: + # headers = {"Authorization": "Bearer "+self.CLOUDFLARE_API_KEY} + # else: + # headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY} + headers = self.get_headers() + body = { + "type": s_type, + "name": domain_name, + "content": "{0}".format(value), + } + + create_cloudflare_dns_record_response = requests.post( + url, headers=headers, json=body, timeout=self.HTTP_TIMEOUT + ) + if create_cloudflare_dns_record_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=create_cloudflare_dns_record_response.status_code, + response=self.log_response(create_cloudflare_dns_record_response), + ) + ) + + def create_dns_record(self, domain_name, domain_dns_value): + domain_name = domain_name.lstrip("*.") + self.find_dns_zone(domain_name) + + url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records".format(self.CLOUDFLARE_DNS_ZONE_ID), + ) + # if '_' in self.CLOUDFLARE_API_KEY or '-' in self.CLOUDFLARE_API_KEY: + # headers = {"Authorization": "Bearer "+self.CLOUDFLARE_API_KEY} + # else: + # headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY} + headers = self.get_headers() + body = { + "type": "TXT", + "name": "_acme-challenge" + "." + domain_name + ".", + "content": "{0}".format(domain_dns_value), + } + + if self._type == 1: + body['type'] = 'CNAME' + root, _, acme_txt = extract_zone(domain_name) + body['name'] = acme_txt.replace('_acme-challenge.','') + + create_cloudflare_dns_record_response = requests.post( + url, headers=headers, json=body, timeout=self.HTTP_TIMEOUT + ) + if create_cloudflare_dns_record_response.status_code != 200: + # raise error so that we do not continue to make calls to ACME + # server + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=create_cloudflare_dns_record_response.status_code, + response=self.log_response(create_cloudflare_dns_record_response), + ) + ) + + + def remove_record(self,domain_name,dns_name,s_type): + # if '_' in self.CLOUDFLARE_API_KEY or '-' in self.CLOUDFLARE_API_KEY: + # headers = {"Authorization": "Bearer "+self.CLOUDFLARE_API_KEY} + # else: + # headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY} + headers = self.get_headers() + list_dns_payload = {"type": s_type, "name": dns_name} + list_dns_url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records".format(self.CLOUDFLARE_DNS_ZONE_ID), + ) + + list_dns_response = requests.get( + list_dns_url, params=list_dns_payload, headers=headers, timeout=self.HTTP_TIMEOUT + ) + + for i in range(0, len(list_dns_response.json()["result"])): + dns_record_id = list_dns_response.json()["result"][i]["id"] + url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records/{1}".format(self.CLOUDFLARE_DNS_ZONE_ID, dns_record_id), + ) + headers = {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY} + requests.delete( + url, headers=headers, timeout=self.HTTP_TIMEOUT + ) + + def delete_dns_record(self, domain_name, domain_dns_value): + domain_name = domain_name.lstrip("*.") + dns_name = "_acme-challenge" + "." + domain_name + self.remove_record(domain_name,dns_name,'TXT') + + + +class AliyunDns(object): + _type = 0 # 0:lest 1:锐成 + def __init__(self, key, secret, ): + self.key = str(key).strip() + self.secret = str(secret).strip() + self.url = "http://alidns.aliyuncs.com" + + def sign(self, accessKeySecret, parameters): # '''签名方法 + def percent_encode(encodeStr): + encodeStr = str(encodeStr) + if sys.version_info[0] == 3: + import urllib.request + res = urllib.request.quote(encodeStr, '') + else: + res = urllib2.quote(encodeStr, '') + res = res.replace('+', '%20') + res = res.replace('*', '%2A') + res = res.replace('%7E', '~') + return res + + sortedParameters = sorted(parameters.items(), key=lambda parameters: parameters[0]) + canonicalizedQueryString = '' + for (k, v) in sortedParameters: + canonicalizedQueryString += '&' + percent_encode(k) + '=' + percent_encode(v) + stringToSign = 'GET&%2F&' + percent_encode(canonicalizedQueryString[1:]) + if sys.version_info[0] == 2: + h = hmac.new(accessKeySecret + "&", stringToSign, sha1) + else: + h = hmac.new(bytes(accessKeySecret + "&", encoding="utf8"), stringToSign.encode('utf8'), sha1) + signature = base64.encodestring(h.digest()).strip() + return signature + + + def create_dns_record(self, domain_name, domain_dns_value): + root, _, acme_txt = extract_zone(domain_name) + self.delete_dns_record(domain_name, domain_dns_value) + if self._type == 1: + acme_txt = acme_txt.replace('_acme-challenge.','') + self.add_record(root,'CNAME',acme_txt,domain_dns_value) + else: + try: + self.add_record(root,'CAA','@',caa_value) + except: pass + self.add_record(root,'TXT',acme_txt,domain_dns_value) + + def add_record(self,domain,s_type,host,value): + randomint = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + paramsdata = { + "Action": "AddDomainRecord", "Format": "json", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1", "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", "SignatureNonce": str(randomint), "AccessKeyId": self.key, + "DomainName": domain, + "RR": host, + "Type": s_type, + "Value": value, + } + + Signature = self.sign(self.secret, paramsdata) + paramsdata['Signature'] = Signature + req = requests.get(url=self.url, params=paramsdata) + if req.status_code != 200: + if req.json()['Code'] == 'IncorrectDomainUser' or req.json()['Code'] == 'InvalidDomainName.NoExist': + raise ValueError("This domain name does not exist under this Ali cloud account. Adding parsing failed.") + elif req.json()['Code'] == 'InvalidAccessKeyId.NotFound' or req.json()['Code'] == 'SignatureDoesNotMatch': + raise ValueError("API key error, add parsing failed") + else: + raise ValueError(req.json()['Message']) + + def query_recored_items(self, host, zone=None, tipe=None, page=1, psize=200): + randomint = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + paramsdata = { + "Action": "DescribeDomainRecords", "Format": "json", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1", "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", "SignatureNonce": str(randomint), "AccessKeyId": self.key, + "DomainName": host, + } + if zone: + paramsdata['RRKeyWord'] = zone + if tipe: + paramsdata['TypeKeyWord'] = tipe + Signature = self.sign(self.secret, paramsdata) + paramsdata['Signature'] = Signature + req = requests.get(url=self.url, params=paramsdata) + return req.json() + + def query_recored_id(self, root, zone, tipe="TXT"): + record_id = None + recoreds = self.query_recored_items(root, zone, tipe=tipe) + recored_list = recoreds.get("DomainRecords", {}).get("Record", []) + recored_item_list = [i for i in recored_list if i["RR"] == zone] + if len(recored_item_list): + record_id = recored_item_list[0]["RecordId"] + return record_id + + def remove_record(self,domain,host,s_type = 'TXT'): + record_id = self.query_recored_id(domain,host,s_type) + if not record_id: + msg = "Cannot find record_id for domain name: ", domain + print(msg) + return + randomint = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + paramsdata = { + "Action": "DeleteDomainRecord", "Format": "json", "Version": "2015-01-09", "SignatureMethod": "HMAC-SHA1", "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", "SignatureNonce": str(randomint), "AccessKeyId": self.key, + "RecordId": record_id, + } + Signature = self.sign(self.secret, paramsdata) + paramsdata['Signature'] = Signature + req = requests.get(url=self.url, params=paramsdata) + if req.status_code != 200: + raise ValueError("Deleting a parse record failed") + + def delete_dns_record(self, domain_name, domain_dns_value): + root, _, acme_txt = extract_zone(domain_name) + self.remove_record(root,acme_txt,'TXT') + self.remove_record(root,'@','CAA') + self.remove_record(root,'_acme-challenge','CNAME') + +class CloudxnsDns(object): + def __init__(self, key, secret, ): + self.key = key + self.secret = secret + self.APIREQUESTDATE = time.ctime() + + def get_headers(self, url, parameter=''): + APIREQUESTDATE = self.APIREQUESTDATE + APIHMAC = public.Md5(self.key + url + parameter + APIREQUESTDATE + self.secret) + headers = { + "API-KEY": self.key, + "API-REQUEST-DATE": APIREQUESTDATE, + "API-HMAC": APIHMAC, + "API-FORMAT": "json" + } + return headers + + def get_domain_list(self): + url = "https://www.cloudxns.net/api2/domain" + headers = self.get_headers(url) + req = requests.get(url=url, headers=headers,verify=False) + req = req.json() + + return req + + def get_domain_id(self, domain_name): + req = self.get_domain_list() + for i in req["data"]: + if domain_name.strip() == i['domain'][:-1]: + return i['id'] + return False + + def create_dns_record(self, domain_name, domain_dns_value): + root, _, acme_txt = extract_zone(domain_name) + domain = self.get_domain_id(root) + if not domain: + raise ValueError('The domain name does not exist under this cloudxns user, adding parsing failed.') + + url = "https://www.cloudxns.net/api2/record" + data = { + "domain_id": int(domain), + "host": acme_txt, + "value": domain_dns_value, + "type": "TXT", + "line_id": 1, + } + parameter = json.dumps(data) + headers = self.get_headers(url, parameter) + req = requests.post(url=url, headers=headers, data=parameter,verify=False) + req = req.json() + + return req + + def delete_dns_record(self, domain_name, domain_dns_value): + root, _, acme_txt = extract_zone(domain_name) + print("delete_dns_record start: ", acme_txt, domain_dns_value) + url = "https://www.cloudxns.net/api2/record/{}/{}".format(self.get_record_id(root,'TXT'), self.get_domain_id(root)) + headers = self.get_headers(url, ) + req = requests.delete(url=url, headers=headers, verify=False) + req = req.json() + return req + + def get_record_id(self, domain_name,s_type = 'TXT'): + url = "http://www.cloudxns.net/api2/record/{}?host_id=0&offset=0&row_num=2000".format(self.get_domain_id(domain_name)) + headers = self.get_headers(url, ) + req = requests.get(url=url, headers=headers,verify=False) + req = req.json() + for i in req['data']: + if i['type'] == s_type: + return i['record_id'] + return False + +class Dns_com(object): + _type = 0 # 0:lest 1:锐成 + def __init__(self, key, secret, ): + pass + + def get_dns_obj(self): + p_path = '/www/server/panel/plugin/dns' + if not os.path.exists(p_path +'/dns_main.py'): return None + sys.path.insert(0,p_path) + import dns_main + public.mod_reload(dns_main) + return dns_main.dns_main() + + def create_dns_record(self, domain_name, domain_dns_value): + root, _, acme_txt = extract_zone(domain_name) + + if self._type == 1: + acme_txt = acme_txt.replace('_acme-challenge.','') + result = self.add_record(acme_txt + '.' + root,domain_dns_value) + else: + result = self.get_dns_obj().add_txt(acme_txt + '.' + root,domain_dns_value) + + if result == "False": + raise ValueError('[DNS] This domain name does not exist in the currently bound Pagoda DNS cloud resolution account. Adding parsing failed!') + time.sleep(5) + + def delete_dns_record(self, domain_name, domain_dns_value): + root, _, acme_txt = extract_zone(domain_name) + self.get_dns_obj().remove_txt(acme_txt + '.' + root) + + + + diff --git a/class_v2/panel_http_proxy_v2.py b/class_v2/panel_http_proxy_v2.py new file mode 100644 index 00000000..2e8f8ec5 --- /dev/null +++ b/class_v2/panel_http_proxy_v2.py @@ -0,0 +1,277 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +#------------------------------ +# HTTP代理模块 +#------------------------------ + +import requests,os,re,time +from BTPanel import request,Response,public,app,get_phpmyadmin_dir,session +from http.cookies import SimpleCookie +import requests.packages.urllib3.util.connection as urllib3_conn +import socket + + +class HttpProxy: + _pma_path = None + def get_res_headers(self,p_res): + ''' + @name 获取响应头 + @author hwliang<2022-01-19> + @param p_res requests响应对像 + @return dict + ''' + headers = {} + for h in p_res.headers.keys(): + if h in ['Content-Encoding','Transfer-Encoding']: continue + headers[h] = p_res.headers[h] + if h in ['Location']: + + if headers[h].find('phpmyadmin_') != -1: + if not self._pma_path: + self._pma_path = get_phpmyadmin_dir() + if self._pma_path: + self._pma_path = self._pma_path[0] + else: + self._pma_path = '' + headers[h] = headers[h].replace(self._pma_path,'phpmyadmin') + + if headers[h].find('127.0.0.1') != -1: + headers[h] = re.sub(r"https?://127.0.0.1(:\d+)?/",request.url_root,headers[h]) + if request.url_root.find('https://') == 0: + headers[h] = headers[h].replace('http://','https://') + return headers + + def set_res_headers(self,res,p_res): + ''' + @name 设置响应头 + @author hwliang<2022-01-19> + @param res flask响应对像 + @param p_res requests响应对像 + @return res + ''' + # from datetime import datetime + # cookie_dict = p_res.cookies.get_dict() + # expires = datetime.utcnow() + app.permanent_session_lifetime + # for k in cookie_dict.keys(): + # httponly = True + # if k in ['phpMyAdmin']: httponly = True + # res.set_cookie(k, cookie_dict[k], + # expires=expires, httponly=httponly, + # path='/') + + return res + + def get_pma_phpversion(self): + ''' + @name 获取phpmyadmin的php版本 + @author hwliang<2022-01-19> + @return str + ''' + from panelPlugin import panelPlugin + pma_status = panelPlugin().getPHPMyAdminStatus() + if 'phpversion' in pma_status: + return pma_status['phpversion'] + return None + + def get_pma_version(self): + ''' + @name 获取phpmyadmin的版本 + @author hwliang<2022-01-19> + @return str + ''' + pma_vfile = public.get_setup_path() + '/phpmyadmin/version.pl' + if not os.path.exists(pma_vfile): return '' + pma_version = public.readFile(pma_vfile).strip() + if not pma_version: return '' + return pma_version + + def set_pma_phpversion(self): + ''' + @name 设置phpmyadmin兼容的php版本 + @author hwliang<2022-01-19> + @return str + ''' + + pma_version = self.get_pma_version() + if not pma_version: return False + + old_phpversion = self.get_pma_phpversion() + if not old_phpversion: return False + if pma_version == '4.0': + php_versions = ['52','53','54'] + elif pma_version == '4.4': + php_versions = ['54','55','56'] + elif pma_version == '4.9': + php_versions = ['55','56','70','71','72','73','74'] + elif pma_version == '5.0': + php_versions = ['70','71','72','73','74'] + elif pma_version == '5.1': + php_versions = ['71','72','73','74','80'] + elif pma_version == '5.2': + php_versions = ['72','73','74','80','81'] + elif pma_version == '5.3': + php_versions = ['72','73','74','80','81'] + else: + return False + + if old_phpversion in php_versions: return True + + installed_php_versions = [] + php_install_path = '/www/server/php' + for version in php_versions: + php_bin = php_install_path + '/' + version + '/bin/php' + if os.path.exists(php_bin): + installed_php_versions.append(version) + + if not installed_php_versions: return False + + php_version = installed_php_versions[-1] + + import ajax + args = public.dict_obj() + args.phpversion = php_version + ajax.ajax().setPHPMyAdmin(args) + public.WriteLog('数据库','检测到phpMyAdmin使用的PHP版本不兼容,已自动修改为最佳兼容版本: PHP-' + php_version) + time.sleep(0.5) + + + def get_request_headers(self): + ''' + @name 获取请求头 + @author hwliang<2022-01-19> + @return dict + ''' + headers = {} + rm_cookies = [app.config['SESSION_COOKIE_NAME'],'bt_user_info','file_recycle_status','ltd_end', + 'memSize','page_number','pro_end','request_token','serverType','site_model', + 'sites_path','soft_remarks','load_page','Path','distribution','order'] + for k in request.headers.keys(): + headers[k] = request.headers.get(k) + if k == 'Cookie': + cookie_dict = SimpleCookie(headers[k]) + for rm_cookie in rm_cookies: + if rm_cookie in cookie_dict: + del(cookie_dict[rm_cookie]) + headers[k] = cookie_dict.output(header='',sep=';').strip() + return headers + + def form_to_dict(self,form): + ''' + @name 将表单转为字典 + @author hwliang<2022-02-18> + @param form 表单数据 + @return dict + ''' + + data = {} + for k in form.keys(): + data[k] = form.getlist(k) + if len(data[k]) == 1: data[k] = data[k][0] + return data + + def proxy(self,proxy_url): + ''' + @name 代理指定URL地址 + @author hwliang<2022-01-19> + @param proxy_url 被代理的URL地址 + @return Response + ''' + try: + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + s_key = 'proxy_{}_{}'.format(app.secret_key,self.get_pma_version()) + + if not s_key in session: + session[s_key] = requests.Session() + session[s_key].keep_alive = False + session[s_key].headers = { + 'User-Agent':'BT-Panel', + 'Connection':'close' + } + + if proxy_url.find('phpmyadmin') != -1: + if proxy_url.find('https://') == 0: + session[s_key].cookies.update({'pma_lang_https':'zh_CN'}) + else: + session[s_key].cookies.update({'pma_lang':'zh_CN'}) + self.set_pma_phpversion() + + if 'Authorization' in request.headers: + session[s_key].headers['Authorization'] = request.headers['Authorization'] + + try: + session[s_key].headers['Host'] = public.en_punycode(request.url_root).replace('http://','').replace('https://','').split('/')[0] + except:pass + # headers = self.get_request_headers() + headers = None + if request.method == 'GET': + # 转发GET请求 + p_res = session[s_key].get(proxy_url,headers=headers,verify=False,allow_redirects=False) + elif request.method == 'POST': + # 转发POST请求 + if request.files: # 如果上传文件 + tmp_path = '{}/tmp'.format(public.get_panel_path()) + if not os.path.exists(tmp_path): os.makedirs(tmp_path,384) + + # 处理请求头 + if headers: + if 'Content-Type' in headers: del(headers['Content-Type']) + if 'Content-Length' in headers: del(headers['Content-Length']) + + # 遍历form表单中的所有文件 + files = {} + f_list = {} + for key in request.files: + upload_files = request.files.getlist(key) + filename = upload_files[0].filename + if not filename: filename = public.GetRandomString(12) + tmp_file = '{}/{}'.format(tmp_path,filename) + + + # 保存上传文件到临时目录 + with open(tmp_file,'wb') as f: + for tmp_f in upload_files: + f.write(tmp_f.read()) + f.close() + + # 构造文件上传对象 + f_list[key] = open(tmp_file,'rb') + files[key] = (filename, f_list[key]) + + # 删除临时文件 + if os.path.exists(tmp_file): os.remove(tmp_file) + + # 转发上传请求 + + p_res = session[s_key].post(proxy_url,self.form_to_dict(request.form),headers=headers,files=files,verify=False,allow_redirects=False) + + # 释放文件对象 + for fkey in f_list.keys(): + f_list[fkey].close() + else: + p_res = session[s_key].post(proxy_url,self.form_to_dict(request.form),headers=headers,verify=False,allow_redirects=False) + else: + return Response('不支持的请求类型',500) + + # PHP版本自动切换处理 + if proxy_url.find('phpmyadmin') != -1 and proxy_url.find('/index.php') != -1: + if len(p_res.content) < 1024: + if p_res.content.find(b'syntax error, unexpected') != -1 or p_res.content.find(b'offset access syntax with') != -1 or p_res.content.find(b'+ is required') != -1: + self.set_pma_phpversion() + return 'Incompatible PHP version, an attempt has been made to automatically switch to a compatible PHP version, please refresh the page and try again!' + elif p_res.content.find(b'Deprecation Notice') != -1 and not session.get('set_pma_phpversion'): + self.set_pma_phpversion() + session['set_pma_phpversion'] = True + return 'Incompatible PHP version, an attempt has been made to automatically switch to a compatible PHP version, please refresh the page and try again!' + + res = Response(p_res.content,headers=self.get_res_headers(p_res),content_type=p_res.headers.get('content-type',None),status=p_res.status_code) + res = self.set_res_headers(res,p_res) + return res + except Exception as ex: + return Response(str(ex),500) diff --git a/class_v2/panel_lets_v2.py b/class_v2/panel_lets_v2.py new file mode 100644 index 00000000..61abfa4f --- /dev/null +++ b/class_v2/panel_lets_v2.py @@ -0,0 +1,796 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 沐落 +# +------------------------------------------------------------------- +import os,sys,json,time,re +setup_path = '/www/server/panel' +os.chdir(setup_path) +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import http_requests as requests +import sewer,public +from OpenSSL import crypto +try: + requests.packages.urllib3.disable_warnings() +except:pass +if __name__ != '__main__': + import BTPanel +try: + import dns.resolver +except: + public.ExecShell("pip install dnspython") + try: + import dns.resolver + except: + pass + +class panelLets: + let_url = "https://acme-v02.api.letsencrypt.org/directory" + #let_url = "https://acme-staging-v02.api.letsencrypt.org/directory" + + setupPath = None #安装路径 + server_type = None + log_file = '/www/server/panel/logs/letsencrypt.log' + + #构造方法 + def __init__(self): + self.setupPath = public.GetConfigValue('setup_path') + self.server_type = public.get_webserver() + + def write_log(self,log_str): + + f = open(self.log_file,'ab+') + log_str += "\n" + f.write(log_str.encode('utf-8')) + f.close() + return True + + #拆分根证书 + def split_ca_data(self,cert): + datas = cert.split('-----END CERTIFICATE-----') + return {"cert":datas[0] + "-----END CERTIFICATE-----\n","ca_data":datas[1] + '-----END CERTIFICATE-----\n' } + + #证书转为pkcs12 + def dump_pkcs12(self,key_pem=None,cert_pem = None, ca_pem=None, friendly_name=None): + p12 = crypto.PKCS12() + if cert_pem: + ret = p12.set_certificate(crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem.encode())) + assert ret is None + if key_pem: + ret = p12.set_privatekey(crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem.encode())) + assert ret is None + if ca_pem: + ret = p12.set_ca_certificates((crypto.load_certificate(crypto.FILETYPE_PEM, ca_pem.encode()),) ) + if friendly_name: + ret = p12.set_friendlyname(friendly_name.encode()) + return p12 + + def extract_zone(self,domain_name): + domain_name = domain_name.lstrip("*.") + top_domain_list = ['.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn','.gov.cn', '.gs.cn', + '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn','.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', + '.jl.cn', '.js.cn', '.jx.cn','.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn', + '.my.id','.com.ac','.com.ad','.com.ae','.com.af','.com.ag','.com.ai','.com.al','.com.am', + '.com.an','.com.ao','.com.aq','.com.ar','.com.as','.com.as','.com.at','.com.au','.com.aw', + '.com.az','.com.ba','.com.bb','.com.bd','.com.be','.com.bf','.com.bg','.com.bh','.com.bi', + '.com.bj','.com.bm','.com.bn','.com.bo','.com.br','.com.bs','.com.bt','.com.bv','.com.bw', + '.com.by','.com.bz','.com.ca','.com.ca','.com.cc','.com.cd','.com.cf','.com.cg','.com.ch', + '.com.ci','.com.ck','.com.cl','.com.cm','.com.cn','.com.co','.com.cq','.com.cr','.com.cu', + '.com.cv','.com.cx','.com.cy','.com.cz','.com.de','.com.dj','.com.dk','.com.dm','.com.do', + '.com.dz','.com.ec','.com.ee','.com.eg','.com.eh','.com.es','.com.et','.com.eu','.com.ev', + '.com.fi','.com.fj','.com.fk','.com.fm','.com.fo','.com.fr','.com.ga','.com.gb','.com.gd', + '.com.ge','.com.gf','.com.gh','.com.gi','.com.gl','.com.gm','.com.gn','.com.gp','.com.gr', + '.com.gt','.com.gu','.com.gw','.com.gy','.com.hm','.com.hn','.com.hr','.com.ht','.com.hu', + '.com.id','.com.id','.com.ie','.com.il','.com.il','.com.in','.com.io','.com.iq','.com.ir', + '.com.is','.com.it','.com.jm','.com.jo','.com.jp','.com.ke','.com.kg','.com.kh','.com.ki', + '.com.km','.com.kn','.com.kp','.com.kr','.com.kw','.com.ky','.com.kz','.com.la','.com.lb', + '.com.lc','.com.li','.com.lk','.com.lr','.com.ls','.com.lt','.com.lu','.com.lv','.com.ly', + '.com.ma','.com.mc','.com.md','.com.me','.com.mg','.com.mh','.com.ml','.com.mm','.com.mn', + '.com.mo','.com.mp','.com.mq','.com.mr','.com.ms','.com.mt','.com.mv','.com.mw','.com.mx', + '.com.my','.com.mz','.com.na','.com.nc','.com.ne','.com.nf','.com.ng','.com.ni','.com.nl', + '.com.no','.com.np','.com.nr','.com.nr','.com.nt','.com.nu','.com.nz','.com.om','.com.pa', + '.com.pe','.com.pf','.com.pg','.com.ph','.com.pk','.com.pl','.com.pm','.com.pn','.com.pr', + '.com.pt','.com.pw','.com.py','.com.qa','.com.re','.com.ro','.com.rs','.com.ru','.com.rw', + '.com.sa','.com.sb','.com.sc','.com.sd','.com.se','.com.sg','.com.sh','.com.si','.com.sj', + '.com.sk','.com.sl','.com.sm','.com.sn','.com.so','.com.sr','.com.st','.com.su','.com.sy', + '.com.sz','.com.tc','.com.td','.com.tf','.com.tg','.com.th','.com.tj','.com.tk','.com.tl', + '.com.tm','.com.tn','.com.to','.com.tp','.com.tr','.com.tt','.com.tv','.com.tw','.com.tz', + '.com.ua','.com.ug','.com.uk','.com.uk','.com.us','.com.uy','.com.uz','.com.va','.com.vc', + '.com.ve','.com.vg','.com.vn','.com.vu','.com.wf','.com.ws','.com.ye','.com.za','.com.zm', + '.com.zw'] + old_domain_name = domain_name + m_count = domain_name.count(".") + top_domain = "."+".".join(domain_name.rsplit('.')[-2:]) + new_top_domain = "." + top_domain.replace(".","") + is_tow_top = False + if top_domain in top_domain_list: + is_tow_top = True + domain_name = domain_name[:-len(top_domain)] + new_top_domain + + if domain_name.count(".") > 1: + zone, middle, last = domain_name.rsplit(".", 2) + acme_txt = "_acme-challenge.%s" % zone + if is_tow_top: last = top_domain[1:] + root = ".".join([middle, last]) + else: + zone = "" + root = old_domain_name + acme_txt = "_acme-challenge" + return root, zone, acme_txt + + #获取根域名 + def get_root_domain(self,domain_name): + d_root,tow_name,acme_txt = self.extract_zone(domain_name) + return d_root + + #获取acmename + def get_acme_name(self,domain_name): + d_root,tow_name,acme_txt = self.extract_zone(domain_name) + return acme_txt + '.' + d_root + + #格式化错误输出 + def get_error(self,error): + if error.find("Max checks allowed") >= 0 : + return public.get_msg_gettext("CA can't verify your domain name, please check if the domain name resolution is correct, or wait 5-10 minutes and try again.") + elif error.find("Max retries exceeded with") >= 0 or error.find('status_code=0 ') != -1: + return public.get_msg_gettext("The CA server connection timed out, please try again later.") + elif error.find("The domain name belongs") >= 0: + return public.get_msg_gettext("The domain name does not belong to this DNS service provider. Please ensure that the domain name is filled in correctly.") + elif error.find('login token ID is invalid') >=0: + return public.get_msg_gettext('The DNS server connection failed. Please check if the key is correct.') + elif "too many certificates already issued for exact set of domains" in error: + return public.get_msg_gettext('The signing failed, the domain name exact set of domains: (.+): {} exceeded the weekly number of repeated issuances!',(error,)) + elif "Error creating new account :: too many registrations for this IP" in error: + return public.get_msg_gettext('The signing failed, the current server IP has reached the limit of creating up to 10 accounts every 3 hours..') + elif "DNS problem: NXDOMAIN looking up A for" in error: + return public.get_msg_gettext('The verification failed, the domain name was not resolved, or the resolution did not take effect.!') + elif "Invalid response from" in error: + return public.get_msg_gettext('Authentication failed, domain name resolution error or verification URL could not be accessed!') + elif error.find('TLS Web Server Authentication') != -1: + public.restart_panel() + return public.get_msg_gettext("Failed to connect to CA server, please try again later.") + elif error.find('Name does not end in a public suffix') != -1: + return public.get_msg_gettext("Unsupported domain name {}, please check if the domain name is correct!",(re.findall("Cannot issue for \"(.+)\":", error),)) + elif error.find('No valid IP addresses found for') != -1: + return public.get_msg_gettext("The domain name {} did not find a resolution record. Please check if the domain name is resolved.!",(re.findall("No valid IP addresses found for (.+)", error),)) + elif error.find('No TXT record found at') != -1: + return public.get_msg_gettext("If a valid TXT resolution record is not found in the domain name {}, please check if the TXT record is correctly parsed. If it is applied by DNSAPI, please try again in 10 minutes.!",(re.findall( + "No TXT record found at (.+)", error),)) + elif error.find('Incorrect TXT record') != -1: + return public.get_msg_gettext("Found the wrong TXT record on {}: {}, please check if the TXT resolution is correct. If it is applied by DNSAPI, please try again in 10 minutes.!",( + re.findall("found at (.+)", error), re.findall("Incorrect TXT record \"(.+)\"", error))) + elif error.find('Domain not under you or your user') != -1: + return public.get_msg_gettext("This domain name does not exist under this dnspod account. Adding parsing failed.!") + elif error.find('SERVFAIL looking up TXT for') != -1: + return public.get_msg_gettext("If a valid TXT resolution record is not found in the domain name {}, please check if the TXT record is correctly parsed. If it is applied by DNSAPI, please try again in 10 minutes.!",(re.findall( + "looking up TXT for (.+)", error),)) + elif error.find('Timeout during connect') != -1: + return public.get_msg_gettext("Connection timed out, CA server could not access your website!") + elif error.find("DNS problem: SERVFAIL looking up CAA for") != -1: + return public.get_msg_gettext("The domain name {} is currently required to verify the CAA record. Please manually resolve the CAA record, or try again after 1 hour.!" , (re.findall("looking up CAA for (.+)", error),)) + elif error.find("Read timed out.") != -1: + return public.get_msg_gettext("Verification timeout, please check whether the domain name is correctly resolved. If dns is resolved, the connection between the server and Let'sEncrypt may be abnormal. Please try again later!") + elif error.find("Error creating new order") != -1: + return public.get_msg_gettext("Order creation failed, please try again later!") + elif error.find("Too Many Requests") != -1: + return public.get_msg_gettext("More than 5 verification failures in 1 hour, application is temporarily banned, please try again later!") + elif error.find('HTTP Error 400: Bad Request') != -1: + return public.get_msg_gettext("CA server denied access, please try again later!") + else: + return error; + + #获取DNS服务器 + def get_dns_class(self,data): + if data['dnsapi'] == 'dns_ali': + import panelDnsapi + public.mod_reload(panelDnsapi) + dns_class = panelDnsapi.AliyunDns(key = data['dns_param'][0], secret = data['dns_param'][1]) + return dns_class + elif data['dnsapi'] == 'dns_dp': + dns_class = sewer.DNSPodDns(DNSPOD_ID = data['dns_param'][0] ,DNSPOD_API_KEY = data['dns_param'][1]) + return dns_class + elif data['dnsapi'] == 'dns_cx': + import panelDnsapi + public.mod_reload(panelDnsapi) + dns_class = panelDnsapi.CloudxnsDns(key = data['dns_param'][0] ,secret =data['dns_param'][1]) + result = dns_class.get_domain_list() + if result['code'] == 1: + return dns_class + elif data['dnsapi'] == 'dns_bt': + import panelDnsapi + public.mod_reload(panelDnsapi) + dns_class = panelDnsapi.Dns_com() + return dns_class + return False + + #续签证书 + def renew_lest_cert(self,data): + #续签网站 + path = self.setupPath + '/panel/vhost/cert/'+ data['siteName'] + if not os.path.exists(path): return public.return_msg_gettext(False, 'The renewal failed and the certificate directory does not exist.') + + account_path = path + "/account_key.key" + if not os.path.exists(account_path): return public.return_msg_gettext(False, 'Renewal failed, missing account_key.') + + #续签 + data['account_key'] = public.readFile(account_path) + + if not 'first_domain' in data: data['first_domain'] = data['domains'][0] + + if 'dnsapi' in data: + certificate = self.crate_let_by_dns(data) + else: + certificate = self.crate_let_by_file(data) + + if not certificate['status']: return public.return_msg_gettext(False, certificate['msg']) + + #存储证书 + public.writeFile(path + "/privkey.pem",certificate['key']) + public.writeFile(path + "/fullchain.pem",certificate['cert'] + certificate['ca_data']) + public.writeFile(path + "/account_key.key", certificate['account_key']) #续签KEY + + #转为IIS证书 + p12 = self.dump_pkcs12(certificate['key'], certificate['cert'] + certificate['ca_data'],certificate['ca_data'],data['first_domain']) + pfx_buffer = p12.export() + public.writeFile(path + "/fullchain.pfx",pfx_buffer,'wb+') + + return public.return_msg_gettext(True, '[ {} ] The certificate renewal was successful.',(data['siteName'],)) + + + + #申请证书 + def apple_lest_cert(self,get): + data = {} + data['siteName'] = get.siteName + data['domains'] = json.loads(get.domains) + data['email'] = get.email + data['dnssleep'] = get.dnssleep + self.write_log(public.get_msg_gettext('Ready to apply for SSL, domain name {}',(data['domains'],))) + self.write_log("="*50) + if len(data['domains']) <=0 : return public.return_msg_gettext(False, 'The list of applied domain names cannot be empty.') + + data['first_domain'] = data['domains'][0] + + path = self.setupPath + '/panel/vhost/cert/'+ data['siteName'] + if not os.path.exists(path): os.makedirs(path) + + # 检查是否自定义证书 + partnerOrderId = path + '/partnerOrderId' + if os.path.exists(partnerOrderId): os.remove(partnerOrderId) + #清理续签key + re_key = path + '/account_key.key' + if os.path.exists(re_key): os.remove(re_key) + + re_password = path + '/password' + if os.path.exists(re_password): os.remove(re_password) + + data['account_key'] = None + if hasattr(get, 'dnsapi'): + if not 'app_root' in get: get.app_root = '0' + data['app_root'] = get.app_root + domain_list = data['domains'] + if data['app_root'] == '1': + public.writeFile(self.log_file,''); + domain_list = [] + data['first_domain'] = self.get_root_domain(data['first_domain']) + for domain in data['domains']: + rootDoamin = self.get_root_domain(domain) + if not rootDoamin in domain_list: domain_list.append(rootDoamin) + if not "*." + rootDoamin in domain_list: domain_list.append("*." + rootDoamin) + data['domains'] = domain_list + if get.dnsapi == 'dns': + domain_path = path + '/domain_txt_dns_value.json' + if hasattr(get, 'renew'): #验证 + data['renew'] = True + dns = json.loads(public.readFile(domain_path)) + data['dns'] = dns + certificate = self.crate_let_by_oper(data) + else: + public.writeFile(self.log_file,''); + #手动解析提前返回 + result = self.crate_let_by_oper(data) + if 'status' in result and not result['status']: return result + result['status'] = True + public.writeFile(domain_path, json.dumps(result)) + result['msg'] = public.get_msg_gettext('Get successful, please manually resolve the domain name') + result['code'] = 2 + return result + elif get.dnsapi == 'dns_bt': + public.writeFile(self.log_file,'') + data['dnsapi'] = get.dnsapi + certificate = self.crate_let_by_dns(data) + else: + public.writeFile(self.log_file,'') + data['dnsapi'] = get.dnsapi + data['dns_param'] = get.dns_param.split('|') + certificate = self.crate_let_by_dns(data) + else: + #文件验证 + public.writeFile(self.log_file,'') + data['site_dir'] = get.site_dir + certificate = self.crate_let_by_file(data) + + if not certificate['status']: return public.return_msg_gettext(False, certificate['msg']) + + #保存续签 + self.write_log(public.get_msg_gettext('|-Saving certificate..')) + cpath = self.setupPath + '/panel/vhost/cert/crontab.json' + config = {} + if os.path.exists(cpath): + try: + config = json.loads(public.readFile(cpath)) + except:pass + + config[data['siteName']] = data + public.writeFile(cpath,json.dumps(config)) + public.set_mode(cpath,600) + + #存储证书 + public.writeFile(path + "/privkey.pem",certificate['key']) + public.writeFile(path + "/fullchain.pem",certificate['cert'] + certificate['ca_data']) + public.writeFile(path + "/account_key.key",certificate['account_key']) #续签KEY + + #转为IIS证书 + p12 = self.dump_pkcs12(certificate['key'], certificate['cert'] + certificate['ca_data'],certificate['ca_data'],data['first_domain']) + pfx_buffer = p12.export() + public.writeFile(path + "/fullchain.pfx",pfx_buffer,'wb+') + public.writeFile(path + "/README","let") + + #计划任务续签 + self.write_log(public.get_msg_gettext('|-Setting up auto-renewal configuration..')) + self.set_crond() + self.write_log(public.get_msg_gettext('|-The application is successful and it is being automatically deployed to the website!')) + self.write_log("="*50) + return public.return_msg_gettext(True, 'Application successful.') + + #创建计划任务 + def set_crond(self): + try: + echo = public.md5(public.md5('renew_lets_ssl_bt')) + cron_id = public.M('crontab').where('echo=?',(echo,)).getField('id') + + import crontab + args_obj = public.dict_obj() + if not cron_id: + cronPath = public.GetConfigValue('setup_path') + '/cron/' + echo + shell = public.get_python_bin() + ' %s/panel/class/panelLets.py renew_lets_ssl ' % (self.setupPath) + public.writeFile(cronPath,shell) + args_obj.id = public.M('crontab').add('name,type,where1,where_hour,where_minute,echo,addtime,status,save,backupTo,sType,sName,sBody,urladdress',("Renew the Letter's Encrypt certificate",'day','','0','10',echo,time.strftime('%Y-%m-%d %X',time.localtime()),0,'','localhost','toShell','',shell,'')) + crontab.crontab().set_cron_status(args_obj) + else: + cron_path = public.get_cron_path() + if os.path.exists(cron_path): + cron_s = public.readFile(cron_path) + if cron_s.find(echo) == -1: + public.M('crontab').where('echo=?',(echo,)).setField('status',0) + args_obj.id = cron_id + crontab.crontab().set_cron_status(args_obj) + except:pass + + #手动解析 + def crate_let_by_oper(self,data): + result = {} + result['status'] = False + try: + if not data['email']: data['email'] = public.M('users').getField('email') + + #手动解析记录值 + if not 'renew' in data: + self.write_log(public.get_msg_gettext('|-Initializing ACME protocol...')) + BTPanel.dns_client = sewer.Client(domain_name = data['first_domain'],dns_class = None,account_key = data['account_key'],domain_alt_names = data['domains'],contact_email = str(data['email']) ,ACME_AUTH_STATUS_WAIT_PERIOD = 15,ACME_AUTH_STATUS_MAX_CHECKS = 5,ACME_REQUEST_TIMEOUT = 20,ACME_DIRECTORY_URL = self.let_url) + domain_dns_value = "placeholder" + dns_names_to_delete = [] + self.write_log(public.get_msg_gettext('|-Registering account...')) + BTPanel.dns_client.acme_register() + authorizations, finalize_url = BTPanel.dns_client.apply_for_cert_issuance() + responders = [] + self.write_log(public.get_msg_gettext('|-Getting verification information...')) + for url in authorizations: + identifier_auth = BTPanel.dns_client.get_identifier_authorization(url) + authorization_url = identifier_auth["url"] + dns_name = identifier_auth["domain"] + dns_token = identifier_auth["dns_token"] + dns_challenge_url = identifier_auth["dns_challenge_url"] + + acme_keyauthorization, domain_dns_value = BTPanel.dns_client.get_keyauthorization(dns_token) + + acme_name = self.get_acme_name(dns_name) + dns_names_to_delete.append({"dns_name": public.de_punycode(dns_name),"acme_name":acme_name, "domain_dns_value": domain_dns_value}) + responders.append( + { + "dns_name":dns_name, + "authorization_url": authorization_url, + "acme_keyauthorization": acme_keyauthorization, + "dns_challenge_url": dns_challenge_url, + } + ) + + dns = {} + dns['dns_names'] = dns_names_to_delete + dns['responders'] = responders + dns['finalize_url'] = finalize_url + self.write_log(public.get_msg_gettext('|-Return the verification information to the front end, wait for the user to manually resolve the domain name and complete the verification...')) + return dns + else: + self.write_log(public.get_msg_gettext('|-User submits verification request...')) + responders = data['dns']['responders'] + dns_names_to_delete = data['dns']['dns_names'] + finalize_url = data['dns']['finalize_url'] + for i in responders: + self.write_log(public.get_msg_gettext('|-Requesting CA to verify domain name [{}]...',(i['dns_name'],))) + auth_status_response = BTPanel.dns_client.check_authorization_status(i["authorization_url"]) + if auth_status_response.json()["status"] == "pending": + BTPanel.dns_client.respond_to_challenge(i["acme_keyauthorization"], i["dns_challenge_url"]) + + for i in responders: + self.write_log(public.get_msg_gettext('|-Get CA verification results [{}]...',(i['dns_name'],))) + BTPanel.dns_client.check_authorization_status(i["authorization_url"], ["valid","invalid"]) + self.write_log(public.get_msg_gettext('|-All domain names are verified and CSR is being sent...')) + certificate_url = BTPanel.dns_client.send_csr(finalize_url) + self.write_log(public.get_msg_gettext('|-Getting certificate content...')) + certificate = BTPanel.dns_client.download_certificate(certificate_url) + + if certificate: + certificate = self.split_ca_data(certificate) + result['cert'] = certificate['cert'] + result['ca_data'] = certificate['ca_data'] + result['key'] = BTPanel.dns_client.certificate_key + result['account_key'] = BTPanel.dns_client.account_key + result['status'] = True + BTPanel.dns_client = None + else: + result['msg'] = public.get_msg_gettext('Certificate acquisition failed, please try again later.') + + except Exception as e: + self.write_log(public.get_msg_gettext('|-Error: {}, exited the application process.',(e,))) + self.write_log("=" * 50) + res = str(e).split('>>>>') + err = False + try: + err = json.loads(res[1]) + except: err = False + result['msg'] = [self.get_error(res[0]),err] + + return result + + #dns验证 + def crate_let_by_dns(self,data): + dns_class = self.get_dns_class(data) + if not dns_class: + self.write_log(public.get_msg_gettext('|-Error: {}, exit the application process.')) + self.write_log(public.get_msg_gettext('|-Exited the application process!')) + self.write_log("="*50) + return public.return_msg_gettext(False, 'An error occurred while requesting a certificate using dns') + + result = {} + result['status'] = False + try: + log_level = "INFO" + if data['account_key']: log_level = 'ERROR' + if not data['email']: data['email'] = public.M('users').getField('email') + self.write_log(public.get_msg_gettext('|-Initializing ACME protocol...')) + client = sewer.Client(domain_name = data['first_domain'],domain_alt_names = data['domains'],account_key = data['account_key'],contact_email = str(data['email']),LOG_LEVEL = log_level,ACME_AUTH_STATUS_WAIT_PERIOD = 15,ACME_AUTH_STATUS_MAX_CHECKS = 5,ACME_REQUEST_TIMEOUT = 20, dns_class = dns_class,ACME_DIRECTORY_URL = self.let_url) + domain_dns_value = "placeholder" + dns_names_to_delete = [] + try: + self.write_log(public.get_msg_gettext('|-Registering account...')) + client.acme_register() + authorizations, finalize_url = client.apply_for_cert_issuance() + responders = [] + self.write_log(public.get_msg_gettext('|-Getting verification information...')) + for url in authorizations: + identifier_auth = client.get_identifier_authorization(url) + authorization_url = identifier_auth["url"] + dns_name = identifier_auth["domain"] + dns_token = identifier_auth["dns_token"] + dns_challenge_url = identifier_auth["dns_challenge_url"] + acme_keyauthorization, domain_dns_value = client.get_keyauthorization(dns_token) + self.write_log(public.get_msg_gettext('|-Adding resolution record, domain name [{}], record value [{}]...',(dns_name,domain_dns_value))) + dns_class.create_dns_record(public.de_punycode(dns_name), domain_dns_value) + dns_names_to_delete.append({"dns_name": public.de_punycode(dns_name), "domain_dns_value": domain_dns_value}) + responders.append({"dns_name":dns_name,"domain_dns_value":domain_dns_value,"authorization_url": authorization_url, "acme_keyauthorization": acme_keyauthorization,"dns_challenge_url": dns_challenge_url} ) + + + + try: + for i in responders: + self.write_log(public.get_msg_gettext('|-Attempt to verify the resolution result, domain name [{}], record value [{}]...',(i['dns_name'],i['domain_dns_value']))) + self.check_dns(self.get_acme_name(i['dns_name']),i['domain_dns_value']) + self.write_log(public.get_msg_gettext('|-Request CA to verify domain name [{}]...',(i['dns_name']))) + auth_status_response = client.check_authorization_status(i["authorization_url"]) + r_data = auth_status_response.json() + if r_data["status"] == "pending": + client.respond_to_challenge(i["acme_keyauthorization"], i["dns_challenge_url"]) + + for i in responders: + self.write_log(public.get_msg_gettext('|-Check CA verification results [{}]...',(i['dns_name'],))) + client.check_authorization_status(i["authorization_url"], ["valid","invalid"]) + except Exception as ex: + self.write_log(public.get_msg_gettext('|-An error occurred, try again [{}]',(str(ex),))) + for i in responders: + self.write_log(public.get_msg_gettext('|-Attempt to verify the resolution result, domain name [{}], record value [{}]...',(i['dns_name'],i['domain_dns_value']))) + self.check_dns(self.get_acme_name(i['dns_name']),i['domain_dns_value']) + self.write_log(public.get_msg_gettext('|-Request CA to verify domain name [{}]...',(i['dns_name']))) + auth_status_response = client.check_authorization_status(i["authorization_url"]) + r_data = auth_status_response.json() + if r_data["status"] == "pending": + client.respond_to_challenge(i["acme_keyauthorization"], i["dns_challenge_url"]) + for i in responders: + self.write_log(public.get_msg_gettext('|-Check CA verification results [{}]...',(i['dns_name'],))) + client.check_authorization_status(i["authorization_url"], ["valid","invalid"]) + self.write_log(public.get_msg_gettext('|-All domain names are verified and CSR is being sent...')) + certificate_url = client.send_csr(finalize_url) + self.write_log(public.get_msg_gettext('|-Fetching certificate content...')) + certificate = client.download_certificate(certificate_url) + if certificate: + certificate = self.split_ca_data(certificate) + result['cert'] = certificate['cert'] + result['ca_data'] = certificate['ca_data'] + result['key'] = client.certificate_key + result['account_key'] = client.account_key + result['status'] = True + + except Exception as e: + raise e + finally: + try: + for i in dns_names_to_delete: + self.write_log(public.get_msg_gettext('|-Clearing resolve history [{}]',(i["dns_name"]))) + dns_class.delete_dns_record(i["dns_name"], i["domain_dns_value"]) + except : + pass + + except Exception as e: + try: + for i in dns_names_to_delete: + self.write_log(public.get_msg_gettext('|-Clearing resolve history [{}]',(i["dns_name"]))) + dns_class.delete_dns_record(i["dns_name"], i["domain_dns_value"]) + except:pass + self.write_log(public.get_msg_gettext('|-Error: {}, exit the application process.',(str(public.get_error_info()),))) + self.write_log("=" * 50) + res = str(e).split('>>>>') + err = False + try: + err = json.loads(res[1]) + except: err = False + result['msg'] = [self.get_error(res[0]),err] + return result + + #文件验证 + def crate_let_by_file(self,data): + result = {} + result['status'] = False + result['clecks'] = [] + try: + self.write_log(public.get_msg_gettext('|-Initializing ACME protocol...')) + log_level = "INFO" + if data['account_key']: log_level = 'ERROR' + if not data['email']: data['email'] = public.M('users').getField('email') + client = sewer.Client(domain_name = data['first_domain'],dns_class = None,account_key = data['account_key'],domain_alt_names = data['domains'],contact_email = str(data['email']),LOG_LEVEL = log_level,ACME_AUTH_STATUS_WAIT_PERIOD = 15,ACME_AUTH_STATUS_MAX_CHECKS = 5,ACME_REQUEST_TIMEOUT = 20,ACME_DIRECTORY_URL = self.let_url) + self.write_log(public.get_msg_gettext('|-Registering account...')) + client.acme_register() + authorizations, finalize_url = client.apply_for_cert_issuance() + responders = [] + sucess_domains = [] + self.write_log(public.get_msg_gettext('|-Getting verification information...')) + for url in authorizations: + identifier_auth = self.get_identifier_authorization(client,url) + + authorization_url = identifier_auth["url"] + http_name = identifier_auth["domain"] + http_token = identifier_auth["http_token"] + http_challenge_url = identifier_auth["http_challenge_url"] + + acme_keyauthorization, domain_http_value = client.get_keyauthorization(http_token) + acme_dir = '%s/.well-known/acme-challenge' % (data['site_dir']) + if not os.path.exists(acme_dir): os.makedirs(acme_dir) + + #写入token + wellknown_path = acme_dir + '/' + http_token + self.write_log(public.get_msg_gettext('|-Writing verification file [{}]...',(wellknown_path,))) + public.writeFile(wellknown_path,acme_keyauthorization) + wellknown_url = "http://{}/.well-known/acme-challenge/{}".format(http_name, http_token) + + result['clecks'].append({'wellknown_url':wellknown_url,'http_token':http_token}) + is_check = False + n = 0 + self.write_log(public.get_msg_gettext('|-Attempt to verify file contents via HTTP [{}]...',(wellknown_url))) + while n < 5: + print("wait_check_authorization_status") + try: + retkey = public.httpGet(wellknown_url,20) + if retkey == acme_keyauthorization: + is_check = True + self.write_log(public.get_msg_gettext('|-Verified, content [{}]...',(retkey,))) + break + except : + pass + n += 1 + time.sleep(1) + sucess_domains.append(http_name) + responders.append({"http_name":http_name,"authorization_url": authorization_url, "acme_keyauthorization": acme_keyauthorization,"http_challenge_url": http_challenge_url}) + + if len(sucess_domains) > 0: + #验证 + for i in responders: + self.write_log(public.get_msg_gettext('|-Request CA to verify domain name [{}]...',(i['http_name'],))) + auth_status_response = client.check_authorization_status(i["authorization_url"]) + if auth_status_response.json()["status"] == "pending": + client.respond_to_challenge(i["acme_keyauthorization"], i["http_challenge_url"]).json() + + for i in responders: + self.write_log(public.get_msg_gettext('|-Check CA verification results [{}]...',(i['http_name'],))) + client.check_authorization_status(i["authorization_url"], ["valid","invalid"]) + + self.write_log(public.get_msg_gettext('|-All domain names are verified and CSR is being sent...')) + certificate_url = client.send_csr(finalize_url) + self.write_log(public.get_msg_gettext('|-Getting certificate content...')) + certificate = client.download_certificate(certificate_url) + + if certificate: + certificate = self.split_ca_data(certificate) + result['cert'] = certificate['cert'] + result['ca_data'] = certificate['ca_data'] + result['key'] = client.certificate_key + result['account_key'] = client.account_key + result['status'] = True + + else: + result['msg'] = public.get_msg_gettext('Certificate acquisition failed, please try again later.') + else: + result['msg'] = public.get_msg_gettext('The signing failed, we were unable to verify your domain name:

                                    1. Check if the domain name is bound to the corresponding site.

                                    2. Check if the domain name is correctly resolved to the server, or the resolution is not fully effective.

                                    3. If your site has a reverse proxy set up, or if you are using a CDN, please turn it off first.

                                    4. If your site has a 301 redirect, please turn it off first

                                    5. If the above checks confirm that there is no problem, please try to change the DNS service provider.

                                    ') + except Exception as e: + self.write_log(public.get_msg_gettext('|-Error: {}, exit the application process.',(str(public.get_error_info()),))) + self.write_log("=" * 50) + res = str(e).split('>>>>') + err = False + try: + err = json.loads(res[1]) + except: err = False + result['msg'] = [self.get_error(res[0]),err] + return result + + + def get_identifier_authorization(self,client, url): + + headers = {"User-Agent": client.User_Agent} + get_identifier_authorization_response = requests.get(url, timeout = client.ACME_REQUEST_TIMEOUT, headers=headers,verify=False) + if get_identifier_authorization_response.status_code not in [200, 201]: + raise ValueError("Error getting identifier authorization: status_code={status_code}".format(status_code=get_identifier_authorization_response.status_code ) ) + res = get_identifier_authorization_response.json() + domain = res["identifier"]["value"] + wildcard = res.get("wildcard") + if wildcard: + domain = "*." + domain + + for i in res["challenges"]: + if i["type"] == "http-01": + http_challenge = i + http_token = http_challenge["token"] + http_challenge_url = http_challenge["url"] + identifier_auth = { + "domain": domain, + "url": url, + "wildcard": wildcard, + "http_token": http_token, + "http_challenge_url": http_challenge_url, + } + return identifier_auth + + #检查DNS记录 + def check_dns(self,domain,value,type='TXT'): + time.sleep(5) + n = 0 + while n < 10: + try: + import dns.resolver + ns = dns.resolver.query(domain,type) + for j in ns.response.answer: + for i in j.items: + txt_value = i.to_text().replace('"','').strip() + if txt_value == value: + self.write_log(public.get_msg_gettext('|-Successful verification, domain name [{}], record type [{}], record value [{}]!',(domain,type,txt_value))) + print("Verification succeeded: %s" % txt_value) + return True + except: + try: + import dns.resolver + except: + return False + n+=1 + time.sleep(5) + return True + + #获取证书哈希 + def get_cert_data(self,path): + try: + if path[-4:] == '.pfx': + f = open(path,'rb') + pfx_buffer = f.read() + p12 = crypto.load_pkcs12(pfx_buffer,'') + x509 = p12.get_certificate() + else: + cret_data = public.readFile(path) + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cret_data) + + buffs = x509.digest('sha1') + hash = bytes.decode(buffs).replace(':','') + data = {} + data['hash'] = hash + data['timeout'] = bytes.decode(x509.get_notAfter())[:-1] + return data + except : + return False + + + #获取快过期的证书 + def get_renew_lets_bytimeout(self,cron_list): + tday = 30 + path = self.setupPath + '/panel/vhost/cert' + nlist = {} + new_list = {} + for siteName in cron_list: + spath = path + '/' + siteName + #验证是否存在续签KEY + if os.path.exists(spath + '/account_key.key'): + if public.M('sites').where("name=?",(siteName,)).count(): + new_list[siteName] = cron_list[siteName] + data = self.get_cert_data(self.setupPath + '/panel/vhost/cert/' + siteName + '/fullchain.pem') + timeout = int(time.mktime(time.strptime(data['timeout'],'%Y%m%d%H%M%S'))) + eday = (timeout - int(time.time())) / 86400 + if eday < 30: + nlist[siteName] = cron_list[siteName] + #清理过期配置 + public.writeFile(self.setupPath + '/panel/vhost/cert/crontab.json',json.dumps(new_list)) + return nlist + + #===================================== 计划任务续订证书 =====================================# + #续订 + def renew_lets_ssl(self): + cpath = self.setupPath + '/panel/vhost/cert/crontab.json' + if not os.path.exists(cpath): + print(public.get_msg_gettext('|-There are currently no certificates to renew.') ) + else: + old_list = json.loads(public.ReadFile(cpath)) + print('=======================================================================') + print(public.get_msg_gettext('|-{} Total [{}] renewal of visa tasks',(time.strftime('%Y-%m-%d %X',time.localtime()),str(len(old_list))))) + cron_list = self.get_renew_lets_bytimeout(old_list) + + tlist = [] + for siteName in old_list: + if not siteName in cron_list: tlist.append(siteName) + print(public.get_msg_gettext(r'|-[{}] Not expired or the site does not use the Let\s Encrypt certificate.',(','.join(tlist),))) + print(public.get_msg_gettext('|-{} Waiting for renewal [{}].',(time.strftime('%Y-%m-%d %X',time.localtime()),str(len(cron_list))))) + + sucess_list = [] + err_list = [] + for siteName in cron_list: + data = cron_list[siteName] + ret = self.renew_lest_cert(data) + if ret['status']: + sucess_list.append(siteName) + else: + err_list.append({"siteName":siteName,"msg":ret['msg']}) + print(public.get_msg_gettext('|-After the task is completed, a total of renewals are required.[{}], renewal success [%s], renewal failed [{}]. ',(str(len(cron_list)),str(len(sucess_list)),str(len(err_list))))) + if len(sucess_list) > 0: + print(public.get_msg_gettext('|-Renewal success:{}',(','.join(sucess_list),))) + if len(err_list) > 0: + print(public.get_msg_gettext('|-Renewal failed:')) + for x in err_list: + print(" %s ->> %s" % (x['siteName'],x['msg'])) + + print('=======================================================================') + print(" ") + +if __name__ == "__main__": + if len(sys.argv) > 1: + type = sys.argv[1] + if type == 'renew_lets_ssl': + try: + panelLets().renew_lets_ssl() + except: pass + os.system(public.get_python_bin() + " /www/server/panel/class/acme_v2.py --renew=1") diff --git a/class_v2/panel_mssql_v2.py b/class_v2/panel_mssql_v2.py new file mode 100644 index 00000000..c5dd2c84 --- /dev/null +++ b/class_v2/panel_mssql_v2.py @@ -0,0 +1,121 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Windows面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 沐落 +# +------------------------------------------------------------------- + +import re,os,sys,public + +class panelMssql: + __DB_PASS = None + __DB_USER = 'sa' + __DB_PORT = 1433 + __DB_HOST = '127.0.0.1' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + __DB_SERVER = 'MSSQLSERVER' + + __DB_CLOUD = 0 #远程数据库 + def __init__(self): + self.__DB_CLOUD = 0 + + + def set_host(self,host,port,name,username,password,prefix = ''): + self.__DB_HOST = host + self.__DB_PORT = int(port) + self.__DB_NAME = name + if self.__DB_NAME: self.__DB_NAME = str(self.__DB_NAME) + self.__DB_USER = str(username) + self._USER = str(username) + self.__DB_PASS = str(password) + self.__DB_PREFIX = prefix + self.__DB_CLOUD = 1 + return self + + def __Conn(self): + """ + 连接MSSQL数据库 + """ + try: + import pymssql + except : + os.system("btpip install pymssql==2.3.0") + # os.system("btpip install pymssql==2.1.4") + import pymssql + + + if not self.__DB_CLOUD: + sa_path = 'data/sa.pl' + if os.path.exists(sa_path): self.__DB_PASS = public.readFile(sa_path) + self.__DB_PORT = self.get_port() + + try: + + if self.__DB_CLOUD: + try: + self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), + user=self.__DB_USER, + password=self.__DB_PASS, database=None, login_timeout=30, + timeout=0, autocommit=True, charset="CP936", tds_version='7.0') + except: + self.__DB_ERR = 'Failed to connect to database! Check that the remote database information is correct' + return False + # self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), + # user=self.__DB_USER, + # password=self.__DB_PASS, database=None, login_timeout=30, + # timeout=0, autocommit=True, charset="CP936", tds_version='7.0') + + else: + self.__DB_CONN = pymssql.connect(server=self.__DB_HOST, port=str(self.__DB_PORT), login_timeout=30, + timeout=0, autocommit=True, charset="CP936", tds_version='7.0') + self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。 + self.__DB_CUR = self.__DB_CONN.cursor() #将数据库连接信息,赋值给cur。 + if self.__DB_CUR: + return True + else: + self.__DB_ERR = 'Failed to connect to the database, please check whether SQL Server is installed' + return False + except Exception as ex: + self.__DB_ERR = public.get_error_info() + + return False + + def execute(self,sql): + + #执行SQL语句返回受影响行 + if not self.__Conn(): return self.__DB_ERR + try: + result = self.__DB_CUR.execute(sql) + + self.__Close() + return result; + except Exception as ex: + self.__DB_ERR = public.get_error_info() + return self.__DB_ERR + + def query(self,sql): + #执行SQL语句返回数据集 + if not self.__Conn(): return self.__DB_ERR + try: + self.__DB_CUR.execute(sql) + result = self.__DB_CUR.fetchall() + + #print(result) + #将元组转换成列表 + data = list(map(list,result)) + self.__Close() + return data + except Exception as ex: + self.__DB_ERR = public.get_error_info() + #public.WriteLog('SQL Server查询异常', self.__DB_ERR); + return str(ex) + + + #关闭连接 + def __Close(self): + self.__DB_CUR.close() + self.__DB_CONN.close() \ No newline at end of file diff --git a/class_v2/panel_mysql_v2.py b/class_v2/panel_mysql_v2.py new file mode 100644 index 00000000..d3f4e40c --- /dev/null +++ b/class_v2/panel_mysql_v2.py @@ -0,0 +1,231 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +import re,os,sys,public + +class panelMysql: + __DB_PASS = None + __DB_USER = 'root' + __DB_PORT = 3306 + __DB_HOST = 'localhost' + __DB_CONN = None + __DB_CUR = None + __DB_ERR = None + __DB_NET = None + #连接MYSQL数据库 + def __Conn(self): + if self.__DB_NET: return True + try: + myconf = public.readFile('/etc/my.cnf') + socket_re = re.search(r"socket\s*=\s*(.+)",myconf) + if socket_re: + socket = socket_re.groups()[0] + else: + socket = '/tmp/mysql.sock' + + try: + if sys.version_info[0] != 2: + try: + import pymysql + except: + public.ExecShell("pip install pymysql") + import pymysql + pymysql.install_as_MySQLdb() + import MySQLdb + if sys.version_info[0] == 2: + reload(MySQLdb) + except: + try: + import pymysql + pymysql.install_as_MySQLdb() + import MySQLdb + except Exception as e: + self.__DB_ERR = e + return False + try: + + rep = r"port\s*=\s*([0-9]+)" + self.__DB_PORT = int(re.search(rep,myconf).groups()[0]) + except: + self.__DB_PORT = 3306 + self.__DB_PASS = public.M('config').where('id=?',(1,)).getField('mysql_root') + + try: + self.__DB_CONN = MySQLdb.connect(host = self.__DB_HOST,user = self.__DB_USER,passwd = self.__DB_PASS,port = self.__DB_PORT,charset="utf8",connect_timeout=1,unix_socket=socket) + except MySQLdb.Error as e: + self.__DB_HOST = '127.0.0.1' + self.__DB_CONN = MySQLdb.connect(host = self.__DB_HOST,user = self.__DB_USER,passwd = self.__DB_PASS,port = self.__DB_PORT,charset="utf8",connect_timeout=1,unix_socket=socket) + self.__DB_CUR = self.__DB_CONN.cursor() + return True + except MySQLdb.Error as e: + self.__DB_ERR = e + return False + + #连接远程数据库 + def connect_network(self,host,port,username,password): + self.__DB_NET = True + try: + try: + if sys.version_info[0] != 2: + try: + import pymysql + except: + public.ExecShell("pip install pymysql") + import pymysql + pymysql.install_as_MySQLdb() + import MySQLdb + if sys.version_info[0] == 2: + reload(MySQLdb) + except: + try: + import pymysql + pymysql.install_as_MySQLdb() + import MySQLdb + except Exception as e: + self.__DB_ERR = e + return False + self.__DB_CONN = MySQLdb.connect(host = host,user = username,passwd = password,port = port,charset="utf8",connect_timeout=10) + self.__DB_CUR = self.__DB_CONN.cursor() + return True + except MySQLdb.Error as e: + self.__DB_ERR = e + return False + + + + def execute(self,sql): + #执行SQL语句返回受影响行 + if not self.__Conn(): return self.__DB_ERR + try: + result = self.__DB_CUR.execute(sql) + self.__DB_CONN.commit() + self.__Close() + return result + except Exception as ex: + return ex + + + def query(self,sql): + #执行SQL语句返回数据集 + if not self.__Conn(): return self.__DB_ERR + try: + self.__DB_CUR.execute(sql) + result = self.__DB_CUR.fetchall() + #将元组转换成列表 + if sys.version_info[0] == 2: + data = map(list,result) + else: + data = list(map(list,result)) + self.__Close() + return data + except Exception as ex: + return ex + + + #关闭连接 + def __Close(self): + self.__DB_CUR.close() + self.__DB_CONN.close() + + +# Mysql数据库连接类 支持Context +class PanelMysqlWithContext: + def __init__(self, db_name=None, db_user: str = 'root', db_pwd=None, db_host: str = 'localhost'): + self.__CONN = None + self.__DB_NAME = db_name + self.__HOST = db_host + self.__PORT = 3306 + self.__USERNAME = db_user + self.__PASSWORD = db_pwd + self.__CHARSET = 'utf8mb4' + self.__CONNECT_TIMEOUT = 10 + self.__UNIX_SOCK = None + + def __enter__(self): + if self.__CONN: + return self + + if self.__HOST in ('localhost', '127.0.0.1'): + self.__UNIX_SOCK = '/tmp/mysql.sock' + self.__CONNECT_TIMEOUT = 1 + + myconf = public.readFile('/etc/my.cnf') + m = re.search(r"socket\s*=\s*(.+)", myconf) + if m: + self.__UNIX_SOCK = m.group(1) + + m = re.search(r"port\s*=\s*([0-9]+)", myconf) + if m: + self.__PORT = int(m.group(1)) + + if self.__USERNAME == 'root': + self.__PASSWORD = public.M('config').where('id=?', (1,)).getField('mysql_root') + + import pymysql + + try: + self.__CONN = pymysql.connect(host=self.__HOST, user=self.__USERNAME, passwd=self.__PASSWORD, + port=self.__PORT, charset=self.__CHARSET, database=self.__DB_NAME, + connect_timeout=self.__CONNECT_TIMEOUT, + cursorclass=pymysql.cursors.DictCursor, unix_socket=self.__UNIX_SOCK) + except pymysql.Error: + if self.__HOST == 'localhost': + self.__HOST = '127.0.0.1' + self.__CONN = pymysql.connect(host=self.__HOST, user=self.__USERNAME, passwd=self.__PASSWORD, + port=self.__PORT, charset=self.__CHARSET, database=self.__DB_NAME, + connect_timeout=self.__CONNECT_TIMEOUT, + cursorclass=pymysql.cursors.DictCursor, unix_socket=self.__UNIX_SOCK) + raise + + return self + + def __del__(self): + if self.__CONN: + self.__CONN.close() + self.__CONN = None + + def __exit__(self, exc_type, exc_val, exc_tb): + self.__CONN.close() + self.__CONN = None + + # 执行SQL + def execute(self, sql): + cur = self.__CONN.cursor() + + try: + row_count = cur.execute(sql) + + self.__CONN.commit() + + return row_count + finally: + cur.close() + + # 查询多条 + def query(self, sql): + cur = self.__CONN.cursor() + + try: + row_count = cur.execute(sql) + + if row_count == 0: + return [] + + return cur.fetchall() + finally: + cur.close() + + # 查询单条 + def find(self, sql): + ret = self.query(sql) + + if len(ret) == 0: + return None + + return ret[0] diff --git a/class_v2/panel_php_v2.py b/class_v2/panel_php_v2.py new file mode 100644 index 00000000..e3fe0652 --- /dev/null +++ b/class_v2/panel_php_v2.py @@ -0,0 +1,622 @@ +#coding:utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +# +------------------------------------------------------------------- +# | PHP插件兼容模块 +# +------------------------------------------------------------------- + +import json,os,public,time,re,sys +import time +import fastcgiClient as fcgi_client +import struct +FCGI_Header = '!BBHHBx' + +if sys.version_info[0] == 2: + try: + from cStringIO import StringIO + except: + from StringIO import StringIO +else: + from io import BytesIO as StringIO + +class panelPHP: + re_io = None + def __init__(self,plugin_name = None): + if plugin_name: + self.__plugin_name = plugin_name + self.__plugin_path = "/www/server/panel/plugin/%s" % plugin_name + self.__args_dir = self.__plugin_path + '/args' + self.__args_tmp = self.__args_dir + '/' + public.GetRandomString(32) + if not os.path.exists(self.__args_dir): os.makedirs(self.__args_dir, 384) + + #调用PHP插件 + def exec_php_script(self,args): + #取PHP执行文件和CLI配置参数 + php_bin = self.__get_php_bin() + if not php_bin: return public.returnMsg(False,'PHP_NOT_FOUND') + #是否将参数写到文件 + self.__write_args(args) + result = os.popen("cd " + self.__plugin_path + " && %s /www/server/panel/class/panel_php_run.php --args_tmp=\"%s\" --plugin_name=\"%s\" --fun=\"%s\"" % + (php_bin,self.__args_tmp,self.__plugin_name,args.s)).read() + try: + #解析执行结果 + result = json.loads(result) + except: pass + #删除参数文件 + if os.path.exists(self.__args_tmp): + os.remove(self.__args_tmp) + return result + + #将参数写到文件 + def __write_args(self,args): + from BTPanel import request + if os.path.exists(self.__args_tmp): os.remove(self.__args_tmp) + self.__clean_args_file() + data = {} + data['GET'] = request.args.to_dict() + data['POST'] = {} + for key in request.form.keys(): + data['POST'][key] = str(request.form.get(key,'')) + data['POST']['client_ip'] = public.GetClientIp() + data = json.dumps(data) + public.writeFile(self.__args_tmp,data) + + #清理参数文件 + def __clean_args_file(self): + args_dir = self.__plugin_path + '/args' + if not os.path.exists(args_dir): return False + now_time = time.time() + for f_name in os.listdir(args_dir): + filename = args_dir + '/' + f_name + if not os.path.exists(filename): continue + #清理创建时间超过60秒的参数文件 + if now_time - os.path.getctime(filename) > 60: os.remove(filename) + + #取PHP-CLI执行命令 + def __get_php_bin(self): + #如果有指定兼容的PHP版本 + php_v_file = self.__plugin_path + '/php_version.json' + if os.path.exists(php_v_file): + php_vs = json.loads(public.readFile(php_v_file).replace('.','')) + else: + #否则兼容所有版本 + php_vs = public.get_php_versions(True) + #判段兼容的PHP版本是否安装 + php_path = "/www/server/php/" + php_v = None + for pv in php_vs: + php_bin = php_path + pv + "/bin/php" + if os.path.exists(php_bin): + php_v = pv + break + #如果没安装直接返回False + if not php_v: return False + #处理PHP-CLI-INI配置文件 + php_ini = self.__plugin_path + '/php_cli_'+php_v+'.ini' + if not os.path.exists(php_ini): + #如果不存在,则从PHP安装目录下复制一份 + src_php_ini = php_path + php_v + '/etc/php.ini' + import shutil + shutil.copy(src_php_ini,php_ini) + #解除所有禁用函数 + php_ini_body = public.readFile(php_ini) + php_ini_body = re.sub(r"disable_functions\s*=.*","disable_functions = ",php_ini_body) + php_ini_body = re.sub(r".*bt_filter.+","",php_ini_body) + public.writeFile(php_ini,php_ini_body) + return php_path + php_v + '/bin/php -c ' + php_ini + + def get_php_version(self,php_version): + if php_version: + if not isinstance(php_version,list): + php_vs = [php_version] + else: + php_vs = sorted(php_version,reverse=True) + else: + php_vs = public.get_php_versions(True) + php_path = "/www/server/php/" + php_v = None + for pv in php_vs: + php_bin = php_path + pv + "/bin/php" + if os.path.exists(php_bin) and os.path.exists("/tmp/php-cgi-{}.sock".format(pv)): + php_v = pv + break + return php_v + +# def get_phpmyadmin_phpversion(self): +# ''' +# @name 获取当前phpmyadmin设置的PHP版本 +# @author hwliang<2020-07-13> +# @return string +# ''' +# from BTPanel import cache +# ikey = 'pma_phpv' +# phpv = cache.get(ikey) +# if phpv: return phpv +# webserver = public.get_webserver() +# if webserver == 'nginx': +# filename = public.GetConfigValue('setup_path') + '/nginx/conf/enable-php.conf' +# conf = public.readFile(filename) +# if not conf: return None +# rep = r"php-cgi-(\d+)\.sock" +# phpv = re.findall(rep,conf) +# elif webserver == 'openlitespeed': +# filename = public.GetConfigValue('setup_path') + "/panel/vhost/openlitespeed/detail/phpmyadmin.conf" +# conf = public.readFile(filename) +# if not conf: return None +# rep = r"/usr/local/lsws/lsphp(\d+)/bin/lsphp" +# phpv = re.findall(rep,conf) +# else: +# filename = public.GetConfigValue('setup_path') + '/apache/conf/extra/httpd-vhosts.conf' +# conf = public.readFile(filename) +# if not conf: return None +# rep = r"php-cgi-(\d+)\.sock" +# phpv = re.findall(rep,conf) + +# if not phpv: return None +# cache.set(ikey,phpv[0],3) +# return phpv[0] + +# def get_pma_root(self): +# ''' +# @name 获取phpmyadmin根目录 +# @author hwliang<2020-07-13> +# @return string +# ''' +# pma_path = '/www/server/phpmyadmin/' +# if not os.path.exists(pma_path): +# os.makedirs(pma_path) +# for dname in os.listdir(pma_path): +# if dname.find('phpmyadmin_') != -1: +# return os.path.join(pma_path,dname) +# return None + +# def check_phpmyadmin_phpversion(self): +# ''' +# @name 检查当前phpmyadmin版本可用的php版本列表 +# @author hwliang<2020-07-13> +# @return list +# ''' +# return +# pma_path = '/www/server/phpmyadmin/' +# pma_version_f1 = os.path.join(pma_path,'version_check.pl') +# pma_root = os.path.join(pma_path,'pma') +# pma_version_f2 = os.path.join(pma_root,'version_check.pl') +# if not os.path.exists(pma_version_f1): +# src_vfile = os.path.join(pma_path,'version.pl') +# if os.path.exists(src_vfile): +# public.writeFile(pma_version_f1,public.readFile(src_vfile)) +# v_sync = public.readFile(pma_version_f1) == public.readFile(pma_version_f2) +# +# if not os.path.exists(pma_root + '/index.php') or not v_sync: +# o_pma_root = self.get_pma_root() +# +# if o_pma_root: +# if not os.path.exists(pma_root): +# os.makedirs(pma_root) +# public.ExecShell(r"\cp -arf {}/* {}/".format(o_pma_root,pma_root)) +# public.ExecShell("chown -R www:www {}".format(pma_root)) +# public.ExecShell("chmod -R 700 {}".format(pma_root)) +# public.ExecShell(r"\cp -arf {} {}".format(pma_version_f1,pma_version_f2)) +# index = public.readFile(pma_root + '/index.php') +# if index: +# if index.find("use PhpMyAdmin\\Util") != -1: +# resp = "use PhpMyAdmin\\Util;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');" +# index = index.replace("use PhpMyAdmin\\Util;",resp) +# elif index.find(r"use PMA\libraries\LanguageManager;") != -1: +# resp = "use PMA\\libraries\\LanguageManager;\nif(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');" +# index = index.replace(r"use PMA\libraries\LanguageManager;",resp) +# elif index.find("require_once 'libraries/common.inc.php';") != -1: +# resp = "if(function_exists('opcache_invalidate')) opcache_invalidate('/www/server/phpmyadmin/pma/config.inc.php');\nrequire_once 'libraries/common.inc.php';" +# index = index.replace("require_once 'libraries/common.inc.php';",resp) +# +# +# public.writeFile(pma_root + '/index.php',index) +# +# if not os.path.exists(pma_version_f2): +# return False +# +# pma_version = public.readFile(pma_version_f2) +# self.pma_version = pma_version +# if pma_version: +# pma_version = pma_version[:3] +# +# if pma_version == '4.4': +# return ['53','54','55','56'] +# elif pma_version == '4.0': +# return ['52','53'] +# elif pma_version == '4.6': +# return None +# elif pma_version == '4.7': +# return ['55','56','70','71','72'] +# elif pma_version in ['4.8','4.9','5.0']: +# return ['70','71','72','73','74'] +# else: +# return ['55','56','70','71','72'] +# +# def get_mysql_port(self): +# ''' +# @name 获取mysql当前端口号 +# @author hwliang<2020-07-13> +# @return int +# ''' +# try: +# myconf = public.readFile('/etc/my.cnf') +# rep = r"port\s*=\s*([0-9]+)" +# port = int(re.search(rep,myconf).groups()[0]) +# if not port: port = 3306 +# return port +# except: +# return 3306 +# +# def write_pma_passwd(self,username,password): +# ''' +# @name 写入mysql帐号密码到配置文件 +# @author hwliang<2020-07-13> +# @param username string(用户名) +# @param password string(密码) +# @return bool +# ''' +# +# self.check_phpmyadmin_phpversion() +# pconfig = 'cookie' +# if username: +# pconfig = 'config' +# pma_path = '/www/server/phpmyadmin/' +# pma_config_file = os.path.join(pma_path,'pma/config.inc.php') +# conf = public.readFile(pma_config_file) +# if not conf: return False +# rep = r"/\* Authentication type \*/(.|\n)+/\* Server parameters \*/" +# rstr = '''/* Authentication type */ +# $cfg['Servers'][$i]['auth_type'] = '{}'; +# $cfg['Servers'][$i]['host'] = 'localhost'; +# $cfg['Servers'][$i]['port'] = '{}'; +# $cfg['Servers'][$i]['user'] = '{}'; +# $cfg['Servers'][$i]['password'] = '{}'; +# /* Server parameters */'''.format(pconfig,self.get_mysql_port(),username,password) +# conf = re.sub(rep,rstr,conf) +# public.writeFile(pma_config_file,conf) +# return True +# +# def request_php(self,uri): +# ''' +# @name 发起fastcgi请求到PHP-FPM +# @author hwliang<2020-07-11> +# @param puri string(URI地址) +# @return socket +# ''' +# php_unix_socket = '/tmp/php-cgi-{}.sock'.format(self.php_version) +# f = FPM(php_unix_socket,self.document_root,self.last_path) +# +# if request.full_path.find('?') != -1: +# uri = request.full_path[request.full_path.find(uri):] +# if self.re_io: +# sock = f.load_url(uri,content=self.re_io) +# else: +# sock = f.load_url(uri,content=request.stream) +# return sock +# +# def start(self,puri,document_root,last_path = ''): +# ''' +# @name 开始处理PHP请求 +# @author hwliang<2020-07-11> +# @param puri string(URI地址) +# @return socket or Response +# ''' +# if puri in ['/','',None]: puri = 'index.php' +# if puri[0] == '/': puri = puri[1:] +# self.document_root = document_root +# self.last_path = last_path +# filename = document_root + puri +# +# +# #如果是PHP文件 +# if puri[-4:] == '.php': +# if request.path.find('/phpmyadmin/') != -1: +# ikey = 'pma_php_version' +# self.php_version = cache.get(ikey) +# if not self.php_version: +# php_version = self.get_phpmyadmin_phpversion() +# php_versions = self.check_phpmyadmin_phpversion() +# if not php_versions: +# if php_versions == False: +# return Resp( +# 'Phpmyadmin is not installed, or support for phpMyAdmin4.6 has been discontinued due to security issues, uninstall and install other secure versions in the software store!') +# else: +# return Resp('phpmyadmin is not installed') +# if not php_version or not php_version in php_versions: +# php_version = php_versions +# self.php_version = self.get_php_version(php_version) +# if not self.php_version: +# php_version = self.check_phpmyadmin_phpversion() +# self.php_version = self.get_php_version(php_version) +# if not php_version: +# return Resp('No supported PHP version found: {}'.format(php_versions)) +# +# if not self.php_version in php_versions: +# self.php_version = self.get_php_version(php_versions) +# +# if not self.php_version: +# return Resp('No supported PHP version found: {}'.format(php_versions)) +# cache.set(ikey,self.php_version,1) +# if request.method == 'POST': +# #登录phpmyadmin +# if puri in ['index.php','/index.php']: +# content = public.url_encode(request.form.to_dict()) +# if not isinstance(content,bytes): +# content = content.encode() +# self.re_io = StringIO(content) +# username = request.form.get('pma_username') +# if username: +# password = request.form.get('pma_password') +# if not self.write_pma_passwd(username,password): +# return Resp('Phpmyadmin is not installed') +# +# if puri in ['logout.php', '/logout.php']: +# self.write_pma_passwd(None, None) +# else: +# src_path = '/www/server/panel/adminer' +# dst_path = '/www/server/adminer' +# if os.path.exists(src_path): +# if not os.path.exists(dst_path): os.makedirs(dst_path) +# public.ExecShell(r"\cp -arf {}/* {}/".format(src_path, dst_path)) +# public.ExecShell("chown -R www:www {}".format(dst_path)) +# public.ExecShell("chmod -R 700 {}".format(dst_path)) +# public.ExecShell("rm -rf {}".format(src_path)) +# +# if not os.path.exists(dst_path + '/index.php'): +# return Resp("The AdMiner file is missing. Please try again after the [Fix] panel on the first page!") +# +# ikey = 'aer_php_version' +# self.php_version = cache.get(ikey) +# if not self.php_version: +# self.php_version = self.get_php_version(None) +# cache.set(ikey, self.php_version, 10) +# if not self.php_version: +# return Resp('没有找到可用的PHP版本') +# +# #文件是否存在? +# if not os.path.exists(filename): +# return abort(404) +# +# #发送到FPM +# try: +# return self.request_php(puri) +# except Exception as ex: +# if str(ex).find('No such file or directory') != -1: +# return Resp('Specify PHP version: {}, not started, or unable to connect!'.format(self.php_version)) +# return Resp(str(ex)) +# +# if not os.path.exists(filename): +# return abort(404) +# +# #如果是静态文件 +# return send_file(filename) + + + + #获取头部128KB数据 + def get_header_data(self,sock): + ''' + @name 获取头部32KB数据 + @author hwliang<2020-07-11> + @param sock socketobject(fastcgi套接字对象) + @return bytes + ''' + headers_data = b'' + total_len = 0 + header_len = 1024 * 128 + while True: + fastcgi_header = sock.recv(8) + if not fastcgi_header: break + if len(fastcgi_header) != 8: + headers_data += fastcgi_header + break + fast_pack = struct.unpack(FCGI_Header, fastcgi_header) + if fast_pack[1] == 3: break + + tlen = fast_pack[3] + while tlen > 0: + sd = sock.recv(tlen) + if not sd: break + headers_data += sd + tlen -= len(sd) + + total_len += fast_pack[3] + if fast_pack[4]: + sock.recv(fast_pack[4]) + if total_len > header_len: break + return headers_data + + #格式化响应头 + def format_header_data(self,headers_data): + ''' + @name 格式化响应头 + @author hwliang<2020-07-11> + @param headers_data bytes(fastcgi头部32KB数据) + @return status int(响应状态), headers dict(响应头), bdata bytes(格式化响应头后的多余数据) + ''' + status = '200 OK' + headers = {} + pos = 0 + while True: + eolpos = headers_data.find(b'\n', pos) + if eolpos < 0: break + line = headers_data[pos:eolpos-1] + pos = eolpos + 1 + line = line.strip() + if len(line) < 2: break + if line.find(b':') == -1: continue + header, value = line.split(b':', 1) + header = header.strip() + value = value.strip() + if isinstance(header,bytes): + header = header.decode() + value = value.decode() + if header == 'Status': + status = value + if status.find(' ') < 0: + status += ' BTPanel' + else: + headers[header] = value + bdata = headers_data[pos:] + status = int(status.split(' ')[0]) + return status,headers,bdata + + #以流的方式发送剩余数据 + def resp_sock(self,sock,bdata): + ''' + @name 以流的方式发送剩余数据 + @author hwliang<2020-07-11> + @param sock socketobject(fastcgi套接字对象) + @param bdata bytes(格式化响应头后的多余数据) + @return yield bytes + ''' + #发送除响应头以外的多余头部数据 + yield bdata + while True: + fastcgi_header = sock.recv(8) + if not fastcgi_header: break + if len(fastcgi_header) != 8: + yield fastcgi_header + break + fast_pack = struct.unpack(FCGI_Header, fastcgi_header) + if fast_pack[1] == 3: break + tlen = fast_pack[3] + while tlen > 0: + sd = sock.recv(tlen) + if not sd: break + tlen -= len(sd) + if sd: + yield sd + + if fast_pack[4]: + sock.recv(fast_pack[4]) + sock.close() + + + + +class FPM(object): + def __init__(self,sock=None, document_root='',last_path = ''): + ''' + @name 实例化FPM对象 + @author hwliang<2020-07-11> + @param sock string(unixsocket路径) + @param document_root string(PHP文档根目录) + @return FPM + ''' + if sock: + self.fcgi_sock = sock + if document_root[-1:] != '/': + document_root += '/' + self.document_root = document_root + self.last_path = last_path + + def load_url(self, url, content=b''): + ''' + @name 转发URL到PHP-FPM + @author hwliang<2020-07-11> + @param url string(URI地址) + @param content stream(POST数据io对象) + @return fastcgi-socket + ''' + fcgi = fcgi_client.FCGIApp(connect=self.fcgi_sock) + try: + script_name, query_string = url.split('?') + except ValueError: + script_name = url + query_string = '' + from BTPanel import request + env = { + 'SCRIPT_FILENAME': '%s%s' % (self.document_root, script_name), + 'QUERY_STRING': query_string, + 'REQUEST_METHOD': request.method, + 'SCRIPT_NAME': self.last_path + script_name, + 'REQUEST_URI': self.last_path + url, + 'GATEWAY_INTERFACE': 'CGI/1.1', + 'SERVER_SOFTWARE': 'BT-Panel', + 'REDIRECT_STATUS': '200', + 'CONTENT_TYPE': request.headers.get('Content-Type','application/x-www-form-urlencoded'), + 'CONTENT_LENGTH': str(request.headers.get('Content-Length','0')), + 'DOCUMENT_URI': request.path, + 'DOCUMENT_ROOT': self.document_root, + 'SERVER_PROTOCOL' : 'HTTP/1.1', + 'REMOTE_ADDR': request.remote_addr.replace('::ffff:',''), + 'REMOTE_PORT': str(request.environ.get('REMOTE_PORT')), + 'SERVER_ADDR': request.headers.get('host'), + 'SERVER_PORT': '80', + 'SERVER_NAME': 'BT-Panel', + } + + for k in request.headers.keys(): + key = 'HTTP_' + k.replace('-','_').upper() + env[key] = request.headers[k] + fpm_sock = fcgi(env, content) + return fpm_sock + + def load_url_public(self,url,content = b'',method='GET',content_type='application/x-www-form-urlencoded'): + ''' + @name 转发URL到PHP-FPM 公共 + @author hwliang<2020-07-11> + @param url string(URI地址) + @param content stream(POST数据io对象) + @return fastcgi-socket + ''' + fcgi = fcgi_client.FCGIApp(connect=self.fcgi_sock) + try: + script_name, query_string = url.split('?') + except ValueError: + script_name = url + query_string = '' + + content_length = len(content) + if content: + content = StringIO(content) + + env = { + 'SCRIPT_FILENAME': '%s%s' % (self.document_root, script_name), + 'QUERY_STRING': query_string, + 'REQUEST_METHOD': method, + 'SCRIPT_NAME': self.last_path + script_name, + 'REQUEST_URI': url, + 'GATEWAY_INTERFACE': 'CGI/1.1', + 'SERVER_SOFTWARE': 'BT-Panel', + 'REDIRECT_STATUS': '200', + 'CONTENT_TYPE': content_type, + 'CONTENT_LENGTH': str(content_length), + 'DOCUMENT_URI': script_name, + 'DOCUMENT_ROOT': self.document_root, + 'SERVER_PROTOCOL' : 'HTTP/1.1', + 'REMOTE_ADDR': '127.0.0.1', + 'REMOTE_PORT': '8888', + 'SERVER_ADDR': '127.0.0.1', + 'SERVER_PORT': '80', + 'SERVER_NAME': 'BT-Panel' + } + + fpm_sock = fcgi(env, content) + _data = b'' + while True: + fastcgi_header = fpm_sock.recv(8) + if not fastcgi_header: break + if len(fastcgi_header) != 8: + _data += fastcgi_header + break + fast_pack = struct.unpack(FCGI_Header, fastcgi_header) + if fast_pack[1] == 3: break + tlen = fast_pack[3] + while tlen > 0: + sd = fpm_sock.recv(tlen) + if not sd: break + tlen -= len(sd) + _data += sd + if fast_pack[4]: + fpm_sock.recv(fast_pack[4]) + status,headers,data = panelPHP().format_header_data(_data) + return data diff --git a/class_v2/panel_ping_v2.py b/class_v2/panel_ping_v2.py new file mode 100644 index 00000000..dee5f39f --- /dev/null +++ b/class_v2/panel_ping_v2.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +#coding:utf-8 +import os, sys, socket, struct, select, time + +def checksum(source_string): + sum = 0 + countTo = (len(source_string)/2)*2 + count = 0 + while count> 16) + (sum & 0xffff) + sum = sum + (sum >> 16) + answer = ~sum + answer = answer & 0xffff + answer = answer >> 8 | (answer << 8 & 0xff00) + return answer + +def receive_one_ping(my_socket, ID, timeout): + timeLeft = timeout + while True: + startedSelect = time.time() + whatReady = select.select([my_socket], [], [], timeLeft) + howLongInSelect = (time.time() - startedSelect) + if whatReady[0] == []: return + timeReceived = time.time() + recPacket, addr = my_socket.recvfrom(1024) + icmpHeader = recPacket[20:28] + type, code, checksum, packetID, sequence = struct.unpack("bbHHh", icmpHeader) + if packetID == ID: + bytesInDouble = struct.calcsize("d") + timeSent = struct.unpack("d", recPacket[28:28 + bytesInDouble])[0] + return timeReceived - timeSent + timeLeft = timeLeft - howLongInSelect + if timeLeft <= 0: return + +def send_one_ping(my_socket, dest_addr, ID): + dest_addr = socket.gethostbyname(dest_addr) + my_checksum = 0 + ICMP_ECHO_REQUEST = 8 + header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, ID, 1) #压包 + bytesInDouble = struct.calcsize("d") + data = (192 - bytesInDouble) * "Q" + data = struct.pack("d", time.time()) + data + my_checksum = checksum(header + data) + header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), ID, 1) + packet = header + data + my_socket.sendto(packet, (dest_addr, 1)) # Don't know about the 1 + +def do_one(dest_addr, timeout): + icmp = socket.getprotobyname("icmp") + try: + my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp) + except socket.error, (errno, msg): + if errno == 1: + msg = msg + ( + " - Note that ICMP messages can only be sent from processes" + " running as root." + ) + raise socket.error(msg) + raise # raise the original error + + my_ID = os.getpid() & 0xFFFF + send_one_ping(my_socket, dest_addr, my_ID) + delay = receive_one_ping(my_socket, my_ID, timeout) + my_socket.close() + return delay + +def get_ping(timeout = 0.5): + try: + delay = do_one(dest_addr, timeout) + except socket.gaierror, e: + return -2 + + if delay == None: + return -1 + else: + delay = delay * 1000 + return delay + +if __name__ == '__main__': + print verbose_ping(sys.argv[1],2) diff --git a/class_v2/panel_plugin_v2.py b/class_v2/panel_plugin_v2.py new file mode 100644 index 00000000..52a0d4a6 --- /dev/null +++ b/class_v2/panel_plugin_v2.py @@ -0,0 +1,2849 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- +import public +import os +import sys +import json +import time +import psutil +import re +import shutil +import requests +from BTPanel import session, cache, send_file + + +if sys.version_info[0] == 3: from importlib import reload + + +class mget: pass + + +class panelPlugin: + __list = 'data/list.json' + __type = 'data/type.json' + __index = 'config/index.json' + __link = 'config/link.json' + + __official_url = 'https://www.aapanel.com' + # __official_url = 'http://dev.aapanel.com' + + def __init__(self): + self.__isTable = None + self.__tasks = None + self.__product_list = None + self.__plugin_list = None + self.__exists_names = {} + self.__plugin_s_list = [] + self.__plugin_info = None + self.__plugin_name = None + self.__plugin_object = None + self.__plugin_list = None + self.__panel_path = '/www/server/panel' + self.__plugin_path = self.__panel_path + '/plugin/' + self.__plugin_save_file = self.__panel_path + '/data/plugin_bin.pl' + self.__api_root_url = self.__official_url + '/api' + self.__api_url = self.__api_root_url + '/panel/get_plugin_list' + self.__download_url = self.__api_root_url + '/panel/download_plugin' + self.__download_d_main_url = self.__api_root_url + '/panel/download_plugin_main' + self._check_url = self.__api_root_url + '/panel/get_soft_list_status' + self._unbinding_url = self.__api_root_url + '/panel/get_unbinding' + self.__tmp_path = self.__panel_path + '/temp/' + self.__plugin_timeout = 3600 + self.__is_php = False + self.__install_opt = 'i' + self.__pid = 0 + self.__path_error = self.__panel_path + '/data/error_pl.pl' + self.__error_html = '/www/server/panel/BTPanel/templates/default/block_error.html' + self.__sub_rules = [] + self.__replace_rule = [] + + self.pids = None + self.ROWS = 15 + + self.__install_path = '/www/server/panel/plugin' + + if not self.__tasks: + try: + self.__tasks = public.M('tasks').where("status!=?", ('1',)).field('status,name').select() + except: + self.__tasks = [] + + if not os.path.exists(self.__tmp_path): + os.makedirs(self.__tmp_path, 0o755) + + # 检查依赖 + def check_deps(self,get): + cacheKey = 'plugin_lib_list' + if not 'force' in get: + libList = cache.get(cacheKey) + if libList: return libList + libList = json.loads(public.readFile('config/lib.json')) + centos = os.path.exists('/bin/yum') + for key in libList.keys(): + for i in range(len(libList[key])): + checks = libList[key][i]['check'].split(',') + libList[key][i]['status'] = False + for check in checks: + if os.path.exists(check): + libList[key][i]['status'] = True + break + libList[key][i]['version'] = "-" + if libList[key][i]['status']: + shellTmp = libList[key][i]['getv'].split(':D') + shellEx = shellTmp[0] + if len(shellTmp) > 1 and not centos: shellEx = shellTmp[1] + libList[key][i]['version'] = public.ExecShell(shellEx)[0].strip() + cache.set(cacheKey,libList,86400) + return libList + + #检测关键目录是否可以被写入文件 + def check_sys_write(self): + test_file = '/etc/init.d/bt_10000100.pl' + public.writeFile(test_file,'True') + if os.path.exists(test_file): + if public.readFile(test_file) == 'True': + os.remove(test_file) + return True + os.remove(test_file) + return False + + #检查互斥 + def check_mutex(self,mutex): + if mutex == -1: return True + mutexs = mutex.split(',') + for name in mutexs: + pluginInfo = self.get_soft_find(name) + if not pluginInfo: continue + if pluginInfo['setup'] == True: + self.mutex_title = pluginInfo['title'] + return False + return True + + #检查依赖 + def check_dependent(self,dependent): + if not dependent: return True + dependents = dependent.split(',') + status = True + for dep in dependents: + if not dep: continue + if dep.find('|') != -1: + names = dep.split('|') + for name in names: + pluginInfo = self.get_soft_find(name) + if not pluginInfo: return True + if pluginInfo['setup'] == True: + status = True + break + else: + status = False + else: + pluginInfo = self.get_soft_find(dep) + if pluginInfo['setup'] != True: + status = False + break + return status + + #检查CPU限制 + def check_cpu_limit(self,cpuLimit): + if psutil.cpu_count() < cpuLimit: return False + return True + + #检查内存限制 + def check_mem_limit(self,memLimit): + if psutil.virtual_memory().total/1024/1024 < memLimit: return False + return True + + #检查操作系统限制 + def check_os_limit(self,osLimit): + if osLimit == 0: return True + if osLimit == 1: + centos = os.path.exists('/usr/bin/yum') + return centos + elif osLimit == 2: + debian = os.path.exists('/usr/bin/apt-get') + return debian + return True + + # 检查安装限制 + def check_install_limit(self,get): + if not hasattr(get,'pluginInfo'): + pluginInfo = self.get_soft_find(get.sName) + else: + pluginInfo = get.pluginInfo + p_node = '/www/server/panel/install/public.sh' + if os.path.exists(p_node): + if len(public.readFile(p_node)) < 100: os.remove(p_node) + if not pluginInfo: return public.return_msg_gettext(False, 'The specified plugin does not exist!') + self.mutex_title = pluginInfo['mutex'] + if not self.check_mutex(pluginInfo['mutex']): return public.return_msg_gettext(False, 'Please uninstall [{}] first', + (self.mutex_title,)) + if not hasattr(get, 'id'): + if not self.check_dependent(pluginInfo['dependent']): return public.return_msg_gettext(False, 'Depends on the following software, please install [{}] first', + (pluginInfo['dependent'],)) + if 'version' in get: + for versionInfo in pluginInfo['versions']: + if versionInfo['m_version'] != get.version: continue + if not 'type' in get: get.type = '0' + if int(get.type) > 4: get.type = '0' + if get.type == '0': + if not self.check_cpu_limit(versionInfo['cpu_limit']): + return public.return_msg_gettext(False,'At least [{0}] CPU cores are required to install'.format(versionInfo['cpu_limit'])) + if not self.check_mem_limit(versionInfo['mem_limit']): + return public.return_msg_gettext(False,'At least [{0} MB] memory is required to install'.format(versionInfo['mem_limit'])) + if not self.check_os_limit(versionInfo['os_limit']): + m_ps = {0: "All", 1: "Centos", 2: "Ubuntu/Debian"} + return public.return_msg_gettext(False, 'Only supports [{}] system',(m_ps[int(versionInfo['os_limit'])],)) + if not hasattr(get, 'id'): + if not self.check_dependent(versionInfo['dependent']): return public.return_msg_gettext(False,'Depend on the following software, please install first [{}]',(versionInfo['dependent'],)) + + # 获取插件安装包下载进度 + def get_download_speed(self, get): + ''' + @name 获取插件下载进度 + @author hwliang<2021-06-25> + @param plugin_name 插件名称 + @return dict + ''' + result = self.__get_download_speed(get.plugin_name) + return result + + # 取消下载 + def close_install(self, get): + ''' + @name 取消指定插件安装过程 + @author hwliang<2021-07-07> + @param plugin_name 插件名称 + @return void + ''' + plugin_name = get.plugin_name.strip() + tmp_path = '{}/{}'.format(self.__tmp_path, plugin_name) + if os.path.exists(tmp_path): shutil.rmtree(tmp_path) + return public.returnMsg(False, '安装过程已取消!') + + #安装插件 + def install_plugin(self,get): + str1 = public.get_msg_gettext("System critical directory is not writable!") + str2 = public.get_msg_gettext("1. If [System Hardening] is installed, please turn it off") + str3 = public.get_msg_gettext("2. If Yunsuo is installed, please turn off [System Hardening] feature") + str4 = public.get_msg_gettext("3. If Safedog is installed, please turn off [System Protection] feature") + str5 = public.get_msg_gettext("4. If other security software is used, please uninstall it") + if not self.check_sys_write(): return public.return_msg_gettext(False,'ERROR:{}
                                    {}

                                    {}
                                    {}

                                    '.format(str1,str2,str3,str4)) + if not 'sName' in get: return public.return_msg_gettext(False,'Please specify the software name!') + #处理ols还不支持php81的情况 + # if get.sName == "php-8.1" and public.get_webserver() == 'openlitespeed': + # return public.return_msg_gettext(False, 'Sorry, currently OLS official does not support php8.1') + pluginInfo = self.get_soft_find(get.sName) + get.pluginInfo = pluginInfo + check_result = self.check_install_limit(get) + if check_result: + return check_result + if pluginInfo['name'] in ['dns_manager','mail_sys']: + pluginInfo['type'] = 5 + + if pluginInfo['type'] != 5: + result = self.install_sync(pluginInfo,get) + else: + result = self.install_async(pluginInfo,get) + try: + if 'status' in result: + if result['status']: + public.arequests('post','{}/api/setupCount/setupPlugin'.format(self.__official_url),data={"pid":pluginInfo['id'],'p_name':pluginInfo['name']},timeout=3) + # get.force = 1 + # self.get_cloud_list(get) + except:pass + return result + + #同步安装 + def install_sync(self,pluginInfo,get): + import panelAuth + try: + token = panelAuth.panelAuth().create_serverid(None)['token'] + except: + # return public.returnMsg(False,'Please log in as aaPanel account first') + token = None + if 'download' in pluginInfo['versions'][0]: + tmp_path = '/www/server/panel/temp' + if not os.path.exists(tmp_path): os.makedirs(tmp_path,mode=384) + public.ExecShell("rm -rf " + tmp_path + '/*') + toFile = tmp_path + '/' + pluginInfo['name'] + '.zip' + public.downloadFile('{}/api/plugin/download?filename={}&token={}'.format( + self.__official_url, + pluginInfo['versions'][0]['download'], + token + ),toFile) + if public.FileMd5(toFile) != pluginInfo['versions'][0]['md5']: return public.return_msg_gettext(False,'File hash verification failed, stop installation!') + update = False + if os.path.exists(pluginInfo['install_checks']): update =pluginInfo['versions'][0]['version_msg'] + return self.update_zip(None,toFile,update) + else: + # download_url = public.get_url() + '/install/plugin/' + pluginInfo['name'] + '_en/install.sh' + # toFile = '/tmp/%s.sh' % pluginInfo['name'] + # public.downloadFile(download_url,toFile) + # self.set_pyenv(toFile) + # public.ExecShell('/bin/bash ' + toFile + ' install &> /tmp/panelShell.pl') + # if os.path.exists(pluginInfo['install_checks']): + # public.write_log_gettext('Installer','Successfully installed plugin [{}]',(pluginInfo['title'],)) + # if os.path.exists(toFile): os.remove(toFile) + # return public.return_msg_gettext(True,'Installation succeeded!') + # return public.return_msg_gettext(False,'Installation failed') + + if hasattr(get, 'min_version'): + get.version += '.' + get.min_version + + return self.__install_plugin(pluginInfo['name'], get.version) + + # 设置Python环境变量 + def set_pyenv(self, filename): + if not os.path.exists(filename): return False + env_py = '/www/server/panel/pyenv/bin' + if not os.path.exists(env_py): return False + temp_file = public.readFile(filename) + env_path = ['PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin'] + rep_path = ['PATH={}/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin'.format(env_py + ":")] + for i in range(len(env_path)): + temp_file = temp_file.replace(env_path[i], rep_path[i]) + public.writeFile(filename, temp_file) + return True + + #异步安装 + def install_async(self,pluginInfo,get): + # # 只取主版本号与子版本号,忽略修订号 + # if 'version' in get: + # get.version = '.'.join(str(get.version).split('.')[:2]) + + mtype = 'install'; + mmsg = public.get_msg_gettext('Install') + if hasattr(get, 'upgrade'): + mtype = 'update' + mmsg = 'upgrade' + if not 'type' in get: get.type = '0' + if int(get.type) > 4: get.type = '0' + if get.sName == 'nginx': + if get.version == '1.8': return public.return_msg_gettext(False,'Nginx 1.8.1 is too old, no longer available, please choose another version!') + if get.sName.find('php-') != -1:get.sName = get.sName.split('-')[0] + ols_execstr = "" + if "php" == get.sName and os.path.exists('/usr/local/lsws/bin/lswsctrl'): + ols_sName = 'php-ols' + ols_version = get.version.replace('.','') + ols_execstr = " &> /tmp/panelExec.log && /bin/bash install_soft.sh {} {} " + ols_sName + " " + ols_version + php_path = '/www/server/php' + if not os.path.exists(php_path): os.makedirs(php_path) + apacheVersion='false' + if public.get_webserver() == 'apache': + apacheVersion = public.xss_version(public.readFile('/www/server/apache/version.pl')) + public.writeFile('/var/bt_apacheVersion.pl',apacheVersion) + public.writeFile('/var/bt_setupPath.conf','/www') + if os.path.exists('/usr/bin/apt-get'): + if get.type == '0': + get.type = '3' + else: + get.type = '4' + if ols_execstr: + ols_execstr = ols_execstr.format(get.type,mtype) + execstr = "cd /www/server/panel/install && /bin/bash install_soft.sh {} {} {} {} {}".format( + get.type, mtype, get.sName, get.version, ols_execstr) + if get.sName == "phpmyadmin": + execstr += "&> /tmp/panelExec.log" + if public.get_webserver() == 'openlitespeed': + execstr += " && sleep 1 && /usr/local/lsws/bin/lswsctrl restart" + # 清理日志文件 + if os.path.exists("/tmp/panelExec.log"): + public.writeFile("/tmp/panelExec.log","") + public.M('tasks').add('id,name,type,status,addtime,execstr',(None, mmsg + '['+get.sName+'-'+get.version+']','execshell','0',time.strftime('%Y-%m-%d %H:%M:%S'),execstr)) + cache.delete('install_task') + public.writeFile('/tmp/panelTask.pl','True') + public.write_log_gettext('Installer','Successfully added intallation task [{}-{}]',(get.sName,get.version)) + return public.return_msg_gettext(True,'Installation task added to queue') + + #卸载插件 + def uninstall_plugin(self,get): + pluginInfo = self.get_soft_find(get.sName) + if not pluginInfo: return public.return_msg_gettext(False,'The specified plugin does not exist!') + if pluginInfo['type'] != 5: + pluginPath = self.__install_path + '/' + pluginInfo['name'] + if pluginInfo['type'] != 6: + download_url = session['download_url'] + '/install/plugin/' + pluginInfo['name'] + '_en/install.sh' + toFile = '/tmp/%s.sh' % pluginInfo['name'] + public.downloadFile(download_url,toFile) + self.set_pyenv(toFile) + if os.path.exists(toFile): + if os.path.getsize(toFile) > 100: + public.ExecShell('/bin/bash ' + toFile + ' uninstall') + + if os.path.exists(pluginPath + '/install.sh'): + self.set_pyenv(pluginPath + '/install.sh') + public.ExecShell('/bin/bash ' + pluginPath + '/install.sh uninstall') + + if os.path.exists(pluginPath): public.ExecShell('rm -rf ' + pluginPath) + public.write_log_gettext('Installer','Successfully uninstalled software [{}]',(pluginInfo['title'],)) + return public.return_msg_gettext(True,'Uninstallaton succeeded') + else: + + if pluginInfo['name'] == 'mysql': + if public.M('databases').where('db_type=?',0).count() > 0: return public.return_msg_gettext(False,'The database list is not empty. For your data security, please backup and delete the existing database.
                                    Forced uninstall command: rm -rf /www/server/mysql') + if pluginInfo['name'] == 'nginx': + import nginx + nginx.nginx().del_all_log_format(get) + if pluginInfo['name'] == 'apache': + import apache + apache.apache().del_all_log_format(get) + get.type = '0' + if session['server_os']['x'] != 'RHEL': get.type = '3' + get.sName = get.sName.lower() + if get.sName.find('php-') != -1: + get.sName = get.sName.split('-')[0] + execstr = "cd /www/server/panel/install && /bin/bash install_soft.sh "+get.type+" uninstall " + get.sName.lower() + " "+ get.version.replace('.','') + public.ExecShell(execstr) + public.write_log_gettext('Installer','Successfully unintalled [{}-{}]',(get.sName,get.version)) + return public.return_msg_gettext(True,"Uninstallation succeeded") + + #从云端取列表 + def get_cloud_list(self, get=None): + force = 0 + if get and hasattr(get, 'force'): + force = int(get.force) + + if 'focre_cloud' in session: + if session['focre_cloud']: + force = 1 + session['focre_cloud'] = False + + if 'init_cloud' not in session: + force = 1 + session['init_cloud'] = True + + softList = public.load_soft_list(True if force == 1 else False) + + if get and 'init' in get: + if softList: + if 'success' not in softList: + return softList + + if force > 0: + public.ExecShell('rm -f /tmp/bmac_*') + public.run_thread(self.getCloudPHPExt) + # 专业版和企业版到期提醒,aaPanel目前没有先注释 + # self.expire_msg(softList) + + try: + p_token = cache.get('p_token') + + if p_token is None: + p_token = 'bmac_' + public.Md5(public.get_mac_address()) + cache.set('p_token', p_token) + + public.writeFile("/tmp/" + p_token, str(softList['pro'])) + public.writeFile('/tmp/{}.time'.format(p_token), str(int(time.time()))) + except: + pass + + sType = 0 + try: + if hasattr(get,'type'): sType = int(get['type']) + if hasattr(get,'query'): + if get.query: + # 关键词统计 参数keyword + import panelAuth + import requests + countUrl = '{}/api/panel/submit_keyword'.format(self.__official_url) + pdata = panelAuth.panelAuth().create_serverid(None) + url_headers = {} + if 'token' in pdata: + url_headers = {"authorization": "bt {}".format(pdata['token'])} + pdata['environment_info'] = json.dumps(public.fetch_env_info()) + keyword = { + "keyword": get.query + } + try: + requests.post(countUrl, params=keyword, headers=url_headers, verify=False, timeout=3) + except: + pass + sType = 0 + except:pass + + # 扫描本地插件并追加到软件列表中 + softList['list'] = self.get_local_plugin(softList['list']) + + # 软件列表分类处理 + softList['list'] = self.get_types(softList['list'], sType) + + if hasattr(get, 'query'): + if get.query: + get.query = get.query.lower() + tmpList = [] + for softInfo in softList['list']: + if softInfo['name'].lower().find(get.query) != -1 or \ + softInfo['title'].lower().find(get.query) != -1 or \ + softInfo['ps'].lower().find(get.query) != -1: + tmpList.append(softInfo) + softList['list'] = tmpList + + for softInfo in softList['list']: + if 'uninsatll_checks' not in softInfo: + softInfo['uninsatll_checks'] = softInfo['uninstall_checks'] + + return softList + + #取提醒标记 + def get_level_msg(self,level,s_time,endtime): + ''' + level 提醒标记 + s_time 当前时间戳 + endtime 到期时间戳 + ''' + expire_day = (endtime - s_time) / 86400 + if expire_day < 15 and expire_day > 7: + level = level + '15' + elif expire_day < 7 and expire_day > 3: + level = level + '7' + elif expire_day < 3 and expire_day > 0: + level = level + '3' + return level,expire_day + + #添加到期提醒 + def add_expire_msg(self,title,level,name,expire_day,pid,endtime): + ''' + title 软件标题 + level 提醒标记 + name 软件名称 + expire_day 剩余天数 + ''' + import panelMessage #引用消息提醒模块 + pm = panelMessage.panelMessage() + pm.remove_message_level(level) #删除旧的提醒 + if expire_day > 15: return False + if pm.is_level(level): #是否忽略 + if level != name: #到期还是即将到期 + msg_last = '您的【{}】授权还有{}天到期'.format(title,int(expire_day) + 1) + else: + msg_last = '您的【{}】授权已到期'.format(title) + pl_msg = 'true' + if name in ['pro','ltd']: + pl_msg = 'false' + renew_msg = '立即续费' % (title,pid,name,pl_msg,endtime) + pm.create_message(level=level,expire=7,msg="{},为了不影响您正常使用【{}】功能,请及时续费,{}".format(msg_last,title,renew_msg)) + return True + return False + + #到期提醒 + def expire_msg(self,data): + ''' + data 插件列表 + ''' + s_time = time.time() + is_plugin = True + import panelMessage #引用消息提醒模块 + pm = panelMessage.panelMessage() + #企业版到期提醒 + if not data['ltd'] in [-1] : + + if data['pro'] < 0 or (data['pro'] - s_time) / 86400 < 15 : + level,expire_day = self.get_level_msg('ltd',s_time,data['ltd']) + print(level,expire_day) + self.add_expire_msg('企业版',level,'ltd',expire_day,100000046,data['ltd']) + pm.remove_message_level('pro') + return True + + #专业版到期提醒 + if not data['pro'] in [-1,0]: + level,expire_day = self.get_level_msg('pro',s_time,data['pro']) + self.add_expire_msg('专业版',level,'pro',expire_day,100000030,data['pro']) + pm.remove_message_level('ltd') + is_plugin = False + + return True + + + #提交用户评分 + def set_score(self,args): + try: + import panelAuth + pdata = panelAuth.panelAuth().create_serverid(None) + pdata['ps'] = args.ps + pdata['num'] = int(args.num) + pdata['pid'] = int(args.pid) + if 1< pdata['num'] >5: return public.return_msg_gettext(False,'Scoring range [1-5]') + if not pdata['pid']: return public.return_msg_gettext(False,'The specified plugin does not exist!') + + result = public.httpPost(public.GetConfigValue('home') + '/api/panel/plugin_score',pdata,10) + result = json.loads(result) + return result + except: + return public.return_msg_gettext(False,'Connection failure!') + + #获取指定插件评分 + def get_score(self,args): + try: + import panelAuth + pdata = panelAuth.panelAuth().create_serverid(None) + pdata['pid'] = int(args.pid) + if not pdata['pid']: return [] + u_args = "" + sp_tip = '?' + if 'p' in args: + u_args += sp_tip + 'p=' + args.p + sp_tip = '&' + if 'tojs' in args: + u_args += sp_tip + 'tojs='+ args.tojs + sp_tip = '&' + if 'limit_num' in args: + pdata['limit_num'] = int(args.limit_num) + result = public.httpPost(public.GetConfigValue('home') + '/api/panel/get_plugin_socre' + u_args,pdata,10) + result = json.loads(result) + return result + except: + return public.return_msg_gettext(False,'Connection failure!') + + #清除多余面板日志 + def clean_panel_log(self): + try: + log_path = 'logs/request' + if not os.path.exists(log_path): return False + limit_num = 180 + p_logs = sorted(os.listdir(log_path)) + num = len(p_logs) - limit_num + if num > 0: + for i in range(num): + filename = log_path + '/' + p_logs[i] + if not os.path.exists(filename): continue + os.remove(filename) + + today = public.getDate(format='%Y-%m-%d') + for fname in os.listdir(log_path): + fsplit = fname.split('.') + if fsplit[-1] != 'json': + continue + if fsplit[0] == today: + continue + public.ExecShell("cd {} && gzip {}".format(log_path,fname)) + + #清理错误日志 + public.clean_max_log('/www/server/panel/logs/error.log',10,20) + public.clean_max_log('/www/server/panel/logs/socks5.log',10,20) + public.clean_max_log('/www/server/panel/logs/oos.log',10,20) + return True + except:return False + + #取本地插件 + def get_local_plugin(self,sList): + for name in os.listdir('plugin/'): + isExists = False + for softInfo in sList: + if name == softInfo['name']: + isExists = True + break + if isExists: continue + filename = 'plugin/' + name + '/info.json' + if not os.path.exists(filename): continue + tmpInfo = public.ReadFile(filename).strip() + if not tmpInfo: continue + try: + info = json.loads(tmpInfo) + except: continue + pluginInfo = self.get_local_plugin_info(info) + if not pluginInfo: continue + sList.append(pluginInfo) + return sList + + #检查是否正在安装 + def check_setup_task(self,sName): + if not self.__tasks: + self.__tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select() + if sName.find('php-') != -1: + tmp = sName.split('-') + sName = tmp[0] + version = tmp[1] + isTask = '1' + for task in self.__tasks: + tmpt = public.getStrBetween('[',']',task['name']) + if not tmpt:continue + tmp1 = tmpt.split('-') + name1 = tmp1[0].lower() + if sName == 'php': + if name1 != sName or tmp1[1] != version: continue + isTask = task['status'] + else: + if name1 == 'pure': name1 = 'pure-ftpd' + if name1 != sName: continue + isTask = task['status'] + + if isTask == '-1' or isTask == '0': + if task['name'].find('upgrade') != -1: isTask = '-2' + break + return isTask + + + #构造本地插件信息 + def get_local_plugin_info(self,info): + m_version = info['versions'].split(".") + if len(m_version) < 2: return None + if len(m_version) > 2: + tmp = m_version[:] + del(tmp[0]) + m_version[1] = '.'.join(tmp) + + try: + if not 'author' in info: info['author'] = '未知' + if not 'home' in info: info['home'] = '#' + pluginInfo = { + "id": 10000, + "pid": 0, + "type": 10, + "price": 0, + "author":info['author'], + "home":info['home'], + "name": info['name'], + "title": info['title'], + "panel_pro": 1, + "panel_free": 1, + "panel_test": 1, + "ps": info['ps'], + "version": info['versions'], + "s_version": "0", + "manager_version": "1", + "c_manager_version": "1", + "dependent": "", + "mutex": "", + "install_checks": "/www/server/panel/plugin/" + info['name'], + "uninsatll_checks": "/www/server/panel/plugin/" + info['name'], + "compile_args": 0, + "version_coexist": 0, + "versions": [ + { + "m_version": m_version[0], + "version": m_version[1], + "dependent": "", + "mem_limit": 32, + "cpu_limit": 1, + "os_limit": 0, + "setup": True + } + ], + "setup": True, + "status": True + } + except: pluginInfo = None + return pluginInfo + + #处理分类 + def get_types(self,sList,sType): + if sType <= 0: return sList + sType = [sType] + # if sType != 12: + # sType = [sType] + # else: + # sType = [sType,8] + newList = [] + for sInfo in sList: + if int(sInfo['type']) in sType: newList.append(sInfo) + return newList + + #检查权限 + def check_accept(self,get): + args = public.dict_obj() + args.type = '8' + p_list = self.get_cloud_list(args) + for p in p_list['list']: + if p['name'] == get.name: + if int(p_list['pro']) < 0 and int(p['endtime']) < 0: return False + break + + args.type = '10' + p_list = self.get_cloud_list(args) + for p in p_list['list']: + if p['name'] == get.name: + if not 'endtime' in p: continue + if int(p['endtime']) < 0: return False + break + + args.type = '12' + p_list = self.get_cloud_list(args) + for p in p_list['list']: + if not p['type'] in [12,'12']: continue + if p['name'] == get.name: + if not 'endtime' in p: continue + if int(p_list['ltd']) < 1 and int(p['endtime']) < 1: return False + break + return True + + #取软件列表 + def get_soft_list(self,get = None): + softList = self.get_cloud_list(get) + if not softList: + get.force = 1 + softList = self.get_cloud_list(get) + if not softList: return public.return_msg_gettext(False,'Failed to get software list ({})',"401") + softList['list'] = self.set_coexist(softList['list']) + if not 'type' in get: get.type = '0' + if get.type == '-1': + soft_list_tmp = [] + softList['list'] = self.check_isinstall(softList['list']) + for val in softList['list']: + if 'setup' in val: + if val['setup']: soft_list_tmp.append(val) + softList['list'] = soft_list_tmp + softList['list'] = self.get_page(softList['list'],get) + else: + softList['list'] = self.get_page(softList['list'],get) + softList['list']['data'] = self.check_isinstall(softList['list']['data']) + softList['apache22'] = False + softList['apache24'] = False + check_version_path = '/www/server/apache/version_check.pl' + if os.path.exists(check_version_path): + softList['apache24'] = True + if public.readFile(check_version_path).find('2.2') == 0: + softList['apache22'] = True + softList['apache24'] = False + if os.path.exists('/www/server/nginx/conf/nginx.conf'): + import one_key_wp + one_key_wp.fast_cgi().set_nginx_conf() + one_key_wp.fast_cgi().set_nginx_init() + public.ExecShell("/etc/init.d/nginx start") + return softList + + #取首页软件列表 + def get_index_list(self, get=None): + softList = self.get_cloud_list(get)['list'] + if not softList: + get.force = 1 + softList = self.get_cloud_list(get)['list'] + if not softList: return public.return_msg_gettext(False,'Failed to get software list ({})',"401") + softList = self.set_coexist(softList) + if not os.path.exists(self.__index): public.writeFile(self.__index,'[]') + try: + indexList = json.loads(public.ReadFile(self.__index)) + except Exception: + os.remove(self.__index) + public.writeFile(self.__index, '[]') + indexList = [] + dataList = [] + for index in indexList: + for softInfo in softList: + if softInfo['name'] == index: dataList.append(softInfo) + dataList = self.check_isinstall(dataList) + + title_has_version_reg = re.compile(r'-\d+(?:\.\d+)+$') + + # 过滤软件列表 + ret = [] + for item in dataList: + if not item.get('setup', False): + continue + + item['title'] = title_has_version_reg.sub('', item['title']) + + ret.append(item) + + return ret + + #添加到首页 + def add_index(self,get): + sName = get.sName + if not os.path.exists(self.__index): public.writeFile(self.__index,'[]') + indexList = json.loads(public.ReadFile(self.__index)) + if sName in indexList: return public.return_msg_gettext(False,'Please do NOT repeat adding') + if len(indexList) >= 12: + softList = self.get_cloud_list(get)['list'] + softList = self.set_coexist(softList) + for softInfo in softList: + # return softList + if softInfo['name'] == 'php': + for i in softInfo['versions']: + php_v = 'php-'+ i['m_version'] + if not os.path.exists('/www/server/php/{}'.format(i['m_version']))\ + and php_v in indexList: + indexList.remove(php_v) + if softInfo['name'] in indexList: + new_softInfo = self.check_status(softInfo) + if not new_softInfo['setup']: indexList.remove(softInfo['name']) + public.writeFile(self.__index,json.dumps(indexList)) + if len(indexList) >= 12: return public.return_msg_gettext(False,'Dashboard only display up to 12 software!') + indexList.append(sName) + public.writeFile(self.__index,json.dumps(indexList)) + return public.return_msg_gettext(True,'Setup successfully!') + + #删除首页 + def remove_index(self,get): + sName = get.sName + indexList = [] + if not os.path.exists(self.__index): public.writeFile(self.__index,'[]') + indexList = json.loads(public.ReadFile(self.__index)) + if not sName in indexList: return public.return_msg_gettext(True,'Successfully deleted!') + indexList.remove(sName) + public.writeFile(self.__index,json.dumps(indexList)) + return public.return_msg_gettext(True,'Successfully deleted!') + + #设置排序 + def sort_index(self,get): + indexList = get.ssort.split('|') + public.writeFile(self.__index,json.dumps(indexList)) + return public.return_msg_gettext(True,'Setup successfully!') + + #取快捷软件列表 + def get_link_list(self,get=None): + softList = self.get_cloud_list(get)['list'] + softList = self.set_coexist(softList) + indexList = json.loads(public.ReadFile(self.__link)) + dataList = [] + for index in indexList: + for softInfo in softList: + if softInfo['name'] == index: dataList.append(softInfo) + dataList = self.check_isinstall(dataList) + return dataList + + #添加到快捷栏 + def add_link(self,get): + sName = get.sName + indexList = json.loads(public.ReadFile(self.__link)) + if sName in indexList: return public.return_msg_gettext(False,'Please do NOT repeat adding') + if len(indexList) >= 5: return public.return_msg_gettext(False,'Shortcut Bar only display up to 5 software!') + indexList.append(sName) + public.writeFile(self.__link,json.dumps(indexList)) + return public.return_msg_gettext(True,'Setup successfully!') + + #删除快捷栏 + def remove_link(self,get): + sName = get.sName + indexList = [] + indexList = json.loads(public.ReadFile(self.__link)) + if sName in indexList: return public.return_msg_gettext(True,'Successfully deleted!') + indexList.remove(sName) + public.writeFile(self.__link,json.dumps(indexList)) + return public.return_msg_gettext(True,'Successfully deleted!') + + #设置快捷栏排序 + def sort_link(self,get): + indexList = get.ssort.split('|') + public.writeFile(self.__link,json.dumps(indexList)) + return public.return_msg_gettext(True,'Setup successfully!') + + + + #处理共存软件 + def set_coexist(self,sList): + softList = [] + for sInfo in sList: + try: + if sInfo['version_coexist'] == 1 and 'versions' in sInfo: + for versionA in sInfo['versions']: + try: + sTmp = sInfo.copy() + v = versionA['m_version'].replace('.','') + sTmp['title'] = sTmp['title']+'-'+versionA['m_version'] + sTmp['name'] = sTmp['name']+'-'+versionA['m_version'] + sTmp['version'] = sTmp['version'].replace('{VERSION}',v) + sTmp['manager_version'] = sTmp['manager_version'].replace('{VERSION}',v) + sTmp['install_checks'] = sTmp['install_checks'].replace('{VERSION}',v) + if 'uninsatll_checks' not in sTmp: + sTmp['uninsatll_checks'] = sTmp['uninstall_checks'].replace('{VERSION}',v) + else: + sTmp['uninsatll_checks'] = sTmp['uninsatll_checks'].replace('{VERSION}',v) + sTmp['s_version'] = sTmp['s_version'].replace('{VERSION}',v) + sTmp['versions'] = [] + sTmp['versions'].append(versionA) + softList.append(sTmp) + except: continue + else: + softList.append(sInfo) + except: continue + return softList + + #检测是否安装 + def check_isinstall(self,sList): + if not os.path.exists(self.__index): public.writeFile(self.__index,'[]') + indexList = json.loads(public.ReadFile(self.__index)) + for i in range(len(sList)): + sList[i]['index_display'] = sList[i]['name'] in indexList + sList[i] = self.check_status(sList[i]) + return sList + + + #检查软件状态 + def check_status(self,softInfo): + softInfo['setup'] = os.path.exists(softInfo['install_checks']) + softInfo['status'] = False + softInfo['task'] = self.check_setup_task(softInfo['name']) + if softInfo['name'].find('php-') != -1: softInfo['fpm'] = False + if softInfo['setup']: + softInfo['shell'] = softInfo['version'] + softInfo['version'] = self.get_version_info(softInfo) + softInfo['status'] = True + softInfo['versions'] = self.tips_version(softInfo['versions'],softInfo['version']) + softInfo['admin'] = os.path.exists('/www/server/panel/plugin/' + softInfo['name']) + + if 's_version' in softInfo and len(softInfo['s_version']) > 3: + pNames = softInfo['s_version'].split(',') + for pName in pNames: + if len(softInfo['manager_version']) > 5: + softInfo['status'] = self.process_exists(pName,softInfo['manager_version']) + else: + softInfo['status'] = self.process_exists(pName) + if softInfo['status']: break + else: + softInfo['version'] = "" + if softInfo['version_coexist'] == 1: + if softInfo['id'] != 10000: + self.get_icon(softInfo['name'].split('-')[0]) + else: + if 'min_image' in softInfo: + if softInfo['id'] != 10000: + self.get_icon(softInfo['name'],softInfo['min_image']) + else: + # if softInfo['id'] != 10000: + self.get_icon(softInfo['name']) + + if softInfo['name'].find('php-') != -1: + v2= softInfo['versions'][0]['m_version'].replace('.','') + softInfo['fpm'] = os.path.exists('/www/server/php/' + v2 + '/sbin/php-fpm') + softInfo['status'] = self.get_php_status(v2) + pid_file = '/www/server/php/' + v2 + '/var/run/php-fpm.pid' + if not softInfo['fpm']: + softInfo['status'] = True + elif softInfo['status'] and os.path.exists(pid_file): + try: + softInfo['status'] = public.pid_exists(int(public.readFile(pid_file))) + except: + if os.path.exists(pid_file): + os.remove(pid_file) + + if softInfo['name'] == 'mysql': + softInfo['status'] = self.process_exists('mysqld') + if not softInfo['status']: softInfo['status'] = self.process_exists('mariadbd') + if softInfo['name'] == 'phpmyadmin': softInfo['status'] = self.get_phpmyadmin_stat() + if softInfo['name'] == 'openlitespeed': + pid_file = '/run/openlitespeed.pid' + if os.path.exists(pid_file): + pid = int(public.readFile(pid_file)) + softInfo['status'] = public.pid_exists(pid) + return softInfo + + def get_php_status(self,phpversion): + ''' + @name 获取指定PHP版本的服务状态 + @author hwliang<2020-10-23> + @param phpversion string PHP版本 + @return bool + ''' + try: + php_status = os.path.exists('/tmp/php-cgi-'+phpversion+'.sock') + if php_status: return php_status + pid_file = '/www/server/php/{}/var/run/php-fpm.pid'.format(phpversion) + if not os.path.exists(pid_file): return False + pid = int(public.readFile(pid_file)) + return os.path.exists('/proc/{}/comm'.format(pid)) + except: + return False + + + + + #取phpmyadmin状态 + def get_phpmyadmin_stat(self): + webserver = public.get_webserver() + if webserver == 'nginx': + filename = public.GetConfigValue('setup_path') + '/nginx/conf/nginx.conf' + elif webserver == 'apache': + filename = public.GetConfigValue('setup_path') + '/apache/conf/extra/httpd-vhosts.conf' + else: + filename = "/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf" + if not os.path.exists(filename): return False + conf = public.readFile(filename) + if not conf: return False + is_start = conf.find('/www/server/stop') == -1 + if is_start: + if webserver == 'nginx': + is_start = conf.find('allow 127.0.0.1;') == -1 + elif webserver == 'apache': + is_start = conf.find('Allow from 127.0.0.1 ::1 localhost') == -1 + return is_start + + + #获取指定软件信息 + def get_soft_find(self,get = None): + if not self.__plugin_s_list: + softList = self.get_cloud_list(get)['list'] + self.__plugin_s_list = self.set_coexist(softList) + + try: + sName = get['sName'] + except: + sName = get + + for softInfo in self.__plugin_s_list: + + if softInfo['name'] == sName: + if sName == 'phpmyadmin': + # 检查是否需要开启SSL + if os.path.exists('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path())) and os.path.exists('{}/phpmyadmin'.format(public.get_setup_path())): + os.remove('{}/data/phpmyadmin_ssl.mark'.format(public.get_panel_path())) + from ajax_v2 import ajax + ajax().set_phpmyadmin_ssl(public.to_dict_obj({'v': '1'})) + + from BTPanel import get_phpmyadmin_dir + pmd = get_phpmyadmin_dir() + softInfo['ext'] = self.getPHPMyAdminStatus() + if softInfo['ext'] and pmd: + port = softInfo['ext']['ssl_port'] if softInfo['ext'].get('ssl_enabled', False) else pmd[1] + softInfo['ext']['url'] = 'http' + ('s' if softInfo['ext'].get('ssl_enabled', + False) else '') + '://' + public.GetHost() + ':' + port + '/' + \ + pmd[0] + if "php-" in sName: + v = softInfo["versions"][0]["m_version"] + v1 = v.replace(".", "") + if public.get_webserver() == "openlitespeed": + softInfo["php_ini"] = "/usr/local/lsws/lsphp{}/etc/php/{}/litespeed/php.ini".format(v1, v) + if os.path.exists("/etc/redhat-release"): + softInfo["php_ini"] = "/usr/local/lsws/lsphp{}/etc/php.ini".format(v1) + else: + softInfo["php_ini"] = "/www/server/php/{}/etc/php.ini".format(v1) + + return public.success_v2(self.check_status(softInfo)) + + return public.fail_v2('failed to get soft info') + + + #获取版本信息 + def get_version_info(self,sInfo): + version = '' + vFile1 = sInfo['uninsatll_checks'] + '/version_check.pl' + vFile2 = sInfo['uninsatll_checks'] + '/info.json' + if os.path.exists(vFile1): + version = public.xss_version(public.ReadFile(vFile1).strip()) + if not version: os.remove(vFile1) + elif os.path.exists(vFile2): + v_tmp = public.ReadFile(vFile2).strip() + if v_tmp: + try: + version = json.loads(v_tmp)['versions'] + except: public.ExecShell('rm -f ' + vFile2) + else: + version = "1.0" + else: + exec_args = { + 'nginx':"/www/server/nginx/sbin/nginx -v 2>&1|grep version|awk '{print $3}'|cut -f2 -d'/'", + 'apache':"/www/server/apache/bin/httpd -v|grep version|awk '{print $3}'|cut -f2 -d'/'", + 'mysql':"/www/server/mysql/bin/mysql -V|grep Ver|awk '{print $5}'|cut -f1 -d','", + 'php':"/www/server/php/{VERSION}/bin/php -v|grep cli|awk '{print $2}'", + 'pureftpd':"cat /www/server/pure-ftpd/version.pl", + 'phpmyadmin':"cat /www/server/phpmyadmin/version.pl", + 'tomcat':"/www/server/tomcat/bin/version.sh|grep version|awk '{print $4}'|cut -f2 -d'/'", + 'memcached':"/usr/local/memcached/bin/memcached -V|awk '{print $2}'", + 'redis':"/www/server/redis/src/redis-server -v|awk '{print $3}'|cut -f2 -d'='", + 'openlitespeed': "cat /usr/local/lsws/VERSION", + 'gitlab':'echo "8.8.5"' + } + + exec_str = '' + if sInfo['name'] in exec_args: exec_str = exec_args[sInfo['name']] + if sInfo['version_coexist'] == 1: + v_tmp = sInfo['name'].split('-') + exec_str = exec_args[v_tmp[0]].replace('{VERSION}',v_tmp[1].replace('.','')) + version = public.ExecShell(exec_str)[0].strip() + if version: + public.writeFile(vFile1,version) + else: + vFile4 = sInfo['uninsatll_checks'] + '/version.pl' + if os.path.exists(vFile4): + version = public.xss_version(public.readFile(vFile4).strip()) + + if sInfo['name'] == 'mysql': + vFile3 = sInfo['uninsatll_checks'] + '/version.pl' + version_str = None + if os.path.exists(vFile3): + version_str = public.xss_version(public.readFile(vFile3)) + if version_str.find('AliSQL') != -1: version = 'AliSQL' + if version == 'Linux' and version_str: + version = version_str + public.writeFile(vFile1,version) + + if sInfo['name'] == 'nginx': + if version.find('2.2.') != -1: version = '-Tengine' + version + return version.replace('p1','') + + + #标记当前安装的版本 + def tips_version(self,versions,version): + if len(versions) == 1: + versions[0]['setup'] = True + return versions + + for i in range(len(versions)): + if version == (versions[i]['m_version'] + '.' + versions[i]['version']): + versions[i]['setup'] = True + continue + vTmp = versions[i]['m_version'].split('_') + if len(vTmp) > 1: + vTmp = vTmp[1] + else: + vTmp = vTmp[0] + vLen = len(vTmp) + versions[i]['setup'] = (version[:vLen] == vTmp) + return versions + + #取pids + def get_pids(self): + pids = [] + for pid in os.listdir('/proc'): + if re.match(r"^\d+$",pid): pids.append(pid) + return pids + + + #进程是否存在 + def process_exists(self,pname,exe = None): + if pname in ['mysqld','mariadbd']: + datadir = public.get_datadir() + if datadir: + pid_file = "{}/{}.pid".format(datadir,public.get_hostname()) + if os.path.exists(pid_file): + try: + pid = int(public.readFile(pid_file)) + status = public.pid_exists(pid) + if status: return status + except: + return False + + if pname in ['php-fpm'] and exe: + pid_file = exe.replace('sbin/php-fpm','/var/run/php-fpm.pid') + if os.path.exists(pid_file): + try: + pid = int(public.readFile(pid_file)) + return public.pid_exists(pid) + except: + return False + + if not self.pids: self.pids = psutil.pids() + for pid in self.pids: + try: + l = '/proc/%s/exe' % pid + f = '/proc/%s/comm' % pid + p_exe = '' + p_name = '' + if os.path.exists(l): + p_exe = os.readlink(l) + if not p_name: p_name = p_exe.split('/')[-1] + + if not p_name and os.path.exists(f): + fp = open(f,'r') + p_name = fp.read().strip() + fp.close() + + if not p_name: continue + if p_name == pname: + if not exe: + return True + else: + if p_exe == exe: return True + except: continue + return False + + #取分页 + def get_page(self,data,get): + #包含分页类 + import page + #实例化分页类 + page = page.Page() + info = {} + info['count'] = len(data) + info['row'] = self.ROWS + info['p'] = 1 + if hasattr(get,'p'): + try: + info['p'] = int(get['p']) + except: + info['p'] = 1 + info['uri'] = {} + info['return_js'] = '' + if hasattr(get,'tojs'): + info['return_js'] = get.tojs + + #获取分页数据 + result = {} + result['page'] = page.GetPage(info) + n = 0 + result['data'] = [] + for i in range(info['count']): + if n >= page.ROW: break + if i < page.SHIFT: continue + n += 1 + result['data'].append(data[i]) + return result + + + #取列表 + def GetList(self,get = None): + try: + if not os.path.exists(self.__list): return [] + data = json.loads(public.readFile(self.__list)) + + #排序 + data = sorted(data, key= lambda b:b['sort'],reverse=False) + + #获取非划分列表 + n = 0 + for dirinfo in os.listdir(self.__install_path): + isTrue = True + for tm in data: + if tm['name'] == dirinfo: isTrue = False + if not isTrue: continue + + path = self.__install_path + '/' + dirinfo + if os.path.isdir(path): + jsonFile = path + '/info.json' + if os.path.exists(jsonFile): + try: + tmp = json.loads(public.readFile(jsonFile)) + if not hasattr(get,'type'): + get.type = 0 + else: + get.type = int(get.type) + + if get.type > 0: + try: + if get.type != tmp['id']: continue + except: + continue + + tmp['pid'] = len(data) + 1000 + n + tmp['status'] = tmp['display'] + tmp['display'] = 0 + data.append(tmp) + except: + pass + #索引列表 + if get: + display = None + if hasattr(get,'display'): display = True + if not hasattr(get,'type'): + get.type = 0 + else: + get.type = int(get.type) + if not hasattr(get,'search'): + search = None + m = 0 + else: + search = get.search.encode('utf-8').lower() + m = 1 + + tmp = [] + for d in data: + if d['id'] != 10000: + self.get_icon(d['name']) + if display: + if d['display'] == 0: continue + i=0 + if get.type > 0: + if get.type == d['id']: i+=1 + else: + i+=1 + if search: + if d['name'].lower().find(search) != -1: i+=1 + if d['name'].find(search) != -1: i+=1 + if d['title'].lower().find(search) != -1: i+=1 + if d['title'].find(search) != -1: i+=1 + if get.type > 0 and get.type != d['type']: i -= 1 + if i>m:tmp.append(d) + data = tmp + return data + except Exception as ex: + return str(ex) + + + #获取图标 + def get_icon(self,name,downFile = None): + iconFile = 'BTPanel/static/img/soft_ico/ico-' + name + '.png' + if not os.path.exists(iconFile): + public.run_thread(self.download_icon,(name,iconFile,downFile)) + else: + size = os.path.getsize(iconFile) + if size == 0: + public.run_thread(self.download_icon,(name,iconFile,downFile)) + # self.download_icon(name,iconFile,downFile) + + #下载图标 + def download_icon(self,name,iconFile,downFile): + srcIcon = 'plugin/' + name + '/icon.png' + skey = name+'_icon' + if cache.get(skey): return None + if os.path.exists(srcIcon): + public.ExecShell(r"\cp -a -r " + srcIcon + " " + iconFile) + else: + if downFile: + public.ExecShell('wget -O ' + iconFile + ' ' + public.GetConfigValue('home') + downFile + " &") + else: + public.ExecShell('wget -O ' + iconFile + ' ' + public.get_url() + '/install/plugin/' + name + '/icon.png' + " &") + cache.set(skey,1,86400) + + + #取分页 + def GetPage(self,data,get): + #包含分页类 + import page + #实例化分页类 + page = page.Page() + info = {} + info['count'] = len(data) + info['row'] = self.ROWS + info['p'] = 1 + if hasattr(get,'p'): + info['p'] = int(get['p']) + info['uri'] = {} + info['return_js'] = '' + if hasattr(get,'tojs'): + info['return_js'] = get.tojs + + #获取分页数据 + result = {} + result['page'] = page.GetPage(info) + n = 0 + result['data'] = [] + for i in range(info['count']): + if n > page.ROW: break + if i < page.SHIFT: continue + n += 1 + result['data'].append(data[i]) + return result + + #取分类 + def GetType(self,get = None): + try: + if not os.path.exists(self.__type): return False + data = json.loads(public.readFile(self.__type)) + return data + except: + return False + + #取单个 + def GetFind(self,name): + try: + data = self.GetList(None) + for d in data: + if d['name'] == name: return d + return None + except: + return None + + #设置 + def SetField(self,name,key,value): + data = self.GetList(None) + for i in range(len(data)): + if data[i]['name'] != name: continue + data[i][key] = value + + public.writeFile(self.__list,json.dumps(data)) + return True + + + + #安装插件 + def install(self,get): + pluginInfo = self.GetFind(get.name) + if not pluginInfo: + import json + pluginInfo = json.loads(public.readFile(self.__install_path + '/' + get.name + '/info.json')) + + if pluginInfo['tip'] == 'lib': + if not os.path.exists(self.__install_path + '/' + pluginInfo['name']): public.ExecShell('mkdir -p ' + self.__install_path + '/' + pluginInfo['name']) + if not 'download_url' in session: session['download_url'] = public.get_url() + download_url = session['download_url'] + '/install/plugin/' + pluginInfo['name'] + '/install.sh' + toFile = self.__install_path + '/' + pluginInfo['name'] + '/install.sh' + public.downloadFile(download_url,toFile) + self.set_pyenv(toFile) + public.ExecShell('/bin/bash ' + toFile + ' install') + if self.checksSetup(pluginInfo['name'],pluginInfo['checks'],pluginInfo['versions'])[0]['status'] or os.path.exists(self.__install_path + '/' + get.name): + public.write_log_gettext('Installer','Successfully installed plugin [{}]',(pluginInfo['title'],)) + #public.ExecShell('rm -f ' + toFile); + return public.return_msg_gettext(True,'Installation succeeded!') + return public.return_msg_gettext(False,'Installation failed!') + else: + import db,time + path = '/www/server/php' + if not os.path.exists(path): public.ExecShell("mkdir -p " + path) + issue = public.readFile('/etc/issue') + if session['server_os']['x'] != 'RHEL': get.type = '3' + + apacheVersion='false' + if public.get_webserver() == 'apache': + apacheVersion = public.xss_version(public.readFile('/www/server/apache/version.pl')) + public.writeFile('/var/bt_apacheVersion.pl',apacheVersion) + public.writeFile('/var/bt_setupPath.conf',public.GetConfigValue('root_path')) + isTask = '/tmp/panelTask.pl' + + mtype = 'install' + mmsg = 'install' + if hasattr(get, 'upgrade'): + if get.upgrade: + mtype = 'update' + mmsg = 'upgrade' + execstr = "cd /www/server/panel/install && /bin/bash install_soft.sh " + get.type + " "+mtype+" " + get.name + " "+ get.version; + sql = db.Sql() + if hasattr(get,'id'): + id = get.id + else: + id = None + sql.table('tasks').add('id,name,type,status,addtime,execstr',(None, mmsg + '['+get.name+'-'+get.version+']','execshell','0',time.strftime('%Y-%m-%d %H:%M:%S'),execstr)) + public.writeFile(isTask,'True') + public.write_log_gettext('Installer','Successfully added intallation task [{}-{}]',(get.name,get.version)) + return public.return_msg_gettext(True,'Installation task added to queue') + + + #卸载插件 + def unInstall(self,get): + pluginInfo = self.GetFind(get.name) + if not pluginInfo: + import json + pluginInfo = json.loads(public.readFile(self.__install_path + '/' + get.name + '/info.json')) + if pluginInfo['tip'] == 'lib': + if not os.path.exists(self.__install_path+ '/' + pluginInfo['name']): public.ExecShell('mkdir -p ' + self.__install_path + '/' + pluginInfo['name']) + download_url = session['download_url'] + '/install/plugin/' + pluginInfo['name'] + '/install.sh' + toFile = self.__install_path + '/' + pluginInfo['name'] + '/uninstall.sh' + install_sh = self.__install_path + '/' + pluginInfo['name'] + '/install.sh' + if not os.path.exists(toFile) and not os.path.exists(install_sh): + public.downloadFile(download_url,toFile) + self.set_pyenv(toFile) + + pluginPath = self.__install_path + '/' + pluginInfo['name'] + + if os.path.exists(toFile): + public.ExecShell('/bin/bash {} uninstall'.format(toFile)) + elif os.path.exists(pluginPath + '/install.sh'): + public.ExecShell('/bin/bash ' + pluginPath + '/install.sh uninstall') + + if os.path.exists(pluginPath): + public.ExecShell('rm -rf ' + pluginPath) + + public.write_log_gettext('Installer','Successfully uninstalled software [{}]',(pluginInfo['title'],)) + return public.return_msg_gettext(True,"Uninstallation succeeded") + else: + get.type = '0' + issue = public.readFile('/etc/issue') + if session['server_os']['x'] != 'RHEL': get.type = '3' + public.writeFile('/var/bt_setupPath.conf',public.GetConfigValue('root_path')) + execstr = "cd /www/server/panel/install && /bin/bash install_soft.sh "+get.type+" uninstall " + get.name.lower() + " "+ get.version.replace('.','') + public.ExecShell(execstr) + public.WriteLog('TYPE_SETUP','Successfully uninstalled [{}-{}]',(get.name,get.version)) + return public.returnMsg(True,"Uninstallation succeeded") + + #取产品信息 + def getProductInfo(self,productName): + if not self.__product_list: + import panelAuth + Auth = panelAuth.panelAuth() + self.__product_list = Auth.get_business_plugin(None) + for product in self.__product_list: + if product['name'] == productName: return product + return None + + #取到期时间 + def getEndDate(self,pluginName): + if not self.__plugin_list: + import panelAuth + Auth = panelAuth.panelAuth() + tmp = Auth.get_plugin_list(None) + if not tmp: return public.get_msg_gettext('NOT opened') + if not 'data' in tmp: return public.get_msg_gettext('NOT opened') + self.__plugin_list = tmp['data'] + for pluinfo in self.__plugin_list: + if pluinfo['product'] == pluginName: + if not pluinfo['endtime'] or not pluinfo['state']: return public.get_msg_gettext('To be paid') + if pluinfo['endtime'] < time.time(): return public.get_msg_gettext('Expired') + return time.strftime("%Y-%m-%d",time.localtime(pluinfo['endtime'])); + return public.get_msg_gettext('NOT opened') + + #取插件列表 + def getPluginList(self,get): + import json + arr = self.GetList(get) + result = {} + if not arr: + result['data'] = arr + result['type'] = self.GetType(None) + return result + apacheVersion = "" + try: + apavFile = '/www/server/apache/version.pl' + if os.path.exists(apavFile): + apacheVersion = public.xss_version(public.readFile(apavFile).strip()) + except: + pass + + result = self.GetPage(arr,get) + arr = result['data'] + for i in range(len(arr)): + arr[i]['end'] = '--' + #if 'price' in arr[i]: + # if arr[i]['price'] > 0: + # arr[i]['end'] = self.getEndDate(arr[i]['title']); + # if os.path.exists('plugin/beta/config.conf'): + # if os.path.exists('plugin/' + arr[i]['name'] + '/' + arr[i]['name'] + '_main.py') and arr[i]['end'] == '未开通': arr[i]['end'] = '--'; + + + if arr[i]['name'] == 'php': + if apacheVersion == '2.2': + arr[i]['versions'] = '5.2,5.3,5.4' + arr[i]['update'] = self.GetPv(arr[i]['versions'], arr[i]['update']) + elif apacheVersion == '2.4': + arr[i]['versions'] = '5.3,5.4,5.5,5.6,7.0,7.1,7.2,7.3,7.4' + arr[i]['update'] = self.GetPv(arr[i]['versions'], arr[i]['update']) + arr[i]['apache'] = apacheVersion + + arr[i]['versions'] = self.checksSetup(arr[i]['name'].replace('_soft',''),arr[i]['checks'],arr[i]['versions']) + + try: + arr[i]['update'] = arr[i]['update'].split(',') + except: + arr[i]['update'] = [] + + #是否强制使用插件模板 LIB_TEMPLATE + if os.path.exists(self.__install_path+'/'+arr[i]['name']): arr[i]['tip'] = 'lib' + + if arr[i]['tip'] == 'lib': + arr[i]['path'] = self.__install_path + '/' + arr[i]['name'].replace('_soft','') + arr[i]['config'] = os.path.exists(arr[i]['path'] + '/index.html') + else: + arr[i]['path'] = '/www/server/' + arr[i]['name'].replace('_soft','') + arr.append(public.M('tasks').where("status!=?",('1',)).count()) + + + result['data'] = arr + result['type'] = self.GetType(None) + return result + + #GetPHPV + def GetPv(self,versions,update): + versions = versions.split(',') + update = update.split(',') + updates = [] + for up in update: + if up[:3] in versions: updates.append(up) + return ','.join(updates) + + #保存插件排序 + def savePluginSort(self,get): + ssort = get.ssort.split('|') + data = self.GetList(None) + l = len(data) + for i in range(len(ssort)): + if int(ssort[i]) > 1000: continue + for n in range(l): + if data[n]['pid'] == int(ssort[i]): data[n]['sort'] = i + public.writeFile(self.__list,json.dumps(data)) + return public.return_msg_gettext(True,'Sort saved') + + #检查是否安装 + def checksSetup(self,name,checks,vers = ''): + tmp = checks.split(',') + versions = [] + path = '/www/server/' + name + '/version.pl' + v1 = '' + if os.path.exists(path): v1 = public.xss_version(public.readFile(path).strip()) + if name == 'nginx': v1 = v1.replace('1.10', '1.12') + if not self.__tasks: + self.__tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select() + isStatus = 0 + versArr = vers.split(',') + for v in versArr: + version = {} + + v2 = v + if name == 'php': v2 = v2.replace('.','') + status = False + for tm in tmp: + if name == 'php': + path = '/www/server/php/' + v2 + if os.path.exists(path + '/bin/php') and not os.path.exists(path + '/version.pl'): + public.ExecShell("echo `"+path+"/bin/php 2>/dev/null -v|grep cli|awk '{print $2}'` > " + path + '/version.pl') + try: + v1 = public.xss_version(public.readFile(path+'/version.pl').strip()) + if not v1: public.ExecShell('rm -f ' + path + '/version.pl') + except: + v1 = "" + if os.path.exists(tm.replace('VERSION',v2)): status = True + else: + if os.path.exists(tm) and isStatus == 0: + if len(versArr) > 1: + im = v1.find(v) + if im != -1 and im < 3: + status = True + isStatus += 1 + else: + status = True + isStatus += 1 + #处理任务标记 + if not self.__tasks: + self.__tasks = public.M('tasks').where("status!=?",('1',)).field('status,name').select() + isTask = '1' + for task in self.__tasks: + tmpt = public.getStrBetween('[',']',task['name']) + if not tmpt:continue + tmp1 = tmpt.split('-') + name1 = tmp1[0].lower() + if name == 'php': + if name1 == name and tmp1[1] == v: isTask = task['status'] + else: + if name1 == 'pure': name1 = 'pure-ftpd' + if name1 == name: isTask = task['status'] + + infoFile = 'plugin/' + name + '/info.json' + if os.path.exists(infoFile): + try: + tmps = json.loads(public.readFile(infoFile)) + if tmps: v1 = tmps['versions'] + except:pass + + if name == 'memcached': + if os.path.exists('/etc/init.d/memcached'): + v1 = session.get('memcachedv') + if not v1: + v1 = public.ExecShell("memcached -V|awk '{print $2}'")[0].strip() + session['memcachedv'] = v1 + if name == 'apache': + if os.path.exists('/www/server/apache/bin/httpd'): + v1 = session.get('httpdv') + if not v1: + v1 = public.ExecShell(r"/www/server/apache/bin/httpd -v|grep Apache|awk '{print $3}'|sed 's/Apache\///'")[0].strip(); + session['httpdv'] = v1 + #if name == 'mysql': + # if os.path.exists('/www/server/mysql/bin/mysql'): v1 = public.ExecShell("mysql -V|awk '{print $5}'|sed 's/,//'")[0].strip(); + + version['status'] = status + version['version'] = v + version['task'] = isTask + version['no'] = v1 + versions.append(version) + return self.checkRun(name,versions) + + #检查是否启动 + def checkRun(self,name,versions): + if name == 'php': + path = '/www/server/php' + pids = psutil.pids() + for i in range(len(versions)): + if versions[i]['status']: + v4 = versions[i]['version'].replace('.','') + versions[i]['run'] = os.path.exists('/tmp/php-cgi-' + v4 + '.sock') + pid_file = path + '/' + v4 + '/var/run/php-fpm.pid' + versions[i]['process_id'] = public.readFile(pid_file) + if versions[i]['run'] and os.path.exists(pid_file): + if not int(public.readFile(pid_file)) in pids: + versions[i]['run'] = False + + versions[i]['fpm'] = os.path.exists('/etc/init.d/php-fpm-'+v4) + phpConfig = self.GetPHPConfig(v4) + versions[i]['max'] = phpConfig['max'] + versions[i]['maxTime'] = phpConfig['maxTime'] + versions[i]['pathinfo'] = phpConfig['pathinfo'] + versions[i]['display'] = os.path.exists(path + '/' + v4 + '/display.pl') + if len(versions) < 5: versions[i]['run'] = True + + elif name == 'nginx': + status = False + if os.path.exists('/etc/init.d/nginx'): + pidf = '/www/server/nginx/logs/nginx.pid' + if os.path.exists(pidf): + try: + pid = public.readFile(pidf) + pname = self.checkProcess(pid) + if pname: status = True + except: + status = False + for i in range(len(versions)): + versions[i]['run'] = False + if versions[i]['status']: versions[i]['run'] = status + elif name == 'apache': + status = False + if os.path.exists('/etc/init.d/httpd'): + pidf = '/www/server/apache/logs/httpd.pid' + if os.path.exists(pidf): + pid = public.readFile(pidf) + status = self.checkProcess(pid) + for i in range(len(versions)): + versions[i]['run'] = False + if versions[i]['status']: versions[i]['run'] = status + elif name == 'mysql': + status = os.path.exists('/tmp/mysql.sock') + for i in range(len(versions)): + versions[i]['run'] = False + if versions[i]['status']: versions[i]['run'] = status + elif name == 'tomcat': + status = False + if os.path.exists('/www/server/tomcat/logs/catalina-daemon.pid'): + if self.getPid('jsvc'): status = True + if not status: + if self.getPid('java'): status = True + for i in range(len(versions)): + versions[i]['run'] = False + if versions[i]['status']: versions[i]['run'] = status + elif name == 'pure-ftpd': + for i in range(len(versions)): + pidf = '/var/run/pure-ftpd.pid' + if os.path.exists(pidf): + pid = public.readFile(pidf) + versions[i]['run'] = self.checkProcess(pid) + if not versions[i]['run']: public.ExecShell('rm -f ' + pidf) + elif name == 'phpmyadmin': + for i in range(len(versions)): + if versions[i]['status']: versions[i] = self.getPHPMyAdminStatus() + elif name == 'redis': + for i in range(len(versions)): + pidf = '/var/run/redis_6379.pid' + if os.path.exists(pidf): + pid = public.readFile(pidf) + versions[i]['run'] = self.checkProcess(pid) + if not versions[i]['run']: public.ExecShell('rm -f ' + pidf) + elif name == 'memcached': + for i in range(len(versions)): + pidf = '/var/run/memcached.pid' + if os.path.exists(pidf): + pid = public.readFile(pidf) + versions[i]['run'] = self.checkProcess(pid) + if not versions[i]['run']: public.ExecShell('rm -f ' + pidf) + else: + for i in range(len(versions)): + if versions[i]['status']: versions[i]['run'] = True + return versions + + # 取PHPMyAdmin状态 + def getPHPMyAdminStatus(self): + import re + tmp = {} + setupPath = '/www/server' + configFile = setupPath + '/nginx/conf/nginx.conf' + pauth = False + pstatus = False + phpversion = "54" + phpport = '888' + ssl_port = '887' + ssl_enabled = False + if os.path.exists(configFile): + conf = public.readFile(configFile) + rep = r"listen\s+([0-9]+)\s*;" + rtmp = re.search(rep, conf) + if rtmp: + phpport = rtmp.groups()[0] + + # SSL配置文件查看 + ssl_config_file = '{}/vhost/nginx/phpmyadmin.conf'.format(public.get_panel_path()) + if os.path.exists(ssl_config_file) and os.path.getsize(ssl_config_file) > 10: + tmps = public.readFile(ssl_config_file) + m = re.search(r"listen\s*(\d+)", tmps) + if m is not None: + ssl_enabled = True + ssl_port = m.group(1) + + if conf.find('AUTH_START') != -1: pauth = True + if conf.find(setupPath + '/stop') == -1: pstatus = True + configFile = setupPath + '/nginx/conf/enable-php.conf' + if not os.path.exists(configFile): public.writeFile(configFile, public.readFile( + setupPath + '/nginx/conf/enable-php-54.conf')) + conf = public.readFile(configFile) + rep = r"php-cgi-([0-9]+)\.sock" + rtmp = re.search(rep, conf) + if rtmp: + phpversion = rtmp.groups()[0] + else: + rep = r'127.0.0.1:10(\d{2,2})1' + rtmp = re.findall(rep, conf) + if rtmp: + phpversion = rtmp[0] + else: + rep = r"php-cgi.*\.sock" + public.writeFile(configFile, conf) + phpversion = '54' + + configFile = setupPath + '/apache/conf/extra/httpd-vhosts.conf' + if os.path.exists(configFile): + conf = public.readFile(configFile) + rep = r"php-cgi-([0-9]+)\.sock" + rtmp = re.search(rep, conf) + if rtmp: + phpversion = rtmp.groups()[0] + rep = r"Listen\s+([0-9]+)\s*\n" + rtmp = re.search(rep, conf) + if rtmp: + phpport = rtmp.groups()[0] + + # SSL配置文件查看 + ssl_config_file = '{}/vhost/apache/phpmyadmin.conf'.format(public.get_panel_path()) + if os.path.exists(ssl_config_file) and os.path.getsize(ssl_config_file) > 10: + tmps = public.readFile(ssl_config_file) + m = re.search(r"Listen\s*(\d+)", tmps) + if m is not None: + ssl_enabled = True + ssl_port = m.group(1) + + if conf.find('AUTH_START') != -1: pauth = True + if conf.find('/www/server/stop') == -1: pstatus = True + + if os.path.exists('/usr/local/lsws/bin/lswsctrl'): + result = self._get_ols_myphpadmin_info() + if result: + phpversion = result['php_version'] + phpport = result['php_port'] + pauth = result['pauth'] + pstatus = result['pstatus'] + try: + vfile = setupPath + '/phpmyadmin/version.pl' + if os.path.exists(vfile): + tmp['version'] = public.xss_version(public.readFile(vfile).strip()) + tmp['status'] = True + tmp['no'] = tmp['version'] + else: + tmp['version'] = "" + tmp['status'] = False + tmp['no'] = "" + + tmp['run'] = pstatus + tmp['phpversion'] = phpversion + tmp['ssl_enabled'] = ssl_enabled + tmp['port'] = phpport + tmp['ssl_port'] = ssl_port + tmp['auth'] = pauth + except Exception as ex: + tmp['status'] = False + tmp['error'] = str(ex) + return tmp + + def _get_ols_myphpadmin_info(self): + filename = "/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf" + conf = public.readFile(filename) + if not conf:return False + reg = r'/usr/local/lsws/lsphp(\d+)/bin/lsphp' + php_v = re.search(reg,conf) + phpversion = '73' + phpport = '888' + if php_v: + phpversion = php_v.group(1) + filename = '/www/server/panel/vhost/openlitespeed/listen/888.conf' + conf = public.readFile(filename) + reg = r'address\s+\*\:(\d+)' + php_port = re.search(reg,conf) + if php_port: + phpport = php_port.group(1) + pauth = False + pstatus = False + if conf.find('/www/server/stop') == -1: pstatus = True + return {'php_version':phpversion,'php_port':phpport,'pauth':pauth,'pstatus':pstatus} + + #取PHP配置 + def GetPHPConfig(self,version): + import re + setupPath = '/www/server' + file = setupPath + "/php/"+version+"/etc/php.ini" + phpini = public.readFile(file) + file = setupPath + "/php/"+version+"/etc/php-fpm.conf" + phpfpm = public.readFile(file) + data = {} + try: + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" + tmp = re.search(rep,phpini).groups() + data['max'] = tmp[0] + except: + data['max'] = '50' + try: + rep = r"request_terminate_timeout\s*=\s*([0-9]+)\n" + tmp = re.search(rep,phpfpm).groups() + data['maxTime'] = tmp[0] + except: + data['maxTime'] = 0 + + try: + rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + tmp = re.search(rep,phpini).groups() + + if tmp[0] == '1': + data['pathinfo'] = True + else: + data['pathinfo'] = False + except: + data['pathinfo'] = False + + return data + + #名取PID + def getPid(self,pname): + try: + if not self.pids: self.pids = psutil.pids() + for pid in self.pids: + if psutil.Process(pid).name() == pname: return True + return False + except: return True + + #检测指定进程是否存活 + def checkProcess(self,pid): + try: + if not self.pids: self.pids = psutil.pids() + if int(pid) in self.pids: return True + return False + except: return False + + #获取配置模板 + def getConfigHtml(self,get): + filename = self.__install_path + '/' + get.name + '/index.html' + if not os.path.exists(filename): return public.return_msg_gettext(False,'This plugin does NOT have template!') + mimetype = 'text/html' + cache_time = 0 if public.is_debug() else 86400 + self.plugin_open_total(get.name) + import flask + if flask.__version__ < "2.1.0": + return send_file(filename, + mimetype = mimetype, + as_attachment = True, + add_etags = True, + conditional = True, + cache_timeout = cache_time) + else: + return send_file(filename, + mimetype = mimetype, + as_attachment = True, + etag = True, + conditional = True, + max_age = cache_time) + + + def creatab_open_total_table(self,sql): + ''' + @name 创建插件打开统计表 + @author hwliang<2021-06-26> + @param sql 数据库对像 + @return void + ''' + if not sql.table('sqlite_master').where('type=? AND name=?', ('table', 'open_total')).count(): + csql = '''CREATE TABLE IF NOT EXISTS `open_total` ( +`id` INTEGER PRIMARY KEY AUTOINCREMENT, +`plugin_name` REAL, +`num` INTEGER +)''' + sql.execute(csql,()) + + + def plugin_open_total(self,plugin_name): + ''' + @name 插件打开统计 + @author hwliang<2021-06-26> + @param plugin_name 插件名称 + @return void + ''' + import db + sql = db.Sql().dbfile('plugin_total') + self.creatab_open_total_table(sql) + pdata = { + "plugin_name":plugin_name, + "num":1 + } + + num = sql.table('open_total').where('plugin_name=?',plugin_name).getField('num') + if not num: + sql.table('open_total').insert(pdata) + else: + sql.table('open_total').where('plugin_name=?',plugin_name).setField('num',num+1) + + def get_usually_plugin(self,get): + ''' + @name 获取常用插件 + @author hwliang<2021-06-26> + @param get + @return list + ''' + import db + sql = db.Sql().dbfile('plugin_total') + self.creatab_open_total_table(sql) + plugin_list = sql.table('open_total').order('num desc').limit(10).select() + usually_list = [] + for p in plugin_list: + plugin_info = self.get_soft_find(p['plugin_name']) + if plugin_info: + if plugin_info['setup']: + usually_list.append(plugin_info) + if len(usually_list) >= 5: break + return usually_list + + + def get_plugin_upgrades(self,get): + ''' + @name 获取指定插件的近期更新历史 + @author hwliang<2021-06-30> + @param get{ + plugin_name: string 插件名称 + } + @return list + ''' + plugin_name = get.plugin_name + if getattr(get,'show',0): + plugin_info = self.__get_plugin_find(plugin_name) + if plugin_info and 'versions' in plugin_info: + return plugin_info['versions'] + return [] + else: + return self.__get_plugin_upgrades(plugin_name) + + #取插件信息 + def getPluginInfo(self,get): + try: + pluginInfo = self.GetFind(get.name) + apacheVersion = "" + try: + apavFile = '/www/server/apache/version.pl' + if os.path.exists(apavFile): + apacheVersion = public.xss_version(public.readFile(apavFile).strip()) + except: + pass + if pluginInfo['name'] == 'php': + if apacheVersion == '2.2': + pluginInfo['versions'] = '5.2,5.3,5.4' + elif apacheVersion == '2.4': + pluginInfo['versions'] = '5.3,5.4,5.5,5.6,7.0,7.1,7.2,7.3,7.4' + + pluginInfo['versions'] = self.checksSetup(pluginInfo['name'],pluginInfo['checks'],pluginInfo['versions']) + if get.name == 'php': + pluginInfo['phpSort'] = public.readFile('/www/server/php/sort.pl') + return pluginInfo + except: + return False + + #取插件状态 + def getPluginStatus(self,get): + find = self.GetFind(get.name) + versions = [] + path = '/www/server/php' + for version in find['versions'].split(','): + tmp = {} + tmp['version'] = version + if get.name == 'php': + tmp['status'] = os.path.exists(path + '/' + version.replace(',','') + '/display.pl') + else: + tmp['status'] = find['status'] + versions.append(tmp) + return versions + + #设置插件状态 + def setPluginStatus(self,get): + if get.name == 'php': + isRemove = True + path = '/www/server/php' + if get.status == '0': + versions = self.GetFind(get.name)['versions'] + public.ExecShell('rm -f ' + path + '/' + get.version.replace('.','') + '/display.pl') + for version in versions.split(','): + if os.path.exists(path + '/' + version.replace('.','') + '/display.pl'): + isRemove = False + break + else: + public.writeFile(path + '/' + get.version.replace('.','') + '/display.pl','True') + + if isRemove: + self.SetField(get.name, 'display', int(get.status)) + else: + self.SetField(get.name, 'display', int(get.status)) + return public.return_msg_gettext(True,'Setup successfully!') + + #从云端获取插件列表 + def getCloudPlugin(self,get): + if session.get('getCloudPlugin') and get != None: return public.return_msg_gettext(True,'Your plugin list is already the latest version {}!',("-1",)) + import json + if not session.get('download_url'): session['download_url'] = 'http://node.aapanel.com' + + #获取列表 + try: + newUrl = public.get_url() + if os.path.exists('plugin/beta/config.conf'): + download_url = newUrl + '/install/list.json' + else: + download_url = newUrl + '/install/list_pro.json' + data = json.loads(public.httpGet(download_url)) + session['download_url'] = newUrl + except: + download_url = session['download_url'] + '/install/list_pro.json' + data = json.loads(public.httpGet(download_url)) + + n = i = j = 0 + + lists = self.GetList(None) + + for i in range(len(data)): + for pinfo in lists: + if data[i]['name'] != pinfo['name']: continue + data[i]['display'] = pinfo['display'] + if data[i]['default']: + get.name = data[i]['name'] + self.install(get) + + public.writeFile(self.__list,json.dumps(data)) + + #获取分类 + try: + download_url = session['download_url'] + '/install/type.json' + types = json.loads(public.httpGet(download_url)) + public.writeFile(self.__type,json.dumps(types)) + except: + pass + + self.getCloudPHPExt(get) + self.GetCloudWarning(get) + session['getCloudPlugin'] = True + return public.return_msg_gettext(True,'Software list updated!') + + #刷新缓存 + def flush_cache(self,get): + self.getCloudPlugin(None) + return public.return_msg_gettext(True,'Software list updated!') + + #获取PHP扩展 + def getCloudPHPExt(self,get=None): + import json + try: + key = 'php_ext_cache' + if cache.get(key): return 1 + surl = public.get_url() + download_url = surl + '/install/lib/phplib.json' + tstr = public.httpGet(download_url) + data = json.loads(tstr) + if not data: return 2 + public.writeFile('data/phplib.conf',json.dumps(data)) + + # download_url = surl + '/license/md5.txt' + # li_md5 = public.httpGet(download_url) + # if not li_md5: return 3 + # li_md5 = li_md5.strip() + # if len(li_md5) != 32: return 4 + # l_file = 'BTPanel/templates/default/license.html' + # lfile2 = 'data/license.md5' + # old_md5 = '' + # if os.path.exists(lfile2): + # old_md5 = public.readFile(lfile2) + + # if li_md5 != old_md5: + # download_url = surl + '/license/license.html' + # public.downloadFile(download_url,l_file) + # old_md5 = public.FileMd5(l_file) + # if li_md5 == old_md5: + # public.writeFile(lfile2,old_md5) + # s_file = 'data/licenes.pl' + # if os.path.exists(s_file): + # os.remove(s_file) + cache.set(key,86400) + return True + except: + return public.get_error_info() + + + + #获取警告列表 + def GetCloudWarning(self,get): + import json + if not session.get('download_url'): session['download_url'] = public.get_url() + download_url = session['download_url'] + '/install/warning.json' + tstr = public.httpGet(download_url) + data = json.loads(tstr) + if not data: return False + wfile = 'data/warning.json' + wlist = json.loads(public.readFile(wfile)) + for i in range(len(data['data'])): + for w in wlist['data']: + if data['data'][i]['name'] != w['name']: continue + data['data'][i]['ignore_count'] = w['ignore_count'] + data['data'][i]['ignore_time'] = w['ignore_time'] + public.writeFile(wfile,json.dumps(data)) + return data + + #名取标题 + def get_title_byname(self,get): + get.sName = get.name + find = self.get_soft_find(get) + return find['title'] + + + def a(self, get): + try: + return public.run_plugin_v2(get.name, get.s, get) + except: + return public.get_error_object(None, plugin_name=get.name) + + #上传插件包 + def update_zip(self,get = None,tmp_file = None, update = False): + tmp_path = '/www/server/panel/temp' + if not os.path.exists(tmp_path): + os.makedirs(tmp_path,mode=384) + + if tmp_file: + if not os.path.exists(tmp_file): return public.return_msg_gettext(False,'File download failed!') + + + if get: + public.ExecShell("rm -rf " + tmp_path + '/*') + tmp_file = tmp_path + '/plugin_tmp.zip' + from werkzeug.utils import secure_filename + from flask import request + f = request.files['plugin_zip'] + if f.filename[-4:] != '.zip': tmp_file = tmp_path + '/plugin_tmp.tar.gz' + + f.save(tmp_file) + + + import panelTask + panelTask.bt_task()._unzip(tmp_file,tmp_path,'','/dev/null') + os.remove(tmp_file) + p_info = tmp_path + '/info.json' + if not os.path.exists(p_info): + d_path = None + for df in os.walk(tmp_path): + if len(df[2]) < 3: continue + if not 'info.json' in df[2]: continue + if not 'install.sh' in df[2]: continue + if not os.path.exists(df[0] + '/info.json'): continue + d_path = df[0] + if d_path: + tmp_path = d_path + p_info = tmp_path + '/info.json' + try: + try: + data = json.loads(public.ReadFile(p_info)) + except: + data = json.loads(public.ReadFile(p_info).decode('utf-8-sig')) + data['size'] = public.get_path_size(tmp_path) + if not 'author' in data: data['author'] = public.get_msg_gettext('Unknown') + if not 'home' in data: data['home'] = 'https://www.bt.cn/bbs/forum-40-1.html' + + plugin_path = '/www/server/panel/plugin/' + data['name'] + '/info.json' + data['old_version'] = '0' + data['tmp_path'] = tmp_path + if os.path.exists(plugin_path): + try: + old_info = json.loads(public.ReadFile(plugin_path)) + data['old_version'] = old_info['versions'] + except:pass + except: + public.ExecShell("rm -rf " + tmp_path + '/*') + return public.return_msg_gettext(False,'No plugin info found in the archive, please check the plugin package!') + + data['update'] = update + return data + + #导入插件包 + def input_zip(self,get): + if not os.path.exists(get.tmp_path): return public.return_msg_gettext(False,'TEM_FILE_NOT_EXIST!') + plugin_path = '/www/server/panel/plugin/' + get.plugin_name + if not os.path.exists(plugin_path): os.makedirs(plugin_path) + public.ExecShell(r"\cp -a -r " + get.tmp_path + '/* ' + plugin_path + '/') + public.ExecShell('chmod -R 600 ' + plugin_path) + self.set_pyenv(plugin_path + '/install.sh') + public.ExecShell('cd ' + plugin_path + ' && bash install.sh install &> /tmp/panelShell.pl') + p_info = public.ReadFile(plugin_path + '/info.json') + public.ExecShell("rm -rf /www/server/panel/temp/*") + if p_info: + #----- 增加图标复制 hwliang<2021-03-23> -----# + icon_sfile = plugin_path + '/icon.png' + icon_dfile = '/www/server/panel/BTPanel/static/img/soft_ico/ico-{}.png'.format(get.plugin_name) + if os.path.exists(plugin_path + '/icon.png'): + import shutil + shutil.copyfile(icon_sfile,icon_dfile) + #----- 增加图标复制 END -----# + public.write_log_gettext('Software manager','Installed third-party plugin [{}]' ,(json.loads(p_info)['title'],)) + return public.return_msg_gettext(True,'Installation succeeded!') + public.ExecShell("rm -rf " + plugin_path) + return public.return_msg_gettext(False,'Installation failed') + + + #导出插件包 + def export_zip(self,get): + plugin_path = '/www/server/panel/plugin/' + get.plugin_name + if not os.path.exists(plugin_path): return public.return_msg_gettext(False,'The specified plugin does not exist!') + + get.sfile = plugin_path + '/' + get.dfile = '/www/server/panel/temp/bt_plugin_' + get.plugin_name + '.zip' + get.type = 'zip' + import files + files.files().Zip(get) + if not os.path.exists(get.dfile): return public.return_msg_gettext(False,'Export failed, please check permissions!') + return public.return_msg_gettext(True,get.dfile) + + + #获取编译参数 + def get_make_args(self,get): + config_path = 'install/' + get.name + if not os.path.exists(config_path): + os.makedirs(config_path) + #读支持的编译参数列表 + make_args = [] + for p_name in os.listdir(config_path): + path = os.path.join(config_path,p_name) + if not os.path.isdir(path): continue + make_info = {"name":p_name,"init":"","args":"","ps":""} + init_file = os.path.join(path,'init.sh') + args_file = os.path.join(path,'args.pl') + ps_file = os.path.join(path,'ps.pl') + if not os.path.exists(args_file): + continue + if os.path.exists(init_file): + make_info['init'] = public.readFile(init_file) + if os.path.exists(ps_file): + make_info['ps'] = public.readFile(ps_file) + make_info['args'] = public.readFile(args_file) + make_args.append(make_info) + #读当前配置 + data = {'args':make_args,'config':''} + config_file = config_path + '/config.pl' + if os.path.exists(config_file): + data['config'] = public.readFile(config_file) + return data + + #添加编译参数 + def add_make_args(self,get): + get.args_name = get.args_name.strip() + get.name = get.name.strip() + get.ps = get.ps.strip() + if not re.match(r'^\w+$',get.args_name): + return public.return_msg_gettext(False,'Non-compliant names can only be numbers, letters, underscores') + + config_path = os.path.join('install' , get.name , get.args_name) + if not os.path.exists(config_path): + os.makedirs(config_path,384) + + init_file = os.path.join(config_path,'init.sh') + args_file = os.path.join(config_path,'args.pl') + ps_file = os.path.join(config_path,'ps.pl') + public.writeFile(init_file,get.init.replace("\r\n","\n")) + public.writeFile(args_file,get.args) + public.writeFile(ps_file,get.ps) + + public.write_log_gettext('Software manager','Add custom compilation parameters: {}:{}',(get.name,get.args_name)) + return public.return_msg_gettext(True,'Setup successfully!') + + #删除编译参数 + def del_make_args(self,get): + get.args_name = get.args_name.strip() + get.name = get.name.strip() + if not re.match(r'^\w+$',get.args_name): + return public.return_msg_gettext(False,'Non-compliant names can only be numbers, letters, underscores') + config_path = os.path.join('install' , get.name , get.args_name) + if not os.path.exists(config_path): + return public.return_msg_gettext(False,'The specified custom compilation parameters do not exist!') + public.ExecShell("rm -rf {}".format(config_path)) + config_file = 'install/' + get.name + '/config.pl' + if os.path.exists(config_file): + config_data = public.readFile(config_file).split("\n") + if get.args_name in config_data: + config_data.remove(get.args_name) + public.writeFile(config_file,"\n".join(config_data)) + public.write_log_gettext('Software manager','Remove custom compilation parameters: {}:{}',(get.name,get.args_name)) + return public.return_msg_gettext(True,'Successfully deleted') + + + #设置当前编译参数 + def set_make_args(self,get): + get.args_names = get.args_names.strip().split("\n") + get.name = get.name.strip() + config_file = 'install/' + get.name + '/config.pl' + config_data = [] + for args_name in get.args_names: + path = 'install/' + get.name + '/' + args_name + if not os.path.exists(path): continue + if args_name in config_data: continue + config_data.append(args_name) + public.writeFile(config_file,"\n".join(config_data)) + public.write_log_gettext('Software manager','Setup software: Custom compilation parameters for {} are configured as: {}'.format(get.name,config_data)) + return public.return_msg_gettext(True,'Setup successfully!') + + # 安装插件 + def __install_plugin(self, upgrade_plugin_name, upgrade_version=None): + ''' + @name 安装指定插件 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版 + @return dict + ''' + self.__plugin_name = upgrade_plugin_name + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: + raise public.PanelError('指定插件不存在,无法安装!') + if not plugin_info['versions']: + raise public.PanelError('指定插件当前未发布版本信息,请稍候再安装!') + if not upgrade_version: + upgrade_version = '{}.{}'.format( + plugin_info['versions'][0]['m_version'], + plugin_info['versions'][0]['version']) + filename = self.__download_plugin(upgrade_plugin_name, upgrade_version) + # 如果下载失败 + if isinstance(filename, dict): + return filename + return self.__unpackup_plugin(filename) + + # 修复插件 + def __repair_plugin(self, upgrade_plugin_name, upgrade_version=None): + ''' + @name 修复指定插件 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版 + @return dict + ''' + self.__install_opt = 'r' + return self.__install_plugin(upgrade_plugin_name, upgrade_version) + + # 升级插件版本 + def __upgrade_plugin(self, upgrade_plugin_name, upgrade_version=None): + ''' + @name 升级到指定版本 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 版本号.指定版本号 / tls.最新正式版 / beta.最新测试版 + @return dict + ''' + self.__install_opt = 'u' + return self.__install_plugin(upgrade_plugin_name, upgrade_version) + + # 获取插件信息 + def __get_plugin_info(self, upgrade_plugin_name): + ''' + @name 获取插件信息 + @author hwliang<2021-06-15> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + plugin_info_file = '{}/{}/info.json'.format(self.__plugin_path, + upgrade_plugin_name) + if not os.path.exists(plugin_info_file): return {} + info_body = self.__read_file(plugin_info_file) + if not info_body: return {} + plugin_info = json.loads(info_body) + return plugin_info + + # 获取插件最近1条更新日志 + def __get_update_msg(self, upgrade_plugin_name, upgrade_version): + ''' + @name 检查指定插件版本更新日志 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 + @return string + ''' + plugin_update_msg = '' + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: return plugin_update_msg + for _version_info in plugin_info['versions']: + l_version = '{}.{}'.format(_version_info['m_version'], + _version_info['version']) + if l_version == upgrade_version: + plugin_update_msg = _version_info['update_msg'] + break + return plugin_update_msg + + # 获取插件最近10条更新日志 + def __get_plugin_upgrades(self, upgrade_plugin_name): + ''' + @name 检查指定插件最近10条更新日志 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @return list + ''' + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: return [] + + try: + upgrade_list = public.httpPost( + self.__api_root_url + '/down/get_update_msg', + {'soft_id': plugin_info['id']}) + return json.loads(upgrade_list) + except: + return [] + + # 获取指定软件信息 + def __get_plugin_find(self, upgrade_plugin_name=None): + ''' + @name 获取指定软件信息 + @author hwliang<2021-06-15> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + self.__ensure_plugin_list_obtained(True) + + for p_data_info in self.__plugin_list['list']: + if p_data_info['name'] == upgrade_plugin_name: + upgrade_plugin_name = p_data_info['name'] + return p_data_info + + # 如果不在插件列表中 + return self.__get_plugin_info(upgrade_plugin_name) + + # 检查插件依赖 + def __check_dependent(self, upgrade_plugin_name): + ''' + @name 检查指定插件的依赖安装情况 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + plugin_info = self.__get_plugin_find(upgrade_plugin_name) + if not plugin_info: return {} + if not plugin_info['dependent']: return {} + deployment_list = {} + for dependent_plu_name in plugin_info['dependent'].split(','): + p_info = self.__get_plugin_find(dependent_plu_name) + if not p_info: continue + deployment_list[dependent_plu_name] = os.path.exists( + p_info['install_checks']) + return deployment_list + + # 读取指定文件 + def __read_file(self, filename, open_mode='r'): + ''' + @name 读取指定文件 + @author hwliang<2021-06-16> + @param filename 文件名 + @param mode 打开模式, 默认: r + @return bytes or string + ''' + f_object = open(filename, mode=open_mode) + file_body = f_object.read() + f_object.close() + return file_body + + # 解包插件压缩包 + def __unpackup_plugin(self, tmp_file): + ''' + @name 解包插件包 + @author hwliang<2021-06-21> + @param tmp_file 下载好的保存路径,从self.download_plugin方法中获取 + @return dict + ''' + if type(tmp_file) == dict: + return tmp_file + + if "false" in tmp_file or "错误" in tmp_file: + return json.loads(tmp_file) + + s_tmp_path = self.__tmp_path + if not os.path.exists(s_tmp_path): + os.makedirs(s_tmp_path, mode=384) + + if tmp_file: + if not os.path.exists(tmp_file): + return public.returnMsg(False, '文件下载失败!') + import panelTask as plu_panelTask + plu_panelTask.bt_task()._unzip(tmp_file, s_tmp_path, '', + '/dev/null') + if os.path.exists(tmp_file): + os.remove(tmp_file) + + s_tmp_path = os.path.join(s_tmp_path, self.__plugin_name) + + p_info = os.path.join(s_tmp_path, 'info.json') + if not os.path.exists(p_info): + d_path = None + for plugin_df in os.walk(s_tmp_path): + if len(plugin_df[2]) < 3: continue + if not 'info.json' in plugin_df[2]: continue + if not 'install.sh' in plugin_df[2]: continue + if not os.path.exists(plugin_df[0] + '/info.json'): continue + d_path = plugin_df[0] + if d_path: + s_tmp_path = d_path + p_info = s_tmp_path + '/info.json' + try: + try: + plugin_data_info = json.loads(public.ReadFile(p_info)) + except: + plugin_data_info = json.loads(self.__read_file(p_info)) + + plugin_data_info['size'] = public.get_path_size(s_tmp_path) + if not 'author' in plugin_data_info: + plugin_data_info['author'] = 'aapanel' + if not 'home' in plugin_data_info: + plugin_data_info['home'] = 'https://www.aapanel.com' + + p_info_file = self.__plugin_path + plugin_data_info[ + 'name'] + '/info.json' + plugin_data_info['old_version'] = '0' + plugin_data_info['tmp_path'] = s_tmp_path + if os.path.exists(p_info_file): + try: + old_info = json.loads(public.ReadFile(p_info_file)) + plugin_data_info['old_version'] = old_info['versions'] + except: + pass + except: + public.ExecShell("rm -rf " + s_tmp_path + '/*') + return public.get_error_object(plugin_name=self.__plugin_name) + plugin_data_info['install_opt'] = self.__install_opt + plugin_data_info['dependent'] = self.__check_dependent(plugin_data_info['name']) + plugin_data_info['update_msg'] = self.__get_update_msg( + plugin_data_info['name'], plugin_data_info['versions']) + not_check = self.not_cpu_or_bit(plugin_data_info) + if not_check: + if os.path.exists(s_tmp_path): shutil.rmtree(s_tmp_path) + return not_check + + return plugin_data_info + + # 检测是否为不支持的平台和系统位数 + def not_cpu_or_bit(self, plugin_data_info): + ''' + @name 检测是否为不支持的平台和系统位数 + @author hwliang<2021-07-07> + @param plugin_data_info 插件信息数据 + @return dict or None + ''' + if 'not_os_bit' in plugin_data_info: + if public.get_sysbit() == int(plugin_data_info['not_os_bit']): + return public.returnMsg( + False, + '该应用不支持{}位系统'.format(plugin_data_info['not_os_bit'])) + if 'not_cpu_type' in plugin_data_info: + if not plugin_data_info['not_cpu_type']: return None + machine = os.uname().machine + for c_type in plugin_data_info['not_cpu_type']: + c_type = c_type.lower() + result = public.returnMsg( + False, '该应用不支持{}平台,{}'.format(c_type, machine)) + if c_type in ['arm', 'aarch64', 'aarch']: + if machine in ['aarch64', 'aarch']: + return result + elif c_type in ['mips', 'mips64', 'mips64el']: + if machine.find('mips') != -1: + return result + elif c_type in ['x86', 'x86-64']: + if machine in ['x86', 'x86-64']: + return result + return None + + # 下载插件安装包 + def __download_plugin(self, upgrade_plugin_name, upgrade_version): + ''' + @name 下载插件包 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @param upgrade_version 插件版本 + @return string 保存路径 + ''' + + pkey = '{}_pre'.format(upgrade_plugin_name) + pdata = public.get_user_info() + pdata['name'] = upgrade_plugin_name + pdata['version'] = upgrade_version + pdata['os'] = 'Linux' + pdata['environment_info'] = json.dumps(public.fetch_env_info(), ensure_ascii=False) + filename = '{}/{}.zip'.format(self.__tmp_path, upgrade_plugin_name) + if not os.path.exists(self.__tmp_path): + os.makedirs(self.__tmp_path, 384) + + if not cache.get(pkey): + import config, socket + import requests.packages.urllib3.util.connection as urllib3_conn + _ip_type = config.config().get_request_iptype() + old_family = urllib3_conn.allowed_gai_family + if _ip_type == 'ipv4': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET + elif _ip_type == 'ipv6': + urllib3_conn.allowed_gai_family = lambda: socket.AF_INET6 + try: + download_res = requests.post( + self.__download_url, + pdata, + headers=public.get_requests_headers(), + timeout=(60, 1800), + stream=True) + except Exception as ex: + str_ex = str(ex) + if 'Name or service not known' in str_ex: + return public.returnMsg(False, + '下载软件包时DNS解析失败,请检查服务器网络配置是否正常:

                                    Error: Name or service not known

                                    ') + elif 'Failed to establish a new connection' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Failed to establish a new connection

                                    ') + elif 'Read timed out' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Read timed out

                                    ') + elif 'Connection refused' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Connection refused

                                    ') + elif 'Remote end closed connection without response' in str_ex: + return public.returnMsg(False, + '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: Remote end closed connection without response

                                    ') + else: + return public.returnMsg(False, '连接下载节点失败,请尝试到【面板设置】页面切换通讯节点,或检查服务器网络是否正常:

                                    Error: {}

                                    '.format(str_ex)) + finally: + urllib3_conn.allowed_gai_family = old_family + + try: + headers_total_size = int(download_res.headers['File-size']) + except: + try: + return json.loads(download_res.text).json() + except: + if download_res.text.find('') != -1: + raise public.PanelError( + public.error_conn_cloud(download_res.text)) + raise public.PanelError(download_res.text) + + res_down_size = 0 + res_chunk_size = 8192 + last_time = time.time() + with open(filename, 'wb+') as with_res_f: + try: + for download_chunk in download_res.iter_content( + chunk_size=res_chunk_size): + if download_chunk: + with_res_f.write(download_chunk) + speed_last_size = len(download_chunk) + res_down_size += speed_last_size + res_start_time = time.time() + res_timeout = (res_start_time - last_time) + res_sec_speed = int(res_down_size / res_timeout) + pre_text = '{}/{}/{}'.format(res_down_size, + headers_total_size, + res_sec_speed) + cache.set(pkey, pre_text, 3600) + + except Exception as ex: + ex_str = str(ex) + if "Read timed out" in ex_str: + return public.returnMsg(False, '软件包下载超时,请重试: {}'.format(ex_str)) + if "No space left on device" in ex_str: + return public.returnMsg(False, "磁盘空间不足,请先清理后再试!") + finally: + with_res_f.close() + if cache.get(pkey): cache.delete(pkey) + + if public.FileMd5(filename) != download_res.headers['Content-md5']: + return public.returnMsg(False, '软件包校验失败,请更新软件列表,并重试') + else: + while True: + time.sleep(1) + if not cache.get(pkey): break + return '' + return filename + + # 获取插件安装包下载进度 + def __get_download_speed(self, upgrade_plugin_name): + ''' + @name 取插件下载进度 + @author hwliang<2021-06-21> + @param upgrade_plugin_name 插件名称 + @return dict + ''' + pkey = '{}_pre'.format(upgrade_plugin_name) + pre_text = cache.get(pkey) + if not pre_text: + return public.returnMsg(False, '指定进度信息不存在!') + result = {"status": True} + pre_tmp = pre_text.split('/') + result['down_size'], result['total_size'] = (int(pre_tmp[0]), + int(pre_tmp[1])) + result['down_pre'] = round( + result['down_size'] / result['total_size'] * 100, 1) + result['sec_speed'] = int(float(pre_tmp[2])) + result['need_time'] = int( + (result['total_size'] - result['down_size']) / result['sec_speed']) + return result + + + # 获取插件与授权信息 + def __ensure_plugin_list_obtained(self, force: bool = False): + if force or not self.__plugin_list: + self.__plugin_list = public.load_soft_list(force) diff --git a/class_v2/panel_push_v2.py b/class_v2/panel_push_v2.py new file mode 100644 index 00000000..01199ae7 --- /dev/null +++ b/class_v2/panel_push_v2.py @@ -0,0 +1,703 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 沐落 +# | Author: lx +# | 消息推送管理 +# | 对外方法 get_modules_list、install_module、uninstall_module、get_module_template、set_push_config、get_push_config、del_push_config +# +------------------------------------------------------------------- + +import os, sys + +panelPath = "/www/server/panel" +os.chdir(panelPath) +sys.path.insert(0,panelPath + "/class/") +import public,re,json,time +try: + from BTPanel import session +except : + pass +class panelPush: + + __conf_path = "{}/class/push/push.json".format(panelPath) + def __init__(self): + spath = '{}/class/push'.format(panelPath) + if not os.path.exists(spath): os.makedirs(spath) + + """ + @获取推送模块列表 + """ + def get_modules_list(self,get): + cpath = '{}/class/push/push_list.json'.format(panelPath) + try: + spath = os.path.dirname(cpath) + if not os.path.exists(spath): os.makedirs(spath) + + if 'force' in get or not os.path.exists(cpath): + if not 'download_url' in session: session['download_url'] = public.get_url() + public.downloadFile('{}/linux/panel/push/push_list.json'.format(session['download_url']),cpath) + except : pass + + if not os.path.exists(cpath): + return {} + + data = {} + push_list = self._get_conf() + module_list = public.get_modules('class/push') + + configs = json.loads(public.readFile(cpath)) + for p_info in configs: + p_info['data'] = {} + p_info['setup'] = False + p_info['info'] = False + key = p_info['name'] + try: + if hasattr(module_list, key): + p_info['setup'] = True + # if key in module_list: + # print(dir(module_list)) + # print(dir(module_list[key])) + # print(dir(getattr(module_list[key], key))) + push_module = getattr(module_list[key], key)() + p_info['info'] = push_module.get_version_info(None); + #格式化消息通道 + if key in push_list: + p_info['data'] = self.__get_push_list(push_list[key]) + #格式化返回执行周期 + if hasattr(push_module,'get_push_cycle'): + p_info['data'] = push_module.get_push_cycle(p_info['data']) + except : + return public.get_error_object(None) + data[key] = p_info + return data + + """ + 安装/更新消息通道模块 + @name 需要安装的模块名称 + """ + def install_module(self,get): + module_name = get.name + down_url = public.get_url() + + local_path = '{}/class/push'.format(panelPath) + if not os.path.exists(local_path): os.makedirs(local_path) + + sfile = '{}/{}.py'.format(local_path,module_name) + public.downloadFile('{}/linux/panel/push/{}.py'.format(down_url,module_name),sfile) + if not os.path.exists(sfile): return public.returnMsg(False, '[{}] Module installation failed'.format(module_name)) + if os.path.getsize(sfile) < 1024: return public.returnMsg(False, '[{}] Module installation failed'.format(module_name)) + + sfile = '{}/class/push/{}.html'.format(panelPath,module_name) + public.downloadFile('{}/linux/panel/push/{}.html'.format(down_url,module_name),sfile) + + return public.returnMsg(True, '[{}] Module installed successfully.'.format(module_name)) + + """ + 卸载消息通道模块 + @name 需要卸载的模块名称 + """ + def uninstall_module(self,get): + module_name = get.name + sfile = '{}/class/push/{}.py'.format(panelPath,module_name) + if os.path.exists(sfile): os.remove(sfile) + + return public.returnMsg(True, '[{}] Module uninstalled successfully'.format(module_name)) + + + """ + @获取模块执行日志 + """ + def get_module_logs(self,get): + module_name = get.name + id = get.id + return [] + + """ + 获取模块模板 + """ + def get_module_template(self,get): + sfile = '{}/class/push/{}.html'.format(panelPath,get.module_name) + + if not os.path.exists(sfile): + return public.returnMsg(False, 'template file does not exist!') + + shtml = public.readFile(sfile) + return public.returnMsg(True, shtml) + + + """ + @获取模块推送参数,如:panel_push ssl到期,服务停止 + """ + def get_module_config(self,get): + module = get.name + p_list = public.get_modules('class/push') + push_module = getattr(p_list[module], module)() + + if not module in p_list: + return public.returnMsg(False, 'The specified module [{}] is not installed!'.format(module)) + + if not hasattr(push_module,'get_module_config'): + return public.returnMsg(False, 'No get_module_config method exists for the specified module [{}].'.format(module)) + return push_module.get_module_config(get) + + + + """ + @获取模块配置项 + @优先调用模块内的get_push_config + """ + def get_push_config(self,get): + module = get.name + id = get.id + p_list = public.get_modules('class/push') + if not module in p_list: + return public.returnMsg(False, 'The specified module [{}] is not installed.'.format(module)) + + result = None + push_module = getattr(p_list[module], module)() + if not hasattr(push_module,'get_push_config'): + push_list = self._get_conf() + + res_data = public.returnMsg(False, 'The specified configuration was not found!') + res_data['code'] = 100 + if not module in push_list: + return res_data + if not id in push_list[module]: + return res_data + + result = push_list[module][id] + else: + result = push_module.get_push_config(get) + return self.get_push_user(result) + + def get_push_user(self,result): + + #获取发送给谁 + if not 'to_user' in result: + result['to_user'] = {} + if 'module' in result: + for s_module in result['module'].split(','): + result['to_user'][s_module] = 'default' + else: + return False + + info = {} + for s_module in result['module'].split(','): + msg_obj = public.init_msg(s_module) + if not msg_obj: continue + + info[s_module] = {} + data = msg_obj.get_config(None) + + if 'list' in data: + for key in result['to_user'][s_module].split(','): + if not key in data['list']: + continue + info[s_module][key] = data['list'][key] + result['user_info'] = info + return result + + """ + @设置推送配置 + @优先调用模块内的set_push_config + """ + def set_push_config(self,get): + module = get.name + id = get.id + p_list = public.get_modules('class/push') + + if not module in p_list: + return public.returnMsg(False, 'The specified module [{}] is not installed.'.format(module)) + + pdata = json.loads(get.data) + if not 'module' in pdata or not pdata['module']: + return public.returnMsg(False, 'The specified alarm method is not set, please select again.') + if module == "load_balance_push": + pdata = self.__get_args(pdata,'cycle', "500|502|503|504") + else: + pdata = self.__get_args(pdata, 'cycle', 1) + pdata = self.__get_args(pdata,'count',1) + pdata = self.__get_args(pdata,'interval',600) + pdata = self.__get_args(pdata,'key','') + pdata = self.__get_args(pdata,'push_count',0) + + nData = {} + for skey in ['key','type','cycle','count','interval','module','title','project','status','index','push_count']: + if skey in pdata: + nData[skey] = pdata[skey] + + public.set_module_logs('set_push_config',nData['type']) + class_obj = getattr(p_list[module], module)() + if hasattr(class_obj,'set_push_config'): + get['data'] = json.dumps(nData) + result = class_obj.set_push_config(get) + if 'status' in result: return result + + data = result + else: + data = self._get_conf() + if not module in data:data[module] = {} + data[module][id] = nData + + + public.writeFile(self.__conf_path,json.dumps(data)) + return public.returnMsg(True, 'Saved successfully') + + """ + @设置推送状态 + """ + def set_push_status(self,get): + id = get.id + module = get.name + + data = self._get_conf() + if not module in data: return public.returnMsg(True, 'module name does not exist!') + if not id in data[module]: return public.returnMsg(True, 'The specified push task does not exist!') + + status = int(get.status) + if status: + data[module][id]['status'] = True + else: + data[module][id]['status'] = False + public.writeFile(self.__conf_path,json.dumps(data)) + return public.returnMsg(True, 'Successful operation.') + """ + @删除指定配置 + """ + def del_push_config(self,get): + id = get.id + module = get.name + + p_list = public.get_modules('class/push') + if not module in p_list: + return public.returnMsg(False, 'The specified module {} is not installed.'.format(module)) + push_module = getattr(p_list[module], module)() + if not hasattr(push_module,'del_push_config'): + data = self._get_conf() + del data[module][id] + public.writeFile(self.__conf_path,json.dumps(data)) + return public.returnMsg(True, 'successfully deleted.') + + return push_module.del_push_config(get) + + """ + 获取消息通道配置列表 + """ + def get_push_msg_list(self,get): + data = {} + msgs = self.__get_msg_list() + from panelMessage import panelMessage + pm = panelMessage() + for x in msgs: + x['setup'] = False + key = x['name'] + try: + obj = pm.init_msg_module(key) + if obj: + x['setup'] = True + if key == 'sms':x['title'] = '{}?'.format(x['title']) + except : + pass + data[key] = x + return data + + """ + @ 获取消息推送配置 + """ + def _get_conf(self): + data = {} + try: + if os.path.exists(self.__conf_path): + data = json.loads(public.readFile(self.__conf_path)) + self.update_config(data) + except:pass + return data + + """ + @ 获取插件版本信息 + """ + def get_version_info(self): + """ + 获取版本信息 + """ + data = {} + data['ps'] = '' + data['version'] = '1.0' + data['date'] = '2020-07-14' + data['author'] = '宝塔' + data['help'] = 'http://www.bt.cn' + return data + + """ + @格式化推送对象 + """ + def format_push_data(self,push = ['dingding','weixin','feishu'], project = '', type = ''): + item = { + 'title':'', + 'project':project, + 'type':type, + 'cycle':1, + 'count':1, + 'keys':[], + 'helps':[], + 'push':push + } + return item + + + + def push_message_immediately(self, channel_data): + """推送消息到指定的消息通道,即时 + + Args: + channel_data(dict): + key: msg_channel, 消息通道名称,多个用逗号相连 + value: msg obj, 每种消息通道的消息内容格式,可能包含标题 + + Returns: + { + status: True/False, + msg: { + "email": {"status": msg}, + ... + } + } + """ + if type(channel_data) != dict: + return public.returnMsg(False, "The parameter is wrong") + + from panelMessage import panelMessage + pm = panelMessage() + channel_res = {} + res = { + "status": False, + "msg": channel_res + } + + for module, msg in channel_data.items(): + modules = [] + if module.find(",") != -1: + modules = module.split(",") + else: + modules.append(module) + for m_module in modules: + msg_obj = pm.init_msg_module(m_module) + if not msg_obj:continue + ret = msg_obj.push_data(msg) + if ret and "status" in ret and ret['status']: + res["status"] = True + channel_res[m_module] = ret + else: + msg = "Message push failed." + if "msg" in ret: + msg = ret["msg"] + channel_res[m_module] = public.returnMsg(False, msg) + return res + + """ + @格式为消息通道格式 + """ + def format_msg_data(self): + data = { + 'title':'', + 'to_email':'', + 'sms_type':'', + 'sms_argv':{}, + 'msg':'' + } + return data + + def __get_msg_list(self): + """ + 获取消息通道列表 + """ + data = [] + cpath = '{}/data/msg.json'.format(panelPath) + if not os.path.exists(cpath): + return data + try: + conf = public.readFile(cpath) + data = json.loads(conf) + except : + try: + time.sleep(0.5) + conf = public.readFile(cpath) + data = json.loads(conf) + except:pass + + return data + + def __get_args(self,data,key,val = ''): + """ + @获取默认参数 + """ + if not key in data: data[key] = val + if type(data[key]) != type(val): + data[key] = val + return data + + + def __get_push_list(self,data): + """ + @格式化列表数据 + """ + m_data = {} + result = {} + for x in self.__get_msg_list(): m_data[x['name']] = x + + for skey in data: + result[skey] = data[skey] + + m_list = [] + for x in data[skey]['module'].split(','): + if x in m_data: m_list.append(m_data[x]['title']) + result[skey]['m_title'] = '、'.join(m_list) + + m_cycle =[] + if data[skey]['cycle'] > 1: + m_cycle.append('every {} seconds'.format(data[skey]['cycle'])) + m_cycle.append('{} times, with an interval of {} seconds'.format(data[skey]['count'],data[skey]['interval'])) + result[skey]['m_cycle'] = ''.join(m_cycle) + + # 兼容旧版本没有返回project项,导致前端无法编辑问题 + if "project" not in result[skey] and "type" in result[skey]: + if result[skey]["type"] == "services": + services = ['nginx','apache',"pure-ftpd",'mysql','php-fpm','memcached','redis'] + _title = result[skey]['title'] + for s in services: + if _title.find(s)!=-1: + result[skey]["project"] = s + else: + result[skey]["project"] = result[skey]["type"] + if "project" in result[skey]: + if result[skey]["project"] == "FTP server": + result[skey]["project"] ="pure-ftpd" + return result + + + #************************************************推送 + """ + @推送data/push目录的所有文件 + """ + def push_messages_from_file(self): + + path = "{}/data/push".format(panelPath) + if not os.path.exists(path): os.makedirs(path) + + from panelMessage import panelMessage + pm = panelMessage() + + for x in os.listdir(path): + try: + spath = '{}/{}'.format(path,x) + if os.path.isdir(spath): continue + data = json.loads(public.readFile(spath)) + + msg_obj = pm.init_msg_module(data['module']) + if not msg_obj:continue + + ret = msg_obj.push_data(data) + if ret['status']: pass + + os.remove(spath) + except : + print(public.get_error_info()) + + """ + @消息推送线程 + """ + def start(self): + + total = 0 + interval = 5 + + tips = '{}/data/push/tips'.format(public.get_panel_path()) + if not os.path.exists(tips): os.makedirs(tips) + + try: + if True: + # 推送文件 + self.push_messages_from_file() + + # 调用推送子模块 + data = {} + is_write = False + path = "{}/class/push/push.json".format(panelPath) + + if os.path.exists(path): + data = public.readFile(path) + data = json.loads(data) + + p = public.get_modules('class/push') + for skey in data: + if len(data[skey]) <= 0: continue + if skey in ['panelLogin_push','panel_login']: continue #面板登录主动触发 + + total = None + obj = getattr(p[skey], skey)() + + for x in data[skey]: + try: + + item = data[skey][x] + item['id'] = x + if not item['status']: continue + if not item['module']: continue + if not 'index' in item: item['index'] = 0 + + if time.time() - item['index'] < item['interval']: + print('{} Interval not reached, skip.'.format(item['title'])) + continue + + #验证推送次数 + push_record = {} + tips_path = '{}/{}'.format(tips,x) + if 'push_count' in item and item['push_count'] > 0: + item['tips_list'] = [] + try: + push_record = json.loads(public.readFile(tips_path)) + except:pass + for k in push_record: + if push_record[k] < item['push_count']: + continue + item['tips_list'].append(k) + + #获取推送数据 + if not total: total = obj.get_total() + rdata = obj.get_push_data(item,total) + if not rdata: + continue + push_status = False + for m_module in item['module'].split(','): + if not m_module in rdata: + continue + + msg_obj = public.init_msg(m_module) + if not msg_obj:continue + + if 'to_user' in item and m_module in item['to_user']: + rdata[m_module]['to_user'] = item['to_user'][m_module] + + ret = msg_obj.push_data(rdata[m_module]) + data[skey][x]['index'] = rdata['index'] + is_write = True + push_status = True + + #获取是否推送成功. + if push_status: + if 'push_keys' in rdata: + for k in rdata['push_keys']: + if not k in push_record: push_record[k] = 0 + push_record[k] += 1 + public.writeFile(tips_path,json.dumps(push_record)) + except : + print(public.get_error_info()) + + if is_write: + public.writeFile(path,json.dumps(data)) + #time.sleep(interval) + except : + + print(public.get_error_info()) + + + def __get_login_panel_info(self): + """ + @name 获取面板登录列表 + @auther cjxin + @date 2022-09-29 + """ + import config + c_obj = config.config() + send_type = c_obj.get_login_send(None)['msg'] + if not send_type: + return False + return {"type":"panel_login","module":send_type,"interval":600,"status":True,"title":"Panel Login Alert","cycle":1,"count":1,"key":"","module_type":'site_push'} + + + def __get_ssh_login_info(self): + """ + @name 获取SSH登录列表 + @auther cjxin + @date 2022-09-29 + """ + import ssh_security + c_obj = ssh_security.ssh_security() + send_type = c_obj.get_login_send(None)['msg'] + if not send_type or send_type in ['error']: + return False + + return {"type":"ssh_login","module":send_type,"interval":600,"status":True,"title":"SSH login warning","cycle":1,"count":1,"key":"","module_type":'site_push'} + + + + def get_push_list(self,get): + """ + @获取所有推送列表 + """ + conf = self._get_conf() + for key in conf.keys(): + for x in conf[key]: + data = conf[key][x] + data['module_type'] = key + + conf[key][x] = self.get_push_user(data) + + if not 'site_push' in conf: conf['site_push'] = {} + + data = conf['site_push'] + for skey in ['panel_login','ssh_login']: + info = None + if skey in data: + del data[skey] + if skey in ['panel_login']: + info = self.__get_login_panel_info() + elif skey in ['ssh_login']: + info = self.__get_ssh_login_info() + + if info: + data[skey] = info + conf['site_push'] = data + return conf + + def get_push_logs(self,get): + """ + @name 获取推送日志 + """ + + p = 1 + limit = 15 + if 'p' in get: p = get.p + if 'limit' in get: limit = get.limit + + where = "type = 'Alarm notification'" + sql = public.M('logs') + + if hasattr(get, 'search'): + where = " and logs like '%{search}%' ".format(search=get.search) + + count = sql.where(where,()).count() + data = public.get_page(count,int(p),int(limit)) + data['data'] = public.M('logs').where(where,()).limit('{},{}'.format(data['shift'], data['row'])).order('id desc').select() + + return data + + # 兼容旧版本的告警 + def update_config(self, config): + if "site_push" not in config: + config["site_push"] = {} + if "panel_push" in config: + for k, v in config["panel_push"].items(): + if v["type"] != "endtime": + config["site_push"][k] = v + if "push_count" not in v: + v["push_count"] = 1 if v["type"] == "ssl" else 0 + del config["panel_push"] + public.writeFile(self.__conf_path, json.dumps(config)) + + +if __name__ == '__main__': + panelPush().start() diff --git a/class_v2/panel_redirect_v2.py b/class_v2/panel_redirect_v2.py new file mode 100644 index 00000000..a1bb4b85 --- /dev/null +++ b/class_v2/panel_redirect_v2.py @@ -0,0 +1,754 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2018 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +#------------------------------ +# URL重写类 +#------------------------------ +import os,public,json,re,sys,socket,shutil +from public.validate import Param +os.chdir("/www/server/panel") +class panelRedirect: + + setupPath = '/www/server' + __redirectfile = "/www/server/panel/data/redirect.conf" + __firsturl="" + + #匹配目标URL的域名并返回 + def GetToDomain(self,tourl): + if tourl: + rep = r"https?://([\w\-\.]+)" + tu = re.search(rep, tourl) + return tu.group(1) + + #取某个站点下所有域名 + def GetAllDomain(self,sitename): + domains = [] + id = public.M('sites').where("name=?",(sitename,)).getField('id') + tmp = public.M('domain').where("pid=?",(id,)).field('name').select() + for key in tmp: + domains.append(key["name"]) + return domains + + #检测被重定向域名是否有已经存在配置文件里面 + def __CheckRepeatDomain(self,get,action): + conf_data = self.__read_config(self.__redirectfile) + repeat = [] + # for conf in conf_data: + # if conf["sitename"] == get.sitename and conf["redirectname"] != get.redirectname: + # repeat += list(set(conf["redirectdomain"]).intersection(set(get.redirectdomain))) + + + for conf in conf_data: + if conf["sitename"] == get.sitename: + if action == "create": + if conf["redirectname"] == get.redirectname: + repeat += list(set(conf["redirectdomain"]).intersection(set(get.redirectdomain))) + else: + if conf["redirectname"] != get.redirectname: + repeat += list(set(conf["redirectdomain"]).intersection(set(get.redirectdomain))) + if list(set(repeat)): + return list(set(repeat)) + + #检测被重定向路径是否重复 + def __CheckRepeatPath(self, get): + conf_data = self.__read_config(self.__redirectfile) + repeat = [] + for conf in conf_data: + if conf["sitename"] == get.sitename and get.redirectpath != "": + if conf["redirectname"] != get.redirectname and conf["redirectpath"] == get.redirectpath: + repeat.append(get.redirectpath) + if repeat: + return repeat + # 检测URL是否可以访问 + def __CheckRedirectUrl(self, domainlist): + """ + @name 检测URL是否可以访问 + @author: hezhihong + @param domainlist: 域名列表 + """ + http_list=[] + import requests + for i in domainlist: + i = i.replace("*.", "") + https_url = "https://" + i + http_url = "http://" + i + try: + response=requests.get(https_url,timeout=20) + if response.status_code==200:return https_url + except:pass + try: + response=requests.get(http_url,timeout=20) + if response.status_code==200:http_list.append(http_url) + except:pass + if http_list:return http_list[0] + else:return [] + + # 计算proxyname md5 + def __calc_md5(self,redirectname): + import hashlib + md5 = hashlib.md5() + md5.update(redirectname.encode('utf-8')) + return md5.hexdigest() + + # 设置Nginx配置 + def SetRedirectNginx(self,get): + ng_redirectfile = "%s/panel/vhost/nginx/redirect/%s/*.conf" % (self.setupPath,get.sitename) + ng_file = self.setupPath + "/panel/vhost/nginx/" + get.sitename + ".conf" + p_conf = self.__read_config(self.__redirectfile) + if public.get_webserver() == 'nginx': + shutil.copyfile(ng_file, '/tmp/ng_file_bk.conf') + if os.path.exists(ng_file): + ng_conf = public.readFile(ng_file) + if not p_conf: + rep = "#SSL-END(\n|.)*\\/redirect\\/.*\\*.conf;" + ng_conf = re.sub(rep, '#SSL-END', ng_conf) + public.writeFile(ng_file, ng_conf) + return + sitenamelist = [] + for i in p_conf: + sitenamelist.append(i["sitename"]) + + if get.sitename in sitenamelist: + rep = r"include.*\/redirect\/.*\*.conf;" + if not re.search(rep,ng_conf): + ng_conf = ng_conf.replace("#SSL-END","#SSL-END\n\t%s\n\t" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + "include " + ng_redirectfile + ";") + public.writeFile(ng_file,ng_conf) + + else: + rep = "#SSL-END(\n|.)*\\/redirect\\/.*\\*.conf;" + ng_conf = re.sub(rep,'#SSL-END',ng_conf) + public.writeFile(ng_file, ng_conf) + + # 设置apache配置 + def SetRedirectApache(self,sitename): + ap_redirectfile = "%s/panel/vhost/apache/redirect/%s/*.conf" % (self.setupPath,sitename) + ap_file = self.setupPath + "/panel/vhost/apache/" + sitename + ".conf" + p_conf = public.readFile(self.__redirectfile) + if public.get_webserver() == 'apache': + shutil.copyfile(ap_file, '/tmp/ap_file_bk.conf') + if os.path.exists(ap_file): + ap_conf = public.readFile(ap_file) + if p_conf == "[]": + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + ap_conf = re.sub(rep, '', ap_conf) + public.writeFile(ap_file, ap_conf) + return + if sitename in p_conf: + rep = "%s(\n|.)+IncludeOptional.*\\/redirect\\/.*conf" % public.get_msg_gettext('#referenced redirect rule') + rep1 = "combined" + if not re.search(rep,ap_conf): + ap_conf = ap_conf.replace(rep1, rep1 + "\n\t%s" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') +"\n\tIncludeOptional " + ap_redirectfile) + public.writeFile(ap_file,ap_conf) + else: + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext('#referenced redirect rule, if commented, the configured redirect rule will be invalid') + ap_conf = re.sub(rep,'', ap_conf) + public.writeFile(ap_file, ap_conf) + + # 创建修改配置检测 + def __CheckRedirectStart(self,get,action=""): + isError = public.checkWebConfig() + if (isError != True): + return public.return_message(-1,0, 'An error was detected in the configuration file. Please solve it before proceeding') + if action == "create": + #检测名称是否重复 + if sys.version_info.major < 3: + if len(get.redirectname) < 3 or len(get.redirectname) > 15: + return public.return_message(-1,0, 'Database name cannot be more than 16 characters!') + else: + if len(get.redirectname.encode("utf-8")) < 3 or len(get.redirectname.encode("utf-8")) > 15: + return public.return_message(-1,0, 'Database name cannot be more than 16 characters!') + if 'errorpage' in get:is_error_page = True + else:is_error_page = False + if self.__CheckRedirect(get.sitename,get.redirectname,is_error_page): + return public.return_message(-1,0, 'Specified redirect name already exists') + #检测目标URL格式 + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + if 'tourl' in get and not re.match(rep, get.tourl): + return public.return_message(-1,0, 'Target URL format is wrong %s' + get.tourl) + + #非404页面重定向检测项 + if 'errorpage' not in get: + #检测是否选择域名 + if get.domainorpath == "domain": + if not json.loads(get.redirectdomain): + return public.return_message(-1,0, 'Please select redirected domain') + else: + if not get.redirectpath: + return public.return_message(-1,0, 'Please enter redirected path') + #repte = "[\\?\\=\\[\\]\\)\\(\\*\\&\\^\\%\\$\\#\\@\\!\\~\\`{\\}\\>\\<\\,\',\"]+" + # 检测路径格式 + if "/" not in get.redirectpath: + return public.return_message(-1,0, 'Path format is incorrect, the format is /xxx') + #if re.search(repte, get.redirectpath): + # return public.return_msg_gettext(False, "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]") + #检测域名是否已经存在配置文件 + repeatdomain = self.__CheckRepeatDomain(get,action) + if repeatdomain: + return public.return_message(-1,0, 'Redirected domain already exists {}' , (repeatdomain,)) + #检测路径是否有存在配置文件 + repeatpath = self.__CheckRepeatPath(get) + if repeatpath: + return public.return_message(-1,0, 'Redirected domain already exists {}' , (repeatpath,)) + #检测目标URL是否可用 + #if self.__CheckRedirectUrl(get): + # return public.return_msg_gettext(False, '目标URL无法访问') + + #检查目标URL的域名和被重定向的域名是否一样 + if get.domainorpath == "domain": + for d in json.loads(get.redirectdomain): + tu = self.GetToDomain(get.tourl) + if d == tu: + return public.return_message(-1,0,public.get_msg_gettext('Domain name {} is the same as the target domain name, please deselect it',(d,))) + + if get.domainorpath == "path": + domains = self.GetAllDomain(get.sitename) + rep = "https?://(.*)" + tu = re.search(rep,get.tourl).group(1) + for d in domains: + ad = "%s%s" % (d,get.redirectpath) #站点域名+重定向路径 + if tu == ad: + return public.return_message(0,0,'{}, the target URL is the same as the redirected path',(tu,)) + + #404页面重定向检测项 + else: + if 'tourl' not in get and 'topath' not in get: + return public.return_message(-1,0, 'Please select where you need to redirect to') + #网站首页访问检测 + if 'topath' in get and get.topath == "/": + domainlist=self.GetAllDomain(get.sitename) + self.__firsturl=self.__CheckRedirectUrl(domainlist) + if not self.__firsturl:return public.return_message(-1,0, 'The website cannot be accessed, please check whether the website is working properly') + + #创建重定向 + def CreateRedirect(self,get): + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + Param('redirectname').String(), + Param('domainorpath').String(), + Param('redirectpath').String(), + Param('redirectdomain').String(), + Param('tourl').String(), + Param('type').Integer(), + Param('holdpath').Integer(), + Param('redirecttype').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if self.__CheckRedirectStart(get,"create"): + return self.__CheckRedirectStart(get,"create") + redirectconf = self.__read_config(self.__redirectfile) + redirectconf.append({ + "sitename":get.sitename, + "redirectname":get.redirectname, + "tourl":get.tourl, + "redirectdomain":json.loads(get.redirectdomain), + "redirectpath":get.redirectpath, + "redirecttype":get.redirecttype, + "type":int(get.type), + "domainorpath":get.domainorpath, + "holdpath":int(get.holdpath) + }) + self.__write_config(self.__redirectfile,redirectconf) + self.SetRedirectNginx(get) + self.SetRedirectApache(get.sitename) + self.SetRedirect(get) + public.serviceReload() + return public.return_message(0,0, 'Successfully created file!') + + + def ModifyRedirect(self,get): + """ + @name 修改、启用、禁用重定向 + @author hezhihong + @param get.sitename 站点名称 + @param get.redirectname 重定向名称 + @param get.tourl 目标URL + @param get.redirectdomain 重定向域名 + @param get.redirectpath 重定向路径 + @param get.redirecttype 重定向类型 + @param get.type 重定向状态 0禁用 1启用 + @param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向 + @param get.holdpath 保留路径 0不保留 1保留 + @return json + """ + + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + Param('redirectname').String(), + Param('domainorpath').String(), + Param('redirectpath').String(), + Param('redirectdomain').String(), + Param('tourl').String(), + Param('type').Integer(), + Param('holdpath').Integer(), + Param('redirecttype').Integer(), + Param('errorpage').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 基本信息检查 + if self.__CheckRedirectStart(get): + return self.__CheckRedirectStart(get) + redirectconf = self.__read_config(self.__redirectfile) + for i in range(len(redirectconf)): + domainorpath='' + if 'domainorpath' not in get or not get.domainorpath:domainorpath='domain' if get.tourl else 'path' + if not domainorpath:domainorpath=get.domainorpath + if redirectconf[i]["redirectname"] == get.redirectname and redirectconf[i]["sitename"] == get.sitename: + redirectconf[i]["tourl"] =get.tourl if 'tourl' in get and get.tourl else "" + redirectconf[i]["redirectdomain"] = "" if 'redirectdomain' not in get else json.loads(get.redirectdomain) + redirectconf[i]["redirectpath"] ="" if 'redirectpath' not in get else get.redirectpath + redirectconf[i]["redirecttype"] ='' if 'redirecttype' not in get else get.redirecttype + redirectconf[i]["type"] = int(get.type) + redirectconf[i]["domainorpath"] = domainorpath + redirectconf[i]["topath"] = "" if 'topath' not in get else get.topath + redirectconf[i]["holdpath"] =999 if 'holdpath' not in get else int(get.holdpath) + redirectconf[i]["errorpage"]=1 if 'errorpage' in get and get.errorpage in [1,'1'] else 0 + self.__write_config(self.__redirectfile, redirectconf) + redirect_path=get.tourl.strip() if 'tourl' in get and get.tourl else get.topath.strip() + #404页面重定向 + is_del= True if int(get.type) == 0 else False + if 'errorpage' in get and get.errorpage in [1,'1']: + web_type=public.get_webserver() + if web_type == 'nginx': + self.SetRedirectNginx(get) + self.unset_nginx_conf(get.sitename) + self.get_nginx_conf(redirect_path,get.redirecttype,get.sitename,get.redirectname,is_del) + elif web_type == 'apache' or web_type == 'openlitespeed': + self.get_apache_conf(redirect_path,get.sitename,get.redirectname,str(get.redirecttype),is_del) + else: + return public.return_message(-1,0,'web server not installed or unknown web server') + #非404页面重定向 + else: + self.SetRedirect(get) + self.SetRedirectNginx(get) + self.SetRedirectApache(get.sitename) + public.serviceReload() + return public.return_message(0,0, 'Successfully modified') + + + def set_error_redirect(self,get): + """ + @name 设置404重定向 + @author hezhihong + @param get.sitename 站点名称 + @param get.redirectname 重定向名称(唯一key标志) + @param get.tourl 重定向到的url + @param get.topath 重定向到的路径 + @param get.redirecttype 重定向类型 + @param get.type 重定向状态 0禁用 1启用 + @param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向 + @param get.holdpath 是否保留原路径 0不保留 1保留 + @param get.errorpage 是否为404重定向 1是 0否 + @return json + """ + public.set_module_logs('panelRedirect','set_error_redirect') + check_result = self.__CheckRedirectStart(get,"create") + if check_result:return check_result + redirectconf = self.__read_config(self.__redirectfile) + site_name= get.sitename.strip() + redirect_path=get.tourl if 'tourl' in get and get.tourl and get.tourl.strip() else get.topath.strip() + redirectconf.append({ + "sitename":site_name, + "redirectname":get.redirectname, + "tourl":get.tourl if 'tourl' in get else '', + "redirectdomain":"", + "redirectpath":"", + "topath": get.topath.strip() if 'topath' in get and get.topath.strip() else "", + "redirecttype":get.redirecttype, + "type":int(get.type), + "domainorpath":'domain' if 'tourl' in get else 'path', + "holdpath":999, + "errorpage":1 + }) + self.__write_config(self.__redirectfile,redirectconf) + web_type=public.get_webserver() + if web_type == 'nginx': + self.SetRedirectNginx(get) + self.unset_nginx_conf(site_name) + self.get_nginx_conf(redirect_path,get.redirecttype,site_name,get.redirectname) + elif web_type == 'apache' or web_type == 'openlitespeed': + self.SetRedirectApache(get.sitename) + self.get_apache_conf(redirect_path,site_name,get.redirectname,str(get.redirecttype)) + else: + return public.returnMsg(False,'web server not installed or unknown web server') + public.serviceReload() + return public.returnMsg(True, '404 redirect set successfully') + + + def get_nginx_conf(self,redirect_path,redirecttype,site_name,redirectname,is_del=False): + """ + @name 设置nginx 404重定向 + @author hezhihong + @param redirect_path 重定向到(路径或地址) + @param redirecttype 重定向方式(301/302) + @param site_name 站点名称 + @param redirectname 重定向名称(唯一key标志) + @param is_del 是否删除 + """ + redirectname_md5 = self.__calc_md5(redirectname) + file_path= "%s/panel/vhost/nginx/redirect/%s" % (self.setupPath,site_name) + public.ExecShell("mkdir -p %s" % file_path) + file_path+= '/%s_%s.conf' % (redirectname_md5, site_name) + add_str='#REWRITE-START\nerror_page 404 = @notfound;\nlocation @notfound {\n return '+str(redirecttype)+' '+ redirect_path+'; \n}\n#REWRITE-END' + if os.path.isfile(file_path):public.ExecShell("rm -f %s" % file_path) + if not is_del:public.writeFile(file_path,add_str) + + def get_apache_conf(self,redirect_path,site_name,redirectname='',r_type='301',is_del=False): + """ + @name 设置apache 404重定向 + @author hezhihong + @param redirect_path 重定向到(路径或地址) + @param site_name 站点名称 + @param redirectname 重定向名称(唯一key标志) + @param is_del 是否删除 + @param r_type 重定向方式 + """ + if self.__firsturl:redirect_path=self.__firsturl + add_type=',R={}]'.format(str(r_type)) + add_str='#REWRITE-START\n\n RewriteEngine on\n RewriteCond %\\{REQUEST_FILENAME\\} !-f\n RewriteCond %{REQUEST_FILENAME} !-d\n RewriteRule . '+redirect_path+' [L'+add_type+'\n\n#REWRITE-END' + redirectname_md5 = self.__calc_md5(redirectname) + file_path= "%s/panel/vhost/apache/redirect/%s" % (self.setupPath,site_name) + public.ExecShell("mkdir -p %s" % file_path) + file_path+= '/%s_%s.conf' % (redirectname_md5, site_name) + if os.path.isfile(file_path):public.ExecShell("rm -f %s" % file_path) + if not is_del:public.writeFile(file_path,add_str) + + + def unset_nginx_conf(self,site_name): + """ + @name 取消设置nginx 404重定向 + @author hezhihong + @param site_name 站点名称 + """ + file_path='/www/server/panel/vhost/nginx/{}.conf'.format(site_name) + hta_path='/www/server/panel/vhost/rewrite/{}.conf'.format(site_name) + rep_str_one='error_page 404 /404.html' + rep_str_two='location = /404.html' + #清理nginx伪静态404配置 + hta_conf=public.readFile(hta_path) + if hta_conf: + hta_conf=self.replace_str_to_srt(hta_conf,rep_str_one,'','\n') + hta_conf=self.replace_str_to_srt(hta_conf,rep_str_two,'','}') + public.writeFile(hta_path,hta_conf) + #清理nginx网站配置文件非include方式404配置 + conf=public.readFile(file_path) + conf=self.replace_str_to_srt(conf,rep_str_one,'','\n') + conf=self.replace_str_to_srt(conf,rep_str_two,'','}') + public.writeFile(file_path,conf) + + + def replace_str_to_srt(self,conf,str_src,str_d,end_str,is_replace=False): + """ + @name 替换字符串 + @author hezhihong + @param conf 配置文件内容 + @param str_src 要替换的字符串 + @param str_d 替换成的字符串 + @param end_str 结束字符串 + @param is_replace 是否替换 + """ + if conf.strip(): + start_num=conf.find(str_src) + if start_num !=-1: + d_conf=conf[start_num:] + end_num = d_conf.find(end_str) + if end_num ==-1:end_num=len(conf) + d_conf=d_conf[:end_num+1] + if is_replace:conf=conf.replace(d_conf,str_d) + else:conf=conf.replace(d_conf,'') + return conf + + + + # 设置重定向 + def SetRedirect(self,get): + ng_file = self.setupPath + "/panel/vhost/nginx/" + get.sitename + ".conf" + ap_file = self.setupPath + "/panel/vhost/apache/" + get.sitename + ".conf" + p_conf = self.__read_config(self.__redirectfile) + # nginx + # 构建重定向配置 + if int(get.type) == 1: + domainstr = """ + if ($host ~ '^%s'){ + return %s %s%s; + } +""" + pathstr = """ + rewrite ^%s(.*) %s%s %s; +""" + rconf = "#REWRITE-START" + tourl = get.tourl + # if tourl[-1] == "/": + # tourl = tourl[:-1] + if get.domainorpath == "domain": + domains = json.loads(get.redirectdomain) + holdpath = int(get.holdpath) + if holdpath == 1: + for sd in domains: + rconf += domainstr % (sd,get.redirecttype,tourl,"$request_uri") + else: + for sd in domains: + rconf += domainstr % (sd,get.redirecttype,tourl,"") + if get.domainorpath == "path": + redirectpath = get.redirectpath + if get.redirecttype == "301": + redirecttype = "permanent" + else: + redirecttype = "redirect" + if int(get.holdpath) == 1 and redirecttype == "permanent": + rconf += pathstr % (redirectpath,tourl,"$1",redirecttype) + elif int(get.holdpath) == 0 and redirecttype == "permanent": + rconf += pathstr % (redirectpath, tourl,"",redirecttype) + elif int(get.holdpath) == 1 and redirecttype == "redirect": + rconf += pathstr % (redirectpath,tourl,"$1",redirecttype) + elif int(get.holdpath) == 0 and redirecttype == "redirect": + rconf += pathstr % (redirectpath, tourl,"",redirecttype) + rconf += "#REWRITE-END" + nginxrconf = rconf + + + + # 设置apache重定向 + + domainstr = """ + + RewriteEngine on + RewriteCond %s{HTTP_HOST} ^%s [NC] + RewriteRule ^(.*) %s%s [L,R=%s] + +""" + pathstr = """ + + RewriteEngine on + RewriteRule ^%s(.*) %s%s [L,R=%s] + +""" + rconf = "#REWRITE-START" + if get.domainorpath == "domain": + domains = json.loads(get.redirectdomain) + holdpath = int(get.holdpath) + if holdpath == 1: + for sd in domains: + rconf += domainstr % ("%",sd,tourl,"$1",get.redirecttype) + else: + for sd in domains: + rconf += domainstr % ("%",sd,tourl,"",get.redirecttype) + + if get.domainorpath == "path": + holdpath = int(get.holdpath) + if holdpath == 1: + rconf += pathstr % (get.redirectpath,tourl,"$1",get.redirecttype) + else: + rconf += pathstr % (get.redirectpath,tourl,"",get.redirecttype) + rconf += "#REWRITE-END" + apacherconf = rconf + + redirectname_md5 = self.__calc_md5(get.redirectname) + for w in ["nginx","apache"]: + redirectfile = "%s/panel/vhost/%s/redirect/%s/%s_%s.conf" % (self.setupPath,w,get.sitename,redirectname_md5, get.sitename) + redirectdir = "%s/panel/vhost/%s/redirect/%s" % (self.setupPath,w,get.sitename) + + if not os.path.exists(redirectdir): + public.ExecShell("mkdir -p %s" % redirectdir) + if w == "nginx": + public.writeFile(redirectfile,nginxrconf) + else: + public.writeFile(redirectfile, apacherconf) + isError = public.checkWebConfig() + if (isError != True): + if public.get_webserver() == "nginx": + shutil.copyfile('/tmp/ng_file_bk.conf', ng_file) + else: + shutil.copyfile('/tmp/ap_file_bk.conf', ap_file) + for i in range(len(p_conf) - 1, -1, -1): + if get.sitename == p_conf[i]["sitename"] and p_conf[i]["redirectname"]: + del(p_conf[i]) + return public.return_msg_gettext(False, '%s
                                    ' % public.get_msg_gettext('Sorry, something went wrong') + isError.replace("\n",'
                                    ') + '
                                    ') + + else: + redirectname_md5 = self.__calc_md5(get.redirectname) + redirectfile = "%s/panel/vhost/%s/redirect/%s/%s_%s.conf" + for w in ["apache","nginx"]: + rf = redirectfile % (self.setupPath,w ,get.sitename, redirectname_md5, get.sitename) + if os.path.exists(rf): + os.remove(rf) + + def del_redirect_multiple(self,get): + ''' + @name 批量删除重定向 + @author zhwen<2020-11-21> + @param site_id 1 + @param redirectnames test,baohu + ''' + redirectnames = get.redirectnames.split(',') + del_successfully = [] + del_failed = {} + get.sitename = public.M('sites').where("id=?", (get.site_id,)).getField('name') + for redirectname in redirectnames: + get.redirectname = redirectname + try: + get.multiple = 1 + result = self.DeleteRedirect(get,multiple=1) + if result['status'] !=0: + del_failed[redirectname] = result['msg'] + continue + del_successfully.append(redirectname) + except: + del_failed[redirectname]=public.get_msg_gettext('There was an error deleting, please try again.') + public.serviceReload() + return public.return_message(0,0,{'msg': public.get_msg_gettext('Delete redirects [{}] successfully',(','.join(del_successfully),)), 'error': del_failed, + 'success': del_successfully}) + + def DeleteRedirect(self,get,multiple=None): + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + Param('redirectname').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + redirectconf = self.__read_config(self.__redirectfile) + sitename = get.sitename + redirectname = get.redirectname + for i in range(len(redirectconf)): + if redirectconf[i]["sitename"] == sitename and redirectconf[i]["redirectname"] == redirectname: + proxyname_md5 = self.__calc_md5(redirectconf[i]["redirectname"]) + public.ExecShell("rm -f %s/panel/vhost/nginx/redirect/%s/%s_%s.conf" % (self.setupPath,redirectconf[i]["sitename"],proxyname_md5,redirectconf[i]["sitename"])) + public.ExecShell("rm -f %s/panel/vhost/apache/redirect/%s/%s_%s.conf" % (self.setupPath,redirectconf[i]["sitename"],proxyname_md5, redirectconf[i]["sitename"])) + del redirectconf[i] + self.__write_config(self.__redirectfile,redirectconf) + self.SetRedirectNginx(get) + self.SetRedirectApache(get.sitename) + if not multiple: + public.serviceReload() + return public.return_message(0,0, 'Successfully deleted') + + def GetRedirectList(self,get): + """ + @name 获取重定向列表 + @author hezhihong + @param get.sitename 站点名 + @param get.errorpage 1:404页面重定向 0:非404页面重定向 + @return 重定向列表 + """ + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + redirectconf = self.__read_config(self.__redirectfile) + sitename = get.sitename + redirectlist = [] + for i in redirectconf: + if i["sitename"] == sitename: + if 'errorpage' in get and 'errorpage' in i and int(get.errorpage)!=int(i['errorpage']):continue + if 'errorpage' in i and i['errorpage'] in [1,'1']:i['redirectdomain']=['404 page'] + redirectlist.append(i) + print(redirectlist) + return public.return_message(0,0,redirectlist) + + def ClearOldRedirect(self,get): + for i in ["apache","nginx"]: + conf_path = "%s/panel/vhost/%s/%s.conf" % (self.setupPath,i,get.sitename) + old_conf = public.readFile(conf_path) + rep ="" + if i == "nginx": + rep += "#301-START\n+[\\s\\w\\:\\/\\.\\;\\$]+#301-END" + if i == "apache": + rep += "#301-START[\n\\<\\>\\w\\.\\s\\^\\*\\$\\/\\[\\]\\(\\)\\:\\,\\=]+#301-END" + conf = re.sub(rep, "", old_conf) + public.writeFile(conf_path, conf) + public.serviceReload() + return public.return_msg_gettext(False, 'Old redirection cleaned') + + # 取重定向配置文件 + def GetRedirectFile(self,get): + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + Param('redirectname').String(), + Param('webserver').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + import files_v2 as files + conf = self.__read_config(self.__redirectfile) + sitename = get.sitename + redirectname = get.redirectname + proxyname_md5 = self.__calc_md5(redirectname) + if get.webserver == 'openlitespeed': + get.webserver = 'apache' + get.path = "%s/panel/vhost/%s/redirect/%s/%s_%s.conf" % (self.setupPath, get.webserver, sitename,proxyname_md5,sitename) + for i in conf: + if redirectname == i["redirectname"] and sitename == i["sitename"] and i["type"] != 1: + return public.return_message(-1,0, 'Redirection suspended') + f = files.files() + return_message =f.GetFileBody(get) + return_message['message']['file']=get.path + return return_message + + # 保存重定向配置文件 + def SaveRedirectFile(self,get): + import files_v2 as files + f = files.files() + return f.SaveFileBody(get) + # return public.return_msg_gettext(True, '保存成功') + + def __CheckRedirect(self,sitename,redirectname,is_error=False): + conf_data = self.__read_config(self.__redirectfile) + for i in conf_data: + if i["sitename"] == sitename: + if is_error and "errorpage" in i and i["errorpage"] in [1,'1']: + return i + if i["redirectname"] == redirectname: + return i + + + # 读配置 + def __read_config(self, path): + if not os.path.exists(path): + public.writeFile(path, '[]') + upBody = public.readFile(path) + if not upBody: upBody = '[]' + return json.loads(upBody) + + # 写配置 + def __write_config(self ,path, data): + return public.writeFile(path, json.dumps(data)) + + diff --git a/class_v2/panel_restore_v2.py b/class_v2/panel_restore_v2.py new file mode 100644 index 00000000..93924381 --- /dev/null +++ b/class_v2/panel_restore_v2.py @@ -0,0 +1,226 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: zhwwen +# ------------------------------------------------------------------- +# +# ------------------------------ +# 网站恢复 +# ------------------------------ +import public,os,files,sys +from time import sleep +from public.validate import Param +class panel_restore: + + _local_file = '/tmp/{}' + _progress_file = '/tmp/restore_site.log' + + # def __init__(self): + # # 清空日志文件 + + def _progress_rewrite(self,content,mothed='a+'): + sleep(2) + public.writeFile(self._progress_file,content+'\n',mothed) + + def _get_local_backup_path(self): + local_backdir = public.M('config').field('backup_path').find()['backup_path'] + return local_backdir + + def _build_aws_backup_path(self,btype,file_name,domain): + config_file = "/www/server/panel/plugin/aws_s3/config.conf" + conf = public.readFile(config_file) + backup_path = conf.split('|')[-1].strip()+btype+'/'+ domain + '/' + file_name + return backup_path + + def _build_google_backup_path(self,btype,file_name,domain): + object_name = 'bt_backup/{}/{}/{}'.format(btype,domain,file_name) + return object_name + + def _get_backfile_method(self,filename): + backup_info = public.M('backup').where("name=?", (filename,)).getField('filename') + backup_info = backup_info.split('|') + if len(backup_info) >= 3: + method = backup_info[1] + else: + method = 'local' + return method + + def _remove_old_website_file_to_trush(self,args): + # 将原来目录移至回收站 + files.files().DeleteDir(args) + + def _get_website_info(self,site_id): + site_name = public.M('sites').where("id=?",(site_id,)).getField('name') + site_path = public.M('sites').where("id=?",(site_id,)).getField('path') + return {'site_name':site_name,'site_path':site_path} + + def _restore_backup(self,local_backup_file_path,site_info,args): + + # 判断备份文件是否存在,如果不存在继续检查是否远程备份 + if not os.path.exists(local_backup_file_path): + self._progress_rewrite('No backup file found: {}'.format(str(local_backup_file_path))) + return public.return_message(-1,0, 'Panel does not find the backup file: {}'.format(local_backup_file_path)) + # 将网站目录移至回收站 + self._progress_rewrite('Move the current website directory to the recycle bin: {}'.format(str(args.path))) + self._remove_old_website_file_to_trush(args) + if not os.path.exists(args.path): + self._progress_rewrite('Create an empty directory for the site: {}'.format(str(args.path))) + os.makedirs(site_info['site_path']) + if 'zip' in args.file_name: + uncompress_comand = 'unzip' + else: + uncompress_comand = 'tar -zxvf' + self._progress_rewrite('The decompression command is: {}'.format(str(uncompress_comand))) + self._progress_rewrite('Start to restore data......') + public.ExecShell('cd {} && {} {} >> /tmp/restore_site.log'.format(site_info['site_path'], uncompress_comand, local_backup_file_path)) + if len(os.listdir(site_info['site_path'])) == 2: + public.ExecShell('cd {s} && mv {s}/{d}/* .'.format(s=site_info['site_path'],d=site_info['site_name'])) + public.ExecShell('cd {s} && rmdir {d}'.format(s=site_info['site_path'],d=site_info['site_name'])) + # 将文件全新设置为644,文件夹设置为755 + self._progress_rewrite('Setting site permissions......') + files.files().fix_permissions(args) + + def _download_aws_file(self,args,btype='site'): + sys.path.append('/www/server/panel/plugin/aws_s3') + import aws_s3_main + aws3 = aws_s3_main.aws_s3_main() + self._progress_rewrite('Building S3 download path...') + download_file = self._build_aws_backup_path(btype,args.file_name,args.obj_name) + self._progress_rewrite('The download path is:{}'.format(download_file)) + self._local_file = self._local_file.format(args.file_name) + self._progress_rewrite('Backup file will be downloaded to:{}'.format(self._local_file)) + self._progress_rewrite('Starting to download file:{}'.format(self._local_file)) + args.object_name = download_file + args.local_file = self._local_file + aws3.download_file(args) + self._progress_rewrite('Download completed:{}'.format(self._local_file)) + return self._local_file + + def _download_google_cloud_file(self,args,btype='site'): + sys.path.append('/www/server/panel/plugin/gcloud_storage') + import gcloud_storage_main + gs = gcloud_storage_main.gcloud_storage_main() + self._progress_rewrite('Building Google Store download path...') + download_file = self._build_google_backup_path(btype,args.file_name,args.obj_name) + self._progress_rewrite('The download path is:{}'.format(download_file)) + self._local_file = self._local_file.format(args.file_name) + self._progress_rewrite('Backup file will be downloaded to:{}'.format(self._local_file)) + self._progress_rewrite('Starting to download file:{}'.format(self._local_file)) + args.source_blob_name = download_file + args.destination_file_name = self._local_file + gs.download_blob(args) + self._progress_rewrite('Download completed:{}'.format(self._local_file)) + return self._local_file + + def _download_google_drive_file(self,args): + sys.path.append('/www/server/panel/plugin/gdrive') + import gdrive_main + gd = gdrive_main.gdrive_main() + self._local_file = self._local_file.format(args.file_name) + self._progress_rewrite('Backup file will be downloaded to:{}'.format(self._local_file)) + self._progress_rewrite('Starting to download file:{}'.format(self._local_file)) + gd.download_file(args.file_name) + self._progress_rewrite('Download completed:{}'.format(self._local_file)) + return self._local_file + + def restore_website_backup(self,args): + """ + @name 恢复站点文件 + @author zhwen + @parma file_name 备份得文件名 + @parma site_id 网站id + """ + # 校验参数 + try: + args.validate([ + Param('file_name').String(), + Param('site_id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self._progress_rewrite('','w') + site_info = self._get_website_info(args.site_id) + self._progress_rewrite('Get site information:{}'.format(str(site_info))) + args.path = site_info['site_path'] + args.obj_name = site_info['site_name'] + self._progress_rewrite('Get the site path:{}'.format(str(site_info['site_path']))) + local_backup_path = self._get_local_backup_path() + local_backup_file_path = local_backup_path +'/site/'+ args.file_name + self._progress_rewrite('Get the local backup file path: {}'.format(str(local_backup_path))) + backup_method = self._get_backfile_method(args.file_name) + self._progress_rewrite('Get the backup method: {}'.format(str(backup_method))) + if backup_method == 'local': + self._progress_rewrite('Start to restore local backup files: {}'.format(str(local_backup_file_path))) + result = self._restore_backup(local_backup_file_path,site_info,args) + if result: + self._progress_rewrite('Recovery failed: {}'.format(str(site_info['site_path']))) + return result + elif backup_method == 'aws_s3': + self._download_aws_file(args) + result = self._restore_backup(self._local_file, site_info, args) + elif backup_method == 'Google Cloud': + self._download_google_cloud_file(args) + result = self._restore_backup(self._local_file, site_info, args) + elif backup_method == 'Google Drive': + self._download_google_drive_file(args) + result = self._restore_backup(self._local_file, site_info, args) + else: + return public.return_msg_gettext(False,'Currently only supports restoring local, Google storage and AWS S3 backups') + if os.path.exists(self._local_file): + os.remove(self._local_file) + if result: + self._progress_rewrite('Recovery failed: {}'.format(str(site_info['site_path']))) + return result + self._progress_rewrite('Successful recovery: {}'.format(str(site_info['site_path']))) + return public.return_message(0,0,'Restore Successful') + + # 取任务进度 + def get_progress(self, get): + """ + @name 获取进度日志 + @author zhwen + """ + # result = public.GetNumLines(self._progress_file, 20) + result = public.ExecShell('tail -n 20 {}'.format(self._progress_file))[0] + if len(result) < 1: + return public.return_message(0,0,"Wait for the restore to start") + return public.return_message(0,0,result) + + # 恢复数据库 + def restore_db_backup(self,args): + """ + @name 恢复站点文件 + @author zhwen + @parma file_name 备份得文件名 /www/backup/database/db_test_com_20200817_112722.sql.gz|Google Drive|db_test_com_20200817_112722.sql.gz + @parma obj_name 数据库名 + """ + if "|" not in args.file: + return public.returnMsg(True,'success') + try: + backup_info = args.file.split('|') + args.file_name = backup_info[-1] + args.obj_name = args.name + backup_method = backup_info[1] + self._progress_rewrite('','w') + self._progress_rewrite('Restoring database...') + self._progress_rewrite('Get the backup method: {}'.format(str(backup_method))) + if backup_method == 'aws_s3': + self._download_aws_file(args,'database') + elif backup_method == 'Google Cloud': + self._download_google_cloud_file(args,'database') + elif backup_method == 'Google Drive': + self._download_google_drive_file(args) + else: + return public.returnMsg(False,'Currently only supports restoring local, Google storage and AWS S3 backups') + public.ExecShell('mv {} {}/database'.format(self._local_file, self._get_local_backup_path())) + return public.returnMsg(True,'success') + except: + return public.returnMsg(False,"Download error!") diff --git a/class_v2/panel_site_v2.py b/class_v2/panel_site_v2.py new file mode 100644 index 00000000..da7794c5 --- /dev/null +++ b/class_v2/panel_site_v2.py @@ -0,0 +1,7633 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------ +# 网站管理类 +# ------------------------------ +import io, re, public, os, sys, shutil, json, hashlib, socket, time + +try: + import OpenSSL +except: + os.system("btpip install pyOpenSSL -I") + import OpenSSL +import base64 + +try: + from BTPanel import session +except: + pass +from panel_redirect_v2 import panelRedirect +import site_dir_auth_v2 as site_dir_auth +from public.validate import Param + + +class panelSite(panelRedirect): + siteName = None # 网站名称 + sitePath = None # 根目录 + sitePort = None # 端口 + phpVersion = None # PHP版本 + setupPath = None # 安装路径 + isWriteLogs = None # 是否写日志 + nginx_conf_bak = '/tmp/backup_nginx.conf' + apache_conf_bak = '/tmp/backup_apache.conf' + is_ipv6 = False + conf_dir = '{}/vhost/config'.format(public.get_panel_path()) # 防盗链配置 + + def __init__(self): + self.setupPath = public.get_setup_path() + path = self.setupPath + '/panel/vhost/nginx' + if not os.path.exists(path): public.ExecShell("mkdir -p " + path + " && chmod -R 644 " + path) + path = self.setupPath + '/panel/vhost/apache' + if not os.path.exists(path): public.ExecShell("mkdir -p " + path + " && chmod -R 644 " + path) + path = self.setupPath + '/panel/vhost/rewrite' + if not os.path.exists(path): public.ExecShell("mkdir -p " + path + " && chmod -R 644 " + path) + path = self.setupPath + '/stop' + if not os.path.exists(path + '/index.html'): + public.ExecShell('mkdir -p ' + path) + public.ExecShell('wget -O ' + path + '/index.html ' + public.get_url() + '/stop_en.html &') + self.__proxyfile = '{}/data/proxyfile.json'.format(public.get_panel_path()) + self.OldConfigFile() + if os.path.exists(self.nginx_conf_bak): os.remove(self.nginx_conf_bak) + if os.path.exists(self.apache_conf_bak): os.remove(self.apache_conf_bak) + self.is_ipv6 = os.path.exists(self.setupPath + '/panel/data/ipv6.pl') + sys.setrecursionlimit(1000000) + try: + if not os.path.isdir(self.conf_dir): + os.makedirs(self.conf_dir, 0o755) + except PermissionError as e: + public.WriteLog('信息获取', "{}失败: {}".format(self.conf_dir, str(e))) + + # 默认配置文件 + def check_default(self): + nginx = self.setupPath + '/panel/vhost/nginx' + httpd = self.setupPath + '/panel/vhost/apache' + httpd_default = ''' + ServerAdmin webmaster@example.com + DocumentRoot "/www/server/apache/htdocs" + ServerName bt.default.com + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + DirectoryIndex index.html + +''' + + listen_ipv6 = '' + if self.is_ipv6: listen_ipv6 = "\n listen [::]:80;" + nginx_default = '''server +{ + listen 80;%s + server_name _; + index index.html; + root /www/server/nginx/html; +}''' % listen_ipv6 + if not os.path.exists(httpd + '/0.default.conf') and not os.path.exists( + httpd + '/default.conf'): public.writeFile(httpd + '/0.default.conf', httpd_default) + if not os.path.exists(nginx + '/0.default.conf') and not os.path.exists( + nginx + '/default.conf'): public.writeFile(nginx + '/0.default.conf', nginx_default) + + # 添加apache端口 + def apacheAddPort(self, port): + port = str(port) + filename = self.setupPath + '/apache/conf/extra/httpd-ssl.conf' + if os.path.exists(filename): + ssl_conf = public.readFile(filename) + if ssl_conf: + if ssl_conf.find('Listen 443') != -1: + ssl_conf = ssl_conf.replace('Listen 443', '') + public.writeFile(filename, ssl_conf) + + filename = self.setupPath + '/apache/conf/httpd.conf' + if not os.path.exists(filename): return + allConf = public.readFile(filename) + rep = r"Listen\s+([0-9]+)\n" + tmp = re.findall(rep, allConf) + if not tmp: return False + for key in tmp: + if key == port: return False + + listen = "\nListen " + tmp[0] + "\n" + listen_ipv6 = '' + # if self.is_ipv6: listen_ipv6 = "\nListen [::]:" + port + allConf = allConf.replace(listen, listen + "Listen " + port + listen_ipv6 + "\n") + public.writeFile(filename, allConf) + return True + + # 添加到apache + def apacheAdd(self): + import time + listen = '' + if self.sitePort != '80': self.apacheAddPort(self.sitePort) + acc = public.md5(str(time.time()))[0:8] + try: + httpdVersion = public.readFile(self.setupPath + '/apache/version.pl').strip() + except: + httpdVersion = "" + if httpdVersion == '2.2': + vName = '' + if self.sitePort != '80' and self.sitePort != '443': + vName = "NameVirtualHost *:" + self.sitePort + "\n" + phpConfig = "" + apaOpt = r"Order allow,deny\n\t\tAllow from all" + else: + vName = "" + phpConfig = ''' + #PHP + + SetHandler "proxy:%s" + + ''' % (public.get_php_proxy(self.phpVersion, 'apache'),) + apaOpt = 'Require all granted' + + conf = r'''%s + ServerAdmin webmaster@example.com + DocumentRoot "%s" + ServerName %s.%s + ServerAlias %s + #errorDocument 404 /404.html + ErrorLog "%s-error_log" + CustomLog "%s-access_log" combined + + #DENY FILES + + Order allow,deny + Deny from all + + %s + #PATH + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + %s + DirectoryIndex index.php index.html index.htm default.php default.html default.htm + +''' % (vName, self.sitePort, self.sitePath, acc, self.siteName, self.siteName, + public.GetConfigValue('logs_path') + '/' + self.siteName, + public.GetConfigValue('logs_path') + '/' + self.siteName, phpConfig, self.sitePath, apaOpt) + + htaccess = self.sitePath + '/.htaccess' + if not os.path.exists(htaccess): public.writeFile(htaccess, ' ') + public.ExecShell('chmod -R 644 ' + htaccess) + public.ExecShell('chown -R www:www ' + htaccess) + + filename = self.setupPath + '/panel/vhost/apache/' + self.siteName + '.conf' + public.writeFile(filename, conf) + return True + + # 添加到nginx + def nginxAdd(self): + listen_ipv6 = '' + if self.is_ipv6: listen_ipv6 = "\n listen [::]:%s;" % self.sitePort + + conf = r'''server +{{ + listen {listen_port};{listen_ipv6} + server_name {site_name}; + index index.php index.html index.htm default.php default.htm default.html; + root {site_path}; + + #SSL-START {ssl_start_msg} + #error_page 404/404.html; + #SSL-END + + #ERROR-PAGE-START {err_page_msg} + error_page 404 /404.html; + error_page 502 /502.html; + #ERROR-PAGE-END + + #PHP-INFO-START {php_info_start} + include enable-php-{php_version}.conf; + #PHP-INFO-END + + #REWRITE-START {rewrite_start_msg} + include {setup_path}/panel/vhost/rewrite/{site_name}.conf; + #REWRITE-END + + {description} + location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) + {{ + return 404; + }} + + {description1} + location ~ \.well-known{{ + allow all; + }} + + #Prohibit putting sensitive files in certificate verification directory + if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {{ + return 403; + }} + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + {{ + expires 30d; + error_log /dev/null; + access_log /dev/null; + }} + + location ~ .*\.(js|css)?$ + {{ + expires 12h; + error_log /dev/null; + access_log /dev/null; + }} + access_log {log_path}/{site_name}.log; + error_log {log_path}/{site_name}.error.log; +}}'''.format( + listen_port=self.sitePort, + listen_ipv6=listen_ipv6, + site_path=self.sitePath, + ssl_start_msg=public.get_msg_gettext( + 'SSL related configuration, do NOT delete or modify the next line of commented-out 404 rules'), + err_page_msg=public.get_msg_gettext( + 'Error page configuration, allowed to be commented, deleted or modified'), + php_info_start=public.get_msg_gettext( + 'PHP reference configuration, allowed to be commented, deleted or modified'), + php_version=self.phpVersion, + setup_path=self.setupPath, + rewrite_start_msg=public.get_msg_gettext( + 'URL rewrite rule reference, any modification will invalidate the rewrite rules set by the panel'), + description=public.get_msg_gettext('# Forbidden files or directories'), + description1=public.get_msg_gettext( + '# Directory verification related settings for one-click application for SSL certificate'), + log_path=public.GetConfigValue('logs_path'), + site_name=self.siteName + ) + + # 写配置文件 + filename = self.setupPath + '/panel/vhost/nginx/' + self.siteName + '.conf' + public.writeFile(filename, conf) + + # 生成伪静态文件 + urlrewritePath = self.setupPath + '/panel/vhost/rewrite' + urlrewriteFile = urlrewritePath + '/' + self.siteName + '.conf' + if not os.path.exists(urlrewritePath): os.makedirs(urlrewritePath) + open(urlrewriteFile, 'w+').close() + if not os.path.exists(urlrewritePath): + public.writeFile(urlrewritePath, '') + return True + + # 重新生成nginx配置文件 + def rep_site_config(self, get): + self.siteName = get.siteName + siteInfo = public.M('sites').where('name=?', (self.siteName,)).field('id,path,port').find() + siteInfo['domains'] = public.M('domains').where('pid=?', (siteInfo['id'],)).field('name,port').select() + siteInfo['binding'] = public.M('binding').where('pid=?', (siteInfo['id'],)).field('domain,path').select() + + # openlitespeed + def openlitespeed_add_site(self, get, init_args=None): + # 写主配置httpd_config.conf + # 操作默认监听配置 + if not self.sitePath: + return_message = public.return_msg_gettext(False, "Not specify parameter [sitePath]") + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if init_args: + self.siteName = init_args['sitename'] + self.phpVersion = init_args['phpv'] + self.sitePath = init_args['rundir'] + conf_dir = self.setupPath + '/panel/vhost/openlitespeed/' + if not os.path.exists(conf_dir): + os.makedirs(conf_dir) + file = conf_dir + self.siteName + '.conf' + + v_h = """ +#VHOST_TYPE BT_SITENAME START +virtualhost BT_SITENAME { +vhRoot BT_RUN_PATH +configFile /www/server/panel/vhost/openlitespeed/detail/BT_SITENAME.conf +allowSymbolLink 1 +enableScript 1 +restrained 1 +setUIDMode 0 +} +#VHOST_TYPE BT_SITENAME END +""" + self.old_name = self.siteName + if hasattr(get, "dirName"): + self.siteName = self.siteName + "_" + get.dirName + # sub_dir = self.sitePath + "/" + get.dirName + v_h = v_h.replace("VHOST_TYPE", "SUBDIR") + v_h = v_h.replace("BT_SITENAME", self.siteName) + v_h = v_h.replace("BT_RUN_PATH", self.sitePath) + # extp_name = self.siteName + "_" + get.dirName + else: + self.openlitespeed_domain(get) + v_h = v_h.replace("VHOST_TYPE", "VHOST") + v_h = v_h.replace("BT_SITENAME", self.siteName) + v_h = v_h.replace("BT_RUN_PATH", self.sitePath) + # extp_name = self.siteName + public.writeFile(file, v_h, "a+") + # 写vhost + conf = '''docRoot $VH_ROOT +vhDomain $VH_NAME +adminEmails example@example.com +enableGzip 1 +enableIpGeo 1 + +index { + useServer 0 + indexFiles index.php,index.html +} + +errorlog /www/wwwlogs/$VH_NAME_ols.error_log { + useServer 0 + logLevel ERROR + rollingSize 10M +} + +accesslog /www/wwwlogs/$VH_NAME_ols.access_log { + useServer 0 + logFormat '%{X-Forwarded-For}i %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"' + logHeaders 5 + rollingSize 10M + keepDays 10 compressArchive 1 +} + +scripthandler { + add lsapi:BT_EXTP_NAME php +} + +extprocessor BTSITENAME { + type lsapi + address UDS://tmp/lshttpd/BT_EXTP_NAME.sock + maxConns 20 + env LSAPI_CHILDREN=20 + initTimeout 600 + retryTimeout 0 + persistConn 1 + pcKeepAliveTimeout 1 + respBuffer 0 + autoStart 1 + path /usr/local/lsws/lsphpBTPHPV/bin/lsphp + extUser www + extGroup www + memSoftLimit 2047M + memHardLimit 2047M + procSoftLimit 400 + procHardLimit 500 +} + +phpIniOverride { +php_admin_value open_basedir "/tmp/:BT_RUN_PATH" +} + +expires { + enableExpires 1 + expiresByType image/*=A43200,text/css=A43200,application/x-javascript=A43200,application/javascript=A43200,font/*=A43200,application/x-font-ttf=A43200 +} + +rewrite { + enable 1 + autoLoadHtaccess 1 + include /www/server/panel/vhost/openlitespeed/proxy/BTSITENAME/urlrewrite/*.conf + include /www/server/panel/vhost/apache/redirect/BTSITENAME/*.conf + include /www/server/panel/vhost/openlitespeed/redirect/BTSITENAME/*.conf +} +include /www/server/panel/vhost/openlitespeed/proxy/BTSITENAME/*.conf +''' + open_base_path = self.sitePath + if self.sitePath[-1] != '/': + open_base_path = self.sitePath + '/' + conf = conf.replace('BT_RUN_PATH', open_base_path) + conf = conf.replace('BT_EXTP_NAME', self.siteName) + conf = conf.replace('BTPHPV', self.phpVersion) + conf = conf.replace('BTSITENAME', self.siteName) + + # 写配置文件 + conf_dir = self.setupPath + '/panel/vhost/openlitespeed/detail/' + if not os.path.exists(conf_dir): + os.makedirs(conf_dir) + file = conf_dir + self.siteName + '.conf' + # if hasattr(get,"dirName"): + # file = conf_dir + self.siteName +'_'+get.dirName+ '.conf' + public.writeFile(file, conf) + + # 生成伪静态文件 + # urlrewritePath = self.setupPath + '/panel/vhost/rewrite' + # urlrewriteFile = urlrewritePath + '/' + self.siteName + '.conf' + # if not os.path.exists(urlrewritePath): os.makedirs(urlrewritePath) + # open(urlrewriteFile, 'w+').close() + return public.return_message(0, 0, True) + + # 上传CSV文件 + # def upload_csv(self, get): + # import files + # f = files.files() + # get.f_path = '/tmp/multiple_website.csv' + # result = f.upload(get) + # return result + + # 处理CSV内容 + def __process_cvs(self, key): + import csv + with open('/tmp/multiple_website.csv') as f: + f_csv = csv.reader(f) + # result = [i for i in f_csv] + return [dict(zip(key, i)) for i in [i for i in f_csv if "FTP" not in i]] + + # 批量创建网站 + def __create_website_mulitiple(self, websites_info, site_path, get): + create_successfully = {} + create_failed = {} + for data in websites_info: + if not data: + continue + try: + domains = data['website'].split(',') + website_name = domains[0].split(':')[0] + data['port'] = '80' if len(domains[0].split(':')) < 2 else domains[0].split(':')[1] + get.webname = json.dumps({"domain": website_name, "domainlist": domains[1:], "count": 0}) + get.path = data['path'] if 'path' in data and data['path'] != '0' and data[ + 'path'] != '1' else site_path + '/' + website_name + get.version = data['version'] if 'version' in data and data['version'] != '0' else '00' + get.ftp = 'true' if 'ftp' in data and data['ftp'] == '1' else 'false' + get.sql = 'true' if 'sql' in data and data['sql'] == '1' else 'false' + get.port = data['port'] if 'port' in data else '80' + get.codeing = 'utf8' + get.type = 'PHP' + get.type_id = '0' + get.ps = '' + create_other = {} + create_other['db_status'] = False + create_other['ftp_status'] = False + if get.sql == 'true': + create_other['db_pass'] = get.datapassword = public.gen_password(16) + create_other['db_user'] = get.datauser = website_name.replace('.', '_') + create_other['db_status'] = True + if get.ftp == 'true': + create_other['ftp_pass'] = get.ftp_password = public.gen_password(16) + create_other['ftp_user'] = get.ftp_username = website_name.replace('.', '_') + create_other['ftp_status'] = True + result = self.AddSite(get, multiple=1) + if result['status'] == -1: + create_failed[domains[0]] = result['message'] + continue + create_successfully[domains[0]] = create_other + except: + create_failed[domains[0]] = public.get_msg_gettext('There was an error creating, please try again.') + return_message = { + 'msg': public.get_msg_gettext('Create the website [ {} ] successfully', (','.join(create_successfully),)), + 'error': create_failed, + 'success': create_successfully} + return public.return_message(0, 0, return_message) + + # 批量创建网站 + def create_website_multiple(self, get): + ''' + @name 批量创建网站 + @author zhwen<2020-11-26> + @param create_type txt/csv txt格式为 “网站名|网站路径|是否创建FTP|是否创建数据库|PHP版本” 每个网站一行 + "aaa.com:88,bbb.com|/www/wwwserver/aaa.com/或1|1/0|1/0|0/73" + csv格式为 “网站名|网站端口|网站路径|PHP版本|是否创建数据库|是否创建FTP” + @param websites_content "[[aaa.com|80|/www/wwwserver/aaa.com/|1|1|73]...." + ''' + # 校验参数 + try: + get.validate([ + Param('create_type').String(), + Param('websites_content').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + key = ['website', 'path', 'ftp', 'sql', 'version'] + site_path = public.M('config').getField('sites_path') + if get.create_type == 'txt': + websites_info = [dict(zip(key, i)) for i in + [i.strip().split('|') for i in json.loads(get.websites_content)]] + else: + websites_info = self.__process_cvs(key) + res = self.__create_website_mulitiple(websites_info, site_path, get) + public.serviceReload() + return res + + # 检测enable-php-00.conf + def check_php_conf(self): + try: + file = '/www/server/nginx/conf/enable-php.conf' + if public.get_webserver() != "nginx": + return + if os.path.exists(file): + return + php_v = os.listdir('/www/server/php') + if not php_v: + return + conf = public.readFile('/www/server/nginx/conf/enable-php-{}.conf'.format(php_v[0])) + public.writeFile(file, conf) + except: + pass + + # 添加站点 + def AddSite(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('webname').String(), + Param('type').String(), + Param('ps').String(), + Param('path').String(), + Param('version').String(), + Param('sql').String(), + Param('datapassword').String(), + Param('codeing').String(), + Param('port').Integer(), + Param('type_id').Integer(), + Param('set_ssl').Integer(), + Param('force_ssl').Integer(), + Param('ftp').Bool(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if get.get('ftp', False): + # 校验参数 + try: + get.validate([ + Param('ftp_username').String(), + Param('ftp_password').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not get.path: + return_message = public.return_msg_gettext(False, "Please fill in the website path") + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if get.path == "/": + return_message = public.return_msg_gettext(False, "The website path cannot be the root directory [/]") + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + rep_email = r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?" + if hasattr(get, 'email'): + if not re.search(rep_email, get.email): + return_message = public.return_msg_gettext(False, "Please check if the [Email] format correct") + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if hasattr(get, 'password') and hasattr(get, 'pw_weak'): + l = public.check_password(get.password) + if l == 0 and get.pw_weak == 'off': + return_message = public.return_msg_gettext(False, + 'Password very weak, if you are sure to use it, please tick [ Allow weak passwords ]') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + # 判断Mysql PHP 没有安装不能继续 + if not os.path.exists("/www/server/mysql") or not os.path.exists("/www/server/php"): + return_message = public.return_msg_gettext(False, + 'Please install Mysql and PHP first!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + self.check_default() + + self.check_php_conf() + + isError = public.checkWebConfig() + if isError != True: + return_message = public.return_msg_gettext(False, + 'ERROR: %s

                                    ' % public.get_msg_gettext( + 'An error was detected in the configuration file. Please solve it before proceeding') + isError.replace( + "\n", '
                                    ') + '
                                    ') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + import json, files + + get.path = self.__get_site_format_path(get.path) + if not public.check_site_path(get.path): + a, c = public.get_sys_path() + return_message = public.return_msg_gettext(False, + 'Please do not set the website root directory to the system main directory:
                                    {}'.format( + "
                                    ".join(a + c))) + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + try: + siteMenu = json.loads(get.webname) + except: + return_message = public.return_msg_gettext(False, + 'The format of the webname parameter is incorrect, it should be a parseable JSON string') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + self.siteName = self.ToPunycode(siteMenu['domain'].strip().split(':')[0]).strip().lower() + self.sitePath = self.ToPunycodePath(self.GetPath(get.path.replace(' ', ''))).strip() + self.sitePort = get.port.strip().replace(' ', '') + + if self.sitePort == "": get.port = "80" + if not public.checkPort(self.sitePort): + return_message = public.return_msg_gettext(False, 'Port range is incorrect! should be between 100-65535') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + for domain in siteMenu['domainlist']: + if not len(domain.split(':')) == 2: + continue + if not public.checkPort(domain.split(':')[1]): + return_message = public.return_msg_gettext(False, + 'Port range is incorrect! should be between 100-65535') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + if hasattr(get, 'version'): + self.phpVersion = get.version.replace(' ', '') + else: + self.phpVersion = '00' + + if not self.phpVersion: self.phpVersion = '00' + + php_version = self.GetPHPVersion(get, False) + is_phpv = False + for php_v in php_version: + if self.phpVersion == php_v['version']: + is_phpv = True + break + if not is_phpv: + return_message = public.return_msg_gettext(False, 'Requested PHP version does NOT exist!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + domain = None + # if siteMenu['count']: + # domain = get.domain.replace(' ','') + # 表单验证 + if not self.__check_site_path(self.sitePath): + return_message = public.return_msg_gettext(False, + 'System critical directory cannot be used as site directory') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if len(self.phpVersion) < 2: + return_message = public.return_msg_gettext(False, 'PHP version cannot be empty') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + reg = r"^([\w\-\*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$" + if not re.match(reg, self.siteName): + return_message = public.return_msg_gettext(False, 'Format of primary domain is incorrect') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if self.siteName.find('*') != -1: + return_message = public.return_msg_gettext(False, 'Primary domain cannot be wildcard DNS record') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if self.sitePath[-1] == '.': + return_message = public.return_msg_gettext(False, 'DIR_END_WITH', ("'.'",)) + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + if not domain: domain = self.siteName + + # 是否重复 + sql = public.M('sites') + if sql.where("name=?", (self.siteName,)).count(): + return_message = public.return_msg_gettext(False, 'The site you tried to add already exists!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + opid = public.M('domain').where("name=?", (self.siteName,)).getField('pid') + + if opid: + if public.M('sites').where('id=?', (opid,)).count(): + return_message = public.return_msg_gettext(False, 'The domain you tried to add already exists!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + public.M('domain').where('pid=?', (opid,)).delete() + + if public.M('binding').where('domain=?', (self.siteName,)).count(): + return_message = public.return_msg_gettext(False, 'The domain you tried to add already exists!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + # 创建根目录 + if not os.path.exists(self.sitePath): + try: + os.makedirs(self.sitePath) + except Exception as ex: + return_message = public.return_msg_gettext(False, 'Failed to create site document root, {}', (ex,)) + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + public.ExecShell('chmod -R 755 ' + self.sitePath) + public.ExecShell('chown -R www:www ' + self.sitePath) + + # 创建basedir + self.DelUserInI(self.sitePath) + userIni = self.sitePath + '/.user.ini' + if not os.path.exists(userIni): + public.writeFile(userIni, 'open_basedir=' + self.sitePath + '/:/tmp/') + public.ExecShell('chmod 644 ' + userIni) + public.ExecShell('chown root:root ' + userIni) + public.ExecShell('chattr +i ' + userIni) + + ngx_open_basedir_path = self.setupPath + '/panel/vhost/open_basedir/nginx' + if not os.path.exists(ngx_open_basedir_path): + os.makedirs(ngx_open_basedir_path, 384) + ngx_open_basedir_file = ngx_open_basedir_path + '/{}.conf'.format(self.siteName) + ngx_open_basedir_body = '''set $bt_safe_dir "open_basedir"; +set $bt_safe_open "{}/:/tmp/";'''.format(self.sitePath) + public.writeFile(ngx_open_basedir_file, ngx_open_basedir_body) + + # 创建默认文档 + index = self.sitePath + '/index.html' + if not os.path.exists(index): + public.writeFile(index, public.readFile('data/defaultDoc.html')) + public.ExecShell('chmod -R 644 ' + index) + public.ExecShell('chown -R www:www ' + index) + + # 创建自定义404页 + doc404 = self.sitePath + '/404.html' + if not os.path.exists(doc404): + public.writeFile(doc404, public.readFile('data/404.html')) + public.ExecShell('chmod -R 644 ' + doc404) + public.ExecShell('chown -R www:www ' + doc404) + # 创建自定义502页面 + doc502 = self.sitePath + '/502.html' + if not os.path.exists(doc502) and os.path.exists('data/502.html'): + public.writeFile(doc502, public.readFile('data/502.html')) + public.ExecShell('chmod -R 644 ' + doc502) + public.ExecShell('chown -R www:www ' + doc502) + + # 写入配置 + result = self.nginxAdd() + result = self.apacheAdd() + result = self.openlitespeed_add_site(get) + + # 检查处理结果 + if not result: + return_message = public.return_msg_gettext(False, 'Failed to add, write configuraton ERROR!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + ps = public.xssencode2(get.ps) + # 添加放行端口 + if self.sitePort != '80': + import firewalls + get.port = self.sitePort + get.ps = self.siteName + firewalls.firewalls().AddAcceptPort(get) + + if not hasattr(get, 'type_id'): get.type_id = 0 + if not hasattr(get, 'project_type'): get.project_type = "PHP" + public.check_domain_cloud(self.siteName) + # 统计wordpress安装次数 + if get.project_type == 'WP': + public.count_wp() + # 写入数据库 + get.pid = sql.table('sites').add('name,path,status,ps,type_id,addtime,project_type', ( + self.siteName, self.sitePath, '1', ps, get.type_id, public.getDate(), get.project_type)) + + # 添加更多域名 + for domain in siteMenu['domainlist']: + get.domain = domain + get.webname = self.siteName + get.id = str(get.pid) + self.AddDomain(get, multiple) + + sql.table('domain').add('pid,name,port,addtime', (get.pid, self.siteName, self.sitePort, public.getDate())) + + data = {} + data['siteStatus'] = True + data['siteId'] = get.pid + + # 添加FTP + data['ftpStatus'] = False + if 'ftp' not in get: + get.ftp = False + if get.ftp == 'true': + import ftp + get.ps = self.siteName + result = ftp.ftp().AddUser(get) + if result['status']: + data['ftpStatus'] = True + data['ftpUser'] = get.ftp_username + data['ftpPass'] = get.ftp_password + + # 添加数据库 + data['databaseStatus'] = False + if 'sql' not in get: + get.sql = 'false' + if get.sql == 'true' or get.sql == 'MySQL': + import database + if len(get.datauser) > 16: get.datauser = get.datauser[:16] + get.name = get.datauser + get.db_user = get.datauser + get.password = get.datapassword + get.address = '127.0.0.1' + get.ps = self.siteName + result = database.database().AddDatabase(get) + + public.print_log(result) + + if result['status']: + data['databaseStatus'] = True + data['databaseUser'] = get.datauser + data['databasePass'] = get.datapassword + data['d_id'] = str(public.M('databases').where('pid=?', (get.pid,)).field('id').find()['id']) + if not multiple: + public.serviceReload() + data = self._set_ssl(get, data, siteMenu) + data = self._set_redirect(get, data['message']) + public.write_log_gettext('Site manager', 'Successfully added site [{}]!', (self.siteName,)) + return data + + # 添加WP站点 + def AddWPSite(self, args: public.dict_obj): + # 参数验证 + try: + args.validate([ + Param('webname').String(), + Param('type').String(), + Param('ps').String(), + Param('path').String(), + Param('version').String(), + Param('sql').String(), + Param('datauser').String(), + Param('datapassword').String(), + Param('codeing').String(), + Param('port').Integer(), + Param('type_id').Integer(), + Param('set_ssl').Integer(), + Param('force_ssl').Integer(), + Param('ftp').Bool(), + Param('weblog_title').Require().Xss(), + Param('language').Require(), + Param('user_name').Require().Xss(), + Param('email').Require().Email(), + Param('pw_weak').Require().String('in', ['on', 'off']), + Param('password').Require(), + Param('prefix').Require().Xss(), + Param('enable_cache').Require().Integer(), + Param('enable_whl').Integer(), + Param('whl_page').SafePath(), + Param('whl_redirect_admin').SafePath(), + Param('package_version').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + from copy import deepcopy + args_dup = public.to_dict_obj(deepcopy(args.get_items())) + + data = self.AddSite(args_dup) + + if int(data.get('status', 0)) != 0: + return data + + data = data.get('message', {}) + + if int(data.get('databaseStatus', 0)) != 1: + raise public.HintException(public.get_msg_gettext('Database creation failed. Please check mysql running status and try again.')) + + return self.deploy_wp(public.to_dict_obj({ + 'domain': json.loads(args.webname).get('domain', ''), + 'weblog_title': args.weblog_title, + 'language': args.get('language', ''), + 'php_version': args.version, + 'user_name': args.user_name, + 'admin_password': args.password, + 'pw_weak': args.pw_weak, + 'admin_email': args.email, + 'prefix': args.prefix, + 'enable_cache': args.enable_cache, + 'd_id': data.get('d_id', 0), + 's_id': data.get('siteId', 0), + 'enable_whl': args.get('enable_whl', 0), + 'whl_page': args.get('whl_page', 'login'), + 'whl_redirect_admin': args.get('whl_redirect_admin', '404'), + 'package_version': args.get('package_version', None), + })) + + def _set_redirect(self, get, data): + try: + if not hasattr(get, 'redirect') and not get.redirect: + data['redirect'] = False + return public.return_message(0, 0, data) + import panel_redirect_v2 as panelRedirect + get.redirectdomain = json.dumps([get.redirect]) + get.sitename = get.webname + get.redirectname = 'Default' + get.redirecttype = '301' + get.holdpath = '1' + get.type = '1' + get.domainorpath = 'domain' + get.redirectpath = '' + if data['ssl']: + get.tourl = 'https://{}'.format(get.tourl) + else: + get.tourl = 'http://{}'.format(get.tourl) + panelRedirect.panelRedirect().CreateRedirect(get) + data['redirect'] = True + except: + data['redirect'] = str(public.get_error_info()) + data['redirect'] = True + return public.return_message(0, 0, data) + + def _set_ssl(self, get, data, siteMenu): + try: + if get.set_ssl != '1': + data['ssl'] = False + return public.return_message(0, 0, data) + import acme_v2 + ssl_domain = siteMenu['domainlist'] + ssl_domain.append(self.siteName) + get.id = str(get.pid) + get.auth_to = str(get.pid) + get.auth_type = 'http' + get.auto_wildcard = '' + get.domains = json.dumps(ssl_domain) + result = acme_v2.acme_v2().apply_cert_api(get) + get.type = '1' + get.siteName = self.siteName + get.key = result['private_key'] + get.csr = result['cert'] + result['root'] + self.SetSSL(get) + data['ssl'] = True + if hasattr(get, 'force_ssl') and get.force_ssl == '1': + get.siteName = self.siteName + self.HttpToHttps(get) + except: + data['ssl'] = str(public.get_error_info()) + return public.return_message(0, 0, data) + + def __get_site_format_path(self, path): + path = path.replace('//', '/') + if path[-1:] == '/': + path = path[:-1] + return path + + def __check_site_path(self, path): + path = self.__get_site_format_path(path) + other_path = public.M('config').where("id=?", ('1',)).field('sites_path,backup_path').find() + if path == other_path['sites_path'] or path == other_path['backup_path']: return False + return True + + def delete_website_multiple(self, get): + ''' + @name 批量删除网站 + @author zhwen<2020-11-17> + @param sites_id "1,2" + @param ftp 0/1 + @param database 0/1 + @param path 0/1 + ''' + sites_id = get.sites_id.split(',') + del_successfully = [] + del_failed = {} + for site_id in sites_id: + get.id = site_id + get.webname = public.M('sites').where("id=?", (site_id,)).getField('name') + if not get.webname: + continue + try: + self.DeleteSite(get, multiple=1) + del_successfully.append(get.webname) + except: + del_failed[get.webname] = public.get_msg_gettext('There was an error deleting, please try again.') + pass + public.serviceReload() + return_message = { + 'msg': public.get_msg_gettext('Delete website [{}] successfully', (','.join(del_successfully),)), + 'error': del_failed, + 'success': del_successfully} + + return public.return_message(0, 0, return_message) + + # 删除站点 + def DeleteSite(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('webname').String(), + Param('id').Integer(), + Param('ftp').Integer(), + Param('database').Integer(), + Param('path').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + proxyconf = self.__read_config(self.__proxyfile) + id = get.id + if public.M('sites').where('id=?', (id,)).count() < 1: + return_message = public.return_msg_gettext(False, 'Specified site does NOT exist') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + siteName = get.webname + get.siteName = siteName + self.CloseTomcat(get) + # 删除反向代理 + for i in range(len(proxyconf) - 1, -1, -1): + if proxyconf[i]["sitename"] == siteName: + del proxyconf[i] + self.__write_config(self.__proxyfile, proxyconf) + + m_path = self.setupPath + '/panel/vhost/nginx/proxy/' + siteName + if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) + + m_path = self.setupPath + '/panel/vhost/apache/proxy/' + siteName + if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) + + # 删除目录保护 + _dir_aith_file = "%s/panel/data/site_dir_auth.json" % self.setupPath + _dir_aith_conf = public.readFile(_dir_aith_file) + if _dir_aith_conf: + try: + _dir_aith_conf = json.loads(_dir_aith_conf) + if siteName in _dir_aith_conf: + del (_dir_aith_conf[siteName]) + except: + pass + self.__write_config(_dir_aith_file, _dir_aith_conf) + + dir_aith_path = self.setupPath + '/panel/vhost/nginx/dir_auth/' + siteName + if os.path.exists(dir_aith_path): public.ExecShell("rm -rf %s" % dir_aith_path) + + dir_aith_path = self.setupPath + '/panel/vhost/apache/dir_auth/' + siteName + if os.path.exists(dir_aith_path): public.ExecShell("rm -rf %s" % dir_aith_path) + + # 删除重定向 + __redirectfile = "%s/panel/data/redirect.conf" % self.setupPath + redirectconf = self.__read_config(__redirectfile) + for i in range(len(redirectconf) - 1, -1, -1): + if redirectconf[i]["sitename"] == siteName: + del redirectconf[i] + self.__write_config(__redirectfile, redirectconf) + m_path = self.setupPath + '/panel/vhost/nginx/redirect/' + siteName + if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) + m_path = self.setupPath + '/panel/vhost/apache/redirect/' + siteName + if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) + + # 删除配置文件 + confPath = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if os.path.exists(confPath): os.remove(confPath) + + confPath = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if os.path.exists(confPath): os.remove(confPath) + open_basedir_file = self.setupPath + '/panel/vhost/open_basedir/nginx/' + siteName + '.conf' + if os.path.exists(open_basedir_file): os.remove(open_basedir_file) + + # 删除openlitespeed配置 + vhost_file = "/www/server/panel/vhost/openlitespeed/{}.conf".format(siteName) + if os.path.exists(vhost_file): + public.ExecShell('rm -f {}*'.format(vhost_file)) + vhost_detail_file = "/www/server/panel/vhost/openlitespeed/detail/{}.conf".format(siteName) + if os.path.exists(vhost_detail_file): + public.ExecShell('rm -f {}*'.format(vhost_detail_file)) + vhost_ssl_file = "/www/server/panel/vhost/openlitespeed/detail/ssl/{}.conf".format(siteName) + if os.path.exists(vhost_ssl_file): + public.ExecShell('rm -f {}*'.format(vhost_ssl_file)) + vhost_sub_file = "/www/server/panel/vhost/openlitespeed/detail/{}_sub.conf".format(siteName) + if os.path.exists(vhost_sub_file): + public.ExecShell('rm -f {}*'.format(vhost_sub_file)) + vhost_redirect_file = "/www/server/panel/vhost/openlitespeed/redirect/{}".format(siteName) + if os.path.exists(vhost_redirect_file): + public.ExecShell('rm -rf {}*'.format(vhost_redirect_file)) + vhost_proxy_file = "/www/server/panel/vhost/openlitespeed/proxy/{}".format(siteName) + if os.path.exists(vhost_proxy_file): + public.ExecShell('rm -rf {}*'.format(vhost_proxy_file)) + + # 删除openlitespeed监听配置 + self._del_ols_listen_conf(siteName) + + # 删除伪静态文件 + # filename = confPath+'/rewrite/'+siteName+'.conf' + filename = '/www/server/panel/vhost/rewrite/' + siteName + '.conf' + if os.path.exists(filename): + os.remove(filename) + public.ExecShell("rm -f " + confPath + '/rewrite/' + siteName + "_*") + + # 删除日志文件 + filename = public.GetConfigValue('logs_path') + '/' + siteName + '*' + public.ExecShell("rm -f " + filename) + + # 删除证书 + # crtPath = '/etc/letsencrypt/live/'+siteName + # if os.path.exists(crtPath): + # import shutil + # shutil.rmtree(crtPath) + + # 删除日志 + public.ExecShell("rm -f " + public.GetConfigValue('logs_path') + '/' + siteName + "-*") + + # 删除备份 + # public.ExecShell("rm -f "+session['config']['backup_path']+'/site/'+siteName+'_*') + + # 删除根目录 + if 'path' in get: + if get.path == '1': + import files_v2 as files + get.path = self.__get_site_format_path(public.M('sites').where("id=?", (id,)).getField('path')) + if self.__check_site_path(get.path): + if public.M('sites').where("path=?", (get.path,)).count() < 2: + files.files().DeleteDir(get) + get.path = '1' + + # 重载配置 + if not multiple: + public.serviceReload() + + # 从数据库删除 + public.M('sites').where("id=?", (id,)).delete() + public.M('binding').where("pid=?", (id,)).delete() + public.M('domain').where("pid=?", (id,)).delete() + public.M('wordpress_onekey').where("s_id=?", (id,)).delete() + public.write_log_gettext('Site manager', 'Successfully deleted site!', (siteName,)) + + # 是否删除关联数据库 + if hasattr(get, 'database'): + if get.database == '1': + find = public.M('databases').where("pid=?", (id,)).field('id,name').find() + if find: + import database_v2 as database + get.name = find['name'] + get.id = find['id'] + database.database().DeleteDatabase(get) + + # 是否删除关联FTP + if hasattr(get, 'ftp'): + if get.ftp == '1': + find = public.M('ftps').where("pid=?", (id,)).field('id,name').find() + if find: + import ftp_v2 as ftp + get.username = find['name'] + get.id = find['id'] + ftp.ftp().DeleteUser(get) + return_message = public.return_msg_gettext(True, 'Successfully deleted site!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + def _del_ols_listen_conf(self, sitename): + conf_dir = '/www/server/panel/vhost/openlitespeed/listen/' + if not os.path.exists(conf_dir): + return False + for i in os.listdir(conf_dir): + file_name = conf_dir + i + if os.path.isdir(file_name): + continue + conf = public.readFile(file_name) + if not conf: + continue + map_rep = r'map\s+{}.*'.format(sitename) + conf = re.sub(map_rep, '', conf) + if "map" not in conf: + public.ExecShell('rm -f {}*'.format(file_name)) + continue + public.writeFile(file_name, conf) + + # 域名编码转换 + def ToPunycode(self, domain): + import re + if sys.version_info[0] == 2: domain = domain.encode('utf8') + tmp = domain.split('.') + newdomain = '' + for dkey in tmp: + if dkey == '*': continue + # 匹配非ascii字符 + match = re.search(u"[\x80-\xff]+", dkey) + if not match: match = re.search(u"[\u4e00-\u9fa5]+", dkey) + if not match: + newdomain += dkey + '.' + else: + if sys.version_info[0] == 2: + newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' + else: + newdomain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' + if tmp[0] == '*': newdomain = "*." + newdomain + return newdomain[0:-1] + + # 中文路径处理 + def ToPunycodePath(self, path): + if sys.version_info[0] == 2: path = path.encode('utf-8') + if os.path.exists(path): return path + import re + match = re.search(u"[\x80-\xff]+", path) + if not match: match = re.search(u"[\u4e00-\u9fa5]+", path) + if not match: return path + npath = '' + for ph in path.split('/'): + npath += '/' + self.ToPunycode(ph) + return npath.replace('//', '/') + + def export_domains(self, args): + ''' + @name 导出域名列表 + @author hwliang<2020-10-27> + @param args{ + siteName: string<网站名称> + } + @return string + ''' + + pid = public.M('sites').where('name=?', args.siteName).getField('id') + domains = public.M('domain').where('pid=?', pid).field('name,port').select() + text_data = [] + for domain in domains: + text_data.append("{}:{}".format(domain['name'], domain['port'])) + data = "\n".join(text_data) + return public.send_file(data, '{}_domains'.format(args.siteName)) + + def import_domains(self, args): + ''' + @name 导入域名 + @author hwliang<2020-10-27> + @param args{ + siteName: string<网站名称> + domains: string<域名列表> 每行一个 格式: 域名:端口 + } + @return string + ''' + + domains_tmp = args.domains.split("\n") + get = public.dict_obj() + get.webname = args.siteName + get.id = public.M('sites').where('name=?', args.siteName).getField('id') + domains = [] + for domain in domains_tmp: + if public.M('domain').where('name=?', domain.split(':')[0]).count(): + continue + domains.append(domain) + + get.domain = ','.join(domains) + return self.AddDomain(get) + + # 添加域名 + + def AddDomain(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('webname').String(), + Param('domain').String(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 检查配置文件 + isError = public.checkWebConfig() + if isError != True: + return public.return_message(-1, 0, 'ERROR: %s

                                    ' % public.get_msg_gettext( + 'An error was detected in the configuration file. Please solve it before proceeding') + isError.replace( + "\n", '
                                    ') + '
                                    ') + + if not 'domain' in get: return public.return_message(-1, 0, 'Please enter the host domain name') + if len(get.domain) < 3: return public.return_message(-1, 0, 'Domain cannot be empty!') + domains = get.domain.replace(' ', '').split(',') + + for domain in domains: + if domain == "": continue + domain = domain.strip().split(':') + get.domain = self.ToPunycode(domain[0]).lower() + get.port = '80' + # 判断通配符域名格式 + if get.domain.find('*') != -1 and get.domain.find('*.') == -1: + return public.return_message(-1, 0, 'Domain name format is incorrect!') + + # 判断域名格式 + reg = r"^([\w\-\*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$" + if not re.match(reg, get.domain): return public.return_message(-1, 0, 'Format of domain is invalid!') + + # 获取自定义端口 + if len(domain) == 2: + get.port = domain[1] + if get.port == "": get.port = "80" + + # 判断端口是否合法 + if not public.checkPort(get.port): return public.return_message(-1, 0, + 'Port range is incorrect! should be between 100-65535') + # 检查域名是否存在 + sql = public.M('domain') + opid = sql.where("name=? AND (port=? OR pid=?)", (get.domain, get.port, get.id)).getField('pid') + if opid: + siteName = public.M('sites').where('id=?', (opid,)).getField('name') + if siteName: + return public.return_message(-1, 0, + 'The specified domain name has been bound by the website [{}]'.format( + siteName)) + sql.where('pid=?', (opid,)).delete() + opid = public.M('binding').where('domain=?', (get.domain,)).getField('pid') + if opid: + siteName = public.M('sites').where('id=?', (opid,)).getField('name') + return public.return_message(-1, 0, + 'The specified domain name has been bound by a subdirectory of the website [{}]!'.format( + siteName)) + + # 写配置文件 + self.NginxDomain(get) + try: + self.ApacheDomain(get) + self.openlitespeed_domain(get) + if self._check_ols_ssl(get.webname): + get.port = '443' + self.openlitespeed_domain(get) + get.port = '80' + except: + pass + + # 检查实际端口 + if len(domain) == 2: get.port = domain[1] + + # 添加放行端口 + if get.port != '80': + import firewalls + get.ps = get.domain + firewalls.firewalls().AddAcceptPort(get) + + # 重载webserver服务 + if not multiple: + public.serviceReload() + full_domain = get.domain + if not get.port in ['80', '443']: full_domain += ':' + get.port + public.check_domain_cloud(full_domain) + public.write_log_gettext('Site manager', 'Site [{}] added domain [{}] successfully!', + (get.webname, get.domain)) + sql.table('domain').add('pid,name,port,addtime', (get.id, get.domain, get.port, public.getDate())) + + return public.return_message(0, 0, 'Successfully added site!') + + # 判断ols_ssl是否已经设置 + def _check_ols_ssl(self, webname): + conf = public.readFile('/www/server/panel/vhost/openlitespeed/listen/443.conf') + if conf and webname in conf: + return True + return False + + # 添加openlitespeed 80端口监听 + def openlitespeed_set_80_domain(self, get, conf): + rep = r'map\s+{}.*'.format(get.webname) + domains = get.webname.strip().split(',') + if conf: + map_tmp = re.search(rep, conf) + if map_tmp: + map_tmp = map_tmp.group() + domains = map_tmp.strip().split(',') + if not public.inArray(domains, get.domain): + new_map = '{},{}'.format(conf, get.domain) + conf = re.sub(rep, new_map, conf) + else: + map_tmp = '\tmap\t{d} {d}\n'.format(d=domains[0]) + listen_rep = r"secure\s*0" + conf = re.sub(listen_rep, "secure 0\n" + map_tmp, conf) + return public.return_message(0, 0, conf) + + else: + rep_default = 'listener\\s+Default\\{(\n|[\\s\\w\\*\\:\\#\\.\\,])*' + tmp = re.search(rep_default, conf) + # domains = get.webname.strip().split(',') + if tmp: + tmp = tmp.group() + new_map = '\tmap\t{d} {d}\n'.format(d=domains[0]) + tmp += new_map + conf = re.sub(rep_default, tmp, conf) + return public.return_message(0, 0, conf) + + # openlitespeed写域名配置 + def openlitespeed_domain(self, get): + listen_dir = '/www/server/panel/vhost/openlitespeed/listen/' + if not os.path.exists(listen_dir): + os.makedirs(listen_dir) + listen_file = listen_dir + get.port + ".conf" + listen_conf = public.readFile(listen_file) + try: + get.webname = json.loads(get.webname) + get.domain = get.webname['domain'].replace('\r', '') + get.webname = get.domain + "," + ",".join(get.webname["domainlist"]) + if get.webname[-1] == ',': + get.webname = get.webname[:-1] + except: + pass + if listen_conf: + # 添加域名 + rep = r'map\s+{}.*'.format(get.webname) + map_tmp = re.search(rep, listen_conf) + if map_tmp: + map_tmp = map_tmp.group() + domains = map_tmp.strip().split(',') + if not public.inArray(domains, get.domain): + new_map = '{},{}'.format(map_tmp, get.domain) + listen_conf = re.sub(rep, new_map, listen_conf) + else: + domains = get.webname.strip().split(',') + map_tmp = '\tmap\t{d} {d}'.format(d=domains[0]) + listen_rep = r"secure\s*0" + listen_conf = re.sub(listen_rep, "secure 0\n" + map_tmp, listen_conf) + else: + listen_conf = """ +listener Default%s{ + address *:%s + secure 0 + map %s %s +} +""" % (get.port, get.port, get.webname, get.domain) + # 保存配置文件 + public.writeFile(listen_file, listen_conf) + return public.return_message(0, 0, True) + + # Nginx写域名配置 + def NginxDomain(self, get): + file = self.setupPath + '/panel/vhost/nginx/' + get.webname + '.conf' + conf = public.readFile(file) + if not conf: return public.return_message(-1, 0, 'domains file not exists:' + file) + # 添加域名 + rep = r"server_name\s*(.*);" + tmp = re.search(rep, conf).group() + domains = tmp.replace(';', '').strip().split(' ') + if not public.inArray(domains, get.domain): + newServerName = tmp.replace(';', ' ' + get.domain + ';') + conf = conf.replace(tmp, newServerName) + + # 添加端口 + rep = r"listen\s+[\[\]\:]*([0-9]+).*;" + tmp = re.findall(rep, conf) + if not public.inArray(tmp, get.port): + listen = re.search(rep, conf).group() + listen_ipv6 = '' + if self.is_ipv6: listen_ipv6 = "\n\t\tlisten [::]:" + get.port + ';' + conf = conf.replace(listen, listen + "\n\t\tlisten " + get.port + ';' + listen_ipv6) + # 保存配置文件 + public.writeFile(file, conf) + return public.return_message(0, 0, True) + + # Apache写域名配置 + def ApacheDomain(self, get): + file = self.setupPath + '/panel/vhost/apache/' + get.webname + '.conf' + conf = public.readFile(file) + if not conf: return public.return_message(-1, 0, 'domains file not exists:' + file) + + port = get.port + siteName = get.webname + newDomain = get.domain + find = public.M('sites').where("id=?", (get.id,)).field('id,name,path').find() + sitePath = find['path'] + siteIndex = 'index.php index.html index.htm default.php default.html default.htm' + + # 添加域名 + if conf.find('') != -1: + repV = r"(.|\n)*" + domainV = re.search(repV, conf).group() + rep = r"ServerAlias\s*(.*)\n" + tmp = re.search(rep, domainV).group(0) + domains = tmp.strip().split(' ') + if not public.inArray(domains, newDomain): + rs = tmp.replace("\n", "") + newServerName = rs + ' ' + newDomain + "\n" + myconf = domainV.replace(tmp, newServerName) + conf = re.sub(repV, myconf, conf) + if conf.find('') != -1: + repV = r"(.|\n)*" + domainV = re.search(repV, conf).group() + rep = r"ServerAlias\s*(.*)\n" + tmp = re.search(rep, domainV).group(0) + domains = tmp.strip().split(' ') + if not public.inArray(domains, newDomain): + rs = tmp.replace("\n", "") + newServerName = rs + ' ' + newDomain + "\n" + myconf = domainV.replace(tmp, newServerName) + conf = re.sub(repV, myconf, conf) + else: + try: + httpdVersion = public.readFile(self.setupPath + '/apache/version.pl').strip() + except: + httpdVersion = "" + if httpdVersion == '2.2': + vName = '' + if self.sitePort != '80' and self.sitePort != '443': + vName = "NameVirtualHost *:" + port + "\n" + phpConfig = "" + apaOpt = "Order allow,deny\n\t\tAllow from all" + else: + vName = "" + # rep = r"php-cgi-([0-9]{2,3})\.sock" + # version = re.search(rep,conf).groups()[0] + version = public.get_php_version_conf(conf) + if len(version) < 2: + return_message = public.return_msg_gettext(False, 'Failed to get PHP version!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + phpConfig = ''' + #PHP + + SetHandler "proxy:%s" + + ''' % (public.get_php_proxy(version, 'apache'),) + apaOpt = 'Require all granted' + + newconf = r''' + ServerAdmin webmaster@example.com + DocumentRoot "%s" + ServerName %s.%s + ServerAlias %s + #errorDocument 404 /404.html + ErrorLog "%s-error_log" + CustomLog "%s-access_log" combined + %s + + #DENY FILES + + Order allow,deny + Deny from all + + + #PATH + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + %s + DirectoryIndex %s + +''' % (port, sitePath, siteName, port, newDomain, public.GetConfigValue('logs_path') + '/' + siteName, + public.GetConfigValue('logs_path') + '/' + siteName, phpConfig, sitePath, apaOpt, siteIndex) + conf += "\n\n" + newconf + + # 添加端口 + if port != '80' and port != '888': self.apacheAddPort(port) + + # 保存配置文件 + public.writeFile(file, conf) + return public.return_message(0, 0, True) + + def delete_domain_multiple(self, get): + ''' + @name 批量删除网站 + @author zhwen<2020-11-17> + @param id "1" + @param domains_id 1,2,3 + ''' + domains_id = get.domains_id.split(',') + get.webname = public.M('sites').where("id=?", (get.id,)).getField('name') + del_successfully = [] + del_failed = {} + for domain_id in domains_id: + get.domain = public.M('domain').where("id=? and pid=?", (domain_id, get.id)).getField('name') + get.port = str(public.M('domain').where("id=? and pid=?", (domain_id, get.id)).getField('port')) + if not get.webname: + continue + try: + result = self.DelDomain(get, multiple=1) + tmp = get.domain + ':' + get.port + if result['status'] == -1: + del_failed[tmp] = result['msg'] + continue + del_successfully.append(tmp) + except: + tmp = get.domain + ':' + get.port + del_failed[tmp] = public.get_msg_gettext('There was an error deleting, please try again.') + pass + public.serviceReload() + return_message = { + 'msg': public.get_msg_gettext('Delete domain [{}] successfully', (','.join(del_successfully),)), + 'error': del_failed, + 'success': del_successfully} + return public.return_message(0, 0, return_message) + + # 删除域名 + def DelDomain(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('webname').String(), + Param('domain').String(), + Param('id').Integer(), + Param('port').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not 'id' in get: return public.return_message(-1, 0, 'Please choose a domain name') + if not 'port' in get: return public.return_message(-1, 0, 'Please choose a port') + sql = public.M('domain') + id = get['id'] + port = get.port + find = sql.where("pid=? AND name=?", (get.id, get.domain)).field('id,name').find() + domain_count = sql.table('domain').where("pid=?", (id,)).count() + if domain_count == 1: return public.return_message(-1, 0, 'Last domain cannot be deleted!') + + # nginx + file = self.setupPath + '/panel/vhost/nginx/' + get['webname'] + '.conf' + conf = public.readFile(file) + if conf: + # 删除域名 + rep = r"server_name\s+(.+);" + tmp = re.search(rep, conf).group() + newServerName = tmp.replace(' ' + get['domain'] + ';', ';') + newServerName = newServerName.replace(' ' + get['domain'] + ' ', ' ') + conf = conf.replace(tmp, newServerName) + + # 删除端口 + rep = r"listen.*[\s:]+(\d+).*;" + tmp = re.findall(rep, conf) + port_count = sql.table('domain').where('pid=? AND port=?', (get.id, get.port)).count() + if public.inArray(tmp, port) == True and port_count < 2: + rep = r"\n*\s+listen.*[\s:]+" + port + r"\s*;" + conf = re.sub(rep, '', conf) + # 保存配置 + public.writeFile(file, conf) + + # apache + file = self.setupPath + '/panel/vhost/apache/' + get['webname'] + '.conf' + conf = public.readFile(file) + if conf: + # 删除域名 + try: + rep = r"\n*(.|\n)*" + tmp = re.search(rep, conf).group() + + rep1 = "ServerAlias\\s+(.+)\n" + tmp1 = re.findall(rep1, tmp) + tmp2 = tmp1[0].split(' ') + if len(tmp2) < 2: + conf = re.sub(rep, '', conf) + rep = r"NameVirtualHost.+\:" + port + "\n" + conf = re.sub(rep, '', conf) + else: + newServerName = tmp.replace(' ' + get['domain'] + "\n", "\n") + newServerName = newServerName.replace(' ' + get['domain'] + ' ', ' ') + conf = conf.replace(tmp, newServerName) + # 保存配置 + public.writeFile(file, conf.strip()) + except: + pass + + # openlitespeed + self._del_ols_domain(get) + + sql.table('domain').where("id=?", (find['id'],)).delete() + public.write_log_gettext('Site manager', 'Site [{}] deleted domain [{}] successfully!', + (get.webname, get.domain)) + if not multiple: + public.serviceReload() + return public.return_message(0, 0, 'Successfully deleted') + + # openlitespeed删除域名 + def _del_ols_domain(self, get): + conf_dir = '/www/server/panel/vhost/openlitespeed/listen/' + if not os.path.exists(conf_dir): + return return_message(-1, 0, 'directory not exists:' + conf_dir) + for i in os.listdir(conf_dir): + file_name = conf_dir + i + if os.path.isdir(file_name): + continue + conf = public.readFile(file_name) + map_rep = r'map\s+{}\s+(.*)'.format(get.webname) + domains = re.search(map_rep, conf) + if domains: + domains = domains.group(1).split(',') + if get.domain in domains: + domains.remove(get.domain) + if len(domains) == 0: + os.remove(file_name) + continue + else: + domains = ",".join(domains) + map_c = "map\t{} ".format(get.webname) + domains + conf = re.sub(map_rep, map_c, conf) + public.writeFile(file_name, conf) + return public.return_message(0, 0, 'Setup successfully!') + + # 检查域名是否解析 + def CheckDomainPing(self, get): + try: + epass = public.GetRandomString(32) + spath = get.path + '/.well-known/pki-validation' + if not os.path.exists(spath): public.ExecShell("mkdir -p '" + spath + "'") + public.writeFile(spath + '/fileauth.txt', epass) + result = public.httpGet( + 'http://' + get.domain.replace('*.', '') + '/.well-known/pki-validation/fileauth.txt') + if result == epass: return public.return_message(0, 0, "") + return public.return_message(-1, 0, "") + except: + return public.return_message(-1, 0, "") + + # 保存第三方证书 + def SetSSL(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + Param('key').String(), + Param('csr').String(), + Param('type').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = get.siteName + path = '/www/server/panel/vhost/cert/' + siteName + csrpath = path + "/fullchain.pem" + keypath = path + "/privkey.pem" + + if (get.key.find('KEY') == -1): + return_message = public.return_msg_gettext(False, 'Private Key ERROR, please check!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if (get.csr.find('CERTIFICATE') == -1): + return_message = public.return_msg_gettext(False, 'Certificate ERROR, please check!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + public.writeFile('/tmp/cert.pl', get.csr) + if not public.CheckCert('/tmp/cert.pl'): + return_message = public.return_msg_gettext(False, 'Error getting certificate') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + backup_cert = '/tmp/backup_cert_' + siteName + + import shutil + if os.path.exists(backup_cert): shutil.rmtree(backup_cert) + if os.path.exists(path): shutil.move(path, backup_cert) + if os.path.exists(path): shutil.rmtree(path) + + public.ExecShell('mkdir -p ' + path) + public.writeFile(keypath, get.key) + public.writeFile(csrpath, get.csr) + + # 写入配置文件 + result = self.SetSSLConf(get) + if result['status'] == -1: return result + isError = public.checkWebConfig() + + if (type(isError) == str): + if os.path.exists(path): shutil.rmtree(backup_cert) + shutil.move(backup_cert, path) + return_message = public.return_msg_gettext(False, + 'ERROR:
                                    ' + isError.replace("\n", + '
                                    ') + '
                                    ') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + public.serviceReload() + + if os.path.exists(path + '/partnerOrderId'): os.remove(path + '/partnerOrderId') + if os.path.exists(path + '/certOrderId'): os.remove(path + '/certOrderId') + p_file = '/etc/letsencrypt/live/' + get.siteName + if os.path.exists(p_file): shutil.rmtree(p_file) + public.write_log_gettext('Site manager', 'Certificate saved!') + + # 清理备份证书 + if os.path.exists(backup_cert): shutil.rmtree(backup_cert) + return_message = public.return_msg_gettext(True, 'Certificate saved!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 获取运行目录 + def GetRunPath(self, get): + if not hasattr(get, 'id'): + 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 public.return_message(-1, 0, "") + if type(get.id) == list: get.id = get.id[0]['id'] + result = self.GetSiteRunPath(get)['message'] + if 'runPath' in result: + return public.return_message(0, 0, result['runPath']) + return public.return_message(-1, 0, "") + + # 创建Let's Encrypt免费证书 + def CreateLet(self, get): + + domains = json.loads(get.domains) + if not len(domains): + return_message = public.return_msg_gettext(False, 'Please choose a domain name') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + file_auth = True + if hasattr(get, 'dnsapi'): + file_auth = False + + if not hasattr(get, 'dnssleep'): + get.dnssleep = 10 + + email = public.M('users').getField('email') + if hasattr(get, 'email'): + if get.email.find('@') == -1: + get.email = email + else: + get.email = get.email.strip() + public.M('users').where('id=?', (1,)).setField('email', get.email) + else: + get.email = email + + for domain in domains: + if public.checkIp(domain): continue + if domain.find('*.') >= 0 and file_auth: + return_message = public.return_msg_gettext(False, + 'A generic domain name cannot be used to apply for a certificate using [File Validation]!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + if file_auth: + get.sitename = get.siteName + if self.GetRedirectList(get): + return_message = public.return_msg_gettext(False, + 'Your site has 301 Redirect on,Please turn it off first!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if self.GetProxyList(get): + return_message = public.return_msg_gettext(False, + 'Sites that have reverse proxy turned on cannot request SSL!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + data = self.get_site_info(get.siteName) + get.id = data['id'] + runPath = self.GetRunPath(get)['message']['result'] + if runPath != '/': + if runPath[:1] != '/': runPath = '/' + runPath + else: + runPath = '' + get.site_dir = data['path'] + runPath + + else: + dns_api_list = self.GetDnsApi(get) + get.dns_param = None + for dns in dns_api_list: + if dns['name'] == get.dnsapi: + param = [] + if not dns['data']: continue + for val in dns['data']: + param.append(val['value']) + get.dns_param = '|'.join(param) + n_list = ['dns', 'dns_bt'] + if not get.dnsapi in n_list: + if len(get.dns_param) < 16: + return_message = public.return_msg_gettext(False, 'No valid DNSAPI key information found', + (get.dnsapi,)) + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if get.dnsapi == 'dns_bt': + if not os.path.exists('plugin/dns/dns_main.py'): + return_message = public.return_msg_gettext(False, + 'Please go to the software store to install [Cloud Resolution] and complete the domain name NS binding.') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + self.check_ssl_pack() + try: + import panel_lets_v2 as panelLets + public.mod_reload(panelLets) + except Exception as ex: + if str(ex).find('No module named requests') != -1: + public.ExecShell("pip install requests &") + return_message = public.return_msg_gettext(False, + 'Missing requests component, please try to repair the panel!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + return_message = public.return_msg_gettext(False, str(ex)) + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + lets = panelLets.panelLets() + result = lets.apple_lest_cert(get) + if result['status'] and not 'code' in result: + get.onkey = 1 + path = '/www/server/panel/cert/' + get.siteName + if os.path.exists(path + '/certOrderId'): os.remove(path + '/certOrderId') + result = self.SetSSLConf(get) + return result + + def get_site_info(self, siteName): + data = public.M("sites").where('name=?', siteName).field('id,path,name').find() + return data + + # 检测依赖库 + def check_ssl_pack(self): + try: + import requests + except: + public.ExecShell('btpip install requests') + try: + import OpenSSL + except: + public.ExecShell('btpip install pyOpenSSL') + + # 判断DNS-API是否设置 + def Check_DnsApi(self, dnsapi): + dnsapis = self.GetDnsApi(None) + for dapi in dnsapis: + if dapi['name'] == dnsapi: + if not dapi['data']: return True + for d in dapi['data']: + if d['key'] == '': return False + return True + + # 获取DNS-API列表 + def GetDnsApi(self, get): + api_path = './config/dns_api.json' + api_init = './config/dns_api_init.json' + if not os.path.exists(api_path): + if os.path.exists(api_init): + import shutil + shutil.copyfile(api_init, api_path) + apis = json.loads(public.ReadFile(api_path)) + + path = '/root/.acme.sh' + if not os.path.exists(path + '/account.conf'): path = "/.acme.sh" + account = public.readFile(path + '/account.conf') + if not account: account = '' + is_write = False + for i in range(len(apis)): + if not apis[i]['data']: continue + for j in range(len(apis[i]['data'])): + if apis[i]['data'][j]['value']: continue + search_str = apis[i]['data'][j]['key'] + r"\s*=\s*'(.+)'" + match = re.search("" + apis[i]['data'][j]['key'] + r"\s*=\s*'(.+)'", account) + if match: apis[i]['data'][j]['value'] = match.groups()[0] + if apis[i]['data'][j]['value']: is_write = True + if is_write: public.writeFile('./config/dns_api.json', json.dumps(apis)) + result = [] + for i in apis: + if i['title'] == 'CloudFlare': + if os.path.exists('/www/server/panel/data/cf_limit_api.pl'): + i['API_Limit'] = True + else: + i['API_Limit'] = False + result.insert(0, i) + return public.return_message(0, 0, result) + + # 设置DNS-API + def SetDnsApi(self, get): + pdata = json.loads(get.pdata) + cf_limit_api = "/www/server/panel/data/cf_limit_api.pl" + if 'API_Limit' in pdata and pdata['API_Limit'] == True and not os.path.exists(cf_limit_api): + os.mknod(cf_limit_api) + if 'API_Limit' in pdata and pdata['API_Limit'] == False: + if os.path.exists(cf_limit_api): os.remove(cf_limit_api) + apis = json.loads(public.ReadFile('./config/dns_api.json')) + is_write = False + for key in pdata.keys(): + for i in range(len(apis)): + if not apis[i]['data']: continue + for j in range(len(apis[i]['data'])): + if apis[i]['data'][j]['key'] != key: continue + apis[i]['data'][j]['value'] = pdata[key] + is_write = True + + if is_write: public.writeFile('./config/dns_api.json', json.dumps(apis)) + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 获取站点所有域名 + def GetSiteDomains(self, get): + data = {} + domains = public.M('domain').where('pid=?', (get.id,)).field('name,id').select() + binding = public.M('binding').where('pid=?', (get.id,)).field('domain,id').select() + if type(binding) == str: return public.return_message(0, 0, binding) + for b in binding: + tmp = {} + tmp['name'] = b['domain'] + tmp['id'] = b['id'] + tmp['binding'] = True + domains.append(tmp) + data['domains'] = domains + data['email'] = public.M('users').where('id=?', (1,)).getField('email') + if data['email'] == '287962566@qq.com': data['email'] = '' + return public.return_message(0, 0, data) + + def GetFormatSSLResult(self, result): + try: + import re + rep = "\\s*Domain:.+\n\\s+Type:.+\n\\s+Detail:.+" + tmps = re.findall(rep, result) + + statusList = [] + for tmp in tmps: + arr = tmp.strip().split('\n') + status = {} + for ar in arr: + tmp1 = ar.strip().split(':') + status[tmp1[0].strip()] = tmp1[1].strip() + if len(tmp1) > 2: + status[tmp1[0].strip()] = tmp1[1].strip() + ':' + tmp1[2] + statusList.append(status) + return statusList + except: + return None + + # 获取TLS1.3标记 + def get_tls13(self): + nginx_bin = '/www/server/nginx/sbin/nginx' + nginx_v = public.ExecShell(nginx_bin + ' -V 2>&1')[0] + nginx_v_re = re.findall(r"nginx/(\d\.\d+).+OpenSSL\s+(\d\.\d+)", nginx_v, re.DOTALL) + if nginx_v_re: + if nginx_v_re[0][0] in ['1.8', '1.9', '1.7', '1.6', '1.5', '1.4']: + return '' + if float(nginx_v_re[0][0]) >= 1.15 and float(nginx_v_re[0][-1]) >= 1.1: + return ' TLSv1.3' + else: + _v = re.search(r'nginx/1\.1(5|6|7|8|9).\d', nginx_v) + if not _v: + _v = re.search(r'nginx/1\.2\d\.\d', nginx_v) + openssl_v = public.ExecShell(nginx_bin + ' -V 2>&1|grep OpenSSL')[0].find('OpenSSL 1.1.') != -1 + if _v and openssl_v: + return ' TLSv1.3' + return '' + + # 获取apache反向代理 + def get_apache_proxy(self, conf): + rep = "\n*#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid\n+\\s+IncludeOptiona.*" + proxy = re.search(rep, conf) + if proxy: + return proxy.group() + return "" + + def _get_site_domains(self, sitename): + site_id = public.M('sites').where('name=?', (sitename,)).field('id').find() + domains = public.M('domain').where('pid=?', (site_id['id'],)).field('name').select() + domains = [d['name'] for d in domains] + return domains + + # 设置OLS ssl + def set_ols_ssl(self, get, siteName): + listen_conf = self.setupPath + '/panel/vhost/openlitespeed/listen/443.conf' + conf = public.readFile(listen_conf) + ssl_conf = """ + vhssl { + keyFile /www/server/panel/vhost/cert/BTDOMAIN/privkey.pem + certFile /www/server/panel/vhost/cert/BTDOMAIN/fullchain.pem + certChain 1 + sslProtocol 24 + ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES128-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA128:DHE-RSA-AES128-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA128:ECDHE-RSA-AES128-SHA384:ECDHE-RSA-AES128-SHA128:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA384:AES128-GCM-SHA128:AES128-SHA128:AES128-SHA128:AES128-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4 + enableECDHE 1 + renegProtection 1 + sslSessionCache 1 + enableSpdy 15 + enableStapling 1 + ocspRespMaxAge 86400 + } + """ + ssl_dir = self.setupPath + '/panel/vhost/openlitespeed/detail/ssl/' + if not os.path.exists(ssl_dir): + os.makedirs(ssl_dir) + ssl_file = ssl_dir + '{}.conf'.format(siteName) + if not os.path.exists(ssl_file): + ssl_conf = ssl_conf.replace('BTDOMAIN', siteName) + public.writeFile(ssl_file, ssl_conf, "a+") + include_ssl = '\ninclude {}'.format(ssl_file) + detail_file = self.setupPath + '/panel/vhost/openlitespeed/detail/{}.conf'.format(siteName) + public.writeFile(detail_file, include_ssl, 'a+') + if not conf: + conf = """ +listener SSL443 { + map BTSITENAME BTDOMAIN + address *:443 + secure 1 + keyFile /www/server/panel/vhost/cert/BTSITENAME/privkey.pem + certFile /www/server/panel/vhost/cert/BTSITENAME/fullchain.pem + certChain 1 + sslProtocol 24 + ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES128-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA128:DHE-RSA-AES128-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA128:ECDHE-RSA-AES128-SHA384:ECDHE-RSA-AES128-SHA128:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA128:DHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA384:AES128-GCM-SHA128:AES128-SHA128:AES128-SHA128:AES128-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4 + enableECDHE 1 + renegProtection 1 + sslSessionCache 1 + enableSpdy 15 + enableStapling 1 + ocspRespMaxAge 86400 +} +""" + + else: + rep = r'listener\s*SSL443\s*{' + map = '\n map {s} {s}'.format(s=siteName) + conf = re.sub(rep, 'listener SSL443 {' + map, conf) + domain = ",".join(self._get_site_domains(siteName)) + conf = conf.replace('BTSITENAME', siteName).replace('BTDOMAIN', domain) + public.writeFile(listen_conf, conf) + + def _get_ap_static_security(self, ap_conf): + if not ap_conf: return public.return_message(0, 0, '') + ap_static_security = re.search('#SECURITY-START(.|\n)*#SECURITY-END', ap_conf) + if ap_static_security: + return public.return_message(0, 0, ap_static_security.group()) + return public.return_message(0, 0, '') + + # 添加SSL配置 + def SetSSLConf(self, get): + """ + @name 兼容批量设置 + @auther hezhihong + """ + siteName = get.siteName + if not 'first_domain' in get: get.first_domain = siteName + if 'isBatch' in get and siteName != get.first_domain: get.first_domain = siteName + + # Nginx配置 + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + + # Node项目 + if not os.path.exists(file): file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf' + + ng_file = file + conf = public.readFile(file) + + # 是否为子目录设置SSL + # if hasattr(get,'binding'): + # allconf = conf; + # conf = re.search("#BINDING-"+get.binding+"-START(.|\n)*#BINDING-"+get.binding+"-END",conf).group() + + if conf: + if conf.find('ssl_certificate') == -1: + sslStr = """#error_page 404/404.html; + ssl_certificate /www/server/panel/vhost/cert/%s/fullchain.pem; + ssl_certificate_key /www/server/panel/vhost/cert/%s/privkey.pem; + ssl_protocols TLSv1.1 TLSv1.2%s; + ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + add_header Strict-Transport-Security "max-age=31536000"; + error_page 497 https://$host$request_uri; +""" % (get.first_domain, get.first_domain, self.get_tls13()) + if (conf.find('ssl_certificate') != -1): + if 'isBatch' not in get: + public.serviceReload() + return_message = public.return_msg_gettext(True, 'SSL turned on!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + else: + return public.return_message(0, 0, "") + + conf = conf.replace('#error_page 404/404.html;', sslStr) + conf = re.sub(r"\s+\#SSL\-END", "\n\t\t#SSL-END", conf) + + # 添加端口 + rep = r"listen.*[\s:]+(\d+).*;" + tmp = re.findall(rep, conf) + if not public.inArray(tmp, '443'): + listen_re = re.search(rep, conf) + if not listen_re: + conf = re.sub(r"server\s*{\s*", "server\n{\n\t\tlisten 80;\n\t\t", conf) + listen_re = re.search(rep, conf) + listen = listen_re.group() + versionStr = public.readFile('/www/server/nginx/version.pl') + http2 = '' + if versionStr: + if versionStr.find('1.8.1') == -1: http2 = ' http2' + default_site = '' + if conf.find('default_server') != -1: default_site = ' default_server' + + listen_ipv6 = ';' + if self.is_ipv6: listen_ipv6 = ";\n\t\tlisten [::]:443 ssl" + http2 + default_site + ";" + conf = conf.replace(listen, listen + "\n\t\tlisten 443 ssl" + http2 + default_site + listen_ipv6) + shutil.copyfile(file, self.nginx_conf_bak) + + public.writeFile(file, conf) + + # Apache配置 + file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + # if not os.path.exists(file): file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf' + is_node_apache = False + if not os.path.exists(file): + is_node_apache = True + file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf' + conf = public.readFile(file) + ap_static_security = self._get_ap_static_security(conf) + if conf: + ap_proxy = self.get_apache_proxy(conf) + if conf.find('SSLCertificateFile') == -1 and conf.find('VirtualHost') != -1: + find = public.M('sites').where("name=?", (siteName,)).field('id,path').find() + tmp = public.M('domain').where('pid=?', (find['id'],)).field('name').select() + domains = '' + for key in tmp: + domains += key['name'] + ' ' + path = (find['path'] + '/' + self.GetRunPath(get))['message']['result'].replace('//', '/') + index = 'index.php index.html index.htm default.php default.html default.htm' + + try: + httpdVersion = public.readFile(self.setupPath + '/apache/version.pl').strip() + except: + httpdVersion = "" + if httpdVersion == '2.2': + vName = "" + phpConfig = "" + apaOpt = "Order allow,deny\n\t\tAllow from all" + else: + vName = "" + # rep = r"php-cgi-([0-9]{2,3})\.sock" + # version = re.search(rep, conf).groups()[0] + version = public.get_php_version_conf(conf) + if len(version) < 2: + if 'isBatch' not in get: + return_message = public.return_msg_gettext(False, 'Failed to get PHP version!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + else: + return public.return_message(-1, 0, "") + phpConfig = ''' + #PHP + + SetHandler "proxy:%s" + + ''' % (public.get_php_proxy(version, 'apache'),) + apaOpt = 'Require all granted' + + sslStr = r'''%s + ServerAdmin webmaster@example.com + DocumentRoot "%s" + ServerName SSL.%s + ServerAlias %s + #errorDocument 404 /404.html + ErrorLog "%s-error_log" + CustomLog "%s-access_log" combined + %s + #SSL + SSLEngine On + SSLCertificateFile /www/server/panel/vhost/cert/%s/fullchain.pem + SSLCertificateKeyFile /www/server/panel/vhost/cert/%s/privkey.pem + SSLCipherSuite EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5 + SSLProtocol All -SSLv2 -SSLv3 -TLSv1 + SSLHonorCipherOrder On + %s + %s + + #DENY FILES + + Order allow,deny + Deny from all + + + #PATH + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + %s + DirectoryIndex %s + +''' % (vName, path, siteName, domains, public.GetConfigValue('logs_path') + '/' + siteName, + public.GetConfigValue('logs_path') + '/' + siteName, ap_proxy, get.first_domain, get.first_domain, + ap_static_security, phpConfig, path, apaOpt, index) + conf = conf + "\n" + sslStr + self.apacheAddPort('443') + shutil.copyfile(file, self.apache_conf_bak) + public.writeFile(file, conf) + if is_node_apache: # 兼容Nodejs项目 + from projectModel.nodejsModel import main + m = main() + project_find = m.get_project_find(siteName) + m.set_apache_config(project_find) + # OLS + self.set_ols_ssl(get, siteName) + isError = public.checkWebConfig() + if (isError != True): + if os.path.exists(self.nginx_conf_bak): shutil.copyfile(self.nginx_conf_bak, ng_file) + if os.path.exists(self.apache_conf_bak): shutil.copyfile(self.apache_conf_bak, file) + public.ExecShell("rm -f /tmp/backup_*.conf") + if 'isBatch' not in get: + return_message = public.return_msg_gettext(False, + public.get_msg_gettext( + 'Certificate ERROR, please check!') + ':
                                    ' + isError.replace( + "\n", '
                                    ') + '
                                    ') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + else: + return public.return_message(-1, 0, "") + + sql = public.M('firewall') + import firewalls + get.port = '443' + get.ps = 'HTTPS' + if not public.M('firewall').where('port=?', ('443',)).count(): + firewalls.firewalls().AddAcceptPort(get) + public.serviceReload() + if 'isBatch' not in get: firewalls.firewalls().AddAcceptPort(get) + if 'isBatch' not in get: public.serviceReload() + self.save_cert(get) + public.write_log_gettext('Site manager', 'Site [{}] turned on SSL successfully!', (siteName,)) + result = public.return_msg_gettext(True, 'SSL turned on!') + result['csr'] = public.readFile('/www/server/panel/vhost/cert/' + get.siteName + '/fullchain.pem') + result['key'] = public.readFile('/www/server/panel/vhost/cert/' + get.siteName + '/privkey.pem') + del result['status'] + if 'isBatch' not in get: + return public.return_message(0, 0, result) + else: + return public.return_message(0, 0, "") + + def save_cert(self, get): + # try: + import panel_ssl_v2 as panelSSL + ss = panelSSL.panelSSL() + get.keyPath = '/www/server/panel/vhost/cert/' + get.siteName + '/privkey.pem' + get.certPath = '/www/server/panel/vhost/cert/' + get.siteName + '/fullchain.pem' + return ss.SaveCert(get) + return public.return_message(0, 0, "") + # except: + # return False; + + # HttpToHttps + def HttpToHttps(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = get.siteName + # Nginx配置 + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if not os.path.exists(file): + file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf' + conf = public.readFile(file) + if conf: + if conf.find('ssl_certificate') == -1: + return_message = public.return_msg_gettext(False, 'SSL is NOT currently enabled') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + to = """#error_page 404/404.html; + #HTTP_TO_HTTPS_START + if ($server_port !~ 443){ + rewrite ^(/.*)$ https://$host$1 permanent; + } + #HTTP_TO_HTTPS_END""" + conf = conf.replace('#error_page 404/404.html;', to) + public.writeFile(file, conf) + + file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if not os.path.exists(file): + file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf' + conf = public.readFile(file) + if conf: + httpTohttos = '''combined + #HTTP_TO_HTTPS_START + + RewriteEngine on + RewriteCond %{SERVER_PORT} !^443$ + RewriteRule (.*) https://%{SERVER_NAME}$1 [L,R=301] + + #HTTP_TO_HTTPS_END''' + conf = re.sub('combined', httpTohttos, conf, 1) + public.writeFile(file, conf) + # OLS + conf_dir = '{}/panel/vhost/openlitespeed/redirect/{}/'.format(self.setupPath, siteName) + if not os.path.exists(conf_dir): + os.makedirs(conf_dir) + file = conf_dir + 'force_https.conf' + ols_force_https = ''' +#HTTP_TO_HTTPS_START + + RewriteEngine on + RewriteCond %{SERVER_PORT} !^443$ + RewriteRule (.*) https://%{SERVER_NAME}$1 [L,R=301] + +#HTTP_TO_HTTPS_END''' + public.writeFile(file, ols_force_https) + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # CloseToHttps + def CloseToHttps(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = get.siteName + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if not os.path.exists(file): + file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf' + conf = public.readFile(file) + if conf: + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + conf = re.sub(rep, '', conf) + rep = "\\s+if.+server_port.+\n.+\n\\s+\\s*}" + conf = re.sub(rep, '', conf) + public.writeFile(file, conf) + + file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + conf = public.readFile(file) + if conf: + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + conf = re.sub(rep, '', conf) + public.writeFile(file, conf) + # OLS + file = '{}/panel/vhost/openlitespeed/redirect/{}/force_https.conf'.format(self.setupPath, siteName) + public.ExecShell('rm -f {}*'.format(file)) + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 是否跳转到https + def IsToHttps(self, siteName): + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if not os.path.exists(file): + file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf' + if not os.path.exists(file): return False + conf = public.readFile(file) + if conf: + if conf.find('HTTP_TO_HTTPS_START') != -1: return True + if conf.find('$server_port !~ 443') != -1: return True + return False + + # 清理SSL配置 + def CloseSSLConf(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + Param('updateOf').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = get.siteName + + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if not os.path.exists(file): + file = self.setupPath + '/panel/vhost/nginx/node_' + siteName + '.conf' + conf = public.readFile(file) + if conf: + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl_certificate\s+.+;\s+ssl_certificate_key\s+.+;" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_protocols\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_ciphers\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_prefer_server_ciphers\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_session_cache\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_session_timeout\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_ecdh_curve\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_session_tickets\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_stapling\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+ssl_stapling_verify\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+add_header\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = "\\s+add_header\\s+.+;\n" + conf = re.sub(rep, '', conf) + rep = r"\s+ssl\s+on;" + conf = re.sub(rep, '', conf) + rep = r"\s+error_page\s497.+;" + conf = re.sub(rep, '', conf) + rep = "\\s+if.+server_port.+\n.+\n\\s+\\s*}" + conf = re.sub(rep, '', conf) + rep = r"\s+listen\s+443.*;" + conf = re.sub(rep, '', conf) + rep = r"\s+listen\s+\[::\]:443.*;" + conf = re.sub(rep, '', conf) + public.writeFile(file, conf) + + file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if not os.path.exists(file): + file = self.setupPath + '/panel/vhost/apache/node_' + siteName + '.conf' + conf = public.readFile(file) + if conf: + rep = "\n(.|\n)*<\\/VirtualHost>" + conf = re.sub(rep, '', conf) + rep = "\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,250}#HTTP_TO_HTTPS_END" + conf = re.sub(rep, '', conf) + rep = "NameVirtualHost *:443\n" + conf = conf.replace(rep, '') + public.writeFile(file, conf) + + # OLS + ssl_file = self.setupPath + '/panel/vhost/openlitespeed/detail/ssl/{}.conf'.format(siteName) + detail_file = self.setupPath + '/panel/vhost/openlitespeed/detail/' + siteName + '.conf' + force_https = self.setupPath + '/panel/vhost/openlitespeed/redirect/' + siteName + string = 'rm -f {}/force_https.conf*'.format(force_https) + public.ExecShell(string) + detail_conf = public.readFile(detail_file) + if detail_conf: + detail_conf = detail_conf.replace('\ninclude ' + ssl_file, '') + public.writeFile(detail_file, detail_conf) + public.ExecShell('rm -f {}*'.format(ssl_file)) + + self._del_ols_443_domain(siteName) + partnerOrderId = '/www/server/panel/vhost/cert/' + siteName + '/partnerOrderId' + if os.path.exists(partnerOrderId): public.ExecShell('rm -f ' + partnerOrderId) + p_file = '/etc/letsencrypt/live/' + siteName + '/partnerOrderId' + if os.path.exists(p_file): public.ExecShell('rm -f ' + p_file) + + public.write_log_gettext('Site manager', 'Site [{}] turned off SSL successfully!', (siteName,)) + public.serviceReload() + return public.return_message(0, 0, 'SSL turned off!') + + def _del_ols_443_domain(self, sitename): + file = "/www/server/panel/vhost/openlitespeed/listen/443.conf" + conf = public.readFile(file) + if conf: + rep = '\n\\s*map\\s*{}.*'.format(sitename) + conf = re.sub(rep, '', conf) + if not "map " in conf: + public.ExecShell('rm -f {}*'.format(file)) + return + public.writeFile(file, conf) + + # 取SSL状态 + def GetSSL(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = get.siteName + path = os.path.join('/www/server/panel/vhost/cert/', siteName) + if not os.path.isfile(os.path.join(path, "fullchain.pem")) and not os.path.isfile( + os.path.join(path, "privkey.pem")): + path = os.path.join('/etc/letsencrypt/live/', siteName) + type = 0 + if os.path.exists(path + '/README'): type = 1 + if os.path.exists(path + '/partnerOrderId'): type = 2 + if os.path.exists(path + '/certOrderId'): type = 3 + csrpath = path + "/fullchain.pem" # 生成证书路径 + keypath = path + "/privkey.pem" # 密钥文件路径 + key = public.readFile(keypath) + csr = public.readFile(csrpath) + file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/' + siteName + '.conf' + + # 是否为node项目 + if not os.path.exists( + file): file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/node_' + siteName + '.conf' + if public.get_webserver() == "openlitespeed": + file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/detail/' + siteName + '.conf' + conf = public.readFile(file) + if not conf: return public.return_message(-1, 0, 'The specified website profile does not exist') + + if public.get_webserver() == 'nginx': + keyText = 'ssl_certificate' + elif public.get_webserver() == 'apache': + keyText = 'SSLCertificateFile' + else: + keyText = 'openlitespeed/detail/ssl' + + status = True + if (conf.find(keyText) == -1): + status = False + type = -1 + + toHttps = self.IsToHttps(siteName) + id = public.M('sites').where("name=?", (siteName,)).getField('id') + domains = public.M('domain').where("pid=?", (id,)).field('name').select() + cert_data = {} + if csr: + get.certPath = csrpath + import panel_ssl_v2 + cert_data = panel_ssl_v2.panelSSL().GetCertName(get) + if not cert_data: + cert_data = { + 'certificate': 0 + } + + email = public.M('users').where('id=?', (1,)).getField('email') + if email == '287962566@qq.com': email = '' + index = '' + auth_type = 'http' + if status == True: + if type != 1: + import acme_v3 + acme = acme_v3.acme_v2() + index = acme.check_order_exists(csrpath) + if index: + if index.find('/') == -1: + auth_type = acme._config['orders'][index]['auth_type'] + type = 1 + else: + crontab_file = 'vhost/cert/crontab.json' + tmp = public.readFile(crontab_file) + if tmp: + crontab_config = json.loads(tmp) + if siteName in crontab_config: + if 'dnsapi' in crontab_config[siteName]: + auth_type = 'dns' + + if os.path.exists(path + '/certOrderId'): type = 3 + oid = -1 + if type == 3: + oid = int(public.readFile(path + '/certOrderId')) + # 作者 muluo + # return {'status': status,'oid':oid, 'domain': domains, 'key': key, 'csr': csr, 'type': type, 'httpTohttps': toHttps,'cert_data':cert_data,'email':email,"index":index,'auth_type':auth_type} + res = {'status': status, 'oid': oid, 'domain': domains, 'key': key, 'csr': csr, 'type': type, + 'httpTohttps': toHttps, 'cert_data': cert_data, 'email': email, "index": index, 'auth_type': auth_type} + res['push'] = self.get_site_push_status(None, siteName, 'ssl') + return public.return_message(0, 0, res) + + def get_site_push_status(self, get, siteName=None, stype=None): + """ + @获取网站ssl告警通知状态 + @param get: + @param siteName 网站名称 + @param stype 类型 ssl + """ + import panel_push_v2 as panelPush + if get: + siteName = get.siteName + stype = get.stype + + result = {} + result['status'] = False + try: + data = {} + try: + data = json.loads(public.readFile('{}/class/push/push.json'.format(public.get_panel_path()))) + except: + pass + + if not 'site_push' in data: + return result + + ssl_data = data['site_push'] + for key in ssl_data.keys(): + if ssl_data[key]['type'] != stype: + continue + + project = ssl_data[key]['project'] + if project in [siteName, 'all']: + ssl_data[key]['id'] = key + ssl_data[key]['s_module'] = 'site_push' + + if project == siteName: + result = ssl_data[key] + break + + if project == 'all': + result = ssl_data[key] + except: + pass + + p_obj = panelPush.panelPush() + return public.return_message(0, 0, p_obj.get_push_user(result)) + + def set_site_status_multiple(self, get): + ''' + @name 批量设置网站状态 + @author zhwen<2020-11-17> + @param sites_id "1,2" + @param status 0/1 + ''' + sites_id = get.sites_id.split(',') + sites_name = [] + errors = {} + day_time = time.time() + for site_id in sites_id: + get.id = site_id + find = public.M('sites').where("id=?", (site_id,)).find() + get.name = find['name'] + + if get.status == '1': + if find['edate'] != '0000-00-00' and public.to_date("%Y-%m-%d", find['edate']) < day_time: + errors[get.name] = "failed, site has expired" + continue + sites_name.append(get.name) + if get.status == '1': + self.SiteStart(get, multiple=1) + else: + self.SiteStop(get, multiple=1) + public.serviceReload() + if get.status == '1': + return_message = { + 'msg': public.get_msg_gettext('Enable website [{}] successfully', (','.join(sites_name),)), + 'error': {}, 'success': sites_name} + return public.return_message(0, 0, return_message) + else: + return_message = { + 'msg': public.get_msg_gettext('Disable website [{}] successfully', (','.join(sites_name),)), + 'error': {}, 'success': sites_name} + return public.return_message(0, 0, return_message) + + # 启动站点 + def SiteStart(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + Path = self.setupPath + '/stop' + sitePath = public.M('sites').where("id=?", (id,)).getField('path') + + # nginx + file = self.setupPath + '/panel/vhost/nginx/' + get.name + '.conf' + conf = public.readFile(file) + if conf: + conf = conf.replace(Path, sitePath) + conf = conf.replace("#include", "include") + public.writeFile(file, conf) + # apache + file = self.setupPath + '/panel/vhost/apache/' + get.name + '.conf' + conf = public.readFile(file) + if conf: + conf = conf.replace(Path, sitePath) + conf = conf.replace("#IncludeOptional", "IncludeOptional") + public.writeFile(file, conf) + + # OLS + file = self.setupPath + '/panel/vhost/openlitespeed/' + get.name + '.conf' + conf = public.readFile(file) + if conf: + rep = r'vhRoot\s*{}'.format(Path) + new_content = 'vhRoot {}'.format(sitePath) + conf = re.sub(rep, new_content, conf) + public.writeFile(file, conf) + + public.M('sites').where("id=?", (id,)).setField('status', '1') + if not multiple: + public.serviceReload() + public.write_log_gettext('Site manager', 'Site [{}] started!', (get.name,)) + return_message = public.return_msg_gettext(True, 'Site started') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + def _process_has_run_dir(self, website_name, website_path, stop_path): + ''' + @name 当网站存在允许目录时停止网站需要做处理 + @author zhwen<2020-11-17> + @param site_id 1 + @param names test,baohu + ''' + conf = public.readFile(self.setupPath + '/panel/vhost/nginx/' + website_name + '.conf') + if not conf: + return False + try: + really_path = re.search(r'root\s+(.*);', conf).group(1) + tmp = stop_path + '/' + really_path.replace(website_path + '/', '') + public.ExecShell('mkdir {t} && ln -s {s}/index.html {t}/index.html'.format(t=tmp, s=stop_path)) + except: + pass + + # 停止站点 + def SiteStop(self, get, multiple=None): + path = self.setupPath + '/stop' + id = get.id + site_status = public.M('sites').where("id=?", (id,)).getField('status') + if str(site_status) != '1': + return public.return_message(0, 0, 'SITE_STOP_SUCCESS') + if not os.path.exists(path): + os.makedirs(path) + public.downloadFile('https://node.aapanel.com/stop_en.html', path + '/index.html') + + # if 'This site has been closed by administrator' not in public.readFile(path + '/index.html'): + # public.downloadFile('http://download.bt.cn/stop_en.html', path + '/index.html') + + binding = public.M('binding').where('pid=?', (id,)).field('id,pid,domain,path,port,addtime').select() + for b in binding: + bpath = path + '/' + b['path'] + if not os.path.exists(bpath): + public.ExecShell('mkdir -p ' + bpath) + public.ExecShell('ln -sf ' + path + '/index.html ' + bpath + '/index.html') + + sitePath = public.M('sites').where("id=?", (id,)).getField('path') + self._process_has_run_dir(get.name, sitePath, path) + # nginx + file = self.setupPath + '/panel/vhost/nginx/' + get.name + '.conf' + conf = public.readFile(file) + if conf: + src_path = 'root ' + sitePath + dst_path = 'root ' + path + if conf.find(src_path) != -1: + conf = conf.replace(src_path, dst_path) + else: + conf = conf.replace(sitePath, path) + conf = conf.replace("include", "#include") + public.writeFile(file, conf) + + # apache + file = self.setupPath + '/panel/vhost/apache/' + get.name + '.conf' + conf = public.readFile(file) + if conf: + conf = conf.replace(sitePath, path) + conf = conf.replace("IncludeOptional", "#IncludeOptional") + public.writeFile(file, conf) + # OLS + file = self.setupPath + '/panel/vhost/openlitespeed/' + get.name + '.conf' + conf = public.readFile(file) + if conf: + rep = r'vhRoot\s*{}'.format(sitePath) + new_content = 'vhRoot {}'.format(path) + conf = re.sub(rep, new_content, conf) + public.writeFile(file, conf) + + public.M('sites').where("id=?", (id,)).setField('status', '0') + if not multiple: + public.serviceReload() + public.write_log_gettext('Site manager', 'Site [{}] stopped!', (get.name,)) + return_message = public.return_msg_gettext(True, 'Site stopped') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取流量限制值 + def GetLimitNet(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + + # 取回配置文件 + siteName = public.M('sites').where("id=?", (id,)).getField('name') + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + + # 站点总并发 + data = {} + conf = public.readFile(filename) + try: + rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" + tmp = re.search(rep, conf).groups() + data['perserver'] = int(tmp[0]) + + # IP并发限制 + rep = r"\s+limit_conn\s+perip\s+([0-9]+);" + tmp = re.search(rep, conf).groups() + data['perip'] = int(tmp[0]) + + # 请求并发限制 + rep = r"\s+limit_rate\s+([0-9]+)\w+;" + tmp = re.search(rep, conf).groups() + data['limit_rate'] = int(tmp[0]) + except: + data['perserver'] = 0 + data['perip'] = 0 + data['limit_rate'] = 0 + + return public.return_message(0, 0, data) + + # 设置流量限制 + def SetLimitNet(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + Param('perserver').Integer(), + Param('perip').Integer(), + Param('limit_rate').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if (public.get_webserver() != 'nginx'): return public.return_message(-1, 0, + 'Site Traffic Control only supports Nginx Web Server!') + + id = get.id + if int(get.perserver) < 1 or int(get.perip) < 1 or int(get.perip) < 1: + return public.return_message(-1, 0, + 'Concurrency restrictions, IP restrictions, traffic restrictions must be greater than 0') + perserver = 'limit_conn perserver ' + get.perserver + ';' + perip = 'limit_conn perip ' + get.perip + ';' + limit_rate = 'limit_rate ' + get.limit_rate + 'k;' + + # 取回配置文件 + siteName = public.M('sites').where("id=?", (id,)).getField('name') + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + conf = public.readFile(filename) + + # 设置共享内存 + oldLimit = self.setupPath + '/panel/vhost/nginx/limit.conf' + if (os.path.exists(oldLimit)): os.remove(oldLimit) + limit = self.setupPath + '/nginx/conf/nginx.conf' + nginxConf = public.readFile(limit) + limitConf = "limit_conn_zone $binary_remote_addr zone=perip:10m;\n\t\tlimit_conn_zone $server_name zone=perserver:10m;" + nginxConf = nginxConf.replace("#limit_conn_zone $binary_remote_addr zone=perip:10m;", limitConf) + public.writeFile(limit, nginxConf) + + if (conf.find('limit_conn perserver') != -1): + # 替换总并发 + rep = r"limit_conn\s+perserver\s+([0-9]+);" + conf = re.sub(rep, perserver, conf) + + # 替换IP并发限制 + rep = r"limit_conn\s+perip\s+([0-9]+);" + conf = re.sub(rep, perip, conf) + + # 替换请求流量限制 + rep = r"limit_rate\s+([0-9]+)\w+;" + conf = re.sub(rep, limit_rate, conf) + else: + conf = conf.replace('#error_page 404/404.html;', + "#error_page 404/404.html;\n " + perserver + "\n " + perip + "\n " + limit_rate) + + import shutil + shutil.copyfile(filename, self.nginx_conf_bak) + public.writeFile(filename, conf) + isError = public.checkWebConfig() + if (isError != True): + if os.path.exists(self.nginx_conf_bak): shutil.copyfile(self.nginx_conf_bak, filename) + return public.return_message(-1, 0, + 'ERROR:
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ') + + public.serviceReload() + public.write_log_gettext('Site manager', 'Site [{}] traffic control turned on!', (siteName,)) + return public.return_message(0, 0, 'Setup successfully!') + + # 关闭流量限制 + def CloseLimitNet(self, get): + id = get.id + # 取回配置文件 + siteName = public.M('sites').where("id=?", (id,)).getField('name') + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + conf = public.readFile(filename) + # 清理总并发 + rep = r"\s+limit_conn\s+perserver\s+([0-9]+);" + conf = re.sub(rep, '', conf) + + # 清理IP并发限制 + rep = r"\s+limit_conn\s+perip\s+([0-9]+);" + conf = re.sub(rep, '', conf) + + # 清理请求流量限制 + rep = r"\s+limit_rate\s+([0-9]+)\w+;" + conf = re.sub(rep, '', conf) + public.writeFile(filename, conf) + public.serviceReload() + public.write_log_gettext('Site manager', 'Site Traffic Control has been turned off!', (siteName,)) + return_message = public.return_msg_gettext(True, 'Site Traffic Control has been turned off!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取301配置状态 + def Get301Status(self, get): + siteName = get.siteName + result = {} + domains = '' + id = public.M('sites').where("name=?", (siteName,)).getField('id') + tmp = public.M('domain').where("pid=?", (id,)).field('name').select() + node = public.M('sites').where('id=? and project_type=?', (id, 'Node')).count() + if node: + node = 'node_' + else: + node = '' + for key in tmp: + domains += key['name'] + ',' + try: + if (public.get_webserver() == 'nginx'): + conf = public.readFile(self.setupPath + '/panel/vhost/nginx/' + node + siteName + '.conf') + if conf.find('301-START') == -1: + result['domain'] = domains[:-1] + result['src'] = "" + result['status'] = False + result['url'] = "http://" + return public.return_message(0, 0, result) + rep = r"return\s+301\s+((http|https)\://.+);" + arr = re.search(rep, conf).groups()[0] + rep = r"'\^(([\w-]+\.)+[\w-]+)'" + tmp = re.search(rep, conf) + src = '' + if tmp: src = tmp.groups()[0] + elif public.get_webserver() == 'apache': + conf = public.readFile(self.setupPath + '/panel/vhost/apache/' + node + siteName + '.conf') + if conf.find('301-START') == -1: + result['domain'] = domains[:-1] + result['src'] = "" + result['status'] = False + result['url'] = "http://" + return public.return_message(0, 0, result) + rep = r"RewriteRule\s+.+\s+((http|https)\://.+)\s+\[" + arr = re.search(rep, conf).groups()[0] + rep = r"\^((\w+\.)+\w+)\s+\[NC" + tmp = re.search(rep, conf) + src = '' + if tmp: src = tmp.groups()[0] + else: + conf = public.readFile( + self.setupPath + '/panel/vhost/openlitespeed/redirect/{s}/{s}.conf'.format(s=siteName)) + if not conf: + result['domain'] = domains[:-1] + result['src'] = "" + result['status'] = False + result['url'] = "http://" + return result + rep = r"RewriteRule\s+.+\s+((http|https)\://.+)\s+\[" + arr = re.search(rep, conf).groups()[0] + rep = r"\^((\w+\.)+\w+)\s+\[NC" + tmp = re.search(rep, conf) + src = '' + if tmp: src = tmp.groups()[0] + except: + src = '' + arr = 'http://' + + result['domain'] = domains[:-1] + result['src'] = src.replace("'", '') + result['status'] = True + if (len(arr) < 3): result['status'] = False + result['url'] = arr + + return public.return_message(0, 0, result) + + # 设置301配置 + def Set301Status(self, get): + siteName = get.siteName + srcDomain = get.srcDomain + toDomain = get.toDomain + type = get.type + rep = r"(http|https)\://.+" + if not re.match(rep, toDomain): + return_message = public.return_msg_gettext(False, 'URL address is invalid!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + # nginx + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + mconf = public.readFile(filename) + if mconf == False: + return_message = public.return_msg_gettext(False, 'Configuration file not exist') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if (srcDomain == 'all'): + conf301 = "\t#301-START\n\t\treturn 301 " + toDomain + "$request_uri;\n\t#301-END" + else: + conf301 = "\t#301-START\n\t\tif ($host ~ '^" + srcDomain + "'){\n\t\t\treturn 301 " + toDomain + "$request_uri;\n\t\t}\n\t#301-END" + if type == '1': + mconf = mconf.replace("#error_page 404/404.html;", "#error_page 404/404.html;\n" + conf301) + else: + rep = "\\s+#301-START(.|\n){1,300}#301-END" + mconf = re.sub(rep, '', mconf) + public.writeFile(filename, mconf) + + # apache + filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + mconf = public.readFile(filename) + if mconf == False: + return_message = public.return_msg_gettext(False, 'Configuration file not exist') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if type == '1': + if (srcDomain == 'all'): + conf301 = "\n\t#301-START\n\t\n\t\tRewriteEngine on\n\t\tRewriteRule ^(.*)$ " + toDomain + "$1 [L,R=301]\n\t\n\t#301-END\n" + else: + conf301 = "\n\t#301-START\n\t\n\t\tRewriteEngine on\n\t\tRewriteCond %{HTTP_HOST} ^" + srcDomain + " [NC]\n\t\tRewriteRule ^(.*) " + toDomain + "$1 [L,R=301]\n\t\n\t#301-END\n" + rep = "combined" + mconf = mconf.replace(rep, rep + "\n\t" + conf301) + else: + rep = "\n\\s+#301-START(.|\n){1,300}#301-END\n*" + mconf = re.sub(rep, '\n\n', mconf, 1) + mconf = re.sub(rep, '\n\n', mconf, 1) + + public.writeFile(filename, mconf) + + # OLS + conf_dir = self.setupPath + '/panel/vhost/openlitespeed/redirect/{}/'.format(siteName) + if not os.path.exists(conf_dir): + os.makedirs(conf_dir) + file = conf_dir + siteName + '.conf' + if type == '1': + if (srcDomain == 'all'): + conf301 = "#301-START\nRewriteEngine on\nRewriteRule ^(.*)$ " + toDomain + "$1 [L,R=301]#301-END\n" + else: + conf301 = "#301-START\nRewriteEngine on\nRewriteCond %{HTTP_HOST} ^" + srcDomain + " [NC]\nRewriteRule ^(.*) " + toDomain + "$1 [L,R=301]\n#301-END\n" + public.writeFile(file, conf301) + else: + public.ExecShell('rm -f {}*'.format(file)) + + isError = public.checkWebConfig() + if (isError != True): + return_message = public.return_msg_gettext(False, + 'ERROR:
                                    ' + isError.replace("\n", + '
                                    ') + '
                                    ') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取子目录绑定 + def GetDirBinding(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + path = public.M('sites').where('id=?', (get.id,)).getField('path') + if not os.path.exists(path): + checks = ['/', '/usr', '/etc'] + if path in checks: + data = {} + data['dirs'] = [] + data['binding'] = [] + return data + public.ExecShell('mkdir -p ' + path) + public.ExecShell('chmod 755 ' + path) + public.ExecShell('chown www:www ' + path) + get.path = path + self.SetDirUserINI(get) + siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + public.write_log_gettext('Site manager', "Site [{}], document root [{}] does NOT exist, recreated!", + (siteName, path)) + dirnames = [] + # 取运行目录 + run_path = self.GetRunPath(get)['message']['result'] + if run_path: path += run_path + # 遍历目录 + if os.path.exists(path): + for filename in os.listdir(path): + try: + json.dumps(filename) + if sys.version_info[0] == 2: + filename = filename.encode('utf-8') + else: + filename.encode('utf-8') + filePath = path + '/' + filename + if os.path.islink(filePath): continue + if os.path.isdir(filePath): + dirnames.append(filename) + except: + pass + data = {} + data['run_path'] = run_path # 运行目录 + data['dirs'] = dirnames + data['binding'] = public.M('binding').where('pid=?', (get.id,)).field( + 'id,pid,domain,path,port,addtime').select() + # 标记子目录是否存在 + for dname in data['binding']: + _path = os.path.join(path, dname['path']) + if not os.path.exists(_path): + _path = _path.replace(run_path, '') + if not os.path.exists(_path): + dname['path'] += ' >> error: directory does not exist' + else: + dname['path'] = '../' + dname['path'] + + return public.return_message(0, 0, data) + + # 添加子目录绑定 + def AddDirBinding(self, get): + import shutil + id = get.id + tmp = get.domain.split(':') + domain = tmp[0].lower() + # 中文域名转码 + domain = public.en_punycode(domain) + port = '80' + version = '' + if len(tmp) > 1: port = tmp[1] + if not hasattr(get, 'dirName'): + return_message = public.return_msg_gettext(False, 'Directory cannot be empty!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + dirName = get.dirName + reg = r"^([\w\-\*]{1,100}\.){1,4}([\w\-]{1,100}|[\w\-]{1,100}\.[\w\-]{1,100})$" + if not re.match(reg, domain): + return_message = public.return_msg_gettext(False, 'Format of primary domain is incorrect') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + siteInfo = public.M('sites').where("id=?", (id,)).field('id,path,name').find() + # 实际运行目录 + root_path = siteInfo['path'] + run_path = self.GetRunPath(get)['message']['result'] + if run_path: root_path += run_path + + webdir = root_path + '/' + dirName + webdir = webdir.replace('//', '/').strip() + if not os.path.exists(webdir): # 如果在运行目录找不到指定子目录,尝试到根目录查找 + root_path = siteInfo['path'] + webdir = root_path + '/' + dirName + webdir = webdir.replace('//', '/').strip() + + sql = public.M('binding') + if sql.where("domain=?", (domain,)).count() > 0: + return_message = public.return_msg_gettext(False, 'The domain you tried to add already exists!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if public.M('domain').where("name=?", (domain,)).count() > 0: + return_message = public.return_msg_gettext(False, + 'The domain you tried to add already exists!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + filename = self.setupPath + '/panel/vhost/nginx/' + siteInfo['name'] + '.conf' + nginx_conf_file = filename + conf = public.readFile(filename) + if conf: + listen_ipv6 = '' + if self.is_ipv6: listen_ipv6 = "\n listen [::]:%s;" % port + try: + rep = r"enable-php-(\w{2,5})\.conf" + tmp = re.search(rep, conf) + if not tmp: + rep = r"enable-php-(\d+-wpfastcgi).conf" + tmp = re.search(rep, conf) + except: + return public.return_message(-1, 0, "Get enable php config failed!") + tmp = tmp.groups() + version = tmp[0] + bindingConf = r''' +#BINDING-%s-START +server +{ + listen %s;%s + server_name %s; + index index.php index.html index.htm default.php default.htm default.html; + root %s; + + include enable-php-%s.conf; + include %s/panel/vhost/rewrite/%s.conf; + %s + location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) + { + return 404; + } + + %s + location ~ \.well-known{ + allow all; + } + + location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + error_log /dev/null; + access_log /dev/null; + } + location ~ .*\\.(js|css)?$ + { + expires 12h; + error_log /dev/null; + access_log /dev/null; + } + access_log %s.log; + error_log %s.error.log; +} +#BINDING-%s-END''' % (domain, port, listen_ipv6, domain, webdir, version, self.setupPath, siteInfo['name'], + public.get_msg_gettext('# Forbidden files or directories'), public.get_msg_gettext( + '# Directory verification related settings for one-click application for SSL certificate'), + public.GetConfigValue('logs_path') + '/' + siteInfo['name'], + public.GetConfigValue('logs_path') + '/' + siteInfo['name'], domain) + + conf += bindingConf + shutil.copyfile(filename, self.nginx_conf_bak) + public.writeFile(filename, conf) + + filename = self.setupPath + '/panel/vhost/apache/' + siteInfo['name'] + '.conf' + conf = public.readFile(filename) + if conf: + try: + try: + httpdVersion = public.readFile(self.setupPath + '/apache/version.pl').strip() + except: + httpdVersion = "" + if httpdVersion == '2.2': + phpConfig = "" + apaOpt = "Order allow,deny\n\t\tAllow from all" + else: + # rep = r"php-cgi-([0-9]{2,3})\.sock" + # tmp = re.search(rep,conf).groups() + # version = tmp[0] + version = public.get_php_version_conf(conf) + phpConfig = ''' + #PHP + + SetHandler "proxy:%s" + + ''' % (public.get_php_proxy(version, 'apache'),) + apaOpt = 'Require all granted' + + bindingConf = r''' + +#BINDING-%s-START + + ServerAdmin webmaster@example.com + DocumentRoot "%s" + ServerAlias %s + #errorDocument 404 /404.html + ErrorLog "%s-error_log" + CustomLog "%s-access_log" combined + %s + + #DENY FILES + + Order allow,deny + Deny from all + + + #PATH + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + %s + DirectoryIndex index.php index.html index.htm default.php default.html default.htm + + +#BINDING-%s-END''' % (domain, port, webdir, domain, public.GetConfigValue('logs_path') + '/' + siteInfo['name'], + public.GetConfigValue('logs_path') + '/' + siteInfo['name'], phpConfig, webdir, apaOpt, domain) + + conf += bindingConf + shutil.copyfile(filename, self.apache_conf_bak) + public.writeFile(filename, conf) + except: + pass + get.webname = siteInfo['name'] + get.port = port + self.phpVersion = version + self.siteName = siteInfo['name'] + self.sitePath = webdir + listen_file = self.setupPath + "/panel/vhost/openlitespeed/listen/80.conf" + listen_conf = public.readFile(listen_file) + if listen_conf: + rep = r'secure\s*0' + map = '\tmap {}_{} {}'.format(siteInfo['name'], dirName, domain) + listen_conf = re.sub(rep, 'secure 0\n' + map, listen_conf) + public.writeFile(listen_file, listen_conf) + self.openlitespeed_add_site(get) + + # 检查配置是否有误 + isError = public.checkWebConfig() + if isError != True: + if os.path.exists(self.nginx_conf_bak): shutil.copyfile(self.nginx_conf_bak, nginx_conf_file) + if os.path.exists(self.apache_conf_bak): shutil.copyfile(self.apache_conf_bak, filename) + return_message = public.return_msg_gettext(False, + 'ERROR:
                                    ' + isError.replace("\n", + '
                                    ') + '
                                    ') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + public.M('binding').add('pid,domain,port,path,addtime', (id, domain, port, dirName, public.getDate())) + public.serviceReload() + public.write_log_gettext('Site manager', 'Site [{}] subdirectory [{}] bound to [{}]', + (siteInfo['name'], dirName, domain)) + return_message = public.return_msg_gettext(True, 'Successfully added') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + def delete_dir_bind_multiple(self, get): + ''' + @name 批量删除网站 + @author zhwen<2020-11-17> + @param bind_ids 1,2,3 + ''' + bind_ids = get.bind_ids.split(',') + del_successfully = [] + del_failed = {} + for bind_id in bind_ids: + get.id = bind_id + domain = public.M('binding').where("id=?", (get.id,)).getField('domain') + if not domain: + continue + try: + self.DelDirBinding(get, multiple=1) + del_successfully.append(domain) + except: + del_failed[domain] = public.get_msg_gettext('There was an error deleting, please try again.') + pass + public.serviceReload() + return_message = {'msg': public.get_msg_gettext('Delete [{}] subdirectory binding successfully', + (','.join(del_successfully),)), + 'error': del_failed, + 'success': del_successfully} + return public.return_message(0, 0, return_message) + + # 删除子目录绑定 + def DelDirBinding(self, get, multiple=None): + id = get.id + binding = public.M('binding').where("id=?", (id,)).field('id,pid,domain,path').find() + siteName = public.M('sites').where("id=?", (binding['pid'],)).getField('name') + + # nginx + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + conf = public.readFile(filename) + if conf: + rep = r"\s*.+BINDING-" + binding['domain'] + "-START(.|\n)+BINDING-" + binding['domain'] + "-END" + conf = re.sub(rep, '', conf) + public.writeFile(filename, conf) + + # apache + filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + conf = public.readFile(filename) + if conf: + rep = r"\s*.+BINDING-" + binding['domain'] + "-START(.|\n)+BINDING-" + binding['domain'] + "-END" + conf = re.sub(rep, '', conf) + public.writeFile(filename, conf) + + # openlitespeed + filename = self.setupPath + '/panel/vhost/openlitespeed/' + siteName + '.conf' + conf = public.readFile(filename) + rep = "#SUBDIR\\s*{s}_{d}\\s*START(\n|.)+#SUBDIR\\s*{s}_{d}\\s*END".format(s=siteName, d=binding['path']) + if conf: + conf = re.sub(rep, '', conf) + public.writeFile(filename, conf) + # 删除域名,前端需要传域名 + get.webname = siteName + get.domain = binding['domain'] + self._del_ols_domain(get) + + # 清理子域名监听文件 + listen_file = self.setupPath + "/panel/vhost/openlitespeed/listen/80.conf" + listen_conf = public.readFile(listen_file) + if listen_conf: + map_reg = r'\s*map\s*{}_{}.*'.format(siteName, binding['path']) + listen_conf = re.sub(map_reg, '', listen_conf) + public.writeFile(listen_file, listen_conf) + # 清理detail文件 + detail_file = "{}/panel/vhost/openlitespeed/detail/{}_{}.conf".format(self.setupPath, siteName, binding['path']) + public.ExecShell("rm -f {}*".format(detail_file)) + + # 从数据库删除绑定 + public.M('binding').where("id=?", (id,)).delete() + + # 如果没有其它域名绑定同一子目录,则删除该子目录的伪静态规则 + if not public.M('binding').where("path=? AND pid=?", (binding['path'], binding['pid'])).count(): + filename = self.setupPath + '/panel/vhost/rewrite/' + siteName + '_' + binding['path'] + '.conf' + if os.path.exists(filename): public.ExecShell('rm -rf %s' % filename) + # 是否需要重载服务 + if not multiple: + public.serviceReload() + public.write_log_gettext('Site manager', 'Deleted site [{}] subdirectory [{}] binding', + (siteName, binding['path'])) + return_message = public.return_msg_gettext(True, 'Successfully deleted') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取子目录Rewrite + def GetDirRewrite(self, get): + id = get.id + find = public.M('binding').where("id=?", (id,)).field('id,pid,domain,path').find() + site = public.M('sites').where("id=?", (find['pid'],)).field('id,name,path').find() + + if (public.get_webserver() != 'nginx'): + filename = site['path'] + '/' + find['path'] + '/.htaccess' + else: + filename = self.setupPath + '/panel/vhost/rewrite/' + site['name'] + '_' + find['path'] + '.conf' + + if hasattr(get, 'add'): + public.writeFile(filename, '') + if public.get_webserver() == 'nginx': + file = self.setupPath + '/panel/vhost/nginx/' + site['name'] + '.conf' + conf = public.readFile(file) + domain = find['domain'] + rep = "\n#BINDING-" + domain + "-START(.|\n)+BINDING-" + domain + "-END" + tmp = re.search(rep, conf).group() + dirConf = tmp.replace('rewrite/' + site['name'] + '.conf;', + 'rewrite/' + site['name'] + '_' + find['path'] + '.conf;') + conf = conf.replace(tmp, dirConf) + public.writeFile(file, conf) + data = {} + return_status = -1 + if os.path.exists(filename): + return_status = 0 + data['data'] = public.readFile(filename) + data['rlist'] = ['0.default'] + webserver = public.get_webserver() + if webserver == "openlitespeed": + webserver = "apache" + for ds in os.listdir('rewrite/' + webserver): + if ds == 'list.txt': continue + data['rlist'].append(ds[0:len(ds) - 5]) + data['filename'] = filename + return public.return_message(return_status, 0, data) + + # 取默认文档 + def GetIndex(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + Name = public.M('sites').where("id=?", (id,)).getField('name') + file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/' + Name + '.conf' + if public.get_webserver() == 'openlitespeed': + file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/detail/' + Name + '.conf' + conf = public.readFile(file) + if conf == False: return public.return_message(-1, 0, 'Configuration file not exist') + if public.get_webserver() == 'nginx': + rep = r"\s+index\s+(.+);" + elif public.get_webserver() == 'apache': + rep = "DirectoryIndex\\s+(.+)\n" + else: + rep = "indexFiles\\s+(.+)\n" + if re.search(rep, conf): + tmp = re.search(rep, conf).groups() + if public.get_webserver() == 'openlitespeed': + return public.return_message(0, 0, tmp[0]) + return public.return_message(0, 0, tmp[0].replace(' ', ',')) + return public.return_message(-1, 0, 'Failed to get, there is no default document in the configuration file') + + # 设置默认文档 + def SetIndex(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + Param('Index').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + + Index = get.Index.replace(' ', '') + Index = Index.replace(',,', ',').strip() + if not Index: return public.return_message(-1, 0, "Default index file cannot be empty") + if get.Index.find('.') == -1: return public.return_message(-1, 0, + 'Default Document Format is invalid, e.g., index.html') + + if len(Index) < 3: return public.return_message(-1, 0, 'Default Document cannot be empty!') + + Name = public.M('sites').where("id=?", (id,)).getField('name') + # 准备指令 + Index_L = Index.replace(",", " ") + + # nginx + file = self.setupPath + '/panel/vhost/nginx/' + Name + '.conf' + conf = public.readFile(file) + if conf: + rep = r"\s+index\s+.+;" + conf = re.sub(rep, "\n\tindex " + Index_L + ";", conf) + public.writeFile(file, conf) + + # apache + file = self.setupPath + '/panel/vhost/apache/' + Name + '.conf' + conf = public.readFile(file) + if conf: + rep = "DirectoryIndex\\s+.+\n" + conf = re.sub(rep, 'DirectoryIndex ' + Index_L + "\n", conf) + public.writeFile(file, conf) + + # openlitespeed + file = self.setupPath + '/panel/vhost/openlitespeed/detail/' + Name + '.conf' + conf = public.readFile(file) + if conf: + rep = "indexFiles\\s+.+\n" + Index = Index.split(',') + Index = [i for i in Index if i] + Index = ",".join(Index) + conf = re.sub(rep, 'indexFiles ' + Index + "\n", conf) + public.writeFile(file, conf) + + public.serviceReload() + public.write_log_gettext('Site manager', 'Defualt document of site [{}] is [{}]', (Name, Index_L)) + return public.return_message(0, 0, 'Setup successfully!') + + # 修改物理路径 + def SetPath(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('id').Integer(), + Param('path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + Path = self.GetPath(get.path) + if Path == "" or id == '0': return public.return_message(-1, 0, 'Directory cannot be empty!') + + if not self.__check_site_path(Path): return public.return_message(-1, 0, + 'System critical directory cannot be used as site directory') + if not public.check_site_path(Path): + a, c = public.get_sys_path() + return public.return_message(-1, 0, + 'Please do not set the website root directory to the system main directory:
                                    {}'.format( + "
                                    ".join(a + c))) + + SiteFind = public.M("sites").where("id=?", (id,)).field('path,name').find() + if SiteFind["path"] == Path: return public.return_msg_gettext(False, + 'Same as original path, no need to change!') + Name = SiteFind['name'] + file = self.setupPath + '/panel/vhost/nginx/' + Name + '.conf' + conf = public.readFile(file) + if conf: + conf = conf.replace(SiteFind['path'], Path) + public.writeFile(file, conf) + + file = self.setupPath + '/panel/vhost/apache/' + Name + '.conf' + conf = public.readFile(file) + if conf: + rep = "DocumentRoot\\s+.+\n" + conf = re.sub(rep, 'DocumentRoot "' + Path + '"\n', conf) + rep = "\n", conf) + public.writeFile(file, conf) + + # OLS + file = self.setupPath + '/panel/vhost/openlitespeed/' + Name + '.conf' + conf = public.readFile(file) + if conf: + reg = 'vhRoot.*' + conf = re.sub(reg, 'vhRoot ' + Path, conf) + public.writeFile(file, conf) + + # 创建basedir + userIni = Path + '/.user.ini' + if os.path.exists(userIni): public.ExecShell("chattr -i " + userIni) + public.writeFile(userIni, 'open_basedir=' + Path + '/:/tmp/') + public.ExecShell('chmod 644 ' + userIni) + public.ExecShell('chown root:root ' + userIni) + public.ExecShell('chattr +i ' + userIni) + public.set_site_open_basedir_nginx(Name) + + public.serviceReload() + public.M("sites").where("id=?", (id,)).setField('path', Path) + public.write_log_gettext('Site manager', 'Successfully changed directory of site [{}]!', (Name,)) + self.CheckRunPathExists(id) + return public.return_message(0, 0, 'Successfully set') + + def CheckRunPathExists(self, site_id): + ''' + @name 检查站点运行目录是否存在 + @author hwliang + @param site_id int 站点ID + @return bool + ''' + + site_info = public.M('sites').where('id=?', (site_id,)).field('name,path').find() + if not site_info: return False + args = public.dict_obj() + args.id = site_id + run_path = self.GetRunPath(args)['message']['result'] + site_run_path = site_info['path'] + '/' + run_path + if os.path.exists(site_run_path): return True + args.runPath = '/' + self.SetSiteRunPath(args) + public.WriteLog('TYPE_SITE', + 'Due to modifying the root directory of the website [{}], the original running directory [.{}] does not exist, and the directory has been automatically switched to [./]'.format( + site_info['name'], run_path)) + return False + + # 取当前可用PHP版本 + def GetPHPVersion(self, get, is_http=True): + # 校验参数--无参数,暂不需要添加校验 + + phpVersions = public.get_php_versions() + phpVersions.insert(0, 'other') + phpVersions.insert(0, '00') + httpdVersion = "" + filename = self.setupPath + '/apache/version.pl' + if os.path.exists(filename): httpdVersion = public.readFile(filename).strip() + + if httpdVersion == '2.2': phpVersions = ('00', '52', '53', '54') + if httpdVersion == '2.4': + if '52' in phpVersions: phpVersions.remove('52') + if os.path.exists('/www/server/nginx/sbin/nginx'): + cfile = '/www/server/nginx/conf/enable-php-00.conf' + if not os.path.exists(cfile): public.writeFile(cfile, '') + + s_type = getattr(get, 's_type', 0) + data = [] + for val in phpVersions: + tmp = {} + checkPath = self.setupPath + '/php/' + val + '/bin/php' + if val in ['00', 'other']: checkPath = '/etc/init.d/bt' + if httpdVersion == '2.2': checkPath = self.setupPath + '/php/' + val + '/libphp5.so' + if os.path.exists(checkPath): + tmp['version'] = val + tmp['name'] = 'PHP-' + val + if val == '00': + tmp['name'] = public.get_msg_gettext('Static') + + if val == 'other': + if s_type: + tmp['name'] = 'Customize' + else: + continue + data.append(tmp) + if is_http: + return public.return_message(0, 0, data) + return data + + # 取指定站点的PHP版本 + def GetSitePHPVersion(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + siteName = get.siteName + data = {} + data['phpversion'] = public.get_site_php_version(siteName) + conf = public.readFile(self.setupPath + '/panel/vhost/' + public.get_webserver() + '/' + siteName + '.conf') + data['tomcat'] = conf.find('#TOMCAT-START') + data['tomcatversion'] = public.readFile(self.setupPath + '/tomcat/version.pl') + data['nodejsversion'] = public.readFile(self.setupPath + '/node.js/version.pl') + data['php_other'] = '' + if data['phpversion'] == 'other': + other_file = '/www/server/panel/vhost/other_php/{}/enable-php-other.conf'.format(siteName) + if os.path.exists(other_file): + conf = public.readFile(other_file) + data['php_other'] = re.findall(r"fastcgi_pass\s+(.+);", conf)[0] + return public.return_message(0, 0, data) + except: + return public.return_message(-1, 0, + 'Apache2.2 does NOT support MultiPHP!,{}'.format(public.get_error_info())) + + def set_site_php_version_multiple(self, get): + ''' + @name 批量设置PHP版本 + @author zhwen<2020-11-17> + @param sites_id "1,2" + @param version 52...74 + ''' + # 校验参数 + try: + get.validate([ + Param('version').String(), + Param('sites_id').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + sites_id = get.sites_id.split(',') + set_phpv_successfully = [] + set_phpv_failed = {} + for site_id in sites_id: + get.id = site_id + get.siteName = public.M('sites').where("id=?", (site_id,)).getField('name') + if not get.siteName: + continue + try: + result = self.SetPHPVersion(get, multiple=1) + if result['status'] == -1: + set_phpv_failed[get.siteName] = result['message'] + continue + set_phpv_successfully.append(get.siteName) + except: + set_phpv_failed[get.siteName] = public.get_msg_gettext('There was an error setting, please try again.') + pass + public.serviceReload() + return_message = {'msg': public.get_msg_gettext( + 'Set up website [{}] PHP version successfully'.format(','.join(set_phpv_successfully), )), + 'error': set_phpv_failed, + 'success': set_phpv_successfully} + return public.return_message(0, 0, return_message) + + # 设置指定站点的PHP版本 + def SetPHPVersion(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + Param('version').String(), + Param('other').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = get.siteName + version = get.version + if version == 'other' and not public.get_webserver() in ['nginx', 'tengine']: + return public.return_message(-1, 0, 'Custom PHP configuration only supports Nginx') + try: + # nginx + file = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + conf = public.readFile(file) + if conf: + wp00 = "/www/server/nginx/conf/enable-php-00-wpfastcgi.conf" + if not os.path.exists(wp00): + public.writeFile(wp00, '') + other_path = '/www/server/panel/vhost/other_php/{}'.format(siteName) + if not os.path.exists(other_path): os.makedirs(other_path) + other_rep = "{}/enable-php-other.conf".format(other_path) + + if version == 'other': + dst = other_rep + get.other = get.other.strip() + + if not get.other: + return public.return_message(-1, 0, + 'The PHP connection configuration cannot be empty when customizing the version!') + + if not re.match(r"^(\d+\.\d+\.\d+\.\d+:\d+|unix:[\w/\.-]+)$", get.other): + return public.return_message(-1, 0, + 'The PHP connection configuration format is incorrect, please refer to the example!') + + other_tmp = get.other.split(':') + if other_tmp[0] == 'unix': + if not os.path.exists(other_tmp[1]): + return public.return_message(-1, 0, 'The specified unix socket [{}] does not exist!'.format( + other_tmp[1])) + else: + if not public.check_tcp(other_tmp[0], int(other_tmp[1])): + return public.return_message(-1, 0, + 'Unable to connect to [{}], please check whether the machine can connect to the target server'.format( + get.other)) + + other_conf = r'''location ~ [^/]\.php(/|$) +{{ + try_files $uri =404; + fastcgi_pass {}; + fastcgi_index index.php; + include fastcgi.conf; + include pathinfo.conf; +}}'''.format(get.other) + public.writeFile(other_rep, other_conf) + conf = conf.replace(other_rep, dst) + rep = r"include\s+enable-php-(\w{2,5})\.conf" + tmp = re.search(rep, conf) + if tmp: conf = conf.replace(tmp.group(), 'include ' + dst) + elif re.search(r"enable-php-\d+-wpfastcgi.conf", conf): + dst = 'enable-php-{}-wpfastcgi.conf'.format(version) + conf = conf.replace(other_rep, dst) + rep = r"enable-php-\d+-wpfastcgi.conf" + tmp = re.search(rep, conf) + if tmp: conf = conf.replace(tmp.group(), dst) + else: + dst = 'enable-php-' + version + '.conf' + conf = conf.replace(other_rep, dst) + rep = r"enable-php-(\w{2,5})\.conf" + tmp = re.search(rep, conf) + if tmp: conf = conf.replace(tmp.group(), dst) + public.writeFile(file, conf) + try: + import site_dir_auth_v2 as site_dir_auth + site_dir_auth_module = site_dir_auth.SiteDirAuth() + auth_list = site_dir_auth_module.get_dir_auth(get) + if auth_list: + for i in auth_list[siteName]: + auth_name = i['name'] + auth_file = "{setup_path}/panel/vhost/nginx/dir_auth/{site_name}/{auth_name}.conf".format( + setup_path=self.setupPath, site_name=siteName, auth_name=auth_name) + if os.path.exists(auth_file): + site_dir_auth_module.change_dir_auth_file_nginx_phpver(siteName, version, auth_name) + except: + pass + + # apache + file = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + conf = public.readFile(file) + if conf and version != 'other': + rep = r"(unix:/tmp/php-cgi-(\w{2,5})\.sock\|fcgi://localhost|fcgi://127.0.0.1:\d+)" + tmp = re.search(rep, conf).group() + conf = conf.replace(tmp, public.get_php_proxy(version, 'apache')) + public.writeFile(file, conf) + # OLS + if version != 'other': + file = self.setupPath + '/panel/vhost/openlitespeed/detail/' + siteName + '.conf' + conf = public.readFile(file) + if conf: + rep = r'lsphp\d+' + tmp = re.search(rep, conf) + if tmp: + conf = conf.replace(tmp.group(), 'lsphp' + version) + public.writeFile(file, conf) + if not multiple: + public.serviceReload() + public.write_log_gettext("Site manager", + 'Successfully changed PHP Version of site [{}] to PHP-{}'.format(siteName, + version)) + return public.return_message(0, 0, + 'Successfully changed PHP Version of site [{}] to PHP-{}'.format(siteName, + version)) + except: + return public.get_error_info() + return public.return_message(-1, 0, + 'Setup failed, no enable-php-xx related configuration items were found in the website configuration file!') + + # 是否开启目录防御 + def GetDirUserINI(self, get): + # 校验参数 + try: + get.validate([ + Param('path').String(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + path = get.path + self.GetRunPath(get)['message']['result'] + if not path: return public.return_message(-1, 0, 'Requested directory does not exist') + id = get.id + get.name = public.M('sites').where("id=?", (id,)).getField('name') + data = {} + data['logs'] = self.GetLogsStatus(get)['message'] + data['userini'] = False + user_ini_file = path + '/.user.ini' + user_ini_conf = public.readFile(user_ini_file) + if user_ini_conf and "open_basedir" in user_ini_conf: + data['userini'] = True + tmp_run_path = self.GetSiteRunPath(get) + if "result" in tmp_run_path: + data['runPath'] = tmp_run_path['message']['result'] + else: + data['runPath'] = tmp_run_path['message'] + data['pass'] = self.GetHasPwd(get)['message'] + return public.return_message(0, 0, data) + + # 清除多余user.ini + def DelUserInI(self, path, up=0): + useriniPath = path + '/.user.ini' + if os.path.exists(useriniPath): + public.ExecShell('chattr -i ' + useriniPath) + try: + os.remove(useriniPath) + except: + pass + + for p1 in os.listdir(path): + try: + npath = path + '/' + p1 + if not os.path.isdir(npath): continue + useriniPath = npath + '/.user.ini' + if os.path.exists(useriniPath): + public.ExecShell('chattr -i ' + useriniPath) + os.remove(useriniPath) + if up < 3: self.DelUserInI(npath, up + 1) + except: + continue + return True + + # 设置目录防御 + def SetDirUserINI(self, get): + # 校验参数 + try: + get.validate([ + Param('path').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + path = get.path + runPath = self.GetRunPath(get)['message']['result'] + filename = path + runPath + '/.user.ini' + siteName = public.M('sites').where('path=?', (get.path,)).getField('name') + conf = public.readFile(filename) + try: + self._set_ols_open_basedir(get) + public.ExecShell("chattr -i " + filename) + if conf and "open_basedir" in conf: + rep = "\n*open_basedir.*" + conf = re.sub(rep, "", conf) + if not conf: + os.remove(filename) + else: + public.writeFile(filename, conf) + public.ExecShell("chattr +i " + filename) + public.set_site_open_basedir_nginx(siteName) + return public.return_message(0, 0, 'Base directory turned off!') + + if conf and "session.save_path" in conf: + rep = r"session.save_path\s*=\s*(.*)" + s_path = re.search(rep, conf).groups(1)[0] + public.writeFile(filename, conf + '\nopen_basedir={}/:/tmp/:{}'.format(path, s_path)) + else: + public.writeFile(filename, 'open_basedir={}/:/tmp/'.format(path)) + public.ExecShell("chattr +i " + filename) + public.set_site_open_basedir_nginx(siteName) + public.serviceReload() + return public.return_message(0, 0, 'Base directory turned on!') + except Exception as e: + public.ExecShell("chattr +i " + filename) + return public.return_message(-1, 0, str(e)) + + def _set_ols_open_basedir(self, get): + # 设置ols + try: + sitename = public.M('sites').where("id=?", (get.id,)).getField('name') + # sitename = path.split('/')[-1] + f = "/www/server/panel/vhost/openlitespeed/detail/{}.conf".format(sitename) + c = public.readFile(f) + if not c: return public.return_message(-1, 0, "") + if f: + rep = '\nphp_admin_value\\s*open_basedir.*' + result = re.search(rep, c) + s = 'on' + if not result: + s = 'off' + rep = '\n#php_admin_value\\s*open_basedir.*' + result = re.search(rep, c) + result = result.group() + if s == 'on': + c = re.sub(rep, '\n#' + result[1:], c) + else: + result = result.replace('#', '') + c = re.sub(rep, result, c) + public.writeFile(f, c) + except: + pass + return public.return_message(0, 0, "") + + # 读配置 + def __read_config(self, path): + if not os.path.exists(path): + public.writeFile(path, '[]') + upBody = public.readFile(path) + if not upBody: upBody = '[]' + return json.loads(upBody) + + # 写配置 + + def __write_config(self, path, data): + return public.writeFile(path, json.dumps(data)) + + # 取某个站点某条反向代理详情 + + def GetProxyDetals(self, get): + proxyUrl = self.__read_config(self.__proxyfile) + sitename = get.sitename + proxyname = get.proxyname + for i in proxyUrl: + if i["proxyname"] == proxyname and i["sitename"] == sitename: + return public.return_message(0, 0, i) + + # 取某个站点反向代理列表 + def GetProxyList(self, get): + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + n = 0 + for w in ["nginx", "apache"]: + conf_path = "%s/panel/vhost/%s/%s.conf" % (self.setupPath, w, get.sitename) + old_conf = "" + if os.path.exists(conf_path): + old_conf = public.readFile(conf_path) + rep = "(#PROXY-START(\n|.)+#PROXY-END)" + url_rep = r"proxy_pass (.*);|ProxyPass\s/\s(.*)|Host\s(.*);" + host_rep = r"Host\s(.*);" + if re.search(rep, old_conf): + # 构造代理配置 + if w == "nginx": + get.todomain = str(re.search(host_rep, old_conf).group(1)) + get.proxysite = str(re.search(url_rep, old_conf).group(1)) + else: + get.todomain = "" + get.proxysite = str(re.search(url_rep, old_conf).group(2)) + get.proxyname = public.get_msg_gettext('Old proxy') + get.type = 1 + get.proxydir = "/" + get.advanced = 0 + get.cachetime = 1 + get.cache = 0 + get.subfilter = "[{\"sub1\":\"\",\"sub2\":\"\"},{\"sub1\":\"\",\"sub2\":\"\"},{\"sub1\":\"\",\"sub2\":\"\"}]" + + # proxyname_md5 = self.__calc_md5(get.proxyname) + # 备份并替换老虚拟主机配置文件 + public.ExecShell("cp %s %s_bak" % (conf_path, conf_path)) + conf = re.sub(rep, "", old_conf) + public.writeFile(conf_path, conf) + if n == 0: + self.CreateProxy(get) + n += 1 + # 写入代理配置 + # proxypath = "%s/panel/vhost/%s/proxy/%s/%s_%s.conf" % ( + # self.setupPath, w, get.sitename, proxyname_md5, get.sitename) + # proxycontent = str(re.search(rep, old_conf).group(1)) + # public.writeFile(proxypath, proxycontent) + if n == "1": + public.serviceReload() + proxyUrl = self.__read_config(self.__proxyfile) + sitename = get.sitename + proxylist = [] + for i in proxyUrl: + if i["sitename"] == sitename: + proxylist.append(i) + return public.return_message(0, 0, proxylist) + + def del_proxy_multiple(self, get): + ''' + @name 批量网站到期时间 + @author zhwen<2020-11-20> + @param site_id 1 + @param proxynames ces,aaa + ''' + proxynames = get.proxynames.split(',') + del_successfully = [] + del_failed = {} + get.sitename = public.M('sites').where("id=?", (get.site_id,)).getField('name') + for proxyname in proxynames: + if not proxyname: + continue + get.proxyname = proxyname + try: + resule = self.RemoveProxy(get, multiple=1) + if resule['status'] == -1: + del_failed[proxyname] = resule['msg'] + del_successfully.append(proxyname) + except: + del_failed[proxyname] = public.get_msg_gettext('There was an error deleting, please try again.') + pass + return_message = {'msg': public.get_msg_gettext('Delete [ {} ] proxy successfully', (','.join(del_failed),)), + 'error': del_failed, + 'success': del_successfully} + return public.return_message(0, 0, return_message) + + # 删除反向代理 + def RemoveProxy(self, get, multiple=None): + # 校验参数 + try: + get.validate([ + Param('proxyname').String(), + Param('sitename').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + conf = self.__read_config(self.__proxyfile) + sitename = get.sitename + proxyname = get.proxyname + for i in range(len(conf)): + c_sitename = conf[i]["sitename"] + c_proxyname = conf[i]["proxyname"] + if c_sitename == sitename and c_proxyname == proxyname: + proxyname_md5 = self.__calc_md5(c_proxyname) + for w in ["apache", "nginx", "openlitespeed"]: + p = "{sp}/panel/vhost/{w}/proxy/{s}/{m}_{s}.conf*".format(sp=self.setupPath, w=w, s=c_sitename, + m=proxyname_md5) + + public.ExecShell('rm -f {}'.format(p)) + p = "{sp}/panel/vhost/openlitespeed/proxy/{s}/urlrewrite/{m}_{s}.conf*".format(sp=self.setupPath, + m=proxyname_md5, + s=get.sitename) + public.ExecShell('rm -f {}'.format(p)) + del conf[i] + self.__write_config(self.__proxyfile, conf) + self.SetNginx(get) + self.SetApache(get.sitename) + if not multiple: + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Successfully deleted') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 检查代理是否存在 + def __check_even(self, get, action=""): + conf_data = self.__read_config(self.__proxyfile) + for i in conf_data: + if i["sitename"] == get.sitename: + if action == "create": + if i["proxydir"] == get.proxydir or i["proxyname"] == get.proxyname: + return public.return_message(0, 0, i) + else: + if i["proxyname"] != get.proxyname and i["proxydir"] == get.proxydir: + return public.return_message(0, 0, i) + + # 检测全局代理和目录代理是否同时存在 + def __check_proxy_even(self, get, action=""): + conf_data = self.__read_config(self.__proxyfile) + n = 0 + if action == "": + for i in conf_data: + if i["sitename"] == get.sitename: + n += 1 + if n == 1: + return public.return_message(0, 0, "") + for i in conf_data: + if i["sitename"] == get.sitename: + if i["advanced"] != int(get.advanced): + return public.return_message(-1, 0, i) + return public.return_message(0, 0, "") + + # 计算proxyname md5 + def __calc_md5(self, proxyname): + md5 = hashlib.md5() + md5.update(proxyname.encode('utf-8')) + return md5.hexdigest() + + # 检测URL是否可以访问 + def __CheckUrl(self, get): + sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sk.settimeout(5) + rep = r"(https?)://([\w\.\-]+):?([\d]+)?" + h = re.search(rep, get.proxysite).group(1) + d = re.search(rep, get.proxysite).group(2) + try: + p = re.search(rep, get.proxysite).group(3) + except: + p = "" + try: + if p: + sk.connect((d, int(p))) + else: + if h == "http": + sk.connect((d, 80)) + else: + sk.connect((d, 443)) + except: + return_message = public.return_msg_gettext(False, 'Can NOT get target URL') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + # 基本设置检查 + def __CheckStart(self, get, action=""): + isError = public.checkWebConfig() + if isinstance(isError, str): + if isError.find('/proxy/') == -1: # 如果是反向代理配置文件本身的错误,跳过 + return public.return_message(-1, 0, + 'An error was detected in the configuration file. Please solve it before proceeding') + if action == "create": + if sys.version_info.major < 3: + if len(get.proxyname) < 3 or len(get.proxyname) > 40: + return public.return_message(-1, 0, 'Database name cannot be more than 40 characters!') + else: + if len(get.proxyname.encode("utf-8")) < 3 or len(get.proxyname.encode("utf-8")) > 40: + return public.return_message(-1, 0, 'Database name cannot be more than 40 characters!') + if self.__check_even(get, action): + return public.return_message(-1, 0, 'Specified reverse proxy name or proxy folder already exists') + # 判断代理,只能有全局代理或目录代理 + check_proxy_even = self.__check_proxy_even(get, action) + if check_proxy_even['status'] == -1: + return public.return_message(-1, 0, 'Cannot set both directory and global proxies') + # 判断cachetime类型 + if get.cachetime: + try: + int(get.cachetime) + except: + return public.return_message(-1, 0, 'Please enter number') + + rep = r"http(s)?\:\/\/" + # repd = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + tod = "[a-zA-Z]+$" + repte = "[\\?\\=\\[\\]\\)\\(\\*\\&\\^\\%\\$\\#\\@\\!\\~\\`{\\}\\>\\<\\,\',\"]+" + # 检测代理目录格式 + if re.search(repte, get.proxydir): + return public.return_message(-1, 0, "PROXY_DIR_ERR", ("?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]",)) + # 检测发送域名格式 + if get.todomain: + if re.search("[\\}\\{\\#\\;\"\']+", get.todomain): + return public.return_message(-1, 0, + 'Sent Domain format error :' + get.todomain + '
                                    The following special characters cannot exist [ } { # ; \" \' ] ') + if public.get_webserver() != 'openlitespeed' and not get.todomain: + get.todomain = "$host" + + # 检测目标URL格式 + if not re.match(rep, get.proxysite): + return public.return_message(-1, 0, 'Sent domain format ERROR {}', (get.proxysite,)) + if re.search(repte, get.proxysite): + return public.return_message(-1, 0, "PROXY_URL_ERR", ("?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]",)) + # 检测目标url是否可用 + # if re.match(repd, get.proxysite): + # if self.__CheckUrl(get): + # return public.returnMsg(False, "目标URL无法访问") + subfilter = json.loads(get.subfilter) + # 检测替换内容 + if subfilter: + for s in subfilter: + if not s["sub1"]: + if s["sub2"]: + return public.return_message(-1, 0, 'Please enter the content to be replaced') + elif s["sub1"] == s["sub2"]: + return public.return_message(-1, 0, + 'The content to replace cannot be the same as the content to be replaced') + + # 设置Nginx配置 + def SetNginx(self, get): + ng_proxyfile = "%s/panel/vhost/nginx/proxy/%s/*.conf" % (self.setupPath, get.sitename) + ng_file = self.setupPath + "/panel/vhost/nginx/" + get.sitename + ".conf" + p_conf = self.__read_config(self.__proxyfile) + cureCache = '' + + if public.get_webserver() == 'nginx': + shutil.copyfile(ng_file, '/tmp/ng_file_bk.conf') + + # if os.path.exists('/www/server/nginx/src/ngx_cache_purge'): + cureCache += ''' + location ~ /purge(/.*) { + proxy_cache_purge cache_one $host$1$is_args$args; + #access_log /www/wwwlogs/%s_purge_cache.log; + }''' % (get.sitename) + if os.path.exists(ng_file): + self.CheckProxy(get) + ng_conf = public.readFile(ng_file) + if not p_conf: + # rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.{1,66}[\\s\\w\\/\\*\\.\\;]+include enable-php-" % public.GetMsg( + # "CLEAR_CACHE") + rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.*\n.*" % public.get_msg_gettext("#Clear cache") + # ng_conf = re.sub(rep, 'include enable-php-', ng_conf) + ng_conf = re.sub(rep, '', ng_conf) + oldconf = '''location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + error_log /dev/null; + access_log /dev/null; + } + location ~ .*\\.(js|css)?$ + { + expires 12h; + error_log /dev/null; + access_log /dev/null; + }''' + if "(gif|jpg|jpeg|png|bmp|swf)$" not in ng_conf: + ng_conf = re.sub(r'access_log\s*/www', oldconf + "\n\taccess_log /www", ng_conf) + public.writeFile(ng_file, ng_conf) + return public.return_message(0, 0, "") + sitenamelist = [] + for i in p_conf: + sitenamelist.append(i["sitename"]) + + if get.sitename in sitenamelist: + rep = r"include.*\/proxy\/.*\*.conf;" + if not re.search(rep, ng_conf): + rep = "location.+\\(gif[\\w\\|\\$\\(\\)\n\\{\\}\\s\\;\\/\\~\\.\\*\\\\\\?]+access_log\\s+/" + ng_conf = re.sub(rep, 'access_log /', ng_conf) + ng_conf = ng_conf.replace("include enable-php-", "%s\n" % public.get_msg_gettext( + "#Clear cache") + cureCache + "\n\t%s\n\t" % public.get_msg_gettext( + "#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid") + "include " + ng_proxyfile + ";\n\n\tinclude enable-php-") + public.writeFile(ng_file, ng_conf) + + else: + # rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.{1,66}[\\s\\w\\/\\*\\.\\;]+include enable-php-" % public.GetMsg( + # "CLEAR_CACHE") + rep = "%s[\\w\\s\\~\\/\\(\\)\\.\\*\\{\\}\\;\\$\n\\#]+.*\n.*" % public.get_msg_gettext("#Clear cache") + # ng_conf = re.sub(rep, 'include enable-php-', ng_conf) + ng_conf = re.sub(rep, '', ng_conf) + oldconf = '''location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + error_log /dev/null; + access_log /dev/null; + } + location ~ .*\\.(js|css)?$ + { + expires 12h; + error_log /dev/null; + access_log /dev/null; + }''' + if "(gif|jpg|jpeg|png|bmp|swf)$" not in ng_conf: + ng_conf = re.sub(r'access_log\s*/www', oldconf + "\n\taccess_log /www", ng_conf) + public.writeFile(ng_file, ng_conf) + return public.return_message(0, 0, "") + + # 设置apache配置 + + def SetApache(self, sitename): + ap_proxyfile = "%s/panel/vhost/apache/proxy/%s/*.conf" % (self.setupPath, sitename) + ap_file = self.setupPath + "/panel/vhost/apache/" + sitename + ".conf" + p_conf = public.readFile(self.__proxyfile) + + if public.get_webserver() == 'apache': + shutil.copyfile(ap_file, '/tmp/ap_file_bk.conf') + + if os.path.exists(ap_file): + ap_conf = public.readFile(ap_file) + if p_conf == "[]": + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext( + '#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') + ap_conf = re.sub(rep, '', ap_conf) + public.writeFile(ap_file, ap_conf) + return + if sitename in p_conf: + rep = "combined(\n|.)+IncludeOptional.*\\/proxy\\/.*conf" + rep1 = "combined" + if not re.search(rep, ap_conf): + ap_conf = ap_conf.replace(rep1, rep1 + "\n\t%s\n\t" % public.get_msg_gettext( + '#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') + "\n\tIncludeOptional " + ap_proxyfile) + public.writeFile(ap_file, ap_conf) + else: + # rep = "\n*#引用反向代理(\n|.)+IncludeOptional.*\\/proxy\\/.*conf" + rep = "\n*%s\n+\\s+IncludeOptiona[\\s\\w\\/\\.\\*]+" % public.get_msg_gettext( + '#Referenced reverse proxy rule, if commented, the configured reverse proxy will be invalid') + ap_conf = re.sub(rep, '', ap_conf) + public.writeFile(ap_file, ap_conf) + + # 设置OLS + def _set_ols_proxy(self, get): + # 添加反代配置 + proxyname_md5 = self.__calc_md5(get.proxyname) + dir_path = "%s/panel/vhost/openlitespeed/proxy/%s/" % (self.setupPath, get.sitename) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + file_path = "{}{}_{}.conf".format(dir_path, proxyname_md5, get.sitename) + reverse_proxy_conf = """ +extprocessor %s { + type proxy + address %s + maxConns 1000 + pcKeepAliveTimeout 600 + initTimeout 600 + retryTimeout 0 + respBuffer 0 +} +""" % (get.proxyname, get.proxysite) + public.writeFile(file_path, reverse_proxy_conf) + # 添加urlrewrite + dir_path = "%s/panel/vhost/openlitespeed/proxy/%s/urlrewrite/" % (self.setupPath, get.sitename) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + file_path = "{}{}_{}.conf".format(dir_path, proxyname_md5, get.sitename) + reverse_urlrewrite_conf = """ +RewriteRule ^%s(.*)$ http://%s/$1 [P,E=Proxy-Host:%s] +""" % (get.proxydir, get.proxyname, get.todomain) + public.writeFile(file_path, reverse_urlrewrite_conf) + return public.return_message(0, 0, "") + + # 检查伪静态、主配置文件是否有location冲突 + def CheckLocation(self, get): + # 伪静态文件路径 + rewriteconfpath = "%s/panel/vhost/rewrite/%s.conf" % (self.setupPath, get.sitename) + # 主配置文件路径 + nginxconfpath = "%s/nginx/conf/nginx.conf" % (self.setupPath) + # vhost文件 + vhostpath = "%s/panel/vhost/nginx/%s.conf" % (self.setupPath, get.sitename) + + rep = "location\\s+/[\n\\s]+{" + + for i in [rewriteconfpath, nginxconfpath, vhostpath]: + conf = public.readFile(i) + if re.findall(rep, conf): + return public.return_message(-1, 0, + 'A global reverse proxy already exists in the rewrite/nginx master configuration/vhost file') + return public.return_message(0, 0, "") + + # 创建反向代理 + def CreateProxy(self, get): + # 校验参数 + try: + get.validate([ + Param('proxyname').String(), + Param('proxydir').String(), + Param('proxysite').String(), + Param('todomain').String(), + Param('sitename').String(), + Param('subfilter').String(), + Param('type').Integer(), + Param('cache').Integer(), + Param('advanced').Integer(), + Param('cachetime').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + nocheck = get.nocheck + except: + nocheck = "" + if not get.get('proxysite', None): + return public.return_message(-1, 0, 'Destination URL cannot be empty') + if not nocheck: + if self.__CheckStart(get, "create"): + return self.__CheckStart(get, "create") + if public.get_webserver() == 'nginx': + if self.CheckLocation(get)['message']['result']: + return self.CheckLocation(get) + if not get.proxysite.split('//')[-1]: + return public.return_message(-1, 0, + 'The target URL cannot be [http:// or https://], please fill in the full URL, such as: https://aapanel.com') + # project_type = public.M('sites').where('name=?', (get.sitename,)).field('project_type').find()['project_type'] + # if project_type == 'WP': + # return public.return_msg_gettext(False,'Reverse proxies are not currently available for Wordpress sites that use one-click deployment') + proxyUrl = self.__read_config(self.__proxyfile) + proxyUrl.append({ + "proxyname": get.proxyname, + "sitename": get.sitename, + "proxydir": get.proxydir, + "proxysite": get.proxysite, + "todomain": get.todomain, + "type": int(get.type), + "cache": int(get.cache), + "subfilter": json.loads(get.subfilter), + "advanced": int(get.advanced), + "cachetime": int(get.cachetime) + }) + self.__write_config(self.__proxyfile, proxyUrl) + self.SetNginx(get) + self.SetApache(get.sitename) + self._set_ols_proxy(get) + status = self.SetProxy(get) + if status["status"] == -1: + return status + if get.proxydir == '/': + get.version = '00' + get.siteName = get.sitename + self.SetPHPVersion(get) + public.serviceReload() + return public.return_message(0, 0, 'Setup successfully!') + + # 取代理配置文件 + def GetProxyFile(self, get): + # 校验参数 + try: + get.validate([ + Param('proxyname').String(), + Param('sitename').String(), + Param('webserver').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import files_v2 as files + conf = self.__read_config(self.__proxyfile) + sitename = get.sitename + proxyname = get.proxyname + proxyname_md5 = self.__calc_md5(proxyname) + get.path = "%s/panel/vhost/%s/proxy/%s/%s_%s.conf" % ( + self.setupPath, get.webserver, sitename, proxyname_md5, sitename) + for i in conf: + if proxyname == i["proxyname"] and sitename == i["sitename"] and i["type"] != 1: + return public.return_message(-1, 0, 'Proxy suspended') + f = files.files() + return_message = f.GetFileBody(get) + return_message['message']['file'] = get.path + return return_message + + # 保存代理配置文件 + def SaveProxyFile(self, get): + # 校验参数 + try: + get.validate([ + Param('path').String(), + Param('data').String(), + Param('encoding').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import files_v2 as files + f = files.files() + return f.SaveFileBody(get) + # return public.returnMsg(True, '保存成功') + + # 检查是否存在#Set Nginx Cache + def check_annotate(self, data): + rep = "\n\\s*#Set\\s*Nginx\\s*Cache" + if re.search(rep, data): + return True + + def old_proxy_conf(self, conf, ng_conf_file, get): + rep = r'location\s*\~\*.*gif\|png\|jpg\|css\|js\|woff\|woff2\)\$' + if not re.search(rep, conf): + return public.return_message(0, 0, conf) + + self.RemoveProxy(get) + self.CreateProxy(get) + return public.return_message(0, 0, public.readFile(ng_conf_file)) + + # 修改反向代理 + def ModifyProxy(self, get): + # 校验参数 + try: + get.validate([ + Param('proxyname').String(), + Param('proxydir').String(), + Param('proxysite').String(), + Param('todomain').String(), + Param('sitename').String(), + Param('subfilter').String(), + Param('type').Integer(), + Param('cache').Integer(), + Param('advanced').Integer(), + Param('cachetime').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + if not get.get('proxysite', None): + return public.return_message(-1, 0, 'Destination URL cannot be empty') + proxyname_md5 = self.__calc_md5(get.proxyname) + ap_conf_file = "{p}/panel/vhost/apache/proxy/{s}/{n}_{s}.conf".format( + p=self.setupPath, s=get.sitename, n=proxyname_md5) + ng_conf_file = "{p}/panel/vhost/nginx/proxy/{s}/{n}_{s}.conf".format( + p=self.setupPath, s=get.sitename, n=proxyname_md5) + ols_conf_file = "{p}/panel/vhost/openlitespeed/proxy/{s}/urlrewrite/{n}_{s}.conf".format( + p=self.setupPath, s=get.sitename, n=proxyname_md5) + if self.__CheckStart(get): + return self.__CheckStart(get) + conf = self.__read_config(self.__proxyfile) + random_string = public.GetRandomString(8) + for i in range(len(conf)): + if conf[i]["proxyname"] == get.proxyname and conf[i]["sitename"] == get.sitename: + if int(get.type) != 1: + if not os.path.exists(ng_conf_file): + return public.return_message(-1, 0, "Please enable the reverse proxy before editing!") + public.ExecShell("mv {f} {f}_bak".format(f=ap_conf_file)) + public.ExecShell("mv {f} {f}_bak".format(f=ng_conf_file)) + public.ExecShell("mv {f} {f}_bak".format(f=ols_conf_file)) + conf[i]["type"] = int(get.type) + self.__write_config(self.__proxyfile, conf) + public.serviceReload() + return public.return_message(0, 0, 'Setup successfully!') + else: + if os.path.exists(ap_conf_file + "_bak"): + public.ExecShell("mv {f}_bak {f}".format(f=ap_conf_file)) + public.ExecShell("mv {f}_bak {f}".format(f=ng_conf_file)) + public.ExecShell("mv {f}_bak {f}".format(f=ols_conf_file)) + ng_conf = public.readFile(ng_conf_file) + ng_conf = self.old_proxy_conf(ng_conf, ng_conf_file, get)['message']['result'] + # 修改nginx配置 + # 如果代理URL后缀带有URI则删除URI,正则匹配不支持proxypass处带有uri + php_pass_proxy = get.proxysite + if get.proxysite[-1] == '/' or get.proxysite.count('/') > 2 or '?' in get.proxysite: + php_pass_proxy = re.search(r'(https?\:\/\/[\w\.]+)', get.proxysite).group(0) + ng_conf = re.sub(r"location\s+[\^\~]*\s?%s" % conf[i]["proxydir"], "location ^~ " + get.proxydir, + ng_conf) + ng_conf = re.sub(r"proxy_pass\s+%s" % conf[i]["proxysite"], "proxy_pass " + get.proxysite, ng_conf) + ng_conf = re.sub("location\\s+\\~\\*\\s+\\\\.\\(php.*\n\\{\\s*proxy_pass\\s+%s.*" % (php_pass_proxy), + "location ~* \\.(php|jsp|cgi|asp|aspx)$\n{\n\tproxy_pass %s;" % php_pass_proxy, + ng_conf) + ng_conf = re.sub("location\\s+\\~\\*\\s+\\\\.\\(gif.*\n\\{\\s*proxy_pass\\s+%s.*" % (php_pass_proxy), + "location ~* \\.(gif|png|jpg|css|js|woff|woff2)$\n{\n\tproxy_pass %s;" % php_pass_proxy, + ng_conf) + + backslash = "" + if "Host $host" in ng_conf: + backslash = "\\" + ng_conf = re.sub(r"\sHost\s+%s" % backslash + conf[i]["todomain"], " Host " + get.todomain, ng_conf) + cache_rep = r"proxy_cache_valid\s+200\s+304\s+301\s+302\s+\d+m;((\n|.)+expires\s+\d+m;)*" + if int(get.cache) == 1: + if re.search(cache_rep, ng_conf): + expires_rep = "\\{\n\\s+expires\\s+12h;" + ng_conf = re.sub(expires_rep, "{", ng_conf) + ng_conf = re.sub(cache_rep, "proxy_cache_valid 200 304 301 302 {0}m;".format(get.cachetime), + ng_conf) + else: + # ng_cache = """ + # proxy_ignore_headers Set-Cookie Cache-Control expires; + # proxy_cache cache_one; + # proxy_cache_key $host$uri$is_args$args; + # proxy_cache_valid 200 304 301 302 %sm;""" % (get.cachetime) + ng_cache = r""" + if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) + { + expires 1m; + } + proxy_ignore_headers Set-Cookie Cache-Control expires; + proxy_cache cache_one; + proxy_cache_key $host$uri$is_args$args; + proxy_cache_valid 200 304 301 302 %sm;""" % (get.cachetime) + if self.check_annotate(ng_conf): + cache_rep = '\n\\s*#Set\\s*Nginx\\s*Cache(.|\n)*no-cache;\\s*\n*\\s*\\}' + ng_conf = re.sub(cache_rep, '\n\t#Set Nginx Cache\n' + ng_cache, ng_conf) + else: + # cache_rep = r'#proxy_set_header\s+Connection\s+"upgrade";' + cache_rep = r"proxy_set_header\s+REMOTE-HOST\s+\$remote_addr;" + ng_conf = re.sub(cache_rep, + r"\n\tproxy_set_header\s+REMOTE-HOST\s+\$remote_addr;\n\t#Set Nginx Cache" + ng_cache, + ng_conf) + else: + no_cache = r""" + #Set Nginx Cache + set $static_file%s 0; + if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) + { + set $static_file%s 1; + expires 1m; + } + if ( $static_file%s = 0 ) + { + add_header Cache-Control no-cache; + } +} +#PROXY-END/""" % (random_string, random_string, random_string) + if self.check_annotate(ng_conf): + rep = r'\n\s*#Set\s*Nginx\s*Cache(.|\n)*' + # ng_conf = re.sub(rep, + # "\n\t#Set Nginx Cache\n\tproxy_ignore_headers Set-Cookie Cache-Control expires;\n\tadd_header Cache-Control no-cache;", + # ng_conf) + ng_conf = re.sub(rep, no_cache, ng_conf) + else: + rep = r"\s+proxy_cache\s+cache_one.*[\n\s\w\_\";\$]+m;" + # ng_conf = re.sub(rep, + # r"\n\t#Set Nginx Cache\n\tproxy_ignore_headers Set-Cookie Cache-Control expires;\n\tadd_header Cache-Control no-cache;", + # ng_conf) + ng_conf = re.sub(rep, no_cache, ng_conf) + + sub_rep = "sub_filter" + subfilter = json.loads(get.subfilter) + if str(conf[i]["subfilter"]) != str(subfilter) or ng_conf.find('sub_filter_once') == -1: + if re.search(sub_rep, ng_conf): + sub_rep = "\\s+proxy_set_header\\s+Accept-Encoding(.|\n)+off;" + ng_conf = re.sub(sub_rep, "", ng_conf) + + # 构造替换字符串 + ng_subdata = '' + ng_sub_filter = ''' + proxy_set_header Accept-Encoding "";%s + sub_filter_once off;''' + if subfilter: + for s in subfilter: + if not s["sub1"]: + continue + if '"' in s["sub1"]: + s["sub1"] = s["sub1"].replace('"', '\\"') + if '"' in s["sub2"]: + s["sub2"] = s["sub2"].replace('"', '\\"') + ng_subdata += '\n\tsub_filter "%s" "%s";' % (s["sub1"], s["sub2"]) + if ng_subdata: + ng_sub_filter = ng_sub_filter % (ng_subdata) + else: + ng_sub_filter = '' + sub_rep = r'#Set\s+Nginx\s+Cache' + ng_conf = re.sub(sub_rep, '#Set Nginx Cache\n' + ng_sub_filter, ng_conf) + + # 修改apache配置 + ap_conf = public.readFile(ap_conf_file) + ap_conf = re.sub(r"ProxyPass\s+%s\s+%s" % (conf[i]["proxydir"], conf[i]["proxysite"]), + "ProxyPass %s %s" % (get.proxydir, get.proxysite), ap_conf) + ap_conf = re.sub(r"ProxyPassReverse\s+%s\s+%s" % (conf[i]["proxydir"], conf[i]["proxysite"]), + "ProxyPassReverse %s %s" % (get.proxydir, get.proxysite), ap_conf) + # 修改OLS配置 + p = "{p}/panel/vhost/openlitespeed/proxy/{s}/{n}_{s}.conf".format(p=self.setupPath, n=proxyname_md5, + s=get.sitename) + c = public.readFile(p) + if c: + rep = r'address\s+(.*)' + new_proxysite = 'address\t{}'.format(get.proxysite) + c = re.sub(rep, new_proxysite, c) + public.writeFile(p, c) + + # p = "{p}/panel/vhost/openlitespeed/proxy/{s}/urlrewrite/{n}_{s}.conf".format(p=self.setupPath,n=proxyname_md5,s=get.sitename) + c = public.readFile(ols_conf_file) + if c: + rep = r'RewriteRule\s*\^{}\(\.\*\)\$\s+http://{}/\$1\s*\[P,E=Proxy-Host:{}\]'.format( + conf[i]["proxydir"], get.proxyname, conf[i]["todomain"]) + new_content = 'RewriteRule ^{}(.*)$ http://{}/$1 [P,E=Proxy-Host:{}]'.format(get.proxydir, + get.proxyname, + get.todomain) + c = re.sub(rep, new_content, c) + public.writeFile(ols_conf_file, c) + + conf[i]["proxydir"] = get.proxydir + conf[i]["proxysite"] = get.proxysite + conf[i]["todomain"] = get.todomain + conf[i]["type"] = int(get.type) + conf[i]["cache"] = int(get.cache) + conf[i]["subfilter"] = json.loads(get.subfilter) + conf[i]["advanced"] = int(get.advanced) + conf[i]["cachetime"] = int(get.cachetime) + + public.writeFile(ng_conf_file, ng_conf) + public.writeFile(ap_conf_file, ap_conf) + self.__write_config(self.__proxyfile, conf) + self.SetNginx(get) + self.SetApache(get.sitename) + # self.SetProxy(get) + + # if int(get.type) != 1: + # os.system("mv %s %s_bak" % (ap_conf_file, ap_conf_file)) + # os.system("mv %s %s_bak" % (ng_conf_file, ng_conf_file)) + if not hasattr(get, 'notreload'): + public.serviceReload() + return public.return_message(0, 0, 'Setup successfully!') + + # 设置反向代理 + + def SetProxy(self, get): + sitename = get.sitename # 站点名称 + advanced = int(get.advanced) + type = int(get.type) + cache = int(get.cache) + cachetime = int(get.cachetime) + proxysite = get.proxysite + proxydir = get.proxydir + ng_file = self.setupPath + "/panel/vhost/nginx/" + sitename + ".conf" + ap_file = self.setupPath + "/panel/vhost/apache/" + sitename + ".conf" + p_conf = self.__read_config(self.__proxyfile) + random_string = public.GetRandomString(8) + + # websocket前置map + map_file = self.setupPath + "/panel/vhost/nginx/0.websocket.conf" + if not os.path.exists(map_file): + map_body = '''map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} +''' + public.writeFile(map_file, map_body) + + # 配置Nginx + # 构造清理缓存连接 + + # 构造缓存配置 + ng_cache = r""" + if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) + { + expires 1m; + } + proxy_ignore_headers Set-Cookie Cache-Control expires; + proxy_cache cache_one; + proxy_cache_key $host$uri$is_args$args; + proxy_cache_valid 200 304 301 302 %sm;""" % (cachetime) + no_cache = r""" + set $static_file%s 0; + if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) + { + set $static_file%s 1; + expires 1m; + } + if ( $static_file%s = 0 ) + { + add_header Cache-Control no-cache; + }""" % (random_string, random_string, random_string) + # rep = r"(https?://[\w\.]+)" + # proxysite1 = re.search(rep,get.proxysite).group(1) + ng_proxy = ''' +#PROXY-START%s + +location %s +{ + proxy_pass %s; + proxy_set_header Host %s; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_http_version 1.1; + # proxy_hide_header Upgrade; + %s + + add_header X-Cache $upstream_cache_status; + + #Set Nginx Cache + %s + %s +} + +#PROXY-END%s''' + ng_proxy_cache = '' + proxyname_md5 = self.__calc_md5(get.proxyname) + ng_proxyfile = "%s/panel/vhost/nginx/proxy/%s/%s_%s.conf" % (self.setupPath, sitename, proxyname_md5, sitename) + ng_proxydir = "%s/panel/vhost/nginx/proxy/%s" % (self.setupPath, sitename) + if not os.path.exists(ng_proxydir): + public.ExecShell("mkdir -p %s" % ng_proxydir) + + # 构造替换字符串 + ng_subdata = '' + ng_sub_filter = ''' + proxy_set_header Accept-Encoding "";%s + sub_filter_once off;''' + if get.subfilter: + for s in json.loads(get.subfilter): + if not s["sub1"]: + continue + if '"' in s["sub1"]: + s["sub1"] = s["sub1"].replace('"', '\\"') + if '"' in s["sub2"]: + s["sub2"] = s["sub2"].replace('"', '\\"') + ng_subdata += '\n\tsub_filter "%s" "%s";' % (s["sub1"], s["sub2"]) + if ng_subdata: + ng_sub_filter = ng_sub_filter % (ng_subdata) + else: + ng_sub_filter = '' + # 构造反向代理 + # 如果代理URL后缀带有URI则删除URI,正则匹配不支持proxypass处带有uri + # php_pass_proxy = get.proxysite + # if get.proxysite[-1] == '/' or get.proxysite.count('/') > 2 or '?' in get.proxysite: + # php_pass_proxy = re.search(r'(https?\:\/\/[\w\.]+)', get.proxysite).group(0) + if advanced == 1: + if proxydir[-1] != '/': + proxydir = '{}/'.format(proxydir) + if proxysite[-1] != '/': + proxysite = '{}/'.format(proxysite) + if type == 1 and cache == 1: + ng_proxy_cache += ng_proxy % ( + proxydir, proxydir, proxysite, get.todomain, + public.get_msg_gettext('#Persistent connection related configuration'), ng_sub_filter, ng_cache, + get.proxydir) + if type == 1 and cache == 0: + ng_proxy_cache += ng_proxy % ( + get.proxydir, get.proxydir, proxysite, get.todomain, + public.get_msg_gettext('#Persistent connection related configuration'), ng_sub_filter, no_cache, + get.proxydir) + else: + if type == 1 and cache == 1: + ng_proxy_cache += ng_proxy % ( + get.proxydir, get.proxydir, get.proxysite, get.todomain, + public.get_msg_gettext('#Persistent connection related configuration'), ng_sub_filter, ng_cache, + get.proxydir) + if type == 1 and cache == 0: + ng_proxy_cache += ng_proxy % ( + get.proxydir, get.proxydir, get.proxysite, get.todomain, + public.get_msg_gettext('#Persistent connection related configuration'), ng_sub_filter, no_cache, + get.proxydir) + public.writeFile(ng_proxyfile, ng_proxy_cache) + + # APACHE + # 反向代理文件 + ap_proxyfile = "%s/panel/vhost/apache/proxy/%s/%s_%s.conf" % ( + self.setupPath, get.sitename, proxyname_md5, get.sitename) + ap_proxydir = "%s/panel/vhost/apache/proxy/%s" % (self.setupPath, get.sitename) + if not os.path.exists(ap_proxydir): + public.ExecShell("mkdir -p %s" % ap_proxydir) + ap_proxy = '' + if type == 1: + ap_proxy += '''#PROXY-START%s + + ProxyRequests Off + SSLProxyEngine on + ProxyPass %s %s/ + ProxyPassReverse %s %s/ + +#PROXY-END%s''' % (get.proxydir, get.proxydir, get.proxysite, get.proxydir, + get.proxysite, get.proxydir) + public.writeFile(ap_proxyfile, ap_proxy) + isError = public.checkWebConfig() + if (isError != True): + if public.get_webserver() == "nginx": + shutil.copyfile('/tmp/ng_file_bk.conf', ng_file) + else: + shutil.copyfile('/tmp/ap_file_bk.conf', ap_file) + for i in range(len(p_conf) - 1, -1, -1): + if get.sitename == p_conf[i]["sitename"] and p_conf[i]["proxyname"]: + del p_conf[i] + self.RemoveProxy(get) + return public.return_message(-1, 0, 'ERROR: %s
                                    ' % public.get_msg_gettext( + 'Configuration ERROR') + isError.replace("\n", + '
                                    ') + '
                                    ') + return public.return_message(0, 0, 'Setup successfully!') + + # 开启缓存 + def ProxyCache(self, get): + if public.get_webserver() != 'nginx': + return_message = public.return_msg_gettext(False, 'Currently only support Nginx') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + file = self.setupPath + "/panel/vhost/nginx/" + get.siteName + ".conf" + conf = public.readFile(file) + if conf.find('proxy_pass') == -1: + return_message = public.return_msg_gettext(False, 'Failed to set') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if conf.find('#proxy_cache') != -1: + conf = conf.replace('#proxy_cache', 'proxy_cache') + conf = conf.replace('#expires 12h', 'expires 12h') + else: + conf = conf.replace('proxy_cache', '#proxy_cache') + conf = conf.replace('expires 12h', '#expires 12h') + + public.writeFile(file, conf) + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 检查反向代理配置 + def CheckProxy(self, get): + if public.get_webserver() != 'nginx': return public.return_message(0, 0, "") + file = self.setupPath + "/nginx/conf/proxy.conf" + if not os.path.exists(file): + conf = '''proxy_temp_path %s/nginx/proxy_temp_dir; + proxy_cache_path %s/nginx/proxy_cache_dir levels=1:2 keys_zone=cache_one:10m inactive=1d max_size=5g; + client_body_buffer_size 512k; + proxy_connect_timeout 60; + proxy_read_timeout 60; + proxy_send_timeout 60; + proxy_buffer_size 32k; + proxy_buffers 4 64k; + proxy_busy_buffers_size 128k; + proxy_temp_file_write_size 128k; + proxy_next_upstream error timeout invalid_header http_500 http_503 http_404; + proxy_cache cache_one;''' % (self.setupPath, self.setupPath) + public.writeFile(file, conf) + + file = self.setupPath + "/nginx/conf/nginx.conf" + conf = public.readFile(file) + if (conf.find('include proxy.conf;') == -1): + rep = r"include\s+mime.types;" + conf = re.sub(rep, "include mime.types;\n\tinclude proxy.conf;", conf) + public.writeFile(file, conf) + return public.return_message(0, 0, "") + + def get_project_find(self, project_name): + ''' + @name 获取指定项目配置 + @author hwliang<2021-08-09> + @param project_name 项目名称 + @return dict + ''' + project_info = public.M('sites').where('project_type=? AND name=?', ('Java', project_name)).find() + if not project_info: False + project_info['project_config'] = json.loads(project_info['project_config']) + return project_info + + # 取伪静态规则应用列表 + def GetRewriteList(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if get.siteName.find('node_') == 0: + get.siteName = get.siteName.replace('node_', '') + rewriteList = {} + ws = public.get_webserver() + if ws == "openlitespeed": + ws = "apache" + if ws == 'apache': + get.id = public.M('sites').where("name=?", (get.siteName,)).getField('id') + runPath = self.GetSiteRunPath(get).get('message', {}) + if runPath.get('runPath', '').find('/www/server/stop') != -1: + runPath['runPath'] = runPath['runPath'].replace('/www/server/stop', '') + rewriteList['sitePath'] = public.M('sites').where("name=?", (get.siteName,)).getField('path') + runPath.get( + 'runPath', '') + + rewriteList['rewrite'] = [] + rewriteList['rewrite'].append('0.' + public.get_msg_gettext('Current')) + for ds in os.listdir('rewrite/' + ws): + if ds == 'list.txt': continue + rewriteList['rewrite'].append(ds[0:len(ds) - 5]) + rewriteList['rewrite'] = sorted(rewriteList['rewrite']) + return public.return_message(0, 0, rewriteList) + + # 保存伪静态模板 + def SetRewriteTel(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('data').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + ws = public.get_webserver() + if not get.name: + return_message = public.return_msg_gettext(True, 'Please enter a template name') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + if ws == "openlitespeed": + ws = "apache" + if sys.version_info[0] == 2: get.name = get.name.encode('utf-8') + filename = 'rewrite/' + ws + '/' + get.name + '.conf' + public.writeFile(filename, get.data) + return_message = public.return_msg_gettext(True, 'New URL rewrite rule has been saved!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 打包 + def ToBackup(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + find = public.M('sites').where("id=?", (id,)).field('name,path,id').find() + import time + fileName = find['name'] + '_' + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.zip' + backupPath = session['config']['backup_path'] + '/site' + zipName = backupPath + '/' + fileName + if not (os.path.exists(backupPath)): os.makedirs(backupPath) + tmps = '/tmp/panelExec.log' + execStr = "cd '" + find['path'] + "' && zip '" + zipName + "' -x .user.ini -r ./ > " + tmps + " 2>&1" + public.ExecShell(execStr) + sql = public.M('backup').add('type,name,pid,filename,size,addtime', + (0, fileName, find['id'], zipName, 0, public.getDate())) + public.write_log_gettext('Site manager', 'Backup site [{}] succeed!', (find['name'],)) + return_message = public.return_msg_gettext(True, 'Backup Succeeded!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 删除备份文件 + def DelBackup(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + where = "id=?" + backup_info = public.M('backup').where(where, (id,)).find() + filename = backup_info['filename'] + if os.path.exists(filename): os.remove(filename) + name = '' + if filename == 'qiniu': + name = backup_info['name'] + public.ExecShell( + public.get_python_bin() + " " + self.setupPath + '/panel/script/backup_qiniu.py delete_file ' + name) + + pid = backup_info['pid'] + site_name = public.M('sites').where('id=?', (pid,)).getField('name') + public.write_log_gettext('Site manager', 'Successfully deleted backup [{}] of site [{}]!', + (site_name, filename)) + public.M('backup').where(where, (id,)).delete() + return_message = public.return_msg_gettext(True, 'Successfully deleted') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 旧版本配置文件处理 + def OldConfigFile(self): + # 检查是否需要处理 + moveTo = 'data/moveTo.pl' + if os.path.exists(moveTo): return + + # 处理Nginx配置文件 + filename = self.setupPath + "/nginx/conf/nginx.conf" + if os.path.exists(filename): + conf = public.readFile(filename) + if conf.find('include vhost/*.conf;') != -1: + conf = conf.replace('include vhost/*.conf;', 'include ' + self.setupPath + '/panel/vhost/nginx/*.conf;') + public.writeFile(filename, conf) + + self.moveConf(self.setupPath + "/nginx/conf/vhost", self.setupPath + '/panel/vhost/nginx', 'rewrite', + self.setupPath + '/panel/vhost/rewrite') + self.moveConf(self.setupPath + "/nginx/conf/rewrite", self.setupPath + '/panel/vhost/rewrite') + + # 处理Apache配置文件 + filename = self.setupPath + "/apache/conf/httpd.conf" + if os.path.exists(filename): + conf = public.readFile(filename) + if conf.find('IncludeOptional conf/vhost/*.conf') != -1: + conf = conf.replace('IncludeOptional conf/vhost/*.conf', + 'IncludeOptional ' + self.setupPath + '/panel/vhost/apache/*.conf') + public.writeFile(filename, conf) + + self.moveConf(self.setupPath + "/apache/conf/vhost", self.setupPath + '/panel/vhost/apache') + + # 标记处理记录 + public.writeFile(moveTo, 'True') + public.serviceReload() + + # 移动旧版本配置文件 + def moveConf(self, Path, toPath, Replace=None, ReplaceTo=None): + if not os.path.exists(Path): return + import shutil + + letPath = '/etc/letsencrypt/live' + nginxPath = self.setupPath + '/nginx/conf/key' + apachePath = self.setupPath + '/apache/conf/key' + for filename in os.listdir(Path): + # 准备配置文件 + name = filename[0:len(filename) - 5] + filename = Path + '/' + filename + conf = public.readFile(filename) + + # 替换关键词 + if Replace: conf = conf.replace(Replace, ReplaceTo) + ReplaceTo = letPath + name + Replace = 'conf/key/' + name + if conf.find(Replace) != -1: conf = conf.replace(Replace, ReplaceTo) + Replace = 'key/' + name + if conf.find(Replace) != -1: conf = conf.replace(Replace, ReplaceTo) + public.writeFile(filename, conf) + + # 提取配置信息 + if conf.find('server_name') != -1: + self.formatNginxConf(filename) + elif conf.find(' 0: return + public.M('sites').add('name,path,status,ps,addtime', + (name, path, '1', public.get_msg_gettext('Please enter a note'), public.getDate())) + pid = public.M('sites').where("name=?", (name,)).getField('id') + for domain in domains: + public.M('domain').add('pid,name,port,addtime', (pid, domain, '80', public.getDate())) + + # 移动旧版本证书 + def moveKey(self, srcPath, dstPath): + if not os.path.exists(srcPath): return + import shutil + os.makedirs(dstPath) + srcKey = srcPath + '/key.key' + srcCsr = srcPath + '/csr.key' + if os.path.exists(srcKey): shutil.move(srcKey, dstPath + '/privkey.pem') + if os.path.exists(srcCsr): shutil.move(srcCsr, dstPath + '/fullchain.pem') + + # 路径处理 + def GetPath(self, path): + if path[-1] == '/': + return path[0:-1] + return path + + # 日志开关 + def logsOpen(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.name = public.M('sites').where("id=?", (get.id,)).getField('name') + # APACHE + filename = public.GetConfigValue('setup_path') + '/panel/vhost/apache/' + get.name + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + if conf.find('#ErrorLog') != -1: + conf = conf.replace("#ErrorLog", "ErrorLog").replace('#CustomLog', 'CustomLog') + else: + conf = conf.replace("ErrorLog", "#ErrorLog").replace('CustomLog', '#CustomLog') + public.writeFile(filename, conf) + + # NGINX + filename = public.GetConfigValue('setup_path') + '/panel/vhost/nginx/' + get.name + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + rep = public.GetConfigValue('logs_path') + "/" + get.name + ".log" + if conf.find(rep) != -1: + conf = conf.replace(rep, "/dev/null") + else: + # conf = re.sub('}\n\\s+access_log\\s+off', '}\n\taccess_log ' + rep, conf) + conf = conf.replace('access_log /dev/null', 'access_log ' + rep) + public.writeFile(filename, conf) + + # OLS + filename = public.GetConfigValue('setup_path') + '/panel/vhost/openlitespeed/detail/' + get.name + '.conf' + conf = public.readFile(filename) + if conf: + rep = "\nerrorlog(.|\n)*compressArchive\\s*1\\s*\n}" + tmp = re.search(rep, conf) + s = 'on' + if not tmp: + s = 'off' + rep = "\n#errorlog(.|\n)*compressArchive\\s*1\\s*\n#}" + tmp = re.search(rep, conf) + tmp = tmp.group() + if tmp: + result = '' + if s == 'on': + for l in tmp.strip().splitlines(): + result += "\n#" + l + else: + for l in tmp.splitlines(): + result += "\n" + l[1:] + conf = re.sub(rep, "\n" + result.strip(), conf) + public.writeFile(filename, conf) + + public.serviceReload() + return public.return_message(0, 0, 'Setup successfully!') + + # 取日志状态 + def GetLogsStatus(self, get): + filename = public.GetConfigValue( + 'setup_path') + '/panel/vhost/' + public.get_webserver() + '/' + get.name + '.conf' + if public.get_webserver() == 'openlitespeed': + filename = public.GetConfigValue( + 'setup_path') + '/panel/vhost/' + public.get_webserver() + '/detail/' + get.name + '.conf' + conf = public.readFile(filename) + if not conf: return public.return_message(0, 0, True) + if conf.find('#ErrorLog') != -1: return public.return_message(0, 0, False) + # if re.search("}\n*\\s*access_log\\s+off", conf): + if conf.find("access_log /dev/null") != -1: return public.return_message(0, 0, False) + if re.search('\n#accesslog', conf): + return public.return_message(0, 0, False) + return public.return_message(0, 0, True) + + # 取目录加密状态 + def GetHasPwd(self, get): + if not hasattr(get, 'siteName'): + get.siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + get.configFile = self.setupPath + '/panel/vhost/nginx/' + get.siteName + '.conf' + conf = public.readFile(get.configFile) + if type(conf) == bool: return public.return_message(0, 0, False) + if conf.find('#AUTH_START') != -1: return public.return_message(0, 0, True) + return public.return_message(0, 0, False) + + # 设置目录加密 + def SetHasPwd(self, get): + # 校验参数 + try: + get.validate([ + Param('username').String(), + Param('password').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if public.get_webserver() == 'openlitespeed': + return public.return_message(-1, 0, + 'The current web server is openlitespeed. This function is not supported yet.') + if len(get.username.strip()) < 3 or len(get.password.strip()) < 3: return public.return_message(-1, 0, + 'Username or password cannot be less than 3 digits!') + + if not hasattr(get, 'siteName'): + get.siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + + self.CloseHasPwd(get) + filename = public.GetConfigValue('setup_path') + '/pass/' + get.siteName + '.pass' + try: + passconf = get.username + ':' + public.hasPwd(get.password) + except: + return public.return_message(-1, 0, + "The password fomart is wrong, please do not use special symbols for the first two digits!") + + if get.siteName == 'phpmyadmin': + get.configFile = self.setupPath + '/nginx/conf/nginx.conf' + if os.path.exists(self.setupPath + '/panel/vhost/nginx/phpmyadmin.conf'): + get.configFile = self.setupPath + '/panel/vhost/nginx/phpmyadmin.conf' + else: + get.configFile = self.setupPath + '/panel/vhost/nginx/' + get.siteName + '.conf' + + # 处理Nginx配置 + conf = public.readFile(get.configFile) + if conf: + rep = '#error_page 404 /404.html;' + if conf.find(rep) == -1: rep = '#error_page 404/404.html;' + data = ''' + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file %s; + #AUTH_END''' % (filename,) + conf = conf.replace(rep, rep + data) + public.writeFile(get.configFile, conf) + + if get.siteName == 'phpmyadmin': + get.configFile = self.setupPath + '/apache/conf/extra/httpd-vhosts.conf' + if os.path.exists(self.setupPath + '/panel/vhost/apache/phpmyadmin.conf'): + get.configFile = self.setupPath + '/panel/vhost/apache/phpmyadmin.conf' + else: + get.configFile = self.setupPath + '/panel/vhost/apache/' + get.siteName + '.conf' + + conf = public.readFile(get.configFile) + if conf: + # 处理Apache配置 + rep = 'SetOutputFilter' + if conf.find(rep) != -1: + data = '''#AUTH_START + AuthType basic + AuthName "Authorization " + AuthUserFile %s + Require user %s + #AUTH_END + ''' % (filename, get.username) + conf = conf.replace(rep, data + rep) + conf = conf.replace(' Require all granted', " #Require all granted") + public.writeFile(get.configFile, conf) + + # 写密码配置 + passDir = public.GetConfigValue('setup_path') + '/pass' + if not os.path.exists(passDir): public.ExecShell('mkdir -p ' + passDir) + public.writeFile(filename, passconf) + public.serviceReload() + public.write_log_gettext("Site manager", "Set site [{}] to password authentication required!", (get.siteName,)) + return public.return_message(0, 0, 'Setup successfully!') + + # 取消目录加密 + def CloseHasPwd(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not hasattr(get, 'siteName'): + get.siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + + if get.siteName == 'phpmyadmin': + get.configFile = self.setupPath + '/nginx/conf/nginx.conf' + else: + get.configFile = self.setupPath + '/panel/vhost/nginx/' + get.siteName + '.conf' + + if os.path.exists(get.configFile): + conf = public.readFile(get.configFile) + rep = "\n\\s*#AUTH_START(.|\n){1,200}#AUTH_END" + conf = re.sub(rep, '', conf) + public.writeFile(get.configFile, conf) + + if get.siteName == 'phpmyadmin': + get.configFile = self.setupPath + '/apache/conf/extra/httpd-vhosts.conf' + else: + get.configFile = self.setupPath + '/panel/vhost/apache/' + get.siteName + '.conf' + + if os.path.exists(get.configFile): + conf = public.readFile(get.configFile) + rep = "\n\\s*#AUTH_START(.|\n){1,200}#AUTH_END" + conf = re.sub(rep, '', conf) + conf = conf.replace(' #Require all granted', " Require all granted") + public.writeFile(get.configFile, conf) + public.serviceReload() + public.write_log_gettext("Site manager", "Cleared password authentication for site [{}]!", (get.siteName,)) + return public.return_message(0, 0, 'Setup successfully!') + + # 启用tomcat支持 + def SetTomcat(self, get): + siteName = get.siteName + name = siteName.replace('.', '_') + + rep = r"^(\d{1,3}\.){3,3}\d{1,3}$" + if re.match(rep, siteName): + return_message = public.return_msg_gettext(False, 'ERROR, primary domain cannot be IP address!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + # nginx + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + if conf.find('#TOMCAT-START') != -1: return self.CloseTomcat(get) + tomcatConf = r'''#TOMCAT-START + location / + { + proxy_pass "http://%s:8080"; + proxy_set_header Host %s; + proxy_set_header X-Forwarded-For $remote_addr; + } + location ~ .*\.(gif|jpg|jpeg|bmp|png|ico|txt|js|css)$ + { + expires 12h; + } + + location ~ .*\.war$ + { + return 404; + } + #TOMCAT-END + ''' % (siteName, siteName) + rep = 'include enable-php' + conf = conf.replace(rep, tomcatConf + rep) + public.writeFile(filename, conf) + + # apache + filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + if conf.find('#TOMCAT-START') != -1: return self.CloseTomcat(get) + tomcatConf = '''#TOMCAT-START + + ProxyRequests Off + SSLProxyEngine on + ProxyPass / http://%s:8080/ + ProxyPassReverse / http://%s:8080/ + RequestHeader unset Accept-Encoding + ExtFilterDefine fixtext mode=output intype=text/html cmd="/bin/sed 's,:8080,,g'" + SetOutputFilter fixtext + + #TOMCAT-END + ''' % (siteName, siteName) + + rep = '#PATH' + conf = conf.replace(rep, tomcatConf + rep) + public.writeFile(filename, conf) + path = public.M('sites').where("name=?", (siteName,)).getField('path') + import tomcat + tomcat.tomcat().AddVhost(path, siteName) + public.serviceReload() + public.ExecShell('/etc/init.d/tomcat stop') + public.ExecShell('/etc/init.d/tomcat start') + public.ExecShell('echo "127.0.0.1 ' + siteName + '" >> /etc/hosts') + public.write_log_gettext('TYPE_SITE', 'Turned on Tomcat supporting for site [{}]!', (siteName,)) + return public.return_msg_gettext(True, 'Succeeded, please test JSP program!') + + # 关闭tomcat支持 + def CloseTomcat(self, get): + if not os.path.exists('/etc/init.d/tomcat'): return public.return_message(-1, 0, "") + siteName = get.siteName + name = siteName.replace('.', '_') + + # nginx + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + rep = "\\s*#TOMCAT-START(.|\n)+#TOMCAT-END" + conf = re.sub(rep, '', conf) + public.writeFile(filename, conf) + + # apache + filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + rep = "\\s*#TOMCAT-START(.|\n)+#TOMCAT-END" + conf = re.sub(rep, '', conf) + public.writeFile(filename, conf) + public.ExecShell('rm -rf ' + self.setupPath + '/panel/vhost/tomcat/' + name) + try: + import tomcat + tomcat.tomcat().DelVhost(siteName) + except: + pass + public.serviceReload() + public.ExecShell('/etc/init.d/tomcat restart') + public.ExecShell("sed -i '/" + siteName + "/d' /etc/hosts") + public.write_log_gettext('Site manager', 'Turned off Tomcat supporting for site [{}]!', (siteName,)) + return_message = public.return_msg_gettext(True, 'Tomcat mapping closed!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取当站点前运行目录 + def GetSiteRunPath(self, get): + siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + sitePath = public.M('sites').where('id=?', (get.id,)).getField('path') + if not siteName or os.path.isfile(sitePath): return public.return_message(0, 0, {"runPath": "/", 'dirs': []}) + path = sitePath + if public.get_webserver() == 'nginx': + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + rep = r'\s*root\s+(.+);' + path = re.search(rep, conf) + if not path: + return_message = public.return_msg_gettext(False, 'Get Site run path false') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + path = path.groups()[0] + elif public.get_webserver() == 'apache': + filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n' + path = re.search(rep, conf) + if not path: + return_message = public.return_msg_gettext(False, 'Get Site run path false') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + path = path.groups()[0] + else: + filename = self.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: + return_message = public.return_msg_gettext(False, 'Get Site run path false') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + path = path.groups()[0] + data = {} + if sitePath == path: + data['runPath'] = '/' + else: + data['runPath'] = path.replace(sitePath, '') + + if data['runPath'] == path: + data['runPath'] = '/' + + dirnames = [] + dirnames.append('/') + if not os.path.exists(sitePath): os.makedirs(sitePath) + for filename in os.listdir(sitePath): + try: + json.dumps(filename) + if sys.version_info[0] == 2: + filename = filename.encode('utf-8') + else: + filename.encode('utf-8') + filePath = sitePath + '/' + filename + if not os.path.exists(filePath): continue + if os.path.islink(filePath): continue + if os.path.isdir(filePath): + dirnames.append('/' + filename) + except: + pass + + data['dirs'] = dirnames + return public.return_message(0, 0, data) + + # 设置当前站点运行目录 + def SetSiteRunPath(self, get): + # 校验参数 + try: + get.validate([ + Param('runPath').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + sitePath = public.M('sites').where('id=?', (get.id,)).getField('path') + old_run_path = self.GetRunPath(get)['message']['result'] + # 处理Nginx + filename = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + if conf: + rep = r'\s*root\s+(.+);' + tmp = re.search(rep, conf) + if tmp: + path = tmp.groups()[0] + conf = conf.replace(path, sitePath + get.runPath) + public.writeFile(filename, conf) + + # 处理Apache + filename = self.setupPath + '/panel/vhost/apache/' + siteName + '.conf' + if os.path.exists(filename): + conf = public.readFile(filename) + if conf: + rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n' + tmp = re.search(rep, conf) + if tmp: + path = tmp.groups()[0] + conf = conf.replace(path, sitePath + get.runPath) + public.writeFile(filename, conf) + # 处理OLS + self._set_ols_run_path(sitePath, get.runPath, siteName) + # self.DelUserInI(sitePath) + # get.path = sitePath; + # self.SetDirUserINI(get); + s_path = sitePath + old_run_path + "/.user.ini" + d_path = sitePath + get.runPath + "/.user.ini" + if s_path != d_path: + public.ExecShell("chattr -i {}".format(s_path)) + public.ExecShell("mv {} {}".format(s_path, d_path)) + public.ExecShell("chattr +i {}".format(d_path)) + + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + def _set_ols_run_path(self, site_path, run_path, sitename): + ols_conf_file = "{}/panel/vhost/openlitespeed/{}.conf".format(self.setupPath, sitename) + ols_conf = public.readFile(ols_conf_file) + if not ols_conf: + return + reg = '#VHOST\\s*{s}\\s*START(.|\n)+#VHOST\\s*{s}\\s*END'.format(s=sitename) + tmp = re.search(reg, ols_conf) + if not tmp: + return + reg = r"vhRoot\s*(.*)" + # tmp = re.search(reg,tmp.group()) + # if not tmp: + # return + tmp = "vhRoot " + site_path + run_path + ols_conf = re.sub(reg, tmp, ols_conf) + public.writeFile(ols_conf_file, ols_conf) + + # 设置默认站点 + def SetDefaultSite(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import time + if public.GetWebServer() in ['openlitespeed']: + return public.return_message(-1, 0, 'OpenLiteSpeed does not support setting the default site') + default_site_save = 'data/defaultSite.pl' + # 清理旧的 + defaultSite = public.readFile(default_site_save) + http2 = '' + versionStr = public.readFile('/www/server/nginx/version.pl') + if versionStr: + if versionStr.find('1.8.1') == -1: http2 = ' http2' + if defaultSite: + path = self.setupPath + '/panel/vhost/nginx/' + defaultSite + '.conf' + if os.path.exists(path): + conf = public.readFile(path) + rep = r"listen\s+80.+;" + conf = re.sub(rep, 'listen 80;', conf, 1) + rep = r"listen\s+\[::\]:80.+;" + conf = re.sub(rep, 'listen [::]:80;', conf, 1) + rep = r"listen\s+443.+;" + conf = re.sub(rep, 'listen 443 ssl' + http2 + ';', conf, 1) + rep = r"listen\s+\[::\]:443.+;" + conf = re.sub(rep, 'listen [::]:443 ssl' + http2 + ';', conf, 1) + public.writeFile(path, conf) + + path = self.setupPath + '/apache/htdocs/.htaccess' + if os.path.exists(path): os.remove(path) + + if get.name == '0': + if os.path.exists(default_site_save): os.remove(default_site_save) + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 处理新的 + path = self.setupPath + '/apache/htdocs' + if os.path.exists(path): + conf = ''' + RewriteEngine on + RewriteCond %{HTTP_HOST} !^127.0.0.1 [NC] + RewriteRule (.*) http://%s/$1 [L] +''' + conf = conf.replace("%s", get.name) + if get.name == 'off': conf = '' + public.writeFile(path + '/.htaccess', conf) + + path = self.setupPath + '/panel/vhost/nginx/' + get.name + '.conf' + if os.path.exists(path): + conf = public.readFile(path) + rep = r"listen\s+80\s*;" + conf = re.sub(rep, 'listen 80 default_server;', conf, 1) + rep = r"listen\s+\[::\]:80\s*;" + conf = re.sub(rep, 'listen [::]:80 default_server;', conf, 1) + rep = r"listen\s+443\s*ssl\s*\w*\s*;" + conf = re.sub(rep, 'listen 443 ssl' + http2 + ' default_server;', conf, 1) + rep = r"listen\s+\[::\]:443\s*ssl\s*\w*\s*;" + conf = re.sub(rep, 'listen [::]:443 ssl' + http2 + ' default_server;', conf, 1) + public.writeFile(path, conf) + + path = self.setupPath + '/panel/vhost/nginx/default.conf' + if os.path.exists(path): public.ExecShell('rm -f ' + path) + public.writeFile(default_site_save, get.name) + public.serviceReload() + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取默认站点 + def GetDefaultSite(self, get): + data = {} + data['sites'] = public.M('sites').where('project_type=? OR project_type=?', ('PHP', 'WP')).field('name').order( + 'id desc').select() + data['defaultSite'] = public.readFile('data/defaultSite.pl') + return public.return_message(0, 0, data) + + # 扫描站点 + def CheckSafe(self, get): + import db, time + isTask = '/tmp/panelTask.pl' + if os.path.exists(self.setupPath + '/panel/class/panelSafe.py'): + import py_compile + py_compile.compile(self.setupPath + '/panel/class/panelSafe.py') + get.path = public.M('sites').where('id=?', (get.id,)).getField('path') + execstr = "cd " + public.GetConfigValue( + 'setup_path') + "/panel/class && " + public.get_python_bin() + " panelSafe.pyc " + get.path + sql = db.Sql() + sql.table('tasks').add('id,name,type,status,addtime,execstr', ( + None, '%s [' % public.get_msg_gettext('Scan directory') + get.path + ']', 'execshell', '0', + time.strftime('%Y-%m-%d %H:%M:%S'), + execstr)) + public.writeFile(isTask, 'True') + public.write_log_gettext('Installer', 'Added trojan scan task for directory [{}]!', (get.path,)) + return_message = public.return_msg_gettext(True, 'Scan Task has in the queue!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 获取结果信息 + def GetCheckSafe(self, get): + get.path = public.M('sites').where('id=?', (get.id,)).getField('path') + path = get.path + '/scan.pl' + result = {} + result['data'] = [] + result['phpini'] = [] + result['userini'] = result['sshd'] = True + result['scan'] = False + result['outime'] = result['count'] = result['error'] = 0 + if not os.path.exists(path): return result + import json + return public.return_message(0, 0, json.loads(public.readFile(path))) + + # 更新病毒库 + def UpdateRulelist(self, get): + try: + conf = public.httpGet(public.getUrl() + '/install/ruleList.conf') + if conf: + public.writeFile(self.setupPath + '/panel/data/ruleList.conf', conf) + return_message = public.return_msg_gettext(True, 'Update Succeeded!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + return_message = public.return_msg_gettext(False, 'Failed to connect server!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + except: + return_message = public.return_msg_gettext(False, 'Failed to connect server!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + def set_site_etime_multiple(self, get): + ''' + @name 批量网站到期时间 + @author zhwen<2020-11-17> + @param sites_id "1,2" + @param edate 2020-11-18 + ''' + sites_id = get.sites_id.split(',') + set_edate_successfully = [] + set_edate_failed = {} + for site_id in sites_id: + get.id = site_id + site_name = public.M('sites').where("id=?", (site_id,)).getField('name') + if not site_name: + continue + try: + self.SetEdate(get) + set_edate_successfully.append(site_name) + except: + set_edate_failed[site_name] = 'There was an error setting, please try again.' + pass + return_message = {'msg': public.get_msg_gettext('Set the website [{}] expiration time successfully', + (','.join(set_edate_successfully),)), + 'error': set_edate_failed, + 'success': set_edate_successfully} + return public.return_message(0, 0, return_message) + + # 设置到期时间 + def SetEdate(self, get): + # 校验参数 + try: + get.validate([ + Param('edate').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + result = public.M('sites').where('id=?', (get.id,)).setField('edate', get.edate) + siteName = public.M('sites').where('id=?', (get.id,)).getField('name') + public.write_log_gettext('Site manager', 'Set expired date to [{}] for site[{}]!', (get.edate, siteName)) + return_message = public.return_msg_gettext(True, + 'Successfully set, the site will stop automatically when expires!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 获取防盗链状态 + def GetSecurity(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + file = '/www/server/panel/vhost/nginx/' + get.name + '.conf' + conf = public.readFile(file) + data = {} + if type(conf) == bool: return public.return_message(-1, 0, 'Reading configuration file failed!') + if conf.find('SECURITY-START') != -1: + rep = "#SECURITY-START(\n|.)+#SECURITY-END" + tmp = re.search(rep, conf).group() + content = re.search(r"\(.+\)\$", tmp) + if content: + data['fix'] = content.group().replace('(', '').replace(')$', '').replace('|', ',') + else: + data['fix'] = '' + try: + data['domains'] = ','.join( + list(set(re.search("valid_referers\\s+none\\s+blocked\\s+(.+);\n", tmp).groups()[0].split()))) + except: + data['domains'] = ','.join(list(set(re.search("valid_referers\\s+(.+);\n", tmp).groups()[0].split()))) + data['status'] = True + data['http_status'] = tmp.find('none blocked') != -1 + try: + data['return_rule'] = re.findall(r'(return|rewrite)\s+.*(\d{3}|(/.+)\s+(break|last));', conf)[0][ + 1].replace('break', '').strip() + except: + data['return_rule'] = '404' + else: + conf_file = self.conf_dir + '/{}_door_chain.json'.format(get.name) + try: + data = json.loads(public.readFile(conf_file)) + data['status'] = data['status'] == "true" + except: + data['fix'] = 'jpg,jpeg,gif,png,js,css' + domains = public.M('domain').where('pid=?', (get.id,)).field('name').select() + tmp = [] + for domain in domains: + tmp.append(domain['name']) + data['domains'] = ','.join(tmp) + data['return_rule'] = '404' + data['status'] = False + data['http_status'] = False + return public.return_message(0, 0, data) + + # 设置防盗链 + def SetSecurity(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('fix').String(), + Param('domains').String(), + Param('return_rule').String(), + Param('id').Integer(), + Param('status').Bool(), + Param('http_status').Bool(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if len(get.fix) < 2: return public.return_message(-1, 0, 'URL suffix cannot be empty!') + if len(get.domains) < 3: return public.return_message(-1, 0, 'Anti-theft chain domain name cannot be empty!') + try: + conf_file = self.conf_dir + '/{}_door_chain.json'.format(get.name) + except Exception as ee: + public.print_log('ee:{}'.format(ee)) + data = { + "name": get.name, + "fix": get.fix, + "domains": get.domains, + "status": get.status, + "http_status": get.http_status, + "return_rule": get.return_rule, + } + public.writeFile(conf_file, json.dumps(data)) + # nginx + file = '/www/server/panel/vhost/nginx/' + get.name + '.conf' + if os.path.exists(file): + conf = public.readFile(file) + if conf.find('SECURITY-START') != -1: + # 先替换域名部分,防止域名过多导致替换失败 + rep = r"\s+valid_referers.+" + conf = re.sub(rep, '', conf) + # 再替换配置部分 + rep = "\\s+#SECURITY-START(\n|.){1,500}#SECURITY-END\n?" + conf = re.sub(rep, '\n', conf) + if get.status == 'false': + public.write_log_gettext('Site manager', "Hotlink Protection for site [{}] disabled!", (get.name,)) + public.writeFile(file, conf) + elif get.status == 'true': + if conf.find('SECURITY-START') == -1: + return_rule = 'return 404' + if 'return_rule' in get: + get.return_rule = get.return_rule.strip() + if get.return_rule in ['404', '403', '200', '301', '302', '401', '201']: + return_rule = 'return {}'.format(get.return_rule) + else: + if get.return_rule[0] != '/': + return public.return_message(-1, 0, + 'Response resources should use URI path or HTTP status code, such as: /test.png or 404') + return_rule = 'rewrite /.* {} break'.format(get.return_rule) + rconf = r'''#SECURITY-START Hotlink protection configuration + location ~ .*\.(%s)$ + { + expires 30d; + access_log /dev/null; + valid_referers %s; + if ($invalid_referer){ + %s; + } + } + #SECURITY-END + include enable-php-''' % ( + get.fix.strip().replace(',', '|'), get.domains.strip().replace(',', ' '), return_rule) + conf = re.sub(r"include\s+enable-php-", rconf, conf) + public.write_log_gettext('Site manager', "Hotlink Protection for site [{}] enabled!", (get.name,)) + + r_key = 'valid_referers none blocked' + d_key = 'valid_referers' + if get.http_status == 'true' and conf.find(r_key) == -1: + conf = conf.replace(d_key, r_key) + elif get.http_status == 'false' and conf.find(r_key) != -1: + conf = conf.replace(r_key, d_key) + public.writeFile(file, conf) + + # apache + file = '/www/server/panel/vhost/apache/' + get.name + '.conf' + if os.path.exists(file): + conf = public.readFile(file) + if conf.find('SECURITY-START') != -1: + rep = "#SECURITY-START(\n|.){1,500}#SECURITY-END\n" + conf = re.sub(rep, '', conf) + if get.status == "false": + public.writeFile(file, conf) + elif get.status == 'true': + if conf.find('SECURITY-START') == -1: + return_rule = '/404.html [R=404,NC,L]' + if 'return_rule' in get: + get.return_rule = get.return_rule.strip() + if get.return_rule in ['404', '403', '200', '301', '302', '401', '201']: + return_rule = '/{s}.html [R={s},NC,L]'.format(s=get.return_rule) + else: + if get.return_rule[0] != '/': + return public.return_message(-1, 0, + 'Response resources should use URI path or HTTP status code, such as: /test.png or 404') + return_rule = '{}'.format(get.return_rule) + + tmp = " RewriteCond %{HTTP_REFERER} !{DOMAIN} [NC]" + tmps = [] + for d in get.domains.split(','): + tmps.append(tmp.replace('{DOMAIN}', d)) + domains = "\n".join(tmps) + rconf = "combined\n #SECURITY-START Hotlink protection configuration\n RewriteEngine on\n" + domains + "\n RewriteRule .(" + get.fix.strip().replace( + ',', '|') + ") " + return_rule + "\n #SECURITY-END" + conf = conf.replace('combined', rconf) + + r_key = '#SECURITY-START Hotlink protection configuration\n RewriteEngine on\n RewriteCond %{HTTP_REFERER} !^$ [NC]\n' + d_key = '#SECURITY-START Hotlink protection configuration\n RewriteEngine on\n' + if get.http_status == 'true' and conf.find(r_key) == -1: + conf = conf.replace(d_key, r_key) + elif get.http_status == 'false' and conf.find(r_key) != -1: + if conf.find('SECURITY-START') == -1: public.return_message(-1, 0, + 'Please activate the anti-theft chain first!') + conf = conf.replace(r_key, d_key) + public.writeFile(file, conf) + # OLS + cond_dir = '/www/server/panel/vhost/openlitespeed/prevent_hotlink/' + if not os.path.exists(cond_dir): + os.makedirs(cond_dir) + file = cond_dir + get.name + '.conf' + if get.http_status == 'true': + conf = r""" +RewriteCond %{HTTP_REFERER} !^$ +RewriteCond %{HTTP_REFERER} !BTDOMAIN_NAME [NC] +RewriteRule \.(BTPFILE)$ /404.html [R,NC] +""" + conf = conf.replace('BTDOMAIN_NAME', get.domains.replace(',', ' ')).replace('BTPFILE', + get.fix.replace(',', '|')) + else: + conf = r""" +RewriteCond %{HTTP_REFERER} !BTDOMAIN_NAME [NC] +RewriteRule \.(BTPFILE)$ /404.html [R,NC] +""" + conf = conf.replace('BTDOMAIN_NAME', get.domains.replace(',', ' ')).replace('BTPFILE', + get.fix.replace(',', '|')) + public.writeFile(file, conf) + if get.status == "false": + if os.path.exists(file): os.remove(file) + public.serviceReload() + return public.return_message(0, 0, 'Setup successfully!') + + # xss 防御 + def xsssec(self, text): + replace_list = { + "<": "<", + ">": ">", + "'": "'", + '"': """, + } + for k, v in replace_list.items(): + text = text.replace(k, v) + return public.xssencode2(text) + + # 取网站日志 + def GetSiteLogs(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + serverType = public.get_webserver() + if serverType == "nginx": + logPath = '/www/wwwlogs/' + get.siteName + '.log' + elif serverType == 'apache': + logPath = '/www/wwwlogs/' + get.siteName + '-access_log' + else: + logPath = '/www/wwwlogs/' + get.siteName + '_ols.access_log' + if not os.path.exists(logPath): + return_message = public.return_msg_gettext(False, 'Log is empty') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + # return public.return_msg_gettext(True, self.xsssec(public.GetNumLines(logPath, 1000))) + return_message = public.return_msg_gettext(True, self.xsssec(public.GetNumLines(logPath, 1000))) + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + # if not os.path.exists(logPath): return public.return_message(-1,0, 'Log is empty') + # return public.return_message(0,0, self.xsssec(public.GetNumLines(logPath, 1000))) + + # 取网站日志 + def get_site_err_log(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + return_status = 0 + serverType = public.get_webserver() + if serverType == "nginx": + logPath = '/www/wwwlogs/' + get.siteName + '.error.log' + elif serverType == 'apache': + logPath = '/www/wwwlogs/' + get.siteName + '-error_log' + else: + logPath = '/www/wwwlogs/' + get.siteName + '_ols.error_log' + if not os.path.exists(logPath): + return_message = public.return_msg_gettext(False, 'Log is empty') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + return_message = public.return_msg_gettext(True, self.xsssec(public.GetNumLines(logPath, 1000))) + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 取网站分类 + def get_site_types(self, get): + data = public.M("site_types").field("id,name").order("id asc").select() + data.insert(0, {"id": 0, "name": public.get_msg_gettext('Default category')}) + for i in data: + i['name'] = public.xss_version(i['name']) + return public.return_message(0, 0, data) + + # 添加网站分类 + def add_site_type(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.name = get.name.strip() + if not get.name: + return_message = public.return_msg_gettext(False, 'Category name cannot be empty') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if len(get.name) > 16: + return_message = public.return_msg_gettext(False, 'Category name cannot exceed 16 letters') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + type_sql = public.M('site_types') + if type_sql.count() >= 10: + return_message = public.return_msg_gettext(False, 'Add up to 10 categories!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if type_sql.where('name=?', (get.name,)).count() > 0: + return_message = public.return_msg_gettext(False, 'Specified category name already exists!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + type_sql.add("name", (public.xssencode2(get.name),)) + return_message = public.return_msg_gettext(True, 'Setup successfully!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 删除网站分类 + def remove_site_type(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + type_sql = public.M('site_types') + if type_sql.where('id=?', (get.id,)).count() == 0: + return_message = public.return_msg_gettext(False, 'Specified category does NOT exist!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + type_sql.where('id=?', (get.id,)).delete() + public.M("sites").where("type_id=?", (get.id,)).save("type_id", (0,)) + return_message = public.return_msg_gettext(True, 'Category deleted!') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 修改网站分类名称 + def modify_site_type_name(self, get): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.name = get.name.strip() + if not get.name: + return_message = public.return_msg_gettext(False, 'Category name cannot be empty') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + if len(get.name) > 16: + return_message = public.return_msg_gettext(False, 'Category name cannot exceed 16 letters') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + type_sql = public.M('site_types') + if type_sql.where('id=?', (get.id,)).count() == 0: + return_message = public.return_msg_gettext(False, 'Specified category does NOT exist!') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + type_sql.where('id=?', (get.id,)).setField('name', get.name) + return_message = public.return_msg_gettext(True, 'Successfully modified') + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + # 设置指定站点的分类 + def set_site_type(self, get): + site_ids = json.loads(get.site_ids) + site_sql = public.M("sites") + for s_id in site_ids: + site_sql.where("id=?", (s_id,)).setField("type_id", get.id) + return public.return_message(0, 0, "Setup successfully!") + + # 设置目录保护 + def set_dir_auth(self, get): + sd = site_dir_auth.SiteDirAuth() + return sd.set_dir_auth(get) + + def delete_dir_auth_multiple(self, get): + ''' + @name 批量目录保护 + @author zhwen<2020-11-17> + @param site_id 1 + @param names test,baohu + ''' + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + names = get.names.split(',') + del_successfully = [] + del_failed = {} + for name in names: + get.name = name + get.id = get.site_id + try: + get.multiple = 1 + result = self.delete_dir_auth(get) + if not result['status']: + del_failed[name] = result['msg'] + continue + del_successfully.append(name) + except: + del_failed[name] = public.get_msg_gettext('There was an error deleting, please try again.') + public.serviceReload() + return_message = { + 'msg': public.get_msg_gettext('Delete [ {} ] dir auth successfully', (','.join(del_successfully),)), + 'error': del_failed, + 'success': del_successfully} + return public.return_message(0, 0, return_message) + + # 删除目录保护 + def delete_dir_auth(self, get): + sd = site_dir_auth.SiteDirAuth() + return sd.delete_dir_auth(get) + + # 获取目录保护列表 + def get_dir_auth(self, get): + sd = site_dir_auth.SiteDirAuth() + return sd.get_dir_auth(get) + + # 修改目录保护密码 + def modify_dir_auth_pass(self, get): + sd = site_dir_auth.SiteDirAuth() + return sd.modify_dir_auth_pass(get) + + def _check_path_total(self, path, limit): + """ + 根据路径获取文件/目录大小 + @path 文件或者目录路径 + return int + """ + + if not os.path.exists(path): return 0; + if not os.path.isdir(path): return os.path.getsize(path) + size_total = 0 + for nf in os.walk(path): + for f in nf[2]: + filename = nf[0] + '/' + f + if not os.path.exists(filename): continue; + if os.path.islink(filename): continue; + size_total += os.path.getsize(filename) + if size_total >= limit: return limit + return size_total + + def get_average_num(self, slist): + """ + @获取平均值 + """ + count = len(slist) + limit_size = 1 * 1024 * 1024 + if count <= 0: return limit_size + print(slist) + if len(slist) > 1: + slist = sorted(slist) + limit_size = int((slist[0] + slist[-1]) / 2 * 0.85) + return limit_size + + def check_del_data(self, get): + """ + @删除前置检测 + @ids = [1,2,3] + """ + # 校验参数 + try: + get.validate([ + Param('ids').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + ids = json.loads(get['ids']) + slist = {} + result = [] + + import database_v2 as database + db_data = database.database().get_database_size(ids, True) + limit_size = 50 * 1024 * 1024 + f_list_size = []; + db_list_size = [] + for id in ids: + data = public.M('sites').where("id=?", (id,)).field('id,name,path,addtime').find(); + if not data: continue + + addtime = public.to_date(times=data['addtime']) + + data['st_time'] = addtime + data['limit'] = False + data['backup_count'] = public.M('backup').where("pid=? AND type=?", (data['id'], '0')).count() + f_size = self._check_path_total(data['path'], limit_size) + data['total'] = f_size; + data['score'] = 0 + + # 目录太小不计分 + if f_size > 0: + f_list_size.append(f_size) + + # 10k 目录不参与排序 + if f_size > 10 * 1024: data['score'] = int(time.time() - addtime) + f_size + + if data['total'] >= limit_size: data['limit'] = True + data['database'] = False + + find = public.M('databases').field('id,pid,name,ps,addtime').where('pid=?', (data['id'],)).find() + if find: + db_addtime = public.to_date(times=find['addtime']) + + data['database'] = db_data[find['name']] + data['database']['st_time'] = db_addtime + + db_score = 0 + db_size = data['database']['total'] + + if db_size > 0: + db_list_size.append(db_size) + if db_size > 50 * 1024: db_score += int(time.time() - db_addtime) + db_size + + data['score'] += db_score + result.append(data) + + slist['data'] = sorted(result, key=lambda x: x['score'], reverse=True) + slist['file_size'] = self.get_average_num(f_list_size) + slist['db_size'] = self.get_average_num(db_list_size) + return public.return_message(0, 0, slist) + + def get_https_mode(self, get=None): + ''' + @name 获取https模式 + @author hwliang<2022-01-14> + @return bool False.宽松模式 True.严格模式 + ''' + web_server = public.get_webserver() + if web_server not in ['nginx', 'apache']: + return public.return_message(0, 0, False) + + if web_server == 'nginx': + default_conf_file = "{}/nginx/0.default.conf".format(public.get_vhost_path()) + else: + default_conf_file = "{}/apache/0.default.conf".format(public.get_vhost_path()) + + if not os.path.exists(default_conf_file): return public.return_message(0, 0, False) + default_conf = public.readFile(default_conf_file) + if not default_conf: return False + + if default_conf.find('DEFAULT SSL CONFI') != -1: return public.return_message(0, 0, True) + return public.return_message(0, 0, False) + + def write_ngx_default_conf_by_ssl(self): + ''' + @name 写nginx默认配置文件(含SSL配置) + @author hwliang<2022-01-14> + @return bool + ''' + default_conf_body = '''server +{ + listen 80; + listen 443 ssl; + server_name _; + index index.html; + root /www/server/nginx/html; + + # DEFAULT SSL CONFIG + ssl_certificate /www/server/panel/vhost/cert/0.default/fullchain.pem; + ssl_certificate_key /www/server/panel/vhost/cert/0.default/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + add_header Strict-Transport-Security "max-age=31536000"; +}''' + ngx_default_conf_file = "{}/nginx/0.default.conf".format(public.get_vhost_path()) + self.create_default_cert() + return public.writeFile(ngx_default_conf_file, default_conf_body) + + def write_ngx_default_conf(self): + ''' + @name 写nginx默认配置文件 + @author hwliang<2022-01-14> + @return bool + ''' + default_conf_body = '''server +{ + listen 80; + server_name _; + index index.html; + root /www/server/nginx/html; +}''' + ngx_default_conf_file = "{}/nginx/0.default.conf".format(public.get_vhost_path()) + return public.writeFile(ngx_default_conf_file, default_conf_body) + + def write_apa_default_conf_by_ssl(self): + ''' + @name 写nginx默认配置文件(含SSL配置) + @author hwliang<2022-01-14> + @return bool + ''' + default_conf_body = ''' + ServerAdmin webmaster@example.com + DocumentRoot "/www/server/apache/htdocs" + ServerName bt.default.com + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + DirectoryIndex index.html + + + + ServerAdmin webmaster@example.com + DocumentRoot "/www/server/apache/htdocs" + ServerName ssl.default.com + + # DEFAULT SSL CONFIG + SSLEngine On + SSLCertificateFile /www/server/panel/vhost/cert/0.default/fullchain.pem + SSLCertificateKeyFile /www/server/panel/vhost/cert/0.default/privkey.pem + SSLCipherSuite EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5 + SSLProtocol All -SSLv2 -SSLv3 -TLSv1 + SSLHonorCipherOrder On + + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + DirectoryIndex index.html + +''' + apa_default_conf_file = "{}/apache/0.default.conf".format(public.get_vhost_path()) + self.create_default_cert() + return public.writeFile(apa_default_conf_file, default_conf_body) + + def write_apa_default_conf(self): + ''' + @name 写apache默认配置文件 + @author hwliang<2022-01-14> + @return bool + ''' + default_conf_body = ''' + ServerAdmin webmaster@example.com + DocumentRoot "/www/server/apache/htdocs" + ServerName bt.default.com + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + DirectoryIndex index.html + +''' + apa_default_conf_file = "{}/apache/0.default.conf".format(public.get_vhost_path()) + return public.writeFile(apa_default_conf_file, default_conf_body) + + def set_https_mode(self, get=None): + ''' + @name 设置https模式 + @author hwliang<2022-01-14> + @return dict + ''' + web_server = public.get_webserver() + if web_server not in ['nginx', 'apache']: + return_message = public.return_msg_gettext(False, 'This function only supports Nginx/Apache') + del return_message['status'] + return public.return_message(-1, 0, return_message['msg']) + + ngx_default_conf_file = "{}/nginx/0.default.conf".format(public.get_vhost_path()) + apa_default_conf_file = "{}/apache/0.default.conf".format(public.get_vhost_path()) + ngx_default_conf = public.readFile(ngx_default_conf_file) + apa_default_conf = public.readFile(apa_default_conf_file) + status = False + if ngx_default_conf: + if ngx_default_conf.find('DEFAULT SSL CONFIG') != -1: + status = False + self.write_ngx_default_conf() + self.write_apa_default_conf() + else: + status = True + self.write_ngx_default_conf_by_ssl() + self.write_apa_default_conf_by_ssl() + else: + status = True + self.write_ngx_default_conf_by_ssl() + self.write_apa_default_conf_by_ssl() + + public.serviceReload() + status_msg = {True: 'Open', False: 'Close'} + msg = public.gettext_msg('Has {} HTTPS strict mode', (status_msg[status],)) + public.write_log_gettext('WebSite manager', msg) + return_message = public.return_msg_gettext(True, msg) + del return_message['status'] + return public.return_message(0, 0, return_message['msg']) + + def create_default_cert(self): + ''' + @name 创建默认SSL证书 + @author hwliang<2022-01-14> + @return bool + ''' + cert_pem = '/www/server/panel/vhost/cert/0.default/fullchain.pem' + cert_key = '/www/server/panel/vhost/cert/0.default/privkey.pem' + if os.path.exists(cert_pem) and os.path.exists(cert_key): return True + cert_path = os.path.dirname(cert_pem) + if not os.path.exists(cert_path): os.makedirs(cert_path) + import OpenSSL + key = OpenSSL.crypto.PKey() + key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) + cert = OpenSSL.crypto.X509() + cert.set_serial_number(0) + # cert.get_subject().CN = '' + cert.set_issuer(cert.get_subject()) + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(86400 * 3650) + cert.set_pubkey(key) + cert.sign(key, 'md5') + cert_ca = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) + private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) + if len(cert_ca) > 100 and len(private_key) > 100: + public.writeFile(cert_pem, cert_ca, 'wb+') + public.writeFile(cert_key, private_key, 'wb+') + return True + return False + + def get_upload_ssl_list(self, get): + """ + @获取上传证书列表 + @siteName string 网站名称 + """ + siteName = get['siteName'] + path = '{}/vhost/upload_ssl/{}'.format(public.get_panel_path(), siteName) + if not os.path.exists(path): os.makedirs(path) + + res = [] + for filename in os.listdir(path): + try: + filename = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(filename))) + res.append(filename) + except: + pass + return public.return_message(0, 0, res) + + # 获取指定证书基本信息 + def get_cert_init(self, cert_data, ssl_info=None): + """ + @获取指定证书基本信息 + @cert_data string 证书数据 + @ssl_info dict 证书信息 + """ + try: + result = {} + if ssl_info and ssl_info['ssl_type'] == 'pfx': + x509 = self.__check_pfx_pwd(cert_data, ssl_info['pwd'])[0] + else: + x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_data) + # 取产品名称 + 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 + # 取到期时间 + 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 len(result['dns']) > 0: + result['subject'] = result['dns'][0] + else: + result['subject'] = ''; + return result + except: + return False + + def strf_date(self, sdate): + """ + @转换证书时间 + """ + return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S')) + + def check_ssl_endtime(self, data, ssl_info=None): + """ + @检查证书是否有效(证书最高有效期不超过1年) + @data string 证书数据 + @ssl_info dict 证书信息 + """ + info = self.get_cert_init(data, ssl_info) + if info: + end_time = time.mktime(time.strptime(info['notAfter'], "%Y-%m-%d")) + start_time = time.mktime(time.strptime(info['notBefore'], "%Y-%m-%d")) + + days = int((end_time - start_time) / 86400) + if days < 400: # 1年有效期+1个月续签时间 + return data + return False + + # 证书转为pkcs12 + def dump_pkcs12(self, key_pem=None, cert_pem=None, ca_pem=None, friendly_name=None): + """ + @证书转为pkcs12 + @key_pem string 私钥数据 + @cert_pem string 证书数据 + @ca_pem string 可选的CA证书数据 + @friendly_name string 可选的证书名称 + """ + p12 = OpenSSL.crypto.PKCS12() + if cert_pem: + x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem.encode()) + p12.set_certificate(x509) + if key_pem: + p12.set_privatekey(OpenSSL.crypto.load_privatekey( + OpenSSL.crypto.FILETYPE_PEM, key_pem.encode())) + if ca_pem: + p12.set_ca_certificates((OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, ca_pem.encode()),)) + if friendly_name: + p12.set_friendlyname(friendly_name.encode()) + return p12 + + def download_cert(self, get): + """ + @下载证书 + @get dict 请求参数 + siteName string 网站名称 + ssl_type string 证书类型 + key string 密钥 + pem string 证书数据 + pwd string 证书密码 + """ + pem = get['pem'] + siteName = get['siteName'] + ssl_type = get['ssl_type'] + + rpath = '{}/temp/ssl/'.format(public.get_panel_path()) + if os.path.exists(rpath): shutil.rmtree(rpath) + + ca_list = [] + path = '{}/{}_{}'.format(rpath, siteName, int(time.time())) + if ssl_type == 'pfx': + res = self.__check_pfx_pwd(base64.b64decode(pem), get['pwd']) + p12 = res[1]; + x509 = res[0]; + get['pwd'] = res[2] + print(get['pwd']) + ca_list = [] + for x in p12.get_ca_certificates(): + ca_list.insert(0, OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, x).decode().strip()) + ca_cert = '\n'.join(ca_list) + key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey()).decode().strip() + domain_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, x509).decode().strip() + else: + key = get['key'] + domain_cert = pem.split('-----END CERTIFICATE-----')[0] + "-----END CERTIFICATE-----\n" + ca_cert = pem.replace(domain_cert, '') + + p12 = self.dump_pkcs12(key, '{}\n{}'.format(domain_cert.strip(), ca_cert), ca_cert) + + for x in ['IIS', 'Apache', 'Nginx', 'Other']: + d_file = '{}/{}'.format(path, x) + if not os.path.exists(d_file): os.makedirs(d_file) + + if x == 'IIS': + public.writeFile2(d_file + '/fullchain.pfx', p12.export(), 'wb+') + public.writeFile(d_file + '/password.txt', get['pwd']) + elif x == 'Apache': + public.writeFile(d_file + '/privkey.key', key) + public.writeFile(d_file + '/root_bundle.crt', ca_cert) + public.writeFile(d_file + '/domain.crt', domain_cert) + else: + public.writeFile(d_file + '/privkey.key', key) + public.writeFile(d_file + '/fullchain.pem', '{}\n{}'.format(domain_cert.strip(), ca_cert)) + + flist = [] + public.get_file_list(path, flist) + + zfile = '{}/{}.zip'.format(rpath, os.path.basename(path)) + import zipfile + f = zipfile.ZipFile(zfile, 'w', zipfile.ZIP_DEFLATED) + for item in flist: + s_path = item.replace(path, '') + if s_path: f.write(item, s_path) + f.close() + + return public.return_message(0, 0, zfile); + + def check_ssl_info(self, get): + """ + @解析证书信息 + @get dict 请求参数 + path string 上传文件路径 + """ + path = get['path'] + if not os.path.exists(path): + return public.return_message(-1, 0, '查询失败,不存在的地址') + + info = {'root': '', 'cert': '', 'pem': '', 'key': ''} + ssl_info = {'pwd': None, 'ssl_type': None} + for filename in os.listdir(path): + filepath = '{}/{}'.format(path, filename) + ext = filename[-4:] + if ext == '.pfx': + ssl_info['ssl_type'] = 'pfx' + + f = open(filepath, 'rb') # pfx为二进制文件 + info['pem'] = f.read() + + else: + data = public.readFile(filepath) + if filename.find('password') >= 0: # 取pfx密码 + ssl_info['pwd'] = re.search('([a-zA-Z0-9]+)', data).groups()[0] + continue + + if len(data) < 1024: + continue + + if data.find('PRIVATE KEY') >= 0: + info['key'] = data # 取key + + if ext == '.pem': + if self.check_ssl_endtime(data): + info['pem'] = data + else: + if data.find('BEGIN CERTIFICATE') >= 0: + if not info['root']: + info['root'] = data + else: + info['cert'] = data + + if ssl_info['ssl_type'] == 'pfx': + info['pem'] = self.check_ssl_endtime(info['pem'], ssl_info) + if info['pem']: + info['pem'] = base64.b64encode(info['pem']) + info['key'] = True + else: + if not info['pem']: + # 确认ca证书和域名证书顺序 + info['pem'] = self.check_ssl_endtime(info['root'] + "\n" + info['cert'], ssl_info) + if not info['pem']: + info['pem'] = self.check_ssl_endtime(info['cert'] + "\n" + info['root'], ssl_info) + + if info['key'] and info['pem']: + return_message = {'key': info['key'], 'pem': info['pem'], 'ssl_type': ssl_info['ssl_type'], + 'pwd': ssl_info['pwd']} + return public.return_message(0, 0, return_message) + return public.return_message(0, 0, False) + + def __check_pfx_pwd(self, data, pwd): + """ + @检测pfx证书密码 + @data string pfx证书内容 + @pwd string 密码 + """ + try: + p12 = OpenSSL.crypto.load_pkcs12(data, pwd) + x509 = p12.get_certificate() + except: + pwd = re.search('([a-zA-Z0-9]+)', pwd).groups()[0] + p12 = OpenSSL.crypto.load_pkcs12(data, pwd) + x509 = p12.get_certificate() + return [x509, p12, pwd] + + def auto_restart_rph(self, get): + # 设置申请或续签SSL时自动停止反向代理、重定向、http to https,申请完成后自动开启 + conf_file = '{}/data/stop_rp_when_renew_ssl.pl'.format(public.get_panel_path()) + conf = public.readFile(conf_file) + if not conf: + public.writeFile(conf_file, json.dumps([get.sitename])) + try: + conf = json.loads(conf) + if get.sitename not in conf: + conf.append(get.sitename) + public.writeFile(conf_file, json.dumps(conf)) + except: + return public.return_message(0, 0, 'Error parsing configuration file') + return public.return_message(0, 0, 'Setup successfully') + + def remove_auto_restart_rph(self, get): + # 设置申请或续签SSL时自动停止反向代理、重定向、http to https,申请完成后自动开启 + conf_file = '{}/data/stop_rp_when_renew_ssl.pl'.format(public.get_panel_path()) + conf = public.readFile(conf_file) + if not conf: + return public.return_message(-1, 0, + 'Website [proxy,redirect,http to https] are not set to restart automatically') + try: + conf = json.loads(conf) + conf.remove(get.sitename) + public.writeFile(conf_file, json.dumps(conf)) + except: + return public.return_message(-1, 0, 'Configuration file parsing error') + return public.return_message(0, 0, 'Setup successfully') + + def get_auto_restart_rph(self, get): + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # 设置申请或续签SSL时自动停止反向代理、重定向、http to https,申请完成后自动开启 + conf_file = '{}/data/stop_rp_when_renew_ssl.pl'.format(public.get_panel_path()) + conf = public.readFile(conf_file) + if not conf: + return public.return_message(-1, 0, + 'Website [proxy,redirect,http to https] are not set to restart automatically') + try: + conf = json.loads(conf) + if get.sitename in conf: + return public.return_message(0, 0, + 'This website has turn on [proxy,redirect,http to https] auto restart') + return public.return_message(-1, 0, 'Website has turn off auto restart') + except: + return public.return_message(-1, 0, 'Configuration file parsing error') + + # *********** WP Toolkit --Begin-- ************* + + # 重置Wordpress管理员账号密码 + def reset_wp_password(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().reset_wp_password(get) + + # 检查Wordpress版本更新 + def is_update(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().is_update(get) + + # 清除Wordpress Nginx fastcgi cache + def purge_all_cache(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().purge_all_cache(get) + + # 设置Wordpress Nginx fastcgi cache + def set_fastcgi_cache(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().set_fastcgi_cache(get) + + # 更新Wordpress到最新版本 + def update_wp(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().update_wp(get) + + # 获取可用的语言列表 + def get_language(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().get_language(get) + + # 获取可用的WP安装版本 + def get_wp_versions(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().get_wp_available_versions(get) + + # 获取WP Toolkit配置信息 + def get_wp_configurations(self, args): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().get_wp_configurations(args) + + # 保存WP Toolkit配置 + def save_wp_configurations(self, args): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().save_wp_configurations(args) + + # 获取wp 安全模块配置 + def get_wp_security_info(self, get): + from wp_toolkit import wp_security + result = wp_security().get_security_info(get) + get.name = get.site_name + # 取防盗链配置 + hotlink = self.GetSecurity(get) + try: + result['message']['hotlink_status'] = 1 if hotlink['message']['status'] else 0 + except: + pass + return result + + # 开启WP 文件防护 + def open_wp_file_protection(self, get): + from wp_toolkit import wp_security + return wp_security().open_file_protection(get) + + # 关闭WP 文件防护 + def close_wp_file_protection(self, get): + from wp_toolkit import wp_security + return wp_security().close_file_protection(get) + + # 获取WP 文件防护 + def get_wp_file_info(self, get): + from wp_toolkit import wp_security + return wp_security().get_file_info(get) + + # 开启WP 防火墙防护 + def open_wp_firewall_protection(self, get): + from wp_toolkit import wp_security + return wp_security().open_firewall_protection(get) + + # 关闭WP 防火墙防护 + def close_wp_firewall_protection(self, get): + from wp_toolkit import wp_security + return wp_security().close_firewall_protection(get) + + def deploy_wp(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().deploy_wp(get) + + def get_wp_username(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().get_wp_username(get) + + def reset_wp_db(self, get): + import one_key_wp_v2 as one_key_wp + return one_key_wp.one_key_wp().reset_wp_db(get) + + # 备份WP站点 + def wp_backup(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('bak_type').Require().Integer('>', 0), + ]) + + from wp_toolkit import wpbackup + + bak_obj = wpbackup(args.s_id) + bak_type = int(args.bak_type) + + if bak_type == 1: + ok, msg = bak_obj.backup_files() + elif bak_type == 2: + ok, msg = bak_obj.backup_database() + elif bak_type == 3: + ok, msg = bak_obj.backup_full() + else: + return public.fail_v2('Invalid backup type {}', (bak_type,)) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 还原WP站点 + def wp_restore(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('bak_id').Require().Integer('>', 0), + ]) + + from wp_toolkit import wpbackup + + bak_id = int(args.bak_id) + bak_obj = wpbackup(wpbackup.retrieve_site_id_with_bak_id(bak_id)) + ok, msg = bak_obj.restore_with_backup(bak_id) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # WP备份列表 + def wp_backup_list(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('p').Integer('>', 0), + public.Param('limit').Integer('>', 0), + public.Param('tojs').Regexp(r"^[\w\.\-]+$"), + public.Param('result').Regexp(r"^[\d\,]+$"), + ]) + + from wp_toolkit import wpbackup + + bak_obj = wpbackup(args.s_id) + + return public.success_v2(bak_obj.backup_list(args)) + + # 删除WP站点备份 + def wp_remove_backup(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('bak_id').Require().Integer('>', 0), + ]) + + from wp_toolkit import wpbackup + + bak_id = int(args.bak_id) + bak_obj = wpbackup(wpbackup.retrieve_site_id_with_bak_id(bak_id)) + ok, msg = bak_obj.remove_backup(bak_id) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 从 [网站管理] 迁移到 [WP Toolkit] + def wp_migrate_from_website_to_wptoolkit(self, args: public.dict_obj): + from wp_toolkit import wpmigration + ok, msg = wpmigration.migrate_aap_from_website_to_wptoolkit() + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 查询可从 [网站管理] 迁移到 [WP Toolkit] 的网站列表 + def wp_can_migrate_from_website_to_wptoolkit(self, args: public.dict_obj): + from wp_toolkit import wpmigration + return public.success_v2(wpmigration.can_migrations_of_aap_website()) + + # 从aapanel WP备份中创建WP站点 + def wp_create_with_aap_bak(self, args: public.dict_obj): + from wp_toolkit import wpbackup + ok, msg = wpbackup.wp_deploy_with_aap_bak(args) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 从plesk/cpanel WP备份中创建WP站点 + def wp_create_with_plesk_or_cpanel_bak(self, args: public.dict_obj): + from wp_toolkit import wpbackup + ok, msg = wpbackup.wp_deploy_with_plesk_or_cpanel_bak(args) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 克隆WP站点 + def wp_clone(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + ]) + + from wp_toolkit import wpbackup + + ok, msg = wpbackup(args.s_id).clone(args) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # Wordpress完整性校验 + def wp_integrity_check(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).integrity_check() + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 重新下载并安装Wordpress(仅限框架文件,不会删除新文件) + def wp_reinstall_files(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).reinstall_package() + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # TODO 获取可安装的插件列表 + def wp_plugin_list(self, args: public.dict_obj): + pass + + # 安装插件 + def wp_install_plugin(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('slug').Require().SafePath(), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).install_plugin(args.slug) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 已安装插件列表 + def wp_installed_plugins(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('force_check_updates').Integer('in', [0, 1]), + ]) + + from wp_toolkit import wpmgr + + return public.success_v2(wpmgr(args.s_id).installed_plugins(bool(int(args.get('force_check_updates', 0))))) + + # 更新插件 + def wp_update_plugin(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('plugin_file').Require().SafePath(), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).update_plugin(args.plugin_file) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Success') + + # 开启/关闭插件自动更新 + def wp_set_plugin_auto_update(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('plugin_file').Require().SafePath(), + public.Param('enable').Require().Integer('in', [0, 1]), + ]) + + from wp_toolkit import wpmgr + + wpmgr_obj = wpmgr(args.s_id) + + if int(args.enable) == 1: + fn = wpmgr_obj.enable_plugin_auto_update + else: + fn = wpmgr_obj.disable_plugin_auto_update + + ok, msg = fn(args.plugin_file) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Success') + + # 激活/禁用插件 + def wp_set_plugin_status(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('plugin_file').Require().SafePath(), + public.Param('activate').Require().Integer('in', [0, 1]), + ]) + + from wp_toolkit import wpmgr + + wpmgr_obj = wpmgr(args.s_id) + + if int(args.activate) == 1: + fn = wpmgr_obj.activate_plugins + errmsg = public.get_msg_gettext('Activate plugin failed, please try again later.') + else: + fn = wpmgr_obj.deactivate_plugins + errmsg = public.get_msg_gettext('Deactivate plugin failed, please try again later.') + + if not fn(args.plugin_file): + return public.fail_v2(errmsg) + + return public.success_v2('Success') + + # 卸载插件 + def wp_uninstall_plugin(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('plugin_file').Require().SafePath(), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).uninstall_plugin(args.plugin_file) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Success') + + # TODO 获取可安装的主题列表 + def wp_theme_list(self, args: public.dict_obj): + pass + + # 安装主题 + def wp_install_theme(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('slug').Require(), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).install_theme(args.slug) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2(msg) + + # 已安装主题列表 + def wp_installed_themes(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('force_check_updates').Integer('in', [0, 1]), + ]) + + from wp_toolkit import wpmgr + + return public.success_v2(wpmgr(args.s_id).installed_themes(bool(int(args.get('force_check_updates', 0))))) + + # 更新主题 + def wp_update_theme(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('stylesheet').Require(), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).update_theme(args.stylesheet) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Success') + + # 开启/关闭主题自动更新 + def wp_set_theme_auto_update(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('stylesheet').Require(), + public.Param('enable').Require().Integer('in', [0, 1]), + ]) + + from wp_toolkit import wpmgr + + wpmgr_obj = wpmgr(args.s_id) + + if int(args.enable) == 1: + fn = wpmgr_obj.enable_theme_auto_update + else: + fn = wpmgr_obj.disable_theme_auto_update + + ok, msg = fn(args.stylesheet) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Success') + + # 切换主题 + def wp_switch_theme(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('stylesheet').Require(), + ]) + + from wp_toolkit import wpmgr + + if not wpmgr(args.s_id).switch_theme(args.stylesheet): + return public.fail_v2('Switch theme failed, please try again later.') + + return public.success_v2('Success') + + # 卸载主题 + def wp_uninstall_theme(self, args: public.dict_obj): + # 参数校验 + args.validate([ + public.Param('s_id').Require().Integer('>', 0), + public.Param('stylesheet').Require(), + ]) + + from wp_toolkit import wpmgr + + ok, msg = wpmgr(args.s_id).uninstall_theme(args.stylesheet) + + if not ok: + return public.fail_v2(msg) + + return public.success_v2('Success') + + # *********** WP Toolkit --End-- ************* + + @staticmethod + def test_domains_api(get): + try: + domains = json.loads(get.domains.strip()) + except (json.JSONDecodeError, AttributeError, KeyError): + return public.return_message(-1, 0, "参数错误") + try: + from panel_dns_api_v2 import DnsMager + public.print_log("开始测试域名解析---- {}") + # public.print_log("开始测试域名解析---- {}".format(domains[0])) + + return DnsMager().test_domains_api(domains) + except: + pass + public.return_message(0, 0, "") + + def site_rname(self, get): + try: + if not (hasattr(get, "id") and hasattr(get, "rname")): + return public.return_message(-1, 0, "parameter error") + id = get.id + rname = get.rname + data = public.M('sites').where("id=?", (id,)).select() + if not data: + return public.return_message(-1, 0, "The site does not exist!") + data = data[0] + name = data['rname'] if 'rname' in data.keys() and data.get('rname', '') else data['name'] + if 'rname' not in data.keys(): + public.M('sites').execute("ALTER TABLE 'sites' ADD 'rname' text DEFAULT ''", ()) + public.M('sites').where('id=?', data['id']).update({'rname': rname}) + # public.write_log_gettext('Site manager', 'Website [{}] renamed: [{}]'.format(name, rname)) + return public.return_message(0, 0, 'Website [{}] renamed: [{}]'.format(name, rname)) + except: + return public.return_message(-1, 0, traceback.format_exc()) diff --git a/class_v2/panel_ssl_v2.py b/class_v2/panel_ssl_v2.py new file mode 100644 index 00000000..3e80a87c --- /dev/null +++ b/class_v2/panel_ssl_v2.py @@ -0,0 +1,1759 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------ +# SSL接口 +# ------------------------------ +from panel_auth_v2 import panelAuth as panelAuth +import public, os, sys, binascii, urllib, json, time, datetime, re +from ssl_manage import SSLManger # 新的ssl管理 + +from Crypto import Random +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher +import base64 +from public.validate import Param + +try: + from BTPanel import cache, session +except: + pass + + +class panelSSL: + # __APIURL = public.GetConfigValue('home') + '/api/Auth' + # __APIURL2 = public.GetConfigValue('home') + '/api/Cert' + # __BINDURL = 'https://api.bt.cn/Auth/GetAuthToken' # 获取token 获取官网token + + + + + __BINDURL = 'https://www.aapanel.com/api/user' # 获取token 获取官网token + # __BINDURL = 'http://dev.aapanel.com/api/user' # 获取token 获取官网token + + __CODEURL = 'https://api.bt.cn/Auth/GetBindCode' # 获取绑定验证码 + __UPATH = 'data/userInfo.json' + + # __APIURL = 'http://dev.aapanel.com/api' + __APIURL = 'https://www.aapanel.com/api' + + __PUBKEY = 'data/public.key' + + # 证书购买 + # __APIURL_CERT = 'https://www.aapanel.com/api/cert' + + __userInfo = None # 用户信息 从文件中读取的 + __PDATA = None + _check_url = None + + # 构造方法 + def __init__(self): + pdata = {} + data = {} # 存放调用接口的参数 + # 记录了用户信息 + 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 = {} + + # public.print_log('初始化 !!!!!!!!!!!!!!!!!!!用户信息: {}'.format(self.__userInfo)) + try: + if self.__userInfo: + # 记录里没有这两个key + pdata['access_key'] = self.__userInfo['access_key'] + data['secret_key'] = self.__userInfo['secret_key'] + # pdata['access_key'] = 'test' + # data['secret_key'] = '123456' + except: + # self.__userInfo = {} + pdata['access_key'] = 'test' + data['secret_key'] = '123456' + else: + pdata['access_key'] = 'test' + data['secret_key'] = '123456' + pdata['data'] = data + self.__PDATA = pdata + + # public.print_log('初始化------->最后 !!!!!!!!!!!!!!!!!!!用户信息: {}'.format(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['username'] = get.username + # data['password'] = public.md5(get.password) + # data['serverid'] = panelAuth().get_serverid() + # pdata = {} + # pdata['data'] = self.De_Code(data) + # try: + # rtmp = public.httpPost(self.__BINDURL, pdata) + # result = json.loads(rtmp) + # result['data'] = self.En_Code(result['data']) + # if result['data']: + # result['data']['serverid'] = data['serverid'] + # public.writeFile(self.__UPATH, json.dumps(result['data'])) + # public.flush_plugin_list() + # del (result['data']) + # session['focre_cloud'] = True + # return result + # except Exception as ex: + # # bind = 'data/bind.pl' + # # if os.path.exists(bind): os.remove(bind) + # # return public.returnMsg(False,'连接服务器失败!
                                    ' + str(ex)) + # raise public.error_conn_cloud(str(ex)) + # + # # 删除Token 最新 + # def DelToken(self, get): + # if os.path.exists(self.__UPATH): os.remove(self.__UPATH) + # session['focre_cloud'] = True + # return public.returnMsg(True, "SSL_BTUSER_UN") + + # 获取Token todo 在用 + def GetToken(self, get): + rtmp = "" + data = {} + data['identification'] = self.en_code_rsa(get.username) + # data['username'] = self.en_code_rsa(get.username) + data['password'] = self.en_code_rsa(get.password) + data['from_panel'] = self.en_code_rsa('1') # 1 代表从面板登录 + try: + rtmp = public.httpPost(self.__APIURL + '/user/login', data) + + # public.print_log("写入用户信息 @@@@222 {}".format(self.__APIURL + '/user/login')) + result = json.loads(rtmp) + # public.print_log("写入用户信息 @@@@ {}".format(rtmp)) + # public.print_log("写入用户信息 @@@@ {}".format(result)) + 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)) + # if bool: + # # public.print_log("写入用户信息 成功 {}".format(userinfo)) + # else: + # public.print_log("写入用户信息 失败") + + session['focre_cloud'] = True + return public.return_message(0,0, 'Bind successfully') + # return result + else: + return public.return_message(-1,0, + '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.return_message(-1,0, '%s
                                    %s' % ( + public.get_msg_gettext('Failed to connect server!'), str(rtmp))) + + # 删除Token todo + 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.return_msg_gettext(True, 'Unbound!') + + # 获取用户信息 todo + # def GetUserInfo(self, get): + # result = {} + # + # # public.print_log("@@@@@@@@@@获取用户信息 开始----- {}".format(self.__userInfo)) + # + # if self.__userInfo: + # userTmp = {} + # userTmp['username'] = self.__userInfo['username'][0:3] + '****' + self.__userInfo['username'][-4:] + # result['status'] = True + # result['msg'] = public.get_msg_gettext('SSL_GET_SUCCESS') + # result['data'] = userTmp + # else: + # userTmp = {} + # userTmp['username'] = public.get_msg_gettext('SSL_NOT_BTUSER') + # result['status'] = False + # result['msg'] = public.get_msg_gettext('SSL_NOT_BTUSER') + # result['data'] = userTmp + # return result + + def GetUserInfo(self, get): + # public.print_log("获取用户信息222") + return_status = -1 + result = {} + try: + if self.__userInfo: + userTmp = {} + userTmp['username'] = self.__userInfo['email'][0:3] + '****' + self.__userInfo['email'][-4:] + return_status= 0 + result['msg'] = public.get_msg_gettext('Got successfully!') + result['data'] = userTmp + else: + userTmp = {} + userTmp['username'] = public.get_msg_gettext('Please bind your account!') + result['msg'] = public.get_msg_gettext('Please bind your account!') + result['data'] = userTmp + except: + userTmp = {} + userTmp['username'] = public.get_msg_gettext('Please bind your account!') + result['msg'] = public.get_msg_gettext('Please bind your account!') + result['data'] = userTmp + return public.return_message(return_status,0,result) + + # 获取产品列表 todo + # def get_product_list(self, get): + # p_type = 'dv' + # if 'p_type' in get: p_type = get.p_type + # result = self.request('get_product_list?p_type={}'.format(p_type)) + # return result + + # # 获取产品列表2 todo + # def get_product_list_v2(self, get): + # p_type = 'dv' + # if 'p_type' in get: p_type = get.p_type + # + # result = self.request('get_product_list_v2?p_type={}'.format(p_type)) + # return result + + # 获取产品列表2 todo 产品列表 + def get_product_list_v2(self, get): + return_status = 0 + result = self.request('cert/product/list') + # if "success" in result: + # print(f"键 '{key_to_check}' 存在于字典中。") + # else: + # print(f"键 '{key_to_check}' 不存在于字典中。") + return public.return_message(0,0,result) + + # 获取商业证书订单列表 todo 用户订单列表 + def get_order_list(self, get): + result = self.request('cert/user/list') # 获取当前登录用户的SSL证书列表 + return public.return_message(0,0,result) + + + # 下载证书 todo + def download_cert(self, get): + self.__PDATA['uc_id'] = get.uc_id + result = self.request('cert/user/download') + return result + + # 获指定商业证书订单 + def get_order_find(self, get): + self.__PDATA['uc_id'] = get.uc_id + result = self.request('cert/user/info') + return result + + # 获取证书管理员信息 todo + def get_cert_admin(self, get): + result = self.request('cert/user/administrator') + return result + + # 完善资料CA(先支付接口) todo 可能是支付后的完善信息接口 + def apply_order_ca(self, args): + pdata = json.loads(args.pdata) + + result = self.check_ssl_caa(pdata['domains']) + if result: + return result + + self.__PDATA['data'] = pdata + result = self.request('cert/user/update_profile') + + return result + + + # 部署指定商业证书 todo 部署证书 + def set_cert(self, get): + siteName = get.siteName + certInfoall = self.get_order_find(get) + + if certInfoall["success"] is False: + return public.return_msg_gettext(False, certInfoall["res"]) + certInfo = certInfoall["res"] + 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['private_key']) + public.writeFile(csrpath, certInfo['certificate'] + "\n" + certInfo['ca_certificate']) + # 改记录 uc_id + public.writeFile(pidpath, get.uc_id) + import panel_site_v2 as panelSite + panelSite.panelSite().SetSSLConf(get) + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + + # # 生成商业证书支付订单 暂无 + # def apply_order_pay(self, args): + # self.__PDATA['data'] = json.loads(args.pdata) + # result = self.check_ssl_caa(self.__PDATA['data']['domains']) + # if result: return result + # result = self.request('apply_cert_order') + # return result + + # 检查CAA记录是否正确 + def check_ssl_caa(self, domains, clist=['sectigo.com', 'digicert.com', 'comodoca.com']): + ''' + @name 检查CAA记录是否正确 + @param domains 域名列表 + @param clist 正确的记录值关键词 + @return bool + ''' + try: + data = {} + for domain in domains: + root, zone = public.get_root_domain(domain) + for d in [domain, root, '_acme-challenge.{}'.format(root), '_acme-challenge.{}'.format(domain)]: + ret = public.query_dns(d, 'CAA') + if not ret: continue + slist = [] + for val in ret: + if val['value'] in clist: + return False + slist.append(val) + + if len(slist) > 0: + data[d] = slist + if data: + result = {} + result['status'] = False + result[ + 'msg'] = 'error: There is a CAA record in the DNS resolution of the domain name. Please delete it and apply again ' + result['data'] = json.dumps(data) + result['caa_list'] = data + return result + except: + pass + return False + + + # 提交商业证书订单到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 + + # 获取证书域名验证结果 todo 暂未使用 + # 用到: 处理验证信息 set_verify_info 完善资料 apply_order_ca 续签证书 renew_cert_order + def get_verify_info(self, args): + self.__PDATA['uc_id'] = args.uc_id + verify_info = self.request('cert/user/validate_domains') + if verify_info['success']: + return "success" + return "error" + + # 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: + # original_domain = domain + # # if domain[:4] == 'www.': domain = domain[:4] + # verify_info['hosts'].append(verify_info['host'] + '.' + domain) + # if 'auth_to' in args: + # root, zone = public.get_root_domain(domain) + # res = self.create_dns_record(args['auth_to'], verify_info['host'] + '.' + root, + # verify_info['value'], original_domain) + # print(res) + # return verify_info + + # 处理验证信息 todo 如果传参 要传uc_id + def set_verify_info(self, args): + # self.__PDATA['uc_id'] = args.uc_id # 新增 + 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: + original_domain = domain + # if domain[:4] == 'www.': domain = domain[4:] + verify_info['hosts'].append(verify_info['host'] + '.' + domain) + + if 'auth_to' in args: + root, zone = public.get_root_domain(domain) + self.create_dns_record(args['auth_to'], verify_info['host'] + '.' + root, + verify_info['value'], original_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, timeout=5): + url = args.url + content = args.content + + import http_requests + res = http_requests.get(url, s_type='curl', timeout=timeout) + 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 + + # 更换验证方式 # todo? ['data'] + def again_verify(self, args): + self.__PDATA['uc_id'] = args.uc_id + self.__PDATA['dcv_method'] = args.dcv_method + result = self.request('cert/user/update_dcv') + return result + + # 获取商业证书验证结果 + def get_verify_result(self, args): + self.__PDATA['uc_id'] = args.uc_id + res = self.request('cert/user/validate') + if res['success'] is False: + return res + verify_info = res['res'] + + 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.return_msg_gettext(False, 'In verification, please contact aaPanel if the audit still fails after 24 hours') + + 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: + # 判断是否是Springboot 项目 + if public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (dinfo['domainName'])).getField('pid'),)).getField( + 'project_type') == 'Java' or public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (dinfo['domainName'])).getField('pid'),)).getField( + 'project_type') == 'Go' or public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (dinfo['domainName'])).getField('pid'),)).getField( + 'project_type') == 'Other': + siteRunPath = '/www/wwwroot/java_node_ssl' + else: + 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:] + domain, subb = public.get_root_domain(domain) + dinfo['domainName'] = domain + 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 apply_cert_install_pay(self, args): + ''' + @name 单独购买人工安装服务 + @param args{ + 'uc_id' 订单ID + } + ''' + self.__PDATA['uc_id'] = args.uc_id + result = self.request('cert/order/deployment_assistance') + return result + + # 生成商业证书支付订单 todo 生成支付订单 下单支付 + def apply_cert_order_pay(self, args): + pdata = json.loads(args.pdata) + self.__PDATA['data'] = pdata + result = self.request('cert/order/create') + return public.return_message(0,0,result) + + # 模拟支付 + # def pay_test(self, args): + # out_trade_no = args.out_trade_no + # # /api/common/stripe/{out_trade_no} + # # result = self.request_test('order/pay') + # result = public.return_msg_gettext(False, '测试用 模拟支付!') + # url = "https://dev.aapanel.com/api/common/stripe/" + out_trade_no + # response_data = public.httpGet(url) + # + # # public.print_log("******************** url: {}".format(url)) + # + # try: + # result = json.loads(response_data) + # except: + # pass + # return result + + + + # 申请证书 ??? + def ApplyDVSSL(self, get): + + """ + 申请证书 + """ + if not 'orgName' in get: return public.returnMsg(False, 'missing parameter: orgName') + if not 'orgPhone' in get: return public.returnMsg(False, 'missing parameter: orgPhone') + if not 'orgPostalCode' in get: return public.returnMsg(False, 'missing parameter: orgPostalCode') + if not 'orgRegion' in get: return public.returnMsg(False, 'missing parameter: orgRegion') + if not 'orgCity' in get: return public.returnMsg(False, 'missing parameter: orgCity') + if not 'orgAddress' in get: return public.returnMsg(False, 'missing parameter: orgAddress') + if not 'orgDivision' in get: return public.returnMsg(False, 'missing parameter: orgDivision') + + 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, + "Request for [%s] certificate requires verification [%s] Please bind and resolve [%s] to the site!" % ( + get.domain, get.domain[4:], get.domain[4:])) + # 判断是否是Java项目 + if public.M('sites').where('id=?', (get.id,)).getField('project_type') == 'Java' or public.M('sites').where( + 'id=?', (get.id,)).getField('project_type') == 'Go' or public.M('sites').where('id=?', + (get.id,)).getField( + 'project_type') == 'Other': + get.path = '/www/wwwroot/java_node_ssl/' + runPath = '' + # 判断是否是Node项目 + elif public.M('sites').where('id=?', (get.id,)).getField('project_type') == 'Node': + get.path = public.M('sites').where('id=?', (get.id,)).getField('path') + runPath = '' + # 判断是否是python项目 + elif public.M('sites').where( + 'id=?', (get.id,)).getField('project_type') == 'Python': + get.path = public.M('sites').where('id=?', + (get.id,)).getField('path') + runPath = '' + else: + 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, 'Unable to write validation file: {}'.format(authfile)) + else: + msg = '''can't correct access validation file
                                    {c_url}

                                    +

                                    Possible cause:

                                    + 1、the resolution is not correct, or the resolution does not work [please resolve the domain correctly, or wait for the resolution to work and try again]
                                    + 2、 check whether the 301/302 redirection is set [please temporarily turn off the redirection related configuration]
                                    + 3、 Check whether the site has HTTPS deployed and set mandatory HTTPS [Please temporarily turn off mandatory HTTPS feature]
                                    '''.format( + c_url=self._check_url) + return public.returnMsg(False, msg) + + action = 'ApplyDVSSL' + if hasattr(get, 'partnerOrderId'): + self.__PDATA['data']['partnerOrderId'] = get.partnerOrderId + action = 'ReDVSSL' + + self.__PDATA['data']['domain'] = get.domain + self.__PDATA['data']['orgPhone'] = get.orgPhone + self.__PDATA['data']['orgPostalCode'] = get.orgPostalCode + self.__PDATA['data']['orgRegion'] = get.orgRegion + self.__PDATA['data']['orgCity'] = get.orgCity + self.__PDATA['data']['orgAddress'] = get.orgAddress + self.__PDATA['data']['orgDivision'] = get.orgDivision + self.__PDATA['data']['orgName'] = get.orgName + self.__PDATA['data'] = self.De_Code(self.__PDATA['data']) + try: + result = public.httpPost(self.__APIURL + 'user/' + action, self.__PDATA) + except Exception as ex: + raise public.error_conn_cloud(str(ex)) + try: + result = json.loads(result) + except: + return result + if 'status' in result: + if not result['status']: return result + result['data'] = self.En_Code(result['data']) + try: + if not 'authPath' in result['data']: result['data']['authPath'] = '/.well-known/pki-validation/' + authfile = get.path + result['data']['authPath'] + result['data']['authKey'] + except: + if 'authKey' in result['data']: + authfile = get.path + '/.well-known/pki-validation/' + result['data']['authKey'] + else: + return public.returnMsg(False, ' Failed to get the validation file!') + + if 'authValue' in result['data']: + public.writeFile(authfile, result['data']['authValue']) + return result + + + # 发送请求 todo + def request(self, dname): + self.__PDATA['data'] = json.dumps(self.__PDATA['data']) + url_headers = { + "authorization": "bt {}".format(self.__userInfo['token']) + } + + result = public.return_msg_gettext(False, 'The request failed, please try again later!') + try: + # response_data = public.httpPost(self.__APIURL + '/' + dname, self.__PDATA) + response_data = public.httpPost(self.__APIURL + '/' + dname, data=self.__PDATA, headers=url_headers) + except Exception as ex: + raise public.error_conn_cloud(str(ex)) + try: + result = json.loads(response_data) + except: + pass + return result + + # # 发送请求 todo 测试购买证书 + # def request_test(self, dname): + # self.__PDATA['data'] = json.dumps(self.__PDATA['data']) + # # "Content-Type": "application/json", + # url_headers = { + # "authorization": "bt {}".format(self.__userInfo['token']) + # } + # + # result = public.return_msg_gettext(False, '测试用 The request failed, please try again later!') + # try: + # response_data = public.httpPost(self.__APIURLtest + '/' + dname, data=self.__PDATA, headers=url_headers) + # + # # public.print_log("******************** url: {}".format(self.__APIURLtest + '/' + dname)) + # + # except Exception as ex: + # raise public.error_conn_cloud(str(ex)) + # + # + # try: + # result = json.loads(response_data) + # 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']) + try: + rs = public.httpPost(self.__APIURL + 'user/GetSSLList', self.__PDATA) + except Exception as ex: + raise public.error_conn_cloud(str(ex)) + try: + result = json.loads(rs) + except: + return public.return_msg_gettext(False, 'Failed to get, please try again later!') + + 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.return_msg_gettext(False, + "Apply for [{}] certificate to verify [{}] Please bind [{}] and resolve to the site!".format( + get.domain, get.domain[4:], get.domain[4:])) + + # 检测是否开启强制HTTPS + if not self.CheckForceHTTPS(get.siteName): + return public.return_msg_gettext(False, + '[Force HTTPS] is enabled on the current website, please turn off this function before applying for an SSL certificate!') + + # 获取真实网站运行目录 + 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.return_msg_gettext(False, 'Cannot create [{}]', (authfile,)) + else: + msg = ''''Unable to access the verification file
                                    {c_url}

                                    +

                                    Possible reasons:

                                    + 1. Incorrect or ineffective DNS resolution [Please ensure correct domain name resolution or wait for the resolution to take effect and try again]
                                    + 2. Check if there are any 301/302 redirects set [Temporarily disable redirect-related configurations]
                                    + 3. Check if the website has enforced HTTPS [Temporarily disable the enforced HTTPS feature]
                                    '''.format( + c_url=self._check_url) + return public.return_msg_gettext(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 + 'user/' + 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): + os.makedirs(spath, 0o755, True) + # 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') + if public.M('sites').where('id=?', + (public.M('domain').where('name=?', (get.siteName)).getField('pid'),)).getField( + 'project_type') == 'Java' or public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (get.siteName)).getField('pid'),)).getField( + 'project_type') == 'Go' or public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (get.siteName)).getField('pid'),)).getField( + 'project_type') == 'Other': + runPath = '/www/wwwroot/java_node_ssl' + else: + runPath = self.GetRunPath(get) + if runPath != False and runPath != '/': get.path += runPath + tmp = public.httpPost(self.__APIURL + 'user/SyncOrder', self.__PDATA) + try: + sslInfo = json.loads(tmp) + except: + return public.return_msg_gettext(False, tmp) + + sslInfo['data'] = self.En_Code(sslInfo['data']) + try: + + if public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (get.siteName)).getField('pid'),)).getField( + 'project_type') == 'Java' or public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (get.siteName)).getField('pid'),)).getField( + 'project_type') == 'Go' or public.M('sites').where('id=?', ( + public.M('domain').where('name=?', (get.siteName)).getField('pid'),)).getField( + 'project_type') == 'Other': + spath = '/www/wwwroot/java_node_ssl/.well-known/pki-validation' + else: + spath = get.path + '/.well-known/pki-validation' + if not os.path.exists(spath): public.ExecShell("mkdir -p '" + spath + "'") + public.writeFile(spath + '/' + sslInfo['data']['authKey'], sslInfo['data']['authValue']) + except: + return public.return_msg_gettext(False, 'Verification error!') + try: + result = json.loads(public.httpPost(self.__APIURL + 'user/Completed', self.__PDATA)) + if 'data' in result: + result['data'] = self.En_Code(result['data']) + except: + result = public.return_msg_gettext(True, 'Checking...') + n = 0; + my_ok = False + while True: + if n > 5: break + time.sleep(5) + rRet = json.loads(public.httpPost(self.__APIURL + 'user/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 + 'user/SyncOrder', self.__PDATA)) + result['data'] = self.En_Code(result['data']) + return result + + # 获取证书 + def GetSSLInfo(self, get): + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + 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 + 'user/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 panel_site_v2 as panelSite + panelSite.panelSite().SetSSLConf(get) + public.serviceReload() + return public.return_msg_gettext(True, 'Setup successfully!') + except: + return public.return_msg_gettext(False, 'Failed to set') + result['data'] = self.En_Code(result['data']) + return result + + def GetSiteDomain(self, get): + """ + @name 获取网站域名对应的站点名 + @param cert_list 证书域名列表 + @auther hezhihong + return 证书域名对应的站点名字典,如证书域名未绑定则为空 + """ + all_site = [] # 所有站点名列表 + cert_list = [] # 证书域名列表 + site_list = [] # 证书域名列表对应的站点名列表 + all_domain = [] # 所有域名列表 + try: + cert_list = json.loads(get.cert_list) + except: + pass + result = {} + # 取所有站点名和所有站点的绑定域名 + all_sites = public.M('sites').field('name').select() + for site in all_sites: + all_site.append(site['name']) + if not cert_list: continue + tmp_dict = {} + tmp_dict['name'] = site['name'] + pid = public.M('sites').where("name=?", (site['name'],)).getField('id') + domain_list = public.M('domain').where("pid=?", (pid,)).field('name').select() + for domain in domain_list: + all_domain.append(domain['name']) + # 取证书域名所在的所有域名列表 + site_domain = [] # 证书域名对应的站点名列表 + if cert_list and all_domain: + for cert in cert_list: + d_cert = '' + if re.match(r"^\*\..*", cert): + d_cert = cert.replace('*.', '') + for domain in all_domain: + if cert == domain: + site_domain.append(domain) + else: + replace_str = domain.split('.')[0] + '.' + if d_cert and d_cert == domain.replace(replace_str, ''): + site_domain.append(domain) + # 取证书域名对应的站点名 + for site in site_domain: + site_id = public.M('domain').where("name=?", (site,)).getField('pid') + site_name = public.M('sites').where("id=?", (site_id,)).getField('name') + site_list.append(site_name) + site_list = sorted(set(site_list), key=site_list.index) + result['all'] = all_site + result['site'] = site_list + return result + + def SetBatchCertToSite(self, get): + """ + @name 批量部署证书 + @auther hezhihong + """ + ssl_list = [] + if not hasattr(get, 'BatchInfo') or not get.BatchInfo: + return public.returnMsg(False, 'parameter error') + else: + ssl_list = json.loads(get.BatchInfo) + if isinstance(ssl_list, list): + total_num = len(ssl_list) + resultinfo = {"total": total_num, "success": 0, "faild": 0, "successList": [], "faildList": []} + successList = [] + faildList = [] + successnum = 0 + failnum = 0 + for Info in ssl_list: + set_result = {} + set_result['status'] = True + get.certName = set_result['certName'] = Info['certName'] + get.siteName = set_result['siteName'] = str(Info['siteName']) # 站点名称必定为字符串 + get.isBatch = True + if "ssl_hash" in Info: + get.ssl_hash = Info['ssl_hash'] + result = self.SetCertToSite(get) + if not result: + set_result['status'] = False + failnum += 1 + faildList.append(set_result) + else: + successnum += 1 + successList.append(set_result) + public.writeSpeed('setssl', successnum + failnum, total_num) + import firewalls + get.port = '443' + get.ps = 'HTTPS' + firewalls.firewalls().AddAcceptPort(get) + public.serviceReload() + resultinfo['success'] = successnum + resultinfo['faild'] = failnum + resultinfo['successList'] = successList + resultinfo['faildList'] = faildList + + if hasattr(get, "set_https_mode") and get.set_https_mode.strip() in (True, 1, "1", "true"): + import panelSite + sites_obj = panelSite.panelSite() + if not sites_obj.get_https_mode(): + sites_obj.set_https_mode() + + else: + return public.returnMsg(False, 'Parameter type error') + return resultinfo + + # 部署证书夹证书 + def SetCertToSite(self, get): + """ + @name 兼容批量部署 + @auther hezhihong + """ + + # 校验参数 + try: + get.validate([ + Param('siteName').String(), + Param('certName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + 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 panel_site_v2 as panelSite + panelSite.panelSite().SetSSLConf(get) + public.serviceReload() + return public.return_message(0,0, 'Setup successfully!') + except Exception as ex: + if 'isBatch' in get: return public.return_message(-1,0,"") + return public.return_message(-1,0, '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: + get.validate([ + Param('certName').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + vpath = '/www/server/panel/vhost/ssl/' + get.certName.replace("*.", '') + if not os.path.exists(vpath): return public.return_msg_gettext(False, 'Certificate does NOT exist!') + public.ExecShell("rm -rf " + vpath) + return public.return_msg_gettext(True, 'Certificate deleted!') + except: + return public.return_msg_gettext(False, 'Failed to delete!') + + # 保存证书 + def SaveCert(self, get): + try: + certInfo = self.GetCertName(get) + if not certInfo: + return_message=public.return_msg_gettext(False, 'Certificate parsing failed') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + SSLManger().save_by_file(get.certPath, get.keyPath) + 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_message=public.return_msg_gettext(True, 'Successfully saved certificate!') + del return_message['status'] + return public.return_message(0,0, return_message['msg']) + except: + return_message=public.return_msg_gettext(False, 'Failed to save certificate!') + del return_message['status'] + return public.return_message(-1,0, return_message['msg']) + + # 读取证书 + def GetCert(self, get): + vpath = os.path.join('/www/server/panel/vhost/ssl', get.certName.replace("*.", '')) + if not os.path.exists(vpath): return public.return_msg_gettext(False, 'Certificate does 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_unixtime(self, data, format="%Y-%m-%d %H:%M:%S"): + import time + timeArray = time.strptime(data, format) + timeStamp = int(time.mktime(timeArray)) + return timeStamp + + # 获取指定证书基本信息 + 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] + result['endtime'] = int( + int(time.mktime(time.strptime(result['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) + 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 + 'user/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.return_msg_gettext(False, 'There are currently no certificates to renew!') + + 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.return_msg_gettext(False, + 'There is no certificate that can be renewed on the current website..') + 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 + + # todo? + def renew_cert_order(self, args): + ''' + @name 续签商用证书 + @author cjx + @version 1.0 + ''' + if not 'pdata' in args: + return public.returnMsg(False, 'The pdata parameter cannot be empty!') + pdata = json.loads(args.pdata) + self.__PDATA['data'] = pdata + + result = self.request('renew_cert_order') + if result['status'] == True: + self.__PDATA['data'] = {} + args['oid'] = result['oid'] + result['verify_info'] = self.get_verify_info(args) + return result + + def GetAuthToken(self, get): + """ + 登录官网获取Token + @get.username 官网手机号 + @get.password 官网账号密码 + """ + rtmp = "" + data = {} + data['username'] = public.rsa_decrypt(get.username) + data['password'] = public.md5(public.rsa_decrypt(get.password)) + data['serverid'] = panelAuth().get_serverid() + + if 'code' in get: data['code'] = get.code + if 'token' in get: data['token'] = get.token + + pdata = {} + pdata['data'] = self.De_Code(data) + try: + rtmp = public.httpPost(self.__BINDURL, pdata) + result = json.loads(rtmp) + result['data'] = self.En_Code(result['data']) + if not result['status']: return result + + if result['data']: + if result['data']['serverid'] != data['serverid']: # 保存新的serverid + public.writeFile('data/sid.pl', result['data']['serverid']) + public.writeFile(self.__UPATH, json.dumps(result['data'])) + if os.path.exists('data/bind_path.pl'): os.remove('data/bind_path.pl') + public.flush_plugin_list() + del (result['data']) + session['focre_cloud'] = True + return result + except Exception as ex: + error = str(ex) + if error.lower().find('json') >= 0: + error = '
                                    错误:连接宝塔官网异常,请按照以下方法排除问题后重试:
                                    解决方法:https://www.bt.cn/bbs/thread-87257-1-1.html
                                    ' + # raise public.PanelError(error) + return public.returnMsg(False, 6) + else: + return public.returnMsg(False, 6) + # raise public.error_conn_cloud(error) + # return public.returnMsg(False,'连接服务器失败!
                                    {}'.format(rtmp)) + + def GetBindCode(self, get): + """ + 获取验证码 + """ + rtmp = "" + data = {} + data['username'] = get.username + data['token'] = get.token + pdata = {} + pdata['data'] = self.De_Code(data) + try: + rtmp = public.httpPost(self.__CODEURL, pdata) + result = json.loads(rtmp) + return result + except Exception as ex: + raise public.error_conn_cloud(str(ex)) + # return public.returnMsg(False,'连接服务器失败!
                                    ' + rtmp) + + # 解析DNSAPI信息 + def get_dnsapi(self, auth_to): + tmp = auth_to.split('|') + dns_name = tmp[0] + key = "None" + secret = "None" + if len(tmp) < 3: + dnsapi_config = json.loads(public.readFile('{}/config/dns_api.json'.format(public.get_panel_path()))) + for dc in dnsapi_config: + if dc['name'] != dns_name: + continue + if not dc['data']: + continue + key = dc['data'][0]['value'] + secret = dc['data'][1]['value'] + else: + key = tmp[1] + secret = tmp[2] + return dns_name, key, secret + + # 获取dnsapi对象 + def get_dns_class(self, auth_to): + try: + import panelDnsapi + dns_name, key, secret = self.get_dnsapi(auth_to) + dns_class = getattr(panelDnsapi, dns_name)(key, secret) + dns_class._type = 1 + return dns_class + except: + return None + + # 解析域名 + def create_dns_record(self, auth_to, domain, dns_value, original_domain=None): + # 如果为手动解析 + if auth_to == 'dns': + return None + from panelDnsapi import DnsMager + dns_class = DnsMager().get_dns_obj_by_domain(original_domain) + dns_class._type = 1 + if not dns_class: + return public.returnMsg(False, + "The operation failed. Please check that the key is correct") + + # 申请前删除caa记录 + root, zone = public.get_root_domain(domain) + try: + dns_class.remove_record(public.de_punycode(root), '@', 'CAA') + except: + pass + try: + dns_class.create_dns_record(public.de_punycode(domain), dns_value) + return public.returnMsg(True, 'Added successfully') + except: + return public.returnMsg(False, public.get_error_info()) + + # 检测ssl验证方式 + def check_ssl_method(self, get): + """ + @name 检测ssl验证方式 + @domain string 域名 + """ + + domain = get.domain + if public.M('sites').where('id=?', (public.M('domain').where('name=?', (domain)).getField('pid'),)).getField( + 'project_type') == 'Java': + siteRunPath = '{}/java_node_ssl'.format(public.M("config").getField("sites_path")) + else: + siteRunPath = self.get_domain_run_path(domain) + + if not siteRunPath: + return public.returnMsg(False, + 'Failed to get the website path. Please check if the website exists') + + verify_path = siteRunPath + '/.well-known/pki-validation' + if not os.path.exists(verify_path): os.makedirs(verify_path) + + # 生成临时文件 + check_val = public.GetRandomString(16) + verify_file = '{}/{}.txt'.format(verify_path, check_val) + public.writeFile(verify_file, check_val) + if not os.path.exists(verify_file): + return public.returnMsg(False, + 'Failed to create the validation file. Check if the write was blocked') + + res = {} + msg = [' domain name [{}] validation file cannot be accessed correctly'.format(domain), + 'Probable cause', + '1、the resolution was not correct, or the resolution did not work [Please resolve the domain correctly, or wait for the resolution to work and try again]', + '2、check whether 301/302 redirects are set [please temporarily turn off redirects related configuration]', + '3、check whether the site has enabled reverse proxy [please temporarily turn off reverse proxy configuration]' + ] + + res['HTTP_CSR_HASH'] = msg + res['HTTPS_CSR_HASH'] = msg + + # 检测HTTP/https访问 + args = public.dict_obj() + for stype in ['http', 'https']: + args.url = '{}://{}/.well-known/pki-validation/{}.txt'.format(stype, domain, check_val) + args.content = check_val + if self.check_url_txt(args, 2) == 1: + res['{}_CSR_HASH'.format(stype).upper()] = 1 + + # 检测caa记录 + result = self.check_ssl_caa([domain]) + if not result: + res['CNAME_CSR_HASH'] = 1 + else: + res['CNAME_CSR_HASH'] = json.loads(result['data']) + + if os.path.exists(verify_file): + os.remove(verify_file) + return res + + @staticmethod + def upload_cert_to_cloud(get): + ssl_id = None + ssl_hash = None + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "parameter error") + from ssl_manage import SSLManger + try: + return SSLManger().upload_cert(ssl_id, ssl_hash) + except ValueError as e: + return public.returnMsg(False, str(e)) + except Exception as e: + return public.returnMsg(False, "operation mistake:" + str(e)) + + @staticmethod + def remove_cloud_cert(get): + ssl_id = None + ssl_hash = None + local = False + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + + if "local" in get and get.local.strip() in ("1", 1, True, "true"): + local = True + + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "parameter error") + from ssl_manage import SSLManger + try: + return SSLManger().remove_cert(ssl_id, ssl_hash, local=local) + except ValueError as e: + return public.returnMsg(False, str(e)) + except Exception as e: + return public.returnMsg(False, "operation mistake:" + str(e)) + + # 未使用 + @staticmethod + def refresh_cert_list(get=None): + from ssl_manage import SSLManger + try: + return SSLManger().get_cert_list(force_refresh=True) + except ValueError as e: + return public.returnMsg(False, str(e)) + except Exception as e: + return public.returnMsg(False, "operation mistake:" + str(e)) + + @staticmethod + def get_cert_info(get): + ssl_id = None + ssl_hash = None + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "parameter error") + from ssl_manage import SSLManger + try: + ssl_mager = SSLManger() + target = ssl_mager.find_ssl_info(ssl_id, ssl_hash) + if target is None: + return public.returnMsg(False, "No certificate information was obtained") + target.update(ssl_mager.get_cert_for_deploy(target["hash"])) + return target + except ValueError as e: + return public.returnMsg(False, str(e)) + except Exception as e: + return public.returnMsg(False, "operation mistake:" + str(e)) + + @staticmethod + def get_cert_list(get): + """ + search_limit 0 -> 所有证书 + search_limit 1 -> 没有过期的证书 + search_limit 2 -> 有效期小于等于15天的证书 但未过期 + search_limit 3 -> 过期的证书 + search_limit 4 -> 过期时间1年以上的证书 + """ + search_name = None + search_limit = 0 + force_refresh = False + + try: + if "search_name" in get: + search_name = get.search_name.strip() + if "search_limit" in get: + search_limit = int(get.search_limit.strip()) + if "force_refresh" in get and get.force_refresh.strip() in ("1", 1, "True", True): + force_refresh = True + + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "parameter error") + + param = None + if search_name is not None: + param = ['subject LIKE ?', ["%{}%".format(search_name)]] + + now = datetime.datetime.now() + filter_func = lambda x: True + if search_limit == 1: + date = now.strftime("%Y-%m-%d") + filter_func = lambda x: x["not_after"] >= date + elif search_limit == 2: + date1 = now.strftime("%Y-%m-%d") + date2 = (now + datetime.timedelta(days=15)).strftime("%Y-%m-%d") + filter_func = lambda x: date1 <= x["not_after"] <= date2 + elif search_limit == 3: + date = now.strftime("%Y-%m-%d") + filter_func = lambda x: x["not_after"] < date + elif search_limit == 4: + date = (now + datetime.timedelta(days=366)).strftime("%Y-%m-%d") + filter_func = lambda x: x["not_after"] > date + + from ssl_manage import SSLManger + try: + res_list = SSLManger().get_cert_list(param=param, force_refresh=force_refresh) + return list(filter(filter_func, res_list)) + except ValueError as e: + return public.returnMsg(False, str(e)) + except Exception as e: + return public.returnMsg(False, "operation mistake:" + str(e)) diff --git a/class_v2/panel_task_v2.py b/class_v2/panel_task_v2.py new file mode 100644 index 00000000..236f9e0b --- /dev/null +++ b/class_v2/panel_task_v2.py @@ -0,0 +1,686 @@ +#coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2019-2099 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------ +# 消息队列 +# ------------------------------ +import json +import time +import public +import sys +import os +import re +from public.validate import Param +os.chdir('/www/server/panel') +if not 'class/' in sys.path: + sys.path.insert(0,'class/') + +class bt_task: + __table = 'task_list' + __task_tips = '/dev/shm/bt_task_now.pl' + __task_path = '/www/server/panel/tmp/' + down_log_total_file = '/tmp/download_total.pl' + not_web = False + def __init__(self): + + # 创建数据表 + sql = '''CREATE TABLE IF NOT EXISTS `task_list` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `name` TEXT, + `type` TEXT, + `status` INTEGER, + `shell` TEXT, + `other` TEXT, + `exectime` INTEGER, + `endtime` INTEGER, + `addtime` INTEGER +);''' + public.M(None).execute(sql, ()) + + # 创建临时目录 + if not os.path.exists(self.__task_path): + os.makedirs(self.__task_path, 384) + + # 取任务列表 + def get_task_list(self, status=-3): + sql = public.M(self.__table) + if status != -3: + sql = sql.where('status=?', (status,)) + data = sql.field( + 'id,name,type,shell,other,status,exectime,endtime,addtime').select() + return data + + # 取任务列表前端 + def get_task_lists(self, get): + # 校验参数 + try: + get.validate([ + Param('status').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + sql = public.M(self.__table) + if 'status' in get: + if get.status == '-3': + sql = sql.where('status=? OR status=?', (-1, 0)) + else: + sql = sql.where('status=?', (get.status,)) + data = sql.field('id,name,type,shell,other,status,exectime,endtime,addtime').order( + 'id asc').limit('10').select() + if type(data) == str: + public.WriteLog('TASK_QUEUE', data,not_web = self.not_web) + return public.return_message(0,0,[]) + if not 'num' in get: + get.num = 15 + num = int(get.num) + for i in range(len(data)): + data[i]['log'] = '' + if data[i]['status'] == -1: + data[i]['log'] = self.get_task_log( + data[i]['id'], data[i]['type'], num) + elif data[i]['status'] == 1: + data[i]['log'] = self.get_task_log( + data[i]['id'], data[i]['type'], 10) + if data[i]['type'] == '3': + data[i]['other'] = json.loads(data[i]['other']) + return public.return_message(0,0,data) + + # 创建任务 + def create_task(self, task_name, task_type, task_shell, other=''): + self.clean_log() + task_id = public.M(self.__table).add('name,type,shell,other,addtime,status', + (task_name, task_type, task_shell, other, int(time.time()), 0)) + public.WriteFile(self.__task_tips, 'True') + public.ExecShell("/etc/init.d/bt start") + if not public.M(self.__table).where('status=?', ('-1',)).count(): + tip_file = "/dev/shm/.start_task.pl" + tip_time = public.readFile(tip_file) + if not tip_time or time.time() - int(tip_time) > 600: + public.ExecShell("/www/server/panel/BT-Task") + public.print_log("Background task restarted") + return task_id + + # 修改任务 + def modify_task(self, id, key, value): + public.M(self.__table).where('id=?', (id,)).setField(key, value) + return True + + # 删除任务 + def remove_task(self, get): + task_info = self.get_task_find(get.id) + public.M(self.__table).where('id=?', (get.id,)).delete() + if str(task_info['status']) == '-1': + public.ExecShell( + "kill -9 $(ps aux|grep 'task.py'|grep -v grep|awk '{print $2}')") + if task_info['type'] == '1': + public.ExecShell( + "kill -9 $(ps aux|grep '{}')".format(task_info['other'])) + time.sleep(1) + if os.path.exists(task_info['other']): + os.remove(task_info['other']) + elif task_info['type'] == '3': + z_info = json.loads(task_info['other']) + if z_info['z_type'] == 'tar.gz': + public.ExecShell( + "kill -9 $(ps aux|grep 'tar -zcvf'|grep -v grep|awk '{print $2}')") + elif z_info['z_type'] == 'rar': + public.ExecShell( + "kill -9 $(ps aux|grep /www/server/rar/rar|grep -v grep|awk '{print $2}')") + elif z_info['z_type'] == 'zip': + public.ExecShell( + "kill -9 $(ps aux|grep '.zip -r'|grep -v grep|awk '{print $2}')") + public.ExecShell( + "kill -9 $(ps aux|grep '.zip\' -r'|grep -v grep|awk '{print $2}')") + if os.path.exists(z_info['dfile']): + os.remove(z_info['dfile']) + elif task_info['type'] == '2': + public.ExecShell( + "kill -9 $(ps aux|grep 'tar -zxvf'|grep -v grep|awk '{print $2}')") + public.ExecShell( + "kill -9 $(ps aux|grep '/www/server/rar/unrar'|grep -v grep|awk '{print $2}')") + public.ExecShell( + "kill -9 $(ps aux|grep 'unzip -P'|grep -v grep|awk '{print $2}')") + public.ExecShell( + "kill -9 $(ps aux|grep 'gunzip -c'|grep -v grep|awk '{print $2}')") + elif task_info['type'] == '0': + public.ExecShell( + "kill -9 $(ps aux|grep '"+task_info['shell']+"'|grep -v grep|awk '{print $2}')") + + public.ExecShell("/etc/init.d/bt start") + return public.return_msg_gettext(True, 'Task cancelled!') + + # 取一条任务 + def get_task_find(self, id): + data = public.M(self.__table).where('id=?', (id,)).field( + 'id,name,type,shell,other,status,exectime,endtime,addtime').find() + return data + + # 执行任务 + # task_type 0.执行shell 1.下载文件 2.解压文件 3.压缩文件 + def execute_task(self, id, task_type, task_shell, other=''): + if not os.path.exists(self.__task_path): + os.makedirs(self.__task_path, 384) + log_file = self.__task_path + str(id) + '.log' + + # 标记状态执行时间 + self.modify_task(id, 'status', -1) + self.modify_task(id, 'exectime', int(time.time())) + task_type = int(task_type) + # 开始执行 + if task_type == 0: # 执行命令 + public.ExecShell(task_shell + ' &> ' + log_file) + elif task_type == 1: # 下载文件 + if os.path.exists(self.down_log_total_file): + os.remove(self.down_log_total_file) + public.ExecShell( + "wget -O '{}' '{}' --no-check-certificate -T 30 -t 5 -d &> {}".format(other, task_shell, log_file)) + if os.path.exists(log_file): + os.remove(log_file) + elif task_type == 2: # 解压文件 + zip_info = json.loads(other) + self._unzip(task_shell, zip_info['dfile'], + zip_info['password'], log_file) + elif task_type == 3: # 压缩文件 + zip_info = json.loads(other) + if not 'z_type' in zip_info: + zip_info['z_type'] = 'tar.gz' + print(self._zip( + task_shell, zip_info['sfile'], zip_info['dfile'], log_file, zip_info['z_type'])) + elif task_type == 4: # 备份数据库 + self.backup_database(task_shell, log_file) + elif task_type == 5: # 导入数据库 + self.input_database(task_shell, other, log_file) + elif task_type == 6: # 备份网站 + self.backup_site(task_shell, log_file) + elif task_type == 7: # 恢复网站 + pass + # 标记状态与结束时间 + self.modify_task(id, 'status', 1) + self.modify_task(id, 'endtime', int(time.time())) + + # 开始检测任务 + def start_task(self): + noe = False + n = 0 + tip_file = '/dev/shm/.start_task.pl' + while True: + try: + time.sleep(1) + public.writeFile(tip_file, str(int(time.time()))) + n += 1 + if not os.path.exists(self.__task_tips) and noe and n < 60: + continue + if os.path.exists(self.__task_tips): + os.remove(self.__task_tips) + n = 0 + public.M(self.__table).where( + 'status=?', ('-1',)).setField('status', 0) + task_list = self.get_task_list(0) + for task_info in task_list: + self.execute_task( + task_info['id'], task_info['type'], task_info['shell'], task_info['other']) + noe = True + except: + print(public.get_error_info()) + + # 前端通过任务ID取某一个任务的日志 + def get_task_log_by_id(self, get): + task_id = get.id + task_type = get.task_type + log_data = {} + if "num" in get: + num = int(get.num) + log_data = self.get_task_log(task_id, task_type, num) + else: + log_data = self.get_task_log(task_id, task_type) + task_obj = self.get_task_find(task_id) + log_data["status"] = task_obj["status"] + return log_data + + # 取任务执行日志 + def get_task_log(self, id, task_type, num=5): + log_file = self.__task_path + str(id) + '.log' + if not os.path.exists(log_file): + data = '' + if(task_type == '1'): + data = {'name': public.get_msg_gettext('Download file'), 'total': 0, 'used': 0, + 'pre': 0, 'speed': 0, 'time': 0} + return data + + if(task_type == '1'): + total = 0 + if not os.path.exists(self.down_log_total_file): + f = open(log_file, 'r') + head = f.read(4096) + content_length = re.findall(r"Length:\s+(\d+)", head) + if content_length: + total = int(content_length[0]) + public.writeFile(self.down_log_total_file, + content_length[0]) + else: + total = public.readFile(self.down_log_total_file) + if not total: + total = 0 + total = int(total) + + filename = public.M(self.__table).where( + 'id=?', (id,)).getField('shell') + + speed_tmp = public.ExecShell("tail -n 2 {}".format(log_file))[0] + speed_total = re.findall( + r"([\d\.]+[BbKkMmGg]).+\s+(\d+)%\s+([\d\.]+[KMBGkmbg])\s+(\w+[sS])", speed_tmp) + if not speed_total: + data = {'name':public.get_msg_gettext('Download file {}',(filename,)),'total':0,'used':0,'pre':0,'speed':0,'time':0} + else: + speed_total = speed_total[0] + used = speed_total[0] + if speed_total[0].lower().find('k') != -1: + used = public.to_size( + float(speed_total[0].lower().replace('k', '')) * 1024) + u_time = speed_total[3].replace( + 'h', 'Hour').replace('m', 'Minute').replace('s', 'Second') + data = {'name': public.get_msg_gettext('Download file {}',(filename,)),'total': total, 'used': used, 'pre': speed_total[1], 'speed': speed_total[2], 'time': u_time} + else: + data = public.ExecShell("tail -n {} {}".format(num, log_file))[0] + if type(data) == list: + return '' + if isinstance(data,bytes): + data = data.decode('utf-8') + data = data.replace('\x08', '').replace('\n', '
                                    ') + return data + + # 清理任务日志 + def clean_log(self): + import shutil + s_time = int(time.time()) + timeout = 86400 + for f in os.listdir(self.__task_path): + filename = self.__task_path + f + c_time = os.stat(filename).st_ctime + if s_time - c_time > timeout: + if os.path.isdir(filename): + shutil.rmtree(filename) + else: + os.remove(filename) + return True + + # 文件压缩 + def _zip(self, path, sfile, dfile, log_file, z_type='tar.gz'): + if sys.version_info[0] == 2: + sfile = sfile.encode('utf-8') + dfile = dfile.encode('utf-8') + if sys.version_info[0] == 2: + path = path.encode('utf-8') + if sfile.find(',') == -1: + if not os.path.exists(path+'/'+sfile): + return public.return_msg_gettext(False, 'Configuration file not exist') + # 处理多文件压缩 + sfiles = '' + for sfile in sfile.split(','): + if not sfile: + continue + sfiles += " '" + sfile + "'" + + # 判断压缩格式 + if z_type == 'zip': + public.ExecShell("cd '"+path+"' && zip '"+dfile + + "' -r "+sfiles+" &> "+log_file) + elif z_type == 'tar.gz': + public.ExecShell("cd '" + path + "' && tar -zcvf '" + + dfile + "' " + sfiles + " &> " + log_file) + elif z_type == 'rar': + rar_file = '/www/server/rar/rar' + if not os.path.exists(rar_file): + self.install_rar() + public.ExecShell("cd '" + path + "' && "+rar_file + + " a -r '" + dfile + "' " + sfiles + " &> " + log_file) + elif z_type == '7z': + _7z_bin = self.get_7z_bin() + if not _7z_bin: + self.install_7zip() + err_msg = 'The p7zip component is not installed, an automatic installation has been attempted, please wait a few minutes and try again!' + public.WriteLog("File manager","Failed to compress file, reason: {}, file: {}".format(err_msg,sfile)) + return public.returnMsg(False, err_msg) + public.ExecShell("cd {} && {} a -t7z {} {} -y &> {}".format(path, _7z_bin, dfile, sfiles, log_file)) + else: + return public.return_msg_gettext(False,'Specified compression format is not supported!') + + self.set_file_accept(dfile) + #public.WriteLog("TYPE_FILE", 'Compression succeeded!', (sfiles, dfile),not_web = self.not_web) + public.write_log_gettext("File manager", 'Compressed file [ {} ] to [ {} ] success', (sfiles, dfile)) + return public.return_msg_gettext(True, 'Compression succeeded!') + + # 文件解压 + def _unzip(self, sfile, dfile, password, log_file): + if sys.version_info[0] == 2: + sfile = sfile.encode('utf-8') + dfile = dfile.encode('utf-8') + if not os.path.exists(sfile): + return public.return_msg_gettext(False, 'Configuration file not exist') + + # 判断压缩包格式 + if sfile[-4:] == '.zip': + public.ExecShell("unzip -P '"+password+"' -o '" + + sfile + "' -d '" + dfile + "' &> " + log_file) + elif sfile[-7:] == '.tar.gz' or sfile[-4:] == '.tgz': + public.ExecShell("tar zxvf '" + sfile + + "' -C '" + dfile + "' &> " + log_file) + elif sfile[-4:] == '.rar': + rar_file = '/www/server/rar/unrar' + if not os.path.exists(rar_file): + self.install_rar() + pass_opt = '-p-' + if password: + password = password.replace("&",r"\&").replace('"','\"') + pass_opt = '-p"{}"'.format(password) + + public.ExecShell(rar_file + ' x '+ pass_opt +' -u -y "' + sfile + '" "' + dfile + '" &> ' + log_file) + + elif sfile[-4:] == '.war': + public.ExecShell("unzip -P '"+password+"' -o '" + + sfile + "' -d '" + dfile + "' &> " + log_file) + elif sfile[-4:] == '.bz2': + public.ExecShell("tar jxvf '" + sfile + + "' -C '" + dfile + "' &> " + log_file) + elif sfile[-3:] == '.7z': + _7zbin = self.get_7z_bin() + if not _7zbin: + self.install_7zip() + err_msg = 'The p7zip component is not installed, an automatic installation has been attempted, please wait a few minutes and try again!' + public.WriteLog("File manager","Failed to compress file, reason: {}, file: {}".format(err_msg,sfile)) + return public.returnMsg(False, err_msg) + pass_opt = "" + if password: + pass_opt = '-p"{}"'.format(password) + public.ExecShell('{} x "{}" -o"{}" -y {} &> {}'.format(_7zbin,sfile,dfile,pass_opt,log_file)) + else: + public.ExecShell("gunzip -c " + sfile + " > " + sfile[:-3]) + + # 异常处理 + log_msg = public.readFile(log_file) + err_msg = None + if log_msg: + if log_msg.find("incorrect password") != -1 \ + or log_msg.find("The specified password is incorrect.") != -1 \ + or log_msg.find("Data Error in encrypted file. Wrong password") != -1: + err_msg = 'Decompression password error!' + public.WriteLog("File manager","Unzip file failed, reason: {}, file: {}".format(err_msg,sfile)) + elif log_msg.find("unsupported compression method 99") != -1: + err_msg = 'Unsupported Zip encryption and compression, only ZIP traditional encryption is supported for ZIP archives!' + public.WriteLog("File manager","Unzip file failed, reason: {}, file: {}".format(err_msg,sfile)) + elif log_msg.find("is not RAR archive") != -1: + err_msg = "It is not a rar archive, check whether to modify the file with the extension rar for other compression formats!" + public.WriteLog("File manager","Unzip file failed, reason: {}, file: {}".format(err_msg,sfile)) + elif log_msg.find("gzip: stdin") != -1: + public.ExecShell("tar xvf '" + sfile + "' -C '" + dfile + "' &> " + log_file) + + if err_msg: return public.returnMsg(False, err_msg) + + # 检查是否设置权限 + if self.check_dir(dfile): + sites_path = public.M('config').where( + 'id=?', (1,)).getField('sites_path') + if dfile.find('/www/wwwroot') != -1 or dfile.find(sites_path) != -1: + self.set_file_accept(dfile) + else: + import pwd + user = pwd.getpwuid(os.stat(dfile).st_uid).pw_name + public.ExecShell("chown %s:%s %s" % (user, user, dfile)) + + #public.WriteLog("TYPE_FILE", 'Uncompression succeeded!', (sfile, dfile),not_web = self.not_web) + public.write_log_gettext("File manager", 'unzip file [ {} ] -> [ {} ] success', (sfile, dfile)) + return public.return_msg_gettext(True, 'Uncompression succeeded!') + + def get_7z_bin(self): + ''' + @name 获取7z命令路径 + @author hwliang + @return {string} 7z命令路径 + ''' + _7z_bins = ["/usr/bin/7z","/usr/bin/7za","/usr/bin/7zr"] + for _7z_bin in _7z_bins: + if os.path.exists(_7z_bin): + return _7z_bin + return None + + def install_7zip(self): + ''' + @name 安装7zip + @author hwliang + @return {bool} True/False + ''' + _7z_bin = self.get_7z_bin() + if _7z_bin: + return True + + # 是否已经尝试安装过 + install_tip = '{}/data/7z_install.pl'.format(public.get_panel_path()) + if os.path.exists(install_tip): + return False + + if os.path.exists("/usr/bin/apt-get"): + public.ExecShell("nohup apt-get -y install p7zip-full &> /dev/null &") + elif os.path.exists("/usr/bin/yum"): + public.ExecShell("nohup yum -y install p7zip &> /dev/null &") + elif os.path.exists("/usr/bin/dnf"): + public.ExecShell("nohup dnf -y install p7zip &> /dev/null &") + else: + return False + return True + + # 备份网站 + def backup_site(self, id, log_file): + find = public.M('sites').where( + "id=?", (id,)).field('name,path,id').find() + fileName = find['name']+'_' + \ + time.strftime('%Y%m%d_%H%M%S', time.localtime())+'.zip' + backupPath = public.M('config').where( + 'id=?', (1,)).getField('backup_path') + '/site' + + zipName = backupPath + '/'+fileName + if not (os.path.exists(backupPath)): + os.makedirs(backupPath) + + execStr = "cd '" + find['path'] + "' && zip '" + \ + zipName + "' -x .user.ini -r ./ &> " + log_file + public.ExecShell(execStr) + + sql = public.M('backup').add('type,name,pid,filename,size,addtime', + (0, fileName, find['id'], zipName, 0, public.getDate())) + public.WriteLog('TYPE_SITE', 'SITE_BACKUP_SUCCESS', (find['name'],),not_web = self.not_web) + return public.return_msg_gettext(True, 'Backup Succeeded!') + + # 备份数据库 + def backup_database(self, id, log_file): + name = public.M('databases').where("id=?", (id,)).getField('name') + find = public.M('config').where('id=?', (1,)).field( + 'mysql_root,backup_path').find() + + if not os.path.exists(find['backup_path'] + '/database'): + public.ExecShell('mkdir -p ' + find['backup_path'] + '/database') + self.mypass(True, find['mysql_root']) + + fileName = name + '_' + \ + time.strftime('%Y%m%d_%H%M%S', time.localtime()) + '.sql.gz' + backupName = find['backup_path'] + '/database/' + fileName + public.ExecShell("/www/server/mysql/bin/mysqldump --force --opt \"" + + name + "\" | gzip > " + backupName) + if not os.path.exists(backupName): + return public.return_msg_gettext(False, 'Backup error!') + + self.mypass(False, find['mysql_root']) + + sql = public.M('backup') + addTime = time.strftime('%Y-%m-%d %X', time.localtime()) + sql.add('type,name,pid,filename,size,addtime', + (1, fileName, id, backupName, 0, addTime)) + public.WriteLog("TYPE_DATABASE", "DATABASE_BACKUP_SUCCESS", (name,),not_web = self.not_web) + return public.return_msg_gettext(True, 'Backup Succeeded!') + + # 导入数据库 + def input_database(self, id, file, log_file): + name = public.M('databases').where("id=?", (id,)).getField('name') + root = public.M('config').where('id=?', (1,)).getField('mysql_root') + tmp = file.split('.') + exts = ['sql', 'gz', 'zip'] + ext = tmp[len(tmp) - 1] + if ext not in exts: + return public.return_msg_gettext(False, 'Select sql/gz/zip file!') + + isgzip = False + if ext != 'sql': + tmp = file.split('/') + tmpFile = tmp[len(tmp)-1] + tmpFile = tmpFile.replace('.sql.' + ext, '.sql') + tmpFile = tmpFile.replace('.' + ext, '.sql') + tmpFile = tmpFile.replace('tar.', '') + backupPath = public.M('config').where( + 'id=?', (1,)).getField('backup_path') + '/database' + + if ext == 'zip': + public.ExecShell("cd " + backupPath + " && unzip " + file) + else: + public.ExecShell("cd " + backupPath + " && tar zxf " + file) + if not os.path.exists(backupPath + "/" + tmpFile): + public.ExecShell("cd " + backupPath + + " && gunzip -q " + file) + isgzip = True + + if not os.path.exists(backupPath + '/' + tmpFile) or tmpFile == '': + return public.return_msg_gettext(False, 'Configuration file not exist', (tmpFile,)) + self.mypass(True, root) + public.ExecShell(public.GetConfigValue('setup_path') + "/mysql/bin/mysql -uroot -p" + + root + " --force \"" + name + "\" < " + backupPath + '/' + tmpFile) + self.mypass(False, root) + if isgzip: + public.ExecShell('cd ' + backupPath + + ' && gzip ' + file.split('/')[-1][:-3]) + else: + public.ExecShell("rm -f " + backupPath + '/' + tmpFile) + else: + self.mypass(True, root) + public.ExecShell(public.GetConfigValue( + 'setup_path') + "/mysql/bin/mysql -uroot -p" + root + " --force \"" + name + "\" < " + file) + self.mypass(False, root) + + public.WriteLog("TYPE_DATABASE", 'Successfully imported database [{}]', (name,),not_web = self.not_web) + return public.return_msg_gettext(True, 'Successfully imported database!') + + # 配置 + def mypass(self, act, root): + my_cnf = '/etc/my.cnf' + public.ExecShell("sed -i '/user=root/d' " + my_cnf) + public.ExecShell("sed -i '/password=/d' " + my_cnf) + if act: + mycnf = public.readFile(my_cnf) + rep = "\\[mysqldump\\]\nuser=root" + sea = "[mysqldump]\n" + subStr = sea + "user=root\npassword=\"" + root + "\"\n" + mycnf = mycnf.replace(sea, subStr) + if len(mycnf) > 100: + public.writeFile(my_cnf, mycnf) + + # 设置权限 + def set_file_accept(self, filename): + # public.ExecShell('chown -R www:www ' + filename) + # public.ExecShell('chmod -R 755 ' + filename) + import files + from collections import namedtuple + get = namedtuple('get',['path']) + get.path = filename + files.files().fix_permissions(get) + + # 检查敏感目录 + def check_dir(self, path): + path = path.replace('//', '/') + if path[-1:] == '/': + path = path[:-1] + + nDirs = ('', + '/', + '/*', + '/www', + '/root', + '/boot', + '/bin', + '/etc', + '/home', + '/dev', + '/sbin', + '/var', + '/usr', + '/tmp', + '/sys', + '/proc', + '/media', + '/mnt', + '/opt', + '/lib', + '/srv', + '/selinux', + '/www/server', + '/www/server/data', + public.GetConfigValue('logs_path'), + public.GetConfigValue('setup_path')) + + return not path in nDirs + + # 安装rar组件 + def install_rar(self): + unrar_file = '/www/server/rar/unrar' + rar_file = '/www/server/rar/rar' + bin_unrar = '/usr/local/bin/unrar' + bin_rar = '/usr/local/bin/rar' + if os.path.exists(unrar_file) and os.path.exists(bin_unrar): + try: + import rarfile + except: + public.ExecShell("pip install rarfile") + return True + + import platform + os_bit = '' + if platform.machine() == 'x86_64': + os_bit = '-x64' + download_url = public.get_url() + '/src/rarlinux'+os_bit+'-5.6.1.tar.gz' + + tmp_file = '/tmp/bt_rar.tar.gz' + public.ExecShell('wget -O ' + tmp_file + ' ' + download_url) + if os.path.exists(unrar_file): + public.ExecShell("rm -rf /www/server/rar") + public.ExecShell("tar xvf " + tmp_file + ' -C /www/server/') + if os.path.exists(tmp_file): + os.remove(tmp_file) + if not os.path.exists(unrar_file): + return False + + if os.path.exists(bin_unrar): + os.remove(bin_unrar) + if os.path.exists(bin_rar): + os.remove(bin_rar) + + public.ExecShell('ln -sf ' + unrar_file + ' ' + bin_unrar) + public.ExecShell('ln -sf ' + rar_file + ' ' + bin_rar) + #public.ExecShell("pip install rarfile") + return True + + +if __name__ == '__main__': + p = bt_task() + #p.create_task('测试执行SHELL',0,'yum install wget -y','') + # print(p.get_task_list()) + # p.modify_task(3,'status',0) + #p.modify_task(3,'shell','bash /www/server/panel/install/install_soft.sh 0 update php 5.6') + # p.modify_task(1,'other','{"sfile":"BTPanel","dfile":"/www/test.rar","z_type":"rar"}') + p.start_task() + # p._zip(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4],sys.argv[5]) diff --git a/class_v2/panel_video_V2.py b/class_v2/panel_video_V2.py new file mode 100644 index 00000000..3298d85c --- /dev/null +++ b/class_v2/panel_video_V2.py @@ -0,0 +1,62 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang <2020-05-18> +# +------------------------------------------------------------------- + +# +------------------------------------------------------------------- +# | 流媒体模块 +# +------------------------------------------------------------------- + +import os,sys,re +import mimetypes +import public +from BTPanel import Response + +def get_buff_size(file_size): + buff_size = 2097152 + if file_size > 1073741824: + buff_size = 4194304 + return buff_size + +def partial_response(path, start, end=None): + if not os.path.exists(path): + return Response("file not fount!",404) + file_size = os.path.getsize(path) + buff_size = get_buff_size(file_size) + + if end is None: + end = start + buff_size - 1 + end = min(end, file_size - 1) + end = min(end, start + buff_size - 1) + length = end - start + 1 + + with open(path, 'rb') as fd: + fd.seek(start) + bytes = fd.read(length) + assert len(bytes) == length + + response = Response(bytes,206,mimetype=mimetypes.guess_type(path)[0],direct_passthrough=True,) + response.headers.add('Content-Range', 'bytes {0}-{1}/{2}'.format(start, end, file_size,),) + response.headers.add('Accept-Ranges', 'bytes') + return response + +def get_range(request): + range = request.headers.get('Range') + m = None + if range: + m = re.match(r'bytes=(?P\d+)-(?P\d+)?', range) + if m: + start = m.group('start') + end = m.group('end') + start = int(start) + if end is not None: + end = int(end) + return start, end + else: + return 0, None + + diff --git a/class_v2/panel_warning_v2.py b/class_v2/panel_warning_v2.py new file mode 100644 index 00000000..84348249 --- /dev/null +++ b/class_v2/panel_warning_v2.py @@ -0,0 +1,1668 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang <2020-08-04> +# +------------------------------------------------------------------- + +import os, sys, json, time, datetime +os.chdir("/www/server/panel") +sys.path.append("class/") +import public +from public.validate import Param + +class panelWarning: + __path = '/www/server/panel/data/warning' + __ignore = __path + '/ignore' + __result = __path + '/result' + __risk = __path + '/risk' + _vuln_ignore = __path + '/ignore.json' + _vuln_result = __path + '/result.json' + __repair_count = __path + '/repair_count.json' + __vul_list = __path + '/high_risk_vul-9.json' # 空i那么;皮的泡沫隔离 + __report = '/www/server/panel/data/warning_report' + vul_num = 0 + discov_count = 0 # 扫描中发现的漏洞数 + score = 100 # 扫描中动态分数 + yum_time = __path + '/yum_time.pl' + new_vul_list = __path + '/vul_centos7.json' + product_version = __path + '/product_version.json' + + def __init__(self): + if not os.path.exists(self.__ignore): + os.makedirs(self.__ignore, 384) + if not os.path.exists(self.__result): + os.makedirs(self.__result, 384) + if not os.path.exists(self.__risk): + os.makedirs(self.__risk, 384) + if not os.path.exists(self.__path): + os.makedirs(self.__path, 384) + if not os.path.exists(self.__report): + os.makedirs(self.__report, 384) + + if not os.path.exists(self._vuln_ignore): + result = [] + public.WriteFile(self._vuln_ignore, json.dumps(result)) + if not os.path.exists(self._vuln_result): + result = [] + public.WriteFile(self._vuln_result, json.dumps(result)) + self.new_system_result = [] + # self.sys_version = self.get_sys_version() + # self.sys_product = self.new_get_sys_product() + + def _get_list(self): + # 最终输出结果 + self.data = { + 'security': [], + 'risk': [], + 'ignore': [], + "is_autofix": [], + } + # 获取支持一键修复的列表 + try: + is_autofix = public.read_config("safe_autofix") + except: + is_autofix = [] + # 临时扫描结果,中断的时候返回 + self.tmp_data = { + 'security': [], + 'risk': [], + 'ignore': [], + 'is_autofix': is_autofix, + 'check_time': datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") + } + context = {"status": "Ready to repair", "percentage": 0, "count": 0, "score": 100} + public.WriteFile(self.__path + '/bar.txt', json.dumps(context)) # 扫描进度条归零 + bar_num = 0 # 进度条初始化 + bar_limit = 0 # 进度条限制 + self.discov_count = 0 # 扫描中发现漏洞数量初始化 + self.score = 100 # 扫描中分数变化 + sys_version = self.get_sys_version() # 获取系统版本 + # self.compare_md5() # 比较漏洞库版本 + # 下载新版漏洞库文件 + if not self.download_new_vulns(): + sys_version = None + # centos_7走新版漏洞扫描 + if sys_version == 'centos_7' or sys_version == 'centos_8' or sys_version == 'centos_8_stream': + self.new_system_scan() + # ubuntu走新版2接口 + elif sys_version == 'ubuntu_20.04' or sys_version == 'ubuntu_22.04' or sys_version == 'ubuntu_18.04' or sys_version == 'debian_12' or sys_version == 'debian_11' or sys_version == 'debian_10': + self.new_system_scan2() + # 旧版本漏洞检测 + # else: + # self.system_scan() # 旧版本系统漏洞扫描 + + # 加载安全风险模块 + p = public.get_modules('class_v2/safe_warning_v2') + for m_name in p.__dict__.keys(): + ignore_file = self.__ignore + '/' + m_name + '.pl' + # 忽略的检查项 + if p[m_name]._level == 0: continue + + m_info = { + 'title': p[m_name]._title, + 'm_name': m_name, + 'ps': p[m_name]._ps, + 'version': p[m_name]._version, + 'level': p[m_name]._level, + 'ignore': p[m_name]._ignore, + 'date': p[m_name]._date, + 'tips': p[m_name]._tips, + 'help': p[m_name]._help + } + try: + m_info['remind'] = p[m_name]._remind + except: + pass + result_file = self.__result + '/' + m_name + '.pl' + + try: + s_time = time.time() + m_info['status'], m_info['msg'] = p[m_name].check_run() + m_info['taking'] = round(time.time() - s_time, 6) + m_info['check_time'] = int(time.time()) + public.writeFile(result_file, json.dumps( + [m_info['status'], m_info['msg'], m_info['check_time'], m_info['taking']], )) + except: + continue + + m_info['ignore'] = os.path.exists(ignore_file) + if m_info['ignore']: + self.data['ignore'].append(m_info) + self.tmp_data['ignore'].append(m_info) # 临时扫描结果 + else: + if m_info['status']: + self.data['security'].append(m_info) + self.tmp_data['security'].append(m_info) # 临时扫描结果 + else: + risk_file = self.__risk + '/' + m_name + '.pl' + public.writeFile(risk_file, json.dumps(m_info)) + self.data['risk'].append(m_info) + self.tmp_data['risk'].append(m_info) # 临时扫描结果 + self.discov_count += 1 # 扫描中发现风险数 + self.score -= m_info['level'] + if self.score < 0: + self.score = 0 + + bar = ("%.2f" % (float(bar_num) / float(len(p.__dict__.keys())) * 50 + 50)) + # 通过进度条限制,防止写文件频繁占用高 + if int(float(bar)) >= bar_limit: + context = {"status": "{}".format(m_info['title']), "percentage": int(float(bar)), "count": self.discov_count, "score": self.score} + public.WriteFile(self.__path + '/bar.txt', json.dumps(context)) + self.dump_tmp_result() # 发现漏洞,先保存一份临时的 + bar_limit += 10 + bar_num += 1 + + # 新版漏洞检测无需读文件 + # is_autofix被包含进tmp_data{}字典里,会动态增加 + self.data['is_autofix'] = is_autofix + # self.data['is_autofix'] += is_autofix + + # if sys_version == 'centos_7' or sys_version == 'ubuntu_20.04' or sys_version == 'ubuntu_22.04' or sys_version == 'ubuntu_18.04' or sys_version == 'debian_12' or sys_version == 'debian_11' or sys_version == 'debian_10': + # self.data['is_autofix'] += is_autofix + # 旧版本漏洞检测 + # else: + # vuln_result = self.get_vuln_result() + # self.data['risk'] = self.data['risk'] + vuln_result['risk'] + # self.data['ignore'] = self.data['ignore'] + vuln_result['ignore'] + # vuln_is_autofix = [] + # for vr in vuln_result['risk']: + # if not vr["reboot"]: + # vuln_is_autofix.append(vr["cve_id"]) + # self.data['is_autofix'] = is_autofix + vuln_is_autofix + + score = 100 + for d in self.data['risk']: + score = score - d['level'] + if score < 0: + self.data['score'] = 0 + else: + self.data['score'] = score + self.data['risk'] = sorted(self.data['risk'], key=lambda x: x['level'], reverse=True) + self.data['security'] = sorted(self.data['security'], key=lambda x: x['level'], reverse=True) + self.data['ignore'] = sorted(self.data['ignore'], key=lambda x: x['level'], reverse=True) + self.data['check_time'] = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") + # 将结果输出一份到报告目录下 + with open("/www/server/panel/data/warning_report/data.json", "w") as f: + json.dump(self.data, f) + self.record_times() + context = {"status": "Detection completed", "percentage": 100, "count": self.discov_count, "score": self.score} + public.WriteFile(self.__path + '/bar.txt', json.dumps(context)) + # public.WriteFile(self.__path + '/bar.txt', "100") # 扫描进度条归零 + return self.data + + def download_new_vulns(self): + ''' + 根据系统版本确定漏洞库名 + ''' + sys_version = self.get_sys_version() + zip_file = "" + if sys_version == "centos_7": + self.new_vul_list = self.__path + '/vul_centos7.json' + zip_file = "vul_centos7.zip" + elif sys_version == "centos_8": + self.new_vul_list = self.__path + '/vul_centos8.json' + zip_file = "vul_centos8.zip" + elif sys_version == "centos_8_stream": + self.new_vul_list = self.__path + '/vul_centos8stream.json' + zip_file = "vul_centos8stream.zip" + elif sys_version == "ubuntu_20.04": + self.new_vul_list = self.__path + '/vul_ubuntu2004.json' + zip_file = "vul_ubuntu2004.zip" + elif sys_version == "ubuntu_22.04": + self.new_vul_list = self.__path + '/vul_ubuntu2204.json' + zip_file = "vul_ubuntu2204.zip" + elif sys_version == "ubuntu_18.04": + self.new_vul_list = self.__path + '/vul_ubuntu1804.json' + zip_file = "vul_ubuntu1804.zip" + elif sys_version == "debian_12": + self.new_vul_list = self.__path + '/vul_debian12.json' + zip_file = "vul_debian12.zip" + elif sys_version == "debian_11": + self.new_vul_list = self.__path + '/vul_debian11.json' + zip_file = "vul_debian11.zip" + elif sys_version == "debian_10": + self.new_vul_list = self.__path + '/vul_debian10.json' + zip_file = "vul_debian10.zip" + # 检查漏洞库文件是否存在 + if not os.path.exists(self.new_vul_list): + if zip_file != '': + downfile = self.__path+'/'+zip_file + public.downloadFile("/safe_warning/{}".format(public.get_url(),zip_file), downfile) + o, e = public.ExecShell("unzip -o {} -d {}".format(downfile, self.__path)) + # 解压报错 + if e != "": + return False + else: + return False + return True + # cve_id + def get_list(self, args): + ''' + @name 开始扫描并返回结果 + @param args: + @return: + ''' + if 'force' in args: + # 校验参数 + try: + args.validate([ + Param('force').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + import subprocess + public.set_module_logs("panel_warning_v2", "get_list") + public.WriteFile(self.__path + '/kill.pl', "False") # 用来判断这次的扫描是否被中断,默认没中断 + command = "btpython /www/server/panel/class_v2/panel_warning_v2.py" + process = subprocess.Popen(command, shell=True) + # 获取进程ID + pid = process.pid + public.WriteFile(self.__path + "/pid.txt", str(pid)) + process.wait() + result_file = self.__path + '/resultresult.json' + output = { + "score": 100, + "check_time": public.format_date(), + "interrupt": False, + "security": [], + "risk": [], + "ignore": [], + "is_autofix": [] + } + if not os.path.exists(result_file): + return output + result_body = public.ReadFile(result_file) + if not result_body: + return output + try: + output = json.loads(result_body) + except: + return output + # 给前端判断这次的扫描结果是否中断 + if public.ReadFile(self.__path + '/kill.pl') == "True": + output["interrupt"] = True + else: + output["interrupt"] = False + return public.return_message(0,0,output) + + def sync_rule(self): + ''' + @name 从云端同步规则 + @author hwliang<2020-08-05> + @return void + ''' + # try: + # dep_path = '/www/server/panel/class/safe_warning' + # local_version_file = self.__path + '/version.pl' + # last_sync_time = local_version_file = self.__path + '/last_sync.pl' + # if os.path.exists(dep_path): + # if os.path.exists(last_sync_time): + # if int(public.readFile(last_sync_time)) > time.time(): + # return + # else: + # if os.path.exists(local_version_file): os.remove(local_version_file) + + # download_url = public.get_url() + # version_url = download_url + '/install/warning/version.txt' + # cloud_version = public.httpGet(version_url) + # if cloud_version: cloud_version = cloud_version.strip() + + # local_version = public.readFile(local_version_file) + # if local_version: + # if cloud_version == local_version: + # return + + # tmp_file = '/tmp/bt_safe_warning.zip' + # public.ExecShell('wget -O {} {} -T 5'.format(tmp_file,download_url + '/install/warning/safe_warning.zip')) + # if not os.path.exists(tmp_file): + # return + + # if os.path.getsize(tmp_file) < 2129: + # os.remove(tmp_file) + # return + + # if not os.path.exists(dep_path): + # os.makedirs(dep_path,384) + # public.ExecShell("unzip -o {} -d {}/ >/dev/null".format(tmp_file,dep_path)) + # public.writeFile(local_version_file,cloud_version) + # public.writeFile(last_sync_time,str(int(time.time() + 7200))) + # if os.path.exists(tmp_file): os.remove(tmp_file) + # public.ExecShell("chmod -R 600 {}".format(dep_path)) + # except: + # pass + + def set_ignore(self, args): + ''' + @name 设置指定项忽略状态 + @author hwliang<2020-08-04> + @param dict_obj { + m_name 模块名称 + } + @return dict + ''' + m_name = args.m_name.strip() + ignore_file = self.__ignore + '/' + m_name + '.pl' + if os.path.exists(ignore_file): + os.remove(ignore_file) + else: + public.writeFile(ignore_file, '1') + return public.return_message(0,0, 'successfully set!') + + def check_find(self, args): + ''' + @name 检测指定项 + @author hwliang<2020-08-04> + @param dict_obj { + m_name 模块名称 + } + @return dict + ''' + # 校验参数 + try: + args.validate([ + Param('m_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + try: + m_name = args.m_name.strip() + p = public.get_modules('class_v2/safe_warning_v2') + m_info = { + 'title': p[m_name]._title, + 'm_name': m_name, + 'ps': p[m_name]._ps, + 'version': p[m_name]._version, + 'level': p[m_name]._level, + 'ignore': p[m_name]._ignore, + 'date': p[m_name]._date, + 'tips': p[m_name]._tips, + 'help': p[m_name]._help + } + + # 解决已经在忽略列表中,但是如果仍然需要检查的话可以检查 + ignore_file = self.__ignore + '/' + m_name + '.pl' + if os.path.exists(ignore_file): + from cachelib import SimpleCache + cache = SimpleCache(5000) + ikey = 'warning_list' + cache.delete(ikey) + os.remove(ignore_file) + + result_file = self.__result + '/' + m_name + '.pl' + s_time = time.time() + m_info['status'], m_info['msg'] = p[m_name].check_run() + m_info['taking'] = round(time.time() - s_time, 4) + m_info['check_time'] = int(time.time()) + public.writeFile(result_file, json.dumps( + [m_info['status'], m_info['msg'], m_info['check_time'], m_info['taking']])) + return public.return_message(0,0, 'It has been retested.') + except: + return public.return_message(-1,0, 'Detection failed') + + + def system_scan(self): + ''' + 一键扫描系统 + :param get: + :return: dict + ''' + self.compare_md5() + sys_version = self.get_sys_version() + + # if sys_version == 'None': + # return public.returnMsg(False, '当前系统暂不支持') + sys_product = self.get_sys_product() + # if not os.path.exists(self.__vul_list): + # return public.returnMsg(False, "扫描失败") + vul_list = self.get_vul_list() + + new_risk_list = [] + new_ignore_list = [] + # error_list = [] + result_dict = {} + # cp_list = [] + vul_count = 0 + ignore_list = self.get_ignore_list() + reboot_count = 0 + self.vul_num = len(vul_list) + bar_num = 0 # 进度条初始化 + bar_limit = 0 # 进度条限制 + for vul in vul_list: + bar = ("%.2f" % (float(bar_num)/float(self.vul_num)*50)) + # 限制进度条,限制写文件频率 + if int(float(bar)) >= bar_limit: + context = {"status": "{}".format(vul['cve_id']), "percentage": int(float(bar)), "count": self.discov_count, "score": self.score} + public.WriteFile(self.__path+'/bar.txt', json.dumps(context)) + self.dump_tmp_result() # 发现漏洞,先保存一份临时的 + bar_limit += 10 + bar_num += 1 + vul_count += 1 + for v in vul["affected_list"]: + if v["manufacturer"] == sys_version: + tmp = 1 # 默认命中 + if not 'affected' in v: continue + arr = v['affected'].split("Up to (excluding)\n ") + if len(arr) < 2: continue + vul_version = arr[1] + try: + for soft in v["softname"]: + compare_result = self.version_compare(sys_product[soft], vul_version) + if compare_result >= 0: + tmp = 0 # 当有一个软件包版本不在漏洞范围内,则不命中 + break + if tmp == 1: + # softname_list = [soft+'-'+sys_product[soft] for soft in v["softname"]] + # softname_list = [{soft: sys_product[soft]} for soft in v["softname"]] + self.discov_count += 1 # 扫描中发现漏洞数 + softname_dict = {} + for soft in v["softname"]: + softname_dict[soft] = sys_product[soft] + level = self.get_score_risk(vul["score"]) + vul_dict = {key: vul[key] for key in ["cve_id", "vuln_name", "vuln_time", "vuln_solution"]} + vul_dict["level"] = level + self.score -= level # 扫描中的动态分数 + if self.score < 0: + self.score = 0 + vul_dict["soft_name"] = softname_dict + vul_dict["vuln_version"] = vul_version + vul_dict["check_time"] = int(time.time()) + vul_dict["reboot"] = "" + if "kernel" in [k for k in v["softname"]]: + vul_dict["reboot"] = "This vulnerability is a kernel vulnerability, and you need to upgrade the kernel version yourself. It is recommended to make snapshots and backups before upgrading" + reboot_count += 1 + if vul["cve_id"] in ignore_list: + new_ignore_list.append(vul_dict) + self.tmp_data['ignore'].append(vul_dict) # 添加到临时字典 + break + new_risk_list.append(vul_dict) + self.tmp_data['risk'].append(vul_dict) # 添加到临时字典 + if vul_dict["reboot"]: + self.tmp_data['is_autofix'].append(vul_dict["cve_id"]) + break + # cp_list.append(vul["cve_id"]+': '+str([soft+'-'+sys_product[soft] for soft in v["softname"]])+' >= '+vul_version) + except Exception as e: + # error_list.append(vul["cve_id"]+': '+str(e)) + break + result_dict["vul_count"] = vul_count + result_dict["risk"] = new_risk_list + result_dict["ignore"] = new_ignore_list + # result_dict["reboot"] = self.__need_reboot + # result_dict["error"] = error_list + # result_dict["compare"] = cp_list + public.WriteFile(self.__path + '/system_scan_time', str(int(time.time()))) + public.WriteFile(self._vuln_result, json.dumps(result_dict)) + # try: + # public.WriteFile(self._vuln_result, json.dumps(result_dict)) + # return public.returnMsg(True, "扫描完成") + # except: + # return public.returnMsg(False, "扫描失败") + + # 版本比较 + def version_compare(self, ver_a, ver_b): + ''' + 比较版本大小 + :param ver_a: 软件版本 + :param ver_b: 漏洞版本 + :return: int 大于等于返回1或0,小于返回-1 + ''' + sys_version = self.get_sys_version() + if "ubuntu" in sys_version or "debian" in sys_version: + if ver_b.startswith("1:"): + ver_b = ver_b[2:] + # if ver_a.startswith("1:"): + # ver_a = ver_a[2:] + result = public.ExecShell("dpkg --compare-versions " + ver_a + " ge " + ver_b + " && echo true") + if 'warning' in result[1].strip(): return None + if 'true' in result[0].strip(): + return 1 + else: + return -1 + return self.vercmp(ver_a, ver_b) + + def vercmp(self, first, second): + import re + R_NONALNUMTILDE = re.compile(br"^([^a-zA-Z0-9~]*)(.*)$") + R_NUM = re.compile(br"^([\d]+)(.*)$") + R_ALPHA = re.compile(br"^([a-zA-Z]+)(.*)$") + first = first.encode("ascii", "ignore") + second = second.encode("ascii", "ignore") + while first or second: + m1 = R_NONALNUMTILDE.match(first) + m2 = R_NONALNUMTILDE.match(second) + m1_head, first = m1.group(1), m1.group(2) + m2_head, second = m2.group(1), m2.group(2) + if m1_head or m2_head: + continue + + if first.startswith(b'~'): + if not second.startswith(b'~'): + return -1 + first, second = first[1:], second[1:] + continue + if second.startswith(b'~'): + return 1 + + if not first or not second: + break + + m1 = R_NUM.match(first) + if m1: + m2 = R_NUM.match(second) + if not m2: + return 1 + isnum = True + else: + m1 = R_ALPHA.match(first) + m2 = R_ALPHA.match(second) + isnum = False + + if not m1: + return -1 + if not m2: + return 1 if isnum else -1 + + m1_head, first = m1.group(1), m1.group(2) + m2_head, second = m2.group(1), m2.group(2) + + if isnum: + m1_head = m1_head.lstrip(b'0') + m2_head = m2_head.lstrip(b'0') + + m1hlen = len(m1_head) + m2hlen = len(m2_head) + if m1hlen < m2hlen: + return -1 + if m1hlen > m2hlen: + return 1 + if m1_head < m2_head: + return -1 + if m1_head > m2_head: + return 1 + continue + + m1len = len(first) + m2len = len(second) + if m1len == m2len == 0: + return 0 + if m1len != 0: + return 1 + return -1 + + # 取系统版本 + def get_sys_version(self): + ''' + 获取当前系统版本 + :return: string + ''' + sys_version = "None" + if os.path.exists("/etc/redhat-release"): + result = public.ReadFile("/etc/redhat-release") + if "CentOS Linux release 7" in result: + sys_version = "centos_7" + elif "CentOS Linux release 8" in result: + sys_version = "centos_8" + elif "CentOS Stream release 8" in result: + sys_version = "centos_8_stream" + elif os.path.exists("/etc/lsb-release"): + if "Ubuntu 20.04" in public.ReadFile("/etc/lsb-release"): + sys_version = "ubuntu_20.04" + elif "Ubuntu 22.04" in public.ReadFile("/etc/lsb-release"): + sys_version = "ubuntu_22.04" + elif "Ubuntu 18.04" in public.ReadFile("/etc/lsb-release"): + sys_version = "ubuntu_18.04" + elif os.path.exists("/etc/debian_version"): + result = public.ReadFile("/etc/debian_version") + if "10." in result: + sys_version = "debian_10" + elif "11." in result: + sys_version = "debian_11" + elif "12." in result: + sys_version = "debian_12" + return sys_version + + def new_get_sys_product(self, flag=False): + ''' + 新版获取系统软件包及版本{"name":"version"} + @param flag bool 为True时直接扫一次 + ''' + # 修复完成需要重新获取一次软件包版本 + if flag: + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + + sys_version = self.get_sys_version() + if sys_version == "centos_7": + # 根据yum日志判断是否用软件包更新 + yumlog = "/var/log/yum.log" + elif sys_version == "centos_8" or sys_version == "centos_8_stream": + yumlog = "/var/log/dnf.log" + else: + yumlog = "/var/log/apt/history.log" + try: + # 先判断有没有yum.log + if os.path.exists(yumlog): + # 第一次将yum.log修改时间记录在文件里 + if not os.path.exists(self.yum_time): + new_modi_time = str(int(os.path.getmtime(yumlog))) + public.WriteFile(self.yum_time, new_modi_time) + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + else: + old_modi_time = public.ReadFile(self.yum_time) + new_modi_time = str(int(os.path.getmtime(yumlog))) + # 比较上一次记录的时间和这次获取的修改时间,相等则根据rpm文件的修改时间 + if old_modi_time == new_modi_time: + if os.path.exists(self.product_version): + # 若rpm文件修改日期大于七天,则还是执行rpm检测 + if self.is_file_too_old(self.product_version, 7): + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + else: + sys_product = json.loads(public.ReadFile(self.product_version)) + else: + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + # 不相等证明最近有用yum安装过新软件,更新文件并检测 + else: + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) # 将软件包版本写入文件 + public.WriteFile(self.yum_time, new_modi_time) + # 没有yum.log,则直接根据rpm文件修改日期 + else: + if os.path.exists(self.product_version): + if self.is_file_too_old(self.product_version, 7): + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + else: + sys_product = json.loads(public.ReadFile(self.product_version)) + else: + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + except Exception as e: + sys_product = self.get_sys_product() + public.WriteFile(self.product_version, json.dumps(sys_product)) + # 其他系统版本 + return sys_product + + # 取软件包版本 + def get_sys_product(self): + """ + 获取系统软件包及版本 + {"name":"version"} + :return dict 如果系统不支持则返回str None + """ + product_version = {} + sys_version = self.get_sys_version() + + # if sys_version == 'None':return public.returnMsg(False,'当前系统暂不支持') + if "centos" in sys_version: + result = public.ExecShell('rpm -qa --qf \'%{NAME};%{VERSION}-%{RELEASE}\\n\'')[0].strip().split('\n') + elif "ubuntu" in sys_version: + # result1 = subprocess.check_output(['dpkg-query', '-W', '-f=${Package};${Version}\n']).decode('utf-8').strip().split('\n') + result = public.ExecShell('dpkg-query -W -f=\'${Package};${Version}\n\'')[0].strip().split('\n') + elif "debian" in sys_version: + result = public.ExecShell('dpkg-query -W -f=\'${Package};${Version}\n\'')[0].strip().split('\n') + elif sys_version == "None": + return None + else: + return None + for pkg in result: + try: + product_version[pkg.split(";")[0]] = pkg.split(";")[1] + except: + return None + # product_version["kernel"] = subprocess.check_output(['uname', '-r']).decode('utf-8').strip().replace(".x86_64", "") + product_version["kernel"] = public.ExecShell('uname -r')[0].strip() + return product_version + + def get_vuln_result(self): + ''' + 获取上一次扫描结果 + :param get: + :return: dict + ''' + d_risk = 0 + h_risk = 0 + m_risk = 0 + vul_list = [] + if not os.path.exists(self.__vul_list): + self.vul_num = 0 + else: + self.vul_num = len(self.get_vul_list()) + if not os.path.exists(self._vuln_result): + tmp_dict = {"vul_count": self.vul_num, "risk": [], "ignore": [], + "count": {"serious": 0, "high_risk": 0, "moderate_risk": 0}, "msg": "", + "repair_count": {"all_count": 0, "today_count": 0}, "all_check_time": "", "ignore_count": 0} + if os.path.exists("/etc/redhat-release"): + result = public.ReadFile("/etc/redhat-release") + if "CentOS Linux release 8" in result: + # tmp_dict["msg"] = "当前系统【centos_8】官方已停止维护,为了安全起见,建议升级至centos 8 stream\n详情参考教程:https://www.bt.cn/bbs/thread-82931-1-1.html" + tmp_dict["msg"] = "The current system [centos_8] has been officially stopped maintenance, for security purposes, it is recommended to upgrade to centos 8 stream" + return tmp_dict + if public.ReadFile(self._vuln_result) == '[]': + tmp_dict = {"vul_count": self.vul_num, "risk": [], "ignore": [], + "count": {"serious": 0, "high_risk": 0, "moderate_risk": 0}, "msg": "", + "repair_count": {"all_count": 0, "today_count": 0}, "all_check_time": "", "ignore_count": 0} + if os.path.exists("/etc/redhat-release"): + result = public.ReadFile("/etc/redhat-release") + if "CentOS Linux release 8" in result: + # tmp_dict["msg"] = "当前系统【centos_8】官方已停止维护,为了安全起见,建议升级至centos 8 stream\n详情参考教程:https://www.bt.cn/bbs/thread-82931-1-1.html" + tmp_dict["msg"] = "The current system [centos_8] has been officially stopped maintenance, for security purposes, it is recommended to upgrade to centos 8 stream" + return tmp_dict + result_dict = json.loads(public.ReadFile(self._vuln_result)) + old_risk_list = result_dict["risk"] + old_ignore_list = result_dict["ignore"] + new_risk_list = old_risk_list.copy() + new_ignore_list = old_ignore_list.copy() + tmp_ignore_list = self.get_ignore_list() + for cve in old_risk_list: + if cve["cve_id"] in tmp_ignore_list: + new_ignore_list.append(cve) + new_risk_list.remove(cve) + for cve_ig in old_ignore_list: + if cve_ig["cve_id"] not in tmp_ignore_list: + new_risk_list.append(cve_ig) + new_ignore_list.remove(cve_ig) + for vul in new_ignore_list + new_risk_list: + vul_list.append(vul["cve_id"]) + if vul["cve_id"] in tmp_ignore_list: + continue + if vul["level"] == 3: + d_risk += 1 + elif vul["level"] == 2: + h_risk += 1 + elif vul["level"] == 1: + m_risk += 1 + list_sort = [3, 2, 1] # 排序列表 + # result_dict["risk"] = old_risk_list + result_dict["risk"] = sorted(new_risk_list, key=lambda x: list_sort.index(x.get("level"))) + # result_dict["ignore"] = old_ignore_list + result_dict["ignore"] = sorted(new_ignore_list, key=lambda x: list_sort.index(x.get("level"))) + # result_dict["reboot"] = self.__need_reboot + result_dict["count"] = {"serious": d_risk, "high_risk": h_risk, "moderate_risk": m_risk} + result_dict["msg"] = "" + result_dict["repair_count"] = self.count_repair(vul_list) + result_dict["all_check_time"] = public.ReadFile(self.__path + '/system_scan_time') + result_dict["ignore_count"] = len(tmp_ignore_list) + if os.path.exists("/etc/redhat-release"): + result = public.ReadFile("/etc/redhat-release") + if "CentOS Linux release 8" in result: + result_dict[ + "msg"] = "The current system [centos_8] has been officially stopped maintenance, for security purposes, it is recommended to upgrade to centos 8 stream" + public.WriteFile(self._vuln_result, json.dumps(result_dict)) + return result_dict + + # 按分数评等级 + def get_score_risk(self, score): + ''' + 拿到分数,返回危险等级 + :param score: + :return: int 若没有符合的分数就报错,需要捕获异常 + ''' + if float(score) >= 9.0: + risk = 3 + elif float(score) >= 7.0: + risk = 2 + elif float(score) >= 6.0: + risk = 1 + return risk + + def get_vul_list(self): + return json.loads(public.ReadFile(self.__vul_list)) + + def get_ignore_list(self): + return json.loads(public.ReadFile(self._vuln_ignore)) + + def set_vuln_ignore(self, args): + ''' + 设置忽略指定cve,若已在列表里,则删除,不在列表里则添加 + :param args: + :return: dict {status:true,msg:'设置成功/失败'} + ''' + cve_list = json.loads(args.cve_list.strip()) + ignore_list = self.get_ignore_list() + for cl in cve_list: + if cl in ignore_list: + ignore_list.remove(cl) + else: + ignore_list.append(cl) + + public.WriteFile(self._vuln_ignore, json.dumps(ignore_list)) + # public.WriteFile(self.__result, json.dumps(result_dict)) + return public.return_message(0,0, 'successfully set!') + # except: + # return public.returnMsg(False, '{}设置失败!'.format(cve_list)) + + def count_repair(self, now_list): + ''' + 获取总共修复漏洞的数量以及今日修复漏洞数量 + :param now_list: + :return: dict + ''' + cve_dict = {} + if not os.path.exists(self.__repair_count): + cve_dict["all_cve"] = now_list + cve_dict["today_cve"] = now_list + cve_dict["time"] = int(time.time()) + public.WriteFile(self.__repair_count, json.dumps(cve_dict)) + cve_dict = json.loads(public.ReadFile(self.__repair_count)) + cve_dict["all_cve"].extend(set(now_list) - set(cve_dict["all_cve"])) + all_count = len(cve_dict["all_cve"]) - len(now_list) + cve_dict["today_cve"].extend(set(now_list) - set(cve_dict["today_cve"])) + today_count = len(cve_dict["today_cve"]) - len(now_list) + # if cve_dict["time"].split(" ")[0] != self.get_time().split(" ")[0]: + # cve_dict["today_cve"] = now_list + cve_dict["time"] = int(time.time()) + public.WriteFile(self.__repair_count, json.dumps(cve_dict)) + return {"all_count": all_count, "today_count": today_count} + + def get_time(self): + return public.format_date() + + def check_cve(self, args): + ''' + 检测单个漏洞 + :param args: + :return: dict + ''' + sys_product = self.get_sys_product() + if not sys_product: + return public.returnMsg(True, 'Detection failed') + cve_id = args.cve_id.strip() + result_dict = json.loads(public.ReadFile(self._vuln_result)) + risk_list = result_dict["risk"] + ignore_list = result_dict["ignore"] + tmptmp = 1 + for cve in risk_list: + if cve["cve_id"] == cve_id: + tmp = 1 # 默认命中漏洞 + cve["check_time"] = int(time.time()) + for soft in list(cve["soft_name"].keys()): + if self.version_compare(sys_product[soft], cve["vuln_version"]) >= 0: + tmp = 0 # 当有一个软件包不命中,则为已修复 + tmptmp = 0 + break + if tmp == 0: + risk_list.remove(cve) + for cve in ignore_list: + if cve["cve_id"] == cve_id: + tmp = 1 # 默认命中漏洞 + cve["check_time"] = int(time.time()) + for soft in list(cve["soft_name"].keys()): + if self.version_compare(sys_product[soft], cve["vuln_version"]) >= 0: + tmp = 0 # 当有一个软件包不命中,则为已修复 + tmptmp = 0 + break + if tmp == 0: + ignore_list.remove(cve) + result_dict["risk"] = risk_list + result_dict["ignore"] = ignore_list + public.WriteFile(self._vuln_result, json.dumps(result_dict)) + if tmptmp == 0: + return public.returnMsg(True, 'It has been retested.') + else: + return public.returnMsg(True, 'It has been retested.') + + def compare_md5(self): + ''' + 对比md5,更新漏洞库 + :return: + ''' + import requests + # try: + # new_md5 = requests.get("https://www.bt.cn/vulscan_d11ad1fe99a5f078548b0ea355db42dc.txt").text + # except: + # return 0 + # old_md5 = public.FileMd5(self.__vul_list) + # if old_md5 != new_md5 or not os.path.exists(self.__vul_list): + if not os.path.exists(self.__vul_list): + try: + public.downloadFile("{}/install/src/high_risk_vul.zip".format(public.get_url()), + self.__path + "/high_risk_vul.zip") + public.ExecShell("unzip -o {}/high_risk_vul.zip -d {}/".format(self.__path, self.__path)) + except: + return 0 + return 1 + + def get_logs(self, get): + ''' + 获取升级日志 + :param get: + :return: dict + ''' + import files + return public.returnMsg(True, files.files().GetLastLine(self.__path + '/log.txt', 20)) + + def record_times(self): + ''' + 记录近七日扫描次数 + ''' + date_obj = datetime.datetime.now() + weekday = datetime.datetime.now().weekday() + if not os.path.exists("/www/server/panel/data/warning_report/record.json"): + tmp = {"scan": [], "repair": []} + for i in range(6, -1, -1): + last_date = (date_obj - datetime.timedelta(days=i)).strftime("%Y/%m/%d") + tmp["scan"].append({"date": last_date, "times": 0}) + tmp["repair"].append({"date": last_date, "times": 0}) + public.WriteFile("/www/server/panel/data/warning_report/record.json", json.dumps(tmp)) + with open("/www/server/panel/data/warning_report/record.json", "r") as f: + record = json.load(f) + if record["scan"][weekday]["date"] == datetime.datetime.now().strftime("%Y/%m/%d"): + record["scan"][weekday]["times"] += 1 + else: + record["scan"][weekday]["date"] = datetime.datetime.now().strftime("%Y/%m/%d") + record["scan"][weekday]["times"] = 1 + public.WriteFile("/www/server/panel/data/warning_report/record.json", json.dumps(record)) + + def get_scan_bar(self, args): + ''' + 获取扫描进度条 + @param args: + @return: int + ''' + if not os.path.exists(self.__path + '/bar.txt'):return 0 + data = json.loads(public.ReadFile(self.__path + '/bar.txt')) + return public.return_message(0,0,data) + + def kill_get_list(self, args): + ''' + 杀掉扫描进程 + @param args: + @return: + ''' + if not os.path.exists(self.__path + '/pid.txt'): + return {"status": False, "msg": "Interrupt failure"} + pid = public.ReadFile(self.__path + '/pid.txt') + err = public.ExecShell("kill -9 {}".format(str(pid)))[1].strip() + if err: + return {"status": False, "msg": "Interrupt failure"} + else: + public.WriteFile(self.__path + '/kill.pl', "True") + return {"status": True, "msg": "Interrupt successfully"} + + def dump_tmp_result(self): + ''' + 动态保存结果 + @param args: + @return: + ''' + public.WriteFile(self.__path + '/tmp_result.json', json.dumps(self.tmp_data)) + + def get_tmp_result(self, args): + ''' + 获取中途中断结果 + @param args: + @return: + ''' + if not os.path.exists(self.__path + '/tmp_result.json'): + return "err" + return json.loads(public.ReadFile(self.__path + '/tmp_result.json')) + + def new_system_scan2(self): + ''' + 新版系统dpkg软件包漏洞检测 + ''' + if not os.path.exists(self.new_vul_list): + return + # 加载漏洞库文件 + try: + vul_json = json.loads(public.ReadFile(self.new_vul_list)) + packages_rule = vul_json['Packages'] + detail = vul_json['Detail'] + except Exception as e: + return + sys_product = self.new_get_sys_product() + if sys_product is None: + return + + dpkg = Dpkg # 获取DPKG对象 + # 符合 + systemscan_result = {} + # 开始检测 + # 第一层遍历系统软件包 + for pk, ver in sys_product.items(): + # 跳过内核漏洞检测 + # if pk.startswith("kernel"): + # continue + # 是否有历史漏洞 + if pk in packages_rule: + # 第二层遍历比较软件包涉及的漏洞版本 + for rule in packages_rule[pk]: + # 判断软件包版本是否存在主版本号,有则漏洞版本一起保留主版本号,否则去掉漏洞版本的主版本号 + pk_ver, vul_ver = self.adjust_ver(ver, rule[0]) + try: + cp_result = dpkg.compare_versions(pk_ver, vul_ver) + except: + continue + # 任意一个命中 + if cp_result == -1: + if rule[1] not in systemscan_result: + systemscan_result[rule[1]] = detail[str(rule[1])] + systemscan_result[rule[1]]["impact"] = [{"package": pk, "version": pk_ver, "vul_ver": vul_ver}] + else: + systemscan_result[rule[1]]["impact"].append({"package": pk, "version": pk_ver, "vul_ver": vul_ver}) + + # 为了兼容旧版本再次做处理 + for sr in systemscan_result.values(): + one_risk = {} + one_risk["title"] = "【{}】Linux系统安全漏洞编号".format(sr["ref_id"]) + one_risk["data"] = "2023-12-08" + one_risk["help"] = "" + one_risk["ignore"] = False + level = self.new_severity_to_num(sr["severity"]) + one_risk["level"] = level + one_risk["m_name"] = sr["ref_id"] + pk_list = [] + # 判断是否有内核漏洞在里面 + is_kernel = False + soft_list = [] + for impact in sr["impact"]: + if impact["package"].startswith("kernel"): + is_kernel = True + continue + soft_list.append("{} Versions below {}".format(impact["package"]+"-"+impact["version"], impact["vul_ver"])) + pk_list.append(impact["package"]) + if is_kernel: + continue + one_risk["msg"] = "Security vulnerabilities are found in the following system software:
                                    {}
                                    Vulnerabilities involved:{}
                                    Please refer to the official announcement for details:{}".format('
                                    '.join(soft_list), '、'.join(sr["cve"]),sr["ref_url"]) + one_risk["ps"] = "【{}】Linux system vulnerability security vulnerability number".format(sr["ref_id"]) + one_risk["remind"] = "Fixing vulnerabilities has certain risks, so it is recommended to take a good system snapshot to prevent system operation from being affected." + one_risk["status"] = False + one_risk["taking"] = 0.000001 + one_risk["tips"] = ["Update the software to a safe version according to the risk description", "Or click [One-click Repair] to solve all security issues"] + one_risk["version"] = 1 + one_risk["type"] = "vulnerability" + one_risk["package"] = pk_list + + # 存储结果 + self.data["risk"].append(one_risk) + self.data["is_autofix"].append(one_risk["m_name"]) + + # 扫描中发现的漏洞数 + self.discov_count += 1 + # 扫描中的动态分数 + self.score -= level + if self.score < 0: + self.score = 0 + # 扫描中的动态风险 + self.tmp_data['risk'].append(one_risk) + # 可修复项 + self.tmp_data['is_autofix'].append(one_risk["m_name"]) + + def new_system_scan(self): + ''' + 新版系统rpm软件包漏洞检测 + 提升扫描速度 + :param get: + :return: dict + ''' + + # 判断新漏洞库存不存在 + if not os.path.exists(self.new_vul_list): + return + + context = {"status": "Checking system software", "percentage": 0, "count": 0, "score": 100} + public.WriteFile(self.__path + '/bar.txt', json.dumps(context)) + sys_product = self.new_get_sys_product() + # 加载漏洞库文件 + try: + vul_json = json.loads(public.ReadFile(self.new_vul_list)) + packages_rule = vul_json['Packages'] + detail = vul_json['Detail'] + except Exception as e: + return + if sys_product is None: + return + + # 符合 + systemscan_result = {} + # 开始检测 + # 第一层遍历系统软件包 + for pk, ver in sys_product.items(): + # 跳过内核漏洞检测 + # if pk.startswith("kernel"): + # continue + # 是否有历史漏洞 + if pk in packages_rule: + # 第二层遍历比较软件包涉及的漏洞版本 + for rule in packages_rule[pk]: + # 判断软件包版本是否存在主版本号,有则漏洞版本一起保留主版本号,否则去掉漏洞版本的主版本号 + pk_ver, vul_ver = self.adjust_ver(ver, rule[0]) + cp_result = self.vercmp(pk_ver, vul_ver) + if cp_result == -1: + if rule[1] not in systemscan_result: + systemscan_result[rule[1]] = detail[str(rule[1])] + systemscan_result[rule[1]]["impact"] = [{"package": pk, "version": pk_ver, "vul_ver": vul_ver}] + else: + systemscan_result[rule[1]]["impact"].append({"package": pk, "version": pk_ver, "vul_ver": vul_ver}) + # public.WriteFile("/tmp/centos7_result.json", json.dumps(systemscan_result, indent=4)) + + # 为了兼容旧版本再次做处理 + for sr in systemscan_result.values(): + one_risk = {} + one_risk["title"] = "【{}】Linux system vulnerability security notice".format(sr["ref_id"]) + one_risk["data"] = "2023-12-08" + one_risk["help"] = "" + one_risk["ignore"] = False + level = self.new_severity_to_num(sr["severity"]) + one_risk["level"] = level + one_risk["m_name"] = sr["ref_id"] + pk_list = [] + # 判断是否有内核漏洞在里面 + is_kernel = False + soft_list = [] + for impact in sr["impact"]: + if impact["package"].startswith("kernel"): + is_kernel = True + continue + soft_list.append("{} Versions below {}".format(impact["package"]+"-"+impact["version"], impact["vul_ver"])) + pk_list.append(impact["package"]) + if is_kernel: + continue + one_risk["msg"] = "The following system software was found to have security vulnerabilities:
                                    {}
                                    Vulnerabilities involved:{}
                                    Refer to the official announcement for details:{}".format('
                                    '.join(soft_list), '、'.join(sr["cve"]),sr["ref_url"]) + one_risk["ps"] = "【{}】Linux system vulnerability security notice".format(sr["ref_id"]) + one_risk["remind"] = "Fixing vulnerabilities has certain risks, so it is recommended to take a good system snapshot to prevent system operation from being affected." + one_risk["status"] = False + one_risk["taking"] = 0.000001 + one_risk["tips"] = ["Update the software to a safe version according to the risk description", "Or click [One-click Repair] to solve all security issues"] + one_risk["version"] = 1 + one_risk["type"] = "vulnerability" + one_risk["package"] = pk_list + + # 存储结果 + self.data["risk"].append(one_risk) + self.data["is_autofix"].append(one_risk["m_name"]) + + # 扫描中发现的漏洞数 + self.discov_count += 1 + # 扫描中的动态分数 + self.score -= level + if self.score < 0: + self.score = 0 + # 扫描中的动态风险 + self.tmp_data['risk'].append(one_risk) + # 可修复项 + self.tmp_data['is_autofix'].append(one_risk["m_name"]) + + # result_json = { + # "vul_count": len(detail), + # "risk": [], + # "ignore": [], + # "all_check_time": "", + # "ignore_count": 0, + # "msg": "", + # "repair_count": {"all_count": 0, "today_vount": 0}, + # } + # for sr in systemscan_result.values(): + # one_risk = { + # "cve_id": "", + # "vuln_name": "", + # "vuln_time": "2021-12-16", + # "vuln_solution": "", + # "level": 1, + # "soft_name": {}, + # "vuln_version": "", + # "check_time": 1701910962, + # "reboot": "" + # } + # one_risk["cve_id"] = sr["ref_id"] + # one_risk["vuln_name"] = "【{}】Linux软件安全公告".format(sr["ref_id"]) + # one_risk["vuln_solution"] = "更新涉及软件补丁,具体信息参考官方公告{}".format(sr["ref_url"]) + # level = self.new_severity_to_num(sr["severity"]) + # one_risk["level"] = level + # # 处理受影响的软件包(为了兼容旧版本暂时这样) + # soft_name = {} + # vuln_version = "" + # for impact in sr["impact"]: + # soft_name[impact["package"]] = impact["version"] + # vuln_version = impact["vul_ver"] + # one_risk["soft_name"] = soft_name + # one_risk["vuln_version"] = vuln_version + # risk_list.append(one_risk) + # + # # 扫描中发现的漏洞数 + # self.discov_count += 1 + # # 扫描中的动态分数 + # self.score -= level + # if self.score < 0: + # self.score = 0 + # # 扫描中的动态风险 + # self.tmp_data['risk'].append(one_risk) + # # 可修复项 + # self.tmp_data['is_autofix'].append(one_risk["cve_id"]) + # + # result_json["risk"] = risk_list + # public.WriteFile(self.__path + '/system_scan_time', int(time.time())) + # public.WriteFile(self._vuln_result, json.dumps(result_json)) + + def is_file_too_old(self, file_path, days): + """ + 判断文件是否过于陈旧 + :param file_path: 文件路径 + :param days: 超过的天数 + :return: bool + """ + mtime = os.path.getmtime(file_path) + # 不存在直接返回True + if mtime is None: + return True + mod_time = datetime.datetime.fromtimestamp(mtime) + days_old = datetime.datetime.now() - mod_time + return days_old.days > days + + def adjust_ver(self, ver_a, ver_b): + ''' + 确保两个版本主版本号统一,一方存在另一方不存在则删除主版本,要么都有,要么都没有 + ''' + if ":" in ver_a: + if ":" in ver_b: + ver_1 = ver_a + else: + ver_1 = ver_a.split(":")[1] + ver_2 = ver_b + else: + if ":" in ver_b: + ver_2 = ver_b.split(":")[1] + else: + ver_2 = ver_b + ver_1 = ver_a + return ver_1, ver_2 + + def new_severity_to_num(self, severity): + ''' + 将漏洞级别转变成数字 + ''' + if severity == "Critical": + return 3 + elif severity == "Important": + return 2 + elif severity == "Moderate": + return 1 + elif severity == "Low": + return 1 + elif severity == "High": + return 3 + elif severity == "Medium": + return 2 + else: + return 2 + + # def new_rpmvercmp(self, sys_ver, vul_ver): + # output, err = public.ExecShell("rpmdev-vercmp {} {}".format(sys_ver, vul_ver)) + # if err != '': + # return 1 + # output = output.strip() + # if output == "{} > {}".format(sys_ver, vul_ver): + # return 1 + # elif output == "{} == {}".format(sys_ver, vul_ver): + # return 0 + # elif output == "{} < {}".format(sys_ver, vul_ver): + # return -1 + # else: + # return 1 + + +class Dpkg: + def __init__(self): + self._fileinfo = None + self._control_str = None + self._headers = None + self._message = None + self._upstream_version = None + self._debian_revision = None + self._epoch = None + + @staticmethod + def get_epoch(version_str): + try: + e_index = version_str.index(":") + except ValueError: + return 0, version_str + + try: + epoch = int(version_str[0:e_index]) + except ValueError as ex: + print(f"Corrupt dpkg version '{version_str}': epochs can only be ints, and " + "epochless versions cannot use the colon character.") + return epoch, version_str[e_index + 1:] + + @staticmethod + def get_upstream(version_str): + try: + d_index = version_str.rindex("-") + except ValueError: + return version_str, "0" + + return version_str[0:d_index], version_str[d_index + 1:] + + @staticmethod + def split_full_version(version_str): + epoch, full_ver = Dpkg.get_epoch(version_str) + upstream_rev, debian_rev = Dpkg.get_upstream(full_ver) + return epoch, upstream_rev, debian_rev + + @staticmethod + def get_alphas(revision_str): + for i, char in enumerate(revision_str): + if char.isdigit(): + if i == 0: + return "", revision_str + return revision_str[0:i], revision_str[i:] + return revision_str, "" + + @staticmethod + def get_digits(revision_str): + if not revision_str: + return 0, "" + for i, char in enumerate(revision_str): + if not char.isdigit(): + if i == 0: + return 0, revision_str + return int(revision_str[0:i]), revision_str[i:] + return int(revision_str), "" + + @staticmethod + def listify(revision_str): + result = [] + while revision_str: + rev_1, remains = Dpkg.get_alphas(revision_str) + rev_2, remains = Dpkg.get_digits(remains) + result.extend([rev_1, rev_2]) + revision_str = remains + return result + + @staticmethod + def dstringcmp(a, b): + if a == b: + return 0 + try: + for i, char in enumerate(a): + if char == b[i]: + continue + if char == "~": + return -1 + if b[i] == "~": + return 1 + if char.isalpha() and not b[i].isalpha(): + return -1 + if not char.isalpha() and b[i].isalpha(): + return 1 + if ord(char) > ord(b[i]): + return 1 + if ord(char) < ord(b[i]): + return -1 + except IndexError: + if char == "~": + return -1 + return 1 + if b[len(a)] == "~": + return 1 + return -1 + + @staticmethod + def compare_revision_strings(rev1, rev2): + if rev1 == rev2: + return 0 + list1 = Dpkg.listify(rev1) + list2 = Dpkg.listify(rev2) + if list1 == list2: + return 0 + try: + for i, item in enumerate(list1): + if i >= len(list2): + raise IndexError + if not isinstance(item, list2[i].__class__): + print(f"Cannot compare '{item}' to {list2[i]}, something has gone horribly awry.") + if item == list2[i]: + continue + if isinstance(item, int): + if item > list2[i]: + return 1 + if item < list2[i]: + return -1 + else: + return Dpkg.dstringcmp(item, list2[i]) + except IndexError: + if list1[len(list2)][0][0] == "~": + return -1 + return 1 + if list2[len(list1)][0][0] == "~": + return 1 + return -1 + + @staticmethod + def compare_versions(ver1, ver2): + if ver1 == ver2: + return 0 + epoch1, upstream1, debian1 = Dpkg.split_full_version(str(ver1)) + epoch2, upstream2, debian2 = Dpkg.split_full_version(str(ver2)) + + if epoch1 < epoch2: + return -1 + if epoch1 > epoch2: + return 1 + + upstr_res = Dpkg.compare_revision_strings(upstream1, upstream2) + if upstr_res != 0: + return upstr_res + + debian_res = Dpkg.compare_revision_strings(debian1, debian2) + if debian_res != 0: + return debian_res + + return 0 + +if __name__ == "__main__": + # st = time.time() + panel = panelWarning() + # panel.new_system_scan() + public.WriteFile('/www/server/panel/data/warning/resultresult.json', json.dumps(panel._get_list())) + # et = time.time() + + + + + + +# #coding: utf-8 +# # +------------------------------------------------------------------- +# # | aaPanel +# # +------------------------------------------------------------------- +# # | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# # +------------------------------------------------------------------- +# # | Author: hwliang <2020-08-04> +# # +------------------------------------------------------------------- +# +# import os,sys,json,time,public +# +# class panelWarning: +# __path = '/www/server/panel/data/warning' +# __ignore = __path + '/ignore' +# __result = __path + '/result' +# __risk = __path + '/risk' +# def __init__(self): +# if not os.path.exists(self.__ignore): +# os.makedirs(self.__ignore,384) +# if not os.path.exists(self.__result): +# os.makedirs(self.__result,384) +# if not os.path.exists(self.__risk): +# os.makedirs(self.__risk, 384) +# +# def get_list(self,args): +# p = public.get_modules('class/safe_warning') +# data = { +# 'security':[], +# 'risk':[], +# 'ignore':[] +# } +# +# for m_name in p.__dict__.keys(): +# ignore_file = self.__ignore + '/' + m_name + '.pl' +# # 忽略的检查项 +# if p[m_name]._level == 0: continue +# +# m_info = { +# 'title': p[m_name]._title, +# 'm_name': m_name, +# 'ps': p[m_name]._ps, +# 'version': p[m_name]._version, +# 'level': p[m_name]._level, +# 'ignore': p[m_name]._ignore, +# 'date': p[m_name]._date, +# 'tips': p[m_name]._tips, +# 'help': p[m_name]._help +# } +# result_file = self.__result + '/' + m_name + '.pl' +# +# try: +# s_time = time.time() +# m_info['status'],m_info['msg'] = p[m_name].check_run() +# m_info['taking'] = round(time.time() - s_time,6) +# m_info['check_time'] = int(time.time()) +# public.writeFile(result_file,json.dumps([m_info['status'],m_info['msg'],m_info['check_time'],m_info['taking']],)) +# except: +# continue +# +# m_info['ignore'] = os.path.exists(ignore_file) +# if m_info['ignore']: +# data['ignore'].append(m_info) +# else: +# if m_info['status']: +# data['security'].append(m_info) +# else: +# risk_file = self.__risk + '/' + m_name + '.pl' +# public.writeFile(risk_file, json.dumps(m_info)) +# data['risk'].append(m_info) +# +# data['risk'] = sorted(data['risk'],key=lambda x: x['level'],reverse=True) +# data['security'] = sorted(data['security'],key=lambda x: x['level'],reverse=True) +# data['ignore'] = sorted(data['ignore'],key=lambda x: x['level'],reverse=True) +# # 获取支持一键修复的列表 +# try: +# is_autofix = public.read_config("safe_autofix") +# except: +# is_autofix = [] +# data['is_autofix'] = is_autofix +# return data +# +# +# def sync_rule(self): +# ''' +# @name 从云端同步规则 +# @author hwliang<2020-08-05> +# @return void +# ''' +# # try: +# # dep_path = '/www/server/panel/class/safe_warning' +# # local_version_file = self.__path + '/version.pl' +# # last_sync_time = local_version_file = self.__path + '/last_sync.pl' +# # if os.path.exists(dep_path): +# # if os.path.exists(last_sync_time): +# # if int(public.readFile(last_sync_time)) > time.time(): +# # return +# # else: +# # if os.path.exists(local_version_file): os.remove(local_version_file) +# +# # download_url = public.get_url() +# # version_url = download_url + '/install/warning/version.txt' +# # cloud_version = public.httpGet(version_url) +# # if cloud_version: cloud_version = cloud_version.strip() +# +# # local_version = public.readFile(local_version_file) +# # if local_version: +# # if cloud_version == local_version: +# # return +# +# # tmp_file = '/tmp/bt_safe_warning.zip' +# # public.ExecShell('wget -O {} {} -T 5'.format(tmp_file,download_url + '/install/warning/safe_warning.zip')) +# # if not os.path.exists(tmp_file): +# # return +# +# # if os.path.getsize(tmp_file) < 2129: +# # os.remove(tmp_file) +# # return +# +# # if not os.path.exists(dep_path): +# # os.makedirs(dep_path,384) +# # public.ExecShell("unzip -o {} -d {}/ >/dev/null".format(tmp_file,dep_path)) +# # public.writeFile(local_version_file,cloud_version) +# # public.writeFile(last_sync_time,str(int(time.time() + 7200))) +# # if os.path.exists(tmp_file): os.remove(tmp_file) +# # public.ExecShell("chmod -R 600 {}".format(dep_path)) +# # except: +# # pass +# +# +# +# def set_ignore(self,args): +# ''' +# @name 设置指定项忽略状态 +# @author hwliang<2020-08-04> +# @param dict_obj { +# m_name 模块名称 +# } +# @return dict +# ''' +# m_name = args.m_name.strip() +# ignore_file = self.__ignore + '/' + m_name + '.pl' +# if os.path.exists(ignore_file): +# os.remove(ignore_file) +# else: +# public.writeFile(ignore_file,'1') +# return public.returnMsg(True,'Successfully set!') +# +# def check_find(self, args): +# ''' +# @name 检测指定项 +# @author hwliang<2020-08-04> +# @param dict_obj { +# m_name 模块名称 +# } +# @return dict +# ''' +# try: +# m_name = args.m_name.strip() +# p = public.get_modules('class/safe_warning') +# m_info = { +# 'title': p[m_name]._title, +# 'm_name': m_name, +# 'ps': p[m_name]._ps, +# 'version': p[m_name]._version, +# 'level': p[m_name]._level, +# 'ignore': p[m_name]._ignore, +# 'date': p[m_name]._date, +# 'tips': p[m_name]._tips, +# 'help': p[m_name]._help +# } +# +# # 解决已经在忽略列表中,但是如果仍然需要检查的话可以检查 +# ignore_file = self.__ignore + '/' + m_name + '.pl' +# if os.path.exists(ignore_file): +# from cachelib import SimpleCache +# cache = SimpleCache(5000) +# ikey = 'warning_list' +# cache.delete(ikey) +# os.remove(ignore_file) +# +# result_file = self.__result + '/' + m_name + '.pl' +# s_time = time.time() +# m_info['status'], m_info['msg'] = p[m_name].check_run() +# m_info['taking'] = round(time.time() - s_time, 4) +# m_info['check_time'] = int(time.time()) +# public.writeFile(result_file, json.dumps( +# [m_info['status'], m_info['msg'], m_info['check_time'], m_info['taking']])) +# return public.returnMsg(True, 'Retested') +# except: +# return public.returnMsg(False, 'Detection failed') diff --git a/class_v2/password_v2.py b/class_v2/password_v2.py new file mode 100644 index 00000000..68b43a6d --- /dev/null +++ b/class_v2/password_v2.py @@ -0,0 +1,218 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: lkqiang +# +------------------------------------------------------------------- +# +-------------------------------------------------------------------- +# | 密码管理 +# +-------------------------------------------------------------------- +import sys, os +if sys.version_info[0] == 2: + reload(sys) + sys.setdefaultencoding('utf-8') +os.chdir('/www/server/panel') +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import re +import public,data,database,config + +class password: + def __init__(self): + self.__data=data.data() + self.__database=database.database() + self.__config=config.config() + + #设置面板密码 + def set_panel_password(self,get): + get.password1=get.password + get.password2 = get.password + data=self.__config.setPassword(get) + return data + + #查看面板用户名 + def get_panel_username(self,get): + data=public.M('users').where("id=?", (1,)).getField('username') + if data: + return data + else: + return False + + # 设置root 密码 + def set_root_password(self,get): + public.ExecShell("echo"+get.user+":"+get.password+"|chpasswd") + return True + + #查看mysql_root密码 + def get_mysql_root(self,get): + password = public.M('config').where("id=?",(1,)).getField('mysql_root') + return public.returnMsg(True, password) + + #设置mysql_root 密码 + def set_mysql_password(self,get): + if 'password' in get: + resutl=self.__database.SetupPassword(get) + return resutl + else: + return public.returnMsg(False, 'password参数不能为空') + + + # MySQL 的其他账户设置 + #获取其他mysql的信息 + def get_databses(self,get): + data=public.M('databases').select() + return public.returnMsg(True, data) + + # 修改MySQL 其他账户的密码 + def rem_mysql_pass(self,get): + ''' + 参数 三个 + id 数据库ID, name:数据库名称, password:数据库密码 + ''' + data=self.__database.ResDatabasePassword(get) + return data + + # 修改其他Mysql 账户的权限 + def set_mysql_access(self,get): + ''' + 参数 三个 + name:数据库名称, dataAccess: 权限 access 权限 + ''' + data=self.__database.SetDatabaseAccess(get) + return data + + + #################### SSH 的基础设置#################### + + # 开启密码登陆 + def SetPassword(self, get): + ssh_password = '\n#?PasswordAuthentication\\s\\w+' + file = public.readFile('/etc/ssh/sshd_config') + if len(re.findall(ssh_password, file)) == 0: + file_result = file + '\nPasswordAuthentication yes' + else: + file_result = re.sub(ssh_password, '\nPasswordAuthentication yes', file) + self.Wirte('/etc/ssh/sshd_config', file_result) + self.RestartSsh() + return public.returnMsg(True, '开启成功') + + # 设置ssh_key + def SetSshKey(self, get): + '''''' + type_list = ['rsa', 'dsa'] + ssh_type = ['yes', 'no'] + ssh = get.ssh + if not ssh in ssh_type: return public.returnMsg(False, 'ssh选项失败') + type = get.type + if not type in type_list: return public.returnMsg(False, '加密方式错误') + file = ['/root/.ssh/id_rsa.pub', '/root/.ssh/id_rsa', '/root/.ssh/authorized_keys'] + for i in file: + if os.path.exists(i): + os.remove(i) + public.ExecShell("ssh-keygen -t %s -P '' -f ~/.ssh/id_rsa |echo y" % type) + if os.path.exists(file[0]): + public.ExecShell('cat %s >%s && chmod 600 %s' % (file[0], file[-1], file[-1])) + rec = '\n#?RSAAuthentication\\s\\w+' + rec2 = '\n#?PubkeyAuthentication\\s\\w+' + file = public.readFile('/etc/ssh/sshd_config') + if len(re.findall(rec, file)) == 0: file = file + '\nRSAAuthentication yes' + if len(re.findall(rec2, file)) == 0: file = file + '\nPubkeyAuthentication yes' + file_ssh = re.sub(rec, '\nRSAAuthentication yes', file) + file_result = re.sub(rec2, '\nPubkeyAuthentication yes', file_ssh) + if ssh == 'no': + ssh_password = '\n#?PasswordAuthentication\\s\\w+' + if len(re.findall(ssh_password, file_result)) == 0: + file_result = file_result + '\nPasswordAuthentication no' + else: + file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file_result) + self.Wirte('/etc/ssh/sshd_config', file_result) + self.RestartSsh() + return public.returnMsg(True, '开启成功') + else: + return public.returnMsg(False, '开启失败') + + + # 关闭sshkey + def StopKey(self, get): + file = ['/root/.ssh/id_rsa.pub', '/root/.ssh/id_rsa', '/root/.ssh/authorized_keys'] + rec = '\n#?RSAAuthentication\\s\\w+' + rec2 = '\n#?PubkeyAuthentication\\s\\w+' + file = public.readFile('/etc/ssh/sshd_config') + file_ssh = re.sub(rec, '\n#RSAAuthentication no', file) + file_result = re.sub(rec2, '\n#PubkeyAuthentication no', file_ssh) + self.Wirte('/etc/ssh/sshd_config', file_result) + self.SetPassword(get) + self.RestartSsh() + return public.returnMsg(True, '关闭成功') + # 读取配置文件 获取当前状态 + + def GetConfig(self, get): + result = {} + file = public.readFile('/etc/ssh/sshd_config') + rec = '\n#?RSAAuthentication\\s\\w+' + pubkey = '\n#?PubkeyAuthentication\\s\\w+' + ssh_password = '\nPasswordAuthentication\\s\\w+' + ret = re.findall(ssh_password, file) + if not ret: + result['password'] = 'no' + else: + if ret[-1].split()[-1] == 'yes': + result['password'] = 'yes' + else: + result['password'] = 'no' + pubkey = re.findall(pubkey, file) + if not pubkey: + result['pubkey'] = 'no' + else: + if pubkey[-1].split()[-1] == 'no': + result['pubkey'] = 'no' + else: + result['pubkey'] = 'yes' + rsa_auth = re.findall(rec, file) + if not rsa_auth: + result['rsa_auth'] = 'no' + else: + if rsa_auth[-1].split()[-1] == 'no': + result['rsa_auth'] = 'no' + else: + result['rsa_auth'] = 'yes' + return result + + # 关闭密码方式 + def StopPassword(self, get): + file = public.readFile('/etc/ssh/sshd_config') + ssh_password = '\n#?PasswordAuthentication\\s\\w+' + file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file) + self.Wirte('/etc/ssh/sshd_config', file_result) + self.RestartSsh() + return public.returnMsg(True, '关闭成功') + + #显示key文件 + def GetKey(self, get): + file = '/root/.ssh/id_rsa' + if not os.path.exists(file): return public.returnMsg(True, '') + ret = public.readFile(file) + return public.returnMsg(True, ret) + + # 下载 + def Download(self, get): + if os.path.exists('/root/.ssh/id_rsa'): + ret = '/download?filename=/root/.ssh/id_rsa' + return public.returnMsg(True, ret) + + # 写入配置文件 + def Wirte(self, file, ret): + result = public.writeFile(file, ret) + return result + + def RestartSsh(self): + version = public.readFile('/etc/redhat-release') + act = 'restart' + if not os.path.exists('/etc/redhat-release'): + public.ExecShell('service ssh ' + act) + elif version.find(' 7.') != -1: + public.ExecShell("systemctl " + act + " sshd.service") + else: + public.ExecShell("/etc/init.d/sshd " + act) diff --git a/class_v2/plugin_auth_v2.py b/class_v2/plugin_auth_v2.py new file mode 100644 index 00000000..557e13d2 --- /dev/null +++ b/class_v2/plugin_auth_v2.py @@ -0,0 +1,97 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +#+-------------------------------------------------------------------- +#| 插件认证模块 +#+-------------------------------------------------------------------- + + +import public +import PluginLoader + +class Plugin: + __plugin_name = None + __is_php = False + __dict__ = None + __obj_dict = {} + + + def __init__(self,init_plugin_name = None): + ''' + @name 实例化插件对像 + @author hwliang<2021-06-15> + @param init_plugin_name 插件名称 + @return Plguin + ''' + if not init_plugin_name is False: + if not init_plugin_name: + raise ValueError('参数错误,plugin_name少需要一个有效参数') + self.__plugin_name = init_plugin_name + + def get_plugin_list(self,upgrade_force = False): + ''' + @name 获取插件列表 + @author hwliang<2021-06-15> + @param upgrade_force 是否强制重新获取列表 + @return dict + ''' + force = 1 if upgrade_force else 0 + return PluginLoader.get_plugin_list(force) + + + def exec_fun(self,get_args,def_name = None): + ''' + @name 执行指定方法 + @author hwliang<2021-06-16> + @param def_name 方法名称 + @param get_args POST/GET参数对像 + @return mixed + ''' + if not def_name: + def_name = get_args.get("s","") + else: + if not 's' in get_args: + get_args.s = def_name + + res = PluginLoader.plugin_run(self.__plugin_name,def_name,get_args) + if isinstance(res,dict): + if 'status' in res and res['status'] == False and 'msg' in res: + if isinstance(res['msg'],str): + if res['msg'].find('Traceback ') != -1: + raise public.PanelError(res['msg']) + return res + + def get_fun(self,def_name): + ''' + @name 获取函对像 + @author hwliang<2021-06-28> + @param def_name 函数名称 + @return func_object + ''' + if def_name in self.__obj_dict.keys(): + return self.__obj_dict[def_name] + get_args = public.dict_obj() + get_args.plugin_get_object = 1 + return PluginLoader.plugin_run(self.__plugin_name,def_name,get_args) + + + def isdef(self,def_name): + ''' + @name 指定方法是否存在 + @author hwliang<2021-06-16> + @param def_name 方法名称 + @return bool + ''' + if self.__is_php: return True + self.__obj_dict[def_name] = self.get_fun(def_name) + return True if self.__obj_dict[def_name] else False + + def __dir__(self): + return '' + diff --git a/class_v2/plugin_deployment_v2.py b/class_v2/plugin_deployment_v2.py new file mode 100644 index 00000000..1eb40cde --- /dev/null +++ b/class_v2/plugin_deployment_v2.py @@ -0,0 +1,519 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +#+-------------------------------------------------------------------- +#| 自动部署网站 +#+-------------------------------------------------------------------- + +import public,json,os,time,sys,re +from BTPanel import session,cache +class obj: id=0 +class plugin_deployment: + __setupPath = 'data' + __panelPath = '/www/server/panel' + logPath = 'data/deployment_speed.json' + __tmp = '/www/server/panel/temp/' + timeoutCount = 0 + oldTime = 0 + _speed_key = 'dep_download_speed' + + #获取列表 + def GetList(self,get): + self.GetCloudList(get) + jsonFile = self.__panelPath + '/data/deployment_list.json' + if not os.path.exists(jsonFile): return public.returnMsg(False,'Profile does not exist!') + data = {} + data = self.get_input_list(json.loads(public.readFile(jsonFile))) + + if not hasattr(get,'type'): + get.type = 0 + else: + get.type = int(get.type) + if not hasattr(get,'search'): + search = None + m = 0 + else: + if sys.version_info[0] == 2: + search = get.search.encode('utf-8').lower() + else: + search = get.search.lower() + m = 1 + + tmp = [] + for d in data['list']: + i=0 + if get.type > 0: + if get.type == d['type']: i+=1 + else: + i+=1 + if search: + if d['name'].lower().find(search) != -1: i+=1 + if d['title'].lower().find(search) != -1: i+=1 + if d['ps'].lower().find(search) != -1: i+=1 + if get.type > 0 and get.type != d['type']: i -= 1 + + if i>m: + del(d['versions'][0]['download']) + del(d['versions'][0]['md5']) + d = self.get_icon(d) + tmp.append(d) + + data['list'] = tmp + return data + + #获取图标 + def get_icon(self,pinfo): + path = '/www/server/panel/BTPanel/static/img/dep_ico' + if not os.path.exists(path): os.makedirs(path,384) + filename = "%s/%s.png" % (path, pinfo['name']) + m_uri = pinfo['min_image'] + pinfo['min_image'] = '/static/img/dep_ico/%s.png' % pinfo['name'] + if sys.version_info[0] == 2: filename = filename.encode('utf-8') + if os.path.exists(filename): + if os.path.getsize(filename) > 100: return pinfo + public.ExecShell("wget -O " + filename + ' https://www.bt.cn' + m_uri + " &") + return pinfo + + #获取插件列表 + def GetDepList(self,get): + jsonFile = self.__setupPath + '/deployment_list.json' + if not os.path.exists(jsonFile): return public.returnMsg(False,'Profile does not exist!') + data = {} + data = json.loads(public.readFile(jsonFile)) + return self.get_input_list(data) + + #获取本地导入的插件 + def get_input_list(self,data): + try: + jsonFile = self.__setupPath + '/deployment_list_other.json' + if not os.path.exists(jsonFile): return data + i_data = json.loads(public.readFile(jsonFile)) + for d in i_data: + data['list'].append(d) + return data + except:return data + + #从云端获取列表 + def GetCloudList(self,get): + try: + jsonFile = self.__setupPath + '/deployment_list.json' + if not 'package' in session or not os.path.exists(jsonFile) or hasattr(get,'force'): + downloadUrl = 'http://www.bt.cn/api/panel/get_deplist' + pdata = public.get_pdata() + tmp = json.loads(public.httpPost(downloadUrl,pdata,3)) + if not tmp: return public.returnMsg(False,'Failed to get from the cloud!') + public.writeFile(jsonFile,json.dumps(tmp)) + session['package'] = True + return public.returnMsg(True,'Update completed!') + return public.returnMsg(True,'No need to update!') + except: + return public.returnMsg(False,'Failed to get from the cloud!') + + + + #导入程序包 + def AddPackage(self,get): + jsonFile = self.__setupPath + '/deployment_list_other.json' + if not os.path.exists(jsonFile): + public.writeFile(jsonFile,'[]') + pinfo = {} + pinfo['name'] = get.name + pinfo['title'] = get.title + pinfo['version'] = get.version + pinfo['php'] = get.php + pinfo['ps'] = get.ps + pinfo['official'] = '#' + pinfo['sort'] = 1000 + pinfo['min_image'] = '' + pinfo['id'] = 0 + pinfo['type'] = 100 + pinfo['enable_functions'] = get.enable_functions + pinfo['author'] = 'Import from local' + from werkzeug.utils import secure_filename + from flask import request + f = request.files['dep_zip'] + s_path = self.__panelPath + '/package' + if not os.path.exists(s_path): os.makedirs(s_path,384) + s_file = s_path + '/' + pinfo['name'] + '.zip' + if os.path.exists(s_file): os.remove(s_file) + f.save(s_file) + os.chmod(s_file,384) + pinfo['versions'] = [] + version = {"cpu_limit": 1, + "dependnet": "", + "m_version":pinfo['version'], + "mem_limit": 32, + "os_limit": 0, + "size": os.path.getsize(s_file), + "version": "0", + "download":"", + "version_msg": "test2"} + version['md5'] = self.GetFileMd5(s_file) + pinfo['versions'].append(version) + data = json.loads(public.readFile(jsonFile)) + is_exists = False + for i in range(len(data)): + if data[i]['name'] == pinfo['name']: + data[i] = pinfo + is_exists = True + + if not is_exists: data.append(pinfo) + + public.writeFile(jsonFile,json.dumps(data)) + return public.returnMsg(True,'Import Success!') + + #取本地包信息 + def GetPackageOther(self,get): + p_name = get.p_name + jsonFile = self.__setupPath + '/deployment_list_other.json' + if not os.path.exists(jsonFile): public.returnMsg(False,'could not find [%s]' % p_name) + data = json.loads(public.readFile(jsonFile)) + + for i in range(len(data)): + if data[i]['name'] == p_name: return data[i] + return public.returnMsg(False,'could not find [%s]' % p_name) + + + #删除程序包 + def DelPackage(self,get): + jsonFile = self.__setupPath + '/deployment_list_other.json' + if not os.path.exists(jsonFile): return public.returnMsg(False,'Profile does not exist!') + + data = {} + data = json.loads(public.readFile(jsonFile)) + + tmp = [] + for d in data: + if d['name'] == get.dname: + s_file = self.__panelPath + '/package/' + d['name'] + '.zip' + if os.path.exists(s_file): os.remove(s_file) + continue + tmp.append(d) + + data = tmp + public.writeFile(jsonFile,json.dumps(data)) + return public.returnMsg(True,'Successfully deleted!') + + #下载文件 + def DownloadFile(self,url,filename): + try: + path = os.path.dirname(filename) + if not os.path.exists(path): os.makedirs(path) + import urllib,socket + socket.setdefaulttimeout(10) + self.pre = 0 + self.oldTime = time.time() + if sys.version_info[0] == 2: + urllib.urlretrieve(url,filename=filename,reporthook= self.DownloadHook) + else: + urllib.request.urlretrieve(url,filename=filename,reporthook= self.DownloadHook) + self.WriteLogs(json.dumps({'name':'Download File','total':0,'used':0,'pre':0,'speed':0})) + except: + if self.timeoutCount > 5: return + self.timeoutCount += 1 + time.sleep(5) + self.DownloadFile(url,filename) + + #下载文件进度回调 + def DownloadHook(self,count, blockSize, totalSize): + used = count * blockSize + pre1 = int((100.0 * used / totalSize)) + if self.pre != pre1: + dspeed = used / (time.time() - self.oldTime) + speed = {'name':'Download File','total':totalSize,'used':used,'pre':self.pre,'speed':dspeed} + self.WriteLogs(json.dumps(speed)) + self.pre = pre1 + + #写输出日志 + def WriteLogs(self,logMsg): + fp = open(self.logPath,'w+') + fp.write(logMsg) + fp.close() + + #一键安装网站程序 + #param string name 程序名称 + #param string site_name 网站名称 + #param string php_version PHP版本 + def SetupPackage(self,get): + name = get.dname + site_name = get.site_name + php_version = get.php_version + #取基础信息 + find = public.M('sites').where('name=?',(site_name,)).field('id,path,name').find() + if not 'path' in find: + return public.returnMsg(False, 'Site not exist!') + path = find['path'] + if path.replace('//','/') == '/': return public.returnMsg(False,'Dangerous website root directory!') + #获取包信息 + pinfo = self.GetPackageInfo(name) + id = pinfo['id'] + if not pinfo: return public.returnMsg(False,'The specified package does not exist.!') + + #检查本地包 + self.WriteLogs(json.dumps({'name':'Verifying package...','total':0,'used':0,'pre':0,'speed':0})) + pack_path = self.__panelPath + '/package' + if not os.path.exists(pack_path): os.makedirs(pack_path,384) + packageZip = pack_path + '/'+ name + '.zip' + isDownload = False + if os.path.exists(packageZip): + md5str = self.GetFileMd5(packageZip) + if md5str != pinfo['versions'][0]['md5']: isDownload = True + else: + isDownload = True + + #下载文件 + if isDownload: + self.WriteLogs(json.dumps({'name':'Downloading file ...','total':0,'used':0,'pre':0,'speed':0})) + if pinfo['versions'][0]['download']: self.DownloadFile('http://www.bt.cn/api/Pluginother/get_file?fname=' + pinfo['versions'][0]['download'], packageZip) + + if not os.path.exists(packageZip): return public.returnMsg(False,'File download failed!' + packageZip) + + pinfo = self.set_temp_file(packageZip,path) + if not pinfo: return public.returnMsg(False,'Cannot find [aaPanel Auto Deployment Configuration File] in the installation package') + + #设置权限 + self.WriteLogs(json.dumps({'name':'Setting permissions','total':0,'used':0,'pre':0,'speed':0})) + public.ExecShell('chmod -R 755 ' + path) + public.ExecShell('chown -R www.www ' + path) + if pinfo['chmod']: + for chm in pinfo['chmod']: + public.ExecShell('chmod -R ' + str(chm['mode']) + ' ' + (path + '/' + chm['path']).replace('//','/')) + + #安装PHP扩展 + self.WriteLogs(json.dumps({'name':'Install the necessary PHP extensions','total':0,'used':0,'pre':0,'speed':0})) + import files + mfile = files.files(); + if type(pinfo['php_ext']) != list : pinfo['php_ext'] = pinfo['php_ext'].strip().split(',') + for ext in pinfo['php_ext']: + if ext == 'pathinfo': + import config + con = config.config() + get.version = php_version + get.type = 'on' + con.setPathInfo(get) + else: + get.name = ext + get.version = php_version + get.type = '1' + mfile.InstallSoft(get) + + #解禁PHP函数 + if 'enable_functions' in pinfo: + try: + if type(pinfo['enable_functions']) == str : pinfo['enable_functions'] = pinfo['enable_functions'].strip().split(',') + php_f = public.GetConfigValue('setup_path') + '/php/' + php_version + '/etc/php.ini' + php_c = public.readFile(php_f) + rep = "disable_functions\\s*=\\s{0,1}(.*)\n" + tmp = re.search(rep,php_c).groups() + disable_functions = tmp[0].split(',') + for fun in pinfo['enable_functions']: + fun = fun.strip() + if fun in disable_functions: disable_functions.remove(fun) + disable_functions = ','.join(disable_functions) + php_c = re.sub(rep, 'disable_functions = ' + disable_functions + "\n", php_c) + public.writeFile(php_f,php_c) + public.phpReload(php_version) + except:pass + + + #执行额外shell进行依赖安装 + self.WriteLogs(json.dumps({'name':'Execute extra SHELL','total':0,'used':0,'pre':0,'speed':0})) + if os.path.exists(path+'/install.sh'): + public.ExecShell('cd '+path+' && bash ' + 'install.sh ' + find['name'] + " &> install.log") + public.ExecShell('rm -f ' + path+'/install.sh') + + #是否执行Composer + if os.path.exists(path + '/composer.json'): + self.WriteLogs(json.dumps({'name':'Execute Composer','total':0,'used':0,'pre':0,'speed':0})) + if not os.path.exists(path + '/composer.lock'): + execPHP = '/www/server/php/' + php_version +'/bin/php' + if execPHP: + if public.get_url().find('125.88'): + public.ExecShell('cd ' +path+' && '+execPHP+' /usr/bin/composer config repo.packagist composer https://packagist.phpcomposer.com') + import panelSite + phpini = '/www/server/php/' + php_version + '/etc/php.ini' + phpiniConf = public.readFile(phpini) + phpiniConf = phpiniConf.replace('proc_open,proc_get_status,','') + public.writeFile(phpini,phpiniConf) + public.ExecShell('nohup cd '+path+' && '+execPHP+' /usr/bin/composer install -vvv > /tmp/composer.log 2>&1 &') + + #写伪静态 + self.WriteLogs(json.dumps({'name':'Set URL rewrite','total':0,'used':0,'pre':0,'speed':0})) + swfile = path + '/nginx.rewrite' + if os.path.exists(swfile): + rewriteConf = public.readFile(swfile) + dwfile = self.__panelPath + '/vhost/rewrite/' + site_name + '.conf' + public.writeFile(dwfile,rewriteConf) + + swfile = path + '/.htaccess' + if os.path.exists(swfile): + swpath = (path + '/'+ pinfo['run_path'] + '/.htaccess').replace('//','/') + if pinfo['run_path'] != '/' and not os.path.exists(swpath): + public.writeFile(swpath, public.readFile(swfile)) + + + #删除伪静态文件 + public.ExecShell("rm -f " + path + '/*.rewrite') + + #删除多余文件 + rm_file = path + '/index.html' + if os.path.exists(rm_file): + rm_file_body = public.readFile(rm_file) + if rm_file_body.find('panel-heading') != -1: os.remove(rm_file) + + #设置运行目录 + self.WriteLogs(json.dumps({'name':'Set the run directory','total':0,'used':0,'pre':0,'speed':0})) + if pinfo['run_path'] != '/': + import panelSite + siteObj = panelSite.panelSite() + mobj = obj() + mobj.id = find['id'] + mobj.runPath = pinfo['run_path'] + siteObj.SetSiteRunPath(mobj) + + #导入数据 + self.WriteLogs(json.dumps({'name':'Import database','total':0,'used':0,'pre':0,'speed':0})) + if os.path.exists(path+'/import.sql'): + databaseInfo = public.M('databases').where('pid=?',(find['id'],)).field('username,password').find() + if databaseInfo: + public.ExecShell('/www/server/mysql/bin/mysql -u' + databaseInfo['username'] + ' -p' + databaseInfo['password'] + ' ' + databaseInfo['username'] + ' < ' + path + '/import.sql') + public.ExecShell('rm -f ' + path + '/import.sql') + siteConfigFile = (path + '/' + pinfo['db_config']).replace('//','/') + if os.path.exists(siteConfigFile): + siteConfig = public.readFile(siteConfigFile) + siteConfig = siteConfig.replace('BT_DB_USERNAME',databaseInfo['username']) + siteConfig = siteConfig.replace('BT_DB_PASSWORD',databaseInfo['password']) + siteConfig = siteConfig.replace('BT_DB_NAME',databaseInfo['username']) + public.writeFile(siteConfigFile,siteConfig) + + #清理文件和目录 + self.WriteLogs(json.dumps({'name':'清理多余的文件','total':0,'used':0,'pre':0,'speed':0})) + if type(pinfo['remove_file']) == str : pinfo['remove_file'] = pinfo['remove_file'].strip().split(',') + print(pinfo['remove_file']) + for f_path in pinfo['remove_file']: + if not f_path: continue + filename = (path + '/' + f_path).replace('//','/') + if os.path.exists(filename): + if not os.path.isdir(filename): + if f_path.find('.user.ini') != -1: + public.ExecShell("chattr -i " + filename) + os.remove(filename) + else: + public.ExecShell("rm -rf " + filename) + + public.serviceReload() + if id: self.depTotal(id) + self.WriteLogs(json.dumps({'name':'Ready to deploy','total':0,'used':0,'pre':0,'speed':0})) + return public.returnMsg(True,pinfo) + + + #处理临时文件 + def set_temp_file(self,filename,path): + public.ExecShell("rm -rf " + self.__tmp + '/*') + self.WriteLogs(json.dumps({'name':'Unpacking the package...','total':0,'used':0,'pre':0,'speed':0})) + public.ExecShell('unzip -o '+filename+' -d ' + self.__tmp) + auto_config = 'auto_install.json' + p_info = self.__tmp + '/' + auto_config + p_tmp = self.__tmp + p_config = None + if not os.path.exists(p_info): + d_path = None + for df in os.walk(self.__tmp): + if len(df[2]) < 3: continue + if not auto_config in df[2]: continue + if not os.path.exists(df[0] + '/' + auto_config): continue + d_path = df[0] + if d_path: + tmp_path = d_path + auto_file = tmp_path + '/' + auto_config + if os.path.exists(auto_file): + p_info = auto_file + p_tmp = tmp_path + if os.path.exists(p_info): + try: + p_config = json.loads(public.readFile(p_info)) + os.remove(p_info) + i_ndex_html = path + '/index.html' + if os.path.exists(i_ndex_html): os.remove(i_ndex_html) + if not self.copy_to(p_tmp,path): public.ExecShell((r"\cp -arf " + p_tmp + '/. ' + path + '/').replace('//','/')) + except: pass + public.ExecShell("rm -rf " + self.__tmp + '/*') + return p_config + + + def copy_to(self,src,dst): + try: + if src[-1] == '/': src = src[:-1] + if dst[-1] == '/': dst = dst[:-1] + if not os.path.exists(src): return False + if not os.path.exists(dst): os.makedirs(dst) + import shutil + for p_name in os.listdir(src): + f_src = src + '/' + p_name + f_dst = dst + '/' + p_name + if os.path.isdir(f_src): + print(shutil.copytree(f_src,f_dst)) + else: + print(shutil.copyfile(f_src,f_dst)) + return True + except: return False + + + #提交安装统计 + def depTotal(self,id): + import panelAuth + p = panelAuth.panelAuth() + pdata = p.create_serverid(None); + pdata['pid'] = id; + p_url = 'http://www.bt.cn/api/pluginother/create_order_okey' + public.httpPost(p_url,pdata) + + #获取进度 + def GetSpeed(self,get): + try: + if not os.path.exists(self.logPath):return public.returnMsg(False,'There are currently no deployment tasks!') + return json.loads(public.readFile(self.logPath)) + except: + return {'name':'Ready to deploy','total':0,'used':0,'pre':0,'speed':0} + + #获取包信息 + def GetPackageInfo(self,name): + data = self.GetDepList(None) + if not data: return False + for info in data['list']: + if info['name'] == name: + return info + return False + + #检查指定包是否存在 + def CheckPackageExists(self,name): + data = self.GetDepList(None) + if not data: return False + for info in data['list']: + if info['name'] == name: return True + + return False + + #文件的MD5值 + def GetFileMd5(self,filename): + if not os.path.isfile(filename): return False + import hashlib + myhash = hashlib.md5() + f = open(filename,'rb') + while True: + b = f.read(8096) + if not b : + break + myhash.update(b) + f.close() + return myhash.hexdigest() + + #获取站点标识 + def GetSiteId(self,get): + return public.M('sites').where('name=?',(get.webname,)).getField('id') diff --git a/class_v2/projectModelV2/base.py b/class_v2/projectModelV2/base.py new file mode 100644 index 00000000..c47f27cb --- /dev/null +++ b/class_v2/projectModelV2/base.py @@ -0,0 +1,63 @@ +#coding: utf-8 +import public,re + +class projectBase: + + def check_port(self, port): + ''' + @name 检查端口是否被占用 + @args port:端口号 + @return: 被占用返回True,否则返回False + @author: lkq 2021-08-28 + ''' + a = public.ExecShell("netstat -nltp|awk '{print $4}'") + if a[0]: + if re.search(':' + port + '\n', a[0]): + return True + else: + return False + else: + return False + + def is_domain(self, domain): + ''' + @name 验证域名合法性 + @args domain:域名 + @return: 合法返回True,否则返回False + @author: lkq 2021-08-28 + ''' + import re + domain_regex = re.compile(r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(? Optional[str]: + _mod_name = self.__class__.__module__ + + # "projectModel/javaModel.py" 的格式 + if "/" in _mod_name: + _mod_name = _mod_name.replace("/", ".") + if _mod_name.endswith(".py"): + mod_name = _mod_name[:-3] + else: + mod_name = _mod_name + + # "projectModel.javaModel" 的格式 + if "." in mod_name: + mod_name = mod_name.rsplit(".", 1)[1] + + if mod_name.endswith("Model"): + return mod_name[:-5] + if mod_name in self._allow_mod_name: + return mod_name + return None + + @property + def config_prefix(self) -> Optional[str]: + if getattr(self, "_config_prefix_cache", None) is not None: + return getattr(self, "_config_prefix_cache") + p_name = self.get_project_mod_type() + if p_name == "nodejs": + p_name = "node" + + if isinstance(p_name, str): + p_name = p_name + "_" + + setattr(self, "_config_prefix_cache", p_name) + return p_name + + @config_prefix.setter + def config_prefix(self, prefix: str): + setattr(self, "_config_prefix_cache", prefix) diff --git a/class_v2/projectModelV2/common/limit_net.py b/class_v2/projectModelV2/common/limit_net.py new file mode 100644 index 00000000..f044654d --- /dev/null +++ b/class_v2/projectModelV2/common/limit_net.py @@ -0,0 +1,239 @@ +import os +import re +from typing import Tuple + +import public +from .base import BaseProjectCommon + + +class LimitNet(BaseProjectCommon): + + def get_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + try: + site_id = int(get.site_id) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + if self.config_prefix is None: + return public.returnMsg(False, "不支持的网站类型") + + # 取配置文件 + site_name = public.M('sites').where("id=?", (site_id,)).getField('name') + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + conf = public.readFile(filename) + if not isinstance(conf, str): + return public.returnMsg(False, "配置文件读取错误") + + # 站点总并发 + data = { + 'perserver': 0, + 'perip': 0, + 'limit_rate': 0, + } + + rep_per_server = re.compile(r"(?P.*)limit_conn +perserver +(?P\d+) *; *", re.M) + tmp_res = rep_per_server.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['perserver'] = int(tmp_res.group("target")) + + # IP并发限制 + rep_per_ip = re.compile(r"(?P.*)limit_conn +perip +(?P\d+) *; *", re.M) + tmp_res = rep_per_ip.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['perip'] = int(tmp_res.group("target")) + + # 请求并发限制 + rep_limit_rate = re.compile(r"(?P.*)limit_rate +(?P\d+)\w+ *; *", re.M) + tmp_res = rep_limit_rate.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['limit_rate'] = int(tmp_res.group("target")) + + self._show_limit_net(data) + return data + + @staticmethod + def _show_limit_net(data): + values = [ + [300, 25, 512], + [200, 10, 1024], + [50, 3, 2048], + [500, 10, 2048], + [400, 15, 1024], + [60, 10, 512], + [150, 4, 1024], + ] + for i, c in enumerate(values): + if data["perserver"] == c[0] and data["perip"] == c[1] and data["limit_rate"] == c[2]: + data["value"] = i + 1 + break + else: + data["value"] = 0 + + @staticmethod + def _set_nginx_conf_limit() -> Tuple[bool, str]: + # 设置共享内存 + nginx_conf_file = "/www/server/nginx/conf/nginx.conf" + if not os.path.exists(nginx_conf_file): + return False, "nginx配置文件丢失" + nginx_conf = public.readFile(nginx_conf_file) + rep_perip = re.compile(r"\s+limit_conn_zone +\$binary_remote_addr +zone=perip:10m;", re.M) + rep_per_server = re.compile(r"\s+limit_conn_zone +\$server_name +zone=perserver:10m;", re.M) + perip_res = rep_perip.search(nginx_conf) + per_serve_res = rep_per_server.search(nginx_conf) + if perip_res and per_serve_res: + return True, "" + elif perip_res or per_serve_res: + tmp_res = perip_res or per_serve_res + new_conf = nginx_conf[:tmp_res.start()] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;" + ) + nginx_conf[tmp_res.end():] + else: + # 通过检查第一个server的位置 + rep_first_server = re.compile(r"http\s*\{(.*\n)*\s*server\s*\{") + tmp_res = rep_first_server.search(nginx_conf) + if tmp_res: + old_http_conf = tmp_res.group() + # 在第一个server项前添加 + server_idx = old_http_conf.rfind("server") + new_http_conf = old_http_conf[:server_idx] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n" + ) + old_http_conf[server_idx:] + new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1) + else: + # 在没有配置其他server项目时,通过检查include server项目检查 + # 通检查 include /www/server/panel/vhost/nginx/*.conf; 位置 + rep_include = re.compile(r"http\s*\{(.*\n)*\s*include +/www/server/panel/vhost/nginx/\*\.conf;") + tmp_res = rep_include.search(nginx_conf) + if not tmp_res: + return False, "全局配置缓存配置失败" + old_http_conf = tmp_res.group() + + include_idx = old_http_conf.rfind("include ") + new_http_conf = old_http_conf[:include_idx] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n" + ) + old_http_conf[include_idx:] + new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1) + + public.writeFile(nginx_conf_file, new_conf) + if public.checkWebConfig() is not True: # 检测失败,无法添加 + public.writeFile(nginx_conf_file, nginx_conf) + return False, "全局配置缓存配置失败" + return True, "" + + # 设置流量限制 + def set_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + try: + site_id = int(get.site_id) + per_server = int(get.perserver) + perip = int(get.perip) + limit_rate = int(get.limit_rate) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + if per_server < 1 or perip < 1 or limit_rate < 1: + return public.returnMsg(False, '并发限制,IP限制,流量限制必需大于0') + + # 取配置文件 + site_info = public.M('sites').where("id=?", (site_id,)).find() + if not isinstance(site_info, dict): + return public.returnMsg(False, "站点信息查询错误") + else: + site_name = site_info["name"] + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + site_conf: str = public.readFile(filename) + if not isinstance(site_conf, str): + return public.returnMsg(False, "配置文件读取错误") + + flag, msg = self._set_nginx_conf_limit() + if not flag: + return public.returnMsg(False, msg) + + per_server_str = ' limit_conn perserver {};'.format(per_server) + perip_str = ' limit_conn perip {};'.format(perip) + limit_rate_str = ' limit_rate {}k;'.format(limit_rate) + + # 请求并发限制 + new_conf = site_conf + ssl_end_res = re.search(r"#error_page 404/404.html;[^\n]*\n", new_conf) + if ssl_end_res is None: + return public.returnMsg(False, "未定位到SSL的相关配置,添加失败") + ssl_end_idx = ssl_end_res.end() + rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *", re.M) + tmp_res = rep_limit_rate.search(new_conf) + if tmp_res is not None : + new_conf = rep_limit_rate.sub(limit_rate_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + limit_rate_str + "\n" + new_conf[ssl_end_idx:] + + # IP并发限制 + rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *", re.M) + tmp_res = rep_per_ip.search(new_conf) + if tmp_res is not None: + new_conf = rep_per_ip.sub(perip_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + perip_str + "\n" + new_conf[ssl_end_idx:] + + rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *", re.M) + tmp_res = rep_per_server.search(site_conf) + if tmp_res is not None: + new_conf = rep_per_server.sub(per_server_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + per_server_str + "\n" + new_conf[ssl_end_idx:] + + public.writeFile(filename, new_conf) + is_error = public.checkWebConfig() + if is_error is not True: + public.writeFile(filename, site_conf) + return public.returnMsg(False, 'ERROR:
                                    ' + is_error.replace("\n", '
                                    ') + '
                                    ') + + public.serviceReload() + public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_OPEN_SUCCESS', (site_name,)) + return public.returnMsg(True, 'SET_SUCCESS') + + # 关闭流量限制 + def close_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + if self.config_prefix is None: + return public.returnMsg(False, "不支持的网站类型") + try: + site_id = int(get.site_id) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + # 取回配置文件 + site_info = public.M('sites').where("id=?", (site_id,)).find() + if not isinstance(site_info, dict): + return public.returnMsg(False, "站点信息查询错误") + else: + site_name = site_info["name"] + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + site_conf = public.readFile(filename) + if not isinstance(site_conf, str): + return public.returnMsg(False, "配置文件读取错误") + + # 清理总并发 + rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *\n?", re.M) + rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *\n?", re.M) + rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *\n?", re.M) + + new_conf = site_conf + new_conf = rep_limit_rate.sub("", new_conf, 1) + new_conf = rep_per_ip.sub("", new_conf, 1) + new_conf = rep_per_server.sub("", new_conf, 1) + + public.writeFile(filename, new_conf) + is_error = public.checkWebConfig() + if is_error is not True: + public.writeFile(filename, site_conf) + return public.returnMsg(False, 'ERROR:
                                    ' + is_error.replace("\n", '
                                    ') + '
                                    ') + public.serviceReload() + public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_CLOSE_SUCCESS', (site_name,)) + return public.returnMsg(True, 'SITE_NETLIMIT_CLOSE_SUCCESS') diff --git a/class_v2/projectModelV2/common/redirect.py b/class_v2/projectModelV2/common/redirect.py new file mode 100644 index 00000000..c224f8f5 --- /dev/null +++ b/class_v2/projectModelV2/common/redirect.py @@ -0,0 +1,806 @@ +import os,sys +import re +import json +import hashlib +import time +from typing import Tuple, Optional, Union, Dict, List +from urllib import parse +from itertools import product + +import public +from public.validate import Param +# from .base import BaseProjectCommon + + +class _RealRedirect: + setup_path = "/www/server/panel" + _redirect_conf_file = "{}/data/redirect.conf".format(setup_path) + + _ng_domain_format = """ +if ($host ~ '^%s'){ + return %s %s%s; +} +""" + _ng_path_format = """ +rewrite ^%s(.*) %s%s %s; +""" + _ap_domain_format = """ + + RewriteEngine on + RewriteCond %%{HTTP_HOST} ^%s [NC] + RewriteRule ^(.*) %s%s [L,R=%s] + +""" + _ap_path_format = """ + + RewriteEngine on + RewriteRule ^%s(.*) %s%s [L,R=%s] + +""" + + def __init__(self, config_prefix: str): + self._config: Optional[List[Dict[str, Union[str, int]]]] = None + self.config_prefix = config_prefix + self._webserver = None + + @property + def webserver(self) -> str: + if self._webserver is not None: + return self._webserver + self._webserver = public.get_webserver() + return self._webserver + + @property + def config(self) -> List[Dict[str, Union[str, int, List]]]: + if self._config is not None: + return self._config + try: + self._config = json.loads(public.readFile(self._redirect_conf_file)) + except (json.JSONDecodeError, TypeError, ValueError): + self._config = [] + if not isinstance(self._config, list): + self._config = [] + return self._config + + def save_config(self): + if self._config is not None: + return public.writeFile(self._redirect_conf_file, json.dumps(self._config)) + + def _check_redirect_domain_exist(self, site_name, + redirect_domain: list, + redirect_name: str = None, + is_modify=False) -> Optional[List[str]]: + res = set() + redirect_domain_set = set(redirect_domain) + for c in self.config: + if c["sitename"] != site_name: + continue + if is_modify: + if c["redirectname"] != redirect_name: + res |= set(c["redirectdomain"]) & redirect_domain_set + else: + res |= set(c["redirectdomain"]) & redirect_domain_set + return list(res) if res else None + + def _check_redirect_path_exist(self, site_name, + redirect_path: str, + redirect_name: str = None) -> bool: + for c in self.config: + if c["sitename"] == site_name: + if c["redirectname"] != redirect_name and c["redirectpath"] == redirect_path: + return True + return False + + @staticmethod + def _parse_url_domain(url: str): + return parse.urlparse(url).netloc + + @staticmethod + def _parse_url_path(url: str): + return parse.urlparse(url).path + + # 计算name md5 + @staticmethod + def _calc_redirect_name_md5(redirect_name) -> str: + md5 = hashlib.md5() + md5.update(redirect_name.encode('utf-8')) + return md5.hexdigest() + + def _check_redirect(self, site_name, redirect_name, is_error=False): + for i in self.config: + if i["sitename"] != site_name: + continue + if is_error and "errorpage" in i and i["errorpage"] in [1, '1']: + return i + if i["redirectname"] == redirect_name: + return i + return None + + # 创建修改配置检测 + def _check_redirect_args(self, get, is_modify=False) -> Union[str, Dict]: + if public.checkWebConfig() is not True: + return '配置文件出错请先排查配置' + + try: + site_name = get.sitename.strip() + redirect_path = get.redirectpath.strip() + redirect_type = get.redirecttype.strip() + domain_or_path = get.domainorpath.strip() + hold_path = int(get.holdpath) + + to_url = "" + to_path = "" + error_page = 0 + redirect_domain = [] + redirect_name = "" + status_type = 1 + + if "redirectname" in get and get.redirectname.strip(): + redirect_name = get.redirectname.strip() + if "tourl" in get: + to_url = get.tourl.strip() + if "topath" in get: + to_path = get.topath.strip() + if "redirectdomain" in get: + redirect_domain = json.loads(get.redirectdomain.strip()) + if "type" in get: + status_type = int(get.type) + if "errorpage" in get: + error_page = int(get.errorpage) + except (AttributeError, ValueError): + return '参数错误' + + if not is_modify: + if not redirect_name: + return "参数错误,配置名称不能为空" + # 检测名称是否重复 + if not (3 < len(redirect_name) < 15): + return '名称必须大于3小于15个字符串' + + if self._check_redirect(site_name, redirect_name, error_page == 1): + return '指定重定向名称已存在' + + site_info = public.M('sites').where("name=?", (site_name,)).find() + if not isinstance(site_info, dict): + return "站点信息查询错误" + else: + site_name = site_info["name"] + + # 检测目标URL格式 + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + if to_url and not re.match(rep, to_url): + return '目标URL格式不对【%s】' % to_url + + # 非404页面de重定向检测项 + if error_page != 1: + # 检测是否选择域名 + if domain_or_path == "domain": + if not redirect_domain: + return '请选择重定向域名' + # 检测域名是否已经存在配置文件 + repeat_domain = self._check_redirect_domain_exist(site_name, redirect_domain, redirect_name, is_modify) + if repeat_domain: + return '重定向域名重复 %s' % repeat_domain + + # 检查目标URL的域名和被重定向的域名是否一样 + tu = self._parse_url_domain(to_url) + for d in redirect_domain: + if d == tu: + return '域名 "%s" 和目标域名一致请取消选择' % d + else: + if not redirect_path: + return '请输入重定向路径' + if redirect_path[0] != "/": + return "路径格式不正确,格式为/xxx" + # 检测路径是否有存在配置文件 + if self._check_redirect_path_exist(site_name, redirect_path, redirect_name): + return '重定向路径重复 %s' % redirect_path + + to_url_path = self._parse_url_path(to_url) + if to_url_path.startswith(redirect_path): + return '目标URL[%s]以被重定向的路径[%s]开头,会导致循环匹配' % (to_url_path, redirect_path) + # 404页面重定向检测项 + else: + if not to_url and not to_path: + return '首页或自定义页面必须二选一' + if to_path: + to_path = "/" + + return { + "tourl": to_url, + "topath": to_path, + "errorpage": error_page, + "redirectdomain": redirect_domain, + "redirectname": redirect_name if redirect_name else str(int(time.time())), + "type": status_type, + "sitename": site_name, + "redirectpath": redirect_path, + "redirecttype": redirect_type, + "domainorpath": domain_or_path, + "holdpath": hold_path, + } + + def create_redirect(self, get): + res_conf = self._check_redirect_args(get, is_modify=False) + if isinstance(res_conf, str): + return public.returnMsg(False, res_conf) + + res = self._set_include(res_conf) + if res is not None: + return public.returnMsg(False, res) + res = self._write_config(res_conf) + if res is not None: + return public.returnMsg(False, res) + self.config.append(res_conf) + self.save_config() + public.serviceReload() + return public.returnMsg(True, '创建成功') + + def _set_include(self, res_conf) -> Optional[str]: + flag, msg = self._set_nginx_redirect_include(res_conf) + if not flag: + return msg + flag, msg = self._set_apache_redirect_include(res_conf) + if not flag: + return msg + + def _write_config(self, res_conf) -> Optional[str]: + if res_conf["errorpage"] != 1: + res = self.write_nginx_redirect_file(res_conf) + if res is not None: + return res + res = self.write_apache_redirect_file(res_conf) + if res is not None: + return res + else: + self.unset_nginx_404_conf(res_conf["sitename"]) + res = self.write_nginx_404_redirect_file(res_conf) + if res is not None: + return res + res = self.write_apache_404_redirect_file(res_conf) + if res is not None: + return res + + def modify_redirect(self, get): + """ + @name 修改、启用、禁用重定向 + @author hezhihong + @param get.sitename 站点名称 + @param get.redirectname 重定向名称 + @param get.tourl 目标URL + @param get.redirectdomain 重定向域名 + @param get.redirectpath 重定向路径 + @param get.redirecttype 重定向类型 + @param get.type 重定向状态 0禁用 1启用 + @param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向 + @param get.holdpath 保留路径 0不保留 1保留 + @return json + """ + # 基本信息检查 + res_conf = self._check_redirect_args(get, is_modify=True) + if isinstance(res_conf, str): + return public.returnMsg(False, res_conf) + + old_idx = None + for i, conf in enumerate(self.config): + if conf["redirectname"] == res_conf["redirectname"] and conf["sitename"] == res_conf["sitename"]: + old_idx = i + + res = self._set_include(res_conf) + if res is not None: + return public.returnMsg(False, res) + res = self._write_config(res_conf) + if res is not None: + return public.returnMsg(False, res) + + if old_idx: + self.config[old_idx].update(res_conf) + else: + self.config.append(res_conf) + self.save_config() + public.serviceReload() + return public.returnMsg(True, '修改成功') + + def _set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ng_redirect_dir = "%s/vhost/nginx/redirect/%s" % (self.setup_path, redirect_conf["sitename"]) + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + if not os.path.exists(ng_redirect_dir): + os.makedirs(ng_redirect_dir, 0o600) + ng_conf = public.readFile(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"\sinclude +.*/redirect/.*\*\.conf;", re.M) + if rep_include.search(ng_conf): + return True, "" + redirect_include = ( + "#SSL-END\n" + " #引用重定向规则,注释后配置的重定向代理将无效\n" + " include {}/*.conf;" + ).format(ng_redirect_dir) + + if "#SSL-END" not in ng_conf: + return False, "添加配置失败,无法定位SSL相关配置的位置" + + new_conf = ng_conf.replace("#SSL-END", redirect_include) + public.writeFile(ng_file, new_conf) + if self.webserver == "nginx" and public.checkWebConfig() is not True: + public.writeFile(ng_file, ng_conf) + return False, "添加配置失败" + + return True, "" + + def _un_set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + ng_conf = public.readFile(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"(#(.*)\n)?\s*include +.*/redirect/.*\*\.conf;") + if not rep_include.search(ng_conf): + return True, "" + + new_conf = rep_include.sub("", ng_conf, 1) + public.writeFile(ng_file, new_conf) + if self.webserver == "nginx" and public.checkWebConfig() is not True: + public.writeFile(ng_file, ng_conf) + return False, "移除配置失败" + + return True, "" + + def _set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ap_redirect_dir = "%s/vhost/apache/redirect/%s" % (self.setup_path, redirect_conf["sitename"]) + ap_file = "{}/vhost/apache/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + if not os.path.exists(ap_redirect_dir): + os.makedirs(ap_redirect_dir, 0o600) + + ap_conf = public.readFile(ap_file) + if not isinstance(ap_conf, str): + return False, "apache配置文件读取失败" + + rep_include = re.compile(r"\sIncludeOptional +.*/redirect/.*\*\.conf", re.M) + # public.print_log(list(rep_include.finditer(ap_conf))) + include_count = len(list(rep_include.finditer(ap_conf))) + if ap_conf.count("") == include_count: + return True, "" + + if include_count > 0: + # 先清除已有的配置 + self._un_set_apache_redirect_include(redirect_conf) + + rep_custom_log = re.compile(r"CustomLog .*\n") + rep_deny_files = re.compile(r"\n\s*#DENY FILES") + + include_conf = ( + "\n # 引用重定向规则,注释后配置的重定向代理将无效\n" + " IncludeOptional {}/*.conf\n" + ).format(ap_redirect_dir) + + new_conf = None + + def set_by_rep_idx(rep: re.Pattern, use_start: bool) -> bool: + new_conf_list = [] + last_idx = 0 + for tmp in rep.finditer(ap_conf): + new_conf_list.append(ap_conf[last_idx:tmp.start()]) + if use_start: + new_conf_list.append(include_conf) + new_conf_list.append(tmp.group()) + else: + new_conf_list.append(tmp.group()) + new_conf_list.append(include_conf) + last_idx = tmp.end() + + new_conf_list.append(ap_conf[last_idx:]) + + nonlocal new_conf + new_conf = "".join(new_conf_list) + public.writeFile(ap_file, new_conf) + if self.webserver == "apache" and public.checkWebConfig() is not True: + public.writeFile(ap_file, ap_conf) + return False + return True + + if set_by_rep_idx(rep_custom_log, False) and rep_include.search(new_conf): + return True, "" + + if set_by_rep_idx(rep_deny_files, True) and rep_include.search(new_conf): + return True, "" + return False, "设置失败" + + def _un_set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ap_file = "{}/vhost/apache/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + ap_conf = public.readFile(ap_file) + if not isinstance(ap_conf, str): + return False, "apache配置文件读取失败" + + rep_include = re.compile(r"(#(.*)\n)?\s*IncludeOptional +.*/redirect/.*\*\.conf") + if not rep_include.search(ap_conf): + return True, "" + + new_conf = rep_include.sub("", ap_conf) + public.writeFile(ap_file, new_conf) + if self.webserver == "apache" and public.checkWebConfig() is not True: + public.writeFile(ap_file, ap_conf) + return False, "移除配置失败" + + return True, "" + + def write_nginx_redirect_file(self, redirect_conf: dict) -> Optional[str]: + conf_file = "{}/vhost/nginx/redirect/{}/{}_{}.conf".format( + self.setup_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]), + redirect_conf["sitename"] + ) + if redirect_conf["type"] == 1: + to_url = redirect_conf["tourl"] + conf_list = ["#REWRITE-START"] + if redirect_conf["domainorpath"] == "domain": + hold_path = "$request_uri" if redirect_conf["holdpath"] == 1 else "" + for sd in redirect_conf["redirectdomain"]: + if sd.startswith("*."): + sd = r"[\w.]+\." + sd[2:] + + conf_list.append(self._ng_domain_format % ( + sd, redirect_conf["redirecttype"], to_url, hold_path + )) + else: + redirect_path = redirect_conf["redirectpath"] + if redirect_conf["redirecttype"] == "301": + redirect_type = "permanent" + else: + redirect_type = "redirect" + hold_path = "$1" if redirect_conf["holdpath"] == 1 else "" + conf_list.append(self._ng_path_format % (redirect_path, to_url, hold_path, redirect_type)) + + conf_list.append("#REWRITE-END") + + conf_data = "\n".join(conf_list) + public.writeFile(conf_file, conf_data) + + if self.webserver == "nginx": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + else: + if os.path.exists(conf_file): + os.remove(conf_file) + + def write_apache_redirect_file(self, redirect_conf: dict) -> Optional[str]: + conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format( + self.setup_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]), + redirect_conf["sitename"] + ) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + to_url = redirect_conf["tourl"] + conf_list = ["#REWRITE-START"] + hold_path = "$1" if redirect_conf["holdpath"] == 1 else "" + if redirect_conf["domainorpath"] == "domain": + for sd in redirect_conf["redirectdomain"]: + if sd.startswith("*."): + sd = r"[\w.]+\." + sd[2:] + + conf_list.append(self._ap_domain_format % ( + sd, to_url, hold_path, redirect_conf["redirecttype"] + )) + else: + redirect_path = redirect_conf["redirectpath"] + conf_list.append(self._ap_path_format % (redirect_path, to_url, hold_path, redirect_conf["redirecttype"])) + + conf_list.append("#REWRITE-END") + + public.writeFile(conf_file, "\n".join(conf_list)) + if self.webserver == "apache": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + + def unset_nginx_404_conf(self, site_name): + """ + 清理已有的 404 页面 配置 + """ + need_clear_files = [ + "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name), + "{}/vhost/nginx/rewrite/{}{}.conf".format(self.setup_path, self.config_prefix, site_name), + ] + rep_error_page = re.compile(r'(?P.*)error_page +404 +/404\.html[^\n]*\n', re.M) + rep_location_404 = re.compile(r'(?P.*)location += +/404\.html[^}]*}') + clear_files = [ + { + "data": public.readFile(i), + "path": i, + } for i in need_clear_files + ] + for file_info, rep in product(clear_files, (rep_error_page, rep_location_404)): + if not isinstance(file_info["data"], str): + continue + tmp_res = rep.search(file_info["data"]) + if not tmp_res or tmp_res.group("prefix").find("#") != -1: + continue + file_info["data"] = rep.sub("", file_info["data"]) + + for i in clear_files: + if not isinstance(i["data"], str): + continue + public.writeFile(i["path"], i["data"]) + + def write_nginx_404_redirect_file(self, redirect_conf: dict) -> Optional[str]: + """ + 设置nginx 404重定向 + """ + r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"]) + file_path = "{}/vhost/nginx/redirect/{}".format(self.setup_path, redirect_conf["sitename"]) + file_name = '%s_%s.conf' % (r_name_md5, redirect_conf["sitename"]) + conf_file = os.path.join(file_path, file_name) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + _path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"] + conf_data = ( + '#REWRITE-START\n' + 'error_page 404 = @notfound;\n' + 'location @notfound {{\n' + ' return {} {};\n' + '}}\n#REWRITE-END' + ).format(redirect_conf["redirecttype"], _path) + + public.writeFile(conf_file, conf_data) + if self.webserver == "nginx": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + + def write_apache_404_redirect_file(self, redirect_conf: dict) -> Optional[str]: + """ + 设置apache 404重定向 + """ + r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"]) + conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format( + self.setup_path, redirect_conf["sitename"], r_name_md5, redirect_conf["sitename"] + ) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + _path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"] + conf_data = """ +#REWRITE-START + + RewriteEngine on + RewriteCond %{{REQUEST_FILENAME}} !-f + RewriteCond %{{REQUEST_FILENAME}} !-d + RewriteRule . {} [L,R={}] + +#REWRITE-END +""".format(_path, redirect_conf["redirecttype"]) + + public.writeFile(conf_file, conf_data) + if self.webserver == "apache": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + + def remove_redirect(self, get, multiple=None): + try: + site_name = get.sitename.strip() + redirect_name = get.redirectname.strip() + except AttributeError: + return public.returnMsg(False, "参数错误") + target_idx = None + have_other_redirect = False + target_conf = None + for i, conf in enumerate(self.config): + if conf["redirectname"] != redirect_name and conf["sitename"] == site_name: + have_other_redirect = True + if conf["redirectname"] == redirect_name and conf["sitename"] == site_name: + target_idx = i + target_conf = conf + + if not target_idx: + return public.returnMsg(False, '没有指定的配置') + + r_md5_name = self._calc_redirect_name_md5(target_conf["redirectname"]) + public.ExecShell("rm -f %s/vhost/nginx/redirect/%s/%s_%s.conf" % ( + self.setup_path, site_name, r_md5_name, site_name)) + + public.ExecShell("rm -f %s/vhost/apache/redirect/%s/%s_%s.conf" % ( + self.setup_path, site_name, r_md5_name, site_name)) + + if not have_other_redirect: + self._un_set_apache_redirect_include(target_conf) + self._un_set_nginx_redirect_include(target_conf) + + del self.config[target_idx] + self.save_config() + if not multiple: + public.serviceReload() + + return public.returnMsg(True, '删除成功') + + def mutil_remove_redirect(self, get): + try: + redirect_names = json.loads(get.redirectnames.strip()) + site_name = json.loads(get.sitename.strip()) + except (AttributeError, json.JSONDecodeError, TypeError): + return public.returnMsg(False, "参数错误") + del_successfully = [] + del_failed = {} + get_obj = public.dict_obj() + for redirect_name in redirect_names: + get_obj.redirectname = redirect_name + get_obj.sitename = site_name + try: + result = self.remove_redirect(get, multiple=1) + if not result['status']: + del_failed[redirect_name] = result['msg'] + continue + del_successfully.append(redirect_name) + except: + del_failed[redirect_name] = '删除时出错了,请再试一次' + + public.serviceReload() + return { + 'status': True, + 'msg': '删除重定向 [ {} ] 成功'.format(','.join(del_successfully)), + 'error': del_failed, + 'success': del_successfully + } + + def get_redirect_list(self, get): + try: + error_page = None + site_name = get.sitename.strip() + if "errorpage" in get: + error_page = int(get.errorpage) + except (AttributeError, ValueError, TypeError): + return public.return_message(-1,0, "parameter error") + redirect_list = [] + webserver = public.get_webserver() + if webserver == 'openlitespeed': + webserver = 'apache' + for conf in self.config: + if conf["sitename"] != site_name: + continue + if error_page is not None and error_page != int(conf['errorpage']): + continue + if 'errorpage' in conf and conf['errorpage'] in [1, '1']: + conf['redirectdomain'] = ['404 page'] + + md5_name = self._calc_redirect_name_md5(conf['redirectname']) + conf["redirect_conf_file"] = "%s/vhost/%s/redirect/%s/%s_%s.conf" % ( + self.setup_path, webserver, site_name, md5_name, site_name) + conf["type"] = 1 if os.path.isfile(conf["redirect_conf_file"]) else 0 + redirect_list.append(conf) + return public.return_message(0,0,redirect_list) + + def remove_redirect_by_project_name(self, project_name): + for i in range(len(self.config) - 1, -1, -1): + if self.config[i]["sitename"] == project_name: + del self.config[i] + self.save_config() + m_path = self.setup_path + '/vhost/nginx/redirect/' + project_name + if os.path.exists(m_path): + public.ExecShell("rm -rf %s" % m_path) + m_path = self.setup_path + '/vhost/apache/redirect/' + project_name + if os.path.exists(m_path): + public.ExecShell("rm -rf %s" % m_path) + + +def test_api_warp(fn): + def inner(*args, **kwargs): + try: + return fn(*args, **kwargs) + except: + public.print_log(public.get_error_info()) + + return inner + + +class BaseProjectCommon: + setup_path = "/www/server/panel" + _allow_mod_name = { + "go", "java", "net", "nodejs", "other", "python", "proxy", + } + + def get_project_mod_type(self) -> Optional[str]: + _mod_name = self.__class__.__module__ + + # "projectModel/javaModel.py" 的格式 + if "/" in _mod_name: + _mod_name = _mod_name.replace("/", ".") + if _mod_name.endswith(".py"): + mod_name = _mod_name[:-3] + else: + mod_name = _mod_name + + # "projectModel.javaModel" 的格式 + if "." in mod_name: + mod_name = mod_name.rsplit(".", 1)[1] + + if mod_name.endswith("Model"): + return mod_name[:-5] + if mod_name in self._allow_mod_name: + return mod_name + return None + + @property + def config_prefix(self) -> Optional[str]: + if getattr(self, "_config_prefix_cache", None) is not None: + return getattr(self, "_config_prefix_cache") + p_name = self.get_project_mod_type() + if p_name == "nodejs": + p_name = "node" + + if isinstance(p_name, str): + p_name = p_name + "_" + + setattr(self, "_config_prefix_cache", p_name) + return p_name + + @config_prefix.setter + def config_prefix(self, prefix: str): + setattr(self, "_config_prefix_cache", prefix) + +class Redirect(BaseProjectCommon): + # 匹配目标URL的域名并返回 + + def remove_redirect_by_project_name(self, project_name): + if not isinstance(self.config_prefix, str): + return None + return _RealRedirect(self.config_prefix).remove_redirect_by_project_name(project_name) + + def create_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "Unsupported website type") + return _RealRedirect(self.config_prefix).create_redirect(get) + + def modify_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "Unsupported website type") + + return _RealRedirect(self.config_prefix).modify_redirect(get) + + def remove_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "Unsupported website type") + return _RealRedirect(self.config_prefix).remove_redirect(get) + + def mutil_remove_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "Unsupported website type") + + return _RealRedirect(self.config_prefix).mutil_remove_redirect(get) + + def get_project_redirect_list(self, get): + # 校验参数 + try: + get.validate([ + Param('sitename').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + self.config_prefix='proxy_' + if not isinstance(self.config_prefix, str): + return public.return_message(-1,0, "Unsupported website type") + return _RealRedirect(self.config_prefix).get_redirect_list(get) + + + diff --git a/class_v2/projectModelV2/dockerModel.py b/class_v2/projectModelV2/dockerModel.py new file mode 100644 index 00000000..6a79438b --- /dev/null +++ b/class_v2/projectModelV2/dockerModel.py @@ -0,0 +1,58 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# aaPanel +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +# ------------------------------------------------------------------- +# Author: zouhw +# ------------------------------------------------------------------- + +# ------------------------------ +# 项目管理控制器 +# ------------------------------ +import os ,public ,json ,re ,time #line:13 +class main :#line:15 + def __init__ (O00000000O0OO000O ):#line:17 + pass #line:18 + def model (O0O0OOO0OO0OO00OO ,OO0O0O000O00O00O0 ):#line:20 + ""#line:29 + import panelPlugin #line:30 + OO00OO0OOOO0OO0O0 =public .to_dict_obj ({})#line:31 + OO00OO0OOOO0OO0O0 .focre =1 #line:32 + O0OO000000O0000O0 =panelPlugin .panelPlugin ().get_soft_list (OO00OO0OOOO0OO0O0 )#line:33 + __OO000O0000OO000OO =int (O0OO000000O0000O0 ['ltd'])>1 #line:34 + try :#line:40 + OO0O0O000O00O00O0 .def_name =OO0O0O000O00O00O0 .dk_def_name #line:41 + OO0O0O000O00O00O0 .mod_name =OO0O0O000O00O00O0 .dk_model_name #line:42 + if OO0O0O000O00O00O0 ['mod_name']in ['base']:return public .return_status_code (1000 ,'Wrong call!')#line:43 + public .exists_args ('def_name,mod_name',OO0O0O000O00O00O0 )#line:44 + if OO0O0O000O00O00O0 ['def_name'].find ('__')!=-1 :return public .return_status_code (1000 ,'The called method name cannot contain the "__" characterrong call!')#line:45 + if not re .match (r"^\w+$",OO0O0O000O00O00O0 ['mod_name']):return public .return_status_code (1000 ,r'The called module name cannot contain characters other than \w')#line:46 + if not re .match (r"^\w+$",OO0O0O000O00O00O0 ['def_name']):return public .return_status_code (1000 ,r'The called module name cannot contain characters other than \w')#line:47 + except :#line:48 + return public .get_error_object ()#line:49 + O0OOO0O0O00O00O0O ="dk_{}".format (OO0O0O000O00O00O0 ['mod_name'].strip ())#line:51 + OO00OO0OOOO0OOOOO =OO0O0O000O00O00O0 ['def_name'].strip ()#line:52 + OO00OO0O0OOOOO000 ="{}/projectModel/bt_docker/{}.py".format (public .get_class_path (),O0OOO0O0O00O00O0O )#line:55 + if not os .path .exists (OO00OO0O0OOOOO000 ):#line:56 + return public .return_status_code (1003 ,O0OOO0O0O00O00O0O )#line:57 + OO00O00OOOOOO0000 =public .get_script_object (OO00OO0O0OOOOO000 )#line:59 + if not OO00O00OOOOOO0000 :return public .return_status_code (1000 ,'{} model not found'.format (O0OOO0O0O00O00O0O ))#line:60 + OOO0O000O0OOOOO0O =getattr (OO00O00OOOOOO0000 .main (),OO00OO0OOOO0OOOOO ,None )#line:61 + if not OOO0O000O0OOOOO0O :return public .return_status_code (1000 ,'{} method not found in {} model'.format (O0OOO0O0O00O00O0O ,OO00OO0OOOO0OOOOO ))#line:62 + O00O0O00O000O0000 ='{}_{}_LAST'.format (O0OOO0O0O00O00O0O .upper (),OO00OO0OOOO0OOOOO .upper ())#line:76 + O0OO0OOOOO00OOOO0 =public .exec_hook (O00O0O00O000O0000 ,OO0O0O000O00O00O0 )#line:77 + if isinstance (O0OO0OOOOO00OOOO0 ,public .dict_obj ):#line:78 + OOO0OOOOOOO000O0O =O0OO0OOOOO00OOOO0 #line:79 + elif isinstance (O0OO0OOOOO00OOOO0 ,dict ):#line:80 + return O0OO0OOOOO00OOOO0 #line:81 + elif isinstance (O0OO0OOOOO00OOOO0 ,bool ):#line:82 + if not O0OO0OOOOO00OOOO0 :#line:83 + return public .return_data (False ,{},error_msg ='Pre-HOOK interrupt operation')#line:84 + OO000OOOO000OOO0O =OOO0O000O0OOOOO0O (OO0O0O000O00O00O0 )#line:87 + O00O0O00O000O0000 ='{}_{}_END'.format (O0OOO0O0O00O00O0O .upper (),OO00OO0OOOO0OOOOO .upper ())#line:90 + O0O000OO0O00O0OOO =public .to_dict_obj ({'args':OO0O0O000O00O00O0 ,'result':OO000OOOO000OOO0O })#line:94 + O0OO0OOOOO00OOOO0 =public .exec_hook (O00O0O00O000O0000 ,O0O000OO0O00O0OOO )#line:95 + if isinstance (O0OO0OOOOO00OOOO0 ,dict ):#line:96 + OO000OOOO000OOO0O =O0OO0OOOOO00OOOO0 ['result']#line:97 + return OO000OOOO000OOO0O #line:98 diff --git a/class_v2/projectModelV2/nodejsModel.py b/class_v2/projectModelV2/nodejsModel.py new file mode 100644 index 00000000..832e9517 --- /dev/null +++ b/class_v2/projectModelV2/nodejsModel.py @@ -0,0 +1,2240 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +#------------------------------ +# node.js模型 +#------------------------------ +import os,sys,re,json,shutil,psutil,time +from projectModelV2.base import projectBase +import public +from public.validate import Param +try: + from BTPanel import cache +except: + pass + +class main(projectBase): + _panel_path = public.get_panel_path() + _nodejs_plugin_path = public.get_plugin_path('nodejs') + _nodejs_path = '{}/nodejs'.format(public.get_setup_path()) + _log_name = 'Project management' + _npm_exec_log = '{}/logs/npm-exec.log'.format(_panel_path) + _node_pid_path = '{}/vhost/pids'.format(_nodejs_path) + _node_logs_path = '{}/vhost/logs'.format(_nodejs_path) + _node_run_scripts = '{}/vhost/scripts'.format(_nodejs_path) + _pids = None + _vhost_path = '{}/vhost'.format(_panel_path) + _www_home = '/home/www' + + + + def __init__(self): + if not os.path.exists(self._node_run_scripts): + os.makedirs(self._node_run_scripts,493) + + if not os.path.exists(self._node_pid_path): + os.makedirs(self._node_pid_path,493) + + if not os.path.exists(self._node_logs_path): + os.makedirs(self._node_logs_path,493) + + if not os.path.exists(self._www_home): + os.makedirs(self._www_home,493) + public.set_own(self._www_home,'www') + + + def get_exec_logs(self,get): + ''' + @name 获取执行日志 + @author hwliang<2021-08-09> + @param get + @return string + ''' + if not os.path.exists(self._npm_exec_log): return public.returnMsg(False,'NODE_NOT_EXISTS') + return public.return_message(0,0,public.GetNumLines(self._npm_exec_log,20)) + + + def get_project_list(self,get): + ''' + @name 获取项目列表 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('search').String(), + Param('limit').Integer(), + Param('p').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + if not 'p' in get: get.p = 1 + if not 'limit' in get: get.limit = 20 + if not 'callback' in get: get.callback = '' + if not 'order' in get: get.order = 'id desc' + + if 'search' in get: + get.project_name = get.search.strip() + search = "%{}%".format(get.project_name) + count = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?)',('Node',search,search)).count() + data = public.get_page(count,int(get.p),int(get.limit),get.callback) + data['data'] = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?)',('Node',search,search)).limit(data['shift'] + ',' + data['row']).order(get.order).select() + else: + count = public.M('sites').where('project_type=?','Node').count() + data = public.get_page(count,int(get.p),int(get.limit),get.callback) + data['data'] = public.M('sites').where('project_type=?','Node').limit(data['shift'] + ',' + data['row']).order(get.order).select() + + for i in range(len(data['data'])): + data['data'][i] = self.get_project_stat(data['data'][i]) + return public.return_message(0,0,data) + + + def get_ssl_end_date(self,project_name): + ''' + @name 获取SSL信息 + @author hwliang<2021-08-09> + @param project_name 项目名称 + @return dict + ''' + import data + return data.data().get_site_ssl_info('node_{}'.format(project_name)) + + + + def is_install_nodejs(self,get): + ''' + @name 是否安装nodejs版本管理器 + @author hwliang<2021-08-09> + @param get 请求数据 + @return bool + ''' + return_message=os.path.exists(self._nodejs_plugin_path) + return public.return_message(0,0,return_message) + + + def get_nodejs_version(self,get): + ''' + @name 获取已安装的nodejs版本 + @author hwliang<2021-08-09> + @param get 请求数据 + @return list + ''' + nodejs_list = [] + if not os.path.exists(self._nodejs_path): return public.return_message(0,0,nodejs_list) + for v in os.listdir(self._nodejs_path): + if v[0] != 'v' or v.find('.') == -1: continue + node_path = os.path.join(self._nodejs_path,v) + node_bin = '{}/bin/node'.format(node_path) + if not os.path.exists(node_bin): + if os.path.exists(node_path + '/bin'): + public.ExecShell('rm -rf {}'.format(node_path)) + continue + nodejs_list.append(v) + return public.return_message(0,0,nodejs_list) + + + + def get_run_list(self,get): + ''' + @name 获取node项目启动列表 + @author hwliang<2021-08-10> + @param get{ + project_cwd: string<项目目录> + } + '''# 校验参数 + try: + get.validate([ + Param('project_cwd').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_cwd = get.project_cwd.strip() + if not os.path.exists(project_cwd): + return_message=public.return_error('The project directory does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + package_file = '{}/package.json'.format(project_cwd) + if not os.path.exists(package_file): return public.return_message(0,0,{}) #public.return_error('没有在项目目录中找到package.json配置文件!') + package_info = json.loads(public.readFile(package_file)) + if not 'scripts' in package_info: return public.return_message(0,0,{})# public.return_error('没有在项目配置文件package.json中找到scripts配置项!') + if not package_info['scripts']: return public.return_message(0,0,{})# public.return_error('没有找到可用的启动选项!') + return public.return_message(0,0,package_info['scripts']) + + + def get_npm_bin(self,nodejs_version): + ''' + @name 获取指定node版本的npm路径 + @author hwliang<2021-08-10> + @param nodejs_version nodejs版本 + @return string + ''' + npm_path = '{}/{}/bin/npm'.format(self._nodejs_path,nodejs_version) + if not os.path.exists(npm_path): return False + return npm_path + + def get_yarn_bin(self,nodejs_version): + ''' + @name 获取指定node版本的yarn路径 + @author hwliang<2021-08-28> + @param nodejs_version nodejs版本 + @return string + ''' + yarn_path = '{}/{}/bin/yarn'.format(self._nodejs_path,nodejs_version) + if not os.path.exists(yarn_path): return False + return yarn_path + + + def get_node_bin(self,nodejs_version): + ''' + @name 获取指定node版本的node路径 + @author hwliang<2021-08-10> + @param nodejs_version nodejs版本 + @return string + ''' + node_path = '{}/{}/bin/node'.format(self._nodejs_path,nodejs_version) + if not os.path.exists(node_path): return False + return node_path + + + def get_last_env(self,nodejs_version,project_cwd = None): + ''' + @name 获取前置环境变量 + @author hwliang<2021-08-25> + @param nodejs_version Node版本 + @return string + ''' + nodejs_bin_path = '{}/{}/bin'.format(self._nodejs_path,nodejs_version) + if project_cwd: + _bin = '{}/node_modules/.bin'.format(project_cwd) + if os.path.exists(_bin): + nodejs_bin_path = _bin + ':' + nodejs_bin_path + + last_env = '''PATH={nodejs_bin_path}:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +export PATH +'''.format(nodejs_bin_path = nodejs_bin_path) + return last_env + + + def install_packages(self,get): + ''' + @name 安装指定项目的依赖包 + @author hwliang<2021-08-10> + @param get{ + project_name: string<项目名称> + } + return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not os.path.exists(project_find['path']): + return_message=public.return_error('The project directory does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + package_file = '{}/package.json'.format(project_find['path']) + if not os.path.exists(package_file): + return_message=public.return_error('The package.json configuration file was not found in the project directory!') + del return_message['status'] + return public.return_message(-1,0, return_message) + nodejs_version = project_find['project_config']['nodejs_version'] + + package_lock_file = '{}/package-lock.json'.format(project_find['path']) + node_modules_path = '{}/node_modules'.format(project_find['path']) + + # 已经安装过的依赖包的情况下,可能存在不同node版本导致的问题,可能需要重新构建依赖包 + rebuild = False + if os.path.exists(package_lock_file) and os.path.exists(node_modules_path): + rebuild = True + + npm_bin = self.get_npm_bin(nodejs_version) + yarn_bin = self.get_yarn_bin(nodejs_version) + if not npm_bin and not yarn_bin: + return_message=public.return_error('The specified nodejs version does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.writeFile(self._npm_exec_log,"Installing dependencies...\n") + public.writeFile(self._npm_exec_log,"Downloading dependency package, please wait...\n") + if yarn_bin: + if os.path.exists(package_lock_file): + os.remove(package_lock_file) + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} install 2>&1 >> {}".format(project_find['path'],yarn_bin,self._npm_exec_log)) + else: + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} install 2>&1 >> {}".format(project_find['path'],npm_bin,self._npm_exec_log)) + public.writeFile(self._npm_exec_log,"|-Successify --- Command executed! ---",'a+') + public.WriteLog(self._log_name, 'Node project: {}, the installation of the dependency package is complete!'.format(project_find['name'])) + if rebuild: # 重新构建已安装模块? + self.rebuild_project(get.project_name) + return_message=public.return_data(True,'The dependency package is installed successfully!') + del return_message['status'] + return public.return_message(0,0, return_message) + + + + def update_packages(self,get): + ''' + @name 更新指定项目的依赖包 + @author hwliang<2021-08-10> + @param get{ + project_name: string<项目名称> + } + return dict + ''' + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not os.path.exists(project_find['path']): + return_message=public.return_error('The project directory does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + package_file = '{}/package.json'.format(project_find['path']) + if not os.path.exists(package_file): + return_message=public.return_error('The package.json configuration file was not found in the project directory!') + del return_message['status'] + return public.return_message(-1,0, return_message) + package_lock_file = '{}/package-lock.json'.format(project_find['path']) + if not os.path.exists(package_lock_file): + return_message=public.return_error('Please install the dependency package first!') + del return_message['status'] + return public.return_message(-1,0, return_message) + nodejs_version = project_find['project_config']['nodejs_version'] + npm_bin = self.get_npm_bin(nodejs_version) + if not npm_bin: + return_message=public.return_error('The specified nodejs version does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} update &> {}".format(project_find['path'],npm_bin,self._npm_exec_log)) + public.WriteLog(self._log_name, 'Project [{}] update all dependent packages'.format(get.project_name)) + return_message=public.return_data(True,'Dependent package updated successfully!') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def reinstall_packages(self,get): + ''' + @name 重新安装指定项目的依赖包 + @author hwliang<2021-08-10> + @param get{ + project_name: string<项目名称> + } + return dict + ''' + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not os.path.exists(project_find['path']): + return_message=public.return_error('The project directory does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + package_file = '{}/package.json'.format(project_find['path']) + if not os.path.exists(package_file): + return_message=public.return_error('The package.json configuration file was not found in the project directory!') + del return_message['status'] + return public.return_message(-1,0, return_message) + + package_lock_file = '{}/package-lock.json'.format(project_find['path']) + if os.path.exists(package_lock_file): os.remove(package_lock_file) + package_path = '{}/node_modules' + if os.path.exists(package_path): shutil.rmtree(package_path) + nodejs_version = project_find['project_config']['nodejs_version'] + npm_bin = self.get_npm_bin(nodejs_version) + if not npm_bin: + return_message=public.return_error('The specified nodejs version does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.WriteLog(self._log_name,'Node project: {}, all dependent packages have been reinstalled') + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} install &> {}".format(project_find['path'],npm_bin,self._npm_exec_log)) + return_message=public.return_data(True,'Dependent package reinstalled successfully!') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def get_project_modules(self,get): + ''' + @name 获取指定项目的依赖包列表 + @author hwliang<2021-08-10> + @param get{ + project_name: string<项目名称> + project_cwd: string<项目目录> 可选 + } + return list + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + Param('project_cwd').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not 'project_cwd' in get: + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_cwd = project_find['path'] + else: + project_cwd = get.project_cwd + mod_path = os.path.join(project_cwd,'node_modules') + modules = [] + if not os.path.exists(mod_path): return public.return_message(0,0,modules) + for mod_name in os.listdir(mod_path): + try: + mod_pack_file = os.path.join(mod_path,mod_name,'package.json') + if not os.path.exists(mod_pack_file): continue + mod_pack_info = json.loads(public.readFile(mod_pack_file)) + pack_info = { + "name": mod_name, + "version": mod_pack_info['version'], + "description":mod_pack_info['description'], + "license": mod_pack_info['license'] if 'license' in mod_pack_info else 'NULL', + "homepage": mod_pack_info['homepage'] + } + modules.append(pack_info) + except: + continue + return public.return_message(0,0,modules) + + def install_module(self,get): + ''' + @name 安装指定模块 + @author hwliang<2021-08-10> + @param get{ + project_name: string<项目名称> + mod_name: string<模块名称> + } + @return dict + ''' + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_cwd = project_find['path'] + + + mod_name = get.mod_name + filename = '{}/node_modules/{}/package.json'.format(project_cwd,mod_name) + if os.path.exists(filename): + return_message=public.return_error('The specified module has been installed!') + del return_message['status'] + return public.return_message(-1,0, return_message) + + nodejs_version = project_find['project_config']['nodejs_version'] + npm_bin = self.get_npm_bin(nodejs_version) + yarn_bin = self.get_yarn_bin(nodejs_version) + + if not npm_bin and not yarn_bin: + return_message=public.return_error('The specified nodejs version does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + if yarn_bin: + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} add {} &> {}".format(project_find['path'],yarn_bin,mod_name,self._npm_exec_log)) + else: + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} install {} &> {}".format(project_find['path'],npm_bin,mod_name,self._npm_exec_log)) + if not os.path.exists(filename): + return_message=public.return_error('Failed to install the specified module!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.WriteLog(self._log_name,'Node project {}, {} module installation is complete!'.format(get.project_name,mod_name)) + return_message=public.return_data(True,'Successful installation!') + del return_message['status'] + return public.return_message(0,0, return_message) + + def uninstall_module(self,get): + ''' + @name 卸载指定模块 + @author hwliang<2021-04-08> + @param get{ + project_name: string<项目名称> + mod_name: string<模块名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + Param('mod_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_cwd = project_find['path'] + + mod_name = get.mod_name + filename = '{}/node_modules/{}/package.json'.format(project_cwd,mod_name) + if not os.path.exists(filename): + return_message=public.return_error('The specified module is not installed!') + del return_message['status'] + return public.return_message(-1,0, return_message) + + nodejs_version = project_find['project_config']['nodejs_version'] + npm_bin = self.get_npm_bin(nodejs_version) + yarn_bin = self.get_yarn_bin(nodejs_version) + if not npm_bin and not yarn_bin: + return_message=public.return_error('The specified nodejs version does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + if yarn_bin: + result = public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} remove {}".format(project_find['path'],yarn_bin,mod_name)) + else: + result = public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} uninstall {}".format(project_find['path'],npm_bin,mod_name)) + if os.path.exists(filename): + result = "\n".join(result) + if result.find('looking for funding') != -1: + return_message=public.return_error("This module is dependent on other installed modules and cannot be uninstalled!") + del return_message['status'] + return public.return_message(-1,0, return_message) + return_message=public.return_error("Unable to uninstall this module!") + del return_message['status'] + return public.return_message(-1,0, return_message) + + public.WriteLog(self._log_name,'Node project {}, {} module uninstallation completed!'.format(get.project_name,mod_name)) + return_message=public.return_data(True,'Module unloaded successfully!') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def upgrade_module(self,get): + ''' + @name 更新指定模块 + @author hwliang<2021-08-10> + @param get{ + project_name: string<项目名称> + mod_name: string<模块名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + Param('mod_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_cwd = project_find['path'] + + mod_name = get.mod_name + filename = '{}/node_modules/{}/package.json'.format(project_cwd,mod_name) + if not os.path.exists(filename): + return_message=public.return_error('The specified module is not installed!') + del return_message['status'] + return public.return_message(-1,0, return_message) + nodejs_version = project_find['project_config']['nodejs_version'] + npm_bin = self.get_npm_bin(nodejs_version) + + if not npm_bin: + return_message=public.return_error('The specified nodejs version does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} update {} &> {}".format(project_find['path'],npm_bin,mod_name,self._npm_exec_log)) + public.WriteLog(self._log_name,'Node project {}, {} module update completed!'.format(get.project_name,mod_name)) + return_message=public.return_data(True,'Module updated successfully!') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def create_project(self,get): + ''' + @name 创建新的项目 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + project_cwd: string<项目目录> + project_script: string<项目脚本> + project_ps: string<项目备注信息> + bind_extranet: int<是否绑定外网> 1:是 0:否 + domains: list<域名列表> ["domain1:80","domain2:80"] // 在bind_extranet=1时,需要填写 + is_power_on: int<是否开机启动> 1:是 0:否 + run_user: string<运行用户> + max_memory_limit: int<最大内存限制> // 超出此值项目将被强制重启 + nodejs_version: string + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_cwd').String(), + Param('project_name').String(), + Param('project_script').String(), + Param('port').String(), + Param('run_user').String(), + Param('nodejs_version').String(), + Param('project_ps').String(), + Param('domains').List(), + Param('project_env').String(), + Param('bind_extranet').Integer(), + Param('is_power_on').Integer(), + Param('max_memory_limit').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not isinstance(get,public.dict_obj): + return_message=public.return_error('The parameter type is wrong, need dict obj object') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not self.is_install_nodejs(get): + return_message=public.return_error('Please install nodejs version manager first') + del return_message['status'] + return public.return_message(-1,0, return_message) + + project_name = get.project_name.strip() + if not re.match(r"^\w+$",project_name): + return_message=public.return_error('The project name format is incorrect and supports letters, numbers, underscores, and expressions: ^[0-9A-Za-z_]$') + del return_message['status'] + return public.return_message(-1,0, return_message) + + if public.M('sites').where('name=?',(get.project_name,)).count(): + return_message=public.return_error('The specified project name already exists: {}'.format(get.project_name)) + del return_message['status'] + return public.return_message(-1,0, return_message) + get.project_cwd = get.project_cwd.strip() + if not os.path.exists(get.project_cwd): + return_message=public.return_error('The project directory does not exist: {}'.format(get.project_cwd)) + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 端口占用检测 + if self.check_port_is_used(get.get('port/port')): + return_message=public.return_error('This port is already occupied, please modify your project port, port: {}'.format(get.port)) + del return_message['status'] + return public.return_message(-1,0, return_message) + + domains = [] + if get.bind_extranet == 1: + domains = get.domains + if not public.is_apache_nginx(): + return_message=public.return_error('Please install Nginx or Apache first') + del return_message['status'] + return public.return_message(-1,0, return_message) + for domain in domains: + domain_arr = domain.split(':') + if public.M('domain').where('name=?',domain_arr[0]).count(): + return_message=public.return_error('Domain name already exists: {}'.format(domain)) + del return_message['status'] + return public.return_message(-1,0, return_message) + pdata = { + 'name': get.project_name, + 'path': get.project_cwd, + 'ps': get.project_ps, + 'status':1, + 'type_id':0, + 'project_type': 'Node', + 'project_config': json.dumps( + { + 'project_name': get.project_name, + 'project_cwd': get.project_cwd, + 'project_script': get.project_script, + 'bind_extranet': get.bind_extranet, + 'domains': [], + 'is_power_on': get.is_power_on, + 'run_user': get.run_user, + 'max_memory_limit': get.max_memory_limit, + 'nodejs_version': get.nodejs_version, + 'port': int(get.port) + } + ), + 'addtime': public.getDate() + } + + project_id = public.M('sites').insert(pdata) + if get.bind_extranet == 1: + format_domains = [] + for domain in domains: + if domain.find(':') == -1: domain += ':80' + format_domains.append(domain) + get.domains = format_domains + self.project_add_domain(get) + self.set_config(get.project_name) + public.WriteLog(self._log_name,'Add Node.js project {}'.format(get.project_name)) + self.install_packages(get) + self.start_project(get) + return_message=public.return_data(True,'Added project successfully',project_id) + del return_message['status'] + return public.return_message(0,0, return_message) + + def modify_project(self,get): + ''' + @name 修改指定项目 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + project_cwd: string<项目目录> + project_script: string<项目脚本> + project_ps: string<项目备注信息> + is_power_on: int<是否开机启动> 1:是 0:否 + run_user: string<运行用户> + max_memory_limit: int<最大内存限制> // 超出此值项目将被强制重启 + nodejs_version: string + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_cwd').String(), + Param('project_name').String(), + Param('project_script').String(), + Param('port').String(), + Param('run_user').String(), + Param('nodejs_version').String(), + Param('project_ps').String(), + Param('domains').String(), + Param('bind_extranet').Integer(), + Param('is_power_on').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not isinstance(get,public.dict_obj): + return_message=public.return_error('The parameter type is wrong, need dict obj') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not self.is_install_nodejs(get): + return_message=public.return_error('Please install nodejs version manager before installing at least one nodejs') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('Item does not exist: {}'.format(get.project_name)) + del return_message['status'] + return public.return_message(-1,0, return_message) + + if not os.path.exists(get.project_cwd): + return_message=public.return_error('The project directory does not exist: {}'.format(get.project_cwd)) + del return_message['status'] + return public.return_message(-1,0, return_message) + rebuild = False + if hasattr(get,'port'): + if int(project_find['project_config']['port']) != int(get.port): + if self.check_port_is_used(get.get('port/port'),True): + return_message=public.return_error('The port is already occupied, please modify your port, port: {}'.format(get.port)) + del return_message['status'] + return public.return_message(-1,0, return_message) + project_find['project_config']['port'] = int(get.port) + if hasattr(get,'project_cwd'): project_find['project_config']['project_cwd'] = get.project_cwd + if hasattr(get,'project_script'): + if not get.project_script.strip(): + return_message=public.return_error('Start command cannot be empty') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_find['project_config']['project_script'] = get.project_script.strip() + if hasattr(get,'is_power_on'): project_find['project_config']['is_power_on'] = get.is_power_on + if hasattr(get,'run_user'): project_find['project_config']['run_user'] = get.run_user + if hasattr(get,'max_memory_limit'): project_find['project_config']['max_memory_limit'] = get.max_memory_limit + if hasattr(get,'nodejs_version'): + if project_find['project_config']['nodejs_version'] != get.nodejs_version: + rebuild = True + project_find['project_config']['nodejs_version'] = get.nodejs_version + pdata = { + 'path': get.project_cwd, + 'ps': get.project_ps, + 'project_config': json.dumps(project_find['project_config']) + } + + public.M('sites').where('name=?',(get.project_name,)).update(pdata) + self.set_config(get.project_name) + public.WriteLog(self._log_name,'Modify Node.js project {}'.format(get.project_name)) + if rebuild: + self.rebuild_project(get.project_name) + return_message=public.return_data(True,'Modify the project successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def rebuild_project(self,project_name): + ''' + @name 重新构建指定项目 + @author hwliang<2021-08-26> + @param project_name: string<项目名称> + @return bool + ''' + project_find = self.get_project_find(project_name) + if not project_find: return False + nodejs_version = project_find['project_config']['nodejs_version'] + npm_bin = self.get_npm_bin(nodejs_version) + + public.ExecShell(self.get_last_env(nodejs_version) + "cd {} && {} rebuild 2>&1 >> {}".format(project_find['path'],npm_bin,self._npm_exec_log)) + return True + + + def remove_project(self,get): + ''' + @name 删除指定项目 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist: {}'.format(get.project_name)) + del return_message['status'] + return public.return_message(-1,0, return_message) + + self.stop_project(get) + self.clear_config(get.project_name) + public.M('domain').where('pid=?',(project_find['id'],)).delete() + public.M('sites').where('name=?',(get.project_name,)).delete() + + pid_file = "{}/{}.pid".format(self._node_pid_path,get.project_name) + if os.path.exists(pid_file): os.remove(pid_file) + script_file = '{}/{}.sh'.format(self._node_run_scripts,get.project_name) + if os.path.exists(script_file): os.remove(script_file) + log_file = '{}/{}.log'.format(self._node_logs_path,get.project_name) + if os.path.exists(log_file): os.remove(log_file) + public.WriteLog(self._log_name,'Delete Node.js project {}'.format(get.project_name)) + return_message=public.return_data(True,'Successfully deleted item') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def project_get_domain(self,get): + ''' + @name 获取指定项目的域名列表 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + project_id = public.M('sites').where('name=?',(get.project_name,)).getField('id') + domains = public.M('domain').where('pid=?',(project_id,)).order('id desc').select() + project_find = self.get_project_find(get.project_name) + if len(domains) != len(project_find['project_config']['domains']): + public.M('domain').where('pid=?',(project_id,)).delete() + if not project_find: return public.return_message(0,0,[]) + for d in project_find['project_config']['domains']: + domain = {} + arr = d.split(':') + if len(arr) < 2: arr.append(80) + domain['name'] = arr[0] + domain['port'] = int(arr[1]) + domain['pid'] = project_id + domain['addtime'] = public.getDate() + public.M('domain').insert(domain) + if project_find['project_config']['domains']: + domains = public.M('domain').where('pid=?',(project_id,)).select() + return public.return_message(0,0,domains) + + + def project_add_domain(self,get): + ''' + @name 为指定项目添加域名 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + domains: list<域名列表> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + Param('domains').List(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist',data='') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_id = project_find['id'] + + domains = get.domains + success_list = [] + error_list = [] + for domain in domains: + domain = domain.strip() + if not domain: + return_message=public.return_error('Domain name cannot be empty',data='') + del return_message['status'] + return public.return_message(-1,0, return_message) + domain_arr = domain.split(':') + if len(domain_arr) == 1: + domain_arr.append(80) + domain += ':80' + if not public.M('domain').where('name=?',(domain_arr[0],)).count(): + public.M('domain').add('name,pid,port,addtime',(domain_arr[0],project_id,domain_arr[1],public.getDate())) + if not domain in project_find['project_config']['domains']: + project_find['project_config']['domains'].append(domain) + public.WriteLog(self._log_name,'Successfully added the domain [{}] to the project [{}]'.format(domain,get.project_name)) + success_list.append(domain) + else: + public.WriteLog(self._log_name,'Domain [{}] already exists'.format(domain)) + error_list.append(domain) + + if success_list: + public.M('sites').where('id=?',(project_id,)).save('project_config',json.dumps(project_find['project_config'])) + self.set_config(get.project_name) + return_message=public.return_data(True,"[{}] domain names added successfully, [{}] failed!".format(len(success_list),len(error_list)),error_msg=error_list) + del return_message['status'] + return public.return_message(0,0, return_message) + return_message=public.return_data(False,"[{}] domain names added successfully, [{}] failed!".format(len(success_list),len(error_list)),error_msg=error_list) + del return_message['status'] + return public.return_message(-1,0, return_message) + + + def project_remove_domain(self,get): + ''' + @name 为指定项目删除域名 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + domain: string<域名> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + Param('domain').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('The specified item does not exist') + del return_message['status'] + return public.return_message(-1,0, return_message) + last_domain = get.domain + domain_arr = get.domain.split(':') + if len(domain_arr) == 1: + domain_arr.append(80) + + project_id = public.M('sites').where('name=?',(get.project_name,)).getField('id') + if project_find['project_config']['bind_extranet']: + if len(project_find['project_config']['domains']) == 1: + return_message=public.return_error('At least one domain name is required for the mapped project') + del return_message['status'] + return public.return_message(-1,0, return_message) + domain_id = public.M('domain').where('name=? AND pid=?',(domain_arr[0],project_id)).getField('id') + if not domain_id: + return_message=public.return_error('The specified domain name does not exist') + del return_message['status'] + return public.return_message(-1,0, return_message) + public.M('domain').where('id=?',(domain_id,)).delete() + + if get.domain in project_find['project_config']['domains']: + project_find['project_config']['domains'].remove(get.domain) + if get.domain+":80" in project_find['project_config']['domains']: + project_find['project_config']['domains'].remove(get.domain + ":80") + + public.M('sites').where('id=?',(project_id,)).save('project_config',json.dumps(project_find['project_config'])) + public.WriteLog(self._log_name,'From project: [{}], delete domain name [{}]'.format(get.project_name,get.domain)) + self.set_config(get.project_name) + return_message=public.return_data(True,'Domain name deleted successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def bind_extranet(self,get): + ''' + @name 绑定外网 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + Param('domains').List(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not public.is_apache_nginx(): + return_message=public.return_error('Please install Nginx or Apache first') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_name = get.project_name.strip() + project_find = self.get_project_find(project_name) + if not project_find: + return_message=public.return_error('Item does not exist') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not project_find['project_config']['domains']: + return_message=public.return_error('Please add at least one domain name in the [Domain Management] option') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_find['project_config']['bind_extranet'] = 1 + public.M('sites').where("id=?",(project_find['id'],)).setField('project_config',json.dumps(project_find['project_config'])) + self.set_config(project_name) + public.WriteLog(self._log_name,'Node project{}, enable mapping'.format(project_name)) + return_message=public.return_data(True,'Enable the mapping successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def set_config(self,project_name): + ''' + @name 设置项目配置 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return bool + ''' + project_find = self.get_project_find(project_name) + if not project_find: return False + if not project_find['project_config']: return False + if not project_find['project_config']['bind_extranet']: return False + if not project_find['project_config']['domains']: return False + self.set_nginx_config(project_find) + self.set_apache_config(project_find) + public.serviceReload() + return True + + def clear_config(self,project_name): + ''' + @name 清除项目配置 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return bool + ''' + project_find = self.get_project_find(project_name) + if not project_find: return False + self.clear_nginx_config(project_find) + self.clear_apache_config(project_find) + public.serviceReload() + return True + + def clear_apache_config(self,project_find): + ''' + @name 清除apache配置 + @author hwliang<2021-08-09> + @param project_find: dict<项目信息> + @return bool + ''' + project_name = project_find['name'] + config_file = "{}/apache/node_{}.conf".format(self._vhost_path,project_name) + if os.path.exists(config_file): + os.remove(config_file) + return True + + + def clear_nginx_config(self,project_find): + ''' + @name 清除nginx配置 + @author hwliang<2021-08-09> + @param project_find: dict<项目信息> + @return bool + ''' + project_name = project_find['name'] + config_file = "{}/nginx/node_{}.conf".format(self._vhost_path,project_name) + if os.path.exists(config_file): + os.remove(config_file) + rewrite_file = "{panel_path}/vhost/rewrite/node_{project_name}.conf".format(panel_path = self._panel_path,project_name = project_name) + if os.path.exists(rewrite_file): + os.remove(rewrite_file) + return True + + + def set_nginx_config(self,project_find): + ''' + @name 设置Nginx配置 + @author hwliang<2021-08-09> + @param project_find: dict<项目信息> + @return bool + ''' + project_name = project_find['name'] + ports = [] + domains = [] + + for d in project_find['project_config']['domains']: + domain_tmp = d.split(':') + if len(domain_tmp) == 1: domain_tmp.append(80) + if not int(domain_tmp[1]) in ports: + ports.append(int(domain_tmp[1])) + if not domain_tmp[0] in domains: + domains.append(domain_tmp[0]) + listen_ipv6 = public.listen_ipv6() + listen_ports = '' + for p in ports: + listen_ports += " listen {};\n".format(p) + if listen_ipv6: + listen_ports += " listen [::]:{};\n".format(p) + listen_ports = listen_ports.strip() + + is_ssl,is_force_ssl = self.exists_nginx_ssl(project_name) + ssl_config = '' + if is_ssl: + listen_ports += "\n listen 443 ssl http2;" + if listen_ipv6: listen_ports += "\n listen [::]:443 ssl http2;" + + ssl_config = '''ssl_certificate {vhost_path}/cert/{priject_name}/fullchain.pem; + ssl_certificate_key {vhost_path}/cert/{priject_name}/privkey.pem; + ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + add_header Strict-Transport-Security "max-age=31536000"; + error_page 497 https://$host$request_uri;'''.format(vhost_path = self._vhost_path,priject_name = project_name) + + if is_force_ssl: + ssl_config += ''' + #HTTP_TO_HTTPS_START + if ($server_port !~ 443){ + rewrite ^(/.*)$ https://$host$1 permanent; + } + #HTTP_TO_HTTPS_END''' + + config_file = "{}/nginx/node_{}.conf".format(self._vhost_path,project_name) + template_file = "{}/template/nginx/node_http.conf".format(self._vhost_path) + + config_body = public.readFile(template_file) + config_body = config_body.format( + site_path = project_find['path'], + domains = ' '.join(domains), + project_name = project_name, + panel_path = self._panel_path, + log_path = public.get_logs_path(), + url = 'http://127.0.0.1:{}'.format(project_find['project_config']['port']), + host = '$host', + listen_ports = listen_ports, + ssl_config = ssl_config + ) + + # # 恢复旧的SSL配置 + # ssl_config = self.get_nginx_ssl_config(project_name) + # if ssl_config: + # config_body.replace('#error_page 404/404.html;',ssl_config) + + + rewrite_file = "{panel_path}/vhost/rewrite/node_{project_name}.conf".format(panel_path = self._panel_path,project_name = project_name) + if not os.path.exists(rewrite_file): public.writeFile(rewrite_file,'# Please fill in the URLrewrite or custom NGINX config here\n') + public.writeFile(config_file,config_body) + return True + + def get_nginx_ssl_config(self,project_name): + ''' + @name 获取项目Nginx SSL配置 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return string + ''' + result = '' + config_file = "{}/nginx/node_{}".format(self._vhost_path,project_name) + if not os.path.exists(config_file): + return result + + config_body = public.readFile(config_file) + if not config_body: + return result + if config_body.find('ssl_certificate') == -1: + return result + + ssl_body = re.search("#SSL-START(.|\n)+#SSL-END",config_body) + if not ssl_body: return result + result = ssl_body.group() + return result + + def exists_nginx_ssl(self,project_name): + ''' + @name 判断项目是否配置Nginx SSL配置 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return tuple + ''' + config_file = "{}/nginx/node_{}.conf".format(public.get_vhost_path(),project_name) + if not os.path.exists(config_file): + return False,False + + config_body = public.readFile(config_file) + if not config_body: + return False,False + + is_ssl,is_force_ssl = False,False + if config_body.find('ssl_certificate') != -1: + is_ssl = True + if config_body.find('HTTP_TO_HTTPS_START') != -1: + is_force_ssl = True + return is_ssl,is_force_ssl + + def exists_apache_ssl(self,project_name): + ''' + @name 判断项目是否配置Apache SSL配置 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return bool + ''' + config_file = "{}/apache/node_{}.conf".format(public.get_vhost_path(),project_name) + if not os.path.exists(config_file): + return False,False + + config_body = public.readFile(config_file) + if not config_body: + return False,False + + is_ssl,is_force_ssl = False,False + if config_body.find('SSLCertificateFile') != -1: + is_ssl = True + if config_body.find('HTTP_TO_HTTPS_START') != -1: + is_force_ssl = True + return is_ssl,is_force_ssl + + def set_apache_config(self,project_find): + ''' + @name 设置Apache配置 + @author hwliang<2021-08-09> + @param project_find: dict<项目信息> + @return bool + ''' + project_name = project_find['name'] + + # 处理域名和端口 + ports = [] + domains = [] + for d in project_find['project_config']['domains']: + domain_tmp = d.split(':') + if len(domain_tmp) == 1: domain_tmp.append(80) + if not int(domain_tmp[1]) in ports: + ports.append(int(domain_tmp[1])) + if not domain_tmp[0] in domains: + domains.append(domain_tmp[0]) + + + config_file = "{}/apache/node_{}.conf".format(self._vhost_path,project_name) + template_file = "{}/template/apache/node_http.conf".format(self._vhost_path) + config_body = public.readFile(template_file) + apache_config_body = '' + + # 旧的配置文件是否配置SSL + is_ssl,is_force_ssl = self.exists_apache_ssl(project_name) + if is_ssl: + if not 443 in ports: ports.append(443) + + from panelSite import panelSite + s = panelSite() + + # 根据端口列表生成配置 + for p in ports: + # 生成SSL配置 + ssl_config = '' + if p == 443 and is_ssl: + ssl_key_file = "{vhost_path}/cert/{project_name}/privkey.pem".format(project_name = project_name,vhost_path = public.get_vhost_path()) + if not os.path.exists(ssl_key_file): continue # 不存在证书文件则跳过 + ssl_config = '''#SSL + SSLEngine On + SSLCertificateFile {vhost_path}/cert/{project_name}/fullchain.pem + SSLCertificateKeyFile {vhost_path}/cert/{project_name}/privkey.pem + SSLCipherSuite EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5 + SSLProtocol All -SSLv2 -SSLv3 -TLSv1 + SSLHonorCipherOrder On'''.format(project_name = project_name,vhost_path = public.get_vhost_path()) + else: + if is_force_ssl: + ssl_config = '''#HTTP_TO_HTTPS_START + + RewriteEngine on + RewriteCond %{SERVER_PORT} !^443$ + RewriteRule (.*) https://%{SERVER_NAME}$1 [L,R=301] + + #HTTP_TO_HTTPS_END''' + + # 生成vhost主体配置 + apache_config_body += config_body.format( + site_path = project_find['path'], + server_name = '{}.{}'.format(p,project_name), + domains = ' '.join(domains), + log_path = public.get_logs_path(), + server_admin = 'admin@{}'.format(project_name), + url = 'http://127.0.0.1:{}'.format(project_find['project_config']['port']), + port = p, + ssl_config = ssl_config, + project_name = project_name + ) + apache_config_body += "\n" + + # 添加端口到主配置文件 + if not p in [80]: + s.apacheAddPort(p) + + # 写.htaccess + rewrite_file = "{}/.htaccess".format(project_find['path']) + if not os.path.exists(rewrite_file): public.writeFile(rewrite_file,'# Please fill in the URLrewrite rules or custom Apache config here\n') + + # 写配置文件 + public.writeFile(config_file,apache_config_body) + return True + + + def unbind_extranet(self,get): + ''' + @name 解绑外网 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + project_name = get.project_name.strip() + self.clear_config(project_name) + public.serviceReload() + project_find = self.get_project_find(project_name) + project_find['project_config']['bind_extranet'] = 0 + public.M('sites').where("id=?",(project_find['id'],)).setField('project_config',json.dumps(project_find['project_config'])) + public.WriteLog(self._log_name,'Node project {}, disable the mapping'.format(project_name)) + return_message=public.return_data(True,'Disabled successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def get_project_pids(self,get = None,pid = None): + ''' + @name 获取项目进程pid列表 + @author hwliang<2021-08-10> + @param pid: string<项目pid> + @return list + ''' + if get: pid = int(get.pid) + if not self._pids: self._pids = psutil.pids() + project_pids = [] + + for i in self._pids: + try: + p = psutil.Process(i) + except: continue + if p.ppid() == pid: + if i in project_pids: continue + if p.name() in ['bash']: continue + project_pids.append(i) + + other_pids = [] + for i in project_pids: + other_pids += self.get_project_pids(pid=i) + if os.path.exists('/proc/{}'.format(pid)): + project_pids.append(pid) + + all_pids = list(set(project_pids + other_pids)) + if not all_pids: + all_pids = self.get_other_pids(pid) + return public.return_message(0,0,sorted(all_pids)) + + def get_other_pids(self,pid): + ''' + @name 获取其他进程pid列表 + @author hwliang<2021-08-10> + @param pid: string<项目pid> + @return list + ''' + project_name = None + for pid_name in os.listdir(self._node_pid_path): + pid_file = '{}/{}'.format(self._node_pid_path,pid_name) + #s_pid = int(public.readFile(pid_file)) + data = public.readFile(pid_file) + if isinstance(data,str) and data: + s_pid = int(data) + else: + return public.return_message(0,0,[]) + if pid == s_pid: + project_name = pid_name[:-4] + break + project_find = self.get_project_find(project_name) + if not project_find: return public.return_message(0,0,[]) + if not self._pids: self._pids = psutil.pids() + all_pids = [] + for i in self._pids: + try: + p = psutil.Process(i) + if p.cwd() == project_find['path']: + pname = p.name() + if pname in ['node','npm','pm2','yarn'] or pname.find('node ') == 0: + cmdline = ','.join(p.cmdline()) + if cmdline.find('God Daemon') != -1:continue + env_list = p.environ() + if 'name' in env_list: + if not env_list['name'] == project_name: continue + if 'NODE_PROJECT_NAME' in env_list: + if not env_list['NODE_PROJECT_NAME'] == project_name: continue + all_pids.append(i) + except: continue + return public.return_message(0,0,all_pids) + + def get_project_state_by_cwd(self,project_name): + ''' + @name 通过cwd获取项目状态 + @author hwliang<2022-01-17> + @param project_name 项目名称 + @return bool or list + ''' + project_find = self.get_project_find(project_name) + self._pids = psutil.pids() + if not project_find: return [] + all_pids = [] + for i in self._pids: + try: + p = psutil.Process(i) + if p.cwd() == project_find['path']: + pname = p.name() + if pname in ['node','npm','pm2','yarn'] or pname.find('node ') == 0: + cmdline = ','.join(p.cmdline()) + if cmdline.find('God Daemon') != -1:continue + env_list = p.environ() + if 'name' in env_list: + if not env_list['name'] == project_name: continue + if 'NODE_PROJECT_NAME' in env_list: + if not env_list['NODE_PROJECT_NAME'] == project_name: continue + all_pids.append(i) + except: continue + if all_pids: + pid_file = "{}/{}.pid".format(self._node_pid_path,project_name) + public.writeFile(pid_file,str(all_pids[0])) + return all_pids + return False + + def kill_pids(self,get=None,pids = None): + ''' + @name 结束进程列表 + @author hwliang<2021-08-10> + @param pids: string<进程pid列表> + @return dict + ''' + if get: pids = get.pids + if not pids: + return_message=public.return_data(True, 'No process') + del return_message['status'] + return public.return_message(0,0, return_message) + pids = sorted(pids,reverse=True) + for i in pids: + try: + p = psutil.Process(i) + p.kill() + except: + pass + return_message=public.return_data(True, 'The process has all ended') + del return_message['status'] + return public.return_message(0,0, return_message) + + + + + def start_project(self,get): + ''' + @name 启动项目 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + pid_file = "{}/{}.pid".format(self._node_pid_path,get.project_name) + if os.path.exists(pid_file): + self.stop_project(get) + + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('Item does not exist') + del return_message['status'] + return public.return_message(-1,0, return_message) + + if not os.path.exists(project_find['path']): + error_msg = 'Startup failed, Nodejs project {}, running directory {} does not exist!'.format(get.project_name,project_find['path']) + public.WriteLog(self._log_name,error_msg) + return_message=public.return_error(error_msg) + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 是否安装依赖模块? + package_file = "{}/package.json".format(project_find['path']) + package_info = {} + if os.path.exists(package_file): + node_modules_path = "{}/node_modules".format(project_find['path']) + if not os.path.exists(node_modules_path): + return_message=public.return_error('Please go to the [Module] and click [One-key install] to install the module dependencies!') + del return_message['status'] + return public.return_message(-1,0, return_message) + package_info = json.loads(public.readFile(package_file)) + if not package_info: package_info['scripts'] = {} + if 'scripts' not in package_info: package_info['scripts'] = {} + try: + scripts_keys = package_info['scripts'].keys() + except: + scripts_keys = [] + + + # 前置准备 + nodejs_version = project_find['project_config']['nodejs_version'] + node_bin = self.get_node_bin(nodejs_version) + npm_bin = self.get_npm_bin(nodejs_version) + project_script = project_find['project_config']['project_script'].strip().replace(' ',' ') + if project_script[:3] == 'pm2': # PM2启动方式处理 + project_script = project_script.replace('pm2 ','pm2 -u {} -n {} '.format(project_find['project_config']['run_user'],get.project_name)) + project_find['project_config']['run_user'] = 'root' + log_file = "{}/{}.log".format(self._node_logs_path,get.project_name) + if not project_script: + return_message=public.return_error('No startup script configured') + del return_message['status'] + return public.return_message(-1,0, return_message) + + last_env = self.get_last_env(nodejs_version,project_find['path']) + + # 生成启动脚本 + if os.path.exists(project_script): + start_cmd = '''{last_env} +export NODE_PROJECT_NAME="{project_name}" +cd {project_cwd} +nohup {node_bin} {project_script} 2>&1 >> {log_file} & +echo $! > {pid_file} +'''.format( + project_cwd = project_find['path'], + node_bin = node_bin, + project_script = project_script, + log_file = log_file, + pid_file = pid_file, + last_env = last_env, + project_name = get.project_name +) + elif project_script in scripts_keys: + start_cmd = '''{last_env} +export NODE_PROJECT_NAME="{project_name}" +cd {project_cwd} +nohup {npm_bin} run {project_script} 2>&1 >> {log_file} & +echo $! > {pid_file} +'''.format( + project_cwd = project_find['path'], + npm_bin = npm_bin, + project_script = project_script, + pid_file = pid_file, + log_file = log_file, + last_env = last_env, + project_name = get.project_name +) + else: + start_cmd = '''{last_env} +export NODE_PROJECT_NAME="{project_name}" +cd {project_cwd} +nohup {project_script} 2>&1 >> {log_file} & +echo $! > {pid_file} +'''.format( + project_cwd = project_find['path'], + project_script = project_script, + pid_file = pid_file, + log_file = log_file, + last_env = last_env, + project_name = get.project_name +) + script_file = "{}/{}.sh".format(self._node_run_scripts,get.project_name) + + # 写入启动脚本 + public.writeFile(script_file,start_cmd) + if os.path.exists(pid_file): os.remove(pid_file) + + # 处理前置权限 + public.ExecShell("chown -R {user}:{user} {project_cwd}".format(user=project_find['project_config']['run_user'],project_cwd=project_find['path'])) + public.ExecShell("chown -R www:www {}/vhost".format(self._nodejs_path)) + public.ExecShell("chmod 755 {} {} {}".format(self._nodejs_path,public.get_setup_path(),'/www')) + public.set_own(script_file,project_find['project_config']['run_user'],project_find['project_config']['run_user']) + public.set_mode(script_file,755) + + # 执行脚本文件 + p = public.ExecShell("bash {}".format(script_file),user=project_find['project_config']['run_user']) + time.sleep(1) + n = 0 + while n < 5: + if self.get_project_state_by_cwd(get.project_name): break + n+=1 + if not os.path.exists(pid_file): + p = '\n'.join(p) + public.writeFile(log_file,p,"a+") + if p.find('[Errno 0]') != -1: + if os.path.exists('{}/bt_security'.format(public.get_plugin_path())): + return_message=public.return_error('The start command was intercepted by [Fort Tower Defense Privilege], please turn off {} user protection'.format(project_find['project_config']['run_user'])) + del return_message['status'] + return public.return_message(-1,0, return_message) + return_message=public.return_error('The startup command was intercepted by unknown security software, please check the installation software log') + del return_message['status'] + return public.return_message(-1,0, return_message) + return_message=public.return_error('failed to activate
                                    {}
                                    '.format(p)) + del return_message['status'] + return public.return_message(-1,0, return_message) + + # 获取PID + try: + pid = int(public.readFile(pid_file)) + except: + return public.return_error('Startup failed
                                    {}'.format(public.GetNumLines(log_file,20))) + pids = self.get_project_pids(pid=pid) + if not pids: + if os.path.exists(pid_file): os.remove(pid_file) + return_message=public.return_error('failed to activate
                                    {}'.format(public.GetNumLines(log_file,20))) + del return_message['status'] + return public.return_message(-1,0, return_message) + return_message=public.return_data(True, 'Successfully started', pids) + del return_message['status'] + return public.return_message(0,0, return_message) + + + def stop_project(self,get): + ''' + @name 停止项目 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + project_find = self.get_project_find(get.project_name) + if not project_find: + return_message=public.return_error('Project does not exist') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_script = project_find['project_config']['project_script'].strip().replace(' ',' ') + pid_file = "{}/{}.pid".format(self._node_pid_path,get.project_name) + if project_script.find('pm2 start') != -1: # 处理PM2启动的项目 + nodejs_version = project_find['project_config']['nodejs_version'] + last_env = self.get_last_env(nodejs_version,project_find['path']) + project_script = project_script.replace('pm2 start','pm2 stop') + public.ExecShell('''{} +cd {} +{}'''.format(last_env,project_find['path'],project_script)) + else: + pid_file = "{}/{}.pid".format(self._node_pid_path,get.project_name) + if not os.path.exists(pid_file): + return_message=public.return_error('Project did not start') + del return_message['status'] + return public.return_message(-1,0, return_message) + data = public.readFile(pid_file) + if isinstance(data,str) and data: + pid = int(data) + pids = self.get_project_pids(pid=pid) + else: + return_message=public.return_error('Project did not start') + del return_message['status'] + return public.return_message(-1,0, return_message) + if not pids: + return_message=public.return_error('Project did not start') + del return_message['status'] + return public.return_message(-1,0, return_message) + self.kill_pids(pids=pids) + if os.path.exists(pid_file): os.remove(pid_file) + time.sleep(0.5) + pids = self.get_project_state_by_cwd(get.project_name) + if pids: self.kill_pids(pids=pids) + return_message=public.return_data(True, 'Stopped successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + def restart_project(self,get): + ''' + @name 重启项目 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + res = self.stop_project(get) + if res['status']==-1: return res + res = self.start_project(get) + if res['status']==-1: return res + return_message=public.return_data(True, 'Successful restart') + del return_message['status'] + return public.return_message(0,0, return_message) + + # xss 防御 + def xsssec(self,text): + return text.replace('<', '<').replace('>', '>') + + + def get_project_log(self,get): + ''' + @name 获取项目日志 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + log_file = "{}/{}.log".format(self._node_logs_path,get.project_name) + if not os.path.exists(log_file): + return_message=public.return_error('Log file does not exist') + del return_message['status'] + return public.return_message(-1,0, return_message) + return public.return_message(0,0,self.xsssec(public.GetNumLines(log_file,200))) + + + def get_project_load_info(self,get = None,project_name = None): + ''' + @name 获取项目负载信息 + @author hwliang<2021-08-12> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + if get: project_name = get.project_name.strip() + load_info = {} + pid_file = "{}/{}.pid".format(self._node_pid_path,project_name) + if not os.path.exists(pid_file): return public.return_message(0,0,load_info) + data = public.readFile(pid_file) + if isinstance(data,str) and data: + pid = int(data) + pids = self.get_project_pids(pid=pid) + else: + return public.return_message(0,0,load_info) + if not pids: return public.return_message(0,0,load_info) + for i in pids: + process_info = self.get_process_info_by_pid(i) + if process_info: load_info[i] = process_info + return public.return_message(0,0,load_info) + + + def object_to_dict(self,obj): + ''' + @name 将对象转换为字典 + @author hwliang<2021-08-09> + @param obj + @return dict + ''' + result = {} + for name in dir(obj): + value = getattr(obj, name) + if not name.startswith('__') and not callable(value) and not name.startswith('_'): result[name] = value + return result + + + def list_to_dict(self,data): + ''' + @name 将列表转换为字典 + @author hwliang<2021-08-09> + @param data + @return dict + ''' + result = [] + for s in data: + result.append(self.object_to_dict(s)) + return result + + + def get_connects(self,pid): + ''' + @name 获取进程连接信息 + @author hwliang<2021-08-09> + @param pid + @return dict + ''' + connects = 0 + try: + if pid == 1: return connects + tp = '/proc/' + str(pid) + '/fd/' + if not os.path.exists(tp): return connects + for d in os.listdir(tp): + fname = tp + d + if os.path.islink(fname): + l = os.readlink(fname) + if l.find('socket:') != -1: connects += 1 + except:pass + return connects + + + def format_connections(self,connects): + ''' + @name 获取进程网络连接信息 + @author hwliang<2021-08-09> + @param connects + @return list + ''' + result = [] + for i in connects: + raddr = i.raddr + if not i.raddr: + raddr = ('',0) + laddr = i.laddr + if not i.laddr: + laddr = ('',0) + result.append({ + "fd": i.fd, + "family": i.family, + "local_addr": laddr[0], + "local_port": laddr[1], + "client_addr": raddr[0], + "client_rport": raddr[1], + "status": i.status + }) + return result + + + def get_process_info_by_pid(self,pid): + ''' + @name 获取进程信息 + @author hwliang<2021-08-12> + @param pid: int<进程id> + @return dict + ''' + process_info = {} + try: + if not os.path.exists('/proc/{}'.format(pid)): return process_info + p = psutil.Process(pid) + status_ps = {'sleeping':'Sleeping','running':'Running'} + with p.oneshot(): + p_mem = p.memory_full_info() + if p_mem.uss + p_mem.rss + p_mem.pss + p_mem.data == 0: return process_info + p_state = p.status() + if p_state in status_ps: p_state = status_ps[p_state] + # process_info['exe'] = p.exe() + process_info['name'] = p.name() + process_info['pid'] = pid + process_info['ppid'] = p.ppid() + process_info['create_time'] = int(p.create_time()) + process_info['status'] = p_state + process_info['user'] = p.username() + process_info['memory_used'] = p_mem.uss + process_info['cpu_percent'] = self.get_cpu_precent(p) + process_info['io_write_bytes'],process_info['io_read_bytes'] = self.get_io_speed(p) + process_info['connections'] = self.format_connections(p.connections()) + process_info['connects'] = self.get_connects(pid) + process_info['open_files'] = self.list_to_dict(p.open_files()) + process_info['threads'] = p.num_threads() + process_info['exe'] = ' '.join(p.cmdline()) + return process_info + except: + return process_info + + + def get_io_speed(self,p): + ''' + @name 获取磁盘IO速度 + @author hwliang<2021-08-12> + @param p: Process<进程对像> + @return list + ''' + + skey = "io_speed_{}".format(p.pid) + old_pio = cache.get(skey) + if not hasattr(p,'io_counters'): return 0,0 + pio = p.io_counters() + if not old_pio: + cache.set(skey,[pio,time.time()],3600) + # time.sleep(0.1) + old_pio = cache.get(skey) + pio = p.io_counters() + + old_write_bytes = old_pio[0].write_bytes + old_read_bytes = old_pio[0].read_bytes + old_time = old_pio[1] + + new_time = time.time() + write_bytes = pio.write_bytes + read_bytes = pio.read_bytes + + cache.set(skey,[pio,new_time],3600) + + write_speed = int((write_bytes - old_write_bytes) / (new_time - old_time)) + read_speed = int((read_bytes - old_read_bytes) / (new_time - old_time)) + + return write_speed,read_speed + + + + + + def get_cpu_precent(self,p): + ''' + @name 获取进程cpu使用率 + @author hwliang<2021-08-09> + @param p: Process<进程对像> + @return dict + ''' + skey = "cpu_pre_{}".format(p.pid) + old_cpu_times = cache.get(skey) + + process_cpu_time = self.get_process_cpu_time(p.cpu_times()) + if not old_cpu_times: + cache.set(skey,[process_cpu_time,time.time()],3600) + # time.sleep(0.1) + old_cpu_times = cache.get(skey) + process_cpu_time = self.get_process_cpu_time(p.cpu_times()) + + old_process_cpu_time = old_cpu_times[0] + old_time = old_cpu_times[1] + new_time = time.time() + cache.set(skey,[process_cpu_time,new_time],3600) + percent = round(100.00 * (process_cpu_time - old_process_cpu_time) / (new_time - old_time) / psutil.cpu_count(),2) + return percent + + + def get_process_cpu_time(self,cpu_times): + cpu_time = 0.00 + for s in cpu_times: cpu_time += s + return cpu_time + + + def get_project_run_state(self,get = None,project_name = None): + ''' + @name 获取项目运行状态 + @author hwliang<2021-08-12> + @param get{ + project_name: string<项目名称> + } + @param project_name 项目名称 + @return bool + ''' + if get: project_name = get.project_name.strip() + pid_file = "{}/{}.pid".format(self._node_pid_path,project_name) + if not os.path.exists(pid_file): return public.return_message(0,0,False) + data=public.readFile(pid_file) + if isinstance(data,str) and data: + pid = int(data) + pids = self.get_project_pids(pid=pid) + else: + return public.return_message(0,0,self.get_project_state_by_cwd(project_name)) + if not pids: return self.get_project_state_by_cwd(project_name) + return public.return_message(0,0,True) + + def get_project_find(self,project_name): + ''' + @name 获取指定项目配置 + @author hwliang<2021-08-09> + @param project_name 项目名称 + @return dict + ''' + project_info = public.M('sites').where('project_type=? AND name=?',('Node',project_name)).find() + if not project_info: return False + project_info['project_config'] = json.loads(project_info['project_config']) + return project_info + + + def get_project_info(self,get): + ''' + @name 获取指定项目信息 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + } + @return dict + ''' + # 校验参数 + try: + get.validate([ + Param('project_name').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + project_info = public.M('sites').where('project_type=? AND name=?',('Node',get.project_name)).find() + if not project_info: + return_message=public.return_error('The specified item does not exist!') + del return_message['status'] + return public.return_message(-1,0, return_message) + project_info = self.get_project_stat(project_info) + return public.return_message(0,0, project_info) + + + def get_project_stat(self,project_info): + ''' + @name 获取项目状态信息 + @author hwliang<2021-08-09> + @param project_info 项目信息 + @return list + ''' + project_info['project_config'] = json.loads(project_info['project_config']) + project_info['run'] = self.get_project_run_state(project_name = project_info['name']) + project_info['load_info'] = {} + if project_info['run']: + project_info['load_info'] = self.get_project_load_info(project_name = project_info['name'])['message'] + project_info['ssl'] = self.get_ssl_end_date(project_name = project_info['name']) + project_info['listen'] = [] + project_info['listen_ok'] = True + if project_info['load_info']: + for pid in project_info['load_info'].keys(): + if not pid:continue + if not 'connections' in project_info['load_info'][pid]: + project_info['load_info'][pid]['connections'] = [] + for conn in project_info['load_info'][pid]['connections']: + if not conn['status'] == 'LISTEN': continue + if not conn['local_port'] in project_info['listen']: + project_info['listen'].append(conn['local_port']) + if project_info['listen']: + project_info['listen_ok'] = project_info['project_config']['port'] in project_info['listen'] + return project_info + + + + def get_project_state(self,project_name): + ''' + @name 获取项目状态 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return dict + ''' + project_info = public.M('sites').where('project_type=? AND name=?',('Node',project_name)).find() + if not project_info: return False + return project_info['status'] + + def get_project_listen(self,project_name): + ''' + @name 获取项目监听端口 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return dict + ''' + project_config = json.loads(public.M('sites').where('name=?',project_name).getField('project_config')) + if 'listen_port' in project_config: return project_config['listen_port'] + return False + + + def set_project_listen(self,get): + ''' + @name 设置项目监听端口(请设置与实际端口相符的,仅在自动获取不正确时使用) + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + port: int<端口> + } + @return dict + ''' + project_config = json.loads(public.M('sites').where('name=?',get.project_name).getField('project_config')) + project_config['listen_port'] = get.port + public.M('sites').where('name=?',get.project_name).save('project_config',json.dumps(project_config)) + public.WriteLog(self._log_name, 'Modify the port of the project ['+get.project_name+'] to ['+get.port+']') + return_message=public.return_data(True,'Set successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + + def set_project_nodejs_version(self,get): + ''' + @name 设置nodejs版本 + @author hwliang<2021-08-09> + @param get{ + project_name: string<项目名称> + nodejs_version: string + } + @return dict + ''' + + project_config = json.loads(public.M('sites').where('name=?',get.project_name).getField('project_config')) + project_config['nodejs_version'] = get.nodejs_version + public.M('sites').where('name=?',get.project_name).save('project_config',json.dumps(project_config)) + public.WriteLog(self._log_name, 'Modify the nodejs version of the project ['+get.project_name+'] to ['+get.nodejs_version+']') + return_message=public.return_data(True,'Set successfully') + del return_message['status'] + return public.return_message(0,0, return_message) + + def get_project_nodejs_version(self,project_name): + ''' + @name 获取nodejs版本 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return string + ''' + + project_config = json.loads(public.M('sites').where('name=?',project_name).getField('project_config')) + if 'nodejs_version' in project_config: return project_config['nodejs_version'] + return False + + + def check_port_is_used(self,port,sock=False): + ''' + @name 检查端口是否被占用 + @author hwliang<2021-08-09> + @param port: int<端口> + @return bool + ''' + if not isinstance(port,int): port = int(port) + if port == 0: return False + project_list = public.M('sites').where('status=? AND project_type=?',(1,'Node')).field('name,path,project_config').select() + for project_find in project_list: + project_config = json.loads(project_find['project_config']) + if not 'port' in project_config: continue + if int(project_config['port']) == port: return True + if sock: return False + return public.check_tcp('127.0.0.1',port) + + def get_project_run_state_byaotu(self,project_name): + ''' + @name 获取项目运行状态 + @author hwliang<2021-08-09> + @param project_name: string<项目名称> + @return dict + ''' + pid_file = "{}/{}.pid".format(self._node_pid_path,project_name) + if not os.path.exists(pid_file): return False + pid = public.readFile(pid_file) + pids = self.get_project_pids(pid=pid) + if not pids: return False + return True + + def auto_run(self): + ''' + @name 自动启动所有项目 + @author hwliang<2021-08-09> + @return bool + ''' + project_list = public.M('sites').where('project_type=?',('Node',)).field('name,path,project_config').select() + get= public.dict_obj() + success_count = 0 + error_count = 0 + for project_find in project_list: + try: + project_config = json.loads(project_find['project_config']) + if project_config['is_power_on'] in [0,False,'0',None]: continue + project_name = project_find['name'] + project_state = self.get_project_run_state(project_name=project_name) + if not project_state: + get.project_name = project_name + result = self.start_project(get)['message'] + if result['status']==-1: + error_count += 1 + error_msg = 'Automatically start Nodej project ['+project_name+'] failed!' + public.WriteLog(self._log_name, error_msg) + public.print_log(error_msg + ", " + result['error_msg'],'ERROR') + else: + success_count += 1 + success_msg = 'Automatically start the Nodej project ['+project_name+'] successfully!' + public.WriteLog(self._log_name, success_msg) + public.print_log(success_msg,'INFO') + except: + error_count += 1 + public.print_log(public.get_error_info(),'ERROR') + if (success_count + error_count) < 1: return False + dene_msg = 'A total of {} Nodejs projects need to be started, {} successfully and {} failed'.format(success_count + error_count,success_count,error_count) + public.WriteLog(self._log_name, dene_msg) + public.print_log(dene_msg,'INFO') + return True diff --git a/class_v2/projectModelV2/quotaModel.py b/class_v2/projectModelV2/quotaModel.py new file mode 100644 index 00000000..a8d14116 --- /dev/null +++ b/class_v2/projectModelV2/quotaModel.py @@ -0,0 +1,695 @@ +4wZIF38E5nxrLYkW2uUR7d3iaGQLSTjR4DuJynwT7k0= +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +NF4oGUk70PXEJoCfkU1k+A== +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +n+0ptngHIPIjFuMNQ53bftpaK0KYKjbY/JzxOkjVDFkqjO9WVvBZllMi2G3YerYfc6HYWOzoeF74xGLl1Znalv8dm0hrQlyXIHlBtsUjirU= +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +PEKPgJeDDCLnL9UcS39EYZOZmcluSt+RuygO2AupCV4= +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +1u+XjG/2+GSQRv6EzCaWRQ== +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAbFx1CPChpgx6xlsqPUrTBV +Z71ucSR75ppLp8TcGPXjF3pMbrAmHfUi73meeu5lk5g= +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAbFx1CPChpgx6xlsqPUrTBV +ysVvGAkjYd+CMvqMgwDSXLWjcMyzhbnUJllEs/XAJUc= +dTLQ8Wr3wPn7w6pte1B1YA== +piG6BsMF31u4R4iA6R4SRA== +SbQQ5SqO9QrBwZ9ObpMYLw== +wiYVtfO/yzajW1Tv5z7hyA== +e+1YSk47tjYGkSkbGftbZE2uYCN5IbNhy3jmrXvEvpA= +1u+XjG/2+GSQRv6EzCaWRQ== +XIfdJ79nObMM+vyAmKbTmw== +bbgfjwWjzPNOxnsxTb5hzQ== +NyzlU2YOTFNZFPd7RK11YaWJrITXEmKGOKhVLomHOc20dOocSxleZ0AROzWQsbmh +NKJqjhkvgNdUxIywfoLwoQ+rWX4Ub5rsDIB2VkgmsHbQZuxN+TYMgAuTxJq+YliuIqYyVFQzgzZmvrQpFRfdgg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1POn+WtE+lgXD3ffcoL21lvcxUuxtU7UEzaHRLypkEk= +dOwNHIv3VHyb+LXIiMhwhoNJo9GQoWdlxj44kIhx6Hp82Kdhlr8gY2+EdvH4A5GLyunyBppgYfFAecDj13CWYA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknRXNf+EPwleJNY1jfx8qILnkV2USPGiWudZwlPY4n3oQ== +1u+XjG/2+GSQRv6EzCaWRQ== +/dMCrtsSS3AVo9f6yz5/6W682xaNsg6HJuCqCuwKk/0= +rAmx4p+JZV3M+BRHZb295XUSu2ydOkZmyVfYHgJEFr6pg8rmusZs+IDM4uRAMobX2AfudLBLSPmuRpQF4T5VAKRvfeoURZQ1xEjbrqOIQpoPiEQWE/XxPN5p2F97G+wQ +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9gjQJppw0Kdfu9YroE8LgSjx3OthaS5h4DbYAtc65wP0Q== +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+m/yYGgrco00Chzgj8jXX13K8mLh+/6MvqvJUOejPajKA== +1u+XjG/2+GSQRv6EzCaWRQ== ++1OulhOVgLGZsanMCIMHTov3J4R+Y/qOR1atWG/VUPMrqBDDVvYwgG74FwIEbUmH +wgR07xfoapmx6eEnFHXXYo0pRHKIKLUse0GXAaX9haXCB+q3nG7WybPngoy25/lr +O3CUgrw2GJfB+mDjH5+NdnceC2jE7ElwqGvUxIcW3ioxHSZL5I8DFWp0U9QXyZFCJEIa4bUmblNphufxI1RnSA== +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjakEuF8eNiysMFwArESQEwY +VmVrGQo2zRokW/ZuO9bN6xqOsfK2LZ5KdaTaboUxT+J9crK/OkxM3Cj8QV4T4xVHdhEAJicef5dNzYYGTNmijMbUMqg0Qdml7huVnL95ngY= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjarRfvLG/H9rGMUHrshOkfFMP/VpuSx1UKdq74H98nb16vzDG30yJp2flJ85R7kiKjdYt3OAAWEEUxVb/l0+Vql +1u+XjG/2+GSQRv6EzCaWRQ== +J2qwAW0nzQmbo9HpSOVZlqBGxoFlarg7r90Pqb8pOp0= +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= ++Frj5gqpvxE6m3yWzQ1UlTIgSgRpVewwUpyndC4ejPs= +1O9qhjCr7g42w74sxW8Jlv/qDVNMbtBQQRaVFyF+WSiIU5hjbnf46ZqeQa2eiuyJUliAXdRmA7jeXoq7Zvzsug== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknRXNf+EPwleJNY1jfx8qIL47rDksm6lN7pqbuOzxD+Rw== +HZgQ37DasMTj06jDF6JhZeOucr+OS/yts2fdjXQ7Dm+4vRt5IE1mD34qMjUvwdSpTUFb+HQxnDeJ2haDwxBEfw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmQ2DsRtKL7gFMZzQhcxfIn9vv7elCYoxsXqtak4E/tNQ== +wgR07xfoapmx6eEnFHXXYtX+pH+0NWr1LYbDUaw1pomL5U7cS0NAfF3rE9bUtpXl5cYry5RpCH7tkxjCcl+JBIsfkiUE3HU5Jsl3yt70u28= +L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk= +1u+XjG/2+GSQRv6EzCaWRQ== +hCTaGH97j27PDhFRKBPIW1mpxeh+5/xXma/dtrv0fcg= +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +k1ktu/l6HFULD7Vyr8Cv0NTQ7W8T66w67/IAnPs1BphbcAqgtYD23/UPxAUUoOX3zHiZvFDvN79q8xGwTMxzD2H7sGNp8TutRAPVqCo4E6o= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +k1ktu/l6HFULD7Vyr8Cv0M4/SSQxyWRs7AonOXcLu6M= +lNAImT/ajyr0UfekriSt9zSq0YdZAJlybeil//6Le5Lk+VqsbLPic0TZyMhSi1i4 +9uZN8r0CKVAbfzGtFDuvLzjQrmTpqtae6m5049udhE7jeDlzSXSQQb6RLWBeXr0h +lNbaHNaSPRfUV3Bq+5wk6/o/pGhUxfVnG6Vdh8vueai6mE2PiMdE0bc8q4jeGJk5 +VmVrGQo2zRokW/ZuO9bN6wmr39mr/nsb2Dav+lIH7B04mbHZv7nimwjnfT2u87IJz/Cm6ht29LQKMvtjjf+B66QOHjnv2exZ+gY1I9sna3E= +VmVrGQo2zRokW/ZuO9bN6zpu8f8CSK4jKnbU3ZGJmtvI3LMBtK4e0vlEt/NRBmS6 +VmVrGQo2zRokW/ZuO9bN68oOr6KTHQoLVUXKZT/zSoBoqKYJ9J0V8LlHuynr1gG3 +VmVrGQo2zRokW/ZuO9bN64iIPtwqqzY7IGXnqm0yAg890VkNwZCVekNfOG742tBCDsTNSICGfsHcEmavjt8ryQ== +VmVrGQo2zRokW/ZuO9bN6/DCJtLswi79iRP2VS1Cl7lfMksK9QvQ1cbzmlzMcErh +VmVrGQo2zRokW/ZuO9bN67Cw9WPgw66+cjpw/Ea5bB7otkixB28WuBR7LhKcbZ+r +VmVrGQo2zRokW/ZuO9bN619j4sjPeFT5nbRqDaMF1SyRCEOykS6VtvIiINHSVF6d +VmVrGQo2zRokW/ZuO9bN6+ggYawrMTekjFNmeq2lamXS1PinPTjUn3ObpK94+9cw +VmVrGQo2zRokW/ZuO9bN61dj0GoH2G1Dp6ZIVlQ2vt0= +VmVrGQo2zRokW/ZuO9bN64Mo+xDfj7h1iI635dygp4XcwMixUpSGp93xEK87JQBN +VmVrGQo2zRokW/ZuO9bN64iIPtwqqzY7IGXnqm0yAg890VkNwZCVekNfOG742tBCy+Z23+N8lT5zKrFfykLJXw== +VmVrGQo2zRokW/ZuO9bN61dj0GoH2G1Dp6ZIVlQ2vt0= +YH231WTDnzQG3bFilBiqIg== +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +0snEhJ9RKaTb75xc3VQkty1aZuNySWxV7zy7TvMj3VELunqJJCu6WbOmoTqe9fBTss+PuRF0k3VMAy77nLPDRKCNrghLXLD+9ZRohmhwvHw= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +0snEhJ9RKaTb75xc3VQkt+QytQcTDkSIVmyzyDgzN38= +1u+XjG/2+GSQRv6EzCaWRQ== +qEXluMqy4s3YaMlhK+yN2FagSHb43j5sbjmAKOncUT9h+EXTNgwO3qaX8l+Kgen0 +lNAImT/ajyr0UfekriSt98+S7POF+Z6BRjAWZy5+byneV3Q5JJujzuhONtsDZg8T +9uZN8r0CKVAbfzGtFDuvLzBJvYBsQoZdglGiULRh5LkQepPU8A5qwSrOLluzUtVy8JAf3NUhjARoB9em7CKotw== +lNbaHNaSPRfUV3Bq+5wk6/o/pGhUxfVnG6Vdh8vueai6mE2PiMdE0bc8q4jeGJk5 +VmVrGQo2zRokW/ZuO9bN6wmr39mr/nsb2Dav+lIH7B04mbHZv7nimwjnfT2u87IJz/Cm6ht29LQKMvtjjf+B66QOHjnv2exZ+gY1I9sna3E= +VmVrGQo2zRokW/ZuO9bN64Pq6Hr0GQda+jfI3yI24le74i0xC0v4Da596cm0JfkE +VmVrGQo2zRokW/ZuO9bN67jx0a4m693Et0R92z2wvL71x/3S2aSF/NvGCgu6CHqf +VmVrGQo2zRokW/ZuO9bN68oOr6KTHQoLVUXKZT/zSoBoqKYJ9J0V8LlHuynr1gG3 +VmVrGQo2zRokW/ZuO9bN64iIPtwqqzY7IGXnqm0yAg890VkNwZCVekNfOG742tBCDsTNSICGfsHcEmavjt8ryQ== +VmVrGQo2zRokW/ZuO9bN6/DCJtLswi79iRP2VS1Cl7lfMksK9QvQ1cbzmlzMcErh +VmVrGQo2zRokW/ZuO9bN67Cw9WPgw66+cjpw/Ea5bB7otkixB28WuBR7LhKcbZ+r +VmVrGQo2zRokW/ZuO9bN619j4sjPeFT5nbRqDaMF1SyRCEOykS6VtvIiINHSVF6d +VmVrGQo2zRokW/ZuO9bN6+ggYawrMTekjFNmeq2lamXS1PinPTjUn3ObpK94+9cw +VmVrGQo2zRokW/ZuO9bN61dj0GoH2G1Dp6ZIVlQ2vt0= +VmVrGQo2zRokW/ZuO9bN64Mo+xDfj7h1iI635dygp4XcwMixUpSGp93xEK87JQBN +VmVrGQo2zRokW/ZuO9bN64iIPtwqqzY7IGXnqm0yAg890VkNwZCVekNfOG742tBCDsTNSICGfsHcEmavjt8ryQ== +VmVrGQo2zRokW/ZuO9bN61gFm3fkb7F9QaTU95L/jPOa/fpyFMOpGyv47wlhPWUavkMZtX7vhm++qsgSrcsbMs7iYZPvqF57PAVFlPrQb9g= +VmVrGQo2zRokW/ZuO9bN61dj0GoH2G1Dp6ZIVlQ2vt0= +YH231WTDnzQG3bFilBiqIg== +1u+XjG/2+GSQRv6EzCaWRQ== +IhuisKim47k91RVt8z8qtPC1yXtxP4HpO+Fq1WLJQfIrs72H+rdxAJbYDuziNMhCDP4nqJoVpCdCT84Q09H2lGhMa2KtuWLGVujAUwS8tc0= +CX0zxV/Ac/FH6f+lPc6emlnfZJ8TnNl2PJfU8arp9tA= +CX0zxV/Ac/FH6f+lPc6emtaE/Z+HQ+edntL70zTF0MU= +1u+XjG/2+GSQRv6EzCaWRQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +QRy2uI6M17xa8VECXPwQ/r3FvZ9uhLswudGkKrhamRBUed+4wvaGk+50+pqYUcvK +vwPJuanCw0GGKmHnTL06kqiRb2TJevIkneRS65wI1Gg= +M6K7zImQ7EejaudBMeyRrzUBkAth/QVYgGw+e7NSNq0ok1LuJoFoxPIRP/laAjHQ +Dlyx6ZDFdkn72YIxkC3bo6dRBka5m193wr4Tuji3xDfal4l1bn1HdgJU+c7TJ1Vg +VmVrGQo2zRokW/ZuO9bN66A2fToKVW3Kp87AR7xLfgU= +VmVrGQo2zRokW/ZuO9bN69a0VQFchDw5lCmzSL0TyRpYBO2LnIP1Dq6Tn849AbL8mrG/FghB2zsTPS+n/OODJzFvaZSpI2TVY4zI3RcRmUk= +VmVrGQo2zRokW/ZuO9bN65/ToZah2A1bCyMMmWoOHmwTDoouye0hw/WXVVasqBFLoh/FEdn1xkV5SIYhN6mUm5rSgxZdrKoNrvXutRDqIHY= +VmVrGQo2zRokW/ZuO9bN69kILXNpKBWZL9i8mpYJQQ8= +VmVrGQo2zRokW/ZuO9bN6w1Y5WxgJQ5MLDnvJCe9W01+kddoq5diIGZiCmLcwjNsGj/5PV83zGZZyBT+DL2LZ3LIzW5cj5kK3psaPepTpvXCc+DC0DY9z91U0kNTbcmP +VmVrGQo2zRokW/ZuO9bN6yxP4eEsZPOAIxRf5Vq6NdXCvDij9/Mz7hFjclWDpZcvlMUPpuqhPrhUyyRjD2+HpfnXP2EDVQQwTF1bXx077bw= +VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE= +VmVrGQo2zRokW/ZuO9bN689VNSLJl+o30OP6460WfudZdrYr3BFzOC6rBJ7pfcP2 ++plE/1bhdo64kO07cLlUX9vuy3nOWCHRda7Edql0VQ4= +1u+XjG/2+GSQRv6EzCaWRQ== +H723/Fixv18fFechEvPfz+RShgw63yZpm0RRiruD6qgKiIksxbm4PQvdWh0n3Qv5 ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +fg6BEyojOsebUV1Da0ek5HUdtpZqeBZhnz+XcTtBkgkzND/kRvI8T41ZtFR1F++bMFHpQKZGVIOzgelgaNXw0fIw4UTXnS4xgy4+N07DRSA= +nB7uUqFfRyFo2dz1p5PSrbBSwrXmx/ZlKzCQbe4NOoWD3zLEqpTC3naia1wn2HnJ +nB7uUqFfRyFo2dz1p5PSrVCidKjUrygAfYqtvGaCTx+P7CYvtc/HsI+QlD24z6T1BkgpveKIgrBCfXy8mi9tN3S73ZSEBsomyCpaYUenOzM= +AW6BcohkqRe+6MPCUtbq72rijnAQgwcBIRYlwd0OM3p1c3LrZX0CAPa3YjXFuLAqtq673F6jtVTw9IaFt9S2PBbfjX8+iwEzNVQ+AFpkuXA= +VmVrGQo2zRokW/ZuO9bN69h0gT5W9bsZSh/FPw+t8S7Xg3wbL376ONecW0BSarGt +M6K7zImQ7EejaudBMeyRr3H1QBxcRmFmUDWI5IEICl0= +n/Vg/an64od1JqfYD8zjtpmkT5XhnqiZIvQYECHVkbVtjF9uqp+q8tnLunH1B58zoVQtAiC8nqGnxZE4JD+cDA== +VmVrGQo2zRokW/ZuO9bN63P4gUZ6s3DvlDlNl4yHUxo= +aPdLEV6HY2SBr9CPW/3NlV6skNvI9JsIj9pOVhf2RvA= +1u+XjG/2+GSQRv6EzCaWRQ== +4TmkoDZIUXBDfgrEcjLrILoiV/r45eWI3EuL4iu8YCn8mmUZy1JG/0grjxu+JoNyI4MJoL1550P5cqm9YzkMmg== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +QRy2uI6M17xa8VECXPwQ/msg81KEgiYBmI3HIABNuY6R+ST0p3U6tiMzZl8KC5E/+4LuyaVdKaDwd8xrlzplzA== +/0ULpLqgTvInFD0r5hHANlt/A4HN48BaWWKSY1CtQvDGiXT/SvUITbHNTthFbVxc +IpalQHG0U5xE1KnJ2rA3+zKtqFrPp3t19c9JGCjKU7o005Y2Y/EPTg/HqcBW9dPKmz/Fwy9EC/6B9qvDV4UqS6mdk3YE0NjY7aGYy8qDqg0= +B46DYlFQtDlulKRQCnJ3QcKt5tVSQirJA5+oY7kSjdHFB3yakZu8NqU5tBnxAUCR +ncUn2TP8ZQ4fZMBYk+CUrZu9buP2neyCDJBE1ohmVXDje+hgfrSkxiEVXRD5MMeB +GlJrPWeRtvgVw0Cl3yHj2DnrwYIJQs3/uKozgXNgdGiPoyv85RnxyG7THH1MXgLIEkGYD5JxVcpwFVCXpZX7mA== +jceVTICIbdeQFZqTJKGutfSrJlMev6knX9PMZKWQHjZP2zVkNQw5SLaJxnQB9+kv +1u+XjG/2+GSQRv6EzCaWRQ== +8AKpM+6i2gR98IaMzP+8eqqIPbXcMKDW+V81QFTAVtM= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +EKWXQpq52OG0J2Wd0ZMK1fHl6IB0UXMhfHM8IMciaUE8Cuhs2Jaq+7F+INo0pV0b +tVC11ntb4xes47B6YgBZcZByliVS8Bcywk6SDhPLq9M= +U2Lm52IpUMUiy6pQmybrUNMsxQNvrzGUtIlYUGx/G+d+d11TtLhKhajQSiLMOGbM +1u+XjG/2+GSQRv6EzCaWRQ== +tVC11ntb4xes47B6YgBZcRoAGnZIKcHQ8817VE45RgE/szi0KWuuV0y+WiQG1xKnhxN36HfB+l+pBaungwquSjpKxsA0/x88FP41yUSvh5M= +dBHzFT+Qe5dH9WRInSu8jgBdjJ3d9vUVTbusOnLdjISlXe3y6Hp9jE1CEAXO1Vgo +lNbaHNaSPRfUV3Bq+5wk6635BWCQo9IrgUob1Zrag3U= +TNgWuy5WhP0PGg7S3lA/vmyECpd3KM52AqVa+3aAThk= +1u+XjG/2+GSQRv6EzCaWRQ== +JR/YEWislpM0oP8V4S7W/T3x/sEx/RV81gEcsXh5Vm4= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +EKWXQpq52OG0J2Wd0ZMK1amw32WdMHSvsYBysyBVye7MBGZqqMlCnilH41Ppl5z5 +hCTaGH97j27PDhFRKBPIW1mpxeh+5/xXma/dtrv0fcg= +b4OJVZe8QyIpjuTpKXDL9A== +lNbaHNaSPRfUV3Bq+5wk6zaqNMuN43kTxbH9sz7EO2ht3uTIAUE7QAf8gn2FiLoMM2qnty9PLRQax5H53+LRWsQG/ArUmkNEPQBKMtLhFic= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +oacxXEoPih9NKsd5qduwcOQwetUrLBW9DCPWftWi5APwlnytsKeJdxzEsW4Uksji/RXLLSNM+uMNJD3dcCtnrQ== +L0eUthVnpkGsmKFAX6d+uIwh5dNhu3qfFXQw4bkQcLU= +TNgWuy5WhP0PGg7S3lA/vuqTekPEYUPjtE+v8avEsM0= +1u+XjG/2+GSQRv6EzCaWRQ== +5n6k7W+gw/zLAOXg7V7ef11JvrMmq8Abhcn0Lriux3c= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +rqA8uevZ84obkmE4+PiALnegDM7v+GnS5uqMmEfzMznDMjjPN2InjgfCEjrrETPS +HXgXYcQ054mulb9vBwklSBYhmCzNXMpD7HETcV66cm3Ez8lsbeNFwRrL2pufjKIN +hCTaGH97j27PDhFRKBPIW/dJaxrfoD6RixVo3Rf5Yz3CGP+VwV5No6W32fmew1fv +/Apef+rUhQ+sRd878DPgvsqTHrbDs5B7KWyYuPbx/h60sw7UW87NeTBPLIIKfmyO ++7P0MlEc1eFk6hnR1wxxsLeMOEssU3yb0jKp/217OlU= +qMRWIbfylJbi8JdtlTKCc5qLCzoG+56ejKCriefYCzg= +VmVrGQo2zRokW/ZuO9bN617WCoPIsnyqpe8hS61d314= +VmVrGQo2zRokW/ZuO9bN66WOTbqs3JJ9oARgY2YlMq0= +sQfTf8f6jxyRSx9SNPNEtQ== +qMRWIbfylJbi8JdtlTKCc1faDhfd8MAGeioU59baa8k= +VmVrGQo2zRokW/ZuO9bN617WCoPIsnyqpe8hS61d314= +VmVrGQo2zRokW/ZuO9bN66WOTbqs3JJ9oARgY2YlMq0= +YH231WTDnzQG3bFilBiqIg== +661hZf7vhUQ+50okfwfTXw== +1u+XjG/2+GSQRv6EzCaWRQ== +uNSTKhXsc886vN3euLSVYkMvdkNBDE83aF6HgmNzzfm9nS8DOshApSz6BmwM4QxxEGBVCrw/4EI5+KcUH3As5A== +Dx/Tamsgq45f2G8qqPP8mCCoT8bqocyYm33wbOaP3Tg= +JpR/5cELI47P6HxjnatoQ2/atXOIHNcG1sc28haUJFZ1gZDGEl2o+OKEFUVLEwFaEzyLGFu5kNfMmEsr1svt1g== +r7wsdpnewjrvWfftu89eoYTwkMI5OWUW8b5cozC4vefJKFfxvzCdPhjxCkfhZhB4aI7EzyABwQuKXPBn2jYK3A== +pW8ou9f8OJbDl2h6RV18oo+Nb2xSCvRyGVnGpXJOZKFLJWxu4t/C+dMF19UdhbFV +pW8ou9f8OJbDl2h6RV18oikbwwEIAI/F+KSggaRJYmv/cWHXoGxXdp3crBfIEA5L +lNbaHNaSPRfUV3Bq+5wk63n5uJEj3P2III85nJ0uhYOX7Jw57j5UcSlV5FUAGDx8f48V0h/MPMwLz9LF6kln8w== +51UII3rZ87NZKsRBZ0eyLdVqoWpbfe8TmH2YJ9DWmQlS1xLSN4I2NnpSxLSgoi6ARf5O3cCN9gvq9c/fk2WhpQ== +51UII3rZ87NZKsRBZ0eyLdMEmNp2vC2WieoOz8+5XT1h8AmvAqKPSJXYghFKotIHwSJazFvEKrbDFNimm+5wCA== +TNgWuy5WhP0PGg7S3lA/vq/Pa4m0zdEJKukF/iQ8p7w= +1u+XjG/2+GSQRv6EzCaWRQ== +8AKpM+6i2gR98IaMzP+8eiursxazYPKLu6wgIOWPjLg= +kOT28QQt2GDU8VknV6OBKEjqdQ6KB8XgfYY8d9xdmSdjAru83zSuyfLg72ewneVt +oguKPwdzdIXoCdJnfha//pL2zpBMd1+gikCsUIV2A7Y= +WHkOzVx7seuLmxs5Hu+l/rPH5+mQcujSxbSmaq50dGDrgdFoJ8I/OP4q3nIZk9j6e/sIRs7MFvaiH8s1k9l4xA== +NhnIR3Ilo4H2su9/cTNo/Jz8iK0F0orMNzBf9My4ZxFxRp41ki11trhLUwQPw5UA2abvNkVk4cCi77fZpcH0AM7gHazPquEwSeick4HgGNc= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6UPvO5bgA+DLcTH2CZLr/bGRj54IEJazMCgjM3+swz1g== +1u+XjG/2+GSQRv6EzCaWRQ== +VAMhP650VvSzUoGsza+Zzzu+y5xFCoLk4dxzpbTcSwM= +6we+bQ+BNhpl74ICwbzDDyJheDk8+zESwtN6nsP8zU7Uere9TaY0CfOByEukHqNf +c0jePRxtTVZYop75Q5JCFiIE3RJvFM05nYVUsJRwusjXXH+D9dAyYW92q67gBfn5 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq5ZzPLUX/xqPUerYk2PoPswUHzqbrMkriRu9iUTYNkUQ= +c0jePRxtTVZYop75Q5JCFhNbnjqrU5CDzmRZm0r0Awym1X2wG8X/4uCaFLBbqGAp +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq5ZzPLUX/xqPUerYk2PoPs8fM+dMsuU0wJOAXUgqZD09pxvBN50/ZgncRWUVKlprQ +c0jePRxtTVZYop75Q5JCFhNbnjqrU5CDzmRZm0r0AwwPUU7dHM1clC+rsS6Yiu9o +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq5ZzPLUX/xqPUerYk2PoPszGlLmfF3+XBxrrFCfTO0u+lwudO575CdoR3BcS5rhnD +c0jePRxtTVZYop75Q5JCFhNbnjqrU5CDzmRZm0r0Awz+x7baA4CU9Vq58U1Mh/Bi +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXcMVI23sf4OEQYLlAO6/ObeURw20jcXuWd22HNjPFXSA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklQ/NA0CCb5gYasRx4vE/jiwP1oPICdX16xr3p3prUwUnh9ppk6jJupRQKnfWADwuI= +B9nQgrssj+ws3f+4OSMxKmc+LynALf4SADHGSaLUuGY= +u0YJgTHLzbZHSJypIBwn/LJB/+7gHq/cLzGLRiGjmak2IsQN4SdsQxkVuqy6868E +cbvASHjpysrsjdY5RctXmOQD9rrCTb2ZxmWqcs5K2qSZ7dLEUX0XjSNDZ5yd5cTx8AH3snW3RGX7lCwH1f0D0g== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq1lfQhkMONeVXRmV7LJJvKul+Fy89aBAE/9tY8gTDXFzpUvLB4TFO7WEFIw0odvXb +cbvASHjpysrsjdY5RctXmOQD9rrCTb2ZxmWqcs5K2qTNDFwTtRmB79/9v3BVJsrHpdji1CVknwhFllSpjXF2lA== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXcMVI23sf4OEQYLlAO6/ObeURw20jcXuWd22HNjPFXSA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknjAU/EofwtdUePHnhs2gewQri/RccLzVJp08Zsrqre1shu8DNVPQEBXLS9qc4V6E0= +OE58XJkoqje9oPDfqi73ViDhb/MaA1CDRRN3hfs5qLcD7wPRDa7t67sgSM6GZud7iM7Q+yRzOZwxlLWELW/MJg== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq1lfQhkMONeVXRmV7LJJvKlg2pHEHdC2FnFJvgbKhNWegIP5NFaPmE64WTgBO0ycN +A69EZujp4WFkVkJizqN6C8VUkrxVow0gg23pw9cAA/uGssiFLD9jqO7nvqtN8xHKbEUH8Q6uj7cH8Ol7YnBkNw== +1S1xjYS0s+Ph1PyvECx3QHbY1OYa5gxYwaadGhIGNE4IFG/B7/pkR8wd8Pmtij+2zp1qIIbA4jXbHOMeTyXJWoDCmIqrdouDeBF95pQ4YIo= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpt9ortgN38QM9ut7GqkF6952rTeSJFUEYTgtdoJlNBjI= +1V2v8QerKOmubvSxgB4eTPvgycJaq1uM0FItIxQY+IBkx428stg/YVrimhpw15It +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTlgv7Pbn3qn1rTK72IH5Ea +VmVrGQo2zRokW/ZuO9bN6xRyTbp7DqdRCfMT3PPA8YlDL0IuPjiay9076A6RftkxLnv1sxO4e5wt96hxsWEaz4bz3cyXhvh3Dxk+v8Yfelc= +HXgXYcQ054mulb9vBwklSBYhmCzNXMpD7HETcV66cm3Ez8lsbeNFwRrL2pufjKIN +fLETQ22HuDIh4ibW1C50WWehEWMVGJXX0CLf5TNvJo4Q5+7XSqZAhWnHUsbASo7V +fLETQ22HuDIh4ibW1C50WXEsbJxRZ4lXGXSSB22fG3I= +wgR07xfoapmx6eEnFHXXYo4se8cEG3Kpe+KryARC+58c1jICgdZ/yfiiPJOB8osn +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN6+u15s9HJHBIPZ+EJMD/NKzYtx5z2nANTnl7b4WNC9nIszUUAF2u6QjLYwXkHkGcYqKqXEXhdsB3+KXxMAaa/+M= +WHkOzVx7seuLmxs5Hu+l/vyGPI2ZUfF12Q0eaBVhpeoopSPGh26H6lFmBbaoJJwr +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXcMVI23sf4OEQYLlAO6/ObeURw20jcXuWd22HNjPFXSA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklt1+ZWs9i7/wHYqYPvKals5jrnT1gCW27PaXDJFdo5AIGAqA4bE2k2HJSMU/RDpaw= +WHkOzVx7seuLmxs5Hu+l/gzVZ0Va+DXFUK8nznHUhXhYsSTgQQX072ZHxHlXZn6E +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN6+u15s9HJHBIPZ+EJMD/NKzYtx5z2nANTnl7b4WNC9nI5t1B+HlkK75QMMuB3T2fEryfA0aaLxlmhGePr4B3TCc= +wgR07xfoapmx6eEnFHXXYhJE4xrEpFjyOULkKEdGQ6xpKpQ97D+w2oBDgGLxKzYi +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmXcMVI23sf4OEQYLlAO6/ObeURw20jcXuWd22HNjPFXSA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklt1+ZWs9i7/wHYqYPvKals5jrnT1gCW27PaXDJFdo5AIGAqA4bE2k2HJSMU/RDpaw= +hCTaGH97j27PDhFRKBPIWx5h0aqe91PCFXPRyuarZwHCnfY5zg3rAGF8JC1Rlqi7 +1u+XjG/2+GSQRv6EzCaWRQ== +OE58XJkoqje9oPDfqi73VlGkW2sntmceo8fDxcMP5lY1ekhmQtt77mccgEqJdX7o +5p980mxWRpkxkeJsimzJ0c670/+1EUzG1KvIP4Osx0lqvbiasaqcKL7pqQoXRRlSmSgBIm75JI/P1rRgiosxmw== +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTlgv7Pbn3qn1rTK72IH5Ea +VmVrGQo2zRokW/ZuO9bN60kPGAe8BYlpKmCcCBaEvbVM0wtnd/Sxa3UXu5M3ODaXwT+F3h/lqsCt7RwtEnQ+Juic62CWc/I0sBOjdt4E/FU= +lNbaHNaSPRfUV3Bq+5wk64nrImxso5dDuILvGI+MQwTmWuTQaQzUpeGGa/V25O8w +lNbaHNaSPRfUV3Bq+5wk6wD9WRwpEUG8eRNjfnAkH7n/D7vEeaS6J3HXXnWCNV4afdFt7wSC63jvA3Dmj7kOFiXPh8V4ZxzOGAZNxEO/GSw= +lNbaHNaSPRfUV3Bq+5wk6wD9WRwpEUG8eRNjfnAkH7mxlEizZI4FzeAXryBJu9K5U1bEK3vTRQwkjV/kSXPbLw== +VmVrGQo2zRokW/ZuO9bN68rCCyFZ0GefvEM2z0oNIJFc4wqFGELFP6LESyfNOCfv5kKX8qHPVm9l+0s8kRJrRg== +lNbaHNaSPRfUV3Bq+5wk6wD9WRwpEUG8eRNjfnAkH7n32akGeJETlKGDSs8LfYYC9O61WqHxRHNJwq7Sjf9EemFAkIFEWr7tHIeJwQnQvCo= +lNbaHNaSPRfUV3Bq+5wk6wD9WRwpEUG8eRNjfnAkH7n/F8iZ64HMLmMMgGltzdd5Mybak4VoNhIJ9JWGMsNufQ== +VmVrGQo2zRokW/ZuO9bN68rCCyFZ0GefvEM2z0oNIJFpouw5KukS9rQdg/yOLtUfQIhjL4gMIuurr0xfp6USoQ== +lNbaHNaSPRfUV3Bq+5wk6wD9WRwpEUG8eRNjfnAkH7ndT8Do9qwTTLaDEmGd5tQby5rX6DaA9VhhKXndgHL9JuvRDdXFPEMCn7QL0m3lUzs= +VmVrGQo2zRokW/ZuO9bN608deJSvexKXO21NHnmMGHeIg/pr3nDhgNXYDiErXtGU +lNbaHNaSPRfUV3Bq+5wk6xOlK22tFRCz/MBWHqTO5bcxlrGueZZq4wrMs8Z1lnfjDuzShbwQZHdqLJ+DV3ERdQ== +VmVrGQo2zRokW/ZuO9bN62ec9pTpN5Cg+2rTnrzF44sbSkSgWYcimLMO39fwdTM8QQ9HapIm3253ANyva+ZKow== +l2FJPs4YkAmmok1ulDRuSA== +lNbaHNaSPRfUV3Bq+5wk67RG2evr7TLF3SKM8U8p2AA= +VmVrGQo2zRokW/ZuO9bN6+GpeDJLU0SmT7L+/5sE5xIlwrT8TwQpkYYGHjbVkNftN1CKdBCjUzympMQ9RT/FEg== +VmVrGQo2zRokW/ZuO9bN60neO6DA37E8cryoS6/9NzyxLC2oEdN7DcJRwQhdCjJH +VmVrGQo2zRokW/ZuO9bN68oOr6KTHQoLVUXKZT/zSoBoqKYJ9J0V8LlHuynr1gG3 +VmVrGQo2zRokW/ZuO9bN6yJjT4k1EBcR3P33HPsJKcS2iJiueG/E56TTg8JuY5PMBrWa6U4B5U8rW1KDAZ2JSficZo3wFKS2XvW6ui8Tv2Y= +VmVrGQo2zRokW/ZuO9bN6/DCJtLswi79iRP2VS1Cl7m3kLpB5lzqL2Me52OhtZ1zUFOJHRYhWSvYLeuSZixvqmuGQwZgeXJcumVSIEgd/00= +VmVrGQo2zRokW/ZuO9bN6xRgv05zRD9dGM5M32jg6eA41WZXP7dtRrdMXjEoH3lvGk8OfKeqpDa0bqO9TtO5SbDD1FpNmhqJhoAYwQK56Qs= +VmVrGQo2zRokW/ZuO9bN619j4sjPeFT5nbRqDaMF1SytlAuchmjuOqjKzKLlRlpKp0wX5I7ca4hs64AaenatopykN6zoa1vKVmy5GNym1kk= +VmVrGQo2zRokW/ZuO9bN68Ys5qSHlhgxubR3riCCkMH07t3rA9i/RBuH8FqAjmcn82Cl0Yv2F59R3Y2ngEly5Bn5i06KWEOErKyGGIaEm5s= +VmVrGQo2zRokW/ZuO9bN61dj0GoH2G1Dp6ZIVlQ2vt0= +VmVrGQo2zRokW/ZuO9bN64Mo+xDfj7h1iI635dygp4XcwMixUpSGp93xEK87JQBN +VmVrGQo2zRokW/ZuO9bN6yJjT4k1EBcR3P33HPsJKcRSOVZETH4NsaP+09MQKNzjBQ68t3nxeo4G6JqqxhrjboknRazz5h4TuwgKRRmKioM= +VmVrGQo2zRokW/ZuO9bN61dj0GoH2G1Dp6ZIVlQ2vt0= +YH231WTDnzQG3bFilBiqIg== +1u+XjG/2+GSQRv6EzCaWRQ== +OE58XJkoqje9oPDfqi73VnAo3wfSgmMs+Dj8jbP7qWFcxm5vY3PPNCW7jzGH1g0O +eV/v00/xYHddkCxXY1E1CutyiNhsUp5dQKzgD5wic7IgXr6vXqh+W6d4JHYxTx7KWitSUvhADyOfRWeCoy8QOA== +Dlyx6ZDFdkn72YIxkC3bo1MEi9hlWmv1Zzc0sVxBv1A= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTlgv7Pbn3qn1rTK72IH5Ea +VmVrGQo2zRokW/ZuO9bN64IIz+/BGgp+g6IZceFmjbE= +VmVrGQo2zRokW/ZuO9bN64N/ZpXjHM9K9N0f3E2rn5WmGCGuCqdlsHbQ2dtHALi514chJeA1Hx2w6TnYXI1BkHbq2OIhHdxnU+azwjwrhDl2N6HE5nK+JyS7Sogxob13fbD21rB84V/svtOsrrU1++ayKZo53F/FxktbspEFfFGeAkkO+6B3q7B5P3cOtlN9 +VmVrGQo2zRokW/ZuO9bN66+yo8koaMFaeR72YfcN0MA= +1u+XjG/2+GSQRv6EzCaWRQ== +PWKGbNEPxxxpGpe8THO245iFXovVReeQ+N8g1T/vWbG7nH535jnCqkS519sVTR6d +VmVrGQo2zRokW/ZuO9bN6zupz0i5vSNSVG9TGAJDyeoLDnDUXJHlYRiV20eRKyAX7xIMzgD8k3m4OcgwCDXDKzd9OsJEnb+M92CH7qeRZ0rXTaTN2IFoZniVzx0rvmSkMgg0Am0BzBuKc2Oy4b+Fwz22Rz9N82eyhvd8XOJC5uz4LBqftuz0hWqSTaSElBKoG3MPG7CNEYazXn7ounswqMclIOlDexzhfuPj/JPJkMXV9/kKnrcBcKBfppT05VExwJcuZ3qAf0UMyLGABEPcBYMQ+R9r/5jle4KezOy9UQns1yuOUpszLU/F0HSuvqLHoTleWG+8yf0L4bezFqV+OnoLU3fsTnAAvz4eBfP5CpHNwhaX0L8GQhbbhSCir00QDirnaca7yfuKjeHl9v4mJ+L80X7e9b6rV3BwLAxEclLhmkJjIsXn2ELLz6LA4q1p4l/V0/X4ui7HcDSp8fXfidL8c7vRgYqyWhzzbR2fE3UHBxaDFnpSWp0+ZuXHvCMScaIyJgZnYb+DTCjFMFU6lb4b9VNKQb9QbnC9ujuFU+JAw6rjiLPeHDb8gjkeBiQ9ynw5AYK0ZKL8d0Egv5xzVyq9+JxlM2dkfo11gyetKX4= +VmVrGQo2zRokW/ZuO9bN6707T5hcoTPt8muDk6mBkfIb2fVyGf9Ofl629l94fe8y7TbM2fJP1K6AAnHO78QGE4QIfFGZ0iAOEsBOZhdfiig= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSuY4P/AlnqUWfNJ3Nv+yymeIVnB1recLomFaZp8gbNfQ== +1u+XjG/2+GSQRv6EzCaWRQ== +W+5RMzryWgfLCHr0OO+ErInNFnurUcbPlrIppCTTbAXA12wyKUkFjBea/Qb61VZ3c05s9epd9IsaBOqriuZPqMonOEUobnVmGGPlR7/5sVE= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTlgv7Pbn3qn1rTK72IH5Ea +VmVrGQo2zRokW/ZuO9bN64IIz+/BGgp+g6IZceFmjbE= +VmVrGQo2zRokW/ZuO9bN60cD4yDgVtlJBXBhnVmR4KRv3Cr6w0tsMAgfEOMZqertu+3og0F/QbIVS5TWho6wQ7g1mzy1fG1ZJjmIEe5S5/XwGDw0UuD1NCUzL9LbH/Ai +VmVrGQo2zRokW/ZuO9bN66+yo8koaMFaeR72YfcN0MA= +1u+XjG/2+GSQRv6EzCaWRQ== +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGgafK4AuE7/cXHaYaFctwNo +VmVrGQo2zRokW/ZuO9bN68M/5U7Jf9mVZjM7SR3NTECPgWjfF+U65HeMorIolhvD4HPEv93D1akoDkrkvPrOY9QS2DPJBdmooxB2TGPQNT0= +VmVrGQo2zRokW/ZuO9bN64jhM4qvFQxrXhREkkL4evAvbPabM3xnVTII4I8povi4f3Pd2gWnwleD2BdzN0izmg== +UbJuac0dxLiN+5ocS3w2vb7nzZrB/7qqG45ll0i5qjk= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTlgv7Pbn3qn1rTK72IH5Ea +VmVrGQo2zRokW/ZuO9bN6z2QIHM+MwWG+DHtm57W1+SfMGavOQXjtK5E+Pd/RbfJnnyRuS5HM/BDbhDuGwNAThMAfBz6ti7yWZ76KqpxFpk= +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGgafK4AuE7/cXHaYaFctwNo +VmVrGQo2zRokW/ZuO9bN68M/5U7Jf9mVZjM7SR3NTECEaiFjmR/qWPRUlpmZbiW5VyN/W5PhHp8jokr2q+UyLUeUAIJ6zu+f1a6ekzajeN3jJrbDfSbX1Y+ZZXd3Q0JE +VmVrGQo2zRokW/ZuO9bN68AJK8ucG0UjIlJ8vTejx4/UjlGzME7k234YwzM/EMl+2fL+nZx4XxqxEr363b5fjg== +VmVrGQo2zRokW/ZuO9bN67wENRoFfl+EER/m7L/ET4GRAT4HyRemKVvrVKbyjVfM +VmVrGQo2zRokW/ZuO9bN62Gbiu8sHPTspuRWoP5/2uyG+jNo2Z12jeXvFZbcfbfDFcbvNH4DvbGdfzF2nAV12A== +UbJuac0dxLiN+5ocS3w2vb7nzZrB/7qqG45ll0i5qjk= +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrTlgv7Pbn3qn1rTK72IH5Ea +VmVrGQo2zRokW/ZuO9bN6z2QIHM+MwWG+DHtm57W1+SfMGavOQXjtK5E+Pd/RbfJnnyRuS5HM/BDbhDuGwNAThMAfBz6ti7yWZ76KqpxFpk= +1u+XjG/2+GSQRv6EzCaWRQ== +uRzTWU2MnopGqqEdWeT6ZeDusW2quXQd3pL0k0Xg8wY= +1u+XjG/2+GSQRv6EzCaWRQ== +hCTaGH97j27PDhFRKBPIW4EZFPkfMH002JrNm9PLnXuwc9yq5Lyo6aOmZ30BZnoK +2+RceSNjD2iq0sRzepMysYKB2p+0quZ3k/r81UP7jdA= +0tTy9hW41S4+Kusr1EouU1D1oliZ7e6CIIanBdYINco= +QSlGrRvW/rNBDeF7we6iBWKk2BA8QQKFrP2zSqI9P7ki0jFsOC40u8XHDVJCMmICcxej7Emo/6XIHyFNtg2eKufmZGJET5cF7chd87WOItU= +VmVrGQo2zRokW/ZuO9bN64GHxlyw4JoKjlRq0kI99uVlNgUvBBu0glcGT+bS1WfEDCMuy8Daf9HtiO9MNlppARClW0pxHS+4u0haP8galI0= +IhuisKim47k91RVt8z8qtM11IfmUg3Vzb1vU4L+nUSEXI43SqtfmWpr/I8AYFS79jp/blpkCZsPn/8gvY1O0DDcOOIqpPruCuHCHNHbX95w= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6MJouh6jubSHLlzeu4ZDhMi4c88PYp0hrZSfjI5JTCeSwrNjymmREnDUPzBiIcTHg= +1u+XjG/2+GSQRv6EzCaWRQ== +uC+DXmi0z9guz6E2K34tbxYHTBFHe6AQnlyDTD6Hc/rV77vRQxTn+MRGJaaqppIF ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +kOT28QQt2GDU8VknV6OBKCQUts5PU1sJ6OhmjdEHWvV7DuMTPnU4QUe9GhokxYr/ +qEXluMqy4s3YaMlhK+yN2PBqpgcwSiqQwLqIpKZKB54= +HNr5/wGY+7coHgowTe94LmyrzabZ+F1uba2S8ZHow+auSBEdEXNeDwaLFHcP6wVoY1XpyD3B5G5cVIWyIhMziGO8FVB1Vl4mxAxFXsSIfZ4= +mqjkDRz7NSn5aKPgUezVq11iXRHQrp2tT15ujt/F0bYTQCYjcppgnuJEuiMNwNgr +NhnIR3Ilo4H2su9/cTNo/DyCETVXfy75d7o8helWnBsvvixqWmCON4WSy97dJwy2 +1u+XjG/2+GSQRv6EzCaWRQ== +TZ7deu05xHGE9hF3JFWmFKNL8L7HO7Uej/uyjh+xMus= +YciZwvvDXocThCkMB+g25MvaKVnS5K98mkE7NC2a/WQ4mA3Ht59gE6D6i32RtvWR +qAbVEM8+SQkJ03a0z2JhDtNtV08C1I/YV/6wz4lDUuejaC1OXdVMCt4PD51qEytS +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN698k4+GGpSbXcov4Fqu/oAw0FvcgRKi/DMw/D9yaLkasmWznSNTKwz4/hi3mzIakiA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklcPuLfk0fyZt9CAMcgeEUFsN3OsL0zJLY8NmDJQSQhDA== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN69PvoA3R0Po8RscBiX748GvjYuB6m9pC5D+fF3ib+VKf ++plE/1bhdo64kO07cLlUX7e7q+kMaq7bcRu2iq9qqBQ= +1u+XjG/2+GSQRv6EzCaWRQ== +j/AwtlZD2qc0Bfi4crT5PVuymX2J11M8SjzJoWxpQSM= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +rqA8uevZ84obkmE4+PiALqIGE496RRgzEOnxBnCJi5KM0A/RxMfdl5D+mQS+eMty +hCTaGH97j27PDhFRKBPIW/dJaxrfoD6RixVo3Rf5Yz3CGP+VwV5No6W32fmew1fv +qEXluMqy4s3YaMlhK+yN2FagSHb43j5sbjmAKOncUT9h+EXTNgwO3qaX8l+Kgen0 +/Apef+rUhQ+sRd878DPgvuQmATc00QFbteWJRrJXqqc= ++7P0MlEc1eFk6hnR1wxxsLeMOEssU3yb0jKp/217OlU= +hC9Wqf+oYT8RvKOl1V/o2ht+Kp2nrdRz7LH4nKSmxsw= +qMRWIbfylJbi8JdtlTKCc5qLCzoG+56ejKCriefYCzg= +VmVrGQo2zRokW/ZuO9bN66WOTbqs3JJ9oARgY2YlMq0= +VmVrGQo2zRokW/ZuO9bN617WCoPIsnyqpe8hS61d314= +sQfTf8f6jxyRSx9SNPNEtQ== +qMRWIbfylJbi8JdtlTKCc1faDhfd8MAGeioU59baa8k= +VmVrGQo2zRokW/ZuO9bN66WOTbqs3JJ9oARgY2YlMq0= +VmVrGQo2zRokW/ZuO9bN617WCoPIsnyqpe8hS61d314= +YH231WTDnzQG3bFilBiqIg== +661hZf7vhUQ+50okfwfTXw== +1u+XjG/2+GSQRv6EzCaWRQ== +NrBMtcyWGtR1hVD5pwJYfwziCIADCHuKVoMfBVeB7td+eZjbZWU5lEr9RqqI2vmp +uNSTKhXsc886vN3euLSVYkMvdkNBDE83aF6HgmNzzfl+lr+dusojmKJFGktXhvrngS9Kwg2wwLx9IK9kj8NxAA== +Dx/Tamsgq45f2G8qqPP8mCCoT8bqocyYm33wbOaP3Tg= +JpR/5cELI47P6HxjnatoQ2/atXOIHNcG1sc28haUJFY4USrV8c8DFQmuaJj5pmjKoadTYNH/El5wiqoWDGDr6Q== +lNbaHNaSPRfUV3Bq+5wk63n5uJEj3P2III85nJ0uhYOX7Jw57j5UcSlV5FUAGDx8f48V0h/MPMwLz9LF6kln8w== +1u+XjG/2+GSQRv6EzCaWRQ== +51UII3rZ87NZKsRBZ0eyLdVqoWpbfe8TmH2YJ9DWmQlS1xLSN4I2NnpSxLSgoi6ARf5O3cCN9gvq9c/fk2WhpQ== +51UII3rZ87NZKsRBZ0eyLdMEmNp2vC2WieoOz8+5XT1h8AmvAqKPSJXYghFKotIHwSJazFvEKrbDFNimm+5wCA== +TNgWuy5WhP0PGg7S3lA/vq/Pa4m0zdEJKukF/iQ8p7w= +1u+XjG/2+GSQRv6EzCaWRQ== +3nekdX1X45iQPZ9gGedeIRFpzvW0t6AmXo0Vs+Q3B7MtIFmd1ITSQq54rNj3Of0V ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +W/E3KX5TvlXFgPkaqGzgjqZHeKuJLKPP6l4qtgNz7IY4hJWE8FmjSzvjmmmb5VunZ4QzK5iqrn9HNqRecsPrrjLC7uAEfwtdQjEdDm40Dwo= +iPZI+idezVAJw0Ib5lZU4dl17+hCGjC6tXjNver4UbLVz5ZhKILGgbDAaOsRj0c/ +GIeBciyw9rxD3VOfe1EU6vwglDMZ6MEBP18g88btvtydgx9BEGtIPKmLOI6QmFbkxdYO7/HvVy6vSz6J5utDXg/muXopzjQJUZywMhtpdPA= +VmVrGQo2zRokW/ZuO9bN6xVGSYCDtM0pf92u8Yp9YqgrkVutZIWx3e8oFQ+V83/M +555vbfqS9x/Bki5IPjqgDMk1rrU5PWcetLJxQqKiztI= +YY5wEo94l9qfo5TPxQmUgiOUk9wZ4yEOytm43lMuFnP0mvaVswvUBe1Evg3XhOXV +VmVrGQo2zRokW/ZuO9bN6xeLX5vqx959YU/gZDt0YvZIPNF3mPlWCXYJIVjVWyc/N7aPs92P0AWzUUvFuNKpd1rTZ0TR2jK/sb0o+La8V7w= +VmVrGQo2zRokW/ZuO9bN67dFbrzCL8hImD2yMrdv4Vg= +iPZI+idezVAJw0Ib5lZU4dl17+hCGjC6tXjNver4UbLVz5ZhKILGgbDAaOsRj0c/ +cHKRqjHJIG5yrQxjLveUB+BsKw4vJCwsahPFceqybLFw1oHD0QEOO5T2HuKqoFvf5JKkTScorvUimpe/w1iw3/20b2DhJrTx5Z/eZ8G1Pd8A+/nIh5G8dKiIOMDYEwkXv2bX/3bHTYJSjBrGPPQ2T1tEB8qyc93ELHuc8u/HjQX54E/ovWrEq5ukBa2/xfMqwB2Rn3KzuET5YnmuBYAyK2tysxpciP8LIikFg5VJLeZ0Hh9/dIZ1ahA7xKqyIZH+BO4KLnEoXtwZviiL88jehOElk1E2Xq7Ym8ZGcXfqBfk= +9HH0xFa0JPpRaw85JsXnfXwg8i96ZpFEflcqyM3HGgSPSZ2dtq3IxwdeQSueeE4yRiNtorfxbpEMEQJLb1pr9A== +555vbfqS9x/Bki5IPjqgDMk1rrU5PWcetLJxQqKiztI= +YY5wEo94l9qfo5TPxQmUgiOUk9wZ4yEOytm43lMuFnP0mvaVswvUBe1Evg3XhOXV +VmVrGQo2zRokW/ZuO9bN6xeLX5vqx959YU/gZDt0YvZIPNF3mPlWCXYJIVjVWyc/N7aPs92P0AWzUUvFuNKpd1rTZ0TR2jK/sb0o+La8V7w= +VmVrGQo2zRokW/ZuO9bN67dFbrzCL8hImD2yMrdv4Vg= +PAoN3u73Z3nXo35rPzNO33JGqzHB3DHCHZe0q2yDue7WFNUAgfdpyjJARPhEvbbL +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +Z02eW0OyMdUTGgS/rY5UtxvNt0bONzQv0Ne5fwFyOPvckIt7Mxia2dqwynmp7umh ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +3mFSQTn132ru23T3/05U2VtbqO/J7l6+ra/ix/dBlb4tFyl4q2VRrR7kEYMUqY32jfvvBlqKgjBPSVLN+fFOQE6uwzVJbbAN5c5vub7z7MA= +iPZI+idezVAJw0Ib5lZU4dl17+hCGjC6tXjNver4UbLVz5ZhKILGgbDAaOsRj0c/ +GIeBciyw9rxD3VOfe1EU6vwglDMZ6MEBP18g88btvtydgx9BEGtIPKmLOI6QmFbkxdYO7/HvVy6vSz6J5utDXg/muXopzjQJUZywMhtpdPA= +VmVrGQo2zRokW/ZuO9bN6xVGSYCDtM0pf92u8Yp9YqgrkVutZIWx3e8oFQ+V83/M +555vbfqS9x/Bki5IPjqgDMk1rrU5PWcetLJxQqKiztI= +YY5wEo94l9qfo5TPxQmUgiOUk9wZ4yEOytm43lMuFnP0mvaVswvUBe1Evg3XhOXV +VmVrGQo2zRokW/ZuO9bN6/+//o1VxepUuVN4dtwtzUt1O4qqUXiJ+PucnHjF/NzPsClK+zrktiTn+Ec+l1k8pohfVzCbgFrhTHJ0VGrqs4Y= +VmVrGQo2zRokW/ZuO9bN67dFbrzCL8hImD2yMrdv4Vg= +iPZI+idezVAJw0Ib5lZU4dl17+hCGjC6tXjNver4UbLVz5ZhKILGgbDAaOsRj0c/ +cHKRqjHJIG5yrQxjLveUB9ExfFoUGJtN5RuV80lRdDSmDk68awlmo7TMrBJtA4GHSQ9ZM94NKEG1NkhDJownhQ2r+B6ZIf0BzeTxSESFjTE= +VmVrGQo2zRokW/ZuO9bN6xVGSYCDtM0pf92u8Yp9YqgrkVutZIWx3e8oFQ+V83/M +555vbfqS9x/Bki5IPjqgDMk1rrU5PWcetLJxQqKiztI= +YY5wEo94l9qfo5TPxQmUgiOUk9wZ4yEOytm43lMuFnP0mvaVswvUBe1Evg3XhOXV +VmVrGQo2zRokW/ZuO9bN6/+//o1VxepUuVN4dtwtzUt1O4qqUXiJ+PucnHjF/NzPsClK+zrktiTn+Ec+l1k8pohfVzCbgFrhTHJ0VGrqs4Y= +VmVrGQo2zRokW/ZuO9bN67dFbrzCL8hImD2yMrdv4Vg= +PAoN3u73Z3nXo35rPzNO33JGqzHB3DHCHZe0q2yDue7WFNUAgfdpyjJARPhEvbbL +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +NuiaTGqwOgT56I/A6JuW+OYXU3qy8sRWssvgEhRPgEg= +sXXQWFPsb9cQdoRNLvTIV8rn2NRc5Be1ATIx2zUeuZqsOYDCo+7iBPhL/G74PPQT +c0jePRxtTVZYop75Q5JCFggVGiy5K37SY6O292Ia4MPkJnXEVXE/4VzqWDalKAbQ +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq5ZzPLUX/xqPUerYk2PoPsymFnJ5By9OX6QOc1ugur9I= +1u+XjG/2+GSQRv6EzCaWRQ== +GtEk0s5RgbBW28KN6lhHJPglIAC2uEFNpbOm1inM5bU= +b4OJVZe8QyIpjuTpKXDL9A== +84Vymj90Wzn5yYuvz1pUuANPeEDUN+p9ZnLSm6zv1bU= +VmVrGQo2zRokW/ZuO9bN670H5P+s0tiwdJfbGZCx661rd6moQu3BvusznuCG0tD/ +84Vymj90Wzn5yYuvz1pUuFE+OEJSVzvJho5ZjQiIPBe4Jo8zq8jO8peaF2StFqJO +VmVrGQo2zRokW/ZuO9bN670H5P+s0tiwdJfbGZCx663i8WbYWNuKhaXf+ufb67XdedTrYd6XbXJ4R6divvitmA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +1u+XjG/2+GSQRv6EzCaWRQ== +xvhr5kRQI+GM++iQkYI5iZR7ekEg1I0ti6W1CX4my1o= +i03pkbdctj+nAGBFpRXJf4F1uccpkrAJ6vfCX+MrMLo= +ZBEun/friv7tm8hfXQraKbzUFK6ic1paL3nAvarxZME1vwjFDPMgwxS15zNwFDB7 +1V2v8QerKOmubvSxgB4eTBXpUZR6PqydO8mMhIOfyKFGthL4I2hlbLeW2mVUWO0YoHQS7Cw1T4h9CJtczURt+w== +VmVrGQo2zRokW/ZuO9bN6zM/X2hHBCd+n2OGnnATmrSuY4P/AlnqUWfNJ3Nv+yym8gDrRRn88+E97SwP9crSGg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkljVbRSmRphUPMLwP2qafpDf0RBDjAildUzjjnayI8DL7HlvMSlZFKOqgTdesXR7sc= +1u+XjG/2+GSQRv6EzCaWRQ== +W+5RMzryWgfLCHr0OO+ErKxDL7PCXIjhB4TInB8eVS9O5hZl9zfsNzlK9ihwJvwAWqnaaomc3arDNjrzacptHg== +VmVrGQo2zRokW/ZuO9bN6w7q5JOrUTgpjW8N+SP2Atc7he+WeQiHTFqNuIlGY0i4THU1qwL26q1GrHcLZME4I/17G4fCcBY2RQ1y+CHrq3Q= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw5qEGRH2b9ICWRSmYmVtGvQ= +VmVrGQo2zRokW/ZuO9bN60RwCdNuq+t1770A64ug2G/c85wRX5QkmJMqMmYnxhBYkD7eiGF9ZH8bGFOtAVcsnQ== +VmVrGQo2zRokW/ZuO9bN6y1I47Lsjes5Xkzm/KZ2HUj6vOCoGShzUiscvkZRGEAK7uATIRxawNjeYl+9wQBySA== +VmVrGQo2zRokW/ZuO9bN68viVmfoF0CTwLptyJBHtwoSZr9tnNwdtzzzsyNXbUiH4ZjqOIUbdd3rJE7TWaxfypVJqZu/Aj/SGb5km46+4nA= +hy0P42EdUgeeptYosmZKRIqHHtOztyzrdCi1Ki/o8rei5TptjILoB9DzYg4dhtD8 +1u+XjG/2+GSQRv6EzCaWRQ== +3t+PWTTS6PQiwtwlmlTRxrj1Qed+wRQV5P7UkrnHcTJ09C2yMsencaTpdTOmxNS9 +1u+XjG/2+GSQRv6EzCaWRQ== +W3A5Dt+8AxWSKsTYeXZoLzA4p6eOOqHFU6wo6i6M1wMfoECZ+LC1zy95fZWoQ/4A +VmVrGQo2zRokW/ZuO9bN6y/sHulJCdzqGiPnuEJh09GPOIF9J+niHBhGtw8fD45+pm0DBOHDbSGRphcx4joVf4s3jxWlrdo/+Xom1OuxaXU= +VmVrGQo2zRokW/ZuO9bN693VxLLTwZ5oiZ5WvX/IvvnPb+1eXq+YPA/fVCnI376l +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN69ju9su8sxzPM3AaGjlemMgcJAO2F+YRyjnWgbx5kZm3DDgmoVN2NnxtI1J/EBiaN3wzhoLg5uOydx3vmy3nBp0RYsKdXaZ0938zc7X1H9j+ +1u+XjG/2+GSQRv6EzCaWRQ== +qEXluMqy4s3YaMlhK+yN2MTRTE+r7oIVVgr5FZa6wktdkLfItiTuEdAda4Prd/uu +NrBMtcyWGtR1hVD5pwJYfwziCIADCHuKVoMfBVeB7tfQcYQo9g3WMZE+QdDqr5d9 +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYhJE4xrEpFjyOULkKEdGQ6xpKpQ97D+w2oBDgGLxKzYi +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN6ytmrKShFDNF/sBD9TyNb4M= +VmVrGQo2zRokW/ZuO9bN69ZEePYalO2NtKh63r2pCPdTwSWq+E1d5Vwp+OG0GW3QWB453G+Ivr5bi/FDqKsXgs+adjjgioHoNdPlikgjRC0= +1u+XjG/2+GSQRv6EzCaWRQ== +hCTaGH97j27PDhFRKBPIWx5h0aqe91PCFXPRyuarZwHCnfY5zg3rAGF8JC1Rlqi7 +1u+XjG/2+GSQRv6EzCaWRQ== +OE58XJkoqje9oPDfqi73VlGkW2sntmceo8fDxcMP5lY1ekhmQtt77mccgEqJdX7o +bjpI32qzKze0nfrRYYQXmxQCptR8qXtM3eo8u7Hcpi52MCDBviv4HsElaaoUM2T8SccwLoZulXlCQSP1tUW0zQ== +f6i+lsi35nURDz6EJdx9zHkEwXP+oTAogJyWuMcbY+bCC4q6cHvMzOZJC/zSZqSlBoxWJ31FyRCKzYVEIwqIIGphFDgbiCs3jOWEnfTVYvpfqohCFOIPs7aAQ/e7qizKq2O1OuN6sW3wWSCv1R60FQ== +lNbaHNaSPRfUV3Bq+5wk64nrImxso5dDuILvGI+MQwTmWuTQaQzUpeGGa/V25O8w +1u+XjG/2+GSQRv6EzCaWRQ== +PWKGbNEPxxxpGpe8THO24yNGDxaAdaWP6Ho84adPD4xRdueQCK5aifGjt4a5kMSr +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7BhJJn4LWRADsH3OSK3w2mDm +1u+XjG/2+GSQRv6EzCaWRQ== +1D18KWr2hdVsBcdZx1OaPRKa04PZd6Lh4pE5HE5I+6w= +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7BjTWa3H547K7NAn0dgBK9APa9PKBQvJpaRJnxbK6GlinQ== +VmVrGQo2zRokW/ZuO9bN6/Q2k38eGwybF6UxQWGmZ078Ummo0l7USrgIMp2GbYxOX/mhLPY1elsTwCazaEKdmQ== +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7Bj2jdAwm8k60quASDa57AGB+88A1mMb59gDR7BrxstZVQ== +VmVrGQo2zRokW/ZuO9bN6/Q2k38eGwybF6UxQWGmZ05NTezu3MlCHaWwWIcPk44PcMKo9jE/vk9iLOai9+rrxA== +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7Bipb2FXNrO3nmjKp7FS8IV2D1PFSWGe34foTBexG5IW8NvIEINGi9MDK+xkxh/JWYQ= +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7BiPv3Kfo9CJTrr9+UH2pEnYBMXobVjY6m+4Nub6BAneGA== +VmVrGQo2zRokW/ZuO9bN6/Q2k38eGwybF6UxQWGmZ07a+MiM4TasrtElO3rR88djBDttzYexwnhRBI9yaixsug== +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7BiBPPnFp2y3W2sI/ics+tip6wIqnrMeHchezJBWwG4BnXlrvThfCLQ//csXUmX785Y= +VmVrGQo2zRokW/ZuO9bN65IkmxrkVECa18yoyhjHuJ62aVQkcicwTpC0ikCyAi/t +lNbaHNaSPRfUV3Bq+5wk6xOlK22tFRCz/MBWHqTO5bcv8iF8VlYARBwBFw2wdPus7zN51U2zCgV+IxIlEMlayQ== +l2FJPs4YkAmmok1ulDRuSA== +lNbaHNaSPRfUV3Bq+5wk67RG2evr7TLF3SKM8U8p2AA= +VmVrGQo2zRokW/ZuO9bN6+GpeDJLU0SmT7L+/5sE5xIlwrT8TwQpkYYGHjbVkNftN1CKdBCjUzympMQ9RT/FEg== +VmVrGQo2zRokW/ZuO9bN67oM2Yuk/fGj3koZ5Ii2OcjDkwr5hlr55yICOO4Y42+5 +VmVrGQo2zRokW/ZuO9bN67jx0a4m693Et0R92z2wvL71x/3S2aSF/NvGCgu6CHqf +VmVrGQo2zRokW/ZuO9bN64Mo+xDfj7h1iI635dygp4XcwMixUpSGp93xEK87JQBN +VmVrGQo2zRokW/ZuO9bN6wbwiW2kEQvsTvAnLBH2llxizFasN29ZbVRWydu2b7CB +VmVrGQo2zRokW/ZuO9bN61gFm3fkb7F9QaTU95L/jPPifv6QNOCZefRXntGGdHLE +VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE= +YH231WTDnzQG3bFilBiqIg== +1u+XjG/2+GSQRv6EzCaWRQ== +1D18KWr2hdVsBcdZx1OaPRKa04PZd6Lh4pE5HE5I+6w= +VmVrGQo2zRokW/ZuO9bN67Y8lER9Kabik7S1mV8J7BgQnyGGSctkQxn38k19EVX1 +VmVrGQo2zRokW/ZuO9bN6yJjT4k1EBcR3P33HPsJKcS2iJiueG/E56TTg8JuY5PMBrWa6U4B5U8rW1KDAZ2JSficZo3wFKS2XvW6ui8Tv2Y= +VmVrGQo2zRokW/ZuO9bN6/DCJtLswi79iRP2VS1Cl7m3kLpB5lzqL2Me52OhtZ1zUFOJHRYhWSvYLeuSZixvqmuGQwZgeXJcumVSIEgd/00= +VmVrGQo2zRokW/ZuO9bN6xRgv05zRD9dGM5M32jg6eA41WZXP7dtRrdMXjEoH3lvGk8OfKeqpDa0bqO9TtO5SbDD1FpNmhqJhoAYwQK56Qs= +VmVrGQo2zRokW/ZuO9bN619j4sjPeFT5nbRqDaMF1SytlAuchmjuOqjKzKLlRlpKp0wX5I7ca4hs64AaenatopykN6zoa1vKVmy5GNym1kk= +VmVrGQo2zRokW/ZuO9bN68Ys5qSHlhgxubR3riCCkMH07t3rA9i/RBuH8FqAjmcn82Cl0Yv2F59R3Y2ngEly5Bn5i06KWEOErKyGGIaEm5s= +VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61MJdzJmpSSmQu1uh7zy38qILLEI2fsEffNYGvDUZieQ +1u+XjG/2+GSQRv6EzCaWRQ== +hCTaGH97j27PDhFRKBPIW4EZFPkfMH002JrNm9PLnXuwc9yq5Lyo6aOmZ30BZnoK +1u+XjG/2+GSQRv6EzCaWRQ== +2+RceSNjD2iq0sRzepMysYKB2p+0quZ3k/r81UP7jdA= +0tTy9hW41S4+Kusr1EouU1D1oliZ7e6CIIanBdYINco= +QSlGrRvW/rNBDeF7we6iBWKk2BA8QQKFrP2zSqI9P7kNT8zHpEzOqA9PqxnKhMkciutQQEuCUUTddAcDKQqmZtP+tM67XZ4sPVMO7+1M9AE= +B46DYlFQtDlulKRQCnJ3QWpbjBDDHLSb3vyz7F+mTm17hwykciIqoQznuIX47KmXxdbWZDnI+CY124mc9V20Ag== +IhuisKim47k91RVt8z8qtM11IfmUg3Vzb1vU4L+nUSEXI43SqtfmWpr/I8AYFS79jp/blpkCZsPn/8gvY1O0DDcOOIqpPruCuHCHNHbX95w= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz5S9puu5cR7ebyYVgOteskCUo9CHPdWLgUk6a7nUg/Dd/W6vX2Ar2YxjHE1Ly/3O18= +1u+XjG/2+GSQRv6EzCaWRQ== +Z02eW0OyMdUTGgS/rY5Ut6VY+CyNgF9FPBp9CnQzrMw= +Y8LE4fq4wJWePrU6pM0UJA/F7Cz7yGlo9qD3wRRTzkoqBjEYPeDTgDuU3vQKtZ/tslbdISWCHKSrP4sG9aOO6w== +c0jePRxtTVZYop75Q5JCFggVGiy5K37SY6O292Ia4MPkJnXEVXE/4VzqWDalKAbQ +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLq5ZzPLUX/xqPUerYk2PoPsymFnJ5By9OX6QOc1ugur9I= +1u+XjG/2+GSQRv6EzCaWRQ== +3t+PWTTS6PQiwtwlmlTRxrj1Qed+wRQV5P7UkrnHcTJ09C2yMsencaTpdTOmxNS9 +dh7w9F2NtRbFZXQAT43UNEb8EuN3AGhltXubCNFU3Ojzklfcg9iluJiEzQBZ1zSs +YnAdY30Lg/9fjxdn+Sc57EuR2oeC8XoCVhpUHNm7E15HYPr7Brw6YP/tAPp/5VcUf3nQ01NwI7KLps+jszazNg== +0bmFnPqs+8pv2SMKrUKBGjsjPAHPnQkQBpMrQ+IQDC0= +vxZRPtvgnx9GXrXB4G/UM7qbPAq4XIfzYp2SJP08XdQ= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN69ju9su8sxzPM3AaGjlemMgcJAO2F+YRyjnWgbx5kZm3DDgmoVN2NnxtI1J/EBiaNy+VxOc/ziUJR5VmuD66oPA= +1u+XjG/2+GSQRv6EzCaWRQ== +qEXluMqy4s3YaMlhK+yN2MTRTE+r7oIVVgr5FZa6wktdkLfItiTuEdAda4Prd/uu +NrBMtcyWGtR1hVD5pwJYfwziCIADCHuKVoMfBVeB7tfQcYQo9g3WMZE+QdDqr5d9 +1u+XjG/2+GSQRv6EzCaWRQ== +hCTaGH97j27PDhFRKBPIWx5h0aqe91PCFXPRyuarZwHCnfY5zg3rAGF8JC1Rlqi7 +OE58XJkoqje9oPDfqi73VlGkW2sntmceo8fDxcMP5lZFXTmVGM0w7rfgyRt9m7p5 +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN6ytmrKShFDNF/sBD9TyNb4M= +VmVrGQo2zRokW/ZuO9bN6xf5/2Z1ozJYK3IX9gEGYhudX+JDHzXKmtCPQeY89xZsmJ01Lxhee8vjMmUoN+52YwE0e8xmkBrpg0JAK3o4nqk= +1u+XjG/2+GSQRv6EzCaWRQ== +uNSTKhXsc886vN3euLSVYrxsmQUHvKax1CVDvgx4wzZ3gluW+Zte0pfXA1MbL3uR +1u+XjG/2+GSQRv6EzCaWRQ== +PAoN3u73Z3nXo35rPzNO3zk0x+iuneUZogH87K0TFCnqFSMDSQMOfHfmH+Dp486Z +0Ey56M3Rq4Z87mMZBzWCke79du117mx9ftztVnc2s4Y= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmX0TLONUcGRzdgUZesuWNcR +VmVrGQo2zRokW/ZuO9bN65W8leO0ggdGbf/NAsQSTaUN/Vkvk2lUEMRqevWdlxhOpMxrHYmMEeuI0pVjPC/pCg== +1u+XjG/2+GSQRv6EzCaWRQ== +yRP7zwdiezjpAYBvh+/YJI6Z+aRD7eDMoKmdruOi/nVKZCf2Kpe/EZS92lyLhXJw +S2kU4JuQ9t0SS+VL3mxwjQg60glazkUAeC35PDN/3gi5UC0kW/K3ybMcAsPxO6OqOno9DTOG+s+nbhv8quAd37RmBLZ4bWlOVxQB8830aY4= +VmVrGQo2zRokW/ZuO9bN65NDZuHjHfhtjN50qQqxWwhM0zCWzMVFfYkfFQXIapw7 +cbvASHjpysrsjdY5RctXmCMIFkDgRpOy80SbmGIoVLlLES/Dr35tD/epk2Bblup2 +L0eUthVnpkGsmKFAX6d+uBWSR38I0ooDiDs76zI3rHk= +sux3pEp0y4Ts+BK0jWjiPLanIepY87Lf1wBsVBlLv3Y= +32pdC9DD05OE2l0oXazDFCL2UDwltzVzeKrLP+917rjhOkK+v03ZP0I3EFsTN5wdo+TxLXpWwlAA41kIMUBwEdM0Vq0pElFIAqVtyfgDNJA= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmXtpUVC1kW3J/dl/fkcePcu0IQQNXefRtOGv4ZZf1LvA== +51UII3rZ87NZKsRBZ0eyLdVqoWpbfe8TmH2YJ9DWmQku+7dd9C856BngwQGeroz0DGruUOn3OzHXaFAtZ68Zrg== +2+RceSNjD2iq0sRzepMysYKB2p+0quZ3k/r81UP7jdA= +0tTy9hW41S4+Kusr1EouU1D1oliZ7e6CIIanBdYINco= +noar+Dkj/qVB9vIuoUrH4blDBjPJbLLUMktkKwDg+T3Yyejah7ZSTiS84g0cIqgmOkfT24OfZWuI2mlySo1e1rXnhvoN3FmpA8o1x0iDGmc= +VmVrGQo2zRokW/ZuO9bN69LIx3FNqnEXJvGI/6KUnVMBiTvkfBHbYk7rYKIjJcSR +IhuisKim47k91RVt8z8qtM11IfmUg3Vzb1vU4L+nUSEXI43SqtfmWpr/I8AYFS79jp/blpkCZsPn/8gvY1O0DDcOOIqpPruCuHCHNHbX95w= +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz56MuFgEZqjEPy+8YvS/67ALmhmn5yIn6bMKi20dsYGHJ1JVuo73YMpR5XMGsGJGqk= +1u+XjG/2+GSQRv6EzCaWRQ== +uk/kSuwtaQ0nOezb8+RxpwUfyvjVM2PQ7j0JF7CVDyk= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +aSrbsYE1ObLqGiiKHJGlHAcTCUngVNP7oPzhbRYTc1LguHKuTuQ19V/e58YOe1oL +vHflHGke4H/vMW5vfCuuUZkBDGb44m2BYpshIz0Qtv4= +OqT2rZxw+7PFqTI/7vaTYrDpIYlrorSNlnfPF+E80GZUtRwUSNGvcVbzeIQULPWq +0lIZluLVeEFaTSZzolqlcLJwgguKY+dpcMGzanWqRYgG35UDUa7t06U8mwJTvAIl +3HT+U1PE2cuTIdd28XDjLu4fELXwXCAvh+GRuQu5k6XIkFRLUhSeTkeWNqYg2bjX +661hZf7vhUQ+50okfwfTXw== +b4OJVZe8QyIpjuTpKXDL9A== +RVzqPMqMgbuirEDXLgMelnPNG1gGdxjJLuVE8z6iQD5rYH0fwwATzprmhCYaEIEPHwpSMPEmiRRpa14KTdZ8lrL8H7fceiNvq4Xjg2gDlfA= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +RVzqPMqMgbuirEDXLgMelvzcH7BOolbnVIZqDiEiOec= +1u+XjG/2+GSQRv6EzCaWRQ== +Nyrr9YR1SCpCbvnfcYsQdEJ5IErIE8c0cMFm3mG2Yjebpw+Cl1FLBTAZQhLw09+DifJASUH9cY0pNUe9YSHgQA== +1u+XjG/2+GSQRv6EzCaWRQ== +Nyrr9YR1SCpCbvnfcYsQdJPhbI7hgpVa6+b+pvEaZAgc7bOsYPSHQlBFlGcB4Tjs5+5PriIt/rre1MNSsEjYGg== +1u+XjG/2+GSQRv6EzCaWRQ== +OE58XJkoqje9oPDfqi73Vka6WpT+CnxlVbAMrhaI5qQllYRS+Ky/Og2S0buO4RDjLaK7aaOhQSDmoE/kbmyEX0/AwGUJ48Lz1OaTrJghsS4= +5p980mxWRpkxkeJsimzJ0eLt36Y+8CY+qHE8h6zZGmRXfw3tRaIncwdy2TcKbyGM +VmVrGQo2zRokW/ZuO9bN67r9iEkxlbKOXEHHo/GR3+6OlLSZTy+2hsIBqqaZN8zs2PwpJyZ9IxXb0V3MIkxqLA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6zsF8RkKy/6qv8OwuTSXg5vBuddKZS4lQenCng06/MCtanygpIb/Fmy0N+f5854jLg== +lNbaHNaSPRfUV3Bq+5wk6xregBSeqbTc3HP8b1a2L+LfsHzyC7B1QGy1YoM9wHb8 +lNbaHNaSPRfUV3Bq+5wk6x5c5FnmEkX4AMPnck73YtTfeAm/ehjF+VgLA6BASKYSOc0tdbZri4rI9oHgjk7DZ0y1lndySUX1LpsU+X4a+b4= +VmVrGQo2zRokW/ZuO9bN67l3wsMkIC1w41ITKeGhwtPvYwn0s/yxa7F2gxzMl8sIe2o/YPydyBKaXszPWarTog== +lNbaHNaSPRfUV3Bq+5wk6+MyMCLzOgYEev4vKFrT6VYswGLpldqwZFYz/mwTTxzi4N9Vog0YC88LxDAzU619Jg== +1u+XjG/2+GSQRv6EzCaWRQ== +lNbaHNaSPRfUV3Bq+5wk6wP9GMD3ujIvzKZlvdwah6xFKn9/N3/0c1ldYqNuhjD+DafNHKr4KQgIaI1li2boiw== +l2FJPs4YkAmmok1ulDRuSA== +5p980mxWRpkxkeJsimzJ0XxWkYe8AuDbH/wDJ9SMovWF4UqbpzK2e1xLE+WLbzyS +VmVrGQo2zRokW/ZuO9bN64uAF5aDWHLl0Mr6n5QG1UKr9GBXEgStOxmayztSy2KXpV2gQCOW03tZ9GFDtTtwKg== +1u+XjG/2+GSQRv6EzCaWRQ== +Qqw7/vtLTsLpPtVEcrxvqt1KzLgzU6i+9fBX4qg6S06lwIrFxe7lO1lW93JTA8QUi+I+L9IoSN9HvHBBfimTuQ== +EJoGFvRvZ0ApgLDRyrlLhA== +IhuisKim47k91RVt8z8qtLgFgQIxiwgnyWmJTJdd9h3En7oUD2HpPW3NDaRGcUSt6YoEknERcJPsfCBaPB4nCCEZAIRq1lDGynvHBaj+qnU= +1u+XjG/2+GSQRv6EzCaWRQ== +ZmnvqmOaBVPZA11BQvdr3w== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +5sfgsihl9XVF/Mhh9qm1EXf+NRhU/cymXz1HbW9F6P0AEvJeZudYCA+pkRXe4Uye +hCTaGH97j27PDhFRKBPIW/dJaxrfoD6RixVo3Rf5Yz3CGP+VwV5No6W32fmew1fv +1u+XjG/2+GSQRv6EzCaWRQ== +uNSTKhXsc886vN3euLSVYkem6h/gL5IHP/XBe2yL1bo= +RDZxR0eU/0YxwhYtovyGeQdnHApsoYjk/m7WJoznATFPayg1JrxurOnrWeCKYGPxeVB/Pw06PW3t/mffVKTZXA== +5p980mxWRpkxkeJsimzJ0cW/XPkGPu2EZf497lktZsIIxlZhnrw6R5u2fNEYrFnx +VmVrGQo2zRokW/ZuO9bN60btJ3KSmT5EZdF/tXV227NT+FaJI6ICAUv8U1dGM7fI +VmVrGQo2zRokW/ZuO9bN665BzXWwO22NoRyeko8p9TvBNbaZ4mmPHkdSjtdhxjFs +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +1u+XjG/2+GSQRv6EzCaWRQ== +OE58XJkoqje9oPDfqi73Vls284DI6YzG6XdVqkLU4Lg= +L0eUthVnpkGsmKFAX6d+uMq25m1vOsGY6uP5qR9ZDEk= +1u+XjG/2+GSQRv6EzCaWRQ== +Dx/Tamsgq45f2G8qqPP8mNKTgXHLg2Jqs99bb34tU2xwBPXgAea9fPTD7mOcju2nndDdcCRYG/Dk9k8gfM4mlQ== +1u+XjG/2+GSQRv6EzCaWRQ== +OE58XJkoqje9oPDfqi73Vka6WpT+CnxlVbAMrhaI5qTvWyB5POI819VyoubLgWwB5otU5A2wKU0FdlIfCfOYuwe8tZUX3UkX2C3v16xcahM= +L0eUthVnpkGsmKFAX6d+uEiHn3ZMQfz1ZBQJd6m1KmU= +aPdLEV6HY2SBr9CPW/3NlV6skNvI9JsIj9pOVhf2RvA= +1u+XjG/2+GSQRv6EzCaWRQ== +FNzrf0lVMea1y0IWwp4L8epdqcTQLISFJkRRoX88WUg= +YsP/lfsk7bnmh2Eb9pKHkx7tWRnRvXwXah4j7+bCanMiqa4Sv8rxo4QlIJtxoVVB+SYW2kzxPd3PjQ9b/8SThg== +Z8UsPk1Q7HtwjRd4g01ryw== +mGGXRHZ5m7FaXP7i8r+TjKXApLbXwo2hRd9B7Q+Oh+YScyp7CR8x/JMkWUjHmH2e +Z8UsPk1Q7HtwjRd4g01ryw== +Djb8zu0gG2jt979SRwXXjOvVYuDaNNQCNmI1OvvoD70= +1u+XjG/2+GSQRv6EzCaWRQ== +qEXluMqy4s3YaMlhK+yN2MTRTE+r7oIVVgr5FZa6wktdkLfItiTuEdAda4Prd/uu +1u+XjG/2+GSQRv6EzCaWRQ== +hCTaGH97j27PDhFRKBPIWx5h0aqe91PCFXPRyuarZwHCnfY5zg3rAGF8JC1Rlqi7 +PAoN3u73Z3nXo35rPzNO3zk0x+iuneUZogH87K0TFCnqFSMDSQMOfHfmH+Dp486Z +0Ey56M3Rq4Z87mMZBzWCkcIFCFur1J8qdEROFpa5DTfPmhRCLNzFjK3BYhcI7wHL +b4OJVZe8QyIpjuTpKXDL9A== +wlLHv6kT3Q/RmtMBN4nDAUsRm/HZvlCR+HWwmmPd5BfKH0/mlXTbyd8F5IPAE/TQu6d4aGXkkjKrV3KQBzmzVA== +VmVrGQo2zRokW/ZuO9bN68lFj6lfd3kckH/8VU5KCGqRWPiKTSyoPv5Bah2WfmYPVCp2VdHIolYjln+bUY8IfA== +VmVrGQo2zRokW/ZuO9bN6wN0w1RlCvEbh31Ln2UXkZ4= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64qGHGLHzR3bJ0XTpYCKgYgQpBdbM26ZAvopx/0wQyEoMC3CmrgnHMvfx+hntjfNUHY7L2qLZPXkr8UaUm3Cy0k= +VmVrGQo2zRokW/ZuO9bN6xlpjbR96JcinCRcpOHpwZenlGkZdJSeRDRdYj+Uxd4Q +VmVrGQo2zRokW/ZuO9bN63FDXhc/zgNdUygJevY6sdfklx6SK4YjsNl6mTDBAKgE8h7HBiUUTA9Bt0KMzmPRKQ== +VmVrGQo2zRokW/ZuO9bN674gG529I8kaxTDpeI5FsT8dMvpo4da/IxkpsnqO1PV6 +VmVrGQo2zRokW/ZuO9bN67ldobmDDoizoaOC01WU6Lefy2U3Vi+qnHg+Yz87bA7Yx0kQAUrar1aLZvBPsQzYNQ== +VmVrGQo2zRokW/ZuO9bN641bT1nOxoWTwSy5bjmJpALq5/7D5eyKkgIFXHllLBCM +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6zr+/1wvjvcA1PfTZ75ETzhfDdosE8sRI6toDfDVa/B+GA3ZxX/sQ5x8rNHncB67PQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67bnnwCzLN0nCiOfYCjwGkzY3B74QLecUPd3/scZhk0IehNEkYTW5l8MuaPFKHqVdQ== +VmVrGQo2zRokW/ZuO9bN68lvNCUThol9QOZsTvs3KfyGyJbrExzCQ6mfnSmO05oAUT0VDwsgBOA+xBAbY1o7AH+CI7nyhOXcP2xDCSHKZcc= +VmVrGQo2zRokW/ZuO9bN6y1m/1hYuCtHRuLugwo8qhGp3RtxJPr/0R9/8numyyW4RR2GSU+QZ1wJB5832QK4WA== +VmVrGQo2zRokW/ZuO9bN6zUY/ks5sODBNkit+XUmGYfULT1Hm3kjx3OllDbqxCp5 +VmVrGQo2zRokW/ZuO9bN60uVXYgkwzKRDFzxQBIq76qRjSWU3fpXZBItYruE986KEffW/VhhUdVZm89YsKusT5fg0PTP+/voESni1r2r/mo= +VmVrGQo2zRokW/ZuO9bN6zTqNbvPfLl0muLinRFsIVJnX8w6F7OF6czp+acnqKok +VmVrGQo2zRokW/ZuO9bN664k4tNeuaruJ/sFk8Bpdhd+Kuw7EwE3k6tuVPiwyQPh/dbbmXLoyDryQD+C7WG6IA== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN68lFj6lfd3kckH/8VU5KCGpL8uDM5jESUEVKzanX7SIT34Qe4HuSzT9CzY9onFg14DakCsvU0GyUHbn02itcG7k= +VmVrGQo2zRokW/ZuO9bN6yzC2K2s9gZH1JCopKV0Ar4x5AppikyVQmfuHMxIKL83UsUGiLw1dPHTpVO8OXszJyMOhx9I9L4ie7f5yGBz7F4= +VmVrGQo2zRokW/ZuO9bN6yDqekvzHp5Ed5o8lYwu08gQvbBnlCFR6OzQBk0zuhcJ +VmVrGQo2zRokW/ZuO9bN6+mC4aS+uqusFmsL8BytKn/hS7c7ij9TJiyQy60d4c0NqLXgocb8PZ3sNsFMa3Nip++r3rnQ90me5kM1X+GMQ30= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpglmoyr/KfGS/F8kfb7XBOQmFuNPgF6sTpiQlwU0wFj4= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpyz1Asb8tJdmNZVt1y9C5pjJdvLrwYyZILmakF/bC+zM= +VmVrGQo2zRokW/ZuO9bN693SjNGuvN8cXdnK0A7Y+ycnGy0ZQfSO3nE81pPoyyp3U4SKGgGb3zIB5poznxhWT0VsGXdaYJ0qyM+igR1cz+8= +VmVrGQo2zRokW/ZuO9bN6+6OSQjJyDHPNRB3fSq3EOcAoWNzFMgvgUAWiGAmZDd3 +VmVrGQo2zRokW/ZuO9bN62BvU+ACUylwdsM3AEh5vxG9ahubQdJ/24TjNy1jqCqN +VmVrGQo2zRokW/ZuO9bN6zxA5tAr3l0brvCSLFOCVTe2rbsn8iecC4QUX6qqquupab5ALX5SWCmXowW86sbKKoKxmn3G+92voYN1oleYcRHhyMTKXW/xSk9Vbu3H4Ryh +VmVrGQo2zRokW/ZuO9bN67FRjyUL790gahogpF4bk/0KuYf1lD1XRfqDBFgwVpiQu8K990tiW+jZiLy8UgeJYg== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN68lFj6lfd3kckH/8VU5KCGpL8uDM5jESUEVKzanX7SITo87IQQenhDLL5E41UYEEt1zKN51FxDrrT3NlL/bThGh9fv4aJdoH/yPu/bkEggBJ +VmVrGQo2zRokW/ZuO9bN66pSy2yezMgXP8gSS2GK+PpDegwDKFSH1dYfVl26gnZj1jhP7kPEVPumIlOzBJ+DIJ2PF+2le2jE5SDVCLl1NiE= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehp5dTPBR/trYVhdp27jkIn6J6H5oHikRa60ngWLPNBVzo= +VmVrGQo2zRokW/ZuO9bN61IUDnAmfgBQO6uHH1Yclt0ynNsjH12Q/lM4PiHYK8Yh +VmVrGQo2zRokW/ZuO9bN60JKAP0f5b6Sb7s9Js4pTMpTnlWWsEzXQ+p858xVCqurEIhq7VC4BSJPHvWQ57VJoQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknz/J5UNnSr5HoDQfOjMQiy6/uL7ISdH22fNdwzZ/8iWxubQxhh6seRqDUg0oS1XIAit7c7VNhMHdAx5zY4Vqy+ +VmVrGQo2zRokW/ZuO9bN6ylgXkDyP4zJff9d51ElvAIMq3E5W8e//ErL9duy8ksPrIEGzcQJJw7VFLvdP0ogWi8sDVJaJcJxoKhXOn6xIBk= +VmVrGQo2zRokW/ZuO9bN6/Ykiia9pTHWG10nmQ2azo1nONlRLILx+L82bJzKIMHd +VmVrGQo2zRokW/ZuO9bN6zn88KEm9OgxN0QITEeDgHR9RpFiPd0ZwiLF5lEABccP +VmVrGQo2zRokW/ZuO9bN6/isbQJniBo16ObIGPZRjcFiPqV4uBr2OpohSuDyZjLt0XGVHG4wpO4VVc3reFGBB+/6OMzyTMioQa0/nD9ejqTVB3eW8hcBPdPA6Kl5IG1cw/LW+q0gxEzhV+EvhQTsYA== +VmVrGQo2zRokW/ZuO9bN62dUIyY4EVuFnYchLLg1Q9bs+s6edwxQpeiikyW8nG2yNYj6TTFZTrmMt1EQS41HJA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknNX4uUDjKKQHFJozt8cgdhI80NVDlJ98fuWbEMsUykomMbyoRzTFmFF5uSKeJoOAw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67focE/OItqiH8RxmfzhIw3c9UUf6KvpDEhdOqh/QOWs/1EqaCMGyBSwFbIse4JBKjHHHkNKma+31mNrfboMY3c= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpFeyaEx6TSSxDiBlioSN7WSfpsecKcmnjxXZb+dyTMEM= +VmVrGQo2zRokW/ZuO9bN6yDqekvzHp5Ed5o8lYwu08gQvbBnlCFR6OzQBk0zuhcJ +VmVrGQo2zRokW/ZuO9bN6+mC4aS+uqusFmsL8BytKn/hS7c7ij9TJiyQy60d4c0NqLXgocb8PZ3sNsFMa3Nip++r3rnQ90me5kM1X+GMQ30= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpglmoyr/KfGS/F8kfb7XBOQmFuNPgF6sTpiQlwU0wFj4= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpyz1Asb8tJdmNZVt1y9C5pjJdvLrwYyZILmakF/bC+zM= +VmVrGQo2zRokW/ZuO9bN693SjNGuvN8cXdnK0A7Y+ycnGy0ZQfSO3nE81pPoyyp3U4SKGgGb3zIB5poznxhWT0VsGXdaYJ0qyM+igR1cz+8= +VmVrGQo2zRokW/ZuO9bN6+6OSQjJyDHPNRB3fSq3EOcAoWNzFMgvgUAWiGAmZDd3 +VmVrGQo2zRokW/ZuO9bN62BvU+ACUylwdsM3AEh5vxG9ahubQdJ/24TjNy1jqCqN +VmVrGQo2zRokW/ZuO9bN6zxA5tAr3l0brvCSLFOCVTeGK1EDQpaQ4W+HZzdN7+BB2ZUl65M4XyjU/xYSEFyczi5NDLlf8Rw3rPFXMprgLYPL6UBJd3EBOVlSZi16q0v8cVHtqJGKa0gzXjpjel/S2Q== +VmVrGQo2zRokW/ZuO9bN67FRjyUL790gahogpF4bk/0KuYf1lD1XRfqDBFgwVpiQZsZqUVnw544DWr0gXit21Q== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknzRBBxXsj0OoebX5mHuVG/bP0kyt0UFdIMy35sdT+Xlx9CbxX3haSizx9Fd1+tPwU= +A50UO/kAI17YP7MCbTvBkFTjN5Ay5WDQtfGIvrhR2tvkyujMWxwi3omnSXsVaboD +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +IhuisKim47k91RVt8z8qtM11IfmUg3Vzb1vU4L+nUSEXI43SqtfmWpr/I8AYFS79jp/blpkCZsPn/8gvY1O0DDcOOIqpPruCuHCHNHbX95w= +lIrvZaQ6CfSmdKebjwiKcKdbPCIaBJUdHz+nu4YDNU8= +1u+XjG/2+GSQRv6EzCaWRQ== +a7rKHSoe96bTC1qOWHQhNXgPTFVeAmUicD8E9nHKDLE= +6we+bQ+BNhpl74ICwbzDDxCOR7eR1lYIi6R1B7qE6VQq5fLF725RdYF6Y+savDkL +dHP8w7bwYQXmb0DilW0aGoiXjYmTX2tV1zDoCXSG5x0= +oPiee0X1lhqQ76kw9N2+8TRpQ8dHbXh5l9s3hEzztTi7WxTx3RYObFQX4hoEVzqA +oPiee0X1lhqQ76kw9N2+8VJVl0MJWad+iXT4tHQ1qJM= +S8MkKIBd1s9qx4qteuqTKYtNNok7S0W1D9yy/JgdcKA= +UqaKSIk9RYYWXSK0cIFfXhsSTGfp/1ffoQCd4abr94o= +39tsr9ptyNKBswzHAsYTwQNhpjS5ZgfHk3NXI3mGlC8= +hC9Wqf+oYT8RvKOl1V/o2sfBwmNUWuaRvOIXJpHTuAM= +661hZf7vhUQ+50okfwfTXw== +oPiee0X1lhqQ76kw9N2+8SoJ8C9QFPZokIoCmoh0qvPxsretPquTnGN79JC/ZUu8 +CAhn8dRUItEbEErp4w+lXwA7HvkR+MU9AKdUt6vt65vn7GpKaNuQvNmOChuosmFK diff --git a/class_v2/safeModelV2/base.py b/class_v2/safeModelV2/base.py new file mode 100644 index 00000000..d9966666 --- /dev/null +++ b/class_v2/safeModelV2/base.py @@ -0,0 +1,92 @@ +#coding: utf-8 +import public,re,time,sys,os +from datetime import datetime + + +class safeBase: + + __isUfw = False + __isFirewalld = False + _months = {'Jan':'01','Feb':'02','Mar':'03','Apr':'04','May':'05','Jun':'06','Jul':'07','Aug':'08','Sep':'09','Sept':'09','Oct':'10','Nov':'11','Dec':'12'} + + def __init__(self): + if os.path.exists('/usr/sbin/firewalld'): self.__isFirewalld = True + if os.path.exists('/usr/sbin/ufw'): self.__isUfw = True + + #转换时间格式 + def to_date(self,date_str): + tmp = re.split(r'\s+',date_str) + if len(tmp) < 3: return date_str + s_date = str(datetime.now().year) + '-' + self._months.get(tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] + time_array = time.strptime(s_date, "%Y-%m-%d %H:%M:%S") + time_stamp = int(time.mktime(time_array)) + return time_stamp + + + def to_date2(self,date_str): + tmp = date_str.split() + if len(tmp) < 4: return date_str + s_date = str(tmp[-1]) + '-' + self._months.get(tmp[1],tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + + def to_date3(self,date_str): + tmp = date_str.split() + if len(tmp) < 4: return date_str + s_date = str(datetime.now().year) + '-' + self._months.get(tmp[1],tmp[1]) + '-' + tmp[2] + ' ' + tmp[3] + return s_date + + def to_date4(self,date_str): + tmp = date_str.split() + if len(tmp) < 3: return date_str + s_date = str(datetime.now().year) + '-' + self._months.get(tmp[0],tmp[0]) + '-' + tmp[1] + ' ' + tmp[2] + return s_date + + + #取防火墙状态 + def CheckFirewallStatus(self): + if self.__isUfw: + res = public.ExecShell('ufw status verbose')[0] + if res.find('inactive') != -1: return False + return True + + if self.__isFirewalld: + res = public.ExecShell("systemctl status firewalld")[0] + if res.find('active (running)') != -1: return True + if res.find('disabled') != -1: return False + if res.find('inactive (dead)') != -1: return False + else: + res = public.ExecShell("/etc/init.d/iptables status")[0] + if res.find('not running') != -1: return False + return True + return False + + + def get_ssh_log_files(self,get): + """ + 获取ssh日志文件 + """ + s_key = 'secure' + if not os.path.exists('/var/log/secure'): + s_key = 'auth.log' + if os.path.exists('/var/log/secure') and os.path.getsize('/var/log/secure') == 0: + s_key = 'auth.log' + + res = [] + spath = '/var/log/' + for fname in os.listdir(spath): + fpath = '{}{}'.format(spath,fname) + if fname.find(s_key) == -1 or fname == s_key: + continue + + #debian解压日志 + if fname[-3:] in ['.gz','.xz']: + if os.path.exists(fpath[:-3]): + continue + public.ExecShell("gunzip -c " + fpath + " > " + fpath[:-3]) + res.append(fpath[:-3]) + else: + res.append(fpath) + + res = sorted(res,reverse=True) + res.insert(0,spath + s_key) + return res diff --git a/class_v2/safeModelV2/firewallModel.py b/class_v2/safeModelV2/firewallModel.py new file mode 100644 index 00000000..99686d39 --- /dev/null +++ b/class_v2/safeModelV2/firewallModel.py @@ -0,0 +1,4300 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +# 系统防火墙 +#------------------------------ +import sys, os, json, re, time, sqlite3 +import contextlib +import traceback +from types import coroutine +from xml.etree.ElementTree import ElementTree, Element +from safeModelV2.base import safeBase +from flask import send_file, abort + +os.chdir("/www/server/panel") +sys.path.append("class/") +import public + + +from public import dict_obj +from public.validate import Param + +class main(safeBase): + + __isFirewalld = False + __isUfw = False + __firewall_obj = None + _add_sid = 0 + _ip_list = [] + _port_list = [] + _ufw_default = '/etc/default/ufw' + _ufw_sysctl = '/etc/ufw/sysctl.conf' + _ufw_before = '/etc/ufw/before.rules' + + _trans_status = "/www/server/panel/plugin/firewall/status.json" + _rule_path = "/www/server/panel/plugin/firewall/" + _ips_path = "/www/server/panel/plugin/firewall/ips.txt" + _country_path = "/www/server/panel/plugin/firewall/country.txt" + _white_list_file = "/www/server/panel/plugin/firewall/whitelist.txt" # 证书验证IP + + _white_list = [] + _firewall_create_tip = '{}/data/firewall_sqlite.pl'.format( + public.get_panel_path()) + + def __init__(self): + self.__firewall_obj = firewalld() + if os.path.exists('/usr/sbin/firewalld') and os.path.exists('/usr/bin/yum'): + self.__isFirewalld = True + if os.path.exists('/usr/sbin/ufw') and os.path.exists('/usr/bin/apt-get'): + self.__isUfw = True + # self.__ufw = '/usr/sbin/ufw' + self.get_old_rule() + + if not os.path.exists(self._trans_status): + ret = {"status": "close"} + public.writeFile(self._trans_status, json.dumps(ret)) + + if not os.path.exists(self._firewall_create_tip): + Sqlite() + public.writeFile(self._firewall_create_tip, '') + + def get_old_rule(self): + """ + @兼容防火墙插件规则 + """ + + self._rule_path = self._rule_path.replace('plugin', 'data') + if not os.path.exists(self._rule_path): os.makedirs(self._rule_path) + + if os.path.exists(self._trans_status): + n_path = self._trans_status.replace('plugin', 'data') + if not os.path.exists(n_path): + public.writeFile(n_path, public.readFile(self._trans_status)) + + if os.path.exists(self._ips_path): + n_path = self._ips_path.replace('plugin', 'data') + if not os.path.exists(n_path): + public.writeFile(n_path, public.readFile(self._ips_path)) + + if os.path.exists(self._white_list_file): + n_path = self._white_list_file.replace('plugin', 'data') + if not os.path.exists(n_path): + public.writeFile(n_path, + public.readFile(self._white_list_file)) + + if os.path.exists(self._country_path): + n_path = self._country_path.replace('plugin', 'data') + if not os.path.exists(n_path): + public.writeFile(n_path, public.readFile(self._country_path)) + + self._white_list_file = self._white_list_file.replace('plugin', 'data') + self._country_path = self._country_path.replace('plugin', 'data') + self._ips_path = self._ips_path.replace('plugin', 'data') + self._trans_status = self._trans_status.replace('plugin', 'data') + + def install_sys_firewall(self, get): + """ + @安装系统防火墙 + """ + + res = public.install_sys_firewall() + if res: + # return public.returnMsg(True, 'Successful installation') + return public.return_message(0, 0, 'Successful installation') + # return public.returnMsg(False, 'installation failed') + return public.return_message(-1, 0, 'installation failed') + + def get_firewall_info(self, get): + """ + @name 获取防火墙统计 + """ + data = {} + data['port'] = public.M('firewall_new').count() + data['ip'] = public.M('firewall_ip').count() + data['trans'] = public.M('firewall_trans').count() + data['country'] = public.M('firewall_country').count() + + isPing = True + try: + file = '/etc/sysctl.conf' + conf = public.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)\n" + tmp = re.search(rep, conf).groups(0)[0] + if tmp == '1': isPing = False + except: + isPing = True + + data['ping'] = isPing + data['status'] = self.get_firewall_status() + # return data + return public.return_message(0, 0, data) + + # 服务状态获取 + # def get_firewall_status(self): + # if self.__isUfw: + # res = public.ExecShell("systemctl is-active ufw")[0] + # if res == "active": + # return True + # + # res = public.ExecShell("systemctl list-units | grep ufw")[0] + # if res.find('active running') != -1: + # return True + # + # res = public.ExecShell('/lib/ufw/ufw-init status')[0] + # if res.find("Firewall is not running") != -1: + # return False + # + # res = public.ExecShell('ufw status verbose')[0] + # if res.find('inactive') != -1: + # return False + # + # return True + # + # + # if self.__isFirewalld: + # res = public.ExecShell("ps -ef|grep firewalld|grep -v grep")[0] + # if res: + # return True + # + # res = public.ExecShell("systemctl is-active firewalld")[0] + # if res == "active": + # return True + # + # res = public.ExecShell("systemctl list-units | grep firewalld")[0] + # if res.find('active running') != -1: + # return True + # return False + # + # else: + # res = public.ExecShell("/etc/init.d/iptables status")[0] + # if res.find('not running') != -1: + # return False + # + # res = public.ExecShell("systemctl is-active iptables")[0] + # if res == "active": + # return True + # + # return True + def get_firewall_status(self): + if self.__isUfw: + res = public.ExecShell("systemctl is-active ufw")[0] + if res == "active": + # return True + return public.return_message(0, 0, True) + res = public.ExecShell("systemctl list-units | grep ufw")[0] + if res.find('active running') != -1: + # return True + return public.return_message(0, 0, True) + res = public.ExecShell('/lib/ufw/ufw-init status')[0] + if res.find("Firewall is not running") != -1: + # return False + return public.return_message(-1, 0, True) + res = public.ExecShell('ufw status verbose')[0] + if res.find('inactive') != -1: + # return False + return public.return_message(-1, 0, True) + # return True + return public.return_message(0, 0, True) + + if self.__isFirewalld: + res = public.ExecShell("ps -ef|grep firewalld|grep -v grep")[0] + if res: + # return True + return public.return_message(0, 0, True) + res = public.ExecShell("systemctl is-active firewalld")[0] + if res == "active": + # return True + return public.return_message(0, 0, True) + res = public.ExecShell("systemctl list-units | grep firewalld")[0] + if res.find('active running') != -1: + # return True + return public.return_message(0, 0, True) + # return False + return public.return_message(-1, 0, True) + else: + res = public.ExecShell("/etc/init.d/iptables status")[0] + if res.find('not running') != -1: + # return False + return public.return_message(-1, 0, True) + res = public.ExecShell("systemctl is-active iptables")[0] + if res == "active": + # return True + return public.return_message(0, 0, True) + # return True + return public.return_message(0, 0, True) + + def SetPing(self, get): + + if get.status == '1': + get.status = '0' + else: + get.status = '1' + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + rep = r"net\.ipv4\.icmp_echo.*" + conf = re.sub(rep, 'net.ipv4.icmp_echo_ignore_all=' + get.status + "\n", conf) + else: + conf += "\nnet.ipv4.icmp_echo_ignore_all=" + get.status + "\n" + + if public.writeFile(filename, conf): + public.ExecShell('sysctl -p') + return public.returnMsg(True, 'SUCCESS') + else: + return public.returnMsg( + False, + # '错误:设置失败,sysctl.conf不可写!
                                    ' + # '1、如果安装了[宝塔系统加固],请先关闭
                                    ' + # '2、如果安装了云锁,请关闭[系统加固]功能
                                    ' + # '3、如果安装了安全狗,请关闭[系统防护]功能
                                    ' + # '4、如果使用了其它安全软件,请先卸载
                                    ' + 'Error: Setting failed, sysctl.conf is not writable!
                                    ' + '1. If [System Hardening] is installed, please close it first
                                    ' + '2. If Cloud Lock is installed, please turn off the [System Hardening] function
                                    ' + '3. If a security dog is installed, please turn off the [System Protection] function
                                    ' + '4. If you use other security software, please uninstall it first
                                    ' + ) + + + + # 服务状态控制 dict_obj. + def firewall_admin(self, get: dict_obj): + + try: + get.validate([ + Param('status').Require().String('in', ['start', 'stop']).Xss(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + order = ['reload', 'restart', 'stop', 'start'] + if not get.status in order: + return public.returnMsg(False, 'unknown control command!') + names = ["reload", "restart", "stop", "start"] + result = dict(zip(order, names)) + if self.__isUfw: + if get.status == "stop": + public.ExecShell('/usr/sbin/ufw disable') + elif get.status == "start": + public.ExecShell('echo y|/usr/sbin/ufw enable') + elif get.status == "reload": + public.ExecShell('/usr/sbin/ufw reload') + elif get.status == "restart": + public.ExecShell('/usr/sbin/ufw disable && /usr/sbin/ufw enable') + + # ufw防火墙启动时重载一次sysctl + # 解决deian系统启动ufw后禁ping失1效 by wzz/2023-06-14 + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + public.ExecShell("sysctl -p") + public.WriteLog("system firewall", "firewall {}".format(result[get.status])) + # return public.returnMsg(True, 'firewall has {}'.format(result[get.status])) + return public.return_message(0, 0, "firewall has {}".format(result[get.status])) + if self.__isFirewalld: + public.ExecShell('systemctl {} firewalld'.format(get.status)) + public.WriteLog("system firewall", "firewall {}".format(result[get.status])) + # return public.returnMsg(True, 'firewall has {}'.format(result[get.status])) + return public.return_message(0, 0, "firewall has {}".format(result[get.status])) + else: + public.ExecShell('service iptables {}'.format(get.status)) + public.WriteLog("system firewall", "firewall {}".format(result[get.status])) + # return public.returnMsg(True, 'firewall has {}'.format(result[get.status])) + return public.return_message(0, 0, "fire/wall has {}".format(result[get.status])) + + # 重载防火墙配置 + def FirewallReload(self): + if self.__isUfw: + public.ExecShell('/usr/sbin/ufw reload') # 兼容安装了多个防火墙的情况 hezhihong # return + # 解决deian系统启动ufw后禁ping失1效 by wzz/2023-06-14 + filename = '/etc/sysctl.conf' + conf = public.readFile(filename) + if conf.find('net.ipv4.icmp_echo') != -1: + public.ExecShell("sysctl -p") + if self.__isFirewalld: + public.ExecShell('firewall-cmd --reload') + else: + public.ExecShell('/etc/init.d/iptables save') + public.ExecShell('/etc/init.d/iptables restart') + + #端口扫描 + def CheckPort(self, port, protocol): + import socket + localIP = '127.0.0.1' + temp = {} + temp['port'] = port + temp['local'] = True + try: + if 'tcp' in protocol.lower(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(0.01) + s.connect((localIP, port)) + s.close() + if 'udp' in protocol.lower(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0.01) + s.sendto(b'', (localIP, port)) + s.close() + except: + temp['local'] = False + + result = 0 + if temp['local']: result += 2 + return result + + # 查询入栈规则 + def get_rules_list(self, args): + # 分页校验参数 + try: + args.validate([ + Param('limit').Integer(), + Param('p').Integer(), + Param('query').String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if self.__isFirewalld: + self.__firewall_obj = firewalld() + self.GetList() + else: + self.get_ufw_list() + try: + p = 1 + limit = 15 + if 'p' in args: p = args.p + if 'limit' in args: limit = args.limit + + where = '1=1' + sql = public.M('firewall_new') + + if hasattr(args, 'query'): + where = " ports like '%{search}%' or brief like '%{search}%' or address like '%{search}%'".format( + search=args.query) + + count = sql.where(where, ()).count() + data = public.get_page(count, int(p), int(limit)) + data['data'] = sql.where(where, ()).limit('{},{}'.format( + data['shift'], data['row'])).order('addtime desc').select() + res_data = data['data'] + for i in range(len(res_data)): + if not 'ports' in res_data[i]: + res_data[i]['status'] = -1 + continue + d = res_data[i] + _port = d['ports'] + _protocol = d['protocol'] + if _port.find(':') != -1 or _port.find('.') != -1 or _port.find( + '-') != -1: + d['status'] = -1 + else: + d['status'] = self.CheckPort(int(_port), _protocol) + for i in res_data: + if 'brief' in i: + i['brief'] = public.xsssec(i['brief']) + + return public.return_message(0, 0, res_data) + # return res_data + except Exception as e: + # return [] + return public.return_message(-1, 0, []) + + def check_firewall_rule(self, args): + """ + @检测防火墙规则 + """ + port = args['port'] + find = public.M('firewall_new').where('ports=?', (str(port), )).find() + if find: + return True + return False + + # 端口检查 + def check_port(self, port_list): + rep1 = r"^\d{1,5}(:\d{1,5})?$" + # rep1 = r'^[0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5]$' + for port in port_list: + if port.find('-') != -1: + ports = port.split('-') + if not re.search(rep1, ports[0]): + return public.returnMsg(False, 'PORT_CHECK_RANGE') + if not re.search(rep1, ports[1]): + return public.returnMsg(False, 'PORT_CHECK_RANGE') + elif port.find(':') != -1: + ports = port.split(':') + if not re.search(rep1, ports[0]): + return public.returnMsg(False, 'PORT_CHECK_RANGE') + if not re.search(rep1, ports[1]): + return public.returnMsg(False, 'PORT_CHECK_RANGE') + else: + if not re.search(rep1, port): + return public.returnMsg(False, 'PORT_CHECK_RANGE') + + def parse_ip_interval(self, ip_str): + """解析区间IP + + author: lx + date: 2022/10/25 + + Returns: + list : IP列表 + """ + ips = [] + try: + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + searchor = re.compile(rep2) + if ip_str.find("-") != -1: + pre_ip, end_ip = ip_str.split("-") + if searchor.search(pre_ip) and searchor.search(end_ip): + ips.append(pre_ip) + pinx = pre_ip.rfind(".") + 1 + einx = end_ip.rfind(".") + 1 + pre = pre_ip[0:pinx] + end = end_ip[0:einx] + if pre == end: + start_num = int(pre_ip[pinx:]) + end_num = min(int(end_ip[einx:]), 255) + for i in range(start_num + 1, end_num): + new_ip = pre + str(i) + if searchor.search(new_ip): + ips.append(new_ip) + end_ip = end + str(end_num) + ips.append(end_ip) + except: + pass + return ips + + # 判断是否为ipv6网段 + @staticmethod + def is_ipv6_network_segment_or_ipv6_address(ip_datas: str) -> bool: + from ipaddress import IPv6Network, IPv6Address + try: + tmp_data = IPv6Network(ip_datas) + except: + try: + tmp_data = IPv6Address(ip_datas) + except: + return False + return True + + # 添加入栈规则 + def create_rules2(self, get): + ''' + get 里面 有 protocol port type address brief 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types == [accept、drop] # 放行和禁止 + address 地址,允许放行的ip,如果全部就是:0.0.0.0/0;另外可以包含“,"或者"-" + 表示区间IP + brief 备注说明 + ''' + protocol = get.protocol + ports = get.ports.strip() + types = get.types + address = get.source.strip() + port_list = ports.split(',') + result = self.check_port(port_list) # 检测端口 + if result: return result + + allow_ips = [] + if address: + sources = [ + sip.strip() for sip in address.split(",") if sip.strip() + ] + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + _ips = [] + for source_ip in sources: + if source_ip.find("-") != -1: + _ips += self.parse_ip_interval(source_ip) + else: + _ips.append(source_ip) + + for source_ip in _ips: + if not re.search(rep2, source_ip) and not self.is_ipv6_network_segment_or_ipv6_address(source_ip): + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + allow_ips.append(source_ip) + if not allow_ips: + allow_ips.append("") + for source_ip in allow_ips: + if self.__isUfw: + for port in port_list: + if port.find('-') != -1: + port = port.replace('-', ':') + self.add_ufw_rule(source_ip, protocol, port, types) + else: + if self.__isFirewalld: + for port in port_list: + if port.find(':') != -1: + port = port.replace(':', '-') + self.add_firewall_rule(source_ip, protocol, port, + types) + else: + for port in port_list: + self.add_iptables_rule(source_ip, protocol, port, types) + + # 添加入栈规则 + def create_rules(self, get): + ''' + get 里面 有 protocol ports types address brief 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types == [accept、drop] # 放行和禁止 + address 地址,允许放行的ip,如果全部就是:0.0.0.0/0;另外可以包含“,"或者"-" + 表示区间IP + brief 备注说明 + ''' + + # 校验参数 + try: + get.validate([ + Param('ports').Require().Number(">=", 1).Number("<=", 65535), + Param('address').Require().Ip(), + Param('types').Require().String('in', ['accept', 'drop']), + Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + protocol = get.protocol + ports = get.ports.strip() + types = get.types + address = get.source.strip() + brief = get.brief.strip() + port_list = ports.split(',') + is_add = 2 if 'add' not in get else get.add + domain_total = '' if ("domain" not in get or not get.domain) else get.domain.strip() + domain = '' if ("domain" not in get or not get.domain) else get.domain.strip() + '|' + address + result = self.check_port(port_list) # 检测端口 + if result: + # return result + return public.return_message(0, 0, result) + + allow_ips = [] + if address: + sources = [sip.strip() for sip in address.split(",") if sip.strip()] + rep2 = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + _ips = [] + for source_ip in sources: + if source_ip.find("-") != -1: + _ips += self.parse_ip_interval(source_ip) + else: + _ips.append(source_ip) + + for source_ip in _ips: + if not re.search(rep2, source_ip) and not self.is_ipv6_network_segment_or_ipv6_address(source_ip): + # return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + return public.return_message(-1, 0, "FIREWALL_IP_FORMAT") + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=?', + (ports, source_ip, protocol, types,) + ).count() + if query_result > 0: + continue + allow_ips.append(source_ip) + if not allow_ips: + allow_ips.append("") + # 忽略的列表 + ignore_list = [] + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + for source_ip in allow_ips: + for port in port_list: + if is_add == 1: continue + # 检测端口是否已经添加过 + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=?', + (port, source_ip, protocol, types,) + ).find() + firewall_rules = self.get_sys_firewall_rules() + if query_result: + new_query_result = {"ports": query_result["ports"], "address": query_result["address"], + "protocol": query_result["protocol"], "types": query_result["types"]} + for firewall_rule in firewall_rules: + if new_query_result == firewall_rule: + ignore_list.append(port) + break + + self._add_firewall_rules(source_ip, protocol, port, types) + + if not query_result: + self._add_sid = public.M('firewall_new').add( + 'ports,brief,protocol,address,types,addtime,domain,sid', + (port, public.xsssec(brief), protocol, source_ip, types, addtime, domain, 0) + ) + + if domain: + domain_sid = public.M('firewall_domain').add( + 'types,domain,port,address,brief,addtime,sid,protocol,domain_total', + (types, domain, ports, address, public.xsssec(brief), addtime, self._add_sid, protocol, + domain_total) + ) + public.M('firewall_new').where("id=?", (self._add_sid,)).save('sid', domain_sid) + if len(allow_ips) > 0: + self.FirewallReload() + if not get.source.strip(): + log_ip = "All IPs" + else: + log_ip = get.source.strip() + strategy = '' + if types == 'accept': + strategy = "accept" + elif types == 'drop': + strategy = "drop" + public.WriteLog("system firewall", "Add port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(protocol, ports, strategy, log_ip)) + # 如果有忽略的端口,返回忽略的端口 + if ignore_list: + # return public.returnMsg(True, 'Added successfully, {} The same rule exists for the port and has been skipped'.format(', '.join(ignore_list))) + return public.return_message(0, 0, 'Added successfully, {} The same rule exists for the port and has been skipped'.format(', '.join(ignore_list))) + # return public.returnMsg(True, 'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + + # 删除入栈规则 + def remove_rules(self, get): + ''' + get 里面有 id protocol port type address 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types == [accept、drop] # 放行和禁止 + address 地址,允许放行的ip + ''' + # 检测是否开启防火墙 hezhihong + if not self.get_firewall_status(): + # return public.returnMsg(False, 'Please enable the firewall before proceeding.') + return public.return_message(-1, 0, 'Please enable the firewall before proceeding.') + + # { + # "id": 13, + # "protocol": "tcp", + # "ports": "3309", + # "address": "192.168.69.148", + # "types": "accept", + # "source": "192.168.69.148" + # } + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + Param('ports').Require().Number(">=", 1).Number("<=", 65535), + Param('address').Require().Ip(), + Param('source').Require().Ip(), + Param('types').Require().String('in', ['accept', 'drop']), + Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + id = get.id + address = get.address + protocol = get.protocol + ports = get.ports + types = get.types + self._del_firewall_rules(address, protocol, ports, types) + public.M('firewall_new').where("id=?", (id, )).delete() + self.FirewallReload() + if not get.address: + log_ip = "All IPs" + else: + log_ip = get.address + if types == 'accept': + strategy = "accept" + elif types == 'drop': + strategy = "drop" + public.WriteLog("system firewall", "Delete port rules: Protocol:{}, Port:{}, Policy:{}, IP:{}".format(get.protocol, get.ports, types, log_ip)) + # return public.returnMsg(True, 'DEL_SUCCESS') + return public.return_message(-1, 0, 'DEL_SUCCESS') + + + # 修改入栈规则 + def modify_rules(self, get, addtime=None): + ''' + get 里面有 id protocol port type address 五个参数 + protocol == ['tcp','udp'] + port = 端口 + types==['reject','accept'] # 放行和禁止 + address 地址,允许放行的ip,如果全部就是:0.0.0.0/0 + ''' + # 检测是否开启防火墙 hezhihong + if not self.get_firewall_status(): + return public.return_message(-1, 0, 'Please enable the firewall before proceeding.') + + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + Param('ports').Require().Number(">=", 1).Number("<=", 65535), + Param('address').Require().Ip(), + Param('source').Require().Ip(), + Param('types').Require().String('in', ['accept', 'drop']), + Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + protocol = get.protocol + ports = get.ports.strip() + types = get.types + address = get.source.strip() + brief = get.brief.strip() + domain = '' if 'domain' not in get else get.domain + domain_total = domain.split('|')[0] + sid = 0 if 'sid' not in get else get.sid + if address: + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + if not re.search(rep, get.source) and self.is_ipv6_network_segment_or_ipv6_address(get.source): + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + data = public.M('firewall_new').where('id=?', (id, )).field( + 'id,address,protocol,ports,types,brief,addtime,domain' + ).find() + if data: + _address = data.get("address", "") + _protocol = data.get("protocol", "") + _port = data.get("ports", "") + _type = data.get("types", "") + else: + _address = _protocol = _port = _type = "" + self._modify_firewall_rules(_address, _protocol, _port, _type, address, protocol, ports, types) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + public.M('firewall_new').where('id=?', id).update({ + 'address': address, + 'protocol': protocol, + 'ports': ports, + 'types': types, + 'brief': brief, + 'addtime': addtime, + 'sid': sid, + 'domain': domain + } + ) + if domain: + public.M('firewall_domain').where("id=?", (sid,)).save( + 'sid,types,brief,protocol,domain_total', + (id, types, brief, protocol, domain_total) + ) + + with contextlib.suppress(Exception): + if int(ports) == 22: self.delete_service() + self.FirewallReload() + if not address: + log_ip = "All IPs" + else: + log_ip = address + if get.types == 'accept': + strategy = "accept" + elif get.types == 'drop': + strategy = "drop" + public.WriteLog("system firewall", "Modify port rules: Protocol:{}, Port:{}, Strategy:{}, IP:{}".format(get.protocol, get.ports.strip(), get.types, log_ip)) + # return public.returnMsg(True, '操作成功') + return public.return_message(-1, 0, 'operate successfully') + + # firewall端口规则添加 + def add_firewall_rule(self, address, protocol, ports, types): + if not address: + if protocol.find('/') != -1: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + + ports + '/tcp') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + + ports + '/udp') + else: + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule="rule family=ipv4 port protocol="tcp" port="%s" drop"' + % ports + ) + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule="rule family=ipv4 port protocol="udp" port="%s" drop"' + % ports + ) + else: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + + ports + '/' + protocol + '') + else: + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule="rule family=ipv4 port protocol="%s" port="%s" drop"' + % (protocol, ports)) + return True + if self.is_ipv6_network_segment_or_ipv6_address(address): + if protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv6 source address="%s" port protocol="tcp" port="%s" %s"' + % (address, ports, types)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv6 source address="%s" port protocol="udp" port="%s" %s"' + % (address, ports, types)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv6 source address="%s" port protocol="%s" port="%s" %s"' + % (address, protocol, ports, types)) + else: + if protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 source address="%s" port protocol="tcp" port="%s" %s"' + % (address, ports, types)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 source address="%s" port protocol="udp" port="%s" %s"' + % (address, ports, types)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 source address="%s" port protocol="%s" port="%s" %s"' + % (address, protocol, ports, types)) + return True + + # firewall端口规则删除 + def del_firewall_rule(self, address, protocol, ports, types): + if not address: + if protocol.find('/') != -1: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + + ports + '/tcp') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + + ports + '/udp') + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family=ipv4 port protocol="tcp" port="%s" drop"' + % ports + ) + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family=ipv4 port protocol="udp" port="%s" drop"' + % ports + ) + else: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + + ports + '/' + protocol + '') + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family=ipv4 port protocol="%s" port="%s" drop"' + % (protocol, ports)) + self.update_panel_data(ports) + return True + if self.is_ipv6_network_segment_or_ipv6_address(address): + if protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv6" source address="%s" port protocol="tcp" port="%s" %s"' + % (address, ports, types)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv6" source address="%s" port protocol="udp" port="%s" %s"' + % (address, ports, types)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv6" source address="%s" port protocol="%s" port="%s" %s"' + % (address, protocol, ports, types)) + else: + if protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv4" source address="%s" port protocol="tcp" port="%s" %s"' + % (address, ports, types)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv4" source address="%s" port protocol="udp" port="%s" %s"' + % (address, ports, types)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv4" source address="%s" port protocol="%s" port="%s" %s"' + % (address, protocol, ports, types)) + return True + + # firewall端口规则编辑 + def edit_firewall_rule(self, _address, _protocol, _port, _type, address, + protocol, ports, types): + if not _address: + if _protocol.find('/') != -1: + if _type == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + + _port + '/tcp') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + + _port + '/udp') + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family=ipv4 port protocol="tcp" port="%s" drop"' + % ports + ) + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family=ipv4 port protocol="udp" port="%s" drop"' + % ports + ) + else: + if _type == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-port=' + + _port + '/' + _protocol + '') + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family=ipv4 port protocol="%s" port="%s" drop"' + % (protocol, ports)) + else: + if self.is_ipv6_network_segment_or_ipv6_address(address): + if _protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv6" source address="%s" port protocol="tcp" port="%s" %s"' + % (_address, _port, _type)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv6" source address="%s" port protocol="udp" port="%s" %s"' + % (_address, _port, _type)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv6" source address="%s" port protocol="%s" port="%s" %s"' + % (_address, _protocol, _port, _type)) + else: + if _protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv4" source address="%s" port protocol="tcp" port="%s" %s"' + % (_address, _port, _type)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv4" source address="%s" port protocol="udp" port="%s" %s"' + % (_address, _port, _type)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--remove-rich-rule="rule family="ipv4" source address="%s" port protocol="%s" port="%s" %s"' + % (_address, _protocol, _port, _type)) + if not address: + if protocol.find('/') != -1: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + + ports + '/tcp') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + + ports + '/udp') + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 port protocol="tcp" port="%s" drop"' + % ports + ) + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 port protocol="udp" port="%s" drop"' + % ports + ) + else: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-port=' + + ports + '/' + protocol + '') + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 port protocol="%s" port="%s" drop"' + % (protocol, ports)) + else: + if self.is_ipv6_network_segment_or_ipv6_address(address): + if protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv6 source address="%s" port protocol="tcp" port="%s" %s"' + % (address, ports, types)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv6 source address="%s" port protocol="udp" port="%s" %s"' + % (address, ports, types)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv6 source address="%s" port protocol="%s" port="%s" %s"' + % (address, protocol, ports, types)) + else: + if protocol.find('/') != -1: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 source address="%s" port protocol="tcp" port="%s" %s"' + % (address, ports, types)) + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 source address="%s" port protocol="udp" port="%s" %s"' + % (address, ports, types)) + else: + public.ExecShell( + 'firewall-cmd --permanent ' + '--add-rich-rule="rule family=ipv4 source address="%s" port protocol="%s" port="%s" %s"' + % (address, protocol, ports, types)) + return True + + # ufw 端口规则添加 + def add_ufw_rule(self, address, protocol, ports, types): + rule = "allow" if types == "accept" else "deny" + if address == "": + if protocol.find('/') != -1: + # public.ExecShell('ufw ' + rule + ' ' + ports + '/tcp') + # public.ExecShell('ufw ' + rule + ' ' + ports + '/udp') + public.ExecShell('ufw ' + rule + ' ' + ports) + else: + public.ExecShell('ufw ' + rule + ' ' + ports + '/' + protocol + '') + else: + if protocol.find('/') != -1: + # public.ExecShell('ufw ' + rule + ' proto tcp from ' + address + ' to any port ' + ports + '') + # public.ExecShell('ufw ' + rule + ' proto udp from ' + address + ' to any port ' + ports + '') + public.ExecShell('ufw ' + rule + ' from ' + address + ' to any port ' + ports + '') + else: + public.ExecShell( + 'ufw ' + rule + ' proto ' + protocol + ' from ' + address + ' to any port ' + ports + '') + + # ufw 端口规则删除 + def del_ufw_rule(self, address, protocol, ports, types): + rule = "allow" if types == "accept" else "deny" + if address == "": + if protocol.find('/') != -1: + public.ExecShell('ufw delete ' + rule + ' ' + ports + '/tcp') + public.ExecShell('ufw delete ' + rule + ' ' + ports + '/udp') + public.ExecShell('ufw delete ' + rule + ' ' + ports) + else: + public.ExecShell('ufw delete ' + rule + ' ' + ports + '/' + protocol + '') + else: + if protocol.find('/') != -1: + public.ExecShell('ufw delete ' + rule + ' proto tcp from ' + address + ' to any port ' + ports + '') + public.ExecShell('ufw delete ' + rule + ' proto udp from ' + address + ' to any port ' + ports + '') + public.ExecShell('ufw delete ' + rule + ' from ' + address + ' to any port ' + ports) + else: + public.ExecShell( + 'ufw delete ' + rule + ' proto ' + protocol + ' from ' + address + ' to any port ' + ports + '' + ) + self.update_panel_data(ports) + + # ufw 端口规则修改 + def edit_ufw_rule(self, _address, _protocol, _port, _type, address, + protocol, ports, types): + _rule = "allow" if _type == "accept" else "deny" + rules = "allow" if types == "accept" else "deny" + if _address == "": + if _protocol.find('/') != -1: + public.ExecShell('ufw delete ' + _rule + ' ' + _port + '/tcp') + public.ExecShell('ufw delete ' + _rule + ' ' + _port + '/udp') + public.ExecShell('ufw delete ' + _rule + ' ' + _port) + else: + public.ExecShell('ufw delete ' + _rule + ' ' + _port + '/' + _protocol + '') + else: + if _protocol.find('/') != -1: + public.ExecShell('ufw delete ' + _rule + ' proto tcp from ' + _address + ' to any port ' + _port + '') + public.ExecShell('ufw delete ' + _rule + ' proto udp from ' + _address + ' to any port ' + _port + '') + public.ExecShell('ufw delete ' + _rule + ' from ' + _address + ' to any port ' + _port) + else: + public.ExecShell( + 'ufw delete ' + _rule + ' proto ' + _protocol + ' from ' + _address + ' to any port ' + _port + '' + ) + if address == "": + if protocol.find('/') != -1: + # public.ExecShell('ufw ' + rules + ' ' + ports + '/tcp') + # public.ExecShell('ufw ' + rules + ' ' + ports + '/udp') + public.ExecShell('ufw ' + rules + ' ' + ports) + else: + public.ExecShell('ufw ' + rules + ' ' + ports + '/' + protocol + '') + else: + if protocol.find('/') != -1: + # public.ExecShell('ufw ' + rules + ' proto tcp from ' + address + ' to any port ' + ports + '') + # public.ExecShell('ufw ' + rules + ' proto udp from ' + address + ' to any port ' + ports + '') + public.ExecShell('ufw ' + rules + ' from ' + address + ' to any port ' + ports) + else: + public.ExecShell( + 'ufw ' + rules + ' proto ' + protocol + ' from ' + address + ' to any port ' + ports + '' + ) + + # iptables端口规则添加 + def add_iptables_rule(self, address, protocol, ports, types): + rule = "ACCEPT" if types == "accept" else "DROP" + if not address: + if protocol.find('/') != -1: + public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + + ports + ' -j ' + rule + '') + public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m udp --dport ' + + ports + ' -j ' + rule + '') + else: + public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m ' + + protocol + ' --dport ' + ports + ' -j ' + rule + '') + else: + if protocol.find('/') != -1: + public.ExecShell('iptables -I INPUT -s ' + address + + ' -p tcp --dport ' + ports + ' -j ' + rule + + '') + public.ExecShell('iptables -I INPUT -s ' + address + + ' -p udp --dport ' + ports + ' -j ' + rule + + '') + else: + public.ExecShell('iptables -I INPUT -s ' + address + ' -p ' + + protocol + ' --dport ' + ports + ' -j ' + + rule + '') + return True + + # iptables端口规则删除 + def del_iptables_rule(self, address, protocol, ports, types): + rule = "ACCEPT" if types == "accept" else "DROP" + if not address: + if protocol.find('/') != -1: + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m tcp --dport ' + + ports + ' -j ' + rule + '') + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m udp --dport ' + + ports + ' -j ' + rule + '') + else: + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m ' + + protocol + ' --dport ' + ports + ' -j ' + rule + '') + else: + if protocol.find('/') != -1: + public.ExecShell('iptables -D INPUT -s ' + address + + ' -p tcp --dport ' + ports + ' -j ' + rule + + '') + public.ExecShell('iptables -D INPUT -s ' + address + + ' -p udp --dport ' + ports + ' -j ' + rule + + '') + else: + public.ExecShell('iptables -D INPUT -s ' + address + ' -p ' + + protocol + ' --dport ' + ports + ' -j ' + + rule + '') + return True + + # iptables端口规则编辑 + def edit_iptables_rule(self, _address, _protocol, _port, _type, address, + protocol, ports, types): + rule1 = "ACCEPT" if _type == "accept" else "DROP" + rule2 = "ACCEPT" if types == "accept" else "DROP" + if not _address: + if _protocol.find('/') != -1: + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m tcp --dport ' + + _port + ' -j ' + rule1 + '') + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m udp --dport ' + + _port + ' -j ' + rule1 + '') + else: + public.ExecShell( + 'iptables -D INPUT -p tcp -m state --state NEW -m ' + + _protocol + ' --dport ' + _port + ' -j ' + rule1 + '') + else: + if _protocol.find('/') != -1: + public.ExecShell('iptables -D INPUT -s ' + _address + + ' -p tcp --dport ' + _port + ' -j ' + rule1 + + '') + public.ExecShell('iptables -D INPUT -s ' + _address + + ' -p udp --dport ' + _port + ' -j ' + rule1 + + '') + else: + public.ExecShell('iptables -D INPUT -s ' + _address + ' -p ' + + _protocol + ' --dport ' + _port + ' -j ' + + rule1 + '') + if not address: + if protocol.find('/') != -1: + public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport ' + + ports + ' -j ' + rule2 + '') + public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m udp --dport ' + + ports + ' -j ' + rule2 + '') + else: + public.ExecShell( + 'iptables -I INPUT -p tcp -m state --state NEW -m ' + + protocol + ' --dport ' + ports + ' -j ' + rule2 + '') + else: + if protocol.find('/') != -1: + public.ExecShell('iptables -I INPUT -s ' + address + + ' -p tcp --dport ' + ports + ' -j ' + rule2 + + '') + public.ExecShell('iptables -I INPUT -s ' + address + + ' -p udp --dport ' + ports + ' -j ' + rule2 + + '') + else: + public.ExecShell('iptables -I INPUT -s ' + address + ' -p ' + + protocol + ' --dport ' + ports + ' -j ' + + rule2 + '') + return True + + # 修改面板数据 + def update_panel_data(self, ports): + res = public.M('firewall').where("port=?", (ports, )).delete() + + # 查询IP规则 + def get_ip_rules_list(self, args): + # 分页校验参数 + try: + args.validate([ + Param('limit').Integer(), + Param('p').Integer(), + Param('query').String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + p = 1 + limit = 15 + if 'p' in args: p = args.p + if 'limit' in args: limit = args.limit + + where = '1=1' + sql = public.M('firewall_ip') + + if hasattr(args, 'query'): + where = " address like '%{search}%' or brief like '%{search}%' ".format( + search=args.query) + + count = sql.where(where, ()).count() + data = public.get_page(count, int(p), int(limit)) + data['data'] = sql.where(where, ()).limit('{},{}'.format( + data['shift'], data['row'])).order('addtime desc').select() + + data['data'] = public.return_area(data['data'], 'address') + # return data + return public.return_message(0, 0, data) + + def check_a_ip(self, address): + """ + @name 检测A记录是否为域名 + @author hezhihong + """ + if address: + if public.is_ipv4(address) or public.is_ipv6(address): + return address + if address[-1] == '.': + address = address[:-1] + if public.is_domain(address): return self.get_a_ip(address) + return address + + def get_a_ip(self, hostname): + ''' + @name 检测主机名是否有A记录 + @author hezhihong + :param hostname: + :return: + ''' + if not self.install_dnspython(): + return public.returnMsg(False, '请先安装dnspython模块') + import dns.resolver + # 尝试3次 + a_ip = [] + for i in range(3): + try: + resolver = dns.resolver.Resolver() + resolver.timeout = 1 + try: + result = resolver.query(hostname, 'A') + except: + result = resolver.resolve(hostname, 'A') + for i in result.response.answer: + for j in i.items: + try: + A_ip = str(j).strip() + if A_ip[-1] == '.': + A_ip = A_ip[:-1] + except: + pass + + if A_ip not in a_ip: + a_ip.append(A_ip) + + except: + pass + + # 去除域名 + if len(a_ip) > 1: + for i2 in a_ip: + if public.is_ipv4(i2) or public.is_ipv6(i2): + continue + if public.is_domain(i2): + a_ip.remove(i2) + return a_ip + + def install_dnspython(self): + """ + @name 安装dnspython模块 + @author hezhihong + """ + # 检测dns解析 + try: + import dns.resolver + return True + except: + if os.path.exists('/www/server/panel/pyenv'): + public.ExecShell('/www/server/panel/pyenv/bin/pip install dnspython') + else: + public.ExecShell('pip3 install dnspython') + try: + import dns.resolver + return True + except: + return False + + def del_domain_ip(self, args): + """ + @name 删除域名设置 + @author hezhihong + """ + + if 'id' not in args or not args.id or 'sid' not in args: + return public.returnMsg(False, 'Parameter error') + domain_id = int(args.sid) + + # 删除IP规则 + if domain_id > 0: + # 删除域名解析 + public.M('firewall_domain').where('id=?', (str(domain_id),)).delete() + # 删除端口规则 + if 'ports' in args: + self.remove_rules(args) + # 删除IP规则 + else: + self.remove_ip_rules(args) + + # 当没有域名解析时,删除计划任务 + if not public.M('firewall_domain').count(): + pdata = public.M('crontab').where('name=?', '[Do not delete] System firewall domain name resolution detection task').select() + if pdata: + for i in pdata: + args = {"id": i['id']} + import crontab + crontab.crontab().DelCrontab(args) + + return public.returnMsg(True, 'successfully deleted') + + def add_crontab(self): + """ + @name 构造日志切割任务 + @author hezhihong + """ + python_path = '' + try: + python_path = public.ExecShell('which btpython')[0].strip("\n") + except: + try: + python_path = public.ExecShell('which python')[0].strip("\n") + except: + pass + if not python_path: return False + if not public.M('crontab').where('name=?', ('[Do not delete] System firewall domain name resolution detection task',)).count(): + cmd = '{} {}'.format(python_path, '/www/server/panel/script/firewall_domain.py') + args = {"name": "[Do not delete] System firewall domain name resolution detection task", "type": 'minute-n', "where1": '5', "hour": '', + "minute": '', "sName": "", + "sType": 'toShell', "notice": '', "notice_channel": '', "save": '', "save_local": '1', + "backupTo": '', "sBody": cmd, + "urladdress": ''} + import crontab + res = crontab.crontab().AddCrontab(args) + if res and "id" in res.keys(): + return True + return False + return True + + def __check_auth(self): + try: + from pluginAuth import Plugin + plugin_obj = Plugin(False) + plugin_list = plugin_obj.get_plugin_list() + if int(plugin_list['ltd']) > time.time(): + return True + return False + except: + return False + + def set_domain_ip2(self, args): + """ + @name 设置域名规则 + @author hezhihong + """ + pay = self.__check_auth() + if not pay: return public.returnMsg(False, 'Current features are exclusive to the professional version') + if not args.domain: return public.returnMsg(False, 'Please enter domain name') + ports = '' + if 'ports' in args and args.ports: ports = args.ports + ip = args.source + + # 添加计划任务 + self.add_crontab() + # 添加端口规则 + # {"protocol":"tcp","ports":"819","choose":"point","address":"125.93.252.236","types":"accept","brief":"","source":"125.93.252.236"} + args.address = ip + args.source = ip + if ports: + if public.is_ipv6(ip): + return public.returnMsg(False, 'The domain name is resolved to an IPv6 address and port rules are not supported.') + self.create_rules2(args) + # 添加IP规则 + else: + # return 333 + self.create_ip_rules(args) + + return public.returnMsg(True, 'Domain name {} resolution added successfully'.format(args.domain)) + + def set_domain_ip(self, args): + """ + @name 设置域名规则 + @author hezhihong + """ + pay = self.__check_auth() + if not pay: return public.returnMsg(False, 'Current features are exclusive to the professional version') + if not args.domain: return public.returnMsg(False, 'Please enter domain name') + ports = '' + if 'ports' in args and args.ports: ports = args.ports + protocol = '' if 'protocol' not in args else args.protocol + a_ip = self.get_a_ip(args.domain) + # return a_ip + if a_ip and len(a_ip) < 2 and public.is_domain(a_ip[0]): + # return 111 + a_ip = [self.check_a_ip(a_ip[0])] + # return a_ip + if not a_ip: + return public.returnMsg(False, 'The domain name resolution has not been resolved or the resolution has not taken effect. If it has been resolved, please try again after 10 minutes.') + if public.M('firewall_domain').where("domain=? and types=? and port=? and protocol=?", + (args.domain, args.types, ports, protocol,)).count(): + return public.returnMsg(False, 'Domain name {} already exists'.format(args.domain)) + + # 添加计划任务 + self.add_crontab() + # 添加端口规则 + # {"protocol":"tcp","ports":"819","choose":"point","address":"125.93.252.236","types":"accept","brief":"","source":"125.93.252.236"} + for ip in a_ip: + args.address = ip + args.source = ip + if ports: + if public.is_ipv6(ip): + return public.returnMsg(False, 'The domain name is resolved to an IPv6 address and port rules are not supported.') + self.create_rules(args) + # 添加IP规则 + else: + # return 333 + self.create_ip_rules(args) + + return public.returnMsg(True, 'Domain name {} resolution added successfully'.format(args.domain)) + + def modify_domain_ip(self, args): + """ + @name 修改域名规则(当修改为指定域名或从指定域名修改为其他时,需要调用此方法) + @name hezhihong + """ + pay = self.__check_auth() + if not pay: return public.returnMsg(False, 'Current features are exclusive to the professional version') + + # 检测是否开启防火墙 hezhihong + if not self.get_firewall_status(): + return public.returnMsg(False, 'Please enable the firewall before proceeding.') + + modify_args = public.dict_obj() + modify_args.id = args.id + modify_args.types = args.types + modify_args.brief = args.brief + modify_args.address = args.address + modify_args.sid = 0 if 'sid' not in args else args.sid + ports = '' if 'ports' not in args else args.ports + domain = '' if 'domain' not in args else args.domain + if ports: modify_args.ports = ports + choose = '' if 'choose' not in args else args.choose + pdata = {} + if int(args.sid) > 0: + pdata = public.M('firewall_domain').where('id=?', (args.sid,)).find() + # 修改端口规则 + if ports: + modify_args.protocol = args.protocol + # 已经指定域名 + if int(args.sid) > 0: + # 当修改为指定域名时 + if choose == 'domain': + # 当修改为不同域名时 + if domain != pdata['domain']: + self.del_domain_ip(args) + self.set_domain_ip(args) + # 当修改为相同域名时 + else: + pdata['protocol'] = args.protocol + pdata['types'] = args.types + pdata['brief'] = public.xsssec(args.brief) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + pdata['addtime'] = addtime + public.M('firewall_domain').where('id=?', pdata['id']).update(pdata) + self.modify_rules(args) + return public.returnMsg(True, 'Successfully modified') + else: + args.domain = '' + self.del_domain_ip(args) + self.create_rules(args) + return public.returnMsg(True, 'Successfully modified') + # 当未指定域名时 + else: + # 修改为指定域名 + if domain: + self.remove_rules(args) + self.set_domain_ip(args) + return public.returnMsg(True, 'Successfully modified') + # 修改IP规则 + else: + if int(args.sid) > 0: + modify_args.address = pdata['address'] + modify_args.domain = pdata['domain'] + return self.modify_ip_rules(modify_args) + + # IP地址检测 + def check_ip(self, address_list): + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + for address in address_list: + address = address.split('/')[0] + if address.find('-') != -1: + addresses = address.split('-') + if addresses[0] >= addresses[1]: + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + s_ips = addresses[0].split(".") + e_ips = addresses[1].split(".") + head_s_ip = s_ips[0] + "." + s_ips[1] + "." + s_ips[2] + "." + head_e_ip = e_ips[0] + "." + e_ips[1] + "." + e_ips[2] + "." + if head_s_ip != head_e_ip: + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + if not re.search(rep, addresses[0]): + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + if not re.search(rep, addresses[1]): + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + else: + if not re.search(rep, address) and not public.is_ipv6(address): + return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + + # 获取IP范围 + def get_ip(self, address): + result = [] + arrys = address.split("-") + s_ips = arrys[0].split(".") + e_ips = arrys[1].split(".") + head_s_ip = s_ips[0] + "." + s_ips[1] + "." + s_ips[2] + "." + region = int(e_ips[-1]) - int(s_ips[-1]) + for num in range(0, region + 1): + result.append(head_s_ip + str(num + int(s_ips[-1]))) + return result + + def handle_firewall_ip(self, address, types): + ip_list = self.get_ip(address) + if isinstance(ip_list, dict): + return + public.ExecShell( + 'firewall-cmd --permanent --zone=public --new-ipset=' + address + + ' --type=hash:net') + xml_path = "/etc/firewalld/ipsets/%s.xml" % address + tree = ElementTree() + tree.parse(xml_path) + root = tree.getroot() + for ip in ip_list: + entry = Element("entry") + entry.text = ip + root.append(entry) + self.format(root) + tree.write(xml_path, 'utf-8', xml_declaration=True) + # public.ExecShell('firewall-cmd --permanent --zone=public --add-rich-rule=\'rule source ipset="'+ address +'" accept\'') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-rich-rule=\'rule source ipset="' + + address + '" ' + types + '\'') + + def handle_ufw_ip(self, address, types): + ip_list = self.get_ip(address) + if isinstance(ip_list, dict): + return + public.ExecShell('ipset create ' + address + ' hash:net') + for ip in ip_list: + public.ExecShell('ipset add ' + address + ' ' + ip) + public.ExecShell('iptables -I INPUT -m set --match-set ' + address + + ' src -j ' + types.upper()) + + # 检查IP地址是否在范围内 + def ip_in_range(self, ip, ip_range): + import ipaddress + # 2024/1/3 下午 7:59 兼容192.168.0.0/24这种形式 + if ip_range.find('/') != -1: + return ipaddress.ip_address(ip) in ipaddress.ip_network(ip_range) + + ip_range = ip_range.split('-') + if len(ip_range) == 1: # 如果只有一个IP地址 + return ipaddress.ip_address(ip) == ipaddress.ip_address(ip_range[0]) + else: # 如果是一个IP范围 + start_ip, end_ip = ip_range + ip_networks = ipaddress.summarize_address_range(ipaddress.ip_address(start_ip), ipaddress.ip_address(end_ip)) + return any(ipaddress.ip_address(ip) in net for net in ip_networks) + + + # 添加IP规则 + def create_ip_rules(self, get): + # { + # "protocol": "tcp", + # "ports": "3309", + # "choose": "point", + # "address": "192.168.69.148", + # "types": "accept", + # "brief": "", + # "source": "192.168.69.148" + # } + + + # 校验参数 + try: + get.validate([ + Param('ports').Number(">=", 1).Number("<=", 65535), + Param('source').Ip(), + Param('address').Ip(), + Param('types').String('in', ['accept', 'drop']), + Param('protocol').String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + from flask import request + user_ip = request.remote_addr + _address = get.address.strip() + original_types = get.types + brief = get.brief + domain_total = '' if ('domain' not in get or not get.domain) else get.domain.strip() + domain = '' if ('domain' not in get or not get.domain) else get.domain.strip() + '|' + _address + address_list = _address.split(',') + # public.print_log('############ ip接口 {}'.format(address_list)) + result = self.check_ip(address_list) + + if result: + # return result + return public.return_message(0, 0, result) + + # 先处理用户的IP地址 + old_login_ip = public.M('firewall_ip').where("brief=?", ("IP that allows users to log in",)).field('id, address').select() + # public.print_log('############ ip接口user_ip {}'.format(user_ip)) + for ip_range in address_list: + + if self.ip_in_range(user_ip, ip_range): + + if old_login_ip and old_login_ip[0][' address'] != user_ip: + address = old_login_ip[0][' address'] + public.M('firewall_ip').where("address=?", (address,)).delete() + + self.update_panel_data(address) # 删除面板自带防火墙的表数据 + + self.add_rule(user_ip, "accept", "IP that allows users to log in", domain, domain_total) + + break + + # 然后处理其他的IP地址 + for address in address_list: + + self.add_rule(address, original_types, brief, domain, domain_total) + + self.FirewallReload() + + + public.WriteLog("system firewall", "Add IP rules: IP: {}, policy: {}".format(_address, original_types)) + + # return public.returnMsg(True, 'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + + # 添加单个IP规则 + def add_rule(self, address, types, brief, domain, domain_total): + if public.M('firewall_ip').where("address=? and types=? and domain=?", (address, types, domain)).count() > 0: + + return + if self.__isUfw: + # public.print_log('############ ip接口 1') + _rule = "allow" if types == "accept" else "deny" + if address.find('-') != -1: + # public.print_log('############!!! ip接口 2') + self.handle_ufw_ip(address, types) + else: + # public.print_log('############!!! ip接口 3') + is_debian = True if public.get_os_version().lower().find("debian") != -1 else False + if not is_debian: + if _rule == "allow": + if public.is_ipv6(address): + public.ExecShell('ufw ' + _rule + ' from ' + address + ' to any') + else: + public.ExecShell('ufw insert 1 ' + _rule + ' from ' + address + ' to any') + else: + public.ExecShell('ufw ' + _rule + ' from ' + address + ' to any') + else: + public.ExecShell('iptables -I INPUT -s ' + address + ' -j ' + types.upper()) + else: + # public.print_log('############!!! ip接口 4') + if self.__isFirewalld: + # public.print_log('############ ip接口 6') + if address.find('-') != -1: + self.handle_firewall_ip(address, types) + else: + if types == "accept": + public.ExecShell('firewall-cmd --permanent --add-source=' + address + ' --zone=trusted') + else: + if public.is_ipv6(address): + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule=\'rule family=ipv6 source address="' + address + '" ' + types + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule=\'rule family=ipv4 source address="' + address + '" ' + types + '\'') + else: + # public.print_log('############ ip接口 7') + if address.find('-') != -1: + self.handle_ufw_ip(address, types) + else: + public.ExecShell('iptables -I INPUT -s ' + address + ' -j ' + types.upper()) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + self._add_sid = public.M('firewall_ip').add('address,types,brief,addtime,domain,sid', + (address, types, public.xsssec(brief), addtime, domain, 0,)) + # public.print_log('############ ip接口 8{}'.format(self._add_sid)) + if domain: + # public.print_log('############ ip接口9{}'.format(domain)) + domain_sid = public.M('firewall_domain').add( + 'types,domain,port,address,brief,addtime,sid,protocol,domain_total', ( + types, domain, '', address, public.xsssec(brief), addtime, self._add_sid, '', domain_total)) + + public.M('firewall_ip').where("id=?", (self._add_sid,)).save('sid', domain_sid) + + + # 删除All IPs规则 + def remove_all_ip_rules(self, get): + ip_list = public.M('firewall_ip').select() + for ip in ip_list: + id = ip["id"] + address = ip["address"] + types = ip["types"] + if self.__isUfw: + _rule = "allow" if types == "accept" else "deny" + if address.find('-') != -1: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + address + ' src -j ' + types.upper()) + public.ExecShell('ipset destroy ' + address) + else: + is_debian = True if public.get_os_version().lower().find( + "debian") != -1 else False + if not is_debian: + public.ExecShell('ufw delete ' + _rule + ' from ' + address + ' to any') + else: + public.ExecShell("iptables -D INPUT -s " + address + + " -j " + types.upper()) + else: + if self.__isFirewalld: + if address.find('-') != -1: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-rich-rule=\'rule source ipset="' + + address + '" ' + types + '\'') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --delete-ipset=' + + address) + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-source=' + + address + ' --zone=trusted') + if public.is_ipv6(address): + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv6 source address="' + + address + '" ' + types + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv4 source address="' + + address + '" ' + types + '\'') + else: + if address.find('-') != -1: + public.ExecShell( + 'iptables -D INPUT -m set --match-set ' + address + + ' src -j ' + types.upper()) + public.ExecShell('ipset destroy ' + address) + else: + public.ExecShell('iptables -D INPUT -s ' + address + + ' -j ' + types.upper()) + public.M('firewall_ip').where("id=?", (id, )).delete() + self.update_panel_data(address) # 删除面板自带防火墙的表数据 + self.FirewallReload() + return public.returnMsg(True, 'All IP rules have been removed.') + + # 删除IP规则 + def remove_ip_rules(self, get): + + # {"id": 4, "types": "accept", "address": "192.168.168.162", "brief": "123", "addtime": "2024-04-12 17:19:04", + # "sid": 0, "domain": "", "area": {"info": "Intranet"}} + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + # Param('ports').Require(), + Param('address').Require().Ip(), + Param('types').Require().String('in', ['accept', 'drop']), + # Param('area').Require().Dict().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + id = get.id + address = get.address + types = get.types + if self.__isUfw: + _rule = "allow" if types == "accept" else "deny" + if address.find('-') != -1: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + address + ' src -j ' + types.upper()) + public.ExecShell('ipset destroy ' + address) + else: + is_debian = True if public.get_os_version().lower().find( + "debian") != -1 else False + if not is_debian: + public.ExecShell('ufw delete ' + _rule + ' from ' + address + ' to any') + else: + public.ExecShell('ufw delete ' + _rule + ' from ' + address + ' to any') + public.ExecShell("iptables -D INPUT -s " + address + + " -j " + types.upper()) + else: + if self.__isFirewalld: + if address.find('-') != -1: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-rich-rule=\'rule source ipset="' + + address + '" ' + types + '\'') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --delete-ipset=' + + address) + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-source=' + address + + ' --zone=trusted') + if public.is_ipv6(address): + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv6 source address="' + + address + '" ' + types + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv4 source address="' + + address + '" ' + types + '\'') + else: + if address.find('-') != -1: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + address + ' src -j ' + types.upper()) + public.ExecShell('ipset destroy ' + address) + else: + public.ExecShell('iptables -D INPUT -s ' + address + + ' -j ' + types.upper()) + public.M('firewall_ip').where("id=?", (id, )).delete() + self.update_panel_data(address) # 删除面板自带防火墙的表数据 + self.FirewallReload() + strategy= '' + if get.types == 'accept': + strategy = "accept" + elif get.types == 'drop': + strategy = "drop" + public.WriteLog("system firewall", "Delete IP rules: IP:{}, policy:{}".format(get.address, strategy)) + return public.returnMsg(True, 'DEL_SUCCESS') + + # 修改IP规则 + def modify_ip_rules(self, get): + + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + # Param('ports').Require(), + # Param('source').Require().Ip(), + Param('address').Require().Ip(), + Param('types').Require().String('in', ['accept', 'drop']), + # Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + id = get.id + address = get.address.strip() + types = get.types + brief = get.brief + result = self.check_ip([address]) + sid = 0 if 'sid' not in get else get.sid + domain = '' if 'domain' not in get else get.domain + domain_total = domain.split('|')[0] + # return 22 + if result: + # return result + return public.return_message(0, 0, result) + data = public.M('firewall_ip').where( + 'id=?', (id, )).field('id,address,types,brief,addtime').find() + _address = data.get("address", "") + _type = data.get("types", "") + if self.__isUfw: + rule1 = "allow" if _type == "accept" else "deny" + if _address.find('-') != -1: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + _address + ' src -j ' + _type.upper()) + public.ExecShell('ipset destroy ' + _address) + else: + is_debian = True if public.get_os_version().lower().find( + "debian") != -1 else False + if not is_debian: + public.ExecShell('ufw delete ' + rule1 + ' from ' + address + ' to any') + else: + cmd = "iptables -D INPUT -s " + address + " -j " + _type.upper( + ) + public.ExecShell(cmd) + # public.ExecShell('ufw delete ' + rule1 + ' from ' + _address + ' to any') + rule2 = "allow" if types == "accept" else "deny" + if address.find('-') != -1: + self.handle_ufw_ip(address, types) + else: + is_debian = True if public.get_os_version().lower().find( + "debian") != -1 else False + if not is_debian: + if rule2 == "allow": + if public.is_ipv6(address): + public.ExecShell('ufw ' + rule2 + ' from ' + address + ' to any') + else: + public.ExecShell('ufw insert 1 ' + rule2 + ' from ' + address + ' to any') + else: + public.ExecShell('ufw ' + rule2 + ' from ' + address + ' to any') + else: + public.ExecShell('iptables -I INPUT -s ' + address + + ' -j ' + types.upper()) + else: + if self.__isFirewalld: + if _address.find('-') != -1: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-rich-rule=\'rule source ipset="' + + _address + '" ' + _type + '\'') + public.ExecShell( + 'firewall-cmd --permanent --zone=public --delete-ipset=' + + _address) + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-source=' + + _address + ' --zone=trusted') + if public.is_ipv6(address): + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv6 source address="' + + _address + '" ' + _type + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv4 source address="' + + _address + '" ' + _type + '\'') + if address.find('-') != -1: + brief = address + self.handle_firewall_ip(address, types) + else: + if types == "accept": + public.ExecShell( + 'firewall-cmd --permanent --add-source=' + + address + ' --zone=trusted') + else: + if public.is_ipv6(address): + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule=\'rule family=ipv6 source address="' + + address + '" ' + types + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --add-rich-rule=\'rule family=ipv4 source address="' + + address + '" ' + types + '\'') + else: + if _address.find('-') != -1: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + _address + ' src -j ' + types.upper()) + public.ExecShell('ipset destroy ' + _address) + else: + public.ExecShell('iptables -D INPUT -s ' + _address + + ' -j ' + _type.upper()) + if address.find('-') != -1: + self.handle_ufw_ip(address, types) + else: + public.ExecShell('iptables -I INPUT -s ' + address + + ' -j ' + types.upper()) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + public.M('firewall_ip').where('id=?', id).update( + {'address': address, 'types': types, 'brief': brief, 'addtime': addtime, 'sid': sid, 'domain': domain}) + if domain: + public.M('firewall_domain').where('id=?', (sid,)).save('sid,types,brief,domain_total', + (id, types, brief, domain_total)) + self.FirewallReload() + old_strategy = '' + if types == 'accept': + old_strategy = "accept" + elif types == 'drop': + old_strategy = "drop" + if get.types == 'accept': + strategy = "accept" + elif get.types == 'drop': + strategy = "drop" + public.WriteLog("system firewall", "Modify rules, IP:{}, Strategy:{} -> IP:{}, Strategy:{}".format(_address, old_strategy, get.address.strip(), get.types)) + # return public.returnMsg(True, 'Successful operation') + return public.return_message(0, 0, 'Successful operation') + + # 查看端口转发状态 + def trans_status(self): + content = dict() + with open(self._trans_status, 'r') as fr: + content = json.loads(fr.read()) + if content["status"] == "open": + return True + self.open_forward() + content["status"] = "open" + with open(self._trans_status, 'w') as fw: + fw.write(json.dumps(content)) + return True + + # 查询端口转发 + def get_forward_list(self, args): + + # 分页校验参数 + try: + args.validate([ + Param('limit').Integer(), + Param('p').Integer(), + Param('query').String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + result = self.trans_status() + + p = 1 + limit = 15 + if 'p' in args: p = args.p + if 'limit' in args: limit = args.limit + + where = '1=1' + sql = public.M('firewall_trans') + + if hasattr(args, 'query'): + where = " start_port like '%{search}%'".format(search=args.query) + + count = sql.where(where, ()).count() + data = public.get_page(count, int(p), int(limit)) + data['data'] = sql.where(where, ()).limit('{},{}'.format( + data['shift'], data['row'])).order('addtime desc').select() + return public.return_message(0, 0, data) + + # 添加端口转发 + def create_forward(self, get): + # {"protocol":"tcp","s_ports":"1234","d_address":"192.168.198.199","d_ports":"2345"} + # 校验参数 + try: + get.validate([ + # Param('id').Require().Integer().Xss(), + Param('s_ports').Require().Number(">=", 1).Number("<=", 65535), + Param('d_ports').Require().Number(">=", 1).Number("<=", 65535), + Param('d_address').Require().Ip(), + Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + s_port = get.s_ports.strip() # 起始端口 + d_port = get.d_ports.strip() # 目的端口 + d_ip = get.d_address.strip() # 目的ip + protocol = get.protocol + rep1 = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep1, s_port): + # return public.returnMsg(False, 'PORT_CHECK_RANGE') + return public.return_message(-1, 0,'PORT_CHECK_RANGE') + if not re.search(rep1, d_port): + # return public.returnMsg(False, 'PORT_CHECK_RANGE') + return public.return_message(-1, 0, 'PORT_CHECK_RANGE') + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + if d_ip: + if not re.search(rep, get.d_address) and not public.is_ipv6( + get.d_address): + # return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + return public.return_message(-1, 0, 'FIREWALL_IP_FORMAT') + if d_ip in ["127.0.0.1", "localhost"]: + d_ip = "" + if public.M('firewall_trans').where("start_port=?", + (s_port, )).count() > 0: + # return public.returnMsg(False, 'This port already exists, please do not add it again!') + return public.return_message(-1, 0, 'This port already exists, please do not add it again!') + + if self.__isUfw: + content = self.ufw_handle_add(s_port, d_port, d_ip, protocol) + self.save_profile(self._ufw_before, content) + else: + if self.__isFirewalld: + self.firewall_handle_add(s_port, d_port, d_ip, protocol) + else: + self.iptables_handle_add(s_port, d_port, d_ip, protocol) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + public.M('firewall_trans').add( + 'start_port, ended_ip, ended_port, protocol, addtime', + (s_port, d_ip, d_port, protocol, addtime)) + self.FirewallReload() + public.WriteLog("system firewall", "Add port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {}".format(s_port, d_port, d_ip)) + # return public.returnMsg(True, 'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + + + # 删除端口转发 + def remove_forward(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + Param('s_ports').Require().Number(">=", 1).Number("<=", 65535), + Param('d_ports').Require().Number(">=", 1).Number("<=", 65535), + # Param('d_address').Require().Ip(), + Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + s_port = get.s_port + d_port = get.d_port + d_ip = get.d_ip + protocol = get.protocol + if self.__isUfw: + content = self.ufw_handle_del(s_port, d_port, d_ip, protocol) + self.save_profile(self._ufw_before, content) + else: + if self.__isFirewalld: + self.firewall_handle_del(s_port, d_port, d_ip, protocol) + else: + self.iptables_handle_del(s_port, d_port, d_ip, protocol) + public.M('firewall_trans').where("id=?", (id, )).delete() + self.FirewallReload() + public.WriteLog("system firewall", "Delete port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {}".format(s_port, d_port, d_ip)) + # return public.returnMsg(True, 'DEL_SUCCESS') + return public.return_message(0, 0, 'DEL_SUCCESS') + + # 修改端口转发 + def modify_forward(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + Param('s_ports').Require().Number(">=", 1).Number("<=", 65535), + Param('d_ports').Require().Number(">=", 1).Number("<=", 65535), + Param('d_address').Require().Ip(), + Param('protocol').Require().String('in', ['tcp', 'udp']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + id = get.id + s_port = get.s_ports.strip() + d_port = get.d_ports.strip() + d_ip = get.d_address.strip() + pool = get.protocol + rep1 = r"^\d{1,5}(:\d{1,5})?$" + if not re.search(rep1, s_port): + # return public.returnMsg(False, 'PORT_CHECK_RANGE') + return public.return_message(-1, 0, 'PORT_CHECK_RANGE') + if not re.search(rep1, d_port): + # return public.returnMsg(False, 'PORT_CHECK_RANGE') + return public.return_message(-1, 0, 'PORT_CHECK_RANGE') + data = public.M('firewall_trans').where('id=?', (id, )).field( + 'id,start_port,ended_ip,ended_port,protocol,addtime').find() + start_port = data.get("start_port", "") + ended_ip = data.get("ended_ip", "") + ended_port = data.get("ended_port", "") + protocol = data.get("protocol", "") + if d_ip: + rep = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(\/\d{1,2})?$" + if not re.search(rep, get.d_address) and not public.is_ipv6( + get.d_address): + # return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + return public.return_message(-1, 0, 'FIREWALL_IP_FORMAT') + if d_ip in ["127.0.0.1", "localhost"]: + d_ip = "" + if self.__isUfw: + content = self.ufw_handle_update(start_port, ended_ip, ended_port, + protocol, s_port, d_ip, d_port, + pool) + self.save_profile(self._ufw_before, content) + else: + if self.__isFirewalld: + self.firewall_handle_update(start_port, ended_ip, ended_port, + protocol, s_port, d_ip, d_port, + pool) + else: + self.iptables_handle_update(start_port, ended_ip, ended_port, + protocol, s_port, d_ip, d_port, + pool) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + public.M('firewall_trans').where('id=?', id).update( + {'start_port': s_port, "ended_ip": d_ip, "ended_port": d_port, "protocol": pool}) + self.FirewallReload() + public.WriteLog("system firewall", "Modify port forwarding rules: Start port: {}, Destination port: {}, Destination IP: {} -> Start port: {}, Destination port: {}, Destination IP: {}".format(start_port, ended_port, ended_ip, s_port, d_port, d_ip)) + # return public.returnMsg(True, 'Successful operation.') + return public.return_message(0, 0, 'Successful operation') + + # 处理ufw的端口转发添加 + def ufw_handle_add(self, s_port, d_port, d_ip, protocol): + content = self.get_profile(self._ufw_before) + if content.find('*nat') == -1: + content = "*nat\n" + ":PREROUTING ACCEPT [0:0]\n" + ":POSTROUTING ACCEPT [0:0]\n" + "COMMIT\n" + content + array = content.split('\n') + result = array.index(":POSTROUTING ACCEPT [0:0]") + if d_ip == "": + if protocol.find('/') != -1: + _string = "-A PREROUTING -p tcp --dport {1} -j REDIRECT --to-port {2}\n".format( + s_port, d_port) + _string = _string + "-A PREROUTING -p udp --dport {1} -j REDIRECT --to-port {2}".format( + s_port, d_port) + else: + _string = "-A PREROUTING -p {0} --dport {1} -j REDIRECT --to-port {2}".format( + protocol, s_port, d_port) + else: + _string = "-A PREROUTING -p {0} --dport {1} -j DNAT --to-destination {2}:{3}\n".format( + protocol, s_port, d_ip, + d_port) + "-A POSTROUTING -d {0} -j MASQUERADE".format(d_ip) + array.insert(result + 1, _string) + return '\n'.join(array) + + # 处理ufw的端口转发删除 + def ufw_handle_del(self, s_port, d_port, d_ip, protocol): + content = self.get_profile(self._ufw_before) + if d_ip == "": + _string = "-A PREROUTING -p {0} --dport {1} -j REDIRECT --to-port {2}\n".format( + protocol, s_port, d_port) + else: + _string = "-A PREROUTING -p {0} --dport {1} -j DNAT --to-destination {2}:{3}\n".format( + protocol, s_port, d_ip, + d_port) + "-A POSTROUTING -d {0} -j MASQUERADE\n".format(d_ip) + content = content.replace(_string, "") + return content + + # 处理ufw的端口转发修改 + def ufw_handle_update(self, start_port, ended_ip, ended_port, protocol, + s_port, d_ip, d_port, pool): + content = self.get_profile(self._ufw_before) + if ended_ip == "": + s_string = "-A PREROUTING -p {0} --dport {1} -j REDIRECT --to-port {2}\n".format( + protocol, start_port, ended_port) + else: + s_string = "-A PREROUTING -p {0} --dport {1} -j DNAT --to-destination {2}:{3}\n".format( + protocol, start_port, ended_ip, ended_port + ) + "-A POSTROUTING -d {0} -j MASQUERADE\n".format(ended_ip) + if d_ip == "": + d_string = "-A PREROUTING -p {0} --dport {1} -j REDIRECT --to-port {2}\n".format( + pool, s_port, d_port) + else: + d_string = "-A PREROUTING -p {0} --dport {1} -j DNAT --to-destination {2}:{3}\n".format( + pool, s_port, d_ip, + d_port) + "-A POSTROUTING -d {0} -j MASQUERADE\n".format(d_ip) + content = content.replace(s_string, d_string) + return content + + # 处理firewall的端口转发添加 + def firewall_handle_add(self, s_port, d_port, d_ip, protocol): + if protocol.find('/') != -1: + public.ExecShell( + "firewall-cmd --permanent --zone=public --add-forward-port=port=" + + s_port + ":proto=tcp:toaddr=" + d_ip + ":toport=" + d_port + + "") + public.ExecShell( + "firewall-cmd --permanent --zone=public --add-forward-port=port=" + + s_port + ":proto=udp:toaddr=" + d_ip + ":toport=" + d_port + + "") + else: + cmd = "firewall-cmd --permanent --zone=public --add-forward-port=port=" + s_port + ":proto=" + protocol + ":toaddr=" + d_ip + ":toport=" + d_port + "" + public.ExecShell(cmd) + + # 处理firewall的端口转发删除 + def firewall_handle_del(self, s_port, d_port, d_ip, protocol): + if protocol.find('/') != -1: + public.ExecShell( + "firewall-cmd --permanent --zone=public --remove-forward-port=port=" + + s_port + ":proto=tcp:toaddr=" + d_ip + ":toport=" + d_port + + "") + public.ExecShell( + "firewall-cmd --permanent --zone=public --remove-forward-port=port=" + + s_port + ":proto=udp:toaddr=" + d_ip + ":toport=" + d_port + + "") + else: + public.ExecShell( + "firewall-cmd --permanent --zone=public --remove-forward-port=port=" + + s_port + ":proto=" + protocol + ":toaddr=" + d_ip + + ":toport=" + d_port + "") + + # 处理firewall的端口转发修改 + def firewall_handle_update(self, start_port, ended_ip, ended_port, + protocol, s_port, d_ip, d_port, pool): + if protocol.find('/') != -1: + public.ExecShell( + "firewall-cmd --permanent --zone=public --remove-forward-port=port=" + + start_port + ":proto=tcp:toaddr=" + ended_ip + ":toport=" + + ended_port + "") + public.ExecShell( + "firewall-cmd --permanent --zone=public --remove-forward-port=port=" + + start_port + ":proto=udp:toaddr=" + ended_ip + ":toport=" + + ended_port + "") + else: + public.ExecShell( + "firewall-cmd --permanent --zone=public --remove-forward-port=port=" + + start_port + ":proto=" + protocol + ":toaddr=" + ended_ip + + ":toport=" + ended_port + "") + if pool.find('/') != -1: + public.ExecShell( + "firewall-cmd --permanent --zone=public --add-forward-port=port=" + + s_port + ":proto=tcp:toaddr=" + d_ip + ":toport=" + d_port + + "") + public.ExecShell( + "firewall-cmd --permanent --zone=public --add-forward-port=port=" + + s_port + ":proto=udp:toaddr=" + d_ip + ":toport=" + d_port + + "") + else: + public.ExecShell( + "firewall-cmd --permanent --zone=public --add-forward-port=port=" + + s_port + ":proto=" + pool + ":toaddr=" + d_ip + ":toport=" + + d_port + "") + + # 处理iptables的端口转发添加 + def iptables_handle_add(self, s_port, d_port, d_ip, protocol): + if d_ip == "": + if protocol.find('/') != -1: + public.ExecShell( + "iptables -t nat -A PREROUTING -p tcp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + public.ExecShell( + "iptables -t nat -A PREROUTING -p udp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + public.ExecShell("iptables -t nat -A PREROUTING -p " + + protocol + " --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + if protocol.find('/') != -1: + public.ExecShell( + "iptables -t nat -A PREROUTING -p tcp --dport " + s_port + + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell( + "iptables -t nat -A PREROUTING -p udp --dport " + s_port + + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell( + "iptables -t nat -A POSTROUTING -j MASQUERADE") + else: + public.ExecShell( + "iptables -t nat -A PREROUTING -p " + protocol + " --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -A POSTROUTING -j MASQUERADE") + return True + + # 处理iptables的端口转发删除 + def iptables_handle_del(self, s_port, d_port, d_ip, protocol): + if d_ip == "": + if protocol.find('/') != -1: + public.ExecShell( + "iptables -t nat -D PREROUTING -p tcp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + public.ExecShell( + "iptables -t nat -D PREROUTING -p udp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + public.ExecShell("iptables -t nat -D PREROUTING -p " + + protocol + " --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + if protocol.find('/') != -1: + public.ExecShell( + "iptables -t nat -D PREROUTING -p tcp --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell( + "iptables -t nat -D PREROUTING -p udp --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -D POSTROUTING -j MASQUERADE") + else: + public.ExecShell( + "iptables -t nat -D PREROUTING -p " + protocol + " --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -D POSTROUTING -j MASQUERADE") + return True + + # 处理iptables的端口转发删除 + def iptables_handle_update(self, start_port, ended_ip, ended_port, + protocol, s_port, d_ip, d_port, pool): + if ended_ip == "": + if protocol.find('/') != -1: + public.ExecShell( + "iptables -t nat -D PREROUTING -p tcp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + public.ExecShell( + "iptables -t nat -D PREROUTING -p udp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + public.ExecShell("iptables -t nat -D PREROUTING -p " + + protocol + " --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + if protocol.find('/') != -1: + public.ExecShell( + "iptables -t nat -D PREROUTING -p tcp --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell( + "iptables -t nat -D PREROUTING -p udp --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -D POSTROUTING -j MASQUERADE") + else: + public.ExecShell( + "iptables -t nat -D PREROUTING -p " + protocol + " --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -D POSTROUTING -j MASQUERADE") + if d_ip == "": + if pool.find('/') != -1: + public.ExecShell( + "iptables -t nat -A PREROUTING -p tcp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + public.ExecShell( + "iptables -t nat -A PREROUTING -p udp --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + public.ExecShell("iptables -t nat -A PREROUTING -p " + + protocol + " --dport " + s_port + + " -j REDIRECT --to-port " + d_port + '') + else: + if pool.find('/') != -1: + public.ExecShell( + "iptables -t nat -A PREROUTING -p tcp --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell( + "iptables -t nat -A PREROUTING -p udp --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -A POSTROUTING -j MASQUERADE") + else: + public.ExecShell( + "iptables -t nat -A PREROUTING -p " + protocol + " --dport " + s_port + " -j DNAT --to-destination " + d_ip + ":" + d_port + '') + public.ExecShell("iptables -t nat -A POSTROUTING -j MASQUERADE") + return True + + # 开启端口转发 + def open_forward(self): + if self.__isUfw: + content1 = self.get_profile(self._ufw_default) + content2 = self.get_profile(self._ufw_sysctl) + content1 = content1.replace('DEFAULT_FORWARD_POLICY="DROP"', + 'DEFAULT_FORWARD_POLICY="ACCEPT"') + content2 = content2.replace('#net/ipv4/ip_forward=1', + 'net/ipv4/ip_forward=1') + self.save_profile(self._ufw_default, content1) + self.save_profile(self._ufw_sysctl, content2) + self.FirewallReload() + return True + if self.__isFirewalld: + public.ExecShell( + 'echo "\nnet.ipv4.ip_forward=1" >> /etc/sysctl.conf') + public.ExecShell('firewall-cmd --add-masquerade --permanent') + self.FirewallReload() + else: + public.ExecShell( + 'echo "\nnet.ipv4.ip_forward=1" >> /etc/sysctl.conf') + public.ExecShell('sysctl -p /etc/sysctl.conf') + self.FirewallReload() + return True + + # 开启或关闭端口转发 + def open_close_forward(self, get): + if not get.status in ["open", "close"]: + return public.returnMsg(False, 'Unknown control command!') + if self.__isUfw: + content1 = self.get_profile(self._ufw_default) + content2 = self.get_profile(self._ufw_sysctl) + if get.status == 'open': + content1 = content1.replace('DEFAULT_FORWARD_POLICY="DROP"', + 'DEFAULT_FORWARD_POLICY="ACCEPT"') + content2 = content2.replace('#net/ipv4/ip_forward=1', + 'net/ipv4/ip_forward=1') + else: + content1 = content1.replace('DEFAULT_FORWARD_POLICY="ACCEPT"', + 'DEFAULT_FORWARD_POLICY="DROP"') + content2 = content2.replace('net/ipv4/ip_forward=1', + '#net/ipv4/ip_forward=1') + self.save_profile(self._ufw_default, content1) + self.save_profile(self._ufw_sysctl, content2) + self.FirewallReload() + return public.returnMsg(True, + 'Enable' if get.status == "open" else "Disable") + if self.__isFirewalld: + if get.status == 'open': + public.ExecShell('firewall-cmd --add-masquerade --permanent') + else: + public.ExecShell( + 'firewall-cmd --remove-masquerade --permanent') + self.FirewallReload() + else: + public.ExecShell( + 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf') + public.ExecShell('sysctl -p /etc/sysctl.conf') + return public.returnMsg(True, "Turn off port forwarding") + + def get_host_ip(self): + """ + 查询本机ip地址 + :return: + """ + try: + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + ip = s.getsockname()[0] + finally: + s.close() + + return ip + + def load_white_list(self): + try: + if not self._white_list: + ip_data = self.get_profile(self._white_list_file) + white_list_ips = json.loads(ip_data) + white_list = [] + for ip_obj in white_list_ips: + white_list += ip_obj["ips"] + self._white_list = white_list + # public.WriteLog("firewall_debug", str(white_list)) + return self._white_list + except Exception as e: + public.WriteLog("firewall", "Failed to load whitelist!") + return [] + + def verify_ip(self, ip_entry): + """检查规则IP是否和内网IP重叠""" + try: + try: + import IPy + except: + ipy_tips = '/tmp/bt_ipy.pl' + if not os.path.exists(ipy_tips): + os.system("nohup btpip install IPy &>/dev/null &") + public.WriteFile(ipy_tips, 'True') + + release_ips = [ + IPy.IP("127.0.0.1"), + IPy.IP("172.16.1.1"), + IPy.IP("10.0.0.1"), + IPy.IP("192.168.0.0"), + IPy.IP(self.get_host_ip()) + ] + + white_list = self.load_white_list() + + release_ips += white_list + + ip = IPy.IP(ip_entry, make_net=True) + for rip_obj in release_ips: + overlap = ip.overlaps(rip_obj) + if overlap > 0: + return False + return True + except: + return False + + def handle_firewall_country(self, brief, ip_list, types, port_list): + try: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --new-ipset=' + brief + + ' --type=hash:net') + xml_path = "/etc/firewalld/ipsets/%s.xml" % brief + tree = ElementTree() + tree.parse(xml_path) + root = tree.getroot() + for ip in ip_list: + if self.verify_ip(ip): + entry = Element("entry") + entry.text = ip + root.append(entry) + self.format(root) + tree.write(xml_path, 'utf-8', xml_declaration=True) + if port_list: + for port in port_list: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-rich-rule=\'rule source ipset="' + + brief + '" port port="' + port + '" protocol=tcp ' + + types + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --add-rich-rule=\'rule source ipset="' + + brief + '" ' + types + '\'') + except Exception as e: + return {"status": "error", "msg": e} + + def handle_ufw_country(self, brief, ip_list, types, port_list): + tmp_path = '/tmp/firewall_tmp.sh' + tmp_file = open(tmp_path, 'w') + _string = "#!/bin/bash\n" + for ip in ip_list: + if self.verify_ip(ip): + _string = _string + 'ipset add ' + brief + ' ' + ip + '\n' + tmp_file.write(_string) + tmp_file.close() + public.ExecShell('ipset create ' + brief + + ' hash:net; /bin/bash /tmp/firewall_tmp.sh') + if port_list: + for port in port_list: + public.ExecShell('iptables -I INPUT -m set --match-set ' + + brief + ' src -p tcp --destination-port ' + + port + ' -j ' + types.upper()) + else: + public.ExecShell('iptables -I INPUT -m set --match-set ' + brief + + ' src -j ' + types.upper()) + + # 查询区域规则 + def get_country_list(self, args): + + # 分页校验参数 + try: + args.validate([ + Param('limit').Integer(), + Param('p').Integer(), + Param('query').String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + p = 1 + limit = 15 + if 'p' in args: p = args.p + if 'limit' in args: limit = args.limit + + where = '1=1' + sql = public.M('firewall_country') + + if hasattr(args, 'query'): + where = " country like '%{search}%' or brief like '%{search}%'".format( + search=args.query) + + count = sql.where(where, ()).count() + data = public.get_page(count, int(p), int(limit)) + data['data'] = sql.where(where, ()).limit('{},{}'.format( + data['shift'], data['row'])).order('addtime desc').select() + # return data + return public.return_message(0, 0, data) + + def create_countrys(self, get): + try: + if not hasattr(get, 'country'): + return public.returnMsg(False, 'Please enter the country name!') + input_country = get.country + countrys = self.get_countrys(None) + countrys = countrys[1:] + + # 2024/1/6 下午 5:00 获取防火墙状态,如果没有启动则启动防火墙 + if not self.get_firewall_status(): + get.status = "start" + self.firewall_admin(get) + + if "Except China" in input_country: + input_country = [i['CH'] for i in countrys if not "China" in i['CH']] + countrys_dict = {i['CH']: i['brief'] for i in countrys} + content = self.get_profile(self._ips_path) + + for i in input_country: + get.brief = countrys_dict.get(i, None) + get.country = i + self.create_country(get, True, content) + else: + countrys_dict = {i['CH']: i['brief'] for i in countrys} + if isinstance(input_country, str): + input_country = [input_country] + for i in input_country: + get.brief = countrys_dict.get(i, None) + get.country = i + self.create_country(get, True) + get.status = "restart" + self.firewall_admin(get) + return public.returnMsg(True, 'Added successfully') + except: + print(traceback.format_exc()) + return public.returnMsg(False, 'Add failed') + + # 添加区域规则 + def create_country(self, get, is_mutil=False, _ips_paths=None): + # {"country": "US Virgin Islands", "types": "drop", "choose": "all", "ports": "", "brief": "VI"} 暂时只有封锁和地区可选 + # 校验参数 + try: + get.validate([ + Param('country').Require().Xss(), + Param('types').Require().String('in', ['accept', 'drop']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + brief = get.brief + types = get.types # types in [accept, drop] + ports = get.ports + country = get.country + rep = r"^\d{1,5}(:\d{1,5})?$" + port_list = [] + + # 检测该区域是否已添加过全部端口规则 hezhihong + add_list = public.M('firewall_country').where("country=?", (country,)).field('ports').select() + + for add in add_list: + if not add['ports']: + # return public.returnMsg(False, 'This area has already been added, please do not add it again!') + return public.return_message(-1, 0, 'This area has already been added, please do not add it again!') + if ports: + port_list = ports.split(',') + for port in port_list: + if not re.search(rep, port): + # return public.returnMsg(False, 'PORT_CHECK_RANGE') + return public.return_message(-1, 0, 'PORT_CHECK_RANGE') + if public.M('firewall_country').where( + "country=? and ports=?", (country, port)).count() > 0: + public.print_log('############ 地区接口8{}'.format(port)) + # return public.returnMsg(False, 'This area has already been added, please do not add it again!') + return public.return_message(-1, 0, 'This area has already been added, please do not add it again!') + self.get_os_info() + if _ips_paths is None: + content = self.get_profile(self._ips_path) + else: + content = _ips_paths + result = json.loads(content) + ip_list = [] + for r in result: + if brief == r["brief"]: + ip_list = r["ips"] + break + if not ip_list: + public.print_log('############ 地区接口7'.format()) + # return public.returnMsg(True, "Please enter the correct area name!") + return public.return_message(0, 0, 'Please enter the correct area name!') + if self.__isUfw: + self.handle_ufw_country(brief, ip_list, types, port_list) + else: + if self.__isFirewalld: + result = self.handle_firewall_country(brief, ip_list, types, + port_list) + if result: + public.print_log('############ 地区接口6{}'.format(result)) + # return result + return public.return_message(0, 0, result) + else: + self.handle_ufw_country(brief, ip_list, types, port_list) + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + if port_list: + for port in port_list: + public.M('firewall_country').add( + 'country,types,brief,ports,addtime', + (country, types, brief, port, addtime)) + else: + public.M('firewall_country').add( + 'country,types,brief,ports,addtime', + (country, types, brief, '', addtime)) + if is_mutil is False: + # self.FirewallReload() + get.status = "restart" + self.firewall_admin(get) + if not get.ports: + log_port = "All ports" + else: + log_port = get.ports + # strategy = '' + # if get.types == 'accept': + # strategy = "accept" + # elif get.types == 'drop': + # strategy = "drop" + # public.print_log('############ 地区接口5'.format()) + public.WriteLog("system firewall", "Add regional rules: Region:{}, Policy:{}, Port:{}".format(get.country, get.types, log_port)) + # return public.returnMsg(True, 'ADD_SUCCESS') + return public.return_message(0, 0, 'ADD_SUCCESS') + + # 删除区域规则 + def remove_country(self, get): + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer(), + Param('country').Require().Xss(), + Param('types').Require().String('in', ['accept', 'drop']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + id = get.id + types = get.types + brief = get.brief + ports = get.ports + country = get.country + reload = True + if "not_reload" in get: + reload = get.not_reload.lower() == "true" + public.M('firewall_country').where("id=?", (id, )).delete() + if self.__isUfw: + if not ports: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + brief + ' src -j ' + types.upper()) + else: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + brief + ' src -p tcp --destination-port ' + + ports + ' -j ' + types.upper()) + if not public.M('firewall_country').where("country=?", + (country, )).count() > 0: + public.ExecShell('ipset destroy ' + brief) + else: + if self.__isFirewalld: + if not ports: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-rich-rule=\'rule source ipset="' + + brief + '" ' + types + '\'') + else: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --remove-rich-rule=\'rule source ipset="' + + brief + '" port port="' + ports + '" protocol=tcp ' + + types + '\'') + if not public.M('firewall_country').where( + "country=?", (country, )).count() > 0: + public.ExecShell( + 'firewall-cmd --permanent --zone=public --delete-ipset=' + + brief) + else: + if not ports: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + brief + ' src -j ' + types.upper()) + else: + public.ExecShell('iptables -D INPUT -m set --match-set ' + + brief + + ' src -p tcp --destination-port ' + + ports + ' -j ' + types.upper()) + if not public.M('firewall_country').where( + "country=?", (country, )).count() > 0: + public.ExecShell('ipset destroy ' + brief) + if reload: + get.status = "restart" + self.firewall_admin(get) + if not get.ports: + log_port = "All ports" + else: + log_port = get.ports + strategy = '' + if get.types == 'accept': + strategy = "accept" + elif get.types == 'drop': + strategy = 'drop' + public.WriteLog("system firewall", "Delete zone rules: Region:{}, Policy:{}, Port:{}".format(get.country, strategy, log_port)) + # return public.returnMsg(True, 'DEL_SUCCESS') + return public.return_message(0, 0, 'DEL_SUCCESS') + + # 编辑区域规则 + def modify_country(self, get): + # {"id":17,"country":"Vanuatu","types":"drop","choose":"all","ports":"","brief":"VU "} + # 校验参数 + try: + get.validate([ + Param('id').Require().Integer().Xss(), + Param('country').Require().Xss(), + Param('types').Require().String('in', ['accept', 'drop']), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + # 2022/11/24 修复编辑地区端口规则问题 lx + id = get.id + # types = get.types + # brief = get.brief + # country = get.country + data = public.M('firewall_country').where( + 'id=?', + (id, )).field('id,country,types,brief,ports,addtime').find() + ori_get = public.dict_obj() + ori_get.id = id + ori_get.types = data.get("types", "") + ori_get.brief = data.get("brief", "") + ori_get.country = data.get("country", "") + ori_get.ports = data.get("ports", "") + ori_get.not_reload = "true" + rm_res = self.remove_country(ori_get) + if rm_res["status"]: + create_res = self.create_country(get) + if create_res["status"]: + # return public.returnMsg(True, "Successful operation") + return public.return_message(0, 0, "Successful operation") + # return public.returnMsg(False, "operation failed") + return public.return_message(0, 0, "operation failed") + + # 获取服务端列表:centos + def GetList(self): + try: + result, arry = self.__Obj.GetAcceptPortList() + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + for i in range(len(result)): + if "address" not in result[i].keys(): continue + tmp = self.check_db_exists(result[i]['ports'], + result[i]['address'], + result[i]['types']) + protocol = result[i]['protocol'] + ports = result[i]['ports'] + types = result[i]['types'] + address = result[i]['address'] + if not tmp: + if ports: + public.M('firewall_new').add( + 'ports,protocol,address,types,brief,addtime', + (ports, protocol, address, types, '', addtime)) + else: + public.M('firewall_ip').add( + 'address,types,brief,addtime', + (address, types, '', addtime)) + for i in range(len(arry)): + if arry[i]['port']: + tmp = self.check_trans_data(arry[i]['port']) + protocol = arry[i]['protocol'] + s_port = arry[i]['port'] + d_port = arry[i]['to-port'] + address = arry[i]['address'] + if not tmp: + public.M('firewall_trans').add( + 'start_port,ended_ip,ended_port,protocol,addtime', + (s_port, address, d_port, protocol, addtime)) + except Exception as e: + file = open('error.txt', 'w') + return public.returnMsg(False, e) + + # 获取服务端列表:ufw + def get_ufw_list(self): + data = public.M('firewall').field('id,port,ps,addtime').select() + if type(data) != list: return + try: + for dt in data: + port = dt['port'] + brief = dt['ps'] + addtime = dt['addtime'] + if port.find('.') != -1: + tmp = self.check_db_exists('', port, 'drop') + if not tmp: + public.M('firewall_ip').add('address,types,brief,addtime', + (port, 'drop', '', addtime)) + else: + tmp = self.check_db_exists(port, '', 'accept') + if not tmp: + public.M('firewall_new').add( + 'ports,brief,protocol,address,types,addtime', + (port, brief, 'tcp/udp', '', 'accept', addtime)) + except: + pass + + # 检查数据库是否存在 + def check_db_exists(self, ports, address, types): + if ports: + data = public.M('firewall_new').field( + 'id,ports,protocol,address,types,brief,addtime').select() + for dt in data: + if dt['ports'] == ports: return dt + return False + else: + data = public.M('firewall_ip').field( + 'id,address,types,brief,addtime').select() + for dt in data: + if dt["address"] == address and dt["types"] == types: return dt + return False + + def check_trans_data(self, ports): + data = public.M('firewall_trans').field( + 'id,start_port,ended_ip,ended_port,protocol,addtime').select() + for dt in data: + if dt['start_port'] == ports: return dt + return False + + # 规则导出:服务器 todo + def export_rules(self, get): + rule_name = get.rule_name + arry = [] + data_list = None + filename = '' + if rule_name == "port_rule": + filename = self._rule_path + "port.json" + data_list = public.M('firewall_new').order("id desc").select() + elif rule_name == "ip_rule": + filename = self._rule_path + "ip.json" + data_list = public.M('firewall_ip').order("id desc").select() + elif rule_name == "trans_rule": + filename = self._rule_path + "forward.json" + data_list = public.M('firewall_trans').order("id desc").select() + elif rule_name == "country_rule": + filename = self._rule_path + "country.json" + data_list = public.M('firewall_country').order("id desc").select() + if not data_list: + data_list = [] + # 将数据格式换成以|分割的字符串 hezhihong + write_string = "" + if data_list: + for i in data_list: + for v in i.keys(): + if v == 'domain': i[v] = i[v].replace('|', '#') + write_string += str(i[v]) + "|" + write_string += '\n' + public.writeFile(filename, write_string) + public.WriteLog("system firewall", "导出端口规则") + return public.returnMsg(True, filename) + + # 规则导出:本地 todo + def get_file(self, args): + filename = args.filename + mimetype = "application/octet-stream" + if not os.path.exists(filename): + return abort(404) + return send_file(filename, + mimetype=mimetype, + as_attachment=True, + attachment_filename=os.path.basename(filename), + cache_timeout=0) + + # 规则导入:json todo + def import_rules(self, get): + try: + rule_name = get.rule_name # 规则名:[port_rule, ip_rule, trans_rule, country_rule] + file_name = get.file_name # 文件命:[port.json, ip.json, trans.json, country.json] + file_path = "{0}{1}".format(self._rule_path, file_name) + data_list = self.get_profile(file_path) + pay = self.__check_auth() + not_pay_list = [] + tmp_data = [] + # |分隔符格式文件导入 hezhihong + if data_list and isinstance(data_list, str): + + if data_list.find('|') != -1: + data_list = data_list.split('\n') + for data in data_list: + if not data: continue + split_data = data.split('|') + data_dict = {} + data_dict['id'] = split_data[0] + if rule_name == 'port_rule': + if not pay and split_data[7].find('#') != -1: + not_pay_list.append(data) + continue + data_dict['protocol'] = split_data[1] + data_dict['ports'] = split_data[2] + data_dict['types'] = split_data[3] + data_dict['address'] = split_data[4] + data_dict['brief'] = split_data[5] + data_dict['addtime'] = split_data[6] + data_dict['domain'] = split_data[7] + elif rule_name == 'ip_rule': + if not pay and split_data[6].find('#') != -1: + not_pay_list.append(data) + continue + data_dict['types'] = split_data[1] + data_dict['address'] = split_data[2] + data_dict['brief'] = split_data[3] + data_dict['addtime'] = split_data[4] + data_dict['domain'] = split_data[6] + elif rule_name == 'trans_rule': + data_dict['start_port'] = split_data[1] + data_dict['ended_ip'] = split_data[2] + data_dict['ended_port'] = split_data[3] + data_dict['protocol'] = split_data[4] + data_dict['addtime'] = split_data[5] + elif rule_name == 'country_rule': + data_dict['types'] = split_data[1] + data_dict['country'] = split_data[2] + data_dict['brief'] = split_data[3] + data_dict['addtime'] = split_data[4] + data_dict['ports'] = split_data[5] + tmp_data.append(data_dict) + data_list = tmp_data + # 一行一条规则格式文件导入 hezhihong + if data_list and isinstance(data_list, str): + data_list = data_list.strip() + if isinstance(data_list, str) and data_list.find('\n') != -1: + data_list = data_list.split('\n') + try: + data_list.remove('') + except: + pass + if isinstance(data_list, str): + try: + data_list = json.loads(data_list) + except: + if os.path.exists(file_path): + os.remove(file_path) + return public.ReturnMsg(False, "The file content is incorrect!!") + if data_list: + if isinstance(data_list, dict): + data_list = [data_list] + if not isinstance(data_list, list): + if os.path.exists(file_path): + os.remove(file_path) + return public.ReturnMsg(False, "The file content is incorrect!!") + if len(data_list) == 0: + return public.ReturnMsg(False, "The file is empty!") + result = self.hand_import_rules(rule_name, data_list) + os.remove(file_path) + if not_pay_list: + not_pay_list = ("
                                    " + "-" * 20 + "
                                    ").join(not_pay_list) + return public.ReturnMsg( + result["status"], + "{}
                                    The designated domain name function is exclusive to the Enterprise Edition, and the following rules are not imported:
                                    {}".format(result["msg"], not_pay_list) + ) + public.WriteLog("system firewall", "Import port rules") + return public.ReturnMsg(result["status"], result["msg"]) + except Exception: + return public.ReturnMsg(False, "The import failed. The format of the rules is wrong. Please try again according to the format of the export rules!") + + # 处理规则导入,读取json文件内容 + def hand_import_rules(self, rule_name, data_list): + table_head = [] + try: + if rule_name == "port_rule": + table_head = ["id", "protocol", "ports", "types", "address", "brief", "addtime", "domain", ] + for data in data_list: + #兼容一行一条规则格式文件导入 hezhihong + try: + data = json.loads(data) + except: + pass + + res = all([field in data.keys() for field in table_head]) + if not res or len(table_head) != len(data.keys()): + return {"status": False, "msg": "The data format is incorrect!"} + get = public.dict_obj() + get.protocol = data["protocol"] + get.ports = data["ports"] + get.types = data["types"] + get.source = data["address"] + get.brief = data["brief"] + # 兼容域名导入 hezhihong + if 'domain' in data.keys() and data['domain']: + get.domain = data['domain'].split('#')[0] + get.source = data['domain'].split('#')[1] + result = self.create_rules(get) + if not result["status"]: + continue + elif rule_name == "ip_rule": + table_head = ["id", "types", "address", "brief", "addtime"] + for data in data_list: + # 兼容一行一条规则格式文件导入 hezhihong + try: + data = json.loads(data) + except: + pass + res = all([field in data.keys() for field in table_head]) + if not res: + return {"status": False, "msg": "The data format is incorrect!"} + get = public.dict_obj() + get.types = data["types"] + get.address = data["address"] + get.brief = data["brief"] + # 兼容域名导入 hezhihong + if 'domain' in data.keys() and data['domain']: + get.domain = data['domain'].split('#')[0] + get.source = data['domain'].split('#')[1] + result = self.create_ip_rules(get) + if not result["status"]: + continue + elif rule_name == "trans_rule": + table_head = [ + "id", "start_port", "ended_ip", "ended_port", "protocol", + "addtime" + ] + for data in data_list: + # 兼容一行一条规则格式文件导入 hezhihong + try: + data = json.loads(data) + except: + pass + res = all([field in data.keys() for field in table_head]) + if not res: + return {"status": False, "msg": "The data format is incorrect!"} + get = public.dict_obj() + get.s_ports = data["start_port"] + get.d_address = data["ended_ip"] + get.d_ports = data["ended_port"] + get.protocol = data["protocol"] + result = self.create_forward(get) + if not result["status"]: + continue + elif rule_name == "country_rule": + table_head = ["id", "types", "country", "brief", "addtime", "ports"] + for data in data_list: + # 兼容一行一条规则格式文件导入 hezhihong + try: + data = json.loads(data) + except: + pass + res = all([field in data.keys() for field in table_head]) + if not res: + return {"status": False, "msg": "The data format is incorrect!"} + get = public.dict_obj() + get.types = data["types"] + get.ports = data["ports"] + get.brief = data["brief"] + get.country = data["country"] + result = self.create_country(get) + if not result["status"]: + continue + except: + return {"status": False, "msg": "Import failed!"} + return {"status": True, "msg": "Imported successfully!"} + + def get_countrys(self, get): + result = [] + content = self.get_profile(self._country_path) + result = json.loads(content) + result = sorted(result, key=lambda x : x['CH'], reverse=True); + + if isinstance(result, list): + result.insert(0, {"CH": "Except China", "brief": "OTHER"}) + return result + + # 读取配置文件 + def get_profile(self, path): + + if not os.path.exists(path): + b_path = os.path.dirname(path) + if not os.path.exists(b_path): os.makedirs(b_path) + + if path in [ + self._ips_path, self._country_path, self._white_list_file + ]: + public.downloadFile( + 'https://download.bt.cn/install/lib/{}'.format( + os.path.basename(path)), path) + + content = "" + with open(path, "r") as fr: + content = fr.read() + return content + + # 保存配置文件 + def save_profile(self, path, data): + with open(path, "w") as fw: + fw.write(data) + + # 读取配置文件 + def update_profile(self, path): + import files + f = files.files() + return f.GetFileBody(path) + + # 获取端口规则列表 + def get_port_rules(self, get): + rule_list = public.M('firewall_new').order("id desc").select() + # return public.returnMsg(True, rule_list) + return public.return_message(0, 0, rule_list) + + # 整理配置文件格式 + def format(self, em, level=0): + i = "\n" + level * " " + if len(em): + if not em.text or not em.text.strip(): + em.text = i + " " + for e in em: + self.format(e, level + 1) + if not e.tail or not e.tail.strip(): + e.tail = i + if level and (not em.tail or not em.tail.strip()): + em.tail = i + + def check_table(self): + if public.M('sqlite_master').where('type=? AND name=?', + ('table', 'firewall_new')).count(): + if public.M('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_ip')).count(): + if public.M('sqlite_master').where( + 'type=? AND name=?', + ('table', 'firewall_trans')).count(): + if public.M('sqlite_master').where( + 'type=? AND name=?', + ('table', 'firewall_country')).count(): + return True + return Sqlite() + + def delete_service(self): + if self.__isUfw: + public.ExecShell('ufw delete allow ssh') + else: + if self.__isFirewalld: + public.ExecShell( + 'firewall-cmd --zone=public --remove-service=ssh --permanent' + ) + else: + pass + return True + + # 获取系统类型(具体到哪个版本) + def get_os_info(self): + tmp = {"osname": "", "version": ""} + if os.path.exists('/etc/redhat-release'): + sys_info = public.ReadFile('/etc/redhat-release') + elif os.path.exists('/usr/bin/yum'): + sys_info = public.ReadFile('/etc/issue') + elif os.path.exists('/etc/issue'): + sys_info = public.ReadFile('/etc/issue') + try: + tmp['osname'] = sys_info.split()[0] + tmp['version'] = re.search(r'\d+(\.\d*)*', sys_info).group() + except: + os_result = public.ExecShell(". /etc/os-release && echo $ID")[0] + if "amzn" == os_result: + tmp['osname'] = 'CentOS' + tmp['version'] = '8' + if tmp["osname"] == "CentOS": + if tmp["version"].startswith("8"): + content = self.get_profile("/etc/firewalld/firewalld.conf") + content = content.replace("FirewallBackend=nftables", "FirewallBackend=iptables") + self.save_profile("/etc/firewalld/firewalld.conf", content) + public.ExecShell("systemctl restart firewalld") + return True + + # 新加代码----- start + + def sync_must_ports(self, get): + ''' + 同步必须放行的端口 + @param get: + @return: + ''' + # 检查必传参数 + try: + public.exists_args('ports', get) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(0, 0, str(ex)) + + protocol = "tcp" + ports = get.ports.strip() + print(ports) + if not ports: return public.returnMsg(False, 'Port cannot be empty!') + port_list = ports.split(",") if ports.find(",") != -1 else [ports] + types = "accept" + check_result = self.check_port(port_list) + if check_result: return check_result + + firewall_type = 'iptables' + if self.__isFirewalld: firewall_type = 'firewalld' + if self.__isUfw: firewall_type = 'ufw' + + try: + # 2024/1/6 下午 5:00 获取防火墙状态,如果没有启动则启动防火墙 + if not self.get_firewall_status(): + get = public.dict_obj() + get.status = 1 + self.firewall_admin(get) + + for port in port_list: + if firewall_type == 'firewalld': + if port.find(':') != -1: port = port.replace(':', '-') + self.add_firewall_rule("", protocol, port, types) + elif firewall_type == 'ufw': + if port.find('-') != -1: port = port.replace('-', ':') + self.add_ufw_rule("", protocol, port, types) + else: + self.add_iptables_rule("", protocol, port, types) + + query_result = public.M('firewall_new').where( + 'ports=? and address=? and protocol=? and types=?', + (port, "", protocol, types) + ).find() + print(query_result) + if query_result: continue + + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + self._add_sid = public.M('firewall_new').add( + 'ports,brief,protocol,address,types,addtime,domain,sid', + (port, "", protocol, "", types, addtime, "", 0) + ) + # return public.returnMsg(True, 'ADD_SUCCESS') + return public.return_message(0, 0, "ADD_SUCCESS") + except Exception: + print(traceback.format_exc()) + # return public.returnMsg(False, 'ADD_ERROR') + return public.return_message(0, 0, "ADD_ERROR") + + def _get_webserver(self): + ''' + 获取web服务器类型 + @return: + ''' + webserver = '' + if os.path.exists('/www/server/nginx/sbin/nginx'): + webserver = 'nginx' + elif os.path.exists('/www/server/apache/bin/httpd'): + webserver = 'apache' + elif os.path.exists('/usr/local/lsws/bin/lswsctrl'): + webserver = 'lswsctrl' + return webserver + + def get_port_info(self, get): + ''' + 获取面板防火墙关键服务端口放行状态信息 + 判断服务是否存在,能读取文件就读取文件,配置文件不大不会影响性能,这种方式能最大缩短接口响应时间,公网测试80ms + @param get: + @return: + ''' + ports_list = [] + result_list = [{"name": "FTP passive port", "status": 0, "port": "39000-40000"}] + + webserver = self._get_webserver() + if webserver in ['nginx', 'apache', 'lswsctrl']: + result_list.append({"name": "website port", "status": 0, "port": "80"}) + ports_list.append("80") + + port_443, _ = public.ExecShell("fuser -n tcp 443") + if port_443: + result_list.append({"name": "HTTPS port", "status": 0, "port": "443"}) + ports_list.append("443") + + _panel_port_file = '/www/server/panel/data/port.pl' + panel_port = public.readFile(_panel_port_file).strip(" ").strip("\n") + + cmd = "cat /www/server/pure-ftpd/etc/pure-ftpd.conf |grep Bind|awk -F ',' '{print $2}'" + ftp_port = public.ExecShell(cmd)[0].strip(" ").strip("\n").strip("\r") + + cmd = "cat /etc/ssh/sshd_config |grep -E '^Port'|awk '{print $2}'|awk 'NR == 1'" + ssh_port = public.ExecShell(cmd)[0].strip(" ").strip("\n") + + if ssh_port == "": ssh_port = "22" + ports_list.append(panel_port) + result_list.append({"name": "panel", "status": 0, "port": panel_port}) + ports_list.append(ftp_port) + result_list.append({"name": "FTP active port", "status": 0, "port": ftp_port}) + ports_list.append(ssh_port) + result_list.append({"name": "SSH", "status": 0, "port": ssh_port}) + ports_list.append("39000-40000") + + if self.__isUfw: + # return self._get_ufw_port_status(ports_list, result_list) + list1 = self._get_ufw_port_status(ports_list, result_list) + return public.return_message(0, 0, list1) + if self.__isFirewalld: + # return self._get_firewall_port_status(ports_list, result_list) + list1 = self._get_firewall_port_status(ports_list, result_list) + return public.return_message(0, 0, list1) + # return {} + return public.return_message(0, 0, {}) + + def _get_firewall_port_status(self, ports_list, result_list): + ''' + 获取firewalld防火墙端口状态 + @param ports_list: + @param result_list: + @return: + ''' + with contextlib.suppress(Exception): + _firewalld_ports, _ = self.__firewall_obj.GetAcceptPortList() + # print("_firewalld_ports: ", _firewalld_ports) + for firewalld_port in _firewalld_ports: + if firewalld_port['ports'] in ports_list: + for result in result_list: + if result['port'] == firewalld_port['ports']: + result['status'] = 1 + break + return result_list + + def _get_ufw_port_status(self, ports_list, result_list): + ''' + 获取ufw防火墙端口状态 + @param ports_list: + @param result_list: + @return: + ''' + with contextlib.suppress(Exception): + rules_result = self._get_ufw_port_info() + # print("rules_result: ", rules_result) + ports_set = set(ports_list) # 将要查找的端口列表转换成集合,以便进行高效查找 + + for rule in rules_result: + if 'tcp' in rule['protocol'] and rule['ports'] in ports_set: + for result in result_list: + if result['port'] == rule['ports']: + result['status'] = 1 + ports_set.remove(rule['ports']) + break + + if not ports_set: break + return result_list + + def _get_ufw_port_info(self): + ''' + 获取ufw防火墙端口信息 + @return: + ''' + with open('/etc/ufw/user.rules', 'r') as f: + content = f.read() + start_index = content.find('### RULES ###') + end_index = content.find('### END RULES ###') + result = content[start_index + 15:end_index] + sys_rules = [rule for rule in result.split('\n') if rule != '' and '###' in rule] + # 将sys_rules列表中的每个元素拆分出来,并且去掉空格,元素为字符串,例如:'### tuple ### allow tcp 20 0.0.0.0/0 any 0.0.0.0/0 in' + # 拆分后的列表元素为:{'protocol': 'tcp', 'ports': '20', 'types': 'allow', 'address': '0.0.0.0'} + rules = [] + for rule in sys_rules: + rule = rule.split(' ') + rule = [i for i in rule if i != ''] + rules.append({ + 'protocol': rule[4] if rule[4] != 'any' else 'tcp/udp', + 'ports': rule[5] if rule[5].find(':') == -1 else rule[5].replace(':', '-'), + 'types': 'accept' if rule[3] == 'allow' else 'drop', + 'address': rule[8] if rule[8] != '0.0.0.0/0' else '' + }) + + unique_set = set(tuple(sorted(item.items())) for item in rules) + rules = [dict(item) for item in unique_set] + return rules + + @staticmethod + def get_listening_processes(get): + ''' + 获取指定端口的进程信息 + @param get: + @return: + ''' + + # 校验参数 + try: + get.validate([ + Param('port').Require().Number(">=", 1).Number("<=", 65535).Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + print("get.get_items().keys(): ", get.get_items().keys()) + + if get.port.find('-') != -1 or get.port.find(':') != -1: + # return public.returnMsg(False, 'Range ports not supported') + return public.return_message(-1, 0, 'Range ports not supported') + + + process_name = '' + process_pid = '' + process_cmd = '' + + cmd = "lsof -i:{}|grep LISTEN|grep -v COMMAND".format(get.port) + "|awk '{print $1,$2}'" + info_list = public.ExecShell(cmd)[0].split("\n")[0].split(" ") + if len(info_list) == 2: + process_name = info_list[0] + process_pid = info_list[1] + cmd_ps = "ps aux|grep {}|grep -v grep".format(process_pid) + cmd_awk = "|awk '{print $11,$12,$13,$14}'" + process_cmd = public.ExecShell(cmd_ps + cmd_awk)[0].split("\n")[0].strip(" ") + + # return { + # "process_name": process_name, + # "process_pid": process_pid, + # "process_cmd": process_cmd + # } + + return public.return_message(0, 0, { + "process_name": process_name, + "process_pid": process_pid, + "process_cmd": process_cmd + }) + + def get_diff_panel_firewall_rules(self, get): + ''' + 对比面板防火墙规则数据库和防火墙配置文件,取出差异的规则 + @return: + ''' + # 获取面板防火墙规则数据库 + panel_firewall_rules = self.get_panel_firewall_rules() + # 获取防火墙配置文件 + firewall_rules = self.get_sys_firewall_rules() + # 取出差异的规则 + diff_rules = self._get_diff_rules(panel_firewall_rules, firewall_rules) + # return diff_rules + return public.return_message(0, 0, diff_rules) + + def get_panel_firewall_rules(self): + ''' + 获取面板防火墙规则数据库 + @return: + ''' + all_ports = public.M('firewall_new').field('protocol,ports,types,address').order('addtime desc').select() + unique_set = set(tuple(sorted(item.items())) for item in all_ports) + new_ports = [dict(item) for item in unique_set] + return new_ports + + def get_sys_firewall_rules(self): + ''' + 获取防火墙配置文件 + @return: + ''' + if self.__isUfw: return self._get_ufw_port_info() + if self.__isFirewalld: return self.__firewall_obj.recombine_rules() + return [] + + def _diff_dict_list(self, list1, list2): + ''' + 比较两个dict类型的list,返回list1中有,而list2中没有的元素 + @param list1: + @param list2: + @return: + ''' + list1_not_in_list2 = [] + for item1 in list1: + found = False + try: + for item2 in list2: + # 对比'protocol,ports,types,address'是否相同 + if all(item1[key] == item2[key] for key in ('protocol', 'ports', 'types', 'address')): + found = True + break + except KeyError: + continue + if not found: list1_not_in_list2.append(item1) + return list1_not_in_list2 + + def _get_diff_rules(self, panel_firewall_rules, firewall_rules): + ''' + 取出差异的规则 + @param panel_firewall_rules: + @param firewall_rules: + @return: + ''' + firewall_diff_rules_name = 'firewall_diff_rules' + firewall_diff_rules = {} + if os.path.isfile("config/{}.json".format(firewall_diff_rules_name)): + firewall_diff_rules = public.read_config(firewall_diff_rules_name) + if not firewall_diff_rules: + data = { + "panel_not_in_sys_fw_diff_list": self._diff_dict_list(panel_firewall_rules, firewall_rules), + "sys_not_in_panel_fw_diff_list": self._diff_dict_list(firewall_rules, panel_firewall_rules), + "panel_exclude": [], + "sys_exclude": [] + } + public.save_config(firewall_diff_rules_name, data) + return data + + panel_not_in_sys_fw_diff_list = self._diff_dict_list(panel_firewall_rules, firewall_rules) + sys_not_in_panel_fw_diff_list = self._diff_dict_list(firewall_rules, panel_firewall_rules) + + # 如果firewall_diff_rules的panel_exclude和sys_exclude中有值,则将其从panel_not_in_sys_fw_diff_list和sys_not_in_panel_fw_diff_list中去掉 + if firewall_diff_rules['panel_exclude']: + for key in firewall_diff_rules['panel_exclude']: + if key in panel_not_in_sys_fw_diff_list: + panel_not_in_sys_fw_diff_list.remove(key) + if firewall_diff_rules['sys_exclude']: + for key in firewall_diff_rules['sys_exclude']: + # panel_not_in_sys_fw_diff_list是list,如果key在panel_not_in_sys_fw_diff_list中,则将其从panel_not_in_sys_fw_diff_list中去掉 + if key in sys_not_in_panel_fw_diff_list: + sys_not_in_panel_fw_diff_list.remove(key) + + # 将差异规则写入文件并返回 + firewall_diff_rules['panel_not_in_sys_fw_diff_list'] = panel_not_in_sys_fw_diff_list + firewall_diff_rules['sys_not_in_panel_fw_diff_list'] = sys_not_in_panel_fw_diff_list + public.save_config(firewall_diff_rules_name, firewall_diff_rules) + return firewall_diff_rules + + def exclude_diff_rules(self, get): + ''' + 排除firewall_diff_rules的规则,并写入配置文件 + @param get: + @return: + ''' + try: + panel_excludes = get.panel_exclude if "panel_exclude" in get.get_items().keys() else {} + sys_excludes = get.sys_exclude if "sys_exclude" in get.get_items().keys() else {} + status = get.status if "status" in get.get_items().keys() else {} + # print("panel_excludes: ", panel_excludes) + + if status == 'add': + return self._add_exclude(panel_excludes, sys_excludes) + elif status == 'del': + return self._del_exclude(panel_excludes, sys_excludes) + except Exception as e: + # print(e) + # return public.returnMsg(False, 'Ignore rule failed,{}!'.format(e)) + return public.return_message(0, 0, 'Ignore rule failed,{}!'.format(e)) + + def _add_exclude(self, panel_excludes, sys_excludes): + ''' + 添加排除规则 + @param panel_exclude: + @param sys_exclude: + @return: + ''' + firewall_diff_rules_name = 'firewall_diff_rules' + firewall_diff_rules = {} + if os.path.isfile("config/{}.json".format(firewall_diff_rules_name)): + firewall_diff_rules = public.read_config(firewall_diff_rules_name) + + if not firewall_diff_rules['panel_exclude']: + firewall_diff_rules['panel_exclude'] = panel_excludes + else: + for exclude in panel_excludes: + if exclude not in firewall_diff_rules['panel_exclude']: + firewall_diff_rules['panel_exclude'].append(exclude) + + if not firewall_diff_rules['sys_exclude']: + firewall_diff_rules['sys_exclude'] = sys_excludes + else: + for exclude in sys_excludes: + if exclude not in firewall_diff_rules['sys_exclude']: + firewall_diff_rules['sys_exclude'].append(exclude) + public.save_config(firewall_diff_rules_name, firewall_diff_rules) + return public.returnMsg(True, 'Ignore rules successfully!') + + def _del_exclude(self, panel_excludes, sys_excludes): + ''' + 删除排除规则 + @param panel_excludes: + @param sys_excludes: + @return: + ''' + firewall_diff_rules_name = 'firewall_diff_rules' + firewall_diff_rules = {} + if os.path.isfile("config/{}.json".format(firewall_diff_rules_name)): + firewall_diff_rules = public.read_config(firewall_diff_rules_name) + + new_panel_exclude = [] + new_sys_exclude = [] + + for exclude in firewall_diff_rules['panel_exclude']: + if exclude not in panel_excludes: + new_panel_exclude.append(exclude) + for exclude in firewall_diff_rules['sys_exclude']: + if exclude not in sys_excludes: + new_sys_exclude.append(exclude) + + firewall_diff_rules['panel_exclude'] = new_panel_exclude + firewall_diff_rules['sys_exclude'] = new_sys_exclude + public.save_config(firewall_diff_rules_name, firewall_diff_rules) + # return public.returnMsg(True, 'Cancel ignore rule successfully') + return public.return_message(0, 0, 'Cancel ignore rule successfully') + + def _add_firewall_rules(self, source_ip, protocol, port, types): + ''' + 添加防火墙规则 + @param source_ip: + @param protocol: + @param port: + @param types: + @return: + ''' + if self.__isUfw: + if port.find('-') != -1: + port = port.replace('-', ':') + self.add_ufw_rule(source_ip, protocol, port, types) + elif self.__isFirewalld: + if port.find(':') != -1: + port = port.replace(':', '-') + self.add_firewall_rule(source_ip, protocol, port, types) + else: + self.add_iptables_rule(source_ip, protocol, port, types) + + def _del_firewall_rules(self, source_ip, protocol, port, types): + ''' + 删除防火墙规则 + @param source_ip: + @param protocol: + @param port: + @param types: + @return: + ''' + if self.__isUfw: + self.del_ufw_rule(source_ip, protocol, port, types) + elif self.__isFirewalld: + self.del_firewall_rule(source_ip, protocol, port, types) + else: + self.del_iptables_rule(source_ip, protocol, port, types) + + def _modify_firewall_rules(self, address, protocol, port, type, source_ip, source_protocol, ports, types): + ''' + 修改防火墙规则1 + @param address: + @param protocol: + @param port: + @param type: + @param source_ip: + @param source_protocol: + @param ports: + @param types: + @return: + ''' + if self.__isUfw: + self.edit_ufw_rule(address, protocol, port, type, source_ip, source_protocol, ports, types) + elif self.__isFirewalld: + self.edit_firewall_rule(address, protocol, port, type, source_ip, source_protocol, ports, types) + else: + self.edit_iptables_rule(address, protocol, port, type, source_ip, source_protocol, ports, types) + + # 新加代码----- end + + # 端口防扫描 --- start + def _get_server_lists_scan(self): + """ + @name 获取服务器常用端口 + @return: + """ + return { + "sshd": "{}".format(public.get_sshd_port()), + "mysql": "{}".format(public.get_mysql_info()["port"]), + "ftpd": "21", + "dovecot": "110,143", + "postfix": "25,465,587", + } + + def get_anti_scan_logs(self, get): + """ + @name 获取防扫描日志 + @param get: + @return: + """ + get = public.dict_obj() + server_lists = self._get_server_lists_scan() + result_dict = { + "currently_failed": 0, + "total_failed": 0, + "currently_banned": 0, + "total_banned": 0, + "banned_ip_list": [] + } + + import PluginLoader + for key in server_lists: + get.mode = key + + logs_result = PluginLoader.plugin_run('fail2ban', 'get_status', get) + if type(logs_result['msg']) is dict: + result_dict["currently_failed"] += int(logs_result["msg"]["currently_failed"]) + result_dict["total_failed"] += int(logs_result["msg"]["total_failed"]) + result_dict["currently_banned"] += int(logs_result["msg"]["currently_banned"]) + result_dict["total_banned"] += int(logs_result["msg"]["total_banned"]) + result_dict["banned_ip_list"] += logs_result["msg"]["banned_ip_list"] + + # return result_dict + return public.return_message(0, 0, result_dict) + + def get_anti_scan_status(self, get): + """ + @name 获取端口防扫描 + @return: + """ + plugin_path = "/www/server/panel/plugin/fail2ban" + result_data = {"status": 0, "installed": 1} + if not os.path.exists("{}".format(plugin_path)): + result_data['installed'] = 0 + # return result_data + return public.return_message(0, 0, result_data) + sock = "{}/fail2ban.sock".format(plugin_path) + if not os.path.exists(sock): + # return result_data + return public.return_message(0, 0, result_data) + + + server_lists = self._get_server_lists_scan() + s_file = '{}/plugin/fail2ban/config.json'.format(public.get_panel_path()) + if os.path.exists(s_file): + try: + data = json.loads(public.readFile(s_file)) + if len(data) == 0: + # return result_data + return public.return_message(0, 0, result_data) + + for key in server_lists: + if key in data: + if data[key]['act'] != 'true': + result_data['status'] = 0 + # return result_data + return public.return_message(0, 0, result_data) + + result_data['status'] = 1 + # return result_data + return public.return_message(0, 0, result_data) + except: + pass + + # return result_data + return public.return_message(0, 0, result_data) + def set_anti_scan_status(self, get): + """ + @name 设置常用端口防扫描 + @param get: + @return: + """ + scan_status = get.status if "status" in get else 0 + param_dict = { + 'type': 'edit', + 'act': 'true' if scan_status == 1 else 'false', + 'maxretry': '30', + 'findtime': '300', + 'bantime': '600', + 'port': '', + 'mode': '' + } + server_lists = self._get_server_lists_scan() + _set_up_path = "/www/server/panel/plugin/fail2ban" + _config = _set_up_path + "/config.json" + if not os.path.exists(_set_up_path + "/fail2ban_main.py"): + # return public.returnMsg(False, "fail2ban plugin is not installed") + return public.return_message(0, 0, "fail2ban plugin is not installed") + + if os.path.exists(_config): + try: + _conf_data = json.loads(public.ReadFile(_config)) + except: + _conf_data = {} + else: + _conf_data = {} + + import PluginLoader + + # if scan_status == "1" and PluginLoader.plugin_run('fail2ban', 'get_fail2ban_status', get) is False: + # get.type = "start" + # PluginLoader.plugin_run('fail2ban', 'set_fail2ban_status', get) + + for key in server_lists: + tmp = param_dict.copy() + tmp["port"] = server_lists[key] + tmp["mode"] = key + + if key not in _conf_data: + tmp["type"] = "add" + else: + tmp["maxretry"] = _conf_data[key]["maxretry"] + tmp["findtime"] = _conf_data[key]["findtime"] + tmp["bantime"] = _conf_data[key]["bantime"] + + tmp = public.to_dict_obj(tmp) + + PluginLoader.plugin_run('fail2ban', 'set_anti', tmp) + del tmp + + # if scan_status == "0": + # get.type = "stop" + # PluginLoader.plugin_run('fail2ban', 'set_fail2ban_status', get) + + public.WriteLog("Port Scanning Prevention", "[Security]-[System Firewall]-[Set Port Scanning Prevention]") + # return public.returnMsg(True, "Setup successful!") + return public.return_message(0, 0, "Setup successful!") + + def del_ban_ip(self, get): + """ + 删除封锁IP + @param get: + @return: + """ + get.ip = get.ip + import PluginLoader + server_lists = self._get_server_lists_scan() + for key in server_lists: + get.mode = key + PluginLoader.plugin_run('fail2ban', 'ban_ip_release', get) + + # return public.returnMsg(True, "Unlocked successfully") + return public.return_message(0, 0, "Unlocked successful!") + + +# 端口防扫描 --- end111 + + +class firewalld: + __TREE = None + __ROOT = None + __CONF_FILE = '/etc/firewalld/zones/public.xml' + + # 初始化配置文件XML对象 + def __init__(self): + if self.__TREE: return + if not os.path.exists(self.__CONF_FILE): return + self.__TREE = ElementTree() + self.__TREE.parse(self.__CONF_FILE) + self.__ROOT = self.__TREE.getroot() + + # 获取规则列表 + def GetAcceptPortList(self): + try: + mlist = self.__ROOT.getchildren() + except: + mlist = [] + data, arry = [], [] + + if len(mlist) < 1: + return data, arry + + data, arry = [], [] + for p in mlist: + tmp = {} + if p.tag == 'port': + tmp["protocol"] = p.attrib['protocol'] + tmp['ports'] = p.attrib['port'] + tmp['types'] = 'accept' + tmp['address'] = '' + elif p.tag == 'forward-port': + tmp["protocol"] = p.attrib['protocol'] + tmp["port"] = p.attrib['port'] + tmp["address"] = p.attrib.get('to-addr', '') + tmp["to-port"] = p.attrib['to-port'] + arry.append(tmp) + continue + elif p.tag == 'rule': + tmp["types"] = 'accept' + tmp['ports'] = '' + tmp['protocol'] = '' + ch = p.getchildren() + for c in ch: + if c.tag == 'port': + tmp['protocol'] = c.attrib['protocol'] + tmp['ports'] = c.attrib['port'] + elif c.tag == 'drop': + tmp['types'] = 'drop' + elif c.tag == 'reject': + tmp['types'] = 'reject' + elif c.tag == 'source': + if "address" in c.attrib.keys(): + tmp['address'] = c.attrib['address'] + if "address" not in tmp: + tmp['address'] = '' + else: + continue + if tmp: + data.append(tmp) + return data, arry + + def recombine_rules(self): + ''' + 重组防火墙规则,将tcp和udp端口相同的规则合并111111 + @return: + ''' + firewalld_rules = self.GetAcceptPortList()[0] + tcp_rules = [] + udp_rules = [] + for rule in firewalld_rules: + if rule['protocol'] == 'tcp': + tcp_rules.append(rule) + elif rule['protocol'] == 'udp': + udp_rules.append(rule) + + result_rules = [] + + for tcp_rule in tcp_rules: + for udp_rule in udp_rules: + if tcp_rule['ports'] == udp_rule['ports']: + if tcp_rule['types'] == udp_rule['types']: + if tcp_rule['address'] == udp_rule['address']: + if tcp_rule['protocol'] != udp_rule['protocol']: + tcp_rule['protocol'] = 'tcp/udp' + udp_rules.remove(udp_rule) + break + result_rules.append(tcp_rule) + result_rules.extend(udp_rules) + return result_rules + + +class Sqlite(): + db_file = None # 数据库文件 + connection = None # 数据库连接对象 + + def __init__(self): + self.db_file = "/www/server/panel/data/default.db" + self.create_table() + + # 获取数据库对象 + def GetConn(self): + try: + if self.connection == None: + self.connection = sqlite3.connect(self.db_file) + self.connection.text_factory = str + except Exception as ex: + import traceback + traceback.print_exc() + return "error: " + str(ex) + + def create_table(self): + # 创建firewall_new表记录端口规则 + if not public.M('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_new')).count(): + public.M('').execute('''CREATE TABLE "firewall_new" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "protocol" TEXT DEFAULT '', + "ports" TEXT, + "types" TEXT, + "address" TEXT DEFAULT '', + "brief" TEXT DEFAULT '', + "addtime" TEXT DEFAULT '');''') + public.M('').execute( + 'CREATE INDEX firewall_new_port ON firewall_new (ports);') + + if public.M('firewall_new').count() < 1: + # 写入默认数据 + if not public.M('firewall_new').where('ports=?', ('80',)).count(): + public.M('firewall_new').add( + 'ports,brief,addtime,protocol,types', + ('80', 'Website default port', '0000-00-00 00:00:00', 'tcp', 'accept') + ) + if not public.M('firewall_new').where('ports=?', ('21',)).count(): + public.M('firewall_new').add( + 'ports,brief,addtime,protocol,types', + ('21', 'FTP service', '0000-00-00 00:00:00', 'tcp', 'accept') + ) + if not public.M('firewall_new').where('ports=?', ('22',)).count(): + public.M('firewall_new').add( + 'ports,brief,addtime,protocol,types', + ('22', 'SSH remote service', '0000-00-00 00:00:00', 'tcp', 'accept') + ) + try: + _panel_port_file = '/www/server/panel/data/port.pl' + panel_port = public.readFile(_panel_port_file).strip(" ").strip("\n") + except Exception: + panel_port = '8888' + + if not public.M('firewall_new').where('ports=?', (panel_port,)).count(): + public.M('firewall_new').add( + 'ports,brief,addtime,protocol,types', + (panel_port, 'panel', '0000-00-00 00:00:00', 'tcp', 'accept') + ) + + # 创建firewall_ip表记录IP规则(屏蔽或放行) + if not public.M('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_ip')).count(): + public.M('').execute('''CREATE TABLE "firewall_ip" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "types" TEXT, + "address" TEXT DEFAULT '', + "brief" TEXT DEFAULT '', + "addtime" TEXT DEFAULT '');''') + public.M('').execute( + 'CREATE INDEX firewall_ip_addr ON firewall_ip (address);') + + # 创建firewall_trans表记录端口转发记录 + if not public.M('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_trans')).count(): + public.M('').execute('''CREATE TABLE firewall_trans ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "start_port" TEXT, + "ended_ip" TEXT, + "ended_port" TEXT, + "protocol" TEXT DEFAULT '', + "addtime" TEXT DEFAULT '');''') + public.M('').execute( + 'CREATE INDEX firewall_trans_port ON firewall_trans (start_port);' + ) + + # 创建firewall_country表记录IP规则(屏蔽或放行) + if not public.M('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_country')).count(): + public.M('').execute('''CREATE TABLE "firewall_country" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "types" TEXT, + "country" TEXT DEFAULT '', + "brief" TEXT DEFAULT '', + "addtime" TEXT DEFAULT '');''') + public.M('').execute('CREATE INDEX firewall_country_name ON firewall_country (country);') + + # 创建firewall_domain表记录域名规则(屏蔽或放行) + if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'firewall_domain')).count(): + public.M('').execute('''CREATE TABLE "firewall_domain" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "types" TEXT, + "domain" TEXT, + "domain_total" TEXT, + "port" TEXT, + "sid" int DEFAULT 0, + "address" TEXT DEFAULT '', + "brief" TEXT DEFAULT '', + "protocol" TEXT DEFAULT '', + "addtime" TEXT DEFAULT '');''') + public.M('').execute('CREATE INDEX firewall_domain_addr ON firewall_domain (domain);') + + + # 修复之前已经创建的 firewall_domain 表无 domain_total 字段的问题 + create_table_str = public.M('firewall_new').table('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_new')).getField('sql') + if 'domain_total' not in create_table_str: + public.M('firewall_new').execute('ALTER TABLE "firewall_domain" ADD "domain_total" TEXT DEFAULT ""') + # 修复之前已经创建的 firewall_new 表无 domain 字段的问题 + create_table_str = public.M('firewall_new').table('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_new')).getField('sql') + if 'domain' not in create_table_str: + public.M('firewall_new').execute('ALTER TABLE "firewall_new" ADD "domain" TEXT DEFAULT ""') + if 'sid' not in create_table_str: + public.M('firewall_new').execute('ALTER TABLE "firewall_new" ADD "sid" int DEFAULT 0') + # 修复之前已经创建的 firewall_ip 表无 domain 字段的问题 + create_table_str = public.M('firewall_ip').table('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_ip')).getField('sql') + if 'sid' not in create_table_str: + public.M('firewall_ip').execute('ALTER TABLE "firewall_ip" ADD "sid" int DEFAULT 0') + if 'domain' not in create_table_str: + public.M('firewall_ip').execute('ALTER TABLE "firewall_ip" ADD "domain" TEXT DEFAULT ""') + + # 修复之前已经创建的 firewall_country 表无 ports 字段的问题 + create_table_str = public.M('firewall_country').table('sqlite_master').where( + 'type=? AND name=?', ('table', 'firewall_country')).getField('sql') + if 'ports' not in create_table_str: + public.M('firewall_country').execute('ALTER TABLE "firewall_country" ADD "ports" TEXT DEFAULT ""') + + def create_trigger(self, sql): + self.GetConn() + self.connection.text_factory = str + try: + result = self.connection.execute(sql) + id = result.lastrowid + self.connection.commit() + self.rm_lock() + return id + except Exception as ex: + return "error: " + str(ex) + + +sql = """ + CREATE TRIGGER update_port AFTER DELETE ON firewall + when old.port!='' + BEGIN + delete from firewall_new where ports = old.port; + delete from firewall_ip where address = old.port; + END; + """ +s = Sqlite() +s.create_trigger(sql) diff --git a/class_v2/safeModelV2/freeipModel.py b/class_v2/safeModelV2/freeipModel.py new file mode 100644 index 00000000..a7762a98 --- /dev/null +++ b/class_v2/safeModelV2/freeipModel.py @@ -0,0 +1,97 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: cjxin +#------------------------------------------------------------------- + +# 免费IP库 +#------------------------------ +import os,re,json,time +from safeModelV2.base import safeBase +import public + + +class main(safeBase): + _sfile = '{}/data/free_ip_area.json'.format(public.get_panel_path()) + + def __init__(self): + try: + self.user_info = public.get_user_info() + except: + self.user_info = None + + def get_ip_area(self,get): + """ + @获取IP地址所在地 + @param get: dict/array + """ + ips = get['ips'] + arrs,result = [],{} + for ip in ips:arrs.append(ip) + if len(arrs) > 0: + data = self.__get_cloud_ip_info(arrs) + for ip in data: + result[ip] = data[ip] + return result + + + def __get_cloud_ip_info(self,ips): + """ + @获取IP地址所在地 + @得判断是否是我们的用户 + @param ips: + """ + result = {} + try: + ''' + @从云端获取IP地址所在地 + @param data 是否是宝塔用户,如果不是则不返回 + @param ips: IP地址 + ''' + data = {} + data['ip'] = ','.join(ips) + data['uid'] = self.user_info['uid'] + data["serverid"]=self.user_info["serverid"] + #如果不是我们的用户,那么不返回数据 + res = public.httpPost('https://www.bt.cn/api/ip/info',data) + res = json.loads(res) + data = self.get_ip_area_cache() + for key in res: + info = res[key] + if public.is_local_ip(key): + res[key]['city']="Intranet" + if not res[key]['city']: continue + if not res[key]['city'].strip() and not res[key]['continent'].strip(): + info = {'info':'Unknown IP'} + else: + info['info'] = '{} {} {} {}'.format(info['carrier'],info['country'],info['province'],info['city']).strip() + data[key] = info + result[key] = info + self.set_ip_area_cache(data) + except: + pass + return result + + + def get_ip_area_cache(self): + """ + @获取IP地址所在地 + @param get: + """ + data = {} + try: + data = json.loads(public.readFile(self._sfile)) + except: + public.writeFile(self._sfile,json.dumps({})) + return data + + def set_ip_area_cache(self,data): + """ + @设置IP地址所在地 + @param data: + """ + public.writeFile(self._sfile,json.dumps(data)) + return True diff --git a/class_v2/safeModelV2/ipsModel.py b/class_v2/safeModelV2/ipsModel.py new file mode 100644 index 00000000..1fc95238 --- /dev/null +++ b/class_v2/safeModelV2/ipsModel.py @@ -0,0 +1,118 @@ +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +jh3cxzkA2htccqfZRKAUdI8r17q57nOGP4OxbJlL1NAnrF4weHnS0MpT6C6jbrX5 +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +rUv3hasppUA6pOugYcQZyA== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +iEprTHe70MI/8Rhzd8EK+QnVQn6aZyZ8dBODiF5pySg= +P1nWGQOfbECkszATyvUnYMoMB+zOtupYIF89n5X+cOkvqsM/qxPIR+8JRAch8USo +XIfdJ79nObMM+vyAmKbTmw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +hbDorme8BqzUEmeXCAWmYa52umm3PBCcfSvATzTG02w= +1u+XjG/2+GSQRv6EzCaWRQ== +giysigZ4bhExZB4emZkX3a9fe6PGGMNE9ZZnk7xPqW8QW2Ij1yNFf5XEj5pPPlMwdhA6dIcO2ZX23qJszvnq1fcl6NckihyZ+Uf83rbYjeQ= +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +b4OJVZe8QyIpjuTpKXDL9A== +32pdC9DD05OE2l0oXazDFI+hlwIzzkFTo7tZH3axSGoKNgh7EevAzaB+FO2ZLVS/maKOtFAjIeKErPBj7dsfmg== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +32pdC9DD05OE2l0oXazDFOQO8mg+tgs322u5NSxm1hnE+dYVd/hkfCsat3ixeRAk +1u+XjG/2+GSQRv6EzCaWRQ== +Fq/4dKD9ssEKrNEHrSuygxu04P7BPF8PboiRMtr4FMM= +Z8UsPk1Q7HtwjRd4g01ryw== +KNy3a/ETENhyr8ymSkKe9l1RX/SenKsGMtRp3/Nl0CxdpK2w2cyo43SfPqzKitUD +ho/Q+jrWDtBeg9J9ZTnKi7MMSdcDWkuDSRbkOa0slC0= +Z8UsPk1Q7HtwjRd4g01ryw== +1aJ5h9ef6qScYRSEXHxMz/JV+hrqnP7g6CgzmGbTA34= +CH5utP+NORdjI2nqATw4gJ30bQaw4oV4TkWtZlCiO9A= +lbIj6ug3LX3xS019kmbRSTcfm4XASPCYnVO8MD2z14s= +iZaIsa48RXfE+uf/yF/rQD9SK8CJ49+yPAMIKmMZPD4= +lOh2GtzHjjMM8E9J40AuOc+/vc1yhUL+xJx/Mivlb25pg5HBJ1HJ91Rfq30bJq9S +UbJuac0dxLiN+5ocS3w2vRp92UK1M7Voei4ApHZyTgY= +VmVrGQo2zRokW/ZuO9bN69BDpCzHoJtTjTgNlAOe5sUQUaSnZ1Z1hl5M9Ym+7tCG +VmVrGQo2zRokW/ZuO9bN61V3/TpT/zrd2QvdUtMgGKRq8f0CD0RNnZ4rTqSInsSj +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN657tYrblt+z3q06zgiQZcYA= +VmVrGQo2zRokW/ZuO9bN66aFKP1IdEjWKaMooH0pyYReSxgrF6gfc+R+CrZAggrY +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN69KBqHrgU1XKz6C1sxuKNo2/9c/EcmFt/gfo+iaOu2+K +1u+XjG/2+GSQRv6EzCaWRQ== +n+FHKmWLbioNpC38yMj4WmiXmXqyLuzKUmIAM1Ft5eM= +NhnIR3Ilo4H2su9/cTNo/MME7jTfPzQcknP67wyItNzcCacoNjdvCuiW0x8udsSO/edftRARPJ4xwm1wQrVT2A== +wlLHv6kT3Q/RmtMBN4nDATwL/9Oa0sOvSxwVwQfq99U= +VmVrGQo2zRokW/ZuO9bN68Jg91voF9Ce3nf2o3e+jkofqu9SSuRXcnpG7smrmLLo +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhMRKs67DB9ZOIiBDrB02YSQctSNS1aqQlPvqVprQ2WDG +Z8UsPk1Q7HtwjRd4g01ryw== +fXZfnC6cxxgiN+onu+xJ1wUhirDdf4kByrL5vhQHFnCYKlzy/eVgdjzy55WiEFTz +LikBEoRjt8wwWzUZNw+jJgsad0LKWFNZK+YO+SfR6UU= +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +W3A5Dt+8AxWSKsTYeXZoL+tHA8UsZgQuAUkDUOPJ9p9irMcLKjcsuwS2q6lHXAKR +L0eUthVnpkGsmKFAX6d+uOlJx8vHVMiotg8sk086vHQ= +TQRmC0vOvJ1P+lfyS3Os4X1xcTKiQ06lXIJEwRwWbq4ieECEmB4BmDhbitv1CsLg +L0eUthVnpkGsmKFAX6d+uLqKlmsh+FpVhvWy0B6mjhM= +1u+XjG/2+GSQRv6EzCaWRQ== +YabJCT93a3/IohXaIkr3oS6/BpCE446GJgDFFvo/yjjYAG9E6229ssUIWmf519v1 +hBWY30cr8RshUe3F5HK1cRZ9KmhsMz0lNR6pLxg9eoM= +L0eUthVnpkGsmKFAX6d+uFd45MXPp4WlUdlXMx7fhE8= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +avrdBvQ7h/91pEC4PcvqMSR2TJ0t+8EMPSsnARb+XVHHkBrQO1HsCg6QcbTZvZ2W +Z8UsPk1Q7HtwjRd4g01ryw== +KNy3a/ETENhyr8ymSkKe9l1RX/SenKsGMtRp3/Nl0CxdpK2w2cyo43SfPqzKitUD +LikBEoRjt8wwWzUZNw+jJpxJaQXGUN8g/Hr0khQ9A1Q= +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +b4OJVZe8QyIpjuTpKXDL9A== +1u+XjG/2+GSQRv6EzCaWRQ== +NhnIR3Ilo4H2su9/cTNo/F9IByyW2CHxX2+3PSbgZmE= +NhnIR3Ilo4H2su9/cTNo/JEWIdEQUX/MlMcOaFZ8tGgjCfBcJNHhmedart7PDRhF +NhnIR3Ilo4H2su9/cTNo/E4e/PRk3DEtgyjsUDeOpSOwGpZehbHYaLN6kP4SrhN+PFbXCI6E8Z+8865ULB7IOw== +NhnIR3Ilo4H2su9/cTNo/C/mkLay+Rx5WJjwGcYV7pSktyrOzy78W2NcadJ49lO1rNrUNnnzcDMri5NWEH6lGQ== +lOh2GtzHjjMM8E9J40AuOVVeyoh6rSy98QMdDBotkjU+CpzMJCQMfJrCkE4uEcQAgmW0jhOnZHI5mtglpnoAuAijuP+2lAOsmb6+b+801pIjLK0hXXo2u4rmG7jkFs3R +lOh2GtzHjjMM8E9J40AuOeMCiZGQsphriaBgvIdFiyOx9f7gw874ih7YoSVMDwrD +1u+XjG/2+GSQRv6EzCaWRQ== +NhnIR3Ilo4H2su9/cTNo/A5SzFv4xHkReni2nLCW9wf/+pP3k795kKoMZAQXwXD6 +wlLHv6kT3Q/RmtMBN4nDASCRBoPIX3WHiVt75UTjznI= +VmVrGQo2zRokW/ZuO9bN63WVsNPEwwprYYkAUqx5H2WQM74qCCEzrB3qJy/Mpn1CTpqeZGmRvqbYKmI/uSdTZw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61LJQniFugoi6x2ujjnJM7tldXKimSA8e3cVKdvY80W2 +VmVrGQo2zRokW/ZuO9bN64CPlXy2HmOeksJNoNOmCTRN2iWrExIMUdvTGpj/NNWq+ALT/bwKyDsjcfkGB9eQcrQ3H8UC+ywFIkXx32JcLCKuolLfVZKLqPTsQHTpYF9P +VmVrGQo2zRokW/ZuO9bN6yDw6frMtVHHQaPzsZSKK0aES6D4Kud9zFd68X/jO6wJ +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN61V3/TpT/zrd2QvdUtMgGKQFi9VDtwL4Vc83iegeMAft36zpJ+t/eeWtmA4Eamfb77jKlCL/2MWCO4n+tmFfKcs5bo8I5tFr5Qu31v4FEN0K0c0Au7xRuJmPMknxTzJ1AQ4L8Z8QbLuQ3EJTDAgG6zw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wKjnvaTwlMeHWSuJ/EAxZ8Z1KtkmFIaU/d9KNNMifcN +VmVrGQo2zRokW/ZuO9bN62ExR0OHxzNY3oC4Mfi694VYJyilbRPMd6JDlCBDdbCe +32pdC9DD05OE2l0oXazDFLdmyVEUr33LQ7qI5CZQN57igfVF4gan9s5C0Jnc3Ki1 +6ZPJI/HSoc4xA2zncU65FpfH+eGtPlOYNU1YCnnAis8= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Fq/4dKD9ssEKrNEHrSuyg9mm2ubmmEvtKER/ZGhfGKuM0YKPX8D/je6PexvVvgfs +Z8UsPk1Q7HtwjRd4g01ryw== +KNy3a/ETENhyr8ymSkKe9l1RX/SenKsGMtRp3/Nl0CxdpK2w2cyo43SfPqzKitUD +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +Z8UsPk1Q7HtwjRd4g01ryw== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +b4OJVZe8QyIpjuTpKXDL9A== +NhnIR3Ilo4H2su9/cTNo/Ps3vYSOiOY6esn1xeoWmAlmLwmaOoQ9T7lnCb6TRVZ67prVtxc96eRcwG0EieQAHA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +xWoGNWjKGPfI4gq8aHoTfBq1oaYa956TFzYuHTbqc+nhmNgO2Yqkfbla+bgPzSp/ulULnjCxA+skRNBtWTg0BA== ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +0jp9NuG0oWLkMfJ/MsYOmfu7WkSnZjHksXr6BBYLZSUOzHDXl9XjsxiUKNVbrHEG +Z8UsPk1Q7HtwjRd4g01ryw== ++kAMkUztiJJUlfW5H0qbp+x6TgEEQKPe4CNld5ATADH0jjvxLnFDBqMEQxxyT+xg +vPgTNKnHCM+ykPDJGfC5NIdfn4GuPGcMgxOHmcFE4sA= +Z8UsPk1Q7HtwjRd4g01ryw== +IhuisKim47k91RVt8z8qtK4Hi8QoBKGCIdLYuWbuvVULY/yxOc14YsJd+ymvu1M2ua6mSguBgb6gPVMjpVflaQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= diff --git a/class_v2/safeModelV2/securityModel.py b/class_v2/safeModelV2/securityModel.py new file mode 100644 index 00000000..b99a503f --- /dev/null +++ b/class_v2/safeModelV2/securityModel.py @@ -0,0 +1,1691 @@ +4wZIF38E5nxrLYkW2uUR7d3iaGQLSTjR4DuJynwT7k0= +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +dBZyCsfrbwqvA0sbdGrIGg== +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +n+0ptngHIPIjFuMNQ53bfryjWeDQdwMlvn4xPuJO5u+Z2ORjG7RQlZx91meRqdmMVOBP9Rmyv3YsNRyjdE2DJuPBBv+kSws1+u5OFq4s9Vo= +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +i5dtmtvJAakTQ4h3th/Bg/k+ogi7UBuwCMMdkJp2H/ABwRFxrb4nYZGwElANdaIy +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAa23LCoGr2YlDYZYC1tcZk6mnFUnSSAMkOAkjmcftNoKhCg3JPMIJRBsMXbngZMA3Q= +1u+XjG/2+GSQRv6EzCaWRQ== +QEXWR1+ijYfZcVT7Jxgn9b/1qODi2lleSOUlnjby8yCki/rkQucI6WIYSwMiN5/Z +XqBLRXNa/0V7439P+rc6hEwNOx43wLjN61FZ6E2obAbFx1CPChpgx6xlsqPUrTBV +/hGh1tb/B6hRHKE+/g3LdcoNHYeGiE8itwmNgTlyaS8= +SbQQ5SqO9QrBwZ9ObpMYLw== +1u+XjG/2+GSQRv6EzCaWRQ== +D0dsg8rStGc6knFsZy6SyBGy0nmeaHYzoYKgGu7QIjA= +sRPdbbdTaQFxLkgXulRHBHxNJMNNOY69Hqrbr+c7JKc= +WVZHNW0sF72nCINduLgk2oCijQvdyFvHnftiwPgbI1o= +P1nWGQOfbECkszATyvUnYMoMB+zOtupYIF89n5X+cOkvqsM/qxPIR+8JRAch8USo +rpy88ff5JnVuJqXSNlYf+CQ4VKHExseR2LosMAdYEeHGNm80vDZH+2U6FEAnuapk +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +hbDorme8BqzUEmeXCAWmYa52umm3PBCcfSvATzTG02w= +1m0JCmsSm1bWc5jyHCVv6Cnrr9E+yNmZlb1WibISmGbr+KMdKOT/9t43CBeYx5Mc +xJmvc/WjX5ZL7/2nnJq3MuPp0h21tahzXjft6G3i4wA= +OjvdpCJPmEE/XXQgxX6Q24fmhYnAOnqgciYGQvGkzouXZiBeHrHfoFUV4jYH3VEvsLAIpicQnJ6upUuxH++tCQ== +Pk6vNT/ApTXrbpdwgZfTzevfF7XV6fOgcuiG9Z1yvQU= +2R9NYztgl9lLDOzubkvxEV6cK6WrhYbB1T56PdZLuuHhbNjHFVl740pepm61uO3JkeqD0S6IXZYSllKQKR0sbza6F7w0qyZA/ZvlpU8Xpd5OrZURZQl8icGs4TiJvjd+ +aefSBGgeVI8UGNoyrQSBRfmVCVIDHP8GpI8mqMUyykdnPz4dml7AVkuruY4OmLvPIpB69geoHjAHaUF0XjdTm7meTUUu9x/6OX7szGQ6fX0PRtKB+8ysX5M3W7iEm572MkNBJ65MMNxDypeJiTv2kFa/BVQmUgZuooWqwii6XQKOVGUIv+98RZxlm6zjVnAD +Z4pQntO7b1+TL5Qz65Fz5zriYgn2ylWcQkZg4u1EWj4jHgcNFqKUv7EgQUHg42sI +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +W+Nfmvg8yIH+Ez2ByJPwEuRsNvYcoPFkZ3RWm/lu7+xcxc+Ys2eYbEO1gFL92OPb +8Aj3fakGh9c+1bfJQURlxSYFVwepqRq9sYBTYz2BfDs= +58w29u4j3WzehOmWB7rm9YG5zqbZp63232wfcqK8b/HRchMGw7R3evjgobd7vgm0JeHu+mrOAZSJQH8cfxJ2lofJu0lYkVVJPeACgd1Qn78= +zgKkJRNMCn8GCiKimPAwiS6Eh5QvNeiufZlJW0pVFOCDTi8Zq0UjIqdrcCYoKZBKrXH/0J+OG6Q9qMcVo+7QExcods3k0ZKFFS84IvfuuLl8iHj5uJJlzgbhhyFVu9/sCz1h/jxQpRl5r8nx7xfBpalTSL6FvFvJsUdubtLHW24= +8CjvdUzUhFR4VHs3tQRSSwYiHBlxTz6PjSFhA3FUgYqT1zzri2hRFYa/xrlfgQBo1FYnA4pxFvpNU3l8P3TBOehLOqu9HRpz2/ZdUhRLmyM= +IwNSi3B49UePWkec9h2aViQNaJUtCr9Fg/zONZJ7BAHhAsPlhlhiTSxbbZt7qBzHuRXA8ssbgkncTWfewCFCSg== +IwNSi3B49UePWkec9h2aVgOML7jzMqS//KVIa6H25JlhxzwLYUXHhh9ZWyM9VxIU +IwNSi3B49UePWkec9h2aVu9+rAVrv2IeJy5ZA+9BD24UnRRiP78d4Ay8RJEgtlD2 +IwNSi3B49UePWkec9h2aVlW8jH99OssjKYRGL6FWV964jAUW5pxd8jCuWDX8dtdA +IwNSi3B49UePWkec9h2aVvJSXqZVYn+ZKcZ+eqKPth235+6nSxcTSEEjMq/cEdOq +IwNSi3B49UePWkec9h2aVuswOY+QUwCBRWJFqAEl89arDwaEbUyDQXH1w3CtPXfb +IwNSi3B49UePWkec9h2aVrKxz33p+NgrbHjI2f68/l0= +xZ9u1heNQTI58qBqs7KOylcOEoGYAm9wm2Pc3fOQ0cJLZYrlXYJw2ARn9LsMw+7x +1u+XjG/2+GSQRv6EzCaWRQ== +mlGJWwQAi41HKr7j70v3zhszuDsZD3HZNEjtkStFCvaGWj5rbui1QRSx2O28AJAD +KZTmaJLp+FU9X93j5Tpqtw== +onBbf8l/M7QFwfZbFXfWhYgF0MmzmYL20H6FmpRmhbVRUdjnkUi/v3dYWgjVOc60 +ho/Q+jrWDtBeg9J9ZTnKixi1K9oP2iYTOwPxyGNJHuD9qiXINrmWXThQlfvTzpE9ciXrbN9G/qMSY3MMkCKL/5/nFme7bdfFltbItJiFd8RYpPELl34HLaIyxxkMZIKJ +WuRvIBaPNRsVGEMoM4NSTj9okWpIPDBwXYlx0ptZRL/htKAkfBzMha+fobGCSqWT +KZTmaJLp+FU9X93j5Tpqtw== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6+4zVF4aBgZVa7yqS9yu7Z6BodAkPc4fSu07VpQm9LQX +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs9hz49lIM9QDOgdKn6cpsbvJLdc46vLcM1bYfBB2uH1ZN96pfFNt1UywexVY6Eg3nQ= +nz85yNtnEmjM5Lt96/cqExqhhOV1bjZ7LXcnfSnc2mQxUr3BlqzDO/LhVuFObkBAuW5KUUj8ijP08AWgaDHjtQ== +JDFeI9M1ZJLkjtjDVE9IMPiP1kqYwJdmJMKGfC47oA7Kp+dmwuvb9PEetQRbd6lRwlRirecZpfXrJocTJxgu+WHDfEg7JRIGK0obV3ES5iUNkXsHfXND5u2JNIYiNCu3 +2+RceSNjD2iq0sRzepMysVUxXE5N/Z4aOUZt1dAV3sWaDWXqIfjhxQTvYnq4ah3QZj0WoRcONsWRupa+3ejbEBRYVH5nwvT9JMx7WrOcBonEe8X2OOlNs5jKQwIqSNFYdXIWehQc0oVw/jQL1cNoycRLhmLrMIFuEjNpmpdc+KxN55kgYP7/iCNcqdgh+UMEKF4Ua10x33s2VaYGF8BIsQ== +NjzfvS0PyiH/YLJl8s484uWnGRrJKl44YUVPdHIyTzs7zCpdD7dKmGCibl9hUZ4X +XgacqF8d3I7m81jvrWCZxEbPrRTJ4dccIpuqFLLKM3SrDnEjcEI5CZs2Wxa6jAbiAtaZXoIMixtLIkqYZtB1Xby8dmoJWlCtJymRxQbLhFqNfEzgbaQXN4NcNeCSDglY +D5IA5Wx3J15yXUzPYlpr3mf5+WwRzHJeA8ARYLZ4caU= +w9DemAtCZ0Ko6Anoc5h4VW7y7dUeT8/wcW1sU8fqHKOgYZ9pl2XvvKLCvcl6CyW8 +zI8euyep3wFTTSveCidutgleCpP4XPHma8hywAp0dMtSW6RT3fEFTSUppl2kz6zL +n4ws3AetOdjkVQ7W9sbFyIb1VMlkK8TIfGjugWfYcosSyIq6C5dXbLvQ9I1T7BHT +1WrybT4InUxTVVQ5qhmrwoPkhYSV3eIyimLs+wiMJF5L1U7FlpulsmbfN84+/4mu +PNZcZ67j5O9c3iqbWgpxu5nKlQozrHwjVzIdJAvGVYEoNvdnI3rSIqCHs17t7HS9 +dV9xz3yyeXnMinI4Y9Bh+g8oAw4fkxA8V1c13xBw/CUGBZNtriVcNDYJYGckmm8W +VmVrGQo2zRokW/ZuO9bN64DKxIwe3TA0ZLk66JmNQXsifOH5RrSbRDqQwbzEts/g +F9Sftf1PoN5P+lgvc+r10FBMPK3gvX66Cu6ezHPjSU1/y3nolrnayo5Gf/xX5Vj2sKxQdIO0cNrptiMHaAQ1BjubdHD6cPNhAtbjhwvNK+s= +VmVrGQo2zRokW/ZuO9bN6/hr1K0eBkvC9NUF3b0MUUJ3i6RzmoS9/63Bct21USwa +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6xi6Ahf4/3nDVsR0SZow43cnpBa1vPhNrgBa4z4semwK +5STPzn/BifKzw7c0kQHkfSIlGB7jcmAM5JZZKFm0TG02Opdmx8Z2gVax2S6s4Ylu2ZVwcsrSjAEy9iImRgcXMAwMPli6wqYNTMxXqBNprJ6xy/E1th9i37Nha8qdi87G +NjzfvS0PyiH/YLJl8s484nsOAbYHJry53idXgVQiuUWbrWeQqcaxytCuRIeFNu+I+LKqM8tQ6Loq3MAFvKviQRV+Ve9xKo+uITTOYC+AueeCEfOoBXHtmleK5nbGAQVC97OUZ41r4GQIxoDM38npc5PvoA0zaHU9sw8g4M3CgKM= +Du0z5l9SDrO/Iv1mHDRkV+2SoQzopuUElLrQ6UgQIpflAYRmn6+pjfyt1mlRpjXZocfLDB7jtns7FyPuEIErvw== +m5eWh32xFyyO8LCP2Vawm0LvIWVhsBJCQVLCzszazG0= +9tYtzqEz04IhMzB5yZnN6kcwmurGecELg77mlM5HHsI= +bJM2RTsGFKAbaqkE1Sj6R0Z6GTygMGuI5TmGACQgShE/Bl3RfPqENt2hnEeKv4pq +JbnXIK/axHVmA2plDNFCpEofHtK6A0JApZ48oEIOmf0= +yisP9bnsS89PFbBRJbtYnF8Fz028aCPQdjOlgUye+4A= +nuQO1if3lMKUHCYi1j17dS3Q/eaY/q+iWSoWyU0wzdY= +QzW09Oyj0g7smRjIbo1KUooEoyib3fJA7tjfk3GopNpDQnzVj7Gt04kf4xUrvLtk +661hZf7vhUQ+50okfwfTXw== +5VCCe6IJEmur5OrxSKQjRy3ztaMlZW5jXIYv4pxo+nOUSKQJT98C64IJZq+/tw86f97cK0yNmn7K8efC/fb64mhtL1iDKqOmaUGYe8G49BQupwMPZd7D/p0mG+CNrF69 +KAi6Ky2zICxpxofM/wpAf9uCC26kg1b5ddk95IooqzvsSbZHJRlqmcos6vwQ69s2 +e4wK/DYuOItHm+Ja22fo+265Gx45nnvVUa4C2H5Mgvo= +3W5ILeM0JZ9iXzlHvguO5B+mxh/pX62YGmjEidibBfUqEY0J6XYt+HvYkO9ai7VO +IwNSi3B49UePWkec9h2aVk4UXoXVaJ9qrpAMx0aJvDTMxPcHU3yDfwGWAyMNYBu6JE/+j+YKjUxEiCzOMioN3g+uvH10bqobqmys5DvAsXE= +IwNSi3B49UePWkec9h2aVv/ob4vTPqn27fqWQT45EsrgRZ6BzpV9qJ4b/c/ewvMcL03aVfS5RHI8WmnxVIzG4QI7WZvgVszoVPLcveGEZM3rZoVn3YHJamE+mmtY97n+776O0JaOcMZ27p5gSfUFk4c7VUAaTa5+w8aQlmYW1Oo= +IwNSi3B49UePWkec9h2aVjsT0WYGT/LbilqaR+VLAy+xTERHvM8GkYpvyw8eWpG4c3jJUw3KEIwjIxHeJEymIr96ZH6TCjLttgJpjl/KTeBViJAxQZ+SAtMNDVjxBgCY +IwNSi3B49UePWkec9h2aVmLpQ9q5D1+jKkSrtSZBN4Q= +IwNSi3B49UePWkec9h2aVi7anccYLXBKtuipT0ML+0U= +IwNSi3B49UePWkec9h2aVgawMbI8ZWX/Xthp5FWNpTm02Lkxd9v8ubMNX9x8JKDF +IwNSi3B49UePWkec9h2aVlUEpUR6OtqBi/qhu7NMaFTzGaGrows7DWabczrBMLMqx7UYjFxGeO5j5sISF6Wl8g== +IwNSi3B49UePWkec9h2aVuEWYmD9zZq7SqfctvdTuegHWnU7GtxmXsO6Ex+TYRsR +IwNSi3B49UePWkec9h2aVpDSHoWHblWlctFbb3T0wn7kT9dbfRWCMBBcKXvptGx6Iyj9VinRWXcAD7bzHBWaIzj2u3iflHj6jYLIc1iOd3Q= +IwNSi3B49UePWkec9h2aVqCItR/0Skx2rxI0FNlR3eTU8Mcy9auyJZRPKogRnHiUeXETFnehNdLAfDEraSiSPg== +IwNSi3B49UePWkec9h2aVrAdvpZovuYoEMvM76ppquahUOn+ZXzu/YvYlyw84LWp +IwNSi3B49UePWkec9h2aVs1nT9H8GPxL9ewV2R/PADe5mMoK+Rt8Fa4Rm5dp5IVC +IwNSi3B49UePWkec9h2aVkWqi+tO4rK1OQBy5RtqrDmx85Ll9vM58wEcCSvi/NNd +IwNSi3B49UePWkec9h2aVrsmuP8cJlGgy5uDNkRzP1oH1/0PpcrLleuVhRXqfWPiVvswjp3rvGyNSY1AT33Kr6cKPXwQg2UReoHOwoobyC9sGk0KxMTWqTwptw2R52m8+wr1UCePfU/w34PFNxYfiu6GT11WU2tu5RBT50NRK6I= +IwNSi3B49UePWkec9h2aVg/ONH6pQSdKZSA1IVlvWS/ar/yzE1xDkU2l0Ue3HYVETw3EV0tplvmq/j1TpJWUhQ== +IwNSi3B49UePWkec9h2aVtSXV3is472UB+EIIPIVVRDvrA5LpLJ7cy1YL59hpv7b +IwNSi3B49UePWkec9h2aVmmt5aJVr2D6g6kdfI46jiDateIEZYAW4ZJcvMPCZoFd +IwNSi3B49UePWkec9h2aViQu4nXoXYfJBDkVJG10Xjy3xnK5+ASnxqwekT8TTxQ+NIzIPuGf3QNp7DHSF+XGHALN6MLwF9pNj4IRhsIfWJHOwmsMpUYHWiplMeJBynyXlJO41+MQRg3WoUoWPdlTmzvY3Qn1KhI8NVlVo1DGyDI+cWuxKx7A3usGE1fX9Y/w +IwNSi3B49UePWkec9h2aVrAdvpZovuYoEMvM76ppquZ2XWBdd/QTtjqDVwiuUScLlEQKBmSwBjaesbSMBLuX4HmovPkJ4154JvQTeq8xWzQ= +IwNSi3B49UePWkec9h2aVtSXV3is472UB+EIIPIVVRDvrA5LpLJ7cy1YL59hpv7b +XAJ9ytHVbHM1Btk2P2iYeI0x8NHMILHjHS2x8djNkDxbw/0Fw47Iz1lwrR5xvvn7 +Tk9xlR7qdfcItfb2ZCawoa3gDrkaHoK+GdTFuR0DhNiseeLZWRRwq8iA//kQVb4I +mGjRXdtl0PckyTSYK5yVPc6XcHPw/lAYYguBn17flAo= +DPgKxKUksPJmBI8KfD7MnG19wQLsGqEChmw0QoF0cBEGNXse2EH2RoQjVnGDcYmdB0GUkNwww4YhQ8aieso8Hw== +DPgKxKUksPJmBI8KfD7MnHMEClnTrZOgWuhoya+UtfRGOBC1uS5ILl2CMZwpFEHoFGX1Hq6KjlDVLLyHJa4A22z9KqUeYzo7kdD3IRmApficIMZX9EgibFlELgr1Nuve2jGxqJBcYG5NM3h2kdRegg== +Nm/97dX7QPdHIqApKEes3DDGlNYTObtQW4TndMqXq36d2O/XMJDcOXXw1xcOIV/CXht4Yme2sDhmwflt9XyXOV3//eCre3z5J8TLTuCe6MeGV4CDvsiwh3JeJvhS+PHh +IW3EqlnnkSmY1/JPGNw5+3vURuvsnXAvPp4bO9F0tPsoflnTjqJSRd8ZzMGHdglHHfxLeAi74N4KRzlqNSRQ2ptalZyi8UHTJ1zH0cYyr406TJ06FY/tbiwgCjZHi4eVd4h3DKYyqPMBoM2LyQdFGg== +wlLHv6kT3Q/RmtMBN4nDAZOq/KkXwjmJXU6lIwaeSQux0oN4tIbhF8ZR5VYXdOa8 +VmVrGQo2zRokW/ZuO9bN66tKItbXQ2S07vvESMb0Xbx3fmLgrcSLbgPkYQ0cjDs4n4nwgxuwWO5ZRu/l3LP7KA== +VmVrGQo2zRokW/ZuO9bN65KPamIARv99cFn9NbPlKuvW0pRhETj4/81+ZPDZcAKs +VmVrGQo2zRokW/ZuO9bN67vH5ZnhExKao21k1gaT2G0FW1Fan/3LmBluWT+lEhtBkl8E6G66TjuT3POYVcwWj+zIW2FtUL84muoV4/VPvDE= +wlLHv6kT3Q/RmtMBN4nDAa0nhnlZDCiGXf0V3odfM5gdveMKPlnoe1cvlrbeeQf9 +VmVrGQo2zRokW/ZuO9bN692I9BHTUUNtPoqLNpaHMVuAxs6D7xvFG9y4+GFpJG4QYj/otOtWLL3aoqR3r2jKZw== +VmVrGQo2zRokW/ZuO9bN63oQXh9DZkh7TCvG9sp3gJi/ZoKhTvTBKm2jHLQI+hVS +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZcKSoZD8wPjLjehW2rIbxpWCeB5SDWc7JzgrKKoXCb5EA== +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZfPeLJTt9nZb7Y5PSF7XoobGM/xM4dWAILbiNCrNHLtUw== +VmVrGQo2zRokW/ZuO9bN64aCc/sRPofqjT6zGI12oEVmj13/GVwVAJqjfidsNNMY0k2NMsYUOupTPggVIvGz73NWNMN+yri319t6Pflfq9Q= +VmVrGQo2zRokW/ZuO9bN684ct1InVNowfFSmyust26FWDCXHcd0UemTNTXUth2VboC0EOmXg6vzaA4Ro8MFNXNdxXSqKlzNKA4j6jPhO++I= +VmVrGQo2zRokW/ZuO9bN62eFz5hnP30y3wtyekB6DRbzyysSkha/M93Z9tUVsPioFD8M/Myvsauv7CKZIsUF8bN+2irFUFxyhWCGqDkE96/DoEDPhby03UyOqQUOnNhFQH7+91qtwIUZYcHjf+Pj0/TeagyZaNvNNVB5JJTm8vk= +VmVrGQo2zRokW/ZuO9bN6+NM+EEAOXoul34lVekTk9XdEpwm5N6VOdmYCRu0UZNDeYYo3JSnU7nGyfWq5IWysj2+Cbclo86tR/GmgN8a0L4jIMMwhPZ25sfNuOCBhCQ/ +VmVrGQo2zRokW/ZuO9bN68pj4OAM9ceLEL0V8G9Pm0M= +VmVrGQo2zRokW/ZuO9bN6w63gxf8yZDAhmO6QX8vKO99kRo8Tw6ZtkZ8S0o86rGGlhSeH6EcFyzEAEBZLglLkUsgn8ynefX876R1E6+Q838qPxsIQ+nTjMcmjFwk6NfA +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN6yzRIO0vwutUgOnYoAZiENfKGuEEla9CfQfa6gTxunqEsZiyxjnfoaBugkUvvXr2Rw== +VmVrGQo2zRokW/ZuO9bN69nE2sfjmDVttSaOgdZVlWJ63QYeqvyoAYe2eLCe1n94fhdbFrmlBBGKELpX6vh0Zw== +VmVrGQo2zRokW/ZuO9bN67OLNVXM5ci5HUbiKSxFONHiC6Xt4U1aS+8X88zD3fZq +VmVrGQo2zRokW/ZuO9bN67JgsGJtX6EgeNIetmCvIZTDKouBEO/dMjDCbSOu57B3ED/vMQt5sqDDht16EZufT3i2I7twNbn5Ks4qihBXVwY= +VmVrGQo2zRokW/ZuO9bN6yaCiWSg4HG+i5+Du7fqu+RvYiZHs1OKN/b+c06Yjb7eN/WozKC4OfXfeFilLlqgmQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklhYQuSnpbSGQ82T8TKI2z6 +VmVrGQo2zRokW/ZuO9bN6yMdhcWuddd6xv9F64cBYywreWSVvaR5B3yfWI7OYuMP +VmVrGQo2zRokW/ZuO9bN6/vn/3cOWDwADLDcgHwMLeI3JY5bka5LlkKpq0MYXcsoB7UmUgxFfYsvrjsD8DNpiw== +VmVrGQo2zRokW/ZuO9bN6wKd3hA771N/TeGmptQceIYiS5uPTHliZojP2k7ZtxzSHJ/A7emyoPtxPldUadDQ1z4YB2e03b3LCuUSznXaCdcatAod67kNj0SyKlsbUFaD +VmVrGQo2zRokW/ZuO9bN6yFoyEK37tDej6I4O17EJK459bjz0b2aVOFysAY69AAI +VmVrGQo2zRokW/ZuO9bN67rthG39/u+hUAk/TFHDa1VGBOFJwoQOghMtw0t9SgUwXuyF/hRrB+Gf3XckWgDxww3QLz/Gu/peTY0Vd6Gg1xb+KAX00Wk3vr1B5OMxTI7aQ/Ifoh11zpKu/NDW2I0hGmizjni0htGkt74vDHRxoVh3MSUDH5SZpu+51ji4sZAP +VmVrGQo2zRokW/ZuO9bN65K4OzhqN62rdPrqAylIxiSfc/0Q13hiTjBH1bGNcEz4aPPkPX5svr4sN1Ftu9vIYA== +VmVrGQo2zRokW/ZuO9bN6+hkvN/q3FP5RxK/Rg+svANXm3H1HT2pqctOKa00PJej +VmVrGQo2zRokW/ZuO9bN6yju6Ue5iIbZkqhzYfgvkrySRMheiu5V+Ta8hxwUEYtyV5yMYCJkOp17i9yxEqnSoAsVha/5IlfEIG466h6aA9ABaVS8/6yuKdQuxOEv3DKPUhMOrfnEfM8aC4GqWgHrnhfMnM+XlC7Uxi87ysjRKWDOrWQU6YBXRwjupMCbe0SEXZK9A8ozplzLIcf5aN/JXdGELghOktU+LBGwgiBuIfji4VuamEV2GC4h1ZfwrRQb5QJjIufhNn+QKkNZF7kvARNPPt4X2NsrnHvMVkl22BBGRWXXuvnBlCRdn1kGgUiCECwtjF8r8qnQy09o78UxQQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkldwb8jwt9DxZpgrJeOzJ5Ij8OQIUCa0CerXo1Qo3bw3IPKZ5V8SctaW+5DIK7QWhk= +VmVrGQo2zRokW/ZuO9bN6+hkvN/q3FP5RxK/Rg+svAOJYrDIO7s7OB0iiKzDZl59 +VmVrGQo2zRokW/ZuO9bN67rthG39/u+hUAk/TFHDa1VGBOFJwoQOghMtw0t9SgUwXuyF/hRrB+Gf3XckWgDxw7DLMf5H1eEmfPk6cdOXzW47In+/SEGy9c/a4ZkM6IZjDOboo6FQzRVZNiW7ZTA8hw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehptp4khfwBylvSeT1suy0diqoTvxNS9EMUDiNnHRZaYX2N+xEgg1O4D3bbS/Ud4Du8qxQtV3GSlMBzkn2zpP7PCfLt+zFrmQaQ8dqjYJASJzEJjI0p44nybI7rSOX1Ur8pzKOOL8SG6FUY8ynPnHXdBRwBLnSnIn3C69Oytt+NtUsAj6NlXA4snQKXDdq3pyeyEhcXvMkv5THWk/K9RxmMqA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklZleNLu+NU2fHIDIK+5y42Se4QjVlk3EpMo+ZcELlSW7vLZXtQKUZ1BEsv4QUd1oM= +VmVrGQo2zRokW/ZuO9bN6/xDysL+wsJSb13AswERz4dbP32z4nnYwOYj0CDjnhOh +VmVrGQo2zRokW/ZuO9bN65TLhtjykfNX8WJm3Pk8YW+WVkYXvKuXVAWeiZcTVClQY0GxZVLKZco4wIWMwgiDQXjteoRkeHHNPD3UcQj8FGg= +VmVrGQo2zRokW/ZuO9bN67CsaWaIPoNPPv9rrlUCpWPQJYFKEdBkMv0SxvLDOQ817rdfMwkgWxpkGk9a8pCge7/S+HnN4lZX28CNzWerH014+BGQD0z4R7x01aJw6EXHRYVXV9MynwKzDH6wO3RcXbSmldK4BIfvVkkZ9kP17fX+3TrVTnh7UmvSBwgDROjAAm8UpQj33+2wvfSvu9SMyFi15HP6t19gWxOVeFZtx2N+CTOnrHuZ91CltWAghlaAVlGlx6T42OXYkt9M/UJCc3tfRGJNF1KaelYVCIS9HfY= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknzyrk+dVp6PmqNpyPq6vZZnihxgqkTAYKhUWLqhmAkYwLePWapfwKt03bgcDUCeOE= +VmVrGQo2zRokW/ZuO9bN68IVqk5miXE2mnKv9puBimRl2KBuaeKnnVtZiU+rRfK4sSkVydSM0QE6ee/tsEEwaM6zcOj8rcIicUVSnuqtteY= +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZcIBa2uNfov+/XeRNdZe66MEk1poQGPzb9rX/JgATA/bMUXBS/CbKPdTay5uZcZ3sYUgk9ezt/r8bq3kLsYMmsQesgDWI6MwVt3ta42RiBxgoIMVS6ETH4RrMqxHchWD/8l2ne5CpfrnuXoy4Qf1BKH +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZfSUix7t/J02EKzLYN3W7lneCAT73Pg77ICBvCCeR7hahskumb4s5Qhf5/YpDeUBLJNhvZfKlRbiypJO9SGwHZTr3sFcR9vQT6gpuX3ULVcemq7uN1K4SJeWJzF2xZfztqyzJLVMc3GuAIpeupNq1UTK5yfbeWF+zRppUr+865LfO9sEUKaS0qPDfbiKbxRPGV/qsky4VKryRpka6kLCFDMdueo+Bz2tGJOoGEN1klX/QTZmvX7EEqQBAfK2/7AMecNmvc/63C18FanCYKATtCKUJkCFpKQmiLJFg/MhGd3Dw== +VmVrGQo2zRokW/ZuO9bN6+/4QbgLnH6ddT96QVU2BWIrsd3eEvEMj5S3ByOD7Cmm3QMSJN2q4KX4YNvBGAWi4w== +rWUQK5drq2FAykvoqvdEFxoGIhNxRjfJiH+/FZVNPLeg8Jfg4FKs2Fr94MGjSAK4 +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN65PZoHgqHeg2tPblo1krTlxzlv5EOqp5opCmAyCAeKT45K6wg8ejAyXR1JcE8YF1SA== +VmVrGQo2zRokW/ZuO9bN6woHMy6hb62x2fhlLnsl631XUsxwnZL7Y/DKLi3T2mQs66jvP/0U+MMKUttSSGlCpcRD5qxdcOcpSgZdBtkwlus= +VmVrGQo2zRokW/ZuO9bN6yzeHL+uw3HF0QgQWqkuWpLXViw8Q08RxhcsgNDUoPUdEWAcNJd85Weog2y46VgzAdqS1DwDPxix7MAz3WaXam0= +M0ZySqkmhuHCw6olbCKv95w0wnb5tXdVpvk+nm5slh1G8xJLU1xuHApKfUAMvlbG +VmVrGQo2zRokW/ZuO9bN61Jo4bcSJ4kcuwCfHogcpLvrNPD9zJMAQccIRdfKWJAC31+hQ9ZXGOd8d+LhnFiWN8BpaSyxK14CiuXsSZrbI3hL/0ulrVAR9WWoHJRHnxHbOm8ok+YUHaagbP8z96+R7Q== +K4yn9TN/uzMe9BBgrE1owVggOblz5V+u7pKTqFtSQHL3iIEokAVpreCfX7C6eBtN +F+/dN6XwseDUiXkUekEXT6mO4mb6/XgfmHVOqlLPeww= +DPgKxKUksPJmBI8KfD7MnG19wQLsGqEChmw0QoF0cBEGNXse2EH2RoQjVnGDcYmdB0GUkNwww4YhQ8aieso8Hw== +DPgKxKUksPJmBI8KfD7MnHMEClnTrZOgWuhoya+UtfRGOBC1uS5ILl2CMZwpFEHo6fFfTMIb89Rz87f1qgOy7zYaiIaklAdfGPQoGliv0z/2Y2unPwdN86OTnYU524u8 +Nm/97dX7QPdHIqApKEes3DDGlNYTObtQW4TndMqXq36d2O/XMJDcOXXw1xcOIV/CXht4Yme2sDhmwflt9XyXObZHIkfqN7uTVhRogjoM7kx9b2SsH0KN1XeM3xwm1QuR +IW3EqlnnkSmY1/JPGNw5+3vURuvsnXAvPp4bO9F0tPsoflnTjqJSRd8ZzMGHdglHHfxLeAi74N4KRzlqNSRQ2ptalZyi8UHTJ1zH0cYyr406TJ06FY/tbiwgCjZHi4eVd4h3DKYyqPMBoM2LyQdFGg== +wlLHv6kT3Q/RmtMBN4nDAZOq/KkXwjmJXU6lIwaeSQux0oN4tIbhF8ZR5VYXdOa8 +VmVrGQo2zRokW/ZuO9bN66tKItbXQ2S07vvESMb0Xbx3fmLgrcSLbgPkYQ0cjDs4n4nwgxuwWO5ZRu/l3LP7KA== +VmVrGQo2zRokW/ZuO9bN65KPamIARv99cFn9NbPlKuvW0pRhETj4/81+ZPDZcAKs +VmVrGQo2zRokW/ZuO9bN67vH5ZnhExKao21k1gaT2G0FW1Fan/3LmBluWT+lEhtBkl8E6G66TjuT3POYVcwWj+zIW2FtUL84muoV4/VPvDE= +1u+XjG/2+GSQRv6EzCaWRQ== +wlLHv6kT3Q/RmtMBN4nDATC997xtl3+dwIM3Vjo1fNfYpvl7rJe+1k7mDowOrD7Q +VmVrGQo2zRokW/ZuO9bN692I9BHTUUNtPoqLNpaHMVuAxs6D7xvFG9y4+GFpJG4QYj/otOtWLL3aoqR3r2jKZw== +VmVrGQo2zRokW/ZuO9bN63oQXh9DZkh7TCvG9sp3gJhiav0QeisBSComydq5Jid9 +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZcKSoZD8wPjLjehW2rIbxpWCeB5SDWc7JzgrKKoXCb5EA== +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZfPeLJTt9nZb7Y5PSF7XoobBD8togGt22mVzBahmkvECA== +VmVrGQo2zRokW/ZuO9bN64aCc/sRPofqjT6zGI12oEVVHCITULKhIq+Lgy4/mXUYAgwHGxd8E4XOx9XL+uCCFOUHXOvKRFjQS2RPn1Qrhn8= +VmVrGQo2zRokW/ZuO9bN684ct1InVNowfFSmyust26FWDCXHcd0UemTNTXUth2VboC0EOmXg6vzaA4Ro8MFNXNdxXSqKlzNKA4j6jPhO++I= +VmVrGQo2zRokW/ZuO9bN62eFz5hnP30y3wtyekB6DRbzyysSkha/M93Z9tUVsPioFD8M/Myvsauv7CKZIsUF8ZlcrcMflLDlILvfcomKvahHyi+BSgElsOhxz5LvpeJjUuRght49ekCUWF3gL+z6saGhSF3KvtEuIE+XtgluEL8= +VmVrGQo2zRokW/ZuO9bN6+NM+EEAOXoul34lVekTk9XdEpwm5N6VOdmYCRu0UZNDeYYo3JSnU7nGyfWq5IWysj2+Cbclo86tR/GmgN8a0L4jIMMwhPZ25sfNuOCBhCQ/ +VmVrGQo2zRokW/ZuO9bN68pj4OAM9ceLEL0V8G9Pm0M= +VmVrGQo2zRokW/ZuO9bN6zB63Ivt2xCMJfE7SCLw43s= +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN6yzRIO0vwutUgOnYoAZiENeHqGqZbMOarS/0U42fodjXqH7Cei9mZkN2WxnVngmsrg== +VmVrGQo2zRokW/ZuO9bN69nE2sfjmDVttSaOgdZVlWJ63QYeqvyoAYe2eLCe1n94fhdbFrmlBBGKELpX6vh0Zw== +VmVrGQo2zRokW/ZuO9bN67OLNVXM5ci5HUbiKSxFONHiC6Xt4U1aS+8X88zD3fZq +VmVrGQo2zRokW/ZuO9bN67JgsGJtX6EgeNIetmCvIZTDKouBEO/dMjDCbSOu57B3ED/vMQt5sqDDht16EZufT3i2I7twNbn5Ks4qihBXVwY= +VmVrGQo2zRokW/ZuO9bN6yaCiWSg4HG+i5+Du7fqu+RvYiZHs1OKN/b+c06Yjb7eN/WozKC4OfXfeFilLlqgmQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklhYQuSnpbSGQ82T8TKI2z6 +VmVrGQo2zRokW/ZuO9bN6yMdhcWuddd6xv9F64cBYywreWSVvaR5B3yfWI7OYuMP +VmVrGQo2zRokW/ZuO9bN6/vn/3cOWDwADLDcgHwMLeI3JY5bka5LlkKpq0MYXcsoB7UmUgxFfYsvrjsD8DNpiw== +VmVrGQo2zRokW/ZuO9bN6wKd3hA771N/TeGmptQceIYiS5uPTHliZojP2k7ZtxzSHJ/A7emyoPtxPldUadDQ15OwaiosCVmwCzZO8uObu34= +VmVrGQo2zRokW/ZuO9bN6yFoyEK37tDej6I4O17EJK459bjz0b2aVOFysAY69AAI +VmVrGQo2zRokW/ZuO9bN67rthG39/u+hUAk/TFHDa1VGBOFJwoQOghMtw0t9SgUwXuyF/hRrB+Gf3XckWgDxw7DLMf5H1eEmfPk6cdOXzW47In+/SEGy9c/a4ZkM6IZjwFbCBfLcjuN1ph7dXL69NbEYqSHAUYrdgovEd+6R44A= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkldwb8jwt9DxZpgrJeOzJ5I120dhk8M+FtTOjxWb/xzhxVLKAVuFPJZ0WZ/dGerni0= +VmVrGQo2zRokW/ZuO9bN6+hkvN/q3FP5RxK/Rg+svANXm3H1HT2pqctOKa00PJej +VmVrGQo2zRokW/ZuO9bN6yju6Ue5iIbZkqhzYfgvkrySRMheiu5V+Ta8hxwUEYtyV5yMYCJkOp17i9yxEqnSoGaswzu9+McwwHad5v1bjq0= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpWAZDWQYZM9u1cxtCvNhbPooL0ajh9MpCi4Hg6mHWFepIrUaELGkT8BlfyIopeU47PGFEELMdT7TSYIQ0qNiPmJGqasOXFDTjF2zWDf0dAS0l3lnIwfdrfU0GAHbuHcr8 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpAQPQTfFcASsalkbD4pFXaseNfXO3/kTNf61EMrDi+Je/7g8ehzlbUlTX4aZ1hVLch+r1EfurKSDSPnqHD8HY+hSS15SwBbExPjTnF1IBBX4= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklZleNLu+NU2fHIDIK+5y42fBSjXSW3t6amo3LLHZ6byf2e2/PI7Tj1IoF9SPmTQlo= +VmVrGQo2zRokW/ZuO9bN6+hkvN/q3FP5RxK/Rg+svAOJYrDIO7s7OB0iiKzDZl59 +VmVrGQo2zRokW/ZuO9bN67rthG39/u+hUAk/TFHDa1VGBOFJwoQOghMtw0t9SgUwXuyF/hRrB+Gf3XckWgDxw7vyIGIrTvsqSwVabH94NPs= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpdgMJEzyO03tJBQm2adJkUrRWBuS6Ey7sxkpJGNCZ/ath4Am5fEEZFWjHHvvOeERK +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehptp4khfwBylvSeT1suy0diqoTvxNS9EMUDiNnHRZaYX2N+xEgg1O4D3bbS/Ud4Du8qxQtV3GSlMBzkn2zpP7PCfLt+zFrmQaQ8dqjYJASJzEJjI0p44nybI7rSOX1Ur8pzKOOL8SG6FUY8ynPnHXdBTDnIRva7UUPKtL6AHaQF08vtcQm7NpSUDmKTfcHj9wcFSUkOspwTbuxcTbJ8bbnpA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklZleNLu+NU2fHIDIK+5y42fBSjXSW3t6amo3LLHZ6byf2e2/PI7Tj1IoF9SPmTQlo= +VmVrGQo2zRokW/ZuO9bN6/xDysL+wsJSb13AswERz4dbP32z4nnYwOYj0CDjnhOh +VmVrGQo2zRokW/ZuO9bN65TLhtjykfNX8WJm3Pk8YW+WVkYXvKuXVAWeiZcTVClQY0GxZVLKZco4wIWMwgiDQUmeYSvaHyOxGnQt5qzprsw= +VmVrGQo2zRokW/ZuO9bN67CsaWaIPoNPPv9rrlUCpWPQJYFKEdBkMv0SxvLDOQ817rdfMwkgWxpkGk9a8pCge/VeLlYftLVcRLaZqX5ex29GZk48TuAk8VjtoY2bRUdZZWLkBdYDgVaIsMZT7g5QjcvFH2Luho2y2TCfmiZp1EUDPOnkiH61//ooBkHid+aYP/rRJzr1darh4Mq9VWhFlUiifB5crQ7DrfsgqlvjgkvCtLMuCxVVuJY5aa8QABaw/3Q0mHKYcCm7hQZ8mkcwGw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknzyrk+dVp6PmqNpyPq6vZZOfE2049o5YQczTseRZhSU48lvYICEhHh8MGvMwzXDTg= +VmVrGQo2zRokW/ZuO9bN68IVqk5miXE2mnKv9puBimRq9Q/v1MO8qgdK5uBK+6EHJyHxhfkOmVNW09AfJgsvhSwjQuPDOrwSPWH/kVPU7Co= +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZcIBa2uNfov+/XeRNdZe66MEk1poQGPzb9rX/JgATA/bMUXBS/CbKPdTay5uZcZ3sYUgk9ezt/r8bq3kLsYMmsQesgDWI6MwVt3ta42RiBxgoIMVS6ETH4RrMqxHchWD/8l2ne5CpfrnuXoy4Qf1BKH +VmVrGQo2zRokW/ZuO9bN64EUjFb66du5csfmqn4guZfSUix7t/J02EKzLYN3W7lneCAT73Pg77ICBvCCeR7hav/zdd9VH7r/m6sUb1HxqmhsvpSJhPoVpKk4z9Vjw6MAQ7kvU6PJlyy+3C1oiCIuW6ebkWvm/6qoMHlI53uC2HoxE8lugv8/DgfWrz3cBv7cNjT33H9uv2A9d55LgMqGEgzS0+MRIa7Q7COyOBgv/l/M/OIYLIWYbtZ25RMtTNjRmWhWsVkz1UV0KR1oCJcpuGNegIFtMVTWQTmQL6pnNCpb9j7EvbZeoRKZ0wobGD3wuS+RsWmC/cLLmfEDdhIQIg== +VmVrGQo2zRokW/ZuO9bN6+/4QbgLnH6ddT96QVU2BWIrsd3eEvEMj5S3ByOD7Cmm3QMSJN2q4KX4YNvBGAWi4w== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN65PZoHgqHeg2tPblo1krTlxzlv5EOqp5opCmAyCAeKT45K6wg8ejAyXR1JcE8YF1SA== +VmVrGQo2zRokW/ZuO9bN6yzeHL+uw3HF0QgQWqkuWpLXViw8Q08RxhcsgNDUoPUdEWAcNJd85Weog2y46VgzAdqS1DwDPxix7MAz3WaXam0= +M0ZySqkmhuHCw6olbCKv95w0wnb5tXdVpvk+nm5slh1G8xJLU1xuHApKfUAMvlbG +VmVrGQo2zRokW/ZuO9bN61Jo4bcSJ4kcuwCfHogcpLvrNPD9zJMAQccIRdfKWJAC31+hQ9ZXGOd8d+LhnFiWN8BpaSyxK14CiuXsSZrbI3hL/0ulrVAR9WWoHJRHnxHbOm8ok+YUHaagbP8z96+R7Q== +1u+XjG/2+GSQRv6EzCaWRQ== +rVN2gQsrcQDlbnpSi8Z8Q6rgn/0T7hQWvJY4GQLMIs9AIys1inq5DkYPMgls2o+mJW3SfNTEx/GhhDY0TQd0UB/KkXGzZbqKcvH8j2fDwSmuGfxlLBtk3AbGZSvDtpoSGwuzLY43M5ISj8W0y0/imQ== +9cDZy0qlZW3l1x5OBfxnvO0ZOfno5ld/uuXXhKKyg3uIcXrrtMO6pFpU0DI5e3Yx +GCn4vkscBQCyFsJqT8NwR2LqhGP3NYb9FEma1BX3QpWYg74ASXZiriukTidAkbQI +NAimcGcce9/2qY2fTDUfwj3/O6QRkR+LZhxD40hWYL4TxYQ3miCx5/h9nbCzTANPM8KZYeDE5eSEQPxFH78dvw== +NAimcGcce9/2qY2fTDUfwhcqLyAvzPw4wNTLao2yokYlX1rKhl/FIVm4Lgve1IVc76RE0Jqa7hKlMEraqRFBxw== +PNZcZ67j5O9c3iqbWgpxu2EHvTQ7URoTi+/pefkS6d6fbDFw7ZBZnx6O9h7ZvR60 +xWoGNWjKGPfI4gq8aHoTfEof00xZubKgl7GPVFGSDVetH85R9nTnrsUt0fCijUwRlw/sWM3BY5Pjj/8zJYtqDA== +6K6zXmJJNC0xAXfJ6TCmfWKrZRJUke7mWTEHsPav/5bJPLpyykZ7KCd7fBI5s0BC +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj2I06OqcNmpw9TSQDmoIjgsN8SILLl14ekx/mYMdKWdhA== +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj2hTItQ2Ij+1snSKd1v7XUNy/e7NOVcHIv3beElF9/qQw== +RJ0Jr3va304T+IIxEmBMaDZh5F9oISUovbDJxgb1Fci7kfpoP2KFguUQ2iicYcsiJw9j/EJ8UbGkVKJAnZcOSaYZsaFa8g9piUwzwxW1VnQ= ++Clzt950thRTm4LwIgUtPhyWBqEsgJp1+aWVuN4uzhj1mySJAgcqukdWlyarfnnKaL4r8VoOp/K2eGKuJ9zO9KtrBdNySU/kcg1SUQqE5Xj4I/Gqu97OR6xgDaFUpIeq +xWoGNWjKGPfI4gq8aHoTfEqKjy/gKwJlHr8gW57qOoBqcb0urvlg7lYsKGdM0qdHasQGA3eLdYAyBwzMOTeIzSnoVbpqljeEGULKc9z/qVerE6OJNKH9bfgPDxyaWhBt ++Clzt950thRTm4LwIgUtPgVr3Pc2smn6BhW76Xa4ivk= +o6QhOIN2Sc4SHELnst17uccQsGoG6l+gSpQG5tq8A0nmmKRHpnDq+7te8JNh1HhV +IW3EqlnnkSmY1/JPGNw5+4LhZm/frK6qv93QOVkJnqJWHyDzktjmb22a4qC7XhwTvmUv976mKYyq0xbMDgaj3w== +0Cj55jDWeNf+rIE9r3fPh6JSGqHoFqAU4HV6us+IVRVh29MQUSpF+EBdHBvHUV9G +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033Zi5ItsJ3y41J6ZURymCdQyD+cPGgw2n+a8rLGuLKEUNQ== +dV9xz3yyeXnMinI4Y9Bh+tDzHweRiR3OY04CwsDSrEzJ8NdJW9o0KROEfFeWty1m +VmVrGQo2zRokW/ZuO9bN6+H/V3XeapzirFfkI6FkhjlZtbue/6GBNBtcFaRV3lEw2dULRqauw8C8XrGfYteMzw== +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +wlLHv6kT3Q/RmtMBN4nDASsdDC1+/U09X7W1/sF+PBgDYxQHTs6TeZfbpjCRfxsR7Ri0v1pNpacNyjvn+ZAWcQ== +VmVrGQo2zRokW/ZuO9bN6xIPRe73WG3trsUg7zsddsUGML8FxwJqzkawilmA/yG4 +VmVrGQo2zRokW/ZuO9bN60b1xUHVzhvqjGgJEFa7z2PStb70jOyvc8lS3Di3xXvE3QFnlqvAtgdrxSwXHNUW4Q== +VmVrGQo2zRokW/ZuO9bN60qVoAbkZpaJD4xo3DSlycVxelswpyldpCEVTkQNyWer +VmVrGQo2zRokW/ZuO9bN6/A/JH2g7iLFpPjkuo1x7nSreL7iMQZNdvu11f1uZaBYeeVwK0zeqqcKrot+B7LvGw== +VmVrGQo2zRokW/ZuO9bN6wf3F3T/p04Y+AmmtjbatEIGoQwyQhVz/a/AzqStaf9rhOzlP5uUixaG1KHUTIK8ow== +VmVrGQo2zRokW/ZuO9bN60c9qsbDvegp0EbVxa/vRG3fOUmkm9bGganfHODTzHqXy0UDvXB0g4VA0t9QGBGbuw== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6+rB+IyBYXcXUy46qig+BtwdYdqD+TxxLhWkYSkJmiO4RFsna/dwqHaJ4pO+KsmBBdYrBgm6RWet76WBcw59cVY= +VmVrGQo2zRokW/ZuO9bN648jccnJRw8a2i6QrfCqObE= +VmVrGQo2zRokW/ZuO9bN6zEqFF4QC6LTSGFDZhIGz7GcR+ygVS9QnYBnqBZxUAT9 +VmVrGQo2zRokW/ZuO9bN6/Vwcya7gDAvJTKuMpYpyFlafJ/BPYjtHMwkK9de1BoC +VmVrGQo2zRokW/ZuO9bN654Enj8yhWFV20SJ83piZWE= +VmVrGQo2zRokW/ZuO9bN63So1HGlXm9PaEG2pZJhSdcK7+Mc8DRjd5XdH1Tqy15w +VmVrGQo2zRokW/ZuO9bN62RPHcQXi3ZrUKSXDdSHHUIE+TC5rWLejWhcUHBiNr87 +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN67CsaWaIPoNPPv9rrlUCpWMcwkU6teswneWijOIA4pB0 +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +M0ZySqkmhuHCw6olbCKv95w0wnb5tXdVpvk+nm5slh1G8xJLU1xuHApKfUAMvlbG +VmVrGQo2zRokW/ZuO9bN67RqmNj5b9Yl7yAom+TwpFzVrmvT/HrQJWe28Zbtkp15 +1u+XjG/2+GSQRv6EzCaWRQ== +BP5df94zrjqSm8bAbTOrL5GvmURzSLlleO9d/CPt3EdSw9Np3rh+GFXgZddZgINx +MOmjZUF9MED9E8RkMZXTKuAZ6lHL+/KvEyaqYJYD8unbnnXi6XeQdy2cbayVl0zR +24o3icEhYfd+ebd6821V1vk+Ob66PUIOpthX78JkZSOWniOIUe8Z2y/ETnuz7vmR/ZJhFufm/OjJnLSwzQAgzg== +1u+XjG/2+GSQRv6EzCaWRQ== +2+RceSNjD2iq0sRzepMysVUxXE5N/Z4aOUZt1dAV3sWaDWXqIfjhxQTvYnq4ah3QQ/ApXfydcjDZjJErj9wzNQ== +VmVrGQo2zRokW/ZuO9bN6wOXRPNxR5cZD1m/7kptrG2nCI6NBuWJvllrDx6o+52L7gJNIS2eWfBcZwnn+hz7K/wu0xeSDGe/V0C2AaBo49gM9vt9cgZtIdxSk6AReeoDEynV2qmrTB+ZwTT93iZJMw== +8hsf0L9bk/4d+fdV9pZPDw== +382wTcvh4neRg+mxPFc2rCefgHeTPQCz7FdbLHHgNfvV0PAxIqkvHsu8vBFlt1WHkYHwpigjrRPDkt0HxLed5A== +gxkHD5Myi9QB+wCuYFL+wWc3s44uTroMHNL09BK3V9Y= +mKLWhD6ySe40lXWfdhAlw/EiKhvOuh5IjHI66mvpGPOkWDpO8c3ZlbHXSZlyKiJyW787HAHiImrxsDCjXxrqfQ== +382wTcvh4neRg+mxPFc2rG2pU6jpYp9eTnamxOu1i1A= +o+u2uhgAD+6IzOb94R7i3PsKHQ4xrrpf81AY40SXV/ldpAm6Hc2r0ESFFDSIsUib +WHkOzVx7seuLmxs5Hu+l/rPH5+mQcujSxbSmaq50dGAumaYdp++Urceo/sFK7JDvNePZE19AENBMudTBCBu/+w== +3zDbxVuaFCI1nhEYDQoyM8I3uk74D7lNZO/e2PnOR4BzKyhfYbGMLSvwwAa2ovdbrVyoeiBr7HltiyLXLgbQu3J0cWKS/0hxGK7UrO+IPmY= +xWoGNWjKGPfI4gq8aHoTfEqKjy/gKwJlHr8gW57qOoCwycshGI0AFaixups5pnycze/ENiP7AP2bo0m8VIkIQtZ8Z6Ib8E1lrQyfpi4aEqk0CpBVwAeefhCrk3Kq1B4N +l2FJPs4YkAmmok1ulDRuSA== +xWoGNWjKGPfI4gq8aHoTfEqKjy/gKwJlHr8gW57qOoCwycshGI0AFaixups5pnycze/ENiP7AP2bo0m8VIkIQnZoroz4r0AAFpXTko/h6PBTUzIwhFO49lcotmCqudMD +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz6oNJYJ3oKHQnTz+Hw7wWSQ +1u+XjG/2+GSQRv6EzCaWRQ== +iVuFz3/PiBaH1Tva0mvaRKzuSK6RTdeIraH0vCGAPH8= +Z8UsPk1Q7HtwjRd4g01ryw== +N/c3VKyijhiHyyJkJRVAXzxvXhx9eCkM3b+f7TKuxvcDiazOSxHmadhOm9IqzB9i +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNUZltf51gbntc2j7XAPs8+MGpwQUGSXnr1GRlhLGDckpqtG/8Yila5edTgq85180DA4s0FUMiYZIMEHXHAnu700tADqDXaTPJos5uKYE+e+d26TSIYJIRFyM2NK9/wxBqEEA5Fb2NfuB+8HINWgkQzO +b4OJVZe8QyIpjuTpKXDL9A== +Tf4gSxOqrVf/zV4SjgKLZwbhuXlNuItVIwTZNlpCQLJ0Q/RDC11dDCuy56JlBZpAXSExNRBKtSmzN8+NglHLL9DhtI/OznA1iN64ZpslzaoNU3TiNZtXYT8Rh/DavDSZFjVkiLBLDjQrkb5s0+lgBAmfPxb7nPrkzMBdWsgKoS3s7FRd1MXukyuE9orAplI3PQ8N8NaBde9596Wqnw5U9A== +VmVrGQo2zRokW/ZuO9bN66obcEt8FvVttW4yYWEH6uIA7s/HoyUpTNCCqNXuG0ea58Ui4rsjqKur6hAMNcrcMx/at0bQyXzWyEII9AQ7KjNK2AeIF0eWTCrs4Snj1qaEKkv0qPpMC0x9MTz4WEf6Mg== +VmVrGQo2zRokW/ZuO9bN65lxcp/TK9w0q9jz7GHWjKIlBr8LHu2cXmcQ2DsHkT1/8rPPDJdw5+/SjWCMRz1HD6RKMiShwU9H1dKRDQMgwsVDUkSArWZRzEgDv/PWeBFb +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0H4xphI80JaVwdOBqT4vpFtnVDTN0hWgNIS/91N0m4FCPqgIhArHY8jwdYWxzDKrBk59lfM+i8sqN8QCKbMO7OP11K4RR8s9DIo07Xvipy3NSIj+dcgSDYNjADQTqLHTrO7javH+mxN+lydeM4yDOI +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0Go3ZdM40X+nb01il25Cd+AljdpR/WSOL7piwzm9D4WcaH6KL8uJyr/VORP5gWAcRZ1zHGtdYKybNNqyW10mUeSUktNLRhZDDQn3EjSdQr5g== +A50UO/kAI17YP7MCbTvBkFQ/jf5eMs11PzZj1m3IBsQ= +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj2OXw8C3o6E1wLjd5wbvbsNzy5l5ApUuk5qBPz1WdMnlw== +1u+XjG/2+GSQRv6EzCaWRQ== +Vtg8xVOtjpKd+p8vaXfWJRm3BenTCuwYX+Bxe4j96OlTkLr1UKAMKSzEVyMv3B9m +FOaMsCyYl1K5BM/R7S86ICr1MTzEduCDbdR5dv7vfTdB/FZw6sr77rwVLL+uV9uw +E8W4VRiGsaFlC63shQT6iGQ3NT+hVNaOOiy1bY8KRLHgpbbZDfGGu0nZ5d1Wf6Cu308i6+gPlIv8CspUlVbYDX50Q5QfOTZIouZfjH/opqd98cmLn+YpH2Nbl2Z7XUGT9T8gA9FQUFuOhIGTZcsfnQ== +lQJjRn1YGiZ9pmOVzoRZH/LzOv9nkxZA24G8DNkRdAhOWCagTJeCmUveyd/p/vmUlVOMXXoQY+Q5YnuX9s5XRh19ZxQQcUHRd97rhfiUx5M1ahkZdWtUEoalumzyvb4S +lQJjRn1YGiZ9pmOVzoRZHxukC7GWttKaP+XUscJXWEggaM7c22lTg2UY2Nb/ws+Mb7D7WFU1XcMVxdR7sTThHE4TD9Ee0zQVbxVFEAr4ucg= +Xsx+eKeQ2UpKLnofos5x2caURkwi5/bjNLjA2duBbgRiOKtrTQAenMlL2o3N7YpEDwJKG6ztGICevAUUH+3QcpW0T0uAA4EXdsa9UuY7Fl0= +o3kxTO4RwuXO7GZ9wpXG+fb8QBpjTrjWHeDnUy/AgYU= +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uXTeuYyqadnM9vezgj1BtYH9u8offJe1HA3KV1PTtnzL33A0OfyNVLZeRc+C0JHwAdXW16ZDl/Sm31MqyWR8jotHjt8vvFRc9Z4dGbarb923 +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +fxKEovgMcVETu4isdJSo/jhZxMQfQuYY1Vy5f4FcYio= +KZTmaJLp+FU9X93j5Tpqtw== +bXpQuJfgvk/X2lgXTBSBmmPIeohiQPFvf9agMVKrPCpiJXvay09w777OBeP4X78N +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +yqOVJCkAGOhqcQYBOo7yqW1oXCc6F5Djl9me/lICq8g= +Z29bUbmuVBrM98CRqOY4amLs9Omys8D+fr/4IELH5v6WD8rdrrF0X1tdK2GsKzgwLrVhiaJc/nfUg0MItfuMmg== +Z29bUbmuVBrM98CRqOY4amLs9Omys8D+fr/4IELH5v7ZyI1LbiANbBjGOIBhYM4BZAyiTObbg9dRbqPDN6coXw== +YfTgIRhahLXe0ss6mxVGGcSQ4eqMOTJWPxuhr+fHH4s= +744+BgzK3LVsSzMxY5sSrw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5g0grokREAXB9h6KEOQ7SQ3rmh2uBNavUr8TaYBLS9og2llVxjctC8yMwUXWvSYY0lyYX+9/18jVIFLI76aWlxWMeabF029wDZJDPqQV+blE= +KgZhnCYglllfsrazbVxNli3dO57PXUycfNR797BU9y4= +e1qLykdSSG57G+I4DSZ3lPbnd9MDQtXYM1cRPgH2NZY= +b4OJVZe8QyIpjuTpKXDL9A== +p9oVsBgcyiBMjDb8CcPOqFV5wpmixLB1nyXJiJ2HPNs= +wlLHv6kT3Q/RmtMBN4nDAafzR3IJaSQy1F8fEUDT0KURDliq5QrdEI8/0cInAzfT +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvMpc2DCYsr/Y+VR15aQe/co +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN61QY7FRZP3nLjLSHZpK1fChnZ3O3NtYzOWr34yLfe2iFvPA6Ez/VuxipQPOhQ7naEudlyVefOAk7fmuoeCfZQzU= +VmVrGQo2zRokW/ZuO9bN6zhkfconuDiQ7Hi+HHfb092tsLnoojdy07SctBem0iwa +VmVrGQo2zRokW/ZuO9bN67aDIttUw3ewuD5Ela+0fVjSsNlHIVXDPlU35BbI9ASiwsZCpEAG3FaOgv1pdnMAbg== +VmVrGQo2zRokW/ZuO9bN64VbAs0AMhVgj9Vdo6de1Hm9k6jRFiP6pd3OuTpILNmsO4T96zohpHhsxIIJTvpHJQ== +VmVrGQo2zRokW/ZuO9bN69AxjbwgN5/r4H729rZYw+itpXa7Sfd3UqvtnySLc7pbHlYlkwrDJkN4lKVteM6PSv8iDLaQAfsFSZW78PCIWj+ncV0kvU5QvUJdv7cFQD3N +VmVrGQo2zRokW/ZuO9bN6y1qjxtkMU9nGvhd3REzjQlflUcZo8MWVjp13B9YfVOFvwSEF/c3vjwH4gvmctbPKA== +VmVrGQo2zRokW/ZuO9bN65a1lvAsgN8t7bU4ymW4veAsILdDJ60KfokEuwETIFZk +VmVrGQo2zRokW/ZuO9bN65IMmCegrj1iToe3m+KEt2lYR2GShaC1HZt0ENbtcWDT +VmVrGQo2zRokW/ZuO9bN66zBSwHJm6ElN+7ZdiAVZBTvBew8YxafVWuo2yjvJVU7XQLDpZScPIbuHyakPvsAt7g//bPIKP5wqL0K9MrvB0NLkTgDLvhL/Msehj5zQDqP +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65a1lvAsgN8t7bU4ymW4veAumH9BDyXcgjZGd5H0sUIW +VmVrGQo2zRokW/ZuO9bN65IMmCegrj1iToe3m+KEt2lYR2GShaC1HZt0ENbtcWDT +VmVrGQo2zRokW/ZuO9bN66zBSwHJm6ElN+7ZdiAVZBTvBew8YxafVWuo2yjvJVU7XQLDpZScPIbuHyakPvsAt7IEViCt2T4QtzN9Qy9djaZTr3A6biDXued2Flv/NxGi +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uXr5UNbgUmXTutS1OHze4KAWfW3eA+x3RJPXRQ93lOYqGcInwu3ERZkI0NS5vrNL+sP+PoD3MJwWoJIn1h1ruB2mlWtaP9ATlsd+gksrWj49 +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0H4xphI80JaVwdOBqT4vpFoYlr2YEOLcHWVeU8XvA6DcJG/8/11CmMgn+dU2p+MiteDOVRuiE+vkRJyCKGHE41wHxivV6nzAK9IrCg/kgKo1r3TbCAho4S/unYWBGxwmrqp+8ZOvtXQB5XA8bjBzXdDymC/ZUqQHO8ss4ZVeIjTA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw58tqRCidnUtuhiV/SQek1uLeqFhZN4Qp7W1PBlJxy9Uij4W4Ylrlk/HX5S/Q3N9gbstqjFhfBAcmwuzubPT0II= +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0Go3ZdM40X+nb01il25Cd+Ij2ks0QNQ8ckELJbWPuSoOaKJkHC6l3WHCulMTp8j+LeAWCb5DBJpRVpiRip8pDe/KIfBFKsAy1uPllcESEV07XCLR6vETN2Y//6YxPUCufIc5q0ZxCiI0dqCJvZuBn0 +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +wwsYDn3qQ9eA0sEqk73MVuPXfo7SRy5xLbvd1X4NKq8= +KZTmaJLp+FU9X93j5Tpqtw== +v6hV3loTEdQDUP9Ry99+0RAKjxfNw2Dz+nW6kFLJdPapLbTXMY2KJjmTphId8NeM +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKMlEIqjWm8+kOtLiMZRGDRK2CASDX1cIWeRfBMLHYT8i9caoCs1tnMoi/mLDYHLHsrG5pYDOjL5lbQQFrLwKmqEf4eUWu36maCOXwDY2JSwmxjItoWpsMmOWjeU8IEe5UsZSP3vWVanEc8+qzu9RF +KgZhnCYglllfsrazbVxNlhZvo/OKoJzdIEoxUuSu8luaoRrzRCusP9S+W8XyCKbv7XFXz65iKXwYhcIBP+oK2DxE0i5qVgufTG67CahKc1wsqgjEyrxHBX32oHmncRC/beuNoupsN0Dg0UScKVJFUg== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DHoOe8nxF4SPJvnopjp+aXxQW+2hMxHCJlMDYgHaIwV9/agBpo/OaGkqJKy/l1jf8tlaOj9owrGI3tR4SCZF9/fDvrdqc4ZWW7Q4NpEMrhzh39he3y8EB8embKGdQ9gt39z72m8Bgcgl+Bgm5ytGw1nf+fDKO00hvhLOu3hJ64SPpPXdeljrdMa6mNSQ2eoQc= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27guByVel75+n6aNpw/ji8t66pMWEvvfB0qjiOgKN2OK2JnDa1DI8vVGfbIvfmOW81ScJ4skmbFuY92sIqmFZn6hMag5GUZpPnAEdGK9yQwCm6dMYt1piNKwtRFu7LC/VcK2ulORGmq7ygse1vRFE6DdA +td0Hee4NJPblA28GiIcOmVz/iEnSh7ccFLM88eucCwhDxWpOIeDaytxmPiP2pwkpNyGbS3x73U5/Z+SQSDEikA== +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +JgoYwGgHB5s2Z4NsL2JUA5RS8BCOiLnJjVzPXD6IXDZAIwZ6LljCrK4YzU8NX+ta +NcZ6sQ1mLPT8+JyzvATZXEFVG1KyQwPWlwFE3CIvEPhQby6LJAZAkWdC/cDzvIH2FxKh8I3W2yWeMr9En0u9IwE9OI+ifUFV3c8+IYfti3+3SIhgnDLoC0gR+kL4AB7y +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM6xtVlQGubW43oelk8eYxRIH8zFsXQbG1pTf3qhdrUJMf371FEbN3vCj8HCBSwaDRcg== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uQb44yiF3et2MoRPcBFK+GMeDEfpsZJKqxSJcEGTH6Rz+9X1BKI8qQupMymJcWNG5+XC2z1XIpw9nq74i1WHl36cklVUtWYHm8jM5UCo2Kz2zRxAWh3xmytAGDv6LQv1nw== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +NZydr5h7sqaRpfl7TbGhKd19ljGVD0oUWF8Qdy6kYbMjwXI28kAw6q5cS8e7La2e +KZTmaJLp+FU9X93j5Tpqtw== +bqizElyCHvmotMcL7tLUXISKYaW23+snAEAzwAXO0AJdIQ0sKphjO0wpP7Oa2+dR +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +IZj4TYq2HlScfSdRkI+QP2MphWNMq1cYD9ikLH7arN4012AQ4XEbHR7fbs2p2xZI +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+BrcUIMudTtdKnBbOiZIsdm+76h3MsHger1Q6VGZyZqpv1mxz/XRJktIzRZ7/ikiX7QXPA7qgSnErFged9pU+fhNJZ4ZyZU5ZDbDTyTQQkEeA== +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gtyrN3fmhNqvxN36kot5RF2fOWlkbJiEZHzIAHNv7pMzkmswcUK9woTu9LfE/4jOcCk+Ff2NWiDHRjrYsOXg9XYD4MTMx3MoSP5s/abZGj+zg== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5bMAh4sKbNzkcjzof9wZD14bso4do6g8oMHxd8Gzsibd0PLp5P+1G5ALMI5bhosmbepYaf3c6LZvIATzFuMxyU7EZYN6aCuVLFSuSSPc1Y5k= +8hsf0L9bk/4d+fdV9pZPDw== +m5EI3+BXxkQEYKYaxORD+jMrAWQO06+dOKA0AMSR1lo= +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033YYZQbvhYc0zusW/weg/CqL +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +CW/G9plDL3SfzmBxnqr6q87wTQCNFJP1gF2kN0sG2a4Ay72WUFWA27WL0DdCE7Rmca525jyZpBIhzsH+Eea2b2BA90XJYyeqnQeFEhxJ8CI= +ltIZHHUgft++nMUXBsBkizMqTqlEdfzGFVQe64Ee6MnnA8YVQnL0OWdmHGxjivUF +vLjkUA7bT696TSSdDLcBOwHGyjTF6RjwKcPbS8/3KEz8IDyLGkB044FzrMJdsSGT +vLjkUA7bT696TSSdDLcBOxxQj41fdn/gNOhiWy4qS+r+dwSOL/cS6zSr65GTqjeAOQzAr29RUiT6j6J9tKLwPycKER71qUKPBR6sHcdh1/P73QzrPupswIlih/8T/HWy +gxkHD5Myi9QB+wCuYFL+wWc3s44uTroMHNL09BK3V9Y= +382wTcvh4neRg+mxPFc2rG2pU6jpYp9eTnamxOu1i1A= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +Sv2zQAAwtxKbD/XdM/+WUM7lG6QiYp1yBcKQ64zS9vXrBe1i9UUCioqbSWCX3EPc +KZTmaJLp+FU9X93j5Tpqtw== +DXnCT40IH6WTABJ8YSFXroivLD+L5XjDnseVaJ3GRDsvr+GwN+xEtXVl274WIJxN +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5Yjd0zuj3dh2k1soSq8z7BD366CML8egIr9+RWKS9tu7hy5ThiQW9xrT1yOgfZpmFnbdA1C/Mq2xStnh5Qt3XdA== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64ifsvsF0vJsKao+rZhSUts0KS+GfybfE/6SJOvKSzRkCPmZ6bx3oIdSgzFoKVqjZhe05uY3gWbpjDWLae4DFBZgg== +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gtyrN3fmhNqvxN36kot5RF2zItGfyfEiFuBhMm2rOiJ7OkZP6a7cOrneDOCN2184l+ZwZJF5qeIzal2Dop22X5hW26mYIKv2A9GjfQi08Q3BHNaSn5fukm5t+d/lDz+OrBMKxFq2jS013FuZef967uC +4+wKB+Tel1iow8au3cOIJaTLjj3pysITCcJJ7ec43SjbGuLLwmdIpxnALkilclujXdCRkF1pWzr9l8hRm545yA== +Vtg8xVOtjpKd+p8vaXfWJRm3BenTCuwYX+Bxe4j96OnxMxH9X//3nMcOtd6Y6/TgOHH8oPBGfVeprOiaoeXRDl7vU4I8BRD+VocM/McwMuo= +o3kxTO4RwuXO7GZ9wpXG+fb8QBpjTrjWHeDnUy/AgYU= +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uXr5UNbgUmXTutS1OHze4KCsFAtrypiPgQFgZLgamax9XqIv2DluNeQrjzBIITfz2UDLd1PBFsxuyK0PgWT6E2U= +l2FJPs4YkAmmok1ulDRuSA== +o6QhOIN2Sc4SHELnst17uTGbfoG65qJYuOo7HVAUfwrrWa4B36uJ5REt040T2SNmGAV0LMPrERjzC/oRk1GEuX7AfuSpWeNyK561rxlWmqKq8esttm3BDwzTTA5Wp/ZKFGU72IJK2pjrHuSoqkzLVujx2qaqM/Km1yCxuLXdC2UZk358XQ+ddlTAL22cH156434R092KKggual1jvxpED9VmPJ1PNoUP6cx4WbWWYCyTyPc7+BhF/kMm12S2r8/z +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +q8NAywUgfWU6ZD/x59lWQG/RfUCU4y+olc/SbIg8B/0xCDpFWcjdNZBflnfCoehl +KZTmaJLp+FU9X93j5Tpqtw== +k4Oy3nZBEabTf/R3M0qud1RXTMs/SVAz7a00xWJGU6ZDDOl3CXrcw25TDnbSo0az +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNVRlZAMt29egPfWa3xSy0wC+hMCHaHnKnUtceII0RupXd8IOQSC4RUB0NbAR30foCsKXj+XCTmbMk/YqyH7uqUi6NJk/EPAbnzegcwhi9vBLw== +oSHYYVMaAzYJr1tP/DcgRffJWEj903F5HYDoV4XPcGHYkVn3IHc6Ap3uBQjWJsvBhk3bbvqYj3Zbc0k0mzj8qg== +xBNXAnUYpksOoaUztSBEcDCA3geSIlub48bUfXp9xmXPUKk/lJKMEh7cy/BygLUlHNyI47PyoopKMBq3MKayWePpwk8J5h++jGIkejavX4k= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+A+mcHfJAcdSEyMx/0+b22bsoYMl4HNKrPY0rcwU/I2hR+egxv39o+j6gCI9ao+XHx5IZEEj0CJYjzIuFWDW//j +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvukJqMTKpVsftOd4s//BOvjT/PcuWcqYjTPhGPjAJjS9eD/6U2Ktl8yDSB7KwR46Y3aEj92Nse6GQg7Z1sPKog +q4nc/jwATOMUyfSjLibfEW5DCAn6VBbRQRnaOjh7eA0= +v4Cu02hZRDsW+85CLRBC5yJTAnmRyE/J47U8yoV7+OG1o5+ROeIFbBg7WFiHmbuo +YKnHO5bC7P3fGpw5spE9ySFVNxaPfjLTGzTVXG8c3HHtkOS5QmjnMETu5VCO88YN +lNoJsK2hWEGOaXNpZFMEKYMgvifZKE+W+E/+8t9UT9fUy+Mm60dxPXPiyrPXtjO0 +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN62bqq+dVd1n67gwziL9Mi81vExgcI5sYg/aWnUHWD1weRj6bWPlcMwz8F38kqrVw9A== +VmVrGQo2zRokW/ZuO9bN61VTXWM6E/WTa3hYSohE63iz4D2nDm1NDjVxR/N+aqwD +VmVrGQo2zRokW/ZuO9bN6/X63Q8flAlw7IgGSkzO/eJ45k1Wq+ti01sEdZA9zkoT +VmVrGQo2zRokW/ZuO9bN63NDPumt9xAJMEP12SPXSnVwOUffjfek/z3/ToWdpbmGJIlq9ap8wrv8M+T271jK9mxGqM/8s3N+90r7p+FOhUg= +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN64/n5EL8V7CIqhp9FPzHEuQ= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +7oQeD4Rmi1Y5D/LIXmHxSfqLHPqYMZQsaxNs+fmYClg= +yXqkSqtRGSEMRZqQHZ1+mk5ETQkjHG8RX3fksiZJH6g= +KZTmaJLp+FU9X93j5Tpqtw== +ICNW6HFddBNmnBhH77VOJpT9s18JQsgSzvTZHyZ0w/4BTSBkCJlMBEOpTsvIP5F+ +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXtJmfln4rMhDKBEG62wPdFimDT8DrWkwoNehtLLH8mTfjv4GTcLa5F6IswN1F3EXm5eevHvy7XTk9dd5xSeqaq3iWTfgmsLs05fIcCd2nLmSfim33RMfQcMyMyH+bNZCQ= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+ApG5Mqf2WNrCxn3Gs2J4gOKJOfwShy25ZiJUHypApjzbExoGzP9ZnIzmZeb871vjf1OWvvHB0xalVmr1yKPjHm +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IH5JkziqYhKBZseo0PQgzjjffFH7hOgJvxXJ916evBfNF+Czpo6v6J9zA5Straj6eLiZTyh0X+Lij4+07yiJyg3L+/nKZjO/37S8aq1NXQqY= +b4OJVZe8QyIpjuTpKXDL9A== +hSTJYpfOKqYLHJXvUGdsqJ27r1F8m8FTyXTwnXVWi/gwMflgvTRpPmpjhbuVtEc/D4oFAlC0tXqwyXGJrWKXr8XJhcHJ+BIOKSJ2bNuQQ2IiG+zkAmA072UbR6+1amZQ +k1ktu/l6HFULD7Vyr8Cv0HMlLvOl3L1DvBU4YpKX0wBIjUg6dQrNHRkXanVRfhKXrNAnH5kYjXIKcVHFQPJZnQ== +k1ktu/l6HFULD7Vyr8Cv0JUVYI748cB6mvkJiRhx4Gzmd0xowSn+pxKufrg3tRQEiN+qR26hQm6+FP8SWt1NvTBFRMGZKZLENueY0QAf2XEwTtcCoTzjnubqEJqekjtm +xWoGNWjKGPfI4gq8aHoTfIVkMgneahEXssm3KpTrqpr5HFLdQc+EjdkYRpshTbPj/FzqroxUZeB57FAHeJpOBw== +NUvaU3NpUZfa22aObUMgTYksNTb1aGXRl3st8KW92Rsv2ee7hf5/t42axhCc2LaZMCVUBuj9c7r1sNAlPrjZWEmcWdRR6B528+MZD+zeF8g= +PV+X4fvCTN/6758fJ9HMvyJNBHisoA22qKMtx3QNPL9j5Nbn1tfk//YG7JNXM2E3LE54k6F6pCjzabbI8uezxw== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uZqjPvbxWGrU5F/9Mfe3I3cQaISW5zLheEHDcMmSfSBTxrJ4RTwERVV5xP1tPbQDoa3QJdzK+tQWy8CxB4ANhIwZ0D/AypHVjTXbbIoOkq6L +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +5bq1N2nQOsMJOSjumB5HbtNgV5IhQTWyu3kx0nLJVhI= +KZTmaJLp+FU9X93j5Tpqtw== +F5PB/Ezvy87BYEWhACXaHg+G2+JUNqttQ2BGipoe/HpCQCzCmv72B8lUwrzg28+k +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNVE7/nTAY/4DTMPMCxe0BFtLYToSYLqyUhVBf/EV63jXhcNZIHDDe6HQPwUa0kyWFT32N1vLAiAOvAT7fU5rdXIepRDtCEJjI9PR4Y4xHvHRw== +UGqQTbo+FQ89dDBUZtFrg5DxdIZZTfO4XMCcertveHM= +B9q3HJxjdhik+xeIzU4Jb+seILuHUGw5SYrPoE3Rqnw= +WHkOzVx7seuLmxs5Hu+l/qqTd4Qb9qnLQgtLGl5Gi+JbwOQ4t8GjAIXgCmAhab1k +EiP/K054tbB05pkitbcV30bEwW+W/C7CxFeH+V8oCtQd48ujpIzk3Na8clHX1dQ6 +wlLHv6kT3Q/RmtMBN4nDAVvgi32EgeeClpdnP2N/vcs= +VmVrGQo2zRokW/ZuO9bN66M15Npik7O655FPzJgq2Wqqn8DYR5Ke//SMXY15WGpT +VmVrGQo2zRokW/ZuO9bN6xZT7d8s6PBBzk+QUE5OFqPg0uFK6TvMCZaaARpX7A7LUoi4zMHgUUUEwXqc3agjjw== +VmVrGQo2zRokW/ZuO9bN611xUZIvytu5Gk61D+9F8MsSkVQh32Q03nPjSVxte+Z4nBieQndu1Aj5lvKbavuiSA== +VmVrGQo2zRokW/ZuO9bN6zi5tiZ+l+/RutFjjKVqzPJkMdXh6CpSlXrI0U6mysdW +VmVrGQo2zRokW/ZuO9bN61TuBb9tXEBoRJN/gin7Z4MhGb/wu2L0XkxMGyLo8nS48xqakc5UMvRQYiOJKhGpTFF9kL5a7zBMKM7IJHY2FiU= +VmVrGQo2zRokW/ZuO9bN65TLhtjykfNX8WJm3Pk8YW9wqUJNdhnquGvjaQe9nAEAcWw78IXj0KBEhDvjixJVT7z6aeJsKaoK/nPDQpRZceE= +VmVrGQo2zRokW/ZuO9bN64htQvpIoTo9uIrNUaqC7ysnNeOSpoTevFJ4BT/hGFM8 +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5GoIUl64lV4c6SM6jvfq6ZHRjPDd8079G2HEigi/ZfKC4Q== +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5GplD/uP1150ghFS+BYu/BamG/T4xQD6Xbs8SqOlow0H2KXfbDHQXLNog8EBLWEUhHE= +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5Gq0d2ZE6cnfazkFCFePQ+aqXYHgHLHiD4y+zDPj0czCQUlieFMRFTuiURI05jIC8LO4owCk6/6Vbwv+eWNKllLGSBLtQ+B5DOSD/NKOY2/yoiAS2Hybi5KXRKsHdHO50DH6shFKOeIEeUNisDiCVipLY3mfH+CDhejxvwQZNsp//YYuvRJo14m/n4uj8RfcLGS4Ld+8xYRWQhdZjYOaJiiR +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+D2021K6m4bs2o1hVfLElNe60aysgdIwheeBdFbreKFe7rJFqa1bYA6PILIzkpa31MAdU9tyqa9Vg8ArQG4G0A+2B4T3UKCyJQwlEpXGbjiUijyAnYRD+fvROHSP22zdbc= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gtDMVu+iNCTaW92ygiohkArsnYj8DZEk2XNcYZHd7kRFH404wvz3DwFVfB6ZIJyX9k= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +yJPjNZwRskKR56nld/uGbduJY1jzekNuDeo3AqUOCi8= +KZTmaJLp+FU9X93j5Tpqtw== +3py9oHXgbkm3kj5vZO424kY9EbV+2w060lPtk1RV/So= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64iO862fVKEvhHjtcapW+VHgbaTOI7XkhvepSbVjtoaHSI= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3ICsdE6DJkIqo6NqgVbROB8ltob0F22NhfxLMnSWsptnQV/2EqJqMrHV05DP1dh2QH ++m2Ig0EQEJ4Gc+2v8s5xrnZRBGAfDHuVg5r9VE/U7Dqcsp5mSPEG0DHHYcBZkfWo +/0ULpLqgTvInFD0r5hHANokPEeujoIOEEMQg2gP5hZ7UUQoqIjD1Xd/oZDwlfcsy +r4lXdc7E0Sbufy5J9lgVEw+k/OsdnZeoR4umjEfazo4so584LJL1JvLbSnSv0RnlMbdUX30yqQUO6nKGPhmNRqRcO6RFHU4qH5PVHX7n7Ng= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +IiaIcsewT3XJ3flbzG9czZETXGNwH3y/VWrivm8ME4s= +KZTmaJLp+FU9X93j5Tpqtw== +znSKQADK6yp9O52W6r7arDGBJVANQwVc4yDg/LI4eMo8u5U5a/7AWstzKi372NPeGEf4x/fSQCRmwq+F45NO2w== +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DHoOe8nxF4SPJvnopjp+aXp/KQoD44qBgpZViiCaDvjdfmvXT6xn+iCCBIQzsirNbuT+eoAcUlA09mHzRIOqGZ/EYpBc0376dZodYLjZbw9opLwbcYBtvpXXHK+yTTtuo10WBsbgpHX/YN+DeVbP49SbGnqhpe6tIFPytCCaMomQ== +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IJhj7qdo0pX8MRXotsDWzMgf80RDCpPxKyjEFk9lQUERbV4chjxQhuGP6lP5uqlh7smfTguuKNL7+GaMfGKdg+w== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKMlEIqjWm8+kOtLiMZRGDiLR7B/+aZPftSeL16BO251zD/ekYvXghGe/B4OR2pKhCi4e0FcxpqxYknLcvp7fM5/uW2MIwxhXJjRGmh83ppP6qmYI6KMRl9hsl8LYKb/k= +BIa0ibHnqUboaK9kYHYO4i2dMxeM+vZKXAzDd8j8jd/yn/ENK7wJenIypTdgVnal +q289llODyfpvIou3XwdsGimFXHjDd6hIxEHPXbgygNntXvmhJR+Jqa1CbILKAv5drJNbn0IVP138z0q5XdUOiWINp96cMzaVBk+RM0VKb0uqMuIqlhIW3bAsgUMNZvUV +D9nFoWE/zqXaNxfAecYXR4Fh5DHDUtWIpaiFYfWvnDw= +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uT4RR1LPOL6E9tmqLd6Y6X4vLBk1OvZe/5L9ln+zX6CIit1RotOsuNgr+2hJUY/X/BSsL2A2K0C+Q9QJhBFSULwymfk1FVjKxlkVtAVbGek2 +kFG+HHGCYC9YcDUqd/b7oS1ZUpCvemM/AZwyk439i3N8ljE9lCYl7mEZugjCSGeT8rE+l49BssP170i7a5XEjctDsYoR5zwoa+zzuqQ8603rvR3tMX3pd2Yr/rfDLpwy +o6QhOIN2Sc4SHELnst17uV6uru3KtN7sc12yr5NsDmNgF6lzBfLQ5gSkgIMKaKncHwM7j+KEq5dXilr0gom31GjRPKOwT7x5i8iT/CJU8vsT0+LsGde9gh1zQXKdQHJ2rMyf/jXKkITuuyk1Yop55dl46UGYURcETbX3huzp9J0/LYbZxHuV7qKqJHeEM1zGUQKX6q3nIZFaQIT07DIgQf2V1ZFSqlKSpxJzKVTh2Jk= +o6QhOIN2Sc4SHELnst17uTGbfoG65qJYuOo7HVAUfwrrWa4B36uJ5REt040T2SNmGAV0LMPrERjzC/oRk1GEuX7AfuSpWeNyK561rxlWmqKq8esttm3BDwzTTA5Wp/ZKFGU72IJK2pjrHuSoqkzLVujx2qaqM/Km1yCxuLXdC2UZk358XQ+ddlTAL22cH156434R092KKggual1jvxpED9VmPJ1PNoUP6cx4WbWWYCyTyPc7+BhF/kMm12S2r8/z +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +NZydr5h7sqaRpfl7TbGhKe216Fai60U6XhMGhAUjgS8= +KZTmaJLp+FU9X93j5Tpqtw== +OviH5bvijIV+DBBPGXEvCeQxYzYrA2Fv4OsiuQeQbUkmL2HMsYDDAMjOhkCr2Tnp +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANnKkcJSE/H3VghkioZ1tnjyunYYq8XfVeT+J18IgTe/zFYLqRUYiaB1wowQIrBkYKw5d33go/f72T4D/yyYBCiG4Q1a2QpXXpdXNM2eyLBKd +yqOVJCkAGOhqcQYBOo7yqXSmQrhxny/twquABNpyYQrRxdmuEjhqv0I7ly8qcq9M0YOpjkkiv64zNUhRH18xB5+07iPi/pvu9+xDvtZyVBFGBqdzgHOGVcUQ1iTa9g/Z +VmVrGQo2zRokW/ZuO9bN6xR0lUkNHI1Unbm3W80skQ4Fc8+zISiEmEeRAyY2VWkD3Vn41T9b3eYLbyrqcfI2Kw== +VmVrGQo2zRokW/ZuO9bN61CLj991rBI9py3Mg2t0s6qFlO9r4CArF2KyduCrowCUtQmQHS+E3guomUvU9r9WWk5BCrM3zWpMIxUdEP53b1SKKCiLqXwN3v+Gqd3LFJWz +VmVrGQo2zRokW/ZuO9bN61qoeAEMHMJQFtGb5H5iFFSLl++WsYL8tJ/EWHrHSPF/UpymNQjoFMe4e5Air7Wb7A== +ChQf0+VWU2P1GW8Y1diCReOM1CzElHSu6IohDJwDQKQ= +lqgpKSCmWXEPVsTfAK5gKNLM+j7V3wcFdbeGqSPza2A= +TBZ8vu6Ktk1d60dO/QgtuFjcDR2A6F9M+r4Nr+2kgGFTuQ6TqiiMd0CoMviLaqVR/HASMw71dnfQOt20QHCyfLwl5vCwrQMAH2kG4aMJHOVycZAk7NgRpvkYE9YxwzukntI0cZ25BrrOM4mZg90pJhTYUvd8nGsixHMv4qLLZMM= +6TNZQyb72eciPdabT8jRGfmuaRvwk/Mze1bnDI+wkJ9pu7p4D8bccTL3dM+QXYUR/W+Fqi8avhGjWlgxFaXhVd9m0d+D8yk8VsnB/rwN/QcmQTSfWr8tEdjJPInELiWDl2gvFPziker2PRSkevW8S4IupBgdJzCWboThvA465n0= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DY554v+n5AiLrboeHZMaNQuH5thKl7X1yjAIM4IcU20xPRP6qFXv4bSQVe49cNneL7k17W0S5V/B9GSCvAbisSnhCOGMFNXd45AK1U26BUCDGK0K8Nu6yOQF/lK+9KkSj8N1U6UsDosAlbOSk8Z2zojHM06HkJuGC/EO2ZrAXACC5apDIyUEMCMOLfxRR7Rlc= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gtyrN3fmhNqvxN36kot5RF2oafj5DnCTDYuiKfAO0vfxHXDV1Se5E88TFWe4ZWoVcY4oI3KLyRWKLDNgW4dsbT3mdksYnoqnqqy6L0m/74mcb2fh3zC9jkL+4KDa6fIPJEJ9YHKPrMiItYf4z/dX0Vk +Cq6vhHOCmT4DWgfjwQdcYyiy1M1IDdVXJwLjV4udcbI= +Lp+10g3H3f14x0jksZoIZ/GLIPoRjf+Nw9N/DO0k+YU= +keqgOa/ELntiDC6RBRh+WL3vBVGsi3+jLKV3Y5rnZbE= +xh2vymxYeeZomkH262+n7plVUTkskt6kICx3mGCen7E= +b4OJVZe8QyIpjuTpKXDL9A== +wlLHv6kT3Q/RmtMBN4nDAafzR3IJaSQy1F8fEUDT0KURDliq5QrdEI8/0cInAzfT +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvMpc2DCYsr/Y+VR15aQe/co +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN64JUrjunpKQspTKm5wPUxf6/8fn9iycJNxQEzrRgKGCtNOqMeXzSymH1TgsVYcmN2UVZm7EhcksllhYNLkg6mD8= +VmVrGQo2zRokW/ZuO9bN62tDGDWvKCYUebsejX4RaJAFq9louk8D3mueq5NpdQByFjpctkUKLYKwcbguVOwJKXr2njOXfo1Js6EGYJSYwGw= +VmVrGQo2zRokW/ZuO9bN62fDdtc0mkszRhY1uV11YW4hXfV+ivpQSVthbEPY7zMTbcPEWtnBUa2cpauAzQSZB9CvXLuvdzmuAmv6Dgj0s6HotusH5aCxgCaT5MVjFPJlTwRQSHFT7rVUuNFtt0zYAg== +VmVrGQo2zRokW/ZuO9bN6w4io/b2JfrrmF5Ha/WszofXR/BET/NWvwsve+jrPERFZuO5aqMCRqJtW2w1QEntkRDN8kZmDAQOrNWvXorHXstYfUTJP0rYpggc/JT4Vigt/AU4+WYOVn1ZA0tKYQv7IA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +1u+XjG/2+GSQRv6EzCaWRQ== +NYuttixIfaRvF0QtYZhNf9Sr9Ezmh/DjG0auW/4hLi4= +yisP9bnsS89PFbBRJbtYnF/l6kdsBz3Rim9oMpP6KMQ= +VmVrGQo2zRokW/ZuO9bN6ynLlFbGdWgwF9oAzMWAsfq3SP9mz9i1GoYlYGPxKaOg7yvwd6pL2IHH2Pn++jQzOA== +VmVrGQo2zRokW/ZuO9bN68MToCp8Q3LqgKNwHk9HXpVWQpUvTYbzaPq6og2ymqH55WRDGbQdfaEUR9DX7Jx86w== +sQfTf8f6jxyRSx9SNPNEtQ== +LUyc0dkTAKzugKnygsDP/38QmLeSAJyAB/Ppb3nQQELYQDgknMMUXYjkzF/EL+pL +LUyc0dkTAKzugKnygsDP/5Z4sHWYFfh1kDOdFaqFILQ8vsbTDmaHqPa3BecYk48U ++kIRCoW/7ri3diXr3wWp1w== +nuQO1if3lMKUHCYi1j17dU2TVxcjNRiFeijXT8u08XI= +VmVrGQo2zRokW/ZuO9bN60ZBYPiu5K44Q81Y3HdJjz75baowlOpwAvMkyYnEhssZkO2CI06Zd/H57Fc7UDQyZQ== +VmVrGQo2zRokW/ZuO9bN687Bhmo9x1q4Sv+T3D3k61cVoxASpAc7ExocFqj5Uf9WIWfFdWMJcFXS47nqSlBKtw== +sQfTf8f6jxyRSx9SNPNEtQ== +LUyc0dkTAKzugKnygsDP/zoq6bTmOgvD3qTc1/nBOA7xH4brF+K37o7urVklYQvN +LUyc0dkTAKzugKnygsDP/8vbQfLRKD1F6cRboe96aOaYtpywuVHYyNBuk5TJlq3i +L4gdNj7prH5DbeXua1XCbw== +0zuirfbdySeBs+yooVipit3YVEWuFLtLLm3mSba015N2OChGckw+zhnhhTa+i2Le +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +HeO84Ys53EcJxPZee59V7+QahpyULu2MesUbbnsHLV8= +KZTmaJLp+FU9X93j5Tpqtw== +9LbWYShHWRb8kqF6HqdNj6ezf1RzgSeG9JidnRXnGdX/IDyjSP6FOKgxBDMRBccZ +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5cDGSaMte+M461CXbcrSNIkerQ4ssZDfTqopcwoVN51k4ZoSD57ED4izO3AHkb/Y1h9emANNKxzlKU2rzEatXtnMMAXVGbjTY9r1q7LoAFlE= +td0Hee4NJPblA28GiIcOmVz/iEnSh7ccFLM88eucCwhDxWpOIeDaytxmPiP2pwkpNyGbS3x73U5/Z+SQSDEikA== +0ow4r3+bBC/sw9JQ+2+N6bQ79V5uMTUjRNGc4T4cAh2FOu2MjnIIMRWqvnMnFGnbv1x4EnQ1TdZXDdS+rZQFbiHH5FW4N8dc1NDPckXp5oqoA3pk6qKPzyG9VkIif6AEf60pT+ibksesb2c/NsuCPg== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+AOtWH9LO9ZBEToAbmupyOjq4r2xS6X3ooTK59khXwyxMphKsyzdV2T4Hnszg9ewV4f/dsIou9h3HQbCNTQbOqUUlvQ+ORbUWrUhrs858PljmTQ4m3bPKPNC/8xZrsvK1RhMA9bQvcU4SvOcBc+VC6r/Jy2XtcAdGkQ1dj/iVDkK7B8GqblBnZOBMc3y/hNrS+Lda5EYfUX7c8XRyzH2cyASXyprblojVnmJrnkPYRNRsYwGz+hcJ3OZTPm9qD1Xsc= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IfkuRDQnQwoXQsyTGB5dwnnwLgkEfh4pR18v6bDOnh3B353j8TPunwxYTg1rZro30xivHOhHgMr2UMnoD9xg30YM1g57qkcLLdVNAFh58ZjMLTTcNaotcuDQ4ROv1X0rJ +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +E7HVj5u79lsEJBByAcI6+Y+iKetyWZpxWwXpNnY61L9K9E5sTBYilct1gvcFQJXX +NcZ6sQ1mLPT8+JyzvATZXNzUyynsS0mSwmHNHb0WEkKhIEOCarOBj/VNewvvS2r6y/Nzg2lxm+39+tlylXcMGzNRdESr6Nwk+sO8AiKTri5lczg0Joh6jwYSOdWZ/xzs +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM6ydIkN3ng0PuKyALE9oNtyobOGvrGiji6Fktt/Q/jdyF +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uafQ2FFgng7Owlb2ojgdsbmj25Egdnnu7s1LrnHeckFsmr/dhQ070oBFuJsDLG1N2+O22yM3TQqnLHaCcXMPL5aaMr3+PJKPzxB29pbOHwL9 +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +2ard15XnfQb3lp+vey3nABFSCrBCIZnKXwgggL5wjCo= +KZTmaJLp+FU9X93j5Tpqtw== +U1uWmisr/Bzw00RzQojfMqW2zprEuuyc+F8DxOqhiC5mNNbPyRjvX4X0n94q3zbo +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5Y5IiAYey2dZpBSLvBmCmiTdM6LndDn8aYxZpLkGl58QmsrFNt/Xhzti1amoabI3SDkQv5V3yFTOwPCfZIQxPCfECh2PhXI082wqRdcnmlnw= +td0Hee4NJPblA28GiIcOmTuwGfcKcaMQUQAHvx5+SrqJRTOnfpxLSMOragNj5LzS +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+BfByZDBmKK7K3HCn0xbzDN/p1PsgAQJWE3qzjMeMU1gCmeV57i/W0t1+UyJzE+KaZmHfcCRIqlb81oSG/kpEejc7iBJnN4FWS3kw5GtvDOJA6er0KzkFPZyw7Zqq8Snsc= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3I1HQBLdpHf/UEi3kk35v3/r9YpR/3yiYo+wEA0D66txcqEjQV/544BZpZ8QEwYr9Cl5Xu0TtbFHmzTQHYZArivllZFNvaxVOwvVEpgBPTF6A= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +b4OJVZe8QyIpjuTpKXDL9A== +gZ7BoIAABc+I8g/rYzq+fxN650FDIkzSAlL6+Jr9FEI/Q3wY42Db+gtvNHy5SguA +VmVrGQo2zRokW/ZuO9bN673lu7cHUGvKUqK/AdiZP5r/K7bXVjEqb5SYh7vvuzRX +VmVrGQo2zRokW/ZuO9bN63QrZyqUb5BKoT4N9cyeKvyfSYVxVAmHuRZgE98udr/TTkHnNOUUUGZ2+0jty8b2cSsROo8qzEMZ/XSVHRgbkhA= +VmVrGQo2zRokW/ZuO9bN63CK8jinFx30LvrC5BheN9+Vzu9dKNjPscy3v1elx2imYJ9CITDn0Nb2t/sy83JQAQ== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjao/TArw7d9PyMMKcD/gCyakAqAOInO/kyLo0xwBhDw4HFB6R4lqJg7BgJThUr7tdk= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM676D02CK8sgxlqu813CFwIReF8PG0JQQtpVrQcpOA1a0 +CW/G9plDL3SfzmBxnqr6q87wTQCNFJP1gF2kN0sG2a7bqaDYcLhdeA9QmQTdIHLVcJ0jJwg47dIkFyZzmF2U02GNPNgixcpO69jEa3mc2Mg= +1V2v8QerKOmubvSxgB4eTM++l0T/KuLt76uWSU9JjRixsDS2R16+WnBtvujyRShs +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VWSi8LKj0objBUIL73B+ABRovi/1tQfrdsCJTFxnHsgX5Nm+Gz285S/IPocayx2mJMK8RAWx+2TsPclXjuZG1Cj +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +oLkjJHDOdcPq1MvWDVGWmS9Ci5tZeDQS30Leq2hud0RyBR33jbClETck9j3BDjgb +KZTmaJLp+FU9X93j5Tpqtw== +T64f/w1LnBT4KwoMv72GxqryOhcsfNMoJd8yAgS0pbE= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +KgZhnCYglllfsrazbVxNli3dO57PXUycfNR797BU9y4= +e1qLykdSSG57G+I4DSZ3lPbnd9MDQtXYM1cRPgH2NZY= +N5xldEoitd9eWUThCvdOTOnQKBuAThYmYC8u+4DcAu5Tc/F6a1hQxBvmeWgTOpVI/wA+jhFG50+kJF42ji29zmrDzE1c92kPIwz6/8Mjv0E= +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5dq1EJSpcqAYEPXy6WaSHxs9fAXX/i7MYu+4BBwECfWZNM+SgGf9wWDjprmWthwO3+OXnvBwSuKB6jb5d3MBnTQ== +b4OJVZe8QyIpjuTpKXDL9A== +p9oVsBgcyiBMjDb8CcPOqFV5wpmixLB1nyXJiJ2HPNs= +wlLHv6kT3Q/RmtMBN4nDAXDoAk0pYvGIaqdbi2BBOVs= +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJnCX4DFeOIdkDtEirmUW5Ek +VmVrGQo2zRokW/ZuO9bN6/lQLfGZFAL2XD1b3BIrGimFqqXjMICw7zHJXBpX9GU+N7Igcy5nBNFAN5iTqyRUqpp7FAYH52O9YRVJDSmTxBhJ1FbjehHcE7Xxv8M8gnD8 +VmVrGQo2zRokW/ZuO9bN6654aX7czo4xbfX5k3Xe/kWgm50rbPsjEZDMcSxuTnM1 +VmVrGQo2zRokW/ZuO9bN6wFRMguAm+TeJk1+fnt1XFUKXJnn5v8/CZK5fl5mMLFYBNRgOK3irni8g1fo4OxwSz60lYH+vPSgypNUpdk2hSo= +VmVrGQo2zRokW/ZuO9bN6/A97cjuU4K9eGnKJMbx3jem/I4X01j8TXo9M2kXhK5vPAbexcO0WsbiraLHoYAxuH/s00M7q68EnsJkSQu+EdY= +VmVrGQo2zRokW/ZuO9bN612MpsaDdEzPe6hUSc/u/3d/wHgKOWbxgXCW+3+76tnnqUpZpa7KtkEvjfjCd4auFj1jVZzIo4Tq1x2v3NP6TP5mpfH2V3IprP3UaIKvPpuQ +VmVrGQo2zRokW/ZuO9bN66zBSwHJm6ElN+7ZdiAVZBTvBew8YxafVWuo2yjvJVU7XQLDpZScPIbuHyakPvsAt6MbXk5duFoiXazpWjBOs+dS3qJ+rWeGKS3yWHGtPNmE +VmVrGQo2zRokW/ZuO9bN65a1lvAsgN8t7bU4ymW4veC29rX5B8jKlrdWpQ78Mp7t +VmVrGQo2zRokW/ZuO9bN65IMmCegrj1iToe3m+KEt2lYR2GShaC1HZt0ENbtcWDT +VmVrGQo2zRokW/ZuO9bN6/X63Q8flAlw7IgGSkzO/eJ45k1Wq+ti01sEdZA9zkoT +VmVrGQo2zRokW/ZuO9bN63NDPumt9xAJMEP12SPXSnVwOUffjfek/z3/ToWdpbmGkgQ/+uK86U8g1oipbDnW9PymzEkvRKIRFdQUbVx5NK0= +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0H4xphI80JaVwdOBqT4vpFoYlr2YEOLcHWVeU8XvA6DcJG/8/11CmMgn+dU2p+MiteDOVRuiE+vkRJyCKGHE41wHxivV6nzAK9IrCg/kgKo1r3TbCAho4S/unYWBGxwmrqp+8ZOvtXQB5XA8bjBzXdDymC/ZUqQHO8ss4ZVeIjTA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw58tqRCidnUtuhiV/SQek1uLeqFhZN4Qp7W1PBlJxy9Uij4W4Ylrlk/HX5S/Q3N9gbstqjFhfBAcmwuzubPT0II= +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0Go3ZdM40X+nb01il25Cd+Ij2ks0QNQ8ckELJbWPuSoOaKJkHC6l3WHCulMTp8j+LeAWCb5DBJpRVpiRip8pDe/KIfBFKsAy1uPllcESEV07XCLR6vETN2Y//6YxPUCufIc5q0ZxCiI0dqCJvZuBn0 +A50UO/kAI17YP7MCbTvBkFQ/jf5eMs11PzZj1m3IBsQ= +s6ChnXH5zaR4nss2Jj7ULBibRmB/kmin0eYU9S2eTfU= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +2JIP/YvC/EYiPSt6qDLzOQhm3De1OK9Fj2Y8TJvQnSE= +KZTmaJLp+FU9X93j5Tpqtw== +n4iAFdIZCZOiKRB3MX5Ggpkc+IH/i74d6PZkAfctWjw= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKMlEIqjWm8+kOtLiMZRGDUax5h2UcZ8Ya1rrZl2aGrIJbzHdHHnX+T/+biFUpKbdHonbFld4dXvY+6LOVTF9Nv3yUp5wratDvKbY8p3n8eg== +td0Hee4NJPblA28GiIcOmbsckmQsAXhpobaiY9HWzrc= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+A5lwM2h5lzBxHhpyk75OffzP5T6ccpWZGCDSLAwHQAOqdLucRP8vYEN1wPagfqO9A= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3I6VuQRbRuF5/N81TAoJjY/0z7udH8+YFAivsDd7eWtdtCqEwXmCnT6vpxY7FYdo/A/o1CeWSjqxfBy7nqNuAO4ypjYC7dTdgCEv48YYiV4mY/NyGUA2VOjNuy6IyI12wI +kzM691k2SeYKeNMbUF0yqmSymIPsSgGyT9n/RVKtUNJJbYbqp5uYucQrLfQAkK40GofBHd/0/2BBbBXjbCtyxjMGVWZtqZoQvgak39kHPuTE8e+zClutQI0HPSrrwnOz +hQvBDyxoZ883xMeg+G+dguIRaVdeXNIpB22dELbA1zShjsM2m9SHvl2Jf0Gm2g69hp/QoBxrjqsFHdLlWcqnMJQReKNHOuNET9qNrMQDIuNPm0dXTZGq4nNCZqdwv07V +ncUn2TP8ZQ4fZMBYk+CUrbbEKRHejS+40NUHzR6Awm8= +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uXr5UNbgUmXTutS1OHze4KAGr7se+yI7S4wrgZcXm/EOOFcoL0lqR7Jo9kpGphoKPg== +kFG+HHGCYC9YcDUqd/b7oS1ZUpCvemM/AZwyk439i3MszfOTyV8WWbttn3e2LEnKaUTIOxww65MvXZ3IghDToyT4lC6GeuPdeiITUZesqYNzMbxL9aEgohkmBpjvYNb9 +o6QhOIN2Sc4SHELnst17uV6uru3KtN7sc12yr5NsDmNgF6lzBfLQ5gSkgIMKaKncHwM7j+KEq5dXilr0gom31GjRPKOwT7x5i8iT/CJU8vsT0+LsGde9gh1zQXKdQHJ2rMyf/jXKkITuuyk1Yop55dl46UGYURcETbX3huzp9J0/LYbZxHuV7qKqJHeEM1zGUQKX6q3nIZFaQIT07DIgQf2V1ZFSqlKSpxJzKVTh2Jk= +o6QhOIN2Sc4SHELnst17uTGbfoG65qJYuOo7HVAUfwrrWa4B36uJ5REt040T2SNmGAV0LMPrERjzC/oRk1GEuX7AfuSpWeNyK561rxlWmqKq8esttm3BDwzTTA5Wp/ZKFGU72IJK2pjrHuSoqkzLVujx2qaqM/Km1yCxuLXdC2UZk358XQ+ddlTAL22cH156434R092KKggual1jvxpED9VmPJ1PNoUP6cx4WbWWYCyTyPc7+BhF/kMm12S2r8/z +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +Rz4VePnQFedRnn2vEuyuMMWmrmrCy2te1mN0yZZPeFf/tPi4zDP/Sz8hLtaLzNpv +KZTmaJLp+FU9X93j5Tpqtw== +6mbfqKQBA+uDZRQFe2c1GjvCRYwRRIW84uHjS5VFY8riJ/whk63HCANV+SfATQqk +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5bVBRiuiys3C14OcWDZeDhsb7M6ThnoThQZMir8c/N4fN4C/QfrpDjbsTSrY49RV8Ds2C+piCS+iPG8dLc0wgfQ== +td0Hee4NJPblA28GiIcOmTn4RzFMEvXiU0UZLJrdfkCrgL97jIVINknjWk0x+1O4 +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+BfByZDBmKK7K3HCn0xbzDNUpzYPikvK6gsqSpFFB/HgzVgjoip/a8H78JMmT/0PXDSGDzhZIgh9eXeP4s6EaxotE1e+SBHya9ymoCIszuucrnHyOJrpOoPfKv3h+TSd1Q= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3I1HQBLdpHf/UEi3kk35v3/tvXJWw4f6CagBPpoTfZCj+Lmc0U1XGVkEFGtq5U4bGibDNW8X8NONEYSmiHAx6wv6vqnJSoZCL4gYLNaDY3MTBdVk1PvJ7DJAZqmf8BDNXXBCb3ywi6cRUXaomQCEJa0P/0RzrpdU1u7zAsUxjCgdk= +4+wKB+Tel1iow8au3cOIJW20IY6a/C0B44B/KjPC0z0PBCwjA2BpF8P7X8qas5oE4QSzW2DfcB4JA+JKqVwe2bK9oPMOFT4IiqCjFQ5nOrBV5TVuUnwRK5fUL/jPXykk +NCNZbNM/44ZjjoozPyWc0+tM/Ko39cLvmZ6rDu3bqd56+rv+caEBclISWvT9u3a+TK2k3OkQpEHdSwPR66zFBpcPlkoPpLCn1AeE9BFlqr488UcWR3G6Z6tlekXYyIda +4+wKB+Tel1iow8au3cOIJQ0cJN9p5Wagnh0MqbqOZlO9xYInEhd3gMa/cM0J9Goc +Z//5BJjWi0XgA176xj120AVcrX6xxmymlS4iQjuS12A= +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uXr5UNbgUmXTutS1OHze4KARyxr9E96roa5Gpi912InxCeT7C+HJljZhbQ1Txb1yYBlO/ZfFkaSTm2ohx5UydoA= +kFG+HHGCYC9YcDUqd/b7oS1ZUpCvemM/AZwyk439i3OEAGHnGg7APcs+Q7Jc+pmL1zHxbcw19Bs7JnJUIIsYm8/Iz6SRn8WolmmMLo9qtlVcb1r2LlcggL4UnFnE0Ma8 +o6QhOIN2Sc4SHELnst17uV6uru3KtN7sc12yr5NsDmNgF6lzBfLQ5gSkgIMKaKncHwM7j+KEq5dXilr0gom31GjRPKOwT7x5i8iT/CJU8vsT0+LsGde9gh1zQXKdQHJ2rMyf/jXKkITuuyk1Yop55dl46UGYURcETbX3huzp9J0/LYbZxHuV7qKqJHeEM1zGUQKX6q3nIZFaQIT07DIgQf2V1ZFSqlKSpxJzKVTh2Jk= +o6QhOIN2Sc4SHELnst17uTGbfoG65qJYuOo7HVAUfwrrWa4B36uJ5REt040T2SNmGAV0LMPrERjzC/oRk1GEuX7AfuSpWeNyK561rxlWmqKq8esttm3BDwzTTA5Wp/ZKFGU72IJK2pjrHuSoqkzLVujx2qaqM/Km1yCxuLXdC2UZk358XQ+ddlTAL22cH156434R092KKggual1jvxpED9VmPJ1PNoUP6cx4WbWWYCyTyPc7+BhF/kMm12S2r8/z +1u+XjG/2+GSQRv6EzCaWRQ== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +y62YGJfxIw6ef8JbuviiBWSxpyb44wiHodqhKrmL6B0= +KZTmaJLp+FU9X93j5Tpqtw== +RVwcA1dEXc+xOwN+lDdOfay7l0LhXFDOOUxgQf/xIAk= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +CGMG5LRrLyjmr66XNRnXQNcf/FN2KrQbe28ibKHRFdY= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+ApG5Mqf2WNrCxn3Gs2J4gOVvw02w7il4t0HnisbvGHY4YduN5akAghrek50kSlw/0= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IhDx8iWxquXZunJU5mVL7/VqsZSKV/+K3GrhX3KVQZghpRq0S+Y5TwF7MsnjJ/WxfCZso5gfYjIOEP1TwvA0cvg== +cOxD5sJ0lN579xb/M4GPhw3kVmt2grK1diTnTZzA0WhBFWh0NiJqiHTHuZjgDQtw ++m2Ig0EQEJ4Gc+2v8s5xrnZRBGAfDHuVg5r9VE/U7Dqcsp5mSPEG0DHHYcBZkfWo +15GT7DI/XLgGdHKj30fzw8q7e+nW8+9C3TkmYHTBvMc= +/0ULpLqgTvInFD0r5hHANiJDT3VB4wGOmEljuAQxauIwEB9fxQTLUxSj5siVt0NQ +r4lXdc7E0Sbufy5J9lgVEw+k/OsdnZeoR4umjEfazo7kva6TIJ+8qApX4pM8br8il/BdL8JkriTmAZSYpuq/9tAqSe+ugBhf+4W0shpum5E= +l2FJPs4YkAmmok1ulDRuSA== +o6QhOIN2Sc4SHELnst17uV6uru3KtN7sc12yr5NsDmNgF6lzBfLQ5gSkgIMKaKncHwM7j+KEq5dXilr0gom31GjRPKOwT7x5i8iT/CJU8vsT0+LsGde9gh1zQXKdQHJ2rMyf/jXKkITuuyk1Yop55dl46UGYURcETbX3huzp9J0/LYbZxHuV7qKqJHeEM1zGUQKX6q3nIZFaQIT07DIgQf2V1ZFSqlKSpxJzKVTh2Jk= +o6QhOIN2Sc4SHELnst17uTGbfoG65qJYuOo7HVAUfwrrWa4B36uJ5REt040T2SNmGAV0LMPrERjzC/oRk1GEuX7AfuSpWeNyK561rxlWmqKq8esttm3BDwzTTA5Wp/ZKFGU72IJK2pjrHuSoqkzLVujx2qaqM/Km1yCxuLXdC2UZk358XQ+ddlTAL22cH156434R092KKggual1jvxpED9VmPJ1PNoUP6cx4WbWWYCyTyPc7+BhF/kMm12S2r8/z +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +gEP05OadtLdkLYzVLnPWJtuEd0CjPV4sWpcuA8Bc8lY= +KZTmaJLp+FU9X93j5Tpqtw== +0gvMBZ4kovgEhxHWNAchbKTO/JlgzQRZj63SucHKPWw= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +gpFa656cOAA2jnAKeODU3AAgd95/CC8ayLarQGb5lNyubLQWTspFThZdlTMj0rfCwqOkvu/JHRe2+4sfRPXO9BEe+aKv8eIE6Ui+ph/DQmiO2BKts4OZwzwl/kbrmheD +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+BfByZDBmKK7K3HCn0xbzDNr3SGPGW1QVBFNvitxp4NtueJYE+UZBZmiy0WI8jJuAI= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IOzliM2uqy6AvVrnIbYzBb6iXCJHdiyjd+X+/w+QOeI/Mjam5uOMoe8Re6LfHO/Jzmnc4a3ZSaRhMJ4QG0k58rpV7WD0G4FI/AGg4MxTdtFQ= ++m2Ig0EQEJ4Gc+2v8s5xrnZRBGAfDHuVg5r9VE/U7Dqcsp5mSPEG0DHHYcBZkfWo +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +FBy3vCcAP87b/+M8ydgUJfBFLx67cYyGkiMKbGJXSvk= +YRZX6HCWMiKTW01+ivlbhAIWu/PNVBQgfWo9+hdhs9kxvZC3ZudmNefMrUxrLPI5 +o6QhOIN2Sc4SHELnst17ueoeSgiOQdahK7r1p0YoOKG+GFm7FBIFuvdjXiD4Jty/S0l6Ls4msakVN/hcKfNT2g== +1V2v8QerKOmubvSxgB4eTN1T1U8qetoMKVWXAhsfWddPSgYrLGFPjONflBvn6GGY +o6QhOIN2Sc4SHELnst17uUa6ao9WPlAvu6qJ9Oaltu+N9yoda7jqAbjw8dgbdvmN +3xAddec2urtvyH+TFd6l1MpXQTsst1/yT8DXfXPGrEk= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +lwkeHL0mQDDDLPNH1hfdfpmM6lMgNPGwcK2tYqIdJrw= +KZTmaJLp+FU9X93j5Tpqtw== +mW+EgwYdyL65jNEPCrA0ZwWKQbTBzhVydoIGmj/tuShzlyIlRPiIHzo8mFnkmp9qHnhBREmgooQgxhEx6+g8dg== +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +KgZhnCYglllfsrazbVxNlhZvo/OKoJzdIEoxUuSu8lsXUJ953DROtt9/W8ov0/2Uy3wnno06iDJ/U99VKO/wJ6hnihhWj9KvFR0gCHiVXANKPHeKBkWwHMbB3tRL18NSfH63uhuZkCBsbHYFiQY54A== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+Ckp19baRdobJWMhNbvSH4AO5DkozbXxMbeGpE/feH7BvuGSRBMHz/7PQ4wOt3URRejc+wF57CZzZ9EfIQjh/OIo5gheS7i0OW3Oeu12l3Qa7cZZdmALg/LcGympci9F2F82RQaoVLFCShpVGHU/GlWl58NwsR2iB97Qxs5+ig2F2e8DUiV/axaUWm1u3YrJ2GX9rLSRWcXFP/1zHThELnt +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gu/u/ZWm9DYyJF1WaRWt8m4szLYRnpzJhkglnvq95ps7XeOSDK1kvlY62CKviuMif7n2Mmbj2lybmTmobHcleWUc2EGPCh9WGRsXg5Dr6Lf+VVp6chVEJryPq4SV5TIO2CP9nBy8Pb7VSc0MyqKHEdYrq5l9lzSf+35SA/FEvWXE0qs8P2XxmOMGYB79WsuAri8FJ0kGBMMBqwpDy4X2UjfKHhcRMntbynLU+sO/97imYQkaPgxYpo2DifZURRPKOc= +td0Hee4NJPblA28GiIcOmclQqHHAArGnEN+3TpG3G/6oIQzAMSnmClJuq4p8RfBh +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXoz0tnByKjXrpn0PixC53x4Xpx0Ix1Z2lwFszcOsVRwdpekP4+NVMuYq0zb5Za/NtCyCB7VEiNKTN/tLrKLhTkSIIGLQlDqmIqZN6+PysaCyYqhylEOsE0UmJ944jPtek= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +FbS8mn3AvBwAFaIg5VB7LOzT9U0kJ5mxsQx8KNUG+5LCI0buRob1rf5RKOq9EDAvD204ODuaxW5pUhthVQx83A== +NcZ6sQ1mLPT8+JyzvATZXLyE31SvRRysQ1/hjQengeSM+y8n10m3af8e9oG8ETAQCO0q9SGk2Ohqx6GNl/3rh86tSpFetzdEltlMTixrnEM= +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +50qVUF0NwqssMqlRNPRPPpqMj6LbxX8baefoYpKbhOVpTChUCOK9IHPvQbc/mS1LHqoYfTg+KvGPXxnX7JArf7qx/5PC9hhM7cUAL0aFIzM= +UjGO21cC7sdtapmgeLbjSQFA3swQz8WfVfZ1OKckSOkQAvRjEFagtcWFL9TBY8IY +o6QhOIN2Sc4SHELnst17uQJAWihAAHNbOeUoWaFGGFB9Wh/G28ZIDmYw8Fa+iVby +L0eUthVnpkGsmKFAX6d+uGkPqUsUWC4LzxhOoLQ9PX8= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM6zvFOB89rC01/aufevhgpB+OHXClzxML5RkaNLVMRSyndrWXRAxADnCSvgpLctf23g== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uX3TRUWCciAkYHgiV/48FRFbL+GGan2NKhot8hPXtlt++f0g2KGc+U61KFJMmqRQNXsCKI4ttSMCoSWvCUqzyPYn6UV3HuiPo0PnAWM1c9nbuJ+zH48uovRGxvn/ipU5qA== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +lwkeHL0mQDDDLPNH1hfdfiUvzPpE2+Al7c4iRuJXSXs= +KZTmaJLp+FU9X93j5Tpqtw== +mW+EgwYdyL65jNEPCrA0Z/HbZf7YqLhq4ozQYKWbG37D0lH/Pu/ByyjqrUuy+3sl +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +td0Hee4NJPblA28GiIcOmclQqHHAArGnEN+3TpG3G/6oIQzAMSnmClJuq4p8RfBh +KgZhnCYglllfsrazbVxNlhZvo/OKoJzdIEoxUuSu8lsXUJ953DROtt9/W8ov0/2Uy3wnno06iDJ/U99VKO/wJy4MCRJUSGE+Cbr4gBuLOYtyJL4v+ANAq01XFRdIzIW9IP8V9bUqZpDUCww8rpTE/w== +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/hwfY+PcmHUBHGDqmg8nupFflJCXYm3kUTl++1ggJJQQ8JHS/TmguEAQrFARTn8O8sQnMFQCmypot0yJldoctZ0/s/5aM3/Wz4EZuhhxz14irWVVXH3qxdf71j0CpNXXKzWFbDx3eK1nlPrJrd/L48kUmRVF2BQNFomvLaBWHRY0NK5dSH6zapA8DRfUnmz42Z8BszLTKE+xNW1YpI9OPxQ= +3zDbxVuaFCI1nhEYDQoyMwlgMc9uqhh0gl/QmFcvmxA= +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRjex9aSwVHDL31oGD3Nb9mdsHgcdoHbrke3lOU1OjnP4fBvI2tq6+API/aYD8YVtWEVgTS73G5g/W6+ZAlpx4X/4RkIpBReNnGXcSmW3syV2MMZ/V+Bemzfq3e6tmc73qklfNmbkNxtdp8FqHpfKzoxlsus2UXk8esOZoo5ddrO+EBbIrFdyDSVuvBUkWY2jUtf3uny1f+8G5lWLo6qsX/Z0/J2Nuk4GeukbqDMxIfR/23KurY+yaue8uvWdm8RtYU9JXaWAsc/UYdHavNL5Koo= +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXoz0tnByKjXrpn0PixC53xWdKQvml7T8eOgThNFvdQGnBcayAu8Me7h5s2hlYcFGEHeQLuiMrummvBHmV5RJyJSK6DWzuBeKrTegS10yGfqxHRJjPZNhRIfic198uyDyc= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +FbS8mn3AvBwAFaIg5VB7LDGfk3PQI2b+STPhWtnGc8D3iulNRzeDuTfaMY/U+yzu52qH2fy10KLHYMa6coqlSw== +NcZ6sQ1mLPT8+JyzvATZXLyE31SvRRysQ1/hjQengeRCKPXtbhTAJUlGW621z0XKkgSRfauHtZ9aomyb7WCUwI4oK5AcjCtFxg8T0Pcu8K4= +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +50qVUF0NwqssMqlRNPRPPpqMj6LbxX8baefoYpKbhOVpTChUCOK9IHPvQbc/mS1Laq1B3OTDkQPLCmgfNigbW37+SZsoTGtAvaDWgH7sZqY= +UjGO21cC7sdtapmgeLbjSQFA3swQz8WfVfZ1OKckSOkQAvRjEFagtcWFL9TBY8IY +o6QhOIN2Sc4SHELnst17uQJAWihAAHNbOeUoWaFGGFB9Wh/G28ZIDmYw8Fa+iVby +L0eUthVnpkGsmKFAX6d+uGkPqUsUWC4LzxhOoLQ9PX8= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM6xcX3Pk89cGteJAfYh6IoNGakYTDDlTPcUzPja4BiQ6YkcwLdAKaF9O4p34Vm0ChxA== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uW0V0SNu6gMkNVnvQaya81p4dbSBuufxcX8hYuE+m+2pjG3aE7tNeDeTL4XXFoTLLtqYdCA64SICmGn+zTcsfvVou/lm7Y33q16raIq50Q7m +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +lwkeHL0mQDDDLPNH1hfdfsShqwPc+CUlLp3NmcMPEhU= +KZTmaJLp+FU9X93j5Tpqtw== +ZE/mIUUH6qV6H+P0DGv+nt4Y2tE1Cu+fFmYIchcr6x3dQ3imPAjJgyFCKQfqibPk +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +td0Hee4NJPblA28GiIcOmclQqHHAArGnEN+3TpG3G/6oIQzAMSnmClJuq4p8RfBh +KgZhnCYglllfsrazbVxNlhZvo/OKoJzdIEoxUuSu8lsXUJ953DROtt9/W8ov0/2Uy3wnno06iDJ/U99VKO/wJ/rbeDYVTRn12IFOo2gbCeFXXhFBQ3YjA/zkr6p6N9LcaIAoH5Q5/E87/U/1SgduZw== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+BfByZDBmKK7K3HCn0xbzDNFqqXFzNmD6bGgOB9/PE1TmzCg8PBCY9+RK/3mbcJkupjXVE++SIInpFExFpl4aBvseXMcSLuHWRNVZDzAjSxmOany7aipQEQcTY/pdEAzxgzKzDWT+o19NIR1eoL8wCJWix95p//OPjJIS1DizRkRizVe13hCCrb2F0lFkdJQgBz1ibxdpNsfT/YqMrGtew8 +3zDbxVuaFCI1nhEYDQoyMwlgMc9uqhh0gl/QmFcvmxA= +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRq0SE3ke1DVICYcXA2LLsKIbdLH7j92zGlDWBkSn1r8jjU7z6f7zl/bCsa/nN9UdQPt7jUFU3466JQex2z6lVWzL6cmBtWDz5pRXKAmOCGMTE8E8JWsS9knqIHDELxtXe0FfYNWpg2aG3ShN9Tmxszf9Ix5Z2yaPIIySqU2pkq2S +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXoz0tnByKjXrpn0PixC53xWdKQvml7T8eOgThNFvdQGu9KW0EKUp9/AOqFnMyl7N0p6kgywp4Iycx09eTDi9coo+2QD6y+GkDvFtQctJv1GWaJ5m+hnPs4ty1VkZaTu20= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +FbS8mn3AvBwAFaIg5VB7LF5D+W5DVBr7vJDfpCs+o7UfcFlxIT9U/KT0IiGdMlcpT41Jf3vQM+3R+hEWLGg2HQ== +NcZ6sQ1mLPT8+JyzvATZXLyE31SvRRysQ1/hjQengeQkNcIkG8myBjLQODy4wUw6b6IgxvpG3kDss4bAp4Mkg7+j3iMBcRlmzV08ot9P0ho= +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +50qVUF0NwqssMqlRNPRPPpqMj6LbxX8baefoYpKbhOVCc9uuNYEopENSH1IAYrkcJIAGJfEGaukafekVV6Q4TZrMJvKnrl4Ety0JsUyWxLo= +UjGO21cC7sdtapmgeLbjSQFA3swQz8WfVfZ1OKckSOkQAvRjEFagtcWFL9TBY8IY +o6QhOIN2Sc4SHELnst17uQJAWihAAHNbOeUoWaFGGFB9Wh/G28ZIDmYw8Fa+iVby +L0eUthVnpkGsmKFAX6d+uGkPqUsUWC4LzxhOoLQ9PX8= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM6xRep9h4JeTcmDXtoHfuJJhLeMp/96lFFHPE3DsqJf5dPQVeKcYvlq5i5hNVg/oNQA== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uW0V0SNu6gMkNVnvQaya81p4dbSBuufxcX8hYuE+m+2pz+G/9AHxWfOf3ccqQKB6hww2KBeXJf/QOdql70Jnc2DCyqXTwxBzlShb/nrkfeWjxsflBXGxhI3vDXsYvMobXQ== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== ++crFcMETgRZk2iHejzHCbekxzGHP2OCYmVF6HPBoESU= +KZTmaJLp+FU9X93j5Tpqtw== +ZE/mIUUH6qV6H+P0DGv+nr2CTZLs1/rJcRO1XwCVW/9XAw45hBgxaY0BFfGvHswU +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +td0Hee4NJPblA28GiIcOmc/Ym6Bu3ogIAYm5c/oogwk0XBSzLRar0Ih7afhnqigD +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/j9YDYTBW7wzXOYwIgWpVKa4bLLtq8FvlTrhYJpES3jZ27y8d3fqhIUr+i3S+RDaElKHFSFGyD1FTDeDliZEytNZNtgA9D8ezFQj/kBFAN9E/P7It4Etpn5TBosumpDnXVft66fsfb6ncNZmrFxs3jk= +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRrzaJ3fStGEkJ1TrpFS03bbdUIiVBbfc+ifRnD8hoj5i/vGHJwbslvMaI07I6UZqlSe4f3Af9BMCsD66aZlfXP6JZgVfGWadkqsrXGCRsfIV +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXoz0tnByKjXrpn0PixC53xxW424JtFQiPFy3Sr4iAaZyHbxdg7T+nBoNbXsIKW0qzqpTFzWnwebvdbxflyIaYDFF7xsOPdqQ/uMxgOiuHsDxnkGhQh2qQOqFgAuZecsfQ= +wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJjgpziToag3V5fNXXvZF2VqJzdP2yN9TApu5LJMNhW/Bphlhm7/zg8uo3tW5D2Oe/deZXzoOQx/WMoQDK1USmSn +WHkOzVx7seuLmxs5Hu+l/vewF5INruHu21pbeP57J1zziU9fiig3+az234q1W7m2 +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +kgF2/6843W+vdtmAWZoAPUUQVr7H1bIkInRSHfSIegJxbEkLNKdQmNKtEDjEYvE7TAdXnlvIsQZVLB8wq0YhbA== +PRG/YRzXVD8iVR3bzEcUnpaQzJLIqAWGPz21mQzKfgigD0h0w3pUrWBlzWfZSsNIAA8aq2BTcbI8zUNJoPHL8I/D3OGrbtm9uWjtHKbcm1c= +xWoGNWjKGPfI4gq8aHoTfOuj52m++iET96O/j9y6xVlGm4+LbAACgyp9oG8Hv7OC2+YhWhhRxM7XkLUnZ8CjRA== +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +gZ7BoIAABc+I8g/rYzq+f9+JYoehhU6jEk+BbcHC0G4+oVooZaXV7wQeZ+nU74Bk7rzgbj24HoseqPAY5HVLBg== +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VV8gPVA+bYqqpFFCw1YseDEzzB2XhvwgeARRSR2QlM7ZSrGEPeKj2PlbIY2wtLsEq61MP+8rJkGGqrAgI/6o6I2 +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +c19batRfRungBChht8vUMh1zy11wl0LMS69ItNS4EhzPiaGMcmOapctmdbTQO/LV +KZTmaJLp+FU9X93j5Tpqtw== +mW+EgwYdyL65jNEPCrA0ZyAiluXKvg/5eAb7XSOfiNawxURG9t1EBba6VRmOBkJq +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +yXqkSqtRGSEMRZqQHZ1+mt+fn6ZoE4w4GNs8EC4uyuo= +EXpmYt6ARP1igJ4wg7aM1bxZSfm/CYsM2UZa57yY2kwV1UU3EwzSn6LkGJw8sL7Y4278DX0WDnRvgif0UVQ0RQ== +td0Hee4NJPblA28GiIcOmTuwGfcKcaMQUQAHvx5+SrqJRTOnfpxLSMOragNj5LzS +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/vYqxGK254IkSnFz5Gbp+4D+189i758u5SMyioBNY12TNuKolISMgIdF+ywmCAE+gka4NGyLSjBg4/EcgJgqQdsBg7Pt9zrLwkffmO+9XgeDDbKDnsDT2xAjXIapIMddUSL36aiHe3sWwxqbPgWPNsEDr95n1XMy2Qabo0H99FGN +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRkp8YQy11zttrnYylwpxv3NT21EGBXtuBB6FRyIL3b/coiheIu5ek4X7YoV1Evy83rI3w8z6fy1IGgpLjNGAd4qf9g8Ugjb6DVjrN/HmIchP7rkjrv8/uIekNKtMI2/f6hpIJSyeP/bOXKVDe2uMBks= +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNUKpj9eBHJWRU8H36UXDyZdte2RnR4/TzUtpmwI7Mb1yeurMacT25OjD/5z3Jjpbra3hHXD+LiqfMD2gFdaHlb4VLjRqQW884HCyYph4x+cSA== +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +w3tITPcVqHzL879MydOuJWxTXRB2aaFKrq0+P1wwYr+Tn/VQhZrVtE1sOh/Shl8tzkg/xw0YCXeaoCjUhruUE4HePjZG55uasXJhtmcbhEE= +NcZ6sQ1mLPT8+JyzvATZXLyE31SvRRysQ1/hjQengeTwNZn320jW8CLJ6hhiOvminGdO9sL1lYxrbAiYZ3/ZSyLPfa4mpmClQ25ZDtSIL8xQOLRuc/8OLNGi5EGzIJQQ +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +EXpmYt6ARP1igJ4wg7aM1eAL38y2o9wJmdEMbt2Sx8MVFzLtkihZLt87HyV7GGKe +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM68aMl/bRRI6Tco5z/OD4QuuJOPxL9bPneSyufUxuGKiyG8VZgNRnFN758snADqdGzA== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uW0V0SNu6gMkNVnvQaya81rLC1/0Fvm7UsmCiOH3f1ao04XjlFRFbSsOJfb5NzMdtgTbqjkn3P+poxpfB2JcbFY= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +j+SwqpR+F2lYRZXNJryLScYai1d96SkGSYuWvaU/bq8= +KZTmaJLp+FU9X93j5Tpqtw== +mW+EgwYdyL65jNEPCrA0Z2ByoKEXE9FxVo/BFhk+1r9rNF5yEpIfXSbwvjCXhJcc +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +yXqkSqtRGSEMRZqQHZ1+mt+fn6ZoE4w4GNs8EC4uyuo= +EXpmYt6ARP1igJ4wg7aM1bxZSfm/CYsM2UZa57yY2kwV1UU3EwzSn6LkGJw8sL7Y4278DX0WDnRvgif0UVQ0RQ== +td0Hee4NJPblA28GiIcOmTuwGfcKcaMQUQAHvx5+SrqJRTOnfpxLSMOragNj5LzS +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/j9YDYTBW7wzXOYwIgWpVKZT2HFNTZ/dnELZpQw8sFx2NM6OSEYzJeSbY+hg0pY2YR3YlUvPs8waHihkMT3/w5gEqC9SDEfFBLCggA+6vVOvCmKlXfSId3RedaMql/OQGI2KIRH9oZZr0FLJwJ/rfiHLvHF5emM0UM6Pvk5A4rYV +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRkp8YQy11zttrnYylwpxv3NT21EGBXtuBB6FRyIL3b/coiheIu5ek4X7YoV1Evy83rI3w8z6fy1IGgpLjNGAd4rnSQmRRxr8fplixGB+zZpnozX8CXOVBq7e4QokRD6H0A== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKMlEIqjWm8+kOtLiMZRGDCdhGoNPppiiLZ6wPeQonyx8QVAsGxKGp5aqYd41djmd7mKT222yACYHIgsJosqizpwpc3VQxt+nUwmZflirICXuEVd78LA+xjQBmoYusnc0= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +B6GcXnDqGMwz94rY1vzl3lcoIveB2pMDzZOwAWkcbnDoSW9+kEaPJIiGT80+9uuaMaFtSlE3linX5NcYT8M6tg== +NcZ6sQ1mLPT8+JyzvATZXLyE31SvRRysQ1/hjQengeQgHhAHwpP51q6ZVvlSjrFoRWgULlt6+pnCGuxC90wI5LMIgyeRUwkutTf5mzchqYk= +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +EXpmYt6ARP1igJ4wg7aM1eAL38y2o9wJmdEMbt2Sx8MVFzLtkihZLt87HyV7GGKe +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM67AZHUosgLUiIdVjNoh6KcHkOZpX0/gAyUoZNZVkt8TMqg1owN4+6WgtvW5QxxJ3XA== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uX3TRUWCciAkYHgiV/48FRG5mrFv0q5F8ynfWe8wmV1Tlzb9GJX3yEyrdbRkbKBig4oFpaaO8y3nH+CJvUnnEyqNGWpMs3VNzicgdIgpKjgl +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +QUCbQ9a0sqSffASbUUf6Zlw29gjlpj1Jf/NheMzsBZs= +KZTmaJLp+FU9X93j5Tpqtw== +vnyeEw84C3zNqYtEr8IrMVYfdroxRyDoD6fgBCWOC1zxCh9TdDz61H66jS9ZZgyW +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +yXqkSqtRGSEMRZqQHZ1+mhBaPbfHnkFsN1qDfA9aL2jK43JmePxKyQ83j7jD0949 +EXpmYt6ARP1igJ4wg7aM1bxZSfm/CYsM2UZa57yY2kwV1UU3EwzSn6LkGJw8sL7Y4278DX0WDnRvgif0UVQ0RQ== +td0Hee4NJPblA28GiIcOmTuwGfcKcaMQUQAHvx5+SrqJRTOnfpxLSMOragNj5LzS +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/j9YDYTBW7wzXOYwIgWpVKaRwM8m4W7nRnbHQEcrh5EBYmJZlGIqfYwQxFmlVxM49wxm6Cnv5y3/UTXc/s5sEe3C/UKbwp+Egw42yyoblh1S+6ifPQgYTWCNgqq6llQ9VEIcgO5wEXZMNvV21T+07emp/0tTHW6U5xh1na8pXVLZ +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRkp8YQy11zttrnYylwpxv3PAoF9abnkTLvBuxKwqngRf4jL/n7LWjAvSrAYdFb+9BaNinGPi5QRyUCX9iUPvtUB38XcGR52uCIxFIGPX5y8P1MrqTIQ+eSStzPUhzlz7XPpl1fxR/UZQ8U9/TTFSoHY= +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXoz0tnByKjXrpn0PixC53xu/Hz275RJL4kiRWBr+mu2ooqlwYGOWxj1p59WTwtqYxYP4BapWeh+MjlYkUmAK3o6NE0FnbzwUeJ+/leN7aNVRh1wvtsFyrqW27xcgc34Ec= +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +1bgO57ntu/GNYZX2caSlwYZUyyyxdGMxQKqFhp5LtTSjxRUhVjR4S9CAeRudMXK38fJvAV6auHGbfNnBiiE8baXvHIT1dDjV6qtkJtEtHDs= +NcZ6sQ1mLPT8+JyzvATZXLyE31SvRRysQ1/hjQengeTrNPpSvOL6d79ECgjYIgQaZ+/wTT4FPcAS6JC05hJ7fTG7gykXosXQBVFP6bEQdlz0GEkRvFECHWX7l/owsLJP +IhuisKim47k91RVt8z8qtAEkNTtsA6zH8QG+PK5FdYvfkYixZzQRMXKE3CLvPLBh +EXpmYt6ARP1igJ4wg7aM1eAL38y2o9wJmdEMbt2Sx8MVFzLtkihZLt87HyV7GGKe +Nd4NGMamJX/VaDzIQ/eFpJMlRO3YexRW3lEtVUlCMgx/FZPEtgNZe5Yx355KpsFi +nktihtQ3XBvUv8xRFezM61BR4+uEHudN/AadFltFcKTYemmqpnexZSV8Nh+Cr0OVLstCu4a/JtyIJRGOEOnY7Q== +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uW0V0SNu6gMkNVnvQaya81qwIzWYl9wpCfcvIrYp5Poa3hLEkz8EUxDfrf/Ywk7Zp166IwQYGb5Hif9xtrMf5JXDxDjMQ2w896YpYhT8CjcA +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +gEP05OadtLdkLYzVLnPWJgAmUJ75B38mNHU6Ew0rSyiwMGR/TReCNztLh0QT5fOC +KZTmaJLp+FU9X93j5Tpqtw== +dEamQ2u1OznjljWi73pYBuaIah6zJ4rofIkRjr/0s/U= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== ++m2Ig0EQEJ4Gc+2v8s5xrnZRBGAfDHuVg5r9VE/U7Dqcsp5mSPEG0DHHYcBZkfWo +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64ilYPlqw9L4ZaTKohSCA0j+Cgq1ydAwCcdZTbRoiCAjSs= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gu/u/ZWm9DYyJF1WaRWt8m4jRqhzizNU1gK/xrmGr0hXVY3UUV08M8l9gUQoeu5PPREImMreYrCZJ+E8reG4IsJzSRP8CrJT+peGh5SAc3lShXPFKo4Bik5dGrbBucJyiY= +KlUaknvMM0XG1KrqU1G4aqEq7xOHjfhNa6dXTCBym0E= +1jxLOCmFwAyjjgguzOAQujJ0C1Mc9f/enNUulmX/aPQ= +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5MWjC6OS8dz++HLDh9rgwCg8eJTHO8GLedgAPe2+4wRq1YT7E6kAyCN9VhCDEMLkdT9tL4CDW9/n5tF12+RhFhg== +HLX4lLKY+SXlZdPzp8fBulkdKWMqwxNxIAJ+qchj7AXrCXVeQe8/sC1B3D88QrNc +UP8nNoA9lY66xsBlnwz1GFTpFEvmG+sRugBw9rVzyuU= +o6QhOIN2Sc4SHELnst17ud6xJhYBLL1CI0oeXeqG7U7HOupQi5nahh0bhahrL3Vq +o6QhOIN2Sc4SHELnst17uR7klP9o7zRX3OHkgqJMrpdIzGaTlIXDBx4pw1TNtIL3zv6hzXyxdWcDB+hqAhJRlSCApqCXSRusVAmzB+biouM= +o6QhOIN2Sc4SHELnst17uTTVGGxmpRyQZPTY6PQEKa5SY8WEKWbH6/gFdfBFWrxu +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +i+CTuLsvZvzsrs4dmj1KKRYZ90Ua9c+VB1lKC01O6+1xYa3w6cjuyODS7zol9LuQ +KZTmaJLp+FU9X93j5Tpqtw== +03VatSOa2OwbTaGxCCsDkfRNwuehin4sgRTEQbZqq7kVYhs1CEWFqJtNU++WDwFatgr039Pfm2BjL4R3Wy2RgQ== +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +KgZhnCYglllfsrazbVxNlpRsHzYPmB5M2ILaQpT/0noosYD48ccQjgkGrnOLuFIlUfWt0Jxc2AEcng9bpMY6YA== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNVnsl1+vJ2tBE0OwPxs7prnT5qapGvONu8Ngbc+l7WyuuVfJW+FtR/74vS+pISAmo6KWIZQc2VlGsAsSf3tYsJM25uB3sclTZycx0fC6R4YjifNDos6GxaOCSoI1UCWY34= +hv9JMLvs53QQh1DP5+6ndZgln+eW0nElKMm1KbM2W2PskqOPB99iZL61ZSnG7rJ2eDdknIv/XgX6lVBgD8IQWg== +XdtEyBM3Mm5sMRdUDFrxBy4laVZx2zxt3h85A7IU2cFKM0GDhpJXtqHS8spMHIJsvz0OEaBwNFrn+KMa6btmO8oo/wXvMMdi+NMtM1ihg0g= +WHkOzVx7seuLmxs5Hu+l/iDmKuVKi/pTW+c8evqV+TMVEqGxxzIStmDwxFwASXBI +CW/G9plDL3SfzmBxnqr6q87wTQCNFJP1gF2kN0sG2a6iDm31tOkXDjbm8C6mn5Aa0SWfua5kDd00kMFazsK26LlL62O+1eQeI35+d9zGbnk= +ltIZHHUgft++nMUXBsBki69X2QPZHt4EmCiu3d4tkyvgWs+bXIvDihJYJrCebLpX +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VUBj8icwO++avtf1sayWKFhVOSpue1yY9bDJYOTTzSX+hmpAzNJxKSaCs2lwXXKg6s4n1M++HfQ5CBXXS/2BpJ6QBoC6+CwoyyT3S8vHxCKiA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN60wnCWEGAjttVF/M5NHLkMAqyk4umCw9L2ii1LX6tY23 +e1qLykdSSG57G+I4DSZ3lDDNWWotaAXYRIzVKa1N2epQ6l683rHvc1gkqjl/iCnr2qtmRYNKoBqDAWQAoAmgqw== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DJq9FsIZwgRA0seTz5ZLwf45ZaY/43JatXtyv1sn0l0W6pN14o95ePpIaeWhFSX11VADyLJ8CqNt/eJ+ry+p7nphc7oPJdOUSR1dHg6UxeDeaelTU+cnumX2i4XH+SR6VfJl0sCuoZtf5wyHE/bXM3AIC96j7iuo68O1F/NLd6Py8iiKh+hKfIqZUuJdkdQhE= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gtyrN3fmhNqvxN36kot5RF2oiiBq19d9SVBzZYiebNEINSmskRQEht+1EFBWi9GHHz1bQLYHdD3FkyAvTyWsMdyPzrxR3h8BEtAcNzMAqTIecygi91vhM2xBUA701EBPeI+8qxtM5zgqvJBtXwFAez+ +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +MgPAHo2kx/v09x73PLfPz4Y9t0RHilXsEXvbfeZF4mc= +KZTmaJLp+FU9X93j5Tpqtw== +YSMUXXbx4tCGvAMJYzluZMmToF6l7FIiZwFQRe3i0aW86r8VPTzleeun6DfljKPh +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +fdeYOeeuoWy6J7ezwxYzw4ZIY9pPIraCczrpQYJiR9mb7+DAhEV3ebytkVxaIQ23 +Mb1TsV/7Ki446XO5w7AS5Tnke4Mw0UNWSe5LSnNguUo= +1WBG7bZf/s5gJ+qIaMFAbF6S8FDZoLzAhhqJ2MNTCVdX0rFK8L37rJXe6iLQ+VIT +/0ULpLqgTvInFD0r5hHANnKkcJSE/H3VghkioZ1tnjyunYYq8XfVeT+J18IgTe/z8NUrzTSoyNxgWbkOYZIRQgQdFDdl6ZyMvSRHhimVOo+6+IPoXd7e8XcJmZN+sF1bywAr0k3SZVauL4vVCrZUmA== +7KFwZuSJAC7mbgtMUMJu2zeBbC3XtaoOE8JL/ymxSKo= +Dlyx6ZDFdkn72YIxkC3bo24wiNcBTdhojKIyzDox6X6Fn/6SHA+LMut2OBSeK24aqcciho46v81csISe+FPjxh696U3t5f8i6aTPRj3iFBD2ad2pyEBsB010WMOs1tGGILKoL0q3TjlbS/5SqbuicA== +VmVrGQo2zRokW/ZuO9bN60EXpD8GKK2fiY1Xg0XFbMyYwhONhKS+p6paMXgT3qX0nX6YjEUDLqRgt9iLSNuHcQ== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmXbFWE3Z8RNiviG8V4hH16 +VmVrGQo2zRokW/ZuO9bN67li7ywWLYu9Y0gRIjplCnGtfVJYQf3jsgZWBUOgAMgb +VmVrGQo2zRokW/ZuO9bN632Bl5M8VcuruOzTbQLrYlXe5tWig3n9A4hJ+A+5Alc4L9uOOt7pn/CI01swnNUUvg== +VmVrGQo2zRokW/ZuO9bN69oalWQ/Y/wPLgrpr0WDj4hWOHZqXDwO01uW8tMCe6+T+IcR7L5rURVZI+OAbd1kSA== +VmVrGQo2zRokW/ZuO9bN65rHFZTGDKBLHDMo5/MqrG0zTM5Wypw2eZ4UHeJSniwCC68QyXMnn1OcmKYARfxFLjR065H6tvg75tpwdq0aMv8= +VmVrGQo2zRokW/ZuO9bN6+Aghyufrhh2DrE8EjGxU9B9P0zZZOVvSNoiMbJ8u2TGCQfRBmDeQ561PT7skPTptg== +VmVrGQo2zRokW/ZuO9bN6w6imMnMMPRq3v/Vj215HswQhNFa+u7N7x5+h75+em5GT8jPtaH8gFgTeTBSxpfSDA== +VmVrGQo2zRokW/ZuO9bN66IgW9hzgMYjzzLREnulmKvAw/D9tfQZ8fSSgnT1zRpdJ6OEyrEsY9aSVoO92LEzJA== +VmVrGQo2zRokW/ZuO9bN66z2sdLiszhqoZcMSNt8QNUub75cDq0NNFT+IReTx+rjc4S7Mq7fPH6kcdZUXNFv+g== +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5Gr5CHxLtPtSieSDXE0Cb/KjcFqA1XA/MXMoi2HpbdsqDw== +gApoKqs53F0hB+0nTxFBUPbKBjYJlSBd3lBIeTTeEd+DPe1cmLese42GyyMquowC +o6QhOIN2Sc4SHELnst17uVVY+9qVJ1W0msopxPqPktiralI+o9zT0ki0vmr3WpBr +o6QhOIN2Sc4SHELnst17ucR5tMgam53vmM/6MsjArHiEQsl9RJ+nyTnBs5oVagK9XukIqUhbZVd8sXKP5yHIiNKDz8B/KmT9KN7HJwI2BcPSX02PuccB3Z/IFU27EqWKPCpbdnA3DsU/4bepg0I4i5dBl4+hmyKk0e7qg4I3G5wcHgbY54DVLW18NqkNdkb0 +o6QhOIN2Sc4SHELnst17uTGbfoG65qJYuOo7HVAUfwrrWa4B36uJ5REt040T2SNmGAV0LMPrERjzC/oRk1GEuX7AfuSpWeNyK561rxlWmqKq8esttm3BDwzTTA5Wp/ZKFGU72IJK2pjrHuSoqkzLVujx2qaqM/Km1yCxuLXdC2UZk358XQ+ddlTAL22cH156434R092KKggual1jvxpED9VmPJ1PNoUP6cx4WbWWYCyTyPc7+BhF/kMm12S2r8/z +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+AI78hRtJVdZsXVPm3xzZWyq2UU/Mc2lFq9aD/4Cho4iS44X5riwbq/fq6uPnqzbTcKYuqgsiWplT21S8LZ4Fm2zty/dHSsU/avOJz6rLWj7Je4qi3fKZwvPFeX7kuPiMB4Yq6CAzssmnUrr7OiNswW +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gu/u/ZWm9DYyJF1WaRWt8m4c6VFLbfAfHnQc9YRPwfcloEvHTXGvN6Pl7C/SMpwgHElRXOpJnHTV1iiSJ8iz3dqt83iucDEN0bGwOeUejFRB3HPF/p4p0surHRPXiGRcTNk4/L7ZKrCYSp8ociPUGApdade1OrQqF2OyGzrfVrUDo8JaTMR4VipxZ4gZP4qTHKj5LN5ozU9PmCuQxpl3wHvIT1lbRsfuac9vaCl6BAdPXY4r31LU1E4tPw1tvt6ooM= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +rOsFgabYAzAaGnc11wPwfr9SKNg/vOxFZuHKBFHseoil0bLX+vnM+JDUWI+3VHCK ++m2Ig0EQEJ4Gc+2v8s5xrnZRBGAfDHuVg5r9VE/U7Dqcsp5mSPEG0DHHYcBZkfWo +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+ApG5Mqf2WNrCxn3Gs2J4gOCuJpjPTZCrljpDKhczQ7rzIKfTvOZGrf0s+/mJDgPbU= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3I3oWHCNytmq/khPZ8T5vop9d8mBTS0Q0tTFiGtAE0nP5h/ZBt1B4WKQrqqTDIqvR+PZeU2ufL5ZkReQeSSo8K/w== +CGMG5LRrLyjmr66XNRnXQM947ggRCBWkdZ3U+YWgedy2DMtmq9woOHurkWQQKXj1 +N5xldEoitd9eWUThCvdOTIiFGgkhHo+5taj7z225wOk= +mx/N1vLTSXxv3fzHVQc+Ld+BhrsjDb/SE7F6msNJ+3P32RMgFq8NWcxgNjec9esvqcQUK3a1Ff8Q7ZaBHVBGZpPDopiwFhi8WouoZKAu8Hw= +wgR07xfoapmx6eEnFHXXYsOrmBevoT4x/SdtelHhA34TH6958NaiARR18MH1W5Zyw+ONWn77sAOwiF7dLtKmtnm5vZ/jUNl7TgUev4NvSj8= +L0eUthVnpkGsmKFAX6d+uPOqTLQy9mN/LSujvNUBfJILNcqX+tIER2PaAivxKdD5 +1u+XjG/2+GSQRv6EzCaWRQ== +8W/JzearhnJZ/f1fuuFws1bd87udt2BzqP1QM+dEHmQ= +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNU+8Y6i6Nj3L9nyiNA2a/7B/3wHduPj4RjT8eK5AyaCKUxLExONYh+9hvT9FTHh9fD6G+9Ge/whDoUufbCKcn/p004yMZ8iVPclymeY+PFJa/RUPTY+X2OhDtINflLUn4g= +KgZhnCYglllfsrazbVxNluM0wwLR9C9fkh4Pz9PkxWumtUfGfHTWVnBtNg7DAsoHZkbWSD0tcMd4XAK42WFKJ72tG6nWENMgudeMeVoZNF/g6695TZv+m7OkQ6qGTkk1 +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+ApG5Mqf2WNrCxn3Gs2J4gOyeUOMMa0j3ynXabSclI520O67KtE5ptoz++MMmbDiXrVgINl6S4qZyg5YZQtf0fuDkfYvYTaSyVxl/SmDQiGsl/bspMGVS44bf2lQChSrR86ZH9rF5pt76PJLSL7rkhrPgpE6vVEEBx5dYFTA0cODkh+wHPg3f9QypkuQ+aI9g8= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gsaEBHtsTX5vLF1A0mWW99ykODxJ3k+2T+yMQKxUoogBbz3OIO46VTOBz5+V6BXF3C0auyYhKrkqaLjQ+muwZlyRlF897Aq6r4BzK7piTkJ+hv96K4iWQKsWsYAZQAMc/R+Nhte+F5inwutMUSMOXJFutn297TrNfsagkLU43GTnbcNFNx8E7pjKiaa48ZrhNLxxk60X7XP7AQJzmLT+JAz+hbZuYqdOZVZSxDT0eHMV4tYTLwla+erBXlkxziCFGJ6OahhrazzD7zbNeQ2KzEEcIxOesuonYUs5ktdGWuspQ== +td0Hee4NJPblA28GiIcOmUAQS8rUDYsqzBGv1wXjMizXxtTyVHniddoIJFWktq56tn2BgJd7xITgOUFxE9+rWg== +WHkOzVx7seuLmxs5Hu+l/vewF5INruHu21pbeP57J1zziU9fiig3+az234q1W7m2 +CW/G9plDL3SfzmBxnqr6q87wTQCNFJP1gF2kN0sG2a4BRmZHGr+3zw2g1xf5vtfgLwfVm8mGYIny9ZcTFjXWVKK6iLDSSj/rpfEb6rp4iEk= +ltIZHHUgft++nMUXBsBki69X2QPZHt4EmCiu3d4tkyvgWs+bXIvDihJYJrCebLpX +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VU06FxUJvqkHXdK+DAvhPcso+ah+qu24mF+1RXcb6qM5EmSmg1x0qIsCtR+09SHqPw= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN60wnCWEGAjttVF/M5NHLkMDrXkykOUsjbKojY14TcXm0 +4+wKB+Tel1iow8au3cOIJQ0cJN9p5Wagnh0MqbqOZlNSf/KBhrLWbADm0lYhyxRZSTv1g0SpHIeHGOo6DJBP1Pxj0XFYKXqcn18zEdiU1ng= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +NNi1hKZwwGwLUUv7+aXfFZA+Iw3un2r6P72Ea5HC0dqLITVSg45e6aTL62Bu0dbw +KZTmaJLp+FU9X93j5Tpqtw== +s6MM2ymq3uTQAnahg9ZXG5jOA0jMV+0VYOIZp5JMlIE= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKzFnp5L8v7gjpRd9cQpcaHB+Wayp+7VzPYdk0sF4AoTWeyVn1M609OmPqDhov5yljlIPUExR6T6+xi1AMd2nmE/TST+Kij0fvHcVXKrM7+Q== +JmsPmuBt43Be+lPMfSus704KoO026ezXn6mEblx2QTe8QfYhTuwHXsXH0R4EwA7WweYFxxjsfZCrQQJKRELANQ== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64i1f+1W/w8dngvqNZEKnyPaydYFpnV/gNAPkvDbqDtt8Axr0OD5HFD7pDSWgjWT+kj +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gu/u/ZWm9DYyJF1WaRWt8m4WICqCsMbx1EgPW0h8DWlhcT3CCFTaJ5AOdc1RFp6LNCnvBeeqXIuaO3pMcyZ2LIthcqYu7tRXZ1ElqTv9riSxCHD+L+16e6wlm0Bxs55V5btAJZ3dMw1FkL0S3BJZvSVOsM0xpkKtPQOj98RoVc9Cp8PYveJV8ES07Vz6f+DtWAaMZGrwkv9mNisPngiTETORUjmJdrr/g8tUbl2Uagcx0hlu8hwtnY+5ho15Xw+4CU= +WHkOzVx7seuLmxs5Hu+l/nDthAkam1k6D4nYqoGh8cipRxIezIDmlk5DZPBQiAaC +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN60lFRhtJKRQkH4TNEtZxNneE3WeZsRDha5/iKw47AfE0emSsmQ7PWPQXPg2kLPkYGA== +VmVrGQo2zRokW/ZuO9bN60fEHr48OZi82IVUC6YL3H0= +VmVrGQo2zRokW/ZuO9bN6/79/0H/+4xrFviTRFBeiNXjvYVaeWNuMzUV59P1gfmlEg6Omuf71CFpA90HyUXUzRCpsNjekwSQDS8UWjK2eal9Rvahps0GRwBh8VR+JcvK +VmVrGQo2zRokW/ZuO9bN66D2kHqyaaaDXvKupYqk/8786fCDGP+SR9HR380T9MZRh9mNZmdOqHh5iPNTdQGDxQ== +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5GoIUl64lV4c6SM6jvfq6ZHRjPDd8079G2HEigi/ZfKC4Q== +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5GpgKez6lH4gXHygcarAPRBnkgOyt/F4Ace8Vj3cK4++8eimHerH6cVUupLPWGCcqVM= +VmVrGQo2zRokW/ZuO9bN63Bu8EQDuwqHh06ZdDTIvqAiFMjwA7VbfCT2Ne9JHAZv +VmVrGQo2zRokW/ZuO9bN6/po07eiFpLnQezWsZS4nGPPig3Mk2QOpRNzGs2hSrN2yBAx0P10m5qVtjlTeATq4hYMkYUmz9Habg0RlyYw/iQ= +VmVrGQo2zRokW/ZuO9bN6xwvWX36N5sH14is1gdaKn5yp9luAKwrLh+Qt1r7O2jENYPr6Cli2gmaPDxTG+1787BDzfYrwHiwMfjoqFlpXSk= +VmVrGQo2zRokW/ZuO9bN631IQ4qTkXU6zgbp3sCrd1NlYB8hewvpcJNKvjMrbUEzL2M7tq/dbcSz/GolEssynfajWfrV45qeOrdjq4k5Rep+mmAhTL6rGIL/vMYaF6/S +VmVrGQo2zRokW/ZuO9bN6wJ6Cg+0P6lZUZNdwnbFiXDIRqFpSEIlfB5NK2xsspXXxdR6zIPWUVgA8Jvm6DNRFZQ3FdczlOxHbTzI2zZhWG9zKJudPh3RFXkz+qxI/SI0 +VmVrGQo2zRokW/ZuO9bN6+Aghyufrhh2DrE8EjGxU9Dlq2r8b/D5xW5WgaM5MFXpz5KcJG9DakmG9eznFCr2mw== +VmVrGQo2zRokW/ZuO9bN66vfklQEmoLc3ecqJIXu87MePSNwYUYJqyLugLRSgxrOVoqSBxoLcq7Ceaq9HlkGdgeeAdYQBlUxf9velRkNBNNkOkxPTnk2O9G00hCADkZQ +VmVrGQo2zRokW/ZuO9bN66D2kHqyaaaDXvKupYqk/84CKVCkSmNV0w9PNkR0+m87Pw4VsDgnnvQ9qy2bouykwg== +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5GoIUl64lV4c6SM6jvfq6ZHRjPDd8079G2HEigi/ZfKC4Q== +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5GpgKez6lH4gXHygcarAPRBnkgOyt/F4Ace8Vj3cK4++8eimHerH6cVUupLPWGCcqVM= +VmVrGQo2zRokW/ZuO9bN63Bu8EQDuwqHh06ZdDTIvqAiFMjwA7VbfCT2Ne9JHAZv +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN62wASuZkFvG/cu/SkmwF5Gq0d2ZE6cnfazkFCFePQ+aqXYHgHLHiD4y+zDPj0czCQUlieFMRFTuiURI05jIC8LO4owCk6/6Vbwv+eWNKllLGSBLtQ+B5DOSD/NKOY2/yoiAS2Hybi5KXRKsHdHO50DH6shFKOeIEeUNisDiCVipLY3mfH+CDhejxvwQZNsp//YYuvRJo14m/n4uj8RfcLGS4Ld+8xYRWQhdZjYOaJiiR +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN66Px6Idtf+WhzwY1S1dxdnQ= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +eno9WEg2wTTzJ+gLpzzw/HTtAiB8aKTYv7hWsOroVkI= +KZTmaJLp+FU9X93j5Tpqtw== +VoUrgU2B8KdsRpJ7efZ+EA5jANK2VE4Q9yJqIsWf0kDunXA6T/uxqyFKNXOtG4S0 +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +td0Hee4NJPblA28GiIcOmQvZP3B6R2ZujAdkKnUx0Q+I4+Sv4HSGtNjiXZnpen2szaYMKvGoBXyB7qIf+7BawA== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O55N3rbhAq7klcRcckAjfgMUIW9a1jx1oEi1X++CwmLErG8HB6OZL7x4Hr5sQznpZ6W4/Ts68IEMmYnin3AQei0w== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64io59bs+NGpD+pgxT5vCczMLbfrJTSxqqt/Hw8Op4348+BrAUdwrp3VpXTh7mH1WSM +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gu/u/ZWm9DYyJF1WaRWt8m4WICqCsMbx1EgPW0h8DWlhcT3CCFTaJ5AOdc1RFp6LNCr00FtbkTnWsCwfrXrTsbLMLqIIdyowhKIEVZ1uMxl0aBbWG7UNTfincWWCEaudDtMd47vWWXiO6D/JoTPSrtYC0G8p1Bm5nHc/HdBY+8rTfS8lOCEPpP59kCqsis2/VJHupMxAupSe0IWI6wYkxX2 +WHkOzVx7seuLmxs5Hu+l/vewF5INruHu21pbeP57J1zziU9fiig3+az234q1W7m2 +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +QUS70OYdt9peiASDI5xIXhmuP5qGNd7l29FUn0MVUOKUoVKh81g3CNlyheex2BJCXnT0ZA4lUnRVs2EFBjO3NAYSDO5CZ+iNOggPK3uST5o= +PRG/YRzXVD8iVR3bzEcUnpaQzJLIqAWGPz21mQzKfgjXbgf1iITB9OijnCvCJdY/3JhJQM98k5ft0O7J677c1xgw4zfoiZ14vhhJp0udnmOkskg+IOrFJIjKK0kLYunI +xWoGNWjKGPfI4gq8aHoTfOuj52m++iET96O/j9y6xVlGm4+LbAACgyp9oG8Hv7OC2+YhWhhRxM7XkLUnZ8CjRA== +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +xWoGNWjKGPfI4gq8aHoTfAu9Vs6/gfwfIM7OdnTK36Kfyn1EWsGnxFcC//aU1qhR +gZ7BoIAABc+I8g/rYzq+f7YphugF2lPRkwa5nJWjTMdXHzOu3xxHriH9vVu1fRerJsjp1v++nvVN0LNnEFp/NxA3klFsj65vbAPPxX3ES58= +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VUVV9LQ44dzTLse/kwqOl9yxmLv8W4mg0ZKrhszhJym8x2yHwRsRyGJqB9LxBe/DF8= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN60wnCWEGAjttVF/M5NHLkMCCbzD9pGO2cReuqjFybSp4kz4yQBLIUnA+3PCye0fdAn/WRK9dgI7mgpXuHvqzysrNqHoOJf0LnrV0M3c+lzju3l0nHt+m2gvMHoModDcBDjPxUKv+v27VRiOh9jUhbrppuC+UGQw2aw9YgBOPhWf32MKM/vjAEsYElsi9V29gOQ6f9EIZ/UK1WQooXYQeAnyt6UGuX/jmPTKUkOoByT4L +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +8XTxPQIfYULtvjCBvkZx1YhoUTFClr16CT915q1viIE= +KZTmaJLp+FU9X93j5Tpqtw== +0+26ahAP5xvCkxbzFI4FXcr4ixe9Woj9tMUwqqejvuqkOa65iAEbFYfwb0oO5kxV +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +VaCAe3UOqxyutBS1/stGgipTu1LCBB6LfEItp6T8ogw= +o6QhOIN2Sc4SHELnst17uQEjK8c+2Lhy2hqf8rBcSosctX4Bqp2L483M87OE9eXCGS0gqgBqPSCFoCG71wRbJu3JX8WfDjtLEd1sjE6RVCkHyG5at/ORgRx5QkamXNtMHbIKKxfSINigd4EufkdHhLXViRuJvlEjZRSF0fNCHg1u+zI6kuMck2hgx3CY3lhR +p9oVsBgcyiBMjDb8CcPOqM81PXqdethGefe3rfhxupg= +R+prWFCOPrLT5joahWmk52xc+RdsPT+vH/fKn9PeKDeMUXUKiIbyxa8tqQID7SBN +h2UtuTBnsrr4HCgqqYYJnWrnByIjZpML/uoCeGo7MsLJGsfnYIvKrtVcwss1Oh+u +FLMbt5iAjw12yzW85KcCsjoifO62vxUFIWez1JgdTFHE88gXvEhSlegwuVdY3pAG8tOY307gOTK7Ozx8DjifMPuGQsnxoFa6TuTaP1CVs0XMNRPKG/qsLUd84L/gEMxHIIv6vDPJUPC78RE6arUx2w== +VmVrGQo2zRokW/ZuO9bN69CajjgKg5fUQ/+FvW133FWUCCqqTzYArO5dPHlx+pkF +wlLHv6kT3Q/RmtMBN4nDAd97h19+OtRfxNGzgi50jGOssMw8atTBQYoxchzMqbEw +VmVrGQo2zRokW/ZuO9bN62Cwe0ClUSmyaEfpupp3IhPxOnz12DoA1ftqTu4+jHbSL3aXsFJVGUTTTp3E3l+QKw== +VmVrGQo2zRokW/ZuO9bN69KnuFUVVuap5VPherGCzxD67VRKanNxWNtD/wYRqFgm +VmVrGQo2zRokW/ZuO9bN63h2RLSpmgNQ9reaQqWoBOdLPQqe1RaG92OesbHjIRNW +VmVrGQo2zRokW/ZuO9bN638Nc0xIUBtDeFSRLYCJUAKsTYVvdvblY0PmimcsMh4i +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJla/4dM8s7JsXnXWwFHUnQekOTfql1IW25401hZC7tHuw== +VmVrGQo2zRokW/ZuO9bN6+llKPV15WQhW7xuzoBZz1guvOzmtgE/eKSB+h4nHTiiCmN9xjvaac480xJ2OvxKzQ== +VmVrGQo2zRokW/ZuO9bN64m8VszAejd6d4FfAhX9Cv8KptT/EOR6e94FpQNclqzm +VmVrGQo2zRokW/ZuO9bN6/X63Q8flAlw7IgGSkzO/eJ69mkVltYvmfkDQ6tS4dWE +VmVrGQo2zRokW/ZuO9bN63NDPumt9xAJMEP12SPXSnWEdWqH10pkLGs8/lFhJOGSkDnXFusKkW00fzymwglmQVXPu8fni+fBi16Rhny/PSL25mZby/s2O6nh2lQqvIQ+//fwyJrqHV3j5/8fV+Vx7mQYf9ldV61iQS9Ao77tCafWvgOB0v8NGmGJZsM0QMcA +VmVrGQo2zRokW/ZuO9bN69Fq9tQArdLM3BMqzPbmvmpjhNhlnB+ZcEKXrBeiVz47 +VmVrGQo2zRokW/ZuO9bN60PTt5T70hT6FO7/DB/tfTkN6NfI5stb993dqOlw89AB +VmVrGQo2zRokW/ZuO9bN6y8gyB2r/UIU6IQwHyLn4Lpsf8X1wDPfRvI+wVriqk2tvjZlU3nAx/f7gMQC2jP0tQ== +ACEEdgL/kNbx7RaZcSRxZKNHHeC8ShmrOBm/ecY32lz8qYq3yY7+wb6+3Ah3KqAo +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqEmK4YtKPxBWv6u+f5Z+JoC +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VW9kPh4MRgv9O9KvycXV4e/NNm+VgR/nnCxAKUgoDvpCnR3WNh7E34jXwn8UXub1u1f93LnSmwqSexksosXixf4OyJTE6cuXVhhCuVhPYaRtWoCUl7WW59vUKyH2bpVomHH3bZshbw1WLTi7ueyFafT +L0eUthVnpkGsmKFAX6d+uGkPqUsUWC4LzxhOoLQ9PX8= +1u+XjG/2+GSQRv6EzCaWRQ== +Vb5WV9jio932WKGNTjSAezegCS9tOGWq8jZ9rekzK/MmnXXK7zq32D8EyCcjQxduXK1IX3EyULpgCr2okmKSrg== +Ge4cVWkLTUQ/z9Yna5V2THkcuZPJNSf+2pCrKnAlUqE= +VmVrGQo2zRokW/ZuO9bN61KUtgGUCHuZv3UzXTqlm8eMiRgXug9QtUOYSuzu0Wdu +VmVrGQo2zRokW/ZuO9bN6ycnC3aGaIgah7TcR12oSsCezj2/uODAc95kytori8b9 +VmVrGQo2zRokW/ZuO9bN65fb+fYMg9c9vIPVuWlMQmxyfUxscDhXWrOR4VbrPTGL3OAYIVNcolbRwqMS7vsqig== +VmVrGQo2zRokW/ZuO9bN6y8QP3PQuUkUEDKUNOPDK+Gwcrhc53lkiYcTB9/FmicNXUcV6kT/lwU/7VbDc/oY+w== +VmVrGQo2zRokW/ZuO9bN6+F3OQjQ4pLjv/kdVYoaQPc= +Ge4cVWkLTUQ/z9Yna5V2THkcuZPJNSf+2pCrKnAlUqE= +MehrqbDHPhwXNY3FGnyrJ8ifFwtO/xOi2TmviP1CfElAFk+Nn4CGca7bvdq2Utki +dbeoYmi2AyUYPPKRkPUUOREQ0To7SbiMINlYYXBzWA1QRyUMOUGLfVlY0tE2lhMV7nu1mgYGcpvK3qcwkr3w1w== +9uZN8r0CKVAbfzGtFDuvL4PHTg7r1boDDHPeFGuViZA= +W15L7Vl4niMZUGqXxD6c3ZcFuCu5BaPAmzxhA6Q2NaeMcYqB9mn3t51p4nrx4o8W +VmVrGQo2zRokW/ZuO9bN65FWZiWNfabLLUKv/j/pIruOHKtdVs6BolvMynLb0NYkXKXbAWt4ZPE2ia8CqxhBEndi0L5bYkV+It0WSusoO4RmgN9xWt34rRAeWeExd3EU +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJm9y8nqv5Py/zRJVpi7pE0u +VmVrGQo2zRokW/ZuO9bN61S44hR6AKUobovVufqsNmgQEZ5BONP/3l6yR6Aq4ATaFdy5Ec/+YbAdwsytHjsjQg== +VmVrGQo2zRokW/ZuO9bN68DuozpuQTlmqRl34f2eOHseerzXblhEKsNhb2vs5Ff0 +VmVrGQo2zRokW/ZuO9bN68MXPHGMPcxrDl8VU8nc78icU4F1zYizwy2HV0DPHYJTway1sSDQd8lzZio+Ktlx/g== +VmVrGQo2zRokW/ZuO9bN69dRIuBXxqstSMyqkjuz4hmc+/u0Pn8R0vur1PkwWxJxkdPkyDRHx/DzxwNK3aPtvw== +1u+XjG/2+GSQRv6EzCaWRQ== +F9Sftf1PoN5P+lgvc+r10FT6GwAWf3OH1Mcvp8jOdb1pTtMZQczVgCKJGOvEAzY0 +VmVrGQo2zRokW/ZuO9bN65FWZiWNfabLLUKv/j/pIruOHKtdVs6BolvMynLb0NYkINJbqGdGHKh3pwzU96n3H16eWZo5MOHVBpM75uHBdJ3ipz6Pg0iRPdE6+7+SnwM2 +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJm9y8nqv5Py/zRJVpi7pE0u +VmVrGQo2zRokW/ZuO9bN61S44hR6AKUobovVufqsNmgQEZ5BONP/3l6yR6Aq4ATaFdy5Ec/+YbAdwsytHjsjQg== +VmVrGQo2zRokW/ZuO9bN66XiAxJifzrdqMJr0Vi6f6Er1za0cjqXwfvOvug+gGN+UI8G5pjIIlZZhjnEsTtIaQ== +VmVrGQo2zRokW/ZuO9bN68MXPHGMPcxrDl8VU8nc78icU4F1zYizwy2HV0DPHYJTway1sSDQd8lzZio+Ktlx/g== +VmVrGQo2zRokW/ZuO9bN69dRIuBXxqstSMyqkjuz4hmc+/u0Pn8R0vur1PkwWxJxkdPkyDRHx/DzxwNK3aPtvw== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN65FWZiWNfabLLUKv/j/pIruOHKtdVs6BolvMynLb0NYkdIquYr81OqSexjH1hTfuKf0JPSSa1cXoTFlZCQcTvRAl3r4tOoST1NcYL/bQCxTD +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJm9y8nqv5Py/zRJVpi7pE0u +VmVrGQo2zRokW/ZuO9bN61S44hR6AKUobovVufqsNmgQEZ5BONP/3l6yR6Aq4ATaFdy5Ec/+YbAdwsytHjsjQg== +VmVrGQo2zRokW/ZuO9bN66jeJFeEUBfpMFw08/CF0hEGc1OR/ngG1FuEwe27Qxa5 +VmVrGQo2zRokW/ZuO9bN6/2tEmuR5A8x+f/v64Yc57rXiZXo/lwRfR2FWn71IN/Gwt9ZxCfxEXo2EnFJk8l4lw== +VmVrGQo2zRokW/ZuO9bN6zWtJzhMO8kW4zfjc6tVOvSTLJsrBTA+DYgjo0v95rM6 +VmVrGQo2zRokW/ZuO9bN66R2klGQ2C2715cC3kBnsTYQd1JYjglbXRxBRgwJuMoB +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN69msIuj8j7GAyYqVMi7jZzwj7ThRkDWXP/o2D0Fzd7aLnh8YvmsE2A2FmEZPcXQNJQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTB3pcp1z08CiB5hzzXu9vyM= +VmVrGQo2zRokW/ZuO9bN6+nKvAE8klUAS7rTKwqs9qpnkti3YXsvNZ6cQz30INAW +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uFpDF6Gyk2UbO0kYdOu3SN4= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+ApG5Mqf2WNrCxn3Gs2J4gO9QtfVyhZMMNOC56RS2QJkE1F3jgXr8Uz++T95hbdOqu5X6sTdgwv+5kZMylMPTuF +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gsTsx7xA9KhGk+vbrG1DfC1PhrBVd7Gpna1pDUNfyFbTqCY5hB/aQUfHzrVs571qDedA6chJ2qePaIOlsvgkWLOno5+qSBUGAvbjDACK8QbvMTFHi1x85WX6nbG5M1bs0LrLcyjlsK7yPv89RDHlAChqJslIXj3ftpKqWjjulqGiVfL3E4OGOAcswtWVnHufAk= +VFTOjet/jHfq61eirHfZIdgCtpI7t4oMY/WZBhwrqJo= +1u+XjG/2+GSQRv6EzCaWRQ== +sDtJSpHge52JmNZhrZMCGYpIcONHyTFzOT2w3fomHV7sC9WfKTKMgYDd/fLvLr/T +KZTmaJLp+FU9X93j5Tpqtw== +OesRJKYu44O4NH30ZL8V4BEPEJFmy//dPbNw4FRhznmbbpLLpb8IbIC+B/Kkz6jv +vyYQukiyWticSXOwUY8Mi6Si8vrAGYSN6Jhlp0g6zD0vA03deIc7rjRB+nBYUZwN +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +0R+h+w6Hf9rHroWFUcGug0Bg6EHLqpJlj9PtariFudE= +fdeYOeeuoWy6J7ezwxYzw4ZIY9pPIraCczrpQYJiR9mb7+DAhEV3ebytkVxaIQ23 +ojcUvW+Z2ehEJ6yMJpmY+1qbBrZXJsPhXyLqkhY6MM2eeV6AyNZSnMoRBLFCNVnT +1WBG7bZf/s5gJ+qIaMFAbF6S8FDZoLzAhhqJ2MNTCVdX0rFK8L37rJXe6iLQ+VIT +ABbY04lsTxNhdVh21a9JsdTVstQlKOYc4GlR2ph+k3k= +7KFwZuSJAC7mbgtMUMJu2zeBbC3XtaoOE8JL/ymxSKo= +Dlyx6ZDFdkn72YIxkC3bo7GMaBnPIeNZdMzUVdRqGKdC+WYea6xFLhhU85UufGTUhNvOWD2JEFPBIUptqGgTCBTTbJ2EaowPispOLjbj6Pf5uWt+rvwnHL9C4/+D95rklyWC2NrA2USx2CODo7WzXA== +VmVrGQo2zRokW/ZuO9bN60EXpD8GKK2fiY1Xg0XFbMyYwhONhKS+p6paMXgT3qX0nX6YjEUDLqRgt9iLSNuHcQ== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmXbFWE3Z8RNiviG8V4hH16 +VmVrGQo2zRokW/ZuO9bN656pTK3mAcJtKx4qITAmjAubpEiO6CYsc0NUVzgKF6yg +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6/53VW+ZlBDVzVmTlHlsnW9qegPY0HL3zn+yEThRxHYemjjfRlraqfjjigIl5ztY0Q== +VmVrGQo2zRokW/ZuO9bN65wi8pbg35KHVf5hLCcaNjP6Y52g0tULksGaE6M5gmLRKi9lbuBWKL5pUOw11lRIyQ== +VmVrGQo2zRokW/ZuO9bN61w45ohnrfIHuVWBaR1GWNC4IGPG59UBAfvbyrGXbovuROgbv8sXB3LYfCsdgynLaA== +VmVrGQo2zRokW/ZuO9bN65+n3J7Q157xwH480GlAa6Glg9kTevDy3qUm8fgZXDfVy+/ld4KKcEetaksCcicCWQ== +VmVrGQo2zRokW/ZuO9bN67JeIeiYGs5UBd0XMQ9WocCkzsvFIGNYiDZf9hvLeuOZ1sVSi0InB3+1FfCCHIYgy8JxxOTZL8/cjogbKkfWXTc= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn3/nWqVEkTaZU6+sH0KaiKxOOrUCk0nTXWtEvL0pqNFg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl05UTLpRkWz5XW+5+6lTE6JjhToXs7eBWuf2pul2IyEbF5rG4luplCJxK4K+cXwO96KZBYpxSWFkz+HCmNy6zGYBI9VOVCx/WRp+TOZuLP2WXF1PUWt6BBmW7tlOiLCxg= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmi60cDp2TxrMvVedfpglvacyvjQdToUD7W/8SB0KfBknabyryJl+w2o9ArKH35SLM= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkHCzH2qHhO/qDkepEmUpj7lb5oI/8ZPQEQscOzPkJeMhAtxd+T2VZKP8A3UfotdqI= +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6+iELpcntY7rjeay17ceU1DAjm/bhNxoMtXxcae3Elda +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DJq9FsIZwgRA0seTz5ZLwfjooyZ9lmW+fh8LaNxhSyH3dIDn7uBoRZsSyNQarnt4JOWZuFvvm+G9tPw79kxhBpeIwBnMSZVMS/10wQa5Nz3PZwf4o6EgYxFAIDuDYMlQH1MM3mHcjlSCBBzRAO3mUc +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gsTsx7xA9KhGk+vbrG1DfC1Em+Vi7/vI69YuDPCPXI7UZWkEwMfdSnw3H9JUPEP6s039CTBUnsztM0S5z+QZrRK0X7DBp/id2lXgQ4Sckkc0TOFwKo7d1VFsY6NlJd6k29m2LSKkRKj5Dw9gUql+lElj3ab5H9KmY2RsWndac3zthWAMmFnOwibXGbiSVr0PngLIFyLtohxrYRMSGU1cxvztdWDAp88mQU2xhUKoFWfgw== +O2/afRhLjcpEpE7n3oiRK+e/jZdM44aBvevq/p7Lcog= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvIvdA/BPxMMswj2KwAj0DzPJpEuJa49EPzEJPcZn930wbH3mCRcBIkwcbSSbJ2Buf/ckKrCBwetl0stqUIpxv/PgScjmiLT0rQ63qva/FBQuc7EtzX0sRoHtiTBV5bAi4A8tGF6iTh9bv/CV7GFXUaJf5r87LwMnuxG+Hf0pn7tbmJ1j60p18iOZWgJa5y3p9iaazpAtqHxJ9cyUksjilQs8eJkKaY9y63M/u4ftUReOLHQekR26YMv4TWRCVXokA69ZjHFGpxWB4G1US+urNzEUvoyK60K5wdUPbNGThdHmK/5yu734i4rtJi90FFL6WF6rXApZSljp+Me6oECUHd+5naIminskFkGbEqzsxuCqw== +l2FJPs4YkAmmok1ulDRuSA== +L0eUthVnpkGsmKFAX6d+uINvW468tRk1gDFX3gUWs5BHEstgx78O51lyIwt6GKBBESpRrUckWKsGqB23pJTF8uQCSM7KG8/5kLoTI00Fxb/XU2EYNcz9bMBO6gxbwsfE +1u+XjG/2+GSQRv6EzCaWRQ== +RbYBt1qPOjc7CqnjQqSariWidMbAf/9f2bpNNAc6jss= +KZTmaJLp+FU9X93j5Tpqtw== +n7Qm0xjL76LGt3LsjFPTfN+RjEF8lK4wZw2W6CGgGPI= +vyYQukiyWticSXOwUY8Mi6Si8vrAGYSN6Jhlp0g6zD0vA03deIc7rjRB+nBYUZwN +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +fdeYOeeuoWy6J7ezwxYzw4ZIY9pPIraCczrpQYJiR9mb7+DAhEV3ebytkVxaIQ23 +ojcUvW+Z2ehEJ6yMJpmY+1qbBrZXJsPhXyLqkhY6MM2eeV6AyNZSnMoRBLFCNVnT +1WBG7bZf/s5gJ+qIaMFAbF6S8FDZoLzAhhqJ2MNTCVdX0rFK8L37rJXe6iLQ+VIT +ABbY04lsTxNhdVh21a9Jsa5MM1huVcW3K4ep78S0YvzIBzl0Htj8tDy2gZLA89S+ +/OGQuMZnBzFhUjEyuxVI3QyDvHtQGcefzTwcHXPdSDcqeG/4oUO/xM1j30dN/366 +7KFwZuSJAC7mbgtMUMJu2zeBbC3XtaoOE8JL/ymxSKo= +Dlyx6ZDFdkn72YIxkC3bo7GMaBnPIeNZdMzUVdRqGKdC+WYea6xFLhhU85UufGTUhNvOWD2JEFPBIUptqGgTCBTTbJ2EaowPispOLjbj6Pf5uWt+rvwnHL9C4/+D95rklyWC2NrA2USx2CODo7WzXA== +VmVrGQo2zRokW/ZuO9bN60EXpD8GKK2fiY1Xg0XFbMyYwhONhKS+p6paMXgT3qX0nX6YjEUDLqRgt9iLSNuHcQ== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmXbFWE3Z8RNiviG8V4hH16 +VmVrGQo2zRokW/ZuO9bN656pTK3mAcJtKx4qITAmjAubpEiO6CYsc0NUVzgKF6yg +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6yiPTEYWFyiii1z6fYyTRwSQ5ZL6P09z2N6tIvkiKIJtlGP7QJM9/vC0Mgb2xTKyzA== +VmVrGQo2zRokW/ZuO9bN6/53VW+ZlBDVzVmTlHlsnW9qegPY0HL3zn+yEThRxHYemjjfRlraqfjjigIl5ztY0Q== +VmVrGQo2zRokW/ZuO9bN6/iKpolEV5R8cPB4C0cpY6IbSgCdIcDA+feAwumGbO/xhqZzuW5iwvjZW43lLpJcRpGsf5x3gc/LoEyYAff1Uhw= +VmVrGQo2zRokW/ZuO9bN61w45ohnrfIHuVWBaR1GWNAbiZG8jVcxusqzImOwPDwesXh9MAcy+SfJqT0TOvN43A== +VmVrGQo2zRokW/ZuO9bN63QaiT3TZL1ld2XIlhhp8mFsNZtZPH34RFJK+kA1vyvF6PaxDUXc8/VIU6ejv2dGt7ZvsXzis/SA9ZJ1c0PEd5A= +VmVrGQo2zRokW/ZuO9bN63a0AyG+0Yd1AVfwXv5GgxEXbo4ulcLt1fkQsIDZCYWX3Z9Vg86swx4NgmYxAbcSaw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmeDBC+wwB7L00dSItUTzpGoOpMae46Kkr4wRAOQamej3gtJ6pC/34dwPDdn2JtxuWTliUU5G0oYJC1JjkBSyvX +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknib4F/ULGtFP9wWoFfp45lKxxdqNkeevIWoGcgcUXpRvcWv2iRU6EqmJxLnVVR0eA= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklTEsfbjnR8UzKnIvKxQFo/ +VmVrGQo2zRokW/ZuO9bN64ABOq3ND4zxH495RpxNO32j60kGFmn9giYlDubEKLPyAv6w72okw/zZdhRnx3YDTsaPP0AyDncoglEF7HYknOw= +VmVrGQo2zRokW/ZuO9bN61w45ohnrfIHuVWBaR1GWNCB/5rkvOQ1JUxCLOsb7iACAoxoWOv1PvyamMkuHECtiA== +VmVrGQo2zRokW/ZuO9bN6+ITNwUqwqpPF4QwbxHLM1Bea/LGRIU8PGwRgIxuIboEoZ1UOShIaoAEy5qqmZ4FuA== +VmVrGQo2zRokW/ZuO9bN63a0AyG+0Yd1AVfwXv5GgxEXbo4ulcLt1fkQsIDZCYWXzOEaaOaXx7rdqQJyyyVRzg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmM/9Z7SK54zPvY/da926qA8bChbyodfv/rEIOy2PE/ByXjCo8xy89KE1f+7F0p3mwv+tNgofGqpLqp0m8m2YcP64OmtoxsBfOQLx09zV6jAQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl+7Z916maMXI5sNa5j8I1rOztvvDuMDLeRh5/7FfDQKi2q0Wz6n2Mk8AgB1+W5p2wU4KRs6OXsttNzkCU/O6ly +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hknib4F/ULGtFP9wWoFfp45ldJzgqemiYb1uWHn5Z4CyZAz/mxlNoz4KAOXoElHPKO8= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklTEsfbjnR8UzKnIvKxQFo/ +VmVrGQo2zRokW/ZuO9bN6ywyqAt0WUYk0wz2RaTbkeMb61E3V3fbNF4IaFu+N7qh +VmVrGQo2zRokW/ZuO9bN69mYlXMpkeP/tI+BiRsKgP/nDrnSbwXecg0sLxbrNySXN48BrsK05HP3ZcpiOF2B0w== +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6+iELpcntY7rjeay17ceU1DAjm/bhNxoMtXxcae3Elda +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+Bi8Z1FE+FAse72JdcAwLyz7N7AdgeMilAl4jFyEUL9y/9danSEfjfFUTf/EaLw6ihA9xgPI2q6p8tZwr0SCG9th6hRuGRTl5HCHN8CCp37mvvAOYzVOE6RqFtDkj2E1keSZ3DDp8w8VfhovKHylky2y0VvfSxIim82AMPNYVnf/Y3ecf8pgjXf4nTfee0sp6U= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IcUXlek89393nCblo8Isrs4O2g78nhDNiOaiqZ4JTWhg2uFQtFA7cs1sgChJQO16mi/8h3lvnbKT6S/5yL3VpSujVeV7oQ1BVAlP+yJCxr0Bpqa5GjhYFYHLFBa1o2HO1Ux4Goq9jKl9+1e12Hz8DkJumijhNOnCgZxde/xHUVevT1IajgJWnlM7SOB0LsASMbcA0ZMJJDbDYGW4lhyPb3RYXf+MZJMtoemC7yqRWR2s= +O2/afRhLjcpEpE7n3oiRK+e/jZdM44aBvevq/p7Lcog= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvI0ChiHvIXECwh13b1ou5tGyAcZ989PvBblU5ej+BDvnI3VEWIlXkiZuznaMMdmT5p8K47ecvwSrlykEZn5WeFny2c0ti4qrLfqupJMrEqSfZQdm7BkeYEiBJ0FCE41WVNsw/WAHqnAFU9UIMcV/HWmYewZVUQsIKROXUH5Qr2+Wjq2fS1jDWPIWjs0ru1Uf6ZinM78UnM/dcwjlvf6BmEfYaaEFUP/yV980z/SZz+mB90Rv72yOFcHjxqnQMKNs2SuV7wkWIEDKjgYhRQE3wISoVBN4KKL8UObGYP6Xc2giXSgcVv4N/Sav6SIWi+DTP5yk+0/OJc3BhybDAUFDstSEeZ3g6Ne0HRmCz1Gj+UxWQ== +l2FJPs4YkAmmok1ulDRuSA== +L0eUthVnpkGsmKFAX6d+uINvW468tRk1gDFX3gUWs5BHEstgx78O51lyIwt6GKBBPCwh9svUVTinVDauyCUCzpvXvQeGO/iAQvQInO+ZySF9go1JNC0U7B5THCUdtyaeE0k59LpmHWJ8fXKRCbiBKg== +1u+XjG/2+GSQRv6EzCaWRQ== +NNi1hKZwwGwLUUv7+aXfFX0WlAgJjMF1mn0cr0V+eLJZgPm6ioMdh3wF5NBd97bZ +KZTmaJLp+FU9X93j5Tpqtw== +bbq4lairxF5x9Y/2FznYXzYyAEdIhqXtt33hBXZ7ZUI= +WuRvIBaPNRsVGEMoM4NSTre3AWcVm9DI4cyTv48g9mE= +KZTmaJLp+FU9X93j5Tpqtw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKzFnp5L8v7gjpRd9cQpcaGsEL+3gMZ3+Rt/32jPeRKibN+zEdpsIi8yNaEoNqLoj53hjcigwD4wJB5VWlIvJMPU2/HcstUaxhHgX47JfoOyrmDcVnouQB3nlqyUUZYv0= +FdiCS85SRBQfnSjMA8tkZV6oRvzqSRJD1LpQbDuFDxeH0O81t1s2G1n0XMXVJbAl55s6qHrz67OVRBVai6CnmQ== +WHkOzVx7seuLmxs5Hu+l/oKDOLwxIevEYDQQSkSHLXAyjmecsvnAyvZgm4ierwqH +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN64iCNmu0AaHyXrBNPVPWAp1ThxeVb/KD0vtV/xz0AgMQCRJTK6q5yiadoQl/y78/NZagoV8IvrhjMB4eEHoMngc= +VmVrGQo2zRokW/ZuO9bN6/buxDVz6te1BcmgUscw+/QJM0DywotxCnRiR/pXIylL +VmVrGQo2zRokW/ZuO9bN65Duud/K4k4MvHXmn2qEajZGrx7fedXEig/ku4JxVgi4 +VmVrGQo2zRokW/ZuO9bN60bFno3MqHd3T/rG2S3ig0CBVt+KXXSqwzc4kcuoC/vf0yf6wiIJozIMOf5i8CNXoA== +VmVrGQo2zRokW/ZuO9bN6+y0ZOZctjGr3gvXBvJjV+rNELb6u1MfHULHNG8PWPaW +VmVrGQo2zRokW/ZuO9bN6yjHGE0JXsLlrAxP5SVVNcL0NMqchZOgDKhkrULXUXh28+y615jF6QPTvHtvK/hzXnrcYC0dElsKGyMbLigQiVI= +VmVrGQo2zRokW/ZuO9bN6/Ykiia9pTHWG10nmQ2azo226d7dP/GWOCZv7AWa3+C7tBnYRIz4NKi885GjKIz/9w== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN6+iHurRnEBNTF3klJjMOc+gO0ZMGRV1MNe1GkDEzOG7EbokHDAj4CUk5czh9q13yyQ== +VmVrGQo2zRokW/ZuO9bN695kdCbBp3rs6kAkTjsyuSsZoEWA5E6Gt2wvV074dODC6bbCoQ7owj8e4lpPmNCB4A== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN66wmtriv6QXeuDc39Fjfymhh1V6oDvIzFea05q1WLQ48dxmVpbbVfW5hVjLhRerYwTelQ7K+1ebii3T4OIcEnkkAOE4av29HoAr2p57LJb8SR8sBvEnH+n/pjbeSWzcfN5B5cORWnJjsqZ3Oq2En0tlZjCjlRDROXlpsOTbv/0Yx4UBLzcVjMZVmMRpnu+qwrFKUDB6hnkaLeRD0rn8jVNUZBhYnFEOWAOQBzIbOmRgXeSLQJfhUqiWvgS7Fby4liBiBCVAUu2eJus9dOc7DkS3IRXbWeuTSGY5OdPdpQDMMzfr6ZX1yPidjE5J2PUetDQ== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64iyrZNRFu6p0NBUfnEATxgiNqQd82aNTyTpr9w4nTw1e+MI7JW+nfXnMQ24Ue3P7Ar +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gsTsx7xA9KhGk+vbrG1DfC1Em+Vi7/vI69YuDPCPXI7UYtEkmFhepnZKbL1TW+7aIkE2Fa6jeCR6vjETvLOLnprnY7fIGTOFdybiFsR/vAhH30D34Mg9qevkD5LCjmm49X5v0sbzzD8OAzttjX+w8ae6YeRRcmoU4SohKnYA+kB4Q== +AWid6G8ypXqQe9XxPPYEDxbTVrr/afH0tgxxR3vp12kZjXdV2hhayXt4Z3ANZRuYGTykjT9/QbnIaFTnHbK234Xm8RKJ1q0gBzf6kKc3zbvXY0O/dlJbKajVKf4Se08l +1u+XjG/2+GSQRv6EzCaWRQ== +eno9WEg2wTTzJ+gLpzzw/Dt6MMn2JHOrHDTXxQU5NQzKXNPSaPCkxJpWXmITHMkF +KZTmaJLp+FU9X93j5Tpqtw== +UkZvM94VD+N5rc+bpUuSyj930VFc/PZjT/8WTlyk7uUibm82rLjnHJ7wAPKl7iCT +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +FdiCS85SRBQfnSjMA8tkZbHZZT2xZv+S1BUUnmnW7OQypFh1+BoWo8VnVA+4JdUw5lxTKt/Yq3/o1vI7ddK9Kw== +gAw6jEA1xpCl2x7CdbH5N0fhpimFkvWV/09+ofK3lEE= +2rR7FCuURs31owV4VKiJMGBmLmcYRap+vwGtJtbKN/qJPNLFgtBP5E7GmPy4CTWCFZsWXTpiYPMCFSrxHp7gUQ== +CoEn0XmcJvBhYKpgK7QRAsUOx/pm360OzXz3G91DO7c= +VYbNNoCl2L/0xN1rxzqDfNaNMljNeaskgOGXkCD7BmpIHlyPxUitV/bU34NYNXJp +m5EI3+BXxkQEYKYaxORD+gqL2sR27598EwZ5hS5OCirQ3GfmLOOwsZHXlapY4KmEI4Nz1+babOgSBI3i/+6TmGjdDaoKX+8ilzuJAOBclJY= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN68zGYCiKFErhHvD5ZZAc/LfZgze3bLwYCdQ+hqSMbOSJ +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN6/buxDVz6te1BcmgUscw+/SurFNddjQ0UZMkynBUYcYRlWOccxGXy4wiecMi2gRrCQ== +VmVrGQo2zRokW/ZuO9bN65Duud/K4k4MvHXmn2qEajZGrx7fedXEig/ku4JxVgi4 +VmVrGQo2zRokW/ZuO9bN60bFno3MqHd3T/rG2S3ig0CBVt+KXXSqwzc4kcuoC/vf0yf6wiIJozIMOf5i8CNXoA== +VmVrGQo2zRokW/ZuO9bN64m//3Jz0ZiAR0S9mCkiLdNkG7nfU6RCaa9+TDQYlAOS +VmVrGQo2zRokW/ZuO9bN6yu5K/jGWYCtAGCexwcaRcOK8Hxixptm3cYErBC4rrj97iBCLVsEw9fsQ99+QRUS03KrgkC1dSobB0UXSTnqzthbpV5nqQxQ98XAc0/fW/H4OycAZ258d6jaM7iz6X+UJQ== +VmVrGQo2zRokW/ZuO9bN67YR/1PYhFzsDnaYstxq2K+czAMB7do8wzjOL1nCKk1rspQT9RsBLfw6hrfNff2FpQ== +VmVrGQo2zRokW/ZuO9bN62QpyNaGZlZhv4IbQG7908XnDM+dQtlsizeF8XBhx/7k9f+WeHjo7ZCi84RwMM3TBA== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6/MOkLqGwS8VnfX7E3oaijPEFJmGqIQCWkaO7MDul/7J +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +mrVdYSdrlMUt/i3dk4wvYPt7L71DMwMIf5lCFz5wmcU= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvI2ZIJtXma82ILlINzNlhZetsXiQ3gRQKKQIbpQDk1s6LdW1Hi++CXreQV6VXdV0s/TETX+UVVrO4bUWKetk00vP/gJ0ZdiGAjhw8vLqBLrrKC3XjIOM+Kg2l1GyWAkwVuuNT3o+LPuuVTzGwqQvj2u2EFlKX4ynpxHRCFuHbSe1keAxrLIN+gpx7gXOPWFk2BeL+DV7fjd38MLf749v/pMupa4kRdjh55XM6bDOrUxHdLNcytaKRBWyMCdV6+tF9GQ6XsH2GUfjUQPgxGHN3zFNlpeddmsp3Httq+o4Bp+vQ== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+Bi8Z1FE+FAse72JdcAwLyz7N7AdgeMilAl4jFyEUL9y8T8uhJuOqsPISjy+vfkHb5K1qNJgW5Hsmh1mSpGt/1F40AtzDdYVHJB03cN5Qrn6/H6/8VvEXu3/i8UmZi6lIExC/uUi/UnRa0lruTyFvle +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3Ir5ImnwbZflgaEXs04WUTtwWYt4EXu+bQOpWaHFMmFWXCu5yGjn0udwWDB0DQccukiu8C/U6kAE1V9ka5ZvVJye4Rfi6OHYAsCdqf3txPdW0= +AWid6G8ypXqQe9XxPPYEDxbTVrr/afH0tgxxR3vp12kZjXdV2hhayXt4Z3ANZRuYm1LgMK/Ec4Kzzh4X/6KIhhr7M5HNACrgGIQBlrPavApjR0Z25h56CbtFcJjA/x6v +1u+XjG/2+GSQRv6EzCaWRQ== +OkR9+o3X4leB2g7h1B3lJkYA4nWPGhkQYZcLBLYBzWBhuUETlC6XCwMuUKo/Qm2a +KZTmaJLp+FU9X93j5Tpqtw== +t987AUZxQR72IPRcAqvQ/tibhHz8PUmHWkn4njG5giZS80R/TFcN2/nOV1r8J8nvYZ8RCxMEMkjRnAHsR12zbA== +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +KgZhnCYglllfsrazbVxNluM0wwLR9C9fkh4Pz9PkxWumtUfGfHTWVnBtNg7DAsoHYQOgXz6ncqBs8DnqbgiyyLdhIxLWHNsTf9TX85io1+t0CP0KVo/0GvYMUnoLzpLU +b4OJVZe8QyIpjuTpKXDL9A== +O3CUgrw2GJfB+mDjH5+NdnceC2jE7ElwqGvUxIcW3ioHfSctkOVZd2w7nq+FgQrLyf6Tgrcdtbn8ez0vOK6b8m4yew6GoCDzKNGlRD2A0GM= +VmVrGQo2zRokW/ZuO9bN6/cAwD7FtgMVHkis4Ui84zAFkE61SWNJhtiefH7/hOOPjBzO5I2WYVxQGNiLItvNgEmqLLMm/ELgKduXss+aI3zOABy9AFY2xOClqptdlMv9 +VmVrGQo2zRokW/ZuO9bN67z7xKi7t0sis4QRWFCs8hCiS/uJ/Rl+Ues2MIWik2Yhg6JPuE/+oEOAiLw5IqEmDw== +VmVrGQo2zRokW/ZuO9bN6/yFKioIAkiiom978C458vKlCkkSuaTcDpWTlkmoI+njhiij6OvVizC2XzPIbWC1Eewk/G77IoNCtrWstqFbHqWTw6rID9GtmRuIIaZK1eFJ +VmVrGQo2zRokW/ZuO9bN6wGoHfF5haCaGx7RecrNSSqUFt3rf2NR06hTa+m4t650 +VmVrGQo2zRokW/ZuO9bN6/qFPBjAcCRKO+Q71Jok/yHRvedPc1qQTGBXupsmVZ9s14eEIq0P74SyLdV71YdqGVsocuDW/C5ZUF5UTemUsGKEPZhl/D5s+F6CY+v5tPTTR2bWJd6vm5YQ7mwK5FWGyg== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvIvdA/BPxMMswj2KwAj0DzPEJjkGl5wONj2n7h0C7z0XGS+5+7HRt1O+hmCRFHoKHpsjAkmMAECdaLGsWFcfHLmNZDybUkxxON27SiRjircb8Ptt90NZT4PSLZV6DdZerV+hVL4WQdAAAImnUlf92pQEVUPf1RdeMI4p9k09by9ukjCzPgaMuPGscSgFXJAN5l4QxftLoy+J0JbddBrKQJk +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DJq9FsIZwgRA0seTz5ZLwf3RdIiHDnlgHNwLKLC+ZJ5HCtb7i6Wj9Uhnc4GJoKW5Hw6Un3PAKmVUNrhtXbGu6D4lfXzvdo5Pc3SRuejzw/NbyjSacrlbht9vQMVkU63oYGCzO7S4JLQVfttCN6gGxcPCASuq7U/xQJcvVJt7UurajFfxNzFG9hlSFcHj9wPrs= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gv+I2RFyT79Y1QmzUWqMcCDL/GNHeFUDcNCmJXnMAHPVyFWZrjYwGSdBpX4MYsdiSPFEycEk6zFFEiJEFHbI2jY6FB6n69s8D1lfH5FHL6HY+XNLVP5dpKcKA6Poow5VvNy1PO1SAei41UmoZXro/ftL35R5lS4vC0Qz/C3eh8wKhTyB3X94e6wF6TCF+7NbfNtkevu7HmPUe2fq8qEN7hy +AWid6G8ypXqQe9XxPPYEDwu+TqeLjRNiCyKIeDxmJ+4n/guLg4KTbgvVWBA2UxP+F3daAs7BWSBRZ/SEQhqEHzBKP6qD2YfRVpYLkJCVkNvNWwUrJ5qCi4bonEMuSfaVPUxVhgvHjBo9pDaX7Ni8v2f9auriDVW+rIbbzM6NQgWSsvQ1/V17Qwi6hW4n9eSR5kgTGefBQXNC7YipJTZahBfcWUzOoqOGvOuzfKWESNQ= +1u+XjG/2+GSQRv6EzCaWRQ== +OkR9+o3X4leB2g7h1B3lJt7bvetLNL6EgiHL51ksw1bqEGmo228ga5yo3iobCwJN +KZTmaJLp+FU9X93j5Tpqtw== +t987AUZxQR72IPRcAqvQ/sY0M7/tWSmKJ6lEKTULAB8E+VMcnZguJpvOKm7smI4pP8Ot9fGmR9fg+Q/ldI1yQg== +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +KgZhnCYglllfsrazbVxNluM0wwLR9C9fkh4Pz9PkxWumtUfGfHTWVnBtNg7DAsoHiQXjI/rHvzhqEIyknkG/Ifr8bO3bbT1UMq8Jk4B6AwPIfmej/ppzKIXLRKYI/PIP +b4OJVZe8QyIpjuTpKXDL9A== +O3CUgrw2GJfB+mDjH5+NdnceC2jE7ElwqGvUxIcW3ioHfSctkOVZd2w7nq+FgQrL9VSD27J3Dcu41ygRndBX1xhgoRZ55s1JBrOA0mZyTtA= +VmVrGQo2zRokW/ZuO9bN6/cAwD7FtgMVHkis4Ui84zAFkE61SWNJhtiefH7/hOOPjBzO5I2WYVxQGNiLItvNgA72wsm8ydc4Us2l2g4nMBAaoMrml2MMcVNkOn7dX8CK +VmVrGQo2zRokW/ZuO9bN67z7xKi7t0sis4QRWFCs8hCiS/uJ/Rl+Ues2MIWik2Yhg6JPuE/+oEOAiLw5IqEmDw== +VmVrGQo2zRokW/ZuO9bN6/yFKioIAkiiom978C458vKlCkkSuaTcDpWTlkmoI+njhiij6OvVizC2XzPIbWC1EV4lsITGATrevYEXkFb8uxs4pARhuCnMRDW6Rraw23Ls +VmVrGQo2zRokW/ZuO9bN6wGoHfF5haCaGx7RecrNSSqUFt3rf2NR06hTa+m4t650 +VmVrGQo2zRokW/ZuO9bN6/qFPBjAcCRKO+Q71Jok/yHRvedPc1qQTGBXupsmVZ9s14eEIq0P74SyLdV71YdqGZ34+YV+eKd8BTPwwqngzU2tqaOM0mIi29h5CvJe7pNXrSKW9l44xnz+tEZsS6ec3g== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvIvdA/BPxMMswj2KwAj0DzPmk+d6hDUeK/kx243PE+f8sqNntVT5eLoUyIjVrPRtztat+3tF5b1T8ZIAQqw2jKbd6lihN8mZw1V7gcaYKPkKN5wbGX/V9n+veN5oEZ0D6xFVL3onHQKF/dH92EdkENRePgiLdfOWCUmFzMUFlZ87zr6lk+yGPTuMDg+P8LdkOx5CNhBtzjG2+o2/p9DiqJH +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/gsvOkuNyKQEB1x1K7yfn1TNiYOv6GG4D+U48JzhvRUS0bxD/zJ2fkUDse8r/E35BwQPVtqyvrjP1+QF4FN9iloGi6v7PCTzziQB5AsRUDe4UpC84dAUU4j8KO2r8tMYStidDeSbGD3EzXncoHUoPZmaMODoKUdjbdvXONXAzg8K2u/6PCPfrWatd/WgYGkEfg== +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRq+pLnKGBonJOCon5A9Iu8obfo4Ssynv14o8JKpRSe6NPCsRcl5AsiUByvWIr4wdCRPSK7QEEVzs5Fd0chvgRHU6ZcQimIsGnFqKwigy3DnwuDapzHnc6X04JApWIwQJUc2Bf08/ZcLcCdkrPPISg90/+NLvLhZKBaqmKVC/bpCADv4G/fpfkhZguwFnZ0COmw== +3zDbxVuaFCI1nhEYDQoyM1f9xGpqJQ6I+xPuSfmlBjqc+8R8rBVsAmdAcWdq4mue +AWid6G8ypXqQe9XxPPYEDwu+TqeLjRNiCyKIeDxmJ+4n/guLg4KTbgvVWBA2UxP+fBwe0nvyQ79KXDrPUBCbLAqlyVwXhlUNFFpEb0i9zx5v2cb4Cve9UVABZYxsp0yQ+qIxds/h9TXRRbu101xW8ucqsnWSnEly8Yrbg5eDRCvc1OIH0An2bLDPwXq6eY9AR+7pLowhltH9HLY/LOZ2LfcuVtjs0GOWPjgscxphDhI= +1u+XjG/2+GSQRv6EzCaWRQ== +nVECa+x0Yqtq1QnLZbnSF2l2tGr3XJm8Xdf1dyDLfWP5o0OdC0QXVDDKsyyZR7iC +KZTmaJLp+FU9X93j5Tpqtw== +MhuKaQ3W5CTX2qAwHlXPZ9AMl+XCZrfAVFW9msOF5ycLZctKA2pRtHm66BBrcqoK +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +FdiCS85SRBQfnSjMA8tkZaMBJpdFWavd+guuEDi4XTAEoWvJW639SgiAZFJNrBYP +WHkOzVx7seuLmxs5Hu+l/oKDOLwxIevEYDQQSkSHLXAyjmecsvnAyvZgm4ierwqH +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6/buxDVz6te1BcmgUscw+/QJM0DywotxCnRiR/pXIylL +VmVrGQo2zRokW/ZuO9bN67SBoWlR2uADcUbbqmSDS0p5wI4DxzvHXXmzRioh9YVd8vZIG89Ur675dvik11NyvA== +VmVrGQo2zRokW/ZuO9bN6/ECUQ1pEZydIMH/HOQ6ULrP4XlNr+6Dc22l4vbPzglY +VmVrGQo2zRokW/ZuO9bN6+iHurRnEBNTF3klJjMOc+gO0ZMGRV1MNe1GkDEzOG7EbokHDAj4CUk5czh9q13yyQ== +VmVrGQo2zRokW/ZuO9bN695kdCbBp3rs6kAkTjsyuSvRn4PKR7um+ls3qO0eKiW4dCIXgCkz+R5d7vbkpjl3xA== +VmVrGQo2zRokW/ZuO9bN62q9XghE2OCF4PKntiTTdFeYBDavV+DrGKV98RjWw7enVFUfL1MfqZmVc9AvSrMU6a0Nf++LAJdgStRWeVEBl68= +VmVrGQo2zRokW/ZuO9bN64+UkdN0Lokn3FF1p3mXL8nNUlW4zXUenXSS+taauSH/ +VmVrGQo2zRokW/ZuO9bN6/qFPBjAcCRKO+Q71Jok/yF9QXjEMLxZU0vpc1LZyobx4spAJCp7PacjQidkDYnNyRuyWgHy5yGVVlhTMoIrV+qTLPGUel8eu62qzDEhi8UtqQa3TYESyoC+tFaOH4HKR4toh7OXTYd7hzOe25wDtSjKbIX6formVoRnimOC1XjsKJccugDqhIbtN4yEOsB6tUm2/BuTW0Ieoq1A52unTPi45QdudDUDFM+sojsDecJVKETfM0mWkMwxGjNY73UZmQ== +VmVrGQo2zRokW/ZuO9bN6147x1dkhbDeqs2uAuxfhFmxq2/uca9NaDYGJ2BAX8sLScPslONplwtqoLEYQiXt435lYdccpEtUz0QUHTdsE4wdgNU1Z3gU/KjMGv1PSqrsTRh2GodoAkU5qGNLv9MU+Q== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN66wmtriv6QXeuDc39Fjfymhh1V6oDvIzFea05q1WLQ48sDs5DKnirpJPxnGDyxIvNUcTbGulP7M6tYTS1Bq3ia+fp6vhaSXc8lmTaDZT/r3bct6iukzQWNHyMQ79YE1J0I5uPs1xVGBqkHPkG3YVIzm8RUt0VTyugnlhhSHRXLSJvb6Kbw6GJuRxI2lrLaY1pkX/JZ7Sy2D99MBeynX5EyAgNE2NFKpBpx7Elp8jU4b1 +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+BrcUIMudTtdKnBbOiZIsdmYYc3BnIuHAW6aQ1+iO+Ya6rMo2YOjrP9G31wNRAG90uOoQjvFvgHPFxoX1Zgzty1 +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3I1HQBLdpHf/UEi3kk35v3/oRCA3fCp5MAsrwfLTMfNVpDjudu5qwB5RKwL0YOAgE4w9JM78FC966GnO2Ae/vIC0Xj0wqT12IaOoKtxwkRk/9PtUex0DAX/MMt6wf86pVOYVI83W7GiJSivRwmzmF0PomdSNFQQZD+v6I5mfo5Stcr1yhPA1XE02kY8nyaY8lh +AWid6G8ypXqQe9XxPPYEDwu+TqeLjRNiCyKIeDxmJ+4n/guLg4KTbgvVWBA2UxP+v8XGqRSyNn1ZxoEQrkOk9Blse6VyVcHv0uZie2PeTg1ZCw/FrVhBKWLVp5Vak/muNp9qI98pCAkH9KTAtXMI7wru23+EuiZX8ZVAHfNTIOZ55fE8AefcGh00bkrZR4bKZ7Ffb4KN7C5REs8msjM4ry5CLFaxF9U0TtgOen2AHZcx2HpRPyQDB1xykk3sKOJP +1u+XjG/2+GSQRv6EzCaWRQ== +xRssbjvdeiRK/kTrbUih2Uwt8ZvAZwKl0lbBn5ClukAIGp00rpYSraa6CBi28C5F +KZTmaJLp+FU9X93j5Tpqtw== +X8vlhlWnGAA1nrnZ5ZveWZdJtvjS5hFrl/a/QftKPrJ8ZBElpfR9S0lTyghWy45c +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +CoEn0XmcJvBhYKpgK7QRAsUOx/pm360OzXz3G91DO7c= +gAw6jEA1xpCl2x7CdbH5N0fhpimFkvWV/09+ofK3lEE= +JJZbyyrz/lRPhW4FMh1hcGuyLEPLRHRXVqorNJ2XOjwnDmS7Vz++8Bj5AU5/VIUs +DR5nxYJ6zQKKFtuQybUoKkmLYmPagy2c3k8dauTnSv5uufhzA4rdZXVfFHdo/iph +ST/USnRm3XSWOYqaR3MkqSuaJAfILc3umNmiE5Yr9mc= +nkN2edJiPuYCVVYG3gy17NM3foSxSLfpW7iDEGJfg5POb5mDQhK13/akeyzZ7/2+ +WHkOzVx7seuLmxs5Hu+l/hyHoX60ZpE0NaiFK74zP+frkLDZ94LjTFf/aXlDaMjs +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6y8Eoo36yyDXTxzlT/au5oVrbOQpyEqqlHDNx+x2s76vDrgJG0ZhN8rteYV6W9PeGQ== +VmVrGQo2zRokW/ZuO9bN65Duud/K4k4MvHXmn2qEajaT3SMsDDJFD+yYrbVmOMwi +VmVrGQo2zRokW/ZuO9bN60bFno3MqHd3T/rG2S3ig0DEBnYH1Fvoj1weGsf6mT+CGD+z4HPX0o2/0zdDBqDOnw== +VmVrGQo2zRokW/ZuO9bN6/JRZibcuPBU8/CiGNnTBLd1gAIkc167sax4FZiX/DqL +VmVrGQo2zRokW/ZuO9bN66TNhkklohQU8yXWkX1/8ve6ltv0dyEaRRtNS0DqtCgfiuY0sj0e46QmJTbTGgSYVAKGhjPIHt0/3Mii2a8wop19SJ9wtoOuz5VD7sYzYc0QCVEn1Vu6aJ4PTTuRlFepUw== +VmVrGQo2zRokW/ZuO9bN62FEf+z25aVPh3mnr1/MzeWjVU+Y8vf15zZdQF6nDzcWQUJPYD5kQKQCDhlZCoY2tWLd0Ju8WroQQJKgEsvcSI4= +VmVrGQo2zRokW/ZuO9bN6/Ykiia9pTHWG10nmQ2azo1VcMPhswdrFHN1jpqZ5/0/p/4F35gxzjwDww4PDWG8iw/Ua1JnKY3kbAPR9vfEE9s= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6/MOkLqGwS8VnfX7E3oaijOqD6at5Fcyjj46Bdo8bAj+ +m5EI3+BXxkQEYKYaxORD+gqL2sR27598EwZ5hS5OCionjw/VUCtHeAjFt4Miidb4AZkU8kRNmZULy7bwWzTgQ4I9ZrJxfqVy7DqMaTV4k2M= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN6/buxDVz6te1BcmgUscw+/SurFNddjQ0UZMkynBUYcYRlWOccxGXy4wiecMi2gRrCQ== +VmVrGQo2zRokW/ZuO9bN65Duud/K4k4MvHXmn2qEajaM2D4s3SjB5Hr4TTgvZLCH +VmVrGQo2zRokW/ZuO9bN60bFno3MqHd3T/rG2S3ig0DEBnYH1Fvoj1weGsf6mT+C9rASYnUPL7355LK9b8fFgQ== +VmVrGQo2zRokW/ZuO9bN67wGGKFHu25hl6gpHC8tYCtH7b/7LbkWqLaeWk0nWtuG +VmVrGQo2zRokW/ZuO9bN66TNhkklohQU8yXWkX1/8ve6ltv0dyEaRRtNS0DqtCgfiuY0sj0e46QmJTbTGgSYVCZHXJfPAcVRyw/D3CdZY6R9Zx2hogX1nhG53zZnpqpGQTFZYaPP/H247ipcsajy9w== +VmVrGQo2zRokW/ZuO9bN62FEf+z25aVPh3mnr1/MzeWjVU+Y8vf15zZdQF6nDzcWPgV1DSs36UkVti8EGHpqEKawT5xR19GETdKgZj0kciM= +VmVrGQo2zRokW/ZuO9bN6/Ykiia9pTHWG10nmQ2azo0Z5RerD2mnZSPH22OB1sPWUzceVXCUx3ftu18I7o2Z4j/UN1l2oPX2/HkrGZlz3eY= +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6/MOkLqGwS8VnfX7E3oaijPEFJmGqIQCWkaO7MDul/7J +mrVdYSdrlMUt/i3dk4wvYPt7L71DMwMIf5lCFz5wmcU= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvI2ZIJtXma82ILlINzNlhZetsXiQ3gRQKKQIbpQDk1s6MK15d6K2fN7nXRH9JpS+6LJ9jT3Dws2zQ3hIhHzvSTHzyZuYoQs1/m2bhspkn9k+3hy2VIIZ0OEJnp9PdN00KqqLQMEXIlCluYD7E1iDGPsToQHgj3EdAbMxiUIDf0j3ROe0Of0WY5QoEindqrjJkSlw8nTiYOj9EhaDzKXz0HP61dBLR80YMKz00imFucXgYHgggxiFzjTTKtUNTxVXLkn05KXjzpN/1qOmZRsQ9UZxpROfVQ3nObQ9YoqdnVanA== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+Bi8Z1FE+FAse72JdcAwLyz7N7AdgeMilAl4jFyEUL9yzoKALVFbH3Pk63mBNjTaPLQkCBTlVz+Jd0gQwCgitq9nGLGuUuCLbGcWuBS773pDpQA/1DlgQH8h8qHYffcB023mRs2DxSNcclBtdNtJZLuMqUsUl9GUCBOikTLBjh8fg== +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3Ipt4cRxWBWkLTuaRegERI0v8yYOIOrlEbGVUktufL5Jb7YIFuObXcD9nhWh1kKnrlNHHa0ZZOt+8grgzMCe2lSg== +AWid6G8ypXqQe9XxPPYEDxbTVrr/afH0tgxxR3vp12kZjXdV2hhayXt4Z3ANZRuYoqtTnI43CmuVBBSyJx/ylrocToxV5Kdg1MqpFGv+3ZEZS6pKFeCKCxEPgplfwKbG0nc018FXRtPKKRSI5nQyLA== +1u+XjG/2+GSQRv6EzCaWRQ== +cJmB06UmqDKjxva/Os4rBK7oCenU2s+jLAZWOIZU63f2BVRBpk0n9UUXSDOvnWyj +KZTmaJLp+FU9X93j5Tpqtw== +t987AUZxQR72IPRcAqvQ/rreeag3Ad8GhEoJTkRKansYIGwJWg68JbCEtEi3Vp8i +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +b4OJVZe8QyIpjuTpKXDL9A== +O3CUgrw2GJfB+mDjH5+NdnceC2jE7ElwqGvUxIcW3io9lXrx1058xtn9b8Zy8PwI2q5hrccgRDSF+rrBtZxvEA== +VmVrGQo2zRokW/ZuO9bN62kQ47H3AOQW/v2ah7ChRq/hUBhc1YCYvdXATP3+1l3lI94+ObeQmysbvWBuFqyEAy5wirBdC4ub7rWVyjoZf1o= +VmVrGQo2zRokW/ZuO9bN67C3EO0AODwHr2XhnVyil+hJ8qzhwLSszejCosoVe9WL +VmVrGQo2zRokW/ZuO9bN6/yFKioIAkiiom978C458vKlCkkSuaTcDpWTlkmoI+njhiij6OvVizC2XzPIbWC1Edjp/BY2kye43PvmFOvtC6hq+tOcTQc/ClTZ3efZ4QB+ +VmVrGQo2zRokW/ZuO9bN6wGoHfF5haCaGx7RecrNSSqUFt3rf2NR06hTa+m4t650 +VmVrGQo2zRokW/ZuO9bN6/qFPBjAcCRKO+Q71Jok/yHRvedPc1qQTGBXupsmVZ9s118rmnOZS2ZklXHHvgkYh6hbIYuNoCrmEb1433eONkZJaAblhFS+4r6uTwWgUscH +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvJYnepdBaFtfrpFU+gSppNNbRDT0bDLvNMku8ssdOm/T6cMT14iN7gTUqTMhEjP8z1lIadC+Z+r311p/nPWwrt51a0ncxV5Y8pYIX/JI3nc5PxemeYXR0otHWJ3twto+bGawb7QD6KzsdQgYwT1a+J9VjUNqmcsYN+A/2DfJa3qaKMIJDgKLogizg+DiL2oXvk= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DMp35ZO6aoXY/WoLH+K64iJgSodJNk5FY0TIG0OqDrOKFMlV3bDTEdk6Sk0l1OyeIEdCHnlqhNfhN2c+SnE4e4+PUWnKiEGupg/xmBEI38fVKaCGg5ShHrFnzIGsqqcz0= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gtyrN3fmhNqvxN36kot5RF26vQ+BLZ61btprKN3uvvY01GiAwZhD81cs48Qtr3XbV/XxpEddkZVGzHLT/inP3jh0zcBNk4j+ZScrRGlh4dNVwT8K23F1oHM4PfVRLPiESV5tSlzohg2AmsgfcjsCD4H +AWid6G8ypXqQe9XxPPYEDwu+TqeLjRNiCyKIeDxmJ+4yRZNkxlzn9JWKePvvN7ODib/dl6KchyLFVQd1OfygWGYUmC18ELkGE1OewZAGkH7jUN171KEq4zCeovaS7znP0lwiIpHq43tBGjTO+/Kft63PBOUbkhlzXpXbcHOgvP0tUnsb7DHz1c1UayZQ9t9hZ+eNLc5Z7uHHOviT4QVekFBsWGO6dXyvtAPvyJT/g2M= +1u+XjG/2+GSQRv6EzCaWRQ== +n/F9tQKFCOBL8SSKtdjmoF0Tv0yL0K1sVJs5HcqZwpU= +KZTmaJLp+FU9X93j5Tpqtw== +CPTcjU2rzLyHvO5pcE/jijlYQy8NUAqCiCYgA9driBWF5b4QtriVvqUcWCsH9HZ4 +vyYQukiyWticSXOwUY8Miw4iIHb6MzwycaSxDkiiwaiAUvfAc/a7+RBBFPbc7tve +KZTmaJLp+FU9X93j5Tpqtw== +jn4Jp5w8pGBZmH/mrk7JdXgXtIJkc9O8w1VIYQSgAjw= +ABbY04lsTxNhdVh21a9JsdTVstQlKOYc4GlR2ph+k3k= +hikyW30JAmj5eBRHdEOqhFUIfnOQvVxMUeF19AfupMu9TCv5ouwrbo3LTSFK42kePhFyzPjYc4J1hJfTumUu++kQPyE2Vf5PLEDMFVOmPoo3r6t8lTjfk1H8TNdoQaWLIQE2P3S2W27OgArr3+YTig== +VmVrGQo2zRokW/ZuO9bN61vH5sjWffQJ/Z6LhUukRqhFUVsgnKIN1U2qntxDo0ah8KrzveVikvczGRpykv6JvQrAKL11QBS9uuhDOccBU8w= +VmVrGQo2zRokW/ZuO9bN6+GURGd6DS1JyFV64xtzrEC6cPlHGdJqfoBpNgB/sSTgcgT4U4PH2nIVtKJ6QMQ5oAd7rQ1fOctb3DNvtmXOVWcbr1qFUu2REriFoUvh1j8RTUIO+LjOPzeEPXH34iWnCA== +VmVrGQo2zRokW/ZuO9bN60DiZAwvOpVeHgZDS6zB0aBvYLXw5nHb5KOvVaZNTx5JTS6hj9OkNVdCtBdF0ioAGA== +m5EI3+BXxkQEYKYaxORD+sjWZkM336Qy6KuMov9/njc= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvOvKbZ3tEv+5Q+mTyYmHi9v32uKyu6CDih1wbEzENWW8Q== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN68zGYCiKFErhHvD5ZZAc/LfZgze3bLwYCdQ+hqSMbOSJ +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +VmVrGQo2zRokW/ZuO9bN66q6FC3QEoB+YRZ1QHy5pxVGlfmACFQi/OJ+PP4I9cNoXXSZJmfFuEcJTQLq/ZCtU19yTz4vtrPnXVX762d9h+4= +VmVrGQo2zRokW/ZuO9bN61PJA7yxDL1KVTl01QCuQGtJW3vzQJOOgYhl5Y7o7eST +VmVrGQo2zRokW/ZuO9bN6w2ObTNAGN/v2fqKVfeJS6c= +VmVrGQo2zRokW/ZuO9bN6xf//wKDPUVWK2L+sTurgG8pByAOHY9FmNge5ivZzg0F +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6+aAtE6B7Ua6ua44h+Blx7sxVJQb5CW9peZjxWzhjxqj +mrVdYSdrlMUt/i3dk4wvYFDCIEEJMR2D5CYOFcP0t1Y= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvIvdA/BPxMMswj2KwAj0DzPteynFqBUhl8Kq1bDuRSgDa51fMeRQlv6aZPWLzFbUT8/e6KYdMeonvqmmXj1kYGh +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+D2021K6m4bs2o1hVfLElNe60aysgdIwheeBdFbreKFewz8Yrj6oafuN+wI69l1aFSUwWIV1h9WjnZOAYWfOxAU525+9Hn3fQP/7T13b2eVM1900+fb7XzdSr4D+nwHPVA= +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gsm54ihysnJ59T3r1w58GlkE0AWSJA+NwfowV84ygSuF8SxIMNMu4ntji5etztp4X0vBHWWwFWNCMhf5buob1Ps +AWid6G8ypXqQe9XxPPYEDxbTVrr/afH0tgxxR3vp12kZjXdV2hhayXt4Z3ANZRuYfhbRbVINB7LqQRnXe8IESyUoanNJmlA2D+4vzUjFZgYhphe6/FpCfcrSuNP8lVrg +1u+XjG/2+GSQRv6EzCaWRQ== +BHwLTkTRuTKcPwdIjAcfVkOik2Rc/K5Nexf+DT+LukSaHYw9Dj4sDCbc58/s1Fiq +Z8UsPk1Q7HtwjRd4g01ryw== +9JiWXiRNWDTg+XstQ0v3tPeqUlPmg6wBEj7lFSkjZd+alW6ktC8WQV0ss0QeBhrB +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNWBffLwUmKuHtFpMsDDA2O5v4M3zia2qUITwhdFgY4yggQokKnC4zVVqaK8NKI64dN4SBpt9ju2hqjDIAZj/ujfRUfaFZYSRCOULuYNpZhbqFOW9sFxf5P1jMQUbHGS/ymWMT0ei8bT5/Qdo6qRAv3KNQSGgGGvSmQltaJYyK6sLyvR5rNR+SnSLtwiKXLwqPo= +ThFx2Cr48x/QQ85XdFiDbJ0MpfhAacDjCUEeAv6461qpGtfgUo3h7kg7zIYRtLC8 ++m2Ig0EQEJ4Gc+2v8s5xrnZRBGAfDHuVg5r9VE/U7Dqcsp5mSPEG0DHHYcBZkfWo ++BDeh+Jl+ij831Dpk0PB+Z4HFq6+q1a4rNdeKuppydU= +LZmMG6wvL/RwTngCfmYObZFZwcfNS11r9ToFIpEHI5qSZA2RNbC5oYcATq4hxL+4 +gLN6phBd7ANIyGGdAIDQMbyCTw00UfS1R/3TnxM0GcHBqU9YRCImNzP8M7/QLNMx +2L0QJ15u7QVqb2rh2+Jj9OjvspJ74+mv9yVwvkZheBQ= +o6QhOIN2Sc4SHELnst17uQEjK8c+2Lhy2hqf8rBcSosctX4Bqp2L483M87OE9eXCkj/+ppL669Mf7q4I1AC6IZB6xcQgkLQzvCONBhXLvAADDiTIf4CA8j+v20CZ3Bia +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj0H4xphI80JaVwdOBqT4vpF3VmvR6RTq2YTGtk8EvQYIjitR3pAJjIGNX8AedGosB4= +32pdC9DD05OE2l0oXazDFEMJXq0ne9PGT66MpxxXKj3tninr4hwd2T2yAOK1JXtQKoyiP9cx8TICWduIpGMbbmX/j9MK2tkD+qwr7zwgCre7WU0Rtm3bCVTiRNVsJ6evOuWrKI+elR1nYHXO5o/br6SiMhiUUajUSMv4bE1I88M= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +sDtJSpHge52JmNZhrZMCGU8smhngYGnIx0FtIWFXIK9UOc0pvoHveaPk3U/EAgVK +Z8UsPk1Q7HtwjRd4g01ryw== +TxNYSU1H3DuE77lI6VVbczU2no0FFBSymt4xdgX/hdJWA2DmdWCYK6kxKEv2A+M1 +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANnKkcJSE/H3VghkioZ1tnjyunYYq8XfVeT+J18IgTe/zHEZGG5QWrYq0zwWxfhCVZWEWl8rO045J1o8hf2K/ZnB87MjLagLhDLLXO9zmkwzP +fdeYOeeuoWy6J7ezwxYzw4ZIY9pPIraCczrpQYJiR9mb7+DAhEV3ebytkVxaIQ23 +/OGQuMZnBzFhUjEyuxVI3ZnOu9toaGbEBmEpZpK4MC2T4SSUWnvQ4S1vyipoqwRb +ojcUvW+Z2ehEJ6yMJpmY+1qbBrZXJsPhXyLqkhY6MM2eeV6AyNZSnMoRBLFCNVnT +1WBG7bZf/s5gJ+qIaMFAbF6S8FDZoLzAhhqJ2MNTCVdX0rFK8L37rJXe6iLQ+VIT +ABbY04lsTxNhdVh21a9JsdTVstQlKOYc4GlR2ph+k3k= +lzSgbK+CTpY6T87LqdbU/clHi3bu6SNcJfqLXtam1ikWDppG2m46c0kV81wr46VR9gD170FUCMnwcOAwRf7brsv3F0ZWF8CxpJqJ31n3ShkANwE580geiRO02zBhyMPBr5dy1w0DLVL600RnWY38Ew== +7KFwZuSJAC7mbgtMUMJu2zeBbC3XtaoOE8JL/ymxSKo= +Dlyx6ZDFdkn72YIxkC3bo7GMaBnPIeNZdMzUVdRqGKdC+WYea6xFLhhU85UufGTUhNvOWD2JEFPBIUptqGgTCBTTbJ2EaowPispOLjbj6Pf5uWt+rvwnHL9C4/+D95rklyWC2NrA2USx2CODo7WzXA== +VmVrGQo2zRokW/ZuO9bN60EXpD8GKK2fiY1Xg0XFbMyYwhONhKS+p6paMXgT3qX0nX6YjEUDLqRgt9iLSNuHcQ== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmXbFWE3Z8RNiviG8V4hH16 +VmVrGQo2zRokW/ZuO9bN656pTK3mAcJtKx4qITAmjAubpEiO6CYsc0NUVzgKF6yg +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6/53VW+ZlBDVzVmTlHlsnW9qegPY0HL3zn+yEThRxHYemjjfRlraqfjjigIl5ztY0Q== +VmVrGQo2zRokW/ZuO9bN61w45ohnrfIHuVWBaR1GWNCnJgiclV34O09qlernj8Ngf6EPDG6ycXjoJqX/O/rUAnXGfm/Ym0q6fn3eJ8nDZ9Tkbz08C0hO8cdYYeNZR2RT +VmVrGQo2zRokW/ZuO9bN6x0Gk75J1/BaQz5SHiFE7LwusXV3DXYl/kmqwGfdbXxhyjblUNvdDWBZ7Y3+15CcnPKmWcZhqIO1IDh0Hx5VgLVb0OsMYQOPFoPFpckxPmp4IWB7WGAgQzl0dyS2Ed7jEw== +VmVrGQo2zRokW/ZuO9bN66gdKC4XWAPTc0u6UqkBUwXMTb+xl65yl85BJEDDPLGV42bLC/xBHO//T/VE02D0IA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hklp363Bu8rdFozhQMZEcyJuoa6y0cF6ek/6sCDR56ZgU0ov7DD10tY1dsdlxz2uV8EdRT998sybm5qC0dXNJab9 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkfFqOUpBuFDDedffQiweXDdQB51lYJTB6cDjIMCMAIMqWOvERgha3FSBeCQfNsH64= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknqHMtA0p81h7WgPWuZvjLxGWgU49/Fzn0oKjOapfQvQg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl/MJBWJAVmoJGxsDL68ycvUqeI+YX6IGWXpVF22fMLHYMJRaQNEb53dzwEXO7VDCA= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklMyQpx4RA3yWj2gHs3GC4vIyqjDRL3KM2k0B1E0sZxGg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkP1XBTxxQoteTxOSKZlOU1xCch6VJoD/wVOttkUfGtZZrzrOJ2CTHlETW6IXIYi0eYqYCOgLXPKQc1q+IprgTZAtukeGTMV7QE+GJ1mi7ItQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkna8O36iQ0f6+VQ9jUD+CypNUm59qefVh1Q5vRIL6VHzaitUehF2TwSMFU2620My6XgR2Hh+KLxbKXFnTvM9XTw9YIaoMyazG0SmZ8aHIP3xnKpMRfHm1HDvKI9k9aY780= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkc2m8aZr4thivH/Ab3oA/a +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmdt5dOdCvjZNhOajiM06zyI8F1z6cBCD/13fp5iaIUjwl9xxsQL1Hat8hK9yzEQhRSssNZvkdLVkAj/oVNnPPegZ3G4SG3u3mt+NnA0QdjeA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknqHmN/WlP3X2MHW/MQN0MSBCwwPsPQ4SSwOYFC6Zl0pBilRqFplQP1lFInZHBmYbY= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmnus2k6YYE0gU7vts7In+q4D69qS4CoEAQsjosGcUieZmtjpnG2kPzCSXy9SQyOK4= +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6+iELpcntY7rjeay17ceU1B98gClccl8gUonh8ZDKaHe1HtcfxRP9urvpol+KcbQCg== +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +mrVdYSdrlMUt/i3dk4wvYFDCIEEJMR2D5CYOFcP0t1Y= +L0eUthVnpkGsmKFAX6d+uE/CYsJNqOMjxonFB46aPvI2ZIJtXma82ILlINzNlhZe1ST9fmewhy9KXv5tOsXOUjPk1VN2suqdjbPMMI98LX65RJWDLHLQj5mVoBttiC7YkZt5W1l5TdVyq3TDsza2yA8w1NwsbJ+Ot9VMl0ovN/yp15X3s2uB27IPzOdgEXSaveH5lr0wrLVJXdtbVKBYbzvqsf9xKXQ9rw3D7ofi22PVkI/9qREszUVghc9nlpQOA6mTwq9ydiK9PjudV0xISCbRjxb+BgOwIM5ZLe8E1OZZvC4KeHShrMgqENlPBMWLLN4EFcMzjWmf+pEefYkveA== +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DV0qk9bj7sgF/XbcR6HNF0PiIwxkkZrBn4EvPXRpixrjusCFcxWk4YLbs9fkPbJCfWm39gvlyvUtZawoxhllMj9Xarua5W2M4YPFqxLtjnIeqaxLEE3DmkcMomNhXopwVO97i1xtDtCOGOcGBfFFrf +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gsTsx7xA9KhGk+vbrG1DfC1PhrBVd7Gpna1pDUNfyFbTurtAWA/h1CW6lcZlBLfBYaVVq+1swaK7TIue8EHKDcf3mUiQwLL1G2q6tpxj/J/y+bv0oGFnFKjesSbmlm7kcQBkeytFhNsup8HGn15QhEVsre2FZALBJjQS+tiMIyTHtOdlA3VrkKkskPv51l5F5IgRBAZfaEgYTrGHM2OSW6e +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +j+SwqpR+F2lYRZXNJryLSfytyH8xp6DD2Ek1E//Dlq4= +Z8UsPk1Q7HtwjRd4g01ryw== +FJoH8ANT+vaNanqqjLNUASlBlTfMmvJKMlUIANe/xUS2YR7YnTt49y+tUZRjmDTy +vyYQukiyWticSXOwUY8Mi9HOMnnB1n92sYcB0OnLxm1bO4npQEN0XyKOb+Ol/RKB +Z8UsPk1Q7HtwjRd4g01ryw== +td0Hee4NJPblA28GiIcOmc/Ym6Bu3ogIAYm5c/oogwk0XBSzLRar0Ih7afhnqigD +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/j9YDYTBW7wzXOYwIgWpVKbM/qZEO9IUBt+yeznHTI43/fXnwevohkxSPhjjCK6wHqaB//FH6qUR6SJKMWA35iXTIp4jTxg7HPrg+nGOAj04yFsP5MvH3XSBm7FLwf7MvS8ROXzpuIA+qX0XLVxcKrNAUf0xbbW+xr8o6lS0wIjc +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRrzaJ3fStGEkJ1TrpFS03bbdUIiVBbfc+ifRnD8hoj5i/vGHJwbslvMaI07I6UZqlSe4f3Af9BMCsD66aZlfXP6z0uy27fEfDlLHFmfF/xMg +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXoz0tnByKjXrpn0PixC53xxW424JtFQiPFy3Sr4iAaZyHbxdg7T+nBoNbXsIKW0qy5z7tFq9bNw3QQLZMfinM5lOIiROL/n6xbmdNH/l+s9yhWf3NmfcHw1Rs7YD44yQUlCuWIay2lcvI1qON9rks/ +wgR07xfoapmx6eEnFHXXYsXheOqb+ac7ivrKomECGJjgpziToag3V5fNXXvZF2VqJzdP2yN9TApu5LJMNhW/Bphlhm7/zg8uo3tW5D2Oe/deZXzoOQx/WMoQDK1USmSn +WHkOzVx7seuLmxs5Hu+l/vewF5INruHu21pbeP57J1zziU9fiig3+az234q1W7m2 +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +UbJuac0dxLiN+5ocS3w2vY6ev/aUUjOLImcKs1TVtH7uEx9dvbj/NPEHyUCaV7obCsYW06pB01JKt2QDWmY/u+Vi8KfUkoDI96zKFszF2vc= +VmVrGQo2zRokW/ZuO9bN63QrZyqUb5BKoT4N9cyeKvwVDGzKQdsnMEcG985fFOqcgHsu+uLtgt7Vq8Wk3JHDXDpm1QlW8uY/EhfiY2ZD55vGyoD6HUoMDMPNAuGx2EeN +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6ywsF/deIZTFh2C8FEj6CP6YghSMMH++16olpAkUr8aH61SNeNj92Dha8MaRKdYrjw== +xWoGNWjKGPfI4gq8aHoTfOuj52m++iET96O/j9y6xVlGm4+LbAACgyp9oG8Hv7OC2+YhWhhRxM7XkLUnZ8CjRA== +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +gZ7BoIAABc+I8g/rYzq+fxNPIG9Bz2s8T7kg5H+enXflQyLGwsFGAoEO/fc2Ik1Dmi+JF5ZO1umLWtK9kK01SA== +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VX19S4Vbf3FE8SyCSP2reHhg7sd0ER/UUOD9fjemeCn7d8eagdZzgkRi3pUJOoynXbu6ZEhByY0iftuMMH2Ze8I +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +eWkbU128GVQ8HNvAeSSgRCkOh+s4OU/QYOa8I/Ismus= +Z8UsPk1Q7HtwjRd4g01ryw== +pWDI3suimaW/fzzd89s5Xbvno7XAD4tQJ3S6aVXwVoYuocfGBi0UxPXNL7vswEjX +vyYQukiyWticSXOwUY8Mi9HOMnnB1n92sYcB0OnLxm1bO4npQEN0XyKOb+Ol/RKB +Z8UsPk1Q7HtwjRd4g01ryw== +td0Hee4NJPblA28GiIcOmVz/iEnSh7ccFLM88eucCwhDxWpOIeDaytxmPiP2pwkpNyGbS3x73U5/Z+SQSDEikA== +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +vdBxA0rg+ZMSoZiCzvqi/gqIiVZTQcFAiLmPoNai8xutYA0dS83vWxSWdRLODeDp4q4QeK7kwtUtEti2vAkZRSQA4dw/W0RTHFPO851VdCTY0ktN4EQtfhMkQ+a30AGvrl1Vaagw/r25jo3ENPWu+eXus/Jo0iW8we/7jbdwBQjB9zp3h7LesrbSjhjlsmwanUy6Xyo7srjQHGgZvJG7pQ== +898mfgNS2d01I2C0OohgeqJkYuBJuZv3XRwykfNYg/I= +oS2TjyjVloijdu/67u+fRjo8xBq+dALUaqMR32a7+KRtMnzAWvGG9TrwG/jaGJP4P7qHeX27kwpQafD9eT9biE/pqVeLldo5PWIL2at0SiCLXdDB0neLpLXz6VzBcXwznkGLqkUWTDloI16oOqAXLA== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKMlEIqjWm8+kOtLiMZRGDzJmH9PGjMR4p8yi4oAXfNcgEl2vZxxgv8NVYMmLnl+v42UgdEFHJPZcltMDkGeY+xBlp0cPQhJMkm+IxLo4M+2i+Am1Vz6IcVdBmY4Os14rFxvOJaPGWGxV8MBT6BzDyjHm/l0LUbD6rUFkipEhoTA== +WHkOzVx7seuLmxs5Hu+l/vewF5INruHu21pbeP57J1zziU9fiig3+az234q1W7m2 +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +UbJuac0dxLiN+5ocS3w2vY6ev/aUUjOLImcKs1TVtH5T76Zz5yb1C9U5rakgdm9RW5nHdQvRPYO143hdvo+HIA== +VmVrGQo2zRokW/ZuO9bN63QrZyqUb5BKoT4N9cyeKvzykZRK7pHPkeDFgAlLwcFk4kSum59DnV9kJdKHpC/A2ISh5PNPzp/qpyaJdATbMlTIoYou1YK2/ZSLGwPFlAXWkM6aQwZSO0FxOI6JoW9q+Q== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6ywsF/deIZTFh2C8FEj6CP7DuKwnsBG8hBHPbBdcBfSCHxeDBAbFdt+XiARKA1MomU6qtvykBPVi6KHdqBDCPxE= +xWoGNWjKGPfI4gq8aHoTfOuj52m++iET96O/j9y6xVlGm4+LbAACgyp9oG8Hv7OC2+YhWhhRxM7XkLUnZ8CjRA== +NZ8anjNE8y4KXkXCdAa3W8kH6DmFwpSFX5BZjT1uwtrxI0WPyIknAqss64fcQgDb +gZ7BoIAABc+I8g/rYzq+fxRpaih0LRniUlkMHsVtxMEnYoFJPfaZbR5kMXorUXCehBDH0psjaJSqMceG+oUtwOBbbRcs3Z7Su59BVe/h8ic= +VmVrGQo2zRokW/ZuO9bN67ce+2hN0tsNBlpK2BZSXqGyyDPCcCSOlRV5etylc9M5 +VmVrGQo2zRokW/ZuO9bN61Ms/KjQeK69A0jSbIx+0VX5fR/aAYlzEz6OTgMCNWMwdkboe2dfTAhdyyWmB2y67cYtcOlXL8H85aKqsoB2DoTGQLb8N1LojshwROENSJaoEBPipFiXAHubq9mik6A0Pw== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +0U7VJZXCdgOpQNU62wPJQE/UePejZ9QzjtvZYbqon2oN3mK8me36GGGZ2OPkTBxp +Z8UsPk1Q7HtwjRd4g01ryw== +DzneBA92dU5V88ETMYFQ58NxLyhmUEIVlWZDWxrt0Mn/UQsW65SPLeHjo6VlpkgVOX+07UHpql5UeKozmcS33Q== +Z8UsPk1Q7HtwjRd4g01ryw== +/0ULpLqgTvInFD0r5hHANlHeJWQw9fSF1K6yNPR7TNXKMlEIqjWm8+kOtLiMZRGDexhZBoIAf0WAPIsI2Bi69+daBHJk0QBFqm9azTOEwXyl/TjY+SrKdfHV4yX7UqnL+bHOT+A2D8kciW14Jx2V75S7+9oeo2B0w2fsEhD7pcWuZSYtzH87yNjLnwXZWshYz3lL++0DW4SqFI6j5RIwA2h6+bUUPK3Wl9iZ5cwx72Q= +UGqQTbo+FQ89dDBUZtFrgwmFMzzlmsFtvZg4+pH6Qs79Gq+C4Ck/Usx9f8JkpPA2 +WHkOzVx7seuLmxs5Hu+l/qqTd4Qb9qnLQgtLGl5Gi+JbwOQ4t8GjAIXgCmAhab1k +k1ktu/l6HFULD7Vyr8Cv0AK1TDWGg/XdXjBE31fdyPB2B5OmiMxgO7bZeua7BoWU +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN68GhBqRTSx7b66q1h0SPjamiShttYZYtB/bL+x1+9gBL6a/8TtD3hX3wysZxq0l0VDucqm9mgSrBadqMkbFS5y0= +VmVrGQo2zRokW/ZuO9bN6510v1jY/Sjqk6PHjy+iz+IM5pMt0wEdPwZx+4gi9VAt +VmVrGQo2zRokW/ZuO9bN6yVOCMsCJ4iLcnswVEG9lew= +VmVrGQo2zRokW/ZuO9bN6936UGmgfBI5SpeqACm5kwysJAm/xNZ7cKcs6XkJehlaiV6VnhN/O98kVrhggzYeM4knX4jr6QJIksrEqAaEzXE= +VmVrGQo2zRokW/ZuO9bN6+6OSQjJyDHPNRB3fSq3EOchq4P2ddcxWXhO1zetOnKmyIOdqGQsUFDo6VZggORDnA== +VmVrGQo2zRokW/ZuO9bN6ylkoE1zhjzQNj2TzKNcV51RpDvRchETaM43HO/RmjbjRZuV9JQk5rUXcO1NwMJ/nXK+hbrNfPDcYhgprqsmpJGW0Vbu3wJ5obGDwzEggmv8HvHB80kIfzBSaJ671gcVAA== +VmVrGQo2zRokW/ZuO9bN68hgSH6aExFBRtC3BQoB9fGNY/KuuyVJ8rT89YRZBLSE +VmVrGQo2zRokW/ZuO9bN696q5lrt6GPZtHqhTnRu4kfipTGQ62kylytalQ/LzQMAXLd1F7jC0FKKozy1USi+UHzqBRctxqLBWMTHN3AYBvCDMmil2VRD0DGA6U9C/VZRzgC0mZHM8LzZSb0MCGnWHSzc166EOAF0sFLBk9SxELv4KyBcqOdZ+TF9e/YGdYNgRD9EQypEmsGh7hCgnzWXdzLcMul9JSmpLmPz2lAOT9g= +VmVrGQo2zRokW/ZuO9bN68hgSH6aExFBRtC3BQoB9fHf+4oDrdgnlf7iqtuRvlzDqBGJEACuxZTqr7OlaB4j2z5xgdoCjEbtO3uP9fbhkhp+pxzUj3h6Askvq1HF23eVt2uhxVReLZq0b/FUl76NxuGMeVJCEsnyKrRBwDGQqruB8nxdjlPJuw0iW1MFRhGGNG7XB0GhT720aJEPKMBwrQ== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN66Px6Idtf+WhzwY1S1dxdnQ= +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +Nhb1Brnfw2g37hqPO7sHSRwdew3jTXEfOCUuPk1m52o= +Z8UsPk1Q7HtwjRd4g01ryw== +TxNYSU1H3DuE77lI6VVbc5W9rK3Rhspm2qS129+sVQzX88IbkgKkv+nhn3s+6WweAcBpFTFLR1nYXwRqzCFAOw== +Z8UsPk1Q7HtwjRd4g01ryw== +UGqQTbo+FQ89dDBUZtFrg2BLWJrWwmrMQcEReM1++ABiGJdvEdJy4p2e72NjUhX6 +jQ3ffgdk5F3qci4zFBKoIcXBKAyBSxkzXHyAK98DGV+aih2DPKuqnX9CsRZR0w3W +MYV814tIGmmxARDMkm8xwje3+pWdIcAMtkpxX1J7i4iRTzdcgMEmmoGsIJGDeOu6FV/JBjJ8wBzBgw5aOXgVvA== +tv/17GGxFXcthQ3Mn9JkloLRwsg6BziAmBUzA5lKZPilZkThOpdX7kCVsjuEEe0C +BFN6J8z7zeK+NmgEVVl/O5WC+l21AQTyKJG7WJUGT4k= +Xntf5tKQoxjFTkKgcujQGT4lt8J/gJMuatMbMqnL6Wj4txU7aSJrOs92uZUakKQrktGDQUuHhVDx043KfLncaA== +JhRCGz84Brc1np71ddZ7rk1JYormFFESzmIiY1mrggzRpIGqz2ZSvMC+XRbvcpF4 +1V2v8QerKOmubvSxgB4eTPPa7eui7zDRItA19Ag3PR0= +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN65rD4nI0MfwzlsBII0cdPaYdlqqTXeiar/5F20QT5k8d6zPM46scMv8VX2QdB/T36tWZNkSDIucLfsd2OjaWjXA/k5p9yZTjMJaZfyIOeArHL47/iXF3gwhcjm5Rlkzrtw== +VmVrGQo2zRokW/ZuO9bN68hgSH6aExFBRtC3BQoB9fGNY/KuuyVJ8rT89YRZBLSE +VmVrGQo2zRokW/ZuO9bN696q5lrt6GPZtHqhTnRu4kfipTGQ62kylytalQ/LzQMAXLd1F7jC0FKKozy1USi+UHzqBRctxqLBWMTHN3AYBvCDMmil2VRD0DGA6U9C/VZRzgC0mZHM8LzZSb0MCGnWHSzc166EOAF0sFLBk9SxELv4KyBcqOdZ+TF9e/YGdYNgRD9EQypEmsGh7hCgnzWXdzLcMul9JSmpLmPz2lAOT9g= +VmVrGQo2zRokW/ZuO9bN68hgSH6aExFBRtC3BQoB9fHf+4oDrdgnlf7iqtuRvlzDqBGJEACuxZTqr7OlaB4j2z5xgdoCjEbtO3uP9fbhkhp+pxzUj3h6Askvq1HF23eVt2uhxVReLZq0b/FUl76NxuGMeVJCEsnyKrRBwDGQqruL4teiu1UyHCzigYMQ7Wxh +VmVrGQo2zRokW/ZuO9bN6/xDysL+wsJSb13AswERz4dbP32z4nnYwOYj0CDjnhOh +VmVrGQo2zRokW/ZuO9bN6147x1dkhbDeqs2uAuxfhFmqV1MUQe7ntuqK6FUrhRdWrhb6PFzNeMbW752CHp/xOcV4lOx/+l7F8C+hsS/7fGJa4xAb+A4A56JkrCbngLOIkjPfh6O86JCi1rPBFnmHVDEhWfPEMFUWdqib4+0YMKx3uV35+1cRpK5JKIXpL0HGwCZ4eBIzv1fSWDVgotUWepFA6NwKW0m+RKNVHjVf4B/Z6ThMx9gWCeYkK3qOPCvw +AWid6G8ypXqQe9XxPPYEDxbTVrr/afH0tgxxR3vp12kZjXdV2hhayXt4Z3ANZRuYyQUpOWBMl0q+ODdH4cmNOX5wCNLlqGkH68vGj3DMWZh7ID3mLi0fFs/xEt/mD6eM +1u+XjG/2+GSQRv6EzCaWRQ== +mT9/9GDeyDquZiN6X8gEAWVfQh1dKbyQd9AnYmwsV+qKs48j1ia2S84MCdV0XhVm +Z8UsPk1Q7HtwjRd4g01ryw== +btFyzMGgdk+J9spCzbDmLrPmdjo2LkuGg5/zyBJtrrkS96BNBVO4uQFj93pZtz62 +vyYQukiyWticSXOwUY8Mixyz/Y61a4ulbPhcSy36UUHNE5uCHFyd8WxpyS6PjoRT +Z8UsPk1Q7HtwjRd4g01ryw== +fdeYOeeuoWy6J7ezwxYzw4ZIY9pPIraCczrpQYJiR9mb7+DAhEV3ebytkVxaIQ23 +ojcUvW+Z2ehEJ6yMJpmY+1qbBrZXJsPhXyLqkhY6MM2eeV6AyNZSnMoRBLFCNVnT +1WBG7bZf/s5gJ+qIaMFAbF6S8FDZoLzAhhqJ2MNTCVdX0rFK8L37rJXe6iLQ+VIT +BCHJZOAnQzSQuLH4cAWqAD40vaEYChOYgPF83uywbL8= +7KFwZuSJAC7mbgtMUMJu2zeBbC3XtaoOE8JL/ymxSKo= +Dlyx6ZDFdkn72YIxkC3bo7GMaBnPIeNZdMzUVdRqGKdC+WYea6xFLhhU85UufGTUhNvOWD2JEFPBIUptqGgTCBTTbJ2EaowPispOLjbj6Pf5uWt+rvwnHL9C4/+D95rklyWC2NrA2USx2CODo7WzXA== +VmVrGQo2zRokW/ZuO9bN60EXpD8GKK2fiY1Xg0XFbMyYwhONhKS+p6paMXgT3qX0nX6YjEUDLqRgt9iLSNuHcQ== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJmXbFWE3Z8RNiviG8V4hH16 +VmVrGQo2zRokW/ZuO9bN656pTK3mAcJtKx4qITAmjAubpEiO6CYsc0NUVzgKF6yg +VmVrGQo2zRokW/ZuO9bN62HWtxZ2I/oCwtyWP0WSTIg= +VmVrGQo2zRokW/ZuO9bN6/53VW+ZlBDVzVmTlHlsnW9qegPY0HL3zn+yEThRxHYemjjfRlraqfjjigIl5ztY0Q== +VmVrGQo2zRokW/ZuO9bN6wJWVgTmJ2EgLSyZyUD8GJvuHeaf0spXYmylV4qXy9bv +VmVrGQo2zRokW/ZuO9bN61w45ohnrfIHuVWBaR1GWNAm4hIL0YdCOkxxghBXsrDNVZLtGLIXEmOsSIG7qSONcIKf9yowJaUr7bX1jpVx/9dYB5vu5CZT7elU293Se6+e +VmVrGQo2zRokW/ZuO9bN65Ce/QOFjiSIqWrjA3Er0R7cq13Ywzo6BVLzfKd5i4T9KWnqpI+rdk5rD52UxDTMe5ESZtIhT80HF0t9dvklhFYRivipKbc4izxkqOF7nD1aq86RpWIgdXHLInyT/G9TdKHEmByuJozqgV7i0liSu2E= +VmVrGQo2zRokW/ZuO9bN67K98zu3r3M0RkF92kvh12XnD9tV/+RgwuvbKkE4b5FdDl218u7Gd5XpjU37yihT/++A/5ESJcYgfipI+1y2djg= +VmVrGQo2zRokW/ZuO9bN67T1YHfouoH37HxIGfwtIV53hsmDtjo2IqQwFmkRMIWW7EyF0LvAQ0ClcioTFbLI0Q== +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbfWbaFV6asNTxE8gO4sa1C2 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmU2PP8T4rE0Ki2DpDYGQQTMBQPeP+VDqiMKBG8WE9YhabFJOyPwvzPtBeNAqp+F6w3uhUvQ27nyMB9ItJOAwHO4cp38z/RclIqa42VDd+bqQTkuxT/++1Z+7ibbkn/8IFPAPtKXXiaaBLLv1Vt4Sk8CbDkTCDEWKsNtB93KN1PLyZOF/f3opKHyi9/Bvrf8gLoVOW5yEYJLLYRI4BMc1viPO2lPYBlyCPWmCyItaqP3w== +VmVrGQo2zRokW/ZuO9bN6zTwEIRXNDisRd1QRhA8x1E= +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +898mfgNS2d01I2C0OohgeruAe/UCLgGD6NNdg7XFY+DJq9FsIZwgRA0seTz5ZLwfjooyZ9lmW+fh8LaNxhSyH3yppJ68a4s3H54tsSOpWoZ2m6PWB40Ky92OraEDselU2Qk3ji/D/iY4v+bIOMW+2A7SpeMS4h3h8qlYXo0JWbtgm9ezxrehbVsJ0liFX1gh +898mfgNS2d01I2C0OohgemSehcduuEOb/A/jSuv27gvK+dzc/IUFREK2vrqEjL3IcUXlek89393nCblo8Isrsx0C7qa9fJ7b2Xxsl2RtDAJplaSUPejh9zSGeZWWQWbiHBhEOP8J9idbfwP5l9UbdsbJ6a7CtrHEFTvVZUENLl9FoGcin9oi7/ZxDTQWSN4mf8qodCt5Lng/4uh8oghzsZ2rWteQPiz0XJDtWCCEmaw= +AWid6G8ypXqQe9XxPPYEDxbTVrr/afH0tgxxR3vp12kZjXdV2hhayXt4Z3ANZRuYgbX4QKMnToJ3T90Hv966oI9IH50UIaYKlBaOTM+olPbmcW/SjwYVBBC4/OQfpx1W +1u+XjG/2+GSQRv6EzCaWRQ== +nXT3H2N0DWm/wen4hKeMsWmii+E32LKTZLTl/eBH0rc= +QdTjTxbShl+8mrARISGoAe20TT841Yg39a2OpGi6Sb8= +KZTmaJLp+FU9X93j5Tpqtw== +6cOGAVw2kriw5n/nELD3EzDieqNF+nFwRUrV3liflgFtretISs87Q7zY7vAjAZsi +Ys4bozvJBgtB7q16qcvtB02lyBPPkFWxHdwFABPScSc= +KZTmaJLp+FU9X93j5Tpqtw== +cUWcz3lcQ6qo3eAgjfy6hcBWsrIOiJOuPw9tmudt9no= +WHkOzVx7seuLmxs5Hu+l/jlzY3WAQ5RZScd8PnXAehWqEELjLynZVT3LobYIZ/w/30Q6418opECWCKddX40xNg== +o6QhOIN2Sc4SHELnst17uWIkkNEHVSV9/Mf3IwuQT5oWHGgnzfKSw5gD7uL0PrYiWJWeHrDWof1TpozUKVXCXw== +PWKGbNEPxxxpGpe8THO249G7n1OzHa4qu7TqsAFL1ussgdlPfAKMdupfQKMuwnR8s7m3gXBwkyoEl6H2pcQF4Q== +VmVrGQo2zRokW/ZuO9bN69CLEnLN/3TzL7iVitOx0qOanWOL3ccZQGpL5b45AlgM +F9Sftf1PoN5P+lgvc+r10Cd2oRkiWUAJQpp2kvYSUOKRyoMCdwcp9b65maauRWu7QcAuEovJwCqZKKX6ehkIZxvzRsa+MKUYj2szuzk8Kz90R7acxowMdh+wDztnyA5L +VmVrGQo2zRokW/ZuO9bN69CLEnLN/3TzL7iVitOx0qOnBkFJIyNLmtrTlZAVUyNY +F9Sftf1PoN5P+lgvc+r10GMXgUldLBRR33Dadi8u6OA8bUTVO2N7S+6ANZzHThORqV1/TdlcekIu+FMlbuYcsf6saBggftYMn1qrCHI00ss= +VmVrGQo2zRokW/ZuO9bN65Z/+3Ip5etct37v7j19XWoJfRDvWc0/MW3gn6OwUnX+ +F9Sftf1PoN5P+lgvc+r10GMXgUldLBRR33Dadi8u6OA8bUTVO2N7S+6ANZzHThORQOa5dwCApnFuLSiohDrK1/EpVeb/GgZDYWIB0Uicqyu2cjFmNxQa8wpUMgPEMjlC +VmVrGQo2zRokW/ZuO9bN65Z/+3Ip5etct37v7j19XWpv0JGsd66pHsu8hfkT/w13 +SAfJlmeeNJ+zov8ENFGT09M8rmWLHAFAE9hX9AZnIaeRvIdH4lb4Dv/EphznOIG0p+BtyTEt52gBeN0XDtnnYA== +PWKGbNEPxxxpGpe8THO249UOX20uJsE/HUiON5To0V2M5OkhyqQZnLyfsaPnuR+ElxVkbZWLHeByQrSysFw3qHnC7iVp9kxao5KnEJm2Cbo= +VmVrGQo2zRokW/ZuO9bN6739lKjFbXcMNJNlnotcdtZVgaenRLW4xOhLXGNCESYw +F9Sftf1PoN5P+lgvc+r10KLYM0ScSe6shiqZtH8DkCVUWXTUNLs5bvA0vNjn5jSZF+EsetsUvn8ldRytIuidaaNtYIo2VbuEkFWUiW4qe5o= +VmVrGQo2zRokW/ZuO9bN6739lKjFbXcMNJNlnotcdtY75pP4x2cTIQwbiiOwVG8e +F9Sftf1PoN5P+lgvc+r10MDkY9Zzcv6r6mBg6yHiWa0jq7Lr02xbnTUCpb4C3i3GRv5DwdGIkGtj9V9QfyK3JIbq4WDzOvoRNvgRKJuxyZc= +VmVrGQo2zRokW/ZuO9bN6739lKjFbXcMNJNlnotcdtYFW2yb3/MDorhH/+SF0I6I +SAfJlmeeNJ+zov8ENFGT09M8rmWLHAFAE9hX9AZnIaexDpx/z5hFlldfLGzHTsJ+MjIJjhGKMzeGqtLLfveLAA== +o6QhOIN2Sc4SHELnst17uWIkkNEHVSV9/Mf3IwuQT5q8c7RLrXh5V+xjdjr/pAz+JiONfDRu5Ynrv/K9N+ikig== +PWKGbNEPxxxpGpe8THO246Lf+8uDPmcVXUEAqHlmGZAV60/XYRPLoj1gpM92g2f7 +VmVrGQo2zRokW/ZuO9bN6/eIyTQVswpdOu9Nq1sM3lQBu05pzXl0GtKAgGyGfqsD +F9Sftf1PoN5P+lgvc+r10JJRjwlrykEl+0UkBy+8s+wl6iJLRTrEjruWhbanwxCI +VmVrGQo2zRokW/ZuO9bN6/eIyTQVswpdOu9Nq1sM3lQkxBfOuMPt3otkFc4Q500J +F9Sftf1PoN5P+lgvc+r10P5rsxboKPJdW+WQ92oAYaPBjhzguqiGDlmBhbYnpPXA +VmVrGQo2zRokW/ZuO9bN6/eIyTQVswpdOu9Nq1sM3lQZSfEQkoKmCvlWl3+15Yox +CAhn8dRUItEbEErp4w+lXyV0loCDlCywENzaYBwlkWk= +1u+XjG/2+GSQRv6EzCaWRQ== +0Ve/zXHSGNiOUBo4yQnSN8h4af+HeQDngC0H65SCobeLf7kMLGavxVwShh5imclm +KZTmaJLp+FU9X93j5Tpqtw== +mi1BvZebKjLnvrPqK6jwja3E3QfTJvJWnVYsxIEAKzI= +WLkT6KP2hJRlDfsZ6zo34K8RZH9Q1hb1hNi9wmThLMB12O7KPk+5j0/GRJoss+VE +Ys4bozvJBgtB7q16qcvtB6gj7zK8RzQFnHpNWJR4p4UaRcqxVOHLSW7Jj6fuzno7AdapSE7H3a1qkP/O08Wpbgc1HMk/XRoJspdxk6iDbug= +KZTmaJLp+FU9X93j5Tpqtw== +P9I0Yo5Y0r5MzXHfQxIQ4VRw6cg3Tvyquq6BFuWWzahipyJoyglt4PwdBHXadxz1FJ32cb4J2MTOw4Ifoq2QXw== +fg51De/+rw3N9wbc1MuySUIHo3PVXjOsQFdU3u96SKLKMqHC1U9t4ZcQP/kzZvTdsIpmeX9rZrgXvyotzgfBKnScZaI4Te8WBFbkZHaz1bNRc7JqV7RfIL5HFJ1TU8HY05shYcGZaaZ4g/6fFmdknA== +CgqYRLrQUkd1LIPh8RCRgASyeniOqnpA8Um4ooJeuPfl6TOwvTdPe7mY7KreIB+P +/0ULpLqgTvInFD0r5hHANsVMQyYMTxOCtjrehT+unwA= +j6FlrHlwfUXyl86hihVEkw///qeVs7YU/Opojpb+AmM= +4NFRQhGbYiwbXMyUtItQUY+Gh83Woc67fsFC1u/FOwaKoBHeKz4nEVgnTpMsVFNs +PV+X4fvCTN/6758fJ9HMvyJNBHisoA22qKMtx3QNPL+ICERBAD22cVhzRCEq6qsHMy1foH0BG3pz06U/2cWylm0RzH3tg1ZoI9WotVFooiu5a2T8MVwdO1z5AMemNHq1 +PV+X4fvCTN/6758fJ9HMvyJNBHisoA22qKMtx3QNPL/LD40K0qPB9Bm1U2/qklY5gUG8NCl5v3rrKcRvqW/0ZMmOAowv3rGD+1TnJMcobi923YBqBmPj9+f+fZpv2sgWsBex+D8k4prheh4K9rUh1Q== +o6QhOIN2Sc4SHELnst17uYPG2bSrBTq+8xVqwH4sIfiS9YmVUjMQLk6dgCU2pCGWV7cf/q0OTAr//rcGY7mah6es65Pbro3DCx0E/Ug7nL4= +rdDP4+hyPVJxG5CN3xYWHf113zx3tYHA6xKLMCTBJj2Rx4jTkLfp9CuMTi8GUSbSHYn2wabDmvJPq9k+FZvmrJGwBbQ95lt/e9PDFbOjo38= +2XzP+t8SYt+LAJpCSTOHHRM4YGWtBn9wyjrl0Z7w9n6H7vIvHcmV3axtCpvSaWdf +o6QhOIN2Sc4SHELnst17uYPG2bSrBTq+8xVqwH4sIfiS9YmVUjMQLk6dgCU2pCGWV7cf/q0OTAr//rcGY7mah6es65Pbro3DCx0E/Ug7nL4= +NU9SH7jhJIwKb5zjqWXqk65fNeGhk77lzYivS19ZJBF6ITWFKYey4oiKohXFwI+p +PV+X4fvCTN/6758fJ9HMvyJNBHisoA22qKMtx3QNPL/iIJraL6kdY5lY6KL7/ip6Hwc1nBwX70nUzCehNIv8ZBAFuC7y06txh0/BrVlycXwh5TzFT5xImNR/uqQcv9BV +PV+X4fvCTN/6758fJ9HMvyJNBHisoA22qKMtx3QNPL/BDOVGwZZxv9gP6y6NzEIAaULdSERVvUeGpt40DMTAJazDZ2pV+w93CRpMNi4QLCPQQykW9deLj4tPkezHrIzmv5s5VmsVo6AY9pqgH5C9hQ== +o6QhOIN2Sc4SHELnst17uYPG2bSrBTq+8xVqwH4sIfgR8iv3tcx+sjdMESJ1elfUCfSSeNXkxnnNboKgKtyl3oTlPkGr4KqB1+/RZZu0Td8= +BqvuIz6bL1BUHqmqyqHsX1XrX2F1wazlPq7N3wCpRPTpG4/rkBe7vHqfjJ7P2T4M +PV+X4fvCTN/6758fJ9HMvyJNBHisoA22qKMtx3QNPL/BDOVGwZZxv9gP6y6NzEIAaULdSERVvUeGpt40DMTAJazDZ2pV+w93CRpMNi4QLCPQQykW9deLj4tPkezHrIzmv5s5VmsVo6AY9pqgH5C9hQ== +o6QhOIN2Sc4SHELnst17uYPG2bSrBTq+8xVqwH4sIfgR8iv3tcx+sjdMESJ1elfUCfSSeNXkxnnNboKgKtyl3oTlPkGr4KqB1+/RZZu0Td8= +0zuirfbdySeBs+yooVipiiz0Cxx+yREQNDtO9afFJ0E0auuJuItw++uRZsyCs/0mwsD9m0mtlp/PKEQbrYj9pw== +gabdfZBybRrEth0k+KwuxZyyEqlDV6bZCuwzFLSo2WORLdRQsU80BfqPYfKlTg/W +UbJuac0dxLiN+5ocS3w2vb1KFn10TLldzslPwtaSogaw6uXdYc4F3/TAd3bUxElmTpKU0xCUDJnKuyWkxkudk/W7JqKwNXL6VxCha5g8CSM= +VmVrGQo2zRokW/ZuO9bN6xpbx1qLI3EtiMid/m2TtjY= +kTR15emObMQ9ZEgK/gbh5d35BqzmDFEwThoStH8fe0R8w+jBvBjLky3/oggxoKM9 +UbJuac0dxLiN+5ocS3w2vQi6x7BNbtUV3Q64NadvISTMKztUb0hq+3ThgAzVoB7pugZ8qxnxmGvVa1DL8F9QXw== +VmVrGQo2zRokW/ZuO9bN6wF6MF6VaPt6TgWwEopVPSc= +YL8fmwlqGqA5UEMeQuiL3j/sOMhe5GgzqH42CajYxfY= +1u+XjG/2+GSQRv6EzCaWRQ== +mXfGGds+NfPoeaDAMT3F3De3/vlx6XXvO4H7C/y//YrT01i3Gq0AAfY8U3eN59ss +KZTmaJLp+FU9X93j5Tpqtw== +sI0jOmEN85Ro1te7GgeteXy94cioshWIePmeDD6Uxd4= +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +WuRvIBaPNRsVGEMoM4NSTuFzX4eqJNnk/iFRNUV/bCc= +KZTmaJLp+FU9X93j5Tpqtw== +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9gF5hYkCkXc4JDP5o6wj7zgNnj43zzTJobXabJOujNI/3d5gYLfgopXsQ9TvgmajM5w7X1OF3hSKAH2q3itrMyifNr4tcqv5PdLsKtyJ/LZZg== +GedaiPN5dqYTkpDAFlSse/fC+o3CaXrtPxdRW8m742Kdn3Cepr1sPUG1iwk6gWB3KfxJJylEJ31Ej++TN1MLwNhNXqxXqu31jPQhtmiOeX0= +oacxXEoPih9NKsd5qduwcF9ATIokCvDNdMWbbQ+1okLBLD+YzR0umulBCe4LKyBV +NhnIR3Ilo4H2su9/cTNo/JKKP+tBN/b/Js0u4fdYxT9Ou4UdOoVlTebwsMHqtpY8 +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN63t2MnnwkYHotYsrWH9vRICyWhn4bg+fudldZUhNSnla +VmVrGQo2zRokW/ZuO9bN698zdDf3TANViroaMFHNADkZuAZgDosHI43LqmWsgNONNNe48/1mFtI+asjmDrtKdA== +M0ZySqkmhuHCw6olbCKv95w0wnb5tXdVpvk+nm5slh1G8xJLU1xuHApKfUAMvlbG +VmVrGQo2zRokW/ZuO9bN69x5pV7nqDSWcTIi/Vxvw0q6MyrM+QjxLfKuXvnjwTTx +VmVrGQo2zRokW/ZuO9bN62pzVXz4KUejuXAqVdbEaEA= +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmUEMlB6WmrP7+t/B8UnJ5dehldGx1WpDO2ExqKyCtyLHA== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz4jzvAaM97jaS2W0DRLyZ0k +1u+XjG/2+GSQRv6EzCaWRQ== +MB1q+N8CKxdHGjQk/9deWZnhOVPe2MNBJZhO1Yv83Kikb5nCCv4dYz93XNUeuoRM +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/PwBxF6bViFfZCK2od+BAyePkH05jVBu/cqOQR75KzenE +eeFiPlXUI0Fos82HYztSmek1Hai4VfOTZKn5AL9OI2s= +akHibliG6j9uvBEg96vReoCSW/2cQPbVxMmo1WnRDSTKckRA339gBDtR6W7CFwfso7I+qtvW4vUOFQbswXXT2w== +Z8UsPk1Q7HtwjRd4g01ryw== +SpI0zYSub7pmTdpBbKZzxqqObf5aYw5azkv7zdXxcMAHXq7l4H31BeCIFw7vemzpAatEUGoPtgFcgAdwExxlChv9FP5972erQefCcZ9QD9hgsNikShhS8ERWyNZAF2tiug7tqKnhHl0bMX21kDW3NA== +b8Z66UKHFNKfnviyrBdGNF7ubN/CHfspitZnXjg/lpCt22nhOjl8D94DRsY99XX2cZe5+WrAvq3IRMqKjiGzbQ== +/0ULpLqgTvInFD0r5hHANn/h2hIINSKoXkLr0tVjm7A+IDYkO5eeoSYY8vf6hSf3 +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +dH7AMVfUJYrKAb48JP4/cg61oLtT2qbIX591S6dFu+nmhL+9JNt4vu7EAVziuI9l +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P7e7/XNxPy5zWcJOvTd9fDBvrYakNMRuqp9fvHhaooUF +eeFiPlXUI0Fos82HYztSmek1Hai4VfOTZKn5AL9OI2s= +akHibliG6j9uvBEg96vReoCSW/2cQPbVxMmo1WnRDSTKckRA339gBDtR6W7CFwfso7I+qtvW4vUOFQbswXXT2w== +Z8UsPk1Q7HtwjRd4g01ryw== +SpI0zYSub7pmTdpBbKZzxqqObf5aYw5azkv7zdXxcMAP8PrCcWZBSrIVB3sZgZlURbCaOz0RX72rrSkrsnQlhJPjlbXFYTjZgbsrw8P20Qhi9NXAs6Vrjcxm16J5GJr3o+1+GgVvLzU/1fkVo52Myw== +b8Z66UKHFNKfnviyrBdGNF7ubN/CHfspitZnXjg/lpCt22nhOjl8D94DRsY99XX2cZe5+WrAvq3IRMqKjiGzbQ== +/0ULpLqgTvInFD0r5hHANn/h2hIINSKoXkLr0tVjm7A+IDYkO5eeoSYY8vf6hSf3 +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= diff --git a/class_v2/safeModelV2/sshModel.py b/class_v2/safeModelV2/sshModel.py new file mode 100644 index 00000000..9fc643eb --- /dev/null +++ b/class_v2/safeModelV2/sshModel.py @@ -0,0 +1,189 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: hwliang +#------------------------------------------------------------------- + +# ssh信息 +#------------------------------ +import json +import os +import re +import time + +import public +from safeModelV2.base import safeBase + + +class main(safeBase): + + def __init__(self): + pass + + + def get_ssh_intrusion(self,get): + """ + @获取SSH爆破次数 + @param get: + """ + result = {'error':0,'success':0} + if os.path.exists("/etc/debian_version"): + version = public.readFile('/etc/debian_version').strip() + if 'bookworm' in version or 'jammy' in version or 'impish' in version: + version = 12 + else: + try: + version = float(version) + except: + version = 11 + if version >= 12: + result['error'] = int(public.ExecShell("journalctl -u ssh --no-pager |grep -a 'Failed password for' |grep -v 'invalid' |wc -l")[0]) + int(public.ExecShell("journalctl -u ssh --no-pager|grep -a 'Connection closed by authenticating user' |grep -a 'preauth' |wc -l")[0]) + result['success'] = int(public.ExecShell("journalctl -u ssh --no-pager|grep -a 'Accepted' |wc -l")[0]) + return result + # return public.return_message(0, 0, result) + data = self.get_ssh_cache() + for sfile in self.get_ssh_log_files(None): + for stype in result.keys(): + count = 0 + if sfile in data[stype] and not sfile in ['/var/log/auth.log','/var/log/secure']: + count += data[stype][sfile] + else: + try: + if stype == 'error': + num1,num2 = 0,0 + try: + num1 = int(public.ExecShell("cat %s|grep -a 'Failed password for' |grep -v 'invalid' |wc -l" % (sfile))[0].strip()) + except:pass + try: + num2 += int(public.ExecShell("cat %s|grep -a 'Connection closed by authenticating user' |grep -a 'preauth' |wc -l" % (sfile))[0].strip()) + except:pass + + count = num1 + num2 + else: + count = int(public.ExecShell("cat %s|grep -a 'Accepted' |wc -l" % (sfile))[0].strip()) + except: pass + data[stype][sfile] = count + + result[stype] += count + self.set_ssh_cache(data) + return result + # return public.return_message(0, 0, result) + + def get_ssh_cache(self): + """ + @获取缓存ssh记录 + """ + file = '{}/data/ssh_cache.json'.format(public.get_panel_path()) + if not os.path.exists(file): + public.writeFile(file,json.dumps({'success':{},'error':{}})) + data = json.loads(public.readFile(file)) + + return data + + def set_ssh_cache(self,data): + """ + @设置ssh缓存 + """ + file = '{}/data/ssh_cache.json'.format(public.get_panel_path()) + public.writeFile(file,json.dumps(data)) + return True + + + def GetSshInfo(self,get): + """ + @获取SSH登录信息 + + """ + port = public.get_sshd_port() + status = public.get_sshd_status() + isPing = True + try: + file = '/etc/sysctl.conf' + conf = public.readFile(file) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" + tmp = re.search(rep,conf).groups(0)[0] + if tmp == '1': isPing = False + except: + isPing = True + + data = {} + data['port'] = port + data['status'] = status + data['ping'] = isPing + data['firewall_status'] = self.CheckFirewallStatus() + data['error'] = self.get_ssh_intrusion(get) + data['fail2ban'] = self.get_ssh_fail2ban(get) + return data + + + def get_ssh_fail2ban(self,get): + """ + @防爆破开关 + """ + data = {} + s_file = '{}/plugin/fail2ban/config.json'.format(public.get_panel_path()) + if os.path.exists(s_file): + try: + data = json.loads(public.readFile(s_file)) + except: pass + if 'sshd' in data: + if data['sshd']['act'] == 'true': + return 1 + return 0 + + #改远程端口 + def SetSshPort(self,get): + port = get.port + if int(port) < 22 or int(port) > 65535: return public.returnMsg(False,'FIREWALL_SSH_PORT_ERR') + ports = ['21','25','80','443','8080','888','8888'] + if port in ports: return public.returnMsg(False,'Please dont use default ports for common programs!') + file = '/etc/ssh/sshd_config' + conf = public.readFile(file) + + rep = r"#*Port\s+([0-9]+)\s*\n" + conf = re.sub(rep, "Port "+port+"\n", conf) + public.writeFile(file,conf) + + if self.__isFirewalld: + public.ExecShell('firewall-cmd --permanent --zone=public --add-port='+port+'/tcp') + public.ExecShell('setenforce 0') + public.ExecShell('sed -i "s#SELINUX=enforcing#SELINUX=disabled#" /etc/selinux/config') + public.ExecShell("systemctl restart sshd.service") + elif self.__isUfw: + public.ExecShell('ufw allow ' + port + '/tcp') + public.ExecShell("service ssh restart") + else: + public.ExecShell('iptables -I INPUT -p tcp -m state --state NEW -m tcp --dport '+port+' -j ACCEPT') + public.ExecShell("/etc/init.d/sshd restart") + + self.FirewallReload() + public.M('firewall').where("ps=? or ps=? or port=?",('SSH remote management service','SSH remote service',port)).delete() + public.M('firewall').add('port,ps,addtime',(port,'SSH remote service',time.strftime('%Y-%m-%d %X',time.localtime()))) + public.WriteLog("TYPE_FIREWALL", "FIREWALL_SSH_PORT",(port,)) + return public.returnMsg(True,'EDIT_SUCCESS') + + + + def SetSshStatus(self,get): + """ + @设置SSH状态 + """ + if int(get['status'])==1: + msg = public.getMsg('FIREWALL_SSH_STOP') + act = 'stop' + else: + msg = public.getMsg('FIREWALL_SSH_START') + act = 'start' + + public.ExecShell("/etc/init.d/sshd "+act) + public.ExecShell('service ssh ' + act) + public.ExecShell("systemctl "+act+" sshd") + public.ExecShell("systemctl "+act+" ssh") + + public.WriteLog("TYPE_FIREWALL", msg) + return public.returnMsg(True,'SUCCESS') + + diff --git a/class_v2/safeModelV2/syslogModel.py b/class_v2/safeModelV2/syslogModel.py new file mode 100644 index 00000000..31e7450b --- /dev/null +++ b/class_v2/safeModelV2/syslogModel.py @@ -0,0 +1,983 @@ +QRASP55VO/1DQ98p1csw9A== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +dBZyCsfrbwqvA0sbdGrIGg== +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +n+0ptngHIPIjFuMNQ53bfj+Na/fdhk6k1yTpAwW7j353Dw920mEqQQZjykAHeRmp0ZD/P3ftGifsmPOMf2b7XdEqyZH0yl9kjaUugj3dYPI= +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +PEKPgJeDDCLnL9UcS39EYQ0oxS0YHncLtyklh46m5EBkNzGCoSotSCFeO4A9wJZj +I8MGJUwtjfcKc5w4E0SmjHtl5KRuv0WI7NyW3SlEwCrZmcmaREiC99KS4CzwW5Su330khbLdQaeuAWr4x/NqQCTep2zIARzdXiKXPh1Fe+M= +1u+XjG/2+GSQRv6EzCaWRQ== +izOKcEjLnmyxxrod80R5Ow== +I8MGJUwtjfcKc5w4E0SmjHwLsErBQ84ek459TV1n0Iir/P4mdpfwDI34s6+8CBN0 +9/AEQy6sYem8wkQNCggJi4i9kWw0XQ381/dYG6HKfZo= +piG6BsMF31u4R4iA6R4SRA== +dTLQ8Wr3wPn7w6pte1B1YA== +wiYVtfO/yzajW1Tv5z7hyA== +bDrBrYFBEwTqHDQlSf5Wqw== +SbQQ5SqO9QrBwZ9ObpMYLw== +1u+XjG/2+GSQRv6EzCaWRQ== +XIfdJ79nObMM+vyAmKbTmw== +P1nWGQOfbECkszATyvUnYMoMB+zOtupYIF89n5X+cOkvqsM/qxPIR+8JRAch8USo +rpy88ff5JnVuJqXSNlYf+CQ4VKHExseR2LosMAdYEeHGNm80vDZH+2U6FEAnuapk +1u+XjG/2+GSQRv6EzCaWRQ== +hbDorme8BqzUEmeXCAWmYa52umm3PBCcfSvATzTG02w= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +hxPPiT2HRNyGCUbYWjiGemMsho9zzoERNvAKutOOCEWVhC1sbjDeebh+SGn5u8GmkL2g41VpzeVyv9m/J//MuDXVnCmww1qdZncqaHpwyg8= +wgR07xfoapmx6eEnFHXXYlFDtUVt+yJ+tU8VHk3FJTvnJUFpUNld/t84QFPEYKTpJU156tin3Vc241jPdNebVm6HK/Ydm5jv2Mb3PtLSJC4= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0feE7ECax+/ZCUSs3gQ/3AaKzCTzDvnrH+EOl140p3Ejvkj/qRKNVgZY6MNTd6YR+5IenuNCFRCqo568dB1SxUvcHBcdz25PdNciWuU0T5T0+kclUUtQxfNacEShYTanibNFhUnMe4HHLWOUhn13GDCqcyIADTbNu8ZEuFklZzsT8l2mK3j3yk9iI88+Tz8U +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2UpJqD2VXFNDkddRfcdBjn5Xo11cfHVg4MHTU1ECnQkBR +Z8UsPk1Q7HtwjRd4g01ryw== +wa8O1mcGGxpEvPPRX3ikmvykeeKhIH9HZybe0SWYg6M= +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +wI4EEWzdSnynhqFxMCzFmo1bZxLdYghu7tPjNNa83Yw= +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +/p0PqyMitwnpAkMVBMAV0MOxh8vna0k0svW7xqoHRyo= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6+eQW79nEpaaEVxkQ5mhMLDYFz3gFM695f1GA6K12DxM +VmVrGQo2zRokW/ZuO9bN6wYljL95v5S+KLH0XvYeRAu7jaBdeUshyw1Me0QcftpN +VmVrGQo2zRokW/ZuO9bN67F/Q5IDafbWxan46AKyqXhJkHTWPWMJlgqQQU22Q4EH +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +SvL+08tQNi0qJOHjInKZY6RndgMQo6zUlLYIZu8iQNU261HNxXIEnmCJLHd/cAn682cyzkJMzokqlyn5ZLB0wQ== +GDxnwLueKeYNT0/dIp4TaA2EgKkJaFhgdpXqI89CP9JxudfN4YO5Bip/xZCOAyUl +89eRayX3G8tCX3FDU5IcjV0+/dsDUitdv1ScoFSstoWJGpFlcQ4x5TphzsFb2IQj +VmVrGQo2zRokW/ZuO9bN66izANLHguDX6h0ZMylPTZjSNwJjUmKceY0nGArlnnjq +F9Sftf1PoN5P+lgvc+r10NBQ12btoHUSbewJsWZIkLA4htiDy2/7V46D3DsQdHGR +VmVrGQo2zRokW/ZuO9bN61Go4AfJcv9+G+6Yze5rakTqOEVt8xxc04JEb0WvhXiiyOuFu/FvRpO928WIX0wTxw== +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +1u+XjG/2+GSQRv6EzCaWRQ== +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P3lRQWSpZzKNexozsWlQSmt +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidZcC3xryH5q1zGGaoBvvKTdC1AgWcdepqNrVhCT6tqt7k+JxPMwVGPnSqxhvLgv1DQhFmLlBc8XKdIY0AICqQQB +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmzHeTpF9D/4KEYPN1ABlSXLB+bxGaCCDvXrlgokvNEu+QTyZ0OkNbOjawGeCOhOAY= +esnxVrQTJckUcXi8Vt8zdyY4i7Esn3+rv1ghcUNo4q0= +NhnIR3Ilo4H2su9/cTNo/L0HJQSj7xDirJmInN4ogA7N4utqCGavJw/zWl3BqVBOcu0yh4310yAJbcS5M5y7KLT8b6U9cXXn+DOi/El+juU= +1V2v8QerKOmubvSxgB4eTCJqrBg4vZgNAdzlsRAbXfGCflRUO5Z4sXw8H0VTfzWe +1u+XjG/2+GSQRv6EzCaWRQ== +o6QhOIN2Sc4SHELnst17ubkalL7sSCEY8nh1Aau3kAESZfNThdx/u6aA+dfb26kB +1u+XjG/2+GSQRv6EzCaWRQ== ++kSw/JERkIrPRLyCm/D7AdGS0jaXYQYSNkJ6PDaS5kKRDw+QnaOx8ss81acdnppHhvRGi5fhyW54Z5gPLzuFGbO8BuY6dL1EqHs8DpgsB0c= +DGL4HmYq7EifnYNbqVjovJ9rKPDbCo2TWohz7qULva5wehaU26sfTyFVmBnq3LLIjH6lvyfPNa4W+j1TNuSYYA== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz7wO3aq9w4Rrk6lz64ig0wO +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2UpCUU06JrEEfNymvLzRwUVgF/R4iaOcaI2S3O3N7OAwl +Z8UsPk1Q7HtwjRd4g01ryw== +wa8O1mcGGxpEvPPRX3ikmjl4u4bTOurjxSyetFOSLpQ= +5Ch5Cy/OpWh+1WaF8Su/eDwl93O5klNCtuxXaiypyoA= +wI4EEWzdSnynhqFxMCzFmo1bZxLdYghu7tPjNNa83Yw= +Z8UsPk1Q7HtwjRd4g01ryw== +/p0PqyMitwnpAkMVBMAV0MOxh8vna0k0svW7xqoHRyo= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6+eQW79nEpaaEVxkQ5mhMLDYFz3gFM695f1GA6K12DxM +VmVrGQo2zRokW/ZuO9bN6wYljL95v5S+KLH0XvYeRAu7jaBdeUshyw1Me0QcftpN +VmVrGQo2zRokW/ZuO9bN67F/Q5IDafbWxan46AKyqXhJkHTWPWMJlgqQQU22Q4EH +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P1DHXJ/TJdbmPH2ckSraWgG +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidZcC3xryH5q1zGGaoBvvKTdC1AgWcdepqNrVhCT6tqt7pCgU8fcpxsvRgulBE1NZu9SSmus2oz4HzvvEcXgYZbN/5tlDq8RdAY5WQkwLWPTq/dVzZBv6z2B8FhigMKfcMnFomnMj1iyhyb10pjIliz/ +1u+XjG/2+GSQRv6EzCaWRQ== +esnxVrQTJckUcXi8Vt8zdyY4i7Esn3+rv1ghcUNo4q0= +NhnIR3Ilo4H2su9/cTNo/L0HJQSj7xDirJmInN4ogA7N4utqCGavJw/zWl3BqVBOcu0yh4310yAJbcS5M5y7KLT8b6U9cXXn+DOi/El+juU= +1V2v8QerKOmubvSxgB4eTBzsiAfEZawGIv/PJDVZk8DLw/veVTQK3qL0m1VX1XdQ +o6QhOIN2Sc4SHELnst17ubkalL7sSCEY8nh1Aau3kAESZfNThdx/u6aA+dfb26kB +1u+XjG/2+GSQRv6EzCaWRQ== ++kSw/JERkIrPRLyCm/D7AdGS0jaXYQYSNkJ6PDaS5kKRDw+QnaOx8ss81acdnppHhvRGi5fhyW54Z5gPLzuFGbO8BuY6dL1EqHs8DpgsB0c= +3iLgbOjdwb2NJRX0BUpwUIGMMUcYdts4/byp4bTeA76nRH83w4WFLV5l6fI4JsOauRI+ykY2xjrMOgZrZSyGfw== +DGL4HmYq7EifnYNbqVjovJ9rKPDbCo2TWohz7qULva5wehaU26sfTyFVmBnq3LLIjH6lvyfPNa4W+j1TNuSYYA== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz7wO3aq9w4Rrk6lz64ig0wO +TC/LaX35Eb0V/GmW3Zr2UtYsmXWasld7EcVPoh9dGjGt+2Xjb/ezeoPiUvh51Scc +Z8UsPk1Q7HtwjRd4g01ryw== +wa8O1mcGGxpEvPPRX3ikmj1BsUFLbf8jUkWlQ6+15/GeyFemD4bVxaphwo/ntI75 +ho/Q+jrWDtBeg9J9ZTnKi+3gjgJLBNWc0CWgOki/1ZE= +wI4EEWzdSnynhqFxMCzFmo1bZxLdYghu7tPjNNa83Yw= +Z8UsPk1Q7HtwjRd4g01ryw== +/p0PqyMitwnpAkMVBMAV0MOxh8vna0k0svW7xqoHRyo= +b4OJVZe8QyIpjuTpKXDL9A== +OO/dMlOTxBikYSnUTxYxGoo1e4d9a2dJZTGtQemHKj8= +VmVrGQo2zRokW/ZuO9bN6+eQW79nEpaaEVxkQ5mhMLDYFz3gFM695f1GA6K12DxM +VmVrGQo2zRokW/ZuO9bN6wYljL95v5S+KLH0XvYeRAu7jaBdeUshyw1Me0QcftpN +VmVrGQo2zRokW/ZuO9bN67F/Q5IDafbWxan46AKyqXhJkHTWPWMJlgqQQU22Q4EH +YdSR6pyH4bNMAFEsS23H+Q0wLdDp3qS2XQEA+Pt5gvQ= +VmVrGQo2zRokW/ZuO9bN61NtX+W4Z81vyIgNc7bfj8ScGuujW22yH0T8YtIQEhHl +CVRTg4gcJQueLD3Ni7+7eA== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +xWoGNWjKGPfI4gq8aHoTfCvmCdiXBxNOXqLgrzMPQ6YpjxbqlA6+KifkyBYEhUnLM4EqaRP0uxbk2Gj0m0Rw7w== +L0eUthVnpkGsmKFAX6d+uDz5ZRBys3d0SQKK5GYgSmWghjKa4Bx/9hwfBJOgnWLqzcS5swPR9eX3Xw0rx13vEw== +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcOuWMW7q96BiVbhOlHj8k0LS+GLTgLVoUNjFkoOOd6Q/ +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidZcC3xryH5q1zGGaoBvvKTdC1AgWcdepqNrVhCT6tqt7pCgU8fcpxsvRgulBE1NZu9SSmus2oz4HzvvEcXgYZbNsAyiUdVbe7Hjmg9ET03bHboNn3w+ITWBFzpEUndWW90= +1u+XjG/2+GSQRv6EzCaWRQ== +esnxVrQTJckUcXi8Vt8zdyY4i7Esn3+rv1ghcUNo4q0= +NhnIR3Ilo4H2su9/cTNo/L0HJQSj7xDirJmInN4ogA7N4utqCGavJw/zWl3BqVBOcu0yh4310yAJbcS5M5y7KLT8b6U9cXXn+DOi/El+juU= +1V2v8QerKOmubvSxgB4eTCJqrBg4vZgNAdzlsRAbXfGCflRUO5Z4sXw8H0VTfzWe +1u+XjG/2+GSQRv6EzCaWRQ== +o6QhOIN2Sc4SHELnst17ubkalL7sSCEY8nh1Aau3kAESZfNThdx/u6aA+dfb26kB ++kSw/JERkIrPRLyCm/D7AdGS0jaXYQYSNkJ6PDaS5kKRDw+QnaOx8ss81acdnppHhvRGi5fhyW54Z5gPLzuFGbO8BuY6dL1EqHs8DpgsB0c= +DGL4HmYq7EifnYNbqVjovJ9rKPDbCo2TWohz7qULva5wehaU26sfTyFVmBnq3LLIjH6lvyfPNa4W+j1TNuSYYA== +96orka/uERLyRst14azQwnND1LaAMeJ6hPF2g6RNEz7wO3aq9w4Rrk6lz64ig0wO +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +gn9R9ypc7EQr3UZGxM7R2AzpBmjpt//gk3VbIVrmsWPUgmmJziYMAJ3SBavXHHpg +Z8UsPk1Q7HtwjRd4g01ryw== +IafCwgGxn6pY+tyyf7k+k1uPwgoyKLoGy1T2IHFPUrs= +lGY+HGJ/DlLufZHWWlj7U8JUjESN387GevZrf0/dwpoTGJutrsojcsEElOQwuKhR +ZCo7BKJgBzBoR5QyWRMODvuiXuF1YyjhIRa499f9oeDFOwCSNimfzEv6xwOiYENc +Z8UsPk1Q7HtwjRd4g01ryw== +NYUPEpAMYcAgMU1lywXh+APRTYQ7bT0FxrwWNK9r+kE= +X/Lp4PsbbXulceSXUvGUpAixZi3IzpQX5iLz2HxFdWwS5A14Kie1TLFHz4QUExEMrjBXCKSQ2mS07fFxKBoIRQ== +s2s/1ED0ebJ2NmFlE8kk05gdRKkjvH+bphLoBZkO6vMkQmee56NmkDMz3ewSmOSg +wlLHv6kT3Q/RmtMBN4nDASYMmJRC8P+40dq2mnWeVhc= +VmVrGQo2zRokW/ZuO9bN6+vO0T5LxpdIZ6hmr5+BXMwvx1h4H3tu9h3Lw+gtPHVT +ACEEdgL/kNbx7RaZcSRxZKy0zW5JN3zc9G0K673Kt8E= +VmVrGQo2zRokW/ZuO9bN6+CU0Uzm+BLGwlKn+xqu8O0= +L0eUthVnpkGsmKFAX6d+uOW5BVuM+7tcrCiEwvU64kY= +CAhn8dRUItEbEErp4w+lX3cOtVXg8rs8OR96pWoJZvY= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +TC/LaX35Eb0V/GmW3Zr2Uq12VTZnVAgLNUPRVpboscSkdq/rWesDEOFnnwfIdOM2 +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY8qDyry3eeYEnSUnFHqFOEdqLpNRCL3M5LaBhSQtDro7 +7JQQDTi0T2idhFjao4jEpWyyzmLHEngKhI6rkPYC+OEQ3v3507uiPdl6O4aO+4ER +7JQQDTi0T2idhFjao4jEpV/go6iO1qzsLRmLYXsC2awnSWcIsvPYVwnYvfMjZaDtow5+WSfBeFo+pvL7hARzXDxRko1EzJaJ+fi1oy8D1/8= +KZTmaJLp+FU9X93j5Tpqtw== +CRgPeWAvMGMS2vfLmB+XqAPNkWJygDEUlCyJRk0QVvtGXcHrIivyKjsAViAs3OCtr/wdXA9sspltDH7P0695hw== +/R3t0fIiNxFa1+EM4KdcC/Tq9YNQxX2rnc682C9+fe8= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +JbnXIK/axHVmA2plDNFCpPYlXWvPB/7lIz9ynMkifTY= +gmymfXJ+E4e4NAtQtupPJnNetLAHixU3GutHyzvm5FjbdeuSmaUynxh9ANB6mVzZ +/2yBdrTJDE3TFtcSLCcx/9sJSd4kIdV7hBv4DW/k5kQjLCSiOqb/ooMNKbVZJjgekfdghInCCdI6qQz/jIgZgA== +NhnIR3Ilo4H2su9/cTNo/FhhB6MZ5HlL8ilxek9dnzUbA0zrRzxd/m81iyHyj8Jn +NhnIR3Ilo4H2su9/cTNo/KlYN/bsxuWzFF+nWhu3hBR2BWGqsiqwtBlTpURXFcpL +NhnIR3Ilo4H2su9/cTNo/G1hu91GucoyUk5CAa7Se62ja+Iodh3ArUEpNw9/IIap +l2FJPs4YkAmmok1ulDRuSA== +NhnIR3Ilo4H2su9/cTNo/FhhB6MZ5HlL8ilxek9dnzWFsroGkpAbYhhbP16I8llP +NhnIR3Ilo4H2su9/cTNo/KlYN/bsxuWzFF+nWhu3hBQYlkX5gl9hxYgaKXgpyNbH +NhnIR3Ilo4H2su9/cTNo/G1hu91GucoyUk5CAa7Se62jqdhNgRiCY2VhbppcAkgq +1u+XjG/2+GSQRv6EzCaWRQ== +eDDKQ1PxEOXD40WdPkjAt+4gIw5LFJ2JffPLp1i0RM0= +/2yBdrTJDE3TFtcSLCcx/7i0c5WFgpYA4Y1T5vIUHVO5oUJ3yHw6al3zQOD6XGLm +NhnIR3Ilo4H2su9/cTNo/B0KkXyoVKsrbyorgJnjAt4= ++plE/1bhdo64kO07cLlUXzzWH25pNAS0eDxp8hnHILg= +1u+XjG/2+GSQRv6EzCaWRQ== +0feE7ECax+/ZCUSs3gQ/3AaKzCTzDvnrH+EOl140p3Ejvkj/qRKNVgZY6MNTd6YRirlilOXAKg9O2fXCfHGfBtMDPeLErFs86v0ZeSLWbiUlxSp8Q6JZIdSDtsOukEmI9CkgD9cr1YKi5zNr/8OAB6nq4n5Nx0RnnUUavr/JMa8eGXsXCu3G6OAO8lGrXUZm +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +ISoywpaLHuQBxWIUflU2WE6goDl6H+SMTlvAvhjEOunmRWoDI71YFjPGTq2PREVfgI/0kDXgAM1iC59/IdE0zUqUlwg/visqR/ji4CUCNls= +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P2neUS+qyt4fxBbg7aS5U8ZVSEGgWJcksomV0iOXfr3S +wOl7lRII4ZgqRTj9M//0b1qhFBAHPMvcKJHX8xF2dd3LFwNZYH3pF0t/6DGxSkRF +yqM179SzW9HnBHozkU+IlZLJm7366WyNnx7ZMsu9exUEQG5/9gzkoWdK3lm2KNSi +NjU4zQ5DRyOHitK6CHLOgNNxRKGkyV3kGqu2T3oe3wk= +NjU4zQ5DRyOHitK6CHLOgMJ4vze7G/BcNjyu30dz0dI= +bIts5UhOLbX+5vrkUi2fEhpPnL7BGuZkarYaOytlgoinj9jnlLh+lL3x0ZV5fo0E +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +XEKtPwhadR+kpKQN/+UTafshnKFviV+idW9zO4AP3io= +1P+dKgYYLCQ5O0NZwxSOaJcUBjFbQdzF9CNcdagyAI18a21hAHVeicWVY9WgSU2K +zrSEP+cwIY0LGAkn1FqokI6RwnSJpjctW3UvricDZLxItXuL73USA+6pRr2HvpJaC7RiR4yb1PJQkbGlH0YRWQ== +1u+XjG/2+GSQRv6EzCaWRQ== ++xLLwGkVmjlAlAN2wYY0FOnVwRrXqNlRBemygIVo5xo= +p0E//cFR8EOf13yDhOlObMD3Yuxj4bTt6EnfF43vdnLk7mp9E28enjNVoyEj00PVBw63PNtJHh7nYjCP57FGPqozYy2TzONryzbzwkoUMNwvUkxvlVRrGo2rxAnE/g0V4Cmmkw+dKL0nhwQooy8bnVmuDHamveEqMpiknfK8NBA= +p0E//cFR8EOf13yDhOlObP3FQjpN4YYsA4YJPXooDzrxOF7DYxRlXeKipAKSaevnHWB9P8oJq6upWDy/MUOvLA2G18HPNK28orPv/JxpaCU= +p0E//cFR8EOf13yDhOlObJwu0OCz0PycRy7pO+A7qycPBfJfS4wcckld5TKXEJcQ8LTGY/BgOJ8/Ey+ycvnkaPNCF8x8ePPtPFV33M0BgGR33MbsHGevwAuVMKg7zRUDIXwgrTQUFIdujw+lXXU2GmtNOEJjkhHjhR2dXDsYQl+qxS0SKUpVIYzDr1eRI12GUQFYJwR76xewQvSowM7l2Q== +p0E//cFR8EOf13yDhOlObNQNgN99cJOreDX6FzaHhxv3V4pef0BAT3QaZNKMIUivldoeOBbHNkFVO4BMgqzu4fDHYcR1BKL6eBdwt3OBu1kVdmgx3s4fTZ8LAlEPq2BRzuN4F8ZeuA1ppvWiDhACcEHyX6Pz6yKUiqxmQn0qung= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw58tqRCidnUtuhiV/SQek1vZ6sNmdinwpH5+NOEy1mNl8yusqwsRmAnJlzr3FKfnstUDfM7vtqOWitJUQd0uNl0= +tmi7c6vWl6N6sdrwujX3GReSPhJ5ssuXUZtdOTkPGXc= +4E0YNxQKZtqIoiEqkAJApA== +fE3zmgbfhO9ZwEda5GgwDzwSc+DUx0Ss08sG6egCcJP+XUG+DzlYynYbT/JVkU7P +EsliW4ECGA8kkEJ0BUSIKZ+6kvYYxaoa29sn6RE+30Q= +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUqoJYM2k3wpGIGNfe4ZVylRY2CmgGV6o3LCAp54cduWSIwMrRMTIErqM/4y21/qV2s2Bxs/+ycPzzZdeNzsz9/q4sZS2NAVCp5rDAV2WNLaY= +VmVrGQo2zRokW/ZuO9bN6xCL2jO48J3n3RHpsxsFW0A= +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUtD4Dy7F2C5KfRvcNVwIaIQRcd7Fy2ZXHLWGZJPl8MX9RBwYra8rs4CAKfeGMN3Bb +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUgC86euXJOlFyHZyqDAOLUAdeaHFM4IYtg18Uequ7wafHVvZue+VX8ISJjUr1sqwFRVRgsWgqIDu5NviNkV3pauDs1a5D7tn5bIpmJcr1KCm6cN6DLgXTjktT05vWK1srWo2mj1pylV4R46zpVCmFGA== +VmVrGQo2zRokW/ZuO9bN66Op6JyhixbDicezrxsJdmvmw/JvaaHtlFkTsLFyz3qi +VmVrGQo2zRokW/ZuO9bN67OqiCrLl/L35iT+4iw8qa11ix7vyn67SQSkxOVmBzkUv3HBNQdTfHHC76HdkzkeKhcn8l+7nqNJm8fOH+30rrfwQzm8hP4An/vuWNJxgVfdBK/7rxGOROS+FKukjRDAA7v4+O6TFSTWLNyfZEFdSHAACyn19p4G4SaBK+kNp7Y4 +VmVrGQo2zRokW/ZuO9bN63hl7oGUPd8q+ErnISkgMco= +VmVrGQo2zRokW/ZuO9bN6xCL2jO48J3n3RHpsxsFW0A= +4E0YNxQKZtqIoiEqkAJApA== +AaV0Q13NpbJz1j/27jY/ubg6CfL/9D/rk9oHG6vv7Tk= +J1UGlXkKhKXD36OrXXhN8PbGS4CCkC/QcSnejunC4s1HNXfKIR7pGHCoZHkqc2al9dG3/LRVuqxK1/lHGulhpw== +VmVrGQo2zRokW/ZuO9bN64ywnuovLuNhCiDoP2wQAWzHwT2J7ZgEwYI2DHVR0db7 +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN64ywnuovLuNhCiDoP2wQAWxQoOqVjvwf024i/wGnySdC +l2FJPs4YkAmmok1ulDRuSA== +EsliW4ECGA8kkEJ0BUSIKVKU5W04dIhCWYbq1NBglGIbd3FwxA+IXISbsRb5KU8x +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +6xO+ubEwkQTp/D6ATXPZaR9RFvPZlxoFFcMoTEBWzdY= +1u+XjG/2+GSQRv6EzCaWRQ== +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGjZZ/Q9S9r5t3lPlsYorgw68Y18oFZuov4IXxgtwIU+fyOxgNLXV9uwsqyK3UQIuC0= +T0lwjDK5GymcJe/LbHEB+5LTXLLonGWNuuTQfjCTPqg= +wlLHv6kT3Q/RmtMBN4nDAfGIYwX5xDRj7uztmlOephI= +VmVrGQo2zRokW/ZuO9bN615pZCvbchosnTxjAGDSTuu5F/3dP6AEc2sI/MY5xwN3 +1u+XjG/2+GSQRv6EzCaWRQ== +YnP/gQVEpWES+RQ7J7RJRlUivABjbdmquJjPlRdBH7s= +Wd7OXFQxn2HMHBqE448Pi6S8+/GBA34d3QZMmVea2Tk= +p6i8iK7HDbvmLpoFrhNyvxqRSTuyLOZi037vACDsfa8= +esnxVrQTJckUcXi8Vt8zdytTNOUxGpVlE+9gz0v5AKo= +topgAunkM89n84cH9D9pHIJ0dN6Gk7b/C+bsk/3fJRquhc7fUjMhZYvLbsfG2DMWzaSd6qLjQSoUeAO/UtXhP1Yel7kB7XwKvSI9CIFJqS8= +ACEEdgL/kNbx7RaZcSRxZPvmvyPIJl0tNCG2z5oQFSMF3Rtz2Zl3Uf+ip6mRNkxm +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +1u+XjG/2+GSQRv6EzCaWRQ== +J1UGlXkKhKXD36OrXXhN8DolGRp6NkzBMOOoPdzZSROBDCr+gph8JmqTmJ+BwzEcAyCmQfvb6v5GI6hFw38sPQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+M80RvQb5VsoyLkSgRiqcM= +VmVrGQo2zRokW/ZuO9bN61RJ7HPvuDQyY/7jnqZlstvr14lD1Nsc1GBMciUP56h8 +VmVrGQo2zRokW/ZuO9bN67mWWDHj8CSfv92pyS9sLERjFtkVPnpdPLZSP2NPs1gXeeQZjNsawPLfcnlKBwAakNgFVuo2Yd5N4wsDtWM07aE= +VmVrGQo2zRokW/ZuO9bN68V03MBz2tWTRGBIaN8QcnWa3+54ie/uRDPpsaNfYNUhfJWkoMWEjN141ojxjPQz7iUbeWa1gJ4fwE2A4QfkFyY= +1u+XjG/2+GSQRv6EzCaWRQ== +5wsVwKLrIjIWqeTNpSVp9QDHrlhrdh8VDOVcdAVCwxo= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +QdTjTxbShl+8mrARISGoAew+M6wRwESTJ4GivNwwWTIyDaIjNgge4ZpF0WBvDJqZRgXN2RgjWkE0y98VXQbEpQ== +Z8UsPk1Q7HtwjRd4g01ryw== +EauAFvhEfQAlaCCYJmuW5eV4DJOW7GNwqk+bbKrKpSaH1MvmX7D3DvPHyH3oySvOnRXE5AlMLgwUDhfDZANse1idoOMd3rJKanSLH7Uc123RRwtjA5eu4NckUBCieqKNHkfIlUn5nFjMA1ICSzTCQsv5pY+TV/1yO9BG5IofZXg= +eeFiPlXUI0Fos82HYztSmaNe9in2SzXpHGr5hBhAinnNHyxMDiBuvbazGbrnCMky +bIts5UhOLbX+5vrkUi2fEhOK0qYcUWJPnPtVafbmigUdzXiHWAONlKw9KDOwOlPM +akHibliG6j9uvBEg96vRelg6b5pjOK0azc0J/Ev8D6EPHt5rKichbRoGlTycmehx+Th3o7fyS7lnrCT4ZXS1gA== +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +NmkgFF4ZsyEHu9KVT53La1lz7XNNET0A9zdhd9gFT9fBSLnem4K6iQOWTy9x8XwI +topgAunkM89n84cH9D9pHITAncWu+e82byWKRCcayzEuvdX6re3IAygjiK0dtvwH5pfePRD6LD7p9ALfCp41GA== +1u+XjG/2+GSQRv6EzCaWRQ== +NmkgFF4ZsyEHu9KVT53La1eD1XgGKKkTpXLEHn6AsToySvZkHOsdmdS0PUJccZzo +sVk6QS4Pf3ntiMkl4LqRRdNSXsQEDaTnzm0yHVvLcNydLD7JFUCuBLvtEtInfM9cLHKhemuIRU5S45GYI+zKfw== +1u+XjG/2+GSQRv6EzCaWRQ== +JkdoXgqzqMYpkKh82pPju9o4/urRwxg/lFTkhxBEG5hgOJ5vGO/GPYvjcLXE248o1JQGij+4m8UEtIcbHbGSRRVhEZVih+RN/xjwceaGFpM= +mgV9MrvUK0fssdmv3ApVy7k8mJdR/B+7eG8ccjV2Ab788JJLQjLAWgAvu87PYMIxrNY3ii6KXngsif22LUSmMDbdoMq8P1ubKINGjlYCA7gTuIGpBMZsNYzFMa5NWPLopAgMwMESku+TbkFwChLHaw== +K9fxAkAAgWE70Il8AJdd4rdBcU0my9+uNHCVuDkF28NZN3ATTy62g9PIqSFIMzetO7VcoZ3+2dLF7JWJz2PwmrKOhy7hLolj7vwIWSqWgW1E/24aK4BeKO/DZ6lIwCW2E0Ly26J/TZrEe4nSAsvnhCF2/Sb4A2swT7NQC9kADyg= +L0eUthVnpkGsmKFAX6d+uCiYQic7T+3704A5xRjtHHV7D1K7sA7PU5Lx63H+wuEdqWGBURjxs/fUqKDjPX8aHg== +NmkgFF4ZsyEHu9KVT53La25kspERjj3Kd1WtR5F3DjUeyufiu9OPGHQHCZOJDZd/ +L0eUthVnpkGsmKFAX6d+uCiYQic7T+3704A5xRjtHHULvPeJ8pPVUmfUVgaJGNCIp4sjsG53ijiz9EipY86UOQ== +lGY+HGJ/DlLufZHWWlj7U/wgW3T0y9TOw/q89uQqE+c= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawFxAQnjo2U7M7PxwwYRzZjsaB1ggdhAibLBZPcfdEwOx2KWulak8d6g87sjhm9UYeQ== +Z8UsPk1Q7HtwjRd4g01ryw== +DzneBA92dU5V88ETMYFQ57uX0+Soy2svl62wHi59Ele/p/Fr3qr6/65PhVKxF6Ph +bIts5UhOLbX+5vrkUi2fEuV05t7kiBXke4QQt4fGEIi1pyM37lJMRHRDxQmK3gOZ +Tn3rOfE+s6NRy9yVv10cExsGK3PlpvQ/21Omt6hqVOjP06IQhzjsmDO6W8MidErE +eeFiPlXUI0Fos82HYztSmXW23eSyH2bXlOYpYhyb1RKLhaGx2IoCJa24JWsHrcMAg8yR7zX1oWHc14AqlOrK0g== +9BuKX7c+cBUFGH/xEQqKwAu6MbJNm8+b4qD0aAxmgDU= +Z8UsPk1Q7HtwjRd4g01ryw== +Wd7OXFQxn2HMHBqE448Pi6S8+/GBA34d3QZMmVea2Tk= +LxCLgek/xo8kXqFx9hyUVJPL3TFbh8u2ikhOGSAV0rg85G9Y5wYNCPljvLZlShFM +topgAunkM89n84cH9D9pHIV4dJ5fXtVEdOnz8/oeQYqujphF6DcnuFFxsQW+tGqMts90IYJNxLKB0izTwoEhvQ== +MzoJ1HANqAion0JXNkOKLkUXCYWzU8bckEHdk0zTAVCxXpXc/N2/2C4s2HOzit+j8Cu9wnFj9h55D4ASfhbhaA== +topgAunkM89n84cH9D9pHIjK3O9J9W+DroJme0vc0dxLsI4ZMqq+7wxYZZro3x0X +T8ztCulMhN50eAMwonrN+mnIYb5fDs3vnEBct26rKaEcORGfRo9zgegCK5TefOmJQqk4fO89rFCV3QP2Wzahfg== +bo0XlOt+6S78LT3IsBdT1dEroBAtDg8pr1Pybq8zhQJAu/VoI80oqogQomMUUlPM +v9uxr4HcPPk5UqMUuJoEoB7nY2X+tzb9PZyPToR8I6vD2vPuybQkfI0RMQ5SpdHZ +topgAunkM89n84cH9D9pHLhIfpf4IEOslMiXh31h17WTVnRZP8H/e9ND9t9DHG3EOcgqF1/s/qPfl2fGKEz3rA== +1u+XjG/2+GSQRv6EzCaWRQ== +Wd7OXFQxn2HMHBqE448Pi+XpPmaTTyeqpJxLnY2yRin/EZIgzqJ3CViWAhoEpU6erObfPycKdvcVekGvCE4Fyw== +lGY+HGJ/DlLufZHWWlj7U/wgW3T0y9TOw/q89uQqE+c= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +xzdWmbiM4p5/aGFV8YqP4XLLyrbPzLln0rwZDoXcnKc= +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P4+OZYX1oZ6IDO9Bb8L9jOTF/Bt2J4quQTw6RN85Bumk +Ys4bozvJBgtB7q16qcvtB0RluR7RwJXaqe7bJfwI9hU= +KZTmaJLp+FU9X93j5Tpqtw== +eW+3TRHX+r8wYGmvO/rbmQKRB+qqZDGER9PE45Hh6DhKWFPeFSKD/CBlJ+rZhJXE/6NzadTbUrA2WD4xQ/NtZg== +eW+3TRHX+r8wYGmvO/rbmQA+K0RVUGWoC1b+gP2mkFaXSKIZdVNEI2dyM5Nc324r +m00MPwDBB9sb6xb6db18s1+VrxrV3/MYAhac4NSUZUk= +4lm0ybc3aVxLaNQODc9aBcT0vlFE00Wx+6lONISyLb4= +iUEDlyYF71b358pIL4FKLxKByAvC7nSDmBVkTcIWC+9RPGSBJZRzJ2JFx9aDM6YT +zxz/Z9yjFalbpeH0OEk06wb+y3G26TwFK0BePbfiFxswJa+xbCabv6iKd6mKX8sM +zrSvdYautDwgfoprAQtW7Mh3DupVdGrOOgUnOp6J1n0= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawHo0YqPGy4jvJqzaJIsqw8aht/a5La2JHCiBs7MPYkJNnkttfrNlz3AZpwB+QDSF06CacZvErF4UHWUUepAZDW4= +Z8UsPk1Q7HtwjRd4g01ryw== +NNh1PlSD8WUnUEbz+BU/P5s4ZWJ5nCZ67hdzagwPZfnK+vAzOOBUPTQOeTz/eFRP +yqM179SzW9HnBHozkU+IlS7E9Jat0KMy3qoE1n5uiMAJ7/ZYgCvtzu/Qi+/1mAim +NjU4zQ5DRyOHitK6CHLOgMz13P9khDg6jBdTbmByKXOddMHKhILqXmmCVMC8sS3X +NjU4zQ5DRyOHitK6CHLOgKqZU0dP/fC3Nw6diKT/uI3N4y0eHljA4CIE4JRl3a/I +yqM179SzW9HnBHozkU+IlZ+sLT0JkWbe6QF8HumAIuHDqd+ZqJq4nJMu+r1QY06/ +Z8UsPk1Q7HtwjRd4g01ryw== +XEKtPwhadR+kpKQN/+UTaW/KEaZ8xQsb0J4R4+wy+qw= +F3HmfyKnf+xR8iqi6kYc0jeDpsR33ZKtlDiexNRZYLA= +3hCm6yDF8QSQ/yEbAtwtdmUoSfAgsymzLlnjfiQPPgQUC7w3DY9Wc7Bn+2+99jPC +6P8mKfouOK1hwXy3MrrCzRBPdWeF7NFSQT8rGlPCjGB/+XWHqqTbqSpG6ZavymMALgzEpY4QOg7MZRCal+ePJw== +1D18KWr2hdVsBcdZx1OaPddn9pCR79IrJK89K1q817H5A2Tia2JPGCrrHgOkp8RV +VmVrGQo2zRokW/ZuO9bN67r54/S5K8MNluBiVrjXDNs= +VmVrGQo2zRokW/ZuO9bN68iybrt8ScY6aQlDmfS7eSM= +WHkOzVx7seuLmxs5Hu+l/mrLDQ6BBXRseSNCn19UXP3hoocFrE+8Ryld4yV1KZfPLP8WjHXhqhwqVYteO5GihA== +uLea96U3oZ+p5tRB14NcukOqXOc+02jM+Wu1nAP3Qp8TsFA09eNgyYSzkxUs00j0kvSyulXKCf4d4VyaNdgiOYGlI6IN2tNOigQjWnS0T6k= +84Vymj90Wzn5yYuvz1pUuN+5YqHrmeLr14uTJtaH6Rw8M//FznjjEFAKx/A6G+T4mRGWmaJJ5mrlCNpDHMiqiiab0aARiCzaUr+TVJAp1ktqd5/WqjIhEIQ22J7sfDuL +VmVrGQo2zRokW/ZuO9bN67G5CWsj7J/k/lt9VMRehZ4= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN63zFN5O/w1krXKBn8bzERXc= +VmVrGQo2zRokW/ZuO9bN6wUs+Gj8tefXPS6OI0lu6+C28ajUjdjhJZKZ78jrIltE +VmVrGQo2zRokW/ZuO9bN6xoaa3pLsxXyLdB8GbtG2YY= +VmVrGQo2zRokW/ZuO9bN64WtjIYDMA8f1aIhS71PV/BDDNtiMn/i2htt7qG6uu2/ +xCSE09sQPuao2+KRKinomiRcpRci7l420yHWQGSif24= +VmVrGQo2zRokW/ZuO9bN65aumthNqQ+IW4uZbHuZhwikHiiPwk1EfEOEo86sWjD7LcGb0RbtIKmAR4xv+N7qXZImTRX92Ml/lMtUX2VvSmDQSHW/qxC7fvXW0Egj7mQJn19nWjNYYBlGLlz5NXYlvWMRri9elAmP5NNsyphXvXk= +VmVrGQo2zRokW/ZuO9bN69P4j4YErd4z0j/lq64sehhXBs7XglFhAiuqgq6fZc7o +c0jePRxtTVZYop75Q5JCFsHjToEqjqqXfvyUun7oxD4= +L0eUthVnpkGsmKFAX6d+uHvX7jXsjikZBMo/rKrjiBw= +1u+XjG/2+GSQRv6EzCaWRQ== +RpR4QysGv9QPcAuUlmZB/ldqYJzcdfQBNZ1Th5SM6QO+POEY2DGleYbCe5l6UfNu +yqOVJCkAGOhqcQYBOo7yqQoTxFeI3v/YH5mpkidrW9HyNTAJRD02Ymur9pCntQEY +6P8mKfouOK1hwXy3MrrCzYZqDmfnLEVSCjSn5KIdRmc4dkXAA/1uvzDl3l1PUWtt +PRG/YRzXVD8iVR3bzEcUnllZ+cz9udLBY+J74ckAM+cZ2hSzyyfq/FybzwKXxMpO +1u+XjG/2+GSQRv6EzCaWRQ== +YnP/gQVEpWES+RQ7J7RJRlUivABjbdmquJjPlRdBH7s= +Wd7OXFQxn2HMHBqE448Pi6S8+/GBA34d3QZMmVea2Tk= +p6i8iK7HDbvmLpoFrhNyv9D601DxXdSOTjmgiyd3Nqr1byx7HtjSclWDwuzUoQV7 +m00MPwDBB9sb6xb6db18swVJnbf7H4WMvnIczhPQZ+0aVjp7owaSvIW84fWof7uc +1u+XjG/2+GSQRv6EzCaWRQ== +m5EI3+BXxkQEYKYaxORD+g/7lNafasyk405EhIyuvnNqv6n1h2jC986RGfvXjJkv +50HSHdrYmraajnW93vvabzwR6Glv3j7i9vFV1sPcZ7KgQtnqa8GX9KUqxMUlXQ3m +1u+XjG/2+GSQRv6EzCaWRQ== +gZ7BoIAABc+I8g/rYzq+f81Dacxy6eezCrTC1ekZwPEW9OSpvgIrYa0KGCOwwYXcTIWHKwChc+OzuGx8c8CYxqz7F329o74YYNCcaYwSM9V4eKMtsG8h6eORGPf5+1XV +VmVrGQo2zRokW/ZuO9bN66Yu9w4eCdqZ8p4k0NS0g/mr6t4fHQqW9//3beDBilCZmROWIZ1UbGT0bwXRWX+ibj7PTXWgbRivwGWChIIkZpWmZxWQE8b/GcSOusQIDbAt +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1u+XjG/2+GSQRv6EzCaWRQ== +iUEDlyYF71b358pIL4FKLzQOhNOG5YF9nS6QdrhVDB90W++YzeWFyqsFYbjeCsHl +3puL9F6TU32sbZzfbgCef8z8OvYCxvmTlQzeetx7rGA= +HiVf4YuHR4gw4rFUUeRvKw8qvbDmX+tfMbpiIaNfsno= +gZ7BoIAABc+I8g/rYzq+f+9HHgvBGOxBt37ysBPwI13uPk52FI6uvFjvWgHf7Jko6NUiSbwIYvUdo44a+xTir+OvHDqhK3py8D4bbLHhU7E= +1u+XjG/2+GSQRv6EzCaWRQ== +uWQGv+LShBH8u8S26pv5nPnM8ff4ZGji1vcSAAFUnfWxWXseAysF38MM+4hJ5dUZiFZbZax0zk7JjR3dSb08FV21UNIPkNYIE3W2R6DJCOc= +ltIZHHUgft++nMUXBsBki6T1wXBeAb/L2jS/24mdBRE= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69uRnj8/r8BaXEMGSBfrjESJouhp3OTlYGDThMnU5TB6K0SkOsqpVqbZDNGFz5szCw/AgnLXsS7/vstntyyZyJXLi5GL2bMqOI9UfzQHPSFIzK98I+cwwynqIM0yyc5o2IXjcbGmabsIRO4LkX3gb0k= +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvOMI4vw48TaUq4aRLYrmsXaJb+h7/y2WxDdspRfKEXnQw== +VmVrGQo2zRokW/ZuO9bN6/jFu99KJ+MquaMXJxls+pzVuGNN+Sl79RauAvg76B1wsGLU4dxJTOlUT9dMZ1w1ug== +VmVrGQo2zRokW/ZuO9bN60SggNWucpg/S6RxW0/YnaA1skwCA0ZpXLeYREWmU+BS +1u+XjG/2+GSQRv6EzCaWRQ== +c3KN//OVINDLfTVXAO/Lbk2Hd5TU1RVmBdDgvw8QHupIMpn754UzwYg885IkGmJJnOM8kM3rDIg7hTLxdJmlTw== +gZ7BoIAABc+I8g/rYzq+f+9HHgvBGOxBt37ysBPwI135+k2vlF9wrhYiCZmHN8IE +VmVrGQo2zRokW/ZuO9bN6+8Z7TpabL3QP28zOzKbdjYKmRtsBhPWippExvZOW0/LJEaf8OI42VUCkcmkYiDNBmW4Tnaa50/2shkMsiFA/ejpSEFIqAkBQ53j0BA08zWZ +VmVrGQo2zRokW/ZuO9bN69z/Qlonskd1/GLdnc2k+VvhZQyzqI/fgAtkLK8iM1Hn +1u+XjG/2+GSQRv6EzCaWRQ== +jGJZZqYXcDE83UImRHGU4lffa63XK0BuEvGrV/OrPdOAgULF7Tuf5t89bW0q2hMA +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvOvKbZ3tEv+5Q+mTyYmHi9vPAa0y2OBkMds3bGNOyphY7BSeSDajRh6Njz8SSLq+O0= +VmVrGQo2zRokW/ZuO9bN62BySiMpDH9w/XNtJREDLzDV1N7g8dL9ueDFUmh6GeUDDFVqtUWhLZ39rABIECio6GXmpVIpQAKy8dcZ2qCvhpg= +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +VmVrGQo2zRokW/ZuO9bN6+g5JNrv8rEbMLqUNj28Cdo= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69C6A2K67LgD1eJvP3N6AWwUURoCkGGIg7SORUpL6Seh8a2ykGijyqDWzo86U586lwaKy2+VvF67QGk3R/UXPiXiVzWtIisu9t+GqCz0r6aF +VmVrGQo2zRokW/ZuO9bN6xigJm8qa4WpsQUsklfpWpWMUosdOYpkkLOs84FK2jeGjVjPCXRS43orE0WYC4dCfwQQxhdDG3yLsvZlftXvV0o= +VmVrGQo2zRokW/ZuO9bN63a0R/S59cuVV4xXEauJnWRz3xaZ2HgPpWq/a72oFYZVDKhEoXsn2iHq60nHoc8QreeySx+sbJT56yXV0YT5mB8= +VmVrGQo2zRokW/ZuO9bN6z6oXkNlIbiWTEz21chkjli+NoZjhxN9ORJwLCGQEnpn +VmVrGQo2zRokW/ZuO9bN62SJgGjT/WmMIIxZ/ruMwWWisTQI+r0CB+ZPSVt0T7SS +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6/x9mVEQ39E9CmRqJ+FlnL9EqemW9t23C9OdbGoCQ8cM +VmVrGQo2zRokW/ZuO9bN64w6dpups4cXvLDqooOrnJyvr6Av+x6T4G801ox+KJHC +VmVrGQo2zRokW/ZuO9bN60tueB5yS1mXpf65ThW2BRcr02wDxH+Ue+yMjEpkVNPJk/hP4oWqVTQE7JZlCIIrmA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69Hh4k/KN5p4WHNMxzu2tx2JIw6LXMR5NzQxKIVS864yY10isQvmwGVQ2DlSTPoctMbfzw5uVWHhvpOBeLt9pKI= +VmVrGQo2zRokW/ZuO9bN6wJmwpIiDUqk/W5Xvci/WyF4XF2UWEAYoz09NC+em7kI +VmVrGQo2zRokW/ZuO9bN6+LmY8Wp+WHHBK/N/eg16Q15+pIZBGb6sScWJhvIma2k +VmVrGQo2zRokW/ZuO9bN62L/LKqacP9lunLs/sZVgAVfljLX6fxmEJSvrO4zXddcQ4NElU+FCAcEPT2EZ880Cw== +VmVrGQo2zRokW/ZuO9bN66YYIRANoQmJRNKXv2xg3pCaJdGg4CQVst48mAlOjitX +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN631Ij+DuOT66h6R4m3bVN3TwDZnJGvuPIa0DLVLfOxX/JTIYUqYRpbQ52ElbP7VAkg== +VmVrGQo2zRokW/ZuO9bN66PmE0xfRAboNx2TeC9P7J4NhzSV+m0EkNzefjR+mpgN +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6y49h0FPe0mXa/MRz9rhxplV7doJhsM1E7TfB0kuuQSh +VmVrGQo2zRokW/ZuO9bN6xwhOaVQWDpfnMF6LA0XCFstMJg50MMXLnnkQUEa/wNn +VmVrGQo2zRokW/ZuO9bN68sDl/nq5E1fo4CRxhlKiT6ewilZZCn5nRtj5+srI5B2 +VmVrGQo2zRokW/ZuO9bN6/TV8HYrXfBS0+5RislOy68JgSfzi3n359kKXZzzkBVKkRF/lY4XEsYndx7+YjTP0jz72pF5WNcrFfW3cbITPNg= +VmVrGQo2zRokW/ZuO9bN619JyKoLhHxa0i2TABYxWfIzBj4xXUmyUuFPMVlHNfolzXARazZQnnKzxz8Qgx2TFd/VDzNwQ8p8VUgiV3DidQAni8mtUG7OUvdH9rqfLaPy +VmVrGQo2zRokW/ZuO9bN6x5O5549z42ZbPZdzwy2tzg5ykzCAsDBEFgAKnqr/bsWy+wofZoh6groBJA7PTdK/0btaorXdA/sR+o+7A1E5hLNkjWbWomUfcFC39spL93x +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkllVUyblmrBXx4KTxlxVkh4bcW2se2uqmrpRH3UsBE2hQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklScjVEbWucpHFf4jnfOHB52z9dx42c56L6XC4wp2g2ruIrt2feNZhOUsfn6mmpqAU= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklOzDQU2UmHOWsnNbrLjDmizGDPlPu+SoalSQx/sN2EQIT/3N3nh5LNd2fZegmgZDo= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklS6OX30XitSf1zWXDoSBdzuvFrWI1gEiyE0eWnthSNpA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkngkTGQ8tITagcno1If0T633KRDqrO43O4ckFcdiv4j57M7qt2UPUTmNG9R+ArOTMhYo2cTmli5bm6RYaKnxaZE +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkkyr3u3tmRRdTjbXFN3bPPu9wRFkGc8MqoonARkL67arQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkngkTGQ8tITagcno1If0T633KRDqrO43O4ckFcdiv4j57M7qt2UPUTmNG9R+ArOTMhYo2cTmli5bm6RYaKnxaZE +VmVrGQo2zRokW/ZuO9bN61TKHwbNGf/2Ub0vd756U9O0Gtl0vapnf7HpdYx4U2ib +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklEcvH4r4MfmtfJUGH+dpI5pxEL8+TV2kZl/s7yEkRSBkV6R2mDWz6QJPXIhWnq/4s= +1u+XjG/2+GSQRv6EzCaWRQ== +lGY+HGJ/DlLufZHWWlj7U0N/DF45+V15HT5Nu+KAqWE= +1u+XjG/2+GSQRv6EzCaWRQ== +aH8osMxZgIfRkNShGiaQ8KeostDT+ZPsWsXljTu4fBJd1fM2igANyxWLADUf8Q/PaQu53yzgmI+X5rE72QX5HA== +Z8UsPk1Q7HtwjRd4g01ryw== +8+pJybctMsfvqVW7hafi21j617S4ZJBcnVSvovNJf2fofEHOzMbs09NxooOPyRza +wOl7lRII4ZgqRTj9M//0b3Na+XUNXsGthFHJZn+iwk9DLm8ehZxPVZsuck2aDMbz +FFgrrvXvjrcz4wZcb8tZVBfFxJJtUNj66FBgYGeUBuNe1QYojsfdMMTfmUWbDp4P +Z8UsPk1Q7HtwjRd4g01ryw== +1u+XjG/2+GSQRv6EzCaWRQ== +0yVj5tN/frSXacyX2PgyeT5E+HbZCBESO3Xe8mtOrLxt4iljR9cXdbATNOKhHq7wMxj1yRwJ7OaljkHeJlnzK3LEcWydZBw++GRPav2xpkPrnDffgdhIqRhLBkni7ydMmI2eX/TuJRps99MKfJtUAA== +1u+XjG/2+GSQRv6EzCaWRQ== +PWLW/WTodw3tqQQSBBHInw4g49GUi/PQoXrJnMOKMcv3Wp6VztQYqiLzaEcDdgSs +gZ7BoIAABc+I8g/rYzq+f/E+IDZ6NsXsUDbHValdfzb/Kruq/Vjk28atdn7C+Siu +VmVrGQo2zRokW/ZuO9bN6/8yGXI8x1DvWcxmMbVGl2/d+UnadBjumrSvrXqCFvxL/dQ1IF3GPQDGm1Za9My9dqpvvZxqoUl6zH2mT03wugk= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66QEbsrD511ragqq/ntAN9ip4Cl7MQ5ucFmM7oBrE7CwtFofMzsbhBUBYHWjnO4+Gm4RaVlUCL2nZMQ4ZlUMHk4= +gwLNi9yZl3Cgb1vmhIukcDiwvybm4po7xCREzBIXUUWMhzq1uZtLMFENJQeoM6bf +gZ7BoIAABc+I8g/rYzq+f/E+IDZ6NsXsUDbHValdfzb/Kruq/Vjk28atdn7C+Siu +VmVrGQo2zRokW/ZuO9bN6/8yGXI8x1DvWcxmMbVGl2/d+UnadBjumrSvrXqCFvxL05RBhHMkSHCThcMnvcrtBL755t4Y28qLxX95H9SOw5MIMfwwm+AX7jFrLSlPFL7z/LG23Z7Qi0ZBkt5yi3J065WfKpBp7ljnujnyRI5KxS3WLULIWKJfkqhy13iwfEXTWU5aH1Cdj3ES1NC50RTweA== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66QEbsrD511ragqq/ntAN9gn6I8xS/ec2ORcznjf8NbtWmOguhtVlMEA8/tuxJYlc+EcZYOpOz/OOJi7ykbx5GuqFl+UVDS0tpV5JPDnbAvDoPyg5AP415q+Nhiu9uo2uVlZvJcu7WWtFPCoNTx+1lGiW3wClWt7AZYCeza9v3va +VmVrGQo2zRokW/ZuO9bN63oVhYTQuQYZZogmVe/TzMw= +l2FJPs4YkAmmok1ulDRuSA== +gZ7BoIAABc+I8g/rYzq+f/E+IDZ6NsXsUDbHValdfzb/Kruq/Vjk28atdn7C+Siu +VmVrGQo2zRokW/ZuO9bN6/8yGXI8x1DvWcxmMbVGl2/d+UnadBjumrSvrXqCFvxL05RBhHMkSHCThcMnvcrtBL755t4Y28qLxX95H9SOw5NBHzoK2EBm81zr5nBdZfIqY9xIQsE1T6+1rIBc9RtxWrAgPHnUSyFwCGVIYz6lZga1UtCe6E+ND5HnA71abf4+8Mx/rNnpalgInue9MBDnlQ== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN66QEbsrD511ragqq/ntAN9gn6I8xS/ec2ORcznjf8Nbtv6k9SWAza517BnGnZCkv2xN0WdZ4ZLw0aPWE+30+q6fOg28LzTGXOUhu+Ve6HvJaj4cxoQ4JYcacpAzNMdlTCWLZM+Uptv/BbJhem6UisCnkHFC8acpX/dMFcmgzOKuIaIG9LX5bT7btboBMh7V/7w== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYmirComgoMouw9fWv+9wQLbQn3IxZ0+fHgSwwrVEz19D +lOh2GtzHjjMM8E9J40AuOWo7iSH0P5z8Y9QjLJCJBGiaE95pK7CRTqvzzc1f82PP +xWoGNWjKGPfI4gq8aHoTfMo8UWPcZxcLStbRn3vuZvrvRuO8gwYogoCldXZi6VGk +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +6d8NLnHX3WuS3g79bJvyhOcA9ux5Auf73oocslB7sDeWu0fDEoiafZo2yMYtO6chT1HFJerygYO/wT5DIOkSKA== +Z8UsPk1Q7HtwjRd4g01ryw== +JLWwEPnWK0L+CzhaEIjbQU4GwWA558QpsjOq+5nz4DgxhiCXwzz0jGRGKxh8dUTODJr+s6+Ah0R9doWb6zPLQQ== +L5bpcfZyn5Pc75WQ8ineZfusv0+lxTTZjtC4b06AF3zKUPd2R/NTwAVMY8WVnZ7X +EhfxP26BarXubxBeyusn3NfNOtb3HPBGFTHMHZMXSqvtCtihhBnDLGI6VsAxQd4V +Z8UsPk1Q7HtwjRd4g01ryw== +uTEK8Ng11d3ix2pA+DD/aXy+DXgD07P/dsANZ49KBIoQ9DDyAcW8u95okz+vDaVb +1u+XjG/2+GSQRv6EzCaWRQ== +fE3zmgbfhO9ZwEda5GgwD/RsUOXfY9pzpW9xRZl2fW0PbW4wjRnTUOggbHQB//acwA0Dv9aDYg0PNWy2YecNQfNF05hJabP1q+/C4qoAENQ= +lOh2GtzHjjMM8E9J40AuOZ7zZZM0vaCjhH3uRcP/NrM= +pVMCrZ7PAAHymZ70WROm/67KCrp/ppOHMaNTwq4h7pocNe5jswE5ov4hxkNlgfBC +VmVrGQo2zRokW/ZuO9bN63ZBl+ZLQVwzJ8aZTH8bazNsMnfZ+bm+E1rvhx3AfI2H +1u+XjG/2+GSQRv6EzCaWRQ== +CmFznH7ArXzhNsFBIGOkr5Vsg/ycHXuLeeSShlENgnA= +UbJuac0dxLiN+5ocS3w2vfHOkOj7T66O79wy+D+TgtoNfPdzdywGX8tpg28oiCYnv83pW/YxuQwN1apN//aLdw== +1u+XjG/2+GSQRv6EzCaWRQ== +ACEEdgL/kNbx7RaZcSRxZENcLifoZmLdDLAaOT7aBW7RjTwy4Q7KIvPTg8m/9qBC +VmVrGQo2zRokW/ZuO9bN62YaWBnXKkSoB0qO9pLfxSSPe4WYW+NQbuQnofC6bhb9 +J1UGlXkKhKXD36OrXXhN8PbGS4CCkC/QcSnejunC4s1HNXfKIR7pGHCoZHkqc2al9dG3/LRVuqxK1/lHGulhpw== +VmVrGQo2zRokW/ZuO9bN6/d2CLxxt41IFX+A1fbH83na68t62fa3cjMWGKFnkaMR +L0eUthVnpkGsmKFAX6d+uPmf6IqlOWYOMRLeTkcrllRN4/sFJgvvL5z1UEeHE8+W +VMfVrQporTLzVAs+KagENgnYKzmIrs4+BBVOAKxaFac= +1u+XjG/2+GSQRv6EzCaWRQ== +rKj1u6Uot58r2z9vVGpkYPs6jYzSiEQNQ0Vlqh8zH1eUMBI5lpqMlLhl/4lO3nQu +Z8UsPk1Q7HtwjRd4g01ryw== +ewqdfdhwKtr+fTAdDLdXxNoKltdD5CW97QCrf+yOioE= +Sc8e9jvbHiy5hwiy0HtAoOvGcehWBaW/6Q54bzic4ooH1vds49RieexSGCVDRfXT +1B9Eo1v10Ho41P86Qsxs+Rf3h6kOJxgKbqyJXd2SQDCZaqaZocZ40EQpaNjgDGb2 +Z8UsPk1Q7HtwjRd4g01ryw== +NmkgFF4ZsyEHu9KVT53La24afMe7Hpz+AvRb0pPLT5Y+DtSbSZQ9wXQJ9PdlRpbh +ACEEdgL/kNbx7RaZcSRxZDbs+G2PAEacg2XavjihjHkGDr+yC4TGqKna/D6MU25t +VmVrGQo2zRokW/ZuO9bN66b1EQBxDDTQrHy92SlIikM= +wlLHv6kT3Q/RmtMBN4nDAcHzCoHA7fx8ge824KyD6j2RGkLqKD0SoqmJdGVHetKh +VmVrGQo2zRokW/ZuO9bN6za89G+hm2DHEo1o7nC89kWHww92mvNp+cT0E6zNNzmXwifWbhsAF7H7xfRYH1Tcsg== +VmVrGQo2zRokW/ZuO9bN69h0W3AGoL6Fh678416+Hu2KoilysLPFqxJJbiCWg0kt +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +l2FJPs4YkAmmok1ulDRuSA== +gZ7BoIAABc+I8g/rYzq+f+vaPyTBeRc/jgIGrjGoV1I= +VmVrGQo2zRokW/ZuO9bN64Vu/H9alPnu3iLUUUBoSK+kyhoBM510sPwyNCX8Yr+ofuNO9OIf8TJMlD7EUz7RELcHLIPBGPTTiECymRVomBM= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +vF0ZftWrAGfdsoFfHqmHDJuEsO4R/FX2ga77ewUw7Y11IGYw63nwjmoZNzNj628o +Z8UsPk1Q7HtwjRd4g01ryw== +GlXLieqbP3oJrHHHl1oqk5GfuHjv+M1uY5kQiwNKOz0= +cCPEDb5F9Qd4JMGAsR7UfZfXQMkZOQ9q6AipKLgN9qA= +wzhSc3VGUoAQ+07oznSZPLPT5vU/alKRgCd2xATHoIdgjG3VPCXSpthiTEqckgdX +Z8UsPk1Q7HtwjRd4g01ryw== +rLwQ5yQ9bNuG44la3oQ+3afvJa6cgkEkDaPubWhiBa8= +2/eGB4mNeTC2Lw/I1/B9jHReQG3Sf6fhE0IH64g/YfeE/Ws6fbXaJX/LLa/JASMj +9L3jIB734YKv4S9AqEY35Kh1jnwS0FJtLaDJkqWU7Zs= +J1UGlXkKhKXD36OrXXhN8A/6KQQIEyEAY+cUx85urfskVuM/oTrnEBFsI3zXMuwQtyvGFxwsnrTAlyx/0hytcw== +VmVrGQo2zRokW/ZuO9bN68dKk9G97rhJCB3VEv0S3qjWifbCFeCN1sSryrl3vtTYdLbCLZieWWRVdoajo7IOpA== +VmVrGQo2zRokW/ZuO9bN69r44+V6JJX/2NLSGlXYr1YD313HJ7qeRebm2/AdMhL11t96l/ZN3bscCjY1669x54LQrad8RoESQEFGNJ7tr8I= +VmVrGQo2zRokW/ZuO9bN62MvivPt6ucjDY6Y4UwXjRGX+MgrdU7vOEwe6NO1KSXK +VmVrGQo2zRokW/ZuO9bN60Sd7dFtgik0J339YqGp+LqOWzrYRhuwK3n/DwPxquLs+N36KFtUHhWKI9VFqVraYlnKaqKXuvwu19LED1GmG2Kkna5Ve0z1QySEyTbnYwpj3mq3A5LUCr6VTgwESp9FYrceQzV/js0BIa16dwTapHE06PsyAeRntvguLENP7jvaYu2A79wYGT81tc7XklvAEdronju9kPx+7a+fzFUwV5U= +VmVrGQo2zRokW/ZuO9bN62MvivPt6ucjDY6Y4UwXjRGX+MgrdU7vOEwe6NO1KSXK +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN69r44+V6JJX/2NLSGlXYr1Z5XOqt9InPZIx/uA7fZePS +VmVrGQo2zRokW/ZuO9bN62MvivPt6ucjDY6Y4UwXjRGX+MgrdU7vOEwe6NO1KSXK +1u+XjG/2+GSQRv6EzCaWRQ== +sVwPh1y7MInnwEDK61kL2ZgurqcOtElG452IbPDSARaEvm4o7F6Vljqg3s4Vb98N +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +WjK+gflGKYbL+4lozgCawEfi7RDXlvnIiPUmGf6XyPurYR5IRGH6Jhoi1u8dhC6C +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY3e5Z5xEtbMc4JrlUL6rftaZUmLPHCH16nEnJiDRycKT +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQG9CJ/mCl9wdy9aVT0qKaa8 +7JQQDTi0T2idhFjao4jEpdluONpwOUUVh7y/o1Lzsm4a2FL3SufObmrWF3NyQ11G7uUUREDf5s1H4nVGAyEIiQ== +h2Amc56dmaRo3inGZxN9N0Mh3da6J2tf8CU4p/n8Ca2A7uHSizxBYlbR4VDvVrH8 +KZTmaJLp+FU9X93j5Tpqtw== +W08jtqcK2gqkconJfg+WHLJlYDaZnYglU+gPmeaeJz0dXPQc4GmpJ18lTWeF8Jvc +IsslvnYVqGkrqD0qU5QGYf5J/O0JjEzsRDI+7c68kAPbhQeof0lnZloVKmPJ0/OPwERg1AgDmZ5jUeNQ6jfaldScBwiDIsOdWtI9+ChcDxI= +L0eUthVnpkGsmKFAX6d+uK14dMm1+GhG8c2gpiVyR+E1Z2/jkCwp6Os25PRP8RSk +IsslvnYVqGkrqD0qU5QGYbCFSj6VHiK8jGEp4Z7SGzCVaeuBljBtpL3ckAqxImzQ1d5MvnFkYYWoGsfPJ/WPv8swi2RH+jUjIGOKkl3usaE= +L0eUthVnpkGsmKFAX6d+uH51QgM5kWHywxiE6Ty0F6BCQoJXs5zaJJqYGuXoD81B +IsslvnYVqGkrqD0qU5QGYcjXjwdbef/ruiHWGoRx3+tWAsJVYvcBpbkV5AJPt5MOcPZrXMgXPIOVr93qjXogTtDc9vraJd/N0pYe5+Z4eNM= +L0eUthVnpkGsmKFAX6d+uM+i6uIzzgNIsrvcJ0xQsTE06WRnqMHo4Q1g/bkpbg6d +IsslvnYVqGkrqD0qU5QGYbP084sfOENgLhN/XMGReZg0zyyRimxRdtJtSm6Sqdya +L0eUthVnpkGsmKFAX6d+uDQtcaiDMT/if2pHlZtAF6w7EuP6p/BouaL69QDJbMlX +IsslvnYVqGkrqD0qU5QGYbrfmZ+lxyGmJzabndV+bycc7U0Wl5SH5mS4H3CFQFF5 +L0eUthVnpkGsmKFAX6d+uEpYxmDfNJv/38PnR3IuvUzG7TueShUPZjSOSr1mkV90 +IsslvnYVqGkrqD0qU5QGYdNGxot71haXjS99OqNqqQ074sGxXHCxg0T7gUwpV061 +L0eUthVnpkGsmKFAX6d+uBEBFHibxFuwHX5Xun9xifvXFUxIe1hqpBHRh1Lw72g9 +IsslvnYVqGkrqD0qU5QGYacx+GIk+O8p/nDetEHOBxNO8G66dVdD3XUsN1D4L+DX +L0eUthVnpkGsmKFAX6d+uDKFjFlH66scjoKkr0HS3PwzqDQpIUplT5Z0WdNifCR8 +IsslvnYVqGkrqD0qU5QGYTZbaTDAr7WzW3U05y7JjCJE+JWxxlyG4/oGHZQRzxpM +L0eUthVnpkGsmKFAX6d+uKR3GJL+5DYQD9ef2JvARGDOf7TUitZF3uJDvhf4SXd6 +IsslvnYVqGkrqD0qU5QGYT4PypAZWVuTa3yx/tngA8BLwrQemYJ/HPN/7QEqxYRG +L0eUthVnpkGsmKFAX6d+uOhvQuK51ItTe89peTAEMXOQ5smjp/DG2mf1guds/omG +IsslvnYVqGkrqD0qU5QGYRnGdaaoC1M1g7ZAadH1EZcM7x8K1ujx/VKQzwSiV3gl +L0eUthVnpkGsmKFAX6d+uJ+E0oqu/x++zQJcSBL0VOekQHmZVACJJPl8TTRzVI8m9M3y+IGr3/7RTyRMpjpcig== +IsslvnYVqGkrqD0qU5QGYZTJr0cGLieJcZu1a1iY/RUTgK39OfOzDT+/AdeI2wCL +L0eUthVnpkGsmKFAX6d+uH07G4h7QHuhkW15eHMk0eA= +IsslvnYVqGkrqD0qU5QGYQn8o/Js3mP8t+fBdcR0TgAwd00zhld6rOJZQ6+yFxbe +L0eUthVnpkGsmKFAX6d+uIXRRpClQCuJqr/OY3HUOQs= +IsslvnYVqGkrqD0qU5QGYQheScjm8xnwil+c9CUIBAIfDVr4aEwxf02axHs55dTj +L0eUthVnpkGsmKFAX6d+uAX3W7tCeHuM0iu6taGk0Ik= +IsslvnYVqGkrqD0qU5QGYUyoMIjJLmAF5ojMLgMpyaE41ZKkM8Gjl3APdZx0Hj59 +L0eUthVnpkGsmKFAX6d+uEmIBdMpCCkqkvpD1pJK/Fc= +IsslvnYVqGkrqD0qU5QGYRNYwVLAovvSjrixyYk8aT36EarzpP9AgYTB5zNgeLWC +L0eUthVnpkGsmKFAX6d+uJfb3o2MxlDhM0IFL0qo2QevpUkPYKMcpvvjGL4e+KUJ +IsslvnYVqGkrqD0qU5QGYXSqTc4qTS7hPO4iPJv++5lyNhEn9/t3xBtXLeIofwMp +L0eUthVnpkGsmKFAX6d+uLOTVM8QXQomM/ScmIi0Aurqqbx0I74fDjhRNAJFYEPJ3pYwtdrIJSDHigUgbAk2dg== +IsslvnYVqGkrqD0qU5QGYXG6NlsXd/YqSaoeZGsOge0O+VwQiRXmNu6YtlNYN82K +L0eUthVnpkGsmKFAX6d+uK/VpHdm7KMAlOrmSjRBOrc= +IsslvnYVqGkrqD0qU5QGYU5Lk4PmA/HmYwjmHWabZ6hHV55xwbkRmwNx/z168Oti +L0eUthVnpkGsmKFAX6d+uHf7H9Z69Fy6EtLuPiv9GmjWSmDXIF8makwzDG3As2Xu +IsslvnYVqGkrqD0qU5QGYZszVUnULbYAsg6SI1v40Ie3ai1tISljWyUaHEuTyTeq +L0eUthVnpkGsmKFAX6d+uOMCsU8cjJLqb/iyzhRPI/ckwr99ZMBbGMnD7MK89CzJiEYNWtqeqQk216KxbOgGvQ== +IsslvnYVqGkrqD0qU5QGYQAmKpTYece2KpTt3d1NBCNYX8Ap/8fRHVUzkhz0ko4H +L0eUthVnpkGsmKFAX6d+uG8+ZBh4qlZRJvEzt5lBDV+PtTrj7mpKSjT2XGDtk0Vh +4hdB7RJambPsQ0dtPl5R2akq0BODLVZotsDNh2wSunpksKRZQGda2QfFL3yC7r9FNUGWj4cTVasPp4knqjntXg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +21/52BX1a38vl6/d4QTVP0hoUi6PwJEXFQ6wphCzPr6fXDo3Z5Vg+a7TcMA5dHOO +KZTmaJLp+FU9X93j5Tpqtw== +NNh1PlSD8WUnUEbz+BU/P/pyukZf+uIGpurCH+0HOPz4Yr/Z3jIpQQmOXWHeoYrG +ZtsV8vaHEXutlQxVeZ819plsRMEMfUI0BG6ieJYZQ0SZ5n31ETTM1BuyD7ejcndH +KZTmaJLp+FU9X93j5Tpqtw== +W08jtqcK2gqkconJfg+WHMWCpDQbDVBXW3mpkNRn3lIXAExnTItYo1kq5iRG4GcR +1u+XjG/2+GSQRv6EzCaWRQ== +CTVkyTnZxhWMnQdl2TPfNtZypvunYlImqx0NqMHaPXRBRXlCxO+cXNezSDuvkCTx +TADZGw0D6PLYGUHKmgeZL4HNEC8FTcIShYtTIXIxyo+0tdoxkJP4HbuHSul3u35j +topgAunkM89n84cH9D9pHAfyyGx8htIfqZCXE8ucjJj/OUSO2V7ozSUs83ljlvy2 +O3CUgrw2GJfB+mDjH5+NdkkEd9P97hGK1RTeRfwhysCroE1sFZzj1cl2Wj01k4MM +VmVrGQo2zRokW/ZuO9bN65x96E+pC4TupOHurj34PBY= +topgAunkM89n84cH9D9pHKjRNdYyRqpYiqgVsH37hnqzQCcFnmPRP7x+3/JqeKZ+haxhWTIMsEjUYMkHZbb60w== +1u+XjG/2+GSQRv6EzCaWRQ== +YcIT2RppFBANuzyiUKo/XbqbC0hnpSADZ2Xd7Kwfuo91ygsOr2hFjcoPoo/9iE1B +mEOKzB0uJs35kZrdrKxIkCclaJRcU34BXh5nvgXkUmhXqzXNaToiOYCIt8Xptl1d +W/Rdcl/m9EziNRa9XlvfZH5kAFMc6PQ4eF7Rsh/gbQLJ9bIjLmL0JAuq36SAsgMj +W/Rdcl/m9EziNRa9XlvfZCdb6qvEMYvH5wnxbbWCd1+DR0Cf3qqNnJRCqdPpXwVK +L0eUthVnpkGsmKFAX6d+uMzHH+Rw5vYjEEqS9kb+ft733Qn8E+MZcbFtFKTonvDe +YcIT2RppFBANuzyiUKo/XfLqIAjoBYCaWlb87AUyhxA= +mEOKzB0uJs35kZrdrKxIkAwLXKX1c/aY0fGJeKgMKfhPtxAt6BH1o9t+1mdRbz8z +W/Rdcl/m9EziNRa9XlvfZH5kAFMc6PQ4eF7Rsh/gbQKgUj6yNAchV8t2eLfKtDtG +W/Rdcl/m9EziNRa9XlvfZCdb6qvEMYvH5wnxbbWCd1+DR0Cf3qqNnJRCqdPpXwVK +L0eUthVnpkGsmKFAX6d+uPK1shPIFD6x9kjjo2oaS3fd8Xa6knSu/TMUZumMZllv +lGY+HGJ/DlLufZHWWlj7U5YwItAjxeXXjqfky42+km8= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +QdTjTxbShl+8mrARISGoAcUhgZRi1ym8p5oZAX1ZOiFX408x5lL2opLCXVnt/vA2 +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY933z5nYV5e5Vw9OGrr8WfRmBaVCVOfV3FtWbmWGq0nhK9Hbx3/RcbihQUwUjoECJw== +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +QeR/vJltSrUa36sdgLk/IfL4q9jp7Kh5fPIA7nHg0hc= +f1uTQJeAsYOuzXSplbSkIbPpvJXETldnXXS37p1dj3s= +Ugk5+soYHCBsUKBc634TSP5vAwrOjcBahLrsDvmb5FWZu1d3ORGf9BCh0nq1o7jN +ACEEdgL/kNbx7RaZcSRxZIZ7jqIFcKvTxbeHCZUGxKDJtaY2uw5Wg70BfPJBD9aWShCUnQQRQXXWR+PHAmREhq/pitnanpldXtISZM6BIHXSJKcThqhLjhdomc7tD9FE0gFGYn2hL/fyorY87ERXwg== +1u+XjG/2+GSQRv6EzCaWRQ== +PRG/YRzXVD8iVR3bzEcUnoHre2ym25NbVgAhCcm2A1hoiCeOLVi33wtvGBo3MOPE2Cb54XyHCFdK3+BCNa1Dpg== +O3CUgrw2GJfB+mDjH5+NdiRXh8cEweT1cRWwOW9QX5fh6Yaj08E7nEo3hPidINlo +VmVrGQo2zRokW/ZuO9bN65b6RdMyfA4zumGrUHCFg65RTBrSUTABEpsq9PJ4JCel +VmVrGQo2zRokW/ZuO9bN6w9tUPRz4/J0Cgrs+FV7Tr/YcWK0KgvLa4A8Zh6i8yg+uRoNBCckO5193pFh3dc5mA== +VmVrGQo2zRokW/ZuO9bN6+YGqEY/sG/2DL6NHBGgZFPn8HU0P0VRGqmcpSfpPR9W +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61YlA8R/kneqHpa4ue21nrXaE+mh5Y+vq6iFNzk+uuvg +VmVrGQo2zRokW/ZuO9bN69Ej+WUzR36sZsG0nGLhsTjFUTA171Py2jCOZGaWjhVUzTwgwMBDdiIDiZBt0M49pQ== +VmVrGQo2zRokW/ZuO9bN62oRed6hCMosT7UP35UqsiJ+neIey4O/p83yxE/Xz8Vq7FTWnyXgWBqC2Ofg/IQMLA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xv6VKf5v25Gv35Ngxt7+SxexyFaNW8qsenwXUokPM6b +VmVrGQo2zRokW/ZuO9bN64a61Rpqu4F50qZSKzf6C34rN8pqiwxpLduFB9njxLiv +VmVrGQo2zRokW/ZuO9bN6xO985T9pwQ7sN4pEaMuKfefQO3H0FO740MZBVb4uwOf +VmVrGQo2zRokW/ZuO9bN6xr/qvm+5ucCO1hkB5N9exATtIjyk7lQ3ExTkIcU12jOt9fONgTPPb03RIYeiIzThg== +VmVrGQo2zRokW/ZuO9bN69SQQTccEgrQxwYplaazClQ6D2QNKN0egHCbyag6gxMo/CPqg8bW9jLetQduGzVa8g== +VmVrGQo2zRokW/ZuO9bN6+dlyOY32uq88jvIbgRzKiD+c9puldgGvnigwOeCT81wJWkhhBMtEqqc8b+vZxO8tQ== +VmVrGQo2zRokW/ZuO9bN6/bbff3RPIfdJD9Tz8nKTkS8AWh7qG4PAFWqinn3GS+B +VmVrGQo2zRokW/ZuO9bN68cDhwAKTM2Cy3z7y8RSN78= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6463nYA6JOgSgcM3CVjNTJznMLYNsYHB/6aPvDLy4Q80 +VmVrGQo2zRokW/ZuO9bN6xv6VKf5v25Gv35Ngxt7+Sx0bNvv17oN8olvRoRNFEX7V2BUAqQk1w9AG+fHfA3mjg== +VmVrGQo2zRokW/ZuO9bN69dQDCddUjZfqhLWgtMTA+6lr1BUsQBWcHEfSvRE3879 +VmVrGQo2zRokW/ZuO9bN659sVHCRNKwi5EybWXGjIFHDG+wKjWhy7PLQCmhQOSV4291MUSOlSEESLCcvl7/Fng== +VmVrGQo2zRokW/ZuO9bN62+mfM1KxuYR9GMEOHSn12FMvAE6TgNv6jSr3+eIjIkftGBTCi5VuuIi6ta2B1ahrhcNQtCYZ8yLHfkFzehQCbU= +VmVrGQo2zRokW/ZuO9bN68xhA6nn3nm5MXR5GCHMXtcxC3eMgYv7+sQYRGoRMUKJ/NtYdiS6ZTF06NLLhQeZ3Q== +VmVrGQo2zRokW/ZuO9bN68M2OVEeH8lXEDh7ocUvFZ4= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN60ntial7L47tWO4rV4Icn7HpBltblYWBCieqT4UVgq9CFSyUKzN/U7U1y808EhW5Qw== +VmVrGQo2zRokW/ZuO9bN62SJgGjT/WmMIIxZ/ruMwWUGgOzwXkp7Yl61rDQTwCRFc6zTS2NxH91SNGo4fvq8c0/QMcscIcl8fl2DAYz9BEM= +VmVrGQo2zRokW/ZuO9bN62oRed6hCMosT7UP35UqsiK29Hs7QixVm5i7kdWxory/eb/Qqnm5/jUpYwUUUkLhDA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+6X3Z2WgPscDVJea7oFkkBvFOwA8ZIliXzYpwCO5w8mCjx0uV1YsGYftfAWvGpnZgdXDsovKcS9qBn/5dy9/jU= +VmVrGQo2zRokW/ZuO9bN60TFHropKQybmgi/Ewhnmc7xtekqzThyNJcyQykKtAL8 +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN60TFHropKQybmgi/Ewhnmc7PyGF61LfQUSq+4E3rbm1p +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yOPAnkhPEHUxQR8wIbPiwSTWlEsAD2rF3bnj5NhAv9XbaRnFLHata2faDtCqOdjdb1U7MCISF8/6F97G7mquUM= +VmVrGQo2zRokW/ZuO9bN6w3mLqzdYJP4YnG22gQyoLmoBk43np2N2P04jX+kASBb9gClLRXQLQzop3WjvHjIKKMsPzTJmkWildPSC3LXw1E= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6y7h5D3hwYRtgfvir5w8hacLALFT2aKPT+72WX0CphB7 +VmVrGQo2zRokW/ZuO9bN69dQDCddUjZfqhLWgtMTA+7x3DyGBidwiLe8ycxBOOYA +VmVrGQo2zRokW/ZuO9bN68xhA6nn3nm5MXR5GCHMXtfY9183Ko8rfO9qF7oaQasTgQyzsj3jWJAdaiSjnzCWMw== +VmVrGQo2zRokW/ZuO9bN659sVHCRNKwi5EybWXGjIFHDG+wKjWhy7PLQCmhQOSV4ZrV+dIrpw07fAyDbieibNw== +VmVrGQo2zRokW/ZuO9bN64E7kV0oe1KNCWtfO+2IzG2FPrJbLwlL61MXY6h19yCgwlN05/jodOhk+oUWPJqcNN6IBI7oumAfwTtn3AERBME= +VmVrGQo2zRokW/ZuO9bN62+mfM1KxuYR9GMEOHSn12FMvAE6TgNv6jSr3+eIjIkfOEwcPjcDLjN9tD0xweBslcAkKKcfqK+vUKrRm3X7Nl4= +VmVrGQo2zRokW/ZuO9bN64OojIhoKNJ1F73QGw4VuTsSq1t0rpx7OXXkl8uQ30KM +VmVrGQo2zRokW/ZuO9bN69lJPu1G3iX01kmj1/0ynPc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN60NuI+3rwmZPRXBatW5je5ashCp3DeAz4mwJFRsD2uIz +VmVrGQo2zRokW/ZuO9bN6y7h5D3hwYRtgfvir5w8hacofEZFxYSxFBNP4nrNMDKlU4v51PkdjGnN4kullijdzQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn3k4igWZGKmftHXEMWcn7kly21b/nYMgSJlcJhkvDi9w== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkliTSPh1fvkS/ScWR2yLc4+HTPJwBf81Hy/L5fQgnRrH7bNCsflDO0k2jj/2A1Vbck= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn84mKwOupbY4jKHqhGVSsSRWvM73UP+iiVN5SxtdLKWMg4JgXS/q/83v0PECO1Oxw= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklAMkk+cPHwsBtRXYD12TtxwEYLEKXTLpcd7/0JsjoVdw== +VmVrGQo2zRokW/ZuO9bN66XK99UTYPeH5P9OwsB1SDc= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +h9U6jl4cZ4C22C1wpKu8ve1yUcQ+dIn1vMmQcAZ0Z0w= +FBy3vCcAP87b/+M8ydgUJRF52I47z/TZBzA6Bymagn0= +xOhAHwpsatV+hYHn6t6A6bUPiUFFzlk01p4yvWK50jyy3U69zJPFTOkEngdeeh4dV2ZsmN/celU41tVeKyZCPKR9o4ij9I2DDit4hQ8yk8OvD6qwHkCfsNW43rtkacx8 +topgAunkM89n84cH9D9pHBHJGKogzkmjYZitDzWAv+GuAYoOOkSChVYahktCovv+ +h9U6jl4cZ4C22C1wpKu8vZOLikErPbvUJn108FkBgu9+60+OG57l8ccneiDXTrwOoz7y2s/Cah0Aj4t1RxHPgznaDgjVKZs3ALXpKjeJ0vo= +lGY+HGJ/DlLufZHWWlj7U6go50WaXphLgme8Jt6GCTs= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +IE+9Uft2qvVuoU6zfHciG3wzRZH5DHb0iYqeBG8ShAk= +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY1lJUaDlPvjJcgqNTD10CNjS+JFlHmmzAW7C9TIFGPZZ +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +2A27FrJel7LwQVQbnj4q4hlgih3qBk24vCQh3ZEzZH1D9Te7T5q1mDbPLZp0o0v6 +wXC0PXkDt+//2q1Gfu8pw1omD2s1DPC9/eHWLru0T7M= +/0ULpLqgTvInFD0r5hHANlt/A4HN48BaWWKSY1CtQvClxZHrVHBtcJRLbba3CP1w +9kMYV6mizqMbt4N+qTZtN4zB76eoGATkjA0lU5cQ2Ak= +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +Nxw10FAZUk+m0nadC1hdy7a2+IPpKTLzQ6uOO+E1Dbs= +X/Lp4PsbbXulceSXUvGUpEwg42OTOSx1VxHM46BBNX0= +s2s/1ED0ebJ2NmFlE8kk02x6FisgSkWfiVYBaeNNhGvko7bwZ1FD5aZ3vGnu9kSm +1u+XjG/2+GSQRv6EzCaWRQ== +p+qW8e8p1zrxOyU05vOA0oTKH5zqNz+PCgf62f9tApk= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P3lRQWSpZzKNexozsWlQSmt +FFvLZRifY4ofMFjBuFasFxWXCYrK9vqCc5dq5VE0iw6DSlYwafvxu5DYwNYrMthu +1V2v8QerKOmubvSxgB4eTBZTEixtIaCeLifMkliDnl1FSzgrzmlMEyvFnWK3RGd/ +J1UGlXkKhKXD36OrXXhN8BVlTjNvt2FG2CiPqdl199JaWrFGr3c4jWvQXS12vU6gFbGt23o5BdLlHOVX3j/o5Q== +1u+XjG/2+GSQRv6EzCaWRQ== +2J/cJdNHXZ2Qsa37Fo6/Z/gM9h4l3NzxQe1VHPUoNCj50z035sIRNuNbaAptxn44 +0aOe1PRcA3iTQeyd7fu/UT3OH8TgnUZIetc0kIThlMY= +V79F1HDTm8z6yGnQm/hDJr2dkX3jv5pKaOjk74dknTfkNo8du33ifDa8c3SEgcfl +mWtqqlZvPd5u+MDOxVnfq7x81G9ia1eZdDpax7kDimPICwqRAAp4SD95uu4DP+3q +ftwgBqNbwagslv/gBU4tpEE5GDJfsuYmSwXmJa3+o1fA58g3B92Kp8T+2Ht6OVkf +K/3TjoKXtlVKQEXdtPO0uifsTNFU0eFt8+2eD6sIbn2PmRNN2I5GHAjvsDqrxu3awe7s1IGxmpgEyF5PTdi4Wg== +VmVrGQo2zRokW/ZuO9bN6xz5iGlq9bNMsveN3G8E1JikmXOYVXY7T1MoO8bJd/lt +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P0b5qliwag+wNT+s/UEtf4f +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P3vHLK4yXJh/Y+S+1XhH9rt +1u+XjG/2+GSQRv6EzCaWRQ== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6xz5iGlq9bNMsveN3G8E1JisN1fUkIMjwSqaHMkv+2OFIPr5q4vWGJVqp1MZusIqwg== +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P1Oxzn1tUamy4I7kxYIUhQU3NM1tr9mAx/yIud930NXFA== +VmVrGQo2zRokW/ZuO9bN664XPSepA7GnWfbAIyL55P2Ei14aMB20i0YI/RCMc9vC587JhNV12B308q8WzisouLfqdSseWY0+wqtG9K74mqs= +1u+XjG/2+GSQRv6EzCaWRQ== +1D18KWr2hdVsBcdZx1OaPfvwrE7g7+awLCd9GHCg8UHyIrzitpsEab7Put5dPSX9 +VmVrGQo2zRokW/ZuO9bN654Optg5TGZB1gMGxxgScexFS1RR1Ks0UsELmW81E6uK +1u+XjG/2+GSQRv6EzCaWRQ== +o1bT2zrSxs12aPyNKpukk+RBjtOXgYwyhl9BlbuoPII= +9kMYV6mizqMbt4N+qTZtN73ymQjbbWCfVaP1lIPcs1OU1AQgg6+83fISm2I1XX/N50H2FN/cmR/jUdb8rfxdgjyYdsNSEsociL+R9aPXHUMPalIxO0ZQEAVocqvn4o/N +9zgnmC3+5N9XK5/NRaR4SIX5nH4iJWB9r8DAPkHCqMUc7KHT6GbFzm1/ZZDfEDnV +ACEEdgL/kNbx7RaZcSRxZNCtRTkFUngsUPEeQy/+UyWj7cI78bO+VYj4wVqG+FraCRyNCLzTSU5fw732DiiY4Bs6NS4JZHrotgQbzgu/NHg0XC2ZDt2Tdq3I1jVs24bycirX7+k1becWKOkHHvZ60ZXv1saJ5rczadDgDE9Qj+k= +lGY+HGJ/DlLufZHWWlj7UzT71dQgCsiwDOluCIjxD+k= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +IE+9Uft2qvVuoU6zfHciG5u6HyMWvuiuseR1J2M4pfs= +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY++vWRAhgONxDuLxgRuRo4NAs9c+F3KZcSg+B4cy6yyb +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +2A27FrJel7LwQVQbnj4q4hlgih3qBk24vCQh3ZEzZH1D9Te7T5q1mDbPLZp0o0v6 +/p6t8/yEODj2SpyYrFogNPdnJ598mYDI9OQG3SKrs2s0xEMb9jmts3FpPIdSfwUlsOkZH7GoGPHgLihjl0yD5koXY2E5+OCPEh6lFcT5FWGdmJWettOxpcoMW5CbJ+ym +/0ULpLqgTvInFD0r5hHANlt/A4HN48BaWWKSY1CtQvClxZHrVHBtcJRLbba3CP1w +9kMYV6mizqMbt4N+qTZtN4zB76eoGATkjA0lU5cQ2Ak= +1u+XjG/2+GSQRv6EzCaWRQ== +Nxw10FAZUk+m0nadC1hdy7a2+IPpKTLzQ6uOO+E1Dbs= +X/Lp4PsbbXulceSXUvGUpEwg42OTOSx1VxHM46BBNX0= +s2s/1ED0ebJ2NmFlE8kk02x6FisgSkWfiVYBaeNNhGvko7bwZ1FD5aZ3vGnu9kSm +1u+XjG/2+GSQRv6EzCaWRQ== +y+v+I4rQ8oQ+Y894tAwqPQ== +blmlG6qfvRPckY4P30JkN6n0giAGqVejsbTuRPj0rAA= +NnzeuyTPUXJo3DzO+hKDlpdmpF8keGQCo3mkfAR/FtjeJLOfkOWLB3iDATKP54pf2G2pJLJiXme7qXzDE1IPMA== +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YIReLG3rs8BVbRSwONMnP/2 +1u+XjG/2+GSQRv6EzCaWRQ== +p+qW8e8p1zrxOyU05vOA0oTKH5zqNz+PCgf62f9tApk= +G/hjuaHIfOdhg/DGAsPhcPOPTRXDiI3ZJx/D3q3b4P3lRQWSpZzKNexozsWlQSmt +1u+XjG/2+GSQRv6EzCaWRQ== +FFvLZRifY4ofMFjBuFasFxWXCYrK9vqCc5dq5VE0iw6DSlYwafvxu5DYwNYrMthu +1V2v8QerKOmubvSxgB4eTBZTEixtIaCeLifMkliDnl1FSzgrzmlMEyvFnWK3RGd/ +J1UGlXkKhKXD36OrXXhN8BVlTjNvt2FG2CiPqdl199JaWrFGr3c4jWvQXS12vU6gFbGt23o5BdLlHOVX3j/o5Q== +1u+XjG/2+GSQRv6EzCaWRQ== +2J/cJdNHXZ2Qsa37Fo6/Z/gM9h4l3NzxQe1VHPUoNCj50z035sIRNuNbaAptxn44 +0aOe1PRcA3iTQeyd7fu/UT3OH8TgnUZIetc0kIThlMY= +V79F1HDTm8z6yGnQm/hDJr2dkX3jv5pKaOjk74dknTfkNo8du33ifDa8c3SEgcfl +mWtqqlZvPd5u+MDOxVnfq7x81G9ia1eZdDpax7kDimPICwqRAAp4SD95uu4DP+3q +J1UGlXkKhKXD36OrXXhN8IJbvphSi+7vstAaJ7toEgm4zZMxFe/rKS4u9y2Ssvzh +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAcBxCAXlvfzQFBJgoPBTJbj +VmVrGQo2zRokW/ZuO9bN67iVlFAU8hAYMafjpVDx4oOy1GxC/YZz9MGGC5hXHifDIzzMbooqVq803bDkm/ITJg== +VmVrGQo2zRokW/ZuO9bN66Bl6UaWMaQpzwFzTULo/tY6jlTwP4gsV1QVwm+H2RE+ygziQ7qyYUv2jZGrZghIXhaI9IfXLM4+/7ZEaTQK0+JudXe43jFBuxq9uaQVanpR +F9Sftf1PoN5P+lgvc+r10DzCXpZBs2+NQLZrUg4+WkxCye8nWiOXw8mDAYUsbMktOZPiLEpeqFuRtHXGZ2OIbA== +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAeRwlwRNQErNfyGxNziVpfT +VmVrGQo2zRokW/ZuO9bN67iVlFAU8hAYMafjpVDx4oOy1GxC/YZz9MGGC5hXHifDCkvImVt+p1wORLo6xFoArA== +VmVrGQo2zRokW/ZuO9bN6xYoLnmVOS/ahrj5uuRdV0EWsnEizWRxlOrvM8wENlN4 +VmVrGQo2zRokW/ZuO9bN65e0KdmCuDXHyRDbH0mrOLKZuncKdgINFZSmlvo5RFWwygn3vSbFEX3KrOzxXciCHQemoYWznnH0DlDnPaok5SJghk4Gvzt1lP8Nd8xypH1gdpU29AuUlNmUasQNQYvk1Q== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65e0KdmCuDXHyRDbH0mrOLKZuncKdgINFZSmlvo5RFWwygn3vSbFEX3KrOzxXciCHQemoYWznnH0DlDnPaok5SLNfnGfLb1wbPgdBMy9o4VF+look+gYRrvZgUaelLl4lQ== +F9Sftf1PoN5P+lgvc+r10D7o64ZLbi78S/TgPxw/f4SOC++uRT8tIfgwb8Omo7vu6chE1yw4OMlsbZQVGt7v1YdICPBImfmbQa8l34QUTWP5nPueh8DOOl+YjipCgyfiah1QuO33ZZN9u1isdHNgaw== +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAdgqSKLQvYqIyYU7AJpR7fK +VmVrGQo2zRokW/ZuO9bN65X+HRZP26yr5JJcJjbroHgY2yQZCC2iz/EentJIOM14 +VmVrGQo2zRokW/ZuO9bN66Bl6UaWMaQpzwFzTULo/tY6jlTwP4gsV1QVwm+H2RE+U2dhdllU017qeNlwV/t3qMxoN3b6ZEwtypHeRVok5QLeq7jBhGNGeUsnEKGRoGiN +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN624P9C6dpnhq+uflbVX/QAebii1mp4p3I7SDA1b7UujH +VmVrGQo2zRokW/ZuO9bN65X+HRZP26yr5JJcJjbroHgY2yQZCC2iz/EentJIOM14 +VmVrGQo2zRokW/ZuO9bN66Bl6UaWMaQpzwFzTULo/tY6jlTwP4gsV1QVwm+H2RE+0ESEOFCVWY3zpdLKkxogIjfbS+mp5BcT25dPwPqol/ewrCICKac8rraGy87WhRhdSJ6N6R7PX4AbXUWbAtjbLA== +1D18KWr2hdVsBcdZx1OaPfvwrE7g7+awLCd9GHCg8UHyIrzitpsEab7Put5dPSX9 +VmVrGQo2zRokW/ZuO9bN654Optg5TGZB1gMGxxgScexFS1RR1Ks0UsELmW81E6uK +o1bT2zrSxs12aPyNKpukk+RBjtOXgYwyhl9BlbuoPII= +axUGGc1VN8059OpAvKXjQWop4g5wEilwDML73fTR0aROShh1A/PpsX89P62Fxwd4hZKM+zVAPfmRsWcP03u9Ku3VD03o8PL+LDYwCELmIUqQ9SwDTtOHpMHkHqyUOUm1 +lGY+HGJ/DlLufZHWWlj7UzT71dQgCsiwDOluCIjxD+k= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +OtFYf3L1SCFz3eV8uxxLQDcWdIw+tWys4K3uAhWrSOIHz+Hf+pmJOLXaeG2mKqnJ +Z8UsPk1Q7HtwjRd4g01ryw== +x1Dv0cSaQmavN9ft+ex9jCSwM0+ru2NYHY8G/siatT0= +Z8UsPk1Q7HtwjRd4g01ryw== +b4OJVZe8QyIpjuTpKXDL9A== +L0eUthVnpkGsmKFAX6d+uCuT47MPwULi2lpyJh4ryFDW9qwV9g5joBbVHwA9hMBSw4eJiwzIZ2bMkcNfWxzADeutkGRiY7X7EdjbySNl+rA= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN67Z4GyvH1s+PEZuudjntZcEEAEOgkqKv/lySCowLggPbbCf4J73tOz2c1J7SATG4GzNyDiMvQ17bky1qOfBwo79dgSMSwZ1eu3+WvIgk3sqo +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN64mARHpcJLoYD+9rQ7Ln85U= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +QdTjTxbShl+8mrARISGoAbKd/0Om5LDKQaniNR51X/Q= +KZTmaJLp+FU9X93j5Tpqtw== +hpr9H7QXrORqNT7P49jOY6IrtSknVO/GcflctAZsVFrYCUa5MPG1XS1Xvxi+13Qa +YThduOXrtrzATLnwayQWnYFkaOF5vtHROqD7i+WyeQF+2C5kHzeTIKqA+x7p3Smg +7JQQDTi0T2idhFjao4jEpc6eTmK7gx2xmUXSHS2TmYl+CMUIX3n8ZLAvm1PoDU1M +h2Amc56dmaRo3inGZxN9Nz8nhuDC+/7OFA7hKd2Ve2Q= +KZTmaJLp+FU9X93j5Tpqtw== +1u+XjG/2+GSQRv6EzCaWRQ== +h9U6jl4cZ4C22C1wpKu8vSM7bTtvy9o2g7Di3K7jTo2HJl2CG2yVZIJgod88bc9l +1u+XjG/2+GSQRv6EzCaWRQ== +Mtl1kt9g1gV2KJvyBZ2K8tOa6EEkDwaXESGA9zv8nLEBgBW5UcdKSZH2enp+Ljnf +mcQwVxjUw79rwGN6NopbQ8o+o0Oqap86qulYzVaU1YK+xvdtabAjOdZa8rH/UawJ +VIHA31zdnuwVlvUv3VOlYVRNGNAMqDxg7g3P55Yig7XGJ0frgqBbhflLu9pZDsGtG7PWJFzYnbAhv0HasJD0eg== +X/Lp4PsbbXulceSXUvGUpDfqLabbqgG4MZE+Cdy4FwlQTZqFS7MLKRTLDMwaDvILIRxQybc5sD1eU9BOwIwsyw== +1u+XjG/2+GSQRv6EzCaWRQ== +HTOfkyRnnkvrq92C2ykyOPO4d7gErRgYL7aScZTpzxREn6lD4QDtxAH0B30SgN6E3F22VdO8GzELDUsICPiSeg== +P8KbMTjfuhARpOL8NoffNro45O1U8oEoZJkv8WBE9IrllSlHFr8oQ8frOkOgpbVNE8ApvoZT6kfHiImzMYVOag== +L0eUthVnpkGsmKFAX6d+uFjjhUy+MCKFNT4NrCL5yNnhcA/pEoLZALqem+hbKHoY +1u+XjG/2+GSQRv6EzCaWRQ== +P8KbMTjfuhARpOL8NoffNom7jch4ZQTU5jfIo7wG8ScR4XnHaBOnM0U+2vh4JNta +L0eUthVnpkGsmKFAX6d+uFjjhUy+MCKFNT4NrCL5yNmWWojcHbeJQ61OMhTGFdlC +1u+XjG/2+GSQRv6EzCaWRQ== +i4c/4ocK9c+0kOv/NeukhzRCRIDY4b1IcwhCyS6fwKaxL1iz5YdtgQ8ur54xnSve +89eRayX3G8tCX3FDU5IcjcECGHtpMfMxvoO/SDLARSENffTD+JaXDY9RnayEnHLnlI8b7pgVZ6VRMA8KffpRHg== +VmVrGQo2zRokW/ZuO9bN67AUBZ3/XEKHpa/wOGbEOnmtIF6uAz0tBlf7rcOxUQPuytPu9bJw1t4kl86ncxKWUr6UFGd4R8GkpK1204AYmG4bJSHkhwXQJ4SNyep2Orx8zpIZXaQwin0CO9B7uaPk1A== +1u+XjG/2+GSQRv6EzCaWRQ== +swRuTHNu5hN+oxWHfH2uTRaafzF+WZloRON/s6FBnLM= +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANgyhe0+ApeojOmIJni82PRs= +G/hjuaHIfOdhg/DGAsPhcJ0dAhlHHuuDGrGW0z9/mG1YgeQhelMeN8fM9H05Q7bFoBNC//YD8IgCAMKgkLtISr7jJb8ysmAn68sYqB3zYKY= +XEKtPwhadR+kpKQN/+UTabdcOsvQ70heGQXJNuzLidaKrkdw//NbtiPyzZIdYJRwkO1XfoVngfneyEI34hiEIjZDJsEWIi+RYy+5PJ52y4M= +1u+XjG/2+GSQRv6EzCaWRQ== +6P8mKfouOK1hwXy3MrrCzYNv1bj48gIxdX69IR058kk= +2J/cJdNHXZ2Qsa37Fo6/Z45HJGTQKJ2XarF9YWFlYi/NS7Y1/C8WI42zDRQS65sB +K/3TjoKXtlVKQEXdtPO0urKjWEFV9hqYUzZS4hx2t3vgmB4P0sEnPBB9+cxnHYt4 +VmVrGQo2zRokW/ZuO9bN6zPTlJYqiIwYIHWMLx6MVG4A/jqaX4tahdPmgshFSQG4 +VmVrGQo2zRokW/ZuO9bN60KbPALjpXkxdnMsVQY2qiQ2mn3irY/rZFqtKYoHLxRE +VmVrGQo2zRokW/ZuO9bN60Xvz4OmVOCjpkbTCNQIATsOCVbKnePG0cTDCTWdue9j +VmVrGQo2zRokW/ZuO9bN66guoSIjBQ2hp+h2iWlRadc= +VmVrGQo2zRokW/ZuO9bN6855zfg/ZnrwyfrD1ClJJctc8wQUc7t8LPNPEB5FCa4H +VmVrGQo2zRokW/ZuO9bN6yrrO27yfi+aG1SaNFn36A8CnO5yCexFXRxrEFBi6HH8 +VmVrGQo2zRokW/ZuO9bN6/m/X7phwv/rEzEofJRELW/qETCx/ewa2vv2I76GwEWB +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN6/m/X7phwv/rEzEofJRELW+ZhbjEfjOJN1QgOkdHcgfg +VmVrGQo2zRokW/ZuO9bN69t0x0NhvKGOHr2+EEBw8Jek/n491mR4LKuB/UuUgzMUy7YY4ELecU+vhof4LvKC/Bhkvz+bT+jSRSy/qFVu/F0= +VmVrGQo2zRokW/ZuO9bN6wIR+5HpCjcoa898tUp4mpJqO8Na+qweZg8vpjfBiLDS +F9Sftf1PoN5P+lgvc+r10HYBS8twYBi0U/DCh6ocj+X6RD0HNlSpwv37yho4nl4Ot2LOLOWFJbMnayi7wAK30w== +VmVrGQo2zRokW/ZuO9bN67yPVwtUvdC1tj4awelda/fkZ9dUXYCEUeCyL3Yrqyn/ +VmVrGQo2zRokW/ZuO9bN60Xvz4OmVOCjpkbTCNQIATsXOlREGrlEeF/zy2qslZ1j +VmVrGQo2zRokW/ZuO9bN67HiZr3Mu5Xx/pLDDclKRz8= +VmVrGQo2zRokW/ZuO9bN60KbPALjpXkxdnMsVQY2qiQyq2q2A/0gHrZHUNJvsm2k +VmVrGQo2zRokW/ZuO9bN69t0x0NhvKGOHr2+EEBw8JfSNIZvkvl8jIdEvRQwBwJ8qc6p773V8w3sbnJ7sNRCjGOJmoa2y21Lbs/qYLxw7ZE= +VmVrGQo2zRokW/ZuO9bN6wIR+5HpCjcoa898tUp4mpJqO8Na+qweZg8vpjfBiLDS +F9Sftf1PoN5P+lgvc+r10NgITD/y7z0I4C2wYBhOQf9kEr4e0+0VrZmNsbWDAMY531ZzaC8lUc4x5xBNRPYlTw== +VmVrGQo2zRokW/ZuO9bN6yH86FWoLic2FxcRUV4zebxf0/M2UvczoadL9jeQKWgM +VmVrGQo2zRokW/ZuO9bN69so/u5P+e4KbXGeD0Dm7to/lSyHyW7MicMJ0jDa2eXr +VmVrGQo2zRokW/ZuO9bN6xQjxHnk9JeJcV3ebvE1Z2ZFylERkWn2HmPyx+oo40NY +VmVrGQo2zRokW/ZuO9bN60KbPALjpXkxdnMsVQY2qiQQ1X8tQ+wf4ZCJUp0oMXLr +VmVrGQo2zRokW/ZuO9bN69t0x0NhvKGOHr2+EEBw8JfSNIZvkvl8jIdEvRQwBwJ8qc6p773V8w3sbnJ7sNRCjGOJmoa2y21Lbs/qYLxw7ZE= +VmVrGQo2zRokW/ZuO9bN6wIR+5HpCjcoa898tUp4mpJqO8Na+qweZg8vpjfBiLDS +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6zPiagaoQNUZ6Q8MukMCasPCfaBG6YJFzlL84vUM1kV8 +VmVrGQo2zRokW/ZuO9bN69eu9LBWEB6PDbGO+0TyWwBhVpoFqMPkT8HlHX8RMHrR+75Z9K8m2JUCWTBuR0Tk9Q== +1u+XjG/2+GSQRv6EzCaWRQ== +o6QhOIN2Sc4SHELnst17uXpnZ5EAUQuq8Epej7P3sTRveRgNRdPH82tKZu7g4Eiq +1u+XjG/2+GSQRv6EzCaWRQ== +Ic1WBp01nLyRxbqJnsu2pypx+Gb9M9+y4CZIw5enWs+glmD/7BTlR5C7Ujg9dOtQxQc0uyS/kT/ILSqYhwQGNA== +b4OJVZe8QyIpjuTpKXDL9A== +KToqm5Or/ymuVO66QU/2pu7oKyhGYWQ+0pgKsF2sKsQ= +S76gqn57AVIGnu8xVYuZaO27N6yYhZ4GNoiBGQP9DKg= +K9/ExlRvl0KpaOPuc49gtg1nR3Bc0+g9UN7uwKhXbdM= +wlLHv6kT3Q/RmtMBN4nDAVircipW/U7HQDc2UPUyQ/vmRhc3HaKPFi1NtnisK6xX +VmVrGQo2zRokW/ZuO9bN64JOsgztOsHxYHKGCeX474Qcqbriq9LDc2GKDAaXpEva +VmVrGQo2zRokW/ZuO9bN6173lsXiCfzKN2E9+9sH2KYDQWUma1tCh1mCQLO6go9pew9ww96U7ujnre6SjyCuxQ== +VmVrGQo2zRokW/ZuO9bN6/EQSa+RVcntmzafI6w4hrXeQefCDpqhGzMMDJocinCp +VmVrGQo2zRokW/ZuO9bN64ViM2XLR1EOvB6UVgIoYuF8fdMuzlzZir170SdeoO1N +VmVrGQo2zRokW/ZuO9bN6/EQSa+RVcntmzafI6w4hrVZKx2YSK2fHbBecH4ladDU +VmVrGQo2zRokW/ZuO9bN66vpbXlJpZkCo8bNa8hfdTtD0OOBen8N9PlZvb6sHluL +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +KToqm5Or/ymuVO66QU/2puiKplVgTGTPZqNeAeKoYCRnGqCo0URmUW6QuDrFvxhR +S76gqn57AVIGnu8xVYuZaLg1MWy1KmN4+LZJN+yDmqCMssHrDpVNECVIttB2y5oe +K9/ExlRvl0KpaOPuc49gtoyfPotIPHp16XgJV6eRYyrbA+h8XwwZ0jIgIeiV5WJH +K/3TjoKXtlVKQEXdtPO0uuuN7F0BMuO/Q3cMv6UmDSFakb80AgleKsv9apABSHy8BJ9UVzoo8dLM78KhLfy4Pw== +VmVrGQo2zRokW/ZuO9bN65LhaN63q3ApjnzlgAtjYqw= +F9Sftf1PoN5P+lgvc+r10C1yj1HZBpntAx/i13/oYeYaK3wfHrLLbHW7duBhTqbF+VuvZwLaBegIbS5rvj5IKg== +VmVrGQo2zRokW/ZuO9bN65ZTH8ABglrqk9nDO7nL0rs= +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN64De6ITZeavbNwRiqFkMysk= +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +L0eUthVnpkGsmKFAX6d+uAST/oojvSe5vOTCDj9ZTSci5aesa7iYDqIo84xNyUjr +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9OQ4F/6xMvSNwNUGdXyQ9F1NqMgCz0f8DrpmEaOqG0E= +kkhBrrZfa9Lz2ARtxqQ9WI07s+twubQO6K+lkziI1UrpPBayzeeEXSMVuzem4Nzq +UxWVPzm6Eag/KO1azIHUdx+idAdS+rfPARzR88tVbgYdpuz2ULTF+2vmnBiei7OW +Lb802TYIO8cTPX7RtIYGZ+6eFx+9GqXwRvUIq98SK68Eo/uW/nIxVIKX8k1Y1OBk +b4OJVZe8QyIpjuTpKXDL9A== +I5csN8e3J/KIFkMa5t+SJCqL3g3fUtFpMCVgdY7uBLC2hDdwb73LgrQLVQ3aMtK6 +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033ZLNQ2XZF4KsFXQ0AJ186dTqy0tFe9yVaa5Nvi4GzvRQQ== +UiubnxD7y7+jD/CLXy/lIzUv+EQIUnxp2+tRaByCudDYzFPn13pFELSDNAgNf0aY +wI4EEWzdSnynhqFxMCzFmvtQa54ffiTU8OKIuM+mUtbRQW9n1JFAWLuXCST3voAI +GL/yLvMwjflJNfDPl5ASjQ2UrwSPDpUhyRkgeiXFg2U8KeI5JLJSQLyyHw7AauiQ +P5fFy0VqKNQcixZC6/gotxklJeUYBp6qUkCM0szi5po= +3HDkd2BFYLPk4K73U5D+BYdx+YPBx8edbX2mUEZUhcw= +gZ7BoIAABc+I8g/rYzq+fw0QRhyOskLowTpBMxzNkkXJfHhElpIZe5wsCdqDFCZvGpC72uDw/Cu+D1FuT4o1ew== +NhnIR3Ilo4H2su9/cTNo/AxXtCXBAtO/DWPyG38GkWo= +SXXsDW1aLF5ljPyJUoRc7t2uV1Ea/KDPsubfQRA1/g0= +i/p1vQ7ugXGBvwbjECApz/Cr9YvVAiEjIabMp4aMpkk= +zo+FVrlHkUeZ8fJX4PVhAH4W08MspSsE1ssI18UNPaA= +wlLHv6kT3Q/RmtMBN4nDAaQcqH83V4KNJSetgkXN2mpJpLhuyLAaUNt+tKkzaLSI +VmVrGQo2zRokW/ZuO9bN632VflaKbWaV9Wa8IOB78Qk= +VmVrGQo2zRokW/ZuO9bN608eo5JjqlJJ+hYuirBCFcmf4cKrfv149X3GuxINE1xDDJnAj+I17VZiW+JCiwfRwA== +VmVrGQo2zRokW/ZuO9bN67FMFupuL9NnhZUvLdV1qH1jDx/7tB6qL1SG7Pqfwsin +VmVrGQo2zRokW/ZuO9bN65qbK6/I8bncNNV3Fr84lp9CCx8VYIApMdX4h8SHWyk2 +VmVrGQo2zRokW/ZuO9bN6zEL40rbK+o6j49cNVbzKqOsExNYZ+R8lUnZEGfiuhXY +VmVrGQo2zRokW/ZuO9bN6+EP/UfvFvq8hDmkfhk2A+qnnPSHBLYJiyMbFizN9aWEoBeZFZoLhXy50+Ts9Ur1/A== +VmVrGQo2zRokW/ZuO9bN6+EP/UfvFvq8hDmkfhk2A+pmn/JN+SNJ6dRlNT3kh3N/3tDs7zMtD+kCYf9bfutOkQ== +VmVrGQo2zRokW/ZuO9bN60xwqMjDC3vPo6L4vEr/pBaJQkw/7oJUptDcMB6Fr5Cj4S+qQ9QZRL24FoS5lS3+qQ== +VmVrGQo2zRokW/ZuO9bN61G/tYB+vcuOiiaR8UbkscqXrSG+YeXqyTcHAiaZKnMP/R8AE4EFWhpiT+I3n+NNhw== +VmVrGQo2zRokW/ZuO9bN6zDu/0cPxO8iIAxo/MyAOyO3mi+GHsPcbGU9H8sA9FWz +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm5Kes26rz2vkjDRpZY9y8CG+MKLyqwcOLQaC8uFAcQhA== +VmVrGQo2zRokW/ZuO9bN64ApqCXcODvD57AL9oh19dsQTwsNrJDaw5mMC3iBkcYF +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkmssm/VPICxxxORTX5OIKSeBFVbEgJs/oO6D+R5qX3b7w== +VmVrGQo2zRokW/ZuO9bN69/FjVndCR4PL8NhQnFk2Gew+8ZLbD2plI32rzUojOkk +VmVrGQo2zRokW/ZuO9bN69IgslanOR1qBWJ7u9f8kApHl7m/3x/YUX5TmvRhoWWrhrEvRs3e6Vo03iY4FZoGJQ== +VmVrGQo2zRokW/ZuO9bN6yAU/fW49cRu9QdgvjUnDRs= +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +VmVrGQo2zRokW/ZuO9bN69jIxF5VISZtC2vxWRknx7k= +VmVrGQo2zRokW/ZuO9bN6w7SLxNKr+6YhBMvtqYtIS5B39SOQ9awYMdHGNN7g2HF +VmVrGQo2zRokW/ZuO9bN61ftGV3vPtB2WjF6YrN/0jNHYRNif5kQHZWzQEGp4Mrw +VmVrGQo2zRokW/ZuO9bN68i9AQUAyLa5Izsu2k/9DPtGLI71suphKNgMpp1nHjp3 +VmVrGQo2zRokW/ZuO9bN60FmyZq9YoZep5W4V0xLs2VJw4EwR4WqghfwBKZHKkzFW5QCRYRsMStsmunHScuj3A== +VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C +VmVrGQo2zRokW/ZuO9bN67c57efC+E7a6G1EN/4VXAUA5IVAU8G/FAp8AnNhV+8pDSYVAZUG09Nqr+7c8CWyvg== +VmVrGQo2zRokW/ZuO9bN6x9SPCVoe4PZoqZ6VqYN93GZvfLrMfeSjpwDGE6Mez3z +VmVrGQo2zRokW/ZuO9bN6yf4ml6i++/GqH/wSBe2JUimuAJuO3PPz0XEqqC+kEwmq2P2CCy/D5JDk3aKmyunXg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yZnMkqRvl1W1z1JA43ooyLsysqnBW0cSn//FgR0VFUn +VmVrGQo2zRokW/ZuO9bN6zw+wLmacHPHBj00bNmIGZaQr03AAq32yLrsoy6EYs3C +VmVrGQo2zRokW/ZuO9bN66U3/6225IbLyIW0FVhZRNEyLZurMXbjR98/i0DXR7zf +VmVrGQo2zRokW/ZuO9bN6xaf2vdYGOU85+dskGN5Zti8/wz7YWu3y/jTkefW+oHw +VmVrGQo2zRokW/ZuO9bN6xkyZls2bS2c0K0h3aqZRdZrjes6+0hX3Wr6T+0mnqitWU1y3v2I2Wt4PYH0XBjT6w== +VmVrGQo2zRokW/ZuO9bN62zrDNonCkxA5BeQHJAPUOvVeRTu47AJZhJxjTJXUg6D +QPiJZBcZo3Zu5kDhrrGzbI3GQxTKN1tlSYOd4wqk4Qk= +o6QhOIN2Sc4SHELnst17uYG57hDyRaN+v5d46f3m9TtvuM1edZME3sYQm1fwKXSE +1V2v8QerKOmubvSxgB4eTM+LVbDQYpuIwwcEKVLExe6MC8BJb9x5uoyJJlFncEM6rY6CjjYEoI1lGYiKE1nBng== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +o6QhOIN2Sc4SHELnst17ucZEhQfEUee5E6C4gEbgFRMehTYHPr1sbQ4k/qB7LJ8r1OhV8ece9Zkwgx68RDZgODP4asgDCZmEmIilOSuj+JI= +ACEEdgL/kNbx7RaZcSRxZClHBggmOTwuoukB6MgtasIiEFle6fCvd15573nsLbI9 +VmVrGQo2zRokW/ZuO9bN67Pocv3BMn87TAB+S8aqZRSL3g30PRHmEkGas7a6CXo4 +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN61efkqHAT/xspGlYy7KhUnGfupbfN6kadJV1FzZSIvpa +VmVrGQo2zRokW/ZuO9bN6+b0SyPGp1VLLfFreWS32CdvJQ0lilsNKGrnKkyex2l6ZoEN1nB9tzGcgrcEFt4mWg== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN6zpJcGQDok2bzZbwZ5+2z7FMKwUs1r6SjvZQDsl9E9LF +VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgEYDxdXHdYYYJAWxRhWjvkMURIUYeBTgS8tgmIZ7YENDxgg1Zs4OdrlyVu+VDCE+oc= +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65HmUSl6PKrPLdi3pIfWJgGfllFXAtMFaX/YohJyQyfhm+vA31HUxmdUtlkayzAZUKQbFld81ekbiffgRsrQqQB1xVb1v/rxv/O8tBET/TaPCdwsyclhJhXcaG/6gaDkZg== +L0eUthVnpkGsmKFAX6d+uA6wgjrZ2VgEjfvSiyQz6oysGP6R543HWJTxMrxe6FdC +6ZPJI/HSoc4xA2zncU65Fm80XRief2SLbJIcIhJc9eE= diff --git a/class_v2/safe_warning_v2/sw_alias_ls_rm.py b/class_v2/safe_warning_v2/sw_alias_ls_rm.py new file mode 100644 index 00000000..b1d3b31b --- /dev/null +++ b/class_v2/safe_warning_v2/sw_alias_ls_rm.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, re, public + + +_title = 'Check alias configuration' +_version = 1.0 # 版本 +_ps = "Check if the ls and rm commands set aliases" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_alias_ls_rm.pl") +_tips = [ + "Add or modify alias ls=\'ls -alh\' and alias rm=\'rm -i\' in the file [~/.bashrc]", + "Execute [source ~/.bashrc] to make the configuration take effect", +] +_help = '' +_remind = 'This scheme can make ls command list more detailed file information and reduce the risk of rm command deleting files by mistake, but it may affect the original operation habits.' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + # 存放配置不当的命令,分别用正则判断是否配置别名 + result_list = [] + cfile = '/root/.bashrc' + if not os.path.exists(cfile): + return True, 'Risk-free' + conf = public.readFile(cfile) + # rep1 = 'alias(\\s*)ls(\\s*)=(\\s*)[\'\"]ls(\\s*)-.*[alh].*[alh].*[alh]' + # tmp1 = re.search(rep1, conf) + # if not tmp1: + # result_list.append('ls') + rep2 = 'alias(\\s*)rm(\\s*)=(\\s*)[\'\"]rm(\\s*)-.*[i?].*' + tmp2 = re.search(rep2, conf) + if not tmp2: + result_list.append('rm') + if len(result_list) > 0: + return False, '{} The command does not have an alias configured or is configured incorrectly'.format('、'.join(result_list)) + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_audit_docker.py b/class_v2/safe_warning_v2/sw_audit_docker.py new file mode 100644 index 00000000..e03285f0 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_audit_docker.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + +_title = 'Whether to enable Docker log audit check' +_version = 1.0 # 版本 +_ps = "Whether to enable Docker log audit check" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-13' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_audit_docker.pl") +_tips = [ + "Add -w /usr/bin/docker -k docker from [/etc/audit/rules.d/audit.rules] file", + "Restart auditd process: systemctl restart auditd" +] +_help = '' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + audit_path = '/etc/audit/audit.rules' + if not os.path.exists(audit_path): + return False, 'Risky, the auditd audit tool is not installed' + # auditctl -l命令列出当前auditd规则,匹配是否有对docker做审计记录 + result = public.ExecShell('auditctl -l')[0].strip() + rep = '/usr/bin/docker' + if re.search(rep, result): + return True, 'Risk-free' + else: + return False, 'Risky, the docker audit log is not enabled' + diff --git a/class_v2/safe_warning_v2/sw_audit_log_keep.py b/class_v2/safe_warning_v2/sw_audit_log_keep.py new file mode 100644 index 00000000..90d70b5f --- /dev/null +++ b/class_v2/safe_warning_v2/sw_audit_log_keep.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + + +_title = 'Audit logs are kept forever' +_version = 1.0 # 版本 +_ps = "Check whether the audit log is automatically deleted when it is full" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-15' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_audit_log_keep.pl") +_tips = [ + "In [/etc/audit/auditd.conf] max_log_file_action changes ROTATE to KEEP_LOGS", + "Restart auditd service: systemctl restart auditd" +] +_help = '' + + +def check_run(): + cfile = '/etc/audit/auditd.conf' + if not os.path.exists(cfile): + return False, 'Risky,The auditd audit tool is not installed' + result = public.ReadFile(cfile) + # 默认是rotate,日志满了后循环日志,keep_logs会保留旧日志 + rep = r'max_log_file_action\s*=\s(.*)' + tmp = re.search(rep, result) + if tmp: + if 'keep_logs'.lower() == tmp.group(1).lower(): + return True, 'Risk-free' + return False, 'The current max_log_file_action value is {}, it should be KEEP_LOGS'.format(tmp.group(1)) diff --git a/class_v2/safe_warning_v2/sw_bashrc.py b/class_v2/safe_warning_v2/sw_bashrc.py new file mode 100644 index 00000000..a6129703 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_bashrc.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 用户缺省权限检查 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = '[/etc/bashrc] User default permission check' +_version = 1.0 # 版本 +_ps = "/etc/bashrc User default permission check" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_bashrc.pl") +_tips = [ + "[/etc/bashrc] The umask set in the file is 002, and it is recommended to set it to 027", + "Solution: Modify the /etc/bashrc file permission to 027", +] +_help = '' +_remind = 'This scheme can strengthen the protection of system user rights, but it may affect the original operation habits.' + + +def check_run(): + # 判断是否存在/etc/profile文件 + if os.path.exists("/etc/bashrc"): + # 读取文件内容 + profile = public.ReadFile("/etc/bashrc") + # 判断是否存在umask设置 + if re.search("umask 0",profile): + # 判断是否设置为027 + if re.search("umask 027",profile): + return True,"Risk-free" + else: + return False,"umask is not set to 027" + else: + return False,"umask not set" \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_bootloader_mod.py b/class_v2/safe_warning_v2/sw_bootloader_mod.py new file mode 100644 index 00000000..8f8d5f9d --- /dev/null +++ b/class_v2/safe_warning_v2/sw_bootloader_mod.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# coding: utf-8 + +import sys, os, public +_title = 'bootloader Configuring permissions' +_version = 1.0 # 版本 +_ps = "bootloader Configuring permission checks" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-15' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_bootloader_mod.pl") +_tips = [ + "Configure secure permissions for grub according to the file suggested by the risk description", + "If grub2, then: chmod 600 /boot/grub2/grub.cfg、chown root /boot/grub2/grub.cfg", + "If grub, then: chmod 600 /boot/grub/grub.cfg、chown root /boot/grub/grub.cfg" +] +_help = '' +_remind = 'This scheme can strengthen the server grub interface protection, further prevent external intrusion server.' + +def check_run(): + dir_list = [ + ['/boot/grub2/grub.cfg', 600, 'root'], + ['/boot/grub/grub.cfg', 600, 'root'] + ] + # 存放没有配置权限的文件 + not_mode_list = [] + for d in dir_list: + if not os.path.exists(d[0]): + continue + u_mode = public.get_mode_and_user(d[0]) + if u_mode['user'] != d[2]: + not_mode_list.append("{} Current permissions: {} : {} Security permissions: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + if int(u_mode['mode']) != d[1]: + not_mode_list.append("{} Current permissions: {} : {} Security permissions: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + if not_mode_list: + return False, 'The following critical file or directory permissions are incorrect:{}'.format('、'.join(not_mode_list)) + else: + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_chmod_sid.py b/class_v2/safe_warning_v2/sw_chmod_sid.py new file mode 100644 index 00000000..b53e7559 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_chmod_sid.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, public + + +_title = 'Check for files with suid and sgid permissions' +_version = 1.0 # 版本 +_ps = "Check important files for suid and sgid permissions" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_chmod_sid.pl") +_tips = [ + "Use the chmod u-s/g-s [filename] command to change the permissions of the file", +] +_help = '' +_remind = 'This scheme removes the special permissions of important files, which can prevent intruders from using these files for privilege escalation.' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + exist_list = [] + # 列出重要文件,先判断是否存在 + file_list = ['/usr/bin/chage', '/usr/bin/gpasswd', '/usr/bin/wall', '/usr/bin/chfn', '/usr/bin/chsh', '/usr/bin/newgrp', + '/usr/bin/write', '/usr/sbin/usernetctl', '/bin/mount', '/bin/umount', '/bin/ping', '/sbin/netreport'] + for fl in file_list: + if os.path.exists(fl): + exist_list.append(fl) + # find命令-perm 判断是否有suid或guid,有则返回该文件名 + result_str = public.ExecShell('find {} -type f -perm /04000 -o -perm /02000'.format(' '.join(exist_list)))[0].strip() + result = '、'.join(result_str.split('\n')) + if result: + return False, 'The following files have sid privilege, chmod u-s or g-s remove sid bits: \"{}\"'.format(result) + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_chmod_stickybit.py b/class_v2/safe_warning_v2/sw_chmod_stickybit.py new file mode 100644 index 00000000..b64a4422 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_chmod_stickybit.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, public +_title = 'Check temporary directory for sticky bit' +_version = 1.0 # 版本 +_ps = "Check if the temporary directory has the sticky bit permission set" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_chmod_stickybit.pl") +_tips = [ + "Use the chmod +t [file name] command to modify the permissions of the file", +] +_help = '' +_remind = 'This solution prevents system users from accidentally deleting files on the server. ' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + # result_list存放未配置粘滞位的目录名 + result_list = [] + tmp_path = ['/var/tmp', '/tmp'] + for t in tmp_path: + # 文件不存在则跳过,保险操作。 + if not os.path.exists(t): + continue + result_str = public.ExecShell('find {} -maxdepth 0 -perm /01000 -type d'.format(t))[0].strip() + if not result_str[1]: + result_list.append(t) + if result_list: + result = '、'.join(result_list) + return False, 'The following directories do not have sticky bit permissions set:{}'.format(result) + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_cshrc.py b/class_v2/safe_warning_v2/sw_cshrc.py new file mode 100644 index 00000000..24c681a0 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_cshrc.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 用户缺省权限检查 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = '[/etc/csh.cshrc] User default permission check' +_version = 1.0 # 版本 +_ps = "[/etc/csh.cshrc] User default permission check" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_cshrc.pl") +_tips = [ + "[/etc/csh.cshrc] The umask set in the file is 002, which does not meet the requirements. It is recommended to set it to 027", + "The operation is as follows: Modify umask to 027", +] +_help = '' +_remind = 'This scheme can strengthen the protection of user privileges on the system. ' + +def check_run(): + # 判断是否存在/etc/profile文件 + if os.path.exists("/etc/csh.cshrc"): + # 读取文件内容 + profile = public.ReadFile("/etc/csh.cshrc") + # 判断是否存在umask设置 + if re.search("umask 0",profile): + # 判断是否设置为027 + if re.search("umask 027",profile): + return True,"Risk-free" + else: + # return False,"umask not set to 027" + return True, "Risk-free" + else: + # return False,"umask not set" + return True, "Risk-free" \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_cve_2019_5736.py b/class_v2/safe_warning_v2/sw_cve_2019_5736.py new file mode 100644 index 00000000..f25b9599 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_cve_2019_5736.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, public, re + +_title = 'CVE-2019-5736容器逃逸漏洞检测' +_version = 1.0 # 版本 +_ps = "检测CVE-2019-5736容器逃逸漏洞" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-27' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_cve_2019_5736.pl") +_tips = [ + "docker version查看docker版本是否小于18.09.2,runc版本小于1.0-rc6", +] +_help = '' +_remind = 'An attacker can use this vulnerability to gain access to the server. ' + +# https://nvd.nist.gov/vuln/detail/CVE-2019-5736#match-7231264 +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + docker = public.ExecShell("docker version --format=\'{{ .Server.Version }}\'")[0].strip() + if 'command not found' in docker or 'Command not found' in docker: + return True, 'Risk-free,docker is not installed' + if not re.search(r'\d+.\d+.\d+', docker): + return True, 'Risk-free' + docker = docker.split('.') + if len(docker[0]) < 2: + return False, 'Risky,The current docker version has security risks and needs to be upgraded to a safe version' + elif int(docker[0]) < 18: + return False, 'Risky,The current docker version has security risks and needs to be upgraded to a safe version' + elif int(docker[0]) == 18: + if int(docker[1]) < 9: + return False, 'Risky,The current docker version has security risks and needs to be upgraded to a safe version' + elif int(docker[1]) == 9: + if int(docker[2][0]) < 2: + return False, 'Risky,The current docker version has security risks and needs to be upgraded to a safe version' + else: + return True, 'Risk-free' + else: + return True, 'Risk-free' + else: + return True, 'Risk-free' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_cve_2021_4034.py b/class_v2/safe_warning_v2/sw_cve_2021_4034.py new file mode 100644 index 00000000..02bed4c7 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_cve_2021_4034.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# CVE-2021-4034 polkit pkexec 本地提权漏洞检测 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import public, os +_title = 'CVE-2021-4034 polkit pkexec Local Privilege Escalation Vulnerability Detection' +_version = 1.0 # 版本 +_ps = "CVE-2021-4034 polkit pkexec Local Privilege Escalation Vulnerability Detection" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_cve_2021_4034.pl") +_tips = [ + "Update polkit components" +] +_help = '' +_remind = 'There is a risk to upgrade the software version. It is strongly recommended that the server do a snapshot backup first, in case the operation fails to restore in time! ' + + +def check_run(): + ''' + @name CVE-2021-4034 polkit pkexec 本地提权漏洞检测 + @time 2022-08-12 + @author lkq@bt.cn + ''' + + st = os.stat('/usr/bin/pkexec') + setuid, setgid = bool(st.st_mode & stat.S_ISUID), bool(st.st_mode & stat.S_ISGID) + if not setuid: return True, 'Risk-free' + + redhat_file = '/etc/redhat-release' + if os.path.exists(redhat_file): + data=public.ReadFile(redhat_file) + if not data:return True, 'Risk-free' + if data.find('CentOS Linux release 7.') != -1: + polkit=public.ExecShell("rpm -q polkit-0.*") + if not polkit[0]:return True, 'Risk-free' + polkit_list = polkit[0].strip() + if polkit_list.find('polkit-0') != -1:return True, 'Risk-free' + p = polkit_list.strip().split(".") + if len(p)<2:return True, 'Risk-free' + if p[1] == '112-26':return True,'Risk-free' + p2=p[1].split("-") + if p2[1] <26: + return False, 'Please update polkit' + return True,'Risk-free' + #CentOS 8.0 + elif data.find('CentOS Linux release 8.0') == 0: + polkit = public.ExecShell("rpm -q polkit-0.*") + if not polkit[0]: return True, 'Risk-free' + polkit_list = polkit[0].strip() + if polkit_list.find('polkit-0') != -1: return True, 'Risk-free' + # Centos 7 + p = polkit_list.strip().split(".") + if len(p) < 2: return True, 'Risk-free' + if p[1] == '115-13': return True, 'Risk-free' + p2 = p[1].split("-") + if p2[1] < 13: + return False, 'Please update polkit' + return True, 'Risk-free' + elif data.find("CentOS Linux release 8.2") == 0: + polkit = public.ExecShell("rpm -q polkit-0.*") + if not polkit[0]: return True, 'Risk-free' + polkit_list = polkit[0].strip() + if polkit_list.find('polkit-0') != -1: return True, 'Risk-free' + # Centos 7 + p = polkit_list.strip().split(".") + if len(p) < 2: return True, 'Risk-free' + if p[1] == '115-11': return True, 'Risk-free' + p2 = p[1].split("-") + if p2[1] < 11: + return False, 'Please update polkit' + return True, 'Risk-free' + elif data.find("CentOS Linux release 8.5") == 0: + polkit = public.ExecShell("rpm -q polkit-0.*") + if not polkit[0]: return True, 'Risk-free' + polkit_list = polkit[0].strip() + if polkit_list.find('polkit-0.115-12') ==0: + return False, 'Please update polkit' + return True, 'Risk-free' + return True, 'Risk-free' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_cve_2022_2068.py b/class_v2/safe_warning_v2/sw_cve_2022_2068.py new file mode 100644 index 00000000..8903bf5f --- /dev/null +++ b/class_v2/safe_warning_v2/sw_cve_2022_2068.py @@ -0,0 +1,55 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + +_title = 'CVE-2022-2068 OpenSSL任意命令执行漏洞检测' +_version = 1.0 # 版本 +_ps = "CVE-2022-2068 OpenSSL任意命令执行漏洞检测" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_cve_2022_2068.pl") +_tips = [ + "升级OpenSSL至最新版本或是安全版本", + "1.0.2zf、1.1.1p、3.0.4及以上版本", +] +_help = '' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + # https://nvd.nist.gov/vuln/detail/CVE-2022-2068#range-8768393 + openssl = public.ExecShell("openssl version")[0].strip() + openssl = openssl.split(' ')[1] + openssl = openssl.split('.') + if openssl[0] == '1': + if openssl[1] == '0' and openssl[2][0] == '2': + if len(openssl[2]) < 3: + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + elif not openssl[2][1].islower(): + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + elif openssl[2][1] < 'z': + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + elif openssl[2][1] == 'z' and openssl[2][2] < 'f': + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + else: + return True, 'Risk-free' + elif openssl[1] == '1' and openssl[2][0] == '1': + if len(openssl[2]) < 2: + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + elif not openssl[2][1].islower(): + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + elif openssl[2][1] < 'p': + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + else: + return True, 'Risk-free' + elif openssl[0] == '3' and openssl[1] == '0': + if openssl[2][0] < '4': + return False, 'There are security risks in the current version of openssl; you need to update to a secure version' + else: + return True, 'Risk-free' + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_cve_2022_25845.py b/class_v2/safe_warning_v2/sw_cve_2022_25845.py new file mode 100644 index 00000000..52b6e572 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_cve_2022_25845.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + +_title = 'CVE-2022-25845 Fastjson Arbitrary Code Execution vulnerability Detection' +_version = 1.0 # 版本 +_ps = "CVE-2022-25845 Fastjson Arbitrary Code Execution vulnerability Detection" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-13' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_cve_2022_25845.pl") +_tips = [ + "Open the pom.xml file in the website directory and check for fastjson dependencies." + "If fastjson version is less than 1.2.83, upgrade to security version 1.2.83 or higher ", +] +_help = '' + + +# https://nvd.nist.gov/vuln/detail/CVE-2022-25845 +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + web_list = [] + path = '/www/wwwroot/' + pom = public.ExecShell("find {} |grep pom.xml".format(path))[0].split('\n') + if pom[0]: + for p in pom: + if p == '': + continue + conf = public.ReadFile(p.strip()) + rep = r'fastjson(\s*)(.*)' + tmp = re.search(rep, conf) + rep1 = r'{}(.*)/'.format(path) + if tmp: + fastjson = tmp.group(2).split('.') + if len(fastjson) == 3: + if fastjson[0] == '1' and fastjson[1] == '1': + if not contrast(fastjson[2], '157'): + web_list.append(re.search(rep1, p).group(1)) + elif fastjson[0] == '1' and fastjson[1] == '2': + + if not contrast(fastjson[2], '83'): + web_list.append(re.search(rep1, p).group(1)) + if web_list: + return False, 'Website [{}] fastjson component has a security risk, need to upgrade the component to a secure version'.format('、'.join(web_list)) + return True, 'Risk-free' + + +def contrast(a, b): + if len(a) >= 3: + if a[0].isdigit() and a[1].isdigit() and a[2].isdigit(): + if int(a[0:3]) >= int(b): + return True + else: + return False + elif a[0].isdigit() and a[1].isdigit(): + if int(a[0:2]) >= int(b): + return True + else: + return False + elif a[0].isdigit(): + if int(a[0]) >= int(b): + return True + else: + return False + else: + return False + elif len(a) == 2: + if a[0].isdigit() and a[1].isdigit(): + if int(a[0:2]) >= int(b): + return True + else: + return False + elif a[0].isdigit(): + if int(a[0]) >= int(b): + return True + else: + return False + else: + return False + elif len(a) == 1: + if a[0].isdigit(): + if int(a[0]) >= int(b): + return True + else: + return False + else: + return False + else: + return False diff --git a/class_v2/safe_warning_v2/sw_cve_2023_0386.py b/class_v2/safe_warning_v2/sw_cve_2023_0386.py new file mode 100644 index 00000000..b6741a8c --- /dev/null +++ b/class_v2/safe_warning_v2/sw_cve_2023_0386.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +#coding: utf-8 +import os +import public +import re + +_title = 'CVE-2023-0386 Linux Kernel OverlayFS Vulnerability' +_version = 1.0 # 版本 +_ps = "CVE-2023-0386 Linux Kernel OverlayFS Vulnerability" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-06-06' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_cve_2023_0386.pl") +_tips = [ + "Check whether the kernel version is lower than the specified version according to the prompt [uname -r]", + "If is CentOS 8 Stream, execute [yum install kernel] command to upgrade the kernel version, and restart the server", + "If it Ubuntu 22.04, execute [apt install linux-image] to check the installable version number, select version number higher than 5.15.0-70, execute [apt install linux-image-version_number] again, and restart the server" +] +_help = '' +_remind = 'The above kernel upgrade operation has certain risks, it is strongly recommended that the server do a snapshot backup first, in case the operation fails to restore in time! ' + +# https://nvd.nist.gov/vuln/detail/CVE-2023-0386 +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + kernel = public.ExecShell('uname -r')[0] + result = re.search("^(\\d+.\\d+.\\d+-\\d+)", kernel) + # centos8 + if os.path.exists('/etc/redhat-release'): + ver = public.ReadFile('/etc/redhat-release') + if ver.startswith('CentOS Stream release 8'): + if result: + result = result.group(1).split('.') + result = result[:2] + result[2].split('-') + if len(result) == 4: + if result[0] == '4' and result[1] == '18' and result[2] == '0': + if len(result[3]) <= 3: + fin = contrast(result[3], 425) + if not fin: + return False, 'The current kernel version [{}] has security risks, please upgrade to 4.18.0-425 and above as soon as possible'.format(kernel) + if os.path.exists('/etc/issue'): + ver = public.ReadFile('/etc/issue') + if ver.startswith('Ubuntu 22.04'): + if result: + result = result.group(1).split('.') + result = result[:2] + result[2].split('-') + print(result) + if len(result) == 4: + if result[0] == '5' and result[1] == '15' and result[2] == '0': + if len(result[3]) <= 3: + fin = contrast(result[3], 70) + if not fin: + return False, 'The current kernel version [{}] has security risks, please upgrade to 5.15.0-70 and above as soon as possible'.format(kernel) + return True, 'The current kernel version [{}] is risk-free'.format(kernel) + + +def contrast(a, b): + if len(a) >= 3: + if a[0].isdigit() and a[1].isdigit() and a[2].isdigit(): + if int(a[0:3]) >= int(b): + return True + else: + return False + elif a[0].isdigit() and a[1].isdigit(): + if int(a[0:2]) >= int(b): + return True + else: + return False + elif a[0].isdigit(): + if int(a[0]) >= int(b): + return True + else: + return False + else: + return False + elif len(a) == 2: + if a[0].isdigit() and a[1].isdigit(): + if int(a[0:2]) >= int(b): + return True + else: + return False + elif a[0].isdigit(): + if int(a[0]) >= int(b): + return True + else: + return False + else: + return False + elif len(a) == 1: + if a[0].isdigit(): + if int(a[0]) >= int(b): + return True + else: + return False + else: + return False + else: + return False + diff --git a/class_v2/safe_warning_v2/sw_database_backup.py b/class_v2/safe_warning_v2/sw_database_backup.py new file mode 100644 index 00000000..45107b75 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_database_backup.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 数据库定时备份检测 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'Database backup' +_version = 1.0 # 版本 +_ps = "Checks whether all databases are set up for periodic backup" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_database_backup.pl") +_tips = [ + "On the [ Cron ] page, set the database that is not backed up, or set all databases to be backed up", + "Tip: if the database is not set up for regular backup, once the data is lost accidentally and cannot be recovered, the loss will be huge" + ] + +_help = '' +_remind = 'This solution prevents data loss in the database and keeps data safe. ' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + ''' + + if os.path.exists('/www/server/panel/plugin/enterprise_backup'): + return True,'Risk-free' + if public.M('crontab').where('sType=? AND sName=?', + ('database', 'ALL')).count(): + return True, 'Risk-free' + + db_list = public.M('databases').field('name').select() + + not_backups = [] + sql = public.M('crontab') + for db in db_list: + if sql.where('sType=? AND sName=?',('database',db['name'])).count(): + continue + not_backups.append(db['name']) + + if not_backups: + return False ,'The following databases are not set up for regular backup:
                                    ' + ('
                                    '.join(not_backups)) + + return True,'Risk-free' + + diff --git a/class_v2/safe_warning_v2/sw_database_priv.py b/class_v2/safe_warning_v2/sw_database_priv.py new file mode 100644 index 00000000..98350b92 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_database_priv.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: linxiao +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 数据库备份权限检测 +# ------------------------------------------------------------------- + +import os, re, public, panelMysql + +_title = 'Database backup permission detection' +_version = 1.0 # 版本 +_ps = "Check whether the MySQL root user has database backup privileges" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-09-19' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_database_priv.pl") +_tips = [ + "To temporarily enter the database without authorization, it is recommended to restore all permissions of the root user.", +] + +_help = '' +_remind = 'This scheme ensures that the root user has the permission to backup the database and ensures that the database backup work is carried out. ' + +def check_run(): + """检测root用户是否具备数据库备份权限 + + @author linxiao<2020-9-18> + @return (bool, msg) + """ + mycnf_file = '/etc/my.cnf' + if not os.path.exists(mycnf_file): + return True, 'Risk-free' + mycnf = public.readFile(mycnf_file) + port_tmp = re.findall(r"port\s*=\s*(\d+)", mycnf) + if not port_tmp: + return True, 'Risk-free' + if not public.ExecShell("lsof -i :{}".format(port_tmp[0]))[0]: + return True, 'Risk-free' + + base_backup_privs = ["Lock_tables_priv", "Select_priv"] + select_sql = "Select {} FROM mysql.user WHERE user='root' and " \ + "host=SUBSTRING_INDEX((select current_user()),'@', " \ + "-1);".format(",".join(base_backup_privs)) + select_result = panelMysql.panelMysql().query(select_sql) + if not select_result: + return False, "The root user has insufficient privileges to perform mysqldump backups." + select_result = select_result[0] + for priv in select_result: + if priv.lower() != "y": + return False, "The root user has insufficient privileges to perform mysqldump backups." + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_debug_mode.py b/class_v2/safe_warning_v2/sw_debug_mode.py new file mode 100644 index 00000000..2272c99d --- /dev/null +++ b/class_v2/safe_warning_v2/sw_debug_mode.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测是否开debug模式 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'Developer Mode' +_version = 1.0 # 版本 +_ps = "Checks whether panel developer mode is enabled" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_debug_mode.pl") +_tips = [ + "Turn off developer mode on the [ Settings ] page", + "Note: Developer mode is only used for panel plug-in or API development, do not use in production environment" + ] + +_help = '' +_remind = 'This solution prevents the disclosure of sensitive information and reduces the risk of your website being compromised. ' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-05> + @return tuple (status,msg) + ''' + if os.path.exists('/www/server/panel/data/debug.pl'): + return False,'[Developer mode] has been opened, and risks such as data communication and information leakage exist' + return True,'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_dir_mode.py b/class_v2/safe_warning_v2/sw_dir_mode.py new file mode 100644 index 00000000..69e5e8b8 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_dir_mode.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测关键目录权限是否正确 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'System directory permissions' +_version = 1.0 # 版本 +_ps = "Checks if the System directory permissions are correct" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_dir_mode.pl") +_tips = [ + "On the [ File ] page, set the correct permissions and owner for the specified directory or file", + "Note 1: When setting directory permissions through the [File] page, please cancel the [Apply to subdirectories] option", + "Note 2: Incorrect file permissions not only pose a security risk, but also may cause some software on the server to fail to work properly" + ] + +_help = '' + + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-05> + @return tuple (status,msg) + ''' + dir_list = [ + ['/usr',755,'root'], + ['/usr/bin',555,'root'], + ['/usr/sbin',555,'root'], + ['/usr/lib',555,'root'], + ['/usr/lib64',555,'root'], + ['/usr/local',755,'root'], + ['/etc',755,'root'], + ['/etc/passwd',644,'root'], + ['/etc/shadow',600,'root'], + ['/etc/gshadow',600,'root'], + ['/etc/cron.deny',600,'root'], + ['/etc/anacrontab',600,'root'], + ['/var',755,'root'], + ['/var/spool',755,'root'], + ['/var/spool/cron',700,'root'], + ['/var/spool/cron/root',600,'root'], + ['/var/spool/cron/crontabs/root',600,'root'], + ['/www',755,'root'], + ['/www/server',755,'root'], + ['/www/wwwroot',755,'root'], + ['/root',550,'root'], + ['/mnt',755,'root'], + ['/home',755,'root'], + ['/dev',755,'root'], + ['/opt',755,'root'], + ['/sys',555,'root'], + ['/run',755,'root'], + ['/tmp',777,'root'] + ] + + not_mode_list = [] + # for d in dir_list: + # if not os.path.exists(d[0]): continue + # u_mode = public.get_mode_and_user(d[0]) + # if u_mode['user'] != d[2]: + # not_mode_list.append("{} 当前权限: {} : {} 安全权限: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + # if int(u_mode['mode']) != d[1]: + # not_mode_list.append("{} 当前权限: {} : {} 安全权限: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + + # if not_mode_list: + # return False,'以下关键文件或目录权限错误:
                                    ' + ("
                                    ".join(not_mode_list)) + + return True,'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_docker_api.py b/class_v2/safe_warning_v2/sw_docker_api.py new file mode 100644 index 00000000..5d2b1f15 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_docker_api.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# Docker API 未授权访问 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") + +import public, os,requests +_title = 'Docker API unauthorized access' +_version = 1.0 # 版本 +_ps = "Docker API unauthorized access" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_docker_api.pl") +_tips = [ + "Authentication should be turned on to authenticate or turn off the Docker Api" +] +_help = '' +_remind = "This solution fixes Docker's unauthorized access vulnerability, preventing attackers from using Docker to break into the server. We need to restrict API access to ensure that it does not affect the original website business operation. " + +# +def get_local_ip(): + '''获取内网IP''' + import socket + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + ip = s.getsockname()[0] + return ip + finally: + s.close() + return '127.0.0.1' + +def check_run(): + ''' + @name 面板登录告警是否开启 + @time 2022-08-12 + @author lkq@bt.cn + ''' + try: + if os.path.exists("/lib/systemd/system/docker.service"): + data=public.ReadFile("/lib/systemd/system/docker.service") + if not data:return True, 'Risk-free' + if '-H tcp://' in data: + datas=requests.get("http://{}:2375/info".format(get_local_ip()),timeout=1) + datas.json() + if 'KernelVersion' in datas.text and 'RegistryConfig' in datas.text and 'DockerRootDir' in datas.text: + return False,"Unauthorized access to the Docker API" + return True, 'Risk-free' + except:return True,"Risk-free" \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_docker_mod.py b/class_v2/safe_warning_v2/sw_docker_mod.py new file mode 100644 index 00000000..998d53a4 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_docker_mod.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# coding: utf-8 + +import sys, os, public +_title = 'Docker critical file permission checks' +_version = 1.0 # 版本 +_ps = "Docker critical file permission checks" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-14' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_docker_mod.pl") +_tips = [ + "On the File page, set the correct permissions and owners for the specified directory or file", + "docker.service and docker.socket require permission [644]", + "docker directory chmod /etc/docker 755" +] +_help = '' +_remind = 'This solution strengthens the protection of Docker files and prevents intruders from tampering with Docker files. ' + + +def check_run(): + dir_list = [ + ['/usr/lib/systemd/system/docker.service', 644, 'root'], + ['/usr/lib/systemd/system/docker.socket', 644, 'root'], + ['/etc/docker', 755, 'root'] + ] + + not_mode_list = [] + for d in dir_list: + if not os.path.exists(d[0]): + continue + u_mode = public.get_mode_and_user(d[0]) + if u_mode['user'] != d[2]: + not_mode_list.append("{} Current permissions: {} : {} Security permissions: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + if int(u_mode['mode']) != d[1]: + not_mode_list.append("{} Current permissions: {} : {} Security permissions: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + if not_mode_list: + return False, 'The following critical file or directory permission error:{}'.format('、'.join(not_mode_list)) + else: + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_empty_passwd_user.py b/class_v2/safe_warning_v2/sw_empty_passwd_user.py new file mode 100644 index 00000000..729578a1 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_empty_passwd_user.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, public + +_title = 'Check if an empty password user exists' +_version = 1.0 # 版本 +_ps = "Check if an empty password user exists" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_empty_passwd_user.pl") +_tips = [ + "Log in to server as root, set password for empty password user ", + "If you do not know the user's purpose, you can execute the command [passwd -l (username)] to temporarily block the user." + "Unlock user command [passwd-fu (username)]" +] +_help = '' +_remind = 'detects the existence of blank password users, may be hackers reserved backdoor users, if not business needs to suggest setting a password. ' + + +def check_run(): + ''' + @name 开始检测 + @author lwh<2023-11-21> + @return tuple (status,msg) + ''' + user_list = [] + try: + output, err = public.ExecShell('awk -F: \'($2 == "") {print}\' /etc/shadow') + if err == '' and output != '': + output_list = output.strip().split('\n') + for op in output_list: + user_list.append(op.split(':')[0]) + if len(user_list)>0: + return False, 'Found an empty password user【{}】'.format('、'.join(user_list)) + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_file_lock.py b/class_v2/safe_warning_v2/sw_file_lock.py new file mode 100644 index 00000000..7b7f5f8e --- /dev/null +++ b/class_v2/safe_warning_v2/sw_file_lock.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, re, public + + +_title = '设置关键文件底层属性' +_version = 1.0 # 版本 +_ps = "检查关键文件的底层属性是否配置" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_file_lock.pl") +_tips = [ + "给系统日志文件【/var/log/messages】添加只可追加属性chattr +a", + "给关键文件【/etc/passwd /etc/shadow /etc/group /etc/gshadow】添加锁属性chattr +i" +] +_help = '' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + result_list = [] + result_str1 = public.ExecShell('lsattr -l /var/log/messages*')[0].strip() + tmp_list1 = result_str1.split('\n') + # 执行lsattr -l查看文件特殊属性,若存在特殊属性,则判断是否为“追加属性”,若为否,则加入到result_list,最终显示到面板中 + for tl1 in tmp_list1: + if not "Append_Only" in tl1: + log1 = re.search(r'.*?\s', tl1) + result_list.append(log1.group().strip()) + result_str2 = public.ExecShell('lsattr -l /etc/passwd /etc/shadow /etc/group /etc/gshadow')[0].strip() + tmp_list2 = result_str2.split('\n') + # immutable判断是否为锁属性 + for tl2 in tmp_list2: + if not "Immutable" in tl2: + log2 = re.search(r'.*?\s', tl2) + result_list.append(log2.group().strip()) + if result_list: + return False, '以下文件未配置适当的底层属性:{}'.format('、'.join(result_list)) + else: + return True, 'Risk-free' + diff --git a/class_v2/safe_warning_v2/sw_file_mod.py b/class_v2/safe_warning_v2/sw_file_mod.py new file mode 100644 index 00000000..a61babf0 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_file_mod.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 开启地址空间布局随机化 +# ------------------------------------------------------------------- +import sys, os +os.chdir('/www/server/panel') +sys.path.append("class/") +import os, sys, re, public + +_title = 'Critical file permission checks' +_version = 1.0 # 版本 +_ps = "Critical file permission checks" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_file_mod.pl") +_tips = [ + "On the [File] page, set the correct permissions and owner for the specified directory or file", +] +_help = '' + +def check_run(): + dir_list = [ + ['/etc/shadow', 400, 'root'], + ['/etc/security', 600, 'root'], + ['/etc/passwd', 644, 'root'], + ['/etc/services', 644, 'root'], + ['/etc/group', 644, 'root'], + ['/var/spool/cron/root',600, 'root'], + ['/etc/ssh/sshd_config',644,'root'], + ['/etc/sysctl.conf',644,'root'], + ['/etc/crontab',644,'root'], + ['/etc/hosts.deny',644,'root'], + ['/etc/hosts.allow',644,'root'], + ['/etc/gshadow',400,'root'], + ['/etc/passwd',644,'root'], + ['/etc/shadow',400,'root'], + ['/etc/group',644,'root'], + ['/etc/gshadow',400,'root'], + ] + + not_mode_list = [] + + for d in dir_list: + if not os.path.exists(d[0]): continue + u_mode = public.get_mode_and_user(d[0]) + if u_mode['user'] != d[2]: + not_mode_list.append("{} Current permissions: {} : {} Suggested changes to: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + if int(u_mode['mode']) != d[1]: + not_mode_list.append("{} Current permissions: {} : {} Suggested changes to: {} : {}".format(d[0],u_mode['mode'],u_mode['user'],d[1],d[2])) + + if not_mode_list: + return False,'The following critical files or directories have permission errors:
                                    ' + ("
                                    ".join(not_mode_list)) + else: + return True,"Risk-free" + + +# check_run() \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_files_recycle_bin.py b/class_v2/safe_warning_v2/sw_files_recycle_bin.py new file mode 100644 index 00000000..0a197ec8 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_files_recycle_bin.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测是否开启文件回收站 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'File Recycle Bin' +_version = 1.0 # 版本 +_ps = "Check whether the file recycle bin is open" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_files_recycle_bin.pl") +_tips = [ + "On the [File] page, [Recycle Bin] - opens the [File Recycle Bin] function" + ] + +_help = '' +_remind = 'This solution prevents files from being deleted by mistake and restores them through the recycle bin in time. ' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-05> + @return tuple (status,msg) + ''' + if not os.path.exists('/www/server/panel/data/recycle_bin.pl'): + return False,'The function of [File Recycle Station] is not enabled at present. There is a risk that files cannot be retrieved in case of being deleted by mistake' + return True,'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_firewall_open.py b/class_v2/safe_warning_v2/sw_firewall_open.py new file mode 100644 index 00000000..26058da7 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_firewall_open.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 系统防火墙检测 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'System firewall' +_version = 1.0 # 版本 +_ps = "Check whether the system firewall is enable" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_firewall_open.pl") +_tips = [ + "It is recommended to enable the system firewall to prevent all server ports from being exposed to the Internet. If the server has [security group] function, please ignore this prompt", + "Note: To open the system firewall, the ports that need to be opened, especially SSH and panel ports, should be added to the release list in advance, otherwise the server may not be able to access them" + ] + +_help = '' +_remind = 'This solution can reduce the risk surface of the server exposure and enhance the protection of the website. However, you need to add the port that needs to be opened at the port rule, otherwise the website will be unreachable. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2022-08-18> + @return tuple (status,msg) + ''' + status = public.get_firewall_status() + if status == 1: + return True,'Risk-free' + elif status == -1: + return False,'System firewall is not installed, there are security risks' + else: + return False,'System firewall is not installed, there are security risks' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_ftp_login.py b/class_v2/safe_warning_v2/sw_ftp_login.py new file mode 100644 index 00000000..b080de46 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ftp_login.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# coding: utf-8 + +import re, os, public + +_title = 'Disable anonymous FTP login' +_version = 1.0 # 版本 +_ps = "Disable Anonymous Login FTP Detection" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-3-15' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ftp_login.pl") +_tips = [ + "Modify the value of NoAnonymous to yes in the [/www/server/pure-ftpd/etc/pure-ftpd.conf] configuration file", +] +_help = '' +_remind = 'This scheme can enhance the FTP server protection, prevent illegal intrusion into the server. Unable to log in to the FTP server using Anonymous after configuration. ' + +def check_run(): + + if os.path.exists('/www/server/pure-ftpd/etc/pure-ftpd.conf'): + try: + info_data = public.ReadFile('/www/server/pure-ftpd/etc/pure-ftpd.conf') + if info_data: + if re.search(r'.*NoAnonymous\s*yes', info_data): + return True, 'Risk-free' + else: + return False, 'Currently pure-ftpd does not disable anonymous login, modify/add the value of NoAnonymous to yes in the [pure-ftpd.conf] file' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ftp_pass.py b/class_v2/safe_warning_v2/sw_ftp_pass.py new file mode 100644 index 00000000..4c0e524d --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ftp_pass.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: linxiao +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# FTP弱口令检测 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os,public + +_title = 'Weak password detection for FTP services' +_version = 2.0 # 版本 +_ps = "Detect enabled weak passwords for FTP services" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-12' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ftp_pass.pl") +_tips = [ + "Please go to [FTP] page to change the FTP password ", + "Note: Please do not use too simple account password, so as not to cause security risks ", + "Strong passwords are recommended: numeric, upper - and lowercase, special characters, and no less than seven characters long." , + "Using [Fail2ban] plugin to protect FTP server" +] + +_help = '' +_remind = 'This scheme can strengthen the protection of the FTP server, to prevent intruders from blasting into the FTP server. ' + + +def check_run(): + """检测FTP弱口令 + @author linxiao<2020-9-19> + @return (bool, msg) + """ + + pass_info = public.ReadFile("/www/server/panel/config/weak_pass.txt") + if not pass_info: return True, 'Risk-free' + pass_list = pass_info.split('\n') + data = public.M("ftps").select() + ret = "" + for i in data: + if i['password'] in pass_list: + ret += "FTP:" + i['name'] + "weak passwords exist:" + i['password'] + "\n" + if ret: + # print(ret) + return False, ret + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ftp_root.py b/class_v2/safe_warning_v2/sw_ftp_root.py new file mode 100644 index 00000000..3756e67a --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ftp_root.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# coding: utf-8 + +import re, os, public + +_title = 'Forbid the root user to log in to FTP' +_version = 1.0 # 版本 +_ps = "Prohibit the root user from logging in to FTP inspection" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-3-15' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ftp_root.pl") +_tips = [ + "Modify the value of MinUID to 100 in the [/www/server/pure-ftpd/etc/pure-ftpd.conf] configuration file", +] +_help = '' +_remind = 'This solution can be used to enhance the protection of your FTP server. After configuration, the root user cannot login ftp, use with caution. ' + +def check_run(): + if os.path.exists('/www/server/pure-ftpd/etc/pure-ftpd.conf'): + try: + info_data = public.ReadFile('/www/server/pure-ftpd/etc/pure-ftpd.conf') + if info_data: + tmp = re.search('\nMinUID\\s*([0-9]{1,4})', info_data) + if tmp: + if int(tmp.group(1).strip()) < 100: + return False, 'Currently pure-ftpd is not configured with security access, modify/add the value of MinUID to 100 in the [pure-ftpd.conf] file' + else: + return True, 'Risk-free' + else: + return False, 'Currently pure-ftpd is not configured with security access, modify/add the value of MinUID to 100 in the [pure-ftpd.conf] file' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ftp_umask.py b/class_v2/safe_warning_v2/sw_ftp_umask.py new file mode 100644 index 00000000..37cefecf --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ftp_umask.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# coding: utf-8 + +import re, os, public +_title = 'User FTP access security configuration' +_version = 1.0 # 版本 +_ps = "User FTP access security configuration checks" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-3-15' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ftp_umask.pl") +_tips = [ + "In [/www/server/pure-ftpd/etc/pure-ftpd.conf] change the value of Umask to 177:077 in the config file", +] +_help = '' +_remind = 'This scheme can enhance the protection of FTP server and reduce the risk of server intrusion.' + + +def check_run(): + + if os.path.exists('/www/server/pure-ftpd/etc/pure-ftpd.conf'): + try: + info_data = public.ReadFile('/www/server/pure-ftpd/etc/pure-ftpd.conf') + if info_data: + if re.search(r'.*Umask\s*177:077', info_data): + return True, 'Risk-free' + else: + return False, 'Currently pure-ftpd is not configured with security access. Modify/add the value of Umask to 177:077 in the [pure-ftpd.conf] file' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_httpd_trace_enable.py b/class_v2/safe_warning_v2/sw_httpd_trace_enable.py new file mode 100644 index 00000000..4f608474 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_httpd_trace_enable.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +# coding: utf-8 + + +import re, os, public +_title = 'Apache TRACE request checks' +_version = 1.0 # 版本 +_ps = "Apache TRACE request checks" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_httpd_trace_enable.pl") +_tips = [ + "Set TraceEnable off in [httpd.conf] file and restart Apache server ", + "Or handle security risks with one-click fixes." +] +_help = '' +_remind = 'TRACE request is generally used to test HTTP protocol. Attackers may use TRACE request combined with other vulnerabilities to perform cross-site scripting attacks to obtain sensitive information.' + + +def check_run(): + ''' + @name + @author lwh<2023-11-22> + @return tuple (status,msg) + ''' + + if os.path.exists('/www/server/apache/conf/httpd.conf'): + try: + info_data = public.ReadFile('/www/server/apache/conf/httpd.conf') + if info_data: + if not re.search('TraceEnable off', info_data): + return False, 'TRACE requests are not currently disabled by Apache. Set TraceEnable off in the [httpd.conf] file' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_httpd_version_leak.py b/class_v2/safe_warning_v2/sw_httpd_version_leak.py new file mode 100644 index 00000000..09168516 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_httpd_version_leak.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# coding: utf-8 + + +import re, os, public +_title = 'Apache version leak' +_version = 1.0 # 版本 +_ps = "Apache Version leak check" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-14' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_httpd_version_leak.pl") +_tips = [ + "Add ServerSignature Off and ServerTokens Prod to the [httpd.conf] file", +] +_help = '' +_remind = 'This solution can enhance the protection of your server and reduce the risk of your website being compromised. ' + +def check_run(): + ''' + @name + @author + @return tuple (status,msg) + ''' + + if os.path.exists('/www/server/apache/conf/httpd.conf'): + try: + info_data = public.ReadFile('/www/server/apache/conf/httpd.conf') + if info_data: + if not re.search('ServerSignature', info_data) and not re.search('ServerTokens', + info_data): + return True, 'Risk-free' + if re.search('ServerSignature Off', info_data) and re.search('ServerTokens Prod', + info_data): + return True, 'Risk-free' + else: + return False, 'Currently Apache has a version leak problem, please add ServerSignature Off and ServerTokens Prod in the [httpd.conf] file' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_kernel_space.py b/class_v2/safe_warning_v2/sw_kernel_space.py new file mode 100644 index 00000000..11507463 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_kernel_space.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 开启地址空间布局随机化 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = 'Enable kernel.randomize_va_space' +_version = 1.0 # 版本 +_ps = "Enable kernel.randomize_va_space" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_kernel_space.pl") +_tips = [ + "[/proc/sys/kernel/randomize_va_space] value is 2: ", + "How to set:sysctl -w kernel.randomize_va_space=2", +] +_help = '' +_remind = 'This scheme can reduce the risk of intrusers using buffer overflow to attack the server, and strengthen the protection of the server. ' +def check_run(): + try: + if os.path.exists("/proc/sys/kernel/randomize_va_space"): + randomize_va_space=public.ReadFile("/proc/sys/kernel/randomize_va_space") + if int(randomize_va_space)!=2: + return False, 'Enable kernel.randomize_va_space' + else: + return True,"Risk-free" + except: + return True, "Risk-free" \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_login_fail_limit.py b/class_v2/safe_warning_v2/sw_login_fail_limit.py new file mode 100644 index 00000000..e9ddc545 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_login_fail_limit.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + + +_title = 'Check account authentication failure limit' +_version = 1.0 # 版本 +_ps = "Check whether to limit the number of account authentication failures" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-20' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_login_fail_limit.pl") +_tips = [ + "Add or modify the second line of the [/etc/pam.d/sshd] file", + "auth required pam_tally2.so onerr=fail deny=5 unlock_time=300 even_deny_root root_unlock_time=300" +] +_help = '' +_remind = 'This reduces the risk of a server being blown up. Be sure to remember your login password, though, in case you get locked out of your account for five minutes because of too many failed login attempts. ' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + cfile = '/etc/pam.d/sshd' + if not os.path.exists(cfile): + return True, 'Risk-free' + conf = public.readFile(cfile) + rep = r".*auth(\s*)required(\s*)pam_tally[2]?\.so.*deny(\s*)=.*unlock_time(\s*)=.*even_deny_root.*root_unlock_time(\s*)=" + tmp = re.search(rep, conf) + if tmp: + if tmp.group()[0] == '#': + return False, 'The limit on the number of authentication failures is not configured or is improperly configured' + return True, 'Risk-free' + else: + return False, 'The limit on the number of authentication failures is not configured or is improperly configured' + diff --git a/class_v2/safe_warning_v2/sw_login_message.py b/class_v2/safe_warning_v2/sw_login_message.py new file mode 100644 index 00000000..be981c0f --- /dev/null +++ b/class_v2/safe_warning_v2/sw_login_message.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测用户登录通知 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'SSH user login notification' +_version = 1.0 # 版本 +_ps = "Check whether SSH user login notification is enabled" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_login_message.pl") +_tips = [ + "On the [Security] page, [SSH security management] - [login alarm] enable the [monitor root login] function" + ] + +_help = '' + + +def return_bashrc(): + if os.path.exists('/root/.bashrc'):return '/root/.bashrc' + if os.path.exists('/etc/bashrc'):return '/etc/bashrc' + if os.path.exists('/etc/bash.bashrc'):return '/etc/bash.bashrc' + return '/root/.bashrc' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-04> + @return tuple (status,msg) + ''' + + data = public.ReadFile(return_bashrc()) + if not data: return True,'Risk-free' + if re.search('ssh_security.py login', data): + return True,'Risk-free' + else: + return False,'SSH user login notification is not configured, so it is impossible to know whether the server has been illegally logged in in the first place' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_login_user.py b/class_v2/safe_warning_v2/sw_login_user.py new file mode 100644 index 00000000..274bf5b9 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_login_user.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测风险用户 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'Risk User' +_version = 1.0 # 版本 +_ps = "Detect if there is a risk user in the system user list" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_login_user.pl") +_tips = [ + "If these users are not added by the server administrator, the system may have been compromised and should be dealt with as soon as possible." + ] + +_help = '' + + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-04> + @return tuple (status,msg) + ''' + + u_list = get_ulist() + + try_users = [] + for u_info in u_list: + if u_info['user'] == 'root': continue + if u_info['pass'] == '*': continue + if u_info['uid'] == 0: + try_users.append(u_info['user'] + ' > Unknown administrator user [high risk]') + + if u_info['login'] in ['/bin/bash','/bin/sh']: + try_users.append(u_info['user'] + ' > Logged-in user [medium risk]') + + if try_users: + return False, 'There are security risks for the following users:
                                    ' + ('
                                    '.join(try_users)) + + return True,'Risk-free' + + + +#获取用户列表 +def get_ulist(): + u_data = public.readFile('/etc/passwd') + u_list = [] + for i in u_data.split("\n"): + u_tmp = i.split(':') + if len(u_tmp) < 3: continue + u_info = {} + u_info['user'],u_info['pass'],u_info['uid'],u_info['gid'],u_info['user_msg'],u_info['home'],u_info['login'] = u_tmp + u_list.append(u_info) + return u_list \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_memcached_port.py b/class_v2/safe_warning_v2/sw_memcached_port.py new file mode 100644 index 00000000..1cefc107 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_memcached_port.py @@ -0,0 +1,50 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# Memcached安全检测 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Memcached security' +_version = 1.0 # 版本 +_ps = "Check whether the current Memcached is safe" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_memcached_port.pl") +_tips = [ + "Do not configure bindIP for Memcached as 0.0.0.0 unless necessary", + "If bindIP is 0.0.0.0, be sure to set IP access restrictions through the [SYS firewall] plugin or the Security group" + ] +_help = '' +_remind = 'This solution can reduce the risk exposure of the server and strengthen the protection of the website. However, it is necessary to set the accessible IP according to the business requirements. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + ''' + + p_file = '/etc/init.d/memcached' + p_body = public.readFile(p_file) + if not p_body: return True,'Risk-free' + + tmp = re.findall(r"^\s*IP=(0\.0\.0\.0)",p_body,re.M) + if not tmp: return True,'Risk-free' + + tmp = re.findall(r"^\s*PORT=(\d+)",p_body,re.M) + + result = public.check_port_stat(int(tmp[0]),public.GetClientIp()) + if result == 0: + return True,'Risk-free' + + return False,'The current Memcached port: {} allows arbitrary client access, which can lead to data leakage'.format(tmp[0]) + diff --git a/class_v2/safe_warning_v2/sw_mongodb_auth.py b/class_v2/safe_warning_v2/sw_mongodb_auth.py new file mode 100644 index 00000000..2e7873f4 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_mongodb_auth.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + +_title = 'Whether to enable security authentication for MongoDB' +_version = 1.0 # 版本 +_ps = "Check whether MongoDB security authentication is enabled" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_mongodb_auth.pl") +_tips = [ + "Turn on the security authentication switch in the aaPanel Databases MongoDB", +] +_help = '' +_remind = 'This is a great way to protect your database against hackers trying to steal data from your mongo database. ' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + if not public.process_exists("mongod"): + return True, 'Risk-free,The MongoDB service has not been started!' + cfile = '{}/mongodb/config.conf'.format(public.get_setup_path()) + conf = public.readFile(cfile) + rep = r".*authorization(\s*):(\s*)enabled" + tmp = re.search(rep, conf) + if tmp: + return True, 'Risk-free' + else: + return False, 'MongoDB security authentication is not enabled' + diff --git a/class_v2/safe_warning_v2/sw_mysql_pass.py b/class_v2/safe_warning_v2/sw_mysql_pass.py new file mode 100644 index 00000000..c57484ac --- /dev/null +++ b/class_v2/safe_warning_v2/sw_mysql_pass.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# Mysql 弱口令检测 +# ------------------------------------------------------------------- + +import public, os +_title = 'Mysql weak password detection' +_version = 1.0 # 版本 +_ps = "Mysql weak password detection" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_mysql_pass.pl") +_tips = [ + "If a weak password is detected, please change the password in time" +] +_help = '' +_remind = 'This scheme increases the strength of the database password and reduces the risk of being successfully exploded. ' + +def check_run(): + ''' + @name Mysql 弱口令检测 + @time 2022-08-12 + @author lkq@bt.cn + ''' + pass_info = public.ReadFile("/www/server/panel/config/weak_pass.txt") + if not pass_info: return True, 'Risk-free' + pass_list = pass_info.split('\n') + data=public.M("databases").select() + ret="" + for i in data: + if i['password'] in pass_list: + ret+="Database: "+i['name']+" has weak password: "+i['password']+"\n" + if ret: + return False, ret + else: + return True, 'Risk-free' + diff --git a/class_v2/safe_warning_v2/sw_mysql_port.py b/class_v2/safe_warning_v2/sw_mysql_port.py new file mode 100644 index 00000000..06ac8688 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_mysql_port.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# MySQL端口安全检测 +# ------------------------------------------------------------------- + +import os,sys,re,public,json + +_title = 'MySQL security' +_version = 1.0 # 版本 +_ps = "Checks whether the current server's MySQL port is secure" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-03' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_mysql_port.pl") +_tips = [ + "If not necessary, remove the MySQL port release from the [Security] page", + "Restrict IP access to MySQL port through the [System firewall] plug-in to enhance security", + "Use [ Fail2ban ] plug-in to protect MySQL service" + ] +_help = '' +_remind = 'This scheme strengthens the protection of the MySQL database and reduces the risk of the server being stolen data. Before the repair, open up the accessible IP according to the business requirements to ensure that the website is working properly. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + + @example + status, msg = check_run() + if status: + print('OK') + else: + print('Warning: {}'.format(msg)) + + ''' + mycnf_file = '/etc/my.cnf' + if not os.path.exists(mycnf_file): + return True,'MySQL is not installed' + mycnf = public.readFile(mycnf_file) + port_tmp = re.findall(r"port\s*=\s*(\d+)",mycnf) + if not port_tmp: + return True,'MySQL is not installed' + if not public.ExecShell("lsof -i :{}".format(port_tmp[0]))[0]: + return True,'MySQL is not installed' + result = public.check_port_stat(int(port_tmp[0]),public.GetLocalIp()) + #兼容socket能连通但实际端口不通情况 + if result != 0: + res='' + if os.path.exists('/usr/sbin/firewalld'): + res=public.ExecShell('firewall-cmd --list-all') + elif os.path.exists('/usr/sbin/ufw'): + try: + res=public.ExecShell('sudo ufw status verbose') + except: + res=public.ExecShell('ufw status verbose') + else: + pass + check_str=' '+port_tmp[0]+'/' + if res[0].find(check_str) == -1: + return True,'Risk-free' + else:return True,'Risk-free' + + + fail2ban_file = '/www/server/panel/plugin/fail2ban/config.json' + if os.path.exists(fail2ban_file): + try: + fail2ban_config = json.loads(public.readFile(fail2ban_file)) + if 'mysql' in fail2ban_config.keys(): + if fail2ban_config['mysql']['act'] == 'true': + return True,'Fail2ban is enabled' + except: pass + + return False,'MySQL port: {}, can be accessed by any server, which may cause MySQL to be cracked by brute force, posing security risks'.format(port_tmp[0]) diff --git a/class_v2/safe_warning_v2/sw_mysql_priv.py b/class_v2/safe_warning_v2/sw_mysql_priv.py new file mode 100644 index 00000000..9b529c17 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_mysql_priv.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: linxiao +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 数据库备份权限检测 +# ------------------------------------------------------------------- + +import os, re, public, panelMysql + +_title = 'Database backup permission detection' +_version = 1.0 # 版本 +_ps = "Check whether the MySQL root user has database backup permissions" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-09-19' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_database_priv.pl") +_tips = [ + "To temporarily access the database without authorization, it is recommended to restore all permissions of the root user.", +] + +_help = '' +_remind = 'This scheme ensures that the root user has the permission to backup the database and ensures that the database backup work is carried out. ' + +def check_run(): + """检测root用户是否具备数据库备份权限 + + @author linxiao<2020-9-18> + @return (bool, msg) + """ + mycnf_file = '/etc/my.cnf' + if not os.path.exists(mycnf_file): + return True, 'Risk-free' + mycnf = public.readFile(mycnf_file) + port_tmp = re.findall(r"port\s*=\s*(\d+)", mycnf) + if not port_tmp: + return True, 'Risk-free' + if not public.ExecShell("lsof -i :{}".format(port_tmp[0]))[0]: + return True, 'Risk-free' + + base_backup_privs = ["Lock_tables_priv", "Select_priv"] + select_sql = "Select {} FROM mysql.user WHERE user='root' and " \ + "host=SUBSTRING_INDEX((select current_user()),'@', " \ + "-1);".format(",".join(base_backup_privs)) + select_result = panelMysql.panelMysql().query(select_sql) + if not select_result: + return False, "The root user has insufficient authority to execute mysqldump backup." + select_result = select_result[0] + for priv in select_result: + if priv.lower() != "y": + return False, "The root user has insufficient authority to execute mysqldump backup." + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_nginx_malware.py b/class_v2/safe_warning_v2/sw_nginx_malware.py new file mode 100644 index 00000000..16a052b7 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_nginx_malware.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, public + +_title = 'Check if the nginx config file is compromised' +_version = 1.0 # 版本 +_ps = "Check if the nginx config file is compromised" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_nginx_malware.pl") +_tips = [ + "Follow the prompts to find the nginx config file and delete the js hang-up content ", + "Restart nginx server" +] +_help = '' +_remind = 'Before fixing it manually, it is recommended to make a backup of the configuration file in case the service fails to start. Or use one click to fix ' + + +def check_run(): + ''' + @name 开始检测 + @author lwh<2023-11-21> + @return tuple (status,msg) + ''' + import glob + path = '/www/server/panel/vhost/nginx/' + risk_file = [] + for filename in glob.glob(os.path.join(path, '*.conf')): + if os.path.isdir(filename): + continue + output = public.ReadFile(filename) + if "sub_filter" in output: + risk_file.append(filename) + if len(risk_file) > 0: + return False, 'The following nginx files contain malicious content: {}'.format('、'.join(risk_file)) + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_nginx_md5.py b/class_v2/safe_warning_v2/sw_nginx_md5.py new file mode 100644 index 00000000..e97c7dbb --- /dev/null +++ b/class_v2/safe_warning_v2/sw_nginx_md5.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, public + +_title = 'Check nginx binaries for tampering' +_version = 1.0 # 版本 +_ps = "Check nginx binaries for tampering" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_nginx_md5.pl") +_tips = [ + "Reinstall Nginx in panel [Software Store] - [Run Environment] ", +] +_help = '' +_remind = '/ WWW/server/nginx/sbin/nginx executable been tampered with, risk was invaded site. ' + + +def check_run(): + ''' + @name 开始检测 + @author lwh<2023-11-21> + @return tuple (status,msg) + ''' + nginx_path = '/www/server/nginx/sbin/nginx' + nginx_md5 = '/www/server/panel/data/nginx_md5.pl' + if not os.path.exists(nginx_path): + return True, 'Risk-free' + try: + new_md5 = public.ExecShell('md5sum {}'.format(nginx_path))[0].strip().split(" ")[0] + if os.path.exists(nginx_md5): + old_md5 = public.ReadFile(nginx_md5).split(" ")[0] + if new_md5 != old_md5: + return False, "nginx file tampering has been detected(MD5:{})".format(new_md5) + return True, 'Risk-free' + except: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_nginx_server.py b/class_v2/safe_warning_v2/sw_nginx_server.py new file mode 100644 index 00000000..13ba7615 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_nginx_server.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# Nginx 版本泄露 +# ------------------------------------------------------------------- + +import re, public, os +_title = 'Nginx version leaked' +_version = 1.0 # 版本 +_ps = "Nginx version leaked" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_nginx_server.pl") +_tips = [ + "Set [server_tokens off;] in the [/www/server/nginx/conf/nginx.conf] file;", + "Tips:server_tokens off;" +] +_help = '' +_remind = 'This solution enhances server protection and reduces the risk of your website being compromised. ' +def check_run(): + ''' + @name 检测nginx版本泄露 + @author lkq<2020-08-10> + @return tuple (status,msg) + ''' + + if os.path.exists('/www/server/nginx/conf/nginx.conf'): + try: + info_data = public.ReadFile('/www/server/nginx/conf/nginx.conf') + if info_data: + if re.search('server_tokens off;', info_data): + return True, 'Risk-free' + else: + return False, 'The current version of Nginx is leaked, please add or modify the parameter server_tokens to off; in the Nginx configuration file, for example: server_tokens off;' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_panel_control.py b/class_v2/safe_warning_v2/sw_panel_control.py new file mode 100644 index 00000000..8de58d04 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_panel_control.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# Mysql 弱口令检测 +# ------------------------------------------------------------------- + +import public, os +_title = 'The panel is not monitoring' +_version = 1.0 # 版本 +_ps = "The panel is not monitoring" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_panel_control.pl") +_tips = [ + "Open it in [Monitor] - [System Monitor]" +] +_help = '' +_remind = 'Enable server monitoring, you can record the recent operation of the server, to facilitate the troubleshooting of system anomalies. ' +def check_run(): + ''' + @name 面板未开启监控 + @time 2022-08-12 + @author lkq@bt.cn + ''' + global _tips + send_type = "" + if os.path.exists("/www/server/panel/data/control.conf"): + return True, 'Risk-free' + return False, _tips[0] + + + diff --git a/class_v2/safe_warning_v2/sw_panel_pass.py b/class_v2/safe_warning_v2/sw_panel_pass.py new file mode 100644 index 00000000..0fe48580 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_panel_pass.py @@ -0,0 +1,1156 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 是否修改面板默认帐号密码 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Panel password' +_version = 1.0 # 版本 +_ps = "Check whether the panel account password is safe" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_panel_pass.pl") +_tips = [ + "Please go to the [Settings] page to modify the panel account password", + "Note: Please do not use too simple account password, so as not to cause security risks" + ] +_help = '' +_remind = 'This scheme can strengthen the complexity of the panel login account password and reduce the risk of being successfully exploded. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-04> + @return tuple (status,msg) + ''' + + default_file = '/www/server/panel/default.pl' + if not os.path.exists(default_file): + return True,'Risk-free' + default_pass = public.readFile(default_file).strip() + + p1 = password_salt(public.md5(default_pass),uid=1) + find = public.M('users').where('id=?',(1,)).field('username,password').find() + if p1 == find['password']: + return False,'The default password of the panel has not been modified, and there is a security risk' + + lower_pass_txt = '''12123 +china +test +test12 +test11 +test1 +test2 +test123 +bt.cn +www.bt.cn +admin +root +12345 +123456 +123456789 +111111 +from91 +12345678 +123123 +5201314 +000000 +11111111 +a123456 +163.com +fill.com +123321 +123123123 +00000000 +1314520 +7758521 +1234567 +666666 +123456a +1234567890 +woaini +a123456789 +888888 +88888888 +147258369 +qq123456 +654321 +zxcvbnm +woaini1314 +112233 +5211314 +123456abc +520520 +aaaaaa +123654 +987654321 +123456789a +12345 +7758258 +100200 +147258 +111222 +abc123456 +111222tianya +121212 +1111111 +abc123 +110110 +admin123 +789456 +q123456 +123456aa +aa123456 +asdasd +999999 +123qwe +789456123 +1111111111 +1314521 +iloveyou +qwerty +password +qazwsx +159357 +222222 +woaini520 +woaini123 +521521 +asd123 +qqqqqq +qq1111 +1234 +qwe123 +111111111 +1qaz2wsx +qwertyuiop +5201314520 +asd123456 +159753 +31415926 +qweqwe +555555 +333333 +woaini521 +abcd1234 +ASDFGHJKL +123456qq +11223344 +456123 +123000 +123698745 +wangyut2 +201314 +zxcvbnm123 +qazwsxedc +1q2w3e4r +z123456 +123abc +a123123 +12345678910 +asdfgh +456789 +qwe123456 +321321 +123654789 +456852 +0000000000 +WOAIWOJIA +741852963 +5845201314 +aini1314 +0123456789 +a321654 +123456123 +584520 +778899 +520520520 +7777777 +q123456789 +123789 +zzzzzz +qweasdzxc +5845211314 +123456q +w123456 +12301230 +qq123456789 +wocaonima +qq123123 +a5201314 +a12345678 +asdasdasd +a1234567 +147852 +110120 +135792468 +CAONIMA +963852741 +3.1415926 +1234560 +101010 +7758520 +753951 +666888 +zxc123 +0000000 +zhang123 +987654 +a111111 +1233211234567 +789789 +25257758 +7708801314520 +zzzxxx +1111 +999999999 +1357924680 +yahoo.com.cn +123456789q +12341234 +5841314520 +zxc123456 +yangyang +168168 +123123qaz +abcd123456 +456456 +963852 +as123456 +741852 +xiaoxiao +1230123 +555666 +000000000 +369369 +211314 +102030 +aaa123456 +zxcvbn +110110110 +buzhidao +qaz123 +123456. +asdfasdf +123456789. +woainima +123456ASD +woshishui +131421 +123321123 +dearbook +1234qwer +qaz123456 +aaaaaaaa +111222333 +qq5201314 +3344520 +147852369 +1q2w3e +windows +123456987 +zz12369 +qweasd +qiulaobai +66666666 +12344321 +qwer1234 +a12345 +7894561230 +qwqwqw +777777 +110120119 +951753 +wmsxie123 +131420 +1314520520 +369258147 +321321321 +110119 +beijing2008 +321654 +a000000 +147896325 +12121212 +123456aaa +521521521 +22222222 +888999 +123456789ABC +abc123456789 +12345678900 +1q2w3e4r5t +1234554321 +www123456 +w123456789 +336699 +abcdefg +709394 +258369 +z123456789 +314159 +584521 +12345678a +7788521 +9876543210 +258258 +111111a +87654321 +123asd +5201314a +134679 +135246 +hotmail.com +123123a +11112222 +131313 +100200300 +11111 +1234567899 +520530 +251314 +qq66666 +yahoo.cn +123456qwe +worinima +sohu.com +NULL +518518 +123457 +q1w2e3r4 +721521 +123456789QQ +584131421 +qw123456 +123456.. +0123456 +135790 +3344521 +980099 +a1314520 +123456123456 +qazwsx123 +asdf1234 +444444 +123456z +120120 +wang123456 +12345600 +7758521521 +12369874 +abcd123 +a12369 +li123456 +1234567891 +wang123 +1234abcd +147369 +zhangwei +qqqqqqqq +521125 +010203 +369258 +654123 +woailaopo +QAZQAZ +121314 +1qazxsw2 +zxczxc +l123456 +111000 +jingjing +0000 +1472583690 +25251325 +langzi123 +wojiushiwo +7895123 +wangjian +123qweasd +110120130 +1123581321 +142536 +584131420 +aaa123 +aaa111 +woaiwoziji +520123 +665544 +ab123456 +a123456a +fuckyou +99999999 +5203344 +qwertyui +521314 +18881888 +584201314 +woaini@ +7654321 +20082008 +520131 +124578 +852456 +nihaoma +74108520 +232323 +55555555 +zx123456 +wwwwww +119119 +weiwei +13145200 +LOVE1314 +564335 +123456789123 +wo123456 +123520 +52013145201314 +loveyou +wolf8637 +112358 +5201314123 +yuanyuan +zhanglei +zz123456 +1234567A +a11111 +000000a +321654987 +xiaolong +5841314521 +shmily +520025 +159951 +77585210 +tiantian +134679852 +QWASZX +123456654321 +20080808 +zhangjian +123465 +9958123 +159159 +5508386 +wangwei +5205201314 +woaini5201314 +888666 +52013141314 +qweqweqwe +1122334455 +123456789z +585858 +33333333 +aa123123 +qwertyuiop123 +q111111 +9638527410 +911911 +qqq111 +5213344 +sunshine +liu123456 +abcdef +zhendeaini +007007 +555888 +qq111111 +jiushiaini +mnbvcxz +xiaoqiang +445566 +nicholas +dongdong +123456abcd +111qqq +aptx4869 +258456 +wobuzhidao +qazxsw +123789456 +zhang123456 +7215217758991 +1234567890123 +...... +huang123 +maomao +222333 +wangyang +123456789aa +1.23457E+11 +1234566 +1230456 +1a2b3c4d +13141314 +a7758521 +123456zxc +123456as +forever +s123456 +12348765 +xxxxxx +asdf123 +a1b2c3d4 +246810 +333666 +mingming +000123 +jiajia +12qwaszx +ffffff +112233445566 +77585211314 +520131400 +aa123456789 +wpc000821 +WANGJING +woaini1314520 +  +000111 +qq1314520 +1234512345 +147147 +123456qaz +q123123 +123456ab +xiaofeng +wodemima +shanshan +w2w2w2 +666999 +123456w +321456 +feifei +dragon +computer +dddddd +zhangjie +baobao +x123456 +q1w2e3 +chenchen +12345679 +131452 +caonima123 +asdf123456 +tangkai +52013140 +longlong +ssssss +www123 +1234568 +q1q1q1q1 +asdfghjkl123 +14789632 +123456711 +michael +tingting +woshishei +asd123456789 +1314258 +sunliu66 +qwert12345 +235689 +565656 +1234569 +ww123456 +1314159 +5211314521 +123456789w +123123aa +139.com@163.com +111111q +hao123456 +52tiance +19830122 +y123456 +110119120 +1231230 +sj811212 +13579246810 +123.123 +superman +789123 +12345qwert +770880 +js77777 +zhangyang +686868 +@163.com +imzzhan +xiaoyu +7758521a +abc12345 +nihao123 +wokaonima +q11111 +623623623 +989898 +122333 +13800138000 +laopowoaini +787878 +123456l +a123123123 +198611 +332211 +tom.com +212121 +woaini123456 +wanglei +yang123456 +zhangqiang +zxcvbnm,./ +zhangyan +181818 +234567 +stryker +167669123 +laopo520 +2597758 +aa5201314 +139.com +5201314. +8888888888 +74107410 +zhanghao +77777777 +zhangyu +qwerty123 +zzb19860526 +qwertyu +5201314qq +198612 +q5201314 +999888 +369852 +121121 +1122334 +123456789asd +123zxc +a123321 +QWErtyUIO +456456456 +qq000000 +m123456 +q1w2e3r4t5 +woainilaopo +123456789* +131425 +liuchang +85208520 +zhangjing +c123456 +asdfghjk +qq1234 +asdzxc +hao123 +777888 +131131 +woainia +beyond +zhang520 +556688 +123456qw +wangchao +woshiniba +168888 +7758991 +woshizhu +ainiyiwannian +LAOpo521 +abcd123456789 +qwerasdf +123456ok +woshinidie +huanhuan +1hxboqg2s +meiyoumima +456321 +QQQ123456 +1314 +898989 +123456798 +pp.com@163.com +mm123456 +123698741 +a520520 +z321321 +asasas +YANG123 +584211314 +1234561 +123456789+ +miaomiao +789789789 +7788520 +AAAAAAa +h123456 +3838438 +l123456789 +198511 +ABCDEFG123 +zhangjun +123qaz +198512 +2525775 +54545454 +789632145 +831213 +10101010 +xiaohe +19861010 +10203 +woshishen +0987654321 +yj2009 +wangqiang +198411 +1314520a +xiaowei +123456000 +123987 +love520 +caonimabi +qwe123123 +010101 +qq666666 +789987 +10161215 +liangliang +qwert123 +112112 +qianqian +1a2b3c +198410 +nuttertools +goodluck +zhangxin +18n28n24a5 +liuyang +998877 +woxiangni +7788250 +a147258369 +zhangliang +16897168 +223344 +123123456 +a1b2c3 +killer +321123 +pp.com +chen123456 +wangpeng +753159 +775852100 +1478963 +1213141516 +369369369 +1236987 +123369 +12345a +bugaosuni +13145201314520 +110112 +123456... +JIAOJIAO +100100 +1314520123 +19841010 +7758521123 +shangxin +woshiwo +12312300 +xingxing +yingying +1233210 +34416912 +qq12345 +qweasd123 +nishizhu +19861020 +qwe123456789 +808080 +1310613106 +456789123 +44444444 +123123qq +3141592653 +556677 +xx123456 +jianjian +a1111111 +0.123456 +198610 +loveme +tianshi +woxihuanni +11235813 +252525 +225588 +lovelove +mengmeng +7758258520 +xiaoming +shanghai +huyiming +6543210 +a7758258 +7788414 +123456789.. +Jordan +nishiwode +ZHUZHU +1314woaini +chenjian +131415 +xy123456 +123456520 +a00000 +jiang123 +WOAIMAMA +monkey +7418529630 +lingling +987456321 +w5201314 +qwer123456 +198412 +asdasd123 +zzzzzzzz +1q1q1q +741741 +987456 +19851010 +2587758 +456654 +Iloveyou1314 +q12345 +imissyou +daniel +aipai +2222222 +0147258369 +123456789l +q1234567 +963963 +123123123123 +125521 +womendeai +baobei520 +19861015 +667788 +000000. +zhangtao +yy123456 +chen123 +nishishui +789654 +liu123 +19861212 +1230 +19841020 +wangjun +wangliang +zhangpeng +woainimama +zhangchao +5201314q +19841025 +123567 +aaaa1111 +123456+ +134679258 +668899 +811009 +qaz123456789 +123456789qwe +111112 +130130 +19861016 +wozhiaini +198712 +123... +abcde12345 +abcd12345 +wanggang +llllll +5121314 +456258 +125125 +qq7758521 +369963 +987987 +142857 +poiuytrewq +qqq123 +323232 +baobei +g227w212 +962464 +mylove +p1a6s3m +202020 +19491001 +963258 +hhhhhh +2582587758 +wangfeng +tiancai +11111111111 +summer +wangwang +asd123123 +19841024 +xinxin +0.0.0. +19861012 +19861210 +8888888 +zhanghui +wenwen +635241 +ASDFGHJ +19861023 +1234567890. +888168 +19861120 +tianya +123aaa +111aaa +123456789aaa +8008208820 +123123q +football +dandan +www123456789 +19861026 +qingqing +315315 +1111122222 +171204jg +19861021 +5555555 +AS123456789 +qqqwww +19861024 +yahoo.com +19861225 +1qaz1qaz +19871010 +1029384756 +123258 +zxcv123 +19861123 +1314520. +aidejiushini +123qwe123 +198711 +operation +19861025 +yu123456 +19851225 +wangshuai +19841015 +520521 +wangyan +19861011 +7007 +123456zz +521000 +198311 +299792458 +112211 +****** +00000 +princess +qwer123 +51201314 +password1 +qazwsxedcrfv +LOVE5201314 +198312 +198510 +888888888 +1314521521 +internet +z123123 +a147258 +696969 +1234321 +476730751 +5201314789 +012345 +19861022 +welcome +aqwe518951 +19861121 +HUANGwei +868686 +wanghao +NIAIWOMA +xiaojian +19851120 +19851212 +100000 +19841022 +zhangbin +shadow +mmmmmm +000... +1357913579 +77585217758521 +19861216 +19841016 +az123456 +zxcv1234 +19841023 +wu123456 +163163 +2008520085 +pppppp +789654123 +EtnXtxSa65 +19851025 +woaiwolaopo +ww111111 +woaini110 +123455 +19841026 +19881010 +www163com +159357456 +fangfang +19851015 +19861013 +19861220 +12312 +19861018 +19861028 +a11111111 +19841018 +119911 +AI123456 +198211 +55555 +zhangkai +wangxin +xihuanni +19871024 +19861218 +16899168 +1010110 +nimabi +19861125 +52013143344 +131452000 +19871020 +freedom +baobao520 +winner +123456m +12312312 +''' + + lower_pass = lower_pass_txt.split("\n") + + for lp in lower_pass: + if not lp: continue + if lp == find['username']: + return False,'The user name of the current panel is: {}, which is too simple and poses security risks'.format(lp) + p1 = password_salt(public.md5(lp),uid=1) + if p1 == find['password']: + return False,'The current panel password is too simple and there is a security risk' + + lp = lp.upper() + if lp == find['username']: + return False,'The user name of the current panel is: {}, which is too simple and poses security risks'.format(lp) + p1 = password_salt(public.md5(lp),uid=1) + if p1 == find['password']: + return False,'The current panel password is too simple and there is a security risk' + + lower_rule = 'qwertyuiopasdfghjklzxcvbnm1234567890' + for s in lower_rule: + for i in range(12): + if not i: continue + lp = s * i + if lp == find['username']: + return False,'The user name of the current panel is: {}, which is too simple and poses security risks'.format(lp) + p1 = password_salt(public.md5(lp),uid=1) + if p1 == find['password']: + return False,'The current panel password is too simple and there is a security risk' + + lp = s.upper() * i + if lp == find['username']: + return False,'The user name of the current panel is: {}, which is too simple and poses security risks'.format(lp) + p1 = password_salt(public.md5(lp),uid=1) + if p1 == find['password']: + return False,'The current panel password is too simple and there is a security risk' + if not is_strong_password(find["password"]): + return False, 'The current panel password is too simple and there is a security risk' + return True,'Risk-free' + +salt = None + +def password_salt(password,username=None,uid=None): + ''' + @name 为指定密码加盐 + @author hwliang<2020-07-08> + @param password string(被md5加密一次的密码) + @param username string(用户名) 可选 + @param uid int(uid) 可选 + @return string + ''' + global salt + if not salt: + salt = public.M('users').where('id=?',(uid,)).getField('salt') + if salt: + salt = salt[0] + else: + salt = "" + return public.md5(public.md5(password+'_bt.cn')+salt) + + +def is_strong_password(password): + """判断密码复杂度是否安全 + + 非弱口令标准:长度大于等于7,分别包含数字、小写、大写、特殊字符。 + @password: 密码文本 + @return: True/False + @author: linxiao<2020-9-19> + """ + + if len(password) < 7: + return False + + import re + digit_reg = "[0-9]" # 匹配数字 +1 + lower_case_letters_reg = "[a-z]" # 匹配小写字母 +1 + upper_case_letters_reg = "[A-Z]" # 匹配大写字母 +1 + special_characters_reg = r"((?=[\x21-\x7e]+)[^A-Za-z0-9])" # 匹配特殊字符 +1 + + regs = [digit_reg, + lower_case_letters_reg, + upper_case_letters_reg, + special_characters_reg] + + grade = 0 + for reg in regs: + if re.search(reg, password): + grade += 1 + + if grade == 4 or (grade == 3 and len(password) >= 9): + return True + return False diff --git a/class_v2/safe_warning_v2/sw_panel_path.py b/class_v2/safe_warning_v2/sw_panel_path.py new file mode 100644 index 00000000..5a1b52fc --- /dev/null +++ b/class_v2/safe_warning_v2/sw_panel_path.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 面板安全入口检测 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Safe entrance' +_version = 1.0 # 版本 +_ps = "Check the security entrance security of the panel" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_panel_path.pl") +_tips = [ + "Please modify the security entrance on the [Settings] page", + "Set the binding domain name on the [Settings] page, or set authorized IP restrictions", + "Note: Please do not set up too simple safety entrance, which may cause safety hazards" + ] +_help = '' +_remind = 'This solution can strengthen the panel login entry protection, improve server security. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + ''' + + p_file = '/www/server/panel/data/domain.conf' + if public.readFile(p_file): + return True,'Risk-free' + + p_file = '/www/server/panel/data/limitip.conf' + if public.readFile(p_file): + return True,'Risk-free' + + + p_file = '/www/server/panel/data/admin_path.pl' + p_body = public.readFile(p_file) + if not p_body: return False,'No security entrance is set, the panel is at risk of being scanned' + p_body = p_body.strip('/').lower() + if p_body == '': return False,'No security entrance is set, the panel is at risk of being scanned' + + lower_path = ['root','admin','123456','123','12','1234567','12345','1234','12345678','123456789','abc','bt'] + + if p_body in lower_path: + return False,'The security entrance is: {}, too simple, there are potential safety hazards'.format(p_body) + + lower_rule = 'qwertyuiopasdfghjklzxcvbnm1234567890' + for s in lower_rule: + for i in range(12): + if not i: continue + lp = s * i + if p_body == lp: + return False,'The security entrance is: {}, too simple, there are potential safety hazards'.format(p_body) + + return True,'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_panel_port.py b/class_v2/safe_warning_v2/sw_panel_port.py new file mode 100644 index 00000000..94129830 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_panel_port.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 面板端口检测 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Panel port' +_version = 1.0 # 版本 +_ps = "Check whether the current panel port is safe" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-03' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_panel_port.pl") +_tips = [ + "Please modify the default panel port on the [Settings] page", + "Note: Servers with [Security Group] should release the new port in the [Security Group] in advance to prevent the new port cannot be opened" + ] +_help = '' +_remind = 'This solution can strengthen the panel protection and reduce the risk of the panel being attacked. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + ''' + + port_file = '/www/server/panel/data/port.pl' + port = public.readFile(port_file) + if not port: return True,'Rick-free' + port = int(port) + if port != 7800: + return True,'Rick-free' + return False,'The panel port is the default port ({}), which may cause unnecessary security risks'.format(port) + diff --git a/class_v2/safe_warning_v2/sw_panel_swing.py b/class_v2/safe_warning_v2/sw_panel_swing.py new file mode 100644 index 00000000..0d8ddb3f --- /dev/null +++ b/class_v2/safe_warning_v2/sw_panel_swing.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# Mysql 弱口令检测 +# ------------------------------------------------------------------- + +import public, os +_title = 'Panel login alarm' +_version = 1.0 # 版本 +_ps = "Panel login alarm" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_panel_swing.pl") +_tips = [ + "Enable it in [Settings] - [Notification]" +] +_help = '' +_remind = 'This solution can strengthen the panel protection and reduce the risk of the panel being attacked. ' +def check_run(): + ''' + @name 面板登录告警是否开启 + @time 2022-08-12 + @author lkq@bt.cn + ''' + send_type = "" + tip_files = ['panel_login_send.pl','login_send_type.pl','login_send_mail.pl','login_send_dingding.pl'] + for fname in tip_files: + filename = 'data/' + fname + if os.path.exists(filename): + return True, 'Risk-free' + return False, 'Please enable it in [Settings] - [Notification]' + + + diff --git a/class_v2/safe_warning_v2/sw_passwd_repeat.py b/class_v2/safe_warning_v2/sw_passwd_repeat.py new file mode 100644 index 00000000..e880801e --- /dev/null +++ b/class_v2/safe_warning_v2/sw_passwd_repeat.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + + +_title = 'Check password reuse limit' +_version = 1.0 # 版本 +_ps = "Detect whether to limit password reuse times" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_passwd_repeat.pl") +_tips = [ + "Configuration file backup: cp -p /etc/pam.d/system-auth /etc/pam.d/system-auth.bak", + "Add or modify remember=5 after [password sufficient] in [/etc/pam.d/system-auth] file" +] +_help = '' +_remind = 'This scheme enhances server access control protection by limiting the number of times login passwords are reused. ' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + try: + cfile = '/etc/pam.d/system-auth' + conf = public.readFile(cfile) + rep = r"password(\s*)sufficient.*remember(\s*)=(\s*)[1-9]+" + tmp = re.search(rep, conf) + if tmp: + return True, 'Risk-free' + else: + return False, 'Unlimited password reuse' + except: + return True, 'Risk-free' + diff --git a/class_v2/safe_warning_v2/sw_php_backdoor.py b/class_v2/safe_warning_v2/sw_php_backdoor.py new file mode 100644 index 00000000..573c2722 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_php_backdoor.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- +# Time: 2023-08-07 +# ------------------------------------------------------------------- +# PHP.ini挂马 +# ------------------------------------------------------------------- + + +import sys, os + +os.chdir('/www/server/panel') +sys.path.append("class/") + +import public, re, os + +_title = 'PHP configuration file failure detection' +_version = 1.0 # 版本 +_ps = "Check if PHP config file is suspended" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-8-7' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_php_backdoor.pl") +_tips = [ + "According to the risk description, find the corresponding version of PHP plugin in [Software Store] - [Running Environment]. ", + "On the [Configuration] page, go to auto_prepend_file or auto_append_file, delete the rest, save and restart PHP." +] + +_help = '' +_remind = 'This solution removes malicious code from php configuration files and suggests a full Trojan scan of the server to remove backdoor files and fix website vulnerabilities. ' +_type = 'web' + + +def check_run(): + path = "/www/server/php" + # 获取目录下的文件夹 + dirs = os.listdir(path) + result = {} + for dir in dirs: + if dir in ["52", "53", "54", "55", "56", "70", "71", "72", "73", "74", "80", "81"]: + file_path = path + "/" + dir + "/etc/php.ini" + if os.path.exists(file_path): + # 获取文件内容 + try: + php_ini = public.readFile(file_path) + if re.search("\nauto_prepend_file\\s?=\\s?(.+)", php_ini): + prepend = re.findall("\nauto_prepend_file\\s?=\\s?(.+)", php_ini) + if "data:;base64" in prepend[0]: + result[dir] = ["auto_prepend_file"] + if re.search("\nauto_append_file\\s?=\\s?(.+)", php_ini): + append = re.findall("\nauto_append_file\\s?=\\s?(.+)", php_ini) + if "data:;base64" in append[0]: + if dir in result: + result[dir].append("auto_append_file") + else: + result[dir] = ["auto_append_file"] + except: + pass + if result: + ret = "" + for i in result: + ret += "【PHP" + i + "】A field where malicious code is present:" + ",".join(result[i]) + "\n" + return False, ret + else: + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_php_disable_functions.py b/class_v2/safe_warning_v2/sw_php_disable_functions.py new file mode 100644 index 00000000..4e404ced --- /dev/null +++ b/class_v2/safe_warning_v2/sw_php_disable_functions.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# PHP未禁用函数 +# ------------------------------------------------------------------- + + +import sys,os +os.chdir('/www/server/panel') +sys.path.append("class/") + +import public,re,os + +_title = 'PHP Dangerous Functions' +_version = 1.0 # 版本 +_ps = "PHP Dangerous Functions" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_php_php_disable_funcation.pl") +_tips = [ + "[disable_functions] is not set in the [php.ini] file to disable dangerous functions such as system, exec, passthru, shell_exec, popen, proc_open, etc.", + "Tips: [disable_functions] is not set in the [php.ini] file to disable dangerous functions such as system, exec, passthru, shell_exec, popen, proc_open, etc." + ] + +_help = '' +_remind = 'This solution can strengthen the protection of the website and reduce the risk of server intrusion. ' +def check_run(): + path ="/www/server/php" + #获取目录下的文件夹 + dirs = os.listdir(path) + result={} + for dir in dirs: + if dir in ["52","53","54","55","56","70","71","72","73","74","80","81"]: + file_path=path+"/"+dir+"/etc/php.ini" + if os.path.exists(file_path): + #获取文件内容 + try: + php_ini = public.readFile(file_path) + if re.search("\ndisable_functions\\s?=\\s?(.+)",php_ini): + disable_functions = re.findall("\ndisable_functions\\s?=\\s?(.+)",php_ini) + disa_fun=["system","exec","passthru","shell_exec","popen","proc_open","putenv"] + if len(disable_functions) > 0: + disable_functions= disable_functions[0].split(",") + for i2 in disa_fun: + if i2 not in disable_functions: + if dir in result: + result[dir].append(i2) + else: + result[dir]=[i2] + except: + pass + if result: + ret="" + for i in result: + ret+="[PHP "+i+"] Dangerous functions that are not disabled are as follows:"+",".join(result[i])+"\n" + return False,ret + else: + return True, "Risk-free" + diff --git a/class_v2/safe_warning_v2/sw_php_display_errors.py b/class_v2/safe_warning_v2/sw_php_display_errors.py new file mode 100644 index 00000000..8bc6f5dc --- /dev/null +++ b/class_v2/safe_warning_v2/sw_php_display_errors.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- +# Time: 2023-08-05 +# ------------------------------------------------------------------- +# PHP未关闭错误提示 +# ------------------------------------------------------------------- + + +import sys, os + +os.chdir('/www/server/panel') +sys.path.append("class/") + +import public, re, os + +_title = 'PHP is giving an error message' +_version = 1.0 # 版本 +_ps = "Check if PHP is turned off" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-8-5' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_php_display_errors.pl") +_tips = [ + "According to the risk description, find the corresponding version of PHP plugin in [Software Store] - [Running Environment], and in [Configuration Modification] page, set display_errors to off and save." +] + +_help = '' +_remind = "PHP error prompts may reveal sensitive information about your site's applications; This solution prevents website information from leaking by turning off the [display_errors] option" +_type = 'web' + + +def check_run(): + path = "/www/server/php" + # 获取目录下的文件夹 + dirs = os.listdir(path) + result = [] + for dir in dirs: + if dir in ["52", "53", "54", "55", "56", "70", "71", "72", "73", "74", "80", "81"]: + file_path = path + "/" + dir + "/etc/php.ini" + if os.path.exists(file_path): + # 获取文件内容 + try: + php_ini = public.readFile(file_path) + if re.search("\ndisplay_errors\\s?=\\s?(.+)", php_ini): + status = re.findall("\ndisplay_errors\\s?=\\s?(.+)", php_ini) + if 'On' in status or 'on' in status: + result.append(dir[0]+'.'+dir[1]) # 中间加个. + except: + pass + if result: + ret = "The PHP versions that do not turn off error messages are: {}".format('、'.join(result)) + return False, ret + else: + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_php_expose.py b/class_v2/safe_warning_v2/sw_php_expose.py new file mode 100644 index 00000000..de6d8932 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_php_expose.py @@ -0,0 +1,62 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# PHP存在版本泄露 +# ------------------------------------------------------------------- + +# import sys,os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import re,public,os + + +_title = 'PHP version leaked' +_version = 1.0 # 版本 +_ps = "PHP version leaked" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_php_expose.pl") +_tips = [ + "Set [expose_php] in the [php.ini] file and configure it to Off", + "Tips: Set [expose_php] in the [php.ini] file and configure it to Off" + ] + +_help = '' +_remind = 'This solution can prevent the disclosure of sensitive information on the website and reduce the possibility of server intrusion. ' + + +def check_run(): + path ="/www/server/php" + #获取目录下的文件夹 + dirs = os.listdir(path) + resulit=[] + for dir in dirs: + if dir in ["52","53","54","55","56","70","71","72","73","74","80","81"]: + file_path=path+"/"+dir+"/etc/php.ini" + if os.path.exists(file_path): + #获取文件内容 + try: + php_ini = public.readFile(file_path) + #查找expose_php + if re.search("\nexpose_php\\s*=\\s*(\\w+)",php_ini): + expose_php = re.search("\nexpose_php\\s*=\\s*(\\w+)",php_ini).groups()[0] + if expose_php.lower() == "off": + pass + else: + resulit.append(dir) + except: + pass + if resulit: + return False, "The affected php versions are as follows: ["+",".join(resulit)+"], please set expose_php to Off in php.ini" + else: + return True, "Risk-free" + +# check_run() \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_ping.py b/class_v2/safe_warning_v2/sw_ping.py new file mode 100644 index 00000000..dc23d239 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ping.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测是否禁ping +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'ICMP detection' +_version = 1.0 # 版本 +_ps = "Check whether ICMP access is allowed (Block ICMP)" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ping.pl") +_tips = [ + "Turn on the [Block ICMP] function in the [Security] page", + "Note: The server IP or domain name cannot be Ping after it is turned on, please set according to actual needs" + ] + +_help = '' +_remind = 'This scheme can reduce the risk of the real IP of the server being found, and enhance the security of the server. ' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-05> + @return tuple (status,msg) + ''' + + cfile = '/etc/sysctl.conf' + conf = public.readFile(cfile) + rep = r"#*net\.ipv4\.icmp_echo_ignore_all\s*=\s*([0-9]+)" + tmp = re.search(rep,conf) + if tmp: + if tmp.groups(0)[0] == '1': + return True,'Rick-free' + + return False,'If the [Block ICMP] function is not enabled, there is a risk that the server will be attacked or scanned by ICMP' diff --git a/class_v2/safe_warning_v2/sw_pingin.py b/class_v2/safe_warning_v2/sw_pingin.py new file mode 100644 index 00000000..5e0bb2dd --- /dev/null +++ b/class_v2/safe_warning_v2/sw_pingin.py @@ -0,0 +1,47 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测是否禁ping +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = '2222222' +_version = 1.0 # 版本 +_ps = "222222222(禁Ping)" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ping_in.pl") +_tips = [ + "Enable Disable Ping in Security page ", + "Note: You cannot ping the server IP or domain name after opening, please set it according to your actual needs" + ] + +_help = '' + + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-05> + @return tuple (status,msg) + ''' + try: + cfile = '/proc/sys/net/ipv4/icmp_echo_ignore_all' + conf = public.readFile(cfile) + if conf: + if int(conf)!=1: + return False,'The "Ban Ping" function is not enabled at present, there is a risk that the server is attacked by ICMP or swept' + else: + return True,"Risk-free" + except: + return True,"Risk-free" \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_pip_poison.py b/class_v2/safe_warning_v2/sw_pip_poison.py new file mode 100644 index 00000000..db9aa393 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_pip_poison.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + +_title = 'Pypi supply chain poisoning detection' +_version = 1.0 # 版本 +_ps = "Pypi supply chain poisoning detection" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-14' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_pip_poison.pl") +_tips = [ + "Execute the command btpip uninstall [detected malicious library name]", +] +_help = '' +_remind = 'This solution can remove vulnerable packages from the server and prevent them from being exploited by hackers. Before executing the solution command, make sure that the malicious library name is not a dependency library of normal business, otherwise it may affect the operation of the website. ' + +def check_run(): + pip = public.ExecShell("btpip freeze | grep -E \"istrib|djanga|easyinstall|junkeldat|libpeshka|mumpy|mybiubiubiu|nmap" + "-python|openvc|python-ftp|pythonkafka|python-mongo|python-mysql|python-mysqldb|python" + "-openssl|python-sqlite|virtualnv|mateplotlib|request=\"")[0].strip() + if 'command not found' in pip or 'command not found' in pip: + return True, 'Risk-free,pip is not installed' + if pip: + pip = pip.split('\n') + return False, '【{}】security risk in the python library, please deal with it as soon as possible'.format('、'.join(pip)) + else: + return True, 'Risk-free' + diff --git a/class_v2/safe_warning_v2/sw_protected_hardlinks.py b/class_v2/safe_warning_v2/sw_protected_hardlinks.py new file mode 100644 index 00000000..29b8c6e3 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_protected_hardlinks.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 开启地址空间布局随机化 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = 'Whether hard link protection is enabled' +_version = 1.0 # 版本 +_ps = "Whether hard link protection is enabled" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_protected_hardlinks.pl") +_tips = [ + "operation is as follows: sysctl -w fs.protected_hardlinks=1", +] +_help = '' +_remind = "By enabling this kernel parameter, users can no longer create soft or hard links to files they do not own, which reduces the vulnerability of privileged programs to access insecure filesystems and enhances server security." + + +def check_run(): + try: + if os.path.exists("/proc/sys/fs/protected_hardlinks"): + protected_hardlinks = public.ReadFile("/proc/sys/fs/protected_hardlinks") + if int(protected_hardlinks) != 1: + return False, 'Hard link protection is not enabled' + else: + return True, "Risk-free" + except: + return True, "Risk-free" + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_protected_symlinks.py b/class_v2/safe_warning_v2/sw_protected_symlinks.py new file mode 100644 index 00000000..c4271764 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_protected_symlinks.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 开启软链接保护 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = 'Whether to enable soft link protection' +_version = 1.0 # 版本 +_ps = "Whether to enable soft link protection" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_protected_symlinks.pl") +_tips = [ + "operation is as follows: sysctl -w fs.protected_symlinks=1", +] +_help = '' +_remind = "By enabling this kernel parameter, symbolic links are only allowed to be tracked if they are outside the sticky global writable directory, or if the directory owner matches the owner of the symbolic link. Banning such symbolic links helps mitigate vulnerabilities in insecure file systems based on privileged program access." + + +def check_run(): + try: + if os.path.exists("/proc/sys/fs/protected_symlinks"): + protected_symlinks = public.ReadFile("/proc/sys/fs/protected_symlinks") + if int(protected_symlinks) != 1: + return False, 'Soft link protection is not enabled' + else: + return True, "Risk-free" + except: + return True, "Risk-free" + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_redis_pass.py b/class_v2/safe_warning_v2/sw_redis_pass.py new file mode 100644 index 00000000..11ad179a --- /dev/null +++ b/class_v2/safe_warning_v2/sw_redis_pass.py @@ -0,0 +1,48 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# Redis 密码检测 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Redis weak password' +_version = 1.0 # 版本 +_ps = "Check if the current Redis password is secure" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_redis_pass.pl") +_tips = [ + "1.Redis passwords are too simple" + "2.Please change your password in time" + ] +_help = '' +_remind = 'This solution reduces the risk of server intrusion by strengthening the database login password. ' + +def check_run(): + try: + p_file = '/www/server/redis/redis.conf' + p_body = public.readFile(p_file) + if not p_body: return True, 'Risk-free' + + tmp = re.findall(r"^\s*requirepass\s+(.+)", p_body, re.M) + if not tmp: return True, 'Risk-free' + + redis_pass = tmp[0].strip() + pass_info=public.ReadFile("/www/server/panel/config/weak_pass.txt") + if not pass_info: return True, 'Risk-free' + pass_list = pass_info.split('\n') + for i in pass_list: + if i==redis_pass: + return False, 'The Redis password [%s] is a weak password, please change the password'%redis_pass + return True, 'Risk-free' + except: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_redis_port.py b/class_v2/safe_warning_v2/sw_redis_port.py new file mode 100644 index 00000000..f9372705 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_redis_port.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# Redis安全检测 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Redis security' +_version = 1.0 # 版本 +_ps = "Check whether the current Redis is safe" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_redis_port.pl") +_tips = [ + "If not necessary, please do not configure Redis bind to 0.0.0.0", + "If bind is 0.0.0.0, be sure to set an access password for Redis", + "Do not use too simple password as Redis access password", + "Strong passwords are recommended: numeric, upper - and lowercase, special characters, and no less than seven characters long.", + "If there is a security problem in Redis, it will lead to a high probability of server intrusion, please be sure to deal with it carefully." + ] +_help = '' +_remind = 'This solution can strengthen the protection of Redis database and reduce the risk of server intrusion. When fixing the risk, make sure that you have opened the IP access of the relevant website, and change the access password synchronously to prevent the server from being unable to access Redis. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + ''' + + p_file = '/www/server/redis/redis.conf' + p_body = public.readFile(p_file) + if not p_body: return True,'Rick-free' + + tmp = re.findall(r"^\s*bind\s+(0\.0\.0\.0)",p_body,re.M) + if not tmp: return True,'Rick-free' + + tmp = re.findall(r"^\s*requirepass\s+(.+)",p_body,re.M) + if not tmp: return False,'Reids allows public internet connection, but no Redis password is set, which is extremely dangerous, please deal with it immediately' + + redis_pass = tmp[0].strip() + if not is_strong_password(redis_pass): + return False, 'Redis access password is too simple, and there are security risks' + + return True,'Risk-free' + + +def is_strong_password(password): + """判断密码复杂度是否安全 + + 非弱口令标准:长度大于等于7,分别包含数字、小写、大写、特殊字符。 + @password: 密码文本 + @return: True/False + """ + + if len(password) < 7: + return False + + import re + digit_reg = "[0-9]" # 匹配数字 +1 + lower_case_letters_reg = "[a-z]" # 匹配小写字母 +1 + upper_case_letters_reg = "[A-Z]" # 匹配大写字母 +1 + special_characters_reg = r"((?=[\x21-\x7e]+)[^A-Za-z0-9])" # 匹配特殊字符 +1 + + regs = [digit_reg, + lower_case_letters_reg, + upper_case_letters_reg, + special_characters_reg] + + grade = 0 + for reg in regs: + if re.search(reg, password): + grade += 1 + + if grade == 4 or (grade >= 2 and len(password) >= 9): + return True + return False diff --git a/class_v2/safe_warning_v2/sw_risk_file.py b/class_v2/safe_warning_v2/sw_risk_file.py new file mode 100644 index 00000000..4f2485ce --- /dev/null +++ b/class_v2/safe_warning_v2/sw_risk_file.py @@ -0,0 +1,40 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, public + +_title = 'Check for dangerous remote access files' +_version = 1.0 # 版本 +_ps = "Check for dangerous remote access files:hosts.equiv、.rhosts、.netrc" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_risk_file.pl") +_tips = [ + "Delete the .rhosts and .netrc files in the home directory and delete the hosts.equiv file in the root directory", + "Follow the prompts to find the risk file and delete it" +] +_help = '' +_remind = 'This solution removes all vulnerable files, preventing them from being exploited by hackers to gain access to the server. You can backup files before deleting them to prevent them from affecting the operation of your website. ' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + result_list = [] + cfile = ['hosts.equiv', '.rhosts', '.netrc'] + for cf in cfile: + file = public.ExecShell('find / -maxdepth 3 -name {}'.format(cf)) + if file[0]: + result_list = result_list+file[0].split('\n') + result = '、'.join(reform_list(result_list)) + if result: + return False, 'High-risk files, delete the following files as soon as possible\"{}\"'.format(result) + else: + return True, 'Risk-free' + + +def reform_list(check_list): + """处理列表里的空字符串""" + return [i for i in check_list if (i is not None) and (str(i).strip() != '')] + diff --git a/class_v2/safe_warning_v2/sw_site_logs.py b/class_v2/safe_warning_v2/sw_site_logs.py new file mode 100644 index 00000000..cc2178ef --- /dev/null +++ b/class_v2/safe_warning_v2/sw_site_logs.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 网站日志检测 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'Web log detection' +_version = 1.0 # 版本 +_ps = "Check all site log retention cycles for compliance" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_site_logs.pl") +_tips = [ + "In the [Scheduled Task] page, set the log cutting of the specified website or all websites once a day and save more than 180 copies ", + "Tip: According to Article 21 of the Network Security Law, network logs should be retained for no less than six months." + ] + +_help = '' +_remind = 'This solution can help discover the risk of external intrusions and vulnerabilities by retaining logs to ensure the security of your website. Make sure you have enough log space. ' + + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-03> + @return tuple (status,msg) + ''' + + if public.M('crontab').where('sType=? AND sName=? AND save>=?',('logs','ALL',180)).count(): + return True,'Risk-free' + + log_list = public.M('crontab').where('sType=? AND save=?',('logs',180)).field('sName').select() + ok_logs = [] + for ml in log_list: + if ml['sName'] in ok_logs: continue + ok_logs.append(ml['sName']) + + not_logs = [] + site_list = public.M('sites').field('name').select() + for s in site_list: + if s['name'] in ok_logs: continue + if s['name'] in not_logs: continue + not_logs.append(s['name']) + + if not_logs: + return False ,'The following website log preservation cycle is not compliant:
                                    ' + ('
                                    '.join(not_logs)) + + return True,'Risk-free' + + diff --git a/class_v2/safe_warning_v2/sw_site_spath.py b/class_v2/safe_warning_v2/sw_site_spath.py new file mode 100644 index 00000000..152e74aa --- /dev/null +++ b/class_v2/safe_warning_v2/sw_site_spath.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测网站是否开启防跨站 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'Website anti-cross-site detection' +_version = 1.0 # 版本 +_ps = "Check the website to prevent cross-site" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_site_spath.pl") +_tips = [ + "On the [WebSite] page, [Settings]-[Site Directory], turn on the [Anti-cross-site attack (open_basedir)] function" + ] + +_help = '' +_remind = 'This solution can prevent hackers from stealing server information across directories, and strengthen the protection of the website. ' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-05> + @return tuple (status,msg) + ''' + not_uini = [] + site_list = public.M('sites').where('status=? AND project_type=?',(1,'PHP')).field('name,path').select() + for s in site_list: + path = get_site_run_path(s['name'],s['path']) + user_ini = path + '/.user.ini' + if os.path.exists(user_ini): continue + not_uini.append(s['name']) + if not_uini: + return False,'The following websites are not enabled for cross-site prevention:
                                    ' + ('
                                    '.join(not_uini)) + return True,'Rick-free' + + + +webserver_type = None +setupPath = '/www/server' +def get_site_run_path(siteName,sitePath): + ''' + @name 获取网站运行目录 + @author hwliang<2020-08-05> + @param siteName(string) 网站名称 + @param sitePath(string) 网站根目录 + @return string + ''' + global webserver_type,setupPath + if not webserver_type: + webserver_type = public.get_webserver() + 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 \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_site_ssl.py b/class_v2/safe_warning_v2/sw_site_ssl.py new file mode 100644 index 00000000..caf5f0fc --- /dev/null +++ b/class_v2/safe_warning_v2/sw_site_ssl.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 网站证书检测 +# ------------------------------------------------------------------- + +import os,sys,re,public + +_title = 'Website certificate (SSL)' +_version = 1.0 # 版本 +_ps = "Check whether all websites deploy SSL" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_site_ssl.pl") +_tips = [ + "Please consider deploying an SSL certificate for your website to improve its security" + ] +_help = '' +_remind = 'SSL certificates ensure that the communication on your website is secure, preventing hackers from stealing data while it is in transit. ' + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-04> + @return tuple (status,msg) + ''' + + site_list = public.M('sites').field('id,name').select() + + not_ssl_list = [] + for site_info in site_list: + ng_conf_file = '/www/server/panel/vhost/nginx/' + site_info['name'] + '.conf' + if not os.path.exists(ng_conf_file): continue + s_body = public.readFile(ng_conf_file) + if not s_body: continue + if s_body.find('ssl_certificate') == -1: + not_ssl_list.append(site_info['name']) + + if not_ssl_list: + return False ,'The following sites do not deploy SSL certificates:
                                    ' + ('
                                    '.join(not_ssl_list)) + + return True,'Rick-free' + + + diff --git a/class_v2/safe_warning_v2/sw_site_ssl_expire.py b/class_v2/safe_warning_v2/sw_site_ssl_expire.py new file mode 100644 index 00000000..34942f17 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_site_ssl_expire.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 网站证书过期检测 +# ------------------------------------------------------------------- + +import os,sys,re,public,OpenSSL,time + +_title = 'Website certificate expired' +_version = 1.0 # 版本 +_ps = "Check whether the websites SSL has expired" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_site_ssl_expire.pl") +_tips = [ + "Please renew or replace with a new SSL certificate for your site to avoid affecting normal website access", + "After the SSL certificate expires, the user will be prompted by the browser to access the website as insecure, and most browsers will block access, seriously affecting online business" + ] +_help = '' +_remind = 'SSL certificates ensure that the communication on your website is secure, preventing hackers from stealing data while it is in transit. ' +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-04> + @return tuple (status,msg) + ''' + + site_list = public.M('sites').field('id,name').select() + + not_ssl_list = [] + s_time = time.time() + for site_info in site_list: + ng_conf_file = '/www/server/panel/vhost/nginx/' + site_info['name'] + '.conf' + if not os.path.exists(ng_conf_file): continue + s_body = public.readFile(ng_conf_file) + if not s_body: continue + if s_body.find('ssl_certificate') == -1: continue + + cert_file = '/www/server/panel/vhost/cert/{}/fullchain.pem'.format(site_info['name']) + if not os.path.exists(cert_file): continue + + cert_timeout = get_cert_timeout(cert_file) + if s_time > cert_timeout: + not_ssl_list.append(site_info['name'] + ' Expiration: ' + public.format_date("%Y-%m-%d",cert_timeout)) + + if not_ssl_list: + return False ,'The following sites SSL certificate has expired:
                                    ' + ('
                                    '.join(not_ssl_list)) + + return True,'Rick-free' + + + +# 获取证书到期时间 +def get_cert_timeout(cert_file): + try: + cert = split_ca_data(public.readFile(cert_file)) + x509 = OpenSSL.crypto.load_certificate( + OpenSSL.crypto.FILETYPE_PEM, cert) + cert_timeout = bytes.decode(x509.get_notAfter())[:-1] + return int(time.mktime(time.strptime(cert_timeout, '%Y%m%d%H%M%S'))) + except: + return time.time() + 86400 + + + +# 拆分根证书 +def split_ca_data(cert): + datas = cert.split('-----END CERTIFICATE-----') + return datas[0] + "-----END CERTIFICATE-----\n" diff --git a/class_v2/safe_warning_v2/sw_ssh_clientalive.py b/class_v2/safe_warning_v2/sw_ssh_clientalive.py new file mode 100644 index 00000000..78db2c26 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_clientalive.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# SSH 空闲超时时间检测 +# ------------------------------------------------------------------- +import re,public,os + + +_title = 'SSH idle timeout detection' +_version = 1.0 # 版本 +_ps = "SSH idle timeout detection" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_clientalive.pl") +_tips = [ + "Set [ClientAliveInterval] in the [/etc/ssh/sshd_config] file to be between 600 and 900", + "Tip: The recommended SSH idle timeout time is: 600-900" + ] + +_help = '' +_remind = 'This scheme can enhance the security of SSH service, after the repair of SSH connection for a long time without operation will automatically quit, to prevent others from using. ' + + +def check_run(): + ''' + @name SSH 空闲超时检测 + @time 2022-08-10 + @author lkq<2020-08-10> + @return tuple (status,msg) + ''' + if os.path.exists('/etc/ssh/sshd_config'): + try: + info_data=public.ReadFile('/etc/ssh/sshd_config') + if info_data: + if re.search(r'ClientAliveInterval\s+\d+',info_data): + clientalive=re.findall(r'ClientAliveInterval\s+\d+',info_data)[0] + #clientalive 需要大于600 小于900 + if int(clientalive.split(' ')[1]) >= 600 and int(clientalive.split(' ')[1]) <= 900: + return True,'Rick-free' + else: + return False,'The current SSH idle timeout time is: '+clientalive.split(' ')[1]+', it is recommended to set it to 600-900' + else: + return True,'Rick-free' + except: + return True,'Rick-free' + return True,'Rick-free' + +def repaired(): + ''' + @name 修复ssh最大连接数 + @author lkq<2022-08-10> + @return tuple (status,msg) + ''' + # 暂时不处理 + pass diff --git a/class_v2/safe_warning_v2/sw_ssh_forward.py b/class_v2/safe_warning_v2/sw_ssh_forward.py new file mode 100644 index 00000000..47c913e7 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_forward.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +#coding: utf-8 + +import sys,re,os,public + +_title = 'Use the graphical interface to check after restricting SSH login' +_version = 1.0 # 版本 +_ps = "Use the graphical interface to check after restricting SSH login" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-14' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_forward.pl") +_tips = [ + "Modify X11Forwarding to no in [/etc/ssh/sshd_config]", + ] +_help = '' +_remind = 'This can be used to enforce SSH login security and speed up SSH connections. Notethe fix turns off X11 graphical forwarding, so do not configure it if you need to use it. ' + +def check_run(): + conf = '/etc/ssh/sshd_config' + if not os.path.exists(conf): + return True, 'Risk-free' + result = public.ReadFile(conf) + rep = r'.*?X11Forwarding\s*?yes' + tmp = re.search(rep, result) + if tmp: + if tmp.group()[0] == '#': + return True, 'Risk-free' + else: + return False, 'SSH graphical forwarding is not disabled' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ssh_hosts.py b/class_v2/safe_warning_v2/sw_ssh_hosts.py new file mode 100644 index 00000000..9e34de08 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_hosts.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +#coding: utf-8 + +import os,sys,re,public + + +_title = 'ssh access control list checking' +_version = 1.0 # 版本 +_ps = "Set up an ssh login whitelist" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_hosts.pl") +_tips = [ + "add ALL:ALL in 【/etc/hosts.deny】", + "add sshd:【visitor IP address】 in【/etc/hosts.allow】" +] +_help = '' +_remind = 'This scheme will block the rest of the IP except the white list to login the server, and enhance the security protection of the server. Note that this solution is risky; be sure to add an IP address to the server before you fix it.' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + cfile = '/etc/hosts.deny' + conf = public.ReadFile(cfile) + if 'all:all' in conf or 'ALL:ALL' in conf: + return True, 'Risk-free' + else: + return False, 'ssh login whitelist is not set' diff --git a/class_v2/safe_warning_v2/sw_ssh_login_grace.py b/class_v2/safe_warning_v2/sw_ssh_login_grace.py new file mode 100644 index 00000000..6270e1f0 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_login_grace.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- +# Time: 2023-11-22 +# ------------------------------------------------------------------- +# SSH 登录超时时间 +# ------------------------------------------------------------------- + +import re, public, os + +_title = 'SSH Login timeout configuration detection' +_version = 1.0 # 版本 +_ps = "SSH Login timeout configuration detection" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_login_grace.pl") +_tips = [ + "Set [LoginGraceTime] to 60 in [/etc/ssh/sshd_config] file", +] + +_help = '' +_remind = 'Setting the LoginGraceTime parameter to a small number minimizes the risk of a successful brute force attack on the SSH server. It will also limit the number of concurrent unauthenticated connections. ' + + +def check_run(): + ''' + @name SSH 登录超时配置检测 + @author lwh<2023-11-22> + @return tuple (status,msg) + ''' + path = '/etc/ssh/sshd_config' + if os.path.exists(path): + try: + output, err = public.ExecShell(r"grep -P '^(?!#)[\s]*LoginGraceTime.*$' {}".format(path)) + if output == '' and err == '': + return False, 'The SSH login timeout configuration is not enabled' + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ssh_maxauth.py b/class_v2/safe_warning_v2/sw_ssh_maxauth.py new file mode 100644 index 00000000..a4b1ae08 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_maxauth.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# SSH 最大连接数检测 +# ------------------------------------------------------------------- + +import re, public, os + +_title = 'SSH connection attempts' +_version = 1.0 # 版本 +_ps = "SSH connection attempts" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_maxauth.pl") +_tips = [ + "Set [MaxAuthTries] to 3-5 in the [/etc/ssh/sshd_config] file", + "Tips: Set [MaxAuthTries] to 3-5 in the [/etc/ssh/sshd_config] file" +] + +_help = '' +_remind = 'This reduces the risk of server intrusion by reducing the maximum number of SSH connections. Note Before fixing, confirm the number of simultaneous connections that SSH needs to support to prevent affecting normal business operations. ' + + +def check_run(): + ''' + @name 检测ssh最大连接数 + @author lkq<2020-08-10> + @return tuple (status,msg) + ''' + + if os.path.exists('/etc/ssh/sshd_config'): + try: + info_data = public.ReadFile('/etc/ssh/sshd_config') + if info_data: + if re.search(r'MaxAuthTries\s+\d+', info_data): + maxauth = re.findall(r'MaxAuthTries\s+\d+', info_data)[0] + # max 需要大于3 小于6 + if int(maxauth.split(' ')[1]) >= 3 and int(maxauth.split(' ')[1]) <= 6: + return True, 'Rick-free' + else: + return False, 'The current maximum number of SSH connections is: ' + maxauth.split(' ')[1] + ', please set it to 3-5' + else: + return True, 'Rick-free' + except: + return True, 'Rick-free' + return True, 'Rick-free' + + +def repaired(): + ''' + @name 修复ssh最大连接数 + @author lkq<2020-08-10> + @return tuple (status,msg) + ''' + # 暂时不处理 + pass diff --git a/class_v2/safe_warning_v2/sw_ssh_minclass.py b/class_v2/safe_warning_v2/sw_ssh_minclass.py new file mode 100644 index 00000000..9d946f4d --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_minclass.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# SSH密码复杂度检查 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = 'SSH password complexity check' +_version = 1.0 # 版本 +_ps = "SSH password complexity check" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_minclass.pl") +_tips = [ + "【/etc/security/pwquality.conf】 Set password complexity to require 3 or 4 types of characters, such as lowercase letters, uppercase letters, numbers, and special characters. like:", + "minclass=3", +] +_help = '' +_remind = 'This scheme strengthens the complexity of the server login password and reduces the risk of being successfully exploded. ' + +def check_run(): + try: + p_file = '/etc/security/pwquality.conf' + p_body = public.readFile(p_file) + if not p_body: return True, 'Risk-free' + tmp = re.findall(r"\s*minclass\s+=\s+(.+)", p_body, re.M) + if not tmp: return True, 'Risk-free' + minlen = tmp[0].strip() + if int(minlen) <3: + return False, '【%s】set the minclass setting to 3 or 4 in the file' % p_file + return True, 'Risk-free' + except: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ssh_notpass.py b/class_v2/safe_warning_v2/sw_ssh_notpass.py new file mode 100644 index 00000000..512f9fc9 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_notpass.py @@ -0,0 +1,49 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lkq +# ------------------------------------------------------------------- +# Time: 2022-08-10 +# ------------------------------------------------------------------- +# 禁止SSH空密码登录 +# ------------------------------------------------------------------- +import re,public,os + + +_title = 'Prohibit SSH login with empty password' +_version = 1.0 # 版本 +_ps = "Prohibit SSH login with empty password" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-8-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_notpasswd.pl") +_tips = [ + "Set [PermitEmptyPasswords] in the [/etc/ssh/sshd_config] file to configure it to no", + "Tips: Set [PermitEmptyPasswords] in the [/etc/ssh/sshd_config] file to configure it to no" + ] + +_help = '' +_remind = 'This scheme prevents the server from logging in with an empty password. Note that the server cannot login with an empty password after the repair. Ensure that the login password is configured synchronously for related business access. ' + + +def check_run(): + ''' + @name 检测禁止SSH空密码登录 + @author lkq<2020-08-10> + @return tuple (status,msg) + ''' + + if os.path.exists('/etc/ssh/sshd_config'): + try: + info_data = public.ReadFile('/etc/ssh/sshd_config') + if info_data: + if re.search('\nPermitEmptyPasswords\\s*yes', info_data): + return False, 'The [PermitEmptyPasswords] value is: yes, please set it to no' + else: + return True, 'Rick-free' + except: + return True, 'Rick-free' + return True, 'Rick-free' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_ssh_passmax.py b/class_v2/safe_warning_v2/sw_ssh_passmax.py new file mode 100644 index 00000000..d49c2c1c --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_passmax.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检查SSH密码失效时间 +# ------------------------------------------------------------------- +import sys, os +os.chdir('/www/server/panel') +sys.path.append("class/") +import os, sys, re, public + +_title = 'Check SSH password expiration time' +_version = 1.0 # 版本 +_ps = "Check SSH password expiration time" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_passmax.pl") +_tips = [ + "[/etc/login.defs] Use a non-password login key pair. Please ignore this, and set the PASS_MAX_DAYS parameter to between 90-180 in /etc/login.defs", + "PASS_MAX_DAYS 90 You need to execute the command to set the root password expiration time at the same time. The command is as follows: chage --maxdays 90 root", +] +_help = '' +_remind = 'This solution reduces the risk of a breach by setting an expiration date for the root login password. Note that the repair scheme will invalidate the root password after the expiration date, so it is necessary to modify the password before the expiration date. If the modification is not timely, it may affect the operation of some services. ' + +def check_run(): + try: + p_file = '/etc/login.defs' + p_body = public.readFile(p_file) + if not p_body: return True, 'Risk-free' + tmp = re.findall("\nPASS_MAX_DAYS\\s+(.+)", p_body, re.M) + if not tmp: return True, 'Risk-free' + maxdays = tmp[0].strip() + #60-180之间 + if int(maxdays) < 90 or int(maxdays) > 180: + return False, '【%s】Set PASS_MAX_DAYS to between 90-180 in the file' % p_file + return True, 'Risk-free' + except: + return True, 'Risk-free' + diff --git a/class_v2/safe_warning_v2/sw_ssh_passmin.py b/class_v2/safe_warning_v2/sw_ssh_passmin.py new file mode 100644 index 00000000..228cd54d --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_passmin.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检查SSH密码失效时间 +# ------------------------------------------------------------------- +import sys, os +os.chdir('/www/server/panel') +sys.path.append("class/") +import os, sys, re, public + +_title = 'Check minimum interval between SSH password changes' +_version = 1.0 # 版本 +_ps = "Check minimum interval between SSH password changes" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_passmin.pl") +_tips = [ + "[/etc/login.defs] PASS_MIN_DAYS should be set to be greater than or equal to 7", + "PASS_MIN_DAYS 7 needs to execute the command at the same time to set the expiration time of the root password. The command is as follows: chage --mindays 7 root", +] +_help = '' +_remind = 'This solution sets the number of days after the SSH login password is changed, it cannot be changed again. ' + +def check_run(): + try: + p_file = '/etc/login.defs' + p_body = public.readFile(p_file) + if not p_body: return True, 'Risk-free' + tmp = re.findall("\nPASS_MIN_DAYS\\s+(.+)", p_body, re.M) + if not tmp: return True, 'Risk-free' + maxdays = tmp[0].strip() + #7-14 + if int(maxdays) < 7: + return False, '【%s】In the file, PASS_MIN_DAYS is greater than or equal to 7' % p_file + return True, 'Risk-free' + except: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ssh_port.py b/class_v2/safe_warning_v2/sw_ssh_port.py new file mode 100644 index 00000000..b45b5a19 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_port.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# SSH安全检测 +# ------------------------------------------------------------------- + + +import os,sys,re,public,json + +_title = 'SSH security' +_version = 1.0 # 版本 +_ps = "Check whether the SSH port of the current server is safe" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-04' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_port.pl") +_tips = [ + "Modify the SSH port on the [Security] page, and consider turning off [SSH password login] in [SSH security management], and turning on [SSH key login]", + "If SSH connection service is not required, it is recommended to disable SSH service on the [Security] page", + "Through the [System Firewall] plug-in or in the [Security Group] modify the release behavior of the SSH port to limit the IP to enhance security", + "Use [Fail2ban] plug-in to protect SSH service" + ] + +_help = '' +_remind = "This solution reduces the risk of a breach by changing the default SSH login port. Noteafter the fix, you'll need to change the SSH port that the relevant business logs on to. " + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2022-08-18> + @return tuple (status,msg) + + @example + status, msg = check_run() + if status: + print('OK') + else: + print('Warning: {}'.format(msg)) + + ''' + port = public.get_sshd_port() + + version = public.readFile('/etc/redhat-release') + if not version: + version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace(r'\l','').strip() + else: + version = version.replace('release ','').replace('Linux','').replace('(Core)','').strip() + + status = public.get_sshd_status() + + fail2ban_file = '/www/server/panel/plugin/fail2ban/config.json' + if os.path.exists(fail2ban_file): + try: + fail2ban_config = json.loads(public.readFile(fail2ban_file)) + if 'sshd' in fail2ban_config.keys(): + if fail2ban_config['sshd']['act'] == 'true': + return True,'Fail2ban is enable' + except: pass + + if not status: + return True,'SSH service is not enabled' + if port != '22': + return True,'The default SSH port has been modified' + + result = public.check_port_stat(int(port),public.GetLocalIp()) + if result == 0: + return True,'Rick-free' + + return False,'The default SSH port ({}) has not been modified, and the access IP limit configuration has not been done, there is a risk of SSH breaching'.format(port) + diff --git a/class_v2/safe_warning_v2/sw_ssh_root.py b/class_v2/safe_warning_v2/sw_ssh_root.py new file mode 100644 index 00000000..56fa9091 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_root.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检查SSH root是否可以登录 +# ------------------------------------------------------------------- +import sys, os +os.chdir('/www/server/panel') +sys.path.append("class/") +import os, sys, re, public + +_title = 'Check if SSH root can log in' +_version = 1.0 # 版本 +_ps = "Check if SSH root can log in" # 描述 +_level = 0 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_root.pl") +_tips = [ + "Add [ PermitRootLogin no ] parameter in [/etc/ssh/sshd_config]", + "PermitRootLogin no", +] +_help = '' +_remind = 'SSH remote login as root is not possible after this solution is fixed' + + +def check_run(): + #ssh 检查root 登录 + if os.path.exists('/etc/ssh/sshd_config'): + try: + info_data = public.ReadFile('/etc/ssh/sshd_config') + if info_data: + if re.search(r'PermitRootLogin\s+no', info_data): + return True, 'Risk-free' + else: + return True, 'Risk-free' + return False, 'The parameter [PermitRootLogin] in /etc/ssh/sshd_config is configured as: "yes", please set it to "no"' + except: + return True, 'Risk-free' + return True, 'Risk-free' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_ssh_security.py b/class_v2/safe_warning_v2/sw_ssh_security.py new file mode 100644 index 00000000..7774bdd6 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_security.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# SSH密码复杂度检查 +# ------------------------------------------------------------------- +import sys,os +os.chdir('/www/server/panel') +sys.path.append("class/") +import os,sys,re,public + +_title = 'SSH password length check' +_version = 1.0 # 版本 +_ps = "SSH password length check" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_security.pl") +_tips = [ + "In the [/etc/security/pwquality.conf] file, set minlen (minimum password length) to 9-32 bits", + "minlen=9", + ] +_help = '' +_remind = 'This solution enforces a minimum login password length, reducing the risk of server blow-up. ' + + +def check_run(): + try: + p_file = '/etc/security/pwquality.conf' + p_body = public.readFile(p_file) + if not p_body: return True, 'Risk-free' + tmp = re.findall(r"\s*minlen\s+=\s+(.+)", p_body, re.M) + if not tmp: return True, 'Risk-free' + minlen = tmp[0].strip() + if int(minlen) < 9: + return False, 'In the [%s] file, set minlen (minimum password length) to 9-32 characters'%p_file + + return True, 'Risk-free' + except: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_ssh_v2.py b/class_v2/safe_warning_v2/sw_ssh_v2.py new file mode 100644 index 00000000..9baa2331 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_ssh_v2.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + + +_title = 'Whether to use encrypted remote administration ssh' +_version = 1.0 # 版本 +_ps = "Detect whether secure socket layer encryption is used to transmit information to avoid eavesdropping on sensitive information" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_ssh_v2.pl") +_tips = [ + "Add or modify Protocol 2 in [/etc/ssh/sshd_config] file ", + "Then run the command systemctl restart sshd to restart the process ", +] +_help = '' +_remind = 'This scheme can enhance the protection of SSH communication and avoid sensitive data leakage.' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + cfile = '/etc/ssh/sshd_config' + conf = public.readFile(cfile) + rep = r"\nProtocol 2" + tmp = re.search(rep, conf) + if tmp: + return True, 'Risk-free' + else: + return False, 'Remote administration of ssh without secure socket encryption' diff --git a/class_v2/safe_warning_v2/sw_strace_backdoor.py b/class_v2/safe_warning_v2/sw_strace_backdoor.py new file mode 100644 index 00000000..3ed5f19e --- /dev/null +++ b/class_v2/safe_warning_v2/sw_strace_backdoor.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + +_title = 'strace obtains login credentials backdoor detection' +_version = 1.0 # 版本 +_ps = "Detect user information leakage via strace command during process" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_strace_backdoor.pl") +_tips = [ + "The ps aux command checks whether there are sshd login credentials read through strace", + "ps aux | grep strace", + "If the process is filtered out, use the kill -9 [pid] command to stop the process" +] +_help = '' +_remind = 'detects the existence of hacker intrusion on the server, and the hacker behavior can be interrupted in time through the scheme command to prevent the server from being further invaded and controlled. ' + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + sshd_pid = public.ExecShell('ps aux|grep "sshd -D"|grep -v grep|awk {\'print$2\'}')[0].strip() + result = public.ExecShell('ps aux')[0].strip() + rep = 'strace.*' + sshd_pid + '.*trace=read,write' + tmp = re.search(rep, result) + if tmp: + return False, 'Malicious process that steals sshd login information through strace' + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_su_root.py b/class_v2/safe_warning_v2/sw_su_root.py new file mode 100644 index 00000000..a5730110 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_su_root.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, re, public + + +_title = 'Check if the user outside the whell group su is disabled as root' +_version = 1.0 # 版本 +_ps = "Check if the PAM authentication module is used to forbid users outside the wheel group su to be root" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_su_root.pl") +_tips = [ + "In the file [/etc/pam.d/su] add auth sufficient pam_rootok.so and auth required pam_wheel.so group=wheel", + "To configure the user to switch to root, add the user to the wheel group with gpasswd -d a username wheel", +] +_help = '' +_remind = 'This scheme enhances the protection of server permissions by forbidding low-privilege users to switch to root user. Make sure that there is no need for the business to switch root before fixing, otherwise ignore this risk item.' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + cfile = '/etc/pam.d/su' + if not os.path.exists(cfile): + return True, 'Risk-free' + conf = public.readFile(cfile) + rep1 = r'[^#](\s*)auth(\s*)sufficient(\s*)pam_rootok.so' + tmp1 = re.search(rep1, conf) + if not tmp1: + return False, 'Normal user su is not prohibited as the root user' + rep2 = r'[^#](\s*)auth(\s*)required(\s*)pam_wheel.so(\s*)group(\s*)=(\s*)wheel' + tmp2 = re.search(rep2, conf) + if not tmp2: + return True, 'Risk-free, But the whell group user can su is not configured' + else: + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_sudoers_nopasswd.py b/class_v2/safe_warning_v2/sw_sudoers_nopasswd.py new file mode 100644 index 00000000..aec56fc9 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_sudoers_nopasswd.py @@ -0,0 +1,46 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, public + +_title = 'Check if an empty password sudo is allowed' +_version = 1.0 # 版本 +_ps = "Check if an empty password sudo is allowed" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-21' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_sudoers_nopasswd.pl") +_tips = [ + "Open /etc/sudoers or /etc/sudoers.d ", + "Remove or comment the line of the [NOPASSWD] marker ", + "Or handle security risks with one-click fixes." +] +_help = '' +_remind = 'When sudo uses the [NOPASSWD] flag, it allows users to execute commands using sudo without authenticating. This insecure configuration can lead to hackers gaining advanced privileges on the server.' + + +def check_run(): + ''' + @name 开始检测 + @author lwh<2023-11-21> + @return tuple (status,msg) + ''' + risk_list = [] + sudo_file = "/etc/sudoers" + sudo_dir = "/etc/sudoers.d/" + if not os.path.exists(sudo_file): + return True, 'Risk-free' + try: + output, err = public.ExecShell('grep -P \'^(?!#).*[\\s]+NOPASSWD[\\s]*\\:.*$\' {}'.format(sudo_file)) + if err == '' and output != '': + risk_list.append(sudo_file) + if os.path.exists(sudo_dir): + import glob + for filename in glob.glob(os.path.join(sudo_dir, '*')): + output, err = public.ExecShell('grep -P \'^(?!#).*[\\s]+NOPASSWD[\\s]*\\:.*$\' {}'.format(filename)) + if err == '' and output != '': + risk_list.append(filename) + if len(risk_list)>0: + return False, 'The following sudo files contain the NOPASSWD flag:【{}】'.format('、'.join(risk_list)) + except: + return True, 'Risk-free' + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_suid_dumpable.py b/class_v2/safe_warning_v2/sw_suid_dumpable.py new file mode 100644 index 00000000..3a734470 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_suid_dumpable.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: lwh +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 开启软链接保护 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = 'Whether core dumps are restricted' +_version = 1.0 # 版本 +_ps = "Whether core dumps are restricted" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-22' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_suid_dumpable.pl") +_tips = [ + "operation is as follows: sysctl -w fs.suid_dumpable=0", +] +_help = '' +_remind = 'Core dumps of setuid programs are more likely to contain sensitive data, and limiting the ability of any setuid program to write to core files reduces the risk of sensitive data leakage.' + + +def check_run(): + try: + if os.path.exists("/proc/sys/fs/suid_dumpable"): + suid_dumpable = public.ReadFile("/proc/sys/fs/suid_dumpable") + if int(suid_dumpable) != 0: + return False, 'The core dump is not limited, and information leakage may occur.' + else: + return True, "Risk-free" + except: + return True, "Risk-free" + return True, "Risk-free" diff --git a/class_v2/safe_warning_v2/sw_system_user.py b/class_v2/safe_warning_v2/sw_system_user.py new file mode 100644 index 00000000..e9e86781 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_system_user.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# coding: utf-8 +# Date 2022/1/12 + +import sys,os + +_title = 'System backdoor user detection' +_version = 1.0 # 版本 +_ps = "System backdoor user detection" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2021-01-12' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_system_user.pl") +_tips = [ + "Delete backdoor user in command line", + "Note: If there is a backdoor user, it means that your server has been invaded" +] +_help = '' +_remind = 'This scheme will remove the backdoor users with the same privileges as the root user, and enhance the protection of the server permission control. If it is a business requirement, this risk term is ignored. ' +def check_run(): + ''' + @name 开始检测 + @author lkq<2021-01-12> + @return tuple (status,msg) + ''' + ret=[] + cfile = '/etc/passwd' + if os.path.exists(cfile): + f=open(cfile,'r') + for i in f: + i=i.strip().split(":") + if i[2]=='0' and i[3]=='0': + if i[0]=='root':continue + ret.append(i[0]) + if ret: + return False, 'There is a backdoor user: %s'%''.join(ret) + return True, 'No backdoor users are currently found' \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_tcp_syn_cookie.py b/class_v2/safe_warning_v2/sw_tcp_syn_cookie.py new file mode 100644 index 00000000..057479fa --- /dev/null +++ b/class_v2/safe_warning_v2/sw_tcp_syn_cookie.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, sys, public + + +_title = 'TCP-SYNcookie protection detection' +_version = 1.0 # 版本 +_ps = "Check whether TCP-SYNcookie protection is enabled to mitigate syn flood attacks" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-09' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_tcp_syn_cookie.pl") +_tips = [ + "Add net.ipv4.tcp_syncookies=1 in [/etc/sysctl.conf] file ", + "Then execute the command sysctl -p to effect the configuration ", +] +_help = '' +_remind = 'This scheme can alleviate network flood attacks and enhance the stability of server operation. ' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + + cfile = '/etc/sysctl.conf' + conf = public.readFile(cfile) + rep = r"\nnet.ipv4.tcp_syncookies(\s*)=(\s*)1" + tmp = re.search(rep, conf) + if tmp: + return True, 'Risk-free' + else: + return False, 'TCP-SYNcookie protection is not enabled' + diff --git a/class_v2/safe_warning_v2/sw_telnet_server.py b/class_v2/safe_warning_v2/sw_telnet_server.py new file mode 100644 index 00000000..dffd0f42 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_telnet_server.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# coding: utf-8 + +import sys, os, public +_title = 'Disable non-encrypted remote management telnet' +_version = 1.0 # 版本 +_ps = "Turn off non-encrypted remote management telnet checks" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-15' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_telnet_server.pl") +_tips = [ + "Use encrypted remote management sshd service as much as possible, and close unsafe telnet service", + "systemctl stop telnet.socket stop telnet service" +] +_help = '' +_remind = 'This scheme shuts down the insecure telnet service, reducing the risk of data leakage. If the business requires telnet, this risk term is ignored. ' + +def check_run(): + result = public.ExecShell('systemctl is-active telnet.socket')[0].strip() + if 'active' == result: + return False, 'telnet service is not closed' + else: + return True, 'Risk-free' + diff --git a/class_v2/safe_warning_v2/sw_time_out.py b/class_v2/safe_warning_v2/sw_time_out.py new file mode 100644 index 00000000..8726eea2 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_time_out.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, re, public + + +_title = 'Check if the command-line interface timeout is set' +_version = 1.0 # 版本 +_ps = "Check if the command-line interface timeout is set" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_time_out.pl") +_tips = [ + "Add tmout=300 in the file [/etc/profile], and the waiting time is not more than 600 seconds ", + "Execute source /etc/profile to make the configuration work ", +] +_help = '' +_remind = 'This solution will make the server command line over a certain period of time does not operate automatically shut down, can strengthen the security of the server. ' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + cfile = '/etc/profile' + conf = public.readFile(cfile) + rep = r'(tmout|TMOUT)(\s*)=(\s*)([1-9][^0-9]|[1-9][0-9][^0-9]|[1-5][0-9][0-9][^0-9]|600[^0-9])' + tmp = re.search(rep, conf) + if tmp: + return True, 'Risk-free' + else: + return False, 'No command line timeout is configured for exit' diff --git a/class_v2/safe_warning_v2/sw_tmp_malware.py b/class_v2/safe_warning_v2/sw_tmp_malware.py new file mode 100644 index 00000000..b26280e2 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_tmp_malware.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +# coding: utf-8 + +import os, sys, public + +_title = 'Check the tmp directory for the existence of abnormal Trojan files' +_version = 1.0 # 版本 +_ps = "Check the tmp directory for the existence of abnormal Trojan files" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-11-22' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_tmp_malware.pl") +_tips = [ + "Delete the detected abnormal Trojan file according to the description", +] +_help = '' +_remind = 'This file conforms to the characteristics of a Trojan file. It is recommended to delete and reinstall the nginx server, and conduct a comprehensive security check on the server' + + +def check_run(): + ''' + @name 开始检测 + @author lwh<2023-11-22> + @return tuple (status,msg) + ''' + list1 = ['/var/tmp/systemd-private-56d86f7d8382402517f3b51625789161d2cb-chronyd.service-jP37av','/var/tmp/systemd-private-56d86f7d8382402517f3b5-jP37av','/tmp/systemd-private-56d86f7d8382402517f3b5-jP37av','/var/tmp/count','/var/tmp/count.txt','/var/tmp/backkk','/var/tmp/msglog.txt'] + risk_file = [] + for filename in list1: + if not os.path.exists(filename): + continue + if os.path.isdir(filename): + continue + risk_file.append(filename) + if len(risk_file) > 0: + return False, 'Abnormal Trojan file has been detected, please delete it ASAP:{}'.format('、'.join(risk_file)) + return True, 'Risk-free' diff --git a/class_v2/safe_warning_v2/sw_tomcat_pass.py b/class_v2/safe_warning_v2/sw_tomcat_pass.py new file mode 100644 index 00000000..dd7d6535 --- /dev/null +++ b/class_v2/safe_warning_v2/sw_tomcat_pass.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +#coding: utf-8 + +import os, re, public + + +_title = 'tomcat Background Access Weak Password Detection' +_version = 1.0 # 版本 +_ps = "tomcat Background Access Weak Password Detection" # 描述 +_level = 3 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2023-03-13' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_tomcat_pass.pl") +_tips = [ + "Change password weak password in【/usr/local/bttomcat/tomcat/conf/tomcat-users.xml】", +] +_help = '' +_remind = 'This scheme by strengthening the tomcat background login password strength, reduce the risk of being exploded, to avoid hackers using tomcat to invade the server. ' + + +def check_run(): + ''' + @name 开始检测 + @return tuple (status,msg) + ''' + tomcat_conf = '/usr/local/bttomcat/tomcat{}/conf/tomcat-users.xml' + version = ['7','8','9'] + vul_list = [] + # 第一步先用正则找到密码的输入点 + rep = 'password(\\s*)=(\\s*)[\"\'](.*?)[\"\']' + for v in version: + annotator = 0 + if not os.path.exists(tomcat_conf.format(v)): + continue + with open(tomcat_conf.format(v)) as f: + lines = f.readlines() + # 通过逐行判断是否存在注释符闭合,以annotator作锁计数,存在左闭合则+1,存在右闭合-1,当annotator值为0时才不在闭合范围内 + for l in lines: + if '' in l: + annotator -= 1 + if '' in l: + continue + if annotator != 0: + continue + if 'manager-gui' in l and 'password' in l: + tmp = re.search(rep, l.rstrip()) + passwd = tmp.group(3).strip() + for d in get_pass_list(): + if passwd == d: + vul_list.append(v) + if vul_list: + return False, 'tomcat{} has a weak background password'.format('、'.join(vul_list)) + else: + return True, 'Risk-free' + + +# 获取弱口令字典 +def get_pass_list(): + pass_info = public.ReadFile("/www/server/panel/config/weak_pass.txt") + return pass_info.split('\n') diff --git a/class_v2/safe_warning_v2/sw_umask.py b/class_v2/safe_warning_v2/sw_umask.py new file mode 100644 index 00000000..6393083e --- /dev/null +++ b/class_v2/safe_warning_v2/sw_umask.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 用户缺省权限检查 +# ------------------------------------------------------------------- +# import sys, os +# os.chdir('/www/server/panel') +# sys.path.append("class/") +import os, sys, re, public + +_title = 'User default permission check' +_version = 1.0 # 版本 +_ps = "User Default Permission Check [/etc/profile]" # 描述 +_level = 2 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2022-08-10' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_umask.pl") +_tips = [ + "【/etc/profile] The umask set in the file is 002, which does not meet the requirements. It is recommended to set it to 027", + "Fix: modify umask to 027", +] +_help = '' +_remind = 'This scheme can strengthen the control of user permissions and avoid excessive user privileges. ' +def check_run(): + # 判断是否存在/etc/profile文件 + if os.path.exists("/etc/profile"): + # 读取文件内容 + profile = public.ReadFile("/etc/profile") + # 判断是否存在umask设置 + if re.search("umask 0",profile): + # 判断是否设置为027 + if re.search("umask 027",profile): + return True,"Risk-free" + else: + return True,"Risk-free" + # return False,"未设置umask为027" + else: + return True,"Risk-free" \ No newline at end of file diff --git a/class_v2/safe_warning_v2/sw_waf_install.py b/class_v2/safe_warning_v2/sw_waf_install.py new file mode 100644 index 00000000..d1ea4cdd --- /dev/null +++ b/class_v2/safe_warning_v2/sw_waf_install.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +#coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: hwliang +# ------------------------------------------------------------------- + +# ------------------------------------------------------------------- +# 检测风险用户 +# ------------------------------------------------------------------- + + +import os,sys,re,public + +_title = 'WAF firewall detection' +_version = 1.0 # 版本 +_ps = "Detect whether a WAF firewall is installed" # 描述 +_level = 1 # 风险级别: 1.提示(低) 2.警告(中) 3.危险(高) +_date = '2020-08-05' # 最后更新时间 +_ignore = os.path.exists("data/warning/ignore/sw_waf_install.pl") +_tips = [ + "It is recommended to install a WAF firewall, such as: Pagoda Nginx Firewall, Pagoda Apache Firewall, Nginx Free Firewall, etc.", + "Note: Only one type of WAF firewall can be installed. Installing too many WAF firewalls may cause your website to be abnormal and increase unnecessary server overhead" + ] + +_help = '' +_remind = "WAF firewall is the first line of defense to protect the server, can block external network attacks, to ensure the safe and stable operation of the website. " + +def check_run(): + ''' + @name 开始检测 + @author hwliang<2020-08-04> + @return tuple (status,msg) + ''' + + web_list = [ + '/www/server/nginx/sbin/nginx', + '/www/server/apache/bin/httpd', + '/usr/local/lsws/bin' + ] + is_install_web = False + for w in web_list: + if os.path.exists(w): + is_install_web = True + break + + if not is_install_web: + return True,'Risk-free' + + waf_list = [ + '/www/server/panel/plugin/btwaf/info.json', + '/www/server/panel/plugin/btwaf_httpd/info.json', + '/www/server/panel/plugin/free_waf/info.json', + '/usr/local/yunsuo_agent/uninstall', + '/etc/safedog', + '/usr/share/xmirror/scripts/uninstall.sh' + ] + + for waf in waf_list: + if os.path.exists(waf): + return True,'Risk-free' + + return True,'If the WAF firewall is not installed, the server website is vulnerable to attacks and there is a security risk' + diff --git a/class_v2/san_baseline_v2.py b/class_v2/san_baseline_v2.py new file mode 100644 index 00000000..9b39b786 --- /dev/null +++ b/class_v2/san_baseline_v2.py @@ -0,0 +1,1315 @@ +#!/usr/bin/python +# coding: utf-8 +# | Author: 1249648969@qq.com +# +-------------------------------------------------------------------- +# | 宝塔安全基线扫描 | +# +-------------------------------------------------------------------- +import sys, os + +if sys.version_info[0] == 2: + reload(sys) + sys.setdefaultencoding('utf-8') +os.chdir('/www/server/panel') +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import time, hashlib, sys, os, json, requests, re, public, random, string, requests + + +class san_baseline: + setupPath = '/www/server' + logPath = '/www/server/panel/data/san_baseline.log' + _Speed = None + config = '/www/server/panel/data/result.log' + repair_json='/www/server/panel/data/repair.json' + __repair=None + + def __init__(self): + + if not os.path.exists(self.logPath): + resutl={} + public.WriteFile(self.logPath,json.dumps(resutl)) + if not os.path.exists(self.config): + resutl = {} + public.WriteFile(self.config, json.dumps(resutl)) + if os.path.exists(self.repair_json): + self.__repair=json.loads(public.ReadFile(self.repair_json)) + + # SSH 安全扫描 + def ssh_security(self): + # 确保SSH MaxAuthTries 设置为3-6之间 + result = [] + ret = self.check_san_baseline(self.__repair['1']) + if not ret: result.append(self.__repair['1']) + ret = self.check_san_baseline(self.__repair['2']) + if not ret: result.append(self.__repair['2']) + ret = self.check_san_baseline(self.__repair['3']) + if not ret: result.append(self.__repair['3']) + ret = self.check_san_baseline(self.__repair['4']) + if not ret: result.append(self.__repair['4']) + ret = self.check_san_baseline(self.__repair['5']) + if not ret: result.append(self.__repair['5']) + ret = self.check_san_baseline(self.__repair['6']) + if not ret: result.append(self.__repair['6']) + return result + + ######面板安全监测########################## + + # 监测是否开启IP限制登陆 + def get_limitip(self): + if os.path.exists('/www/server/panel/data/limitip.conf'): + ret = public.ReadFile('/www/server/panel/data/limitip.conf') + if not ret: + return False + return True + else: + return False + + # 监测默认端口 + def get_port(self): + ret = public.ReadFile('/www/server/panel/data/port.pl') + ret = int(public.ReadFile('/www/server/panel/data/port.pl')) + if ret == 8888: + return False + else: + return True + + # 监测是否开启安全入口 + def get_admin_path(self): + if os.path.exists('/www/server/panel/data/admin_path.pl'): + return True + else: + return False + + # 域名绑定 + def get_domain(self): + if os.path.exists('/www/server/panel/data/domain.conf'): + return True + else: + return False + + # 查看APi是否开启 + def get_api_open(self): + if os.path.exists('/www/server/panel/config/api.json'): + ret = json.loads(public.ReadFile('/www/server/panel/config/api.json')) + if ret['open']: + return False + return True + else: + return True + + # 查看用户名是否是弱用户名 + def get_username(self): + userInfo = public.M('users').where("id=?", (1,)).field('id,username,password').find() + if userInfo['username'] == 'admin' or userInfo['username'] == 'root' or userInfo['username'] == 'password': + return False + else: + return True + + # 不安全的插件 + def get_secite(self): + if os.path.exists('/www/server/panel/plugin/ss'): + return False + else: + return True + + # 面板目录权限 + + # 面板安全扫描 + def panel_security(self): + result = [] + if not self.get_limitip(): + ret1 = { + 'id': 7, + "repaired": "0", + "harm": "警告", + "level": "1", + "type": "file", + "name": "宝塔面板登陆未开启(授权IP)限制登陆", + "Suggestions": "加固建议 :如果你的IP存在固定IP建议添加到面板的授权IP", + "repair": "首页-->面板设置->授权IP->添加IP", + } + result.append(ret1) + # 端口是否是8888端口 + get_port_default = self.get_port() + if not get_port_default: + ret1 = { + 'id': 8, + "repaired": "0", + "harm": "中", + "level": "2", + "type": "file", + "name": "宝塔面板登陆端口未修改", + "Suggestions": "加固建议 : 修改默认端口,例如8989或56641", + "repair": "首页-->面板设置->面板端口->修改端口-->保存", + } + result.append(ret1) + get_admin_path = self.get_admin_path() + if not get_admin_path: + ret1 = { + 'id': 9, + "repaired": "0", + "harm": "高", + "level": "3", + "type": "file", + "name": "宝塔面板登陆未开启安全入口", + "Suggestions": "加固建议 : 修改安全入口例如 /123456789", + "repair": "首页-->面板设置->安全入口->修改安全入口-->保存", + } + result.append(ret1) + get_username = self.get_username() + if not get_username: + ret1 = { + 'id': 11, + "harm": "高", + "repaired": "0", + "level": "3", + "type": "file", + "name": "面板用户名过于简单", + "Suggestions": "加固建议 : 修改为强用户名", + "repair": "例如:ad!@#min1750..", + } + result.append(ret1) + + get_secite = self.get_secite() + if not get_secite: + ret1 = { + 'id': 12, + "harm": "高", + "repaired": "0", + "level": "3", + "type": "file", + "name": "存在国家不允许的翻墙插件", + "Suggestions": "加固建议 : 建议删除SS插件", + "repair": "rm -rf /www/server/panel/plugin/ss", + } + result.append(ret1) + panel_chome = [ + { + 'id': 13, + "type": "chmod", + "file": "/www/server/panel/BTPanel", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 14, + "type": "chmod", + "file": "/www/server/panel/class", + "chmod": [600], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 15, + "type": "chmod", + "file": "/www/server/panel/config", + "chmod": [600], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 16, + "type": "chmod", + "file": "/www/server/panel/data", + "chmod": [600], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 17, + "type": "chmod", + "file": "/www/server/panel/install", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 18, + "type": "chmod", + "file": "/www/server/panel/logs", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 19, + "type": "chmod", + "file": "/www/server/panel/package", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 20, + "type": "chmod", + "file": "/www/server/panel/plugin", + "chmod": [644, 600], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 21, + "type": "chmod", + "file": "/www/server/panel/rewrite", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 22, + "type": "chmod", + "file": "/www/server/panel/ssl", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 23, + "type": "chmod", + "file": "/www/server/panel/temp", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 24, + "type": "chmod", + "file": "/www/server/panel/vhost", + "chmod": [600, 644], + "user": ['root'], + 'group': ['root'] + } + ] + for i in panel_chome: + if not self.check_san_baseline(i): + ret1 = { + 'id': i['id'], + "harm": "高", + "repaired": "1", + "level": "3", + "type": "file", + "name": "面板关键性文件权限错误%s" % i['file'], + "Suggestions": "加固建议 : %s 权限改为%s 所属用户为%s" % (i['file'], i['chmod'], i['user']), + "repair": "加固建议 : %s 权限改为%s 所属用户为%s" % (i['file'], i['chmod'], i['user']), + } + result.append(ret1) + + return result + + def php_id(self,php=None,php_2=None): + if php=='52':id =25;return id + if php == '53': id = 26;return id + if php == '54': id = 27;return id + if php == '55': id = 28;return id + if php == '56': id = 29;return id + if php == '70': id = 30;return id + if php == '71': id = 31;return id + if php == '72': id = 32;return id + if php == '73': id = 32.5;return id + + if php_2=='52':id =33;return id + if php_2 == '53': id = 34;return id + if php_2 == '54': id = 35;return id + if php_2 == '55': id = 36;return id + if php_2 == '56': id = 37;return id + if php_2 == '70': id = 38;return id + if php_2 == '71': id = 39;return id + if php_2 == '72': id = 40;return id + if php == '73': id = 40.5;return id + + # php版本泄露 + def php_version_info(self): + ret = [] + php_path = '/www/server/php/' + php_list = os.listdir(php_path) + if len(php_list) >= 1: + for i in php_list: + if os.path.isdir(php_path + i): + if os.path.exists(php_path + i + '/etc/php.ini'): + php_data = { + 'id': self.php_id(i), + "type": "file", + "harm": "中", + "level": "2", + "repaired": "1", + "name": "PHP 版本泄露", + "file": php_path + i + '/etc/php.ini', + "Suggestions": "加固建议, 在%s expose_php的值修改为Off中修改" % (php_path + i + '/etc/php.ini'), + "repair": "expose_php = Off", + "rule": [ + {"re": "\nexpose_php\\s*=\\s*(\\w+)", "check": {"type": "string", "value": ['Off']}}] + } + if not self.check_san_baseline(php_data): + ret.append(php_data) + return ret + + + # PHP 危险函数 + def php_error_funcation(self): + ret = [] + php_path = '/www/server/php/' + php_list = os.listdir(php_path) + if len(php_list) >= 1: + for i in php_list: + if os.path.isdir(php_path + i): + if os.path.exists(php_path + i + '/etc/php.ini'): + php_data = { + 'id': self.php_id(php='1',php_2=i), + "type": "diff", + "harm": "严重", + "level": "5", + "repaired": "1", + "name": "PHP%s 中存在危险函数未禁用" % i, + "file": php_path + i + '/etc/php.ini', + "Suggestions": "加固建议, 在%s 中 disable_functions= 修改成如下:" % (php_path + i + '/etc/php.ini'), + "repair": "disable_functions = passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv", + "rule": [ + {"re": "\ndisable_functions\\s?=\\s?(.+)", "check": {"type": "string", "value": [ + 'passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv']}}] + } + if not self.check_san_baseline(php_data): + ret.append(php_data) + return ret + + # 版本过旧 + def php_dir(self): + + php_version_dir = { + 'id':41, + "type": "dir", + "harm": "高", + "level": "3", + "repaired": "0", + "name": "PHP 5.2 版本过旧", + "file": '/www/server/php/52', + "Suggestions": "加固建议:不再使用php5.2 ", + "repair": "PHP 5.2 已经被淘汰建议升级更高的版本", + "rule": [] + } + if not self.check_san_baseline(php_version_dir): + return php_version_dir + return {} + + # php配置安全 + + + def php_security(self): + ret = [] + php_path = '/www/server/php/' + php_list = os.listdir(php_path) + if len(php_list) >= 1: + for i in php_list: + if os.path.isdir(php_path + i): + if os.path.exists(php_path + i + '/etc/php.ini'): + php_data = { + 'id': self.php_id(i), + "type": "file", + "harm": "中", + "level": "2", + "repaired": "1", + "name": "PHP%s 版本泄露" % i, + "file": php_path + i + '/etc/php.ini', + "Suggestions": "加固建议, 在%s expose_php的值修改为Off中修改" % (php_path + i + '/etc/php.ini'), + "repair": "expose_php = Off", + "rule": [ + {"re": "\nexpose_php\\s*=\\s*(\\w+)", "check": {"type": "string", "value": ['Off']}}] + } + if not self.check_san_baseline(php_data): + ret.append(php_data) + + if len(php_list) >= 1: + for i in php_list: + if os.path.isdir(php_path + i): + if os.path.exists(php_path + i + '/etc/php.ini'): + php_data = { + 'id': self.php_id(php='1', php_2=i), + "type": "diff", + "harm": "严重", + "level": "5", + "repaired": "1", + "name": "PHP%s 中存在危险函数未禁用" % i, + "file": php_path + i + '/etc/php.ini', + "Suggestions": "加固建议, 在%s 中 disable_functions= 修改成如下:" % (php_path + i + '/etc/php.ini'), + "repair": "disable_functions = passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv", + "rule": [ + {"re": "\ndisable_functions\\s?=\\s?(.+)", "check": {"type": "string", "value": [ + 'passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv']}}] + } + if not self.check_san_baseline(php_data): + ret.append(php_data) + + php_version_dir = { + 'id': 41, + "type": "dir", + "harm": "高", + "level": "3", + "repaired": "0", + "name": "PHP 5.2 版本过旧", + "file": '/www/server/php/52', + "Suggestions": "加固建议:不再使用php5.2 ", + "repair": "PHP 5.2 已经被淘汰建议升级更高的版本", + "rule": [] + } + if not self.check_san_baseline(php_version_dir): + ret.append(php_version_dir) + return ret + + # Redis 配置按 + def redis_security(self): + ret = [] + # 查看redis 是否监听的是0.0.0.0 返回True 代表高危 + redis_server_ip = { + 'id': 42, + "type": "file", + "harm": "高", + "level": "3", + "repaired": "0", + "check_file":"/www/server/redis", + "name": "Redis 监听的地址为0.0.0.0", + "file": '/www/server/redis/redis.conf', + "Suggestions": "加固建议, 在%s 中的监听IP设置为127.0.0.1 例如" % ('/www/server/redis/redis.conf'), + "repair": "bind 127.0.0.1", + "rule": [ + {"re": "\nbind\\s*(.+)", "check": {"type": "string", "value": ['0.0.0.0']}}] + } + if self.check_san_baseline(redis_server_ip): + ret.append(redis_server_ip) + + # 查看redis是否设置密码 + redis_server_not_pass = { + 'id': 43, + "type": "password", + "harm": "高", + "level": "3", + "check_file": "/www/server/redis", + "repaired": "0", + "name": "Redis 查看是否设置密码", + "file": '/www/server/redis/redis.conf', + "Suggestions": "加固建议, 在%s 中的为未设置密码 例如" % ('/www/server/redis/redis.conf'), + "repair": "requirepass requirepassQWERQQQQQQQ", + "rule": [ + {"re": "\nrequirepass\\s*(.+)", "check": {"type": "string", "value": []}}] + } + if not self.check_san_baseline(redis_server_not_pass): + ret.append(redis_server_not_pass) + + # 查看redis 是否是弱密码 + redis_server_pass = { + 'id': 44, + "type": "password", + "harm": "高", + "level": "3", + "repaired": "0", + "check_file": "/www/server/redis", + "name": "Redis 存在弱密码", + "file": '/www/server/redis/redis.conf', + "Suggestions": "加固建议, 在%s 中requirepass 设置为强密码" % ('/www/server/redis/redis.conf'), + "repair": "requirepass requirepassQWERQQQQQQQ", + "rule": [ + {"re": "\nrequirepass\\s*(.+)", "check": {"type": "string", "value": ['123456', 'admin', 'damin888']}}] + } + if not self.check_san_baseline(redis_server_pass): + ret.append(redis_server_pass) + # 查看版本是否是低于最新版本 + if os.path.exists('/www/server/redis/version.pl'): + re2t = public.ReadFile('/www/server/redis/version.pl') + if re2t != '5.0.3': + ret2 = { + 'id': 45, + "type": "password", + "harm": "高", + "check_file": "/www/server/redis", + "level": "3", + "repaired": "0", + "name": "Redis 版本低于最新版本", + "file": '/www/server/redis/redis.conf', + "Suggestions": "加固建议,升级到最新版的redis", + "repair": "最新版为5.0.3" + } + ret.append(ret2) + return ret + + # memcached 配置安全 + def memcache_security(self): + ret = [] + memcache_bind = { + 'id': 46, + "type": "file", + "harm": "高", + "level": "3", + "repaired": "0", + "name": "Memcache 监听IP为0.0.0.0", + "check_file": "/usr/local/memcached", + "file": '/etc/init.d/memcached', + "Suggestions": "加固建议, 在%s 中的监听IP设置为127.0.0.1 例如" % ('/etc/init.d/memcached'), + "repair": "IP=127.0.0.1", + "rule": [ + {"re": "\nIP\\s?=\\s?(.+)", "check": {"type": "string", "value": ['0.0.0.0']}}] + } + if self.check_san_baseline(self.__repair['46']): + ret.append(self.__repair['46']) + return ret + + # 查看是否是弱密码 + def get_root_pass(self): + # mysql 弱密码 + if not os.path.exists('/www/server/mysql'): return True + ret = public.M('config').field('mysql_root').select()[0]['mysql_root'] + if ret == '123456' or ret == 'admin': + return False + if len(ret) <= 6: + return False + return True + + # 查看mysql 是否有对外连接用户 + def chekc_mysql_user(self): + if not os.path.exists('/www/server/mysql'):return True + ret = public.M('config').field('mysql_root').select()[0]['mysql_root'] + sql = ''' mysql -uroot -p''' + ret + ''' -e "select User,Host from mysql.user where host='%'" ''' + resutl = public.ExecShell(sql) + if resutl[0] == '': + return True + else: + return False + + # mysql 配置安全 + def mysql_security(self): + result = [] + if not self.get_root_pass(): + ret = { + 'id': 47, + "type": "password", + "harm": "高", + "repaired": "0", + "level": "3", + "name": "Mysql root密码为弱密码", + "file": '/etc/init.d/memcached', + "Suggestions": "加固建议: 使用强密码", + "repair": "例如:adM1#@$544..", + } + result.append(ret) + + if public.M('firewall').where('port=?', ('3306',)).count(): + ret = { + 'id': 48, + "type": "password", + "harm": "高", + "repaired": "0", + "level": "3", + "name": "3306 端口对外开放", + "file": '/etc/init.d/memcached', + "Suggestions": "加固建议: 建议3306不对外开放,如果是特殊需求可以忽略这次记录", + "repair": "关闭3306对外访问", + } + result.append(ret) + + if not self.chekc_mysql_user(): + e = '''select User,Host from mysql.user where host='%' ''' + ret = { + 'id': 49, + "type": "password", + "harm": "高", + "repaired": "0", + "level": "3", + "name": "Mysql 存在外部连接用户", + "file": '/etc/my.local', + "Suggestions": "加固建议: 进入数据库查看mysql用户表", + "repair": e, + } + result.append(ret) + return result + + # 系统用户 安全 + def user_security(self): + result = [] + if not self.check_san_baseline(self.__repair['50']): + result.append(self.__repair['50']) + if not self.check_san_baseline(self.__repair['51']): + result.append(self.__repair['51']) + if not self.check_san_baseline(self.__repair['52']): + result.append(self.__repair['52']) + + # 存在非root 的管理员用户(危险) + get_root_0 = { + 'id': 53, + "type": "shell", + "harm": "紧急", + "repaired": "0", + "level": "5", + "name": "存在非root 的管理员用户(危险)", + "ps": "除root以为的其他的UID为0的用户的应该删除。或者为其分配新的UID", + "cmd": '''cat /etc/passwd | awk -F: '($3 == 0) { print $1 }'|grep -v '^root$' ''', + "find": {"re": r"\w+"} + } + if not self.check_san_baseline(get_root_0): + result.append(get_root_0) + if not self.check_san_baseline(self.__repair['54']): + result.append(self.__repair['54']) + if not self.check_san_baseline(self.__repair['55']): + result.append(self.__repair['55']) + + # 查看用户是否空密码的用户 + if len(self.user_not_password()) >= 1: + user_len = { + 'id': 56, + "type": "file", + "harm": "中", + "repaired": "0", + "level": "2", + "name": "系统存在空密码的用户", + "file": "/etc/login.defs ", + "Suggestions": "加固建议 为如下%s这些用户添加密码" % self.user_not_password(), + "repair": "(如果用户不用可以删除)", + } + result.append(user_len) + + return result + + # 查看用户是否有空密码的用户 + def user_not_password(self): + ret = public.ReadFile('/etc/passwd') + ret = ret.split('\n') + base_user = [] + not_pass_user = [] + for i in ret: + i = i.split(':') + if i[-1] == '/sbin/nologin': + continue + if i[0] == '': continue + base_user.append(i[0]) + check_file_resutl = public.ReadFile('/etc/shadow') + check_file_resutl = check_file_resutl.split('\n') + for i in check_file_resutl: + if not i: continue + i = i.split(':') + # print(i[0],base_user) + if i[0] in base_user: + if i[1] == '!!': + not_pass_user.append(i[0]) + return not_pass_user + + # 计划任务 安全 + def tasks_security(self): + ret = [] + if not os.path.exists(public.get_cron_path()):return ret + f = open(public.get_cron_path(), 'r') + for i in f.readlines(): + + if not i: continue; + i2 = i + i = i.strip().split() + if not i: continue + if i == None: continue + if i[5]: + if '/www/server/' not in i[5]: + if '/root/.acme.sh' not in i[5]: + if 'wget' in i or 'curl' in i or 'bash' or 'http://' in i or 'https://' in i: + task ={ + 'name': "异常计划任务", + "harm": "高", + "repaired": "0", + "level": 3, + "repair": "请排查是否是异常下载", + "Suggestions":"请排查是否是异常下载", + 'list': i2 + } + ret.append(task) + return ret + + # system 关键目录权限 + def system_dir_security(self): + # 关键性文件权限 + result = [] + user_config_chmoe = [ + { + 'id': 57, + "type": "chmod", + "file": "/etc/passwd", + "chmod": [644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 58, + "type": "chmod", + "file": "/etc/shadow", + "chmod": [400], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 59, + "type": "chmod", + "file": "/etc/group", + "chmod": [644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 60, + "type": "chmod", + "file": "/etc/gshadow", + "chmod": [400], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 61, + "type": "chmod", + "file": "/etc/hosts.allow", + "chmod": [644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 62, + "type": "chmod", + "file": "/etc/hosts.deny", + "chmod": [644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 63, + "type": "chmod", + "file": "/www", + "chmod": [755], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 64, + "type": "chmod", + "file": "/www/server", + "chmod": [755], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 65, + "type": "chmod", + "file": "/www/wwwroot", + "chmod": [755], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 66, + "type": "chmod", + "file": "/etc/rc.d", + "chmod": [755], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 67, + "type": "chmod", + "file": "/etc/rc.local", + "chmod": [644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 68, + "type": "chmod", + "file": "/etc/rc.d/rc.local", + "chmod": [644], + "user": ['root'], + 'group': ['root'] + }, { + 'id': 69, + "type": "chmod", + "file": "/var/spool/cron/root", + "chmod": [600], + "user": ['root'], + 'group': ['root'] + } + ] + for i in user_config_chmoe: + if not self.check_san_baseline(i): + ret1 = { + 'id':i['id'], + "harm": "高", + "repaired": "1", + "type": "file", + "name": "系统关键性文件权限错误%s" % i['file'], + "Suggestions": "加固建议 : %s 权限改为%s 所属用户为%s" % (i['file'], i['chmod'], i['user']), + "repair": "加固建议 : %s 权限改为%s 所属用户为%s" % (i['file'], i['chmod'], i['user']), + } + result.append(ret1) + return result + + # 查看站点是否开启SSL + # 取SSL状态 + def GetSSL(self, siteName): + path = '/etc/letsencrypt/live/' + siteName; + type = 0 + if os.path.exists(path + '/README'): type = 1; + if os.path.exists(path + '/partnerOrderId'): type = 2; + csrpath = path + "/fullchain.pem"; # 生成证书路径 + keypath = path + "/privkey.pem"; # 密钥文件路径 + key = public.readFile(keypath); + csr = public.readFile(csrpath); + file = self.setupPath + '/panel/vhost/' + public.get_webserver() + '/' + siteName + '.conf' + conf = public.readFile(file); + if not conf: return False + keyText = 'SSLCertificateFile' + if public.get_webserver() == 'nginx': keyText = 'ssl_certificate'; + status = True + if (conf.find(keyText) == -1): + status = False + type = -1 + return status + + # 取SSL的 SSL 协议 + def get_ssl_tls(self, siteName): + tls = [] + if os.path.exists('/www/server/panel/vhost/nginx/%s.conf' % siteName): + ret = public.ReadFile('/www/server/panel/vhost/nginx/%s.conf' % siteName) + valuse = re.findall(r'ssl_protocols\s+(.+)', ret) + print(valuse) + if not valuse: return tls + if not valuse[0]: return tls + if 'TLSv1' in valuse[0]: + tls.append('TLSv1') + if 'TLSv1.1' in valuse[0]: + tls.append('LSv1.1') + return tls + + # 是否使用宝塔防火墙 + def get_btwaf(self): + if os.path.exists('/www/server/btwaf'): + return True + else: + return False + + def site_security(self): + # 是否开启防御跨站的 + resutl = {} + site_secr = [] + site_lists = public.M('sites').field('name,path').select() + for i in site_lists: + + path = i['path'] + '/.user.ini' + ssl = self.GetSSL(i['name']) + tls = [] + if ssl: + tls = self.get_ssl_tls(i['name']) + if not os.path.exists(path): + site = { + "user_ini": False, + "level": 1, + "name": '%s该站点未启用SSL' % i['name'], + "ssl": ssl, + "tls": tls, + "harm": "警告", + } + if not ssl: + site['Suggestions'] = '加固建议使用https为访问方式' + site['repair'] = 'https 强制模式' + site['ps'] = '%s该站点未启用SSL' % i['name'] + else: + if tls: + site['Suggestions'] = '加固建议: 建议使用TLS1.2及以上的安全协议' + site['repair'] = 'TLS1.2 或者TLS1.3' + site['name'] = '%s该站点启用了不安全的SSL协议LSv1 或者LSv1.1' % i['name'] + site['ps'] = '%s该站点启用了不安全的SSL协议LSv1 或者LSv1.1' % i['name'] + site_secr.append(site) + else: + site = { + "user_ini": True, + "level": 1, + "name": '%s该站点未启用SSL' % i['name'], + "ssl": ssl, + "tls": tls, + "harm": "警告", + } + if not ssl: + site['Suggestions'] = '加固建议使用https为访问方式' + site['repair'] = 'https 强制模式' + site['ps'] = '%s该站点未启用SSL' % i['name'] + else: + if tls: + site['Suggestions'] = '加固建议: 建议使用TLS1.2及以上的安全协议' + site['repair'] = 'TLS1.2 或者TLS1.3' + site['name'] = '%s该站点启用了不安全的SSL协议LSv1 或者LSv1.1' % i['name'] + site['ps'] = '%s该站点启用了不安全的SSL协议LSv1 或者LSv1.1' % i['name'] + site_secr.append(site) + resutl['site_list'] = site_secr + resutl['btwaf'] = self.get_btwaf() + return resutl + + # 主判断函数 + def check_san_baseline(self, base_json): + if base_json['type'] == 'file': + if 'check_file' in base_json: + if not os.path.exists(base_json['check_file']): + return False + else: + if os.path.exists(base_json['file']): + ret = public.ReadFile(base_json['file']) + for i in base_json['rule']: + valuse = re.findall(i['re'], ret) + print(valuse) + if i['check']['type'] == 'number': + if not valuse: return False + if not valuse[0]: return False + valuse = int(valuse[0]) + + if valuse > i['check']['min'] and valuse < i['check']['max']: + return True + else: + return False + elif i['check']['type'] == 'string': + + if not valuse: return False + if not valuse[0]: return False + valuse = valuse[0] + print(valuse) + if valuse in i['check']['value']: + return True + else: + return False + return True + + elif base_json['type'] == 'diff': + if os.path.exists(base_json['file']): + ret = public.ReadFile(base_json['file']) + for i in base_json['rule']: + valuse = re.findall(i['re'], ret) + if not valuse: return False + if not valuse[0]: return False + if i['check']['type'] == 'string': + if valuse[0] in i['check']['value']: + return True + else: + return False + else: + return True + + elif base_json['type'] == 'password': + if os.path.exists(base_json['file']): + ret = public.ReadFile(base_json['file']) + for i in base_json['rule']: + valuse = re.findall(i['re'], ret) + print(valuse) + if not valuse: return False + if not valuse[0]: return False + if not i['check']['value']: return True + if i['check']['value']: + if valuse[0] in i['check']['value']: + return False + else: + return True + else: + return True + + elif base_json['type'] == 'dir': + if os.path.exists(base_json['file']): + return False + else: + return True + + + elif base_json['type'] == 'shell': + ret = public.ExecShell(base_json['cmd']) + if not ret: return True + if not ret[0]: return True + if re.search(base_json['find']['re'], ret[0]): + return False + else: + return True + + elif base_json['type'] == 'chmod': + #@print(base_json) + if os.path.exists(base_json['file']): + ret = self.GetFileAccess(base_json['file']) + print(base_json['chmod']) + if ret['chown'] in base_json['user'] and int(ret['chmod']) in base_json['chmod'] and ret['group'] in \ + base_json['group']: + return True + else: + return False + else: + return True + + # 获取文件/目录 权限信息 + def GetFileAccess(self, filename): + if sys.version_info[0] == 2: filename = filename.encode('utf-8'); + data = {} + try: + import pwd + stat = os.stat(filename) + data['chmod'] = str(oct(stat.st_mode)[-3:]) + data['chown'] = pwd.getpwuid(stat.st_uid).pw_name + data['group'] = pwd.getpwuid(stat.st_gid).pw_name + except: + data['chmod'] = 755 + data['chown'] = 'www' + data['group'] = 'www' + return data + + ####################################网站连通性############################## + + # 网站连通性 + def site_curl_security(self): + result = [] + site_list = public.M('sites').field('name').select() + if len(site_list) >= 1: + for i in site_list: + site = i['name'] + print(site) + try: + ret = requests.get('http://127.0.0.1', timeout=3, headers={"host": site}, verify=False) + if ret.status_code != 200: + ret_status = { + "type": "site", + "repaired": "0", + "name": "%s站点通过本机访问失败" % i['nane'], + "harm": "警告", + 'level':"1", + "file": "%s站点通过本机访问失败" % i['name'], + "Suggestions": "加固建议, 检查是否是绑定了当前服务器的IP", + "repair": "检查是否是绑定了当前服务器的IP" + } + result.append(ret_status) + except: + continue + return result + + ################################## Nginx/APACHE 安全################################ + # Nginx/Apache 配置安全 + def Nginx_Apache_security(self): + ret = [] + Nginx_Get_version = { + 'id': 70, + "type": "file", + "name": "Nginx 版本泄露", + "harm": "低", + 'level': "1", + "repaired": "0", + "file": '/www/server/nginx/conf/nginx.conf', + "Suggestions": "加固建议, 在%s expose_php的值修改为Off中修改" % ('/www/server/nginx/conf/nginx.conf'), + "repair": "expose_php = Off", + "rule": [ + {"re": r"server_tokens\s*(.+)", "check": {"type": "string", "value": ['off;']}}] + } + if not self.check_san_baseline(Nginx_Get_version): + ret.append(Nginx_Get_version) + if os.path.exists('/www/server/nginx/version.pl'): + ret2 = public.ReadFile('/www/server/nginx/version.pl') + if ret2 == '1.8': + Nginx_Get_version = { + 'id': 71, + "type": "file", + 'level': "1", + "repaired": "0", + "name": "Nginx 版本过低", + "harm": "低", + "file": '/www/server/nginx/conf/nginx.conf', + "Suggestions": "加固建议, 升级至最新版的Nginx 软件", + "repair": "例如:Nignx1.17 或者Nginx1.16", + } + ret.append(Nginx_Get_version) + + return ret + + #################################### system 关键文件版本 ###################### + # system 关键文件版本 + def system_version_security(self): + ret = [] + return ret + + # 查询日志(查看进度) + def get_api_log(self, get): + + if not os.path.exists(self.logPath): public.returnMsg(False, "无日志") + ret = json.loads(public.readFile(self.logPath)) + if int(len(ret)) == 0: + return public.returnMsg(False, "无日志") + return public.returnMsg(True, ret) + # 写输出日志 + + def WriteLogs(self, logMsg): + fp = open(self.logPath, 'w+') + fp.write(logMsg) + fp.close() + + # 查询日志(查看进度) + def get_resut(self, get): + time.sleep(0.5) + if not os.path.exists(self.config): public.returnMsg(False, "无日志") + ret = json.loads(public.readFile(self.config)) + if int(len(ret)) == 0: + return public.returnMsg(False, "无日志") + return public.returnMsg(True, ret) + # 写输出日志 + + def Write_result(self, logMsg): + fp = open(self.config, 'w+') + fp.write(logMsg) + fp.close() + + def Write(self, name, count): + Speed = {} + Speed['name'] = '正在进行检测%s' % name + Speed['total'] = 0 + Speed['Current_file'] = None # 当前发送的文件 + Speed['progress'] = "%.2f" % (float(count) / float(13) * 100) + Speed['ok'] = False + self._Speed = Speed + self.WriteLogs(json.dumps(Speed)) + + ######################################################################### + # 统计入口 + def San_Entrance(self): + if os.path.exists(self.logPath): os.remove(self.logPath) + SSH = self.ssh_security() + self.Write(name='ssh安全监测', count=1) + + time.sleep(1) + PANEL = self.panel_security() + self.Write(name='面板安全监测', count=2) + time.sleep(1) + PHP = self.php_security() + self.Write(name='PHP安全监测', count=3) + time.sleep(1) + NINGX = self.Nginx_Apache_security() + self.Write(name='Nginx/Apache安全监测', count=4) + time.sleep(1) + redis = self.redis_security() + self.Write(name='redis安全监测', count=5) + time.sleep(1) + memcache = self.memcache_security() + self.Write(name='memcache安全监测', count=6) + + mysql = self.mysql_security() + self.Write(name='Mysql安全监测', count=7) + time.sleep(1) + system_user = self.user_security() + self.Write(name='系统用户安全监测', count=8) + time.sleep(1) + task = self.tasks_security() + self.Write(name='系统计划任务安全监测', count=9) + time.sleep(1) + site_curl = self.site_curl_security() + self.Write(name='网站连接监测', count=10) + time.sleep(1) + system_dir = self.system_dir_security() + self.Write(name='系统关键目录安全监测', count=11) + time.sleep(1) + site_sec = self.site_security() + self.Write(name='网站安全监测', count=12) + time.sleep(1) + system_file = self.system_version_security() + self.Write(name='系统关键性文件监控', count=13) + + if not self._Speed == None: + self._Speed['ok'] = True + self._Speed['name'] = '所有扫描完毕' + self.WriteLogs(json.dumps(self._Speed)) + + aa = { + "SSH": SSH, + "PANEL": PANEL, + "PHP": PHP, + "NINGX/APCHE": NINGX, + "redis": redis, + "memcache": memcache, + "mysql": mysql, + "system_user": system_user, + "task": task, + "site_curl": site_curl, + "system_dir": system_dir, + "site_sec": site_sec, + "system_file": system_file, + } + + self.Write_result(json.dumps(aa)) + return aa + + def start(self, get): + os.system(public.get_python_bin() + ' /www/server/panel/class/san_baseline.py &') + return public.returnMsg(True, '1') + + # 取爆破 + def get_ssh_errorlogin(self, get): + import datetime + path = '/var/log/secure' + if not os.path.exists(path): public.writeFile(path, ''); + fp = open(path, 'r'); + l = fp.readline(); + data = {}; + data['intrusion'] = []; + # data['intrusion_total'] = 0; + + data['defense'] = []; + data['defense_total'] = 0; + + data['success'] = []; + data['success_total'] = 0; + day_count = 0 + data['intrusion_total'] = day_count + limit = 10000; + flag_limit = 1 + while l and flag_limit <= 10000: + if l.find('Failed password for root') != -1: + flag_limit += 1 + if len(data['intrusion']) > limit: del (data['intrusion'][0]); + + months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + time_str11 = re.findall(r'\w+\s+\d+\s+.\d+:\d+:\d+', l) + if time_str11[0]: + time_str = re.findall(r'\w+\s+\d+', time_str11[0]) + month = int(months[time_str[0].split()[0]]) + day = int(time_str[0].split()[1]) + cur_month = datetime.datetime.now().month + cur_day = datetime.datetime.now().day + if month != cur_month: + continue + else: + if month == cur_month and day == cur_day: + day_count+=1 + else: + continue + + #data['intrusion'].append(l); + #data['intrusion_total'] += 1; + elif l.find('Accepted') != -1: + if len(data['success']) > limit: del (data['success'][0]); + data['success'].append(l); + # data['success_total'] += 1; + l = fp.readline(); + data['intrusion_total'] = day_count + months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + + success = []; + for g in data['success']: + tmp = {} + tmp1 = g.split(); + tmp['date'] = months[tmp1[0]] + '/' + tmp1[1] + ' ' + tmp1[2]; + tmp['user'] = tmp1[8]; + tmp['address'] = tmp1[10]; + success.append(tmp); + data['success'] = success; + + return data; + + + + # 修复的主函数 + def repair_san_baseline(self, base_json): + if base_json['type'] == 'file': + if os.path.exists(base_json['file']): + ret = public.ReadFile(base_json['file']) + for i in base_json['repair_loophole']: + valuse = re.search(i['re'], ret) + if valuse: + data2=re.sub(i['re'],i['check'],ret) + public.WriteFile(base_json['file'],data2) + return True + else: + return False + if base_json['type'] == 'chmod': + if os.path.exists(base_json['file']): + os.system('chown %s:%s %s'%(base_json['user'],base_json['group'],base_json['file'])) + os.system('chmod %s %s'%(base_json['chmod'],base_json['file'])) + return True + + # 修复 + def repair(self,get): + id=get.id + if id in self.__repair: + return self.repair_san_baseline(self.__repair[id]) + else: + return False + + # 修复全部 + def repair_all(self,get): + for i in self.__repair: + if self.__repair[i]['repaired']=='1': + self.repair_san_baseline(self.__repair[i]) + return True + +if __name__ == '__main__': + my_api = san_baseline() + r_data = my_api.San_Entrance() diff --git a/class_v2/site_dir_auth_v2.py b/class_v2/site_dir_auth_v2.py new file mode 100644 index 00000000..f5fb6d79 --- /dev/null +++ b/class_v2/site_dir_auth_v2.py @@ -0,0 +1,420 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: zhwen +#------------------------------------------------------------------- + +#------------------------------ +# 站点目录密码保护 +#------------------------------ +import public,re,os,json,shutil +from public.validate import Param + +class SiteDirAuth: + # 取目录加密状态 + def __init__(self): + self.setup_path = public.GetConfigValue('setup_path') + self.conf_file = self.setup_path + "/panel/data/site_dir_auth.json" + # 读取配置 + def _read_conf(self): + conf = public.readFile(self.conf_file) + if not conf: + conf = {} + public.writeFile(self.conf_file,json.dumps(conf)) + return conf + try: + conf = json.loads(conf) + if not isinstance(conf,dict): + conf = {} + public.writeFile(self.conf_file, json.dumps(conf)) + except: + conf = {} + public.writeFile(self.conf_file, json.dumps(conf)) + return conf + + def _write_conf(self,conf,site_name): + c = self._read_conf() + if not c or site_name not in c: + c[site_name] = [conf] + else: + if site_name in c: + c[site_name].append(conf) + public.writeFile(self.conf_file,json.dumps(c)) + + def _check_site_authorization(self,site_name): + webserver=public.get_webserver() + conf_file = "{setup_path}/panel/vhost/{webserver}/{site_name}.conf".format( + setup_path=self.setup_path, site_name=site_name,webserver=webserver) + if "Authorization" in public.readFile(conf_file): + return True + + + # 设置目录加密 + def set_dir_auth(self,get): + ''' + get.name auth_name + get.site_dir auth_dir + get.username username + get.password password + get.id site id + :param get: + :return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_dir').String(), + Param('name').String(), + Param('username').String(), + Param('password').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + # if len(get.username) < 3 or len(get.password) < 3: + # return public.return_msg_gettext(False, 'Username or password cannot be less than 3 characters') + # name = get.name + param = self.__check_param(get) + if param['status']==-1: + return param + param = param['message'] + password = param['password'] + username = param['username'] + name = param['name'] + site_dir = get.site_dir + if public.get_webserver() == "openlitespeed": + return public.return_message(-1,0,"OpenLiteSpeed is currently not supported") + # if not hasattr(get,"password") or not get.password or not hasattr(get,"username") or not get.username: + # return public.return_msg_gettext(False, 'Please enter an account or password') + if not get.site_dir: + return public.return_message(-1,0, 'Please enter the directory to be protected') + if not get.name: + return public.return_message(-1,0, 'Please enter the Name') + passwd = public.hasPwd(password) + site_info = self.get_site_info(get.id) + site_name = site_info["site_name"] + if self._check_site_authorization(site_name): + return public.return_message(-1,0, 'Site password protection has been set, please cancel and then set. Site directory --> Password access') + if self._check_dir_auth(site_name, name,site_dir): + return public.return_message(-1,0, 'Directory has been protected') + auth = "{user}:{passwd}".format(user=username,passwd=passwd) + auth_file = '{setup_path}/pass/{site_name}'.format(setup_path=self.setup_path,site_name=site_name) + if not os.path.exists(auth_file): + os.makedirs(auth_file) + auth_file = auth_file+"/{}.pass".format(name) + public.writeFile(auth_file,auth) + # 配置独立认证文件 + self.set_dir_auth_file(site_info["site_path"],site_name,name,username,site_dir,auth_file) + # 配置站点主文件 + result = self.set_conf(site_name,"create") + if result: + return public.return_message(0,0,result) + # 检查配置 + webserver = public.get_webserver() + result=self.check_site_conf(webserver,site_name,name) + if result: + return public.return_message(0,0,result) + # 写配置 + conf = {"name":name,"site_dir":get.site_dir,"auth_file":auth_file} + self._write_conf(conf,site_name) + public.serviceReload() + return public.return_message(0,0,"Successfully created") + + # 检查配置是否存在 + def _check_dir_auth(self, site_name, name,site_dir): + conf = self._read_conf() + if not conf: + return False + if site_name in conf: + for i in conf[site_name]: + if name in i.values() or site_dir == i["site_dir"]: + return True + + # 获取当前站点php版本 + def get_site_php_version(self,siteName): + try: + conf = public.readFile(self.setup_path + '/panel/vhost/'+public.get_webserver()+'/'+siteName+'.conf'); + if public.get_webserver() == 'nginx': + rep = r"enable-php-(\w{2,5})\.conf" + tmp = re.search(rep,conf) + if not tmp: + rep = r"enable-php-(\d+-wpfastcgi).conf" + re.search(rep, conf) + else: + rep = r"php-cgi-(\w{2,5})\.sock" + tmp = re.search(rep,conf).groups() + if tmp: + return tmp[0] + else: + return "" + except: + return public.return_msg_gettext(False, 'Apache2.2 does NOT support MultiPHP!') + + # 获取站点名 + def get_site_info(self,id): + site_info = public.M('sites').where('id=?', (id,)).field('name,path').find() + return {"site_name":site_info["name"],"site_path":site_info["path"]} + + def change_dir_auth_file_nginx_phpver(self,site_name,phpv,auth_name): + file_path = "{setup_path}/panel/vhost/nginx/dir_auth/{site_name}/{auth_name}.conf".format( + setup_path=self.setup_path,site_name=site_name,auth_name=auth_name) + conf = public.readFile(file_path) + if not conf: + return False + + if phpv == 'other': + php_conf = "include /www/server/panel/vhost/other_php/{}/enable-php-other.conf;".format(site_name) + else: + php_conf = 'include enable-php-{}.conf;'.format(phpv) + + rep = r"include\s+(enable-php-\w+|/www/server/panel/vhost/other_php/{}/enable-php-other)\.conf;".format(site_name) + conf = re.sub(rep,php_conf,conf) + + public.writeFile(file_path,conf) + + # 设置独立认证文件 + def set_dir_auth_file(self,site_path,site_name,name,username,site_dir,auth_file): + php_ver = self.get_site_php_version(site_name) + php_conf = "" + if php_ver: + if php_ver == 'other': + php_conf = "include /www/server/panel/vhost/other_php/{}/enable-php-{}.conf;".format(site_name,php_ver) + else: + php_conf = "include enable-php-{}.conf;".format(php_ver) + + for i in ["nginx","apache"]: + file_path = "{setup_path}/panel/vhost/{webserver}/dir_auth/{site_name}" + if i == "nginx": + # 设置nginx + conf = '''location ~* ^%s* { + #AUTH_START + auth_basic "Authorization"; + auth_basic_user_file %s; + %s + #AUTH_END +}''' % (site_dir,auth_file,php_conf) + else: + # 设置apache + conf = ''' + #AUTH_START + AuthType basic + AuthName "Authorization " + AuthUserFile {auth_file} + Require user {username} + #AUTH_END + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + #Require all granted + DirectoryIndex index.php index.html index.htm default.php default.html default.htm +'''.format(site_path=site_path,site_dir=site_dir,auth_file=auth_file,username=username,site_name=site_name) + conf_file = file_path.format(setup_path=self.setup_path,site_name=site_name,webserver=i) + if not os.path.exists(conf_file): + os.makedirs(conf_file) + conf_file = conf_file + '/{}.conf'.format(name) + public.writeFile(conf_file,conf) + + # 设置apache配置 + def set_conf(self,site_name,act): + for i in ["nginx", "apache"]: + dir_auth_file = "%s/panel/vhost/%s/dir_auth/%s/*.conf" % (self.setup_path,i,site_name,) + file = self.setup_path + "/panel/vhost/{}/".format(i) + site_name + ".conf" + shutil.copyfile(file, '/tmp/{}_file_bk.conf'.format(i)) + + if os.path.exists(file): + conf = public.readFile(file) + if i == "apache": + if act == "create": + rep = "IncludeOptional.*\\/dir_auth\\/.*conf(\n|.)+<\\/VirtualHost>" + rep1 = "" + if not re.search(rep, conf): + conf = conf.replace(rep1, + "\n\t#Directory protection rules, do not manually delete\n\tIncludeOptional {}\n".format( + dir_auth_file)) + else: + rep = "\n*#Directory protection rules, do not manually delete\n+\\s+IncludeOptional[\\s\\w\\/\\.\\*]+" + conf = re.sub(rep, '', conf) + public.writeFile(file, conf) + else: + if act == "create": + rep = "#SSL-END(\n|.)+include.*\\/dir_auth\\/.*conf;" + rep1 = "#SSL-END" + if not re.search(rep,conf): + conf = conf.replace(rep1, rep1 + "\n\t#Directory protection rules, do not manually delete\n\tinclude {};".format(dir_auth_file)) + else: + rep = "\n*#Directory protection rules, do not manually delete\n+\\s+include[\\s\\w\\/\\.\\*]+;" + conf = re.sub(rep, '', conf) + public.writeFile(file, conf) + + # 验证站点配置 + def check_site_conf(self,webserver,site_name,name): + isError = public.checkWebConfig() + auth_file = "{setup_path}/panel/vhost/{webserver}/dir_auth/{site_name}/{name}.conf".format(setup_path=self.setup_path,webserver=webserver,site_name=site_name,name=name) + if (isError != True): + os.remove(auth_file) + # a_conf = self._read_conf() + # for i in range(len(a_conf)-1,-1,-1): + # if site_name == a_conf[i]["sitename"] and a_conf[i]["proxyname"]: + # del a_conf[i] + return public.return_msg_gettext(False, 'ERROR: %s
                                    ' % public.get_msg_gettext('Configuration ERROR') + isError.replace("\n", + '
                                    ') + '
                                    ') + + # 删除密码保护 + def delete_dir_auth(self,get): + ''' + get.id + get.name + :param get: + :return: + ''' + try: + get.validate([ + Param('name').String(), + Param('id').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + name = get.name + site_info = self.get_site_info(get.id) + site_name = site_info["site_name"] + conf = self._read_conf() + if site_name not in conf: + return public.return_message(-1,0,"The website does not exist in the configuration:{}",(site_name,)) + for i in range(len(conf[site_name])): + if name in conf[site_name][i].values(): + print(conf[site_name][i]) + del(conf[site_name][i]) + if not conf[site_name]: + del(conf[site_name]) + break + public.writeFile(self.conf_file,json.dumps(conf)) + for i in ["nginx", "apache"]: + file_path = "{setup_path}/panel/vhost/{webserver}/dir_auth/{site_name}/{name}.conf".format(webserver=i, + setup_path=self.setup_path, + site_name=site_name, + name=name) + os.remove(file_path) + if not conf: + self.set_conf(site_name,"delete") + if not hasattr(get,'multiple'): + public.serviceReload() + return public.return_message(0,0,'Successfully deleted!') + + # 修改目录保护密码 + def modify_dir_auth_pass(self,get): + ''' + get.id + get.name + get.username + get.password + :param get: + :return: + ''' + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('username').String(), + Param('password').String(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # if not hasattr(get,"password") or not get.password or not hasattr(get,"username") or not get.username: + # return public.return_msg_gettext(False, 'Username or password cannot be less than 3 characters') + param = self.__check_param(get) + if param['status']==-1: + return param + param = param['message'] + password = param['password'] + username = param['username'] + name = get.name + site_info = self.get_site_info(get.id) + site_name = site_info["site_name"] + passwd = public.hasPwd(get.password) + auth = "{user}:{passwd}".format(user=get.username,passwd=passwd) + auth_file = '{setup_path}/pass/{site_name}/{name}.pass'.format(setup_path=self.setup_path,site_name=site_name,name=name) + public.writeFile(auth_file,auth) + public.serviceReload() + return public.return_message(0,0,'Setup successfully!') + + # 获取目录保护列表 + def get_dir_auth(self,get): + ''' + get.id + get.sitename + :param get: + :return: + ''' + # 校验参数 + try: + get.validate([ + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + if not hasattr(get, 'siteName'): + site_info = self.get_site_info(get.id) + site_name = site_info["site_name"] + else: + site_name = get.siteName + conf = self._read_conf() + if site_name in conf: + return public.return_message(0,0,{site_name:conf[site_name]}) + return public.return_message(0,0,{}) + + def __check_param(self, get): + values = {} + if hasattr(get, "password"): + if not get.password: + return public.return_message(-1,0, 'Please enter password!') + password = get.password.strip() + if len(password) < 3: + return public.return_message(-1,0, 'Password cannot be less than 3 characters') + if re.search(r'\s', password): + return public.return_message(-1,0, 'Password cannot contain spaces') + values['password'] = password + + if hasattr(get, "username"): + if not get.username: + return public.return_message(-1,0, 'Please enter username!') + username = get.username.strip() + if len(username) < 3: + return public.return_message(-1,0, 'Username cannot be less than 3 characters') + if re.search(r'\s', username): + return public.return_message(-1,0, 'Username cannot contain spaces') + values['username'] = username + + if hasattr(get, "name"): + if not get.name: + return public.return_message(-1,0, 'Please enter a name!') + name = get.name.strip() + if len(name) < 3: + return public.return_message(-1,0, 'Name cannot be less than 3 characters') + if re.search(r'\s', name): + return public.return_message(-1,0, 'Name cannot contain spaces') + if re.search('[\\/\"\'\\!@#$%^&*()+={}\\[\\]\\:\\;\\?><,./\\\\]+', name): + return public.return_message(-1,0, 'Name format must be [ aaa_bbb ]') + values['name'] = name + + return public.return_message(0,0, values) diff --git a/class_v2/ssh_security_v2.py b/class_v2/ssh_security_v2.py new file mode 100644 index 00000000..23a6497e --- /dev/null +++ b/class_v2/ssh_security_v2.py @@ -0,0 +1,1018 @@ +#coding: utf-8 +#------------------------------------------------------------------- +# aaPanel +#------------------------------------------------------------------- +# Copyright (c) 2015-2017 aaPanel(www.aapanel.com) All rights reserved. +#------------------------------------------------------------------- +# Author: lkqiang +#------------------------------------------------------------------- +# SSH 安全类 +#------------------------------ +import public,os,re,send_mail,json +from datetime import datetime +from public.validate import Param + + +class ssh_security: + __type_list = ['ed25519','ecdsa','rsa', 'dsa'] + __key_type_file = '{}/data/ssh_key_type.pl'.format(public.get_panel_path()) + __key_files = ['/root/.ssh/id_ed25519','/root/.ssh/id_ecdsa','/root/.ssh/id_rsa','/root/.ssh/id_rsa_bt'] + __type_files = { + "ed25519": "/root/.ssh/id_ed25519", + "ecdsa": "/root/.ssh/id_ecdsa", + "rsa": "/root/.ssh/id_rsa", + "dsa": "/root/.ssh/id_dsa" + } + open_ssh_login = public.get_panel_path() + '/data/open_ssh_login.pl' + + __SSH_CONFIG='/etc/ssh/sshd_config' + __ip_data = None + __ClIENT_IP='/www/server/panel/data/host_login_ip.json' + __pyenv = 'python' + __REPAIR={"1":{"id":1, + "type":"file", + "harm":"High", + "repaired":"1", + "level":"3", + "name":"Make sure SSH MaxAuthTries is set between 3-6", + "file":"/etc/ssh/sshd_config", + "Suggestions":"Remove the MaxAuthTries comment symbol # in /etc/ssh/sshd_config, set the maximum number of failed password attempts 3-6 recommended 4", + "repair":"MaxAuthTries 4", + "rule":[{"re":"\nMaxAuthTries\\s*(\\d+)","check":{"type":"number","max":7,"min":3}}], + "repair_loophole":[{"re":"\n?#?MaxAuthTries\\s*(\\d+)","check":"\nMaxAuthTries 4"}]}, + "2":{"id":2, + "repaired":"1", + "type":"file", + "harm":"High", + "level":"3", + "name":"SSHD Mandatory use of V2 security protocol", + "file":"/etc/ssh/sshd_config", + "Suggestions":"Set parameters in the /etc/ssh/sshd_config file as follows", + "repair":"Protocol 2", + "rule":[{"re":"\nProtocol\\s*(\\d+)", + "check":{"type":"number","max":3,"min":1}}], + "repair_loophole":[{"re":"\n?#?Protocol\\s*(\\d+)","check":"\nProtocol 2"}]}, + "3":{"id":3, + "repaired":"1", + "type":"file", + "harm":"High", + "level":"3", + "name":"Set SSH idle exit time", + "file":"/etc/ssh/sshd_config", + "Suggestions":"Set ClientAliveInterval to 300 to 900 in /etc/ssh/sshd_config, which is 5-15 minutes, and set ClientAliveCountMax to 0-3", + "repair":"ClientAliveInterval 600 ClientAliveCountMax 2", + "rule":[{"re":"\nClientAliveInterval\\s*(\\d+)","check":{"type":"number","max":900,"min":300}}], + "repair_loophole":[{"re":"\n?#?ClientAliveInterval\\s*(\\d+)","check":"\nClientAliveInterval 600"}]}, + "4":{"id":4, + "repaired":"1", + "type":"file", + "harm":"High", + "level":"3", + "name":"Make sure SSH LogLevel is set to INFO", + "file":"/etc/ssh/sshd_config", + "Suggestions":"Set parameters in the /etc/ssh/sshd_config file as follows (uncomment)", + "repair":"LogLevel INFO", + "rule":[{"re":"\nLogLevel\\s*(\\w+)","check":{"type":"string","value":["INFO"]}}], + "repair_loophole":[{"re":"\n?#?LogLevel\\s*(\\w+)","check":"\nLogLevel INFO"}]}, + "5":{"id":5, + "repaired":"1", + "type":"file", + "harm":"High", + "level":"3", + "name":"Disable SSH users with empty passwords from logging in", + "file":"/etc/ssh/sshd_config", + "Suggestions":"Configure PermitEmptyPasswords to no in /etc/ssh/sshd_config", + "repair":"PermitEmptyPasswords no", + "rule":[{"re":"\nPermitEmptyPasswords\\s*(\\w+)","check":{"type":"string","value":["no"]}}], + "repair_loophole":[{"re":"\n?#?PermitEmptyPasswords\\s*(\\w+)","check":"\nPermitEmptyPasswords no"}]}, + "6":{"id":6, + "repaired":"1", + "type":"file", + "name":"SSH uses the default port 22", + "harm":"High", + "level":"3", + "file":"/etc/ssh/sshd_config", + "Suggestions":"Set Port to 6000 to 65535 in / etc / ssh / sshd_config", + "repair":"Port 60151", + "rule":[{"re":"Port\\s*(\\d+)","check":{"type":"number","max":65535,"min":22}}], + "repair_loophole":[{"re":"\n?#?Port\\s*(\\d+)","check":"\nPort 65531"}]}} + __root_login_types = {'yes':'yes - keys and passwords','no':'no - no login','without-password':'without-password - only key login','forced-commands-only':'forced-commands-only - can only execute commands'} + + + def __init__(self): + if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'ssh_login_record')).count(): + public.M('').execute('''CREATE TABLE ssh_login_record ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + addr TEXT, + server_ip TEXT, + user_agent TEXT, + ssh_user TEXT, + login_time INTEGER DEFAULT 0, + close_time INTEGER DEFAULT 0, + video_addr TEXT);''') + public.M('').execute('CREATE INDEX ssh_login_record ON ssh_login_record (addr);') + + if not os.path.exists(self.__ClIENT_IP): + public.WriteFile(self.__ClIENT_IP,json.dumps([])) + self.__mail=send_mail.send_mail() + self.__mail_config=self.__mail.get_settings() + self._check_pyenv() + try: + self.__ip_data = json.loads(public.ReadFile(self.__ClIENT_IP)) + except: + self.__ip_data=[] + + def _check_pyenv(self): + if os.path.exists('/www/server/panel/pyenv'): + self.__pyenv = 'btpython' + + def get_ssh_key_type(self): + ''' + 获取ssh密钥类型 + @author hwliang + :return: + ''' + default_type = 'rsa' + if not os.path.exists(self.__key_type_file): + return default_type + new_type = public.ReadFile(self.__key_type_file) + if new_type in self.__type_list: + return new_type + return default_type + + + def return_python(self): + if os.path.exists('/www/server/panel/pyenv/bin/python'):return '/www/server/panel/pyenv/bin/python' + if os.path.exists('/usr/bin/python'):return '/usr/bin/python' + if os.path.exists('/usr/bin/python3'):return '/usr/bin/python3' + return 'python' + + + def return_profile(self): + if os.path.exists('/root/.bash_profile'): return '/root/.bash_profile' + if os.path.exists('/etc/profile'): return '/etc/profile' + fd = open('/root/.bash_profil', mode="w", encoding="utf-8") + fd.close() + return '/root/.bash_profil' + + def return_bashrc(self): + if os.path.exists('/root/.bashrc'):return '/root/.bashrc' + if os.path.exists('/etc/bashrc'):return '/etc/bashrc' + if os.path.exists('/etc/bash.bashrc'):return '/etc/bash.bashrc' + fd = open('/root/.bashrc', mode="w", encoding="utf-8") + fd.close() + return '/root/.bashrc' + + + def check_files(self): + try: + json.loads(public.ReadFile(self.__ClIENT_IP)) + except: + public.WriteFile(self.__ClIENT_IP, json.dumps([])) + + def get_ssh_port(self): + conf = public.readFile(self.__SSH_CONFIG) + if not conf: conf = '' + rep = r"#*Port\s+([0-9]+)\s*\n" + tmp1 = re.search(rep,conf) + port = '22' + if tmp1: + port = tmp1.groups(0)[0] + return port + + # 主判断函数 + def check_san_baseline(self, base_json): + if base_json['type'] == 'file': + if 'check_file' in base_json: + if not os.path.exists(base_json['check_file']): + return False + else: + if os.path.exists(base_json['file']): + ret = public.ReadFile(base_json['file']) + for i in base_json['rule']: + valuse = re.findall(i['re'], ret) + if i['check']['type'] == 'number': + if not valuse: return False + if not valuse[0]: return False + valuse = int(valuse[0]) + if valuse > i['check']['min'] and valuse < i['check']['max']: + return True + else: + return False + elif i['check']['type'] == 'string': + if not valuse: return False + if not valuse[0]: return False + valuse = valuse[0] + if valuse in i['check']['value']: + return True + else: + return False + return True + + def san_ssh_security(self,get): + data={"num":100,"result":[]} + result = [] + ret = self.check_san_baseline(self.__REPAIR['1']) + if not ret: result.append(self.__REPAIR['1']) + ret = self.check_san_baseline(self.__REPAIR['2']) + if not ret: result.append(self.__REPAIR['2']) + ret = self.check_san_baseline(self.__REPAIR['3']) + if not ret: result.append(self.__REPAIR['3']) + ret = self.check_san_baseline(self.__REPAIR['4']) + if not ret: result.append(self.__REPAIR['4']) + ret = self.check_san_baseline(self.__REPAIR['5']) + if not ret: result.append(self.__REPAIR['5']) + ret = self.check_san_baseline(self.__REPAIR['6']) + if not ret: result.append(self.__REPAIR['6']) + data["result"]=result + if len(result)>=1: + data['num']=data['num']-(len(result)*10) + return data + + ################## SSH 登陆报警设置 #################################### + def send_mail_data(self,title,body,type=None): + try: + login_send_type_conf = "/www/server/panel/data/ssh_send_type.pl" + if not os.path.exists(login_send_type_conf): + login_type = "mail" + else: + login_type = public.readFile(login_send_type_conf).strip() + if not login_type: + login_type = "mail" + object = public.init_msg(login_type.strip()) + if not object: + return False + if login_type=="mail": + data={} + data['title'] = title + data['msg'] = body + object.push_data(data) + elif login_type=="wx_account": + object.send_msg(body) + else: + + msg = public.get_push_info("SSH login warning",['>Send Content:' + body]) + object.push_data(msg) + except: + pass + + #检测非UID为0的账户 + def check_user(self): + ret = [] + cfile = '/etc/passwd' + if os.path.exists(cfile): + f = open(cfile, 'r') + for i in f: + i = i.strip().split(":") + if i[2] == '0' and i[3] == '0': + if i[0] == 'root': continue + ret.append(i[0]) + if ret: + data=''.join(ret) + public.run_thread(self.send_mail_data,args=(public.GetLocalIp()+' There is a backdoor user in the server',public.GetLocalIp()+' There is a backdoor user in the server '+data+' please check/etc/passwd',)) + return True + else: + return False + + #记录root 的登陆日志 + + #返回登陆IP + def return_ip(self,get): + self.check_files() + # return public.returnMsg(True, self.__ip_data) + return public.return_message(0, 0, self.__ip_data) + + #添加IP白名单 + def add_return_ip(self, get): + + # 校验参数 + try: + get.validate([ + Param('ip').Require().String().Ip(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self.check_files() + if get.ip.strip() in self.__ip_data: + # return public.returnMsg(False, "Already exists") + return public.return_message(-1, 0, "Already exists") + else: + self.__ip_data.append(get.ip.strip()) + public.writeFile(self.__ClIENT_IP, json.dumps(self.__ip_data)) + # return public.returnMsg(True, "Added successfully") + return public.return_message(0, 0, "Added successfully") + + def del_return_ip(self, get): + # 校验参数 + try: + get.validate([ + Param('ip').Require().Ip(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + self.check_files() + if get.ip.strip() in self.__ip_data: + self.__ip_data.remove(get.ip.strip()) + public.writeFile(self.__ClIENT_IP, json.dumps(self.__ip_data)) + # return public.returnMsg(True, "Successfully deleted") + return public.return_message(0, 0, "Successfully deleted") + else: + # return public.returnMsg(False, "IP does not exist") + return public.return_message(-1, 0, "IP does not exist") + + #取登陆的前50个条记录 + def login_last(self): + self.check_files() + data=public.ExecShell('last -n 50') + data=re.findall(r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0]) + if data>=1: + data2=list(set(data)) + for i in data2: + if not i in self.__ip_data: + self.__ip_data.append(i) + public.writeFile(self.__ClIENT_IP, json.dumps(self.__ip_data)) + return self.__ip_data + + #获取ROOT当前登陆的IP + def get_ip(self): + data = public.ExecShell(''' who am i |awk ' {print $5 }' ''') + data = re.findall(r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",data[0]) + return data + + def get_logs(self, get): + + # 分页校验参数 + try: + get.validate([ + Param('p_size').Integer(), + Param('p').Integer(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + import page + page = page.Page() + count = public.M('logs').where('type=?', ('SSH security',)).count() + limit = 10 + info = {} + info['count'] = count + info['row'] = limit + info['p'] = 1 + if hasattr(get, 'p'): + info['p'] = int(get['p']) + info['uri'] = get + info['return_js'] = '' + if hasattr(get, 'tojs'): + info['return_js'] = get.tojs + data = {} + # 获取分页数据 + data['page'] = page.GetPage(info, '1,2,3,4,5,8') + data['data'] = public.M('logs').where('type=?', (u'SSH security',)).order('id desc').limit( + str(page.SHIFT) + ',' + str(page.ROW)).field('log,addtime').select() + # return data + return public.return_message(0, 0, data) + + def get_server_ip(self): + if os.path.exists('/www/server/panel/data/iplist.txt'): + data=public.ReadFile('/www/server/panel/data/iplist.txt') + return data.strip() + else:return '127.0.0.1' + + + #登陆的情况下 + def login(self): + self.check_files() + self.check_user() + self.__ip_data = json.loads(public.ReadFile(self.__ClIENT_IP)) + ip=self.get_ip() + if len(ip[0])==0:return False + try: + import time + mDate = time.strftime('%Y-%m-%d %X', time.localtime()) + if ip[0] in self.__ip_data: + if public.M('logs').where('type=? addtime', ('SSH security',mDate,)).count():return False + public.WriteLog('SSH security', 'The server {} login IP is {}, login user is root'.format(public.GetLocalIp(),ip[0])) + return False + else: + if public.M('logs').where('type=? addtime', ('SSH security', mDate,)).count(): return False + self.send_mail_data('Server {} login alarm'.format(public.GetLocalIp()),'There is a login alarm on the server {}, the login IP is {}, the login user is root'.format(public.GetLocalIp(),ip[0])) + public.WriteLog('SSH security','There is a login alarm on the server {}, the login IP is {}, login user is root'.format(public.GetLocalIp(),ip [0])) + return True + except: + pass + + + + #修复bashrc文件 + def repair_bashrc(self): + data = public.ReadFile(self.return_bashrc()) + if re.search(self.return_python() + ' /www/server/panel/class/ssh_security.py', data): + public.WriteFile(self.return_bashrc(),data.replace(self.return_python()+' /www/server/panel/class/ssh_security.py login','')) + #遗留的错误信息 + datassss = public.ReadFile(self.return_bashrc()) + if re.search(self.return_python(),datassss): + public.WriteFile(self.return_bashrc(),datassss.replace(self.return_python(),'')) + + + #开启监控 + def start_jian(self,get): + self.repair_bashrc() + data = public.ReadFile(self.return_profile()) + if not re.search(self.return_python() + ' /www/server/panel/class/ssh_security.py', data): + cmd = '''shell="%s /www/server/panel/class/ssh_security.py login" + nohup `${shell}` &>/dev/null & + disown $!''' % (self.return_python()) + public.WriteFile(self.return_profile(), data.strip() + '\n' + cmd) + return public.returnMsg(True, 'Open successfully') + return public.returnMsg(False, 'Open failed') + + #关闭监控 + def stop_jian(self,get): + data = public.ReadFile(self.return_profile()) + if re.search(self.return_python()+' /www/server/panel/class/ssh_security.py', data): + cmd='''shell="%s /www/server/panel/class/ssh_security.py login"'''%(self.return_python()) + data=data.replace(cmd, '') + cmd='''nohup `${shell}` &>/dev/null &''' + data=data.replace(cmd, '') + cmd='''disown $!''' + data=data.replace(cmd, '') + public.WriteFile(self.return_profile(),data) + #检查是否还存在遗留 + if re.search(self.return_python()+' /www/server/panel/class/ssh_security.py', data): + public.WriteFile(self.return_profile(),data.replace(self.return_python()+' /www/server/panel/class/ssh_security.py login','')) + #遗留的错误信息 + datassss = public.ReadFile(self.return_profile()) + if re.search(self.return_python(),datassss): + public.WriteFile(self.return_profile(),datassss.replace(self.return_python(),'')) + + return public.returnMsg(True, 'Closed successfully') + else: + return public.returnMsg(True, 'Closed successfully') + + #监控状态 + def get_jian(self,get): + data = public.ReadFile(self.return_profile()) + #if re.search(r'{}\/www\/server\/panel\/class\/ssh_security.py\s+login'.format(r".*python\s+"), data): + if re.search('/www/server/panel/class/ssh_security.py login', data): + return public.returnMsg(True, '1') + else: + return public.returnMsg(False, '1') + + def set_password(self, get): + ''' + 开启密码登陆 + get: 无需传递参数 + ''' + ssh_password = r'\n#?PasswordAuthentication\s\w+' + file = public.readFile(self.__SSH_CONFIG) + if not file: + return public.return_message(-1, 0, 'ERROR: sshd config configuration file does not exist, cannot continue!') + # return public.returnMsg(False,'ERROR: sshd config configuration file does not exist, cannot continue!') + if len(re.findall(ssh_password, file)) == 0: + file_result = file + '\nPasswordAuthentication yes' + else: + file_result = re.sub(ssh_password, '\nPasswordAuthentication yes', file) + self.wirte(self.__SSH_CONFIG, file_result) + self.restart_ssh() + public.WriteLog('SSH management', 'Enable password login') + # return public.returnMsg(True, 'Open successfully') + return public.return_message(0, 0, 'Open successfully') + + def set_sshkey(self, get): + ''' + 设置ssh 的key + 参数 ssh=rsa&type=yes + ''' + # 分页校验参数 + try: + get.validate([ + Param('ssh').Require().String('in', ['yes', 'no']).Xss(), + Param('type').Require().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + # ssh_type = ['yes', 'no'] + ssh = get.ssh + # if not ssh in ssh_type: return public.returnMsg(False, 'ssh option failed') + s_type = get.type + if not s_type in self.__type_list: + # return public.returnMsg(False, 'Wrong encryption method') + return public.return_message(-1, 0, "Wrong encryption method") + + authorized_keys = '/root/.ssh/authorized_keys' + file = ['/root/.ssh/id_{}.pub'.format(s_type), '/root/.ssh/id_{}'.format(s_type)] + for i in file: + if os.path.exists(i): + public.ExecShell(r'sed -i "\~$(cat %s)~d" %s' % (file[0], authorized_keys)) + os.remove(i) + os.system("ssh-keygen -t {s_type} -P '' -f /root/.ssh/id_{s_type} |echo y".format(s_type = s_type)) + if os.path.exists(file[0]): + public.ExecShell('cat %s >> %s && chmod 600 %s' % (file[0], authorized_keys, authorized_keys)) + rec = r'\n#?RSAAuthentication\s\w+' + rec2 = r'\n#?PubkeyAuthentication\s\w+' + file = public.readFile(self.__SSH_CONFIG) + if not file: + # return public.returnMsg(False, 'ERROR: sshd config configuration file does not exist, cannot continue!') + return public.return_message(-1, 0, "ERROR: sshd config configuration file does not exist") + if len(re.findall(rec, file)) == 0: file = file + '\nRSAAuthentication yes' + if len(re.findall(rec2, file)) == 0: file = file + '\nPubkeyAuthentication yes' + file_ssh = re.sub(rec, '\nRSAAuthentication yes', file) + file_result = re.sub(rec2, '\nPubkeyAuthentication yes', file_ssh) + if ssh == 'no': + ssh_password = r'\n#?PasswordAuthentication\s\w+' + if len(re.findall(ssh_password, file_result)) == 0: + file_result = file_result + '\nPasswordAuthentication no' + else: + file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file_result) + self.wirte(self.__SSH_CONFIG, file_result) + public.writeFile(self.__key_type_file, s_type) + self.restart_ssh() + public.WriteLog('SSH management', 'Set up SSH key authentication and successfully generate the key') + # return public.returnMsg(True, 'Open successfully') + return public.return_message(0, 0, 'Open successfully') + else: + public.WriteLog('SSH management', 'Failed to set SSH key authentication') + # return public.returnMsg(False, 'Open failed') + return public.return_message(-1, 0, "Open failed") + + # 取SSH信息 + + def get_msg_push_list(self,get): + """ + @name 获取消息通道配置列表 + @auther: cjxin + @date: 2022-08-16 + @ + """ + cpath = 'data/msg.json' + try: + if 'force' in get or not os.path.exists(cpath): + public.downloadFile('{}/linux/panel/msg/msg.json'.format("https://node.aapanel.com"),cpath) + except : pass + + data = {} + if os.path.exists(cpath): + msgs = json.loads(public.readFile(cpath)) + for x in msgs: + x['setup'] = False + x['info'] = False + key = x['name'] + try: + obj = public.init_msg(x['name']) + if obj: + x['setup'] = True + x['info'] = obj.get_version_info(None) + except : + print(public.get_error_info()) + pass + data[key] = x + # return data + return public.return_message(0, 0, data) + + + #取消告警 + def clear_login_send(self,get): + + # 校验参数 + try: + get.validate([ + Param('type').Require().String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + login_send_type_conf = "/www/server/panel/data/ssh_send_type.pl" + os.remove(login_send_type_conf) + self.stop_jian(get) + # return public.returnMsg(True, 'Successfully cancel the login alarm!') + return public.return_message(0, 0, "Successfully cancel the login alarm") + + #设置告警 + def set_login_send(self,get): + # 校验参数 + try: + get.validate([ + Param('type').Require().String().Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + login_send_type_conf = "/www/server/panel/data/ssh_send_type.pl" + set_type=get.type.strip() + msg_configs = self.get_msg_push_list(get) + if set_type not in msg_configs.keys(): + # return public.returnMsg(False,'This send type is not supported') + return public.return_message(-1, 0, "This send type is not supported") + + from panelMessage import panelMessage + pm = panelMessage() + obj = pm.init_msg_module(set_type) + if not obj: + # return public.returnMsg(False, "The message channel is not installed.") + return public.return_message(-1, 0, "The message channel is not installed") + + public.writeFile(login_send_type_conf, set_type) + self.start_jian(get) + # return public.returnMsg(True, 'Successfully set') + return public.return_message(0, 0, "Successfully set") + + #查看告警 + def get_login_send(self, get): + login_send_type_conf = "/www/server/panel/data/ssh_send_type.pl" + if os.path.exists(login_send_type_conf): + send_type = public.readFile(login_send_type_conf).strip() + else: + send_type ="error" + # return public.returnMsg(True, send_type) + return public.return_message(0, 0, send_type) + + def GetSshInfo(self, get): + # port = public.get_ssh_port() + + pid_file = '/run/sshd.pid' + if os.path.exists(pid_file): + pid = int(public.readFile(pid_file)) + status = public.pid_exists(pid) + else: + import system + panelsys = system.system() + version = panelsys.GetSystemVersion() + if os.path.exists('/usr/bin/apt-get'): + if os.path.exists('/etc/init.d/sshd'): + status = public.ExecShell("service sshd status | grep -P '(dead|stop)'|grep -v grep") + else: + status = public.ExecShell("service ssh status | grep -P '(dead|stop)'|grep -v grep") + else: + if version.find(' 7.') != -1 or version.find(' 8.') != -1 or version.find('Fedora') != -1: + status = public.ExecShell("systemctl status sshd.service | grep 'dead'|grep -v grep") + else: + status = public.ExecShell("/etc/init.d/sshd status | grep -e 'stopped' -e '已停'|grep -v grep") + + # return status; + if len(status[0]) > 3: + status = False + else: + status = True + return status + + + def stop_key(self, get): + ''' + 关闭key + 无需参数传递 + ''' + is_ssh_status=self.GetSshInfo(get) + rec = r'\n\s*#?\s*RSAAuthentication\s+\w+' + rec2 = r'\n\s*#?\s*PubkeyAuthentication\s+\w+' + file = public.readFile(self.__SSH_CONFIG) + if not file: + # return public.returnMsg(False,'错误:sshd_config配置文件不存在,无法继续!') + return public.return_message(-1, 0, "Error: sshd config configuration file does not exist") + file_ssh = re.sub(rec, '\nRSAAuthentication no', file) + file_result = re.sub(rec2, '\nPubkeyAuthentication no', file_ssh) + self.wirte(self.__SSH_CONFIG, file_result) + + if is_ssh_status: + self.set_password(get) + self.restart_ssh() + public.WriteLog('SSH management','Disable SSH key login') + # return public.returnMsg(True, 'Disable successfully') + return public.return_message(0, 0, "Disable successfully") + + + + def get_config(self, get): + ''' + 获取配置文件 + 无参数传递 + ''' + result = {} + file = public.readFile(self.__SSH_CONFIG) + if not file: + # return public.returnMsg(False, 'Error: sshd config does not exist') + return public.return_message(-1, 0, "Error: sshd config does not exist") + + # ======== 以下在2022-10-12重构 ========== + # author : hwliang + # 是否开启RSA公钥认证 + # 默认开启(最新版openssh已经不支持RSA公钥认证) + # yes = 开启 + # no = 关闭 + result['rsa_auth'] = 'yes' + rec = r'^\s*RSAAuthentication\s*(yes|no)' + rsa_find = re.findall(rec, file, re.M|re.I) + if rsa_find and rsa_find[0].lower() == 'no': result['rsa_auth'] = 'no' + + # 获取是否开启公钥认证 + # 默认关闭 + # yes = 开启 + # no = 关闭 + result['pubkey'] = 'no' + if self.get_key(get)['msg']: # 先检查是否存在可用的公钥 + pubkey = r'^\s*PubkeyAuthentication\s*(yes|no)' + pubkey_find = re.findall(pubkey, file, re.M|re.I) + if pubkey_find and pubkey_find[0].lower() == 'yes': result['pubkey'] = 'yes' + + + # 是否开启密码登录 + # 默认开启 + # yes = 开启 + # no = 关闭 + result['password'] = 'yes' + ssh_password = r'^\s*PasswordAuthentication\s*([\w\-]+)' + ssh_password_find = re.findall(ssh_password, file, re.M|re.I) + if ssh_password_find and ssh_password_find[0].lower() == 'no': result['password'] = 'no' + + #是否允许root登录 + # 默认允许 + # yes = 允许 + # no = 不允许 + # without-password = 允许,但不允许使用密码登录 + # forced-commands-only = 允许,但只允许执行命令,不能使用终端 + result['root_is_login'] = 'yes' + result['root_login_type'] = 'yes' + root_is_login=r'^\s*PermitRootLogin\s*([\w\-]+)' + root_is_login_find = re.findall(root_is_login, file, re.M|re.I) + if root_is_login_find and root_is_login_find[0].lower() != 'yes': + result['root_is_login'] = 'no' + result['root_login_type'] = root_is_login_find[0].lower() + result['root_login_types'] = self.__root_login_types + # return result + return public.return_message(0, 0, result) + + + def set_root(self, get): + ''' + 开启密码登陆 + get: 无需传递参数 + ''' + # without-password yes no forced-commands-only + # 分页校验参数 + try: + get.validate([ + Param('p_type').String('in', ['yes', 'no', 'without-password', 'forced-commands-only']).Xss(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + p_type = 'yes' + if 'p_type' in get: p_type = get.p_type + if p_type not in self.__root_login_types.keys(): + # return public.returnMsg(False, 'Parameter passing error!') + return public.return_message(-1, 0, 'Parameter passing error') + ssh_password = r'^\s*#?\s*PermitRootLogin\s*([\w\-]+)' + file = public.readFile(self.__SSH_CONFIG) + src_line = re.search(ssh_password, file,re.M) + new_line = 'PermitRootLogin {}'.format(p_type) + if not src_line: + file_result = file + '\n{}'.format(new_line) + else: + file_result = file.replace(src_line.group(),new_line) + self.wirte(self.__SSH_CONFIG, file_result) + self.restart_ssh() + msg = 'Set the root login method as: {}'.format(self.__root_login_types[p_type]) + public.WriteLog('SSH management',msg) + # return public.returnMsg(True, msg) + return public.return_message(0, 0, msg) + + def stop_root(self, get): + ''' + 开启密码登陆 + get: 无需传递参数 + ''' + ssh_password = r'\n\s*PermitRootLogin\s+\w+' + file = public.readFile(self.__SSH_CONFIG) + if len(re.findall(ssh_password, file)) == 0: + file_result = file + '\nPermitRootLogin no' + else: + file_result = re.sub(ssh_password, '\nPermitRootLogin no', file) + self.wirte(self.__SSH_CONFIG, file_result) + self.restart_ssh() + public.WriteLog('SSH management','Set the root login method to: no') + return public.returnMsg(True, 'Disable successfully') + + def stop_password(self, get): + ''' + 关闭密码访问 + 无参数传递 + ''' + file = public.readFile(self.__SSH_CONFIG) + ssh_password = r'\n#?PasswordAuthentication\s\w+' + file_result = re.sub(ssh_password, '\nPasswordAuthentication no', file) + self.wirte(self.__SSH_CONFIG, file_result) + self.restart_ssh() + public.WriteLog('SSH management','Disable password access') + return public.returnMsg(True, 'Closed successfully') + + def get_key(self, get): + ''' + 获取key 无参数传递 + ''' + key_type = self.get_ssh_key_type() + if key_type in self.__type_files.keys(): + key_file = self.__type_files[key_type] + key = public.readFile(key_file) + return public.returnMsg(True,key) + # return public.return_message(0, 0, key) + return public.returnMsg(True, '') + # return public.return_message(0, 0, '') + + def download_key(self, get): + ''' + @name 下载密钥 + ''' + download_file = '' + key_type = self.get_ssh_key_type() + if key_type in self.__type_files.keys(): + if os.path.exists(self.__type_files[key_type]): + download_file = self.__type_files[key_type] + + else: + for file in self.__key_files: + if not os.path.exists(file): continue + download_file = file + break + + if not download_file: return public.returnMsg(False, 'Key file not found!') + from flask import send_file + filename = "{}_{}".format(public.GetHost(),os.path.basename(download_file)) + return send_file(download_file,download_name=filename) + + def wirte(self, file, ret): + result = public.writeFile(file, ret) + return result + + def restart_ssh(self): + ''' + 重启ssh 无参数传递 + ''' + version = public.readFile('/etc/redhat-release') + act = 'restart' + if not os.path.exists('/etc/redhat-release'): + public.ExecShell('service ssh ' + act) + elif version.find(' 7.') != -1 or version.find(' 8.') != -1: + public.ExecShell("systemctl " + act + " sshd.service") + else: + public.ExecShell("/etc/init.d/sshd " + act) + #检查是否设置了钉钉 + def check_dingding(self, get): + ''' + 检查是否设置了钉钉 + ''' + #检查文件是否存在 + if not os.path.exists('/www/server/panel/data/dingding.json'):return False + dingding_config=public.ReadFile('/www/server/panel/data/dingding.json') + if not dingding_config:return False + #解析json + try: + dingding=json.loads(dingding_config) + if dingding['dingding_url']: + return True + except: + return False + + #开启SSH双因子认证 + def start_auth_method(self, get): + ''' + 开启SSH双因子认证 + ''' + #检查是否设置了钉钉 + import ssh_authentication + ssh_class=ssh_authentication.ssh_authentication() + return ssh_class.start_ssh_authentication_two_factors() + + #关闭SSH双因子认证 + def stop_auth_method(self, get): + ''' + 关闭SSH双因子认证 + ''' + #检查是否设置了钉钉 + import ssh_authentication + ssh_class=ssh_authentication.ssh_authentication() + return ssh_class.close_ssh_authentication_two_factors() + + #获取SSH双因子认证状态 + def get_auth_method(self, get): + ''' + 获取SSH双因子认证状态 + ''' + #检查是否设置了钉钉 + import ssh_authentication + ssh_class=ssh_authentication.ssh_authentication() + return ssh_class.check_ssh_authentication_two_factors() + + #判断so文件是否存在 + def check_so_file(self, get): + ''' + 判断so文件是否存在 + ''' + import ssh_authentication + ssh_class=ssh_authentication.ssh_authentication() + return ssh_class.is_check_so() + + #下载so文件 + def get_so_file(self, get): + ''' + 下载so文件 + ''' + import ssh_authentication + ssh_class=ssh_authentication.ssh_authentication() + return ssh_class.download_so() + + #获取pin + def get_pin(self, get): + ''' + 获取pin + ''' + import ssh_authentication + ssh_class=ssh_authentication.ssh_authentication() + return public.returnMsg(True, ssh_class.get_pin()) + + def get_login_record(self,get): + if os.path.exists(self.open_ssh_login): + + return public.returnMsg(True,'') + else: + return public.returnMsg(False,'') + def start_login_record(self,get): + if os.path.exists(self.open_ssh_login): + return public.returnMsg(True,'') + else: + public.writeFile(self.open_ssh_login,"True") + return public.returnMsg(True,'') + def stop_login_record(self,get): + if os.path.exists(self.open_ssh_login): + os.remove(self.open_ssh_login) + return public.returnMsg(True,'') + else: + return public.returnMsg(True,'') + # 获取登录记录列表 + def get_record_list(self, get): + if 'limit' in get: + limit = int(get.limit.strip()) + else: + limit = 12 + import page + page = page.Page() + count = public.M('ssh_login_record').order("id desc").count() + info = {} + info['count'] = count + info['row'] = limit + info['p'] = 1 + if hasattr(get, 'p'): + info['p'] = int(get['p']) + info['uri'] = get + info['return_js'] = '' + if hasattr(get, 'tojs'): + info['return_js'] = get.tojs + data = {} + # 获取分页数据 + data['page'] = page.GetPage(info, '1,2,3,4,5,8') + + data['data'] = public.M('ssh_login_record').order('id desc').limit( + str(page.SHIFT) + ',' + str(page.ROW)).select() + + return data + + def get_file_json(self,get): + + if os.path.exists(get.path): + ret=json.loads(public.ReadFile(get.path)) + return ret + else: + return '' + +if __name__ == '__main__': + import sys + type = sys.argv[1] + if type=='login': + try: + aa = ssh_security() + aa.login() + except:pass + else: + pass diff --git a/class_v2/ssh_terminal_v2.py b/class_v2/ssh_terminal_v2.py new file mode 100644 index 00000000..0f4edaae --- /dev/null +++ b/class_v2/ssh_terminal_v2.py @@ -0,0 +1,1259 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import json +import time +import os +import sys +import socket +import threading +import re + + +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +from io import BytesIO, StringIO + +def returnMsg(status,msg,value=None): + if value: + msg = public.get_msg_gettext(msg,value) + return {'status':status,'msg':msg} + +import public + +class ssh_terminal: + _panel_path = '/www/server/panel' + _save_path = _panel_path + '/config/ssh_info/' + _host = None + _port = 22 + _user = None + _pass = None + _pkey = None + _ws = None + _ssh = None + _last_cmd = "" + _last_cmd_tip = 0 + _log_type = public.get_msg_gettext('aaPanel terminal') + _history_len = 0 + _client = "" + _rep_ssh_config = False + _sshd_config_backup = None + _rep_ssh_service = False + _tp = None + _old_conf = None + _debug_file = 'logs/terminal.log' + _s_code = None + _last_num = 0 + _key_passwd = None + _video_addr = "" + _host_row_id = "" + + def __init__(self): + # 创建jp_login_record表记录ssh登录记录 + if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'ssh_login_record')).count(): + public.M('').execute('''CREATE TABLE ssh_login_record ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + addr TEXT, + server_ip TEXT, + user_agent TEXT, + ssh_user TEXT, + login_time INTEGER DEFAULT 0, + close_time INTEGER DEFAULT 0, + video_addr TEXT);''') + public.M('').execute('CREATE INDEX ssh_login_record ON ssh_login_record (addr);') + self.time = time.time() + + def record(self, rtype, data): + if os.path.exists(public.get_panel_path() + "/data/open_ssh_login.pl") and self._video_addr: + path=self._video_addr + if rtype == 'header': + with open(path, 'w') as fw: + fw.write(json.dumps(data) + '\n') + return True + else: + with open(path, 'r') as fr: + content = json.loads(fr.read()) + stdout = content["stdout"] + atime = time.time() + iodata = [atime - self.time, data] + stdout.append(iodata) + content["stdout"] = stdout + with open(path, 'w') as fw: + fw.write(json.dumps(content) + '\n') + self.time = atime + return True + return False + + def connect(self): + ''' + @name 连接服务器 + @author hwliang<2020-08-07> + @return dict{ + status: bool 状态 + msg: string 详情 + } + ''' + if not self._host: return public.return_msg_gettext(False,'Wrong connection address') + + if not self._user: self._user = 'root' + if not self._port: self._port = 22 + self.is_local() + + if self._host in ['127.0.0.1','localhost']: + self._port = public.get_ssh_port() + self.set_sshd_config(True) + + num = 0 + while num < 5: + num +=1 + try: + self.debug(public.get_msg_gettext('Reconnection attempts:{}',(num,))) + if self._rep_ssh_config: time.sleep(0.1) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2 + num) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 8192) + sock.connect((self._host, self._port)) + break + except Exception as e: + if num == 5: + self.set_sshd_config(True) + self.debug(public.get_msg_gettext('Retry connection failed, {}',(e,))) + if self._host in ['127.0.0.1','localhost']: + return returnMsg(False,'Connection failure: {}',("Authentication failed ," + self._user + "@" + self._host + ":" +str(self._port),)) + return returnMsg(False,'Connection failure: {}',(self._host,self._port)) + else: + time.sleep(0.2) + + + import paramiko + + self._tp = paramiko.Transport(sock) + + try: + self._tp.start_client() + if not self._pass and not self._pkey: + self.set_sshd_config(True) + return public.return_msg_gettext(False,'Password or private key cannot both be empty: {}:{}',(self._host,str(self._port))) + self._tp.banner_timeout=60 + if self._pkey: + self.debug(public.get_msg_gettext('Authenticating private key')) + if sys.version_info[0] == 2: + try: + self._pkey = self._pkey.encode('utf-8') + except: + pass + p_file = BytesIO(self._pkey) + else: + p_file = StringIO(self._pkey) + + try: + if self._key_passwd: + pkey = paramiko.RSAKey.from_private_key(p_file,password=self._key_passwd) + else: + pkey = paramiko.RSAKey.from_private_key(p_file) + self.debug("尝试使用RSA私钥认证") + except Exception as ex: + try: + p_file.seek(0) # 重置游标 + if self._key_passwd: + pkey = paramiko.Ed25519Key.from_private_key(p_file,password=self._key_passwd) + else: + pkey = paramiko.Ed25519Key.from_private_key(p_file) + self.debug("尝试使用Ed25519私钥认证") + except: + try: + p_file.seek(0) + if self._key_passwd: + pkey = paramiko.ECDSAKey.from_private_key(p_file,password=self._key_passwd) + else: + pkey = paramiko.ECDSAKey.from_private_key(p_file) + self.debug("尝试使用ECDSA私钥认证") + except: + p_file.seek(0) + if self._key_passwd: + try: + pkey = paramiko.DSSKey.from_private_key(p_file,password=self._key_passwd) + except Exception as ex: + ex = str(ex) + if ex.find('OpenSSH private key file checkints do not match') != -1: + return public.returnMsg(False,'Incorrect private key password:{}'.format(ex)) + elif ex.find('encountered RSA key, expected DSA key') != -1: + pkey = paramiko.RSAKey.from_private_key(p_file,password=self._key_passwd) + else: + return public.returnMsg(False,'Private key error: {}'.format(ex)) + else: + pkey = paramiko.DSSKey.from_private_key(p_file) + if not pkey: return public.returnMsg(False,'Private key error!') + self._tp.auth_publickey(username=self._user, key=pkey) + else: + try: + self._tp.auth_none(self._user) + except Exception as e: + e = str(e) + if e.find('keyboard-interactive') >= 0: + self._auth_interactive() + else: + self.debug('Authenticating password') + self._tp.auth_password(username=self._user, password=self._pass) + # self._tp.auth_password(username=self._user, password=self._pass) + except Exception as e: + if self._old_conf: + s_file = '/www/server/panel/config/t_info.json' + if os.path.exists(s_file): os.remove(s_file) + self.set_sshd_config(True) + self._tp.close() + e = str(e) + if e.find('websocket error!') != -1: + return public.return_msg_gettext(True,'connection succeeded') + if e.find('Authentication timeout') != -1: + self.debug("认证超时{}".format(e)) + return public.return_msg_gettext(False,'Authentication timed out, please press enter to try again!{}',(e,)) + if e.find('Authentication failed') != -1: + self.debug('认证失败{}'.format(e)) + if self._key_passwd: + sshd_config = public.readFile('/etc/ssh/sshd_config') + if sshd_config and sshd_config.find('ssh-dss') == -1: + return returnMsg(False,'The private key verification fails, the private key may be incorrect, or the ssh-dss private key authentication type may not be enabled in the /etc/ssh/sshd_config configuration file') + return returnMsg(False,'Authentication failed, please check whether the private key is correct: {}'.format(e + "," + self._user + "@" + self._host + ":" +str(self._port))) + return public.return_msg_gettext(False,'Account or Password incorrect: {}',(str(e + "," + self._user + "@" + self._host + ":" +str(self._port)),)) + if e.find('Bad authentication type; allowed types') != -1: + self.debug(public.get_msg_gettext('Authentication failed {}',(str(e),))) + if self._host in ['127.0.0.1','localhost'] and self._pass == 'none': + return public.return_msg_gettext(False,'Username or Password incorrect: {}',(str("Authentication failed ," + self._user + "@" + self._host + ":" +str(self._port)),)) + return public.return_msg_gettext(False,'Unsupported authentication type: {}',(str(e))) + if e.find('Connection reset by peer') != -1: + self.debug(public.get_msg_gettext('The target server actively refused the connection')) + return public.return_msg_gettext(False,public.get_msg_gettext('The target server actively refused the connection')) + if e.find('Error reading SSH protocol banner') != -1: + self.debug('The protocol header response timed out') + return public.return_msg_gettext(False,public.get_msg_gettext('The protocol header response timed out, and the network quality with the target server was too bad: {}',(str(e),))) + if e.find('encountered RSA key, expected DSA key') != -1: + self.debug('Private keys may require password access') + return public.return_msg_gettext(False,public.get_msg_gettext('Private keys may require password access: {}',(str(e),))) + if e.find('password and salt must not be empty') != -1: + self.debug('Private keys may require password access') + return public.return_msg_gettext(False,public.get_msg_gettext('Private keys may require password access: {}',(str(e),))) + if not e: + self.debug('The SSH protocol handshake timed out') + return public.return_msg_gettext(False,"The SSH protocol handshake timed out, and the network quality with the target server is too bad") + err = public.get_error_info() + self.debug(err) + return public.return_msg_gettext(False,public.get_msg_gettext('unknown error: {}',(str(err),))) + + self.debug(public.get_msg_gettext('The authentication is successful and the session channel is being constructed')) + self._ssh = self._tp.open_session() + self._ssh.get_pty(term='xterm', width=100, height=34) + self._ssh.invoke_shell() + self._connect_time = time.time() + self._last_send = [] + from BTPanel import request + self._client = public.GetClientIp() +':' + str(request.environ.get('REMOTE_PORT')) + public.write_log_gettext(self._log_type,'Successfully logged in to the SSH server [{}:{}]',(self._host,str(self._port))) + self.history_send(public.get_msg_gettext("Login success\n")) + self.set_sshd_config(True) + self.debug(public.get_msg_gettext('Login success')) + from BTPanel import session + self._video_addr = "/www/server/panel/plugin/jumpserver/static/video/%s.json" % str(int(self._connect_time)) + if not os.path.exists("/www/server/panel/plugin/jumpserver/static/video/"): + os.makedirs("/www/server/panel/plugin/jumpserver/static/video/") + # 如果开启了录像功能 + user_agent = str(request.headers.get('User-Agent')) + if os.path.exists(public.get_panel_path() + "/data/open_ssh_login.pl"): + self._host_row_id = public.M('ssh_login_record').add( + 'addr,server_ip,ssh_user,user_agent,login_time,video_addr', + (self._client, self._host, self._user, user_agent + , int(self._connect_time), + self._video_addr)) + + self.record('header', { + "version": 1, + "width": 100, + "height": 29, + "timestamp": int(self._connect_time), + "env": { + "TERM": "xterm", + "SHELL": "/bin/bash", + }, + "stdout": [] + }) + return public.return_msg_gettext(True,'connection succeeded') + + def _auth_interactive(self): + self.debug('Verification Code') + + self.brk = False + + def handler(title, instructions, prompt_list): + if not self._ws: raise public.PanelError('websocket error!') + if instructions: + self._ws.send(instructions) + if title: + self._ws.send(title) + resp = [] + for pr in prompt_list: + if str(pr[0]).strip() == "Password:": + resp.append(self._pass) + elif str(pr[0]).strip() == "Verification code:": + # 获取前段传入的验证码 + self._ws.send("Verification code# ") + self._s_code = True + code = "" + while True: + data = self._ws.receive() + if data.find('"resize":1') != -1: + self.resize(data) + continue + self._ws.send(data) + if data in ["\n", "\r"]: break + code += data + resp.append(code) + self._ws.send("\n") + self._s_code = None + return tuple(resp) + + self._tp.auth_interactive(self._user, handler) + + def get_login_user(self): + ''' + @name 获取本地登录用户 + @author hwliang<2020-08-07> + @return string + ''' + + if self._user != 'root': return self._user + l_user = 'root' + ssh_config_file = '/etc/ssh/sshd_config' + ssh_config = public.readFile(ssh_config_file) + if not ssh_config: return l_user + + if ssh_config.find('PermitRootLogin yes') != -1: return l_user + + + user_list = self.get_ulist() + login_user = '' + for u_info in user_list: + if u_info['user'] == 'root': continue + if u_info['login'] == '/bin/bash': + login_user = u_info['user'] + break + + if not login_user: + return l_user + + return login_user + + + def get_ulist(self): + ''' + @name 获取本地用户列表 + @author hwliang<2020-08-07> + @return list + ''' + u_data = public.readFile('/etc/passwd') + u_list = [] + for i in u_data.split("\n"): + u_tmp = i.split(':') + if len(u_tmp) < 3: continue + u_info = {} + u_info['user'],u_info['pass'],u_info['uid'],u_info['gid'],u_info['user_msg'],u_info['home'],u_info['login'] = u_tmp + u_list.append(u_info) + return u_list + + def is_local(self): + ''' + @name 处理本地连接 + @author hwliang<2020-08-07> + @ps 如果host为127.0.0.1或localhost,则尝试自动使用publicKey登录 + @return void + ''' + + if self._pass: return + if self._pkey: return + if self._host in ['127.0.0.1','localhost']: + try: + self._port = public.get_ssh_port() + self.set_sshd_config() + s_file = '/www/server/panel/config/t_info.json' + if os.path.exists(s_file): + ssh_info = json.loads(public.en_hexb(public.readFile(s_file))) + self._host = ssh_info['host'].strip() + if 'username' in ssh_info: + self._user = ssh_info['username'] + if 'pkey' in ssh_info: + self._pkey = ssh_info['pkey'] + if 'password' in ssh_info: + self._pass = ssh_info['password'] + self._old_conf = True + return + ssh_key_type_file = '{}/data/ssh_key_type.pl'.format(public.get_panel_path()) + ssh_key_type = '' + if os.path.exists(ssh_key_type_file): + ssh_key_type_new = public.readFile(ssh_key_type_file) + if ssh_key_type_new: ssh_key_type = ssh_key_type_new.strip() + login_user = self.get_login_user() + if self._user == 'root' and login_user == 'root': + id_rsa_file = ['/root/.ssh/id_ed25519','/root/.ssh/id_ecdsa','/root/.ssh/id_rsa','/root/.ssh/id_rsa_bt'] + if ssh_key_type: id_rsa_file.insert(0,'/root/.ssh/id_{}'.format(ssh_key_type)) + for ifile in id_rsa_file: + if os.path.exists(ifile): + self._pkey = public.readFile(ifile) + host_path = self._save_path + self._host + if not os.path.exists(host_path): + os.makedirs(host_path,384) + return + + + if not self._pass or not self._pkey or not self._user: + home_path = '/home/' + login_user + if login_user == 'root': + home_path = '/root' + self._user = login_user + id_rsa_file = [home_path + '/.ssh/id_ed25519',home_path + '/.ssh/id_ecdsa',home_path + '/.ssh/id_rsa',home_path + '/.ssh/id_rsa_bt'] + if ssh_key_type: id_rsa_file.insert(0,home_path + '/.ssh/id_{}'.format(ssh_key_type)) + for ifile in id_rsa_file: + if os.path.exists(ifile): + self._pkey = public.readFile(ifile) + return + + self._pass = 'none' + return + + except: + return + + def get_sys_version(self): + ''' + @name 获取操作系统版本 + @author hwliang<2020-08-13> + @return bool + ''' + version = public.readFile('/etc/redhat-release') + if not version: + version = public.readFile('/etc/issue').strip().split("\n")[0].replace('\\n','').replace(r'\l','').strip() + else: + version = version.replace('release ','').replace('Linux','').replace('(Core)','').strip() + return version + + def get_ssh_status(self): + ''' + @name 获取SSH服务状态 + @author hwliang<2020-08-13> + @return bool + ''' + version = self.get_sys_version() + if os.path.exists('/usr/bin/apt-get'): + if os.path.exists('/etc/init.d/sshd'): + status = public.ExecShell("service sshd status | grep -P '(dead|stop|not running)'|grep -v grep") + else: + status = public.ExecShell("service ssh status | grep -P '(dead|stop|not running)'|grep -v grep") + else: + if version.find(' 7.') != -1 or version.find(' 8.') != -1 or version.find('Fedora') != -1: + status = public.ExecShell("systemctl status sshd.service | grep 'dead'|grep -v grep") + else: + status = public.ExecShell("/etc/init.d/sshd status | grep -e 'stopped' -e '已停'|grep -v grep") + if len(status[0]) > 3: + status = False + else: + status = True + return status + + def is_running(self,rep = False): + ''' + @name 处理SSH服务状态 + @author hwliang<2020-08-13> + @param rep 是否恢复原来的SSH服务状态 + @return bool + ''' + try: + if rep and self._rep_ssh_service: + self.restart_ssh('stop') + return True + + ssh_status = self.get_ssh_status() + if not ssh_status: + self.restart_ssh('start') + self._rep_ssh_service = True + return True + return False + except: + return False + + + def set_sshd_config(self,rep = False): + ''' + @name 设置本地SSH配置文件,以支持pubkey认证 + @author hwliang<2020-08-13> + @param rep 是否恢复ssh配置文件 + @return bool + ''' + self.is_running(rep) + if rep and not self._rep_ssh_config: + return False + + try: + sshd_config_file = '/etc/ssh/sshd_config' + if not os.path.exists(sshd_config_file): + return False + + sshd_config = public.readFile(sshd_config_file) + + if not sshd_config: + return False + + if rep: + if self._sshd_config_backup: + public.writeFile(sshd_config_file,self._sshd_config_backup) + self.restart_ssh() + return True + + pin = r'^\s*PubkeyAuthentication\s+(yes|no)' + pubkey_status = re.findall(pin,sshd_config,re.I) + if pubkey_status: + if pubkey_status[0] == 'yes': + pubkey_status = True + else: + pubkey_status = False + + pin = r'^\s*RSAAuthentication\s+(yes|no)' + rsa_status = re.findall(pin,sshd_config,re.I) + if rsa_status: + if rsa_status[0] == 'yes': + rsa_status = True + else: + rsa_status = False + + self._sshd_config_backup = sshd_config + is_write = False + if not pubkey_status: + sshd_config = re.sub(r'\n#?PubkeyAuthentication\s\w+','\nPubkeyAuthentication yes',sshd_config) + is_write = True + if not rsa_status: + sshd_config = re.sub(r'\n#?RSAAuthentication\s\w+','\nRSAAuthentication yes',sshd_config) + is_write = True + + if is_write: + public.writeFile(sshd_config_file,sshd_config) + self._rep_ssh_config = True + self.restart_ssh() + else: + self._sshd_config_backup = None + + return True + except: + return False + + def restart_ssh(self,act = 'reload'): + ''' + 重启ssh 无参数传递 + ''' + version = public.readFile('/etc/redhat-release') + if not os.path.exists('/etc/redhat-release'): + public.ExecShell('service ssh ' + act) + elif version.find(' 7.') != -1 or version.find(' 8.') != -1: + public.ExecShell("systemctl " + act + " sshd.service") + else: + public.ExecShell("/etc/init.d/sshd " + act) + + def resize(self, data): + ''' + @name 调整终端大小 + @author hwliang<2020-08-07> + @param data 终端尺寸数据 + { + cols: int 列 + rows: int 行 + } + @return bool + ''' + try: + data = json.loads(data) + self._ssh.resize_pty(width=data['cols'], height=data['rows']) + return True + except: + return False + + + def recv(self): + ''' + @name 读取tty缓冲区数据 + @author hwliang<2020-08-07> + @return void + ''' + n = 0 + try: + while self._ws.connected: + resp_line = self._ssh.recv(1024) + if not resp_line: + if not self._tp.is_active(): + self.debug(public.get_msg_gettext('Channel disconnected')) + self._ws.send(public.get_msg_gettext('The connection is disconnected, press enter to try to reconnect!')) + self.close() + return + + if not resp_line: + n+=1 + if n > 5: break + continue + n = 0 + if not self._ws.connected: + return + try: + result = resp_line.decode('utf-8','ignore') + except: + try: + result = resp_line.decode() + except: + result = str(resp_line) + self.record('iodata', result) + self._ws.send(result) + + # self.history_recv(result) + except Exception as e: + e = str(e) + if e.find('closed') != -1: + self.debug(public.getMsg('SSH_LOGIN_INFO')) + elif self._ws.connected: + self.debug(public.get_msg_gettext('Error reading tty buffer data, {}',(str(e),))) + + if not self._ws.connected: + self.debug(public.get_msg_gettext('The client has actively disconnected')) + self.close() + + def send(self): + ''' + @name 写入数据到缓冲区 + @author hwliang<2020-08-07> + @return void + ''' + try: + while self._ws.connected: + if self._s_code: + time.sleep(0.1) + continue + client_data = self._ws.receive() + if not client_data: continue + if client_data == '{}': continue + if len(client_data) > 10: + if client_data.find('{"host":"') != -1: + continue + if client_data.find('"resize":1') != -1: + self.resize(client_data) + continue + self._ssh.send(client_data) + # self.history_send(client_data) + except Exception as ex: + ex = str(ex) + + if ex.find('_io.BufferedReader') != -1: + self.debug(public.get_msg_gettext('An error occurred while reading data from websocket. Retrying')) + self.send() + return + elif ex.find('closed') != -1: + self.debug(public.get_msg_gettext('SSH_LOGIN_INFO')) + else: + self.debug(public.get_msg_gettext('An error occurred while writing data to the buffer: {}',(str(ex),))) + + if not self._ws.connected: + self.debug(public.get_msg_gettext('The client has actively disconnected')) + self.close() + + + def history_recv(self,recv_data): + ''' + @name 从接收实体保存命令 + @author hwliang<2020-08-12> + @param recv_data 数据实体 + @return void + ''' + #处理TAB补登 + if self._last_cmd_tip == 1: + if not recv_data.startswith('\r\n'): + self._last_cmd += recv_data.replace('\u0007','').replace("\x07","").strip() + self._last_cmd_tip = 0 + + #上下切换命令 + if self._last_cmd_tip == 2: + self._last_cmd = recv_data.strip().replace("\x08","").replace("\x07","").replace("\x1b[K","") + self._last_cmd_tip = 0 + + def history_send(self,send_data): + ''' + @name 从发送实体保存命令 + @author hwliang<2020-08-12> + @param send_data 数据实体 + @return void + ''' + if not send_data: return + his_path = self._save_path + self._host + if not os.path.exists(his_path): return + his_file = his_path + '/history.pl' + + #上下切换命令 + if send_data in ["\x1b[A","\x1b[B"]: + self._last_cmd_tip = 2 + return + + #左移光标 + if send_data in ["\x1b[C"]: + self._last_num -= 1 + return + + # 右移光标 + if send_data in ["\x1b[D"]: + self._last_num += 1 + return + + #退格 + if send_data == "\x7f": + self._last_cmd = self._last_cmd[:-1] + return + + #过滤特殊符号 + if send_data in ["\x1b[C","\x1b[D","\x1b[K","\x07","\x08","\x03","\x01","\x02","\x04","\x05","\x06","\x1bOB","\x1bOA","\x1b[8P","\x1b","\x1b[4P","\x1b[6P","\x1b[5P"]: + return + + #Tab补全处理 + if send_data == "\t": + self._last_cmd_tip = 1 + return + + if str(send_data).find("\x1b") != -1: + return + + if send_data[-1] in ['\r','\n']: + if not self._last_cmd: return + his_shell = [int(time.time()),self._client,self._user,self._last_cmd] + public.writeFile(his_file, json.dumps(his_shell) + "\n","a+") + self._last_cmd = "" + + #超过50M则保留最新的20000行 + if os.stat(his_file).st_size > 52428800: + his_tmp = public.GetNumLines(his_file,20000) + public.writeFile(his_file, his_tmp) + else: + if self._last_num >= 0: + self._last_cmd += send_data + + + def close(self): + ''' + @name 释放连接 + @author hwliang<2020-08-07> + @return void + ''' + try: + if self._host_row_id: + public.M('ssh_login_record').where('id=?', self._host_row_id).update( + {'close_time': int(time.time())}) + if self._ssh: + self._ssh.close() + if self._tp: # 关闭宿主服务 + self._tp.close() + if self._ws.connected: + self._ws.close() + except: + pass + + + def set_attr(self,ssh_info): + ''' + @name 设置对象属性,并连接服务器 + @author hwliang<2020-08-07> + @return void + ''' + self._host = ssh_info['host'].strip() + self._port = int(ssh_info['port']) + if 'username' in ssh_info: + self._user = ssh_info['username'] + if 'pkey' in ssh_info: + self._pkey = ssh_info['pkey'] + if 'password' in ssh_info: + self._pass = ssh_info['password'] + if 'pkey_passwd' in ssh_info: + self._key_passwd = ssh_info['pkey_passwd'] + try: + result = self.connect() + except Exception as ex: + if str(ex).find("NoneType") == -1: + raise public.PanelError(ex) + return result + + + def heartbeat(self): + ''' + @name 心跳包 + @author hwliang<2020-09-10> + @return void + ''' + while True: + time.sleep(30) + if self._tp.is_active(): + self._tp.send_ignore() + else: + break + if self._ws.connected: + self._ws.send("") + else: + break + + def debug(self,msg): + ''' + @name 写debug日志 + @author hwliang<2020-09-10> + @return void + ''' + msg = "{} - {}:{} => {} \n".format(public.format_date(),self._host,self._port,msg) + self.history_send(msg) + public.writeFile(self._debug_file,msg,'a+') + + def run(self,web_socket, ssh_info=None): + ''' + @name 启动SSH客户端对象 + @author hwliang<2020-08-07> + @param web_socket websocket句柄对像 + @param ssh_info SSH信息{ + host: 主机地址, + port: 端口 + username: 用户名 + password: 密码 + pkey: 密钥(如果不为空,将使用密钥连接) + } + @return void + ''' + self._ws = web_socket + if not self._ssh: + if not ssh_info: + return + result = self.set_attr(ssh_info) + else: + result = public.get_msg_gettext(True,'ALREADY_CONNECTED') + if result['status']: + sendt = threading.Thread(target=self.send) + recvt = threading.Thread(target=self.recv) + ht = threading.Thread(target=self.heartbeat) + sendt.start() + recvt.start() + ht.start() + sendt.join() + recvt.join() + ht.join() + self.close() + else: + self._ws.send(result['msg']) + self.close() + + def __del__(self): + ''' + 自动释放 + ''' + self.close() + + + +class ssh_host_admin(ssh_terminal): + _panel_path = '/www/server/panel' + _save_path = _panel_path + '/config/ssh_info/' + _pass_file = _panel_path + '/data/a_pass.pl' + _user_command_file = _save_path + '/user_command.json' + _sys_command_file = _save_path + '/sys_command.json' + _pass_str = None + + def __init__(self): + self.__create_aes_pass() + + def __create_aes_pass(self): + ''' + @name 创建AES密码 + @author + @return string + ''' + if not os.path.exists(self._save_path): + os.makedirs(self._save_path,384) + if not os.path.exists(self._pass_file): + public.writeFile(self._pass_file,public.GetRandomString(16)) + public.set_mode(self._pass_file,600) + if not self._pass_str: + self._pass_str = public.readFile(self._pass_file) + if not self._pass_str: + self._pass_str = public.GetRandomString(16) + public.writeFile(self._pass_file,self._pass_str) + public.set_mode(self._pass_file,600) + + def get_host_list(self,args = None): + ''' + @name 获取本机保存的SSH信息列表 + @author hwliang<2020-08-07> + @param args + @return list + ''' + + host_list = [] + for name in os.listdir(self._save_path): + info_file = self._save_path + name +'/info.json' + if not os.path.exists(info_file): continue + try: + info_tmp = self.get_ssh_info(name) + host_info = {} + host_info['host'] = name + host_info['port'] = info_tmp['port'] + host_info['ps'] = info_tmp['ps'] + host_info['sort'] = int(info_tmp['sort']) + except: + if os.path.exists(info_file): + os.remove(info_file) + continue + + host_list.append(host_info) + + host_list = sorted(host_list,key=lambda x: x['sort'],reverse=False) + return host_list + + def get_host_find(self,args): + ''' + @name 获取指定SSH信息 + @author hwliang<2020-08-07> + @param args{ + host: 主机地址 + } + @return dict + ''' + args.host = args.host.strip() + info_file = self._save_path + args.host +'/info.json' + if not os.path.exists(info_file): + return public.return_msg_gettext(False,'The specified SSH information does not exist!') + info_tmp = self.get_ssh_info(args.host) + host_info = {} + host_info['host'] = args.host + host_info['port'] = info_tmp['port'] + host_info['ps'] = info_tmp['ps'] + host_info['sort'] = info_tmp['sort'] + host_info['username'] = info_tmp['username'] + host_info['password'] = info_tmp['password'] + host_info['pkey'] = info_tmp['pkey'] + host_info['pkey_passwd'] = '' + if 'pkey_passwd' in info_tmp: + host_info['pkey_passwd'] = info_tmp['pkey_passwd'] + return host_info + + def modify_host(self,args): + ''' + @name 修改SSH信息 + @author hwliang<2020-08-07> + @param args{ + host: 被修改的主机地址, + new_host: 新的主机地址, + port: 端口 + ps: 备注 + sort: 排序(可选) + username: 用户名 + password: 密码 + pkey: 密钥(如果不为空,将使用密钥连接) + pkey_passwd: 密钥的密码 + } + @return dict + ''' + args.new_host = args.new_host.strip() + args.host = args.host.strip() + if args.host != args.new_host: + info_file = self._save_path + args.new_host +'/info.json' + if os.path.exists(info_file): + return public.return_msg_gettext(False,'The specified host address has been added to other SSH information!') + + info_file = self._save_path + args.host +'/info.json' + + if not os.path.exists(info_file): + return public.return_msg_gettext(False,'The specified SSH information does not exist!') + + if not 'sort' in args: + r_data = public.aes_decrypt(public.readFile(info_file),self._pass_str) + info_tmp = json.loads(r_data) + args.sort = info_tmp['sort'] + + host_info = {} + host_info['host'] = args.new_host + host_info['port'] = int(args['port']) + host_info['ps'] = args['ps'] + host_info['sort'] = args['sort'] + host_info['username'] = args['username'] + host_info['password'] = args['password'] + host_info['pkey'] = args['pkey'] + if 'pkey_passwd' in args: + host_info['pkey_passwd'] = args['pkey_passwd'] + else: + host_info['pkey_passwd'] = '' + if not host_info['pkey']: host_info['pkey'] = '' + result = self.set_attr(host_info) + if not result['status']: return result + self.save_ssh_info(args.host,host_info) + if args.host != args.new_host: + public.ExecShell('mv {} {}'.format(self._save_path + args.host,self._save_path + args.new_host)) + public.write_log_gettext(self._log_type,'Modify the SSH information of HOST: {}',(args.host,)) + return public.return_msg_gettext(True,'Setup successfully!') + + def create_host(self,args): + ''' + @name 添加SSH信息 + @author hwliang<2020-08-07> + @param args{ + host: 主机地址, + port: 端口 + ps: 备注 + sort: 排序(可选,默认0) + username: 用户名 + password: 密码 + pkey: 密钥(如果不为空,将使用密钥连接) + pkey_passwd: 密钥的密码 + } + @return dict + ''' + args.host = args.host.strip() + host_path = self._save_path + args.host + info_file = host_path +'/info.json' + if os.path.exists(info_file): + args.new_host = args.host + return self.modify_host(args) + #return public.returnMsg(False,'指定SSH信息已经添加过了!') + if not os.path.exists(host_path): + os.makedirs(host_path,384) + if not 'sort' in args: args.sort = 0 + if not 'ps' in args: args.ps = args.host + host_info = {} + host_info['host'] = args.host + host_info['port'] = int(args['port']) + host_info['ps'] = args['ps'] + host_info['sort'] = int(args['sort']) + host_info['username'] = args['username'] + host_info['password'] = args['password'] + host_info['pkey'] = args['pkey'] + host_info['pkey_passwd'] = '' + if 'pkey_passwd' in args: + host_info['pkey_passwd'] = args['pkey_passwd'] + result = self.set_attr(host_info) + if not result['status']: return result + self.save_ssh_info(args.host,host_info) + public.write_log_gettext(self._log_type,'Add the SSH information of HOST: {}',(str(args.host),)) + return public.return_msg_gettext(True,'Setup successfully!') + + + def remove_host(self,args): + ''' + @name 删除指定SSH信息 + @author hwliang<2020-08-07> + @param args{ + host: 主机地址 + } + @return dict + ''' + args.host = args.host.strip() + if not args.host: return public.return_msg_gettext(False,'Parameter ERROR!') + host_path = self._save_path + args.host + if not os.path.exists(host_path): + return public.return_msg_gettext(False,'The specified SSH information does not exist!') + public.ExecShell("rm -rf {}".format(host_path)) + public.write_log_gettext(self._log_type,'Delete the SSH information of HOST: {}',(str(args.host),)) + return public.return_msg_gettext(True,'Setup successfully!') + + + def get_ssh_info(self,host): + ''' + @name 获取并解密指定SSH信息 + @author hwliang<2020-08-07> + @param host 主机地址 + @return dict or False + ''' + info_file = self._save_path + host + '/info.json' + if not os.path.exists(info_file): return False + try: + r_data = public.aes_decrypt(public.readFile(info_file),self._pass_str) + except ValueError as ex: + if str(ex).find('Incorrect AES key length') != -1: + if os.path.exists(self._pass_file): + os.remove(self._pass_file) + self.__create_aes_pass() + r_data = public.aes_decrypt(public.readFile(info_file),self._pass_str) + + return json.loads(r_data) + + def save_ssh_info(self,host,host_info): + ''' + @name 获取并解密指定SSH信息 + @author hwliang<2020-08-07> + @param host 主机地址 + @param host_info ssh信息字典 + @return bool + ''' + host_path = self._save_path + host + if not os.path.exists(host_path): + os.makedirs(host_path,384) + info_file = host_path +'/info.json' + r_data = public.aes_encrypt(json.dumps(host_info),self._pass_str) + public.writeFile(info_file,r_data) + return True + + def set_sort(self,args): + ''' + @name 获取并解密指定SSH信息 + @author hwliang<2020-08-07> + @param args{ + sort_list{ + 主机host : 排序编号, + 主机host : 排序编号, + ... + } + } + @return bool + ''' + if not 'sort_list' in args: + return public.return_msg_gettext(False,'Please pass in the [sort_list] field') + sort_list = json.loads(args.sort_list) + for name in sort_list.keys(): + info_file = self._save_path + name + '/info.json' + if not os.path.exists(info_file): continue + + ssh_info = self.get_ssh_info(name) + ssh_info['sort'] = int(sort_list[name]) + self.save_ssh_info(name,ssh_info) + return public.return_msg_gettext(True,'Setup successfully!') + + def get_command_list(self,args = None, user_cmd = False , sys_cmd = False): + ''' + @name 获取常用命令列表 + @author hwliang<2020-08-08> + @param args + @param user_cmd 是否不获取用户配置 + @param sys_cmd 是否不获取系统配置 + @return list + ''' + + sys_command = [] + if not sys_cmd: + if os.path.exists(self._sys_command_file): + sys_command = json.loads(public.readFile(self._sys_command_file)) + + user_command = [] + if not user_cmd: + if os.path.exists(self._user_command_file): + user_command = json.loads(public.readFile(self._user_command_file)) + + command = sys_command + user_command + return command + + + def command_exists(self,command,title): + ''' + @name 判断命令是否存在 + @author hwliang<2020-08-08> + @param command 常用命令列表 + @param title 命令标题 + @return bool + ''' + for cmd in command: + if cmd['title'] == title: return True + return False + + def save_command(self,command,sys_cmd=False): + ''' + @name 保存常用命令 + @author hwliang<2020-08-08> + @param command 常用命令列表 + @param sys_cmd 是否为系统配置 + @return void + ''' + s_file = self._user_command_file + if sys_cmd: + s_file = self._sys_command_file + public.writeFile(s_file,json.dumps(command)) + + def create_command(self,args): + ''' + @name 创建常用命令 + @author hwliang<2020-08-08> + @param args{ + title 标题 + shell 命令文本 + } + @return dict + ''' + args.title = args.title.strip() + command = self.get_command_list(sys_cmd=True) + + if self.command_exists(command,args.title): + return public.return_msg_gettext(False,'The specified command name already exists') + + cmd = { + "title": args.title, + "shell": args.shell.strip() + } + + command.append(cmd) + self.save_command(command) + public.write_log_gettext(self._log_type,'Add common commands [{}]',(str(args.title),)) + return public.return_msg_gettext(True,'Setup successfully!') + + def get_command_find(self,args = None, title=None): + ''' + @name 获取指定命令信息 + @author hwliang<2020-08-08> + @param args{ + title 标题 + } 可选 + @param title 标题 可选 + @return dict + ''' + if args: title = args.title.strip() + command = self.get_command_list() + for cmd in command: + if cmd['title'] == title or cmd['title'] == args.title: + return cmd + return public.return_msg_gettext(False,'The specified command does not exist') + + def modify_command(self,args): + ''' + @name 修改常用命令 + @author hwliang<2020-08-08> + @param args{ + title 标题 + new_title 新标题 + shell 命令文本 + } + @return dict + ''' + args.title = args.title.strip() + command = self.get_command_list(sys_cmd=True) + if not self.command_exists(command,args.title): + return public.return_msg_gettext(False,'The specified command does not exist') + for i in range(len(command)): + if command[i]['title'] == args.title or command[i]['title'] == title: + command[i]['title'] = args.new_title.strip() + command[i]['shell'] = args.shell.strip() + break + self.save_command(command) + public.write_log_gettext(self._log_type,'Modify common commands [{}]',(str(args.title),)) + return public.return_msg_gettext(True,'Setup successfully!') + + def remove_command(self,args): + ''' + @name 删除指定命令 + @author hwliang<2020-08-08> + @param args{ + title 标题 + } + @return dict + ''' + args.title = args.title.strip() + command = self.get_command_list(sys_cmd=True) + if not self.command_exists(command,args.title): + return public.return_msg_gettext(False,'The specified command does not exist') + for i in range(len(command)): + if command[i]['title'] == args.title: + del(command[i]) + break + + self.save_command(command) + public.write_log_gettext(self._log_type,'Delete common commands [{}]',(str(args.title),)) + return public.return_msg_gettext(True,'Setup successfully!') diff --git a/class_v2/system_v2.py b/class_v2/system_v2.py new file mode 100644 index 00000000..7184cb1c --- /dev/null +++ b/class_v2/system_v2.py @@ -0,0 +1,1036 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aapanel x3 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import psutil,time,os,public,re,sys +from public.validate import Param +try: + from BTPanel import session,cache +except: + pass +class system: + setupPath = None + ssh = None + shell = None + + def __init__(self): + self.setupPath = public.GetConfigValue('setup_path') + + def GetConcifInfo(self,get=None): + #取环境配置信息 + if 'config' not in session: + session['config'] = public.M('config').where("id=?",('1',)).field('webserver,sites_path,backup_path,status,mysql_root').find() + if 'email' not in session['config']: + session['config']['email'] = public.M('users').where("id=?",('1',)).getField('email') + data = {} + data = session['config'] + data['webserver'] = public.get_webserver() + #PHP版本 + phpVersions = public.get_php_versions() + + data['php'] = [] + + for version in phpVersions: + tmp = {} + tmp['setup'] = os.path.exists(self.setupPath + '/php/'+version+'/bin/php') + if tmp['setup']: + phpConfig = self.GetPHPConfig(version) + tmp['version'] = version + tmp['max'] = phpConfig['max'] + tmp['maxTime'] = phpConfig['maxTime'] + tmp['pathinfo'] = phpConfig['pathinfo'] + tmp['status'] = os.path.exists('/tmp/php-cgi-' + version + '.sock') + data['php'].append(tmp) + + tmp = {} + data['webserver'] = '' + serviceName = 'nginx' + tmp['setup'] = False + phpversion = "54" + phpport = '888' + pstatus = False + pauth = False + if os.path.exists(self.setupPath+'/nginx'): + data['webserver'] = 'nginx' + serviceName = 'nginx' + tmp['setup'] = os.path.exists(self.setupPath +'/nginx/sbin/nginx') + configFile = self.setupPath + '/nginx/conf/nginx.conf' + try: + if os.path.exists(configFile): + conf = public.readFile(configFile) + rep = r"listen\s+([0-9]+)\s*;" + rtmp = re.search(rep,conf) + if rtmp: + phpport = rtmp.groups()[0] + + if conf.find('AUTH_START') != -1: pauth = True + if conf.find(self.setupPath + '/stop') == -1: pstatus = True + configFile = self.setupPath + '/nginx/conf/enable-php.conf' + conf = public.readFile(configFile) + rep = r"php-cgi-([0-9]+)\.sock" + rtmp = re.search(rep,conf) + if rtmp: + phpversion = rtmp.groups()[0] + except: + pass + + elif os.path.exists(self.setupPath+'/apache'): + data['webserver'] = 'apache' + serviceName = 'httpd' + tmp['setup'] = os.path.exists(self.setupPath +'/apache/bin/httpd') + configFile = self.setupPath + '/apache/conf/extra/httpd-vhosts.conf' + try: + if os.path.exists(configFile): + conf = public.readFile(configFile) + rep = r"php-cgi-([0-9]+)\.sock" + rtmp = re.search(rep,conf) + if rtmp: + phpversion = rtmp.groups()[0] + rep = "Listen\\s+([0-9]+)\\s*\n" + rtmp = re.search(rep,conf) + if rtmp: + phpport = rtmp.groups()[0] + if conf.find('AUTH_START') != -1: pauth = True + if conf.find(self.setupPath + '/stop') == -1: pstatus = True + except: + pass + elif os.path.exists('/usr/local/lsws/bin/lswsctrl'): + data['webserver'] = 'openlitespeed' + serviceName = 'openlitespeed' + tmp['setup'] = os.path.exists('/usr/local/lsws/bin/lswsctrl') + configFile = '/usr/local/lsws/bin/lswsctrl' + try: + if os.path.exists(configFile): + conf = public.readFile('/www/server/panel/vhost/openlitespeed/detail/phpmyadmin.conf') + rep = r"/usr/local/lsws/lsphp(\d+)/bin/lsphp" + rtmp = re.search(rep,conf) + if rtmp: + phpversion = rtmp.groups()[0] + conf = public.readFile('/www/server/panel/vhost/openlitespeed/listen/888.conf') + rep = r"address\s+\*\:(\d+)" + rtmp = re.search(rep,conf) + if rtmp: + phpport = rtmp.groups()[0] + if conf.find('AUTH_START') != -1: pauth = True + if conf.find(self.setupPath + '/stop') == -1: pstatus = True + except: + pass + + + tmp['type'] = data['webserver'] + tmp['version'] = public.xss_version(public.readFile(self.setupPath + '/'+data['webserver']+'/version.pl')) + tmp['status'] = False + result = public.ExecShell('/etc/init.d/' + serviceName + ' status') + if result[0].find('running') != -1: tmp['status'] = True + data['web'] = tmp + + tmp = {} + vfile = self.setupPath + '/phpmyadmin/version.pl' + tmp['version'] = public.xss_version(public.readFile(vfile)) + if tmp['version']: tmp['version'] = tmp['version'].strip() + tmp['setup'] = os.path.exists(vfile) + tmp['status'] = pstatus + tmp['phpversion'] = phpversion.strip() + tmp['port'] = phpport + tmp['auth'] = pauth + data['phpmyadmin'] = tmp + + tmp = {} + tmp['setup'] = os.path.exists('/etc/init.d/tomcat') + tmp['status'] = tmp['setup'] + #if public.ExecShell('ps -aux|grep tomcat|grep -v grep')[0] == "": tmp['status'] = False + tmp['version'] = public.xss_version(public.readFile(self.setupPath + '/tomcat/version.pl')) + data['tomcat'] = tmp + + tmp = {} + tmp['setup'] = os.path.exists(self.setupPath +'/mysql/bin/mysql') + tmp['version'] = public.xss_version(public.readFile(self.setupPath + '/mysql/version.pl')) + tmp['status'] = os.path.exists('/tmp/mysql.sock') + data['mysql'] = tmp + + tmp = {} + tmp['setup'] = os.path.exists(self.setupPath +'/redis/runtest') + tmp['status'] = os.path.exists('/var/run/redis_6379.pid') + data['redis'] = tmp + + tmp = {} + tmp['setup'] = os.path.exists('/usr/local/memcached/bin/memcached') + tmp['status'] = os.path.exists('/var/run/memcached.pid') + data['memcached'] = tmp + + tmp = {} + tmp['setup'] = os.path.exists(self.setupPath +'/pure-ftpd/bin/pure-pw') + tmp['version'] = public.xss_version(public.readFile(self.setupPath + '/pure-ftpd/version.pl')) + tmp['status'] = os.path.exists('/var/run/pure-ftpd.pid') + data['pure-ftpd'] = tmp + data['panel'] = self.GetPanelInfo() + data['systemdate'] = public.ExecShell('date +"%Y-%m-%d %H:%M:%S %Z %z"')[0].strip(); + data['show_workorder'] = not os.path.exists('data/not_workorder.pl') + return data + + def GetPanelInfo(self,get=None): + #取面板配置 + address = public.GetLocalIp() + try: + port = public.GetHost(True) + except: + port = '7800'; + domain = '' + if os.path.exists('data/domain.conf'): + domain = public.readFile('data/domain.conf'); + + autoUpdate = '' + if os.path.exists('data/autoUpdate.pl'): autoUpdate = 'checked'; + limitip = '' + if os.path.exists('data/limitip.conf'): limitip = public.readFile('data/limitip.conf'); + admin_path = '/' + if os.path.exists('data/admin_path.pl'): admin_path = public.readFile('data/admin_path.pl').strip() + + templates = [] + #for template in os.listdir('BTPanel/templates/'): + # if os.path.isdir('templates/' + template): templates.append(template); + template = public.GetConfigValue('template') + + check502 = ''; + if os.path.exists('data/502Task.pl'): check502 = 'checked'; + return {'port':port,'address':address,'domain':domain,'auto':autoUpdate,'502':check502,'limitip':limitip,'templates':templates,'template':template,'admin_path':admin_path} + + def GetPHPConfig(self,version): + #取PHP配置 + file = self.setupPath + "/php/"+version+"/etc/php.ini" + phpini = public.readFile(file) + file = self.setupPath + "/php/"+version+"/etc/php-fpm.conf" + phpfpm = public.readFile(file) + data = {} + try: + rep = r"upload_max_filesize\s*=\s*([0-9]+)M" + tmp = re.search(rep,phpini).groups() + data['max'] = tmp[0] + except: + data['max'] = '50' + try: + rep = "request_terminate_timeout\\s*=\\s*([0-9]+)\n" + tmp = re.search(rep,phpfpm).groups() + data['maxTime'] = tmp[0] + except: + data['maxTime'] = 0 + + try: + rep = r"\n;*\s*cgi\.fix_pathinfo\s*=\s*([0-9]+)\s*\n" + tmp = re.search(rep,phpini).groups() + + if tmp[0] == '1': + data['pathinfo'] = True + else: + data['pathinfo'] = False + except: + data['pathinfo'] = False + + return data + + + def GetSystemTotal(self,get,interval = 1): + #取系统统计信息 + data = self.GetMemInfo()['message'] + cpu = self.GetCpuInfo(interval) + data['cpuNum'] = cpu[1] + data['cpuRealUsed'] = cpu[0] + data['time'] = self.GetBootTime() + data['system'] = self.GetSystemVersion() + data['isuser'] = public.M('users').where('username=?',('admin',)).count() + try: + data['isport'] = public.GetHost(True) == '8888' + except:data['isport'] = False + + data['version'] = session['version'] + return public.return_message(0,0,data) + + def GetLoadAverage(self,get): + try: + c = os.getloadavg() + except: + c = [0,0,0] + data = {} + data['one'] = float(c[0]) + data['five'] = float(c[1]) + data['fifteen'] = float(c[2]) + data['max'] = psutil.cpu_count() * 2 + data['limit'] = data['max'] + data['safe'] = data['max'] * 0.75 + return data + + def GetAllInfo(self,get): + data = {} + data['load_average'] = self.GetLoadAverage(get) + data['title'] = self.GetTitle() + data['network'] = self.GetNetWorkApi(get) + data['cpu'] = self.GetCpuInfo(1) + data['time'] = self.GetBootTime() + data['system'] = self.GetSystemVersion() + data['mem'] = self.GetMemInfo()['message'] + data['version'] = session['version'] + return data + + def GetTitle(self): + return public.xss_version(public.GetConfigValue('title')) + + def GetSystemVersion(self): + #取操作系统版本 + key = 'sys_version' + version = cache.get(key) + if version: return version + version = public.get_os_version() + cache.set(key,version,600) + return version + + def GetBootTime(self): + #取系统启动时间 + key = 'sys_time' + sys_time = cache.get(key) + if sys_time: return sys_time + import public,math + conf = public.readFile('/proc/uptime').split() + tStr = float(conf[0]) + min = tStr / 60 + hours = min / 60 + days = math.floor(hours / 24) + hours = math.floor(hours - (days * 24)) + min = math.floor(min - (days * 60 * 24) - (hours * 60)) + sys_time = "{} Day(s)".format(int(days)) + cache.set(key,sys_time,1800) + return sys_time + #return public.getMsg('SYS_BOOT_TIME',(str(int(days)),str(int(hours)),str(int(min)))) + + def GetCpuInfo(self,interval = 1): + #取CPU信息 + cpuCount = psutil.cpu_count() + cpuNum = psutil.cpu_count(logical=False) + c_tmp = public.readFile('/proc/cpuinfo') + d_tmp = re.findall("physical id.+",c_tmp) + cpuW = len(set(d_tmp)) + import threading + p = threading.Thread(target=self.get_cpu_percent_thead,args=(interval,)) + # p.setDaemon(True) + p.start() + + used = cache.get('cpu_used_all') + if not used: used = self.get_cpu_percent_thead(interval) + + used_all = psutil.cpu_percent(percpu=True) + cpu_name = public.getCpuType() + " * {}".format(cpuW) + + return used,cpuCount,used_all,cpu_name,cpuNum,cpuW + + def get_cpu_percent_thead(self,interval): + used = psutil.cpu_percent(interval) + cache.set('cpu_used_all',used,10) + return used + + + def get_cpu_percent(self): + percent = 0.00 + old_cpu_time = cache.get('old_cpu_time') + old_process_time = cache.get('old_process_time') + if not old_cpu_time: + old_cpu_time = self.get_cpu_time() + old_process_time = self.get_process_cpu_time() + time.sleep(1) + new_cpu_time = self.get_cpu_time() + new_process_time = self.get_process_cpu_time() + try: + percent = round(100.00 * ((new_process_time - old_process_time) / (new_cpu_time - old_cpu_time)),2) + except: percent = 0.00 + cache.set('old_cpu_time',new_cpu_time) + cache.set('old_process_time',new_process_time) + if percent > 100: percent = 100 + if percent > 0: return percent + return 0.00 + + def get_process_cpu_time(self): + pids = psutil.pids() + cpu_time = 0.00 + for pid in pids: + try: + cpu_times = psutil.Process(pid).cpu_times() + for s in cpu_times: cpu_time += s + except:continue + return cpu_time + + def get_cpu_time(self): + cpu_time = 0.00 + cpu_times = psutil.cpu_times() + for s in cpu_times: cpu_time += s + return cpu_time + + def GetMemInfo(self,get=None): + #取内存信息 + skey = 'memInfo' + memInfo = cache.get(skey) + if memInfo: return public.return_message(0,0,memInfo) + mem = psutil.virtual_memory() + memInfo = {'memTotal':int(mem.total/1024/1024),'memFree':int(mem.free/1024/1024),'memBuffers':int(mem.buffers/1024/1024),'memCached':int(mem.cached/1024/1024)} + memInfo['memRealUsed'] = memInfo['memTotal'] - memInfo['memFree'] - memInfo['memBuffers'] - memInfo['memCached'] + cache.set(skey,memInfo,60) + return public.return_message(0,0,memInfo) + + def GetDiskInfo(self,get=None): + return self.GetDiskInfo2() + #取磁盘分区信息 + diskIo = psutil.disk_partitions() + diskInfo = [] + cuts = ['/mnt/cdrom','/boot','/boot/efi','/dev','/dev/shm','/run/lock','/run','/run/shm','/run/user'] + for disk in diskIo: + if not cuts: continue + tmp = {} + tmp['path'] = disk[1] + tmp['size'] = psutil.disk_usage(disk[1]) + diskInfo.append(tmp) + return diskInfo + + def GetDiskInfo2(self, human=True): + + #取磁盘分区信息 + key = f'sys_disk_{human}' + diskInfo = cache.get(key) + if diskInfo: return diskInfo + if human: + temp = public.ExecShell("df -hT -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")[0] + else: + temp = public.ExecShell("df -T -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")[0] + tempInodes = public.ExecShell("df -i -P|grep '/'|grep -v tmpfs|grep -v 'snap/core'|grep -v udev")[0] + temp1 = temp.split('\n') + tempInodes1 = tempInodes.split('\n') + diskInfo = [] + n = 0 + cuts = ['/mnt/cdrom','/boot','/boot/efi','/dev','/dev/shm','/run/lock','/run','/run/shm','/run/user'] + for tmp in temp1: + n += 1 + try: + inodes = tempInodes1[n-1].split() + disk = re.findall(r"^(.+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\w\.]+)\s+([\d%]{2,4})\s+(/.{0,100})$",tmp.strip().replace(',','.')) + if disk: disk = disk[0] + if len(disk) < 6: continue + # if disk[2].find('M') != -1: continue + if disk[2].find('K') != -1: continue + if len(disk[6].split('/')) > 10: continue + if disk[6] in cuts: continue + if str(disk[6]).startswith("/snap"): continue + if disk[6].find('docker') != -1: continue + if disk[1].strip() in ['tmpfs']: continue + arr = {} + arr['filesystem'] = disk[0].strip() + arr['type'] = disk[1].strip() + arr['path'] = disk[6].replace('/usr/local/lighthouse/softwares/btpanel','/www') + tmp1 = [disk[2],disk[3],disk[4],disk[5]] + arr['size'] = tmp1 + arr['inodes'] = [inodes[1],inodes[2],inodes[3],inodes[4]] + diskInfo.append(arr) + except Exception as ex: + public.write_log_gettext('Get Info',str(ex)) + continue + cache.set(key,diskInfo,10) + return diskInfo + + + # 获取磁盘IO开销数据 + def get_disk_iostat(self): + iokey = 'iostat' + diskio = cache.get(iokey) + mtime = int(time.time()) + if not diskio: + diskio = {} + diskio['info'] = None + diskio['time'] = mtime + diskio_1 = diskio['info'] + stime = mtime - diskio['time'] + if not stime: stime = 1 + diskInfo = {} + diskInfo['ALL'] = {} + diskInfo['ALL']['read_count'] = 0 + diskInfo['ALL']['write_count'] = 0 + diskInfo['ALL']['read_bytes'] = 0 + diskInfo['ALL']['write_bytes'] = 0 + diskInfo['ALL']['read_time'] = 0 + diskInfo['ALL']['write_time'] = 0 + diskInfo['ALL']['read_merged_count'] = 0 + diskInfo['ALL']['write_merged_count'] = 0 + try: + if os.path.exists('/proc/diskstats'): + diskio_2 = psutil.disk_io_counters(perdisk=True) + if not diskio_1: + diskio_1 = diskio_2 + for disk_name in diskio_2.keys(): + diskInfo[disk_name] = {} + diskInfo[disk_name]['read_count'] = int((diskio_2[disk_name].read_count - diskio_1[disk_name].read_count) / stime) + diskInfo[disk_name]['write_count'] = int((diskio_2[disk_name].write_count - diskio_1[disk_name].write_count) / stime) + diskInfo[disk_name]['read_bytes'] = int((diskio_2[disk_name].read_bytes - diskio_1[disk_name].read_bytes) / stime) + diskInfo[disk_name]['write_bytes'] = int((diskio_2[disk_name].write_bytes - diskio_1[disk_name].write_bytes) / stime) + diskInfo[disk_name]['read_time'] = int((diskio_2[disk_name].read_time - diskio_1[disk_name].read_time) / stime) + diskInfo[disk_name]['write_time'] = int((diskio_2[disk_name].write_time - diskio_1[disk_name].write_time) / stime) + diskInfo[disk_name]['read_merged_count'] = int((diskio_2[disk_name].read_merged_count - diskio_1[disk_name].read_merged_count) / stime) + diskInfo[disk_name]['write_merged_count'] = int((diskio_2[disk_name].write_merged_count - diskio_1[disk_name].write_merged_count) / stime) + + diskInfo['ALL']['read_count'] += diskInfo[disk_name]['read_count'] + diskInfo['ALL']['write_count'] += diskInfo[disk_name]['write_count'] + diskInfo['ALL']['read_bytes'] += diskInfo[disk_name]['read_bytes'] + diskInfo['ALL']['write_bytes'] += diskInfo[disk_name]['write_bytes'] + if diskInfo['ALL']['read_time'] < diskInfo[disk_name]['read_time']: + diskInfo['ALL']['read_time'] = diskInfo[disk_name]['read_time'] + if diskInfo['ALL']['write_time'] < diskInfo[disk_name]['write_time']: + diskInfo['ALL']['write_time'] = diskInfo[disk_name]['write_time'] + diskInfo['ALL']['read_merged_count'] += diskInfo[disk_name]['read_merged_count'] + diskInfo['ALL']['write_merged_count'] += diskInfo[disk_name]['write_merged_count'] + + cache.set(iokey,{'info':diskio_2,'time':mtime}) + except: + return diskInfo + return diskInfo + + + #清理系统垃圾 + def ClearSystem(self,get): + count = total = 0 + tmp_total,tmp_count = self.ClearMail() + count += tmp_count + total += tmp_total + tmp_total,tmp_count = self.ClearOther() + count += tmp_count + total += tmp_total + return count,total + + #清理邮件日志 + def ClearMail(self): + rpath = '/var/spool' + total = count = 0 + import shutil + con = ['cron','anacron','mail'] + for d in os.listdir(rpath): + if d in con: continue + dpath = rpath + '/' + d + time.sleep(0.2) + num = size = 0 + for n in os.listdir(dpath): + filename = dpath + '/' + n + fsize = os.path.getsize(filename) + size += fsize + if os.path.isdir(filename): + shutil.rmtree(filename) + else: + os.remove(filename) + print('\t\033[1;32m[OK]\033[0m') + num += 1 + total += size + count += num + return total,count + + #清理其它 + def ClearOther(self): + clearPath = [ + {'path':'/www/server/panel','find':'testDisk_'}, + {'path':'/www/wwwlogs','find':'log'}, + {'path':'/tmp','find':'panelBoot.pl'}, + {'path':'/www/server/panel/install','find':'.rpm'} + ] + + total = count = 0 + for c in clearPath: + for d in os.listdir(c['path']): + if d.find(c['find']) == -1: continue + filename = c['path'] + '/' + d + if os.path.isdir(filename): continue + fsize = os.path.getsize(filename) + total += fsize + os.remove(filename) + count += 1 + public.serviceReload() + filename = '/www/server/nginx/off' + if os.path.exists(filename): os.remove(filename) + public.ExecShell('echo > /tmp/panelBoot.pl') + return total,count + + def GetNetWork(self,get=None): + cache_timeout = 86400 + otime = cache.get("otime") + ntime = time.time() + networkInfo = {} + networkInfo['network'] = {} + networkInfo['upTotal'] = 0 + networkInfo['downTotal'] = 0 + networkInfo['up'] = 0 + networkInfo['down'] = 0 + networkInfo['downPackets'] = 0 + networkInfo['upPackets'] = 0 + networkIo_list = psutil.net_io_counters(pernic = True) + for net_key in networkIo_list.keys(): + networkIo = networkIo_list[net_key][:4] + up_key = "{}_up".format(net_key) + down_key = "{}_down".format(net_key) + otime_key = "otime" + + if not otime: + otime = time.time() + + cache.set(up_key,networkIo[0],cache_timeout) + cache.set(down_key,networkIo[1],cache_timeout) + cache.set(otime_key,otime ,cache_timeout) + + networkInfo['network'][net_key] = {} + up = cache.get(up_key) + down = cache.get(down_key) + if not up: + up = networkIo[0] + if not down: + down = networkIo[1] + networkInfo['network'][net_key]['upTotal'] = networkIo[0] + networkInfo['network'][net_key]['downTotal'] = networkIo[1] + networkInfo['network'][net_key]['up'] = round(float(networkIo[0] - up) / 1024 / (ntime - otime),2) + networkInfo['network'][net_key]['down'] = round(float(networkIo[1] - down) / 1024 / (ntime - otime),2) + networkInfo['network'][net_key]['downPackets'] =networkIo[3] + networkInfo['network'][net_key]['upPackets'] =networkIo[2] + + networkInfo['upTotal'] += networkInfo['network'][net_key]['upTotal'] + networkInfo['downTotal'] += networkInfo['network'][net_key]['downTotal'] + networkInfo['up'] += networkInfo['network'][net_key]['up'] + networkInfo['down'] += networkInfo['network'][net_key]['down'] + networkInfo['downPackets'] += networkInfo['network'][net_key]['downPackets'] + networkInfo['upPackets'] += networkInfo['network'][net_key]['upPackets'] + + cache.set(up_key,networkIo[0],cache_timeout) + cache.set(down_key,networkIo[1],cache_timeout) + cache.set(otime_key, time.time(),cache_timeout) + + if get != False: + networkInfo['cpu'] = self.GetCpuInfo(1) + networkInfo['cpu_times'] = self.get_cpu_times() + networkInfo['load'] = self.GetLoadAverage(get) + networkInfo['mem'] = self.GetMemInfo(get)['message'] + networkInfo['version'] = session['version'] + disk_list = [] + for disk in self.GetDiskInfo2(False): + disk['size'].append(int(disk['size'][0]) - (int(disk['size'][1]) + int(disk['size'][2]))) # 计算系统占用 + disk['size'] = list(map(lambda num: f"{round(int(num) / 1048576, 2)}G" if str(num).isdigit() and str(num).find('G') == -1 else num, disk['size'])) + disk['inodes'] = list(map(lambda num: f"{round(int(num) / 1048576, 2)}G" if str(num).isdigit() and str(num).find('G') == -1 else num, disk['inodes'])) + disk_list.append(disk) + networkInfo['disk'] = disk_list + + networkInfo['title'] = self.GetTitle() + networkInfo['time'] = self.GetBootTime() + networkInfo['site_total'] = public.M('sites').count() + networkInfo['ftp_total'] = public.M('ftps').count() + networkInfo['database_total'] = public.M('databases').count() + networkInfo['system'] = self.GetSystemVersion() + networkInfo['installed'] = self.CheckInstalled() + import panel_ssl_v2 as panelSSL + user_info = panelSSL.panelSSL().GetUserInfo(None) + networkInfo['user_info'] = user_info['message'] + networkInfo['user_info']['status'] = user_info['status'] + networkInfo['up'] = round(float(networkInfo['up']),2) + networkInfo['down'] = round(float(networkInfo['down']),2) + networkInfo['iostat'] = self.get_disk_iostat() + + return public.return_message(0,0,networkInfo) + + + def get_cpu_times(self): + skey = 'cpu_times' + data = cache.get(skey) + if data:return data + try: + data = {} + cpu_times_p = psutil.cpu_times_percent() + data['user'] = cpu_times_p.user + data['nice'] = cpu_times_p.nice + data['system'] = cpu_times_p.system + data['idle'] = cpu_times_p.idle + data['iowait'] = cpu_times_p.iowait + data['irq'] = cpu_times_p.irq + data['softirq'] = cpu_times_p.softirq + data['steal'] = cpu_times_p.steal + data['guest'] = cpu_times_p.guest + data['guest_nice'] = cpu_times_p.guest_nice + data['total_processes'] = 0 + data['active_processes'] = 0 + for pid in psutil.pids(): + try: + p = psutil.Process(pid) + if p.status() == 'running': + data['active_processes'] += 1 + except: + continue + data['total_processes'] += 1 + + cache.set(skey,data,60) + except: return None + return data + + + + + def GetNetWorkApi(self,get=None): + return self.GetNetWork() + + #检查是否安装任何 + def CheckInstalled(self): + checks = ['nginx','apache','php','pure-ftpd','mysql'] + import os + for name in checks: + filename = public.GetConfigValue('root_path') + "/server/" + name + if os.path.exists(filename): return True + return False + + def GetNetWorkOld(self): + #取网络流量信息 + import time; + pnet = public.readFile('/proc/net/dev') + rep = r'([^\s]+):[\s]{0,}(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)' + pnetall = re.findall(rep,pnet) + networkInfo = {} + networkInfo['upTotal'] = networkInfo['downTotal'] = networkInfo['up'] = networkInfo['down'] = networkInfo['downPackets'] = networkInfo['upPackets'] = 0 + + + for pnetInfo in pnetall: + if pnetInfo[0] == 'io': continue + networkInfo['downTotal'] += int(pnetInfo[1]) + networkInfo['downPackets'] += int(pnetInfo[2]) + networkInfo['upTotal'] += int(pnetInfo[9]) + networkInfo['upPackets'] += int(pnetInfo[10]) + + cache_timeout = 86400 + otime = cache.get("otime") + if not otime: + otime = time.time() + cache.set('up',networkInfo['upTotal'],cache_timeout) + cache.set('down',networkInfo['downTotal'],cache_timeout) + cache.set('otime',otime ,cache_timeout) + + ntime = time.time() + tmpDown = networkInfo['downTotal'] - cache.get("down") + tmpUp = networkInfo['upTotal'] - cache.get("up") + networkInfo['down'] = str(round(float(tmpDown) / 1024 / (ntime - otime),2)) + networkInfo['up'] = str(round(float(tmpUp) / 1024 / (ntime - otime),2)) + if networkInfo['down'] < 0: networkInfo['down'] = 0 + if networkInfo['up'] < 0: networkInfo['up'] = 0 + + otime = time.time() + cache.set('up',networkInfo['upTotal'],cache_timeout) + cache.set('down',networkInfo['downTotal'],cache_timeout) + cache.set('otime',ntime ,cache_timeout) + + networkInfo['cpu'] = self.GetCpuInfo() + return networkInfo + + + #取IO读写信息 + def get_io_info(self,get = None): + io_disk = psutil.disk_io_counters() + ioTotal = {} + ioTotal['write'] = self.get_io_write(io_disk.write_bytes) + ioTotal['read'] = self.get_io_read(io_disk.read_bytes) + return ioTotal + + #取IO写 + def get_io_write(self,io_write): + disk_io_write = 0 + old_io_write = cache.get('io_write') + if not old_io_write: + cache.set('io_write',io_write) + return disk_io_write + + old_io_time = cache.get('io_time') + new_io_time = time.time() + if not old_io_time: old_io_time = new_io_time + io_end = (io_write - old_io_write) + time_end = (time.time() - old_io_time) + if io_end > 0: + if time_end < 1: time_end = 1 + disk_io_write = io_end / time_end + cache.set('io_write',io_write) + cache.set('io_time',new_io_time) + if disk_io_write > 0: return int(disk_io_write) + return 0 + + #取IO读 + def get_io_read(self,io_read): + disk_io_read = 0 + old_io_read = cache.get('io_read') + if not old_io_read: + cache.set('io_read',io_read) + return disk_io_read + old_io_time = cache.get('io_time') + new_io_time = time.time() + if not old_io_time: old_io_time = new_io_time + io_end = (io_read - old_io_read) + time_end = (time.time() - old_io_time) + if io_end > 0: + if time_end < 1: time_end = 1 + disk_io_read = io_end / time_end + cache.set('io_read',io_read) + if disk_io_read > 0: return int(disk_io_read) + return 0 + + #检查并修复MySQL目录权限 + def __check_mysql_path(self): + try: + #获取datadir路径 + mypath = '/etc/my.cnf' + if not os.path.exists(mypath): return False + public.set_mode(mypath,644) + mycnf = public.readFile(mypath) + tmp = re.findall(r'datadir\s*=\s*(.+)',mycnf) + if not tmp: return False + datadir = tmp[0] + + #可以被启动的权限 + accs = ['755','777'] + + #处理data目录权限 + mode_info = public.get_mode_and_user(datadir) + if not mode_info['mode'] in accs or mode_info['user'] != 'mysql': + public.ExecShell('chmod 755 ' + datadir) + public.ExecShell('chown -R mysql:mysql ' + datadir) + + #递归处理父目录权限 + datadir = os.path.dirname(datadir) + while datadir != '/': + if datadir == '/': break + mode_info = public.get_mode_and_user(datadir) + if not mode_info['mode'] in accs: + public.ExecShell('chmod 755 ' + datadir) + datadir = os.path.dirname(datadir) + except: pass + + def ServiceAdmin(self,get=None): + # 校验参数 + try: + get.validate([ + Param('name').String(), + Param('type').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + #服务管理 + if get.name == 'mysqld': + public.CheckMyCnf() + self.__check_mysql_path() + if get.name.find('webserver') != -1: + get.name = public.get_webserver() + + if get.name == 'phpmyadmin': + import ajax_v2 + get.status = 'True' + ajax_v2.ajax().setPHPMyAdmin(get) + return public.return_message(0,0,'Executed successfully!') + + if get.name == 'openlitespeed': + if get.type == 'stop': + public.ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl stop') + elif get.type == 'start': + public.ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl start') + else: + public.ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl restart') + return public.return_message(0,0,'Executed successfully!') + + #检查httpd配置文件 + if get.name == 'apache' or get.name == 'httpd': + get.name = 'httpd' + if not os.path.exists(self.setupPath+'/apache/bin/apachectl'): return public.return_message(-1,0,'Execution failed, check if Apache installed') + vhostPath = self.setupPath + '/panel/vhost/apache' + if not os.path.exists(vhostPath): + public.ExecShell('mkdir ' + vhostPath) + public.ExecShell('/etc/init.d/httpd start') + + if get.type == 'start': + public.ExecShell('/etc/init.d/httpd stop') + self.kill_port() + + result = public.ExecShell('ulimit -n 8192 ; ' + self.setupPath+'/apache/bin/apachectl -t') + if result[1].find('Syntax OK') == -1: + public.write_log_gettext("Software manager",'Execution failed: {}', (str(result),)) + return public.return_message(-1,0,"Apache rule configuration error:
                                    {}",(result[1].replace("\n",'
                                    '),)) + + if get.type == 'restart': + public.ExecShell('pkill -9 httpd') + public.ExecShell('/etc/init.d/httpd start') + time.sleep(0.5) + + #检查nginx配置文件 + elif get.name == 'nginx': + vhostPath = self.setupPath + '/panel/vhost/rewrite' + if not os.path.exists(vhostPath): public.ExecShell('mkdir ' + vhostPath) + if not os.path.exists("/dev/shm/nginx-cache/wp"): + public.ExecShell('mkdir -p /dev/shm/nginx-cache/wp && chown -R www.www /dev/shm/nginx-cache') + vhostPath = self.setupPath + '/panel/vhost/nginx' + if not os.path.exists(vhostPath): + public.ExecShell('mkdir ' + vhostPath) + public.ExecShell('/etc/init.d/nginx start') + + result = public.ExecShell('ulimit -n 8192 ; '+self.setupPath+'/nginx/sbin/nginx -t -c '+self.setupPath+'/nginx/conf/nginx.conf') + if result[1].find('perserver') != -1: + limit = self.setupPath + '/nginx/conf/nginx.conf' + nginxConf = public.readFile(limit) + limitConf = "limit_conn_zone $binary_remote_addr zone=perip:10m;\n\t\tlimit_conn_zone $server_name zone=perserver:10m;" + nginxConf = nginxConf.replace("#limit_conn_zone $binary_remote_addr zone=perip:10m;",limitConf) + public.writeFile(limit,nginxConf) + public.ExecShell('/etc/init.d/nginx start') + return public.return_message(0,0,'Configuration file mismatch caused by reinstalling Nginx fixed') + + if result[1].find('proxy') != -1: + import panelSite + panelSite.panelSite().CheckProxy(get) + public.ExecShell('/etc/init.d/nginx start') + return public.return_message(0,0,'Configuration file mismatch caused by reinstalling Nginx fixed') + + #return result + if result[1].find('successful') == -1: + public.write_log_gettext("Software manager",'Execution failed: {}', (str(result),)) + return public.return_message(-1,0,"Nginx rule configuration error:
                                    {}".format(result[1].replace("\n",'
                                    '),)) + + if get.type == 'start': + self.kill_port() + time.sleep(0.5) + if get.name == 'redis': + redis_init = '/etc/init.d/redis' + if os.path.exists(redis_init): + init_body = public.ReadFile(redis_init) + if init_body.find('pkill -9 redis') == -1: + public.ExecShell("wget -O " + redis_init + " " + public.get_url() + '/init/redis.init') + public.ExecShell("chmod +x " + redis_init) + + #执行 + execStr = "/etc/init.d/"+get.name+" "+get.type + if execStr == '/etc/init.d/pure-ftpd reload': execStr = self.setupPath+'/pure-ftpd/bin/pure-pw mkdb '+self.setupPath+'/pure-ftpd/etc/pureftpd.pdb' + if execStr == '/etc/init.d/pure-ftpd start': public.ExecShell('pkill -9 pure-ftpd') + if execStr == '/etc/init.d/tomcat reload': execStr = '/etc/init.d/tomcat stop && /etc/init.d/tomcat start' + if execStr == '/etc/init.d/tomcat restart': execStr = '/etc/init.d/tomcat stop && /etc/init.d/tomcat start' + + if get.name != 'mysqld': + result = public.ExecShell(execStr) + else: + public.ExecShell(execStr) + result = [] + result.append('') + result.append('') + + if result[1].find('nginx.pid') != -1: + public.ExecShell('pkill -9 nginx && sleep 1') + public.ExecShell('/etc/init.d/nginx start') + if get.type != 'test': + public.write_log_gettext("Software manager", 'Executed successfully [{}]!',(execStr,)) + + if get.type != 'stop': + n = 0 + num = 5 + while not self.check_service_status(get.name): + time.sleep(0.5) + n += 1 + if n > num: break + + if not self.check_service_status(get.name): + if len(result[1]) > 1 and get.name != 'pure-ftpd' and get.name != 'redis': + return public.return_message(-1,0, '

                                    failed to activate:

                                    ' + result[1].replace('\n','
                                    ')) + else: + return public.return_message(-1,0,'{} service failed to start'.format(get.name)) + else: + if self.check_service_status(get.name): return public.return_message(-1,0, 'Service stop failed!') + return public.return_message(0,0,'Executed successfully!') + + def check_service_status(self,name): + ''' + @name 检查服务管理状态 + @author hwliang + @param name 服务名称 + @return bool + ''' + if name in ['mysqld','mariadbd']: + return public.is_mysql_process_exists() + elif name == 'redis': + return public.is_redis_process_exists() + elif name == 'pure-ftpd': + return public.is_pure_ftpd_process_exists() + elif name.find('php-fpm') != -1: + return public.is_php_fpm_process_exists(name) + elif name == 'nginx': + return public.is_nginx_process_exists() + elif name in ['httpd','apache']: + return public.is_httpd_process_exists() + elif name == 'memcached': + return public.is_memcached_process_exists() + elif name == 'mongodb': + return public.is_mongodb_process_exists() + else: + return True + + + + + def RestartServer(self,get): + if not public.IsRestart(): return public.return_message(-1,0,'Please run the program when all install tasks finished!') + public.ExecShell("sync && init 6 &") + return public.return_message(0,0,'Command sent successfully!') + + def kill_port(self): + public.ExecShell('pkill -9 httpd') + public.ExecShell('pkill -9 nginx') + public.ExecShell("kill -9 $(lsof -i :80|grep LISTEN|awk '{print $2}')") + return True + + #释放内存 + def ReMemory(self,get): + public.ExecShell('sync') + scriptFile = 'script/rememory.sh' + if not os.path.exists(scriptFile): + public.downloadFile(public.GetConfigValue('home') + '/script/rememory.sh',scriptFile) + public.ExecShell("/bin/bash " + self.setupPath + '/panel/' + scriptFile) + return self.GetMemInfo() + + #重启面板 + def ReWeb(self,get): + # 校验参数 + try: + get.validate([ + Param('toUpdate').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + public.ExecShell("/etc/init.d/bt start") + public.writeFile('data/restart.pl','True') + # 重启面板 默认开启系统监控 + public.writeFile('data/control.conf', '30') + return public.return_message(0,0,'Panel restarted') + + + #修复面板 + def RepPanel(self,get): + # 校验参数 + try: + get.validate([ + Param('toUpdate').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + public.writeFile('data/js_random.pl','1') + public.ExecShell("wget --no-check-certificate -O update.sh " + public.get_url() + "/install/update_7.x_en.sh && bash update.sh") + self.ReWeb(None) + return public.return_message(0,0,True) + + #升级到专业版 + def UpdatePro(self,get): + public.ExecShell("wget --no-check-certificate -O update.sh " + public.get_url() + "/install/update_7.x_en.sh && bash update.sh") + self.ReWeb(None) + return True diff --git a/class_v2/user_login_v2.py b/class_v2/user_login_v2.py new file mode 100644 index 00000000..4b1e6b32 --- /dev/null +++ b/class_v2/user_login_v2.py @@ -0,0 +1,422 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +import public,os,sys,db,time,json,re +from BTPanel import session,cache,json_header +from flask import request,redirect,g + +class userlogin: + limit_expire_time = 0 + def request_post(self,post): + if not hasattr(post, 'username') or not hasattr(post, 'password'): + return public.returnJson(False,'User name or password cannot be empty!'),json_header + self.error_num(False) + if self.limit_address('?') < 1: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header + post.username = post.username.strip() + format_error = 'Parameter format error' + + # 核验用户名密码格式 + post.username = public.rsa_decrypt(post.username) + + if len(post.username) != 32: + return public.returnMsg(False,format_error+"1"),json_header + post.password = public.rsa_decrypt(post.password) + if len(post.password) != 32: + return public.returnMsg(False,format_error+"2"),json_header + + if not re.match(r"^\w+$",post.username): return public.return_msg_gettext(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header + if not re.match(r"^\w+$",post.password): return public.return_msg_gettext(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header + last_login_token = session.get('last_login_token',None) + if not last_login_token: + public.WriteLog('TYPE_LOGIN','LOGIN_ERR_CODE',('****','****',public.GetClientIp())) + return public.returnJson(False,"Verification failed, please refresh the page and log in again!"),json_header + + public.chdck_salt() + sql = db.Sql() + userInfo = None + user_plugin_file = '{}/users_main.py'.format(public.get_plugin_path('users')) + if os.path.exists(user_plugin_file): + user_list = sql.table('users').field('id,username,password,salt').select() + for u_info in user_list: + if public.md5(public.md5(u_info['username'] + last_login_token)) == post.username: + userInfo = u_info + else: + userInfo = sql.table('users').where('id=?',1).field('id,username,password,salt').find() + + + if 'code' in session: + if session['code'] and not 'is_verify_password' in session: + if not hasattr(post, 'code'): return public.returnJson(False,'Verification code can not be empty!'),json_header + if not re.match(r"^\w+$",post.code): return public.returnJson(False,'Verification code is incorrect, please try again!'),json_header + if not public.checkCode(post.code): + public.write_log_gettext('Login','Verification code is incorrect, Username:{}, Verification Code:{}, Login IP:{}',('****','****',public.GetClientIp())) + return public.returnJson(False,'Verification code is incorrect, please try again!'),json_header + try: + if not userInfo: + public.WriteLog('TYPE_LOGIN','LOGIN_ERR_PASS',('****','******',public.GetClientIp())) + num = self.limit_address('+') + if not num: return public.returnJson(False,'You have failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header + return public.returnJson(False,'wrong user name or password,please refresh the page and try again,You can retry {} more times'.format(num)),json_header + + if userInfo and not userInfo['salt']: + public.chdck_salt() + userInfo = sql.table('users').where('id=?',(userInfo['id'],)).field('id,username,password,salt').find() + + password = public.md5(post.password.strip() + userInfo['salt']) + s_username = public.md5(public.md5(userInfo['username'] + last_login_token)) + if s_username != post.username or userInfo['password'] != password: + public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) + num = self.limit_address('+') + if not num: return public.returnJson(False,'You failed to log in many times, please try again in {} seconds!'.format(int(self.limit_expire_time - time.time()))),json_header + return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header + _key_file = "/www/server/panel/data/two_step_auth.txt" + + # 密码过期检测 + if sys.path[0] != 'class/': sys.path.insert(0,'class/') + if not public.password_expire_check(): + session['password_expire'] = True + + #登陆告警 + #public.run_thread(public.login_send_body,("账号密码",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) + # public.login_send_body("账号密码",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))) + if hasattr(post,'vcode'): + if not re.match(r"^\d+$",post.vcode): return public.returnJson(False,'Incorrect format of verification code'),json_header + if self.limit_address('?',v="vcode") < 1: return public.returnJson(False,'You have failed verification many times, forbidden for 10 minutes'),json_header + import pyotp + secret_key = public.readFile(_key_file) + if not secret_key: + return public.returnJson(False, "Did not find the key, please close Google verification on the command line and trun on again"),json_header + t = pyotp.TOTP(secret_key) + result = t.verify(post.vcode) + if not result: + if public.sync_date(): result = t.verify(post.vcode) + if not result: + num = self.limit_address('++',v="vcode") + return public.returnJson(False, 'Invalid Verification code. You have [{}] times left to try!'.format(num)), json_header + now = int(time.time()) + # public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) + public.writeFile("/www/server/panel/data/dont_vcode_ip.txt",json.dumps({"client_ip":public.GetClientIp(),"add_time":now})) + self.limit_address('--',v="vcode") + self.set_cdn_host(post) + return self._set_login_session(userInfo) + + acc_client_ip = self.check_two_step_auth() + + if not os.path.exists(_key_file) or acc_client_ip: + public.run_thread(public.login_send_body,("account",userInfo['username'],public.GetClientIp(),str(int(request.environ.get('REMOTE_PORT'))))) + self.set_cdn_host(post) + return self._set_login_session(userInfo) + self.limit_address('-') + session['is_verify_password'] = True + return "1" + except Exception as ex: + stringEx = str(ex) + if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1: + public.ExecShell("rm -f /tmp/sess_*") + public.ExecShell("rm -f /www/wwwlogs/*log") + public.ServiceReload() + return public.returnJson(False,'USER_INODE_ERR'),json_header + public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) + num = self.limit_address('+') + if not num: return public.returnJson(False,'You have failed to log in many times, please wait {} seconds and try again!'.format(int(self.limit_expire_time - time.time()))),json_header + return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header + + def request_tmp(self,get): + try: + if not hasattr(get,'tmp_token'): return public.returnJson(False,'Parameter ERROR!'),json_header + if len(get.tmp_token) == 48: + return self.request_temp(get) + if len(get.tmp_token) != 64: return public.returnJson(False,'Parameter ERROR!'),json_header + if not re.match(r"^\w+$",get.tmp_token):return public.returnJson(False,'Parameter ERROR!'),json_header + save_path = '/www/server/panel/config/api.json' + data = json.loads(public.ReadFile(save_path)) + if not 'tmp_token' in data or not 'tmp_time' in data: return public.returnJson(False,'Verification failed'),json_header + if (time.time() - data['tmp_time']) > 120: return public.returnJson(False,'Expired Token'),json_header + if get.tmp_token != data['tmp_token']: return public.returnJson(False,'Invalid Token!'),json_header + userInfo = public.M('users').where("id=?",(1,)).field('id,username').find() + session['login'] = True + session['username'] = userInfo['username'] + session['tmp_login'] = True + session['uid'] = userInfo['id'] + ids=public.WriteLog('TYPE_LOGIN','Login success',(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT')))) + public.cache_set(public.GetClientIp() + ":" + str(request.environ.get('REMOTE_PORT')), ids) + self.limit_address('-') + cache.delete('panelNum') + cache.delete('dologin') + session['session_timeout'] = time.time() + public.get_session_timeout() + del(data['tmp_token']) + del(data['tmp_time']) + public.writeFile(save_path,json.dumps(data)) + self.set_request_token() + self.login_token() + self.set_cdn_host(get) + return redirect('/') + except: + return public.returnJson(False,'Login failed,' + public.get_error_info()),json_header + + + def request_temp(self,get): + try: + if len(get.get_items().keys()) > 2: return public.get_msg_gettext('Parameter ERROR!') + if not hasattr(get,'tmp_token'): return public.get_msg_gettext('Parameter ERROR!') + if len(get.tmp_token) != 48: return public.get_msg_gettext('Parameter ERROR!') + if not re.match(r"^\w+$",get.tmp_token):return public.get_msg_gettext('Parameter ERROR!') + skey = public.GetClientIp() + '_temp_login' + if not public.get_error_num(skey,10): return public.get_msg_gettext('10 consecutive authentication failures are prohibited for 1 hour') + s_time = int(time.time()) + if public.M('temp_login').where('state=? and expire>?',(0,s_time)).field('id,token,salt,expire').count()==0: + public.set_error_num(skey) + return public.get_msg_gettext('Verification failed') + + data = public.M('temp_login').where('state=? and expire>?',(0,s_time)).field('id,token,salt,expire').find() + if not data: + public.set_error_num(skey) + return public.get_msg_gettext('Verification failed') + if not isinstance(data,dict): + public.set_error_num(skey) + return public.get_msg_gettext('Verification failed') + r_token = public.md5(get.tmp_token + data['salt']) + if r_token != data['token']: + public.set_error_num(skey) + return public.get_msg_gettext('Verification failed') + public.set_error_num(skey,True) + userInfo = public.M('users').where("id=?",(1,)).field('id,username').find() + session['login'] = True + session['username'] = public.get_msg_gettext('TEMPORARY_ID({})',(data['id'],)) + session['tmp_login'] = True + session['tmp_login_id'] = str(data['id']) + session['tmp_login_expire'] = time.time() + 3600 + session['uid'] = data['id'] + sess_path = 'data/session' + if not os.path.exists(sess_path): + os.makedirs(sess_path,384) + public.writeFile(sess_path + '/' + str(data['id']),'') + login_addr = public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT')) + ids = public.write_log_gettext('Login','Login succeed, Username: {}, Login IP: {}',(userInfo['username'],login_addr)) + public.cache_set(public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT')),ids) + public.M('temp_login').where('id=?',(data['id'],)).update({"login_time":s_time,'state':1,'login_addr':login_addr}) + self.limit_address('-') + cache.delete('panelNum') + cache.delete('dologin') + session['session_timeout'] = time.time() + public.get_session_timeout() + self.set_request_token() + self.login_token() + self.set_cdn_host(get) + public.run_thread(public.login_send_body("Temporary authorization",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT')))) + return redirect('/') + except: + public.print_log(public.get_error_info(),'ERROR') + return public.get_msg_gettext('Login failed') + + + def login_token(self): + import config + config.config().reload_session() + + def request_get(self,get): + ''' + @name 验证登录页面请求权限 + @author hwliang + @return False | Response + ''' + # 获取标题 + if not 'title' in session: session['title'] = public.getMsg('NAME') + + # 验证是否使用限制的域名访问 + domain_check = public.check_domain_panel() + if domain_check: return domain_check + + # 验证是否使用限制的IP地址访问 + ip_check = public.check_ip_panel() + if ip_check: return ip_check + + # 验证是否已经登录 + if 'login' in session: + if session['login'] == True: + return redirect('/') + + # 复位验证码 + if not 'code' in session: + session['code'] = False + + # 记录错误次数 + self.error_num(False) + + #生成request_token + def set_request_token(self): + html_token_key = public.get_csrf_html_token_key() + session[html_token_key] = public.GetRandomString(48) + session[html_token_key.replace("https_","")] = public.GetRandomString(48) + #session['client_hash'] = public.get_client_hash() + + def set_cdn_host(self,get): + try: + if not 'cdn_url' in get: return True + plugin_path = 'plugin/static_cdn' + if not os.path.exists(plugin_path): return True + cdn_url = public.get_cdn_url() + if not cdn_url or cdn_url == get.cdn_url: return True + public.set_cdn_url(get.cdn_url) + except: + return False + + #防暴破 + def error_num(self,s = True): + nKey = 'panelNum' + num = cache.get(nKey) + if not num: + cache.set(nKey,1) + num = 1 + if s: cache.inc(nKey,1) + if num > 6: session['code'] = True + + #IP限制 + def limit_address(self,type,v=""): + import time + clientIp = public.GetClientIp() + numKey = 'limitIpNum_' + v + clientIp + limit = 5 + outTime = 300 + try: + #初始化 + num1 = cache.get(numKey) + if not num1: + cache.set(numKey,0,outTime) + num1 = 0 + + self.limit_expire_time = cache.get_expire_time(numKey) + + #计数 + if type == '+': + cache.inc(numKey,1) + self.error_num() + session['code'] = True + return limit - (num1+1) + + #计数验证器 + if type == '++': + cache.inc(numKey,1) + self.error_num() + session['code'] = False + return limit - (num1+1) + + #清空 + if type == '-': + cache.delete(numKey) + session['code'] = False + return 1 + + #清空验证器 + if type == '--': + cache.delete(numKey) + session['code'] = False + return 1 + return limit - num1 + except: + return limit + + # 登录成功设置session + def _set_login_session(self,userInfo): + try: + session['login'] = True + session['username'] = userInfo['username'] + session['uid'] = userInfo['id'] + session['login_user_agent'] = public.md5(request.headers.get('User-Agent','')) + ids = public.write_log_gettext('Login','Login succeed, Username: {}, Login IP: {}',(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT')))) + public.cache_set(public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT')),ids) + self.limit_address('-') + cache.delete('panelNum') + cache.delete('dologin') + session['session_timeout'] = time.time() + public.get_session_timeout() + if 'last_login_token' in session: del(session['last_login_token']) + self.set_request_token() + self.login_token() + login_type = 'data/app_login.pl' + if os.path.exists(login_type): + os.remove(login_type) + try: + default_pl = "{}/default.pl".format(public.get_panel_path()) + public.writeFile(default_pl,"********") + except: + pass + + address = public.GetClientIp() + port = str(request.environ.get('REMOTE_PORT')) + + login_address = '{}(unknown)'.format(address,) + #返回增加登录地区 + res = public.returnMsg(True,'LOGIN_SUCCESS') + res['login_time'] = time.time() + res['login_time_str'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + try: + ip_info = public.get_free_ip_info(address) + if 'city' in ip_info: + res['ip_info'] = ip_info + if 'Internal network address' in ip_info['info']: + res['ip_info'] = ip_info + res['ip_info']['ip'] = address + login_address = '{}({})'.format(address,ip_info['info']) + except: + print(public.get_error_info()) + + last_login = {} + last_file = 'data/last_login.pl' + try: + last_login = json.loads(public.readFile(last_file)) + except:pass + public.writeFile(last_file,json.dumps(res)) + + res['last_login'] = last_login + session['login_address'] = public.xsssec(login_address) + session['login_time'] = res['login_time'] # 记录登录时间,验证客户端时要用,不要删除 + public.record_client_info() + return public.getJson(res),json_header + except Exception as ex: + stringEx = str(ex) + if stringEx.find('unsupported') != -1 or stringEx.find('-1') != -1: + public.ExecShell("rm -f /tmp/sess_*") + public.ExecShell("rm -f /www/wwwlogs/*log") + public.ServiceReload() + return public.returnJson(False,'Disk inode has been exhausted, the panel has attempted to release the inode. Please try again ...'),json_header + public.write_log_gettext('Login','Password is incorrect, Username:{}, Password:{}, Login IP:{}',('****','******',public.GetClientIp())) + num = self.limit_address('+') + return public.returnJson(False,'Invalid username or password. You have [{}] times left to try!',(str(num),)),json_header + + + # 检查是否需要进行二次验证 + def check_two_step_auth(self): + dont_vcode_ip_info = public.readFile("/www/server/panel/data/dont_vcode_ip.txt") + acc_client_ip = False + if dont_vcode_ip_info: + dont_vcode_ip_info = json.loads(dont_vcode_ip_info) + ip = dont_vcode_ip_info["client_ip"] == public.GetClientIp() + now = int(time.time()) + v_time = now - int(dont_vcode_ip_info["add_time"]) + if ip and v_time < 86400: + acc_client_ip = True + return acc_client_ip + + # 清理多余SESSION数据 + def clear_session(self): + try: + session_file = '/dev/shm/session.db' + if not os.path.exists(session_file): return False + s_size = os.path.getsize(session_file) + if s_size < 1024 * 512: return False + if s_size > 1024 * 1024 * 10: + from BTPanel import sdb + if os.path.exists(session_file): os.remove(session_file) + sdb.create_all() + if not os.path.exists(session_file): + public.writeFile('/www/server/panel/data/reload.pl','True') + return False + return True + except: + return False + diff --git a/class_v2/vilidate_v2.py b/class_v2/vilidate_v2.py new file mode 100644 index 00000000..c7e29e95 --- /dev/null +++ b/class_v2/vilidate_v2.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 宝塔(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- + +import random, math +from PIL import Image, ImageDraw, ImageFont, ImageFilter + +class vieCode: + __fontSize = 20 #字体大小 + __width = 120 #画布宽度 + __heigth = 45 #画布高度 + __length = 4 #验证码长度 + __draw = None #画布 + __img = None #图片资源 + __code = None #验证码字符 + __str = None #自定义验证码字符集 + __inCurve = True #是否画干扰线 + __inNoise = True #是否画干扰点 + __type = 2 #验证码类型 1、纯字母 2、数字字母混合 + __fontPatn = 'class/fonts/2.ttf' #字体 + + def GetCodeImage(self,size = 80,length = 4): + '''获取验证码图片 + @param int size 验证码大小 + @param int length 验证码长度 + ''' + #准备基础数据 + self.__length = length + self.__fontSize = size + self.__width = self.__fontSize * self.__length + self.__heigth = int(self.__fontSize * 1.5) + + #生成验证码图片 + self.__createCode() + self.__createImage() + self.__createNoise() + self.__printString() + self.__cerateFilter() + + return self.__img,self.__code + + def __cerateFilter(self): + '''模糊处理''' + self.__img = self.__img.filter(ImageFilter.BLUR) + filter = ImageFilter.ModeFilter(8) + self.__img = self.__img.filter(filter) + + def __createCode(self): + '''创建验证码字符''' + #是否自定义字符集合 + if not self.__str: + #源文本 + number = "3456789" + srcLetter = "qwertyuipasdfghjkzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" + srcUpper = srcLetter.upper() + if self.__type == 1: + self.__str = number + else: + self.__str = srcLetter + srcUpper + number + + #构造验证码 + self.__code = random.sample(self.__str,self.__length) + + def __createImage(self): + '''创建画布''' + bgColor = (random.randint(200,255),random.randint(200,255),random.randint(200,255)) + self.__img = Image.new('RGB', (self.__width,self.__heigth), bgColor) + self.__draw = ImageDraw.Draw(self.__img) + + def __createNoise(self): + '''画干扰点''' + if not self.__inNoise: + return + font = ImageFont.truetype(self.__fontPatn, int(self.__fontSize / 1.5)) + for i in range(5): + #杂点颜色 + noiseColor = (random.randint(150,200), random.randint(150,200), random.randint(150,200)) + putStr = random.sample(self.__str,2) + for j in range(2): + #绘杂点 + size = (random.randint(-10,self.__width), random.randint(-10,self.__heigth)) + self.__draw.text(size,putStr[j], font=font,fill=noiseColor) + pass + + def __createCurve(self): + '''画干扰线''' + if not self.__inCurve: + return + x = y = 0; + + #计算曲线系数 + a = random.uniform(1, self.__heigth / 2) + b = random.uniform(-self.__width / 4, self.__heigth / 4) + f = random.uniform(-self.__heigth / 4, self.__heigth / 4) + t = random.uniform(self.__heigth, self.__width * 2) + xend = random.randint(self.__width / 2, self.__width * 2) + w = (2 * math.pi) / t + + #画曲线 + color = (random.randint(30, 150), random.randint(30, 150), random.randint(30, 150)) + for x in range(xend): + if w!=0: + for k in range(int(self.__heigth / 10)): + y = a * math.sin(w * x + f)+ b + self.__heigth / 2 + i = int(self.__fontSize / 5) + while i > 0: + px = x + i + py = y + i + k + self.__draw.point((px , py), color) + i -= i + + def __printString(self): + '''打印验证码字符串''' + font = ImageFont.truetype(self.__fontPatn, self.__fontSize) + x = 0; + #打印字符到画板 + for i in range(self.__length): + #设置字体随机颜色 + color = (random.randint(30, 150), random.randint(30, 150), random.randint(30, 150)) + #计算座标 + x = random.uniform(self.__fontSize*i*0.95,self.__fontSize*i*1.1); + y = self.__fontSize * random.uniform(0.3,0.5); + #打印字符 + self.__draw.text((x, y),self.__code[i], font=font, fill=color) diff --git a/class_v2/wp_toolkit/__init__.py b/class_v2/wp_toolkit/__init__.py new file mode 100644 index 00000000..46795cb3 --- /dev/null +++ b/class_v2/wp_toolkit/__init__.py @@ -0,0 +1,17 @@ +import public +import public.PluginLoader as plugin_loader + + +core_m = plugin_loader.get_module('{}/class_v2/wp_toolkit/core.py'.format(public.get_panel_path())) +wpmgr = core_m.wpmgr +wp_version = core_m.wp_version +wpfastcgi_cache = core_m.wpfastcgi_cache +wpbackup = core_m.wpbackup +wpmigration = core_m.wpmigration +wpdeployment = core_m.wpdeployment +# from .core import wpmgr, wp_version, wpfastcgi_cache, wpbackup, wpmigration, wpdeployment + + + +security_m = plugin_loader.get_module('{}/class_v2/wp_toolkit/security.py'.format(public.get_panel_path())) +wp_security = security_m.wp_security diff --git a/class_v2/wp_toolkit/core.py b/class_v2/wp_toolkit/core.py new file mode 100644 index 00000000..e4b09a86 --- /dev/null +++ b/class_v2/wp_toolkit/core.py @@ -0,0 +1,3818 @@ +3m6maKOpM/L/yP3rpT4bK2ggj7JOWTKnF9lbOKsSNQw= +hlsbBRfiGI29i3aXREgRMrgu25uNDIw/Wngp8RMy9JY= ++0eYQPDDVpFGHN21OazCCuiB1t3ZWxn4cDgvZYFE/8A= +dTLQ8Wr3wPn7w6pte1B1YA== +SbQQ5SqO9QrBwZ9ObpMYLw== +piG6BsMF31u4R4iA6R4SRA== +XIfdJ79nObMM+vyAmKbTmw== +wiYVtfO/yzajW1Tv5z7hyA== +w42r1xg9qpESU6Bd+WNVzhnKUgvI6yig6xoFOZh+Cng= +DRVOWA8p3Hg+UVFB+ijoeJiQKS/oQs+5utRgqpiaBn4= +2+FiEb7mbSCVnobUl8p5wg== +SLvA+cN7EMWEfpSbTDPlOtvm1okUJx/fvmAkl6hTius= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +B1MnpepwGABZj+h+l4KbeCnHuyUvLETYyVVJBjqH0Qk= +gpdp0iBlo1faqisT2fyaTEQwitRJuKpFcBeXqsk4rjo= +GCKcam944byMATazjtzHeVsUGqC6vWeA1DfjDIipRhxLtfv4KarwG6sc3yAKPfXzW0/4GSvEN8yz/jftCH5BLkqJhp0cFRhshphRei2x/2T/DgZXH+DlrxsSX42f866jmbz88aLHwJ+ztyswFButMg== +EAgPtM5g3sE/C9xowdVWHh5T9sPYgMk499kn7UZXGsqqddNIQpwwYZQDmFPx4l/vAvWreETGtszc1fz27yvBRGXBILoZ97zoSJZctY15wm1P+zDFeWIDXUoPKAnfvDM1 +BZJVsvwr9RklOP4oCxlRj7RoslAW4veKqRHMHaknsfsiMLPxk5j/x9wk24l57+FuwD/veQlzmDlP2ltT+fkJv6gEyhMY1rtExZrfKMKcrVxZM7OBAmOa9gBDDceMbmhPoyIh1hR/NZOA8YSULoP87w== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Rsz65WKl8ZLvUDoyoE6qaZxywIHsbaqTmpElJujTduk= +CqGpN3Lz6vgA0ts4Kp9Fzgup9BufOQ4OyxefqALb8iQ= +mA5cESknfOIsZuRV/c4Jnj2ahKMS5z0hQlLzYOzH8EIHcPRQP+M8ShrrFNXoeRVHsVmYb4gFZNJkCEB9jgK7jYTJ2Prh5cGlgjjjAqkJWf0B9SGTjc09NWzT/IUc2rbfyAkKLD5gyHdCVAbnwUl5DxohqYaXb/8pE7ZroGbKqlE= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+HUA8m6mVgnOLyW2K/n831KjDD/vs3+PYavlI9PIHAVxZitd/gf/EGgU3HSjiGPHZ9PnOduRTbHl7wqc9DjbH150xNqzn5OZ7ZUKk32Z9wcXUG6N3AytRvnrsT2MPmW1ogEWqmw2arSEV2dGU1vSOAbKpL8Xs1EbRJcSRn2l2o9XiXklQXPDp2iZDtrMZNCidA== +IqGIB4xf/Wocd4vrF30SvjrifOecHjUB/3CQ+0MqtOc= +TRyo3DV8r8YcF71x/BjKjVtNCQE12YHUh8Hewxm2qQmm/R5Zwzp25xb8A8+g7CQ8 +xL30zHgIG5h0PGFEOMTs7R3DGeKTKDH8OyCwEBkjeo639eNUHj9xIKJtp9XyvZlR +fXBVUljuohLKH8mwvXLO+SePHL7KvCNMwBdVXOouxz8= +fXBVUljuohLKH8mwvXLO+focbmHlRM72hp4YjCz9E3E= +fXBVUljuohLKH8mwvXLO+VWJDPoImeUvuijs+4Qy5hw= +fXBVUljuohLKH8mwvXLO+ZFndevbcoyfhrye9pOf6DZbMYg1ZAQu3a08jxwZC6ju +xqyScO/MOJo+rfifnO2pVJpWeznnAM0PzFdZ280Jy/34dEIKaw/7o7MVUnsgPLioaawyeQDCDAqnThzqWocCug== +1u+XjG/2+GSQRv6EzCaWRQ== +bH9iS7BmfKTTQ5e5cIHrV+NToa+Qu0Q6miLpIDPC7OQ= +/gHFu7aRGoB8NMhadfH0LSYgFv3ONQfPrqC2lhgmRwSiatfh+gngguBY7Z04lTP2 +wgR07xfoapmx6eEnFHXXYlEV6h2Qh0/E9zwcuGp+QctxMca/YFYUc4GaSAHUOjC/ +YY5wEo94l9qfo5TPxQmUgm3uLJxp9ZFr+0KFa0oXP4fvmkRFy3kB+iXcpGXlgGUvSOAewGU+5zp9LlFk0DAKdbERHqOgd3tqzlIJKxfIuc5T8Q1EqMGy6UZxtIacOK0y +1u+XjG/2+GSQRv6EzCaWRQ== +gx3w+Kr8Nv+8r/RRihui3svS1Hqc14zcq/98XoqABuY= ++pBWk5LxptzizUTsHnZAzy9uhfHenDbrnbLeEHvXb3gY87+VI2BjWXeaFF/dYIPd +1ZFe+UUdQmu/QujLaciJaqHVy1/Zv7gv7g81P1rTkAeDT02SO6T7WTRZBmkZwNSh +1u+XjG/2+GSQRv6EzCaWRQ== +ewMPic4oY8KA6fNbX11t2SULqPgooroL0OECyzsrHb8= +kgIgK7U5R0FgdlMg5Rh9so7sozUcB0zfuzq4OYYN619zxkcq69ovj0XL19qa+qDQuEiz3G0oQoHCwoMeF3rGZw== +xWoGNWjKGPfI4gq8aHoTfKj7V6/GbVtiII/Elkzy/SIxsAN6Em7FysJa4jADHlGMg/lVpPwNP8IEbwy04HBldA== +xWoGNWjKGPfI4gq8aHoTfKPuN4OuuBBNBwJwP5t+rrG0Y2u5WoVDzNeyHKGQE/w3iS/K+Zw1zFYz9h0+kWnC2g== +xWoGNWjKGPfI4gq8aHoTfIj4i28ss+29lGHGjDyT4yR9pxg/1r/Xdbf0jjEA9G5Eo0UfSlH1xtjkf2egjpprdQ== +xWoGNWjKGPfI4gq8aHoTfI65fOfKuI1D/27XO5rrJkxYDYN5AaD/U8ieE2lndMCRUHP5i2HSRS1QzIGTv9NIPQ== +xWoGNWjKGPfI4gq8aHoTfBJP/QcKNqdYzuq5movMnohyKLAZnic66Q+f6D9RHfEv4F2aRqi56VqKzsll17+P3wQU2933tacjO6MptwnWvUs= +xWoGNWjKGPfI4gq8aHoTfBmQh5TOrrwkxUFe47DZWLniKO5z5xOMDVxEQ+jICCB7I4S9CMcaer47K7CObFBgnYhS8H9/OW6bU2bSVR0IyeU= +xWoGNWjKGPfI4gq8aHoTfIn6PnMrB/8qeqFwmI91ZmH9qroXVV1zNa+Wb/R36MP0Ov2NlEydgmSe0VAxyfEZYA== +xWoGNWjKGPfI4gq8aHoTfOFwAtPLj4a53BzMR+y33t5F5Dsx8SXJwti8PdMsllMW8x40N19uyMI4YjnqPGawjqrcb8pVrh08FkRfVMPdizE= +jIYtewpcL6elNlf4btqXRw== +1u+XjG/2+GSQRv6EzCaWRQ== +3PYuJCOiZH1sjGKjxSVU6FxQzcYGr9jxKQMV6Y0aCm7Q8D0/3M6ro6FjClK10BcOVWBbfWS93OOUvuvrZj/Uvg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmezvkVwBnln66Bc2ddZ9UUpfzRP5JCN9M61YkL8kkfZGts31X0xlOn1WK79Qdtt/I= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkRTza+VuW0x8O83NImYiJZGuJJW/3v1tzOfzL5MSH+EVtJRiicUo0JkYXm8N8Rnlc= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklLUHU89Sv40FT2Rv0m4sknkQd0bS6cwOdmtwhtNePqhw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl7yTnnnorksGepT/Q4eVwpjg2BBCtQxuOPuKZY6DHa1A== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkml9enbBiSpusrvGBnE/5OLrGUo44QSfVEE9FVYnEZ39w== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm3+5dZCbMg80GQapK6hYjbDrbBo07UYr5auewiEcA57IxmIKl+Fp+HHS+0CDYltcE= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn2roHpOuDc0PI1P3pE25/MdJ6gcnQqeeLqyKYycmGD0rFHSqCjj/7fZpiImHAkXmg= +1u+XjG/2+GSQRv6EzCaWRQ== +xHcOA00ZGeV2Y1yiFUriAeqhbb5Y4Kg7BYO9ULVhShk= +xZTDA13Z2aXQCOTarFNfvvSHkKKdIEL9fr6G2/Uo5VMrTV+42Fqo8lo/M3Vsgecu +AWid6G8ypXqQe9XxPPYED21qUZ05KduG0I5aBgcmb0M= +Oc/iRPlDEtyVPsBvXbRFyzMsneQ3tkrY+OvPJlqNc4zJi/LtX9Sh6vLSPyzveTXG +K8WxDHXgwdNGovmPv4l3bBPIHS/ynmOr4VV2JPV3z1uOCkG03cwN02ZluHoUCy1B +V2fR4lY2I9TUOq7llIMViSAda5sjsuG/aghTCUzxHRWMOqTAJEcPGQV1sgnSfFgg4nKP1sEAsvPHYiKS1tL3qw== +XogJGekt1YVWNznrGGQkHMZZ++lm9bt3hejW59CNL3xJd1jGnpwyEQ9fGd1lH7b/ +XogJGekt1YVWNznrGGQkHKhAB+YaJVFLU31KreZaNlc8Z9HZUKZPjIBpTPjEmw5n +XogJGekt1YVWNznrGGQkHP8ntlWAsRSKvGhVAbTDFZ9wUhIbjcc+T25Fxq0kdi5y +XogJGekt1YVWNznrGGQkHDWgIPBFSa7I/42S9W8M7xeJxq0R0TwZDuNXQ/txpDKM +DxAshdaxPMjTqVP65pzgn1mDaN/wjMK9a/Skh3Gwtmb+beZNBxsoNe9KC9IBpxReAzx87DZlPxMWjZlx1qbUmA== +661hZf7vhUQ+50okfwfTXw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +Brinyc2FMdG3K66yYsaM/46LvfR7DZvVtJk4kqVJrnY= +kKEHZSjBs0JaG0I7BGh34QWeEiULH9CknWVRytnon9g= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +bLlixn6igwVr7WeYa5jmwDrF36ciRNNuWP8IKAPThj5ALnT3Qy0qMqUkocWUN7aNFt2VdOGgb55P2u2uNVSshI+uybRclxp2wf2lD+4rExY= +M52FSgXgnXx2Hd7dfJpFXqUxWAn//1/37n40o4U555UEM6YD+xkY0/vXUQIyze8JhCezbHWAWdjOA28+qUxzVQ== +Gft9mnUbPRlw7Lcbqmj5nG1LPrJBxU5tQ+GYPbByiRyY8OR/ds74YdTRBXCb/dwZqN/XE5ko3wEpHgez9k0PszM+auooFj2yvntzJvdDmbvhvDo3YY06I9zKyvnJ4AeK +Gft9mnUbPRlw7Lcbqmj5nFjZ5vJ7MSGmsRJBY2Y6jeqvYHf6NMbgHnKumVDiLPlnfOsFMk07Dtx4sKqykIB2lw== +Gft9mnUbPRlw7Lcbqmj5nOn0r1+dTzEFMz/TuZQujsMtys2TUz7xp4iv61K/LfIQ +1u+XjG/2+GSQRv6EzCaWRQ== +NRG8WiBknxy8LZ7uM7RtwuCiVTmnFdHKG+846d0G0jqPO1wAZOfRb+FVWlMKscSalXdEAOtk8QzGkMMpNBEFfQ== +1u+XjG/2+GSQRv6EzCaWRQ== +ab9qVNCikBOofZBq3Gme8HQcHs9Y7cbGOcd1c8d7Zbs3R+Q0JL5emJPPF2BzU/W7 +fsuS82vhGsZ1ifItIPgGCr9WKjaKw1ANZnihJNoOblBBD1DzvcRUtMY5sUO13mSsXI7hDfDDRAvY5tgIyQqIiQ== +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9i3iIoN4da+NV6zS2jUJBjOZ25VMbpcWtPa1ePPSKlOug== +NqhMbY8+EQI8zhTG6Zh2I2tVjSnXDwhlBCtnVzuUnFfryLTPmSMz+UQHehiKmZtj6p470FlnBoiFHv/pEpQaag== +1u+XjG/2+GSQRv6EzCaWRQ== +DBrQ+tT9Otxe5su49ASt/93XucGowmX/RxXV+PlJleqPcZvcm/67VQJCCWj7YBEAwt20nevK67+4EoTjXeEqQQ== +O6khUd/vkbHbRdUkJIkLvYmnN4I20A6/6B/+MBaixZYSK9w8XgrkTuu7sLDpxm9f9qZGhX3HgkU6LeaQ2Hysn9urJd4DjDoOuFiHG2CZ0eA= +91gsC2HsVrvTm3cwuG3h9nZQd4BMS+GIOQtJeYVYg42RqQFL3+RZI6cOvFHmnDcH +2yQsUep5WrV3T3htNZ1L8VpIZobQOi6VLLDMdOiUktny6MBI+2Mz1bnR2dv2ZweR24dy1pkWzmWOlmazYnw/lvsyuX828RamOjCWr05DU+I= +Sue/4scTt39iGPa9/O3QwnWJDL8HoMWb/oV3KtMVeK2pqskV7L/K1Ekh5NMfbCTqJlUBSpp4ABGyfflrxMTSzuwLIsXNuArLc/qa/JoE0qc= +1u+XjG/2+GSQRv6EzCaWRQ== +sJAqu+RKGNS3DoGZfXMrdAtUx1XnMB78PshxGhtOKXY= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9o2kvXaOAtJwzudc1BuzPhLyb33yC+z5K6zpgrKtmiPS +ghdF4G8YV9yNjU+KsNcQR/VMWHcF+CTNHIlYTVhUWmgu04/2YZIzDcfqjhf3fFrtoaPuJ1ZAgoFdTrU2n3Bp6CEuImuelVqhbjlIS3Ots6bXPpgIt6k6gOj9Lz+xpaOd +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYo4se8cEG3Kpe+KryARC+5+xlpIc9lykYcAsobdrHGRP +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9vI0FW78zR8MLP0SwlWQPTGDShhW6CG3xtqXeqzB1OG/u8NShoIZl2E0BcAQlNUE2osl5b3NQ0uBeogijU+Jk6ce8mNGCpm8lE8TQpUA5NGb +WHkOzVx7seuLmxs5Hu+l/qarLKXvM4ZssfOCb5hUzDDAsdyt2nHkmI9xC7FYW2tZ9GF+QKMxspFxWk+uvKcPgQBKWqcX/JWdl1opCZTMEtSuSoOqYyklRY/ccPZ97bSS +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +e91oxhGuXYdY7jQndbCTnkR3/JBPSsXMQKkFKHIi4XfC7qybkzu3totipce6WUuj +MzOj4ZiBOJDNVP8vi/lFQLZHDfm+szfyZrGm6N0CNxOdc6o1JD5GkGrCFoHqWVqlvhA8R9vwjwMTxPNXj2xD2P2HI0OKA5jewTS9Lgua+r5FgsWY2eGBp8WUFOdz6QAr +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +cGe8DWc/z+TXH5eIZ06sPPCnlejxDyXdQgh5XWNRPbE= +fcd26qdp+FAAJ8Un64yfsngs/9D3TyO1zoT1IRKT/a6fAKrRvA8A8L3cZg5sA9OIsb9KhrQ344PFm+9BDoubDwBfi8BZ4dDf4PQcDhpBPY8= +MzOj4ZiBOJDNVP8vi/lFQIitY6ec9KUdnqBwYPj8LW/XB6/o9WZZ8zoxcP5nWFGM +L0eUthVnpkGsmKFAX6d+uHtCZj/pUaiDieVAnXD5pY7Sv65rDuNWilNBbWAi5frQ +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9q4OT4WqICTkepUshfIn1VEuFQwhgGt/4mtj5sBxLljedFEPYuA1FNDwgs84qwYY6A== +ghdF4G8YV9yNjU+KsNcQR/VMWHcF+CTNHIlYTVhUWmgu04/2YZIzDcfqjhf3fFrtoaPuJ1ZAgoFdTrU2n3Bp6CEuImuelVqhbjlIS3Ots6bXPpgIt6k6gOj9Lz+xpaOd +SMMdC6NQONyUThzk8KcNdiEuFChkdyfrXoOuNv8F7GqSdn5UrMO3EUirdrzZ4jGHWbw/HM9MQ7L2CQBSQfvL0Q== +1u+XjG/2+GSQRv6EzCaWRQ== +2yQsUep5WrV3T3htNZ1L8VpIZobQOi6VLLDMdOiUktnA+K5G6RVZfVMSE462fDWBz3hXD5zSQkk0VJk7hWSnkg== +1u+XjG/2+GSQRv6EzCaWRQ== +25O1h/mctlgTNH6uiqc0ANi8TL5k3W089rH3RO7g6ojDONahflohA2lT39BHtPfS/e78VaqMfeHp1t6nySWXuyj5Wi+KAKTin4GN5bcN4kWykvXky2N/hrzdjjiAwDjDtwWmjCwYBVJSOw5y/zNProDMi9lDy2fHRbDB3ZXM30M= +WHkOzVx7seuLmxs5Hu+l/tf51q8WG3vFFhDtLbV80GiYN5VO2BrUQHJaggSEtkf6k+WBi+mP3olNpbaqYCkL12zjClyDut3FRePfbDYMdXclfBg9ZWQ4tAO9uaJjRghMLltswGa/In+ATyIB2lu3DI0YsFOlxsnRD06C3L0uEqAqIbnCgtaeYgXx1JDkVl9nYjTnF+dLAyNooi78VqHcVA== +1hs1FfIPISXmd7TJ/r+Vl7qG4Vk04Nqb7xlEO974+g36ebjqekhI5N1i1YoQXLyn +VmVrGQo2zRokW/ZuO9bN60oVPe45Jqv1Ihdxdz+8r+BoxG1E2W9FjbYTt0EQ+jVH +1u+XjG/2+GSQRv6EzCaWRQ== +zBfpEDEHUpKcgzvJDq0C4efibHCd1IfBfYtJcrr2rCKJrTrkHOWDEuDZ2db0VLXf +dV9xz3yyeXnMinI4Y9Bh+qW0OFHtwajp60c7Zo+aS8zNCfHvOKjfr4NL7ZPuzC6+oANC/f3C6vRb+AeYU/Kwwg== +VmVrGQo2zRokW/ZuO9bN68M1iR/q7rXPheDRGlmMwDTXXKGMyTcbL56i8fKCdpuj21TCZxNzrC3W7EwT7e0FdQ== +VmVrGQo2zRokW/ZuO9bN60pZC8KgCSt5/wO3m11t4ORj72sfxotiDHY4TlDYBOZd +1u+XjG/2+GSQRv6EzCaWRQ== +u9l1ZaXtVaYlyzKhJ32cEHtjbGYyhbGklEQ89tkB0FKhN7oEmT2WsEheTBy3Wo5D6/lal/9CZs8NYkERcwLmMg== +ghdF4G8YV9yNjU+KsNcQR37Z9yu4rJQS2qMMMkR/Z0ATZx7dENZgOS+DLQo8+AgyFOFWwS95Y5VRLCFY4BzR8HZjEccLEwAIWyx7ieM6BEIqip222ROM+Gg28PulsH/cGHsGz0ore9TvdpCv87Bd/C4Hn+lynXanwalY4AWdrZBI1QPCqIJld9eQAkbTs/cv +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9pTEmSWM9G4WzELaUcpESanbkDFxPARX0TRtOSOLCviAsAptbuukF/Ud3XzEa5tzvQ== +0Ey56M3Rq4Z87mMZBzWCkc+uSKtphLKybBPpWa3LqbDSjktYcDOLY4QyDDr0fDRZAgH25cnh7Oc0+hAdqsEWNA== +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7Z+2OVMl5+r4kRRoTlBeW+kf/aK+IIIQHVM99XKaa9eAf1cJVLTv1fR1S38khDnTdD0jiXxx8qZwRyxAhpFGVWaQAj5VgnsfoSyrPK7kJlVKCZn6wEyCL1uds860YV2fp +1u+XjG/2+GSQRv6EzCaWRQ== +YzaD6B908RU9bV8IDcajo6chDSERkl2seXU/yZlu/+7JS8dFpcbiT9RD33d9SqMY ++pBWk5LxptzizUTsHnZAz0YU69PyIiLz+EZj+GV7UHCvthR5xAqr3A75+NFTC33L +p2+bVXgc/OGpnu+3Mkh/HbF7FFV9jaNYd2Ecmtu19AY/zaj2C5YMN3GytlTkrQrW +1u+XjG/2+GSQRv6EzCaWRQ== +Gft9mnUbPRlw7Lcbqmj5nNwiPZVAZaRv5qe+FBX936kLOPRsZNspd3GpR2+3ryGZ +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwrZC6E3RApMFvCYKX/b+QNg= +1u+XjG/2+GSQRv6EzCaWRQ== +jCqMwRKTYZbqj942eGY3g6H7HrJzpJtNEeAiCxVEQsUY+94jjj4jjGMYW5tcQQVO +iVrumwMNPYZ207haO9u4MdLAwVB5xOk7iikXIMTVL/qL0+TBe1COKZNOXz9EWzaa +yXSv/K3mXXlz2KvCo7xU+as167yQDD7w3um0fZ/Zo5HXPyK5XwkS3F80/E1fUXUS7P7Urig0jQFbPt18H7X61A== +1u+XjG/2+GSQRv6EzCaWRQ== +0ZrlxPlHcPlwwdbql/Y0goAKwSbzsj5E0f+76P+ZTI0= +L0eUthVnpkGsmKFAX6d+uDdUftKQ37e2yKnmxKzZfC0= +1u+XjG/2+GSQRv6EzCaWRQ== +dcfAqzXc4801QvnwiMyF+2DhmN8XNNuQlHXbQfCV0Nn9ypOlE4j4bW7JGiceAExZ +1u+XjG/2+GSQRv6EzCaWRQ== +ncUn2TP8ZQ4fZMBYk+CUrdu53GyhDeivQVbLe/Cj8XY= +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg76SFgO0NgF6IQZ0deo2NvyZe9iARvP/9LNBMCXc/GwFKhfGg0oHXvQhP6zj+NTzm2WWDLwmt6i8LJm1s52bqPwMicoRl1tqnBMMlAk6QdUw0= +1u+XjG/2+GSQRv6EzCaWRQ== +v3wpVTLCYf7MVLSW8dNUkMNCsmhq64mdHqB4jWWj7ECOBeTJVXByucVDZMM4OgAc +1u+XjG/2+GSQRv6EzCaWRQ== +9vrQICgDEUtTq2I1sk9wVZxywwiqb4lPgg1tfjsx9ks= +L0eUthVnpkGsmKFAX6d+uLib58fjFLHLA3nq/44o/Cw= +1u+XjG/2+GSQRv6EzCaWRQ== +yXSv/K3mXXlz2KvCo7xU+TwZ8YT1S8ZVU1YkdAJMPPc= +1u+XjG/2+GSQRv6EzCaWRQ== +H1zFwfLyaEvixPsCrENwTVKYo5wX2+mZpzdnjbJxYXFr/VM0UXh2/6nKGuWYcaYRVBWbeqiy0hYB23zdr2qqzw== +1u+XjG/2+GSQRv6EzCaWRQ== +auZi0vV8Th1M5xUz1rxru/S/WPgeQ9D9vW1dYZxGC28= +1u+XjG/2+GSQRv6EzCaWRQ== +S+kfs54VNIP/n32P81eUqUVnMQh0dtT8GLygVFqA+pI= +iVrumwMNPYZ207haO9u4MbOs5zARicyvkD7ENDcmKp1YMhInE1BWOWJaZVqn9fug +HCOe5+CEuFhpcNFnn2isi7n6VeCjSB8p31KLJNkuVQcdsGx+IxWL2LnX2N0sWmqi +1u+XjG/2+GSQRv6EzCaWRQ== +5Bx9T8RabvzydjBVIGKDKF1934sgTtE9dUuydNh5v3U= +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg76SFgO0NgF6IQZ0deo2NvyZe9iARvP/9LNBMCXc/GwFKhfGg0oHXvQhP6zj+NTzm2YCL6arRmvQoIO8XVGEm50kPI4g0OrcTYHXBerclgg+k= +1u+XjG/2+GSQRv6EzCaWRQ== +8nTfjc42y9zZE/C8Mgio16q1Vv6hM/RPQZOPQVLeito= +1u+XjG/2+GSQRv6EzCaWRQ== ++GQhUHFV8V4JMfVXtWjle+jCaSZKCHeDhapa4+6ehPE= +vHI6ym+jnn06PgL99476z6Uwx4qvfe0JegnXA4qmLNl8/pYgnzU4wh0jCA58VVutfY4MrkqewDmJI+ejDGKNQQ== +DzWU5pX0U6cGCLLtlLj3YJU3Thb2TzBJE1jx5FMcRponhGm9owvR/LWtpuV1gqBq+oa72tMTaZWAJjWghMubjMlnHtotD7AxMClsn6ZQftEkzbOCLb9RhvcObU5RYT74 +1u+XjG/2+GSQRv6EzCaWRQ== +BIa0ibHnqUboaK9kYHYO4tbU8n1NMn8Xi2IBkiJce/0aOeCXvbyPEZUd4kMLMJP1mcLvJ0FK4ro+W9D/Ckf1vxbra62mI0mIQL9r4GzXc2fEiUSTo9SLgrEQSCR3x6GJ +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9jfwEyK95V8JMRysAynvXyLfTKMD36VsdSSSKTE/9HcJDu7rBiq5+LZUasIHQ8orig== +uTEK8Ng11d3ix2pA+DD/aeX4oVY4ObR2fwNjSqwh/LbiZHEYz3Vfv/nBYRIy+S7F5cTeG6y7Yv8hmDkyVTnlnnhtgtTOE51bS1FBhGbaS+Y= +nH/wawDoCvT30BetT9hlE02N6v2mxLpWzvHZPo21ucDSIFS/eqxhwPlfTCcpcx6+ +xWoGNWjKGPfI4gq8aHoTfAr5zz+AGV1f1n+brmMBXW0jGAESEjoMrZ7jItK278XKCKp5YiJWlk9daKvLt52yWPoHEl2lNUxAiDxxcNAZE9sKYPkAYH2eydLUEXTX2BrS +L0eUthVnpkGsmKFAX6d+uD2wphTRX7Sm0fBhJDeUo+w= +1u+XjG/2+GSQRv6EzCaWRQ== +ojcUvW+Z2ehEJ6yMJpmY+1BIkc6rSsZIuNzII88JWGre1k6Lx1bHPPy2u2hfHiab +ghdF4G8YV9yNjU+KsNcQR9/VTmoHLDyOw1Oy4SVcpoph4aTe2gOSesTP5XETsPoFunerNRWlPc17+NqkC+JA75UFkhCO+zPstdL+gghr7Oo= +1u+XjG/2+GSQRv6EzCaWRQ== +lMcV7pLM4V6yRWTmLUOCgaVkqzYwnwVgunmVXX73/bQ= +JxCuM+wh8jU3LxN1s2jW+ou1+YFUOpfeMZrEoruVlCea1QWpBOw33j8/t5tn5v48AJdT+ITut52C8bioDjwemj4RpfzLAAJaXatuY/UhYVqPQuhe0gCr9LHvUAET6KOjWRgtNj1V0Rco8JxZ39lG2g== +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +13mReRIe0T94ElNhUup+f1S2jbSZpPAXzAJ/8Q5RExkq8wzwjYROwYGrrxYGMM+E +VmVrGQo2zRokW/ZuO9bN60/Romck7/ZGlIQYh0r0SKY= +VmVrGQo2zRokW/ZuO9bN69y4ScxvyusaIJAAB2obAVLmwbX6ZHhBC9fe26H9hBYT6yFt09qy0i6ljp54zYEzwQ== +VmVrGQo2zRokW/ZuO9bN6+4NNVEiV/ck9Ae+8oDJrc1+c/vMbOREODpMOUcSlpwJ +VmVrGQo2zRokW/ZuO9bN6/VbEMb4SrLRnTT1zhB19ac= +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +Cj5bIZIaHsN6z1G2HZPA70cvamsJbajcxwl4EX/9FVA= +84Vymj90Wzn5yYuvz1pUuMcQc/5cIBQNdMvZ5GFX+Y8q95LOQWt+X47RrsBXSt+WgP/tuYrfYCCeoHJBWCOrdw== +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNsteJ+IsYunNCc1YkZMiZyZU= +VmVrGQo2zRokW/ZuO9bN6+F2CFsALMaXVdBA3HKUr8gd3TOT5slJjO8b2qVlVJ2nEs96wh6X4ADX3XYZ+wXX1kEwKHJWnOgeXeClZ94m9sVawkJRJwPTOldubjLm3ZP/zsEh9r0VBlko5tuNNHcV+vKPu1T/r6BjfMcQZoQJUc4= +1u+XjG/2+GSQRv6EzCaWRQ== +F9Sftf1PoN5P+lgvc+r10NBKW2R+26ei/bRKalXOAhAI236wwjVau5pNFZvrcQ86ZP5625mPyV6IMNdP77Y6wFYZfiUM0wwcV+lAZPlyjko= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNsteJ+IsYunNCc1YkZMiZyZU= +VmVrGQo2zRokW/ZuO9bN6zOXQ+FQiEpTeJqvLADK9HrT9/1LnCYltn0TCdscSKv9twISbOLYeMT1vRGyXss78s5PBFoEZzJkvAP1whVmMo0TQyOiLxWwbFs/lIBqlhqeJgRpHGCtq9RC+bxkSTYdbS9Gqw3uxCF3p/a+1M489Mk= +1u+XjG/2+GSQRv6EzCaWRQ== +F9Sftf1PoN5P+lgvc+r10A845uNSmtFh8fvuV7jkYMquSNXbHEcmCUotgU+/LvzN +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNssk9PPZCaHrfAXriSfntx6s1jAMIIPF1n/P/4sI7mev/ +1u+XjG/2+GSQRv6EzCaWRQ== +F9Sftf1PoN5P+lgvc+r10JxBhEIEKrfIEl/IdIR0YkD/vO2L5FCeUyz7ul+h1Dgt+DHkTHy1qB+TYfB86VRYrg== +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNslWI+TykiAi4ZvrI+lQnfhKNeBcmc3sS9GbiGHYF5shr +1u+XjG/2+GSQRv6EzCaWRQ== +F9Sftf1PoN5P+lgvc+r10ItKLbtXkEFBanzJA32vJBHAq7Hel/C4XvC6BvvRgLu78+wogiv5E5FwxFvvWDpkYa8TxVlShxRa/4gKNR9gs18= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNslnNlTS+MrKg1dEvZwgZC1czCRmLfvB+oTHO+ZsL8czaIHc7hfz5zVlgT13pzRh7dfqtQhu8Gxvq/YLpztx1O90= +1u+XjG/2+GSQRv6EzCaWRQ== +YY5wEo94l9qfo5TPxQmUguohX8FNa5Am+zbgHGpyzEY= +1u+XjG/2+GSQRv6EzCaWRQ== +ZYadN0lxV3av7R0IlSlFc4jHJgL9o9Bu/DN+HyKi8za2mlriK24hCtsUGlPUk0J3kW+jUn/gkPU/z4JksqmdenLF2phYW6Ldki1dFNGSDgk= +1u+XjG/2+GSQRv6EzCaWRQ== +AyzgCjEanvVjy2pbs0WTmY32p5FBb6bDD4pNT8f+HU0= +h1aN8vz6GICRzkv/5tZnhit754oWtIyBrjl7ztyIcrQ= +MEgdP2ueBOmTLeiwlOehUTJtRVa6mPXbpVD4PeJhjtN3sb0eKjvpC7usqe8+r23y +b4OJVZe8QyIpjuTpKXDL9A== +1hs1FfIPISXmd7TJ/r+Vl20P/yfp1nvjLuNOvIMEPE7FfByQiv0oXZLjstjozSI/Vh/jkaFCfsawdKLJVJxKnw== +VmVrGQo2zRokW/ZuO9bN62rM3Hdt9MDIS0VX28CReuLiGyFICWxXFD8oPeJolyenaKARJbHEbsTc4UMK1uKl40tK00PdmnkKMZ5nBWliEm6L92G+EcrAm0slVBE6AHfl +VmVrGQo2zRokW/ZuO9bN68X19+v7hiouS8aCgSgcC310W4EEQk8lcEs0wB1lqE7B +VmVrGQo2zRokW/ZuO9bN65Qt/eNdU97Xgg3MBTwMk+j3nkkrbNVglsIqBXsXS6Ea7wLjLa/uoNTumjIBCN4VMQ== +VmVrGQo2zRokW/ZuO9bN62Y1nlba5dAZkCotwLpvSRLuemdvRbjxhFIOFHlbMlr4dAHNHHRqKWSDJtopzS8RYw== +VmVrGQo2zRokW/ZuO9bN65NYJ2r3bXAWyqzEJGl9lwQYT8w2NGvuw6XdCwNgyeZo6MVXTMlJk/zeCWJ2W8AS9g== +VmVrGQo2zRokW/ZuO9bN6zy4TLHyrcZJLPS8QMGFxO5ThIzCAU9EsO75fHFtqJqZHZV/w+9QNHgul+Vn42PpWw== +VmVrGQo2zRokW/ZuO9bN67dwuvMkl+StGG2xLb7VnpMrHZuj/OmxjOHQoAlFGt985VZT+RbK/egoTQ9t2JOsZyycEHCgSslu7SheQFoVOHc= +VmVrGQo2zRokW/ZuO9bN65aeNv0pgf+lyAFM+EtM/NWcjqFdwDCPzInlXF/heOHj0OvN301aT/WNxYqGZHJX+N9kgX/AJCNgROgpqeVcABM= +VmVrGQo2zRokW/ZuO9bN6wC3E3y2lww84cJpWrUnHxcfX/3HqrtfnuqEhoZ9ufTayVRnLdAcbqSODxJznhaSXR1FGFgRpktwLVHzkeKrmBk= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpsJVPSWMHfiHsVRWFGceRv1LMg1Vu6K3a/hYcINVMcdM= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpOo9L/MB/xTLfSDfaLkEAzADgZJIsZ/PLmUe8Q9TxMHI= +VmVrGQo2zRokW/ZuO9bN66DIsOpHyMnIWw8lV1Va/qW2RVl1rTPqvlLzkCO4/7pn0I7Qr7PGQlmCrGBmQOs7XKn646ADijtbWAL6y8UXhcs= +1u+XjG/2+GSQRv6EzCaWRQ== +7bJ3Uk7aqJsghutxBeStwgvqR4bkEY5QVcOQS/PJlRdXIPN4aRNIb4mVLW6S7+Gt +n/Vg/an64od1JqfYD8zjtuhUx0dEkE8tG18Cs4NQXPxx5sy783NZOSwjs9+QhV8It1fc9kFscbYVv4ylrN2HuA== +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNstN/qHjpTO9iRJHpCbOxN+TMBfwk7N0MXaSimAMiFTOxteDedfb9T+7KvvZj49awNg== +A50UO/kAI17YP7MCbTvBkF1QxWGO4d7iFEjQyMKkG7Et7gIkhal1mvZiTsAv76s9 +mi1SrApdTCLGy389+Ojfxu/q0EBE6R6dRrIZe1CIcNI= +PWKGbNEPxxxpGpe8THO244ghyizvbQZERneppW2EwRWsPuiKc0jPoRLQHA/bWvpO +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKXrTBAX3Epgf61rKlJnxQ +VmVrGQo2zRokW/ZuO9bN64xe4hDTZPj3fRbdVOy3M0gsl+SOQ6YsPkIgqa8pzIFfrtUF7F9rbmTKcpkr49UiGgR1WiH9jlJ0oZUVuBOuyLYIkW5B7HXfTSfUGRS/JZKTzT7S3dJJQ+BFKSyBC2XBmw== +1u+XjG/2+GSQRv6EzCaWRQ== +PWKGbNEPxxxpGpe8THO244DGL576GmijVaKevMErWO119RbSnNIEkuBNIHeT6Ai2+a1pBvUn/ZOnbD69V27C2Q== +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNsteJ+IsYunNCc1YkZMiZyZU= +VmVrGQo2zRokW/ZuO9bN6zYI+LIJJd4lYJHncT3lTUMt15YZBA6AE1TLzwGllt4/isIlE6x6dAiSncL/gkfdvHbREBv3JQM4xVbHvZu89Tb8lVb2E9ns3688cxUHJQkPfBERrxPIl3tJ0LwWB6DEftjYpjjikJvLL6NGBvxA1yQ= +1u+XjG/2+GSQRv6EzCaWRQ== +YY5wEo94l9qfo5TPxQmUguohX8FNa5Am+zbgHGpyzEY= +1u+XjG/2+GSQRv6EzCaWRQ== +lMcV7pLM4V6yRWTmLUOCgZTWTB0MwKd2QFinlTk55lzWhcIOj8c6+ON1lhK0GZAnWhQuyFz0fu9WO0bbW8zmPsX79jRGERAgKC/0xJfqqTM= +9cHtK3WQzcQ0plYUXf7jo5qnXPqri9UiaWkgG1RZ5EpxPoOkZjJYFxNIQYeRlKIlewO1Ew4gZrnV0HQK55rPdfdd5g3ZS8nq2eU11rMtBfvFrVJwT45Q8gjcLpKo/O/OZfM44Ucc7wpE2DceXSzeBw== +1u+XjG/2+GSQRv6EzCaWRQ== +5wsVwKLrIjIWqeTNpSVp9SZAE79lgehXxNsrtfaN8Wk= +1u+XjG/2+GSQRv6EzCaWRQ== +cGe8DWc/z+TXH5eIZ06sPKkzzs973lpkVMCsZnaRSTMykUjo9hw9lXtwZPLAWJxA +rd+FFDwen8tJkKTf8UQJhMdUyYbtrdwamsCwQBeqM77gCC1coFEm67jW5p3AGGTFaEx3k5up907Bu9eID9IsuBdOvJyvhUZnVYJWja7Du7U= +DzWU5pX0U6cGCLLtlLj3YJU3Thb2TzBJE1jx5FMcRponhGm9owvR/LWtpuV1gqBq+oa72tMTaZWAJjWghMubjMlnHtotD7AxMClsn6ZQftEkzbOCLb9RhvcObU5RYT74 +nTtNt3MBgYNjJmpj+SJTbjmFU1ysPkgosk2DIZGJGZANd2VdAYGqOWvHYE/dQ7FE+w+H4dMtnj7ZZFpDBWu3fw== +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANoosZ+xywkOR5dozStfmlYk= +1u+XjG/2+GSQRv6EzCaWRQ== +6wFlSwTFCI9eChOaieSFsx80SKDMht+X6uTKCTpMEqiGD6rExt4ONikcUUc/Y4vZ +YSlit6erKXylrjSZnvyW8KXMcZpc+ySBJipnAYUbDnrciP8x5aX/z37JXnXmIVnXeNHyWzgNPddmP33SYJutS1wQp6BoE3YXMy3wcBQpxeVVEPFYVgObLHXqCksjD1kH +YSlit6erKXylrjSZnvyW8BTZ20UXEQbIv8O2Jnz5Wos= +YSlit6erKXylrjSZnvyW8IK6PYdRu+CyjyKaHL3rmxC4EpZLip/M0Tp3pIiSLRDQ5ZNTqPsqEfAPwVyj4NZ3hA== +YSlit6erKXylrjSZnvyW8CA/pcANPjvtu+uuPfJXIprc2q4WoAuHzJfiQx7JYwdW +1u+XjG/2+GSQRv6EzCaWRQ== +r4lXdc7E0Sbufy5J9lgVE4NJfYY/c+gDUuK2Iz/JCT7wrjo+1KR4JjoPpqwZqCim +o6QhOIN2Sc4SHELnst17uZgjkgwXn1/M+/MspOMRe8qErqHrwASJCbkY7Zd2v7ukVyqyMVloFrPueTGSTHWs6ZHYNZAeUkN73bKXZ7/kk6L5CT68BmkH0qvMlJ+Y0UqJ +1u+XjG/2+GSQRv6EzCaWRQ== +r4lXdc7E0Sbufy5J9lgVE4pbGMag5Hpdnvj5BejBwvCAFwc1r7BOrfAB+laDU83a +o6QhOIN2Sc4SHELnst17ue7UgX3cUWMg9t/Eu2uBRoMOMUD0FIX1etHDPWEW1yOD1hvfuObOXulfzOA0+6HEoS7AmK9fWHTMUeLmI9mgjz3zn+KCwSLstzU6XXjgfy9d3NCkWIa/TP3YMIDcr9/9cA== +1u+XjG/2+GSQRv6EzCaWRQ== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +E/n3ybIG4ZImU99q/cGzcu/5uqUXXASkKd805ti+c5U= +c5CYytli+0H4ItySqYCTUXW313IrB73EGKOLQIMh9jh6rSkAc91jWwLuYUGr3xT9tsRK7xA1/niUlMsXlRBZCYbnJ9NjkwpjP+9aRb12ChiiS8NwbRQCckGdVBRz92NAXH/6NYiWKGxMl9ktE1am8g== +91gsC2HsVrvTm3cwuG3h9mG24K43yYxFv+xvDyViy4N+wu3NdWlBIFLk0b7CGRRA +ERFPcpFJ5x6x1iUKDQh3uhV6XHEL2RzE+8jUFszzqrcdB5G+ZfrbdykWEc9ZX/x1Ejas1dVOGo86IikmY7wyIYS6RbzzvRNojI2tYlyVQ2g= +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7JMh0e6jas9TdX6IgGuKJ8WEfWqw7FpYn6Vks40J9UfmyN68pAzdF2M2fRcS5j2gXOZYsvfx/4AdDv/SBK4oPL4zuaQmzHdrO/YOUGT0+j4U= +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9o2kvXaOAtJwzudc1BuzPhLyb33yC+z5K6zpgrKtmiPS +wgR07xfoapmx6eEnFHXXYo4se8cEG3Kpe+KryARC+5/s+COk9MOO9VM3V7stcJr5 +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg75lda++q/Brd6OdhuAQiMuzDqQg2rcamWh4QNAJqK9ZjVTK9OCy8SvVsEP817o9Aw02erW1GmSUt/X7K9Z4FB/Q== +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9tiBLFoSz+Q8e/mqFDjU1mzBbo/zwQDSGZ8Clmtl4uRF +wgR07xfoapmx6eEnFHXXYryMp/IOpAVMQWeh6o1xVJVY+WtE4kQ2Rf2ZdrFBqqhV +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg75lda++q/Brd6OdhuAQiMuzNINW594ZtQp61oKq9T7kSjfzt6WR7Ekku0esal+faO65KWAUAWl1VEgI4EUAQ2aA== +1u+XjG/2+GSQRv6EzCaWRQ== +UjJYObka+XFwpFaJTEtLqy29c/jy/AjNarMMaME61HK/XJRX5uPdMC4CfV5PJQpfy8h8NtjJmI2duXGfeeNjzQ== +UfmKuMUgPrxICqV/6pOXFeS+VYzMIR9DZH5eKkrr0p+eP/9ecU+z5yDwXL2SDsfj1A3cHOtWQNfH2vUCkEvVOg== +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7Z+2OVMl5+r4kRRoTlBeW+s8qjH+9UWHyNO2Kom9gBLD3Bnl+Jep2pKnim0Pw+N1RsviTmCIC0XUAq4DK4fjWLQ== +1u+XjG/2+GSQRv6EzCaWRQ== +ay0EttHaNaneTGWIcmFa++K73P9pvh2VSzg+P00CwohuGiaVM/ZapxK0+tdbyiZG +oacxXEoPih9NKsd5qduwcFVQqOK1fDx9z+cJtgCHbASOkBZ+RhKAHisYrDl0KzY8 +LIVQpNl3np6j6izGHqNuEtktf69h2ilj8nRc8mvgqL9P1n83aQsTnON2FL3CUilT +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +P40wuHk7MslPmvVoiV0eZS9GHjGsA1BpCAEYF+DyKrg= +MfyKAQ23ShWgHgDOhnt00JYPadq1ebm9qznrho4FZBduZ2u7wkpwf0AJ8OAq0h9d2g9MARQ1tSItZxXz5keeiKcuHp+62maKGp8UGkoCv5k= +CUHO82G5MDpSEiqhNImyKyJX2KNfQKTBGoaamjo5u92wM337YigK+jqUrPMgpjyDCu1A4H5EjlyWyI7VjuJwqdmC21PoPZcLG9J36qN0EnM= +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPh2gJStPMOTkFnyHb98nJDM1TVOasHYBTWXvTvlsc7cuiS6gGwRCJYYPZYXLWU3C2tg== +4nsuLyRybkZg8C3fJCsRPV1NQ1meiKgBF7vL5WvRnVWCYL/jOt5ljw6aC2uSGkFKQiztAIGFXkPKZ1r80cJ3j6yRkFhQ2AmAeo7arPXOC/s= +1u+XjG/2+GSQRv6EzCaWRQ== +I5csN8e3J/KIFkMa5t+SJENrCajRah1QU3uYF/z5CIDRw0MaiyCM2fRi6PpL0QdK +1u+XjG/2+GSQRv6EzCaWRQ== +O3CUgrw2GJfB+mDjH5+Ndpu+WAzgg1kWTsaUvXCjyy00gT44xpgRo/Z8tiUOas3LbAwwk8LiKg0Ys7278kAUyA== +VmVrGQo2zRokW/ZuO9bN6+UDkdyH1jvzci/SO7sIyeGh25hP2YNRS26DPmSNPqGYn9TvWug0oujS37FMzLac1yaEK1mqsx30h8eypGHDFxQ3zVqdRramRybhAQL4WEHq +VmVrGQo2zRokW/ZuO9bN66qOwalg1l6K9dBrmRBv69FxXJyYfs33xPcKtJ4DIqtnOOdfdzpIzeX/v8Eil+aQS9HpUwSxhuII7GhlYzNPL0k= +VmVrGQo2zRokW/ZuO9bN64gV4ZQ0s+PSF46pcysJ964ZDoTYVV0/vmjUytWdzy3HMJJ4WI0MS+Mk2qZOYT47fw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61ojevdazMjTwm0BH811Ps7aAo73Uk/kFpnGR4koCv3i +VmVrGQo2zRokW/ZuO9bN6769Xwxwzwiz/mrK8IQ3GdDReIRW0D+64vbRHuTCTfYoSATpZ/r/3FtrzvGxY2paCg== +1u+XjG/2+GSQRv6EzCaWRQ== +mG4WxLOsQ3e8HrREEjwO64P1IYQHnm+KlCB71ShayaqW/UVcvwq+j8DAec/g4/ZQjcl33YQ3GGaLUJauCOHBIg== +wlLHv6kT3Q/RmtMBN4nDAbv8zySb0rt8J6SFevmRPRiPnxbmTXG0CIobSeUW6QsVLFj6tZcRzTPaAfIuh264CQ== +VmVrGQo2zRokW/ZuO9bN6zG1efnzclkjRL8albVfDO8= +VmVrGQo2zRokW/ZuO9bN68zGYCiKFErhHvD5ZZAc/LfERjPE86j8NiF3cxijYAj0 +VmVrGQo2zRokW/ZuO9bN69jFBEQu7J6GFTIkqRwcgzHpBHAr6I+Sv94b0eCJtyiI80tUk/0PkMCZ2uHa8NQbUg== +VmVrGQo2zRokW/ZuO9bN66h1h1HHXIvvIkLe/lhJ2KGtNZQybFt634kbmBxer6uWFj64VfbtbyAxsXFf5Wn/Bgf0Nv6VQVpnzH2Z5lljXN8= +VmVrGQo2zRokW/ZuO9bN60nAH3WUKkUlT5O4jQaOZzLK+g4pNiP6/gmrX4QzzMwtpxBskRYTZgiVRHcyb9aijw== +VmVrGQo2zRokW/ZuO9bN6ykI5A2Im7PDglfBDvULTu6TRwoYUqLczuFeU4Fd0+hxu/tetbxzjAR35mOyQk5oSf63CeFF7EFJks7IVthR8pg= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wpw5gwG1/dzQpQyvd2GvdOv0cFdkQwVgF8NVJIgUMl8pk4ktf4DbH2S3bqX+8T+WA== +VmVrGQo2zRokW/ZuO9bN6x5ZLr54eP5z/wgx80I/KMw8cM7Musyjgn5+TkjEGWVOC+Qi8ffREikhJPkHHfHR0Q== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wpw5gwG1/dzQpQyvd2GvdNGvlQRjczPfMuNg4zFLM1sEIijzq1RF2KGUQbcWac9qw== +VmVrGQo2zRokW/ZuO9bN61grNev++OJbZOvcfBl4PSLrLDf60Lt2k5dA/Up9aoqxtke61MuczF5mGxNGw+rL5Q== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64C8lBav4E/bwSvzCtdtWHAolLv2+CayKpgVaX+yF1uO +VmVrGQo2zRokW/ZuO9bN60IwKZKFLs+S/ZVoYxLFr2V28ISbHZU866Ehjd8HsPu0B2ZnoRqc8o8ETwBEkWC2kxjSKTUrpP4gmiUaZVPp91g= +VmVrGQo2zRokW/ZuO9bN65qgzI0QqVLALq/Lz0RhHgW9m2ajcOiIA9uWs5v3wkUaJe47zA/PyA1JX76mEc2rzg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm8F5JEe+U2jlpN6wcqTIdB +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN63P6tOJ7fyq9rvq7kKcvasxFIFZVzWgxXq9T4zBQhhYX +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wpw5gwG1/dzQpQyvd2GvdML/mm/8WOdBeLD0Bu47qOqGUKnOr16K9DtoQn04QBDmQ== +VmVrGQo2zRokW/ZuO9bN6/yFPKJzHk83O/jODZ3f03K+2vnvt1PDTSCyctQ1wMHp+xW7ozrnPXLa/wSmrkiiQA== +VmVrGQo2zRokW/ZuO9bN62i9bsSVIvARJmHSP7/WrsjBOt4biVvSUkMpRJ+wzzCyNuc4hHDB2VhU+axCzGA9Pg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmLDaRX9/A8Mp0mJa00inWJ +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklzREPECe+UKwV56rejoQ6v +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN60LKmkQWzeSoQopkz0jYRYmnrfieabeWSr9l8AM4yxl8 +VmVrGQo2zRokW/ZuO9bN6yMdhcWuddd6xv9F64cBYywreWSVvaR5B3yfWI7OYuMP +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69L6dvVGvUfflzQI/giDj3rYEZFK+6sdVcl6KNxSjDS326f6psyHgorB4N+18EIwag== +VmVrGQo2zRokW/ZuO9bN66eoOYxnr9FHt69Wyrd/dgzp6XR6d82YdgshJrx/Py5fPIZGPxv4pJG9SDuEjnucTw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN63zC9uJg841KP8wNNYc+nngPoEpW4dX5XmdII7Ixe7IKCVHtaWfSpewZ4iVRMuWaoLWF+DfYB/guNXjHetd/U5WXSO+GjMdQOR0UDB/Gl+wZr7zyLvmtf7Tf93gY6dai6ggCM8g8z3uYxcyqMWRcD8M= +VmVrGQo2zRokW/ZuO9bN64C8lBav4E/bwSvzCtdtWHAolLv2+CayKpgVaX+yF1uO +VmVrGQo2zRokW/ZuO9bN6/qFtVlcLtzHiyT37CsGhUvQNduXSZfry/woHogT03g5GozglK/EH/TKFGumU1791b/zXvAjURRbnRCH+Civvnc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xL0H6enpMjxIH6Mjvd9EY84p4RmSCGGwgHSDMbD1BTjcBCdCjpVzlSZBpQCMMAH5g== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62I+mLemZ/lLANFFnbNMu2g= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6z+i9MgIszQbZcSmYMZW1kYuYaSdWaJ3xM6cs1JOYdMrxMj2ddQ7B8UxnQg6ul3ioA== +VmVrGQo2zRokW/ZuO9bN67fwPa8UM9+lklG5XL66l1y+oEnsdYpYY3BhviSkIGec +VmVrGQo2zRokW/ZuO9bN60v8A+kLsJksGY4TM4KxnI1JTWXEnIvy288T8Vuot8uWIuQUXypVZTqU3Nofekpx5A== +VmVrGQo2zRokW/ZuO9bN64Tak+9Ph53fp8HAA81fuFDIyeivIgy79fEFVoRnAGBs +VmVrGQo2zRokW/ZuO9bN69qqX19RJUeMAQ5O8TuJCvc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wmNFrhUNmptvf1kK8QmPZo= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67TRSqbKXcvZWR7E1jtoqMA= +VmVrGQo2zRokW/ZuO9bN6yuXF5mdisxBEft+lGUhyG8F5rYlcaT97mqb+GZ6eoQQ5ZBGGZdtVBOT4CUHnG1XMT3dkpnHpcwJp1zJ3xbTV0U= +1u+XjG/2+GSQRv6EzCaWRQ== +1drs/VADXzYV0BLytcpXyXvzCzd3QBLybXVI1a4mLzI= +9uj34YQ9oTVZUgtppiP8x+tmoscxj+/6NZmGBudbiIaCiMoySzRNQyuo/8kM3M/hCqL1p7DINH0366C4+5xMCTAiHnPPOrkfTtECCiXy4n8yPncljEYARQVoiaNzPkzc +X0OWiIeapcNJaErCRaD3DKR70UoHBRazkGuXISOiiA9VJx6mhhC4Bo5mbcp4rpHq +KE2kjumtDruFjPy8ClG6lvGC1Kx9xjtIzJwV3K+FTCs= +kCHcsIktkP5vF9f3GzzLlSczXK/ATFefbA+LM5rYQWE= +744+BgzK3LVsSzMxY5sSrw== +1u+XjG/2+GSQRv6EzCaWRQ== +W1zN2Bg82icFUtPUoqK/2RvgpuIEchVMvfneG9GmRQlw4qXPEJHuO1Hc2cvE97aVrCXYh0x2bsOPwDGhJEQ2dG4TCwC1a3mFXuX6s85eAsY= +xpUfQ2Yni23t7mfMo+WrgA3G1ylslCSvI4KnxvyhF15IAdh69hJzUoYnq58doFZmQJ5tksBHquIzY9szx7wKfXEOY26gQXhXGARuIBrUvDg/MKIpIKTq5/LA7W4EL7dz8Sx+CKKmU0xk9nfoQJ1u+u63M0qUDtqw1VI/iso5AkPtKP0DZ9OVV6E/C8YE9AwG +1u+XjG/2+GSQRv6EzCaWRQ== +OKUra5bY1GoJSomYqJxvwhNloJB8JlBZ/RbGtwTbupfBFwltjPLWQ37DtDKc8DzPDjUOlIYB8QWdbvfktuTRoqswQaXLKJJzzu9ntywIJA8= +wgR07xfoapmx6eEnFHXXYt1a1nlhtXQu0eU8+K9mU64UukT/EyLV3CdSaW72GYbSqkTsdOvPvfDaxvvR6aDbNQ== +32pdC9DD05OE2l0oXazDFBI8qzZPKE3wxp8e0fCnLEr4eLlpJTvCkNugpnFxO3SaGVmfaWnPzj8UbX73vBn/7Q== +1u+XjG/2+GSQRv6EzCaWRQ== +ojcUvW+Z2ehEJ6yMJpmY+38ZswsJGSBNc7eZcLWhwsDLUdSOubUICH63PGhwN4Ht +px9xYmGR/YR+e6NfoBzEdvxf0F7ihFpkhPP06Dk7Mn0jrenAkPhD+dFrRu8svS/N +1u+XjG/2+GSQRv6EzCaWRQ== +t0GuLt0m++J4JSv49oLSPu9CS2ozx2jEbn+Bf8wrMpk= ++pBWk5LxptzizUTsHnZAz6/A8P7Q4Hih05TEgBD36b03mDKbTfMQDzUISSEHfEIIz27Nr2qhocMuTnXAj7uQfA== +uWQGv+LShBH8u8S26pv5nDFBegwB7lws4Brqvxrrp30DC9VpmoRjeNwPIuWcYhn1 +1u+XjG/2+GSQRv6EzCaWRQ== +OKUra5bY1GoJSomYqJxvwi0hu0Gt46vEkfeQ0nYkRXamtAjDZMLJKCG0hGZJPejY +iBlEIelEq1jinkoBxXIXVhJunMbVgB5RBRAl9RjkDCw= +1u+XjG/2+GSQRv6EzCaWRQ== +e91oxhGuXYdY7jQndbCTnr8V385I1qnsEs4YLZMIhQg= +m5EI3+BXxkQEYKYaxORD+kNr1puCll3/YpxseNRKLLX8Bj2kLpq+mQmWMvl49/dv +JBG/gf6gnSFjRryYbJ1kBiSD8y+9MzUhRsXDAS14Q6HAIFV5lPml12zVWvAKyd4larKyEu5RbNsYxAIA7G9Gh+HjQn0vu4nmDmt/K1W8Y6U= +u7v11solBPjrBcBVo5sXZFFxgTGfWx7uaPShG8O+a9c= +wlLHv6kT3Q/RmtMBN4nDAck3bsisbgfoN3mG5s58Uro= +VmVrGQo2zRokW/ZuO9bN6wNOmDpce5aCei5dlVHmLPwk6N8GJ1S5J3zG4sqyFHvj +VmVrGQo2zRokW/ZuO9bN660v+GJbhUwILTBKZ8XkQOw= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+Ei9AA41PX+b7cBXziSnYY= +VmVrGQo2zRokW/ZuO9bN63P+EUZOI45PqES3tmisTPmd7QRbNS6wsDKOktQ9Qwo6 +VmVrGQo2zRokW/ZuO9bN6zZcpHqbLPCfucCocPyjGlY= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTBFSFUBK/z6XBzbXuYHoD9Y= +VmVrGQo2zRokW/ZuO9bN68bptVQRtuQKW/RUj6bM2TTaszdj81ZCGhRHduuV/AXO +1u+XjG/2+GSQRv6EzCaWRQ== +ldwxuKUlVGrrnQ/DH0TE/9ss+kcPMclos3BsGWAs3VHwskH2OyERPPvVRAgIHUb6TsiVAYYwFhIk9NkAztVB6KuFvSbmhoFLMk89ZRmcqAc= +RsEWPElNhlgl6zgKimcjfSbwxiMtoleUSzTRCkP1AIxvWQAirNRm8hcrkW9XflXT +jLPO8fH2aVlnrPDSrKetO+O96gPwjFf9jB/ILvVUxcan+U+sxJWJeGEcW6LbrA0XqYpfvy30BoKAB4GIK4CKc/BtKLyT0lpxI2afDBRQElqZpFLybRrjCwb+8KuH0RdjTyK3EhhQL4X3vkvw+maTo9bnFcGz1OGk8Wlb5b/5RUY= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0U8Txq6Fo/snJ9Tr0v3OFIX6+VFuT5OBCASomg+3wpXPMEgx02UHFWj9NbkE0bb2tBPswtaZ77xcVwGOASrND/nZOAvUfL/N8mhgGkmWSGRidPHCmdI0h+E21CM8AFLq8yZthnUSsJmzgW/anvD3/zI= +1u+XjG/2+GSQRv6EzCaWRQ== +CP4/PJPfBY94m+CkqaLwMTJpzAeBEAaMFD3d7lqw5ek1rheRoPP/eokYfGcv/shA +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgZHHHGZUr4XV5KWn3V2+BQs= +gv64ikBFr+DizEWnHVZW9Bg09e3LSs96RnSDTeQ1bVwOHOuybfAFCD+eEmSo72Fru+D04igpr3dfhJ3806gMl3ENMPsBL9xQkV14Zr4pPRoSu7w9hWUcKiFHM9yj+wbyVxJbC53MD81QSP63IgEEFoe4PYNQmIwaTPzc+UW4ys4= +1u+XjG/2+GSQRv6EzCaWRQ== +0xDm24SjFmBL3vgymT+RygfT8TfQMNNOTMYZrjS6A9U= +ViTQHUTsV656/tTtMADrUqxCJoZGngdZtIxdupx6nyguPlsEnxGX4o+E22z4ctMI9PhYuYVCN9lUwg/g/t0PiVxnNh7bRp7ic3D+rW1Cic/4KwRrGKHSKqTGu0n7MFrvSL5uLm57DAoiQLm6SuzqrV5Bo6ELSXZQN1dAqFfzjWQ= +xpUfQ2Yni23t7mfMo+WrgFEGGpn7jH7cqeEs0VuGAUlAkVv7N8Ztw2Oa60GTRPc7PzARH75k7n91/ateYBUAYsaMPsU2/eWzuwIzumLPppcmxBG45yq3lTvIlRcVDnIemXYDZf16D/+mSrCSWyHeRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYt1a1nlhtXQu0eU8+K9mU67vJcBqbgpDHa6PsN2xocPKoSZplQYphlyDd+FL0CZimw== +NqhMbY8+EQI8zhTG6Zh2I48Ny5mHBYhSF/P635IIeMp6U8x40vHLxmBtCXUxdRRuagrLbnza8Kq8wIPvwaTHsA== +1u+XjG/2+GSQRv6EzCaWRQ== +25O1h/mctlgTNH6uiqc0AI6rCQnw5P2GRWLl7a4W2pTcvbZZtenhSHZ3vHLzp/eMT0zUIPZpeJaKJMcOnEfqmIE0for5m7KXgPphCgOelx0= +TOmaqtOsYFL5TNLmZ8scY9SHeqrBU/qQeXsmQsOrulUrGmLO1VPihVArwJhDHgZRu9RXdMbbX1kJUQkM6q6cumuKXi387px2dYvFD/4VFa+f94gsL+L/btnyGh0L4M2pDkRmuIcwaiqlcsIf4KgnIY8afBHqGYrTDxY/HYae1YY= +y12DYNXPkWUkYFm2zhC8yLLgaDKZ2YBNHmj1IC7i+PWELcnDHBN89kIEVkNjg/6HsSTbiIe+Ni8nZbHxOKP3qBrZc5/BmEwEtAEREOx3CQw= +1u+XjG/2+GSQRv6EzCaWRQ== +ZoKeJHJp0lAyGfZ4EoQS0DlrjPMXcXRhA57y9yFTzJks5qWSK+q/Wu+OFCA5Myw6CGm/EiFmJyKVspUmsur4Qw== +suEadW8BDgV1o9Z5BuKrKHLRnFgqfxX5X/vN7SnA+LjV7r1W48urFvcqh8u97Z9YgpSaUvOjgGHFP8Cl6/J5M0onL1QKwDHXNv2TcnQDq04= +1u+XjG/2+GSQRv6EzCaWRQ== +XZFXH6iM7A192nPekk4XWT4O2Vr0S0DYQUZxBm3mwEXig1jmm4RGVIBZDU+kWW8Keuztv39rUWbD/HIHWpM7J+1IJdA8TMhrsA05ofAGlVOVqx2F3jMieC8Ah+0l1nEWcwL07jfcBx71AIpuA+99DA== +XZFXH6iM7A192nPekk4XWdkmTnpMuTSimxW/K7oQopU= +1u+XjG/2+GSQRv6EzCaWRQ== +MfyKAQ23ShWgHgDOhnt00JYPadq1ebm9qznrho4FZBduZ2u7wkpwf0AJ8OAq0h9d2g9MARQ1tSItZxXz5keeiKcuHp+62maKGp8UGkoCv5k= +Wk+6iLfu38DoetOmRN2a9zm36qqahYh3GbOZBPQLAUU= +32pdC9DD05OE2l0oXazDFKN8+yCaxsULylpVUl/0HV4COaEhRjguA/YfMKVNRN63 +1u+XjG/2+GSQRv6EzCaWRQ== +I5csN8e3J/KIFkMa5t+SJENrCajRah1QU3uYF/z5CIDRw0MaiyCM2fRi6PpL0QdK +wlLHv6kT3Q/RmtMBN4nDAbv8zySb0rt8J6SFevmRPRjW4HnLs1eBZMvfLGUg2BZlUWqZNZq9fjH99w85ckrllEy7IWLdKhn/PLzoLzGjURA= +VmVrGQo2zRokW/ZuO9bN66JMcTYjLDaEhZc5QfC+HcA= +VmVrGQo2zRokW/ZuO9bN68zGYCiKFErhHvD5ZZAc/LfERjPE86j8NiF3cxijYAj0 +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64AVDBbJZFuUixKENv+dwAbqZLdwlfh8v50ImdsaNeGI +VmVrGQo2zRokW/ZuO9bN68r7FXIh9N9oGEDRCUcPBt0qACIF26/Ycmbt+3LwhVJKfm464IkLzDIIBO+eIgYOyA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6/faHgOQ1El6IVDk+PhHdmVnlAGxE8JxvWa3QmTV+bwv7NvaXvaMdSwwry/1nVJ0yw== +VmVrGQo2zRokW/ZuO9bN66OV/0ptHLZhPYaHOj8YRl5A/bXcx6fkZgocgn0vAzTUX9F2uupJTD9l3Mp4LUOIHg== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62XqkKGTDRfn0OWv6sJzm4qF2iqPkaG7HNnKt8BzUtyb +VmVrGQo2zRokW/ZuO9bN69NWUwEfXLkEPnQ9fFWACgH7kgttpjff46QD2i1lkAAk +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61xFjNPd/WKO0XGcnDIhrwycJU+lc9EH1KnlBMnezwjWhKQdxgqIa4H6NCwrkbuAokS+pvMEnV/He22zuiaNSawDzjChYNKzff4zLkxclW0A68pLYvoDxzvdMhPxn5DbBE7pgPXn0BZyWBQpNsk/B28= +VmVrGQo2zRokW/ZuO9bN6xwWsPypa8PYwj7rMZlyoveCzD1mjwcgMRlNoc0rLxxG2LLPlC+K+rlLmKPc06PSLs0QAW55V4Db72PowA9Sm90= +VmVrGQo2zRokW/ZuO9bN6+386QxSyLES4mWeh3pHPccCibSFdD6YtDiEelWHt3EvrfWQCUec9tVFL9nxCv8Uaw== +VmVrGQo2zRokW/ZuO9bN61ww1Wg9v6SQgGomejo/JshqGpgU8Czj3fwjtIZzbuqb +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6z/YDcEJV+9E2VV7BjdV/W0kxu7gz/Ioijd/ulFe6zJK +o2sgQHazONPoJDqmq4d8xsjERPoWH8JoaRDeqyIl36gdDkp5QJvYO+Mla5bvXpyb +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyVnGSQtPVRcl6g4dN2SK/XBs+Q2vUN/x4CB8BkgNPUp2A== +o2sgQHazONPoJDqmq4d8xn+5v2tTUpCO8U6fXW/R+CFqtIFVblZn6Oh1NOALumM+ +CFjPhNS+4D2oRoMxN/lPRA== +VmVrGQo2zRokW/ZuO9bN6+WAMYleJHArv2JdTgPddeNSNwuwagPD3vkAdGjn3ApO +VmVrGQo2zRokW/ZuO9bN64U0AMlU0Sfqpa3U8qpKk6//YVbvPdfGQek6eA3HS2lEMe1HCUztEMLPcmvgKeNUfA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61uBMsTpTUkJJoJUrQMnvvEOHdEUEFa9ZmUMrKXKiVcc +VmVrGQo2zRokW/ZuO9bN6+UAUlGnbw4fLUGyw+bVh4OKevFjAbzmsXlmSy/BSYVgJxKrV1tFpXuLT8/bMGQ81g== +1u+XjG/2+GSQRv6EzCaWRQ== +mG4WxLOsQ3e8HrREEjwO62Yeh/Lh+udVxYsIfU1ww2xnzlp1pf2VhkLSTbXZfs3fQMGX9Ic2TYevn9ztU25MGg== +1hs1FfIPISXmd7TJ/r+Vl0XS7zAjeMWVpHR+5RwPhu+EkNr3Fen8dHIwweuIX6jarYBX0Q/a7jm2WjX1GDQ3cg== +VmVrGQo2zRokW/ZuO9bN64LUNt5+YaZJNuS5WgwZGmdxtbQjBmmmaCjZWUBxtK2obCGRtIZLGrpu9ulIGnTM+w== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgWfgxG6pllgEDPQYhOTZb/cxoDs8f0laSCoj68mmXhkD7ag6tNFtPFzS7gFJMsEx1Ao7D838Ncfm01V7u8qwt4s= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +pFACfzWgrgLSGEmbkBfExw== +yANqf+EL6aAYN/o4EmjjQQ== +a+/w8Se1Yte7BhPKfcDwZNINWOdFKiSbeGWgIgEkr/g= +hIvXLCqYnsHuuByFmIsauTDkvy5fXysrJx0It3A41OvBgYEosyTjKiuSI41dZd6tsBd8tpijCKetDUannEFBKQ== +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+NKPudEm++RMQZs3h+yWJpXSp4aRpFh9wJGsBkiKsHmF +UNp81wVrUcRkYFwXtaRvZm/YdNzLjqLyw/8a8f96hEtFZaYldoJr8vIZPdZBHs0i +c0ufQ97F5najN1prFovGfmBX2ywnfIpvmeWPFVpBmk1uz5P7fyb972YjO+kozxkW +Oi82qoqybzdF6wkjTqWiMzGzXQL7iHiBqRLBkHw+BOsdRkDMJkM7UYT5cqNiuIZd +Oi82qoqybzdF6wkjTqWiM5H4nyX+z7+K42rG5S0789sDzjVbj36Gw1sRYJ87eXPj +bLlixn6igwVr7WeYa5jmwH9SOJuX3Wjo+qPT3h4cv3bHukJiNa9/seXqbhGgVIwJ +cMOsEjqfjGgKL7ZBm7gTSqFnNa4a2DH2wJl5N01Qd+ZqyYmYIer4O71V8EprhJLj +U8F9NA+lyS3OvZlsASE9k9Chr7r7iXeKLkL6J5ppGVw= +1u+XjG/2+GSQRv6EzCaWRQ== +ET7Gy9IHVEYnOggxHAkB1DZscabMsD9ayftZM6IanZw= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +5C9Yloo3ut0BJGfyUPKOOGLitTrk2UkEwS6/wHrPeZTeajG9pp3nw/V1UtbKgm/wWq6pTnMef2sE/Zt8bfNWGQ== +2+RceSNjD2iq0sRzepMysTdyYOYPWGVIEQKLtGrplu9S3WUPrlF4F3K1VySjyz811mbTNYE2csIBAgRz5Ehh/NLrm63S6dUF9fRWg5WIGmE= +1u+XjG/2+GSQRv6EzCaWRQ== +Vew/k3VsEDJyMKn0A5V2yxQvKKRYaDpPXmabxgnb1uQ= +cCKGdtIe7qSNBJ/C7EH9q4NUvSaLVcv4fntSYZbf5PgrCZHFpjHikwuGgCQ7a8ct +CAhn8dRUItEbEErp4w+lX1dpCvDJRAzR51ESaD6r7YQ= +1u+XjG/2+GSQRv6EzCaWRQ== +dskxeNlXSU2ewA7cm6Iz1V7JqWd6vK/Qszm6LObtmBE= +lH/cdKlrko0tsugF0s+VlniaL1RiUd+5AR4RrK/htuqUbacAqR1Ym2pyjeQHc6OZd5uL41MWlXrjTf3rcH0Tbg== +Jlnomz8gC+UNHsk1SduKX6rHscSuN/Jdn9G08BtBAns= +MzOj4ZiBOJDNVP8vi/lFQGYgoB+pZfuVXVBhng8CoNCSciBPOhTxqc5X/8/ufOTq +L0eUthVnpkGsmKFAX6d+uLVEtHv2wNMbXwfSUCMWgaZUxksZvx3hzFGnkfjPDe5X +1u+XjG/2+GSQRv6EzCaWRQ== +LlqFg7Hz2WoVahwCYiFOVp2SFowQjlNc88+tU9937tw8Zjq23jXmPMthMOlW58A/ +dh7w9F2NtRbFZXQAT43UNN7/yW9jpPDxwnQLlmXSh2N40SDWq0C+2vAvknDDgCGKsXcDvi/C3MZbXoObOf+Ygg== +1u+XjG/2+GSQRv6EzCaWRQ== +U/sc+Dp+fKf1xYip5RqQssF0Fd95YioZhS60mEJ3FY6o2r7WFokXUxFd2KBNB9lT +MfyKAQ23ShWgHgDOhnt00A3E2JnCEMGROSk19XPaoa0gcXfpvHAqoqLv8SuAItLiCiTBC5iEEIXHdub9WG7PGg== +NhnIR3Ilo4H2su9/cTNo/GRiB5uAzdvXt51bXn6abPQ= +VmVrGQo2zRokW/ZuO9bN6/6Egoh7rwbwIEJPbUNotp9g6/T7AJqkCQ/JUwwo5DBpYtW2vRgHN+p1M2uayawcABvnATETsC89dTOUwleteq7CSs/+vcTas8hKNi2ti7PFfnx9Oum8Y5lu5leSLRnVP/9IO6OsuBH0sUsKnduvPS2vDCuWh7vY7zRWtXr99njZw7K32ZVRJjXecZTzwAYbgj+IN48y9DC0myUpul6GVEQ= +VmVrGQo2zRokW/ZuO9bN6847gMZFq+X1CVFOJf/pT+mo9DppVDgxkhcyIarAWXGi +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTNvoC4FCbRFpgf4tShNvOpA= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNslPKOPQq+IBahjd/hMJqCiCk+goCSiQ5N0FaLf19fW0G7oB9N5HuTh1mMYm6ZbTeHw== +1u+XjG/2+GSQRv6EzCaWRQ== +o+Mm3OTJQ7VavMzv55JJwTvkG02Ttli9fAvVMx48bZ2YJxgQadT+J2VrpkCJvGhQ +Oi82qoqybzdF6wkjTqWiM9k078F6fAbJat2dZXQQ1xJEY+8BmLFgGoVrmrIJs+DGBf+YDxBHW/HjBSLIPpwSjA== +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX9WKxjWlNEEUlzMaeHc7B8uxI4F0Afj+uGRT7LgIEni9 +1u+XjG/2+GSQRv6EzCaWRQ== +qWX2pEoFG75csIjWZUSgjwJhUAruu9izeIs1xQGcCWxWjqNcvdO8BGNx5WfO1hd0 +lH/cdKlrko0tsugF0s+VlqulEnFS6q2DYv/82WE5ZElbXOBL1kK8Ov40RcDImlSNwtBivvHWT9Gbu/VABsQTeDiQ5N2axHUvhqWBJnpn30M= +Jlnomz8gC+UNHsk1SduKX6rHscSuN/Jdn9G08BtBAns= +MzOj4ZiBOJDNVP8vi/lFQPj9ZL+Si//Xr2gb0mal0K2sWwzWsizCOg/F4gWV4o9h +L0eUthVnpkGsmKFAX6d+uHyOyOwOgs0ecjP/FgbXXBdQVhOhEgvbdWIrPXjxZNQF +1u+XjG/2+GSQRv6EzCaWRQ== +LlqFg7Hz2WoVahwCYiFOVmFZDDcvw5fBwYpBCi14Rqxfn0LfxmtnRzCC82/xhjmB ++rhAblUdSVCAIh3rTkmcASyeZck/OO96uXmWTAZ4zm/Q9T+vnYHR6XQcwVMJL/vb1LCtSEZc1lQZXonwJG9uLxcBSULgn8ihkLKtky1mYbRz8ZGN+59nvH6CZfaYuAAo +1u+XjG/2+GSQRv6EzCaWRQ== +cbvASHjpysrsjdY5RctXmK6Asu7/18oVVR++ajkLB6BcjXD46xvjCBcEqsP3yQoE +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uKc39XUkAfHgY3XYKmYGqPHyhQW5dM5UDcsRKuOpYsPpu0NLY26QfPUKDlXGoiLz2A== +1u+XjG/2+GSQRv6EzCaWRQ== +PWLW/WTodw3tqQQSBBHIn29ODWH9FqA5eR600Y+xo0xCdtgSloxGWdkfyaDbipIWgUjK2+W0phGPSaFhYmz847CjnIC3/8AON2RVQx54W6w= +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uKc39XUkAfHgY3XYKmYGqPHyhQW5dM5UDcsRKuOpYsPpUZULeRclDszb4IVEjXvOpA== +1u+XjG/2+GSQRv6EzCaWRQ== +Oi82qoqybzdF6wkjTqWiMxht3QXhh4TVamJ8+MzbwHaM6BeLsJ8AJnio/H7aSc8KP48VBVkQAlR3qefPdAOfVQ== +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX0vlU9xLPcaearZQOaPco3nVqTdZJNKgT9eVQ7sL5fAS +1u+XjG/2+GSQRv6EzCaWRQ== +qWX2pEoFG75csIjWZUSgj0O7NRHjDcE4EqF3OLWM5jqn7Qhzf6Np6D4hExMDEEd+ +lH/cdKlrko0tsugF0s+Vlh+uyVPTU2Uu1gdMx4iZDrHpuAWG6+Tsy4s4PZEAUc1y+uInpZOSO3zBzsnGLCYv/w== +MzOj4ZiBOJDNVP8vi/lFQDUGZ3T2w79vfb30gIlWgcI/U/wia2WXTY+3v4RNaupM +L0eUthVnpkGsmKFAX6d+uJJXMK7bnkWfNkkHaMuf40v0BJ979EinOdjlaZcSUibY +1u+XjG/2+GSQRv6EzCaWRQ== +LlqFg7Hz2WoVahwCYiFOVp2SFowQjlNc88+tU9937tw8Zjq23jXmPMthMOlW58A/ +gsfPduqwLLZC7wml5Sb/DXLi7x86waTqKKYtnhY5nX1T8vwbOg1yx7kX6DiwXwrfL+TzZuquclIDLq0LjQmoNu0XVwkRjmq9XntioHniSc4AOqE2iBTWcFhpHFjtOoSexqsySAVm27Qv5imvX3H8lXAijnGFLSuETftne4e/OQo= +1u+XjG/2+GSQRv6EzCaWRQ== +cbvASHjpysrsjdY5RctXmBxHhDN33U2ca26UAHq/cDOhaioL0yjMYh0suHii9extNPKdBjmaxeeqz8myc6umvg== +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uDs4ZxXxn1Bp3/ABNZpsCVE7qAgF1C2TYvgYMCYeV5r8+YvocCLcXd1tdEpvG0QWqA== +1u+XjG/2+GSQRv6EzCaWRQ== +LlqFg7Hz2WoVahwCYiFOVhl4zdxkeNZF7Cuse+0PcbBf17g6+8T1GnxOm6vsIgXx +gaoPpiWV9GllNK+GAvAUtpvPcfa1PhfYTPixiCNsuAqwLRMqFKiMs5r1IJtZaKvKBfR3VSXXLYmfsb/Qze7qh01mUYHjNT9tVmbOkbVQYC70NG7rIQ9xfhaLhE5RSWc00LT/X4QzFd0rW+x2/wZyeQ== +1u+XjG/2+GSQRv6EzCaWRQ== +cbvASHjpysrsjdY5RctXmHG82Mxk8G6rvXGlYzbZRl3d+/e+Yyy+/LmfBmrPwBQU +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uDs4ZxXxn1Bp3/ABNZpsCVFym8/219pens2+ofUn3qi39ph+3SfK02d4PPkbpdl/cg== +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9kc7XgKtI0Il8Zt9Rf6Ft60XaD0Uu4a2UrbOgQZf0Z0v +ncUn2TP8ZQ4fZMBYk+CUrQKQSyulUXTZ0VJpJnAjkq2lI6PJcmZ6seWXTmvT18Rq8TygwMefm6lKCBBQ2yTgtA== +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjui35z8/dEnrWz+BRwiynkVz +VmVrGQo2zRokW/ZuO9bN60ZeHVJUIFWfcACCS5B4b7pMow27nbKKz31iyngemwh2yodnGFPvjpb4Ud35FRSkt2Yu8aFIiWIId9spbK1sxpS0ZzZmgC2FAJ5wBl508R6I/HJUkt5iS+se33KMh/6PVA== +1u+XjG/2+GSQRv6EzCaWRQ== +U8F9NA+lyS3OvZlsASE9kzb4sW35eeYQeqJHBbSnWjmga7nQD/669+csH2pROqT9ru6xg+Sx9SeWJRtXGnrTsepUEp/5a4sZetB/Udp5RtU= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm7I187cTv4QxxxILH8LA4BZXZY+H77XcyVwH1IWlBrX+LygAR9eYXn54DoxKWoVbo= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn4epCnnjOljy7/jzvVjgWcYrLQww6VKP0VMUWD+J0DnA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklOwYH3d3YWdlE1InyBQMAURBKsDFIjP2dweE5ztulUubuUADmhyV7/VRSS/c4GFLE= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkkyFIxg24+ePD95zE6A0SC5eRCOMHRv6dtSVuImhmJjKdo4YpvIWb8yLMMif4f7Wwo= +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX0u7Y5xoMOF7x4YZ9MSjXqk= +1u+XjG/2+GSQRv6EzCaWRQ== +qWX2pEoFG75csIjWZUSgj59Dr5jY16aBxRjMyk2yTPNl5QyOGBj8kMfYw1Y5EZn9 +lH/cdKlrko0tsugF0s+Vll1ahjLcp1f0IXErQDWFgfL4xooQZlHIRJBmA9KbC3EN +DLVzBwuz4PMv4vTgkifUTJaKr4VJTYryoHRFaipUjq0= +1u+XjG/2+GSQRv6EzCaWRQ== ++pBWk5LxptzizUTsHnZAzxAJwezCSSmljUZUExfm7Ltyf0Kivt3Uq+r4F0WaGQURcGzsb7jXe4cn3NEOv+qNqGCcaGtT4Kd+raV4ru0OCqCjFS0jK7uBlUY6LjNe2DEI +E5z5ik3n347ddaQbLVWZIxOdAncbCwTxI+lXPyWbsa2mEbmC+3K4uqRRbIUbdZz5/sqa0ZLUM32ZUEi7ZObJP1JYt9Th9StNaKGc2Wq8onjp+EOGVoUwTC7dafiMNw+3 +1u+XjG/2+GSQRv6EzCaWRQ== +dV9xz3yyeXnMinI4Y9Bh+pP704aButokeFyzhY64Ypc= +VmVrGQo2zRokW/ZuO9bN6/mq00FvIOFaJg7q9q2tw7KFggB/xekQ9z79QdcEiHe2 +1u+XjG/2+GSQRv6EzCaWRQ== +3PYuJCOiZH1sjGKjxSVU6PvW4RD/wi9fWsjJqzCWS5A= +1u+XjG/2+GSQRv6EzCaWRQ== +dhTtvhm0jvbK9rVEWF62eq0wSd0tqSAkKyDFU0NloTlGdEGP7cfJRaTTNDqFAjTo +lH/cdKlrko0tsugF0s+VlmqAEmeW8mxbsgnQ+fs+BARMVIb6SsXuoKuj6IEW9bVB +2yQsUep5WrV3T3htNZ1L8TIohok8dxSGY22+WeWcmQzlauL17DyWRvW+qqAHUI9z +1u+XjG/2+GSQRv6EzCaWRQ== +w2QuAWUFVSYjyNKuLr0IKmsft563vBeXEDVw611njJAhYVSu44IunqOLx4+4z/7Ov2Y5r47WX5THcTBwc4oozBLv5hZTs4wx51qgMMdywDqB94p0Uk28e3pWmve/zOF1 +1u+XjG/2+GSQRv6EzCaWRQ== +p1hQ9hJvEa/OWih75Q+PzwZE3ySonErKe/TaKmGA9kHR+n3lh/osL8uZNWfgFiwiJe+mK4RaEeGSIFW81Fuhkw== +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uDs4ZxXxn1Bp3/ABNZpsCVF2I8lCbIb1oQvEpdhcx5jdbysDj6lNmlEcJ0+fbwLlAw== +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX+1wdJ9DeqzVohVbiq5d4Ak= +1u+XjG/2+GSQRv6EzCaWRQ== +dhTtvhm0jvbK9rVEWF62eorElN2oZAqdnjin8oXill+vMtu2wCfGYc8vhb2R6xoL +lH/cdKlrko0tsugF0s+VlvMo7d2fvypDmHSCTjtl9paGccSVAo8TjM0TcxWNXhMC +ojcUvW+Z2ehEJ6yMJpmY+y/kPPkMMDIwbG9jlBcEHyvRbVVvbpVbyGkx7eQsRDovsxUnDVNB96kBbUJIwS6XXg== +w2QuAWUFVSYjyNKuLr0IKkWdSvA2IEsC6IP69ni3Dt9KBDzXq3HV2ZNiJdU7uKha +1u+XjG/2+GSQRv6EzCaWRQ== +qfEqfdUCJW3nKBiyck8MpMSixdVknz+Bevwkyv5V4NhOEJ6ArEN9ueCKpL4IwkP2HvP/82lZC3ajBAgYswy+OQSpghte2vT00WwoZhjZPGizZ30T6wB/LuC1skNFfOVcMwHIgdBzwsA7U9aZh3XpkA== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYj7p4q9ubmfYsOireEOtrHzJOhIN10EiGs7jKzal6Y1T +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uOyslvfK3h1sRo0wYiAq3Uix7ySyfwzrlxK1gaG3GvtU69/JVF6hzcFKY2N7uNDn6eOoH28BDZNZyFZ6jWUHjBA= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwtpm12B9B4UugWr8Glp8zlE= +1u+XjG/2+GSQRv6EzCaWRQ== +7gazhtuMiN/qSCX/IGFtRE7Ee7E+rRtONmW4sNnlMhv2ia0VMYv6WmjhQ4wHKUgp +s8Fw7TvFZZTIDpwLQ1IAjjgB8auuxWg3yZEiM+5W/FSiWXgKYISQ9RWGOE+9wiUJ+V7JhJagl8hF8nJIukTRVQ== +FPr8PM9GCeXhiW+GMolpg7lknUtlxGqMTtUxkIXrE/hClDQMM2jgUA1EnRweVAOB +AKR+OhMsr76k2SU3CFoUdaj3FZP7qeQ4MaAUD0QhqThCvCZrb+nfwpLtAjEO6uFzfkwvaEOodZ+BD9j6W7IlGA== +F8dfsz4u1nt76GApBgNQ3+j+HymT0HCOV+sBsgFQ41+Rupr7aPc5/xikyNGSjMP6JrWTseZOdvg0ZVzNo/D3hw== +4hdB7RJambPsQ0dtPl5R2VvAzymSmu0haJp04GkS5OAMVZlakrsaRowJvil9BL5r/ZhYQWERVuum9UAZsSPo7waqRr92jBas7Nw4DXS+wZDNDLHb76EWCXqTHm6XcNfoyo8ahELlA2cWa7MSEzEPCxkrdlRItTWrzSEPoLeZdw40RCjTmW3gmkEPKnEs+yDHNuLbKyIkCmcOxz0Ge7Otaw== +1u+XjG/2+GSQRv6EzCaWRQ== +jb7/3LG719n78U9xnFeh4LiEHOioATtkfGRHQB81/7Z4E1+uxuEyNUu6fidQ3Vlh +lH/cdKlrko0tsugF0s+Vll8qpo1A+xIOWPsv19uguu02U/gjbpfnFn/7z+3Y44j3 +MzOj4ZiBOJDNVP8vi/lFQJzUZ6bZC6athN10lm+cgOzKJbw4R/m8Ylk/HYpP2jk4 +L0eUthVnpkGsmKFAX6d+uAW2pCR1V0qEjRR33nKvawkas/SxD3lQ+lzmDscg76W/ +1u+XjG/2+GSQRv6EzCaWRQ== +4o9XYhAkGdVgUbgn3dFO+LiWBu1bwE9HbEt1cxrZevXmSOBR5MBG6yS17lBOi+RQ0KPxIRd6O5AfpuyF+l4G+lAqp/9GYcUTN12/RaxY22+t6hCepIkb9zPuhg1fV7iFhOCjsr5LFB1jEt1WAtKEroDi5qtkwn6XTLjS68fzIEY= +1u+XjG/2+GSQRv6EzCaWRQ== +oacxXEoPih9NKsd5qduwcEnzBZI8rfgRYwZgCqWSvBGiiXF2EbB3tngSNuauhDoYsgdypq6PIYZcPcmu4EyANWpT14Y08zIC8K6sfD4vSN0= +wlLHv6kT3Q/RmtMBN4nDAW56rv0fhH2Po+f5AS4a+NMzTaXxOIC5lp0xI5qC7yDv5UOOmqDNpFSWktZ2kEGsnA== +VmVrGQo2zRokW/ZuO9bN6/nDTUc8fBZlCkrIOGMhjTWQFJ/mZhkgafwOqjQycpdrHJRvy/Lrf4S/QvwlUxWvgV2xMN5MNnW8gVCk8DA9ZX83j6lcAeeQbeP1sS4+Hwrj +VmVrGQo2zRokW/ZuO9bN6yD5x1/xKggX5H3Nn57LNcimjPWeOd1OaroKeyMqLvPJKjNl0qn2axptrpFkpe+DgNNz6XloVJDu/NeMg1/r4ww= +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN66/eWUNGeG4mAVR3lxT33mY9hv00sP6Yubjx2uWFFRrcxhXU0VrKGI8w7aahssUcPnbNDt7qcKleBLwUgMzW819hkolaKjyV7mHW6qURhPyG +1u+XjG/2+GSQRv6EzCaWRQ== +J1UGlXkKhKXD36OrXXhN8Gl0Hlqu6NDmQFs2CD4UxLqvf/AxL84caGN+U5x1Yqze72o5+/3hF9hFxJCc5yCHHA== +VmVrGQo2zRokW/ZuO9bN6yRuj5F4YDEZPrdVCrZ9+PSbvDfWooLb6mtaKOP5Nmoi +1u+XjG/2+GSQRv6EzCaWRQ== +OKUra5bY1GoJSomYqJxvwjh+I9EVEootZkQ97c/eZnuJ6XSrvy+x4OMHtZmctwl5o8YIHqt5AX6pXEdWhPQMXEQxFFB/NI2X+p4mC7ymMJww+dFAvYIfbK8Q/EBPnp+s +cMOsEjqfjGgKL7ZBm7gTSt9qlMuDQDwGqeFxYznl+wUCFpSJL/VyD4LKst69KhPPzfBMCjfAHVoyJVkUsfZPDb+bnw6euvuykGIp5tfVRYY= +CAhn8dRUItEbEErp4w+lX04zbFiGxfA+WrDRXUky97CgGgJ2JJmEKblfan1r18+Z +1u+XjG/2+GSQRv6EzCaWRQ== +jb7/3LG719n78U9xnFeh4Pj+0m6Z5MGg63BB8B4QOT6LSlvnXW90vRRgaYgRlI8d +lH/cdKlrko0tsugF0s+VltE4SqdKh2621JGNleXVhKWMz4UY8sZXz/wLt+QZWChE/IzuaRu7FkcvFrdqJGyD3g== +TDAwprI7xAg/i9h0iFymfWWcHaCFydAk8tcEdcDL73rWDr8Ka/k/spyMwBKetJkm +CAhn8dRUItEbEErp4w+lX+EzuCHnfawlbGBEWHqTk+uGXgT+1eSqYPZt+TFSCtBQ +1u+XjG/2+GSQRv6EzCaWRQ== +o2ubjfluOOAAUwr4tWKgGBjOaO8YuesUuICLqFn1uQAxpo6IXEzBQgxm6USXIdsVYboCJIsBs1PZ9ZVbEQP/Pg== +GfEkZ4NfPO3albBU65iCnI/YlKcPp89u0yJejNWUbyxYIWxBIvRXparAxFUFZ65RMwYe7jruETuwMgWPiXvTAtc87qJ1e7SjRH1PdM0vQYjM7CH4R6WtOjU+lzqONjvLWvdnRMvUaKQrNm3vlYCV0g== +VmVrGQo2zRokW/ZuO9bN663a1vcP2Wgp3erCrW2K3l+Zhq8scHLL2chDuHbH/IinjQfWslQYOiACDDq93wJsSQ== +UCx684ZLJmRpC8ptLUtEbjyLKBtfZBD9gsKCO9WFUWE= +CuALK1sA7rU5lII7Ti/IXqhlyIFYgvHVh1fw3vP/cWk= +1u+XjG/2+GSQRv6EzCaWRQ== +2h+GQ0ip6FWGdAf+0rsQ8fDvrbBa/8flTMuXE9aM8dU= +Lovnzsh+ewE4yI30ps0ccHsrcv6YVEoN4GdClwAZZGtiePQovzeF6py/nvQiyOOJ +1u+XjG/2+GSQRv6EzCaWRQ== +k2XarOmIG1CiRPdxaUQRe9O9WW6xe3pzBYyNrHH5EWA= +sGvN55RVjPs2jX2J2XGz5mLHqVQsj2GoejqMHQ5TrUyQPt2cny+eifcMA8WhnTMS +3ivNlzX0mI2/YxGM9KTPAul8g9kOqiifSuOcQP4jtqE= +guxFBhjFCahQsRVLv8Fqb8cPJYUV3OvgvPngmakxp08= +6ykQVsEUWBdxXFRX1gkpyjO1BfU0ZI+BieyV2+JvGoa6ldjiEnfi7ARnbXyQEcBn +Z/cADHTKcwZABip0KMBWYyF/jpCNjEVfN+lxRsNFffA= +6Ec1ekJgpoPVeYsFkNhj1PlAzgqMyfI/vBtMn17NFN7GibBFt+3gpTQVZw6Y5BKe/LRX6o0HPkyDHe71R5tlvA== +Eq3OHWgucsEdNSbXWskpRw== +35DMgoqiI2OAWqY91zzNew== +1u+XjG/2+GSQRv6EzCaWRQ== +pcLElPihWNGsLwTfd6tA2lE1NMYIK66wQ499lXREsjU= +kmuqTLFy1lansHX3M1CzBGnuU/Qo0jPALsptaoIk+pA= +mkTKRMvtKXs11nPwUzmgVWR5v9mUDrVm2zGohg8FRkk= +35DMgoqiI2OAWqY91zzNew== +1u+XjG/2+GSQRv6EzCaWRQ== +q9mtXtgnV9kdkurhYlX/A8wXnZxVZaVAIrHeDy0/aVo= +9L+tZdOzKy5VsulB31CUB3MAV9NW1lp0/DM/S+WFVRA= +mkTKRMvtKXs11nPwUzmgVbb3CHI9+m09CEUTqt9WyUo= +35DMgoqiI2OAWqY91zzNew== +xgQxN9GJBE0IKoADj8CxGN1lyul1PqjOv4/Bd1hgfRc= +UkUvEkND05+1eWBhIO9ayBjKVoAihFabfqBamIqjC1wgCNlliEH4ebmszCSNPEy4 +DXqo/gKgZk7T7yIsX7GGL2+jah7PFR/dLsgGcpoIdAYQZSmwlMufH4BBXIHVoH0S +1u+XjG/2+GSQRv6EzCaWRQ== +dZ3Yd/2oq6SGw68qvdZIUlXJxmXL0j9h/9iEXw+QdZIqwzRnfq/+UGAvYbDHtCgZJDlQHsqj2nzA36KkQGwgPA== +1u+XjG/2+GSQRv6EzCaWRQ== +2fs0ZaO/ujkrB5ZGtLBUfOEc2OluEn7JrUkLakDwnSM= +1u+XjG/2+GSQRv6EzCaWRQ== +lmFcCxjFI69GzGyr8PNzny26IWzn2Oyyvgy2nlDXKls3Cm9j9sZFT7Cj3lBheTPZ +wiN7N+OV8hQjTyTLFwEOPhTPSWuBVH9DTPm34Ue/SkNJvUvwN/sg1ifaEANkQFAo +1rs913dQZjyW0NF/9VHH9b7a/BBh4ncqrLWNX0mhCPO6tSSm4f4QWDsvicp9VCpb +35DMgoqiI2OAWqY91zzNew== +1u+XjG/2+GSQRv6EzCaWRQ== +lmFcCxjFI69GzGyr8PNzn2IFObeqH2EqLordlGn+uk+P+Ou6OCrmcdiOnEiNk8S0 +/xV/Pwz1pzUeeJWz00zuwA/bhcRbX7XCO351Mc4MPrhYxHR04pn+BmL3aSImrb3p +1rs913dQZjyW0NF/9VHH9b7a/BBh4ncqrLWNX0mhCPM+CyTeP02ihghLXjV+kKvu9ZHC9rMCoh+LVy46aD0Svw== +35DMgoqiI2OAWqY91zzNew== +1u+XjG/2+GSQRv6EzCaWRQ== +vrwcmHoAVQ+0ACvkFc1wL1dk9ZDmYZ0qUhXXJUnRmE4KaUg3LRNIR4ENG2Y1qjwuV1opfZGExcSjDAF3pPk7vQ== +SshXV5L5XwzNd0TbkNH+HlawOW2QILRbfjgry4oPbRFhDumTOiwk+JBJVAs9DPkA +1u+XjG/2+GSQRv6EzCaWRQ== +NdjeiTbmdvTutE4pgxeq5g== +1u+XjG/2+GSQRv6EzCaWRQ== +bDQBBLco7OXTZrlp7Oq0/w== +1u+XjG/2+GSQRv6EzCaWRQ== +LQ0KPm/tSx92rgvt7una4iJFdfRqWInfo3MzyGa2xkc= +RUOYtOIVoIEW0c5/LE3dYA== +PWPFJDMOUlKtMQsx8OcdBeCIsh7ZnFqAha4/64XVcu8bqwL/cB21S3QRcrGKoCQlVKscqCa2o05U03/uq/9bfg== +N2AxSv647SE8o7pH02PLTVwRIWX7aZS5Oc2LthIwLPI= +ovGXUor0RFoDFl2o1PzVGx5Dk1QkAGyFN3iYcLwiQvh+pEp81KlSiNFxM+EejhMRi2mqyB4DlzXpcWRTmTKlcjLpYPj3/SP02pYou0Rb/WI= +N2AxSv647SE8o7pH02PLTfcDmty3/mTJNQkhnIIAyqs= +3VHxeGDDuNFDLVMbh+qvyX6Lc0z7K7s8cS5ANjkq/CTG/pfKTi3z60MDknIBb7eU +Q7QVGPDExqjR7ynTbPAdwUaFtejNgF2A7xf/cyGEpXwsjpF5JLmqrT8qNEr8K1Y5XzpvGIP0tK1VuKgb8hm1yg== +1u+XjG/2+GSQRv6EzCaWRQ== +76vFhm0rUQUkhnOJW3uilxfKkyhEsaOiPZT/zrOkEk+TYEZA5Mz6VazGXfI9ln5c0hFwEuCuUHhB0TIAA47v8g== +7AKAp+KkzjPZuOR3b89ifrPTm9hESZmaOReycYgDDByJ8/EiaV0E2tOUyZ8rGQAhI8glxa6A6Be6X/Qa1TSRjg== +9UvoHK2VToOX7RS9aHcpAVa7hUi7kNnFTnR0gBfNtqaOcXjSXyRWFP4Va8t+uwKN +rLgcnY7EUTjJ7/2Z/RIYEpqDLaO6oJm4hr6u/q0x+5DGK1IiL5dnsGo36WVRr+ND +yoFtq8KDstGoS8N/pwvtcw== +1u+XjG/2+GSQRv6EzCaWRQ== ++jegiVkS4cuX6Qy92PUlORw0Ev69pyAjCXMCDqnP1ea34J9ppQP+5FB4HDBFrvzV +2hBg5PiJoedaDi4r6TXwo/u3V8+5GvaV4k3aC6cey6bHx5SdONOmtPgVnbzruWCF +UeNT1dmY702Loi8xix1N/Y9v1944wqSMejOdVM8uDLzVELJpCTeFZXeEJBqWcOdWD0xnDQroen53P/0k4GOLDsw5nT634zOAme/AYIR6+5z2WUi/Qnunef5PO0um3KEV +744+BgzK3LVsSzMxY5sSrw== +1u+XjG/2+GSQRv6EzCaWRQ== +VjdgpwoKK3BTMsCBbM5v1kyuF0wLAY8PmteuSynRDeU= +dd4XXun59JHdLPquVYyhuphAOrPbNRWJV3hO/mdMS6FuSRor8tToju6GeRLlXcrpyhreP1UBtd8SoNYD0n/+iZzFwvPk54imoIVD3LDxKAE= +1u+XjG/2+GSQRv6EzCaWRQ== ++pBWk5LxptzizUTsHnZAz7mzwwOakqLVEwy5kkQvI9DGr115jKDalwXcNQzHi+3frHXmEm9PZAEg1TWbhhfM2g== +p2+bVXgc/OGpnu+3Mkh/HboqxyaARCwkxmEd/dSA3Ek= +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +sQf6NLYGZxk4Y9knQLy60wKwQq7oAVYJkZBcerOK9Av+i0gwwe5coMr4cy8maEpieoh+kv5D9sebn2ggVATijQ== +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033a0p7ZTOs4HLJWM5pa8I8wXg9ZmXXDBuoUY9f/vWJOkWg== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJn/xkDohKB2FOar9iIwA6LOdYh6U4GTMfaPBq0bPmJGJg== +VmVrGQo2zRokW/ZuO9bN65rD4nI0MfwzlsBII0cdPaY53XQcAP1K7MD7XDuIYxlJjHyPmHbsy8Iwat3/1Rw5FgEV295FW1kcTvXh38W7xCeiRxhwCFt3NWJ5IXxc/Hz/ +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN65rD4nI0MfwzlsBII0cdPaZxGq73u9PQD1+IxG4Rbi9vRoYfgsSJoffX9b6BeK31+CuD3umRfx8C7XHcYNodxbsb2PZKIH7aQIbtOnMviAES +1u+XjG/2+GSQRv6EzCaWRQ== +OpwSuFTBAL7XPZ3GOEDUSdpXkC9LFF8I2vj6o010A6I= +O3CUgrw2GJfB+mDjH5+NdmBcNzOXWsLRlv/Eo9RwDRld9W0gWDl+xk62GLY3Qv2DWGKpIX5tFl+cO8m6/hK1Mg== +VmVrGQo2zRokW/ZuO9bN67ZrXFXKDa7npzYMgDzE3vnAj/Qjbl8cXHqReM85Rn39 +VmVrGQo2zRokW/ZuO9bN674wiAEkOXZuouIpywYVpYhDLB80TDafhfVQ/XKGGGDeJpiWjm6svggX0B4Bt5PbI2YjVgoM58ElF2LWkAYx2BU99WBhKDJqjamY3AgGLeGO07lsU9N+nToSzDhHkNAgRw== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN6783Wuxhwb2sxokA50qL52ZhgGhtQ8rehhFhgBhiVRiT +VmVrGQo2zRokW/ZuO9bN674wiAEkOXZuouIpywYVpYgYQo0fVK9J34UXk88uPiaXZt/AGrZOeV9dHK2dSCKRkvDGRgg70LAFEH/SXTzKjMQLINMeDPBZNMrSaMH9JrBMvB0oL/szrCBM73uTaYAM1A== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62IPEWb3HxwK3/8kqu9CPGWphXg606ZeDmzQjj9scBytKgmpCbGm2xyiNSddgD9gTg== +VmVrGQo2zRokW/ZuO9bN6++ongIsGLxOwnmsK56k8eo8nthS+TB8bD6bBvjw8Krw9JM3Y7bF8icw3X/1fRakVJ5Xd2n7ocRlWZIDVddFXqg= +VmVrGQo2zRokW/ZuO9bN60Noo1YoOv2brxUNEwUO+Aq6tREQAFumdXlyA6HdKVMsNjNfvz8z2upx2HVvVCisZOVTbYZ3ZkIMwucvC8Q5XH4= +1u+XjG/2+GSQRv6EzCaWRQ== +8i88MKADknh6ve+bU8Ezb2fMWyOdoYwy/nQLgcNcqQKKSdGUiteGncWnyshAKHiv +YA93D7bVXAwMe11nxv6BP0osBRZ1aDv7C092rWDZE9s= +VmVrGQo2zRokW/ZuO9bN61Jo4bcSJ4kcuwCfHogcpLuF3fcu3K2c+Ai88EDEcUqK8OYgX3butIj1TyW3wFzkIeddODjkw7ZFIoK8AK/m9Yg= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+PvOxVUEC8J2Zv1Mf1MR9f7HAHl5gxCz9Ra2KeHai2W +VmVrGQo2zRokW/ZuO9bN67ySazUSUjnUfpehBMCBA3PealA3xew8Lxgpk3bBd/icpSulRDbeDwAoSCxnkYMWx51OVStf4mIJKVDgCAMD0Gg= +VmVrGQo2zRokW/ZuO9bN638zramoHtIC9zyxgaBuC7/ZiLhR0xQx9iLnwCv5JsN3Cr51CZFf6nG+zuC5p8BkoQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN647dISbCvs+83ZjMHun8Zak= +VmVrGQo2zRokW/ZuO9bN66sK8yway8eq4yOKP0KSly1WR+xW3zjEjNELOJdLQdAJv79Hr1Wx/wGqcerWY9l+DoB0urw8GBJXDsmK9e4zBFY= +VmVrGQo2zRokW/ZuO9bN65msj2VfsvrEWaPQWhn4JLSldG8fzS8ZswPhfN3JoFhjByI1egZfGIzCFcnrbKLglgPzzaX+oygCzvcKG5rVMKy5tn4EdZZal9nnGVvAsWvVIoTVmSN9f1KUUgh2szjwT43NAa8oS7rN9qEIfw6IyNFpiVGuy3e3v1jbt5HaSSU57z6bAkkbp8c2bXEJEuNgwWGv0Z6DnuAGR9qXlwNWFyo= +1u+XjG/2+GSQRv6EzCaWRQ== +EwP7AACOVTDx5AWnAeeGJ1feDvaGNb16m3I20fR33Zthrs03y7dSVKWSc/IMwSfE +L0eUthVnpkGsmKFAX6d+uOW5BVuM+7tcrCiEwvU64kY= +1u+XjG/2+GSQRv6EzCaWRQ== +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +a0q7lWJSm6u4huPzjxOOQdfyAK962CJDhqJrKlaIlmP6OIGqOThO/9i2JrxXJL2I +gC7pmugJ3/w5trYRkDiKMfwbAVR+lU9AovUlz17O5l1MqcYhNqibDOmf4bMkzZtc +1u+XjG/2+GSQRv6EzCaWRQ== +nr7wnSngruPkaFV7FwaQkBxMMcgyUH/Aqu2obbYJTxY= +Xw7xih7AI+PZHSQQqJxLaj32V3SAelBI3FCd01OyUChaTNrQPLcS2sZvi3PUacj3HNO6dGXUqbOAvAWWuM6B/tvKcENk/1nX3JY5CkmfRgBN9leT02AGgRpOuxYhOT6tdGm7oSKlNHSnZhHfbLBDXALS/nktH06Tu1W5usWstt4= +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUCsKQJMKz57hDGzJ13+V8f6 +uB5xMqywxOr6sg2uvk1oIA== +afVXxqs8+P3b7z8Rj2RIMoYkaZYUhApCKoPSV5q4efI= +vXJ5dquhhV65cFgqiJNSzg== +QYg0P6cy0syPj2wysSE7JRFQTPUz7IuQpOIRqTWHI0WnVQT0uQCXbK0ZZ1XOIyWr +1u+XjG/2+GSQRv6EzCaWRQ== +uB5xMqywxOr6sg2uvk1oIA== +O6raHGR22ptSzcLlTA6plZtx0fpb1ELa6w/zugUV/qh0OVH+0+9QY61qCDG6PJYJ +vXJ5dquhhV65cFgqiJNSzg== +nmcFcbHyKAPaGCmiuUmblsmi72hbMIMtxRCY/4Q9jP9co8BCZQBoGkh/gznMPPOd +1u+XjG/2+GSQRv6EzCaWRQ== +q3JI+CJn8EUW6JbRAwDJwwrOBECg/0QADmFnC8JvjwJ2qg4FgMh4CAE8VyU5Ba6Z +Yi0nLXW+2v/erEDLKcVze4KgKKfsG2Fi8DLInjE0+mOp5J/Fx0rj6I25o87L5Fvl +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +Q7QVGPDExqjR7ynTbPAdwdQcxZFfXYvgsHOQLIMaqKB5CxgZn+t1/y/7rMhBEuhw +1u+XjG/2+GSQRv6EzCaWRQ== +1AoMPtvRxz5qNPcY9ZnByWyDRZ/bMTurllH5x7LLfo1VnJuTsyhzH5XlwS5fQK19hM+fHswzD+i6YAXYSTlXZQ== +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyV580u5rQuaq3+Osg23k3+voq97h4Fl38/mV5TWQrrm+g== +1u+XjG/2+GSQRv6EzCaWRQ== +1AoMPtvRxz5qNPcY9ZnByQOS2KjSpVH1eKO7bE2sQaCN/RJFyLGvTSvfRYlUWnSgQzCHaMkR5ElKaWq1r17+9g== +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyVlWHWRVMXlumVg06MNKbE3dS8MvKjLjUKe/Cc7fDtt41mohfLoJvsAOaJGG/Uisw4= +1u+XjG/2+GSQRv6EzCaWRQ== +Rt2DIhU5+Br7TTS/+aIzhpfwe2Za0BTqWj5Nns1H3Ee9rqLAcPVwhSwmJQychDO0hY04rJxRLAJvTqNuJkFn2vYTdrwas6Cgegei39li7Nk= +0BKHdp/dyazawjuP87aIMRN0k85QzpJDqlUPvsirnnsr03l1/pkd3sxDxe0tB9X5FlDbEWbvHulmEeCZS98UBQ== +iDSwwU+SCMb+zcKzWYZQw6oBz9ZY830bE9lNHotXMcWxUfrY904r4W6X3qpocd1fTOUbZEzHpuwndyzVYr/JoQ== +JNr0N0tggrxTj82vgv/JR3L8EGbD2H5kO3ZfNQvi5ua3biG+3H2jyMDJxHZOBqi4rwJ4EUsDekl70Fl/FWSBmsbzVHU9/cfxr3TlwITWvvI= +iDSwwU+SCMb+zcKzWYZQwy0W0kDMDkhisD1Z98pyS+4WMfFyHWdsB1N9GHcEiftJpTRQjQpiO9QLHvSfzl1/ydVASt1EssXgGyPnHPPHSUc= +3gHx3euGCEcAU89Mm6ORtA== +jjL6Cxo9xAtZA7Vdo0ubcQ== +B7gcgvHV8FsVJzB0bc6LYw== +Ivf7313boLfZJ1CPVYIsdUEA9aSYIhQfQWsNgcZHDRivRLeitrddNX0V3e/L0Vzp +yweGob02k0AO/YkOVMp6X+TVqTFIlOs0QRSw8j+c/uxK3JFq7i0Re3GaDOpn0tDa5EerD1d53geTnuKGOcXQQn0dYA6mPy6YflkgOaIa7nshRPhUv2BR6g1KsRmahhE6sNJrzqTw6ieiGfcKOYmlXQ== +1KTMEHdMaiUieavWhiidDJN3EBokEyCm5JMZaMHg5pY= +gPY7b/O18gvpr9jG+iVvig== +jRwywiW0qJerbVBQxbiiWA== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +3BB32YsArGvi5To0fsgr2CiId++wXq5fXwnxK1zJS1sKVRT7xb2qvIk/jQA6vpU2 +0BKHdp/dyazawjuP87aIMRN0k85QzpJDqlUPvsirnntTAp6i4Teq5wK5qX7pRYdLQVLIxY3Yw8Z2tHMiAQJWPg== +jjL6Cxo9xAtZA7Vdo0ubcQ== +B7gcgvHV8FsVJzB0bc6LYw== +Ivf7313boLfZJ1CPVYIsdfWUkxmMal1NW60s+7G4NRtPVn33XtRI6X5yjFme6lP2GbimWo45DSN5jIdUjcaZjA== +/5tg6CGVS8djohtRN2xAIPUdxmfaI+M87JUlZ/wdVLntpRUfykep3JSSHdYJwl1g7C0XEbrVv1IUwuHG8vvOZrPHlSbZupp0KEg/zwecp6hEORrcYvcZ5xRDOhUP3Exr9FwbzHJVYqOClUJJLFGB9k4MGYt3P3M33xmo261zfndjnL14LgWg1Pz4rtwLuB4qJWIf7EynzFZXslV8Oh+FgAqE+5nAOu+e6qtg0BhRP9j4q9Ga6n9d9+4L7SieV8N/ +G7QeY+zhxDXR6kMOT+CbTpc2Hg5tgLRewcArSHBwE+E= +fYHNWwfvnaqvh2J3taNb9y8ULds1NmI0kHUSIa4DZqE= +gPY7b/O18gvpr9jG+iVvig== +jRwywiW0qJerbVBQxbiiWA== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +3BB32YsArGvi5To0fsgr2KV/xrBSiQN5hPtLkKizFfupJnbhHaMsREHQ1q00Xu4v1DIsk0xlDEYIkB2R7DBqCs6ERmQMiWRNi0ILhiSbYxjUmShXEfq4sWRVr0THuB1yzCTvsMB5OmOPtO1JAdpo7g== +cCb3q+SmIZu3FGnZqV9x9uwlbmYxgiXNK+dEKMu2KrRtANfeYUP1wKppgWruOld7o5d/e63j1XMZV+YR15XgXRgOhQNTsSOcMRcubwhervKcU0lnkdimphGOIWUkGNiHcV2k6ADfGA4IQKgmtWkPlw== +jjL6Cxo9xAtZA7Vdo0ubcQ== +B7gcgvHV8FsVJzB0bc6LYw== +Ivf7313boLfZJ1CPVYIsdfWUkxmMal1NW60s+7G4NRtPVn33XtRI6X5yjFme6lP2GbimWo45DSN5jIdUjcaZjA== +/5tg6CGVS8djohtRN2xAIPUdxmfaI+M87JUlZ/wdVLmKgFIRwtU+pmA07/3/kp8auG2zBPo44ye4korlSkP4PBATP2E8VDUWgu78dJjOIvbM3seIHUJXS13bYCUMvqsEVivES14voZBhUwMvlYjpAuuV7J2AlQaZ6oVnLPO+g9BigNz11TAQ1UIi2gSgv9EAlGFCXeVspm++cRMQ7aU/+BT4AIJt3v4TaYPRRtskKjrkXh89iiNoccdtn9SHmmncca3i8PWwYQghY7FfI00taaPFbXBq5kzLL2UyTgHCVdQ= +G7QeY+zhxDXR6kMOT+CbTpc2Hg5tgLRewcArSHBwE+E= +fYHNWwfvnaqvh2J3taNb9y8ULds1NmI0kHUSIa4DZqE= +gPY7b/O18gvpr9jG+iVvig== +jRwywiW0qJerbVBQxbiiWA== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +QH7NV0DTZEFfFUF4o7B+jhE5fPmec+pPjwsuCA6SAfh12Bd/xH9KbjWtC6oPLloGq86Xv++T1m4M1iGHDRUI9A== +wMiVF+RVQuJVQj3n+9Eg/URACPI4AcGVQ1yEY4iIOmzEGJ+l9TaWpFFsf58o1sJxZkn3kVW2ucZNexURGsahwg== +zi8rzIVkr2F99KlemQoOq0K/D/TW5K5Jn96rQe4iFeytYHCH71ssy1dk4G+oWE1J +kDiPYAmVdaQjaaqZ31PehEqdxbwK28N0KhU+QxnQlxvY4qHvVK1kw+21mzagywGKATFm7dwJcsSsbofCd78LyA== +8GzSzmPyECyoWE/uo85qdR0FRPKFPtTU+3b26HuE16an4j2VLYFdgBiFDAG4037JjXQi38NmLzmVONL7ZNWXjQ== +1u+XjG/2+GSQRv6EzCaWRQ== +kgkMvQXTCwh6F+mcyMsGrJHxKHe5twNkkDmRCTdzEWc= +VzUo2AcOkR7s/IzsaL0SuA== +xkoukpxOayWRzNMpKNBZQQ== +T5Yz3hzK/gTYy8E1rqMujQ== +vXJ5dquhhV65cFgqiJNSzg== +s4QT2gUXNI7CtR+xBG9scWrxxXFh4d8c/AqK+6+HXGE= +J3cCF5F5lMJzHabBBFbIO/ygWwiMTjd+KpvovO3cLo4= +Hv0b+60gZXEtM/aOnH1MztAvUT3xkS6j/OfA1Cxl4bo= +kduFpFkLKg9qGyT9ZrvAjALNchpgJk8eWcyLhrVLGps= +Agl71OM9UQtkHo2XveoIWQ== +1u+XjG/2+GSQRv6EzCaWRQ== +FfPd0mAfqb2L4b20V5L3JrqGq3JspkF3cpDgYWe0EfF/M1b9hc9l0kGsXNU6gIVL +mvzsqGDGq7heX5STUWahtjkERaEip7AbHLibPK+TaCs= +DfnFu8zXb1HhdphG17AZ+KycT+FZ5oMtfqHqLoq8ssA= +1u+XjG/2+GSQRv6EzCaWRQ== +t6py7+n2nYrnRcTqYmVk1g== +o8bvh0VBIgvVkzBeHxXlLEPg5hbU7mw8LBVIa0zm1PJDr6qfLVLMoV/qEOA4I58208v7luEU6ZulhKE+sCrRlD2XejWHLuOKH1Vvtpu0beo= +9O5uzZQEX7OrNEGnTQTdKvY2+5FcbLbOKhp2ZN+qIMRYxVZInb13lj4zCz9xO0FtQy1hGQxs1EahTSjkLDbFygWupFGlaLt+iXaU9oe7DCs= +NTfD/2QdEOMSOY8p8LQQLA== +++Emx+f2aZSbtkRPOSPAhoTY6Aa8kbY9SwCojUSy6W4= +1u+XjG/2+GSQRv6EzCaWRQ== +fjmhS4v/8xWhzMXplqU38F6gHgLmEsWhV3IJjzQaXKOL1bWtfuPODGE/wrG0IkfA +DxJiUgt/ReBxNNtPK0WQ/gnIhO5UVevBd25STZFgp7Fp6tLIfb8lpPaxmUTxQIgBMHPXhJYLicparwLmitsLMZ23u07apTK1we81Ju2WNOE= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +4roTOrfENPXMa3wMTcZ48Gd5caiMTH1R/njuvBZLW+D9gqk+dSMmLP+udSxsX44I +wMrZm0BQsU33qqqzWGG/j0XGM9bzMTLDvUiqKcOSVg9+6cgMFTwGpN7NTLmWQRIc +4uSJTxLG3WRqfql+VEh2mKErIioHP277fpu9rvziK6EJtsxOC5pmoqrRPPODi765 +1u+XjG/2+GSQRv6EzCaWRQ== +3kdsAwWZSEEaChmQUD9QZgT6/AIYqAB8Z97VZco5/Wg= +ImBuWrJYZyJ3Z4QS3TZY/AEQdf457fdIEZVYZBcXO2G9wPu4JkLY3xPoNqAPCe5Oo/sxoEBwxfjXvMB4Rv0UAEL0xxRBQ4o/MEj7mnjW9Kf+jWYR0Hd/Xdi7bGWbj9cc +6dgC9QozWIlvE3N2y59t3B39Bl1HqTOFD6k50U11osVKiFqfmAT+qe+ChOCdqWQPTGzq8/eiaE+cyewno8SD59KCD1wUfuCEWC789tE7h4w= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +esZqCbY2U0tDkYEJ9WdOGqH7ZkpKbR9eBdR3DqWTppG2qKjWrzvAdguobZXxV5rPbZWxJbrZFWWemeEno/X0xlcGWSivODQqOPpmetD49zM/Qf2fvpvLsICJPK9yKvmMCbrugvwaXbrasqa+4y7I9DkYG3RPgseLnby09FZEK2z3Li1cpvzM7/VFTIIFUDs+ +O7f6kKV1hliq+ujUlJHLfQ== +nqT6Z0I4DEJ1fpJD0qCwW877uh0YGimzdJbZB5dqeYO5lVcy5AsASWZ9RvOU9OTDabujsLVV8CZ0qutrvF0pdbTpP5vaYDCfmmgPnyooCNbMirfd5zZMkCMhds/zUl5qDN8RfVlBeJ75MwTr6zoxoA== +s2MXk3gaIHoG6PiyucOWEiaaKhwCovxHG6+uDsssb62WLyV8d9tvOhrfsBg5fuLe +5V1NTYeA3RKnDDORgkA4dL0HB3Y0TuSJVSqnvg7bThBAoxwCQ4roE9JUNYK0DkUJ +/HRPF/ybrrEZU2N30BOMAzleN/qbLV2/0JyoBaHaG0g= +yM7LmRhDX4N6be9pOzWVCdEj5U1wvLfZ1Q8LPLd9EbGZggBsaNoAIXWkVIQyvBAt +BWnhzGvzqVPra/cBIsaYUTWwVgnYBiWHTVlANjkJPeowP+uyKjSpMTF5IjW2xUU0LeNFain+ZOa2zvJbDboLFoyyQdE201sj0+hNvhY2S/Q= +661hZf7vhUQ+50okfwfTXw== +s+CwzRPn0zEGm7n1YL21pauz6alw5B6ioQN4tBO7OL4= +4bCaCPES2AXNnKU3FRtflw== +ayV4CzMiV10qa/iRMvYWiARPQ/i7V4UcD78VrjQePkE= +PRRuJqBA1MDLG67eRb7TP0NC9c1/dqtd3DKW2T+wMqjLgK/ncoADl+aLR+GlQgM1 +1u+XjG/2+GSQRv6EzCaWRQ== +cX5hj2layJNfcD945JBsE6qXR7UkO+h5v7X8+cTSunY= +s+CwzRPn0zEGm7n1YL21pcwxb4HcpX5toin6jgGoUat6vgpdG8v/JQ1ANL2n8PR8gnF1vXTymVQt/YhdidCT9bVtfV7vohNTDj3qH2XSiDl5oNPdCUbk83pomb7qngnI +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +QwsrIBX0gHK/V9IcqmQNOcv+ytwiuCifDUSXSwvcOS4/8TfuTNy5MiRCYxeKKFwavmk5BH4nuwVO4mUVDPVuwQ== +s+CwzRPn0zEGm7n1YL21pYNB+K6hRyax7Bx09qeurXRHkg44VCIdGiiCK/QQeUEI +uANEHXj3yvYHbmB7zAZgp/PchJxBEvfjWYUu3WQ8qq2EoCVa8OQAAyDm9KfQxNxp +TF57p/nEanynaty0cxMJMe8X86IfHPI5cZCc8X1KWZVo0GRByrRV87sM/JpEMgYgm9s0T3mtLJzZDeodxn85HDmLVA+KdNMXtTqyqNnLMpc= +661hZf7vhUQ+50okfwfTXw== +bCsu6Ogu/YuUlBRl9FFbjA== +s+CwzRPn0zEGm7n1YL21pYLW+/nCyXStQglZ9J0GvETBggiHm3XFJS89JAuM2S1juY9eNpG384+ce40odcEyG68cHyug6DN1VXNzseOj1P0ivBkBTF0FDa5wIST1WZq+ +1gtsWh8TwXmfYlHhuq8EY2BzNX/G5cPq+FXJJ4QMZYLRVUdRnD9BkTj4yiAyYCjW +TF57p/nEanynaty0cxMJMc5tnLkjVkLepRQ9Dl9WVR6dsyWXghO01NWlFVnCvGH5Js7zHOr/fki2jtE+/mltUw== +661hZf7vhUQ+50okfwfTXw== +4bCaCPES2AXNnKU3FRtflw== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +6iRSx4r0kYhT+dJaF3ntCw== +B/mj7m8tERtuEgm33+3/aA58DPeaWARPCdDHL/8PMqtBZNj76eBSH7RzBLBh29bxnZnqFHqh4ysKRHS++hMtsA== +pw+kZ1ziE7hWMzi2OIcxaSUfawylbhk03ROLv9phMRFt3IVL1ku5MosMpnk9/An71A4NYgfnrUfk+0/ADTR82g== +V0TZHpooT9iGmiNRKGMOR8j/qoroYkDDBE0RA082YMM/isbFmEFbppQ13IPHIEvYllBcpMyEbd5q4hdfevakvRJr8jdpI2TVMN5zHgQLApar/DOvvLjZjn+sNW1wQEpNkhcuPY6NWodfeqdUTLO3kQ== +wp2KLov3TQOyTMSOIUz76muW83ROQ+sHAB1YN4U97aY= +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +JAFfusAXVDjXTUC0nMBtimFCgWcWJbt1pZJNLh5aVidX3mknMVIabpM1gKcf1EzYbVg42UuqVomFzt9wKe1/Hc8jK1ZCgoH9lpGcRD0ihhqpbrYIgZVCjx0g3ShzNitF +wp2KLov3TQOyTMSOIUz76muW83ROQ+sHAB1YN4U97aY= +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +EqkcabguX+DNjJSa+LEtYGutw8NbQP7QsjM2D83zYws= +56W3WYut0WdYk6EvvuhL0kN+XZ0CRW0hKEuxA8hKw64= +1u+XjG/2+GSQRv6EzCaWRQ== +sRAb6ov102JQTBX6qaRghoZQxpwIoEn6Vwa9FsN6SPs= +XHjlJ5QkyFRvw9xr26/0xPscUAEqKOLYM5G/5Um10Yk= +XHjlJ5QkyFRvw9xr26/0xAleb71i55hiyT/TFhLianA= +XHjlJ5QkyFRvw9xr26/0xPrbDD8AmcZjtERDQZm6YUU= +XHjlJ5QkyFRvw9xr26/0xGPMpvTlPIhXh1JMSiAgEn8= +X7OfeH9FO4zPna0tm5rnBd50bCjVbbJMizCxiLJUbQ1wRm2Uu3OwLK0w5iGHfLdJWyvHuwFzGmvWdQSX+HlcrqUSRoeOoVHy8r+gQgsowOUpvvw7AZDyCzbT03n8pbaPeB8n4EpFzzR6R0erB45Fn20MFMVI5Sp6VGyia7zuo4iW4DujJ0bybBgo0pceKt1MvL3tbCiBeqca2TDHutktkw== +3xAddec2urtvyH+TFd6l1ND1/W4Rs+gX8BoEyACd3/s= +XHjlJ5QkyFRvw9xr26/0xKhNG+MNLZLfV2u+MOzvoX0= +oJQSCA4LmnTse1ofrS7jT/Z+dGfPQeL58AJ9eM0RSpi6Wwk7RD68WHkNYmF15o2xp4RDH10ucwfvqO+RvVQKnEY7VU7gQFPQzavienLYhTQZ91LCFisTLzR7eB0KHG8kz+qkHMQ25scob/LXXrbzoA== +VmVrGQo2zRokW/ZuO9bN69klrP5VS1yvWj3KRK1cKibenNx52wZY3VQMgwjtLaHpueDTt6P2pW5CZqiIzx8mFpg3TzAFYjrr+7Zbricd5ekQv0ACYM97FxXgdkD8B9WHblh1Kr0/8k6CzB3TZgDahA== +YH231WTDnzQG3bFilBiqIg== +3xAddec2urtvyH+TFd6l1ND1/W4Rs+gX8BoEyACd3/s= +b4bsEu9DXPsyWIbebfjSoLZ3cTIy0TPH2pfFE2kFWTU= +lyLtdrLJs+Z73tmx1kioONtOKETMncaUX5vBp3qD46esCHncHjdgPwMZa9yh4/TQ +J4Lce4mpTPiXpoeeH1bDCr76PLmrVXGIwuatJprs5z4= +EHif0Y8cXHj0pZ6DUXyAHxGPo85eCMoK37szLhBDJlg= +b4bsEu9DXPsyWIbebfjSoPPLo564fPBg9v3dET3wtVY= +lyLtdrLJs+Z73tmx1kioOIRfS3Ws0rwPUl0b6u5j8lC/2y5P6Iuj8+GDmZDRcXz9 +J4Lce4mpTPiXpoeeH1bDCk7LFwdoNWI7epgouLthuzg= +EHif0Y8cXHj0pZ6DUXyAHzM750WhsoTesLNNLL9OA2Y= +X7OfeH9FO4zPna0tm5rnBd50bCjVbbJMizCxiLJUbQ1wRm2Uu3OwLK0w5iGHfLdJWyvHuwFzGmvWdQSX+HlcrqUSRoeOoVHy8r+gQgsowOUdZxPkywkp/8Rvx6lr9X1J5AXK9rEcHGLQVXeFOJWr5IXfRj2cpHm1MkFmFcJa494= +3xAddec2urtvyH+TFd6l1ND1/W4Rs+gX8BoEyACd3/s= +4bCaCPES2AXNnKU3FRtflw== +YHmVAP+XfXvh6Do8nF7SSw== +zs8IyhC4mp7fdo+QSsNdNVtBjg3/nyvjxRZmGkai39U= +1u+XjG/2+GSQRv6EzCaWRQ== +t6py7+n2nYrnRcTqYmVk1g== +9Cm9j2OVKaoZvBE5ROReRE0glF3xam7vLDZOTh9E+RtMgrLV1Ffv3ctNus/pFF+SbXxMM2Hpng7W68zX1t0sKfYqDqREFCnTwb5SXNv+Vp8= +OQ/PAwNecxnJvYI0crHwNZvNGQCPkCzyhAa19r9kkC7gqCy2F61R8TbjavEfZMheoBUorsxnZGx+6kd68PiVcg== +vXJ5dquhhV65cFgqiJNSzg== +0BKHdp/dyazawjuP87aIMRN0k85QzpJDqlUPvsirnnsr03l1/pkd3sxDxe0tB9X5FlDbEWbvHulmEeCZS98UBQ== +RGJaNRhUc7xniMpOCx47lJjbr9whnQatzbexv5d9E489UTiSabkn84oItSMkcRtxzWFPj5w+xnJvnUZvWuxm2A== +3gHx3euGCEcAU89Mm6ORtA== +RGJaNRhUc7xniMpOCx47lDc2J39/g8ducuD+8z9qvTMs+C/MQJwZopRiCHibzb/HdanJ0eUTAYn/aZi4AR76kOnioW2B/CLcNjsH4Tf1OMQ= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +q9hnzMssrXqcTCLiOgb3Pt4x0pbPvMnHF/Wpas9MxK8= +7VEvsiaW/e4WzKqjRhxdn/J/yts2zO4mzP17OL0mpdxK3iBNwvoOLY4yAXjGgkWQg+zKx8F6CelpwLLAicmA1Q== +t6py7+n2nYrnRcTqYmVk1g== +3XRjvbBj9M6051ma0SUeJyk7qUafmNtsLffgYQboLDhVEnf2H+YRVTNKphXOFqxYH6lpLwsVWGXqUqZPr1pPbOlWVC7MnGu8bgmkBDQFk6Eats97zPgJaAaxTyb4jcIH +JQ4X5JxszsxxqNxCHNJECvya39bVGA+18mtCCin2QPooWOqFlBS0lZRdA0bDssigdruaPmBMeo1QillfT4ilziC02+1HSsGM7U7/wAJMqwmEswodsyZ7nIORZsTFWzfg +VcN8SF/gDhRH3UFk49vbA0D7gn/naFttl+/9F5MtSfI= +vXJ5dquhhV65cFgqiJNSzg== +j4jUczuOnT8XNKeqhEWEhBGJb0aupNBHfpG79rOa1vU= +MOK+RHsT0acZqB9RaYxDHnZ8X5woyOjVf8AcVkal9r0K63Gx5h8/BO4jIIMbIXZ1 +EmQ+qnEVpsnDPjMBQYSK3COgFFzJxnrdwFqhBxfvSHcv6wuAdaZcApglVxq03xP6 +4bCaCPES2AXNnKU3FRtflw== +twlDdKYxDod7XsLjF7TCAdB5t09Kb2peV05+2+gw7xw= +3gHx3euGCEcAU89Mm6ORtA== +SQKIchox1OOH6Vto0u7oEmpXtDLRk+8KAk5trrtYRrB8kx0eK6F6Q1Cw9D/PK+/i+cX0vkt4xirF0b+DcZz6og== +IoLDBLko7iHiCa1p2GnUi5C797S5mivkr1QYG93iYoIHkdJ7WYFywQwaJb7zMpqvWMvzbzUuV5T5bv9hNX/eaNsMsgrmW/nnYnBd4MXshwOZ4c2lU8TNK693pzKapzb0 +/unGt5iSJSQDvBe72EnC7GlgaGpSycw5v/7OJDym0L5RLv/ybCsPlRA8eRAFOrW8 +0H85JoP4BLHDFlJ38WrsgFccHaSvR+EHOUV7ZjPKkTQh1+0pqVfm+ZuUsnx5Fs/JOSnRBRY5Wk1RhPZS7AvqnT7vcWHaqiO+twBztRkvfdI= +u0lLTa3LFnMjc0kFzruvMMgXrrflUH6C4OWAi+ObpmT5t23i583Pd+a6yuDhdLSDbYoxfcRiJD4Ejo2BrsmLDtaCXx3UQ0lVVa+uWPGOVd3xM8vi6tyywOls/WDowJIZIONxrrlTil6/q4S6mEiApLOFkb1szLeIXJvoLJX/kjbRQPPKtGEJUE4tiyx1c3B/VrtUVR/bu7mPAU0y/797a8PLmHdz0tfxS7oXMCgc5Gg= +UwukhuHIWIoHdyFrCKw1NPYBzg+/Au7ChwM47beA7yHm2AtAZziOZndzOSOREOcj +u0lLTa3LFnMjc0kFzruvMFUq1+CWxlY7u+WQFJb12KcO9DXB+RWOdIqEIfy7kqtHrLV/rADNkkJkPdCUofHXb4jULYhXaHOFiJ5eA98Xbjf0/z1ecT4jTdsFquC2Js25 +lkAvgw4IgCnb5kWz9IAqZg== +bCsu6Ogu/YuUlBRl9FFbjA== +/unGt5iSJSQDvBe72EnC7GlgaGpSycw5v/7OJDym0L5RLv/ybCsPlRA8eRAFOrW8 +0H85JoP4BLHDFlJ38WrsgFWCX6SS4vr/ZvqcTM4rmKQ8SgVosb2DnfRd4pB3jmIAX5Q/B7IWF85tCUVHGenYzg== +u0lLTa3LFnMjc0kFzruvMLgSdA91qlq64+C8z/lyMbW/TweccdonCj0Z4bAAlK3EihDuok4VNHOA7f0R3tAORg== +UwukhuHIWIoHdyFrCKw1NPYBzg+/Au7ChwM47beA7yFy5GbCfZpAYjtY3IQ09KDS +lkAvgw4IgCnb5kWz9IAqZg== +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +dDXZXjuwcrIxqy6es61/nyRcKfn1Y2qGovdjM3oOYXY= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +tuabV/M24KrbK72tyVMUbHHV7LOQEXDWZyGMItEqUKQz4yLbDtQMekJg4Qg0sUO9 +1u+XjG/2+GSQRv6EzCaWRQ== +IiX74ldEcGDQXKip1H05bN+167oRPVQ39pYJkFOMTpU= +Mcnw0hYRTOqKGEpOyBgpBw== +UCrNFjv1VaMMEy3B4FzV8obiwulaoWqVBA7sXzlFLro= +VmVrGQo2zRokW/ZuO9bN69pK000FWnjwYXoOGvK9SBFj+eF3B9SQga2bUT2Yydcv +VmVrGQo2zRokW/ZuO9bN64kSw7YXl4tjs9oeHSnIzSwZ0v/i6lIVBlTTpFdDIVT9 +VmVrGQo2zRokW/ZuO9bN65TPyVZdihnyMCOvbLGaxyk= +VmVrGQo2zRokW/ZuO9bN65pxmLHk9w3KGdkwWsJ28DH/fPPmX31ryqpN/GvDZn6c +VmVrGQo2zRokW/ZuO9bN64IqjyidHMp9xXl6n73N1fqCcUsKRbzSuB3kxPgNe7G8 +YH231WTDnzQG3bFilBiqIg== +9qER4YIkDZjdPoLOFvkvNlk9NR3L9JigkekReXxE3xmdtdj7JmGhsYcb7wRb4un5 +1u+XjG/2+GSQRv6EzCaWRQ== +3vDcRXnLzCU8KW6t8SEdRkU3kVEcTs9Bi2k4YRiVLI6/B0Gy5PF44mVjoBtae4hk +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z4NS+dyWIuctgwZb9g2MGkpDRTGNoVTnpm25oSGOXR+RyOR598O+VUWrXhufpZu95HYQpceZrisY7ZoCAUk8wLf +1u+XjG/2+GSQRv6EzCaWRQ== +nr7wnSngruPkaFV7FwaQkNCh2w6Uyjqv8aVxqT7swq4= +U9yRwXmCyj2Iaoh9IwqvfkVUQJ/N2p+VvI84AcFXBnAKFZHIVUgBZEIsD+wVme7U6UDabWZ0t6MdE9zUxuiomvu6z4m8hPlOUrHo1KB2TnI/atz0ZHOdp2aM4khKae6T5yGnfII9BydSqqTahAFOHg== +VmVrGQo2zRokW/ZuO9bN61rniUjnyU8v2a/upLDkgdO0L1ySdL7Ms5h8Hy8q3iWTTK2lHczABdUrvmNWVzy/Yo73PxbYi86+/JNHbqa9xPs= +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUCsKQJMKz57hDGzJ13+V8f6 +uB5xMqywxOr6sg2uvk1oIA== +afVXxqs8+P3b7z8Rj2RIMoYkaZYUhApCKoPSV5q4efI= +vXJ5dquhhV65cFgqiJNSzg== +QYg0P6cy0syPj2wysSE7JRFQTPUz7IuQpOIRqTWHI0WnVQT0uQCXbK0ZZ1XOIyWr +1u+XjG/2+GSQRv6EzCaWRQ== +uB5xMqywxOr6sg2uvk1oIA== +EvY2KCF9uy04UZiPOMpkQrfXmJpmBQQvu7xY803wjds= +NTfD/2QdEOMSOY8p8LQQLA== +Qy3HRQRwJZ3e8l9ZIXEc892h37EtSM9sBXxXVfO9jrkwVxxTpbnz7ByA3FImoth36dvKHUUTa3cTOHIczm9uGg== +1u+XjG/2+GSQRv6EzCaWRQ== +1AoMPtvRxz5qNPcY9ZnByRHH/ref6tnyRFC2B9uN9Yskqi8G/U2RNcwDyCDWIahL +Sj4v6DYFiLE4K/aEoT1l1oYuEhDwke+uGTDzUOi7LL/hYMH0YZRVXWpUvZjA/ked +1u+XjG/2+GSQRv6EzCaWRQ== +1AoMPtvRxz5qNPcY9ZnByWyDRZ/bMTurllH5x7LLfo1VnJuTsyhzH5XlwS5fQK19hM+fHswzD+i6YAXYSTlXZQ== +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyV580u5rQuaq3+Osg23k3+voq97h4Fl38/mV5TWQrrm+g== +1u+XjG/2+GSQRv6EzCaWRQ== +1AoMPtvRxz5qNPcY9ZnByQOS2KjSpVH1eKO7bE2sQaBIBtZjw1YVSGIE4QPIx5xk +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyVlWHWRVMXlumVg06MNKbE3dS8MvKjLjUKe/Cc7fDtt41mohfLoJvsAOaJGG/Uisw4= +1u+XjG/2+GSQRv6EzCaWRQ== +xbDY1Pf/5gTDcwQWw+dIWmRHUmWUyazH68q/Pn9mLlbu0cwrXcRHEh7BBiyFQUBPuhlXngQAO4XFwWijH7hAiw== +Q8Ng/8lgE0TE6lAb2ZrEmQ== +8yxJ9jVfhlWOpV1s5kNCj45HdhVOGsKeUlCwyJhqyrcb67yx6ZX5cxYwnak8iMzZjVgGBplc2r1vDFcgIrSlyw== +lXkyogSVp/PthG2AB7BW3Q== +uB5pAOhcVBu56EIfiydcJfhSuCwTSAnBk0uesto5i2Y= +lXkyogSVp/PthG2AB7BW3Q== +IIVrnJVbyailXHSj5+5tvNLQjFL2Bg6W2kd9iA50KpbFTcZxn/sC9/KvzU3Y7gn5px77DzIHff7nJ6vrNRpHCJNI7d2GcDg4Jr+e0jr+Oyg= +4SIkpRlUAC2uCZaICxRMP/EwXEhokG3OsDHrzyyXPBGcHkEMKzJ9qLyPUQwGJiBS +zGAdgOrTcRtUoT7wXPkM7w== +R9LOcCsedpYlDZdzMp8Et40/Z1VTbO5NcjEaZ8SmirkOiPAOLKBhvcfKFNHC5Yv8y9lyyCVn9feRLW6bQ3wqsA== +zRc9rqL2954JVDVGY6jmezTzEvtQ/kn1zXEsoq0cxGV8m1v6N+olP3EoIdZsVwWn +MSY3FO5VrLzKJIF/dJedrDIIuIYtQDy2Buf22l2TuuKQq/IqbnW8woMAC8X9Tv1gbSVzb2Y2Cw+PkmrDPydNfwaatG+9EZIdXyvUPJ/uviQ= +1u+XjG/2+GSQRv6EzCaWRQ== +rPclH30oEJmGG1aCsaEUow== +P+daphW1KMbcBtDFREt2+OeAZSihF8C+u4QO/Nfn+A/nJ4kHlKx2ahIJCK0lCg0Otvhr4SbskDOjIvOnNxKY2Q== +gbXg2jMpSTnF2ryZp4BBYg== +te8+BPZgyFMg6L+OAX7dgdzWP/+plQBYEGlBCIt23IQ= +gbXg2jMpSTnF2ryZp4BBYg== +6JGYWzgQ7nr2g6aL9XgoeiTf5qXC93FQ9QhL4m+eszFJHmyEnFwADIgrMCTkryhy8o6+9gYPVIue+S8TYOGJfqBoYue7YS+/SIGriaYMRes= +6JGYWzgQ7nr2g6aL9Xgoeh3Czi+TAIJAIGvkK/YMZOdqFd9rIsl3lESKpGmy9GfvegVvWlnF8NeJg7fLuC7VGQVevD2B20qVkx6mUZIePhA= +0rlRB1kTKPNKo3Ah4iFYhA== +Sgq5UjzDFxqKLmpW5b2c3Whahg2KN6BFVW5pJFLgUoiPfw34msEXseZZRYU30dBUxQgAQQ3ozWB00X9AI/kpinRWSdOLQDLgUe8whPQzU7k4SPQoUnuq4YrG0Nv/cAEH +4bCaCPES2AXNnKU3FRtflw== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +TAhxN2R+Pt700IuaaVSuHcgygtjCxWmnITgHeRMkSH0GAXoTLdWcoj6xczFEJSM3wX0sAib7kT3lipQfQicKEI1t+iSyxtvt0MNc0wQBU1Y= +Mxa6u+oWaeDYujlKlKoRg08kP1e7MSD5fPNSCm8KJXyZG9aoB2thmehjQoWrDu5Gjr3+qRDuPGOkP1Pe9a+jVWDlnVqfPGRAaVOhgDSo5cw= +6OayCSS+2n0aZPt8Zt4GPSqTWUlsjIoIO1515Ro+rT9M4Tlf+oDEYOaxUaJkYwZfm8UBL4C28r1dAqXz/33emxdgntTSaDzZJx7JTEvD4wM= +e8tZHNzJI2MUNqqsKeaZfZwb5am3sdEPjNKN9XYbuzCiOWu4nEvBDSfzc9EHTVT6mEHgoGJqo7z93A2GvTJUjPvYhmcisf5krDCG/aqgbNs= +1Z7N+R6KUqEHl4gdueaetQNIyMWeGvmvLBNJLtv9R/FI9cwMfz+G6jsPRWeZeiEN9CH9zDX1hvzd0vGPFuLygBb+hmsCxg90MJbYWI2kD/w= +NhJUaNtTG4TU1FZR22MFcMlMK+FEDdCZJDEp9pJeeGA= +1u+XjG/2+GSQRv6EzCaWRQ== +HWqNEoY5C+an3Gw944UBo+Ag5VhBqmnfaZTY5V7H3/Q= +1u+XjG/2+GSQRv6EzCaWRQ== +REsEDBcn8X7lu6D8frjYCHE9h3P031N3vUruw3A193k= +8vxRGfWjHjYgzqup9Q+m+//rouGfVDxtEDvGpfiz3lg= +JSau+28Jw2AQxXNKzdbnsNuXubL71P0LV3umaY8hsIuCwmhbLi5IdEDwE/bjuxSjjzSGroIv1AT1G5Z93Q+MVw== +8Oo5KVF3BBzc2x3nk8qPOxwpIfOHuMp+ocVy+0AXWLA= +C+Fb7zAf4RW+4DYpTQiePWBaky2T5CzTdjDT7oDx6eMFRzxqyi5UuH7h8n15b3HMdnD2Bg3Oe7b8cwWtlmkb0w== +4aC5cfvQ3l9iGnDGVJAIjEiHBUQawNJJ2JjkxYBR8XV/+yMry60EG2nYyFnVjZC/CAgYY11vf9HM50Nqo8sSSQ== +4bCaCPES2AXNnKU3FRtflw== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +PV/aqPEboJ9Pq3+JHyD1u7IzHrxXfjtA7YbTXPrjpk5/B3hadzV3ftZxWzbQCiLD/JeHfjal5Qu1OygdvRmz7KccunK71voBxAuOdN9YwXdrOrYJvJOnBkhWOQyrrjCyvpF6kP2UPOr8C12g0xJiB8lCdx3/Z5Ce+0M2nenpOb/lH045pH0iQD0HDBz8yVr1 +Mcnw0hYRTOqKGEpOyBgpBw== +K8WxDHXgwdNGovmPv4l3bCxjrGV6ObKbdJZxtQ5auwWViMVgS03Egc754OuevB67 +xIgEw49v8sDhC1CmSU2fOSx7yWEfGg1vKqqPOXad9B/rYl2dcCjk8LjOFZgMAQlM +/rf/6WKYRysX0TuKufaOmCRITfLcrYlXOrBc7MNJUHEExOfwYXzJKsfplvwL5TGg +/rf/6WKYRysX0TuKufaOmK+AKWMpjn4snWLPmIlgbsCcKX1gGDQALcMM5Z7FuyUW +/rf/6WKYRysX0TuKufaOmK9kBkU3JDrz53d2RYMb7sszLzuy/mskIp3Jg+EmLZi9 +rWUZ2EBvPnm9di5UCBWoASJuwkacwLzx7wFiKfg94vlJIWKTQ+uPLkA09UViXUa4 +9qER4YIkDZjdPoLOFvkvNlk9NR3L9JigkekReXxE3xmdtdj7JmGhsYcb7wRb4un5 +1u+XjG/2+GSQRv6EzCaWRQ== +3vDcRXnLzCU8KW6t8SEdRkU3kVEcTs9Bi2k4YRiVLI6/B0Gy5PF44mVjoBtae4hk +1u+XjG/2+GSQRv6EzCaWRQ== +6FicM4CqMxenNhTRcLHhndR2GDv/lntmO+sbtn13HIVkc9B1zI2UX5LpLSXuCPlT +32pdC9DD05OE2l0oXazDFJQn0hVp8gc+C5QQmnaSN9hUoeSYMqla2T0C4R4eeBD6DflhuTwMX8qXxZ+uCNSKyg== +1u+XjG/2+GSQRv6EzCaWRQ== +h6T+tg8vGGKXsOucEwh3nnqLxnNvVWWrXKiZuXIG6qiDLad/tmVy0dEbd+QKhuiA +LZxY+aE8R2cSJ2ihvlvUVNPZkUww7pwaEFmxE2HiqRgvUFPOvzwbam3kaM6W+bNP +2zinJNDNj3eHwjxh2vOuZEGmSIe0J+MoJGW+BU5LSvc+i6kOxPW/AFK602WFK6bmxlQrd1L7hKrseQD8hXIaD9l03R7a4pCf/O07ikZvJk8wxK6ifHj955Va5Sge0npX4uEs3SUXlvxGE6uw1ElPGUxT77d4n+7hcbFkRuHmhec364MSr/AA+eEve/0xQesC +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z4NS+dyWIuctgwZb9g2MGkpDRTGNoVTnpm25oSGOXR+RyOR598O+VUWrXhufpZu95HYQpceZrisY7ZoCAUk8wLf +1u+XjG/2+GSQRv6EzCaWRQ== +OMWgmFohSY+HhGBycmwDAPvXi4Q/Detqp8NaL/+BD8Tc13tXiek8NNFjWVNHqANT +D9TdMirjWSUZlIBcDV7TR1j/eU/Bo1+eiNZ06muakZDw2DUgSvE8J/QU577njUb3 +CAhn8dRUItEbEErp4w+lX9tO1yhDltqrpu/1lMlkX6WpztWzmtAW/PHf+d4uuEHbSZnksfwIE2PpTs1bkSwI8yS0ZD4BNKZ9M6AWf0p4ce0= +1u+XjG/2+GSQRv6EzCaWRQ== +OMWgmFohSY+HhGBycmwDAPguPHGvuWdhpr8O7kAaTabHRi1DhwdeN5s826/klvpT ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +z+dYlwdfLYFVN90Ks4B0y61Rwolq6gKCDi/AKThfddItCsZ63QlGvJYc1Czo5YjdEWV4KCAR9RxFK39nwTeWoQ== +DzWU5pX0U6cGCLLtlLj3YP3wnqb4eg2zQDwRoDEc7R5c/iv9ijuULunRt8jzWUq1YnQQIlHFIYZ/r4CC6y4YEK6NI4B3ZccourZeOw8fL3c= +1u+XjG/2+GSQRv6EzCaWRQ== +yXSv/K3mXXlz2KvCo7xU+as167yQDD7w3um0fZ/Zo5E9JIjyB2KBd0obBhq914c8 +1u+XjG/2+GSQRv6EzCaWRQ== +0ZrlxPlHcPlwwdbql/Y0goAKwSbzsj5E0f+76P+ZTI0= +L0eUthVnpkGsmKFAX6d+uDdUftKQ37e2yKnmxKzZfC0= +1u+XjG/2+GSQRv6EzCaWRQ== +yXSv/K3mXXlz2KvCo7xU+deCM883gaWEi8LmG1e47+IypQlWwO46ASs6dnJht4QKgac5hZEMhVWDaMdCouYTSPbZmJqexrkts6twefQ6p7Gm4+8m3QD27ZBgJCBqCcDc +H1zFwfLyaEvixPsCrENwTQh03kfRdd0G/qa3bdyiewG5csj1cVE56lC+O/fo/7FZU3a03qFsh82HJPq3LbpdWQ== +1u+XjG/2+GSQRv6EzCaWRQ== +auZi0vV8Th1M5xUz1rxru/S/WPgeQ9D9vW1dYZxGC28= +1u+XjG/2+GSQRv6EzCaWRQ== +OMWgmFohSY+HhGBycmwDANQZ//yxLFZ2NZt0XkyM8WIxo3CYYe1psj4hyT7J3zZH +6Tszs5q0lk7xuZNv95x3DFnGucP2++Lm8QD+AuJ6qu0Vd0ZxpkaECa5kzK/6+Q0Ye2coeOggKVMjYn7Wus+S7A== +RqVoQzk6IR4JMpR/e8KmeBBBq50KHICX2aiKgGPkxZtJ1GPEBK5yGFwxFArBEhdUBSDg5+jJKgJxzOOU/SywVg== +fP11mcmXn9udwkOIYV19lXBgr4l37SABb2pThLc8BpwqmrPa6pfPqn01jxyPm4Md +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyVlWHWRVMXlumVg06MNKbE3dS8MvKjLjUKe/Cc7fDtt41mohfLoJvsAOaJGG/Uisw4= +1u+XjG/2+GSQRv6EzCaWRQ== +uPpiLeur2btrOEQRiBLfQigEnPc+SEYY+OKbGWGE0exRhzUKakUk60feja1CydWa +wXVIuIhRYsvivxjKWnaJzZhpBbxw15smk30jzXwsUOY= +1u+XjG/2+GSQRv6EzCaWRQ== +ReYQo2pn9rmJzmBZKqwu+xVbpre4IpoWWtGUSQVnByM= +SdiasT2hKhjFSAeRn9qV76o2NewaZrMKlDzTFPgke+MHJZQmVbLT8ofsTgC27SZiSGfeuUucpHiplzLvB4l7sw== +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUCsKQJMKz57hDGzJ13+V8f6 +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyVlWHWRVMXlumVg06MNKbE3dS8MvKjLjUKe/Cc7fDtt41mohfLoJvsAOaJGG/Uisw4= +1u+XjG/2+GSQRv6EzCaWRQ== +WpY9ZtIiA9ABEnbVj5DyJmWVcpFCUNIJIYnEeSA4FHWNeby09RxyUAlrL5Y4Wf+O +1u+XjG/2+GSQRv6EzCaWRQ== +avI5joWVv4iCIn016GtmLjpKF/TDmYZ2DkiySqe/TD//Ce16V20cW357y28WVVbL +1u+XjG/2+GSQRv6EzCaWRQ== +rVtuMq2z2rZvAFHgd8U8Gi7grSuEbMXjWt3nlf8Yt6EDzprHxt7hTAe3nKttC/hh+hg79ZsiVa4gNMJAsH/vbQ== +WkD9i24WL0SHRwWpBC8kWuzCHz2N4m57yrY5+Emq+aI= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +2i6x8wBckU1qBAmwxiL9oph2Y+6CCZXuys0610hvxNk= +WkD9i24WL0SHRwWpBC8kWqdZ/bUUC3b+1CuNif8RhcnZ2x6OMDL15EXevCqgszDiJMzZgLuir5K22zDwGQKCLQ== +1u+XjG/2+GSQRv6EzCaWRQ== +xYmaecvol+c9YasGJNvV/qhhNEUjcZwFfZDtZJa5DfY= +87Ms4TywY4z4R2LZYVXhx5tNMGfPRnY5UNEFGdEmg27+AACpFoxPt8UVeo3Xy+Fc +4bCaCPES2AXNnKU3FRtflw== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +hgaVJJLhwzT4fPjiPJtX3SYFhdem66oTfVZrP7bYjY01lCfhZRgcHjp3EKhAUQ1n +1u+XjG/2+GSQRv6EzCaWRQ== +t6py7+n2nYrnRcTqYmVk1g== +lX1lTDNy/dioNNWGwt7Vn1YrrlE6Bz3LPQLoIabgSvavE39QR33gtj03mEz2ZXNYXaF6rebKbI0fVwTv0fJmPQ== +LqraSsPmAErNgEH2eD4t4JNa4RV+xYcfEX5LTv+p9xm4KjFmRq2FWm5T45J86X6MSQjyxl2iOo9KgUunV8i1Pg== +xF643vIXkxvpAtwArjcNE8WEuf+lw6UUX2VFQYnBrDXbtQcthrh3TfSJrvfJc8PO +vXJ5dquhhV65cFgqiJNSzg== +0c/SnHA15Lp8idrmXI1HInrecnrdbTTjs/InHLqYQv4= +tsB4P1bMIORXogy+7KnvWagc7KIWmt4/itYL1MGJdYw2Q/6Jqxy0BW+BiBpb82xQ +GXXSXcQc7VKFoarv46j5oI7aSXymMmoaJG7iEnmnYD/MYm6jg1qyw9mDJWe+yQvkjNWG41Pb+o/nVXsVYQJcVw== +5atW9Av1Gc1JH7IpyAg4UfrAD0UAtbWI28bsXBSnCgMRn8P0KMN/8G9u1DF+ledd1KFZwf7ss4lHi9NEEY1vdQ== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +IiX74ldEcGDQXKip1H05bN+167oRPVQ39pYJkFOMTpU= +Mcnw0hYRTOqKGEpOyBgpBw== +UCrNFjv1VaMMEy3B4FzV8obiwulaoWqVBA7sXzlFLro= +VmVrGQo2zRokW/ZuO9bN64DWW2h47Z2bn9xDV8evWEGtTLYAd70MokLPjnkeEYrC +YH231WTDnzQG3bFilBiqIg== +Q3VFEGn9TH87P5446LMfE8uD9OcqjCZCzl+dH7oa+ys= +1u+XjG/2+GSQRv6EzCaWRQ== +3vDcRXnLzCU8KW6t8SEdRkU3kVEcTs9Bi2k4YRiVLI6/B0Gy5PF44mVjoBtae4hk +1u+XjG/2+GSQRv6EzCaWRQ== +ruTTNFTdswRs4Mc+srnRk0bG4n/8owB9YurdUoxgVWEHRPulP6ItpyVe7Vr7iDkO +1u+XjG/2+GSQRv6EzCaWRQ== +3JQ3q/ROrvwRm8WfyA+npAARKnASum6++D50zIpE/R6AvQuG/3wkscYmXT1XQloO +D9TdMirjWSUZlIBcDV7TRxhh2KHeKJ1VsN3W8maNQ37B426ov12fcLO12TdqnkqQ +OocbHHgFkR258yFUzxWbR9rwh2ylTC2OU8qnmIhXBPCocYkILEh+yrCdZ+EhH7bM6wC8z6w0bDULnLKekRi+tgZIzVMMVWK104cvnr/TcWxP+vV5j5PvknTH/DuUIP2ltXPJg//FIOJf7trKB8PfGQ== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYsBNzI8ySI29Nw64a94cEffbHlSBlXbGfuFOOeuh89TeM10ryA7G2otgu37lFn/HHQ== +L0eUthVnpkGsmKFAX6d+uBrpNoWGSfoffH/xD2VynzU= +1u+XjG/2+GSQRv6EzCaWRQ== ++pBWk5LxptzizUTsHnZAz2/Ryv0b/Ufe8w2TCjmy7OcFJCyKfbUntURDtOc1cFdf +E5z5ik3n347ddaQbLVWZI3KmUxpbIbyuQ3XX+9hJJ9/hJqDjXbqp/uuiRB1b1MGgwnb1DfSuE3JTnvMrshWrp40nJ9hQ670vJj1aiXjBX1BlzyGUtrO9cVUkddVLmJh6 +1u+XjG/2+GSQRv6EzCaWRQ== +dV9xz3yyeXnMinI4Y9Bh+pP704aButokeFyzhY64Ypc= +VmVrGQo2zRokW/ZuO9bN6+9JtNWtCjOEM5vrdD8eHUecs2LoaeesFMLbkty+ImwS +1u+XjG/2+GSQRv6EzCaWRQ== +4hdB7RJambPsQ0dtPl5R2bJk5yDbOboCJXKH8r58L7c= +1u+XjG/2+GSQRv6EzCaWRQ== +S+kfs54VNIP/n32P81eUqYWl7+7t/Z5112WhM6MBUhQ4I++QYoWH8PI6U6TVLdLs +g+oKTl5ncG77t0ddYxT7iINA5lehid/0FkWDJig2hvvzJtlry5BXYxkw4C6Rb1W6bZdU0EI6JDB1+K4kgDb/MCF2TVqWazgoyprzbiO+gU4= +Z8UsPk1Q7HtwjRd4g01ryw== +8G3AhHvwQIC2dfce6V5Hi+Sz3pSulk3ZPGPOORUFimqh/+3hiDvQFR5kSo57EELg66iO3UpXh9PkPjMeuVbn9LdU2pNhzDmLu2cIoKce9Ho= +Ys4bozvJBgtB7q16qcvtB4iNMB7C9mpb+KRQ/xKMgCE= +Z8UsPk1Q7HtwjRd4g01ryw== +/6nzhTiQpSJoBsodavJSvhrnpsry7VIjV0S0SBrQKxg= +03QGGoLai4/2Dt+r4/Z7bqT22Tv5nhkPQ4c80/zB6Hk1HlcVBsxuEVamB0M9fr7kF2oLuG2KV1ofJX8Fwgqh+g== +N2AxSv647SE8o7pH02PLTTxQJZyLiFQi3ZKpaS+0d+6+cMXYt0/6YMi9vJ8FGxm2NxYyXp60JimXSdNQmcuNxg== +L0eUthVnpkGsmKFAX6d+uFnEdtTQvGxHWVdWPengwM8SrOznNDd+JSKhY5WBoxIAhZhjHlAc5QQze4ffdB4yQ4M7T5sU27auKKLvUmePwgTP1YBnZFC97G0Kn3drNW79njIrqbRsrvggaPnHBPiWZQ5OhFA+gDJ+Zm7ZnsVYyX0= +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lXyfKXxXDhOK8Y13cqRVbOCfJo810ckrr45hdYa5VfxH0n9n4OtlDZG92Ia6b0Voo0A== +1u+XjG/2+GSQRv6EzCaWRQ== +OMWgmFohSY+HhGBycmwDAKlzMy1Qrye4ySBrbLpHQh/10ohLj1C1SI23bRneYhun +iVrumwMNPYZ207haO9u4MdLAwVB5xOk7iikXIMTVL/qL0+TBe1COKZNOXz9EWzaa +CAhn8dRUItEbEErp4w+lXyfKXxXDhOK8Y13cqRVbOCfJo810ckrr45hdYa5VfxH0C8N5JEg/9LHhKHeo0RbNqg== +1u+XjG/2+GSQRv6EzCaWRQ== +Srf5AIzkpfdN0c4nOY6t4sGAUtQIfdofNy7n/g7tol4= +vHI6ym+jnn06PgL99476z6Uwx4qvfe0JegnXA4qmLNl8/pYgnzU4wh0jCA58VVutfY4MrkqewDmJI+ejDGKNQQ== +CAhn8dRUItEbEErp4w+lXyfKXxXDhOK8Y13cqRVbOCdhCWl1Qh746Xywu95/4pkITT8KgljSGAwu/5cKQ5rTniUjdofLMTwl9oAp06PR5ls= +1u+XjG/2+GSQRv6EzCaWRQ== +OMWgmFohSY+HhGBycmwDACm8KBM9McA6bMfF7bWoCcq/96vBjywwKMifRE7T7X+G +rd+FFDwen8tJkKTf8UQJhMdUyYbtrdwamsCwQBeqM77gCC1coFEm67jW5p3AGGTFaEx3k5up907Bu9eID9IsuBdOvJyvhUZnVYJWja7Du7U= +CAhn8dRUItEbEErp4w+lXyfKXxXDhOK8Y13cqRVbOCciGRpycCKiBr1kcnrqM2Ni1tSv5A/haJTQeOWgwogWK/mLhT6Lu8q34h1NwJSnvtY= +1u+XjG/2+GSQRv6EzCaWRQ== +Ou9p7fXXFVhRiwA9kUvqbQB3LRoICh5tKq/vC0zda/YXw/tQhr8ReoxBKRzjqWbY +TPDhEyc3gvdCLuSpfWPtkMo217/b+wUr0WK8q7nkfj/nu9POVb56XZDN6pu5OBmU2Eo3m8A3YSU276U1x5fvHf07fdUjNR6Oj23JkwK+sbom7XizX6t2ETi9O7cG2ntDEmNei3bJgzOyduxBN5qqOg== +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUCsKQJMKz57hDGzJ13+V8f6 +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyXW1h+kfMT68veMT5Cm+2zxz8Vmt2bA2ScEIICK1Jf8Kjys09SeWqEQXr4sZGzDGtI= +1u+XjG/2+GSQRv6EzCaWRQ== +X0HrHnnVVAGJX1LFiXPkceg5OodpPnylmQkbSgbOhdnBFW5kZYDLk5gYUZvUZCeu5XUOTVzbErklH12QSsLHvZvciMa+XkhozpxGVVTuajVPncY9lIy5bxfMgY49D3gc +/NaqBMGVF/kApFw75QMoWE7Bi8yCcG2HPuA7c8S6vUNoikVL8DsXPN6kxcUhLAMpv6NaSljIODjfhl/mDMRvEz9Zx2TBsH/cDKirDoVcRKDRTmOSnNecVHu3SALXSizJ +1u+XjG/2+GSQRv6EzCaWRQ== +6tLF2b2MbPQJ/+1LCtIRa57AcYo8PJYJ+3q+V09oOKv2nWk4K+IbPEvRI1bzSMlQHtoTXBVrsp04deCTn4biHQ== +K+etge8Zj4gb40jmKXSx29zMdWcXNAkih8N1HpwmcEU= +8k1NHlf/dJJ71i6dP4d7XM8rtRFXkuktgcatPKvMuoASR0leN0Aiyn0dzAIvBBmmdN7XzJeHHcfBA33dE1+9rg== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +ZDt4tHr/EqgJvUzyQHIFSmiu85buXZguIJEShIlI8YFKDS0DK0ZuDq5qLGHmR8M6vIb1wA2eLZ45JowB+n1pMQ== +1u+XjG/2+GSQRv6EzCaWRQ== +CTyZ7MfB5iFIHP7wDXTc1o2wRLSVdLEtFqWiz/0+BDU= +gNkHdAF675KKZeBcAgFegB/sM2X2Y6bJt298opT8yTn9AD7jeF0eTQbrz/YtEJk+ +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +TDIZYXrOARpw3pgUPeUmeRLVXw/+4xgF2d1NrS1ZoKzqvY+UDd54F49SOls+n0qz +CO7W/6aJFQTRG/xTm6ku3kw62D37m8x0vrdg4RooiDSQZs6q0s61X9iJ7qqN0pM6 +FfOMsVlmcn2d70w9TKFQhhFhdaCPRRsgc9EyfesHHcd3gHPIGQd1OrDWqSXKZh++ +1u+XjG/2+GSQRv6EzCaWRQ== +ofbnZlafmPW+1kVnxnIwQOFl55Q6lyscHPdpS1CcGuo27gOlJCnQtk6SBo2EF/PB +x4fPj1hBhtair/f3xLPwJQktZQsbYZcnxh7Rg6O43sL4QEYXlG2z4QbhMGU1/aSy +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +U7h6BIX4p+v7nxzqr+lrQD35elqwPh3VgUWs2GHxhOQ= +Mcnw0hYRTOqKGEpOyBgpBw== +x+7nl26IqLqPeUC9UGWi4sALKH8l8SN9EE0EoCaUklKHkzReRxFn0BorkBQFnyoD +HxMcStsbV913HJxaH8CyjlVi2ECFUqb7EDcn7FH4LgoBjaR1g4xrkrZAcLSRghPh +03A0XX8qTsa/Wb4yaQ9jXv/t9evdswNpDBdnjsuz+f8dWJdjlhi6TMmu5N/mCoNibo2WXpWuFWFgYk2e8485lQTeBC+eYbA1rr0ygM+oXFH3bKzycimXoSyrCxyB9l2W +Q3VFEGn9TH87P5446LMfE8uD9OcqjCZCzl+dH7oa+ys= +1u+XjG/2+GSQRv6EzCaWRQ== +3vDcRXnLzCU8KW6t8SEdRkU3kVEcTs9Bi2k4YRiVLI6/B0Gy5PF44mVjoBtae4hk +1u+XjG/2+GSQRv6EzCaWRQ== +ruTTNFTdswRs4Mc+srnRk0bG4n/8owB9YurdUoxgVWGVu4Z7ijI97A5SVbr3+BdCmF01aiyZvnqpwcnaEp0dNw== +1u+XjG/2+GSQRv6EzCaWRQ== +dskxeNlXSU2ewA7cm6Iz1fYt678fBjl85HT3mB8+LJM= +C0sKqLgrkjALklBmidqKczuu6n3ZhV3ftlrBKdkQlL6TAqQxrxb3fGL+WZpjpkis +RqVoQzk6IR4JMpR/e8KmeBBBq50KHICX2aiKgGPkxZtJ1GPEBK5yGFwxFArBEhdU33OpOKmcHAynmrOx93P56SxkUrOSmhL95kGcSEHq/1Q9bivwrjJyQYMc+22AJN81dU5MSDYUNVba0rdJ9FmkQqb9gsYLWLgWSI+qfxdKqGU= +1u+XjG/2+GSQRv6EzCaWRQ== +x76N8xgB8xjuhknjQsti/5opyRcSliwcCfQ4SsxEiAPvMf7JggL8LKCao8x4abcn +dYBxYC/SPPLDq4jXDlgeUXWL1ZZige1IGh/Ka3jXGFtW1ITTwiXwsPrRCWNYtWnV +RqVoQzk6IR4JMpR/e8KmeBBBq50KHICX2aiKgGPkxZtJ1GPEBK5yGFwxFArBEhdUBSDg5+jJKgJxzOOU/SywVg== +VTFF6c252MOvMdjaSnnOt5zC6iglsMzhnYs2G6lrDCY= +1u+XjG/2+GSQRv6EzCaWRQ== +u+tG5dK3//gTqbaBgFgQJQ== +5dgN48vgAE6fUdm8Cg+kvaNYK4/BCgONdK4dvjPbbcx1+U/3TgvUCfMVz1BbLXjQTe18iSOC75OGg0Bzyt6J5Q== +GYvLm+RxY6GAhJqRwEJV2WC1caUdnFFkgZ7yW53m1Aaq4YGSwmm2Dn59A/CgkaOOjPPf97hfUEHdVgaB5aH2yOGcD0+feSNWsm5vsfsduE4= +WASycj2Wa6aTDHB+95lzJWfKcUAm+/xexypzaHrXYC7qcfXNxKS/UqnVo9AVhhGr +flaxCjdiK/3pdKEQMZfg01rU4BTe0j7Jdm0gHzKjl9QYgxabYgNkUdYdcTzlTABNi2daxEc2Yu5VdMM4/i7CfA== +OBqjTkQhw4mQaVgdxp0kIHWqUGSF0wB7yidkUDsuGWo= +ys5lzs4DwATAOzYtPZvF1zp9axmk5Xs8tRr3D23tmzG17k0hbYoRMPXBNRXnqhX5Z+R3zKHWkMzCq0GRKueCD4eShnGrlR1umutWz/sTZ9K1t6EHZ/InYovQ64ktijdP +vqVKYDeh6Q1Iw+2qfAj2xhtY3zrwlUINQqWLzgvvZ3Y56fX2wXYiJ4HPKOzT1YevEkRkVBGI3nXPhS0L3UgFQ+ZrAtVT4Rc/LtUcCL6xcSM= +H0pYAv/06Ol8kQ7vSZ2pjeDJUGWx6uI3t/U3Oy0K+fdd8zmrvC05fpjVSuNMJCL8CcG6EolQIxI2ocMuWpERGlj7xYHen6D+2vFBow2doCYbSGBj28JRdTmUVhCQLT8+ +x0nVCxDD2zr+LjlV0wlFgg== +ym2R2BitFkCIemDFubiwLg== +wXVIuIhRYsvivxjKWnaJzZhpBbxw15smk30jzXwsUOY= +1u+XjG/2+GSQRv6EzCaWRQ== +GlaIlOOYnMM1QkhK/fd3iMZ/ohgTkVJVWE0ntP4W40jJLwkrx/ac0SDy+W0FpeStPGjiDHZ4r9w9syHUBaIXzw== +iLhV4FEnj1EmEQKF/ZATY3M/FNJUHhCT23gyUqYFd9jWjWBRzt8Fu4R/X1V+5bDanFzQhiDtuhcX6B3wTcS7ka3VsCGp5UGiO1HthFTQZbZb4Oc8CeSz7+knCm9m3OZ6xcSnuqmtB0aOCCMvkbPN3VhtyqM5BHfREIV0qEekcKKxbF8LYCfdz8LSWZ/EiehHJB3ziKvtGgMXD0UlbagogVjQAL8ZGyOFaPIC0JCSNB7yOprJAzw4EtnNyfzW7q0DRRZncf4wrWm1XcRk586glQ5+hS5bAY3V3WUbnLntTXkmnetDE+ulhtlGVy11kIoT +arZDCk94/K/J0iZu4oF+HRuqYEk8Vy/hVmDkwxFFqLlpKv/NJl33jR7nYw2cThvOTx9AmeVFT9p/BxfOqZik8Q== +1u+XjG/2+GSQRv6EzCaWRQ== +ncUn2TP8ZQ4fZMBYk+CUrWeaSmVXN5jSwIoxdmYJQMztQZ4JvkpRAxRbBaoWp0U7 +IaE0wXi3qVFO6+jRuxMC7qoy2fPw/2Em9xHbKwjSAwOPIrYaQ1d1x/FXvJrgjiZihZkBPiPRjA30kZdERp/aiw== +1u+XjG/2+GSQRv6EzCaWRQ== +YA93D7bVXAwMe11nxv6BP8N2JjEI8RKP112QbKxpuWA= +VmVrGQo2zRokW/ZuO9bN605Wj+7zpmTV/RDAnHeUp4v8j+vBMiP9wzEjlBAwLSyne822pv8KTa7xVdMPTpyQt6MH4sxXZOi+dBMrqgMrft3ZNG1CSAdMn4zoeVqqtbM0 +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X3r8XvXVNPjPJP8mrPh5ahg== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zAgKjCBCMFWcjEGKZKeBnYuHbmqnbW+28rSCnK8i3pDT9TMA+U1QMV0B5YS8m3XvUq +1u+XjG/2+GSQRv6EzCaWRQ== +uEiJaDMDAi1FoDM5lppdq9SF+6YelJsoEY3ZHuPMu/LI4rDG3pLdm5x5wjDwlj4g +GqKSCRi5O2RDIx2yEhsWngsKn1ziJo9iV8IGQryLbuxInqd0vtp5a0RohTqZaJzl2iQLfIoF3GeoxVHGSh8yPFyw+7HF0QxCRQD+b8Y7LLlT9OPQu+7ispLKoDk7W31NoCmCTtOkKjiyBIda3rfSTA== +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +ELZnlgNdTWbG8JmZqANPTtICkopfjqD2q88Xf4yxAmE= +EmZYXAoWwLEw6AEemfKeYDL+Mm6dJt59rMxv90xwwQimFjCIiHVBZhVl1R2yvUg7iVmyjZH6b5XeDmq6hHFFHIBUGAwPUGkouwNMHgvaAb4= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpnFTGf0yf7GnaDVYS3coZ6Yh4KLB+u22zIIdWhA9g05+pUTZczg4x2J3oXBMHMP6yAeQfyZgxWhDpT2a6smi7fZmGavb8vp0xuKcZyaMbTZbHUR8i290iZ6b6nDiGf4INSi3HXPz3QMBSFmYSWZySPH6SVzcwFrcv+ns0ROBMBR8= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpnFTGf0yf7GnaDVYS3coZ6TxoHBdtsrDWqesC3XQSmVYf8i0WrlOvkFaOqUFq68PUm4P2pVA7ABE1Dfiqr07OthiFuvXWs8h9dR0+n8S3m+XlMdEj/xkwQ6szopis63QXA0BxuUtCZorFYNexwVURWtGRCc+w+XoPUzaOOJ57iR0= +1u+XjG/2+GSQRv6EzCaWRQ== +uiIF/qFEXBu0rVeM0ZiVRrsuzpVplMyIrEoh588zQNa7LgNNPihQq+OxTL9IXdo9/laR/cZLatR5sNKP6+Z2iw== +ju17ZXRoA3BJIHkFg5ZKWr4pfPiuJhh95gPAvQHudLUcfIARKX9jv+Grou8/3OKd +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT3PFDAZpFnQ8l757+75ltgEjR9hK3iRyKMAFAfyUdLSIQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +2/KyATjQOqxRBb1WFuBZrtK/MLzOCJgKrDkN6X2yG3jZLCbxczaoNIN74FewOHig +47qB4zCkPWSNQHMg32YzdUy4ZK/KQbraTTR0QOCeNW4diavPCbKBub6PI1jH9Vc0li6nX9tU6x11U+/N7xB0wpFbOArXXE0g+En1Al/orak= +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUCsKQJMKz57hDGzJ13+V8f6 +Q7QVGPDExqjR7ynTbPAdwUHuFe4OKIlOD99L5ojaFA3L2UdCkE+gebH/hMzoz22JgvfRDkI0tRQJOFMEAhM0KQ== +1u+XjG/2+GSQRv6EzCaWRQ== +pxrc1WLH/H5lt/NGAqhTitoSpdXVvJ6Kv6ABkIYPYPTiR0z/V/PSUvEe2L8snFA8 +ruVNXLPFmkyjjAB0LDE/N2Ymqg9ceD1CjYDATDeVK8Y= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +vcNCYe+BnNTo29rOge0xtys1DPV99nqlMXGfoPHaos4= +MZ1exjepopSsVT4i3zM7Rz+Ui00ZvMt4Q5SQB4StHAexXCJqO0zCYRVJtuqMihFn +lzvxzEnWSUQFSnfrWc3cNWpvNAl84J96nrLlEsRgPeI713lHwwNGRkwGjxpr+Vg0JeUJkHMF4miEeIZiYFjyCJTiVRINYvVSQ/yhA8vrUqs= +fB5YVyq+Mh1BzTmuk43D5unBtLKBpLX6HXhyeOrv9ffzt0qlo02qsq5dIaMDErODiCGD1xXhYXE5NI8oelQlbQ== +8KyOS+Ff4JzgjqlSdBaVLa8RMBHkgsKXCsKW+BjZk5hSPQprgE+Ij9fYVynPuzyl388XYTIBhLXUyX/pYgtAH3cYcoiEyW28HYNTgi02+UDvJbVxZe9Ik5uwnV7D9CJ2 +1u+XjG/2+GSQRv6EzCaWRQ== +o5JFNYRcDoTkRRL5F2vl3Q== +1u+XjG/2+GSQRv6EzCaWRQ== +bld/RgDAciWvrNce3UF9ayA0lJr9osshYNoRb1QHUhxJMDZ+kSfX48vFWKuSE+wB +R1d2q3mo0tt3i9cxkLE/AGOYQJ/iMDUe4unhX52RYj7/TLLEmkGwnB7aq8oxciOF +HGnYSz17CMUrJXyLBdcBXeL9mvyGtPW+wtb6CzqK2+5VFGSAOfwhOpBwlqWhZfxpxidwc67dGaCmgLUSxWeZBJSdNvjbQUrEKXvyYNP/koZqcMg4kO7cTCM5tXHe6PF56e1gakMoQrWOPieKtIA8CgIVjNcHBAAN1hUul5pYqS8= +NLg+36Rajr6hGkly3f37K4i3hjcUgiTv6a1u1cIla/WRgttaJQUlpbazq+WHT2KZ +eMNmCZf8V6J71T4XuVP27PvULBvVHnmruRI2Qz5hfCrVO1PZPj/Ph58vUnfdfVpP +uXoZXAVOz0sQA4gwnrLcfUUGZy7debabEfu76Egpvmg= +09UuO0tw3kJ5J618hVggcQ== +yi4USbg972Yx1t8cEq+SaxT/h0vE3+fmWsgbJM7vuAQt2AQeheXnQ+jB027jiVfE +1zUreoqlIVA+vRqhYARhcllgHIcu5oCip7CBwrzZ/d/I/9O7w1JC+cQr38BVLFXUrJnbWRG7elpiBt7V1OUSVw== +QKClQ4XOaAjfVBfstd1swg== +MFZbje12INoH5rrCsfSaIqZIf4nQgQ2DMwYznhp8MTX08M9XPRTCpgObVs4wLdiWxntq0EcLin7RQL/YElpAu5VGBxZ3qDU371dtBOTkAyjGc4oYD0bsrLozyLXTs0GhRZgxdQS7Gskg6eVW5E83F3MUL5wSXgR1wWFmiN/fbPWue365Rit5sNTW+EgNClZyboOJWgYqJPw/x0kH1F1MlQ== +SCzSXq7nmS1ZLNRPWXJY+EK3m5qXAxVb/9Vh4tVeM06eKt6I9zZisq4iD8IcJ4KP +661hZf7vhUQ+50okfwfTXw== +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +6zjp987/AMIbKnKovhAijQ== +qwRFMQtB//Sb3qPvlGYoE8E+xP+owOVyFQ0q2crm7Bw= +aenAUWzQKgBPx3+h8uG+r/5FNgb8FCyZOmvzmQcgVHnq3vhWuRD2vC5Izlcjm7rQ +l2yLikINIVyLeFLumT0qzoqlRnXCrSN4knk1KIfxws3eP5nbL6xsfEMZ3XPuRA++v3gVu88+DhE1nQxkTdI3zg== +4GguegA3qQ75gzaokIqjx9k+qXfXkvTCbb7ehNIEE+DwvbdaGkHAldp2VAB8Ujup +6HBnM1U2T6LZrk6Crmb3zSw+27WapQM7gqZvLI/3sacGf2Re1hlzoYAX2WbZbWMY +1KNPUTnB0+GSRuoO+9nUS1JIDmVKgL45RpJskaf9gFyLVy+Ui7nbPXMT6LUNJINO +6P2gJ+vDC1pRh1WT99ShL8llWAjZiunK3McnsIzPqcHzCZs1Z+nRKNy6bk9YlopocGfWFCnC+mvKZWwS6ur88g== +nrwNQBTj88dT7zfiKny867DRRc5nxwhABXy7lwigvRxW/Vzp39tFuvuK3zb1rRxK +AKbJWWdo/DU+a/JrHx1OHXK6ROJwEocJqPIcSJdJwA2hZVELZ+2Wskryw/bOqTVD +yFstOGL9WPJZAYINe9h/sTjDoynAzYDiDKhqXn2z5tdt9O82YG+IygXS+Mwnsd2aOz/hcE9XtXJq4ucGLhMZyw== +w4DgeJTyUUQbStJqN2eVkpgT9LWwau0D3sP0VXSeyp9aH71mM/5SQZkiHiwzN2NJ93hvyFLCYlG7NZ+yy2yNKA== +yTTp28LIim5+YjxAfVNM0HMBj5i6hO7cEwrn4UqGKuxdOiaOTrAqs+rSjarYssN+onSj4darz2KahQbGg2a3Eg== ++XAZx/LA8Tqt2DY5E6RCz+RMtgTQd5aWdbscNadpGT0HUonFD5NLcViOnThShnPoNBbJQ3lEF9tlqtzejL86vQ== +XIHRHCfwuCFaNecBe9SVlA== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +U7h6BIX4p+v7nxzqr+lrQLHkPVBLWwWs53akWmvnS6Y= +H3X3EGUrpS/29Wm+tIafOyUN8R5pTpM1CsRClEWPZgVpkgRyTwWuh54nHfoGymxr +b4OJVZe8QyIpjuTpKXDL9A== +L0eUthVnpkGsmKFAX6d+uIYN9x+SK7VEV0i6S3Uj+u76lOGg6RYQqX/NDsCTZwiEh4gNkI/Qv/zFNStH5jSeKA== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +YY5wEo94l9qfo5TPxQmUgiOUk9wZ4yEOytm43lMuFnOPIa+XXU9SaayNCqc6hc7h +1u+XjG/2+GSQRv6EzCaWRQ== +wWjnF9eN5P+UWvdDUKiAFegjuAMpxcMGGMHmthta34w= +yDOAduTbv6/KjNMoxqPpJ2w10n4roBaEHSpxMMKG6UTwiBX3VHChUlfx1Sav3pODM6bim3j43otXnHneUJYNvJmdl1iMLY9XgofE/AuzLzY= +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +PzEvxs4M77eQp4726OdUdmiaFs5FrB+rN501JI5oyeQ= +EmZYXAoWwLEw6AEemfKeYE6959W5nEE/3bBTBQPwWpM1mOu+SGdpiCkmvvutBWda +1u+XjG/2+GSQRv6EzCaWRQ== +ZJDi9TUcMjkN4cH/dB5wm1El6QQzopeDtiLfApPqTxE= +UUUaCiklesr+fpQDx89I9CEGENY8eKaRi+RVs952oN9/jAJF76rgi5zarBh8nv8cjYxGRAqnnFkZQ2StmpjCmlIEjU3OSsouFY3xx/5yLGY= +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +byLWqIOe1XbkFTQu7j5nOqKeWJfJJJ/cW+Dnb0APrGk= +EmZYXAoWwLEw6AEemfKeYGdwgSeN1PIbEbfhEAXejJWkmhL2PcOjg45phhb2skZz +1u+XjG/2+GSQRv6EzCaWRQ== +7uEPwOBJ6pip2ywoNW4IE4VbaRdQZAV1GeeyVgXoZnk= +na8APpoRB8t6c4RbUreK+jVWU1RNWhVsPHkcHM+g9Ikqdn89KNcL5kBOWHQd//53tnUTYT6RPNUzOtPRo8dt2Q== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT2RcWpkQ0BBnE0X4ertjcM2O7FXNP0Ca5yK1Wbc1hUvLc/1O8sbhADCELO0EWSW5QKQzil7HdD3Kq23yniR6EBhbgOwMyMfbd80M2UsLEZTCA== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +fHhyoqZJ+sh07SfZEu+gxL49MsVNHwlg4gQuNEkifi0= +UfrFLwox5T7Q8/E+j18mDJclg8/8iK9JYYboPg3vbv9qmGu6PuprVbXp6GQ5aVde9eCvyqnqlETt0eQgF8iF49QJfhurwrdj82K0cfFAHVk= +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +MZ1exjepopSsVT4i3zM7Rz+Ui00ZvMt4Q5SQB4StHAexXCJqO0zCYRVJtuqMihFn +1u+XjG/2+GSQRv6EzCaWRQ== +M+B95+gF897A4LE3SraesW9jqG6vPRMBZ45nxtjn/F4YKS14BseTSdBfBPdz0a50 +JlOeqrNAZXGZgxJpw3ujiDg8IoCONpPFeiTT3WZ/aLasSWVSHTBf49MOnKpo/v2f +pUencgOuLmNHRknRyPT9Xx3oP7Tmg5fibR26+fVERDzKtPqLarPtCnVUtVrzLFlmF9uT+NgPEUVOrc8O1CEqQqRCwXUAtaqsPvgJ+YHbrGROHSkwtgGYFxBfKGpDbGzWijmLINXAgUw150Hz5CKXeHj0V5MWdo85LRgw1AoA7Tw= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +n8+Sm0LGPcnI3Npys6C0BV/7VdctpXK5jz8n71G2NgI= +EmZYXAoWwLEw6AEemfKeYGdwgSeN1PIbEbfhEAXejJWkmhL2PcOjg45phhb2skZz +1u+XjG/2+GSQRv6EzCaWRQ== +B9YE1vegPqFZhW7U1qNrMKSgsytgrvH/2eTz8V1hwAE= +lSCItBDj81FscebZao0b39tg7nb6Ucxna+I6FNpfYZU0wH/VgGPepuRFQQciC3fCzS9uyA3SzsxN5Q58Npc8tOT+slmDqaIupOEbXEXl6xN6WByRxc60/K+EqXBlUEvK +CAhn8dRUItEbEErp4w+lX16+n40eaV6hpDW/6jSggsErVdLIeCO7GpA5aTIWY3OqL6w1mrhk2CNVFcw+RI+FuR6xW+eHcODg8NN4pYiAdBI= +1u+XjG/2+GSQRv6EzCaWRQ== +G0dSb8cfwCOcOtjEWz8TiMXyy0yCBhNe8qZ/+hOEL3M= +QvwWeJbbvcvxBQxj3taJOtGxJXXi/nmUe3b0KpG16fZ98qbugsjzYpFQRs5T271Sl82bSyJ00KVKhD1Q19FWQiv8YRuD1wOOycCvOLu5pdQkhDzSqVE8cOGRvSrT3xs6 +CAhn8dRUItEbEErp4w+lX16+n40eaV6hpDW/6jSggsErVdLIeCO7GpA5aTIWY3OqL6w1mrhk2CNVFcw+RI+FuTE8jycmPD5eNNO7fhwVyuM= +1u+XjG/2+GSQRv6EzCaWRQ== +uiIF/qFEXBu0rVeM0ZiVRucOq113oDIxjuY5HbU69bYfbQfvTjQC5NZ48+QAKoCgsc8yeIJDuM+Cl47BzppOiw== +ju17ZXRoA3BJIHkFg5ZKWg+V7+37jlKz+K2dTb5oHVb5L+GjrH/+qvmFPO599xZARR/tPk6SENuiCyM0RitPDA== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT12X7G0fgM/G1g1mq2qs0HiBi+U45fZJ1W/OzC2qNiQPA== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +2/KyATjQOqxRBb1WFuBZrrLqCF/uEBM7vP+L6PwCMKN9p+q8X6WK2g2t5KY0gaTy +47qB4zCkPWSNQHMg32YzdfQWa/EFuV9bOpEezNrhnVTXpT7OxL/rmhcaYi/87liiyOqos8Ow1P8uaMr6rE4YjhG+XurNHIUDkma7t5bKES0= +RqVoQzk6IR4JMpR/e8KmeBBBq50KHICX2aiKgGPkxZtJ1GPEBK5yGFwxFArBEhdUBSDg5+jJKgJxzOOU/SywVg== +Q7QVGPDExqjR7ynTbPAdwUHuFe4OKIlOD99L5ojaFA2iOKYJ3ochJrOwbe3IsxOB +1u+XjG/2+GSQRv6EzCaWRQ== +pxrc1WLH/H5lt/NGAqhTitoSpdXVvJ6Kv6ABkIYPYPTiR0z/V/PSUvEe2L8snFA8 +oi9cfgCdWXEjlixQmXZZ36u4NEhmY4xEjyWTDvLrpLk= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +PfBdnBUSRvetrvXb3KKmYjrMMbFx4Hu/xMfwM7V4tT8= +lzvxzEnWSUQFSnfrWc3cNWpvNAl84J96nrLlEsRgPeI713lHwwNGRkwGjxpr+Vg0O/mIV7z3HrttkMyqTPxBesPa5HyHRTC+O3s+qJX9Cuk= +EMbPxLmnXz3C9+PY6ukDEJbIMJHUnqpEg1qX/67gXW3avaIOGUNe4YeCjCb+n/LpHV5+idq1YAU77Jsn+LGa8g== +w/pHxNu4waIaMplDWJEPc5+0zVolFaxS+yRA8k0rTsA81gzByI+ANivS4kmnj7cZKcHJZx8RmgTfKcMjrKWH8RYf3ail6nbwPDveChS9b67QD7bSlyUR5X8pCDxKhz7e +1u+XjG/2+GSQRv6EzCaWRQ== +o5JFNYRcDoTkRRL5F2vl3Q== +1u+XjG/2+GSQRv6EzCaWRQ== +q+wANJnRBfSeOt8GfRM+HHMKe8/R8tQaXHlCNxGp8DE3GGIKvah9L4d+XXu3UUGT +HGnYSz17CMUrJXyLBdcBXXicBhFS+rjbAxXSuZjBzmtnFqvSvj7ZoMVroYsSVM6rA2sUKv+jBhKxzXDt8NLAYRVp2yNNIpYF9QUiYmBSxuUS15U7Pf4Qro2gC9NPJHjM4REu+C6Fstd2Felv9Zb5Zw== +NLg+36Rajr6hGkly3f37K7uhk90incRRI1r8ofy8z/TPpcO1jBhQ8MMIXNK4XTXM +eMNmCZf8V6J71T4XuVP27PvULBvVHnmruRI2Qz5hfCrVO1PZPj/Ph58vUnfdfVpP +uXoZXAVOz0sQA4gwnrLcfUUGZy7debabEfu76Egpvmg= +09UuO0tw3kJ5J618hVggcQ== +yi4USbg972Yx1t8cEq+SaxT/h0vE3+fmWsgbJM7vuAQt2AQeheXnQ+jB027jiVfE +1zUreoqlIVA+vRqhYARhcllgHIcu5oCip7CBwrzZ/d/I/9O7w1JC+cQr38BVLFXUrJnbWRG7elpiBt7V1OUSVw== +QKClQ4XOaAjfVBfstd1swg== +MFZbje12INoH5rrCsfSaIqZIf4nQgQ2DMwYznhp8MTX08M9XPRTCpgObVs4wLdiWxntq0EcLin7RQL/YElpAu5VGBxZ3qDU371dtBOTkAyjGc4oYD0bsrLozyLXTs0GhRZgxdQS7Gskg6eVW5E83F3MUL5wSXgR1wWFmiN/fbPWue365Rit5sNTW+EgNClZyboOJWgYqJPw/x0kH1F1MlQ== +SCzSXq7nmS1ZLNRPWXJY+EK3m5qXAxVb/9Vh4tVeM06eKt6I9zZisq4iD8IcJ4KP +661hZf7vhUQ+50okfwfTXw== +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +6zjp987/AMIbKnKovhAijQ== +qwRFMQtB//Sb3qPvlGYoE2ancTXrfnV9a36LAYM7eUkSUoQXtyhACA+39DOLvWgk +aenAUWzQKgBPx3+h8uG+r/5FNgb8FCyZOmvzmQcgVHnq3vhWuRD2vC5Izlcjm7rQ +4GguegA3qQ75gzaokIqjx9k+qXfXkvTCbb7ehNIEE+DwvbdaGkHAldp2VAB8Ujup +6HBnM1U2T6LZrk6Crmb3zSw+27WapQM7gqZvLI/3sacGf2Re1hlzoYAX2WbZbWMY +1KNPUTnB0+GSRuoO+9nUS1JIDmVKgL45RpJskaf9gFyLVy+Ui7nbPXMT6LUNJINO +l2yLikINIVyLeFLumT0qznDyhSEyXtc2IpufUPJvZ7c7VXllgjDAT6BF5pRPlB38GVVG67pOJVUZcPJTDqemig== +yTTp28LIim5+YjxAfVNM0FqY2cnfoKiEfoGcP/vw6X8QIj8yCecttyUJBJLARZjW +AKbJWWdo/DU+a/JrHx1OHfiyYnDqwTLg8eqd6kxEOWOqCCqDz0NYnQLyhlVXJTvi +w4DgeJTyUUQbStJqN2eVkknuj55C7ViAisQ1E/U062F3gHtzlhLtgbQxBKYhbUkr +WCJLqPptHtaJYPlph+wWeg+sOxk3Uv6xj2ce5wJkmKCUipTEif/EVPfK4rUVBt28 +4mNcCYu6bgJf0BSQhxRcisvwAScbMuCqKWHcVXIbj7wNu6qpAFwGTeJeboRpV4r8mv+H9WiDLaRerTAnGAFJEA== +WCJLqPptHtaJYPlph+wWevd5sI5xYoG1zjsIm1ZW7JA= ++XAZx/LA8Tqt2DY5E6RCz+RMtgTQd5aWdbscNadpGT0x9Pd4PGItjkiDDgieRB7b77Sl7xeASrdD6cS5HIBf+Q== +XIHRHCfwuCFaNecBe9SVlA== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +U7h6BIX4p+v7nxzqr+lrQLHkPVBLWwWs53akWmvnS6Y= +H3X3EGUrpS/29Wm+tIafOyUN8R5pTpM1CsRClEWPZgVxxWJEx6/M8Hwi9JWiUQOvspeTeunE8GXUosngMViJjg== +1u+XjG/2+GSQRv6EzCaWRQ== +U2kUm68+PfY7l2FEHprZHQ4zaSjqboIWU0C6fmJl7mI= +YP707gDXV92HFzvrbPngtud6mQr0wqhlxyeAi0kZpiB3Ihd1qVQvkSij13D1jYfC8oUMT7Nn8G1DPFdi/15r2nMTGZPV0z3fzp94fCaM1+3f+oNiWu0sfaQY9eUn9cEs +oacxXEoPih9NKsd5qduwcJP5AF6Kl6JdSjECtt0tknbNs4pgmNiR6Yl+BqOoKyll +8eMNwOFVbk4AtLbMoBZ3WQXk6rNPQP4WCclseKXAP1JIV/JyHM54kMCWSAIjVhmJ +1u+XjG/2+GSQRv6EzCaWRQ== +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUBV16H/wFIocLdPKt+iN1Q+QPnCkfaCBWt/veMZH79zLlyiS5k1FWT+2Yml0FGnP+5VvgWsaRh4pvYQ3oU3Kj69sQnSPzOWni71Ew6+zBjjRA== +1u+XjG/2+GSQRv6EzCaWRQ== +NuQf5vOe0Oik9LWfqOnHdy/NOX+H2BGhwJI9uG0gx6tVV0LDgDKjt5zGVa6JPR2W +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +YKTlZVqhMnSRcIeAIjPbFD/jkWWEolgtSWeBe9MPXjc= +NoDeiQRzOwaAKEe1H+Wt2oljPZfQG4ZaFi0eYcsMPioBvlqYa+ow1OMLcn5Vt8JUkVqJstTJ7wh7Sk4PR7iGOl17IYCtzUj4sAXJu9I6YVWXciC0L4ttEwR7mM+Qr6En +oacxXEoPih9NKsd5qduwcJP5AF6Kl6JdSjECtt0tknbNs4pgmNiR6Yl+BqOoKyll +8eMNwOFVbk4AtLbMoBZ3WQXk6rNPQP4WCclseKXAP1JIV/JyHM54kMCWSAIjVhmJ +1u+XjG/2+GSQRv6EzCaWRQ== +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUDJtODfX51TdteaqQ3a+UcgHFQmZwOovGMrZXXAykB3xneSZS9ptT38XIhclZuW4tIUsA9oZG/PT+pLKemuFLdw6cydjnbL9fdsWUVK5NAZQA== +1u+XjG/2+GSQRv6EzCaWRQ== +NuQf5vOe0Oik9LWfqOnHd71D0GHLd+Dnie3THU9YhIw= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +2Ul3GipzCUNX7RveCe82CbBj+sKXsmwByweOM1JQOH4= +yDOAduTbv6/KjNMoxqPpJ4CUNe4+WGLXKDJ93lX0uZUoSZaLEi5gwHitgUzbD1tFUzOR/pdMYMSsY1m2If+wBGNve6WdoZMW2Lh0pAiZwpE= +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +PzEvxs4M77eQp4726OdUdjy1F7989QHAkzQUa5m7IUo= +EmZYXAoWwLEw6AEemfKeYE6959W5nEE/3bBTBQPwWpM1mOu+SGdpiCkmvvutBWda +1u+XjG/2+GSQRv6EzCaWRQ== +BEefnVQInBLLUz5V3TvZjhb2ZCGHPFwPTtFWXf/Hahk= +UfrFLwox5T7Q8/E+j18mDM78wSrP5Sl+ShMSLTRst5kT4jdbSnGHbabDR7jbAt0ipcRtkX2IuVMOFlh8rGIlvbY6T+MVm8fHFBrIB6AULjVOUHOanq/Rb72gsyADXZOu +PRTQce9kzL0wbtVMaFHuX/dt4H8WFwH22DPdffzZqPLttVTviK6wrLoAb38Vzp0W +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +KQjAPiNciP5B8oZtUmJlKnrbmvn0317qudFG0Bt0PrY= +EmZYXAoWwLEw6AEemfKeYD7dAwuwV/Y5SW0i/EiGH54OTgiiIoJ5DKeDr2Z23b/hi7QI/t68KhCH+HxXxTotk30oGLvkUwgFdM94oSQYvk0= +1u+XjG/2+GSQRv6EzCaWRQ== +FihJYIbh7GjJE1FoiskZG0ZvWVP3/pyMdhA9rlfHbfM= +Ix9T6TIGvd+yLi6JDJzskxBFKq9fJlmSJr7Z6ignFwGm/e7819CIiVV4QJQHO/I1/7JnaRhaQBTeIM0uU3Zmpw7sjgHeoRA1ChI9we+IAk1OBkWCvapdKrjhtpDFRQWj +CAhn8dRUItEbEErp4w+lX16+n40eaV6hpDW/6jSggsH3BMR7I+h+5pwJwKlMreghBi/c+hgEI1zRZknIqU3eS1IlKuU4hEmdzpwcI2z3hAA= +1u+XjG/2+GSQRv6EzCaWRQ== +fqar2jT9xx+bGYaxqWLMVYeBlBDx9IDnUH3/AtFX2VQ= +QvwWeJbbvcvxBQxj3taJOvme5tgtQlpINZ8yhC6dvJ+r5mm9OpIu0bQuTo3/IGwVzPX/FrVInqx1HJTKYbaCM14+KDM1YY+5DBIoXrGEHHpzd7iUY0WnIl4WiBhdGoZ7 +CAhn8dRUItEbEErp4w+lX16+n40eaV6hpDW/6jSggsH3BMR7I+h+5pwJwKlMreghBi/c+hgEI1zRZknIqU3eSy44aVGzW09U+Xn5L0wfj/E= +1u+XjG/2+GSQRv6EzCaWRQ== +pJpolZmpdjkaaSzPnxZ8yElNV4hiqHY+b6WpMB50Sa/m/daco24GRJNlL8Bm/buQ +xdXOUWOTkkx6T85mcNzH6zSlu7P9BcOAh7EHvdS4zxOtS3cnyRYoWkd7wGd1aMRKxOV6GcIp3qdrQACZt1OgKtG2X8Ug5xB7PRXuXinJJkM= +PRTQce9kzL0wbtVMaFHuX/dt4H8WFwH22DPdffzZqPLttVTviK6wrLoAb38Vzp0W +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX+NNLjQ3s7dTCionRz0FX53bLy7t8/38W+QbRkSn/k6fBpDHAETQi9H9KM4C/14NFP18j04UcGidK01HI8DQXS1iB91jNLybWJeWT+QOGbMh +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyW/8gUwPdqqjWj5njHrCfAtKpK8jdvX2JoJE02bBmKZZA== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY5P7pklwxOw6dxXjirT1q1bEvfiu25vBLYVWLwsqVoO/g== +1u+XjG/2+GSQRv6EzCaWRQ== +nvdnm1OK3Jn6gqZBwAyoEPqO8Rg1yIK6wyF5T9GpZxY= +EmZYXAoWwLEw6AEemfKeYD7dAwuwV/Y5SW0i/EiGH54OTgiiIoJ5DKeDr2Z23b/hi7QI/t68KhCH+HxXxTotk30oGLvkUwgFdM94oSQYvk0= +1u+XjG/2+GSQRv6EzCaWRQ== +unIQShDuzrPdTMMq+8wt+bvogYdwlYiMkGwWf63Ga3g= +9d/IppkQ2PyRmRu5osqwoODc5CDWJohhv0xlwFqB2IhoN9c+zmwSjJwlbpoMs79EmxYOCz+VexZABh6SG2CRHQ== +9uF3R/OfNUGVQlxbTNUS57cIoFaPqhrrnXficEPkiqM= +aqZ3DiBpVY4y61L8uxIxkDYLYA59C5wmzW7qiAI6evbvFvxuTloi+oIdrvqJ623ezEJ5p1OLdZ0VCv3rFLXWt0wS5wpoHvMetdvu2ROiJaobIXWjCjqaecfqQPN0ca2hhPmXxOeP7qW0EnWHkfhwZg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw6Vm3TUw8Lv4bv6ZRU2TmU8hXNojMo0qmLtZ+hZTHjq6 +1u+XjG/2+GSQRv6EzCaWRQ== +unIQShDuzrPdTMMq+8wt+U+Wz/v0Jg4gyP+ueTxOOc4AcsQ/hNjiq5uI1N0crM4B +9d/IppkQ2PyRmRu5osqwoJLIGbq2r9f/L/Nhg78pizqK30f9mR0UWajDI6Im5XYv6Yp7ixriqrxg1x+CNjyb/Q== +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUABXrfIFy3unwvGyjMmaKXnAeSARa0sWBILuhzTWPaTrcrjKyA3m1znDhqukw6fQ6pEAn63jUEzYiJVOtXn0XWCFgy7htf7csPJEhrttL4dhw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklbmV5J1RodET3Yh1/QCAz460nMx47+LQNMiL/R8TOs2uFXXpzYMVXcunKSLN219OehsoCDhwpRSVID5Aqd256/ +1u+XjG/2+GSQRv6EzCaWRQ== +NuQf5vOe0Oik9LWfqOnHdy/NOX+H2BGhwJI9uG0gx6tVV0LDgDKjt5zGVa6JPR2W +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +VMfVrQporTLzVAs+KagENs0jVofd/mPyGKFNcwoEK/8= +1u+XjG/2+GSQRv6EzCaWRQ== +sxMa/4cqt2s6Ko+hmWaWEifcl6/bwjn01DNZahA1tAw= +OAEDDa+aQ7uy1PKxSeroSW/gOn353zJYbhpwKG5CTdc= +e45CXpLCksCwwl+qz0OhtwCL153WIIB7r8/dO7BoNjrEpThnr297Ejqueer+xuKM51wTjoVVoWCq2gjKnDH9dg== +4IMnPiozlLMNPPcmwmcZbPKIGYm+t01jGPwHOqqlXjw= +1SbkTSil8XzgXK/aUkDPBq8OMXws2xvAQiCzL5QcHCTkiaR3ktH2dVNxKRKmi5TpJAOxU+OghdinX7oVFAx/Lsxd3JfLqYwbYW/SyO4OVmO17W3BN/w33noVEljvSxI6 +JIiKyjS/k33RBMRccr+b+pR9EUVPccDNK8t/6wKdwQNIr4sZ2ttmiaf8q90zDBdw1AEHFu+TyZJZO/TZbRjTvlj81nTdJiv04G7vkmlvbGjfztAr2ev+Ga2mvBH4YF/sW6oWWKZnXpobhu9INrVRQg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw9WPwFTtYvtbUyDFEE2SgTg/SQczH1eMbb7YCuamz+2z +1u+XjG/2+GSQRv6EzCaWRQ== +1hs1FfIPISXmd7TJ/r+Vl9dMPEgcDvEOlu1C3C1qpMWS8rh+CAbRFm5iXmz7ilkKPqVF7sbrHwdGLf+K4KP18Q== +VmVrGQo2zRokW/ZuO9bN688igcXPE7yYZaIJiLiDRRXaN+Ef3quooLUT5xQpwAgG +1u+XjG/2+GSQRv6EzCaWRQ== +UbJuac0dxLiN+5ocS3w2vb6NR4w8OuptZ2vYLZkEwyjtjE6EQUnY8kZBjMXw91b8Za/uM0INyKfIkcTMic2le1vG1tIbsY4NAxy9c7005dXEF/sqGLFLPxXywI0QD2KV3lhYGTIFsxoftW46+TY2DoKzgyZRZDYCjCdS5jN7gic= +VmVrGQo2zRokW/ZuO9bN6+HJdArV1b33DElozCQPNnvLNKQQtHNevoVLbf1iU/JbaoTpj76VkUPBqRLHqqaJyA== +VmVrGQo2zRokW/ZuO9bN69P6LVR0oSl+6UWthAPQB3mNhu2gEk8qrTxZZ2YVj788 +o2sgQHazONPoJDqmq4d8xsjERPoWH8JoaRDeqyIl36gdDkp5QJvYO+Mla5bvXpyb +bd+9QIe29SsrbbehlnMAB09DVblHAHKEk7Pw/HBeQyVnGSQtPVRcl6g4dN2SK/XBs+Q2vUN/x4CB8BkgNPUp2A== +o2sgQHazONPoJDqmq4d8xn+5v2tTUpCO8U6fXW/R+CFqtIFVblZn6Oh1NOALumM+ +DlzIWJ6kmiDAMD0FTp4Tbw== +1u+XjG/2+GSQRv6EzCaWRQ== +s+huLxQ7UCfPSSYad9iGU7bGybh4FBpMS7xWjHgqZqhkfSVFfkrvZO19eJRM8Q9gNGPkC25aEsh+qePC/Rb5qw== +s+huLxQ7UCfPSSYad9iGU37qZUvgsWJ/BGDTQZINlS546Abk3zye2dfrSMqBWLa4vq6Obz9wgbwjzmczPuScYFVkG6KyyRkXch4NaUKNNR2UM0Kc0cEi34mntRiVfSBC1xfYM2+GrYmy0zYialYm3sp5QE0sn6Z70rSB43aieOM= +1u+XjG/2+GSQRv6EzCaWRQ== +6K6zXmJJNC0xAXfJ6TCmfXX8ZMxXxIpirco7xXSn7dPeoPtziy1mOBK1RHoKEXs6 +I5csN8e3J/KIFkMa5t+SJENrCajRah1QU3uYF/z5CIDRw0MaiyCM2fRi6PpL0QdK +wlLHv6kT3Q/RmtMBN4nDARgRHJZodbVUlxKMP98xSUWZLqHxT63+ALyRl8UjRkj9ZgPwhaXVgbAH+EnkPOgvn5H3icsDS+6qgTq4EvJ/Ph1Ayr5+O522VWNouuh+Nu25eW/TMOgvGRhLmPYRASS6EBSXwMNHVwYQI6wRv6/SjkI= +VmVrGQo2zRokW/ZuO9bN68iARkW+2qOAi4bZ/j/vhWzi0utY9vdPwPxCn7JFSs9I +1u+XjG/2+GSQRv6EzCaWRQ== +1hs1FfIPISXmd7TJ/r+Vly2zekzOC5ssUL1V8jlhnk8Df2VI1IsRdyGx1Nj4Fyjr +VmVrGQo2zRokW/ZuO9bN6wMcoqume3QgnGKKcerpjjOagfMFVUa4Yn6IjT+fhDzx +1u+XjG/2+GSQRv6EzCaWRQ== +h0+APXqIzYQEOyw+poOgAQtD5D2A2VnZ8W4aLPzIrYX7EhijWi5n047kz/O9j8mIBOcmjJg1zfzbETkddwL1hA== +h0+APXqIzYQEOyw+poOgAQraxOLbnSj8RyhHDbBa3bRROceUrlermIcRfKzfYFWs +1u+XjG/2+GSQRv6EzCaWRQ== +wviPW+SHXn5CELvbZpyCzfIWQYKipi377C6iCgSuFqTRa5HA7QMAn/6tpJp2iaHx +1u+XjG/2+GSQRv6EzCaWRQ== +Jsvyu+OJlxxgWnhhtvuaanku2sR/0iBg3aC4QIN1isAhAIT7i6BzAg0iIEuK+qds +yi4USbg972Yx1t8cEq+Sa8G6FvHc2YbLbTqw+SKRWSXkp7gSAD8yYrKs+HSinb+5FTSIg7djs8FKVMAeZLPhq+nZOABJQiJSAx9Y/oFEmHmZilYB0S8uj9UMBgQTW5qtO2zp8bm3ro2i/9OxSwcLnrIWt+rxmq6wlbNFdldcc/qxMQzkJgtj4utT67SKgiL/YNXfs1K5VtrH3rbCEqInUpNl8DkdMeminJN16YG1MGQ= +9A0z4e7cwr+yotmVsrNffU3PtcyTHkpBUTP51niLILgBdrFotqUxLb+gugfUNdPByIu9P4AnLyAyhvvtlxuKOymr66njf2Jx069hVs8Oja0= +sRgqjbkCp0JTY71i6IpaD5yr1bzxGPNwLclSeXwxzo1jPqTrJMG+Or71f1eSnQwLeB9/Rb/iLp0AUuKtKNjWEA== +sRgqjbkCp0JTY71i6IpaD1pMqeBWiwpDG+N/zqBc9+wA9DPb4tkCuDcHb9iIrnbKkcEDcos+xoLvbR836lEpYw== +sRgqjbkCp0JTY71i6IpaD9z9G5z1tgUn7HoU0UqLUs7BX0H30wt7a3b2oVOACqFbfEtEcsXgAjOCq6IrGK5wxQ== +cTJX444NgL3VDcF24XeDWA== +1u+XjG/2+GSQRv6EzCaWRQ== +9A0z4e7cwr+yotmVsrNffcGYZmeoiobQdku68vaLrufsiuxP4PgdHvXKY6a6hylV +Hijs30SKpxh3hwbgfMdgtjdqDQp5tdX/SkvgcT1R0aNriJw28ajQ2hr+/0jaxTkHBD9esO2rNbFT6yF3O03HFoIFDChiHjvrQ0UnMbYg78g9FayBJtc7b9/cevS5FUPk +cTJX444NgL3VDcF24XeDWA== +1u+XjG/2+GSQRv6EzCaWRQ== +4hBTaWEn4eqD2LZuK5M9ym4eeg4p+Byf8jyEneh1GNOlYax1xnlbrMWw8sNi/fxf +4umSJ0NiqamOepFV/j7D/zptZ6SDe3pg/46YlA4AEh+8IpV7v+HDfg4fdss9vefJ +ujmcTMuWacoI527PHGC6aQ== +yoFtq8KDstGoS8N/pwvtcw== +/tSUgLBgzBmGkFZIpM4i8XoH0jW2npLvm+9a50QJUxhQ7pbZpBZ410Qf74ZxZ9XNs8Mr7S3+B4SpZ6n+MhSDVExOLjILc0L8BSnPTwDZojY7bSxuM0bCcPtSh8/reHRZ +8Irf3aUta4tuLJtYoptydt6RKGKtjAWJW/tjMvOO19IrXYEdrmYS+f4a0AfRy8+q +1u+XjG/2+GSQRv6EzCaWRQ== +1hs1FfIPISXmd7TJ/r+Vl/T2i5E2us7QWAoF81arf0fnOndNSwVEGRHo8XJ2N/afn10yTJs3e2VgOdeoGD3ICNayduTKXNkJio3jqwCPnOC34CGK1Yfg3M0aHbnF0qbCz4jmh+hWWUmIDo2Souh4/w== +VmVrGQo2zRokW/ZuO9bN6yuEJkW7NDtWtC4lviaMGRlWvQMZZmF87qNEzwGL2tpy +1u+XjG/2+GSQRv6EzCaWRQ== +I5csN8e3J/KIFkMa5t+SJDeqDKlPRRY/FSBBPJ6csdT9pYImto1Xyd5p/dFDUxDF +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uDZCYj4Guowyuc6J+GNxC+7ExiXI+OqLR+7RlpkYma+8HdbcotJiewZc0TGawVCZMO8IP+QhfoLz2bwP1nqYzAqWVdi4KGKvCMtUKa4TnlbzGjdOZ6z9JrdUT6i9TXBVsQ== +1u+XjG/2+GSQRv6EzCaWRQ== +viJsJdQuChPEovMu+m/oTwa2nn60sePbzT8fCKchiAQ= +cCKGdtIe7qSNBJ/C7EH9q1kLv7DOS/qhmjyapqTSfedVLP9JF6AGmsrMko5+cBM0 +CAhn8dRUItEbEErp4w+lX9tO1yhDltqrpu/1lMlkX6XUv+KQeN58tkpQ3d0vZO/YZZNjI6c+F8Ktu7JIGxObVfPGh/6mBnEhxKLtIU9l1gY= +1u+XjG/2+GSQRv6EzCaWRQ== +EMb90FFcbfUzo1/WG7lt1eU5UBhN0ruXREL6QXZf3FU= +GqUqYGmRl+hr+XI1B2wabDDAyxp3UQGXW5nSgVLbop6lo3RV3aCe/w3+Z4S+Kt0xz+ZRlavpureSVZGlt7eldQ== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT1NId7eTcvWP2D8jig7SHgz +mx3k5W7Ft7693DnOXXB9KbmlSGY9oDre0yMpuoKMrh0CNKyWRuqdjcZ85mwdCfM91B4zqdT97jS/m2g3fnap/w== +SAJBrQbXj/3Yv5Z90xSuEx7a7gDVMPDVI2TmcWrMbNpZnfidDGOknuOBCv3b962Kx3Rap0poWauVI+C6ON5Fpg== +WCmWIlk33/3XPV4HpL28c69ZEzmFJQoxmqH6ZfMqWxM= +1u+XjG/2+GSQRv6EzCaWRQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +sEDutDf0WonupGrRy9fpQcF5Va5jvIN+k+U+fmYCPIowwpEJPKvtPJlRfraV/h3M +69Uik37T733MFTHEnugne1m32oomlDt4UU5Ljjc1D9D4PvLNTB7C6iFSu+gFuakO +CAhn8dRUItEbEErp4w+lX9tO1yhDltqrpu/1lMlkX6X/Q4KQ9gaJO3pTyPTMj/sGFpGsOdRM+EcVFoeqSKhM0oORu57jXW/E5b8VtVpAa74= +1u+XjG/2+GSQRv6EzCaWRQ== +2BgtnQjBgSe2j4V15cu0Dm2XMMCmCwVJSxjjoUzXSZY= +r/0wCwJk3rNLiCV218dnRUUkvIsEBWEaS8u+Mr/2l/xNMKY1cor5WKiOyAgN+YtVzmaViZIQ/dcDlmJ8GHm+yQ== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT0+rOMOcbGFclG/jCcKe3nTdMnqO6KAIfcuBZwBHnc1kKyoEOK3zcmm/23+uF4vn+3FNJBoA91UgxXiT3x46QjDmSZyDI06svXdTOi8CKkcIwfjDRF6Ujky+uantQOv9pA= +VmVrGQo2zRokW/ZuO9bN69GFvmmq/nTHbXpTajOaul9+G3CNogrAtvwj5naTtXBkLELB4fMdjAjWozSvpJZS3MmZOXACNOqTEdsQ3V6rOng= +1u+XjG/2+GSQRv6EzCaWRQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +2BgtnQjBgSe2j4V15cu0DtoAXNyfevDbms0TtLHKBto= +r/0wCwJk3rNLiCV218dnRTrkBCvcvyaRRM5tN0a41zRHnYOiogFwb3MLTY9ZYm4dMIIujTbFFLkLMfeqASkYuQ== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT1NId7eTcvWP2D8jig7SHgz +grzBy7GUsBDuu0zii8vywQ== +1u+XjG/2+GSQRv6EzCaWRQ== +KL0iUMGm7xl6MMomGt0oXw== +uPPxGK64MnKQJZKou95/VLUmdE/OK7fVqH7ENaNbOQg= +UCQ8aAKvTghblJekMqQwxg== +lDZnNCM+YynWMsABEEsKrqo1I3AIuOrMO3or/QPxaQ4AbO97cMN4zn6chclW3i151CWrfqh9HoM/UiEV5JqHng== +OziAiqoRfo7FIe75SosO3A== +k7wxYTGqdPiSOHYIW9IKk+s0n87RoUnpU8ssdlSyiY7NvvPKS76go4jJ5UG6VuJS +KateciczQIv5SSuk5VEvWA== +1u+XjG/2+GSQRv6EzCaWRQ== +D5yUdC/X/2X8xRRnHhs0OHahuod7ipGPfybdbUJBKpPWS1JTrRZWxoCi8ZAQfyjU +1u+XjG/2+GSQRv6EzCaWRQ== +GB2nISfsU+i5RmZ8DRo+4YN+XjxeUMjbvGz+t7of97thh6/kkM7sdNWKHFacZKO320cmFDmM62NXXx5ByVBjtg== +hoCqJ3JcDOOxFSmkMqcwgkNZg7YhdSOdH6GkLDSTKvo= +ZFpbrqPVIsB3KH4ufFtdKtVCai4ko6OzhZt6p2E1Bwp4TBbxL2KBHRBlLVMQ+fUk +8UWeATmC2XV4LIrvDOTfGE2loSgv6pbumOy7/eZY7Y8gYSCHak7omiomEIp7z8zh +1u+XjG/2+GSQRv6EzCaWRQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +u9kTefckBoXakXk3CTY4UIj/f0jufIRnkH3ynvmhPcFrck7rPekjBPKWoxaJghu7 +KP52Q/5db8uomsPaNzzcfIBjLFJhlSafmTXNQwcksBNM/dWURKZ7tigXEGdz28kjUyJ1QCa3FloYIl3wdJs1eCU2/90k2ni1+EZJe4e886OYOeWxS+S7ypBZeLCrlwD12KOmubvHJcBWOuk/JFqwgEV/4SUWy0ybR/5S7ie/146m/Gu0nv0W29RxEK07Jqil +3PYuJCOiZH1sjGKjxSVU6D375Z9ZgehGZdANj6AiuKHBMJNPOLB5MQmgTUWROoA4fjmQLGPZuV9g3aw1jUGp1Exhygj9B4L9l3rPJxSUH8499UkZDWWz5lMm+2N9Z/To73cn62qXoDa9GQbQQ4nDYXf+FZ7hxOQwL0xvGFnUlUY= +1u+XjG/2+GSQRv6EzCaWRQ== +xEseMjL2u1zyCrXAmoV2Na6fJqpnL+CP/HaW2+j1AKLn7xZZzglrOzWv+s36xzMn4ACa0jVf4y7LG3AUn1J2tA== +hmCKbWWrRb4+/UkzNiAoHWug+k1X8xlXC4xlCvy74XaShT7URFdytUA3T0TczOy5PFGVSC3XqRrEB1M1gxwfGA== +Gs5/4CF0+TC/AxMFhocKRfWRy1dIZPIlJLzDBICJZWg= +1u+XjG/2+GSQRv6EzCaWRQ== +MzOj4ZiBOJDNVP8vi/lFQAZAqITjRF84dp3Z2sCcn8tGMMINDi2isbVwyJqk27Ts +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +lMcV7pLM4V6yRWTmLUOCgUZ7m1lBdwCVgXdmCls9ytIkvM+5sLlzt0i3d/wBPI16 +HslBPRwNoisnbgrxs4yVRZbcWkA7FZiBP8esA/l08OK3uVqT+XSoSIftyNMSAAQu +1u+XjG/2+GSQRv6EzCaWRQ== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT1NId7eTcvWP2D8jig7SHgz +IXNngFiySrZ19SPkSCMqPNm1Qo+QXsa3tJhorZy3l54= +wZ7V04uSzznB9BGl8vsW37jyoPdP8Acl2EpSFy1TAGtXnJartRbalXySHd6eDtGDP4U5bCyjzL77Ti6XVu+gUA== +KXRtA12NIhDKUxoVgaJi30EO1QCarO2WkYf3FTqBwxqirBAyrKEpiOpGO5Rbtq0SYesFuJ8iS9sFKfikt3tK7A== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +uISNfeyYNv2Q42tw0THAnAyeWL6M8kyxs8FpWaZGBkI= +aY1ilRUfFFER7CArZPWDNNQ7upxdJWYzB2KT2gdOCbyZMVun5y92c+G7qQWcAkafOJdbc6/K6gHJ9tFBCw1DjmOxo+BPluZ7weCacHqNmNiogzvTANHdPY6bHCd+PsWD +1u+XjG/2+GSQRv6EzCaWRQ== +TCg8jQstvFHGdSGuCc1KgaFyaoNWzNonyS3v4IGn+IfwSUJCVAVYBWozbIU/t2yI8pruX4yuH0Gjj0heMNrqaG6IgoCDI2BAqynyABGHbdLeRQQcf6uqI5Rxtb73HnoxLFTuvRQsr5tmxXNXY2A2Vg== +Mcnw0hYRTOqKGEpOyBgpBw== +gxlR2PLWboEZkMexyPwRCiIOzb1aU8OaYgy7xLlrmduA18IRSkZjwCBXwE2Xp6Ja1w7Mb2bPvsCrP0uXB6ImmmuY1krORwxw0JUoq4FLcos= +UCrNFjv1VaMMEy3B4FzV8obiwulaoWqVBA7sXzlFLro= +VmVrGQo2zRokW/ZuO9bN68OzLq2MgGMAx+4zQqOWwjXCHE94r7mbPBA9XTDctoqL +VmVrGQo2zRokW/ZuO9bN6/jcfOTLNldQyZXzkxdlxzc9zg3Sf9DhTFva0LpW1q3k +VmVrGQo2zRokW/ZuO9bN66gVyRtF8qKm1PSxGVl6HNUVci9c7APbYBkpw0BKy2R3Lioao0VA+DkaB5AV/mms9A== +VmVrGQo2zRokW/ZuO9bN63BFmZ7ivlH94JUGp638GVflgqQ1nvRPFEtqmP6VjjjqHX7O2AhQKEVbwkeXBbA5jw== +VmVrGQo2zRokW/ZuO9bN68HhOPQXu4oTax1KsfZyiyFdFDnoB/tHIxtbJ5anMmeu +VmVrGQo2zRokW/ZuO9bN660fmWwEJcvP9dAG+whDSI8uhKTEdGxKFtrutTAWnl8r +VmVrGQo2zRokW/ZuO9bN6/tRxb/wN9AtJtJU22fO8GadHU9NMgeZQncWFKlhlIgD +VmVrGQo2zRokW/ZuO9bN6+UOF94flNEg0zGJRSI7fXaGFU1NJiFQR+ImvIV9qSmO +VmVrGQo2zRokW/ZuO9bN6+UOF94flNEg0zGJRSI7fXYEqTb/7Ok4zLRpW+70ceGe +VmVrGQo2zRokW/ZuO9bN64AmMQAL82p7VguSmnhEZ6hLrJ5kBIzsc5tB0oBv2+M+ +VmVrGQo2zRokW/ZuO9bN67jRPBnb6YliAbYhKWq82NwcOtZt67rzMA8UUtJ7DEp3BAvbUi/JZJpcCa/f4P+Bgw== +VmVrGQo2zRokW/ZuO9bN66OJLO5Ju+MbANF/1yEuP+gKEqCm24nW8fx9sAfrYesYJtqSMCle8eRjKv/Sl2TqrA== +VmVrGQo2zRokW/ZuO9bN61qUBcUSC7RUve4rKSjgUJQVYApbD3gE7ZiNK1YZb5Sf +VmVrGQo2zRokW/ZuO9bN61qUBcUSC7RUve4rKSjgUJRfGmElip4DBK9DatLtFULH +VmVrGQo2zRokW/ZuO9bN61qUBcUSC7RUve4rKSjgUJQqUPFbEmDCe39RhBoDIl+s7gfvqQQyUPCLOUM0ecC+bQ== +VmVrGQo2zRokW/ZuO9bN61qUBcUSC7RUve4rKSjgUJQ1HR6r8DVgg98vEI+3RQ5DZPn8k4P24lLukGwUCDtIVw== +VmVrGQo2zRokW/ZuO9bN61J3nzXUnT1eAKT9Wzo8fbIXPPOU2UJ+nRNj8533fB2X +VmVrGQo2zRokW/ZuO9bN6yWEqfht7pe/XepbCKScuC/xKzomUEBzQfvkhIpFCZXI +VmVrGQo2zRokW/ZuO9bN6w4G4e082/GXwvTFes/2c9D4qgNOBIos+8hcH+9F80qg +VmVrGQo2zRokW/ZuO9bN60g7LF9BVGGxX4UUGy13KM3l8sVAXnZXlgswLpKM1resiCZEqoG+/+Lj/PnMspoQ7w== +VmVrGQo2zRokW/ZuO9bN60g7LF9BVGGxX4UUGy13KM3XiTgjinSYUJUlaRCHAGlVg0MP6znueS283ddAamX1gg== +sQfTf8f6jxyRSx9SNPNEtQ== +cJ1Wblh8gF3p5U3Kb/RGlQ== +1u+XjG/2+GSQRv6EzCaWRQ== +V1ScATKwVuxN5IABiDyoJZjIuKObRPtYuA01KmsSWLlGGWgSFKdMsHqhXpZkyR05 +V1ScATKwVuxN5IABiDyoJYZrsVnLaxqHITnbAierM5ptIs//Y24gnsyAah/g9TJH +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4u9daMDHK700A8L9Yt6A9aSf5jgoZR0fhg62RnUq8JMj +J2qwAW0nzQmbo9HpSOVZlls8HVNZZa3niK9RcAZZhRGBQj1ppGSDDxPdFq8eTYo6FWEynTKLrEUBF+dufTx+oftnH3SpViDHRos5eJpOXZLAq+NZLYFIsRtFei6Kzj51 +1u+XjG/2+GSQRv6EzCaWRQ== +2KB6FUymfVlSXKS0i/5ug8d9ecb/r7dav0sN47NAJtU= +icE/1HXqa4P9k/UugIr7hiS6P9OYskJnKKex+xP/icPzqLyDFDzei5SuttsEByH6hsHBIC7+lpa3ZhxrvD6aJaDVxDXuGrpzJen87EBk3us= +1hs1FfIPISXmd7TJ/r+Vl8wmau3SjlrAOVP7jo5XBOg5UPV0iSkwPv5kgrTNpseuKiyqJjvWHxvHK2iUVJby7g== +VmVrGQo2zRokW/ZuO9bN6+EocMRiC9L9hAVGHkM+STXpdaJ9Gg9L+2/r5c6x0BYR +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdZZGI/Bm8B56URRt92I2Sic= +ltIZHHUgft++nMUXBsBki9zUIO+KQFOzUqIhvbD3f+FNMKzrTV7bWmTdcsTgEIbzcpuJLb3OEax5pB+V5es4pQ== +VmVrGQo2zRokW/ZuO9bN675hS/LcFlcq/BjQUaJzFZ1NDnzfojKO/3lzAqNEW7axhBR41xHL8R3XjM3D9HqNuU6VypV/nQwEziWPbgpwWY+5Tyy42B8oGourZU0/E6ZL/l3UeRMxn4Td9QDcqDRuO3gHfymrThtaQJw9Q5HJcvQits+i5qWseL/YffzSpCec +VmVrGQo2zRokW/ZuO9bN6yqz1dlqtk3pOCKCyM/1IIFUdQhNYYqY7n1oRKZREMJSNyOcM/zDrS5FCVXVlVKI4A== +VmVrGQo2zRokW/ZuO9bN60lvcKqcaQTU1BtN9b6GQkKCh3a9NSIORQqd0UoHfge5 +1u+XjG/2+GSQRv6EzCaWRQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +LQf+folDfiGQMcxC7dO+lDBCbZnG5zOWwHGK9ASM5nmsGno7qe9v/ax41bQSa6l5OTlHRDTiSskx5HniW6mBsQ== +RPDz9BzqpeCKMTW9YCV91bc03VIMXGUhS4opsNtJ+NJKLcUinuKvHdpNAE0BZtochgUxCnh4A+AHED5dJMKDBA== +sOGOvvpsg0kUOYaWfW/HeNbUCFZpBQ9FqOxJ4ZRqxzIMPUHZJ94Kt8VxFQ/LkG4BrOsDhuI4ruCuzwckibBWh46gcte7aYqkHf761Pc6BWg= +RC8IWtD8oQ+ISFVqRg4bKSW04wSMM2Z9CjzWrQPAh7V0nNy4IO0ezXpVsA02kwCn +1u+XjG/2+GSQRv6EzCaWRQ== +tQDb7SQIT3JulrlZ/b7u3XdjEC7ESHbvFrvbY038JZY= +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT1NId7eTcvWP2D8jig7SHgz +YdyoD9gMz8atqH8muJOm+Ll06HqTCBCR1d3b2W+SGNY= +1u+XjG/2+GSQRv6EzCaWRQ== +2VsuHWLc2PbyPgC+gaUrxG2G41IQ6UaVHfeblBfCKn3Cf/7LjLLRf9z4FubRI4zv +DlzIWJ6kmiDAMD0FTp4Tbw== +1u+XjG/2+GSQRv6EzCaWRQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +xEseMjL2u1zyCrXAmoV2Neanxx5/YzzawG2lRv5hB+t76SC0IctgRXZvNn56+Ig1ubQawfDUQRrcmRqwOZ7V4A== +hmCKbWWrRb4+/UkzNiAoHX0e+uZWXcaoT5CbMnrUDl6CsrPdxya9C2uM4/WPuo9c6p2YJn9KP3h54Wc1c4iDuyy5C7BS1e2g5nFMgY+TDUcunx6wPw5rmpS7Nk8ook44 +Gs5/4CF0+TC/AxMFhocKRX/kqiBLyDVESab3Sruf2Bixqzq7gsfedcPnEv/p6UXB +1u+XjG/2+GSQRv6EzCaWRQ== +MzOj4ZiBOJDNVP8vi/lFQAZAqITjRF84dp3Z2sCcn8tGMMINDi2isbVwyJqk27Ts +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +lMcV7pLM4V6yRWTmLUOCgcp/K74eIJxjJHbTaVKvaXiox5BmDOqTGnjWbGvl5Fup +HslBPRwNoisnbgrxs4yVRZbcWkA7FZiBP8esA/l08OK3uVqT+XSoSIftyNMSAAQu +1u+XjG/2+GSQRv6EzCaWRQ== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT1NId7eTcvWP2D8jig7SHgz +IXNngFiySrZ19SPkSCMqPNm1Qo+QXsa3tJhorZy3l54= +wZ7V04uSzznB9BGl8vsW37jyoPdP8Acl2EpSFy1TAGtXnJartRbalXySHd6eDtGDP4U5bCyjzL77Ti6XVu+gUA== +KXRtA12NIhDKUxoVgaJi30EO1QCarO2WkYf3FTqBwxqirBAyrKEpiOpGO5Rbtq0SYesFuJ8iS9sFKfikt3tK7A== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +uISNfeyYNv2Q42tw0THAnAyeWL6M8kyxs8FpWaZGBkI= +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY7KZMO/ZVvs81Dvb/IbegIIFTH9WoJwcOQCgdxqvvMKCQ== +1u+XjG/2+GSQRv6EzCaWRQ== +RJO4PIle9WJa6nrSrDK4/acyIWMkOtAojDEaZjGUWntDFAELN+iKcRBdMcdsMLNDVe4SL6OWsMKGwLg/KAM/TEYc/HHjJGKNLscUn4D2BzQ= +Mcnw0hYRTOqKGEpOyBgpBw== +gxlR2PLWboEZkMexyPwRCiIOzb1aU8OaYgy7xLlrmduA18IRSkZjwCBXwE2Xp6Ja1w7Mb2bPvsCrP0uXB6ImmmuY1krORwxw0JUoq4FLcos= +UCrNFjv1VaMMEy3B4FzV8obiwulaoWqVBA7sXzlFLro= +VmVrGQo2zRokW/ZuO9bN6+b4NmZdyk5c5LHWgu6MLaKte6pFj7SOCW8I4rmGl0mM +VmVrGQo2zRokW/ZuO9bN63gL+NYGsT8A9ybxZZqQV63Hb/ut/ai+kM302hcAEJRwZL7ytjctI1y9OyTQHitVUw== +sQfTf8f6jxyRSx9SNPNEtQ== +cJ1Wblh8gF3p5U3Kb/RGlQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +sbAVO78DchSWkYbMhox27hgIvZs06l1RTZnD0/enKaMpWOajgHava/aY74uqJG3N +aWYZWD7hPqKlzlWWEJ1xN3QqDMQsB2DQzcpRyaGC1liL+0YHyeKfGB8onX+iDlA9cFyl2cj1wxO/Jr8n1M1wH+J0VdA6hiI3ewhK5HyofKWFXoh1Di3FHBCk0Mp5i27l +Gs5/4CF0+TC/AxMFhocKRX/kqiBLyDVESab3Sruf2Bixqzq7gsfedcPnEv/p6UXB +1u+XjG/2+GSQRv6EzCaWRQ== +6CkLE5vty/NbnywWy3iWahp+6HrIDDUTlX7Wuo25xfTL2JV/mlFCtzUtgmJlgiN5ljX/x+aDdpp/KDOl81ZxLw== +uTEK8Ng11d3ix2pA+DD/afa4+lge0mDF8jIYu0ES6knXDT7VzQAcCe5LhWXidX57 +L0eUthVnpkGsmKFAX6d+uKItPv+N8UKkEdcZ0HZF2G37oXxzr916p5G6V/GkH+ULTqGd3YcBByDnHgry95125Y25CDPwuMdsuMwQos3x5h1qNGEKs+sVNL3TUHGaF7Ht +1u+XjG/2+GSQRv6EzCaWRQ== +c+eZ9iERZLzrToMbbeDzTg2bRa2LBcfKbG8lAaFyHT1NId7eTcvWP2D8jig7SHgz +IXNngFiySrZ19SPkSCMqPNm1Qo+QXsa3tJhorZy3l54= +wZ7V04uSzznB9BGl8vsW37jyoPdP8Acl2EpSFy1TAGtXnJartRbalXySHd6eDtGDP4U5bCyjzL77Ti6XVu+gUA== +KXRtA12NIhDKUxoVgaJi30EO1QCarO2WkYf3FTqBwxqirBAyrKEpiOpGO5Rbtq0SYesFuJ8iS9sFKfikt3tK7A== +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +7cZLCIyMlhVx/ty2znkcZk88zXX0jr6WhPmfnm1FMY7KZMO/ZVvs81Dvb/IbegIIFTH9WoJwcOQCgdxqvvMKCQ== +1u+XjG/2+GSQRv6EzCaWRQ== +RJO4PIle9WJa6nrSrDK4/acyIWMkOtAojDEaZjGUWntDFAELN+iKcRBdMcdsMLNDVe4SL6OWsMKGwLg/KAM/TEYc/HHjJGKNLscUn4D2BzQ= +Mcnw0hYRTOqKGEpOyBgpBw== +gxlR2PLWboEZkMexyPwRCiIOzb1aU8OaYgy7xLlrmduA18IRSkZjwCBXwE2Xp6Ja1w7Mb2bPvsCrP0uXB6ImmmuY1krORwxw0JUoq4FLcos= +UCrNFjv1VaMMEy3B4FzV8obiwulaoWqVBA7sXzlFLro= +VmVrGQo2zRokW/ZuO9bN6+b4NmZdyk5c5LHWgu6MLaKte6pFj7SOCW8I4rmGl0mM +VmVrGQo2zRokW/ZuO9bN63gL+NYGsT8A9ybxZZqQV63Hb/ut/ai+kM302hcAEJRwZL7ytjctI1y9OyTQHitVUw== +sQfTf8f6jxyRSx9SNPNEtQ== +cJ1Wblh8gF3p5U3Kb/RGlQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== ++lzLYqoQ0EreL84jN73LzkRkzIA83nRpDjgtA2b6HsfLlk3qQ08jeHIEnv1zEEh5DtsBdqOMNFikXk3/9aQ7Cg== +w4jmCS/kxlITpTLK5ezfwaOotiyvPbwFNhVrk5TZl3UoCWi9A++xJJOJ9Aa9UNXtM37+Xmb1SBCeJyiRmCFLtQ== +Gs5/4CF0+TC/AxMFhocKRX/kqiBLyDVESab3Sruf2Bixqzq7gsfedcPnEv/p6UXB +1u+XjG/2+GSQRv6EzCaWRQ== +6CkLE5vty/NbnywWy3iWahp+6HrIDDUTlX7Wuo25xfQHKpWxhUohgmgQVhz3ftjr+6LJu7Nn0zavA+4delF7YA== +uTEK8Ng11d3ix2pA+DD/afa4+lge0mDF8jIYu0ES6knXDT7VzQAcCe5LhWXidX57 +L0eUthVnpkGsmKFAX6d+uDMxEKKefTCRPxLapN/kTgE= +VmVrGQo2zRokW/ZuO9bN66BKCjNCszk9OrpNhI5/lDKVIVWzFgCJKcjF8B2gQmH0 +VmVrGQo2zRokW/ZuO9bN6/AjGw7s+1fp5mLt2vsBgZChSTjmCbLl+IlX2G7k3f6M +VmVrGQo2zRokW/ZuO9bN63gL+NYGsT8A9ybxZZqQV63OYMZGRv5n7o3maWLfj4sg +YH231WTDnzQG3bFilBiqIg== +1u+XjG/2+GSQRv6EzCaWRQ== +5QnPArxk3YUw+SK3Ek+bmYwKlDcszoeS750KGzFKtUCsKQJMKz57hDGzJ13+V8f6 +qKiQto1Mg19+mk4MC0Qyno+n31U2xaBfScynGItdFlA= +9MjkQZlYeGQ2p2SsU3BCwUGs3x5evPwwdBgt+S90T6wVSkDc2DbhJNTQ9CNEqI0WR6mOdFZp1GZ2j0q8X9Oh5qfRxAWRjObfsM07o4aSUac= +wYXwkdeg3SHiLLFBdFd7x9FkOEIJ1IJawZdL/1VUJ91+PVQlTFD0NFdtjd47QRZsp5GYLsIAGyUiM7r0UkMqfw== +YWocLyuwJOlEJPhi4S5IIMZzheKeIep7CtuOJJl4wYmTFQalfs+7zI1ZvOxtY/7p0TzwLyXWgmjHxUuzemJIrY7CO4sYrHHuKxvsZX8tRkk= +0BzZlwrn+EZHwB+aAGLzKYG6k7W1nuepU1wt9Uv69DyAOxBXzletESCKCKaZRXA9nMkne8pMQmozTiLgDyOHOg== +PsDdTxSrRX/dITzDjfn9sSpd6qvCaBFHjXYc23OzMU12aWnTey+FEczimFLZbyOVteFrDxGJRmlFD/F88jcAeA3dhaHqSSeiGpFGE9YkwBY= +1u+XjG/2+GSQRv6EzCaWRQ== +RqVoQzk6IR4JMpR/e8KmeOh2Tqc5m3P9w6Y4h9L6IWE= +1u+XjG/2+GSQRv6EzCaWRQ== +C+35W5EnWcdohnWDdhY3U4LZza0CXooGoP2tHWIqZirSjmthbZ/1q51XWlQ/i1Fb +1PZrVi4rzkp2jtFwplJMv4xS/hxrY7O0tEh+sre4MMW/fM3b/4YMLtOYhQE49I0gKb8sXbVtSUrEjspPPJFfhw== +CAhn8dRUItEbEErp4w+lXyfKXxXDhOK8Y13cqRVbOCfhKbvdex7OjupCU/YrP/GvbdpVx2a1yoVXqFhzhAnb4PWqTd9UEvbTiyAw+DhcegCTzQ4vorEJPpM5giZjGot+1/hvmeaimXv/DR2G79t0eg== +1u+XjG/2+GSQRv6EzCaWRQ== +y0mf7vZk9Gzg5n8pC30UOVAUMGeuSJdfKl4EUiyD7nURiVUeYjQjxLElhSoR3bNV +2/XryqUzwHCgbYFyhZW9WnmAk1jY/jK0oPXrZQ55yqc0+G5lGKhFT9JYJfp8rUbVxIo8AlQWU5oUedsIA4zb5Q== +ghdF4G8YV9yNjU+KsNcQR85CT1ozzusVQI4giY22sxScoZZcUBWP2VXsKbvwAotoCo7sJ/l3iwhmdlat45lcYfeKMumOBM2rAbPy+SPVkN9fRTJHmfQCDO6strAruBpH +1u+XjG/2+GSQRv6EzCaWRQ== +ojcUvW+Z2ehEJ6yMJpmY+9UYnB/4hbTswrKo/LDRiWU= +IXE5utwGYOdlZaZ+BFyYQe1G2Tnq0CGGgT232Xu04sUWUEYIotwb3bTJroY57YnW8kCPSAVUyslXJprqpbPKug== +1u+XjG/2+GSQRv6EzCaWRQ== +IluO19DOYseHfsOaGSZp+R4V7B9GVY5oAk8TC8T6Gnc7LrdNbGMIhnCSWofIYMb9dQLD32WGVqZKgIAXAu9XxvNHlWgZHFZlmDg8w7ZfYs0= +c0ufQ97F5najN1prFovGfgk0GuVcVIrGJkW3P7fFMPUiGaR3aLplwVVbNzgbsqp+0vGQbpy2CAV+ubAdUGLAcx/dacUKuBlHZaJjoF7OmVk= +1u+XjG/2+GSQRv6EzCaWRQ== +H+FNXRb0Umc4CYTtn00ZPsezhzhZnftPrifLFVTEL610sfwvMP4rwX7H+VjgeuAIsVL0VyE7Z9UNt75beyU3tw== +V1ScATKwVuxN5IABiDyoJZjIuKObRPtYuA01KmsSWLlGGWgSFKdMsHqhXpZkyR05 +V1ScATKwVuxN5IABiDyoJYZrsVnLaxqHITnbAierM5ptIs//Y24gnsyAah/g9TJH +V1ScATKwVuxN5IABiDyoJX1bESOEMJu9Q4TMxlPdlqt9I7l3T4IqY6aVA0L38+LioKZuR66g0LUNOWt4x4gymQ== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgdBHeuqRUKH+enUeTo9ZkZgIkGXBBjkWk7rTGnBK4iYFAIC0JfbJeWcgY1tHoFo1z9NuwLfCTGj0sqfaIJKcDncwD4CqCGELOtRsNgIStfwUgi5bX2adkmr6SDjjmH7JWw== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +aZA9PYUJziqBP2H5HskmSVCfuC3EjLFxSG1cBIplggomTSKkos5JazKt1IJCco3j +h4QFtaqS3ViS+INB4rxJt9qsy/cEarJxhur1+b1Epq0= +1u+XjG/2+GSQRv6EzCaWRQ== +1Pmp5sZxqBziC3LYj2Yu9x/CfkN5l5V1iip1b6Q5io0= +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +PMsnmYnXwTnywF6bV4DtbjBmhBKkqmzzWhgWTMQKUtmEG7+uMZ1O0FzmJK/3hQ4e +M52FSgXgnXx2Hd7dfJpFXpiKlzAN4YRV/CIkbNeNVwQd62ZPvhrLx7OdKmSaMiHnbJA4qgZq5m5KLCmSyuXkug== +1u+XjG/2+GSQRv6EzCaWRQ== +1DMUmhwH/xlvvlF9Wem6fpbYbbn0p+nKYrV/0hTgwyo= +trSptuLx8nV2jiPpFv9oLQZzRewlWovNpEZlX9ZHqdNJS+w8pfaVBXwYDtyCfN1c ++pBWk5LxptzizUTsHnZAzw98qUHepCXnd8PKllvEYMVQqP5jFGDm74q9ET0oP4ac +p2+bVXgc/OGpnu+3Mkh/HSvD2ZS1eGKMWD4ICmOkJ7Yc2VZQ033ixPInbY93yISA +1u+XjG/2+GSQRv6EzCaWRQ== +m92ezhFeyf+sWceUE1djmpp4NJFeExHTa4QK+T/1NCA= +cGzT2936rNP0cRSqygcUrFh3FmD38wvnuTWk2SyzI59xWXnxNVRcrLh19A4g7HMO +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9g04MsbYTOIAGaquwEscysPIIK8VGgbwfrEcungom0j8w== +NqhMbY8+EQI8zhTG6Zh2IxVqZLUKZKeArb9KXtRVtI1zza2alI0lIMMW0lO74psM +7jSw4GB3eBRbmC1MzdtYH+CDxdUbtTlhKtB1IFfKlC11A8M2REU4c77xpWdfeCN3AHrmvn2VSqCGaY8uQ26pIklaULHxf7ArASyPUJmvV1I= +2Yi69n4qf67UzMPLWBtv/o0+nYhkxACwgdoHt5MRwUJTAjuB+cIzQ0tyb1Ai455T3Mb019U/W5B2/n0IxenWZw== +2Yi69n4qf67UzMPLWBtv/hkw3ZwgSmwqGRNu3ZH6b2OOBTGAKTRGhfEMiWpNdvKr1IDi0DdhO/3uYJ0NbKzSZQ== +1u+XjG/2+GSQRv6EzCaWRQ== +zCIGDIqtj2dlccTvTm9UXWCmwqyMGJ0YiWI74SUSA3VQsERFMQr4CRCaEeXs71Sh +6UhqZyAo5HepxMPmTLDSMd1/xMbuYZ2J2bf1UogTJQ2R2XIoxZuqn4nnSgEZPQLR+WbxECMBKJ1Nap1j+2FSVw== +p1hQ9hJvEa/OWih75Q+Pz5Q9VMnMDN5bSSFB0OHsJNbb8yXQEa6Zf0Bm3WVa5ah1 +N2AxSv647SE8o7pH02PLTRW2x6MA9cDI2cPf/O1sMfglaiJwFomfzciuBxQr9Mgy +1u+XjG/2+GSQRv6EzCaWRQ== +ruTTNFTdswRs4Mc+srnRk+bc5hfn355hOu4LLRIyPbo= ++v5jFclfrXth0YtG2QE2a8MQXOKx495Y3PXsDHZfSc0= +1u+XjG/2+GSQRv6EzCaWRQ== +/gPwyveRFK3/9KFETEewLWQOV4XftFZCVsqYRjt77pY= +8Ikvgoz7G6LjRTgHz+Nt69tOOXff2rqyevdQTuhV1hI= +5zNnqVXnV6Chbrd3pP6XYA== +1u+XjG/2+GSQRv6EzCaWRQ== +WemqAXk0niM1sSsUg6jdeTbtqaCdmhPPCoo39cSeU2Q= +8Ikvgoz7G6LjRTgHz+Nt69tOOXff2rqyevdQTuhV1hI= +9uUY3kk/WJwpmBRXbh6HKA== +1u+XjG/2+GSQRv6EzCaWRQ== +JPz+oc/K3sEZgugfA6X1X+6ySKjCnFVofSBuWB28FvwjJ0cxcTdMIP0dE1OWGdx1wckqCFuZ1A0BHtul3R2FenqJEjWFvzXd59KNk/4oNeX3qhLVk6GAumseqXOY5UlcyX8z6CyZXCY3UgJFtUE5Pg== +8Ikvgoz7G6LjRTgHz+Nt69tOOXff2rqyevdQTuhV1hI= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +6U2650ptI+oPKvxPm5zL45f5u97dfSzSVoHpNFJ+A4MFNCWlaZTVNSKl0Rk3SDuoEYYJRzfeBQx4bACUrDkAjmbEaFZ0FlJCsslKWYCjsUJlAX07oUmlnA4xReTp12bAMK6jKv4AsTbMUuUDYgqdHQ== +8Ikvgoz7G6LjRTgHz+Nt69tOOXff2rqyevdQTuhV1hI= +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +CBfgBEEA7tq4ux9Gx6WS1/M+ABiuXumx6BCqjcbiLqY= +QyES93W+LGyOBhDFuc2jKi5yToxLD4QuqNwf24V7DJXtBunEZ/LW5RXnsZ8YDCPH +i17nysO1t1ITOoDCL72Are7OX/eZStswDWqnlSCNraw= +4bCaCPES2AXNnKU3FRtflw== +1u+XjG/2+GSQRv6EzCaWRQ== +ItdSTWx/Mvr9hjd/GqTKHFUc1ht+CkWsmbY5eJzN14d1QIUHuhUkvZ2B3SbMI9Za +r81T5uZ9X/I0cCXfT4cueD/dyVMDPM+HKjMgDG2I41Q= +O47dNRnVwHIis+DPEB6/DX7lujZdEMbQ+1VvQqZcHvY= +f9ezXD1Yr6Z74a+8ZNKcY5C4T3GB+JqRqTx6kCZaunt7KU5rhnNcFLF5SLwYpRtgbxSjxU8ZRLY+jonAkEjZdDAYSTyNeUaynZmhknT7P+b0h8eEvymtMuO6Wkf11w9c +VIv8qOuEkjvP/JYQFt0oyAn0GIUqowr6bnEiQPoUOHSlhKLqbRzUBmmoz2W+WdVw +/v5heaJ9sRBLbNxUdXuMAIab2VCV0zV1gjiXajR7ubuitmu8NUoWbMv7onWRbB4I +vKHUDXVnyBYBzViFHpiy1GAIV6YVlhk1T3u+0XLNpzsaR2oe30zpnZ+OO6m2PV1HJ0LQZTBDY2T2eEAaz4i0/Q== +VIv8qOuEkjvP/JYQFt0oyAx6pI1t0s+phkJ6CHCmcco= +hcMtC74tsxWufkV6jtbL0vbEaXA7ze899vAQ+c+zk9xBtmsuNgTPflBTHSPRtjON +wQq7tqChd8xWopyCcIMbNUPGH6B2NVU6ZXgqujtUaHVpn7dliEK8pKYqylm3P6QhCks/8WDXDe2tm3P8mJ5YVw== +QKNI/Y0oXWCAKvQ5eNPPzEaieKXZOQFim9enUTo051oFla/MESYtuaCc9BGHc6O/ +vKHUDXVnyBYBzViFHpiy1KhbsCJXt7qYAWGl4UJdBiL6IquFPYcBNZEQBkN/2ArH +vKHUDXVnyBYBzViFHpiy1Pzy6O43SQLOqf4JTpfWxFlMX5ikhfn+7n32L3JBmeuw +vKHUDXVnyBYBzViFHpiy1Pn10/PuJ7Ev/Y4m4TSUWCA/WH0WSNJq9h1q6Vb9twDbMSJJKOq1fIHLUekAJ5KQvQ== +33PnBUJTi+OsoyuQ5/WaYg== +VIv8qOuEkjvP/JYQFt0oyMkX0llDCaD1oU4fhPFRY0uaWBpHlbVx4RG9xvyZW1q2 +YHmVAP+XfXvh6Do8nF7SSw== +1u+XjG/2+GSQRv6EzCaWRQ== +fVpK7pI0vuTDeI9U+N/SkXV9wQX47UqiuaMQk8+i41Q= +GvmW0nHk58bq7fXWqIlW/DcSR+3AY/zEF2yfl3+bhiM= +ME8Kpf7OhCa4sOzsqiPQhw== +VIv8qOuEkjvP/JYQFt0oyNAlKIvgmFkQIyA9XLDmipPTgnEMlECyRwMVHf2qFFQ07eAxfE+I3ejc6oIN3Gf/Jvaber20F//2Eb0FzBiaYfg= +YHmVAP+XfXvh6Do8nF7SSw== +0lLaiu4mZY7mSMLrB3JPhg== +1u+XjG/2+GSQRv6EzCaWRQ== +CaYPcoo3hrIrfdb7ag2X67CrplDHZqI5hNTxKQEQ5NgnUTyt6Ciav9i1BLiiJLMI +6UhqZyAo5HepxMPmTLDSMcee5/Z7ylNjOrtvxkZQaEmC093m6D+eWbq0pikBWpRTYEZm4FeNSV7+thWRruqnEDx0JlyJGR77prg9yuRJKg0= +4TatgresHQvhPdvLpNRT9/tOXOKiQa1gutZyOO6FlyM= +TQRmC0vOvJ1P+lfyS3Os4WprL4M30aPh5ZZ8aCvQohlxZYtSKbBmnILGnqXGWfhZ +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +p1hQ9hJvEa/OWih75Q+Pz5Q9VMnMDN5bSSFB0OHsJNbb8yXQEa6Zf0Bm3WVa5ah1 +N2AxSv647SE8o7pH02PLTRW2x6MA9cDI2cPf/O1sMfglaiJwFomfzciuBxQr9Mgy +1u+XjG/2+GSQRv6EzCaWRQ== +mDRkPFp/j8KULnXRVcN0L0yqOW8umfrqR7aShWloZvEk+TnIwHULtqQ2QK5aDlL55LLOXmMtOQ4V2JBVGBsfzyMkX88uEXwXDkS3QdUC9O35X5js/wsq9KXlJbpgSnV6 +QjEYNJp9YKIp2aaVIb+6f6gVzO2t3XqwygKKaudSGLZwsx7jEUvqLi1wMpsVMRKD +1u+XjG/2+GSQRv6EzCaWRQ== +o3kxTO4RwuXO7GZ9wpXG+a0z0P2h4pccG1GCZJnRu2U= +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7PqEY/t3psY8Uj1eii891uGnTMMS0HKp1hOus8shMqxU5v5gbao4nwbk601ukDtNI +1u+XjG/2+GSQRv6EzCaWRQ== +Nd5rVySRefuK+cWgFJnVfBAdTcp/n+9a7PVBTnI8UfdxG9tmHzlXLaqopg0Rax4Km2+opphtA9Kg0XgHI2K3oMQw4/+YV/khxJvcs2tvWFo= +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX8s6Egsn/Ecj0xE23wGfbU1REI0ww2/VyZZCPSRphVZGFPbP95GY0G0wmFIK2h4Smw== +1u+XjG/2+GSQRv6EzCaWRQ== +Ab5B1FuMu6QDBtV+HE9XYL88VNgWcI0oAPnVxRFQLNrnxx+Es3CNBiwMMggj8m99 +oAptEs9PzyZpwE9U096RuO2jQBGmre3FmrEJfIo+zUJgeZmvmn/jzcPJg1L8TYl+ +S9tzajN7j7km7OLOHRXxfStxcqmdbj5DFbF7yR/TmmftBkgkgsHLHa3sGJ10CRau +1u+XjG/2+GSQRv6EzCaWRQ== +ZVyfYzP/n5Z7xgLr646p2cso7EYQKXARTQgN3etlAJM= +1vTE71QDj8abeS2qCOg+Mq3xL/mJAEC4AvMwgLb1Q1Y= +sY+h3BA+xMy9GeW34XQAabd7Wi8q9JFFbepPpqBRnRoWfMsuD4jfN1SjzHQFmO6M3cknt9Ks3mpymXImeKJ5uQ== +AD6SJc/iTPwH54/xssz1WVBWcH/ZEzyHa5mjHMSYrB5GeiQ+f2SWwPqnKqcSi+h6gEdIGtWRwKGZVM0HGnq6tXrVwmabpNx5JH1YRhQPOvhwMfOOGFTNMWxkT9xpTxXB3SUNK/pW+bPnaZtAe/+MgQ== +2JT5nIzi3HZAVmpbu86iZtf9bpFoQsFTXEZ1DpvglCR/Fi5CZYhRntJ2TkC214SfE25FA6XvLpJdRMGHpwKJ9g== +2sUL8q4SzZWwUPFU8GqGKwlSX8gJLLZDiTnldkGow68t+MACNiQJkyqEihk5i0t9i6Djy9cGBAaz7N8s/DiUlQ== +1vTE71QDj8abeS2qCOg+MgByo7zmSIRb7zHL7r506EE= +3NwppGL6CXZoJeTRAeohZw== +mDRkPFp/j8KULnXRVcN0Lyp5WnNz/kgxfVb5zO+CjZsN5QDfY+27nx7D2xQHkVoArXItOma5gJUyRFa/R2Rjy93sk25s3yS0BYLm/AOkERg= +bzZrWU71Kl/tzM0dltE5S0vrk0As/eofsNFV2X57dSUVHi1smijIpQNOIDEUkx+k +QjEYNJp9YKIp2aaVIb+6f6gVzO2t3XqwygKKaudSGLZwsx7jEUvqLi1wMpsVMRKD +1u+XjG/2+GSQRv6EzCaWRQ== +o3kxTO4RwuXO7GZ9wpXG+a0z0P2h4pccG1GCZJnRu2U= +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg75lda++q/Brd6OdhuAQiMu/X5dNEKabxgfMfiDhU0jZkrRy8PediFwsv5MLvLU0aT +1u+XjG/2+GSQRv6EzCaWRQ== +UmS1yzxZhsQI5gKyRk5d5lqliffLO2gbVqdb+WFM95KiRFbdF4zc7m+cuRBaFuVkNC0svUnyr2bmcUat93KT0w== +32pdC9DD05OE2l0oXazDFCi1BbdwJQkVAtSX5ha9T5+RIQUJvLGGrI7U0YrpeV0YxfTiLNDcg/vWmq3qe2hRiv8axmzPTZOR7VXC3NnMaac= +8hgDxirDXnnAyiJ4NE3XAh1L75lxsU5g8Q2vpUpKCnLQRvSoo6un8mbBwbulC3Y+Z34YLvKJaXqproeMMrsz6RJn4sdi7TxTtPRxHDrs+Fg= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +lWZgRwbdKIHiAzPxX6jE+9LxDLPZ9yRU9XV+SZ6Di92P2h9FyC/TkmJYGOwOwbDp +nkN2edJiPuYCVVYG3gy17C3t6eYJXmuD7prKh+1YrOA= +QjEYNJp9YKIp2aaVIb+6fyu2743KZ2UaISTafvxfNjROIoGdfCsptyBRj9161HvW7YR2/ZmaeMMuidD+IuK2gQ== +IhuisKim47k91RVt8z8qtLwjSwIip6cQtpdYaeIQehPNjDveraAeyqcFD3YskoBA +1u+XjG/2+GSQRv6EzCaWRQ== +ujLyuF4aD5b3aVgwNdn4LEZp2uPP77K8ayqbjETZ7NEqvDbrF/U24Zz6A3sBJtq8 +7fawZPkPf9I8tocNh8gW1M+aD2PnNv9ZBwayYOFUEKJtB+DUakmieFm3USDuw0Q/ +eZELc9H0KX5T8+zDFk/NnDXLZHl/0S1UA7Xr0ZYFSeU= +xWoGNWjKGPfI4gq8aHoTfCcui7R5ftvzgjCoNLWDE5qRBMSouXk3l6sAEA4jM24h +32pdC9DD05OE2l0oXazDFCi1BbdwJQkVAtSX5ha9T5/UbYjI80Zbuzx5oNnPU84gSPa/bH/kT/5aRUMT0WjBle5ZusJuzt4vjBzbYDj2qsZ1Rkk65+ZIhiS9XX3Rzlgd +8hgDxirDXnnAyiJ4NE3XAh1L75lxsU5g8Q2vpUpKCnLyrQbYQL/EUUZ+kQAxn1InllG8Bxq90nNyBew2Xwz5YG2dHCGc2iZhIVXpW6AQrTM= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBGO8XXm4X8uGfSGWh0Q9Dh4OeJdWimialzhHIAzr1KphDMQGqBgOocRe5ZY0OMuR5bi2Gwcas0w7Oib78mnfc98= +6RYaxNf0Q7ur8o6H0KcmAne5oDvYfvSK2ahZ9xoAioWCh5olS1e7HKg3qZL3C+AsaeqFUBzSkYfZEFRZVjFOiQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +AfYhxE1RMT4+vSRagSjmsLsUzfcE70zc6dM6L1ej1do= +oAptEs9PzyZpwE9U096RuCus3APIDPDy2uyJitbODeClUvUg2OU9IInsdr2NC1Go +zgKkJRNMCn8GCiKimPAwidR1NhrGRrF7Q2XXJLlPplfTFnChzzCion3R9SEDQ+foZ4w9liYQTrWrPKaRsWMToA== +QBw8o37CfRNQqICuEYC4D6GsO8P7Yz2X05moJafy1kH1rIRjiHEP5co6htBQurmlpFOToI4GUIfaIN35X6oXPA== +0/E9i/F0M+CrJ1F9soXnsKDDiXSMfOHlr0TCbPx+W8W50Day5ctjGvq6hG4Ur2LAbzqpEjF9reqGEb/vAKZ7eYPBSeYmG8I4uRN0PujYjvo= +VH9v8S09LC3phvMB+CmLUbe1vtFQrSs4ByQ5tsm1f0Q= +N/Su6K1KPpvRUC24rdHSo9Pcn1Mipkl6Mes428lawMmFcdAeP47i33W4xy9ZCoZE +ONpBmHTGg834+jRUzvf6DbXxy33wSuzhXUugCxbtoo4y77u2Ecq5viDjcWO0RVde +N/Su6K1KPpvRUC24rdHSo24O3FYDDs8pqzz2dfcCHQQ= +qDOj0w76F0mFhV/v8uZ+CQ== +1u+XjG/2+GSQRv6EzCaWRQ== +3aDESarhfDmQ/r/rgqARObXv8Xn9M6rcH3agWA8YLjU8n7FM9GYIVg2WFLmy0Xte +bzZrWU71Kl/tzM0dltE5S+oh1fstnjeyZPeRfGr+w6Ua/NAlKKh3qbAqIacZqIa7 +WoK/RbwXPZh2ejRVQ59oGOCkdAAhuCQq2//ME+i1YBkacV8oAGKt7vMrnxVl4w2PO/h720Ujo+p6duTJNMAv2Q== +1u+XjG/2+GSQRv6EzCaWRQ== +o3kxTO4RwuXO7GZ9wpXG+c9alTV6xYS1q5pSZZwCAH0= +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +UmS1yzxZhsQI5gKyRk5d5lqliffLO2gbVqdb+WFM95KiRFbdF4zc7m+cuRBaFuVkGlR9NfCfU7HBUtUKDLlUiQ== +32pdC9DD05OE2l0oXazDFCi1BbdwJQkVAtSX5ha9T58SGl5i8Kq4zTLHG7S1jDTpmiJD3xJ9+XHDUyrGE6HTiI4Wf75O+CVfsByElPvBD4AZ2THJhcazJ8ftU30V7aAQ +8hgDxirDXnnAyiJ4NE3XAoXAEwwNQ2QyoD90wpcU2wp1yA50Bj2ES07+7laOdBbsoI+WSy7OcwdR51e8rhfugr7PgaD1qKUU2G/4cNqJCK8= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +Uh05WP1QqV5PcVEL2PdGDQI98xtXE9gqUJ5FPEQvuGWuhObfgWoNrWy1VrBfPl2I +WoK/RbwXPZh2ejRVQ59oGLmF69fFU+GWrWX8eZSJVghccOKpnc2nZYxL7SirH4yKnqD3furGYyLPFsQVGdh3PZbUQjjTuHwJNgloU6EzzBjoK85GI8L4woqkEn0CnS/D +IhuisKim47k91RVt8z8qtK/TXQTSbdLKYe19YsHte9SRs25QHUzAZYgQ0fxof5lduwz1AxllhIIeHNsNIOKVXQ== +1u+XjG/2+GSQRv6EzCaWRQ== +ujLyuF4aD5b3aVgwNdn4LEZp2uPP77K8ayqbjETZ7NEqvDbrF/U24Zz6A3sBJtq8 +4+wKB+Tel1iow8au3cOIJTXxOTIBsTZ98UgT9I4N6WDO5I0NFKX7FPDWH0wxN+MEM8CFh2PbZT41YEIRrKHAJQ== +7fawZPkPf9I8tocNh8gW1GggbMuSSfyuxQNC1dbuQC0pUCKBoiH86+FkIe29b/fXXLRLVa8hmP9rcqE7IJtrsQ== +eZELc9H0KX5T8+zDFk/NnNXv04aSZ1VDBqaryxS12TI= +xWoGNWjKGPfI4gq8aHoTfCcui7R5ftvzgjCoNLWDE5o6ZSJO/O0DThhI7iNgUIpz +32pdC9DD05OE2l0oXazDFCi1BbdwJQkVAtSX5ha9T5+qjQFtb0cwYmYWRLm8TYX9QW4AeuR/8A6qsKVhz1H7ABGEtDVZkWStUmTzusexzTO7EhdAvql5TCdqLwxhciif +8hgDxirDXnnAyiJ4NE3XAoXAEwwNQ2QyoD90wpcU2wqXLKnagnLmsuUbRo7hX7JsEXIFHebP89bmeSpfvtUYCu94jmLdf/7S65TERZwPCZpxb05unDrz0gPysbpR+Z8V +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBLSvHCv5lbF+UpVH3FEr+ldO11eH3FrDkT1OuSJMVBdHcCzMHldhxmOlQo5GOhymmucHzO7G/DhLRdjL7VWfbfk= +6RYaxNf0Q7ur8o6H0KcmAntx2MI8lZnuMCivKrzMt9HT62AqyzkT0HnCS8YUYIeQCAzBSsFqtRKOzSDNND7xG+wZdOvdxeUgjbQ766a6iMg= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +gWFiPJSntgxSRYbzsKluMWOCieKV9/Pke2ng5S1HSW5y/DCVQhfrQzCEQWmJARhh +ic9m9nLege344z3astc4PdiEs4ZI/9xLnqY4aKi0iz6vx0zP0ZvJv4TD0vQdHmoDj2KKzv9Qt31+ZPeETqBCKw== +p1hQ9hJvEa/OWih75Q+Pz5Q9VMnMDN5bSSFB0OHsJNbb8yXQEa6Zf0Bm3WVa5ah1 +N2AxSv647SE8o7pH02PLTRW2x6MA9cDI2cPf/O1sMfglaiJwFomfzciuBxQr9Mgy +1u+XjG/2+GSQRv6EzCaWRQ== +mDRkPFp/j8KULnXRVcN0Lyp5WnNz/kgxfVb5zO+CjZvIU/la8LVZWcuZ6FJgZRKDsSk1JKbBUDivtbb0qo+ZrhLl6d1rN+PoIN64Izfk87p63611k4RmUD4ChgaTE3MbfzpL82b9YPMPHvZIQmzTvA== +Nd5rVySRefuK+cWgFJnVfKuTBzFVbSzNcdb5qU+Fk00bADr0EhUmDWWE8VJXMmBZKQZ2/BejLGCtQGnnTZLjXA== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYtX+pH+0NWr1LYbDUaw1poloo/SQreGe/i/geKkMYIe0 +32pdC9DD05OE2l0oXazDFCi1BbdwJQkVAtSX5ha9T5+x3Qx+NKBxMKPHco8yAWL/OJtoiyx4uWw5VI38M8cH0L2vxDDrIW6p/FtPXUFj4lKwHyoXkfbiyS6H7Je5XddC +8hgDxirDXnnAyiJ4NE3XAnpb4a/ZQsxvMQPhJZvfVvuIb7XYB4QeeT1fsju5A6udlz3sItqQXhMatlhFCJGAPmUFnm5Zh8XBHK9c+LW0UxP2JLVNxTSAzbOqfkvJ4hou +xWoGNWjKGPfI4gq8aHoTfAVzAXop9hMYnJknq6h/JgEQTT6r137FN2QVFE8/GuHp6sdq04v1tOimEcXA2YpwTg== +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +TQRmC0vOvJ1P+lfyS3Os4RuPQ8hF+YFFMqpaB/VTTDlP3bmpX2gF1zcf/IiqtvFIE7KUBJDowOTDY9SiLkWmw+k+glDsreRvMO6jUE4rEQg= +32pdC9DD05OE2l0oXazDFCi1BbdwJQkVAtSX5ha9T5+x3Qx+NKBxMKPHco8yAWL/OJtoiyx4uWw5VI38M8cH0GpcTJanjod967P5zHA6shNET4rGfyBkGPXk6v/FPBKf +8hgDxirDXnnAyiJ4NE3XAnpb4a/ZQsxvMQPhJZvfVvuIb7XYB4QeeT1fsju5A6ud2Sy1NlHAxGMQxMd3OYQTymR//g6QvP5Tmr3cNXjbmX6SHW/28QhlLh6s99lo5xm5 +xWoGNWjKGPfI4gq8aHoTfAVzAXop9hMYnJknq6h/JgGeK11hs8QPXgVGiTUQEBtpxhBCp0qLZfulH9cPU6bVxCR5Yy8UXMNPDqmgcV97Ysw= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBGO8XXm4X8uGfSGWh0Q9Dh4sGrEdgDWSuxarfQzodCIV61pnaF6n92qnPOQd6Gfitcli+smspn/tZCQIdPS5OrY= +0xm/B4AFa71L5BjV6JltokEncoxnqsOzNuF86FshPuxzuZU9fdYxwmyWnAb90taaKm98BwzhPaxsaKRBmaiQHBmUzTICnOHOmD5xQMFRwA8= +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +O4aek/DKwaWYXPpxyDmn8Dwj7Hp8+wI9C4JTzDyBSyOSEty+9uyFk+eQxt0EiOEEwGpA4F4k3hyik4xa8MlNtQ== +8ELD6Q/osNELEQY4NJz5zFGnBmiX3BbURb82nib66lYhSiY6ieDaLK0TJpNQApZwajzwfANmVtz0UeFlkCvYZQ+Pk3pmYHmAxH+qE2OF73T54i3GgbkbLU8wu+1yYxHspLBh6ROBRzHx4dcd5KnEc1JsBpxoqzZMnyQaeTsD2+lHu/4gid8Wug7JcpAcxxzN +mDRkPFp/j8KULnXRVcN0L0yqOW8umfrqR7aShWloZvEk+TnIwHULtqQ2QK5aDlL55LLOXmMtOQ4V2JBVGBsfzyMkX88uEXwXDkS3QdUC9O35X5js/wsq9KXlJbpgSnV6 +bzZrWU71Kl/tzM0dltE5S0vrk0As/eofsNFV2X57dSUVHi1smijIpQNOIDEUkx+k +jQ3ffgdk5F3qci4zFBKoIcXBKAyBSxkzXHyAK98DGV8JWHXnrpg5uso8bDmzMUMO +1u+XjG/2+GSQRv6EzCaWRQ== +o3kxTO4RwuXO7GZ9wpXG+ccsEfCpqX+ouikkRqgtBPo= +8hgDxirDXnnAyiJ4NE3XApEVlKRLAace6M0X89R2ldVUxvOmPTWqbuHGoPJPtuhsaLz0uAA/IMgQAGnglNB63mbwrur8pT2nPfMsISnuRkmohWt9As0UOPxlqrGxAWmm +32pdC9DD05OE2l0oXazDFEGpY065jdJgUgEBkm0CwDbQI3tBc7SMMY7mZve74irmOD750Rq1VALn6p2Qz0se1boLrABazyJSOtqu7kpuDTRzjLO8581bXDkO3+alVeh/ +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0XNfGTRUIWBl4JET8wgpUrGZT/gYIwgTJT1MOvb4bK/P8EHyRZbqYNYrjJvOno1qZKI8hxm6JU9Y39tFX1C+j28= +1u+XjG/2+GSQRv6EzCaWRQ== +p1hQ9hJvEa/OWih75Q+Pz5Q9VMnMDN5bSSFB0OHsJNbb8yXQEa6Zf0Bm3WVa5ah1 +N2AxSv647SE8o7pH02PLTRW2x6MA9cDI2cPf/O1sMfglaiJwFomfzciuBxQr9Mgy +1u+XjG/2+GSQRv6EzCaWRQ== +sMKoPjDBuqzbbqMokHb7qBK6eCzOVQDquwxr518batA= +ljjmkU1Q/8Bu6zm/9Kyjlz1n+vbphPN17dG062Zh6qnzLsomCI+z2cu6YkCz0FnVxi+3sxvaO+VQBjwm2SZDJPZvwi76SFMRpEExqS6CdkDY2ReTa6XWNRaOmH12+cEt +1u+XjG/2+GSQRv6EzCaWRQ== +gZ7BoIAABc+I8g/rYzq+f3zw1YdgnCLySWu6UKK8Troc6wJZ1uW8+TyN9EcXK0Hq +VmVrGQo2zRokW/ZuO9bN68yhfDu0q2U53395uu7cTD6Jj4SiUIk+DA5DvLQtYJFaLpLf6oC+Zj98Rb+W3mKcQAyhtF3mfDYaDm+mBFnCQykivFDc53z6Gm83/gIK7SpU +VmVrGQo2zRokW/ZuO9bN66NCc6SIFwft2suCXJ0OllShZ+C4+jJGGTg7tiXgNGtkz1v9KX0ErEO6aZ6Er2g8nfmEe3HbhELEJAf0RlRT5IUvCJjHqns494BqLCiVJEmlgE8jqWgwl6ZgP4xb0TYJZg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpT73tXJGPh9xzuFnKDgTWtsdbWbNX/tXxLXIlkjl64xBuS4C5J2xhg4JBnw7ir5qk9qpgihJz26D6Zi3e0CbqbQCEpaB56JvfVxWJwrEwJbUV9ia0+BWmD4uBYD98d9tzBzYe6FMX2KWyi8lxIrzqTQ== +1u+XjG/2+GSQRv6EzCaWRQ== +zWJ7/ALVhEfwzAYgn+dRKswddbaeR79XwNONdh/sTNCO+BK1uX/Gse8oNTPpGw1GN2b67iK33WYtn6In7TJDWWf2ePMTQrYnhCXrBbzjqT4= +k1ktu/l6HFULD7Vyr8Cv0KP7PNmReoQOWf8Y2ED4xF4ZQLx+BEhzgvnG9vMmTQmSTVdERCO0Xf3/cfMoR8iJ6lz3IOX1aAi7TbKADKG9oL2bGeQ7W2qw7I6VC2LwgGiQ +1u+XjG/2+GSQRv6EzCaWRQ== +7l9d04qcMHKdUu9FRiKktau3Sg6akhe9l3PRtks7t64= +a0q7lWJSm6u4huPzjxOOQep1TYTsFv9gGElhAisuDgPJ4eCy2ZTynze2GPMkYk3Kbl+YOR4qSyD3tByKTFyt9Q== +32pdC9DD05OE2l0oXazDFOIHV3NTG5lRlSLTG9FZ9TMtekMLJ+bJ8AwiqyaW+nVL +1u+XjG/2+GSQRv6EzCaWRQ== +ljjmkU1Q/8Bu6zm/9Kyjlz1n+vbphPN17dG062Zh6qnzLsomCI+z2cu6YkCz0FnVxi+3sxvaO+VQBjwm2SZDJPZvwi76SFMRpEExqS6CdkDY2ReTa6XWNRaOmH12+cEt +1u+XjG/2+GSQRv6EzCaWRQ== +gZ7BoIAABc+I8g/rYzq+f9wRS/yLEkjmZguTwxPynAm4YdQdrLr0p0joitrQW3tD +VmVrGQo2zRokW/ZuO9bN63KhtIGhClV8lKEP7LJ9bvu1/9SW47zTc8mAeAK9yfZ1a7qhgjXk8vBf/dKtcTNZ0ax37QChG0Hlrd1/HcVV3XF+tE2UvN2FFw9ObveudrTG3WyLB2mtsFCGwzQ0D/69jw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpnUPZP5I1ml+E1aCJdhd7CkYbD2NI/Cwh8L2i13eZBlVb/ntGp27aQ1PsrXBsu2nLzOnxQhLKTxIYhGqh4xZv675atHosYofA+/Jx5mWz6NRG/D+eUeQAqJzcslcgVAYakyUBMKfEEsdpuaf5UOmNOg== +1u+XjG/2+GSQRv6EzCaWRQ== +zWJ7/ALVhEfwzAYgn+dRKswddbaeR79XwNONdh/sTNA5nRL3au1RHYDH1/5J96qSOhLA/MnL4a642Mb+xCXYJTlhWnE3vpOzTyEuNXeV69c= +k1ktu/l6HFULD7Vyr8Cv0L2tearPPLAF1k2Yxwn7SM3YZBNYu8v0r6sPjPJDQ/UaYJ9y3fO9DQ7aUBscbefFJQ== +1u+XjG/2+GSQRv6EzCaWRQ== +l2FJPs4YkAmmok1ulDRuSA== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0dYBGHqHJ+ZHR0bgDyInIJZZzQQvijBrBN8jA6zW8mkSX2gS6R+Ozsz8OpJPh4jBTg== +1u+XjG/2+GSQRv6EzCaWRQ== +IhuisKim47k91RVt8z8qtLwjSwIip6cQtpdYaeIQehPMxoSsCV7gSNK2n8mfVb2q +7fawZPkPf9I8tocNh8gW1M+aD2PnNv9ZBwayYOFUEKJtB+DUakmieFm3USDuw0Q/ +1u+XjG/2+GSQRv6EzCaWRQ== +o3kxTO4RwuXO7GZ9wpXG+ctGHEJ1gwkP3dOavw7bIO0= +xWoGNWjKGPfI4gq8aHoTfCcui7R5ftvzgjCoNLWDE5qRBMSouXk3l6sAEA4jM24h +1u+XjG/2+GSQRv6EzCaWRQ== +8hgDxirDXnnAyiJ4NE3XAhYEjurUtKtsotqEFRxrvovmdycwwos3+Qkh2MGIv1TaULI5J+NveYWrdBc0VBR5qo1zvMIlUYwftJpGe8YmjPI= +32pdC9DD05OE2l0oXazDFEGpY065jdJgUgEBkm0CwDZ5zMyx2aSiQQeWrQjmxny0kXV4y/ibZmLeL1QrgP6Xzq5LiTnjopGM9cZhD6WbeT0/3vRqzYcfQwCzpSkzURcI +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Wh08/fy52mF/swYriPujPxjJZVljR+KdhHigD0XKfnhFwQ5YrW2aWXvdVzhxNaXguEIPZFaNpBVpVxJd0pzan4= +1u+XjG/2+GSQRv6EzCaWRQ== +xvhr5kRQI+GM++iQkYI5iRzYgEK4/VTYe0WhCZrOYLbR3msUiSBD7xvy8p9JDsSW +bPvoUv/oYIf95kcOJg0X+NNoAYMJQAMLTjT9pmuQRiw= +xWoGNWjKGPfI4gq8aHoTfAu9Vs6/gfwfIM7OdnTK36Kfyn1EWsGnxFcC//aU1qhR +1u+XjG/2+GSQRv6EzCaWRQ== +nHTktSVwzL+LTRoOM6vU1JkIBUJIzDEqGKhmLOtagMOf8iOvrGRM/dOQOyQ6kgK1Xo1q9yiZcZD/0GK/4H1xYA== +a6zIn83aERAna8E909JSBHm/vydPUR78j2AfKIzU3catrtZQ6G1SNU8GZ2ojNYDY3rFyY+AFxCQMH1W2RPfsWDBG+bp2fe+339xcyDUQauc= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgUvrGiMY/BUEAaAwYq6mHKHgxj2b4jhhvkbC9TjHed3dQ5kxfbJH8h7wBPvR/HoyjuzeY2MeoHl6yqWSlejIj4M= +1u+XjG/2+GSQRv6EzCaWRQ== +KswAwn8yMtQiBZzxbzyvrzMxWc9NjHna1XdPoh75eOQ= +ic9m9nLege344z3astc4PcEJamOecLZiB1PkVZ9oWvtNZ6EXAqDfAUwfAKoKj3RA+Vh/eWJyUCGYV8lIfCXi959iRbY+alv86eYmHedEmSA= +Z8UsPk1Q7HtwjRd4g01ryw== +gvnPhBIKcIKOwmiSOZA5DBVFpY4mO1aB6cV+QQXA8Cg= +omlT268+oBkimNFOsNx6YunY1ufTnG1xLJhuUGAdlNw= +Z8UsPk1Q7HtwjRd4g01ryw== +neG2ZqmysBKy0DmUM70J4iiPfPCyqwlAQFJ1UVW9tyx0lB7dlCj1KImwGNIWCJAF +UAktoVS4akIiKTcgVUIl8dzIBWAK47O24mfsy0MquWM= +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4vbukQaHE0gUMnrX8E6T0tYJ4lUJCGMpVsNwhUR3EOS0 +UAktoVS4akIiKTcgVUIl8c6Q1/CFtjCX8ZjdkDvQNfc= +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4nzfAB2BnukWfJon1idMsXgB85kwTgteyodQAzMCaRUU +UAktoVS4akIiKTcgVUIl8fo8xE5+HFnXsZ/VG8SNg9QbX2Zn3tTD9EXgcaUENTvj +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4r5Snk0+WRT229e/KLul9UuKwnJGy9SABbGMfSxNrRNk +UAktoVS4akIiKTcgVUIl8Za85FwNfaFtdwG8k3mc9w1hljQ6Hyhk92zvziTGBH8E +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4pR+5fGjmcFY04mKxcb+NkfTmUGk0Pp235eGnRoMPh2Z4XlMFFFdHQqIRKMgyi7w3r1VSqF2lwjPHVCPMbdjeEo= +UAktoVS4akIiKTcgVUIl8f6vZz7OYn3aRsDA3l3DqywrV32HZcUE4Ouhh0b3Q46V +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4ufDq5bIgyvNFX+xNgoXw5gLoaqTaQckHsD2jMsSN1s4O8ogda4ppaYW3YSI1oVaegOoWnofD0c+/fGVwh0SV7g= +UAktoVS4akIiKTcgVUIl8XCh4UNWQHYgn23kwh7Zgy257V+Md+P0Lsk9oD4hmiyo +1u+XjG/2+GSQRv6EzCaWRQ== +nfCoFuGbfqaeHgp9xDhU2sqztj1hNoztcl3jZgyzBnzWvu3vhX79IUdg71nlyYqM +GlJUIzKyUWmd9WndFdsMBAaxs0QsjrkL+f6oe4h4Eld9SvTwHsAR+0pL6LnTsfKUOqO/hFyvDqmuFDcCM7gnHA== +V1ScATKwVuxN5IABiDyoJdTV42dHALLvsDdEXU4uHVtVZ0XDRo6QfIbKpbVyUh9x4ZaoTT1nwK8OuBGVlBRBCxqVeH1+3vhmpZbvtSc0WOSWgQtQKNz0rGhqfMCI+BYHn0VDEbohoq5AjNji7Ce5DSdNEqDTKitL/25KGt07xiUR3xmQn5AcakmTSBIEqx0e +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +csLthA53ojJM7MvqrKXR/IVSbZvZqW9/ti8E3LU+0sY= +DQh9OqLqPHUvWKrDS01q945GWzrJkPBrnXQZFgjWbJd+EwN2g6b4lzhZ4K249a7NOSh4gftd4pzX9/8f9t8V/Q== +96gahBYKYgdoVkNtss7VEIqReftla6L2G/wQSaYc+JtZ2mltrJT0ayycamLkAMymad6CkOw/Lrrfl69C2zBWSQ== +jQ3ffgdk5F3qci4zFBKoIcXBKAyBSxkzXHyAK98DGV8EReHjma6MPG5BkPVxltjf +o3kxTO4RwuXO7GZ9wpXG+ccsEfCpqX+ouikkRqgtBPo= +8hgDxirDXnnAyiJ4NE3XAruj382Df9L5WU9Xc7bZhJKTvZJmssE+ZNd2zehiIi0zySWjj3T8z9p/U9GoORYd7sOCSIhN7IFhmkwC6ha9qM3AaffFnyjh+eKzhwgU1WAK +32pdC9DD05OE2l0oXazDFH9aIUzYqykzFyV91nuGAgz5o36+rbZbPvhhAhhso57ADPyFMWlOHZBYuMIfmsc+GvISWliXxkO2+ze32aSI8A6Cn0ON6Aez0d33h5586GxdO/bOb2Gkd7O7Et8OQhulPw== +L0eUthVnpkGsmKFAX6d+uKdLEGlEXm+o7O9zcmHr26U= +1u+XjG/2+GSQRv6EzCaWRQ== +MzOj4ZiBOJDNVP8vi/lFQBnf+vH0KPJCtQI0kfLkdxzE3zrEXe5Q2trbA9Ips9q5 +8hgDxirDXnnAyiJ4NE3XAruj382Df9L5WU9Xc7bZhJKTvZJmssE+ZNd2zehiIi0zhvhEP+f0BqCGut5sQ77wA99os+thTFXzLh3CbgbSNZI= +32pdC9DD05OE2l0oXazDFH9aIUzYqykzFyV91nuGAgz5o36+rbZbPvhhAhhso57AaCY9jqliNSQZ2LVkroT3AfToqjQkHSdaEaMU7S/UcTM= +L0eUthVnpkGsmKFAX6d+uMfCKxbeZE5XF/T3qfhxdVg= +1u+XjG/2+GSQRv6EzCaWRQ== +4+wKB+Tel1iow8au3cOIJeDFU2Q2B3JtrpibuQEbYES8SabktJN7fqgQK1x4ewV5RO8D2I8AhLEtCMss0y778g== +6wrc0niBYuog/c7MnH4VtcoT/Tfu4sUPPe/LrZuHbJGoNO5gU5vQ5iphy4qPmUZYq6AmhpIlOw8aD51ubOGXnw== +IhuisKim47k91RVt8z8qtJyWLVz+KjhPuZWupljEG6xC9dMadE3qrj97YQsLRp7E +4+wKB+Tel1iow8au3cOIJeDFU2Q2B3JtrpibuQEbYERAap2FyD37a6qdU/GC8K3eRu/4Wvd25srzAJzT6RAcSQ== +a6zIn83aERAna8E909JSBP8SJBr70N46YgudK+Sv1Qop/gxH/Mi8DST7vZVoEJfEHetyd55JLA5s4ROPds+DqCW8G1uV7VrAQjhA35rrd+I= +1u+XjG/2+GSQRv6EzCaWRQ== +guSZID0bFQuDFoWO2uxAJoOpitq6s6c9ladjgxAHqGE= +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +GLGtUp+TFOQm+GBvlIABmg== +yumxBYTWMrcppQtunXLfmyEyOAz+K4NmRL+cFQgmtVI= +A9UA1yJHjpCuekrdSHwJzwuDsOzAva/TKyY4hRWkzUItvkH9O2yMtLzK1BqycTAR +5q5ONz8Yaa8v7vRvRPTOzakXAoRyKg9pON5usuPrXorHSii72CArhD5EZ22ZviPj +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+NKPudEm++RMQZs3h+yWJpXSp4aRpFh9wJGsBkiKsHmF +9WE49JNcaw71DcH3jZRpmVwnPH/UaQKqus8UOE6/l52OAN3uW1xlorJ8f2t6En8j +S9tzajN7j7km7OLOHRXxfSHfnw+TtpuP+NLP0Gzk/e5WV7MWASPZuEhp5Xsam7rT +S9tzajN7j7km7OLOHRXxfSHfnw+TtpuP+NLP0Gzk/e4DFqSiIEs8XTS7puvw52m1 +1u+XjG/2+GSQRv6EzCaWRQ== +oVio2TufACN3lw0gU/jnBOjY0lEe/kxafoD+w+zPtFignUcYDzgftDO/QdXWk+Rx +cGzT2936rNP0cRSqygcUrG2qeeQUduHW4QB07SyID2pn3NgaN6zRgOwLY6ByjyC4 +W3A5Dt+8AxWSKsTYeXZoL03E+GmF6G6ZqKMiSSB2XE+S2J0lAHvztfjEq7cK++/6RAqMfNQ2O/HkSgB6w6nRHhdnFMRajeJIomuRGUCboZe+BWjJqWc7ADlhoqz9wDqAmarDK7NshyxA30lz2Aug+A== +xWoGNWjKGPfI4gq8aHoTfMILR2ZzS4c+Hu2XOwXpf0zVeJvwZXetFtWYi+vkCyUVIbLFP303SoVxsMoy3AE211EkR4yh6N5rWi0xHDl3FBM= +JYdPWj5v53ypTmGSOiRIZZjWI/qJIrSui12uoATtWVc0Z+qBy9Y+0p3qHMEVtIeV +y8KOrd2JMFCujyKjz49scwnPCdv1TY713CDyrrQ//Gn9d3P0H8rbCLW8XOZU30SO +DlxUi3JAc/vyg4ay0A/IItFIl6i3fu/PnJqNXd7VVeTWZG+mT8lSHuVt1ZhP7RjO +cckoT/9AvNsG/TDCsQNU+tAHxhnQmFupeyfgMD4iCxhCQmuPMTsP075ogjdgbabg +v+DDyXDjf61g9n4W6mcmtthl1GYvBAZPVxvw3FJtXWvVJnZdmdLyZ83L0Ig9TgZg +ZScVkvS+1nFlxR0kJgx8r4przx4jubQnKFwJYHop8YCpqt1NN+lfBsal4NV4yUd3D1f98lRwh2bQuY8ln4Ydvvn+8YstTNwUhv4kalesCroVoOmCWAx5udr125AdQssQcV5VT6+9CLaosZGd5wa5Jg== +1u+XjG/2+GSQRv6EzCaWRQ== +52/Lmy4Q4wKraJQg2hRIeyuHFHLh11m1ZeCnt+Ni5ug= +cGzT2936rNP0cRSqygcUrJOxUjnCHa9sEX5hR04nyiLdvhetCk/PoI82Y5lcc00/ +wgR07xfoapmx6eEnFHXXYrIeMJaP3Lg2M3zaGXD/L9jKh/8KBfZ0+G+MHb1MJ10o287KiHh5wxJj6tj0uIAaHw== +NqhMbY8+EQI8zhTG6Zh2I7OuFm0LEyDZo5ubxQ6qlyuG3LeSLODqszufAD8BP74/6Zg2lF8CrLjuMDYEmGv7Ng== +1u+XjG/2+GSQRv6EzCaWRQ== +vTNP81yZLjoLdNpbBobM74/BQxPMwxMsJu8vs2PJnsg= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +qQRjwP5jr3EkHpYOFL9JKpejYz3YkCA6D/FaPfRpuAg= +96orka/uERLyRst14azQwhglCKRM38C+C4aUvBEVMMIDcSTKbaOo7uyX1IVdLPpT +1u+XjG/2+GSQRv6EzCaWRQ== +hKAY1MN4tjDTCH4mzcSt5x3bIyz256V7Ktk4aXetbXTjkvzjKHD15uKc8LJ7EOHj ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +lH/cdKlrko0tsugF0s+Vlmch8L7TiwqJVSGo0y300TN4LLLRYbuM32nlrjpXlpOcCg8dZybbMXU0l+U4PCfBvw== +Zlk2SG0MJaZt6EfRBI0kp9vUxHcN1+bEkJyK2UlKPfv0QxeK1+j8vMCfDiNB4kcag2mAtxXyro2MWSfm1cctgs+2MUSDMO1LN0D4WDqUFaI= +1u+XjG/2+GSQRv6EzCaWRQ== +cbvASHjpysrsjdY5RctXmNuRWaVCTV3IfXxq5/B4M/l3pXW1iHZZkPg8E2gTy8to +YY5wEo94l9qfo5TPxQmUgsjASH85p5GWa2GCKbiYjuhHNdPmXacx4jJgyIx3crg7JMh0e6jas9TdX6IgGuKJ8e+zKb8DDG6oIcqgcRHkJJ9Y1gHeomwDIkT8MoVShXIb +1u+XjG/2+GSQRv6EzCaWRQ== +jceVTICIbdeQFZqTJKGutUQB7Hbi81qWzj0AOly7qXovSR3Lf1HM0DNmAwq/l6om +1u+XjG/2+GSQRv6EzCaWRQ== +thVfqBgAco1nB4F6IluvgSqMP63nNItipWQE1gNjliLD2cXvuUd9muNJSLOfozaG +MMrMMpc9v7ckr66/VK7Ec1hIOdGGwmvX9E5gNWPp+MkCPVdLAZb9AxNz/Z/G9FvN +CAhn8dRUItEbEErp4w+lX7vbmr/bNP07jusNGdRn6UMqnttWuRB8zdyXUvih53uhyR9L/RgXlaLctW4AE9qHdvpWtKn6Vd66tj2LDakdZj8= +1u+XjG/2+GSQRv6EzCaWRQ== +jn78igeUmfFOceOz2+Dq0t2oRr5O3ng9C3dd7IUVhRY= +iFyq/frsCN+T+ob9HZgV6RBC4AasXij0tA2Q5r6p0sqZ8S5iGJ8EQfPmoQg/gwZVwCG1mGwMchcPX0D7Z47ezAfOVrnMuRy/wH1nj69wXNA= +nB06I2Hxz6OOZ/h3WgJqXO93iUNYGfwGKgOYnEP6PME= +kE0OGUDieDpLXO/pX4APBArHp97I07iQR92UhHwLo6k= +xWoGNWjKGPfI4gq8aHoTfGhDoL0FWhHLxOhWanDWjydm9xWWyNnAoNHcNpQ0g102 +xWoGNWjKGPfI4gq8aHoTfBbHBrew4OORUYkhM6YqKjf6SDQWC2/RaegGQGu8zZGJKrE4SiBPQK2X49lKnyMSlQ== +xWoGNWjKGPfI4gq8aHoTfDKxSKKNgns4/0qynXnFwUEifYc25s9jEppiSHlmNgJIbbiAZqBzjbUlKMMWWAWZFA== +xWoGNWjKGPfI4gq8aHoTfJtqtdlbDzIlSSQeU93WLnLhiTD4ARtudXQBl82ZNrDxq7aGHKp4d0YyU0yv5Akxog== +jIYtewpcL6elNlf4btqXRw== +1u+XjG/2+GSQRv6EzCaWRQ== +blmlG6qfvRPckY4P30JkN335dk8Qhiv9xtSTJvy74nXt4KKGUQeMxv3LGqC4EiKlrrrM9waZ/IX8yREn7fzx6lHyHwLbwl89Ha8qWWuN2sE= +1u+XjG/2+GSQRv6EzCaWRQ== +q4nc/jwATOMUyfSjLibfEenY5RFzfDAlBFkea5rX8Ic= +1u+XjG/2+GSQRv6EzCaWRQ== +nhEO6jGYTLTxNd7tfL8haGb6dIE98SqR0mAOJokWbaA= +2gqFvhn8R1IfPGeV2rMswbHUHFXVUzGBLmhsu02wzqE= +1u+XjG/2+GSQRv6EzCaWRQ== +C3JSV38/w2nvM3I7TZ5+4epkOMkaWYvoOErk6ygROHs= +to+v3MAswOY2NqesdSm5Exj6qBaLBkXUylBlDyDbIhw= +vJtKpUSg5Ukl4nJbF6QjQLuZX3zqzNFC23N7KFHJ2pb9VBWPaZgOK4GnOINBgv0Qe7eUBHDPvgSE5jB9PRi0bA== +m64JmVQ8ZxnJzJGTbbSLFmPZmjvCILB/bV9nRo8GL6NKsGOJsqMWcGGUi++CT+7m +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +I5csN8e3J/KIFkMa5t+SJIq0Ov5w/UnVo4TFeszykl1vfhlws6PesLWNE3+wXgp2 +iZaIsa48RXfE+uf/yF/rQFPJLjyJKEpYeouGzVmJly57Pnx1mBkjOCPGwtJiM69as+U2C/swKlzzbyYBv31C7Q== +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +iZaIsa48RXfE+uf/yF/rQDmVf328qBPv3bR+Py+WNSE= +1u+XjG/2+GSQRv6EzCaWRQ== +ed2GXKkXmwU2EQk+6MUOb6X0dykjON5F9loCj9NSFIJ1utl5wcg82br2RuE7nqGidwvIZth0k8yrt/tKMlvveQ== +1u+XjG/2+GSQRv6EzCaWRQ== +UdRrmeNuA6iQO1dKro9kRwF3OYmiy5ZlRqyd3A4uZyN+pGoX0K+1Onfj/vzDTyNDuILAyXnOlgmr282vwaZMqQxD6vNEJivqCpZ9XFuzAiQ= +YabJCT93a3/IohXaIkr3oWlupiwiF+JLx3IX7SB5elhN4NhpGYJQTmff6iRPs2JdtOAZBzYcm1TdmxaznlUYkN9sOVTwRJNPkv415eed3cyu1xFcEqng44Ouauyp/fl5/Xn8FQ3+NGfujRnSxomrATZM9so5PfWEeXWn2zk/O+AsF7DyGrsU9Gz/S1xpPl0MTJYbqSABe6w5QSXv5ivAawAMfWT2nJd8IVyiL+nuVeNGTerw1NJTlRxAf6/0DibQ +1u+XjG/2+GSQRv6EzCaWRQ== +ei/1A+2pKZSasjYF/t4qonJTYWTgtQ6/8yY2jxeVp1Q= +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033YUrJ6QMqkWO5Cc0jIsEGw+6iP8H3Dk0UjjYsqtFnyUkQ== +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +Z+ggJg2m6KwS0nwm7QET8pV5hELSna1KrJ3dbsxMCZvtvEutwFmvxFwwvWdWg0JOapnYKCpfmfjmyKil9taONgs4/en9BS/usV/XEf+JUTs= +Z+ggJg2m6KwS0nwm7QET8rn13r7w/TkLuMZO7IcPMTSmMxfZpJJJMtkuMAvCM5kcaEei7wIzhDqADk7lQX8WZw== +1u+XjG/2+GSQRv6EzCaWRQ== +AWid6G8ypXqQe9XxPPYED21qUZ05KduG0I5aBgcmb0M= +4aPKyS2x5nu37fgMhLLhDhMCDK1kNFoG8MumDTVGGyxB4RtDSDWoU/vMteU2gspy +pNBzRwWPbUPagxYCBV0D9504jwGzgP6k404+8oPMDSY= +661hZf7vhUQ+50okfwfTXw== +1u+XjG/2+GSQRv6EzCaWRQ== +cXtpAJUiuedkrLUQ1sudz2FjlsFQU1P3cvwuWZEnYro= +GGi2YAbG0JZpdujBWPDe8q6r0BXXG/ADJ0/vIyhuvF24eqDiBL1a4pgQF9vmODmY2EanODYfYSmVY9McoVPpNQ== +2yQsUep5WrV3T3htNZ1L8TIohok8dxSGY22+WeWcmQzlauL17DyWRvW+qqAHUI9z +s8oZlk3kc0FacCBDAb+rTuU1p7WSuwEaBvHSlQxhPF1A/pzDaZHAC0iGCpRNjvRQ9gHLPvTdcN94+q732TJAjg== +phCI6T9adFO3Wc/DVsBsqQ== +32pdC9DD05OE2l0oXazDFDA9Lzw/0WeBxrdioFHnxzVwvcVrLrxArU4IIOJOa11S +VmVrGQo2zRokW/ZuO9bN6zCkMpfQe4UnnBkVgICzbEb6tolP+BPLLou1a+/RbH1M +VmVrGQo2zRokW/ZuO9bN6xNU5PME4RrEC5PBTWK1bEU= +VmVrGQo2zRokW/ZuO9bN6965xIByjT49kMuhmr9spXrl6X5/thGLtEfrS6s7Jz3X +VmVrGQo2zRokW/ZuO9bN6w02CrW9obh4JLgJqDECxKzH4NhaqQzS+0lZtQC6hevX +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +nNkEkiLAGyoMlIeE163bvCjozNxoG5VPQyS9oAyg2RtYQyGDfNbsHZUXkQuqIg9X13MameKB055zyFkjS7YoOyUuPt/6wl8Hkip3Sy2v26o= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z5HnZoMWfDfSE6uG5YIN4bem1GfJCtbwzGPfNujBHcvww== +1u+XjG/2+GSQRv6EzCaWRQ== +UN4dh6a4b0J/bagwf+RdMs7USLMnorVY2XpiQ4UceK8= +YDTEV1k7tbLtvEoTqGEv2OXhvBkm9KUpW7wsfNngLXivFnQ2VcwH/UZImexftwrtfaVwMoU15vw7D9tN6E1f5Q== +2yQsUep5WrV3T3htNZ1L8ZgufAH/JYncX/gwsMyHRywlSW/d2XbPF8kVwpa/08ZU +dh7w9F2NtRbFZXQAT43UNN7/yW9jpPDxwnQLlmXSh2N40SDWq0C+2vAvknDDgCGKsXcDvi/C3MZbXoObOf+Ygg== +s8oZlk3kc0FacCBDAb+rTn/xz1jUP6sCeC6dcjjAsaIRXjy7gZRdXrHmgn6OL5Nb +phCI6T9adFO3Wc/DVsBsqQ== +32pdC9DD05OE2l0oXazDFDA9Lzw/0WeBxrdioFHnxzVwvcVrLrxArU4IIOJOa11S +VmVrGQo2zRokW/ZuO9bN6zCkMpfQe4UnnBkVgICzbEb6tolP+BPLLou1a+/RbH1M +VmVrGQo2zRokW/ZuO9bN62+L6cQRTk8P6sDJrn8EAMI= +VmVrGQo2zRokW/ZuO9bN6965xIByjT49kMuhmr9spXrl6X5/thGLtEfrS6s7Jz3X +VmVrGQo2zRokW/ZuO9bN6w02CrW9obh4JLgJqDECxKzH4NhaqQzS+0lZtQC6hevX +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +nNkEkiLAGyoMlIeE163bvCjozNxoG5VPQyS9oAyg2RtYQyGDfNbsHZUXkQuqIg9X13MameKB055zyFkjS7YoOyUuPt/6wl8Hkip3Sy2v26o= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z5HnZoMWfDfSE6uG5YIN4bem1GfJCtbwzGPfNujBHcvww== +1u+XjG/2+GSQRv6EzCaWRQ== +BvSmZ/BI0uZGHBYtB585N/U3iN59wZfemYloAVPaSaUVTis7uAFH5Yydmw5Pyx8Sgknnv4FE7h8Acc5wglEqjw== +GGi2YAbG0JZpdujBWPDe8uvKC1MrIqDB0nBS13CWxf+38Go+PNVPp+EA4h4sSPIf8Ag2tSnFd7XlwYFKl+kJIA== +7Ve/CPYxbRIq/L2vO/1jNQ6UJGckq6ZieBVpLhPhDlw= +Ouu0Z4BmmXiNrI/mUpX7JrmB73n8FqZOUNkVpBZGbsp4b/jr849u9oLh5uxbLy1s +1u+XjG/2+GSQRv6EzCaWRQ== +HTdnfikDSTQ3eoPgrOd1XSIVSJ0FqgT0ujqJT4TT0N0= +khWCUyuDMwE3p+YqYO+JiuSzaDWchHxEBdmaNldbu4/8n2h1UDnGoFL3l6U+zr7yjbpDtI+lpXv0bGiBl+Gm+DUBoSG2xaU4LxcCgaLsFHAafulP/uWgQxkZFPfsN2NX +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgcKB9TzbQjRX3Joo6+0Mt87UTjldZEiqXA+V8/7jkju+ +1u+XjG/2+GSQRv6EzCaWRQ== +BvSmZ/BI0uZGHBYtB585N/U3iN59wZfemYloAVPaSaUVTis7uAFH5Yydmw5Pyx8S8WQtcPE+1GY07MDllZTjgPbJHSd079dwtH1u6JaM+BM7SczuvnoHVIn0Cxrss13V +GGi2YAbG0JZpdujBWPDe8qgDvsVRQGKs6M5wE1KkLqk212AUjZ2JKyRIckpfxrDYrol4/LZB4EFrwaW2L/zKSA== +ojcUvW+Z2ehEJ6yMJpmY+zpSWeXvgsw3nc4Fnh9nwZmEVHOyzIHhUN0xTP6ilzSt +Y1nYwlj45GWxs+Tt8B+LmX+sNN7H8KkIIw4ZqJqDU/F+DrT5pwbOp1aHf5p9sZAxXKDIRbagPOS8t1fJKz3HZg== +1u+XjG/2+GSQRv6EzCaWRQ== +P40wuHk7MslPmvVoiV0eZS9GHjGsA1BpCAEYF+DyKrg= +IZj4TYq2HlScfSdRkI+QP7ds2Lg0nLguFAxiA9uKmwWNy6Iyrh6ke395/+UQsoR+T3bjNrP1/7gKiifvP7wGuw== +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +nXHLJs+jc9UGwRbqbBpdlu0AA4913T2Ow3f4dJt3NQs= +I5csN8e3J/KIFkMa5t+SJP5ieXqyhKdpsUu8T3AoWE7POsp2/AbDJJ+EacQGfSh3 +Fabvv1g95jlx3ERwtS0pokMTpJX6SfPcIoCkhbe+ucBsMqfvfsjyGkOgq6g10Owag05vKfEyZIsm+RHQvpNIvA== +0snEhJ9RKaTb75xc3VQkt0AnFMygVKUR82TOQgO0LlTgiK0cDl8t3IfMVUhY4uZp1+Aq3ZrJoKh7aCp4JjiW2k8tNk1oUh4/x+UyMI2WQXI= +1u+XjG/2+GSQRv6EzCaWRQ== +AtqIw4BuTOuXet8gp4hSxVytPu35+6Fh6JqtAycgjXL2DjziJ6bnWfSIklmkKg1j +CUHO82G5MDpSEiqhNImyK8rF+px8D9Apk2tILux+kOFfRgA0QwD3TgdEqdiFSXwWvizkWItTF3jVcGqSzaPvEr+/L5WYdvI386+jaLSFwqE4Rv07J6yXA01s+6h0sQ+U +1u+XjG/2+GSQRv6EzCaWRQ== +nXHLJs+jc9UGwRbqbBpdlnwxDWxHB/SirxtmpFRuBJMZYg7A14xxDPiNwLI5RaAq +CUHO82G5MDpSEiqhNImyK9/OM4rF5zR/mlqmGFjGt4m+iNy3vI4bD6mEhNundnbuuxnt8UhKqS397s7FpGEVYUuR4yJt5YkUp2S0b4r1B21BoHJSK2mVnzr/rWHx+ihB +1u+XjG/2+GSQRv6EzCaWRQ== +1ZFe+UUdQmu/QujLaciJajx26a+2STfwMi+zieErgi3CBgaNFGZigZi8pTmSdXTQmGqTiUVKLPzrQkD8KSrdFg== +1u+XjG/2+GSQRv6EzCaWRQ== +lTLlgCoqofnya1u8s4t+TDawwrR4oUXemb8SF/aUeJZWG0A/XJNhZqwgus1CUe2E +1hs1FfIPISXmd7TJ/r+Vl48ped6CO4DUHuCgWusI3kallCcZLBpmRjFrGPj00Ur8taAQU/SisSdBkNC8m3lz9w== +VmVrGQo2zRokW/ZuO9bN64LUNt5+YaZJNuS5WgwZGme6UYMSga5cgiF24rpHaJsx +VmVrGQo2zRokW/ZuO9bN61kIfIQ8DIhwn6LdOOFqHc/lF3MRnE4cwPcgtSvez1VRJ9groAMVhUy6oYbGUHVpYg== +VmVrGQo2zRokW/ZuO9bN61bvrNj0Ph72scyutykwz/koB4bDmWokTi1uq1Abi1Km3tmvlDF3S6tz5+YtKcQ9J2jkI9ZdrCb4DKXDk6FNpOw= +VmVrGQo2zRokW/ZuO9bN66YryIEGaR0UOYU9rBMMwfmUm9CxVX4jsMTvX8adUwuO7mQspac8jr1VIV/pJ0L/OQ== +VmVrGQo2zRokW/ZuO9bN6xq7wQbNlpaf9ykrQ4xyoDR7YkSnhkMlXCgAAfTr1fEWEaCwUEK1oX5Jfr/TJpMY4Q2FWBcbR8MsJWUcXitbNWfVGg7AQ5z4a76mwhR937vY +VmVrGQo2zRokW/ZuO9bN65Cwfp0UoeyzmXbNAj08Gj2seJKExdcpkF+MxU7N8+3j +VmVrGQo2zRokW/ZuO9bN69nmqVlwEfkeInTfzNPBlpt3FCBDtT/vHHqiuGu29vny +VmVrGQo2zRokW/ZuO9bN6wv4AKQhKyRNmuTyLRo1pGizt7/tAWfGxRsLrowheuSs9sxqpN3/sDXEuy580V+msg== +VmVrGQo2zRokW/ZuO9bN62QuF9RgIvWYAvtkBOklWahJuS5Iwrpco5flniSnXTMnKmF6EanvevZl7ro/UcwauQ== +VmVrGQo2zRokW/ZuO9bN61GGTyOeuxbBELjSBi9UjiuHUrSs0Qw4w4RQ0FpK9Uhw +1u+XjG/2+GSQRv6EzCaWRQ== +p9oVsBgcyiBMjDb8CcPOqISoXcP31sEy6yeGqrdTHTE= +9roQnpt+Z2/MRGQwTV70NmvcIxxAFj+jWWxJm0JLQ+8toR3dEMzh+bvbY3wCbu1mXhNOFaMf293ZJdD8nAR2vRWuDQ0xFK1XOYgRT9saoCZAoMAlxYAeMwEYZ5i1EUeO +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmXE0DHXGFB6aJiNsHWco656Um0K+v9MxXclNSH3Y2Y94IBt2VU17vowICuf1l4x5+3ys5NwwgsWQGB/O8+kRvW +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw1rzSdHaz4/toHahnquvybLe56ob+rTcpUnQNLoF8FdDkYoXHE7rb7P+VtPnklwyk8leqsnKjpX8iLFIbauaCBo= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpnqrVS87Jazk1COXxY6MKoGdqTUD/rrae2XraDTP/sxg= +1u+XjG/2+GSQRv6EzCaWRQ== +/F49ykgjedWbzpXYCpMOghhz4+aAm4g1cL7md+v3VTg= +CUHO82G5MDpSEiqhNImyK9/OM4rF5zR/mlqmGFjGt4ntdgFK1WwSZcRWwmCxvBIE3s6rp4znbtySNAs0shfQr1XP3oYG+rZj6Tg0JrXTaaI= +1u+XjG/2+GSQRv6EzCaWRQ== +Z8vww4cYScz1fdJzPlqVZ+6A5TA48aA/WM1C/4K6iLj65MrumO53WZnax3RZ5aMI +1u+XjG/2+GSQRv6EzCaWRQ== +Z8vww4cYScz1fdJzPlqVZ1Fr5D7yweiZGvqFkjWBesigBcoLEhPnVl/yBmOg8mCu +VmVrGQo2zRokW/ZuO9bN6zCkMpfQe4UnnBkVgICzbEb6tolP+BPLLou1a+/RbH1M +VmVrGQo2zRokW/ZuO9bN6+H21AmEV6PHgIhYl3OzxYc= +VmVrGQo2zRokW/ZuO9bN6/ao2MdYJvICHKJi0iswn+ipqpiAi+O/r+bOKGxPFUWk +VmVrGQo2zRokW/ZuO9bN6zBS33FyqU5nOu7l2DKtAMI+wopEbCffT7KX3GUxYv8P +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uKuogcIjV98/chvhl6NjkqTkfbhJEHcVdgJyQRXlPbM5 +VmVrGQo2zRokW/ZuO9bN62MS9nmzeLlDUQSVLN+VybIHMMP4nyOG7ekrDcQ67mz0jIHnNSKxpFxniyoeiWpU5w== +VmVrGQo2zRokW/ZuO9bN62MS9nmzeLlDUQSVLN+VybIbkojqK5eksnefGgNlzw1V +VmVrGQo2zRokW/ZuO9bN63nfiB1fb9BC3tUwYcETBbhJUCbL+zNAoeECaUn0yNhmnmBze1ME6WnXiUtSzOtL5w== +VmVrGQo2zRokW/ZuO9bN62MS9nmzeLlDUQSVLN+VybJVhEftsZNCd4velHlcgGxldZDD+5m9cDufAPjwj6Y9SQ== +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +6K6zXmJJNC0xAXfJ6TCmfaBFl7DuG7+1nNvbLJkeawSnTK85ofp4jusSyh9wK19J +CUHO82G5MDpSEiqhNImyK3tJdLE8+4hhF+l+X4RDWwZAafVU8dGDkz+ecUDZLlFH +1u+XjG/2+GSQRv6EzCaWRQ== +hKAY1MN4tjDTCH4mzcSt5683GyUfTVF4A+b07SeQ8/U= +idAyQSjf8l5iNhCK9+gaITQB4kgmdDtbyl/z5F9YPRD0ykLyuvEjiFmNreaIjKyTehPHBBk24XGkKG/+P0FZ7m8PjTi/DCgHxZIDt8B/rZI= +bIkY0AstamhoAJWQHS2qfNmqdFJjl53QH6aOEHrfyT4WfmpECEcW5OVLsa5rLQ1h2KspJxu32LsHEodsEkKYAUslzaOOc0wG2G+6Co7ob1WL+31rpusFfjNEojxjBgXY +cbvASHjpysrsjdY5RctXmP6D+Vy4glrmEJZK4yi1rZ5rwerVlH4IKMknSrmKJNSc +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0aVB8BypfrBhxU22tb3SHdH7cLG+LHPTKEXXLctsVUbMkrF/lUbZghZ3wz2/0/SazA== +1u+XjG/2+GSQRv6EzCaWRQ== +kS9bS0X8uoJgO+kRy6AKEYJI24CYqFGct9VL5n2L2/FNn06N27RdCd4EwjGRePON +OIAm83eO9UgPVVx31CBN5xreZ2M+ksUUTrtU1zMxpFFXtFphqoCUmkaNp3CiNkgE +1u+XjG/2+GSQRv6EzCaWRQ== +pYLSDpJvxx66yCfbYL/7EbfQxG/toh+Cmgxys2Df/pI= +pAfxmP+viWgKmtuFOCzDu1hEZQQp+wqTknfp6z2Tm4U= +I5csN8e3J/KIFkMa5t+SJKcvYE4NNVj3kiL8YMvvrz8H6inVAPWunV1GucokSU+W +L0eUthVnpkGsmKFAX6d+uF9QuZRvAVhOdp7bYvmE4v2pGZRCaEHh0pj3OUCYNeLAaPji9u190jf+uxjEzWhHYBk4nt7aLuBKJRPOGrG8SEo= +1u+XjG/2+GSQRv6EzCaWRQ== +pYLSDpJvxx66yCfbYL/7EdgkfwHG3aEGi17Yw6v2tyQ= +pAfxmP+viWgKmtuFOCzDuyQ1vioe7dK9S8u8CBHIFJI= +I5csN8e3J/KIFkMa5t+SJP5ieXqyhKdpsUu8T3AoWE7POsp2/AbDJJ+EacQGfSh3 +Fabvv1g95jlx3ERwtS0pokMTpJX6SfPcIoCkhbe+ucBsMqfvfsjyGkOgq6g10Owag05vKfEyZIsm+RHQvpNIvA== +L0eUthVnpkGsmKFAX6d+uCYHancDBEBlUnfd05YgW3SDMIHSXx22lljPFS1K4roEZ51GSak5pZlbDiXrhxL5+A== +1u+XjG/2+GSQRv6EzCaWRQ== +pYLSDpJvxx66yCfbYL/7EZH0qryfIX31tmUAbL+gWkEhWkWvBqU+QJ+hc6hbVqe6 +pAfxmP+viWgKmtuFOCzDu2JmOf9GQXytwsvaIgIhszk= +1V2v8QerKOmubvSxgB4eTJhFEA1z5XTTrkAvv9L033ZAG7k0tx3iiFdZDczcHAoX +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpT73tXJGPh9xzuFnKDgTWtsdbWbNX/tXxLXIlkjl64xCRxa5L//4pcJZwyko8vq8Ddn3VzIupuHrhyDNYxUSe9A== +1u+XjG/2+GSQRv6EzCaWRQ== +O3CUgrw2GJfB+mDjH5+NdrFhpXZtHMkHCqEznptoGf0lBJpze3PNPJcRe3oa85S4MDyhGh3U0fz16HxaImXUQXarHy4q9LbhIUifu0i2eHYtIl9DWdSgEfv3paanVjCv +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpT73tXJGPh9xzuFnKDgTWtsdbWbNX/tXxLXIlkjl64xBGobRRP6kDKta2YP5emQ03k2IAFnD3PzGu4kdTyLtxtg== +1u+XjG/2+GSQRv6EzCaWRQ== +FLMbt5iAjw12yzW85KcCsne9YBxTXZnNpbhDsA7Fr51zRS4AaCpZE8V+2GCKGB99jqRKEEjE9tTqqNOgz/FIZg== +1u+XjG/2+GSQRv6EzCaWRQ== +p9oVsBgcyiBMjDb8CcPOqN0/eKL3KRSmTvNKShWWzrw= +sDlCNtGYD6nPzg4cZ+6YAByKWn4AGh8Mzj5t7m4RHwvubLRA8HJ9/h6upYn0rD+dEoRGvgO5paBrB4hcTihbcg== +1u+XjG/2+GSQRv6EzCaWRQ== +mG4WxLOsQ3e8HrREEjwO6zSQnaDODPqx7hMDbjtUismkv2I9zu4roUGjTYTyaaNr/0Ak8Jd+GSIXo6o32+4njSSKq8d06R7FmG9pCBgEDGI= +CUHO82G5MDpSEiqhNImyK7iEFplWCvgelfrRiFWadE1r4Xk7E8F6zZPA1k86kuFB4F6PpavPmhcMnLVaGFIITQ== +1u+XjG/2+GSQRv6EzCaWRQ== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN65wVnPTSBJsAF25KglBK1zY8WoWiscTmnWzWDp3SoJoFUlAu0H6rthhk6T+8/hs4Xg== +VmVrGQo2zRokW/ZuO9bN60ZPyni6U+mAJSWcuTR0dUAEJYkMv1AFk5xdJ21gf5OqWalMa4OOzbm7YMPcloHCbAuFRFjmesnBI6NjeSXTavc= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64tbY8l/LJ1xG0ple3sqNLiZ8LChK6DqTZF6bZWus+xJ6uopxYut/oeNC1OHGnkiMg== +VmVrGQo2zRokW/ZuO9bN60ZPyni6U+mAJSWcuTR0dUBkfKfptHml8+4mSQ9K+w9jUZ3Xxe4ZSu0dX8asqBn+VMcv/ibBiatwY+ZGR2EeOyHh2xDKMs7iHPDLdpYVVXZ5 +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wIUNMPxkYSX/0GfNzJ3xUfYXcXzgHFUgcIQPyiinkOW +VmVrGQo2zRokW/ZuO9bN646OXWelyIYU2xRPUxRlGkJcIaIJLy1ed19RDL6uKh8/ +VmVrGQo2zRokW/ZuO9bN63SGeQVwDZMIg/q7Rk0N58YkDmBJRbv3hogMcIoRjjwsqMOOvazJs9boqaajLTdjbA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN683UE/A93CRhdY1t6X40J92nGgImx/KyCa54eyFhMmSe +VmVrGQo2zRokW/ZuO9bN646OXWelyIYU2xRPUxRlGkIoRVshwk5gzLGe1zgdOkhR +VmVrGQo2zRokW/ZuO9bN62PWu0VDstvxytifZKTp7nHtf3/ncqMW+HuGM+N2K+nVQTS+PV2r0SfVVXdiNQ+/WQ== +VmVrGQo2zRokW/ZuO9bN6+uD3+pvByB5DglVdzot2VsZTsK9H+SNLyasTa76LKnruZk2yvNkUno9d2Y3OQpRxphyCRA60H1+NrJhw5/8LLSaQXcCp1dZGt07bbIc3teV9+jYEkn3YjPItybzEN/lbw== +VmVrGQo2zRokW/ZuO9bN67Nw2FfqHjSLPJc/1V3wls4= +VmVrGQo2zRokW/ZuO9bN65msj2VfsvrEWaPQWhn4JLQOnMC5giqyFGmolHfkuSbGxIJcp2aIMZjU92LK95LucA== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN69jxBxUR3JCDU2eiVWpDzR5AR8IAtxhsgnlMTA7SgWob +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJk2Xs964T5TXoRM4N/jz1qgA9IkrN606LnjGizXZqo2bw== +VmVrGQo2zRokW/ZuO9bN67AUlCoGGGFeT+yIe3Vo1T1wlumgJWQNCCq0gfXaTzaMEE7BEh3eAD+I76ehEb9DMY8RHUnzt9m6hSDiNem6wx4= +VmVrGQo2zRokW/ZuO9bN63jU+XEf5ff8P7z9TBBfViG/kpIf7jXs93Y1tmNGW9mRQ8KmlMVem/ESe+BpyIlFTw== +VmVrGQo2zRokW/ZuO9bN67C8R2BqtiOVDd4j9LKAl9gBYjN5mZzYwBrm0MhBhKu1MhcCj1ODpye5zh6S3bgKzWa+v/yOWx0/4gEhIanyN18= +VmVrGQo2zRokW/ZuO9bN68jYngvlcpgCPZczKlSbEura1ZOdwl1Td8C0KFwIfN+HbOLHnG5adJWe8zWZBbepJA== +VmVrGQo2zRokW/ZuO9bN660x1E2z80RG1M2zA4mBLClrRqBFqfukmmRdeofCWtYPJbM5+NgcUnf5E5x3UHFXQBM90iAi6iCW94Q8SGPtMd14h/qikOGv+kfWBPWHQhR973pwJHhs0giXhGwEjakgkkmxt0itQTLjc42KcVb/ejE= +VmVrGQo2zRokW/ZuO9bN6xuBEXhOP3O4ciLUH53QFC0= +YJhe5dA54PK+sRzGNXyfpxJNuz0JDGq+jOlsqpxouD0= +VmVrGQo2zRokW/ZuO9bN6zOdvfncHnE4ZOiekMEGN90v9Keolx6PWBYkQCqgcucU +VmVrGQo2zRokW/ZuO9bN675TerfJPX0w8BHJqZqh1glUh+/xY+yOI2Vv/D8E/AFH/5EpbteBA4NqRFuF0xb/k0PV9UKofxoT/3oWAF8SvAMgGNnZauhwjMPGiidtXLh1aro46xf8n7Ib0Nno39o43A== +VmVrGQo2zRokW/ZuO9bN6xF1SJaAm3VttartJvhccJnyW/ti+4dj1F8iJbyP9yoDrLn3/I8TUd+n58isdoRtjw== +VmVrGQo2zRokW/ZuO9bN65rD4nI0MfwzlsBII0cdPaaZpIV+fzDC6hg78Cjxx/8NSrACu6//htKGSL52rvk7cwPl1C+Sbu5k/0wyDW0xiUc= +VmVrGQo2zRokW/ZuO9bN67XKiXTToYD4Xj8EJf9tU1+G0DDYHo9+c8BkhKhpt52Q +1u+XjG/2+GSQRv6EzCaWRQ== +lTLlgCoqofnya1u8s4t+TKUxw0oaCBKvUBBgu/9ntrs= +iEbw/85IA9dHrKUZiqzvJXZy1BSITBE6scdeXmXhVokjaUw6U3cWyAc1c5cQJJ97MfMgs0Rf6wBnBJMwNTsrstHS23vkCN89ogbpejUXPY89L9yw/sp4qCPrFlSwIwo1F7O+MPmPe0HtpYdS/upm4w== +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcIuWcW1HaUBcTUGockc85s1agOkpQZfCzrtnUqIugFXnSjQrieNzx60LxT3MMOZ5RGdLjH3Ih0pcSyMNTnl1Ptg== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z7Vc7221CfIz8ZtOcaiNHvZkZy6HmpWI+rolHwFXNUBFFiZwVxZXFUVrWyCHCIWcGx/WaCGsO10+7MAHOvcnd0RLFCqhKBqnlrNLHn1aola0g== +1u+XjG/2+GSQRv6EzCaWRQ== +an12ef3MEYZRkLo9u1pLIxgtduPO1eU2evs0LB4g6To= +ZIINHHQqnB+3o7SPJLU6718MxCs8rC3NXHx0+pOLLOs4F8HgaFgB2G0uEixGsaYIuOIkktee4ok6HSonoGjeLbG1FTWjAj7SbFjGT+6PnbA= +bIkY0AstamhoAJWQHS2qfNmqdFJjl53QH6aOEHrfyT4WfmpECEcW5OVLsa5rLQ1h2KspJxu32LsHEodsEkKYAUslzaOOc0wG2G+6Co7ob1WL+31rpusFfjNEojxjBgXY +cbvASHjpysrsjdY5RctXmP6D+Vy4glrmEJZK4yi1rZ5rwerVlH4IKMknSrmKJNSc +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0aVB8BypfrBhxU22tb3SHdH7cLG+LHPTKEXXLctsVUbMkrF/lUbZghZ3wz2/0/SazA== +1u+XjG/2+GSQRv6EzCaWRQ== +kS9bS0X8uoJgO+kRy6AKEYJI24CYqFGct9VL5n2L2/FNn06N27RdCd4EwjGRePON +OIAm83eO9UgPVVx31CBN5xreZ2M+ksUUTrtU1zMxpFFXtFphqoCUmkaNp3CiNkgE +1u+XjG/2+GSQRv6EzCaWRQ== +JO+88//yCrSdCZ+EAMDm0yFbSIopqNx3oIgThJXexUg= +3evf4kKYx93swnLRU7HD6o3c0ZSE4DrGhUeq5hD0Tk6sCeZ8grDcjGgmV9ekLXlXLrjiKhGxLN/RGc9CfEUS2hZK4IFrEcH9P3r7zs0yv9Y= +1u+XjG/2+GSQRv6EzCaWRQ== +Ok7/pyXurdp+IyZCH/4JsjwYGkFmVamoKkgQ/Sxb3qURbC16yjmYW7NsBU4DwPV1 +pAfxmP+viWgKmtuFOCzDu1hEZQQp+wqTknfp6z2Tm4U= +I5csN8e3J/KIFkMa5t+SJKcvYE4NNVj3kiL8YMvvrz8H6inVAPWunV1GucokSU+W +FsRTDT5nlYHpT6JC9UdH4T/Ks1cr44pCsms/6UrSZVup0zIWRkrkO5J74QApXSGyC9gRdNAEpidKLLWeHj+Wpg== +1u+XjG/2+GSQRv6EzCaWRQ== +Ok7/pyXurdp+IyZCH/4JsrX/xmC2hqUpuA5tYuXgXM/zu/KZHZNQGd8q3r453WRu +z4ZB+KgsiuNQr27ZYy3/iLBr6gY3JFidQY4HnHOG1WE= +I5csN8e3J/KIFkMa5t+SJP5ieXqyhKdpsUu8T3AoWE7POsp2/AbDJJ+EacQGfSh3 +FsRTDT5nlYHpT6JC9UdH4e46H3OUVWY2nDDUZuv7bYX/aBzdhhcivJQ5ftagC0vBI6oRuNmT4qbsTFi6+BY0bg== +1u+XjG/2+GSQRv6EzCaWRQ== +Ok7/pyXurdp+IyZCH/4Jsir7IfJ+8RckVBFzCfSCTIfUG3sHk63L30a4Cc8bunFe +z4ZB+KgsiuNQr27ZYy3/iC8E0qBENptDhINYHhQB2rw= +O3CUgrw2GJfB+mDjH5+NdnSaXRqSe5/vglum8pPqhP1kN5ssZXebzL4qrP3wm1YS +VmVrGQo2zRokW/ZuO9bN6xOcE74GXukFgoP7mn89osg8SsOUQXFdzvPn+qkat/4/ +1u+XjG/2+GSQRv6EzCaWRQ== +EFBFgJDcvthaVbYohI7UmsUkC6amB/doNkBisGj5LmY= +nNkEkiLAGyoMlIeE163bvCjozNxoG5VPQyS9oAyg2RvorzxOhrFffjzL1Y4lPgxXPigscZpJNW4Xksn/TqWrxXexkrtmcCPVaNBXIZ8xAh4= +1u+XjG/2+GSQRv6EzCaWRQ== +phCI6T9adFO3Wc/DVsBsqQ== +32pdC9DD05OE2l0oXazDFHGwLxQjI8TRgs4IRu7iv8BCvdjOJiV7f4nfgJ+TK7+mP+ORhQXW55an/xk0xcPrzQ== +1u+XjG/2+GSQRv6EzCaWRQ== +lTLlgCoqofnya1u8s4t+TKUxw0oaCBKvUBBgu/9ntrs= +iEbw/85IA9dHrKUZiqzvJfBJjW1/xsIsR8A2CXbUbTDF856L6rSWQ+dTSrJL9C43oAkW44qMM6W91BRZ6TbD8HJs0JZRxyJR85GlHfR4G7E= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z5HnZoMWfDfSE6uG5YIN4bem1GfJCtbwzGPfNujBHcvww== +1u+XjG/2+GSQRv6EzCaWRQ== +5xPAMLVvOxwzYIoGEl+KvlDMXEKZkD6wifs2Gz721lQ= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +AjHNSdyORItsH7GfVM9IMkEQfAG8nvpJ5PlBXkkA4c6uVb4nV8pDwKyCuOnroe6myPTXQuthSQdyVS+yTRqpatFK+UJ3mgiIlT0oL/T0j5g= +91gsC2HsVrvTm3cwuG3h9ku+SnEuh1gfPlaT0BYQ3ws= +ewXF36/n6Kf3NzNwV/iVfvwkfIk6841e0Ue7kEYDxQ4= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0YxSyi5rjFDyLtdtV6fGTbqCUrVbW9jXMKIMhkWcPFOF8Dui5BREfhlXCbWX4Wq02oFgzzgkuOtvoX6E1LDgkC4= +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9sd97ybfxMksfg+EU7vkw/c= +DeJGSUuvGZdpIIECWorxwVf8BOBEktMLPmP9cIr++leKhxxVkTq7YW2L3GnsowyD +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0YxSyi5rjFDyLtdtV6fGTbqCUrVbW9jXMKIMhkWcPFOFIzuN7mmSLPtngA2wCqTx+2J/IShLth0EDROW6Lo7Ros= +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9i1NEZiWQbUWUf1I+2XxcOPyVXXChizlVctse2YyhkT7 +wgR07xfoapmx6eEnFHXXYmVJ5v0pwDT3iCzPtUJe5eY3RjX2ra7K+8jmS/1AkuXm +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0YxSyi5rjFDyLtdtV6fGTbqCUrVbW9jXMKIMhkWcPFOFBRAEpGizsECN2YlJX8ywlhVlKo6NdWc5iBwanJVStXM= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgRy3xtF/BIZqC2qdzL6cmg6LMRGLe6FQeczA1WOg2/oY +1u+XjG/2+GSQRv6EzCaWRQ== +FkDKDwNuIXbbD9xZMbZzXKACTQpPNB+EXUHoS4rgiCpdwfK+kMCs0TPIsFl2PUtc ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +L2O3BGGkFVpSEfRRE02F9q5srPOdWcGGM5brUKy/ynpX8Q1shh2zIsxyBAeb6vYTRFvQAsLy/pOfnHTRucKxz8PgOzhMH805I+D2oQJIJCzYmoCDVaXibcgTo6PV82Yr +wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0VgYJcDXtyKb/JKLOY2KZO4UB+NPR50aKckf8eAL2HnqEcojt9ZBnrI4j1z2fJqPuQ== +1u+XjG/2+GSQRv6EzCaWRQ== +cALTOh7OQEr4o5fCc9/A03qUKf+Tn4nSnE7MBDCNym8sW+P3qPtD52WwMx6XflDoef7qvmEIEoplahR/D7P1jg== +Il5YcNzsAW1ppTTOdwCOzSr5dk99Ps4e1hQaV/md3oyuR0zkTwtNhfpKQ17x4GtqcBU9F80v1byDZSyaMuObzg== +jFWp7iGXcNFAo7+mU0KY9ahm9fm2OLt//Id5Opfj3a06nttS5zFGBG0bD/0dE2HzQEOVppFdSfzEePm7Ka355Q== +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9ie0DHX18L8LGfHVKI63Ke/pNWx8fOQwIScGdpToAWtI +wgR07xfoapmx6eEnFHXXYlEV6h2Qh0/E9zwcuGp+QctxMca/YFYUc4GaSAHUOjC/ +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPSYxJ9SeZK+6AUohBzjAqKpGaY/Pc5FRIPzVpxGjpYMLzNG3i6E7y5GvOCz0EtKxmgQ9WBxh93nG76H/azI8fKZwYTplbkyl6TOnVDhwQ/jg== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYp8QPW/Rny7GxLHpvcjeLGxgJSlfxADrCxF/0Fw8Dr1L +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPSYxJ9SeZK+6AUohBzjAqKpGaY/Pc5FRIPzVpxGjpYMLzNG3i6E7y5GvOCz0EtKxm+ShYMtCfjIyflJQ5tzWuWXQox0G6KPLXWwOeoXn1KRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYhx8u27be6FTqpRMEKSdDxFFRQGrLDDOpz/lgdLZ14Au +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPSYxJ9SeZK+6AUohBzjAqKpGaY/Pc5FRIPzVpxGjpYMLzNG3i6E7y5GvOCz0EtKxlQqvnXgrs5S+WY8mcBeEx9Rtwo0s+xbAWYW/hc65t15ZV/iLOS5mrgOAn4xWFVlMs= +1u+XjG/2+GSQRv6EzCaWRQ== +ewMPic4oY8KA6fNbX11t2et6gSY3TY22d+P69UwmEhMFCd+Kvsrq+ING6uFRo9XR +XFFJBAEuV3u3dxqzRUxYy6I54hjChMSzUkAPwfcU37M/WvG0nD8Ukv536FNzU6xe +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgRy3xtF/BIZqC2qdzL6cmg72RAKeTh1iY9zoq4fK2PdLVCZC4a1j1X0DgFlBQiM9xg== +1u+XjG/2+GSQRv6EzCaWRQ== +a6iZYbokeyFY3ARiw5T+6DQlFHi2Qvw66wFDifoWB24TnOelgbiQ9gGMHj2IrS3uiASYZej7fIYhwhI82Nhq1A== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +Ws6i1ZM/c9zrSAbxeLx1LWu3wUH7xCI4ahh5PhMYd67NejEZazvcf+jkP2YLfr6knfXxb1hvXg91B7lZ1H/XnwFZqnDt3Wcdh2Kw3C/0vWHG3yfR0y75HhiXSsYZkPqH +91gsC2HsVrvTm3cwuG3h9ku+SnEuh1gfPlaT0BYQ3ws= +s8oZlk3kc0FacCBDAb+rTqHvR/rpCiMtf9O5/oRpCMDKQKA1V+fg392NLu/6HnRe +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYrQR/7nBKlFU7UIR7Tp7Ask= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/XRRyd9HDiBpo0pCt8PUySBg== +1u+XjG/2+GSQRv6EzCaWRQ== +P40wuHk7MslPmvVoiV0eZS9GHjGsA1BpCAEYF+DyKrg= +IZj4TYq2HlScfSdRkI+QP7ds2Lg0nLguFAxiA9uKmwWNy6Iyrh6ke395/+UQsoR+T3bjNrP1/7gKiifvP7wGuw== +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +Wk+6iLfu38DoetOmRN2a98ccri5id3E91jM5eSptqngq5wL3OhenY4CPUO5T8L0u6OwkphGnac6ebW9W/b+NfQ== +CUHO82G5MDpSEiqhNImyKyJX2KNfQKTBGoaamjo5u91mIsUT/9ESdK1Yvlj/kDEPdW5TWq4h64U4gwbyure4PQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPh69q5/xKR7d/fTlhH/eiNy2HOp9v+xDToENMafrI2uH+ +FsRTDT5nlYHpT6JC9UdH4XvVWOdC38vK3DRrBxV85ZmavtyCegaO9LHz5BgOeHOtGNMY3uKNB5/c2qM203CTHA== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpKm/Hz2+WKHac+36/qoVAtg== +1u+XjG/2+GSQRv6EzCaWRQ== +Nm/97dX7QPdHIqApKEes3MdGfvBCDkBlgzrReFmLJtM= +1ZFe+UUdQmu/QujLaciJakTompq9jdeKbyO+InnG7jf+rN+ro8NqSu4sq3o0fjhL/Xl1OWOxH32TuBUBpXhGOb0l+MfmSWcAi8hOuIFR/5I= +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcnZn8b7/Sy8ao1iKp+B8eNa2x9mngQD5DySa3JctmO+E= +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +6K6zXmJJNC0xAXfJ6TCmfaBFl7DuG7+1nNvbLJkeawSnTK85ofp4jusSyh9wK19J +CUHO82G5MDpSEiqhNImyK3tJdLE8+4hhF+l+X4RDWwZAafVU8dGDkz+ecUDZLlFH +1u+XjG/2+GSQRv6EzCaWRQ== +evtbGaKLtemdDHjWgKV7NPj/mVfU/GjMxho1uU6G59IXPsNE8ecIelvjLKue6j3hS2NNnUxYMJuf58RqfRfnhQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +ZKXiNVFMygAA5wm63MFYL6obkTLenBSf5k8VJAPp5ngGs0PMZUcSj6GjMkDoXKcetkZtYlbP9iN4n8/XkbLp5hSnhSLePQghMUqrvv8+QM4UQzU/8e8Y6TcovrPiv7Nk +2yQsUep5WrV3T3htNZ1L8TIohok8dxSGY22+WeWcmQzlauL17DyWRvW+qqAHUI9z +1u+XjG/2+GSQRv6EzCaWRQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +kE0OGUDieDpLXO/pX4APBArHp97I07iQR92UhHwLo6k= +xWoGNWjKGPfI4gq8aHoTfOHCHk0sElv9s8frQ1Es3TZlMYL9ooet3sY2uLB3HVTi3e57gINhClfifZldkEGkgg== +xWoGNWjKGPfI4gq8aHoTfKj7V6/GbVtiII/Elkzy/SIxsAN6Em7FysJa4jADHlGMg/lVpPwNP8IEbwy04HBldA== +xWoGNWjKGPfI4gq8aHoTfPfBJKXQ9w97Eln03DT906wnKaz6rRkQSCBVFLl+HC23ufNrzhC5bijcSs87ACWLNg== +xWoGNWjKGPfI4gq8aHoTfI65fOfKuI1D/27XO5rrJkxarQ3VgRs1nfQOOlihDLiD+LO2IfhZysqSiHq1mjfExfKeT5oNAUBXJTvvHENcYk770mveqykgpfgJ9/IEcV3cKm7aaYU1d9T5J29bDv573OBqG10PZi8y457Tnusw4Va2gexlwGVlDGG5TengHzQH +xWoGNWjKGPfI4gq8aHoTfBJP/QcKNqdYzuq5movMnogxB5R2dU5gt0Zk7St83JWg8FD4u9UEVIw/00jn1jMKcA== +xWoGNWjKGPfI4gq8aHoTfIn6PnMrB/8qeqFwmI91ZmH0gsNqllNJ0dN1JhivPS8/w0GsFtLkbsrsy19h/7c+0g== +xWoGNWjKGPfI4gq8aHoTfOoPpV8RnCEp0qDDG75IWv/DIdiq3sMANSD/7jkYq96F/ZclnJTAxRILux6aALMcJg== +jIYtewpcL6elNlf4btqXRw== +1u+XjG/2+GSQRv6EzCaWRQ== +3sfJhDjqeEN3VkPRXnOTXHQyM5tO/8e8FVlTeqKqv46WsUzAvHp6vNSziJaI721C +A69EZujp4WFkVkJizqN6C/fqBP4sRbQVabCFtjuIU2ZOJ2TWCbEt4t9BMwacHaGI +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0QA9S0cmE5ZptD/tu+M6uzCl/jog6QV0tj6yWgEiZQ4q6uuSDo3ayDOPPYnkUgMtX16FWlrLW+uZ5n5D5c/0/Tc= +1u+XjG/2+GSQRv6EzCaWRQ== +OIAm83eO9UgPVVx31CBN5xrFh1fu4lJbxv0hxvFbJ9/Lzwlg3G1oy4PAivfxK5DN +1u+XjG/2+GSQRv6EzCaWRQ== +ptbr9LWGGT7tn7j3hgPjfbrssREvkhB1Av6GQHuY5C8= +91gsC2HsVrvTm3cwuG3h9ku+SnEuh1gfPlaT0BYQ3ws= +s8oZlk3kc0FacCBDAb+rTqHvR/rpCiMtf9O5/oRpCMDKQKA1V+fg392NLu/6HnRe +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYrQR/7nBKlFU7UIR7Tp7Ask= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/XRRyd9HDiBpo0pCt8PUySBg== +1u+XjG/2+GSQRv6EzCaWRQ== +P40wuHk7MslPmvVoiV0eZS9GHjGsA1BpCAEYF+DyKrg= +IZj4TYq2HlScfSdRkI+QP7ds2Lg0nLguFAxiA9uKmwWNy6Iyrh6ke395/+UQsoR+T3bjNrP1/7gKiifvP7wGuw== +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +YnT9M79JgM+dY7nG7ivAFRDN5w3P6LhHZG0naG4H2pMqSOnLuEF3TrnObbybq/T3 +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +Wk+6iLfu38DoetOmRN2a98ccri5id3E91jM5eSptqngq5wL3OhenY4CPUO5T8L0u6OwkphGnac6ebW9W/b+NfQ== +CUHO82G5MDpSEiqhNImyKyJX2KNfQKTBGoaamjo5u91mIsUT/9ESdK1Yvlj/kDEPdW5TWq4h64U4gwbyure4PQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPhwm4Yk27M5YlBXxNpkGWTJ+votWQvP1T5Ojr7y0INGHkHfvpZbOucM52tKCtp/K5SQ== +FsRTDT5nlYHpT6JC9UdH4XvVWOdC38vK3DRrBxV85ZmavtyCegaO9LHz5BgOeHOtGNMY3uKNB5/c2qM203CTHA== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpKm/Hz2+WKHac+36/qoVAtg== +1u+XjG/2+GSQRv6EzCaWRQ== +1ZFe+UUdQmu/QujLaciJajx26a+2STfwMi+zieErgi3CBgaNFGZigZi8pTmSdXTQmGqTiUVKLPzrQkD8KSrdFg== +FLMbt5iAjw12yzW85KcCsnQmJLJhCftAGa03uwPfXWNUQ2Jfp4woEQ+JLN+7bg9Oy3HrWj/QgzOMHfF3mMIFtg== +YROiDf0HfA7clzlpwWrPnWCbzQCFmxCtQkXbusOY3gjdQhuS38kc2bwUPY2GagZ/m/n1Cy54BuGtIYCO0lz2+Xnwiapa99iOCszKltCMtTw= +1u+XjG/2+GSQRv6EzCaWRQ== +Wk+6iLfu38DoetOmRN2a93iqiWLghhd+n8qz7n4B9D0= +1ZFe+UUdQmu/QujLaciJakTompq9jdeKbyO+InnG7jdyCf15aEY6LOsV+bdTpipbwl3ZH5jxRlMPO8+qalE9xw== +1u+XjG/2+GSQRv6EzCaWRQ== +8i88MKADknh6ve+bU8Ezb+WJ6niZtJuVbQw4qpRaLig/H0N9vJfeujRYJbh3YTtH +84Vymj90Wzn5yYuvz1pUuDvv2eJTuL0TJq9mU3xLbg30wcVaLL+kSX0M9FeEUdaO +VmVrGQo2zRokW/ZuO9bN67C2ez7Z7xiKZG19sVmgmMg/szOSu0TUxszZWDrXwid9LxFkwHgd1UBJa6AfIpQ+qA== +1u+XjG/2+GSQRv6EzCaWRQ== +84Vymj90Wzn5yYuvz1pUuCWRuZGkhlJnLSClaFLOOGb6poAqIb2TXQcYiRPjpWOS +VmVrGQo2zRokW/ZuO9bN675vxEk+ZDMj1IxUahsCYOb8sGgoVXMqknYjdiwdUQrN1uOQv6+nqJfxxhc+fmvEydbSMV0wyDhXbD2QcmxYuFbeQv6ltaFTzgkV/Gjsana4Z6uPBK3w93KR+W5cIfoStg== +1u+XjG/2+GSQRv6EzCaWRQ== +84Vymj90Wzn5yYuvz1pUuDeimrBZjjT24kCGUmMMVrxvZ2a3W/syVkEg92NrBZWw +VmVrGQo2zRokW/ZuO9bN6y8aQcwZ/hSna7uqNWY2EKiaIYyFt7YecCLXxJctzYQmq2zUc1xRjRe2sZt4Yq8Ovw== +1u+XjG/2+GSQRv6EzCaWRQ== +FLMbt5iAjw12yzW85KcCsmy0dtqL77758KtFtx4Wv3amJjJGHf130+oBwGsdvcbN+qQC09Ap2i1s7iy0TrXHib90sT1B/mL+D7Tlnm4/Kho= +1u+XjG/2+GSQRv6EzCaWRQ== +7bJ3Uk7aqJsghutxBeStwitQyrNq3vvsQtPQ7nQ8Roo= +I5csN8e3J/KIFkMa5t+SJKcvYE4NNVj3kiL8YMvvrz8H6inVAPWunV1GucokSU+W +UCjjP9uamQhFjq6n90cX334w5N6kM7POhL+ZChb6xIMkwXdXYjEQ5NlTf7yiiE9gvWTb6WOc+v/DaO8XKvCo0DfwvXhwZmBLMdEWuY0QChlXwvqsJ3uRRSP2BgNVeIh3y8nv99zO6pX6H+vKgdKxbPBCRVDJV3qv+rKTCbhMJSTb3EZvFShchBKabWneBbufQFurmxe8euQhTZnVG+1OKutH0YhtkgjuKkdMoUYOnHo= +1u+XjG/2+GSQRv6EzCaWRQ== +zBfpEDEHUpKcgzvJDq0C4YC6UMbAUfqAJ0xI0cKnOaESr1M8ldgYE+7A7EPPEZ5g +84Vymj90Wzn5yYuvz1pUuBf9UiMoSzUCfQBk2T/lZO1ZenPtkuAwJZ/DjYGk7EF3U1UkgN8sUTg2e8HZLYpjgw== +VmVrGQo2zRokW/ZuO9bN61K4MXaMsg5/Kxe0Z+g/3qNgb6okRPQ5AYXwGskH9ejOnwi4YAc+D7PnIV1ewTrjYg== +VmVrGQo2zRokW/ZuO9bN6wZri2Z3CBt2YpUf7zFZ0+KNguNuVrtBMhHtFgJ7WYYf3JHfXVX1T0GtSc/XTfHPmg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wXyEF6XhF/PmYb/RCvDnIg= +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvNCObquF8KcqaCn9joiTHShp4vhVSeHci2o+TX7lXV5bg== +VmVrGQo2zRokW/ZuO9bN684ehqpwxtlh5PoulYJQlzwAsh2I93oB3djuYOuh9qQN7UA7ptBZ9kbWq4QL87fNrg== +VmVrGQo2zRokW/ZuO9bN632U9Ut74PgKeN/Vd++qe96QUfvqhKcAMSKVyTsZ5tjiis5cEaQeER/nY9riZ04zHw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64IOlCXEAuw7+JQuoGU1tA7y+jY6s/JvEWCe2pdd+pjEK7nJUBmqZJQTwm3h72ag5FdSSEPIKbcMVZEYOsof8a+Ny0UHjA4vHFUkNX8Y9NDS +VmVrGQo2zRokW/ZuO9bN6+0ruBcpcWh1tg//57328LXWh98WBeFYX/reLiggO4A8 +VmVrGQo2zRokW/ZuO9bN65YLSeu6SlsROZcKj6GOXcg= +1u+XjG/2+GSQRv6EzCaWRQ== +P1bBdERejTBi3ynjwlj7j9BN16EKwuXwZL0J6G3zRwc= +I5csN8e3J/KIFkMa5t+SJP5ieXqyhKdpsUu8T3AoWE7POsp2/AbDJJ+EacQGfSh3 +FsRTDT5nlYHpT6JC9UdH4bKLDUGeT2OmWeP4uXVXctcW9UIqr4TBGbKKt1WvG/efazn8+MYwktdhj56RVE/jzB6VLeItbFDX6J9e2phdbn4= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +4Ky5RNfad94RDxFAfm3/hD1S6TvHVGkU5RCZUxk7mPUS55Jd4Ae2dEHUhzTAxZvn +FsRTDT5nlYHpT6JC9UdH4XY/Mijkqlb0CXZ+gLX4s8Lo/U2S2nexcGC+4OIyPFh5WgAPq9Gi1jmpFN9HHqK8ckgWpJQHRdLlpUv0ZQ66iK+mMCKlcQxGqoBtWZH9V+uyb9EdarEJRD9Gwzv3czT0CHCq3JfIYwJXLBpxNcw57pU= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +KZsPH0cmEM2QRSeJCcSyucww7vjWuHBitH1P49Sqh2Kwbdrie0k2+bXVIdFrP4Hp +iEbw/85IA9dHrKUZiqzvJYFw7QmcLtkqxGPIfRytlW97cg8SvyFzcKnhDF/yOCIqBpgg2zvuF6H5ZBpWx9EJEA== +1u+XjG/2+GSQRv6EzCaWRQ== +Oq4JxO2byG1NpOr3BWRu3IQ7sxcnLbXUljntZmFfE3nh6wlJfMDKKO9ubZppLWjY +Oq4JxO2byG1NpOr3BWRu3Oj+k38Jm2FrVzxggusCskbYvEUoOq/NQoGJhFujv6sPpGYXVLDzIbfY26IswYzgjA== +1u+XjG/2+GSQRv6EzCaWRQ== +/F49ykgjedWbzpXYCpMOgrI9YLZA9ExR/eC6+PgN+EBaWlpc2Mqf7k4KIlVymqOX +Oq4JxO2byG1NpOr3BWRu3DAGQWIxEWJFwo5ywit0Hm8vjztDN9DFBJzSbRYvk9E8aVOOFQPA0eKwd04IoEKP0Q== +1u+XjG/2+GSQRv6EzCaWRQ== +Wk+6iLfu38DoetOmRN2a9+VXMx6SjWK1apPtY6dMfYMoHCpgsiaPyMgPmEtMZnTp +CUHO82G5MDpSEiqhNImyKyJX2KNfQKTBGoaamjo5u93IOV5gsvlF+rJFV1zpx+Xg4cy6nKs4KOvEdUG+jqVRMcHps5klF6H7/4DUJCho/6Q= +1u+XjG/2+GSQRv6EzCaWRQ== +tm8SW4k/FXA1T5YrQDZ7wWnoVqAkjsZ5VE6E38yZjMw= +Oq4JxO2byG1NpOr3BWRu3MthcyzqNuNs+1GYOnU5w+jsgU7gzHR2JfE2rBUCpEkb +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdatB7FelKh/g6Qxw8LyyT8HGxMvOPbla6t91vkFW97vk +Oq4JxO2byG1NpOr3BWRu3EQ6fq4nu+wy0ERSqtq/yw8M0VQBkl4VxvsO8pLJ0tXjHk57blGJcDTSf5IZnT6A33vklf/1JB4f15dsurs4TIQGV33FHDGQiycTjLI89DIU +VmVrGQo2zRokW/ZuO9bN68PLvFAyPYCFptnUBkrwhM+fcDkoqf6krf9e3/VUGhLb +VmVrGQo2zRokW/ZuO9bN6wj+eM5w23B0fsVf/h/HrFSTzO98rc5IeoE6011BQZVK +VmVrGQo2zRokW/ZuO9bN69QoqAP8+J9sRJqfCnnzeuAXZF+1S2UZnPaDpalUlo3b +VmVrGQo2zRokW/ZuO9bN68ULkZoiR2t+tvBrm+xgZHsLbfA78IfbZM7Dajj7ThPX +VmVrGQo2zRokW/ZuO9bN6yLf0ryvhEUQNBfas0QHrzpoCyWrDfpVrSO9zlFCkvcL +VmVrGQo2zRokW/ZuO9bN649yP7OWYGC7nlAREd+ogQMAhw3vr7ETL50mj9Txb3p4QXNDSpgyxEDuOxx/H+bCgw== +VmVrGQo2zRokW/ZuO9bN6yXvSimWjBkSPa0oACTE6ZLwBjvix6RVUl1pckZX2MOfzsYgQ/oO/5Wg0pi5sa44/Q== +VmVrGQo2zRokW/ZuO9bN68Th/U76hkAmXqrzmdE/ZI2hWSlM1jO4tNZ9jOuBXyUu4UXCtxGmpfpurmSBZ7nMz3CfJpY98NDnzDpftzNKbQ0= +VmVrGQo2zRokW/ZuO9bN60dcEpCcNfgwHuX11nm6/kpaOKlAgQkhD4jWIN9kUu++6vBRzZCJihAhZezy7MiEDg== +VmVrGQo2zRokW/ZuO9bN68woQ38PWuIkn2KGEnMhf18vsoH73KIsVQPZZeg0H6i/uUTwlcqjqaOOcGE2yARxSA== +VmVrGQo2zRokW/ZuO9bN6xc3KvQQ3gxQg4pqItQtHXv4FkR9WdU3ksM799ggpOqo2DDqalu/U1q8We9X/tjBew== +VmVrGQo2zRokW/ZuO9bN6zKmc9qq/eDXqFNWaoFb0zhPVy2ABZhvEveRj7QcX4YCewTr6ys+LhFoqIchJ6zMaMOLXJXIH3Brcdeb5Ue6260= +VmVrGQo2zRokW/ZuO9bN64l1qBkNbm6bRJujDvKFqz8cikEfZdrnEcpounC4jt3+LsHKisT4qqKZ9+YZq//PUHsLDe4jo0qqLnSPkq2P1BU= +VmVrGQo2zRokW/ZuO9bN6+PaCbxoEB1NEX+VMo3rreipvAaQyLbViNAr3J6XiR8BJigxp9Grj+VE6re2Su5SXw== +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdQI2Yuhuvo0Ui21i4BCi/nOhdFEM8veprF56BF8YOMRn +xWoGNWjKGPfI4gq8aHoTfNm7h2/3rk5d26JyRNKl45IM1E5n5Mf0G579JPEy0AiTBQKCeUZgjg8y5TnHK2UMyh8aGD16tXjy7by0AqnrLgJYAe3ICK+4mv1iRt4grae/ +VmVrGQo2zRokW/ZuO9bN622mJBXMeZSsngb+R2rsXcQ/aLBiSrwL1n1nxMo0NOr+ +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +CU51y3DKNJot7qy7c7KKwO8WYvpMDLkO/fSRGZVdWOribLR0Z36bszb9TfMNFtaC +FsRTDT5nlYHpT6JC9UdH4XY/Mijkqlb0CXZ+gLX4s8Lo7IDK7Ba57kevbJOEF5g5iN9/CDdZUu2p5umGxZgi8mGXi2A6aBVsitBddxv3fWSEKNYQ+BpNe6ioS+VoiF7ZIdU5qPOWwJ3oLZqIke8l1aP0euqLUVfelvnlr4cIIFIj5ihOF/r3Za1FMkLpsZeZTi+bY+yHHE5B+Oyn2aPT4A== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +CU51y3DKNJot7qy7c7KKwEfIM1RpEMC8MSDuKuFpo9c0WuShpb1Gljya7chVcXrt +FsRTDT5nlYHpT6JC9UdH4XY/Mijkqlb0CXZ+gLX4s8J+IQGDwbrhMIxooL0HHyeMwJROaopw2r49Np4gDG/ZPg== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +OpwSuFTBAL7XPZ3GOEDUSXmEvKt60PMcxH4xv5faETw= +Oq4JxO2byG1NpOr3BWRu3HsPBkHg7cg6rT4zB1qEB0iT4ntPsedRnYHV5l2bFN/rGoeO8y0fYbDnXhc++lmpPqD/7rT/EUbMaZkgXSxKZhc= +1u+XjG/2+GSQRv6EzCaWRQ== +OpwSuFTBAL7XPZ3GOEDUSYYkSmNb531LWXBThqJnA2Q= +Oq4JxO2byG1NpOr3BWRu3HsPBkHg7cg6rT4zB1qEB0gxNz22V2Dc9j8EyqMVksnN +1u+XjG/2+GSQRv6EzCaWRQ== +8i88MKADknh6ve+bU8Ezb/5J/KOGrNrp/hd+fOHffgs= +Oq4JxO2byG1NpOr3BWRu3Oj+k38Jm2FrVzxggusCskZ4mzSuMzk+00wLYsSzYvJQZjz2fhF1T8o51Q/mKcz39g== +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdV6+T1+oCOldFzv131aPdi4= +Oq4JxO2byG1NpOr3BWRu3MthcyzqNuNs+1GYOnU5w+j+CaEwIuq9rJHkO8fHjOtidD5V4PbuwUZmtFwGxxAulOrqkCwdTQ9F4tN/9xCEPFk= +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPh8pA+0jaLcKE0K2LySjNBG5CU9NDINKpk6NXnWZNXYHv +84Vymj90Wzn5yYuvz1pUuOO0+fywpFJdc39xXTBu9YirwjBLohUzBWtPEoQZ/0wTdvMyNkBBMME3Rb8uomVU2c1LaHGQYxq0DYsuUSVKELM= +VmVrGQo2zRokW/ZuO9bN69JCeXP8VKIhEnUaQVSzY2BypuWREM5sjaruxBFKD2EvkbTZZ0Pw6RDYZMEKcXxRO3BkbFDyeCCX4BTkwrqvx9cQXOTLphOa12SDXvFCXlDo +1u+XjG/2+GSQRv6EzCaWRQ== +Hl/tmaE5nbbDLFZ4/b5RqLZNnSDcxCnQkebcIFUyCThHdTJZ1+0H2i0w2oG8wc2kUhrQlZ5vz/e+PVt8T4Avsg== +Oq4JxO2byG1NpOr3BWRu3MthcyzqNuNs+1GYOnU5w+iR55CovI+mLkNUdXQ9Cjms +1u+XjG/2+GSQRv6EzCaWRQ== +4yfrVt9mcL2kjZOi6OFl+/gCpFE8/3I3bb9RGL9+SD5GqtpUVKxQmo2nLkA+/LSc +xWoGNWjKGPfI4gq8aHoTfPWfjS6vSpTufw6V8YWjiHIbW78881nysbHK/4+YnV4b +1u+XjG/2+GSQRv6EzCaWRQ== +nXHLJs+jc9UGwRbqbBpdlnoY9wZYkQQX09YG8sUW0k9w37YahVa+gJNOrD7ggdpa +6K6zXmJJNC0xAXfJ6TCmfcRxgBy4y4bgsEJ6wp9YKM0ivN098EI8rMPmitQhDg0Y +1V2v8QerKOmubvSxgB4eTNOWXfUINjsWZ02BDEUwQGr1EDSYeDhTrvRMzg0k85YK+Tdos98WcA+iIgGaBKpiKT9+UZijWczfGNGyP/pcPfc= +VmVrGQo2zRokW/ZuO9bN6xOcE74GXukFgoP7mn89osg8SsOUQXFdzvPn+qkat/4/ +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +6K6zXmJJNC0xAXfJ6TCmfaVK7fRvRq1+Vnadm3/saDE= +J1UGlXkKhKXD36OrXXhN8NQKeNxEQEtSrCTi40QJlhZc8+TaSALhLYCLySZ2J9Ro +VmVrGQo2zRokW/ZuO9bN646OXWelyIYU2xRPUxRlGkJcIaIJLy1ed19RDL6uKh8/ +VmVrGQo2zRokW/ZuO9bN63w47pW0ZChbYuF1zj78dlkjTE1Ux1w+sowcZh1RTEJIJotfy7tFcWOcc+h/FsMt6JMpiz5HQflNCEI8Eq16698= +1u+XjG/2+GSQRv6EzCaWRQ== +YY5wEo94l9qfo5TPxQmUgoNFl2Fp8+FsNDKaanjjEPI= +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +6K6zXmJJNC0xAXfJ6TCmfaBFl7DuG7+1nNvbLJkeawSnTK85ofp4jusSyh9wK19J +CUHO82G5MDpSEiqhNImyK3tJdLE8+4hhF+l+X4RDWwZAafVU8dGDkz+ecUDZLlFH +1u+XjG/2+GSQRv6EzCaWRQ== +HTdnfikDSTQ3eoPgrOd1XSIVSJ0FqgT0ujqJT4TT0N0= +p/2Atf50M8NSYtiojzMPSPDDLBr62CZS2VKFEpxbOYlh2aRzggeKZtOgJa4QNRRjDmlZSoUEDWHTnLBsPR5L1g== +khWCUyuDMwE3p+YqYO+JihDH6VWAOeZ4oABpau/+19tmJy+J3rvX4fpN/8te7LPxB1H3pwVBig7ITL7uPGmgS1trp4e84U+WKBrLkYlgk+9ksLUpnsMXlluLn47Pq+RSKPiYH3r5Z+ysz5QznyNytQ== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgULgM9yjXNLL1pbqni6GCYV7COvUA6EuHsC6DPEvWiBT +1u+XjG/2+GSQRv6EzCaWRQ== +ImN2fuKsoGGvkOmi8kzyJurqrN+AxWpO5c/NRxwjHm3eBS+7qYgiJIt4WV5eHPYs96HzZh1O4ZAmtTRU1UWauA== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +LwzzX0OXdM0O35JPf5nR+zpuH+Oz32pLKy+0Xt6eMbm0Y5ZLRkVsXFZlYPOoDLADcVXsKSLYUG0oej+lTH/6n2bE+KW3uYrALghxrY4nD6JiEM+VpsPSaFVP665QVb9u +wgR07xfoapmx6eEnFHXXYm7IOJ/6J1NThWNK2EdPRzYwWPvLP+jlqDp61mwE1mzG +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0VgYJcDXtyKb/JKLOY2KZO4UB+NPR50aKckf8eAL2HnqEcojt9ZBnrI4j1z2fJqPuQ== +1u+XjG/2+GSQRv6EzCaWRQ== +cALTOh7OQEr4o5fCc9/A03qUKf+Tn4nSnE7MBDCNym8sW+P3qPtD52WwMx6XflDoef7qvmEIEoplahR/D7P1jg== +Il5YcNzsAW1ppTTOdwCOzSr5dk99Ps4e1hQaV/md3owjglrqco9N56a1CqAlb1gpQZpEginNw3bJlFt+dUpTdg== +jFWp7iGXcNFAo7+mU0KY9db0cG5f3MFhw9ssDGknJ+lQMNh1E1cVt/CHqWM/XVAFpscGO9Y+3QE7bod6bSZD2Q== +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9ie0DHX18L8LGfHVKI63Ke/pNWx8fOQwIScGdpToAWtI +wgR07xfoapmx6eEnFHXXYlEV6h2Qh0/E9zwcuGp+QctxMca/YFYUc4GaSAHUOjC/ +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPSYxJ9SeZK+6AUohBzjAqKVFZzCleRVbF2iRpitP5hQgQDYykjqMPTrGdNOh5USUgVOc60cmy5X8OkSRPq2LH7uKtidfwECLQ8kdPEcFfoIUj9s6fZ546ljWRMYy7X1Cc= +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYp8QPW/Rny7GxLHpvcjeLGxgJSlfxADrCxF/0Fw8Dr1L +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPSYxJ9SeZK+6AUohBzjAqKVFZzCleRVbF2iRpitP5hQgQDYykjqMPTrGdNOh5USUihJ7M+2HTHaNfcLtMv1MUxT4d54PujWuc7tNecdnY3A+t68VMhYvkK7T9sEEKUv60= +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYhx8u27be6FTqpRMEKSdDxFFRQGrLDDOpz/lgdLZ14Au +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPSYxJ9SeZK+6AUohBzjAqKVFZzCleRVbF2iRpitP5hQgQDYykjqMPTrGdNOh5USUgVOc60cmy5X8OkSRPq2LH7g5LjOgHDdJQF3u3PU0J3OO4wjU8AHSNNeufnzOhIS/M= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgRy3xtF/BIZqC2qdzL6cmg72RAKeTh1iY9zoq4fK2PdLVCZC4a1j1X0DgFlBQiM9xg== +1u+XjG/2+GSQRv6EzCaWRQ== +6XgPd5VIjYSXPl1j1b9rwkngQLI6fjrUdgeS2Mo5IRMUECnehgLEabBcaYc6RpEJiVJ/Alx91bDLqTwMAq/6drU1zGNaE9D9DHePb11qcsw= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +Ws6i1ZM/c9zrSAbxeLx1LfJruhTdKESdPJU7A5Fw9/6rLNuF8MiZFkrRY4q1bxW00FETxRds2YLATonbAPmJ10qFQPtJm7UyXPmuZtqu5D9H7EXWmVryyQnef8CYOZ5Fdv0Ygg+pX9uuCBY5taEYaA== +91gsC2HsVrvTm3cwuG3h9ku+SnEuh1gfPlaT0BYQ3ws= +s8oZlk3kc0FacCBDAb+rTqHvR/rpCiMtf9O5/oRpCMDKQKA1V+fg392NLu/6HnRe +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYrQR/7nBKlFU7UIR7Tp7Ask= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/XRRyd9HDiBpo0pCt8PUySBg== +1u+XjG/2+GSQRv6EzCaWRQ== +P40wuHk7MslPmvVoiV0eZS9GHjGsA1BpCAEYF+DyKrg= +IZj4TYq2HlScfSdRkI+QP7ds2Lg0nLguFAxiA9uKmwWNy6Iyrh6ke395/+UQsoR+T3bjNrP1/7gKiifvP7wGuw== +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +Wk+6iLfu38DoetOmRN2a98ccri5id3E91jM5eSptqngq5wL3OhenY4CPUO5T8L0u6OwkphGnac6ebW9W/b+NfQ== +CUHO82G5MDpSEiqhNImyKyJX2KNfQKTBGoaamjo5u91mIsUT/9ESdK1Yvlj/kDEPdW5TWq4h64U4gwbyure4PQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPh69q5/xKR7d/fTlhH/eiNy2HOp9v+xDToENMafrI2uH+ +FsRTDT5nlYHpT6JC9UdH4XrDOnfzNeguIPWOtTAjsddxGDd9fUcR8/xS//eaWJr4NParBtsOHs0TnJGQL6RQHX0pdc/CP7V/Z+HvKowX1qI= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpKm/Hz2+WKHac+36/qoVAtg== +1u+XjG/2+GSQRv6EzCaWRQ== +vQwaisdUFOt9b0SJYuH/nrHigrTUtycF35lf58xGUQo= +VmVrGQo2zRokW/ZuO9bN63M3N/QI6jfN21aCOjn5qoIH65oh3lPdyA0LInTMBTnC +VmVrGQo2zRokW/ZuO9bN6y1nBG3iYKMCH+hzUkF1M8ZnhEqE9tZS72zfT2evK1dQ2LMvOK1lgrtQuS5Vtw6hT5MeP0utiFLoDnrrf+qjvwU= +VmVrGQo2zRokW/ZuO9bN676cRzFmy9/zo55zwgaMh+JG53w7pjIKf/vbD16cejyz49FB2vYDXVhkdLeF4Rdnrw== +M0ZySqkmhuHCw6olbCKv9zQJzSQJo4Zslq06bXS41SQ= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25cy4PX077/sCXB8dqPHOKWHwGfhj+HPrIcuJ8jQUUnUNsgOaiEdOQeCfdlLOd3khzQkvQkPfY59MGCnqpJ+W4jmx1K9o6ogXNBZ7YBZsaluozw== +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcdQu4K6cOeOFIx/oZ5wHn8A== +VmVrGQo2zRokW/ZuO9bN63QpQYSLmPtP/Hy48PObUTAIbyGMAEXMcC/u59GCOJz2SDcCydB78ik1XpbXw9Vz0Q== +krWGxagzYm1msYy2LL9fQw== +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +6K6zXmJJNC0xAXfJ6TCmfaBFl7DuG7+1nNvbLJkeawSnTK85ofp4jusSyh9wK19J +CUHO82G5MDpSEiqhNImyK3tJdLE8+4hhF+l+X4RDWwZAafVU8dGDkz+ecUDZLlFH +1u+XjG/2+GSQRv6EzCaWRQ== +hxOlvQqGzvBGNobqltO1S2DP9hqsRonfsYFJZcqe/d42lsMgi7PCiKBUwTriYSCM+Ol5iVFA0QvMhVJ85kmwsg== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +ZKXiNVFMygAA5wm63MFYLzANoH3WugY0nYaKERSEhzNXjmqU0LhVN+s6xZNku1ol2uRURizImcI6Q9qNqfY4Mry4RpzUtuYD2GTDJo86DjSmwlPpqhYKKGZL/m7DI37Lng5fq7lCIcNZovKBj6FBCQ== +2yQsUep5WrV3T3htNZ1L8TIohok8dxSGY22+WeWcmQzlauL17DyWRvW+qqAHUI9z +1u+XjG/2+GSQRv6EzCaWRQ== +ewMPic4oY8KA6fNbX11t2ac6pvQBdZ/brZ6zxY+WDEY= +kE0OGUDieDpLXO/pX4APBArHp97I07iQR92UhHwLo6k= +xWoGNWjKGPfI4gq8aHoTfOHCHk0sElv9s8frQ1Es3TZlMYL9ooet3sY2uLB3HVTi3e57gINhClfifZldkEGkgg== +xWoGNWjKGPfI4gq8aHoTfKj7V6/GbVtiII/Elkzy/SIxsAN6Em7FysJa4jADHlGMg/lVpPwNP8IEbwy04HBldA== +xWoGNWjKGPfI4gq8aHoTfPfBJKXQ9w97Eln03DT906wnKaz6rRkQSCBVFLl+HC23ufNrzhC5bijcSs87ACWLNg== +xWoGNWjKGPfI4gq8aHoTfI65fOfKuI1D/27XO5rrJkxYDYN5AaD/U8ieE2lndMCRssuSq+OPqH5+o3oPnZhR2fDSmXENHaMrM/plpW2LB2tYrPOpDeIQ/1hajVeQlo8yggq+xVJXt+IYapVi/hVh3yV/lFwctoQT7KaBsjEaTru6xmXSpD+4XvjxxDywCjgs +xWoGNWjKGPfI4gq8aHoTfBJP/QcKNqdYzuq5movMnogxB5R2dU5gt0Zk7St83JWg8FD4u9UEVIw/00jn1jMKcA== +xWoGNWjKGPfI4gq8aHoTfIn6PnMrB/8qeqFwmI91ZmH0gsNqllNJ0dN1JhivPS8/w0GsFtLkbsrsy19h/7c+0g== +xWoGNWjKGPfI4gq8aHoTfOoPpV8RnCEp0qDDG75IWv/DIdiq3sMANSD/7jkYq96F/ZclnJTAxRILux6aALMcJg== +jIYtewpcL6elNlf4btqXRw== +1u+XjG/2+GSQRv6EzCaWRQ== +3sfJhDjqeEN3VkPRXnOTXHQyM5tO/8e8FVlTeqKqv46WsUzAvHp6vNSziJaI721C +A69EZujp4WFkVkJizqN6C/fqBP4sRbQVabCFtjuIU2ZOJ2TWCbEt4t9BMwacHaGI +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0QA9S0cmE5ZptD/tu+M6uzCl/jog6QV0tj6yWgEiZQ4q6uuSDo3ayDOPPYnkUgMtX16FWlrLW+uZ5n5D5c/0/Tc= +1u+XjG/2+GSQRv6EzCaWRQ== +OIAm83eO9UgPVVx31CBN5xrFh1fu4lJbxv0hxvFbJ9/Lzwlg3G1oy4PAivfxK5DN +1u+XjG/2+GSQRv6EzCaWRQ== +ptbr9LWGGT7tn7j3hgPjfbrssREvkhB1Av6GQHuY5C8= +91gsC2HsVrvTm3cwuG3h9ku+SnEuh1gfPlaT0BYQ3ws= +s8oZlk3kc0FacCBDAb+rTqHvR/rpCiMtf9O5/oRpCMDKQKA1V+fg392NLu/6HnRe +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYrQR/7nBKlFU7UIR7Tp7Ask= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/XRRyd9HDiBpo0pCt8PUySBg== +1u+XjG/2+GSQRv6EzCaWRQ== +P40wuHk7MslPmvVoiV0eZS9GHjGsA1BpCAEYF+DyKrg= +IZj4TYq2HlScfSdRkI+QP7ds2Lg0nLguFAxiA9uKmwWNy6Iyrh6ke395/+UQsoR+T3bjNrP1/7gKiifvP7wGuw== +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +YnT9M79JgM+dY7nG7ivAFRDN5w3P6LhHZG0naG4H2pMqSOnLuEF3TrnObbybq/T3 +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +Wk+6iLfu38DoetOmRN2a98ccri5id3E91jM5eSptqngq5wL3OhenY4CPUO5T8L0u6OwkphGnac6ebW9W/b+NfQ== +CUHO82G5MDpSEiqhNImyKyJX2KNfQKTBGoaamjo5u91mIsUT/9ESdK1Yvlj/kDEPdW5TWq4h64U4gwbyure4PQ== +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPh8hYVyhGzrLc1d5gspB1nJ6MkdD973lU5E6ZY+/i87tn +FsRTDT5nlYHpT6JC9UdH4XrDOnfzNeguIPWOtTAjsddxGDd9fUcR8/xS//eaWJr4NParBtsOHs0TnJGQL6RQHX0pdc/CP7V/Z+HvKowX1qI= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpKm/Hz2+WKHac+36/qoVAtg== +1u+XjG/2+GSQRv6EzCaWRQ== +1ZFe+UUdQmu/QujLaciJajx26a+2STfwMi+zieErgi3CBgaNFGZigZi8pTmSdXTQmGqTiUVKLPzrQkD8KSrdFg== +FLMbt5iAjw12yzW85KcCsnQmJLJhCftAGa03uwPfXWOyGjMGMSeZIIZ4JQD4SoKE8cwGXJJKnfKd3VO5NrxPjA== +YROiDf0HfA7clzlpwWrPnZTelRd7BRvkhZN4nFqogkcleIQbXGcCHAtpjDXpXs8esQomgYP0+wCvwRWlLSwEmw== +1u+XjG/2+GSQRv6EzCaWRQ== +Wk+6iLfu38DoetOmRN2a93iqiWLghhd+n8qz7n4B9D0= +1hs1FfIPISXmd7TJ/r+Vl48ped6CO4DUHuCgWusI3kaQo9WDqYldGt+iTzvFGu+sJOuc4kzIb/fNjSI7+VIovA== +VmVrGQo2zRokW/ZuO9bN6yrtj1Y8IOc0iyqbQeKQWnIEwvxCta/kYWjRV+ISgn1lBunAHsxOxxCiik7fS/QPJA== +1u+XjG/2+GSQRv6EzCaWRQ== +8i88MKADknh6ve+bU8Ezb+WJ6niZtJuVbQw4qpRaLig/H0N9vJfeujRYJbh3YTtH +84Vymj90Wzn5yYuvz1pUuCWRuZGkhlJnLSClaFLOOGb6poAqIb2TXQcYiRPjpWOS +VmVrGQo2zRokW/ZuO9bN675vxEk+ZDMj1IxUahsCYOb8sGgoVXMqknYjdiwdUQrN1uOQv6+nqJfxxhc+fmvEydbSMV0wyDhXbD2QcmxYuFbeQv6ltaFTzgkV/Gjsana4Z6uPBK3w93KR+W5cIfoStg== +1u+XjG/2+GSQRv6EzCaWRQ== +84Vymj90Wzn5yYuvz1pUuDeimrBZjjT24kCGUmMMVrxvZ2a3W/syVkEg92NrBZWw +VmVrGQo2zRokW/ZuO9bN6y8aQcwZ/hSna7uqNWY2EKiaIYyFt7YecCLXxJctzYQmq2zUc1xRjRe2sZt4Yq8Ovw== +1u+XjG/2+GSQRv6EzCaWRQ== +FLMbt5iAjw12yzW85KcCsmy0dtqL77758KtFtx4Wv3amJjJGHf130+oBwGsdvcbN+qQC09Ap2i1s7iy0TrXHib90sT1B/mL+D7Tlnm4/Kho= +1u+XjG/2+GSQRv6EzCaWRQ== +7bJ3Uk7aqJsghutxBeStwitQyrNq3vvsQtPQ7nQ8Roo= +I5csN8e3J/KIFkMa5t+SJKcvYE4NNVj3kiL8YMvvrz8H6inVAPWunV1GucokSU+W +UCjjP9uamQhFjq6n90cX334w5N6kM7POhL+ZChb6xIMkwXdXYjEQ5NlTf7yiiE9gvWTb6WOc+v/DaO8XKvCo0DfwvXhwZmBLMdEWuY0QChlXwvqsJ3uRRSP2BgNVeIh3y8nv99zO6pX6H+vKgdKxbNRuD8SQHzWYnwtq/tDTamc= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw1yG/npscG9yRca9RdwsykUzZ2fB1ISUhruNnNyh6AU0h80pp4mCZIr3r6vzPWn4QA== +1u+XjG/2+GSQRv6EzCaWRQ== +zBfpEDEHUpKcgzvJDq0C4YC6UMbAUfqAJ0xI0cKnOaESr1M8ldgYE+7A7EPPEZ5g +84Vymj90Wzn5yYuvz1pUuBf9UiMoSzUCfQBk2T/lZO1ZenPtkuAwJZ/DjYGk7EF3U1UkgN8sUTg2e8HZLYpjgw== +VmVrGQo2zRokW/ZuO9bN61K4MXaMsg5/Kxe0Z+g/3qNgb6okRPQ5AYXwGskH9ejOnwi4YAc+D7PnIV1ewTrjYg== +VmVrGQo2zRokW/ZuO9bN6wZri2Z3CBt2YpUf7zFZ0+KNguNuVrtBMhHtFgJ7WYYf3JHfXVX1T0GtSc/XTfHPmg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wXyEF6XhF/PmYb/RCvDnIg= +VmVrGQo2zRokW/ZuO9bN66V9gsLif7RpodOVxdJVsvNCObquF8KcqaCn9joiTHShp4vhVSeHci2o+TX7lXV5bg== +VmVrGQo2zRokW/ZuO9bN684ehqpwxtlh5PoulYJQlzwAsh2I93oB3djuYOuh9qQN7UA7ptBZ9kbWq4QL87fNrg== +VmVrGQo2zRokW/ZuO9bN632U9Ut74PgKeN/Vd++qe96QUfvqhKcAMSKVyTsZ5tjiis5cEaQeER/nY9riZ04zHw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64IOlCXEAuw7+JQuoGU1tA7y+jY6s/JvEWCe2pdd+pjEK7nJUBmqZJQTwm3h72ag5FdSSEPIKbcMVZEYOsof8a+Ny0UHjA4vHFUkNX8Y9NDS +VmVrGQo2zRokW/ZuO9bN6+0ruBcpcWh1tg//57328LXWh98WBeFYX/reLiggO4A8 +VmVrGQo2zRokW/ZuO9bN65YLSeu6SlsROZcKj6GOXcg= +1u+XjG/2+GSQRv6EzCaWRQ== +P1bBdERejTBi3ynjwlj7j9BN16EKwuXwZL0J6G3zRwc= +I5csN8e3J/KIFkMa5t+SJP5ieXqyhKdpsUu8T3AoWE7POsp2/AbDJJ+EacQGfSh3 +FsRTDT5nlYHpT6JC9UdH4bKLDUGeT2OmWeP4uXVXctcW9UIqr4TBGbKKt1WvG/efazn8+MYwktdhj56RVE/jzB6VLeItbFDX6J9e2phdbn4= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +4Ky5RNfad94RDxFAfm3/hD1S6TvHVGkU5RCZUxk7mPUS55Jd4Ae2dEHUhzTAxZvn +FsRTDT5nlYHpT6JC9UdH4XY/Mijkqlb0CXZ+gLX4s8Lo/U2S2nexcGC+4OIyPFh5WgAPq9Gi1jmpFN9HHqK8ckgWpJQHRdLlpUv0ZQ66iK+mMCKlcQxGqoBtWZH9V+uyqy/HzwNXYzZkmwHooOMLk3U5rG/7RvTMa+ny/hOD4o4= +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +KZsPH0cmEM2QRSeJCcSyucww7vjWuHBitH1P49Sqh2Kwbdrie0k2+bXVIdFrP4Hp +iEbw/85IA9dHrKUZiqzvJYFw7QmcLtkqxGPIfRytlW97cg8SvyFzcKnhDF/yOCIqBpgg2zvuF6H5ZBpWx9EJEA== +1u+XjG/2+GSQRv6EzCaWRQ== +Oq4JxO2byG1NpOr3BWRu3IQ7sxcnLbXUljntZmFfE3nh6wlJfMDKKO9ubZppLWjY +Oq4JxO2byG1NpOr3BWRu3Oj+k38Jm2FrVzxggusCskbYvEUoOq/NQoGJhFujv6sPpGYXVLDzIbfY26IswYzgjA== +1u+XjG/2+GSQRv6EzCaWRQ== +/F49ykgjedWbzpXYCpMOgrI9YLZA9ExR/eC6+PgN+EBaWlpc2Mqf7k4KIlVymqOX +Oq4JxO2byG1NpOr3BWRu3DAGQWIxEWJFwo5ywit0Hm8vjztDN9DFBJzSbRYvk9E8aVOOFQPA0eKwd04IoEKP0Q== +1u+XjG/2+GSQRv6EzCaWRQ== +mG4WxLOsQ3e8HrREEjwO673DdHmO16hnGJ4F+Ej5WMn87jXFSwAlFwhj2veKWfNoDM+uXyfiitL62YeJKZOR3Q== +I5csN8e3J/KIFkMa5t+SJENrCajRah1QU3uYF/z5CIDRw0MaiyCM2fRi6PpL0QdK +wlLHv6kT3Q/RmtMBN4nDAbv8zySb0rt8J6SFevmRPRgKzDMeoocwhCXrYYhV8NxX6MkHxyBVKtZUBa2yXK5ttQ== +VmVrGQo2zRokW/ZuO9bN6yuXF5mdisxBEft+lGUhyG8veZzPJ1y7VdReq0jTr8QY +1u+XjG/2+GSQRv6EzCaWRQ== +tm8SW4k/FXA1T5YrQDZ7wWnoVqAkjsZ5VE6E38yZjMw= +Oq4JxO2byG1NpOr3BWRu3MthcyzqNuNs+1GYOnU5w+jsgU7gzHR2JfE2rBUCpEkb +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdatB7FelKh/g6Qxw8LyyT8HGxMvOPbla6t91vkFW97vk +Oq4JxO2byG1NpOr3BWRu3EQ6fq4nu+wy0ERSqtq/yw8M0VQBkl4VxvsO8pLJ0tXjHk57blGJcDTSf5IZnT6A33vklf/1JB4f15dsurs4TIQGV33FHDGQiycTjLI89DIU +VmVrGQo2zRokW/ZuO9bN68PLvFAyPYCFptnUBkrwhM+fcDkoqf6krf9e3/VUGhLb +VmVrGQo2zRokW/ZuO9bN6wj+eM5w23B0fsVf/h/HrFSTzO98rc5IeoE6011BQZVK +VmVrGQo2zRokW/ZuO9bN69QoqAP8+J9sRJqfCnnzeuAXZF+1S2UZnPaDpalUlo3b +VmVrGQo2zRokW/ZuO9bN68ULkZoiR2t+tvBrm+xgZHsLbfA78IfbZM7Dajj7ThPX +VmVrGQo2zRokW/ZuO9bN6yLf0ryvhEUQNBfas0QHrzpoCyWrDfpVrSO9zlFCkvcL +VmVrGQo2zRokW/ZuO9bN649yP7OWYGC7nlAREd+ogQPwefQidhBHbKOs43VE+Bj4wleoG4CsMdHTj27yDJrlMA== +VmVrGQo2zRokW/ZuO9bN6yXvSimWjBkSPa0oACTE6ZLwBjvix6RVUl1pckZX2MOfzsYgQ/oO/5Wg0pi5sa44/Q== +VmVrGQo2zRokW/ZuO9bN68Th/U76hkAmXqrzmdE/ZI2hWSlM1jO4tNZ9jOuBXyUu4UXCtxGmpfpurmSBZ7nMz3CfJpY98NDnzDpftzNKbQ0= +VmVrGQo2zRokW/ZuO9bN60dcEpCcNfgwHuX11nm6/kpaOKlAgQkhD4jWIN9kUu++6vBRzZCJihAhZezy7MiEDg== +VmVrGQo2zRokW/ZuO9bN68woQ38PWuIkn2KGEnMhf18vsoH73KIsVQPZZeg0H6i/uUTwlcqjqaOOcGE2yARxSA== +VmVrGQo2zRokW/ZuO9bN6xc3KvQQ3gxQg4pqItQtHXv4FkR9WdU3ksM799ggpOqo2DDqalu/U1q8We9X/tjBew== +VmVrGQo2zRokW/ZuO9bN6zKmc9qq/eDXqFNWaoFb0zhPVy2ABZhvEveRj7QcX4YCewTr6ys+LhFoqIchJ6zMaMOLXJXIH3Brcdeb5Ue6260= +VmVrGQo2zRokW/ZuO9bN64l1qBkNbm6bRJujDvKFqz8cikEfZdrnEcpounC4jt3+LsHKisT4qqKZ9+YZq//PUHsLDe4jo0qqLnSPkq2P1BU= +VmVrGQo2zRokW/ZuO9bN6+PaCbxoEB1NEX+VMo3rreipvAaQyLbViNAr3J6XiR8BJigxp9Grj+VE6re2Su5SXw== +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdQI2Yuhuvo0Ui21i4BCi/nOhdFEM8veprF56BF8YOMRn +xWoGNWjKGPfI4gq8aHoTfNm7h2/3rk5d26JyRNKl45IM1E5n5Mf0G579JPEy0AiTBQKCeUZgjg8y5TnHK2UMyh8aGD16tXjy7by0AqnrLgJYAe3ICK+4mv1iRt4grae/ +VmVrGQo2zRokW/ZuO9bN622mJBXMeZSsngb+R2rsXcQ/aLBiSrwL1n1nxMo0NOr+ +krWGxagzYm1msYy2LL9fQw== +1u+XjG/2+GSQRv6EzCaWRQ== +CU51y3DKNJot7qy7c7KKwO8WYvpMDLkO/fSRGZVdWOribLR0Z36bszb9TfMNFtaC +FsRTDT5nlYHpT6JC9UdH4XY/Mijkqlb0CXZ+gLX4s8Lo7IDK7Ba57kevbJOEF5g5iN9/CDdZUu2p5umGxZgi8mGXi2A6aBVsitBddxv3fWSEKNYQ+BpNe6ioS+VoiF7Zzw3HAtWZ78yWlxOYh+qZ7g== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw0YOhGBfjG2Hra1hWU5/NCZ/kMm8XEy455Qk9gUpHzzUb19ZnbEg357UEEe8f0jDkw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl8sUPd5ZHgQChuaaRMaehpEMPvuOg4yWJa5Si36Ytvw8p43/TPHAazuzA5FuFRGKEfnXqhECXWFVnsFjkiUzh6SuNCWVHYS2nQXqvJpPbJeg== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +CU51y3DKNJot7qy7c7KKwEfIM1RpEMC8MSDuKuFpo9c0WuShpb1Gljya7chVcXrt +FsRTDT5nlYHpT6JC9UdH4XY/Mijkqlb0CXZ+gLX4s8J+IQGDwbrhMIxooL0HHyeMwJROaopw2r49Np4gDG/ZPg== +1u+XjG/2+GSQRv6EzCaWRQ== +1V2v8QerKOmubvSxgB4eTDkpvxy+zQh0OKrDfGYCy1o= +VmVrGQo2zRokW/ZuO9bN61kbD8u+dbPaT3TvyJy25czKezzdOCDSA9H1RwvJ5lkG4q/cqRp8Wx3Ph/wk2iqr5A== +1u+XjG/2+GSQRv6EzCaWRQ== +OpwSuFTBAL7XPZ3GOEDUSXmEvKt60PMcxH4xv5faETw= +Oq4JxO2byG1NpOr3BWRu3HsPBkHg7cg6rT4zB1qEB0iT4ntPsedRnYHV5l2bFN/rGoeO8y0fYbDnXhc++lmpPqD/7rT/EUbMaZkgXSxKZhc= +1u+XjG/2+GSQRv6EzCaWRQ== +OpwSuFTBAL7XPZ3GOEDUSYYkSmNb531LWXBThqJnA2Q= +Oq4JxO2byG1NpOr3BWRu3HsPBkHg7cg6rT4zB1qEB0gxNz22V2Dc9j8EyqMVksnN +1u+XjG/2+GSQRv6EzCaWRQ== +8i88MKADknh6ve+bU8Ezb/5J/KOGrNrp/hd+fOHffgs= +Oq4JxO2byG1NpOr3BWRu3Oj+k38Jm2FrVzxggusCskZ4mzSuMzk+00wLYsSzYvJQZjz2fhF1T8o51Q/mKcz39g== +1u+XjG/2+GSQRv6EzCaWRQ== +kniDpSFE0vLELL83X2MGdV6+T1+oCOldFzv131aPdi4= +Oq4JxO2byG1NpOr3BWRu3MthcyzqNuNs+1GYOnU5w+j+CaEwIuq9rJHkO8fHjOtidD5V4PbuwUZmtFwGxxAulOrqkCwdTQ9F4tN/9xCEPFk= +1u+XjG/2+GSQRv6EzCaWRQ== +0Cj55jDWeNf+rIE9r3fPh8pA+0jaLcKE0K2LySjNBG5CU9NDINKpk6NXnWZNXYHv +84Vymj90Wzn5yYuvz1pUuOO0+fywpFJdc39xXTBu9YirwjBLohUzBWtPEoQZ/0wTdvMyNkBBMME3Rb8uomVU2c1LaHGQYxq0DYsuUSVKELM= +VmVrGQo2zRokW/ZuO9bN69JCeXP8VKIhEnUaQVSzY2BypuWREM5sjaruxBFKD2EvkbTZZ0Pw6RDYZMEKcXxRO3BkbFDyeCCX4BTkwrqvx9cQXOTLphOa12SDXvFCXlDo +1u+XjG/2+GSQRv6EzCaWRQ== +Hl/tmaE5nbbDLFZ4/b5RqLZNnSDcxCnQkebcIFUyCThHdTJZ1+0H2i0w2oG8wc2kUhrQlZ5vz/e+PVt8T4Avsg== +Oq4JxO2byG1NpOr3BWRu3MthcyzqNuNs+1GYOnU5w+iR55CovI+mLkNUdXQ9Cjms +1u+XjG/2+GSQRv6EzCaWRQ== +4yfrVt9mcL2kjZOi6OFl+/gCpFE8/3I3bb9RGL9+SD5GqtpUVKxQmo2nLkA+/LSc +xWoGNWjKGPfI4gq8aHoTfPWfjS6vSpTufw6V8YWjiHIbW78881nysbHK/4+YnV4b +1u+XjG/2+GSQRv6EzCaWRQ== +nXHLJs+jc9UGwRbqbBpdlnoY9wZYkQQX09YG8sUW0k9w37YahVa+gJNOrD7ggdpa +6K6zXmJJNC0xAXfJ6TCmfcRxgBy4y4bgsEJ6wp9YKM0ivN098EI8rMPmitQhDg0Y +1V2v8QerKOmubvSxgB4eTNOWXfUINjsWZ02BDEUwQGr1EDSYeDhTrvRMzg0k85YK+Tdos98WcA+iIgGaBKpiKT9+UZijWczfGNGyP/pcPfc= +VmVrGQo2zRokW/ZuO9bN6xOcE74GXukFgoP7mn89osg8SsOUQXFdzvPn+qkat/4/ +mfT8DrNbUmxQ2BnZ1bZFTLh9MEuZKOpmAfF70OnZ9TU= +6K6zXmJJNC0xAXfJ6TCmfaVK7fRvRq1+Vnadm3/saDE= +J1UGlXkKhKXD36OrXXhN8NQKeNxEQEtSrCTi40QJlhZc8+TaSALhLYCLySZ2J9Ro +VmVrGQo2zRokW/ZuO9bN646OXWelyIYU2xRPUxRlGkJcIaIJLy1ed19RDL6uKh8/ +VmVrGQo2zRokW/ZuO9bN63w47pW0ZChbYuF1zj78dlkjTE1Ux1w+sowcZh1RTEJIJotfy7tFcWOcc+h/FsMt6JMpiz5HQflNCEI8Eq16698= +1u+XjG/2+GSQRv6EzCaWRQ== +YY5wEo94l9qfo5TPxQmUgoNFl2Fp8+FsNDKaanjjEPI= +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +6K6zXmJJNC0xAXfJ6TCmfaBFl7DuG7+1nNvbLJkeawSnTK85ofp4jusSyh9wK19J +CUHO82G5MDpSEiqhNImyK3tJdLE8+4hhF+l+X4RDWwZAafVU8dGDkz+ecUDZLlFH +1u+XjG/2+GSQRv6EzCaWRQ== +HTdnfikDSTQ3eoPgrOd1XSIVSJ0FqgT0ujqJT4TT0N0= +p/2Atf50M8NSYtiojzMPSPDDLBr62CZS2VKFEpxbOYlh2aRzggeKZtOgJa4QNRRjDmlZSoUEDWHTnLBsPR5L1g== +khWCUyuDMwE3p+YqYO+JihDH6VWAOeZ4oABpau/+19tmJy+J3rvX4fpN/8te7LPxB1H3pwVBig7ITL7uPGmgS1trp4e84U+WKBrLkYlgk+9ksLUpnsMXlluLn47Pq+RSKPiYH3r5Z+ysz5QznyNytQ== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgULgM9yjXNLL1pbqni6GCYV7COvUA6EuHsC6DPEvWiBT +1u+XjG/2+GSQRv6EzCaWRQ== +x4FKpKjDTgzvmsthM9ed85mLitLpZHpV+2azaMAwDvA= +fy7xOJXQBoDzcC4fp2Es8b4oK73/j4/qQT4PiJNAKpYgQpTKqYT+u/Wt6mMI8by8/GiIcIkDsL8FfJgpPklA9ORnDbZQ31dWakN+BYNoQ9c= +UWFnivDUNpc4sbUlvIH35sb1fP3ifqnvtwVEW7gDPMgzaTOToWZ+dtm0gJs6iOgW +D4h5kSTIuI12S4B28Nji1MQZ6Vc6GT9t5sEWSn9oPc+6yHMu1JHyMvb1duE1CGOy +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +1S1xjYS0s+Ph1PyvECx3QEpO6VoTDgpnpbfwQwThNxKM/15BQhqE/rSxGMGgDBro +1u+XjG/2+GSQRv6EzCaWRQ== +Hl/tmaE5nbbDLFZ4/b5RqJ6A6NK6PDhvbfLkcmuyEhj5oVkDGTTQyM++uB2zy5uw +JSomnfT9VTS5sDZJMF/Aaelww2MKwbZp/7eVVD8kr6OXesFepu35fbSIQV1yLVid +FsRTDT5nlYHpT6JC9UdH4aLgoBMH9iI/gDvK9Otd3oohooirPb2qWTIh8DVE0WcJGabF1RhdQ7m+M4FZrMwTig== +1u+XjG/2+GSQRv6EzCaWRQ== +O3CUgrw2GJfB+mDjH5+Ndrd4WB3m61bDboPhSQC1vd0= +VmVrGQo2zRokW/ZuO9bN66z9WxSrMZJFS7jTHpVswVz5UK0TwzzVbyNLsvrwIKMT +VmVrGQo2zRokW/ZuO9bN6/H50zYoQCvE/xsaNA2dJ0smLeVKwyq8MoZNwgB82urOHV+e+XqjX8rx2PoseXYk0Nx/Ao/FZzLApoRzXwE3RbodsGHAw0KVf6rai1cXQVq+JBHQTExmenCnQdeYk/0nNw== +1u+XjG/2+GSQRv6EzCaWRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOktxTjajw9xsLSKA56jqUoJZmUS8grJOzdDA9zc5o9VsbA== +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +6K6zXmJJNC0xAXfJ6TCmfV4ZV8gD9Up1YDhGvRtUFUU= +32pdC9DD05OE2l0oXazDFHqxhgDkaP1Qwpz/PDvbqSJEPj5vobYs0/kRpbeR3LCO +1u+XjG/2+GSQRv6EzCaWRQ== +JLRZ+53GxwzadsJQ+cNcEvILo3rrnPDBNygnfFALFu+Mh1D4ajcYKZqLxpxVE+W/ +gzS5oEArlO5TxOrOKotmI/9kCRGAvCQb4oKKj4l+uig= +6c3cWGGlsEf4q4E+EjPFyg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +pO1FJuvEndUwxGBqdeSIRA== +wyfCe6oL2aEYwEDDv8Scf1z8OmPYpCQARbBm8WGAJQE= +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+NKPudEm++RMQZs3h+yWJpXSp4aRpFh9wJGsBkiKsHmF +9WE49JNcaw71DcH3jZRpmVwnPH/UaQKqus8UOE6/l52OAN3uW1xlorJ8f2t6En8j +1u+XjG/2+GSQRv6EzCaWRQ== +EtuU2kTXk31Go8LkHgiwYE1zCM6asOCACOKJw6mfvhDS6qwg3RORxK+jxMszbXZ6CynXCUpaL1kYqgYUZ/tD8w== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +TZemUyEKHHK61yV1V/TL3c3aAr/plbfFTj5mrv0ZHu1mWLy3iXWg6zOKwPN9jPx42Kk2EL2cFJJubM2P5tt5Ch9LvVIGm+tXfMWqGMkJgdoiyrdvD+hTbjXfbXjMqer+ +ia8+eC8+w4kAKqFUfldoNFpsh1NWef58Ny6sIyvqtw6qH9CxYpcyf6YG9EGHgDajB/4NeuN4mjZrHxd1A1rlGLVe5pVBRyL5CNF1ue/s1Nw= +bT1+2FdOMy7aHyHoEhqX31H35PmRS18TqFVVYUSHorhJIaeU5AOidVN7OfUSEexj +cJ1Wblh8gF3p5U3Kb/RGlQ== +1u+XjG/2+GSQRv6EzCaWRQ== +HTdnfikDSTQ3eoPgrOd1XSIVSJ0FqgT0ujqJT4TT0N0= +khWCUyuDMwE3p+YqYO+JiqLTIUygvZ5uMSKfTR+fx1XKwwwUntKKHZyTjiz9ncDw3ezEL7rreBIGqeATfWQvY60Y8R0oiKJZboV4IbR8UB6XyjBiPH6/vUGqWSORfz+5 +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgba26bgHYZ1ZBeY1/SjzhAsPvlAejrJC4fhBL2mDvxzMJtEw8xuxPJkjj+xihfDqZw== +1u+XjG/2+GSQRv6EzCaWRQ== +5t2lVsKh3JYt9KTvLOEawsxLSsX41phqO0MU8uSWo6r4XDrDQwudtJWSeEzqg3NotAx507xB+RVyfqJg2igAxQ== ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +fspf8pU+r9SJrkYWWMygT6wKhlTIxgusmWmD2dnHgu7HDbL3Gkq2md95yvz+bnSuPmaPD5+aC+NXXr8/6KAvxAghl6aEaxfJwZ2dmiqJQZs= +HNr5/wGY+7coHgowTe94Lr/tpcWCBkrQdCN5TmR/MAme0Jr5QQWmAR1uVKIjAvAzn+s7yX5xJQZR5lwQ+YXaCBx4yeoACHcsCmEXecYQ2du8mE4EUU3yUKDsd9098flSOInGOo2qJpGVdwv/jFTOGQ== +1u+XjG/2+GSQRv6EzCaWRQ== +cbvASHjpysrsjdY5RctXmNuRWaVCTV3IfXxq5/B4M/l5hIxMJvyrVBtT7iVWvCds +L0eUthVnpkGsmKFAX6d+uLib58fjFLHLA3nq/44o/Cw= +1u+XjG/2+GSQRv6EzCaWRQ== +lGY+HGJ/DlLufZHWWlj7U9r38R8Fxa6wY+rYHsrxZNlSONyk6R3MNMnRoFhbQcysKRZ1RW/46c7fe3pKcKgneg== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +YC1UY33E0KOAQUeqmpcQgQ== +oISfKnZmdvuToULQZ+FFyr2i2Brz9ljXaHOKyGYb25c= +A9UA1yJHjpCuekrdSHwJzwo1EuST4zAJ3LNkG5YnB4PPfjBHPyZxUhXtg2keHQ22 +1u+XjG/2+GSQRv6EzCaWRQ== +9GxZpCRwMRDPejWR2Vvf+LKn0tNtFKp8Eh2tnr4Da9U= +PMsnmYnXwTnywF6bV4DtbjBmhBKkqmzzWhgWTMQKUtmEG7+uMZ1O0FzmJK/3hQ4e +nISGhBW0tbL/D0lQ2yNPTT7FVkdEno/QyBB3p9pKFKC6TdxJPov/HfIHAHYw7yRYeNPnXUiRtuBRXDHMjr0X3w== +Oi82qoqybzdF6wkjTqWiM5joMzAjQWTwbxNSIEXhMgiab78qj/lZLvmdt1PPZ2bnGI46aVJ2+zh0tp64hc8PgQ== +S9tzajN7j7km7OLOHRXxfebLQ823piED5q0tpH8z/1YPaWNiYvsAENBtlVGf97n8uTj8hVrVf6ihHZvjygrbZg== +1u+XjG/2+GSQRv6EzCaWRQ== +CO4zLQS+7sxKNzjwb5Qdv7eEsH7r2P2dLPAV1to97gol96aFPusyoJUNwYFFNYbo +cGzT2936rNP0cRSqygcUrE7A42L7FIG/hGqW+QvEGkYcjK//YXubOaR/fkQS1+cw9Dyjvvz4uuakJwfgKFL0Ug== +W3A5Dt+8AxWSKsTYeXZoL03E+GmF6G6ZqKMiSSB2XE+S2J0lAHvztfjEq7cK++/6RAqMfNQ2O/HkSgB6w6nRHhdnFMRajeJIomuRGUCboZe+BWjJqWc7ADlhoqz9wDqAmarDK7NshyxA30lz2Aug+A== +xWoGNWjKGPfI4gq8aHoTfMILR2ZzS4c+Hu2XOwXpf0zVeJvwZXetFtWYi+vkCyUVIbLFP303SoVxsMoy3AE211EkR4yh6N5rWi0xHDl3FBM= +Yaigb1wG5vfgU7Q9M8eNHG4/xVhRXpcLlEI3ZAQ1KH7Fb+RCLrOE7B5W481+LpjQ +2VEsH0pOk9hnXSSgTnnijKIYL450dGfbk9HM/wtm/o4urNDTfRtdqkVM+pQb6jmH +oJ9IMj4ANseQkyyPRqcQFFOmpjYou3Smk4qEnAgkl5KSr+zFhFa88zZ1Ug4ba+PI +x1dgkCsAmAsGlXluxUqAjWLjzp9psxIa8kZebpYD08O/k9dqMliiryELooAt4Xn5 +MQzeGnqHGo4wZ4zu4kGdwqlUbK0ZzFZG41yK61wvKsGmqFjk3xQlSBk3ljyhiIJC +34r0w5Whxk2ASQ6HLGWfjYft42ErrWXEFVAKelxYobY8vy4Um42KbVMsAh3Cek0SelH0oyKFyaWGPcQvfG3i1vsM63kYV+g/egDCSHvBP2E= +1u+XjG/2+GSQRv6EzCaWRQ== +1DMUmhwH/xlvvlF9Wem6fpbYbbn0p+nKYrV/0hTgwyo= +trSptuLx8nV2jiPpFv9oLQZzRewlWovNpEZlX9ZHqdNJS+w8pfaVBXwYDtyCfN1c ++pBWk5LxptzizUTsHnZAzw98qUHepCXnd8PKllvEYMVQqP5jFGDm74q9ET0oP4ac +p2+bVXgc/OGpnu+3Mkh/HSvD2ZS1eGKMWD4ICmOkJ7Yc2VZQ033ixPInbY93yISA +1u+XjG/2+GSQRv6EzCaWRQ== +vTNP81yZLjoLdNpbBobM74/BQxPMwxMsJu8vs2PJnsg= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +qQRjwP5jr3EkHpYOFL9JKpejYz3YkCA6D/FaPfRpuAg= +96orka/uERLyRst14azQwhglCKRM38C+C4aUvBEVMMIDcSTKbaOo7uyX1IVdLPpT +1u+XjG/2+GSQRv6EzCaWRQ== +G6YJbDdwBAkXl7+77P1VXsOTVb80rJImj1GUv8HzzrnTsvRqoxVq97cqSh5TqqXNZFj72XSR1FalQoEg6zIUfirlaJswaMTfYLUxATtNUkw= +2jj1sPN6onDq9H0brUOW8QaXy9+XjjcDu0XJ1gL/R9WfNVmPzRgHqCGjZTiIH+ozcL1S5ZpYEL1CVlX5aLXcw/ob647jqxaJBHXBhg53iEtZkWxuk6wUDb3ZeyxhYUjg+hVSeXHh2jNM6Hp9wnVCScSVo5u4gAXSl04VDSb4oshlO8RMnh8DAM2Xnq1PlQ6qCrWJMBa355tpCgjgxveZFQ== +Z8UsPk1Q7HtwjRd4g01ryw== +Mqbt0PMmZi4h/xKUypXQQacVcRFEO1s+fuomosiA4i4vhKbObY/72ISr111UDQrL7/AulKpsowQr7iFl2gPLZ/me9uc7gFsfVeCvi3kQksM= +Mqbt0PMmZi4h/xKUypXQQcR0BoWthVBkcYEvEFJoVQt1QSpsLzyCCHLMqCeGVJYtKB+h5wXanOmJNDUy7CAPmRDmr2WVJqKuolo8HBr9dWI= +dY4KBvoy6P950RQXZL6GYqLIqrTEq4Y5oeZN3IYCOfc9zK38SIvjtAvasPc+ddRdntcypyB7CxJFKWE8IPyVvg== +dY4KBvoy6P950RQXZL6GYnJWa9907U6wpOurCx9j7Z5VOxp5kLTqY/2BD3q//8uhlGspDDcFpc1zd780k3Lkfw== +Ys4bozvJBgtB7q16qcvtBwNMiYZSYHZr5hA+OJTMPZqqqm6alXyu2POp+5gODZhP +Z8UsPk1Q7HtwjRd4g01ryw== +qK4BqV+m2mfNquWU05Vg/0NMTolkH/PzyzM1mbdgq4g= +yBHE1iDakfhoGXyKaj1gSGfHrpXcMw6rnEfH9XHrgr607aQXMfaN1Hj/8IfgUIUBdG/klgyZT5Vm01HWYQlfjg== +2ysp4amLVCES1Pxcsvl/lHS0fM5YKC4M6iZSbJILQAyfyq3F2+f1mUQU/7yehOqV +EV8iUGS7sT+PUUyXub9OjRjJTBTvfEZOSua8POeq29FOkVJNwiVdoDDoyr7td/h2 +/rf/6WKYRysX0TuKufaOmJOoPJlyPU2hMSnbVKPQaHm/1V+wiuV8GfC9QadVbBjP +815Ee+iZtyjtHRMIJUIZZt36Fw/kbeWkQhsrhAK1mPVqRN0QJFZp5rEae/AMxWIl +661hZf7vhUQ+50okfwfTXw== +1u+XjG/2+GSQRv6EzCaWRQ== +YabJCT93a3/IohXaIkr3oWlupiwiF+JLx3IX7SB5elgYui0suzuRoXjqQr6C50M1d2QOXZCiCtbcrQ2cnuAdpsoM6Dy/0tYW04qaANfH51WFUE96cDd6YClRjyA02R5Q8iU5D8qUNXwHVUnDft9fYA== +1u+XjG/2+GSQRv6EzCaWRQ== +EfRe9mf74UfK+9Ag3xivJHhph+dverhm4EdMcw3Xx0gmgVSQnFzq7kwn/hVChgPd +oacxXEoPih9NKsd5qduwcEDhOLgYVO7fKFngVnbMb6E7IvS/sqSZFXzo3XUaLunP +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Sj35ZJ4tRqncv3eTewvWCYTVi//owYnWUb3EQdeX95EDDg6adquVddsWeGr0Ty9h/m037knzcOQNL6388HNwCY= +1u+XjG/2+GSQRv6EzCaWRQ== +zACox7+Ziz8wU0czVTxOUy+ks7x6cRtUos5ttlQSAFcy/PMmajBThYyk6c7kIdkm +oacxXEoPih9NKsd5qduwcDiYSJyxXWrMlIwKVA+jt11Nl4igHLNM+eecuFa+MI2V +32pdC9DD05OE2l0oXazDFHGwLxQjI8TRgs4IRu7iv8AJg9X0Ms5wLpGMWQIg4x52n6cXO1mISZ9rNPx1WG2EsbOTjO6/U0JvvsRkuZxOpxoRGlG5Dq7TVsoTOyYlFTox +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcIuWcW1HaUBcTUGockc85s2tAMP1FH3uC0yUPmPkoVGKAhoRfRSNXjDN7uOhecIT+uWgmQyflNPIMcVN2xiHvNw== +1u+XjG/2+GSQRv6EzCaWRQ== +XgKqf76l8RNsRorJUycsTjsBLg+53P3wxj6t48F4LaUz1IW9XZkwmjHdKLketDxx +/o1IY9/qcv/0KFiM9egBx/gKHJAl67agkj8npfqcJQnuaRN1a1TxspwvD+Tj/5Pp +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgWO6e0QX9n6cbuR3oEEP2/AjHeZCSnyTT42Ga28tQxqRyUhdaNOXsY5oYaNn/hJa6g== +1u+XjG/2+GSQRv6EzCaWRQ== +LsvGMcKEG3AW3jqdlISpabFo0vNBFt6+XKN0xCaGVpo= +MggzX5igTzCWk6jnex4yNhZaYkbGyk+2bWWUgJKFK8lZcet6pQ65vxbLg3Yw0gPH +Oi82qoqybzdF6wkjTqWiM3Ej/6cJYZT99PxuXrJGKsklj8CWLBtXfXEVRo4dS2WV +CAhn8dRUItEbEErp4w+lX1hGKjitJdXTa8UuB+McQN0= +1u+XjG/2+GSQRv6EzCaWRQ== +sEDutDf0WonupGrRy9fpQWSlBBi1IQpmFiKRDVcSFf0= +74b5ByHflXvpHDAbFdMOR1KbRyH5iYw91R9i/OMBXOw= +MzOj4ZiBOJDNVP8vi/lFQDKjTKywLp3ERfUCqj4x8U1yWrEbKh5LG0cEQH9nD1tM +YY5wEo94l9qfo5TPxQmUgm3uLJxp9ZFr+0KFa0oXP4fvmkRFy3kB+iXcpGXlgGUv3IV80OjzGlcQlKBZhlYIqyJv1jiDIv6mIuk5wUcpf6+QBYYu8DqIAW10Dm8ErIR9 +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lX6DMJVgu4pHprAR4mk9MP9Y= +1u+XjG/2+GSQRv6EzCaWRQ== +PtnzyMg0rQKQyXWwmsDmjLu8Hf+EvRsKp4XgGkQ3PSQ= +5rjHMLATIBIsMC+DPjqwgJ0PTbeD55FI9SR6yWcTxomOepeWBoqzGc0fNM7pbR0i +MzOj4ZiBOJDNVP8vi/lFQKuN6FqSR/X1y5Ym9ludFS2siHQAn+kOqZoKDv3PDMJh +L0eUthVnpkGsmKFAX6d+uAqd6gumt8xhrtuQ4n/xAmGddE6Jp4uyfuELyFXtm7wb +1u+XjG/2+GSQRv6EzCaWRQ== +q4nc/jwATOMUyfSjLibfEW5DCAn6VBbRQRnaOjh7eA0= +7ruGyOsuKMUu2VJjD7qOMeaAkd8lbFxmFgrVLxFs0oPIjIIPvkx0ZFyMjf6oRSyt +nISGhBW0tbL/D0lQ2yNPTcTXXQ0x+6saiqpEkbK6NAeuJX6XzqONacN+7aveOlAT +SXXsDW1aLF5ljPyJUoRc7gTkpwyyuZiQadFWUK6ykeVcUQREHclZPKPeiKYvZapm +k5tq0z46pbBEHjNLy3B210WNtogyJ1pKl5SDWGobuN7lIugvbz0/UjtbpBjJtfep +vutPSFQmcQS0oPfw5a/SpHhmS47guKe061165kwNCHVBa9zeUiK5VwM/CeGN0AnG +7jSw4GB3eBRbmC1MzdtYH4Mvcc/Am0zt+u3UuaEBahPSlLobj01cABzDY1FoGFCl +1u+XjG/2+GSQRv6EzCaWRQ== +CAhn8dRUItEbEErp4w+lXz7yoJNCHk+ySttYgyPNvHViu9Hpq9uXa+rVadpMBL7U +1u+XjG/2+GSQRv6EzCaWRQ== +ppSV4h/JuTO765ee8P60djFRTcv7RbQXr3ejg/5Stro= +enjROCFkVaOWy0LTK7q34hv13RHzNPw8gX1Hu4pdzpwkBazrf1LBazavB+eTYwPl1Z/6ldcy91ldsw6oqOOIP+hnq5pzxcIt4EaeT6dCf0kGmxKRT0iopahN+XchpQK4 +TzinVBSdupkFZuXDgmwYft0hyWG9gyvqIM5z2+BRSDSvDLhxrZ/Hg0Og/OncZLBF +HxMcStsbV913HJxaH8Cyjg0iaGCAJnQeoQ3h2LRHpbqQu6HIltuuNboh1V+GpS7B +cJ1Wblh8gF3p5U3Kb/RGlQ== +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBOiUkMe6n0pbo14wGSTsDQOk8liAs4JeLrT3NGczOns2jBJYnsItK2zQ0a5S5xadMw== +QKClQ4XOaAjfVBfstd1swg== +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 +QKClQ4XOaAjfVBfstd1swg== +qxwHEnmbNjMSRPgeUHc97/jUb4NHog5a2OXXyrE5u2Of3XiO+4extGGaGn3tNa7e +7PAOTihuiyx8HjohYQQI8loW0wLM6/rTzDgnVLHLrvIdLPn96K23u+K9vXl0dME7O/JRL3Q4S0gC5IANpqaGnA== +QKClQ4XOaAjfVBfstd1swg== +c5XAUFkwrmDqd0lBTuA47ZNDz8svkxc+khqA0H7J1k8HFv06pegjE2fNysr82zF9 +xqM9kL6IfC1Rf9JegDmC+ccMma6Uewr2Y0GD7LeUqg8+lOT2/clkrbZ0C/8g9o/e +QKClQ4XOaAjfVBfstd1swg== +a6zIn83aERAna8E909JSBDqt1cQLqKrsvQYIMf651TQ= +IFG43LCpIjO82IA6vnlrf5L6k5l2Z7kaFblYYwre7b3rrYQHDUIsH2J6z/J9gxVt1LeHuTFxwKMm6VP5kJdY8w== +1u+XjG/2+GSQRv6EzCaWRQ== +FVoz2gTAtboGsizqSnJ3jww9cBxg3GA/8DdZ8K/YOQM= +okO46V5/+Iw5Cn7pxha+1Cjh2KQ02cL1Ml9F1rWAsUw= +8MfNZ2qNU5c4gNo9k4SQSprgPJF4as/VuJWfMh+Lfik= +sa8TD1DC5UMKDDvtk4naVSMHAkbu51tWOn2Ae+n43d8= +fpyOtVj+Hi3KT0znpAq9h2b4mXK4/sYA8Hn8jy/kPgY= +10p5ngvfyrUYoKtSdW4psO65l2NSS/xzodovSXPEglY= +1u+XjG/2+GSQRv6EzCaWRQ== +IFG43LCpIjO82IA6vnlrf5L6k5l2Z7kaFblYYwre7b3rrYQHDUIsH2J6z/J9gxVt1LeHuTFxwKMm6VP5kJdY8w== +1u+XjG/2+GSQRv6EzCaWRQ== +1u+XjG/2+GSQRv6EzCaWRQ== +vklqNFXBTCz+emXAvQbqCmTyGNezXWtr4nhxd7vprN6spAuLmFPhm0znocdIYVOcFuEuT41R4eIF4LyG+xHauP43mNiv9RKb7tGmGt0Vp1V7wfmSJqxr9n5OV4pcp7RL3ISRND2qxky4qyrya7eCTcOMKVkC+CA5lLq6CsSiFeQ= +QKClQ4XOaAjfVBfstd1swg== +/0ULpLqgTvInFD0r5hHANn0d+C4pinWmo+ezxdjIRItfT6OYsNXkv0SpD+iyt2Vp +ncUn2TP8ZQ4fZMBYk+CUrRwKSWxmuB8Lw8CqqP/PPoRn6EV0WYYsVVC1R0wBcsmp +32pdC9DD05OE2l0oXazDFFaYrzujKRzF6iM6m7f/iWZ5Kn5ob6aBarlGlMBKy9YxEiSqThn9qgj8Sq520UiTGttGM5ZOU8F6A7t/xYjvSSI= +YY5wEo94l9qfo5TPxQmUgnlAHpO3fFZOqoui4H9UIxi30xHoX4p3D192RUI+AmwAxMJoUuZQP5ZQzEFFlVQZK1QrzVmdrWBoreVkoGjLqeKc7a3qd2Vp2J3qMPwzKm1HSn0WC4BLoooyUPPrr9vsQ/RjtIWK0rdDnN48LmxmbH4= +QKClQ4XOaAjfVBfstd1swg== +a6zIn83aERAna8E909JSBHhJtq8ngOEr28mE8+KAnfy+dCG3Se8doeiKaUlR2Sa+LeFJjhBDsYYk5AYgmm0wYw== +QKClQ4XOaAjfVBfstd1swg== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgWw9lTNZDbjWm86zjFsfk7fJXF5DJDnyD5Xvclrc1YbXWazwvYez0tTHnkuj5IZG/A== +09UuO0tw3kJ5J618hVggcQ== +SaZ4oe/LxFZ0EyU/TY70RbOqRL58hj9gO2Z24GnCulw= +enjROCFkVaOWy0LTK7q34sNdGLv3RPN7LaAk3QYUfrVY5IpXrwwhIbUuUN0u2kxza1BYg+uVoUFH5W/7zjNIcw== +TzinVBSdupkFZuXDgmwYft0hyWG9gyvqIM5z2+BRSDSFEMzGdIs0FhDrn3wW2meV +QKClQ4XOaAjfVBfstd1swg== +a6zIn83aERAna8E909JSBP20sdUGQnUau01b8cCNryaXRaP5Bs06FUEZPwhTsZHJ +1u+XjG/2+GSQRv6EzCaWRQ== +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 +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBDqt1cQLqKrsvQYIMf651TQ= +IFG43LCpIjO82IA6vnlrfzMReHRbZWQ8dI5L4Jx4xFX82YkE6Hp/QlzSN60BmOl/DjzujgtD7AazYeGTU+ef9Q== +1u+XjG/2+GSQRv6EzCaWRQ== +OI7XXcJXnrIII2GiXN7VOonZx2KmFXV8HHUfygdVqIE= +q0BvNrS1iCae7wPxzcti3nPuymljOYKxSS+j/H2t3+A= +xBiDY2gzO50UCzaSTm05eL8zCGZlJTOzZ31IHnUR2Yg= +WZ0p9nwXkFA1muq3Fk3fAFere6UHZKo/Is+2TI1VyYU= +GkPrcJMZt4MurQQIgQeoOTRN+uesEkSLIkg38Lg4H7A= +WgLyQBjil3em1amUHBXYkr78Vvu8sbcZUY8u2slWL08= +4a3Ec2l4WLyfo1D1AU8OWZekK+O1EJ/ZWb9E+LHCc/w= +QXwQ5GTwiZ22rTbFVwfzNsveilFnj/BZfW7SIVgW/xo= +O09S0GM8fpSDiFbyYHciM4Iv1lqmryVDqhHUavtFdS0= +1BcovXVZrWFlgzWmVAN9LymlH++i2PjeHe3Gm1MJevY= +yPXQlut/XebodovHtXmIWJNwUiITQb8IuDkVAosmX3o= +EsijZB03SsL8d5BUzE5XOVumuQPWNBWG+ZsvMWCHGdQ= +NZ+dhEPsNw4VvBoRDlz2vEzmhmwxfuhe+7PLpGyxRqc= +45NcT2PdBV9V+6rOpgsfVhQ3S1HrhnlFcF85dwAH+bE= +NfdjIB2SoHREnA/OxPPhyDk07RXCe6xz9yAr2saEOBU= +iFbeB1kOlkZ9ppCgIEEjq6mXkNvFQ3gWnxOkscPDPLc= +1u+XjG/2+GSQRv6EzCaWRQ== +IFG43LCpIjO82IA6vnlrfzMReHRbZWQ8dI5L4Jx4xFX82YkE6Hp/QlzSN60BmOl/DjzujgtD7AazYeGTU+ef9Q== +1u+XjG/2+GSQRv6EzCaWRQ== +vklqNFXBTCz+emXAvQbqCjdTrbXsYerJAcQSTOmZEiuQQO16qJ96/L6MEXk69iIs/s/z62TG1LiTTUmSf7NxBjIwppBMR395oNKnc3w4/RDU/Xutgd5YjaT/rzkqqI6Y64VJrcd4s4L5FJfxk6kp/g== +MErXDftQ+xr/u1OkdBBq1BIa4JKCHyvCbz3I9Kxh8Enua63tr8weBf6Z2HCAoqc0dPeq+VpmLSBXxxDnZYPtYxH+wWaP3Av3dYWi8XeKKQRif5BkAhl/RPCZVKRfyOXvexIKvuVgGCJPusaLGpibmw== +AVUNgqiRCLFrtq3+4Kn7wwfbLaZ8P6z8nlnRlIb9aITJ2gLbVZz6FHc4SbHHUpF8IDIdONoBiIdSXsNGI8BdbQCTxoGdB+96fGfrpJd85W9VjXs5ttw/iTqTnsD0SKy+xHoJG7jGenhUhEi0b/FJcA== +yiPmCGIpg0oISpLv9VnQ/W5FzPf609jJGm1QRe9nZu0= +/SNeT522SVGIBtw6IVssOI9bSU9tOMBOjmZhbynDLzWSBlTXwgWGjTORytYt+zp7HlVNY8P8t6CVbjVrnUes+uax9LdanugfBIqd6y8FUZCEBSrVzL1zsJPWC0wtoj1o +1u+XjG/2+GSQRv6EzCaWRQ== +FPr8PM9GCeXhiW+GMolpg+w+AXb9/K7D1HbxkEjhLAWugcs3/N0Qvl9Gz3mYbBRv +/0ULpLqgTvInFD0r5hHANuNzaA1vC2kIeLZ34++4hCWSsKig58IDJlQdSGXBrWIl +1u+XjG/2+GSQRv6EzCaWRQ== +4zBt+EL5WSFbHsGmdOu5qsGueq5ernEwjO8s0U4z/5oY7CEMjK3XuEb1rJ28SktS +32pdC9DD05OE2l0oXazDFGl7O4fHxPWEbSR4z08KD1UXw9CakW5mzLN7EbtRHIJ9VB6IMXkiXYFQtXLBHtdlLFj7RvW/7LrlSchK11cTRn8= +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0aC4uUZdlLB2twSbs9qonY7D5PAxRlyelpbvDMX+ES1fHsPCm0XUzzs04SnkU8Fe5zdJRkWIxVX9rHx4DfwayrQ= +1u+XjG/2+GSQRv6EzCaWRQ== +4+wKB+Tel1iow8au3cOIJTXxOTIBsTZ98UgT9I4N6WAjwpeOWnbO4P4GElShXJSE9GddxjXrR8uwTYH7CCuPPQ== +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBP1yHXNm1SKt6FEfia7PkLl+YIrFxWTKar/SWOyud2SDyEGxxoa27ylYfHmG2oiQMA== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgb77l0Z6frIZyDRbHki5n3bxHQrtmQQiRJ33iBCyc9yzXFd0Tp8ZWE5qw1HmC9GduA== +1u+XjG/2+GSQRv6EzCaWRQ== +MIoXGNzn9yHKdgBbrjqXHMxDbCEWd78y6VoFbKknTJM= +IhwiQ6c7kIg/TFydBot2SO+Hb+O3YgDGtHs2mT+94gO0C89VvB/zyIVkI/MWCMSdpcIjZf312e/4Fwst+C8AjQ== +ojcUvW+Z2ehEJ6yMJpmY+365uxDMHhvYP1+9nY+XIpRmNjEB4/EnbEzWomGDfrkK +AKR+OhMsr76k2SU3CFoUdQCld9mYBwHKY4bTjN1twhsOpMQeg7+RtGd77Ky7gtODMsDAYRe8/pDPPxQP8zwS8g== +1u+XjG/2+GSQRv6EzCaWRQ== +ojcUvW+Z2ehEJ6yMJpmY+9rwchSyxh2jERaT/W2pGwE= +Y1nYwlj45GWxs+Tt8B+LmYxBMp2B2z+cYxVQuDl2gZaAP+0FdPNHv9CT4GDs3QuY5KBczdRuIrVwzLeJP96o+Q== +1u+XjG/2+GSQRv6EzCaWRQ== +ojcUvW+Z2ehEJ6yMJpmY+9MoFwyHPJNw4Ww50lDl5A1mBJqkFjopAZGXuWRoOiZRHk7VIG1mXh/DNm4dhl1Wdw== +geMiRxLD+RL5sRJPNAhMYJfabWpMBq5Uma/RAbghuSyFZesyYu9qZWtCJqDqODnv +1u+XjG/2+GSQRv6EzCaWRQ== +luZ90593aV7WhjMgeAQMJ6VREYXnHz2s9POji2NJwFKKdiLYHo385XTo97dmhGA8 +dbeoYmi2AyUYPPKRkPUUOW9rg3JuFtKNeXRQIhTW7PmpiDQD7ZSXvMqAgENABgSO +1u+XjG/2+GSQRv6EzCaWRQ== +4TatgresHQvhPdvLpNRT9/7uxiwpA1D44/S/bAXIZlFCwEZQNwN+YyJHFkulyk0o +luZ90593aV7WhjMgeAQMJ3NoZiLqUCw2/VNZl5QI0tqsZL6yqK8C3YiPXN2aAcd+GlOs9jK0b5Kp82ds//z2Fw== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0a3HoxgAwEO6mZx0EkNLX8SJgZi4w4QwsQaSaGMJ4oODSbJg7pAxBQOQeZbTJwApVO2IbyIkaA9CkK8VB5zXBfn2G5LFz0UA0D4K4IL+GztU +1u+XjG/2+GSQRv6EzCaWRQ== +ojcUvW+Z2ehEJ6yMJpmY+7GcfzGZWBHKqwbNDjqpt4K3JzVNp9HV1PSLhb+aFmttSqpuCyO0fsTgY4mv93/3hg== +NMi8UsFw86NN/FccVttHUaORN1u2j1crkMWlROFkJaA+EpMIaWrdpW9z/JUhEoQVF4a9gS9C2IcrEguNYs7/RLbU62wYCbZjWjT3hWZKGfNQ9pJQvjJYBBh8UenijobZ +wgR07xfoapmx6eEnFHXXYk5FrJ2FLX4JhQ064+WlI5TqgJcmaXxaJcDhp0DNqQ+0 +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0ePjvkTlFnwXtvFn7E2pyDz480edL6ye4sMejwvcjVa/OjS42529tdC96Fz/mBJuNbC/f3fRiewSGJzL+zR2mkc= +1u+XjG/2+GSQRv6EzCaWRQ== +Zz7V+a7CTH+8kewJI1afPyTaVYrCHeEVhqALbi8MB4PdT9Okdpgq/m33yXST5aNS +1u+XjG/2+GSQRv6EzCaWRQ== +luZ90593aV7WhjMgeAQMJ3I1QogDsz/uwbSX3gm6LcirQCC0YJtYrhAspLOlJWh6 +SPX4u9u/0KH75hz32JgtQHXTh69bfcvrs+87KM/53MO/s0BOvorhq82N2mtr78IhPw6f+zKNqrlAEK3bOSa58sFkzCdoIv0i7pw90AxEt8zWd1d+DdZBt8A0NlxMOyTk +l2FJPs4YkAmmok1ulDRuSA== +SPX4u9u/0KH75hz32JgtQNgRHAD1u3yTwQTCzUH08EXcDkWzTO2EYQdrGmWi5vd1LuKLjM6EZFJgf4zR2Q+Ebg== +1u+XjG/2+GSQRv6EzCaWRQ== +IhuisKim47k91RVt8z8qtD7kriuPGqp4ckZ30RGBSw+AuTXuDldOdIaOIUn2Ii3W +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgVmdjPMlUk6mZmWovLLUEYZ+ZqXNh+YvK0HwFE+YrA0DIE8fPEodMfNYCsaU55nRvrunxPFh+DTCk4lxM9NY1uc= +1u+XjG/2+GSQRv6EzCaWRQ== +onAPAt0fHSY2gF8A/EW/pXkZ8u0x++PYItwapADNfJ0= +Fymfc54sieZHZmkEhOshrYIKO69Se9tgbZ1kRKOJz8iN71getnpQBtz7GMQTYSkO93NjK6fZaOh3U0Ty//zX3OO4uGMnZwcAAJ0mq9h6Tgx/D5PGSULJ1QEDvjy6CIeJvKyAyfgi2GVgVkRFBV4n4Q== +uLdHavPlpyoTtfi79NzuD4irZaJjSvR77u3FTdek4+gf/r3Ust+vUo/FyRwrMbzTw40YptpFVZofYcQRjLKvBZIjaqdKp3NyvnhCoRQrdXc= +r9s+exzBpvjvlZprTESsD842BwePoNeX7dpITypqUq9x37NRdrY4VhG8e090xeTu +vF6cvI2zz3yfLrz/L6DzRhQ6a2ytaNSIU/GDWts2+oElRlMqO5XQIuma6td47Y4usUpSeChf1aq0cHCcS3BzUQ== +1u+XjG/2+GSQRv6EzCaWRQ== +2yQsUep5WrV3T3htNZ1L8TIohok8dxSGY22+WeWcmQzlauL17DyWRvW+qqAHUI9z +3PYuJCOiZH1sjGKjxSVU6JzQUvtdJ+VVWARI44QMBGlh/gUtgUeUNGH3r4EubhzNR+5hibzjxp2yDGUd527b+g== +1u+XjG/2+GSQRv6EzCaWRQ== +mDXWO5b8TJxjEYL777wpQTYZo2Ga1uh5pSM07bzJNv22/DAgU3WGlSs/NOE6bPUlef2NRRZvcHbXa3zq+wJ9+RrXqccmcIN5DO+ZXKKEI5g= +PgWbOuBwavQIqRuZzZE8k7Uine4Ykoj/jwONh9/Ai5+iwjYA+vZPiIwBOqr0rZLsr3qVtFh+JfYh8BhAZyyKWb5vVWBX0yid4ICiQD1Adfg= +a6zIn83aERAna8E909JSBDOCWX/Dz1m2VNy56hiOHudLJ7i2tbYRZ0/VNjFjTl1B7WMOshHHxt8a++/G8NazvmTFJKk1ySb2t1L9XIohmKo= +1u+XjG/2+GSQRv6EzCaWRQ== +ERFPcpFJ5x6x1iUKDQh3uhV6XHEL2RzE+8jUFszzqrcEqnovXY8cqnrdhcaKMKYN +32pdC9DD05OE2l0oXazDFLDC8Y3XJ6Q+kvlM4fAob6pFoskCLn1bRmvXqvEFDrxfBcWK+T3LN+Nx7bPw4vXdahQzHmvKsZDhr+NQmTBO7TDMZVc9ES+1nTTgE5Qm2XLQLwDcAnmG5rdXqi52ZAInPw== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0WC8kW3bE+2X+BQZ2qWFR2dblcp0jQihBxk5XETTmJmUhJqxiw+mAczruhVhUDly7w== +1u+XjG/2+GSQRv6EzCaWRQ== +TOmaqtOsYFL5TNLmZ8scYxpit/p0HllL53yegHPETk+yqAho1/9kMv4GjYiI4pQ9 +32pdC9DD05OE2l0oXazDFLDC8Y3XJ6Q+kvlM4fAob6pFoskCLn1bRmvXqvEFDrxf96ZdQNyJo6/ZF8ohSNGFDk2JcwLqk7DuaW/UuAC6BAgJY8mYx+uvQ5eyuHinF3rIACeW0CH+RnQElp6pWLdLEw== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0WC8kW3bE+2X+BQZ2qWFR2eA2eQ3Z+GaqEIqQNMwYIPX/GEQJ9XdGLtsD4r5G2llaA== +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYo4se8cEG3Kpe+KryARC+5/s+COk9MOO9VM3V7stcJr5 +32pdC9DD05OE2l0oXazDFLDC8Y3XJ6Q+kvlM4fAob6pFoskCLn1bRmvXqvEFDrxfRNrLULHAYnnf2hHerDGSYZLJJjKH94XJw6LAvIuCId6YAdyiiU8q0W1PZ7naMtjoPAwls8myIpRZdqBmZpFiqQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0WC8kW3bE+2X+BQZ2qWFR2fdfGk66PDxQDgTN0Urx1EjJNyfDnMCtPFIgDzeFRiGAQ== +1u+XjG/2+GSQRv6EzCaWRQ== +yXqkSqtRGSEMRZqQHZ1+mrgtQzra/P1V601MdoqkoAU= +1u+XjG/2+GSQRv6EzCaWRQ== +IluO19DOYseHfsOaGSZp+fJ8N5g+OaC8b7k3ympQvaGMGyhiyfwWR2pzsuqqwUZA +tMFpb/D+AxluSZnCdwhYWtSYJY9GbZxtoaNwiPkdykyV3LNmni+sCGKEkOL/T5rOJyanyEzLH2GSOCw+5lJ7yn0RzOXTLrxGgNC/4vn3PQnbN6Yo2gZqiHC2D23cy+mm +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBOxfDPv1caf3MVW0/bzicu2a0aeg/x6WnWH2wJJTytV56y8+0TcVJEarc5ZmcTxqTUswmEV7vETpOqfgqSDkC1g= +1u+XjG/2+GSQRv6EzCaWRQ== +neG2ZqmysBKy0DmUM70J4lIP33D1GfhPBL+Wg7KQ2BkDE77E/2K5WGxQnOu8m/A0uBqEBzSQfFFEF296r40wYQ== +s8oZlk3kc0FacCBDAb+rTqYD2zBXMxp249h0ThifeozwWhcQz5oX2ByAR/Ez25cu +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYrQR/7nBKlFU7UIR7Tp7Ask= +32pdC9DD05OE2l0oXazDFLlxeme0qQsW9zSWINEVqdI9GKmjReB/qO89pKloFO9GyhaqMKHcux0qBd5mqGBMhC1FPc4z+4iPVLBHDNXiVjZFzCCdElNlSXNFSFN2liCeH05FIQwSmtBo8JWqN1VpRQ== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/XRRyd9HDiBpo0pCt8PUySBg== +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBORQpeyOGH5rhd8/0ewNMCSTxsv0lluP3y2Z5wRzuZT8WIMCWlFvQ7XQFC2SdLlTjj/QLLHfImFlZdb1mlvLeQaCw+q2t86+41nAQ+wyhHkx +1u+XjG/2+GSQRv6EzCaWRQ== +a6zIn83aERAna8E909JSBP8pJQPtlYRRn6plgZBpZpB2oPOV5cTcii5VWlLmobX4Q0zFUDXFwHxhoo5Nh2SRFNSWjqRtdyWKFPTKK+QMkco= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgQG1+/Qgep0fT2He09pY+Ml5uk0cFWikdszqoM55EKQl/fYItdN7Zxh71psNj3mAYQ== +1u+XjG/2+GSQRv6EzCaWRQ== +H41vcsdfxPVk/6oDUlZ/BP3M0lFxno+/zrBHSB0fPms= +fJv4WNxy/gQMuHmvLnp8VvQ8fmE2jG3iJrlz1TKSIBpR4GPKJEGtMqpg8U37TStq49Gh9wRLaRotwvq4t+lIJw== +CDPfAm/EwLePrkOZgdeVYKOrmGQELqLsNAmExyF30rk3tOkMH/Bx1x83Ad3+GZ+s +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYgDVslGW6Tjip0lox1J5sqlBCynPg4r4cj8czqxp4Bct +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0d+RcKVoSGK0peBx39cFmtm116OHDpWP/LXfzehL0ORTHy0Jc+S5tSD5KxfmCBWM22Yc7rvXvWIYHJmEZmQZiCgivw/eVt2HwE4LuwoyqBOR +1u+XjG/2+GSQRv6EzCaWRQ== +mEOKzB0uJs35kZrdrKxIkFIbyleq3BbZR6kwmxMNH4lw6SxiWS+B8nQJYJz8JPZtth3mCpnpihp5L0FHci20M9iHXhCRv6yMxGIVQuJTbKdJMyaTwFnhX56XpimqN+q676kxoCGLvrNU0WbKrccIt34Ybx7DVbGxo6fX57CsE1RPwn27uZioxigyFxtlJDU2 +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcIuWcW1HaUBcTUGockc85sxrSAaaq/5LrID+23fcnDrEBsJnoL2CVi/OL9fwEGw6veGthGn92N+wSctGOfX5IOg== +1u+XjG/2+GSQRv6EzCaWRQ== +IhuisKim47k91RVt8z8qtDZn3vSHi6GdokkJW4MxFlElWlnCrzThXPwU4IrYTTIdSSBgyo5OOR4P9Q95RqcUi5B1sADy9cu8D8QbTfdvkpb9lSElhxX4hqutFbyXQMH59ADrKCoreTdIq97TZ3dizw== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgcbEldxW1qTzCmNrnlq1LxOgyzyEButiX8Ugd2H56CDJjUIyDKGL0d6LGLmS8vaEQg== +1u+XjG/2+GSQRv6EzCaWRQ== +EeZmdYErF7AflKJCqxtKMg6b0pGdD0QOT6/Z1g3jHeWtqM/on8V8danCl00U/Chf +YuZBUDBm4rTV8T4XSN+9AasKJpYeJR+KM6McfNGy0UHUK0cFzIeHUrrnUOZ9peToPNnIkhDU+1ZM/SF78o/qi+OD4p7IkMFASQdI8m37bx++GD4f2zw+KFa8ieYBQwugdZpuOWst0VsDWlazVBSkaZxQRWZcxHnxOdKThxJQHyE= +kxpI+i2rKAyojfy7HFEIC0EQy1ABCZ0vy7+ga7nuf9dfVcMIkye3AkL4fVw8LXP4o3xcxn1l8Vsme4OXHaJ5r6kP0dTfux62uf8C3og4ewB0eze/S7A+r69X7DtiOmnT +kxpI+i2rKAyojfy7HFEICw/FXPQUMGkENEmBxT5Hi+ny5yboBcFJUVRsJQdwRZoedAZRQ4eajCDLNVOioICfup+IRbn9sNPir8Uy/gxK3PZ3XWRbCnBEKGXlkEQRBWr15aCLve20NB5cuonuSFfsvA== +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9lPOP0lY7q4hvohNcUtsqtEL932oLK/jp/Tjh617JoAz +wgR07xfoapmx6eEnFHXXYsBNzI8ySI29Nw64a94cEffIMV9wqgCfiQ9HYQJJ8aonAXh5PVq6JW8YOuwuAORGtbyWXhvDh9hSJzYZ7nCJ/C4TQVxgMX6LVTbXtz2TmFAaDlhTXv6NlfSjiAOfW2yV5Q== +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0YjBKrjsytEwU6dzdcOfBC4t6wbBVvK6nrDz9aCSeQG9LGyvyFv7Lorrh6Y5jVjXMNUF83wusFlVPk1GiDYR0u7ytascDktrk/gNDKEAdb/oAExojz2xd3kV3kSkyJE+ammVUEIgpQniFH8yQb6xZefmpRjy5gxraclTMQd9uAIOF+YqOb2J9YbU4xy/3GsBTQ== +1u+XjG/2+GSQRv6EzCaWRQ== +91gsC2HsVrvTm3cwuG3h9hkj/X8VjpYDw5TnTmbutMcrO6FCN8mCfAgz+L96d169 +WHkOzVx7seuLmxs5Hu+l/vuirCks0EHZBBXVgwpgabkiMw9aNr1FMS9nozRa2SKSmJqPScuD/h7i0EBMBfgcJ5IGDIHEO/Y6IFk4Jrxsj75Gt0YslCm6kw4ZrbQnEuOz +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0Z4Oar5wqYQDTCRptdPgFxPsPsjN2AeFbeXlACBjPzgSKLxtrs9M9BQMLKBxvBMoHsNi4yxx3hO61hwwv//nWiOKzcWp1v/e6qREqP/7sC1l4TcrylyxgBZtdtYBg+WAPJg5vMTeQ2fFzVYbxkYEG8AFNnLGaE9AuqXpVc7Mfd7Ys+D4OdP3srGBvrYpz7TPR72XnrwBk3bLGtD1WPigJEg= +1u+XjG/2+GSQRv6EzCaWRQ== ++pBWk5LxptzizUTsHnZAz99i/5vNWptrZ9p5u7vIcfi5qEc39vhVrczSNIQsTzd1 +1hs1FfIPISXmd7TJ/r+Vl0ubuLMfXeCOli9jAjGpnrrwPm63h+EdkpPu7CLcQhV5pYZQDPB+VG5U+OMuKfxrhQ== +VmVrGQo2zRokW/ZuO9bN68/aqwLZuBDDNiLmObYqCwz/LzioFoJlCT3GV6dGDP1i +1u+XjG/2+GSQRv6EzCaWRQ== +/R3RsIyyuJzgF1BbtmZTiH7qhWhAIqUT5M07bHjDIBE= +CAhn8dRUItEbEErp4w+lXzHj0KEav7R82AXYdQaL+QYlq4rbQkgbntuj6ycJvwzBWjhu0u6EUDd0C8TmJFMu9Q== +zp9fjHH16jzBIgtF28Da2YOvmdawqMDTu2Bh9h68R4Q= +zp9fjHH16jzBIgtF28Da2c+aU+2QxcgoX54n6QYh+lU= +zp9fjHH16jzBIgtF28Da2fr07paxMrC1d5oLLJXG+5JKMR1JpjhnDFe8g5XNxTq4 +zp9fjHH16jzBIgtF28Da2WsyLCM8SyPHcLf1wAy4XRw= +7CO/lzuS2S8MDvGd4S4S/IalBviO8MTV90p8lL14VHHdMO0ff1TwO/CAycdFE4ty +KCyhk8I+KaKyBa22YL+N2gjVwOTdvm1sKS8fP2tGIbtMS1Cl8j41UTUDANPSh5st +4PrWX+3jeUMWuxDrEA3D6EpOcgYGLQWMBkB4xqBeMa60vwyyu83vzV2wBlCR8C/wXXPpBxTDiEFM0eLimSCGJg== +/j0OZS3RkHNaeh863pRRt+dpqitBk3unrtRVwXgYOpQyAj3sdeq1SKPCtvLlK8Gfqy/ydRlZXM6vaSmiVAItHw== +8y1tZpQcUJ+ePSQFq/eV+sEM3xfHLTKbvzwwOnsLzCca2azkEbA/Utw1HsXImScL +KCyhk8I+KaKyBa22YL+N2o6VCfA74hipUsC+qIzvz6soMQoC61Txij2ap8GOzEvT +4PrWX+3jeUMWuxDrEA3D6IFlj30nx90db/NfTrCW68Mlw20waiTKadYnGV3D51zcrlUnEs3mRL7px4YA1VmSdw== +/j0OZS3RkHNaeh863pRRt08PgfIvDTeMRf1H8Ka+mVrBtz249BhURkPXWaEQp9/qGp9dLpdlFp7YOptdFLQzFg== +8y1tZpQcUJ+ePSQFq/eV+jLexRGdMoIwl174I7ydZ3azFShSloNvfN3zE3cBaRIpSq/zN1ACcvNiVs6X9y+iRQ== +cJ1Wblh8gF3p5U3Kb/RGlQ== +1u+XjG/2+GSQRv6EzCaWRQ== +NTRv89rZA7DPzi76uqJdbzXOdgBgNzT6QDUOzrlbuz1gJAX81O/dl+LZ367aJ2hBsaHQ7TXAtxkV10yvImGVGxEjk1f755R9btlslEeQTI4= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +KP52Q/5db8uomsPaNzzcfBn0Wjgc5Vn7k816BhiOEa0yQxZGpiMFcg2VAxUiuXWPWQwoEFWgWLe1IyDs5XNGiS2hWBSj4zXhvrDP3L+5Bv5rP1o+9rur4BaQfMunkW4lta28xCCqzPexjJzZTKAm9K5fp0zDeB0U+HOA0qZg+W7U3UyNf0h+XI4pESiSFYHc1awlgF9IR1Ew9Tsmn1nxIw== +91gsC2HsVrvTm3cwuG3h9i1NEZiWQbUWUf1I+2XxcOPyVXXChizlVctse2YyhkT7 +wgR07xfoapmx6eEnFHXXYsBNzI8ySI29Nw64a94cEffXW8F+CU/im35npnABgsjX +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkuoFr7qnY1fGt33XKYP4P/X/VyoP20fBYr9NZA/eWwI0RPGbYy2dR0G/Doe39YMmgG+mU3AETWDI+tP+t0yh6/IpeakQOiKDz2cqZHFypPSAk+qITC9qWXZh1H86dCZl0s= +1u+XjG/2+GSQRv6EzCaWRQ== +i/zCPf9Qs/+kk7v5QupM9ktzU/pxIPa+5LXi+xSzGQU= +1u+XjG/2+GSQRv6EzCaWRQ== +DTYhmGovF1cVlw5xWr0CoXOCjpxAh7QjCRjwCK4S6+4gt/e+HT30mXhkurFOI4xR +CdQuMPSL2hJUsDHDyZQSpR71yvB0E0H6vD8WVTIil6g= +7CO/lzuS2S8MDvGd4S4S/IvwnfFqwr7s1TxB+WyG8fw= +zp9fjHH16jzBIgtF28Da2eyKqanv7EW9dVHpDUtfa5E= +zp9fjHH16jzBIgtF28Da2R2Utw2iwv0Zb6OWswkn4zM= +zp9fjHH16jzBIgtF28Da2d8O3Pnb/er/sFLCDaflPFk= +zp9fjHH16jzBIgtF28Da2eGSS7cf64kFJT4RQ07PuAA= +zp9fjHH16jzBIgtF28Da2X0uyMOo9W7KQx+3luRcZaA= +zp9fjHH16jzBIgtF28Da2aGciHU2TRzJEvNcgmYe1Yw= +KCyhk8I+KaKyBa22YL+N2pQHbunP9syXmbPopwYYfuc= +4PrWX+3jeUMWuxDrEA3D6NHHMBm8HBRXBpqA0M+ngPg= +/j0OZS3RkHNaeh863pRRt28YLyhV6ospqk1aNo78Pn0= +8y1tZpQcUJ+ePSQFq/eV+vn97szSzdj2V+8ZMiOcsvs= +KCyhk8I+KaKyBa22YL+N2sHzTUvM7KwB0xiWYAMrC7M= +4PrWX+3jeUMWuxDrEA3D6PwUG9XzlT5dHAqiTmtYb9Se1LCHQVlYujfLkVqOUKTQ +/j0OZS3RkHNaeh863pRRt2ywOO/Y4uWpogYIf3a9cgE= +8y1tZpQcUJ+ePSQFq/eV+kVi8e+9+Thk3cM/Z/bzq1Y= +uumnoU2LrNuffNNOaLzz8BKARJb9Kn15vhyiYohdTx4= +axxUqcdVGr1JJnerFS5k2Wtj33XEqOslTB7ROcUmkyY= +744+BgzK3LVsSzMxY5sSrw== +1u+XjG/2+GSQRv6EzCaWRQ== +gMD/odU6bw4q71U7vnM8p43WvHcvPK4XqMf03RJyBFw= +kxpI+i2rKAyojfy7HFEICxc1rwUXvGMiicmWBwCBm50= +1u+XjG/2+GSQRv6EzCaWRQ== +6IBX/NbikFfeTGpcH6P73cx5TNBfxE8Rflj/2ckzt6NTYpxGjCBXImPVgszH7a/V +RuNblYXcNMm2CK4ud0d7WsIPxNHzHI2me6wjkWQM8rx/mupZmcoMoTNGwZMxUZ3j +4uNFnuZO7ryRU8xLndWWZ2ESRPOipJWPGA7A97qnJZEOvhxml7IRLVJqED5v3VpD +1u+XjG/2+GSQRv6EzCaWRQ== +kMiP2L0bhoHJnBv2fGeU0aETkJ5D8MfNd0uuMJ629cthy7dL2tCVGiMmkYQHZ+g5 +hUBmVW1yXNp5HDi8AdqadAriG38dOuVpam0yMcQYhHg46E/bIt2iIkSDAfuSuUbkueEOaCQwYHsu+Uwr52/pyhN3YvzF18C/oIQKg3gquP0= +hUBmVW1yXNp5HDi8AdqadAmxe96DWEWEjMtViLMzPGEYmE9eFmnZZnKWIhfIX+St +1u+XjG/2+GSQRv6EzCaWRQ== +fZFC9ntdS82BL3LhVXodWOW+PK36r48XyLFmqg7XRuJTamyT4wsP/4JjobBfTKfs +OerrFZmSJ9J50EZUqnQsfaK4B4BPUg0/kOi4Pzd+MXnnO/Qs0dKGSyktcDxL6uKTIh2N8zQ/gAY9DCc8vpFPEw== +1u+XjG/2+GSQRv6EzCaWRQ== +b93MO6Z2suiz1Fjm60rjIwvt6Pnl+Bt6G/961J/ZSfLQogWE8pEQj6ItEIcK740p ++pBWk5LxptzizUTsHnZAz99i/5vNWptrZ9p5u7vIcfjWNgTSYjncObhYz2uB45Mx +zXhoKyuNaw2yhNT+M+EnM9hSZ+9RY1V7jjGxZ3LEJGE= +wlLHv6kT3Q/RmtMBN4nDAXW1lDvH/Dt9OaSyalc22mM= +VmVrGQo2zRokW/ZuO9bN6+le2yBw+Q1tvVBKJnyyii1uo7OVGnioZ6XV3GUfagGq +VmVrGQo2zRokW/ZuO9bN6zxIfAqaAztckaecNx3iwpgczmxDHOlL9zGuSohYhWyNXJOPY0ihVIw636OyelHv+A== +VmVrGQo2zRokW/ZuO9bN63NQDSoOf1fHuK5xTuU+C1YFeUqeWEpg5FeoE44mF0vhtUAT5SX/7FB8qPCddnWg9w== +VmVrGQo2zRokW/ZuO9bN65hAGaICagbU0z0X3nArVjY= +VmVrGQo2zRokW/ZuO9bN6yG1lGH/32At5mNyjtgpBJsy3atwNxad12gy4FY0QyLt +VmVrGQo2zRokW/ZuO9bN6/2uJitF9IbwWElkXfhIj90rIpRTgCV5/2wWbbt/RNwMLCl8UcVXq3ZlnX2vgWA4ag== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN60VMFs6YNEgtSc/wRLv43YqqU7U8RVHzwVeXYJ1DZZmo +VmVrGQo2zRokW/ZuO9bN69W/qVVVHiwit5fvkuVU6IT9YdjS+s/PaXgJ0VAS6WK6WUyqjBPDAtCEehVPSiGMfw== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN63Z9zmLIE92FM7w5Lo6V45EShP4cuLfp9c8qZhUzqrCfOheFFriTIVriVXTOpXJUZg== +VmVrGQo2zRokW/ZuO9bN6wxZiVPiC/XiYv1gwQ0iYp2OSTMIXua53iaLx02LpOwefirT/l0AlzQM0Psoj/SPLg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64auVdecQEHLb+XuKkuiiVSm/WO0WaJO5XuXtDtQwUSR +VmVrGQo2zRokW/ZuO9bN6//e8iZcVVdZNKq5EcFKJJU= +1u+XjG/2+GSQRv6EzCaWRQ== +o1msixA+MDtPVXFYKZeSXCMnz2H3hhFkmVSwecnXMkIKcjH13RpDhYsEv3B9ZR07 +McSfMGbGDseDL9zZOM/2UnQRv5JGc1OPARlS31Mv16vw3qCKjvaLABYDLBpmy276 +1u+XjG/2+GSQRv6EzCaWRQ== +xBwuQlkMmm42MK+w1NUK8vJefzT9BJUeJCAIu7z3mg0= +aM6iHfRmEebzKIflcWS+OhHFOnACt62ZxssTET7+24o= +1u+XjG/2+GSQRv6EzCaWRQ== +bjZj6OKTweM5OZURBrTjsObnLWBUl+YceEw1D23vbJc= +VWk88LPTBSz9q4QFIgbDNhRMO2uwT2FY3us1iuTgEsY= +1u+XjG/2+GSQRv6EzCaWRQ== +7Ve/CPYxbRIq/L2vO/1jNW8apRCWnAzslCYX9W5e0jETZHCNQ8Fk6nI+ybOE3Cht +C8mxUpsh4XMtIN+H0SJ4glrglri4/JGM6lWSY9opT90NHbyMdCWvIf4WWqzzrmTT +jEH+E0FSKBqwvo8QKx5Bbc+r84Ib0C+KyPsQ5+s8rnDbzZhuoz9OWGrPWOVuz7JyAhNWEXviUWMymZHycoFrUQ== +nYils6ppgxoVJaY4oukM0W+bHFNl+YUudTDuVTnqvyM= +jEPV0YXDCqWCap/VHplvwQ== +xCSE09sQPuao2+KRKinomkSNGTwhnKwbHmZxiX6Wca8= +VmVrGQo2zRokW/ZuO9bN61QC6t8vqLH+1I4/Oj5z54VmAFtJ7oTCvKbL0Zs9g24O +VmVrGQo2zRokW/ZuO9bN6xrOzovpr9LhhtFOlE/zdBN4iaEhYXMJEpnUzDcxhxes +F9Sftf1PoN5P+lgvc+r10BfxjF9TrM4nZooF1uf3jHRyKk8q88ZSc3l8lSS20qa9 +VmVrGQo2zRokW/ZuO9bN61QC6t8vqLH+1I4/Oj5z54VmAFtJ7oTCvKbL0Zs9g24O +F9Sftf1PoN5P+lgvc+r10OPUzmdzCAkOuP8E6cZUG6Go6KuYU9OF//rNP4Q51eM6 +VmVrGQo2zRokW/ZuO9bN64XuLHX8TC3s3b1uGPe+ujw= +F9Sftf1PoN5P+lgvc+r10DjWtjPZiHJbHB3vxWiN0sBmlHwxOQcu8hDX8WHkhpbz1wQMdmEM+WGDbT3dyclbDA== +VmVrGQo2zRokW/ZuO9bN6639zDKuATNNI1Q9z+5tQonkvQo6FgRE5j7xdzZ42nJ/lORyMCWm/wmnAOaqz92/GQ== +sobCGpmMf4/g7+HpPqBjC6hatZ6rUY3AAzqC73FJ58Q= +VmVrGQo2zRokW/ZuO9bN659qpiza6mT3x4YMO+6u+7sN6/jgn1zAWO8Fa+uepsZ12GMbwXphSbiRomrqpihvqJtjJXmbUczu8tcAfkU1VRGmOTk1tRp7A3GYuk/UIEJpWyvivkPpiXNND8vshaZd5g== +1u+XjG/2+GSQRv6EzCaWRQ== +rWUQK5drq2FAykvoqvdEF6wWd7LjE4kHgkD92yYOWOs= +hAMG91uEKNfkOfEEFCO+FK0iS+0JGy5+jARrADnDEa32Uwwylxze//eg1DVXCY5UZ3UEnExsn2/G7vh8hVbTiiy7fbfiULVIcxktbE4qc2I= +VmVrGQo2zRokW/ZuO9bN67R1DwALFuRG0kM6YcDyBBen891nAny/jFMWmWnUs5Xk +VmVrGQo2zRokW/ZuO9bN64vCg92/s/g+NlYhCZamCQQ= +VmVrGQo2zRokW/ZuO9bN6+fwD8q80qE6VL1CccMoSNmddfkxATao7BBHZ23dmm+dVGm8NaGh/D9nozgZwu6iSYbwk0n9GtiCOjFHgzFtCpbH3+d0wZKnx4RoydMBeyBbiC4r6YELRejWrnlHc+Ep/G/AZY3P4OKdprYZDVlosTA= +VmVrGQo2zRokW/ZuO9bN64tf15HGxAcSa9z84GJV9y8= +VmVrGQo2zRokW/ZuO9bN6xkIM7I1Zmovmt3Y/W47TxySobbDNMTMXRX8veFk2OcdMhbLUcmcmpgjhM8Na8h2efDuZ9bZTQpu/Rtis2UquulFC7QgLTgzcv1Fhnmv1p7L1oI6cjPZK+PrntXES6YK4A== +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1u+XjG/2+GSQRv6EzCaWRQ== +qWJGUrAouxOI5V0qjzZzNbr8eYFMpkg05uX84QkJkgmKptzv9ua2VKBjG4gtNuCP +pL3S7PtZieqNcOPWW1Wy+Mm7q9BQIc0sQpM7UAUeNTM= +1u+XjG/2+GSQRv6EzCaWRQ== +EwP7AACOVTDx5AWnAeeGJ8tasYoLLkoLzEywVuXVV8ABaGlOEOidBIf2tuEyZkNO +1D18KWr2hdVsBcdZx1OaPcbLFHQ4KhN0pa1s00kniNA= +VmVrGQo2zRokW/ZuO9bN68Qt9FjfzL4A+CvxKkSuuDddZb9mZOEJg2J+h2XvUCxJ3pX6wIF06uaCgRYRBLRJ0Q== +VmVrGQo2zRokW/ZuO9bN65ijqnEyrvIyAVuIqxq4rQePVAVfCKXoOne7xnzZp5/m +VmVrGQo2zRokW/ZuO9bN6+fwD8q80qE6VL1CccMoSNmddfkxATao7BBHZ23dmm+dVGm8NaGh/D9nozgZwu6iSYbwk0n9GtiCOjFHgzFtCpaI3Kw98MklI5pjdt5Q6CIRfgojQbDvoa/J2SE1A6cSVQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69ezyNmZ3pvZTpJb9DT/bjOuH1V3vD5arhSOiaIgRUpcXNAuyHS2zmxk0TSStp2Z3VIy2oVdAkgS3Sqr97fIvEM= +VmVrGQo2zRokW/ZuO9bN63bmR2XEvZ4L8bBhKPo3OQLTqbFd6Ae79tpkaZaBPMho +VmVrGQo2zRokW/ZuO9bN69N1o2p5L/ExX/nsz6f7VFFh4KJh6WnruPKbBhGPrAZ4LF3bWZTGf7ODPGMQS2L2CA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN69eui6a1YbX7OrkBUaCzmLfYn73897Y2o89d9qvD2Buc +VmVrGQo2zRokW/ZuO9bN69r29wuoFWHhjYiqT94OnmuE8uq5TDMbg+TXvfMmjAuymzswJCGcu31956jb3gLsgg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN650Weibqrj1zpZBud8Hh3QBuuR7J0JDAtVE1TX/2/0aP +VmVrGQo2zRokW/ZuO9bN6/gw6K9Dj/fRRsfPo2yr1sO6xMQpow3e6uvawqtUyTvc +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64bUSgVrpXojaBUFhIlsUuUH0OEUdsr2gEPd2WsWDpG7 +VmVrGQo2zRokW/ZuO9bN6+HkCu44TJXLDmAXqHXxhisd/D/DeG+UFYK+iifh9lpclsrbXug8dtoRGGnrw8dWZw4dT7SB8CtduTBEdzchxWSBRZ1pPQ/xlUFTaJYgh4UzxwqXXJ8rB/G8x3gCls3eu0lHYX7/sLGoZK8ZMnwjo2niOIahEUotY9xD/+yc0Etp +VmVrGQo2zRokW/ZuO9bN6/6TnYYUnVY4bZC18wJU6T0= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6113zUljeX9o2ixJP/gnH7LaL606P1ZBF0ApW3FjcwLj +VmVrGQo2zRokW/ZuO9bN6w/uKfdWuN3PSSKvYL7HvZvEZtmIGDE8QXjplmQREyZd5Jt2NWUJ4p1Y7zQL2Rzj5bHnAt5nVS0dmeiNa8rgGSE= +VmVrGQo2zRokW/ZuO9bN6y7t+gxoP0rr0hobxqEQxMV6sMQGQaapwFJ+dMIXisWN +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN68Z27ko+jrFTO2jEAZG6Hd0IKAo0cF5PrRwGYZwZusNh +VmVrGQo2zRokW/ZuO9bN61aNVJHNHezpHwuTzqAZxa3EWynVWi45cMZ4zVY4MdTvgAmiSYU8T2nhMDTBDGpi1xugbM/s23975mmW9xs6yLaXlILHKcdjWYb/LdBQYTllE1868gW2t6qrJoMETwQfLw== +VmVrGQo2zRokW/ZuO9bN65tUAmzXVbhX4oRxj91LK4yLExrPpaMCGO47d/g8kXWh +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN68GJ79NnzQlhumBucX2yu0w7h/u7IIzNGkfI8BE4SJPayPdwml+f7L3rq2aoaAmU2TFvl6W63C4k7u7Wx7Zh2Wc= +VmVrGQo2zRokW/ZuO9bN6xrw6sIykZVlpj5qj21yDJxBELIrBcRTMwtecqgfrvXahlRsktoFJrTTujTkBIAoiw== +VmVrGQo2zRokW/ZuO9bN63tULs45GHnXM3NqslS6LEBz3nyDgu52+Tsitysf0QZf +VmVrGQo2zRokW/ZuO9bN66VrfdQtjHzqiJ8BALUrs1gQLJX1wN9UiEeNoZc8eynMS4OApEC7DwmyjDyZ6LENYQ== +VmVrGQo2zRokW/ZuO9bN66M5iubTg6dStDldTTJLmzOabe5lXsXe8id+7YhUbGvC +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6/bQaA9LJJZW8FYc3p9IPrclO+QBQrl1iB/ZfHDCnfMNK8+HzIy/06dNIxpovmSwSL9ek3VWTeTPeewVJQ0ajis= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN625yrsrHJv0V0Hwm7FOoKjMr8kVSVbN0F1GQPCkOFsLt +VmVrGQo2zRokW/ZuO9bN6283QTUhEash6C7foDlF++oHmO3XQ0iVIG6KVSmKS93o +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmGVADbU4booSzUdXZ4GUEguvg8lJAwadAJSkwxJTBUFA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklTMk1Q7TBsNExASi5wYm4q2NZUngXyA/qnkTRODkcT2rXFwwSzP3pTnp1TCeViSUk= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl1VW212KeLe3VRnhjOZq3zwnR0k+VkoaUIGp7kBt/7yA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm8F5JEe+U2jlpN6wcqTIdB +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62knPdY6gJAD0N4C9oFG5jjFE5e0Mg9EHaZI0bGTZiTvia+sZP3mgeE7PSkqsMhBCg== +VmVrGQo2zRokW/ZuO9bN63LTkM1jyAeOymc1jas3GbfGMxfBlK4yuq8zmyDwO7H8kgPWvIbT8OxQflnzbQpyWTyNliWGe70Ol2nLxBj/8o614j0YiI1OP6I7dEmJ+cXgCV1F6ZRZ8wjAJGdjkCHNpw== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklzREPECe+UKwV56rejoQ6v +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1u+XjG/2+GSQRv6EzCaWRQ== +rWUQK5drq2FAykvoqvdEF6Nj2zy9yrHRoRHq+fx1eQ3fg+Gjp8wUR8okTW5YANCa +hAMG91uEKNfkOfEEFCO+FHZFLrW1eiEP9aPK/bPVELZugQqC+Ayx5wGrmA7ySAfR +VmVrGQo2zRokW/ZuO9bN6xkIM7I1Zmovmt3Y/W47Txw7vYnaH2F+/diM3U7W9Ag+VB2tiqTKg+iwzMiTXdBhvB27uDbEGVA/s3PoXQKaimqNGqngEdX/9SuOA/QMMdh6cbECLpAf6I2luY/hUBPVnA== +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1u+XjG/2+GSQRv6EzCaWRQ== +7bJ3Uk7aqJsghutxBeStwoenI08pUWSoM4N7sSAouEtjh+QS1U5nuyJhxZiIhpSI +yFgeqfMAgEJgI96oT6TzlCEDLfDNroEDqZFIIp32DMJIxdgcrgg3B9rdn+GxNmdnpPIoMTbNuc1y4s868VOQPfREj2g33+OAiKCGorTI7np4tkgOC4hVOIeCd9zxT2hS +1u+XjG/2+GSQRv6EzCaWRQ== +LGmSsHqujAfmhwNEd6RJ+GmsyXVlQMo4npjoyzQ9KVg= +qBwwXitZ6fOSGblD0bzEzzRm6ns8kU1RkVj81hEHjRE= +YZ5nkj7LXEY8vgx+ejNN3F5X/iIwOnVUJm0i5Gg4NMHLl3e2vTbzlZ8Gb1qqwmF4 +1u+XjG/2+GSQRv6EzCaWRQ== +7Ve/CPYxbRIq/L2vO/1jNQeS0Fo0VpZMr4nUR1PVEumsw9imXKwrNof2G9CctIO2 +9zgnmC3+5N9XK5/NRaR4SMCZ85P2RzwaGK5SA0vBOslCjy9ZmcAs3KQ2NtaSwfQA +EwP7AACOVTDx5AWnAeeGJ2Zsz8LDAHir+G/F6YdEjBz9n0F9mAAuiGt8X314+TfU +1D18KWr2hdVsBcdZx1OaPTohP4zASL7n5IxKaRzFUvefWqcLeMNWReV686Q0FGEM +VmVrGQo2zRokW/ZuO9bN6/vHEEiMOBtCIlg1b8xVuig= +1u+XjG/2+GSQRv6EzCaWRQ== +fsjjo9JMj+4JWdxlwkj1H6sXoRH1lwp8SiB+9r4aFoDyAJj9l69OTq45rHb3H17R +1u+XjG/2+GSQRv6EzCaWRQ== +P1bBdERejTBi3ynjwlj7jzyBMGnK/wy6ECH6atxCqHX+s9CqS5SvzZZ7qXGoPDFb +1V2v8QerKOmubvSxgB4eTJ7mxqKaHgZDk0TCKmDxab/i7UiTimIyWpTvPeWGcxSx +VmVrGQo2zRokW/ZuO9bN6w6Z/TqX6Wl2dt8y89u0Y8A5fvocduAP8ymKlzd7yd2c3l78L1+lgP1PDHuclUTZRg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6+h/Yx5zTOXfG2zCKV0c8HY= +VmVrGQo2zRokW/ZuO9bN65JUC//yK7sSrpv1nCoiFRgR55ff6DKxoPHhk5usJx4ABjdpJH6whWXq6v6x9SZDFt0eN1VSfrHgvSmlgkspxH8nUpgMwAlTKBaQQH6rQTe3dUR5To5zoruJAG/AXkwC1AezUsfJsrbjt6YyCq87Kbo= +VmVrGQo2zRokW/ZuO9bN60Hl0aNCqYkJum1dslT8fcgI2FgNEP77JyWsgMf7Ze0N +VmVrGQo2zRokW/ZuO9bN66rIgoW0pjLA4ZrmefxAT7FEmwWt4kUPVWjixEuCfAhs +VmVrGQo2zRokW/ZuO9bN6zWChYLPlHamJFV5kS2vr8eccn0RUBVPyzIUacZ8cLtx09HLs05RuP3IUPIzFhMi3g== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN67tavF5vvboi8AuT9uuOo4jN1a0eFrjxRsSgiiE8G9nd +VmVrGQo2zRokW/ZuO9bN65JUC//yK7sSrpv1nCoiFRikDiuqro1UpUyozvyAERApVod95iuaZ3EToM9uBv33irNb/rmenKeTRyzJgCIW4//NdBYd6UYHgv1j8KTQrljmggpfyFTWVa2XukBRipHQhA== +VmVrGQo2zRokW/ZuO9bN60Hl0aNCqYkJum1dslT8fcgI2FgNEP77JyWsgMf7Ze0N +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN65QMABcAGQGqWS2bDDTVP0U= +VmVrGQo2zRokW/ZuO9bN67F9GiVmC3C5V+1yVe9fw/qyhwJ+i0/o+6252tPxa0UZFnD4giSgSuQ0EHrgxn/Bag== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN61aIVCtFWfxi0nFWfro5cfGZmqCuA0Qw8ghbwvTZCMFEQvwlOEHTc0ivdB4oX25nh/wlIxKoovR4X8cI7Nn6w10UTTfw5KeazVcPa6A2plXA +VmVrGQo2zRokW/ZuO9bN68nP/EVKSwLU0SzNmqvOcnjaHw8sK/A3thzVqcqSXyMe +VmVrGQo2zRokW/ZuO9bN6+7S5/1zNa0L7V/u81W4gn/YEoqcJwrT4OSZThBu4rjJfbXGfDcXeKnLQ6pGanw0Ug== +VmVrGQo2zRokW/ZuO9bN6wesJclK6hOBZiYJ2iVGe8bQj9VqOl7yZLKvFfO5uA0ordgKjxk+YGna1voajuWtOQ== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6y7t+gxoP0rr0hobxqEQxMWmZMJeuUJOrzc0ZIHIhLoI +VmVrGQo2zRokW/ZuO9bN65tUAmzXVbhX4oRxj91LK4xkgsmYb7WSC0w0hE0rMDNW +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6yyFuucItNMqkjHU/pLTjeNNW1EGDBzHTHdjw/btxqOf +VmVrGQo2zRokW/ZuO9bN69WmSOt0p25HK+6oDGX0onDmIbjHZglYciw7pSM/Ax0F9PTT7ldUlOW3pMN+8lAPAFV3roUCy/UVmTKzy20yWbs= +VmVrGQo2zRokW/ZuO9bN6yxribns4fsS10IzsnXprQ8cQ3YChjMu1EDJkvreQ8Gtjs7S5CMc+Dwtj1HGJt1TwA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6w+74ARNR31sZhmaCuAMwsnKN/MnD1BKy565H23K8AoA +VmVrGQo2zRokW/ZuO9bN6x0LKap7xve1BUCCCZ/ZM9+B1+2mcJDoFZ1qAcdfRoQmlzIZofQopeEqOQmkl4DirbA0kyqPAD67+3O4Nym6xl+Xw52AiB5HdP+FzZ/gbsLUuyL4YDRunQQ1Li4gL1h2Ww== +VmVrGQo2zRokW/ZuO9bN669R7mdHpygJNR1k/erSr8ba1V+vnWiGgk4PWIWLXymYT0CQ2pPCYGGxLlvYfwaDNg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6wRWLqdJyjJ0e37jNW4UHPtkNfA+fwtgEj54x943BOGimir/JnsLnQzXetoOQlv/Vw== +VmVrGQo2zRokW/ZuO9bN69WmSOt0p25HK+6oDGX0onCGMOSiXbzJb3PVdPrcevf1eWbq9rjIwUO2VrcHrAZC/A== +VmVrGQo2zRokW/ZuO9bN6zatYZyKtIQNclCsWFRbkxqcYHdny41DrJbTs//IH0QNN9Z+Pab/Si+tGBqs1nrMNg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl0loyRl3Y+2lMvxKZrsSP4mX0eu+fncq0R/Gj2sMxNtg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkm7B1iycNQxsaqQDzmbZTu3A/QICa/qVeUrppmsxrFqmg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklGFrs4gwGXvmp4BPscyldS0+7KEZ325wQEwfOSBU6xppAdoh79sA9sJGXSncT3qcs= +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn7riDEdBtVgGPKUzJw06A1 +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknxuDY0yMUd6CtugRn68diS7IRPBwto8KUITtCHjzsVgg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HkmyOHs1nXMxTF8IZWi4vGdIu/dwsvSGUeMRnZRjGJJ/rA== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HklOv3JCeX9TwkZ2qOV8fdCeOA5GFMg7l9EKy86Bo3BHEtBW5iRaGbAAysxJvejkA1M= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkl44hEndxWfoKun5ZaEbUMH7GPrgZQODWEVCSDVK4quyg== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk1A7YjKzApThJwBqVzLaWq +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkn6VEPmWYcJhg8lgmV5LgMEWpJARBLApS9tW/DU9OLvFg== +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6Hkk/Xr/2ghELRNGM0Tnj00JYzXj6NQFPFGdK04zU180w8or2N1YqsnGnkPmBi3iiKOz3Hb5VN5RzZz88iahP5EhZIiY/IxHFclTRwrKa6NEYy4aCInm+rtS6Kh1UcX7Akxk= +VmVrGQo2zRokW/ZuO9bN64ZTCtjiSIe1Vj/8GYr6HknHU9sbiSXnkJH2Ine9gQMa +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN6zWChYLPlHamJFV5kS2vr8eccn0RUBVPyzIUacZ8cLtx09HLs05RuP3IUPIzFhMi3g== +VmVrGQo2zRokW/ZuO9bN66rIgoW0pjLA4ZrmefxAT7FEmwWt4kUPVWjixEuCfAhs +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN62hT4Us+sf3STuKUPNaNAmwVCNrYqUsg2YHsybMZByDD +VmVrGQo2zRokW/ZuO9bN69CHCfVBuJ8M9my8n/THYQAAVVSzed4flp6c/dEcJ5QmmO3PuHa6h6i0NGmizE0IkA== +1u+XjG/2+GSQRv6EzCaWRQ== +VmVrGQo2zRokW/ZuO9bN620zSkiHBXev6YTvy3qjpOw= +1u+XjG/2+GSQRv6EzCaWRQ== +/R3RsIyyuJzgF1BbtmZTiGOqRYPC1wSxCIUPgAw0AXw= ++pBWk5LxptzizUTsHnZAz99i/5vNWptrZ9p5u7vIcfi5qEc39vhVrczSNIQsTzd1 +p2+bVXgc/OGpnu+3Mkh/HWgCi8l5R+8ciwkTACh8s8pCY45MbTxrLbNgCiSPXvng +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgZS60kNIgA2GGL7NP/H70aZ/rEh3QTPcRuOhpA401DEz +1u+XjG/2+GSQRv6EzCaWRQ== +SPGIcLQvfIdvbEm4bSwFQee10PGHfW4lbbTEmggKXRk= ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +XIS7WB0EDgFbcOJd3ow5ksYQUhL1QPtlxmX0lZI5T6UeSzkMcT9kwZxfgvYXCNqU +Fk6c/WQWq+cjUfytKivXf3LJLCykmIxL10jYThUpGn4IB8gURfF+sNfw7Gr2p7xE7RtBFpXdgfnQ+F9kQLtr0vSs7TodrIZHgl++fEDZeP7W17KqWSg3tsEvOjGupuCyE6D3X2uU7KapE9X26cI0XA== +tyk4qsf5nvIeNK9AIRjxVKtCCQCkuvSRzv5fqZrIqlg= +1u+XjG/2+GSQRv6EzCaWRQ== +ASbBxj96B4nLdItQB6jdDzHn62WA0u8GeY1oMMA4+/Js+QppsP6IdKCODXPagf4J +1u+XjG/2+GSQRv6EzCaWRQ== +/0ULpLqgTvInFD0r5hHANsVMQyYMTxOCtjrehT+unwA= +1u+XjG/2+GSQRv6EzCaWRQ== +9zgnmC3+5N9XK5/NRaR4SAkovFAqchP05HrrVafSP3Q= +o6QhOIN2Sc4SHELnst17uS1HqGOIMdnzZRPat3BllvVCeJ1Z6uEV9DN2tsvk3F0e +1u+XjG/2+GSQRv6EzCaWRQ== +ruTTNFTdswRs4Mc+srnRk818d9d/TGGXzgPjSrVtMi8= +1u+XjG/2+GSQRv6EzCaWRQ== +YqxtfS4Jl1CuIMzPO0zZvzYpKKy/M6UA7pvbZGxgUhn3deCKXI/5yLc9AmKyIV2O ++qdJgONcot1WKgm0a/QSWRfjD/63UhGjDDaD83ombAk= +YcpegqjyUjgt7VJT3/cANyAV3hppZQCGgDoI7Ovcr55fV+vIFvz1cOJe/qWkGyWXcWfvKt2XxZY24ZrY4Oajp3DLokK44fyb03eU51IiZzvI7MJ5Ov6cV/A8Dt/6Q9C6yT5eVkPA5RK6ZcGCzRj0QLNg+gMJ+XS3AWD2knLUrccZwfl/VpSqZFMYZiCoigD+SYKdAAe/vxlwVnS4JEJpdg== +q4nc/jwATOMUyfSjLibfEZ4XjohyYx40xFfjkATboNc= +1u+XjG/2+GSQRv6EzCaWRQ== +b4OJVZe8QyIpjuTpKXDL9A== +1hs1FfIPISXmd7TJ/r+VlwAvPWZGdqAvmnIAjKFyuOELw291PAhj1V4Xxj5g/nqdLmx/rPGpdKS4yvHkefKp8i0IaPXFt4dxP3dbflnJPps= +VmVrGQo2zRokW/ZuO9bN60Gth2ckbJATM/BfrvUVPmue2p+WofJm/WxwT7mqVqTvgLK+8t4OFQLjOf5z9aRHWA== +g+Jm/FxeTBTKtmon+biYfZROYL1BhogZsAe/p0Xuf+OUExdOCR/cA+jbvzjhaUeV +AtqIw4BuTOuXet8gp4hSxawGcJ3LMOV8osinYrIlYbs2j0TVHQsHVjjAeewMAVb69ISskpQyRuvAetXmOzd/TA== +J1UGlXkKhKXD36OrXXhN8GGOTZOUFHzOMTHrRyVHVYZ5816/SOJMs9TyaFp9Axdr +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpQTcVH02FaIC+n3BD2EZiZ7GdVRqNxGvbDvHwy4TqkEQ= +1u+XjG/2+GSQRv6EzCaWRQ== +AtqIw4BuTOuXet8gp4hSxawGcJ3LMOV8osinYrIlYbs+l4h2IfleAKUNo0IMzhsNR678evF5KCIVXdg2jUlBQg== +J1UGlXkKhKXD36OrXXhN8GGOTZOUFHzOMTHrRyVHVYbwXDCKseAmFmmaGUxzaTCb +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpQTcVH02FaIC+n3BD2EZiZ7GdVRqNxGvbDvHwy4TqkEQ= +1u+XjG/2+GSQRv6EzCaWRQ== +zYuCrpMrZGCN996UA1dFfCqrlOoIQdYuHJjs/EiwTAjnR0eLJq4Yq0OFR/Vezo7BVoy78uRiTBjRiW0lltTvSU5jvePE2Js5VQGPu57LdAeeczfocTqtzjmnRXzsdiexBe2Lj5Gkzq2Ivs9H2fzp1w== +J1UGlXkKhKXD36OrXXhN8GGOTZOUFHzOMTHrRyVHVYYdK9Onw9+O40QawqeKhZFY +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpnUPZP5I1ml+E1aCJdhd7Cr+eL7kl8Hn422yZZO07Uf3sg9l9R9yP9YHxLqn5Fczm+zM17b5MeGui2pJw7Da9eQ== +1u+XjG/2+GSQRv6EzCaWRQ== +YY5wEo94l9qfo5TPxQmUgoNFl2Fp8+FsNDKaanjjEPI= +g+Jm/FxeTBTKtmon+biYffaq+RoNyUuVQpnhzMR9ZI9ada3yog46BhaKJEyY9rrm +zYuCrpMrZGCN996UA1dFfCqrlOoIQdYuHJjs/EiwTAjnR0eLJq4Yq0OFR/Vezo7BVoy78uRiTBjRiW0lltTvSU5jvePE2Js5VQGPu57LdAeeczfocTqtzjmnRXzsdiexBe2Lj5Gkzq2Ivs9H2fzp1w== +J1UGlXkKhKXD36OrXXhN8GGOTZOUFHzOMTHrRyVHVYYdK9Onw9+O40QawqeKhZFY +VmVrGQo2zRokW/ZuO9bN6xCz3gmXMpXHlLbeFcnZX8BjeW9DGnq0/peKsi5Sk+PpnUPZP5I1ml+E1aCJdhd7Cr+eL7kl8Hn422yZZO07Uf3sg9l9R9yP9YHxLqn5Fczm+zM17b5MeGui2pJw7Da9eQ== +1u+XjG/2+GSQRv6EzCaWRQ== +YY5wEo94l9qfo5TPxQmUgoNFl2Fp8+FsNDKaanjjEPI= +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z7Vc7221CfIz8ZtOcaiNHvZkZy6HmpWI+rolHwFXNUBFEpU9CY15Oz/Aa0LvqSOT/n2OtBHgVt7X+g0nKeI9QJQcntqrGdqnHB792/SuaMskNbTw645/oWoVaxxDSribyqfiqnQa23vYCz1caGrcuT7 +1u+XjG/2+GSQRv6EzCaWRQ== +WxLCrccfrU/GbYaQM0CttymCEoSSL4XQ2dUp46xr/R4= +B5OpQk9aNCq2cqtWnot+0ektsSR04ZQDuLGC6CtaDUd1iC1qQLD2uvrnEKJf7KyiVEbSBuQTlVXOMFeYjT7NSvHdAvjLAnwJW+s1N/peQUo= +NX/64OOR80jDJXFsjw7Fd2YEHAapydu6aFaFkA/faxiitqfJRm/RafF4reJcLKcr0FB7azaNeAGrQBvvmtKR0g== +1u+XjG/2+GSQRv6EzCaWRQ== +lSUcoY7WGh25lL+WkZ2yhFKeopk8TTSGyYLCojtKOrbHs0ZeC70IpO+XHxenHz3VBvV/qqAXWKrRXX1CpTy5Yg== +1u+XjG/2+GSQRv6EzCaWRQ== +YupK9RnBtGWSJ/bJnNE323UsRUYz7fAl7U+FnhNXx5NJzsbMCgA5QSLHBAut1/xN +32pdC9DD05OE2l0oXazDFKrh78d0BRqSJX0q2CkcDE93BpFpOsUT0ysr/XrsMnFYOEkaY3FIqaqavCLBkH2U+g== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgTxF21Ps2kmxgSF7PHkiu0MGSqzva9k+nwV7qCXvJa+3aB1BQ7DR77CAXbylNAQwjg== +1u+XjG/2+GSQRv6EzCaWRQ== +CtrEhNe2VFAiMmP1D651CXARpKnz5L5gnl8P0Cw9Nf71zlpMlCHzn5h1+ygqMJA5 +NQncuz/16VbFlhzZ0M9aVNMjHZ01MIK51TBvwohq/aRI5YtMYBD9ZDDuLopvZpD7enL+ip6O02WnELmwZzpDi07sEEiaMnbwiJ5JT4pHO8U= +pHO5j+XfjoQBHQFq+CDhtnG6ZeQfJihks/j/OzQePRsiP9KrD2nqkoBypFrGLzsI8uu9YPme2V2eyTJAYhMGv5+ah5IuWHC6xlz/w3lz3/74aAiQRAfMoH7oj+Kms0CK +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYidWX2gC3fVamYQXJ9ey9xQWt939cRs/gIoasbB0JAK0 +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcIuWcW1HaUBcTUGockc85s1CH1aR+7H9LuAtylnJJMAMSo6nahEmsxkN44zkjC9dXVkzfjHBfBrf5QGp646Bz8Q== +1u+XjG/2+GSQRv6EzCaWRQ== +4+wKB+Tel1iow8au3cOIJeDFU2Q2B3JtrpibuQEbYEQsv3h+Pn1Sph6+lKk2YYEZoZ5M40Os/ejo6Vasw0SzwA== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgV5MR2wpcZ2T3AJHGErCLIxvBJzMyE+aTyZ5n3RXBFa7h3v1HmAxsqWfbDVied+W3MVE+u34oT59Ro+iVsfSQpX6SK3qe3xEyraF9Uiio+M0 +1u+XjG/2+GSQRv6EzCaWRQ== +csLthA53ojJM7MvqrKXR/JMLDLfhogJnpxuetF5T7vY= +6Pn8WKJkUFkKq6rlGCHZR+o5YGDgTV8miYZLyJP0GtH3JleoBl+kgxNFB49Y+n6XiUg7bE1b429A6uNDgX9thw== +pHO5j+XfjoQBHQFq+CDhtnG6ZeQfJihks/j/OzQePRsiP9KrD2nqkoBypFrGLzsI8uu9YPme2V2eyTJAYhMGv5+ah5IuWHC6xlz/w3lz3/74aAiQRAfMoH7oj+Kms0CK +1u+XjG/2+GSQRv6EzCaWRQ== +wgR07xfoapmx6eEnFHXXYidWX2gC3fVamYQXJ9ey9xQWt939cRs/gIoasbB0JAK0 +L0eUthVnpkGsmKFAX6d+uJFrCS/2hF2imwgPWwEHOkvCs01Ef75uP/SBKZUAi7QcIuWcW1HaUBcTUGockc85s1CH1aR+7H9LuAtylnJJMAMSo6nahEmsxkN44zkjC9dXVkzfjHBfBrf5QGp646Bz8Q== +1u+XjG/2+GSQRv6EzCaWRQ== ++pBWk5LxptzizUTsHnZAz+rqReKZ+A+992491ivMMGOTakmdHhRY+LzmYCN+4Pu0 +0976Vy/lfkoO1AbuRG+nV0apXetJKv3OodW/dljN/INIGDb/zuAzFLXnaYu62PSZ +1u+XjG/2+GSQRv6EzCaWRQ== +QjEYNJp9YKIp2aaVIb+6fxAxGWjlbup6X2xFhqc4V37TtVEsRNLZDsrqifdWwuU9Hl0/Mb7eu8BQqjySX2BjJOWz0WPOpM6/fQ+oRDRutdyw5Qdets8ldoS+RFYb17+Ny0tjX4ue1UBrJYIl6jAMKWdXCqYaw6N1+Ja9rDImwuQJPqXXFIK8i6Sqs96664B8 +1u+XjG/2+GSQRv6EzCaWRQ== +4+wKB+Tel1iow8au3cOIJeDFU2Q2B3JtrpibuQEbYEQsv3h+Pn1Sph6+lKk2YYEZoZ5M40Os/ejo6Vasw0SzwA== +1u+XjG/2+GSQRv6EzCaWRQ== ++pBWk5LxptzizUTsHnZAz+rqReKZ+A+992491ivMMGOw4uN0RmY6FHaPfgSPvDAH +p2+bVXgc/OGpnu+3Mkh/HX0nh9R726Gt/6kJ6Z8jC4I= +1u+XjG/2+GSQRv6EzCaWRQ== +4+wKB+Tel1iow8au3cOIJeDFU2Q2B3JtrpibuQEbYERdXH1NACiaD4lhdcNmPcy4tzPE6sUzhYykOaufrGsnmA== +1u+XjG/2+GSQRv6EzCaWRQ== +96orka/uERLyRst14azQwomqJXk08MT8ElCYPFEm1z46GfCHZKttxdYHc80Wh2zA9fcGihKrecOIC7ra8FlSgRYzwHLo9JtKhRtTS3Fjr3IJMH/yCG7ebNQo8ouASR8TymckgcJhWCYtdsr21ivlHoH4JoegN0OzbyP2ILL+c9I= +1u+XjG/2+GSQRv6EzCaWRQ== +X2wedBXc8JweJQhy5Mkny6kgVWWDAcJUfZ0tRXKZ1QcENA/FiBSXZ1NGld+Z5g6F +JBUvLRho2v4T0sgReac7edzubT6+00LY16NgjS7SFCM= +MVNqiWIB6fAQku1mTlf1Jxq+pMUfgeOXSedGI3zuVG6fEtfmYQ5CWzTFA+jTu2bi +b4OJVZe8QyIpjuTpKXDL9A== +zxz/Z9yjFalbpeH0OEk066ab1RF3DcbItEg0mv/cL8Yx9+E07lhARaUu01cucUxRGrRzIlk14o2iChsrkpCS+/4eAYj4epWkLH+4PRjsCayFvqF5Z/CSwgbfszGPnPB5 +32pdC9DD05OE2l0oXazDFHjt/ErrLQ9zO8Hn8/+OjTf+ZYkoqbck5z09Fea0uwJv +/9/zB6N+j20R9WpzrjSFz7AHCoy3iHwP7TIpB9/jIKfVWViFPp3zrtzJ2mRSV4Aj +jYPHQAqSoL3QG9u85m6Jg0oiDIwa2nYjYliF1PoBA2U= +32pdC9DD05OE2l0oXazDFHMTHwhviEdVnkMORuxEOpOD1JJJ1BRdiehEFmiWTs9b diff --git a/class_v2/wp_toolkit/security.py b/class_v2/wp_toolkit/security.py new file mode 100644 index 00000000..2a985192 --- /dev/null +++ b/class_v2/wp_toolkit/security.py @@ -0,0 +1,223 @@ +# WP Toolkit security +# author: hzh<2024-07-02> +import public +from public.validate import Param + + +# WP安全模块类 +class wp_security: + + # 开启文件防护 + def open_file_protection(self, get): + # 校验参数 + try: + get.validate([ + Param('paths').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + tamper_core_status = public.run_plugin('tamper_core', 'get_service_status', public.to_dict_obj({})) + if not (tamper_core_status["kernel_module_status"] and tamper_core_status["controller_status"]): + return public.return_message(-1,0,'Sorry, opening failed, please go to the App Store --Tamper-proof for Enterprise to view details') + + result= public.run_plugin('tamper_core', 'multi_create', public.to_dict_obj({ + 'paths': get.paths, + })) + if 'status' in result: + return public.return_message(-1,0,result[0]['msg']) + if type(result)==list: + # for i in result: + + public.run_plugin('tamper_core', 'assign_rule_to_directory', public.to_dict_obj({ + 'rule_group_name': 'WordPress Normal', + 'path_id': result[0]['pid'], + })) + return public.return_message(0,0,'Successfully added file protection') + + #取安全防护配置 + def get_security_info(self, get): + + # 校验参数 + try: + get.validate([ + Param('path').Require().String(), + Param('site_name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + return_result={ + 'hotlink_status':0, + 'file_status':0, + 'firewall_status':0, + 'file_count':0, + 'firewall_count':0, + } + if get.path[-1] !='/': + get.path+='/' + #取文件防护配置 + tamper_core_status = public.run_plugin('tamper_core', 'get_service_status', public.to_dict_obj({})) + if tamper_core_status["kernel_module_status"] and tamper_core_status["controller_status"]: + result= public.run_plugin('tamper_core', 'get_tamper_paths', public.to_dict_obj({})) + if type(result)==list: + for i in result: + if i['path']==get.path and i['status']==1: + return_result['file_status']=1 + today=i['total']['today'] + return_result['file_count']=int(today['create'])+int(today['modify'])+int(today['unlink'])+int(today['rename'])+int(today['mkdir'])+int(today['rmdir'])+int(today['chmod'])+int(today['chown'])+int(today['link']) + break + + #取防火墙配置 + result= public.run_plugin('btwaf', 'get_site_config_byname', public.to_dict_obj({'siteName':get.site_name})) + try: + if result['open']: + return_result['firewall_status']=1 + except: + pass + result= public.run_plugin('btwaf', 'get_site_config3', public.to_dict_obj({'siteName':get.site_name})) + try: + if type(result['data'])==list: + for i in result['data']: + if i['siteName']!=get.site_name: + continue + if type(i['total'])==list: + for total in i['total']: + return_result['firewall_count']+=int(total['value']) + except: + pass + + return public.return_message(0,0,return_result) + + #关闭文件保护 + def close_file_protection(self, get): + # 校验参数 + try: + get.validate([ + Param('path_id').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + tamper_core_status = public.run_plugin('tamper_core', 'get_service_status', public.to_dict_obj({})) + if not (tamper_core_status["kernel_module_status"] and tamper_core_status["controller_status"]): + return public.return_message(-1,0,'Sorry, opening failed, please go to the App Store --Tamper-proof for Enterprise to view details') + + result= public.run_plugin('tamper_core', 'remove_path_config', public.to_dict_obj({ + 'path_id': get.path_id, + })) + status=0 + if not result['status']: + status=-1 + return public.return_message(status,0,result['msg']) + + + + # 开启防火墙防护 + def open_firewall_protection(self, get): + # 校验参数 + try: + get.validate([ + Param('site_name').Require().String(), + Param('obj').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + result= public.run_plugin('btwaf', 'get_total_all', public.to_dict_obj({ + })) + if not result['open']: + result= public.run_plugin('btwaf', 'set_open', public.to_dict_obj({ + })) + if not result['status']: + return public.return_message(-1,0,'Failed to open firewall') + + result= public.run_plugin('btwaf', 'get_site_config_byname', public.to_dict_obj({ + 'siteName': get.site_name, + })) + if not result['open']: + result= public.run_plugin('btwaf', 'set_site_obj_open', public.to_dict_obj({ + 'siteName': get.site_name, + 'obj': get.obj, + })) + if not result['status']: + return public.return_message(-1,0,'Failed to open firewall') + return public.return_message(0,0,'Successfully opened firewall') + + + # 关闭防火墙防护 + def close_firewall_protection(self, get): + # 校验参数 + try: + get.validate([ + Param('site_name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + result= public.run_plugin('btwaf', 'set_site_obj_open', public.to_dict_obj({ + 'siteName': get.site_name, + 'obj': 'open', + })) + if not result['status']: + return public.return_message(-1,0,'Failed to close firewall') + return public.return_message(0,0,'Successfully closeed firewall') + + # 获取防火墙防护配置 + def get_firewall_info(self, get): + # 校验参数 + try: + get.validate([ + Param('site_name').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + result= public.run_plugin('btwaf', 'get_site_config_byname', public.to_dict_obj({ + 'siteName':get.site_name, + })) + return public.return_message(0,0,result) + + + # 获取文件防护配置 + def get_file_info(self, get): + # 校验参数 + try: + get.validate([ + Param('path').Require().String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + if get.path[-1] !='/': + get.path+='/' + result= public.run_plugin('tamper_core', 'get_tamper_paths', public.to_dict_obj({ + 'path':get.path, + })) + for i in result: + if i['path']==get.path: + return public.return_message(0,0,i) + return public.return_message(0,0,{}) + + + + \ No newline at end of file diff --git a/class_v2/wxapp_v2.py b/class_v2/wxapp_v2.py new file mode 100644 index 00000000..890d62a1 --- /dev/null +++ b/class_v2/wxapp_v2.py @@ -0,0 +1,110 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | aaPanel +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2019 aaPanel(www.aapanel.com) All rights reserved. +# +------------------------------------------------------------------- +# | Author: hwliang +# +------------------------------------------------------------------- +import os +import sys +if not 'class/' in sys.path: + sys.path.insert(0,'class/') +import public +import json +import time +import uuid +from BTPanel import session,cache,request + +class wxapp(): + + def __init__(self): + self.app_path = '/www/server/panel/data/' + self.app_path_p = '/www/server/panel/plugin/app/' + + def _check(self, get): + if get['fun'] in ['set_login', 'is_scan_ok', 'login_qrcode']: + return True + return public.returnMsg(False, 'Unauthorized') + + # 验证是否扫码成功 + def is_scan_ok(self, get): + if os.path.exists(self.app_path+"app_login_check.pl"): + try: + key, init_time, tid, status = public.readFile(self.app_path+'app_login_check.pl').split(':') + if time.time() - float(init_time) > 60: + return public.returnMsg(False, 'QR code expired') + session_id = public.get_session_id() + if cache.get(session_id) == public.md5(uuid.UUID(int=uuid.getnode()).hex): + return public.returnMsg(True, 'Scan QRCORE successfully') + except: + os.remove(self.app_path + "app_login_check.pl") + return public.returnMsg(False, '') + return public.returnMsg(False, '') + + # 返回二维码地址 + def login_qrcode(self, get): + tid = public.GetRandomString(32) + qrcode_str = 'https://app.bt.cn/app.html?&panel_url='+public.getPanelAddr()+'&v=' + public.GetRandomString(3)+'?login&tid=' + tid + data = public.get_session_id() + ':' + str(time.time()) + ':' + tid + ':' + tid + public.writeFile(self.app_path + "app_login_check.pl", data) + cache.set(tid,public.get_session_id(),360) + cache.set(public.get_session_id(),tid,360) + return public.returnMsg(True, qrcode_str) + + # 设置登录状态 + def set_login(self, get): + session_id = public.get_session_id() + if cache.get(session_id): + if cache.get(session_id) == public.md5(uuid.UUID(int=uuid.getnode()).hex): + return self.check_app_login(get) + else: + cache.delete(cache.get(session_id)) + cache.delete(session_id) + return public.returnMsg(False, 'Login failed 2') + return public.returnMsg(False, 'Login failed 1') + + #验证APP是否登录成功 + def check_app_login(self,get): + #判断是否存在绑定 + btapp_info = json.loads(public.readFile('/www/server/panel/config/api.json')) + if not btapp_info:return public.returnMsg(False,'Unbound!') + if not btapp_info['open']:return public.returnMsg(False,'API is not turned on') + if not 'apps' in btapp_info:return public.returnMsg(False,'Unbound phone') + if not btapp_info['apps']:return public.returnMsg(False,'Unbound phone') + try: + session_id=public.get_session_id() + if not os.path.exists(self.app_path+'app_login_check.pl'):return public.returnMsg(False,'Waiting for APP scan code login 1') + data = public.readFile(self.app_path+'app_login_check.pl') + public.ExecShell('rm ' + self.app_path+"app_login_check.pl") + secret_key, init_time, tid, status = data.split(':') + if len(session_id)!=64:return public.returnMsg(False,'Waiting for APP scan code login 2') + if len(secret_key)!=64:return public.returnMsg(False,'Waiting for APP scan code login 2') + if session_id != secret_key: + return public.returnMsg(False,'QR code expired') + if time.time() - float(init_time) > 60: + return public.returnMsg(False,'Waiting for APP scan code login') + import uuid + if status != uuid.UUID(int=uuid.getnode()).hex[-12:]: return public.returnMsg(False, '当前二维码失效222') + cache.delete(session_id) + cache.delete(tid) + userInfo = public.M('users').where("id=?",(1,)).field('id,username').find() + session['login'] = True + session['username'] = userInfo['username'] + session['tmp_login'] = True + public.WriteLog('Login','APP scan code login, account: {}, login IP: {}'.format(userInfo['username'],public.GetClientIp()+ ":" + str(request.environ.get('REMOTE_PORT')))) + cache.delete('panelNum') + cache.delete('dologin') + session['session_timeout'] = time.time() + public.get_session_timeout() + login_type = 'data/app_login.pl' + self.set_request_token() + import config + config.config().reload_session() + public.writeFile(login_type,'True') + public.login_send_body("aaPanel Mobile",userInfo['username'],public.GetClientIp(),str(request.environ.get('REMOTE_PORT'))) + return public.returnMsg(True,'login successful!') + except: + return public.returnMsg(False, 'Login failed 2') + #生成request_token + def set_request_token(self): + session['request_token_head'] = public.GetRandomString(48) diff --git a/config/GeoLite2-City.mmdb b/config/GeoLite2-City.mmdb new file mode 100644 index 0000000000000000000000000000000000000000..5a0a3125a4d5f35ed54af02690e61a7575c87d55 GIT binary patch literal 9723538 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 z

                                    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%0AUmfNbyM_NWTOkM#JgFhuH?fgtlk{!`R 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

                                    rfkdVF@!4f*51dHo+#L+A3&8xU-0$VLQP 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�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⁡r;`62i!l{0UpPlaQDF7#hxtd4R<#o4G`SDaQ}k) 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%0A1%o610#?oma1VxaBHV4@oCIeQoRi^% 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

                                    7zU=yG(YTyOo5m$HE~0Ug$dxpnry=)$ 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 z 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

                                    ?|#y>*`MGGRc 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ò=sT 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�jGXHb8E`g5jyK94QP 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 W*nN^8KLlzSP`+i)qvr3!Sk83Kj;;vPcV* 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

                                    +ZYqmWnTN_kROY2J zl*)Wm22&YCMb`Yqwnw+A6wmVOIh@K6Ey{1*4wYVYrNRZMEU3bwILw3#>#nwyMW`&Q zUqHyM)>x9t;wE2$%8FFfEtRFHEN3Q58(qfevTCB*;mT+&%Nt!Gmz!!OqbmzF)v8oB zp)!KXx>j6`%Ie0fpU@L!O}>-z zh5wzY?4lUO@2W!Wgx#s+$UUg+X_CFD>}#04sq9m9wH}@Q)TA&!fXcB{4y1Azm4mFk z2lqa-o0%U%YfGjm?`WmCAW29~P;M(S2FBji{VW<(xrd9-(rs z(esR+FLcn08I=pDTu9|&<1Z2_-$JEwiE8Cry=pa<>Ks%qqdFUv%c;tRFixwGO|vUk zP`Q$dIFagFRIa8HQn`kTPvu%FpHR7u$`e$sr*bcq8>rk&WxQ+^tGmTkZlrRP^7($e zm0PIXMdem1a&6v5<@RE)X44w}&JuaI6?OmTQgR=aN2%OT%g(zfhHQ{VP>T=F+a;sQgalPurDj zx&qZ@sV-OA4yU@j)@ZKG?TX45o>!*22Gv!ljxg1#LIq59HL9x@R%O6c*QB~uf2CB{ zp}HB>b*XMlRX+YMajs8Q`eB2-f#zqUydrbHiD5<>-E@GTQB=2~y18Mtpt=>+EeB|~ zR(_+qO}niyRJWtLgE8BybCv9v1B@R{^+2k!|G&hu3)Q`-?n-qJs=JwF_W_>e>sr>; zirky(epKZHTf*- znPV-^<22@?T_;ezn(B#EFQj@B)pMwxZ0$XT>gi^3s?pPgn*0o^W2l}<^{iadPpO_Q z0PFd4&E!0z=a*GqP%;-$mGmx~|4TfV7?p3w3orTqGGTI)lH1GJ?j-({>RkkKKHrTu zlIlIUazfrqwMz9qsvlF83-b-C4^WkJ^+BqSQ+JwC-r24dJi|0S7PSoX&d}XQnEY%lv&7}Gq)k#!ep!$6OdPt5j+n1=mOjVbCsJ@a{ zLG?AN|9$?a`X<%)sMK9ZeQ~k04=gTkEuMO}G)$gf(OZB^+qbF#1rZrXh|8Lcv|EZSG|5W=- ztA7Dgtq8R$T-+Y29@T%S`czY@0aZ!wf(fZcRBOf;>pxWG`;YzYlKd~QCe@bKo-Zg> z+j&=0?NI%LYDV=}s*?YI$pLwsRDV-H+Q#B3AgX^_@h_Fj0#x;HeXd@ae{pBSoeFn4 zt+EVwh^&S-LqTHKj%XU;LDWY(gnuH-i_?(Dd8;m)CQ70TT~ z?sM)^qK^QT=JVm&^PdG~e%wWI2jeb?JH*U~mR7yE3-lYBY9XV;a2M8gx#b$8@{8dv zZQ8|gmoP?dSR|OGgqBq=gKJOwxXa=$hr2xP@BvzT{-=m)C9_=_cU9a~6q7sAjv7&V zTOIdx+%<47#$6M4Kisu&x58Z;cRe#-2Y20mTio?=H^tomcVpZQaW~5SaUCPhJJul}=i9Y;;dm-*c{eg)J_Y&N@aWBQa1$Qj& z4Y-%#UWI!(u0HvLJFfI#&wm#2UyXY$?lt|caIeF?K1b$4-0^09Bd$ILR64o2)Y{^| zYm5Kx?YMX1-Z21nm*R`M?!kQ$_g>tGaqq)@z}()SpG&w8;y#okOZ+3akKx*I;7(9s z;pcJO^7)_Q40#IoCETZRC*e-SeO8dT&*XUH%jZ8`&HuPB;Oe+goW^<%ysTigdIk4Y z+}HY>V8S=>Bq_a#>)^hH`vvaXxF6%bgZmNgySVzFFYbHlM41n8KP)_$B&fuoFzu|Upf5FYn zq&pCUtIvX({CC{HasR;mv$VDRUugd+h5r_KZ)&{7@utC>3vXJynenE>18;iu=FOn^ zG8jA?3%r?1+gb2t$D0*zwgH$ja_Ks64m?Z#<^gYRy!r9w!5f4(Z|S6X3KXxWaH58T z@fO4zg4c^@`QPFf0Nw(n;X-(e;0?oDxW9H$;Vp`{SdJ-?OW>`KwLcjf9YiPl35dP9lW*h*3PZ+ z2=Ug%)3G3jndc3Z*LH1)w-MeZcpEDq#}ukf@ejt^3@^ePg?BIB=6I*zZGpEh-j;Zy z@wUR-4sUC`ZE{<@ZPjz(VSBtC@pdTOnt!}v6u{dVPqRJVE_l1*l@EUwwtL|1jkhP> zUb(k|YRonSsNsHihvV&!Cks9zKLAhje_6W(e<WXW)&&I}`7$!ke|r z>azT=e$K@^AMZTzSw?jMUh(`t-bE_6=HOj|cLm<1c$bL;Z)_>Q9BFyS3uCyuEmTC}Tn5{e?dh z-rxAs;{Ag^HQvAYQ{{dNZ@$g{bG$ztzNUTr=?f1&_%r5wiJuvN4*Xg0HS^=oS|Wv? zJ;#)iIq~N_}k#Gj4!Ep75sJaSH)iwe+2&Og27*{^tQ$T&uigZ{+A=}uQR~gdia|5@z*c? zZ-~DM{zmwg{L9!3GqS{NW)+Pxx;g$<_*>wY^M7lLskSa7*%p5f{O#~}#or!ZvpxO} z_&W+yVn&;AXZ&6Ig{5jYt6j$h!|aK_FaBN%Q$Kqf-A9Ysko~N`3;rDZbMY_2KM(%`{PPP#I|}$07PV+IFUG$_ zwer(5YApVh_%;yum*bBs>$;*a(ORy;zs{<@8vh#nYx}*K@Ou35_%{r!(d0MbKZ}1e z{-gM};NOXFLx4KG4gYrhJO1nEF8urP@5a9u{~lFY&*0xzz|`;oe2H1}f2n;K|B<5V zDLpv>|1tb01%v-MzU6uCU&4PuD^fo% zDx)3vvK3#!e>Ins-d@Ll2mcNHxA5Q0FL?#1?c4uFzK1^<|9$*V@IS!+$S@z~evJRP z%ov~Ie~xcMfW>UuFLH1AU*g--{=VJ?;D234@-4o;_J#jlDfvN7ik$EhzNFKi@!R-) z_?pl09sCNuXM7dkRlK#=B)(A_0<;Gr{KOh22&Z;=RXOiDcT!MM=%?~^aL{# z%s?=sX%VWC1jXdP1kYmHGXDqSXI_HY3Fac0gTN;L()sy06UNVFFSxAPO!WQi~LWpq7=>f%0w#>tU|aT!KwtW5R4!=iC{H?y$Dt(7)h`O!TJPi znuoOr)*-MVpzyFR!Fsv3(x(js!G;7I6Kqt3q5Zsx@?H*p*->f}II8|M$m3uuF;AjbIOgBKeDuV9$Oe z!QKQ%5$r>tsh(h8qx%u;Kfu+21P7b^pj=3B2*Ke5hY}oCz>3x#K`>?UM^)Q`CA~==c9D>u#_H=@?2+knTn7UL<&u;2DCaO!YLu z#KNkG_F01G37#XEq&1eEKv3lWGK`l9H2;^yR|$S6c#YsIg4YS&Hs@~;yr~#%!doTs z9fD5@-X-{m;5~v5toiQ`a8=}gf{$|{L3tMtd`2*tK$n0BisWznmx?Un|C%5q_=ezT zf^P{l;S+pEpaVgUkw^%BH2F`tgrJY0Y7$%j59|?8HFODl0v!&@_$~PtXGKJi5yS*d zf*L`cK!<|?;6~09vR-aC{(}#vA)H=S;k1O)sS^vHaE6>GEZ6_TnF!}Fay;Se(!O1OYAz5k7QA;KpKhY{{WxG>>* zgkruD;i81g5H3czB;n#3hQ_vpGIG^~OO=x1E`V@ZLQDAJa)iTG+kY*DD-c@pFQQ$U za5chJ%;Bm=NA%+fS0`MHa1E1d{?Ao}YZIIw~u#tqD_FI|ZD8kJNw={l>0mxz$Al!y<55jE;M-y&GxC7z#{T>K+B;2XENUava zoe6g%+=Xygg~^FJrS{zi816~9msP5h|6ESEFX1JG`w^Z(xIf{sga;5FPIw^UA%q7J z+WNnIdiaZ*#Vz5Xgk}COdi4mxqX~~BJgUDgLu&r-mk=IDcs$`rgeMT5*e}mRB|JHo z5S~hS9^q+(XA_=IcoyLqgk}D>8U;x>ro^A4j7rWeU7b&Okx4Ef9FYIRi*plGT}pU6 z;aI{O2rnbNituv6D@--6^mC=+twzGD39luz_5ZwY^;FbXfC;ZxyfWhnZy~&q@TS~@ zdDBkI{Se+tcw6q!RCf?QOn4{ZeS~)rTG|iGQ6RjxA1NLP?g;R{yFbA*!!pD$vz$O&I0 ze1-5O!j}ii&3SPbK=?Z0JA`i#zD4-vKp0{9{D1gv9+(-vPxv|E2ZWyxen|LHzX{>T z1+Ojr)EZc<0ugHdC!9=ZbN=v4byzg*>zpV2mM|s!jxZ$rp3ouuf$(R-9|?cz_nb3? zeI-*N^a!hjuKJON7su2m4068o9}(6GV<{5W2$S4HP6-=?Ey8BMkg(lvPS_#*lQ1Lv zjj(GTejzMo{-p<-|A&8QbruQXUqsUo{!KI$;Xj1M%73AabZ98~X@w!O{GWGAGy~D% zL=X)onvrNOqM3*UGc(bwM4JC|TQe;3Khf+&a}wz=QE2lsA)1?L5RvA8qIrpmmHz^x z!XBder&y_E2+>fYg@}61Y5^jf{O3KafMG<75G~wqMYJfu^Mzv`@5M>1_uE7x zqJ50n*Ql-kMEesRkRu1Q`XHi%4ReUmLx~PIX38i)bVP1NbQIBTL`M@{LUat#DTX|j z=s07JCpwYngdA*=lZb5R2WeDsC{HCim*_O2Gl@<&%o&O=T74GL*+gSetc0m4*Zp~d0-A?o#(H%rj6WvMlFwtE^4-nl=bRW?@ zME_0x(fzqwb7;w5k7+T=6Foxo7}2BF6%({i%t1|M*L|+qG@>l#IIYGM_O~lc+{ix0t7l z0z{26o)%Gus7<7I0lCn?8Bw>ts46|0zY_gc^1l;J$^WMP%jn-k{}}VH(s(MN`jbwG zry*XDcv@nSYyKymo_I!MW-tInyW*LMXCa=sKLFxciKoo`4Lm#Xyu_OSiRUDq+nBk8 zns&haKc0_R6F#vn0TK7)ZixpI4<#O=#XJ+ny~GRT9!kkV#3P7@5id==F!2(^(vZc7 z7ad@=xbpcCiI-G!t;L1_WtJgcfp}Ts;U?E1K)}Rh{#SlQ;#G)OB3@aw7K~w5)nX|j zUX6HlDH50YKVGwpb8X^_iPs_Cn|NK~Es5779!0!9@krtgh&LkMu-_Z;#>AWSdlo0e zn*WJ6>lYGlZju4{Ki-OXH1XENI}mR}ydCkj{dfcGSWt$vBk@iHiaQhUW|&=!?wYHJ zcPHMHxJdpg)G&&Ffr{fOyV>8 zg~VqK@P9V(g~aC&pHF-)@p-xD(*Fg@zKZyA z;wy;9^;;2NIe@>K_&VZih;8vFcUv^%`n(q6@x(U~-a2=Su>ijNV$PW(9WGsI62 zKTZ6kSv@rXGf{cDvb6SRiC-Xoj(E}l!1H-9#4i%RLi`eOk^FNE@vFrDef}qYgZM+@ zH;LboXo=qh>E17lPB!|b z(XWK+ZpNzphMK&8Bq`-v;_s-@8Mh<_mdk=Qf-C*q%pE5`Tr43(xiJqt8@ z2H!?p?fv2#;fY<9sLJoT;t~-O2gIT9#1U~Us@%jR3Gpw)b>cR0qi4{DCKM;FoFBZ& z`@#^X#GRhIe<#j_S}on&3GuJgBwhbT{5SFMy_4i*`-AvTfr%~gUm}t4ES`U;O+)-I zHJSF`Ms2DhjG?dePM(U|w7rv;GiExW2AF}`%+#PZ6SWx&ur%$O4>W+lO0eO>GgAENXNyYKyB%emnm~ZAog& zn0zT}OBZrAS=J1f%O%u?8%90>oLjAE#g(Y7PHkmst6I_CZYXLTVdkr809F^ZHK@tQ zf305izc#gfsjWk86t#5?ydJepsI5X+6YiJ`sg0(#D>ZrlH+Q>BDc?;c z8p-ZGd+u$F?1Lo5y{PSDj0zPzCC>d!zCX1Cj5*NgK?+mDgN+_S?a*A(J9&3%hf|Xa zO|*|vJCfRY)Q+Ndsv(c2b_}%>sU1sA-qID!aca2d-qcRW855pl^kiz1`HLPi%xTol zq;`7Q92o*sOYJOb=TIA?9<+FN*}8L8Qgr_L)NY}60kzAiT}bUxY8M&!VrrKtEU&2O zma(R~OywHeIBHi@yMo%4rMIgJ6V+Zr?K*1Ls-*No?Rw#<-C%S)wHv9)%ef+vRUm6o z5&5mu?xuE|2&vsp?M`ZUsFR}Jy9!>l_fUI?+P&27H$V54kvu?6KK^CREP|K)fAW5K zL>aZ6K`+Bk($2nMeP}*`YxDOq)sN4HNHSiU*n?o zqS2R(zD!Lf{{nlJn&ki7o4f)bk~fULsjl+2>t*{kwRg<0eE&;(@_lOZg)i~^fzc0* zeq>bgf9~^BYLlsbX2Q>n+WTLHR^R`kCY4Is|62I+OqlZi7q#!H$x@79exUXvwVz7Q zKU4Fm^--%*bJSK{$@;(6YrIR%Q@*GxpeB3%3nru%&7(8%{BIf!I0lKn}f)Al9NfuO8tKjjObmj4sY|G7jc$-yLt zkQ`?Gq3X7X@bJ6}k|Rk@GVM_$N0V62PmUoumgKl1j49QfV8|1NiXW1bNiHNgh2(UT zpGtCCaRL{o(-|aZno2f*kYL7;oNLV4c1oX9fK_!K$@wG~^fz2!hP;U6VvdlkvmB-fH$lY0~AB-bgQpCnrE4J6}9Zp?)w zH}z{tZXvmYB!5iRx<-efigpqve0|Y})UQ{y_4h0*csv zCW%ST{8Fs82_dkw_0~{wMi`&Z|F6>eR5^h9G*;As zpf~~Q)0<=l>Xzs0P@mBxGY!DZLVb1<&PshY>i<)fgl(-h`5Z>)%;nVQroJ-ud8jW$ zeO~HAsn17!e!~o+-jl? zp?a_(AU`y98v^P&1W26Jm!ZC#G0PUcT^~+;Me55_U!kZsKMHlt|0QM>>T5`m`l{4N zs9gEgsIQ*$=5S5w>r!8<)anpGeVyEc@#|4vzjUa#0DA7#H==$X^^K|TM|~6OJ5V1< zeRJxYn#pDg)38UChFehAY)^elqniH>unqO?Oro~{##sI@v^!GY#e_Q<9c^@Hp>qCE z-UYTokCyM&j)UTl-=lr!aJL$WaDbnOsXuO3j~LYuEg-=Y3A^;fA+q^`RTeqVRzD_ZsJ}~nGWGYUe@gv*>Xzi|A5i~L%{3BP0uoxx|NG6U ze@5N%e|QK0^{*?wcxhJZ3i>OY83f7&@e8U5L4pHYX#wA3rqJJhSx z6Y4JYh`LAJ7l1j`{BKNH`iaf9$p5C2KLR9}2K5&8DMJADc4?cIwi%77sduUWY9_yw z^53Y-^{cl4)c;VSGJlrlHvd=2Kh!n(YXFTh|2L+|ov2A;IvVn{zc4lgG-Ul>cnUv_ znP|*OV`duvqcIDOS#v`gv*iGDtAm0un*Yswu3TcgPX1}kOT#jMV?G)>`8Q!tiP!v3 zV@O_9s>NO!3(&ZmhUR}73mF|oV`mx*(^$odi_ln<#`4B5W>oV(jU|jONkfxAjirSe zvy9PYjV@<&co~ch0SaD`hUR}7D+?{PtJ2tj#t0f~&{$373Rpb{&{(sStYz}GX{=++ zx<=PCy1r2T6>Z;;#zr(YH`T^QH!(WW=%z+DqcKXD(qs!7@>HiBoh?nf6%8E@jNgXF zw#Mi{P~vwewL6;1@_%D=sojOf*)(>gaUzY~XdF#rcNz!K*n`IYH1?#i9}Ug_G;}U! z%sxi<6`D)>DUAbZ98|&%7KX+lG>)R7lYbhAm8!#OSSP64Bmd(^VaFKwSQ^LWDjLVr zIH4a#<0J!|Z1fZwV`!WzS{kRBq+I`L==u+hGfj0?zqtraevZ*|X`DynIvVHGxPrz7 z3R71X(zuAmSQ-~AUL}{%(EP8zLUI|6%S~0}e{*;xjjK&~Re{tPWC)PI0JL6HT~Fgy zV=VtS#?!b_!CK2rG;S_ox2RCfZT+WlyYY7zwaI^@$p2<|4-JpTy~b5euu`pCV$VU{1G4+ zS^rm8#(zZP3mPAn#ZPE_YKEVclFxH6jmb2=H51tjRGj4JfaU*&=6}VTPt$%+!}7m& z$d5EM|I3fY&ougIIQ@7U6&h7jxjDwneVR+r(ELv$r11}ph{mrpVj3M9n*V7eM(ai! zLXBw}Z5eITD3X6(9Ss=*Z2sT)CC8WY-wgjdjX!cZ4bA^F{xbTv3bo1q(wvs2JOcU^ z&8dwJ$p6jhXwFGooq4__<%xaR^a#dc7N#@9Tnsd=y zgy!5d2h*I#Oy)Jfd?i4K0GgWrY3hoQF_!;TGSsy4{HI_RFuEYkg=h}Tkp^CP0Kce7 zEdMtbr)kN*xnuz-oG(pt8Ja5^a#^ExFVGxLb9tI8^m`MArse;p=6{+x&=|8S%@M|E z{-?P*%{5e&pF(Q27Mb+h+BA3Ux&AVm>(E@+nDvaVPjhpc8whX34QXyv@*A6E6Qd(( zZfeYCrFK-$xTNH_pt+@Kw=%kQDc{D5+tS>wlx$CPhn$zqnrQAsyGwI4t)FP_OlwP; zyU^U9=C1l5O>;N>FGgeDo#q}i_c4A?qk9?MTmK9ajDq(yx}O&1U!kUbR-kzx&2MNP zMDsD42h%)RH!y4-Li13X$Iv{C=20{cr+FmJBV?;X*^yGHY|yH^Vm6P~%@0R>C|hLH zJeKD1GH1vD?AX`6pGFQR#|ezSYj zE~4t)VOg4&(!9)Ejg?(wb*I_p}J_~l;(u| z>k?9_ZZ6tqHM?AeANK28GT)-3E#*MgbClG`8Lf@X}&}A1CzW<^F5mH_fCFY zdQUo8cY}`l=kZPe|sm_X?~J-h&A~$nsO!DUbxLKXigSC0;Bn5@1}S39#ZVr zO7m+CKsJwUeoIR-!*{eKLw--QM)L=n6`DU<06)>x9Z>CL?xX2Qhv%oRZmQXopKmbN zsHc6qmm~5znZ+)z&3|c4 zts5z~rjidmtN+$CqLR-^_kQ^dt?6hDqBT9O|IwO()=YX5wxBhmF!Gb?Xw6J(R$8;j zMIjRX7FBCD=?c9@zVv9#PHQeb6*EA+(0duKv=ua(s(VpR^XBwV=c-n_3TDK>`~_Yf)MY>wdPa zMTCmV_{C@~K}+}lm!f%Fl9o*V4Imgf9Ia)n?aR`V*p{QU7Omm5R-v^#t(9m={4zS| zR|DkNyZ8OHR@N}2nc9R^X{}Cc1g+xxFTJBY^R@=9HKiW}rkAiB=+@e_Hl(!}Yf+TBCDG&(Ni4$*~wqYgbw)(%OyI zp|p0Vbs()hXzfc&x_WO~dl~*LGvBAMUFj@Z`_VeUg!}ibG@&13l7oyMOzRMnO8^sS z9cIGAX&pmL8gi5skL+0~_k47T6wk-fI^ML$mD&?bnO?)2yWS+;B|EoI~p(TIZU{d9*Gt=6nO^$P06%$uBl~39U;_m;)}Obv3QaO*PI8 zrQ5Ht;+3?nGD(iS#)Q`zz0Txj@J+sj!vbGX7~=&(WG_{4++MRqYhOBwEkYdeNHmg39&mdMU5P_*ZCsLhDspZ_|3s zJiKo74Wn-w)w;@aNt*DE3FQobkJg8@-nZfh|HXVn>tmDTz5S_SKBM)mF_KTdp!F54 z$yWT*ka|AQ`a0)neN&zl-<6W@Y5h?0KbG2`X)i#lk9IFDr)OiylNH)i(W=r)bc5NJ zOUt7b((;EaDaj-dDstl^1H?Uprt4Y$Yg#oa3QwzU@&>IxXfS>zZ(5bgHEFNaGj78SRlW6Q;eX&WB~7mwB=lN71&;|J$1XX>XaUjNh8}_O!P#*tSNu6FR_l z2NUjSRBi!Ao7K((JnTwa`g1qh&(hwV_La2vpnW3kJ!v08doS79mG<67_c6M!(fx!D z874iyztIDX9%%F++6VUxy+U}Qv=60y1nt9m7LkK>xKJ^)J~@*1akP)3eGKiRRcI>O z$M){?fZkHcbB68Xd-uJT_6e$8copMMqJ19ilWCtx`xM%z(>~QapH_I*TF&U5_=x<( zg!Wmq&k>&X7}{s|ezmcxjM6^0#K_^hg!TorFDz9TneD||@0#Bke<|(DX^-uh_tu^p zBSm9{itJnidf-$48Nexx#Men0NkqkW5}cA4llI%R-^%5>>8onr z(W2~L+kTJs$0m87_6IqJ_J_1T%J~xi3GGh}_L))LOSV+W^Pl4IOWGmruW0{B`)k_Y zneZE<-{xT2-z%fwAC%FUf1>Tumi+ISyswN|@_#kQlt|A2KJ6gKl=6sn%TzJ#8tppm zL{&vI8?>86(Ikd!(@s^Wk#v-it#Kv!Xg2$WjwHHYN#)%5jrQ+7cf7AhP@ZCK|3Ujt zQn@7mBAu7^-=s6r{)cpG+W(T;%D>#OZyOEX@&nRoNa>k4nsi#y>3U=}Bb}a9SN=8i zNtTnuD;ZejGm*|gIy0#*@{`U&I;$ozNpb0Hr2kVsPiuo@C-xDfbCS-b57|ns_j=BI)p+h1Mrso^(b1gh9H3e!M_da?+JZ<@tYG?lVfdD(Oa~(!g~{S0i1+ zV5@77$#F^7B-PFTWe0vz+5dk;&)}V{H`XQHfOI|5^?M%JdC=}Z$xiax!y530LUo7v zbYqKQ6Mc+19VxW;Wu>H>k#0viiqxk1>E@(cXb)&dZJBpF>DE1WOQ_peUv1m}{RPtP zNq6pf=s40HNOvUNNe$PQ;^@Jn&J($sk4uKU_&n*Zr2CQXM!FB_?xcI<=K$%RI!@^k z+)H+mCEZ*1l9imC+Ty<)Up+eelO9ZZ0O>)b2Wl1h$smks4Ho*`(u0&mp~l^jy;ONzcd5CcTC9c2Wsmc7A?WkEv18 zJ4o*=d+9FH`wV|K={=!Gtf8zBK=_XP8{Iq_2>^E}ltWC4EghTukP^;k&cpBv^2qmxy&+U2BQ8vV-X z*FsJEEuGm&#i~O3J?YP+KUfSu%7P^6Pd%d-)t~Zxq%sPOmtY2u)QPwCf0fiF^?FYH zOb5`^w@W*q18GQVtMO?>8k4q3ZT&x$OQ1oj>;Kv=p?a}2Nn2Vp3292&CGD6h%Q2!N z{e|>*(qD5VsjmN<+}8i6c#|REZ#vVE{*&91{!7P(0CCdMzW~&8XIi!GOlNfZQmE_y zB|oFVW}-8*F|*LomH%98R{x_jSdVOHb~K%q?`ti{pA$_>9iH za+K-JN2iC*AU)!e230cu0DcIa1?UW=(>rA83-r`_@cj8@3AD2yorUxS(3pqi0nk~P z&LVUc)nMd|%KM?SIGrWslN@xG(64POvlN|Y=qyd=2+b#*W#}wRXHz=M>74)ijLvX6 z%hOql&I+YuMLH{)YUNV0iWOI-Gs2kFjIM5U4WY)Ysf;vAeXebE9i#FWfJoM(v%WF1 z{x8K1jmrAJ@Ur-yd)S1|NaYJBo6*^a&M3odZgdNyvi>htThZB>&en8f!C#8o(Aid) z(sp}c=*arNsCG0efBuSObU(&~`UyxnyVBXsio2^;CH66|j;#NS`CfGPR=GK+vv1G! zZ<%~QI{TOW0VX-n=t1V;U^<5wbEwh7j2qSBN9l~G^B|oY>D+1Bo9Nt3=QiVS zp>wM+@)Hv}x92>aJ4z3C8SHL4_Zf2!oqI)9YVSAU1G$xXcqr%TJWS^i;Y;KMLq2Bo zaidSrdCHh4h2}o#Je}iZHl^Pv={!s4Ila%->v592PDST=Iw74G=zKuuMLI9(r-Asva~ zBRXHw`IydUIzQ=rVyaJbCx-u=&KGnhtJU#y>hI_i=tyV&MCWS*d_(6uI^Qb5Je%-) zIzJfmqYCYnH#$Glv7MifKB3rAzf+;((y8V}@oX{3X8RI}Z#2lw=|psXr4!R>(5cZ$ zN_kyiyG>DL4P`q#_qB9k~W|=YKj~t4Q%y{EciHI=|ETqxU%7;Dyeg zbpA1Kf6;|7=En?IoLuUxt#+O!s86 zS?EqjHY?d+vf0SyBl{oOoMf|Wrp@NiPcO;mkT%$#Y%XO~I5*im`rRmb$t#;znL<9u zBs~V6UkznOlno&}f=sGAluW8#pKJlL<;fN#TbyhmvPH;-kuBV_Uaeh}Jl zR0c1}C)v7W>uKsZxT{|bmnm7c0oe{@8|$>3QlIX#&~Ep3SAAP02RX z*2xq_l7M#O=31$Sv4swHl9#fr$hIZhn#?}Uw#W;D9K6YOWZRK#uU29%LP@aMj$~4= z9PT~HMw9K@bJde%JCp6ALCQevZLibvY&SCb{>ymH#`1P#wkO$M+V)k>*N(mNOR{~) z4kFu^>_D>p%(EQm12iGaBv!gb4x-MEZY4XI>=5;Q&|Z3&)|B=hrn93=+oF}qePVVb zSxj~m*$rezlU+!54B0tk$C8~&b{yG>WXJ1(lAWNV%!m(*u_il-Oism9M3qZ~lAT6& z2HENTLb5Z-#+d3XvlTyQ_iN4Vxn$=VbH2$1a6#!%_>0IcCcE4Kml(a2Y%JMj2F~LV z*f=sd^j8?*N;9{?F}s>fj>R=(*XFh7@snL&Rv|GyV(N1#ptaC zQqQ8>$?hS$gX}J{JNrEt>~1ZN_>k;gvir&IGp)v8A?a+yU=NW!NA@t89QsGd9wU2{ zY(j2pCeoJ2$)5Ocv`>*uBzrmsn4f3Jp3QwK)p{q9y-fBz*^6W^^gASb$*l6;c!lgW zi|5rcp4Z9VDE;IZ>5w1D-X{Bu>>aX?$lfLUfb2c8_e*5n7C95Nb-8~sxjH@}`!w%i z6G~e?C;OW03oCFw4uvpa3_5pf|Kd$n4-KkXJfh}>29KlcSTtfkOc($reSw8y8F`|MR!-ao9htM-Gc7+bho6t z4c)EiZmn5&!O8lQJE`urbhp#uaQYkJl>J+lTH6 zbWa?z)C+V^5-JkoPtg^g?x}Rg(LIgsIdr8JXVE=_?wRs&IyrB(fm+KLx@YT{Xnz1% zW6z~~3ElJPUTn3Wud|Wv1#~Z@d(n^uZ_y6aJ{+%3WL>CxDc#ErIhO8adf~{Tt57*x z^y;{Rt{l@V={`kQnkoB#&SCLiL-#hi*V4U_?saz6Trbp^8|1Gdy5ofw6O!&tCcoL} zEkE*1cfP{hL)UJlyZ6$)ujd`<_WQNpE;>di-3RGD zLiZt^dvqTb+OzsR#y?8;F}f25NFJyAM5&eiADohZn(ni7Cu(O(rs_VUJt@gV{<`Wu zM|Tq4_vt=Q_YJyofL@{dBHfqi%F6;eJ(GL1-mmI|&KK>g)|0Q%eO=EMS;&=Ov~T5l zf5(`&jJ_>YOz6IA#rN`}@zU5&=zd7|BmFR_IMEZ}W9h&_pGbtC(*3OG%y$Qk&VdjL+Ji&v`?r?q$@OwR_V$zjxa7=k8a5SiFylYJBsXkxP5Qj$;(R~8Z@{QoDkgI z-G8{dyAKY*o!|~3xD5jg4#9)F4r!NfpSq9#w^pxSeXFZZ9j{ZTs_Rbvu5a}}F!_7k z?RrqGlcV_AGWnB#GF!8^F8{B$T3?v_8zzg564PW%)~mk;?zkV5{~R{Y^aHn##ANB@ z-xf^nxtvOL$K(`~3rx;1Iq%gO_;5j2USe|9caWZ+RYONT)K5N~bpMvWIDq zPW#XEbV#S~Jr6tNM)!6`q%$F%*)sow^=Cmkt9v!XN_xIj7Ni(8?MFJyypt40yc%!71Z_b{K8xIBX`AEyf-T@dLRNJk=F z1L;CYmq)rV(q)h?f^=!KrgTxHi%BPuE{=3bTN+50upIIr=1bYbGMAdw8$*^wx|}T# z>sX9CPcu)OjzYQ;(iM=dI5?_GL*&8Im649N&^KKL>8eOq7lBAuGctP~M(U>+TfQ+J zgY-nCYa-np=~_s4L%OyWcaYltf28a7>LXnb>H7aXZ-8_|q}w8O`TuldS924jTOd{b zN3Aydr{+KBKcrj!Q?eD(t&whHKZVRc_#oX5>Gnw7KL6B~fspQqbf>{;_S5qJNO%2z zCFTkY|2>c%Bd?BhPZKY?bneF39Z$o-7 z(%X^Vh1BqLsNRXx?f;QOR-lsJjr1OSB{wc4jh6e6K7#aqqz~8#u2-hg4=_1NMAXwKNZ$n)6p6*Ej!^NMAv!!{2OW#CkCd|AQ<*`Wn(nNMA>) zFTbv}csP~5z2(&JO=E>g`EAD{`LxfRUB8R;y}pw~-TOA`rP^2UCDISweWJU6TI%y<0{>EB4@ z%qREFCE8P@wR3Tqg^_9Q?uEOT?p~QbC?K5aj-gv!j|Ad?hjg znZe!)zAmXyHj_PQa*@r9Y!+nw$Yw>>hir&=VjP}pDPvAHwD*i`m`Y5Uv*G`g%tnq! zHaptmkn%?k!cmozq83E8U1R<#p(|tW;n<85u*+$4VK(?W+ zl0P(j;LghSDP^Eqa9XTBHK#7X!yd5t1q*ZY#U_a z&$h^RM7ABW?M-P7{~dH6`0!tjOFJRk**t(~S1TEEwkxu|knM(SPh`7Wr`BW-L-?|D z41x)9Z)9%$*D=KLR&{PbwjZ)Hk?oJ{7-R<^I}F)@$PRU24?=b@vO{c@T(go@A9Ty* zJ$w#Fc7z+M#H%A6y+}+HgB9j7~YtDaeH9SwuM0P$h9scGv zi3tG{@kPk~HSnSKJ*qn$z-6X^>=J`-xLt@=Ok`dse##Kc7Nk64^7%g@yAj#_$ZkS*C$gK7-G=NIWViN>8tWX~4eOnA5lHaAfx)yH8DeN-O7&>;YsGkjZ8rNA?i1$B;ejD0&3h zqt;L=r%S0tZ2;5$f$Rxn;|->9;7Nm{%Lso8*|W%=M&|s#3Z)fR{v0w}3pP=%@^k;j z;>nB1rOz)R7tdct_5-q4kbQ_u0N+CPDzZ0_i8-(JjnO_O0p9`H>w}S4ChwuMK(#FE z+|D?e#@oo=L-r1`cdZv8CSPiH{l3!~>9Cs}Yf#H3BAbNlBV?Z;`xx1$7QJMj7>zUi zZK`gqK1cS2l?YpDn#tu$WZxqD3fb2tSxua?Z~B(r&-~=ldix!+@5NZdM~+>q71@u- zDr6GepU8ej_8YQako{^E{(nq50OvKo1WtU>PeO+P=f7FlQeWTu~g;KQ$x`^W>6g*c-jAP=Qu z0~4o39wR661o`W6;qvmzgcd!HwVXA5N}{d~M{bAzuUe>c~gi;G)XHLDP+V z404+&Az#zvC}mT3hTA&G*GIlC^7Rb3a_oaNZh(A4RV|mP(fmOx&yQzSL|X-x~Qg7QIXHr0`a<9r7KJZ*N%X(kP$r zh}<@RI_mWQ@Bg;8@|O9o$oE0M8}dDo?~Z&ADf7U@%T4Ds>&f>*zPI5f(p2AgxG(bk zk?X;Ne@iL~Z6o#oxqPcu+r zxVWm_P5Bwf&lF{J zFGhYP@=K6k=3ZTD@Qn|bBfr9)HT{=1n*Lvf{2Js}BOhyMxXGZSORui86sVSU)@xkb z8<5|D{6^%rAioLu&C)~5hKc7!^{vQnGYn1tZx;yz6U)9Q#g;pf-;I16^1DnM)wLvI z_3uG`udzk=59;~-$mNP3Kt2KagUBC2{t$8(`3o^!#=S?8KV~W|{us%RBY(n1w|u;9 zFwUQ}y~i`|>zAV9^740(ziZM_zh5TdWUm*X)>Kl@uBL58e=hn5*mW?Q$$tNNI(gFr4 zwt6>wzDE8X@^6rTYc=Os)!1S>{5^7WdVOoS0Z5Tp{uA;H`OnDzM*a)(Kau~6{5ON9 z&GWeo;SaMirRKzq6Q$&T4Nfg{?f-OpsPf6k(}6LuIvV(BQyU}l9C?MjKwjF2GvZkD zC#GXH^2YL(Qe$zzFz-;zhr&ZKEean6Q3NPrTPiK&|Ib1Z*?``2Yej+r*0$#@iz!e{ zsrAAB^*%%~l`*H78pSk5uViP}?n4b&XPh6cpw-Pl5-ViUbGZ~4TIC^kc}J&MgyY=dG86kEIcTe`Me z4Se*$u%{of8Mgu17R7dkiGn2&ZoCz!olyvg&0lOeNxdlcD0V@ytCdLAwZXvJ?vCO} z6nmgJ7{#6_4nVONioFfmJ@28|2Zf;Ri$Y-U&{(WX4=DCGaunYx5-tuzagfzl*Sd_i zhoCqd#i1w;v%ciw^i~Mit0Q{PD2_sLB8sC?9Eai<6gHi)I`S%p)$u4!FmifqSBzAg zgyK{bC!;u}N2BFBWShllDBR)CYUqX`6lbDT&9hKS3TLBu1;sfi?nEJFxEh6+a0!a@ zQCx)L0u&dT(-wLP#*A<3`eOUJie^mL+PuEF6vY)NE<Prn3cNI^dcoxNY6i=ZLlP6fdTEVbjny{Wm@r?0DixGn~K8NB( z6wjl0!7vm8;>l%V?@K6Nwk^SrZfxVVJb$6)-=cUGg`D<(P`riWH5C6v@j8k(Z2!DE zGK=`diTll-ilTTM#k-EZZqK}Ff|T%m>r0g-BjfysC_YCq5ydAcK0@*FAg_k3-oMF) zC_Y8;S>K{=P9z_w@T2$w#aAdMq4?5h7bpdj%03leqxi-=fVg3WlF@f4{zmaVir-ND zfZ}HqKce`_IG_rq$3BqU`}h>7n#ZG=i_BkT_F@D950T zP|lAsMmZE^f^tTbKshB!qMSmcS&Shgk|vi^p`5{hms6vhM);$g*4?Lb_vwuyaZGnj z0mO}RCX}n{V3-|ISl1&D2JmQ7$iiK zjs;IgIXlWZ231Z#L~l74%6U-EjdFzbD5gn^q;KWCDCe^|)XZlYaQT^X0hG(2ToC2r zC`Y1P1m!{~7d9^;P%ivHxhTrTOpdZm#Z=bg5-68+IigGp60g24I{vG9Llxvu2NPi?v z#Zl#&D0fG>7RoJAu8nddl~u*S6g>xgw5qudPT zCMY+xT!&CnWqIs!bChoVS3Z2@{|y@<;ckU;N0eKm+z#b7D7Q5R3asu{e|wZW7@qnm ztV~aKLb)r-ol)*$6Odj_r45bY?x)Fm50rL3yqjj(QaST4OEGM|pv<$k4dMN%x|W>-p7+q1YQTC{VZd=%wVC?7*P9_8aGpD-t^`r~XN z73GsCCrBI3V~8T{=qsN_`5ek;P(IstQ)QUX{F=?4%I8tOU|ezvW@)$bC6wQwd>Q5Y zC|^M-4gWVv;qWTT|ClB_*X^|Nb(C*diAGp4Uw*QD6XiQ7-$MDedLQ^WF-_2DRlbYz zJySN3Aif#?s{9$s4^fJH6H)5RFVpjZkC$}z_zB8SZ6s9_`4H3m&ryDf@(Yxc2F0P6 z$Mo|nlwa#XQL&9XM*FuY|3>*8%3o1_kMbv!KcM{4HnfOp0aR>O{)|$imDRV{$-L8V zDE~zHJIX%>J(9$vy(Q&eDF3HAvR1=<gwQ5GmOl(}K~b|tk0Ulvy_k7|_p@L4{vJgs5ABC3^L7a9ewHEC9> zpjy@F70wcb*8MB7aTiphQEiH94OHu)8iQ(WRBNJI%RHVokW844}83fh()y-s%=qij%q7ZTcFaQyo^C! zI;z-#~Q=Y6~s3aiy|JR0(++NcwRF|N-6qVzfuoBLi zCstRWx)qfKbSSjya zt#s?~#rus$gK!(FJ5k+^${1y`5T1(dt8u9A5+TDD8ZXHXeEc1%dr-ZC>Rwb&p}G&% z!>H~@_0Yg4GoX3^)q~btm()O#+VfddqGbZAM^Qb2>M>N0n`E_dTHexhemtrtg~`w@ zcN+L)6Frz5wZ2e2jp{j6&!Bo%ivJwDop!yWm7pOi%^|`^(x2xiSqWx+TDz^$_ zmi3jh{jcrS(0|*AR(*>~(idaDH$Nlb*4CUPDs3abRt!P)Gpfm`enIs+s$Wt4*7xn} zMvj0e5mEht>TgtkqWY`vv8(!5ThFP=|Lm<)SD*|+imFDHp~`I?Q`@$y!d79b5>;hm zx^zrzTuLh4pmJ;UYOb~y%!%4VJu_+_HBkqsW7HvPt&Tloi$n@PB*!{I4O<)(!fJKh zA*iQ7Jss*PQBP&SH5;m@HjAsLLETHkE;D338;DU)k9r1MXO!IQ8O>^Bfb~qKnf5RX z>bX$Qih4HGLr@QscA@S=Jrs4nMICZngYbtN)9L}^;lCcV*r1*rwdMR#&tZruG}2|5 z%#C_U)FV(YY7@8GmH<%Ci`r!X>iJPGXgsW)1T}@R=aKHdkh?GJ?u+PdOddZH^Th3iT?eS3te;z$d4o zUeQ5b$smXd@kHSK)s<2a6(L781RizZ(_Mhm7Kqs1GpLLEl_W66`VGr-V*gzHqQ1& zURg)I4eGs7Z;N^t)Z3xn5%u<{cTf{sSdr`u@=mCC9`?oZhI~&C>s?XrfqFO8yZ3MW zx|R2$pn6Z#ds!WIr)$lRQSXELSk(KXJ{+~W8w_7j+Yo^I0Mv(}*7*;UP;0;95~vS$ zcSXN?*8a~Wj03&<2r>T%)W@JcvVWbYP#=ZbK1Z^_T6gf%-Jmm!Li!wFGen>a$UwiCRIFON`9(FI)Yub)9Q;t$0ZP zMju&*xjrBDMW`=8eW97BD$C;;MY>%6N0r6BQDaeGidy%}P+y7qa?~#WFNZH}*C<(E zh1$jcz3$}M>T6KniuzjAH=@?REWMBVdek?VWZf}QQu>um>OyTwO03`zTa}lB19T%TJa$2M^Hb6 z`eCE%+54<~g^3QJ60lYc_2a0YMg0V7i}q1#71*5zSWiIxl%eFhK>al8XRNQ@LVW!k zYPEeH^(&}fK>af67g4`t_0@&$GHng?njP29hg(N(3BVr2uGdYZ)SsaK z9QCKDKU1JMY_W+Z!67o$FHnDtdXk(Z>Mv1$rJberN@G@!V-F2S{VnR>P=AN|N7Ub= z{=vp{Q6y{?In+O){uTAl1D~kQFT!NVy6-uBY-WmD#9HPD^&f_b(e)SV|C!$q{83|v zjTnh~GMWf=in>9ap)OJ9s0)KH!Ksx=twLSfvlF88(k&Vvb%#c@JF8RxEL&&-G@;dW zu0sJEni$OtXc9D2paB}1i_qBWxQ%8?H0GbtOoe7@G}H90e5H+CYOcGSe>0tR*G#Xw z$*!3Z%`9kULNoJ_T|T$vWA(MdL^QLa8Dh`ko<=KC+6+ar3Yva23!xc?W-c_t(aer! z0L^TJ7zzhTp_v2CoYtcV5d@8W&D>}fKr;f(JmvwcWL~{OGanko|5w_&y!P13rkVxO zjI{kZuAz;j&BADe@FHjyN3$rJ#roI1$+kiXxWQQh&60grxFFN`b|spn(JYT<88pk9 zVr$;f*odi^#MTI!QD|1M`IqEn2&{-^B{a7A-^^TdWb08(-U!WVXx2p|a@IgI+CpWa zWc(k4W=-R^1~^UMnzhiZjbw( zm;YC_ozaMy``PISeb*0vd@UUj;F|A_W)Fj=<`Sy`+zU;HW^Xk2q1gw`S!nh}b2J(; z;ZQW{;vh8pqdCxZcYrx?iPcFK&B18o0o)RsV9M&7!_XYzz#cxBw``6?bJSpn((`-G zF=$Rgb1a$@&>V;6czL#gPj$+Q%kn9>YEHCglkCZ8&Ombtn$yvoism%4;{GrD*SOkb zXUy-Z<(amgXzV>3&9!LGL30V3bJ1Lg<~%g|^Ix|+(a^h~@1jjCUTrQy260yUalLV9ZbPH{?P%^q&V?_oI0q%>!tjNAn<>@n{}G^B9_k(L8FCzUC1J=US7OX0^@ZXr8c~r%YZ` zNbO;0o<#Eunh9v08gkrgR@s!{Y3;_>Rv$FaqIu4^Dq^*L)Hw438d32g8nN*uG~)Kl zXkHndcZgSGj?*L*&8wEG(m2(;hUN`4iZ^x`Ht}+s4hWQ4)|+VFLGu=xw@m|8sLM3@ zT{Q362{}?N&8ao7Y{Z1`(0qvI3p5kae1hg9G#`sa1D|ea2v4t8pQ8E9niz*a?_X(q zQwe*Xgyw5BU!wVHuxpi@Mty@uhyVQB+_hSn&U}yN4>UiZ`4x>A^^=Lf@D~$)F?K0L z*W}t*`kRBS<43d#uK`5_{)y&qG=HISE5GVOk{z=Sn#pKV!$eCw>ctqFqg??_fp%6j zC0d}V&`Jt5nifr?hK8p)GG){GZ!Xd(254>NR~uK%!zeS>#%L2EZ{eKtlW2){ zTC`K3of_?wXs0qz3Mz!B6j%tfHtn*oxt$K}jA*AvJA=CDskhPF&V+Urv@;uX)Jm9Z zsL~kP4neyB+CH>%q8*BMINE-+!_?ODX}#HBJAigJY3abHf}{PPbEwCG@osm`uxruI zg?1jabE6$$BfHqrQ?_xM_F^R z!g(9CE27;2?Mi6ZN4qlGF=$soyE@uc(XM8Cc-dbD`Le&zjz;VDe=2!9i2s^s*VbFK zYZ)iCW2#*T?Rsd}HKTJHqVj2vX!k@bmP$`{ zLA$GoSoDfxLdojyZedKjhm~u6t=$XlzG(MGyN@~52|Co}HODK}+)s1cfxBL?nwr_Q z2cSJ1?SW_yL3GlhK`o_7rqLdn#J-=QOl8qdgt1^yCb* z=c7Fn?Kx=ALVLDZkWiJ}O%u*Vd!DV?Nr?LH!?fiBw8H8_v=`YZEMkR{JZ*b1+DlB~ zB@yK)G+$^hLwgO{%h6ti_6oFD8f0NRWOY5Dy&COU5n_ePshZTp#v9OHhxU47h$vFF zR@v9~Mzl8>N@i2n3O?Ff&_03oRQqBs`xx5CjZ#sk(NjLK9gp@o zw9)}7(gd_mS-AwKI-1_L&!By_?`XGrA}*Q2JdgGzv@f82(JN}$M9=xdzIPdyJ#hg z_t3iaUwzgmfAZOAv>&3KXrYnPk|r;)`eU?$@Cn+_(SC~dGs8+k7l$?fYQI1`No+I@ z$N|brw_lAVbYsv>fo^_u zQ=%J+ZYp#$qMI7sbm*o*H?4JKBf_LCb{ayJvYS<%gmZWe>7%4V25 zpc{g&&o%tOcqK`7{pjXIHw@iu=!T;kuR`FS>cr&1V)W2O{N^W_AmpTMpfV=oUve65S%`7DBhMX^x<2g;D(P7Dcz1 z?Nic+LMoIlbxWXI8r_oUmNKC@G3gaLp{m3G?=X_39@2zvd2}nI8-;EK!_7FkB087( z?|B1RU$+Xn)zPhrZZ+$%M}?J-Mz@B+QA1t**P&Yz-R|htLbnCFwb5;eZXI;C@{4X= zqjB-~(XEee1G5o<8gcBZf64`K1kfgv0c&aX3j*&57yiR z-J$6AM7KY>z0mE8Zf|tX|M#?7R@O-+_Uk+Hd4qrQ2uTgy0q71wcc7_(;A=$a4n}u~ z%PQMU%Jp>^x?|8Cj_ycwN2tW!8t0Efr~J=#8qsVzZ~7*sJzlNQ9cTQsIwzoW`G0}Z zb;VQYPDaIHXuI^!LO^oN)pt~NO*ma!^o!T^|e4tTx1G*bcno8eUdPg1IjP5~nx1hTV-L2@< z*KO!-H#{XNISuRmPITi2O-UZlZ0&Az_o2H7oeRYUP!g9X>F!78oWC}Rs=4+pbPu5$ zkM3b~kD+@6-J^!5(AM4jgVgB>vm-&c=RH^QB)X^3O+e@7(Z}~&O^p|w%m2s)8FkO0 z`vTqb=-x*60y@1_N3WoJ3Ej)u3SyjbHiGWo=w7vbeJW8x!!VRU|BLQ*bZ?lp2%yb7 zRPrV|`wGlNV7956GKz`l-bE*S(GsxRqpO`KonGmmU)-)RH8->R2;HaXK1TP6Df9nt z>7Swdd@x0$zKs8q(5b$h!w=}bLia7YuhD&DaiCm~V47WghwgjVooxhl{QnW%FX(>sRt}#e{ zzBy?~zF=6~pg#@m&jrWeyo zOt>;?&CzC=n;z*+NpA`RC5`I;lHOGGrZ%<<4c%w`7rkle%|dTFdNb0Sp56>rE+Ddq zq5q~g6TO*DE~@X;hTg37`t+0D5R;TW45im^?)u3ajE4h4ayY$_^akjSpf?-6IqA(# zZw}K^#mORV)d%U#MbGX25^id{+HUmbp*KIhdFjpPM#gIm+UWV{EkJKU+mAHsi`L^% zL0gF4lJpj)w-~)e=q+jl_9!|cp|?1_C3*;(pRtmq=q*ccX?n}p03kGVpXCF3%h6lj z=#stkEi>@h0rXa&w++1&>8(p|C3w?4g%=xso6LmLJ3_MzdLA=2BJ-X`)4 zdNztylsGn{w1|;WRCmsY)7y&P*7j3i9pUu0rMEY|?da`7Z+m(>(c6KZPX2U% z=ri||1LMT5oyB=;HPapRcBQumJ+Xavvl^|EeD;!@6}>&_?PV$?w&*W7$4id;&^w6U zzV!B|Co)|uEt18htoU4YHDy- zo_Wl1^p2wU9KECI-9YacdS}x+mfk7!^y)-<$J0BZ=Q;)sGe<{H@&C!@BF2xj^4lfo zQ|X;S?=*U+>!+nbTsn^4ne@&w&ZwHSt50pup?4|0bLmOv&!cytY?DjhVddlz*w~MPG`Rn>*%)NTU-Cb%+rak;ZdT-Htm)_g- z-Z3{WWJIh1vAvGHF@>IF^as5U>HSP^BE65Kcmtnh^xS4Glj$e)bpEHNoO=F@-skkb zrT4`@CE}Yz@RiYJ6~1=&Z+dT4Nbfs(wxyHa_wN3Kd!_Q9dJnGf7kc)=DZO9m{bsDw z60>NO`Wd!=()*jw+h8v4^(1&!A;?MHul`ZE}|O4jKvi}PoqKP&y2>Ca+hsz(!o8{o(X4r9VJ_Yx=X%Uy=Uo^cSW-2mSfz&q;p-{kiD7lh*WEy&!AU z@aLgF?_dJICwG5-`U}!uz#NqPzx6Vb{zAqvp`l^)lI7?xLVs!ci_%|${$lhOH@~E} zsx#f8^p~W+lv${}lh#8=|A+oE^p~fOq4Pmz5xAA>2E=QGx~1vSDlJpgTE#HtyI~D zHtp_MRS>qJe+2z)>F-5^V~T^$gcEv@84cB z>V?F@a}WC3|Klcu>T8et>F-VdK>GX8mu~D!|NnX#-t$8A_oshAPj=Lge2 z#7+-hVWLUFIB*#K!z~A*pEK>JV3_`q^iQXM6n!Ojj;4Pc{bT5B-TX3BJPGRpDarBl zPv|@LQBiH7PUYEc$1g15`NV zWanQejz93&n|03&40A)d=f1rOc{Y#7l*@AXlo90d?v8v0k$A3JQ$H%%Edx|uZznd^pZXM*Ty8vPsS-%S5T`Zt*-7^VAZ z(n|jp`nUEC`Oes=%cOie{d?&Py}Rh&Nq^j6!b(| zYHA{{t2o5}nEoe&Vs#9s{~7&B^gpNn#s3p`-?j78|C0V!YG|K)s<~nV{~P*0)Bl$K z5A?sI|Gmje2nf$HkeXqd%Fx#hC_27@CxG29ZOcaB5JxyM}<8en-EtXu)rdjmm61bejDPEXl^e zXAoHVL#Htad#?sQA7OE6kT95n0T|G5lP8p_%ku|QGMK7w)MnXK9Gnkga z3=F1Ypu>L#zqMDu#$ZMUGg-z`EK<4rVK57WJ_fTg7&7qrMivHZUZc1!7|NjEysnsI z5;=jva0XW}7+|n1gV`9Y$Y6E`i!hjj!F&wnWH5rkTnubc)zm{6DjE;wVKA>fiH z1q6A11|u0Pz(BizhFfEYDzB2FqDr%h}(u$ha7cVz7ew5Ot)a;tCTPti)h_1}ifd!(bH#!e>E)-!M+T3V6Z!b9U1JxU?&DUTPr0lpHi(Mg0nY+eT-6}p%Qt^K-CUmppFh?us?$X%u;6gz)VS&8yv*o;K6x{MltK< zPzFaZIE;bY|EbDi$na+v9LeA)3*!ZL?kB}P2FEZso58URPGN8ygA*AX&)@{ZS=kbO zFtz=^8Jxu6WaIzn`TEyZ{B{Q17+YJpNX6UJlI~m;1KrFh4!CegQwsO;}I~>e=8Qf<&ECrChX|@|Yz~Es94>EYj zrUQ!V^~z-N2!lrrf0?R)Ype_&XYdMxCm1}-U_67T7(B^fg3&9UtERPjnt=^xrYB;i z!GDgyiwvG;;5K6jlxiBZml(Wkz?Du>##_^kKq7mS!K(~jXYd~euNmetLsL0X`UV3f z&+q7ELDdC=w-~(3;B5wO|EGwOVo#6=-eaKeq)srf;@i2>=MNc7W-yV#_Y6K_Fp0s( z3_fG<34>3K8xAEW+0Pk#Vf0E0TJ*7AqzvCM_=>^T7DTzQq3^Dx7<|j%JClyhPc4_r z;0Ff3Gx(9gFARQS@U!tj{=xRWNG`uJ_|1kh)t4?Ad4OS`K4u&fW44V9{yzYvJFr0>=n+1f^TFIQ} zF`S;^3}ywYqst)A#Bf)JGc#O`;Vca2XE-avIT#LMIKZ%v;ZWn1smECiH3b;P(2WA; z4eL8^m{#5y&c@Jbpv6VbYlU+%oQL6D4Cn6ON!~!cNK4-{?J}(9WjLQLFRHSzRa_A+ zz;FqM3o=}o;YfxHSv;@GV?$BLa1n-!Gh9@g`WP;zU4;tR>@JEF?SvwHIfhFyTw0aw zs}>Dp;W7-DH6Z!~K%OC7p5aQycn-=iI*#yy!pxH7|47_K_($Zz|e z{KX25Sh06AhNBs-!*C6TYcU+daLt~)%mjqIwO!k0F4C58U4|PoT#w=UwgX@GB|(Q9 zFx-&gMpju7{h}Y9&D`)t3#Y*|M~92usWRK5qlcmRM|v%Cd11ZiogpPp3U%FhUZul)fpzXOV!U;PRWeaMG*`yWOy;dix|3m zk%a-a@sHsp3@_C=nWo{ojEc({j%9cS!>e2cciOhV+ABN%(>+LJ*E3W{N_tr4-}*H@ zzk#9i|E|7XF}#`aWQMmemX+PgNEL2lG&jTB8Ggs`4u-EVyp!SM4978ifZ<&X?`3#5 z!+T7_d*hvTeILX7&G)J=DU$K(L57boe2C%0HiC;H1=#XG;iC+7{+sp`DUG{~D|v$9 zvkb>Ge2U?d4D~ONE*GRGR{v>+&sf`~P8;}qlC!Dj7{18xd4?|-4yr64uCXk9iQ&sO zUD8h#p71t9G4Ml%uQGg#p@@Bh;cE;o&#UFuU9mi2)}0(G5mpH#_&gme=z)s;javT zX84OyClVxCMMU9m3@vQ4PBs22(hmP*_&3A97`mPRYksWWEou!VX#M%)i`r8?@P*Al z8RiUIh6Tf#Vac$vM6^JOULj_{o8F!Qh8-hc2s2XrufIWjhf%;NwBbNh$U@a`6f>Hc zQNm~%Mqo50BQl!80Ggwi(|8h1#b|2#j6$N9IrJ^VXj(=yFq)3h^k(c1nu04vGcuaV zOiZksety%aXck7p8O_RQD5D|N#6^|Wq{r>3pV2VGToI;#8-@dn=3q1%quC8lp`m`I ztI?c{<{J3IZG=ThQL!W={V(D6hNq*Bkw{*N(Xxz2F7&u>Q?Fxr^WhKv;aUum(8#@taKGunjF zrp9(BR>!f;8EwaC3r1Tr+LF;$_DaaucyT48Z5VB9dS%RCMzk~9p3zQ>c3|XASy6Y2 zSB<)z8SP@@ks7Ky(}&#{ox*5$Mu#%mgOS?q$!H%&dognUUz~IX&S+mo?ju|YLQ*jD z_h)nvqXQTnXf(Qj!IU;Sn2|YuBUax|n!FBUBpDsf=qN@6MkgEcYO7C2wGtGa%IIQ7r!hL0(dmpN@iQ2m zsos?#H*Of7XEQpd@5(dGnMe;OsMUFlE@X5*BX|CfKBqd=*1;KF#K;ZAVy{%f_;U%P zs~KI&=n6)cF}mD>f4Pp!wZyhMBQgIfCt{b=&qo)}?&ge-- zPcRzaTPCngafb5*MsEGLCnnRJXBa)t=vhY38E)!G8f!IQVDzF5qdLP-#%R)fnUQFG zg^}K>;cJXuWn>cvOPPu-R_Ap_Zw%)8zP-(ryvax&<1I#S3%K&MrcufXNAEIn3|F`! zi_qv0$=p6=^dTcz%0x!`{?}$_y^ZdSKIt*UqNeCGM$)~{8M*V{Mr~$8e>6!zoaTJR z=xa;KDo&2v7NF}bR$=rVZ$4Ii$(u}8OJdMpAq!?pI8_w}`jAvv#J!72?G0t3{LzS@`0_6WC>v$H%!x_)Ycqro` zjQgy))^nwD^1yLF<6)*4y^z341{lx5cs9nfTb*-vu~)LkcuvN18L|4Q`tn!t2*#rs z&%<~r#`7{>n6auY$asFn3mB{ATU1kXfp{e2g;Zk7CVGe5%XksSi!)x7@nXh3Nx_MR z@e+)ev^rv=e8^>gF60cq`k8B_$Lz z(}8UmZ)T6G$v?bo2@lK3)V7#M^hC07gmpnkcGvi&XL?p`+rTOu0jPGH*JL7X1 z@4@&u#(Oe8knvuO_hq~{<9+&9nbsCnwKTcjhm6guYlAUkNo{||2N>o$i*?aPf0)Sl zAjU^9KA7?0!iVu8jK!|QjC=B#LRb%yqdahYB;%uOmZbHI*mgBDK8CUTT2SuGs;SQL zj89{H0^^ezpUC*6o=zI(R{s>nroe8Y)xQ;E#sBX6FGrLx zWPBUrag1+gd?#azPA%J@+=aZHS;}3E@3yuo*JYT<1m0(SALCaT-_Q7I#t$%lnDK** zAF`T4Ms{kDA7MP6@uQ3%vs{X`eVp+Vsxa`yuez+Jao|bD6Byf(!eVv*p1Y_c#?LT* zp7FDcpBwxNSjuM9y}rx_$9_K+w@iq*Ao?L_$K358B1Y=fXg6B|5aa6LHq{e z|JrG9@-`~bDpmXz<98emZyV;u^LH7)XV1NTo<_w7jK5_pRs5XsM8=;m{)q9%u8ysA zD|Nv5Q^s!pzgXlP0^=_jf5mtbW4A$C$zgSH%P;0Csq@TYr{vYGt8UJAtF$VrAiWs{s;({#68l(O;+;)(Y zNr|HwXH3*8XL2~>g2}RsOD27cD<)Giu9<|48zvs(mT_l23bYpBvC|Cvn1WJV@aGntObG)x@lRauwfpJaL_GYCTem;)_U zB2SXc#AFr=aTA>aYP+GB%*w?5`J<$!-o>$GD3ke_^fQ^0$uK6fF&WNez+j4e`BKR- znVrcTX1|*B32oVLG8dD1n9R*&gn0%zE?K?2K{78Bx3DFTp_+!(0!$WTvLKU%nT%wz zkYOT~*7;GI6()-?S=8DJvU-$HOBQFc6q6;GENMMTvU2j{4q>u1lVwaC+NUH3DE&{C zW3nZa<(aI_WE7Ltn5@8LWhN^!S;?@H`_)WYo-4{YcWK@{AK4b*^0@YOtxmS6O(P2Y|ms{CfgZjoG6{_c3`rj zp&^oc(QL9aliir?!emz~R88HDW4kli!?tj%JMCRpCNSBH$pK9EW+MLY!(?CUUFij_ zD9H;Yy7sp?N{kif4Z?v;#NmUO9BfDm4f!kCcycI{!>nyDGh=O!VDbo)Bbi*zG`b}xFmd}o1y({gE}hKeG$yAoIdxDX!ue*2`*bE} z7)3%!K2!PmM1s4J$=OUqk(j1G|2@r+5t*iw=QFv$G(^~n5RFR7MNBSXqAo7BYEp0U z%{2T{CYPBs$v9OrP$Er{SaLTL{bjZr zOozRr{qRigWAY%A`*#5e4NnPk}(iAywGkKHATUMgQDZ#Ot?=X4S_@kQo zo=7`1lJ}W>!$gMj8Iup0e9UAblaDO4Q18Ofe8neBbojre-TF69oRmLjB0cNOul0Bwarg8%&EC?^;;&tF!_$jk4(O2@`H7)U`d%1v*Mqa{A`SMnHlr&$*)ZQ zVDcN2-}|>4W!j=Vp_Tv1uEh z=#^NCDXohhdoi_9EeT>;Vg_P5!bZ=*_*t^XjKs_ipql~+4ZW3r#;nAA#1LY3qK_C( z3?=%FSdFJ}e*vSK+P{GTVm9khulBUhD~LIW5rkg3jc}MtajBv#GnqY?BjzFIHLTQJ zOg`a8Vt!&VVgX`dVnJdd6DUR+IW|2s_ed;4ENZ@2O;oP^hgh6gidceJa!|MuvBoki zO}Mkj)P>VnLY;0)EKjUUj3P!8D-f#?D-tVNoSp^&LM zjuC4RYY}6JH4T5mM0*r>B-SRh|I@80Ytdh$9@Zl^Bi1K2CN?0%&kYT2w-RB{q?y=+ z*wnPtX}$`H&55muEr>0xjy?v_UGX-yCbqFTi_$xaE+(BsY)9-%Y)|Y)>_F^H2#uZ0 zBRN!cCw3uhJB^#{SQ41no!E=mgU~;}yk@E^{0-Z^iG6I#ka`q1jQJ`&gphh1NbFA> zV55MHLgB8wJq{wA|Cc})e$eT|p~MlyVTAVoB-W{%iQ#Z0;Xch)TlbATaSSnm5cy+? zP?HBdJq>Amkdn0-&m)Nll%iNBd#PaC$2Dk zQ#I#0h^vUJZQr{}BueAkHN-eVDt9v>6_O@jPuw6J?9c!)M>VaQTjDXpyoI=(xRtnV z5Xc05HSjla2XUvdx94EZzV0F(B+)q5v!@=ksD}8>5c!GGC zcw`W=G2~I=G2-!FR&Zd_r+Og96Sip8zv;5pokl}EMXpFZO*F(a#OK7b#GAx(gm8YI zc!_v{cyVxgEmkBpFU*>h|QM=IbD)Bn;AL6xvFJ~M~yw9@(@dokVzJ;f>YPyUM zZxJ67Zxink?-1{r74%fs?Badm1M`RSv`^a#FENq$g!qW~*mfO=`Oae#pAw&$qf?=j z*=l}4{6V28qU2v@h$P4jdx<5e2bzQ{6PFn{7C#{r-mvf(O72X zzYuP^q{u-SYTSXini78y;^d!%3;tc&-@TP6CmW3tz4DfZPeznP{{K;R7GQG}$^Xam z8P}bi-OWpI3liKlcyPDi{yQ9k9PSQ>ySux)9`1)*LK6INm;bkV_T~8Rvrj)gJ=N9K z)z#J2)iMnE*{Y*g+A+S|lnnWmA-}mjc$x*9LSx7uFu;&MA%;mXojxh%*`Ew1w~VTz zx`2Fo=I(I|KoU#UeY$I~UjQCBCO3LXI+GA0TNiFbQM!=?DoCM#4D!D6aYi+i&@y^7 zIxCBIhR}vC)X*9DaOq}Du|7f%hT5oSEWKBbLD2VK=73=^6HEis!?ZA+dXQalELKZG zm;q)q&Wr7u{;Sr^470&3FsqHx0u$dP{|txO4Y?{PDb+Z|oG?Gk1@piNnA>(2OAI89 zYANQ0`D_p$IkU}MrK4B?7KR1E9i5YOsYV*DSOgX|?Wbyr2FV+XgTf_X6Ic>fg{5G5 zSQ?fEAs=akX@a9xkY9-9EFy$Ueg-SR%CI7=)E_29vu00>f>o@=U1zDc6Xw-mU05B~ zf;C`GtDW9z)G&Uo4eQvd&^^PAFj^Yo!J%+49AYMlAXSR2Sq_844VzMIljpzSNH_+L0%zDuqGS!4oE!_s z^=}f84X=HDoB)!~6G6F7f|HF8>O0b<+M&cLaH?S*Gpe~p2~LNz;0!pkH?Wcs(DZ%I zhI34E1S}igBEjZA0A*u>u>2Vz>5YZmYT4=G?@Mhya}&@ zn=Ig*#s=^@xc#3pn03#drMw02!rSnU4fK*OVK(V{58ijoamxf&)`##FjD=5N987?Z zK=(h`ipIc)*Smf<5k9s#nn)54uNDVBg)iVU_`IP_L!_O~;7jix*MHm_vcFvC7E80*Td`J$^L(9;6vxw6oGGz*n-gzZyqWQ)!bn zqC8#J*W`)y7Qu7+|I~#B#?C)5c5A%F@wUNR0&g9>CGke#Erqu{-qLu>;Vm;T_Efx) zc-sH@e0|9#wuPzD>YC|zE8wlz@EOl}hm0*N57uiHyfyJw#ajb!wSlpZ;H_?|Z@urN z{d8`~ECFvVytU1U9W#~P+oqX_w=SOAWj(wN@kHyW#C#pL4ANpYW;f;KJL}VD#5n6>kjQ0eE}k zsnzzu+YfJFTN!j1Y?zb!wd(9YF!p2lUGWaYI|T0_yn_v)z{C?{=An3p8BTMLo2wA- z2>i+Lj>LNi?@Q%hi3-1`blktwlI}z_Vyc6(_H$l=Usq8AzB+A(sQjCU2rJsg( z3ZB#dn&6y%tb;in?+j~GUEiU@bl#Z)wppHcHr_>e=ir@>cP^goQ5+c8GPyF$YPbt6 zi&m^ex%H-t@h-)?#At8=Y~zbn`EtBl@vgwT3GYh0Yw)ge7_PQj_c?qm-t~CbnQC%F zGu{n&H(Fd>Qq__F!MhpH>3@-`E?}5%!@C#ncD%dsYzrvfop^UC(%sDMz*JrXyT`tu zXK(Mrd(fuR-u-wFSVdI2kSml%^&ZCi5Klt;9NwdN&h6kmhW9w$69(hv#dx+HJTPuq zyr=M<#e3RbX_)h#F;r?bJ<7uI^nVTS1-zH>UUd9_$;cLrwZ$vOB5e!wUbSJ%i2M)U zJ9w|-y@e;y(#Idy?^CEVu-@CIa73_an|JX(z3pAX(xyl?Qv;eC$x z5#B_+@viO@6xv20Sy0}`c%S0A%Rf~YEmg>P_C7NiFrD)Sp6!Le`_e2inbUHocwgat zEjn#=d*NB|zQy|m?>oF7@!S+Z8$7%pOi{=d^?tHy8pD4!ZcMWj-miFn$~MFM4exin zKUC|!M@}{JC$VX!M96nuAZ7F?$6pbD3jCSzr^GMur@{~Mhu}NS@1su`<9iDG1CSqB zS&P1bAK_<4zaQf#_&O%zM5yV2zSIA5G5PNPPm%4n@TbPF@H_ZzBipZyrTWXd^Sk)w z2E^}~Vv(lx2dp6J4Yis-41WgvY4E4Rw;h9K`>C0%bTjx(g<9SjU8qw4n&|0q7W{?q zXT_fve>Qy6`S`=}XE&Y*iCV#*6Muv`i~PByF-$)Fx$&L;S2O8vrO$^yzw+8>>n~s# z4bOt+bvFVQ#$OtL5&R|a7sX%97^U66{^FKJUQ?Ma2EQc!Qm&Wj1NXwI3ND8~(qIh9 zvSu3_*zzJC-^O42%OmQqgfITAjK3lNDEzhYSHWKce^q?@v^~DvQDE9Y1Dr(2U(?aN zmKl@cjHJk42j970{BJ#tqueZ`@kbk4qiQpUb8{;~ zMuNd_iN7uWR*r{T<8NaK^;Vo8`7!=>4rY7&9SnZH!>zG2|MG=uUwnz;p7=ZA?~cDS z{;uxTF6Ijoc~;zRO~l3bQ0+8H#NP{lAN(;c(QN@zS#Dv%daJ)5{z3R^&;w2L`1|7@ zU~ExisYk0tj8O;Ui=T(!A8I#}S<4>QG_i#mB7F@3N8%r4(j~IxS&~lhk0H1W|5ySE z@NxK0;~$TI4gLxE=ir}+e=7b-_$M0@c{qKy-@s~dij{7W)9}x*Ug@81^Cs&HXX5KB z@_ii*XE$KIIR^06G?(I^hkr4?*kzg@|AK*W=A(817ujrG3eCTy>5mRTWc9rk|1SLN@NdDt9^a-sQa<=M;@^yalT|_HmO?dz$>FW| zx2XsxMdkv+zXRXxD!1mAyRMX!JSx4E`7R&*F=R&*8s}uV#HogFgNX zu6w>{{pc?{%6|p_P5f8!RhQRHdy6F5SN`kxZ&+CkaARaz?cT;8i~kP(`}prVL3+Wy@%BYcK0+CFXQcZ^jJb>HkT zf5=q$U*i9P|6lwc@xQ|V7XNFd2rXs)^B^YAV zYOn}AtCtq&#h)M`NC-lLxVy#<1d;W3ZAa0x*RZ7o1wlq2;!WW%wdVI&QCC>c_1~w zSp`Q}MC}sHL!ejlnl~n}T_X0^m>VqM@GnTPkRg|b(bfWSJ6MEZMS?{MmL^z?U5@WhJqcbGdg!C z*qdMvg1rc2|GSUK2$iwUh5*-^`w)l&`x5MDt87ZtsWHpDzZH7M90TLV5r~+R2qaNQ z5*$o$7{MWq&xcwsc58nGhuhjmaD)*rt{R4;2#zNZRW`FAIL44$_~oR}q|UHlDtMWPR#Pg0l(SDGZIj(%8W{ z1X}O2bKHYvolkIaec!1B7Z6-XaFOl8(uPCRU}`nf{{)vBuv2#gmlG~Wa0S8l1XmI~ zPoP5YCb*j5Mgq0P^#s=vTxZ{0klbqOr(J5n4JIB!V)Wlca67@x1h*30VmwsU`W3&; z$~6w$p^py|+)3c<|JAQGf~`jP5Ijb3FTsNZ_YpimaR1PUev_WGP)Mwf4-q^<@UT6~ zE>hRfNF6+CJ_^I|IDtgy34*5xo+OY8mHF|pHHwYkBF{|$b`)ElC3vpZmGus-qv{2M zj|g5Qc$45If>#M%7BdN6F{8rmyC8Ut;B^AC4F9Sc!5dD&O8(y>_<-PTg7*mCA$Zs7 zE};^m4(JjLP614kr3A(a3Bg$F%i@w#o&@6wREr4&pAt+Y_=MnND@#)?NwvU+4I}uB z;0t}`o8a@tqvbl$m-e>bh+h$WP4FGTHw52Wb|G(eBTL4t$NfMkdVVDI34S8@li+89 z-`%TU2!18_&1xj3HO7xM*&o(hW&ebe5Dp=nlyC~d$p|O6F+Lbq>CQeo+d+^2-8vHGg@P1?hpuuWJHw#?BQmiFp^e-l=H zp)&UBu_o+TahB^JggwGJ2!|3*OE^F{%tSOaN5Q}r)^LQ=D716R;dF#E6HZS!!yu_M z61tB+YN=5Mry-w(a5&+tgtM8DHw)RPA4@p9A(3Cv5Y9=s2;p3W^Ae6AoQH7kruxlc zC`W*3Sdegj!oCM-V3{`tK3bA+A;N`iD&dA>lechD!leioBV57}xwy4L@eflk3+<6FUD-$$NJs=@ zV!1cr0fhSy{*%ybW5WH68%`Iw$Nf#-Ed4;jgZ@_EgI(ew#x#wd;bDYV6CO?|&L2T2 zK|PZ2s73$?Mc%QZlkga8DXr0j$2CK(OFV(_WWp2s3rXQgR*K1h$UBwrl)juB2Tn8N zMQx0ZzjBs&@RZejB*-;>bW-( z-qffdWwD7ycnjg}E^;fO+XCc{SQ6erc&BM^&5Xjk2=6xYPsB*ltn_;cpCG)C@L|IH z3H3PuB|f0gN#;X>9QbHgE$a|ILiiZrqsBC6%n-UMfF7-VpCo*S(4>^`DMH&KZl$=^ zxA4M<@Hyg-37;qWgzyC-3GItS3E@kGUlG1c_&(t)gs&66O86hiC!r(N6`^toC9Cfc zzDfAjAcb$+RePe=WaC}!opU|wJzEMfNfsM$bwrQp)GNm2G*819S5T!)( z6JX=od|Ke)I9+9*B4ebEYFrul= z^3;_DvdW@qObqm>4dl^uM6(i2Pc#$J3`UZnvhTl`LNiir1&2t^3bPD}W+NIwG@NJ- zqS>v_DvM@D(VRqc+0Y=h*7!Q2xryerc8T0A5VF9d`3zW?triOqElac@(Go-p5iRDB zFHE!u(V`8EDroy3qQ&h|Y&0^KBof254gM^mrG?yJG_*w{Z4M$Z8A8!=L~9Z)PqYfr z3PdXttw^-ez(*5}y`oKBDH>&*S6t^2qE(5U|0h~ac8DR=JS$pb;Ne$oZWygav_8?= zMC+RNj@B92{!q&+RS@Ywv8FSkWCNm&h&Hs^=^Tx#BavGL)~z_vXre2LBmjpKZAP>k z(dI+x#Si)>tzeHC3o zbeU^~OKne-p}L&Neg9pCy;P9)4MqZcoakzz+lj6rx{2soqU(vSYlt*OXP9pwy3wW{ z=g*?fMRYUKZH{=G0ubFQ80&Y^1_rE(+)s2T(LF?W86Hc#+hAl28yn5DO5|<?ZM_zNHe*PrBa(A+aN$NqY4iq>?p;?$ zc+)E4mY5y!?+}e6dY9-!qW6eCa82)SfoO~+Co@E2t=3k@kBBC?$aoViOPN?-kr6pn zsSd8`ZS*Pejzpgk4%KqPxUBtC4W3e~E|j0I{tCTcYqAVbc&V zNjxp_oW#=+&qO>uu~Yt18dACOjCLEmLC#D(2k|V#!-;1lp3U$$gV51AyGg!SCz)UH zY1=Up&qcfd@d)C1iOv2ep2zN-Z=LVn&SwNm4r99oLiHtHka$tzg&ekpiQVSCrlwMT z@nXb_H)@L5_J3L~t?b835wAnMH1R0nWr)=TBOTex+I2MU+ywFR#48c6K)j;OS=8x7 zswh{-j90cMu-dIcye9Fg#H*R(#aecC;x+89wx;Q&sp7RvImBzrAh$%zwJz}%#Oo1n zM7%!n239HE0u*m(_+{Z3Q3UthV^KzWtpz?Q_@5pPAj z4e{32K+>_sA~XJ*60K*&+qTZ++nDDh#Az3%QKYtti$k2K>-9aeox7F2vR@oB`z5T8tZEb)oN$2o2vPkcgO z+c(F@tZFBjT9XA8Hzl6ZUs#V%HN|+Mg!6P_HR2h>=LnMcOk(lpY!gDQ_t|G;g+yDO zb>{A6;`5A7Ey2VW5MM@oA@Rk;7ug=dKK+*vU)p=!T@mR99pcN0uQ2V}?<`kZLm1Ah ziJv0AhWK9MYl&|ozK-}tV*3Ob@eKwqb4Yi7#y1h)Vh7yfo2`{Kmx*sREiL?3uiJ@D z;S=9MtR;ZHPu{h~-7ftelk<6${XXKyiSH*igP-^TVw(t%H#~SrQ?3RGFz!BT}FLATvDn>m`{1&kYeuem1;^(Y(SF1NkXtnGsfEO&q+WIA8 z?O_|d-_Lr%tHioEPI+G=R$1=;XO*jZX{u>iJDzw#zk$@^>QJh$Vfch}2=S*Rdl7#|BE9rENk;qy$)vt4B4U@O=B|0MI1^xd;| zVpKL;vJlC_*2)I6h^|*9aZ>>C!MMFR$#Nu1kSs&8q{FZjiEaM3&ZQe)t@M#rcg^pT zWvv@Zo|ENCR&v003XH_v0%xVKOtOlp>tvL*pyo%(s@AeHTIBpmRwvn-WDSyyN!BD; zpJXkPbxGDHSw|va7bS_YR`>Npt<~LV-hgC7Ma&Mj$Bhi3{1wS2B%70rCfU@qy5-u; zWw&N>Ew}~AmLywQJ4#?AA@ay2+mP%^vMtGuHmD`rk!(-m?tfDqh1uv7@||48olIJx zJCnHkpG^xr-Fs1jw;Qr~Oip&Ufl1`8tc-gq;@*xSxsPOTlG8}`Avug>Uy=hz_9NNf zeDaBXM{MZuyVXDnNe(1Al;j|igYEmy#`8nWT`yr!c;B?!gt)84naw^G5Bn@XywiCYAea!y1TnZaKkep6( z4apfK7m}Pwat_H^jvfsG&cN3Ao19Bx_P=Z2^GPn~BhiA3QGOB0Wh57qT++vEiEatN zO1zxpN^`>|SMqu@Qxt`=k68ZmaaohmZnzF}|n@MhU zvUiKJ42<7J8*)f)vu!!oze;iki6s9{67&B%R*1>>m>6hmQi>wlr(`U;pX4Kw2S{Eb zd647@l7~ptKo2{vK4ROF)m0mlCV9+}XNzTKha^vuC=|n=A$iIXqYc|;TD$nOB+vCZ zqmv^tjS^G-Brh5Pa;2K2y-e~Z$txtUk-TbaOnPPV_8*croJM-xw2cz=H_p68@;=Gi zB=3>DL*o4ZqD>_>s>AYrKr)uZC8`TMy-G4}5W{$qpGYQ<$;O|glaWqhJBQOrZPS|m_g+LgIq6iSQ;<$+ zLZVW{|7K*DQc58`o75v+o75+rlQbZmnlvPBkw&CBX-t}uCN|z_X-pKRnOVFVe`S}Z zh0VND^Z)b~+|kaWrWNT>(l%+wJ=RuJiJv-G+9mB7!3WuyU#osPV3pNOEgeQW6X`Uh zGm=hAIz8!h%_|u^q%#=tYDa}m)Jav)Z0OzzB^u^gEnU4#oJoh1&Q9ujw9)CN!0BA1 zOOcKsU5IpUQm4|>c^nNo1^kRYn?*W5sT8lxO&p#D2azvKx&-MWq}utTva}0S3yY#^ zaXp$nCK{|ROIk+b#?quKkuF2Jya{tUl5|?ZlvpyZcDlz>1L$slWsz~ z0qI6A`-Ube8ne=kjatb-pZ}vtH*KnEyL!^iNw+55f^;j=Ev=(TV+elOdDOKLf zu}MC|bURY9dVA6xO_ink3XF5w^jW8rT}XE#-Pwj7VQAJCQ+EVFFzPAk?xaVN?m@ag z>7Jx}JGSiQu#GWn(p2d_q~`M>-B+Qp!V>?f0n6?rNe>`Bg!DktgACrl4z`FW)S7;J zDCrTThmjs`1V{py6r_23WIv=wlb%d^4Cx7^ZXqB&j?|9y8oXGf_BDw*k@Td2 z3Da3EOaN5SsV@DLL4eap&m>jXIKz-DyQWbZ6b#sg07v#Yq@R+WOZq73d8F5oo=?=0NlHmCRNjIAyVi-&#iW;zUTRV;JUSUCota)vYFj-?uOPjW^eQ{iu%`|WI*&wZ zd?vltND}ed?37+ldMBwG?iSJ;NpCXOu7;2FW@C}#3F)n*w>vs-vyAH0hW`$`&DdCd z7wLWGT}NgpD8fb>D*r#LVEnEXFX`iNmu5lS>BJVq*RKTi5A z=@X<+kvii)bucP)Imy7&`mDC#NuM$Kltr38NBWYJ3f27u(ifY(Pc~~du$M_+b;w`o z7o_We($}n9?QKq9Cw-Un4bpc=-z2p=)*SHL2B7L|S}of^PQvs((vL{rCsmC;FjRdR z9!omTn6DXx_<7tGq~l3HCY?Y!(M)K836C1ch_USfj&+}r#iXB;{!aP@>9?d`l72(# zHiD*Kk$yceVMQy&^$5Gg(fIQn=`W<;lm0~d1L=IN&g_5ob*q!NzA__dIXbAN;X+z^$kqei);!q$ei;hn~H3RQL9A?XE~60WFeVP z7BqD?$yeXkpqWK#P^(v#kWELHlGS7xS&J-p1r=naF;ncl@rQv4H`rj5Rb*`&FB;E$ z)*%~4)+HMt>yZsLF_YI${-s9VvZ+nN#fNMfqum^AX|v+X|jdM_9t6}Y$LKo$wrYaMmCacak6E|mLOY-%v}Mb z7BTXcww4lAQaIwwqHmBbOSS^pa%yz4_oDI$POnvnCvjJ zLsTN!p$cutXb3RzID+grvLnfkAv=o9hFg_g{w05%e3rG2{%gGs(^}{Blsc&zX^(Lv{g~o!1~ckL-Nw zzfv!*-N`N_yO`{vzDhARUSdbcRl3D5BfEp_ag zEtzxsWY;yF%SreRHrY{!GEUw^b~D+nWaj)g!I0X_ZX>(h9z~Ku!+a;%!(?}n-A{Hm z*}Y`<*xNp}_x1gRQhk}k^&zqcn+x|WT{IhpN64Nedz4I)^O&)sd}d%`Og4A~OrM}N zFll3j+5&1LCfPIO(p}GzeMR;h*#~6Lll_P61+rJkUL><67_ygKmBqAI{u-uT-*@_7 zwpsQ%**j!!kjekA7J1V~Nm*ak1>Ux@)IqHDcgfx(d*3W=DLsXj_#xRu7a2=7j%>V( ze`LMDBw>P~YAk{5W3tc9R?j{m`;^Sx|IiE#){DPz8NVcRt}10$xrX6uvOmebA^Vx^ zTe2TC(Td>Fan)42Gw$t;b=yvb)EpV3MYH`Mj=naQQ1XCa@} zjyy<>=d+Q|VGGCkaPryf1vaz+&M?nOJ~#PX&pV>3{{4SJt zOB;Znaq)%7#nOezmn2_=d@=Gxg|o4C$QLJH!sroyB=)j=@}6-P_HDh z`LdSLBFmGnO1=X5%H%7Oo8!xn2-vtWihLCtm-VQ>tiF6T@-o@Taj-@zPTZ{ zw_A|Q#UW)o${g%6P4lhEw@;|YdjT~)OquNlAlDr zKlw4_2aq2@exT#qLF9*$AFQ~0w6y>i(Gq~Ts$1`LqeyI9dF@eGeJVVP{Af#ei4OU( z9RCtMo{I zIr$ajSK2Ef8N@?=wab2uLI-vo`OW0lJ8Ew*)>)suk^CkT1GTc|;PSxdw~*gTeuo3Q zjr?|bE+0$o-2VAEvrH^yJo!)L6UasF#Ck8$`LSc; zC*+^nP_DsLKEq1<`JDV4@-N8$Oa7&ym2Ui4k6)30Et-eUF`0?9tik+S^6#C%e`iNT zt@r&v{-ZHmY`^+&n|$RzQ%p|&3;FNlznZGotgKU}ZQa>%)6f;uP6jM`lD27sWDS9S*Zu-{fF^U0$w~WIm zrlXjKVp@A8gpLCg(^Jf#DoajmiBOuqn2BNz3RPh?idiUTHDK{X(+#y|F`Qy{>(ru0 z=`t;fIVt9*n2TbBr3krRN!u6mP&mt9z%HFa_P?$3QY=8Rpm=MITP#Gea1%Ff7f%1{ zm4Pizv9ygK#S#=t8uN{SrHnbo;bkNb6eB5CbdSqYEaxK2Q>_)K-#SV_JZ7H^Mk?l>BtEE(B8Q8^+6k^NHF7A#%7CRXpNwU4&g<{txu0#s)c5jEo z>>>*D|5NNm;p~5vYc%gep?cXs(4290{IOl26#G*gVC0!7u19f@2FLFDS5X{Hafqp} z;!ujSUF#f1aX5vLA3<@n?rEVo(v05XC^rTEuS+?`g~z(^IEv%#0%wCff#O7plPONJ zL4Sc~Ep!ozQz%YzFsB+Gy)yXIDbAocv&rsKrx8=Tlrn zp`#XV%qlMIk6FdV6qgLfFKyoHk>YX+S^LvdTtRUq=7e#(UzEKW?22nBuKgSQ^%Qqd z+(2=Q%XK5gO%yj9fZ+RCZl${tkIQuz#ohfZRe1?q1pUZj*1y+pYT#mkft#VZt_QoKs>mQy3IQD}eWe<-vBtndwrH?1tm z_UO$Vz}pluJN4=v3R?o8c#lHg|N1+Ol*4%UYAnS#ijORN1F-&Y6cZ>uc5f#(i4>n0 zfPkO++Qdq z)xT0oboFSj{cb`=W~g(P|iv@7v*g3aX96il(W0|918nc z`q)NL&Oa3!pR? z5am*6TahcyJ&ttYvXq-pE=RdGr$>a7+;@qgTeTQly33YkZf#U+_uPQO6R>VH>KQ+ay!b+ zDYv5Bf^y6MpKn`JZeytWO}Xu0+imagIOqTW8q_UrmJK~SIe?ul?`RdVE3KO;ccc8m zG)B2QkpsxEc3>wo3tD{VzjkCbOno=JHwIlvhw*MtON3iB4Q@ z@ty;|lJY9btCiR8NR-FS$i9~HCd%t5Z=}4Q@&?n@I##Ao1r0l|{pwGYH&fnXkD42n zw^H7ww$L_mErHoN{qhdV2PyBQyqoeam1V^ZQ%&!oyw42v@?M(_X*??Lr+mQnpUd4R zcZAXZ5anZ(4^uvBHf{Nc;nbv09#>IVKGVBj>gx$B+JK*=w1{D|$EPXBV69P!_Vhb@s)7?JKc$>V z`3a@gQ>@}fnB0=u^i_&KpVybzt_G7y`7p~bX*C`8zm#9mnu+pjS{daxw5Fu|me!<{ z-%(UR-H?Nc)R z1GJ{5H7zZbJB_L-`&0iWLTkDvPHP5QGivF|wxCPT$lcwVnbu;oW}!6?tyyW!NozJ* za~L;T!_9@>n%!mu5*`tt?$(-%*4&P;5q-@ju3GxMv=*i{AFcTfQoh#K0xn$8p;}0@ z>1HNQYY|$Dx>k0VU>M}$w3er}1g)iMElF!BvkbLe!7Nf$Z5dk2(Hd#ixQy-AvS#o| zRJ3MfM6N(MZaXb2u)IqO}ICRU8egS}$v@X7-guR_~i(Y6ZzvYb{zA z(OR39+F~7Ao6=gB)&{h+`F~pdX=&CltzCj`@eN(L5v`3~WD|R3={kVf0Bq(SH>b5V ztu0)9OImXNI}Cl8ZD{R4Yg<~|HMtzv_J51-NK1A9tI(ZjoknYCT8Gfuh1Nf5N&oLf zYd4os-=+}(d(e{e$7c6`g|x=d+MCwCwDz$?hme;1|9#Ag(AwXHngS?(AgzOFN%;=~ zjJ%ZAp|p;rb(jM?T?T&B=PE~j;ci(Ki#s|M4rrghC={95mpmjT~yJ_7)>rN9|bqBefTX$)>-$shoJ+vM-Pj%~FTKCaK3qPd?an3b&Bm zQd!Ts9`qcomuWpu>qS~GG>WJRX}#2cw663DEn5gS%+~MzL+c}2uRA56O`%8AdXv`M zv}_@u?TfG-`GzVO(*{hF#v>l<3%)B2Xycjn=2{l~7m)Jd7v z4^%C?!oBq)t)Hk;T0c`wN$VFXq5759?^5!#-2NXU^$%KqHu~R4)vIb!s>!L`C*a+< z;1b<9WpAgV@~MVU(ZE~PDzCvn6;Q=gA(hko%KkqL$sqpB5)D=ELboDNm3?e-dQ?^Y ze@9cbscNc@r8jz5UYDvzH8s^xssZ!6HUoszbv2A?8hbqbSgL8ILaC-RDXpfbnw@F} zs#&RKq;iL(D!UiLTmjWAHic|vk#bX4vnicw`2XgW)f`lF4g$~Bq*KjJEwFiLkD{8F z>MpAJsP?3qpK5ig1*jI2rlnetY9Uj+)xz#^5voP|8raRCsTQYNk!lGlRe4DVvlP|R zrmh`;OBqSE9M!UaOIe<3h5lnR2d`G5TE!K#GSw){u7BgHu2!XT`oCY|8dO_Stx2^$ z)mkpg+EnXOt<%rd2V2h`tqL1Zji%a=YGVhy(O>0KZ88Y6Db?mw+W$F7wpDqH!M9sc zsV%mq+SXy%rolk99n}slW&6RD9W6`0+<#E*Mzs^w&VwntQ0>~p2UB)egi1%7|H`!& z)yY(2sMI=pySMvL{gZ0n28L=si&(w(ci{nl%X<*j@l*#>9Yb{p)nQbJHiQk5XG;K7 zM^GKM76*JQ)$LS$7m!Q2gUWsXQDA+@ zyJ>q=_fV<--b?i|)qPY?P~A`U2-O2r4^inlI7fUx%fnXCV%JbTO7&QS$2~sY5J2@L z)w5L6JkL-)MfG$)dsF>B2cDyP!BzA5K}uevdg(80j@nnK-llq$>UAo89!s@$U*Btp zvBx*4-lTe~DR&T$J^`Rt?^1n0^&Zvxe}CJ=sm4-$Pc@F}GpdiMCQ^-exh5EZVd|c-p=#)vjrJn6 zhtr;y_UyDr(AFm)Rmz-AUfOg0E!W)c)jWfF=cB!#OVJXrvS}c_R zt^YxL7uq|~-nkFZuj#ITQA>Mw+K15IgZ2Tm_oTg#L$Vj`F|Hj`VzrMHAzQgeJOLwDy(SJAX`)S`p``!kg_I-b4 zr2PQx2M5z1`dj)Vw4c)>?MG=pM*At+kJEnAr99E6){y_(vRLG4+RwVT&-_&vMQA@y z`%T&}(0-Nni?m;+{gP#IG&_J-nmFy(XunSTKmU7crN1%w_AT1)(|()wyGo(`PE!i) z_x^wQ58T@i2Xl?1mcsdndJ5X(X@5t10`1RfPjp}()Bcq9pz=3rKQpvqpvAwS{Wa|` zUHreazxwNy%lnNBo&N9h|9jd$xx^n__@luasXx>Ho%Sy-{wwW4<==n%2lXV5fq(YP zttX|P?60z@C-38_rAvZ_UqaIDvNr~!BR$0&qF=;UwB+y<{f-HKlP%> zu~RQhy&$z+fN%6Pl4c|=(!{A3a~T(>UZM})?v zh5%)xUXgl~i>ySwvNd1R_3BlqS99sBHu$M^3d}WxAzYJsd+N2QH=$medVT73sMl>E z9sczOb8SGq5%q@l*wn=e-FWcrXzHz~H>KW!dNbfM@G3aR&?-m}3oSobm1|D@iVdOzxY zsGadYNc`V2?oaLPfA@;|AnHe`52n6=`Vi{lsSl+-mijR2!{xQJo0sY%>{w8JB=s@W zN4f2qBH(Dlx!Cx|D@c8u`8}Ldm-+s-R@!~YIVClliJWio^rM{S2 zmp@->)8U0@5%JWQnP;HBTpmlCdduliUrBu(^;OhY+a9@Pw1lL%`o*>8=9NcfjJo;t z)Hm3c55sUHwWzv@`gZD@sc+SGP2K#fP}f^g-zL9@wph6M9n^PG-)Yxg{c~TJayRuo z+LO?C=3B=5%p+9aPyL{aJYcnu|D=A1`eEaw9_=DK!8}U+8ueq;qTzAse^Wm}{iIb~ zZt832GY3Nb6!p{Eu3&7}=Lu@%dY1Y*^Bc%XYhKf{s9&IdnfgWQm-MMV^G9|3jQT6;&#Aw3 z_`k4Cr&iE^6%Kr|dwoYu?au$I7OJ4!?AEg1F?3bx@99W;{Gd;!*ansQM;B^DQ2b|V z>3>Cjb>VMxq)vaQGo^df6hN>3w6HS?ok?9}GKDVU)`B~78mmhRU@8|5p;OXsp$-}Ew7zv=**AYp`BUHW7e6@h11iSfzFH)2Ho96XQqKqE~GQF3ujTNM1}5g zHaf%U%;Dm*E3{jhJ9D~RbGdK?oq6c!9xX$qkj}h=5xZ5x!7M;$DLM<*7wtf2Av%k@ z#D!hB2%SagEY@ThgkM6CuAn9DOCpL}#--^<O8%?PC_1aqS)0zP?r}92u0HrydqD-WCLO!~q3_Y^tTXs_T{`Ox#@Ba{ z8@O=8|9Qze8`If@jv8^ad$lQ@t?6twm}_&FxP=S1bfGTcao)C$T?*tG4 zM;F<_UdbcZ(bW)tQMD7DgWOx40uar+(AkyFK6G}YvzJTRoz5O~_OvWFS&6-uU#2t0 zz1rIn^*_ExXJ0z|)7j6ZYcFCSzk58ug%0vYb)19g97*R8gRv41rE{2z9PYv+nsk*# z=O{WS(K*_sA4BJOI>$C|=^XbrUY$VaL`!!*Mmi@C!f1b*@Smbk$9d?SM)xi{r_0D0dO84ptg##<>P3J0uPp=XxI@i#-p3b#&t{Yh4qMtJXp0LOC zs@{F0T`oIXLgy(Ls%t+pbgnDvEOmcn(W~cN_&l8#>SSKki_VL5UTWeB>AXTWqw^{q zsl?ald_w0xbl#)$I-R%ayg}#9I$Ky3q4Snd4gBBh3LVaO=)Bv+U1i^=^AVj7=!~WF zq2XVD&ViercSVdFSm9>}GoH>wIul&3O9%0MY#g|6G@VcBd`ssuI$zQGoQ`e*)vlEh zbiSmcMduN!ul}kaov&3vI^QVNV{b-P>N`3=IjX*Q;SVnSap+7pyU5RUeo@46^;eOk zxFhU$x>L|;TJ}%6lhK`o?xZ4h=!lN}C1zbIe`^%6&QNuyq&pSeknRw=o=cIv@7lfV z(+$+h18W|nzk3zYOds-RbB~Z(xl)81XaGok=iO9m#e! zhVCqM=c79--MQ(`Mt4rS!|BdmC+@I@A)iA{Q>Xu+JD15ocSK*bM4lm>hwi*4*v8L~ z=+5sF7jWT%bhY!xWLvF3*F?x#v%4tW)#xrpcUcFvINc@aE=_kyy0-p1bf&2XKKXs% zQ$a36ccfB`fTs6#m!mt1?(%e3qPqg!6;0Z$X!QtDxbncwBP27{%H36zNOx6*mT2+S z>25%G4Z3T)$2DEJmbFW^Fx_?Nu19xWB@Ufwd4=xn`X>3(4BZXsZccY2x})iCOm`C% zp`yjZfls%jyD8nxhTgKAvJ9-Ek0m`xcMG~()77i34B?EIyZ$R0wxPT2z)Bk!el@E> zZtn`(f$mXsccgm&-T$MzFI`nft+Nx|T^xXY1=2cicUQW*J2vj7u+Ei5X!jHM zj&R|T?v;u=n(n1^kD+@a-D9m+c8^o&C^_DRCn$93C()J23E*V9|Dt=Ui=QId82I!A zx~GXqHKL>W47%siJ(I3oy-4>gd!<^KG@hf-bVc_(x);(t9~lg86fm`E(j}5Eag4pV z>HAGyx|h+F5UL?WfU0vN-7D$dKvy`gr+YQsYpezB?X`5Tvs@Ak{l#SC^Y5yKSmd~L zlR^i1i-B2|Tj`qer+d2#?-)$KQ*T|$-Sp0(dk;PJ_j~DnNB2IuW9i;c_cgi?(0$y2 zJxKQx^*s4Hv$t z(9!d@7)AFTW3MSj3+cW`_kFq_7+QOjvFbX$Nsd(LH*}>OzN9;z?q_r-&^4V;ccO99 z`1T3iPZd$O8dT0o{9IJg{lfK7^#u(9%JP*9zgF0kd_IC-g(+ zH-sLhMfG^&3WlWT(~Ddrpck6fa=MFNOfRFStAbL;Lz5`$%e|bQwh$Hc%4USMLZxkb zl~u=Or&rUPlU|43wDh|42I$$|9(qF+YBW)3oxL|Ty6Yb&-f{rFnd!}9y>Ep@8;wVAHhQzu8{V&`Mu*-UhQH65xfG!{g5Co3 z=B76-ol2Yk>1{-^!B2+7`=_?ElzJ0dP~rA zV?l38dP~t;hThVB#!A5&t4Au7;X!XXdMnXep56*|F0Jyd{`6K9@_`kFWMx%^-YD5) zdQ?bnReEdFTaDhD?$zq_)-X!kXyOQ4%kpZx=&eI)7yaFhL**+ z`q99r>c|_@+lk&L^md>(n%>s*Hl?=(z0K%tE`w)ay;tnN^1tY9NpCAf20r~-q2tLm z^rZjCJ4xG)-uCt#0TCm6;}$jIj;^3f>FHya3Z)FqMxwVfz1>}87kayDe01^M6k6O` zUod;>(Rzx(JHxN1N*zgWA9@GU+n3&c2B3mw61IQR+n?S6id$n9PNVh?qIa0YgWkdP z4xx8wqgMz1;XXiYXb-1%MAPEdF?&bRJDJ|m^p2x^DgDi^*WNX)Ic?e|Wk{PcGu$#WGcz+YGcz+YGv9L3 z6jH_~$-Do}JI6;y)@U>u&5S%AYsUA}xR6F|sEcS^Y(qe_y~JAZQf*4{8;#3p+(bh} zT}R_e8nvOWs;4eAuAy;p93q46M%=V&}c;|UrM(|DA|BgV6* zvu=aNV>BMuTOHa0#mG<6c!q|U@o6nZpZn@X<)Wd_0;})yG+v|e0*#kxyh!6EA*zwX zE6l2|AkkvdTeLsebF=X}jkjpLLE}w*3PfXSS4`t=5hz)#L%vHx#?*T>)X&p+pT-9? zKGYJmyM82y=F+zQgoXrvYKcNrC*1gq#uqd`*X9uZp+fmmmG71QHO;wbd_!{v8e?hv zO=BF5pJ{wc<3}3fX?)jj$M0#F^S2RNTbRZK8b2w64Bl0ypz#ZhKWO|)<98arnR-%U zsLJSI`BT_v{H6RoTm3_GD#2;|OLG#Mlhd4(rbPbh(@h_6lGL1n=9DICgs80&np10k zXhL&Z>6tY3UBFrDc$(AEoZbv88Alk;j5OzwF^@q0RB4>A2?nmNrb&D3raaV9$ao0;ijlZR$Ov(#I8Dm5#bI<%deUNQx?r8&1C zG)EYmhvwom=cOqn&PQ_;&5<(pr9X&Rn)B0Kz!`gkv*J0bu;yBb=Atwgrn!iet}ep5 z8BKi!&`aj?%_V3qOLH{MF*KK?X*|+1my^ewSY~OO%P6O{qmGp3ax|A0pJugKK?T;X zvbhqiKWMH@>pq&R;2c47RhrMzT#e>QG*_o7by z+tJ*erc{4NnmZ`Z+&3yz&YcYIOmi2SyAF()W?q&HasjfIz*Jeh|$=jCgQeBF#fB=|CeMPV+dLN6zwM`v?@Ra| zXL6c9(EN+$k2HUwIl(x8GWfIdj~Fp9&nh&3rTK>ue>3>INw|^bpUP9SsTsv}|6>gQ z;!KLfm)5D$bDYU0!l%F)f-@z~G&ob$beyR*qeD+OXIh*YaHccj^vW|r3KfPK6<1^? zBc6jZGtO*A8HzJYKXukxj?rhwnZu9)OO+PC5oZ|AayatVQQjX-jnmSR<+ujD zfe|`VRFo1s0xfTV)5Xy-XtxngTpPqnNpN!GPjNEkQMLmI#JxC$(aZkraGWJ@=E50; zGdIqBI3sZ8#hJ&-y|E`#iymo0ZZskD<1AKkh zzpD0-x^<+VIIEk^VlJu3nmGSK_QE*p;jC?jS_fxcEk#YLwZmE8=o_d$Y9(ox4RJQH zq>XSkHlj$AR^GHHS$Q_c*%@aGob7S8#MxHZaJItP8mIRBVwSq$gn2vbIa1aRCU!?0 z_56Lah~!;xBz0GueQHElVvR^gVI*(pxQJZ#_m;EZLMM-WO+oW7tnqt&`v! zP~$iU;fUwgYT_J%a~RH{R^__xBKB~7;%L2&#JK|JD4er#j>b6+=NO#haE`4%ttym{ zl8&ANhjW5{Y;{gFs9V5rPBv(J#GF$FH@4Fao?-Azfrgx=h%jg_-2#SlF3x#47vr3d zqbGmj=q{d`4M)#^>*u`$=Te-@P0nS5a_sg>9I=dj2ym{(c>w1cnJwVxD*(=QIJeJIDN#$gU!cmAS()>o;!1qT;KK$V!O?{v!ym)3SAhBr+If=JGB{7snjGh892q{( z;Jl6_*4DWh&T|HzH~50V7wf4w&dWL@bzZ@FRaXy&{w?!coY#a$=$7;b&YQ}ti+G~* z+c@9iyo2*Tj_m)^DLu}6${_Pt=L4Kiar6}cN9X@IALGd4k1TQMd!KWZTAnYNpb$AHHlDk$|{yI{b}i2fXZx5 zL90t^N?Jo{O+{-)T2s@SmKK&ejWngs_Ura)O-E~bqt77i+xza*nn~;18bWJkrbU31BskQVW$8nimJnzS4(PHNX`NfIsBQavRq zTU(%7rsdO$jS|ob4T%KGTb{nG^=g;UT8LIkOXOs<3R-!cWGa=kD!tXRv}I|{Wl+|C z#5nRoB$Rnf^1QU>*SCq*dCbsJz5*nTHlo1K%n#!TDr^P!oHY_C{^xBg}wD(mIjWQG)A>SnFsb9z*L`Lyj|eyryc0ktLwN zO~^@hd$P$nMW7H3KaJJ}v`(jWCM{h}t&?b-)kn^@?B^IfmzMcet?cxG$S{K!~ zwE9axtxIX$N$WDR$>p@Jr*(z!m<_L_brr2^4ZqspHD(R5_I1jm!&Xdf{=ao2t=oi1 z>n2(^)4J90Tg1bw@vU}Nk=qU4A<**PC5RP$H?4bU-K)cG<{=8D?h<&@x}Vkqv_8>8 z@>>tmdWhC@hCfW}5s8X1{83tu(R$MG#|=IqP{Ti3;{Ts9==DtS-VePQ#rVSHc)= zYk)-1$sls4#T|+}o&I~&ogQ~4+!=6Z6lUjx(Hi!ka)#i}tbu z*I!7uZQPEQGRG8>iW}gTxFK#AHyT)QRY}6_?fk+`aCOfYZmMw_C#;T};})ueh!s5# z-x0UMoqO0Kk~G{@(qHKf8+{J$2;6y_-6wJ9X`~VYBzBk&cci8+_L4A&G8%*A&X2nQ z?&-J-;%{@i77SH)cxS5AB02X{H#<#AVNv;%D>Ic-UTdE_`0cO~4FaaS1_d4k5^ zNz%v@a96_>bFHh}=Vf320mtF4iMs*rTDa@su8q4c?mBwpsmv-R(o7FGxDI!HolFj! zQG)$&|AV^)?uNLV;BKVBjqb)e{AICP#{Sq(aHS_~hP%1O;K@<6QmzDXNtmCz<-o-@ z#dWvFJrZ{t+&yr&#oYmSJ6zjkuT$Bk6pAax-_?rS33q4QT?XctGZ6IjMb%sa_T1gf z>=p&2&AKP<{_;ntn&Xjh0;T$I$KF6IRN)y4WDyu3xIo&MsiB% zse1_SVFL%gf_tbIp@Z* zqj9_h_34Rf?n$^(my=cC$fog}ihG)PLj7mu(m$F$XW%}7dnWFUxM$&BhjSk9FSzNDR2yZy6f1Zhz538MBJxUZqd3%oh7 z&((UwggSvYao>_g*Xl?q2fpIG{SxjwxV>LMT4#G7_jBA2a6iTU5cd<@k7Ur{ek?j@ zM(JQuqCDo@|Kfh8eM2fDU0Nc0-7j#*;eLrbR*zY5zrq!BebadG4*heeWSsSA89o|s z?0$aJN!omxDe&p;l+3vUKcObPU0mhU!p0*`17BQm*bUqa-zbU zI(8)R(yQ?1QXlIL7iDzRcyr^8=+#AXsgQZ`R>zwUZ;TmXB;F{z`ISd}wYLD?LU^(T zSi+lSRLZF%B4lB_MYOW2r=0&Ia5006<1Nvrk2cDZc=D04|6b`Wjkg@$GI$cXt*4G? zCy}iO-tu^I{-^Xe5w#-TDtIg5tt^g9JbxqHUXFUhTNQ6L6(wqj-BhxRI4l;~u<$MC1edz_9m+!M6l z#e0(Wx_D3Fi5q-cUq-xV@IJtM7SCSyyyx_#&wC#41-zFHmx1<@Xj0Ei@LtguXI;#( z_h|1meVg`P$9n_sZNo*Mw`vA~c<F@eO6YVye}lx7<3f~?<>94Qoh0a7H_Qaj1#EzdR59Bk5?P#d*M`~ z^8ZMCS-c6f=fwMo_GBjQXS`qVG};qSLxAvpGx)neT{ZIlq&+F#UwHr6?cW0RJE`|C z?MVhM*h$!GP6<@djP1#3qv09tDQHiri+k;zK%TBs$N z(H5mW+K|NzE^eGl2<%&FN!m-%UfOgXBN+`@MpLCZ)TGN9WqI0b(O!Y}sQ_ouxh?Y(I4 zB#RQXcc#4u?OkZgBG{|6ZT&~aLVI^n($)+`VOeFeac8TDwD-1*`xxAp_I{F47cMl{ z0kn^$eIV^4X-fw?jP}8_57AVu_@Tm6a~iX)3$-N#NQtzMGI+GXW0cKSjP3R~gU8c8 zf%eI?Po#a4+E849DOMx2Po*sjT+%M589bf#8MJj(hxVBU&!T-c?Q;#+^`8Mb!@qr= zB67}^{7Zrq#fOR$`%Qc1O&J=Vc;Aa9I zi2@Zkl|boH+NnCwnMTCgCAZUA_UY-<3g#L$rdO8MWNbOp>H(XesLd=?oPp0WEZ9 zwcFVY&aT8UFGv=<)u#ZRISme@(-6e)CLR3*K&K_J7pK$_Q47;)8@)qkBpshlLMNaT z*1lBY&*?;T^rL{f%}!UK@uze&+J{bNFsIXh1U$3>or=x~eGqhpBfk#m%w2n6b>Q*| zNmd~`^Qu4Z={8@_KX*o%sQKwEL}vjy3(A8`J+?j3>PUDxi_pww(prS-vy6U&=;6!JS)R_ybXHKJ%3P7oN|LLecNJ4?1f5lNX}u%< zU)V(E8pgb)!L{h@MQ3d~ThkF+ZA@ofofLJ}qhlWqI_s-H>};TZOeR{L4SO@9&PH-8 zxG`@+XHz;`7`_>u&BY~J(yMf~>?2$CW>B4N=!mM)&32@-oux`~vMW$Zk@G+6Qg))V zvm{9tD|8owyVj{fr?b1kJq*ex0Gp(B_O=@BV{l)Aw#qGK?N8?!BOXBKKsqNGeh{65 z=^SDBAqEdMco?0-1<|YiCe=BT&QX?hG@auNImV!D0l3qU;|-o5(2x@q5%1kOnT~ns z&M9Ly45M=n9eXZx>{-y!?&^%&gU$st51kA9oMMNo z=v*S!*5_U4QaaMOI@_mnxjvOT7Azaja~qv&>D)}`Iy2z)bYv{wAQ|h1 zqjQtc?N*AoMT~E3x7K#Hwz!>+F8HWKT78@I!{UxoyQG6QL|a9Zu+3}bWNf2tXc9oI-k&ap3V!>)pa`2d6CW= zbj0~fyNmM|D9&Hts|H^aXf}VnwxJQ zYe4?Y;+@2e{9EG!@h8Qf9Dg#cQO)2_fj>3=l=xFA(fW^Z(!)iFua6SB#h*@zVmE&V zeDmS{jCQO3AHPnT8Gk6gYm`~=XT=|eZ~os`|8Mj;@CWeK^ULEvEZL_w@SA6KQX+6?;8@}NB9!|sbBd`^BBKtxASWqHDiij;+ymLbE6aj<+1=^VW{wj z;xB-|CH{i=E8#DMZznwX z3*#?>zc~J)_>0x+&o#tf0)MowOW2~cu3-2};V+9n27eiRJO8InG9LWp@K?ZJej@%A zHG400Wn@LdUj=_H{8jN+$6swwIQ|;={qrAs{A=T{hcA_~!ylxeep&eI*Es%v@HaNa z>?|O^fBuiZ3I1jx9A8d>R{ile$M2o|*3Y{Y{vP;S%k zcB1ni{GIW4$KS<-?~1=$Ev@FSar`~;^~?wSy$tT%r|*luKmLBoZ1PRW0sT~IvTN}V z#y=bX5d7ou55+$kUmElX<2=02c_jW(gA%NWW8^~^{;~MS_2}}8c2(d6{8RBy#6Q_2 zoYYIzBwMRg{j8dTe-8d-_~+tZh<_gb`F%}fWwL*!hkp_NCHNN) zQcQxZ0@)W0|8o4R@UPHYE%eHXG_l1o|C&DMb@+O)5B~M|HyCmw{>}I|DML@%Efe9l z;opgWdriT=LnW&^>RJjW7E#_C_#w- z7{0iy$MHYHe**tS{3r3B!GB6P@t>YZlV|bev#*Gibvzq2ioa0f_%GqVkN>hUyn_F# z>Yx%{!+#H7J^={+hB7PiCjMK7yxpg~W4G_>tr($~_k$Y8mv6y#29f_U{!jRy;D3q# zDgI~p|J5YRh5tFe9sXve2p#_`d^^U+{~G@rD`KoNs}AFkUmo=E@%Y~v(T)Jp)E|0c z6`!DND(z?d-|&AiPFer&m20=ZYtGf{|LZWY;>* zPq09Z_bCezEK0C2!6LO*ea#o^-Kr6mAXtH5G{G_iOA?GB(8GTQ`3aUDyd_wcU^#-m z^Y69biUg|?tVFO1!OEJ^=rtr*jbIIe)m4azC9uQadidG|I}ofxuqna11RDqsft~_K z&_Dkt_z%Ix1RE0cJbz#ECKJirj9?pr%?Y+5*n(iox~!fiDs$_8>Dv-)*Q0AG@=?Qj zb+9AB?gTp#=&?TpJNJ0BQFaxaV7ET!9t8Um>`AZ>!CnNi_-~_iB8bF11P55I{RcH6 zIIu6_V0~%_hY;BMzdZfr%e7JtCpwYf2ts+8lG`H*jv}0z;An!E2#z68S4?m$!Epp9 z5gc!~ClH+Y|J(g!f>Q}j(W2$jDx5}edcRkk(I=isaF(T>ZSWj}=PH|KInSUy1@!hp zg8K+ABDj=5{XfAaTAiNtE+e?dh?f&wK_E-|Vlnmq1Xmfnn&28`R*Fcw&XDVs((4H~ z8s#RXD9_CVw-BhGC%BbB;t&M6y-w|CxzmVu5!|ixo}7Cp!tW<|l;8n^hm6x60jl}K z1dnJntt?afF@hHedPjf~JV9WGziFZN2ne1w3C|G7_tsj&=LnvkNWzP%KyM_zOz=6u zD+KQoyhJ=WnVS@hyUP40&6#YjN)qNbpa804n7Jf=>xPB>31kKk9Qz z5K-T2sr>&Ed^VVo;0wY@2)-mxS5NR2!Pf-e^cc)s1mg(4BlwnJyjJA@6{?Q_f*%Ne zB>0`coWD}^{7-_P4gNy#t7cS+d;&I|71u`q!JhQ_{WI<))ZkToRn}1LOlYG zaB|IV5(uX>%2dkPqd-^@PD9uvoR)A1p?oP83DXm5EIqAa5b=O^r) z{HAQ0eIdf72^S_DO}Gf*VuXuomR{W#CtPAsf(cxba15dO|GIWPIm-|(N4RW{S*y7` z;R?Op9OlZzu)U#1cxLI9gm`b>N%|^I~$=uW6UWE1t(C)Mk;l4)f&HoAY z2xyT&cp%{+h8#qAa7{GMLkSNjJZz9+M12=9X7T^06CQ2&F@z@)9!qF$KRix|#(aV@ z_xMjDJeBZdBnV`%QkF`1TA%+6!t)8wBs_;uC;x`xF@++ z)}w@v^(eg>Jwf;a;gf{V5UTSx3F`lajqq86&k^cdK%Huo7mYQx$uQa2(;=gr5_>L--NlyM*r(zBed=@B_jRYaw-sgdZaxF~d&?Kb=?y z;UWC2E|KsH!m)&35`Inim9o{^Y8CoVfu04vl_bLPgg+P}M}Enz`hU&RWBZlx4?=bRgc1lk$p5EN{!&V>`u`A3L-;Sz_(iNk)*CJ6u zlulGc)~{nhG=iwK>=n^mh79kg+TqVV9p)jLpJ-m9kwgL`x#a@@ST&`lZ|Czb9u|q7{giBU-*!T#vru zpm3s_T8WJgV3HyA^M0&M#Pgu zlIw1w>yeHBXNp(3f#^m0$L{AKy`2^9UM2`_Yu1WRsZ?E(xq!gm3h@K{To#+{&7m1!F z65?~pe5PbqNc2JAMUZhdooGCf{$tb>s4MjY(Jw?l63MM_PUv&~+!Lk7`IYDoqTh&qw~T`Z`IG3c z|6k%i#8VReOFWrMizhKSsphK95>HM%#YDGL5l=@vHSsjYNiRtXjqMYFm_44JcqZZ* zh~>XjwVYZ3kv~K!$}^NWBc6rWBc7FbPU6{!XYbV|o`YDw{0^#0JdD^OZV)$>sJdA< zh+D*NEweAVO&k+CtiejapFaZ^~-NB z$>b0(QQs0TNqiphQp8&kk0D-zcxmF5h?gN=UIsYvvJyE=yqv}l%fI&G6^K_9T>jQ4 zmnE%Cyejc3lB#je67#HVt7*hXpCRH1@V?5P=5$0Y%458 zyfyKm#M=<>PP{GgPQ=?0>u=wQw^z?5vt`-%5btQAj3RAk;$4a5|6ls+ZIMH)A)^|W zuVw8)yg%`t#QPBMMZC9Wm)KFcBpN*4mv}#A-v4y9fY$2(;)Bc%2kIHWN;#PL5Dko% z5KV~ySK}N;d;;;|#K#gJL3}jvk;F%-9aOUZj*3|R|D_5DBtA|HRofn~MAwckYxU~v= z(0BxS(vXJ@K4S1u;>Y@;9@l*|@e=|~)KkRI8uGLWc}68moOb-2WHie2#4psiiF%3d z-o!7{O^KyZek6XC_`k%j5$lf~iKWTjFhn})JH!(IDMXFBm#(YO5Mr7C3&Z=w9}$00 z(}_Rqr+!TQso=zRUa@4fyu_aok0<`T&-MlJm&D%?f7SaDYW#ISX{==*NBnKC+x|0+CO#C~stpAIKvi>9Rw?QGqe-Qs;dH*Dq^&d&K zEdV_Y?dQKDxjPBnN$JYYU#obX+MR;#Oq#blCEcm$PF6r-7$1a zy7L-OMOP;OS~OkR0xVEo0YtYEbmx)Vo>oek&*&rRj-tCD-TCP*Fqmo?7oxjxFN^Xo zLU&QRqvOXWhHl7XX z$|nG8l%Cj4lufI)8QmS|Zfw)t~>;-HGmQ zbaz%Bz1@Yb1pf?bvin5%o)%-j58b_FZAe+k;9!lkQn`FEF074W480T!XR&Sa{ACX!0+ldkNi( zj3^;GJtgb1=w3$m8cVv|;1zTw97wWXMfYlD7M0{ugI`Pc2D;bLyxYq8ZIF~wN`g2q6OXEmvgV(-e>TBx)0HP!0-nJ_IV^IsLpPG z0nqG^(fy6?<8(iw`vl$R=su}Dn)DRir|CY^(@iPz383!P&(nQ}?hABZHO?35zGR5_ z|5s|BKIdz6Usr+Z%HE*+meA=A*4_O7|I~Yz?t66KpQywS=*kjMujkYKnC_2sKcV|I z-B0O$PFF&aYi&R4m-t1W@};TtmELNh-_RXzl(BTj8S=kJ0NwA5^1bP4&cByyf@asc z%Ol|DKK=_`_5XUSl7A;j>Ha}7lEJL)nrk zNM0$;phKlN5%OB$Z0AN|6jFQRiQ0AsIpPFv&b5$CAuTvN_3oBx{q5Bw3nd z6v-Hp`Q^XlBnz0d1xXesS%_p2-I$XsJh1oEMqiX0wE zCE6oEw&BR{CuDbFvJA-zBr^He`7p_HB+Kg#Vwob!j6IR{{~;tRk*sd1E0e53vZ_vQ zm3cKqWWTe_1Cup`V#GBKuGO4tnC^v4)*;!1WL=VtN!A;Xqe7DPN&aIz@(W;nJ1~4h zgB$6s5aqwgDt}Xw&Gd)tLXll&O4)+sFp@1vq#|39)WofYfn*z!ZF>T1u?tT}vIEHh zBs-GqNwSkwe`k{2NOn&1nWOtH11`Zgebb};&k?c#dH;L{AQ^~RuR%^N+ ziT?auC;9Tr64~{f95}G}X1cpPIf&$7-P5Z0AqEfa{Rt&Goa88yBS?w2 zBuA4RBj;hOd}#vNf0u~BD@cwfIg{iBl9NeJBsobfASKFfS@{5+h$&8$zq+VmvYje9 zjl|wZ65DERHY7QVOeavq6z@~cUteWe4~Y(Ke>Za-BDw11BM9ww3t+E!z#J`XddU{c{g&i?k`GkcX``HRWA*>b z$wwr=$|iS`k4Ziu`BL8jlTS(htHW9G&kTNU@C$*~Oka`wsBbaJ*9N~K8LMjo$vBc9 zNWL}Vc*`O@-)ka>A zv;+5M1IJ!PIvwfsf|JfbI-{lr`#GDuX<3Gl%Hsbg+U?V!Mwx}w(*{asC7q3QPQ#_F zIYSEd7jnn6QTKr?wF+T}F?qwJm1aZPMjPmnU6`bOk#+M<^>wjH@1coUU9SFK?Qx zO1c5*YNTsQU@htDq#FOPLAEMnEz)%jS=-<`^*PC;>yfTs2iXeL(ChR+q??j%sK?-> z8i*#=# zin(OWYFYcLZgS?>txJ*aPb!=Th!EZUm>wuG)vAM5|6mh7f>he>c+$g2k0L#s^hnYp zY@5jlg(6lCH%pHuJ(lzst(pkeW90uPJx*I(MV&x;8tI9oGN4W(Jz0yey?DwgK0G~D zDXQD)q-P=SHU z^VRn<`InMjMk+0JxwMz6t%Y7mdX?}<57Bm%5b^XH(pyNcCB2dKI@0U=R=q)r*4|a` zdPrpy+*}8@i+s{sN$)gc-bQ*m=^b^FW|2*u>0N@5-mS`uN{Y)yFoE}xzDIgL>C>bS zkUmWMpw;4`UP~#@BczWT@ln#pM4I+Mr9VOXq}oUEr|KRqkn|bSS4p2GeVOz*Qt8sq z>!yJE-ze#eq%Wxu*@r7o4ry2QUa2XhuaUlCLS7eB=oW+YO;VZmi=ku*$d0f}-yzk= zf7wkSmzbUOebTQrNWV1r zmB4!65a~ChzmSe4{gHGWsh;^v`Yq{rbqg8*Fa6|u(jTlv?iBgLAT3Bb!Jr%gC-~2z zfFT#O(iDM zrpaVGnC`MsC9`R?`q{Jwr;{wA1KISJcLwL%j|WCAM>Z4r0GY5|KsGbk8e~JsGO}67 zhLO!`%ohKj%}zF8qUKP`$bN)OTU?xj@ifRnvL>1B9mpIqIRt)tWsozIGmp$CYs>I) z#!Fe9dJ~x%KMUmVNY0qyWD!{+5&L8@Syu;(jD4|fCI>+ekzry*=VXhM6=d_0mD)Wr z@&6;pgl%rJxwJU*!=j=7%OIOadzTC`<(!XfL9&r#^V{txwUUi!vIVpV*-V`+L?$Oc zi-rqZ)dX3TY%$#*Dw4&3;$5>P$d)A=O*TfmWoGBUWlKpeVIW&tHp!ALBSnaCD@cs2 zTYbrvCtHDR#d_O<@@spoOtu=?D!SV%lg(e3)%u$bt2^TtHe^jQY0b6Bwj^7dY(uhj z$Yf{AEoAGGtw*-L958OBZ=eG~ehVVdnucs6vdzdgHU%~z+f)QLtF8L{n_Di~|9gvU z05pqjMYaRk)@0jR(l%t<3L-tnG_eM?lkUiNB$N8?M7Fd3M@j3x3z;nbh*udQ%vL+y z|4X)qHeb(r(&ERE?M-$F**;_klI=^jKiPiPk+k|`=KsgkH9E*7Yy783HhUgwWL}?3VtEkdt?`p zJxV71;|$M#T2?7TT^ulnE3(e54Vxsss^b~x65uPyF)qE{v-5!`|K{V2gvRwyI-dt**&J} zy6J*bjJy}1j$(|y6T6M5? z5u%BDj_i3Ojv#x1>_x@3MlX@Qtc@iHKlWN&9Lrl|uaUj3W>KCuL}2m4)JH(=YRKLv`-SWSvaiWLB>SA~BeG9S$j3S;w52{J`;1KezgEXOs*Qa;|AFjF zgI@`B*0BAm**9e0k&Pvj!yjw|VO3?1TR7#``2W@V=937NXN|UUJ{kFx zYM|WBssh%Kws2M=q}3oPR!pG0!Max{$VJE=9~rKC_iI zRIQ!QqOEBo$cVF%&rUwa|4I}~=5vy7PdRqU1?BLEU8N*xBXvvPA=2_LK;FO=%g;M$i?Bv zkW>e*-8i3Hhj1esBi~k)5%a16KNErEJCGkrz9ab_cDob#&g8q2?^0(}PVKb9xw{NK9e45^ z&-WzXk6apeZy|~@(el+HMdXpGK~)9^|JB)Wb%!Yn>_3h-Z^uNPZ6a`Q+!CKIaM4OS4>{rRWZHEz1O6OfIAB z67tK*FO^DJtuGU3wUaI<6#2i2^t!9auO+{l{2C>Sq4W=flKndJ8%_B2>QhN%E%+c}k!j=8!)_{;VL*_zQGw z$_yuep8O5+7sy{Ce^F~^Tj0rGCV!3m6>|Iki_}OqIP0K$UG{J4-+5#gXZ|Mnd*pAC zzhk#=3zW^a>TL6O#W7kf-Y5UiC?AOR#I|~=RX-;Gj{FnyudLRek_%Bgv(@o)@-O90 zpZp6m>3iCC>S@0wA4mQT`B=G?D(I!7?_15NU0S{i$gAbH`u!ck8{Y=KoT}6q8WQNiiwK zv=oz3OhqxdGD|7N6k3I1N)=nZr?6thrFPhq=#iy0_pbjC~T z>?V+62*pr}nN^7NI(=-(JnA5dSt(|xm`%!(TOm@+QR_)DpbBWw!zf&ehCZE@LAL-? zI20}Qlj=9jTNWNgo5F@q(b0ZV_+kw!C8UTXyKp+|H00bxiY~?c6bZ%L6e&eTkx}F# zS$%#{=rf`y1v=YbX?Sf4o&T%;$}@suUejbA%_W1k&YQ7o<%7g0TFqbbIiv?UE{-HqSQe^3i7qnv7=gQujXp zdd9!LKz+w7>|mc_LyB!FHlo2{ewJ$73kg+q*4Dt^5@l_;*Fkm_8c#@8NrEyZ<$h#Ae2l5_*btrRy>+$0&T z#cwtZZxL8~7>e5{?lj`<`V*q!4(Gdi=-owex5iJJO(^b>-=A6O_fdR8aX-at6c125 zu8yd9km4bVM<^by`JETUAv{X)*uaCkIp68*V$N0Gqj-Yid5R|~o}qY(;^|?_yzhK> zl3apNJWKJMGiEbAPxb1x1)+F>;$?~#DPD5Ed(`>vSGi1wS14X}zK@;nSCh*quTy+L z@rG22;!TQoDBhxYTUTY5Swt@5c~@WGR9l%~%V>IEh&Jv%R7AXV@e#$xg3Dh}jsH`M zA1VGzF;+5CNS4njzM}Yo;!7C?VrsdJ^J|K41gSm0QN~exuPc+qw-n+}_=!S02F1_P7KZ#v@dw3k6u--DZ^=}>#Gn1Vmr?vpIXT5Yl#^JN zf9oX5Nhv3DR$ELn*1!ENr%;A+O3JB}(z(|8{&1r}^*!Y@l%G;gYn16Icc+}5axuyo zC^O0#DO;2?QO-&^gmS3bu+&!onMi_ zkMsQm=LhlXBB4p?2t_6dGR7%gN}KJMo{4IUSJBs{(x;3l?e7(3C{F=3vOKBFm@=X4 zO0O1v?c1fU|L9l{&rs%+qbOzle=f?BayVrrY?4rmT z&L<3%Bb8I?C^j$Wr(B3q7k?@;FKo^aYf!FCxiRG`lxtG1O1YZ! zZ4qmdSEpRVw32Z>fpRU%|4^JQQ0n3T z;ufrkocU!F#Jx-LgcYicQHQl6zA*xnFL z;5n4i!_TF>z!=WcM`wAyY5SQ`E~LCjCD*GlkM8`d4nKwsbpJ;QukQiY-W*K-ztdVw^80+N|(pS1DyxWOmXN-|xs zK}7j9<%`zUpD_tK&!l|LpsoT@%CdRwTVA4ki}GciM3t{lzF|en$d!t`X7F`+Gd1K* z>#Ma-unz&{JCq+#zDxO@Q% zQhrYP1?3NvUsCGyp7JZouZ7u!d}DB|!Eqv75aStd@H>5XD8JXI(hrAI{z$3LoN@x? zPo~Mwlz&kELiwvyvsRVzH{+3~zz9L=9%9UYQT|Oe73DuvQ&9e^g;tZO@vBJTaIMtMb>x+CfHPv(~r;XETS>?ayy=Q?uOshGJf570JROcyjlPgp`Z}!d z+SS}tHeap88uL(%q?(s%K66U?FHXy@i~n-z%(Gg6Y6+?ZsTQMJh)UM~Cr~Zi8yD50 zT0}j5tHlNHd4p;+)lyVTsz6B{t0mJ9)PP(%L~XHI4Ff0?n%if*+Y)mBvNQ*BDM z0oBG-=HIFfsWuWd^vAySN>R0mb~d%Z2&&DfHt*L_J^|ZcthQ9Or6IKcY;ABGs%`5z zunzNTd#XLDcA(moYDbZy@0-<5)~R+jd>3Puic1kP9tG}ha1VhZoN6zsL#XzqI*4i? zsspI@rP@!D^zB$C29>-5=nJ?pAE=1fU(I;1BHDWor8=4FFsh@e4yQU&6s9^tw3XFz zs-v`mmW%2bs^h67{!==hsIC6rM4ezA>_n=QG?&gECm7`vOFh-#Y3jzMW6Kk~l1}vu z)tOYcQk_M05!KmL7f_vJxz4o;o@WY6@Tauh7%fpNtMh-en>5q)RF@#0v$~Y(ax3C8 z9T8eJeY(kIz40olYpKNkvi_s9(0+Ser>e?3xIEdb8!Xq2R5w%Iq#Z*$%#KvID1Wc5 zZ=-sE>UOHTOuaj(?iBg`Qtqa@N15f@Ty-zi{rVDJ-KS4pagriXwR({1395&v9;JF% zhL60SQ$13TQ)76{;N!!V)p0@fq(G~+tp7})dfHh_J{E|!&r*Ft^&Hh3RL@hrZ2Cwa zeo+UGHt0*v+G9<^D^#z^6PD^#eMYMR#rdmc^vhKBCfYsTqI#F=ZK`)9uc%tj7KFzv z@IKXt*47_rMRat3WOVWWyE$u@0;xWw`i4sC@+H-0roiV$|3Z0wxXtjdsJ<4YS6Llp zs-Y!~qxzQ0R{nLNSj_&NKugm3Kh=*0Coo*_pBP?I{mk%LsD5EMRKGHO5-M~4m3TR+ z;~!LiQT=Jgx7F`T=a6z+_e`pP#T06^;gd3aDuz$S@F^sf;kE=MNn)tsQ#wD$$8ITf zYISQe|B<>6pN8Q>89pt;hcJ9PhR3V&vg7YVRFBleU6Xt)AizpILF4mej6$ z_^b?189p1sU53xj@Fv6OV7NMeWBVKFQ8GCkK8)dwnqn3cRsWBvvjDrQcpA9v&n}e9 z`|={7B3RsWPuw_p?|liy4zRGhQ0(p&^|L#$#iR_dyT$ItPC#t^X6D?>pXWKxJhQX2 zv$M0ivwQaJ?&+e~M_cU3?M%JC6j!I%kK$@N1Qg{>VXAzE)LIsc71yA+rXd4Vh;--T zK#GGD7vC;7H_EtERR2#=mw+g)Yfx*mt+p_1O3|U%q*$V;lYEMcCDST3pfvSjLNTLg{y*=|#X7}?%Fzuux$tg^8&VuXaeazI zDGrkcuMSXaWVq!ek~h$m&*BIXD`^E%l>dJT+}QHnLyn#|*iZ|j6qbODOB1Jv?Me!DcQWw)H-bQgU#oJYm zMopl22gSQ6-q}*j)T4Me#d|1D(rvWGdks#Ur?~~EP<+57NY}fM;{C1IR`LfeYFeI) z@jpcI8Hx{6e1hU56d%X=PL5v&x{zb4HhKpOW^4cI-Hxyr|_!h-CD84Ddf^(TZ@}*cGiG7FShZNtX z_`U_-6WBR$FvSl93Xebwenj!(Jj&QUr8tw~XQtTa2ER}}*V$5`YWtm@&A724&oFmcm&MXK9>ejlN7_tq!3Vif8K{V`uqXnm`;07I0R=S=oX; zG)lI>IjdO0o+?ltDfKP| zr-#LiF1@7#&9&wu{g(QrV^{|ijj>YO&|znA>->y{X+a z(+kz$qKsM~x5FhGRNch*=JuV-=gGmjvMv8=oT)h1;7q`|){oCk1TzJdE>@CYKhj zt^JW4k%o=)7|v71_BhTHI8SQSl?!(ad$u4a9G$0ep2c}a)3&#j%7t-WDNOzm=hecx>sxR}*TjbCL^9Ih_IB#l3n(8f0rCPnCh!Ktcp27DG zexOk*`6Ha)a6ZQQ3Fi}>FL6G_`2y!NQ4i$EbBzpdd^RE&A!ucELAHh4kJ5q?q_lua5Xq&53WMKn#g-PKv@E4X zRfp1Il$J7NanX$uhA&}oNr7Dxr%+njg3Ab0JqkGCZ$ys5u2C#QM!jxe@eG$ zc}uHNTAk7uN^4NsoYDYF9;G!Ytz)qRDGgG;S6WNEq4KZYHG7oeN)!dwqa?>D1RqSP zC|!?|qZ$f>ILVSLgKXFAOFP&6nUYVbn^Hh2p;V?6QL0c1bx;ewxqNprL8D!PiZ8jZ7GeSw6*#f zRdO30#Kog(UUEWb5zWOUP#SFvJLvR7OR*EB-6`!%X*Wu{P})^}wfur}_B#@*a{!gL z2c^9z?Wsvbh)i8n;k{K!jn%ssDD7u(e`U~uACTur=|D>7Thu|6#!@<&(!rFDrgR9U z!zdl9LPR&Mm%}L?Wt>MCJW@p!XDEY~;uz#l@ug#xC=}KFcuFU<%BA&nqE*33s-(=d zN~cgdi_)o-q+gsy>2yt)n^E&RGp|;ir^!JtCe*A-!9R$lw`tx9i{7A zZA|PS<(0HD11#NW{Py#&(#=ZLGTtI-DczbkFiPtDDNRs;D*O&gcN*m`gLmh2!|&Bm zpfr)vr<5j9ddT=E8=OLEDy0Vuzt3R%EkKR@pqY9arRmx%2Yu0XsC40nDLq5!5tI2S zr6(ypX87X+Q~PNxpb)hP&r*7w(sPtvqVzna7tOr-gFn?)Y_6$ZrZj`n zE0kVsC2aMP*R**Hr_3u#Z%}&Ix`Wj9Ta?~0{B32(Ev&Wn9;FXW{`&?$&>o@kKce)B z@qa9n|3%8#tI}`!`&YSYTh98G{?LS1-^wFDSDp5K*ASo5zqqSV`VV(`+y!u##a$403EYKn!Ce@4aoj~z0e4Y@ ziwTsBRDm3s{;~>pm&9GVFsV20QibXL3fGLqT}Fb6uQm;LIn7amWnowWcO~2vl`{Qh zQ3kgM?#im992OHEP1_T8n~XUv0h zib756;FfR$T(?c}jP7etPlHOPW!wsGh+Az_YKoMf$BlAC;mv=`DMH*7cM@)fdmL^Z zcNA^|cT?OZ?)tdhxWi5U5Zs}-!}PD3a>=~rb(uDsao^%@fV&Cqh{FF()U&JZhPZkK zkl`B_9^65XG0IpeUEAFZcT3#OHMy2Wu7I|GUb|c2j?{CeLKpdRzS!Lw_h8&@a1X@Y z7IzoDgT>trcYE9&3;uq%qYdsLP!34q?qtE8HQ4h@!*`YAvADb8?t{BK?w+`N6z=&= zo~{$5()YsMTky{Qn|Jp6%P9Ne?k^PF{W|*}Cq&!>aL05#v0&GnLf3PB1>zorJGR#p zxv};wDSp=zqj3+xJskH?+{5g&?`2vFt%4(PkClVBxJPzudIj!L29Gv)jKD&?l>X&b z&Pck)<6eq;0`7UZC*q!ldlK%cdZgDqS+8_+?f!qc%uP(%DV8}M_gvgF%z$U&%FUnB zjL*V7TYnhZLvQK99aq@r>Ym5Epw|-8tR?zIN5(-O&HTX#I}O}IDc z$x!!3fqEH^Kn>n(Q2z+2cjLIXY3_0(oO?U&1VQu&xt!2+@5H?a_b%MKrP<^MMXl`j z;!c#`1nY@rN!Ux2oQx+%n1cHW?o`}oaPPyF;@por9rpp;2eq(vCKq>_wzuZjxDVkz ztR)h2i5=u@ulp$OlemxJKCY6bVXHNsP##5|!hKrP$|9S@%2{Sts`z!>=Wt)eeIEBE z+!ury_r?4=AT#O9s(kPw?kjq4o+2}FUlSM6`N=9OM@BvO4czywV~REGq^kQi?z@(S z{srEU_tf(8Xw80r`!Vi^xN`HGE6qb5l_q{$`|_uFV{kvi{T26f-0yI|z?JgORB!Bl zX~zFbjW0JK36z1%{Ra12IY+E@C#Q+s?{R++o9GX=+#d_Ki$9cFn}z!`?l0P$qA9mx3V@oQAQrQ9n0$}L18w|UU;2` zbSUxIvsJQY-Gw(0uMgfDc=7^!l?m^Q*AGvB=Ze=~B`9M5L+|l|2IXP|Z-BuyEhu>n z!Yksfg|{x=+IZ_|DI^Q~-#6ZRc!QNwezz?rzBOORUQzUapd99vJ6U##6xy z@v3;W7SSZ*MR>6=XkkUH%1rV0!^`lx@#=UD)gizB%WL)=v6Jx(!5b(j@5X}bO=8}rIU*Oe;caek3&{mk z#XDTPvv*kj+xo7tTNNfsDUQTD8Sf~(leBw!N8=rXcRb#)c*hkEYk5UM@J_(fn;+yy zx2lOq7WvXUyi@Sx%dcHJ;hiRZ){1kw#NwSHcLhl85HXaqc2lO%lPnfU7yBzNd9acmE z8JD$yQm67)ce$V89CfOyhA`iRcOBk%yz8~w$~|e~ZM+-s^u7pnzfv3-3cZ{0?!dbR zZvx(}>P}_s@NQGLjWOPk#QZ9#wN!wtMiTYu}6a6y8L<>3EayCM!?N zI8*SZ;yr*T4eWlsRqvuVRc7ryya(~7iP!DeL7M$)R$mX{iT}5&OT0($9>;rB?`!fN z({hQnirdFu@5$EZ6|Lo`@t(tb2JhLz#b>E}v4EEAc|229G?B5`d(kRR_W$Iy_6nX@ z=~cYf@n+z?rlX-`A;p)G#d`x!^t2imZDo$?y^Z%F-aB~jnlb;ydr!urj?q$6dIdDz z2bxyv?jyX9RgL8b7NW5oL!>i)iZ4#`GdyXrpX1FmO};2>`i+j2-j{e^>G&?ATj5%{ zfCBFuyua|i#rqTQJ8dL4?2h-n!5{E`#QWKBS+;Fhj=uH|JQ^HWn`Dxj`ug-KbpH-h2i}xm16DrKl}x>@yqPdUl4yGo5q}hzcBtH zS_FSlHKz1O#TTc%B0dD`<1c}Kh?EY0N&KboWBjG@2jDM*zp`E<cLFV8x9jfp?d z;2`|93|YIyv(}sV>*81O*TeVl2je^VMU9oot+Y44gzt8pa)sbsH{T`*zK>tV50po4 zEYV!#{%83!`*F&qdDRN?KXE^_VnzkAuv#*~-yT23-yA=~ABJDo2Ie>Lhu}ByyIb~= z(uLPpd588M_?rnCj=vGURKbS$8{m)V$gb?DcS@8Z*(+i({EhK9>6D8;RG&@pHxs0z zd!))#mA1g&4u4Dht&}3yclaamw>IIUx|+XQs%`MM)l|*P1h8H}4Xu4Sf;1f_uZhvLt~KMemy{KN4@!V&mK z$_(6uABBIkW~X*I2LD9-WATr-Dv%0#R!4BHjuQkL&q+G%_fOU&@(g(x|5W@7@lV4) z2mkcKInpwvcbui(#y?XSbf)2--QrKb!yku#0sgu8=bOOuIvSto#KX5?MNA~6KBu#w zoEPCsGrrjLyu_F<#lNhhQ5U?evox@4@UO(bs<+&MCUh;x)rGV3I=UABdi?7oC{Q}~~VYBe>G{=5x zVf^+Y1pjXQd+_gVsnoLhB>V^QrE}efKLvkkez$gKpNj>-mnwTeDKZgVV|i=U)9}TT z)A1kbTtnNrJ~Dsaqk{j4K;wA~Ukix;IQ|ocJc<95{(WAmM%MgQnP>2y?>b8=@mYh< z3DFR#f)};ZiG9XDWt=bLzlHw_{u}tOcFh@zKf}7=Yxu9Lxss2BS=@^?{x`dt7g?&e z@!vBeyo3L)P*k9dv;O<|9~Z_if&T&ihxi{!LoM{3AhFU}@IS%-9ADbQXR42MA8Dx4 z*Tx)zFZaK#CYKT6e@W06|0{xJ@W00Y3;!GZKk>iC{}Epr!w>l1Yj&%fCuy}7E2j z5-cqgf~Azc(te%&R+ll3U|E7y2$m!0L9jf*N(3wP9&|dvid}P#?LBC!`YUO26DM2R zl~qF((vv{a76>{CdJ%MpGf`#aIr3Ix=uOZ?&`0x9Q_FaImBxx23HlMZ1pNutAy|!I zb!}i$P2y65H3$X}3?h&$25Qmn)~_ep2ZOZ;)-FUdl_*a%lVDwfqP_?R>k$m@dSO4E zmS~9_wQx|%Eo`_)kP`R=AwfV;R-5nnB7wE%ph{3HOqy!^tBKnuh)mB|Jb*Mjf<#-j zOtDn?jG#_1l%PS-Z6$4LxvZE3L)0fsovK4;FpOY$NB2`2C2KXo1_Vb~8AlLoNU$@( zMg*Ja)GLsMviB@%QwwfJFp^+%qikVtO9J)(GLUt|+gqli2u2fZO`yMhA=pN3D5Fxa z9f4l{uO@0yb|BcXjkhlUH_lxM#uDsGus^|W1bbUXyA$kT$esjy2_iP>h^MI&)7sgG zU|%K56gSwfBU@a3esF-8#bU=8)K9s#Fpl7Sf^!MZv+=8+H1&L3CiuUObbTo` z!G#2u5L`rXv1FlrL7?`gO9?J(@i#A5?;TvBcF-1br9pYYT+C|o8iMPLa;;L-jMo!P zB^Xa|8^H|(w}>2q8wqYAxVh`l4b|E#>3Em=jpWtnRcMF$` zsCT3>cg?w7-`9fobSX*PMgLC-J`j56jJ3@29}#>^@VOaZqCOSGl=-ai;@m=?Nd#XI zd_^EG!FY3u@Zev@U1xzx&Q6B!lY9Oz9TT_ul-_stM?x%OV&RT{7LXLLEck- zA(&;^%_fk`-;V1@$K;kb;#>l`|5JMF?*xBnk!8{~=L;*~Uj+Xdj|>6-be(;JHR*qa zN0IUZ;?F5BNO>VmC9b2qup-j^Uz|&MQOZkEwnkW9T!oY|xCG@Tb%uGL?NSi`R$iL& z3Y3?zSoOV>m!rJAbY~fD1*%MWAGvHY<&`KOM!5&&y(q6t*`utBa+G^g?nk*mxi95j z$d>|XHsub=+Cf_NsFW_F_fcb(SDi<I_TA1RL@Mo$c;lryE=U$rKFUs%ccZ+s(RVQ@=P~E0x4TjHFirN%bv7OLrhKTr$(Hw_ysuS?T!JbUxi=PM#?wn#iug*%lEtTO_U#}d^2U~cehZ! zlk%A>sk<(T{HOkxm6p zG&o5<;q{j5kIGXhKVkx>QofJ!gO>b$gAb@!l|PO0LxNMDUYN3`66NDy`C$o7B9AxTJma3dZ`7_GjQU09rSCqe?JX1K8N48;rGrWr6?s{ZSlte)401lb(uYc4)3*K5UnHpV{izJ3 zvYPG)s;o}MwtZG~3#jfOk_}FkH5FO&c`AdbtV2aNe^Oc77@k*KRo104*of;XQRNhK z#8Q>0Y)8eVl2Gxe_*y#I=lF6Jj(FUV5AhaQrxy-N@X~ej7qoB>r@(4 zn#v|w%Om?tDnr_-h8oW>-QTM^uTN!j#a%8gVeP|=^i z$V0`B090-9;7mv%6(L%fK&4% zIgc^u{vRs3BgjNev*7eRX!yfzfsa`5QG>ew-}s-P@;a3#sXRyJDWg1XQ1^dQG0PjL z1@q?d0u`xcw*%nYOf^Pma ziuwPF`hN?4M72MakE!Us9c0{8_$igo4EdbOOe$aG%%;GXRMh8F(Jeq!zHZC;R)TH( zdn$9N{9p_}Qu&#Rtln#O3N`I724@+Ztwa^|E0zDK%%$?D1%EU6I~7^~*Hji%|8J@O zrlMOwsr;KqDY3c$)upK}NOdu)3sJQ#wRys-#4f67^W@dVHEk8DOSCEG|CP8@o3aem zRjDpZwLo<_776)oohBMLiX=9o6lXV%i$d4hDCmx|531R6A4Mh3f8x?@DzyrMF7A z2h}}WX zQoTU8n+k*C7g4>G>cvzq(H*n0u`r**R4>ylqq1SOdbzP(Vem?WR|z!X*HFEI>a|qI zQ@xI=Ie*(QsS1c3m474En_A~4sy7?u7TJzV^;Uzo8N6LL=!!l{zk}*MRBiufRrh~d ziSF(kbgb-_H2hww6RA$p-Tqp@$yBEl1|5|jc%eE~j=fO5&*1$AA7~wfsZO)x)2Y5o z^&zS+QGJ-|6I36e`l#lvc79Cvj8z}kvbXeklIqh`pVFWihw3v_pVj@GYMkekKIkZ_ z&r`L{pNhXI6rodnnd)m)U!nSH+hQ~Fgl31=smg8ndA`yx-Vz1!(VyzuRL%KIj;hsr zR6nOG#{8hQW48LC740L7{a6z!+b25Zu6}Cpv(}Eg>KEc@IQmvBGlCX zQ(H_Kl(Kl<7>u}t!6glv{}P@ntAxzimI)Aa#GmC z;L5GN%{86>Q!5zkC9qXTwN7g5QR_{uzeU*)Q0qfYvg=2!Z+`GW=Zb@M1gEwdwE@&t zr?!TkFR)_~I+L%hNo`$f19i4p8>I6qC9Y*~ZEEYN1bHN1d54<(BelWQYSfC<%G4ZH z=ALt?m84Hmb9JIv^9=e1^@keDqejpnfLc}Co^150h13#iky=>kv2r>+sHN00?V*yR z6_#3qn!O6un$)_f4L4~+3=TCoOf{4_WNm$F8yaN;Y9j=ZM-HP=+lbmGM&DS8n%$<< zHj^lskP9@*7Swj2wk5T#%@kWv8>zEXHOnZS*($OPwe1br*5GzJr&aoBRaNXGP=h-f z+{xh126s^j@`hF0joSXycDE{)&%sjNIs{PLi`qWa_7+@)2vp*})b^90dK=aG0BVO* z8)F4IklG>C4l;Z!HTl(rID{p&@wcpfs6`#7TD2&4F06JWwWBl(t&yVz>Uo^nvB-i$ z?Ko;;tK+GiMePJ?CtIdE3Q#*qlc-9XY=3vyaPl$3X|+UbfYkNLxz`hVj*o0<@% zZP+=5+BhTXs6_2N!^)4X*LRIVQ)}7h=~-xiJF}YtKCfPmfSrSWW#ywHfr}! zyPewI)Fv3`9aepJ8oWzjYds-dlI#WsZFLfo!S&?_Zx94wfmH()vX?& z_Mo&GJ?m6c|E~fCYVwEjAhk!R*_p4}qtqUw_9V5(sXfu+oNN;qNur(fX=>7gj zKbz~?MeXZ6N}+OoOHKU$kA{D5P+tM8)IU-Co!ZaTW>Nb^iPsga4bP_bE44X!M-xbG zt~omCkH6{YW2UC|he`WWd#-wnzX`>U{X^KF+P{QL>DqPeKix1EE0+Z& z7B;vDq5c0w)>mbHK3tq|Nkbr9LKj_)xrN9!r*LV)We8U$Tvj*MhRYGINVq)V3d$*q zb@B+JqE;g8(F!W(Dunivm#`u3-fkKscCCwolwZIMCoA!gUB`|9?NiwRO8%^M>C2rM&A3V#M`wqA?c< z9YaclKA}tKDVuy=m&XvoAjb(SgkxnFB4L%VMmUNvB;0^7B5V@IgpFSJUPPD>riAsv zl|syPQ~H(778Dc7C&;jyQ2zXWwH8WNM8lzk!wA>ch3;^8A$+H`bxl5Cg(CI3fYp-_W$HWfwBorExa}19)#NvZcn%^;dZ)P zPmC$4RPt!T{0xF^&klDa+=*~k!kr1_q`=GCO64EKvKctsjc|9hiP_dnx+md*gnJQ= z(VwG*dlT+MsQ*4A%y*TGR{Ikk&?=hj&67o7$yXX`c#tAj>9%mfg9)!CJcRH}!b1s9 zB0P-HE_n+N*G-1u5eAPmc$97iAC5nfJsHlb9g{`{A49O3z*D&e^X&&$g~D3l8cFZf^4 zq;#!{xR_8k{}WznNpx>yJJl70R}so0`ASMTSK6wz2bC{B(v{zty+C*!;drZ>>+Ppz zV+_B6@J7O02yY_1S$D=t(d3JsCcKsK4#L|+AfX*?P-McqO!aI+co)&@dUzqcoA4f@ zBXp-Q{;IMPcrG|71NR6~1f|UJ+(PW=NCknjlKPM)*3R5(`&MkW?L; zyi52N;Y`A}2|v`+M&UbzLVVv6>L1OdV-kK)xaLmPK{NV@@KeH%wQFeb6Xj7een$8O z;pbiN?JQq6^vIPpV8Sm6e<1vd@LR&KP3$+CM0Si!m!9&SQNCA-j6|~MIsB3EXTqNf zi8Thz;unq8EM|56+HJ@jqU8vGCH#+YZr8`p5dKE^r-}OAf`7t6fI7)1Q8O{NB&F~ElISr z30bNoKU#)p*{)v??)vpQwUs7co~Rem3Pk$zFV#F+iKvI>rLij$6^K?L>M0+9IyM+D z3>}-7d-r!Nwu4BHhJ8rXsnS&2E~0)!eTej&h_?TzuSQv3mZU$?Y8oYv^p|K2q6*Of zB8O;AqIHP|8rz_phiEOLwTag0`t`ZOwCf}bi(Sw1((_+LMJ2X!FA)Vqdj6~H*WZae zDFuxyC%o9OtZ*2svOM0KKu<{}@(q^7Jy zL|W0pZ2Uush7oN*G@NLCsSa)P(j6pLs#4`_NVFBvMnszuZLD-P*Cv9t#uf({|^#vYn1H-c79y7HZYoKAEF(IcC}1* zv?|z%XlJ5b=4oqL??$v2(e6b0|95qmDtynb&mOmyv$rx!YggjFLLjbf1(44 z4j>w%sYFR@CuZk^a$ljXS5qBAbhw%QP@==~=2w{bQ`g)z1j;x_bR^MHMAM0+YR)G* zhUjFXV~I{6I*#ag-M=JWQASu9*P;`NPSR_H?F^!E zL}wD6P1O4TugZDgd7^U!>Q~+9+`=kP61AMa8A=qsiRc2NtB5Wn(oivY_2_GbymIw7Tjm&DIhlm~_dRPZ9 zwex7AM~TF$j}bj@74bNcI2LK8V!)?#;-NNwy3j|q-rcHD&HJpu=QLK@RXz?zFA}|^ zpRn`q)X~dCuMoXj*y~1}0Y)?QmzQ#Fa`c)&oft=N5KAs^67Nm)7I94UHgOk`^yqgB zSIbGgfapC_>3w2|K9HSzL?0Ubi0B8RkBPpq;3q_))fYsc=}xfdb8T9-S&n>fp%TBe zsILru-C~gKI?=a8-x=}y7EzghB>IEsC!)DTKNE?o|HU$zr8|70*|~6qs?x6l+u8kQ zqJGas5&cQ@FVSDd_P4=*TFjd8KjKA*7tkc}f(92dxUeP{nF2LxQR2lk-+1vBQPRds zsI+)V;^h!`p|MLFl>fi$UP|J&1>)t2dlRofybAG(#62u_C1q0yE9d-%_arW8P&4gC z+@T7nsLpvjs(@1Z5J&0+;#Gs>sjm7H_%Z~^4l_a{hCi7TnSB;g3i^M*$LtN6cU^1cAd|hID z{h9iK{zWtnh%2UcS=+e^-;cOTTq6#3P$&)SnmbZ^jr=D!+k`kF-kLZi&h&(cx<`>v zCvFgLO57wKPTWmAq#&~$*(?zcB_5Was#Zs*l=X=>(AWppCLTe&5%GpPcG#W@;*E(n zQCz1t6O3mw;*rFg6XycA$R(Soty;K>8r7D$4e?&Y+Y)c58&_lw9B)s&3-M@m4)G2K zcO>4~tg%zqA$Msh@;OGlEAei+wO(EXsQ9_jdIUgCX-&m$J$rxT0C z4l=R(6Ca>22=N%=1J&nCeM#pPKNgQAKAQMo?UnH%#D@_ds?y{vq|}QhIh^tVB#HSLUWb~5_o}#hFX~EO-An_T* z=MtZ(bH9ev!db*;%jQzU&oMYo|F|j?X+PRj&L_T%SP+~1%kd~R=7oAyV0@85xe;3M zONcMk=9=Fb7++3&7x5Lu6Ns;rMn!xTv7GZ2BV1jm^dP>bFjH#t+Mc7jjd(rrO~m7g zZ|u3JbhjIn-ZfI9EbYz2w-Mh$e5)E$h5&hHZr*dd9f)u5xx0ALYl-j3YlryGRvn4k zjPEAChxjq#dx^!o6N&F9oN_%-5}h~*g0 zE;_7h2YZYvNM)p|z|GOP~-A??DK=TRjS@V0r zh#wGtPW&P9XZng1f7EO80OF5{KM}<6PkT*1LJ)0`a`+{dI{nf5^-SU~iN7WOiuh|4 zDE>j!_(n9e+WF4l_ryO4A`H@N6p=&y>PCJh{*(9@;@^m85zm!|M?Bk_)*NEF0>Tzm z1oq7=>UZKlbaE(rz@&-FNEiP_vM}-A#Q*3Uy7u3Hb>NQwBUy-K0hJ&x9m#^ai>&!| z!Pg57NERVk!jMHt7Ry&BNEX*up#-f;ar=@*GMhvwLr9h;S%YL5lAa{XlB}dVvXbSr z781MlH(7yXMG5L7{x<2Mnj|Z$SColOvP#QsCIymSQZ=3VAIy^ul1`FUNqUp?DKvg2 z=@O{@T^{D@B?*_6aUk^Uz?Dp(Hzz3?tcsWH`wP zlJ!Y8C|n||RbPt%b>%2h)c@-v&RqJGww9eq_9NMaWDk;E zNp>gMP0S*0Uk}U6sT}3mlf(|0CVP?Wt!+EmM{8f6sZ&*yw8>k$$2D~kepxGV?`1v{zW7gkX)#$$}voNWC1FyG0QhNi}$eQEn%BjAR0dG> zs{H+ilRQo$ZQu!#r%9ya*!*AqZZ8Ft;~U8{B+qJ_*OEThvGb9d>cF`qQZ&)?MUt0V zv#CK1$&2I_l2>*1C0R>dE9EuP9we`m{6+Ex$&Vy&l6*(<7RlS9iB4CNcXWGy@-E5e zB)0gY32iMX`GDj@Ern7(GWaoxE&eFQjuj`LX`|BM7Y1hvG~`Q?uMGLRP5Fl8Tg6-a z->dgde$YlJEkR~D$xkG+NW^{pqV|+h$fBP*huI`^bOe{Q+Wvnf5hMI=K2VQ{S}uQ( z{3+d8JD?=24kY=TbP1AwNEasgm*hX~&JWswnsfou1$ADXE~GPUv8v*WkS;2ISQ~>Z zxTTBfJ6F26L6j&hDqWIvS<?)Ps_^jmUKB%x$x(C(&csAf4YJ|9f;GF zbao^ol+2vdl}XnoU4^tiX;0D)(gJBO?PAhYZEHFri@f8i9N-Qq-&B6)b=2Yndu-kpm@b}t#*0WA>EjCUD6Wi zdZdF@2XO_d90v4rM2r!~^h zj24kLNMoIFswESHslkl2p3}ACYiyIWn{>G0Lr8}jGHf0X>H0?5z~G2^bnQ@LD7hO! zD?-%Ro^(^vk))fEZY8&WkZw-81?iT06NpR(L|Z|`KTEgjc^By@(yd9iCEZ4&^z7Rk z0!g>iR;0*i(jBz!q(jMArdHjFQA?BV%*KOBcVU<`n_X#ML%JK;gQUBYtw_2D>9wSL zT5aw{dK~HAq=%61LwW$|zDBpDk92=6!f5%k%zmU}NXL>MX!t=&muHmx)lQ;ptzO3Q z^iWdy@3~M8vlb%A5f(g>^k_qlYSWJ)JvPVNl;cUyB|U-kRMHcT`6PoUlb(`i)YjxQ z(lbfr7ce6K41sO>S)}I}@oXh(6BUMWZR9-C%Sq2Cy@=GhYI*^w`G4aSA*2_RUP^k& zyyPa~GL_IW!WE=fl8T+>{(rMTE0=59xm-v32I=*r(wN7SN+sSvdON9H0U^rVM0$%M zH*0dWm^6&r^0Y0eY`O$QdWXR~+njfkP7*ti-a~2&LFq(IB2}PrCX-Gz{wcW}()%=_ z%F$IIL!<#bO*)PA5z^`H*oQ3YVGXLBM@gR`eawiD=R}1n=SkA1a@;tdG2*kNFOxn; z`XZ_LmKT)4vTL`Em)g0%LOR2e$X|f9<&eHc`nuv~Ta)=F=`2!F;}g=ijrkqY_eu34 zAPc_N7W)C|N2DL-bW8j3e7N-4?@2!+{hIW1(l1HBAf1_~Rkl{U{Yr5ywT^wH-y)OA zwlYTf!QhXiKO6FsCTtn=m$s1EWDAkbA^n5&SJJt{+2;9;^mh%)qpj;t(*H=M*Z)JR z9j7fp{6o79XA1~UwxCkv5#6$d$(AKsglq}2MadQ?TWlV`G1HQhElIYtQI=|_T}F}r z315y(CjTOTg%(|EAzO)T7+DXp8rjNZUB$Z~Q2G0hQ zxnxC)bu>x~Qp)R#%p)rs#WyGm6x!wuuaH%9`KETLQL2AL7L#RUiE^q|X`YI#PBz5S zHprS}-8sX2GKaP~hm)N`wm#XeWE+r;A{#-r1=)sVo0^1;$TlY1WPS-`n`uz>x39pt zZ^^ba%2s3}^VoJ4Ta#@=wgcI=WZN5$`2TsO8?DT${ElQhlga;ot;V&0yUdeACjS3G zGV%X5)z9`I+cS?P+p8^MAF}-gC)?NHyf!Jn0LZl+)8;&g>`1b)WQUTO^Vd{|{zm6a+#+2aq}fSfy~@~cA}=0UXz_XU+k%5SCgGac0SqZ zWM`A9|0g@s;C$=boYBuEJ5L*c)w@ytkL(h%3v%_yE+o5%?BcvF34>{MDVeVJ zliB)zrtAN0{ws}sRXcbM+3jT4lHFhluQPZ(+4%XA+(>o{*-c7OnK!r7-rAhx1kUgzSDA|)_Pw8iCxy0s~R|Fw@ z#^AI14MN@mvga-Mg7U}=S)g2Pm%T(LqC`Vm{K;M=d!1|s*=s6iB{@0!lT4(^-XMEZ z<=CWAmclH@w++e`Aj$n*<(D~&r{_iNQ||erE7< zfrflhIDL{KUmE<%;MWGfG5D>)?*!(YGASkdf$T@>ixw`KL-rHd&t$)o{bIpcWWSQl zHhhjioe~RF&bbDEn@1%3!zh1}{Z+U`CV0XlJ@g;N<;64mm+U`9>I>-fSdj${E<}A{ zLl(*DYUlc5l8XA`)K{bqb@_01EA=I)FKNh9)R&>Yv?`ibadpzb4Q(&c@s-kbVb)Vrwn*B7*UAA_svf!$fVWP*QQ>izK#eqX|kvy zLhLMXeX#P#yI9@PPY;@}+@2@WT;ufgZA+O0>f2KGVjI+(IcJ+bg!%~TLoId~_4TO_*Qoyq++ZF*b=m(TS!`s` zJ^{Bf-P9>C5^K)OT#9 z(t_+{#o3v9`xCGdccZRfeyQ)SoYI5py8cgnFAXZPw{|^6#Jpco-;esm)c2=;6!inB zAF7}2>SGKZNc|w{2b;~u3Pl%!H1?1jr+%0qMmgNz5!8>=t}TzU9Zmf#>c>z&nfkHR zPoS7Q~qbIa*T2c_0x=as>+cDqaERN>H^Qypu9H;r@T_t&z1#e z>gQ0ufciM<=TSd*zS#4r>z=xK6q9)&^^3H{=5#gACDbpa{vq|ts9#I{a_U!6m-D|< zsb49u_mh))uQSGias*h2*R+GzQJ3V`Q@@w`cemnJ>x=xTY#ln9x z^;_Dwl=rqA7f5}AFc@-&!8;A!W$N+AY49zBZ&QCKkD~srGMMH$ zLj3~?irJ}uME!H>A5;HC+pu~7xm`I&KGU8ePD6=bShJt0i^KIV3+E2Ap!xs$H@eOv zn?~y2QvZ(nPkI4e{d+wwUH`$L-v6whpJcmo{b$`qRsY4{Ed9W(#5pv?9sNpUQR;K4 z+s>c*Z`PrHH~5FDr2XqJfrk7|{U1aArJ-9rssBe~fmYXUEU0Xvyma2i!Up96SoK;8 zr;MbrI1O}Im-UT?eHv&isp~n7rDz;RV`&-{8q3gdXe>)(5RK(%^rRs>5OnHDV+9(r z{%^C9#!5!%LBrPnHFgynJ_IS~?4r?&MjzwpprJ1UhW9q8?+eDWDvi}>^liuXqtQQ) zGB%z6(^$jc0E25994Js9S%=Y#)-q_X0F8BM*eif0T#v?J3wq=sY$>jq_hVmBE!nz?)$LuhPEVni|_&F5)O!{+~ut&Bd>;3$Jz8{DQXYC9U+ zxAD<5_Mx!@ja_NjDosP@|1@?sxQoEN9xb>Vjol5|!|1}MuK+amqOrFMY&8s<{5NtX z?RB7W0FAN6Ifll8?X(BA`E~wJ!zTZV9NMNIPIDxUBWSKd<477W(m0C76dFg^F1_d{iiX}f|LHo^S|~smBxKE9yZh6Ps8T_YQ_g?OjAu1pHAbUd1+}p(xyL3 z<1rdCX%wEvX*|)UKdH=0e455{hCE~N**5)o3%(#wA1(MxG{hyoOyhkTuUPD>24~QC z!;sf#*b-1n@|#A?b9tMFF8k1UCzo&dd+oeFpfS@Z9~%6~;K#`QewyS{8lThnOqpAf z74j{z@imR_E%*(MZw>iwKF%M6V#FV5{4^i^7bDIxINRWy`C{kN_?yOW zLboVg0;2JU!9QvIrSz6o|Il27#=lBYp8serph#2Ke`qdbaN+++ZZ1l5DI@9<5Y5F6 zLURd2mYk1sX__k-aT%JnvrhS!qq)2g^~n`zu1M3If3t@%%l@CXm6|_M@rJ-ze(;Y1-(}T-ETtZQWLH9AwB^2GVmzz~)7*rnI)9^VKyySJ-%$8zZbWnAR>D@Mn;Pe4G&eV73xiu4 z+)6oHY@=u%O>=9S`_tTprW9mbHRR`FLYKf#!*ZoJ8{!nkQ@87Q?CYaAP=~ z<{3)S9M3d(mcg@Wo|DrhAdh`G1-h8k-FP&5QG?i1A-)@G|6@ zQziIwobG(YCTmh|`TkN%TPoQ}n&2MyrLGyZ=<7rMp4!Jb%qMChSnveZ2RyBEo=98w$Q#7BW zY4<-gpP^|x$3$CsOb&7hq-JVIz_h$CnQkxBe4FMgG+);%IGV50oI&%oRu)z$OJ!F; zG~YDbP64ZgcW8b<^Iappr%9Bk_dgir!+934j6Sy5PiTHh^9!1v(foX#ZaF8-nU?TN znqSG!*9^BKV1pVozoq#D&F^Toc8lo>hI|}v{z!AKE)6w*qWLq;*))HlIZNl^_SVq# z?f{x|kh?}DXft-b(Wv>G-fh_Yo$jS*{z12>{U_ZE(fo_<1!(?F^FNyZ=)R827Nq&F zK*i-lp6veWUQjnA$d_Nm7gnOg3Y7JJy4Rq)NcRA`*P(k& zBMzi{ExHG(*y@f7rK5GPJs-X<-RAt2J~*e-?a&<t4j~2F| zy15ZM21iicw2Y-{`~SvCbqlH^sg9#6>W!hg71dExZSXgfQXO3i?4=$1Z%`Oi;cfM^ zbyb%DRJW&Ub_L>GG=`>n9S~1 z0IK^@J(TMH7JC5IgQ*@!wLAFtvN)tnWju%VBpgol2*tYskD_`q)uX8%PgTc%syg@^ zqT_#0)Cp8iG~!ABA)Z3@463IpMH8Mz_4H}6Mmdw}S-n^t|5ch=X#&-AsHz)(s^?NY zkLs0F&!>7R)eCz37gD{bhhI$fl5X;DUb+OJdbz>w{(n!^tE@AMhMgSENp%v{d#FyPdI!}hRBxktYq=e2vA6eJ zKAIoD>YY^YqI$OyWy@4udCTU!2oYax0`F6zY?xFZp!zV?2dO?(dOH`o`UR~%LRDP7 z3Fo6CfvRq>P<>ptS(Nys1)rk&bV=!YO0Pak^<%2fQB{BJRG+8%0@atPzG(a}32}gY zJ}&i9v9D5j!8^*gFRnZn;w{Xq+; zW&cr986v`er}_)k->8bAe$}#Xvh+0T_4xm=?Ecie#PTZPZ-f6(n~s|PDnd+uY!oM0BNEn)ep|4(Y_4pbkhi`p{OR-?8owH2u?*GsiLwH3<7sWnv_N^KQtD_Po= zmFI*JhOa76+0~@MMDZ*CNwjQ+79Hpw%Mo}AGiZbT0)V4EZ9JOsUM^$gz0h^vT za5)jaJ+&Q;vV+0@iJWd)xoE5=|KMCmZ5JHTVOMHDQQM8$+0=HYb^^6Ms7cN4SsApf z{9ZwAFN1qi+eawWlp4z)?t&ZTy#18U+EG}2WqdWK*80{Z%}))hrdPb zZ6m&8TD|)p4aM7lFn>Vpb7~*<_&=idvC%&<_^H9qRCw2VUl>L1f04AZ1Q7VO!Ed^f zRmty4WvG2m?FYfB{b&*nGW=(pVbp%X=}&Dc&J5IkrKUd|QTvVB@0HaA{xOZ;=zmfB z8)rJh|IsKhyHuq!eVHlFj5zb*%!D&L&dfOA%z`s(W!3AYecEH)$C<4nHz7)kb>_gC z8)r_Oxhz)LloMwjoOvs&O;Dn8x;XP2TmWZboCR?f>hYHX`{7hnAI+tt48U0qXCTf} zID>E&!|B5rjI)R_bUASr?eP!6SpsMAo)A$(GF`G1h_kc>m$6u(ciW^9m&aKFXBC_k zaaO_^YBrhBv&_mBxxr9Pw5q|?O1cyiXLXzrIBVdvan{5MaMr@v#Mst0xQ@Yfan{qC zb=Ehyfx!(84mY@w!Hor~^^~)UBTCkA9L;Xnl06+f9KT0VI!+xY#%bV0LdOX;Rt+f0 zn~KQIo=yuV#Yswf*TTx28G|4>PGQMA#v@fArQ5XR$JtB}oXv5zFl40BOY?4pb2`q} zI7i@&!r9#zM&pdZ*#T#)1;^oRYsfa5P)oNR&h}PV)zc{d!;ztWN1UB{l$}dNq2|7; z!QG}2arVI36GvLwUO4;X?2WUp@$6Ib;OO{Y=7Mv8MIHE`a}LgUtA%dct7yGT?VM}yJZ%GNiVJYAz_}3TQk;u$E)h1Ii-kwKn?Ma- zhI6?fM*Pj7$hlGwoQX!4O1TvYr#7th;t{-T}qcnIq$)F7w2A_=Wy=Bc@*b<9ASF^=Rrwj zNo@SrB$CS`N)$V*sK;=glpxOII8WH{QqrHodBzx?*5<98&kEE>b9^4>C7c&Z3eJl? z`pYJ6MXyC(hDuy5nY_%C*r0Z?*z59d3a_i?_!`2go*oDW3- zoR6$kl>O)voKH(RIG^Esu4R#KZZdJc#QEApeWl~1%KQfBTN%2wAX0Fpe2?=N&JQ?K zael=4SqFCKC*>Iy;sW#`-1%_l#}((F59uFLN?A}D;+1e0#$5!rAFjB46MCgmmyI$( z2H*~?1bb-`e=6=E+&)d)6z_Z`WialdxJ%(KhP${W8B&Rl69(KRaF^_I3Uhs9+@*1s zHJ)WE@#cmshr7J;3qw3bl)+sQw~ad#cRgHDY)#yiafjiqVlr37T}@@iEA=F--nWbF zJ&LMpRHC7VtcANS?%I~)I+bQyG*|guh1bWe<8FXk#oZ8BhW(Rq?mZ7e~ zaCgDYamU~mxFc~pxSQdQz}-|ir5-1#ad0=s-9pP^RU=B`Ziy=g{ zxVz!*j=M+CVteB5WpZR{TH0_QTxrt#;_i>TpGL`}(hk5C=W;mv9*%oNDNX5-G=`&aZ^Atq_hQ^*a3|m%i+cv{ak!`A9*=tx?g_Xu`BxoOPE%?` z)ILRn&6`DYL2ysARw2&ctXxWwCO01UETw4hY)w1pWVN{lE?nz_!!JrDOn-1Bk8 z^Uuk`*5kQIID7KN5uBvD6!&V}%cP0oUXFVuu5A8KYk?DSuc|~NRYni)KDsxAr z+a&I?vfqt+PuV7wQ+wQfxWXp;{{kPteNf9H$wjyNytohJK8E`Ut~h@aqs^-$hH^eG zuxEiMabGkmJ%uZS)pNMd;6B@B=;r=B?hBdZesUg1L%CY5r+>dZS!2M8dTh@s7k&kgd(K;>V>mc?S z?l-ufr;Z27(5N~?CdGThzn^E%;J9snU z&5k!So(%Z2;LX|{Cq=TTB+mB?(d@iA@aDpsQ!^6fl`=QpJjx)WlbH)|KD>qT=C@dV z1>h}+w@{f^w;g)@@CN8m<5i5IzZ6h~h=g!gyg_(F@%r$V!dnDyF+BM=C2|&39x?dj z7;gyP5_tL|=(ZDYNsY4JXOfr3TLy1=yk+s0Q#R3AM#x}wycO_Pw1nE_EA^i$@r-yY z;jJu0yj85!t9GR|kHi~>*T$2CF5Vh=8{@5sw*lT-c)%XlYbi@ zN~mHt#2b#ck>xAzN_n$L@i$Se!VB=Kcr`qytU=`oFAyF)56{Q5q23Gd>Ua$sNbl8g zSj9$oExaaPtQ1Mrt%U?HRcl)(()QzJcw_N$yshyHyv^`Bc$?yl(CpMc@{z8r@y+qH zVdIGlP;Fg!TjFh{?aG0LVArs9p$x4gFv-VQnlc-!J_hbQB|4ll-Y zFW&#~cE;NgZ>Ms67&P<@iNf0jZ&&4!B9wjE+a2#3ygl$P#oH6_Bs@{(V7$HY_P46s z2X9}z{bcSTEvMpLXv6~y9*B2PrLm~s^7P#&Ne;m~8t+iNBk&HxJ6xl}CzVHYmx?}0 zdz`#>#b}KX?-;z}@s7niP7NDQr%grM=LvWxYJ)e)A_4DYyz}u+!8;4@RJ=3rPQyFB z60T}l?5ZWwu5@OlF1s;;oQ-!5-gvwT-9Fyc@Las}Q~@zUBa~?sp0tfiBwxG>@h-x< zSTzwfM4;w=la=c-yesf7@9CqN;$2yZq`yxzSY~>)rqYsLi}wQFb$E~9U5|Gs-VJzD z@NUGr1@9(2d&9;vszB9t65eFBt!lNXzH7W&@$SI84e$0!pnXcCdR-XrE_rH8dp5|nq7D*vci^)b9>@gB!}3hxQLC#Af`c9Zcu zjrUBq(nXr4dQR#H?|GF|j+x$zc%S0Eg!c~K%Xn|%y@IDr1@BcYk-YJ}*YV`zuSig_ z)*mJIEfe^*W~UZ=7w;py_wYV2`uoZ$^CfLmA1ZFuf%maVmMx%@ML6DPc;DlFj`tnj z7kFReeTgTYKjl5LZ>6!1iTVcbTa_SV*L{0yLb0#}4dVSsFe~0q_|oKl#+M=D7yQNX zrsB_s_ba{(T)*M{gZDe$UwD7u$>47VQ4OVK{%z7iX?Xb4X>Iz`&6S(ZvFZ3 z7r|cuzd!zh$jd_c7t#Sl8nfRIUuOPS31@dw?GL~ogg;P?P|8vMKFw5h9*n=Jc2&~~ ze=(CbM6=V_CGc0nUlM<5qc3F=B>6Jiufzz55-?e`;-*0 zY!Ch__^YZKC9|qA41Ys>LDt4!!}MPhe=XBL7AV=_uY?oq_~Y=$;Ez?Ci&oNrwSKq3-&Q2(ASRZSUg>Xdx$J=dzwW3g z`m5b`!rv2rXZ+ppcfsFPQ;FJThw^vF-$PfI7L=jPn%`ddd*f^OQD%YCL;d~m4>fJ~ z$3Fo7VEhB|W&9UuRveL_Y=?g@TKA|!aoK7Jp5Df&%!?q|4jVT@y}2TOzYhuL2LPJ{0XM* z_)1d;Tl{nI&lNV!LM=H1{`sY$@XIQ;hNSK4V*H8tmzeUG;$MM(8UE#6#ky60Wmi=h z-+WR2x=KTmT#bJX{382@qnN38ukivO7LOFO?wlV}yl`5(#J1_4>ps-2%U=4bGq#TS{+;oIOZb<}P7 zFXF$Xa?19jc76r_Tl`n?-^Z8gei#3B6aEJN+xTzdzolcEzUVJd-L%WRqY^Z`_k>e% z?ee0}2lyZ2e~$kV{wMe!SNwZ5R>#aw@joj&ks46j`4{+K;eTli@99g~{~G@reSe8G zgZSSO2>pBfAEe6&C;pH4zu`-V`UU@I6(uthSyRhI%l}nPElNslYPo*L{|o;Q{6Dqq zqQA^!RKnl*{|sDYAMDR>T>{zvx1B2u%Km@JFP{J;IGjK?g7i_{HX%5gph~a}L5(0I za0o&I*J3?_z*6~EoT&tL0-68YMv!eC5JUtzwI*m1#02^lQ0B5Ol%5jk9!p6i$O*O} zD0Hj~Is}^$j3C%lEh$qbvm1f#nCZ$?laC}AMX)8o)&yI1<>*dMndxYPvBoy0tOdiz z=_<^Zir$uBKZ5NDcC@_o6T!OjFaNha7uEU%hq*1MHuA=rap zZ-PCMC`l;G9AS{LCD?~xU*Qy@=BSNxe}V&a)gw!%;6Q@I2@WDS#F!5*ISCFWIIP4K zYWo!CBM6QpII7H1t)Y$c7=qgfjwQI5;5dQ_1jiGcMsNawsCpv7Nt$moQk#nMpF(h| zPJ?anA_XKko!~5jGYF)xXG%uB5}i#jUMXe0$PQ9)4#7nP=Mr2%a2~<=#%~oZ9ZIbs zOPjsQ3KCNjTtaXy!KDP!o-ZS~g5dJ5G}Yuv0y+6l?-IOB@Fu}41k&T%FDrHtPh{NCUX27fg8lR*8AEBHl^cm-mpKlSgz z;8%j*^w&bgeeIM#R^|90g>RVE;Qy)RSK|RvjtLvd^<-9?? zNj(;P;G1&^q~223TlIu`YQeTaIsY$FxxvCf2M7!=H-}TxuR8 z?jW!tUmkbfgZhrtcc;D+^PL2eew2C< zpHcOrsUJuE80yEWHze`oX_ZA?3aHNi_0VShM0L?tKZ*Ke>L;t8EBQgPeu_b92B)dp zsQT&DFQa}2_3=hO)8JVK&lYGr6R4kWlyeN8Yw$ch4y_U{pnfs+3ypY@Ksl$aQ1iOP z;H4UrAC~KvQ@_cOE2v*d{W|IssY^4sN*TK9T|@oaZcyaQk?;ETCgcX{H)@U|{9pMu zTa+9DmAurQS5L?kqsS3Zq01ppfwxn?gSy?uTffsNcNx6f;5`DZEca1=i2D6Te8Av? zWt2jd^Dy;Cs6TG_qXr+-C{bPu_yqMQsXtY!p>);%85&X~jK9{dpQte}THt zU!?vn^_Qr>MO~QR)E`>wuTXzgo%?CmdyV?*)ZftG%#EUmT%INVLhEl+f2XpSIVV5! zI5~e#{XOcRQh%TNC)DNtuk)#YXo`KLraOKh^^XNAF1``u*V?+s|6JRZ9NibsZ{l>T z{w0m+sY|s=VZWySjk+e2rX^*OYsKo{QU6}~<=|ZX2kL)Q|B?Ey$bz~qzCcCb&)U4# zUdEdARB_s%=j7|ZQU8X(mfk9 z(3qLVj5KCaX9(77)agM(o>}#UBCasRv5Zn?qj3|B*=b0sIcN-^F(-|=DmPCO`84LH zu>g&E#M9uQDd*9cm&SZF=I^UsDZw6bK^pyNETm`n#f*)G6%pe%Di$;saH6?J4Wu!I z#vmGlY4qs;E2d~HqKNp1lwTeii_%z35V1!8DO1#ChnS_Y1dZ!yEJdX3jb#lkN8i*rqgs z<-Q#a(P4Yz-=Sm>D34iSCmOra*x6!tF}Q0PYiUKX{b}q$V{ePv(^TC{pau7#u^)|n z1urQj(u+ONcn+c=hB}zWF*FVliV+W`aTtvwX&m0893kD-qK={=&OcufyrdZMSQ^LC zIEBXXG)|(S^=R}Hl}&zaXq;S9dO}XM*wbj7UNV%iG|r@P7LAK(oK53=;~7t50*!NN zoKy0cwDa`nzUe^Y0vZ>Vxba^kP;#Mh35_dgTx!J23|?MF87@8L8X6M~zlz4y7WJe; zt(|L)ew`7^Qr|%1M)i5P$hQMV)=h_-X*^5g78(!Gm_*}#8k1?&OP7ozbG;cSG{5zaz5J>iUmGpGmP{)@>dN;nhY%sQM}P+eGrvl22bXgsqM&PO;0 z;k*`{lW?wHa&Z_dN#>cx-#2P`!ubgo5KcL)PPm{9;DQ+2!h|aj_9I+|u%g3z*q?A| z!U2Sf5)LHnBOD~0r8L4t2nUOt0b4vJ&?HD+OArnrT)gDzaV|-?RLL_fy8&BB!Ivdm zPIyED;qoOSkZ?u9)r>f_a?av}D-o_NbcQZHzZusG+&Ew z9gA9fTE2wq60WCdwP@=b+(4kQ4JUL6HySWge}5YB&(e@HX%%XnX!qXcPS_BH?kWl6YOPd9i5;RM1n zRe}=FGI+MZ@k*56zQS`XsF%eUavtINJ^TV4Pc-30x(T4j#e|piVlOqyWd<)dc!lz- zZ6^}yJywKQ>GV4k`(I<_y;h4akCy#?6v~-gQA19nl z_yD0a&btY3CA^dHHq}Rqe7nIrRE`Gk(zFZNZ++oCg!gJQ&_Pn<->2^2!uvHlMII!4 z*mQeH?`lzoM+ik>asKv{`koy!Om8%kwQwk6Uq?Qlh0-ddBR{P&cO0OtZZD~~K5w=5g4UztBEO)@KdRwNgfA1mYFU`e>#m)eKBz1;vo=&IlnVcdXimb93I8Jegz!hgPYJ&u{EYB(ok+`Vu(FEO ziujW72g0wck9OVPy>~zNe&s->cb$B2X4J%XbKWBK)23XTo2UKHvhv zscLF%HoqGDO>6uBvCkibf9kAQjX%+H`I~5F!heWnK(=~WZy&SDnPCz_FHCS8N* zkGg9}XD6D4Xf~o*iBJn`!w?_lI}puIG>7IQ!Lc{$D>#~qXb92VL<30s1+xklhp!472jAk*iR)|nRIiaRcMJOs}k=H*HNX(L|Tcl`pA}Pgs zl@o6xTGQZK2G=&Yj=^;Ws#C#eeIl1=1ES$X8|qa8vJ4a`mn%gZ5eb8%&c~un3|0-+ z^bhNY_WKW~N8}SFL;+DqBt>p$tlX9*kMxTuB8m-ZmUN<4mqFO%1{{G>x$@6=lo1_4 zB&8TbR1l3M>JV*4G=fN`h0l}~t)p%vM%Y4c&5%kIG&Dz5#3y#)> zt+X6jiAQ6JgmWCxc0}6{ne#WPK8clH6KzkVj@iY|`u>u>AMHqVDA7(ta>7&8+nHz= zUByJZ8Wf-5g6q0QTe9MM{|nJxMEeo#ZMDCTz8bU{>?^SEm&rt8kb{U0=+O`C(GNDZ zLrS`F9!7My&g*3HD|0g~+L1&e;V7c3h>j+@l;{|uvx$zi@*YQYGSTrwClZ~YEnJ>` zJ-s$4I!RwrSBY&;Av%NTRGk|~rxBg5`>uUQDZKwAWXoFI9YkkU2A`*YyGujW4kDJB zKy;*&@8|6Zxiv$s(1uxNV)x|F(x?F9mR=UEsb@E zzO;*7O>{HSHAL5H_ZQ}MUnA1{Ux=D zCGI1-jp#w5+odZJ-9co|@S{5|ySohDt#Z1W+-sEk4Bk)lKsQRWdx+?9Yi17{e8k|R z25tP&F8>5gX<$#%lm`41&Gm?$CK3_R$>*UtuTkbxo>GD40yGz;xggC!G#8@TpXS2G z(~o9l8k>qLill|1{T}mWt-u ziYsv)gX{h$O0ED9`UW&Nq}eolIL(b{Iy5&{HjUkcX0?ac6wxxeH0v}yV-WxUC1>y- zJsTDk8jSu!kB!nYm>5hAwrOU9=%exrnp+#aV{in`P0LuCo6+3dh+Fh1BQ3b4!L9z2 z*C?Zmra4AAyY?AJa|+FEXzoIDTbkP&&vqpP%^fT#j_;)4I~v@{^s)W_u5P>1JelTh zH20$^llijjd(hmohwnvm?;gGnO}iYVn|s+NW%4ib52SfC&4XwjZoz|T9%9I$G!N^R z;@^5d0{Kp=sg4q;PgmeEG>@fuf<+x?@c944o@kVldd#QLyq@N%G{+nLG@7R?vx+@~ z=9xYGETf#QL9L4kG}Rvq&2wm;+vAaUgiy|>c|l1r&WmVXMe|~sm(#q&7%uJcU#3V` z=PNAgN}3b@L%h1jd5y(hYw)^Wk{c{|qrsa58gjG2TWC%)WO7M2O>U+670ugd%CLMp z&HHKIQO44|)0povcsI>^47t~!UH;Nl@&ThfXz(F{hCEF3kskgi&BtgyUTUaBwUvzj zFVcL7<)zoGdp z%|B^=NAnk&-y874|43abYH&iFhI6nJsD-gR>IPOAPUx#NF|qcy{9M_}^6{o{LxoS`z)h*Td%{p1+4L zK)j&hrRK4X|8YM{Vy^(zr$6z49{)h%LI2@j1UXM04<=rUcv0ddiDirCG2$UQ+lfW_ zC3J!z@@3{M`wueVJmY2JrHPj**FnU~5-%qge(5qZUY=M!`@TZFg7PaeRHhlaT#~7H zyfX1>#H$dms&WR*ty>2&laGfHuddTAnX}0PEnb6oP2zQk*CJk9r!lhiCs0;N@w&v~ z`QcmL!H^wJ`6wT6KpYcqNbC_0C*Fj3BVzIVcD_!GWo9d1Fts?c|0A$QZ2q5PR|U$W zM4z}p91z!a)gxwhxL*hvF6#ZqJB5vvoO?7Ayk0DNoHzQ7o3*vTV54+4U&P-q~ zP>m*2uehU&zIX(&`~r5W?)Jz!NDg?*mT4?WYNnx2;%*S4>r0)9bm|T#0Tjv zjjVvm6_chq#G(!*K8*NqrI$N78g(SGw5X$qk0n03OhSCjf6^W&%*4l+sfcxLMSPOM zla*+>5R19aGU92(ryFuc8B45N8S2qDKAZS_ixQhmAhxAkd`@>w7oVqWCw^fJ7Z6`e ze4$b_yNmQDXv0mJvW;Cvd>`@U#CH>4K|G20N;A|%;_Hd8BEFXRYT|3uurm1^nqU zuA5wi|3FJd#UE+SK>QQ2jNU&J|84gCg?K9QU&Ox>n=k12x3WhQ|3Pdwm`mTedN(zo ztZf%Mf8bqr=yI<$9j)o*E4kbfuKZFptr=;}L2D*ja`wsZ@KmdyW!GKAJ@ zv=*ne0<9%zEvaL5Y5CStw3arWWehHBa5;m^3oMOHYeibC(i$oWX|1Gm+2U@kOly@A z{}1OdNkVJ&e`}|;CM`KDC}muW*4n1kI<#b$Thv>Z)_Syt(^_AGMp6Htv^LbBwB*)C zv;tZi(=tDwtxafE^#!CnweC$K%I4DYG>Ovv9z&g0Myo+9p%v0<(u$PF?jNBQ(`t2t zmY3c{(@JTn|4)5XY))%)S_Q35X?19s=VwVSkMYnl|8JU5s$*nHr?q97Tp+EjX&p#w z6s=OX(X__U+LqSXo|5BeZBycw<95BQx2I+Mhuy;NNNaamI~mW;w07wx(JXeQrOw|< z3ave8?PY9x_Vn4C)_%0i|7Ytz{y#PC{-mylWCo#Dp`qox}DlXPScDe zN(D+AJJY0{RZ_st~UIdGC8eldlIgvbsMc4Xx&WfM&r4u)Zf&;h1L{W zlW0vY`4wt$Zte1Di@KfGU9|4#MVSj|(~#EPwC*W$G|Bh<2Y-N;?enxAr1g-+>gG>3 z7cKImv>q#Sp(U$;(nQh?U!wICt>-Ms)3kKol9u{^?(saYxDsD5DBlG%iq^}tUN=PW zS1szbGRp8b48BS0ZA0GbNqeW{p(XQwN%&rm{(-T5Xz(M09~;9bhJPwhIn`F5lZe`1 zkVwsbNg_??D-s!bz9yN2);F~Np!F@SUub!Y5k`6#IsLbaMf)niTZz&Ob2W-fn*g? zQfK?Bz-lDBkcg0sWOb5&WDSz_N!BD;Tdun$SxaTA0oNf>FK~mF3rXYyL0Ty<+)Oqg zsgi6+vJuH}^%ETHB_Wg9Aar%|j)ob4X zaX+pZ)kzv8O_ETWhY(33jg@=Vlb9sYHj}hOjlQ=xA(85C>uXDv4Dx|V%bt^LK~j)x zPSVjPl#I~!lWa<|nHoW^7=G~J%AreCZk?cC!IF_AV@b9o*_uQ)f6f=ldgEs@%1k$! zWQ-izv=berF~^bYK(Y_@T(iMVZ=s{j+yAvz=CI!j5lP0 z!E;LbzNb}JBe3J7?E->UmfreaU@M4{AC6|y~YQf7iwp`F9SCBkRA`0I^GSTYy zDw69Bxtin}l56z@pw|0!0`-wGC%M5WHyXUDq?6oSavE_Gi70l5;ZqFWYVbCa+Xc}_ z<=jbfuhH+a;N6DbQ_3N^kK_T8`!#4f7|(+w4^5+xJW@&{dDPNMF(2zP<@8vRTAkwFB$)HB+vKwUogsxWnLt5|GVl*@=A|l|P2?8ek%NMjfiCF)(t&bMld@SBqZ{|?-vw@DaASdbQIt}uq%|F)mGZDrq?o?EZAm?WN>{c( zk>NL!Hb|SKp_Mn%Rwrs)@`nZGFMwA_Tcn8+SUWG-PGV_TG%dS3)(l3j->rR(k*GPL@L&Pj&y6%!%0Vx?o2wGbX(Ff`i@S=>Nu~+ zIMQuOynC5ox*h5E7A5)aNZP$*P;X#NcT&zR?9$J47g9UPpYCeRyOHirx;N<_r8H8# z0)TX{$`*S3gH-4L-6>+aAE_PqPxmK1K*wr{Qd1m6dI+ga{*6d#hyPPM{BKit(j!PO zCOwk$bkd_pPb58>)V%$t$B=gYKby4UE#DJV_`g}7M0zr*G>lWG5smG%o^EH5+T1=p zlk_ao@ucSb*(8uoD6M4j&n3OU%yJ&7&i}hnTG$IoFX~dXT$hmEO?oNmjii^6UPXF2 zsZIM+^Z%SLQQ3M4(utL^Nu~WV>D8pykzPY8=l^f1NbHy&Dr3ap*OT5*N#wVMTPo>0 z6(kZ03R(w(0seVkM#FhYMq z>9$ZMeX1v4=l>S_Y$?a^=Sg26{fYEN(vL}BB9&1{jPM$%m`+at>F0HUG9yY~Cw1U*$Ym^+Wm}ht0XGp&!{Yt0bQe-)QrSG!1Hl76S0vIQ!_b&&ai^hcE` zN(z+y-t=eM3zPmrdsfn^v}YpymG+FJztNtK^mo#~NdF-HQ{QgV_hnd+ZQ=B9Qn~-# zBnw6SF}9}{g=x>AYRhy~;DvLk(b_Z9p2bcWj@KJ1Wyh}#?Rja>M%%V}+OyN1L-i5$ z+H)!*Qw}+v)Sg>6$=ma&^%R+p_CmDhFKt450on`7rrf~0pVzH9Icp?ZwfoT?OuIsR z5bgf72bux{`o8*75PK%l?xVekPKv~Oa%ra6s=X-fC1@{3dx+XZwjA4XKJzi1uFK|9 zdr8{M(O!!7GFlOBTPrEDtO_jKSbKS`@%9RGx{UUUw1?_3v$IanM=2}Q-h}olwAZ06 zE(pX{s|hq@n8DR)%m2SZm!E%2`dYNtR$RR5bt&r_gSh~0uTNY4{}q1q|7pl@+Uoz) z@Quqm+i6|hwg-j>Jr>PgY|ZUQCN@I7cBPJ2(XCJe%C#3KwIX>yJ- zc(g(J2}>j&D^OEu^FH3-3A9h7eFg24XrE=2lMS9?P&&wIGKLs_I_)!LLP%SG{?bRr zmiF0tYOy`u-~@x`&_0j$xqVah?<#?`r2}3}`vThf^Ov>oi?oZKt|tR&Uqbsb+L!)^ za=GFqg9^Wr_C!6G)xL_h%>RWVR@F}cw68TCu2W`{tg0&KjpQ}jH<5j!pL^Oj)4qjF zQca@$2<^$V@1iY=-A?;f+P784zEW9qxb`d=o!WQMzEg2o(uy=4BJQSrAMJZ+%g5jM z%Y~lKrQ7$@en7jeERbbd)qYTL+7How*hUQ7mu^2wCZ!Yl2ecojEw%Xs?RRNQ=Y3i~ z)wQ3}Rjne=(3Wvq=<*3b;B&N}r~MLb85~|zo&oRcKC_(8YQJm@wgb?X{r@M8_?p4j z1zPNzN>@2=(SF;a-YKILYN6iKaZi+AZ(eJwACg^4`y&~SXn#zW(f)*NP1>K5%|rV$ zvKeWAPWxxtU(o)J_LsE3v9azegJ1W?CA(dvJ4SuqBmO}9N7_G48@_~2`xn~3(Vl9_ zf7K`(+-Uz!`ybkW(Ef{dcQ4oY|DKkdY&tSI|7kmE*$n@t&1NE-gKTD@kj+9i8`-Re z^PjY{Pvf_!Imz^`VffsNh;G@uWJAg3BO6RMKUsgW1;`dAThQ1RD*27GUx|}dN_~uB z0NFsYKEnqoLs!*BN}Oy_vgODYBU{3Fh8UFZp=It%k}X5F6q%g-vFTrz{-5zbTi%ka zK(=C;s+Yw|Mqin1HA7Y*TeZueHW{Y4BC8u*qnBze3$9I8CtHVX6S8&5HZ|?qnUZ%`NQ+gZ8snX8Zq|d-+3{w?^4WM`9|M0Oh4$tK|xvQwvJXL+4YX50SR86}iw#~<^6NBA2GZ&i~uDTHU_LWc;5*cC##;2QED$ z*)2LFT`AH1Lm2?GDP*^k-AZx@W`xBwUtzl>-TPvX$J$yo+Q)L`(*NCi^M)n z_6*tcgH{|(_AJ?Rl?}!VE|Ba6vX{tSEQ7}IayNFZ_sCu)d!J17e9NT0Ztx8?^V#jQF*|Z_Hxfl6^<^y;?@zLGoc#3;3hvt9kuooIe}1p zzmff}sTRL;;B)%VFWH}Da`~731FNFs&$a9y@|nq}BcFkM`mT{hLOvt8{@SBEV|V?d zY4cggXCt4L9GShSgt9L3*~#b70~^XSr~ZnY>!Lt+rE>k$uDI+f)8jE3tdRF7A5a-Dm)*#1WeUj$k*`4BM=qWpUeOJfTsDFv z-$lunCSQzvN%A4uw)4exK_N>OS)xiw6i&|=i^!L=B=!|RIaieb z{>X=tuS32P`7m9WKAm5ODxF)%0cZ=PKd}B#Mz6p6%>2u#Yz&pmG9P(eu zUGfp+9(j}8CvT7kno zX$6&yD$mLF@^>?7M-i>WP07z9-;8`D`R3$XXuV5y3zXSiz9spYR=-=3Z%w`r`6%+; z$VZd!NIr&qdvf{yE1csD>iAD?uY9NalFLQ#?-_n{FZNgq9!GuxxlH~wi9qrbOT=PNR$L{V zV(?Uhr;(rDqsu`uODNWsKP>XI$z`%Vo?JHG>*N!xu;-AUOMV6Uc@i{AX|xN-^&UN= zTtt3x57%D+$n{=7^2^9CFX`I&m12j%@`>bES+KnPOX$~F@LGe{8N9wsqOD7|8S|US z-zLAA{4Vla$Zsc~L_S&jm%IqHsZ1fi)f)C~+H~cSt@8X1@;kN3NnewaYHz)p{8e&k zsQ2ohTv`|R>EBZM{p2r`KS2JZzTol)$sZzr%)oN%tze;Yf-FS;(d?V zKf=Yj6dO^jN3j9L`Whv2eib8ZNHJWs`d5dIDXJ2r*rZpq8ik{B{^ju~wxsYWQi^~g zqNrPv21TfBW+B39xCb5ZF1U+OeNvuIOnN|8|%6n6QSYE`A^P>hh=`+nU+ zp#G^{Y(}vK#pX&pYFwEG#Yhc~UXx-giXADorr3sJ6vY^d(Gn%)vb19W|NA|Nkkz>3N3lM6s&`DR!pVMWu<_TBo~F>|ylXyF}&Lvy?-zH^og9 z`%s)gu`k8Z6#G#eLa{%^ftKn3;p`hFYzI*stTKC(5Q;-7j-WV<;&7EVe)_&CpIDM3 zDUOqJPpd&~kgCs#0_A=*ijyf$qd0{^owsN)%i7Vxp5D{> zOo~e=&Z0P%;%tfu6uO705+)@S=V+2{+0Uc6km7ub3kGaq!)oDQ-1>ImI-67TDQHg|8$xIc{iKTurA+$E8#2 z8+Em4NXL=9v`2J2I)UZoi!}rhx&<3_E}|2fN)erX=``tVK_{k@(-HHgbdqwg(%#)^ z3zR{LP9{X76m&K}!VBN~pLHgvY5v#si26jOD3ftLIK_&>7F0&0$8Y1lXq?&QszY;bo8o&*gVTy|u4 z$7gmH3j}w!5Ht`hxVsbFT`%tL?h*ojRn2bh{my^RoIW+x)zwwi)ipgmJ>B)asqaR8 zPwKlee{yM}2>-(FhE4AoXKS;De|iO#KMz zhfqJvvJO>B<#V{OmCN8GsUK~*NBs|zV|tXwQ9pzF@zlprKY{v5)K8Qw%~cbPrGBz7 zk;tPCEKiyGDb!D+eyTP73soPXetIn%s^@0b&!m1X_3_jvP(O?M*}_EqB>O{SSN$Bl zjg}K8Ke8tiBFUQhj2>Niloh5C)cocc}FZ`NLKo*?l-0P94zQNPoG z+pY87q5VR6-etXS67{8)h{Q>F^Q-6^9Ln5b?)l1{R zBh(*Nj94;SPSm9SIQ1t@%_r0*@tf42QuR}{D)ncmzexRA>d#YuPWnjIAF02fk;>u~ z^_Qr>LS58(Srrj{nwoa6szC&Ehjf$IsSCF^sJ~BLs(6R`Th!myiju2Uo^&1R?^1tH zj3Bj0Dd`~8KcM~*^$)9C52tn+?O{R^dx6I16aoSCS9P5o!; z-%$TnvaHFzqyD2vNd0?L@CT{U>iS6|r-e1@zfhk-{a5OLQvc0L{Z3t<1wtvoQwo|e ze^H-I{cnvG+R;Xck5T^zXFALM7iSurY1Qi_KuP0HSPEx)0dQu(nX!^bQWe9Q8D}sK zICJ34q84*z#hFbQ3V}M0GrOp)`^-3V;w*qO7tXvmbK}fo4WVNm^$lk}ocSfDRpCnv zg|i^eK%4={r%16|GK&f?uIu#U3=&~vG*wwlJj(DxaKFY`2-m||80RFMMR0b*SrkVw zi{XsGSsZ6ooF#CU$5|3*85~hNsoS48>VPI=lLVc!-RLowab*u8c1jDUP!)&UzIeoMAY_ z#oBUf7T6zWeVnauHozH)vmwsLR%D}!AW&1v9dI8B_$4A{a6aJo%d`4~=YlnG9%43&dM51brlSDXT;gVV+-RjTv_O=I0U zNn1g>O#TGU?l_0y?17_Wdz?Kj9c}4eID6xaQA&$Nm3BD$TCPYaCOrt}0GtEG{-UEj zK&3$s#yLa;6Edk(l~vA%;T(>0jKN2kL5{>Z3g>9Gur#PfU}@`PEj8zIXGwI z$TkjIuZk_BDd$|A^W;{|n2*s)|yCC)^gOK>j4xftgnArLwB6=1@&Mk72g$In~SyVeOk&;oOLGHO_T7*Wg?`HA?`T>v3)nZe8(IubXgg#km>h79r4zdTa&8xgF&qYC&_&{58&KCm5JiDmWObj#CaI!F`P$m9#wTz z@k*n|aWwK*x02#Kh4T!~(@JAxIM3qf$bV`VmRO6&VchR&hI$i;rxR0JeGMWr2Rl) zTdmGg+!=A_$DIjx4&0e>XT=3q&VpCAl4|3+v*FJEe@o4YI}h$$xN|F|Hk{4jyYu4C zr_AO3T6BW!SHxXO@Y)Sc9^6%M zSJiH4l}d`cI_^5SLvh!{)fWM|Oy;$4*A~uFwsIldb#aH`u2<1W7VdCd2^!jhl?u2U z;EuxG5O-7Djc_+H<7`|>h+7T*&2Qb!aJRtSysA!8+%0i6q*v9MY+K{*h`SB$cDQ=} zyIhisyFKm>YNG0)rjqQ0TgTlQSK@!S(T%5r>sGDSlh8MO1NUOwCT@b;($Sh5^bOUi z8{E*Uh;U;W42a|!!CYzLBXKj_y>WBg-Ea%k)7ny``@ZRRaAh;YZ7UTd(BbZmI~sQn z+&#O)67>OTzP(iP_5ZYr#^4@=yASUExclPnS3QOGaU)|4_W;}jg-o9jUEL4HJq-5{ z+(Y%TZb%dRaNHyG$z|g}c`o1{g?l>g(YPn!9)o*4?yvOIzHJdl~L6xR>Kz zi+cs`RYq{77MD@0do}JgDpjO2gaY7RhkGOL^|&(gZ?7KJJHC4p?#+S`qp2q-<*m4P z;@)QP?YMV{`eJ9h#2@a$y$5#^uFesOH|qO^YH=^_efH!nMG~~`t|LJ~;`z7vY<_VwU>fpZ;Kx7t8 zalf(=yDS@HGBcx#}>Uz5Tll6cyr-_H#^=ec(Vx}Z`O)l4I(!8=D?d%+dbJzTUkb}-rRT#;?09MpWyN4l~P7I zKi&dWueTA}ctZ}5B1Se4Z%MpGgt;%}z!3V06Qig=sit%SEW-pY7G z@m9fG&G@e>rjXXOva4&Qs?-{I`V~Oh!f4jADD(o}I(X~jt&2AtZ#}$W^7%l>G*`YO zcq7CCDzC%a0B>Ww4e>Tof77?cQjCMQ37!Uj=|@tjIh^9N>Z7($H;WB^a?WnMDmiJR{X94hbv9zxEu3j$Q-gq8ff#>5z zcn!Rk>DW~MS}MQ`g<(}nW3LzErFcm->^G}sc)64k`Sp_dF0YNZCtiuS8(s%*S9MgA z$C&Sqr}ID3e42_Ud-^#*F1)=IBN5megLgRIK6nS??TdE+-hO!dYoMsKR^kKk4yrs; zkm4uC;Sjt-@eWh6|7v;!-tl-x;vIu`6yDL|GoqkcRlWLHyyK+Bt0GqW33y}iPQ*J& zgzTDH!j3lC$?67L`zd(RYfiFs^cm|uf;ossEVfB*(xFR8{fb&MPFZo+#2?`FKa@NU7o9q(4W+qykYd|so(9e8)D zUs<5g5r{Vl?_RvS@$M0Il%YJ}l6Ua#!@FMusG7+-(SvwT;5~%*DBi<(kEnz)J6+8M zd5_^eF0x6)7DMT~q4y--Gk8zoJ#D>gZmCY=v-d3CbJYy0JVrH!dm{gzcrW68hW8TQ zyLe)d*YRG)dll~$Whn21>Q2J;H3d}VH}KxZdlT<1QB8xDj{0P3OL~EtRNn->_wYW# zdmrxuDPl+|_@O-qMfZ>KKGA1)Hq;tLAMWNDpX2?2_XXZJcwgdur8UYE#QR#UEzdZu z;#<7$)a|=%@x6v78Ao|P;{A&E6W%X&E1v*#V51z=+P_&vzvKO(NGof){Dr>&-ei36 z{>GmUZwlVOc-{Cf#Q4+TPdo6o>tyOu%gT(XKRy18_%=pbX88#2 zv*6E#KP&zm__N{9UX4`sOOtq=KW9&dbMWWJpVyG{C{j9zKOg@5%1u?)K8U{{{-XE; z@N4*e`2F|;yBxBi_=E5V%dNJ8w5;gs55Zs9crMiADH}3hBW_^aM1>{s7dL_>lt9YL zSi%=Ymcd`T+hoGZ+74eBo{7Ia{*m}A;Ai+kyczyV`0L@XjK2o{D)_5e)vIcY$Oy_` z9bem5o}T9RCH|WD>)@}2zqYuRWyu_cIvD=C=F+0eF#HYhhZ~0x`0H1(LM2q58{%(* zzY+e%)k96^Uc}%0k@%a6w9=K-mz3e=_`Be5fxjL8miSxaZ-qZfm6b-6o+z_?{xUMI zeBtkozel%INIX}4N8|5p9QLwq`zrnzxfP}KN$T&5e-QqD_y-t`J_WR!_y;OadG5#) z$3GbVF#JRC4^^aCX~I$(yZpoPj}Ru>UKZi;kFpw%#y>#5E|3>_q@ozGo@~-_n{w)F< zjfiv`{=@jUWd!^O@gEY^ zw0G;=^*i{Fn3W#Ie**t8{KxH?CGSUnh#%rViT{-H6ekqjWhCG~ga0%Bv-qFmKZpM& zzKHq?{tNgonH#*Q^y&{H&&&E|Dqj|)?`s>0BfVB}!+#zB4aF<-bM%?wzlHxH{@eKP z;frAJ>T_A`vz&M}{`>gy_q}x`{cv*z{zv$q;D0Q>)Z_Un{%1m?kCJ#zQ3U@B{BQBU z#Q#R>!vD(BuY2|CHw*ml@PESpUYOwjfd8X%i{BKhippA}yifVR(2zj-D-DtJH~cC1 zzvKUfFP&7LYs*!mFYAeu@&DG@X8T;CPZ|Fo8q?zcOG77SH563udX4F5Os^qJkky=R zV@4XY)0m0IEHq}Ofn=#V`hs>2jag~PTfjAf6t9q!#vC+c!OxsD=BhB=nB16$#=OcX$+(>fJUD%5nENY(CDWzNa$sJC6{X5sL@!M#t<3{X>3sk zSkdZTgvO%cAzDy`SIWg{EJtGr8cWkyl7@D6i34(}1scoHSXO(7O)D9Po#tjVhSK<&#u_xPps^;6(KObgu{DjgX-Mum zG&Yc3r)aE8V?7$f^+&?SFx`hrfe|$1PnpH^wu=$Ao6y)ukxH;}4?nVp*|egkA)EhH zG+WTv(ulXBF{;Y#mD+|zKx11PI|_!zc9w2$sU87lz)mz=8avad8qiF0+;~*MiXdFOe9~ye>7Y+Fftde~HZgExlz^b4T$p61Wb_k6_ zDXdFS~@Tv4h(A@=;#?ds+r*RC8(`Xz^V=Rs1Xo%k)PveBC5-Ok-om63HoJ`{s z8soHzp2{>%?bUcXjk9T-L1Vm?Ix? zgf=dsaq<6B(zw(pFRMz?xV*>tN*edmxQfQzG{lW>qj3$58)#hHx1RXDa|dG-YTilNb%eCq`Af&H15>0LZ(K(i^e3qH4D(VhsJ$0?yZ<8 za*S*wF5DiV@e+*(X*{G1GjXbiX*^Bi5gJbz!J{-DqhXtWN~!g(G3K)3QL2#5f4Uw; z;~5&yBkPC+qgl@>&?SC>#*6=RD*@_#8ZXm$orWv{t>|B+A?rV<7I}ll+j2|eO&ZU-_e*t<9ixE$UZ8vwLXm>`}WzC#!obUmwIXZOhe-TZ&v%ST}itB{|Ak~ zXz1pjQ}Hw=TdBV*42^&KI>+~w8}^Mpj^;EpXQMeS%~@$q*SG8QG^eLIGtC*4d2>dZ zy7{NdS@CaT_*oQFA=g-~Z`WV+NWSLmH0Lm2PCX7lD-pi)8jB7&Gl){Pjgk8 z3(#Dg=7KZ_(HuatuW!?z)T5dMY4$6;_pq*SGzZhH_23~i7ooWj&4pD4Gp|*#C{4Q+ zQ7fCv1Y>gr^`+*LG?${eEX}29%I0sTk;zYUIhxBWMx3yVS<#BDWa-K@W%IW#Lv6L@ zYBblSDe->|BN(a$#e17;(p+1Q6=<%duCBm36`-lgupZ4}MmgNl5tdtJZD0f&(%h8h zM#@m>H@0*WnjpLNZU(WXGhVO=1!LGOmi2S`V_D%M^)Bbw}X zqWKZcy=mS~a}3RKH20x-G|hc!9z@g5e^fsE(>#D?FZg#o;b58?`Dw~8z?Eu;(L9po z;WUq^Fx^yXj_OHr49&4LkEN+GpXPBikFTG|#6wk*3D~Zm!BHr+`iEnEDi; zc}bN^^D>&((!8AJRWz?qq>6N9FZXJ@)ri`|Tt~C&v)9wSf#!`Bz9OJ`GtFDA$Spn0 zZ3;H=AES8}&HHFhqIs{;>lV;7b^qszSW=qz(|pMA572z@|7JZ*^AVbl zc8RSN&BtjzPxA?y&(O5^-+YSZ)BmN>Ua9e)rUn0QDOvwNRXgbpZ_@k^&DUtYY?QtD z-+Wccl*8-VUsT#RR3r^(5{TY0;BC!P{_hSN@&nEHXntU~?@tw;=7-8hi+@aO4w|3P zoJ{jmnqQggen#_i1HPd7rIxCETX}v>^Cz0$(EOg}wL zU;druuQc^3Q1u`My1M^qb^Xv(pYyE0$K}MT?^A%q{~gR6feX} znMjM%TB4$-wPa7WrD?55YZ+S0(^{6+a#I;r0??A@Ma4(szj9VPtU_xYTC37piV)=2r$gVv_{yNCQl)7nh`xM^+P{nw+lrQ(%fD_Wy^@YaUe#?ozR zZKwD(pYG-EKx;?K+KHA&YiC-Hao&YiU1{XM4`t|9RcNX;`aQgO!v3^cv`Qn>^IvF% zv?5w5Em{Asb0+Ld(hg=vUaa%dX(}DKviU~+8-Ai z!QQmS(AwAFeR_QM)7*hiN=oYhT1Oagprr>{da$L3Sb8X}!=#|Whf692X&p)HBw9!H z7#>Yaj{v51EUgoa;5bW*VB&^lWQgtKxxr^oi(3Q6mHS{Ko}KyQ^`BCQK6SSV?U%DUkjtxGJG_3)Kc zm(#k^FjrIpNQCs}@_|E|(}mOOQVs@RLfl(tNajq4n$F#YRbuddU$pt>0-)q4fu? zzwGu;6;jJiruDa4W7J|oZ;JmzFb%DLg_Wu~YB9BQFl|@UV0wb32xcHyfM7;~ISFPW zn3Z5=X>I}tX3>TghFf{+0E(QAV0MBz?6#8Uc7nMG=9Wej{si+7%%^P~%&SrKTHC2o{xF zf`u(zLt>}mw73m^=Ic+iTw5UfG4Ho=+%Yl$VLQmuDVAoQl`x&)gNtVgf` z!7zdmh8!+Oo|)L|OA`pOwo9-f!Nvr_c7Qao>AMNRNFft_wNI(tHY3=YU~_`42(}>D zvJ%58t<+bSum;_RU|TV-IFb}J{@eG-)_K7Wk{Ylhf&2no!8;Sk|GyPnuXeg4a0xu! zr$xer{0uISsz8d%=06?XOGN!3_ivPsPxbf!f;K@}#k#(GF6vu*0)d_eNU$4$1%C^J`dein!C!Jm6NroMWyNj(DF250uB2F6GeAXgy15Ai)FX2L5m;Vvx5eIg3Aalul;$P7Cdhwf-5979j_vgP=B?x z&NaOv*DBdHHG=CEBU-5CZzPajCh`AXtMO)nTL`3I-%4;>-^6#di0Ny&cM#l3a5uqS z1e3%*gX2{yeJ$6{!sr_IK7#uR9wT_5Z`f=E4-!ZtK1A?v^;E6>dArIWzXk`7);{0F zfX4}*lK)!?qy^Q&dX#_~;pu)kA3<97EpfkR34SGbj^KTQ=Ludl1z#X|k>F*5mk9o& zttK+apR?D$LhwrMjTe-avVD!9PYFIIki*}fQR0c$8_&-OKG*+qD-ILIwqFu_r7fr}^|hq6 zzZNw3TY?`5zBBlH-8ZRQ%Z~&<>BdUh7k(!AMQywOHnqQWHwJ>=2qqK!Eex zie(7&AOK-y$iE4vF`G;wupJy~A)Jd+szYftIGr}1OXA-jhEw7PO zjtn4#uwQ=)4rfue;jDzS5iUSDJE2NTs9Qh~$}hlk3DE81ikZjo^AgTy!2DBl2^SM47KmxGtd{1Y8v-Tu*?3x68JBk~^Gm zgly=l+L>1nZa}yl;f918>2|Mz@4Jw2W5P`cw)(D&1GbR; zJ!+c_)y;$#yN7Tq!cp2a6yBO}8w0l0+T|>!zQ!-|4S(6zf9GrMHm@U#{ZHPR`i6i-6oQ%Su`sn+=DPDEX`a6 zVOs#rRqKf_?M5j7$am?j$nKJ5z@CKr5soGtL%0{A{rOuphnC%kaNnr~b!9@hKjDE^ z@PLZH!3QagDs>3qIKo2-k0U&c@JP!&+|na@H6BHH4593`vPk6^mV2zE^68N9c*2tm zBX0zv+KKv6*la>5@n0|}_xPMbcqZYggl7;+HDUFneYw5R|sD-3%uGl_D(56_nM6Up}S8xYMxG;7tGnu_kx>_l@& z)#63Eb9*!=(Og9HsCd!bQi}oeR=d`#r$zGUK(shfAJGt^fkcCe z`iTbBCgs9VIg8z*nmL=a*AGMs5iLrzFwr8Sn&g^1BI;t51DM!L5G_lzB+=4D%0c+l z?ps{#94(_>ZATIyD`BGLiB=G!RTYWD5UoVCnpL{8rK?!Fs`iem??kH;4K?H%6{hcj zm5J6O+L>r=qRoibArf1yOEjElJ)&VkCY<}%-^2KiAX;C2L(eAoL0c!-YbEi_s7+K(mC!tGS0eGE-H3MA?j{{pIA{Y&gYKz5 zslZ-D7ZL4EbQIATqQi;yAv(Z3b6;c;OSHdr3XxN;-6Foq53DHeNmm;g3BZ>c45?xMog-R&lOZAl&zl!K; zX)G-$zNh`@TB7SkTB7TE_PK%RMsv&Klt%l)%|y~=ZXvpz=vJcJgi?d8upPb#(H%s0 z3cV7Kc*a;wBD$C8ZlZe>KlV=ZhkJCE7&W(i6vqaCS+08^E7|{#F3lP0XBs#uC^cB&6 zh~6i9nMn3<>kz$ytU8ltYxEk?TSU4AG|?N98t`WIY&H08A|3w|*;|hg$fH(vRF=_r z^a0W5L?04;BqxRsUR65I$C5?#i5@EueQNM$N-U#O#ebpvWy{m(OG$O-)#z)Y?})x3 z`c}zgxGV3-T^a1GZF$j-ek7ij=qI9Ih-3?BdkOknbo`Zg8hu5Iek1x_-noeWAo`2w z&x(U(O*YB@CbD6FG(~P}V|12}$aaC2x^yd{3B=P8&qzEy@eF6KXlXz3AOi+lTC;SBr3*=_r?hLqMMw@O zUX-LxycqG0#ET>Az572TUc%BPEnSLuY2p=#A4$Zn}^t*os75e~BcN75Z6 z)jgBro#Y*pcxU1%#JdphM=S;mi5+6u{J%3W*_PPTJ*HxxxFx4B6E`ev>Urg|9hPDO zJ)K!z-{OdPcR>=z$a1^5FgPX7i1UG|Y_BTYyInO)4h%rID+^@;vtLLv5e$?x;^o!#6J;BC;Eo?bmFgx&oIGc z^S954b^mALvxsjcKHDU*%|GLFh_56*m-u4jCz^O7@%hBI`M(^7DAxjFc2Vad-4kyC zS-v8>lZmvK5MNGwDe+};YXaMTcd>l`ov2pr7d}EJGG9%6t=-C}u8Ai6b;Q?en+}*k zFZqrZ-$;Bj@lB@7oYp$GC{IyMEp{97Uq*a8@pHs?5I;(MC-MEncM;!1Jc;=3N-9mY z`R*mY&tNI8O-%d%@x#Oq5K3KOolq--zEKevkNFtyCFG?)zN}tL4=nh(99!g7{-%Y1vPRKUHE?R&;qytV;a3 z!U{<67y7T%_(C8CmsI9v<8O(76dL01h{Z8}kn=m#k>nDmsx7JRs>OdM7FYkBxLW)p z-Saoyw_hdsgZMAvKb5D}E5UFw@!wrBWEVd5*nddYBL0_TA(ClG<|3JvWG0g7NTyer zCGsEo63GlCGwN%p@DW=jGn33FijaV;SWafODxT4__B_e#Bx3((NapBPEoX-*@!TW> zN#-HxBbk?E0h0Mh=2wFZ+*vmdlyH76Xm}HZx3YA1P+7wyU zpKM05xi-)sx!ppEwQ06gZnuaLMv-hIGLUSo#uTK9O0pfv_9~vrCW}8s_Z>-gQUdXZ zWM{L|E`38*)w)y+ha@9$Ng5;`i7%Ur%1A&jo0BAIlC}w*{(8+%}U`q!B$!;XO%NegEdswe{q;7`!t)ZSt(O$!pXmhsIxz&8D1j`(AA_Tw}$rRgtm>Ym>hvxt`<}<0D>i zBgxIRGbWMTRQtQXFFRV;$_dUSx02jRavRC*lB;+X`;NZ7blD8aUA4c(Go_To|HW!& zuP&>_L_8Adv*LC4kvu_iKgq)+4_LtmMLe+>$wN}ScFEo(kErmna3Xn>nFcf&6m319oxN8vtG-9_er>Ov)V5mP z2R#daBGc8e#{CapY`oNof=sd;3Z#>d|5kxliJ_=35G=4_iK{xNWLNY zRwWU8iX-i21}`rl`GIsbk{?OuA^C~q50alrek1wCWc#(ReV5utD$-W?T{xRaf0Bx2 z{vwsgHko9KA#MHVKH>%v|Nqt1DYC3i1QQj~X-Q`wosM*RWhDa08bV2B%}+X`N+rIO z&P+NRDWtQgHAHu*F`c#c_l

                                    _ep0bor`o1Qt`vrbcvvF`AIrhW!rT#ZM$@c8cG~y!m53{ z3c(_z%aJZhx-_YXx+Ll1q)Vt}#3lC9h4G|IsUoI}Nxlr}vI3}!uCW^F@}#Shu0Xm{ z?eDipSL_?US#8Qpq$`uIQak4p@ti?J##bD)r=+Ws?npY6bQ97wNQaZINxCkn=(|qs z!k0DHuMk&e>pWLdAI+Hl*DZmSlMhN!ewBW!P` z=&k=xcOuP6cP8~ocOmsi>!hw!O=?R(JMZ_6lwskV+K`b z5~)W??OIWS{97TnW^>Z4l7X}!-JP^8Y)MN}UGquW=_}Uj5$sm+B;7+Wq6`}RS!Dr15wSixK70zAwB2@(#J_}B)u2y z<2Nf_+u|0|J4kP}wi8FaU1Nywk)W!%calyr;I7(~vDRVlCcQ^x5HAupP>t>*eTeja z>$9>2^rvRk2i3bI;3$)aNgpv`9wmLOcE(Co)f)NJCrDo)eUkJkaj_`=mdSikp2!`XT9OgP)$8^dr)bNk27tK2b5GioJg@ z&YxTQMc*F3m;t}k@Tg*cP5J}rH>BV8?YXnMf!g7_+L`y0ey`4;{{Q2kWuFvViGPy* zOe*317t&w*_PD`_f2&=%IqC0xZ7KMNs&8IF`WM-Bq?1Yisa-7b^Ka5A8jeJlDbiT~ zB0qVFC1uc(O{;?zDL7(jp_CX)HUn8d*^Fd!lFdX0+005T8foq8 zZ6fMt`hd_&G|C2%ElW0-Y%#JL*$@f8>NeRzWQ&k3Z2e_k(Z~ues-?uL+2UkNku4!g z4O(2=T02FyjG990T8?aGvgO6XWGhG$kgZ6zlCE$NR@sC4OwU#! zTa#>6ne}rrV z*)C-3lWj$|0okTx8nKwKo(ZFJ<6CY zCrilE9-00B)#X!=b;#Oe<^RV`W>hMzcSizpCN5-wCEJs1v=Eco{Eq^A_v+e*>l3#8%L)9e36~f#Y@E2+tbO;LVgs@#@p?g6$eeV)Y)X` zl1(5xr;G2FI{7BT$SxzhT%Q!iTvD{@*mC?F37>4`UJ-;|!NtsuzP$!;cllI#|;`^j!4n?!aS*&QZ~oC06T ze<#^pTB8WlmE>-+d#&nwdNeWxq{N1Nfb20OuxF2uJw*0!g|CW}*)ISlKiT72O2w8V z0IIs4B72$aX|fl{o*}dGf2J>1Qk?AhioRFTi)1g6{iiBBwHC5h$X+%2*A!XVP&@A% zWN(s5uzHK^eX_U7-nH7_>C&hq@AV}4psI_kxBfr-m`o<$d@G_NWKVpKY7h^ z2ayjJKrgL*h#?muU%1Pyi(iy{3Cj|;i~kS(lH^MpeknzEm0G5P$(JKviF|qEyn>Of z_&;PTlMf|dg`ZCAYX@k7xHz0P;h~4t#iB z-=wGYI9i#d%x@&WNzb~xZjQ{*p^KdqlI@@L4ON2Uw%=PES^c+CO%U~;MJMFI5a8PQ!PLgh?Y znfc6LCYN={Ps#}tlMk`WSPk|x-j$FS?>7_FNNTFx9k^e;gGx?w7zmWfdjAL_&{CYMG`R@uC z+{64uK1E>i$wnYw0V;0)*zLd7t-{5$-6DmZ|58j(A+yKb+SQX~7QL96Vh#!@W}}#e zVpcV&&HZW&5!DJi|5v%qNih$_ToiNb>Ev>9lh{OpOffITeBHxhG$+vVTwii_MS)k zvZWM@QLI9-IK|59!^IL5OPY>LRT@z&O|eW5UY26H9=yDf$!AJYVMU6S6kpj>79?*J zQLIX_I)xqw*)!Kr{q|AF`X4#jXBvt%Ra5!OP^?X{KE*l|!zk88CO5=TE13P)c$Q)~ z#R&aQAv_X9^s8ck}0uF_11wBiBOB<4^%#w-`Wc6|pP^5ZLY!On3iDH#s977!@ z(Ju~iEA6FH)S1iSQ)D9)$Q z^`8dY5>U1OMHFvPTugB<#U&IsP+UrJE%E`PxV&rLjpwA0r-1O+Qy?j>qPUvE2I|#U zLiyrTTt}fNf9dLLIp9gojx27ZxRv52id!gdR{V;qtGCGMQQEC;qqtpHq{}CpLe>e3 z*mqJ)(kE$gmwuL#FHObW6!)kPh^vT8Xf5|qyhI_2KTjdu|2fm;L5hbc9;bMi;?e$L zD^ti8K(cO4fXB3tydsMg1%86!X$n#KDY@0h+|XSO{|v>mgYI|rc9YE&5T(W)Pw@i9 zi~8q_Jki9>Ch6@vQ@lxA0?1pmB`Cd3TSDJE z6n{~?OYuF$dlVm1*rb-~t1}+@lrKIq^*^TgM5o0x>r+cVqxe#X2gT@2O zrmP?MkT$eu(b#q29F{vP?b&G0O?!4^uBJVQnp!?pODg`>mUo2LRND4DwCATiFYWm> zSDZmoS=ZEFfcAoVJK%r;awelv_R(I5_CVT0^uWz_KW+K=yDRO%mg+&k%2V4;ILttM zVcLt)UWE3d>Y~C*TvSdbl}I?aLwgC@o6ug8_R6%EqP-IBrA^;uXfJQTvb2{|#iguT zV+Do9CE6>hC#?Cjo)ayrU)rnC-kA2P8tvN>|3ywY|M#lC#eCXBY0F{~!K_JpM4ucV z*e>(E}$Fzf0lOEJSVOerv2fi6-{fuy~Gr5n=TNIC1e6g~9Kq#8+kbK0BI z-b^~0E}9ZGRmd%9Z`loo5)~zwtD#2G-kNrk_BOOdm~CnAKzlpd^7Gee)nL!acBH+N zKAq+KM`g8hPw_f!kG4Zw*8i-bMZ_S=Ry1w&H|@Ad@dSwg?H27*X$Q2AqaD)TU597w zh;~f7pl#oO+xGoe`De6q^Z2nyH#RnDksym_n^He?L%pgrhO3Y zy=d=GTWqy2?J>0X(I7Bx5gn0^(^U$z_mfnRv}MSrpXq5IDCwXfp}`01fT4YeK00M> zUi&cGNB6BWAML|wA3^&l+DBFoP)+xpse7Vp`xx5CR+zqMOWMLyPIbS5_6g>GqOzR- zD1JEB(vvM6Cu!f=qcojs@7hSo71buvKApDs(iz%PGMI0lNqao)a|}L<_Spg$JVDah zTc6QBw}&~8wvPYxR}M+#=R394g|siySbb8cRy|%k=Mu_4XkSVxj&T|7Cuv_!`vL0+ zSBNvyzLNG;v~Q+;HSOzZUt`E?X9I1w4V^9Hi~9FMf)e(RiAx^_NTO;rTq@==V-r5TRQ1Wv|ph8;(v{{m`MH~HJ}17 z(|)CEOudyz^_JmZr~O9H=5J0dOZ)8#rv0vwy+`|F+V2Zee!HXn0qqZIf7E3t4K&{` zl7)OZvi71aP5BM&&uM?5aZhXfvIl%+lwVi;`yM@z_P2d&o=p2YBm3UcAB^V5E{(j_ zwST7l3*|Djf2Ew6_HVTRq5V7U$+Z8d+L893wEz46mo?wtmg@RXYYYAVi*g#u87QZv zoK73de1LL#1*A2VU`9*r*DAe*a&gL8DCeM@)v{)@)CT|mEj1_QT*x1tb_M@nFiDN+k|Z4zYA0%7qP(pOPy4qLhnO z@PMs!)J>`Xeo`(;sb?QkE-k4ND7-A?x|GXNu57oGd$_)%Z%+leMBdT1=^?NiMQjVnDsE6O!C^zZJ zu&Lc{*2}fG0IhvX%F8LYqCA3f6y;8oTT^aFxeevE`jnAjq+D{uXSqG)j=Get+@Ws` znS7QdSQB*oR_;u>E9EYfE$wJ!ozl_&g-h2`kFr7O3tZdy&DuX3)*g~L)2wZLrNEQ{ zW!QK2{_?1zj3{%;xa#$k31v!|sr>T5vdBPLP_`9g&PG|9QXN%Fb=i$_cZIJWWP<*>6n1d+~r95Qn z_=@r{%EOh<)bUt(B;|O@qbN_IJeqPG9ypQrK%DXAA zro2WSK*_GP^g7DhD6glyiSh=0T$VR#s|k(5H&fm!Fy$>3#?ogfZ>PMI@{a$DyvvGA z(m|RQxrg#z6-?H6%K%%(66O7r4^fIAKB&LK$%)e9hw7FOQwq;V`fj;RB&U3o@-fP) zUq4Ry#8mAlpE3fAS6x#~pnR6{Im%ZkpQro}MZ~c36+lvX1pH=J{k<}{!GF>br~Ql0B9xQq45a*< zj>Od|I!-A6p(6qOUph0;nWi=}mW$4`bf)XU(+e=@y&DWaBb}KHnAuV!tsM~>JS&~~ z=*&iEE;_R-Uir*HXU+<){j(RHxeYl_FLz!A{<+wY^IN(AodtWj1FBqAWZN(4^wSw6 z*3g+can#OWIyE{A>3c_Kh&rddk8~DR-;+y5;GIS3tVw4vI*aQc4dYL=QcKWTlFo{B zmZGx^9dWj9-_eHXEK6rOIxEo8*8#oc@omElIvV`xtW0M$I;&Ks&>2Q&T`em=)OXg?k&k#$XE>dW=!~fS^PKTn-_i~ACAzbr z_623QF`dn8|40jNV(Caao6^}#vTFb8BTv!_ptB_%kIq(BY80KF=xl9@Z)2Ek>1

                                    4@YFIw_qdorF${ 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

                                    odTzm0Ox5#J&nJCJ8nlJf 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 sU=j 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�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%0D$7gfk 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

                                    +Q0q=W3%ArqoydMi|VgzrRIXNBgXQ`C|AktuJQ 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

                                    !RvMP<$X{=UB8N(Vh*8ES8QP%F- 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 OzDhqt}wik;3}!D{k>0KZGvkEt|z$GEdM$J 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 Eo#0jy}8N99?R_IR@w0E`FTY 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

                                    &I3noY6RU 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 z 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 z zR0>V)In$&|-0pN9G5x1IL$bg20d;4xkU2wiXQ4aF`4%-~R=QIDk#nFs2mLMR&Pn%B zx^vMTO?Pe;oQLj0bmvvpe6o7~r)!UZ>FWKT8>!sp=vnsMC-F1Yl zMOWgV;_K2~pRTR{lWO94{qJr>cOU!EyBpKpgzm0%H>JBh-OcE3MR#+$ThiTP{HNU2 z{eO2nZ2()--Ine)nW(-0W1B@y{ORsUcW1ggW$;}b?m4@8XLq`L(A`TZd!~e;F?2Kf z-`$t4ZvJ=or+W}xJr3xa{`(?&5YV-Wz$1szn}M$T$d03XxQ@~hvPasXu#fU6x<|`7 zM)ueQ*D#K!`ySmB=-x^9M7kHzJ&Ep_bWf&x8r@TpUg+xnj}6u9fA@@J{B+Nvdp_N> z>7MJzbnS}3XPxIc7kK1Ck6cXmI=Yw8y^`*wbnTuG-ODn$S9s=Cbfy2@jQ)48_0j9; z-a_{Vx^~-#uC2j*UpITst#oz&r>iMI$BphCK6)43XX)Ne_c6Nn(0!2Zy>#zawfi#F zO#cNwL|3^=;L&sqWgp&gDwG5&eI}1bYGxr;$SKN{V&}Y z>1rnGzLbKtU$6MA*XX`U_jO-7I?F~7>x|0d&)QPidYIX}=X(EX9_Fx{UVNY}*Qh0Izr zC!0?|x<$GX-4flh(rpSbvW+Cn_;w43Zq;YSbpNDVr#qnApldgL=r(0-3Xs#GYxjKU zb~ANsE)Y1D?k{x5rJ?`+m+r50P5J5mmf`>5`G3)yobKQBoXGz}&+hrqn_xV%H&M#$ zL2pt;{S;u#RR8HsL2p`mQ_`DS9Ge21BY&EVuWNyxU;p=Jr1uQHndt3HZ)SR{(;K3< zD7{hi=AkzWz1iu_>g&wrM=}S!xm0=1Oy#+KbY6N3(VLIn0^-b{;VkGm3;UXjcw{kp z%hOw&-crh1LUu_XU7DU=`{^x{skWStu0U@UdMnZ!t*mtYFMnmvS=BX#nEuOIgWiAW ztx0badTY^JpWfQ^)>X)61kYd3^EaTkk)j)Bq8n%YP3dh$&t#w8<{3O)|I^!w-Y)dE zrnf!4ZRqK>&)&8pOy|(sf!N+ z?_zrU(>sCQ0rU>1cOX5j{Cfw9aIj|{O7Ad-Skne$)2IkX&^wx*T?nY@*Z;j^=p85I z*n}znc%OA5y|d_@MDKKZC(}Ds(Ni+a(>(JG7gF?0kDN{K0($4rJ5TtznXL0Y$1VgE zy~rb%(7Th~rSxu~cNx8_>0M6mN^w&Cmw%P#UqjE{`JtEUzx?Zc^hSEO2+>^7^ZMVr zm7es!cU!^~$LoLZE_x5syPMtv^zNZ|pQ86>nD_ho54w<|4|(JfdXLky`~Sip^Y|0= z?6psN8U63+62QP`>HS9UIeN~2p5BM_{!8x-dN0s>ncj=OTB`r_UZMAzs=b;S)$6{m zH|a_FdvA%8>OZ}A>Af%Hy-d~zKIV8t{qMW=|J?NFbj4MFF3(Kg|EE8%N9L!$ zGW`YUFG+tv`iqFaknF;qvnc(=g)EjJ@|W<@rRXnDe`)&C|9(dQ`(FS1E6`s_Su18L zjP?~)p}#);Rq3xqe>M7Rh`D-(xu$2XO<(%o&**>O>wkX(`kT_v zKaBoS^dt`sm`Zw`cMCKXWankoA}c|i~czJXVbrx{yFq77I-fG^W>QL)4#x3ITy-a?U{`lWU z|84rW(|?No9rPche<%HW>EGpBPmHrz-UXzvn zTh^PdDda74Y=rO7|B?Q?^uMJ49{o@0zfa$NULVl^FmtRva(sVqK2`Oh2GMOus-sXDPJMNr z4TDJ-Ou=AM%Nk53EAbacP1An{Q^`*4tc_$aErXebOvhk)1~bZ^!C6;X{!EF_V8}T_ zMlqO$!K?}Hqq8$uh`}5T=4CLaXmiQV?W~-6%t`lRJ_hr9d;tbp{HOb}FoUHTEaIb! zGFXhk5)2kk5bf`h43@HH@<_;?nS*6qlfkk+%k-bY^3ICCB7;L1ti)g|2BR4`{>ltY z=NYUbyD9@M{!{)M4Ay3_rb25u>o$KhO#d0IYoPVJK7-8}Y`|a>1{*505rd6K_{j(v zY^u;^mX)0IgDr$?$v{&;+V9p3c4y%9e+NakmEDfP_Li9TxFdsIgzv;)=LFY2n*KA` zEkQK4Js9lAU{B%h{r`RB?Cq@Imwk-!|i)F60ab z=g2ve!C6U^!P(ByUTg7heQ639*c71L3mII=;35W>FtBODZbKREQrXL7FJ~a}9~$GD zs&*BF?-*R|nj&1o;93SQ>pBMaGPs_>9Sm+@aEqch%HG5v)qmHokXvPMV{p53Y)kPw z8Qhg14DM!d&q$QPeGDFFa6f~G8JPGpc+fK+GAFI@h(eDtcr1x(%ug_QMz~D@44#sG z+6d_p))X*!j=}Q=sv3hA7&zT{k-=LGUSjYXgO`=~O2T1a>wkZqyw1R;0}WKb)R64v1>BOGjzUnq*Ww8Yk3d3zxVyU&;_e}C#GL?v1W1Srad&rjB}(!q z?gDZ5owb?Wuj`EkKqPRA)z3!M{3drp@mEt8<__H>z`H^0}O(=dtQM zsP;j1-qcSQ)74b%L3J%u`=Po5s`I0|D60KY9gOM#R6W|Gsx6?pV7CXV3uRIc;! zGF#YC#QzqM$YQ82i|XR2E`{n6k}R3pc#l<=PC2UD1zh6sUuM1qsJ$NiQC$hu)kIcC zbrmDxt2&wYBXPz5>KfwyU9ia7hFMM4MRj9T*E5Mac6}LaAk=!VZj@R|qAj4hDXP8& zpgI)QV^Q54)!k7YhUzvFZh`8SM#Q%gY74M6RoxcVoh8{0)$LK;5!D^sGTr9f;ZB~k zXN2l5PF&(!0II$tNWKTEhoZVCst2OF7pnWAx;Lu($YywVMpgH9WBa7>p}Ie+2XrOf zVf8Hl)q{oF0=&e-lpKMohk8^^as;YJ8t+ywovziRrOp2SPxUxd$Dpe1pn3wTBc(l2 zc#_cLe^!%IQ0>NlR7bnq%RLptQjY3{sCMJON}nvtOAy&qyj1)$;pM_BQ2iX$D^YzC z)vHjw6V{4P`we=Yf<$#KT*BTjD2c3zo8fR_dij+IhCM#E2<`0R3Dbs_XQ*3kM!b?q58NdA2XsWASX|u`X;JR zqdEoEXHb0}Rlf*Q-sjT1sJ?*eOOm|U%bUl4311Puit6hkz6BT&*B0Ovo{H*wsJ?~j z+gYyvLsfS^)oH!#J^m}>`>0wjzx+}1!%Y4$stWtnPkQC@EkI@#|3g$Q{{MmMSEzoE zs>T0rP@SF`%*e>MnP+VQUc(!|*P>c6OJ z3-G-EWZM5wjZm$oWo5!zMlAlfQEj3c%Xur6izmH!$0bqF%@ySk&5J0HXm&(ER7ONZ zMcEYomn$k|_7z0B|BrkNFf&=smTBieG`A#k3KjpoxOpdEfwEX{~akL_$l~TE!cjLeKYN(xqXmv!FBU%H|CWzKl_O%fC zk9iUK7Jz6S;krWK0uZe)+(4-KA8lmZqfi<=qfHTAifA)Lm&j%)qRkPFM>Gu4v52-n zv=^c+5$%L%D@5BO+FHeJ)62tlh;~46y=mFt_h#o@ZF(1*x zhTX$V^r&%F;BiC=q94?5TG^3aN+g|*8L_Z?xzW=FQ+54aF z!TF}QxT`PJW?314HYGu?a zT}j#@wOLW~;E&quGv%nwiJHfG)I9#9HaBYX%+yLUFKY9p64d%SvD()9p|&7u^UI*W z(1Sl}e)#u~oos6ZQCkePg-}}OuHAVZ{dZ>BuPqnCRh}y=+QOn}L^G!1CW~gl`?NHP<7a1np!VT=>eX^A#Tcfre zYTJl!+tqrnZl7}0c5vd7olrX-wVhErOvbyQwkv7}ptc)od!x2HYI{n)M`pj5lWyMO zsO^W^KBy`9ODN<0Q-jnJwF6N*2(?3`I(ViEwL?>m+6bwvGDo9!gt#67yzSZoR3d7} zpmwZhartphy#6PkHW9TGQ5%QaNvMrR%@6;m>7k%@3TmTL%gla^6E`?jsQB+m5B`#P z{6}p(Y7<=29mF%7&-)Q-XQDPq>pmsHC7ow)azx~3c zb}?#~p*C4XT!PxA>Em!Wkk&3o?Ml?HNXv5cnq+_fxpoa|H%f9XYS(4j>m|9tlU}`> zP`ewon^C(RwOgdRHM7(fFz-ha`uQKVJ5jqU)mk-By9c!gP`fv?zfU&zXKi^flRt#o z!>IW#;Hs`e^Y*mtA4g5`zo!5ExAs(;Oa7lh?N!vCmDzKsy^PxP;x7naOf6A+$w<~A zQ&4-QSMoK~-a_ql*}sw5zp14E1T2%hEy@3(HcjLm;k&4Pf!ceheT>@ssC|gq2dTa9 zT(xdq@lR0u47E>tX+Q79zeMdD)V@M(I%;3{lF#VHzeVkP)O7f_UUL6G^wR!>_;l2M zMy=L&3#;TWsQoJPn_#Spi8lpZc>T{t!8|rhSKD*bXK1Zr@T<_POktd@*H|h(ZJ`d_WsP~c0yu$h9p|4@j zm~$N0`xW(m!ue6}-!o=@nGJAPX*25!I`?uHl4_7}FzSn=K16(B;UdCCg^L+l7V#y7 zOA40~E{*!Ks4rvO%9Uiflw>8YfclE4uZQ|dsIMV|l~G?sWK~0v)lgsENYAvRGRc~f zuZ8;BnZ)Y6ZpzIJ_4QHTLN*(qz9H&EQQrvlO;F!Bt*Z5#w3`Yy%aWU0(kh1fu%1)j zm1;}Ww?TcYo{@K;zO|tnINw&>bp7_a}Mgeqdo%l zJ*3?e^#f4fOMGwPaN$0v?}z%n#?uN*xPMv!@dHsmNaSGQA;Lq2hZ)+Z=kytZ2t; zMK~(!w=qhdDjaLrtInsPemd%BqCURow1rWh&@=Wv)F%qhaLaLzD%Yf*QRe(C<1QRH z9QAV~KNt0jP(Kg#^Lw@Z0;w)^%Wn3IQJ*Z~B`zHMomCt4%Ots6c!lsv)UOe_3iYdv zSe2B#R(PFDPTxagcLVAVqkbdm_oIFj>UYWDX4G#HxfOMv38>#DygltD@jDGw`rSq( zxkq@fg!iSEs6Qa#gPF}kSx-KK`eUejpV-H`5%tGWw+Z=#?4J}qm1ooPzofsK0{xC#b)Q`Uj}LhWeYRziwI?yy5oaXF+`` z>hGZbmUSlTZwsxUX&#+cf2L>B$yz4q8hH=(_dVBykJI6hpAS+0$cXsIt{r1B=Tp?D zqyAYY{~Yx%Q2!eBFJ<;sn%yw#jTxEcH>iKBI((N&zE9@^>OZ3XH|js3{yXYF%g-;F z&96%SmUeJpN~6@ek^eg#QXHdRJY$dLJcyLlLQ?-blHIvW14#JVs-4)DtvT zK)sDdU(`E3YK;Jm+0h8msGyNUqku-<PSNVB6cFB%^IdrqGu`yMm~qtOqI1(cj$*dGlAe?@O^V`D)y z28t|%#-Q%5k;eAM5HyxS!`F)p7C~cCG`hhbjm0HgLa6xPSW0|pLn}*s*^DfQ#_}mw zHC9ApO*B@L&C0@6(p+e)ipFY^te#2M$egT&#)eX@jYitB>&n}DnZf$fZjfc%NXd=S z*i2@d2scfQeR4L2b~nVvFf^`2V+%BnMq^7f4n<=tH1wFx zaUmMVp`jq(I3A4?&^QT=6Rpl>Y?u->Mxt>R8mFK!N^@Dy=2~f8%mAuIQ>P40?%Iq45YBSEF$+8rlvT*P?MJ8rPw58yeSJxIp8EZU}1J zh{jE5+=9leLqw!(4&wbQBRzG}_^@GQMG(J!5(f9%l#s9`v%HEN7IvRhW;qf1hZ-n0p zzeD2}k?(~+pz)&-@t^#_)9@`oBwYx_oH19)mQ#5Zyb2BuLMRO>cBhcI&%{|c^hUPA4Zh_`D zXl~hE)XlBrb8Gi$@u9gbnmdYYC){4RL#EvcO^^I0^v|h>p}DIJb`$O{v~#A%Tk*Zn zJP^&j(L4an;b`uUrpJG&_C<5QlpBgn??#E{L1rMEgN26(4@L8^RPNh>SM_k=5yB&d zsr+a(kD2Ki&EwF#7R}?)JRQvw&>W5Ci84M(Xnsbz@u-nXo`R+y{(HVy-_c7MgXXCj zAB(2Pe>6|)<$pYy=b1m!_V@FBe`R<0~_RtChSaH52#qKbqGYD(PDQnl}n> zLR0bICAXx}RmM91H*ZJt4khn26uC=yH=6f|+?&e%xu$tPn$My60Gf|V^`P(};lpTp zxahmDM?y&+L-X;BKOxDJ!l%&mjX?YvG@nhmEhsdfNAnFdUy$lWG+#sWB{V(wTSFwA zB7DWp@Myl8wgt`CP3SguMbLax22;@r(R>Td-_d*<&Ck%Z&iNS4X=uLVmKOh;@1pr3 zn(v|cfwH{cJx$ppu`_VcCt3vf8qMix zF16?zG=E0(TQq+_^E))Z@BjQ&ZfwsWUdoTM`6aBsE%t%j`} zTJxcmN6XLfXaOyvr2~JffL5_)aBah$kweATMe7N))mt1w01#jFSK?=YY((6{_BL++C4QxYtK~T>#ntTnh~wxuC1Z8})~RS6f!49o9*Ndb(jG0h z#~9j2lH<@CiPrIGorKm2neasS+^w@m4>>snt>jJbclIp_LOdc*qYjRo{ zsVw-X*)L0N(7FPxYtgzAt*g;e^ynH`{C7(^xlVPs9<3YFIxBgjYrSJ{M(bX*Jouw^ ztME2MscuKhgm+p}rQd~?!hY-SRFe8vmivVFqxAq@8tAPSH(F)k$kr%F_kQM(i##MGkF>DB8V%9=R-UT z;@J?-YL*_}-OubYo&)hbi04E+H{!WW-fyk0|L*=C_mQzb0?58E;z5Xe5D)N^OxzE# z;=eoW?>2Vb9WP)NM!cYKAYzaFUZu3(;=w(i4n{o0&<8YL*t^9giy~eV@nVQqM!Y!U zr6pWKxTJ8Yv>y;JgLoyx%gX<9h*wk%mq)yU716sa_uTE1RS>T#`_&Myo|++EBXubG zT8KA7yf)(XZ>#D}FE@s@~B zK)e;=J(YcH#M>a=MSNSt+acZ=@%EOKL|Z_-BjTM>iS1nWFerz+%3wEP7X0Hq>bNSJUZiJ z5T9y%z{|dQNjna)#s6&Ah{qG=VFL3jAfAZML5R;lVxw~=l35W?LVP>ovk>2k_-w>i z$iq2^&qaKZKS0Ij3C|Z^AXNN!)x}CqMtrHrC5Dz={4(L?7F9&9l-sKi-+=gPKLE$q z2(J}hhxmHmYwTdDyII6HBEDIwn_Orr@4#ESHqLJ|E}u4k-a&jP;^&pT3-R5EpF(^O z;s+%0M*zh4`ME2;-yQ^%RQ!)U{ww*gfB7Cif>@6Rv4Vg6IN~RzeZtM`!au$9kNx~F zdt1Ny`#-V&{U86v)}6e7_)WwwB7RkQUqbAU0El(?j}`yrN5a<-zdqn)-*yrE7Lak@ z0uU?y$8RG}D>==CsSV09+eiFeFK<5}wt4;|;-3-!Wb$6BUs8_vH^hG+ z{=JvVh&xDv z?wU$`n{cxv=Xq^aCQwrGKk;L&eN2U`9A(1Fz|K@E|0!__r-ztdC?iB+DR~ z9f=2gBy%8{(=9dkB$*q@JV+Kn(g(?W_RR&7dF|>$q%V>Mk@O(xuVgw7Nmy$#X67|BLR4o9*vl3kE&f@B*cn+xGkYY*Bk3OgqYTrju4^=zCu_w71$=yhF z{!euNPj0h<Xrr2VGmtL?rAiM)tp3X+$Q= zd*pycbGcWjbO7tS!0>RTk=d0$z% z78Sjhen8uT&yPrABtIdkBKaA~Z%BSY@~ioj=hs!}?@0bc@`p=Qq8#c9DES-7KS*r! z|Lsn&nLBI}A*mzr!++M6hN{|3{UB+jDkKTo^CD@ZZ85xqHqj2y&iPYDJ2dp#w)1Fv z@b?3uzE(rq<3HL(VX5o0T~2F=_AF@6>i%u2v}a43gZAtuL3<9g75}aC+jIF4wC5Ji zBeZz#vjXk;&>oC-U$hrMyGI%Op*=tiw++zV1Z|7|8>{q< z(v})`;)Akj*7l)j?~L~5Xm5%3Fl)6h*!C878<|cyw6{WgYjdkYw?TVbk?n+j{zqGf z|MrgJe*RBuh4wCJ+l9Z4+OBBtmJYo~%l00!+*7z0+WV_Md!s#E#`_3$`P1I7Tj4Yw zwhxf%K;c0`yZ<-;hZxG+VQ8O*_6W33M*DEIkMN#xgCo&C8g0LmOotWiVoaf++J62=d#rF=Di=Q;?eowckM_A}Pe9u` zb|TtmqkV?TJySSIc$Q&0B3+UOz%*jF&qwfqe*W*CklT--{Uq9tO66Ms+K;=|gTNEsUY9(D_7t?AM*BG>pHbKO6_Aq8 zqx}-vFQENm8adNiN%e9s{)!~8D(`D(zi!E_EpMW2{WcZtkI;S#?P+Mg?T5OyO#^#2 z)G?s_jFVjZol+mtbx$0;)(6%7o)cS3q9s4@!Gu1w5C;eAjtLL=g zXm`-bp%Zuj>4a{O29r)6odP;Qhb~|T>$>fz3u{x)4_E_4<^XKr+Q(3uCF`BbQ7oYyS7PlBDko=eYq==4L! zC#MyJPJeXlM(B>7%|?5)99l)1Q3z{R*AseR}Nq zx`z?_vbeLNsU%++owd+e1)bHcp6IM9T+QqJ{XgPspyTmBORkO1y6CKvCJoWi143tg zbT;S;)A`fc2%XL3VPkYQ5!uwxUD=anXQ(8b3x}oCM)EDu*#@1h(AnCJZAPs#EhXq| ztKQfSo$WPPJD{_p2M{0e`85zb_e?njon6owj?S*=?2gWEmN$LKQkFf0dkXhLXKy3+ zQQm#}9r*(~`x=VuC$xGVfX;#aPF^vU_ZwlIe=u_=p>qgx^PzJn1MB(2(D@jh5$Ifx z&f(~sjLs1~qiqz9G*p$25*{r)MtCeb$7$5;{>MD{BLF(~{jZ^Y|9gkoh>t|)GIXp_ zXQJbWe{>Z8-T4@F6#qM8(HXDe#tBaop6*4b`&VZIIupGg+_`T7@-qpYb41P(o^4Ca zzpw9{i_ZDzoagrTJy&{g>gYh&xe%R;WPGu3vhWh&rH0+_WjdEjas@iJ)^z{VxeA@D z-M_uWqNgVrT#L?iMm(eK&Yc_3c|`IX(YXnoyU@8A9jovy=-ejxt)9KxqTAC}NS-|c zbnZsyL3Hjx=YDkVMd!ZMre{>BmHU7t2TbvsTbmE)JS2SBYxR;XOl$Z{=sbqbQ#O~- zd0hAeI!_w$+2uX|G&;|r^NjejhVIIZpBKJ>&WmoGK8CyeWprLcXNt71xXOk82%!09 zP4*a$&Ku~wiH;rzI#VTiOZc`hEn=GZJBGc=eIK0<(fPoHHgJya?W3$#pD@@AolhB9 zu=)(0-_ZFSof+tSfsSAMpyTI%Bg*x)aJr!h(fJ0QpV9di9a|CKq2rhTa{Ib6{)o;` zsl-(^tAYGbl2cL&6e+vW-52 zijsa6!@%$V8O$b}-7xb!Cxf}nLz=XJ%wQe{Lm2d7FdqZOf4v&bpf7_381yjk&ScQf zos6G_ffY2sd3sqo_B#d(G8n{QAcKXxdbZr;!H~gVv$W{PU||MpGFXJcstguourhTJwsJo7h{&iLs1OEzu!40m}d7Hsa zlHBY`8$msHFu0Y0Zv+hdLBK!W!*?>YO5VlLmeSn}erIqGgI5^b%iu`{_c3@-{d_-z z2kZppt@HWd=YPo`w$l-VM;JUR&wl=A@VM{^L(k%prx?7z;AsXP_8GX)R1BVD@O;`= zF7Z~p$lzrLFIh5`+n&W>O3E3$%3vCU*BDG?@H&GxJeMzazyGmWZOFhc{~5f^z}x5@ zr4u!ScNolI@GgT-8N4SOe-L2sf$&422Y&`13qLVT=dhBWG5B0l=nI#hW>Mu!;a3d4 zmVqx%lZb!A;8zCUGWdnTcP=0GH-qmP{E+bqW;_uZks4-|Us4K=cE#SrV{kivqY%t{P<7?v5%$*{t3c80SsoXwtv8P1yaTk1r{bNE2G zYA%NJGMt-XABOYzgLC@4Y;nm3I-HMTU!SoGsvfDVIblEH{0#du9AI1yeI;gmAj3l# zF2ryPhJzTc#&9sh#TgD^xQNHPaN+LU2p46zm}j&{gV*zF&JqllX1FB7rPBCjGtEai zT!!JYSxc8wi5~y$^u=%mhKm28$A5+^3s=dstEMM3hO0ANkKq~&*JijT!?oN%J*g^L zyVmu<{{25*u7$F2eTEw`+<@VRZl=j>Dux>~9Ht^R@g);(D%_0WPzn9~pRO_^zM{h| z86LoJD~3BW+?t_Bc!t~9rpa(yhJOFUaC_knhSm>;O73LcBC-p^-5Ku6a5p#bP_vHj z^5Gr~_hGmv!{H3~lBLK0wBJ%c3>E)FkN*t!cdh(0JWy>pNR>aho5fpqD8sP~t!~FN z9HD+VTFcor(;e8D6w^VB{a6oy|iY%~0dp~q^5Uo)J}@Mp7OID_FgCY1bJNxoy~_dg6h z{xkeh_>-Y&4VCtSw1=K9HUe&!Zq&M*I&^D7|c77#8d^vi$d{EGnQ1_|veVBHWhBh+|V>vmUkF) zhcdSXbK5hwrEInmZY|u#P-I)?JpTK~c8c5%%ICBRJ4{>?7XAWad z;XOBkxx=OP7%Orla~}V@?e*-(FsJyR^Y|~DDnfyK_?-xEGe9%y3 zJiR{GZcC zkn{NOQ~LAYnEQ;mI&+^hH=VgJJU&>Q&wbf55zKw1j9nG-Zw!A**Ue#Zi`&9wwH=(~2%G@79zx<#1hAne{tHghN z)6D%V{7+bAE|SXQzi*&Ew;Rm&W3I{ktjx8T515OYORPQ$;BDqQ))v3h)};#bA@g89 z$GlGdb}Y9yd+ofDr+dr$Nr3sHcu80m>i#G1&tKggAwL`QeVCt}`MH>%gZVi<^4nID z-X`VeW_}(o(IoZ=U=7L7%e2WXfd9_-wb_xLaER?KfL;_;vPZG|5H`(Ld|o8N)?1DM~Dc^&5SJ2AgA^LsGw z!JqkE4Mlbn?rxYKKbYUsxn3b-es5{5+CjUF@|9_cpGjG+<@jhQ=UhzNg@t^s6X5X-H-!k5kDrP?M7NwUw z`3^BR5x5E=F$a+&Kxhj{M+zj0M8RylrMK-slw@BPD*nU6BQdLRHsS1s-A=%qlFVi3 zLc6@eJVcL#eTaEQ=5v(=M)XbVCSgBfek0=ji2))D5DWJF=tsLWbPx*>+Yp0@HHpE* z(!>y1E=(*XvIwy#v4r?y#Nz4lF(j6p8GkZw%MdHea#>-chQwyXM$&3ifCqmi z{Sm+(u8E$rizg;|VM41gTCGcH_Sv ztKc()I7K*$7$Y*ey944>Km6DQmmTLYj-~&I)5!CP)5+Bdd;k9(Vgg~>iNvkM8N^k@ znS}MfzgnRBpGBOVat|XoS3+}gF>yX|5ph9g>8}8pjh`jF;gg9gh)amel)N;PU!Iy7 zN`B?cB;mI}#5KgV#LdKY#0`@8R{$#gM&hQ_Pv%W8|97>w5f97$cH$0^JBj;>nib9*7)D@<^IR{4v7T_2a~ggkJvf4tbJzO4_FhKkXA9|A}YQ zjKuR9c_FLwOT=rkd099`_=?c~{(}+wWX5k0ZxZ$clS#x>;w{&DDfazu+RxMM+m_Ux zc-I0M@gDIf@jmfA@d5D#@geak;lKYSJ{EfXw(! zF~iQHp6m2Unr0sVrTxy&rSAC$;y2<)@t@@I=gj68;#cRYstkS?{$VJ~zlbXFx7)a@ zeS)h_9N#f7m$5_;ea%k_<|`X7a~_62a!vXgULmecZkpjgK>FYRA=gt`>(6vTZYcRiKBc*_ce46f!cED|$j!;2-KpZG3?sLc zb_+xEBZph14#{oE9i{d6f5`1ffB9cFJ7i=hau;d+{eP);RdTn~i6ncFhm(7f3g+BP z278mk$^FTFvW)%{0CK-HBYD6~50W1wJXm;$y68~hVZsq@ zm*hC%@#G2QDdH!RCz)3MPbNopyUu%Z6lpk`98ZoR$C0O!W8K&moz@+BnmJM4(+!Qw z=LB-1$QhaBOd~Qo%d>lXeG4E{p>F}QKc5Ag{1=cPkr$E=kQb4DRwpkeCyQJ{UP)%> ze_kfx<-#kv&%eA%$*ak0dd_%B{95w5o{2D?KDLlIkhhaJN_&&=W4$$edsz=GypQaj|J9-g$tO)qK1Ax6!ACL=kCKmN{BiOL=hlI4 z{1iD=TL#C7&Z-A)ohWYQ8{Dk?=)f+RT@|C#^E09`j>o`Yg%p{Lj~A`G)XK zL)Gvt@*PRuCe!Bo`QJQ9=;wct_k`~YKOp_^Zy)P$@?-KF@{?3TeoB5OGadfpqWo6laBmb4~ck&MrKmUvTMgE;} zkN*<>M|MQ2WJK0&hLE*%ZjcQ!CYv(wBa?qTL?W6Y6H8&|iFjZ~@_h!htLd7FkF*$nWCZ&k&)#{5jk{ znQ+mRv#_`-yaWr&^h|n~g(Zbcv9Poe>mntWmCbT2tfb`fEcoR=3oG^=vx7f{joE<( ze@t-h-LfhRJF>7E3tOkBtvVZ%(a zkt7@U{CtAQrYsD#m%v%r%nj7fENsrgFz4MmY{|m5ENmtFt+V2`$+X+4GTRGx=#^_H z7ItA_XP1ngIo7+f;PIb@-C5XES@uZZHW%NkI|B>W)stD+hlMdL?90N5EbPa^L9*GO zg#$zmG|cP|X5ly%4q@TYtX7A)*86G%3x{W1=l{ZylIw|}aI_kAj3mcqd5?E{?-PFn zP;F0Q!Jk27Jd%Y|(mJy+s_$0mSd7lHoXWzPER1F03>L<*Fp-7RSQu|w7EVw7i%*dK z)UL$(dGu5kCb4i43um!#u58X0`jWKbSU8V`3s^Yco1CuGf``+T+jT1o7x$g!_arP> z&2M1gQWg~C3yS{*#s9(;GPu%EGx=)w;90I=;aZXFSh&7t!1Ml=vb~Mm)!xX$O)T8a z!p$t)#)5)>;a0!*)?cG#;dU18WZ@38@9qVKyVAT_m--gK!o9-#GT{R(wpe(OMO&>8 zvG5KHwrZxZ@CXY}vG6DhkDDb6kNNYI-O&}EV8M3^6Z#dm-Io@gX5mE^o?+oR7M^vt zHXPoQ&$Hks|FjPN)K++jg_kYquP%3I^(!ox<*O`M5wG?1g=^>ih=n(_x~;f3-Gf)) zEf(H3F0B@Y3#av5^owO-!Fv947T#mwV;0_L;RAme-o9lne8|E_-8yu~_Y)RAW8qUT zX#QQ&yETo%7c6|k!j~*~%xB@No&|LqP?+A`hzm1%2A!)3`7Mh*EPTgeUybVbi!cX2buiMWo{KCTDEd0vCACmkg{M|>#4gNF~`OD7(F8POr8VmojP*w83)KWak za@ARAO48`1ZDrbo#aUQrvsjQ#hsA)!yz^ox%>6G{5t8_?06gLqi%OOZl`IP@-Tp7m z%Hmuym`ymla1P;|na$jmlw=+j`-sfT;(RW&AG2s2yQ`zvkHtj?ywaD&`C07Wcfxio z4iGM2D6*h%Ad3sJI7EDqaIg)5$ijvqi=+~}9`u1LF3#de7MEagWfqrYaTy7h5-x41 zS}n`siYzY2qQZW0dHX`sYq;-M{ZF3ZlcBhh*V8*~6&8Vkqw+!3@L8J;#MqfY|DhjO?(9vHx+JXC^D4A z%|(V~k}X*D{|LUj;)+|VZrk+CH-*J*Sv-VA>%je#+@3`n$~{@!k;PqD+)2WnyLsJy zR~C0?aktd7w0rd8dr7i4i~F!RyqA36nOp|@3l9(;C_IS8gJ+f^$)Uo-SR5g8IE%-y zcm#__v3TT6Z!8`?lRNPm9>?N|EFRC|2|a_S+jQ+Yd0X|{Ni3e+_u{MjP7ZrcPN&%^ zEWW_vC>F0_aWso#HOgaHJkviJy#w@H4xp)BuU z@lF=+6~9Y(xA2~`E`~KI2PqFxP zCV9q>C0^C%SbSb%H{2@k_vghIS$vJfmsp&l5qa4vZ^O;vD=fb1<8EK{?E96?9~Le9 z_bk4_;+y75lBq1dm7ZQ$eA{=Hq90~hoF;sS#jjX=m&Fg2d{6kk@B?4eZfR~mXYpee zKUMORR4)EmX7hz5CHXRwe68ek;S3hP75T;++qL{I9>N68@fP|4{Nzm1V|%EBTM`-^|8eXObk!BsCW68E;6{%sj*_Ey7~LQjx_r zOKG(4u;lmuUQb&HrJOL&Qh_DkYFVOd?PD8j_BZWWDzQ{%X^;#mEX~4FU-4OmdO9f0 zE`~?+t`FD^%o8hE+AY`I8eBdVd{;g z!OA;?C6D%Lc1ac$E+$-DxP)*?;Zi=Iy}iq@G@PYnSz3*y#nKj8mA0HYMzY){^RTVlZpV_J|Ff3v z$kNU%?UdTcei!2`?aGqQ{H5Jk+MOkT^T%zvGh{E>?Cp|nkL|dZetJERl#XC&BuhuKbUaH(v7|G9 z>FAz?{E*Mmu`C^Dr2jGJ_B;LsNlswtB$iI>*Jm}0HvNzOlO=xyNK0Yq6qd%aG>WA$ zG9K**$Wvz#Kh^9-?Ec?I;WXn)p3c&EU#F!B!imB&d=a_uOrbUBESAo8p>>OWjCl3V zwTg+HFT8-I%UQaRrAt`4h^33uhD$ZM-#$J%EM00Sa#>IR%|vwgFZtnLNss>`*JKhs z0+gh#=~k9rVCgoN9%1QrmhNTg4wmj>>CXN~PaW{e z2t$_cX6YX5VXM6LThBWKSh|m;2U)tGr3c*Y=&9DR*2644#L~k(L;muBY>~&ydX%MS zSbB`5Cs}%&r6g|D&Hdp5qw(lnN=ptq%c%beT6o+UT7j~xtHdWWU=q(bdiV>E6Wa%gOU_$Q)J)4w%mFhR)?}nKNJ=wY0 zUo8DC?LWeQ4K2HPm2>RPQpAeQ^BT)GvUQei88%p6o~0(sv#``+DfTQLWD=IsAm3)G z<3g)d*|z|e{W#=z{^dN&C6*Qc?Y_TUWVz6NU)uh=3jG~p+3WtNT=87?o2>GzEHA?H zY%I^m^6V_n?XL`#=Mc^*oGX2iMtSF9+5d(Q%l^lI+=KJJEDvH?!N1&3X8wymmivnj z5H28GP&iPykYP8pmIvE=^(+q&F6{5v>!-LZFUqnN`97R z*`Edc_ci5ZC0Q;_im$-(HY~5m@&+ug#PZrKugvo5Qmw+W;=lLdYH4#=UcBN4VJs{Dmp%Toya~&jv8>>49xQ~GhqAnR`Yw9< zCJD=1$Z|{JR)!*58>;keS>Dct_Rf`e^7bq%?v;0Bc{s~EvAhe*JEs*;Wp-tGHzR)O z>fN=wgnO{Or`+x(^!T4QWxPMjqgXzGwp&2O4$t;gdlZO8B zhKy$Ue3r+sJl-TMpDG;7^0?H7<@2%aeM|tFu`? zC!J%KOJ?V}xAcXa@&znk%kqURU&``DCTIC#_vRC9vhWf^ed6?B_tRMF;; zUK9PE3$HWZ3N}_#kB<}Z}Aj@ zTK6+6i?jR-%l@BmvHUB`zp?xm%fHL=5250}+558no8_2gn}0V}hx5?U!)>khQt8CF)BnTwTGSlNe_Rax1VmDN~T!)IM(bu&oA z0xN5>vKA|wu(EcVi*9G|qYyE2ndob6FYBS!|Y0V8wcJA}f!uat13muyUq6SeCO{ zIm>3Od=(*502k)Q23GXW8o);|BtG(0E3%o{y+9X8oAs>F3IJR;_gn7qAl(& z#ogVC7b))U?)p;P3dR4$p-6!imr~%x?LRZQ>&x$X?)g5G+1Xj2o!xBqw1ASFcobR{ z?=j$gE7}LjRL*$A4+V|?F#}}aPhh}AttiQo-J8mQPiq0o&*u#ImH}TdK(a0N|EpS+ zzBf7MYtv>Q>1)O&F<`Q({jToM4EUY_GN#o3x19f2e{HId;+zcl8D{_ke!-cR0l(r* z&4Ax1F>1EhDcK@e*+~-V(GXoAd>ip}0sEM2D zI~r$3oSF1gq;!(HavTSziR0qJ^Y;(j%Wx~#_CP8;ph<{ zJvZpIan{61aTdVIa4MV}r=z#hNyn3xatfR-PN|PJDO)N7apuIC8D}<}S#W0URe0Sl z&g?ky<(Iyr>3wD9%!M;A&fGZj)J4`a|5~p3aP+_DB)YbUSXXC3oaJy9!dVh$VVuQq z7QtCGJ$9J(WGTMvTRMy5EFtC95=koJ^E*r7EMw(bTJ?xgbq3)qt7;_=y<9DivntLC zI4j`{##vFnwUo=}C3X28!C4t+6)mGwuMRq?$<=Vo>^nn5v#yDp)p6F)0U|neG>Obw zIQ!$Qjk7h*Iyjr)tc$ZD&U!fOt2WiF_y6>Il(P}e#+vRsfA({=Db5x+o8fG(9au6i zn#I03TjFe`99d7LPFr4jiw#C^_i*J=Cs&IC|*-<&>QCL;%jI$5UE;zgP zdx9+r9JT5=dl>F%xR>GHf}+sqeR1}a{MSawI0xVyi*q2(5jY3o9IEcFb1;s50&M3- za1O&cyiQB3gw@ZHI7i_O*L>DX8|P@8V`^HfQH+Tr_Wu+dvHvIHoPaYzTSO|stQpQp zI44_wwy$!uq$4dqr)qAcm&(-doQ^Af=?t95aL&ZJ4(BW!HS9QN`|Wil&NVn!;ashmlo268&rOS? zivrE3ywPy3$GHdR2Atb)Zp66-M>_4znw&If{q?SMYdT!d0a_2a9p_GK#5?*qybH(d zzeJH*I{FTrdvPAbxerI(ew_bka`IiOGYUse{`aa^^y55)^9atvDqt&dd5?hes2txF zF`UP7zQB0`=N+6UabCuG3g-o!(Kt_QiN@KA!+8d0j3uP@AE)jW>i_q?b#h+Bd8yad zHpSqG*jN=1Tk5=m^ES?_Rw(oTo!3pz8-{NhzNNzTD6Z!qXB^H%3wjslJ)8+RX8#?v z|2X3fKQy!n(NX7L3Z*EcvNz17KYWVwInHOokzORBDxj_WB`!E$;rxUn9q$L6Z*acV zxH2BKawg$egDO24=ld!8ael;+%L+cJwMoHo#Qy(=^9znT|I$o~Rvet)asI%uRfhAY z3IApIcU@z+Q;9I{)P~a-PHQ-wptOR~)8o#BJA=_PDqlu~%(?CWT-TUl+-gY=SIs-F zuO{7X7zVgajisEHU{8be5I4phgqv8DHtt-wDef$|8E%1_OL21G}hyC&|2 zxNG6Ahr2fJy146@fb~YP{LGcNng0{`4*evAEmg%DvEL*<70fCTh*&?u=^&-`&L?cU3)_ z^WAax!rcRRPc5~4b*RM@&ffazgtUd`d|y1t>VCMBarei)3-ZZrl{U6*D4No#W z+3*y>+6Tiu758l1({RtgJze-3S3GL>Ox&~7$1sk?ItTZB+;eet7N{FSXIS?F9raRn z9a9(K-hg{C?&Y|bSgcENFH-@%R>DTT40-no+$(XflIUudwc)PDy~gC#{^MR}sQ!Py zTsPw0V$7QiZLRk`>=?%lX= z)&AGkUI`sd&*Q$JE|Wa=#>-2%V{u=`RRLjYD`;QU{U0M<#Wnlyn*Ddp{<~)X-M3Vx z_BY&ja3|u9!~FpFUE$-thx@*I5z;`tu`?dGHg_LtFV)Kb7mX!hTm(dd~B2ME?H2s{_B!t?MFJReVP{xQqr>Hd$}YEN&0!&Cc@7aB%} zv7l*dhBiUBgna*B0JDyan)P#+x5+7Q8v}W;GcdcX+cKsuk+X z&t;ss4d*eO*Kj^T?KXnu>fkMiw@^KPW%+}*Fy102xu~Jpe^2dyJ+tF2iMJl!Qh00N zEseJ#-ZFUR>Ue|jmbF;R87^F>)*O)&3 zzi;r?#9Ie%ExfhGztX9>9ueNU+5}P}*(>nY&zAfaZv(>(1*P4M-WcyIyiM?K!`l>Z z7~W>a-yCl%ye*90Qc!Ye^wx&k2-ZxzZShVv=5~17TRwL%+!60^yq)m&x5u6FcEQ^d zZ&!P?>A>4vXnWk_fA9Fbz3}$N+t2v>;O$$>NQUdPj(7+3V;yKR2jLxzr^dfN4~bWI zr^9-E-a7*Cc$2g#z#EQtlsz7ecbpN&7#=(2EM-693C15`cp{$K|Mak-_IL{31$ZOz zPRBbH?=)f7&W4U$?+m>2)gF3h;+&t2Ohk5EPCDU4?hG5o-U9xV8?(lk=aC;N4)%8x1uU-pzu&ElTfJ zrR%kTcRSu#ygTq7z`N5V@3KC2x6$_)-fMWDp^g_kwf}gd1p8%q5bt@shwz@ndl>JL zdQQQ6RJyLZSl(lXkK;X|vFd$J?!5p{;RxJ>^pex z;f=%7kH2(6tmg&qeHG~Cb3C4m)(`PM7ABrGn3Im7{jx z7rejme#QF(?>D^PHMiyfn-Zytzoavmsj2PxKloE?_wc7u4Puu4X@ux$^QXgi@#Vv> znea{Cp8^jM zzYl(bzaf4be=+(?_y716{w(+dRiQMTKXae3`+t19 z|Hs$+e`>x(oKuUhder_~hUdYbS84O}g}|TRZ~?;w4Hq(8*di`sxM(epzc~In_)?H% z@t4Hczx~2rN@oz6GW=!m2kF+CeZyiLNqPwW^7yOboBda@!T2j$5-SOs*vf{h2nxr7 zbP6!$Q2e#T@?Pi9Xs~+*v{VkOcv*ybxKyYh8ovr*~_?o`@ioJmD|sX4?})#X z1?_COi{Y+@y9xG2mcK_$^dD^t$ltqPhx_8ckG~)O?fCoSkHA;^kAEQk;rIvPOTRrB z|B%`;s9G7?Vm*9aL73zb_{Za`@wdm}Rw(@h82@NPc?EE+1?hAkoWA4<;)EFgMEtAp zPr^S7|77EwqLRmLi9ZtmRO9FrfUo{P{uzd63ib;G6yT z&Hnp0oBS4nW_iNV@ z{t3700crn1{3q}qGR+SgYAF7rhL0IOE?CE^jYHo=jK+Ujg7i^4@H2h>v-mHnC+zFa zB>wY;F9@3LdI|qc{Fkjk>wY`dQhCMjRm0Z=P2hF>H)^`ic?`;_KXP^v9Ol34-=017tG(r}#1wKg0hL|8x8=)c(lY)^77w7uWwv zqt{c6{|)}P_}}4AQoCKB8mf0N{O|F9#{U6d9R_@x1w@Z(_(|t(*%S7E!T%foR})bG zAOCm5KMemAv`+Px5GHS%esvFTOid%DF%1nd%hS?m(wL5hPeZ=qb!kj5CDOQ!84UFk zU>Y;g7@(RPYUhPxv?rE94iOpMpb-e)=FpncvWQ_VteJ1bCYjV4XtaCZZZ|R-^U}y^ z%%Zj0=+G#{g)q8nSQ=J_0}W>uR6VlvZp=zUUuPISyWt!(=In>gWt_PU=Ml6#%tvD( z8uQawkj4UP;_RP>G>RC##=4WrjITuV?@ zX^X5wV`Gc5uHkxy>(kgk2%|Tou~AKHPiSmn%uNk9tNAoGr?CZ%t!QldFUhU{MGqsm zjmEY#{$mlhGu+;A2O2xlIE}_mG!CY*GmSlszl-6nhPxT=Zn%eFde~4Jd(qgB#@^OX z_Mx$FG30G2krquHmB#)w4y18_cBjTPrwErkqNt+DHA+#o&N=o@M1 zZErO64KIzG1&x0zjeBXR{ikue;T?ijw|5%eMZ^66hWh`)F`4@Wd#4Z^_Y=qf8AU@) zI*kVmA2fW(@L?Kt9jg5|j!psTIBA6^2xN3WN#h$DPth1pV>FF-XguA!tfTP^jWIOL zm^YraxX&3rPvZq4jDC^EOMUuf<0xM)ilL$Q-#9h}G+v|ex;?(p=f6p#=l|2t&L)Sb zX^f-so?ge)cvtPf`?8A9X}nM4gKSr?Ki)s2@i~o;XiTK>ar%6h#soptptP7liSj9p z&s0ADSbxKFxzdEj7ovp5mo&bj@wMugzhAf0^)$Yv@fVFrG=8M{V=Pe)7UXC;_T2p!A%IET_o%$d#}7%?}&`~>reTGKo)!F)B{a~gvMjK83I3&BF^ z;D$XeLU0Vhq69+;79&_fJ-}dbNm@OSU`c{yO>8NGrE6^j%UIB$+6}W%$>;I}s}QU} zuoA&wf)(XUL;V~^N?K2y!O9|;uJRhessyXm^P0ugDL_xe1*;S6ORxrk)WDhqn-Hu; zus*@s1nUy4qeEM~U(qHbJy=g;iAx-8K(LWS9V zWO%Wl?DOllKzbNAMtljE+$R_v@q} zT|&p;12rLNHTe+1!y4BF2p*+5fZ#EL2?UQ5$llJY1W#DfPZB&OovK(=L$5xhz8vd-^;NQ&CA@~^A}uLufL&>mki ze4XHpnrZSfpxz~r`+p?vI|Oq1TTfmHW4=f5zD4{%&^Y4>KJ3$S$&W<*SkPo968uT< z3Bi{HpAvjd@R?S1?c@hvXoYt-u_Av(@C$*oxV-rqD-~;n`j%jl4teo=1F7)I#{Ay! z2SauKjSzvK`t;AD)0n>!{H9GHTfy2KzZ3jX*S`o5{6%wW0)6>Q@DI(YgwXs*j}lIE z8k*BfK50&CI2}#up4>~NIfLcOLuC9GzC-r+@KlLOyqtb&7@>oekaSn3kg4?srx_5k%BY}nq3`YV)L6N%~@zx zGzY3e`NM0~IkOP5jZAY^nsd;cjpppa?+K`eIVDs*rRH23MefgO&O>u4n)A|JR5$CI z^U<82=0Y?V&~DjWP^Uln^DoJOJ}zvyi2kpx@U@JK>Fm&4oaPcVmlV3LC1Wm4b0wO~ z&|IG8ARP!z{lJ-~4HFsH_2_8Y6+q3wG*_%SR??NNIIGZHRr4?HDBr5f!HMP&nrg{u z4mDhzruzRzuSrw=f1}qnT!-emHAgjTto3PbP;+Q*Xq=5`ZrtZs=V@+Ab2H`iqHHna zUW?J(a{BK!q`B4f%f3i+Ywo_A<~Ho~A9} z1kD?1o=8*b|0J4c(>$5xY1SO4&>U%mjkS^c8-2Q<5oZ{lN%O3lV-rf&Xc%aq2sLA9x{49PpJyf@RlMMk})cq>iy|BcoqK<_jB=AH7#lr--$zAgbQ z;=MGVr+FVu?eNxD@26>7I?Yjf0IB%^%?D}PwomgR{S1HXD4LS=qk0DKg!OfcK#p5z z%|A}_37StyyU?`zpLHOQra6Y@(=?ya{x5x?p6{B^((FyL^|+uZn?FOPP-_31FVXU8 zzD#p+@yypWr5eZ5ltKCm%`a%aO7jz%a+S1X<8{L~XuePLO)J`4hU)y&e8+H{pb_sH zz9&(P_`q z|1|u|lKb26AH|mW|1Ilv!0O(RB3`(z7krk0*eGeY-&w6R(#lg;vP_g!L7Glt_goMa|^QmYG>-%}Q%-TC>rbgO=?74>d{2+nluKQYG?%o~ja# z-2W*!ugXa4wC0l;%9JcXYeAXHjb6xbVOopQ(#?O3+pCSmwDY&r|EINt&MD$!x0a&y z2Cb!O-9c*^T6@wOL~9dT%hFn(mPihvwY>bf9<3DwP5EG2E7H;(ep)LTu1sr{er8q` zj!vDuyh(=Fp|v`#wP~sSr?sY`+W%Ui5+@bu^OI@m6hKR74NGwY^<7&V3T<_}5v`4D zT2Wq>wKk=-87(p0o738s))tZ|tu1M7Lu)HqTWf9GOC{qE(_BeQS>9-EXUy#l?Ujc% zpY8v*cBZu}Ew%re6Rob@EMrn#d(=$jtNdQHPNKCpt;1;TLu-Fp`&!QT6YQ7b0OK4; z>rh$;Y3f>;2h%!4Xq7RJX*-hsU8`6hENt@DKF zOI|?hDq0ts{)=c`PU~V?m)fJw2>N!fWz#`@yL;;jVOr>w5@f{Hv~Hwz4Xqn!T}$hF zW9k*4y^ny5NDr49lS;l>Lak7@^od*T@ixQT`z5`T)(f=mqBWY<-LxL1bq}pkwC+`R zw{@SPym%E_{C{y@Wf+dN)_;K3gT{Y|mJBQL|NEh}j7E9P1Rkd)&i_zaPu9_C$yZ}4 zOzUY{&-8Sv{1}se)=&(caGvi=zDVn3i}KRHF1 zG$m&LL$&{eNl(5WVqvO>J;We~IpMs79l}CqG)=2ZII|I@VP!Z_v!|i67|v=qo1hW1 z6V72N&S_{?J)GOIS0`MDa1Fw>3D+bv<8L!&od?OV<#}DJf%UX1w4OI0 z+)&q3z0i#aH&(hAx+&4Vgqsn~Pq;bZn}k~sK0&x8;Vy(*5pHj#+uCp&!(oQo5^g7i z{PZwDDE|M>ggX+d@mFWBo;2l9gyvya!jlMhBRrCDcf$P$_b?@U67EB2|Nj;4tr3sg zQc-qJ!hHozW`DxN2@mM=546yO2oLUa4k5Hfbf}v@{a8oTgb5EPR8voQ6rt|_7_AGs zTAuJY!s7`~=*JyFc%laB-j3!WUGGc6lL=2D6vuuT;YdPh6mk27K23KQ#cK-BAUspL zhwNP_N6b!mHsN`M=O{a65g*&jr#zhjNAuJ_#r_fbSHlA^e6m>A?} z1`C~0AEoO?1Bg5=Y$QYbBO+M>=z&!tpC};GzlN7bjjo|$|MgQEtpu^Lk)$#+QA{+D zC=n)6nw!;{sibVMM;)RrQBgk%ns7-}^{Q0%0BajAQ$REe(X2$X6V0ZU#|~K$ z&0!gslV~2Ixh(a$dq%1D<7zBGM^9XrjGzi6J&W+MCEW|K-*% zecX>&`se;cqlgY5I*aH)A}QTLM8^@S{UeOG0}NM7wCd9I$w>IEVs3LTxfWa>Xes3!mke}M3)k&{Ws;8 zYpBdA(UnAZ5nV;3#$P_xCc1{`R-$W(ZYH{p=my=6j;GrB6`&DF~i3NwV29(l4v~9Q$(*2jV5}Q=;>^ktBIa593!ZiQH}_|XvFhGFZ_%D z64C!N{>wF!Xzag2UnP2%=ry7@h+h8}|4pKIh{R;QJ%!m%Xk1^=*+BK_i$XtLq=f>xhD+GFoewe%EC^mDd@RQoSPe-QmDv<=VS41X82&_50T z5;Q^@TE&!(r!t({a2msD4W}cPSHQaqtsOOk@c`mBu|wQQ4_=el zCH9Q)1@$-R3f(4(1L7ueteKBn#38ZG0^?Ro$E_wzS`;lhzAOxMnfM(csAl$MOC)#*D{t(^X%sF9!fkX@xsJ&5idwQH?cYYvauh} zt4WKIiRUApUk6~kfO5>7%GbWc3u#AvZEE60h?gc_lz1uP#q@PRtXoOMO9&dVq^^?0 zc#6ANZ`Z`j^s})n@jk@M5pP7iJn>q@D-f?nJXk%Pctzq>h;<1-yt1Y~;tZo#tqC1p zDmKJ$DDmpq9(x(RhT)po!xKf`qO7e(EMCWOUE=kKH!ymA!TLW1h&QZh;*E)SCfEu(+p2H)V8bF6vSr{tA!^%oA?|f&NVzwW$cw-jl>s{ z%22w9-1Ze$DU|#8(pEKztSPHI}QZ>r4_~OME@? zb(&jqp*11R_Kn2i@oGW(oLh(=H*L2P-$wiZ@$JNS+F-v!t4KzWxFWIM|4Dqe;XTCa z{}bDCAXfjM*rtYfl&Y;)DDi{DdIpL3A=CeGtxZjR{HW=9OwhW-6T~kNKS?Zq@AwGu zQ^cc*W#>oYKCR>gCXK^#P3Wfn> z^h09x|K(@eCXjf<6N&#M{)G5D;!lacCjN~03!Sp#&vk;(%zSB&U)2qi-qI!hhSf04Z`G>ZJ>QPcX3_;=zzG;d}h zts(v*nVR@-$*sx!Bf=z8^`a!xkjy|bEeTqjMCPpin~Ow`m})jeb21}|T>gA2$pCwF zNL&rFbTuc5PqG3@gJfp?OK> zBU#Ak`AHTaSx|FQ8*|OG$Sh2<2+5+tv`5<%N){(kC!a+9e-c{@B)SA3Sz1qgDIte{ zY7NVh^!@)t{r~i^p(KMzRwh}IL~jADb7-+v(Y~GN5`aW!Ns=Lky2cj)qt_tWmt;*6 z+viVo7l_2(|0e6Gn3&vTJ(5jH*6*#Vk_|{?3n=d_IX%fnBpZ`#B5$_oCP$#x{$=$Mj`m<%J?wytV>+}==*WYnR$P1>jBFTg}z z{sLZbSHs;%Yzd&8JxKPn%ion(@O4G+<- zuBDa?57*78i`!UUZSyNwUt~(a)og&7c|b5{h+HzbO|8*8cD7-$?Hh2S7DQvAd(wN zZnFBhndDBATS#svxs~KL+tC%9IZS$8F-Gd=4nZ~4$z3G(lH5&lkJas1MJc%aWM?w@ z4~cH+klar)N>>euxR-(tk~~cEkkDcojPuA8+T5V=T(Ef<{P(d0vRRyd<)_A|2o*&6ph#*C4e*V@bx7yh8GUx@w91 zUL$#%bE>=;0rd2_(APNitEpy=wlH)SC{P>B$gVbw*LP+?U@AA z3D?thXlJxt+NmmU%QwTmscl&Fz|ib}yJd7}7#Z54psmh7Z9V)cIu)&wX`2(MIqcMT zkhLng0C`?Kx=AleQ&qbJCtm1k#m-m|IY% zW984Q8$|8-XwOeuEWgkTXbUQ_5bZ^2FKqN8f;#GzFZSQw_q7+dAtsz9Y451b)Lx4A z(zKV6k#5--M0*?B%St`cma?o&dwJupKwGZcJ(Koe!D7sHwAKIDSN~cYtI%Fm2(1vU z?bT9Q61IoX9;&^ly}HS)@h|zcXm3h;ZQAS6UPn8&bj!AV0xZ^}y*}*?wa>RVko;#m zA4q#66WCZaD1Q?{)2ZiwXm3t?E81Jomdzh?eI-;(Uwi9Xv!Zmuw(Zrlx3wtZ|4Suo zZ}bj=YN*>g(cXvl&X(t0Xz!^5v%M?r-LmZupuM}{9)e;V3_qd07wx^RN^g?Zp}jBd z=V|Xp`zqS|(>{&1L_C7F6hR7j5bc9S*b+TNP=EEzuFZOQ&9UBiB<Ok7}WWhR=_DI@l|La$(&F7QrZ^?leWG8ZR_R_ZQK8EoAI|RL}*_|`w9!P z{hyb9wko|+D@q&UYT9?uzJ~UVw6CRoo!T7BKke%+)(wIdMLN$d#<^KJ8uwOvyp8tl z%Bf%0=%Blk_CvJqqJ1xIJ^W*0`jX3f&V95;(Uv}Wzvkh6-NrP52WZR7UpuyU;+9tP z57Qn^`w`mq&bR%jm`am;oc0sxh{aS`lY5f39t0BFdfL;p&Fr_I(G<0ds{N-eqf)0o zqhFvsp7x6t{UzET(0-ZrJG9O7Y1gT|B9bPouXt#`M*Hp4kN&8dUpPBy84Zkq_((o(8 zx-8!q{jH#Em>c~a?O$n6ru`G`@2$>%Ftq8Tt#-0*W!gW}{za?he~JD^+m8LTf0q$w z`TvvlUsAgC)myatX?^}fIs@rcq|=j5O*$RvG^BF)N9#?M%Z!#H+xw}kItA-?NoUl& zr85~0P)TXBW43zvX zIw$Eoq;p9W(z!LFy^J88mvlkW`RZ09onHvj1?tKq)&GAa)sKIbBQ`KylynJGVoQKj z?Y})PDTk$G;iL_r*<0Fh8PY+d%L-kLc};#Ass&u0bOj-d9!$ET(lX;{(8{Dz^Q(|v zPP!`T0i>&uZbdqTbVJgir0bBbPP!K98l-D#M9G`|kD+B_Z7rHih3UGa>l??;|7a;T zNbkL0(%nhT?N9g6X-?Zk z??VT8AS^FCdjJ zaR%vWq^HXqt-|$AczUL&RnId$%evy(hUXaS{coh_8J;g_IlPec64HxEFIGMB_Dbqj z#V#eitjEzU9jU!5NTq{aNqPh6RixJ%^J>y-Gg^UyYju-A4K-sZIf;W`EK#DL#Ec zgEYf08p`<}9f|srLfM-aE7~iDuNuB)_`2a6hHo0aC1?iZ9kQiK$B~J>dY5b(()UO| zC)FhY=?A1A>z;Nxp49&QUFnashimo96+kj_Cz5_@`T0c9_@8Mhq|fLG`hxT?Q}re3 zSEN6XnrD@MLn`&G_Mdc;;dh3URf&~LQSFb}|J3Zi4uqdbeA=~hYWK3u z3^y0l5s__a(YGSooos8eoyoQ#lja^qww(p_{{kRW=U;wmDzhCeXeWKF*Fo7XYB^<% zknO6*Guur+^iX0CliZVRKeD~Z_L1z7?L8%*WM==hT>Fz9M0Nn#ffD6kr8rnq(X*+K zkR3`c?Ryy63uK3r-9mN**=b})k{xFW4JSKFqidfznoR9Knfd><9nOx|uB&x>0@(<% zlgM=cU%vZR)FB}Lzu+lkBQ36uDd8A>I@#4^XUH*cvNOppB0G!he6q92&Lul18?u-F zb{mszeWLH@flNSM#Art#Q zR<~ucYss!7yOB(tf6Len+O-C&0Vgy6B)eI6;C1}oN+xxC8`-^Nx0BstrMrVn?Z3(2 zMP~lLSr1v(2~)SQviqdZbPl|pOd9b?vQcDDkUc>5IN5{N77vj_xKA$zCE8 zqx~}3>(;UVpKdy3V=e!$7@Gf|y{2ht^Swd#4%wTM6U)+DWN&M(Bvd$3SLRlcy-W5U znObtP_sKpWn?N?6OuF%h>ZsH{Pxi4!G}$mHbbO;(Vv$fqWg zCis=?n`}G5Z^?cnn?&|K*>_}<>$CLfVMDD0{GbiJy)62~GRPSDne3wkU%u|xE$Jq>LC!gd0#+{3Ne)74==QV+O zri7B~5*JKa$+`|NqAvUWc0S(d72=SAHz{aZ~t~ z$_eC`l8+!ioBTv_N%ti3Q^YO0Yvj~sLLrqtNxHyhq!c&nkE|IhXOKeiCV!6n8J%eI zF^1~@%g<GlY@5m<$Vf6QgKN$XKsEcAN+Ryg*i=Yv@$|nEK@OSb* zjQCT~h`;(N{-ckbspze17i@C~m(KGk}g$YwPLk zcqZo4N$E6<4(P;m)c(_H8HR>>T0;Z`?J=R#t{(+0Vn!$L(;Yg6F}pR#qEvK-(iuo+ zehZq}a2CT^>C9%t?9w#yqcaDcIqA$p#})z|^Z(7Cu~1zC(3wx>K_eERvnrhh=`2ZS zAvz1!Sv7@=7%pnKnBn4dmJmXIwHTeH=qznP%NP!#vm%{kjkcWxt@h>ZaRo!01$w1k z$v7+1Sw)C{rMnuPAyYVXR;RNOoi*reKxa+Kv&CA=aBVv4SSi-+^Vc)}`Wn4g8yoh6 zHm0L)Ih{@DY-)ra{-m=xo$cvtL1!DaCmq|!?QCWAzovlBFgn|+DwUD_|GM^c^Pi6T z|3>UYXJ@6Qxy9M&sPk_L?M7#JBla-dlg_?$_A+{J!+iw%Rl1+h#@yfVfIj~qI)@nN z;P!FTw2!`x&Y`+Z)REtrbPlI;LOT38I!DksQu1K*a5_iPkvD6?KU%P#&tvJ>0-1!Klk2O<}&>HIhnvxrWXybgrdy1D)&Cx9?mpsAZ9C>*I}dZlZIueuXL=adDM-E1kRPNY&j*=XN@G z=*bxgmBp-v-lepxt(3lp&VT6KOXoh7Ir2PV>dvqRSxYJL0G-iv9;Bldo{nt+b!-c$ zquw>0M+Ms>W>x>Z^LREw+UNwxw4kRYT{_RudEP2*44r4`*tN;BEhazN;q1IX z=VdxC%A;w1sn3^G#wsmG$mFH4oFMAFO6MIquj%I-o!5Koh>qL>D*QL;yw&HtEqaVO zj*gV&T{;u!yhmp|o%b!p59;a3=nr+y(rkQ0=VPHYu1sg0iF7`vV@rmP`v1nX`=2d8 zbiS}yUs|5O61sSHPCDPv`H{}IbiOzKBs$;qJE3g>$??C=52~%#Gyf|DouBCZLg#1U zYmjWhbbh5{+diG&YRNhZoj>(WR_8B@h|b>>V%z_rm`W}Jk{1Lzev7ForV(L^X*G)Y z4@yH3P)tuTfMSO9Bin^2X0)K0RBZUV6b^+aK@_fFI&K1mpB}xE$uv~v=#?mJ|EFkC zgz_j2nm)OitcJ8@i?}8KsMK^ok8s&_9H#>eP zMMW_S#XyRg<=~2Nvgc-(Ac|QjX3I|9HG9aZ`4n?d%%g`}ia9CfqL^Eh6wj_*JbPsJ zz#$a#Qp~4nMTyGHpPf3R@TC)45f-92m||gy^(hvi7)-G!#o`q5@vp5GwFpa4NDi0O zm+!LrE|%)OPcN3Ckcu6o!$*!%6wB&}Q(}2RBUX@lHeyAJRVeC8TUiE_)|+@f#i|r* zQLLsb$6^S@>gi!pa9y?8j_z2BHH@=n&7oLZBdYK^6lVXGUQakymJKL&qu7vQ8;Xr6 zwx-zFntl_CEh#po*qmZB{a&L!MN{aD0DUy?ltS&l1j)b@zf0fb7Q-mErP#@o+g?zy zz0o^RsQvHt)k5t*#V&$I?Aqt=PO&G&9`c@7g|!pzMRAZ0*kW&reJJ*)(2u`#!CvUU zz@+!TZ}b6%`u&&YMBFVcrZnYQ6hfRqaTvw%6o*qBMR5ehk!r+xr5LWn_`fVUy#j*b z7>Z;2p~nfKmZvy@;&h4;6sJ&}XksVzWlpYTjH&)Vg({~wZAvPdCC&4hdR$qFvnjr$ zIEUghigPJ0)xE*uJc{!vE;9N8!wdC*u4Gk1FE+eHACEhkLb}aFipwcprMSX$UTJuh z;nft<53iwkgyLF?dnvA?xQ*g^ikm2IFqs<#^>@6K_hyP)jd@GWsgIga+)i;9#T_Oi ze*~lFPmI3XQ2zY!n3_p(AH@R{a;JSQa6iSUo+>TxgF;h0q#PyW6M$OmQHrNc)ngQo zQ#@t#6BJL@YVC1!Eo>2=QCcEvJ26$|+d-FYd#rJGPpqB{e{cXY)ZO{VyR z;(LmpDb)E_Z>RW?LY;pKT{6hedTihSFMg#k=fC*Svzyo)bmy!&{rt>LcOJFF<`vVOkM0tdtNH0JK-YGDG}{YV^o6zUx{Da<{=Z2s zMt5mBf47{u`zNRRZovC z0lJ&f-CQ`eD$A<*|6TL{yIUK_)&<>R{UU5fcNe7#qB;c;|p|KDcJ z?g+Xk(><{svvg0=Y>1cDJ%#SbnrTh19l)ZWPWMu}XV4XA{!Ez#=$@r>fjGw9vklLo zdoEqQ_KmLj&$T<(y}&fcL?%%#GPLub-AjblpxVXlUQYK~OYsW2R~lj4LEWpZ+GYP= z+=*dEUq@GV_=nNG-o$P&ywT7UilfrKMc-F;Z#BHl@OJeE)gcuAopc|idl%hNbnn); zz}JpOfy@vPEHK+Foby?*nGRC(ppzeco&H3+|^WQaRsVDFl-O(obINc}cK3T`5 z`;=(V?RP;r9V+jsyU)=5j_w$`@6dggt|a}ORnGHt^}r|H7YtvNn@Ej#Sx^u4Y0y}@ zZPhZfr!~gP4uF`LcvyqBD@5&S)__m<#l6A+?{h02%bl+27PW}(E`@XvI zUETblJKpd^x^@~+-l~YTk%RQz33ToMAGK~jq5FmD{FLrzs#f)XUMFWse@R#DznFbp zphPD44BMk%Kjlwa}ebxMe2IUS`Ot9iue z=|#DQR*O^4NEuPiMCnrw5CKYAWYuD>345A^ys6Qk24z6mRGs?RQbLmpMU@dTWlEV) zwk5+FvCes!>HJvgq^x|IL&Ya73$28*OIcFRszoR(Lwo;Q&P+LrJjzexr9~`gcFJWb z=b&7ea!%vSML9p^+?4ZbCn@#*XPpIP#wq91zNf?jea?cE3)Qr`yX7L3OHnRLsm{Ol zhjMX2?F{7-hD&O?!#AW{nsN}Oej1=wU7AmpikkW5C|9Cfo^r4ia0Sh+3|M(7u2y(HfNNQm#q4w)U=aE!D8X&6MlZg!ajD zJquc2Ila&gsifc=QGP+WG3Be2n^0asxhdt*l$%lRNx3=YE|gnXp0_mIigHKFtt~&> z7!EVk{U1tm{!4TI%N>LxQH;M6r8@t0{*ALMh@Fa zW2z3N+>cUlf67BB51`cdzdAs~ik313NW~s(lBz@qr8WBD7IcK5a43(Y98Re(e`_(5 zIfhcUUyh|5NqHRQ2+HG)e}anjBAWkSoQZ7XugHISfY4{Z7Xv(LB)+|ZRB|qA}V<_cCufD zbtvU{$`2_gTIffH`U;qGf}qZo+9nl=g&UzS1-D%`iE*Ns%a#MYU)}*qgS>BPy#BSO8tMT8K@kp8I7KaN)LbPj)x^m zC6_saa=8u-T3Z%A;Xi=3^AyrNliA5D*s@O20vhzPxN|kA0<(QxBG0Nx3 zRmTMM3LvVkp#0Ja=G9bH`ue4opP8w?rJ99mB-N}`>r>4}HJEC4s`;trpqe{9Y$(;7 zRC5U-KYc}B%|kV>rez!|+5eZJDq;&zEn>uiR0~lptO8QZ!-f{yUQ4woG7naZb@ts> zIN5$HQ7u8Wf_kUbk``+zDmnZqdc^(<4l-QUQ08$>ol4LD2qA8&TIp&F_p z^e6LXB_IC^eUo~R)s9rVQ|&}0c3xubOtnibNwsS&re)WlJ=8?VPfTmI7u6wDdmDcr zs(nS36=Xk+qBXF;1syZB7YRs z$wnM)cnsCCYHGw5RmV{s-=|NY8ez;64Nt21>Iqab5e(HwE$OLLH&LC2*1+jh=Tn_Q zbvBjU{}54$|1WtPs{g`PokMl5m9#F)d3C3vx`66pstapLs*BW|YCbP9yj1yW28DAu z)zwB^L1pJas;ew?h%v9By53q-dZ~N@(5n**x`FCOiYn10 zo>M|g@dDLLR4;0feAJ^Hv;S3XcJ#Or)hkq=P|4VQo9Z>=ye??O8&q#ny`_&^>6~J1 z^bXbgRO3wGUBmY@NPcVCN3YcfRO6{WruvXd{r{RTNHu|KVvl3OLQ{Q8rRP7XKBxMM z>IFXdfm7*+8*<0az^PS!;4}3ijPoYU+ehMuLDTOu#P52aI3LQV1PlZ%Q$>1(B3iDC$pTBsH|GT>dC@e`~K?(~~ zSjeLCSPF|!Sd4;=&Q{xtI#cJHQCQq=T+Af~oTVr%V+tuOJ;-ZW3d_0JATJ6lm<9?f z3Re=YZ0Jp>Uq7o-SdGG36jpZ=YTdc8hR}8a$)Xn4rm!A`bttUsFCczIm3v%%x#kTh zY-ofVlY$j^V;ddB-=yEh3Y$`p|6g$b-zDvPtHKr(E~Bs|g##&UMPVd`t!<%)!ZyNf zDQxd1v9O)Xzjzjf9gHuvJ5tz5GCK=*F&ujDG10qH*i*!A!rg^?Dfoh*8|}g<3j2$BK+X|8n!*?g2T?dg!GnFnr{DFVh9V9V9zNiYrEoEYBPdvz zkEC#tV#iU?{?EdHDI81TXfcm5^qTDJIZn*uRgx2gC+4vf-2bO=iqI#2D4ZrdU3iA@ zOhc;&(Pw-AEu14fS9qTI=Nl?`0fh@)-tYRNK`xh2@c+NKOh3vV=oR{O(Pqi^^=||U zS5bJA!qpTWr*I7g+uLImcrAtNED43{g*OOqbS3%z+YJB53M_foho=ScDa3J>PTC@J`m^;QZGQ_xIB;Zc{AU2?)^ zR|-$~EL`D93Qrl|&W)k)GzDwq&sZ0rppIap*qe(}(B{v=^Auj8@B)RGhCge@;mf?t>M6rSO~gXs3S{Vfp^y#N6vr_)E>>Z`IO2 zj>SnRPHMtN=SXpKinCLkg5oqTY+qKH881#Lv`+vYr#Q9IBBrG{BgN@#l&2{Fzc_>0 z2Oso>6lbD1GsRgYFpFvPC>NeBm!UWZ#kqWXtvDyeVHADdhT@R$f4e|EJ1@fsigWvN zWd40zabAk^Q7llbQ!G-fQVb}TDV98Un?EUrz6ZeF!pkXEoNy&I*Jich%m&4nVr2K? zGb+WVuqAAJOLJz2V&b3q7w!CieYsyu{mr+S348h93cR@%=cl+b#RVuXOL0Mp3;7r2 z`JvFog_ZRpLVK)Mj72HhD?@%?i;J^_a7p1(6qi;dER)NKUXG$otXW$5UH)iRq_~m` zm{VtHU&|I(p|~c+RehU%aW#r-NZ$Pa<-Iw1i}JY_Z%)Ov34Wls4n^C7vM$A=D6U6w zdy4B1U*dL(8&KSw;)dS3iW^bfjN-;tX%siHM&eDYxT#R{|9Rs%d^(C-P;^6TrS;*U zxRsJ`?TxnRvp^KL70Uj*61$ht6nCV!8^xWZ-_L)exQlS4a92aST&G9vPH_*42a4X4 z;$9R-QQVv2z7+SFNNm3ev?TWz9x#Dv#qiOvXzRa*c8`aQc+84ti@z2;jN({|hl{q4 zzn&dB;5E@lP&_iH6&#mGQMATz{J%E;g5uE>kD++1(Suq#E~gcH0>u-DZ9773=_KLF zhQ^_ID#45tPoww;#nUOCL-7oXXSrisJkz$>xL;a4+YXyGVbSMOw9=kO@lJ~8Q@obq z1r)EKcp=40C|*SIV)^-gAi19Pr4()P_i+oV{@qLTfw-vOffcW!cn!s?UBGOt)w8`N zc?-FY;!XaW*W&dQZ=k4IZtKF{-tMM&GsW8|-a^q=1Ux&N5%pBoc-;OQoi|I0cTs!@ zoBl1{Pw^i0yL&0#m(M}@Y?7Ol2P`8s@&`Swx9x{1K1uNriWd7QMLQixja)Mcx{D|J zBT>=(f9uOnQ+!4;=7?(aDcYlao8og6tzTHd-1*nP2u~AN#do zu{P~i{F&mf6n{~^`4j(*;_rDm{TAFB{7Epivi?gJ_-~5;5KQ58FbTn=A|@01zAwAH z7#<73l-{15p30-F=LWv{hrp+R2&NP2bs(6*Q|0v^%tUY?!OR35f>{Xa1hW#%OE4S3 zaDv&DY7T;71asPGZu3s!xbvTDQ1D*_bBP#1Fpr42U0`&}E(Hq&Wr89>FyNHjHwZ%4 zIp$Xv_FzR=C8#+ke`^RDgQ7-K)s#$&z>U8+ao;Box&&(xBm_$nqy!5QWCZg|rbjSe zE^qBboCOFLoWLPim|!u2MF`yOC-B#+ya38>ae^fYmT;z>2WTg~2mbS46A--&!HNXS z`g0GKBUpi8`CN1V)||f*!RiDn6Rb+Giluc`wt3I*dNt+suK!XtSc72Aep>U^0<-@+ z5UfM6Il;OF8xpKXuzsGLU;|HR#qfk15o}7ZF@ZV%`qa_*_JvOHAA-$Hz>DhFy98Sh zY)4@EZcU*5KmC@l4S`#Jy9?K8=*8aN(3k`}5{x3)iC|BHoe4$~?Bc>UTKO&PDw*8~ z_8{2ZTDV7f|B@*S_9ECvVtczQ)qiSsclPsNZyt(r|KBcx0|q&cCb);-Ac7MK4kkE` z;1GhblJQpng2M<7_us+WKp%{8yJ5!1=3|2+2#zE$<~S1*=O{y4p;M}(3EclDIM!od zA8$Bp^S23(A2xbAf)fmt%tZpS2vl=Uz@=Gn~)* zOQ?-a!8ruyniAzA|35gN-~!LiiZOJa`*^`c1UC{~OmHp1B?PA7(&1why9h4x5A1f> zmEa13t3+IBoDnNqAH1628W-DP*Wu&llkjx}Hw+*58Nv0=w1gJBGXysg+)Z#Z!EFS$ zh;ysQN`~Nef;*M`jtQv2HMdmH5j^ka%ieH( z&DZ2@{nzj%LlG|vUmPJcjR;ypBb$w3o1;Ll@soI{jxBn9JYl3eHy!quL zvY6jl&G^egzAzE|NH__>Pn3=&_?eO!s9z|}Lhvi4X$gKK_ulDm!V%? z1OHH(iqa&Mpfo9^$@=`#PD(zDLTMPKAxgu|bGD#g)&Wbh z|D_R>=C+`#dfnei?V>a+CBLb#>jpgdcPvyE_DO55d8l(wHpW=BfLf7JZ;_w51yHxV49aDIF%xA(Rd^Vo>tKDUGExW+KiJl#ZMbRHjE+mvq;$WUcgQ zH(Vu~g?5hU<0w5z>3B*vQ#ygt>6A`%`&>Fnc(U*m;i_gSBl?j+?4y((lZMABJuyK*oTn(gM(Jrv&r^Da(zBFg zI?VQ(H7-5pmcbkbb6-m@P}1CvQ`-%e1|tRAy&Bqx7BO?`(WmqWrN6}d(^gVM{OwV;I&3?a?2gRK3MVB@2`3xz_(p`23#Sl5I0xaBgfkOP zMK}ZD)P&O$YHBE)me3~ued)w}6*taC*w_)yNH~*wFB&_%=EGSCXH(RyF5?_6`I(Hp z8HIBahJ?ci=Or8>oQH5Y;RwQi5zf_jgna!=Cq0I9`}>+bP($;PJiCIw6o*B^fUx9I z#<$BgmkIq~f5Hl3m9RlrBdl9mFNWoAd+fu=zhu#OG}6-|Y!h}w$9e9AUBbkZ=#XL6 zOh#o%!X9PIcRs?02~2sQa{3Vlw= z>TL_cBM7ZI?Mb*5;ZB5G6WUYIFxNnM5TShj@L)nM1cY+_ zLrwmNhf98p%Uh+%-%x8glJF$LafDWfNA>ys`g1wq(S*l%{|b*)+T(=B3r`T9XgGM{ zClj7a==1;9s0dFbJdN;7!qW-QuxDg^kFuxi59=&Kdq6pTjyD@;o=14TKTS>Vs{j`g z-cEQC;mw2>TSkPJ2rm_0MtBY3<))VK3gMN)tAtk@4$5||)84bLBXnO&^bNurg*O@I z@`U6>B zX80oEs}g&O@MXeRJj#pVXFd>)Cp70jucg-s->`we2aAV>E6H1gpAx=J_z~edgdY&T zOZYya+y6Y35rblU=pV=VQ}{Tq8NyEn_4Aq9(&vT}_`;LBE&7V`jD%lPo{aDt!k-Ag zCH$W7yIjDK@CQO~9`*w}N5B1_DNjoH3*|`&e>JAWek1%{#2*9Bp9=m(`1gSG&(MC$ z=d|B`zVch1obr^Er|>ECvhV-*GTWL(c`C}&P&WVH-%5OZEKf_>yM{N3vYh|&40+Iy z@=U^+Da*Gn&q8@t%BsflY?S5vm*&Q2QeMc@+V%z^o?6Sh;goEehrCO2l8kAR}yej3DEmi`nM*AS^u_GzVbeDIfEc;(}`%iff;hsWY z|E0XQa39LnV%`3Gi*>&5{~TJ8Cn7>+59!>cmKcTmLFy+H3A0h$S|FYZv z+^A9>Bbl*-R7Xh6QjNoQl1sUe`OA)tI!ZU-n|qo`|z*LkW5_Q7VS%QsTKpYlzV?=WqYZ>D^UBySbo zCcNEKDK8n2J1O5K{@ucRg!c+H1mrs;%MVa~(3sZo{f=w|DL*WkM<_o=`B9^-_QZLd zvfF>jJSomo!lx-eC*m2Q-TSjfOejC)=PAEH`6b2r6p)CQ4RbNduTnPiJ6=}hHOe1Q zex35$60<3weJQ_5`7H^18>9S=+ajCyD8EbDI@kN=j~PF=U6enh{Do9~B>b52CzLtElv<*eXRD2*1-5rRf3Y!1FAeEkI+5d{{e`NvV*t2uYXRB;rsw_-p5h}g~%lXd_ z{<~dSjEdWTyQnN-H7sH&Dr<{aTDT0AWvQ$rdO0d;x0MwXT+wjo^7TeMK9&W8x60rM7 z6+8dmAC133S2iR5j>_gV?MLui(D<6lmelvBvK7_8sccQvINMOQX0t7oYp85T#Y($9 zm4kdDw6X)09jWZ;zxb)_L}h1Xw2N@0a95%1e?|7c;`V>g2KJ(IAeFtT?BieB=6_JF z>`Nst@F=C--{q~dR1U}m#2hVob>zyyR1TqXn16*)In=wMEppjQapiDHj_LQ=$`Mpf zrE(;dW5gdvB(%w=+d%C%J9pmH6R2dG@{Ca!V=l{=~2DEXU&Hw$kO z%Klem|0}nPbBAGW52?8Q7t>m%?0-e}zvA|vist|G)tt(MR9>L+5S2%%JWS=0|68iZ zs64Ib;;Tqho}luiG(2UPKO+U75t?SV|5P+6RGxPko!(F7MJk%`sk}txW#{K}zZIMR zw<>k}FXA;SuTK>FCKY?2Z&8uyuDng<9V+hKQF%9CjHB|t1;zP*%7-F85`HZFMEI#; zZW*Y2E{@sK@2Pz0rzlsx5`HcIH^Og)-x=n$LFEUh)#|AHM0GMMKU4XQ$}g&xUk&{h zyo!FO@)wmqME{xdJ*`LmE&PY-B(A^e_TM<7W&f*FVCNaL`z zdz8R%L#3UIYK7_uw>;IksfJYNk-Y4GwII4E41{j~bNirR*@M>ot5vFPsx_+iEr1QC z`%-OCjZ9K>Q^Iclb1OkLrn&&t4prO#zprvn+)Pzv|ErngdqTJW%3}UO+6AdDEa8O= z6}ztl{!MjLs*6%xiRxlh7w?aK)g`DdPjyLYTS_S7UtLD@vO@R&2l6XWT`{KzoRz7r zLvWj;ehcu&?gdJ^`?b_4RA2TM4%oZX>i$0AzBj_ECU65o=mI=JjuUadxJ<2i0Au z?xsgSQZ2#01^Y+2?`}=VTZK2IJ*nKRlI zq&kl3XsTnW9;DcV4MiM6^-vLq3ElX+4X=)wV7;jx;oY-(qz$QV=c`BQmj6rjB&st0 z-aL+>daN`YCp=zwf}v`_zW>T=;AE<&Q$0norz+MeUCdI?plmP@H#mJe!qT~WP)>NUEBE2+Bur+T%gQjO(R zO7%Lb4^h3I>YY?=^2a@Msy9+~`!D)ts<$ZZtwP!Vs@s3X`dh_=N(^OxgD&t?x z70UisW&f*g|Eazpe9>^wHePnxTh1#~U(M+}mg;M2MXytR!#Gsm9PrMsR~eP^KO zJ*w6)-k0PDLfQYS?0?nmzn=alRAv9YcYH?G?SI}Ll;lgQUk$zYOMi5zena&Ss^3!m zMX}#e{hq2h|K5JY`BC_j@Mpt(VU+5xRDYxTyK{7yzt1dH|FqkbJ~LTy88|E9JYwMD5d zM{P0bU!0n6_@TB0wIy9=ZnbK%|25hFn%jTd4kY>IsjW&){(o&naaIzpEOh&yOXdW% z)v2vdZ4GK`DVH_-wzTHU25y)1@`jWHl*4HZY!MFh}t&PHm0_fl59e4 zQ)-(l>Oa&r%QI5c7DBhWc^Rl}?R5W+wxzb+fWJMp9dbGsptcjW{i*Ft&DzN>)OMvd z(xc?(xYe!gMos>IZ4cv9+f%q#E<gjfD=bkdw|+8)J~D)vDA*Eb|SUo z6)XEc;iai|l9(qOO88W2cTziz+O^b9r*<*5GpM;?r*Da|G##nVrBnpZvUxWGm*@7)NZ9_-~axjs2ixs|F60I zr*^YTdb_=4@O*C@5Vup4|F0G+nY)Cx1Yi&M9%}b`S~mjsQM*5Do;VVMf|9&rfjoNqAUZ?f}wKu4}>3zXWgx~L5)ZSJH_w`?&38}qH?LBec&pDz$ zr1rUpkEpr*r}l}V`r4<|{3XI-4Hfl;(4OE|)V`ti^@Lby_%@fM_C59KsQo}4YQE@3 z?I&t~Q~R0P@6>(~=T}1!zvW6q+q{qo|4Hqy37VOKjsD zL>lA~)fW{l`(Iz2`dZYN5NAp1D^XvHy3BsvhXB=>{QvrL)K{dwyd+ofD0NH|Fj`Cv z0rgd=uS$J&C0xzL{Bf)yfi*p-)lBMZi?a^(b#oa7*AuQ!eFGyV;%_AW#?);KsGZtp zHNUB1?M^qNEf5?+P~T3d55el%h|$(uGWWuK}q(azPF0E4|UbK`8M_a+)J#FvX>**W3%-2 z188hS{XptZQ6EkHeCh{LKZp9k)K8~=2=(KrA4>g5>W3-Q!>Q}ohr0HEesUdko&RRF z?V86?m*uy4o4N&$@d=#z(T46+*N^q$<&$Rheo=enEDlZQkPJ_ zl)ANn%PdDPK)z2yH1#W~U!{^b)zlr+&)NiJKC-qyX-%kBj z>bF_h`kK8wcNm>>sNY5X9_n{b#JQLHeR=Fa<^k%DQh$)Tj{U6r_g^MY{ShMu^kdYu z)#QJiC+$4X+{LB-G<7qK&rtuA`v0>X9n_x{K1cmM>Q-rQQGbE@o77*ltsRPbiMs56 z-R-|!)L*4Oen7t_&g;TAJc-HJw;1+OM*VH-?+knPHV@j)llr>@h3`}Ui24T>Mg2pM zwQIu`^^c99{z)FxeYyRo{<-*H2*0HMt%$Fve@*?HzOa1_Ej`q~Q|$L1^h|%Gu`KnU zXqe6XnYy2rK>Zg}E8;gAQ&6|>zl>=gfBCvR^*^cqHK6|%=O4$$B*IB)Oy;F(Or9$d zjc`gDGfHME8dHm~{h$A(F|BYq;q<~83{61vOiDYm(2YNhS%tF+W&azp{|(vye9g}O z(`YZF#{qctGSW1{@7T9#@aO2q_LK(GG(g-KZvr$n z%%h}XV;Zm1*o4M~G&ZGiDvkfp*oMYtG`6I%IgKrbMlCrs<{@{JJndFAw)W0zOtXrk zmZY&QjU7d7M`L>$J9t#ym%U1NqG9tqc6U3|*hR!hL%+=8HFk4L*w~%M@ig|JaU6|3 z#o3F-ku>(EaR3cJvX#cZLOcITyXG1?Ot`VX4T1yCfiy(KvX(_g8=ceHe|y zX^fT3n2Gr2|35C~IN?#k{~C%oT6m1`SnK9Sm~~R{1R5viK^iBCbF%Q1oGIFkKaJCA zobN++;|v;S(m2P>MdK`6kEU_9uS&~$(>RyLd3jJ#7xd#zizT~st-_*_R zS^pvCpEUk5!kU)%eNXZa(G*0JcwW(@Lbv}S{Ec8hLo}tBQxQ!=G_}1r`(;%TO=}ss zLd!0ip6FjhGZ4w7M>7)5L?p8x`4HeaMza#lPUQAKw~#qs%6$kR8YUbP`YV7d_l)Ku z()pj!+(drhC(%5@c@5p#M85x@DDYrjdr?TV5>c6`uBZx8)xAOcHX*9HS|=JrixWje zJ)$O2Ow<}OCnsuKSMnOLt`~KP5+eNqKy^Zt5@jx+QPsqV<|A52!t)a?K(wGgT+71V zIz4t_A}iY><~djri7hHzEcZ@zvr72WJdt`qZ#DBInnM!Tj(ioNwg!;Rzy2kEYa3P+ZbQ; zwnWFQNm8_LlHILJfS; zexgSS_cxr7Bhi6kjusvyJlF;7E0X9?q9ce7n`{0liG2P?v15qF8ZqJtA1hT+COM8s z8~&rCi26-MQO6J+C*oMIpZo)^==hu_I+5ryTks`1iRfgai-_#!kJiRcB|3x1?Z0yL z6QM<%Nu+6?$X@}BDdBU7&LujZNX~y>@&ZL&m?so{u^!VU!b=T}nO6dlcWpC$L|4+Z zK6@3>$3#~Xy`)U9A+oMu{gCisBF+Cqj}SeYe}?JDq(ttA6Fp(r z|28wy{7>{Wk*4~iXNdA@)cj9=%1ZRSQoUd(;>A2U(aS{g@S|76e^oeM_*(u6A<-K| z_Q>8OdY|YmqIZbg{<{X<5z)Iuvj6&*G0_L!XY<`kk!}9*25FK+pU|{+^(oP>M4u6T zPxLv_mqb4KlZz33Mf8nw|2k(9eXHPi6J!+pf#^q~pNW2QzW&H2{x61lYQGWv9_CEpDt|sw4=Xm>a73D*_?#t6f`HbC2X3L8T!*{>J`9fXW~pNe{(9Dv(TKH z=JcLdQ>P&{rxiV&YtCPIn=^=^BQgz>=y=N7HI}{g|V>7$lXr>}EVQ;{nPr>IMD9%DO z<-O-)N7I{)_<99sE=F^4n?e*vZwgI+1@QjcT$<*ZG&K>_T$bi4G?$au@%S(>#IZHZ=F6xh>6|X>R8kc`>%9 zxdY9eXzrME3tCzFrt|~ zZ;qllmgfGZmgWI852bk^&C!;-I0p$2rg=!7Z_XFIc-SO^15Bb%`|VJc^gd~6|OAuToii;%{ys6K=Uq| z_o?J})4Ye~y?s@=wQSyR!9K^8Kd7`i|EKwI&Zqe(%@=7tmP^t!`|t$KXJ|f2^Qno_ zK0UZcYq9>>7tLoUDvzJ2L-U1Pp5{w5-xB9#ny=7&o#v}F#}8ud#E*WBNyd+Wqxt4U z+I$G0`3_BO#q2lf_i4>R^8;GY{E+4^G(V#0{yfc(-N9&nLi1AvKco2t&CiYY%T4Z= zL#hx%nqSlWRyq3Xp5(t1elPrirn~(#b!4kQ`k(z28q?$Sue8jb|3=fk|FtsP_rHdJ z2>+z{ml1w>@_%SeN^26M|94-l$!JZU2PYt{DQQhhYbsCGnp!xGVSij`O-E~1TGP{- ziPj9XW}KkS2wF1>XZgRm%tlN9|H|vFH7Biz)-YNDtsz=+^;^Sf>4mE`SN>sd|MT9~ z+_dJQRgl_w^So%e|39Ehw5lp(C@j;e6+fdjAFZCzdF%wFwE(RJC9@E%MQAOYXEBk6MQI&KYcX2u(psF>^0bzae1ATK z)>5>VrX`=hwM?E|0`C7?kk$&cRuyMO;Yzet7O_e$V5s0~wCwyhdunUYTAS9IwAPwX zn1ReXIfvGIv_{fepVs!YHlVeM5^hLqBjvd9goLyN5&HSp|1#Q(*52ap zLu)@;efvKs#wc3*OV0thLP?IMbq1}2Xq`ywU|MeBX&plAP!Weo{_tGGK+jlON69P{av_CPV8aS|{hU_!J>wH=l87A{F|A8zT_*a{+(amg%a!jH6H?K- zYLNCC+Sk##mUe-b5x3F0-pBXW4YW+*jYG5jIP}=Few;;1{(tKhTDSV!?(9d7c;e6@ zJA=A)JFRzV-9bx^ed|tIcS*zDh9d3}-YfKRLBs>JUZM3MEz9d6T94Vh6Rn4ZkI?ep zCHqX&9YtD?(|S_;CvqmOr)WKGoDol4E&eku`REd~-2bQb9IY2=J?}4Ntrz@7%qKq$ zE%=hq&VR61^?&(cajjQrji>b%t=BBAlD|&t4O(v+?b~K1oIccgTe0sr-+a#2d$i3$ zzEA6GS|2FtLs}n;_{e2EyH99+LF-d7Kcn?|&L4U$7#ee|m|qINa=y9lt#4>gO6yx% zztH-Q){lz)p4Jb!$JqB9TR+kI*`fxiex>z?B!3hB?ssR4)U7{h{VmR4F6rg@#|Y=N zC$UxO0X-S*$!TlCr#*#YG4%4Zr&4fg+S6FjnYmBfo{sh`iqd;Qdj{GwD(L>dh?xzC zKlTUhS!oX!XEy&#vOPQPIcN_PJ*R!DEMmxTK>y3j*`90Yqwi^tpgoU>xecXc-kcD# zNP8XH0qwPEmuSyVJEYyEU8dclU7_8eUG<5?w$EPDuKS$figSsMXg57*bD{eDpLSdP zn06;W4Pf|@ekg&M3GI}2HjwPOz>0Ixo^POS0oqH@UXb>`X)ok&7VU*;>+lDkYn^BK ziVx9VR7n=2y|@K)&EhOcdllMC(O!}E(zKUxof4q!M?lbCPPjbn6+GW9R+j#iXglG; zrr*U@rM)`s)%wb%g!US=b@)SjEzfa_JuHb;qZt98du*>udp+7m(q5nTfwVWEy*=#> zX>Ue*Bifs~#b|FV+$6UZqJ1vNO-g%n+S}0Hg0{?ld&>d8Z~yz|rL7^L?L&Z4?Ld1E z+B?!7NqZ;S8VlMx=dlBsU1{$&fkWGe0HxhixR-EmL*vudJ3`xs0NQe=+oNdjZ?~xi zbAX9C?FII?0NMu$4;CIG^uGYpK1>=87ix59kEMM?UR_c(PIwgUb4AFAwU4HK4DGXN zA4~fb+Q%vCc;N}c6NM)UPc|H~^Ip6Io+|YDAKIr2&k&v|Jj<}ZqSQXeXe*)k=h41I z#QC%@pnZ|(3nvn|IHze}s;J9^KKVoY3ZYNY(7wvhUC{Q`w6788+MFZ$dg2c48)*Md z+xqM+V*06-v~RYx00nQQeY-Sh71*Qhp#3H7J88c}+vfkR7u)6^!+U5yL;GIZkI}x5 z_Crd3KkWyM5dGl5sywXVBf>`w2mHsy@%bOxPttx`#8bIIE+bB^O+!HYIoh)SZMXll z?c*=61ko?kevkGmwBMxtD(%;3k9YU@$bE-x^fv9+Y1^N_{_9J<8*V1GZ_$3o4H_x#bS^Dn_@odEN63G0^V>M#r6E97?2=Nlc|5nzE3Kt_@ z+z789vuSzzkA41!cqtF&AB(w$Wr;T;UXIw;bBUK1+5()>D-y3nyo%_R4V8RV!~X1O zygKn(#B1nTtZ4%7#(2WDi8ml#hj@MBb;Ys$pSf#f=$rZ7v57bIslt5vF5Z}U6XGq1 zH&rg$(;IK5wA%mgs~)a_So?pD{D^pK;ya1AAwG_HTjB$VwVi#w+s$C7m*^Sut|EN{l{9e2#@qWa65$|oaErESJiM>9@UI|8sIf_{J zKmTkzK9G1U@n~ZGi-=DkK9$(*znCWxpDf}O!@<3uM(pOD_;lhkh~4ZHpE2wMa9_@zX=2&K|zC-Rv_$OZ%)5#w31@*gE6O#4lJ-oEM2-%4yxpD`LJX96y1t z;OoS15L-)rllVR2w}{^*emj>Te#esMbiNu${Js&yANXGnVq5>!cOvn}#Gey?Vl}DC z{8adv2W@#ewo3b&_)EK4VxKH>on{kU&o_F$-{y^k_=UoA^h%M-l%-XGh|n z=~y@ag^uxmr85=rZ*(Rn{+;-5;y;N09Mq&A{%P8*Zi(Ihr!$FA>%X1Jyv`5SM6pLr zp(ywN>G&2<5mVFA41Z@DI@20Q%;|)_1ysb0bmpKl6CHV&)+IZ$(3w@I$^DCtfB$6{ zo!R|?Ix#1m5p;&p8Lr@vq3Q9lrepGR<+Py_EQ#pujSXCXQTI$h(_Dbfk(lw9V3 zi|Agk)h?~=mIS|ZLe^rV>%r>aNeV=A$1Zub}#AB zm?``LjhTW@kIn-Aj5_ntncuV4B8bYfprJiV(F+R~5&oNw+kd-;9=MCn;&fJ_vjm-` z6}u!I8Gnm){XY3aXIVNcSdMg-qqBTJZNI8lq_Yy8m0g>eP`L_pR;9CAp0)FBQ%%Pz zVH4?JQ@9qLwdrgidL24G|4(N|n=XKT^h(Am}qSKF_U?Zxj~T4U1LiSD#?cBbJmir!b_NKEZoxS`qsV(Ud$o{KsEA4(lI|4$jy>ozq2YQ!0{3<%54c$6*4yJRc zq7Jd3h{Na{N#}4nW9f`BPHxQ{m*-W4FSeERd|~4bVKPmlg_zx&Jy!%;W>tWc`vq9w#7x0Ih_mW+)U>} zI#CI0otx-bZR7%1({cab(i%$OIy%>jxFP4$ zxp6|Q5p-^$^8lS&>D))>Hc8%YDB=z}cZ#@6nAhz+qVF}#Yn6`Me-HL=`9V4lN#d`0K$!5!JTvZ2v*WdH4Z$Bq|Vg1Lw9nzQ;O+UjxKo= z-Ki9u+Iwr)7k>x*>F6#+cY3;W(Vc%?dYkDwdTottir?mSjAbmyh()}C&GZjo-_!d7Ei4X0}Y6}n|( z=6p$3bDC~l!G;I(f0A~abh~13`GoTe7ZAGf zr@N4$*HT~4B6O`jm!P{S-NpJ(xx2W_zb zSERc_p2QuA?n>fo2(lk2q5lTa z-H7g%bT_8^AG$t>_zsrtru{#{bT^~B1zorQ6YOOFg|WMpwD}OAB-_#*Lw7s6d(+*X z?(TGVpu3A?b`;tWu(J#6iHkE*xGUY=oPW^X3hp7?Q@EG&`**z$-9zZwh+t1@Ke}f5 z^_6$mHh&sN_P=|eQjHcKBs|zP zqv=|;97Fdyy2sKzgYIz>JD%>TbWfnGZ@#)GS}hHY*_7_dbWbrN_Zua78r{>)U9nzZ z4vaWw(!H4OS#-~*d$woiE#Vxx=ML!eoUoo~PEPj%x)%=k7Y!ZcZHewBbg!g)>41M3 zUAOIYuW*jWVg;|Fd$p8YBfQp&VQ+J;_IkSa(!GK1J(9eU?oD)Wr+YKqTj}01d=VW} zX^waIHs2bW2W=y6_YS&u(!Fc=ZBselId|tid7d@h`{-Ip?x*{}piMqV_c3>gyARQQ znC>GkZ<{tf?V~nbX+fXx>^@HS3A#^7^2wq7mKX6f-DmRsoW9r8#Z1pTbf2U965Z$f z$5VG-p!;He47Hzn-F=zv8+2cx`x@O>>5lK8fSsR`-F>}(gteWR-F?#!weG$}_wC{1 zj<g_c~9o+eYzi5F!uxLene8B`!UHRbU&f{HQi6?e&z!9uGIZp3BM42 zDg4TC_?X;=d_&hQzxdzL{aKy;d%8b}_>t~U-gxXK!amk;JMatL-|7A;`ZwoWLvsEf zbZyz!h(8Ub?QgpO7~wKXPBJOUaFWSLW+R#0D?FKkWJVHPKABQDl_xQuJeitgngKm6 z$#jaHUN}R}7d;cnEF^CHotZmV$*lc5N@gb+Mly#_G9)tox!p^K{C4{Ezes$Am&E3U zEZ-3%HvhAOzvm`?g0!g`Xe*MeN)nJPOHv}qNJ0|*NG>UpR7h$*cqLWuEKbx(eDX(h zMA95SYB4Wo?wuxWlFrbFyOG3(ezZlRp&?29Mm(4r8cot8S%_pl>6xEofn2A83l8)z zOtJ*YA|#8ESk{Z?{D~wNH+c(^EJ?C7$x?YxN|rJ7py#z5$qLr+NS61rTlZUDOs)SW zD@j;Oz{x88jWPWSU!BBmWDN>`5|~WG@mK_GE98eMscnEljFIG05;yoHmyz@ZB!4Bz zH6pGexjHX^#I6oq0l%yGdRq zxrgL&l6y%WCAp8}L6Z9=rpce&IwkV|lZU115m%M}ZzXa6Ki5q11j$PzPm(-O@{}Z> zCh?zt4O&j#qMlXzdCqW<(EWcBTLN&q>P+*@lUGPSA$gVLO%-Om@HL_N|5mYY7%Jgg zBp;HzP4Yg;J0$jJjs80<$$L4i96!i&R1zBkKJFL5+xDjVWe}A&go9OJ{odW9_vfMW{8G= zb1tc!EM%OyNJqHXA)%Q*(s`KgMbdfcT}@gbyNI+%`UGh}x(8{AbTv{-wG3&Qv`1QT z&CeZ2S|zQK`sN=mnAKj|AdN^nqMM{GBSg1JW2ay9ja8(+1&A~eYX47~8Eqfpi8CMR zVx;qvn#=;Ei>Ou?6fQ)%uveF<(hqJtmDls4ULEc79rXyW~bV)w~)aj+P#$mVV z|9|v#gmhWbm6XwPq~`5dE-U!(l&aos~DiZ%j1^rkjy!{wLkS z1SG#DsqBC1_FtTBNVgTSUC!}u7}Fg{N2(HbB;CpQqIV|U#SiVY%f1>*cO~7;>HTk2 z^4(o*|4B*rB)yh&FVdq(_a@z+bRSZm!S;UP6}um)y#mPG+1Hop0i*}^Urf@`q=%9o z&d|%460vM9s;cIp_RhZr>yvuuv2k#+$g!Eq02i)+c_mSRj$z4N!NJwgr;~`Qv z{&`cha=O2lK1$lR|D=x(UG8m_^hvUDq)(B)M{2k8yqM3B=1oq6@hE2eGe_zApY7IcLx{-V*a|(sx9>+mC&ABB3qSgX0j1v zvycrVo7Jl&n@y<6pKK1YIemc3y$hXaO*SNf;X zfmdTz^v|iBD3OJ}S=9QPacns_tB}>ms?ISd%FxB?PFVNPBC;-7lPo4{k+r?g+E`=F z#@e|{b`1UYoleM7PiVc^YCG$ZEk-sU*@B9lU$}q^cnvH>_HVL<$rf>i#x(0>=z11) z!kpo3akAyemLOZ2Y)P`E{O=)>@mb1j8M0+PXkQZ9GqNY1El;)**$QMUdcC<4zaz4h z$yV`pl7ErsoYlzoAX}YmN3u1@HYHn=Y<;q|$kriS+Y4iNVZAk5mrS0&1xMM){yf`1aBOu%Jq4e@>1)$Ay;^ib5$gU>4(nq4K?=hOU;O<9u4cWCml-Oj82d^i4 zfb0e`cL>RD6y8L3vv-qGXS)^3tg-p7ezMzqSjlcDyMxRu|I>PQcRAtwyUFe`!V8&O z_3S>Gk^3zp5fAz&;rm;Uc*ysPc>j8Y>`}WT|HVo6nDX+&KkcE2ev-_r&{Jful9~Qz z$(~U${4x1IV(ia2*>ftehK9`TzXV<+bK@`iW#KD^#*y%Nve(GoCUg5Qjt>E3ZwlT1 z+w&6r4w*HNFUj5``+)3yZ=9nqP_2GQ_9@v%R$8);byGel$fTIn{fx{ff2<2j&lldy z?0@CiS7g@OzjklLnuNR{GMfdmr}iD$_tN}BKFF#M{zUc{+0X7M+R&f{@yHJFehfCXSvKyZ&`YC z&|8$=ob(o;H_WS|H$<;MPv3vp4~BaGqBoa6>)wc5GrhU#%`>3q^?}vu&;^S00(uR4 zC3+QlA-%F6N03*eJ%wI%P=K1R0QTx`UaZmfB6_i}_V${_q1U3<_Nq7__IcV~hh9dn zOE2{f-b?xo*t)j$j$Y4yN8X#ytG745SMT9R42`}(H?knTh3G9pZ($4igUC;r?)}>y zq68MBxA@Qzi@P=JEkRGG!1b2&=52pj>Mh+rbH2CC@RN7;!{mF*(c6mN^7J;Nw*tM5 z>8(g_14XSwZ)G3xd#lh}i{7fz@ALnPU7g+<^wu0^Zk~2kdB5nbP0wt;C0R$fuGgg3 z|N1%M{js;9x8B}HUL+f6oZiHBTF30k+35L9UEjItZLU_dh0uGbh^^^uuWH;zsQsV4 z?OdDJyuUlE8tq(&=_If-y~FA4LT|JRGm_q}^!A{)n}WN$yv_+xa8IEx0npo9xR227 zJUz_?^+t)_U#OX)-hqQVJ&4}HK8AYT9^yCR#G&+j^QY0iX3-l%?=pI0>77OI2zn>b zGnwO61J+=UqUY|t_r%`OLvP(j?-*rttf834S;KJJnv;#Hy%W_?Poj6G4=X(z0t{^k zFtj1S&}N4WPZ!!0kh|HQ+`HJ>^e&`#j(6?ex%6aYdgr-8=$&r@-XqK_^Nw&4Jvscn zi&dCQY%Re2dH3tQS~TMKeDgm&ef-tC%J0|Qb?02eeE*-Svw)V{X4-HePnq#1y=6$5 zukGFSn$}*goiZ~+%FIa$DKj%;%FN7Z+LW0w<^F}QO#jSyH%-p*dCzDx8u3V$C0Xz$ z2EJk7W(MA6;1&j+W#Co@?qlFK2JGEu;C2S?Fl{<0$h6kzyBWAwU)cukQEMh6wRouo zxSxT?84%)81|HO14FeAuJ}hX&BT8rXaIA%(!wLiA8F-5UAzrrL`hww$449;7)_(d518*>3_FpUIHLIG}rA?ZKH??tA zD^3Gy{~1u@FHf)8yvM-%41CVO2Mp*^KLb)_9~tqnioF{Nnp4`(XFYm?46nxg!cac} zFrY&K1KL9L(YpH&aXy7F(%>2YSjSqNuFoH=o3GN!Kot6_KjA7^%R&LOCOZFAz%i=6&#PWhhNNKES#7c%M_xP*Kx9Y7udH~1i(JqeHqiZQRaw#N~tltzq`NXr*_9fO>fHfblt$#B|zi#;T!8z;vZhSR|*aSEMC zYvVsqzS3%4JFDTWjkCJ83TF+RwQ$yyZm1Iy>5M|?Y)v$;gR?HqdKysryR*J_B6(zv z?QDp%Db7Zc2Aquz#s0scS&;P;XEU6wjHyEa&K5XoPkJT1HO{ui-$v)&qFlxZXS?o5 z=j?!U2+oc;yW$L2JMY+_;OuO)8Go4p%B@+>Za4?v?2fY+&K^1jIeQ9fERqq~72xb` zxDU?0IQv^H`w42F9l54?*>*||co5FP8c-j}@(9cCNSq^aM&a1{zjG+gVLC(5M+)Z% z%fe{QmsEtuRPL{ zaZbZI*Z8O7oME+jrVhv25zfLnyGPqEAhoW~!?^(Ge9fA5Vaw@-`c+qBzqqT}Y%I>D zIG5qvf^)eHTNcX|I9KX$=3HfXHO}=o*O=EB4XaUHi*sE?8~+BAywUI`LHXT0&dq{C z%cFL~xee!T6TThC?7w3#1&&%yd0#NEdvKn{xfkbgocnMdH0J$=vh%8HBoE;{g7a`S zx>=4M#d%Dorlv<$yrtJaVL{CPJ7)hS3K<-ou{h)PMc8=;XB^IRIL}J;X+Wt&nZrnH z(e^3N3pg+1ylC3w7rUEs2aNfYN4Fk@bIGPfiw{hOb zk;Q+B_g%yHbWqkB5ftVJf+qP9&X+hJ<4nN$1m{ytkF8hZd?tj+*!*9vA$7ix%Scr~ zdaky*uW^3G`3C1Z6Z=-ZlrLTKM;tT$&JVgDTjH&DVmm+K$ZCuv;Ag{M#LGen=Qo@` zael8jIDb_9p7U?qKAeAWXT|v!x#z^40#}Pgqjsml9b|+K0is!zy9gR{THKj%r;}vk zPH#Aa;f#VRuY9@u^FZ8L)LG@)oeg(RW6o~a{r+nT=fa)anDgMyTg9RQHPXSjOW_W| zT?}_V+(mHvaTmayzgu)tjP8OKb|Kt_g^++2w#vw&Rg!S!{%1*#-2W^%6n9A>M87>k z2ycO6_;UwmX5 z<9a$D)ACgNkJ~WR0jO%IxPggQ33n@>+1JkOXo(od*N=3 zyCd!&c(dYn*3R zsl`282;6gU&&NF%_q>T&R!ikxpf#(RlHPg|?q#@Y|8Xxdyi}FgUA0#Am*ZZ6d!^*M zYZbL9uErgQdkwBs#2DNgtRn0cz`f4WcDlA+CIt8zSE# z#cA2R16PV=h$cXDeK+odxc68o-HU72{hwSlMd>58*z6`!McfxR2mITJa|l zcwEz?4ev=@*%2ho<|(ZL)gvYOOjVNFPL%Vk;d6%LacvlK<#V~LS`4u=dC>~$B~7Q= zs8?`*!+jO^Biz?;-@$zySLZo$6c_hRJ&>!!TZV6|Q%3yHzevSJn?iaYd`M)~>SM9$_eq5!{UIAQN1=6tJh!?IJf86g>tU46z zn*Dcwz_oGBwfVpMQ_uHjk+dMc;Qp$iM(ip2aev2?w)zL&6u5s{`u`F%$-fO{2>7?+ zXg0km@utI@O1-?PH80*EylL>{@}FMrylI8*#_LUQGBZr#Yx93^W;~tYh zI{&v&y86-6GdI!Uc=Hhaj5jadws?Jb3Ep75W$=dJErvHA-a>f&8cJSay!r8T{%`bx zfZmcq_~|e}T8W zyb&9*BHpUHoaL>Aw=&);LeINnzrHWqyhsQ<2QS2P@fv;oyWn|vwf<+`?;A1;UcG*H-;#D;?U$Vor6#qb3!%J>HvmJK$Y{w!rb;~a{2B;H|oqw%B>9-&^642h^pj1a2rD8r+5&y_l* z{7=C<4(~*~TPEH$gGYDNcb7-Z_d2{g@vg_a1@8vDn=^27TSlxezAyCEiz)r~DhdU-7=h`yTH*y#LcGlO}h3 z-O~I6-v97^H2NpiZwaSoZGP%^ zQCoo8wbT}*wk5TNL_f8KsWquBLajkl3e;AjwxV7}A!*pTq_%SZxP#=Owu<4Z)CQ<|)Ev{`3JTxonhA*USEryQ z(ib$}fLcKBn^N1H+GfJn!KE_qHGKsz z$*rjEOl@mwJ5bw(nwb4>1xBAv?F?#XQ#+H|S;857|8gR%c>i+L&M}#DshwxU`NFJZsHy#@c9G%5 zhL=#glG>%zE;nHvo=nLVl{S;PirO{Cyjq#6VN69B^E&ElQ@fseW?nZ?yOG+1)NZ17 zvv5r9Emq62|5I9mwmoXMQ@h8!?l3g7QC5>xdLc4ombSx3els-QG3avkR>2V z*mH*CsZ}krit9y( zsr}3FZ)*Qg`&ZJYUWU}C)KK-Qs82(EYNH2HXL6Zosn0-tI_mlfKx8VUJ|lIV{7=G} zh5CxrXQjS4_1UQRnZntr&p~}|>T^<`tCH{Lu09WSS^v?T_Bey7FF<_=^?vH}O~Rjl z5_&=Ei%?&P`ohYT{i24{7p1<~t(S|si8Ta!zD?r<)Dt5@>Jjz0^6kmAgfQQ< z$7xd^Mm?vlm#$OSmfXLp>;n|K6qMnK`fAkIpuV~q+x@i7Q(u$%TGeAsQD2Yx`r2ESzXA0Pbsl@v!TsYdv#=Xe-<0|$s=Vs6_06asNPTna+sGV-`WDo; zG-4~mt*eQR(c4lVPJKJ=^7ZXC>b(-`I~eXLX!>`ez6*7GU+C)JmHIx^cQfDJ4fmkF z7j?D&;xui0SA_ZQYq+1`{)PuklpHJv|Fr}TrhX3fL#UrceFXJ0sgI<78ud}skE4Dl z^`oep{jaP2r*1z5sE;=KNVR4*)SA=&1yDc6=wmA{i{g0dCm137eG2-Kd{S{XF6538wn_)W?|b1=KI3t{47O zzsT@nLG!wl`c+0$6?ZxHE6jPNu6T^O`D@(bpPYXLvpJ8>pL+uiq%Gm-;Rs>04FUDrso&A#+*$cjzZ?G^>i3|{Lps{K)bFD{&O+%BK>Y#32Mr%GeAw_2 z>W@-?%IL?aKW@Ym)SnchZ=|lXDWMvkrmi&gXDUwL$g$L)rT(@tpQAqB2(|x4NDF+C zx*7SpjTH5ljsJ?_t9_=K`s?B}&KuNq2r&AszOL^(I%AP~lo??CJsM(m%=p(op#D4c z52cGy*FnKTeN6om>i?tuDfMrue@6W)>JzAcLH+Ye#;WhjN$9UF;5UmG4fz~U zX_(76a~sanXPRmB^?U~#e+Z5FXv}YPf451io&_eM?G>Q0FpWiMENzmD(papgXK@;` z{x3m>8ZK$LROPEyrLjz9H)t$NL#JCNusn?wCfXAkE791U#>zCdqOl5%EoiJtL%!Hb z@Bu@|(52y7@EQ$SX&2u*jfTdhw6AtRiJ*#|Mo1%~5vx;{>L|u4q9T>zbs_|IN9+;RZA|q_K(78yRja zXp);6ZboDCDoBrS=B2A-YZ|-K*oMX~CcLfTc7{3x(AdFnN5kPXcG6zc*tt7A**k)H z?Iu_;Y3xDca2k8k*qg>)%C9VBV;>ra(b!jRv9loi(NOzu^Z_&uq%nfVLFPQT$3Nsh zyVn>=V^oiSXvJ4mVs9Hq;9o&wG>waB97*Fu8b_I)qYaNSJl60yK_iZ*aY9AQl*TwG z(Ky+NQw&d~aUPA+Xq-*sbQ)*UIAfwbjk7AcYo#0K&^UJ@hsOD);R4Z2gT(q2q$eS0hg2_K7kU{7xEoRU8_3(-8ZAZ&$vn|9%?J(0G7`{PTA# zjRyrqfX2f#9y8(*8jlL0j|x0a<0%?X(0H=q2vZ0ePt#CBon6j28n4lKmd1-To}(e9 zG+qTN84dD6Px2)iuh3BAKZ$SG{tJhO4goaYFnqJGtN(5MDQLVy;|t@zOGE7cr!?ND z@ez#=XnZJq74FJ^OhYIClW;y0Cyfa-KA)WbC5>Nbd`0628eh}+j>b1MzMWh~s?3c4 zfBJu<@jp|jM}SpWmH%APG=8P=hjD(R@q6VfUNpr1i^2bkh8ln2=%WGuRi|f_X_qWAIlp=F0f1OfElwAL2XsH4||0 z)&5s9_;vgyegog1JXF{Io8}0=jUVHu_z8ZiD_Kq6G?CeVBb}e)3#XV|+c5mC@K?iM zTln~^vZ-BoZ{`!;qZiv6puMeYW%BE_&eb5guf&H@X7V;jIZN`Rf?#RfV<)EjlVnop7?v1m-^zH z{WnSceen0g-**zU+JR3<2t8^i7608{T4gEB8mxlbOMgZUq;mlFC3z=S`;|I#=Uq!;6Vj{im9$li?& z{#W>-=WG0L@xRfq;-y*mt|AOIQ#x7lf588d=EC?t;s0e~|HJA)6 z4I;FrHbH+)1HOA)56^G`mGi{kBE3LDNU3Lo$OB!!D09Lsh6mL^Gz@rkV6OE#s$#nV|4#<}_EQX*H|P zf~H*lPmX`VszLgSxF^DH5ZKHKmdn&;BAX@65jfiXQ_83L51d6D78G;g4J3C%0a zc`41ydXh24peg{n*3uGOV=3OR#cTfHvbKXnyKIKTkY7`F`K1lNenh()@g66~Ot6q;7n*DD+ zMpNy7cbsiLN%KXTPtknVWS*v3X?w<;YX2)qbK2<89B=gVhT1#&MvkTV63y3*qptuo zU!nP`dMW){&w9K;^BtOR_V{nne7mB1*?-sM-=nDmfj(*t>=i)ud}O{l1kn71rVSs> z&&)Z2=I1J}#VC&md`a_bBfe68w=?PxK=WIg-x(pw#ZZ4w^G}*T(EOd|k2HTaub&KU z*PjOY#hky={7s0ykrP|;AL^_|7Ff+k)1Tzp!ufA2XgBb~Cs+`@Gfnb)No>{x=y}|4Rix8;&Cz#W4F2lJEW&e+S z-c|cg&?jibV8bDX^L5KDu=#&r#$SbPEC_T6AXxandz(gJ3AZFoGos zngmM`tVFOh!Ez?EjN!6;Sp6>f+GozHnlPijxqVJ{Wr1W2u>n6p5R0R*&b=pRhl(YoefI7 zCV48sl0-v8c}{xX^s{+p!TB5nM`eF~KDh z!wNxgnTG1hTtRR>!IcDN`Gczn#Q1A0J%KR<*Al4Z?<&!@eSh?{ z81OcN=Lv2nc$nZ00-5CBNuZy73GR{)Nk-_WVci5GUjc&q2p%-zenb8KYcdZF4)ll4 zvc-kq5rVMpo>;;s)pq1}41kVwSBY0LcSIq8~r(nEl zetr%DDV7fjUL<&f;3a}r30@|6MZXP6L3LB|8o}!#-{ok$ZxXym@D{EL7;C0T3pZ1 zA%uB-P4JCslPKh4S@0dfPXtmp-%k?75Bixe_)$<_2ZH~pk{xV{82myc7U@^Q3kiNB zoR{EtLb2h05ZG?d;7_rQ1b?Z8Y>M^!r$_v&&d{y^4W}fWN|yw6)$TyTL4-3ALa1MR z3GE12IIYpsiHs357|tjtn}m&?nQ#t5+x!{o=1*hJMyQ)V<+14O^lPY}fC=ZW_=NNH zH1rYLcFl0G2@D~ek8lyfe!>L_=kLkO5MW#V!-Y(2;l88wH&ud!i&`v;^%A+b5^`W9 z97=es2`@>w6yZq1r3qIgT!wIY!et3%EcmeRvvuSdvhDSREA+kaju7G`T#0ZM3%l}= zZL8$1N;sg28F40|L%1HHOIQ$kgaKhqy)=V$Lfiiz>i&O1EjN8O8%r1xrpAm2gsBV3Dcb)(lXTvJe=QCdNSYZIxqN4UQb`e=ua2N~)Tknu+-(bY4G@Cd>~2@mV;l?@N?A2(hE1PMnI9!+>8 z;Zf26bW`iX56nB_S$PPL5!z&qBRq@nc*2t{mJ?ukCf8>$G>sDTD9^!kY+h)VR7m>t+jb zi(thhyiL3aZzsHq@D2+luK+UIR@UHd!h4N*PcN4HDqjor0HKuagM?3-+J^`qCVY(W z5yD4BK!PZ00X|Onghtxq81pH@rwOG;NMn1Zic$EcS?Wda2&$YWEJ|KB;swJO33c zAexP6Mxt4WW+IwdrwX?1R!694R{d^n`xS|1*D*bsgGeX;y76z~C-q2<0L())m}p+2 zzKMJhBN{>^Gk=qp!BM`yNAnXcO|$^fVnhoPEv$w;TBtjAM2iqDs&zE+#7eX{(UL?< z5Dl&P6%s9_gh>)DL$o5%vP8@EILi~QP|@1bG>VmoRv}usVrm~QL=Mqf z;zi^Vd3wS(su8t`>U!QhY7hlhbiSdDu<|p|C?txBBGoA^ME-FWB}6Gv&;Bc?YyZ2y zJd1K7DVEiU3OV+rl^98x=n*h|M(s?rx+P-`!!=cH_5bT=ZK8dN)*;%IXkDUBh}I+8 zm`Kn65^W&ehiF6XL`rNV0fmtJ%~XTT2UPQBL|YJTu4ihh%TFSSeLFpr6m3Oh=f5I5 z|D~L5bz-B$_C&iA?Lf2>(T?2?9u3!9^~91yqIMUdEw#H6?N-r)cNnJ$(DK}aNbNt- zUPSvCv3K9gQwq@wwI9(4qWvuw2k7xD&5me3$cTe`96c8xv{mIu(=&?bP&x3fzg>$C z6SSH-f_N>W(L|>a9jSkjh>kKm+VB`bBaSsZ&hU7{6AVu@Jc;OJBTg|qRZwGBZKo4U zH#vjo6{0hVZXr60NczRuL>CgBLv#Vrx!UWa^9;`y)JJTrpqz(_F0#a5Y-s1dB0K+e z=%>cHoM;S@M07RLm0EGyVXx9lmXx?gk5C=@sp+|vNG^O9h1U_u;*aUq8o5y%{GzA)$K)iGY8FNwbD9_j6t+c!i% z5PfUK{+*x^|3_s1|FTLkj@X2sh<-8pe?)rur#x0ivi>i%{99FnM*l%PHPN5MQ&`dc zMfA6}0U02oe~A9AXiZ@}rQYrmPgPA_j2_fCa;&B~hIkrcS@02hTEpoCtw25`oml?=4*4}lZdw< z9!b0;j)jO zHm9@)z3Hf867NC0Kk=T#`w;I%ym#MgkM%$IPyhI#{o~gs-j{g4{_&&w$6ssDYs3c- zA51LiIcV_J7Y#mdoIY1yM0^PG2vI9e)iC}a;!(uM5!?R%_%LE=ONSF5Ni6n%w2XTF z&o3#D{Mts4_$YHq`5e>#yx_5d_KdbX9^W@|EU_H{j!)FGQs>FUrxBk*d}?KFM6!C( z(5@GsL3|nUnZ#%7AQj76P}SzoA-a6~!7GFiO81dC4a}ZxcEM-21So-_5#J3S&XIYTuakCLO5Z_3A zQ z5%V^o`Qv7{s3ue?vTh_)Fr?iNENZcaR*J)XA0lentGXIwe#!or}LE{+{?d;{Vff zIBJj#Mv_h9ABca{{0ir&L6*n=kxWJWGx1-UIGK%Pb{QPC%C!A_h}>+G%u6yC zi7x)=Y13pL^Ey!H)kz=80wjZlqwPEyVrW0=P5O6-}}N$%-;4=yGVX z5=lt1GD(eO6%vPJRgwXXMJ8ABA}Q@5aY;PsAX=3&$V%f$>Lfl%Lvtq_nGPjQl0cm@ zwn{+h{z*iVlf)z`NkU@Azfz*|8A)4eM}lZvT2>vBVI&1fsiiF~r|S60Y9y;y-BXlX z9@ivUmqcGobZAS~wv4Q!>$=wX1@)+YvcBO4h8vP>WW>gTMr_h2?{q4*8S8&evN?qm z(H6A+k7P>{$@NwwTWj-BnQaWWHQbKmc#`c&Mw9G7asbJWBzuz#C)u53Cz4%AZ2hPD zvud)d=25F=Hzle?p=1x^>`AiMyydIPFJC3m7Xc&oCE2e>?_UX!97r-!`(SbqiOv6& z*8QI(BLq$1D3ZfS4i#Eo3N+HgtuEx}kLon~NPW}M3Ob79Xd~oiKYPDOjwO*F#>rI= zCm8<(k}FB1D_uZx63N*lCzG5`a*Aq@X<>3IiCq3L#Clo9v46oNXOf&%F$GD^=^L`4 z5$BSe*Q3u@fgw^a7m{2;auLbJVimPB>LbnOQj*Jrunb;q(OscUMRF?dt;tnZ7grnq z8pAOp*OOdp-S#@|@{%yKoFq3G^Tvuvax<;-NN%CEyeYYrM5_L2lG{n{GtM1`c6~>3 z*L*`;BzKeCW5m7v&rg_d=z&7_%`IuZpF|AHV~&!zT@&5|qb$$CBvlA;~jxhk!JAl4s5N9LYZ<<4Hassnot;e4YI3)ewnP z<;x^*lc@bCk%sgJ$!kWxE+`2w`c0)(z?Oit4Bs)QE&eF~J#)(ce@`&tLy{ki_{i{M z!%s-;jV$?$4w-hW1gQHG|ORoUyC3H8ZW*Xw9Mp-kP<_tTvL??6l@I<{Tnv#9XxI6B|ft zZd&sgXI?=g`V0rt8d7n3LHcRUPisk93(#7`WEM1B$Z%mnOU$CQ7B^xsO`gr45%cT$Yxu|A@KM=wxEwT7i~FYeilEZ>`jK!q&7_9=yZ8v{o_sRSgGd zIXY6yq`Kv5@MQRECtzCgZKY#GU>MSppTFqRggFyhm2ZnyYMe~a zh&HXfqIHQ+Rp}>STBYGIRU+d>Yjs-Z(prPo2wH2>+Jn|ww05MmHm&t7!IGDCX|312 zVMazbF>$R8Xl+DmLruTjQ7?lR>?(%PJsuKydorQueUHd?m+ zqcRd|JCUKay`g>@=*bMHwF|ACjK8z!S1;-Bt)BgF?M7?&N#ysWwI8j$gkwSWrnL{P zePwv97SHu3u&w=R9YpH@6F9Jvlzf@rh&w&G#@`htrbF ze|obNP0x{)9$MXB0Mj~#)@ihkrFDww(IJ4=@wChiv~>NS)=7pZ3r^HV>(t6g>vURY zo4^^g&ZKo#_uigv!p;#dl{BZWBDT(_^&qVaXkA6?LRy#8y2xteV#7-`Q}X}r)}@A* z2`Wdc>v7P6s^Zh)#HXw(0WoMvLqSjXkI~rE;_wb>%_CDx~!-t?yL1<(<~|w59X@ zKzbjoA4%numNd?vXvyXuA%3Ry8!fy4S)IRXC?$TU^$)E-jQ-P5e*Pu=zXjFWR5O#7 zybBB=oucB9PDOeI>C~jtlMa#}rf6eGF`UM5TGHvd%T}`1m(D=ClMcb@jHEM>E=4*s z=>nv)kj_pzt0ab0b`glt(0U+u4kx&LN9jN0wgKiXkrnOZbB+6`AyPIN!9+7Zce%tsoHGq^MlFBmCekTmx!cJCp81x(n$Z7Is&|-3)hE8F`0F_5M%Ny|js{b8kVR zO=e%i{YdvGJ%scCQn~Z(Q&Rc=m%OugjF$V~lqMZPdMN2g(ox#@g(-Se_%PDL)oJfL z#u-g|4(XAkr;r{+dW^+#^x$nS(Im+mVtOp;amGBJ^dx_QHZ{LggH-E$?Dd` z^i)!_`RQpENy^>UZ{-xI$kzQ@dzDAwZCMzRIuOpT0Tu&;? z{vu`@W3*mwBGp|$R!6r;MH>0(}wb< zAhg^9Cpb>f%HcWES4qc{zD%m$TSYnP3x+Qmz9cA;Cj3gTL%l}&HtFl6Z^}{z=^N4) zv>w&&y`^J>w!L>q-zR;S^t~#SXfWXqG!|Jm)>`|BRDyp@`l+Sr6P3{fd`9{O=>!#1 zou60wNxvlhN@c41y;Z|Eq~G@P@*U}~r2j|yi>3B^QnCL(nZh46)brmMt^aKy{aH|% z_UCcZ-$>2ur}_$DiTRWCFOAodY6|}$Tb%S?viZoSAe)tJO0sFlrXm}pIBwdvo4#+S zNH#awJY;=j^Hwt5AM$2{EhR$)bz(DeELlI<{A3Foy?|+3kZhrDC>1dKpDk)$Iwg=t z%8G0WvVd$T*$QM!k}XTN6q!x?v!zw0DnQM6m5b%OVWoj(E0Q^6D+z~eWwKStR+*T7 zOUXdxBy-7XWZr+iQsY@&2mP!;=4(VsHw8^wNY){X$l7GFaT2nWO#c7XEm$ktOap2m z=aZzfARA`oBlCa#_GI*GWUE_})|i+EvbD$_C0m>9da`xMY?n^9F4=lyo0F|iwlUcT z+Bh?@|AJ!wtqa(Fvt+XVBg{?7HnT52`rU_Y3td~wwj`4h+lp)(vaNN2%WnRZE2Ard zY&){;yG)hekxWz#C)?pE}$c`pEi|iP(Q$>dCSd%%9?0Bm+oBwAglATO;lBQGEY_+wVA}EqZpGJ1N z8q#VNI+Od~M33zMwD~}GHrYA7l%Gp>p_Tc0hUb%ApgL`apr%O!UQBi+*(GF`lU+)7 znXV&2{z)*{c@yYlb@hlD%R0reI}DHLe%8CVPkM18K%&?;7eW0NMM! zM*g8r9^_GhkI6nGlkdMe+aZ&QhyK>b=m|QMWcC)|JwWy)*^Nnauyk_Bhho{~)W3zcR`G6jWce zA%D{zMD`Eusm$wN#r72XlhF2*-LE-qGyb|FF@`p@b+;#NIRz#tSHM}*lI5ND^v0Qi z_KdV=Qod|Ys_yu0&q8|z+M;to+OyFfOnY|PbL%X$J%{0(hWcwKEw}bOLTLH4=QZrp zvQ_60+WoZU=fC#CqBFpkXv;5v1s71Gr^G_Em(=#qUYPbGv=`NhoOJp2Vzif_t(!lq zE>F9={-c$mSzC(s(iY1yw3pM(D(z(jtw5F+bE<=mj0o)&X$Q1dqP?nxTG?+f_cKozsqJ%ZC30Y0Liq4{0Y=8_-tK zP7O1|wl)S8=+Iu1c0qd$+9mDPv^}?nNn)%J^#1>fuI9AHTuV^fe|sH@18A>HXG+@Z zk#9+RecDnK8_<@VZb*A8+8fc9ly7Xg+eA3B@2k$ezSKvV`_sOZwq$LDIi)bAOb%8Vi-qLo!`{u1>Pr{?Q2!^>%3WyBS4FJ-IE+^oM&i{>v8M~=(b(~ZSl%t z8B@JGX+4F6|n|Nm9xzmiW+`!{3$Zup1cpX5^-@fYpCjrd2dd8I9TF4hCUqY3SIFo!Rxr`L+R>CT__4}5KWylA}mnC0`d^z$JjJdp^ z%4on9mC*dIY`6;fsuf3NMy^ZlSnRH$S9$d!t&=y%Q*xg?A#ajL`@DlXr~B=RM*#ECPU#lt^BXYZr+uq-GZ71JV&~)xDzDC$9 zK)z>>zc=}Q-?YmWb#wVb^hPW zh%WwAPVzIzFCjnEB+nwhfLt~LNmyO~H{x8w^T^MyI3{WTZ_4feU-`u%pnX;fQ)d1$ z+sQ8@e}VjR^83lJAisnBO7dICuOh$Rf?rL34f(a?I&Z0FFUG&FJ3-BFAir@E{!QdM z|L@Loa#{QtLw=i~?A`4uA(#FC!k5jTf_IbOOMXwKP>@_UfA%K8`2*x~;8(;RB!5VJ zE9K;mkUwt1kCHz&iTo4f{4??|&1(X=EdEGzUkFxW6 z|DOf?xfkS@o=#~Df0O@C{-+82(G!p*pl-az|HtrO#m*FhT9Tcq==9T>x^KdEbOzCx zhYob6lixYfnTF1^`d=qqZ5T^udO9=cRw$L6QRmo7%tU8qI&;yPh0d%hEYn>LI~$$Z zEz}%_y8a{bin3VOnOpaU9k(%^dFk}&|GdPQYpB6=hR~T$V-Z#Iten%CpU!P`7NB!5 zodxM^NM|8B0iA{EEJJ4zI*VIM7NxV8Mj?;bvCa~7mNsIj`7TLksme=Ha-@l0mQIb% za&%UfML9al8?IouBAu0l&~yr_?<$6?8V(pbhOVjdRIQ5DEnf{fe&4)mk?83AzU0X0 zkj`p!B04D@TmR`K#&4<2gzb#Z=t!>HLMyZSn$YQ(@`6rj(G9DjGkSG8>q(C2tU+f@ zIx^Ll!@uHOTeGReI!aU})8(vhd|d*v7&kJM>e!dgCUo|qvnicjjJcU^*z0Ug$JYNl zThiIyI9t)#n$EU#wh_&mPSa0E%ijX-KxeoSJ667Qb~2}0&Pv9dyVBWB%o?5D#c7;9 z=Pv&-3!PL=Ha=^RjnvLtC~%OlF^974y8ZD$0Xk#t1=DARK&ouiC6 zjE)@sZPJm$zgj_bM$<@&axDqP3HAaH8^>nVHBOUx|I@iee0xjxSf0KCv zoone_r~4RX$5FLsvU3BSo9Wz0=cc~7HxNl(!`HrZ3!Pglof=#V=XMH*&K-2VpmQgk z`{>-&H{o77cMDq7_s|jIUJ*9_{dC@;Bi-QzIu9D>Av%(yhv__N&PV7xO6PGpkBL*d zOoeow=+dhEDLPNndB)_$dZ-wkasSDy%yZ^D-thTJ!oEo7C1bv9_zIo3=)6kj4LYxx z33^?-b``Z+q&IsO>1}127oB(Md_d%K!U{qXDhw()r1}#Qs-?>*vag z&aZUDs!Jc2PWrq0D*XqYKP>@&8U8(qo_{H(r4ZwPAjOo1Q&B)MHN~KLU){f&QtD1T z+2L7CQ#nm+I>YHHWb?npQp~7?^tEDUisdP0p_q$eR^!ZOIJ?FxCb5{qa85zv&rPum z#XJ=An{!@@K8hh)M8#kYqQra@{Yp#2kWoN63s5Xhu^`35=3GdyC%g#7q7;jDg+*T4 zNwI_}A4;(_#gY_DS=+GDK?2I7GRsmdr?dr8ZJM1GC|0xtoBda@l_|{l7iRnmHU28B zS#v4Yr0|TcQDhW#iio12eZR27UxjV9FYGv2q5uEt+JPdr=n})0VXA5?D^av53W{7g z@}J_OBdD~du%sB)6I;zVs~fJN0vhjH6nj#vO|iZuY#qaO4c8O2yl+6U6UBxUTT*O9 zv8g1CVq^2#M9bv(y^Y?C!WMrDwg0`k*otB}#nu#(m~FI?7rI^~gxZQ?dx{+?cBl$g z`RU7o`gWtfHghg^q1aW%$4Z7`H;Ubbrr1Lwtzt1@{RBX zV}#HT!8VuB*S+F;U2u@~#^MIU8x3!wxS8S>9c#30->QTSJruXI#w-+fFzhypJ1I}2 zxQlWy#oZL&QrtuFD#g7Nk5k-7@eqYB{##Lqo(CuXzt1=iQ|RKq(T`F*CPcNFhvEr} z7b%{kct-b_6;ByHZ8)~Om#i2^F`nXCGe^$}YVd;&l=FiWmChH0BTf@|iQ;7;jDAIy zQhW4k6t7e0CV7fCDBh%ao8qmWz=ss?P`pp^u5rZq-XvZ!{})cBN{EjszNGks;xmd* zt3+CYCs2H0dHlRep~NMGalWFE$-hJ`lmBk|DZZnGLh9>p3aLR|z^C|u;zx>~dU@9m z0jB(C^Zmu}SBl?^_}%ai!AdQK%>NIp3ZKG;i9#m-2U1Q!IThuU6ZcS2>iR$BAPrmX zrz)qRoQ-l?%9%93rEUSDoZfH-${AI%`Y~lWb2qwj7Kw{;)^3dD?3D9T&Os?hy(H2( zDd(b`Ta%;}q4Qyh%beod*Hbctazo^VX1P3NKjmVS^HVNp$y-2!Sa8aPC>J)Sq-s$i zq{gc=q?C(`4CNA(OKVw`Ln)V}T&gNV)leOeEtjEOwil_b&ugd^C_9uZQo5Gwl_*!X zSXQBQj98U&pwb}NQ{|agjZ#1OQr0D3l#O0VHYsz;z`R1rNC^GPP}=;zwE2Ij^MA_J zP(S`s>ioabUlEiA2q4@zDCr`*(nZ$`N#rP_b1*)4iCyA`GE0Tazz8_JD9Ld*R>f;wZN z+}_Ymij_MWJzP+YOt~}Vu9Ul|3AJgqAmwgq49nfS*0S7_@?grnDEFt_+X{0Z!+j0+ ztFmnL0ixCtERh~m(HhajzE+N)97%aJYM?Z!o;k@FqiB|LJ}Gu}Zm(@*&FGDep9aJ9;|r5~pz_ z!S@((FXe-j_ZhABpYj1gt!Tk&wV`}i$HDRuN--3V3g5(Z35fD>$|ovG$|sHgROOtA zl)3~&InMA|!{;c+Pa^OF<(HH%Qocj^5~bLD(JVv2n8EUI5jjXwzCrn#1$fRz&PC(7?Be>CCh7r zsrKK9->V^>@=u0A`4_{cF#g|^|4{xbv{)XMsZRY5o04JwPt{vMPf{dZ-`Ed#{SOT8 z?(Qz#)zwE#>?rw{_ySqKOyS@zWzPQ8U_C?(6p5;5I&pnZmkx{8Pv$CqQCZI5p zzXDg7(9jJ+84D9rn1aG26egoEsp&l`+<+G*A6b2j*IQvq3Nuoe%AzPtEu4nJwEru0 zVLBT9;EaaYDvoM84 zG=*g-7@h7#VOa{xQCLCp@`m193N}qx)JnpYDQrk#6$n^KKtWT0n{7m)NMU0|Z6YlEBUL03fofHio(``MPa5M!yC@k14;M+m| zbFM(d(Fp$!x019VPI7^mihp2!QtP;+n zaJ7>|;d}}gP`HSK_+RfWDDA}*tYR;taA_#Q$llgrE?3kQ!YhT&|F&Z+`5K|~zl7^3 zTuNN%7c?UWeL>*|3V%~D!qI!4g&!&Wq>VvK_%nrHDEw+p ztYhnE7FA9GQef}BxyvN$LisMn7kmC3hCkRCtWTrR~#YrhbagveOH=sDNp?L`B zu2?oaSH;OGj-ogP#cAYfN+~r36sHbv6G<~I#px)DTZ+?DwEcg(uH8<0sU^=m6lS65 z{2!tyn%M#rXQMbT#nBY!q&Pc8A5C6f|2^S|hu|31cxsa;e;PH{7eO^SYA zgof z#^65_T)&-g`%%YSGt}%L&5om=+m51)kKUZ(E=1NtcBOb3#oZ{LL2-9oZV!sbQrwf` z!3tVI_NKU>AMkE3`h#p5aZwhu*H|BG*nCs91vE7|_CwuvV>pW+1`^d`HI;-wTXlG(+=OAJT$^)CEywidy6sbuUo- zLFDjXia&;Ys#p9>@h@9JQT&DCugb=6h7x`k{z36i6NY`#Zvi}LH5`pkG(FJ-L{kw> zNHm3a=4c`!JYh7kaFSq8G^v7<5lwEwz+2?x??KCLG_?$-5l$QQMAHRJDQyb)H<4X; zCKDvv_rLt19?=NVIYgs~8bq@atwJ;#(V|47iRK}iooFtiIf&-;=TmlQLpXIdn%h6o zYu{nCBT}Pzi54Q7&klMen&1DZ5-mWakN-aIr>%~9{5hh9i54*fKd5%A^<=Xc(XvE~ z6D>uw1ksWnYoE!s@ASE!rHPgaC$IX2wz67Uj%a0~<%#^`zjkmd(Tc*A+)QV*`nBvd z*JxEDi(QS# z5p6@{AO8vsC$sH{whs=2XQCaX-;ro1KR?_qXU$~i2S&RP9YC}z(OyKm5$!>=yTw|$ zEIHAhE(~(DH_^UIvX8kk8>0P)_V==frtz9Nkmx9)gNP0#I+*AX&y}^f^(b$(!-x(a zb=O}?d&J;u;mE)LD9zDCClMV(beytk{q|T-u2>lyPjmv&iQ(#|QD#n-@hQSnh4vR< ztEkicY{TdbKm5>xXA+$y&DlXC`COtGh|VLrh3I^utB5Wjx>)uX68VS1L(zyXA-Z%Z zUq*C!$dw_{6~ZfnhUjXd>&=qr8Y1iD*M(I6C2?2YKy;JJa-$!OvbU`|tQu8DkG+-X zA)@~yx`*gCqC1IhC%VHD`oOVP@D{m?NFRTYV?fz^h_FMJ#Pj?CXWpl@+61q z|7+2UMBfp;MD!ui%S3Mxy+ZUl(W^wSc}rR5?Nn!Ph&PDd^b%RG81$>RiQXf6hv;2* zXkBH{)!!%jAfydQ^dF+ni9RCwL;D(NAu_+BcqOPx1@Vug+Sgl%D8!qQ8j#Ao|n& zgiG6fcl0-<@!ZCm#=hHrcZ1RdlqRJ#AtfkHWX7YTx*1TKn9?MJO1R69%ce9Lr765Y zOSb>xJ}FH}X(|)sa4K)v(lnIcqBJd~?Ur?Q(BkO43w6nG$W;XD9uD^cApVS zGgF#{(rlDQC~5O29BWhZ>DW)#DUEjF@Z$|-KL@2bhw@y~%sn^>r!+66g`}BJIKPrF zAbCMU+vuRQFr~#PEh6QjhX3%qIHe^_<4V7-6>V8cOWQmv<7KQJ{ou{ga;k^rDXl?i z1xogk|E!c&q_h&Hl}+~bu-%@NRv9deJ$5xpc2nT@!a+%CO-gIYY;EB>17lCP9;Gop zt(VrPv;ie+oee2z{a@OM(#F2JC~ZQ?nBRoL=;zMz6^L#wq$yE~DOD+H{a>ntsDPAe zl>Gd+VKAX&B}ysjbhwg^fGhbCaFm+CGo`i#rRfN}Lw%pp7L+y}y4sAA|7Ca>yCtP< zC~c)ATN@6|wx#6T{*?YTwA?;K8B*GT(q1E9T4nAi+(|WWt+orL-6`#=;BH~j?z`os zJt*zz?WMP~To~ETS2d-5DBVwKUrLu#+K>Afgpm(?0Y>S>p>Q&# zb0}&5r{w!Tlun~`CZ*Hu_JPtFgN-bg&$3u4&kjmT=Tf?m(s{l;TRLBOf!l-6hoezoa{?(!J8$XE-d^ z1C*Yo^dP0D6!j3LhbcWtNjD3nN0qn7gpX6wyHBf+@#I3!vvyOJ_8CggQu>&ZUHT2> z|2axyCD?#^iP8&{wDY6?dYfJ~{Ib%%LP_g?+sZDzM(K6Gg#J;h*f%M?Md@8iZ@b>B z;+Z&B%YFZTH>j^O5>@Cr}5!zN$g(6(3R5>Pft8U80O~6M|y0JfUlI>nTcl!mymuG z@$AI2Drz?2XhZucb`IisiRUDqn|Q8qdSz)I4<5c8@q88x*CJkkcs=3;iI*o{hq;_E`77D)H(j6R$St^YI$N#$u&ki+F7b>kzMNg6C%twLWo$cmv{1e13^H zB;H7t8@pN9!HWyTCE}v*P{omR_``3U z5jTnR!F{Fvc<2xM<2Lc8#2wr*XKvm??}8G@lM3M5bx|!wuj@FcK#pM&+eY> zuzecuNxZNBk1sZxy@v+-1Y^&kO{Vew#D^0fKzuN<^FOinf5I+Jdbz`S};$uTv;^QqFgZ~o7Cla6HN3g~x2~QsEHN~g8 zjq6YIym_|IAijb4OyUcP&mumD_-sFe#mo%HS_aM~K9Bf(uLu3-l=uScQO+vyMZ{MT zUrc;C@g>BUsiH0o{mn;>H`5h9v^~RDdOp2xUrl@+@ikuOw&aMf4J#Ak>jy6vdEdT~ z_)g-Rh;Jdj*#~~$koZ>O+lcM|zq6Xs9oklZe7h=He*tiZcM<#FyNK^5zK8hU!9OwM z`-t!NB;)=$8$U?=DDgwY4|}JwD9@*z|KJ0}EY@1zXni78J;d6EG|lqU@%QNi~p zPeIwXab~4FCFQBS84h^0)yiSJxI8oE5z4c8sHv^WJq`Va6rGw8wotKxTtgZg?a+H1l-+q++5r7-m zv{+t=^2(G~rM!xd7aO5A*2=3<_RD#n4`a5xCglRy#^$t2Q>h1C(o_CwN!%V1sgE z4!zIVrgu4`yaVN&a+h*b&Dx^eHbEEdm|!g{eUI{%l>3x7r@X1Z=TLV3cjs0>5Bi@k zl!vV-Z%27+%G+Acp}ftY|9J2Em(L?Mzm&IEXB#t!a*?S|(FH?Sn@@te|wV>bK3n{-IWLduH z!~Fk!N%?K+Ur~OCnzh2aRE-tiqjD(a_o>*B|A5Lgls}~WnNr!g5Z6=wi1NpjKc)PM z4~_7?jfM&3&na6?enHuMe(9ME>$LLMlz*c94dri#n(s7lzL(n{gj!jZfAm!0=~ek> zlRfBd@hcT0@ZTtl|I5D{?I_is!G`i*ltum(k$*+xUvcuMGNC&OPk1X(nS{#3&K2Q2 ztIDLZnM^pj(8n4T-~XpFm2hf9OCot%DoatBj>`O0rl&G9l^Lk`yTpo}$uJmGnT5(6 zR7Rv1`B%g_mD!{jEu1|VOP*6WSI|(=xj>b9sLU(&77bn3lY~EwR+gZ$qy=5$WxtZj(o~kAvV!cFrLr8A<*j$QfmV;wuP9u}_{XWH zvI>*wWzE)@|G=AXEmg3B_5^ICcoIDVuJ|Kh zD)#)>FcbPM02Q78Q?XY-O>P^?XP3(MRC-i4r_z^ZQ=#*}6J~Uc=yxir)fId4S4;Cio~DWaeQT5sG?*iXHyuL}5tfaVjrUc|zmmNh=_g zr@YkmZdm1MD$h_EJ93oe?OEaf6Fz6C+{v>3i1e38maCipW)&%-NJ-lXy>mDj1f z=Jw&<)}!7~avQU6nIPl0UGO5mOXYJa*6Ti_V*LM+-=0)Hkj;lw{^NPGOFT3im5-(T zL^yc)(`IX9F>SMoA`JKui!yff#*tYU5tjgb1p*kMb3A{Mf@dtC0%YL&!b)rE_RVSu8 zsb9A`iC=46i&Q74`X$vVs2)poN~)_+or>!0RHvpo1J!AiWLn{LRHyf}wl8te7oi|j zXQVolYi!M2otf$^K66z^WH0`&dU>ge|EtdbgITFM2i1kB&PjECs&i4DM+xUPl>NL^ zo&3H3hgEBJ0Vka5g1!I_{|c`zOm!Kmi%?yf>Y_^J6M^}sy0~x&;gZ6o3_W(>e_40# z8Cy=cJXK8!Rgr&nB`H@n45_HDN_Bgxt5K~{U7hOs-kGawP+ik(WV{yDwI!@0^u<5b z^$bH*Q{8}Sk?Mw23sg5!?8YIMs_hH;^fWAKq}Y-$7CQe^tq8^cVdkyYsismkgo&Y3 zSv8}&Db<{+F~1)vMz!Tpp652zj*PoQjlBY7df)$%elx1uQnid{mWqbZBpe}%pX%{cZ=reu)r+Z~NcCi@C%L`u%c!10Rg3oOsZ>v+dJfgoZMe#pn4hA%R`i%lIOQhRozTg zuabPV(gqLLO1@5beMn*`vm2@2MD^yN3|m7~Z>9P))&HY<7uDOS-bwX#s{Z9ipGpD_ zlHX0$|1~eq_fmaC!hKZ5|J4T+^eKSqL&Ap*!GPG++eunB; zs?SpO7jJCg?Q8F9Ur}3v>etjp zQ~ieOpH#o4`V-afsQ%Yiq1ErH`pZ9|ex$KifUcC2pQ-*v^_LJ!^;c64Wt|04{lgeu zN&cc{L~ZYGT}f>`YEw`fpPKFVSa1Sr6H=Rm+C=WNhH&EG%22^cg_8xPrBZN8587U( zJDHl=4AiEf=I{Jao7QT>1j*C;+Srvdy1lJSYcmOFrZ$WGj0na5HSvGV`9G{$sm)Gp z9%^&=>|e82fDC>ApW58s=nv_G0=0SVmY>>u!uf>@3^oC33sPH1{udT5B3xAH{7-Fh zLvtm0Nu^y%xU_H?;j)Gjmh&mbueAd8$*8SJZBJ?|QA??quS>j~GVwt@1qq36v~c|JF$R*tH)_uRlJ^*T*o)e&)b^%!ob>xp+n3tGlJ}#wKeYpm38@|6d>C$uYX@1Zl!wUd zP-=%u&?!L1nYAMnJW_a+@Mz&NhCb43){e&pS^DFtT}tf)YNsf8qVObYC!1hDn^>ux zO6@{wr%`kASJj>2ROWSfrtmCk&i~ZT5uPhNPk6qeY%cI5gBrMq+Qrl^vEayunf+vW z8MSMvT`u_wYFAUUp?B3_5ok*liS`(-}N6j{B#!`Dx!RM*HFrsCK zA+?vNy(Qsg;Vaaf|0Tah?R6z_{-^e4&`W;X(1W&auf40z|DN!DY9CVjzy`(OX^1^2 z_o49-HEYn1mG%>A-%|UO+SiKuOsM_;+85Nmbhn17P3ia`9IQtdr z6Hpi7*C(Xz{6F%^?B2cW6H}kWgGS>XoYYYElT$yE`V`bN>Qho*nfg@JM^T@e`b^ZP zp*}tJX=OQ`CszgxsrwN?)CapjGMJgV^S|^X!IHY=Z3*hLQJ`-5o`fAkIpzf#FTKfj1 zz9#jx+&;8;eI4p$>g!T3QD2Yx2GrNLST_iNVybUwGWCt9Z!BRGp(cWQQF3HBGpk-n>gQ8GmHHW~{L_S|8>;5dq<#+dv#6i#7q#s{?TX08w}7ahXXq=3 z`UTW4Q8ivj{UYiYyRj9))?oHm_xh#Oub_UJWk!X%JlrWLZ$8+SldGw}LfzWs5$e}c zzmNKL)NO!Y?*?A38>l<^Q@=^*w*b_g|Eb?!Hzf(4M37!9q zR}B@scR0H5r|xrv1(oVS>c0M${BW?R{wVda)Sdj5_HpXw-%kGbxrzEyGJaa}GeVyN zs0ZWcOdeW3PhGP>-S&S%+LtU=#=}p*)L*6kA@$d&zeoLb>Tgkh!}ks9ZyLHQm;L-V z>hDm0Hzi!6rx^*O5`R^tBpJ>F?f2J`f^Tk1}CvJ#&>07f`4cxqA?>4XiP(6Vj5G?@F~E*PuG}~#$+^n zJkprlG_H4f${^F2I#^06{%=f2WBPws&hQU|nP|*RV>TMI$oYtHl+Zt!H?Z{Uj+SzE z;T$1~##}VkrZG2-g=x&AE6yvNPw3=NW7zi^3o5wKECsPvV-Xrl(pXf=#SA4ZPGbp| z_d8X=rD&``V`&=8(pcsn`sEbs{2we8T#?2~G%TwtE4YfsdInbghm9up#u_x%q_NgO zuWQM29UAM>*o4M<3OfJO*uYT2hQf`68;Ae;@{->cSV214_=Y|rRG+Gkc!j91Qf9&U9aBg@_N~tNJu^A2Le;PhbjLh-P zFm@{`w-#30I6!zHje{ik6d>UcqgDxr(KwUF;gXM_ah!xBX&hyO zWZeQZj-e6kk9ED$D)x99ng|*v(g+>tBpRA58m9yg&f$&If=uId8fT2t%fneT&ZhBy zlFy-WE)9`G<2)MY)3`|b3w&kLxX{p7BaMq`Tt?#(4;sld>@UF153;$O#uY(U!mDW9 zOyg=A*VDL$#&tBVHN8(#_FyPH0&CnL%NuFjWa~hWHCKT=8=3+dx0=0dZlm!SjoWG5 zP2&z}?sQ|@WN!ErAiubPHgw3N#+{$xd(4(0G`}qck2d15c~_ z4yApZ#>+IGpz$1yCu!(>jK)(mo>rqj<8wj7`9ILFIn*V_`aIrvp2mxIk3-`HH?SMw z#!D6)tYaIm(0G@|t2ExE@fwZS!wO2t-x#c>8gD81HjQ`m@aW2+{yiGz_I<^EK;y&U z{~sPcQk1{{BjHmz`_TA|mUWEJNvzv{L6XtGP(5)5><6FCCOAIQ@eq^Ok$nTT09Z|C)3IK^duFM8AvuE znUQ2kl9@>6C7GGToXkRE_9G;tylL!S&(@;WH3w!yQgJf=!IZ5Uk*E4-I zCC>kYKAFr%vIxoiB;x#JfkA&r79v?V6e<{#EK0Hj$zmjn`wGhcC~6CmSuNI*kSs;A zD#_9$%PF`F$+8|5X5M6Zl9fnSAX(AZ(w02j86_)|_{%?TsU;c7Y9#BDtWL5P$r>bU z`dG85u&__oCRxXg)m|1wvL4BXBu1!1o403{I6g_;{5NwK!8b3(j3NW3P{={;{T*evNcJMWOEYde-e>@ zvf2ON!xki44n1u3KWRvYKqCH6#Q(|uHlYlT>r4)mjrc!t{vXUW$)VC8W+>rs zk|QJ>DRllP5&0*_kU07KBpy}^iKdC^lCwz8 zAUV@t$XWe4v$P~6XDi7$Bs4fXeXc*BYVy_K;ryQqEB@sdj6Z(2gJY8weGYY>p$b<&qn?E z*EOV5k*YwmPe;c~*|g)11!)k>s0kgiNxBVC1bebQA)*CAbv zbWPILN!ReQShor9Q>AN>uI*%QHE(72nqOB%Ue7f)4SD@#CJW>!f|s25E~lA9(X^UB0deqcz>0bd1-GCSTtPNp~bYg>)y<{YiHw-IH_|(%ngSCAB|)%{UlAs})3 zBh~sp75}H=|J3<^F!@@|ct5u$6YZzRdajIB(&I@_@PcR*lb%R=lBFU&+0zdC$*H7Q zke)_*A?fL)=aHU4dgfs5YIKpFMJoPJ&+$~Yh)vHO>_MgHlV0G;u)wglcGHVUFD1R0 z^pZj48%ub*Tt<4im&CI+fW^U=4Tgj|rTaw>MZzFw;^mfumNbexMkMvH`yJdEl&uDIM`M)>F7PKpp z-cR}<=>vXsb#>B*NFVmx9(s_~gYxqz>C>c-kv>WKIO!99^|1JK4^MeeosIMv(&tE@ zB~=c?U4xfsEa~%}Z7Y;F?hB+ZT9EW5;mg8TgwFq#6UncWeoFcV=?A25lDF1ZFsG!ykpAjsHb|`zy#{_Kn}PHXvMEXb zB(vV~7uoovf0K>p(}d+pdyMWa^Y6dA!)zi25sLpa@qZ@%&&2=PBy$HSj&(9tjA_Ek}X6w6WMHJGn0*w&Eno{x_5`8$h=Q_&{BDR zMw87+HanU4-#F0D9P&&2gSx&YFS%qv(vh~Toa4>c2M?qRNi(H?rbRKE@Zp9{h+h$PPP}>9%OqO zH@Xx1d6xDj+b1OPGJ5^*M|LCG{$v-E9YA(2*@0w-kR3!O{i*+QQJWPcvn1!U)Y&?>>(>q4@N1}$P52hU{9m5nY%-c0Jh*R`%f5CJ3^d$nGY)nd~;QTg)5T ztz=>0p<8Cx+)j3?99w56{dA^VA{{Q*6ym>($BzwpN`>CXlklVE$ zCAT5-82MymkCS~)W<_|D>`5{k?@y6EL-uqipmP2!nfiuhUPG8{EZJ*h&y&5X)_j3X z{GW;cGx2}s{BIrD&;=jJuamuDf=}Yolf6au3EA6ZACbL7_O4sn(Dokkp6{?_Izc-7 zK=Ow|>(9RbMrNBoK4h%hDEO)HGeed13$owHtdf5q`-<#avaiW}iu5FQv*3069hq(Z z+m?ts^wm*j*ZrC7N3x&%p|ftIED6~!WLp1gT2%IaC!c`q53;|>{v`V=T-Q0uGM|q} z?r(m2Nv$D_1M>;VCnleW9Mkya!dS~EA$RikiZzO`Yvq%Z&p|!~`LyIyN;8#kYVv8! zp&MIIbIa+-N0U!aZt@J|zR0v#@|lD)3!VRc(U^}2M-5h4w!q70^Sd7Fz4`3kd&62c zpObuX^0~+tB%hmnKJt0U_5ZK1Q1Q~uPriW9aaKQ8KznwRFGRj5`NHIj{IA7T)#QtL z(LA?Hkgr0%B>6JrOOY?_8M7*~vKY(d%aSkW8XGUxRr2LcAYXxe#h@f#iG1ZC8%s9Leop0c22btW6yhOe+d4YVB z&=8g}@*;T@mU(XMnwY#I&prisz1hg}I;oNO$m`^pVjJWMx%glGS<0NeP2MDLslvT0 zDs6|n8|HNHitepXzBT!#$8>;MQl3zf6 zR>&dw+2l_C{6FUFf6(wIteo<&6?~vZ^myq8;eku8N0OtNdTC%DF;#rSK}@ z)#TT>x8Z`>s*(J9)%+OwzmfbFYaQ~N$ZvMbaGF7WE4kB!<=N6|2;5GyLVgF$naS@Y zf1ms=@@L8KCN~1Vhx|S{yw{zCJL~*@@&|Oq2Nm^@@L}O2!bc6m9xwUhoKkD$6Xs$-nTiImk*QB|Tgtcg{O>;wKX&svD zO1YkJeWCdO3HM_^Wp87eG0jbAmS`4KQmy}+kqo@O%v&e|&GKLdZ&qoZMzcn951MtF z+t6&#Y|%_;W;8Vg*f{dy=QNvxHq~TGvrTg=njMmCG_C`<-d-MspXM zyAE>Yt66Vln!8(C3478!K*p8!wkK=UA)htfQl z<{^GXb2aGOhtWKq=HWDtp?QS-i~pO>|1^DrC{(YrYt#CV*8fc>e<%X zef{r0*PKH0R5#OlTbVqa=8ZJZpm{0HGihEx^DLU@(L6h(qInKY=l|fD=J_F$@^&H3 zizQq%PY+X(7cZ3^&xxzxZ+K6 z;`~qZ7MfP%TRnEr1h<(yyy6|ScBFYHt<7lOMbpZAw^H3h)6(8cQ&iu)kLLZBT$y|z z%wIGgqG_-6Soe6C<|8!i&tIX5ACrg2g--~dGz`63!KZ0HL-Sd0kuXASCD441=7%)L z(tMTX^E6+g`2x)s1K0V?<^1q6%~wK?GC!)u*J!>+)A?U5`-bpMns4bsZx8kU{db!0 z4qN1XnjeI9>CnIi(Wkn^M>IdC`H5SGzUSBdjOKSVKd1Sv&p6F5XnraGUkSf()^pI z^S|rELb|0Xz>3qFfYxNRCZshnt%+z6YS_o1WwJF1Egk-2yPCGPv1i0B-~XXyQB!)+ zf=?f?PtclLIE@UZr8OO`>BBg+Y|Gn>v{s@u6Rmk^%}i@9TC>oaoz@7g(X>YSRzORw z-T87rLmiZ8C-G$t@S@|Uaoa$RcJZ?>$>aFTAx;t)&{gT zp|zou8woddn_->PDp=4mNz3`4R!JBSl|%k-RcX~+={HlM6&kc!v=Umn7iguv?`mVG zmDAGxzpmou5Qd)4E=G1Fah+IR8tynbs{s*{1+n{?{G}w+rtWy1G-ry9}KMTldg< zme#$r9+mz+TKCg>MDhbd%?quEXgwS}4A9d)M(b(WXbNaOA^AyKPnj_K5wCD*o-y>L za?69#JV$FRt=DKhPwPcmF9f~SC#{!gy*$kMD<;sg^B=q*L$sdu2Cd&{y-DjUT5r+% zP(I(L^-l0Z%Xfijy+`Z)q2_~;Tj!H9@X|-LK2`8zTAu`)+0OCgv_7NtdC<`Mf|m3D z;0r#juVrAv*(lv_0cd?k%L$g2an27WjJ!UNf?h9bV{n+D6@f&^BWK zQ^o`Sr}Z~&Cx6=G3n%cj>ahw=Bt$szsJ(mvOnXw=v(lc7_O!Gor!9tWPeFT1yBzJQ z{3&pIYD3?T)t?hbKH1xTw5OvzJ?)uk&mccD3THC>-`Sh(S)?4HJ<9cAUDtO0r#)IY zJMB4)yClykoJ%;j;h^KS=cRol?fGc0Nqc_Ui^^aD;examlCZF$ghj#=H`*tKZ4 zXs=D%DV_E@LZ1R?uP0nzxB=}AX>Vk*Rji{;vh7V2RiIs>t%E?@kw*>c&H0~pIcR8C zyxX^{!W!+mVjIFlm_z#)J@Mf|UQFy`6;Zg=2>L9Y*bXkTg5d-iP+iw0Bo<7uvf@*ezH( zSGPs}?LC8r_FlC24)UnozNNh{?Sp9VNBaQU1O5-&9kdTLGwV*Y52k%6ZRh{6C^CWe zVYCmI;Ol=yndMQmub_Q2?Q>}#L;FO!wExcy!o<`UzoKYv}FnH zhiE@+LEk^LNv-{;%=Gcc_T#kQz+P)_zexK@+RxB-{-^!4p|g#x295mN&(iiF?dM$S zf?fe_KQH+OL#29&_N%mCmhu%tTRBL6P58QN%+eN_?%_?^Z_)mM_S>{Alkd>>KY#gk zZ3*p}f8PYpfT0h}54{MU@FO~b89t`{37wgB=}&2YCgF41ze=#K;`~qhE85@F{+hP+ z+iylkKaGCw>5;uR8vWd_w7+vBu~FPdhEab zyz%yLbf%&GI~{2ML1zNmf71Tj{~FN#Yt%gFkDB{zI^)q9-!!A<^)LL6zHt>g6S_fX zB16~Dy$qd+=}ac&B*ICD`pM}`Da{liIi0EKOdVn!=Uv4|L1$Vz(@B_~&WsXt3aCfT zWI-bYAH|(n=qyfWWMrRn>5QVYD4kj9%u8oBI&(Tzc1F{gePqM^B+oJ1MIV@KKRR;> z=N8UmD1-TY9ow0o&O&sY{1seqFz`FWkH2&l@hDpi+pa}N`~TK^J4?`6md=viA{{&V z{Jl{-d`xF)CyLH8{#9-lmZP)$VCD)t2Avh@ROqZkXKf{1*#|;r72&GF)r7vBqqBx^ zP2pPJ&0M(-osHF`Z4q8wiRm&?!m~|Jx}Rosz}U ziG}6Cy9%AEY-)6JI?n$JHs~Z0oc|?cL;EJ3ZFRX8oi?4Wdi_uN`N1ffr7)1kbX zqBa+9A>2~v@Bh%*TDXm&E@Ta~8=dXw>?Hm6be#Vs??7kAAREefXW=eFp8{mIJDoix z>=87I-AfvK|0iT`Up9ZA&VKZ*3++$$R5}OHxsc9*bdHk!L39o_LGmGV4wZ1&P;)q) zBdo=xJTg3|pmQ{xW9b|-wE6dd+&ah65pQ>nr*i_G6O9d&@FY5?NjO>PQvjV)4UI&$1&oh*8J{{+O>p2QuMAtgr#dIg6a|xX{>0CDaRXPa@-6 zhx*&-JV@tuI(I9T=7^4_fR5JxUY2|4+)w9T8QkYihKp@F{uZdYqVo`)M`ZA@U&8)r z-Vy(I9s~2SvM2I%i(jwR3iV*3v^x#v2r=UAC-6`o#tEj1jQ`;U9-D!Ltv>x7_j_yo! zrw`eqI|JRpBVgAA{_M^|cPY9fbmycyitcE-I{&%r=RccIpCh`n%bTD7;7ZrbMR#$! zbJLwyQGWij^z$jG@Ben4|LHC$Tu8XE(8-_fqJ|O{8#!FJJl!SCgOV?4=&wC@wf^re zL)Q=dq`NHLH317?rL-gN#tZr?|a>~glh}e zF_h)H?#g<5cYV4M-3{n&B7+Tu8`0g^4fM>EZb3=>{I{T_TcX>g8`Dkcmg$Q2yA_WO zy9(VJ-TL4d&u+szPd5qO!#)8?H=~==ZPRViZT+*`$kO#I$={a`?MQw290 zJ=S0Upt}X#1L% zkTREH2>Sa!=0tuzr0XyL(EVtb#N3-p<>?Wf7CEc%-$*-mV zM)55thk(J$xZZ-QI)#-}$i zJtu!R?oCKfOxSb&x1Y}ly-Dazu2@Y0Jxu|2zZ2GSy(#G(LvJd2J$h5qTZG;;^k$(q zExj4&Y5m`uKD3Bcz8ABndx73e-j2PQZMv895qfh;7)5VZdUMdTmw(J)^f1-zok8oc5T%CmZ=`AS1kAU-n_;nYew{S=(&x_LAklteSmh~aoTb$k!^p=vm zq@mC0J?DSrYMD@n^p>NyHofKPtwwJJdLnBaQQ zO8dNXSFcJhr&ptw(W}!-<-9>J8RtJ7jq5?LiYC1_y;cayxI?cSa_dU(IemJ2(%Y2Y zF7!5|H-?^d6|09W=xwd)-BM`ZfA^_PPPS1iY-?yr#coG$dv`eK7(2*p$p5{a6x=y@ zptq}%>?Yh@=;uF#QXWq42ooe9N$;qkd~~p+_iuX6=k$&h9!Kx^Kn3(ppm!p@lZL^QgP%1icsNJxR~mo}N7~HqFx`M}4FWJWJ10DeAd!MS5cueBMwt_6XP# z+TqWJFH7U=e|oP9Uo+HoUk`bp_a^=6=)FbnS9)*LGa7$~-UsyFrRRtLcw2Y_z3)x) z{?GJ2r1y~oKLT_Z`>_cYr1vSkuO;aGpWf#qZ>~zuim4;K+>f9Cubhbgd(Qvz{~bN& zf2GwC5Irredq2_>|Mz|##`?ix^6(q|sp$Pq-^TtQ_FPiRKZSqE&)<&y@jRhjy6-Rl zdu)F~=_e9Ge`3!-e-iprN|==XWD+KKdyn#0b}ivJq(3$NY2<2J*N1HPrCaAoPWp5FZyfdKqCX$~xs`k#Piy?ypLZzC zFZ}}luwRJ&YV;STzasrb=r2j%U;d}R82u$=yttPhuWe|0MXei_SoGJYzaf3`zt$?U z+-T(CQ4%(x|1bIl`fd6}`U(BWyKcWEjOkbDmj~UxUvdA|=leCq)`g8Q?V4UefBs89 zqn{6rn@Z9$y{_1y-}NY45A=KVx1`^vzlHL=DgDi4v$-1#a~lNuThZTIvD*wa+XgfG z+tI(D{`T}wq(6qfZPe^Qe^2^5(%+T-PV{$iitF!eXh}Sk&5}0t^mn7by9Zs~Be1ar z>H9K_zNUcwKK~z6X8|=ivAlikhr8{&u(-QB7k77;cO;paWHOc+d~sR$ z7H4sni@WnxJw3BiG=)%V|q&r$mwx5n#_$~s-`>x~Ed21D9;GjjF?=_}+DrEezqh|w8@_L#^8tomWB5UaE%qUX zA7l98pqb%Ej9~atSLGx0aiPDP&#-+0$nYt1MHqfsV$TS*|D%71F#H_DFG=iqhQ|(+ zyuk2_fi_eUO##E20)~AGP-lK!QR9Sf2;UUGWjL@)?@+v*;dd!oN#3KdE5q+on3CZS z7&eppA;VuY{1L;SF#Peq?9ZnR+joAtyM0qG3sYE3#3B?Hb$&2?g~cf>DLu0P5>{|&3ae6B zhQf*zmi6&fSWdWn$eO|mL9>`E30D@bVkix(DZAAvtV2OZfNXpf)}XMaVrBdbYa4B1 z;;c(yV)00Q`p`c(KYWtVaEZzlgoG$@8U#XU^fa6Q`nutRTTE1a5#lMDeOgIm_nOEfkHx| zNFk=+_Mbx8)B2r8VWf*$p->eeYV_5(+lxE~RkU zKTm`Ums7aH)2hxT?@Iu8vwWBjzJ5184+TAj z6ds}Q2Zcu|jHB=vh3CY4Txex}lEPCi-0wn9Qy6Ui6nqMxpfbDhk@H^|OW_p?FHm@i zg6w~P)V~}CsfLoD|Do_21^4`eW^vx2@GXTmDOmfyMd2MW-}cFomCj5+(U1BzMj~ntwO_7At{2Df~q-*!I6EPGAF^;)E0@p*Rsm_xyvT7pW-Y zZv}9f$tX@MVseU8P@KxAgW{C7r7Y&u6sHMkZRQeZI*PMUoZd4n&OmWSj}7aEqCfwo z=#PCBJ1fN@5hI4icv?3;Mf3l)xG2s}@hFO;D9%gKjlU%46wXC)Zi=$}*3H5mOL0Dm z>rW+MT81HWat*GdQv8DAYZO1B_&P;7{6+moxcJ7PSG+~>1B!0^DZWGTJ&Nyo zru_l_ekg#cm4K#zqJ0I(?)~F|hEFM4PyS2-pS!%B+6wNC_wX+%{z>sGia$~On&Nj9 zzoGc8ryajYEPhY%2Tv&HhoVmb7L>xD-7zTsq8#-Ip!l0;`vj1c;SWQJ{YA+P{@<>| zf8d8yrO7DGPHA#VGf|p?(v+TDo)V?0C{0aiTF<34O~^$` zrlT~y2>Jgd`Tu=wGmAe9rID0or8L6w8fY8x-H%|0OST{Gd`hDzEkS7xN()k&lhXWR z&Ly0ilBNLbGo^XG=al9Py+v8b{`+k%L}@YX=k=vUjPHZGWUm1CJv~V--nUn!B`GZ@ z&Qg??rnIaZqS7*kA)%s{r?iT4cmJQ#ij-Coe`S{$Kiig8m5iPLxmvORp|mTdH7IRE zX-!HSQCf@AdX&}PC0Sn@HW2Opzf82rY)okrN?TIel+xx(wV6G8 z9i-aA#rjpf6{W2M-=~1mwv=|FwB0~*dm||AAl%X2ywQJClKn5q{|~G9(r%O*ly;}& zHk{HPl&nhaD}Vo`G)$>LspwWnWe&GbsZ1%NFd0aul*_6(qBx7FkB><)KD4kE~ zB1#tweN;wxD#Gih4PvYbaeo=_*Rv=qp|6u0`-JN>_)Q9W;&X zf9X2YW*ka4PV3b;-`Qp-6Fh|(ruJ{!=KXaLVpA>UMHoyRWMne(!HX6 z^M}&?h9VxI^dO~&Jg6hAGD?p~@=;3i=Sw~XP2%&HuT=EZ66JU zP!-QndX16}f0o8ldYRG-QX>D~Z|x<^(KuqhB7D`|7%$uF3XY@nmV$2oDZTkG`L`*( zL&-kH=7&xLpVIr3m!|Z=K<9^)K9Zh~DSbyt{(tFHN?(fk8KuuDeL=~GhH(r7q1dk| zeJ$b}O5b{{W=3(or#wBSA1F^r=|{?wQu>L~UzC2P^c$sLDE(@~+#A;(&c5~srQa$2 z;eNHfA5{9&G`KdK#7loup2!uJClF2;-bJE3F=Z%E;$nL6?Ru^Kg-X2jIrJ(G8c}B{MQJ#tN+>~dgJd*M(lxGd!Jfu8=@{s8qdc4~I zVqtkU%5zYjo${!mq15#Bzp_}Klk!|cbGXSGnn(SrJP+jsDbGuJJ{Q~d49fFUUSMd1 z?qarSRfdHqFG6`?Pxbim{xrmnxt160zcEx^Trx`tmlQ7Ll0z$sUWRBt%F9xIn(}g# zx2L>3<#i~pKv`zL>|#Fklvk#_8s$}#YE@^3RY`eu%4<<}3rTqm;hKgnVCUq^+W)aW zY(9B;UCLWfUQaUXQ{IHK|NoKlhQf{dL%Y1Orwtxpc~i=pNq+OdG*oa);Z{PQ0w`}o zdEon%eG2dyp}Yg-2PyAJ`5MYQQ9h3H&Xnz5cA;FOyes9R_y6*4ly|2*Y`v879+da= zqP0W!7@kZ_v;Ec*-*MEW)Ls~3MPE$$GRl{TzSQ|Lr<58D7!25FaA}O2gbi&E!R@MiSl)70zD2c>mX?P#(pJx)7(t?0m`>fzD;7chA7In zQ@)$B|NNcuos{nie9z7~VVvGe`F;ic2&hGcSVz~ar9t^&%8yfiL`>_1k5YEy|Bpvk zwqg6EaVWe0Z>{lDBIy5E4dy1F_i!R&xrVt@<)AP zkNt%5XOurRj$ih^XW9LKyWEQ?e?i#{@|To$x z20rDVDgR3Omk=~m>~ECq=f9@k{&p0a^-uBt3SvYP5KT%np{tEF9Yhn0p2YQA!$tHn zie&$z$t@@Wp8{M}G!@Yd;>i9-vj5Sv;!H>6{(opYq8W*1A)3kUSY)px|7Yk({aY%U zRh$vRA)$Z#$1eAaqS=Y&AsQuxa}doYe9Xrk4L-2M;vYZ9$%K_Yh%iPk1s$A}<6!%a)d$Xe*+vy)whVw@r`8eh{p|7|H&7$#)>ynP^9% zojk2A0n8!kXT1wifB%PQx4=}R??JRDQH^MrC?YDTs}zaKL?tiJzeYDvOeFj7y+!ul zV!dS(qSO+auykq(5M@LSqGr$_wXN~mh&sAQUjh(yg*~FZi1sDg+k)ckV>q(pAC6FP zet%ZRQcx}4}LqAQ5}3#bFh z{$GHJG-F2BxbG2N+uulZ`g&=;f#^0OxBo;p5xKP|lK&sw;{7DtMZX2*|3`Na-Kn&9 z5#8fOnN} zDVGmt3F2jl3&hJ3Z$-Quu^jk#dEym_-Mka8NW2p9%3ea(zY6iHfp&{wwk}?s*k2_G zLHGINHHkMTUW<4g@4)feUVi&BYP>G7{s7B$|Mcsn0ivc1GR5bvxkb|l`(KQ3x@YyY*3cOl+` zcvs@xoauUY_blw|f0`qR_aye^z(7xt_&#DQzyZW%;+!}lt`WzsaR1wgE5fRu&h?f` zh+D)daf7%{?0@%j{niydFFgVdpE~iI@&n|=9p4*tqDyRNeykVSQ(#N=F7e*P`-JwD zz`n%$5$|ub)qgva(}+(XKAHGL;*(tGTQm9RxDQ-{_!Qz(?Ip+1 zLI?K_y*z|J<58EwTqYSX5xE@ zZy~oBas_#*z6*cSy}0B@Qfh~4~{g zGUdLHcc;n}RA#0!C6#GybWoW}I5m}Nta!dBX851ys7y!2{eP=5Dl-UYq%xBcc8z*` zAu6*_S)R(QRE!=$WnL;nROX;ElFIDv4pn?2^TF%ou~z`Ji&dGE%G`?8D!>acPu~+T zgHV}IQS(z-P{aa;t|Yu3URjvRQdAb9vY2A+5x~q|Lf5~za0x0)x_tPWO=W2+e&(Ob zGC>cO<@yTE8?g~#9ztbBD(_HPiOOD7R;IEUl~t&$M`cwiYg1W`%34%bADUx9D*q9# zAzahYuh}>BOEi^rjHa?~;8R(j%0^T+@F}daq5FRJUc8|PH>R=)l}-O)9y67F9_Eb9wcc$WpKSlczz{IHR zPNhm^4=QCUdn#&pAXA`H9MC=yt1=_W_#*%+{yKnVB)UeWNkuMSB^Ae%H$-GEV}EI} ztDk#|N?TDKDqSjh;Hx}6rzJ^cZz`8k*@wz8RQ9EE2$lV)97x5d04fIvN4vd}9aHci z;la-I61wwGOHw@H8rN{`;N$Oez;rIg5(*E4>2XMbZ|K-^F=Kdp;F^_-nK)v9I)3 z-2bO?ao|w7B*ap=jLNfAE~j!Gl`E)ROGVytr1G$teJT&R@%K^u2$e^jHV3B9d7R2qRGy&X#@|h}S!_@8 zG?i!kjdh#8P1vTJidDs1R4h9`{G)uwQhAli3shdA@*kWzbxtbpQTd-*^L;8G=+-_In*aZa_#Y2i zW4pr0M)66K3v~}AFfYz8mhBWomLvA6T1DUIs?_2sm@4sCKoo%VN6wL z@hk>CnW~lm)gdJrN!9WlC3O53eq&hFvWvI?abz!RW zE1~cIP+gGfLgRCho<*oGNp(@Gi@9d2|LWpY&GXmkBh{zGmJ%)<_*9qm+&$~%gjz~e zR~W>uB<9M(Rj95(bycdXQ(euJ7}L>jVSK%|GJ7=PiVF3`~QA< z@{OqOMs;JV+bMPv;ignKqq;TK&8coF&K54OzCm@X@%pK5qYBy9Vnz7*PpUfzcci+D zm^%q~HdKzjN9Toi$=#iHVh^f&QuSxPRBZ~d3M_Y-Fqss~W5Q|(aAs5bsFq}3+XmI~7j59SQ1=2Z9f(>v9!qIy*K60tW` zuSQEAG>EyMaR0!kI-2T1R1XY6MIB7_5D(fHtL!rCT|JDdn|G>*Q$0d5N4o!0Jxa_m zp$0@BL-jPOCV3)NxBpcA{6E#>h5jVbueIb$S5KmPvIlLVsh&dhRMEDGa2cwn3(pXq zDLhN)PXSbu=Tg0d>UmTzqIy2n3#t14fAC)1f3WAF)r&{!P>{-eDb<^(UPkqLs+Uu} zO3W*$YF@6&{~zm2@$KGARId?UOZB=i9VqGsp)UdKQp?^<^)@kY5o+P!Eq*)IJ0)<3 zp`z{*-t8`B^&a89hQ?GS+)wofst-_omFk03pQri|)u*XGO!Y~sk4Tk&0+i}wLO=iS z>w>Dk0uWYDRQ-FQrcfC@L-kpznpvv-(*7%UtYlsgz9@W2__FX7LldU@8r83h^!sSYHDgQvHkS-_*1VRGT31UAX4^KQ2(4M2K)w;bew(4Fsm3HYK$gs7*y} zT58(=srmj-kfAo+fbc26XlgV1ll|IE)Mim|=7GSh;X##{L)6TvHnEZYr&qPvsm*N~ zsEwjFhln|aa{j{;uG&1}%xfrOKA}$mro^z6@7(O+nm}K)ONO&0<|rvZAEP-(OV0*5pGLuJI~Z*wx_m( zH18NVhJ&bGoUo0c+OE`gGn(4&!aan04q}HTQ!pHGO5&6agSDg$2ZVYNc7MtUl> zg!CwCDd{fM>LgZg8Og`g8YItAYmz)etwmyO*QVx=B&c<$<>bLr>bfHkE#y0v4d9ZStu2Z2xRxV{GOawkx;dpwbvRspq>ELDi5=1Tw- z@-!o;ovz?U>Ro42yN=pf)GnuXHnoeWokQ(>YUffr&z#SoLAhT*?ZR+Vp4@xg#ndj9 z!b?JVMB67IWD;vO0I#8D{pu=eS9;|b-|zHlRg~A2igc}^5?)X3UTQZ`yN%k7)NY}6 z6SbQ|Uj5`=c;7CQ{O#24qIL&0-~116n@Zu`)b4SPiS?V{K57qByPw*F)E=;?5KHZ$ zAY-V6k5GFw&{j5Tk5hY{+7r~CRjjsvYEOxNn%Xmx47XsVdO>{o>NQ^lQ1dMyKk`v~ zQNfp}y-Mw6YOmO-65|YR|25J7^`DH_#!<80_y)Cil;lm}Tf(;`W~F+U+6UD1{I~YL zn68T2hk>SMkAS^Yc2WC;+NUBu6Mi1}R#(*YZ-UyFBxWAJqV_vAYtSDg_6@afMfm51 zseP|hu9Mo2qK&XA;Ad*TiuudnUVi(RNS2bk_J5KkL@#OBU*aW8i?a;LvPL-HR_1pD&NU!7!AlK+rwK(YqOx+H6otW6^0ujC}_^yOXu zdIN>FT+;qSvLVUFBwn{3q~8Ohcpn;VB@iy%X?rGi@x&7@UuvJJ_0B-@5q zm3I3+?b>!EDU$3&BJ-Z?OtK4!%zm<~iJ82T_!MA-A<3R3!$AW{LHvzKN+f2?%On+& zh$MD_(Da_9N}_F_q!#W=dQuY*VfMd6(jaLm*fb2CiliNAlANSR;?BRT4aP0m%VWKs z_aV8MWM7iwN%kW-l4O69gGmk`8Eu<&CPs1~$wA>3f)bKLNDe1Cl;p6W$FX0eBL+8n z6v;74K4!!&OOPBrUIU4Zj$=vO`1iY_kCzij&L%mLx+3|D)N1c<(uqhXAf3>X^zXvbrW1#* z=#nXM!F>F?4%2ljv}3hbPm$FNZtMi&7^aO@+j@Rr1O)`XLKl= zm3=@zzW;C(OE5ukJ{?_;LT_8HP`Jjxr${eMqr3+i--K$H3sfOIF) zot+c9bh;~Popd+S3hC~oZs$q&Aa(y=v`+!PWJ`;rWzv!d^{`2?5visCmpPZTDrU`P z4u6%@mH?^I9<-60W~BQ_ph4OsZIiafKfNODkmmkO%FeT;T~d4U>$fTSy-4?VC3>1e zx-aRWr2CN`M7lre0Ul+~h||%kI(PmpD75-NSWS6|ukyU14yb-3K^hDB=28BFXyB4Nd{(mqmsg?lg=@LGJ z^i0xoM4v@^wwF-HTEy`wfb=|PzHejm02<>E4Hb1ms8um{IIAT8QM8E4kC(iRBs4n^f>5C#>@`=Ej)%FMcUS1)!-uMye zYozax`XgY{aU(aGUE}BtQn&x4Z;`(3`enDpQ7^JhoAfk>8GS$k$&b%T%pebq@k;PLF)G3QVn9iCjBOGNc|HK68_!@(olgvlKw2_ zPcCK^WtYePN_`2^->A1}(H`cy-Qj-fuaa2nyX!s!gXJoOoayJzsmMWRF(sOHyB7s+OX@ zH1*}EFXOXeeOWv7_0O}d`tsCQpuUP~U`6UH`JZ{cHdc1R_6O>#QvZ)Qt5IJ)FdbcH z4dI#+TZ_6q0@#SUJsaIfoOP+2{dd|>CD}l@A$1S>Bft8_)He~ascU&TxQQwog=7PHHzw?VhiISA5ry?TiG4+IcCB#y%QkVUI?GNJwrX=gaOxOr~>MiPb zQMX3fn|gU+b!=${Rry&Q$L9M0lv9i zA5Gof{Ph{i_1l|n^@D|n2oDt=Hi$C&|M!4ClDZw_(93c4G1QNC+7%uXv{65n`f1dU zqkanY<1H2S6NL5&XnlsdeiC)x2nzCwIyD5TpHBT;>Su^~CUtlIRT9~M&*hv!E^hp( zpKs_g9%c6bYU&q>zL>fm@76D&eyJoc8;D&lfhz`j{Q0j0t{N2Y8tT?9W&i8fDa!3X zb$9-i_QpXHxBrT|h5D`3-S}ISWNsJUL0$I0pT*tOU!-p1?osOZQh!9F^*-wNQ-6^9 z1NIEpHQ1Bfx*q;I;m?9YE*d6}QGZ&aSf7BaKOy=_>Q5Qr8;m}{pP~K&^=GL+PhH-u zO*i%DLaYRQ|Hp%U`ImGHFAHCxK92gUz6V=>&H57c*Mo@@{f1;@|LboVZ9Pz&cgUuo z{x0?JsJ}i6Kc?;<|CfN>$VY*ex%-5=n|JD;3O}PR<6n0JD~@jgiTG-KDg}KC zpzd2hBEBb^nEDUYf201R&r|iE+;BPZv+x({{tMtyW1my-cj|vq_pxR{Y52>}HPyXu zlTAQ25!r;{>9#!q?;D(Kl988>w2KVkq{7LBliP?BF(uj7WK)qXLN+y-Iq(~iO+z*< znK}7JPe(RA*^FfF{96`=fgm&g-)XX0$jtsb?KUyfQWwl>*n5?-Bb z4YJ_;dn&Ru$=vfdx}Vl{u0v-2zw&jN^~p9Rv(0~t+K|kAej~lmxL zCfPn@EwZkn+QJT5-k0&M4@=S`+beL$_I9Gr-edG}$p^$12LVp+p=2HE{&XOdk_b{5%%WM`9IKz0t98+@{J z$<7Ny*w8QYjl(JQ>>{#D$u4%EExW|MWxw7fyNpamBfFgJieR{W0?n=@Gt2LG%=~I8 zzlQ8yvTMn1CX@Zot|#+7A2Lk=!T4l1^{r5L3)x*{x02mX=2L*H@`}BKOvc}gZTO;Z z=KDWn_XGjMksD@Y_YH^#$Q~nmknB-1Ise(i8l;c-AhokYLKAi$CVSkKxRrR4>N(#jK-`qCZ{nS zjVWkMO=C(^qNu4{gUd`~D8l~&BVu|QGl`gi#*8j+J<}!acDzf_@FjqkvoV6kd@9cn zjgd5F7p;>sEiZ9kxoON3IEGI60n~SC%uizh8VmWLXe{Xb(Ox74 z7oo8ijYVmcXe>r!a~g}&Se?caG?u5aqzThlO1QLe8KLk0(^$^1?*lYe5NAc1#udGxa3dO<(b!n@Cc;e( z`@0$AcU>A=(AbiOn|B&p3AYw*Lqqnzv7M(1W52NjjooO-{x{tI)7V+Ki*Q$$9KR-Q z?C$$EjXh}WsaSXZY4}3GdtIX#948uO8f_Yp56nhPBc)NHQIky7m_~?pluVsQlSW3P z5jdm9{yB8>Z8TaI>zC)!nSG^6Y2@D98(lx>Zl~WHwhJ_~h`nj-;X#2Z`j9}=IE<$Cfx~HBL*ob`h z(YT(*4MT67x%@aERb>TSS z8v~iQXuL<`Z86_56!GpL)}H_N*X%~n_>jg&G(M;Cv4WonKXn<8{mhd)VGaEgjW1|? zOXEu#-_ZDq#@F67k~AXFG`^$p0}XrrYa0HmwD$bhwOIjZ{2XW+ztFG;!R83Lw@Kr7 znl@hkpg9qZKdq`&+kXko{y#_4?7!iJL!+M%F)>ZE=f;_YCNw7(J*jZAAS2q2fSSyd zgH%(~oI#vvXiiIWdeQa@nCtXw^b<7g6L6Nq-v9OBkd>`D3r(~0#>!{H}?&EHy};7|8|L9f#wc0SERWP&6Q}bv`W~7rrUqtz%+Eto0@(0UCrj^G|m6F3faOrqo1I;70s=UaFgr7ZD_jv zcaz)P&Lowo+e4Z=(ma;tPBfb|ccxjUxeHCx>Gq%IZl21C-D&O-Xen{~FQP!RC;{JC zFplVmWiIZO>O=(8^#<&wP+qrvrTh9njM;Z(ab|ynq8&s z^}Vv@-U<$Edvjl_YH{`#9zgRDnxknRO!GjR2aR{6Eq0*cP@0FiUmYfhuo!P1LGwtO zM@z|3!ZC(E;+n?{2tU%`?o;zPn#T{coj~(MKNF>@Fr73{rWeyZh0X#rPo-rIeHzVQ zXr4~}ia%oLVYZqG6(VCmq^t5KBrT@RQ{QobsW)kY>FRfYJVY5dJ zb}p+mg4S$`8WN5i@MpKRi8!NZ%|UA}(Q}4aLw~B+nrGHCoHjT1mm>g)0bGG#sQ|S)5gb zs|G%;)t&G{{ztfma7|~P{sXPGo$%OogzE~|qqRP*O+{}Y+)!xW|2N5vg_{^k!)CO$ z7H4ze7Q!usTNz4b8(JZkZN=YCxP6Efy`yj^;m(2Y9jvwM(7jd_J_XR)otFOshSr|6 ziaw{bhH2UVAMI4OkIlZ_Zk1{MNh_jdNn%-n448>a-FmOg+_sOBgDc z(P|9nCaspon)PV4g&koo>K?0gsTlVv>{_WE`-l8OP!XWKQv~Hwz zGOhDzokHtOT4w)G8)!IPdd@H$$e%^)JbziZbvCVYXu0!m!SS^u`3q=WN$Wydm(jY2 z)+Mwq{#O|;HTqw{%V}Lf%g$r`yN#}*buF!{X}R%t{b6sRbseo6Xk8x)^RH6fMC)N% zH`CG@p>+$bTf;3F(z>12y|nJ2bq}pOY29UZ%$*Q>Zri%MPn$=oL&CH)1+?y`^#HAh zXgxT-V1c0Z2(8CxJ!+ag3%5UZ{@*&AT~E<^GE{{jt*4dyGqj!^&lKl5TJQKCLhE^2 zV@13`YaFc?MZZMrRa*A`mp>7p<)45M@!Ej%df2+utq-tT|BDCHAAE;fN_8PR8puIBfC26lndnww>)As!zXU$@3)*|o-jen%w6*!u-dahvp}nJI zwxzwDCwKYnY3~qd$?PQb{h$5@U3*vByZJ!2(bwMH`N2xG?cl^cw1e+#uUFJ%xQO( zs;A&yLihjszIS_H+HUP>yZxuVzn5Qqfc9wG2a0)+acCbbTAM#%L$ZAs?bB%=PWx!u zN6H|{5bOj5#jq`_9Tmi8Fhr_%mE+9%LHmiFd4BBVYK2zD96|xh34($s?_$)yCJlf~G25-9y44wAETx2Ff0+-OX zmb#SoEwnGAeHHD?Y5$kDIsbZN-CONSuZ@RhS4&+@+y4bA`dX>Ij`q#8ulM=1?f$=_ zZWP)tAZK%q`CZ;0tPJ_sq(y-W1nF0XPb=t}@o67Z?(sqQ2C|gN!sIRKSlc`+E3H=I;Z^%?PtC6?X;WU<8!oM zr2V`lq%Hg3mh&&KJKV2Sy-fQR+HUq~ziJ#$?%H1Wa+UOX8f(FU(o)Y_LsDObW`8{ ziuTvEzoY#P?Qh)ywoMG{b>{1~zo+eIEOo*ojHBeH5j7K+;rygCu^N~h4TsL z4-eNwFG&6}orUQ9PRGP9ptA^_!|5zaXJg_B+XGyQs&Qijq{RX|Z z_oTBdo#n+`&hN_>UtUH23K*Rg>8zyW{s|yDtEi}M|LLq|DCX)yJ!*0OnsnBuvlgB8 z=&Wr$Ru#C8a9!_3#_3o126VQdvmqUO;A_>j5gmJjUelTNw9ck_BHbI{xyn-$m$?ogL}yWE{`h`jw5K&MtI%batha(b3ZS&ceEFs(RB8vv!CRB#-wBWKi)v9vHj^B5GqrR zbs(KXrT-w^&cUi5O#!y6(>ct7K5D!M^!>Nak#tU_a}=Eu>5NhCN7Ffu&M|a+l!cPe zIo5~}q;tH)P8bAFQt;#;K*!$#5a%>HXNx$Uj(`83&Y8mg_y0|d&N;)E7A&Q%r^=W2f`wR4T|+OWSN zzP5lmJ_XRZQFxR4^BwyIu+IW?Zl&`&o!jUtHd89PtX87t;fbmaeceE)~ev%+vw z&xwBCa8TM8=)CB`u))%K$!w8iUJ=UpyAnGB6qJvn^BtWx=zKxvO*$XZd5ezbf{yIJ zSA}l@(Rt6+dL`=!Xy*eu9|n$MKc@3Jolod|>df#-vCd~9ccrq)G}NbW0nz!2&Nm{y z9`L{QgngOs>HJLR2MPRWDB`D3GopVHY6;N!&1l>2qVorNjn1Fs3()zCd~ToGI)9T- z;KKQYwTJ&tf*@dHoa|q`&^eM?uMv{CU zY49Zg`Fw^V<~JPL|66ji{{`}e$hRh6n0zhrMaWkoUzB_q^2NxPBww7|PX4&*@Kol< z=1Y+;?X-Cv!8qqu70Z*$|98tXmV5T_Vx_OX8(L`@=eIsAzz<-U75js$k($EkGT2DHz41bd_(e$`l&+Nu>Wt< zO};7lX5?FvZ%)32TLv?&k|E#9Z^8Wp)3c4+lYCq9oyoWJGotzS`7Y$UlkX~l-GUy`dnoOmBBJf;{_?z|VDZi84-#f6!BKA#1rMidwe)4?W8^kQ9w&c>+-~7%@+ZlkGR>ynvU7zh zIr+2XzKRSEgGT7{3o&$dE~Fros0Z+@?Xfuk$+46 z2KlGtzW+o17WqfyZ~MuF{2g)|=I^Rj-TsrmFZ{r;-%@IQ34cuP9|AI3dOjonlKk@^ zNv_3I{zXV7C0_~6{@eZfTp;2*@}I^3UZ^P`_bGt9{|LZ)mzVQbx>J$=M%Rq_@8o}z z{~`XLUIsS;e|bs9+)P)8e=JFNLb?-KtPf9z`pXpR+q1-RbGt;m`jX$I~9MJl&b-&MIPN;Vhn`^G67W z=#DhPm;?Uobm#QKcSq5k!&JE*Qy6+ncW%1d(w&FyYILm_OVgc??m{Z9+kd*+3+gWD zsjM=)3)5YK?jm#-Q*hC~x7=Mk2pB5WlES3|lkPHfm-FRtcUk8+VFg~1?g~CTXhlhP zCAzE7UD<+u^^3QvgW5O>C|au^HVh>ALezcMD(ITO-;sw!0PGZRl?8Vqssx-s7+i z(A|!1hwk=t%XD|3J4|;+x_i*wiSBN6ceatGV(%j5yBhjfbLG1Sn(m&Sg*`2`D8H8i zT|4|Umaad*3U@>|qMOl;=~ld-nBD7E>B`u4YtFHUmEDxC`~QBw{l3(oYo|S}cQu7A z=c~mP%<00||I2*oJ7#qEqdSJ~{&fA!Kivc9`gGtKc~3Zq z?jgFzgRM)8IF#;DbPp4KxbO(!k%o4KHZ`PsG~JU$97ESC^CZ#73Xc;WPuHga(I*-@ zZO78Q=1-w}4&76YL-#b{>B2Ky#s$u#dzLt72achFT1MFlrF%YIYlsWzUZ`9bJ6`p?f9WtLa|lj@BJt z`I{8oYv^8Uuip;Mvcu5GCx&LWUV6Q1`v$r_N(P*Cnyq;fj+KE@_`ffkn zSB0+`nhf1>3ce8+r#4D1z>{bbnN*{f_SUbgleuF?1z#f1+y! z|7W^>*dPr2aBIr(cX|`i{e$k`bpN!h>Hg&f@X}7|!QKSI2?LYf#PlYkHwisVzt?%s z`mH;+uG#e4*y&9{ZwY!+(wmXqRP?5i*wo%4HllmedcM8s=*{4j)|=i-Wy$UG;7s)9 zqi15X)0>6ftRCz95%fmV8}eL~7d(kqc-UjqGRyj8mZe(am=xsu8bJhH&^fq(9J-nmm zO|XR;VoPV5A?`;BkwcH7%=dwL~$JJ2i8+mYVR(zcV8)2q>JW^WfI-<95O zB6g>@C%rw4_RDNh&z1n9z5JGu`~O}KeuMfid@phb+k+MMHa%7LT8Lha-i!1SdXLdd z>77NdPH#VY89h_dpx1OgwleCq=;idloS}e+(7u3VZbSqPKV8TPol8J9=k& z`x?3uuhRY9#PtrKH=5pQ^bVwV*w8*!u?NvRnBE~qkNn?V^bR#*Kp#Ha#l!TDpl1n> zq<1{MqegyHr8h=+wD1^15!M#R4(Q{Ikn$5|JAXF2=$%OKBoQYIPYL{xmvT8>@@ELo zob7~?F%9XRP45bN=g_;H-nsNHQw^U-?|ga})4PD)h5u}F_AavO^4-p!ef%>Jmxfq7 zlNPGQklugkJxcFNdR8r0sgqpozJaY+d)LsrHqes1o}Sgo4fL$T+(^%Mezf<}yP4iC zekxC&1osK3cN@Js>D^B64*OHBwY6KPLwB{qy3%tuz5D6i<3}ZX_tLv>=sn-b8M$9u zefR--57V>8wPPgus;Bi#+Y|0R;sZQ9YY1zo-sAMl$lFTx-JyrAN}m)yMek`JQ2T%D z-!blK|EH(vviF>Fe_rTzOdb9OA3I(TFVXvg-plmHDX1-=o^JusdyU@fvgdBw#d(9? zoAf@T_ZB@l`90tNq4y3w?f>+&|DzkH_W`~CPt{pK-Ay$AAN$}Gr_Tc|F2&t>aV=g7 z6sNd*koJN&rELm_J2;!_sq`D&id?TceD9S63-aB z#d^%AWkwhy_TLtH#(W`Rg=WlGhF|xxshn?dq?9Hz<}b!b=lYW|-!bM7#(b}Hr!kVj zUm5cwV}53gPX1NTOA{?6zX&dP;7G>U;?Ede{8{k8rTab|p)#3%l4IHbn=x|o)7F8< z{EMTLe+}+Ti!+_t3R#bJrpK8DX9k>^aKO<@)DkY2qs8nvGb^p3X2qEUXEvPKOO8&4 z<;~F)iP*#$?}e{goj8KH9t>0&lU;EdFA(*oR8nuig)tHF0h;Yb_U z17{zcJ&m)MzRsxE-YTiu_U*mnC{ve!a7N=CpvuM6moGlfK{zMi9E@``&LKF5Tfjqc zWC=*kh>qQ|%HfzvagH?Kqm(J5iMEPk3}yXa=;N#&k1yMXMRy|3Nvg!+vapV!i{s&3 zjpO5-juYTCaYCG$WiYa0i8bSzi#krCZ#Ryu0*ToX8|b83N=jrnQaCwI+k^`pH=S11 zUphG5vcN@~uA4b0E=8E~D8b#|fYzZB;RoXc>;@@p^dEi5}%;#{RAW9L_` znO%ePD9*JwH(TjReO-@pqtQ1^Rfac}X~ww)=T4kkacGs(Q(wbZ_c^8t=-1XOKF z?SDmojPo^)G$GyX#rYKHGa;lT6=h-B`NHr^tNpJ8mEW_&-{R-=SU{>Ggd z=O3%nf0g4-BdC(@w7An5XL`dKaA!0=5?naOoJrmgh0vZ@4gv10xU*IC>^dtDJM0d^ z9f~_A?t-{;;m(gcH|~76a{uQ=xbvz83$BE67O059xP3z7s{QYkiEH;ixO)GCGR4}s z3*#<^yNF3HYLZsh?&7$%=Hu!RAk~3eUH-4BS_XF&++m`}61gnyaz-qVyRsIDy8`Zt zs!i#Y1cjE*-%MuJzBA;rBT-`i0&rIsT6}TW#9doIR(01BR1@K@V;YXYT^DyfHF56x zh8qZ0UK`@>h`SN)_P874ZiTxEu2g}X|B>A3{2y2Df3P(!&Bc~V+sa1&A#>nvjk}GS zN*UeU;keu4ZrAt94a$*S%W;odbUOdH6z(Ly&cxjrcQ;&}4B+l!I8qZM!>GG!Mc77w zJ8JN@`^*0~ab@znKklBDb8oyh?moCb;O>iiJMMnC=i`dZ8Mvcyk2bXj;2wy3IPO8X zhZ^T#!$UNd(yY0Ml?(gEH~a6J{dbR285vXDV{lKwJr=itdmL^Z_jnUN0oTVp(P*{* zxMK{>_`9yqGPlI_1dSgU>g*RcGK>vtf>Y9YS-59ct>_&299RX!_`B!HN{i6edN07e z*n-FSn}H^Pko#u2j3Nm3uYr^+sG{c&*`ef-)V_ z3AuZNeuboxH{sq~(YL68GH=C|AwUiT=xh!54&1kK@5Fr!cO34cR#|tciFWVCl{WJ* z?!CAV;NFLOzYYr0AQ!e_%ay+KP|s<1!Qnn40-9?P(DC9E+{baB#eD+Tw$j`ut!kda zecFg;bl{WGR>r~d9zXXv+?Q~l*W%JDe!=iXTy^TBedlG|*KwssPw3fC_f->nO;DE? zG~gS!ZyBNUe_>XZ=N(*~wBt_1orJ54KPL7b?#D*FkNW}cN4Pfsx8^PUArnU6eqsWj z3L0TUfY$F9=KRt+=~uXujnEw^+;0r!{O3zX{GZ`>hTm(|NC%fUV)sY9wQ!}(f5ny0 z0X$Qv=Rb8a2dY&j}^k7xPReIi!1j3A6?~>&0P0iJo)qcMMEY;WqIl$ zd~Z5g64bj-y&3Rk#{+N1Ub%TQ;mxXXc{Ah9QZf~#_PyD9PHn^2;SIu@vsZ@RTzJFq z=EhqDZyvmUJhlIL^Wn{}Hcf6vk!x7I1@HzNzfVv&M%(L>w~)~09D+C02ptn8Y{g#` zZ!uG{xS<+E0>{+mW7}) zN%jh$VzT&Ca@H(!fwwl^E_mzUZI8Du-j;al;cbYwKHdhZN^0Nxj}cp&&W-Rk!PEJF zpIqZ`;&5Ybinkfw7DjJgdX4>RPBf? zXehBW-Uzv8Kxm1}I3w|nHey%2-S7^^+Z}Ho@D9T}0#7~!enr;`bP-R2sQp)3 zEQUId!7K2Nl{T;UCwRx<9gi2{oq#t6??k+l^y_l5OP?L23p<{J=c+9&KRNfLoPF`a z3j~F4bcENyi}C7sH96QI0VPsFq45$`rR_g8Y~p36Iae{eCfeG0OCq)GbnxE8>*76y zH-L8y-pP1p;GLq~P^QP;SiDp5PS@V&ou+-k-nPu^OuWnS&NBLJyo>P8!8;F6&;OK? zc<1BUu^&%Q2Tc|BV!YnTAG}LV!)2v_pgFI=yAn_Ce3mp^B>_vmCF5GW`|zZDj>Eei z?2-aUBtsw!C* zvU|Vr?#Fx7d>_Dj5bt5Uho)#20`HMZ$zynrmpYCAgoS#tAPN z=iQPpf%oyJ$NK>9C%g~wzQ+3q?=!rQ&G!>QBR&-@W5LrcV7xEzq=)L^f8keN-{9#2 zJKnc=lZ7aK@pNmz_}}CGfcIl5U-|xw_a~kie+~O9-fwt+;Qd~PDuo5{{=%OI?{8D} zPZjCEJw5)k717%Q#-9Oy9(+{ZpAmm{e7OQt=$Y~5%U?}7{;c@3m8wdP3Cw{%2w&Iy z@#pMC47m`SItQD#!vBa4`PD_p#A&|Cn<8 z#mV2oUxM0l_)8M)hQAcyd-zKeE{4Ah{{HyG@VCNW7Jof_5nB;|dHfZ01|sj{7ldl1 z{gv=n#b3GqXz*82Dh6tGRzm~8R{}lWteg{9pFYxmr6aFhQg7_``wnU*ENw8|}O4Rt$&QC67G$opb zvG`}>pNfA5{%QE93ttm2s1E#+q)}Z_=@g;Xx;hTVn$s-xp0>4&eG=?&{>}I| z;@>2`y?A?)w`h>s)%dsJ-_f5;#=l+5QzI4nPC-*BjrKA8yYU~wzX$(*{Cn~5Q(sA& z#fvXRq(8xtcoj7n4_g|fx*rv1>1Apk$A8Map1^<7ayQwWPveglG5lvt^Dz8p#aU^4 zo?v?X7x2Hte-VEozEsw0_%GqVVrpO3O4oQN;J?~aJLYBcdL92Q{5SC5>=%yZ&LrQ) ze@6r~3N2*G@+AC^@!v((;r;jU-!F3~IONr7@IS==$bze|5cr?qe}@05G~<$w|2h5_ zJ*P48zrz0wUwZ2g_}}3FAO5%alZU)IOvU2iLg0Ug|GjFE4x+tgF#eDDzu^Cb|Feko zgr!-k@UP0zw)#8%zxaRP|Aqgj%1Cf+tAFGFQ-)Gz*&u^y2&N^NPBPNVue6Y027*Ni zAeftAMuOQ1W+Iq{U}i~%NNT+Vvl7fEgoZLCn1f(0fUmocf!NLTKh@>*DSO^v)ScPD5f@KMo zAXu7UNrI(@ygJIFXp1DlG6che zNP?q!nuQsEPH+st7=mL7P9QjrK#ji!k-Y|OCnpk|q+!d%NWTso0-wMo@H8$l>Ef&W zfFRU}gkx1h5EEQTP$M{npiWQ_Gzgjm2|;RNpZC{%2~Lm^m}!#WwSUEnpheInXcKhG z*em`3f!cp<3}*BQ#uA)Ga4NwW1g8<4UiM!tjU$FH^wPNmX9`Mig0l(EBRGfP+>)b> zQ>;gDKEVY-2vb5?kc$YeC%Bm43W7@rE;F%9B}VhTT(qeM?HyMVTtjdb!PTlw5*7}q z1q-evxK2}I?OePFZXmeDh#LuRBDh&g&vGG=65L84rF|R0?V8^*cM?`R{+)tW7k3eS zMQ}I4O9b~2JV9_T!Gi=+T+$2f*91rqiBt$J)rSZkC3u+N5#>wTdi=)-9@khzQc%;Gz<2wo?6i{K4{H>KW%sr*D*``ZL!|A&rbgN;hYtxp z5{}51e1cC1J}3B;;4|&2Cf4tKNALx~mzwJ;`vhMT&P?zP!Jh=*68uCUmGwQr{}FuG zlZ*ySB@+BV@T2OtmM;Xs&jh~_{6e6XUlZTU_3s3Klww-Qn$y1srz7~AP-^xcf`3(5 z%FT*SRfW?E3X^bpLI`INuQEG?GZN0ElA@~2Z#WC#e1x+S&Pg~M;T(jz_^-ZJ_=JPX z;DmD#&SPrlmh5Yr59bx93fFwX`3V;yT!65ja4=z?#-bCCf3?Mi3zme18bY`*;ZO^x zi6>lya8VW3AcIY2al#V`mmpl8a7kTB2xb1i9HFlN5H4dlOlPgK$Q&*^Wd%vs&B7H3 zcO+bq@IQnr5w1h1>pz665Uy^-s)nnTf3hH4gK#avHC0==o*Aw!^i-Tx)r zpcE4%+)!6L!;J{H(CKuzG2teJo672qzTncVb!kGLo^w0G?Fo12aeB<12yIp$?rhl{LAVRyZkD8xgu3`IPj6u}+}-%2 zN)F+kgohFCMR*Y5-h>Ab?nAgA;l7gW^6iXpe-+kTj8=~3^uW?f(D(-%9zu9%-+TJ{ zDM)xY;qio$wc`knBs|)fNA(0$+c6gESV7fa>}AdqrW__DJc;lT!ZC#B5;}xAp-b2x z^avyLNUD@iKp5(pn7m%e@jTgV4P#xO3~PjS*+x{aa+f4b2s6S|Uwl-oDJbtX`Z^|O zXv2c=WJ3M_qdD7z_Jhcwc)dKgwHDcnaZ}gkuR$v#LC`To)xgo$w6Zc`x^5 zwU*B^fwKwE(F{s-@`|TXpGSBT;rWEu5MDrd1>uE+7ZYBjTczc8YIq6ZWtI|M0@5^V zA}?1$i%u5*CGS@eUTyNXl==y;B`iOPlM=8^f$#>x8?}u|?6QR_Z=m7LgbxzlLU=df zt%TCcZX>*1Tc$)U3vTjq8s2HqjU&9PjIm4u;XQ=+Tc++Myid!je1Dhq;qZYn{HO_J2C z7MgU57nCo~5|#Oq9wH52CVZ1nOu=h}6Rh98D%iKkYl_+pUpIV1yPMj9w+JT^%K0Dh zdPil-^8n!_rIq=v?2Z}nKG95s9}r3gKP3E0q9*)^@MFTy2|tk)0R2oR{M7I>LCv5X zqYb|x{8DIj%63&KM(d z1@#P4G&9i#M6(dd0?VvKeMGYn%}X>p(VQkA_dkf(pfaF^or`GhQZvy!!qKQBJ(s8@ z6U}e90Fkc$m!(A1PqYNlf<%iDEkrbwXh@|{(j)ri8j24@-c&70v>4Ii8hok)V;1#N zM5_=jO|$~hGDOP~4V$8eXgR446I)&rBUKaiJ^>?I$uzIrTiA(KC0c`MH4|Q4g{@dj zW=*2Ch}JV&hXA5=4AuTib}V=6SNRe{A%(di(e^|e5p8MCjfplP+O(3{jA#oYUHmUK z^z>-JtxSGvqHTz_BO0#lC(^}#;rHZ~vjfoxq8&|GzW=%O`3Q1w^qqL!xMkuW@Qbb)rVeB(e_yqEwvyhc0FU z8PS=@OGMN*XF;U^W%B)D4dKpTl3K8da;y2*$ei1Z+k(2C{$W^@bDZ$!5eiR5iWcj_c0x?LCgB0bBzg1@(PKmp>jW%%MEEA~s0t`Y zhXA4{44))=N|;h6Rq08?euHQ{(X*Aj7_R55yuUysVPDiBFbVBGOC#>Qbh-V>& zct-Im+XnGW#4{^hwicOY#Iwp4Gx2Q1vl}soZt=^bYUf1Sb37MuKk?ke^Ad~kw;LhE z3dQqTjPnx@Hp0FFkh5EHpExzf`P!05^I8abdAIqSC2T11!o*7wFG9RH@uI}C`75_f z$T#UaRRDl(US0&afu!vVuI~lKRxXP3? zTLxDn7W;2KMMJGgyb1AI#Ov#!<#=twbqv=vTu&wyMr=U55wY!?#2c2AJw=&N#TyH1 z4aS=iZ$-Qr@fO6JPZf&{6TO*RSq{Y85Pv~DoLI_vTjIlrw<>>RW@Fh$eW zR#`in_&j2@|HS7`iQSy%6JKDlsQo9l;UK=)XtV$Er9z8-Z=tR-zS)1R zplhrFTwCStdg7aiZ%|>e?eUG31-O~`3F2Fb?@wjr% zU^?$ozRWS=dx#$;zL)rZ;`{U_89B!y7t6#C5I-n{2|q;qu!b!yVf=_Fv~)hEW=E#| z@#CsZi6@C)Ab!d!^l8Iqh}H5V1DU+_$lFT%Tv_tO&ud1sJHJRgiCAP_wjeqL^oEc4 z72*lR?-1MhpIGfb@#}_f7`|!vmf_oirE+sl6twieOZ+MEd&D0RzhBA_i>>%@ioC`9 zv4#3X3r%DBjQH~@+Dza}VlmNQ5&udoE&Lk|Zl<32TjI&YKNA0+GS!5BM{LGFR^$J_ zTO(q5BNJcwE==&3GMg6NZ&koQsLgB~83M%q|1Flmoc~ap&O-f5Z5kouJ9=uCJB%spv1ePi+vjIrW@>Z7yo_QJb5Z z{{PqX==q;Y=lpWT0ks9F4VL#f;g`MG#QLc%sO_z`5VfVL4WYIewV~7&Q4?KTSa9%O z=gLEE(ZR2b(Djho;?$O;wuJKK|4{0+ls2%v&NV&DC@m)&Ym%yEOWOF$(|m#23N$5C zE0XR`Z6#{LS(#dc+A7qfa8{+JCqFgiG7GA$PHhcp>lnQzwY4h#+NF%8U+4eS)~mcW zpmsbpN%`K?Hl(&awT-B4O>JX6HC@}pa8tw01dZ6-a0|mNscj{M_Ku$KHpU;W&9b(w z(c6{2)OMh@8?_xP$(^X}Yyu-H&MxL0X}GIk6;Sd%irOA3CU3i%ussd;ss#3-b~Lqp zsU1LVKWd}RcmGnpzZh%I1F0Qs#6gvkL(F-oRor3Pd9?-)HbNB@eF-IBLc(FFp^_$nvwDr#9CctAQgH24Y7?lvL+w>+ zYW($>M9u7fP3^xq-=wDY-)Q*-q>Zh$iPS!zHi_E173V#Xq4vIDZ(XaV_TQKvQ8VLT z`$U{FW32d}QTv?QmnQRtda0R}q<&r29W@;SsOkKl+T=<=KA0_S@AuTj*#3ab%xgcI z%uj|tBP;5)U#R^_P49oBW}g7d2Pw7R^|O@PA2RP1zIpv+_&2qGdQ6F{K8@5{^cN2k#BnW*b%qS`dbEXtQrxIP>8MX1kCeIe>|P@j+bAocq1H`M1e zoXc=-!+8wn6*MLD8!lit*s#yA-%#^jzK?6w*N0FaYW#%-D-DYpXE8w|7N@?15xUw& zUC)17j7w8rrlgI(EcIQeFGpP#Nv@;5JoOc*Z%BPb>g!QoiTWDGUzz$U8cJnWHIyYF zq4gC&_@-)2!?mcdT}iHEGHU;gSlan^51{h)t<)MtuwFI{#N; zL+V>n-%6ZQakim8oceaux1EZ)eJMkIhbo}0|I}su$D=+%P#1fZ9!Xt_bXTjv-Kg(n z#O~Bb8L@}xp}wbTF29bc?``~j4EHr(ZQIoCMW8;~XnjqnDrcT``D9omuMsrjT0zq+m;VS~=l|4i zG*tUf{bs{k1S`qggs|RsJM}v()Sb%F;Nz&@WyIZrWf1E3Qh${Cebmji*YBrp_P=iS zzy6Tve^`aBJ*X<}+Ism9^~VjLFqtO}pE7*fP=|vuBI?glf5({5QGcHLo77*RF6n=9 zsur%3f9fw&f6W@aZVgbMK>bzITFCyL+<|tN7nyK&WsgjX5-I7V-Ss5 zE8p2@%uYjA{?&+8l5^6StE5Z51=LT#6y!xco*0*V)7Xs0C>op7*q+7~G=|gIl15nzTTLauO(nl= zrD3~@vjdG?XzWNs%)Z3F(^R2GR5BxJ$enNEyX#cGyH~z@&^U<3o;3EOu@? zRFeBvlKay*z@N9A|jE z;R%K((m07mOk)gxtf%LWs*c9dxDT2BHSVDy z_TN&X-S*z8==*6rNaKMi9Mh&lK*^!;2#rT8`Y{@h(|FQkbO0kz;%7SRE?+c zsd+t1<2f2{(0HE4OEhHvS8^owzs#>rFe>_G8n4iJjm88TuPReIome52f4!9Hi$(35 zG$tDJEyK5Iywmg5d`+V9K8<(H>%FP?AJF*7m>*Up^D&K2RN#Ns=`$LC(fFLkk2JoZ zA=$q^(oldM8A3(4Xn zvyv=GG8@SPB(syuO)>|`oECggnG&Pt>a*OD%tJCC$-GlEo39K3*BLR`u+OkxP@i7F zg-C{wEMi_Z79_I&TZUTHn2Sv%yadVe$i}PY%Z7twY2+xEI+y8{PqHk@avDUMfkk1y zD;Tb5XhT4znTNT$R;gIZ6@`XvV7s(+cdn2=;LT={?yP#>T_pFB^z1*$JtUR=SNZ!Z zc^v{s^cBE}he=)_d4%LCb3RJ)7|9bxKR#7I(pLauK27p0$umNmb9|NN=gj$h$rmQc zizKfYQ|#DFC7(pMfGc_e$*UxRTBB3+8)ZPIB--XT#-PBM{X63GWf z+YpfG5J2*NUp!t2qdzpXu^{=lO5vv@KaqS!Vpcu*oa76VuSvcnQOmDlHWFFbZ>ms} zNz~wzNRHJ0>l154BtIDbSV{g&qBlH{{6g}p5xC94>hCf$Z~8PW|&hmo#Dx-98xq|1@6Ou9U&Ec>hR*GFEs(v?cW6s}^ps&eG9 zA|+kjm}?lWSut%ONY^2?*?zh%>3SCGIup}@zzDe=YrGK~k#26p#-y7FVf3bkt(+>7UC+Xg#`;hKWYLkEIZs~sNrNn4bo%53(P;!hu zNJ~a_9zuGk@-+>IkseKYxa5&z!JBC8*DrI^M>9M5832ilgJn4y~dj3Zr z*|JPeQZJ#kJvgM}NnO%2Nj=hx)F-Wz2BdcLPa2X&q_GB(4J(Nevc zR1eDP@{yL61i6j$PSV>+#rW&BgvyVlw8xP?MtT?Ny`*=O%8|%hq`D<8%iVpXk61eI zH+(?WD@Y$Se8})&K}*=9s#BVx_PfVPpCWyN^hwQ^9n7#aJWcwH5LzI1hL-eM(ho_W zBb`Y4Jn3tsFOa@$75XBng;Lwi1b_1k4Zl@uTKQ6tUe?ChV*mNFG;`9N|B?y z+V;L8{aQ06-K5N{S_Vn#WYT{~r9yuv{f_hp6V??WE3qF*e<777^s^M5nq@()6jAuA z;crs5M#$p7)YPA(f13tf|1XQ(od429iRLskr_}^Er_+omF+I&0%Bs|=kt8)|q&XAK znN@>SjU-rBoSL)JJR3Q*(_Dk*>@-)RIS0*!Xb#eJHs_={zY%lMoSUX4zB!L^=2f+F zWuKjErD^N`OWcth0_S#8v2u)q&r#Y0SuK&|qM3zix>iU0g?XhFKwlE4zszL z1>D>!Vhh79wO%Au(nFhD)7+irHZ(`l9Igw7&24G!L~}cuJMCYxsp-7_|QC#qPYjnJ;hghUvn=Llgr=qfQ0`1 zy}2(<5!;XE;WYQBIa*V$stynhH0AzhN#{W{52bl9%|p~~*kAvoLan_Wre8Oxj6BEE zJW{*1wy~oOk2X9;LoKqOuH7|{qj@~dlW3km^F;aBq3^M=iXtFZr8&mXq3OywD}A&{ z`!wq`1Ddt|!=I)Z(u{N@YQ{PY$%!mM`3Oa9X0t)FO*65Kr!;f1Ff^Mqv!1aPQ?Ckb z3!tg@znRm{|2MldPopWmr&xD5S$(x%jHPMz-)yg*F=(Dn^GurM5Fj5NNU5Hsxi&LP z^BkJOIhW=|G|!`Xft8YO0a)!{sKs8I@#e)eucCR0dTD`QYIvF9qN}bVC(-);oL&=CS8|q-YlrTO5a*(yG<)jqq~EqG}=2Y>^Mz) zPtV=vdk@Wfg(*XfJT&j4c|XkuO0iPX#2%vgFipMQlcwGO+>{%ugq9;fg3>qzpAakq z(tL`h)Zp_L_L=^JrB289?{hxQXASiVI3ZNO*t+J6Wbe}aFWHVXrE9!J^JVLFdX*^6 z2{d0bLT-WX-JeYJb&K&0!#6cvm3*5_tl~Q~f2BE*=9e@l(fowwyOJuJ@6r6wi1%sA z-F;e*LtcHzI3F2)Jml5ijrdg1h|df^H~c~ls~GVWO);dR@M}Z++g<~g56$0IvfS=z{%M@Q4F5L#$M9c4t?O)BvPHOoaUNcvmS;-bK&TM3}lg(-L9Atx3vKPv>05sIxWM=#` zGyclaZ(hjeFNMj>{%3ufu&m$E27*kd2V_GGhZ?H=mq(%?Ta;`$vc<@jHm}9W)c6~1 zTfo^;B35yhAsc3q>iy3$)iio}vbBs@fow&x)s0@saAlpNW~&&kO17Hl(Ftl#{~8v{ znpIBM?hB=S)D8= zYmjAR30adYm7zg=b%2mZjCrL^zxT;>|JSs&4LgQiLGwM?3gi^BGswo0okn)5dQCaM zot<7f$<7o)9>?csvy1gkq~T8p zt$S5+fm?Q2Nto{yWY>~iNp_Wr9UujDwehbJEJKl9M|QpX8i(u#vKwU&%bW|5-Ar~T z*)3$Zlif;ooA5=rlqb7mDm~-+4_KH?8s6O&*LIcud&yoWyN~P<5hlBz>;bX|D-91B z^I_G~OaG%}&yzhy_B7e!WKWvF6Owq9Ibb`or!?&FC&->58()Qbmh3r6Kq+HEULbp! z>_xJd$o?zxWsFt4uaLcJViQU)vez`hCPwxK+1q4qRuR2b@!uhvL^e?+Ej=pGmR9;6 z`N?GOlW#=!0r?VSACmn__7T|^WFK4QeqyNY)`-u@J}+tgXL0r=*|%h0k$o*pbg$jta>f03!> zF9$f&^Dp@fER!sI&bCtsx0ufnQx@scpFCCS$#Uy6JM@}=gQjosM<6$1Q=zsf$9H}?A=WF*Z zIEZ{5@^$5`dez6JS~&qwV~E zKEmi-$oC>2S%tC{p?o*fu)EK}-kneA5N6Xn-BMu}# zl>8tQJJ|3LmDCEo)0~HqA6|0EkEobOkvGYYCO?V%7;^c@OR{;azO&@V86Ge97m=SZ zj&uU!lKe-wIyhGlV5w^<3fc%2Wd`}_2(ulE!r;?vWeh&HRJV^dD`9tJVp%0Tk(i@KRN68;6OQ0NW z$)6B;3-YAlQ&kF|A%B&8yfL38f6<8N4DIb8f1#=cN%kw`FNv4=+WNoz2QZ%?&fY73 z{#xbxI{6zWX0Ny9EINOivE#_!Vc;wBiFDs3pG4f*BgsFYn2r2H zWQLP}ME-;1g8XChPsqO`|CIb&^3TY>F#hMgUZVy3CHYrX8ori!4!I5i)`KQndj3yP zEJh{sz0QmDZFE8MAIX0r|HZ_9md0sJS^TNKmCJuOuRkcJCs+G#`u{Tgn_MUVM*mAO zjY<}F_Nla0x0IwfaS@(VgZVUD9rvBedgP5sAGrN0YN2H!%&Jv%xmHD^pw$y zQrP{UMQQ&fQ%g|jVtI;nC|00YnPNpLL({pE1=sSV zScPI$iq)p#tZw`@4A(SVi(>6kKv8P5SeIfwift(L=kF972pa!C6dO{=I={$lWGM50 zp=AreI%csM#g-JZH6Tn`|5v7jH1|!Vh<6c*i&ce(x}RV(1i^F#Xc1KQtYoL zvDi;g74{1401G9VJ&fWYibE;X{_EtwIHYXJ(hKzEqNwbDAxe(4P)8Z+`A?HSrj(~R zj^Y%G<0%4)6HMkr3Wwq(qsOScCC{9$Rj5bdPvKL96d6TCk(h5xQ8VGXVMDOAMfy@u zq$blWbyDOMg{mr-UW*n*+d_4esg0*gq2r*@vJ3FF`HrQynBr85v-RrH;xr1K{8OA^ zsPlh{vjqG8qxm)GIYLvMOL3lZG$rR#Twq>i|CMu*yyF<>5{ersE~U7N!WMsu%Y|Qvlhns27CF}k?LoVOa@Mp4=SLKn@9 zInF}q+BJn5e~NnqjZph<{QD_BrFcMJ42uUT##1~*@d(Ak(iFv#Sig9b;<3_+tW0Vf zexh^mT7$%?kJ!D|T(kz$np-PT=D@9aXvz5> zQ7ikuf;#`FwSWecMJ?s@(Xy3+RzEG<-D@pGYY|#QD&<3IEnLzSXHi;<(K6$&8kYE< zZ)tK{OViqv)-tr#qBV@xaw0%WM*-~)t>tO0px6JFwbojZ)=DO@GOaaftwL*cTB{nz zhJbRDOEs(^OljxxnD5%AZ5>+cmV9HbM{9i}HZauH30fP{+L+cxLQ9Y;xSszp-_2<4 zNNaOi+tb>D)|S1ut=3lNyS3pqhQkH5*|fGb+^%db=Cwmf)7q($*}3BELhBG(BWdkV zYgbyMXzeC47HW4DmX^~p``_A=)_&&PiOwgich zRz@qQ)uLrXfTh92YzSy|Xm!PD92){O$SJf=r!|(=spZuodZ|e3w34QE2CcJcok{B~ znPV&S9c{F<&Y>kIe|j7tXq`{%C0ZBIx`x(;w636~zko5}i)meADZkWE?Z5FauS)4k zwYROS1dWgjDjzc9T3X|1Ne8)&mfZhibDP!;v~Codmf8Q-&8F%W!&?QbHgmhl+(Aq4 zf+(5Bxr^4LwC<*LpLO1Q4CM%rS`4XoTK5}1VE7=dhbsQV7R4i_B(2A2Jxl9xT2E-R z>DiwrX+33PPt%gaKdRH5;|0Y@>$ytud0Hh%t-4`@x)>#tjrOy*s~_YB_`l*x>u%=ud%()!30 zer))Opw3}b&u6s0r}a6lFG?n@FKK;kmGYIKY11Kq*0+X}4NGhAozQ&~OB?b-Z&K9y zk(TtA-)a49lD`=Ks**CfYMK2nw`^K}&=UJE2LCVdHL<@XYZa|S0B!jMSUI$(qdgPt z>1od(POaaze*8;YKLnP?WM-y42klvC&qjMzZM40MWjxyQ7cjBvLJXokl=hso7oaT% zfrK-+g`LOH=Kt;aj8^+E0*bP6)*ei|&-nd@WsD0MJw#>7ea7~}v==kxBD5Es!q>jm zmdEUW*|XY9(f*9~(zLIoy$tQcXb+=3g7&hsx1+rr?G0!zPkUY3E6`rubgoEyB_mcg zT*c7re_QRpJo+N`;jgsUpuMK~u4TBk;W{c{t%CM?w6)@-*7T7^(w1jq+8ff|NLqq= zm8T%uo0MX-H>JH9?QLjpPJ3(GTWEvPfLj`FrScMRN)5J$)81CXPQ}@t_71dnqP?T? zB?__6?VYEhccHy6?UA(ip}ni{Y41jRca_w-9%US}|Lr}E-pkPJzqNr%azEOK(%xVH zj?^AaTPOeWvZmdztzDS*L52q#>MpPfl(T~N;k3`DeFSYYyzL`tA4U5FwYu%2X&tJ2oN`>>0^+jdq=OqjXlx zl=gtGCbgTiGunCCwrLmEv|6U5ZK(5q+G_u+HhD7bGc4>W*5Jp|KGkGSGdz7N$unu6 zRXSC_HrjJ&UqSm^+80W3(Sv;ocgQ zmW(NUiuTk0V`}oo(|(q=P5aw61oRTALx2U5s(F+4OGb{tXE3_xjepRE;V^KPa zS^8f$d_&OW-!kXhhVK|or2UZ*lW3d$Z>#+`j-3CL8u`HRLqXH=qbj0xfPG<(%f6$qZ_MaxN_Mi6OhW{9v@$XEdbZ1(@vcH&9-vatq`IZjal{z!h znVrr|<~6gS&Hp>I3T*5`_mb1gbo38%wlx(eTt5) z|91`%x~kd3=p1QYhtpC0Jqy!0iq3I#j;3==DNN_ssY03kFINjYC(=2e&PjB9tEn-D z4xQf5-|5Jl#Oy#Ppp(-HO&~Ii=``rnjIIl+>Fd>dLMJtTv*Ki$0BuMGoilU>+iB5h z(>aBX+JED8=?n->=j2k@=&=&Yh*J$uqa)}4m1Ek@G}Ix0&e?|N&^cGi!T9HuV#dFK z&Lwm%q$5|19&Mb91$*OK=TbV7w#(>TE}W{nT|wtclT_nx#MOq^(7Cqch$Nls>D(Yr zp`~7oa}%9MbfoCqOy?Fl4;Xzbo!cz}7mQ=` z|BeOM_N+hBGX5)c#VkyqD}Cx!x?(O~qcf4t>vZ0sBj^96N4zO$#9MUi<6nCt>)+-} z(=dtdB6QxRJCx3QbQhxYKHXXAd_d=CIv>*cN-sO=d}R1Boloe9!T(h5`_(IdJD(|` z0$&(@S@Ml8%gyHh9i9JMbd%{wC^Pt-@92DA^7UUu9kc%(wg2)6$K-8{==@6OH*@|@ zcRD(M&@ofr`IC;?fAi8Iz~WW=ubi$80bLyel;2h3Pj?2oGts4znX&XWubB;JDLHg! zvrx0sU6Ae^bmycyNPiP4ZBlNI>dr;CpYGgr`^-6y;k&|O}42F1#CS1bu*u57r9a-?L+GgsZ!=&JqK z-O276bk+WsI}~)+ru!edX8*hE($$$i-Sv85<)CJF1F_8Nl#_?D!`R)3?lyEcrn@=a zO)RcW4L2*}8uHrpbhi*;x?385E4unCPf4c=D1W%|w>8|(P>jEN8NDOjW9jZhcVD_Y z(;Y>31l?WDYZt?jgR`BD*iFzFz;yZ@POLb1(6g_l`>KHUfC26Qi_8`3?GZbbKVx-s1@-5TADZrwsP z1l6Q;6T{T7DQLdAVNuDn=(a0b?tf4j6Ofjp_Mh%4hGPxQ_;*hex@U#DXV5*fJP>69 zQf_AdyXTnmT$6l7(46Pfy`Z8mqE}4x@!L|ch?!}5)j=RN*TI0nf{v%Z!x^p(CmNrcBAhQEalBPj;_vOOwZkVT%>!C z;k}0Ps|exTFKEKDy6fq2<*uCn7nJk=f{*AK32n)b>6@eWsmGNNYutU3?j*WT(Val| zX}Ti(4BhAD1P)yhdzP-v{Pld=OFDjaUoeF)(zX622jsNvZB6%Oy00iLrK(do(tS%>ou%h(RV#Bam7my?wAWATx$iL`ed>J%#N3H#`k3yA((LJe zRCW`iKcV{>UA_F%GA_QK)BS_)7j$)PlkS&vzoPps-LK90O=-c5o;(%(9o?VghkA6s zH~hg+hk*XkkJ0^^?ytuAMbJ3ES#|s_s7HNH9{8uOU~>Y!O4X{|qc9Xlj>WU>OFMWMFBntbwHjr)XecSS7Y>$z))83$+3Rt1z%411psR z##~utUOtrp`Tk3ySgrIjdJPj;lYtEwSW8>%z}gI~%fLD|k@GB5^8Hsy=oYZa|A&E% z7}#)%jEQY*xQWV0!x-3%fn6EcoPli_*uuQF6f|Nh2DWBkxY64vQzmbw*_3a`!1fG` zU|0|f!+1glh`ylUo@}>1N$(r zr~VadKinq*^;=d^~Gt({8H#4$x`<)Gndcp;l1KRak74 zd{a;>sdY8D)vV55YL`;GOjD(?Tu$u@z2s0^)Rola@W1jc{`j-aT6skJ%qsr7S{5Z8I23}v3+C(`% zCro+t5MAvlYHw3}n%WD(q4o^5XN?g0Ih`w3(~sIDYF21xT|KzUH#&8xy-4jPYHv_` znc8cEhh0zY6~k8rP4nv&Vazv$GkC{z)ZP*_U$Ot6Q+t=%M;6_CmdN)FKcM!Z5b|hY zYHRt}WIi$c)KK372vKzQhpm z+MH8WzA8|k#!$C_G}-m(#LHx+r>_71r9PwZjp* z3sIkw`uxV9Yv8#PsLyRU5A}J4FnS2}0V4(t=d1V@`%puh>D6`rXXs0B2pV(Y!EIvd zi%{3W#W;&mU%XE*VVotYFJ;8i)XnlgcY-)AmgT6gPknjnt5aWr`pVQ-q;8gfvQ??C zVqsS`Tuo3O{cl2j4dbjyeO)7L3#h&}b=&``=8$z;2-JtEt*Go?eFN&JP~VXHPSiKD zfEy3IJs0&&sBb-V2T8`J)HgH!=7w7sZfUrcpxo4I^frdu8g6H}z2OdqI|>fi=_%?v zQ$K|IF4Xs-zN`7}roJ*X)OV+D`#(zWNqsN#-MjJ?H0Qq5_fw9{=<53$9$w(1ILlc$neg)Q_cpgwaPD+5l0vwR!y*;Z*5V!&N`d@OZ-$3{NyX$xuH5HVvmz zcc`C6eKhscsgE?~873nQ&lbh?5vth+LGvA@PA%uNsh@A0bEuzdg)qjPV-3$!G3^@T zv@ZxPCa6B1`UU2Dq2Wb_7aM9{rmpjUG0sLes7KVBM(g~ax@Xuj^bG^UP*A@HRhc&R zSW~;le}}$oOY(Y3{T}KW^&550U+-Ao$*C9AucTh8Evk17FEPB-@G`^84X+TCD|U^( ziu%>+{6Fc2*BIwo!|M#q?$vKFdV*kYxR=qbeiQYZssD$%%%7!^+^UI`;ii6@yq6T( zn732EgZf=Y-zjg$`h5K(j4`D=AE168b-nywIFe;KRBsu7P;W(3bMY|s52!yv{T=F$ zQh$;9W7MCd{h|N{`tzC| z`)#rK%8_I0FHwJ;`pf;WuTX!Lx^Dgpr_X#t2c7zx;-vnTHHo(?feNX=OZ`3S?^p5z zuWw-q_>j6;`?`$|^^dE}QvbBlO#L(JpX-D{bM%FV)k6LX=M?H+qf${pm5CsfECi%RhVbJ#$WrGo7r1aHhwZ5l4OjtSc`GB@fO_ zRd7iL&MY{y;;f7_8_se#y6nQ4!_a;N=E(UUTQ4|sTvy^HRTF&G<%b29R zCfG{%nl#k%I4fErSCDcuVkJQp(Dbi@vo_AEIBVdnhO>IbuLN+`#92!VMC9d>15wU8 zIGf?Di?gZxmK|q3oMBct+U#*Q(4?x^hB#*bosIjPO)6`kPTA^F;mvWjFk;JrOY1mW z8E$R3ji3?R8g7TPJ&qcGaaO1ss?JV0JLBw!vy183RS(ZNyQxWWb~ldNe;nQXF|WN0 z_r}?$&ygj-aVE1r&fz!*;2eT;AkM)!YX8k^xEL;7Q;1Ut`Fg@R)bOz0n-1p)oa1qh z#5o4%C~f@C(Sjmxw9WsOd0fTBIRWQnoD(aZI4AWqTNF5_;*7&N4QDjY>Ds#|4aYeH zM>ZMO$2kjU6wU~ok;2!$BUWhAaD6I!<(!Rk4vrds?S0OezJ{@;=e){-;hc{X;EczS zlGl%aaV|8x$natfrA*VWg#P~>r=iCKoTj0R)57so(yStmUlHaT4sP-ojvW5fWLVgk z#uPXS&Ko!>&Yd`!6>kUUR-7E?I-COM3Y-$>QkW z&g14g(NOI_&Xa~u8LIuqdB)J@|4!8)pA(KWMMV{OzIP(XdC{EqQrJ=ZkMj!7YdEt1 zuSFqfMWH7}&G$_lF`;kae1-Ei&WGmv4$iwc?@PPFv45IeD7B;Kf3&4(8~Dh|;bWXn z&H0I{RsLru_Fr1gH#k4y*h0zC@4w{1`5xy7r5_nI=YI`(Q8w#>zCNr?HG?q%i}H8EFimF%ym1jWaWiSu{K9J1dRZgy@wZuesLOSs1j1(4W0iRM}Glt#3Fr;?JqSJGv?wn zmY}f|jU~lg3rCQ~(iPE{U)Cg-qp^ISvx0F}G+e2#e-#=!rKhnfjn#};UC@X%4A(TY z`F~^WeyDZj<8(Ji0nLEl8db^>^6c%Y-_0Z{~NIbjXh}WNMkn|I|;`UV7~xp=q~_h=>7jDsrJ7gO6|Wf zZ3)oWn}*tdO=}{JCulrP<4K(}G@h#XfEoE7Vt$HFR9w6{b4bk}ATGSW4J%PqoH0*a*jjw5Z zqw;1vX?$0Or6KqKR2qJy@fVH%>cqS86OG?=THg41aO+#ku+rgXKtEu_|x9JG^eCFRYhz1o72#omnJl4(9|}kr8%9Jm`pI5)2l)y zW~4cj>Xa_foSEjFrf?R+Sq*2SIXlfcgqFe))Ky+{F7wh~j?$b*P#;an5Q}$!<}x$~ zX)a1rwkz+YIX}&XXbv@c0h;>eS5u-h4e?#ra1p^?K{XdM&f+x1>`Qb@sIU~BCUPl@ zXldP`)3D3ZT$Sc>G*_gl_TQLl|HWPj(p<@KWy4jJFO&A>YBblTxw;9jL31rxLC{qD zU&Uhlb(Gd9)}^@~&0#%Xd6%us4QL)rb3>Xt(%gvVmNYk}xjD^EXl^R6Q$<+sIcaV- zc&6<4%56~-XzCKc62BGAZ7g3~OAFC`_@)|vT`D&9{%4xo8}6V?&GJq(_o2Bn%{^(# z<)8D>+|_V5!`%({5R?ivdM~9_Z10Lmb6=VV(%jFO`}dg#RPx3=$Z&Yy>kyhJ(>#=> zyw#QRKaA$#YAc#Y7#=AJqj{7pOmqU%)cYTlmOYl{v8KoNf11ae*a`a0Z}UW&Hu>Lo zabuoB(>i1GRGMo42R=QE=IQ!*VDk*aGj%^fx_?v3aIEPZX?jM{98L3FnrHWGM_&Oe zEe8ggW2*LJQJhC}9LO`1N&yyqso4Gt<{D%{I-LW@<_jtzXe#?+9p03cECO@iO0n zW?9kIE<>40HWg5_?)|P#p|I@t6P@04^r)!1`e~0F^G_R+5-N3=y>M4tZ zx21W*zvvrP!*IEdLmI=)G#{dQ3(eb1$*qDG(SK;(X&jqQH1ANp{5Pk07fosBchkH_ zIr0Iu^;wg=&+vYl572y2edUdSrnW}&VVcsF9-%pr=A$O|7)_o0>xpZzhhpKIPw1ku z`6SIrG@qjREX}8BKBF(fs{CpXmCQb;FJAS2l`rL^V46}{FVTE~ru_VOf>g9VqE)XEIVJ&{F0`={FUQ*7WLOOx%QC~h|lS6>9kgF6i_6|I?ar&EjVPLDe$?hLrI;LfO~$ra|zTB>5T z)|*PR;LeIWJML`%KXZ;s0C%p6k2|*s&m*Bs&k)>KaR+eE#vR1n7I!|}m2v0CT@rUF z?xI>^?gF?A;x24q7ZOx+=PuH#FHPrSxN0bjzl0X7oV3({OG)A5E{(gKWye-A?y}mh zG}Q9AE8?yobnmMTcO~@_J(}xPa5unR6?ZL*cQw88#a$hD4cs;LFIRSxk1^NAmAAc; zk#)48y6dV}_2+1Jm5j;kZXzB6SIXdnE2rGLlw1UAV_s?)3j(xW@^qm(nNTo{oDW?kW0a!959A=l|9F z1=Dk?p$!3IqueuaN8p~R_R5w2zsrcGXS{Jo8jivpU0Fr*dLH*2+*@(a#ckt`!F6!Q z;?{A`!yS*SFMn~**Y?nBAs66YBy9ut!hQ?67`M0HuVng?4O}0$X&PMImNhW1r%JLQ zz1Wpb+A)F9(6)eNY39bb*W)I*SKy|&Ic{bG9StrmS=(x1oD#R&ZyT5BLd?C?3gj{^ zB~AS0g2uTL_gY-D|E^TZH7a1uQV0{cPB|LHe*NrDz`Y5#clciiXl-dX8{VP~KsFWK z+i;)6m1g`1u0(C$dAWC(*PXa`srj|rA#m@(y&v~pX?wW$Sqrp(ZMzTP%9p>={2mnS zStR#iCG?XbN#~=uj|pM)^z4%3D-9N ztx@Ux&9%$lv_-vSdAIxDTs{A15 zco+9QEk+k5kC@qTXjg{E1ly8jH=eO;Yy% zdpk3#!J9(ky6{$QTHaK6vi~p4Y4E1SqiUs!vQwl2)8j3GHv`@rcr)V7f;SW1%&OlE zkv0HtR&6StT>c}dm;Z~egw?3MIq~Mg8-h1C-n?ojJ$VHnB4H>kZ# zgUpXNR8v?LwX}0@LA*uq?C_7LhkrD7X)xZRcuV6ghPNc19{#uBHvd=OrBtliZ}gTi zuVpI^-g3RSmfi|@yW*{gw=v#Icx&RVthM8l4lQNOEIs$LJD90O#Hwteo-e_~`{tuo`{*5?SS0COOL35th=a0iX zUudl(mA^nzg?FK$9{$9;Sjt%iw6yEeT=8W8|2e#dq1gZDjBxQhsSAD7*T>870=!Uc zT|ce}udSv}izPNp3{ycVF}#kRL-TS=&vV8p@$SIu;$4e(37)j@OLflaU8b`s?{b~; zc~=--iFZ{sC)D88+SZf(|E9H_ioX<5zqGjy_@lFGvQnG<*9e8 zptL>X{KxS2-aSs9Pxa-WR!Qw9&*GU?_e4O7dXfrgJCWA)g2_}iL0Ms)bg8nf8f1^_jbP}yn`ok$;V$(p}I!HdmryRybtg`$NLcP6TFXf zGtK*0P|8`*oSzzgrn_yzk(+6~FYu)8eS`NE-q-p%Rph1FoF(1-TXn9tveDn;{bce# z;QfgAUllVYf_OjcUXb?--fw!xo%gH8IQ-Y4FSqf2uf!z3cz@#kMQa|T|Hk`=)?Bov zpfx?MDQQiuCbT6-K(8K4Yh?dCg$LGaAlhD2G2K_$-1FoYt(g zW;2fM4z=bmdQSDNW`-?o`IVE_ytD>s4bkS;8qgv&t01l#YLI0Nmo;2Y(1_(}tXl+VsW6S9#TJ^GG)m(2j(B57^YA|B z2q7f8`MuRF;L)^>k@s1HXR03gi8HO^Xq`ywcv>eIb2ok4beN1mZiJxl8x zT4U67x6YMR8D}i5^CY-LL~9%^Y0u}=ifE0eRW~IU(7LeFKuc0}u`#t&^^pynmSda- zt(Fl@T5jb_%Tr=hK+ErQgbpj(_-$G_tynm;lD>@X1+_9-oqtIdv~-eBtEAO6;u1rh z{}0)J7De^FoYob6`by*I62RiRhSt4OwzRILbsepnjJ}@M4JJ0h@J1EhuWG$F)3P0& z)-C3BE3MmTspVGzJ0@!UJ80cW>u#g(l8B5j``@GQqcxG1+J9OP(0YWH+JAFO+wR%_ ze!xd*J!W2yS2Fq`RZ9wa60IkV{}iofjZphfOXvUn1XntrQ;z)dyCrt)Ws`Y9;<$DC8Fa&kx=&J>?5RY4BeXu0!i9!j)-#Z35pAc(lGH zn3>jh1XI!ao?uE^Kj2HJ`w@R3TK~nLgVs;@)6@DHe+pW^(E5Ycue5%r^_%Fa4v>hI zFr9zW`rC-V)JrYQKT1@-{*?IB;!lM?jmr2s78pS#Z1i+W_xLm5&w@Xr@n41Y`f&COT5s`-Ca4qKT9Sp`ha+P3)H^(D8j`1m{G?}@(? zzM5S8o$+_U-*s{!o08qNCHQ+(VNGl={QdA{^Iz&~AAB+X+7dLBw0ULjkAFbrv^qTq zUy?T*zlnb^{+alP;2)2FDE`s-hv6TAf4Ip%VKE+wf0RZiuYGmZqdgS=82n@LW%I{6 ztVE{~oq&G|{)zY})VeVH?fz8Zg-%k>i@=Rd0y z7WkLqmsKeIF236TN>~v8GW^T&&F1?$1c(xRGyXmL8vHl#uf=}||2q8t;9rk_GyVApY(6_u$`wf0u>5v!6~K2sEtb>t6i(g^z!q zgfg)Q@E@#bl~J#U@t?te1b-s_qxib{W0H@{a#ED&vebV9|0yG$tkQt5#=kH5EdERQ zlK$uMCDKXiWi8G0ynrv8KUy#SurK4khW`rws}-lO=XH_SM|1rq{tx(X;eU?*HvWhB z@8G|W|1SP}s=r_R_#af71@S+^|HKOIV~cSs{7>;eQ-R4hhWTIMe~bSmzL|ahE7K;D z-&C<%t$kO~_}}-_{3HIa!o-(${*%Qe_Wu_RYu(2E!O}t)XBmQJD`uawyzy5sT(O_3l?nFHW)Q4G zuqwgk1gjCON3c4Ojz^hY_f$Cs?0g0|FfdDtTjWOt2Zj zCIp*`vntq1z1oB2>GChLe9DpfdiG zQ>8;dKgcx}@LI#`2(A~R@+FvH4gN-gI|*(g_z%I&1h*2X{jX#M32swbyT-WUFOoJZ&C%A_|PhY5THJ}9d6HGL(2Mix1c!=Opf`B_?qAYg3ky(B>0%%BaN$P13w}7v~p??EtAg) zz9jhKUm7aoU)AO}1mBAn!M6n8_0p^<`GMfaDn|3w-{VE`Ya3;cO31=Xjj&S-)vPzPL zol!aR1~#0Ta5lnO2xnCd)(1^sc0%3!sR9~jF2bRNa}y3R=RAbE1nA`@93Y&JP>sJl z5|B{m|9yG^!bJ!dBwUzqp^Dj){9l?E6At0xgv${wLAWg8l7veW>Yz}mG68)B(9uL4>Oiu4$oGHC)Ybbwm9NfDlI8SP-tQd$yW}bqO~m zw9TS$7~zJ-T;Fg5L7m2g8<}%s!%dWF`$gv5%y4tVEeN+H+|%f-2)8z38^T=()&3K1 zN4O*5_ClL;hw6g}LLCl_v$NQ@KD{g9ZpPfb&)K85=nnTX{@#QK8Lnwzl`uo6TaNAHw1{6IV)k={1?e<3GXuEI>PG-Z!!7?!U;kceWT$`7Vu_4 zbKXjLn-Ip-c5cM&hIbhD=KuX%+(Y>Az?n}F-fMUt;r#;(NSAzo@FBtn2j<^WpEIA( zXMSnqkC>8237;l>jPMC_K2A7sV1x~VvMC)tDNJz^J|(CxFG98dgwHC!SK7}J?N2y~ zP>i7j`GoKV!jA}FBz&9jCBjz-Up9G-!h*j__!{Axgs&66F35># z2>&3OpYTtjISBtEnwIcyqNxf0A(~PT5Jpo7_Erax9L1SdYdM-mGOJ1=-T&8Z(r7xO z8HuLX9v96Znd;>$nu%yuqM3Jci{*(nBU*uIU7{6a1R_MVk}tLbL_Z_C#9}Z9}w`_?npg0;WfAOSE0Zwl8M?f__`w*$|x3uZ`&we=^Ky)0@fka0U9Yi!-H0UWf zt@MMfRPDq_bSTkbL`N9s@PVh~qD3k2Benc{v^1(?2IOXMt&U@d9<<@Wh%<-2{Hdkk zc%l=C^vhp!o;0|z)X2&D^Gf+6OmvD8GEzjRarr$&r;|%WXOQecbS7;n>a&Q>B^p6A znrI}^sKLz_7z% zS`#hy@kAF0G4RgA6%t)UR5!1S4Q&X}Hs;V?f~Y}T8fug1K_ZtZCGv>O=0`0l1nYDG zQ9=|FwTU9Vs6qY-D5wn}mIBd;0!FM9&hb{ntm{utY|vs^@x#W25IQU!oWKoR^4RH4Qoh5WUiy z|3t4~FRg!Dv@oBX$R@^3P-|1Xkj(5~#i&@v(j zu5Bp$|3a@zd%a3t5>I=5;wNZtK)ea<4Q0%ty%FsjX>Uw>8;e5to6^>q|G;?typ;Cl zw6_q;pck{Wx1=pfwpQo3HS|&DwzRLOy&dhHX>Z?`+=2FveR?M$O!+RfYqWQzEunU! zeSihuowf}JZMppOdE@M5xVPaxhWpaq&q{dzN=Dm%`#?>k7W+Z8htnQO`(WC~(>{du zk+ct`eS}4NnBn0A7u+huke7Znr_TRrA5HsM+B*3k^73YVuj4A3_6fAlpnW3k(`cVW z`xN2RK3RkZ51lYLT(WSgpoUddrwa~jIf}Lp(8f8-aD==s7R|K9>s;ESjj8j0BhC?= z?@&=ShW7c!8B6=TzVA2*SRIX~J)X7>0rCvovWQ7uL|cu&a;jth?K*9-{~7Iu2{dVk zM!2-SN|JWVI6mz_h`y@Gm~F$@Fi~GE+f)l&K5%I3)YrUn+6C<^j4lnkhL;##YIvF9 zn~0|}=FPNkq5UN7TWLQ=`!-|# z$MAMRBkrJmrxAD2e!z&kX`B6ToBeOA{Wt#oJv-cfu+MqO_%;NzZ3t*T`oA~J?Z;_P zG=V27c}X7ar;PJ7?Qdy6L)*rS_OrC@U3~jF^PNQdRoYULFVTL%I4`OOYlr4E``>;= zXgMB0`!(8X@M*tJ`wf*8zLvik|F#)_;ViI+$-HYQ-CXSd7qma1{R!<4jroz`$ASYB z8%BR>_?e&)pI4_5Xn)z~d}aKvX@663^!B#)cYWXQ&G!e|Khpk__J3(htJZfu0*^l@yf)j$jnNURDGZkuSUGOmY6NFEZ#LOM{5yps8i*5ZQ^w-#&r$XGnB*s zk_I{4F1Uf9NE*G-z^Iih)W*t@bymD7@i^kmi1#4goOmbVEi9HTiMKOiE8?w<&>_Q! zZKX!~^!CI%^l5tqaG_Mz&cwT$1r(>?@4?t@m|D|hP{dR(bb%0 zXJ6vs#QRy9?@xRX@d3p8UlZkFXYUrlH=iqTe{kBm0x_C}P>?m!cR=d^WNE{6(Bqxe?3eztY5G ztw7G~eJL5APb>y-Jn;?07Z97Fj%6%X5PVDH7we!6%Bc>QG;wG_M z+0~w{i(AA2v9D3gH>)6VsBKu}wGi6GUE)}e2*wF41In% zlPL!u-&pzAJw$v7@fA`y#FrXgMtu3;!Sa`!=M2{;zLNMV;%kYeZ(bw08@yAlOj&y9 z>QcPd5zEO>y}e}c{1LJRq0^Fh!oV{lh;OXUkPzQYEV0}|{DAq&FW^PyHe#uxyNRXh z?M9{z@ zQb8{ezbJ%Ohn-m>Hsc?w@h8^hfDx}5zD{iRzuNPO-y&I@_-&G@h~FXpg!o(1|}Y<)-M?lFOwN0nXjUaKa^xK zk_AZA?3393kCd$#|72m3MMxG^<Ims3zYU)Y0B-x5YcYd_| zO*zT7=CvKk_Pvx?lOs{%PqGt<8hnzSNp=yUpB~%%Np{!9AnBLxoa{;ZKFMCB3z6(i zGM;1~lCw$nB{_*?KawLz_9r=nzO@2EEU$9K{sX_2_b)cL=dJEMJ_T_igHSEdGxNZJy_d}9)u z{EKZ&Qj)APgMG=I7kHlJ1(Fw4l?Hr?y|>@;%ANBw8sXpOAb?@;S+8+M-^V z(wJWueyL$6?}H^@TLFGUBJW|V2BS*=WBx!o70HhzI@Krnui;N5y`R5J*CzRu^d%Rpd`TB3y_Iwk($h&7B^^e(80m_ni<2&^qeHrc1zgf_Dbl4ipbS0fGJ;CW znj&3JXQ%1%hAZ^$lTTM7U6*ua(sf8zAzi~XtV*i(pLF$Ja8p9MrZH`#NY_@i#+L+G z8rG9oOkjP|{YW<;-JWzqQr+hv-H3E!Bh>yIv8gP7)KJStZn`GA- zW6ph5tom(Dx<9Ejm;0zWtksjU;egx@}6)i}5H0d#w z(}EvMD)~MBUrJ8sYdeYbG>i0P!&694RWbYiPPeADpw1wTNY5lakMu0kb4W*!o=rN^ zf|&77M@uOAv5|)D)yTP|V@bzoO$k%3xif-v9O*^EAw8dTJgL0-Jx-j)xv-KXy_nQ7 zPK~s#z82Ov4brB?>yrAUYX6Pj`d5B+2p|pnd2EwjOB$1AqzP#{@VpvlT`|e%oOb#e za?*nIa?+CYQqnHzCDjT}h3$PA>19gK^9tz|q*s$(NqW`4Wk~h?@4)M_>Ji`TNGFh9 zPpX%HN~_a`r!n40dW&`Qnr{P_Ocaz?u zpD3pH3JS;Q`$-=meSq{KQr-Wlyi8tR>&OP|nbvC_?bpTQq)(ID{!eNqF?~{mP3Eb- z%rm6V_Vr6j^yZ&_H@nsHuN&POK+n$w1W z^ev;`HhhQl-F}ewR7_3H2Xv%%J|vUeeMBZk<73j#EZ$EHjd=EbX_XKaqY%D$QHG>=R%meiWy1^!=t0Ka>7u#4n__7_ZI`q`#B? zP5KAvpOvJ^{3U2<_=jv7vMIz%otX_u*;Gc`03oAE2HCU&PijZ#i%m~98`%tGGs=#Q z6p=h+Ga1foI1AaVA}_S5nw@NJvN_1+{9hWfxhk4$9%aScjwlvuyWJ{7QN~Ujn$rdA9T+{H*UyAFmKR9ATGQIzy z(kw`}Or?r!IkFXvv%KL7l?>TRLKtV|zT~Q8=aQ{PwkO%@WTJBoGX27rY)vvX{+8vn z$=0dp-f)(!M>b4bex@aFG8+i0)yg&^+nj7;?PA#`+Qp0;_O%S|XF>MYAd>RR@M@an2&m*@I9cELYx)oAU7MdBMmB;>C;wzekex|( zB-znqM~OC#QH75oJGQ!X%bcR^cryFLV|D`Bi5Av+q6+IjBC9lzooZgE8J=!<#=m@Z z|EDiGl57;2{qQ#%O?I}3Nfsp1-YAfbAuGtnlDTB(kzGhO&T?%FflMxco15$cK}n%S zWJ5!CvC-1r9Q|uRR+sXZFVx5yWKF#S-P(_Nd1P%9Zjt$9L0?Z;l_XhISy!^y#1gWc zEG5(5{*q;an&xW8vi@<_3`(*a$hu^j3$jbdE;YXV{88%VazS}a+m&S3km=^XX}h`_ z0nJN?7_#du$n}z7b50<;o$N-koBHK*Guf?Vw`el-bcfW@ZCZdbYs&N&@D}P0vU|zY z_?x!7$nGY)M>R;OUP|sGdyq_i{`DU#mIrj3ND4|5{E#VmScO&XqjdHsdyLMMWRH`5 zK{k=>C6losAk!g$>?y;i$(}JnhXAswi#=!bB*W(oUoh+~0gQf`>^(BI|75QU>RxpA z8kyODrQaYkLG6j}lYKz;G1-Ua{HUsZGe@70eP+y01^dS2 z^NJ?Z`9IlLWIvI8O(y63McX%Ib{$0a-Q*y~x3M78A%INx$@|)VCi{i#cQUpAWI7!D z%l8kmKl|zYi|lWqEnzyzq|+Y)I#bcvgwE7-Y_i&!M(ueAEtAf)bf!}cN>5K`hCZ#M zfNGO0bY>nHzb~Cx=*&uIDLS*!nV*hM{^`s?XC6&hXHLVp4CUB)&*syam(CEA9O!Es zG^eftERjR$EJ#P~f5kBk3(;9voJKD~XHhzf(^;$%5Ts+nL1)Ryq3A44XB9fj&{>Yo zvj37?-sD#>T#?R7JufXEwg1LnmCkxbtVU;bOVt`GrV*`4XDvEv_UWvxObxhhMHq7! z9m{ObOYJ|M4GlLkVO;_ku_>J$jlUV4&6TN=TNrLh$BchxYjbX6xUHd1{^@LQxPxGC z>D1YYj#SFdboQiU_P=9GgN_=1tKZ$}=={GLuPyjqhI=xoK9yXoimiLk)CPHvkXV{m5-uxHl5Kb zW6|}6&o!|zbeeR=(y7xqkB-#cI67j$pQCd=o$+*J{x3($^^}6Xdgxqac(J~K=)aZF z&35^fQpcgwP_d)85v1eNu`{0?k4{VH+a2F9&>6Tq`Z=>OBRXw537xp&TT4jk=r><< z!Wkxt3hx zGo3f+d_d<-I%@o7)YE~l^ERD#bWg1FuHk!z?+dDcw$cyj{7B~`I$zQGn9ipr@QDhj z?`L#AS8cCsi5=Ud}^Cz5o&r}Kk~X-R4Qy4hX5nyB%hmnuF9$J7V~+?=NlMz z9l6{EBsj!yz)-HazRp6;FY@F=4Hqz6P&G_y8oe<2+T@FnuSC8mxtetH#mM{j|Kzs+ zliU7Jz7)B&z?xACtyqAN-Nm$*&-H$P4lYc}U(Q z_e|1NNlj0S+!w+EN|Iy$fDol)P(E$QF=h z&1l{HG0Dp-Ci#`dzl!`iy+1I&np}qfavdP#G3NDs;Tx2r8NZSIIr5vxr3>Foei!*I zx1ZyNs%L94sBD#DoWD6Ls|x2jF@_bDW^A5aXCe@Olp z`A6hGk$+771^FlBpOJq$^p%t4kqtER&jtJRm*n4)e=ZLo%w`&9p_p~DSVfrd92ENh7n7N* z;#15+F~of5oh)ergA|)l%tx^T#rzbDQVccz0)`7xEKISG29ZhEx|bMpkv_4QITxo` zhGGedr6`sZjtXm*4H+pS~^^I7EVqGKFqZpW5{^EauTv;SQ=CdMN;nj!QONhd zQY>c}O2@Rz&x^CncZA_cLA4SVqeMz^HpMv<=W0@`iM^^COHrpdkK#g#aTFI&oKG>n z7fRHMu%M+7pb4Gxn%5Gn1)DJBX> zK7f*xo8*%elPI2|c-HdrG{rL-(29cM|Kyiv6!v$j!#C5FTJb!^3-U8l(M<7TwPd4s zNrR~GD-`chyh`x~#cLGy^G8u7S-yB(iZ?0VHLh$>!+AFZfm{%@<%;%AEADSqiYWeZ3O?Kf%jgCphpKFRVQ6n|34wV%S5`Tui+ zCv8IU59Jg>XxhYCPDweHYB*^V4N^`+xhy4=b5TyK*)Q$im*w=7GxQ=VXQZ5kawb{o znueKGn=FCLSq*2?C9uR;&Q3W;pPo}=SDCpf7p9ztawz4zl=E8@LnsG~7!=fum-7h< zJ-GfU`qPSX0m=odL{cs!dZdMz{34W#86lG^>!Ic1mZT*pCFNrOFQHsYP+~WF8Es>t zR@y_k9OW96%TumOxdP?Nlq*uMq~%~;+w`oWYOB&NW&Uq{pmdWOR^wT|dtU(h%kQcAIJM7gmBk;F&~*K*#Jax>K|)h#hfzbLn$ zJd|=v$~`D0mK`a#rreHFicxm{tu0A#X>aBBlshQLnvM2?awp1NDR-varPrLqci6X- zyHW05#VCa!K`8g6+>dfE%6%yJ7Gcf47>aUVP1`XKn#}%`!zmA-JWzeLf)1+EY4pLA zhp4k^@WVXH!zjQ=UL6H$O`P zPBc7;@?=$B{S~D=l~Qm1F|X5=uUR-l+PvO~Se`|BBjpInF6Btdka84di*hvO#gu1L zj-x!sYT;bNF@|Fe&lA*SNPWo%nC1DDC|3fL4KWK14;Zxp8c{k-?DBrgr_6neU%V>KAP`+dIyOj3wSF|l4ee(m#?tGDy&TW#schDmn@y0g)pk?t&XXQDf^_)0*HNDfoUo=I~Af9Tq-V|N~-=QXtb|L%a%gNCY;uI~Tz{&?G6fUa%+$VyyJ zE_D~u=rlcx&|Q)4qI8#_yBJ-2`72&_?8`9mC?nZRCwcs1m)%_p3n;P2Yk8B!rH`nE7cMHQU>29S;l(RM6Z7N#Og4ip7 z?)G$d=yP^7$(`u#TuIX1ML0@pq3uR@ce;BTt-pY&LYZ@Kx<^|P?L&88x`)x-Pkia_ zZ+L*AzW1Sfkl}E{gAKI_(X}lgD^GJCZg_;@k%mVJYME7W9jVi4E&ow;N7I#UpR4JfEok!R znsZF$H2OTc5#4cgTXfH-TcbOk?giqdOn>tE;_Q zrl;ph2CrdI?7xUz?c^<%YaMOLxZZqa^G9TEbV`i2$^W2E{u#WLLEHHsyp6%z{}on- z2^ZI0j>+I4g9U>ggEE8;$7k?+_YU2X@uQ)>RcG)gK|M`3_zRwxq9dCNo?AiN$cb3DO32z>}nek?kvB2bK#WN%C&1TNo4d=j9`!A0x9K5;lhN#Vy zA;TNM8|rAypl9}9ykz|H=EFN2Z+^T@@D{*Z4sSuc#ngs)3mNJsz<7(`^}hc#=HhtE z;w_==$6FF_X*}Kk7pF@D-ZIYFYg*p&cpKrZfVT$Tig>Hyt%SF-7L-gRq)5G01a&<1 zRrAx0^jwv$S4s z7o8h;dJssXQ+jv2J@NMFjq);xdwYo&-ri2Y=zZ}Hz}pXRe^*w*v{F3~?=ZZBWI}~^ zFy5hf`u0Rytmt>|gWM?*?+Cmr@s7kh22cObV!@A=w_}!uWAV-xe^3#grH9I9@ovMrMlFeV zEuNnF!Mo1zdRHcRH{#ulcas!Cza($L(|sXv_BprX-EI9*F1i_mcc-QHF7;LCAYKd4 z!>i-fdZCoxz-y|EoU|V?M%t2fSYJ??n(+`%_y1j7cx`G6;dSty$4l@;LyGq}UWRuM zUXCX{v2gnFO3Ou8!iq8M=jC3!2SotyKD_%a*AED46j}oEk=Jr~592+C_Xysj%9n)M z5qC%9J%RTWo@@b0@4Yq?<+%_|9ju4 z5@qVTz{I|%HU-`fct7F&h^Kv47cXwa^nS+s%_98;?^pHeDgPbsFT6iYmDRgTo@)Nv znEz0lObDfG`U+T;)TX31HMOaz$<04UX{%mffBm4QHjO4*KIc)H>8Q;{ZF*`mQ=5U> zjKY-Qn$+4%E>+rPYO@&5s>PzAW~Vlk+8os8rY632^N;B7`3|8rAhbp*zo>9lu{IC2 zd8sX6!R-iWO^*Ok)9=45*$ZkAR}Qs>sclJZ5o#+_Ta?<;)b!=Ai7ifT2_u#?RQs=w zs#4=mZCS^mwj8zPsjXQu{NCA#yXtU)}^){wGF7PuLHMONKM0r)HV|0Uz|;-ZANWVC*b(hHWz~07V4BC zueKGn-KcF%Z5L|WP}^1wm-PMGc82=@7iv3DlixlHv7_p@9!G6wAuQfqC11*POS9VU z)b^mZulBXto`!o-Q~PhU-2b_*(9~@HU)!JBLDX#iU(@-&KAIQp@+M|W08Qs%)LYaJ zrzXX91hpHf9ZBtSYDbya(bUeSb_}(XsOkJ)m)^DGsOkG(qfelAqNCN4)a?Cl?G$Qf zS@2V-okr~pYNrp3*xtmpr*@_gN?$xmQO2^`IYLkyLG3bX=c-w#oo9GHwM)(G0%{iy zjM~8Hi>O^JgwZ3ZT|#ZtzH=oq;?CnfZDCp>Lzv@wcDxPP3;bI-bw8)DP*Db(b67t zG&RozYzR<(gIdctO(#!HMvm5?Ez^Dmi{T23vS#4#Bg0#tvOn%Zz` z(jf1l_As@3&3T{U{nQ?`a`jmZwUa0!i)Tg07y~$5YeLBY%G3qlI zb4KO3QmxPIe5uc3Ub7m`W;nay9MtEeuJ&J?`t$-0p*}!;LF%^uQ}69aP@k9jFbg;z z^#!QU|KCu(fD4)L!qk_ez6kXtsV{0Awg1!?r>>X3X{erj&;Ch-2{8rhSc}avA(`%UuG}rYX41aAL_RMQ&;=%ZVS9V&>j&!Spjkecx@b6ry4?IW5A{P057Vi>d`>Gfg!&QGk5mor)8P8iO3U>T^<$_X zt3Nc9aY~)XQ$K_H3Di%~rChcQPE}E#HtYR9qyaEtB&G2;fmB&r&>t`DCEKAba zhUW+x^IYmzQ$LUTCDhNitm*e()GwrdG4+dtBThvvoRK<+%bgYVQPgGfuQPM%mr=jM z@^ZNjBK801a3F(%=I%=BS2@~paSipGsb5R|dg^Nb<y+Vn>5#&ms_Y0 zQooh@oz!pBwMPAR>T3M;&Jeo*f%;vVO%=Y|ePux1v-H=f>pvf<*Hu8vu<4=`r0&yL zk-DVtTk0Y8SExtS$54-{KSaGvJ-4tO!-RTjL?+lTw}N_Q%#ykt{_NGbMDzgld#THw zyu^N=peD?H0U`gctv{$7B_5_en))NuAD3#P{;1(&uF5Rz6V!G7?_xC0Q-(JGuj~BZ zs`6POjQ(t*d`VH!D z%86E^-!gn#aA4%Bdd#&x-tb)~V7~8DH{)ObkorX9Oi+3GX|H?_QvZm$4G?v;|I|O# zy0}m$8|M7nP=^5OUmAWz{cGyqNaDq*k2Z|&Xv|3cd+L8v|AG2%Cix?E`}j+XREB_` zssGX|ZH@O=M_cgUt%U!e{->r&Yv(VQLhAp}n2N?^>J)xsavD?gX|?~xgvPW+Oijb) z|E@e6)6tkgH8-Z8RCijxjhSdHLStqcbJLiGh8)lyL64KPZZ5r$8;N07!A zG`69!rE;_=wxY51q!OU9t@51#Y-~^CP#QbX*n`H77Ir5ZyVBU%=v@R|-py(MIB4wN zFO5BE$QxdXZZ8^=s(p>#r|-Mp{||crjRX6|u3rJrIGDyE|80Vq9JE~Bs~{P+GvcNltPJuhLri0G%gbXlewIR zP8*EALeONcqH#TqtNSw7(72Yybe2)aTzqLk#t;GzMwh)|a`R#vL@& z?9;eYnZ4G2w@VuhIoRORQ?mrrY1lx}Xwqmo&3*j=jf6&Msv^VKg0yL<<)0)>Bc);1 zzM(?^jhu!Y32?DUv2=UIqD6EMjr(cb+xNY%AJ+qpWBMPWEq&)qY1P zG+v}3|Eljf~b-GKjRH3BdGt=`ijZbKN+S_^ibUtH#PU8!A zj@J~LDmS%gd`;sU8b8zcR`;43-x+>yVmA42=;WV<&QECkBq#!c=KRIbwtzJ3?=)wo z@dwRmX#7cYN*aIBoPx&RG$*6+kA{^R7d_3%rA*Xm-}DJ#%&82aDZlFO`O=)$m^%Nb zIX%r8Y0ltYbh>vLG-uN3jZB1^v(TK&#Ac;wlmDho{+n~qoYTeYwwh_qO>=pgLuk%x z@&h!7(wwK4FRL<|!)Pu}b3U3jv2V^l@Wkmf7ofSI1TlIc!-WkO5j0gg|99urX)Zx? zIVp3ROVZT&Kh32Lmod~OfZPTwsDz5GKyy`^E1K_0G*{6L`R2;*WU{L}6I+eu>NMAw z#4)k8Xl`Qx*QU7+&CQJ-Zn!SZ_4JFX=K3@@)5g=>z;HvFHveyKY+{=jZYn6gf=Xzp zEr$Ag(cIE-E5oe?-7b?kw>8|3=JquAq^a|Ns>=Dsw)pt&E-duU2xuBUkb&2woUNb@9`whU<=O!E*4Yi;0An#a&Q%ykx; zhnvnL40Q=W^C-ik1$E9WOONKUG>J1kml_)@1l7J%{x_0J|nuob%DFZsT}M6G(DO%O}6ap$y%n_pc&I_(v+*$ z6PhiWz6#q4LI|3nVdR)J+ca~Uy8lB{ z#k4R#?{n1n)BK9o>@>foH6_h&X#P&qzK(BxNAp*j-@$XzM6^1sd1)fy{M{7x1^V3?yI1A8PP{tiv3mMAcPhE}~ZGEJr_TQX~8|o1t z3uyMgwY1RYv>~9iEUo2~<3^m;3VmN20$MAXGM}L&T4)6HE3-{YfW0~8(+78 zXsu0aoxZ%;|CSB7t@T_eli9#td}PRX~QD*3Pu#-|ipL+J%foVB#$P5uPK6Ae$AByY}BXq`&yG-Yab%>K93{?oFdp{4ep z*4c(?|9itw>s(rw(K?UT#k9_+bs;Sq0wn#t7OPu8YJ6HFX{qtoF4Gz%XoTKZU^*{1 z{12@wR7Om9>q=TzNkDT3=6emkM0YLzG_kiB2orZTA-c4&z2$3{rjh57JomSiQ=oJvOnuaY}z7d)#BSKnH zpVs>yEJ%k|ZbV|38fJnrjM6GBU@2%sSNR&nJ+vM+;$B+!(R$Eo{C-*wxY~EQpkF(s)|<56qoqefX}v9| zFY;RN&>BzcU7=l4l`qNGl!)>9h1Q3(zM(aN)+ffCNb4ghF{3}02kCeEr{5b4wENqptB=-x+>S>j&*(wyS1&(SWpU2x$H6ih|a!wCvbt zt2YGD`U8JTT7TkCM(ZzH&g}oKk=kG(3BcF+Kfe6@rPr2>Ih7$=y#CadNFk=h-xz;7 z{DtwS$Dad#2K*VNb~G{mO!%`HF|*3MM&-|nKbts>KfB8>{+#$a!^fXX%fX);e+d3S zFZ)s&{?OhX0{*=C3*iqF8;Cz2{sLNM{`|dpoxh+)WX(VcDK38z{59|w#a{w{F|8(P zy8hyQ`6VqGOW`k%zcl``_{+E?DQZzHCum7p0e?086(u@zu4K3}zS@7IR~0mEt1HLV zvcD$&2KZ~?o1OR9w$!d;sP^9o+Z6Kc{jaQ*M8B9#e?!BKhCa2N5u4!ejlU`Ww)mUj zZ((J%c|R9hN=_~6tt`!38*bxLZv5@=&Hnp4;O~sTBmPdBT4zsOAM|&@*YiJ?m)-Eq z{`+eGP1~M^dub4@*?sV(_V>ltyPfg(!`~nOAfpe!KTvbwdXRsxC{(9b>!J9^;2&nW zJ{+z&{oLM10-+(XDd-WYc_##xkL4 zX*&)74E)ozuFZg(a^3$o-?Oa&pMyW*zwJbu^m+Ix{`vUQX4L-UUx+^v-}e92DW3q_ zXyRXjZ|6T{13_v~?7V*&{w)^da(qdWe&LIM1^$)Byh@gz#*t3|oCdM~H{i?VZwKRF zFEXmfEm8d&EyzvyYW&><2LD$4yN$1-0RHWUci`VCgwc2PW-|UDeu(cGvxeWWDC)Y` zp#hutK7PwN%}Z}(7cZmL{+n2guS15>9sEQHCx)Nlk2ZdeU*JD#jkCn>;y;LQqlSME z{(boOYN(zaxF27Qzi@h}iFpYBVU>}q`~61*^-nSWWB5V zXPqI4{~rGP>Kr+hCR^@;@INFFoIo%+{zUvg@THG@hyQWEJ%3`Y`cwSR@V~;>A;36l z|M9=nw`odz-RFN}&Tj|i-rI=p4Sz8F5&vg=4Q|e#^cNL9`WJjV|L@!Re_sazc|^dv z%%AvwSrMu6$G3qXm`tZJOnQ6Fiq#jKM>3(XkH5tEJPsZe;yL2lh?(shFXMRQ6p^rFXvk{3ri3zN3f*j zZYhFg36>^Ure`tCb~!x+%UjqL2v#Ookzgfpy2Y9{6&ZbkRWzw$27}cI&LLQx;1Gf} z2sS5JlVBYJoBRiBYd~iKg5d<~6RbKRBE~@Bh@&lM~CqQ3S^l98GYHDiNkwcv;13z~dx#-3|&)AUKuaM1qqo(vwt7 z`KP!b#ypL{%xrMF_!{R-0-gNpKoIEszt0~*a5urZ1lJIpM=+A$e1eMzE+Dv2YeANW z(qG(R@8Du7TipZ*E+M#-V3g`{3xwb@f~&1jT~6>Hi&2*V1ZMw(t5nkJ-31}Imf#kG z>j-WnxSrq!XT5t{UcpTS`twI^6%wPit6K@~Ah?a-cGtZ7J?Kt?yY#;dawN}^N-#*! zA@B%V1T}&NL0u!&!qmAjfew+n;Sl%)5kWu@s;|Z+Z{(#UV}ka;MEU8PW>zvtkPvhU zQi7a7C;z?WXHXE7E+raVB`dYD!94``YUxRERd^r4{RHC)9w3keJV@{)fp$`YhY21d zc!c0l{p*1w%>Aq+c%0w~NB23S37#c*ir^Un-TxVw-?c_{|2-xB;n@SQQgCy=iC z1Hq5Zw=eS(!O#Dn^DDt$1iumdE&(MY1b-0xIVrQI?QcRg`vm{=^n{bEuq)wkO2X*~ zry`t&5Ut;EYCZNQFS(SSb`ovQ=?!NfoY9Dx1dW)Pa26wG73_zdop3$EIS3apuQ?6p zBAi|BE@=V_8!lqFsNrIUixVy( zL|@NR#$1|k1tXR*T$XS-BbFDO6bs>sgew_;Wy4hnSDhr#SGc-)twFe^5Js;>xV91N z7!D`2muu$R54Ap_6zSoF8xn3yxDnyjCcLrXCWM=6FAFy#+`>4U>-ViP)eN^J+-k_) zi;AySoLKm98zJnYOYz-~@Ib=t3HKu0fp9m%9SL_aF*yq=!gBs|9&G^Ot}eLI_90kk zKYvu2J^M0y6Yfj6kCW70{m|@xxIf_m10%0eBPNdqIf(EuLY@B;9zuAi@HLdY^KwfM zt*;{pkL-nvUQ#}Mk>%aTFD;|L`e#}l4Jc*3yP<&*8PgeMA`F&t%hsi5WYa>DDZ=p_AD5ME7qrH(k^Rcg#_olba7pSadB39l!-)k57scq8G> zYVgCG1chmQU0FD%dEHL<5aAt!9l|>a8-#ZedW5z>2nSWz%>lxi1y}q3FJ_alMHrfw zPbf8ICbSnMB8&;!ljOyhFd@8$FeR)AGeX_)A=LYy3FSuUH&j^ZuAl{UZRuXZ2lbMl z@IJ!(2_JA5H>rTT!Re!6ggXBxe1uS9d6e);b3R7+xW)U#q_h!^CKOdqnSj~<|Cj7% zb+!{eM<@+L?Z1i165vHb%exFu|1Hm#%=cwME6-PnW+Qx!@K?fdgdY;VPWT?-8-#BY zzNyFc<>*wX=YN!5KuZ1{!tsRfDo5VD+v|Rz3E$W2Uc(QZB;f?YF9|0Seo82Hs-wWb zgx}>M{6tWkmXgm5&Hjg9sEiioSA<_HEjt+DH-z62>Uh`Zd{6ijq0M{49|`sFk9xU@ zO87J3FXq%HWZHfsG7BGSq=bJM{z>>3;orT^D09E?A6-EU5lv2{J3K^F5KZZ7foLk@ z(05KlG!xOZsy~{JXhx#xiR9*gYY&DZZ@x44iCKtr5g|-dH9OI&L~{@=K{O}P{6upR z4JDe}e21u{Oba8q|4pZe#+-*}UZVMowk3c}U3y*%5G_Qspm=FO=|s`OM2itEVze&n z`uxR(c21%tiIyi?ifB14>S$@hWek@U)T(s%&P6K_tzz-6Xt)y5$}+uCow7u7XBVT@ zh}I)oooG1G8boUot*HXiiK4Y!xmnnC9MkA^|3$A)v;om3L>pR=jrv8cOMpJTsd?!V zfM|20x9EFqMRcZB-_}Ii5b34LMB5T=N3<)^_Cz}o?O*|S6ck@Y)uXEbqFo%5Xg8um ziFPO2pJ)%FJ++Zb7l`&E(z9^d1)_b3_U*NNG1t+4LYtBUhz=q;P-MjE1c(kMIz;=9 z^c3kJ(P2bKXn%?1M5rE>Q7_eh6wxU}M-v@SWb5BZhXA7E1beMi(|LkLbRv;m{vVw@ zsb-CTs^MuwrxWQapqEY6a~9EKL}wG-MRX3)RYW6*#NeN6LCzC&rin=G|0P5h8vi00 z%XB!4E+!i3X#J=#8bx#k(WUyYndma2%Qd*t|C#jhowXvp3)(V!HPNj^*AU%AbS=^K zMAx~dD@b&M#dV`9>9yyZE%+_HMkq=yj)`s~x?S>1WSjqyZvJZ!<=jmqdIpISB9Ev^ zR3mB-)m`%t?6;E^QAFeu{nz*tg?-=HQqm^sObTe6)G#y5i3%Y^fT$$uI>|mqs`5b- zyVvkO6T6@20U;(y5W#2(e)y}Un8^ghuOL@yFON%V}B|7a_arwpHV#X_VC z)%Zux5WN(|9MABx&jEp6E)dXH5dfAk`qDriGECH_>jx&6{pOQC; z-XXHXpOGE@l;&!UmT0_HtBwLh@AXbzMIR7-X&OExnn3g!(L^G9^BYMWeN6O;_-dN} zHGY}A+5hMZ$0z!V=zAjl`HT5}L!|$IA^J|7P6N>oL_ZPz=r~07u3tOI&qTkP@Gt$~ zV*h`4bU*B$#19kwMJ&zcZ{qEU{vlqFcrxPIh$knWTAO7&1@V*;gxHLKj0W%9cH(J> zrzM`5csk-4iKi!?K?TGnPns3PGr5vfOCHZ+II9yNo}G9e;yE;_@tnj%jF^jfZkJR+ z;|v%M?WH`Pmv}zo3=%s~OrW0Qtf( zUXyq@@mj>9aBU5^!7PG~P;(KlYq*}{6K_Dg74e3|n-OnBya}-xf7dVKO?zRrYV6~$ zcnjh!rBr)`to*HswOsp#cr=M8& ze~h^Y@!m%4X}Fi5<$WLGeU)~b>hXTWy8ojb&D4R!#}FSRPU3?dpZE}S9!h+e5%&H! zKEmiD4RtUjK3dRK5b?3ZCmZuP;^T?c^6N}HK9SgN{+Dq<`lAfo@hQY-5T8n{hyN|e z=_>4Q9*ECW+O2WovxzStKF4H67@GZ$)&3KouaZ3t7ZQ&mzR36&8;&%*M6h2z67?0v zyo~sA;{Uign2Xo=wgnVlW%Sj8;zfKd@omJmSctDDzR7%VAU6ANX*SNy#5(yW)&)X8 z{kKcaZwl=92W-Rf`#4kBX!5*Qxe$`U`8u2*dw~W>;bYd}CZ@Oe#I^QOKN7Jn3Fy0X+ z_8#$H#P1V-Mf?G=&hv?N3y64v;Y7oadi(0}C&Zr->!9FrMEp6iw2Cj>57dO;&&b!r zKM{XJ{2g&`{y+3AY5d=tLYx1`Kgy!poU&1w81Xal@5H|dZ6@wl;@=!?zJK&P*PnfX zziEpp{)hH-v?rrIHErGhp>2iPo>IkR?$Msg5JB^u#&BBobv>&+J?$BdIfF9A4zy>Y zJ&O@DJNst7v(lc;37B&Z+H-4zY|lx1E+Kl&QaSn;7}^7dL$#r{_58mP!)UKfdp_DL z(Vn07qB^l`FJQPJ?S*JBVxjE(zfC(eplVx;_Tsdcr@aL2WsI|=;ZicJ(_XqaxVM+3 zEnmw2N2jLp$f@SGp8u!4qC}yOGZF2Tbpxcm3hh;CuWs~eZkL1h8noB!(`#vdUG22j zp}nDHd^l~HOQ^}Ey`JIvhI;d7(Ap*2FHr&Ke=KsRkjJ8bvbzZ7-(e{=mzm?(E zhHC$5Z!6eao3ytleT()EbS|X5BkdOLooF9IduQ5v>$Y}#7uvh(^<3@UXzwxf!HV|o zg2vg?a4)%2P6)*{zNWp8Hvaa$wD+TZfYJMFr?CAW+6U4;NH>R7=3prDSJBqP|8j;+<8lLQ`&!!9 z(Y}@T^*ViR-$46j+Bed^Nk5B|mmM#BZ9j3)SA!~1C8Pg|T1SP;1a zI#7XLrXDt#M^vXAOl?0#`!m{)(-!4V(AH0VX+LQ=n)Xv#!m@d&%x7pnZ-jli-qs<2 zwj2Sn(ZBtIdA(?fnbW+aFyEl9_Mf(Nnb&B)qQ2UjUR6R);kL&KVFGgg^9<`0Z_@sd zwsf5F#(&%J9YOsRz5Ooj_i4W;PJJ|0AE=l8sg3po+BO!nC(^bFr|o}=0PRm`oBfwd z$+RQr`(N5$(Ed^`*)su2%y+bq%)PKs)Odv&8a%m z(3y^o*?%c3IkM83p3cm4bmZf&u_iD(9i9AJ6k10@ z^tP}%wguD~qBkyf2Ivem4Q47k^Eye>Fdv;&>C8_@zu}^@0G$Pekk2mYEJSBvI*ZX+ zgwCSc+zV=p(wfV=xNI%@p2HFh@8 zhS%9p(DJykRHzY~7;b8~8J*3I*g|mN%-N0Jiq6)mP43csYIi!@(Ak#G_H?#W%U~mg z4i+650(R6ja%U$&^-_8lI(yRDmCo*_O74FfW5gbkBRPel^PkRMbPl7lH=TXdz-oKg z*Xm_IItSC)U*9fu4v-@XbPl9*kQT4~&BXYJNRlj7hYDJ#!|8}Umxgf!og?X-K<6kr z$67reZFr0pyR;>(+2iOO|1al>bo6U4Iw#RNnU3!KYjpAxXW52Q{%MA%I}RP4|I;~3 zzp(3^P3K%X=SX{?GeVnzyrz`hDh+iWo%88jAhfm?dE_?E&P5~-(YcspQ#vE*lyokk zb2pt)bgrT!S-xBib?35veTkkcP5Bj0k3?-1dNrL}=v+hRTA6^U_2^uu7O!(Xog20L zcWzKg<=iArBW_mftzNzPKb_m?+)n3Ct4H|}q}hC})4Oy|A||&pNGGD>(eW+IH99u= z@7UzOqsE_3OCyqYa)GO2wZ?QpsVqID(TVAF28K()>J@NSEIO;SfT>|-m>U*?I%;&f zbY7=Z(Rqf>J#-$Xb1$9y)K`;tzllj*>lJWx?Ebfoo&WFIibv=?N@p}3{rH#8<8+?T zE+(HYs`E)Dv;*45zn!OhUdkCm=XpBM(s^#^@p=!x+!|^Ldx4IccRDZ95tKBH)tg{D zFUc*b(xDXPy40~^P9^bjJCNXOhT{Zf5U2A7$trZ-r1Kt~x2y)=*1=u9-k~$zh<61o zkHUPP&IfJ;r1K$(SdR&Gex@^#&c_2{v^yUSd;KSCQJ>KHRNIC!KNF6X$>(&wumpce z=X>LPMdxc(t|j&j9qEAI2`!=IG07jySCsr{s$}XV^iO?XvH!nWbb9~49bzc7%?S@%1c+0LEku28%{$qJ;}5r z)2XkVDH?L7@n;~JvB#;eY@C@5XCaxxh*?QyBbi-gL}yQCPV<_JWNzixp6bUnVEmyZ zi<8V_^t>bs7%_}wK9c!WUSjX1Z9$SnNERYl`2X`4C0Xp>9MiCb;gTdvku2>r2$C$* z*CzIVEQt&Xf-?UfYs89%D@iLNSy@M0^<9-@b?ryVYQ0rnvIfa;y`DE&lVmL&WlWj@%AlYs}tehC|WP6ewNOmFFkz^-nOPUN#^Ui{@=pfmZ zWOpNW(=~|sYHBqldy?#B#NN87O!hI{SJ#r;3^)G%B!?Su0Lg(Qhgq@6_rFp&2a_CP z#G!)vtpBB!n+$Ww5hN#)97%F4$x%a(`$YR-ax}>?L&kosW?7>?PEfmBa=hUQ8maP6 z5;Wpul2b?|;Hf0~&qtEew5P}{T5Y=|ObYpIBhFI0C+1h9KBw<{F3D9S=aF1Qaz4q0 zrt^ZK2R<%@q{p0M|K-mwfzh`AqX`&Aaw&<!;Cb^SWJp2YTlk{d{L|3?hI#d0&rZ6vqo<>AS#Lyvq~AEj?6xs&7$q4hC7$z6^n z86+u4JQAOzMxw^wv^7XtBuyLF^fs-2*#;yjNl4NriAZ8Sp(knBXUveX_mXr-60I*e zW-#yOI;bUT{7s?Sf7K=vo}?T&{Rv}A6MA6ip`z*@HT%iEhWC-&FNBu7G9T2~-3%~! zm~Bz1;>G(~}oTv=XhIk0p7D?#aJHqRRmb`>uMe`H*hiXpjDYM5_KnlFzNZO(2;_@(GD_nUA#uY(A;8<>XV6 z&w8g=Wmr+p7lxwbYa_l=%`(O%-)O+I&lI#6za#mc0~52|0nrJP$h*Qr-U{oxQI| z|Nkqr9A6ps>cdJ%OG}544mdvP(7xn6r1O%_H*og%q{Cbg()mdjB$fUDc?RCvOq++M zWFakTnZKorkgi9%DCzQ~i)j|5RiulHX3`}{mn2<=bScuM%}ZA3cF}ISEUDd;Xiir$ zq$`lFXv$Y2U6pj@Ugt_z(Ui#4Jzb4-IO*!7Ym(|q0Vf~?=~{+s8?Ga03DCaTJL8?M zPr3`~2Be#iZm6e-(~U?scIH3*b_k_!I_(gR2j5=qhn)xyj1k5u>nNe{979%^`)pb>`)>It9pNYYD4 zk0QN*^k~xKw1=k0SeB0!TFrGT`#%!n38ZI|o=9rHm61UqJ=yrO1te1=5jd69jK9*S zlj;&fPE(uLS)}Kao^9zlN3)|cBMi?aJ!^{ACVj@d zo-%n^0*tj17(@Ep&=<#(>i&N-9&eEl58j{ams``i1L>Mt?>69qHHF(bVf3(r+DY zL1cj-0e>L1Ie*&Q0wUGPzf+>K)ASd^UrB!xzR|xM{vl`@{vs1Q^S4Fuk76eG{|HWQ zIEA3BrxZn7HWe9U(;IESfXSvIo0hEi{}(5wYO@*0W;C&xTtr6CLNO%ncSjE$el6M3WU~3wf zqgN$c-H6pBF-EK*MBjHUvdzfWCfk5)9c5~H4mVuaa6QBIdl}c%Zb-IKKam?-?3);F zDyV$PwfqYvQ{yjPn`}$6t;n{~&X{d2Xv}TZq`3XkY43Y&Wu< z$#xlfEJKevVb~kHknK8b%+Kn)WM$!y?LoFb*`8#3iBp(ldz0-;w$ISZJ{RY(H%5vu z*?#IfDkM9A>|nA3jd_qVd!Y^?JDluLvcraLGent}9V`)%9pNG}`J>2ABs-eyc(P-R zb1d0$D$q~Qus3qD6VxdhG~ScQq#{lk_Qpr%JeBNpBTjPyMxP;QDL;!$?o$4M>};}g z$Sx-vL3X|lK-sxu=V`&pL{|&{0CCRbpxzjdtc zZYH~>C+5Dq$ZoS5yq!!6K?3Sco@95D-9=`<^T_Tt&Y)Jh%oeg5S=|j)#&76$oGc7A zREx|fYm-T9kya6EM3Od5Pb`FL(1z3@OUaTRM+8QUA=6&~n{Pq(09k1YyJYv0>6(%3 z9u>H3c|mjP{Xb;)%bMC!rN-Yl4;em8CjWnzFP>bBeT?iavd77uBYT3(tbO(**=VMf?Sr-dMU#!z?g$m9s94KCU9WUrZKdj*i`6>wz#LuRLivzN$T7LE>H*(+qP3L#F% zG0Aa;_SsSPhS6^ds$tK>{!c+B_J1<6@nrAH3Q<<9WMcn+HsXCUXU_KihqTR^f0t4hA@4KdmG zWdBR{1KE$FaA0EQLYao2l;cLO>=&|M$^Ihy&4T|YDa zXFj=jNi){$=Tnl;Nj??%Y~+y7N7PPitZ_3TO>#!^meaoY6V8f8{f4 zf5~TY{i~P#e0K6Vl$I%kn~~;okuO9(H@VL3$%l{+kk2Ep5y^)N%9fq+?G=DqLgr%s z&mfSLF7l1+x$O2g#2*wL&^2#Pm$LcHSb4|A4PtobBcZF zXYCmBv&fGnKbibE(|^3lpP&iQq@GBAl2c{;Q%vU6zS`5sPdARuCvz>X-a0EkoBTX- zo&TG_2*Yz#pr`qKO@^lJLh{?mFCxE=Tt5DlhCPzpX7c$Z=G3o%$uA|pjNB&t`Q_xc zl9j%o4fskCP|a>_l3#7|*N|W9IL?dQ)<r=6j>zO@{UhnEV!U8Md6l+xkK7Aos}c zG_ku3WeFhDa!Jpi?gl7PBTtN|lQ)cLl82VC7P&8k(HhE#h`dc63q5e&X~yXYsT$;@@ zBmAC(=PeX$I9UubBc+3kNg#@WjPcob^RLo+vMZO->@X9{Ws!G z^0!o#w2eu-viUpY<1N&?g3g!xee%yN$Ojh1hvXB;KPI0@{*flb5+ed8^NGoPDrnBn ztx4PdPyVH3O-3c-e68R6l|E2fRB)3;5 z`ETTQ`DgxzzXkI%~>_)MZF?TlHg<@Cd`>#;DQ|v*pH^rWg zNwL=?8H#;O)xLeN{VB}U7Y7*sK*NJvKczT?;!27`DNd$1%)AaaJc8m_iX$nGa-~FZ zG{rIErS)zlLvb9%i4@11{0V}tRL!Zc0K`dg3dO|~r&3%*aT>*06sPwUo&JV$RoNYe zce)y(xLY(^0tVGI6&^)R<5F6-fYeM;G%2^IXicT+;`ZEfO=O~`1ctJJD z7|{zWv3yD~mf{17mnhz#c$s3HMfVDY+JB>8a~58l6tDN0Z&JKN@s=^)7BSVV0^=#( zr+C+KDBg2?hbHhL#RQ5^C?-;f#nU%duBM#kfemJn#5;M4&ndnVn&Jz?9^Zn9l5Z%d zp!k;JAByiNexvxF;#Z0vD0Hiz;zx@ArI5`ZdCegkDIe%Gxab}J*JYhNau&V#-Gu)z z{8N?7B(V64;%`R_s#iIgp)LV*ty)e=xisZeltU?@oQZO3<4Q zT;+@!-2T@?IkP4}Rmu5(!C5JF|KI4@4d*bN({L`sxebRH%J*Lf>wb7S59Oki^HMHI zIgE0C%K600sWQn0`f3-_g=)F5pl~P`QND5(>l2GpF43o#G%q>->4cY|T#s^D%C#sZ zBdbs@Z%S66T#0hU-fY?C%#=0+^ypP7SEF2ma&@Ol&@`{v*St36x|Hi!Qio4Uc|RHJ zQ*LMxZP1t3@4qNFrZjtAZejs9HPn7bxw+vMhVm7FaJDks+He~|E4S@*5$YOwxdY`P zlsi(ItuA+>+?jH3%3Ub;qTH2ocgo#t0&M59O1=MIm+j@AO31WCyOpGBAIklV*jG?Z zR=Gdr0V*JqXX&}+fs_Y1T2c67Im<&S@1s17@+Qi|DbJ@of>I~#lt)rZsFNstq1}>nyjPk<1LC`>S*L7KHYl5Na9#ILWUEJR{3vz)uiE4~tTLo5DI-dq@KeTy z?SYR@6amVFGN()_GuM!Yy}XBRqm?!U*!L`yYWyu!g}iJk)%d%WU~jQd-cPAR0Of;} z<0v1Ze3tTI%BLtFp?re!QOd`(#cGT?G`K>ee3Ejs#TaO6HRVrJ$`@g-xAtn&a{3&l zOT+V&FHpXysTDKpRyXBX%2y~~qI_AE%g`XX(>D1k#i*4lR@)Q+{I3 zPYvb#kIjnZw0!vm2E2&>(k$hb=CdA@<+;_DRuHs`O_p} zoimibQ2uIsIS3^Bf7jCJ)tB0T%D*Vp`0LWX{D2~qb8|4gG;0d&qQ}-y0aTS3*A|bF#B(#gE{BW;i)UPg$T~o z8#TH^=njalW~4im?t*mZp=&!omJ7PW=*~x1Z9d)kRkB}_##xB2t*yFd{Ov@p@pTBG zyExq?=q_dSl7hNJ($(+3bnNafqs>D(lAbH*E>Cw0x+~CKRV%l(R9`R%VRRf86IyaKY!Q$Lf7oSrsQOEo?@s&iHV)o7dwOQ zD7t6TJ5>bmpH=2)c(`8`F~f9 zzg35P1+bj)ucX_cdzH~w)4jupYv^7}_hzH7qkBEwo9N!q4`M@r`_Z90M&7-}ByTmm zjqdG2$kUg<9|qWKzS7ZtjZLN~7d~z3FzjSFA@x^%dQFsHBzNOEojy`{@2a_kOxB(S3mK^K>7i z`z&3F<#Dn=z45xS4jeN+4+?h|yMHi0MUj;3onKmC(1I!tu+|1WfP z`k`TEc_f6)Dn?(cfO*S@*axU_ix6vB-B-*o@ckynOGd89_F$*HEMnu2O7 znOaay=_VMO)T(z1h)VzeGW2LYNKZ8#)eKbA57}C0fPyADBh^eAg+wQZDyvzjmZF-K zYCfvjsOB&k9T)V#ST!frFsiwXIky&XHH2y&ssSoJ{I5#nMmUXYUZwl9I6Zw)%}=!i z)dEzDQ!S`DsurSJ)Ko33Ijw96(ARB3FQ!e!Z63PwRFz%2-rw*5X{$yYM0L0&@?gV53=cIt zY}lL9PDIHORL4-6@vqGIS4Rtf=s!>!UsV>pC-fte=d#7Ao>Ii+&TwP8jMo)_9Mye~QuF{XQsw)Mpw6CVRf$AFh zaU_*|0w5^o{{^oX)Ke&eQWsjnH&NY1bu-nS7WFMuw^H4v8IjLns@wZHy2EkwMi1&UGhMld7f8$FGvd;*w?%QH9EsrGfD0l1LAr zYEzX|9V*-Tscej>)c&g>k}ol=l@idpb_`DFV3j$qxyjAeyZ1~9-taa z^&r*rR1Z--L-p{mae7XS>Jh_74IeXn-0%s*Ck;m%K4tiHKj0W~3QhH_;d6cd3nuoW z;r}?M3B1(rgHq|Q^yycXkQbEIII7n*gYqU(2Kwp^syC_L(YKt{TUK0e3z~C0)w|j$ zv>&}k^}cIP4yit*n$WZ7)kLaKskALw6d&vR(CQO?6Y36WSD#VoFMv(|7gS$r6!OYa zTf*1eBen1i_iRY@E%z)!^&R)jPW3(aOhNSn)!$Sj#0myTfs~|Cw*k?D0o*ypf%i zm6c^#($(Eh=|d1t>E*AdeiP?+p0cOF>7b_mG(!Bp`<&7QlqPg$v8_>>xGzwegc6i& zljnI#lTwCr!+#SXRn+HbY_12D9uG_Zc29Y=U@KtqWJns zX+BCzD0F^G3s72=lJ zQcKHHT0xqZ6E0t<8icMyX$?v%Q(Dd12BlRf*~g#1bQwGFUveL)SpDz+Q|fR2C`W5k zx|`BEl+L8IE~U*RxgMqUDQ!ean?EJr{Bdn=;l`9UHNz-vGJbub+s!F$@xK}`ZAob> zN=H)Kn$k{`wvpOxU5O1krR{{92zo9(^Hnh>fzX;^d_e%#+vdTS-lKX#3KL5AZt_^j2 zxbTRA>6;Iwql8COI);)R@-)t|!s94u|EF|<3+o3EihB~JlPR4_$p?$^L6lBYs4oF1 zol)>9oki(ZN@r8DT%1GcTuNiCSUl9m+|qfJZlH8NrHd(zwSrY;jicoAe@aEMTqyb? z!{S|j$y`F|Qc71*x=gC9(OypJ3Q1mBa13>OweT9D=Km$l|4Y{!$5Ygk0!lYgx`mR@ z|NG6LnBSJ{@TV!iozfj1)S(nAVo*dJUxWQy&sR+81xg8}T-h{ER_IjI9hT0gDLqT+8PPJk@6DboBq=>_ zgnGw|lwOwDOV05Uc!kni;=D@fHA-(%vi*Nc<&C01JlEs>XX$N9?@0JvO7F{n_vFB> zD}6xe!=k1Nr1UYRFDZRu@`~~)CC&d!KL4j==RbX)(`Owe>%~8ey!bmxHUxY_$%t=F zMrQn{C<;pd9q9SK=&)le^8!?(w~(6ru3Kp!qP@P z8|KOr6j`IJAwb#mM9Y&<-Jdd)_oh54<;5sZMtL;l$tllBc?wUWJSFAnDNiMOYRc16 zcK<*0+$EGp^k3{PyZ?8ly?JV{otAz6FT#?RpD)jB9Lncm9-pXpr!1hT_al zc^=Ahh&ktgITz)*3tIenCF#on-Of*W0m|<1DKAKQp}wRWxri~vU$kISUYzm4U&yJLLqr6k0f%49h*+sZ(u>~Po{=d8jWq1DK z>@~1_A1Y?azLYPeydUK=DDO}C7|I7wKAiG_ln;^oL6i?3`bfjNeHpTRsPDzu7F_wT z{#HiWoj>Ix4OKEnQ9jz~ewiOj`9#XcQ9eOpJ_MLpu}wkwB+4h7q#~X|`P4xycm9$; zeZV=B^2Lp=K+%J)AP}BITOE2xo3`%a-`e1kfEGV);zW^pDT2j^79gYTKEiQS1#Jla+{Lp z44u<2(ibVeP1&74<(DbHD&mzwfbwg)eO>s5@J-=chBh4){SM{#M7-gmn_Wd`HDEeEU<|;y|%W0Nvm2|r%m9-RoZQZUTTvzDMpUV2e4TKvS_B~`} zW1s0&Ht{J_(IYCGQTdw6=2Y&dvIUhB6=h4|R#dj8vYY5_sBG(m$2iB^(y_whs2pE#9Q$qcBq}#b_+%=lP`QH2 zsZ=fy=QJv(QyD|WC;wE=6rLqKTX>G46^`h0h35&+7mgK| zL;N=foVSWsYsGwr%DYrPrt+R{-xn6n=R?sS84g zKL^BLRNVjf1*#LeSal-d#KK902q!h{+g6>N>J)xk45rm7sZK?81l6gXX*F1#rf92* z=tDri=~jIRpgIH98T%clIuq5Ijp;I0N7a#3cc3~%Rn4+GimF;dbv8vDEu39Ahj32e zT*A49^B4{cn~&-yROhF<9MuJ=E=hGkstfg-P<3IYxQJpcDqKvc;;Js;dWvzax|H}! zJI9S&hN}DjK`P5rU7hL*l37u>l2HD?x=JBQbyceJ|J8pL9LcOfb$tk8L19Hg~@IPU+cZY13JAEBEXE#_uaHy_YjP~DR1wp6$Beaq_BhLYLFP@L^_yM1vh zdPik*C*jUi_o2EA)xB-ghU%`u-GsXf_n^9`V(sM`#-3>CgjTgw_oeFpe{`mKr0M}w zhp8S&^>V5QQ8k4JQ$3pMAwCjS52bp94`M2yuw767HrD`w#T9ng;XBfIp=bU9YNbwxe#vDWS zT&m|Q?s)^ru>~RK1;T$*y;w>#1XS(&&%224{@-*`y_D)@1ud~FsNO2#N~%{;yvsy8Xr{l7HZD**C8)!V36sNPQXeyVp+y+>kqQg#0? z`fk%8!VdqN!uyI_(GO64OhFG)eTeF#q93OE$aop!`1h=;_QcAv^90qWsQTpJZl$3l z!h*^LlWLV}&0~35b*cf?2GzE1o5Geoe^Ejma~l0e+trY2LbdBJELVF}?aR+zk`jyi zbMI=Z=o!`gfBzToRGy|b71d{`enQpi`cWfrgC=4E0`_jOE_y2Y& z-PfqTp%hSt7c zq53&h>k(g2{f6q7RKKSB)u{b*D`Z0_JYDnu-%PT>URqcmDq9O21T~>PJAS z{v`alNL2K%RR5s*o6%+=)!z$3L4O*`u)nEIPHh5e6Zvh;hX5C-O-yYvaVDWA|6iN5 z-xP}mX-le_4*}GsH1t?jU$v>J%};F_YI9K=LCsEKDV5rE)MlnOJ+&EJ()G_sZKfjh zfAw3Nh1y8z91@Na&MK7uugU+{E#rqjo&C6a2O~9a^&@HNG_40f5>m)W%XXv0WZB!4b7d->M~ZffTbh;h_zqILnb>ts?<9T!r& zh}u=uE|%~m)GnuXsc8BC;_Zgo72^2(zj$Fmw^#eS3AJmeUF-Z}kG*#NsAG3mUT&av z<9K;$H!IOwgtr=ckeAi%)a;85X89e|?xc1xyg%?Aa$sQLHbJc{)W@5>Je zAEfpuHU9(zwTFd|82Y7evWfFJwTd)Yefj*K+Ea!iO4P~)t)5a9(}w_R^@2~WVL{@w zs9Q&CQ=f=hhnoM}ky=14R4%&0o-h)|h89tDO6?138MTk7S(p5P+Ay_e)r9OVFf;5K zYN}Sf`69EQ^9}IY^VD9T_8PSpWrODbwUV~-;VVOgmV5xBdsU!5Cxl^A^IJu!>O-X%v>c*ei?XkDmEbsMc zJh}P^&v<=W>eHE|(UPCRBAO)i84D)$nW>MYKFj!$p*}=?H1$!`{qMj7owGS@l~|vh z`oh%bpuQmWIjPS}eJ*L9oBBLOuW?IU(w#r``4zf=Cs(|iQ`g}i(`KHbz6kY2EeVCr zLw&J<%o5Z$qP`^cRlJbvdLy{LwAny?8S2Z9+AyHL9Q75Ye0kvtQexR(NrkYo*HKYx z^;M~_tthJr^$KuZZ-mv?puQ&cwF*Dbf9=RqS%><%)Hk5Mo~!ke^dUg`lK=118&kJj zY(jl=>YK{E&CFmUWYQMYcc8u{_3fx{rApb_L%mqGp}uWVDYAC^e$jc-^8fXnsPFC` zslGGyU8wJBw3$bJw}NnEianY7p43mEz8Cc)sqanwAnI25`zg77EeTm=pN6w693VW< znP!HeGY_VI2=&9MA37+m!(4g4=#D68NghT0XzIsH^D)$qwII8wA6Lu_U9xa!^%EuU zNl-soc#5I5G3uw$uu4Cj`die`p#CuRGpS!k{VeKNQa_vezopYh0qSF@pG$o#b)WoG z_k}=_Yacr6bTAr|wGt>OKVc2dwJ8|3lsVzuk(y%234B!fS-r z4nnV|ejD{0T*ekq^&6?*r0m=*v0H?<8X8md?bPq5eh2ltbbF@)Juj#Heaf1 zfT&lgH>lT&--1xDTaXDzvMID7pe>>!3=GW@(OqFr9J4l}9*dI*Q(-35Xi^_Gy0BTy zXQ&qmJ?nfo={ceO0>)_j1&mulUH-rRGWFMW`wDgU|I}aeSpC}3LZJSpaSA5&x2gX@ z{T=E*Qh%5FH`L#w{(gT~q5c8&PpRt>u-Df|Quwj(6IbHQ&!~SX&gay>aHb{KufDIu z|GMB%|CYLa|Fb9)Ydqg8)VF{JO#KU^FYpufpDjo-zX*R7{wDl=s5pC5|C5F}*uQ8@ zNBwUalhK%f#>6hun2?4q1nlxQ)|jNAX`s+a`$jen3u+UG!_(pei{o{6q}6?5?V-nUjlfOXe>(Oej1C>ID*FFG}fiD z1dUZ`EJ6j<4TKvCHxh1a=*ROKo6@jp|1UH)bB?VA z8g~BE(%RDhWzg74xV3N_8avV0R)w&gaC_kn!W|8Z=SRBTS*R}4*j4myg#Zozwjqr@ zY1l_Tjo3?}d(+rQxBHqBBb1{2f8&6HL&Jvv8V3pe6*C%#2oDw7CwGiBM8(XZ1%2|PukMx!KwGL4F>Rr%1U4rJ=ibZZ+lnlxH|JIEc4j)DSV=s_;o zrO_KikDTdkIHBH18r|}bwAB;^_dm^f zXwEBQKH>ZY-%GV=#w<&7A)1T%EU39K%|#@$s9`Y|qUo;y&|E^eq;M(W(!ym7eY?Q? zQgb<)Ylyi#%@t^_DtbklE4hp-Sy{M>q3d_fYQldBS1d}IrXT(ky}h9e zyPZA+(AtC(XTz6^F%=N&C<|h339A zPolXW&BJN#FNFsP4-_8c!k)^(G!Gfjhtl-P|H!?xzHa*DpXQOmqXsfZ(>z9;WBuFp z&EsgEpxfgO?a;euZ#n)3O7rA@Fi-VQhc!==!0C1po8}obEwg9Ryjsk&Xr4`T9L;lR zo+r*2;klM6BSiZWfaX|3>A!&Hr8KQ+T}<;rnio0WK5Nzw^~rz1p?MiipZ3%AC4l0Z z@jm%Cx`;y4x{Ni;YiZs{^EwG%UnEWQ1``wKCYra?yqV^$y1iv++M+aWvs=4N70o+n z-c9pPn!XD7hduYWOtEv=ypQISH1DVRkTGdKK+`AxF7M^`FwMtkK0@=+g5&6#tEE$t#nP#P*9ZRM7NO-fRAbkRE)75A;X}(CaMN^afrj`KBj${I1IIyrs z^J$t9&5UL|bkc@26Po@)iir(mH2-f7|AX@k&F5%(Mx;$kfRi>9{k+f~0Tj0{(foks z%QWAh>1C_XS82XR(^mwZM3I(f?M<3*(R`QY+cdp#dN%tt@*d6i|H+Y_4{7@3U-ZW` zeeyr3<$-nqt)BHxnx5EDjwOVZYOW0GSqA9JHM1Fdyw z%}8rGS~Jm_lh(|%j6VylQM7#iPix3c(tfONeF@4=uUyv=*baq(T>`wZuR8OVL_J z{G|&?(aXAko3T8tRcNgs=8D3Vgex1`B^y?ywU(Hx(fSvyHE6BwLB&pE%bkC5OKWY3 zt@Dr2^=KVMYkgX~(%OL5jb;)(*6`cc#v^IpL+f6D`g8TR#8yeU-koyV2U0*6zlnwFj*|2mHNgS**QXxOgc$ zo!EkC?MLfCTK)+*TJHZ{z((_y`+r&o7aUrL&^lBc_y02BaN!ZcBMlXLG_4D19YgD6 zTF25lUd-b>O5Z$<1+5bm>!kkUnARz@&ZKoJte?&Zs))-o6(>iB7Q%cTN z6dw-!^^Ml~!m+|}!V84{2O>#cMC&qI7aOhHOK4qc$ysC5?d7!G=hM1Ec%`9A=4x8E z(7J}!&9tsnko4&u4U){t(qPx^eglcp^pNz9y7kgo}l$KttV;q z#D9ubNko}eOGJfMRfL>>t1jASLA08NgVK=mZ@KeVx`8mH)pd?eUnoi>jD-oU)Ckd; zFsCK|?@{cladPr4OXww9&r0Aqq4Q0E)(gTHi`Gu-Wm>P&viEs6ka?4q zRsCDyzfH@XIjwhSy-Vu@(eKfE-;%b5t=kWUAJNkHKlQ?tIG+kXGZgVTtuKt|?>V== zqV=_<=z6>szM=K4hdS*;fSqilCI8?0p4P9l-2cnqAB8{B`q?NRu3)v1;DJdCvdtwVPC#IG3{w+PeOZA+Bj3~N-~qtp4bWpO^LsyA?+bvN)63p5D?Db4J=TD_wu?>G`#KY0pA?B<)eOhx*O9*sgESYCTKB zqiN4adv@CMNPZ65{{4U2b4lmihQ=2?uc6=elk*{f_5#kaM&4eCjwQD+?H_0_LVIo6 zi_%__wiX5L#c3~LpA&cCGamM#t-TcOm1!?cdl@?t(?7h|wzmKtro9~P&zspt!9(Q%xYe?ArKW!f%EIHBZ(7uZHy0lNAy&mmd zXs=IuE7}{--qa3L(cX}@e*%>D#d?(sF_v5;SyV5>{_HMNIqrE$A{ZgcO zPr1D(?Y(I4Lwj#uO_;V~P0=3g|F`$2ZEt?rOEi|~fj$#xA4L1$qA;!2R4<3pK9=@j zv=27}RPsmAK8p5{g@8;wn)WfCS(UT0a2)O9O|wTSem&Vfk@oqtPojM)?UN;QN}+u>=uskrBM4^+UGe(#jC0uOZ!3vjiY@5ZC^2Y z(q@2H%|)~?5pl8eUH_%DucUpM61|+Zz5J{CvU;($(U-ZJw#B`M_T#j#rF{qO>uBFZ z`+7C?8-)HuP~K%B>Q99iFXjQt>2O}+Hcd& zX}?T+nD(=@pZ4r~Q9MJtf5_I<(teJ%|L|^**%xTPNc$yE(b~Kz^m>1V_G_~0RYRrt zx^CZ4cHX4@mTR!=dm&hNeuwrKwBM!u0qyr_zwfcs{7gVLd`SBv+Mg&uGbd)ohz!tZGN$3Op9P?n;X)sJ+nKmJ6= z+|O#d%*rlI{bq(SrOzfGMjlF|Nb+bDGg1=HFu__GmXDkZC<4_LO87#Z)Z9~-OfPAZf7)M zS8Hy+Gcz3>(do=WXQZDw>O@9Md3<@Ha8Q!3Z2!w!E{#j>h|1Ofos>O zv$|EEG`RowzGJo7S=(q8zU}{}+bN zQZ;&WY2HH7x1_U`r7n(7{^@K($0z@8WIwap)7gd24s>>+vtz+msruyKzHXsdyVBW* z&TgVL|L^P}dQU^|Xq~<2>|M~}+Yz9d`ZK-G{xbLgFBvbW1BD0qt(VCml0THd%Jwii zZ|e4NI!DkkwMWvqn9fmjP8I)XI>*qlVmX%1@pOFhZ&&|0W5?(Je&DThl7vqdo?`vZ zN?7!1bjFA{oz59_&Zcvw|CzCKmNAQUqGg}XIezPe=Kr1Z6yO=OH?`(y=1Fjm{l1>~?R-cDA8&C!KqIg4DT-&fTuI*oQRx zUH^S_tY7%g-#y6uQpYF%7UZ|Z#HsTLoi3e6>D1{wW_&u2EA-!Vo}lxj(PEnAn*Vpo zx~&K`|L^$xztBm?3N27jQ`n-@rql63t;n@a1Kcn@0tlFBOP<{ zKhgP>j`{yzJXDUQNZq4*#r;mllK9haWtEG$pZuF(0)mMNCM56`fm!Ql8Y~OJB>kj= zNeT4b#X#qOg2@S{AehoxqboFdbNIp31k;S?5R4e`rz2RGV0wZD2xcG{NiZY9%r=M+ z%;ZCfeT5^i-@!jSYNL(B93q%q%uxih5{w>o&V~fD*=IdWz)(SR5X?<5r{w2yCH7lS zS2z#Bd?MyG^#5B0^ZWnuO!G;mWI=+Z2^Jz)oM2&sMJ2h2OPYY2v6$=co3w;D=Kqbq z)PT7R!5Re160Ah99KrGyIxxu|0h?t17cc}X6Rc`M1gi`LRwJ;TcKrw@@SndFe95dS zTuZn%f&c%vkhC%+SWk7~Lx65ABWDumm}syu!AS(05bQy)DZvf|n-OeHusMNFoCaGc z%9e$n_ZqPQCD?|*H+%@TCD_iH@>c`>I}+@w(4B-k6Zl4uixtO;g53z*{}(z5_9QUZ zy%)iO1bY+g=P$_y+A0kE7hnUm`x6ZIf2?;02N8J3_aVUCY;Z{5O9WPB#}FJ&a1_B2 z1ViKWc z>w~ih&QlGZO>mA{%NXBC3C{JVWxs%N`h0?m2*wha!f^x_*e9g?*T%E#GTZDM5nSl> zOk+nLFq+_Eg3Ac394Gn#3s|c1HnB6Hxk@RaFctj;AVnbM(uy>=+~zl{rU={U$+9hjo?m#+X?O%wdz|| z6n2>a!CeG*k2=uj75xz>u;@<_+(+;r!Tkge6hX5!uQ!6=A%cfZ(&Fm&5dv-g1oHoZ z`+xDD5I#B3St6(q*x`R4)I9p~OAu5|iO~dgf`p(!5D+v8wCxl4PSNPsH6{j~(Xab0 zK}gUe=qkv4BSAzE`*HVT{q>Fsqy*0pWCR))f_(HFBZtm51D^I+Z>%ZWB0f*>EWvX_ zFT7hQHzfow5V-RndM9)|w@dIc!Pf+@5WGk5DuKE8*9hJq(EgA8iQv>F3Em{|pZ|`2 zW51!|GZDc%{*!|9t)kyG96I%Ng7*nNBlv*eV}cI}KJvlz)Fpk1;i-H=@TqCA=w{xi zWvtJBPVg1M7X)9rD&rSejJ&}&1iun|OYl9xe+b;?6S)6(wWl9!AIl>6f#9c+V@&yv z_5$+I82>XJ!OsN0_%G`0n$=ADjc{Uu-w7uq_`|=P7W_%@ml10Je_NuiIh?>X8`Clp z+NY(?cbmgW2qz|-*Po!6}dT+(Kn;S_Wy9y(_O!YK)-BCHWkO?Uv|G=y6c zjv!owa9YAyM!#_x;dF%4E1?+(XLJoVKM7|NXJ$k963!V(xG>=m;rxW72= zzX%s6T!wH7!lk^3!X^Drf=(=L=$CypE?kz-yuC4(vy&Ty%lpG1=dVb(3gJqGD?7)8 zb;^-&Rl?OwKpLL4Ai~uN*CSkm(7pgR6XBXd|Npz0WEbH&!gc-DRwd3^pU_HsBf<>} zj-&a*aO3`SrEpV~r!5UmlvSG(Zs7*lXN%m(tq6A~+?sF)iESg?R=6FZ4+nO+D-L%g z+?8-A!kzs|wtFXST$#}OVycnaasgqD|M2u~oC zg9?u$^ijY%MS)7f{eOSl2~YNwmmP}=PbEB?@HE0REtK$dp>F|+@b|wwmTNwT@H`dT z7($=_JKx65@ce=%9P1qWTv&Jk;Z=lY@TG(o`o}9mpZ^nHOlZ46-c?=Fb8WrgO2W&f z{|aaJU8TnK@M^+qRixLLurypp=nmP&FY#|6ypiyxzC!y@hTC%sVVUq&$?N++;qC6_ zLaU?u3GXDlhwv`KyRBMnA?A(2EwMVf&o^6a&@Yzl;RA$E5Ufg0MsATR@8Co|({x0AJ~7g=k0^6DA^3!kjSco8dvjF6PA3gwGJ_Lu#SCika#& z5DT(4_B-JVgl`ePNcb}0OP+-FFI&xp)+PPVU!FnlkFOEB|0jGy=nK+f^+fnK;m3sU z5WXv>3BMRt%#FgYvg`|Y7wf{=^+xSp1|8S3C>-6wXnfDjr z-)>uX0^x*(wRHXa&*Dr%*PJ|bC#O3pT^;`Mt=fb8J;j_)cS^b+)18X$j&!G{yD;5p z=nl~xL3akXsyi*+>FB!ix7VGDH=e|wk?u@%XQ68k0#Xk(KhquQMDf+8?kKwR(Vdm< zoOEZSJG)|ywxq3vi=M-Ut-W>UqC2LCP_g{2Zr@N*_q`O98XQ7Ag+H^OiyAIur=&nn5y@Ac^)7_A+fB(%T z$M4&8H?~{4n>e#r9dtLNE8pJTobDEMx1zhHg}Q*sneNt3o1gD)OLqsln*aN4{}q5j zlI~7)&!f9D-9zYFA@56fSGs#zDBazJo+G;c=9GxNq;qehRaRbN;_pZIKoR@X^+y0M z(65?<=oap=kUW&`X><>xdo10<73B!JM@en}`LCEq(>=xr{|2U4%5ik99*-ZGaf0ht z`OrOy?#YUNittqTf%1fOPp5k}-81Og=8vZ?&BYc_Vaz$=Xs4*_Lx9pcpYFAE$I`u$ z?l`)a(!GH0#dQBo_rme@NcSR3MbR%Y^tfKpm(ji4T!iUS!?RU@PPcZiqI>l~|20ln z3-^3oNB1SV*VBED?hSOGwII4T(!GhUPIY!~cF)|sh3>8Hd!4>bc)RcpLqDa~y^C&( z?%i~spnDJ92kG8RSBF16^!`!%-%9rZL(?x>{=fS$-A8r%ND-veAEW!Y5q3%QlXT1C zKP4;;`0oGdR_QkA){K#++`UySj+Mm)hZ}u78zvzBW_XoOP(EUon z{)p2E(O=X3hORuncg+8&^?pb9d%FJ}--Zih!+v`nC54WT@iE{?g=}k5en4I1eMUcd% zqUXlbo0{G|L=|NJO7?Lf4jUZ_2#0tD80Gq%}>w$zg_g^^-bX3d@fUXo8AKS7Noa`LKm`3 z(OcN?9|DWfTaw=55?I3d8fbL86uqVCEvs0|6tTo#j^6V0R;IUtZdVkpWLP}Hrnd^c zHR;*MpQoj_+JI@Fq{O+y1@Xi%=IX+Es6_LbLR!V7;REqjxgB{plS+?*Mv-(mSvaqj!+S zrFSsBL;ex*Fr%g6@PXKo^o|qfD0)ZJ)3m>LOh0wK(;+>_(>sCQNs>Hqyf-#6dZ*C4 zhTf_4>?n?$iHLGSXu+159ESJJzh-c`owGyBDOExnr*{W{rsz3>Kl?mvxI z(9QHT?eE><YdS!YonOC7# zrPrWWqbKM8!i4?zk9tiX1p7JFxOOYtlYqju$q9o}a6cfIEMBFVVAddzs!_^j@L&I=xrvz2<9PA3Al$ zyY~jYHw%tyvk|lRHobT0y<-*Omya*0+}Hc`KB4yky^rX9C=E9{dKdWEcLTi1eM;{O zdY{qz+#?QBk=ic{4!y7GeNXQjOHM|9OV1YtqQ5hrPw&4*i}-=wPgauje)OvMpr45* zrS}UxbL_v;`-9$Z^nUkg|Ck4SdhhMkM-ub-7@0-}kCCiFFwP9ykoDVkU~ ziO|3Q-?t(1A%JLdqA5&?Te4zKG!@a(L{k$jL^KW22>N&2tdVLo_GR+_G>kL(h?2ZoD~v z!}*975HWudO0=L8-c39^L<s(gI);4VCDq59j4Oy}p(Z7gRcb$45>-{KN z)9WQ#i)d|+VsUMS<(8~Vv^mjwL>m)X$!zFdHQK;sY@zMuZDi=S*&;65gvgEtS&F7= zGk@Ia#1=$bx(!+d6KzGbHPN<2+gQ5)v%%Bao@gJU9f)=&+L34{PsPg0ny4$^g=lZ0 zU5WM}+KtG*NZ?QNO^Hm}lgPjPS#&Oo=y5&Q`w|^Yv>%Z_=Ox;o=m4Sv&0tH~#f083 zqCng0b=%jcPLqVtKy zS>Y3n^(wF(%gA2-^|~ff#d*+0L^ly#Omu@9st*A~ml9n@bfxIaiLNNhO0|C#(X~WZ z6J6uwX};0t|JK56tsxCq!QoeMIAa|BGLZR|=r;-f?xB54{v@7^=r3aP z`F|5n&_6g4`~HtR6SF*?m{{}wcoMG&%Y`#1b;7D3o}73_;wgxy^gioBQxVVLjv}6# zcp6VJ9wD4oIGs>iKt+BnA&)haq3<<0JwyfE>+c4}zo?Kbgz!ug38Fb?s81O7tpqnxt{@zSIA zK8AQvnXwr0;>1hZ+X}=>cM%yXdhX~q>ckt3I`~NYH3{)1#G4auO1znc7R=GN*NTjK3p*rM3~OY1oZ;PvYH`?jEDY ztssFFi1#AipLlQLeTnxm4HkX$tB)B$yr1iT^)WM>_yFPqi4Q8Yjeg@x={$t^Wa2}K zts)L1K2k{^t|X2qk{~`xw?|vX2Pqy)d;;-t#K(^scS(`KQRDU@K5_J`mbH^i%$d$P zh1illmG~^;(}>R?KHV)W%F2u7Oi#u0dp7YHnRJeaT9#EB#OD!TNPIr=IO4I+R}k?9 z#Q*k$HXUh~EVM>-3Gu})U`d!^RtTQNWyH6sZC_4&1@X0_uOz;T_!`mn7VvjQ*kuIq zb;R!NiLV#l;LXWzZz8^h_-3Q+@^|rKUkDhX_H(=9-a&kigzpsIMeOH)T+F<8e6M8g z6W(v=d{6N~;#Y|6=U+1sKTK@R^%3HL_)+32@ngg#;>U@fBz~gUz)`dJ(Y*MQwhZt` zz{C}M%;Bf1;u^8_Aou@v48xGPNo;3+o+oY%tzm7$hX8Y2KI(bIkT|x!N!%6ogpv1F zw@S`GPKlo;&WQcbUp{$`eVO2T%#Fv-j5@}8ijCmU7U$N~ub&g@72sHpK4X6cnD`~( z%Z5H*ieDxEh4?k%Pl#V9ewSDSLHuT+pZG1}w>_aT)~Md``9?Z1@q2?%9|DLqF2vdb zihT=+_~XJei~f}Ovw{5Q#9t8qNc<)7*HZOWAxZoV@wdLc?+Lm8C-!P0cK=W8&Y#%Z zL-D5x-D)im|7=lo`zy)h#J>?+E&NXWN3n0OBKni~FBQ1|^@d~ul8HzrbmwoipdEga ziAg3Qv6=ti*kLlM$4w?Pbp6Q`BqK?tB$Gg-I6hnUc+!lLdwH|9-m&$>Jo7in&-J<4rDEf@CQ%mvrXr z+YcoNdJ9aJA^8`{vLq{!EJv~e$?|r1!$W5pOR}O7o{Qu_l9fqTC0S+Yoj?6@&T1Z1 zOx=^!N!BD;!#OqwQ|l#JtDs5NAvuR+U6KPx)+5=DWPOq?NH!qZh-5>r0_zc89UGHu zMzRTszYFZziv59P^CBVRkZeh^jd#^#D<8s=tqU2^+xjxZ^SeFCUL-q^>_W1mgm)s* z;eX4%Y4+&5dLK!4^TmZ1-R|CZ5??Hn>}ja9_9od^V*B*_RI;DREBgM1lCh#Vp5!2s z!^J;XmK;KID2Z?WSUKBhE6F2BjwU%$Vn-RewQk8VB*%IQn8!$tv*h~go#X_P)07=+ zHYbssLZb6O7SVHIc|X-9ZI+&#PI4B>86;=A9;=kX10-jAtFz06$B&Lw%6`{`(M7R>W21!3jVqLqe zpwd71m4cpaMf)AY<0N&Gh@?T%RkdmfkhDlN|4)4WPZFrJhsAfF3{|;3LkpEaOp=hi zNRpB~Lz1cCa^iTr<}Vfx&Q#s8k<4-)GLzmWV$qRGFF;K|Px zYL^WDmBc6iK7u4Z|99H6`6sD)`@cx_*f;sRFP~0W2pdwYBT{Yt7yUY&lyo-I$w;Rs zot$)P(kV!%^eFbfT1z_h_kX?0(rHMiB^}{GS`A5|F99Sr1L-WJGm`q-!Nydm`+vn9 zNvipOI`ogYvku~pCY_&jcG7uB=OCR+ap(MB38!nwM?MW9T zUC70Xm0G%p)5T-ibTQHuNPYi@bP3^-q|1^nC3*Z~UKy)8$B)H>UGF-4#hU zAYF-cb<&kdS8@I+-znp(lCDN-CxZIz!RsYmgLG}uHBF3ktwLDzI;87~SXae+5~**L zjgL#ZA?ZdE*w|3SCZwB+*i5*&a0^2f?3P78BHfzI>Tw&=?Y+CD+gdr0ZZ{~69ms}A zcO-*!C(>U@cP8D9bQfvc)wQkE7`5d4q`Q;uDdrvpQ}kY>dyBBFJxIDQ=~<-vksd+1 zKj|T)2Uw^C4kSH@^kAdS&p6Jixl?*5sSg39hda|wUfCJ8^hnYZNRJZ#Xwu_Ik0CwQ zopyi7^MQ}__(2{mU#F2;p`9ZB$;NkYmY!M=LTjj}lb%6(rUexo$(&7k9qBow7n6=5 z9ZTw4K&0o9p6_DzlRh)dMv!zI>Ay)Y@Y`ZLF};xVBHg-=Rjf-$uOz*c^l~Y`%tL29 zR6+I&c=v3iSCMM|pI&Wl$2ekMJIMR>q<4_sKzb|bjifh|-c-nV-fcKeZ}~^8+emM> zP-VyRdnf5Vq<2YSfBvs1miPNf?<@3(^T0pohe)3#eV8;LeT1}3`Y7p>q>o9@i*wP!KH1|j$3OpsPVI)G$iekW~4pR zgft@c6@f=7jF0{_h44#5CHgh#_oUyD%I&9K$SN!IvHvX!%F({Xk(yikne<1} zp9(vRv`Bsb$7ydKzmfh$`a9{Lq<>iF^?e2_I(ruHp?{N2L^i>Y&2}>Re?6nu?ZjmA z|HV`LY*Mo6$tEM4ifnSSDLe^#gkg)XY)U80`(;y;jqqu3HceqC*|cQSIc+}Eel}{O zPc{SD%w#h<$D{a6Pr?>!7P66kYlY@P{tI9)kj&=)zDYthnrwcu*~#YiwNhqFfH}#0 z6fj>}45>zw%_E$bY(9H~$wTe!uxtS*ik;GIA+i_97A8B9Y!R~6$rdGBifl2mB@}n@ zQ70cY>TI9=k}X+iCR>_pRWcs}$d;7>%aN@_wmg}={B@%76=bh~IZd{*q3mD9?YF1B z*=l6}8hXpUz3a5{&(l?yOuV=ze+YWHH0ojIR zn~-fZ2;JC;w@k^Vet_J*(w}Mn--#{AY=-|Z*_LEm`FbJST4?+KKFgJz+mY=_wmsR- zifco_j$}J|Bhk#?AAM%KknJYIzW=XhpxN$ZdQ4_wc6rVECxFQIHdEbNueg254k6o* zY=8Ocl(g@!rGxQocT6m1`SVQH) zUjdc@CwSBHoSsD1Av>AuCbCn=u9VoRWT%l?$)8SkF4-AmXOo>tc9!=Ds~2m2p2Rt1 zV@AI8i&tFX)3Wo(#*v**HrB;dbp7w!WET{@kL*ISOO?t+WEYcN;*xS8BX>>3zl`j1 zvMYSwQl(@}pE+k&kzFkT9|%NTtGr()yq@d^F=b(KNHV*b>|s+vb_?09WOtL@Ms^3; z?Oyom-3q-^c$YKnd~$XV*+XPjH4l*8M|Qt`KuPj?)|ox%hS_D`?J?^WY$X-(tuez8Q?CWIjlf6Oq4%wSNP0HT#X_5!MZD7Nd&c+6IUkUHN%kR`zwJxrj{wN*D-as2vroxBC-WtMG-&ma*;4@TL4LXNugU9V z-;n)G_AQyd2%Y^$CH9@r-vU;SevqI1v8ZmcpIoOscgubuw_5v^d;+rH$o?ezo$L?q zgK8ME^Di-Qw`Nys7Jn0!+5CCDcupN)KS^6AK@AfK9i z%7LV!6h)m+Lq39hS_^XDKBJBA!{pPG&qS{8KU?kRGrF}-%uGH+J`4HC;vBn2G4GL& zBA<2qtibva`DpTa$Y&>?lia@lcCYK#e2jc9a-aPBWo4MpOTGyCeB=v~&rdGrZ)Oyu zdA<<&!hPGu&pPr&$rmH{$^X!-BZfx$*hju3`Re3LkuOKSv@3V!GUUq^w3lSQJozf* zE0C|GQ2+j?2>1VX^(P_us^qJY`zDET29~TrzK(=_P#|B6+_$h?#x<`?z5%%w0{Qv{ zf1rFr$!|2!vkCe3c15VlZ3l679LVqF zgqNr0|GCfq$#)|^hkSSP!^rm_-=BO>@_oqn8pQG?fNFf-0cSs}(*b<|`GMqzkRL?u z1CVE-Ut&Hn_RF3~=7*D?NPYzQapXsmA47f=`O$@>$MW(ywvZQnd_j|+;Dp$S)v2 zpIid7jvyarN?fPYmYmQ0$uD$fznNX^dOSHF6cp;8fDmy7`7PvEl3!1L75TMdUQOQj z|7!o&8PnRKG~7UblhNcidY$U~a^!jh@RD`*TgmSuzm5DZ^4rP%CDMB%gQZ}Pm@1m%zmgxvFmwq9|BynCgkM$1-vKm62p^|zfAr+`77lA zC4ZIt8}ip={OjbO_(Ct&BY^x(^0&y}Cx2VacZBZ>-!t^x4|A|C|AFvB;YY%c4W;u_ z;b+3ng?tCpkQUs~w(@;fXxT@Wev-z+psBDxA!)h{f;}438Ak zhX97BVt8tXXJL36hG!OMgijBKr)AiynBnP#GYDrC&SW^=2A@|HGw0zUg^pr)HWAwY zaq~votw*u>{O}wM&#%xq8P@k7hUaE@UWR@0?=pQ=^EqvA^b9Y+@Xib`$nd%hFU0W5 z3@^;^G7K-m@RAHK%JAY0FJ@B?p8?u5dw2<-hl|$jQo^NOo2^ZTmt}YbhL>a5AO7~m zoWG*PRvI6d;Z+!3lVN*X=U#?aV|aBD{s`DqG3?KOWvzv-C64=lhSzaNWG&WaT*K=z zycNUin{5nlAly*6k#J+-CW^JGa5F<0xw&u)hTZ=gtl(&|zBz_(GfET^K%+;a$C-4(~Sl&Cv|+&hTE+pcll3_w-4fm%!c(@9VvF z*p43`><-Yn@bG@Z{TV)h;Y0X8s?Gvhj-qM9*n>mx+zX4ly9Wp)KyY^t{-GfdNJxM{ zfZ!I~-QC@FM|Wp+c4js%xI6q+HM=4IIep$!U0q$RQ{B_k-BUcNo5PbS+7OVxxfj*` zQ#?)3h|>+v=;?$G=^cEpIErT(4yAZ@&e17!aTu=XKNsg{iqh-;pg5f3=M+a!yp7`d z6fc%%hvG=XQHB>7UTAoc;b=kqYFWI5qU;nEVhqJgDPBSGGRcg|TuyPUOv`)kSk1gw zQoM$uyab8x)q>h;9VORNlp|pO_T4p7VHLQ6;*Atf_zp!~{I^oPr%abAJAICOy`fU(JHE0M3F!7`>3; z!a5LZkhs1$i^^-A_Hbu0oW-?tjxPS-EQzy}wy70a`=ryO3lUDQaBy_@kr6r;SoCGh zyPSG+E9$I(a|q6gIQ!tNgd<6;jI$-qDmZK5*f8O&hO-9F>Jm#M=FR8WTYzjjb=JmN z7iXPWg4V;?0B8MNQk%)y5N8V`Hp0>QztNlEY}%za!`XbM2I;gWu$6Gc;cSi5Z)PaY zwm7@vY-d6JadyPn9!HJ8#4_dpoPo1=C6i+O^PoY(HJ4fIg znbSHnIBNf`U5>$d7H2SyFptGKMPzV}Gdv#W1e}v`PQ)?eFI_$Vd!1uup;N9?aW2O> z4d+sv({aw|rg$dKNSq-!lKNRV=im&*vC01lUf(f2eaA?<55qaPZ?mt^J$IaMi4NEE zFU|;~m6M-6#2JNikp*4QQ#b|Z!kz)E8!;N^5}b>BR=ip{D_)H=Mq>@oK7*t7U%fIS zJ7aOK(1%xP-Tx^AkjY4thjFgKxeMo7oExlKuCt))^*PGtpU#aqH{slZGfr2%oSOyZ zL9pI(E6yD_x8dBb%&AfY4Z^uoSN4R~et0*|z0yE9_h>W8Q0v^6=MCq6YY|-n!qK+? z99{p>DX8-Z&ZC-tX<|7F?MU>;ah}j~yP{;0)a{cvPj&S?jq^-STSCv_{Dt#8P95h3 zoKJA1BgsOURQpRfFXOy!^ecw18onkd8gSlFv!+k|o0_`T*xNWC;=F?+Zvn!8H~*@I z^S)%&qI{r|BX+>~NC@NT{r^UMic`jsF6!cZZk#W09R1aWQxvZx+H;{kL>45eOXZY| z4lGw;F3{7~w`rURr)GqX0ywfoUv74iMXk1Z8#q7WG;yZl#5m(|S~!UW>E}Er?b%^* zoD8RJtspZx-8q9Z0cR4y#+e~vIA0omCD@6m z%x{dTJH0JGYX5P5$n^+qUK^&JpK*TC&GXK$IC9`?FPz^Df7g2(l>euoMpXK5-1Tw( z!R>?dFYfHxsodGLKTF=+IdJEZF&B4E!?|$LAy_$c>l(fi^WrXqJ0I=>vQGeae%&n~ zx1+dn`MiRUf zlGSx|Wz}61cP-qtb(rfKLsPP@#ad5e$=K&^fV)5LhPeH4H^SWvcVpa5#O|4x+JBXl zS)98$u8a{me@onL^fQ6G74Fu!{c^sZTFpPNyW8Tb{TC-p-0gAqz_t0mJ3yGY195l5 z-4Shp$I`+=qKV?n7#t+y^Z5!C68dR$3Hl z+(&T*pT^ZIAWZTJ+$Sv?PicPSrX9`FGa5xcg}BceK4&h5Iq? zCrYbzlKUS7ARjKrk+?LL7)y|w}aZ}t3cQS4pccR__?vBTupx1zl z?UL7CcamC8xw}|x<`i5#^MyOraGC|Jf;&SANQp#gg2Mw!9(l8ifrC_@aDx^1aCgP1@Y#WO}2Oo2o|DIg63TaZ($?){D1zU z##{_<@tlvhL>>!oDZHKW`r@sN*Mql;_EoPJ&t|;dGM01!Z$-Ri@s_vH<#ZE*imhNd zd`!G}E1BfVf;kgU4}am=K`>9xf8niRxF+7(cxwsWiLOEV3Lqc9v^#n0;jNFiq0t)% z>VV~KgtrCW#xf|IcN4r#@isSlvzdLvqHKw`J>FJ$+u?1EXNU2czs+JU{@v=m~^ygl#^#@iEb zAH2PUiPxR~%h7djU%Ug1xgXyCT3Cst`~y1&)xCqdoI_0PP(u+o!id8)R9=uhdj-hf zaJ{4PZ1F#zzj=eT<9V|FqnDB59glYf-U)b@;fej1HabZwQ*MCqPR7$Kpp8C7jgOs) z!#fS{biAQ>XW$LNlPlmJ6Q{SUX&ar@CCAMYHzVOoJwQ5t$Kp0wTYTwWycMs(#z z;$4C_3U4&t1$eUaN8~S@DQVt|1#=GG7~@=;dyUpX!3bFb(%W6|uEaC@?_H%qGHvs& z!Mj!ps1vG7f9pNxU%cz_`{Uh!_YvNWc;oPH(s`zwE|>Kd?`FIQ@ovGB<~IB9sr}bQ zE$?>2I}Gm>^Jv7~c=zN+OvLc+wdYq~0q}JF2T$$45f9utPuRLRg?#Vay+@8Nxb_kQk`AiNJP;z&Wf zkMYGieS-Hp-luqFyw9v4pX2EU8@w;@ig+%bBVKhnhZnxKls;lIHT43#P~R!Ml5Wkt z;C^F9coh*aZB-p@y&9g>RUK~vUIQ=2YihN7i&@RL@Dd?>1ihY}f-;2K-{?hmbe6{}~re@GzHdke_ zmdDr6zWDhNAlg>a(0ujMUj=_ne4YH`uZAzi-v(}fjajs*X@| z>*qnb7~*eesy4#k7=KIrP4G9z@1FniH`8T6nb&FbEd&ekDEzJPw=rUC!+wII#OQ4e zw-eNR8vX6@@5SE%{~Y`Q_=n>U#NP{lN6X1$_&ee6jK4enApBjmhx@zaJsf|xPFJ`7 zk8ig_`+IgwqQAE^Q}1gJ;O~RKFaAOJ`{C=JPyGFR@|n4Rpr&GrwD-Rk6M{OwmcL zrS=}jf6nM9@O5Y~`YFSw@t+Y6{QN7ROzkuuu@?SLXOI^Ob0{E|}SCzlk<-d;q zM$Qz(m$v%||Ltz*JNWhqK+EzT{`)5LfuT+QJMAL&-!Z1X1>k>bXhVQ+OF;e?_(fq# z(iY_6m+|uq`^E_jwg2Gja!}9b+IC&}3Vy?as)jXuy8^=2k?@=N)9_>bN%$@N@%RaT zhA;O2JX<}Jt}gwpt+dwv1oP_o-<)IK$%a#`(@xF#mezFq8TfzV%Y*Q{4jleh_-g<0 zzfl9O#JBk0;fqQ6UPmJ3{DA*sPV2xSBe?%F{x24!#@{%S85K62e{_kzgb;`ScOHuW zFO^*hW~1DnV0OaC3FaU;f?!Sp;mk#_H30;D2<9eOh+v-1EHRjuU_OEc3Fg<5O2LB# z^pLF3{rgB^bs`ijEM-))87xAuKEa{{%MdI^umpkHe-YD42$m%1Ay`VMyFp(;v7EjA zPc(0@q5S{lWu0>d1%g!wmL*t`U^!h&43;NYL5F+U6k$l9_rzMz%1X$}XRs>4+61c+ ztYQ4srQMBKlVB|&)N9cR)*)E8ORp!UMm6N7CfLBTx*@?v1e+6VOt2}zCh{d(x0qhA zKu_Nt2sTqfzD);P5Ns)%Sgh<@<(vI_k6xUhAHlX_ZwcfB-_rV}IM_}qq4(HHqTKXv zPat~sCKy1lJHbGL-3ZkF>w8nM6T!{|a^_d)L4tY|I#A5%o@MPFC)k5vFM>TKR&GD_ z{N03=3HBj4h+toRht>Alk6?cz4lq1WP^%(eXble5z8V}taHtSQA10v$hpUVR9Z7H> zffQjF!O;XK5*$M?Sj&|^w!yIk#}OQ#e+AS2ADp0B5(6HbL?E|4EKP7S!T$(s=5L+B zLQf+&gWz-#R(WmrGYQTnu*IL?EK@a9Fn{YeJ$C=I47M}wFml9l0a2dhX1hyy?jFlh)v;SI(D@~`q zfE!;Ie+aI{R?Ot%nxvwi*n%4fZX&o*)pmORID$I}ZYH>u;1(^BbUCf4+f3o@nnQ7N zr)Y2|!Mz0XfZSuM?iN%%_Wq@@H0SpdUP15xp*&Cz5@ZAq5qv=KFu_X%j}Saf@F>9( z1k!wu_ipp3_MBly>&!oRlHjS{k(cTRJ7J#xI>9pp&l5aL@SHGv&mAXB>v$skVn=Mm z-4^#{0%_A%2qf041h3^9B#MQ;LGWhIQ9av82HqxkkKi4GceURNbMxL_=VMd;zF_Y; z0l|j^A;CukMS_nBJ|p;q;M3j_x}89s_WaKYzEBNUZlz93;Sl%)E`iLibRUuA*(8HJ zR`0Ou2ucJEf-*ssAR?#~;z?2ok_XdJBdGUY@UO=0aGC@OK}^u<9rj&E&xi8tq$+d4 zaEsL@_?2Khf%KLM1XBnm8h;YO|)s9XM$fklAU_^jc{&)-wFOD_=DhYfGT~Z;s}QbExGLdlh1Mfl zkaGe-LMif^rZ5t;)Ym3lmv9~Nid)9Nm2f@6^)+<#o1NAPHze#&xDnx&gc}oXMz{&# zrmA*Sq4OMtn-gv!QB+Uf7NI1!4dK>={c=63bJ)>@+Y)Y9Nc41M@5pDRAcWf!?o7A? z;f{m@2nTAnwDq)ZW$vVDX+>LM-LVJ9qGe|p*vLD)pyXQ$0?n!tm;a-GC67EfS z5aB+A`xEX4aw!oFwsz+P@ zJVJdiHBo6kt=sbnFD4vGcp>2^!V9!q7TxlB5#i|G^Vins(nD0$C4`p|jv>4>7ZZ7T z*a#X9(XWlnU2L zKH+nQ&l|p=)2i^rLUNpWUnZ0~e}(W>-S#Hao3Jzg*BMUu2H{(U5~`-EQ+en99F%I5#22|ptI!el-s{De?6>*QZE---Kqey5zt6bYR?$l^+# zYex8l0bz;I-XHTZIxG`b2%}l#b@HEk3G0My!UkbVC{5fl{dxpI-yFjvPrX}^ERRSy zo^UeZ1j0##6K87chE5@zPB@itS|?Gt5KqfEgYe6l92J%~+3;(^ZwUV+lzRS^@H;|j z{qG5X=sjYk7GtVIWxl8;aUJLro@OQ&MG_(`tFG_O|{!RF=1^x41P-!+w zv**!OXKBtnh|*l0xTU!%Eu|B`(ma&rrL-ue`6w-9ocSp&V8nug;-nOm7S>)=>SMTw zpauylb1}ok4Q+E#X-N%I<$Wn#L#c<-36y#%ZA{4yT$J?ZdXx&3)}XX3rPV1dXQ?c2 zxB{hBD6MERD{04;r>V4ZK9x1*s)nlxio812wkD;uDXo=zP0u>H0Ht*)t!Fap8*V^p zBT5_Qq5q*O*~A1krL-BPt&G4mn}`fs}SKLeGC$=+2aOr8J1rE;E(SlsAFh4fmk5r$yZBzfjH9K9u&QbRebu zDA`wl(*7Mi9sLJUvdy2`f_ep99-Y!*l#ZjMi~pwf2uep%8cgXZO2<$-dZuJoUQf4m zg^%wfp*l~bG?LOul!j0`xht$&fGC|p=~UrRI?eEO!!uMynP)1YVrNkrPHCvoXB(bl zILz=|L6bi(mpA4J!}GgAqbOZQ=>kfZP`Z$kE&G&ob*P)f#k0g6V{vu;$JAa<=}O~_ zr8LVLLdgoN*}J+cc`c^Na<1IJVZ&?e~h*vK;!BYYMjR@J)v}` zZl9v`GNq>}nVl~^V=~WDQsbYCQFB^ggBc@=(+B0i_S~SY64FDY=wBq4a6aF+H~aQ~KQKFAR%@x>96% zB&{kXI|EzN@4uAnYgDO3sY0o2!ci`)Xt7MJW>`0D7&Zk>zC~#UrG(N{N-3oYlrkA` zDYbJAl*S98Z*ZlFl=P)u*9X))*`iO;wSrDPOrtbCr)551p}O1T80Jx&KY*zPSwL z9`p86w(rqaMy;iC!Ejm4Rja>cczMbz=;e*&6)CTz^ z8RKka+P0?LPwT(Dji7Lh-YzFhV0$7_xC7;9C=alR11Voic}L2pQr?O3A(VHfyc=b? zo@F}aT@2+T+H@VPm9snLy(#ZOc~51^_cgf)tGt&ICrOL!L-|0;`%>QDy!+)NyUqm4 z2dD-a_sa)$iG%aW0Odm|AC}KgC?8Jw6v{_XK92H{l#ii&6lFF3I_QraC!ZTA52k#q z?jey)q4`1P^6`{UqI?476J_2g3wqMwbzevMWXiggLTf;T1wT;hP5Cs+=TbhM@=(h1 z3E(`+XBvNq;aQTI&gzX!7|LgxfDHj<9RhToT$U(T=#ofT+z85}jXvLSq|Ob>qjYJm zd;#SPDPJVCMC_o(x|s4M%29d@Wi|ekFEh0DpR%t1bZkQTN>gb2J<3<>yF>Y!d@`ed zp(=(kw(8w_uxe3LHYlx6>?{t<(+Yyq_`XXRUEv54|*l<%T^yDl85_YR$Q z%DEodh1KEQZTx#WUlYprQGUocYX2$g_g~5n3hGCP^23y$ru+!yCn?*YP_`Lb`Ee6d z`!8_?&HL2MRLuJ+GiFojUmB2O@!nDZ%5ru+wGF?!!wic<}z8Ok#|!-y{p zbxn`**M{E+>UPcYw}Qs`UeJgiDE~j8j#E6z6>YIyc_M%>*<%o3uKhZLR#x(mM zEvuG9`O6cnNVLMtSlS07{RB+3GSMotC|`|ebrqA>t7r|PjkQ6eHHp?DTAyfbqIEP> zZfS_tC0b9LRlL&Kqz^s~Z=zj@_9WVsXm^v}O$3bCBPUc|zQRO% zp6CXmn~lDa=q93ZdERvUhYWw(k!~TnRr6_CCAytRn0F98OmrvFJw!Hi%GyhGx7rpZ z?j?GV=sr_>KhXoyK)u`EUHDvAr->fwlv(eNXf=(GSM|G3OKgl;_`=zYzUO^t;hE1W0vhLH@|oGX7sgYW$5>`%m<*Vr4c# z6|T%dWeqBGQdx@1TvXXz7^$|7AOXh^}`%>xA``9%na{o`8%FW3EkQeJblnp{T5DxL#gBqc^b1ks#R%D!7q)#n4qYp|Z6_-_&q3Dw_*o^cGaM z?9y9xPPkP1QIXoPfvmEviEU@7ZxgZ@tEl>SpfZ4pT7D`6yUd-a46;P^7hoo~iv($? z=43ajE1Uf8RQ90qB$Yj>97JU=wX~JJ4fiqJmx_J%t?btmA1@qZ+9%-3fofG${$MKS zQaOam8B`9XGT4}hQ8}E-(NydrpvsYY5s(VoEkI*mq;iaSyG+T_DO8T5auSu}^9%N= zoIvHo|L)$eoUHP47OC>T&fba2sZ>rg<|d~3^q$M~zmrtXq%zcqA%Dx-xk`eH%(7pl?HZ!e{C zBbCdj=$S7nmm7{%8O__3RIWD8RXyj9G2$93*HgKc%5~zEEdjbKV5K&d8#KyFZIii) z%AHikQMrxE%~WocxKwV@2f>o=Iqv}~w^O-8f5|FWOh{(ro`;HVji927|2^j|ZuGr* zg?Fp{ekv02QSnlF(C{I{hYcSQv^>jDnAiN{RAlq#Hr;xbr~6wfPg7}7d4|d>RGy{s zJeB8EMxIKmXDTmHc~M7@ioOC+c{%SkdI+oXDwWr%ye5*}zVrqa>BetTc}JL3?Be&z z+p0=4`7V|By19CvO3`FKF#OQ)BPt(L`ON4~3_lgLxVHFH`9dFou`gQZ7rW%<+PXS@ zDitaLl@gUui(sXoQl=8k?24vVZc35t)l|9m$$Ga>HmOXc5>pvZrKO?rR#-_4Q^U-# z-TU#nLYNYr|62|xQTdw6WGY`$nLn)vnsLoAwF{<;J*t}F1pgNzncy)e3aoQ8A3sPN# z>OxwU>cXl)1^TFfDqr+Jo$6hj>Jn6!qq-#3UaCuJsPg+#?a65^U3F=y1yi_8o)eQ< zw$tTQetF}pKy^i`D``Y&g}foEt57|L>Z(+Crn(wcv))xZ0#IE;O=xvZs%ud-gI`^n z>N=e_clk(BT~8ZJiS?<<=70Nmp;0!Xx+T?(HB`M)KsEkGZ$`Ci|EpVcnOjjEKy_=X zlGT19*5z+YRW|>0zll`)n}+SF?l4Op1{!lm!<|&vUL-jC0`G+{w z!>Jz8rH?euQHDngn&e=rS5iHe>IkaGQ9YCD@l@^qFVz!F;6%fd1TCxoqk0C_Q>dPr z=gmS-qk4MootbCj521P%)$^zhr8>+wXH%8UKQn7afBxRhxgG(?l~6sOs_pi#j-)!O zceBf=USN2kpoj^YcQn^--!K|5)!hso}?| zK11~hqn|W1@*tB)o1lUS@k)?=M7&le9`bFL)n|BH{2EyJ;_V;RjRKQlF3wG zH+;iTKK=?vJ_HNCZTODiyMpS{MXA0|wMz8^svj28Z3^j$R6jC>9~*u`)uH;S(VrQ9 zZuo^^QBYhr4=z=yv=UW6=Ti-+hW}rtEMBS+)yhmh)f&}$&&V_7@9I78+}``p7rpo1 zNwrBeraIZ^7S*JXO5LWqf>+H9+lJ!}Cm2pNoFpi&kbY1|zot6H>Q-898r6TPPNym@ zHG}H6RKF}_%ToQy@N2_w1jSjD?{b>z_ZIX6)gNaG{h8_?#{Y%tuSWc4`1>p}f95pR zzo`Cgq5u3BTANKdwb?r|wK>c?C$+hZAm?jNYV%OrlG?n~mZvr!wI!&{Pi=8(3&^ok zY6}`JWVo`aC8;e{$R5-$DYd>*SJZk6S(#d|;nIf77#0kr zS|oHiL2*5oeW2}FTY=h&Myy0_LuxBiTZ`H%z2k1DwyNQ3hN~N{VYsHCxI%jl^R7*8 zot#5$U25xf>Gi2?kkcYbZKFbaV`>{4ZeqBp;bw-L8*U+J?g(mIQ5!~WYs*_dYJ;h5 zL+wy%+fv()+IG}-q}IRCzM9(hhC3JzFdQgo?tN-IQQOU!I~xw7wu=$Fnt+Ij%a50&2HXbEw@$?Hy{;bzh=(2el`u-APSybyx2u5w*Jojkw3~UTXIlalhdMg5nC} zLuwE8TzM9?hYe-_o6xf7Q}8jv#|@tl6esfLeTv%CMm%Hqtl@Kp&l}3FD$($wpg2qA z<(#JW3bj`)RJMSMjO@J>e8W(-fC~MV;oE}ZEUq-Vw9$Li-q%LF@~qy0zi6mF(H~MX z`(HErU;D(EX8&tu|7&LdYi9pz|Jnb-_`j*SW@_|mdTPEZR6;7GNv%Y!MokPzg<2$L zS)6EA`&_H`j5yDTx}_x-0e!E%LNgOnn@+7oZ7Q{dS}J2f??ZP{%XCnvwatKxH=JNN z(QuOCWJ59jYHc*4Mv+}WpLOXO#`%)kS0?#&k8GE!ePj5o;dh4L8~$MUqo6p8_%rp1 z)PA8pirTN#7o+wY^|@sHqV_wrKd8@1?azYj0I&UJW%=9iAH#nY>$4foZa9aaxWa@z z1$%e?i#pWjr9Ll&_Sxc*EVXxU&T>h5DY<_p)|;oci9>4=zlIsP994Um=X% zkNW;y`T*(&8uOr>Q<(4%^+O~&^+Ty2PyI0JM^iuCI7j4tfclZtkILzewXYvz0)q{Y zrGDJZQ0gZb^TeD(-G+er$<$}D|MgR;pH`T-mPwvom>@YngZeP)XBH+NM16>rREtS{ zsNva$=Lm``OdLb~+`_~csGpZ}s1G-R5!BB&Vr0&@TDpMxBh)XXekb*dsNX<+H1)C6 zFSgV#p?;YWqVv)`&xMKdzAqJWd6$q+%;LS$@G8Tr4X-i0*6=#R>jlM0A29EY)Ne9k zoIGXJZ#KNe@K(dy3~x8QLr|Q_-=$+mT{r(zzeks0>-QRcpW*$64;VgZ_>kelg5q?P zsXt1+Nc}PD?^2iQeZ|y1LH$W1o}wO8p%!n5C#E4V{Ix=nJj5nMx3x5*w0k=Mx#sbu*Q2(3yRO&xapGIBkVR~WG1nM&i zlchetH2li&Yr}60zcu_$P~D^ng~eQY z@J~T;g~>zJXsi4`G^F|d?fLvW8nYSBZa9bGoQ885VmPO<3p|J`L`T65nG?q17&Tx6d6%1E2T*+`{L2>e=n|DxQ?JWwGWN;jI+Ms28J6NZX_tqWHzC(jWMM*HZx*#!z~QAG?c?BlIN`r`w5Ec z=4x9S+Zn&V;r50*7^?lJG0;%$zc@>AXBubI7)0Y_8oSUqipH)qc9ULFm@c--hGXgovXNu!@Kd|FVP zd^I!gvxd(ZKHoFuawA?ed`ZxVmubAxrC;s2ykN}NY1nwzupyxFCJnRy4YU7^j{T=$ zt459Yq_HfkA6Tn>X!w!g$A+I6eroucpt`Bo()fbLREt=o;m~N)aA}ljc*gV%1H;g; zln>v9snVK}aVj)gG^#Xo!cU_{L+AhMXe*MBcY*_0pn+dX^VB~@iZoM z>4_F9L%`%NN2B)+eT&94nsd;YPUB}9GfeDD8e)gPGFsn9XnbS%t)RGWFB1L_#{bdq zrq2F7L7EHET+--;Y4)MHn9+;STvP~g@-Aka#SNF}dY7WPjB#`e z5X~N%y+$l8Cqf;9Ex zug2=+VNDaTPr%K!Y3ddrn(G?s`ajL}4fXpk%?$-R>#@y^Y3f&9nwwbArZhLBsULi4 z>L&o2o%R22&bOv%pL(19EY>zOx6OmP0{v-jZ$Y~GN1V|EX*S7!B zJkU4?(L9)@eqZQ(lFn~%rGPc_uNAMzc?=raV>tECu1^K6=D(bScnoSBEx zJf|yjt`IcyN*HeR2*dLYM;eYYyuk26!;1_@3+9?>UScVZp?Mii-T&F;Uv83PXW?8) z^GTXl(Y(uouBLen%^PW6t1_|`p?Mw6>n-R8<#Zb8CYrYxa~#c^h0q|m#Ho2J&D*;4 z?KJPCd55$@-s`&jyJ^a@|2>kWS=#YF38g8Y03M+ED9s1W`;ed!atqL7G#{C%ho*QR zmmuRyx3RAb&8KL-Nb_ln@{HlLhR+#3Z}@^>-Z9Mk63v%|=(g{xG(VvE8qGKK>X@e7 z3#y-EkbhM--_?x@P2K-V^KHX-@{VT$@6mie-_ELgR+}HvbZCA=^Apqaajr*~h?}1p zen#_iO;?qFL9>|C7Ua^LOfwJiX*OvFmRx99GAz?<(A51uG%JR>1(asZur4Us5L80G zjcT4_!xqiNWKzS-ux+TDe`roHoM<>nFxNwKibPkhs+vapI?d_CYtWoQECy5R>Svl? z(fo$y*CJ*SrSp7C^Cz0$iI=AKd73|HkldZs{82B?k~8M^!HecE#B2J7 zY5t+Yw#V2wf6{acxGOs^?dzy;7eUCOX; z&Lr+7UYU4l;+2S(AzqQVU`Z@Xyu1<1soLN=;uVyztHU}btCQ9$#H)7GwLu~7*ni96 zn#B7PuSL8I@!G^&60bwNDe=0*8<_s}h}Z8Z+~O_b4T(1)-l)^nWBmlIbZ*(=&4@S8 zb8d0B=$2wDH3IS0#Qll;5pQRq+w@E-5O15u6^^7rygl&%VzvKyqQnD{+p*#uiFcaG z>;?@|jw(_6PrRFYHKE;!_aNSzcu(W+C8$AKz55WG@$cWqg7)j?@Brddi4P<`hWH>; zaxn1`#D@?cs&VuC+2X^9&Hh`jBR(?MOnj7ehNGn+OzmLelZcNcKCa7A`)@KfUc@JM zS}I?hUYZ#`NWr)unhrnFH>yxKfchIX8&Wg|HKyycH49e@mOND|HPLWs{NN86h>cR zc%|W0hF1%AABt;<#}P~Cyiv3fUr(&|UubLVo0PV!65mX$V}WGV@_cJI&$ko5M0^MF z1H^ac^2B!$->n-S;(HA56+ZEOT~+sY<333I6tUWW-R}`UO#Dcfe$+UR5v%I;!lkJi1=e6)M+(-YD_g#;w<79#7Ywv4IN@P zS7o8TF@vsfNL&(r-ZL%8hJe_HfVfIrBW@AbiJQa?5l|;hrbZ^t)mp4XIHo_-&-$_5 zuosUP+Po7CCmK%b`E*_*rVxK+#8ks+hSLpa7=GE6RQpf-4e|HH-x~d$pk{kgZUBGC zb3*(R>2}0FlSp;_LL!gtuO#ae|3>Rv;@@e_N&E-#zoz6*!@r2t{u}*IPquD{Zp}t( z_MWNBD!(;{pq?3T%|&ZlTF_dF*4(rfp*0V!`Do2sn5G7&@bS%sY3I_KzcB57S_@c| z1q~N6T-dOWps8JyR*%UqMr-lHvL? z?RB8FmGG^wThr>-rMD4|#oCV6A+-9_+C}m}YkOKd&>CdX2hbX5GCK+yXD3=a=d@t& zQO{e=>`H4NE6#3(=}lU@)7ryg?P<7|;ogGX6!(>cEaHB&_P5Xj3=cFs$WYs>Fk>F0 z52bauRp4RLE|$q7Xq`^$NLt6zI?6alo8&QugEj8-rqRdII@xOPc*7G6Pc%G9&}9B+ zsP>*T2(7b(FZE1oD6O+i=A15PShxPqqgA9eoYw8M zM$o#B*7>xqp*51$SX!b-jXbRj3NtpQb)n%!hNA^7$Oenn7@-R@PM~!uEw%qfUoNQh zwB2Z3Ax{vkD`}biZ>jy43K67rtrVd!<9e%~>uKFW>jqlmXh~PRN$N))8Og1LT5a5{ z!XF(?>sDH~X)NiYGv2qjchGvA)}6HOp>-FnyCq$VRk6@}X+28oKIx6t`tle)V5vW7 zsP>=M!-i`AJ2}^$_L!uu>)Wj-XuVGBNm|d_Q~Xq6#&@)yruB@)de%_wfA8?wRD<@h z7c6Os`;vveY-sks^{UZk|3Aum#T&Ffurj`BE%;X9OLML}%tNvi$-E>BlguZ@(Gn$Q|C0rbUeItML6h&J$t8;zy{PJuzLzXU zvUrzX!Z=HI+Hr>H7ZZIhNsm4bNiWIL7FX@RaExA-WNnh=3SX_HEtM=!vI5DfBr96z zN`@;Nt|FLgCRxoms~fIixTfJ+f`zZlW+v;9tSdS#OY3Q;${){U1Cs4XHYAbEZ$z@0 z+MmR#J=w&RY}&Cr$>!$WLeK~s0umbn@?k6KN3xCd1Pk3(P&;ALpJWip_9O#HcF386 zCNq#^MFfXOZ-dy?$hBH5p0Z<2k4W7*!<1oq3lMyvfd z!iIq4plJ)Y!*uE2@H z>4u(6GL+>kvRPj6}y2aTeFs zf07X-BT3Hh@U^x_rxlE7# z$^TW8v9it}99?Zlt|YmJ~!+i{ydi=Kof5l3O)bni<*rDR@WjHL<${yP@}xJV0`<@$WO#Cr3wHEdnl4rGyQWjYa7$pUKp5!Hx7f5vT zzXo;qQVFkU*^Q%9+2l2ncSv3*d5h!?k~f8!>k%|nZ>t8Ce3wLj`$A&B0247y`h#3Q z$w#E~l6*|kCi#RUBvJcMA~Ii)e6HmZg~}MJ^1h6#z?|7rgn5CIV*8Bg*J$pn%aBomD@iDatD zPd4uq)n->fkW3>{`>z~1EU!6~qqTxxk$kO8X@!f&l6*_@7s+=dGQIzvL=8TP4FSoI zLie8Vg*cL*1uaPC|9_JFW~zSA3q_*mzjE4S{w9UwAJREV{w1AV596n^<=IeyRE_{! ziBh%y`G1=9iuiPH3!NutlFmoE7U}$?OOh^NK?@@Lc+-VQ7tvCu3zPN{elD-5N)|K8 z#k)yp^O^8cq$`v5B~?>T+GE&Dx^$iaEoNFEU4e92(&e>)>2kS}PL-xBnx2*N8%ixL zwg056lCCC_R!p`37IzK9HFFK5Ym;tHx(?|E7G+)1^@K2b{am5Z8yaq8xH0J_q??g$ zDtXKGbpC0TZb542EYdAWx3Yxf|BsU4etA|q?UKs*Upb&B71f{gL(=U@FCpE5^bFDg zq{opCB$e&=5^+b;os8I-^k7n5{2|?ibZ=6%|D8)QrDH?`k-cRCfDI#38~V z)ggd%DCyZk7=2DRpXZWZU{xx@!%0VxjvyULs`g(KwcK`kNT>2IB)!O#j8+->`)Yb| zml#9(FzKbFHL`%Qlin%{P224zd5087yjG~YNY%)b-c5QB>AiV(GR}Q@p-3OF zxN7{3|B#?@9wB{-^ik5sjsIBAx1c9Tb@8X0%G0E;S#r;iK1=!nsm}jNpYPP0mhMH; zS1j&Jq%Y^HOy*TV^{UM4q;HbGA!4}z>06}lk-km(uEl*v__^US@B3Y`54xS|BeJ7N zKPD5y@(JlwQfZwA>1U)R($7gi4sRzflylG^X6)T`I}b*}Qe zWDseYv`QM0R)pW%&u-e>#wV?j)b`k}FYdDxZCdFUk?Q@=7O{H;bo#q_?egb5bycfZK=%wEMEVce zyrln<%|SMsae4d?_H=;$%yb*%hF|k(_(EKwq+D$a=_@)<277 zno92_gL+R`sCVlPv^ZHo>qiqBUwSr`ul5HkT70BBwlf{3jhb;wFt+yE2)?^2g^&{JpY#Xv&$hIZhnQS|R zA=_8;X>Dr!{mJCym+HxtlO05M1lhr4hiK*{MJ?c=WQUO*u5tgX&m%itE$Pu@pOGCy zHkNEK*-)}$$xb9Y&KmuAvJ{OL`qbkl~olbU! za!z?qTSw3u*MiPczP9q&WTVN>Asa&O(0Glz>bD1$ZjXQlI#YutH`b+yP8Zd z|Fj-{t!7(ng2|Izudy@(H`t=m-t$zC-X89uas zTDotT{x=0RZ`RYu-X?p8>|?Tb$==hRCO67zHr^-uz%+bl_>n4+7Sx{n3E8JPtv_&4 z&gW#)$-W?~lNHG-WDc2>)Fsm&A4qbVqSmk!r!1+Eg=8gVb}BjQ$aK6_GCBWiu{1Lc zvWa9(vWzSyOC^X*jsOcLg1M5s&dJ(jRQzcT@N)*>5vtw8v^${-h1r zUu6GS{{PPNPxdeEIcd*^oPiRt_UwXcZ}T6@C}%FUz4BbN^`>Il^BB&nV%A-0&(Fjq zXfMF{GifhK`+C|7(LRCp!nAj%-G}yuv=^bh0qsTgI-2%kvgUm3 zQn2vVaHIR?gwegUm!`eUGOG^|uekxVm!-WD?d52%KzsRakO)Z7in*8e%Cy&_y$bC$ zX|GCqwZd1go51R(b`297Q26SH!q@8=XKmW+(q2b65>)v5G}^)!Vm;dHo6LZo%lqri zb?uF4?}QuyYi~n)6WUwS-c;r-v^S%@c|O&ly+yt~Nl-r$wRQc6_SS~|I^S;E+tQX= z-A-@4X!p0E?FHrkjM}%`QkH?@&CfE^-cbngn%K_z;@lRQU36`sEyKZMM(if2GwSvp zwD+OCC+)p@n)Pn1y}Rk|OM5@L@5NN<9MrG~{JrlyG$NPx~m^dh?Ia%GdTCO#3+6GXEc>!h-T;wtc*ycxj(VdnoOb zXv^WR;qvlMTbO6jK85xf|3}qXKs|2ke!KLP8Q-_vGBeZdmYJEEnR(mYGBYzXGv6|7 z@n3GY977y1#EzXZe3B;F+wYv5-_d9^8V$)~*%_PK!>Ao@#1Vq})5adX?LhVu`E_3{$lV#dp zfhP;?N@`;2Rn*?3b~Uwosa-?uW@^_`yIy*d+I4!Cj+~-XyMfwG)_XUqyUFQU|*qxQV< zUl>TfXi2>UP?H{e*^;{Xr}nDhYlgc2r}l=RNE-bXwXxLRruL5NmzdkU9lgZY-lg^t zwfBbiA2ocE^v3(tJ}}}#L77y7Qru>aG@(iD6KY>k`;^+})IQTJ32Mm*%6(bw3u<3V z&*)EXfooq=`+?dx^K#$V%>UN#J8It#&3wKr0hwLG(KD}VKbnz08P=$Is#yYzHJ_Tr ze`?`?Q>UiEA8HN9F}g*qN3CuBER(#TmQc&6rNSBXe`i4Cj%j_^rKaD1p(ZDgjF*(d zA8h+w`^Aixw)|!?zf+SF9z^&LLpcIMX!ZZp^je_$WsyqKn-FhByor!EguRLJro)>= zpSgQj?xc8A<4uM)CEnzCQ|L03i7H=CkxA=KrL>HZ(susaX^-N$BOqi#o`g3&-VAuN z=vQ~W8S!S)_n^F)bxhrvW}ck?@bU1*&j@Pgctd!@cuU~Tj<+D*9C-8Mjli1=Z_c3w zch@fwFStA2+<5Z{q2E~f_&Gg8a?GcA^Wn{pw}4u>oO~@;5pN;9#qbu!TSRPDS0=aZ z-lBS^dFs{j!&_Vl@paykc+27~rDvUaOB*gDd>wSRKnrf74U~w14F}$4c$?#Gp|uE8ek!vS-p+Vi<87xejdc_kt@OHx6QRk<<*P&ii`Mcm9h_|b*CT};qJ@9r{fqt%@`G&WbR;R*y8}4Jc zui<`%`x_o0XoGtY-l2F0OIa&^h~V(jp2-}BcQ~Fof73bi<6r%kb-knUC&4=g?-jgb zk$>rW$KjogcRb#Sc+*A z{2{|@9BuTOcxMSAqio4@@Gi$Y7w-?(>uhuhIJcD7XR^X#Jfq26l3IT zr#gJM;N6aQE8cC|USU2sr%Y+QJMivQ&-UWt4)N~Bdl>H?>)3no9yH=UJU3VmNK$?_ zdmnNGDrg2gVhxw2`j{==$MK%SdjjtnyeGvC$`9`;Lp=q~h-c+!Bh8YNj=bmbWHP)k zwCWP;b6&;!4DU6(*R==KQKQT;gra^a` zj)^~`ac07w)rgr5XK@1f+7|pF!{GsQc6@y(;56vy%r*)BT=?_i&#gNmUoHXi#6fo; z`19c}h5XssUle}<{Dtrr)LdE4a%u1v?q6U0MU>-0(Eeihi{tCz|1wg7_)E&NGGb}` zd+bWG5+%SE8(wzzoIG^-zzR%X0N|8{;E0$d<_BVaI3I}AY{9XKN5dk z{MDr`*1$FJ*EE5()Ia!Z0sQubY+v4wlza9Se%F#CuWwR~)yra7u zn^imGKZw5z{w?^s;-4fvjK3TH?)V4c?}5LMarC;6zZd@AIt2RL$T<7r?}vYY(fd25 z(eenu2^@@n6#gOjhvOe=S%(P@a*r@tG#@!+AA-a`8vg|RWAKl|m+yaxf;9rM-DZU-`<6nkAO5@e@0*W$ zPnUq+i;2Jbz%2hzzCwuqkb1NdLs;{D2s ze65n&=T5`7_#Xau_&?!)KWM@aCaL>>tJEylD~sz@e^55WZ{gRiLn8c!t~K>SO-C5N zji2JXo=mhkqve^qbbf}P<9A#YR$YPrj}cw`690FjrB8bJzvBNaN#p#Y`&jj&zqt*q zZf^ZQ2DyJ))?fI4E5}I&6DTv#iwnU-%2#4yf=LE6vu!rTfS!zCa)RjyrXZM#U`oA_ z7yo0|UxKNHAee?gH~&(^A%XbklL%(eCI>U>kjW!0X{j=2Avl9zR^&ha!E6NU6PW)G zh6$E1OJ*mSgJ6Dw5#sy_<|LSxU@n5WRln==U>@yK8S7xa{%OgwXATyy;tLYk?PRc! zCEX+7`w12$Sll>^jn`wzB?;Ele^doa5iCux!ie-1f@KVsC0LGNd7+2p+uZ+mm>|K5 z1S1JnB3PARWr9^CcSQP$%1AUm_z%Hq!~Zwdm{Mvi!RkU2tf2z`H&&GB6bja|s@FDL z$8cSO^@JE2HI*edAlR5-LxPQlzPmyLAlAJEn-FYi0-GAjB|xA58okB;o(~3F5o}Fh z0iR$S%e7a49&TADs)TYg0Lr*=c_cd}dWN-w* zaRf&a9BTqc5ghFfBqTUSg!_jR%KcGrJb}!j69`UJrX0{MGeJTrx~6usHNO3RiIY^g0l?m6`0m1nM7Z6-ba3O(yKh*97 z&$sz`3BhH$ArCHfe1gkGxe91YuOzshK+k_OZR-CCt|7RV;5w0)%eqMFCPxJ1UpB#w zG?pj0iMn+A&D3R$+(LLY!L5Wc@opn{jo@~IM+xpAc!1ze0#T^`pWtq@=N^Ll2<}w_ zWJh=42-Ux-eYHW{W+3zYAzi<+j|v`^i;Jz$M^r|weT?8~g2xGDM4lL$a|b!gl;BBS zx$YiTH9SM`k_N1TXKevKNASE6FA%&agxH|X(Y2{TGlG{1)cKnk;%8n}pD%aN!Ry*& zHTw;MH;s_^za-x#C<)#nNC;&4{6HY8z94vy;1h!PP38jvQT3tDKrPbu|2FqNc1+{T zCQWibGyGgo%}`(WrJ)Sj*96}Y$n^Nel`{UfPLkkzNjg5kj|2h1PbQ%LpFrb3M)-n* zBB62W1QCJ0@I#;>AcCfoA!rXI^%5Yo{0LIR%&=pa8y1FoD`Z4P@Fzjf=${RB|4;C% z;ctd^nF!SX%L(en`HOIFg1-sHOaDVS1>ppQlM!l}oXBAk|R7Q*SQ`1FLf?GI=8m%vPfGyhA^tb}tA&PF(FMTT6KDWCmc zVk0bfPQtnVrD`6+r3mLGT!e5w!UYNEH!=19t}LNCe`78@;4DhG1mR+Xw`Bad^#KLt5Hy~VBZyDs?AzaUJeRnrOxZ!}m5#h#4+g%3Xri42aZbrD1 z-e`oI6K+Ac4dIrATj`ZiqQ`QB5N@rs4(PUo+Z(anfU^VPjv^yT$I)f0&EJJkgFiwG zpKv$A-3j+1+(Uh7xaWAkYRSC~_i;?Z{RodE+~4#ZKzLyPDi9uIRUb@v1mPishnaxh z0vK_)BTVK9f$BNCA7)s5MHd?w(wlS^9U~>JiqV#8lwL~LJgED zM_-kko$wMu{k{+3rG}Te&;1ZyVaY3|-Gp~(3`%|*9!_`*iV2@2e9FZ-6m^O|L-@QA z&l)}_C|QIr2w|KT314!wOjjGbmkD1Xd_y<7;j4zP5x%YucAs9rl5c7+Y3^HwZ)@^t z8RD^oUlY2S`5xhCgzpo&nW<4A!Vd{Q>d#@jVJEatfQ6qB>i56orx!t${G9L$$07XE zIA7_0Sgw-p70vJ)>eCT^OV}p-jxZqnp6~}3Hz53x(36{GLc0Wnb_*cUY`K>SW&bY` zp_2$h!WLnj(0qQVj{r2V7B*!})sn&7Ba8|ECQJx_Axy1{GQz@!zGEoy|JC)~<4~PH zVM*8{tW4pj#?k%1Oedpd8c3X9rj0xT5d59+54*?LZ)p)q@V~z^C;W%{l+-7nESLKlMIc!Lv_|Cwd7=4i%jzR{t>a$QEqAu}&F<>@TGUyrV!^X5vz|{2T~r+ zp*}ZtarHIo^H87Ha_7^k-O8;mKwSbpqIN;Ug_Q68LVXeHiyLQA>bm)NnyvT})R&Yj z$E3bA^);z4Lw!Z+5&{xc%jw{14=hi81?f-iSn1XJO4L`QzOpG<#c);X68W*6synH> zKGJEXE{;Vs$hY8K*|n%|L49rN8&Y3~y7+vlXkF*2Omcn04V=7n=|zeUdk~4XJ*mG=eJ|>#QQw>T3DozYei-$AsUNJDv-*D2_osfKl%jrsoI+;A zL47^$+===j)DKm)vR72G!>J!h{RrilyfKfWel+#ts2^j=V>Q?PpCS|6>=4Lm%4l%-qmII z-Z03O{r}BMtJ$|ucT4hC>bF@Z-!7|3bG5_oG?}|x(~Q1{`s38^rT&oVypOt!>4VfC za0Fcb=~|^e~S7u#(Y|f%W~GLpEb^Nt|0XnsJ}}6 zMPt51{blO^RpI^|ctwbRjqhukrJCO$nwR>U)C=lwQ5PrtHuaCFzeD|f>SG6DBKe-u z{VDW;(9}OvjyrLpt`pUmA5;H?`Z((58|$AL^K+F{o4+vp((o(8ujQ$bD{IMbsfX0R zqwZ7xp1OJa`VZ7C_|vc5oWC0HNfV@$RS^t`I`xElM7>45VOdSB-R7Pp+t%<{nR2pU zJ*A%OSK;cJVt0}XQ!jUbxS#H8N2h~^=h zTfLbWrV;Gft>An_%M;B{v^dcMM2i@6K_cD%8@;gLKyvG|Yle6$47azslKElsr4 z_}Ynwn`fvWu^Fj`)8a zS)#Rw)>cVnYVb#DAzIgPJy#>q21HvDZAi2^(MCj@5Xt*L8bdP;n-XoNmjLI+RnHcx z$E`Ivb}!n-#I|4s+*o;fIWHqrS+=Mb6OkM#CgG`lK@E+EpwpM|M>HTzQ2hqJmcM{!AbeC{kmhO%sc{*S_(&#>uyuY8T{rMo#vqTROJxTO1(c?sqxLl$~ z&49-S?R`Sbc2kb%Dbw?`2|VKjtOuSWdX?yTqL+wXFc}L0iM2Wk|0Q}^XQ{lFpjE%3 z%T{+#lJ%Muf8Fp6K`Z+f4RJwl6U9XD5dA8huLi1(EuHOMdRMO!&(I^J}8-j5Cht8=`NOKNxZ2YY4~;{*kCj^b?U! zr2gM>^%DR_1co6|M5KTIa$Q8!==YU2za@MVYpZ}fvW#R36~;*oGolWWM1Jhz-uG`^ zqTh&0>%fZW7owgC|2#M%Nv|VBzYZ+qR2OeLk?8qk=P#-uc6q%j$dX=qGNV`>^x(3r{urX1gsqPefUF|E@;V>%&d zsQ;%igR{!$nGE$RKw}m`ZBb)38dBpBjfH3o(-=X+yn15}6)@XOY)%>)|FN=j8_q*x z-T`Mm8uLpwtY(PUd`7|U1B)Accjdjj9HgQ{hqxBMC#O8)u z7;Y(OMYg7~7Y(}*G`6))+s<%%!yU9;s(D8mI~iwZ8oL{37sFi*cM}v>Ph$_0*;7lo zuP@0-m0Els8v8n?yCTropT@B?4xn)ujRUo~iXBAbVAF7jB@cCc{m*0La2iL_I6^q8 zr!Rk$i5+ctO#drRjpJyHqM_gaqH%%-z8WX$($p&1y#BG zAXMWO6;^Na8jaWg)f;b^+BXf~8jSTjG(IxUSVLK=@6mW)Xw&e4F+bF#Ge#$a4A#dq zKBw^s4Y|swCo!?loKB;^=wBHdU(pz6oUc{Gr3H;|Xm~WfrSSs|{r;~l$?x6rw0`)} z@FyBIAymK$4j)j_2xv}V+Cmz28Yzv4hIpigFpb}&(HhY53aE5r+}9wgwDyd~A2d2N zx-@dDs1PwHWAY^psYtwp#Q$ZsOZ?w16S4u*%R6|0&vXzrQkJCkV6OmlXcv(Ox-Y4M+?#(#7`n?wEiVBa94IS0*o zX^x;dH_bU|&ZRcXK2wC9D{jsswCfz2^9fCJe$%sn(?fG1nj>j0OmjJ!i_lzx=Atwg zQw_3M)Hb^WXu&0EE~VPsY-uiS+Lkd~c2LFgG*_ajZKt`SV1Jf2SEi|ZewwQcikts$ z_Wi$z(Ucg~Ka0fwAB|n;a2{5KP|E4+r zra6DrwmZ!|TvzCHYVJkz02AJu<~~O3YbgK!a&u$Jpa|z(f1O~^Q1*0OjE}3fFcs|*(9}zS_1-l!<+NvM__Z{z6K4Ms*1Un{EvDf{ z!wYpAcQ8Sy^N zPiTHX)3*Ihy#xr~vc%Mn9c?DL+47lii$nzq$%c4?NTymE~lI$O;CnU=WjUugbe&iq%sJ#7AF z_`9IV|4H+&p_+OUntvPqBdCV8>}u80_`mX76VsZM)+CbD4R8xL(6lC_HMw+xzS_~6 z!X&3OoJ#e$^BY^!(3*qRw6tcRrJH{f(EY#mUTa2LGwZ}?%_PY|?ku!4{zGdv!y&_A zLzU_GLu&-Bd1%c^Yi`S$YiOL@(dfI2mel<}Esg&hG5^5k1!=8DYav=o(^{C;;m#oV={wIZ#RB&nQ!2de+4 zW!Hk%f0QpXzcrH9=Cq`@*QBM+-^tTji`IIy)~2P`1uM9&X1N*OTA$Wtv~1K`8(Qv0 zh8r7hVz{ZG>m^+~+Pzx{$FjDhwUyPrwOdw3Z%gYCZ~}F);Y8eqb0q4IIZJo9YO18TI&C8XpVBDX-zwZ*0I9ZpSD!}Kdlo6k|z#k z%gMAxTh=IArwnpW6~d{aHHOw{w9eFHR9mMTo-q(Ri>@zxLRuGTDP1NP)4IgX#=)Gt%w#Ti0!Cj+>jqj^(Yjiha#D>>ylV`vHN4L7 zdO?%F(a_>Qt(%A5T}@^Mty^^By(_c&Hd<2e?X;evbqB3`O!!V(ciAZ4tyP#Fu@KpbCf${gwOhK;7)Ep`4zyg=(kr`(dF=S^BK(|VPb`u{;Mng4I8|Cb*vy9D$X^;^a<|KECtmO6j= zS#Q5btDyBhtuJVOKpK?r}edU?>OWrw$?Y&309YM_;;>nEcXXmA*~;2`LupAPK}o5 zdQ9c}V-)m@=rGlVNh=bL2+(TMN@%qP&2Jl1|JZ2N$$+M`GFrJcvmgH-WOZqab1rH9 zN~;MK*DggMypU-kkOp{p#Fl!|knTw`p%pJD|M{ z?aOIzOZz0++tJ=zBY5rYY0IeXWb}^zbIa|WY42k8>?&xvyBY3ITW5&Tdm8Q~Xp;NT zK8W_dwD-59#{Xs9bt~RJ!0ZUv7pRR2%=C_~%- zw{8FL-bd1`-I&!&9}?K5bfN_&injqWeK_Gz?FcN*l? zM9Vr;(1^3-TCBV1_BjL2x!Mq&w8FfY_5~((p`gCj*w(jzjC09A{!%kTF9EtGYhOY8 zMcP-=zK8Zzv@Oox)~f(*i~qDW{$t7OY2Qfu1}&)ncQ&WdzKOQfqIU%PR#N*`&6QVM z+qcoaeL&w~oI4HgGL-MXpJY1krTv5v_tCze_G7dku%uoBXg_55u;C+yj|#fKvD1Fs zZnxF>>vpIu{}B*;iniSjwAKI9e%A0gL-qf(Ul1Jh$xF1~r!6!8HQFx=-vnNv{i>tQ z)YoZ`rTqr&cWA#kka>%?I)At2P@>-(@6vv+e_y5x>I2$e)BcdQ)ccW&Sx;L3e@y!m z;n24Ie_Qwev_CieLeONsH2g}?_~U5nJD;?_5j4(sv_0c|Px}WWz=3m6K%OY zR9aC>$@@R|ON-1eg@$#*$W%3GH;rfs%FmR@r230?Li=ahDeVsJtUoRL?agTy!lB)@ zvL$Ub)x5Df|A9mPh4!Dcf7Q!P`#0g6@b9$M`Ky@!yJQ*jZ(?!9{}4|`JOMGp6ZQuy z)=L2K#DAV5m`=HPQ-CA8N_N1jS~fk03st z_(CoFPMe8}aSLcgm}k#CPZkV@lkmF9yqDKKpD&d=GJ#_+H{Si0>nQkywWH3E~Hc zCE6qUA0&QA&dCv4j`kvcg!oZC1V*iWjQH{Y*)QiN#iIN<;-`q8?w_I)KSTViHbf4M zk@Jp@UWfR3vq3G<<8j0iHR&Z{nN+VB{qlI7#IF*+M*O6Mx{e$w@VmB|qYih{Yx9A27wLp_4Zv{)G5b;xC9lBbGP+ z^?2MhYLz6D z2}mX*nUG{sl8HzrA(>daPTDJ@nV^L9mCaRcdonr6bR<)dOieN+iA4T&CXaYuB9M|h z4av0q_DYjiKbB;Al9@x{e$lryYB(d|~tWhMhlI%({8_7tLA(ACXhDpS> z*+~{4nS*35k`Zo*N#=AllFUsqub4+NkD%2)AIbcVRvEEea~C9$mp?U0variFdQpaZkgQ2!$G#M|r zy-IQb$-yKCk{l#!+{rjQNe)qK<(#hMFp{H44j1P`a)gR0^GLygVSkQ_%c zn&fzrlSodGQ`ba{L8JZ==%TMS0 zStMt>#V%cH_Mb~~CCPat7n7V%av_Pt|Nqy?B=Y!Ay$#7FB$o+Ia;Y{!N9}TwD@5~% z&9_ti%DIZ<29m2ut|PgIc{Vd6yB#)5XMe-oY-6Z#s+(UA&w#8a!hTTu{fU9fx#lMa7kfB}hEV(H3bLIK3_N1&wl4sO5nR0?^>T@K|le|Rog6VltCHtlROY$no%OtNT z)10RbaesVm+}|MimgG$m>A<&0)Z3H1P4W)OSZReM<^N*R7LxZ!J|uacME|@d!=(1; zg!za>wlq2!+z66V}Ek39whY$I1)SmO&n|zlEfr+k|s$+(&$$zK2ZCuMbg%KCE4$v zgd`(L`^{8K)c@;b&`!`JKuNkJzmb$AzmVwh6eK;XRQ_q!R!Cm=rmOP_OJNIT`7cYJ57mC8rSl zUr(k}lTJ@6xzkzhw60xhl9ru;RF$awi+}5yN@pgWopct`A<|h%XA`C_8TrY{oawO6 zSl2V@9HeuSj?l6;aHMmQ&h1u{URYKCyrheg&PTcs>HMS%2-CHZbU`6x^^q>Dohf6K z>dBTyEJiAp$oGFWDM-2`=`zU4Z|TyGGvF^vx}1thC(Gcb%ag7!UVwBZ(gR6XCf%NN z6;cscm2_=VQMe}QYNR7|`Kz6)>$#5U8rrKPuP|EAf6!hM@13qgx&f)3|C#FfpEd+` z1ZcnE^8DBJ=f2{JU9IIjn z(%ndRB;Cb|>}05?fEux@;DFwpbYIdvNcSe)lXNdxATC$07wJCZ`$P!R{YdvGJ-{)C z_u0&}9YlH@>A|E&ksd;NxEXM$;bGc}{-7U0dgQ;7a^oibbBv%7#}34fSLK>@g5imz zC;h88M$wV1Q^+17J(X-m($SbuV z^c|%4lHN&rH|brH>-d7C_xy{#&p7uh=TkcmMBbPh|BN7gSa1E(M@SzfeVX(!QuX|# zkDEdvo)l+m9K8exP5KP!bH>p(g_NoC&+DV@ROZ8rdebfOkeLo670oH>%cNhEzC!v1 z>8qq~SwmhUeVz0T?FtF9NuVQrQ}4XwZ^h}`r0=K(Idm%>OZpM1bf(bnk;*9$athzf z8f?*H38mDB(k_#f;ro8N9zlXF(HUa6+q`wS( zzm|qr(_ck^^f%H!)Z3@O581Ap^iR@%NdF@JTPz=cfzWG#IJ9g+G7Zs?O{6O~o0x18 zBhdAwoJq+hGs(#fry!e#Y)Yf2GMrjaCxB|xTL2@bBa_1)`h1xTR>e$Yqse9_+nQ_^ zvbo7-C7X+EHnQOn+393MI;^r%GCT7yn?t_}nvF1=Q_$AhKcY(eB%6n9NwRs#79yLE zY(bsj+5BX31e{DrjhFTbl%N(0bgzbBv=0bWz)c_AlsO1li>qy*H=EQnPi)p*ydzgl5Np%vbCLTtAWfm zWIK^dg6k&EI*;(pLGCll3em2L>CA*pIJhH3E&L_K!>;kfj$u#~? z)*ly>9K`>#ORX+D6fwJ;>&xWY3Jq%Ap_MF#0)1 z2rcqtFAhq*L}t%NvX_njir~<#@dv;1v2dz0)fAxzjF0h}J| z|DCh3maFcI>^-vg$v#k@WB)9cci_oBBKv|&8)a%gCi{f!GowG%Q{Ak+WS_gM9@&=z z&R1k#lZ|t|XZ5OivLeX7qXXIZWDT+($m(Q2lIdGvWIyR+2)X2C@_;~+?$5tM1Z1H- zp^z!$Bqa(YlCqGSWYUlpS*8y@vbK#yY?zSsw;z`5kV((vdL7IP!>&$dSsH>m8N{}p zEN!x%$$lB{3XJm`*`H*;ll`H8|Cj$+NGV;af6WdEoy>P(;oE^fGl;gW_+8Jhp^EF-j2LT5QTYtS+O-&ui< zx&4k_0?c5GOLtbWvKIgE{Kx3k3`ZL3&P`m9pe5I&qyC?c#{cQ8Bd89lvo0O=|8&+j zwCBGai~n~vax%u>gw6qUHl?#Woz3WMO=olCZ$W2E`3#3ywUx@d7iBx!(Ak;Jwsdwd zF^&Jz*}nhwV@JRLYn+|j6D6Z}p|dNU-Gp|zrc<21jPjmz_M@{GoqgzN@Lztz2hK}p zUq|Z$l+OPDf58Lk97IPBd6wdG1d!k%bPjbgbPgL>as(aegd^!3P3Nfb8t5FOw9NdD zy#Fb9oZ<0?Cm726pNELdK;bAlrzrjXT69jO^FE!?bgrf|hR*qPPNQ=cozv-@X_lNJ z=!z?$ww-Nwj%c8BE}irGxmxuFbS|ZHp$W(aLxeA;Bk%v{!*2ccHC#sLaynPik^R4A zspM5IS9fcjYv|lT=UO^9($SCq(z#yHYP>-$*Sc<^bDL$|Y8izGSEeWzdl$Km}hheAVzZK`Z+Poww+`=~M~Q zd7I9Ablw?MI+l*^y2lGBEgQwo2XsC(;v+#L27BqwClveB`IKB5_!;>|bUr5+@AL(o zoX(eY>U6%M^CO+F>4>WzC-;wZzA^k(e0%>+v-3TjA9T+kyIHv%7N6hwiH=XFM#qyI zdA-4R&o4RwozNB3vJzG3M0DD88oH70H0iV?Y9Rvp(_FDXqm$6l_u9Ff|AuVFA_u2OW+4_bXLxf0Iv6=O6Nk$R{A5 z(8U2

                                    ph-}d!^9J6TdF=l7Vl7Oph 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

                                    F3q3kf# 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

                                    +

                                    恭喜,异步项目创建成功!

                                    +

                                    这是默认index.html,本页面由系统自动生成

                                    +
                                      +
                                    • 本页面在项目目录下的index.html
                                    • +
                                    • 您可以修改、删除或覆盖本页面
                                    • +
                                    • 您可以将web目录修改为您的前端文件所在目录
                                    • +
                                    • 您也可以将后端直接反代到此域名
                                    • +
                                    +
                                    + + """ + public.writeFile('{}/index.html'.format(self.sitePath), connect) + # 添加反向代理 + if hasattr(get, 'open_proxy') and int(get.open_proxy) == 1: + args = { + "proxyname": self.siteName, + "sitename": self.siteName, + "proxydir": get.project_proxy_path, + "proxysite": "http://127.0.0.1:{}".format(get.project_port), + "todomain": "$host", + "type": "1", + "cache": "1", + "subfilter": '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]', + "advanced": "1", + "cachetime": "1", + } + + self.create_proxy(public.to_dict_obj(args)) + # 修改启动命令 + start_cmd = get.project_cmd + if 'php' == get.project_cmd[:3]: + get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.site_path, get.project_cmd[3:]) + else: + get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.site_path, get.project_cmd) + is_fork = 0 + if ' -d' in get.project_cmd: + is_fork = 1 + # 创建服务 并设置开机启动 + realserver = RealServer() + realserver.create_daemon(self.siteName, '', get.project_cmd, get.site_path, get.run_user.strip(), get.is_power_on, logs_file=os.path.join(self._phpa_logs, self.siteName + '.log'), + is_fork=is_fork) + # 安装依赖 + dependence = 0 + if hasattr(get, 'install_dependence') and get.install_dependence == "1": + public.run_thread(self.install_dependence, (self.siteName, get.php_version, get.site_path, get.composer_version)) + dependence = 1 + else: + public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}/'.format(get.php_version, get.site_path)) + public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(get.site_path, 'php-cli.ini'))) + public.ExecShell("chown root:root {}".format(os.path.join(get.site_path, 'php-cli.ini'))) + public.ExecShell("chomd 644 {}".format(os.path.join(get.site_path, 'php-cli.ini'))) + # 写入数据库 + pdata = { + 'name': self.siteName, + 'path': get.site_path, + 'ps': get.project_ps, + 'status': 1, + 'type_id': 0, + 'project_type': 'PHP', + 'project_config': json.dumps( + { + 'ssl_path': '/www/wwwroot/phpa_ssl', + 'sitename': self.siteName, + 'domains': [webname['domain']] + webname['domainlist'], + 'project_path': get.site_path, + 'project_cmd': get.project_cmd, + 'is_power_on': get.is_power_on, + 'port': int(get.project_port), + 'log_path': self._log_name, + 'php_version': get.php_version, + 'dependence': dependence, + 'start_cmd': start_cmd, + 'project_port': get.project_port, + 'type': 'PHPMOD', + 'run_user': get.run_user.strip(), + } + ), + 'addtime': public.getDate() + } + pid = public.M('sites').insert(pdata) + for domain in domains: + domain_arr = domain.split(':') + if len(domain_arr) == 1: + domain_arr.append(80) + domain_arr[0] = self.ToPunycode(domain_arr[0].strip()) + public.M('domain').add('pid,name,port,addtime', (pid, domain_arr[0], domain_arr[1], public.getDate())) + + # 检查是否创建数据库 + if get.sql == "MYSQL": + if not (hasattr(get, 'sql_user') or hasattr(get, 'sql_pwd') or hasattr(get, 'sql_codeing')): + return public.returnResult(False, '请填写数据库信息') + mysql_data = { + "server_id": 0, + "database_name": get.sql_user, + "db_user": get.sql_user, + "password": get.sql_pwd, + "dataAccess": "ip", + "address": "127.0.0.1", + "codeing": get.sql_codeing, + "ps": "", + "listen_ip": "0.0.0.0/0", + "host": "", + "pid": pid, + } + add_database(db_type="mysql", data=mysql_data) + + self.modify_project_run_state(public.to_dict_obj({'sitename': self.siteName, 'project_action': 'start'})) + # 写日志 + public.WriteLog(self._log_name, '添加PHP动态项目{}'.format(webname)) + return public.returnResult(True, '添加成功' + ("" if dependence == 0 else ",正在安装依赖!")) + except: + return public.returnResult(False, str(traceback.format_exc())) + + def re_install_dependence(self, get): + if not hasattr(get, 'id'): + return public.returnResult(False, '参数缺失:ids!') + project_config = public.M('sites').where('id=?', (get.id,)).select()[0] + project_config = json.loads(project_config['project_config']) + if os.path.exists('/tmp/php_mod_{}.pl'.format(project_config['name'])) and int(project_config['dependence']) == 1: + pid = public.readFile('/tmp/php_mod_{}.pl'.format(project_config['name'])) + try: + import psutil + p = psutil.Process(int(pid)) + if p.name() == 'BT-Panel' or p.name() == 'btpython': + return public.returnResult(False, '正在安装中请稍后重试!') + except: + pass + public.run_thread(self.install_dependence, (project_config['name'], project_config['php_version'], project_config['path'])) + return public.returnResult(True, '正在安装依赖!') + + def check_install(self, get): + if not hasattr(get, 'name'): + return public.returnResult(False, '参数缺失:name!') + if not hasattr(get, 'php_version'): + return public.returnResult(False, '参数缺失:php_version!') + if get.name == 'swoole': + res = public.ExecShell("/www/server/php/{}/bin/php --ri {}|grep Version|awk '{{print $3}}'".format(get.php_version, get.name)) + if res[0] != '': + return public.returnResult(True, res[0].strip()) + return public.returnResult(False, '未安装!请前往PHP-安装扩展中安装{}!'.format(get.name)) + if get.name == 'fileinfo': + res = public.ExecShell("/www/server/php/{}/bin/php --ri {}".format(get.php_version, get.name)) + if res[0] != '' and 'fileinfo' in res[0] and 'enabled' in res[0]: + return public.returnResult(True, '已安装!') + return public.returnResult(False, '未安装!请前往PHP-安装扩展中安装{}!'.format(get.name)) + + def print_log(self, path, log): + public.writeFile(path, log, 'a+') + + def check_auto_install(self, get): + if not hasattr(get, 'path'): + return public.returnResult(False, '参数缺失:path!') + if not os.path.exists("{}/composer.json".format(get.path)): + return False + if not os.path.exists('{}/composer.lock'.format(get.path)) or not os.path.exists('{}/vendor'.format(get.path)): + return True + return False + + def get_swoole_correspondence_php(self, get): + if not hasattr(get, 'swoole_version') and get.swoole_version.strip() in ['2', '4', '5']: + return public.returnResult(False, '参数缺失:swoole_version!') + swoole_version = get.swoole_version.strip() + php_correspondence = { + '2': ['52', '53', '54', '55', '70', '71', '72'], + '4': ['72', '73', '74', '80', '81', '82'], + '5': ['80', '81', '82', '83'], + } + php_list = php_correspondence[swoole_version] + result = [] + for php in php_list: + setup_status = os.path.exists('/www/server/php/{}/bin/php'.format(php)) + res = public.ExecShell("/www/server/php/{}/bin/php --ri swoole|grep Version|awk '{{print $3}}'".format(php))[0] + swoole_status = True if res != '' else False + swoole_version = res.strip() + result.append({ + 'php_version': php, + 'setup_status': setup_status, + 'swoole_status': swoole_status, + 'swoole_version': swoole_version, + 'setup_swoole_isactive': True if swoole_version != '' and swoole_version[0] == get.swoole_version else False + }) + return public.returnResult(True, data=result, msg='获取成功!') + + @syssafe_admin + def install_composer(self, composer_version, logs_path): + composer_name = '/usr/bin/composer{}'.format(('_' + composer_version.replace('.', '_')) if composer_version != '' else '') + if not os.path.exists(composer_name) and composer_version != '': + url = public.get_url() + '/src/compose/composer_{}'.format(composer_version.replace('.', '_')) + public.ExecShell('wget -O {} {} &> {}'.format(composer_name, url, logs_path)) + if os.path.exists(composer_name): + public.ExecShell('chmod +x {}'.format(composer_name)) + public.ExecShell('echo "{}" >> {}'.format('composer{}下载成功!'.format(composer_version), logs_path)) + + def install_dependence(self, webname, php_version, site_path, composer_version='2.7.3'): + logs_path = '/tmp/{}_install_dependence.log'.format(webname) + + # 检查锁和所文件 + if os.path.exists('{}/composer.lock'.format(site_path)): + if os.path.exists('{}/vendor'.format(site_path)): + public.ExecShell('echo "{}" >> {}'.format('composer.lock和vendor目录存在,不需要安装依赖!', logs_path)) + try: + project_list = public.M('sites').where('project_type=? and name=?', ('PHP', webname)).field('project_config').select()[0] + project_config = json.loads(project_list['project_config']) + project_config['dependence'] = 0 + public.M('sites').where('project_type=? and name=?', ('PHP', webname)).setField('project_config', json.dumps(project_config)) + except: + pass + if not os.path.exists('/www/server/phpa_project/logs'): + public.ExecShell('mkdir -p /www/server/phpa_project/logs') + public.ExecShell('chmod -R 755 /www/server/phpa_project/logs') + RealServer().daemon_admin(str(webname), 'start') + return + else: + public.ExecShell('echo "{}" >> {}'.format('composer.lock存在,但vendor目录不存在,开始安装依赖!', logs_path)) + else: + public.ExecShell('echo "{}" > {}'.format('composer.lock不存在,开始安装依赖!', logs_path)) + self.install_composer(composer_version, logs_path) + public.writeFile('/tmp/php_mod_{}.pl'.format(webname), str(os.getpid())) + public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}/'.format(php_version, site_path)) + public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(site_path, 'php-cli.ini'))) + # 尝试安装composer中的依赖 + self.print_log(logs_path, '开始安装composer依赖!\n') + # self.print_log(logs_path, 'cd {} && export COMPOSER_ALLOW_SUPERUSER=1 && /www/server/php/{}/bin/php -c {} /usr/bin/composer{} install --no-interaction &>> {}'.format(site_path, php_version, + # os.path.join(site_path, + # 'php-cli.ini'), + # '_' + composer_version.replace( + # '.', + # '_') if composer_version != '' else '', + # logs_path)) + public.ExecShell( + 'cd {} && export COMPOSER_ALLOW_SUPERUSER=1 && /www/server/php/{}/bin/php -c {} /usr/bin/composer{} install --no-interaction &>> {}'.format(site_path, php_version, + os.path.join(site_path, 'php-cli.ini'), + '_' + composer_version.replace('.', + '_') if composer_version != '' else '', + logs_path)) + # 查看安装依赖报错 + logs = public.readFile(logs_path) + if "No composer.lock file present. Updating dependencies to latest instead of installing from lock file" in logs and not os.path.exists('{}/composer.lock'.format(site_path)): + self.print_log(logs_path, '\ncomposer.lock文件不存在,安装依赖时需要使用此文件,请前往项目官网下载composer.lock文件放入{}目录后前往重新安装依赖!'.format(site_path)) + public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {} >> {}'.format(php_version, site_path, logs_path)) + public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(site_path, 'php-cli.ini'))) + public.ExecShell("chown root:root {}".format(os.path.join(site_path, 'php-cli.ini'))) + public.ExecShell("chomd 644 {}".format(os.path.join(site_path, 'php-cli.ini'))) + self.print_log(logs_path, '安装依赖结束!') + try: + project_list = public.M('sites').where('project_type=? and name=?', ('PHP', webname)).field('project_config').select()[0] + project_config = json.loads(project_list['project_config']) + project_config['dependence'] = 0 + public.M('sites').where('project_type=? and name=?', ('PHP', webname)).setField('project_config', json.dumps(project_config)) + except: + pass + if not os.path.exists('/www/server/phpa_project/logs'): + public.ExecShell('mkdir -p /www/server/phpa_project/logs') + public.ExecShell('chmod -R 755 /www/server/phpa_project/logs') + self.modify_project_run_state(public.to_dict_obj({'sitename': webname, 'project_action': 'start'})) + + def check_development_setup(self, php_version, name): + try: + # /ajax?action=GetPHPConfig + import ajax + a = ajax.ajax() + get = public.to_dict_obj({}) + get.version = php_version + res = a.GetPHPConfig(get) + for i in res['libs']: + if i['name'] == name and i['status'] == True: + return True + return False + except: + return False + + # 域名编码转换 + def ToPunycode(self, domain): + import re + if sys.version_info[0] == 2: domain = domain.encode('utf8') + tmp = domain.split('.') + newdomain = '' + for dkey in tmp: + if dkey == '*': continue + # 匹配非ascii字符 + match = re.search(u"[\x80-\xff]+", dkey) + if not match: match = re.search(u"[\u4e00-\u9fa5]+", dkey) + if not match: + newdomain += dkey + '.' + else: + if sys.version_info[0] == 2: + newdomain += 'xn--' + dkey.decode('utf-8').encode('punycode') + '.' + else: + newdomain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' + if tmp[0] == '*': newdomain = "*." + newdomain + return newdomain[0:-1] + + def ToPunycodePath(self, path): + if sys.version_info[0] == 2: path = path.encode('utf-8') + if os.path.exists(path): return path + import re + match = re.search(u"[\x80-\xff]+", path) + if not match: match = re.search(u"[\u4e00-\u9fa5]+", path) + if not match: return path + npath = '' + for ph in path.split('/'): + npath += '/' + self.ToPunycode(ph) + return npath.replace('//', '/') + + # 添加到nginx + def nginxAdd(self): + if not os.path.exists('/www/server/panel/class/404_settings.json'): + public.writeFile('/www/server/panel/class/404_settings.json', json.dumps({})) + + template = None + use_template = public.readFile("{}/data/use_nginx_template.pl".format(public.get_panel_path())) + if isinstance(use_template, str): + template_file = "{}/data/nginx_template/{}".format(public.get_panel_path(), use_template) + if os.path.isfile(template_file): + template = public.readFile(template_file) + + with open('/www/server/panel/class/404_settings.json', 'r') as f: + settings = json.load(f) + status = settings.get('status', "0") + filename = settings.get('filename', "404.html") + error_page_line = 'error_page 404 /' + filename + ';' + + if status == "0": + error_page_line = '#' + error_page_line + else: + error_page_line = 'error_page 404 /' + filename + ';' + + listen_ipv6 = '' + if self.is_ipv6: listen_ipv6 = "\n listen [::]:%s;" % self.sitePort + + conf = r'''server +{{ + listen {listen_port};{listen_ipv6} + server_name {site_name}; + index index.php index.html index.htm default.php default.htm default.html; + root {site_path}; + #CERT-APPLY-CHECK--START + # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除 + include /www/server/panel/vhost/nginx/well-known/{site_name}.conf; + #CERT-APPLY-CHECK--END + + #SSL-START {ssl_start_msg} + #error_page 404/404.html; + #SSL-END + + #ERROR-PAGE-START {err_page_msg} + {error_page_line} + #error_page 502 /502.html; + #ERROR-PAGE-END + + #PHP-INFO-START {php_info_start} + include enable-php-{php_version}.conf; + #PHP-INFO-END + + #REWRITE-START {rewrite_start_msg} + include {setup_path}/panel/vhost/rewrite/{site_name}.conf; + #REWRITE-END + + #禁止访问的文件或目录 + location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) + {{ + return 404; + }} + + #一键申请SSL证书验证目录相关设置 + location ~ \.well-known{{ + allow all; + }} + + #禁止在证书验证目录放入敏感文件 + if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {{ + return 403; + }} + + location ~ .*\\.(gif|jpg|jpeg|png|bmp|swf)$ + {{ + expires 30d; + error_log /dev/null; + access_log /dev/null; + }} + + location ~ .*\\.(js|css)?$ + {{ + expires 12h; + error_log /dev/null; + access_log /dev/null; + }} + access_log {log_path}/{site_name}.log; + error_log {log_path}/{site_name}.error.log; +}}'''.format( + listen_port=self.sitePort, + listen_ipv6=listen_ipv6, + site_path=self.sitePath, + ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), + err_page_msg=public.getMsg('NGINX_CONF_MSG2'), + php_info_start=public.getMsg('NGINX_CONF_MSG3'), + php_version=self.phpVersion, + setup_path=self.setupPath, + rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), + log_path=self.get_sites_log_path(), + site_name=self.siteName, + error_page_line=error_page_line + ) + + template_conf = None + if isinstance(template, str): + try: + template_conf = template.format( + listen_port=self.sitePort, + listen_ipv6=listen_ipv6, + site_path=self.sitePath, + ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), + err_page_msg=public.getMsg('NGINX_CONF_MSG2'), + php_info_start=public.getMsg('NGINX_CONF_MSG3'), + php_version=self.phpVersion, + setup_path=self.setupPath, + rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), + log_path=self.get_sites_log_path(), + site_name=self.siteName, + error_page_line=error_page_line + ) + except: + template_conf = None + # 写配置文件 + if not os.path.exists("/www/server/panel/vhost/nginx/well-known"): + os.makedirs("/www/server/panel/vhost/nginx/well-known", 0o600) + public.writeFile("/www/server/panel/vhost/nginx/well-known/{}.conf".format(self.siteName), "") + filename = self.setupPath + '/panel/vhost/nginx/' + self.siteName + '.conf' + if template_conf is not None: + public.writeFile(filename, template_conf) + else: + public.writeFile(filename, conf) + + # 生成伪静态文件 + urlrewritePath = self.setupPath + '/panel/vhost/rewrite' + urlrewriteFile = urlrewritePath + '/' + self.siteName + '.conf' + if not os.path.exists(urlrewritePath): os.makedirs(urlrewritePath) + open(urlrewriteFile, 'w+').close() + if not os.path.exists(urlrewritePath): + public.writeFile(urlrewritePath, '') + + return True + + # 删除站点 + def DeleteSite(self, get, multiple=None): + try: + proxyconf = [] if not os.path.exists(self.__proxyfile) else json.loads(public.readFile(self.__proxyfile)) + id = get.id + if public.M('sites').where('id=?', (id,)).count() < 1: return public.returnResult(False, '指定站点不存在!') + siteName = get.webname + get.siteName = siteName + # 删除反向代理 + for i in range(len(proxyconf) - 1, -1, -1): + if proxyconf[i]["sitename"] == siteName: + del proxyconf[i] + public.writeFile(self.__proxyfile, json.dumps(proxyconf)) + m_path = self.setupPath + '/panel/vhost/nginx/proxy/' + siteName + if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) + + # 删除目录保护 + _dir_aith_file = "%s/panel/data/site_dir_auth.json" % self.setupPath + _dir_aith_conf = public.readFile(_dir_aith_file) + # 删除保护目录 + if _dir_aith_conf: + try: + _dir_aith_conf = json.loads(_dir_aith_conf) + if siteName in _dir_aith_conf: + del (_dir_aith_conf[siteName]) + except: + pass + public.writeFile(_dir_aith_file, _dir_aith_conf) + dir_aith_path = self.setupPath + '/panel/vhost/nginx/dir_auth/' + siteName + if os.path.exists(dir_aith_path): public.ExecShell("rm -rf %s" % dir_aith_path) + + # 删除重定向 + __redirectfile = "%s/panel/data/redirect.conf" % self.setupPath + redirectconf = [] if not os.path.exists(__redirectfile) else json.loads(public.readFile(__redirectfile)) + for i in range(len(redirectconf) - 1, -1, -1): + if redirectconf[i]["sitename"] == siteName: + del redirectconf[i] + public.writeFile(__redirectfile, json.dumps(redirectconf)) + m_path = self.setupPath + '/panel/vhost/nginx/redirect/' + siteName + if os.path.exists(m_path): public.ExecShell("rm -rf %s" % m_path) + + # 删除配置文件 + confPath = self.setupPath + '/panel/vhost/nginx/' + siteName + '.conf' + if os.path.exists(confPath): os.remove(confPath) + + # 删除伪静态文件 + filename = '/www/server/panel/vhost/rewrite/' + siteName + '.conf' + if os.path.exists(filename): + os.remove(filename) + public.ExecShell("rm -f " + confPath + '/rewrite/' + siteName + "_*") + + # 删除日志文件 + filename = public.GetConfigValue('logs_path') + '/' + siteName + '*' + public.ExecShell("rm -f " + filename) + + # 重载服务 + public.serviceReload() + + # 从数据库删除 + public.M('sites').where("id=?", (id,)).delete() + public.M('binding').where("pid=?", (id,)).delete() + public.M('domain').where("pid=?", (id,)).delete() + public.WriteLog('TYPE_SITE', "SITE_DEL_SUCCESS", (siteName,)) + + # 是否删除关联数据库 + if hasattr(get, 'database'): + if get.database == '1': + find = public.M('databases').where("pid=?", (id,)).field('id,name').find() + if find: + import database + get.name = find['name'] + get.id = find['id'] + database.database().DeleteDatabase(get) + + # 是否删除关联FTP + if hasattr(get, 'ftp'): + if get.ftp == '1': + find = public.M('ftps').where("pid=?", (id,)).field('id,name').find() + if find: + import ftp + get.username = find['name'] + get.id = find['id'] + ftp.ftp().DeleteUser(get) + try: + # 删除项目 + Redirect().remove_redirect_by_project_name(siteName) + except: + pass + RealServer().del_daemon(siteName) + remove_sites_service_config(siteName) + return public.returnResult(True, '删除成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def get_sites_log_path(get=None): + log_path = public.readFile("{}/data/sites_log_path.pl".format(public.get_panel_path())) + if isinstance(log_path, str) and os.path.isdir(log_path): + return log_path + return public.GetConfigValue('logs_path') + + # 检查端口是否被占用 + def check_port_is_used(self, port, sock=False): + ''' + @name 检查端口是否被占用 + @author sww + @param port: int<端口> + @return bool + ''' + if not isinstance(port, int): port = int(port) + if port == 0: return False + project_list = public.M('sites').where('status=? AND project_type=?', (1, 'PHP')).field( + 'name,path,project_config').select() + for project_find in project_list: + project_config = json.loads(project_find['project_config']) + if not 'port' in project_config: continue + try: + if int(project_config['port']) == port: + return True + except: + continue + if sock: return False + return public.check_tcp('127.0.0.1', port) + + # 获取项目列表 + def get_project_list(self, get): + ''' + @name 获取项目列表 + @author sww + @param get{ + sitename: string<项目名称> + } + @return dict + ''' + try: + if not 'p' in get: get.p = 1 + if not 'limit' in get: get.limit = 20 + if not 'callback' in get: get.callback = '' + if not 'order' in get: get.order = 'id desc' + type_id = None + if "type_id" in get: + try: + type_id = int(get.type_id) + except: + type_id = None + + if 'search' in get: + get.sitename = get.search.strip() + search = "%{}%".format(get.sitename) + if type_id is None: + count = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?)', ('PHP', search, search)).count() + data = public.get_page(count, int(get.p), int(get.limit), get.callback) + data['data'] = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?)', ('PHP', search, search)).limit(data['shift'] + ',' + data['row']).order(get.order).select() + else: + count = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?) AND type_id = ?', + ('PHP', search, search, type_id)).count() + data = public.get_page(count, int(get.p), int(get.limit), get.callback) + data['data'] = public.M('sites').where('project_type=? AND (name LIKE ? OR ps LIKE ?) AND type_id = ?', ('PHP', search, search, type_id)).limit( + data['shift'] + ',' + data['row']).order(get.order).select() + else: + if type_id is None: + count = public.M('sites').where('project_type=?', 'PHP').count() + data = public.get_page(count, int(get.p), int(get.limit), get.callback) + data['data'] = public.M('sites').where('project_type=?', 'PHP').limit(data['shift'] + ',' + data['row']).order(get.order).select() + else: + count = public.M('sites').where('project_type=? AND type_id = ?', ('PHP', type_id)).count() + data = public.get_page(count, int(get.p), int(get.limit), get.callback) + data['data'] = public.M('sites').where('project_type=? AND type_id = ?', ('PHP', type_id)).limit(data['shift'] + ',' + data['row']).order(get.order).select() + + for i in range(len(data['data'])): + data['data'][i] = self.get_project_stat(data['data'][i]) + logs_path = '/tmp/{}_install_dependence.log'.format(data['data'][i]['name']) + if not os.path.exists(logs_path) or int(os.path.getmtime(logs_path)) - int(time.time()) > 20: + data['data'][i]['dependence'] = 0 + return public.returnResult(True, data=data) + except: + return public.returnResult(False, '获取项目列表失败') + + # 获取项目状态信息 + def get_project_stat(self, project_info): + ''' + @name 获取项目状态信息 + @author sww + @param project_info 项目信息 + @return list + ''' + project_info['project_config'] = json.loads(project_info['project_config']) + # project_info['project_config']['bind_extranet'] = int(project_info['project_config']['bind_extranet']) + # project_info['run'] = self.get_project_run_state(sitename=project_info['name']) + # project_info['load_info'] = self.get_project_load_info(sitename=project_info['name']) + project_info['run'] = RealServer().daemon_status(project_info['name'])['status'] + project_info['ssl'] = self.get_ssl_end_date(sitename=project_info['name']) + project_info['listen'] = [] + project_info['listen_ok'] = True + # if project_info['load_info']: + # for pid in project_info['load_info'].keys(): + # if not 'connections' in project_info['load_info'][pid]: + # project_info['load_info'][pid]['connections'] = [] + # if 'connections' in project_info['load_info'][pid]: + # for conn in project_info['load_info'][pid]['connections']: + # if not conn['status'] == 'LISTEN': continue + # if not conn['local_port'] in project_info['listen']: + # project_info['listen'].append(conn['local_port']) + # if project_info['listen']: + # project_info['listen_ok'] = project_info['project_config']['port'] in project_info['listen'] + return project_info + + # 获取指定项目的域名列表 + def project_get_domain(self, get): + ''' + @name 获取指定项目的域名列表 + @author sww + @param get{ + sitename: string<项目名称> + } + @return dict + ''' + try: + project_id = public.M('sites').where('name=?', (get.sitename,)).getField('id') + domains = public.M('domain').where('pid=?', (project_id,)).order('id desc').select() + # project_find = self.get_project_find(get.sitename) + # if len(domains) != len(project_find['project_config']['domains']): + # public.M('domain').where('pid=?', (project_id,)).delete() + # if not project_find: return [] + # for d in project_find['project_config']['domains']: + # domain = {} + # arr = d.split(':') + # if len(arr) < 2: arr.append(80) + # domain['name'] = arr[0] + # domain['port'] = int(arr[1]) + # domain['pid'] = project_id + # domain['addtime'] = public.getDate() + # public.M('domain').insert(domain) + # if project_find['project_config']['domains']: + # domains = public.M('domain').where('pid=?', (project_id,)).select() + return public.returnResult(True, data=domains) + except: + return public.returnResult(False, traceback.format_exc()) + + # 获取指定项目配置 + def get_project_find(self, sitename): + ''' + @name 获取指定项目配置 + @author sww + @param sitename 项目名称 + @return dict + ''' + project_info = public.M('sites').where('project_type=? AND name=?', ('PHP', sitename)).find() + if not project_info: return False + project_info['project_config'] = json.loads(project_info['project_config']) + if 'run_user' not in project_info['project_config'].keys(): + project_info['run_user'] = 'www' + return project_info + + # 获取项目SSL信息 + def get_ssl_end_date(self, sitename): + ''' + @name 获取SSL信息 + @author sww + @param sitename 项目名称 + @return dict + ''' + import data + return data.data().get_site_ssl_info(sitename) + + # 删除指定项目中的域名 + def project_remove_domain(self, get): + ''' + @name 为指定项目删除域名 + @author sww + @param get{ + sitename: string<项目名称> + domain: string<域名> + } + @return dict + ''' + try: + project_find = self.get_project_find(get.sitename) + if not project_find: + return public.returnResult(False, '指定项目不存在') + result = [] + domain_list = json.loads(get.domain) + project_id = public.M('sites').where('name=?', (get.sitename,)).getField('id') + for domain in domain_list: + if public.M('domain').where('pid=?', (project_id,)).count() == 1: + result.append({'name': domain, 'msg': "项目中至少需要一个域名", "status": False}) + continue + domain_id = public.M('domain').where('name=? AND pid=?', (domain, project_id)).getField('id') + if not domain_id: + return public.returnResult(False, '指定域名不存在') + public.M('domain').where('id=?', (domain_id,)).delete() + public.WriteLog(self._log_name, '从项目:{},删除域名{}'.format(get.sitename, get.domain)) + result.append({'name': domain, 'msg': "删除成功", "status": True}) + domain_list = public.M('domain').where('pid=?', (project_id)).select() + domain_list = [(domain['name'], str(domain['port'])) for domain in domain_list] + NginxDomainTool().nginx_set_domain(get.sitename, *domain_list) + return public.returnResult(True, data=result) + except: + return public.returnResult(False, traceback.format_exc()) + + # 为指定项目添加域名 + def project_add_domain(self, get): + ''' + @name 为指定项目添加域名 + @author sww + @param get{ + sitename: string<项目名称> + domains: list<域名列表> + } + @return dict + ''' + try: + project_find = self.get_project_find(get.sitename) + if not project_find: + return public.returnResult(False, '指定项目不存在') + project_id = project_find['id'] + domains = get.domains + if not isinstance(domains, list): + domains = json.loads(domains) + flag = False + res_domains = [] + for domain in domains: + domain = domain.strip() + if not domain: continue + if not self.check_domain(domain): + res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) + continue + domain_arr = domain.split(':') + domain_arr[0] = check_domain(domain_arr[0]) + domain_arr[0] = self.ToPunycode(domain_arr[0]) + if domain_arr[0] is False: + res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) + continue + if len(domain_arr) == 1: + domain_arr.append("") + if domain_arr[1] == "": + domain_arr[1] = 80 + domain += ':80' + try: + if not (0 < int(domain_arr[1]) < 65535): + res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) + continue + except ValueError: + res_domains.append({"name": domain, "status": False, "msg": '域名格式错误'}) + continue + if not public.M('domain').where('name=?', (domain_arr[0],)).count(): + public.M('domain').add('name,pid,port,addtime', + (domain_arr[0], project_id, domain_arr[1], public.getDate())) + if not domain in project_find['project_config']['domains']: + project_find['project_config']['domains'].append(domain) + public.WriteLog(self._log_name, '成功添加域名{}到项目{}'.format(domain, get.sitename)) + res_domains.append({"name": domain_arr[0], "status": True, "msg": '添加成功'}) + flag = True + else: + public.WriteLog(self._log_name, '添加域名错误,域名{}已存在'.format(domain)) + res_domains.append({"name": domain_arr[0], "status": False, "msg": '添加失败,域名{}已存在'.format(domain)}) + if flag: + public.M('sites').where('id=?', (project_id,)).save('project_config', json.dumps(project_find['project_config'])) + domain_list = public.M('domain').where('pid=?', (project_id)).select() + domain_list = [(domain['name'], str(domain['port'])) for domain in domain_list] + NginxDomainTool().nginx_set_domain(get.sitename, *domain_list) + return self._ckeck_add_domain(get.sitename, res_domains) + except: + return public.returnResult(False, traceback.format_exc()) + + def _ckeck_add_domain(self, site_name, domains): + from panelSite import panelSite + ssl_data = panelSite().GetSSL(type("get", tuple(), {"siteName": site_name})()) + if not ssl_data["status"]: return public.returnResult(True, data={"domains": domains}, msg="添加成功") + domain_rep = [] + for i in ssl_data["cert_data"]["dns"]: + if i.startswith("*"): + _rep = r"^[^\.]+\." + i[2:].replace(".", r"\.") + else: + _rep = "^" + i.replace(".", r"\.") + domain_rep.append(_rep) + no_ssl = [] + for domain in domains: + if not domain["status"]: continue + for _rep in domain_rep: + if re.search(_rep, domain["name"]): + break + else: + no_ssl.append(domain["name"]) + if no_ssl: + return public.returnResult(True, data={ + "domains": domains, + "not_ssl": no_ssl, + "tip": "本站点已启用SSL证书,但本次添加的域名:{},无法匹配当前证书,如有需求,请重新申请证书。".format(str(no_ssl)) + }, msg="添加成功") + return public.returnResult(True, data={"domains": domains}, msg="添加成功") + + # 获取项目状态 + def get_project_run_state(self, get): + ''' + @name 获取项目运行状态信息 + @param sitename 项目名称 + @return dict + ''' + try: + sitename = get.sitename.strip() + result = {} + project_info = self.get_project_find(sitename) + res = RealServer().daemon_status(sitename) + result['status'] = res['status'] + result['sitename'] = sitename + result['is_power_on'] = int(project_info["project_config"]['is_power_on']) + result['project_path'] = project_info['path'] # type : str + result['project_cmd'] = project_info["project_config"].get('start_cmd', '') + result['project_port'] = project_info['project_config']['port'] + if result['project_path'][-1] != "/": + result['project_path'] += "/" + result['site_run_path'] = DirTool().get_site_run_path(sitename).replace(result['project_path'].rstrip("/"), "") + if not result['site_run_path']: + result['site_run_path'] = "/" + result['php_version'] = project_info['project_config']['php_version'] + result['run_user'] = project_info['project_config'].get('run_user', 'www') + result['ps'] = project_info['ps'] + result['pid'] = RealServer().get_daemon_pid(sitename)["data"] + if result['pid'] in [0, '0', ''] and result['status']: + res = public.ExecShell("""ps -aux |grep "{}"|grep -v "grep"|awk '{{print $2}}'""".format(result['project_cmd']))[0] + res = res.strip().split("\n") + if res and len(res) >= 1: + res.sort() + result['pid'] = res[0] + result['Listen'] = [] + if result['pid']: + children = RealProcess().get_process_tree(result['pid'])["data"] + for chi in children: + for i in chi.get("connections", []): + port = i.get("local_port", '') + addr = i.get("local_addr", '') + status = i.get("status", 'dasd') + if addr == '0.0.0.0': + addr = public.GetLocalIp() + if port and addr and status.lower() == 'listen': + result['Listen'].append((addr, port)) + result['Listen'] = list(set(result['Listen'])) + + return public.returnResult(True, data=result) + except: + public.print_log(traceback.format_exc()) + return public.returnResult(False, '获取项目状态失败') + + def domain_to_puny_code(self, domain: str) -> str: + new_domain = '' + for dkey in domain.split('.'): + if dkey == '*' or dkey == "": + continue + # 匹配非ascii字符 + match = re.search(u"[\x80-\xff]+", dkey) + if not match: + match = re.search(u"[\u4e00-\u9fa5]+", dkey) + if not match: + new_domain += dkey + '.' + else: + new_domain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' + if domain.startswith('*.'): + new_domain = "*." + new_domain + return new_domain[:-1] + + def check_domain(self, domain: str): + domain = self.domain_to_puny_code(domain) + # 判断通配符域名格式 + if domain.find('*') != -1 and domain.find('*.') == -1: + return None + + # 判断域名格式 + rep_domain = re.compile(r"^([\w\-*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$") + if not rep_domain.match(domain): + return None + return domain + + # 修改项目运行状态 + def modify_project_run_state(self, get): + sitename = get.sitename + project_action = get.project_action + logs_path = "/www/server/phpa_project/logs" + if not os.path.exists(logs_path): + public.ExecShell('mkdir -p {}'.format(logs_path)) + public.ExecShell('chmod 777 -R {}'.format(logs_path)) + if project_action in ['start', 'restart']: + self.async_dependence_config(sitename) + res = RealServer().daemon_admin(sitename, project_action) + if res['status'] and project_action in ['start', 'restart']: + public.M('sites').where('name=? and project_type=?', (sitename, 'PHP')).setField('status', 1) + elif res['status'] or project_action == 'stop': + public.M('sites').where('name=? and project_type=?', (sitename, 'PHP')).setField('status', 0) + # if project_action in ['start', 'restart'] and not res['status']: + # log = os.path.join(self._phpa_logs, sitename + '.log') + # logs = public.GetNumLines(log, 5) + # res['msg'] += '\n' + logs + # res['msg'] += '\n 详情前往日志-项目日志查看' + + if project_action in ['start', 'restart']: + site_info = self.get_project_find(sitename) + for i in range(20): + time.sleep(0.1) + conn = public.ExecShell('systemctl status {}'.format(sitename)) + public.print_log(conn) + if 'deactivating' in conn[0]: + # 开启forking模式 + sys_conf = '/usr/lib/systemd/system/{}.service'.format(sitename) + sys_conf = public.readFile(sys_conf) + public.print_log(sys_conf) + if 'Type=forking' not in sys_conf: + sys_conf = sys_conf.replace('Type=simple', 'Type=forking') + public.writeFile('/usr/lib/systemd/system/{}.service'.format(sitename), sys_conf) + public.ExecShell('systemctl daemon-reload') + public.ExecShell('systemctl restart {}'.format(sitename)) + + pid = RealServer().get_daemon_pid(sitename)["data"] + if pid in [0, '0', '']: + res = public.ExecShell("""ps -aux |grep "{}"|grep -v "grep"|awk '{{print $2}}'""".format(site_info['project_config']['project_cmd']))[0] + res = res.strip().split("\n") + if res and len(res) >= 1: + res.sort() + pid = res[0] + if pid not in [0, '0', '']: + pids = psutil.Process(int(pid)).children(recursive=True) + pids = [str(i.pid) for i in pids] + pids.append(str(pid)) + if pid not in [0, '0']: + res = public.ExecShell('lsof -i |grep -E "{}" |grep LISTEN'.format('|'.join(pids))) + if res[0]: + return public.returnResult(True, '启动成功') + return public.returnResult(False, '启动失败') + return public.returnResult(True, '关闭成功') + + def async_dependence_config(self, sitename): + """ + 增加安装依赖的配置文件 + """ + try: + config = self.get_project_find(sitename) + # public.ExecShell("chown -R {}:{} {}".format(config['project_config'].run_user.strip(), config['project_config'].run_user.strip(), get.site_path)) + # public.ExecShell("chmod -R 755 {}".format(config['path'])) + php_version = config['project_config']['php_version'] + php_ini_path = os.path.join(config['path'], 'php-cli.ini') + public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}'.format(php_version, config['path'])) + public.ExecShell('chown root:root {}'.format(php_ini_path)) + public.ExecShell('chmod 644 {}'.format(php_ini_path)) + public.ExecShell("sed -i '/disable_functions/d' {}".format(php_ini_path)) + public.writeFile(php_ini_path, php_cli_ini) + + except: + pass + + # 修改项目网站运行目录 + def modify_project_path(self, get): + if not hasattr(get, 'new_run_path_sub'): + return public.returnResult(False, '新的运行目录不能为空') + new_run_path_sub = get.new_run_path_sub + sitename = get.sitename.strip() + project_info = self.get_project_find(sitename) + DirTool().modify_site_run_path(sitename, project_info['path'], new_run_path_sub) + return public.returnResult(True, '修改成功') + + def modify_project(self, get): + try: + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'project_cmd'): + return public.returnResult(False, 'project_cmd不能为空') + if not hasattr(get, 'project_path'): + return public.returnResult(False, 'project_path不能为空') + if not hasattr(get, 'site_run_path'): + return public.returnResult(False, 'site_run_path不能为空') + if not hasattr(get, 'run_user'): + get.run_user = 'www' + + public.ExecShell('chown -R {}:{} {}'.format(get.run_user, get.run_user, get.project_path)) + public.ExecShell('chmod 755 {}'.format(get.project_path)) + config = self.get_project_find(get.sitename) + config['project_config']['run_user'] = get.run_user + start_cmd = get.project_cmd + if 'php' == get.project_cmd[:3]: + get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.project_path, get.project_cmd[3:]) + else: + get.project_cmd = '/www/server/php/{}/bin/php -c {}/php-cli.ini {}'.format(get.php_version, get.project_path, get.project_cmd) + if config['project_config'].get('start_cmd') != start_cmd: + config['project_config']['start_cmd'] = start_cmd + if config['path'] != get.project_path: + if not os.path.exists(get.project_path): + return public.returnResult(False, '项目目录不存在') + config['path'] = get.project_path + config['project_config']['project_path'] = get.project_path + + # get.site_run_path = os.path.join(config['path'], get.site_run_path.lstrip("/")) + config['project_config']['site_run_path'] = get.site_run_path.rstrip('/') + get.new_run_path_sub = config['project_config']['site_run_path'] + self.modify_project_path(get) + if config['project_config']['php_version'] != get.php_version: + config['project_config']['php_version'] = get.php_version + public.ExecShell('cp /www/server/php/{}/etc/php-cli.ini {}'.format(get.php_version, config['path'])) + public.ExecShell('chown root:root {}'.format(os.path.join(config['path'], 'php-cli.ini'))) + public.ExecShell('chmod 644 {}'.format(os.path.join(config['path'], 'php-cli.ini'))) + public.ExecShell("sed -i '/disable_functions/d' {}".format(os.path.join(config['path'], 'php-cli.ini'))) + if config['project_config']['project_cmd'] != get.project_cmd or config['project_config']['is_power_on'] != get.get('is_power_on', 1) or config['project_config'][ + 'run_user'] != get.run_user: + config['project_config']['is_power_on'] = get.get('is_power_on', 1) + config['project_config']['project_cmd'] = get.project_cmd + systemd_conf = '/usr/lib/systemd/system/{}.service'.format(get.sitename) + conf = public.readFile(systemd_conf) + is_fork = 0 + if conf: + if 'Type=forking' in conf: + is_fork = 1 + realserver = RealServer() + realserver.create_daemon(get.sitename, '', get.project_cmd, get.project_path, config['project_config'].get('run_user', 'www'), get.is_power_on, + logs_file=os.path.join(self._phpa_logs, get.sitename + '.log'), is_fork=is_fork) + if config['ps'] != get.get('ps', '') and get.get('ps', ''): + public.M('sites').where('name=? and project_type=?', (get.sitename, 'PHP')).setField('ps', get.get('ps', '')) + config['project_config']['port'] = get.get('project_port', 0) + public.M('sites').where('name=? and project_type=?', (get.sitename, 'PHP')).setField('project_config', json.dumps(config['project_config'])) + return public.returnResult(True, '修改成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def get_project_log(self, get): + log_file = self._phpa_logs + '/' + get.sitename + '.log' + if not os.path.exists(log_file): + return public.returnResult(status=True, msg='暂无项目日志') + return public.returnResult(True, msg=public.GetNumLines(log_file, 1000)) + + def get_access_log(self, get): + from logsModel.siteModel import main as siteModel + get.sitename = get.siteName.strip() + sitelog = siteModel() + return public.returnResult(True, data=sitelog.get_site_access_logs(get)['msg']) + + def get_error_log(self, get): + from logsModel.siteModel import main as siteModel + get.sitename = get.siteName.strip() + sitelog = siteModel() + return public.returnResult(True, data=sitelog.get_site_error_logs(get)['msg']) + + def get_config_file(self, get): + result = {} + site_name = get.sitename + conf = self.get_project_find(site_name) + result['nginx配置文件'] = self.setupPath + '/panel/vhost/nginx/' + site_name + '.conf' + result['php-cli配置文件'] = os.path.join(conf['path'], 'php-cli.ini') + env_path = os.path.join(conf['path'], '.env') + if os.path.exists(env_path): + result['.env配置文件'] = env_path + result['伪静态配置文件'] = "/www/server/panel/vhost/rewrite/{}.conf".format(site_name) + composer_path = os.path.join(conf['path'], 'composer.json') + if os.path.exists(composer_path): + result['composer配置文件'] = composer_path + return public.returnResult(True, data=result) + + # 上传版本 + def upload_version(self, get): + """ + 上传压缩包并存储为版本 + """ + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return public.returnResult(False, '版本号不能为空') + if not hasattr(get, 'ps'): + get.ps = '' + try: + upload_files = os.path.join("/tmp", get.f_name) + from files import files + fileObj = files() + ff = fileObj.upload(get) + if type(ff) == int: + return ff + if not ff['status']: + return public.returnResult(False, ff['msg']) + output_dir = str(os.path.join('/tmp', public.GetRandomString(16))) + os.makedirs(output_dir, 777) + if not self.extract_archive(upload_files, output_dir)[0]: + return public.returnResult(False, '解压失败,仅支持zip,tar.gz,tar,bz2,gz,xz格式的压缩包') + if len(os.listdir(output_dir)) == 1: + output_dir = str(os.path.join(output_dir, os.listdir(output_dir)[0])) + versiontool = VersionTool() + res = versiontool.publish_by_src_path(get.sitename, output_dir, get.version, get.ps, sync=True) + public.ExecShell('rm -rf {}'.format(output_dir)) + public.ExecShell('rm -rf {}'.format(upload_files)) + if res is None: + return public.returnResult(True, '添加成功') + return public.returnResult(False, '添加失败' + res) + except: + return public.returnResult(False, traceback.format_exc()) + + def extract_archive(self, file_path, output_dir): + name = os.path.basename(file_path) + if name.endswith('.tar.gz'): + with tarfile.open(file_path, 'r:gz') as tar: + tar.extractall(output_dir) + elif name.endswith('.zip'): + with zipfile.ZipFile(file_path, 'r') as zip_ref: + zip_ref.extractall(output_dir) + elif name.endswith('.gz'): + with gzip.open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-3]), 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + elif name.endswith('.tar'): + with tarfile.open(file_path, 'r') as tar: + tar.extractall(output_dir) + elif name.endswith('.bz2'): + with open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-4]), 'wb') as f_out: + with bz2.BZ2File(f_in) as bz: + shutil.copyfileobj(bz, f_out) + # elif name.endswith('.xz'): + # with lzma.open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-3]), 'wb') as f_out: + # shutil.copyfileobj(f_in, f_out) + else: + return False, '文件格式错误.' + + return True, '解压成功' + + # 获取列表 + def get_version_list(self, get): + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + versiontool = VersionTool() + return public.returnResult(True, data=versiontool.version_list(get.sitename)) + + # 删除版本 + def remove_version(self, get): + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return public.returnResult(False, '版本号不能为空') + versiontool = VersionTool() + if versiontool.remove(get.sitename, get.version) is None: + return public.returnResult(True, '删除成功') + return public.returnResult(False, '删除失败') + + # 恢复版本 + def recover_version(self, get): + try: + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return publihuoc.returnResult(False, '版本号不能为空') + conf = self.get_project_find(get.sitename) + versiontool = VersionTool() + res = versiontool.recover(get.sitename, get.version, conf['path'], DirTool().get_site_run_path(get.sitename)) + if res is not True: + return public.returnResult(False, res) + return public.returnResult(True, '恢复成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def now_file_backup(self, get): + try: + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return public.returnResult(False, '版本号不能为空') + if not hasattr(get, 'ps'): + get.ps = '' + config = self.get_project_find(get.sitename) + path = config['path'] + versiontool = VersionTool() + res = versiontool.publish_by_src_path(get.sitename, path, get.version, get.ps, sync=True) + if res is None: + return public.returnResult(True, '添加成功') + return public.returnResult(False, '添加失败' + res) + except: + return public.returnResult(False, traceback.format_exc()) + + def set_version_ps(self, get): + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return public.returnResult(False, '版本号不能为空') + if not hasattr(get, 'ps'): + get.ps = '' + versiontool = VersionTool() + versiontool.set_ps(get.sitename, get.version, get.ps) + return public.returnResult(True, '设置成功') + + def get_setup_log(self, get): + log_file = "/tmp/{}_install_dependence.log".format(get.sitename) + if not os.path.exists(log_file): + return public.returnResult(True, data='暂无项目日志') + return public.returnResult(True, data=public.GetNumLines(log_file, 1000)) + + def add_crontab(self, get): + try: + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + if not hasattr(get, 'cron_name'): + return public.returnResult(False, '定时任务名称不能为空') + if not hasattr(get, 'sBody'): + return public.returnResult(False, '定时任务内容不能为空') + args = { + "name": get.cron_name, + "type": get.type, + "where1": get.where1, + "hour": get.hour, + "minute": get.minute, + "week": get.week, + "sType": "toShell", + "sName": "", + "backupTo": "localhost", + "save": get.sitename, + "sBody": get.sBody, + "urladdress": "", + "flock": get.flock + } + import crontab + res = crontab.crontab().AddCrontab(public.to_dict_obj(args)) + if res['status']: + return public.returnResult(True, msg='添加成功!') + return public.returnResult(False, res['msg']) + except: + return public.returnResult(False, traceback.format_exc()) + + def get_crontab_list(self, get): + try: + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + cront = public.M('crontab').where('save=?', (get.sitename,)).select() + + data = [] + for i in range(len(cront)): + tmp = cront[i] + + if cront[i]['type'] == "day": + tmp['type_zh'] = public.getMsg('CRONTAB_TODAY') + tmp['cycle'] = public.getMsg('CRONTAB_TODAY_CYCLE', + (str(cront[i]['where_hour']), str(cront[i]['where_minute']))) + elif cront[i]['type'] == "day-n": + tmp['type_zh'] = public.getMsg('CRONTAB_N_TODAY', (str(cront[i]['where1']),)) + tmp['cycle'] = public.getMsg('CRONTAB_N_TODAY_CYCLE', ( + str(cront[i]['where1']), str(cront[i]['where_hour']), str(cront[i]['where_minute']))) + elif cront[i]['type'] == "hour": + tmp['type_zh'] = public.getMsg('CRONTAB_HOUR') + tmp['cycle'] = public.getMsg('CRONTAB_HOUR_CYCLE', (str(cront[i]['where_minute']),)) + elif cront[i]['type'] == "hour-n": + tmp['type_zh'] = public.getMsg('CRONTAB_N_HOUR', (str(cront[i]['where1']),)) + tmp['cycle'] = public.getMsg('CRONTAB_N_HOUR_CYCLE', + (str(cront[i]['where1']), str(cront[i]['where_minute']))) + elif cront[i]['type'] == "minute-n": + tmp['type_zh'] = public.getMsg('CRONTAB_N_MINUTE', (str(cront[i]['where1']),)) + tmp['cycle'] = public.getMsg('CRONTAB_N_MINUTE_CYCLE', (str(cront[i]['where1']),)) + elif cront[i]['type'] == "week": + tmp['type_zh'] = public.getMsg('CRONTAB_WEEK') + if not cront[i]['where1']: cront[i]['where1'] = '0' + tmp['cycle'] = public.getMsg('CRONTAB_WEEK_CYCLE', ( + self.toWeek(int(cront[i]['where1'])), str(cront[i]['where_hour']), + str(cront[i]['where_minute']))) + elif cront[i]['type'] == "month": + tmp['type_zh'] = public.getMsg('CRONTAB_MONTH') + tmp['cycle'] = public.getMsg('CRONTAB_MONTH_CYCLE', ( + str(cront[i]['where1']), str(cront[i]['where_hour']), str(cront[i]['where_minute']))) + + log_file = '/www/server/cron/{}.log'.format(tmp['echo']) + if os.path.exists(log_file): + tmp['addtime'] = self.get_last_exec_time(log_file) + data.append(tmp) + + for i in data: + if i['backup_mode'] == "1": + i['backup_mode'] = 1 + else: + i['backup_mode'] = 0 + if i['db_backup_path'] == "": + i['db_backup_path'] = "/www/backup" + + if not i.get('rname', ''): + i['rname'] = i['name'] + if i['time_type'] == 'sweek': + i['type'] = 'sweek' + week_str = self.toweek(i['time_set']) + if week_str: # 检查week_str是否为空 + i['type_zh'] = week_str + i['cycle'] = "每" + week_str + i['special_time'] + "执行" + + elif i['time_type'] == 'sday': + i['type'] = 'sweek' + i['type_zh'] = i['special_time'] + i['cycle'] = "每天" + i['special_time'] + "执行" + + elif i['time_type'] == 'smonth': + i['type'] = 'sweek' + i['type_zh'] = i['special_time'] + i['cycle'] = "每月" + i['time_set'] + "号" + i['special_time'] + "执行" + if i['sType'] == 'site_restart': + i['cycle'] = "每天" + i['special_time'] + "执行" + return public.returnResult(True, data=data) + except: + return public.returnResult(False, traceback.format_exc()) + + # 转换大写星期 + def toWeek(self, num): + wheres = { + 0: public.getMsg('CRONTAB_SUNDAY'), + 1: public.getMsg('CRONTAB_MONDAY'), + 2: public.getMsg('CRONTAB_TUESDAY'), + 3: public.getMsg('CRONTAB_WEDNESDAY'), + 4: public.getMsg('CRONTAB_THURSDAY'), + 5: public.getMsg('CRONTAB_FRIDAY'), + 6: public.getMsg('CRONTAB_SATURDAY') + } + try: + return wheres[num] + except: + return '' + + def get_last_exec_time(self, log_file): + ''' + @name 获取上次执行时间 + @author hwliang + @param log_file 日志文件路径 + @return format_date + ''' + exec_date = '' + try: + log_body = public.GetNumLines(log_file, 20) + if log_body: + log_arr = log_body.split('\n') + date_list = [] + for i in log_arr: + if i.find('★') != -1 and i.find('[') != -1 and i.find(']') != -1: + date_list.append(i) + if date_list: + exec_date = date_list[-1].split(']')[0].split('[')[1] + except: + pass + + finally: + if not exec_date: + exec_date = public.format_date(times=int(os.path.getmtime(log_file))) + return exec_date + + def start_task(self, get): + try: + if not hasattr(get, 'id'): + return public.returnResult(False, 'ID不能为空') + import crontab + res = crontab.crontab().StartTask(get) + if res['status']: + return public.returnResult(True, '启动成功') + return public.returnResult(False, res['msg']) + except: + return public.returnResult(False, traceback.format_exc()) + + def modify_crontab_status(self, get): + try: + if not hasattr(get, 'id'): + return public.returnResult(False, 'ID不能为空') + if hasattr(get, 'status'): + cronInfo = public.M('crontab').where('id=?', (get.id,)).select()[0] + if int(cronInfo['status']) == int(get.status): + return public.returnResult(True, '修改成功') + get.if_stop = False + import crontab + res = crontab.crontab().set_cron_status(get) + if res['status']: + return public.returnResult(True, '修改成功') + return public.returnResult(False, res['msg']) + except: + return public.returnResult(False, traceback.format_exc()) + + def remove_crontab(self, get): + try: + if not hasattr(get, 'id'): + return public.returnResult(False, 'ID不能为空') + import crontab + res = crontab.crontab().DelCrontab(get) + if res['status']: + return public.returnResult(True, '删除成功') + return public.returnResult(False, res['msg']) + except: + return public.returnResult(False, traceback.format_exc()) + + def modify_crontab(self, get): + try: + if not hasattr(get, 'id'): + return public.returnResult(False, 'ID不能为空') + self.remove_crontab(get) + res = self.add_crontab(get) + res['msg'] = res['msg'].replace('添加', '修改') + return res + except: + return public.returnResult(False, traceback.format_exc()) + + def get_crontab_log(self, get): + try: + if not hasattr(get, 'id'): + return public.returnResult(False, 'ID不能为空') + import crontab + res = crontab.crontab().GetLogs(get) + return public.returnResult(True, res['msg']) + except: + return public.returnResult(False, traceback.format_exc()) + + def clearn_logs(self, get): + try: + if not hasattr(get, 'id'): + return public.returnResult(False, '项目名称不能为空') + import crontab + crontab.crontab().DelLogs(get) + return public.returnResult(True, '清空成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def get_group_file(self): + config = json.loads(public.readFile(self.group_file)) + return config + + def get_group_list(self, get): + try: + config = self.get_group_file() + result = [] + for i, j in config.items(): + tmp = [] + for k in j['project_list']: + con = self.get_project_run_state(public.to_dict_obj({'sitename': k}))['data'] + if con: + tmp.append(con) + else: + self.group_remove_project(public.to_dict_obj({'group_name': i, 'sitename': k})) + j['project_list'] = tmp + j['name'] = i + result.append(j) + + for i in result: + for j in i['project_list']: + if not j: + print(j) + continue + if not j['status']: + i['status'] = 0 + break + if len(i['project_list']) == 0: + i['status'] = 0 + return public.returnResult(True, data=result) + except: + return public.returnResult(False, traceback.format_exc()) + + def set_order(self, get): + project_list = get.project_list.strip(',').split(',') + group_name = get.group_name + config = self.get_group_file() + if group_name not in config: + return public.returnResult(False, '组名不存在') + config[group_name]['project_list'] = project_list + public.writeFile(self.group_file, json.dumps(config)) + return public.returnResult(True, '设置成功') + + def create_group(self, get): + try: + if not hasattr(get, 'group_name'): + return public.returnResult(False, '组名不能为空') + config = self.get_group_file() + if get.group_name in config: + return public.returnResult(False, '组名已存在') + config[get.group_name] = {'interval': get.get('interval', 30), 'project_list': [], 'status': 0} + public.writeFile(self.group_file, json.dumps(config)) + return public.returnResult(True, '添加成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def remove_group(self, get): + if not hasattr(get, 'group_name'): + return public.returnResult(False, '组名不能为空') + config = self.get_group_file() + if get.group_name not in config: + return public.returnResult(False, '组名不存在') + del config[get.group_name] + public.writeFile(self.group_file, json.dumps(config)) + return public.returnResult(True, '删除成功') + + def group_add_project(self, get): + try: + if not hasattr(get, 'group_name'): + return public.returnResult(False, '组名不能为空') + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名不能为空') + config = self.get_group_file() + if get.group_name not in config: + return public.returnResult(False, '组名不存在') + if get.sitename not in config[get.group_name]['project_list']: + config[get.group_name]['project_list'].append(get.sitename) + flag = 1 + for i in config[get.group_name]['project_list']: + if not self.get_project_run_state(public.to_dict_obj({'sitename': i}))['status']: + flag = 0 + break + if flag: + config[get.group_name]['status'] = 1 + public.writeFile(self.group_file, json.dumps(config)) + return public.returnResult(True, '添加成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def group_remove_project(self, get): + try: + if not hasattr(get, 'group_name'): + return public.returnResult(False, '组名不能为空') + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名不能为空') + config = self.get_group_file() + if get.group_name not in config: + return public.returnResult(False, '组名不存在') + if get.sitename not in config[get.group_name]['project_list']: + return public.returnResult(False, '项目不存在') + config[get.group_name]['project_list'].remove(get.sitename) + flag = 1 + for i in config[get.group_name]['project_list']: + if not self.get_project_run_state(public.to_dict_obj({'sitename': i}))['status']: + flag = 0 + break + if flag: + config[get.group_name]['status'] = 1 + else: + config[get.group_name]['status'] = 0 + public.writeFile(self.group_file, json.dumps(config)) + return public.returnResult(True, '删除成功') + except: + return public.returnResult(False, traceback.format_exc()) + + def set_group_interval(self, get): + if not hasattr(get, 'group_name'): + return public.returnResult(False, '组名不能为空') + if not hasattr(get, 'interval'): + return public.returnResult(False, '间隔时间不能为空') + config = self.get_group_file() + if get.group_name not in config: + return public.returnResult(False, '组名不存在') + config[get.group_name]['interval'] = get.interval + public.writeFile(self.group_file, json.dumps(config)) + return public.returnResult(True, '设置成功') + + def set_group_status(self, get): + if not hasattr(get, 'group_name'): + return public.returnResult(False, '组名不能为空') + if not hasattr(get, 'status'): + return public.returnResult(False, '状态不能为空') + config = self.get_group_file() + if get.group_name not in config: + return public.returnResult(False, '组名不存在') + if get.status == 'stop': + public.run_thread(self.stop_groups, (get.group_name,)) + return public.returnResult(True, '开始停止项目') + if get.status == 'start': + public.run_thread(self.start_groups, (get.group_name,)) + return public.returnResult(True, '开始启动项目') + return public.returnResult(True, '设置成功') + + def stop_groups(self, group): + config = self.get_group_file() + if group not in config: + return public.returnResult(False, '组名不存在') + config[group]['status'] = 0 + for i in config[group]['project_list']: + self.modify_project_run_state(public.to_dict_obj({'sitename': i, 'project_action': 'stop'})) + public.writeFile(self.group_file, json.dumps(config)) + + def start_groups(self, group): + config = self.get_group_file() + self.stop_groups(group) + if group not in config: + return public.returnResult(False, '组名不存在') + for i in config[group]['project_list']: + self.modify_project_run_state(public.to_dict_obj({'sitename': i, 'project_action': 'start'})) + if config[group]['project_list'].index(i) == len(config[group]['project_list']) - 1: + continue + time.sleep(int(config[group]['interval'])) + flag = 1 + for i in config[group]['project_list']: + if not self.get_project_run_state(public.to_dict_obj({'sitename': i}))['status']: + flag = 0 + break + if flag: + config[group]['status'] = 1 + else: + config[group]['status'] = 0 + public.writeFile(self.group_file, json.dumps(config)) + + # 取代理配置文件 + def get_proxy_file(self, get): + try: + import files + conf = [] if not os.path.exists(self.__proxyfile) else json.loads(public.readFile(self.__proxyfile)) + get.webserver = public.GetWebServer() + sitename = get.sitename + proxyname = get.proxyname + proxyname_md5 = self.__calc_md5(proxyname) + get.path = "%s/panel/vhost/%s/proxy/%s/%s_%s.conf" % (self.setupPath, get.webserver, sitename, proxyname_md5, sitename) + for i in conf: + if proxyname == i["proxyname"] and sitename == i["sitename"] and i["type"] != 1: + return public.returnResult(False, '代理已暂停') + f = files.files() + return public.returnResult(True, data=(f.GetFileBody(get), get.path)) + except: + return public.returnResult(False, traceback.format_exc()) + + # 计算proxyname md5 + def __calc_md5(self, proxyname): + md5 = hashlib.md5() + md5.update(proxyname.encode('utf-8')) + return md5.hexdigest() + + # 保存重定向配置文件 + def save_proxy_file(self, get): + import files + f = files.files() + get.data = get.config + return public.returnResult(True, f.SaveFileBody(get)) + + def set_cron_status_all(self, get): + import crontab + return crontab.crontab().set_cron_status_all(get) + + def get_index_conf(self, get): + if not hasattr(get, 'id'): + return public.returnResult(False, msg='请输入id') + sitename = public.M('sites').where('id=?', (get.id,)).getField('name') + if not sitename: + return public.returnResult(False, msg='项目不存在') + res = DirTool().get_index_conf(sitename) + if str(res) == str: + return public.returnResult(False, msg='获取失败' + res) + else: + return public.returnResult(True, data=res) + + def set_index_conf(self, get): + if not hasattr(get, 'id'): + return public.returnResult(False, msg='请输入id') + if not hasattr(get, 'Index'): + return public.returnResult(False, msg='配置不能为空') + try: + sitename = public.M('sites').where('id=?', (get.id,)).getField('name') + index = get.Index + if type(get.Index) == str: + index = get.Index.split(',') + DirTool().set_index_conf(sitename, file_list=index) + return public.returnResult(True, msg='设置成功') + except: + return public.returnResult(False, msg='设置失败') + + def get_referer_security(self, get): + if not hasattr(get, 'id'): + return public.returnResult(False, msg='请输入id') + sitename = public.M('sites').where('id=?', (get.id,)).getField('name') + if not sitename: + return public.returnResult(False, msg='项目不存在') + get.site_name = sitename + return Referer('').get_referer_security(get) + + def set_referer_security(self, get): + return Referer('').set_referer_security(get) + + def get_system_user_list(self, get=None): + """ + 默认只返回uid>= 1000 的用户 和 root + get中包含 sys_user 返回 uid>= 100 的用户 和 root + get中包含 all_user 返回所有的用户 + """ + sys_user = False + all_user = False + if get is not None: + if hasattr(get, "sys_user"): + sys_user = True + if hasattr(get, "all_user"): + all_user = True + + user_set = set() + with open('/etc/passwd') as fp: + for line in fp.readlines(): + tmp = line.split(':') + user_name = tmp[0] + uid = int(tmp[2]) + if uid == 0: + user_set.add(user_name) + continue + if uid >= 1000: + user_set.add(user_name) + continue + if uid >= 100 and sys_user: + user_set.add(user_name) + continue + if all_user: + user_set.add(user_name) + continue + + return list(user_set) + + +if __name__ == "__main__": + m = main() + m.install_dependence("555.com", "82", "/xiaopacai/laravel11.5.0/", composer_version="2.7.3") diff --git a/mod/project/php/serviceconfMod.py b/mod/project/php/serviceconfMod.py new file mode 100644 index 00000000..88514712 --- /dev/null +++ b/mod/project/php/serviceconfMod.py @@ -0,0 +1,17 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: baozi +# ------------------------------------------------------------------- +# 服务配置模块 +# ------------------------------ + +from mod.base.web_conf import IpRestrict + + +class main(IpRestrict): # 继承并使用同ip黑白名单限制 + def __init__(self): + super().__init__(config_prefix="") diff --git a/mod/project/proxy/__init__.py b/mod/project/proxy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/project/proxy/comMod.py b/mod/project/proxy/comMod.py new file mode 100644 index 00000000..2ad47842 --- /dev/null +++ b/mod/project/proxy/comMod.py @@ -0,0 +1,3884 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import json +import os +# ------------------------------ +# 反向代理模型 +# ------------------------------ +import sys + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +os.chdir("/www/server/panel") +import public +from public.validate import Param + + +class main(): + + def __init__(self): + self._proxy_path = '/www/server/proxy_project' + self._proxy_config_path = self._proxy_path + '/sites' + self._site_proxy_conf_path = "" + if not os.path.exists(self._proxy_config_path): + public.ExecShell('mkdir -p {}'.format(self._proxy_config_path)) + public.ExecShell('chown -R www:www {}'.format(self._proxy_config_path)) + public.ExecShell('chmod -R 755 {}'.format(self._proxy_config_path)) + + self._init_proxy_conf = { + "site_name": "", + "domain_list": [], + "site_port": [], + "https_port": "443", + "ipv4_port_conf": "listen {listen_port};", + "ipv6_port_conf": "listen [::]:{listen_port};", + "port_conf": "listen {listen_port};{listen_ipv6}", + "ipv4_ssl_port_conf": "{ipv4_port_conf}\n listen {https_port} ssl http2 ;", + "ipv6_ssl_port_conf": "{ipv6_port_conf}\n listen [::]:{https_port} ssl http2 ;", + "ipv4_http3_ssl_port_conf": "{ipv4_port_conf}\n listen {https_port} quic;\n listen {https_port} ssl;", + "ipv6_http3_ssl_port_conf": "{ipv6_port_conf}\n listen [::]:{https_port} quic;\n listen [::]:{https_port} ssl ;", + "site_path": "", + "ssl_info": { + "ssl_status": False, + "ssl_default_conf": "#error_page 404/404.html;", + "ssl_conf": '#error_page 404/404.html;\n ssl_certificate /www/server/panel/vhost/cert/{site_name}/fullchain.pem;\n ssl_certificate_key /www/server/panel/vhost/cert/{site_name}/privkey.pem;\n ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;\n ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;\n ssl_prefer_server_ciphers on;\n ssl_session_cache shared:SSL:10m;\n ssl_session_timeout 10m;\n add_header Strict-Transport-Security "max-age=31536000";\n error_page 497 https://$host$request_uri;', + "force_ssl_conf": '#error_page 404/404.html;{force_conf}\n ssl_certificate /www/server/panel/vhost/cert/{site_name}/fullchain.pem;\n ssl_certificate_key /www/server/panel/vhost/cert/{site_name}/privkey.pem;\n ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;\n ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;\n ssl_prefer_server_ciphers on;\n ssl_session_cache shared:SSL:10m;\n ssl_session_timeout 10m;\n add_header Strict-Transport-Security "max-age=31536000";\n error_page 497 https://$host$request_uri;', + "force_https": False, + "force_conf": " #HTTP_TO_HTTPS_START\n if ($server_port !~ 443){\n rewrite ^(/.*)$ https://$host$1 permanent;\n }\n #HTTP_TO_HTTPS_END", + }, + "err_age_404": "", + "err_age_502": "", + "ip_limit": { + "ip_black": [], + "ip_white": [], + }, + "basic_auth": [], + "proxy_cache": { + "cache_status": False, + "cache_zone": "", + "static_cache": "", + "expires": "1d", + "cache_conf": "", + }, + "gzip": { + "gzip_status": False, + "gzip_min_length": "1k", + "gzip_comp_level": "6", + "gzip_types": "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js", + "gzip_conf": "gzip on;\n gzip_min_length 10k;\n gzip_buffers 4 16k;\n gzip_http_version 1.1;\n gzip_comp_level 2;\n gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;\n gzip_vary on;\n gzip_proxied expired no-cache no-store private auth;\n gzip_disable \"MSIE [1-6]\\.\";", + }, + "subs_filter": False, + "sub_filter": { + "sub_filter_str": [], + }, + "websocket": { + "websocket_status": True, + "websocket_conf": "proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";", + }, + "security": { + "security_status": False, + "static_resource": "jpg|jpeg|gif|png|js|css", + "return_resource": "404", + "http_status": False, + "domains": "", + "security_conf": " #SECURITY-START Anti theft chain configuration" + "\n location ~ .*\\.({static_resource})$" + "\n {{\n expires {expires};" + "\n access_log /dev/null;" + "\n valid_referers {domains};" + "\n if ($invalid_referer){{" + "\n return {return_resource};" + "\n }}" + "\n }}\n #SECURITY-END", + }, + "redirect": { + "redirect_status": False, + "redirect_conf": " #Referencing redirection rules, the redirection proxy configured after annotation will be invalid\n include /www/server/panel/vhost/nginx/redirect/{site_name}/*.conf;", + }, + "proxy_log": { + "log_type": "default", + "server_port": "", + "log_path": "", + "log_conf": "\naccess_log {log_path}/{site_name}.log;\n error_log {log_path}/{site_name}.error.log;", + }, + "default_cache": "proxy_cache_path /www/wwwroot/{site_name}/proxy_cache_dir levels=1:2 keys_zone={cache_name}_cache:20m inactive=1d max_size=5g;", + "default_describe": "# If there is abnormal access to the reverse proxy website and the content has already been configured here, please prioritize checking if the configuration here is correct\n", + "http_block": "", + "server_block": "", + "remark": "", + "proxy_info": [], + } + self._template_conf = r'''{http_block} +server {{ + {port_conf} + server_name {domains}; + index index.php index.html index.htm default.php default.htm default.html; + root {site_path}; + + #CERT-APPLY-CHECK--START + # Configuration related to file verification for SSL certificate application - Do not delete + include /www/server/panel/vhost/nginx/well-known/{site_name}.conf; + #CERT-APPLY-CHECK--END + + #SSL-START {ssl_start_msg} + {ssl_info} + #SSL-END + #REDIRECT START + {redirect_conf} + #REDIRECT END + + #ERROR-PAGE-START {err_page_msg} + {err_age_404} + {err_age_502} + #ERROR-PAGE-END + + #PHP-INFO-START PHP reference configuration, can be annotated or modified + {security_conf} + include enable-php-00.conf; + #PHP-INFO-END + + #IP-RESTRICT-START Restrict access to IP configuration, IP blacklist and whitelist + {ip_limit_conf} + #IP-RESTRICT-END + + #BASICAUTH START + {auth_conf} + #BASICAUTH END + + #SUB_FILTER START + {sub_filter} + #SUB_FILTER END + + #GZIP START + {gzip_conf} + #GZIP END + + #GLOBAL-CACHE START + {proxy_cache} + #GLOBAL-CACHE END + + #WEBSOCKET-SUPPORT START + {websocket_support} + #WEBSOCKET-SUPPORT END + + #PROXY-CONF-START + {proxy_conf} + #PROXY-CONF-END + + #SERVER-BLOCK START + {server_block} + #SERVER-BLOCK END + + #Prohibited access to files or directories + location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) + {{ + return 404; + }} + + #One click application for SSL certificate verification directory related settings + location /.well-known{{ + allow all; + root /www/wwwroot/{site_name}; + }} + + #Prohibit placing sensitive files in the certificate verification directory + if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {{ + return 403; + }} + + #LOG START + {server_log} + {monitor_conf} + #LOG END +}}''' + self._template_proxy_conf = '''location ^~ {proxy_path} {{ + {ip_limit} + {basic_auth} + proxy_pass {proxy_pass}; + proxy_set_header Host {proxy_host}; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Real-Port $remote_port; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + {timeout_conf} + {websocket_support} + {custom_conf} + {proxy_cache} + {gzip} + {sub_filter} + {server_log} + }}''' + + def structure_proxy_conf(self, get): + ''' + @name + @author wzz <2024/4/19 下午4:29> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.proxy_conf = self._template_proxy_conf.format( + ip_limit="", + gzip="", + proxy_cache="", + sub_filter="", + server_log="", + basic_auth="", + proxy_pass=get.proxy_pass, + proxy_host=get.proxy_host, + proxy_path=get.proxy_path, + custom_conf="", + timeout_conf=get.proxy_timeout, + websocket_support=self._init_proxy_conf["websocket"]["websocket_conf"], + ) + + get.proxy_info = { + "proxy_type": get.proxy_type, + "proxy_path": get.proxy_path, + "proxy_pass": get.proxy_pass, + "proxy_host": get.proxy_host, + "ip_limit": { + "ip_black": [], + "ip_white": [], + }, + "basic_auth": {}, + "proxy_cache": { + "cache_status": False, + "cache_zone": get.site_name.replace(".", "_") + "_cache", + "static_cache": "", + "expires": "1d", + "cache_conf": "", + }, + "gzip": { + "gzip_status": False, + "gzip_min_length": "1k", + "gzip_comp_level": "6", + "gzip_types": "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js", + "gzip_conf": "gzip on;\n gzip_min_length 10k;\n gzip_buffers 4 16k;\n gzip_http_version 1.1;\n gzip_comp_level 2;\n gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;\n gzip_vary on;\n gzip_proxied expired no-cache no-store private auth;\n gzip_disable \"MSIE [1-6]\\.\";", + }, + "sub_filter": { + "sub_filter_str": [], + }, + "websocket": { + "websocket_status": True, + "websocket_conf": "proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";", + }, + "proxy_log": { + "log_type": "off", + "log_conf": get.server_log, + }, + "timeout": { + "proxy_connect_timeout": "60", + "proxy_send_timeout": "600", + "proxy_read_timeout": "600", + "timeout_conf": "proxy_connect_timeout 60s;\n proxy_send_timeout 600s;\n proxy_read_timeout 600s;", + }, + "custom_conf": "", + "proxy_conf": get.proxy_conf, + "remark": "", + "template_proxy_conf": self._template_proxy_conf, + } + + # 2024/4/18 上午10:53 构造反向代理的配置文件 + def structure_nginx(self, get): + ''' + @name 构造反向代理的配置文件 + @author wzz <2024/4/18 上午10:54> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.err_age_404 = get.get("err_age_404", "#error_page 404 /404.html;") + get.err_age_502 = get.get("err_age_502", "#error_page 502 /502.html;") + get.proxy_info = get.get("proxy_info", "") + + get.server_log = get.get( + "server_log", + self._init_proxy_conf["proxy_log"]["log_conf"].format( + log_path=public.get_logs_path(), + site_name=get.site_name + ) + ) + get.remark = get.get("remark", "") + get.server_block = get.get("server_block", "") + get.websocket_status = get.get("websocket_status", True) + get.proxy_timeout = "proxy_connect_timeout 60s;\n proxy_send_timeout 600s;\n proxy_read_timeout 600s;" + self.structure_proxy_conf(get) + is_subs = public.ExecShell("nginx -V 2>&1|grep 'ngx_http_substitutions_filter' -o")[0] + + self._init_proxy_conf["subs_filter"] = True if is_subs != "" else False + self._init_proxy_conf["site_name"] = get.site_name + self._init_proxy_conf["domain_list"] = get.domain_list + self._init_proxy_conf["site_port"] = get.port_list + self._init_proxy_conf["site_path"] = get.site_path + self._init_proxy_conf["err_age_404"] = get.err_age_404 + self._init_proxy_conf["err_age_502"] = get.err_age_502 + self._init_proxy_conf["proxy_log"]["log_conf"] = get.server_log + self._init_proxy_conf["remark"] = get.remark + self._init_proxy_conf["http_block"] = "" + self._init_proxy_conf["proxy_info"].append(get.proxy_info) + self._init_proxy_conf["proxy_cache"]["cache_zone"] = get.site_name.replace(".", "_") + "_cache" + + # 2024/4/18 上午10:35 写入Nginx配置文件 + def write_nginx_conf(self, get): + ''' + @name 写入Nginx配置文件 + @author wzz <2024/4/18 上午10:36> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 这里可能会报错 + self.structure_nginx(get) + listen_port = " ".join(get.port_list) if len(get.port_list) > 1 else get.site_port + # listen_ipv6 = "\n listen [::]:{};".format( + # " ".join(get.port_list) if len(get.port_list) > 1 else get.site_port) + # port_conf = self._init_proxy_conf["port_conf"].format(listen_port=listen_port, listen_ipv6=listen_ipv6) + + if type(listen_port) == list: + ipv4_port_conf = "" + ipv6_port_conf = "" + for p in listen_port: + ipv4_port_conf += self._init_proxy_conf["ipv4_port_conf"].format(listen_port=p) + "\n " + ipv6_port_conf += self._init_proxy_conf["ipv6_port_conf"].format(listen_port=p) + "\n " + else: + ipv4_port_conf = self._init_proxy_conf["ipv4_port_conf"].format(listen_port=listen_port) + ipv6_port_conf = self._init_proxy_conf["ipv6_port_conf"].format(listen_port=listen_port) + port_conf = ipv4_port_conf + "\n" + ipv6_port_conf + + # 2024/6/4 下午4:20 兼容新版监控报表的配置 + monitor_conf = "" + if os.path.exists("/www/server/panel/plugin/monitor/monitor_main.py"): + monitor_conf = '''#Monitor-Config-Start monitor log sending configuration + access_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag={pid}__access monitor; + error_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag={pid}__error; + #Monitor-Config-End'''.format(pid=get.pid) + + conf = self._template_conf.format( + http_block=get.http_block, + server_block="", + port_conf=port_conf, + ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), + err_page_msg=public.getMsg('NGINX_CONF_MSG2'), + php_info_start=public.getMsg('NGINX_CONF_MSG3'), + rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), + log_path=public.get_logs_path(), + domains=' '.join(get.domain_list) if len(get.domain_list) > 1 else get.site_name, + site_name=get.site_name, + ssl_info="#error_page 404/404.html;", + err_age_404=get.err_age_404, + err_age_502=get.err_age_502, + ip_limit_conf="", + auth_conf="", + sub_filter="", + gzip_conf="", + redirect_conf="", + security_conf="", + proxy_conf=get.proxy_conf, + server_log=get.server_log, + site_path=get.site_path, + proxy_cache="", + websocket_support=self._init_proxy_conf["websocket"]["websocket_conf"], + monitor_conf=monitor_conf, + ) + + # 写配置文件 + well_known_path = "{}/vhost/nginx/well-known".format(public.get_panel_path()) + if not os.path.exists(well_known_path): + os.makedirs(well_known_path, 0o600) + public.writeFile("{}/{}.conf".format(well_known_path, get.site_name), "") + + get.filename = public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf' + + return public.writeFile(get.filename, conf) + + # 2024/4/25 上午11:11 检查nginx是否支持http3 + def check_http3_support(self): + ''' + @name 检查nginx是否支持http3 + @author wzz <2024/4/25 上午11:13> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return public.ExecShell("nginx -V 2>&1| grep 'http_v3_module' -o")[0] + + # 2024/4/18 上午9:26 创建反向代理 + def create(self, get): + ''' + @name 创建反向代理 + @author wzz <2024/4/18 上午9:27> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('remark').String(), + Param('proxy_pass').String(), + Param('domains').String(), + Param('proxy_host').String(), + Param('proxy_type').Bool(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + # 2024/4/18 上午9:41 前置处理开始 + from mod.base.web_conf import util + webserver = util.webserver() + if webserver != "nginx" or webserver is None: + return public.return_message(0,0,public.returnResult(status=False, msg="Only Nginx is supported, please install Nginx before use!")) + from panelSite import panelSite + site_obj = panelSite() + site_obj.check_default() + + wc_err = public.checkWebConfig() + if not wc_err: + return public.returnResult( + status=False, + msg='ERROR: Detected an error in the configuration file, please troubleshoot before proceeding

                                    ' + + wc_err.replace("\n", '
                                    ') + '
                                    ' + ) + + # 2024/4/18 上午9:45 参数处理 + get.domains = get.get("domains", "") + get.proxy_path = get.get("proxy_path", "/") + get.proxy_pass = get.get("proxy_pass", "") + get.proxy_host = get.get("proxy_host", "$http_host") + get.remark = get.get("remark", "") + get.proxy_type = get.get("proxy_type", "http") + + # 2024/4/18 上午9:45 参数校验 + if not get.domains: + return public.return_message(-1, 0,"The domain name cannot be empty, please enter at least one domain name!") + if not get.proxy_pass: + return public.return_message(-1, 0,"The proxy target cannot be empty!") + if get.remark != "": + get.remark = public.xssencode2(get.remark) + if get.proxy_type == "unix": + if not get.proxy_pass.startswith("/"): + return public.return_message(-1, 0,"The Unix file path must start with/, such as/tmp/flash.app.sock!") + if not get.proxy_pass.endswith(".sock"): + return public.return_message(-1, 0,"Unix files must end with. lock, such as/tmp/flash.app. lock!") + if not os.path.exists(get.proxy_pass): + return public.return_message(-1, 0,"The proxy target does not exist!") + get.proxy_pass = "http://unix:{}".format(get.proxy_pass) + elif get.proxy_type == "http": + if not get.proxy_pass.startswith("http://") and not get.proxy_pass.startswith("https://"): + return public.return_message(-1, 0,"The proxy target must start with http://or https://!") + + # 2024/4/18 上午9:45 创建反向代理 + get.domain_list = get.domains.split("\n") + get.site_name = util.to_puny_code(get.domain_list[0].strip().split(":")[0]).strip().lower() + get.site_path = "/www/wwwroot/" + get.site_name + get.site_port = get.domain_list[0].strip().split(":")[1] if ":" in get.domain_list[0] else "80" + get.port_list = [get.site_port] + if not public.checkPort(get.site_port): + return public.return_message(-1, 0,'Port [{}] is illegal!'.format(get.site_port)) + + if len(get.domain_list) > 1: + for domain in get.domain_list[1:]: + if not ":" in domain.strip(): + continue + + d_port = domain.strip().split(":")[1] + if not public.checkPort(d_port): + return public.return_message(-1, 0,'Port [{}] is illegal!'.format(d_port)) + + if not d_port in get.port_list: + get.port_list.append(d_port) + + # 2024/4/18 上午10:06 检查域名是否存在 + main_domain = get.site_name + opid = public.M('domain').where("name=? and port=?", (main_domain, int(get.site_port))).getField('pid') + if opid: + if public.M('sites').where('id=?', (opid,)).count(): + return public.return_message(-1, 0,'The website [{}] already exists, please do not add it again!'.format(main_domain)) + public.M('domain').where('pid=?', (opid,)).delete() + + if public.M('binding').where('domain=?', (main_domain,)).count(): + return public.return_message(-1, 0,'The website [{}] already exists, please do not add it again!'.format(main_domain)) + + # 2024/4/18 上午10:06 检查网站是否存在 + sql = public.M('sites') + if sql.where("name=?", (get.site_name,)).count(): + if public.is_ipv4(get.site_name): + get.site_name = get.site_name + "_" + str(get.site_port) + else: + return public.return_message(-1, 0,'The website [{}] already exists, please do not add it again!'.format(main_domain)) + + # 2024/4/18 上午10:21 添加端口到系统防火墙 + from firewallModel.comModel import main as comModel + firewall_com = comModel() + get.port = get.site_port + firewall_com.set_port_rule(get) + + # 2024/4/18 上午9:41 前置处理结束 + + # 2024/4/18 上午10:46 写入网站配置文件 + get.http_block = "proxy_cache_path /www/wwwroot/{site_name}/proxy_cache_dir levels=1:2 keys_zone={cache_name}_cache:20m inactive=1d max_size=5g;".format( + site_name=get.site_name, + cache_name=get.site_name.replace(".", "_") + ) + self._site_path = self._proxy_config_path + '/' + get.site_name + if not os.path.exists(self._site_path): + public.ExecShell('mkdir -p {}'.format(self._site_path)) + public.ExecShell('chown -R www:www {}'.format(self._site_path)) + public.ExecShell('chmod -R 755 {}'.format(self._site_path)) + + if not os.path.exists(get.site_path): + public.ExecShell('mkdir -p {}'.format(get.site_path)) + public.ExecShell('chown -R www:www {}'.format(get.site_path)) + public.ExecShell('chmod -R 755 {}'.format(get.site_path)) + + if not os.path.exists(get.site_path + "/proxy_cache_dir"): + public.ExecShell('mkdir -p {}'.format(get.site_path + "/proxy_cache_dir")) + public.ExecShell('chown -R www:www {}'.format(get.site_path + "/proxy_cache_dir")) + public.ExecShell('chmod -R 755 {}'.format(get.site_path + "/proxy_cache_dir")) + + self._site_proxy_conf_path = '{}/{}.json'.format(self._site_path, get.site_name) + + # 2024/4/18 上午10:22 写入数据库 + pdata = { + 'name': get.site_name, + 'path': "/www/wwwroot/" + get.site_name, + 'ps': get.remark, + 'status': 1, + 'type_id': 0, + 'project_type': 'proxy', + 'project_config': json.dumps(self._init_proxy_conf), + 'addtime': public.getDate() + } + + get.pid = public.M('sites').insert(pdata) + public.M('domain').add('pid,name,port,addtime', (get.pid, main_domain, get.site_port, public.getDate())) + for domain in get.domain_list: + get.domain = domain + get.webname = get.site_name + get.id = str(get.pid) + from panelSite import panelSite + panelSite().AddDomain(get) + + # 2024/6/4 下午4:30 写nginx配置文件 + self.write_nginx_conf(get) + public.writeFile(self._site_proxy_conf_path, json.dumps(self._init_proxy_conf)) + wc_err = public.checkWebConfig() + if not wc_err: + public.ExecShell("rm -f {}".format(self._site_proxy_conf_path)) + public.ExecShell("rm -rf {}".format(get.filename)) + public.M('sites').where('id=?', (get.pid,)).delete() + public.M('domain').where('pid=?', (get.pid,)).delete() + return public.return_message(-1, 0,'ERROR: Detected an error in the configuration file, please troubleshoot before proceeding

                                    ' + + wc_err.replace("\n", '
                                    ') + '
                                    ' + ) + if type(wc_err) != bool and "test failed" in wc_err: + public.ExecShell("rm -f {}".format(self._site_proxy_conf_path)) + public.ExecShell("rm -rf {}".format(get.filename)) + public.M('sites').where('id=?', (get.pid,)).delete() + public.M('domain').where('pid=?', (get.pid,)).delete() + return public.return_message(-1, 0,'ERROR: Detected an error in the configuration file, please troubleshoot before proceeding

                                    ' + + wc_err.replace("\n", '
                                    ') + '
                                    ' + ) + + public.WriteLog('TYPE_SITE', 'SITE_ADD_SUCCESS', (get.site_name,)) + public.set_module_logs('site_proxy', 'create', 1) + public.serviceReload() + return public.return_message(0, 0,"Reverse proxy project added successfully!") + + def read_json_conf(self, get): + ''' + @name + @author wzz <2024/4/18 下午9:53> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + try: + proxy_json_conf = json.loads(public.readFile(conf_path)) + conf_file = public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf' + conf_string =public.readFile(conf_file) + if 'ssl_certificate_key' in conf_string: + proxy_json_conf['ssl_info']['ssl_status']=True + else: + proxy_json_conf['ssl_info']['ssl_status']=False + except Exception as e: + proxy_json_conf = {} + + return public.return_message(0,0,proxy_json_conf) + + # 2024/4/18 下午9:58 设置全局日志 + def set_global_log(self, get): + ''' + @name + @author wzz <2024/4/18 下午9:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('log_type').String(), + Param('log_path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.log_type = get.get("log_type", "default") + if not get.log_type in ["default", "file", "rsyslog", "off"]: + return public.return_message(-1, 0,"The log type is incorrect. Please pass in default/file/rsyslog/off!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["proxy_log"]["log_type"] = get.log_type + + if get.log_type == "file": + get.log_path = get.get("log_path", "") + if get.log_path == "": + return public.return_message(-1, 0,"The log path cannot be empty!") + if not get.log_path.startswith("/"): + return public.return_message(-1, 0,"The log path must start with/") + + get.proxy_json_conf["proxy_log"]["log_path"] = get.log_path + get.proxy_json_conf["proxy_log"]["log_conf"] = self._init_proxy_conf["proxy_log"]["log_conf"].format( + log_path=get.log_path, + site_name=get.site_name + ) + elif get.log_type == "rsyslog": + get.log_path = get.get("log_path", "") + if get.log_path == "": + return public.return_message(-1, 0,"The log path cannot be empty!") + site_name = get.site_name.replace(".", "_") + get.proxy_json_conf["proxy_log"]["log_conf"] = ( + "\n access_log syslog:server={server_host},nohostname,tag=nginx_{site_name}_access;" + "\n error_log syslog:server={server_host},nohostname,tag=nginx_{site_name}_error;" + .format( + server_host=get.log_path, + site_name=site_name + )) + get.proxy_json_conf["proxy_log"]["rsyslog_host"] = get.log_path + elif get.log_type == "off": + get.proxy_json_conf["proxy_log"]["log_conf"] = "\n access_log off;\n error_log off;" + else: + get.proxy_json_conf["proxy_log"]["log_conf"] = " " + self._init_proxy_conf["proxy_log"][ + "log_conf"].format( + log_path=public.get_logs_path(), + site_name=get.site_name + ) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + public.serviceReload() + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/18 下午10:21 设置basic_auth + def set_dir_auth(self, get): + ''' + @name 设置basic_auth + @param auth_type: add/edit + auth_path: /api + username: admin + password: admin + @return: + '''# 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('auth_path').String(), + Param('username').String(), + Param('password').String(), + Param('name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.name = get.get("name", "") + if get.name == "": + return public.return_message(-1, 0,"Name cannot be empty!") + + get.auth_path = get.get("auth_path", "") + if get.auth_path == "": + return public.return_message(-1, 0,"Auth_path cannot be empty!") + if not get.auth_path.startswith("/"): + return public.return_message(-1, 0,"Auth_path must start with/!") + + get.username = get.get("username", "") + get.password = get.get("password", "") + if get.username == "" or get.password == "": + return public.return_message(-1, 0,"The username and password cannot be empty!") + + get.password = public.hasPwd(get.password) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if len(get.proxy_json_conf["basic_auth"]) == 0: + return public.return_message(-1, 0,"[{}] does not exist in HTTP authentication, please add it first!".format(get.auth_path)) + + for i in range(len(get.proxy_json_conf["basic_auth"])): + if get.proxy_json_conf["basic_auth"][i]["auth_path"] == get.auth_path: + if get.proxy_json_conf["basic_auth"][i]["auth_name"] == get.name: + get.proxy_json_conf["basic_auth"][i]["username"] = get.username + get.proxy_json_conf["basic_auth"][i]["password"] = get.password + break + + auth_file = "/www/server/pass/{site_name}/{name}.htpasswd".format(site_name=get.site_name, name=get.name) + public.writeFile(auth_file, "{}:{}".format(get.username, get.password)) + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + public.serviceReload() + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/22 下午4:17 添加指定网站的basic_auth + def add_dir_auth(self, get): + ''' + @name 添加指定网站的basic_auth + @author wzz <2024/4/22 下午4:17> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('auth_path').String(), + Param('username').String(), + Param('password').String(), + Param('name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.auth_path = get.get("auth_path", "") + if get.auth_path == "": + return public.return_message(-1, 0,"Auth_path cannot be empty!") + if not get.auth_path.startswith("/"): + return public.return_message(-1, 0,"Auth_path must start with/!") + + get.name = get.get("name", "") + if get.name == "": + return public.return_message(-1, 0,"Name cannot be empty!") + + get.username = get.get("username", "") + if get.username == "": + return public.return_message(-1, 0,"Username cannot be empty!") + + get.password = get.get("password", "") + if get.password == "": + return public.return_message(-1, 0,"Password cannot be empty!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + auth_file = "/www/server/pass/{site_name}/{name}.htpasswd".format(site_name=get.site_name, name=get.name) + + auth_conf = { + "auth_status": True, + "auth_path": get.auth_path, + "auth_name": get.name, + "username": get.username, + "password": public.hasPwd(get.password), + "auth_file": auth_file, + } + + if len(get.proxy_json_conf["basic_auth"]) != 0: + for i in range(len(get.proxy_json_conf["basic_auth"])): + if get.proxy_json_conf["basic_auth"][i]["auth_path"] == get.auth_path: + return public.return_message(-1, 0,"[{}] already exists in HTTP authentication and cannot be added again!".format(get.auth_path)) + + if not os.path.exists("/www/server/pass"): + public.ExecShell("mkdir -p /www/server/pass") + if not os.path.exists("/www/server/pass/{}".format(get.site_name)): + public.ExecShell("mkdir -p /www/server/pass/{}".format(get.site_name)) + public.writeFile(auth_file, "{}:{}".format(get.username, public.hasPwd(get.password))) + + get.proxy_json_conf["basic_auth"].append(auth_conf) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Added successfully!") + + # 2024/4/23 上午9:34 删除指定网站的basic_auth + def del_dir_auth(self, get): + ''' + @name 删除指定网站的basic_auth + @author wzz <2024/4/23 上午9:35> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('auth_path').String(), + Param('name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.auth_path = get.get("auth_path", "") + if get.auth_path == "": + return public.return_message(-1, 0,"Auth_path cannot be empty!") + + get.name = get.get("name", "") + if get.name == "": + return public.return_message(-1, 0,"Name cannot be empty!") + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + auth_file = "/www/server/pass/{site_name}/{name}.htpasswd".format(site_name=get.site_name, name=get.name) + + panel_port = public.readFile('/www/server/panel/data/port.pl') + proxy_result = self.get_proxy_list(get) + if proxy_result['status']==-1:return proxy_result + proxy_list=proxy_result['message'] + if proxy_list[0]["proxy_pass"] == "https://127.0.0.1:{}".format(panel_port.strip()) and get.auth_path.strip() == "/": + return public.return_message(-1, 0,"[{}] is a reverse representation of the panel, and the HTTP authentication of [/] cannot be deleted!".format(get.site_name)) + if len(get.proxy_json_conf["basic_auth"]) != 0: + for i in range(len(get.proxy_json_conf["basic_auth"])): + if get.proxy_json_conf["basic_auth"][i]["auth_path"] == get.auth_path: + if get.proxy_json_conf["basic_auth"][i]["auth_name"] == get.name: + get.proxy_json_conf["basic_auth"].pop(i) + break + + public.ExecShell("rm -f {}".format(auth_file)) + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + return public.return_message(0, 0,"Delete successful!") + + # 2024/4/18 下午10:26 设置全局gzip + def set_global_gzip(self, get): + ''' + @name 设置全局gzip + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('gzip_min_length').String(), + Param('gzip_types').String(), + Param('gzip_status').Integer(), + Param('gzip_comp_level').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.gzip_status = get.get("gzip_status/d", 999) + if get.gzip_status == 999: + return public.return_message(-1, 0,"Gzip_status cannot be empty, please pass number 1 or 0!") + get.gzip_min_length = get.get("gzip_min_length", "10k") + get.gzip_comp_level = get.get("gzip_comp_level", "6") + if get.gzip_min_length[0] == "0" or get.gzip_min_length.startswith("-"): + return public.return_message(-1, 0,"The gzip_min_length parameter is invalid. Please enter a number greater than 0!") + if get.gzip_comp_level == "0" or get.gzip_comp_level.startswith("-"): + return public.return_message(-1, 0,"The gzip_comp_level parameter is invalid. Please enter a number greater than 0!") + get.gzip_types = get.get( + "gzip_types", + "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js" + ) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["gzip"]["gzip_status"] = True if get.gzip_status == 1 else False + if get.proxy_json_conf["gzip"]["gzip_status"]: + get.proxy_json_conf["gzip"]["gzip_status"] = True + get.proxy_json_conf["gzip"]["gzip_min_length"] = get.gzip_min_length + get.proxy_json_conf["gzip"]["gzip_comp_level"] = get.gzip_comp_level + get.proxy_json_conf["gzip"]["gzip_types"] = get.gzip_types + get.gzip_conf = ("gzip on;" + "\n gzip_min_length {gzip_min_length};" + "\n gzip_buffers 4 16k;" + "\n gzip_http_version 1.1;" + "\n gzip_comp_level {gzip_comp_level};" + "\n gzip_types {gzip_types};" + "\n gzip_vary on;" + "\n gzip_proxied expired no-cache no-store private auth;" + "\n gzip_disable \"MSIE [1-6]\\.\";").format( + gzip_min_length=get.gzip_min_length, + gzip_comp_level=get.gzip_comp_level, + gzip_types=get.gzip_types + ) + get.proxy_json_conf["gzip"]["gzip_conf"] = get.gzip_conf + else: + get.proxy_json_conf["gzip"]["gzip_conf"] = "" + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/18 下午10:27 设置全局缓存 + def set_global_cache(self, get): + ''' + @name 设置全局缓存 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('expires').String(), + Param('cache_status').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.cache_status = get.get("cache_status/d", 999) + if get.cache_status == 999: + return public.return_message(-1, 0,"Cache_status cannot be empty, please pass number 1 or 0!") + + get.expires = get.get("expires", "1d") + if get.expires[0] == "0" or get.expires.startswith("-"): + return public.return_message(-1, 0,"The expires parameter is illegal. Please enter a number greater than 0!") + expires = "expires {}".format(get.expires) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + static_cache = ("\n location ~ .*\\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\\.map|js\\.map)$" + "\n {{" + "\n {expires};" + "\n error_log /dev/null;" + "\n access_log /dev/null;" + "\n }}").format( + expires=expires, + ) + + cache_conf = ("\n proxy_cache {cache_zone};" + "\n proxy_cache_key $host$uri$is_args$args;" + "\n proxy_ignore_headers Set-Cookie Cache-Control expires X-Accel-Expires;" + "\n proxy_cache_valid 200 304 301 302 {expires};" + "\n proxy_cache_valid 404 1m;" + "{static_cache}").format( + cache_zone=get.proxy_json_conf["proxy_cache"]["cache_zone"], + expires=get.expires, + static_cache=get.proxy_json_conf["proxy_cache"]["static_cache"] if get.proxy_json_conf["proxy_cache"][ + "static_cache"] != "" else static_cache + ) + + get.proxy_json_conf["proxy_cache"]["cache_status"] = True if get.cache_status == 1 else False + if get.proxy_json_conf["proxy_cache"]["cache_status"]: + get.proxy_json_conf["proxy_cache"]["cache_status"] = True + get.proxy_json_conf["proxy_cache"]["expires"] = get.expires + get.proxy_json_conf["proxy_cache"]["cache_conf"] = cache_conf + else: + get.proxy_json_conf["proxy_cache"]["cache_status"] = False + get.proxy_json_conf["proxy_cache"]["cache_conf"] = static_cache + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/18 下午10:43 设置全局websocket支持 + def set_global_websocket(self, get): + ''' + @name + @author wzz <2024/4/19 下午2:37> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('websocket_status').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.websocket_status = get.get("websocket_status/d", 999) + if get.websocket_status == 999: + return public.return_message(-1, 0,"Websocket_status cannot be empty, please pass number 1 or 0!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if get.websocket_status == 1: + get.proxy_json_conf["websocket"]["websocket_status"] = True + else: + get.proxy_json_conf["websocket"]["websocket_status"] = False + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/19 下午2:54 设置备注 + def set_remak(self, get): + ''' + @name 设置备注 + @param get: + @return: + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.id = get.get("id", "") + if get.id == "": + return public.returnResult(status=False, msg="id不能为空!") + + get.remark = get.get("remark", "") + get.table = "sites" + get.ps = get.remark + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from data import data + data_obj = data() + result = data_obj.setPs(get) + if not result["status"]: + public.returnResult(status=False, msg=result["msg"]) + + get.proxy_json_conf["remark"] = get.remark + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return public.returnResult(msg=result["msg"]) + + # 2024/4/19 下午2:59 添加反向代理 + def add_proxy(self, get): + ''' + @name + @author wzz <2024/4/19 下午3:00> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('proxy_pass').String(), + Param('proxy_host').String(), + Param('proxy_type').String(), + Param('remark').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.proxy_pass = get.get("proxy_pass", "") + if get.proxy_pass == "": + return public.return_message(-1, 0,"Proxy_pass cannot be empty!") + + get.proxy_host = get.get("proxy_host", "$http_host") + get.proxy_type = get.get("proxy_type", "http") + get.remark = get.get("remark", "") + get.proxy_timeout = "proxy_connect_timeout 60s;\n proxy_send_timeout 600s;\n proxy_read_timeout 600s;" + + if get.remark != "": + get.remark = public.xssencode2(get.remark) + if get.proxy_type == "unix": + if not get.proxy_pass.startswith("/"): + return public.return_message(-1, 0,"The Unix file path must start with/, such as/tmp/flash.app.sock!") + if not get.proxy_pass.endswith(".sock"): + return public.return_message(-1, 0,"Unix files must end with. lock, such as/tmp/flash.app. lock!") + if not os.path.exists(get.proxy_pass): + return public.return_message(-1, 0,"The proxy target does not exist!") + + get.proxy_pass = "http://unix:{}".format(get.proxy_pass) + elif get.proxy_type == "http": + if not get.proxy_pass.startswith("http://") and not get.proxy_pass.startswith("https://"): + return public.return_message(-1, 0,"The proxy target must start with http://or https://") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + # 2024/4/19 下午3:45 检测是否已经存在proxy_path,有的话就返回错误 + for proxy_info in get.proxy_json_conf["proxy_info"]: + if proxy_info["proxy_path"] == get.proxy_path: + return public.return_message(-1, 0,"【 {} 】 already exists in the reverse proxy and cannot be added again!".format(get.proxy_path)) + + if len(get.proxy_json_conf["basic_auth"]) != 0: + for i in range(len(get.proxy_json_conf["basic_auth"])): + if get.proxy_json_conf["basic_auth"][i]["auth_path"] == get.proxy_path: + return public.return_message(-1, 0,"[{}] already exists in Basicauth, please delete it before adding a reverse proxy!".format( + get.proxy_path)) + + get.proxy_conf = self._template_proxy_conf.format( + ip_limit="", + gzip="", + proxy_cache="", + sub_filter="", + server_log="", + basic_auth="", + proxy_pass=get.proxy_pass, + proxy_host=get.proxy_host, + proxy_path=get.proxy_path, + custom_conf="", + timeout_conf=get.proxy_timeout, + websocket_support=get.proxy_json_conf["websocket"]["websocket_conf"], + ) + + get.proxy_json_conf["proxy_info"].append({ + "proxy_type": get.proxy_type, + "proxy_path": get.proxy_path, + "proxy_pass": get.proxy_pass, + "proxy_host": get.proxy_host, + "ip_limit": { + "ip_black": [], + "ip_white": [], + }, + "basic_auth": {}, + "proxy_cache": { + "cache_status": False, + "cache_zone": "", + "static_cache": "", + "expires": "1d", + "cache_conf": "", + }, + "gzip": { + "gzip_status": False, + "gzip_min_length": "10k", + "gzip_comp_level": "6", + "gzip_types": "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js", + "gzip_conf": "gzip on;\n gzip_min_length 10k;\n gzip_buffers 4 16k;\n gzip_http_version 1.1;\n gzip_comp_level 2;\n gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;\n gzip_vary on;\n gzip_proxied expired no-cache no-store private auth;\n gzip_disable \"MSIE [1-6]\\.\";", + }, + "sub_filter": { + "sub_filter_str": [], + }, + "websocket": { + "websocket_status": True, + "websocket_conf": "proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";", + }, + "proxy_log": { + "log_type": "off", + "log_conf": "", + }, + "timeout": { + "proxy_connect_timeout": "60", + "proxy_send_timeout": "600", + "proxy_read_timeout": "600", + "timeout_conf": "proxy_connect_timeout 60s;\n proxy_send_timeout 600s;\n proxy_read_timeout 600s;", + }, + "custom_conf": "", + "proxy_conf": get.proxy_conf, + "remark": get.remark, + "template_proxy_conf": self._template_proxy_conf, + }) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + public.set_module_logs('site_proxy', 'add_proxy', 1) + return public.return_message(0, 0,"Added successfully!") + + # 2024/4/19 下午9:45 删除指定站点 + def delete(self, get): + ''' + @name + @author wzz <2024/4/19 下午9:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('remove_path').Integer(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + get.id = get.get("id", "") + get.remove_path = get.get("remove_path/d", 0) + if get.id == "": + return public.return_message(-1, 0,"ID cannot be empty!") + + get.reload = get.get("reload/d", 1) + + if public.M('sites').where('id=?', (get.id,)).count() < 1: + return public.return_message(-1, 0,'The specified site does not exist!') + + site_file = public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf' + if os.path.exists(site_file): + public.ExecShell('rm -f {}'.format(site_file)) + redirect_dir = public.get_setup_path() + '/panel/vhost/nginx/redirect/' + get.site_name + if os.path.exists(redirect_dir): + public.ExecShell('rm -rf {}'.format(redirect_dir)) + + logs_file = public.get_logs_path() + '/{}*'.format(get.site_name) + public.ExecShell('rm -f {}'.format(logs_file)) + + self._site_proxy_conf_path = "{path}/{site_name}".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.ExecShell('rm -f {}'.format(self._site_proxy_conf_path)) + + if get.remove_path == 1: + public.ExecShell('rm -rf /www/wwwroot/{}'.format(get.site_name)) + + if get.reload == 1: + public.serviceReload() + + # 从数据库删除 + public.M('sites').where("id=?", (get.id,)).delete() + public.M('domain').where("pid=?", (get.id,)).delete() + public.WriteLog('TYPE_SITE', "SITE_DEL_SUCCESS", (get.site_name,)) + + return public.return_message(0, 0,"Reverse proxy project deleted successfully!") + + # 2024/5/28 上午9:50 批量删除站点 + def batch_delete(self, get): + ''' + @name 批量删除站点 + @author wzz <2024/5/28 上午9:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_list = get.get("site_list", []) + get.remove_path = get.get("remove_path/d", 0) + get.reload = get.get("reload/d", 0) + + try: + site_list = json.loads(get.site_list) + except: + return public.returnResult(False, "请传入需要删除的网站列表!") + + acc_list = [] + for site in site_list: + args = public.dict_obj() + args.site_name = site["site_name"] + args.remove_path = get.remove_path + args.reload = get.reload + args.id = site["id"] + de_result = self.delete(args) + if not de_result["status"]: + acc_list.append({"site_name": site["site_name"], "status": False}) + continue + + acc_list.append({"site_name": site["site_name"], "status": True}) + + public.serviceReload() + + return public.returnResult(True, msg="批量删除站点成功!", data=acc_list) + + # 2024/4/26 下午4:57 获取证书的部署状态 + def get_site_ssl_info(self, siteName): + ''' + @name 获取证书的部署状态 + @author wzz <2024/4/26 下午4:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + import time + import re + s_file = 'vhost/nginx/{}.conf'.format(siteName) + is_apache = False + if not os.path.exists(s_file): + s_file = 'vhost/apache/{}.conf'.format(siteName) + is_apache = True + + if not os.path.exists(s_file): + return -1 + + s_conf = public.readFile(s_file) + if not s_conf: return -1 + ssl_file = None + if is_apache: + if s_conf.find('SSLCertificateFile') == -1: + return -1 + s_tmp = re.findall(r"SSLCertificateFile\s+(.+\.pem)", s_conf) + if not s_tmp: return -1 + ssl_file = s_tmp[0] + else: + if s_conf.find('ssl_certificate') == -1: + return -1 + s_tmp = re.findall(r"ssl_certificate\s+(.+\.pem);", s_conf) + if not s_tmp: return -1 + ssl_file = s_tmp[0] + ssl_info = public.get_cert_data(ssl_file) + if not ssl_info: return -1 + ssl_info['endtime'] = int( + int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) + return ssl_info + except: + return -1 + + # 2024/4/19 下午10:05 获取所有project_type为proxy的站点,需要做分页配置,按照添加时间排序 + def get_list(self, get): + ''' + @name 获取所有project_type为proxy的站点,需要做分页配置,按照添加时间排序 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('search').String(), + Param('p').Integer(), + Param('limit').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.p = get.get("p/d", 1) + get.limit = get.get("limit/d", 10) + get.search = get.get("search", "") + + where = "project_type=?" + if get.search != "": + where += " and name like ?" + param = ("proxy", "%{}%".format(get.search)) + else: + param = ("proxy",) + + import db + sql = db.Sql() + count = sql.table('sites').where(where, param).count() + + import page + page = page.Page() + data = {} + info = {} + info['count'] = count + info['row'] = get.limit + + info['p'] = 1 + if hasattr(get, 'p'): + info['p'] = int(get['p']) + if info['p'] < 1: info['p'] = 1 + + try: + from flask import request + info['uri'] = public.url_encode(request.full_path) + except: + info['uri'] = '' + info['return_js'] = '' + data['where'] = where + data['page'] = page.GetPage(info) + + sql.table('sites').where(where, param) + sql.field('id,name,path,status,ps,addtime,edate').order('id desc') + sql.limit(str(page.SHIFT) + ',' + str(page.ROW)) + data['data'] = sql.select() + + try: + path = '/www/server/btwaf/site.json' + waf_res = json.loads(public.readFile(path)) + except: + waf_res = {} + + for site in data['data']: + get.site_name = site["name"] + project_config = self.read_json_conf(get)['message'] + site["healthy"] = 1 + site["waf"] = {} + if not project_config: + site["healthy"] = 0 + site["conf_path"] = "" + site["ssl"] = -1 + site["proxy_pass"] = "" + continue + + site["conf_path"] = public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf' + site["ssl"] = self.get_site_ssl_info(get.site_name) + site["proxy_pass"] = project_config["proxy_info"][0]["proxy_pass"] + + if waf_res: + for waf in waf_res: + if "open" in waf_res[waf]: + site["waf"] = {"status": True} + + return public.return_message(0,0,public.returnResult(data=data)) + + # 2024/4/19 下午11:46 给指定网站添加域名 + def add_domain(self, get): + ''' + @name + @author wzz <2024/4/19 下午11:46> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('domains').String(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1,0,"Sitename cannot be empty!") + get.id = get.get("id", "") + if get.id == "": + return public.return_message(-1,0,"ID cannot be empty!") + get.domains = get.get("domains", "") + if get.domains == "": + return public.return_message(-1,0,"Domains cannot be empty!") + if "," in get.domains: + return public.return_message(-1,0,"The domain name cannot contain commas!") + + get.domain_list = get.domains.strip().replace(' ', '').split("\n") + get.domain = ",".join(get.domain_list) + get.webname = get.site_name + port_list = [] + for domain in get.domain_list: + if not ":" in domain.strip(): + continue + + d_port = domain.strip().split(":")[1] + if not public.checkPort(d_port): + return public.return_message(-1,0,'The port number of domain name [{}] is illegal!'.format(domain)) + + port_list.append(d_port) + + # 2024/4/20 上午12:02 更新json文件 + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["domain_list"].extend(get.domain_list) + get.proxy_json_conf["site_port"].extend(port_list) + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + from panelSite import panelSite + result = panelSite().AddDomain(get) + result_status=0 + if not result['status']: + result_status=-1 + return public.return_message(result_status,0,result["msg"]) + + # 2024/4/20 上午12:07 删除指定网站的某个域名 + def del_domain(self, get): + ''' + @name + @author wzz <2024/4/20 上午12:07> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('domains').String(), + Param('id').Integer(), + Param('port').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.id = get.get("id", "") + if get.id == "": + return public.return_message(-1, 0,"ID cannot be empty!") + get.port = get.get("port", "") + if get.port == "": + return public.return_message(-1, 0,"Port cannot be empty!") + get.domain = get.get("domain", "") + if get.domain == "": + return public.return_message(-1, 0,"Domain cannot be empty!") + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.webname = get.site_name + + # 2024/4/20 上午12:02 更新json文件 + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1, 0,"Reading configuration file failed, please delete the website and add it again!") + + if len(get.proxy_json_conf["domain_list"]) == 1: + return public.return_message(-1, 0,"Keep at least one domain name!") + + while get.domain in get.proxy_json_conf["domain_list"]: + get.proxy_json_conf["domain_list"].remove(get.domain) + if get.port in get.proxy_json_conf["site_port"] and len(get.proxy_json_conf["site_port"]) != 1: + while get.port in get.proxy_json_conf["site_port"]: + get.proxy_json_conf["site_port"].remove(get.port) + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + from panelSite import panelSite + result = panelSite().DelDomain(get) + return_status =0 + if not result['status']: + return_status=-1 + return public.return_message(return_status,0, result["msg"]) + + # 2024/4/20 上午12:20 批量删除指定网站域名 + def batch_del_domain(self, get): + ''' + @name 批量删除指定网站域名 + @param get: + @return: + ''' + get.id = get.get("id", "") + if get.id == "": + return public.returnResult(status=False, msg="id不能为空!") + get.domains = get.get("domains", "") + if get.domains == "": + return public.returnResult(status=False, msg="domains不能为空!") + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.webname = get.site_name + + # 2024/4/20 上午12:02 更新json文件 + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + # if len(get.proxy_json_conf["domain_list"]) == 1: + # return public.returnResult(status=False, msg="至少保留一个域名!") + + get.domain_list = get.domains.strip().replace(' ', '').split("\n") + + # if len(get.domain_list) == len(get.proxy_json_conf["domain_list"]): + # return public.returnResult(status=False, msg="至少保留一个域名!") + + for domain in get.domain_list: + while domain in get.proxy_json_conf["domain_list"]: + get.proxy_json_conf["domain_list"].remove(domain) + + if ":" in domain: + port = domain.split(":")[1] + if len(get.proxy_json_conf["site_port"]) == 1: + continue + + while port in get.proxy_json_conf["site_port"]: + get.proxy_json_conf["site_port"].remove(port) + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + from panelSite import panelSite + res_domains = [] + for domain in get.domain_list: + get.domain = domain + get.port = "80" + if ":" in domain: + get.port = domain.split(":")[1] + result = panelSite().DelDomain(get) + res_domains.append({"name": domain, "status": result["status"], "msg": result["msg"]}) + + public.serviceReload() + return public.returnResult(status=True, data=res_domains) + + # 2024/4/20 上午9:17 获取域名列表和https端口 + def get_domain_list(self, get): + ''' + @name 获取域名列表和https端口 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + get.id = get.get("id", "") + if get.id == "": + return public.return_message(-1,0,"ID cannot be empty!") + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1,0,"Sitename cannot be empty!") + + get.siteName = get.webname = get.site_name + get.table = "domain" + get.list = True + get.search = get.id + + # 2024/4/20 上午12:02 更新json文件 + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + result_data = {} + import data + dataObject = data.data() + result_data["domain_list"] = dataObject.getData(get) + if get.proxy_json_conf["ssl_info"]["ssl_status"]: + if not "https_port" in get.proxy_json_conf or get.proxy_json_conf["https_port"] == "": + get.proxy_json_conf["https_port"] = "443" + result_data["https_port"] = get.proxy_json_conf["https_port"] + else: + result_data["https_port"] = "HTTPS not enabled" + + # 2024/4/20 上午9:21 domain_list里面没有的域名健康状态显示为0 + for domain in result_data["domain_list"]: + domain["healthy"] = 1 + if domain["name"] not in get.proxy_json_conf["domain_list"]: + domain["healthy"] = 0 + + public.set_module_logs('site_proxy', 'get_domain_list', 1) + return public.return_message(0,0,result_data) + + # 2024/4/20 下午2:22 获取配置文件 + def get_config(self, get): + ''' + @name + @author wzz <2024/4/20 下午2:22> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + '''# 校验参数 + try: + get.validate([ + Param('site_name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + site_conf = public.readFile(public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf') + ssl_conf = get.proxy_json_conf["ssl_info"]["ssl_conf"].format( + site_name=get.site_name, + ) + if get.proxy_json_conf["ssl_info"]["force_https"]: + ssl_conf = get.proxy_json_conf["ssl_info"]["force_ssl_conf"].format( + site_name=get.site_name, + force_conf=get.proxy_json_conf["ssl_info"]["force_ssl_conf"], + ) + + if "If there is abnormal access to the reverse proxy website and the content has already been configured here, please prioritize checking if the configuration here is correct" in get.proxy_json_conf["http_block"]: + http_block = get.proxy_json_conf["http_block"] + else: + http_block = '''# All HTTP fields such as server | upstream | map can be set, such as: +# server {{ +# listen 10086; +# server_name ... +# }} +# upstream stream_ser {{ +# server back_test.com; +# server ... +# }} +{default_describe} +{http_block}'''.format( + default_describe=self._init_proxy_conf["default_describe"], + http_block=get.proxy_json_conf["http_block"], + ) + + if "If there is abnormal access to the reverse proxy website and the content has already been configured here, please prioritize checking if the configuration here is correct" in get.proxy_json_conf["server_block"]: + server_block = get.proxy_json_conf["server_block"] + else: + server_block = '''# All server fields such as server | location can be set, such as: +# location /web {{ +# try_files $uri $uri/ /index.php$is_args$args; +# }} +# error_page 404 /diy_404.html; +{default_describe} +{server_block}'''.format( + default_describe=self._init_proxy_conf["default_describe"], + server_block=get.proxy_json_conf["server_block"], + ) + + data = { + "site_conf": site_conf if not site_conf is False else "", + "http_block": http_block, + "server_block": server_block, + "ssl_conf": ssl_conf, + } + + return public.return_message(0, 0,data) + + # 2024/4/20 下午2:38 保存配置文件 + def save_config(self, get): + ''' + @name 保存配置文件 + @param get: + @return: + '''# 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('conf_type').String(), + Param('body').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.conf_type = get.get("conf_type", "") + if get.conf_type == "": + return public.return_message(-1, 0,"Conf_type cannot be empty!") + + get.body = get.get("body", "") + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if get.conf_type == "http_block": + get.proxy_json_conf["http_block"] = get.body + else: + get.proxy_json_conf["server_block"] = get.body + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + public.serviceReload() + + return public.return_message(0, 0,"Save successful!") + + # 2024/4/20 下午3:11 根据proxy_json_conf,填入self._template_conf,然后生成nginx配置,保存到指定网站的conf文件中 + def generate_config(self, get): + """ + @name 根据proxy_json_conf,填入self._template_conf,然后生成nginx配置,保存到指定网站的conf文件中 + @param get: + @return: + """ + # 2024/4/20 下午3:36 构造ip黑白名单 + ip_black = "" + ip_white = "" + for ip in get.proxy_json_conf["ip_limit"]["ip_black"]: + ip_black += ("deny {};\n ").format(ip) + for ip in get.proxy_json_conf["ip_limit"]["ip_white"]: + ip_white += ("allow {};\n ").format(ip) + + if ip_white != "": + ip_white += "deny all;" + ip_limit_conf = ip_black + "\n " + ip_white + + proxy_conf = "" + ignore_path = [] + if len(get.proxy_json_conf["proxy_info"]) != 0: + for info in get.proxy_json_conf["proxy_info"]: + proxy_auth_conf = "" + if info["basic_auth"]: + proxy_auth_conf = ("auth_basic \"Authorization\";" + "\n auth_basic_user_file {auth_file};").format( + auth_path=info["basic_auth"]["auth_path"], + auth_file=info["basic_auth"]["auth_file"], + ) + + if len(get.proxy_json_conf["basic_auth"]) != 0: + for auth in get.proxy_json_conf["basic_auth"]: + if info["proxy_path"] == auth["auth_path"]: + ignore_path.append(auth["auth_path"]) + proxy_auth_conf = ("auth_basic \"Authorization\";" + "\n auth_basic_user_file {auth_file};").format( + auth_path=auth["auth_path"], + auth_file=auth["auth_file"], + ) + break + + p_ip_black = "" + p_ip_white = "" + for ip in info["ip_limit"]["ip_black"]: + p_ip_black += ("deny {};\n ").format(ip) + for ip in info["ip_limit"]["ip_white"]: + p_ip_white += ("allow {};\n ").format(ip) + + if p_ip_white != "": + p_ip_white += "deny all;" + p_ip_limit_conf = p_ip_black + "\n " + p_ip_white + + if p_ip_black == "" and p_ip_white == "": + p_ip_limit_conf = "" + + p_gzip_conf = "" + if info["gzip"]["gzip_status"]: + p_gzip_conf = info["gzip"]["gzip_conf"] + + p_sub_filter = "" + if len(info["sub_filter"]["sub_filter_str"]) != 0: + p_sub_filter = 'proxy_set_header Accept-Encoding \"\";' + if not "subs_filter" in get.proxy_json_conf: + get.proxy_json_conf["subs_filter"] = public.ExecShell("nginx -V 2>&1|grep 'ngx_http_substitutions_filter' -o")[0] != "" + + if not get.proxy_json_conf["subs_filter"]: + for filter in info["sub_filter"]["sub_filter_str"]: + p_sub_filter += "\n sub_filter {oldstr} {newstr};".format( + oldstr=filter["oldstr"] if filter["oldstr"] != "" else "\"\"", + newstr=filter["newstr"] if filter["newstr"] != "" else "\"\"", + ) + + p_sub_filter += "\n sub_filter_once off;" + else: + for filter in info["sub_filter"]["sub_filter_str"]: + p_sub_filter += "\n subs_filter {oldstr} {newstr} {sub_type};".format( + oldstr=filter["oldstr"] if filter["oldstr"] != "" else "\"\"", + newstr=filter["newstr"] if filter["newstr"] != "" else "\"\"", + sub_type=filter["sub_type"] if "sub_type" in filter and filter["sub_type"] != "" else "\"\"", + ) + + p_websocket_support = "" + if info["websocket"]["websocket_status"]: + p_websocket_support = info["websocket"]["websocket_conf"] + + timeout_conf = ("proxy_connect_timeout {proxy_connect_timeout};" + "\n proxy_send_timeout {proxy_send_timeout};" + "\n proxy_read_timeout {proxy_read_timeout};").format( + proxy_connect_timeout=info["timeout"]["proxy_connect_timeout"].replace("s", "") + "s", + proxy_send_timeout=info["timeout"]["proxy_send_timeout"].replace("s", "") + "s", + proxy_read_timeout=info["timeout"]["proxy_read_timeout"].replace("s", "") + "s", + ) + + tmp_conf = info["template_proxy_conf"].format( + basic_auth=proxy_auth_conf, + ip_limit=p_ip_limit_conf, + gzip=p_gzip_conf, + sub_filter=p_sub_filter, + proxy_cache=info["proxy_cache"]["cache_conf"], + server_log="", + proxy_pass=info["proxy_pass"], + proxy_host=info["proxy_host"], + proxy_path=info["proxy_path"], + custom_conf=info["custom_conf"], + timeout_conf=timeout_conf, + websocket_support=p_websocket_support, + ) + info["proxy_conf"] = tmp_conf + proxy_conf += tmp_conf + "\n " + + # 2024/4/20 下午3:37 构造basicauth + auth_conf = "" + if len(get.proxy_json_conf["basic_auth"]) != 0: + for auth in get.proxy_json_conf["basic_auth"]: + if auth["auth_path"] not in ignore_path: + tmp_conf = ("location ^~ {auth_path} {{" + "\n auth_basic \"Authorization\";" + "\n auth_basic_user_file {auth_file};" + "\n }}").format(auth_path=auth["auth_path"], auth_file=auth["auth_file"]) + auth_conf += tmp_conf + "\n " + + websocket_support = "" + if get.proxy_json_conf["websocket"]["websocket_status"]: + websocket_support = get.proxy_json_conf["websocket"]["websocket_conf"] + + gzip_conf = "" + if get.proxy_json_conf["gzip"]["gzip_status"]: + gzip_conf = get.proxy_json_conf["gzip"]["gzip_conf"] + + ssl_conf = "#error_page 404/404.html;" + # listen_port = " ".join(get.proxy_json_conf["site_port"]) + # listen_ipv6 = "\n listen [::]:{};".format(" ".join(get.proxy_json_conf["site_port"])) + # port_conf = get.proxy_json_conf["port_conf"].format( + # listen_port=listen_port, + # listen_ipv6=listen_ipv6, + # ) + if not "https_port" in get.proxy_json_conf or get.proxy_json_conf["https_port"] == "": + get.proxy_json_conf["https_port"] = "443" + if not "ipv4_port_conf" in get.proxy_json_conf: + get.proxy_json_conf["ipv4_port_conf"] = "listen {listen_port};" + if not "ipv6_port_conf" in get.proxy_json_conf: + get.proxy_json_conf["ipv6_port_conf"] = "listen [::]:{listen_port};" + if not "ipv4_http3_ssl_port_conf" in get.proxy_json_conf: + get.proxy_json_conf["ipv4_http3_ssl_port_conf"] = "{ipv4_port_conf}\n listen {https_port} quic;\n listen {https_port} ssl;" + if not "ipv6_http3_ssl_port_conf" in get.proxy_json_conf: + get.proxy_json_conf["ipv6_http3_ssl_port_conf"] = "{ipv6_port_conf}\n listen [::]:{https_port} quic;\n listen [::]:{https_port} ssl ;" + if not "ipv4_ssl_port_conf" in get.proxy_json_conf: + get.proxy_json_conf["ipv4_ssl_port_conf"] = "{ipv4_port_conf}\n listen {https_port} ssl http2 ;" + if not "ipv6_ssl_port_conf" in get.proxy_json_conf: + get.proxy_json_conf["ipv6_ssl_port_conf"] = "{ipv6_port_conf}\n listen [::]:{https_port} ssl http2 ;" + + ipv4_port_conf = "" + ipv6_port_conf = "" + for p in get.proxy_json_conf["site_port"]: + ipv4_port_conf += get.proxy_json_conf["ipv4_port_conf"].format( + listen_port=p, + ) + "\n " + ipv6_port_conf += get.proxy_json_conf["ipv6_port_conf"].format( + listen_port=p, + ) + "\n " + if get.proxy_json_conf["ssl_info"]["ssl_status"]: + if public.ExecShell("nginx -V 2>&1| grep 'http_v3_module' -o")[0] != "": + ipv4_http3_ssl_port_conf = get.proxy_json_conf["ipv4_http3_ssl_port_conf"].format( + ipv4_port_conf=ipv4_port_conf, + https_port=get.proxy_json_conf["https_port"], + ) + "\n " + ipv6_http3_ssl_port_conf = get.proxy_json_conf["ipv6_http3_ssl_port_conf"].format( + ipv6_port_conf=ipv6_port_conf, + https_port=get.proxy_json_conf["https_port"], + ) + "\n " + port_conf = ipv4_http3_ssl_port_conf + ipv6_http3_ssl_port_conf + "\n http2 on;" + else: + ipv4_ssl_port_conf = get.proxy_json_conf["ipv4_ssl_port_conf"].format( + ipv4_port_conf=ipv4_port_conf, + https_port=get.proxy_json_conf["https_port"], + ) + "\n " + ipv6_ssl_port_conf = get.proxy_json_conf["ipv6_ssl_port_conf"].format( + ipv6_port_conf=ipv6_port_conf, + https_port=get.proxy_json_conf["https_port"], + ) + "\n " + port_conf = ipv4_ssl_port_conf + ipv6_ssl_port_conf + ssl_conf = get.proxy_json_conf["ssl_info"]["ssl_conf"].format(site_name=get.site_name) + if get.proxy_json_conf["ssl_info"]["force_https"]: + ssl_conf = get.proxy_json_conf["ssl_info"]["force_ssl_conf"].format( + site_name=get.site_name, + force_conf=get.proxy_json_conf["ssl_info"]["force_conf"] + ) + else: + port_conf = ipv4_port_conf + "\n" + ipv6_port_conf + + redirect_conf = "" + if get.proxy_json_conf["redirect"]["redirect_status"]: + redirect_conf = get.proxy_json_conf["redirect"]["redirect_conf"].format(site_name=get.site_name) + + security_conf = "" + if get.proxy_json_conf["security"]["security_status"]: + domains = get.proxy_json_conf["security"]["domains"] if not get.proxy_json_conf["security"][ + "http_status"] else "none blocked " + get.proxy_json_conf["security"]["domains"] + security_conf = get.proxy_json_conf["security"]["security_conf"].format( + static_resource=get.proxy_json_conf["security"]["static_resource"], + expires="30d", + domains=domains.replace(",", " "), + return_resource=get.proxy_json_conf["security"]["return_resource"], + ) + + default_cache = get.proxy_json_conf["default_cache"].format( + site_name=get.site_name, + cache_name=get.site_name.replace(".", "_") + ) + get.http_block = default_cache + "\n" + get.proxy_json_conf["http_block"] + + # 2024/6/4 下午4:20 兼容新版监控报表的配置 + monitor_conf = "" + if (os.path.exists("/www/server/panel/plugin/monitor/monitor_main.py") and + os.path.exists("/www/server/monitor/config/sites.json")): + try: + sites_data = json.loads(public.readFile("/www/server/monitor/config/sites.json")) + + if sites_data[get.site_name]["open"]: + id = public.M('domain').where("name=?", (get.site_name,)).getField('id') + monitor_conf = '''#Monitor-Config-Start monitor log sending configuration + access_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag={sid}__access monitor; + error_log syslog:server=unix:/tmp/bt-monitor.sock,nohostname,tag={sid}__error; + #Monitor-Config-End'''.format(sid=id) + except: + pass + + get.site_conf = self._template_conf.format( + http_block=get.http_block, + server_block=get.proxy_json_conf["server_block"], + port_conf=port_conf, + ssl_start_msg=public.getMsg('NGINX_CONF_MSG1'), + err_page_msg=public.getMsg('NGINX_CONF_MSG2'), + php_info_start=public.getMsg('NGINX_CONF_MSG3'), + rewrite_start_msg=public.getMsg('NGINX_CONF_MSG4'), + domains=' '.join(get.proxy_json_conf["domain_list"]) if len( + get.proxy_json_conf["domain_list"]) > 1 else get.site_name, + site_name=get.site_name, + ssl_info=ssl_conf, + err_age_404=get.proxy_json_conf["err_age_404"], + err_age_502=get.proxy_json_conf["err_age_502"], + ip_limit_conf=ip_limit_conf, + auth_conf=auth_conf, + sub_filter="", + gzip_conf=gzip_conf, + security_conf=security_conf, + redirect_conf=redirect_conf, + proxy_conf=proxy_conf, + proxy_cache=get.proxy_json_conf["proxy_cache"]["cache_conf"], + server_log=get.proxy_json_conf["proxy_log"]["log_conf"], + site_path=get.proxy_json_conf["site_path"], + websocket_support=websocket_support, + monitor_conf=monitor_conf, + ) + + # 2024/4/21 下午10:46 设置商业ssl证书 + def set_cert(self, get): + ''' + @name + @author wzz <2024/4/21 下午10:46> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.oid = get.get("oid", "") + if get.oid == "": + return public.returnResult(status=False, msg="oid不能为空!") + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.siteName = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from panelSSL import panelSSL + ssl_obj = panelSSL() + set_result = ssl_obj.set_cert(get) + if set_result["status"] == False: + return set_result + + get.proxy_json_conf["ssl_info"]["ssl_status"] = True + get.proxy_json_conf["https_port"] = "443" + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return set_result + + # 2024/4/21 下午11:03 关闭SSl证书 + def close_ssl(self, get): + ''' + @name + @author wzz <2024/4/21 下午11:04> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.siteName = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from panelSite import panelSite + result = panelSite().CloseSSLConf(get) + if not result["status"]: + return result + + get.proxy_json_conf["ssl_info"]["ssl_status"] = False + get.proxy_json_conf["https_port"] = "443" + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return result + + # 2024/4/21 下午11:10 保存指定网站的SSL证书 + def set_ssl(self, get): + ''' + @name 保存指定网站的SSL证书 + @author wzz <2024/4/21 下午11:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.key = get.get("key", "") + if get.key == "": + return public.returnResult(status=False, msg="key不能为空!") + + get.csr = get.get("csr", "") + if get.csr == "": + return public.returnResult(status=False, msg="csr不能为空!") + + get.siteName = get.site_name + get.type = -1 + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from panelSite import panelSite + result = panelSite().SetSSL(get) + if not result["status"]: + # return public.returnResult(status=False, msg=result["msg"]) + return result + + get.proxy_json_conf["ssl_info"]["ssl_status"] = True + get.proxy_json_conf["https_port"] = "443" + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return result + + # 2024/4/21 下午11:29 部署测试证书 + def set_test_cert(self, get): + ''' + @name + @author wzz <2024/4/21 下午11:30> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.partnerOrderId = get.get("partnerOrderId", "") + if get.partnerOrderId == "": + return public.returnResult(status=False, msg="partnerOrderId不能为空!") + + get.siteName = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from panelSSL import panelSSL + ssl_obj = panelSSL() + set_result = ssl_obj.GetSSLInfo(get) + if set_result["status"] == False: + return set_result + + get.proxy_json_conf["ssl_info"]["ssl_status"] = True + get.proxy_json_conf["https_port"] = "443" + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return set_result + + # 2024/4/21 下午11:33 申请let' encrypt证书 + def apply_cert_api(self, get): + ''' + @name + @author wzz <2024/4/21 下午11:34> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.domains = get.get("domains", "") + if get.domains == "": + return public.returnResult(status=False, msg="domains不能为空!") + + get.auth_type = get.get("auth_type", "") + if get.auth_type == "": + return public.returnResult(status=False, msg="auth_type不能为空!") + + get.auth_to = get.get("auth_to", "") + if get.auth_to == "": + return public.returnResult(status=False, msg="auth_to不能为空!") + + get.auto_wildcard = get.get("auto_wildcard", "") + if get.auto_wildcard == "": + return public.returnResult(status=False, msg="auto_wildcard不能为空!") + + get.id = get.get("id", "") + if get.id == "": + return public.returnResult(status=False, msg="id不能为空!") + + get.siteName = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from acme_v2 import acme_v2 + acme = acme_v2() + result = acme.apply_cert_api(get) + if not result["status"]: + return result + + get.proxy_json_conf["ssl_info"]["ssl_status"] = True + get.proxy_json_conf["https_port"] = "443" + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return result + + # 2024/4/21 下午11:36 验证let' encrypt dns + def apply_dns_auth(self, get): + ''' + @name + @author wzz <2024/4/21 下午11:36> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.index = get.get("index", "") + if get.index == "": + return public.returnResult(status=False, msg="index不能为空!") + + from acme_v2 import acme_v2 + acme = acme_v2() + return acme.apply_dns_auth(get) + + # 2024/4/21 下午11:44 设置证书夹里面的证书 + def SetBatchCertToSite(self, get): + ''' + @name + @author wzz <2024/4/21 下午11:44> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.BatchInfo = get.get("BatchInfo", "") + if get.BatchInfo == "": + return public.returnResult(status=False, msg="BatchInfo不能为空!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from panelSSL import panelSSL + ssl_obj = panelSSL() + set_result = ssl_obj.SetBatchCertToSite(get) + if not "successList" in set_result: + return set_result + + for re in set_result["successList"]: + if re["status"] and re["siteName"] == get.site_name: + get.proxy_json_conf["ssl_info"]["ssl_status"] = True + break + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return set_result + + # 2024/4/22 上午9:43 设置强制https + def set_force_https(self, get): + ''' + @name 设置强制https + @param get: + site_name: 网站名 + force_https: 1/0 + @return: + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.force_https = get.get("force_https/d", 999) + if get.force_https == 999: + return public.returnResult(status=False, msg="force_https不能为空!") + + get.siteName = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["ssl_info"]["force_https"] = True if get.force_https == 1 else False + + from panelSite import panelSite + if get.force_https == 1: + result = panelSite().HttpToHttps(get) + else: + result = panelSite().CloseToHttps(get) + + if not result["status"]: + return result + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return result + + # 2024/4/22 上午10:27 创建重定向 + def CreateRedirect(self, get): + ''' + @name + @author wzz <2024/4/22 上午10:27> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('domainorpath').String(), + Param('redirecttype').String(), + Param('redirectpath').String(), + Param('tourl').String(), + Param('redirectdomain').String(), + Param('redirectname').String(), + Param('type').Integer(), + Param('holdpath').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.domainorpath = get.get("domainorpath", "") + if get.domainorpath == "": + return public.return_message(-1, 0,"Domainorpath cannot be empty!") + + get.redirecttype = get.get("redirecttype", "") + if get.redirecttype == "": + return public.return_message(-1, 0,"Redirecttype cannot be empty!") + + get.redirectpath = get.get("redirectpath", "") + if get.domainorpath == "path" and get.redirectpath == "": + return public.return_message(-1, 0,"Redirectpath cannot be empty!") + + get.tourl = get.get("tourl", "") + if get.tourl == "": + return public.return_message(-1, 0,"Tour cannot be empty!") + + get.redirectdomain = get.get("redirectdomain", "") + if get.domainorpath == "domain" and get.redirectdomain == "": + return public.return_message(-1, 0,"Redirectdomain cannot be empty!") + + get.redirectname = get.get("redirectname", "") + if get.redirectname == "": + return public.return_message(-1, 0,"Redirectname cannot be empty!") + + get.sitename = get.site_name + get.type = 1 + get.holdpath = 1 + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["redirect"]["redirect_status"] = True + + from panelRedirect import panelRedirect + result = panelRedirect().CreateRedirect(get) + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + if not result['status']: + return public.return_message(-1,0,result['msg']) + return public.return_message(0,0,result['msg']) + + # 2024/4/22 上午10:45 删除指定网站的某个重定向规则 + def DeleteRedirect(self, get): + ''' + @name 删除指定网站的某个重定向规则 + @author wzz <2024/4/22 上午10:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('redirectname').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.redirectname = get.get("redirectname", "") + if get.redirectname == "": + return public.return_message(-1, 0,"Redirectname cannot be empty!") + + get.sitename = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + from panelRedirect import panelRedirect + redirect_list = panelRedirect().GetRedirectList(get) + if len(redirect_list) == 0: + get.proxy_json_conf["redirect"]["redirect_status"] = False + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + result =panelRedirect().DeleteRedirect(get) + if not result['status']: + return public.return_message(-1,0,result['msg']) + return public.return_message(0,0,result['msg']) + + # 2024/4/23 上午10:38 编辑指定网站的某个重定向规则 + def ModifyRedirect(self, get): + ''' + @name 编辑指定网站的某个重定向规则 + @author wzz <2024/4/23 上午10:38> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + '''# 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('domainorpath').String(), + Param('redirecttype').String(), + Param('redirectpath').String(), + Param('tourl').String(), + Param('redirectdomain').String(), + Param('redirectname').String(), + Param('type').Integer(), + Param('holdpath').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.domainorpath = get.get("domainorpath", "") + if get.domainorpath == "": + return public.return_message(-1, 0,"Domainorpath cannot be empty!") + + get.redirecttype = get.get("redirecttype", "") + if get.redirecttype == "": + return public.return_message(-1, 0,"Redirecttype cannot be empty!") + + get.redirectpath = get.get("redirectpath", "") + if get.domainorpath == "path" and get.redirectpath == "": + return public.return_message(-1, 0,"Redirectpath cannot be empty!") + + get.tourl = get.get("tourl", "") + if get.tourl == "": + return public.return_message(-1, 0,"Tour cannot be empty!") + + get.redirectdomain = get.get("redirectdomain", "") + if get.domainorpath == "domain" and get.redirectdomain == "": + return public.return_message(-1, 0,"Redirectdomain cannot be empty!") + + get.redirectname = get.get("redirectname", "") + if get.redirectname == "": + return public.return_message(-1, 0,"Redirectname cannot be empty!") + + get.sitename = get.site_name + get.type = get.get("type/d", 1) + get.holdpath = get.get("holdpath/d", 1) + + from panelRedirect import panelRedirect + result =panelRedirect().ModifyRedirect(get) + if not result['status']: + return public.return_message(-1, 0,result['msg']) + return public.return_message(0, 0,result['msg']) + + # 2024/4/26 下午3:32 获取指定网站指定重定向规则的信息 + def GetRedirectFile(self, get): + ''' + @name 获取指定网站指定重定向规则的信息 + @author wzz <2024/4/26 下午3:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + get.validate([ + Param('path').String(), + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.path = get.get("path", "") + if get.path == "": + return public.return_message(-1, 0,"Path cannot be empty!") + + if not os.path.exists(get.path): + return public.return_message(-1, 0,"Redirection has stopped or the configuration file directory does not exist!") + import files + f = files.files() + result = f.GetFileBody(get) + if not result['status']: + del result['status'] + return public.return_message(-1, 0,result) + del result['status'] + return public.return_message(0, 0,result) + + # 2024/4/22 上午11:12 设置防盗链 + def SetSecurity(self, get): + ''' + @name 设置防盗链 + @author wzz <2024/4/22 上午11:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('fix').String(), + Param('domains').String(), + Param('return_rule').String(), + Param('name').String(), + Param('http_status').Bool(), + Param('status').Bool(), + Param('id').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.fix = get.get("fix", "") + if get.fix == "": + return public.return_message(-1, 0,"Fix cannot be empty!") + + get.domains = get.get("domains", "") + if get.domains == "": + return public.return_message(-1, 0,"Domains cannot be empty!") + + get.return_rule = get.get("return_rule", "") + if get.return_rule == "": + return public.return_message(-1, 0,"Return_rule cannot be empty!") + + get.http_status = get.get("http_status", "") + if get.http_status == "": + return public.return_message(-1, 0,"Https status cannot be empty!") + + get.status = get.get("status", "") + if get.status == "": + return public.return_message(-1, 0,"Status cannot be empty!") + + get.id = get.get("id", "") + if get.id == "": + return public.return_message(-1, 0,"ID cannot be empty!") + + get.name = get.site_name + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["security"]["security_status"] = True if get.status == "true" else False + get.proxy_json_conf["security"]["static_resource"] = get.fix + get.proxy_json_conf["security"]["domains"] = get.domains + get.proxy_json_conf["security"]["return_resource"] = get.return_rule + get.proxy_json_conf["security"]["http_status"] = True if get.http_status else False + + from panel_site_v2 import panelSite + result = panelSite().SetSecurity(get) + if result["status"]==-1: + return result + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return result + + # 2024/4/23 下午3:07 添加全局IP黑白名单 + def add_ip_limit(self, get): + ''' + @name 添加全局IP黑白名单 + @author wzz <2024/4/23 下午3:08> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('ip_type').String(), + Param('ips').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.ip_type = get.get("ip_type", "black") + if get.ip_type not in ["black", "white"]: + return public.return_message(-1, 0,"The ip_type parameter is incorrect, black or white must be passed!") + + get.ips = get.get("ips", "") + if get.ips == "": + return public.return_message(-1, 0,"IPS cannot be empty, please enter IP, one per line!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.ips = get.ips.split("\n") + for ip in get.ips: + if ip not in get.proxy_json_conf["ip_limit"]["ip_{}".format(get.ip_type)]: + get.proxy_json_conf["ip_limit"]["ip_{}".format(get.ip_type)].append(ip) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/23 下午3:12 删除全局IP黑白名单 + def del_ip_limit(self, get): + ''' + @name 删除全局IP黑白名单 + @author wzz <2024/4/23 下午3:12> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('ip_type').String(), + Param('ip').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.ip_type = get.get("ip_type", "black") + if get.ip_type not in ["black", "white"]: + return public.return_message(-1, 0,"The ip_type parameter is incorrect, black or white must be passed!") + + get.ip = get.get("ip", "") + if get.ip == "": + return public.return_message(-1, 0,"IP cannot be empty, please enter IP!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if get.ip in get.proxy_json_conf["ip_limit"]["ip_{}".format(get.ip_type)]: + get.proxy_json_conf["ip_limit"]["ip_{}".format(get.ip_type)].remove(get.ip) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Delete successful!") + + # 2024/4/23 下午3:13 批量删除全局IP黑白名单 + def batch_del_ip_limit(self, get): + ''' + @name 批量删除全局IP黑白名单 + @author wzz <2024/4/23 下午3:14> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('ip_type').String(), + Param('ips').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.ip_type = get.get("ip_type", "black") + if get.ip_type not in ["black", "white", "all"]: + return public.return_message(-1, 0,"The ip_type parameter is incorrect, black or white must be passed!") + + get.ips = get.get("ips", "") + if get.ips == "": + return public.return_message(-1, 0,"IPS cannot be empty, please enter IP, one per line!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.ips = get.ips.split("\n") + for ip in get.ips: + if get.ip_type == "all": + if ip in get.proxy_json_conf["ip_limit"]["ip_black"]: + get.proxy_json_conf["ip_limit"]["ip_black"].remove(ip) + if ip in get.proxy_json_conf["ip_limit"]["ip_white"]: + get.proxy_json_conf["ip_limit"]["ip_white"].remove(ip) + else: + if ip in get.proxy_json_conf["ip_limit"]["ip_{}".format(get.ip_type)]: + get.proxy_json_conf["ip_limit"]["ip_{}".format(get.ip_type)].remove(ip) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Delete successful!") + + # 2024/4/22 下午9:01 获取指定网站的方向代理列表 + def get_proxy_list(self, get): + ''' + @name + @author wzz <2024/4/22 下午9:02> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + '''# 校验参数 + try: + get.validate([ + Param('site_name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if len(get.proxy_json_conf["proxy_info"]) == 0: + return public.return_message(-1, 0,"No proxy information!") + + subs_filter = get.proxy_json_conf["subs_filter"] if "subs_filter" in get.proxy_json_conf else public.ExecShell("nginx -V 2>&1|grep 'ngx_http_substitutions_filter' -o")[0] != "" + + if get.proxy_path != "": + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + info["global_websocket"] = get.proxy_json_conf["websocket"]["websocket_status"] + info["subs_filter"] = subs_filter + if "http://unix:" in info["proxy_pass"]: + info["proxy_pass"] = info["proxy_pass"].replace("http://unix:", "") + return public.return_message(0, 0,info) + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + public.set_module_logs('site_proxy', 'get_proxy_list', 1) + return public.return_message(0, 0,get.proxy_json_conf["proxy_info"]) + + # 2024/4/23 上午11:16 获取指定网站的所有配置信息 + def get_global_conf(self, get): + ''' + @name 获取指定网站的所有配置信息 + @author wzz <2024/4/23 上午11:16> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0, "Sitename cannot be empty!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + return public.return_message(0, 0, get.proxy_json_conf) + + # 2024/4/22 下午9:04 设置指定网站指定URL的反向代理 + def set_url_proxy(self, get): + ''' + @name + @author wzz <2024/4/22 下午9:04> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('remark').String(), + Param('proxy_pass').String(), + Param('proxy_host').String(), + Param('proxy_type').String(), + Param('websocket').Integer(), + Param('proxy_connect_timeout').Integer(), + Param('proxy_send_timeout').Integer(), + Param('proxy_read_timeout').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.proxy_host = get.get("proxy_host", "") + if get.proxy_host == "": + return public.return_message(-1, 0,"Proxy_host cannot be empty!") + + get.proxy_pass = get.get("proxy_pass", "") + if get.proxy_pass == "": + return public.return_message(-1, 0,"Proxy_pass cannot be empty!") + + get.proxy_type = get.get("proxy_type", "") + if get.proxy_type == "": + return public.return_message(-1, 0,"Proxy_type cannot be empty!") + + get.proxy_connect_timeout = get.get("proxy_connect_timeout", "60s") + get.proxy_send_timeout = get.get("proxy_send_timeout", "600s") + get.proxy_read_timeout = get.get("proxy_read_timeout", "600s") + + get.remark = get.get("remark", "") + if get.remark != "": + get.remark = public.xssencode2(get.remark) + + if get.proxy_type == "unix": + if not get.proxy_pass.startswith("http://unix:"): + if not get.proxy_pass.startswith("/"): + return public.return_message(-1, 0,"Unix file path must be in/or http://unix: At the beginning, such as/tmp/flash.app. lock!") + if not get.proxy_pass.endswith(".sock"): + return public.return_message(-1, 0,"Unix files must end with. lock, such as/tmp/flash.app. lock!") + if not os.path.exists(get.proxy_pass): + return public.return_message(-1, 0,"The proxy target does not exist!") + get.proxy_pass = "http://unix:" + get.proxy_pass + elif get.proxy_type == "http": + if not get.proxy_pass.startswith("http://") and not get.proxy_pass.startswith("https://"): + return public.return_message(-1, 0,"The proxy target must start with http://or https://!") + + get.websocket = get.get("websocket/d", 1) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if get.proxy_json_conf["websocket"]["websocket_status"] and get.websocket != 1: + return public.return_message(-1, 0,"The global websocket is in an open state, and it is not allowed to individually disable websocket support for this URL!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + info["proxy_host"] = get.proxy_host + info["proxy_pass"] = get.proxy_pass + info["proxy_type"] = get.proxy_type + info["timeout"]["proxy_connect_timeout"] = get.proxy_connect_timeout.replace("s", "") + info["timeout"]["proxy_send_timeout"] = get.proxy_send_timeout.replace("s", "") + info["timeout"]["proxy_read_timeout"] = get.proxy_read_timeout.replace("s", "") + info["websocket"]["websocket_status"] = True if get.websocket == 1 else False + info["remark"] = get.remark + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/22 下午9:34 删除指定网站指定URL的反向代理 + def del_url_proxy(self, get): + ''' + @name 删除指定网站指定URL的反向代理 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + get.proxy_json_conf["proxy_info"].remove(info) + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Delete successful!") + + # 2024/4/22 下午9:36 设置指定网站指定URL反向代理的备注 + def set_url_remark(self, get): + ''' + @name 设置指定网站指定URL反向代理的备注 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('remark').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1,0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1,0,"Proxy_path cannot be empty!") + + get.remark = get.get("remark", "") + if get.remark != "": + get.remark = public.xssencode2(get.remark) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + info["remark"] = get.remark + break + else: + return public.return_message(-1,0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0,0,"Set successfully!") + + # 2024/4/22 下午9:40 添加指定网站指定URL的内容替换 + def add_sub_filter(self, get): + ''' + @name 添加指定网站指定URL的内容替换 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('oldstr').String(), + Param('newstr').String(), + Param('proxy_path').String(), + Param('sub_type').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.oldstr = get.get("oldstr", "") + get.newstr = get.get("newstr", "") + + if get.oldstr == "" and get.newstr == "": + return public.return_message(-1, 0,"Oldstr and Newstr cannot be empty at the same time!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.sub_type = get.get("sub_type", "g") + if get.sub_type == "": + get.sub_type = "g" + import re + if not re.match(r'^[ior]+$|^g(?!.*o)|^o(?!.*g)$', get.sub_type): + return public.return_message(-1, 0,"Get.sub_type can only contain letter combinations from 'g', 'i', 'o', or 'r', and 'g' and 'o' cannot coexist!") + + is_subs = public.ExecShell("nginx -V 2>&1|grep 'ngx_http_substitutions_filter' -o")[0] + if not is_subs and re.search(u'[\u4e00-\u9fa5]', get.oldstr + get.newstr): + return public.return_message(-1, 0,"The content you entered contains Chinese. We have detected that the current version of nginx does not support it. Please try reinstalling a version of nginx 1.20 or higher and try again!") + + if get.sub_type != "g" and not is_subs: + return public.return_message(-1, 0,"Detected that the current nginx version only supports default replacement types. Please try reinstalling nginx version 1.20 or higher and try again!") + + if not "g" in get.sub_type and not "o" in get.sub_type: + get.sub_type = "g" + get.sub_type + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + for sub in info["sub_filter"]["sub_filter_str"]: + if get.oldstr == sub["oldstr"]: + return public.return_message(-1, 0,"Content before replacement: The configuration information for [{}] already exists, please do not add it again!".format( + get.oldstr)) + info["sub_filter"]["sub_filter_str"].append( + { + "sub_type": get.sub_type, + "oldstr": get.oldstr, + "newstr": get.newstr + } + ) + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/22 下午10:00 删除指定网站指定URL的内容替换 + def del_sub_filter(self, get): + ''' + @name 删除指定网站指定URL的内容替换 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('oldstr').String(), + Param('newstr').String(), + Param('proxy_path').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.oldstr = get.get("oldstr", "") + get.newstr = get.get("newstr", "") + + if get.oldstr == "" and get.newstr == "": + return public.return_message(-1, 0,"Oldstr and Newstr cannot be empty at the same time!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + for sub in info["sub_filter"]["sub_filter_str"]: + if get.oldstr == sub["oldstr"]: + info["sub_filter"]["sub_filter_str"].remove(sub) + break + else: + return public.return_message(-1, 0,"No configuration information found for content before replacement: [{}]!".format(get.oldstr)) + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Delete successful!") + + # 2024/4/22 下午10:03 设置指定网站指定URL的内容压缩 + def set_url_gzip(self, get): + ''' + @name 设置指定网站指定URL的内容压缩 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('gzip_min_length').String(), + Param('proxy_path').String(), + Param('gzip_types').String(), + Param('gzip_status').Integer(), + Param('gzip_comp_level').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.gzip_status = get.get("gzip_status/d", 999) + if get.gzip_status == 999: + return public.return_message(-1, 0,"Gzip_status cannot be empty, please pass number 1 or 0!") + get.gzip_min_length = get.get("gzip_min_length", "10k") + get.gzip_comp_level = get.get("gzip_comp_level", "6") + if get.gzip_min_length[0] == "0" or get.gzip_min_length.startswith("-"): + return public.return_message(-1, 0,"The gzip_min_length parameter is invalid. Please enter a number greater than 0!") + if get.gzip_comp_level == "0" or get.gzip_comp_level.startswith("-"): + return public.return_message(-1, 0,"The gzip_comp_level parameter is invalid. Please enter a number greater than 0!") + get.gzip_types = get.get( + "gzip_types", + "text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js" + ) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + info["gzip"]["gzip_status"] = True if get.gzip_status == 1 else False + if get.gzip_status == 1: + info["gzip"]["gzip_types"] = get.gzip_types + info["gzip"]["gzip_min_length"] = get.gzip_min_length + info["gzip"]["gzip_comp_level"] = get.gzip_comp_level + info["gzip"]["gzip_conf"] = ("gzip on;" + "\n gzip_min_length {gzip_min_length};" + "\n gzip_buffers 4 16k;" + "\n gzip_http_version 1.1;" + "\n gzip_comp_level {gzip_comp_level};" + "\n gzip_types {gzip_types};" + "\n gzip_vary on;" + "\n gzip_proxied expired no-cache no-store private auth;" + "\n gzip_disable \"MSIE [1-6]\\.\";").format( + gzip_min_length=get.gzip_min_length, + gzip_comp_level=get.gzip_comp_level, + gzip_types=get.gzip_types, + ) + else: + info["gzip"]["gzip_conf"] = "" + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/22 下午10:15 添加指定网站指定URL的IP黑白名单 + def add_url_ip_limit(self, get): + ''' + @name 添加指定网站指定URL的IP黑白名单 + @author wzz <2024/4/22 下午10:16> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('ip_type').String(), + Param('ips').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.ip_type = get.get("ip_type", "black") + if get.ip_type not in ["black", "white"]: + return public.return_message(-1, 0,"The ip_type parameter is incorrect, black or white must be passed!") + + get.ips = get.get("ips", "") + if get.ips == "": + return public.return_message(-1, 0,"IPS cannot be empty, please enter IP, one per line!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.ips = get.ips.split("\n") + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + for ip in get.ips: + if get.ip_type == "black": + if not ip in info["ip_limit"]["ip_black"]: + info["ip_limit"]["ip_black"].append(ip) + else: + if not ip in info["ip_limit"]["ip_white"]: + info["ip_limit"]["ip_white"].append(ip) + + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/22 下午10:21 删除指定网站指定URL的IP黑白名单 + def del_url_ip_limit(self, get): + ''' + @name 删除指定网站指定URL的IP黑白名单 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('ip_type').String(), + Param('ip').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0,"Proxy_path cannot be empty!") + + get.ip_type = get.get("ip_type", "black") + if get.ip_type not in ["black", "white"]: + return public.return_message(-1, 0,"The ip_type parameter is incorrect, black or white must be passed!") + + get.ip = get.get("ip", "") + if get.ip == "": + return public.return_message(-1, 0,"IP cannot be empty, please enter IP!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + if get.ip in info["ip_limit"]["ip_{ip_type}".format(ip_type=get.ip_type)]: + info["ip_limit"]["ip_{ip_type}".format(ip_type=get.ip_type)].remove(get.ip) + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/24 上午11:21 批量删除指定网站指定URL的IP黑白名单 + def batch_del_url_ip_limit(self, get): + ''' + @name 批量删除指定网站指定URL的IP黑白名单 + @author wzz <2024/4/24 上午11:22> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('ip_type').String(), + Param('ips').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0, "Sitename cannot be empty!") + + get.ip_type = get.get("ip_type", "black") + if get.ip_type not in ["black", "white", "all"]: + return public.return_message(-1, 0, "The ip_type parameter is incorrect, black or white must be passed!") + + get.ips = get.get("ips", "") + if get.ips == "": + return public.return_message(-1, 0, "IPS cannot be empty, please enter IP, one per line!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + get.ips = get.ips.split("\n") + if get.ip_type == "all": + for ip in get.ips: + if ip in info["ip_limit"]["ip_black"]: + info["ip_limit"]["ip_black"].remove(ip) + if ip in info["ip_limit"]["ip_white"]: + info["ip_limit"]["ip_white"].remove(ip) + else: + for ip in get.ips: + if ip in info["ip_limit"]["ip_{ip_type}".format(ip_type=get.ip_type)]: + info["ip_limit"]["ip_{ip_type}".format(ip_type=get.ip_type)].remove(ip) + break + else: + return public.return_message(-1, 0, "No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0, "Delete successful!") + + # 2024/4/22 下午8:14 设置指定网站指定URL的缓存 + def set_url_cache(self, get): + ''' + @name 设置指定网站指定URL的缓存 + @param get: + @return: + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('expires').String(), + Param('proxy_path').String(), + Param('cache_status').Integer(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.cache_status = get.get("cache_status/d", 999) + if get.cache_status == 999: + return public.return_message(-1, 0,"Cache_status cannot be empty, please pass number 1 or 0!") + + get.expires = get.get("expires", "1d") + if get.expires[0] == "0" or get.expires.startswith("-"): + return public.return_message(-1, 0,"The expires parameter is illegal. Please enter a number greater than 0!") + + expires = "expires {}".format(get.expires) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + static_cache = ("\n location ~ .*\\.(css|js|jpe?g|gif|png|webp|woff|eot|ttf|svg|ico|css\\.map|js\\.map)$" + "\n {{" + "\n {expires};" + "\n error_log /dev/null;" + "\n access_log /dev/null;" + "\n }}").format( + expires=expires, + ) + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + info["proxy_cache"]["cache_status"] = True if get.cache_status == 1 else False + info["proxy_cache"]["expires"] = get.expires + if get.cache_status == 1: + info["proxy_cache"]["cache_conf"] = ("\n proxy_cache {cache_zone};" + "\n proxy_cache_key $host$uri$is_args$args;" + "\n proxy_ignore_headers Set-Cookie Cache-Control expires X-Accel-Expires;" + "\n proxy_cache_valid 200 304 301 302 {expires};" + "\n proxy_cache_valid 404 1m;" + "{static_cache}").format( + cache_zone=get.proxy_json_conf["proxy_cache"]["cache_zone"], + expires=get.expires, + static_cache=static_cache, + ) + else: + info["proxy_cache"]["cache_conf"] = "" + break + else: + return public.return_message(-1, 0,"No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0,"Set successfully!") + + # 2024/4/24 上午9:57 设置指定网站指定URL的自定义配置 + def set_url_custom_conf(self, get): + ''' + @name 设置指定网站指定URL的自定义配置 + @author wzz <2024/4/24 上午9:58> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + Param('proxy_path').String(), + Param('custom_conf').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0, "Sitename cannot be empty!") + + get.proxy_path = get.get("proxy_path", "") + if get.proxy_path == "": + return public.return_message(-1, 0, "Proxy_path cannot be empty!") + + get.custom_conf = get.get("custom_conf", "") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + for info in get.proxy_json_conf["proxy_info"]: + if info["proxy_path"] == get.proxy_path: + info["custom_conf"] = get.custom_conf + break + else: + return public.return_message(-1, 0, "No proxy information found for this URL [{}]!".format(get.proxy_path)) + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.return_message(0, 0, "Set successfully!") + + @staticmethod + def nginx_get_log_file(nginx_config: str, is_error_log: bool = False): + import re + if is_error_log: + re_data = re.findall(r"error_log +(/(\S+/?)+) ?(.*?);", nginx_config) + else: + re_data = re.findall(r"access_log +(/(\S+/?)+) ?(.*?);", nginx_config) + if re_data is None: + return None + for i in re_data: + file_path = i[0].strip(";") + if file_path != "/dev/null": + return file_path + return None + + def xsssec(self, text): + replace_list = { + "<": "<", + ">": ">", + "'": "'", + '"': """, + } + for k, v in replace_list.items(): + text = text.replace(k, v) + return public.xssencode2(text) + + # 2024/4/24 下午5:39 获取指定网站的网站日志 + def GetSiteLogs(self, get): + ''' + @name 获取指定网站的网站日志 + @author wzz <2024/4/24 下午5:39> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('type').String(), + Param('site_name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"site_name不能为空!") + + get.type = get.get("type", "access") + log_name = get.site_name + if get.type != "access": + log_name = get.site_name + ".error" + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + if get.proxy_json_conf["proxy_log"]["log_type"] == "default": + log_file = public.get_logs_path() + "/" + log_name + '.log' + elif get.proxy_json_conf["proxy_log"]["log_type"] == "file": + log_file = get.proxy_json_conf["proxy_log"]["log_path"] + "/" + log_name + '.log' + else: + return public.return_message(0, 0,{"msg": "", "size": 0}) + + if os.path.exists(log_file): + return public.return_message(0, 0,{ + "msg": self.xsssec(public.GetNumLines(log_file, 1000)), + "size": public.to_size(os.path.getsize(log_file)) + } + ) + + return public.return_message(0, 0,{"msg": "", "size": 0}) + + # 2024/4/25 上午10:51 清理指定网站的反向代理缓存 + def clear_cache(self, get): + ''' + @name 清理指定网站的反向代理缓存 + @author wzz <2024/4/25 上午10:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # 校验参数 + try: + get.validate([ + Param('site_name').String(), + + ], [ + public.validate.trim_filter(), + ]) + except Exception as ex: + public.print_log("error info: {}".format(ex)) + return public.return_message(-1, 0, str(ex)) + + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.return_message(-1, 0,"Sitename cannot be empty!") + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + cache_dir = "/www/wwwroot/{site_name}/proxy_cache_dir".format(site_name=get.site_name) + if os.path.exists(cache_dir): + public.ExecShell("rm -rf {cache_dir}/*".format(cache_dir=cache_dir)) + + public.serviceReload() + return public.return_message(0, 0,"Cleanup successful!") + + return public.return_message(-1, 0,"Cleanup failed, cache directory does not exist!") + + # 2024/4/25 下午9:24 设置指定网站的https端口 + def set_https_port(self, get): + ''' + @name 设置指定网站的https端口 + @author wzz <2024/4/25 下午9:24> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空!") + + get.https_port = get.get("https_port", "443") + if not public.checkPort(get.https_port) and get.https_port != "443": + return public.returnResult(status=False, msg="https端口【{}】不合法!".format(get.https_port)) + + get.proxy_json_conf = self.read_json_conf(get)['message'] + if not get.proxy_json_conf: + return public.return_message(-1,0,"Reading configuration file failed, please delete the website and add it again!") + + get.proxy_json_conf["https_port"] = get.https_port + + update_result = self.update_conf(get) + if update_result["status"]==-1: + return update_result + + return public.returnResult(msg="设置成功!") + + # 2024/4/23 下午2:12 保存并重新生成新的nginx配置文件 + def update_conf(self, get): + ''' + @name + @author wzz <2024/4/23 下午2:13> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.conf_file = public.get_setup_path() + '/panel/vhost/nginx/' + get.site_name + '.conf' + self.generate_config(get) + get.data = get.site_conf + get.encoding = "utf-8" + get.path = get.conf_file + + import files + f = files.files() + save_result = f.SaveFileBody(get) + if save_result["status"] == False: + return public.return_message(-1,0,save_result["msg"]) + + self._site_proxy_conf_path = "{path}/{site_name}/{site_name}.json".format( + path=self._proxy_config_path, + site_name=get.site_name + ) + public.writeFile(self._site_proxy_conf_path, json.dumps(get.proxy_json_conf)) + + return public.return_message(0,0,"Save successful!") diff --git a/mod/project/push/__init__.py b/mod/project/push/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/project/push/msgconfMod.py b/mod/project/push/msgconfMod.py new file mode 100644 index 00000000..d5a94e6a --- /dev/null +++ b/mod/project/push/msgconfMod.py @@ -0,0 +1,70 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: baozi +# ------------------------------------------------------------------- +# 新告警通道管理模块 +# ------------------------------ +from mod.base.msg import SenderManager, WeChatAccountMsg, update_mod_push_msg +from mod.base.push_mod import SenderConfig +from mod.base import json_response + +import public + + +update_mod_push_msg() + + +class main(SenderManager): + + @staticmethod + def wx_account_auth(get=None): + return WeChatAccountMsg.get_auth_url() + + @staticmethod + def unbind_wx_account(get): + try: + sender_id = get.sender_id.strip() + except AttributeError: + return json_response(status=False, msg="参数错误") + + conf = SenderConfig().get_by_id(sender_id) + if not conf: + return json_response(status=False, msg="未查询到对应绑定信息") + + res = WeChatAccountMsg.unbind(conf["data"]["id"]) + public.WriteFile(WeChatAccountMsg.need_refresh_file, "") + return res + + def set_default_sender(self, get): + try: + + try: + sender_id = get.sender_id.strip() + sender_type = get.sender_type.strip() + except AttributeError: + return json_response(status=False, msg="参数错误") + + sc = SenderConfig() + print(sc) + change = False + print("SenderConfig",sc.config) + for conf in sc.config: + if conf["sender_type"] == sender_type: + is_original = conf.get("original", False) + if conf["id"] == sender_id: + change = True + conf["original"] = True + else: + conf["original"] = False + + sc.save_config() + if change: + self.set_default_for_compatible(sc.get_by_id(sender_id)) + return json_response(status=True, msg="设置成功") + except Exception as e: + return json_response(status=False, msg=e) + diff --git a/mod/project/push/taskMod.py b/mod/project/push/taskMod.py new file mode 100644 index 00000000..95b0edab --- /dev/null +++ b/mod/project/push/taskMod.py @@ -0,0 +1,140 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: baozi +# ------------------------------------------------------------------- +# 新告警通道管理模块 +# ------------------------------ +import json +import traceback +import os +from mod.base import json_response + +from mod.base.push_mod import PushManager, TaskConfig, TaskRecordConfig, TaskTemplateConfig, PushSystem +from mod.base.push_mod import update_mod_push_system, UPDATE_MOD_PUSH_FILE, load_task_template_by_file, \ + UPDATE_VERSION_FILE +from mod.base.msg import update_mod_push_msg +from mod.base.push_mod.rsync_push import load_rsync_template +from mod.base.push_mod.task_manager_push import load_task_manager_template +from mod.base.push_mod.load_push import load_load_template +from mod.base.push_mod import PUSH_DATA_PATH + +def update_mod(): + + if not os.path.exists(UPDATE_VERSION_FILE): + load_task_template_by_file("/www/server/panel/mod/base/push_mod/site_push_template.json") + load_task_template_by_file("/www/server/panel/mod/base/push_mod/system_push_template.json") + load_task_template_by_file("/www/server/panel/mod/base/push_mod/database_push_template.json") + with open(UPDATE_VERSION_FILE, "w") as f: + f.write("") + + if not os.path.exists(UPDATE_MOD_PUSH_FILE): + update_mod_push_msg() + + load_rsync_template() + load_task_manager_template() + load_load_template() + + update_mod_push_system() + + +update_mod() +del update_mod + + +class main(PushManager): + + def get_task_list(self, get=None): + try: + res = TaskConfig().config + res.sort(key=lambda x: x["create_time"]) + for i in res: + i['view_msg'] = self.get_view_msg_format(i) + return json_response(status=True, data=res) + except: + print(traceback.format_exc()) + + @staticmethod + def get_task_record(get): + page = 1 + size = 10 + try: + if hasattr(get, "page"): + page = int(get.page.strip()) + if hasattr(get, "size"): + size = int(get.size.strip()) + task_id = get.task_id.strip() + except (AttributeError, ValueError, TypeError): + return json_response(status=False, msg="参数错误") + + t = TaskRecordConfig(task_id) + t.config.sort(key=lambda x: x["create_time"]) + page = max(page, 1) + size = max(size, 1) + count = len(t.config) + data = t.config[(page - 1) * size: page * size] + return json_response(status=True, data={ + "count": count, + "list": data, + }) + + def clear_task_record(self, get): + try: + task_id = get.task_id.strip() + except (AttributeError, ValueError, TypeError): + return json_response(status=False, msg="参数错误") + self.clear_task_record_by_task_id(task_id) + + return json_response(status=True, msg="清除成功") + + @staticmethod + def remove_task_records(get): + try: + task_id = get.task_id.strip() + record_ids = set(json.loads(get.record_ids.strip())) + except (AttributeError, ValueError, TypeError): + return json_response(status=False, msg="参数错误") + task_records = TaskRecordConfig(task_id) + for i in range(len(task_records.config) - 1, -1, -1): + if task_records.config[i]["id"] in record_ids: + del task_records.config[i] + + task_records.save_config() + return json_response(status=True, msg="清除成功") + + @staticmethod + def get_task_template_list(get=None): + res = [] + p_sys = PushSystem() + for i in TaskTemplateConfig().config: + if not i['used']: + continue + to = p_sys.get_task_object(i["id"], i["load_cls"]) + if not to: + continue + t = to.filter_template(i["template"]) + if not t: + continue + i["template"] = t + res.append(i) + + return json_response(status=True, data=res) + + @staticmethod + def get_view_msg_format(task: dict) -> str: + from mod.base.push_mod.rsync_push import ViewMsgFormat as Rv + from mod.base.push_mod.site_push import ViewMsgFormat as Sv + from mod.base.push_mod.task_manager_push import ViewMsgFormat as Tv + from mod.base.push_mod.database_push import ViewMsgFormat as Dv + from mod.base.push_mod.system_push import ViewMsgFormat as SSv + from mod.base.push_mod.load_push import ViewMsgFormat as Lv + + list_obj = [Rv(), Sv(), Tv(), Dv(), SSv(), Lv()] + for i in list_obj: + res = i.get_msg(task) + if res is not None: + return res + return '--' diff --git a/mod/test/__init__.py b/mod/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/test/docker/routetestModTest.py b/mod/test/docker/routetestModTest.py new file mode 100644 index 00000000..ada85da8 --- /dev/null +++ b/mod/test/docker/routetestModTest.py @@ -0,0 +1,92 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +# ------------------------------ +# Docker模型测试模块 - 容器模型 +# ------------------------------ +import sys +import unittest + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, '/www/server/panel') + +from mod.project.docker.routetestMod import main as routetest_main + +routetest = routetest_main() + + +# +class TestContainerModel(unittest.TestCase): + """ + 创建测试用例 + """ + + def test_returnResult(self): + """ + 测试模型测试方法,检测返回结果是否为json格式数据 + @return: + """ + result = routetest.returnResult({'data': {}}) + self.assertIsInstance(result, dict) + self.assertIn('status', result) + self.assertIn('msg', result) + self.assertIn('data', result) + self.assertIn('code', result) + self.assertIn('timestamp', result) + + def test_wsRequest(self): + """ + 使用ws长链请求ws://127.0.0.1:8888/ws_mod + 并发送{"mod_name":"docker","sub_mod_name":"routetest","def_name":"wsRequest","ws_callback":"111"},检测返回结果是否为True + + 备注:请将__init__.py中ws模型路由的comReturn和csrf检查注释掉再测试 + @param get: + {"mod_name":"docker","sub_mod_name":"routetest","def_name":"wsRequest","ws_callback":"111"} + {"mod_name":"模型名称","sub_mod_name":"子模块名称","def_name":"函数名称","ws_callback":"ws必传参数,传111",其他参数接后面} + @return: + """ + import json + import time + from websocket import create_connection + ws = create_connection("ws://127.0.0.1:8888/ws_mod") + print("连接状态:", ws.connected) + + params = {"mod_name": "docker", "sub_mod_name": "routetest", "def_name": "wsRequest", "ws_callback": "111"} + ws.send(json.dumps(params)) + + while True: + result = ws.recv() + print("接收到结果:", result.strip()) + + try: + result_data = json.loads(result) + if "result" in result_data and "callback" in result_data: + if result_data["result"] == True and result_data["callback"] == "111": + print("websocket测试成功!") + break + except Exception as e: + pass + + # 等待一段时间再继续接收消息 + time.sleep(0.1) + + ws.close() + self.assertIn('result', result) + self.assertIn('callback', result) + + +if __name__ == '__main__': + # unittest.main() + # 创建测试套件 + suite = unittest.TestSuite() + suite.addTest(TestContainerModel('test_returnResult')) + suite.addTest(TestContainerModel('test_wsRequest')) + + # 创建测试运行器 + runner = unittest.TextTestRunner() + runner.run(suite) diff --git a/mod/test/java/__init__.py b/mod/test/java/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/test/java/test_spring_config.py b/mod/test/java/test_spring_config.py new file mode 100644 index 00000000..f77a88d8 --- /dev/null +++ b/mod/test/java/test_spring_config.py @@ -0,0 +1,46 @@ +import json +import os +import time +import unittest + +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +from mod.project.java.springboot_parser import SpringConfigParser, SpringLogConfigParser + + +class TestSpringConfigParser(unittest.TestCase): + + # def test_parser_config(self): + # file = "/www/java_mall/yami-shop-admin-0.0.1-SNAPSHOT.jar" + # # file = "/www/lilishop/lili-shop-error_jar.jar" + # # file = "/www/wwwroot/111_java/demo.jar" + # # file = "/www/wwwroot/33_java/tduck-api.jar" + # # file = "/www/java_mall/yami-shop-api-0.0.1-SNAPSHOT.jar" + # + # scp = SpringConfigParser(file) + # print(scp.get_tip()) + # + # def test_shell_env(self): + # SpringConfigParser.get_env_file_data("/www/wwwroot/33_java/test.sh") + + def test_parser_log_config(self): + # file = "/www/java_mall/yami-shop-admin-0.0.1-SNAPSHOT.jar" + # file = "/www/lilishop/run_api/buyer-api-4.3.jar" + # file = "/www/wwwroot/111_java/demo.jar" + # file = "/www/wwwroot/33_java/tduck-api.jar" + file = "/www/wwwroot/55_MCMS_java/ms-mcms.jar" + # file = "/www/java_mall/yami-shop-api-0.0.1-SNAPSHOT.jar" + + if not os.path.exists(file): + print("文件不存在") + scp = SpringLogConfigParser(file) + print(scp.get_all_log_ptah()) + + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/java/test_sprintboot_mod.py b/mod/test/java/test_sprintboot_mod.py new file mode 100644 index 00000000..f7653c43 --- /dev/null +++ b/mod/test/java/test_sprintboot_mod.py @@ -0,0 +1,46 @@ +import time +import unittest + +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.project.java.projectMod import main as springboot_main +from mod.base.web_conf.util import GET_CLASS +from mod.project.java import utils + + +class TestSpringBootConfig(unittest.TestCase): + + def setUp(self) -> None: + self.springboot = springboot_main() + + # def test_process_for_create(self): + # get = GET_CLASS() + # get.is_java_process = '' + # get.search = 'ja' + # data = self.springboot.process_for_create(get) + # print(data) + # + # def test_process_info_for_create(self): + # get = GET_CLASS() + # get.pid = "15701" + # data = self.springboot.process_info_for_create(get) + # print(data) + + # def test_create_port(self): + # t = time.time() + # print(t) + # print(utils.create_a_not_used_port()) + # print(time.time() - t) + + def test_get_jar_war_config(self): + file = "/www/wwwroot/33_java/tduck-api.jar" + data = utils.get_jar_war_config(file) + if data: + data = utils.to_utf8(data) + print(data) + + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/java/test_util.py b/mod/test/java/test_util.py new file mode 100644 index 00000000..85ad5163 --- /dev/null +++ b/mod/test/java/test_util.py @@ -0,0 +1,126 @@ +import unittest +import tempfile +import os +import zipfile +import sys +from unittest.mock import patch, MagicMock +if "/www/server/panel" not in sys.path: + sys.path.append("/www/server/panel") + +from mod.project.java.utils import get_jar_war_config, to_utf8, parse_application_yaml, TomCat + + +class TestJarWarConfig(unittest.TestCase): + def setUp(self): + # 创建测试用的zip文件 + self.test_jar = tempfile.NamedTemporaryFile(delete=False) + self.test_jar_name = self.test_jar.name + self.test_jar.close() + + # 创建一个包含application.yaml的zip文件 + with zipfile.ZipFile(self.test_jar_name, 'w') as jar: + jar.writestr('application.yaml', 'spring:\n datasource:\n url: jdbc:mysql://localhost:3306/testdb') + + # 创建一个不包含application.yaml的zip文件 + self.test_jar_no_config = tempfile.NamedTemporaryFile(delete=False) + self.test_jar_no_config_name = self.test_jar_no_config.name + self.test_jar_no_config.close() + + with zipfile.ZipFile(self.test_jar_no_config_name, 'w') as jar: + jar.writestr('README.txt', 'This is a test JAR without configuration') + + # 创建一个非zip文件 + self.test_non_zip = tempfile.NamedTemporaryFile(delete=False) + self.test_non_zip_name = self.test_non_zip.name + self.test_non_zip.close() + + # 创建一个不存在的文件路径 + self.test_non_existent = 'non_existent.jar' + + def tearDown(self): + # 删除测试文件 + os.unlink(self.test_jar_name) + os.unlink(self.test_jar_no_config_name) + os.unlink(self.test_non_zip_name) + + def test_get_jar_war_config_with_valid_jar(self): + # 测试有效的JAR文件 + result = get_jar_war_config(self.test_jar_name) + self.assertIsNotNone(result) + self.assertEqual(len(result), 1) + self.assertIn('application.yaml', result[0][0]) + + def test_get_jar_war_config_with_no_config(self): + # 测试没有配置文件的JAR + result = get_jar_war_config(self.test_jar_no_config_name) + self.assertIsNone(result) + + def test_get_jar_war_config_with_non_zip(self): + # 测试非ZIP文件 + result = get_jar_war_config(self.test_non_zip_name) + self.assertIsNone(result) + + def test_get_jar_war_config_with_non_existent(self): + # 测试不存在的文件 + result = get_jar_war_config(self.test_non_existent) + self.assertIsNone(result) + + def test_to_utf8(self): + # 测试转换为UTF-8 + byte_data = 'test'.encode('utf-8') + file_data_list = [('test.txt', byte_data)] + result = to_utf8(file_data_list) + self.assertIsNotNone(result) + self.assertEqual(result, [('test.txt', 'test')]) + + def test_parse_application_yaml_valid(self): + # 测试有效的YAML配置 + byte_data = 'spring:\n datasource:\n url: jdbc:mysql://localhost:3306/testdb' + file_data_list = [('application.yaml', byte_data.encode('utf-8'))] + result = parse_application_yaml(file_data_list) + self.assertIsNotNone(result) + self.assertEqual(len(result), 1) + self.assertIn('application.yaml', result[0][0]) + self.assertIsInstance(result[0][1], dict) + self.assertEqual(result[0][1]['spring']['datasource']['url'], 'jdbc:mysql://localhost:3306/testdb') + + def test_parse_application_yaml_invalid(self): + # 测试无效的YAML配置 + invalid_byte_data = 'not --- a valid yaml' + file_data_list = [('application.yaml', invalid_byte_data.encode('utf-8'))] + result = parse_application_yaml(file_data_list) + self.assertIsNotNone(result) + self.assertEqual(len(result), 0) + + +class TestTomCat(unittest.TestCase): + def setUp(self): + self.tomcat_path = "/usr/local/bttomcat/tomcat10" + self.tomcat = TomCat(self.tomcat_path) + + def test_jdk_path(self): + self.assertEqual(self.tomcat.jdk_path, "/usr/local/btjdk/jdk8") + + def test_config_xml(self): + self.assertIsNotNone(self.tomcat.config_xml) + print(self.tomcat.config_xml) + + def test_save_config_xml(self): + self.tomcat.add_host("taaaaa", "/tmp/aaaa") + self.tomcat.add_host("taaaaa", "/tmp/aaaa") + self.tomcat.add_host("tbbb", "/tmp/tbbb") + self.assertEqual(self.tomcat.save_config_xml(), True) + with open(self.tomcat_path + "/conf/server.xml", "r") as f: + print(f.read()) + + self.tomcat.remove_host("tbbb") + self.assertEqual(self.tomcat.save_config_xml(), True) + with open(self.tomcat_path + "/conf/server.xml", "r") as f: + print(f.read()) + + def test_status(self): + print(self.tomcat.status()) + + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/php/__init__.py b/mod/test/php/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/test/php/test_php_asyncMod.py b/mod/test/php/test_php_asyncMod.py new file mode 100644 index 00000000..827674b5 --- /dev/null +++ b/mod/test/php/test_php_asyncMod.py @@ -0,0 +1,273 @@ +import unittest +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") +from mod.base import RealProcess + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public + +from mod.project.php.php_asyncMod import main as php_async +phpasync = php_async() + +class Testmain(unittest.TestCase): + def test_create_project(self): + args = { + 'webname':{"domain":"test.c","domainlist":[]}, + 'php_version':'74', + 'site_path':'/xiaopacai/swoole-webim-demo-master', + 'project_cmd':'php server/hsw_server.php start', + 'install_dependence':'1', + 'sql':'', + 'sql_name':'', + 'sql_user': '', + 'sql_pwd': '', + 'sql_codeing': '', + 'project_ps': '', + 'open_proxy': '', + 'project_proxy_path': '', + 'project_port': '', + } + # phpasync.create_project(public.to_dict_obj(args)) + + + def test_delete_site(self): + args = { + 'webname':"test.c", + 'id':'1', + } + # phpasync.delete_site(public.to_dict_obj(args)) + + def test_get_project_list(self): + res = phpasync.get_project_list(public.to_dict_obj({})) + self.assertEqual(type(res), dict) + + def test_project_get_domain(self): + args = { + 'sitename':'sdadaw.c' + } + res = phpasync.project_get_domain(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + + def test_project_remove_domain(self): + self.fail() + + def test_project_add_domain(self): + args = { + 'sitename':'sdadaw.c', + 'domain':["daw.daw","ssff.cxs"] + } + res = phpasync.project_add_domain(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + def test_get_project_run_state(self): + args = { + 'sitename':'sdadaw.c', + } + res = phpasync.get_project_run_state(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_modify_project_run_state(self): + args = { + 'sitename':'sdadaw.c', + 'status':'stop' + } + res = phpasync.modify_project_run_state(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_async_dependence_config(self): + self.fail() + + def test_modify_project(self): + args = { + 'sitename':'sdadaw.c', + 'php_version':'74', + 'project_path':'/xiaopacai/swoole-webim-demo-master', + 'project_cmd':'php server/hsw_server.php start', + 'site_run_path':'/xiaopacai/swoole-webim-demo-master', + 'project_port': '', + } + res = phpasync.modify_project(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_get_project_log(self): + args = { + 'sitename':'sdadaw.c', + } + res = phpasync.get_project_log(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_get_config_file(self): + args = { + 'sitename':'sdadaw.c', + } + res = phpasync.get_config_file(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_upload_version(self): + self.fail() + + def test_get_version_list(self): + args = { + 'sitename':'sdadaw.c', + } + res = phpasync.get_version_list(public.to_dict_obj(args)) + + def test_remove_version(self): + args = { + 'sitename':'sdadaw.c', + 'version':'1' + } + res = phpasync.remove_version(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_recover_version(self): + args = { + 'sitename':'sdadaw.c', + 'version':'1' + } + res = phpasync.recover_version(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_now_file_backup(self): + args = { + 'sitename':'sdadaw.c', + 'version':'2' + } + res = phpasync.now_file_backup(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_set_version_ps(self): + args = { + 'sitename':'sdadaw.c', + 'version':'2', + 'ps':'test' + } + res = phpasync.set_version_ps(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_get_setup_log(self): + args = { + 'sitename':'sdadaw.c', + } + res = phpasync.get_setup_log(public.to_dict_obj(args)) + self.assertEqual(type(res), dict) + + def test_add_crontab(self): + pass + + def test_get_crontab_list(self): + args = { + 'sitename':'sdadaw.c', + } + res = phpasync.get_crontab_list(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_start_task(self): + args = { + 'id':'19', + } + res = phpasync.start_task(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_modify_crontab_status(self): + args = { + 'id':'19', + } + res = phpasync.modify_crontab_status(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_remove_crontab(self): + args = { + 'id':'19', + } + res = phpasync.remove_crontab(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_modify_crontab(self): + pass + + def test_get_crontab_log(self): + args = { + 'id':'19', + } + res = phpasync.get_crontab_log(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_clearn_logs(self): + args = { + 'id':'19', + } + res = phpasync.clearn_logs(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_get_group_list(self): + res = phpasync.get_group_list(public.to_dict_obj('{}')) + self.assertEqual(res['code'], 1) + + def test_create_group(self): + args = { + 'group_name':'test', + } + res = phpasync.create_group(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_remove_group(self): + args = { + 'group_name':'test', + } + res = phpasync.remove_group(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_group_add_project(self): + args = { + 'group_name':'test', + 'project_name':'sdadaw.c' + } + res = phpasync.group_add_project(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_group_remove_project(self): + args = { + 'group_name':'test', + 'project_name':'sdadaw.c' + } + res = phpasync.group_remove_project(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_set_group_interval(self): + args = { + 'group_name':'test', + 'interval':'15' + } + res = phpasync.set_group_interval(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_set_group_status(self): + args = { + 'group_name':'test', + 'status':'1' + } + res = phpasync.set_group_status(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_get_proxy_file(self): + args = { + 'sitename':'sdadaw.c', + 'proxyname':'test' + } + res = phpasync.get_proxy_file(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + + def test_save_proxy_file(self): + args = { + 'sitename':'sdadaw.c', + 'proxyname':'test', + 'file':'test' + } + res = phpasync.save_proxy_file(public.to_dict_obj(args)) + self.assertEqual(res['code'], 1) + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/process/__init__.py b/mod/test/process/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/test/process/test.py b/mod/test/process/test.py new file mode 100644 index 00000000..48f40fca --- /dev/null +++ b/mod/test/process/test.py @@ -0,0 +1,4 @@ +import time +while True: + print("Hello, World!") + time.sleep(1) \ No newline at end of file diff --git a/mod/test/process/test_process.py b/mod/test/process/test_process.py new file mode 100644 index 00000000..e9856c94 --- /dev/null +++ b/mod/test/process/test_process.py @@ -0,0 +1,105 @@ +import unittest +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") +from mod.base import RealProcess + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public + +real_process = RealProcess() + + +class TestRealProcess(unittest.TestCase): + + def test_get_process_list(self): + res = real_process.get_process_list() + self.assertEqual(res['code'], 1) + + def test_get_process_info_by_pid(self): + res = real_process.get_process_info_by_pid(1) + self.assertEqual(res['code'], 1) + + def test_get_process_info_by_name(self): + res = real_process.get_process_info_by_name('system') + self.assertEqual(res['code'], 1) + + def test_get_process_info_by_exec(self): + res = real_process.get_process_info_by_exec('/usr/sbin/sshd') + self.assertEqual(res['code'], 1) + + def test_get_process_info_by_port(self): + res = real_process.get_process_info_by_port(22) + self.assertEqual(res['code'], 1) + + def test_get_process_info_by_ip(self): + res = real_process.get_process_info_by_ip('192.168.168.66') + self.assertEqual(res['code'], 1) + + def test_get_process_info_by_openfile(self): + res = real_process.get_process_info_by_openfile('/etc/passwd') + self.assertEqual(res['code'], 1) + + def test_get_process_ps(self): + res = real_process.get_process_ps('grep') + self.assertEqual(res['code'], 1) + + def test_get_process_tree(self): + res = real_process.get_process_tree(1) + self.assertEqual(res['code'], 1) + + # def test_kill_pid(self): + # try: + # pid = real_process.get_process_info_by_exec('/www/server/panel/mod/test/process/test.py')['data'][0]['pid'] + # res = real_process.kill_pid(pid) + # self.assertEqual(res['code'], 1) + # except: + # pass + + # def test_kill_name(self): + # try: + # name = real_process.get_process_info_by_exec('/www/server/panel/mod/test/process/test.py')['data'][0]['name'] + # res = real_process.kill_name(name) + # self.assertEqual(res['code'], 1) + # except: + # pass + # + # def test_kill_tree(self): + # try: + # pid = real_process.get_process_info_by_exec('/www/server/panel/mod/test/process/test.py')['data'][0]['pid'] + # res = real_process.kill_tree(pid) + # self.assertEqual(res['code'], 1) + # except: + # pass + # + # def test_kill_proc_all(self): + # pid = real_process.get_process_info_by_exec('/www/server/panel/mod/test/process/test.py')['data'][0]['pid'] + # res = real_process.kill_proc_all(pid) + # self.assertEqual(res['code'], 1) + # + # def test_kill_port(self): + # res = real_process.kill_port(22) + # self.assertEqual(res['code'], 1) + + def test_add_black_ip(self): + res = real_process.add_black_ip(['1.2.3.4']) + self.assertEqual(res['code'], 1) + + def test_del_black_ip(self): + res = real_process.del_black_ip(['1.2.3.4']) + self.assertEqual(res['code'], 1) + + def test_firewall_reload(self): + res = real_process.firewall_reload() + self.assertEqual(res['code'], 1) + + def test_get_run_list(self): + res = real_process.get_run_list() + self.assertEqual(res['code'], 1) + + + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/process/test_server.py b/mod/test/process/test_server.py new file mode 100644 index 00000000..76eb47ca --- /dev/null +++ b/mod/test/process/test_server.py @@ -0,0 +1,69 @@ +import unittest +import sys +import time + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +from mod.base import RealServer + +realserver = RealServer() + + +class TestRealServer(unittest.TestCase): + def test_server_admin(self): + res = realserver.server_admin('httpd', 'stop') + self.assertEqual(res['code'], 1) + res = realserver.server_admin('httpd', 'start') + self.assertEqual(res['code'], 1) + + def test_server_status(self): + res = realserver.server_status('httpd') + self.assertEqual(res['code'], 1) + + def test_add_boot(self): + server_name = 'swwnb' + pidfile = '/tmp/test.pl' + start_exec = 'btpython /root/1.py' + stop_exec = 'kill /time/111.log', int(time.time()) + 3000) + self.assertEqual(res['code'], 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/process/test_user.py b/mod/test/process/test_user.py new file mode 100644 index 00000000..f3818551 --- /dev/null +++ b/mod/test/process/test_user.py @@ -0,0 +1,149 @@ +import unittest +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +from mod.base import RealUser + +realuser = RealUser() + + +class TestRealUser(unittest.TestCase): + def setUp(self): + realuser.add_user('test1', 'test1', 'test1') + realuser.add_group('test') + + def tearDown(self): + realuser.remove_user('test1') + realuser.remove_user('test') + realuser.remove_group('test1') + realuser.remove_group('test') + + def test_get_user_list(self): + print('get_user_list:') + res = realuser.get_user_list() + print(res) + self.assertEqual(res['code'], 1) + + def test_get_group_list(self): + print('get_group_list:') + res = realuser.get_group_list() + print(res) + self.assertEqual(res['code'], 1) + + def test_add_user(self): + print('add_user:') + res = realuser.add_user('test', 'test', 'test') + print(res) + self.assertEqual(res['code'], 1) + + def test_remove_user(self): + print('remove_user:') + res = realuser.remove_user('test1') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_pwd(self): + print('edit_user_pwd:') + res = realuser.edit_user_pwd('test1', 'test111') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_group(self): + print('edit_user_group:') + res = realuser.edit_user_group('test1', 'test') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_ps(self): + print('edit_user_ps:') + res = realuser.edit_user_ps('test1', 'test') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_status(self): + print('edit_user_status:') + res = realuser.edit_user_status('test1', '0') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_login_shell(self): + print('edit_user_login_shell:') + res = realuser.edit_user_login_shell('test1', '/bin/bash') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_home(self): + print('edit_user_home:') + res = realuser.edit_user_home('test1', '/home/test1') + print(res) + self.assertEqual(res['code'], 1) + + def test_get_user_info(self): + print('get_user_info:') + res = realuser.get_user_info('test1') + print(res) + self.assertEqual(res['code'], 1) + + def test_add_group(self): + print('add_group:') + res = realuser.add_group('test2') + print(res) + realuser.remove_group('test2') + self.assertEqual(res['code'], 1) + + def test_remove_group(self): + realuser.add_group('test') + print('remove_group:') + res = realuser.remove_group('test') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_group_name(self): + print('edit_group_name:') + res = realuser.edit_group_name('test', 'test5') + print(res) + print(realuser.remove_group('test5')) + self.assertEqual(res['code'], 1) + + def test_get_group_info(self): + print('get_group_info:') + res = realuser.get_group_info('test1') + print(res) + self.assertEqual(res['code'], 1) + + def test_get_group_user(self): + print('get_group_user:') + res = realuser.get_group_user('test1') + print(res) + self.assertEqual(res['code'], 1) + + def test_get_user_group(self): + print('get_user_group:') + res = realuser.get_user_group('test1') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_permission(self): + print('edit_user_permission:') + res = realuser.edit_user_permission('test1', '777') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_group_permission(self): + print('edit_group_permission:') + res = realuser.edit_group_permission('test1', '777') + print(res) + self.assertEqual(res['code'], 1) + + def test_edit_user_name(self): + print('edit_user_name:') + res = realuser.edit_user_name('test1', 'test') + print(res) + self.assertEqual(res['code'], 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/mod/test/test_backup_tool.py b/mod/test/test_backup_tool.py new file mode 100644 index 00000000..7760039e --- /dev/null +++ b/mod/test/test_backup_tool.py @@ -0,0 +1,35 @@ +import os +import sys +import time +from unittest import TestCase + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + + +from mod.base.backup_tool import BackupTool, DB + + +class TestBackupTool(TestCase): + + def test_backup(self): + src = "/www/wwwroot/aaa.test.com" + sub_dir = "site/aaa.test.com" + + site_info = DB("sites").where("name= ?", ("aaa.test.com", )).find() + print(site_info) + print(BackupTool().backup(src, sub_dir=sub_dir, sync=False, site_info=site_info)) + time.sleep(2) # 等待执行完成 + # print(BackupTool().backup(src, sub_dir=sub_dir, sync=True, site_info=site_info)) + + print(os.listdir(BackupTool().backup_path + "/" + sub_dir)) + + def runTest(self): + self.test_backup() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestBackupTool()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_database_tool.py b/mod/test/test_database_tool.py new file mode 100644 index 00000000..435070f5 --- /dev/null +++ b/mod/test/test_database_tool.py @@ -0,0 +1,54 @@ +import os +import sys +from unittest import TestCase + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + + +from mod.base.database_tool import add_database + + +class TestDataBaseTool(TestCase): + + def test_create_data_base(self): + mysql_data = { + "server_id": 0, + "database_name": "aaa", + "db_user": "eee", + "password": "ffff", + "dataAccess": "ip", + "address": "127.0.0.1", + "codeing": "utf8mb4", + "ps": "", + "listen_ip": "0.0.0.0/0", + "host": "", + } + print(add_database(db_type="mysql", data=mysql_data)) + + pgsql_data = { + "server_id": 0, + "database_name": "aaa", + "db_user": "eee", + "password": "ffff", + "ps": "", + "listen_ip": "0.0.0.0/0", + } + print(add_database(db_type="pgsql", data=pgsql_data)) + + mgo_data = { + "server_id": 0, + "database_name": "aaa", + "ps": "", + } + print(add_database(db_type="mongodb", data=mgo_data)) + + def runTest(self): + self.test_create_data_base() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestDataBaseTool()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_git/test_git_tool.py b/mod/test/test_git/test_git_tool.py new file mode 100644 index 00000000..de9e8ce3 --- /dev/null +++ b/mod/test/test_git/test_git_tool.py @@ -0,0 +1,59 @@ +import json +import os +import sys +import time +from unittest import TestCase + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.base.git_tool import GitTool, GitMager +from mod.base.web_conf.util import GET_CLASS + + +class TestGitTool(TestCase): + def runTest(self): + # g = GitTool( + # project_path="/www/test/git_test", + # git_url="http://git.bt.cn/baozi/bt_sync.git", + # user_config={ + # "name": "baozi", + # "password": "swt258452.", + # "email": "1191604998@qq.com", + # } + # ) + # g.pull("master") + os.remove("/www/server/panel/data/site_git_config.json") + + get = GET_CLASS() + get.git_path = "/www/test/git_test" + get.site_name = "git_test" + get.url = "http://git.bt.cn/baozi/bt_sync.git" + get.config = json.dumps({ + "name": "baozi", + "password": "swt258452.", + "email": "1191604998@qq.com", + }) + + gm = GitMager() + print(gm.add_git(get)) + + get = GET_CLASS() + get.site_name = "git_test" + get.refresh = "1" + print(gm.site_git_configure(get)) + + # get = GET_CLASS() + # get.git_path = "/www/test/git_test1" + # get.site_name = "git_test" + # get.git_id = "git_test" + # print(gm.modify_git(get)) + # + # gm.git_pull("master") + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestGitTool()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_version_tool.py b/mod/test/test_version_tool.py new file mode 100644 index 00000000..d1d2f1b7 --- /dev/null +++ b/mod/test/test_version_tool.py @@ -0,0 +1,33 @@ +import os +import shutil +import sys +import time +from unittest import TestCase + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + + +from mod.base.backup_tool import VersionTool + + +class TestVersionTool(TestCase): + + def test_backup(self): + src = "/www/wwwroot/aaa.test.com" + v = VersionTool() + print(v.publish("aaa", src, "1.0.0", sync=False)) + print(v.version_list("aaa")) + time.sleep(2) # 等待执行完成 + # print(BackupTool().backup(src, sub_dir=sub_dir, sync=True, site_info=site_info)) + v.recover('aaa', '1.0.0', src) + + def runTest(self): + self.test_backup() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestVersionTool()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/__init__.py b/mod/test/test_web_conf/__init__.py new file mode 100644 index 00000000..e544f9a6 --- /dev/null +++ b/mod/test/test_web_conf/__init__.py @@ -0,0 +1,190 @@ +import os +import sys +from unittest import TestCase + +SITE_NAME_CASE = "aaa.test.com" +SITE_PATH = "/www/wwwroot/aaa.test.com" +SUB_SITE_PATH = SITE_PATH + "/test_run" +if not os.path.exists(SUB_SITE_PATH): + os.makedirs(SUB_SITE_PATH) +VHOST_PATH = "/www/server/panel/vhost" +PREFIX = "" +NGINX_CONFIG_FILE = "{}/nginx/{}{}.conf".format(VHOST_PATH, PREFIX, SITE_NAME_CASE) +APACHE_CONFIG_FILE = "{}/apache/{}{}.conf".format(VHOST_PATH, PREFIX, SITE_NAME_CASE) +NGINX_CONFIG_CASE = r"""server +{ + listen 80; + server_name aaa.test.com; + index index.php index.html index.htm default.php default.html default.htm; + root /www/wwwroot/aaa.test.com; + #CERT-APPLY-CHECK--START + # 用于SSL证书申请时的文件验证相关配置 -- 请勿删除 + include /www/server/panel/vhost/nginx/well-known/aaa.test.com.conf; + #CERT-APPLY-CHECK--END + + #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则 + #error_page 404/404.html; + #SSL-END + + #ERROR-PAGE-START 错误页配置,可以注释、删除或修改 + #error_page 404 /404.html; + #error_page 502 /502.html; + #ERROR-PAGE-END + + #PHP-INFO-START PHP引用配置,可以注释或修改 + include enable-php-00.conf; + #PHP-INFO-END + + #REWRITE-START URL重写规则引用,修改后将导致面板设置的伪静态规则失效 + include /www/server/panel/vhost/rewrite/aaa.test.com.conf; + #REWRITE-END + + #禁止访问的文件或目录 + location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) + { + return 404; + } + + #一键申请SSL证书验证目录相关设置 + location ~ \.well-known{ + allow all; + } + + #禁止在证书验证目录放入敏感文件 + if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) { + return 403; + } + + location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ + { + expires 30d; + error_log /dev/null; + access_log /dev/null; + } + + location ~ .*\.(js|css)?$ + { + expires 12h; + error_log /dev/null; + access_log /dev/null; + } + access_log /www/wwwlogs/aaa.test.com.log; + error_log /www/wwwlogs/aaa.test.com.error.log; +}""" + +# access_log /www/wwwlogs/aaa.test.com.log; + +APACHE_CONFIG_CASE = r""" + ServerAdmin webmaster@example.com + DocumentRoot "/www/wwwroot/aaa.test.com" + ServerName 630e5c70.aaa.test.com + ServerAlias aaa.test.com + #errorDocument 404 /404.html + ErrorLog "/www/wwwlogs/aaa.test.com-error_log" + CustomLog "/www/wwwlogs/aaa.test.com-access_log" combined + + #DENY FILES + + Order allow,deny + Deny from all + + + #PHP + + SetHandler "proxy:unix:/tmp/php-cgi-00.sock|fcgi://localhost" + + + #PATH + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Require all granted + DirectoryIndex index.php index.html index.htm default.php default.html default.htm + +""" + +APACHE_PATH = "/www/server/apache" +NGINX_PATH = "/www/server/nginx" + + +class WebBaseTestcase(TestCase): + + def __init__(self): + super(WebBaseTestcase, self).__init__() + if not os.path.isfile(NGINX_CONFIG_FILE): + with open(NGINX_CONFIG_FILE, "w+") as f: + f.write(NGINX_CONFIG_CASE) + + if not os.path.isfile(APACHE_CONFIG_FILE): + with open(APACHE_CONFIG_FILE, "w+") as f: + f.write(APACHE_CONFIG_CASE) + + self.site_name = SITE_NAME_CASE + + @staticmethod + def reset_site_config(): + with open(NGINX_CONFIG_FILE, "w+") as f: + f.write(NGINX_CONFIG_CASE) + + with open(APACHE_CONFIG_FILE, "w+") as f: + f.write(APACHE_CONFIG_CASE) + + @staticmethod + def change_env_to_apache(): + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + import public + + if os.path.exists(NGINX_PATH): + public.ExecShell("/etc/init.d/nginx stop") + os.rename(NGINX_PATH, NGINX_PATH+"_back") + + if os.path.exists(APACHE_PATH + "_back"): + os.rename(APACHE_PATH + "_back", APACHE_PATH) + + @staticmethod + def change_env_to_nginx(): + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + import public + + if os.path.exists(APACHE_PATH): + public.ExecShell("/etc/init.d/httpd stop") + os.rename(APACHE_PATH, APACHE_PATH + "_back") + + if os.path.exists(NGINX_PATH + "_back"): + os.rename(NGINX_PATH + "_back", NGINX_PATH) + + def check_web_server_config(self): + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + import public + + # nginx + ng_error = "" + if os.path.exists(NGINX_PATH): + shell_str = "ulimit -n 8192; {np}/sbin/nginx -t -c {np}/conf/nginx.conf".format(np=NGINX_PATH) + ng_result = public.ExecShell(shell_str) + if ng_result[1].find("successful") == -1: + ng_error = ng_result[1] + + # apache + ap_error = "" + if os.path.exists(APACHE_PATH): + shell_str = "ulimit -n 8192; {ap}/bin/apachectl -t".format(ap=APACHE_PATH) + ap_result = public.ExecShell(shell_str) + if ap_result[1].find("Syntax OK") == -1: + ap_error = ap_result[1] + + if ng_error: + print(ng_error) + + if ap_error: + print(ap_error) + + if ng_error or ap_error: + self.fail("Failed to execute") diff --git a/mod/test/test_web_conf/test_access_restriction.py b/mod/test/test_web_conf/test_access_restriction.py new file mode 100644 index 00000000..23db8292 --- /dev/null +++ b/mod/test/test_web_conf/test_access_restriction.py @@ -0,0 +1,119 @@ +import os.path +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX +from mod.base.web_conf import AccessRestriction +from mod.base.web_conf.util import GET_CLASS + + +class TestAccessRestriction(WebBaseTestcase): + as_obj = AccessRestriction(PREFIX) + + def test_create_auth_dir(self): + get = GET_CLASS() + get.site_name = self.site_name + get.name = "fshfd" + get.dir_path = "/" + get.password = "ssss" + get.username = "aaaa" + res = self.as_obj.create_auth_dir(get) + self.assertTrue(res["status"], res["msg"]) + + def test_modify_auth_dir(self): + get = GET_CLASS() + get.site_name = self.site_name + get.name = "fshfd" + get.dir_path = "/" + get.password = "ssss" + get.username = "aarrr" + res = self.as_obj.modify_auth_dir(get) + self.assertTrue(res["status"], res["msg"]) + + def test_remove_auth_dir(self): + get = GET_CLASS() + get.site_name = self.site_name + get.name = "fshfd" + res = self.as_obj.remove_auth_dir(get) + self.assertTrue(res["status"], res["msg"]) + + def test_create_file_deny(self): + get = GET_CLASS() + get.site_name = self.site_name + get.name = "fshfd" + get.dir_path = "/" + get.suffix = "[\"txt\"]" + res = self.as_obj.create_file_deny(get) + self.assertTrue(res["status"], res["msg"]) + + def test_modify_file_deny(self): + get = GET_CLASS() + get.site_name = self.site_name + get.name = "fshfd" + get.dir_path = "/" + get.suffix = "[\"ffff\"]" + res = self.as_obj.modify_file_deny(get) + self.assertTrue(res["status"], res["msg"]) + + def test_remove_file_deny(self): + get = GET_CLASS() + get.site_name = self.site_name + get.name = "fshfd" + res = self.as_obj.remove_file_deny(get) + self.assertTrue(res["status"], res["msg"]) + + def test_site_access_restriction_info(self): + get = GET_CLASS() + get.site_name = self.site_name + res = self.as_obj.site_access_restriction_info(get) + self.assertTrue(res["status"], res["msg"]) + print(res["data"]) + + def setUp(self) -> None: + if os.path.exists("/www/server/panel/data/site_access.json"): + os.remove("/www/server/panel/data/site_access.json") + self.reset_site_config() + + def runTest(self): + # self.change_env_to_apache() + + self.change_env_to_nginx() + self.test_create_auth_dir() + self.check_web_server_config() + self.test_create_file_deny() + self.check_web_server_config() + self.test_modify_file_deny() + self.check_web_server_config() + self.test_modify_auth_dir() + self.check_web_server_config() + self.test_site_access_restriction_info() + self.test_remove_auth_dir() + self.test_remove_file_deny() + self.check_web_server_config() + + self.change_env_to_apache() + self.test_create_auth_dir() + self.check_web_server_config() + self.test_create_file_deny() + self.check_web_server_config() + self.test_modify_file_deny() + self.check_web_server_config() + self.test_modify_auth_dir() + self.check_web_server_config() + self.test_site_access_restriction_info() + self.test_remove_auth_dir() + self.test_remove_file_deny() + self.check_web_server_config() + + def tearDown(self): + if os.path.exists("/www/server/panel/data/site_access.json"): + os.remove("/www/server/panel/data/site_access.json") + self.reset_site_config() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestAccessRestriction()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_config_mgr.py b/mod/test/test_web_conf/test_config_mgr.py new file mode 100644 index 00000000..0f5cd05a --- /dev/null +++ b/mod/test/test_web_conf/test_config_mgr.py @@ -0,0 +1,68 @@ + +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf import ConfigMgr + + +class TestConfigMgr(WebBaseTestcase): + + def test_nginx_config(self): + self.assertEqual(self.config_mgr.nginx_config(), NGINX_CONFIG_CASE, "nginx 配置文件读取错误") + + def test_apache_config(self): + self.assertEqual(self.config_mgr.apache_config(), APACHE_CONFIG_CASE, "apache 配置文件读取错误") + + def test_save_nginx_config(self): + self.config_mgr.save_nginx_config(NGINX_CONFIG_CASE + "\n\n") + self.assertEqual(self.config_mgr.nginx_config(), NGINX_CONFIG_CASE + "\n\n", "nginx 配置文件保存错误") + + self.assertIsInstance(self.config_mgr.save_nginx_config("hshdgajdgg"), str, "nginx 配置文件保存错误") + + def test_save_apache_config(self): + self.config_mgr.save_apache_config(APACHE_CONFIG_CASE + "\n\n") + self.assertEqual(self.config_mgr.apache_config(), APACHE_CONFIG_CASE + "\n\n", "apache 配置文件保存错误") + + self.assertIsInstance(self.config_mgr.save_apache_config("hshdgajdgg"), str, "apache 配置文件保存错误") + + def test_history_list(self): + print(self.config_mgr.history_list()) + + def test_history_conf(self): + res = self.config_mgr.history_list() + if len(res["nginx"]) > 0: + print(self.config_mgr.history_conf(res["nginx"][0])) + + if len(res["apache"]) > 0: + print(self.config_mgr.history_conf(res["apache"][0])) + + def setUp(self) -> None: + self.reset_site_config() + self.config_mgr = ConfigMgr(self.site_name, PREFIX) + + def runTest(self): + self.change_env_to_nginx() + self.test_nginx_config() + self.test_save_nginx_config() + self.test_history_list() + self.test_history_conf() + self.check_web_server_config() + + self.change_env_to_apache() + self.test_apache_config() + self.test_save_apache_config() + self.test_history_list() + self.test_history_conf() + self.check_web_server_config() + + def tearDown(self): + self.reset_site_config() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestConfigMgr()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_default_site.py b/mod/test/test_web_conf/test_default_site.py new file mode 100644 index 00000000..bec090c1 --- /dev/null +++ b/mod/test/test_web_conf/test_default_site.py @@ -0,0 +1,66 @@ +import os +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX +from mod.base.web_conf import get_default_site, set_default_site, check_default + + +class TestDefaultSite(WebBaseTestcase): + def test_check_default(self): + vhost_path = "/www/server/panel/vhost" + nginx = vhost_path + '/nginx' + httpd = vhost_path + '/apache' + check_default() + self.assertTrue(os.path.exists(httpd + '/0.default.conf')) + self.assertTrue(os.path.exists(nginx + '/0.default.conf')) + + def test_get_default_site(self): + self.assertEqual(get_default_site(), (self.site_name, PREFIX), "设置默认站点错误") + + def test_set_default_site(self): + set_default_site(site_name=self.site_name, prefix=PREFIX, domain=self.site_name) + + def setUp(self) -> None: + self.reset_site_config() + vhost_path = "/www/server/panel/vhost" + nginx = vhost_path + '/nginx' + httpd = vhost_path + '/apache' + + if os.path.exists(httpd + '/0.default.conf'): + os.remove(httpd + '/0.default.conf') + if os.path.exists(httpd + '/default.conf'): + os.remove(httpd + '/default.conf') + + if os.path.exists(nginx + '/0.default.conf'): + os.remove(nginx + '/0.default.conf') + if os.path.exists(nginx + '/default.conf'): + os.remove(nginx + '/default.conf') + + panel_path = "/www/server/panel" + new_ds_file = panel_path + "/data/mod_default_site.pl" + if os.path.exists(new_ds_file): + os.remove(new_ds_file) + + def runTest(self): + self.test_check_default() + self.test_set_default_site() + self.test_get_default_site() + + self.change_env_to_nginx() + self.check_web_server_config() + + self.change_env_to_apache() + self.check_web_server_config() + + def tearDown(self): + self.reset_site_config() + set_default_site(None) + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestDefaultSite()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_dir_tool.py b/mod/test/test_web_conf/test_dir_tool.py new file mode 100644 index 00000000..8c5274ef --- /dev/null +++ b/mod/test/test_web_conf/test_dir_tool.py @@ -0,0 +1,95 @@ +import os +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, SITE_PATH, SUB_SITE_PATH +from mod.base.web_conf import DirTool +from mod.base.web_conf.util import DB, write_file + + +class TestDirTool(WebBaseTestcase): + dir_tool = DirTool(PREFIX) + + def reset_site_config(self) -> None: + super(TestDirTool, self).reset_site_config() + + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + import public + + if os.path.exists(SUB_SITE_PATH + "/.user.ini"): + public.ExecShell("chattr -i " + SUB_SITE_PATH + "/.user.ini") + os.remove(SUB_SITE_PATH + "/.user.ini") + + if not os.path.exists(SITE_PATH + "/.user.ini"): + write_file(SITE_PATH + "/.user.ini", "open_basedir=/www/wwwroot/aaa.test.com/:/tmp/") + + site_info = DB("sites").where("name=?", (self.site_name,)).find() + DB("sites").where("id=?", (site_info["id"],)).setField('path', SITE_PATH) + + def test_modify_site_path(self): + res = self.dir_tool.modify_site_path(self.site_name, SITE_PATH, SUB_SITE_PATH) + self.assertIsNone(res, "修改路径失败") + self.assertTrue(os.path.exists(SUB_SITE_PATH + "/.user.ini"), ".user.ini错误") + self.assertEqual(DB("sites").where("name=?", (self.site_name,)).find()["path"], SUB_SITE_PATH, "数据库错误") + self.reset_site_config() + + def test_modify_site_run_path(self): + self.dir_tool.modify_site_run_path(self.site_name, SITE_PATH, "/test_run") + self.assertEqual(self.dir_tool.get_site_run_path(self.site_name), SUB_SITE_PATH, "修改运行目录失败") + self.assertTrue(os.path.exists(SUB_SITE_PATH + "/.user.ini"), ".user.ini错误") + self.reset_site_config() + + def test_index_conf(self): + self.assertCountEqual(self.dir_tool.get_index_conf(self.site_name), + ["index.php", "index.html", "index.htm", "default.php", "default.html", "default.htm"], + "获取index信息错误") + self.dir_tool.set_index_conf(self.site_name, "index.php", "default.php", "default.html", "default.htm") + self.assertCountEqual(self.dir_tool.get_index_conf(self.site_name), + ["index.php", "default.php", "default.html", "default.htm"], + "设置index信息错误") + + self.reset_site_config() + + def setUp(self) -> None: + self.reset_site_config() + + def runTest(self): + # 测试改变path + # self.change_env_to_nginx() + # self.test_modify_site_path() + # self.check_web_server_config() + # + # self.change_env_to_apache() + # self.test_modify_site_path() + # self.check_web_server_config() + + # 测试改变run_path + # self.change_env_to_nginx() + # self.test_modify_site_run_path() + # self.check_web_server_config() + # + # self.change_env_to_apache() + # self.test_modify_site_run_path() + # self.check_web_server_config() + + self.change_env_to_nginx() + self.test_index_conf() + self.check_web_server_config() + + self.change_env_to_apache() + self.test_index_conf() + self.check_web_server_config() + + def tearDown(self): + self.reset_site_config() + + +if __name__ == '__main__': + import unittest + + s = unittest.TestSuite() + s.addTest(TestDirTool()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_dns_api.py b/mod/test/test_web_conf/test_dns_api.py new file mode 100644 index 00000000..808c8d86 --- /dev/null +++ b/mod/test/test_web_conf/test_dns_api.py @@ -0,0 +1 @@ +# dns_api 部分来自dns集中管理插件, 未做单元测试 diff --git a/mod/test/test_web_conf/test_domain_tool.py b/mod/test/test_web_conf/test_domain_tool.py new file mode 100644 index 00000000..3aae17de --- /dev/null +++ b/mod/test/test_web_conf/test_domain_tool.py @@ -0,0 +1,68 @@ +import os +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, SITE_PATH, SUB_SITE_PATH +from mod.base.web_conf import normalize_domain, NginxDomainTool, ApacheDomainTool, ConfigMgr, set_default_site +from mod.base.web_conf.util import DB, write_file, read_file + + +class DomainToolTest(WebBaseTestcase): + + def test_normalize_domain(self): + res, err = normalize_domain("www.example.com", "www.example.com:2546545", "www.example.com:4545") + self.assertCountEqual(res, [("www.example.com", "80"), ("www.example.com", "4545")]) + print(err) + + def test_nginx_domain_tool(self): + ngd_tool = NginxDomainTool(PREFIX) + # 检查default_site 与 域名设置 + set_default_site(self.site_name) + res, _ = normalize_domain("www.example.com", "www.example.com:2546545", "www.example.com:4545") + res = ngd_tool.nginx_set_domain(self.site_name, (self.site_name, "80"), *res) + self.assertEqual(res, None, "设置域名出错") + + conf_mager = ConfigMgr(self.site_name, PREFIX) + conf = conf_mager.nginx_config() + self.assertIn("www.example.com", conf) + self.assertIn("4545", conf) + print(conf) + + def test_apache_domain_tool(self): + ngd_tool = ApacheDomainTool(PREFIX) + + res, _ = normalize_domain("www.example.com", "www.example.com:2546545", "www.example.com:4545") + res = ngd_tool.apache_set_domain(self.site_name, (self.site_name, "80"), *res) + self.assertEqual(res, None, "设置域名出错") + + conf_mager = ConfigMgr(self.site_name, PREFIX) + conf = conf_mager.apache_config() + self.assertIn("www.example.com", conf) + self.assertIn("4545", conf) + print(conf) + + def setUp(self) -> None: + self.reset_site_config() + + def runTest(self): + self.test_normalize_domain() + + # self.change_env_to_nginx() + # self.test_nginx_domain_tool() + # self.check_web_server_config() + + self.change_env_to_apache() + self.test_apache_domain_tool() + self.check_web_server_config() + + def tearDown(self): + self.reset_site_config() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(DomainToolTest()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_ip_restrict.py b/mod/test/test_web_conf/test_ip_restrict.py new file mode 100644 index 00000000..53d36ad8 --- /dev/null +++ b/mod/test/test_web_conf/test_ip_restrict.py @@ -0,0 +1,123 @@ +import os.path +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf.util import GET_CLASS, service_reload +from mod.base.web_conf import IpRestrict, ConfigMgr + + +class TestIpRestrict(WebBaseTestcase): + ip_restrict = IpRestrict(PREFIX) + + def setUp(self) -> None: + self.reset_site_config() + setup_path = "/www/server/panel" + ip_restrict_conf = "{}/data/ip_restrict_data/{}{}".format(setup_path, PREFIX, self.site_name) + if os.path.exists(ip_restrict_conf): + os.remove(ip_restrict_conf) + nginx_ip_restrict_conf = "{}/vhost/ip-restrict/{}{}.conf".format(setup_path, PREFIX, self.site_name) + if os.path.exists(nginx_ip_restrict_conf): + os.remove(nginx_ip_restrict_conf) + + def test_black_ip_restrict(self): + # 设置为黑名单格式 + get = GET_CLASS() + get.site_name = self.site_name + # black white closed + get.set_type = "black" + self.ip_restrict.set_ip_restrict(get) + + # 添加黑名单信息 + get = GET_CLASS() + get.site_name = self.site_name + get.value = "192.168.168.65" + self.ip_restrict.add_black_ip_restrict(get) + + config_mgr = ConfigMgr(self.site_name, PREFIX) + self.assertIn("ip-restrict", config_mgr.nginx_config()) + setup_path = "/www/server/panel" + ip_restrict_conf = "{}/data/ip_restrict_data/{}{}".format(setup_path, PREFIX, self.site_name) + nginx_ip_restrict_conf = "{}/vhost/ip-restrict/{}{}.conf".format(setup_path, PREFIX, self.site_name) + + self.assertTrue(os.path.exists(ip_restrict_conf)) + self.assertTrue(os.path.exists(nginx_ip_restrict_conf)) + + # 移除黑名单信息 + # get = GET_CLASS() + # get.site_name = self.site_name + # get.value = "192.168.168.65" + # self.ip_restrict.remove_black_ip_restrict(get) + + def test_white_ip_restrict(self): + # 设置为白名单格式 + get = GET_CLASS() + get.site_name = self.site_name + # black white closed + get.set_type = "white" + self.ip_restrict.set_ip_restrict(get) + + # 添加白名单信息 + get = GET_CLASS() + get.site_name = self.site_name + get.value = "192.168.168.65" # "192.168.168.66" + self.ip_restrict.add_white_ip_restrict(get) + + config_mgr = ConfigMgr(self.site_name, PREFIX) + self.assertIn("ip-restrict", config_mgr.nginx_config()) + setup_path = "/www/server/panel" + ip_restrict_conf = "{}/data/ip_restrict_data/{}{}".format(setup_path, PREFIX, self.site_name) + nginx_ip_restrict_conf = "{}/vhost/ip-restrict/{}{}.conf".format(setup_path, PREFIX, self.site_name) + + self.assertTrue(os.path.exists(ip_restrict_conf)) + self.assertTrue(os.path.exists(nginx_ip_restrict_conf)) + + # 移除白名单信息 + get = GET_CLASS() + get.site_name = self.site_name + get.value = "192.168.168.65" + self.ip_restrict.remove_white_ip_restrict(get) + + def test_ip_restrict_conf(self): + # 添加黑名单信息 + get = GET_CLASS() + get.site_name = self.site_name + get.value = "192.168.168.65" + self.ip_restrict.add_black_ip_restrict(get) + + # 设置为白单格式 + get = GET_CLASS() + get.site_name = self.site_name + # black white closed + get.set_type = "white" + self.ip_restrict.set_ip_restrict(get) + # 添加白名单信息 + get = GET_CLASS() + get.site_name = self.site_name + get.value = "192.168.168.65" # "192.168.168.66" + self.ip_restrict.add_white_ip_restrict(get) + + get = GET_CLASS() + get.site_name = self.site_name + print(self.ip_restrict.restrict_conf(get)) + + def runTest(self): + # self.change_env_to_nginx() + # self.test_black_ip_restrict() + # self.check_web_server_config() + + # self.change_env_to_nginx() + # self.test_white_ip_restrict() + # self.check_web_server_config() + + self.change_env_to_nginx() + self.test_ip_restrict_conf() + self.check_web_server_config() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestIpRestrict()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_logmanager.py b/mod/test/test_web_conf/test_logmanager.py new file mode 100644 index 00000000..aa437a65 --- /dev/null +++ b/mod/test/test_web_conf/test_logmanager.py @@ -0,0 +1,159 @@ +import json +import os.path +import shutil +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf import ConfigMgr, LogMgr +from mod.base.web_conf.util import GET_CLASS + +LOG_FORMAT_1 = json.dumps([ + "server_addr", "server_port", "host", "remote_addr", "remote_port", "protocol", "method", "uri", + "status", "sent_bytes", "referer", "user_agent", "take_time" +]) +LOG_FORMAT_2 = json.dumps([ + "server_addr", "host", "remote_addr", "remote_port", "protocol", "method", "uri", + "status", "user_agent", "take_time" +]) + + +class TestRealLogMgr(WebBaseTestcase): + log_mgr = LogMgr(PREFIX) + + def test_log_format_mager(self): + # 查看初始 + get = GET_CLASS() + get.site_name = self.site_name + print(self.log_mgr.log_format_data(get)) + + # 添加 一个格式 + get = GET_CLASS() + get.format_name = "btlog1" + get.keys = LOG_FORMAT_1 + get.space_character = "|" + print(self.log_mgr.add_log_format(get)) + + # 添加 第二个格式 + get = GET_CLASS() + get.format_name = "btlog2" + get.keys = LOG_FORMAT_2 + get.space_character = " " + print(self.log_mgr.add_log_format(get)) + + # 修改 第二个格式 + get = GET_CLASS() + get.format_name = "btlog2" + get.keys = LOG_FORMAT_2 + get.space_character = "|" + print(self.log_mgr.modify_log_format(get)) + + # 删除 第一个格式 + # get = GET_CLASS() + # get.format_name = "btlog1" + # print(self.log_mgr.remove_log_format(get)) + + # 查看格式 + get = GET_CLASS() + get.site_name = self.site_name + print(self.log_mgr.log_format_data(get)) + + def test_set_site_log_format(self): + # 设置使用 第二个 + get = GET_CLASS() + get.site_name = self.site_name + get.format_name = "btlog2" + print(self.log_mgr.set_site_log_format(get)) + + # 修改 第二个格式 + get = GET_CLASS() + get.format_name = "btlog2" + get.keys = LOG_FORMAT_2 + get.space_character = "|" + print(self.log_mgr.modify_log_format(get)) + + # 设置使用 第一个 + get = GET_CLASS() + get.site_name = self.site_name + get.format_name = "btlog1" + print(self.log_mgr.set_site_log_format(get)) + + # 查看格式 + get = GET_CLASS() + get.site_name = self.site_name + print(self.log_mgr.log_format_data(get)) + + def test_site_log_path(self): + # 查看初始 + get = GET_CLASS() + get.site_name = self.site_name + print(self.log_mgr.get_site_log_path(get)) + + # 修改日志路径 + get = GET_CLASS() + get.site_name = self.site_name + get.log_path = "/www/test/logs" + print(self.log_mgr.set_site_log_path(get)) + + def test_site_crontab_log(self): + get = GET_CLASS() + get.site_name = self.site_name + get.hour = "1" + get.minute = "1" + get.save = "180" + print(self.log_mgr.site_crontab_log(get)) + + def setUp(self) -> None: + self.reset_site_config() + self.config_mgr = ConfigMgr(self.site_name, PREFIX) + panel_path = "/www/server/panel" + if os.path.exists("{}/data/ng_log_format.json".format(panel_path)): + os.remove("{}/data/ng_log_format.json".format(panel_path)) + + if os.path.exists("{}/vhost/nginx/log_format".format(panel_path)): + shutil.rmtree("{}/vhost/nginx/log_format".format(panel_path)) + + if os.path.exists("{}/data/ap_log_format.json".format(panel_path)): + os.remove("{}/data/ap_log_format.json".format(panel_path)) + + if os.path.exists("{}/vhost/apache/log_format".format(panel_path)): + shutil.rmtree("{}/vhost/apache/log_format".format(panel_path)) + + def runTest(self): + # self.change_env_to_nginx() + # self.test_log_format_mager() + # print("==================================") + # self.test_set_site_log_format() + # self.check_web_server_config() + + # self.change_env_to_apache() + # self.test_log_format_mager() + # print("==================================") + # self.test_set_site_log_format() + # self.check_web_server_config() + + # self.change_env_to_nginx() + # self.log_mgr = LogMgr(PREFIX) + # self.test_site_log_path() + # self.check_web_server_config() + + # self.change_env_to_apache() + # self.log_mgr = LogMgr(PREFIX) + # self.test_site_log_path() + # self.check_web_server_config() + + self.test_site_crontab_log() + + def tearDown(self): + pass + self.reset_site_config() + + +if __name__ == '__main__': + import unittest + + s = unittest.TestSuite() + s.addTest(TestRealLogMgr()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_proxy.py b/mod/test/test_web_conf/test_proxy.py new file mode 100644 index 00000000..d97cf623 --- /dev/null +++ b/mod/test/test_web_conf/test_proxy.py @@ -0,0 +1,107 @@ + +import os +import shutil +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf import ConfigMgr, Proxy +from mod.base.web_conf.util import GET_CLASS + + +class TestProxy(WebBaseTestcase): + proxy_obj = Proxy(PREFIX) + + def test_create_project_proxy(self): + get = GET_CLASS() + get.proxyname = "aaa" + get.sitename = self.site_name + get.proxydir = "/" + get.proxysite = "https://www.baidu.com" + get.todomain = "www.baidu.com" + get.type = "1" + get.cache = "1" + get.subfilter = '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]' + get.advanced = "1" + get.cachetime = "1" + print(self.proxy_obj.create_proxy(get)) + + # get = GET_CLASS() + # get.proxyname = "ggfff" + # get.sitename = self.site_name + # get.proxydir = "/dad" + # get.proxysite = "https://www.baidu.com" + # get.todomain = "www.baidu.com" + # get.type = "1" + # get.cache = "1" + # get.subfilter = '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]' + # get.advanced = "1" + # get.cachetime = "1" + # print(self.proxy_obj.create_proxy(get)) + + def test_modify_project_proxy(self): + get = GET_CLASS() + get.proxyname = "ggfff" + get.sitename = self.site_name + get.proxydir = "/dygvccc" + get.proxysite = "https://www.baidu.com" + get.todomain = "www.baidu.com" + get.type = "1" + get.cache = "1" + get.subfilter = '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]' + get.advanced = "0" + get.cachetime = "1" + print(self.proxy_obj.modify_proxy(get)) + + def test_remove_project_proxy(self): + get = GET_CLASS() + get.proxyname = "aaa" + get.sitename = self.site_name + print(self.proxy_obj.remove_proxy(get)) + + def test_get_project_proxy_list(self): + get = GET_CLASS() + get.sitename = self.site_name + print(self.proxy_obj.get_proxy_list(get)) + + def setUp(self) -> None: + self.reset_site_config() + self.config_mgr = ConfigMgr(self.site_name, PREFIX) + panel_path = "/www/server/panel" + _proxy_conf_file = "{}/data/mod_proxy_file.conf".format(panel_path) + if os.path.exists(_proxy_conf_file): + os.remove(_proxy_conf_file) + + ng_proxy_dir = "/www/server/panel/vhost/nginx/proxy/" + self.site_name + ap_proxy_dir = "/www/server/panel/vhost/apache/proxy/" + self.site_name + if os.path.exists(ng_proxy_dir): + shutil.rmtree(ng_proxy_dir) + + if os.path.exists(ap_proxy_dir): + shutil.rmtree(ap_proxy_dir) + + def runTest(self): + # self.change_env_to_nginx() + # self.test_create_project_proxy() + # self.test_remove_project_proxy() + # self.check_web_server_config() + + self.change_env_to_apache() + self.test_create_project_proxy() + self.test_remove_project_proxy() + self.test_modify_project_proxy() + self.test_get_project_proxy_list() + self.check_web_server_config() + + # def tearDown(self): + # self.reset_site_config() + + +if __name__ == '__main__': + import unittest + + s = unittest.TestSuite() + s.addTest(TestProxy()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_redirect.py b/mod/test/test_web_conf/test_redirect.py new file mode 100644 index 00000000..35579d3b --- /dev/null +++ b/mod/test/test_web_conf/test_redirect.py @@ -0,0 +1,87 @@ +import json +import os +import shutil +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf import ConfigMgr, Redirect +from mod.base.web_conf.util import GET_CLASS + + +class TestRedirect(WebBaseTestcase): + redirect = Redirect() + + def test_create_project_redirect(self): + get = GET_CLASS() + get.sitename = self.site_name + get.redirectpath = "/" + get.redirecttype = "301" + get.domainorpath = "path" + get.redirectname = "aaa" + get.tourl = "" + get.topath = "/ashdjadg" + get.redirectdomain = "[]" + get.type = "1" + get.errorpage = "0" + get.holdpath = "1" + print(self.redirect.create_project_redirect(get)) + + def test_modify_project_redirect(self): + get = GET_CLASS() + get.sitename = self.site_name + get.redirectpath = "/" + get.redirecttype = "301" + get.domainorpath = "domain" + get.redirectname = "aaa" + get.tourl = "https://www.baidu.com" + get.topath = "" + get.redirectdomain = json.dumps([self.site_name]) + get.type = "1" + get.errorpage = "0" + get.holdpath = "1" + print(self.redirect.modify_project_redirect(get)) + + def test_remove_project_redirect(self): + get = GET_CLASS() + get.sitename = self.site_name + get.redirectname = "aaa" + print(self.redirect.remove_project_redirect(get)) + + def test_get_project_redirect_list(self): + get = GET_CLASS() + get.sitename = self.site_name + print(self.redirect.get_project_redirect_list(get)) + + def setUp(self) -> None: + self.reset_site_config() + self.config_mgr = ConfigMgr(self.site_name, PREFIX) + + def runTest(self): + # self.change_env_to_nginx() + # self.test_create_project_redirect() + # self.test_modify_project_redirect() + # self.test_get_project_redirect_list() + # self.test_remove_project_redirect() + # self.check_web_server_config() + # + self.change_env_to_apache() + self.test_create_project_redirect() + self.test_modify_project_redirect() + self.test_get_project_redirect_list() + self.test_remove_project_redirect() + self.check_web_server_config() + + # def tearDown(self): + # pass + # self.reset_site_config() + + +if __name__ == '__main__': + import unittest + + s = unittest.TestSuite() + s.addTest(TestRedirect()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_referer.py b/mod/test/test_web_conf/test_referer.py new file mode 100644 index 00000000..8478a070 --- /dev/null +++ b/mod/test/test_web_conf/test_referer.py @@ -0,0 +1,70 @@ +import json +import os +import shutil +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf import ConfigMgr, Referer +from mod.base.web_conf.util import GET_CLASS + + +class TestReferer(WebBaseTestcase): + referer = Referer(PREFIX) + + def test_referer_security(self): + # 开启 + get = GET_CLASS() + get.status = "true" + get.http_status = "false" + get.name = self.site_name + get.fix = "fsf,dhjdh,uooo" + get.domains = self.site_name + ",www.asdad.com" + get.return_rule = "403" + print(self.referer.set_referer_security(get)) + + # 修改 + get = GET_CLASS() + get.status = "true" + get.http_status = "false" + get.name = self.site_name + get.fix = "fsf,dhjdh,hjhlh" + get.domains = self.site_name + ",www.asdad.com" + get.return_rule = "404" + print(self.referer.set_referer_security(get)) + + # 删除 + get = GET_CLASS() + get.status = "false" + get.http_status = "false" + get.name = self.site_name + get.fix = "fsf,dhjdh,hjhlh" + get.domains = self.site_name + ",www.asdad.com" + get.return_rule = "404" + print(self.referer.set_referer_security(get)) + + get = GET_CLASS() + get.site_name = self.site_name + print(self.referer.get_referer_security(get)) + + def setUp(self) -> None: + self.reset_site_config() + self.config_mgr = ConfigMgr(self.site_name, PREFIX) + + def runTest(self): + self.change_env_to_nginx() + self.test_referer_security() + self.check_web_server_config() + + # def tearDown(self): + # pass + # self.reset_site_config() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestReferer()) + unittest.TextTestRunner().run(s) diff --git a/mod/test/test_web_conf/test_ssl.py b/mod/test/test_web_conf/test_ssl.py new file mode 100644 index 00000000..0e841a7a --- /dev/null +++ b/mod/test/test_web_conf/test_ssl.py @@ -0,0 +1,63 @@ +import json +import os +import shutil +import sys + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +from mod.test.test_web_conf import WebBaseTestcase, PREFIX, NGINX_CONFIG_CASE, APACHE_CONFIG_CASE +from mod.base.web_conf import SSLManager, set_default_site +from mod.base.web_conf.util import GET_CLASS + + +class TestSSLManager(WebBaseTestcase): + ssl = SSLManager(PREFIX) + + def test_set_site_ssl_conf(self): + # 开启 + get = GET_CLASS() + get.ssl_id = "2" # 保证这个ID存在 + get.site_name = self.site_name + print(self.ssl.set_site_ssl_conf(get)) + + def test_mutil_set_site_ssl_conf(self): + # 开启 + get = GET_CLASS() + get.ssl_id = "9" # 保证这个ID存在 + get.site_names = json.dumps([self.site_name, "www.123test.com"]) + print(self.ssl.mutil_set_site_ssl_conf(get)) + + def test_close_site_ssl_conf(self): + # 关闭 + get = GET_CLASS() + get.site_name = self.site_name + print(self.ssl.close_site_ssl_conf(get)) + + def setUp(self) -> None: + self.reset_site_config() + + def runTest(self): + set_default_site(site_name=self.site_name) + self.change_env_to_nginx() + self.test_set_site_ssl_conf() + self.test_mutil_set_site_ssl_conf() + self.test_close_site_ssl_conf() + self.check_web_server_config() + + # self.change_env_to_apache() + # self.test_set_site_ssl_conf() + # self.test_mutil_set_site_ssl_conf() + # self.test_close_site_ssl_conf() + # self.check_web_server_config() + + # def tearDown(self): + # pass + # self.reset_site_config() + + +if __name__ == '__main__': + import unittest + s = unittest.TestSuite() + s.addTest(TestSSLManager()) + unittest.TextTestRunner().run(s) diff --git a/requirements.txt b/requirements.txt index 0302613c..e5277fd2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,54 +1,128 @@ -bcrypt==3.2.0 -certifi==2021.10.8 -cffi==1.15.0 -chardet==4.0.0 -charset-normalizer==2.0.12 -click==8.0.4 -configparser==5.2.0 -cos-python-sdk-v5==1.9.15 +aliyun-python-sdk-core==2.13.36 +aliyun-python-sdk-core-v3==2.13.11 +aliyun-python-sdk-kms==2.16.0 +async-timeout==4.0.3 +bcrypt==4.0.1 +beautifulsoup4==4.12.2 +cachelib==0.12.0 +cachetools==5.3.0 +certifi==2024.2.2 +cffi==1.15.1 +chardet==5.1.0 +charset-normalizer==3.3.2 +click==8.1.7 +configobj==5.0.8 +configparser==5.3.0 +cos-python-sdk-v5==1.9.23 crcmod==1.7 -cryptography==36.0.1 -Cython==0.29.28 -Deprecated==1.2.13 -dicttoxml==1.7.4 -dnspython==2.2.1 -docker==5.0.3 -Flask==2.0.3 -flask-sock==0.5.1 -h11==0.13.0 -idna==3.3 +cryptography==40.0.2 +Cython==0.29.34 +decorator==5.1.1 +dicttoxml==1.7.16 +dnspython==2.3.0 +docker==6.0.1 +enum34==1.1.10 +Flask==2.2.5 +Flask-Session==0.4.0 +flask-sock==0.7.0 +Flask-SQLAlchemy==3.0.3 +future==0.18.3 +geoip2==4.8.0 +gevent==24.2.1 +gevent-websocket==0.10.1 +google-api-core==2.11.0 +google-api-python-client==2.86.0 +google-auth==2.17.3 +google-auth-httplib2==0.1.0 +google-auth-oauthlib==1.0.0 +google-cloud-core==2.3.2 +google-cloud-storage==2.8.0 +google-crc32c==1.5.0 +google-resumable-media==2.5.0 +googleapis-common-protos==1.59.0 +greenlet==3.0.3 +h11==0.14.0 +httplib2==0.22.0 +idna==3.7 +importlib-metadata==6.7.0 +iniparse==0.5 +ipaddress==1.0.23 IPy==1.1 -itsdangerous==2.1.1 -Jinja2==3.0.3 -MarkupSafe==2.1.0 -oauthlib==3.2.0 -packaging==21.3 -paramiko==2.10.2 -Pillow==9.0.1 -psutil==5.9.0 -pyasn1==0.4.8 +itsdangerous==2.1.2 +Jinja2==3.1.3 +jmespath==0.10.0 +kitchen==1.2.6 +MarkupSafe==2.1.5 +mongo==0.2.0 +natsort==8.4.0 +oauthlib==3.2.2 +oss2==2.17.0 +packaging==23.1 +paramiko==3.4.0 +peewee==3.16.2 +pillow==10.3.0 +protobuf==4.22.3 +psutil==5.9.5 +psycopg2-binary==2.9.9 +pyasn1==0.5.0 +pyasn1-modules==0.3.0 +pyasyncore==1.0.4 pycparser==2.21 -pycryptodome==3.14.1 +pycryptodome==3.17 +pycurl==7.45.2 +Pygments==2.15.1 pyinotify==0.9.6 -PyMySQL==1.0.2 +pymongo==4.6.3 +PyMySQL==1.0.3 PyNaCl==1.5.0 -pyOpenSSL==22.0.0 -pyparsing==3.0.7 -qiniu==7.5.0 -qrcode==7.3.1 -redis==4.1.4 -requests==2.27.1 +pyOpenSSL==23.1.1 +pyparsing==3.0.9 +pypdf==3.17.0 +PySocks==1.7.1 +pytz==2023.3 +pyudev==0.24.1 +pyxattr==0.8.1 +PyYAML==6.0.1 +qiniu==7.10.0 +qrcode==7.4.2 +redis==4.5.4 +requests==2.31.0 requests-file==1.5.1 requests-oauthlib==1.3.1 -rsa==4.8 -simple-websocket==0.5.1 +rsa==4.9 +simple-websocket==0.10.0 six==1.16.0 -soupsieve==2.3.1 +soupsieve==2.4.1 +SQLAlchemy==2.0.10 +supervisor==4.2.5 +typing-extensions==4.7.1 upyun==2.5.5 -urllib3==1.26.8 -websocket-client==1.3.1 -Werkzeug==2.0.3 -wrapt==1.14.0 -wsproto==1.1.0 -gevent -pyyaml \ No newline at end of file +uritemplate==4.1.1 +urlgrabber==4.1.0 +urllib3==1.26.18 +websocket-client==1.5.1 +Werkzeug==2.2.3 +wheel==0.38.1 +wsproto==1.2.0 +xmltodict==0.13.0 +zipp==3.15.0 +zope.event==5.0 +zope.interface==6.2 +pandas==2.2.2 +Brotli==1.1.0 +rarfile==4.2 +pyotp==2.9.0 +unrar==0.4 +sphinxapi-py3==2.1.11 +jmxquery==0.6.0 +billiard==4.2.0 +lxml==5.0.0 +docxtpl==0.16.8 +ntplib==0.4.0 +pyasynchat==1.0.4 +toml==0.10.2 +boto3==1.34.102 +distro==1.9.0 +pymssql==2.3.0 +distro==1.9.0 +pymssql==2.3.0 \ No newline at end of file diff --git a/runserver.py b/runserver.py index 3c991ee7..c059ae4b 100644 --- a/runserver.py +++ b/runserver.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- from os import environ from BTPanel import app,sys diff --git a/script/Werkzeug-2.2.3-py3-none-any.whl b/script/Werkzeug-2.2.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..4c9b46f2b3746250ee39fcd5bb6bd58f671ffbd0 GIT binary patch literal 233551 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 literal 0 HcmV?d00001 diff --git a/script/backup.py b/script/backup.py index 7068ae60..47b205a4 100644 --- a/script/backup.py +++ b/script/backup.py @@ -1,12 +1,15 @@ #!/usr/bin/python #coding: utf-8 #----------------------------- -# 宝塔Linux面板网站备份工具 +# aaPanel +# 网站备份工具 #----------------------------- import sys,os os.chdir('/www/server/panel') +sys.path.append("./") sys.path.append("class/") +sys.path.append("class_v2/") if sys.version_info[0] == 2: reload(sys) sys.setdefaultencoding('utf-8') @@ -24,11 +27,9 @@ class backupTools(panelBackup.backup): def backupPath(self,path,count,echo_id=None): self.backup_path(path,save=count,echo_id=echo_id) - def backupSiteAll(self,save,echo_id=None): self.backup_site_all(save,echo_id=echo_id) - def backupDatabaseAll(self,save,echo_id=None): self.backup_database_all(save,echo_id=echo_id) diff --git a/script/check_msg.py b/script/check_msg.py index c689915e..26f97449 100644 --- a/script/check_msg.py +++ b/script/check_msg.py @@ -14,7 +14,7 @@ data = msgObj.get_messages() for x in data: if x['level'] in ['danger', 'error'] and not x['send'] and x['retry_num'] < 5: msg = '服务器IP【{}】: {}'.format( - public.GetLocalIp(), re.sub(',?', '', x['msg'])) + public.GetLocalIp(), re.sub(r',?', '', x['msg'])) is_send = False ret = public.return_is_send_info() diff --git a/script/dk_log_split.py b/script/dk_log_split.py new file mode 100644 index 00000000..0fdf3d16 --- /dev/null +++ b/script/dk_log_split.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# coding: utf-8 +# ----------------------------- +# docker container log cutting script +# ----------------------------- +import sys +import os +import time +import datetime + +os.chdir("/www/server/panel") +sys.path.append('class/') +import public + + +class DkLogSpilt: + task_list = [] + + def __init__(self): + if not public.M('sqlite_master').db('docker_log_split').where('type=? AND name=?', ('table', 'docker_log_split')).count(): + self.task_list = [] + else: + self.task_list = public.M('docker_log_split').select() + + def run(self): + if not self.task_list: + print('No docker log cutting task') + for task in self.task_list: + try: + if task['split_type'] == 'day': + self.day_split(task) + elif task['split_type'] == 'size': + self.size_split(task) + except: + print('{} Failed to cut log!'.format(task['name'])) + + def day_split(self, task): + now_time = int(time.time()) + exec_time = int(self.get_timestamp_of_hour_minute(task['split_hour'], task['split_minute'])) + if now_time <= exec_time <= now_time + 300: + print("{} container starts log cutting".format(task['name'])) + split_path = '/var/lib/docker/containers/history_logs/{}/'.format(task['pid']) + if not os.path.exists(split_path): + os.makedirs(split_path) + os.rename(task['log_path'], split_path + task['pid'] + "-json.log" + '_' + str(int(time.time()))) + public.writeFile(task['log_path'], '') + print("{} log has been cut to:{}".format(task['name'],split_path + task['pid'] + "-json.log" + '_' + str(int(time.time())))) + self.check_save(task) + else: + print('{}container log has not reached the cutting time'.format(task['name'])) + + + def size_split(self, task): + if not os.path.exists(task['log_path']): + print('Log file does not exist') + return + if os.path.getsize(task['log_path']) >= task['split_size']: + print("{} container starts log cutting".format(task['name'])) + split_path = '/var/lib/docker/containers/history_logs/{}/'.format(task['pid']) + if not os.path.exists(split_path): + os.makedirs(split_path) + os.rename(task['log_path'], split_path + task['pid'] + "-json.log" + '_' + str(int(time.time()))) + public.writeFile(task['log_path'], '') + print("{} log has been cut to:{}".format(task['name'],split_path + task['pid'] + "-json.log" + '_' + str(int(time.time())))) + self.check_save(task) + else: + print('{} container log has not reached cutting size'.format(task['name'])) + + def check_save(self, task): + split_path = '/var/lib/docker/containers/history_logs/{}/'.format(task['pid']) + file_count = len(os.listdir(split_path)) + if file_count > task['save']: + file_list = os.listdir(split_path) + file_list.sort() + for i in range(file_count - task['save']): + os.remove(split_path + file_list[i]) + print('Delete log files:{}'.format(split_path + file_list[i])) + print('The latest {} logs have been retained'.format(task['save'])) + + def get_timestamp_of_hour_minute(self, hour, minute): + """获取当天指定时刻的时间戳。 + Args: + hour: 小时。 + minute: 分钟。 + Returns: + 时间戳。 + """ + current_time = datetime.datetime.now() + timestamp = current_time.replace(hour=hour, minute=minute, second=0, microsecond=0) + return int(timestamp.timestamp()) + + +if __name__ == '__main__': + dk = DkLogSpilt() + dk.run() diff --git a/script/logsBackup b/script/logsBackup index 943299e0..9333bf6f 100644 --- a/script/logsBackup +++ b/script/logsBackup @@ -1,7 +1,8 @@ #!/usr/bin/python #coding: utf-8 #----------------------------- -#宝塔Linux面板网站日志切割脚本 +#aaPanel +# 网站日志切割脚本 #----------------------------- import sys import os diff --git a/script/logsBackup.py b/script/logsBackup.py index 6daa506f..a6e99ec4 100644 --- a/script/logsBackup.py +++ b/script/logsBackup.py @@ -1,7 +1,8 @@ #!/usr/bin/python #coding: utf-8 #----------------------------- -#宝塔Linux面板网站日志切割脚本 +#aaPanel +# 网站日志切割脚本 #----------------------------- import sys import os diff --git a/script/polkit_upgrade.py b/script/polkit_upgrade.py index b7c33e7b..67a7ce0c 100644 --- a/script/polkit_upgrade.py +++ b/script/polkit_upgrade.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- #-------------------------------- diff --git a/script/process_network_total.py b/script/process_network_total.py index aae02adf..59e1eb5a 100644 --- a/script/process_network_total.py +++ b/script/process_network_total.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- import sys diff --git a/script/run_script.py b/script/run_script.py index 331f0ed8..920d9895 100644 --- a/script/run_script.py +++ b/script/run_script.py @@ -1,10 +1,10 @@ #coding: utf-8 #------------------------------------------------------------------- -# 宝塔Linux面板 +# aaPanel #------------------------------------------------------------------- -# Copyright (c) 2015-2099 宝塔软件(http:#bt.cn) All rights reserved. +# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. #------------------------------------------------------------------- -# Author: hwliang +# Author: hwliang #------------------------------------------------------------------- #------------------------------ diff --git a/script/webserver-ctl.sh b/script/webserver-ctl.sh new file mode 100644 index 00000000..8d942409 --- /dev/null +++ b/script/webserver-ctl.sh @@ -0,0 +1,228 @@ +#!/bin/bash +action=$1 +panel_path="/www/server/panel" # 面板路径 +webserver_bin="$panel_path/webserver/sbin/webserver" # nginx二进制文件 +webserver_conf="$panel_path/webserver/conf/webserver.conf" # nginx配置文件 +webserver_pid="$panel_path/webserver/logs/webserver.pid" # nginx pid文件 + + +PID=0 + + + +get_pid() { + if [ ! -f "$webserver_pid" ]; then + PID=0 + else + PID=$(cat $webserver_pid) + if [ "$PID" == "" ]; then + PID=0 + else + PID=$(ps aux | grep "$PID" | grep -v grep | awk '{print $2}') + PID_11=$(ps aux | grep "$webserver_bin" | grep -v grep | awk '{print $2}') + if [ "$PID" != "$PID_11" ]; then + PID=0 + fi + fi + fi + + PID=$(ps aux | grep "$webserver_bin" | grep -v grep | awk '{print $2}') + if [ -z "$PID" ]; then + PID=0 + else + PID="$PID" + fi + + if [ -z "$PID" ]; then + PID=0 + fi +} + +validate_server_files() { + if [ ! -f "$webserver_conf" ]; then + echo "BaoTa Web server configuration not found: $webserver_conf" + exit 1 + fi + + if [ ! -f "$webserver_bin" ]; then + echo "BaoTa Web server binary not found: $webserver_bin" + exit 1 + fi +} + +start() { + validate_server_files + get_pid + + if [ $PID -gt 0 ]; then + echo "BaoTa Web server is already running with PID ($PID)" + exit 1 + fi + + echo -n "Starting BaoTa web server..." + if [ -f "$webserver_pid" ]; then + rm -f $webserver_pid + fi + chmod 700 $webserver_bin + $webserver_bin -c $webserver_conf + if [ $? -ne 0 ]; then + echo "Failed to start BaoTa web server" + exit 1 + fi + + echo " Started" +} + +stop() { + validate_server_files + get_pid + if [ $PID -eq 0 ]; then + echo "BaoTa Web server is not running" + exit 1 + fi + + echo -n "Stopping BaoTa web server..." + $webserver_bin -c $webserver_conf -s stop + + pids=$(lsof -c webserver|grep LISTEN|awk '{print $2}'|sort -u) + for pid in $pids; do + kill -9 $pid + done + + echo " Stopped" +} + +restart() { + validate_server_files + get_pid + echo -n "Restarting BaoTa web server..." + if [ $PID -eq 0 ]; then + $webserver_bin -c $webserver_conf + else + $webserver_bin -c $webserver_conf -s reopen + fi + + if [ $? -ne 0 ]; then + echo "Failed to restart BaoTa web server" + exit 1 + fi + + echo " Restarted" +} + +status() { + validate_server_files + get_pid + if [ $PID -eq 0 ]; then + echo "BaoTa Web server is not running" + else + cmdline=/proc/$PID/cmdline + if [ ! -f $cmdline ]; then + echo "BaoTa Web server is not running" + rm -f $webserver_pid + exit 1 + fi + echo "BaoTa Web server is running with PID ($PID)" + fi +} + +reload() { + validate_server_files + get_pid + if [ $PID -eq 0 ]; then + echo "BaoTa Web server is not running" + exit 1 + fi + + echo -n "Reloading BaoTa web server..." + $webserver_bin -c $webserver_conf -s reload + if [ $? -ne 0 ]; then + echo "Failed to reload BaoTa web server" + exit 1 + fi + echo " Reloaded" +} + +configtest() { + validate_server_files + # 检查配置文件正确性,检查程序自动输出检查结果 + $webserver_bin -c $webserver_conf -t + +} + +download() { + tip_file=$panel_path/data/download.pl + if [ -f $tip_file ]; then + echo "BaoTa web server binary has been downloaded" + exit 1 + fi + + # 标记已下载 + echo "1" > $tip_file + + # 获取machine + machine=$(uname -m) + zip_file=$panel_path/data/webserver-$machine.zip + wget -O $zip_file https://node.aapanel.com/webserver/webserver-$machine.zip + if [ $? -ne 0 ]; then + echo "Failed to download BaoTa web server binary" + rm -f $zip_file + exit 1 + fi + + # 验证文件hash + hash256=$(sha256sum $zip_file | awk '{print $1}') + cloud_hash256=$(wget -q -O - https://node.aapanel.com/webserver/webserver-$machine.txt) + if [ "$hash256" != "$cloud_hash256" ]; then + echo "Failed to verify BaoTa web server binary" + rm -f $zip_file + exit 1 + fi + + # 解压文件 + unzip -o $zip_file -d $panel_path/ + if [ ! -f $webserver_bin ]; then + echo "Failed to extract BaoTa web server binary" + rm -f $zip_file + exit 1 + fi + + # 删除临时文件 + rm -f $zip_file + # 设置权限 + chmod 700 $webserver_bin + echo "BaoTa web server binary has been downloaded" + bash /www/server/panel/init.sh reload +} + + +case "$action" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + status) + status + ;; + reload) + reload + ;; + configtest) + configtest + ;; + test) + configtest + ;; + download) + download + ;; + *) + echo "Usage: $0 {start|stop|restart|status|reload|configtest|test|download}" + exit 1 + ;; +esac \ No newline at end of file diff --git a/task.py b/task.py index 17a2c082..83d99dfd 100644 --- a/task.py +++ b/task.py @@ -1,11 +1,11 @@ #!/bin/python #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2016 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2016 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- # ------------------------------ @@ -625,7 +625,7 @@ def daemon_panel(): def update_panel(): - os.system("curl -k https://node.aapanel.com/install/update6_en.sh|bash &") + os.system("curl -k https://node.aapanel.com/install/update_7.x_en.sh|bash &") def service_panel(action='reload'): @@ -730,7 +730,7 @@ def push_msg(): def JavaProDaemons(): ''' @name Java 项目守护进程 - @author lkq@bt.cn + @author lkq@aapanel.com @time 2022-07-19 @param None ''' @@ -816,6 +816,7 @@ def run_thread(): "update_software_list": update_software_list, "send_mail_time": send_mail_time, "check_panel_msg": check_panel_msg, + # "check_panel_auth": check_panel_auth, "push_msg": push_msg, "ProDadmons":ProDadmons, "process_task_thread":process_task_thread @@ -854,6 +855,21 @@ def scan_log_site(): # os.system('{} {}/script/check_msg.py &'.format(python_bin,base_path)) # time.sleep(600) +# # 检测面板授权 +# def check_panel_auth(): +# python_bin = get_python_bin() +# from BTPanel import cache +# if cache: +# key='pro_check_sdfjslk' +# res = cache.get(key) +# while True: +# update_file='/www/server/panel/data/now_update_pro.pl' +# # pro_file='/www/server/panel/data/panel_pro.pl' +# if os.path.exists(update_file) or res is None: +# os.system('nohup {} /www/server/panel/script/check_auth.py > /dev/null 2>&1 &'.format(python_bin)) +# if cache: +# cache.set(key, 'sddsf', 3600) + def main(): main_pid = 'logs/task.pid' diff --git a/tools.py b/tools.py index 965d2457..e8d703a0 100644 --- a/tools.py +++ b/tools.py @@ -1,10 +1,10 @@ #coding: utf-8 # +------------------------------------------------------------------- -# | 宝塔Linux面板 +# | aaPanel # +------------------------------------------------------------------- -# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# | Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved. # +------------------------------------------------------------------- -# | Author: hwliang +# | Author: hwliang # +------------------------------------------------------------------- #------------------------------ @@ -24,8 +24,8 @@ if sys.version_info[0] == 3: raw_input = input def set_mysql_root(password): import db,os sql = db.Sql() - - root_mysql = '''#!/bin/bash + + root_mysql = r'''#!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH pwd=$1 @@ -34,7 +34,7 @@ mysqld_safe --skip-grant-tables& echo 'Changing password...'; sleep 6 m_version=$(cat /www/server/mysql/version.pl|grep -E "(5.1.|5.5.|5.6.|10.0|10.1)") -m2_version=$(cat /www/server/mysql/version.pl|grep -E "(10.5.|10.4.)") +m2_version=$(cat /www/server/mysql/version.pl|grep -E "10.([4-9]|[1-9][0-9]).") if [ "$m_version" != "" ];then mysql -uroot -e "UPDATE mysql.user SET password=PASSWORD('${pwd}') WHERE user='root'"; elif [ "$m2_version" != "" ];then @@ -56,18 +56,18 @@ sleep 2 echo '===========================================' echo "The root password set ${pwd} successuful"'''; - + public.writeFile('mysql_root.sh',root_mysql) os.system("/bin/bash mysql_root.sh " + password) os.system("rm -f mysql_root.sh") - + result = sql.table('config').where('id=?',(1,)).setField('mysql_root',password) print(result) #设置面板密码 def set_panel_pwd(password,ncli = False): password = password.strip() - re_list = re.findall("[^\w\d,.]+", password) + re_list = re.findall(r"[^\w\d,.]+", password) if re_list: print("|-Error: password cannot contain special characters: {}".format(" ".join(re_list))) return @@ -83,7 +83,7 @@ def set_panel_pwd(password,ncli = False): #设置数据库目录 def set_mysql_dir(path): - mysql_dir = '''#!/bin/bash + mysql_dir = r'''#!/bin/bash PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH oldDir=`cat /etc/my.cnf |grep 'datadir'|awk '{print $3}'` @@ -197,7 +197,7 @@ def CloseTask(): os.system("kill `ps -ef |grep 'install_soft.sh'|grep -v grep|grep -v panelExec|awk '{print $2}'`") os.system('/etc/init.d/bt restart') print(public.GetMsg("CLEAR_TASK",(int(ncount),))) - + def get_ipaddress(): ''' @name 获取本机IP地址 @@ -351,7 +351,7 @@ def ClearRecycle_Bin(): import files f = files.files(); f.Close_Recycle_bin(None); - + #清理其它 def ClearOther(): clearPath = [ @@ -362,7 +362,7 @@ def ClearOther(): {'path':'/www/server/panel/install','find':'.zip'}, {'path':'/www/server/panel/install','find':'.gz'} ] - + total = count = 0 print(public.GetMsg("CLEAR_RUBBISH3")) for c in clearPath: @@ -409,7 +409,7 @@ def set_panel_username(username = None): import db sql = db.Sql() if username: - re_list = re.findall("[^\w\d,.]+", username) + re_list = re.findall(r"[^\w\d,.]+", username) if re_list: print("|-Error: username cannot contain special characters: {}".format(" ".join(re_list))) return @@ -423,13 +423,13 @@ def set_panel_username(username = None): sql.table('users').where('id=?',(1,)).setField('username',username) print(public.GetMsg("NEW_NAME",(username,))) return; - + username = sql.table('users').where('id=?',(1,)).getField('username') - if username == 'admin': + if username == 'admin': username = public.GetRandomString(8).lower() sql.table('users').where('id=?',(1,)).setField('username',username) print('username: ' + username) - + #设定idc def setup_idc(): try: @@ -449,7 +449,7 @@ def setup_idc(): titleNew = (pInfo['brand'] + public.GetMsg("PANEL")).encode('utf-8') if os.path.exists(tFile): title = public.GetConfigValue('title') - if title == '宝塔Linux面板' or title == '': + if title == 'aaPanel' or title == '': public.writeFile(tFile,titleNew) public.SetConfigValue('title',titleNew) else: @@ -494,6 +494,8 @@ def bt_cli(u_input = 0): print("(23) %s (16) %s"% ("Turn off BasicAuth Authenticator","Repair panel (check for errors and update panel files to the latest version)")) print("(24) Turn off Google Authenticator (17) Set log cutting on/off compression") print("(25) Save copy when modify file in panel (18) Set whether to back up the panel automatically") + # if not os.path.exists('/www/server/panel/data/panel_pro.pl'): + # print(" (19) Update to aapanel pro version") print("(26) Keep/Remove local backup when backing up to cloud storage") print("(27) Turn on/off panel SSL (28) Modify panel security entrance") print("(0) Cancel") @@ -550,7 +552,7 @@ def bt_cli(u_input = 0): exit() except: pass - nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,22,23,24,25,26,27,28] + nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,22,23,24,25,26,27,28] if not u_input in nums: print(raw_tip) print(public.GetMsg("CANCELLED")) @@ -606,6 +608,7 @@ def bt_cli(u_input = 0): print('Security entrance set successfully:{}'.format(admin_path)) if u_input == 1: + if os.path.exists('data/plugin_bin.pl'):os.remove('data/plugin_bin.pl') os.system("/etc/init.d/bt restart") elif u_input == 2: os.system("/etc/init.d/bt stop") @@ -643,7 +646,7 @@ def bt_cli(u_input = 0): if not re.match(rep, input_mysql): print(public.GetMsg("PASS_SPECIAL_CHARACTRES_ERR")) return; - + print(input_mysql) set_mysql_root(input_mysql.strip()) elif u_input == 8: @@ -715,8 +718,15 @@ def bt_cli(u_input = 0): elif u_input == 15: ClearSystem() elif u_input == 16: - os.system("/www/server/panel/pyenv/bin/pip install cachelib") - os.system("curl -k https://node.aapanel.com/install/update6_en.sh|bash") + pro_path = '/www/server/panel/data/panel_pro.pl' + if os.path.exists(pro_path): + print("|-Updating aapanel version to pro version...") + os.system("curl -k https://node.aapanel.com/install/update_pro_en.sh|bash") + else: + # os.system("/www/server/panel/pyenv/bin/pip install cachelib") + only_update_pyenv312 = '/tmp/only_update_pyenv312.pl' + if os.path.exists(only_update_pyenv312): os.remove(only_update_pyenv312) + os.system("curl -k https://node.aapanel.com/install/update_7.x_en.sh|bash") elif u_input == 17: l_path = '/www/server/panel/data/log_not_gzip.pl' if os.path.exists(l_path): @@ -737,6 +747,10 @@ def bt_cli(u_input = 0): print("|-Detected that the panel automatic backup function is turned on and is closing...") public.writeFile(l_path,'True') print("|-Panel auto-backup function turned off") + elif u_input == 19: + if os.path.exists('/tmp/update_to7.pl'):os.remove('/tmp/update_to7.pl') + print("|-Updating aapanel version to pro version...") + os.system("curl -k https://node.aapanel.com/install/update_pro_en.sh|bash") elif u_input == 22: os.system('tail -100 /www/server/panel/logs/error.log') elif u_input == 23: @@ -768,6 +782,45 @@ def bt_cli(u_input = 0): os.mknod(keep_local) +# 旧的插件系统升级到新的插件系统 +def upgrade_plugins(): + print("====================================================") + print(public.GetMsg("PLUG_UPDATEING")) + print("====================================================") + exlodes = ['gitlab', 'pm2', 'mongodb', 'deployment_jd', 'logs', 'docker', 'beta', 'btyw'] + for pname in os.listdir('plugin/'): + if not os.path.isdir('plugin/' + pname): continue + if pname in exlodes: continue + + print("|-upgrading [ %s ]..." % pname) + + try: + # 查找是否存在主程序SO文件 + specified_so_file = 'plugin/{plugin_name}/{plugin_name}_main.cpython-{major}{minor}m-x86_64-linux-gnu.so'.format(plugin_name=pname, major=sys.version_info.major, minor=sys.version_info.minor) + if os.path.isfile(specified_so_file): + # 存在SO文件则将其删除 + os.remove(specified_so_file) + + so_file = 'plugin/{plugin_name}/{plugin_name}_main.so'.format(plugin_name=pname) + if os.path.isfile(so_file): + # 存在SO文件则将其删除 + os.remove(so_file) + + # 检查主程序py文件是否为空 + main_file = 'plugin/{plugin_name}/{plugin_name}_main.py'.format(plugin_name=pname) + if os.path.isfile(main_file) and os.path.getsize(main_file) < 10: + # 主程序py文件为空时,重新下载py文件 + public.re_download_main(pname) + + print(" \033[32m[success]\033[0m") + except Exception as e: + print(" \033[31m[fail] {}\033[0m".format(str(e))) + upgrade_plugins_exists = '/www/server/panel/data/upgrade_plugins_3.12.pl' + public.writeFile(upgrade_plugins_exists, 'True') + print("====================================================") + print("\033[32m" + public.GetMsg("PLUG_UPDATE_TO_6") + "\033[0m") + print("====================================================") + if __name__ == "__main__": type = sys.argv[1] @@ -799,5 +852,7 @@ if __name__ == "__main__": except: clinum = sys.argv[2] bt_cli(clinum) + elif type == "upgrade_plugins": + upgrade_plugins() else: print('ERROR: Parameter error') diff --git a/webserver/tpls/webserver.conf b/webserver/tpls/webserver.conf new file mode 100644 index 00000000..26ef6006 --- /dev/null +++ b/webserver/tpls/webserver.conf @@ -0,0 +1,100 @@ +user www www; +worker_processes 1; +error_log /www/server/panel/webserver/logs/error.log crit; +pid /www/server/panel/webserver/logs/webserver.pid; +worker_rlimit_nofile 512; + +events + {{ + use epoll; + worker_connections 8192; + multi_accept on; + }} + +http + {{ + include mime.types; + default_type application/octet-stream; + server_names_hash_bucket_size 512; + client_header_buffer_size 32k; + large_client_header_buffers 4 32k; + client_max_body_size 50m; + client_body_buffer_size 512k; + + proxy_connect_timeout 3600; + proxy_read_timeout 3600; + proxy_send_timeout 3600; + proxy_buffer_size 32k; + proxy_buffers 4 64k; + proxy_busy_buffers_size 128k; + proxy_temp_file_write_size 128k; + proxy_next_upstream error timeout invalid_header http_500 http_503 http_404; + + sendfile on; + tcp_nopush on; + keepalive_timeout 3600; + tcp_nodelay on; + + gzip on; + gzip_min_length 256; + gzip_buffers 16 16k; + gzip_http_version 1.1; + gzip_comp_level 3; + gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/json image/jpeg image/gif image/png font/ttf font/otf image/svg+xml application/xml+rss text/x-js; + gzip_vary on; + gzip_proxied expired no-cache no-store private auth; + gzip_disable "MSIE [1-6]\."; + + server_tokens off; + access_log /dev/null; + + server + {{ +{LISTEN} + server_name _; + + # 错误页 + error_page 502 /error_page/502.json; + + # rewrite + rewrite ^/site/static/(.*) /static/$1 last; + + {HTTP3_HEADER} + add_header X-Quic "h3"; + + # 静态文件 + location ~ ^/static/ {{ + root /www/server/panel/BTPanel; + expires 24h; + }} + + # 图标处理 + location ~ ^/static\/img/ {{ + root /www/server/panel/BTPanel; + try_files $uri /static/img/soft_ico/icon_plug.svg; + expires 24h; + }} + + #PROXY-START/ + location / + {{ + proxy_pass http://unix:/tmp/panel.sock; + proxy_set_header Host $host:$server_port; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Real-Port $remote_port; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + # proxy_http_version 1.1; + + {HTTP3_HEADER} + add_header X-Quic "h3"; + }} + #PROXY-END/ + +{SSL_CONFIG} + + access_log /dev/null; + error_log /www/server/panel/webserver/logs/webserver.log; + }} +}} + diff --git a/webserver/tpls/webserver_listen.conf b/webserver/tpls/webserver_listen.conf new file mode 100644 index 00000000..edde8f75 --- /dev/null +++ b/webserver/tpls/webserver_listen.conf @@ -0,0 +1,2 @@ + listen {PORT} default_server; + listen [::]:{PORT}; \ No newline at end of file diff --git a/webserver/tpls/webserver_listen_ssl.conf b/webserver/tpls/webserver_listen_ssl.conf new file mode 100644 index 00000000..f5af7688 --- /dev/null +++ b/webserver/tpls/webserver_listen_ssl.conf @@ -0,0 +1,6 @@ + listen {PORT} quic reuseport; + listen {PORT} ssl default_server; + listen [::]:{PORT} ssl; + listen [::]:{PORT} quic reuseport; + http2 on; + http3 on; \ No newline at end of file diff --git a/webserver/tpls/webserver_ssl.conf b/webserver/tpls/webserver_ssl.conf new file mode 100644 index 00000000..2ef626a1 --- /dev/null +++ b/webserver/tpls/webserver_ssl.conf @@ -0,0 +1,16 @@ + # HTTPS安全配置 + proxy_cookie_path / "/; httponly; secure; SameSite=Lax"; + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; + + #SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则 + ssl_certificate /www/server/panel/ssl/certificate.pem; + ssl_certificate_key /www/server/panel/ssl/privateKey.pem; + ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 1m; + add_header Strict-Transport-Security "max-age=31536000"; + add_header X-Frame-Options SAMEORIGIN; + error_page 497 https://$http_host$request_uri; + #SSL-END \ No newline at end of file

                                    +hV_T160=GWio4~El@Vg}_ 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 W`AK;yPgX%foIAf+J5+t&lm9 literal 0 HcmV?d00001 diff --git a/config/databases.json b/config/databases.json new file mode 100644 index 00000000..680a13c6 --- /dev/null +++ b/config/databases.json @@ -0,0 +1,698 @@ +{ + "ssl_data.db":{ + "ssl_info":{ + "sql":"CREATE TABLE IF NOT EXISTS 'ssl_info' ('id' INTEGER PRIMARY KEY AUTOINCREMENT,'hash' TEXT NOT NULL UNIQUE,'path' TEXT NOT NULL,'dns' TEXT NOT NULL,'subject' TEXT NOT NULL,'info' TEXT NOT NULL DEFAULT '','cloud_id' INTEGER NOT NULL DEFAULT -1,'not_after' TEXT NOT NULL,'use_for_panel' INTEGER NOT NULL DEFAULT 0,'use_for_site' TEXT NOT NULL DEFAULT '[]','auth_info' TEXT NOT NULL DEFAULT '{}','create_time' INTEGER NOT NULL DEFAULT (strftime('%s')));", + "fields":[ + [0, "id", "INTEGER", 0, null, 1], + [1, "hash", "TEXT", 0, null, 0], + [2, "path", "TEXT", 0, null, 0], + [3, "dns", "TEXT", 0, null, 0], + [4, "subject", "TEXT", 0, null, 0], + [5, "info", "TEXT", 0, "", 0], + [6, "cloud_id", "INTEGER", 0, "-1", 0], + [7, "not_after", "TEXT", 0, null, 0], + [8, "use_for_panel", "INTEGER", 0, "0", 0], + [9, "use_for_site", "TEXT", 0, "'[]'", 0], + [10, "auth_info", "TEXT", 0, "'{}'", 0], + [11, "create_time", "TEXT", 0, "strftime('%s')", 0] + ] + } + }, + "panel.db": { + "config": { + "sql": "CREATE TABLE `config` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `webserver` TEXT,\n `backup_path` TEXT,\n `sites_path` TEXT,\n `status` INTEGER,\n `mysql_root` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "webserver", "TEXT", 0, null, 0], + [2, "backup_path", "TEXT", 0, null, 0], + [3, "sites_path", "TEXT", 0, null, 0], + [4, "status", "INTEGER", 0, null, 0], + [5, "mysql_root", "TEXT", 0, null, 0] + ] + }, + "users": { + "sql": "CREATE TABLE `users` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `username` TEXT,\n `password` TEXT,\n `login_ip` TEXT,\n `login_time` TEXT,\n `phone` TEXT,\n `email` TEXT,\n `salt` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "username", "TEXT", 0, null, 0], + [2, "password", "TEXT", 0, null, 0], + [3, "login_ip", "TEXT", 0, null, 0], + [4, "login_time", "TEXT", 0, null, 0], + [5, "phone", "TEXT", 0, null, 0], + [6, "email", "TEXT", 0, null, 0], + [7, "salt", "TEXT", 0, null, 0] + ] + } + }, + "site.db": { + "sites": { + "sql": "CREATE TABLE `sites` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` TEXT,\n `path` TEXT,\n `status` TEXT,\n `index` TEXT,\n `ps` TEXT,\n `addtime` TEXT,\n `type_id` integer DEFAULT 0,\n `edate` integer DEFAULT '0000-00-00',\n `project_type` STRING DEFAULT 'PHP',\n `project_config` STRING DEFAULT '{}',\n `rname` text DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "path", "TEXT", 0, null, 0], + [3, "status", "TEXT", 0, null, 0], + [4, "index", "TEXT", 0, null, 0], + [5, "ps", "TEXT", 0, null, 0], + [6, "addtime", "TEXT", 0, null, 0], + [7, "type_id", "integer", 0, "0", 0], + [8, "edate", "integer", 0, "'0000-00-00'", 0], + [9, "project_type", "STRING", 0, "'PHP'", 0], + [10, "project_config", "STRING", 0, "'{}'", 0], + [11, "rname", "text", 0, "", 0] + ] + }, + "site_types": { + "sql": "CREATE TABLE `site_types` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` REAL,\n `ps` REAL\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "REAL", 0, null, 0], + [2, "ps", "REAL", 0, null, 0] + ] + }, + "domain": { + "sql": "CREATE TABLE `domain` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `pid` INTEGER,\n `name` TEXT,\n `port` INTEGER,\n `addtime` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "pid", "INTEGER", 0, null, 0], + [2, "name", "TEXT", 0, null, 0], + [3, "port", "INTEGER", 0, null, 0], + [4, "addtime", "TEXT", 0, null, 0] + ] + }, + "binding": { + "sql": "CREATE TABLE `binding` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `pid` INTEGER,\n `domain` TEXT,\n `path` TEXT,\n `port` INTEGER,\n `addtime` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "pid", "INTEGER", 0, null, 0], + [2, "domain", "TEXT", 0, null, 0], + [3, "path", "TEXT", 0, null, 0], + [4, "port", "INTEGER", 0, null, 0], + [5, "addtime", "TEXT", 0, null, 0] + ] + } + }, + "ftp.db": { + "ftps": { + "sql": "CREATE TABLE `ftps` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `pid` INTEGER,\n `name` TEXT,\n `password` TEXT,\n `path` TEXT,\n `status` TEXT,\n `ps` TEXT,\n `addtime` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "pid", "INTEGER", 0, null, 0], + [2, "name", "TEXT", 0, null, 0], + [3, "password", "TEXT", 0, null, 0], + [4, "path", "TEXT", 0, null, 0], + [5, "status", "TEXT", 0, null, 0], + [6, "ps", "TEXT", 0, null, 0], + [7, "addtime", "TEXT", 0, null, 0] + ] + } + }, + "database.db": { + "database_servers": { + "sql": "CREATE TABLE `database_servers` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `db_host` REAL,\n `db_port` REAL,\n `db_user` INTEGER,\n `db_password` INTEGER,\n `ps` REAL,\n `addtime` INTEGER,\n `db_type` REAL DEFAULT 'mysql',\n `type` STRING DEFAULT 'MySQL'\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "db_host", "REAL", 0, null, 0], + [2, "db_port", "REAL", 0, null, 0], + [3, "db_user", "INTEGER", 0, null, 0], + [4, "db_password", "INTEGER", 0, null, 0], + [5, "ps", "REAL", 0, null, 0], + [6, "addtime", "INTEGER", 0, null, 0], + [7, "db_type", "REAL", 0, "'mysql'", 0], + [8,"type","STRING",0,"'MySQL'",0] + ] + }, + "databases": { + "sql": "CREATE TABLE `databases` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `pid` INTEGER,\n `name` TEXT,\n `username` TEXT,\n `password` TEXT,\n `accept` TEXT,\n `ps` TEXT,\n `addtime` TEXT,\n `db_type` integer DEFAULT '0',\n `conn_config` STRING DEFAULT '{}',\n `sid` integer DEFAULT 0,\n `type` TEXT DEFAULT 'MySQL'\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "pid", "INTEGER", 0, null, 0], + [2, "name", "TEXT", 0, null, 0], + [3, "username", "TEXT", 0, null, 0], + [4, "password", "TEXT", 0, null, 0], + [5, "accept", "TEXT", 0, null, 0], + [6, "ps", "TEXT", 0, null, 0], + [7, "addtime", "TEXT", 0, null, 0], + [8, "db_type", "integer", 0, "'0'", 0], + [9, "conn_config", "STRING", 0, "'{}'", 0], + [10, "sid", "integer", 0, "0", 0], + [11, "type", "TEXT", 0, "'MySQL'", 0] + ] + }, + "mysql_increment_settings": { + "sql": "CREATE TABLE `mysql_increment_settings` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `cron_id` INTEGER,\n `db_name` TEXT DEFAULT '',\n `tb_name` TEXT DEFAULT '',\n `zip_password` TEXT DEFAULT '',\n `last_backup_time` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "cron_id", "INTEGER", 0, null, 0], + [2, "db_name", "TEXT", 0, "''", 0], + [3, "tb_name", "TEXT", 0, "''", 0], + [4, "zip_password", "TEXT", 0, "''", 0], + [5, "last_backup_time", "TEXT", 0, "''", 0] + ] + }, + "mysql_increment_backup": { + "sql": "CREATE TABLE `mysql_increment_backup` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `cron_id` INTEGER,\n `size` INTEGER DEFAULT 0,\n `type` INTEGER DEFAULT 0,\n `addtime` TEXT DEFAULT '',\n `name` TEXT DEFAULT '',\n `localhost` TEXT DEFAULT '',\n `ftp` TEXT DEFAULT '',\n `alioss` TEXT DEFAULT '',\n `txcos` TEXT DEFAULT '',\n `qiniu` TEXT DEFAULT '',\n `aws_s3` TEXT DEFAULT '',\n `upyun` TEXT DEFAULT '',\n `obs` TEXT DEFAULT '',\n `bos` TEXT DEFAULT '',\n `gcloud_storage` TEXT DEFAULT '',\n `gdrive` TEXT DEFAULT '',\n `msonedrive` TEXT DEFAULT '',\n `jdcloud` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "cron_id", "INTEGER", 0, null, 0], + [2, "size", "INTEGER", 0, "0", 0], + [3, "type", "INTEGER", 0, "0", 0], + [4, "addtime", "TEXT", 0, "''", 0], + [5, "name", "TEXT", 0, "''", 0], + [6, "localhost", "TEXT", 0, "''", 0], + [7, "ftp", "TEXT", 0, "''", 0], + [8, "alioss", "TEXT", 0, "''", 0], + [9, "txcos", "TEXT", 0, "''", 0], + [10, "qiniu", "TEXT", 0, "''", 0], + [11, "aws_s3", "TEXT", 0, "''", 0], + [12, "upyun", "TEXT", 0, "''", 0], + [13, "obs", "TEXT", 0, "''", 0], + [14, "bos", "TEXT", 0, "''", 0], + [15, "gcloud_storage", "TEXT", 0, "''", 0], + [16, "gdrive", "TEXT", 0, "''", 0], + [17, "msonedrive", "TEXT", 0, "''", 0], + [18, "jdcloud", "TEXT", 0, "''", 0] + ] + } + }, + "docker.db": { + "dk_sites": { + "sql": "CREATE TABLE `dk_sites` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` TEXT,\n `path` TEXT,\n `status` TEXT DEFAULT 1,\n `ps` TEXT,\n `addtime` TEXT,\n `type_id` integer DEFAULT 111,\n `edate` integer DEFAULT '0000-00-00',\n `project_type` STRING DEFAULT 'dk_proxy',\n `container_id` TEXT DEFAULT '',\n `container_name` TEXT DEFAULT '',\n `container_port` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "path", "TEXT", 0, null, 0], + [3, "status", "TEXT", 0, "1", 0], + [4, "ps", "TEXT", 0, null, 0], + [5, "addtime", "TEXT", 0, null, 0], + [6, "type_id", "integer", 0, "111", 0], + [7, "edate", "integer", 0, "'0000-00-00'", 0], + [8, "project_type", "STRING", 0, "'dk_proxy'", 0], + [9, "container_id", "TEXT", 0, null, 0], + [10, "container_name", "TEXT", 0, null, 0], + [11, "container_port", "TEXT", 0, null, 0] + ] + }, + "dk_domain": { + "sql": "CREATE TABLE `dk_domain` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `pid` INTEGER,\n `name` TEXT,\n `addtime` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "pid", "INTEGER", 0, null, 0], + [2, "name", "TEXT", 0, null, 0], + [3, "addtime", "TEXT", 0, null, 0] + ] + }, + "dk_backup": { + "sql": "CREATE TABLE `dk_backup` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `type` INTEGER,\n `name` TEXT,\n `container_id` TEXT,\n `container_name` TEXT,\n `filename` TEXT,\n `size` INTEGER,\n `addtime` TEXT,\n`ps` STRING DEFAULT '\u65e0',\n`cron_id` INTEGER DEFAULT 0\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "type", "INTEGER", 0, null, 0], + [2, "name", "TEXT", 0, null, 0], + [3, "container_id", "TEXT", 0, null, 0], + [4, "container_name", "TEXT", 0, null, 0], + [5, "filename", "TEXT", 0, null, 0], + [6, "size", "INTEGER", 0, null, 0], + [7, "addtime", "TEXT", 0, null, 0], + [8, "ps", "STRING", 0, "'\u65e0'", 0], + [9, "cron_id", "INTEGER", 0, "0", 0] + ], + "comment": { + "table": "容器备份表", + "type": { + "ps": "备份类型", + "0": "容器备份", + "1": "镜像备份", + "2": "容器日志备份", + "3": "容器目录或文件备份" + }, + "name": "备份名称", + "container_id": "容器ID", + "container_name": "容器名", + "filename": "备份文件名", + "size": "备份文件大小", + "addtime": "备份时间", + "ps": "备注", + "cron_id": "计划任务ID" + } + }, + "container": { + "sql": "CREATE TABLE `container` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `cpu_limit` VARCHAR DEFAULT (1),\n `container_name` VARCHAR DEFAULT ''\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "cpu_limit", "VARCHAR", 0, "1", 0], + [2, "container_name", "VARCHAR", 0, "''", 0] + ] + }, + "container_count": { + "sql": "CREATE TABLE `container_count` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `container_count` INTEGER,\n `time` INTEGER\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "container_count", "INTEGER", 0, null, 0], + [2, "time", "INTEGER", 0, null, 0] + ] + }, + "cpu_stats": { + "sql": "CREATE TABLE `cpu_stats` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `time` INTEGER, \n `cpu_usage` VARCHAR,\n `online_cpus` INT,\n `container_id` VARCHAR\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "time", "INTEGER", 0, null, 0], + [2, "cpu_usage", "VARCHAR", 0, null, 0], + [3, "online_cpus", "INT", 0, null, 0], + [4, "container_id", "VARCHAR", 0, null, 0] + ] + }, + "hosts": { + "sql": "CREATE TABLE `hosts` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `url` VARCHAR,\n `remark` VARCHAR DEFAULT (''),\n `time` INTEGER\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "url", "VARCHAR", 0, null, 0], + [2, "remark", "VARCHAR", 0, "''", 0], + [3, "time", "INTEGER", 0, null, 0] + ] + }, + "image_infos": { + "sql": "CREATE TABLE `image_infos` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `num` INTEGER,\n `size` INTEGER,\n `time` INTEGER\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "num", "INTEGER", 0, null, 0], + [2, "size", "INTEGER", 0, null, 0], + [3, "time", "INTEGER", 0, null, 0] + ] + }, + "io_stats": { + "sql": "CREATE TABLE `io_stats` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `container_id` VARCHAR,\n `time` VARCHAR,\n `read_total` INTEGER,\n `write_total` INTEGER\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "container_id", "VARCHAR", 0, null, 0], + [2, "time", "VARCHAR", 0, null, 0], + [3, "read_total", "INTEGER", 0, null, 0], + [4, "write_total", "INTEGER", 0, null, 0] + ] + }, + "mem_stats": { + "sql": "CREATE TABLE `mem_stats` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `mem_limit` VARCHAR,\n `cache` VARCHAR,\n `usage` VARCHAR,\n `usage_total` VARCHAR,\n `container_id` VARCHAR,\n `time` INTEGER\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "mem_limit", "VARCHAR", 0, null, 0], + [2, "cache", "VARCHAR", 0, null, 0], + [3, "usage", "VARCHAR", 0, null, 0], + [4, "usage_total", "VARCHAR", 0, null, 0], + [5, "container_id", "VARCHAR", 0, null, 0], + [6, "time", "INTEGER", 0, null, 0] + ] + }, + "net_stats": { + "sql": "CREATE TABLE `net_stats` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `container_id` INTEGER,\n `rx_total` INTEGER,\n `tx_total` INTEGER,\n `rx` INTEGER,\n `tx` INTEGER,\n `time` VARCHAR\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "container_id", "INTEGER", 0, null, 0], + [2, "rx_total", "INTEGER", 0, null, 0], + [3, "tx_total", "INTEGER", 0, null, 0], + [4, "rx", "INTEGER", 0, null, 0], + [5, "tx", "INTEGER", 0, null, 0], + [6, "time", "VARCHAR", 0, null, 0] + ] + }, + "registry": { + "sql": "CREATE TABLE `registry` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `url` VARCHAR,\n `username` VARCHAR DEFAULT (''),\n `password` VARCHAR DEFAULT (''),\n `name` VARCHAR UNIQUE,\n `namespace` VARCHAR,\n `remark` VARCHAR DEFAULT ('')\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "url", "VARCHAR", 0, null, 0], + [2, "username", "VARCHAR", 0, "''", 0], + [3, "password", "VARCHAR", 0, "''", 0], + [4, "name", "VARCHAR", 0, null, 0], + [5, "namespace", "VARCHAR", 0, null, 0], + [6, "remark", "VARCHAR", 0, "''", 0] + ] + }, + "stacks": { + "sql": "CREATE TABLE `stacks` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` VARCHAR UNIQUE,\n `status` VARCHAR,\n `path` VARCHAR,\n `template_id` INTEGER,\n `time` INTEGER,\n `remark` VARCHAR DEFAULT ('')\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "VARCHAR", 0, null, 0], + [2, "status", "VARCHAR", 0, null, 0], + [3, "path", "VARCHAR", 0, null, 0], + [4, "template_id", "INTEGER", 0, null, 0], + [5, "time", "INTEGER", 0, null, 0], + [6, "remark", "VARCHAR", 0, "''", 0] + ] + }, + "templates": { + "sql": "CREATE TABLE `templates` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `path` VARCHAR,\n `name` VARCHAR UNIQUE,\n `remark` VARCHAR DEFAULT (''),\n `add_in_path` INTEGER\n);", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "path", "VARCHAR", 0, null, 0], + [2, "name", "VARCHAR", 0, null, 0], + [3, "remark", "VARCHAR", 0, "''", 0], + [4, "add_in_path", "INTEGER", 0, null, 0] + ] + } + }, + "firewall.db": { + "firewall": { + "sql": "CREATE TABLE `firewall` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `port` TEXT,\n `ps` TEXT,\n `addtime` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "port", "TEXT", 0, null, 0], + [2, "ps", "TEXT", 0, null, 0], + [3, "addtime", "TEXT", 0, null, 0] + ] + }, + "firewall_country": { + "sql": "CREATE TABLE `firewall_country` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `types` TEXT,\n `country` TEXT DEFAULT '',\n `brief` TEXT DEFAULT '',\n `addtime` TEXT DEFAULT '',\n `ports` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "types", "TEXT", 0, null, 0], + [2, "country", "TEXT", 0, "''", 0], + [3, "brief", "TEXT", 0, "''", 0], + [4, "addtime", "TEXT", 0, "''", 0], + [5, "ports", "TEXT", 0, "''", 0] + ] + }, + "firewall_domain": { + "sql": "CREATE TABLE `firewall_domain` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `types` TEXT,\n `domain` TEXT,\n `domain_total` TEXT,\n `port` TEXT,\n `sid` int DEFAULT 0,\n `address` TEXT DEFAULT '',\n `brief` TEXT DEFAULT '',\n `protocol` TEXT DEFAULT '',\n `addtime` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "types", "TEXT", 0, null, 0], + [2, "domain", "TEXT", 0, null, 0], + [3, "domain_total", "TEXT", 0, null, 0], + [4, "port", "TEXT", 0, null, 0], + [5, "sid", "int", 0, "0", 0], + [6, "address", "TEXT", 0, "''", 0], + [7, "brief", "TEXT", 0, "''", 0], + [8, "protocol", "TEXT", 0, "''", 0], + [9, "addtime", "TEXT", 0, "''", 0] + ] + }, + "firewall_ip": { + "sql": "CREATE TABLE `firewall_ip` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `types` TEXT,\n `address` TEXT DEFAULT '',\n `brief` TEXT DEFAULT '',\n `addtime` TEXT DEFAULT '',\n `sid` int DEFAULT 0,\n `domain` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "types", "TEXT", 0, null, 0], + [2, "address", "TEXT", 0, "''", 0], + [3, "brief", "TEXT", 0, "''", 0], + [4, "addtime", "TEXT", 0, "''", 0], + [5, "sid", "INTEGER", 0, 0, 0], + [6, "domain", "TEXT", 0, "", 0] + ] + }, + "firewall_new": { + "sql": "CREATE TABLE `firewall_new` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `protocol` TEXT DEFAULT '',\n `ports` TEXT,\n `types` TEXT,\n `address` TEXT DEFAULT '',\n `brief` TEXT DEFAULT '',\n `addtime` TEXT DEFAULT '',\n `domain` TEXT DEFAULT '',\n `sid` int DEFAULT 0\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "protocol", "TEXT", 0, "''", 0], + [2, "ports", "TEXT", 0, null, 0], + [3, "types", "TEXT", 0, null, 0], + [4, "address", "TEXT", 0, "''", 0], + [5, "brief", "TEXT", 0, "''", 0], + [6, "addtime", "TEXT", 0, "''", 0], + [7, "domain", "TEXT", 0, "", 0], + [8, "sid", "INTEGER", 0, 0, 0] + ] + }, + "firewall_trans": { + "sql": "CREATE TABLE `firewall_trans` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `start_port` TEXT,\n `ended_ip` TEXT,\n `ended_port` TEXT,\n `protocol` TEXT DEFAULT '',\n `addtime` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "start_port", "TEXT", 0, null, 0], + [2, "ended_ip", "TEXT", 0, null, 0], + [3, "ended_port", "TEXT", 0, null, 0], + [4, "protocol", "TEXT", 0, "''", 0], + [5, "addtime", "TEXT", 0, "''", 0] + ] + } + }, + "log.db": { + "logs": { + "sql": "CREATE TABLE `logs` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `type` TEXT,\n `log` TEXT,\n `addtime` TEXT,\n `uid` integer DEFAULT '1',\n `username` TEXT DEFAULT 'system'\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "type", "TEXT", 0, null, 0], + [2, "log", "TEXT", 0, null, 0], + [3, "addtime", "TEXT", 0, null, 0], + [4, "uid", "integer", 0, "'1'", 0], + [5, "username", "TEXT", 0, "'system'", 0] + ] + } + }, + "crontab.db": { + "crontab": { + "sql": "CREATE TABLE \"crontab\" (\n \"id\" INTEGER PRIMARY KEY AUTOINCREMENT,\n \"name\" TEXT,\n \"type\" TEXT,\n \"where1\" TEXT,\n \"where_hour\" INTEGER,\n \"where_minute\" INTEGER,\n \"echo\" TEXT,\n \"addtime\" TEXT,\n \"status\" INTEGER DEFAULT 1,\n \"save\" INTEGER DEFAULT 3,\n \"backupTo\" TEXT DEFAULT off,\n \"sName\" TEXT,\n \"sBody\" TEXT,\n \"sType\" TEXT,\n \"urladdress\" TEXT,\n \"save_local\" INTEGER DEFAULT 0,\n \"notice\" INTEGER DEFAULT 0,\n \"notice_channel\" TEXT DEFAULT '',\n `db_type` TEXT DEFAULT '',\n `split_type` TEXT DEFAULT '',\n `split_value` INTEGER DEFAULT 0,\n `rname` TEXT DEFAULT '',\n `type_id` INTEGER\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "type", "TEXT", 0, null, 0], + [3, "where1", "TEXT", 0, null, 0], + [4, "where_hour", "INTEGER", 0, null, 0], + [5, "where_minute", "INTEGER", 0, null, 0], + [6, "echo", "TEXT", 0, null, 0], + [7, "addtime", "TEXT", 0, null, 0], + [8, "status", "INTEGER", 0, "1", 0], + [9, "save", "INTEGER", 0, "3", 0], + [10, "backupTo", "TEXT", 0, "off", 0], + [11, "sName", "TEXT", 0, null, 0], + [12, "sBody", "TEXT", 0, null, 0], + [13, "sType", "TEXT", 0, null, 0], + [14, "urladdress", "TEXT", 0, null, 0], + [15, "save_local", "INTEGER", 0, "0", 0], + [16, "notice", "INTEGER", 0, "0", 0], + [17, "notice_channel", "TEXT", 0, "''", 0], + [18, "db_type", "TEXT", 0, "''", 0], + [19, "split_type", "TEXT", 0, "''", 0], + [20, "split_value", "INTEGER", 0, "0", 0], + [21, "rname", "TEXT", 0, "''", 0], + [22, "type_id", "INTEGER", 0, null, 0], + [23, "backup_mode", "TEXT", 0, "''", 0], + [24, "db_backup_path", "TEXT", 0, "''", 0], + [25, "time_type", "TEXT", 0, "''", 0], + [26, "special_time", "TEXT", 0, "''", 0], + [27, "flock","INTEGER", 0, "0", 0], + [28, "post_param", "TEXT", 0, "''", 0], + [29, "log_cut_path", "TEXT", 0, "''", 0], + [30, "user_agent", "TEXT", 0, "''", 0], + [31, "version", "TEXT", 0, "''", 0], + [32, "table_list", "TEXT", 0, "''", 0], + [33, "time_set", "TEXT", 0, "''", 0], + [34, "keyword", "TEXT", 0, "''", 0] + ] + } + }, + "backup.db": { + "backup": { + "sql": "CREATE TABLE `backup` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `type` INTEGER,\n `name` TEXT,\n `pid` INTEGER,\n `filename` TEXT,\n `size` INTEGER,\n `addtime` TEXT,\n\t`ps` STRING DEFAULT '\u65e0',\n\t`cron_id` INTEGER DEFAULT 0\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "type", "INTEGER", 0, null, 0], + [2, "name", "TEXT", 0, null, 0], + [3, "pid", "INTEGER", 0, null, 0], + [4, "filename", "TEXT", 0, null, 0], + [5, "size", "INTEGER", 0, null, 0], + [6, "addtime", "TEXT", 0, null, 0], + [7, "ps", "STRING", 0, "'\u65e0'", 0], + [8, "cron_id", "INTEGER", 0, "0", 0] + ] + } + }, + "task.db": { + "boce_task": { + "sql": "CREATE TABLE `boce_task` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` STRING (64),\n `url` STRING (128),\n `status` INTEGER,\n `channel` STRING (128),\n `cycle` INTEGER default 10,\n `addtime` INTEGER default 0,\n `address` STRING (128) default 'localhost',\n `keyword` STRING (128) default '',\n `status_code` BOOLEAN default 0,\n `delay` INTEGER default 0,\n `similarity` TEXT default '',\n `size` INTEGER default 0,\n `sensitive` BOOLEAN default 0,\n `alarm_count` integer default -1\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "STRING (64)", 0, null, 0], + [2, "url", "STRING (128)", 0, null, 0], + [3, "status", "INTEGER", 0, null, 0], + [4, "channel", "STRING (128)", 0, null, 0], + [5, "cycle", "INTEGER", 0, "10", 0], + [6, "addtime", "INTEGER", 0, "0", 0], + [7, "address", "STRING (128)", 0, "'localhost'", 0], + [8, "keyword", "STRING (128)", 0, "''", 0], + [9, "status_code", "BOOLEAN", 0, "0", 0], + [10, "delay", "INTEGER", 0, "0", 0], + [11, "similarity", "TEXT", 0, "''", 0], + [12, "size", "INTEGER", 0, "0", 0], + [13, "sensitive", "BOOLEAN", 0, "0", 0], + [14, "alarm_count", "integer", 0, "-1", 0] + ] + }, + "boce_list": { + "sql": "CREATE TABLE `boce_list` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `pid` INTEGER,\n `data` TEXT,\n `status` INTEGER,\n `addtime` INTEGER\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "pid", "INTEGER", 0, null, 0], + [2, "data", "TEXT", 0, null, 0], + [3, "status", "INTEGER", 0, null, 0], + [4, "addtime", "INTEGER", 0, null, 0] + ] + }, + "task_list": { + "sql": "CREATE TABLE `task_list` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` \t\t\tTEXT,\n `type`\t\t\tTEXT,\n `status` \t\t\tINTEGER,\n `shell` \t\t\tTEXT,\n `other` TEXT,\n `exectime` \t \tINTEGER,\n `endtime` \t \tINTEGER,\n `addtime`\t\t\tINTEGER\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "type", "TEXT", 0, null, 0], + [3, "status", "INTEGER", 0, null, 0], + [4, "shell", "TEXT", 0, null, 0], + [5, "other", "TEXT", 0, null, 0], + [6, "exectime", "INTEGER", 0, null, 0], + [7, "endtime", "INTEGER", 0, null, 0], + [8, "addtime", "INTEGER", 0, null, 0] + ] + }, + "tasks": { + "sql": "CREATE TABLE `tasks` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` \t\t\tTEXT,\n `type`\t\t\tTEXT,\n `status` \t\tTEXT,\n `addtime` \tTEXT,\n `start` \t INTEGER,\n `end` \t INTEGER,\n `execstr` \tTEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "type", "TEXT", 0, null, 0], + [3, "status", "TEXT", 0, null, 0], + [4, "addtime", "TEXT", 0, null, 0], + [5, "start", "INTEGER", 0, null, 0], + [6, "end", "INTEGER", 0, null, 0], + [7, "execstr", "TEXT", 0, null, 0] + ] + } + }, + "default.db": { + "div_list": { + "sql": "CREATE TABLE `div_list` (\n`id` INTEGER PRIMARY KEY AUTOINCREMENT,\n`div` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "div", "TEXT", 0, null, 0] + ] + }, + "download_token": { + "sql": "CREATE TABLE `download_token` (\n`id` INTEGER PRIMARY KEY AUTOINCREMENT,\n`token` REAL,\n`filename` REAL,\n`total` INTEGER DEFAULT 0,\n`expire` INTEGER,\n`password` REAL,\n`ps` REAL,\n`addtime` INTEGER\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "token", "REAL", 0, null, 0], + [2, "filename", "REAL", 0, null, 0], + [3, "total", "INTEGER", 0, "0", 0], + [4, "expire", "INTEGER", 0, null, 0], + [5, "password", "REAL", 0, null, 0], + [6, "ps", "REAL", 0, null, 0], + [7, "addtime", "INTEGER", 0, null, 0] + ] + }, + "messages": { + "sql": "CREATE TABLE `messages` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `level` TEXT,\n `msg` TEXT,\n `state` INTEGER DEFAULT 0,\n `expire` INTEGER,\n `addtime` INTEGER,\n `send` integer DEFAULT 0,\n `retry_num` integer DEFAULT 0\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "level", "TEXT", 0, null, 0], + [2, "msg", "TEXT", 0, null, 0], + [3, "state", "INTEGER", 0, "0", 0], + [4, "expire", "INTEGER", 0, null, 0], + [5, "addtime", "INTEGER", 0, null, 0], + [6, "send", "integer", 0, "0", 0], + [7, "retry_num", "integer", 0, "0", 0] + ] + }, + "temp_login": { + "sql": "CREATE TABLE `temp_login` (\n`id` INTEGER PRIMARY KEY AUTOINCREMENT,\n`token` REAL,\n`salt` REAL,\n`state` INTEGER,\n`login_time` INTEGER,\n`login_addr` REAL,\n`logout_time` INTEGER,\n`expire` INTEGER,\n`addtime` INTEGER\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "token", "REAL", 0, null, 0], + [2, "salt", "REAL", 0, null, 0], + [3, "state", "INTEGER", 0, null, 0], + [4, "login_time", "INTEGER", 0, null, 0], + [5, "login_addr", "REAL", 0, null, 0], + [6, "logout_time", "INTEGER", 0, null, 0], + [7, "expire", "INTEGER", 0, null, 0], + [8, "addtime", "INTEGER", 0, null, 0] + ] + }, + "security": { + "sql": "CREATE TABLE `security` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `type` TEXT,\n `log` TEXT,\n `addtime` INTEGER DEFAULT 0\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "type", "TEXT", 0, null, 0], + [2, "log", "TEXT", 0, null, 0], + [3, "addtime", "INTEGER", 0, "0", 0] + ] + }, + "send_msg": { + "sql": "CREATE TABLE `send_msg` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` TEXT,\n `send_type` TEXT,\n `msg` TEXT,\n `is_send` TEXT,\n `type` TEXT,\n `inser_time` TEXT DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "send_type", "TEXT", 0, null, 0], + [3, "msg", "TEXT", 0, null, 0], + [4, "is_send", "TEXT", 0, null, 0], + [5, "type", "TEXT", 0, null, 0], + [6, "inser_time", "TEXT", 0, "''", 0] + ] + }, + "send_settings": { + "sql": "CREATE TABLE `send_settings` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `name` TEXT,\n `type` TEXT,\n `path` TEXT,\n `send_type` TEXT,\n `last_time` TEXT,\n `time_frame` TEXT,\n `inser_time` TEXT DEFAULT''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "name", "TEXT", 0, null, 0], + [2, "type", "TEXT", 0, null, 0], + [3, "path", "TEXT", 0, null, 0], + [4, "send_type", "TEXT", 0, null, 0], + [5, "last_time", "TEXT", 0, null, 0], + [6, "time_frame", "TEXT", 0, null, 0], + [7, "inser_time", "TEXT", 0, "''", 0] + ] + }, + "ssh_login_record": { + "sql": "CREATE TABLE `ssh_login_record` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `addr` TEXT,\n `server_ip` TEXT,\n `user_agent` TEXT,\n `ssh_user` TEXT,\n `login_time` INTEGER DEFAULT 0,\n `close_time` INTEGER DEFAULT 0,\n `video_addr` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "addr", "TEXT", 0, null, 0], + [2, "server_ip", "TEXT", 0, null, 0], + [3, "user_agent", "TEXT", 0, null, 0], + [4, "ssh_user", "TEXT", 0, null, 0], + [5, "login_time", "INTEGER", 0, "0", 0], + [6, "close_time", "INTEGER", 0, "0", 0], + [7, "video_addr", "TEXT", 0, null, 0] + ] + }, + "rsync_oss": { + "sql": "CREATE TABLE `rsync_oss` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `rsync_path` DEFAULT '',\n `rsync_name` DEFAULT '',\n `all_num` INTEGER DEFAULT 0,\n `rsync_num` INTEGER DEFAULT 0,\n `recycle_bin` DEFAULT '',\n `where_1` DEFAULT '',\n `where_2` DEFAULT '',\n `webdav` DEFAULT '',\n `ftp` DEFAULT '',\n `alioss` DEFAULT '',\n `txcos` DEFAULT '',\n `qiniu` DEFAULT '',\n `aws` DEFAULT '',\n `upyun` DEFAULT '',\n `obs` DEFAULT '',\n `bos` DEFAULT '',\n `gcloud_storage` DEFAULT '', \n `gdrive` DEFAULT '',\n `notice` DEFAULT '',\n `notice_channel` DEFAULT '',\n `rsync_status` DEFAULT '',\n `rsync_cycle` DEFAULT '',\n `last_time` TEXT DEFAULT '',\n `rsync_mode` TEXT DEFAULT '',\n `is_del` TEXT DEFAULT '',\n `add_time` DEFAULT ''\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "rsync_path", "", 0, "''", 0], + [2, "rsync_name", "", 0, "''", 0], + [3, "all_num", "INTEGER", 0, "0", 0], + [4, "rsync_num", "INTEGER", 0, "0", 0], + [5, "recycle_bin", "", 0, "''", 0], + [6, "where_1", "", 0, "''", 0], + [7, "where_2", "", 0, "''", 0], + [8, "webdav", "", 0, "''", 0], + [9, "ftp", "", 0, "''", 0], + [10, "alioss", "", 0, "''", 0], + [11, "txcos", "", 0, "''", 0], + [12, "qiniu", "", 0, "''", 0], + [13, "aws", "", 0, "''", 0], + [14, "upyun", "", 0, "''", 0], + [15, "obs", "", 0, "''", 0], + [16, "bos", "", 0, "''", 0], + [17, "gcloud_storage", "", 0, "''", 0], + [18, "gdrive", "", 0, "''", 0], + [19, "notice", "", 0, "''", 0], + [20, "notice_channel", "", 0, "''", 0], + [21, "rsync_status", "", 0, "''", 0], + [22, "rsync_cycle", "", 0, "''", 0], + [23, "last_time", "TEXT", 0, "''", 0], + [24, "rsync_mode", "TEXT", 0, "''", 0], + [25, "is_del", "TEXT", 0, "''", 0], + [26, "add_time", "", 0, "''", 0] + ] + }, + "panel_search_log": { + "sql": "CREATE TABLE `panel_search_log` (\n `id` INTEGER PRIMARY KEY AUTOINCREMENT,\n `rtext` TEXT,\n `exts` TEXT,\n `path` TEXT,\n `mode` TEXT,\n `isword` TEXT,\n `iscase` TEXT,\n `noword` TEXT,\n `backup_path` TEXT,\n `time` TEXT\n)", + "fields": [ + [0, "id", "INTEGER", 0, null, 1], + [1, "rtext", "TEXT", 0, null, 0], + [2, "exts", "TEXT", 0, null, 0], + [3, "path", "TEXT", 0, null, 0], + [4, "mode", "TEXT", 0, null, 0], + [5, "isword", "TEXT", 0, null, 0], + [6, "iscase", "TEXT", 0, null, 0], + [7, "noword", "TEXT", 0, null, 0], + [8, "backup_path", "TEXT", 0, null, 0], + [9, "time", "TEXT", 0, null, 0] + ] + } + } +} diff --git a/config/docker_project_info.json b/config/docker_project_info.json new file mode 100644 index 00000000..7e24b0f5 --- /dev/null +++ b/config/docker_project_info.json @@ -0,0 +1,338 @@ +[ + { + "id": 1, + "sort": 1, + "Top-notch": 1, + "template_id": 1, + "server_name": "nextcloud", + "title": "Open source private cloud disk", + "version": "1.0.0", + "home": "https://nextcloud.com/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "20230406", + "icon": "", + "ps": "Nextcloud is an open source private cloud project, which can quickly deploy your own or your team's private cloud disk" + }, + { + "id": 2, + "sort": 2, + "Top-notch": 1, + "template_id": 2, + "server_name": "redis", + "title": "Open source key-value database", + "version": "1.0.0", + "home": "https://redis.io/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304012", + "icon": "", + "ps": "Millions of developers use it as an open source in-memory data store for databases, caches, streaming engines, and message brokers" + }, + { + "id": 3, + "sort": 3, + "Top-notch": 1, + "template_id": 3, + "server_name": "jenkins", + "title": "Continuous integration and delivery servers", + "version": "1.0.0", + "home": "http://www.jenkins.org.cn/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304012", + "icon": "", + "ps": "Jenkins, the leading open source automation server, offers hundreds of plugins to support building, deploying, and automating any project." + }, + { + "id": 4, + "sort": 4, + "Top-notch": 1, + "template_id": 4, + "server_name": "gitlab-ce", + "title": "Open source project for a repository management system", + "version": "1.0.0", + "home": "https://about.gitlab.com/", + "type": 1, + "bt_web": 1, + "cpu": 2, + "mem": 2, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "", + "ps": "GitLab is an open source project for repository management system, using Git as a code management tool, and on this basis to build a web service." + }, + { + "id": 5, + "sort": 5, + "Top-notch": 1, + "template_id": 5, + "server_name": "kodbox", + "title": "Web file manager", + "version": "1.0.0", + "home": "https://kodcloud.com/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n\n", + "ps": "kodbox Cloud (formerly Mango Cloud KodExplorer) is the industry's leading government/enterprise private cloud and online document management system." + }, + { + "id": 6, + "sort": 6, + "Top-notch": 1, + "template_id": 6, + "server_name": "mongodb", + "title": "Database based on distributed file storage", + "version": "1.0.0", + "home": "https://www.mongodb.com/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "", + "ps": "It provides a scalable and high-performance data storage solution for WEB applications." + }, + { + "id": 7, + "sort": 7, + "Top-notch": 1, + "template_id": 7, + "server_name": "nexus3", + "title": "Very popular warehouse management software", + "version": "1.0.0", + "home": "https://www.sonatype.com/products/nexus-repository", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n\n", + "ps": "SonatypeNexus3 software repository server, which is both a private software repository server and can serve as a proxy and cache server to fetch content from public software repositories." + }, + { + "id": 8, + "sort": 8, + "Top-notch": 1, + "template_id": 8, + "server_name": "awvs", + "title": "Web Vulnerability Scanner", + "version": "1.0.0", + "home": "https://www.acunetix.com/vulnerability-scanner/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "", + "ps": "It is a complete Web application security testing solution that can be used either on its own or as part of a complex environment." + }, + { + "id": 9, + "sort": 9, + "Top-notch": 1, + "template_id": 9, + "server_name": "kafka", + "title": "Open source distributed event streaming platform", + "version": "1.0.0", + "home": "https://kafka.apache.org/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "", + "ps": "Provides high-performance data pipelines, streaming analytics, data integration, and mission-critical applications." + }, + { + "id": 11, + "sort": 11, + "Top-notch": 1, + "template_id": 11, + "server_name": "solr", + "title": "A standalone enterprise search application server", + "version": "1.0.0", + "home": "https://solr.apache.org/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n\n", + "ps": "Solr is a popular, blazing fast, open-source enterprise search platform." + }, + { + "id": 12, + "sort": 12, + "Top-notch": 1, + "template_id": 12, + "server_name": "gogs", + "title": "A self-service Git service that's easy to set up", + "version": "1.0.0", + "home": "https://gogs.io/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n", + "ps": "The easiest, fastest, and easiest way to set up a self-service Git service." + }, + { + "id": 14, + "sort": 14, + "Top-notch": 1, + "template_id": 14, + "server_name": "teleport", + "title": "An easy to use open source bastion host system", + "version": "1.0.0", + "home": "https://tp4a.com/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n", + "ps": "Teleport is a simple and easy to use open source fortress machine system. It supports RDP/SSH/SFTP/Telnet protocol remote connection and audit management." + }, + { + "id": 15, + "sort": 15, + "Top-notch": 1, + "template_id": 15, + "server_name": "prometheus", + "title": "Leading open source monitoring solution.", + "version": "1.0.0", + "home": "https://prometheus.io/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "", + "ps": "The Prometheus monitoring system and time series database." + }, + { + "id": 16, + "sort": 16, + "Top-notch": 1, + "template_id": 16, + "server_name": "metabase", + "title": "Open source business intelligence tools", + "version": "1.0.0", + "home": "https://www.metabase.com/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n", + "ps": "Let your company explore the data on its own with a friendly user experience and integrated tools for fast analysis." + }, + { + "id": 17, + "sort": 17, + "Top-notch": 1, + "template_id": 17, + "server_name": "grafana", + "title": "Open source data visualization tool", + "version": "1.0.0", + "home": "https://grafana.com/grafana/", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "", + "ps": "With Grafana, you can create, explore, and share all your data through beautiful, flexible dashboards." + }, + { + "id": 18, + "sort": 18, + "Top-notch": 1, + "template_id": 18, + "server_name": "nacos", + "title": "Configuration management and service platform", + "version": "1.0.0", + "home": "https://nacos.io/zh-cn/index.html", + "type": 1, + "bt_web": 1, + "cpu": 1, + "mem": 1, + "disk": 10, + "tip": "lib", + "help": "", + "author": "", + "date": "202304013", + "icon": "\n\n \n\n", + "ps": "A dynamic service discovery, configuration management, and service management platform that makes it easier to build cloud-native applications." + } +] diff --git a/config/menu.json b/config/menu.json index 3d4cc54b..0ad6cdd2 100644 --- a/config/menu.json +++ b/config/menu.json @@ -13,6 +13,13 @@ "id": "memuAsite", "sort": 2 }, + { + "title": "WP Toolkit", + "href": "/wp/toolkit", + "class": "menu_wp_toolkit", + "id": "memuAwptoolkit", + "sort": 2 + }, { "title": "FTP", "href": "/ftp", @@ -105,4 +112,4 @@ "id": "dologin", "sort": 14 } -] \ No newline at end of file +] diff --git a/config/php_versions.json b/config/php_versions.json new file mode 100644 index 00000000..266979ec --- /dev/null +++ b/config/php_versions.json @@ -0,0 +1 @@ +["52","53","54","55","56","70","71","72","73","74","80","81","82","83","84","90","91"] \ No newline at end of file diff --git a/config/safe_autofix.json b/config/safe_autofix.json new file mode 100644 index 00000000..56de2e00 --- /dev/null +++ b/config/safe_autofix.json @@ -0,0 +1,45 @@ +[ + "sw_ping", + "sw_panel_swing", + "sw_ssh_passmin", + "sw_ssh_passmax", + "sw_ssh_clientalive", + "sw_panel_control", + "sw_ssh_notpass", + "sw_cve_2021_4034", + "sw_php_expose", + "sw_ssh_maxauth", + "sw_ssh_security", + "sw_files_recycle_bin", + "sw_kernel_space", + "sw_nginx_server", + "sw_ssh_passwarn", + "sw_site_spath", + "sw_tcp_syn_cookie", + "sw_alias_ls_rm", + "sw_bootloader_mod", + "sw_ssh_v2", + "sw_ftp_umask", + "sw_chmod_sid", + "sw_time_out", + "sw_debug_mode", + "sw_system_user", + "sw_pip_poison", + "sw_docker_mod", + "sw_ftp_root", + "sw_chmod_stickybit", + "sw_telnet_server", + "sw_strace_backdoor", + "sw_mongodb_auth", + "sw_httpd_version_leak", + "sw_php_display_errors", + "sw_php_backdoor", + "sw_httpd_trace_enable", + "sw_nginx_malware", + "sw_ssh_login_grace", + "sw_sudoers_nopasswd", + "sw_suid_dumpable", + "sw_tmp_malware", + "sw_protected_hardlinks", + "sw_protected_symlinks" +] diff --git a/data/pay_type.json b/data/pay_type.json index ab37d8ff..b76928de 100644 --- a/data/pay_type.json +++ b/data/pay_type.json @@ -8,11 +8,10 @@ "price": "188.3", "ps": [ "All paid plugins", - "Low then $0.52/day", - "2 free 1-year DV SSL (for year)", - "Replaceable authorized IP", - "Priority response service", "15 days no reason to refund", + "Replaceable authorized IP", + "2 free 1-year DV SSL (for year)", + "Priority response service", "Paid service group (for year)" ] @@ -201,4 +200,4 @@ "preview": "" }] } -] \ No newline at end of file +] diff --git a/data/softList.conf b/data/softList.conf index d63c585e..c1b8bf1f 100644 --- a/data/softList.conf +++ b/data/softList.conf @@ -6,4 +6,4 @@ {"name":"Tomcat","versions":[{"status":false,"version":"7"},{"status":false,"version":"8"},{"status":false,"version":"9"}],"type":"语言解释器","msg":"java-ee解释器","shell":"tomcat.sh","check":"server/tomcat/bin/catalina.sh"}, {"name":"phpMyAdmin","versions":[{"status":false,"version":"4.0"},{"status":false,"version":"4.4"},{"status":false,"version":"4.7"},{"status":false,"version":"4.8"},{"status":false,"version":"4.9"},{"status":false,"version":"5.0"}],"type":"数据库工具","msg":"Web端MySQL管理工具","shell":"phpmyadmin.sh","check":"server/phpmyadmin/version.pl"}, {"name":"DNS-Server","versions":[{"status":false,"version":"3"}],"type":"DNS server","msg":"DNS Manager","shell":"dns.sh","check":"server/panel/plugin/dns_manager/dns_manager_main.py"}, -{"name":"Mail-Server","versions":[{"status":false,"version":"4"}],"type":"Mail Server","msg":"Mail Server","shell":"mail.sh","check":"server/panel/plugin/mail_sys/mail_sys_main.py"}] \ No newline at end of file +{"name":"Mail-Server","versions":[{"status":false,"version":"3"}],"type":"Mail Server","msg":"Mail Server","shell":"mail.sh","check":"server/panel/plugin/mail_sys/mail_sys_main.py"}] diff --git a/init.sh b/init.sh index 23ab81df..2fe873b7 100644 --- a/init.sh +++ b/init.sh @@ -11,14 +11,25 @@ # Short-Description: starts bt # Description: starts the bt ### END INIT INFO + panel_init(){ + + if [ -f "/etc/redhat-release" ]; then + os_version=$(cat /etc/redhat-release | grep -E "Red Hat|CentOS" | grep -Eo '([0-9]+\.)+[0-9]+' | grep -Eo '^[0-9]') + fi + OPENSSL_VER=$(openssl version|grep -oE '1.0|1.1.0') + if [ "$os_version" == "7" ] || [ "${OPENSSL_VER}" ]; then + if [ -d /usr/local/openssl111 ]; then + export LD_LIBRARY_PATH=/usr/local/openssl111/lib:$LD_LIBRARY_PATH + fi + fi + panel_path=/www/server/panel pidfile=$panel_path/logs/panel.pid cd $panel_path - env_path=$panel_path/pyenv/bin/activate + env_path=$panel_path/pyenv/bin/python3 if [ -f $env_path ];then - source $env_path - pythonV=$panel_path/pyenv/bin/python + pythonV=$panel_path/pyenv/bin/python3 chmod -R 700 $panel_path/pyenv/bin else pythonV=/usr/bin/python @@ -36,9 +47,9 @@ panel_init(){ chmod 700 $panel_path/BT-Task log_file=$panel_path/logs/error.log task_log_file=$panel_path/logs/task.log - if [ -f $panel_path/data/ssl.pl ];then - log_file=/dev/null - fi +# if [ -f $panel_path/data/ssl.pl ];then +# log_file=/dev/null +# fi port=$(cat $panel_path/data/port.pl) } @@ -65,7 +76,7 @@ panel_start() get_panel_pids if [ "$isStart" == '' ];then rm -f $pidfile - panel_port_check + echo -e "Starting Bt-Panel...\c" nohup $panel_path/BT-Panel >> $log_file 2>&1 & isStart="" @@ -81,6 +92,7 @@ panel_start() fi done if [ "$isStart" == '' ];then + panel_port_check echo -e "\033[31mfailed\033[0m" echo '------------------------------------------------------' tail -n 20 $log_file @@ -91,7 +103,7 @@ panel_start() else echo "Starting Bt-Panel... Bt-Panel (pid $(echo $isStart)) already running" fi - + get_task_pids if [ "$isStart" == '' ];then echo -e "Starting Bt-Tasks... \c" @@ -114,7 +126,7 @@ panel_start() panel_port_check() { - is_process=$(lsof -n -P -i:$port|grep LISTEN|grep -v grep|awk '{print $1}'|sort|uniq|xargs) + is_process=$(lsof -n -P -i:$port -sTCP:LISTEN|grep LISTEN|grep -v grep|awk '{print $1}'|sort|uniq|xargs) for pn in ${is_process[@]} do if [ "$pn" = "nginx" ];then @@ -172,6 +184,14 @@ panel_port_check() fi } +stop_webserver() +{ + webserver_ctl=$panel_path/script/webserver-ctl.sh + if [ -f $webserver_ctl ];then + bash $webserver_ctl stop &> /dev/null + fi +} + panel_stop() { echo -e "Stopping Bt-Tasks...\c"; @@ -194,6 +214,9 @@ panel_stop() if [ -f $pidfile ];then rm -f $pidfile fi + + stop_webserver + echo -e " \033[32mdone\033[0m" } @@ -206,7 +229,7 @@ panel_status() else echo -e "\033[31mBt-Panel not running\033[0m" fi - + get_task_pids if [ "$isStart" != '' ];then echo -e "\033[32mBt-Task (pid $isStart) already running\033[0m" @@ -218,60 +241,77 @@ panel_status() panel_reload() { isStart=$(ps aux|grep 'runserver:app'|grep -v grep|awk '{print $2}') - if [ "$isStart" != '' ];then + if [ "$isStart" != '' ];then kill -9 $isStart sleep 0.5 fi get_panel_pids + stop_webserver if [ "$isStart" != '' ];then + get_panel_pids + for p in ${arr[@]} + do + kill -9 $p + done + rm -f $pidfile + echo -e "Reload Bt-Panel.\c"; + nohup $panel_path/BT-Panel >> $log_file 2>&1 & + isStart="" + n=0 + while [[ "$isStart" == "" ]]; + do + echo -e ".\c" + sleep 0.5 + get_panel_pids + let n+=1 + if [ $n -gt 8 ];then + break; + fi + done + if [ "$isStart" == '' ];then + panel_port_check + echo -e "\033[31mfailed\033[0m" + echo '------------------------------------------------------' + tail -n 20 $log_file + echo '------------------------------------------------------' + echo -e "\033[31mError: BT-Panel service startup failed.\033[0m" + return; + fi - get_panel_pids - for p in ${arr[@]} - do - kill -9 $p - done - rm -f $pidfile - panel_port_check - echo -e "Reload Bt-Panel.\c"; - nohup $panel_path/BT-Panel >> $log_file 2>&1 & - isStart="" - n=0 - while [[ "$isStart" == "" ]]; - do - echo -e ".\c" - sleep 0.5 - get_panel_pids - let n+=1 - if [ $n -gt 8 ];then - break; - fi - done - if [ "$isStart" == '' ];then - echo -e "\033[31mfailed\033[0m" - echo '------------------------------------------------------' - tail -n 20 $log_file - echo '------------------------------------------------------' - echo -e "\033[31mError: BT-Panel service startup failed.\033[0m" - return; + echo -e " \033[32mdone\033[0m" + else + echo -e "\033[31mBt-Panel not running\033[0m" + panel_start fi - echo -e " \033[32mdone\033[0m" - else - echo -e "\033[31mBt-Panel not running\033[0m" - panel_start - fi } install_used() { - if [ ! -f $panel_path/aliyun.pl ];then - return; + if [ -f $panel_path/aliyun.pl ];then + password=$(cat /dev/urandom | head -n 16 | md5sum | head -c 12) + username=$($pythonV $panel_path/tools.py panel $password) + echo "$password" > $panel_path/default.pl + rm -f $panel_path/aliyun.pl + chattr +i $panel_path/default.pl fi - password=$(cat /dev/urandom | head -n 16 | md5sum | head -c 12) - username=$($pythonV $panel_path/tools.py panel $password) - safe_path=$(cat /dev/urandom | head -n 16 | md5sum | head -c 8) - echo "/$safe_path" > $panel_path/data/admin_path.pl - echo "$password" > $panel_path/default.pl - rm -f $panel_path/aliyun.pl + + if [ -f $panel_path/php_mysql_auto.pl ];then + bash $panel_path/script/mysql_auto.sh &> /dev/null + bash $panel_path/script/php_auto.sh &> /dev/null + rm -f $panel_path/php_mysql_auto.pl + fi + + pip_file=/www/server/panel/pyenv/bin/pip3 + python_file=/www/server/panel/pyenv/bin/python3 + if [ -f $pip_file ];then + is_rep=$(ls -l /usr/bin/btpip|grep pip3.) + if [ "${is_rep}" != "" ];then + rm -f /usr/bin/btpip /usr/bin/btpython + ln -sf $pip_file /usr/bin/btpip + ln -sf $python_file /usr/bin/btpython + fi + fi + } error_logs() @@ -290,7 +330,7 @@ case "$1" in ;; 'restart') panel_stop - sleep 1 + sleep 1 panel_start ;; 'reload') @@ -306,22 +346,31 @@ case "$1" in $pythonV $panel_path/tools.py cli $2 ;; 'default') - LOCAL_IP=$(ip addr | grep -E -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -E -v "^127\.|^255\.|^0\." | head -n 1) port=$(cat $panel_path/data/port.pl) password=$(cat $panel_path/default.pl) if [ -f $panel_path/data/domain.conf ];then address=$(cat $panel_path/data/domain.conf) fi + auth_path=/login if [ -f $panel_path/data/admin_path.pl ];then auth_path=$(cat $panel_path/data/admin_path.pl) fi if [ "$address" = "" ];then - address=$(curl -sS --connect-timeout 10 -m 60 https://www.aapanel.com/api/common/getClientIP) + address=$(curl -sS --connect-timeout 10 -m 20 https://www.aapanel.com/api/common/getClientIP) + # IPV6_REGEX="^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$" + IPV6_REGEX="^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$" + if [[ $address =~ $IPV6_REGEX ]]; then + address=$(echo "[$address]") + fi fi - pool=http - if [ -f $panel_path/data/ssl.pl ];then - pool=https - fi + pool=http + if [ -f $panel_path/data/ssl.pl ];then + pool=https + fi + if [ "$auth_path" == "/" ];then + auth_path=/login + fi + LOCAL_IP=$(ip addr | grep -E -o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -E -v "^127\.|^255\.|^0\." | head -n 1) echo -e "==================================================================" echo -e "\033[32maaPanel default info!\033[0m" echo -e "==================================================================" @@ -331,7 +380,7 @@ case "$1" in echo -e "password: $password" echo -e "\033[33mWarning:\033[0m" echo -e "\033[33mIf you cannot access the panel, \033[0m" - echo -e "\033[33mrelease the following port (7800|888|80|443|20|21) in the security group\033[0m" + echo -e "\033[33mrelease the following port ($port|888|80|443|20|21) in the security group\033[0m" echo -e "==================================================================" ;; *) diff --git a/install/public.sh b/install/public.sh index 11659009..09104268 100644 --- a/install/public.sh +++ b/install/public.sh @@ -141,7 +141,7 @@ if [ -d "/www/server/phpmyadmin/pma" ];then rm -rf /www/server/phpmyadmin/pma EN_CHECK=$(cat /www/server/panel/config/config.json |grep English) if [ "${EN_CHECK}" ];then - curl http://download.bt.cn/install/update6_en.sh|bash + curl http://download.bt.cn/install/update_7.x_en.sh|bash else curl http://download.bt.cn/install/update6.sh|bash fi diff --git a/mod/__init__.py b/mod/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/base/__init__.py b/mod/base/__init__.py new file mode 100644 index 00000000..0bba13cd --- /dev/null +++ b/mod/base/__init__.py @@ -0,0 +1,31 @@ +import time +from typing import Dict, List, Tuple, Union + +from .process import RealProcess, Process +from .process import RealUser, User +from .process import RealServer, Server + + +def json_response( + status: bool, + msg: str = None, + data: Union[Dict, List, Tuple, bool, str, int, float] = None, + code: int = 0, + args: Union[List[str], Tuple[str]] = None, +): + if isinstance(msg, str) and args is not None: + for i in range(len(args)): + rep = '{' + str(i + 1) + '}' + msg = msg.replace(rep, args[i]) + + if msg is None: + msg = "ok" + + return { + "status": status, + "msg": msg, + "data": data, + "code": code, + "timestamp": int(time.time()) + } + diff --git a/mod/base/backup_tool/__init__.py b/mod/base/backup_tool/__init__.py new file mode 100644 index 00000000..94552888 --- /dev/null +++ b/mod/base/backup_tool/__init__.py @@ -0,0 +1,135 @@ +import os +import time +from hashlib import md5 +from typing import Optional, List, Union, Dict, Any + +from .util import DB, ExecShell, write_file, write_log +from .versions_tool import VersionTool + + +class BackupTool: + + def __init__(self): + self._backup_path: Optional[str] = None + self._sub_dir_name: str = "" + self.exec_log_file = "/tmp/mod_backup_exec.log" + + @staticmethod + def _hash_src_name(name: Union[str, bytes]) -> str: + if isinstance(name, str): + name = name.encode('utf-8') + md5_obj = md5() + md5_obj.update(name) + return md5_obj.hexdigest() + + @property + def backup_path(self) -> str: + if self._backup_path is None: + config_data = DB("config").where("id=?", (1,)).select() + if isinstance(config_data, dict): + path = config_data["backup_path"] + else: # 查询出错 + path = "/www/backup" + self._backup_path = path + return self._backup_path + + # sub_dir 可以设置为多级子目录, 如 "site/aaa", 或使用列表传递如:["site", "aaa"] + def set_sub_dir(self, sub_dir: Union[str, List[str]]) -> Optional[str]: + if isinstance(sub_dir, str): + self._sub_dir_name = sub_dir.strip("./") + elif isinstance(sub_dir, list): + self._sub_dir_name = "/".join(filter(None, [i.strip("./") for i in sub_dir])) + else: + return "不支持的类型设置" + + def backup(self, + src: str, # 源文件位置 + backup_path: Optional[str] = None, # 备份位置 + sub_dir: Union[str, List[str]] = None, # 备份目录的子目录 + site_info: Dict[str, Any] = None, # 关联的站点信息, 必须包含 id 和 name + sync=False # 是否同步执行, 默认异步由单独的线程放入后台执行 + ) -> Optional[str]: # 返回执行错误的信息 + + if not os.path.exists(src): + return "源路径不存在" + if backup_path is None: + backup_path = self.backup_path + + if not os.path.exists(backup_path): + return "备份目录不存在" + if sub_dir is not None: + set_res = self.set_sub_dir(sub_dir) + if set_res is not None: + return set_res + + target_path = os.path.join(backup_path, self._sub_dir_name) + if not os.path.isdir(target_path): + os.makedirs(target_path) + zip_name = "{}_{}.tar.gz".format(os.path.basename(src), time.strftime('%Y%m%d_%H%M%S', time.localtime())) + if sync: + return self._sync_backup(src, target_path, zip_name, site_info) + else: + return self._async_backup(src, target_path, zip_name, site_info) + + def _sync_backup(self, src: str, target_path: str, zip_name: str, site_info: dict): + try: + write_file(self.exec_log_file, "") + execStr = ("cd {} && " + "tar -zcvf '{}' --exclude=.user.ini ./ 2>&1 > {} \n" + "echo '---备份执行完成---' >> {}" + ).format(src, os.path.join(target_path, zip_name), self.exec_log_file, self.exec_log_file) + ExecShell(execStr) + if site_info is not None and "id" in site_info and "name" in site_info: + DB('backup').add( + 'type,name,pid,filename,size,addtime', + (0, zip_name, site_info["id"], os.path.join(target_path, zip_name), 0, self.get_date()) + ) + write_log('TYPE_SITE', 'SITE_BACKUP_SUCCESS', (site_info["name"],)) + except: + return "备份执行失败" + + def _async_backup(self, src: str, target_path: str, zip_name: str, site_info: dict): + import threading + + hash_name = self._hash_src_name(src) + backup_tip_path = "/tmp/mod_backup_tip" + if os.path.exists(backup_tip_path): + os.makedirs(backup_tip_path) + + tip_file = os.path.join(backup_tip_path, hash_name) + if os.path.isfile(tip_file): + mtime = os.stat(tip_file).st_mtime + if time.time() - mtime > 60 * 20: # 20 分钟未执行,认为出现在不可抗力,导致备份失败,允许再次备份 + os.remove(tip_file) + else: + return "备份进行中,请勿继续操作" + + write_file(tip_file, "") + + def _back_p(): + try: + write_file(self.exec_log_file, "") + execStr = ("cd {} && " + "tar -zcvf '{}' --exclude=.user.ini ./ 2>&1 > {} \n" + "echo '---备份执行完成---' >> {}" + ).format(src, os.path.join(target_path, zip_name), self.exec_log_file, self.exec_log_file) + ExecShell(execStr) + if site_info is not None and "id" in site_info and "name" in site_info: + DB('backup').add( + 'type,name,pid,filename,size,addtime', + (0, zip_name, site_info["id"], os.path.join(target_path, zip_name), 0, self.get_date()) + ) + write_log('TYPE_SITE', 'SITE_BACKUP_SUCCESS', (site_info["name"],)) + except: + pass + finally: + if os.path.exists(tip_file): + os.remove(tip_file) + + t = threading.Thread(target=_back_p) + t.start() + + @staticmethod + def get_date(): + # 取格式时间 + return time.strftime('%Y-%m-%d %X', time.localtime()) diff --git a/mod/base/backup_tool/util.py b/mod/base/backup_tool/util.py new file mode 100644 index 00000000..d8f96f6f --- /dev/null +++ b/mod/base/backup_tool/util.py @@ -0,0 +1,65 @@ +import sys +from typing import Optional, Callable, Tuple, Union + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + +ExecShell: Callable = public.ExecShell +write_log: Callable[[str, str, Tuple], Union[int, str, type(None)]] = public.WriteLog + + +def write_file(filename: str, s_body: str, mode='w+') -> bool: + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode=mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode=mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +def read_file(filename, mode='r') -> Optional[str]: + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): + return None + fp = None + try: + fp = open(filename, mode=mode) + f_body = fp.read() + except: + return None + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +class _DB: + + def __call__(self, table: str): + import db + with db.Sql() as t: + t.table(table) + return t + + +DB = _DB() diff --git a/mod/base/backup_tool/versions_tool.py b/mod/base/backup_tool/versions_tool.py new file mode 100644 index 00000000..1e9796b5 --- /dev/null +++ b/mod/base/backup_tool/versions_tool.py @@ -0,0 +1,291 @@ +import json +import os +import shutil +import tarfile +import time +from hashlib import md5 +from typing import Optional, List, Union, Dict, Any + +from .util import DB, ExecShell, write_file, write_log, read_file + + +class VersionTool: + _config_file = "/www/server/panel/data/version_config.json" + + def __init__(self): + self._config: Optional[Dict[str, List[Dict[str, Any]]]] = None + self._pack_class = BasePack + self.pack_path = "/www/backup/versions" + + @property + def config(self) -> Dict[str, List[Dict[str, Any]]]: + if self._config is not None: + return self._config + + data = {} + try: + res = read_file(self._config_file) + if isinstance(res, str): + data = json.loads(res) + except (json.JSONDecoder, TypeError, ValueError): + pass + self._config = data + return self._config + + def save_config(self): + if self._config is not None: + write_file(self._config_file, json.dumps(self._config)) + + def add_to_config(self, data: dict): + project_name = data.get("project_name") + self._config = None + if project_name not in self.config: + self.config[project_name] = [] + self.config[project_name].append(data) + self.save_config() + + def set_pack_class(self, pack_cls): + self._pack_class = pack_cls + + def version_list(self, project_name: str): + if project_name in self.config: + return self.config[project_name] + return [] + + def get_version_info(self, project_name: str, version: str) -> Optional[dict]: + if project_name in self.config: + for i in self.config[project_name]: + if i.get("version") == version: + return i + return None + + # 把某个路径下的文件打包并发布为一个版本 + def publish_by_src_path(self, + project_name: str, # 名称 + src_path: str, # 源路径 + version: str, # 版本号 + ps: Optional[str] = None, # 备注 + other: Optional[dict] = None, # 其他信息 + sync: bool = False, # 是否同步执行 + ): + + if project_name in self.config: + for i in self.config[project_name]: + if i["version"] == version: + return "当前版本已存在" + if not os.path.isdir(src_path): + return "源路径不存在" + + if ps is None: + ps = '' + + if other is None: + other = {} + + zip_name = "{}_{}.tar.gz".format( + os.path.basename(src_path), time.strftime('%Y%m%d_%H%M%S', time.localtime()) + ) + + data = { + "project_name": project_name, + "version": version, + "ps": ps, + "other": other, + "zip_name": zip_name, + "backup_time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + } + return self._pack_class(src_path, self.pack_path, zip_name, sync=sync, vt=self, data=data)(**other) + + def recover(self, + project_name: str, # 名称 + version: str, # 版本 + target_path: str, # 目标路径 + run_path=None + ): + if not run_path: + run_path = target_path + if project_name not in self.config: + return '项目不存在' + + target = None + for i in self.config[project_name]: + if i["version"] == version: + target = i + break + + if target is None: + return '版本不存在' + + file = os.path.join(self.pack_path, target["zip_name"]) + if not os.path.exists(file): + return '版本文件丢失' + + tmp_path = '/tmp/version_{}'.format(int(time.time())) + tar = tarfile.open(file, mode='r') + tar.extractall(tmp_path) + user_data = None + if os.path.exists(target_path): + ExecShell("chattr -i -R {}/".format(target_path)) + user_data = read_file(run_path + "/.user.ini") + ExecShell("rm -rf {}".format(target_path)) + os.makedirs(target_path) + if not os.path.exists(target_path): + os.makedirs(target_path) + ExecShell(r"\cp -rf {}/* {}".format(tmp_path, target_path)) + if user_data: + write_file(target_path + "/.user.ini", run_path) + ExecShell("chattr +i {}/.user.ini".format(run_path)) + ExecShell("rm -rf {}".format(tmp_path)) + return True + + def publish_by_file(self, + project_name: str, # 名称 + src_file: str, # 源路径 + version: str, # 版本号 + ps: Optional[str] = None, # 备注 + other: Optional[dict] = None, # 其他信息 + ): + + if project_name in self.config: + for i in self.config[project_name]: + if i["version"] == version: + return "当前版本已存在" + + if not os.path.isfile(src_file): + return "源路径不存在" + + if ps is None: + ps = '' + + if other is None: + other = {} + + zip_name = os.path.basename(src_file) + + data = { + "project_name": project_name, + "version": version, + "ps": ps, + "other": other, + "zip_name": zip_name, + "backup_time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + } + try: + shutil.copy(src_file, self.pack_path + "/" + zip_name) + except: + return "文件保存失败" + self.add_to_config(data) + return None + + def remove(self, + project_name: str, # 名称 + version: str, # 版本 + ) -> Optional[str]: + + if project_name not in self.config: + return '项目不存在' + + target = None + for i in self.config[project_name]: + if i["version"] == version: + target = i + break + + if target is None: + return '版本不存在' + + file = os.path.join(self.pack_path, target["zip_name"]) + if os.path.isfile(file): + os.remove(file) + + self.config[project_name].remove(target) + + self.save_config() + return None + + def set_ps(self, name: str, version: str, ps: str): + [i.update({'ps': ps}) for i in self.config[name] if i["version"] == version] + self.save_config() + return True + + +class BasePack: + exec_log_file = "/tmp/project_pack.log" + + def __init__(self, src_path, target_path, zip_name, sync=False, vt: VersionTool = None, data: dict = None): + self.src_path = src_path + self.target_path = target_path + self.zip_name = zip_name + self.sync = sync + self.v = vt + self._add_data = data + + def save_config(self): + self.v.add_to_config(self._add_data) + + def __call__(self, *args, **kwargs) -> Optional[str]: + if not os.path.exists(self.src_path): + return "源路径不存在" + target_path = "/www/backup/versions" + + if not os.path.isdir(target_path): + os.makedirs(target_path) + if self.sync: + return self._sync_backup(self.src_path, target_path, self.zip_name) + else: + return self._async_backup(self.src_path, target_path, self.zip_name) + + def _sync_backup(self, src: str, target_path: str, zip_name: str) -> Optional[str]: + try: + write_file(self.exec_log_file, "") + execStr = ("cd {} && " + "tar -zcvf '{}' --exclude=.user.ini ./ 2>&1 > {} \n" + "echo '---打包执行完成---' >> {}" + ).format(src, os.path.join(target_path, zip_name), self.exec_log_file, self.exec_log_file) + ExecShell(execStr) + self.save_config() + except: + return "打包执行失败" + + def _async_backup(self, src: str, target_path: str, zip_name: str): + import threading + hash_name = self._hash_src_name(src) + backup_tip_path = "/tmp/mod_version_tip" + if os.path.exists(backup_tip_path): + os.makedirs(backup_tip_path) + + tip_file = os.path.join(backup_tip_path, hash_name) + if os.path.isfile(tip_file): + mtime = os.stat(tip_file).st_mtime + if time.time() - mtime > 60 * 20: # 20 分钟未执行,认为出现在不可抗力,导致备份失败,允许再次备份 + os.remove(tip_file) + else: + return "打包进行中,请勿继续操作" + + write_file(tip_file, "") + + def _back_p(): + try: + write_file(self.exec_log_file, "") + execStr = ("cd {} && " + "tar -zcvf '{}' --exclude=.user.ini ./ 2>&1 > {} \n" + "echo '---备份执行完成---' >> {}" + ).format(src, os.path.join(target_path, zip_name), self.exec_log_file, self.exec_log_file) + ExecShell(execStr) + self.save_config() + except: + pass + finally: + if os.path.exists(tip_file): + os.remove(tip_file) + + t = threading.Thread(target=_back_p) + t.start() + + @staticmethod + def _hash_src_name(name: Union[str, bytes]) -> str: + if isinstance(name, str): + name = name.encode('utf-8') + md5_obj = md5() + md5_obj.update(name) + return md5_obj.hexdigest() diff --git a/mod/base/database_tool/__init__.py b/mod/base/database_tool/__init__.py new file mode 100644 index 00000000..3dc4ed48 --- /dev/null +++ b/mod/base/database_tool/__init__.py @@ -0,0 +1,53 @@ +from .pgsql import PgsqlTool +from .mongodb import MongodbTool +from .mysql import MysqlTool +from .sql_server import SQLServerTool + +from typing import Optional + +DB_TYPE = ( + "pgsql", + "mongodb", + "mysql", + "sqlserver" +) + + +def add_database(db_type: str, data: dict) -> Optional[str]: + """ + data: 中包含的有效参数为 + database_name:数据库名称 + server_id:数据库 id + db_user:数据库用户名 + password:数据库用户的密码 + dataAccess :链接限制方式 如:ip + address:可允许使用的ip, 配合上一个参数使用 + codeing: 编码 + ps:备注 + listen_ip: pgsql 有效,可设置访问地址 + """ + if db_type not in DB_TYPE: + return "错误的数据库类型" + + if db_type == "pgsql": + tool = PgsqlTool() + elif db_type == "mongodb": + tool = MongodbTool() + elif db_type == "mysql": + tool = MysqlTool() + else: + tool = SQLServerTool() + + f, msg = tool.add_database(data.pop("server_id"), data.pop("database_name"), **data) + if not f: + return msg + return None + + +__all__ = [ + "PgsqlTool", + "MongodbTool", + "MysqlTool", + "SQLServerTool", + "add_database", +] diff --git a/mod/base/database_tool/base.py b/mod/base/database_tool/base.py new file mode 100644 index 00000000..2540c29b --- /dev/null +++ b/mod/base/database_tool/base.py @@ -0,0 +1,34 @@ +import sys +from typing import List, Dict, Optional +from .util import DB + + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +from db_mysql import panelMysql +from database import database +from databaseModel.mongodbModel import main as mongodb +from databaseModel.pgsqlModel import main as pgsql +from databaseModel.sqlserverModel import main as sqlserver + + +class BaseDatabaseTool: + _type_name = "" + + def local_server_info(self) -> Optional[Dict]: + raise NotImplementedError() + + # 获取所有可以管理的服务器的信息 + def server_list(self) -> List[Dict]: + data = DB('database_servers').where("LOWER(db_type)=LOWER('?')", (self._type_name, )).select() + if not isinstance(data, list): + data = [] + local_server = self.local_server_info() + if local_server is not None: + data.insert(0, local_server) + return data + + # 添加一个数据库 + def add_database(self, server_id: int, database_name: str, **kwargs) -> List[Dict]: + raise NotImplementedError() diff --git a/mod/base/database_tool/mongodb.py b/mod/base/database_tool/mongodb.py new file mode 100644 index 00000000..016f8ee4 --- /dev/null +++ b/mod/base/database_tool/mongodb.py @@ -0,0 +1,51 @@ +import os +import re + +from typing import Optional, Dict, List, Union, Tuple + +from .base import BaseDatabaseTool, mongodb +from .util import read_file, GET_CLASS + + +class MongodbTool(BaseDatabaseTool): + _type_name = "mongodb" + + def local_server_info(self) -> Optional[Dict]: + bin_path = "/www/server/mongodb/bin/mongod" + if not os.path.isfile(bin_path): + return None + + conf_file = '/www/server/mongodb/config.conf' + conf = read_file(conf_file) + default_port = 27017 + if not isinstance(conf, str): + port = default_port + else: + rep_port = re.compile(r"\s*port\s*:\s*(?P\d+)", re.M) + port_res = rep_port.search(conf) + if not port_res: + port = default_port + else: + port = int(port_res.group("port")) + + return { + 'id': 0, + 'db_host': '127.0.0.1', + 'db_port': port, + 'db_user': 'root', + 'db_password': '', + 'ps': '本地服务器', + 'addtime': 0 + } + + # 添加一个数据库 + def add_database(self, server_id: int, database_name: str, **kwargs) -> Tuple[bool, str]: + get_obj = GET_CLASS() + get_obj.name = database_name + get_obj.sid = server_id + get_obj.ps = kwargs.get("ps", "") + res = mongodb().AddDatabase(get_obj) + if res["status"] is True: + return True, "添加成功" + else: + return False, res['msg'] diff --git a/mod/base/database_tool/mysql.py b/mod/base/database_tool/mysql.py new file mode 100644 index 00000000..cbe21583 --- /dev/null +++ b/mod/base/database_tool/mysql.py @@ -0,0 +1,98 @@ +import os +import re + +from typing import Optional, Dict, List, Union, Tuple + +from .base import BaseDatabaseTool, panelMysql, database +from .util import read_file, write_file, DB, GET_CLASS + + +class MysqlTool(BaseDatabaseTool): + _type_name = "mysql" + + def local_server_info(self) -> Optional[Dict]: + bin_path = "/www/server/mysql/bin/mysql" + if not os.path.isfile(bin_path): + return None + + conf_file = '/etc/my.cnf' + conf = read_file(conf_file) + default_port = 3306 + if not isinstance(conf, str): + port = default_port + else: + rep_port = re.compile(r"\s*port\s*=\s*(?P\d+)", re.M) + port_res = rep_port.search(conf) + if not port_res: + port = default_port + else: + port = int(port_res.group("port")) + + return { + 'id': 0, + 'db_host': '127.0.0.1', + 'db_port': port, + 'db_user': 'root', + 'db_password': '', + 'ps': '本地服务器', + 'addtime': 0 + } + + # 检测服务是否可以链接 + # def server_status(self, server_id: int) -> Union[Dict, str]: + # """ + # 数据库状态检测 + # """ + # db_name = None + # if server_id != 0: + # conn_config = DB("database_servers").where("id=? AND LOWER(db_type)=LOWER('mysql')", (server_id,)).find() + # if not conn_config: + # return "远程数据库信息不存在!" + # conn_config["db_name"] = None + # db_user = conn_config["db_user"] + # root_password = conn_config["db_password"] + # db_host = conn_config["db_host"] + # db_port = conn_config["db_port"] + # else: + # db_user = "root" + # root_password = DB("config").where("id=?", (1,)).getField("mysql_root") + # db_host = "localhost" + # try: + # db_port = int(panelMysql().query("show global variables like 'port'")[0][1]) + # except: + # db_port = 3306 + # mysql_obj = panelMysql() + # flag = mysql_obj.set_host(db_host, db_port, db_name, db_user, root_password) + # + # error = '' + # db_status = True + # if flag is False: + # db_status = False + # error = mysql_obj._ex + # + # return { + # "status": True, + # 'error': str(error), + # "msg": "正常" if db_status is True else "异常", + # "db_status": db_status + # } + + # 添加一个数据库 + def add_database(self, server_id: int, database_name: str, **kwargs) -> Tuple[bool, str]: + get_obj = GET_CLASS() + get_obj.name = database_name + get_obj.sid = server_id + get_obj.db_user = kwargs.get("db_user", "") + get_obj.password = kwargs.get("password", "") + get_obj.dataAccess = kwargs.get("dataAccess", "") + get_obj.address = kwargs.get("address", "") + get_obj.codeing = kwargs.get("codeing", "") + get_obj.dtype = "MySQL" + get_obj.ps = kwargs.get("ps", "") + get_obj.host = kwargs.get("host", "") + get_obj.pid = str(kwargs.get("pid", '0')) + res = database().AddDatabase(get_obj) + if res["status"] is True: + return True, "添加成功" + else: + return False, res['msg'] diff --git a/mod/base/database_tool/pgsql.py b/mod/base/database_tool/pgsql.py new file mode 100644 index 00000000..dad3decb --- /dev/null +++ b/mod/base/database_tool/pgsql.py @@ -0,0 +1,54 @@ +import os +import re + +from typing import Optional, Dict, List, Union, Tuple + +from .base import BaseDatabaseTool, pgsql +from .util import read_file, GET_CLASS + + +class PgsqlTool(BaseDatabaseTool): + _type_name = "pgsql" + + def local_server_info(self) -> Optional[Dict]: + bin_path = "/www/server/pgsql/bin/postgres" + if not os.path.isfile(bin_path): + return None + + conf_file = '/www/server/pgsql/data/postgresql.conf' + conf = read_file(conf_file) + default_port = 5432 + if not isinstance(conf, str): + port = default_port + else: + rep_port = re.compile(r"\s*port\s*=\s*(?P\d+)", re.M) + port_res = rep_port.search(conf) + if not port_res: + port = default_port + else: + port = int(port_res.group("port")) + + return { + 'id': 0, + 'db_host': '127.0.0.1', + 'db_port': port, + 'db_user': 'root', + 'db_password': '', + 'ps': '本地服务器', + 'addtime': 0 + } + + # 添加一个数据库 + def add_database(self, server_id: int, database_name: str, **kwargs) -> Tuple[bool, str]: + get_obj = GET_CLASS() + get_obj.name = database_name + get_obj.sid = server_id + get_obj.ps = kwargs.get("ps", "") + get_obj.db_user = kwargs.get("db_user", "") + get_obj.password = kwargs.get("password", "") + get_obj.listen_ip = kwargs.get("listen_ip", "") + res = pgsql().AddDatabase(get_obj) + if res["status"] is True: + return True, "添加成功" + else: + return False, res['msg'] diff --git a/mod/base/database_tool/sql_server.py b/mod/base/database_tool/sql_server.py new file mode 100644 index 00000000..278f99f9 --- /dev/null +++ b/mod/base/database_tool/sql_server.py @@ -0,0 +1,28 @@ +import os +import re + +from typing import Optional, Dict, List, Union, Tuple + +from .base import BaseDatabaseTool, sqlserver +from .util import read_file, GET_CLASS + + +class SQLServerTool(BaseDatabaseTool): + _type_name = "sqlserver" + + def local_server_info(self) -> Optional[Dict]: + return None + + # 添加一个数据库 + def add_database(self, server_id: int, database_name: str, **kwargs) -> Tuple[bool, str]: + get_obj = GET_CLASS() + get_obj.name = database_name + get_obj.sid = server_id + get_obj.ps = kwargs.get("ps", "") + get_obj.db_user = kwargs.get("db_user", "") + get_obj.password = kwargs.get("password", "") + res = sqlserver().AddDatabase(get_obj) + if res["status"] is True: + return True, "添加成功" + else: + return False, res['msg'] diff --git a/mod/base/database_tool/util.py b/mod/base/database_tool/util.py new file mode 100644 index 00000000..b21df69c --- /dev/null +++ b/mod/base/database_tool/util.py @@ -0,0 +1,68 @@ +import os +import sys +from typing import Optional, Tuple, Callable + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + +import public + + +def write_file(filename: str, s_body: str, mode='w+') -> bool: + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode=mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode=mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +def read_file(filename, mode='r') -> Optional[str]: + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): + return None + fp = None + try: + fp = open(filename, mode=mode) + f_body = fp.read() + except: + return None + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +class _DB: + + def __call__(self, table: str): + import db + with db.Sql() as t: + t.table(table) + return t + + +DB = _DB() + +GET_CLASS = public.dict_obj + +ExecShell: Callable = public.ExecShell diff --git a/mod/base/git_tool/__init__.py b/mod/base/git_tool/__init__.py new file mode 100644 index 00000000..0bf72f8c --- /dev/null +++ b/mod/base/git_tool/__init__.py @@ -0,0 +1 @@ +from .tool import GitTool, GitMager, RealGitMager diff --git a/mod/base/git_tool/install.py b/mod/base/git_tool/install.py new file mode 100644 index 00000000..afa9b4f7 --- /dev/null +++ b/mod/base/git_tool/install.py @@ -0,0 +1,40 @@ +import re +from .util import ExecShell + + +def installed(): + sh_str = "git --version" + out, error = ExecShell(sh_str) + if re.search(r"git\s+version\s+(\d+\.){1,4}\d+", out): + return True + return False + + +def version_1_5_3() -> bool: + sh_str = "git --version" + out, error = ExecShell(sh_str) + res = re.search(r"git\s+version\s+(?P(\d+\.){1,4}\d+)", out) + if not res: + return False + ver = [int(i) for i in res.group('v').split(".")] + if len(ver) < 3: + ver.extend([0] * (3 - len(ver))) + if ver[0] > 1: + return True + elif ver[0] == 1 and ver[1] > 5: + return True + elif ver[0] == 1 and ver[1] == 5 and ver[3] >= 3: + return True + return False + + +def install_git(): + check_str = "Git installed successfully" + if not installed(): + script_path = "/www/server/panel/mod/base/git_tool/install.sh" + out, error = ExecShell("bash {}".format(script_path)) + if out.find(check_str) != -1: + return True + else: + return False + return True diff --git a/mod/base/git_tool/install.sh b/mod/base/git_tool/install.sh new file mode 100644 index 00000000..697fef0e --- /dev/null +++ b/mod/base/git_tool/install.sh @@ -0,0 +1,26 @@ + +if command -v apt-get &> /dev/null; then + package_manager="apt-get" +elif command -v yum &> /dev/null; then + package_manager="yum" +else + echo "Git installation failed." + exit 1 +fi + +# 安装Git +if [ "$package_manager" = "apt-get" ]; then + apt-get update + apt-get install git -y +elif [ "$package_manager" = "yum" ]; then + yum install git -y +fi + +# 验证Git安装 +git_version=$(git --version) +# shellcheck disable=SC2181 +if [ $? -eq 0 ]; then + echo "Git installed successfully. Version: $git_version" +else + echo "Git installation failed." +fi \ No newline at end of file diff --git a/mod/base/git_tool/tool.py b/mod/base/git_tool/tool.py new file mode 100644 index 00000000..ad761d34 --- /dev/null +++ b/mod/base/git_tool/tool.py @@ -0,0 +1,543 @@ +import json +import os +import re +import shutil +import time + +from .install import installed, install_git, version_1_5_3 +from .util import read_file, write_file, ExecShell, set_ownership +from typing import Optional, Dict, Union, List +from urllib3.util import parse_url, Url +from mod.base import json_response + +GIT_TMP_PATH = "/www/server/git_tmp" + + +class GitTool: + + def __init__(self, project_path: str, git_url: str, user_config: Optional[dict] = None, git_id: str = ""): + self.git_url = git_url + self.get_id = git_id + self.project_path = project_path + if self.project_path[-1] == '/': + self.project_path = self.project_path[:-1] + if not os.path.isdir(GIT_TMP_PATH): + os.makedirs(GIT_TMP_PATH) + + self._tmp_path: Optional[str] = None + self._askpass_path: Optional[str] = None + + self.user_config = user_config + _conf = self.get_user_config_by_project_path() + if _conf and not self.user_config: + self.user_config = _conf + + self._init_tmp_path = False + + @property + def tmp_path(self) -> Optional[str]: + if self._tmp_path is not None: + return self._tmp_path + if not os.path.isdir(self.project_path): + return None + ino = os.stat(self.project_path).st_ino + self._tmp_path = "{}/{}".format(GIT_TMP_PATH, str(ino)) + self._askpass_path = "{}/help_{}.sh".format(GIT_TMP_PATH, str(ino)) + return self._tmp_path + + def get_user_config_by_project_path(self) -> Optional[dict]: + if os.path.isfile(self.project_path + "/.git/config"): + git_config = read_file(self.project_path + "/.git/config") + if isinstance(git_config, str): + return self._read_user_conf_by_config(git_config) + return None + + @property + def askpass_path(self) -> Optional[str]: + if self._askpass_path is not None: + return self._askpass_path + if not os.path.isdir(self.project_path): + return None + ino = os.stat(self.project_path).st_ino + self._tmp_path = "{}/{}".format(GIT_TMP_PATH, str(ino)) + self._askpass_path = "{}/help_{}.sh".format(GIT_TMP_PATH, str(ino)) + return self._askpass_path + + def _setup_tmp_path(self) -> Optional[str]: + if not os.path.isdir(self.project_path): + return "目标目录:【{}】不存在,无法进行git操作".format(self.project_path) + + if not os.path.exists(self.tmp_path): + os.makedirs(self.tmp_path) + self._init_tmp_path = True + else: + if not self._init_tmp_path: + shutil.rmtree(self.tmp_path) + os.makedirs(self.tmp_path) + write_file(self.askpass_path, "") + ExecShell("chmod +x " + self.askpass_path) + + if os.path.isdir(self.tmp_path + "/.git"): + return None + sh_str = """cd {tmp_path} +{git_bin} init . +{git_bin} remote add origin {git_url} +""".format(tmp_path=self.tmp_path, git_bin=self.git_bin(), git_url=self.git_url) + + ExecShell(sh_str) + if not os.path.isfile(self.tmp_path + "/.git/config"): + return "git 初始化失败" + + git_conf = read_file(self.tmp_path + "/.git/config") + if not (isinstance(git_conf, str) and git_conf.find("origin") != -1 and git_conf.find(self.git_url) != -1): + return "git 设置远程路由失败" + + if self.git_url.find("ssh://") != -1: # ssh的情况下不做处理 + return + + if isinstance(self.user_config, dict): + sh_str_list = ["cd {}".format(self.tmp_path)] + config_sh = self.git_bin() + " config user.{} {}" + for k, v in self.user_config.items(): + if isinstance(k, str) and isinstance(v, str) and k.strip() and v.strip(): + sh_str_list.append(config_sh.format(k, v)) + ExecShell("\n".join(sh_str_list)) + askpass_str = """#!/bin/sh +case "$1" in + Username*) exec echo "{}" ;; + Password*) exec echo "{}" ;; +esac +""".format(self.user_config.get('name', "--"), self.user_config.get('password', "--")) + write_file(self.askpass_path, askpass_str) + + def remote_branch(self) -> Union[str, List[str]]: + error = self._setup_tmp_path() + if error: + return error + out, err = ExecShell("cd {} && export GIT_ASKPASS='{}' && git ls-remote origin".format( + self.tmp_path, self.askpass_path)) + rep_branch = re.compile(r"refs/heads/(?P[^\n]*)\n") + branch_list = [] + for tmp_res in rep_branch.finditer(out): + branch_list.append(tmp_res.group("b")) + + if not branch_list: + return err + return branch_list + + @staticmethod + def _read_user_conf_by_config(git_config_data: str) -> Optional[dict]: + rep_user = re.compile(r"\[user][^\n]*\n(?P(\s*\w+\s*=\s*[^\n]*\n)*(\s*\w+\s*=\s*[^\n]*)?)(\s*\[)?") + res = rep_user.search(git_config_data) + if not res: + return None + res_data = dict() + k_v_str = res.group("target") + for line in k_v_str.split("\n"): + if not line.strip(): + continue + k, v = line.split("=", 1) + res_data[k.strip()] = v.strip() + return res_data + + @classmethod + def global_user_conf(cls) -> dict: + res_dict = { + "name": None, + "password": None, + "email": None, + } + global_file = "/root/.gitconfig" + if not os.path.isfile(global_file): + return res_dict + data = read_file(global_file) + if not isinstance(data, str): + return res_dict + res_data = cls._read_user_conf_by_config(data) + res_dict.update(res_data) + return res_dict + + @classmethod + def set_global_user_conf(cls, data) -> None: + sh_str = cls.git_bin() + " config --global user.{} {}" + for k, v in data.items(): + if isinstance(k, str) and isinstance(v, str) and k.strip() and v.strip(): + ExecShell(sh_str.format(k, v)) + + @classmethod + def ssh_pub_key(cls): + key_files = ('id_ed25519', 'id_rsa', 'id_ecdsa', 'id_rsa_bt') + for key_file in key_files: + key_file = "/root/.ssh/{}".format(key_file) + pub_file = "/root/.ssh/{}.pub".format(key_file) + if os.path.isfile(pub_file) and os.path.isfile(key_file): + data = read_file(pub_file) + if isinstance(data, str): + return data + return cls._create_ssh_key() + + @staticmethod + def _create_ssh_key() -> str: + key_type = "ed25519" + ExecShell("ssh-keygen -t {s_type} -P '' -f /root/.ssh/id_{s_type} |echo y".format(s_type=key_type)) + authorized_keys = '/root/.ssh/authorized_keys' + pub_file = "/root/.ssh/id_{s_type}.pub".format(s_type=key_type) + ExecShell('cat %s >> %s && chmod 600 %s' % (pub_file, authorized_keys, authorized_keys)) + key_type_file = '/www/server/panel/data/ssh_key_type.pl' + write_file(key_type_file, key_type) + return read_file(pub_file) + + @staticmethod + def git_bin() -> str: + if not installed(): + if not install_git(): + raise ValueError("没有git工具,且安装失败,无法使用此功能") + default = "/usr/bin/git" + git_path = shutil.which("git") + if git_path is None: + return default + return git_path + + def pull(self, branch, set_own: Optional[str] = None) -> Optional[str]: + if self.git_url.startswith("https://") or self.git_url.startswith("http://"): + res = parse_url(self.git_url) + if isinstance(res, Url) and not res.auth: + if self.user_config and "name" in self.user_config and "password" in self.user_config: + res.auth = "{}:{}".format(self.user_config["name"], self.user_config["password"]) + self.git_url = res.url + git_name = self.git_name() + if git_name is None: + git_name = 'None' + if os.path.isdir(self.tmp_path + "/" + git_name): + shutil.rmtree(self.tmp_path + "/" + git_name) + + log_file = "/tmp/git_{}_log.log".format(self.get_id) + + shell_command_str = "cd {0} && {1} clone --progress -b {2} {3} &>> {4}".format( + self.tmp_path, self.git_bin(), branch, self.git_url, log_file + ) + + ExecShell(shell_command_str) + if not os.path.isdir(self.tmp_path + "/" + git_name): + return "拉取错误" + + ExecShell(r"\cp -rf {}/{}/* {}/".format(self.tmp_path, git_name, self.project_path)) + if isinstance(set_own, str): + set_ownership(self.project_path, set_own) + + if os.path.isdir(self.tmp_path + "/" + git_name): + shutil.rmtree(self.tmp_path + "/" + git_name) + + def git_name(self) -> Optional[str]: + if isinstance(self.git_url, str): + name = self.git_url.rsplit("/", 1)[1] + if name.endswith(".git"): + name = name[:-4] + return name + return None + + @classmethod + def new_id(cls) -> str: + from uuid import uuid4 + return uuid4().hex[::2] + + +class RealGitMager: + _git_config_file = "/www/server/panel/data/site_git_config.json" + + def __init__(self): + self._config: Optional[Dict[str, List[Dict[str, Union[int, str, dict]]]]] = None + # c = { + # "site_name": [{ + # "id": "", + # "site_name": "aaaa", + # "url": "http://git.bt.cn/cjxin/panel-plugin.git", # ssh://git@git.bt.cn/cjxin/panel-plugin.git + # "path_ino": 4564524, + # "git_path": "/www/wwwroot/site", + # "config": { + # "name": "", + # "password": "", + # "email": "", + # }, + # } + # ] + # } + + @property + def configure(self) -> Dict[str, List[Dict[str, Union[int, str, dict]]]]: + if self._config is None: + try: + res = read_file(self._git_config_file) + if res is None: + data = {} + else: + data = json.loads(res) + except (json.JSONDecoder, TypeError, ValueError): + data = {} + + self._config = data + return self._config + + def save_configure(self): + if self._config: + write_file(self._git_config_file, json.dumps(self._config)) + + def add_git(self, git_url: str, site_name: str, git_path: str, user_config: Optional[dict]) -> Union[str, list]: + url = parse_url(git_url) + if not (isinstance(url, Url) and url.scheme and url.host and url.path): + return "url格式错误" + + if user_config and not (isinstance(user_config, dict) and "name" in user_config and "password" in user_config): + return "用户信息输入错误" + + if not os.path.exists(git_path): + return "git目标目录不存在" + else: + path_ino = os.stat(git_path).st_ino + + if site_name not in self.configure: + self.configure[site_name] = [] + + for c in self.configure[site_name]: + if c["path_ino"] == path_ino or git_path == c["git_path"]: + return "该路径已存在,请不要重复添加" + + try: + GitTool.git_bin() + except ValueError as e: + return str(e) + + git_id = GitTool.new_id() + git = GitTool(project_path=git_path, git_url=git_url, user_config=user_config, git_id=git_id) + res = git.remote_branch() + if isinstance(res, str): + return res + + self.configure[site_name].append( + { + "id": git_id, + "site_name": site_name, + "url": git_url, + "path_ino": path_ino, + "git_path": git_path, + "remote_branch": res, + "remote_branch_time": int(time.time()), + "config": user_config, + } + ) + self.save_configure() + return res + + def modify_git( + self, + git_id: str, + site_name: str, + git_url: Optional[str], + git_path: Optional[str], + user_config: Optional[dict] + ) -> Optional[str]: + target = None + for i in self.configure.get(site_name, []): + if i["id"] == git_id: + target = i + break + + if target is None: + return '指定的git配置不存在' + if git_url: + url = parse_url(git_url) + if not (isinstance(url, Url) and url.scheme and url.host and url.path): + return "url格式错误" + target["url"] = git_url + + if git_path: + if not os.path.exists(git_path): + return "git目标目录不存在" + else: + path_ino = os.stat(git_path).st_ino + target["path_ino"] = path_ino + target["git_path"] = git_path + + if user_config: + if not (isinstance(user_config, dict) and "name" in user_config and "password" in user_config): + return "用户信息输入错误" + target["config"] = user_config + + git = GitTool(project_path=target['git_path'], + git_url=target['url'], + user_config=target['config'], + git_id=target['id']) + res = git.remote_branch() + if isinstance(res, str): + return res + + self.save_configure() + return None + + def remove_git(self, git_id: str, site_name: str) -> Optional[str]: + target = None + for i in self.configure.get(site_name, []): + if i["id"] == git_id: + target = i + break + + if target is None: + return '指定的git配置不存在' + + self.configure[site_name].remove(target) + self.save_configure() + + def site_git_configure(self, site_name, refresh: bool = False) -> List[dict]: + if site_name not in self.configure: + return [] + res_list = [] + for i in self.configure[site_name]: + if time.time() - i.get("remote_branch_time", 0) > 60 * 60 or refresh: + g = GitTool(project_path=i['git_path'], git_url=i['url'], user_config=i['config'], git_id=i['id']) + res = g.remote_branch() + if isinstance(res, str): + i.update(remote_branch_error=res, remote_branch=[], remote_branch_time=int(time.time())) + else: + i.update(remote_branch=res, remote_branch_time=int(time.time())) + res_list.append(i) + + return res_list + + @staticmethod + def set_global_user(name: Optional[str], password: Optional[str], email: Optional[str] = None) -> None: + data = {} + if name: + data['name'] = name + if password: + data['password'] = password + if email: + data['email'] = email + GitTool.set_global_user_conf(data) + + def git_pull(self, git_id: str, site_name: str, branch: str) -> Optional[str]: + target = None + for i in self.configure.get(site_name, []): + if i["id"] == git_id: + target = i + break + + if target is None: + return '指定的git配置不存在' + + g = GitTool( + project_path=target['git_path'], + git_url=target['url'], + user_config=target['config'], + git_id=target["id"]) + return g.pull(branch) + + +class GitMager: + # 添加git信息 + @staticmethod + def add_git(get): + user_config = None + try: + git_url = get.url.strip() + site_name = get.site_name.strip() + git_path = get.git_path.strip() + if hasattr(get, "config") and get.config.strip(): + user_config = json.loads(get.config.strip()) + except (json.JSONDecoder, AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + res = RealGitMager().add_git(git_url, site_name, git_path, user_config) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, data=res) + + # 修改git信息 + @staticmethod + def modify_git(get): + git_url = None + git_path = None + user_config = None + try: + git_id = get.git_id.strip() + site_name = get.site_name.strip() + if "url" in get: + git_url = get.url.strip() + if 'git_path' in get: + git_path = get.git_path.strip() + if hasattr(get, "user_config") and get.user_config.strip(): + user_config = json.loads(get.user_config.strip()) + except (json.JSONDecoder, AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + res = RealGitMager().modify_git(git_id, site_name, git_url, git_path, user_config) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, data=res) + + # 移除git信息 + @staticmethod + def remove_git(get): + try: + git_id = get.git_id.strip() + site_name = get.site_name.strip() + except (json.JSONDecoder, AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + res = RealGitMager().remove_git(git_id, site_name) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, data=res) + + @staticmethod + def site_git_configure(get): + if not version_1_5_3(): + return json_response(status=False, msg="git 版本低于1.5.3无法使用") + refresh = '' + try: + site_name = get.site_name.strip() + if "refresh" in get: + refresh = get.refresh.strip() + except (AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + if refresh in ("true", "1"): + refresh = True + else: + refresh = False + res = RealGitMager().site_git_configure(site_name, refresh=refresh) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, data=res) + + @staticmethod + def set_global_user(get): + name = password = email = None + try: + if "name" in get: + name = get.name.strip() + if "password" in get: + password = get.password.strip() + if "email" in get: + email = get.email.strip() + except (AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + + RealGitMager().set_global_user(name, password, email) + return json_response(status=True, msg="设置成功") + + @staticmethod + def git_pull(get): + try: + site_name = get.site_name.strip() + git_id = get.git_id.strip() + branch = get.branch.strip() + except (AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + res = RealGitMager().git_pull(git_id, site_name, branch) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, data=res) + + @staticmethod + def git_global_user_conf(get=None): + return GitTool.global_user_conf() + + @staticmethod + def git_ssh_pub_key(get=None): + return GitTool.ssh_pub_key() diff --git a/mod/base/git_tool/util.py b/mod/base/git_tool/util.py new file mode 100644 index 00000000..f4cd2474 --- /dev/null +++ b/mod/base/git_tool/util.py @@ -0,0 +1,56 @@ +import os +import sys +from typing import Optional, Tuple, Callable + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + +import public + + +def write_file(filename: str, s_body: str, mode='w+') -> bool: + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode=mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode=mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +def read_file(filename, mode='r') -> Optional[str]: + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): + return None + fp = None + try: + fp = open(filename, mode=mode) + f_body = fp.read() + except: + return None + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +ExecShell: Callable = public.ExecShell +set_ownership: Callable = public.set_ownership diff --git a/mod/base/msg/__init__.py b/mod/base/msg/__init__.py new file mode 100644 index 00000000..8342523c --- /dev/null +++ b/mod/base/msg/__init__.py @@ -0,0 +1,145 @@ +import json +import os.path + +from .weixin_msg import WeiXinMsg +from .mail_msg import MailMsg +from .web_hook_msg import WebHookMsg +from .feishu_msg import FeiShuMsg +from .dingding_msg import DingDingMsg +from .sms_msg import SMSMsg +from .wx_account_msg import WeChatAccountMsg +from .manager import SenderManager +from .util import read_file + +from mod.base.push_mod import SenderConfig, PUSH_DATA_PATH + + +# 把旧地告警系统的信息通道更新 +def update_mod_push_msg(): + + if os.path.exists(PUSH_DATA_PATH + "/update_sender.pl"): + return + with open(PUSH_DATA_PATH + "/update_sender.pl", "w") as f: + f.write("") + + WeChatAccountMsg.refresh_config(force=True) + sms_status = False + sc = SenderConfig() + for conf in sc.config: + if conf["sender_type"] == "sms": + sms_status = True + break + + if not sms_status: + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "sms", + "data": {}, + "original": True # 标记这个通道是该类型 旧有的通道, 同时也是默认通道 + }) + + panel_data_path = "/www/server/panel/data" + + # weixin + if os.path.exists(panel_data_path + "/weixin.json"): + try: + weixin_data = json.loads(read_file(panel_data_path + "/weixin.json")) + except: + weixin_data = None + + if isinstance(weixin_data, dict) and "weixin_url" in weixin_data: + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "weixin", + "data": { + "url": weixin_data["weixin_url"], + "title": "企业微信" if "title" not in weixin_data else weixin_data["title"] + }, + "original": True + }) + + # mail + stmp_file = panel_data_path + "/stmp_mail.json" + mail_list_file = panel_data_path + "/mail_list.json" + if os.path.exists(stmp_file) and os.path.exists(mail_list_file): + stmp_data = None + try: + stmp_data = json.loads(read_file(stmp_file)) + mail_list_data = json.loads(read_file(mail_list_file)) + except: + mail_list_data = None + + if isinstance(stmp_data, dict): + if 'qq_mail' in stmp_data or 'qq_stmp_pwd' in stmp_data or 'hosts' in stmp_data: + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "mail", + "data": { + "send": stmp_data, + "title": "邮箱", + "receive": [] if not mail_list_data else mail_list_data, + }, + "original": True + }) + + # webhook + webhook_file = panel_data_path + "/hooks_msg.json" + if os.path.exists(stmp_file) and os.path.exists(mail_list_file): + try: + webhook_data = json.loads(read_file(webhook_file)) + except: + webhook_data = None + + if isinstance(webhook_data, list): + for i in webhook_data: + i["title"] = i["name"] + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "webhook", + "data": i, + }) + + # feishu + if os.path.exists(panel_data_path + "/feishu.json"): + try: + feishu_data = json.loads(read_file(panel_data_path + "/feishu.json")) + except: + feishu_data = None + + if isinstance(feishu_data, dict) and "feishu_url" in feishu_data: + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "feishu", + "data": { + "url": feishu_data["feishu_url"], + "title": "飞书" if "title" not in feishu_data else feishu_data["title"] + }, + "original": True + }) + + # dingding + if os.path.exists(panel_data_path + "/dingding.json"): + try: + dingding_data = json.loads(read_file(panel_data_path + "/dingding.json")) + except: + dingding_data = None + + if isinstance(dingding_data, dict) and "dingding_url" in dingding_data: + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "dingding", + "data": { + "url": dingding_data["dingding_url"], + "title": "钉钉" if "title" not in dingding_data else dingding_data["title"] + }, + "original": True + }) + + sc.save_config() + read_file(PUSH_DATA_PATH + "/update_sender.pl", "") diff --git a/mod/base/msg/dingding_msg.py b/mod/base/msg/dingding_msg.py new file mode 100644 index 00000000..c1a681c2 --- /dev/null +++ b/mod/base/msg/dingding_msg.py @@ -0,0 +1,155 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: baozi < +# | 消息通道邮箱模块(新) +# +------------------------------------------------------------------- + +import re +import json +import requests +import traceback +import socket + +import requests.packages.urllib3.util.connection as urllib3_cn +from requests.packages import urllib3 +from typing import Optional, Union + +from .util import write_push_log, get_test_msg + +# 关闭警告 +urllib3.disable_warnings() + + +class DingDingMsg: + def __init__(self, dingding_data): + self.id = dingding_data["id"] + self.config = dingding_data["data"] + + def send_msg(self, msg: str, title) -> Optional[str]: + """ + 钉钉发送信息 + @msg 消息正文 + """ + if not self.config: + return '未正确配置钉钉信息' + + # user没有时默认为空 + if "user" not in self.config: + self.config['user'] = [] + + if "isAtAll" not in self.config: + self.config['isAtAll'] = [] + + if not isinstance(self.config['url'], str): + return '钉钉配置错误,请重新配置钉钉机器人' + + at_info = '' + for user in self.config['user']: + if re.match(r"^[0-9]{11}$", str(user)): + at_info += '@' + user + ' ' + + if at_info: + msg = msg + '\n\n>' + at_info + + + + headers = {'Content-Type': 'application/json'} + data = { + "msgtype": "markdown", + "markdown": { + "title": "服务器通知", + "text": msg + }, + "at": { + "atMobiles": self.config['user'], + "isAtAll": self.config['isAtAll'] + } + } + status = False + error = None + try: + def allowed_gai_family(): + family = socket.AF_INET + return family + + allowed_gai_family_lib = urllib3_cn.allowed_gai_family + urllib3_cn.allowed_gai_family = allowed_gai_family + + response = requests.post( + url=self.config["url"], + data=json.dumps(data), + verify=False, + headers=headers, + timeout=10 + ) + + urllib3_cn.allowed_gai_family = allowed_gai_family_lib + + if response.json()["errcode"] == 0: + status = True + except: + error = traceback.format_exc() + status = False + + write_push_log("钉钉", status, title) + return error + + @classmethod + def check_args(cls, args: dict) -> Union[dict, str]: + if "url" not in args or "title" not in args: + return "信息不完整" + + title = args["title"] + if len(title) > 15: + return '备注名称不能超过15个字符' + + if "user" in args and isinstance(args["user"], list): + user = args["user"] + else: + user = [] + + if "atall" in args and isinstance(args["atall"], bool): + atall = args["atall"] + else: + atall = True + + data = { + "url": args["url"], + "user": user, + "title": title, + "isAtAll": atall, + } + + test_obj = cls({"data": data, "id": None}) + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + + test_task = get_test_msg("面板消息通道配置提醒") + + res = test_obj.send_msg( + test_task.to_dingding_msg(test_msg, test_task.the_push_public_data()), + "面板消息通道配置提醒" + ) + if res is None: + return data + + return res + + def test_send_msg(self) -> Optional[str]: + + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + test_task = get_test_msg("面板消息通道配置提醒") + res = self.send_msg( + test_task.to_dingding_msg(test_msg, test_task.the_push_public_data()), + "面板消息通道配置提醒" + ) + if res is None: + return None + return res diff --git a/mod/base/msg/feishu_msg.py b/mod/base/msg/feishu_msg.py new file mode 100644 index 00000000..0af9b3e4 --- /dev/null +++ b/mod/base/msg/feishu_msg.py @@ -0,0 +1,139 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: lx +# | 消息通道飞书通知模块 +# +------------------------------------------------------------------- + +import re +import json +import requests +import traceback +import socket + +import requests.packages.urllib3.util.connection as urllib3_cn +from requests.packages import urllib3 +from typing import Optional, Union + +from .util import write_push_log, get_test_msg + +# 关闭警告 +urllib3.disable_warnings() + + +class FeiShuMsg: + + def __init__(self, feishu_data): + self.id = feishu_data["id"] + self.config = feishu_data["data"] + + @classmethod + def check_args(cls, args: dict) -> Union[dict, str]: + if "url" not in args or "title" not in args: + return "信息不完整" + + title = args["title"] + if len(title) > 15: + return '备注名称不能超过15个字符' + + if "user" in args and isinstance(args["user"], list): + user = args["user"] + else: + user = [] + + if "atall" in args and isinstance(args["atall"], bool): + atall = args["atall"] + else: + atall = True + + data = { + "url": args["url"], + "user": user, + "title": title, + "isAtAll": atall, + } + + test_obj = cls({"data": data, "id": None}) + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + + test_task = get_test_msg("消息通道配置提醒") + + res = test_obj.send_msg( + test_task.to_feishu_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒" + ) + if res is None: + return data + + return res + + def send_msg(self, msg: str, title: str) -> Optional[str]: + """ + 飞书发送信息 + @msg 消息正文 + """ + if not self.config: + return '未正确配置飞书信息。' + + reg = '(.+)' + tmp = re.search(reg, msg) + if tmp: + tmp = tmp.groups()[0] + msg = re.sub(reg, tmp, msg) + + if "isAtAll" not in self.config: + self.config["isAtAll"] = True + + if self.config["isAtAll"]: + msg += "所有人" + + headers = {'Content-Type': 'application/json'} + data = { + "msg_type": "text", + "content": { + "text": msg + } + } + status = False + error = None + try: + def allowed_gai_family(): + family = socket.AF_INET + return family + allowed_gai_family_lib = urllib3_cn.allowed_gai_family + urllib3_cn.allowed_gai_family = allowed_gai_family + rdata = requests.post( + url=self.config['url'], + data=json.dumps(data), + verify=False, + headers=headers, + timeout=10 + ).json() + urllib3_cn.allowed_gai_family = allowed_gai_family_lib + + if "StatusCode" in rdata and rdata["StatusCode"] == 0: + status = True + except: + error = traceback.format_exc() + + write_push_log("飞书", status, title) + + return error + + def test_send_msg(self) -> Optional[str]: + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + test_task = get_test_msg("消息通道配置提醒") + res = self.send_msg( + test_task.to_feishu_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒" + ) + if res is None: + return None + return res diff --git a/mod/base/msg/mail_msg.py b/mod/base/msg/mail_msg.py new file mode 100644 index 00000000..57b2b252 --- /dev/null +++ b/mod/base/msg/mail_msg.py @@ -0,0 +1,135 @@ +#coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: 沐落 +# | Author: lx +# | 消息通道邮箱模块 +# +------------------------------------------------------------------- + +import smtplib +import traceback +from email.mime.text import MIMEText +from email.utils import formataddr +from typing import Tuple, Union, Optional + +from mod.base.msg.util import write_push_log, write_mail_push_log, get_test_msg + + +class MailMsg: + + def __init__(self, mail_data): + self.id = mail_data["id"] + self.config = mail_data["data"] + + @classmethod + def check_args(cls, args: dict) -> Tuple[bool, Union[dict, str]]: + if "send" not in args or "receive" not in args or len(args["receive"]) < 1: + return False, "信息不完整,必须有发送方和至少一个接收方" + + if "title" not in args: + return False, "没有必要的备注信息" + + title = args["title"] + if len(title) > 15: + return False, '备注名称不能超过15个字符' + + send_data = args["send"] + send = {} + for i in ("qq_mail", "qq_stmp_pwd", "hosts", "port"): + if i not in send_data: + return False, "发送方配置信息不完整" + send[i] = send_data[i].strip() + + receive_data = args["receive"] + if isinstance(receive_data, str): + receive_list = [i.strip() for i in receive_data.split("\n") if i.strip()] + else: + receive_list = [i.strip() for i in receive_data if i.strip()] + + data = { + "send": send, + "title": title, + "receive": receive_list, + } + + test_obj = cls({"data": data, "id": None}) + test_msg = { + "msg_list": ['>配置状态:成功
                                    '] + } + + test_task = get_test_msg("消息通道配置提醒") + + res = test_obj.send_msg( + test_task.to_mail_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒" + ) + if res is None or res.find("部分接收者时失败") != -1: + return True, data + + return False, res + + def send_msg(self, msg: str, title: str): + """ + 邮箱发送 + @msg 消息正文 + @title 消息标题 + """ + if not self.config: + return '未正确配置邮箱信息。' + + if 'port' not in self.config['send']: + self.config['send']['port'] = 465 + + receive_list = self.config['receive'] + + error_list, success_list = [], [] + error_msg_dict = {} + for email in receive_list: + if not email.strip(): + continue + try: + data = MIMEText(msg, 'html', 'utf-8') + data['From'] = formataddr((self.config['send']['qq_mail'], self.config['send']['qq_mail'])) + data['To'] = formataddr((self.config['send']['qq_mail'], email.strip())) + data['Subject'] = title + if int(self.config['send']['port']) == 465: + server = smtplib.SMTP_SSL(str(self.config['send']['hosts']), int(self.config['send']['port'])) + else: + server = smtplib.SMTP(str(self.config['send']['hosts']), int(self.config['send']['port'])) + + server.login(self.config['send']['qq_mail'], self.config['send']['qq_stmp_pwd']) + server.sendmail(self.config['send']['qq_mail'], [email.strip(), ], data.as_string()) + server.quit() + success_list.append(email) + except: + error_list.append(email) + error_msg_dict[email] = traceback.format_exc() + + if not error_list and not success_list: # 没有接收者 + return "未配置接收邮箱" + if not error_list: + write_push_log("邮箱", True, title, success_list) # 没有失败 + return None + if not success_list: + write_push_log("邮箱", False, title, error_list) # 全都失败 + return "发送信息失败, 发送失败的接收人:{}".format(error_list) + write_mail_push_log(title, error_list, success_list) + + return "发送邮件到部分接收者时失败,包含:{}".format(error_list) + + def test_send_msg(self) -> Optional[str]: + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + test_task = get_test_msg("消息通道配置提醒") + res = self.send_msg( + test_task.to_mail_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒" + ) + if res is None: + return None + return res + diff --git a/mod/base/msg/manager.py b/mod/base/msg/manager.py new file mode 100644 index 00000000..a246d608 --- /dev/null +++ b/mod/base/msg/manager.py @@ -0,0 +1,282 @@ +import time +import traceback + +from mod.base.push_mod import SenderConfig +from .weixin_msg import WeiXinMsg +from .mail_msg import MailMsg +from .web_hook_msg import WebHookMsg +from .feishu_msg import FeiShuMsg +from .dingding_msg import DingDingMsg +from .sms_msg import SMSMsg +from .wx_account_msg import WeChatAccountMsg +import json +from mod.base import json_response +from .util import write_file, read_file +import sys,os +sys.path.insert(0, "/www/server/panel/class/") +import public + +# 短信会自动添加到 sender 库中的第一个 且通过官方接口更新 +# 微信公众号信息通过官网接口更新, 不写入数据库,需要时由文件中读取并序列化 +# 其他告警通道本质都类似于web hook 在确认完数据信息无误后,都可以自行添加或启用 +class SenderManager: + def __init__(self): + self.custom_parameter_filename = "/www/server/panel/data/mod_push_data/custom_parameter.pl" + + def set_sender_conf(self, get): + sender_id = None + try: + if hasattr(get, "sender_id"): + sender_id = get.sender_id.strip() + if not sender_id: + sender_id = None + sender_type = get.sender_type.strip() + args = json.loads(get.sender_data.strip()) + except (json.JSONDecoder, AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + sender_config = SenderConfig() + if sender_id is not None: + tmp = sender_config.get_by_id(sender_id) + if tmp is None: + sender_id = None + + if sender_type == "weixin": + data = WeiXinMsg.check_args(args) + if isinstance(data, str): + return json_response(status=False, data=data, msg="测试发送失败") + + elif sender_type == "mail": + _, data = MailMsg.check_args(args) + if isinstance(data, str): + return json_response(status=False, data=data, msg="测试发送失败") + + elif sender_type == "webhook": + custom_parameter = args.get("custom_parameter", {}) + if custom_parameter: + try: + public.writeFile(self.custom_parameter_filename, json.dumps(custom_parameter)) + except: + pass + + # 检查参数 + data = WebHookMsg.check_args(args) + if isinstance(data, str): + return json_response(status=False, data=data, msg="测试发送失败") + + # 从文件读取并删除文件 + try: + if os.path.exists(self.custom_parameter_filename): + custom_parameter = json.loads(public.readFile(self.custom_parameter_filename)) + data['custom_parameter'] = custom_parameter + os.remove(self.custom_parameter_filename) + except: + pass + + elif sender_type == "feishu": + data = FeiShuMsg.check_args(args) + if isinstance(data, str): + return json_response(status=False, data=data, msg="测试发送失败") + + elif sender_type == "dingding": + data = DingDingMsg.check_args(args) + if isinstance(data, str): + return json_response(status=False, data=data, msg="测试发送失败") + else: + return json_response(status=False, msg="当前接口不适应的类型") + # Check if the sender configuration already exists + existing_sender = any( + conf for conf in sender_config.config + if conf['sender_type'] == sender_type and 'title' in conf['data'] and conf['data']['title'] == data['title'] and conf['id'] != sender_id + ) + if existing_sender: + return json_response(status=False, msg="同样的发送配置已存在,无法重复添加") + now_sender_id = None + if not sender_id: + now_sender_id = sender_config.nwe_id() + sender_config.config.append( + { + "id": now_sender_id, + "sender_type": sender_type, + "data": data, + "used": True, + }) + + else: + now_sender_id = sender_id + tmp = sender_config.get_by_id(sender_id) + tmp["data"].update(data) + + type_senders = [conf for conf in sender_config.config if conf['sender_type'] == sender_type] + if len(type_senders) == 1: + for conf in sender_config.config: + conf["original"] = (conf['id'] == now_sender_id) + + sender_config.save_config() + if sender_type == "webhook": + self.set_default_for_compatible(sender_config.get_by_id(now_sender_id)) + + return json_response(status=True, msg="保存成功") + + @staticmethod + def change_sendr_used(get): + try: + sender_id = get.sender_id.strip() + except (AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + + sender_config = SenderConfig() + tmp = sender_config.get_by_id(sender_id) + if tmp is None: + return json_response(status=False, msg="未找到对应发送者") + tmp["used"] = not tmp["used"] + + sender_config.save_config() + + return json_response(status=True, msg="保存成功") + + @staticmethod + def remove_sender(get): + try: + sender_id = get.sender_id.strip() + except (AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + + sender_config = SenderConfig() + tmp = sender_config.get_by_id(sender_id) + if tmp is None: + return json_response(status=False, msg="未找到对应发送者") + sender_config.config.remove(tmp) + sender_config.save_config() + + return json_response(status=True, msg="删除成功") + + @staticmethod + def get_sender_list(get): + # 微信, 飞书, 钉钉, web-hook, 邮箱 + refresh = False + try: + if hasattr(get, 'refresh'): + refresh = get.refresh.strip() + if refresh in ("1", "true"): + refresh = True + except (AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + + res = [] + WeChatAccountMsg.refresh_config(force=refresh) + simple = ("weixin", "mail", "webhook", "feishu", "dingding") + + for conf in SenderConfig().config: + if conf["sender_type"] in simple or conf["sender_type"] == "wx_account": + res.append(conf) + elif conf["sender_type"] == "sms": + conf["data"] = SMSMsg(conf).refresh_config(force=refresh) + res.append(conf) + res.sort(key=lambda x: x["sender_type"]) + return json_response(status=True, data=res) + + @staticmethod + def test_send_msg(get): + try: + sender_id = get.sender_id.strip() + except (json.JSONDecoder, AttributeError, TypeError): + return json_response(status=False, msg="参数错误") + + sender_config = SenderConfig() + tmp = sender_config.get_by_id(sender_id) + if tmp is None: + return json_response(status=False, msg="未找到对应发送者") + + sender_type = tmp["sender_type"] + + if sender_type == "weixin": + sender_obj = WeiXinMsg(tmp) + + elif sender_type == "mail": + sender_obj = MailMsg(tmp) + + elif sender_type == "webhook": + sender_obj = WebHookMsg(tmp) + + elif sender_type == "feishu": + sender_obj = FeiShuMsg(tmp) + + elif sender_type == "dingding": + sender_obj = DingDingMsg(tmp) + + elif sender_type == "wx_account": + sender_obj = WeChatAccountMsg(tmp) + else: + return json_response(status=False, msg="当前接口不适应的类型") + + res = sender_obj.test_send_msg() + if isinstance(res, str): + return json_response(status=False, data=res, msg="测试发送失败") + return json_response(status=True, msg="发送成功") + + @staticmethod + def set_default_for_compatible(sender_data: dict): + if sender_data["sender_type"] in ("sms", "wx_account"): + return + + panel_data = "/www/server/panel/data" + if sender_data["sender_type"] == "weixin": + weixin_file = "{}/weixin.json".format(panel_data) + write_file(weixin_file, json.dumps({ + "state": 1, + "weixin_url": sender_data["data"]["url"], + "title": sender_data["data"]["title"], + "list": { + "default": { + "data": sender_data["data"]["url"], + "title": sender_data["data"]["title"], + "status": 1, + "addtime": int(time.time()) + } + } + })) + + elif sender_data["sender_type"] == "mail": + stmp_mail_file = "{}/stmp_mail.json".format(panel_data) + mail_list_file = "{}/mail_list.json".format(panel_data) + write_file(stmp_mail_file, json.dumps(sender_data["data"]["send"])) + write_file(mail_list_file, json.dumps(sender_data["data"]["receive"])) + + elif sender_data["sender_type"] == "feishu": + feishu_file = "{}/feishu.json".format(panel_data) + write_file(feishu_file, json.dumps({ + "feishu_url": sender_data["data"]["url"], + "title": sender_data["data"]["title"], + "isAtAll": True, + "user": [] + })) + + elif sender_data["sender_type"] == "dingding": + dingding_file = "{}/dingding.json".format(panel_data) + write_file(dingding_file, json.dumps({ + "dingding_url": sender_data["data"]["url"], + "title": sender_data["data"]["title"], + "isAtAll": True, + "user": [] + })) + + elif sender_data["sender_type"] == "webhook": + webhook_file = "{}/hooks_msg.json".format(panel_data) + try: + webhook_data = json.loads(read_file(webhook_file)) + except: + webhook_data =[] + target_idx = -1 + for idx, i in enumerate(webhook_data): + if i["name"] == sender_data["data"]["title"]: + target_idx = idx + break + else: + sender_data["data"]["name"] = sender_data["data"]["title"] + webhook_data.append(sender_data["data"]) + if target_idx != -1: + sender_data["data"]["name"] = sender_data["data"]["title"] + webhook_data[target_idx] = sender_data["data"] + write_file(webhook_file, json.dumps(webhook_data)) + + diff --git a/mod/base/msg/sms_msg.py b/mod/base/msg/sms_msg.py new file mode 100644 index 00000000..e6ec2652 --- /dev/null +++ b/mod/base/msg/sms_msg.py @@ -0,0 +1,121 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: baozi +# | 消息通道 短信模块(新) +# +------------------------------------------------------------------- +import json +import os +import time +import traceback +from typing import Union, Optional +from mod.base.push_mod import SenderConfig +from .util import write_push_log, PANEL_PATH, write_file, read_file, public_http_post + + +class SMSMsg: + API_URL = 'http://www.bt.cn/api/wmsg' + USER_PATH = '{}/data/userInfo.json'.format(PANEL_PATH) + + # 构造方法 + def __init__(self, msm_data: dict): + self.id = msm_data["id"] + self.data = msm_data["data"] + self.user_info = None + try: + self.user_info = json.loads(read_file(self.USER_PATH)) + except: + self.user_info = None + + self._PDATA = { + "access_key": "" if self.user_info is None else self.user_info["access_key"], + "data": {} + } + + def refresh_config(self, force=False): + if "last_refresh_time" not in self.data: + self.data["last_refresh_time"] = 0 + if self.data.get("last_refresh_time") + 60 * 60 * 24 < time.time() or force: # 一天最多更新一次 + result = self._request('get_user_sms') + if not isinstance(result, dict) or ("status" in result and not result["status"]): + return { + "count": 0, + "total": 0 + } + sc = SenderConfig() + tmp = sc.get_by_id(self.id) + if tmp is not None: + result["last_refresh_time"] = time.time() + tmp["data"] = result + sc.save_config() + else: + result = self.data + return result + + def send_msg(self, sm_type: str, sm_args: dict): + """ + @发送短信 + @sm_type 预警类型, ssl_end|宝塔SSL到期提醒 + @sm_args 预警参数 + """ + if not self.user_info: + return "未成功绑定官网账号,无法发送信息,请尝试重新绑定" + tmp = sm_type.split('|') + if "|" in sm_type and len(tmp) >= 2: + s_type = tmp[0] + title = tmp[1] + else: + s_type = sm_type + title = '宝塔告警提醒' + + sm_args = self.canonical_data(sm_args) + self._PDATA['data']['sm_type'] = s_type + self._PDATA['data']['sm_args'] = sm_args + print(s_type) + print(sm_args) + result = self._request('send_msg') + u_key = '{}****{}'.format(self.user_info['username'][:3], self.user_info['username'][-3:]) + print(result) + if isinstance(result, str): + write_push_log("短信", False, title, [u_key]) + return result + + if result['status']: + write_push_log("短信", True, title, [u_key]) + return None + else: + write_push_log("短信", False, title, [u_key]) + return result.get("msg", "发送错误") + + @staticmethod + def canonical_data(args): + """规范数据内容""" + if not isinstance(args, dict): + return args + new_args = {} + for param, value in args.items(): + if type(value) != str: + new_str = str(value) + else: + new_str = value.replace(".", "_").replace("+", "+") + new_args[param] = new_str + return new_args + + def push_data(self, data): + return self.send_msg(data['sm_type'], data['sm_args']) + + # 发送请求 + def _request(self, d_name: str) -> Union[dict, str]: + pdata = { + 'access_key': self._PDATA['access_key'], + 'data': json.dumps(self._PDATA['data']) + } + try: + result = public_http_post(self.API_URL + '/' + d_name, pdata) + result = json.loads(result) + return result + except Exception: + return traceback.format_exc() diff --git a/mod/base/msg/test.json b/mod/base/msg/test.json new file mode 100644 index 00000000..016f6ac2 --- /dev/null +++ b/mod/base/msg/test.json @@ -0,0 +1,105 @@ +[ + { + "id": "f4e98e478b85e876", + "used": true, + "sender_type": "sms", + "data": {} + }, + { + "id": "fb3e9e409b9d7c27", + "sender_type": "mail", + "data": { + "send": { + "qq_mail": "1191604998@qq.com", + "qq_stmp_pwd": "alvonbfcwhlahbcg", + "hosts": "smtp.qq.com", + "port": "465" + }, + "title": "test_mail", + "receive": [ + "1191604998@qq.com", + "225326944@qq.com" + ] + }, + "used": true + }, + { + "id": "79900d4fb37fa83d", + "sender_type": "feishu", + "data": { + "url": "https://open.feishu.cn/open-apis/bot/v2/hook/ba6a3f77-0349-4492-a8ad-0b4c99435bf1", + "user": [], + "title": "test_feishu", + "isAtAll": true + }, + "used": true + }, + { + "id": "8f70de4baa89133e", + "sender_type": "webhook", + "data": { + "title": "webhook", + "url": "http://192.168.69.172:11211", + "query": {}, + "headers": {}, + "body_type": "json", + "custom_parameter": {}, + "method": "POST", + "ssl_verify": null, + "status": true + }, + "used": true + }, + { + "id": "10bcf5439299d9dd", + "used": true, + "sender_type": "wx_account", + "data": { + "id": "jsbRCBBinMmFjYjczNTQyYmUzxNDiWQw", + "uid": 1228262, + "is_subscribe": 1, + "head_img": "https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epBUaqBcCkkxtKwuaOHLy1qjGeDvmf1hZsrkFGNrldyRgSuA3sYB1xlgKv1Z98PUciaxju71PUKchA/132", + "nickname": "沈涛", + "status": 1, + "create_time": "2023-12-27 11:30:15", + "update_time": "2023-12-27 11:30:15", + "remaining": 98, + "title": "沈涛" + } + }, + { + "id": "2c7c094eb23ddaae", + "used": true, + "sender_type": "webhook", + "data": { + "url": "http://192.168.69.159:8888/hook?access_key=IUSEViIMMhQio1WyP0ztCyoa8sIBjaWulihhcJX4rRJ4sW79", + "query": {}, + "headers": {}, + "body_type": "json", + "custom_parameter": {}, + "method": "GET", + "ssl_verify": 1, + "status": true, + "name": "aaa", + "title": "aaa" + } + }, + { + "id": "63c30845916fa722", + "used": true, + "sender_type": "feishu", + "data": { + "url": "https://open.feishu.cn/open-apis/bot/v2/hook/c6906d9f-01c5-4a74-80bd-3ccda33bf4ec", + "title": "amber" + } + }, + { + "id": "6ccf834a95010bed", + "used": true, + "sender_type": "dingding", + "data": { + "url": "https://oapi.dingtalk.com/robot/send?access_token=00732dec605edc1c07f441eb9d470c8bdfa301c4ce89959916fe535d08c09043", + "title": "dd" + } + } +] \ No newline at end of file diff --git a/mod/base/msg/util.py b/mod/base/msg/util.py new file mode 100644 index 00000000..a5e38c9e --- /dev/null +++ b/mod/base/msg/util.py @@ -0,0 +1,139 @@ +import sys +from typing import Optional, List, Tuple +from mod.base.push_mod import BaseTask, WxAccountMsgBase, WxAccountMsg, get_push_public_data + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + + +PANEL_PATH = "/www/server/panel" +public_http_post = public.httpPost + + +def write_push_log( + module_name: str, + status: bool, + title: str, + user: Optional[List[str]] = None): + """ + 记录 告警推送情况 + @param module_name: 通道方式 + @param status: 是否成功 + @param title: 标题 + @param user: 推送到的用户,可以为空,如:钉钉 不需要 + @return: + """ + if status: + status_str = '成功' + else: + status_str = '失败' + + if not user: + user_str = '[ 默认 ]' + else: + user_str = '[ {} ]'.format(",".join(user)) + + log = '标题:【{}】,通知方式:【{}】,结果:【{}】,收件人:{}'.format(title, module_name, status_str, user_str) + public.WriteLog('告警通知', log) + return True + + +def write_mail_push_log( + title: str, + error_user: List[str], + success_user: List[str], +): + """ + 记录 告警推送情况 + @param title: 标题 + @param error_user: 失败的用户 + @param success_user: 成功的用户 + @return: + """ + e_fmt = '{}' + s_fmt = '{}' + error_user_msg = ",".join([e_fmt.format(i) for i in error_user]) + success_user = ",".join([s_fmt.format(i) for i in success_user]) + log = '标题:【{}】,通知方式:【邮箱】,发送失败的收件人:{},发送成功的收件人:{}'.format( + title, error_user_msg, success_user + ) + public.WriteLog('告警通知', log) + return True + + +def write_file(filename: str, s_body: str, mode='w+') -> bool: + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode=mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode=mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +def read_file(filename, mode='r') -> Optional[str]: + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): + return None + fp = None + try: + fp = open(filename, mode=mode) + f_body = fp.read() + except: + return None + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +class _TestMsgTask(BaseTask): + """ + 用来测试的短息 + """ + + @staticmethod + def the_push_public_data(): + return get_push_public_data() + + def get_keywords(self, task_data: dict) -> str: + pass + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + raise NotImplementedError() + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = self.title + msg.msg = "消息通道配置成功" + return msg + + +def get_test_msg(title: str, task_name="消息通道配置提醒") -> _TestMsgTask: + """ + 用来测试的短息 + """ + t = _TestMsgTask() + + t.title = title + t.template_name = task_name + return t diff --git a/mod/base/msg/web_hook_msg.py b/mod/base/msg/web_hook_msg.py new file mode 100644 index 00000000..9fa174f6 --- /dev/null +++ b/mod/base/msg/web_hook_msg.py @@ -0,0 +1,221 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: baozi +# | 消息通道HOOK模块 +# +------------------------------------------------------------------- + + +import requests +from typing import Optional, Union +from urllib3.util import parse_url + +from .util import write_push_log, get_test_msg +import json + +# config = { +# "name": "default", +# "url": "https://www.bt.cn", +# "query": { +# "aaa": "111" +# }, +# "header": { +# "AAA": "BBBB", +# }, +# "body_type": ["json", "form_data", "null"], +# "custom_parameter": { +# "rrr": "qqqq" +# }, +# "method": ["GET", "POST", "PUT", "PATCH"], +# "ssl_verify": [True, False] +# } +# # +# # 1.自动解析Query参数,拼接并展示给用户 # 可不做 +# # 2.自定义Header头 # 必做 +# # 3.Body中的内容是: type:str="首页磁盘告警", time:int=168955427, data:str="xxxxxx" # ? +# # 4.自定义参数: key=value 添加在Body中 # 可不做 +# # 5.请求类型自定义 # 必做 +# # 以上内容需要让用户可测试--! + + +class WebHookMsg(object): + DEFAULT_HEADERS = { + "User-Agent": "BT-Panel", + } + + def __init__(self, hook_data: dict): + self.id = hook_data["id"] + self.config = hook_data["data"] + + def _replace_and_parse(self, value, real_data): + """替换占位符并递归解析JSON字符串""" + if isinstance(value, str): + value = value.replace("$1", json.dumps(real_data, ensure_ascii=False)) + elif isinstance(value, dict): + for k, v in value.items(): + value[k] = self._replace_and_parse(v, real_data) + return value + + def send_msg(self, msg: str, title:str, push_type:str) -> Optional[str]: + the_url = parse_url(self.config['url']) + + ssl_verify = self.config.get("ssl_verify", None) + if ssl_verify is None: + ssl_verify = the_url.scheme == "https" + + real_data = { + "title": title, + "msg": msg, + "type": push_type, + } + # 处理custom_parameter,将$1替换为real_data内容并递归解析 + custom_data = {} + for k, v in self.config.get("custom_parameter", {}).items(): + custom_data[k] = self._replace_and_parse(v, real_data) + + if custom_data: + real_data = custom_data + + + data = None + json_data = None + headers = self.DEFAULT_HEADERS.copy() + if self.config["body_type"] == "json": + json_data = real_data + elif self.config["body_type"] == "form_data": + data = real_data + + for k, v in self.config.get("headers", {}).items(): + if not isinstance(v, str): + v = str(v) + headers[k] = v + + status = False + error = None + timeout = 10 + if data: + for k, v in data.items(): + if isinstance(v, str): + continue + else: + data[k]=json.dumps(v) + + for i in range(3): + try: + if json_data is not None: + res = requests.request( + method=self.config["method"], + url=str(the_url), + json=json_data, + headers=headers, + timeout=timeout, + verify=ssl_verify, + ) + else: + res = requests.request( + method=self.config["method"], + url=str(the_url), + data=data, + headers=headers, + timeout=timeout, + verify=ssl_verify, + ) + + if res.status_code == 200: + status = True + break + else: + status = False + return res.text + except (requests.exceptions.Timeout, requests.exceptions.ConnectionError): + timeout += 5 + continue + except requests.exceptions.RequestException as e: + error = str(e) + break + + write_push_log("Web Hook", status, title) + return error + + @classmethod + def check_args(cls, args) -> Union[str, dict]: + """配置hook""" + try: + title = args['title'] + url = args["url"] + query = args.get("query", {}) + headers = args.get("headers", {}) + body_type = args.get("body_type", "json") + custom_parameter = args.get("custom_parameter", {}) + method = args.get("method", "POST") + ssl_verify = args.get("ssl_verify", None) # null Ture + except (ValueError, KeyError): + return "参数错误" + + the_url = parse_url(url) + if the_url.scheme is None or the_url.host is None: + return"url解析错误,这可能不是一个合法的url" + + for i in (query, headers, custom_parameter): + if not isinstance(i, dict): + return "参数格式错误" + + if body_type not in ('json', 'form_data', 'null'): + return "body_type必须为json,form_data或者null" + + if method not in ('GET', 'POST', 'PUT', 'PATCH'): + return "发送方式选择错误" + + if ssl_verify not in (True, False, None): + return "是否验证ssl选项错误" + + title = title.strip() + if title == "": + return"名称不能为空" + + data = { + "title": title, + "url": url, + "query": query, + "headers": headers, + "body_type": body_type, + "custom_parameter": custom_parameter, + "method": method, + "ssl_verify": ssl_verify, + "status": True + } + + test_obj = cls({"data": data, "id": None}) + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + + test_task = get_test_msg("消息通道配置提醒") + + res = test_obj.send_msg( + test_task.to_web_hook_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒", + "消息通道配置提醒" + ) + if res is None: + return data + + return res + + def test_send_msg(self) -> Optional[str]: + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + test_task = get_test_msg("消息通道配置提醒") + res = self.send_msg( + test_task.to_web_hook_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒", + "消息通道配置提醒" + ) + if res is None: + return None + return res + diff --git a/mod/base/msg/weixin_msg.py b/mod/base/msg/weixin_msg.py new file mode 100644 index 00000000..6ef27d65 --- /dev/null +++ b/mod/base/msg/weixin_msg.py @@ -0,0 +1,130 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: baozi +# | 消息通道邮箱模块 +# +------------------------------------------------------------------- + +import re +import json +import requests +import traceback +import socket + +import requests.packages.urllib3.util.connection as urllib3_cn +from requests.packages import urllib3 +from typing import Optional, Union + +from .util import write_push_log, get_test_msg + +# 关闭警告 +urllib3.disable_warnings() + + +class WeiXinMsg: + + def __init__(self, weixin_data): + self.id = weixin_data["id"] + self.config = weixin_data["data"] + + @classmethod + def check_args(cls, args: dict) -> Union[dict, str]: + if "url" not in args or "title" not in args: + return "信息不完整" + + title = args["title"] + if len(title) > 15: + return '备注名称不能超过15个字符' + + data = { + "url": args["url"], + "title": title, + } + + test_obj = cls({"data": data, "id": None}) + test_msg = { + "msg_list": ['>配置状态:成功\n'] + } + + test_task = get_test_msg("消息通道配置提醒") + + res = test_obj.send_msg( + test_task.to_weixin_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒" + ) + if res is None: + return data + + return res + + def send_msg(self, msg: str, title: str) -> Optional[str]: + """ + @name 微信发送信息 + @msg string 消息正文(正文内容,必须包含 + 1、服务器名称 + 2、IP地址 + 3、发送时间 + ) + @to_user string 指定发送人 + """ + if not self.config: + return '未正确配置微信信息。' + + reg = '(.+)' + tmp = re.search(reg, msg) + if tmp: + tmp = tmp.groups()[0] + msg = re.sub(reg, tmp, msg) + + data = { + "msgtype": "markdown", + "markdown": { + "content": msg + } + } + headers = {'Content-Type': 'application/json'} + + status = False + error = None + try: + def allowed_gai_family(): + family = socket.AF_INET + return family + allowed_gai_family_lib = urllib3_cn.allowed_gai_family + urllib3_cn.allowed_gai_family = allowed_gai_family + response = requests.post( + url=self.config["url"], + data=json.dumps(data), + verify=False, + headers=headers, + timeout=10 + ) + urllib3_cn.allowed_gai_family = allowed_gai_family_lib + + if response.json()["errcode"] == 0: + status = True + except: + error = traceback.format_exc() + + write_push_log("企业微信", status, title) + return error + + def test_send_msg(self) -> Optional[str]: + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + test_task = get_test_msg("消息通道配置提醒") + res = self.send_msg( + test_task.to_weixin_msg(test_msg, test_task.the_push_public_data()), + "消息通道配置提醒", + ) + if res is None: + return None + return res + + + + diff --git a/mod/base/msg/wx_account_msg.py b/mod/base/msg/wx_account_msg.py new file mode 100644 index 00000000..5a624e15 --- /dev/null +++ b/mod/base/msg/wx_account_msg.py @@ -0,0 +1,556 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: baozi +# | 消息通道微信公众号模块 +# +------------------------------------------------------------------- + +import os, sys +import time, base64 + +import re +import json +import requests +import traceback +import socket + +import requests.packages.urllib3.util.connection as urllib3_cn +from requests.packages import urllib3 +from typing import Optional, Union, List, Dict, Any + +from .util import write_push_log, get_test_msg, read_file, public_http_post +from mod.base.push_mod import WxAccountMsg, SenderConfig +from mod.base import json_response + +# 关闭警告 +urllib3.disable_warnings() + + +class WeChatAccountMsg: + USER_PATH = '/www/server/panel/data/userInfo.json' + need_refresh_file = '/www/server/panel/data/mod_push_data/refresh_wechat_account.tip' + refresh_time = '/www/server/panel/data/mod_push_data/refresh_wechat_account_time.pl' + + def __init__(self, *config_data): + if len(config_data) == 0: + self.config = None + elif len(config_data) == 1: + self.config = config_data[0]["data"] + else: + self.config = config_data[0]["data"] + self.config["users"] = [i["data"]['id'] for i in config_data] + self.config["users_nickname"] = [i["data"]['nickname'] for i in config_data] + try: + self.user_info = json.loads(read_file(self.USER_PATH)) + except: + self.user_info = None + + @classmethod + def get_user_info(cls) -> Optional[dict]: + try: + return json.loads(read_file(cls.USER_PATH)) + except: + return None + + @classmethod + def last_refresh(cls): + tmp = read_file(cls.refresh_time) + if not tmp: + last_refresh_time = 0 + else: + try: + last_refresh_time = int(tmp) + except: + last_refresh_time = 0 + return last_refresh_time + + @staticmethod + def get_local_ip() -> str: + """获取内网IP""" + import socket + s = None + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + ip = s.getsockname()[0] + return ip + except: + pass + finally: + if s is not None: + s.close() + return '127.0.0.1' + + def send_msg(self, msg: WxAccountMsg) -> Optional[str]: + if self.user_info is None: + return '未获取到用户信息' + + msg.set_ip_address(self.user_info["address"], self.get_local_ip()) + template_id, msg_data = msg.to_send_data() + url = "https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v3" + wx_account_ids = self.config["users"] if "users" in self.config else [self.config["id"], ] + data = { + "uid": self.user_info["uid"], + "access_key": self.user_info["access_key"], + "data": base64.b64encode(json.dumps(msg_data).encode('utf-8')).decode('utf-8'), + "wx_account_ids": base64.b64encode(json.dumps(wx_account_ids).encode('utf-8')).decode('utf-8'), + } + if template_id != "": + data["template_id"] = template_id + + status = False + error = None + user_name = self.config["users_nickname"] if "users_nickname" in self.config else [self.config["nickname"], ] + try: + + resp = public_http_post(url, data) + x = json.loads(resp) + if x["success"]: + status = True + else: + status = False + error = x["res"] + except: + error = traceback.format_exc() + + write_push_log("微信公众号", status, msg.thing_type, user_name) + + return error + + @classmethod + def refresh_config(cls, force: bool = False): + if os.path.exists(cls.need_refresh_file): + force = True + os.remove(cls.need_refresh_file) + if force or cls.last_refresh() + 60 * 10 < time.time(): + cls._get_by_web() + + @classmethod + def _get_by_web(cls) -> Optional[List]: + user_info = cls.get_user_info() + url = "https://www.bt.cn/api/v2/user/wx_web/bound_wx_accounts" + data = { + "uid": user_info["uid"], + "access_key": user_info["access_key"], + "serverid": user_info["serverid"] + } + try: + data = json.loads(public_http_post(url, data)) + if not data["success"]: + return None + except: + return None + + cls._save_user_info(data["res"]) + return data["res"] + + @staticmethod + def _save_user_info(user_config_list: List[Dict[str, Any]]): + print(user_config_list) + user_config_dict = {i["hex"]: i for i in user_config_list} + + remove_list = [] + sc = SenderConfig() + for i in sc.config: + if i['sender_type'] != "wx_account": + continue + if i['data'].get("hex", None) in user_config_dict: + i['data'].update(user_config_dict[i['data']["hex"]]) + user_config_dict.pop(i['data']["hex"]) + else: + remove_list.append(i) + + for r in remove_list: + sc.config.remove(r) + + if user_config_dict: # 还有多的 + for v in user_config_dict.values(): + v["title"] = v["nickname"] + sc.config.append({ + "id": sc.nwe_id(), + "used": True, + "sender_type": "wx_account", + "data": v + }) + sc.save_config() + + @classmethod + def unbind(cls, wx_account_uid: str): + user_info = cls.get_user_info() + if user_info is None: + return json_response(status=True, msg='未获取到用户绑定的信息') + url = "https://www.bt.cn/api/v2/user/wx_web/unbind_wx_accounts" + data = { + "uid": user_info["uid"], + "access_key": user_info["access_key"], + "serverid": user_info["serverid"], + "ids": str(wx_account_uid) + } + try: + datas = json.loads(public_http_post(url, data)) + if datas["success"]: + return json_response(status=True, data=datas, msg="解绑成功") + else: + return json_response(status=False, data=datas, msg=datas["res"]) + except: + return json_response(status=True, msg="链接云端失败") + + @classmethod + def get_auth_url(cls): + user_info = cls.get_user_info() + if user_info is None: + return json_response(status=True, msg='未获取到用户绑定的信息') + url = "https://www.bt.cn/api/v2/user/wx_web/get_auth_url" + data = { + "uid": user_info["uid"], + "access_key": user_info["access_key"], + "serverid": user_info["serverid"], + } + try: + datas = json.loads(public_http_post(url, data)) + if datas["success"]: + return json_response(status=True, data=datas) + else: + return json_response(status=False, data=datas, msg=datas["res"]) + except: + return json_response(status=True, msg="链接云端失败") + + def test_send_msg(self) -> Optional[str]: + test_msg = { + "msg_list": ['>配置状态:成功\n\n'] + } + test_task = get_test_msg("消息通道配置提醒") + res = self.send_msg( + test_task.to_wx_account_msg(test_msg, test_task.the_push_public_data()), + ) + if res is None: + return None + return res + + +# class wx_account_msg: +# __module_name = None +# __default_pl = "{}/data/default_msg_channel.pl".format(panelPath) +# conf_path = '{}/data/wx_account_msg.json'.format(panelPath) +# user_info = None +# +# def __init__(self): +# try: +# self.user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path()))) +# except: +# self.user_info = None +# self.__module_name = self.__class__.__name__.replace('_msg', '') +# +# def get_version_info(self, get): +# """ +# 获取版本信息 +# """ +# data = {} +# data['ps'] = '宝塔微信公众号,用于接收面板消息推送' +# data['version'] = '1.0' +# data['date'] = '2022-08-15' +# data['author'] = '宝塔' +# data['title'] = '微信公众号' +# data['help'] = 'http://www.bt.cn' +# return data +# +# def get_local_ip(self): +# '''获取内网IP''' +# import socket +# try: +# s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +# s.connect(('8.8.8.8', 80)) +# ip = s.getsockname()[0] +# return ip +# finally: +# s.close() +# return '127.0.0.1' +# +# def get_config(self, get): +# """ +# 微信公众号配置 +# """ +# if os.path.exists(self.conf_path): +# # 60S内不重复加载 +# start_time = int(time.time()) +# if os.path.exists("data/wx_account_msg.lock"): +# lock_time = 0 +# try: +# lock_time = int(public.ReadFile("data/wx_account_msg.lock")) +# except: +# pass +# # 大于60S重新加载 +# if start_time - lock_time > 60: +# public.run_thread(self.get_web_info2) +# public.WriteFile("data/wx_account_msg.lock", str(start_time)) +# else: +# public.WriteFile("data/wx_account_msg.lock", str(start_time)) +# public.run_thread(self.get_web_info2) +# data = json.loads(public.ReadFile(self.conf_path)) +# +# if not 'list' in data: data['list'] = {} +# +# title = '默认' +# if 'res' in data and 'nickname' in data['res']: title = data['res']['nickname'] +# +# data['list']['default'] = {'title': title, 'data': ''} +# +# data['default'] = self.__get_default_channel() +# return data +# else: +# public.run_thread(self.get_web_info2) +# return {"success": False, "res": "未获取到配置信息"} +# +# def set_config(self, get): +# """ +# @设置默认值 +# """ +# if 'default' in get and get['default']: +# public.writeFile(self.__default_pl, self.__module_name) +# +# return public.returnMsg(True, '设置成功') +# +# def get_web_info(self, get): +# if self.user_info is None: return public.returnMsg(False, '未获取到用户绑定的信息') +# url = "https://www.bt.cn/api/v2/user/wx_web/info" +# data = { +# "uid": self.user_info["uid"], +# "access_key": self.user_info["access_key"], +# "serverid": self.user_info["serverid"] +# } +# try: +# +# datas = json.loads(public.httpPost(url, data)) +# +# if datas["success"]: +# public.WriteFile(self.conf_path, json.dumps(datas)) +# return public.returnMsg(True, datas) +# else: +# public.WriteFile(self.conf_path, json.dumps(datas)) +# return public.returnMsg(False, datas) +# except: +# public.WriteFile(self.conf_path, json.dumps({"success": False, "res": "链接云端失败,请检查网络"})) +# return public.returnMsg(False, "链接云端失败,请检查网络") +# +# def unbind(self): +# if self.user_info is None: +# return public.returnMsg(False, '未获取到用户绑定的信息') +# url = "https://www.bt.cn/api/v2/user/wx_web/unbind" +# data = { +# "uid": self.user_info["uid"], +# "access_key": self.user_info["access_key"], +# "serverid": self.user_info["serverid"] +# } +# try: +# +# datas = json.loads(public.httpPost(url, data)) +# +# if os.path.exists(self.conf_path): +# os.remove(self.conf_path) +# +# if datas["success"]: +# return public.returnMsg(True, datas) +# else: +# return public.returnMsg(False, datas) +# except: +# public.WriteFile(self.conf_path, json.dumps({"success": False, "res": "链接云端失败,请检查网络"})) +# return public.returnMsg(False, "链接云端失败,请检查网络") +# +# def get_web_info2(self): +# if self.user_info is None: +# return public.returnMsg(False, '未获取到用户绑定的信息') +# url = "https://www.bt.cn/api/v2/user/wx_web/info" +# data = { +# "uid": self.user_info["uid"], +# "access_key": self.user_info["access_key"], +# "serverid": self.user_info["serverid"] +# } +# try: +# datas = json.loads(public.httpPost(url, data)) +# if datas["success"]: +# public.WriteFile(self.conf_path, json.dumps(datas)) +# return public.returnMsg(True, datas) +# else: +# public.WriteFile(self.conf_path, json.dumps(datas)) +# return public.returnMsg(False, datas) +# except: +# public.WriteFile(self.conf_path, json.dumps({"success": False, "res": "链接云端失败"})) +# return public.returnMsg(False, "链接云端失败") +# +# def get_send_msg(self, msg): +# """ +# @name 处理md格式 +# """ +# try: +# import re +# title = '宝塔告警通知' +# if msg.find("####") >= 0: +# try: +# title = re.search(r"####(.+)", msg).groups()[0] +# except: +# pass +# +# msg = msg.replace("####", ">").replace("\n\n", "\n").strip() +# s_list = msg.split('\n') +# +# if len(s_list) > 3: +# s_title = s_list[0].replace(" ", "") +# s_list = s_list[3:] +# s_list.insert(0, s_title) +# msg = '\n'.join(s_list) +# +# s_list = [] +# for msg_info in msg.split('\n'): +# reg = '(.+)' +# tmp = re.search(reg, msg_info) +# if tmp: +# tmp = tmp.groups()[0] +# msg_info = re.sub(reg, tmp, msg_info) +# s_list.append(msg_info) +# msg = '\n'.join(s_list) +# except: +# pass +# return msg, title +# +# def send_msg(self, msg): +# """ +# 微信发送信息 +# @msg 消息正文 +# """ +# +# if self.user_info is None: +# return public.returnMsg(False, '未获取到用户信息') +# +# if not isinstance(msg, str): +# return self.send_msg_v2(msg) +# +# msg, title = self.get_send_msg(msg) +# url = "https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v2" +# datassss = { +# "first": { +# "value": "堡塔主机告警", +# }, +# "keyword1": { +# "value": "内网IP " + self.get_local_ip() + "\n外网IP " + self.user_info[ +# "address"] + " \n服务器别名 " + public.GetConfigValue("title"), +# }, +# "keyword2": { +# "value": "堡塔主机告警", +# }, +# "keyword3": { +# "value": msg, +# }, +# "remark": { +# "value": "如有疑问,请联系宝塔客服", +# }, +# } +# data = { +# "uid": self.user_info["uid"], +# "access_key": self.user_info["access_key"], +# "data": base64.b64encode(json.dumps(datassss).encode('utf-8')).decode('utf-8') +# } +# +# try: +# res = {} +# error, success = 0, 0 +# +# x = json.loads(public.httpPost(url, data)) +# conf = self.get_config(None)['list'] +# +# # 立即刷新剩余次数 +# public.run_thread(self.get_web_info2) +# +# res[conf['default']['title']] = 0 +# if x['success']: +# res[conf['default']['title']] = 1 +# success += 1 +# else: +# error += 1 +# +# try: +# public.write_push_log(self.__module_name, title, res) +# except: +# pass +# +# result = public.returnMsg(True, '发送完成,发送成功{},发送失败{}.'.format(success, error)) +# result['success'] = success +# result['error'] = error +# return result +# +# except: +# print(public.get_error_info()) +# return public.returnMsg(False, '微信消息发送失败。 --> {}'.format(public.get_error_info())) +# +# def push_data(self, data): +# if isinstance(data, dict): +# return self.send_msg(data['msg']) +# else: +# return self.send_msg_v2(data) +# +# def uninstall(self): +# if os.path.exists(self.conf_path): +# os.remove(self.conf_path) +# +# def send_msg_v2(self, msg): +# from push.base_push import WxAccountMsgBase, WxAccountMsg +# if self.user_info is None: +# return public.returnMsg(False, '未获取到用户信息') +# +# if isinstance(msg, public.dict_obj): +# msg = getattr(msg, "msg", "测试信息") +# if len(msg) >= 20: +# return self.send_msg(msg) +# +# if isinstance(msg, str): +# the_msg = WxAccountMsg.new_msg() +# the_msg.thing_type = msg +# the_msg.msg = msg +# msg = the_msg +# +# if not isinstance(msg, WxAccountMsgBase): +# return public.returnMsg(False, '消息类型错误') +# +# msg.set_ip_address(self.user_info["address"], self.get_local_ip()) +# +# template_id, msg_data = msg.to_send_data() +# url = "https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v2" +# data = { +# "uid": self.user_info["uid"], +# "access_key": self.user_info["access_key"], +# "data": base64.b64encode(json.dumps(msg_data).encode('utf-8')).decode('utf-8'), +# } +# if template_id != "": +# data["template_id"] = template_id +# +# try: +# error, success = 0, 0 +# resp = public.httpPost(url, data) +# x = json.loads(resp) +# conf = self.get_config(None)['list'] +# +# # 立即刷新剩余次数 +# public.run_thread(self.get_web_info2) +# +# res = { +# conf['default']['title']: 0 +# } +# if x['success']: +# res[conf['default']['title']] = 1 +# success += 1 +# else: +# error += 1 +# +# try: +# public.write_push_log(self.__module_name, msg.thing_type, res) +# except: +# pass +# result = public.returnMsg(True, '发送完成,发送成功{},发送失败{}.'.format(success, error)) +# result['success'] = success +# result['error'] = error +# return result +# +# except: +# return public.returnMsg(False, '微信消息发送失败。 --> {}'.format(public.get_error_info())) diff --git a/mod/base/process/__init__.py b/mod/base/process/__init__.py new file mode 100644 index 00000000..7bc1c36f --- /dev/null +++ b/mod/base/process/__init__.py @@ -0,0 +1,7 @@ +from .process import RealProcess +from .user import RealUser +from .server import RealServer +from .process import Process +from .user import User +from .server import Server +__all__ = ['RealProcess', 'Process', 'RealUser', 'User', 'RealServer', 'Server'] diff --git a/mod/base/process/process.py b/mod/base/process/process.py new file mode 100644 index 00000000..65204437 --- /dev/null +++ b/mod/base/process/process.py @@ -0,0 +1,889 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: sww +# ------------------------------------------------------------------- +import json +import os +# ------------------------------ +# 进程模型 +# ------------------------------ +import sys +import time +import traceback + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +os.chdir("/www/server/panel") +import public +import psutil +from typing import Any + +try: + from BTPanel import cache +except: + import cachelib + + cache = cachelib.SimpleCache() + + +class RealProcess: + process_path = '/proc' + ps = json.loads(public.readFile('/www/server/panel/mod/base/process/process_ps.json')) + __isUfw = False + __isFirewalld = False + old_info = {} + new_info = {} + old_path = '/tmp/bt_task_old1.json' + __cpu_time = None + __process_net_list = {} + last_net_process = None + last_net_process_time = 0 + old_net_path = '/tmp/bt_network_old1.json' + old_net_info = {} + new_net_info = {} + + def __init__(self): + if os.path.exists('/usr/sbin/firewalld'): self.__isFirewalld = True + if os.path.exists('/usr/sbin/ufw'): self.__isUfw = True + + def object_to_dict(self, obj): + result = {} + for name in dir(obj): + value = getattr(obj, name) + if not name.startswith('__') and not callable(value) and not name.startswith('_'): result[name] = value + return result + + def get_computers_use(self): + result = {} + cpu_usage = psutil.cpu_percent(interval=1, percpu=True) + result['cpu'] = round(sum(cpu_usage) / len(cpu_usage), 2) + memory = psutil.virtual_memory() + print(memory.total) + result['memory_usage'] = memory.percent + disk = psutil.disk_usage('/') + result['disk_usage'] = round(((disk.used / disk.total) * 100), 0) + network_io = psutil.net_io_counters() + result['network_io_bytes_sent'] = network_io.bytes_sent + result['network_io_bytes_recv'] = network_io.bytes_recv + + return result + + # ------------------------------ 获取进程列表 start ------------------------------ + def get_process_list(self): + """ + 获取进程列表 + :return: + """ + try: + process_list = [] + if type(self.new_info) != dict: self.new_info = {} + self.new_info['cpu_time'] = self.get_cpu_time() + self.new_info['time'] = time.time() + self.get_process_net_list() + for proc in psutil.process_iter( + ['pid', 'ppid', 'name', 'username', 'create_time', 'memory_info', 'io_counters', 'num_threads', 'create_time', 'connections', 'open_files', 'status', 'cmdline']): + try: + proc_info = proc.as_dict( + attrs=['pid', 'ppid', 'name', 'username', 'create_time', 'memory_info', 'io_counters', 'num_threads', 'create_time', 'connections', 'open_files', 'status', + 'cmdline']) + p_cpus = proc.cpu_times() + process_list.append({ + 'pid': proc_info['pid'], + 'ppid': proc_info['ppid'], + 'name': proc_info['name'], + 'username': proc_info['username'], + 'cpu_percent': self.get_cpu_percent(str(proc_info['pid']), p_cpus, self.new_info['cpu_time']), + 'running_time': time.time() - proc_info['create_time'], + 'memory_info': proc_info['memory_info'], + 'io_info': proc_info['io_counters'], + 'num_threads': proc_info['num_threads'], + 'create_time': proc_info['create_time'], + 'connections_info': proc_info['connections'], + 'open_files': proc_info['open_files'], + 'ps': self.get_process_ps(proc.name())['data'], + 'status': proc_info['status'], + 'cmdline': proc_info['cmdline'], + 'net_info': self.get_process_network(proc_info['pid']) + }) + cache.set(self.old_path, self.new_info, 600) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data=process_list) + except Exception as e: + return public.returnResult(code=0, msg='获取进程列表失败' + str(e), status=False) + + # ------------------------------ 获取进程列表 end ------------------------------ + + # ------------------------------ 获取进程信息 start ------------------------------ + + @staticmethod + def _format_connections(connects): + result = [] + for i in connects: + r_addr = i.raddr + if not i.raddr: + r_addr = ('', 0) + l_addr = i.laddr + if not i.laddr: + l_addr = ('', 0) + result.append({ + "fd": i.fd, + "family": i.family, + "local_addr": l_addr[0], + "local_port": l_addr[1], + "client_addr": r_addr[0], + "client_rport": r_addr[1], + "status": i.status + }) + return result + + @staticmethod + def get_connects(pid: str): + ''' + @name 获取进程连接信息 + @author hwliang<2021-08-09> + @param pid + @return dict + ''' + connects = 0 + try: + if pid == 1: + return connects + tp = '/proc/' + str(pid) + '/fd/' + if not os.path.exists(tp): + return connects + for d in os.listdir(tp): + f_name = tp + d + if os.path.islink(f_name): + l = os.readlink(f_name) + if l.find('socket:') != -1: + connects += 1 + except: + pass + return connects + + def get_process_info_by_pid(self, pid: int) -> dict: + """ + 获取进程信息 + :param pid: + :return: + """ + try: + status_ps = {'sleeping': '睡眠', 'running': '活动'} + process = psutil.Process(int(pid)) + if type(self.new_info) != dict: self.new_info = {} + self.new_info['cpu_time'] = self.get_cpu_time() + self.new_info['time'] = time.time() + self.get_process_net_list() + p_cpus = process.cpu_times() + # 获取连接信息 + connections = process.connections() + p_mem = process.memory_full_info() + io_info = process.io_counters() + info = { + 'pid': process.pid, + 'ppid': process.ppid(), + 'name': process.name(), + 'threads': process.num_threads(), + 'user': process.username(), + 'username': process.username(), + 'cpu_percent': self.get_cpu_percent(process.pid, p_cpus, self.new_info['cpu_time']), + 'memory_info': self.object_to_dict(p_mem), + 'memory_used': p_mem.uss, + 'io_info': self.object_to_dict(io_info), + "io_write_bytes": io_info.write_bytes, + "io_read_bytes": io_info.read_bytes, + 'connections': self._format_connections(connections), + "connects": self.get_connects(str(process.pid)), + 'status': status_ps[process.status()] if process.status() in status_ps else process.status(), + 'create_time': process.create_time(), + 'running_time': process.cpu_times().user + process.cpu_times().system, + 'cmdline': process.cmdline(), + 'open_files': [self.object_to_dict(i) for i in process.open_files()], + 'ps': self.get_process_ps(process.name())['data'], + 'net_info': self.get_process_network(process.pid), + "exe": ' '.join(process.cmdline()), + } + cache.set(self.old_path, self.new_info, 600) + return public.returnResult(code=1, msg='success', status=True, data=info) + except Exception as e: + return public.returnResult(code=0, msg='获取进程信息失败' + str(e), status=False) + + # 通过name获取进程信息 + def get_process_info_by_name(self, name: str) -> dict: + """ + 通过name获取进程信息 + :param name: + :return: + """ + try: + pids = [i.pid for i in psutil.process_iter(['pid', 'name', 'cmdline']) if i.name() == name] + infos = [] + for pid in pids: + try: + info = self.get_process_info_by_pid(pid) + if info['status']: + infos.append(info['data']) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data=infos) + except Exception as e: + return public.returnResult(code=0, msg='获取进程信息失败' + str(e), status=False) + + # 通过启动命令获取进程信息 + def get_process_info_by_exec(self, cli: str) -> dict: + """ + 通过启动命令获取进程信息 + :param cli:启动命令 + :return: + """ + + try: + pids = [i.pid for i in psutil.process_iter(['pid', 'cmdline']) if cli in ' '.join(i.cmdline())] + infos = [] + for pid in pids: + try: + info = self.get_process_info_by_pid(pid) + if info['status']: + infos.append(info['data']) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data=infos) + except Exception as e: + return public.returnResult(code=0, msg='获取进程信息失败' + str(e), status=False) + + def get_process_info_by_port(self, port: int) -> dict: + """ + 通过端口获取进程信息 + :param port: + :return: + """ + try: + infos = [] + for i in psutil.process_iter(['pid', 'connections']): + for conn in i.connections(): + try: + if conn.laddr.port == int(port): + info = self.get_process_info_by_pid(i.pid) + if info['status']: + infos.append(info['data']) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data=infos) + except Exception as e: + return public.returnResult(code=0, msg='获取进程信息失败' + str(e), status=False) + + def get_process_info_by_ip(self, ip: str) -> dict: + """ + 通过远程ip获取进程信息 + :param ip: + :return: + """ + infos = [] + try: + for i in psutil.process_iter(['pid', 'connections']): + for conn in i.connections(): + try: + if conn.raddr: + if conn.raddr.ip == ip: + info = self.get_process_info_by_pid(i.pid)['data'] + if info: + infos.append(info) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data=infos) + except: + return public.returnResult(code=0, msg='获取进程信息失败', status=False, data=infos) + + def get_process_info_by_openfile(self, file_path: str) -> dict: + """ + 通过打开文件获取进程信息 + :param file_path: + :return: + """ + infos = [] + try: + for i in psutil.process_iter(['pid', 'open_files']): + try: + for file in i.open_files(): + if file.path == file_path: + info = self.get_process_info_by_pid(i.pid)['data'] + if info: + infos.append(info) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data=infos) + except: + return public.returnResult(code=0, msg='获取进程信息失败', status=False, data=infos) + + # ------------------------------ 获取进程信息 end ------------------------------ + + # ------------------------------ 获取进程ps start ------------------------------ + + def get_process_ps(self, name: str) -> dict: + """ + 获取进程ps + :param name: + :return: + """ + + return public.returnResult(code=1, msg='success', status=True, data=self.ps.get(name, '未知进程')) + + # ------------------------------ 获取进程ps end ------------------------------ + + # ------------------------------ 获取进程树 start ------------------------------ + + def get_process_tree(self, pid: int) -> dict: + """ + 获取进程树 + :param pid: + :return: + """ + try: + pid = int(pid) + process = psutil.Process(pid) + process_tree = process.children(recursive=True) + infos = [] + info = self.get_process_info_by_pid(pid) + if info['status']: + infos.append(info['data']) + + for prc in process_tree: + info = self.get_process_info_by_pid(prc.pid) + if info['status']: + infos.append(info['data']) + return public.returnResult(code=1, msg='success', status=True, data=infos) + except Exception as e: + return public.returnResult(code=0, msg='获取进程树失败' + str(e), status=False) + + # ------------------------------ 获取进程树 end ------------------------------ + + # ------------------------------ 结束进程 start ------------------------------ + # 结束进程pid + def kill_pid(self, pid: int) -> dict: + """ + 通过关闭进程 + :param pid: + :return: + """ + try: + os.kill(pid, 9) + return public.returnResult(code=1, msg='success', status=True, data='') + except Exception as e: + public.ExecShell('kill -9 ' + str(pid)) + return public.returnResult(code=1, msg='结束进程失败' + str(e), status=True) + + # 结束进程名 + def kill_name(self, name: str) -> dict: + """ + 通过name关闭进程 + :param name: + :return: + """ + try: + os.system('killall ' + name) + return public.returnResult(code=1, msg='success', status=True, data='') + except Exception as e: + return public.returnResult(code=0, msg='结束进程失败' + str(e), status=False) + + # 结束进程树 + def kill_tree(self, pid: int) -> dict: + """ + 通过关闭进程树 + :param pid: + :return: + """ + try: + p = psutil.Process(pid) + p.kill() + for i in p.children(recursive=True): + i.kill() + return public.returnResult(code=1, msg='success', status=True, data='') + except Exception as e: + public.ExecShell('kill -9 ' + str(pid)) + return public.returnResult(code=1, msg='success', status=True) + + # 结束所有进程 pid,进程名,进程树 + def kill_proc_all(self, pid: int) -> dict: + """ + 结束所有进程 + :return: + """ + try: + proc = psutil.Process(pid) + name = proc.name() + self.kill_pid(pid) + self.kill_name(name) + self.kill_tree(pid) + return public.returnResult(code=1, msg='success', status=True, data='') + except Exception as e: + return public.returnResult(code=0, msg='结束进程失败' + str(e), status=False) + + def kill_port(self, port: str) -> dict: + """ + 结束端口进程 + :param port: + :return: + """ + for process in psutil.process_iter(['pid', 'name', 'connections']): + try: + for conn in process.connections(): + if conn.laddr.port == int(port): + self.kill_pid(process.pid) + except: + pass + return public.returnResult(code=1, msg='success', status=True, data='') + + # ------------------------------ 结束进程 end ------------------------------ + + # ------------------------------ 拉黑ip start ------------------------------ + def add_black_ip(self, ips: list, ) -> dict: + """ + 拉黑ip + :param ip: + :return: + """ + try: + if not public.get_firewall_status() == 1: return public.returnMsg(False, '当前系统防火墙未开启') + if [ip for ip in ips if ip in ['0.0.0.0', '127.0.0.0', "::1"]]: return {'status': False, 'msg': '禁止拉黑本机ip', 'data': ''} + for ip in ips: + if not public.check_ip(ip): continue + if public.M('firewall_ip').where("port=?", (ip,)).count() > 0: continue + if self.__isUfw: + if public.is_ipv6(ip): + public.ExecShell('ufw deny from ' + ip + ' to any') + else: + public.ExecShell('ufw insert 1 deny from ' + ip + ' to any') + else: + if self.__isFirewalld: + if public.is_ipv6(ip): + public.ExecShell('firewall-cmd --permanent --add-rich-rule=\'rule family=ipv6 source address="' + ip + '" drop\'') + else: + public.ExecShell('firewall-cmd --permanent --add-rich-rule=\'rule family=ipv4 source address="' + ip + '" drop\'') + else: + if public.is_ipv6(ip): return public.returnMsg(False, 'FIREWALL_IP_FORMAT') + public.ExecShell('iptables -I INPUT -s ' + ip + ' -j DROP') + addtime = time.strftime('%Y-%m-%d %X', time.localtime()) + public.M('firewall_ip').add('address,addtime,types', (ip, addtime, 'drop')) + self.firewall_reload() + return public.returnResult(code=1, msg='success', status=True, data='') + except Exception as e: + return public.returnResult(code=0, msg='拉黑失败' + str(e), status=False) + + # ------------------------------ 拉黑ip end ------------------------------ + + # ------------------------------ 取消拉黑ip start ------------------------------ + # 删除IP屏蔽 + def del_black_ip(self, ips: list) -> dict: + try: + if not public.get_firewall_status() == 1: return public.returnMsg(False, '当前系统防火墙未开启') + for ip in ips: + if not public.check_ip(ip): continue + if self.__isUfw: + public.ExecShell('ufw delete deny from ' + ip + ' to any') + else: + if self.__isFirewalld: + if public.is_ipv6(ip): + public.ExecShell('firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv6 source address="' + ip + '" drop\'') + else: + public.ExecShell('firewall-cmd --permanent --remove-rich-rule=\'rule family=ipv4 source address="' + ip + '" drop\'') + else: + public.ExecShell('iptables -D INPUT -s ' + ip + ' -j DROP') + + public.WriteLog("TYPE_FIREWALL", 'FIREWALL_ACCEPT_IP', (ip,)) + public.M('firewall_ip').where("address=?", (ip,)).delete() + + self.firewall_reload() + return public.returnResult(code=1, msg='success', status=True) + except Exception as e: + return public.returnResult(code=0, msg='删除失败' + str(e), status=False) + + # 重载防火墙配置 + def firewall_reload(self): + try: + if self.__isUfw: + public.ExecShell('/usr/sbin/ufw reload &') + return public.returnResult(code=1, msg='success', status=True) + if self.__isFirewalld: + public.ExecShell('firewall-cmd --reload &') + else: + public.ExecShell('/etc/init.d/iptables save &') + public.ExecShell('/etc/init.d/iptables restart &') + return public.returnResult(code=1, msg='success', status=True) + except: + return public.returnResult(code=0, msg='重载防火墙失败', status=False) + + # ------------------------------ 取消拉黑ip end ------------------------------ + + # ------------------------------ 获取进程cpu start ------------------------------ + + # 获取cpu使用率 + def get_cpu_percent(self, pid, cpu_times, cpu_time): + self.get_old() + percent = 0.00 + process_cpu_time = self.get_process_cpu_time(cpu_times) + if not self.old_info: self.old_info = {} + if not pid in self.old_info: + self.new_info[pid] = {} + self.new_info[pid]['cpu_time'] = process_cpu_time + return percent + try: + percent = round( + 100.00 * (process_cpu_time - self.old_info[pid]['cpu_time']) / (cpu_time - self.old_info['cpu_time']), 2) + except: + return 0 + self.new_info[pid] = {} + self.new_info[pid]['cpu_time'] = process_cpu_time + if percent > 0: return percent + return 0.00 + + def get_process_cpu_time(self, cpu_times): + cpu_time = 0.00 + for s in cpu_times: cpu_time += s + return cpu_time + + def get_old(self): + if self.old_info: return True + data = cache.get(self.old_path) + if not data: return False + self.old_info = data + del (data) + return True + + def get_cpu_time(self): + if self.__cpu_time: return self.__cpu_time + self.__cpu_time = 0.00 + s = psutil.cpu_times() + self.__cpu_time = s.user + s.system + s.nice + s.idle + return self.__cpu_time + + # ------------------------------ 获取进程cpu end ------------------------------ + + # ------------------------------ 获取进程net start ------------------------------ + + def get_process_network(self, pid): + ''' + @name 获取进程网络流量 + @author hwliang<2021-09-13> + @param pid 进程ID + @return tuple + ''' + if not self.__process_net_list: + self.get_process_net_list() + if not self.last_net_process_time: return 0, 0, 0, 0 + if not pid in self.__process_net_list: return 0, 0, 0, 0 + + if not pid in self.last_net_process: + return self.__process_net_list[pid]['up'], self.__process_net_list[pid]['up_package'], \ + self.__process_net_list[pid]['down'], self.__process_net_list[pid]['down_package'] + + up = int((self.__process_net_list[pid]['up'] - self.last_net_process[pid]['up']) / ( + time.time() - self.last_net_process_time)) + down = int((self.__process_net_list[pid]['down'] - self.last_net_process[pid]['down']) / ( + time.time() - self.last_net_process_time)) + up_package = int((self.__process_net_list[pid]['up_package'] - self.last_net_process[pid]['up_package']) / ( + time.time() - self.last_net_process_time)) + down_package = int( + (self.__process_net_list[pid]['down_package'] - self.last_net_process[pid]['down_package']) / ( + time.time() - self.last_net_process_time)) + return up, up_package, down, down_package + + def get_process_net_list(self): + w_file = '/dev/shm/bt_net_process' + if not os.path.exists(w_file): return + self.last_net_process = cache.get('net_process') + self.last_net_process_time = cache.get('last_net_process') + net_process_body = public.readFile(w_file) + if not net_process_body: return + net_process = net_process_body.split('\n') + for np in net_process: + if not np: continue + tmp = {} + np_list = np.split() + if len(np_list) < 5: continue + tmp['pid'] = int(np_list[0]) + tmp['down'] = int(np_list[1]) + tmp['up'] = int(np_list[2]) + tmp['down_package'] = int(np_list[3]) + tmp['up_package'] = int(np_list[4]) + self.__process_net_list[tmp['pid']] = tmp + cache.set('net_process', self.__process_net_list, 600) + cache.set('last_net_process', time.time(), 600) + + def get_network(self): + try: + self.get_net_old() + networkIo = psutil.net_io_counters()[:4] + self.new_net_info['upTotal'] = networkIo[0] + self.new_net_info['downTotal'] = networkIo[1] + self.new_net_info['upPackets'] = networkIo[2] + self.new_net_info['downPackets'] = networkIo[3] + self.new_net_info['time'] = time.time() + + if not self.old_net_info: self.old_net_info = {} + if not 'upTotal' in self.old_net_info: + time.sleep(0.1) + networkIo = psutil.net_io_counters()[:4] + self.old_net_info['upTotal'] = networkIo[0] + self.old_net_info['downTotal'] = networkIo[1] + self.old_net_info['upPackets'] = networkIo[2] + self.old_net_info['downPackets'] = networkIo[3] + self.old_net_info['time'] = time.time() + + s = self.new_net_info['time'] - self.old_net_info['time'] + networkInfo = {} + networkInfo['upTotal'] = networkIo[0] + networkInfo['downTotal'] = networkIo[1] + networkInfo['up'] = round((float(networkIo[0]) - self.old_net_info['upTotal']) / s, 2) + networkInfo['down'] = round((float(networkIo[1]) - self.old_net_info['downTotal']) / s, 2) + networkInfo['downPackets'] = networkIo[3] + networkInfo['upPackets'] = networkIo[2] + networkInfo['downPackets_s'] = int((networkIo[3] - self.old_net_info['downPackets']) / s) + networkInfo['upPackets_s'] = int((networkIo[2] - self.old_net_info['upPackets']) / s) + cache.set(self.old_net_path, self.new_net_info, 600) + return networkInfo + except: + return None + + def get_net_old(self): + if self.old_net_info: return True + data = cache.get(self.old_net_path) + if not data: return False + if not data: return False + self.old_net_info = data + del (data) + return True + + # ------------------------------ 获取进程net end ------------------------------ + + # ------------------------------ 获取启动项列表 start ------------------------------ + def get_run_list(self, search: str = ''): + runFile = ['/etc/rc.local', '/etc/profile', '/etc/inittab', '/etc/rc.sysinit'] + runList = [] + for rfile in runFile: + if not os.path.exists(rfile): continue + bodyR = self.clear_comments(public.readFile(rfile)) + if not bodyR: continue + stat = os.stat(rfile) + accept = str(oct(stat.st_mode)[-3:]) + if accept == '644': continue + tmp = {} + tmp['name'] = rfile + tmp['srcfile'] = rfile + tmp['size'] = os.path.getsize(rfile) + tmp['access'] = accept + tmp['ps'] = self.get_run_ps(rfile) + runList.append(tmp) + runlevel = self.get_my_runlevel() + runPath = ['/etc/init.d', '/etc/rc' + runlevel + '.d'] + tmpAll = [] + islevel = False + for rpath in runPath: + if not os.path.exists(rpath): continue + if runPath[1] == rpath: islevel = True + for f in os.listdir(rpath): + if f[:1] != 'S': continue + filename = rpath + '/' + f + if not os.path.exists(filename): continue + if os.path.isdir(filename): continue + if os.path.islink(filename): + flink = os.readlink(filename).replace('../', '/etc/') + if not os.path.exists(flink): continue + filename = flink + tmp = {} + tmp['name'] = f + if islevel: tmp['name'] = f[3:] + if tmp['name'] in tmpAll: continue + stat = os.stat(filename) + accept = str(oct(stat.st_mode)[-3:]) + if accept == '644': continue + tmp['srcfile'] = filename + tmp['access'] = accept + tmp['size'] = os.path.getsize(filename) + tmp['ps'] = self.get_run_ps(tmp['name']) + runList.append(tmp) + tmpAll.append(tmp['name']) + data = {} + data['run_list'] = runList + data['run_level'] = runlevel + if search: + data['run_list'] = self.search_run(data['run_list'], search) + return public.returnResult(code=1, msg='success', status=True, data=data) + + # 启动项查询 + def search_run(self, data, search): + try: + ldata = [] + for i in data: + if search in i['name'] or search in i['srcfile'] or search in i['ps']: + ldata.append(i) + return ldata + except: + return data + + # 清除注释 + def clear_comments(self, body): + bodyTmp = body.split("\n") + bodyR = "" + for tmp in bodyTmp: + if tmp.startswith('#'): continue + if tmp.strip() == '': continue + bodyR += tmp + return bodyR + + # 服务注释 + def get_run_ps(self, name): + runPs = {'netconsole': '网络控制台日志', 'network': '网络服务', 'jexec': 'JAVA', 'tomcat8': 'Apache Tomcat', + 'tomcat7': 'Apache Tomcat', 'mariadb': 'Mariadb', + 'tomcat9': 'Apache Tomcat', 'tomcat': 'Apache Tomcat', 'memcached': 'Memcached缓存器', + 'php-fpm-53': 'PHP-5.3', 'php-fpm-52': 'PHP-5.2', + 'php-fpm-54': 'PHP-5.4', 'php-fpm-55': 'PHP-5.5', 'php-fpm-56': 'PHP-5.6', 'php-fpm-70': 'PHP-7.0', + 'php-fpm-71': 'PHP-7.1', + 'php-fpm-72': 'PHP-7.2', 'rsync_inotify': 'rsync实时同步', 'pure-ftpd': 'FTP服务', + 'mongodb': 'MongoDB', 'nginx': 'Web服务器(Nginx)', + 'httpd': 'Web服务器(Apache)', 'bt': '宝塔面板', 'mysqld': 'MySQL数据库', 'rsynd': 'rsync主服务', + 'php-fpm': 'PHP服务', 'systemd': '系统核心服务', + '/etc/rc.local': '用户自定义启动脚本', '/etc/profile': '全局用户环境变量', + '/etc/inittab': '用于自定义系统运行级别', '/etc/rc.sysinit': '系统初始化时调用的脚本', + 'sshd': 'SSH服务', 'crond': '计划任务服务', 'udev-post': '设备管理系统', 'auditd': '审核守护进程', + 'rsyslog': 'rsyslog服务', 'sendmail': '邮件发送服务', 'blk-availability': 'lvm2相关', + 'local': '用户自定义启动脚本', 'netfs': '网络文件系统', 'lvm2-monitor': 'lvm2相关', + 'xensystem': 'xen云平台相关', 'iptables': 'iptables防火墙', 'ip6tables': 'iptables防火墙 for IPv6', + 'firewalld': 'firewall防火墙'} + if name in runPs: return runPs[name] + return name + + # 获取当前运行级别 + def get_my_runlevel(self): + try: + runlevel = public.ExecShell('runlevel')[0].split()[1] + except: + runlevel_dict = {"multi-user.target": '3', 'rescue.target': '1', 'poweroff.target': '0', + 'graphical.target': '5', "reboot.target": '6'} + r_tmp = public.ExecShell('systemctl get-default')[0].strip() + if r_tmp in runlevel_dict: + runlevel = runlevel_dict[r_tmp] + else: + runlevel = '3' + return runlevel + + # ------------------------------ 获取启动项列表 end ------------------------------ + + +class Process(object): + process = RealProcess() + + # 获取进程列表 + def get_process_list(self): + return self.process.get_process_list() + + # 获取进程信息->pid + def get_process_info_by_pid(self, get: Any) -> dict: + if not hasattr(get, 'pid'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_info_by_pid(get.pid) + + # 通过name获取进程信息 + def get_process_info_by_name(self, get: Any) -> dict: + if not hasattr(get, 'name'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_info_by_name(get.name) + + def get_process_info_by_exec(self, get: Any) -> dict: + if not hasattr(get, 'cli'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_info_by_exec(get.cli) + + def get_process_info_by_port(self, get: Any) -> dict: + if not hasattr(get, 'port'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_info_by_port(get.port) + + def get_process_info_by_ip(self, get: Any) -> dict: + if not hasattr(get, 'ip'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_info_by_ip(get.ip) + + def get_process_info_by_openfile(self, get: Any) -> dict: + if not hasattr(get, 'file_path'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_info_by_openfile(get.file_path) + + # 获取进程树 + def get_process_tree(self, get: Any) -> dict: + if not hasattr(get, 'pid'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_tree(get.pid) + + # 结束进程pid + def kill_pid(self, get: Any) -> dict: + if not hasattr(get, 'pid'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.kill_pid(get.pid) + + # 结束进程名 + def kill_name(self, get: Any) -> dict: + if not hasattr(get, 'name'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.kill_name(get.name) + + # 结束进程树 + def kill_tree(self, get: Any) -> dict: + if not hasattr(get, 'pid'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.kill_tree(get.pid) + + # 结束所有进程 pid,进程名,进程树 + def kill_proc_all(self, get: Any) -> dict: + if not hasattr(get, 'pid'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.kill_proc_all(get.pid) + + def kill_port(self, get: Any) -> dict: + if not hasattr(get, 'port'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.kill_port(get.port) + + def add_black_ip(self, get: Any) -> dict: + if not hasattr(get, 'ips'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.add_black_ip(get.ips) + + def del_black_ip(self, get: Any) -> dict: + if not hasattr(get, 'ips'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.del_black_ip(get.ips) + + def get_process_ps(self, get: Any) -> dict: + if not hasattr(get, 'name'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_process_ps(get.name) + + def get_run_list(self, get: Any) -> dict: + if not hasattr(get, 'search'): return {'status': False, 'msg': '参数错误', 'data': {}} + return self.process.get_run_list(get.search) + + +if __name__ == "__main__": + p = RealProcess() + print(p.get_computers_use()) + # print('========================') + # print(p.get_process_list()['data']) + # print('========================') + # print(p.get_process_info_by_pid(1)['data']) + # print('========================') + # print(p.get_process_info_by_name('systemd')) + # print('========================') + # res = p.get_process_tree(1) + # print(res['data'][1]) + # print('========================') + # print(p.kill_pid(1)) + # print('========================') + # print(p.kill_name('systemd')) + # print('========================') + # print(p.kill_tree(1)) + # print('========================') + # print(p.kill_proc_all(1)) + # print('========================') + # print(p.get_process_info_by_exec('nginx')) + # print('========================') + # print(p.get_process_info_by_port(8888)) + # print('========================') + # print(p.get_process_ps('nginx')) + # print('========================') + # print(p.get_process_info_by_ip('192.168.168.66')) + # print('========================') + # print(p.add_black_ip(['1.1.1.1'])) + # print('========================') + # print(p.del_black_ip(['1.1.1.1'])) + # print('========================') diff --git a/mod/base/process/process_ps.json b/mod/base/process/process_ps.json new file mode 100644 index 00000000..8863f7f3 --- /dev/null +++ b/mod/base/process/process_ps.json @@ -0,0 +1,133 @@ +{ + "bioset": "用于处理块设备上的I/O请求的进程", + "BT-MonitorAgent": "面板程序的进程", + "rngd": "一个熵守护的进程", + "master": "用于管理和协调子进程的活动的进程", + "irqbalance": "一个IRQ平衡守护的进程", + "rhsmcertd": "主要用于管理Red Hat订阅证书,并维护系统的订阅状态的进程", + "auditd": "是Linux审计系统中用户空间的一个组的进程", + "chronyd": "调整内核中运行的系统时钟和时钟服务器同步的进程", + "qmgr": "PBS管理器的进程", + "oneavd": "面板微步木马检测的进程", + "postgres": "PostgreSQL数据库的进程", + "grep": "一个命令行工具的进程", + "lsof": "一个命令行工具的进程", + "containerd-shim-runc-v2": "Docker容器的一个组件的进程", + "pickup": "用于监听Unix域套接字的进程", + "cleanup": "邮件传输代理(MTA)中的一个组件的进程", + "trivial-rewrite": "邮件传输代理(MTA)中的一个组件的进程", + "containerd": "docker依赖服务的进程", + "redis-server": "redis服务的进程", + "rcu_sched": "linux系统rcu机制服务的进程", + "jsvc": "面板tomcat服务的进程", + "oneav": "面板微步木马检测的进程", + "mysqld": "MySQL服务的进程", + "php-fpm": "PHP的子进程", + "php-cgi": "PHP-CGI的进程", + "nginx": "Nginx服务的进程", + "httpd": "Apache服务的进程", + "sshd": "SSH服务的进程", + "pure-ftpd": "FTP服务的进程", + "sftp-server": "SFTP服务的进程", + "mysqld_safe": "MySQL服务的进程", + "firewalld": "防火墙服务的进程", + "BT-Panel": "宝塔面板-主的进程", + "BT-Task": "宝塔面板-后台任务的进程", + "NetworkManager": "网络管理服务的进程", + "svlogd": "日志守护的进程", + "memcached": "Memcached缓存器的进程", + "gunicorn": "宝塔面板的进程", + "BTPanel": "宝塔面板的进程", + "baota_coll": "堡塔云控-主控端的进程", + "baota_client": "堡塔云控-被控端的进程", + "node": "Node.js程序的进程", + "supervisord": "Supervisor的进程", + "rsyslogd": "rsyslog日志服务的进程", + "crond": "计划任务服务的进程", + "cron": "计划任务服务的进程", + "rsync": "rsync文件同步的进程", + "ntpd": "网络时间同步服务的进程", + "rpc.mountd": "NFS网络文件系统挂载服务的进程", + "sendmail": "sendmail邮件服务的进程", + "postfix": "postfix邮件服务的进程", + "npm": "Node.js NPM管理器的进程", + "PM2": "Node.js PM2进程管理器的进程", + "htop": "htop进程监控软件的进程", + "btpython": "宝塔面板-独立Python环境的进程", + "btappmanagerd": "宝塔应用管理器插件的进程", + "dockerd": "Docker容器管理器的进程", + "docker-proxy": "Docker容器管理器的进程", + "docker-registry": "Docker容器管理器的进程", + "docker-distribution": "Docker容器管理器的进程", + "docker-network": "Docker容器管理器的进程", + "docker-volume": "Docker容器管理器的进程", + "docker-swarm": "Docker容器管理器的进程", + "docker-systemd": "Docker容器管理器的进程", + "docker-containerd": "Docker容器管理器的进程", + "docker-containerd-shim": "Docker容器管理器的进程", + "docker-runc": "Docker容器管理器的进程", + "docker-init": "Docker容器管理器的进程", + "docker-init-systemd": "Docker容器管理器的进程", + "docker-init-upstart": "Docker容器管理器的进程", + "docker-init-sysvinit": "Docker容器管理器的进程", + "docker-init-openrc": "Docker容器管理器的进程", + "docker-init-runit": "Docker容器管理器的进程", + "docker-init-systemd-resolved": "Docker容器管理器的进程", + "rpcbind": "NFS网络文件系统服务的进程", + "dbus-daemon": "D-Bus消息总线守护的进程", + "systemd-logind": "登录管理器的进程", + "systemd-journald": "Systemd日志管理服务的进程", + "systemd-udevd": "系统设备管理服务的进程", + "systemd-timedated": "系统时间日期服务的进程", + "systemd-timesyncd": "系统时间同步服务的进程", + "systemd-resolved": "系统DNS解析服务的进程", + "systemd-hostnamed": "系统主机名服务的进程", + "systemd-networkd": "系统网络管理服务的进程", + "systemd-resolvconf": "系统DNS解析服务的进程", + "systemd-local-resolv": "系统DNS解析服务的进程", + "systemd-sysctl": "系统系统参数服务的进程", + "systemd-modules-load": "系统模块加载服务的进程", + "systemd-modules-restore": "系统模块恢复服务的进程", + "agetty": "TTY登陆验证程序的进程", + "sendmail-mta": "MTA邮件传送代理的进程", + "(sd-pam)": "可插入认证模块的进程", + "polkitd": "授权管理服务的进程", + "mongod": "MongoDB数据库服务的进程", + "mongodb": "MongoDB数据库服务的进程", + "mongodb-mms-monitor": "MongoDB数据库服务的进程", + "mongodb-mms-backup": "MongoDB数据库服务的进程", + "mongodb-mms-restore": "MongoDB数据库服务的进程", + "mongodb-mms-agent": "MongoDB数据库服务的进程", + "mongodb-mms-analytics": "MongoDB数据库服务的进程", + "mongodb-mms-tools": "MongoDB数据库服务的进程", + "mongodb-mms-backup-agent": "MongoDB数据库服务的进程", + "mongodb-mms-backup-tools": "MongoDB数据库服务的进程", + "mongodb-mms-restore-agent": "MongoDB数据库服务的进程", + "mongodb-mms-restore-tools": "MongoDB数据库服务的进程", + "mongodb-mms-analytics-agent": "MongoDB数据库服务的进程", + "mongodb-mms-analytics-tools": "MongoDB数据库服务的进程", + "dhclient": "DHCP协议客户端的进程", + "dhcpcd": "DHCP协议客户端的进程", + "dhcpd": "DHCP服务器的进程", + "isc-dhcp-server": "DHCP服务器的进程", + "isc-dhcp-server6": "DHCP服务器的进程", + "dhcp6c": "DHCP服务器的进程", + "dhcpcd": "DHCP服务器的进程", + "dhcpd": "DHCP服务器的进程", + "avahi-daemon": "Zeroconf守护的进程", + "login": "登录的进程", + "systemd": "系统管理服务的进程", + "systemd-sysv": "系统管理服务的进程", + "systemd-journal-gateway": "系统管理服务的进程", + "systemd-journal-remote": "系统管理服务的进程", + "systemd-journal-upload": "系统管理服务的进程", + "systemd-networkd": "系统网络管理服务的进程", + "rpc.idmapd": "NFS网络文件系统相关服务的进程", + "cupsd": "打印服务的进程", + "cups-browsed": "打印服务的进程", + "sh": "shell的进程", + "php": "PHP CLI模式的进程", + "blkmapd": "NFS映射服务的进程", + "lsyncd": "文件同步服务的进程", + "sleep": "延迟的进程" +} diff --git a/mod/base/process/server.py b/mod/base/process/server.py new file mode 100644 index 00000000..2a300445 --- /dev/null +++ b/mod/base/process/server.py @@ -0,0 +1,715 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: sww +# ------------------------------------------------------------------- +import json +import os +# ------------------------------ +# 服务模型 +# ------------------------------ +import sys, re +import time +import traceback + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +os.chdir("/www/server/panel") +import public +import glob + +# 关闭系统加固执行函数后打开 +def syssafe_admin(func): + def wrapper(*args, **kwargs): + syssafe_flag = 0 + # 检查系统加固并且关闭 + if os.path.exists('/www/server/panel/plugin/syssafe/init.sh'): + res = public.ExecShell('/www/server/panel/plugin/syssafe/init.sh status') + if 'already running' in res[0]: + try: + syssafe_flag = 1 + public.ExecShell('/www/server/panel/plugin/syssafe/init.sh stop') + res = public.ExecShell('/www/server/panel/plugin/syssafe/init.sh status') + if 'already running' in res[0]: + import PluginLoader + PluginLoader.plugin_run('syssafe', 'set_open', public.to_dict_obj({'status': 0})) + print('已关闭系统加固!') + except: + pass + e = None + result = None + try: + result = func(*args, **kwargs) + except Exception as ex: + e= ex + try: + if syssafe_flag: + public.ExecShell('/www/server/panel/plugin/syssafe/init.sh stop') + res = public.ExecShell('/www/server/panel/plugin/syssafe/init.sh status') + if 'already running' not in res[0]: + import PluginLoader + PluginLoader.plugin_run('syssafe', 'set_open', public.to_dict_obj({'status': 1})) + print('已开启系统加固!') + except: + pass + if e is not None: + raise e + return result + return wrapper + + + +class RealServer: + + server_list = ['mysqld_safe', 'redis-server', 'mongod', 'postgres', 'nginx', 'memcached', 'httpd', 'pure-ftpd', 'jsvc', 'dockerd'] + system_info = None + # --------------------- 常用服务管理 start---------------------- + def server_admin(self, server_name: str, option: str) -> dict: + + """ + 服务管理 + :param server_name:'mysqld_safe', 'redis-server', 'mongod', 'postgres', 'nginx', 'memcached', 'httpd', 'pure-ftpd', 'jsvc', 'dockerd' + :param option: start,stop,restart + :return: + """ + servers = { + "mongod": self.__mongod_admin, + "redis-server": self.__redis_admin, + "memcached": self.__memcached_admin, + "dockerd": self.__docker_admin, + "jsvc": self.__tomcat_admin, + "pure-ftpd": self.__ftp_admin, + "httpd": self.__apache_admin, + "mysqld_safe": self.__mysqld_admin, + "nginx": self.__nginx_admin, + "postgres": self.__pgsql_admin, + } + from system import system + self.syst = system() + if server_name in self.server_list: + res = servers[server_name](option) + return public.returnResult(code=1, msg=res['msg'], status=res['status']) + else: + return public.returnResult(code=0, msg='操作失败!参数不存在', status=False) + + def __mongod_admin(self, option: str) -> dict: + try: + Command = {"start": "/etc/init.d/mongodb start", + "stop": "/etc/init.d/mongodb stop", } + if option != 'restart': + public.ExecShell(Command.get(option)) + return public.returnMsg(True, '操作成功!') + public.ExecShell(Command.get('stop')) + public.ExecShell(Command.get('start')) + return public.returnMsg(True, '操作成功!') + except: + return public.returnMsg(False, '操作失败!') + + def __redis_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'redis' + get.type = option + + return self.syst.serverAdmin(get) + except: + return public.returnMsg(False, '操作失败!') + + def __memcached_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'memcached' + get.type = option + return self.syst.serverAdmin(get) + except: + return public.returnMsg(False, '操作失败!') + + def __docker_admin(self, option: str) -> dict: + try: + exec_str = 'systemctl {} docker.socket'.format(option) + public.ExecShell(exec_str) + return public.returnMsg(True, "操作成功") + except: + return public.returnMsg(False, '操作失败!') + + def __tomcat_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'tomcat' + get.type = option + self.syst.serverAdmin(get) + return public.returnMsg(True, '操作成功!') + except: + return public.returnMsg(False, '操作失败!') + + def __ftp_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'pure-ftpd' + get.type = option + return self.syst.serverAdmin(get) + except: + return public.returnMsg(False, '操作失败!') + + def __apache_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'apache' + get.type = option + res = self.syst.serverAdmin(get) + import time + time.sleep(1) + return res + except: + return public.returnMsg(False, '操作失败!') + + def __mysqld_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'mysqld' + get.type = option + return self.syst.serverAdmin(get) + except: + return public.returnMsg(False, '操作失败!') + + def __nginx_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'nginx' + get.type = option + return self.syst.serverAdmin(get) + except: + return public.returnMsg(False, '操作失败!') + + def __pgsql_admin(self, option: str) -> dict: + try: + get = public.dict_obj() + get.name = 'pgsql' + get.type = option + return self.syst.serverAdmin(get) + except: + return public.returnMsg(False, '操作失败!') + + # ----------------------常用服务管理 end---------------------- + + # ----------------------常用服务状态 start---------------------- + def server_status(self, server_name: str) -> dict: + """ + 服务状态 + :param server_name: 'mysqld_safe', 'redis-server', 'mongod', 'postgres', 'nginx', 'memcached', 'httpd', 'pure-ftpd', 'jsvc', 'dockerd' + :return: + """ + try: + if server_name in self.server_list: + res = self.__get_status(server_name) + return public.returnResult(code=1, msg=res['msg'], data=res['data'], status=res['status']) + else: + return public.returnResult(code=0, msg='操作失败!参数不存在', status=False) + except Exception as e: + return public.returnResult(code=0, msg='操作失败!', status=False) + + def __is_installation(self, name: str) -> bool: + map = { + "mysqld_safe": "mysqld", + "redis-server": "redis", + "mongod": "mongodb", + "postgres": "pgsql", + "nginx": "nginx", + "memcached": "memcached", + "httpd": "httpd", + "pure-ftpd": "pure-ftpd", + "jsvc": "tomcat", + "dockerd": "docker", + "php": "php", + "tamper_proof": "tamper_proof", + "bt_security": "bt_security", + "syssafe": "syssafe", + + } + import glob + dir_path = '/etc/init.d/' + files = [os.path.basename(f) for f in glob.glob(dir_path + "*")] + if name == "dockerd": + res = public.ExecShell('docker -v')[0] + if 'version' in res: + return True + return False + if name == "postgres": + res = public.ExecShell('/www/server/pgsql/bin/psql --version')[0] + pgsql = False + if 'PostgreSQL' in res: + pgsql = True + Manager = False + if os.path.exists('/www/server/panel/plugin/pgsql_manager'): + Manager = True + return {'pgsql': pgsql, 'Manager': Manager} + if name == "php": + php_l = [i for i in files if name in i.lower()] + if len(php_l) != 0: + return True + if name == "tamper_proof": + return os.path.exists('/www/server/panel/plugin/tamper_proof') + + if name == "bt_security": + return os.path.exists('/www/server/panel/plugin/bt_security') + + if name == "syssafe": + return os.path.exists('/www/server/panel/plugin/syssafe') + + if map[name] in files: + return True + return False + + def __get_status(self, server_name: str) -> dict: + try: + if not self.__is_installation(server_name): + return {'status': True, 'msg': '', 'data': {'install': False, 'status': False}} + res = public.ExecShell('ps -ef|grep {}|grep -v grep'.format(server_name))[0] + if 'mongod' in res: + return {'status': True, 'msg': '', 'data': {'install': True, 'status': True}} + return {'status': True, 'msg': '', 'data': {'install': True, 'status': False}} + except: + return {'status': False, 'msg': '获取失败!', 'data': {'install': False, 'status': False}} + + # ----------------------常用服务状态 end---------------------- + + # ---------------------- 通用服务管理 start---------------------- + def universal_server_admin(self, server_name: str, option: str) -> dict: + """ + 通用服务管理 服务器在/etc/init.d/目录下有同名的启动文件,且启动文件中有start,stop,restart,status命令 + :param server_name: 服务名称 + :param option: start,stop,restart + :return: + """ + try: + get = public.dict_obj() + get.name = server_name + get.type = option + dir_path = '/etc/init.d/' + files = [os.path.basename(f) for f in glob.glob(dir_path + "*")] + if server_name in files: + res = public.ExecShell('/etc/init.d/{} {}'.format(server_name, option)) + if 'is running' in res[0].lower() or 'is active' in res[0].lower() or 'already running' in res[0].lower(): + return public.returnResult(code=1, msg='操作成功!', status=True) + if 'is stopped' in res[0].lower() or 'is not running' in res[0].lower(): + return public.returnResult(code=1, msg='操作成功!', status=True) + else: + return public.returnResult(code=0, msg='操作失败!未在/etc/init.d/目录下找到该服务', status=False) + except: + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 通用服务管理 end---------------------- + + # ---------------------- 通用服务状态 start---------------------- + def universal_server_status(self, server_name: str) -> dict: + """ + 通用服务状态 服务器在/etc/init.d/目录下有同名的启动文件,且启动文件中有status命令,status中有输出is running或is active + :param server_name: 服务名称 + :return: + """ + try: + get = public.dict_obj() + get.name = server_name + get.type = 'status' + dir_path = '/etc/init.d/' + files = [os.path.basename(f) for f in glob.glob(dir_path + "*")] + if server_name in files: + res = public.ExecShell('/etc/init.d/{} status'.format(server_name)) + if 'is running' in res[0].lower() or 'is active' in res[0].lower() or 'already running' in res[0].lower(): + return public.returnResult(code=1, msg='运行中', data=True) + return public.returnResult(code=1, msg='未运行', data=False) + return public.returnResult(code=0, msg='服务不存在!', status=False) + except: + return public.returnResult(code=0, msg='获取失败!', data=False) + + # ---------------------- 通用服务状态 end---------------------- + + # ---------------------- 添加开机自启 启动脚本 start---------------------- + + # 添加开机自启 + @syssafe_admin + def add_boot(self, server_name: str, pid_file: str, start_exec: str, stop_exec: str, default_start: str = '2 3 4 5') -> dict: + """ + 添加开机自启 + :param server_name: 服务名称 + :param pid_file: 启动pid记录文件 + :param start_exec: 启动命令 + :param stop_exec: 停止命令 + :param default_start: 默认启动级别 + :return: + """ + + content = """ +#! /bin/sh +# chkconfig: 2345 55 25 + +### BEGIN INIT INFO +# Provides: {name} +# Required-Start: $all +# Required-Stop: $all +# Default-Start: {default_start} +# Default-Stop: 0 1 6 +# Short-Description: {name} +# Description: {name} +### END INIT INFO + +# Author: licess +# website: http://www.bt.cn + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +case "$1" in + start) + echo -n "Starting {name}... " + if [ -f {pid_file} ];then + mPID=$(cat {pid_file}) + isStart=`ps ax | awk '{{ print $1 }}' | grep -e "^${{mPID}}$"` + if [ "$isStart" != "" ];then + echo "{name} (pid $mPID) already running." + exit 1 + fi + fi + nohup {start_exec} & + if [ $? != 0 ]; then + echo " failed" + exit 1 + else + pid=`ps -ef|grep "{start_exec}" |grep -v grep|awk '{{print $2}}'` + echo $! > {pid_file} + echo " done" + fi + ;; + stop) + echo -n "Stopping {name}... " + if [ -f {pid_file} ];then + mPID=$(cat {pid_file}) + isStart = `ps ax | awk '{{ print $1 }}' | grep -e "^${{mPID}}$"` + if [ "$isStart" = "" ];then + echo "{name} is stopped" + exit 1 + fi + else + echo "{name} is stopped" + exit 1 + fi + nohup {stop_exec} & + if [ $? != 0 ]; then + echo " failed. Use force-quit" + exit 1 + else + echo " done" + fi + ;; + status) + if [ -f {pid_file} ];then + mPID=`cat {pid_file}` + isStart=`ps ax | awk '{{ print $1 }}' | grep -e "^${{mPID}}$"` + if [ "$isStart" != '' ];then + echo "{name} (pid `pidof {name}`) is running." + exit 1 + else + echo "{name} is stopped" + exit 0 + fi + else + echo "{name} is stopped" + exit 0 + fi + ;; + restart) + $0 stop + sleep 1 + $0 start + ;; +esac +""".format(name=server_name, pid_file=pid_file, start_exec=start_exec, stop_exec=stop_exec, default_start=default_start) + + + if os.path.exists(os.path.join('/etc/init.d/', server_name)): + return public.returnResult(code=1, msg='操作失败!服务已存在', status=False) + try: + public.writeFile(os.path.join('/etc/init.d/', server_name), content) + os.chmod(os.path.join('/etc/init.d/', server_name), 0o777) + if os.path.exists('/usr/sbin/update-rc.d'): + public.ExecShell('update-rc.d -f {} defaults'.format(server_name)) + else: + public.ExecShell('systemctl enable {}'.format(server_name)) + return public.returnResult(code=1, msg='操作成功!', status=True) + except: + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 添加开机自启 启动脚本 end---------------------- + + # ---------------------- 删除开机自启 启动脚本 start---------------------- + def del_boot(self, server_name: str) -> dict: + """ + 删除启动脚本 + :param server_name: 服务名称 + :return: + """ + try: + if os.path.exists(os.path.join('/etc/init.d/', server_name)): + if os.path.exists('/usr/sbin/update-rc.d'): + public.ExecShell('update-rc.d -f {} remove'.format(server_name)) + else: + public.ExecShell('systemctl disable {}'.format(server_name)) + os.remove(os.path.join('/etc/init.d/', server_name)) + return public.returnResult(code=1, msg='操作成功!', status=True) + return public.returnResult(code=0, msg='操作失败!服务不存在', status=False) + except: + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 删除开机自启 启动脚本 end---------------------- + + # ---------------------- 创建服务守护进程 start---------------------- + + @syssafe_admin + def create_daemon(self, server_name: str, pid_file: str, start_exec: str, workingdirectory: str, user: str = 'root', is_power_on: int = 1, logs_file: str = '', environments: str = '', + is_fork=0, restart_type='always') -> dict: + """ + 创建服务守护进程 + :param server_name: 服务名称 + :param pid_file: 启动pid记录文件 + :param start_exec: 启动命令 + :param stop_exec: 停止命令 + :return: + """ + + # 检查系统加固插件是否存在 + try: + content = ''' +[Unit] +Description={server_name} +After=network.target + +[Service] +{environments} +ExecStart={start_exec} +ExecStop=/usr/bin/pkill -9 "{start_exec}" +WorkingDirectory={workingdirectory} +Restart={restart_type} +SyslogIdentifier={server_name} +User={user} +Type=simple +PrivateTmp=false +PIDFile={pid_file} + +[Install] +WantedBy=multi-user.target +'''.format( + start_exec=start_exec, + workingdirectory=workingdirectory, + user=user, pid_file=pid_file, + server_name=server_name, + environments=environments, + restart_type=restart_type, + ) + + if is_fork: + content = content.replace('Type=simple', 'Type=forking') + if not os.path.exists('/usr/lib/systemd/system/'): + public.ExecShell('mkdir -p /usr/lib/systemd/system/') + public.writeFile('/usr/lib/systemd/system/{}.service'.format(server_name), content) + if is_power_on: + public.ExecShell('systemctl enable {}'.format(server_name)) + public.ExecShell('systemctl daemon-reload') + + if not logs_file: + logs_file = '/www/wwwlogs/project_{}.log'.format(server_name) + + rsyslog_conf = public.readFile('/etc/rsyslog.conf') + add_conf = "if $programname == '{}' then {}\n".format(server_name, logs_file) + if rsyslog_conf: + idx = rsyslog_conf.find("if $programname == '{}' then".format(server_name)) + if idx == -1: + rsyslog_conf += "\n" + add_conf + else: + line_idx = rsyslog_conf.find('\n', idx) + rsyslog_conf = rsyslog_conf[:idx] + add_conf + rsyslog_conf[line_idx:] + public.writeFile('/etc/rsyslog.conf', rsyslog_conf) + + public.ExecShell('systemctl restart rsyslog') + if not os.path.exists(logs_file): + public.ExecShell('touch {}'.format(logs_file)) + public.ExecShell('chown -R {user}:{user} {logs_file}'.format(user=user, logs_file=logs_file)) + self.daemon_admin(server_name, 'restart') + return public.returnResult(code=1, msg='操作成功!', status=True) + except: + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 创建服务守护进程 end---------------------- + + # ---------------------- 删除服务守护进程 start---------------------- + @syssafe_admin + def del_daemon(self, server_name: str) -> dict: + """ + 删除服务守护进程 + :param server_name: 服务名称 + :return: + """ + try: + public.ExecShell('systemctl stop {}'.format(server_name)) + if os.path.exists('/usr/lib/systemd/system/{}.service'.format(server_name)): + public.ExecShell('systemctl disable {}'.format(server_name)) + os.remove('/usr/lib/systemd/system/{}.service'.format(server_name)) + public.ExecShell('systemctl daemon-reload') + public.ExecShell(r'sed -i "/if \$programname == {}/d" /etc/rsyslog.conf'.format(server_name)) + public.ExecShell('systemctl restart rsyslog') + return public.returnResult(code=1, msg='操作成功!', status=True) + return public.returnResult(code=0, msg='操作失败!', status=False) + except: + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 删除服务守护进程 end---------------------- + + # ---------------------- 服务守护进程状态 start---------------------- + def daemon_status(self, server_name: str) -> dict: + """ + 服务守护进程状态 + :param server_name: 服务名称 + :return: + """ + try: + if not os.path.exists('/usr/lib/systemd/system/{}.service'.format(server_name)): + return public.returnResult(code=0, msg='服务不存在!', status=False) + if not self.system_info: + self.system_info = public.ExecShell("systemctl |grep service|grep -E 'active|deactivating'|awk '{print $1}'")[0] + if server_name+'.service' in self.system_info: + return public.returnResult(code=1, msg='运行中', status=True) + return public.returnResult(code=1, msg='未运行', status=False) + except: + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 服务守护进程状态 end---------------------- + + def daemon_admin(self, server_name: str,action:str) -> dict: + """ + + :param server_name: 项目名称 + :param action: 操作 + """ + public.ExecShell('systemctl {} {}'.format(action,server_name)) + return public.returnResult(code=1, msg='操作指令已执行', status=True) + # if action == 'start' or action == 'restart': + # num = 0 + # for i in range(5): + # time.sleep(0.01) + # if self.daemon_status(server_name)['status']: + # num += 1 + # if num > 3: + # return public.returnResult(code=1, msg='启动成功!', status=True) + # return public.returnResult(code=0, msg='启动失败!', status=False) + # return public.returnResult(code=1, msg='关闭成功!' + res[0] + res[1], status=True) + + def get_daemon_pid(self, server_name: str) -> dict: + """ + 获取守护进程pid + :param server_name: 项目名称 + """ + res = public.ExecShell("systemctl show --property=MainPID {}".format(server_name))[0] # type: str + if not res.startswith('MainPID='): + return public.returnResult(code=0, msg='获取失败!', status=False) + + try: + pid = int(res.split("=", 1)[1]) + return public.returnResult(code=1, msg='获取成功!', data=pid, status=True) + except: + return public.returnResult(code=0, msg='获取失败', status=False) + + + # ---------------------- 延时定时启动 start---------------------- + def add_task(self, shell: str, time: int) -> dict: + """ + 服务定时启动 + :param server_name: 服务名称 + :param start_exec: 启动命令 + :param minute: 定时启动时间 + :return: + """ + data = { + 'type': 3, + 'time': time, + 'name': shell, + 'title': '', + 'fun': '', + 'args': '' + } + + res = public.set_tasks_run(data) + if res['status']: + return public.returnResult(code=1, msg='操作成功!', status=True) + return public.returnResult(code=0, msg='操作失败!', status=False) + + # ---------------------- 服务定时启动 end---------------------- + + +class Server: + server = RealServer() + + def server_admin(self, get): + try: + if hasattr(self.server, get.name): + return getattr(self.server, get.name)(get.type) + return public.returnMsg(False, '操作失败!参数不存在') + except: + return public.returnMsg(False, '操作失败!') + + def server_status(self, get): + try: + if hasattr(self.server, get.name): + return getattr(self.server, get.name)() + return public.returnMsg(False, '操作失败!参数不存在') + except: + return public.returnMsg(False, '操作失败!') + + def universal_server_admin(self, get): + try: + return self.server.universal_server_admin(get.name, get.type) + except: + return public.returnMsg(False, '操作失败!') + + def universal_server_status(self, get): + try: + return self.server.universal_server_status(get.name) + except: + return public.returnMsg(False, '操作失败!') + + def add_boot(self, get): + try: + return self.server.add_boot(get.name, get.pid_file, get.start_exec, get.stop_exec) + except: + return public.returnMsg(False, '操作失败!') + + def del_boot(self, get): + try: + return self.server.del_boot(get.name) + except: + return public.returnMsg(False, '操作失败!') + + def create_daemon(self, get): + try: + return self.server.create_daemon(get.name, get.pid_file, get.start_exec, get.user) + except: + return public.returnMsg(False, '操作失败!') + + def del_daemon(self, get): + try: + return self.server.del_daemon(get.name) + except: + return public.returnMsg(False, '操作失败!') + + def daemon_status(self, get): + try: + return self.server.daemon_status(get.name) + except: + return public.returnMsg(False, '操作失败!') + + def add_task(self, get): + try: + return self.server.add_task(get.shell, get.time) + except: + return public.returnMsg(False, '操作失败!') diff --git a/mod/base/process/user.py b/mod/base/process/user.py new file mode 100644 index 00000000..e205a4a1 --- /dev/null +++ b/mod/base/process/user.py @@ -0,0 +1,630 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: sww +# ------------------------------------------------------------------- +import json +import os +# ------------------------------ +# 用户模型 +# ------------------------------ +import sys +import traceback +import psutil + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +os.chdir("/www/server/panel") +import public +from typing import List, Dict, Any, Union + + +class RealUser: + def __init__(self): + self.groupList = self.get_group_list()['data'] + print(self.groupList) + + # --------------------获取用户列表 Start-------------------- + def get_user_list(self, search: str = '') -> Dict[str, Union[bool, str, List[Dict[str, Any]]]]: + """ + 获取用户列表 + :param search: 搜索关键词 可搜索 用户名、备注、shell、home、用户组 + :return: list + """ + try: + tmpList = public.readFile('/etc/passwd').split("\n") + userList = [] + for ul in tmpList: + tmp = ul.split(':') + if len(tmp) < 6: continue + userInfo = {} + userInfo['username'] = tmp[0] + userInfo['uid'] = tmp[2] + userInfo['gid'] = tmp[3] + userInfo['group'] = self._get_group_name(tmp[3]) + userInfo['ps'] = self._get_user_ps(tmp[0], tmp[4]) + userInfo['home'] = tmp[5] + userInfo['login_shell'] = tmp[6] + userList.append(userInfo) + if search != '': + userList = self._search_user(userList, search) + return public.returnResult(code=1, data=userList, msg='获取用户列表成功!', status=True) + except Exception as e: + print(traceback.format_exc()) + return public.returnResult(code=0, data=[], msg='获取用户列表失败!错误:' + str(e), status=False) + + def _get_user_ps(self, name: str, ps: str) -> str: + """ + 获取用户备注 + :param name: 用户名 + :param ps: 备注 + :return: str + """ + userPs = {'www': '宝塔面板', 'root': '超级管理员', 'mysql': '用于运行MySQL的用户', + 'mongo': '用于运行MongoDB的用户', + 'git': 'git用户', 'mail': 'mail', 'nginx': '第三方nginx用户', 'postfix': 'postfix邮局用户', + 'lp': '打印服务帐号', + 'daemon': '控制后台进程的系统帐号', 'nobody': '匿名帐户', 'bin': '管理大部分命令的帐号', + 'adm': '管理某些管理文件的帐号', 'smtp': 'smtp邮件'} + if name in userPs: return userPs[name] + if not ps: return name + return ps + + def _get_group_name(self, gid: str) -> str: + """ + 获取用户组名称 + :param gid: 用户组ID + :return: str + """ + for g in self.groupList: + if g['gid'] == gid: return g['group'] + return '' + + def _search_user(self, data: List[Dict[str, Any]], search: str) -> List[Dict[str, Union[str, Any]]]: + """ + 搜索用户 + :param data: 用户列表 + :param search: 搜索关键词 + :return: list + """ + try: + ldata = [] + for i in data: + if search in i['username'] or search in i['ps'] or search in i['login_shell'] or search in i['home'] or search in i['group']: + ldata.append(i) + return ldata + except: + return data + + def get_group_list(self): + """ + 获取用户组列表 + :return:list + + """ + tmpList = public.readFile('/etc/group').split("\n") + groupList = [] + for gl in tmpList: + tmp = gl.split(':') + if len(tmp) < 3: continue + groupInfo = {} + groupInfo['group'] = tmp[0] + groupInfo['gid'] = tmp[2] + groupList.append(groupInfo) + return public.returnResult(code=1, data=groupList, msg='获取用户组列表成功!', status=True) + + # --------------------获取用户列表 End---------------------- + + # --------------------删除用户 Start------------------------ + def remove_user(self, user: str) -> Dict[str, Any]: + users = ['www', 'root', 'mysql', 'shutdown', 'postfix', 'smmsp', 'sshd', 'systemd-network', 'systemd-bus-proxy', + 'avahi-autoipd', 'mail', 'sync', 'lp', 'adm', 'bin', 'mailnull', 'ntp', 'daemon', 'sys'] + if user in users: return public.returnResult(code=0, msg='系统用户或关键用户不能删除!', status=False) + r = public.ExecShell("userdel " + user) + if r[1].find('process') != -1: + try: + pid = r[1].split()[-1] + p = psutil.Process(int(pid)) + pname = p.name() + p.kill() + public.ExecShell("pkill -9 " + pname) + r = public.ExecShell("userdel " + user) + public.ExecShell("rm -rf /home/" + user) + except: + pass + if r[1].find('userdel:') != -1: return public.returnMsg(False, r[1]) + return public.returnResult(code=1, msg='删除成功!', status=True) + + # --------------------删除用户 End------------------------ + + # --------------------添加用户 Start------------------------ + def add_user(self, user: str, pwd: str, group: str) -> Dict[str, Any]: + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if not pwd: return public.returnResult(code=0, msg='密码不能为空!', status=False) + if not self._check_user(user): return public.returnResult(code=1, msg='用户已存在!', status=True) + if not self._check_group(group): self.add_group(group) + r = public.ExecShell("useradd -g " + group + " -m " + user + ' -p' + pwd) + if r[1].find('useradd:') != -1 and r[1].find('already exists') == 1: return public.returnResult(code=0, msg=r[1], status=False) + # public.ExecShell("echo \"" + user + ":" + pwd + "\" | chpasswd") + return public.returnResult(code=1, msg='添加成功!', status=True) + except: + print(traceback.format_exc()) + return public.returnResult(code=0, msg='添加失败!', status=False) + + def _check_user(self, user: str) -> bool: + """ + 检查用户是否存在 + :param user: 用户名 + :return: bool + """ + tmpList = public.readFile('/etc/passwd').split("\n") + for ul in tmpList: + tmp = ul.split(':') + if len(tmp) < 6: continue + if tmp[0] == user: return False + return True + + def _check_group(self, group: str) -> bool: + """ + 检查用户组是否存在 + :param group: 用户组 + :return: bool + """ + tmpList = public.readFile('/etc/group').split("\n") + for gl in tmpList: + tmp = gl.split(':') + if len(tmp) < 3: continue + if tmp[0] == group: return True + return False + + # --------------------添加用户 End------------------------ + + # --------------------修改用户密码 Start------------------------ + def edit_user_pwd(self, user: str, pwd: str) -> Dict[str, Any]: + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if not pwd: return public.returnResult(code=0, msg='密码不能为空!', status=False) + if self._check_user(user): return public.returnResult(code=0, msg='用户不存在!', status=False) + public.ExecShell("echo \"" + user + ":" + pwd + "\" | chpasswd") + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户密码 End------------------------ + + # --------------------修改用户组 Start------------------------ + def edit_user_group(self, user: str, group: str) -> Dict[str, Any]: + try: + if not user: return public.returnMsg(False, '用户名不能为空!') + if not group: return public.returnMsg(False, '用户组不能为空!') + if self._check_user(user): return public.returnMsg(False, '用户不存在!') + if not self._check_group(group): self.add_group(group) + r = public.ExecShell("usermod -g " + group + " " + user) + if r[1].find('usermod:') != -1: return public.returnMsg(False, r[1]) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户组 End------------------------ + + # --------------------修改用户备注 Start------------------------ + def edit_user_ps(self, user: str, ps: str) -> Dict[str, Any]: + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if self._check_user(user): return public.returnResult(code=0, msg='用户不存在!', status=False) + r = public.ExecShell("usermod -c " + ps + " " + user) + if r[1].find('usermod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户备注 End------------------------ + + # --------------------修改用户备注 Start------------------------ + def edit_user_status(self, user: str, status: int): + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if self._check_user(user): return public.returnResult(code=0, msg='用户不存在!', status=False) + if int(status) == 1: + r = public.ExecShell("usermod -L " + user) + else: + r = public.ExecShell("usermod -U " + user) + if r[1].find('usermod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户备注 End------------------------ + + # --------------------修改用户登录Shell Start------------------------ + def edit_user_login_shell(self, user: str, login_shell: str) -> Dict[str, Any]: + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if self._check_user(user): return public.returnResult(code=0, msg='用户不存在!', status=False) + r = public.ExecShell("usermod -s " + login_shell + " " + user) + if r[1].find('usermod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户登录Shell End------------------------ + + # --------------------修改用户家目录 Start------------------------ + def edit_user_home(self, user: str, home: str) -> Dict[str, Any]: + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if self._check_user(user): return public.returnResult(code=0, msg='用户不存在!', status=False) + r = public.ExecShell("usermod -d " + home + " " + user) + if r[1].find('usermod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户家目录 End------------------------ + + # --------------------获取用户信息 Start------------------------ + def get_user_info(self, user: str) -> Dict[str, Any]: + try: + user = user.strip() + tmpList = public.readFile('/etc/passwd').split("\n") + userInfo = {} + for ul in tmpList: + tmp = ul.split(':') + if len(tmp) < 6: continue + if tmp[0] == user: + userInfo['username'] = tmp[0] + userInfo['uid'] = tmp[2] + userInfo['gid'] = tmp[3] + userInfo['group'] = self._get_group_name(tmp[3]) + userInfo['ps'] = self._get_user_ps(tmp[0], tmp[4]) + userInfo['home'] = tmp[5] + userInfo['login_shell'] = tmp[6] + break + return public.returnResult(code=1, data=userInfo, msg='获取用户信息成功!', status=True) + except: + print(traceback.format_exc()) + return public.returnResult(code=0, msg='获取用户信息失败!', status=False) + + # --------------------添加用户组 Start------------------------ + def add_group(self, group: str) -> Dict[str, Any]: + """ + 添加用户组 + :param group: 用户组 + :return: dict + """ + try: + if not group: return public.returnResult(code=0, msg='用户组不能为空!', status=False) + if self._check_group(group): return public.returnResult(code=0, msg='用户组已存在!', status=False) + r = public.ExecShell("groupadd " + group) + if r[1].find('groupadd:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='添加成功!', status=True) + except: + return public.returnResult(code=0, msg='添加失败!', status=False) + + # --------------------添加用户组 End------------------------ + + # --------------------删除用户组 Start------------------------ + def remove_group(self, group: str) -> Dict[str, Any]: + """ + 删除用户组 + :param group: 用户组 + :return: dict + """ + try: + if not group: return public.returnResult(code=0, msg='用户组不能为空!', status=False) + if not self._check_group(group): return public.returnResult(code=0, msg='用户组不存在!', status=False) + r = public.ExecShell("groupdel " + group) + if r[1].find('groupdel:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='删除成功!', status=True) + except: + return public.returnResult(code=0, msg='删除失败!', status=False) + + # --------------------删除用户组 End------------------------ + + # --------------------修改用户组名称 Start------------------------ + def edit_group_name(self, group: str, new_group: str) -> Dict[str, Any]: + """ + 修改用户组名称 + :param group: 用户组 + :param new_group: 新用户组 + :return: dict + """ + try: + if not group: return public.returnResult(code=0, msg='用户组不能为空!', status=False) + if not new_group: return public.returnResult(code=0, msg='新用户组不能为空!', status=False) + if not self._check_group(group): return public.returnResult(code=0, msg='用户组不存在!', status=False) + if self._check_group(new_group): return public.returnResult(code=0, msg='新用户组已存在!', status=False) + r = public.ExecShell("groupmod -n " + new_group + " " + group) + if r[1].find('groupmod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------获取用户组列表 End------------------------ + + # --------------------获取用户组信息 Start------------------------ + def get_group_info(self, group) -> Dict[str, Any]: + """ + 获取用户组信息 + :param group: 用户组 + :return: dict + """ + try: + group = group.strip() + tmpList = public.readFile('/etc/group').split("\n") + groupInfo = {} + for gl in tmpList: + tmp = gl.split(':') + if len(tmp) < 3: continue + if tmp[0] == group: + groupInfo['group'] = tmp[0] + groupInfo['gid'] = tmp[2] + break + return public.returnResult(code=1, data=groupInfo, msg='获取用户组信息成功!', status=True) + except: + return public.returnResult(code=0, msg='获取用户组信息失败!', status=False) + + # --------------------获取用户组信息 End------------------------ + + # --------------------获取用户组信息 Start------------------------ + def get_group_user(self, group: str) -> Dict[str, Any]: + """ + 获取用户组用户 + :param group: 用户组 + :return: dict + """ + try: + group = group.strip() + tmpList = self.get_user_list()['data'] + userList = [] + for ul in tmpList: + if ul['group'] == group: + userList.append(ul['username']) + return public.returnResult(code=1, data=userList, msg='获取用户组用户成功!', status=True) + except: + return public.returnResult(code=0, msg='获取用户组用户失败!', status=False) + + # --------------------获取用户组信息 End------------------------ + + # --------------------获取用户组信息 Start------------------------ + def get_user_group(self, user: str) -> Dict[str, Any]: + """ + 获取用户组用户 + :param user: 用户 + :return: dict + """ + try: + user = user.strip() + tmpList = self.get_user_list()['data'] + groupList = [] + for gl in tmpList: + if gl['username'] == user: + groupList.append(gl['group']) + return public.returnResult(code=1, data=groupList, msg='获取用户组用户成功!', status=True) + except: + return public.returnResult(code=0, msg='获取用户组用户失败!', status=False) + + # --------------------获取用户组信息 End------------------------ + + # --------------------修改用户权限 Start------------------------ + def edit_user_permission(self, user: str, permission: str) -> Dict[str, Any]: + """ + 修改用户权限 + :param user: + :param permission: + :return: + """ + try: + if not user: return public.returnResult(code=0, msg='用户名不能为空!', status=False) + if self._check_user(user): return public.returnResult(code=0, msg='用户不存在!', status=False) + r = public.ExecShell("chmod -R " + permission + " /home/" + user) + if r[1].find('chmod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + # --------------------修改用户权限 End------------------------ + + # --------------------修改用户组权限 Start------------------------ + def edit_group_permission(self, group: str, permission: str) -> Dict[str, Any]: + """ + 修改用户组权限 + :param group: + :param permission: + :return: + """ + try: + if not group: return public.returnResult(code=0, msg='用户组不能为空!', status=False) + if not self._check_group(group): return public.returnResult(code=0, msg='用户组不存在!', status=False) + r = public.ExecShell("chmod -R " + permission + " /home/" + group) + if r[1].find('chmod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + def edit_user_name(self, user: str, new_user: str) -> Dict[str, Any]: + try: + user = user.strip() + new_user = new_user.strip() + r = public.ExecShell("usermod -l " + new_user + " " + user) + if r[1].find('usermod:') != -1: return public.returnResult(code=0, msg=r[1], status=False) + return public.returnResult(code=1, msg='修改成功!', status=True) + except: + return public.returnResult(code=0, msg='修改失败!', status=False) + + +class User(object): + def __init__(self): + self.real_user = RealUser() + + # 获取用户列表 + def get_user_list(self, get): + search = '' + if hasattr(get, 'search'): + search = get.search + return self.real_user.get_user_list(search) + + # 删除用户 + def remove_user(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户不存在!') + user = get.user.strip() + return self.real_user.remove_user(user) + + # 添加用户 + def add_user(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'pwd'): + return public.returnMsg(False, '密码不能为空!') + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + user = get.user.strip() + pwd = get.pwd.strip() + group = get.group.strip() + return self.real_user.add_user(user, pwd, group) + + # 修改用户密码 + def edit_user_pwd(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'pwd'): + return public.returnMsg(False, '密码不能为空!') + user = get.user.strip() + pwd = get.pwd.strip() + return self.real_user.edit_user(user, pwd) + + # 修改用户的用户组 + def edit_user_group(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + user = get.user.strip() + group = get.group.strip() + return self.real_user.edit_group(user, group) + + # 修改用户备注 + def edit_user_ps(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + user = get.user.strip() + return self.real_user.edit_user_ps(user) + + # 添加用户组 + def add_group(self, get): + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + group = get.group.strip() + return self.real_user.add_group(group) + + # 删除用户组 + def remove_group(self, get): + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + group = get.group.strip() + return self.real_user.remove_group(group) + + # 修改用户组名称 + def edit_group_name(self, get): + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + if not hasattr(get, 'new_group'): + return public.returnMsg(False, '新用户组不能为空!') + group = get.group.strip() + new_group = get.new_group.strip() + return self.real_user.edit_group_name(group, new_group) + + # 获取用户组列表 + def get_group_list(self, get): + return self.real_user.get_group_list() + + # 获取用户组信息 + def get_group_info(self, get): + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + group = get.group.strip() + return self.real_user.get_group_info(group) + + # 获取用户组用户 + def get_group_user(self, get): + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + group = get.group.strip() + return self.real_user.get_group_user(group) + + # 获取用户组用户 + def get_user_group(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户不能为空!') + user = get.user.strip() + return self.real_user.get_user_group(user) + + # 修改用户备注 + def edit_ps(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'ps'): + return public.returnMsg(False, '备注不能为空!') + user = get.user.strip() + ps = get.ps.strip() + return self.real_user.edit_ps(user, ps) + + # 修改用户登录Shell + def edit_user_login_shell(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'login_shell'): + return public.returnMsg(False, '登录Shell不能为空!') + user = get.user.strip() + login_shell = get.login_shell.strip() + return self.real_user.edit_login_shell(user, login_shell) + + # 修改用户家目录 + def edit_user_home(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'home'): + return public.returnMsg(False, '家目录不能为空!') + user = get.user.strip() + home = get.home.strip() + return self.real_user.edit_home(user, home) + + # 修改用户权限 + def edit_user_permission(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'permission'): + return public.returnMsg(False, '权限不能为空!') + user = get.user.strip() + permission = get.permission.strip() + return self.real_user.edit_user_permission(user, permission) + + # 修改用户组权限 + def edit_group_permission(self, get): + if not hasattr(get, 'group'): + return public.returnMsg(False, '用户组不能为空!') + if not hasattr(get, 'permission'): + return public.returnMsg(False, '权限不能为空!') + group = get.group.strip() + permission = get.permission.strip() + return self.real_user.edit_group_permission(group, permission) + + def edit_user_name(self, get): + if not hasattr(get, 'user'): + return public.returnMsg(False, '用户名不能为空!') + if not hasattr(get, 'new_user'): + return public.returnMsg(False, '新用户名不能为空!') + user = get.user.strip() + new_user = get.new_user.strip() + return self.real_user.edit_user_name(user, new_user) + + +if __name__ == "__main__": + user = User() + print(user.get_user_list(public.to_dict_obj({}))) diff --git a/mod/base/process/user_readme.text b/mod/base/process/user_readme.text new file mode 100644 index 00000000..bf028237 --- /dev/null +++ b/mod/base/process/user_readme.text @@ -0,0 +1,86 @@ +User类: + 返回值默认类型: + Dict[str, Any] + { + 'status': bool, + 'msg': str, + 'data': Any + } + def get_user_list(self, search: str = '') -> Dict[str, Union[bool, str, List[Dict[str, Any]]]]: + # 获取用户列表 + # 传参:search(可选参数,搜索关键词) + + def _get_user_ps(self, name: str, ps: str) -> str: + # 获取用户备注 + # 传参:name(用户名),ps(备注) + + def _get_group_name(self, gid: str) -> str: + # 获取用户组名称 + # 传参:gid(用户组ID) + + def _search_user(self, data: List[Dict[str, Any]], search: str) -> List[Dict[str, Union[str, Any]]]: + # 搜索用户 + # 传参:data(用户列表),search(搜索关键词) + + def _get_group_list(self) -> List[Dict[str, Union[str, str]]]: + # 获取用户组列表 + + def remove_user(self, user: str) -> Dict[str, Any]: + # 删除用户 + # 传参:user(用户名) + + def add_user(self, user: str, pwd: str, group: str) -> Dict[str, Any]: + # 添加用户 + # 传参:user(用户名),pwd(密码),group(用户组) + + def edit_user(self, user: str, pwd: str) -> Dict[str, Any]: + # 修改用户密码 + # 传参:user(用户名),pwd(新密码) + + def edit_group(self, user: str, group: str) -> Dict[str, Any]: + # 修改用户组 + # 传参:user(用户名),group(新用户组) + + def edit_ps(self, user: str, ps: str) -> Dict[str, Any]: + # 修改用户备注 + # 传参:user(用户名),ps(新备注) + + def edit_login_shell(self, user: str, login_shell: str) -> Dict[str, Any]: + # 修改用户登录Shell + # 传参:user(用户名),login_shell(新Shell) + + def edit_home(self, user: str, home: str) -> Dict[str, Any]: + # 修改用户家目录 + # 传参:user(用户名),home(新家目录) + + def get_user_info(self, user: str) -> Dict[str, Any]: + # 获取用户信息 + # 传参:user(用户名) + + def add_group(self, group: str) -> Dict[str, Any]: + # 添加用户组 + # 传参:group(用户组) + + def remove_group(self, group: str) -> Dict[str, Any]: + # 删除用户组 + # 传参:group(用户组) + + def edit_group_name(self, group: str, new_group: str) -> Dict[str, Any]: + # 修改用户组名称 + # 传参:group(用户组),new_group(新用户组) + + def get_group_list(self) -> Dict[str, Union[bool, str, List[Dict[str, Any]]]]: + # 获取用户组列表 + + def get_group_info(self, group) -> Dict[str, Any]: + # 获取用户组信息 + # 传参:group(用户组) + + def get_group_user(self, group: str) -> Dict[str, Any]: + # 获取用户组用户 + # 传参:group(用户组) + + def get_user_group(self, user: str) -> Dict[str, Any]: + # 获取用户组用户 + # 传参:user(用户) + diff --git a/mod/base/push_mod/__init__.py b/mod/base/push_mod/__init__.py new file mode 100644 index 00000000..1aa6df3f --- /dev/null +++ b/mod/base/push_mod/__init__.py @@ -0,0 +1,495 @@ +import json +import os +from typing import Dict, Union + +from .mods import TaskConfig, TaskTemplateConfig, TaskRecordConfig, SenderConfig, load_task_template_by_config, \ + load_task_template_by_file, UPDATE_MOD_PUSH_FILE, UPDATE_VERSION_FILE, PUSH_DATA_PATH +from .base_task import BaseTask +from .send_tool import WxAccountMsg, WxAccountLoginMsg, WxAccountMsgBase +from .system import PushSystem, get_push_public_data, push_by_task_keyword, push_by_task_id +from .manager import PushManager +from .util import read_file, write_file + + +__all__ = [ + "TaskConfig", + "TaskTemplateConfig", + "TaskRecordConfig", + "SenderConfig", + "load_task_template_by_config", + "load_task_template_by_file", + "BaseTask", + "WxAccountMsg", + "WxAccountLoginMsg", + "WxAccountMsgBase", + "PushSystem", + "get_push_public_data", + "PushManager", + "push_by_task_keyword", + "push_by_task_id", + "UPDATE_MOD_PUSH_FILE", + "update_mod_push_system", + "UPDATE_VERSION_FILE", + "PUSH_DATA_PATH", + "get_default_module_dict", +] + + +def update_mod_push_system(): + if os.path.exists(UPDATE_MOD_PUSH_FILE): + return + + # 只将已有的告警任务("site_push", "system_push", "database_push") 移动 + + try: + push_data = json.loads(read_file("/www/server/panel/class/push/push.json")) + except: + return + + if not isinstance(push_data, dict): + return + pmgr = PushManager() + default_module_dict = get_default_module_dict() + for key, value in push_data.items(): + if key == "site_push": + _update_site_push(value, pmgr, default_module_dict) + elif key == "system_push": + _update_system_push(value, pmgr, default_module_dict) + elif key == "database_push": + _update_database_push(value, pmgr, default_module_dict) + elif key == "rsync_push": + _update_rsync_push(value, pmgr, default_module_dict) + elif key == "load_balance_push": + _update_load_push(value, pmgr, default_module_dict) + elif key == "task_manager_push": + _update_task_manager_push(value, pmgr, default_module_dict) + + write_file(UPDATE_MOD_PUSH_FILE, "") + + +def get_default_module_dict(): + res = {} + wx_account_list = [] + for data in SenderConfig().config: + if not data["used"]: + continue + if data.get("original", False): + res[data["sender_type"]] = data["id"] + + if data["sender_type"] == "webhook": + res[data["data"].get("title")] = data["id"] + + if data["sender_type"] == "wx_account": + wx_account_list.append(data) + + wx_account_list.sort(key=lambda x: x.get("data", {}).get("create_time", "")) + if wx_account_list: + res["wx_account"] = wx_account_list[0]["id"] + + return res + + +def _update_site_push(old_data: Dict[str, Dict[str, Union[str, int, float, list]]], + pmgr: PushManager, + df_mdl: Dict[str, str]): + + for k, v in old_data.items(): + sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl] + if v["type"] == "ssl": + push_data = { + "template_id": "1", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", "all"), + "cycle": v.get("cycle", 15) + }, + "number_rule": { + "total": v.get("push_count", 1) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "site_endtime": + push_data = { + "template_id": "2", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 7) + }, + "number_rule": { + "total": v.get("push_count", 1) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "panel_pwd_endtime": + push_data = { + "template_id": "3", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 15), + "interval": 600 + }, + "number_rule": { + "total": v.get("push_count", 1) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "ssh_login_error": + push_data = { + "template_id": "4", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 30), + "count": v.get("count", 3), + "interval": v.get("interval", 600) + }, + "number_rule": { + "day_num": v.get("day_limit", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "services": + push_data = { + "template_id": "5", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", "nginx"), + "count": v.get("count", 3), + "interval": v.get("interval", 600) + }, + "number_rule": { + "day_num": v.get("day_limit", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "panel_safe_push": + push_data = { + "template_id": "6", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": {}, + "number_rule": { + "day_num": v.get("day_limit", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "ssh_login": + push_data = { + "template_id": "7", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": {}, + "number_rule": {} + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "panel_login": + push_data = { + "template_id": "8", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": {}, + "number_rule": {} + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "project_status": + push_data = { + "template_id": "9", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 1), + "project": v.get("project", 0), + "count": v.get("count", 2) if v.get("count", 2) not in (1, 2) else 2, + "interval": v.get("interval", 600) + }, + "number_rule": { + "day_num": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "panel_update": + push_data = { + "template_id": "10", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": {}, + "number_rule": { + "day_num": 1 + } + } + } + pmgr.set_task_conf_data(push_data) + + send_type = None + login_send_type_conf = "/www/server/panel/data/panel_login_send.pl" + if os.path.exists(login_send_type_conf): + send_type = read_file(login_send_type_conf).strip() + else: + # 兼容之前的 + if os.path.exists("/www/server/panel/data/login_send_type.pl"): + send_type = read_file("/www/server/panel/data/login_send_type.pl") + else: + if os.path.exists('/www/server/panel/data/login_send_mail.pl'): + send_type = "mail" + if os.path.exists('/www/server/panel/data/login_send_dingding.pl'): + send_type = "dingding" + + if isinstance(send_type, str): + sender_list = [df_mdl[i.strip()] for i in send_type.split(",") if i.strip() in df_mdl] + push_data = { + "template_id": "8", + "task_data": { + "status": True, + "sender": sender_list, + "task_data": {}, + "number_rule": {} + } + } + pmgr.set_task_conf_data(push_data) + + login_send_type_conf = "/www/server/panel/data/ssh_send_type.pl" + if os.path.exists(login_send_type_conf): + ssh_send_type = read_file(login_send_type_conf).strip() + if isinstance(ssh_send_type, str): + sender_list = [df_mdl[i.strip()] for i in ssh_send_type.split(",") if i.strip() in df_mdl] + push_data = { + "template_id": "7", + "task_data": { + "status": True, + "sender": sender_list, + "task_data": {}, + "number_rule": {} + } + } + pmgr.set_task_conf_data(push_data) + return + + +def _update_system_push(old_data: Dict[str, Dict[str, Union[str, int, float, list]]], + pmgr: PushManager, + df_mdl: Dict[str, str]): + + for k, v in old_data.items(): + sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl] + if v["type"] == "disk": + push_data = { + "template_id": "20", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", "/"), + "cycle": v.get("cycle", 2) if v.get("cycle", 2) not in (1, 2) else 2, + "count": v.get("count", 80), + }, + "number_rule": { + "total": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + if v["type"] == "disk": + push_data = { + "template_id": "21", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 5) if v.get("cycle", 5) not in (3, 5, 15) else 5, + "count": v.get("count", 80), + }, + "number_rule": { + "total": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + if v["type"] == "load": + push_data = { + "template_id": "22", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 5) if v.get("cycle", 5) not in (1, 5, 15) else 5, + "count": v.get("count", 80), + }, + "number_rule": { + "total": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + if v["type"] == "mem": + push_data = { + "template_id": "23", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "cycle": v.get("cycle", 5) if v.get("cycle", 5) not in (3, 5, 15) else 5, + "count": v.get("count", 80), + }, + "number_rule": { + "total": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + return + + +def _update_database_push(old_data: Dict[str, Dict[str, Union[str, int, float, list]]], + pmgr: PushManager, + df_mdl: Dict[str, str]): + + for k, v in old_data.items(): + sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl] + if v["type"] == "mysql_pwd_endtime": + push_data = { + "template_id": "30", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", []), + "cycle": v.get("cycle", 15), + }, + "number_rule": {} + } + } + pmgr.set_task_conf_data(push_data) + + elif v["type"] == "mysql_replicate_status": + push_data = { + "template_id": "31", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", []), + "count": v.get("cycle", 15), + "interval": v.get("interval", 600) + }, + "number_rule": {} + } + } + pmgr.set_task_conf_data(push_data) + + return None + + +def _update_rsync_push( + old_data: Dict[str, Dict[str, Union[str, int, float, list]]], + pmgr: PushManager, + df_mdl: Dict[str, str]): + + for k, v in old_data.items(): + sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl] + push_data = { + "template_id": "40", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "interval": v.get("interval", 600) + }, + "number_rule": { + "day_num": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + + +def _update_load_push( + old_data: Dict[str, Dict[str, Union[str, int, float, list]]], + pmgr: PushManager, + df_mdl: Dict[str, str]): + + for k, v in old_data.items(): + sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl] + push_data = { + "template_id": "50", + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", ""), + "cycle": v.get("cycle", "200|301|302|403|404") + }, + "number_rule": { + "day_num": v.get("push_count", 2) + } + } + } + + pmgr.set_task_conf_data(push_data) + + +def _update_task_manager_push( + old_data: Dict[str, Dict[str, Union[str, int, float, list]]], + pmgr: PushManager, + df_mdl: Dict[str, str]): + + for k, v in old_data.items(): + sender_list = [df_mdl[i.strip()] for i in v.get("module", "").split(",") if i.strip() in df_mdl] + template_id_dict = { + "task_manager_cpu": "60", + "task_manager_mem": "61", + "task_manager_process": "62" + } + if v["type"] in template_id_dict: + push_data = { + "template_id": template_id_dict[v["type"]], + "task_data": { + "status": bool(v.get("status", True)), + "sender": sender_list, + "task_data": { + "project": v.get("project", ""), + "count": v.get("count", 80), + "interval": v.get("count", 600), + }, + "number_rule": { + "day_num": v.get("push_count", 3) + } + } + } + pmgr.set_task_conf_data(push_data) + diff --git a/mod/base/push_mod/base_task.py b/mod/base/push_mod/base_task.py new file mode 100644 index 00000000..ca63e62e --- /dev/null +++ b/mod/base/push_mod/base_task.py @@ -0,0 +1,207 @@ +from typing import Union, Optional, List, Tuple +from .send_tool import WxAccountMsg + + +# 告警系统在处理每个任务时,都会重新建立有一个Task的对象,(请勿在__init__的初始化函数中添加任何参数) +# 故每个对象中都可以大胆存放本任务所有数据,不会影响同类型的其他任务 +class BaseTask: + + def __init__(self): + self.source_name: str = '' + self.title: str = '' # 这个是告警任务的标题(根据实际情况改变) + self.template_name: str = '' # 这个告警模板的标题(不会改变) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + """ + 检查设置的告警参数(是否合理) + @param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值) + @return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据, + 当检查有误时, 直接返回错误信息的字符串 + """ + raise NotImplementedError() + + def get_keyword(self, task_data: dict) -> str: + """ + 返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字, + 后续通过规则id和来源tamper 查询并使用 + @param task_data: 通过check_args后生成的告警参数字典 + @return: 返回一个关键词字符串 + """ + raise NotImplementedError() + + def get_title(self, task_data: dict) -> str: + """ + 返回一个标题 + @param task_data: 通过check_args后生成的告警参数字典 + @return: 返回一个关键词字符串 + """ + if self.title: + return self.title + return self.template_name + + def task_run_end_hook(self, res: dict) -> None: + """ + 在告警系统中。执行完了任务后,会去掉用这个函数 + @type res: dict, 执行任务的结果 + @return: + """ + return + + def task_config_update_hook(self, task: dict) -> None: + """ + 在告警管理中。更新任务数据后,会去掉用这个函数 + @return: + """ + return + + def task_config_remove_hook(self, task: dict) -> None: + """ + 在告警管理中。移除这个任务后,会去掉用这个函数 + @return: + """ + return + + def task_config_create_hook(self, task: dict) -> None: + """ + 在告警管理中。新建这个任务后,会去掉用这个函数 + @return: + """ + return + + def check_time_rule(self, time_rule: dict) -> Union[dict, str]: + """ + 检查和修改设置的告警的时间控制参数是是否合理 + 可以添加参数 get_by_func 字段用于指定使用本类中的那个函数执行时间判断标准, 替换标准的时间规则判断功能 + ↑示例如本类中的: can_send_by_time_rule + @param time_rule: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值) + @return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据, + 当检查有误时, 直接返回错误信息的字符串 + """ + return time_rule + + def check_num_rule(self, num_rule: dict) -> Union[dict, str]: + """ + 检查和修改设置的告警的次数控制参数是是否合理 + 可以添加参数 get_by_func 字段用于指定使用本类中的那个函数执行次数判断标准, 替换标准的次数规则判断功能 + ↑示例如本类中的: can_send_by_num_rule + @param num_rule: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值) + @return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据, + 当检查有误时, 直接返回错误信息的字符串 + """ + return num_rule + + def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]: + """ + 这是一个通过函数判断是否能够发送告警的示例,并非每一个告警任务都需要有 + @param task_id: 任务id + @param task_data: 告警参数信息 + @param number_rule: 次数控制信息 + @param push_data: 本次要发送的告警信息的原文,应当为字典, 来自 get_push_data 函数的返回值 + @return: 返回None + """ + return None + + def can_send_by_time_rule(self, task_id: str, task_data: dict, time_rule: dict, push_data: dict) -> Optional[str]: + """ + 这是一个通过函数判断是否能够发送告警的示例,并非每一个告警任务都需要有 + @param task_id: 任务id + @param task_data: 告警参数信息 + @param time_rule: 时间控制信息 + @param push_data: 本次要发送的告警信息的原文,应当为字典, 来自 get_push_data 函数的返回值 + @return: + """ + return None + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + """ + 判断这个任务是否需要返送 + @param task_id: 任务id + @param task_data: 任务的告警参数 + @return: 如果触发了告警,返回一个dict的原文,作为告警信息,否则应当返回None表示未触发 + 返回之中应当包含一个 msg_list 的键(值为List[str]类型),将主要的信息返回 + 用于以下信息的自动序列化包含[dingding, feishu, mail, weixin, web_hook] + 短信和微信公众号由于长度问题,必须每个任务手动实现 + """ + raise NotImplementedError() + + def filter_template(self, template: dict) -> Optional[dict]: + """ + 过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务 + @param template: 任务的模板信息 + @return: + """ + raise NotImplementedError() + + # push_public_data 公共的告警参数提取位置 + # 内容包含: + # ip 网络ip + # local_ip 本机ip + # time 时间日志的字符串 + # timestamp 当前的时间戳 + # server_name 服务器别名 + def to_dingding_msg(self, push_data: dict, push_public_data: dict) -> str: + print("dddddddddddddddddddddddddddddddddddddddddd") + msg_list = push_data.get('msg_list', None) + if msg_list is None: + raise ValueError("任务:{}的告警推送数据参数错误, 没有msg_list字段".format(self.title)) + print("dddddddddddddddddddddddddddddddddddddddddd") + return self.public_headers_msg(push_public_data,dingding=True) + "\n\n" + "\n\n".join(msg_list) + + def to_feishu_msg(self, push_data: dict, push_public_data: dict) -> str: + msg_list = push_data.get('msg_list', None) + if msg_list is None: + raise ValueError("任务:{}的告警推送数据参数错误, 没有msg_list字段".format(self.title)) + return self.public_headers_msg(push_public_data) + "\n\n" + "\n\n".join(msg_list) + + def to_mail_msg(self, push_data: dict, push_public_data: dict) -> str: + msg_list = push_data.get('msg_list', None) + if msg_list is None: + raise ValueError("任务:{}的告警推送数据参数错误, 没有msg_list字段".format(self.title)) + public_headers = self.public_headers_msg(push_public_data, "
                                    ") + return public_headers + "
                                    " + "
                                    ".join(msg_list) + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + """ + 返回 短信告警的类型和数据 + @param push_data: + @param push_public_data: + @return: 第一项是类型, 第二项是数据 + """ + raise NotImplementedError() + + def to_weixin_msg(self, push_data: dict, push_public_data: dict) -> str: + msg_list = push_data.get('msg_list', None) + if msg_list is None: + raise ValueError("任务:{}的告警推送数据参数错误, 没有msg_list字段".format(self.title)) + spc = "\n " + public_headers = self.public_headers_msg(push_public_data, "\n ") + return public_headers + spc + spc.join(msg_list) + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + raise NotImplementedError() + + def to_web_hook_msg(self, push_data: dict, push_public_data: dict) -> str: + msg_list = push_data.get('msg_list', None) + if msg_list is None: + raise ValueError("任务:{}的告警推送数据参数错误, 没有msg_list字段".format(self.title)) + public_headers = self.public_headers_msg(push_public_data, "\n") + return public_headers + "\n" + "\n".join(msg_list) + + def public_headers_msg(self, push_public_data: dict, spc: str = None,dingding=False) -> str: + if spc is None: + spc = "\n\n" + title = self.title + print(title) + if dingding: + print("dingdingtitle",title) + if "面板" not in title: + title += "面板" + print("dingdingtitle",title) + + print(title) + return spc.join([ + "#### {}".format(title), + ">服务器:" + push_public_data['server_name'], + ">IP地址:{}(外) {}(内)".format(push_public_data['ip'], push_public_data['local_ip']), + ">发送时间:" + push_public_data['time'] + ]) diff --git a/mod/base/push_mod/compatible.py b/mod/base/push_mod/compatible.py new file mode 100644 index 00000000..b459f918 --- /dev/null +++ b/mod/base/push_mod/compatible.py @@ -0,0 +1,27 @@ +import os +from .util import read_file, write_file + + +def rsync_compatible(): + files = [ + "/www/server/panel/class/push/rsync_push.py", + "/www/server/panel/plugin/rsync/rsync_push.py", + ] + for f in files: + print(f) + if not os.path.exists(f): + continue + src_data = read_file(f) + if src_data.find("push_rsync_by_task_name") != -1: + continue + src_data = src_data.replace("""if __name__ == "__main__": + rsync_push().main()""", """ +if __name__ == "__main__": + try: + sys.path.insert(0, "/www/server/panel") + from mod.base.push_mod.rsync_push import push_rsync_by_task_name + push_rsync_by_task_name(sys.argv[1]) + except: + rsync_push().main() +""") + write_file(f, src_data) diff --git a/mod/base/push_mod/database_push.py b/mod/base/push_mod/database_push.py new file mode 100644 index 00000000..5b3b728e --- /dev/null +++ b/mod/base/push_mod/database_push.py @@ -0,0 +1,239 @@ +import json +import os +import sys +import ipaddress +from datetime import datetime, timedelta +from typing import Tuple, Union, Optional + +from .send_tool import WxAccountMsg +from .base_task import BaseTask +from .util import read_file, DB, GET_CLASS + +try: + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + from panel_msg.collector import DatabasePushMsgCollect +except ImportError: + DatabasePushMsgCollect = None + + +def is_ipaddress(ip_data: str) -> bool: + try: + ipaddress.ip_address(ip_data) + except ValueError: + return False + return True + + +class MysqlPwdEndTimeTask(BaseTask): + + def __init__(self): + super().__init__() + self.template_name = "MySQL数据库密码到期" + self.source_name = "mysql_pwd_end" + + self.push_db_user = "" + + def get_title(self, task_data: dict) -> str: + return "Msql:" + task_data["project"][1] + "用户密码到期提醒" + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + task_data["interval"] = 600 + if not (isinstance(task_data["project"], list) and len(task_data["project"]) == 3): + return "设置的用户格式错误" + project = task_data["project"] + if not (isinstance(project[0], int) and isinstance(project[1], str) and is_ipaddress(project[2])): + return "设置的检测用户格式错误" + + if not (isinstance(task_data["cycle"], int) and task_data["cycle"] >= 1): + return "到期时间参数错误,至少为 1 天" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "_".join([str(i) for i in task_data["project"]]) + + def check_num_rule(self, num_rule: dict) -> Union[dict, str]: + num_rule["day_num"] = 1 + return num_rule + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + sid = task_data["project"][0] + username = task_data["project"][1] + host = task_data["project"][2] + + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + try: + import panelMysql + import db_mysql + except ImportError: + return None + + if sid == 0: + try: + db_port = int(panelMysql.panelMysql().query("show global variables like 'port'")[0][1]) + if db_port == 0: + db_port = 3306 + except: + db_port = 3306 + conn_config = { + "db_host": "localhost", + "db_port": db_port, + "db_user": "root", + "db_password": DB("config").where("id=?", (1,)).getField("mysql_root"), + "ps": "本地服务器", + } + else: + conn_config = DB("database_servers").where("id=? AND LOWER(db_type)=LOWER('mysql')", (sid,)).find() + if not conn_config: + return None + + mysql_obj = db_mysql.panelMysql().set_host(conn_config["db_host"], conn_config["db_port"], None, + conn_config["db_user"], conn_config["db_password"]) + if isinstance(mysql_obj, bool): + return None + + data_list = mysql_obj.query( + "SELECT password_last_changed FROM mysql.user WHERE user='{}' AND host='{}';".format(username, host)) + + if not isinstance(data_list, list) or not data_list: + return None + + try: + # todo:检查这里的时间转化逻辑问题 + last_time = data_list[0][0] + expire_time = last_time + timedelta(days=task_data["cycle"]) + except: + return None + + if datetime.now() > expire_time: + self.title = self.get_title(task_data) + self.push_db_user = username + return {"msg_list": [ + ">告警类型:MySQL密码即将到期", + ">告警内容:{} {}@{} 密码过期时间{} 天".format( + conn_config["ps"], username, host, expire_time.strftime("%Y-%m-%d %H:%M:%S")) + ]} + + def filter_template(self, template: dict) -> Optional[dict]: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return "", {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "MySQL数据库密码到期" + msg.msg = "Mysql用户:{}的密码即将过期,请注意".format(self.push_db_user) + msg.next_msg = "请登录面板,查看主机情况" + return msg + + +class MysqlReplicateStatusTask(BaseTask): + + def __init__(self): + super().__init__() + self.template_name = "MySQL主从复制异常告警" + self.source_name = "mysql_replicate_status" + self.title = "MySQL主从复制异常告警" + + self.slave_ip = '' + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not (isinstance(task_data["project"], str) and task_data["project"]): + return "请选择告警的从库!" + + if not (isinstance(task_data["count"], int) and task_data["count"] in (1, 2)): + return "是否自动修复选择错误!" + + if not (isinstance(task_data["interval"], int) and task_data["interval"] >= 60): + return "检查间隔时间错误,至少需要60s的间隔" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + import PluginLoader + + args = GET_CLASS() + args.slave_ip = task_data["project"] + res = PluginLoader.plugin_run("mysql_replicate", "get_replicate_status", args) + if res.get("status", False) is False: + return None + + self.slave_ip = task_data["project"] + if len(res.get("data", [])) == 0: + s_list = [">告警类型:MySQL主从复制异常告警", + ">告警内容:从库 {} 主从复制已停止,请尽快登录面板查看详情".format( + task_data["project"])] + return {"msg_list": s_list} + + sql_status = io_status = False + for item in res.get("data", []): + if item["name"] == "Slave_IO_Running" and item["value"] == "Yes": + io_status = True + if item["name"] == "Slave_SQL_Running" and item["value"] == "Yes": + sql_status = True + if io_status is True and sql_status is True: + break + + if io_status is False or sql_status is False: + repair_txt = "请尽快登录面板查看详情" + if task_data["count"] == 1: # 自动修复 + PluginLoader.plugin_run("mysql_replicate", "repair_replicate", args) + repair_txt = ",正在尝试修复" + + s_list = [">告警类型:MySQL主从复制异常告警", + ">告警内容:从库 {} 主从复制发生异常{}".format( + task_data["project"], repair_txt)] + return {"msg_list": s_list} + return None + + @staticmethod + def _get_mysql_replicate(): + slave_list = [] + mysql_replicate_path = os.path.join("/www/server/panel/plugin", "mysql_replicate", "config.json") + if os.path.isfile(mysql_replicate_path): + conf = read_file(mysql_replicate_path) + try: + conf = json.loads(conf) + slave_list = [{"title": slave_ip, "value": slave_ip} for slave_ip in conf["slave"].keys()] + except: + pass + return slave_list + + def filter_template(self, template: dict) -> Optional[dict]: + template["field"][0]["items"] = self._get_mysql_replicate() + if not template["field"][0]["items"]: + return None + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "MySQL主从复制异常告警" + msg.msg = "从库 {} 主从复制发生异常".format(self.slave_ip) + msg.next_msg = "请登录面板,在[软件商店-MySQL主从复制(重构版)]中查看" + return msg + + +class ViewMsgFormat(object): + _FORMAT = { + "30": ( + lambda x: "剩余时间小于{}天{}".format( + x["task_data"].get("cycle"), + ("(如未处理,次日会重新发送1次,持续%d天)" % x.get("number_rule", {}).get("day_num", 0)) if x.get( + "number_rule", {}).get("day_num", 0) else "" + + ) + ), + "31": (lambda x: "MySQL主从复制异常告警".format()), + } + + def get_msg(self, task: dict) -> Optional[str]: + if task["template_id"] in self._FORMAT: + return self._FORMAT[task["template_id"]](task) + return None diff --git a/mod/base/push_mod/database_push_template.json b/mod/base/push_mod/database_push_template.json new file mode 100644 index 00000000..0304a96f --- /dev/null +++ b/mod/base/push_mod/database_push_template.json @@ -0,0 +1,154 @@ +[ + { + "id": "30", + "ver": "1", + "used": true, + "source": "mysql_pwd_end", + "title": "MySQL数据库密码到期", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.database_push", + "name": "MysqlPwdEndTimeTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "选择用户", + "type": "cascader", + "default": 0, + "items": [ + { + "url": "database?action=GetPushUser" + }, + { + "url": "database?action=GetPushUser", + "data": [ + "sid" + ] + }, + { + "url": "database?action=GetPushUser", + "data": [ + "sid", + "username" + ] + } + ] + }, + { + "attr": "cycle", + "name": "剩余天数", + "type": "number", + "unit": "天", + "suffix": "", + "default": 15 + } + ], + "sorted": [ + [ + "project" + ], + [ + "cycle" + ] + ] + }, + "default": { + "project": [], + "cycle": 15 + }, + "advanced_default": { + "number_rule": { + "total": 2 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": false + }, + { + "id": "31", + "ver": "1", + "used": true, + "source": "mysql_replicate_status", + "title": "Msql主从同步告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.database_push", + "name": "MysqlReplicateStatusTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "选择监控的从库", + "type": "select", + "default": null, + "items": [] + }, + { + "attr": "count", + "name": "自动修复", + "type": "radio", + "suffix": "", + "default": 1, + "items": [ + { + "title": "自动尝试修复", + "value": 1 + }, + { + "title": "不做修复尝试", + "value": 2 + } + ] + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + } + ], + "sorted": [ + [ + "project" + ], + [ + "count" + ], + [ + "interval" + ] + ] + }, + "default": { + "project": "", + "count": 2, + "interval": 600 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": false + } +] \ No newline at end of file diff --git a/mod/base/push_mod/load_push.py b/mod/base/push_mod/load_push.py new file mode 100644 index 00000000..e3430983 --- /dev/null +++ b/mod/base/push_mod/load_push.py @@ -0,0 +1,271 @@ +import json +import os +import time +from typing import Tuple, Union, Optional + +from .mods import PUSH_DATA_PATH, TaskTemplateConfig +from .send_tool import WxAccountMsg +from .base_task import BaseTask +from .util import read_file, DB, GET_CLASS, write_file + + +class NginxLoadTask(BaseTask): + def __init__(self): + super().__init__() + self.source_name = "nginx_load_push" + self.template_name = "负载均衡告警" + self._tip_counter = None + + @property + def tip_counter(self) -> dict: + if self._tip_counter is not None: + return self._tip_counter + tip_counter = '{}/load_balance_push.json'.format(PUSH_DATA_PATH) + if os.path.exists(tip_counter): + try: + self._tip_counter = json.loads(read_file(tip_counter)) + except json.JSONDecodeError: + self._tip_counter = {} + else: + self._tip_counter = {} + return self._tip_counter + + def save_tip_counter(self): + tip_counter = '{}/load_balance_push.json'.format(PUSH_DATA_PATH) + write_file(tip_counter, json.dumps(self.tip_counter)) + + def get_title(self, task_data: dict) -> str: + if task_data["project"] == "all": + return "负载节点异常告警" + return "负载节点[{}]异常告警".format(task_data["project"]) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + all_upstream_name = DB("upstream").field("name").select() + if isinstance(all_upstream_name, str) and all_upstream_name.startswith("error"): + return '没有负载均衡配置,无法设置告警' + all_upstream_name = [i["name"] for i in all_upstream_name] + if not bool(all_upstream_name): + return '没有负载均衡配置,无法设置告警' + if task_data["project"] not in all_upstream_name and task_data["project"] != "all": + return '没有该负载均衡配置,无法设置告警' + + cycle = [] + for i in task_data["cycle"].split("|"): + if bool(i) and i.isdecimal(): + code = int(i) + if 100 <= code < 600: + cycle.append(str(code)) + if not bool(cycle): + return '没有指定任何错误码,无法设置告警' + + task_data["cycle"] = "|".join(cycle) + return task_data + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def _check_func(self, upstream_name: str, codes: str) -> list: + import PluginLoader + get_obj = GET_CLASS() + get_obj.upstream_name = upstream_name + # 调用外部插件检查负载均衡的健康状况 + upstreams = PluginLoader.plugin_run("load_balance", "get_check_upstream", get_obj) + access_codes = [int(i) for i in codes.split("|") if bool(i.strip())] + res_list = [] + for upstream in upstreams: + # 检查每个节点,返回有问题的节点信息 + res = upstream.check_nodes(access_codes, return_nodes=True) + for ping_url in res: + if ping_url in self.tip_counter: + self.tip_counter[ping_url].append(int(time.time())) + idx = 0 + for i in self.tip_counter[ping_url]: + # 清理超过4分钟的记录 + if time.time() - i > 60 * 4: + idx += 1 + self.tip_counter[ping_url] = self.tip_counter[ping_url][idx:] + print("self.tip_counter[ping_url]",self.tip_counter[ping_url]) + # 如果一个节点连续三次出现在告警列表中,则视为需要告警 + if len(self.tip_counter[ping_url]) >= 3: + res_list.append(ping_url) + self.tip_counter[ping_url] = [] + else: + self.tip_counter[ping_url] = [int(time.time()), ] + self.save_tip_counter() + return res_list + + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + err_nodes = self._check_func(task_data["project"], task_data["cycle"]) + if not err_nodes: + return None + pj = "负载均衡:【{}】".format(task_data["project"]) if task_data["project"] != "all" else "负载均衡" + nodes = '、'.join(err_nodes) + return { + "msg_list": [ + ">通知类型:企业版负载均衡告警", + ">告警内容:{}配置下的节点【{}】出现访问错误,请及时关注节点情况并处理。 ".format( + pj, nodes), + ], + "pj": pj, + "nodes": nodes + } + + def filter_template(self, template: dict) -> Optional[dict]: + if not os.path.exists("/www/server/panel/plugin/load_balance/load_balance_main.py"): + return None + all_upstream = DB("upstream").field("name").select() + if isinstance(all_upstream, str) and all_upstream.startswith("error"): + return None + all_upstream_name = [i["name"] for i in all_upstream] + if not all_upstream_name: + return None + for name in all_upstream_name: + template["field"][0]["items"].append({ + "title": name, + "value": name + }) + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "负载均衡告警" + msg.msg = "负载均衡出现节点异常,请登录面板查看" + return msg + + def task_config_create_hook(self, task: dict) -> None: + old_config_file = "/www/server/panel/class/push/push.json" + try: + old_config = json.loads(read_file(old_config_file)) + except: + return + if "load_balance_push" not in old_config: + old_config["load_balance_push"] = {} + old_data = { + "push_count": task["number_rule"].get("day_num", 2), + "cycle": task["task_data"].get("cycle", "200|301|302|403|404"), + "interval": task["task_data"].get("interval", 60), + "title": task["title"], + "status": task['status'], + "module": ",".join(task["sender"]) + } + for k, v in old_config["load_balance_push"].items(): + if v["project"] == task["task_data"]["project"]: + v.update(old_data) + else: + old_data["project"] = task["task_data"]["project"] + old_config["load_balance_push"][int(time.time())] = old_data + + write_file(old_config_file, json.dumps(old_config)) + + def task_config_update_hook(self, task: dict) -> None: + return self.task_config_create_hook(task) + + def task_config_remove_hook(self, task: dict) -> None: + old_config_file = "/www/server/panel/class/push/push.json" + try: + old_config = json.loads(read_file(old_config_file)) + except: + return + if "load_balance_push" not in old_config: + old_config["load_balance_push"] = {} + old_config["load_balance_push"] = { + k: v for k, v in old_config["load_balance_push"].items() + if v["project"] != task["task_data"]["project"] + } + + +def load_load_template(): + if TaskTemplateConfig().get_by_id("50"): + return None + + from .mods import load_task_template_by_config + load_task_template_by_config( + [{ + "id": "50", + "ver": "1", + "used": True, + "source": "nginx_load_push", + "title": "负载均衡", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.load_push", + "name": "NginxLoadTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "负载名称", + "type": "select", + "default": "all", + "unit": "", + "suffix": ( + "*" + "选中的负载配置中,出现节点访问失败时,触发告警" + ), + "items": [ + { + "title": "所有已配置的负载", + "value": "all" + } + ] + }, + { + "attr": "cycle", + "name": "成功的状态码", + "type": "textarea", + "unit": "", + "suffix": ( + "
                                    *" + "状态码以竖线分隔,如:200|301|302|403|404" + ), + "width": "400px", + "style": { + 'height': '70px', + }, + "default": "200|301|302|403|404" + } + ], + "sorted": [ + [ + "project" + ], + [ + "cycle" + ] + ], + }, + "default": { + "project": "all", + "cycle": "200|301|302|403|404" + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": False + }] + ) + + +class ViewMsgFormat(object): + + @staticmethod + def get_msg(task: dict) -> Optional[str]: + if task["template_id"] == "50": + return "节点访问异常时,推送告警信息(每日推送{}次后不在推送)".format( + task.get("number_rule", {}).get("day_num")) + return None diff --git a/mod/base/push_mod/manager.py b/mod/base/push_mod/manager.py new file mode 100644 index 00000000..009edb87 --- /dev/null +++ b/mod/base/push_mod/manager.py @@ -0,0 +1,312 @@ +import json +import os +import time +from typing import Union, Optional + +from .mods import TaskTemplateConfig, TaskConfig, SenderConfig, TaskRecordConfig +from .system import PushSystem +from mod.base import json_response + + +class PushManager: + + def __init__(self): + self.template_conf = TaskTemplateConfig() + self.task_conf = TaskConfig() + self.send_config = SenderConfig() + self._send_conf_cache = {} + + def _get_sender_conf(self, sender_id): + if sender_id in self._send_conf_cache: + return self._send_conf_cache[sender_id] + tmp = self.send_config.get_by_id(sender_id) + self._send_conf_cache[sender_id] = tmp + return tmp + + def normalize_task_config(self, task, template) -> Union[dict, str]: + result = {} + sender = task.get("sender", None) + if sender is None: + return "未设置告警通道" + if not isinstance(sender, list): + return "告警通道设置错误" + + new_sender = [] + for i in sender: + sender_conf = self._get_sender_conf(i) + if not sender_conf: + continue + else: + new_sender.append(i) + if sender_conf["sender_type"] not in template["send_type_list"]: + if sender_conf["sender_type"] == "sms": + return "不支持短信告警" + return "不支持的告警方式:{}".format(sender_conf['data']["title"]) + if not sender_conf["used"]: + if sender_conf["sender_type"] == "sms": + return "短信告警通道已关闭" + return "已关闭的告警方式:{}".format(sender_conf['data']["title"]) + + result["sender"] = new_sender + + if "default" in template and template["default"]: + task_data = task.get("task_data", {}) + for k, v in template["default"].items(): + if k not in task_data: + task_data[k] = v + + result["task_data"] = task_data + + if "task_data" not in result: + result["task_data"] = {} + + time_rule = task.get("time_rule", {}) + + if "send_interval" in time_rule: + if not isinstance(time_rule["send_interval"], int): + return "最小间隔时间设置错误" + if time_rule["send_interval"] < 0: + return "最小间隔时间设置错误" + + if "time_range" in time_rule: + if not isinstance(time_rule["time_range"], list): + return "时间范围设置错误" + if not len(time_rule["time_range"]) == 2: + del time_rule["time_range"] + else: + time_range = time_rule["time_range"] + if not (isinstance(time_range[0], int) and isinstance(time_range[1], int) and + 0 <= time_range[0] < time_range[1] <= 60 * 60 * 24): + return "时间范围设置错误" + + result["time_rule"] = time_rule + + number_rule = task.get("number_rule", {}) + if "day_num" in number_rule: + if not (isinstance(number_rule["day_num"], int) and number_rule["day_num"] >= 0): + return "每日最小次数设置错误" + + if "total" in number_rule: + if not (isinstance(number_rule["total"], int) and number_rule["total"] >= 0): + return "最大告警次数设置错误" + + result["number_rule"] = number_rule + + if "status" not in task: + result["status"] = True + if "status" in task: + if isinstance(task["status"], bool): + result["status"] = task["status"] + + return result + + def set_task_conf_data(self, push_data: dict) -> Optional[str]: + task_id = push_data.get("task_id", None) + template_id = push_data.get("template_id") + task = push_data.get("task_data") + + target_task_conf = None + if task_id is not None: + tmp = self.task_conf.get_by_id(task_id) + if tmp is None: + target_task_conf = tmp + + template = self.template_conf.get_by_id(template_id) + if not template: + return "未查询到告警模板" + + if template["unique"] and not target_task_conf: + for i in self.task_conf.config: + if i["template_id"] == template["id"]: + target_task_conf = i + break + + task_obj = PushSystem().get_task_object(template_id, template["load_cls"]) + if not task_obj: + return "加载任务类型错误,您可以尝试修复面板" + + res = self.normalize_task_config(task, template) + if isinstance(res, str): + return res + + task_data = task_obj.check_task_data(res["task_data"]) + if isinstance(task_data, str): + return task_data + + number_rule = task_obj.check_num_rule(res["number_rule"]) + if isinstance(number_rule, str): + return number_rule + + time_rule = task_obj.check_time_rule(res["time_rule"]) + if isinstance(time_rule, str): + return time_rule + + res["task_data"] = task_data + res["number_rule"] = number_rule + res["time_rule"] = time_rule + + res["keyword"] = task_obj.get_keyword(task_data) + res["source"] = task_obj.source_name + res["title"] = task_obj.get_title(task_data) + + if not target_task_conf: + tmp = self.task_conf.get_by_keyword(res["source"], res["keyword"]) + if tmp: + target_task_conf = tmp + + if not target_task_conf: + res["id"] = self.task_conf.nwe_id() + res["template_id"] = template_id + res["status"] = True + res["pre_hook"] = {} + res["after_hook"] = {} + res["last_check"] = 0 + res["last_send"] = 0 + res["number_data"] = {} + res["create_time"] = time.time() + res["record_time"] = 0 + self.task_conf.config.append(res) + task_obj.task_config_create_hook(res) + else: + target_task_conf.update(res) + target_task_conf["last_check"] = 0 + target_task_conf["number_data"] = {} # 次数控制数据置空 + task_obj.task_config_update_hook(target_task_conf) + + self.task_conf.save_config() + + return None + + def set_task_conf(self, get): + task_id = None + try: + if hasattr(get, "task_id"): + task_id = get.task_id.strip() + if not task_id: + task_id = None + else: + self.remove_task_conf(get) + template_id = get.template_id.strip() + task = json.loads(get.task_data.strip()) + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数错误") + push_data = { + "task_id": task_id, + "template_id": template_id, + "task_data": task, + } + res = self.set_task_conf_data(push_data) + if res: + return json_response(status=False, msg=res) + # target_task_conf = None + # if task_id is not None: + # tmp = self.task_conf.get_by_id(task_id) + # if tmp is None: + # target_task_conf = tmp + # + # template = self.template_conf.get_by_id(template_id) + # if not template: + # return json_response(status=False, msg="为查询到告警模板") + # + # if template["unique"] and not target_task_conf: + # for i in self.task_conf.config: + # if i["template_id"] == template["id"]: + # target_task_conf = i + # break + # + # task_obj = PushSystem().get_task_object(template_id, template["load_cls"]) + # if not task_obj: + # return json_response(status=False, msg="加载任务类型错误,您可以尝试修复面板") + # + # res = self.normalize_task_config(task, template) + # if isinstance(res, str): + # return json_response(status=True, msg=res) + # + # task_data = task_obj.check_task_data(res["task_data"]) + # if isinstance(task_data, str): + # return json_response(status=True, msg=task_data) + # + # number_rule = task_obj.check_num_rule(res["number_rule"]) + # if isinstance(number_rule, str): + # return json_response(status=True, msg=number_rule) + # + # time_rule = task_obj.check_time_rule(res["time_rule"]) + # if isinstance(time_rule, str): + # return json_response(status=True, msg=time_rule) + # + # res["task_data"] = task_data + # res["number_rule"] = number_rule + # res["time_rule"] = time_rule + # + # res["keyword"] = task_obj.get_keyword(task_data) + # res["source"] = task_obj.source_name + # res["title"] = task_obj.get_title(task_data) + # + # if not target_task_conf: + # tmp = self.task_conf.get_by_keyword(res["source"], res["keyword"]) + # if tmp: + # target_task_conf = tmp + # + # if not target_task_conf: + # res["id"] = self.task_conf.nwe_id() + # res["template_id"] = template_id + # res["status"] = True + # res["pre_hook"] = {} + # res["after_hook"] = {} + # res["last_check"] = 0 + # res["last_send"] = 0 + # res["number_data"] = {} + # res["create_time"] = time.time() + # res["record_time"] = 0 + # self.task_conf.config.append(res) + # task_obj.task_config_create_hook(res) + # else: + # target_task_conf.update(res) + # target_task_conf["last_check"] = 0 + # target_task_conf["number_data"] = {} # 次数控制数据置空 + # task_obj.task_config_update_hook(target_task_conf) + # + # self.task_conf.save_config() + return json_response(status=True, msg="告警任务保存成功") + + def change_task_conf(self, get): + try: + task_id = get.task_id.strip() + except AttributeError: + return json_response(status=False, msg="参数错误") + + tmp = self.task_conf.get_by_id(task_id) + if tmp is None: + return json_response(status=True, msg="为查询到告警任务") + + tmp["status"] = not tmp["status"] + + self.task_conf.save_config() + return json_response(status=True, msg="操作成功") + + def remove_task_conf(self, get): + try: + task_id = get.task_id.strip() + except AttributeError: + return json_response(status=False, msg="参数错误") + + tmp = self.task_conf.get_by_id(task_id) + if tmp is None: + return json_response(status=True, msg="为查询到告警任务") + + self.task_conf.config.remove(tmp) + + self.task_conf.save_config() + template = self.template_conf.get_by_id(tmp["template_id"]) + if template: + task_obj = PushSystem().get_task_object(template["id"], template["load_cls"]) + if task_obj: + task_obj.task_config_remove_hook(tmp) + + return json_response(status=True, msg="操作成功") + + @staticmethod + def clear_task_record_by_task_id(task_id): + tr_conf = TaskRecordConfig(task_id) + if os.path.exists(tr_conf.config_file_path): + os.remove(tr_conf.config_file_path) diff --git a/mod/base/push_mod/mods.py b/mod/base/push_mod/mods.py new file mode 100644 index 00000000..ab7827c0 --- /dev/null +++ b/mod/base/push_mod/mods.py @@ -0,0 +1,368 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: baozi +# ------------------------------------------------------------------- +# 新告警的所有数据库操作 +# ------------------------------ +import json +import os +import types +from typing import Any, Dict, Optional, List +from threading import Lock +from uuid import uuid4 + +import fcntl + +from .util import Sqlite, write_log, read_file, write_file + +_push_db_lock = Lock() + + +# 代替 class/db.py 中, 离谱的两个query函数 +def msg_db_query_func(self, sql, param=()): + # 执行SQL语句返回数据集 + self._Sql__GetConn() + try: + return self._Sql__DB_CONN.execute(sql, self._Sql__to_tuple(param)) + except Exception as ex: + return "error: " + str(ex) + + +def get_push_db(): + db_file = "/www/server/panel/data/db/mod_push.db" + if not os.path.isdir(os.path.dirname(db_file)): + os.makedirs(os.path.dirname(db_file)) + + db = Sqlite() + setattr(db, "_Sql__DB_FILE", db_file) + setattr(db, "query", types.MethodType(msg_db_query_func, db)) + return db + + +def get_table(table_name: str): + db = get_push_db() + db.table = table_name + return db + + +def lock_push_db(): + with open("/www/server/panel/data/db/mod_push.db", mode="rb") as msg_fd: + fcntl.flock(msg_fd.fileno(), fcntl.LOCK_EX) + _push_db_lock.locked() + + +def unlock_push_db(): + with open("/www/server/panel/data/db/mod_push.db", mode="rb") as msg_fd: + fcntl.flock(msg_fd.fileno(), fcntl.LOCK_UN) + _push_db_lock.acquire() + + +def push_db_locker(func): + def inner_func(*args, **kwargs): + lock_push_db() + try: + res = func(*args, **kwargs) + except Exception as e: + unlock_push_db() # 即使 报错了 也先解锁再操作 + raise e + else: + unlock_push_db() + return res + + return inner_func + + +DB_INIT_ERROR = False + +PANEL_PATH = "/www/server/panel" +PUSH_DATA_PATH = "{}/data/mod_push_data".format(PANEL_PATH) +UPDATE_VERSION_FILE = "{}/update_panel.pl".format(PUSH_DATA_PATH) +UPDATE_MOD_PUSH_FILE = "{}/update_mod.pl".format(PUSH_DATA_PATH) + + +class BaseConfig: + config_file_path = "" + + def __init__(self): + if not os.path.exists(PUSH_DATA_PATH): + os.makedirs(PUSH_DATA_PATH) + self._config: Optional[List[Dict[str, Any]]] = None + + @property + def config(self) -> List[Dict[str, Any]]: + if self._config is None: + try: + self._config = json.loads(read_file(self.config_file_path)) + except: + self._config = [] + return self._config + + def save_config(self) -> None: + write_file(self.config_file_path, json.dumps(self.config)) + + @staticmethod + def nwe_id() -> str: + return uuid4().hex[::2] + + def get_by_id(self, target_id: str) -> Optional[Dict[str, Any]]: + for i in self.config: + if i.get("id", None) == target_id: + return i + + +class TaskTemplateConfig(BaseConfig): + config_file_path = "{}/task_template.json".format(PUSH_DATA_PATH) + + +class TaskConfig(BaseConfig): + config_file_path = "{}/task.json".format(PUSH_DATA_PATH) + + def get_by_keyword(self, source: str, keyword: str) -> Optional[Dict[str, Any]]: + for i in self.config: + if i.get("source", None) == source and i.get("keyword", None) == keyword: + return i + + +class TaskRecordConfig(BaseConfig): + config_file_path_fmt = "%s/task_record_{}.json" % PUSH_DATA_PATH + + def __init__(self, task_id: str): + super().__init__() + self.config_file_path = self.config_file_path_fmt.format(task_id) + + +class SenderConfig(BaseConfig): + config_file_path = "{}/sender.json".format(PUSH_DATA_PATH) + + def __init__(self): + super(SenderConfig, self).__init__() + if not os.path.exists(self.config_file_path): + write_file(self.config_file_path, json.dumps([{ + "id": self.nwe_id(), + "used": True, + "sender_type": "sms", + "data": {}, + "original": True + }])) + + +def init_db(): + global DB_INIT_ERROR + # id 模板id 必须唯一, 后端开发需要协商 + # ver 模板版本号, 用于更新 + # used 是否在使用用 + # source 来源, 如Waf, rsync + # title 标题 + # load_cls 要加载的类,或者从那种调用方法中获取到任务处理对象 + # template 给前端,用于展示的数据 + # default 默认数据,用于数据过滤, 和默认值 + # unique 是否仅可唯一设置 + # create_time 创建时间 + create_task_template_sql = ( + "CREATE TABLE IF NOT EXISTS 'task_template' (" + "'id' INTEGER PRIMARY KEY AUTOINCREMENT, " + "'ver' TEXT NOT NULL DEFAULT '1.0.0', " + "'used' INTEGER NOT NULL DEFAULT 1, " + "'source' TEXT NOT NULL DEFAULT 'site_push', " + "'title' TEXT NOT NULL DEFAULT '', " + "'load_cls' TEXT NOT NULL DEFAULT '{}', " + "'template' TEXT NOT NULL DEFAULT '{}', " + "'default' TEXT NOT NULL DEFAULT '{}', " + "'send_type_list' TEXT NOT NULL DEFAULT '[]', " + "'unique' INTEGER NOT NULL DEFAULT 0, " + "'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))" + ");" + ) + # source 来源, 例如waf(防火墙), rsync(文件同步) + # keyword 关键词, 不同的来源在使用中可以以此查出具体的任务,需要每个来源自己约束 + # task_data 任务数据字典,字段可以自由设计 + # sender 告警通道信息,为字典,可通过get_by_func字段指定从某个函数获取,用于发送 + # time_rule 告警的时间规则,包含 间隔时间(send_interval), (time-range) + # number_rule 告警的次数规则,包含 每日次数(day_num), 总次数(total), 通过函数判断(get_by_func) + # status 状态是否开启 + # pre_hook, after_hook 前置处理和后置处理 + # record_time, 告警记录存储时间, 默认为0, 认为长时间储存 + # last_check, 上次执行检查的时间 + # last_send, 上次次发送时间 + # number_data, 发送次数信息 + create_task_sql = ( + "CREATE TABLE IF NOT EXISTS 'task' (" + "'id' INTEGER PRIMARY KEY AUTOINCREMENT, " + "'template_id' INTEGER NOT NULL DEFAULT 0, " + "'source' TEXT NOT NULL DEFAULT '', " + "'keyword' TEXT NOT NULL DEFAULT '', " + "'title' TEXT NOT NULL DEFAULT '', " + "'task_data' TEXT NOT NULL DEFAULT '{}', " + "'sender' TEXT NOT NULL DEFAULT '[]', " + "'time_rule' TEXT NOT NULL DEFAULT '{}', " + "'number_rule' TEXT NOT NULL DEFAULT '{}', " + "'status' INTEGER NOT NULL DEFAULT 1, " + "'pre_hook' TEXT NOT NULL DEFAULT '{}', " + "'after_hook' TEXT NOT NULL DEFAULT '{}', " + "'last_check' INTEGER NOT NULL DEFAULT 0, " + "'last_send' INTEGER NOT NULL DEFAULT 0, " + "'number_data' TEXT NOT NULL DEFAULT '{}', " + "'create_time' INTEGER NOT NULL DEFAULT (strftime('%s')), " + "'record_time' INTEGER NOT NULL DEFAULT 0" + ");" + ) + + create_task_record_sql = ( + "CREATE TABLE IF NOT EXISTS 'task_record' (" + "'id' INTEGER PRIMARY KEY AUTOINCREMENT, " + "'template_id' INTEGER NOT NULL DEFAULT 0, " + "'task_id' INTEGER NOT NULL DEFAULT 0, " + "'do_send' TEXT NOT NULL DEFAULT '{}', " + "'send_data' TEXT NOT NULL DEFAULT '{}', " + "'result' TEXT NOT NULL DEFAULT '{}', " + "'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))" + ");" + ) + + create_send_record_sql = ( + "CREATE TABLE IF NOT EXISTS 'send_record' (" + "'id' INTEGER PRIMARY KEY AUTOINCREMENT, " + "'record_id' INTEGER NOT NULL DEFAULT 0, " + "'sender_name' TEXT NOT NULL DEFAULT '', " + "'sender_id' INTEGER NOT NULL DEFAULT 0, " + "'sender_type' TEXT NOT NULL DEFAULT '', " + "'send_data' TEXT NOT NULL DEFAULT '{}', " + "'result' TEXT NOT NULL DEFAULT '{}', " + "'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))" + ");" + ) + + create_sender_sql = ( + "CREATE TABLE IF NOT EXISTS 'sender' (" + "'id' INTEGER PRIMARY KEY AUTOINCREMENT, " + "'used' INTEGER NOT NULL DEFAULT 1, " + "'sender_type' TEXT NOT NULL DEFAULT '', " + "'name' TEXT NOT NULL DEFAULT '', " + "'data' TEXT NOT NULL DEFAULT '{}', " + "'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))" + ");" + ) + + lock_push_db() + with get_push_db() as db: + db.execute("pragma journal_mode=wal") + + res = db.execute(create_task_template_sql) + if isinstance(res, str) and res.startswith("error"): + write_log("告警系统", "task_template数据表创建错误:" + res) + DB_INIT_ERROR = True + return + + res = db.execute(create_task_sql) + if isinstance(res, str) and res.startswith("error"): + write_log("告警系统", "task数据表创建错误:" + res) + DB_INIT_ERROR = True + return + + res = db.execute(create_task_record_sql) + if isinstance(res, str) and res.startswith("error"): + write_log("告警系统", "task_recorde数据表创建错误:" + res) + DB_INIT_ERROR = True + return + + res = db.execute(create_send_record_sql) + if isinstance(res, str) and res.startswith("error"): + write_log("告警系统", "send_record数据表创建错误:" + res) + DB_INIT_ERROR = True + return + + res = db.execute(create_sender_sql) + if isinstance(res, str) and res.startswith("error"): + write_log("告警系统", "sender数据表创建错误:" + res) + DB_INIT_ERROR = True + return + + db.execute( + "INSERT INTO 'sender' (id, sender_type, data) VALUES (?,?,?)", + (1, 'sms', json.dumps({"count": 0, "total": 0})) + ) # 插入短信 + + unlock_push_db() + + init_template_file = "/www/server/panel/config/mod_push_init.json" + err = load_task_template_by_file(init_template_file) + if err: + write_log("告警系统", "task_template数据表初始数据加载失败:" + res) + + +def _check_fields(template: dict) -> bool: + if not isinstance(template, dict): + return False + + fields = ("id", "ver", "used", "source", "title", "load_cls", "template", "default", "unique", "create_time") + for field in fields: + if field not in template: + return False + return True + + +def load_task_template_by_config(templates: List[Dict]) -> None: + """ + 通过 传入的配置信息 执行一次模板更新操作 + @param templates: 模板内容,为一个数据列表 + @return: 报错信息,如果返回None则表示执行成功 + """ + + task_template_config = TaskTemplateConfig() + add_list = [] + for template in templates: + tmp = task_template_config.get_by_id(template['id']) + if tmp is not None: + tmp.update(template) + else: + add_list.append(template) + + task_template_config.config.extend(add_list) + task_template_config.save_config() + + # with get_table('task_template') as table: + # for template in templates: + # if not _check_fields(template): + # continue + # res = table.where("id = ?", (template['id'])).field('ver').select() + # if isinstance(res, str): + # return "数据库损坏:" + res + # if not res: # 没有就插入 + # table.insert(template) + # else: + # # 版本不一致就更新版本 + # if res['ver'] != template['ver']: + # template.pop("id") + # table.where("id = ?", (template['id'])).update(template) + # + + +def load_task_template_by_file(template_file: str) -> Optional[str]: + """ + 执行一次模板更新操作 + @param template_file: 模板文件路径 + @return: 报错信息,如果返回None则表示执行成功 + """ + if not os.path.isfile(template_file): + return "模板文件不存在,更新失败" + + if DB_INIT_ERROR: + return "数据库初始化时报错,无法更新" + + res = read_file(template_file) + if not isinstance(res, str): + return "数据读取失败" + + try: + templates = json.loads(res) + except (json.JSONDecoder, TypeError, ValueError): + return "仅支持JSON格式数据" + + if not isinstance(templates, list): + return "数据格式错误,应当为一个列表" + + return load_task_template_by_config(templates) diff --git a/mod/base/push_mod/rsync_push.py b/mod/base/push_mod/rsync_push.py new file mode 100644 index 00000000..7b07c24e --- /dev/null +++ b/mod/base/push_mod/rsync_push.py @@ -0,0 +1,301 @@ +import json +import os +import re +from datetime import datetime, timedelta +from typing import Tuple, Union, Optional, Iterator + +from .send_tool import WxAccountMsg +from .base_task import BaseTask +from .mods import TaskTemplateConfig +from .util import read_file + + +def rsync_ver_is_38() -> Optional[bool]: + """ + 检查rsync的版本是否为3.8。 + 该函数不接受任何参数。 + 返回值: + - None: 如果无法确定rsync的版本或文件不存在。 + - bool: 如果版本确定为3.8,则返回True;否则返回False。 + """ + push_file = "/www/server/panel/plugin/rsync/rsync_push.py" + if not os.path.exists(push_file): + return None + ver_info_file = "/www/server/panel/plugin/rsync/info.json" + if not os.path.exists(ver_info_file): + return None + try: + info = json.loads(read_file(ver_info_file)) + except (json.JSONDecodeError, TypeError): + return None + ver = info["versions"] + ver_tuples = [int(i) for i in ver.split(".")] + if len(ver_tuples) < 3: + ver_tuples = ver_tuples.extend([0] * (3 - len(ver_tuples))) + if ver_tuples[0] < 3: + return None + if ver_tuples[1] <= 8 and ver_tuples[0] == 3: + return True + + return False + + +class Rsync38Task(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "rsync_push" + self.template_name = "文件同步告警" + self.title = "文件同步告警" + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if "interval" not in task_data or not isinstance(task_data["interval"], int): + task_data["interval"] = 600 + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "rsync_push" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + has_err = self._check(task_data.get("interval", 600)) + if not has_err: + return None + + return { + "msg_list": [ + ">通知类型:文件同步告警", + ">告警内容:文件同步执行中出错了,请及时关注文件同步情况并处理。 ", + ] + } + + @staticmethod + def _check(interval: int) -> bool: + if not isinstance(interval, int): + return False + start_time = datetime.now() - timedelta(seconds=interval * 1.2) + log_file = "{}/plugin/rsync/lsyncd.log".format("/www/server/panel") + if not os.path.exists(log_file): + return False + return LogChecker(log_file=log_file, start_time=start_time)() + + def check_time_rule(self, time_rule: dict) -> Union[dict, str]: + if "send_interval" not in time_rule or not isinstance(time_rule["interval"], int): + time_rule["send_interval"] = 3 * 60 + if time_rule["send_interval"] < 60: + time_rule["send_interval"] = 60 + return time_rule + + def filter_template(self, template: dict) -> Optional[dict]: + res = rsync_ver_is_38() + if res is None: + return None + if res: + return template + else: + return None + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "文件同步告警" + msg.msg = "同步执行出错了,请及时关注同步情况" + return msg + + +class Rsync39Task(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "rsync_push" + self.template_name = "文件同步告警" + self.title = "文件同步告警" + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if "interval" not in task_data or not isinstance(task_data["interval"], int): + task_data["interval"] = 600 + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "rsync_push" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + """ + 不返回数据,以实时触发为主 + """ + return None + + def check_time_rule(self, time_rule: dict) -> Union[dict, str]: + if "send_interval" not in time_rule or not isinstance(time_rule["send_interval"], int): + time_rule["send_interval"] = 3 * 60 + if time_rule["send_interval"] < 60: + time_rule["send_interval"] = 60 + return time_rule + + def filter_template(self, template: dict) -> Optional[dict]: + res = rsync_ver_is_38() + if res is None: + return None + if res is False: + return template + else: + return None + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + task_name = push_data.get("task_name", None) + msg = WxAccountMsg.new_msg() + msg.thing_type = "文件同步告警" + if task_name: + msg.msg = "文件同步任务{}出错了".format(task_name) + else: + msg.msg = "同步执行出错了,请及时关注同步情况" + return msg + + +class LogChecker: + """ + 排序查询并获取日志内容 + """ + rep_time = re.compile(r'(?P(\w{3}\s+){2}(\d{1,2})\s+(\d{2}:?){3}\s+\d{4})') + format_str = '%a %b %d %H:%M:%S %Y' + err_datetime = datetime.fromtimestamp(0) + err_list = ("error", "Error", "ERROR", "exitcode = 10", "failed") + + def __init__(self, log_file: str, start_time: datetime): + self.log_file = log_file + self.start_time = start_time + self.is_over_time = None # None:还没查到时间,未知, False: 可以继续网上查询, True:比较早的数据了,不再向上查询 + self.has_err = False # 目前已查询的内容中是否有报错信息 + + def _format_time(self, log_line) -> Optional[datetime]: + try: + date_str_res = self.rep_time.search(log_line) + if date_str_res: + time_str = date_str_res.group("target") + return datetime.strptime(time_str, self.format_str) + except Exception: + return self.err_datetime + return None + + # 返回日志内容 + def __call__(self): + _buf = b"" + file_size, fp = os.stat(self.log_file).st_size - 1, open(self.log_file, mode="rb") + fp.seek(-1, 2) + while file_size: + read_size = min(1024, file_size) + fp.seek(-read_size, 1) + buf: bytes = fp.read(read_size) + _buf + fp.seek(-read_size, 1) + if file_size > 1024: + idx = buf.find(ord("\n")) + _buf, buf = buf[:idx], buf[idx + 1:] + for i in self._get_log_line_from_buf(buf): + self._check(i) + if self.is_over_time: + return self.has_err + file_size -= read_size + return False + + # 从缓冲中读取日志 + @staticmethod + def _get_log_line_from_buf(buf: bytes) -> Iterator[str]: + n, m = 0, 0 + buf_len = len(buf) - 1 + for i in range(buf_len, -1, -1): + if buf[i] == ord("\n"): + log_line = buf[buf_len + 1 - m: buf_len - n + 1].decode("utf-8") + yield log_line + n = m = m + 1 + else: + m += 1 + yield buf[0: buf_len - n + 1].decode("utf-8") + + # 格式化并筛选查询条件 + def _check(self, log_line: str) -> None: + # 筛选日期 + for err in self.err_list: + if err in log_line: + self.has_err = True + + ck_time = self._format_time(log_line) + if ck_time: + self.is_over_time = self.start_time > ck_time + + +def load_rsync_template(): + """ + 加载rsync模板 + """ + if TaskTemplateConfig().get_by_id("40"): + return None + from .mods import load_task_template_by_config + load_task_template_by_config( + [{ + "id": "40", + "ver": "1", + "used": True, + "source": "rsync_push", + "title": "文件同步告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.rsync_push", + "name": "RsyncTask" + }, + "template": { + "field": [ + ], + "sorted": [ + ] + }, + "default": { + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": True + }] + ) + + +RsyncTask = Rsync39Task +if rsync_ver_is_38() is True: + RsyncTask = Rsync38Task + + +def push_rsync_by_task_name(task_name: str): + from .system import push_by_task_keyword + + push_data = { + "task_name": task_name, + "msg_list": [ + ">通知类型:文件同步告警", + ">告警内容:文件同步任务{}在执行中出错了,请及时关注文件同步情况并处理。 ".format( + task_name), + ] + } + push_by_task_keyword("rsync_push", "rsync_push", push_data=push_data) + + +class ViewMsgFormat(object): + + @staticmethod + def get_msg(task: dict) -> Optional[str]: + if task["template_id"] == "40": + return "文件同步出现异常时,推送告警信息(每日推送{}次后不在推送)".format( + task.get("number_rule", {}).get("day_num")) + return None diff --git a/mod/base/push_mod/send_tool.py b/mod/base/push_mod/send_tool.py new file mode 100644 index 00000000..85fbeb44 --- /dev/null +++ b/mod/base/push_mod/send_tool.py @@ -0,0 +1,133 @@ +import ipaddress +import re + +from .util import get_config_value + + +class WxAccountMsgBase: + + @classmethod + def new_msg(cls): + return cls() + + def set_ip_address(self, server_ip, local_ip): + pass + + def to_send_data(self): + return "", {} + + +class WxAccountMsg(WxAccountMsgBase): + def __init__(self): + self.ip_address: str = "" + self.thing_type: str = "" + self.msg: str = "" + self.next_msg: str = "" + + def set_ip_address(self, server_ip, local_ip): + self.ip_address = "{}({})".format(server_ip, local_ip) + if len(self.ip_address) > 32: + self.ip_address = self.ip_address[:29] + "..." + + def to_send_data(self): + res = { + "first": {}, + "keyword1": { + "value": self.ip_address, + }, + "keyword2": { + "value": self.thing_type, + }, + "keyword3": { + "value": self.msg, + } + } + + if self.next_msg != "": + res["keyword4"] = {"value": self.next_msg} + + return "", res + + +class WxAccountLoginMsg(WxAccountMsgBase): + tid = "RJNG8dBZ5Tb9EK6j6gOlcAgGs2Fjn5Fb07vZIsYg1P4" + + def __init__(self): + self.login_name: str = "" + self.login_ip: str = "" + self.thing_type: str = "" + self.login_type: str = "" + self.address: str = "" + self._server_name: str = "" + + def set_ip_address(self, server_ip, local_ip): + if self._server_name == "": + self._server_name = "服务器IP{}".format(server_ip) + + def _get_server_name(self): + data = get_config_value("title") # 若获得别名,则使用别名 + if data != "": + self._server_name = data + + def to_send_data(self): + self._get_server_name() + if self.address.startswith(">归属地:"): + self.address = self.address[5:] + if self.address == "": + self.address = "未知的归属地" + + if not _is_ipv4(self.login_ip): + self.login_ip = "ipv6-can not show" + + res = { + "thing10": { + "value": self._server_name, + }, + "character_string9": { + "value": self.login_ip, + }, + "thing7": { + "value": self.login_type, + }, + "thing11": { + "value": self.address, + }, + "thing2": { + "value": self.login_name, + } + } + return self.tid, res + + +# 处理短信告警信息的不规范问题 +def sms_msg_normalize(sm_args: dict) -> dict: + for key, val in sm_args.items(): + sm_args[key] = _norm_sms_push_argv(str(val)) + return sm_args + + +def _norm_sms_push_argv(data): + """ + @处理短信参数,否则会被拦截 + """ + if _is_ipv4(data): + tmp1 = data.split('.') + return '{}_***_***_{}'.format(tmp1[0], tmp1[3]) + + data = data.replace(".", "_").replace("+", "+") + return data + + +def _is_ipv4(data: str) -> bool: + try: + ipaddress.IPv4Address(data) + except: + return False + return True + + +def _is_domain(domain): + rep_domain = re.compile(r"^([\w\-*]{1,100}\.){1,10}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$") + if rep_domain.match(domain): + return True + return False diff --git a/mod/base/push_mod/site_push.py b/mod/base/push_mod/site_push.py new file mode 100644 index 00000000..f3f8bf12 --- /dev/null +++ b/mod/base/push_mod/site_push.py @@ -0,0 +1,1290 @@ +import glob +import hashlib +import json +import os +import re +import sys +import time + +import psutil +from datetime import datetime +from importlib import import_module +from typing import Tuple, Union, Optional, List + +from .send_tool import WxAccountMsg, WxAccountLoginMsg +from .base_task import BaseTask +from .mods import PUSH_DATA_PATH, TaskConfig, SenderConfig +from .util import read_file, DB, write_file, GET_CLASS, ExecShell, get_config_value, public_get_cache_func, \ + public_set_cache_func, get_network_ip, public_get_user_info, public_http_post, panel_version +from mod.base.web_conf import RealSSLManger + + +class _WebInfo: + + def __init__(self): + self.last_time = 0 + self._items = None + self._items_by_type = None + + def __call__(self): + if self._items is not None and self.last_time > time.time() - 300: + return self._items, self._items_by_type + + items = [] + items_by_type = [[], [], [], [], []] + + res_list = DB('sites').field('id,name,project_type').select() + for i in res_list: + items.append({ + "title": i["name"] + "[" + i["project_type"] + "]", + "value": i["name"] + }) + + if i["project_type"] == "PHP" or i["project_type"] == "proxy": + continue + idx: int = ProjectStatusTask._to_project_id(i["project_type"]) + if idx is None: + continue + items_by_type[idx].append({ + "title": i["name"], + "value": i["id"] + }) + + self._items = items + self._items_by_type = items_by_type + return items, items_by_type + + +web_info = _WebInfo() + + +class SSLTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "site_ssl" + self.template_name = "网站证书(SSL)到期" + self._tip_file = "{}/site_ssl.tip".format(PUSH_DATA_PATH) + self._tip_data: Optional[dict] = None + self._task_config = TaskConfig() + + # 每次任务使用 + self.ssl_list = [] + self.push_keys = [] + self.task_id = None + + @property + def tips(self) -> dict: + if self._tip_data is not None: + return self._tip_data + try: + self._tip_data = json.loads(read_file(self._tip_file)) + except: + self._tip_data = {} + return self._tip_data + + def save_tip(self): + write_file(self._tip_file, json.dumps(self.tips)) + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + # 过滤单独设置提醒的网站 + not_push_web = [i["task_data"]["project"] for i in self._task_config.config if i["source"] == self.source_name] + sql = DB("sites") + total = self._task_config.get_by_id(task_id).get("number_rule", {}).get("total", 1) + if "all" in not_push_web: + not_push_web.remove("all") + + need_check_list = [] + if task_data["project"] == "all": + # 所有正常网站 + web_list = sql.where('status=1', ()).select() + for web in web_list: + if web['name'] in not_push_web: + continue + if self.tips.get(task_id, {}).get(web['name'], 0) > total: + continue + + if not web['project_type'].lower() in ['php', 'proxy']: + project_type = web['project_type'].lower() + '_' + else: + project_type = '' + + need_check_list.append((web['name'], project_type)) + + else: + find = sql.where('name=? and status=1', (task_data['project'],)).find() + if not find: + return None + + if not find['project_type'].lower() in ['php', 'proxy']: + project_type = find['project_type'].lower() + '_' + else: + project_type = '' + + need_check_list.append((find['name'], project_type)) + + for name, project_type in need_check_list: + info = self._check_ssl_end_time(name, task_data['cycle'], project_type) + if isinstance(info, dict): # 返回的是详情,说明需要推送了 + info['site_name'] = name + self.push_keys.append(name) + self.ssl_list.append(info) + + if len(self.ssl_list) == 0: + return None + + s_list = ['>即将到期:{} 张'.format(len(self.ssl_list))] + for x in self.ssl_list: + s_list.append(">网站:{} 到期:{}".format(x['site_name'], x['notAfter'])) + + self.task_id = task_id + return {"msg_list": s_list} + + @staticmethod + def _check_ssl_end_time(site_name, limit, prefix) -> Optional[dict]: + info = RealSSLManger(conf_prefix=prefix).get_site_ssl_info(site_name) + if info is not None: + end_time = datetime.strptime(info['notAfter'], '%Y-%m-%d') + if int((end_time.timestamp() - time.time()) / 86400) <= limit: + return info + return None + + def get_title(self, task_data: dict) -> str: + if task_data["project"] == "all": + return "所有网站证书(SSL)到期提醒" + return "网站[{}]证书(SSL)到期提醒".format(task_data["project"]) + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return 'ssl_end|宝塔面板SSL到期提醒', { + "name": push_public_data["ip"], + "website": self.ssl_list[0]['site_name'], + 'time': self.ssl_list[0]["notAfter"], + 'total': len(self.ssl_list) + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "网站SSL到期提醒" + msg.msg = "有{}个网站的证书将到期,会影响访问".format(len(self.ssl_list)) + msg.next_msg = "请登录面板,在[网站]中进行续签操作" + return msg + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天 + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] > 1): + return "剩余时间参数错误,至少为1天" + return task_data + + def filter_template(self, template) -> dict: + items, _ = web_info() + template["field"][0]["items"].extend(items) + return template + + def check_num_rule(self, num_rule: dict) -> Union[dict, str]: + num_rule["get_by_func"] = "can_send_by_num_rule" + return num_rule + + # 实际的次数检查已在 get_push_data 其他位置完成 + def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]: + return None + + def task_run_end_hook(self, res) -> None: + if not res["do_send"]: + return + if self.task_id: + if self.task_id not in self.tips: + self.tips[self.task_id] = {} + + for w in self.push_keys: + if w in self.tips[self.task_id]: + self.tips[self.task_id][w] += 1 + else: + self.tips[self.task_id][w] = 1 + + self.save_tip() + + def task_config_update_hook(self, task: dict) -> None: + if task["id"] in self.tips: + self.tips.pop(task["id"]) + self.save_tip() + + def task_config_remove_hook(self, task: dict) -> None: + if task["id"] in self.tips: + self.tips.pop(task["id"]) + self.save_tip() + + +class SiteEndTimeTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "site_end_time" + self.template_name = "站点到期提醒" + self.title = "站点到期提醒" + self._tip_file = "{}/site_end_time.tip".format(PUSH_DATA_PATH) + self._tip_data: Optional[dict] = None + self._task_config = TaskConfig() + + self.push_keys = [] + self.task_id = None + + @property + def tips(self) -> dict: + if self._tip_data is not None: + return self._tip_data + try: + self._tip_data = json.loads(read_file(self._tip_file)) + except: + self._tip_data = {} + return self._tip_data + + def save_tip(self): + write_file(self._tip_file, json.dumps(self.tips)) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天 + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] > 1): + return "剩余时间参数错误,至少为1天" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "site_end_time" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + m_end_date = time.strftime('%Y-%m-%d', time.localtime(time.time() + 86400 * int(task_data['cycle']))) + web_list = DB('sites').where( + 'edate>? AND edate= 1): + return None + + total = self._task_config.get_by_id(task_id).get("number_rule", {}).get("total", 1) + s_list = ['>即将到期:{} 个站点'.format(len(web_list))] + for x in web_list: + if self.tips.get(x['name'], 0) >= total: + continue + self.push_keys.append(x['name']) + s_list.append(">网站:{} 到期:{}".format(x['name'], x[' edate'])) + + if not self.push_keys: + return None + + self.task_id = task_id + + return { + "msg_list": s_list + } + + def check_num_rule(self, num_rule: dict) -> Union[dict, str]: + num_rule["get_by_func"] = "can_send_by_num_rule" + return num_rule + + # 实际的次数检查已在 get_push_data 其他位置完成 + def can_send_by_num_rule(self, task_id: str, task_data: dict, number_rule: dict, push_data: dict) -> Optional[str]: + return None + + def filter_template(self, template) -> dict: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "网站到期提醒" + msg.msg = "有{}个站点即将到期,可能影响网站访问".format(len(self.push_keys)) + msg.next_msg = "请登录面板,在[网站]中查看详情" + return msg + + def task_run_end_hook(self, res) -> None: + if not res["do_send"]: + return + if self.push_keys: + for w in self.push_keys: + if w in self.tips: + self.tips[w] += 1 + else: + self.tips[w] = 1 + self.save_tip() + + def task_config_update_hook(self, task: dict) -> None: + if os.path.exists(self._tip_file): + os.remove(self._tip_file) + + def task_config_remove_hook(self, task: dict) -> None: + if os.path.exists(self._tip_file): + os.remove(self._tip_file) + + +class PanelPwdEndTimeTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "panel_pwd_end_time" + self.template_name = "面板密码有效期" + self.title = "面板密码有效期" + + self.limit_days = 0 + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + task_data["interval"] = 60 * 60 * 24 # 默认检测间隔时间 1 天 + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] > 1): + return "剩余时间参数错误,至少为1天" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "pwd_end_time" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + import config + c_obj = config.config() + res = c_obj.get_password_config(None) + if res['expire'] > 0 and res['expire_day'] < task_data['cycle']: + self.limit_days = res['expire_day'] + + s_list = [">告警类型:登录密码即将过期", + ">剩余天数:{} 天".format(res['expire_day'])] + + return { + 'msg_list': s_list + } + + return None + + def filter_template(self, template) -> dict: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', dict() + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "面板密码到期提醒" + msg.msg = "登录密码将于{}天后过期".format(self.limit_days) + msg.next_msg = "请登录面板,在[设置]中修改密码" + return msg + + +class PanelLoginTask(BaseTask): + push_tip_file = "/www/server/panel/data/panel_login_send.pl" + + def __init__(self): + import public + public.print_log("panel_login") + super().__init__() + self.source_name = "panel_login" + self.template_name = "面板登录告警" + self.title = "面板登录告警" + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + return {} + + def get_keyword(self, task_data: dict) -> str: + return "panel_login" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + return None + + def filter_template(self, template) -> dict: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return "login_panel|面板登录提醒", { + 'name': '[' + push_data.get("ip") + ']', + 'time': time.strftime('%Y-%m-%d %X', time.localtime()), + 'type': '[' + push_data.get("is_type") + ']', + 'user': push_data.get("username") + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountLoginMsg.new_msg() + msg.thing_type = "面板登录提醒" + msg.login_name = push_data.get("username") + msg.login_ip = push_data.get("login_ip") + msg.login_type = push_data.get("is_type") + msg.address = push_data.get("login_ip_area") + return msg + + def task_config_update_hook(self, task: dict) -> None: + import public + public.print_log(4444444444444) + sender = task["sender"] + if len(sender) > 0: + send_id = sender[0] + else: + return + + sender_data = SenderConfig().get_by_id(send_id) + if sender_data: + write_file(self.push_tip_file, sender_data["sender_type"]) + + def task_config_create_hook(self, task: dict) -> None: + import public + public.print_log(444444433333) + sender = task["sender"] + if len(sender) > 0: + send_id = sender[0] + else: + return + + sender_data = SenderConfig().get_by_id(send_id) + if sender_data: + write_file(self.push_tip_file, sender_data["sender_type"]) + + def task_config_remove_hook(self, task: dict) -> None: + import public + public.print_log(33333333333333333333333) + if os.path.exists(self.push_tip_file): + os.remove(self.push_tip_file) + + +class SSHLoginErrorTask(BaseTask): + _months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06', 'Jul': '07', 'Aug': '08', + 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'} + + def __init__(self): + super().__init__() + self.source_name = "ssh_login_error" + self.template_name = "SSH登录失败告警" + self.title = "SSH登录失败告警" + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1): + return "时间长度参数错误,至少为1分钟" + if not (isinstance(task_data['count'], int) and task_data['count'] >= 1): + return "数量参数错误,至少为1次" + if not (isinstance(task_data['interval'], int) and task_data['interval'] >= 60): + return "间隔时间参数错误,至少为60秒" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "ssh_login_error" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + import PluginLoader + args = GET_CLASS() + args.model_index = 'safe' + args.count = task_data['count'] + args.p = 1 + res = PluginLoader.module_run("syslog", "get_ssh_error", args) + if 'status' in res: + return None + + last_info = res[task_data['count'] - 1] + if self.to_date(times=last_info['time']) >= time.time() - task_data['cycle'] * 60: + s_list = [">通知类型:SSH登录失败告警", + ">告警内容:{} 分钟内登录失败超过 {} 次 ".format( + task_data['cycle'], task_data['count'])] + + return { + 'msg_list': s_list, + 'count': task_data['count'] + } + + return None + + @staticmethod + def to_date(times, fmt_str="%Y-%m-%d %H:%M:%S"): + if times: + if isinstance(times, int): + return times + if isinstance(times, float): + return int(times) + if re.match(r"^\d+$", times): + return int(times) + else: + return 0 + ts = time.strptime(times, fmt_str) + return time.mktime(ts) + + def filter_template(self, template) -> dict: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', dict() + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "SSH登录失败告警" + msg.msg = "登录失败超过{}次".format(push_data['count']) + msg.next_msg = "请登录面板,查看SSH登录日志" + return msg + + +class ServicesTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "services" + self.template_name = "服务停止告警" + + self.pids = None + + self.service_name = '' + self.restart = None + + @staticmethod + def services_list() -> Tuple[str, List]: + res_list = [] + default = None + php_path = "/www/server/php" + if os.path.exists(php_path) and glob.glob(php_path + "/*"): + res_list.append({ + "title": "php-fpm服务停止", + "value": "php-fpm" + }) + if os.path.exists('/etc/init.d/httpd'): + default = "apache" + res_list.append({ + "title": "apache服务停止", + "value": "apache" + }) + if os.path.exists('/etc/init.d/nginx'): + default = "nginx" + res_list.append({ + "title": "nginx服务停止", + "value": "nginx" + }) + if os.path.exists('/etc/init.d/mysqld'): + res_list.append({ + "title": "mysql服务停止", + "value": "mysql" + }) + if os.path.exists('/www/server/tomcat/bin'): + res_list.append({ + "title": "tomcat服务停止", + "value": "tomcat" + }) + if os.path.exists('/etc/init.d/pure-ftpd'): + res_list.append({ + "title": "pure-ftpd服务停止", + "value": "pure-ftpd" + }) + if os.path.exists('/www/server/redis'): + res_list.append({ + "title": "redis服务停止", + "value": "redis" + }) + if os.path.exists('/etc/init.d/memcached'): + res_list.append({ + "title": "memcached服务停止", + "value": "memcached" + }) + if not default: + default = res_list[0]["value"] + return default, res_list + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + default, s_list = self.services_list() + if task_data["project"] not in {i["value"] for i in s_list}: + return "所选择的服务不存在" + if task_data["count"] not in (1, 2): + return "自动重启选择错误" + if not (isinstance(task_data['interval'], int) and task_data['interval'] >= 60): + return "间隔时间参数错误,至少为60秒" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + self.title = self.get_title(task_data) + ser_name = task_data['project'] + default, server_list = self.services_list() + if ser_name not in [v["value"] for v in server_list]: + return None + if self.get_server_status(ser_name): + return None + + s_list = [ + ">服务类型:" + task_data["project"], + ">服务状态:【" + task_data["project"] + "】服务已停止"] + + self.service_name = task_data["project"] + + if task_data["count"] == 1: + self._services_start(task_data["project"]) + if not self.get_server_status(task_data["project"]): + self.restart = False + s_list[1] = ">服务状态:【" + task_data["project"] + "】服务重启失败" + else: + self.restart = True + s_list[1] = ">服务状态:【" + task_data["project"] + "】服务重启成功" + + return { + "msg_list": s_list + } + + def get_title(self, task_data: dict) -> str: + return task_data["project"] + "服务停止告警" + + @staticmethod + def _services_start(service_name: str): + if service_name == "php-fpm": + base_path = "/www/server/php" + if not os.path.exists(base_path): + return None + for p in os.listdir(base_path): + init_file = os.path.join("/etc/init.d", "php-fpm-{}".format(p)) + if not os.path.isfile(init_file): + return None + ExecShell("{} start".format(init_file)) + elif service_name == 'mysql': + init_file = os.path.join("/etc/init.d", "mysqld") + ExecShell("{} start".format(init_file)) + + elif service_name == 'apache': + init_file = os.path.join("/etc/init.d", "httpd") + ExecShell("{} start".format(init_file)) + + else: + init_file = os.path.join("/etc/init.d", service_name) + ExecShell("{} start".format(init_file)) + + def get_pid_name(self, pname): + try: + if not self.pids: + self.pids = psutil.pids() + for pid in self.pids: + if psutil.Process(pid).name() == pname: return True + return False + except: + return True + + def get_server_status(self, name: str) -> bool: + time.sleep(5) + if name == "php-fpm": + base_path = "/www/server/php" + if not os.path.exists(base_path): + return False + for p in os.listdir(base_path): + pid_file = os.path.join(base_path, p, "var/run/php-fpm.pid") + if os.path.exists(pid_file): + php_pid = int(read_file(pid_file)) + status = self.check_process(php_pid) + if status: + return True + return False + + elif name == 'nginx': + if os.path.exists('/etc/init.d/nginx'): + pid_f = '/www/server/nginx/logs/nginx.pid' + if os.path.exists(pid_f): + try: + pid = read_file(pid_f) + return self.check_process(pid) + except: + pass + return False + + elif name == 'apache': + if os.path.exists('/etc/init.d/httpd'): + pid_f = '/www/server/apache/logs/httpd.pid' + if os.path.exists(pid_f): + pid = read_file(pid_f) + return self.check_process(pid) + return False + + elif name == 'mysql': + if os.path.exists('/tmp/mysql.sock'): + return True + return False + + elif name == 'tomcat': + status = False + if os.path.exists('/www/server/tomcat/logs/catalina-daemon.pid'): + if self.get_pid_name('jsvc'): + status = True + if not status: + if self.get_pid_name('java'): + status = True + return status + + elif name == 'pure-ftpd': + pid_f = '/var/run/pure-ftpd.pid' + if os.path.exists(pid_f): + pid = read_file(pid_f) + return self.check_process(pid) + return False + + elif name == 'redis': + pid_f = '/www/server/redis/redis.pid' + if os.path.exists(pid_f): + pid = read_file(pid_f) + return self.check_process(pid) + return False + + elif name == 'memcached': + pid_f = '/var/run/memcached.pid' + if os.path.exists(pid_f): + pid = read_file(pid_f) + return self.check_process(pid) + return False + + return True + + def check_process(self, pid): + try: + if not self.pids: + self.pids = psutil.pids() + if int(pid) in self.pids: + return True + return False + except Exception as e: + return False + + def filter_template(self, template: dict) -> Optional[dict]: + default, server_list = self.services_list() + if not server_list: + return None + template["field"][0]["items"] = server_list + template["field"][0]["default"] = default + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return "servcies|{}".format(self.title), { + 'name': '{}'.format(get_config_value('title')), + 'product': self.service_name, + 'product1': self.service_name + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + if len(self.service_name) > 14: + service_name = self.service_name[:11] + "..." + else: + service_name = self.service_name + msg.thing_type = "{}服务停止提醒".format(service_name) + if self.restart is None: + msg.msg = "{}服务已停止".format(service_name) + elif self.restart is True: + msg.msg = "{}服务重启成功".format(service_name) + else: + msg.msg = "{}服务重启失败".format(service_name) + return msg + + +class PanelSafePushTask(BaseTask): + def __init__(self): + super().__init__() + self.source_name = "panel_safe_push" + self.template_name = "面板安全告警" + self.title = "面板安全告警" + + self.msg_list = [] + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + task_data["interval"] = 60 + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "panel_safe_push" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + s_list = [] + # 面板登录用户安全 + t_add, t_del, total = self.get_records_calc('login_user_safe', DB('users')) + if t_add > 0 or t_del > 0: + s_list.append( + ">登录用户变更:总 {} 个,新增 {} 个 ,删除 {} 个.".format(total, t_add, t_del)) + + # 面板日志发生删除 + t_add, t_del, total = self.get_records_calc('panel_logs_safe', DB('logs'), 1) + if t_del > 0: + s_list.append(">面板日志发生删除,删除条数:{} 条".format(t_del)) + + debug_str = '关闭' + debug_status = 'False' + # 面板开启开发者模式告警 + if os.path.exists('/www/server/panel/data/debug.pl'): + debug_status = 'True' + debug_str = '开启' + + skey = 'panel_debug_safe' + tmp = public_get_cache_func(skey)['data'] + if not tmp: + public_set_cache_func(skey, debug_status) + else: + if str(debug_status) != tmp: + s_list.append(">面板开发者模式发生变更,当前状态:{}".format(debug_str)) + public_set_cache_func(skey, debug_status) + + # 面板用户名和密码发生变更 + find = DB('users').where('id=?', (1,)).find() + if find: + skey = 'panel_user_change_safe' + user_str = self.hash_md5(find['username']) + '|' + self.hash_md5(find['password']) + tmp = public_get_cache_func(skey)['data'] + if not tmp: + public_set_cache_func(skey, user_str) + else: + if user_str != tmp: + s_list.append(">面板登录帐号或密码发生变更") + public_set_cache_func(skey, user_str) + + if len(s_list) == 0: + return None + self.msg_list = s_list + return {"msg_list": s_list} + + @staticmethod + def hash_md5(data: str) -> str: + h = hashlib.md5() + h.update(data.encode('utf-8')) + return h.hexdigest() + + @staticmethod + def get_records_calc(skey, table, stype=0): + """ + @name 获取指定表数据是否发生改变 + @param skey string 缓存key + @param table db 表对象 + @param stype : 0 计算总条数 1 只计算删除 + @return array + total int 总数 + """ + total_add = 0 + total_del = 0 + + # 获取当前总数和最大索引值 + u_count = table.count() + u_max = table.order('id desc').getField('id') + + n_data = {'count': u_count, 'max': u_max} + tmp = public_get_cache_func(skey)['data'] + if not tmp: + public_set_cache_func(skey, n_data) + else: + n_data = tmp + # 检测上一次记录条数是否被删除 + pre_count = table.where('id<=?', (n_data['max'])).count() + if stype == 1: + if pre_count < n_data['count']: # 有数据被删除,记录被删条数 + total_del += n_data['count'] - pre_count + + n_count = u_max - pre_count # 上次记录后新增的条数 + n_idx = u_max - n_data['max'] # 上次记录后新增的索引差 + if n_count < n_idx: + total_del += n_idx - n_count + else: + + if pre_count < n_data['count']: # 有数据被删除,记录被删条数 + total_del += n_data['count'] - pre_count + elif pre_count > n_data['count']: + total_add += pre_count - n_data['count'] + + t1_del = 0 + n_count = u_count - pre_count # 上次记录后新增的条数 + + if u_max > n_data['max']: + n_idx = u_max - n_data['max'] # 上次记录后新增的索引差 + if n_count < n_idx: t1_del = n_idx - n_count + + # 新纪录除开删除,全部计算为新增 + t1_add = n_count - t1_del + if t1_add > 0: + total_add += t1_add + + total_del += t1_del + + public_set_cache_func(skey, {'count': u_count, 'max': u_max}) + return total_add, total_del, u_count + + def filter_template(self, template: dict) -> Optional[dict]: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "面板安全告警" + the_msg = [] + for d in self.msg_list: + if d.find("用户变更"): + the_msg.append("用户变更") + if d.find("日志发生删除"): + the_msg.append("面板日志删除") + if d.find("开发者模式"): + the_msg.append("开发者模式变更") + if d.find("登录帐号或密码"): + the_msg.append("帐号密码变更") + + msg.msg = "、".join(the_msg) + if len(the_msg) > 20: + msg.msg = msg.msg[:17] + "..." + msg.next_msg = "请登录面板,查看对应事项" + return msg + + +class SSHLoginTask(BaseTask): + push_tip_file = "/www/server/panel/data/ssh_send_type.pl" + + def __init__(self): + super().__init__() + self.source_name = "ssh_login" + self.template_name = "SSH登录告警" + self.title = "SSH登录告警" + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + return {} + + def get_keyword(self, task_data: dict) -> str: + return "ssh_login" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + return None + + def filter_template(self, template) -> dict: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return "", {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + login_ip = push_data.get("login_ip") + msg = WxAccountMsg.new_msg() + msg.thing_type = "SSH登录安全告警" + if len(login_ip) == 0: # 检查后门用户时使同 + msg.msg = "服务器存在后门用户" + msg.next_msg = "请检查/ect/passwd文件" + return msg + + elif len(login_ip) > 15: + login_ip = login_ip[:12] + "..." + + msg.msg = "登录ip:{}".format(login_ip) + msg.next_msg = "请登录面板,检查是否为安全登录" + return msg + + def task_config_update_hook(self, task: dict) -> None: + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + from ssh_security import ssh_security + ssh_security().start_jian(None) + + sender = task["sender"] + if len(sender) > 0: + send_id = sender[0] + else: + return + + sender_data = SenderConfig().get_by_id(send_id) + if sender_data: + write_file(self.push_tip_file, sender_data["sender_type"]) + + def task_config_create_hook(self, task: dict) -> None: + return self.task_config_update_hook(task) + + def task_config_remove_hook(self, task: dict) -> None: + if os.path.exists(self.push_tip_file): + os.remove(self.push_tip_file) + + +class PanelUpdateTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "panel_update" + self.template_name = "面板更新提醒" + self.title = "面板更新提醒" + self.new_ver = '' + + def _get_no_user_tip(self) -> str: + """没有用户信息的需要,写一个临时文件做标记,并尽可能保持不变""" + tip_file = "/www/server/panel/data/no_user_tip.pl" + if not os.path.exists(tip_file): + data: str = get_network_ip() + data = "没有用户信息时的标记文件\n" + hashlib.sha256(data.encode("utf-8")).hexdigest() + write_file(tip_file, data) + else: + data = read_file(tip_file) + if isinstance(data, bool): + os.remove(tip_file) + return self._get_no_user_tip() + return data + + def user_can_request_hour(self): + """根据哈希值,输出一个用户可查询""" + user_info = public_get_user_info() + if not bool(user_info): + user_info_str = self._get_no_user_tip() + else: + user_info_str = json.dumps(user_info) + + hash_value = hashlib.md5(user_info_str.encode("utf-8")).digest() + sum_value = 0 + for i in range(4): + sum_value = sum_value + int.from_bytes(hash_value[i * 32: (i + 1) * 32], "big") + + res = sum_value % 24 + return res + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + task_data["interval"] = 60 * 60 # 默认检测间隔时间 1 小时 + return task_data + + def check_num_rule(self, num_rule: dict) -> Union[dict, str]: + num_rule['day_num'] = 1 # 默认一天发一次 + return num_rule + + def get_keyword(self, task_data: dict) -> str: + return "panel_update" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + # 不在固定时间段内,跳过 + if self.user_can_request_hour() != datetime.now().hour: + return + + s_url = 'https://www.bt.cn/api/panel/updateLinux' + try: + res = json.loads(public_http_post(s_url, {})) + if not res: + return None + except: + return None + + n_ver = res['version'] + if res['is_beta']: + n_ver = res['beta']['version'] + + self.new_ver = n_ver + + cache_key = "panel_update_cache" + old_ver = public_get_cache_func(cache_key)['data'] + if old_ver and old_ver != n_ver: + s_list = [">通知类型:面板版本更新", + ">当前版本:{} ".format(panel_version()), + ">最新版本:{}".format(n_ver)] + return { + "msg_list": s_list + } + else: + public_set_cache_func(cache_key, n_ver) + return None + + def filter_template(self, template: dict) -> Optional[dict]: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return "", {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "面板更新提醒" + msg.msg = "最新版:{}已发布".format(self.new_ver) + msg.next_msg = "您可以登录面板,执行更新" + return msg + + def task_run_end_hook(self, res: dict) -> None: + if res["do_send"]: + public_set_cache_func("panel_update_cache", self.new_ver) + + +class ProjectStatusTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "project_status" + self.template_name = "项目停止告警" + + self.project_name = '' + self.restart = None + + @staticmethod + def _to_project_type(type_id: int): + if type_id == 1: + return "Java" + if type_id == 2: + return "Node" + if type_id == 3: + return "Go" + if type_id == 4: + return "Python" + if type_id == 5: + return "Other" + + @staticmethod + def _to_project_id(type_name): + if type_name == "Java": + return 0 + if type_name == "Node": + return 1 + if type_name == "Go": + return 2 + if type_name == "Python": + return 3 + if type_name == "Other": + return 4 + + @staticmethod + def _to_project_model(type_id: int): + if type_id == 1: + return "javaModel" + if type_id == 2: + return "nodejsModel" + if type_id == 3: + return "goModel" + if type_id == 4: + return "pythonModel" + if type_id == 5: + return "otherModel" + + def get_title(self, task_data: dict) -> str: + return "项目{}停止告警".format(self._get_project_name(task_data["project"])) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not (isinstance(task_data["cycle"], int) and 1 <= task_data["cycle"] <= 5): + return '不支持的项目类型.' + sql = DB("sites") + web_info = sql.where( + "project_type = ? and id = ?", + (self._to_project_type(task_data["cycle"]), task_data["project"]) + ).field("id,name").find() + + if not web_info: + return '没有该项目,不可设置告警' + + if task_data["count"] not in (1, 2): + return "自动重启选择错误" + if not (isinstance(task_data['interval'], int) and task_data['interval'] >= 60): + return "间隔时间参数错误,至少为60秒" + return task_data + + def get_web_list(self) -> List: + items_by_type = [[], [], [], [], []] + res_list = DB('sites').field('id,name,project_type').select() + for i in res_list: + if i["project_type"] == "PHP" or i["project_type"] == "proxy": + continue + idx: int = self._to_project_id(i["project_type"]) + if idx is None: + continue + items_by_type[idx].append({ + "title": i["name"], + "value": i["id"] + }) + return items_by_type + + def get_keyword(self, task_data: dict) -> str: + return "{}_{}".format(task_data["cycle"], self._get_project_name(task_data["project"])) + + @staticmethod + def _get_project_name(project_id: int) -> str: + data = DB('sites').where('id = ?', (project_id,)).field('id,name').find() + if isinstance(data, dict): + return data["name"] + return "" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + model_obj = import_module(".{}".format(self._to_project_model(task_data["cycle"])), package="projectModel") + model_main_obj = model_obj.main() + running, project_name = getattr(model_main_obj, "get_project_status")(task_data["project"]) + if running is not False: + return None + + s_list = [ + ">项目类型:" + self._to_project_type(task_data["cycle"]) + "项目", + ">项目名称:" + project_name, + ">项目状态:检查到项目状态为停止"] + self.project_name = project_name + + if int(task_data["count"]) == 1: + get_obj = GET_CLASS() + get_obj.project_name = project_name + result = getattr(model_main_obj, "start_project")(get_obj) + if result["status"] is True: + self.restart = True + s_list[2] = ">项目状态:检查到项目状态为停止,现已重启成功" + else: + self.restart = False + s_list[2] = ">项目状态:检查到项目状态为停止,尝试重启但失败" + + self.title = self.get_title(task_data) + + return { + "msg_list": s_list, + } + + def filter_template(self, template: dict) -> Optional[dict]: + _, web_by_type = web_info() + template["field"][1]["all_items"] = web_by_type + template["field"][1]["items"] = web_by_type[0] + if not web_by_type: + return None + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + if len(self.project_name) >= 14: + project_name = self.project_name[:11] + "..." + else: + project_name = self.project_name + msg.thing_type = "项目停止告警" + if self.restart is None: + msg.msg = "项目{}已停止".format(project_name) + elif self.restart is True: + msg.msg = "项目{}重启成功".format(project_name) + else: + msg.msg = "项目{}重启失败".format(project_name) + return msg + + +class ViewMsgFormat(object): + _FORMAT = { + "1": ( + lambda x: "剩余时间小于{}天{}".format( + x["task_data"].get("cycle"), + ("(如未处理,次日会重新发送1次,持续%d天)" % x.get("number_rule", {}).get("total", 0)) if x.get("number_rule", {}).get("total", 0) else "" + ) + ), + "2": (), + "3": (), + "8": ( + lambda x: "面板登录时,发出告警" + ), + "7": ( + lambda x: "检测到SSH登录本机时,发出告警" + ), + "4": ( + lambda x: "{}分钟内连续{}次失败登录触发,每{}秒后再次检测".format( + x["task_data"].get("cycle"), x["task_data"].get("count"), x["task_data"].get("interval"), + ) + ), + "5": ( + lambda x: "服务停止时发送一次通知,{}秒后再次检测".format(x["task_data"].get("interval")) + ), + "9": ( + lambda x: "项目停止时发送通知,{}秒后再次检测,每日发送{}次".format( + x["task_data"].get("interval"), + x.get("number_rule", {}).get("day_num", 0)) + ), + "6": ( + lambda x: "面板出现如:用户变更、面板日志删除、开启开发者等危险操作时发送告警" + ), + "10": ( + lambda x: "检测到新的版本时发送一次通知" + ) + } + + def get_msg(self, task: dict) -> Optional[str]: + if task["template_id"] in ["1", "2", "3"]: + return self._FORMAT["1"](task) + if task["template_id"] in self._FORMAT: + return self._FORMAT[task["template_id"]](task) + return None diff --git a/mod/base/push_mod/site_push_template.json b/mod/base/push_mod/site_push_template.json new file mode 100644 index 00000000..539390d7 --- /dev/null +++ b/mod/base/push_mod/site_push_template.json @@ -0,0 +1,571 @@ +[ + { + "id": "1", + "ver": "1", + "used": true, + "source": "site_ssl", + "title": "网站证书(SSL)到期", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "SSLTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "网站", + "type": "select", + "default": "all", + "items": [ + { + "title": "所有网站", + "value": "all" + } + ] + }, + { + "attr": "cycle", + "name": "剩余天数", + "type": "number", + "suffix": "", + "unit": "天", + "default": 15 + } + ], + "sorted": [ + [ + "project" + ], + [ + "cycle" + ] + ] + }, + "default": { + "project": "all", + "cycle": 15 + }, + "advanced_default": { + "number_rule": { + "total": 2 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook", + "sms" + ], + "unique": false + }, + { + "id": "2", + "ver": "1", + "used": true, + "source": "site_end_time", + "title": "网站到期", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "SiteEndTimeTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "剩余天数", + "type": "number", + "unit": "天", + "suffix": "", + "default": 7 + } + ], + "sorted": [ + [ + "cycle" + ] + ] + }, + "default": { + "cycle": 7 + }, + "advanced_default": { + "number_rule": { + "total": 2 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": true + }, + { + "id": "3", + "ver": "1", + "used": true, + "source": "panel_pwd_end_time", + "title": "面板密码有效期", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "PanelPwdEndTimeTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "剩余天数", + "type": "number", + "unit": "天", + "suffix": "", + "default": 15 + } + ], + "sorted": [ + [ + "cycle" + ] + ] + }, + "default": { + "cycle": 15 + }, + "advanced_default": { + "number_rule": { + "total": 2 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": true + }, + { + "id": "4", + "ver": "1", + "used": true, + "source": "ssh_login_error", + "title": "SSH登录失败告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "SSHLoginErrorTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "触发条件", + "type": "number", + "unit": "分钟", + "suffix": "内,", + "default": 30 + }, + { + "attr": "count", + "name": "登录失败", + "type": "number", + "unit": "次", + "suffix": "", + "default": 3 + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + } + ], + "sorted": [ + [ + "cycle", + "count" + ], + [ + "interval" + ] + ] + }, + "default": { + "cycle": 30, + "count": 3, + "interval": 600 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + }, + "time_rule": { + "send_interval": 600 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": true + }, + { + "id": "5", + "ver": "1", + "used": true, + "source": "services", + "title": "服务停止告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "ServicesTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "通知类型", + "type": "select", + "default": null, + "items": [ + ] + }, + { + "attr": "count", + "name": "自动重启", + "type": "radio", + "suffix": "", + "default": 1, + "items": [ + { + "title": "自动尝试重启项目", + "value": 1 + }, + { + "title": "不做重启尝试", + "value": 2 + } + ] + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + } + ], + "sorted": [ + [ + "project" + ], + [ + "count" + ], + [ + "interval" + ] + ] + }, + "default": { + "project": "", + "count": 2, + "interval": 600 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": false + }, + { + "id": "6", + "ver": "1", + "used": true, + "source": "panel_safe_push", + "title": "面板安全告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "PanelSafePushTask" + }, + "template": { + "field": [ + { + "attr": "help", + "name": "告警内容", + "type": "help", + "unit": "", + "style": { + "margin-top": "6px" + }, + "list": [ + "面板用户变更、面板日志删除、面板开启开发者" + ], + "suffix": "", + "default": 600 + } + ], + "sorted": [ + [ + "help" + ] + ] + }, + "default": { + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": true + }, + { + "id": "7", + "ver": "1", + "used": true, + "source": "ssh_login", + "title": "SSH登录告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "SSHLoginTask" + }, + "template": { + "field": [ + ], + "sorted": [ + ] + }, + "default": { + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": true + }, + { + "id": "8", + "ver": "1", + "used": true, + "source": "panel_login", + "title": "面板登录告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "PanelLoginTask" + }, + "template": { + "field": [ + ], + "sorted": [ + ] + }, + "default": { + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook", + "sms" + ], + "unique": true + }, + { + "id": "9", + "ver": "1", + "used": true, + "source": "project_status", + "title": "项目停止告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "ProjectStatusTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "项目类型", + "type": "select", + "default": 1, + "items": [ + { + "title": "Java项目", + "value": 1 + }, + { + "title": "Node项目", + "value": 2 + }, + { + "title": "Go项目", + "value": 3 + }, + { + "title": "Python项目", + "value": 4 + }, + { + "title": "其他项目", + "value": 5 + } + ] + }, + { + "attr": "project", + "name": "项目名称", + "type": "select", + "default": null, + "all_items": null, + "items": [ + ] + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + }, + { + "attr": "count", + "name": "自动重启", + "type": "radio", + "suffix": "", + "default": 1, + "items": [ + { + "title": "自动尝试重启项目", + "value": 1 + }, + { + "title": "不做重启尝试", + "value": 2 + } + ] + } + ], + "sorted": [ + [ + "cycle" + ], + [ + "project" + ], + [ + "interval" + ], + [ + "count" + ] + ] + }, + "default": { + "cycle": 1, + "project": "", + "interval": 600, + "count": 2 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": false + }, + { + "id": "10", + "ver": "1", + "used": true, + "source": "panel_update", + "title": "面板更新提醒", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.site_push", + "name": "PanelUpdateTask" + }, + "template": { + "field": [ + ], + "sorted": [ + ] + }, + "default": { + }, + "advanced_default": { + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": true + } +] + diff --git a/mod/base/push_mod/system.py b/mod/base/push_mod/system.py new file mode 100644 index 00000000..5d29f682 --- /dev/null +++ b/mod/base/push_mod/system.py @@ -0,0 +1,422 @@ +import os +import time +from typing import Optional, List, Tuple, Dict, Type, Any, Union +import datetime +from threading import Thread + +from .base_task import BaseTask +from .mods import TaskTemplateConfig, TaskConfig, TaskRecordConfig, SenderConfig +from .send_tool import sms_msg_normalize +from .tool import load_task_cls_by_path, load_task_cls_by_function, T_CLS +from .util import get_server_ip, get_network_ip, format_date, get_config_value +from .compatible import rsync_compatible + + +WAIT_TASK_LIST: List[Thread] = [] + + +class PushSystem: + + def __init__(self): + self.task_cls_cache: Dict[str, Type[T_CLS]] = {} + self._today_zero: Optional[datetime.datetime] = None + self._sender_type_class: Optional[dict] = {} + self.sd_cfg = SenderConfig() + + def sender_cls(self, sender_type: str): + if not self._sender_type_class: + from mod.base.msg import WeiXinMsg, MailMsg, WebHookMsg, FeiShuMsg, DingDingMsg, SMSMsg, WeChatAccountMsg + self._sender_type_class = { + "weixin": WeiXinMsg, + "mail": MailMsg, + "webhook": WebHookMsg, + "feishu": FeiShuMsg, + "dingding": DingDingMsg, + "sms": SMSMsg, + "wx_account": WeChatAccountMsg, + } + return self._sender_type_class[sender_type] + + @staticmethod + def can_run_task_list() -> Tuple[List[dict], Dict[int, dict]]: + result = [] + result_template = {} + task_template_ids = set() + for task in TaskConfig().config: + if not task["status"]: + continue + task_template_ids.add(task['template_id']) + # 间隔检测时间未到跳过 + if "interval" in task["task_data"] and isinstance(task["task_data"]["interval"], int): + if time.time() < task["last_check"] + task["task_data"]["interval"]: + continue + result.append(task) + + for template in TaskTemplateConfig().config: + if template["id"] not in task_template_ids: + continue + result_template[template['id']] = template + + return result, result_template + + def get_task_object(self, template_id, load_cls_data: dict) -> Optional[BaseTask]: + if template_id in self.task_cls_cache: + return self.task_cls_cache[template_id]() + if "load_type" not in load_cls_data: + return None + if load_cls_data["load_type"] == "func": + cls = load_task_cls_by_function( + name=load_cls_data["name"], + func_name=load_cls_data["func_name"], + is_model=load_cls_data.get("is_model", False), + model_index=load_cls_data.get("is_model", ''), + args=load_cls_data.get("args", None), + sub_name=load_cls_data.get("sub_name", None), + ) + else: + cls_path = load_cls_data["cls_path"] + cls = load_task_cls_by_path(cls_path, load_cls_data["name"]) + + if not cls: + return None + self.task_cls_cache[template_id] = cls + return cls() + + def run(self): + rsync_compatible() + task_list, task_template = self.can_run_task_list() + for t in task_list: + if t["template_id"] not in task_template: + continue + template = task_template[t["template_id"]] + if not template["used"]: + continue + print(t) + print(_PushRunner(t, template, self)()) + print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>") + + global WAIT_TASK_LIST + if WAIT_TASK_LIST: # 有任务启用子线程的,要等到这个线程结束,再结束主线程 + for i in WAIT_TASK_LIST: + i.join() + + def get_today_zero(self) -> datetime.datetime: + if self._today_zero is None: + t = datetime.datetime.today() + t_zero = datetime.datetime.combine(t, datetime.time.min) + self._today_zero = t_zero + return self._today_zero + + +class _PushRunner: + + def __init__(self, task: dict, template: dict, push_system: PushSystem, custom_push_data: Optional[dict] = None): + self._public_push_data: Optional[dict] = None + self.result: dict = { + "do_send": False, + "stop_msg": "", + "push_data": {}, + "check_res": False, + "check_stop_on": "", + "send_data": {}, + } # 记录结果 + self.change_fields = set() # 记录task变化值 + self.task_obj: Optional[BaseTask] = None + self.task = task + self.template = template + self.push_system = push_system + self._add_hook_msg: Optional[str] = None # 记录前置钩子处理后的追加信息 + self.custom_push_data = custom_push_data + + self.tr_cfg = TaskRecordConfig(task["id"]) + self.is_number_rule_by_func = False # 记录这个任务是否使用自定义的次数检测, 如果是,就不需要做次数更新 + + def save_result(self): + + t = TaskConfig() + tmp = t.get_by_id(self.task["id"]) + if tmp: + for f in self.change_fields: + tmp[f] = self.task[f] + + if self.result["do_send"]: + tmp["last_send"] = int(time.time()) + tmp["last_check"] = int(time.time()) + + t.save_config() + + if self.result["push_data"]: + result_data = self.result.copy() + self.tr_cfg.config.append( + { + "id": self.tr_cfg.nwe_id(), + "template_id": self.template["id"], + "task_id": self.task["id"], + "do_send": result_data.pop("do_send"), + "send_data": result_data.pop("push_data"), + "result": result_data, + "create_time": int(time.time()), + } + ) + self.tr_cfg.save_config() + + @property + def public_push_data(self) -> dict: + if self._public_push_data is None: + self._public_push_data = { + 'ip': get_server_ip(), + 'local_ip': get_network_ip(), + 'server_name': get_config_value('title') + } + data = self._public_push_data.copy() + data['time'] = format_date() + data['timestamp'] = int(time.time()) + return data + + def __call__(self): + self.run() + self.save_result() + if self.task_obj: + self.task_obj.task_run_end_hook(self.result) + return self.result_to_return() + + def result_to_return(self) -> dict: + return self.result + + def run(self): + self.task_obj = self.push_system.get_task_object(self.template["id"], self.template["load_cls"]) + + if not self.task_obj: + self.result["stop_msg"] = "任务类加载失败" + return + + if self.custom_push_data is None: + push_data = self.task_obj.get_push_data(self.task["id"], self.task["task_data"]) + if not push_data: + return + else: + push_data = self.custom_push_data + + self.result["push_data"] = push_data + # 执行前置钩子 + if self.task["pre_hook"] and "hook_type" in self.task["pre_hook"]: + if not self.run_hook(self.task["pre_hook"], "pre_hook"): + return + + # 执行时间规则判断 + if not self.run_time_rule(self.task["time_rule"]): + return + + # 执行时间规则判断 + if not self.number_rule(self.task["number_rule"]): + return + + # 执行发送信息 + self.send_message(push_data) + self.change_fields.add("number_data") + if "day_num" not in self.task["number_data"]: + self.task["number_data"]["day_num"] = 0 + + if "total" not in self.task["number_data"]: + self.task["number_data"]["total"] = 0 + + self.task["number_data"]["day_num"] += 1 + self.task["number_data"]["total"] += 1 + self.task["number_data"]["time"] = int(time.time()) + + # 执行后置钩子 + if self.task["after_hook"] and "hook_type" in self.task["after_hook"]: + self.run_hook(self.task["after_hook"], "after_hook") + + # todo: 下个版本实现一些自定义的hook函数,同时实现用户脚本的hook记录在 self.result 最后统一储存 + def run_hook(self, hook_data: dict, hook_name: str) -> bool: + """ + 执行hook操作,并返回是否继续执行, 并将hook的执行结果记录 + @param hook_name: 钩子的名称,如:after_hook, pre_hook + @param hook_data: 执行的内容 + @return: + """ + return True + + def run_time_rule(self, time_rule: dict) -> bool: + if "send_interval" in time_rule and time_rule["send_interval"] > 0: + if self.task["last_send"] + time_rule["send_interval"] > time.time(): + self.result['stop_msg'] = '小于最小发送时间,不进行发送' + self.result['check_stop_on'] = "time_rule_send_interval" + return False + + time_range = time_rule.get("time_range", None) + if time_range and isinstance(time_range, list) and len(time_range) == 2: + t_zero = self.push_system.get_today_zero() + start_time = t_zero + datetime.timedelta(seconds=time_range[0]) + end_time = t_zero + datetime.timedelta(seconds=time_range[1]) + if not start_time < datetime.datetime.now() < end_time: + self.result['stop_msg'] = '不在可发送告警的时间范围之内' + self.result['check_stop_on'] = "time_rule_time_range" + return False + return True + + def number_rule(self, number_rule: dict) -> bool: + number_data = self.task.get("number_data", {}) + # 判断通过 自定义函数的方式确认是否达到发送次数 + if "get_by_func" in number_rule and isinstance(number_rule["get_by_func"], str): + f = getattr(self.task_obj, number_rule["get_by_func"], None) + if f is not None and callable(f): + res = f(self.task["id"], self.task["task_data"], number_data, self.result["push_data"]) + if isinstance(res, str): + self.result['stop_msg'] = res + self.result['check_stop_on'] = "number_rule_get_by_func" + return False + + # 只要是走了使用函数检查的,不再处理默认情况 change_fields 中不添加 number_data + return True + + if "day_num" in number_rule and isinstance(number_rule["day_num"], int) and number_rule["day_num"] > 0: + record_time = number_data.get("time", 0) + if record_time < self.push_system.get_today_zero().timestamp(): # 昨日触发 + self.task["number_data"]["day_num"] = record_num = 0 + self.task["number_data"]["time"] = time.time() + self.change_fields.add("number_data") + else: + record_num = self.task["number_data"].get("day_num") + if record_num >= number_rule["day_num"]: + self.result['stop_msg'] = "超过每日限制次数:{}".format(number_rule["day_num"]) + self.result['check_stop_on'] = "number_rule_day_num" + return False + + if "total" in number_rule and isinstance(number_rule["total"], int) and number_rule["total"] > 0: + record_total = number_data.get("total", 0) + if record_total >= number_rule["total"]: + self.result['stop_msg'] = "超过最大限制次数:{}".format(number_rule["total"]) + self.result['check_stop_on'] = "number_rule_total" + return False + + return True + + def send_message(self, push_data: dict): + self.result["do_send"] = True + self.result["push_data"] = push_data + wx_account = [] + for sender_id in self.task["sender"]: + conf = self.push_system.sd_cfg.get_by_id(sender_id) + if conf is None: + continue + if not conf["used"]: + self.result["send_data"][sender_id] = "告警通道{}已关闭,跳过发送".format(conf["data"].get("title")) + continue + sd_cls = self.push_system.sender_cls(conf["sender_type"]) + if conf["sender_type"] == "weixin": + res = sd_cls(conf).send_msg( + self.task_obj.to_weixin_msg(push_data, self.public_push_data), + self.task_obj.title + ) + + elif conf["sender_type"] == "mail": + res = sd_cls(conf).send_msg( + self.task_obj.to_mail_msg(push_data, self.public_push_data), + self.task_obj.title + ) + + elif conf["sender_type"] == "webhook": + res = sd_cls(conf).send_msg( + self.task_obj.to_web_hook_msg(push_data, self.public_push_data), + self.task_obj.title, + self.task_obj.title + ) + + elif conf["sender_type"] == "feishu": + res = sd_cls(conf).send_msg( + self.task_obj.to_feishu_msg(push_data, self.public_push_data), + self.task_obj.title + ) + elif conf["sender_type"] == "dingding": + res = sd_cls(conf).send_msg( + self.task_obj.to_dingding_msg(push_data, self.public_push_data), + self.task_obj.title + ) + elif conf["sender_type"] == "sms": + sm_type, sm_args = self.task_obj.to_sms_msg(push_data, self.public_push_data) + if not sm_type or not sm_args: + continue + sm_args = sms_msg_normalize(sm_args) + res = sd_cls(conf).send_msg(sm_type, sm_args) + + elif conf["sender_type"] == "wx_account": + wx_account.append(conf) + continue + else: + continue + if isinstance(res, str) and res.find("Traceback") != -1: + self.result["send_data"][sender_id] = "执行信息发送过程中报错了, 未发送成功" + if isinstance(res, str): + self.result["send_data"][sender_id] = res + else: + self.result["send_data"][sender_id] = 1 + + if len(wx_account) > 0: + sd_cls = self.push_system.sender_cls("wx_account") + res = sd_cls(*wx_account).send_msg(self.task_obj.to_wx_account_msg(push_data, self.public_push_data)) + for i in wx_account: + if isinstance(res, str): + self.result["send_data"][i["id"]] = res + else: + self.result["send_data"][i["id"]] = 1 + + +def push_by_task_keyword(source: str, keyword: str, push_data: Optional[dict] = None) -> Union[str, dict]: + """ + 通过关键字查询告警任务,并发送信息 + @param push_data: + @param source: + @type keyword: + @return: + """ + push_system = PushSystem() + target_task = None + for i in TaskConfig().config: + if i["source"] == source and i["keyword"] == keyword: + target_task = i + break + if not target_task: + return "未查找到该任务" + + target_template = TaskTemplateConfig().get_by_id(target_task["template_id"]) + if not target_template["used"]: + return "该任务类型已被禁止使用" + if not target_task['status']: + return "该任务已停止" + + return _PushRunner(target_task, target_template, push_system, push_data)() + + +def push_by_task_id(task_id: str, push_data: Optional[dict] = None): + """ + 通过任务id触发告警 并 发送信息 + @param push_data: + @param task_id: + @return: + """ + push_system = PushSystem() + target_task = TaskConfig().get_by_id(task_id) + if not target_task: + return "未查找到该任务" + + target_template = TaskTemplateConfig().get_by_id(target_task["template_id"]) + if not target_template["used"]: + return "该任务类型已被禁止使用" + if not target_task['status']: + return "该任务已停止" + + return _PushRunner(target_task, target_template, push_system, push_data)() + + +def get_push_public_data(): + data = { + 'ip': get_server_ip(), + 'local_ip': get_network_ip(), + 'server_name': get_config_value('title'), + 'time': format_date(), + 'timestamp': int(time.time())} + + return data + diff --git a/mod/base/push_mod/system_push.py b/mod/base/push_mod/system_push.py new file mode 100644 index 00000000..d54eefb3 --- /dev/null +++ b/mod/base/push_mod/system_push.py @@ -0,0 +1,421 @@ + +import json +import os +import sys +import threading +import time +from datetime import datetime, timedelta +from importlib import import_module +from typing import Tuple, Union, Optional, List + +import psutil + +from .send_tool import WxAccountMsg +from .base_task import BaseTask +from .mods import PUSH_DATA_PATH +from .util import read_file, write_file, get_config_value + + +from .system import WAIT_TASK_LIST + +try: + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + from panel_msg.collector import SitePushMsgCollect, SystemPushMsgCollect +except ImportError: + SitePushMsgCollect = None + SystemPushMsgCollect = None + + +def _get_panel_name() -> str: + data = get_config_value("title") # 若获得别名,则使用别名 + if data == "": + data = "宝塔面板" + return data + + +class PanelSysDiskTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "system_disk" + self.template_name = "首页磁盘告警" + + self.wx_msg = "" + + def get_title(self, task_data: dict) -> str: + return "挂载目录【{}】的磁盘余量告警".format(task_data["project"]) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if task_data["project"] not in [i[0] for i in self._get_disk_name()]: + return "指定的磁盘不存在" + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] in (1, 2)): + return "类型参数错误" + if not (isinstance(task_data['count'], int) and task_data['count'] >= 1): + return "阈值参数错误" + if task_data['cycle'] == 2 and task_data['count'] >= 100: + return "阈值参数错误, 设置的检查范围不正确" + task_data['interval'] = 600 + return task_data + + @staticmethod + def _get_disk_name() -> list: + """获取硬盘挂载点""" + if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + + system_modul = import_module('.system', package="class") + system = getattr(system_modul, "system") + + disk_info = system.GetDiskInfo2(None, human=False) + + return [(d.get("path"), d.get("size")[0]) for d in disk_info] + + @staticmethod + def _get_disk_info() -> list: + """获取硬盘挂载点""" + if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + + system_modul = import_module('.system', package="class") + system = getattr(system_modul, "system") + + disk_info = system.GetDiskInfo2(None, human=False) + + return disk_info + + def get_keyword(self, task_data: dict) -> str: + print(task_data) + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + disk_info = self._get_disk_info() + unsafe_disk_list = [] + + for d in disk_info: + if task_data["project"] != d["path"]: + continue + free = int(d["size"][2]) / 1048576 + proportion = int(d["size"][3] if d["size"][3][-1] != "%" else d["size"][3][:-1]) + + if task_data["cycle"] == 1 and free < task_data["count"]: + unsafe_disk_list.append( + "挂载在【{}】上的磁盘剩余容量为{}G,小于告警值{}G.".format( + d["path"], round(free, 2), task_data["count"]) + ) + self.wx_msg = "剩余容量小于{}G".format(task_data["count"]) + + elif task_data["cycle"] == 2 and proportion > task_data["count"]: + unsafe_disk_list.append( + "挂载在【{}】上的磁盘已使用容量为{}%,大于告警值{}%.".format( + d["path"], round(proportion, 2), task_data["count"]) + ) + self.wx_msg = "占用量大于{}%".format(task_data["count"]) + + if len(unsafe_disk_list) == 0: + return None + + return { + "msg_list": [ + ">通知类型:磁盘余量告警", + ">告警内容:\n" + "\n".join(unsafe_disk_list) + ] + } + + def filter_template(self, template: dict) -> Optional[dict]: + for (path, total_size) in self._get_disk_name(): + template["field"][0]["items"].append({ + "title": "【{}】的磁盘".format(path), + "value": path, + "count_default": round((int(total_size) * 0.2) / 1024 / 1024, 1) + }) + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return 'machine_exception|磁盘余量告警', { + 'name': _get_panel_name(), + 'type': "磁盘空间不足", + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "宝塔首页磁盘告警" + if len(self.wx_msg) > 20: + self.wx_msg = self.wx_msg[:17] + "..." + msg.msg = self.wx_msg + return msg + + +class PanelSysCPUTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "system_cpu" + self.template_name = "首页CPU告警" + self.title = "首页CPU告警" + + self.cpu_count = 0 + + self._tip_file = "{}/system_cpu.tip".format(PUSH_DATA_PATH) + self._tip_data: Optional[List[Tuple[float, float]]] = None + + @property + def cache_list(self) -> List[Tuple[float, float]]: + if self._tip_data is not None: + return self._tip_data + try: + self._tip_data = json.loads(read_file(self._tip_file)) + except: + self._tip_data = [] + return self._tip_data + + def save_cache_list(self): + write_file(self._tip_file, json.dumps(self.cache_list)) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1): + return "时间参数错误" + if not (isinstance(task_data['count'], int) and task_data['count'] >= 1): + return "阈值参数错误,至少为1%" + task_data['interval'] = 60 + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "system_cpu" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + expiration = datetime.now() - timedelta(seconds=task_data["cycle"] * 60 + 10) + for i in range(len(self.cache_list) - 1, -1, -1): + data_time, _ = self.cache_list[i] + if datetime.fromtimestamp(data_time) < expiration: + del self.cache_list[i] + + # 记录下次的 + def thread_get_cpu_data(): + self.cache_list.append((time.time(), psutil.cpu_percent(10))) + self.save_cache_list() + + thread_active = threading.Thread(target=thread_get_cpu_data, args=()) + thread_active.start() + WAIT_TASK_LIST.append(thread_active) + + if len(self.cache_list) < task_data["cycle"]: # 小于指定次数不推送 + return None + + if len(self.cache_list) > 0: + avg_data = sum(i[1] for i in self.cache_list) / len(self.cache_list) + else: + avg_data = 0 + + if avg_data < task_data["count"]: + return None + else: + self.cache_list.clear() + self.cpu_count = round(avg_data, 2) + s_list = [ + ">通知类型:CPU高占用告警", + ">告警内容:最近{}分钟内机器CPU平均占用率为{}%,高于告警值{}%".format( + task_data["cycle"], round(avg_data, 2), task_data["count"]), + ] + + return { + "msg_list": s_list, + } + + def filter_template(self, template: dict) -> Optional[dict]: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return 'machine_exception|CPU高占用告警', { + 'name': _get_panel_name(), + 'type': "CPU高占用", + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "宝塔首页cpu告警" + msg.msg = "主机CPU占用超过:{}%".format(self.cpu_count) + msg.next_msg = "请登录面板,查看主机情况" + return msg + + +class PanelSysLoadTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "system_load" + self.template_name = "首页负载告警" + self.title = "首页负载告警" + + self.avg_data = 0 + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1): + return "时间参数错误" + if not (isinstance(task_data['count'], int) and task_data['count'] >= 1): + return "阈值参数错误,至少为1%" + task_data['interval'] = 60 * task_data['cycle'] + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "system_load" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + now_load = os.getloadavg() + cpu_count = psutil.cpu_count() + now_load = [i / (cpu_count * 2) * 100 for i in now_load] + need_push = False + avg_data = 0 + if task_data["cycle"] == 15 and task_data["count"] < now_load[2]: + avg_data = now_load[2] + need_push = True + elif task_data["cycle"] == 5 and task_data["count"] < now_load[1]: + avg_data = now_load[1] + need_push = True + elif task_data["cycle"] == 1 and task_data["count"] < now_load[0]: + avg_data = now_load[0] + need_push = True + + if need_push is False: + return None + + self.avg_data = avg_data + + return { + "msg_list": [ + ">通知类型:负载超标告警", + ">告警内容:最近{}分钟内机器平均负载率为{}%,高于{}%告警值".format( + task_data["cycle"], round(avg_data, 2), task_data["count"]), + ] + } + + def filter_template(self, template: dict) -> Optional[dict]: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return 'machine_exception|负载超标告警', { + 'name': _get_panel_name(), + 'type': "平均负载过高", + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "宝塔首页负载告警" + msg.msg = "主机负载超过:{}%".format(round(self.avg_data, 2)) + msg.next_msg = "请登录面板,查看主机情况" + return msg + + +class PanelSysMEMTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "system_mem" + self.template_name = "首页内存告警" + self.title = "首页内存告警" + + self.wx_data = 0 + + self._tip_file = "{}/system_mem.tip".format(PUSH_DATA_PATH) + self._tip_data: Optional[List[Tuple[float, float]]] = None + + @property + def cache_list(self) -> List[Tuple[float, float]]: + if self._tip_data is not None: + return self._tip_data + try: + self._tip_data = json.loads(read_file(self._tip_file)) + except: + self._tip_data = [] + return self._tip_data + + def save_cache_list(self): + write_file(self._tip_file, json.dumps(self.cache_list)) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not (isinstance(task_data['cycle'], int) and task_data['cycle'] >= 1): + return "次数参数错误" + if not (isinstance(task_data['count'], int) and task_data['count'] >= 1): + return "阈值参数错误,至少为1%" + task_data['interval'] = task_data['cycle'] * 60 + return task_data + + def get_keyword(self, task_data: dict) -> str: + return "system_mem" + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + mem = psutil.virtual_memory() + real_used: float = (mem.total - mem.free - mem.buffers - mem.cached) / mem.total + stime = datetime.now() + expiration = stime - timedelta(seconds=task_data["cycle"] * 60 + 10) + + self.cache_list.append((stime.timestamp(), real_used)) + + for i in range(len(self.cache_list) - 1, -1, -1): + data_time, _ = self.cache_list[i] + if datetime.fromtimestamp(data_time) < expiration: + del self.cache_list[i] + + avg_data = sum(i[1] for i in self.cache_list) / len(self.cache_list) + + if avg_data * 100 < task_data["count"]: + self.save_cache_list() + return None + else: + self.cache_list.clear() + self.save_cache_list() + self.wx_data = round(avg_data * 100, 2) + return { + 'msg_list': [ + ">通知类型:内存高占用告警", + ">告警内容:最近{}分钟内机器内存平均占用率为{}%,高于告警值{}%".format( + task_data["cycle"], round(avg_data * 100, 2), task_data["count"]), + ] + } + + def filter_template(self, template: dict) -> Optional[dict]: + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return 'machine_exception|内存高占用告警', { + 'name': _get_panel_name(), + 'type': "内存高占用", + } + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "宝塔首页内存告警" + msg.msg = "主机内存占用超过:{}%".format(self.wx_data) + msg.next_msg = "请登录面板,查看主机情况" + return msg + + +class ViewMsgFormat(object): + _FORMAT = { + "20": ( + lambda x: "挂载在{}上的磁盘{}触发".format( + x.get("project"), + "余量不足%.1fG" % round(x.get("count"), 1) if x.get("cycle") == 1 else "占用超过%d%%" % x.get("count"), + ) + ), + "21": ( + lambda x: "{}分钟内平均CUP占用超过{}%触发".format( + x.get("cycle"), x.get("count") + ) + ), + "22": ( + lambda x: "{}分钟内平均负载超过{}%触发".format( + x.get("cycle"), x.get("count") + ) + ), + "23": ( + lambda x: "{}分钟内内存使用率超过{}%触发".format( + x.get("cycle"), x.get("count") + ) + ) + } + + def get_msg(self, task: dict) -> Optional[str]: + if task["template_id"] in self._FORMAT: + return self._FORMAT[task["template_id"]](task["task_data"]) + return None \ No newline at end of file diff --git a/mod/base/push_mod/system_push_template.json b/mod/base/push_mod/system_push_template.json new file mode 100644 index 00000000..d672613f --- /dev/null +++ b/mod/base/push_mod/system_push_template.json @@ -0,0 +1,284 @@ +[ + { + "id": "20", + "ver": "1", + "used": true, + "source": "system_disk", + "title": "首页磁盘告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.system_push", + "name": "PanelSysDiskTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "磁盘信息", + "type": "select", + "items": [ + ] + }, + { + "attr": "cycle", + "name": "检测类型", + "type": "radio", + "suffix": "", + "default": 2, + "items": [ + { + "title": "剩余容量", + "value": 1 + }, + { + "title": "占用百分比", + "value": 2 + } + ] + }, + { + "attr": "count", + "name": "占用率超过", + "type": "number", + "unit": "%", + "suffix": "后触发告警", + "default": 80, + "err_msg_prefix": "磁盘阈值" + } + ], + "sorted": [ + [ + "project" + ], + [ + "cycle" + ], + [ + "count" + ] + ] + }, + "default": { + "project": "/", + "cycle": 2, + "count": 80 + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook", + "sms" + ], + "unique": false + }, + { + "id": "21", + "ver": "1", + "used": true, + "source": "system_cpu", + "title": "首页CPU告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.system_push", + "name": "PanelSysCPUTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "每", + "type": "select", + "unit": "分钟", + "suffix": "内平均", + "width": "70px", + "disabled": true, + "default": 5, + "items": [ + { + "title": "1", + "value": 3 + }, + { + "title": "5", + "value": 5 + }, + { + "title": "15", + "value": 15 + } + ] + }, + { + "attr": "count", + "name": "CPU占用超过", + "type": "number", + "unit": "%", + "suffix": "后触发告警", + "default": 80, + "err_msg_prefix": "CPU" + } + ], + "sorted": [ + [ + "cycle", + "count" + ] + ] + }, + "default": { + "cycle": 5, + "count": 80 + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook", + "sms" + ], + "unique": true + }, + { + "id": "22", + "ver": "1", + "used": true, + "source": "system_load", + "title": "首页负载告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.system_push", + "name": "PanelSysLoadTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "每", + "type": "select", + "unit": "分钟", + "suffix": "内平均", + "default": 5, + "width": "70px", + "disabled": true, + "items": [ + { + "title": "1", + "value": 1 + }, + { + "title": "5", + "value": 5 + }, + { + "title": "15", + "value": 15 + } + ] + }, + { + "attr": "count", + "name": "负载超过", + "type": "number", + "unit": "%", + "suffix": "后触发告警", + "default": 80, + "err_msg_prefix": "负载" + } + ], + "sorted": [ + [ + "cycle", + "count" + ] + ] + }, + "default": { + "cycle": 5, + "count": 80 + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook", + "sms" + ], + "unique": true + }, + { + "id": "23", + "ver": "1", + "used": true, + "source": "system_mem", + "title": "首页内存告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.system_push", + "name": "PanelSysMEMTask" + }, + "template": { + "field": [ + { + "attr": "cycle", + "name": "每", + "type": "select", + "unit": "分钟", + "suffix": "内平均", + "width": "70px", + "disabled": true, + "default": 5, + "items": [ + { + "title": "1", + "value": 3 + }, + { + "title": "5", + "value": 5 + }, + { + "title": "15", + "value": 15 + } + ] + }, + { + "attr": "count", + "name": "内存使用率超过", + "type": "number", + "unit": "%", + "suffix": "后触发告警", + "default": 80, + "err_msg_prefix": "内存" + } + ], + "sorted": [ + [ + "cycle", + "count" + ] + ] + }, + "default": { + "cycle": 5, + "count": 80 + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook", + "sms" + ], + "unique": true + } +] \ No newline at end of file diff --git a/mod/base/push_mod/task_manager_push.py b/mod/base/push_mod/task_manager_push.py new file mode 100644 index 00000000..bb4d9cbb --- /dev/null +++ b/mod/base/push_mod/task_manager_push.py @@ -0,0 +1,503 @@ +import json +import os +import sys +import threading +import time +from datetime import datetime, timedelta +from importlib import import_module +from typing import Tuple, Union, Optional, List + +import psutil + +from .send_tool import WxAccountMsg +from .base_task import BaseTask +from .mods import PUSH_DATA_PATH, TaskTemplateConfig +from .util import read_file, write_file, get_config_value, GET_CLASS + + +class _ProcessInfo: + + def __init__(self): + self.data = None + self.last_time = 0 + + def __call__(self) -> list: + if self.data is not None and time.time() - self.last_time < 60: + return self.data + + try: + import PluginLoader + get_obj = GET_CLASS() + get_obj.sort = "status" + p_info = PluginLoader.plugin_run("task_manager", "get_process_list", get_obj) + except: + return [] + + if isinstance(p_info, dict) and "process_list" in p_info and isinstance( + p_info["process_list"], list): + self._process_info = p_info["process_list"] + self.last_time = time.time() + return self._process_info + else: + return [] + + +get_process_info = _ProcessInfo() + + +def have_task_manager_plugin(): + """ + 通过文件判断是否有进程管理器 + """ + return os.path.exists("/www/server/panel/plugin/task_manager/task_manager_push.py") + + +def load_task_manager_template(): + if TaskTemplateConfig().get_by_id("60"): + return None + + from .mods import load_task_template_by_config + load_task_template_by_config([ + { + "id": "60", + "ver": "1", + "used": True, + "source": "task_manager_cpu", + "title": "任务管理器CPU占用量告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.task_manager_push", + "name": "TaskManagerCPUTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "进程名称", + "type": "select", + "items": { + "url": "plugin?action=a&name=task_manager&s=get_process_list_to_push" + } + }, + { + "attr": "count", + "name": "占用率超过", + "type": "number", + "unit": "%", + "suffix": "后触发告警", + "default": 80, + "err_msg_prefix": "CUP占用率" + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + } + ], + "sorted": [ + [ + "project" + ], + [ + "count" + ], + [ + "interval" + ] + ], + }, + "default": { + "project": '', + "count": 80, + "interval": 600 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": False + }, + { + "id": "61", + "ver": "1", + "used": True, + "source": "task_manager_mem", + "title": "任务管理器内存占用量告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.task_manager_push", + "name": "TaskManagerMEMTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "进程名称", + "type": "select", + "items": { + "url": "plugin?action=a&name=task_manager&s=get_process_list_to_push" + } + }, + { + "attr": "count", + "name": "占用量超过", + "type": "number", + "unit": "MB", + "suffix": "后触发告警", + "default": None, + "err_msg_prefix": "占用量" + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + } + ], + "sorted": [ + [ + "project" + ], + [ + "count" + ], + [ + "interval" + ] + ], + }, + "default": { + "project": '', + "count": 80, + "interval": 600 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": False + }, + { + "id": "62", + "ver": "1", + "used": True, + "source": "task_manager_process", + "title": "任务管理器进程开销告警", + "load_cls": { + "load_type": "path", + "cls_path": "mod.base.push_mod.task_manager_push", + "name": "TaskManagerProcessTask" + }, + "template": { + "field": [ + { + "attr": "project", + "name": "进程名称", + "type": "select", + "items": { + "url": "plugin?action=a&name=task_manager&s=get_process_list_to_push" + } + }, + { + "attr": "count", + "name": "进程数超过", + "type": "number", + "unit": "个", + "suffix": "后触发告警", + "default": 20, + "err_msg_prefix": "进程数" + }, + { + "attr": "interval", + "name": "间隔时间", + "type": "number", + "unit": "秒", + "suffix": "后再次监控检测条件", + "default": 600 + } + ], + "sorted": [ + [ + "project" + ], + [ + "count" + ], + [ + "interval" + ] + ], + }, + "default": { + "project": '', + "count": 80, + "interval": 600 + }, + "advanced_default": { + "number_rule": { + "day_num": 3 + } + }, + "send_type_list": [ + "wx_account", + "dingding", + "feishu", + "mail", + "weixin", + "webhook" + ], + "unique": False + } + ]) + + +class TaskManagerCPUTask(BaseTask): + + def __init__(self): + super().__init__() + self.source_name = "task_manager_cpu" + self.template_name = "任务管理器CUP占用量告警" + + def get_title(self, task_data: dict) -> str: + return "进程【{}】的CPU占用量告警".format(task_data["project"]) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if "interval" not in task_data or not isinstance(task_data["interval"], int): + task_data["interval"] = 600 + if task_data["interval"] < 60: + task_data["interval"] = 60 + if "count" not in task_data or not isinstance(task_data["count"], int): + return "设置的检查范围不正确" + if not 1 <= task_data["count"] < 100: + return "设置的检查范围不正确" + if not task_data["project"]: + return "请选择进程" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + process_info = get_process_info() + self.title = self.get_title(task_data) + count = used = 0 + for p in process_info: + if p["name"] == task_data['project']: + used += p["cpu_percent"] + count += 1 if "children" not in p else len(p["children"]) + 1 + + if used <= task_data['count']: + return None + + return { + 'msg_list': + [ + ">通知类型:任务管理器CPU占用量告警", + ">告警内容: 进程名称为【{}】的进程共有{}个,消耗的CPU资源占比为{}%,大于告警阈值{}%。".format( + task_data['project'], count, used, task_data['count'] + ) + ], + "project": task_data['project'], + "count": int(task_data['count']) + } + + def filter_template(self, template: dict) -> Optional[dict]: + if not have_task_manager_plugin(): + return None + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "任务管理器CPU占用量告警" + if len(push_data["project"]) > 11: + project = push_data["project"][:9] + ".." + else: + project = push_data["project"] + + msg.msg = "{}的CUP超过{}%".format(project, push_data["count"]) + return msg + + +class TaskManagerMEMTask(BaseTask): + def __init__(self): + super().__init__() + self.source_name = "task_manager_mem" + self.template_name = "任务管理器内存占用量告警" + + def get_title(self, task_data: dict) -> str: + return "进程【{}】的内存占用量告警".format(task_data["project"]) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not task_data["project"]: + return "请选择进程" + if "interval" not in task_data or not isinstance(task_data["interval"], int): + task_data["interval"] = 600 + task_data["interval"] = max(60, task_data["interval"]) + if "count" not in task_data or not isinstance(task_data["count"], int): + return "设置的检查范围不正确" + if task_data["count"] < 1: + return "设置的检查范围不正确" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + process_info = get_process_info() + self.title = self.get_title(task_data) + + used = count = 0 + for p in process_info: + if p["name"] == task_data['project']: + used += p["memory_used"] + count += 1 if "children" not in p else len(p["children"]) + 1 + + if used <= task_data['count'] * 1024 * 1024: + return None + return { + 'msg_list': [ + ">通知类型:任务管理器内存占用量告警", + ">告警内容: 进程名称为【{}】的进程共有{}个,消耗的内存资源为{}MB,大于告警阈值{}MB。".format( + task_data['project'], count, int(used / 1024 / 1024), task_data['count'] + ) + ], + "project": task_data['project'] + } + + def filter_template(self, template: dict) -> Optional[dict]: + if not have_task_manager_plugin(): + return None + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + if len(push_data["project"]) > 11: + project = push_data["project"][:9] + ".." + else: + project = push_data["project"] + msg.thing_type = "任务管理器内存占用量告警" + msg.msg = "{}的内存超过告警数值".format(project) + return msg + + +class TaskManagerProcessTask(BaseTask): + def __init__(self): + super().__init__() + self.source_name = "task_manager_process" + self.title = "任务管理器进程开销告警" + + def get_title(self, task_data: dict) -> str: + return "进程【{}】的子进程开销告警".format(task_data["project"]) + + def check_task_data(self, task_data: dict) -> Union[dict, str]: + if not task_data["project"]: + return "请选择进程" + if "interval" not in task_data or not isinstance(task_data["interval"], int): + task_data["interval"] = 600 + task_data["interval"] = max(60, task_data["interval"]) + if "count" not in task_data or not isinstance(task_data["count"], int): + return "设置的检查范围不正确" + if task_data["count"] < 1: + return "设置的检查范围不正确" + return task_data + + def get_keyword(self, task_data: dict) -> str: + return task_data["project"] + + def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]: + process_info = get_process_info() + count = 0 + for p in process_info: + if p["name"] == task_data['project']: + count += 1 if "children" not in p else len(p["children"]) + 1 + + if count <= task_data['count']: + return None + + return { + 'msg_list': + [ + ">通知类型:任务管理器进程开销告警", + ">告警内容: 进程名称为【{}】的进程共有{}个,大于告警阈值{}个。".format( + task_data['project'], count, task_data['count'] + ) + ], + "project": task_data['project'], + "count": task_data['count'], + } + + def filter_template(self, template: dict) -> Optional[dict]: + if not have_task_manager_plugin(): + return None + return template + + def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]: + return '', {} + + def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg: + msg = WxAccountMsg.new_msg() + msg.thing_type = "任务管理器进程开销告警" + if len(push_data["project"]) > 11: + project = push_data["project"][:9] + ".." + else: + project = push_data["project"] + + if push_data["count"] > 100: # 节省字数 + push_data["count"] = "限制" + + msg.msg = "{}的子进程数超过{}".format(project, push_data["count"]) + return msg + + +class ViewMsgFormat(object): + _FORMAT = { + "60": ( + lambda x: "进程:{}的CUP占用超过{}%触发".format( + x.get("project"), x.get("count") + ) + ), + "61": ( + lambda x: "进程:{}的内存使用率超过{}MB后触发".format( + x.get("project"), x.get("count") + ) + ), + "62": ( + lambda x: "进程:{}的子进程数量超过{}后触发".format( + x.get("project"), x.get("count") + ) + ), + } + + def get_msg(self, task: dict) -> Optional[str]: + if task["template_id"] in self._FORMAT: + return self._FORMAT[task["template_id"]](task["task_data"]) + return None diff --git a/mod/base/push_mod/tool.py b/mod/base/push_mod/tool.py new file mode 100644 index 00000000..37970546 --- /dev/null +++ b/mod/base/push_mod/tool.py @@ -0,0 +1,75 @@ +import sys +from typing import Optional, Type, TypeVar +import traceback +from importlib import import_module + +from .base_task import BaseTask +from .util import GET_CLASS, get_client_ip, debug_log + + +T_CLS = TypeVar('T_CLS', bound=BaseTask) + + +def load_task_cls_by_function( + name: str, + func_name: str, + is_model: bool = False, + model_index: str = '', + args: Optional[dict] = None, + sub_name: Optional[str] = None, +) -> Optional[Type[T_CLS]]: + """ + 从执行函数的结果中获取任务类 + @param model_index: 模块来源,例如:新场景就是mod + @param name: 名称 + @param func_name: 函数名称 + @param is_model: 是否在Model中,不在Model中,就应该在插件中 + @param args: 请求这个接口的参数, 默认为空 + @param sub_name: 自分类名称, 如果有,则会和主名称name做拼接 + @return: 返回None 或者有效的任务类 + """ + import PluginLoader + real_name = name + if isinstance(sub_name, str): + real_name = "{}/{}".format(name, sub_name) + + get_obj = GET_CLASS() + if args is not None and isinstance(args, dict): + for key, value in args.items(): + setattr(get_obj, key, value) + try: + if is_model: + get_obj.model_index = model_index + res = PluginLoader.module_run(real_name, func_name, get_obj) + else: + get_obj.fun = func_name + get_obj.s = func_name + get_obj.client_ip = get_client_ip + res = PluginLoader.plugin_run(name, func_name, get_obj) + except: + debug_log(traceback.format_exc()) + return None + if isinstance(res, dict): + return None + elif isinstance(res, BaseTask): + return res.__class__ + elif issubclass(res, BaseTask): + return res + return None + + +def load_task_cls_by_path(path: str, cls_name: str) -> Optional[Type[T_CLS]]: + try: + module = import_module(path) + cls = getattr(module, cls_name, None) + if issubclass(cls, BaseTask): + return cls + elif isinstance(cls, BaseTask): + return cls.__class__ + else: + return None + except: + print(traceback.format_exc()) + print(sys.path) + debug_log(traceback.format_exc()) + return None diff --git a/mod/base/push_mod/util.py b/mod/base/push_mod/util.py new file mode 100644 index 00000000..dff88441 --- /dev/null +++ b/mod/base/push_mod/util.py @@ -0,0 +1,98 @@ +import sys +import time +from typing import Optional, Callable + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + +import public +from db import Sql + + +def write_file(filename: str, s_body: str, mode='w+') -> bool: + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode=mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode=mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +def read_file(filename, mode='r') -> Optional[str]: + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): + return None + fp = None + try: + fp = open(filename, mode=mode) + f_body = fp.read() + except: + return None + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +ExecShell: Callable = public.ExecShell + +write_log: Callable = public.WriteLog + +Sqlite: Callable = Sql + +GET_CLASS: Callable = public.dict_obj + +debug_log: Callable = public.print_log + +get_config_value: Callable = public.GetConfigValue + +get_server_ip: Callable = public.get_server_ip + +get_network_ip: Callable = public.get_network_ip + +format_date: Callable = public.format_date + +public_get_cache_func: Callable = public.get_cache_func + +public_set_cache_func: Callable = public.set_cache_func + +public_get_user_info: Callable = public.get_user_info + +public_http_post = public.httpPost + +panel_version = public.version + + +def get_client_ip() -> str: + return public.GetClientIp() + + +class _DB: + + def __call__(self, table: str): + import db + with db.Sql() as t: + t.table(table) + return t + + +DB = _DB() diff --git a/mod/base/web_conf/__init__.py b/mod/base/web_conf/__init__.py new file mode 100644 index 00000000..5c858f75 --- /dev/null +++ b/mod/base/web_conf/__init__.py @@ -0,0 +1,66 @@ +import json +import os.path +import shutil +import sys + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +from .ip_restrict import IpRestrict, RealIpRestrict +from .redirect import RealRedirect, Redirect +from .access_restriction import AccessRestriction, RealAccessRestriction +from .domain_tool import domain_to_puny_code, check_domain, normalize_domain, NginxDomainTool, ApacheDomainTool, \ + is_domain +from .dns_api import DNSApiManager, RealDnsMager +from .dir_tool import DirTool +from .referer import Referer, RealReferer +from .logmanager import LogMgr, RealLogMgr +from .proxy import Proxy, RealProxy +from .ssl import SSLManager, RealSSLManger +from .config_mgr import ConfigMgr +from .default_site import set_default_site, get_default_site, check_default + + +def remove_sites_service_config(site_name: str, config_prefix: str = ""): + """ + 用于删除一个网站的nginx,apache的所有相关配置文件和配置项 + 包含: + 配置文件,访问限制, 反向代理, 重定向, 防盗链,证书目录, IP黑白名单, 历史配置文件, 默认站点, 日志格式配置记录, 伪静态等 + """ + # 配置文件 + ng_file = "/www/server/panel/vhost/nginx/{}{}.conf".format(config_prefix, site_name) + if os.path.exists(ng_file): + os.remove(ng_file) + ap_file = "/www/server/panel/vhost/apache/{}{}.conf".format(config_prefix, site_name) + if os.path.exists(ap_file): + os.remove(ap_file) + # 访问限制 + RealAccessRestriction(config_prefix=config_prefix).remove_site_access_restriction_info(site_name) + # 反向代理 + RealProxy(config_prefix=config_prefix).remove_site_proxy_info(site_name) + # 重定向 + RealRedirect(config_prefix=config_prefix).remove_site_redirect_info(site_name) + # 防盗链 + RealReferer(config_prefix=config_prefix).remove_site_referer_info(site_name) + # 证书目录 + cert_path = "/www/server/panel/vhost/cert/" + site_name + if os.path.isdir(cert_path): + shutil.rmtree(cert_path) + # IP黑白名单 + RealIpRestrict(config_prefix=config_prefix).remove_site_ip_restrict_info(site_name) + # 历史配置文件 + ConfigMgr(site_name=site_name, config_prefix=config_prefix).clear_history_file() + # 默认站点 + d_site_name, d_prefix = get_default_site() + if d_site_name == site_name and d_prefix == config_prefix: + d_file = "/www/server/panel/data/mod_default_site.pl" + f = open(d_file, mode="w+") + json.dump({"name": None, "prefix": None}, f) + + # 日志格式配置记录 + RealLogMgr(conf_prefix=config_prefix).remove_site_log_format_info(site_name) + + # 伪静态 + rewrite_path = "/www/server/panel/vhost/rewrite/{}{}.conf".format(config_prefix, site_name) + if os.path.isdir(rewrite_path): + os.remove(rewrite_path) diff --git a/mod/base/web_conf/access_restriction.py b/mod/base/web_conf/access_restriction.py new file mode 100644 index 00000000..807b8b04 --- /dev/null +++ b/mod/base/web_conf/access_restriction.py @@ -0,0 +1,607 @@ +# 访问限制, 目前不兼容之前版本的访问限制 +# nginx 使用 if 和 正则实现,保障与反向代理、重定向的兼容性 +# apache 实现方案未变 +import os +import re +import json +import shutil +from typing import Optional, Union, List, Dict +from itertools import chain +from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, get_log_path, pre_re_key +from mod.base import json_response + + +class _ConfigObject: + _config_file_path = "" + panel_path = "/www/server/panel" + + def __init__(self): + self._config: Optional[dict] = None + + @property + def config(self) -> Dict[str, dict]: + if self._config is None: + try: + self._config = json.loads(read_file(self._config_file_path)) + except (json.JSONDecodeError, TypeError, ValueError): + self._config = {} + return self._config + + def save_config(self): + if self._config: + write_file(self._config_file_path, json.dumps(self._config)) + + +class ServerConfig: + _vhost_path = "/www/server/panel/vhost" + + def __init__(self, config_prefix: str): + self.config_prefix: str = config_prefix + + @staticmethod + def crypt_password(password) -> str: + import crypt + return crypt.crypt(password,password) + + +# nginx配置文件相关操作 +class _NginxAccessConf(ServerConfig): + + # 添加 include 导入配置项 + def set_nginx_access_include(self, site_name) -> Optional[str]: + ng_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, site_name) + ng_conf = read_file(ng_file) + if not ng_conf: + return "配置文件丢失" + access_dir = "{}/nginx/access/{}".format(self._vhost_path, site_name) + if not os.path.isdir(os.path.dirname(access_dir)): + os.makedirs(os.path.dirname(access_dir)) + + if not os.path.isdir(access_dir): + os.makedirs(access_dir) + + include_conf = ( + " #引用访问限制规则,注释后配置的访问限制将无效\n" + " include /www/server/panel/vhost/nginx/access/%s/*.conf;\n" + ) % site_name + + rep_include = re.compile(r"\s*include.*/access/.*/\*\.conf\s*;", re.M) + if rep_include.search(ng_conf): + return + # 添加 引入 + rep_list = [ + (re.compile(r"#SSL-END"), False), # 匹配Referer配置, 加其下 + (re.compile(r"(\s*#.*)?\s*include\s+.*/redirect/.*\.conf;"), True), # 重定向 + (re.compile(r"(\s*#.*)?\s*include\s+.*/ip-restrict/.*\.conf;"), True), # Ip黑白名单 + ] + + # 使用正则匹配确定插入位置 use_start 在前面插入还是后面插入 + def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool: + tmp_res = tmp_rep.search(ng_conf) + if not tmp_res: + return False + if use_start: + new_conf = ng_conf[:tmp_res.start()] + include_conf + tmp_res.group() + ng_conf[tmp_res.end():] + else: + new_conf = ng_conf[:tmp_res.start()] + tmp_res.group() + include_conf + ng_conf[tmp_res.end():] + + write_file(ng_file, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return False + return True + for r, s in rep_list: + if set_by_rep_idx(r, s): + break + else: + return "无法在配置文件中定位到需要添加的项目" + + # 写入配置文件 + def set_nginx_access_by_conf(self, site_name: str, configs: Dict[str, List[Dict[str, str]]]) -> Optional[str]: + """ configs 示例结构 + configs = { + "auth_dir": [ + { + "name": "aaa", + "dir_path": "/", + "auth_file": "/www/server/pass/www.cache.com/aaa.pass", + "username":"aaaa", + "password":"aaaa", + } + ], + "file_deny": [ + { + "name": "bbb", + "dir_path": "/", + "suffix": ["png", "jpg"] + } + ] + } + """ + + path_map = {} + for c in chain(configs.get("auth_dir", []), configs.get("file_deny", [])): + if c["dir_path"] not in path_map: + path_map[c["dir_path"]] = {"path": c["dir_path"]} + path_map[c["dir_path"]].update(c) + + path_list = list(path_map.values()) + path_list.sort(key=lambda x: len(x["path"].split("/")), reverse=True) + conf_template = r"""location ~ "^%s.*$" { + auth_basic "Authorization"; + auth_basic_user_file %s; +%s +} +""" + suffix_template = '{tmp_pre}if ( $uri ~ "\\.({suffix})$" ) {{\n{tmp_pre} return 404;\n{tmp_pre}}}' + suffix_template2 = 'if ( $uri ~ "^{path}.*\\.({suffix})$" ) {{\n return 404;\n}}\n' + tmp_conf_list = [] + for i in path_list: + if "auth_file" in i and "suffix" in i: + tmp_pre = " " + tmp_conf = conf_template % ( + i["path"], i["auth_file"], suffix_template.format(tmp_pre=tmp_pre, suffix="|".join(i["suffix"])) + ) + write_file(i["auth_file"], "{}:{}".format(i["username"], self.crypt_password(i["password"]))) + + elif "auth_file" in i: + tmp_conf = conf_template % (i["path"], i["auth_file"], "") + write_file(i["auth_file"], "{}:{}".format(i["username"], self.crypt_password(i["password"]))) + else: + tmp_conf = suffix_template2.format(path=i["path"], suffix="|".join(i["suffix"])) + + tmp_conf_list.append(tmp_conf) + + config_data = "\n".join(tmp_conf_list) + config_file = "{}/nginx/access/{}/{}{}.conf".format(self._vhost_path, site_name, self.config_prefix, site_name) + old_config = read_file(config_file) + write_file(config_file, config_data) + if webserver() == "nginx" and check_server_config() is not None: + if isinstance(old_config, str): + write_file(config_file, old_config) + else: + write_file(config_file, "") + return "配置失败" + + +class _ApacheAccessConf(ServerConfig): + + def set_apache_access_include(self, site_name) -> Optional[str]: + ap_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, site_name) + ap_conf = read_file(ap_file) + if not ap_conf: + return "配置文件丢失" + access_dir = "{}/apache/access/{}".format(self._vhost_path, site_name) + if not os.path.isdir(os.path.dirname(access_dir)): + os.makedirs(os.path.dirname(access_dir)) + + if not os.path.isdir(access_dir): + os.makedirs(access_dir) + + pass_dir = "/www/server/pass/" + site_name + if not os.path.isdir(os.path.dirname(pass_dir)): + os.makedirs(os.path.dirname(pass_dir)) + + if not os.path.isdir(pass_dir): + os.makedirs(pass_dir) + + include_conf = ( + "\n #引用访问限制规则,注释后配置的访问限制将无效\n" + " IncludeOptional /www/server/panel/vhost/apache/access/%s/*.conf\n" + ) % site_name + + rep_include = re.compile(r"\s*IncludeOptional.*/access/.*/\*\.conf", re.M) + if rep_include.search(ap_conf): + return + # 添加 引入 + rep_vhost_r = re.compile(r"") + new_conf = rep_vhost_r.sub(include_conf + "", ap_conf) + if not rep_include.search(new_conf): + return "配置添加失败" + + write_file(ap_file, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return "配置添加失败" + + def set_apache_access_by_conf(self, site_name: str, configs: Dict[str, List[Dict[str, str]]]) -> Optional[str]: + """ configs 示例结构 + configs = { + "auth_dir": [ + { + "name": "aaa", + "dir_path": "/", + "auth_file": "/www/server/pass/www.cache.com/aaa.pass", + "username":"aaaa", + "password":"aaaa", + } + ], + "file_deny": [ + { + "name": "bbb", + "dir_path": "/", + "suffix": ["png", "jpg"] + } + ] + } + """ + site_path = DB("sites").where("name=?", (site_name, )).find()["path"] + names = [] + old_configs = [] + access_dir = "{}/apache/access/{}".format(self._vhost_path, site_name) + for i in os.listdir(access_dir): + if not os.path.isfile(os.path.join(access_dir, i)): + continue + old_configs.append((i, read_file(os.path.join(access_dir, i)))) + + for c in chain(configs.get("auth_dir", []), configs.get("file_deny", [])): + if "suffix" in c: + self._set_apache_file_deny(c, site_name) + names.append("deny_{}.conf".format(c["name"])) + else: + self._set_apache_auth_dir(c, site_name, site_path) + names.append("auth_{}.conf".format(c["name"])) + + for i in os.listdir(access_dir): + if i not in names: + os.remove(os.path.join(access_dir, i)) + + if webserver() == "apache" and check_server_config() is not None: + for i in os.listdir(access_dir): + os.remove(os.path.join(access_dir, i)) + for n, data in old_configs: # 还原之前的配置文件 + write_file(os.path.join(access_dir, n), data) + return "配置保存失败" + + def _set_apache_file_deny(self, data: dict, site_name: str): + conf = r''' +#BEGIN_DENY_{n} + + Order allow,deny + Deny from all + +#END_DENY_{n} +'''.format(n=data["name"], d=data["dir_path"], s="|".join(data["suffix"])) + access_file = "{}/apache/access/{}/deny_{}.conf".format(self._vhost_path, site_name, data["name"]) + write_file(access_file, conf) + + def _set_apache_auth_dir(self, data: dict, site_path: str, site_name: str): + conf = ''' + + #AUTH_START + AuthType basic + AuthName "Authorization " + AuthUserFile {auth_file} + Require user {username} + #AUTH_END + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + #Require all granted + DirectoryIndex index.php index.html index.htm default.php default.html default.htm +'''.format(site_path=site_path, site_dir=data["dir_path"], auth_file=data["auth_file"], + username=data["username"], site_name=site_name) + write_file(data["auth_file"], "{}:{}".format(data["username"], self.crypt_password(data["password"]))) + access_file = "{}/apache/access/{}/auth_{}.conf".format(self._vhost_path, site_path, data["name"]) + write_file(access_file, conf) + + +class RealAccessRestriction(_ConfigObject, _ApacheAccessConf, _NginxAccessConf): + _config_file_path = "/www/server/panel/data/site_access.json" + + def __init__(self, config_prefix: str): + super(RealAccessRestriction, self).__init__() + super(_ApacheAccessConf, self).__init__(config_prefix) + + # 把配置信息更新到服务配置文件中 + def _refresh_web_server_conf(self, site_name: str, site_access_conf: dict, web_server=None) -> Optional[str]: + if web_server is None: + web_server = webserver() + error_msg = self.set_apache_access_by_conf(site_name, site_access_conf) + if web_server == "apache" and error_msg is not None: + return error_msg + error_msg = self.set_nginx_access_by_conf(site_name, site_access_conf) + if web_server == "nginx" and error_msg is not None: + return error_msg + + # 添加include配置到对应站点的配置文件中 + def _set_web_server_conf_include(self, site_name, web_server=None) -> Optional[str]: + if web_server is None: + web_server = webserver() + error_msg = self.set_apache_access_include(site_name) + if web_server == "apache" and error_msg is not None: + return error_msg + error_msg = self.set_nginx_access_include(site_name) + if web_server == "nginx" and error_msg is not None: + return error_msg + + def check_auth_dir_args(self, get, is_modify=False) -> Union[str, dict]: + values = {} + try: + values["site_name"] = get.site_name.strip() + values["dir_path"] = get.dir_path.strip() + except AttributeError: + return "参数错误" + + if hasattr(get, "password"): + password = get.password.strip() + if len(password) < 3: + return '密码不能少于3位' + if re.search(r'\s', password): + return '密码不能存在空格' + values['password'] = password + else: + return '请输入密码!' + + if hasattr(get, "username"): + username = get.username.strip() + if len(username) < 3: + return '账号不能少于3位' + if re.search(r'\s', username): + return '账号不能存在空格' + values['username'] = username + else: + return '请输入用户!' + + if hasattr(get, "name"): + name = get.name.strip() + if len(name) < 3: + return '名称不能少于3位' + if re.search(r'\s', name): + return '名称不能存在空格' + if not re.search(r'^\w+$', name): + return '名称格式错误,仅支持数字字母下划线,请参考格式:aaa_bbb' + values['name'] = name + else: + return '请输入名称!' + if not is_modify: + data = self.config.get(values["site_name"], {}).get("auth_dir", []) + for i in data: + if i["dir_path"] == values["dir_path"]: + return "此路径已存在" + if i["name"] == values["name"]: + return "此名称已存在" + + values["auth_file"] = "/www/server/pass/{}/{}.pass".format(values["site_name"], values["name"]) + return values + + def create_auth_dir(self, get) -> Optional[str]: + conf = self.check_auth_dir_args(get, is_modify=False) + if isinstance(conf, str): + return conf + + web_server = webserver() + error_msg = self._set_web_server_conf_include(conf["site_name"], web_server) + if error_msg: + return error_msg + + if conf["site_name"] not in self.config: + self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []} + self.config[conf["site_name"]]["auth_dir"].append(conf) + + error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server) + if error_msg: + return error_msg + self.save_config() + service_reload() + + def modify_auth_dir(self, get) -> Optional[str]: + conf = self.check_auth_dir_args(get, is_modify=True) + if isinstance(conf, str): + return conf + + data = self.config.get(conf["site_name"], {}).get("auth_dir", []) + target_idx = None + for idx, i in enumerate(data): + if i["name"] == conf["name"]: + target_idx = idx + break + if target_idx is None: + return "没有指定的配置信息" + web_server = webserver() + error_msg = self._set_web_server_conf_include(conf["site_name"], web_server) + if error_msg: + return error_msg + if conf["site_name"] not in self.config: + self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []} + + self.config[conf["site_name"]]["auth_dir"][target_idx] = conf + + error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server) + if error_msg: + return error_msg + self.save_config() + service_reload() + + def remove_auth_dir(self, site_name: str, name: str) -> Optional[str]: + if site_name not in self.config: + return "没有该网站的配置" + + target = None + for idx, i in enumerate(self.config[site_name].get("auth_dir", [])): + if i.get("name", None) == name: + target = idx + + if target is None: + return "没有该路径的配置" + + del self.config[site_name]["auth_dir"][target] + web_server = webserver() + error_msg = self._refresh_web_server_conf(site_name, self.config[site_name], web_server) + if error_msg: + return error_msg + self.save_config() + service_reload() + return + + def check_file_deny_args(self, get, is_modify=False) -> Union[str, dict]: + values = {} + try: + values["site_name"] = get.site_name.strip() + values["name"] = get.name.strip() + values["dir_path"] = get.dir_path.strip() + values["suffix"] = list(filter(lambda x: bool(x.strip()), json.loads(get.suffix.strip()))) + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return "参数错误" + + if len(values["name"]) < 3: + return '规则名最少需要输入3个字符串!' + if not values["suffix"]: + return '文件扩展名不可为空!' + if not values["dir_path"]: + return '目录不可为空!' + + if not is_modify: + data = self.config.get(values["site_name"], {}).get("file_deny", []) + for i in data: + if i["dir_path"] == values["dir_path"]: + return "此路径已存在" + if i["name"] == values["name"]: + return "此名称已存在" + return values + + def create_file_deny(self, get) -> Optional[str]: + conf = self.check_file_deny_args(get, is_modify=False) + if isinstance(conf, str): + return conf + web_server = webserver() + error_msg = self._set_web_server_conf_include(conf["site_name"], web_server) + if error_msg: + return error_msg + if conf["site_name"] not in self.config: + self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []} + + self.config[conf["site_name"]]["file_deny"].append(conf) + error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server) + if error_msg: + return error_msg + self.save_config() + service_reload() + + def modify_file_deny(self, get) -> Optional[str]: + conf = self.check_file_deny_args(get, is_modify=True) + if isinstance(conf, str): + return conf + + data = self.config.get(conf["site_name"], {}).get("file_deny", []) + target_idx = None + for idx, i in enumerate(data): + if i["name"] == conf["name"]: + target_idx = idx + break + if target_idx is None: + return "没有指定的配置信息" + web_server = webserver() + error_msg = self._set_web_server_conf_include(conf["site_name"], web_server) + if error_msg: + return error_msg + if conf["site_name"] not in self.config: + self.config[conf["site_name"]] = {"auth_dir": [], "file_deny": []} + + self.config[conf["site_name"]]["file_deny"][target_idx] = conf + + error_msg = self._refresh_web_server_conf(conf["site_name"], self.config[conf["site_name"]], web_server) + if error_msg: + return error_msg + self.save_config() + service_reload() + + def remove_file_deny(self, site_name: str, name: str) -> Optional[str]: + if site_name not in self.config: + return "没有该网站的配置" + + target = None + for idx, i in enumerate(self.config[site_name].get("file_deny", [])): + if i.get("name", None) == name: + target = idx + + if target is None: + return "没有该路径的配置" + + del self.config[site_name]["file_deny"][target] + web_server = webserver() + error_msg = self._refresh_web_server_conf(site_name, self.config[site_name], web_server) + if error_msg: + return error_msg + self.save_config() + service_reload() + return + + def site_access_restriction_info(self, site_name: str) -> dict: + if site_name not in self.config: + return {"auth_dir": [], "file_deny": []} + else: + return self.config[site_name] + + def remove_site_access_restriction_info(self, site_name): + if site_name in self.config: + del self.config["site_name"] + self.save_config() + ng_access_dir = "{}/nginx/access/{}".format(self._vhost_path, site_name) + ap_access_dir = "{}/apache/access/{}".format(self._vhost_path, site_name) + if os.path.isdir(ng_access_dir): + shutil.rmtree(ng_access_dir) + + if os.path.isdir(ap_access_dir): + shutil.rmtree(ap_access_dir) + + +class AccessRestriction: + + def __init__(self, config_prefix: str = ""): + self.config_prefix: str = config_prefix + self._ar = RealAccessRestriction(config_prefix) + + def create_auth_dir(self, get): + res = self._ar.create_auth_dir(get) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, msg="添加成功") + + def modify_auth_dir(self, get): + res = self._ar.modify_auth_dir(get) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, msg="修改成功") + + def remove_auth_dir(self, get): + try: + site_name = get.site_name.strip() + name = get.name.strip() + except AttributeError: + return json_response(status=False, msg="请求参数错误") + res = self._ar.remove_auth_dir(site_name, name) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, msg="删除成功") + + def create_file_deny(self, get): + res = self._ar.create_file_deny(get) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, msg="添加成功") + + def modify_file_deny(self, get): + res = self._ar.modify_file_deny(get) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, msg="修改成功") + + def remove_file_deny(self, get): + try: + site_name = get.site_name.strip() + name = get.name.strip() + except AttributeError: + return json_response(status=False, msg="请求参数错误") + res = self._ar.remove_file_deny(site_name, name) + if isinstance(res, str): + return json_response(status=False, msg=res) + return json_response(status=True, msg="删除成功") + + def site_access_restriction_info(self, get): + try: + site_name = get.site_name.strip() + except AttributeError: + return json_response(status=False, msg="请求参数错误") + data = self._ar.site_access_restriction_info(site_name) + return json_response(status=True, data=data) diff --git a/mod/base/web_conf/config_mgr.py b/mod/base/web_conf/config_mgr.py new file mode 100644 index 00000000..a0741a4c --- /dev/null +++ b/mod/base/web_conf/config_mgr.py @@ -0,0 +1,154 @@ +import os +import time +from hashlib import md5 +from typing import Optional +from .util import service_reload, check_server_config, write_file, read_file + + +# 支持读取配置文件 +# 保存并重启配置文件 +# 历史文件记录 +class ConfigMgr: + _vhost_path = "/www/server/panel/vhost" + + def __init__(self, site_name: str, config_prefix: str = ""): + self.site_name = site_name + self.config_prefix = config_prefix + + def _read_config(self, web_server: str) -> Optional[str]: + config_file = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.config_prefix, self.site_name) + res = read_file(config_file) + if isinstance(res, str): + return res + return None + + def nginx_config(self) -> Optional[str]: + return self._read_config("nginx") + + def apache_config(self) -> Optional[str]: + return self._read_config("apache") + + def save_config(self, conf_data: str, web_server: str): + config_file = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.config_prefix, self.site_name) + old_config = self._read_config(web_server) + write_file(config_file, conf_data) + errmsg = check_server_config() + if errmsg: + write_file(config_file, old_config) + return errmsg + self._save_history(web_server) + service_reload() + + def save_nginx_config(self, conf_data: str) -> Optional[str]: + return self.save_config(conf_data, "nginx") + + def save_apache_config(self, conf_data: str) -> Optional[str]: + return self.save_config(conf_data, "apache") + + def history_list(self): + his_path = '/www/backup/file_history' + nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ng_save_path = "{}{}".format(his_path, nginx_config_file) + apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ap_save_path = "{}{}".format(his_path, apache_config_file) + return { + "nginx": [] if not os.path.isdir(ng_save_path) else sorted(os.listdir(ng_save_path), reverse=True), + "apache": [] if not os.path.isdir(ap_save_path) else sorted(os.listdir(ap_save_path), reverse=True) + } + + def history_conf(self, history_id: str) -> Optional[str]: + his_path = '/www/backup/file_history' + nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ng_save_path = "{}{}".format(his_path, nginx_config_file) + if os.path.isdir(ng_save_path): + for i in os.listdir(ng_save_path): + if i == history_id: + return read_file(os.path.join(ng_save_path, i)) + + apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ap_save_path = "{}{}".format(his_path, apache_config_file) + if os.path.isdir(ap_save_path): + for i in os.listdir(ap_save_path): + if i == history_id: + return read_file(os.path.join(ap_save_path, i)) + return None + + def remove_history_file(self, history_id: str) -> None: + his_path = '/www/backup/file_history' + nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ng_save_path = "{}{}".format(his_path, nginx_config_file) + if os.path.isdir(ng_save_path): + for i in os.listdir(ng_save_path): + if i == history_id: + os.remove(os.path.join(ng_save_path, i)) + + apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ap_save_path = "{}{}".format(his_path, apache_config_file) + if os.path.isdir(ap_save_path): + for i in os.listdir(ap_save_path): + if i == history_id: + os.remove(os.path.join(ng_save_path, i)) + + def clear_history_file(self) -> None: + """ + 清空所有的历史文件 + """ + his_path = '/www/backup/file_history' + nginx_config_file = "{}/nginx/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ng_save_path = "{}{}".format(his_path, nginx_config_file) + if os.path.isdir(ng_save_path): + for i in os.listdir(ng_save_path): + os.remove(os.path.join(ng_save_path, i)) + + apache_config_file = "{}/apache/{}{}.conf".format(self._vhost_path, self.config_prefix, self.site_name) + ap_save_path = "{}{}".format(his_path, apache_config_file) + if os.path.isdir(ap_save_path): + for i in os.listdir(ap_save_path): + os.remove(os.path.join(ng_save_path, i)) + + @staticmethod + def _file_md5(filename): + if not os.path.isfile(filename): + return False + md5_obj = md5() + with open(filename, mode="rb") as f: + while True: + b = f.read(8096) + if not b: + break + md5_obj.update(b) + + return md5_obj.hexdigest() + + def _save_history(self, web_server: str): + if os.path.exists('/www/server/panel/data/not_file_history.pl'): + return True + + his_path = '/www/backup/file_history' + filename = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.config_prefix, self.site_name) + save_path = "{}{}".format(his_path, filename) + if not os.path.isdir(save_path): + os.makedirs(save_path, 384) + + his_list = sorted(os.listdir(save_path), reverse=True) # 倒序排列已有的历史文件 + try: + num = int(read_file('data/history_num.pl')) + except (ValueError, TypeError): + num = 100 + + is_write = True + if len(his_list) > 0: + new_file_md5 = self._file_md5(filename) + last_file_md5 = self._file_md5(os.path.join(save_path, his_list[0])) + is_write = new_file_md5 != last_file_md5 + + if is_write: + new_name = str(int(time.time())) + write_file(os.path.join(save_path, new_name), read_file(filename, 'rb'), "wb") + his_list.insert(0, new_name) + + # 删除多余的副本 + for i in his_list[num:]: + rm_file = save_path + '/' + i + if os.path.exists(rm_file): + os.remove(rm_file) \ No newline at end of file diff --git a/mod/base/web_conf/default_site.py b/mod/base/web_conf/default_site.py new file mode 100644 index 00000000..b7ec0457 --- /dev/null +++ b/mod/base/web_conf/default_site.py @@ -0,0 +1,136 @@ +import json +import os +import re +from typing import Optional, Tuple +from .util import listen_ipv6, write_file, read_file, service_reload + + +def check_default(): + vhost_path = "/www/server/panel/vhost" + nginx = vhost_path + '/nginx' + httpd = vhost_path + '/apache' + httpd_default = ''' + ServerAdmin webmaster@example.com + DocumentRoot "/www/server/apache/htdocs" + ServerName bt.default.com + + SetOutputFilter DEFLATE + Options FollowSymLinks + AllowOverride All + Order allow,deny + Allow from all + DirectoryIndex index.html + + +''' + + listen_ipv6_str = '' + if listen_ipv6(): + listen_ipv6_str = "\n listen [::]:80;" + + nginx_default = '''server +{ + listen 80;%s + server_name _; + index index.html; + root /www/server/nginx/html; +}''' % listen_ipv6_str + + if not os.path.exists(httpd + '/0.default.conf') and not os.path.exists(httpd + '/default.conf'): + write_file(httpd + '/0.default.conf', httpd_default) + if not os.path.exists(nginx + '/0.default.conf') and not os.path.exists(nginx + '/default.conf'): + write_file(nginx + '/0.default.conf', nginx_default) + + +def get_default_site() -> Tuple[Optional[str], Optional[str]]: + panel_path = "/www/server/panel" + + old_ds_file = panel_path + "/data/defaultSite.pl" + new_ds_file = panel_path + "/data/mod_default_site.pl" + if os.path.exists(old_ds_file) and not os.path.exists(new_ds_file): + write_file(new_ds_file, json.dumps({ + "name": read_file(old_ds_file).strip(), + "prefix": '' + })) + + res = read_file(new_ds_file) + if not isinstance(res, str): + return None, None + data = json.loads(res) + return data["name"], data["prefix"] + + +# site_name 传递None的时候,表示将默认站点设置给关闭 +# prefix 表示配置文件前缀, 如 "net_", 默认为空字符串 +# domain 站点的域名 如: "www.sss.com:8456" +def set_default_site(site_name: Optional[str], prefix="", domain: str = None) -> Optional[str]: + # 清理旧的 + old_default_name, old_prefix = get_default_site() + panel_path = "/www/server/panel" + default_site_save = panel_path + '/data/mod_default_site.pl' + if old_default_name: + ng_conf_file = os.path.join(panel_path, "vhost/nginx/{}{}.conf".format(old_prefix, old_default_name)) + old_conf = read_file(ng_conf_file) + if isinstance(old_conf, str): + rep_listen_ds = re.compile(r"listen\s+.*default_server.*;") + new_conf_list = [] + start_idx = 0 + for tmp_res in rep_listen_ds.finditer(old_conf): + new_conf_list.append(old_conf[start_idx: tmp_res.start()]) + new_conf_list.append(tmp_res.group().replace("default_server", "")) + start_idx = tmp_res.end() + + new_conf_list.append(old_conf[start_idx:]) + + write_file(ng_conf_file, "".join(new_conf_list)) + + path = '/www/server/apache/htdocs/.htaccess' + if os.path.exists(path): + os.remove(path) + + if site_name is None: + write_file(default_site_save, json.dumps({ + "name": None, + "prefix": None + })) + service_reload() + return + + # 处理新的 + ap_path = '/www/server/apache/htdocs' + if os.path.exists(ap_path): + conf = ''' + RewriteEngine on + RewriteCond %{{HTTP_HOST}} !^127.0.0.1 [NC] + RewriteRule (.*) http://{}/$1 [L] +'''.format(domain) + + write_file(ap_path + '/.htaccess', conf) + + ng_conf_file = os.path.join(panel_path, "vhost/nginx/{}{}.conf".format(prefix, site_name)) + ng_conf = read_file(ng_conf_file) + if isinstance(ng_conf, str): + rep_listen = re.compile(r"listen[^;]*;") + new_conf_list = [] + + start_idx = 0 + for tmp_res in rep_listen.finditer(ng_conf): + new_conf_list.append(ng_conf[start_idx: tmp_res.start()]) + print(tmp_res.group()) + if tmp_res.group().find("default_server") == -1: + new_conf_list.append(tmp_res.group()[:-1] + " default_server;") + else: + new_conf_list.append(tmp_res.group()) + start_idx = tmp_res.end() + + new_conf_list.append(ng_conf[start_idx:]) + + write_file(ng_conf_file, "".join(new_conf_list)) + + write_file(default_site_save, json.dumps({ + "name": site_name, + "prefix": prefix + })) + + service_reload() + return diff --git a/mod/base/web_conf/dir_tool.py b/mod/base/web_conf/dir_tool.py new file mode 100644 index 00000000..5e7cd6ae --- /dev/null +++ b/mod/base/web_conf/dir_tool.py @@ -0,0 +1,252 @@ +# 网站文件相关操作 + +import os +import re +from typing import Optional, Union, List + +from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, pre_re_key, ExecShell + + +class DirTool: + + def __init__(self, conf_prefix: str = ""): + self.conf_prefix = conf_prefix + self._vhost_path = "/www/server/panel/vhost" + + # 修改站点路径 + def modify_site_path(self, site_name: str, old_site_path: str, new_site_path: str) -> Optional[str]: + """ + 修改 站点root 路径 + site_name 站点名称 + old_site_path 旧的root 路径 + new_site_path 新的root 路径 + """ + site_info = DB("sites").where("name=?", (site_name,)).find() + if not isinstance(site_info, dict): + return "站点信息查询错误" + + error_msg = check_server_config() + if error_msg: + return "服务配置无法重载,请检查配置错误再操作。\n" + error_msg + + if not self._check_site_path(new_site_path): + return '请不要将网站根目录设置到以下关键目录中' + + if not os.path.exists(new_site_path): + return '指定的网站根目录不存在,无法设置,请检查输入信息.' + if old_site_path[-1] == '/': + old_site_path = old_site_path[:-1] + + if new_site_path[-1] == '/': + new_site_path = new_site_path[:-1] + + old_run_path = self.get_site_run_path(site_name) + if old_run_path is None: + return '读取网站当前运行目录失败,请检查配置文件' + old_run_path_sub = old_run_path.replace(old_site_path, "") + new_run_path = new_site_path + old_run_path_sub + if not os.path.exists(new_site_path): + new_run_path = new_site_path + nginx_file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + nginx_conf = read_file(nginx_file) + if nginx_conf: + rep_root = re.compile(r'\s*root\s+(.+);', re.M) + new_conf = rep_root.sub(" root {};".format(new_run_path), nginx_conf) + write_file(nginx_file, new_conf) + + apache_file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + apache_conf = read_file(apache_file) + if apache_conf: + rep_doc = re.compile(r"DocumentRoot\s+.*\n") + new_conf = rep_doc.sub('DocumentRoot "' + new_run_path + '"\n', apache_conf) + + rep_dir = re.compile(r'''\n', new_conf) + write_file(apache_file, new_conf) + + # 创建basedir + userIni = new_run_path + '/.user.ini' + if os.path.exists(userIni): + ExecShell("chattr -i " + userIni) + write_file(userIni, 'open_basedir=' + new_run_path + '/:/tmp/') + ExecShell('chmod 644 ' + userIni) + ExecShell('chown root:root ' + userIni) + ExecShell('chattr +i ' + userIni) + service_reload() + DB("sites").where("id=?", (site_info["id"],)).setField('path', new_site_path) + return + + # 修改站点的运行路径 + def modify_site_run_path(self, site_name, site_path, new_run_path_sub: str) -> Optional[str]: + """ + 修改 站点运行路径 + site_name 站点名称 + site_path 站点路径 + new_run_path_sub root路径的子运行目录 + 如 site_path -> /www/wwwroots/aaaa + new_run_path_sub -> bbb/ccc + new_run_path -> /www/wwwroots/aaaa/bbb/ccc + """ + # 处理Nginx + old_run_path = self.get_site_run_path(site_name) + if old_run_path is None: + return '读取网站当前运行目录失败,请检查配置文件' + if new_run_path_sub.startswith("/"): + new_run_path_sub = new_run_path_sub[1:] + new_run_path = os.path.join(site_path, new_run_path_sub) + filename = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + nginx_conf = read_file(filename) + if nginx_conf: + tmp = re.search(r'\s*root\s+(.+);', nginx_conf) + if tmp: + o_path = tmp.groups()[0] + new_conf = nginx_conf.replace(o_path, new_run_path) + write_file(filename, new_conf) + + # 处理Apache + filename = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + ap_conf = read_file(filename) + if ap_conf: + tmp = re.search(r'\s*DocumentRoot\s*"(.+)"\s*\n', ap_conf) + if tmp: + o_path = tmp.groups()[0] + new_conf = ap_conf.replace(o_path, new_run_path) + write_file(filename, new_conf) + + s_path = old_run_path + "/.user.ini" + d_path = new_run_path + "/.user.ini" + if s_path != d_path: + ExecShell("chattr -i {}".format(s_path)) + ExecShell("mv {} {}".format(s_path, d_path)) + ExecShell("chattr +i {}".format(d_path)) + + service_reload() + + # 获取站点的运行路径, 返回的路径是完整路径 + def get_site_run_path(self, site_name) -> Optional[str]: + web_server = webserver() + filename = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.conf_prefix, site_name) + if not os.path.exists(filename): + return None + run_path = None + conf = read_file(filename) + if web_server == 'nginx': + tmp1 = re.search(r'\s*root\s+(?P.+);', conf) + if tmp1: + run_path = tmp1.group("path").strip() + elif web_server == 'apache': + tmp1 = re.search(r'\s*DocumentRoot\s*"(?P.+)"\s*\n', conf) + if tmp1: + run_path = tmp1.group("path") + else: + tmp1 = re.search(r"vhRoot\s*(?P.*)", conf) + if tmp1: + run_path = tmp1.group("path").strip() + + return run_path + + # 获取index 文件 + def get_index_conf(self, site_name) -> Union[str, List[str]]: + web_server = webserver() + filename = "{}/{}/{}{}.conf".format(self._vhost_path, web_server, self.conf_prefix, site_name) + if not os.path.exists(filename): + return "配置文件丢失" + conf = read_file(filename) + if not conf: + return "配置文件丢失" + split_char = " " + if web_server == 'nginx': + rep = re.compile(r"\s+index\s+(?P.+);", re.M) + elif web_server == 'apache': + rep = re.compile(r"DirectoryIndex\s+(?P.+)", re.M) + else: + rep = re.compile(r"indexFiles\s+(?P.+)", re.M) + split_char = "," + res = rep.search(conf) + if not res: + return "获取失败,配置文件中不存在默认文档" + + res_list = list(filter(None, map(lambda x: x.strip(), res.group("target").split(split_char)))) + + return res_list + + # 获取设置index 文件 可以用 filenames 参数依次传入多个, 或 通过 file_list 参数传入index 列表 + def set_index_conf(self, site_name, *filenames: str, file_list: Optional[List[str]] = None): + index_list = set() + for i in filenames: + f = i.strip() + if not f: + continue + index_list.add(f) + + if file_list is not None: + for i in file_list: + f = i.strip() + if not f: + continue + index_list.add(f) + + # nginx + file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = read_file(file) + if conf: + rep_index = re.compile(r"\s*index\s+.+;") + new_conf = rep_index.sub(" index {};".format(" ".join(index_list)), conf) + write_file(file, new_conf) + + # apache + file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = read_file(file) + if conf: + rep_index = re.compile(r"\s*DirectoryIndex\s+.+\n") + new_conf = rep_index.sub(" DirectoryIndex {}\n".format(" ".join(index_list)), conf) + write_file(file, new_conf) + + # openlitespeed + file = '{}/openlitespeed/detail/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = read_file(file) + if conf: + rep_index = re.compile(r"indexFiles\s+.+\n") + new_conf = rep_index.sub('indexFiles {}\n'.format(",".join(index_list)), conf) + write_file(file, new_conf) + + service_reload() + return + + def _check_site_path(self, site_path): + try: + if site_path.find('/usr/local/lighthouse/') >= 0: + return True + + if site_path in ['/', '/usr', '/dev', '/home', '/media', '/mnt', '/opt', '/tmp', '/var']: + return False + whites = ['/www/server/tomcat', '/www/server/stop', '/www/server/phpmyadmin'] + for w in whites: + if site_path.find(w) == 0: + return True + a, error_paths = self._get_sys_path() + site_path = site_path.strip() + if site_path[-1] == '/': site_path = site_path[:-1] + if site_path in a: + return False + site_path += '/' + for ep in error_paths: + if site_path.find(ep) == 0: + return False + return True + except: + return False + + @staticmethod + def _get_sys_path(): + """ + @name 关键目录 + @author hwliang<2021-06-11> + @return tuple + """ + a = ['/www', '/usr', '/', '/dev', '/home', '/media', '/mnt', '/opt', '/tmp', '/var'] + c = ['/www/.Recycle_bin/', '/www/backup/', '/www/php_session/', '/www/wwwlogs/', '/www/server/', '/etc/', + '/usr/', '/var/', '/boot/', '/proc/', '/sys/', '/tmp/', '/root/', '/lib/', '/bin/', '/sbin/', '/run/', + '/lib64/', '/lib32/', '/srv/'] + return a, c + diff --git a/mod/base/web_conf/dns_api.py b/mod/base/web_conf/dns_api.py new file mode 100644 index 00000000..85e7664d --- /dev/null +++ b/mod/base/web_conf/dns_api.py @@ -0,0 +1,1682 @@ +# coding: utf-8 +# +------------------------------------------------------------------- +# | 宝塔Linux面板 x3 +# +------------------------------------------------------------------- +# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# +------------------------------------------------------------------- +# | Author: bazoi +# +-------------------------------------------------------------------- +# | 宝塔DNS管理平台 +# +-------------------------------------------------------------------- +import shutil +import sys +import os +import json +import random +import datetime +import hmac +import re +import base64 +from urllib.parse import urljoin +from hashlib import sha1 +from uuid import uuid4 +from itertools import chain +from typing import Set, List, Optional, Union, Dict, Tuple +from mod.base import json_response + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + +try: + import requests +except: + public.ExecShell('pip install requests') + import requests + +caa_value = '0 issue "letsencrypt.org"' + + +class ExtractZoneTool(object): + def __init__(self): + self.top_domain_list = [ + '.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn', + '.gov.cn', '.gs.cn', '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn', + '.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', '.jl.cn', '.js.cn', '.jx.cn', + '.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn'] + top_domain_list_data = public.readFile('{}/config/domain_root.txt'.format(public.get_panel_path())) + if top_domain_list_data: + self.top_domain_list = set(top_domain_list_data.strip().split('\n')) + + def __call__(self, domain_name): + if domain_name.startswith('*.'): + domain_name = domain_name[2:] + domain_name_copy = domain_name + top_domain = "." + ".".join(domain_name.rsplit('.')[-2:]) + new_top_domain = "." + top_domain.replace(".", "") + is_tow_top = False + if top_domain in self.top_domain_list: + is_tow_top = True + domain_name = domain_name[:-len(top_domain)] + new_top_domain + + if domain_name.count(".") <= 1: + zone = "" + root = domain_name_copy + else: + zone, middle, last = domain_name.rsplit(".", 2) + if is_tow_top: + last = top_domain[1:] + root = ".".join([middle, last]) + + return root, zone + + +extract_zone = ExtractZoneTool() + + +class BaseDns(object): + def __init__(self): + self.dns_provider_name = self.__class__.__name__ + + @staticmethod + def load_response(response): + try: + log_body = response.json() + except ValueError: + log_body = response.content + return log_body + + def add_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + raise NotImplementedError("add_record method must be implemented.") + + def remove_record(self, domain: str, host: Optional[str] = None, r_type: Optional[str] = None): + raise NotImplementedError("remove_record method must be implemented.") + + def modify_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + raise NotImplementedError("modify_record method must be implemented.") + + def get_record_list(self, domain, host: Optional[str] = None, r_type: Optional[str] = None): + raise NotImplementedError("get_record_list must be implemented.") + + def get_record_list_by_view(self, domain): + raise NotImplementedError("get_record_list must be implemented.") + + def get_domain_list(self): + return [] + + @classmethod + def new(cls, conf_data): + raise NotImplementedError("new method must be implemented.") + + def transform_record_type(self, r_type: str): + if r_type not in ("显性URL", "隐性URL"): + return r_type + self_cls = self.__class__ + if self_cls is AliyunDns: + return "REDIRECT_URL" if r_type == "显性URL" else "FORWARD_URL" + elif self_cls is CloudFlareDns: + return "URI" + + +class DNSPodDns(BaseDns): + dns_provider_name = "dnspod" + + def __init__(self, DNSPOD_ID, DNSPOD_API_KEY, DNSPOD_API_BASE_URL="https://dnsapi.cn/"): + self.DNSPOD_ID = DNSPOD_ID + self.DNSPOD_API_KEY = DNSPOD_API_KEY + self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + self.HTTP_TIMEOUT = 65 # seconds + self.DNSPOD_LOGIN = "{0},{1}".format(self.DNSPOD_ID, self.DNSPOD_API_KEY) + + if DNSPOD_API_BASE_URL[-1] != "/": + self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + "/" + else: + self.DNSPOD_API_BASE_URL = DNSPOD_API_BASE_URL + super(DNSPodDns, self).__init__() + + def add_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + url = urljoin(self.DNSPOD_API_BASE_URL, "Record.Create") + body = { + "record_type": r_type, + "domain": domain, + "sub_domain": host, + "value": value, + "record_line_id": "0", + "format": "json", + "weight": weight, + "ttl": ttl, + "login_token": self.DNSPOD_LOGIN, + } + add_response = requests.post( + url, data=body, timeout=self.HTTP_TIMEOUT + ).json() + if add_response["status"]["code"] != "1": + raise ValueError( + "Error creating dnspod dns record: status_code={status_code} response={response}".format( + status_code=add_response["status"]["code"], + response=add_response["status"]["message"], + ) + ) + + def get_record_list_by_view(self, domain): + records = self.get_record_list(domain) + if records is None: + return [] + res = [] + for record in records: + res.append({ + "host": record['name'], + "type": record['type'], + "value": record['value'], + "ttl": record['ttl'], + "updated_on": datetime.datetime.strptime(record['updated_on'], "%Y-%m-%d %H:%M:%S").timestamp(), + }) + return res + + def get_record_list(self, domain, host: Optional[str] = None, r_type: Optional[str] = None): + url = urljoin(self.DNSPOD_API_BASE_URL, "Record.List") + body = { + "login_token": self.DNSPOD_LOGIN, + "format": "json", + "domain": domain + } + if host is not None and isinstance(host, str): + body["subdomain"] = host + if r_type is not None and isinstance(r_type, str): + body["record_type"] = r_type + + list_response = requests.post(url, data=body, timeout=self.HTTP_TIMEOUT).json() + if "records" in list_response: + return list_response["records"] + else: + return None + + def remove_record(self, domain, host: Optional[str] = None, r_type: Optional[str] = None): + records = self.get_record_list(domain, host, r_type) + if records is None: + return False, "未查询到记录信息,没有执行删除" + for record in records: + if record['name'] != host or record['type'] != r_type: + continue + record_id = record["id"] + url = urljoin(self.DNSPOD_API_BASE_URL, "Record.Remove") + body = { + "login_token": self.DNSPOD_LOGIN, + "format": "json", + "domain": domain, + "record_id": record_id, + } + requests.post( + url, data=body, timeout=self.HTTP_TIMEOUT + ).json() + + return True, "删除成功" + + def modify_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + records = self.get_record_list(domain, host, r_type) + if records is None: + return False, "未查询到记录信息,无法执行修改" + + for record in records: + if record['name'] != host and record['type'] != r_type: + continue + + record_id = record["id"] + url = urljoin(self.DNSPOD_API_BASE_URL, "Record.Modify") + body = { + "record_type": r_type, + "record_id": record_id, + "domain": domain, + "sub_domain": host, + "value": value, + "record_line_id": "0", + "format": "json", + "weight": weight, + "ttl": ttl, + "login_token": self.DNSPOD_LOGIN, + } + add_response = requests.post( + url, data=body, timeout=self.HTTP_TIMEOUT + ).json() + + if add_response["status"]["code"] != "1": + raise ValueError( + "Error creating dnspod dns record: status_code={status_code} response={response}".format( + status_code=add_response["status"]["code"], + response=add_response["status"]["message"], + ) + ) + + def get_domain_list(self): + url = urljoin(self.DNSPOD_API_BASE_URL, "Domain.List") + body = { + "format": "json", + "login_token": self.DNSPOD_LOGIN, + } + domain_list_resp = requests.post( + url, data=body, timeout=self.HTTP_TIMEOUT + ).json() + if domain_list_resp["status"]["code"] != "1": + return {} + domains = domain_list_resp["domains"] + return {d["name"]: d for d in domains} + + @classmethod + def new(cls, conf_data: dict) -> BaseDns: + key = conf_data.get("key", None) or conf_data.get("ID", "") + secret = conf_data.get("secret", None) or conf_data.get("Token", "") + base_url = "https://dnsapi.cn/" + + return cls(key, secret, base_url) + + +class CloudFlareDns(BaseDns): + dns_provider_name = "cloudflare" + + def __init__( + self, + CLOUDFLARE_EMAIL, + CLOUDFLARE_API_KEY, + CLOUDFLARE_API_BASE_URL="https://api.cloudflare.com/client/v4/", + ): + self.CLOUDFLARE_EMAIL = CLOUDFLARE_EMAIL + self.CLOUDFLARE_API_KEY = CLOUDFLARE_API_KEY + self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + self.HTTP_TIMEOUT = 65 # seconds + + self._domain_zone_id_cache = {} + + if CLOUDFLARE_API_BASE_URL[-1] != "/": + self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + "/" + else: + self.CLOUDFLARE_API_BASE_URL = CLOUDFLARE_API_BASE_URL + super(CloudFlareDns, self).__init__() + + def _get_auth_headers(self) -> dict: + if self.CLOUDFLARE_EMAIL is None and isinstance(self.CLOUDFLARE_API_KEY, str): + return {"Authorization": "Bearer " + self.CLOUDFLARE_API_KEY} + else: + return {"X-Auth-Email": self.CLOUDFLARE_EMAIL, "X-Auth-Key": self.CLOUDFLARE_API_KEY} + + def find_dns_zone(self, domain): + for domain_key, zone_id in self._domain_zone_id_cache.items(): + if domain_key in domain: + return self._domain_zone_id_cache[domain_key] + + url = urljoin(self.CLOUDFLARE_API_BASE_URL, "zones?status=active&per_page=1000") + headers = self._get_auth_headers() + find_dns_zone_response = requests.get(url, headers=headers, timeout=self.HTTP_TIMEOUT) + if find_dns_zone_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=find_dns_zone_response.status_code, + response=self.load_response(find_dns_zone_response), + ) + ) + + result = find_dns_zone_response.json()["result"] + self._domain_zone_id_cache = {} + for i in result: + self._domain_zone_id_cache[i["name"]] = i["id"] + + for domain_key, zone_id in self._domain_zone_id_cache.items(): + if domain_key in domain: + return self._domain_zone_id_cache[domain_key] + + raise ValueError( + ("Error unable to get DNS zone for domain_name={domain_name}: " + "status_code={status_code} response={response}").format( + domain_name=domain, + status_code=find_dns_zone_response.status_code, + response=self.load_response(find_dns_zone_response), + ) + ) + + def get_domain_list(self): + url = urljoin(self.CLOUDFLARE_API_BASE_URL, "zones?status=active&per_page=1000") + headers = self._get_auth_headers() + find_dns_zone_response = requests.get(url, headers=headers, timeout=self.HTTP_TIMEOUT) + if find_dns_zone_response.status_code != 200: + return {} + domains = find_dns_zone_response.json()["result"] + return {d["name"]: d for d in domains} + + def add_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + zone_id = self.find_dns_zone(domain) + url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records".format(zone_id), + ) + headers = self._get_auth_headers() + body = { + "type": r_type, + "name": host, + "content": "{0}".format(value), + "ttl": ttl + } + + create_cloudflare_dns_record_response = requests.post( + url, headers=headers, json=body, timeout=self.HTTP_TIMEOUT + ) + if create_cloudflare_dns_record_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=create_cloudflare_dns_record_response.status_code, + response=self.load_response(create_cloudflare_dns_record_response), + ) + ) + + def get_record_list_by_view(self, domain): + records = self.get_record_list(domain) + if records is None: + return [] + res = [] + for record in records: + res.append({ + "host": record['name'], + "type": record['type'], + "value": record['content'], + "ttl": record['ttl'], + "modified_on": datetime.datetime.strptime(record['updated_on'], "%Y-%m-%dT%H:%M:%S.%fZ").timestamp(), + }) + return res + + def get_record_list(self, domain, host: Optional[str] = None, r_type: Optional[str] = None): + zone_id = self.find_dns_zone(domain) + list_dns_url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records?per_page=50000".format(zone_id), + ) + list_dns_payload = {} + if host is not None and isinstance(host, str): + list_dns_payload["name"] = host + if r_type is not None and isinstance(r_type, str): + list_dns_payload["type"] = r_type + + headers = self._get_auth_headers() + list_response = requests.get( + list_dns_url, params=list_dns_payload, headers=headers, timeout=self.HTTP_TIMEOUT + ).json() + if "success" and list_response["success"] is False: + return None + if "result" in list_response: + return list_response["result"] + else: + return None + + def remove_record(self, domain: str, host: Optional[str] = None, r_type: Optional[str] = None): + headers = self._get_auth_headers() + zone_id = self.find_dns_zone(domain) + records = self.get_record_list(domain, host, r_type) + if records is None: + return False, "未查询到记录信息,无法执行修改" + + for record in records: + if record['name'] != host or record['type'] != r_type: + continue + dns_record_id = record["id"] + url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records/{1}".format(zone_id, dns_record_id), + ) + requests.delete( + url, headers=headers, timeout=self.HTTP_TIMEOUT + ) + + def modify_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + records = self.get_record_list(domain, host, r_type) + if records is None: + return False, "未查询到记录信息,无法执行修改" + + headers = self._get_auth_headers() + zone_id = self.find_dns_zone(domain) + + for record in records: + if record['name'] != host and record['type'] != r_type: + continue + + record_id = record["id"] + url = urljoin( + self.CLOUDFLARE_API_BASE_URL, + "zones/{0}/dns_records/{1}".format(zone_id, record_id), + ) + + body = { + "type": r_type, + "name": host, + "content": "{0}".format(value), + "ttl": ttl + } + modify_response = requests.put( + url, data=body, timeout=self.HTTP_TIMEOUT, headers=headers + ).json() + + if modify_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=modify_response.status_code, + response=self.load_response(modify_response), + ) + ) + + @classmethod + def new(cls, conf_data: dict) -> BaseDns: + key = conf_data.get("key", None) or conf_data.get("E-Mail", None) + secret = conf_data.get("secret", None) or conf_data.get("API Key", None) + base_url = "https://api.cloudflare.com/client/v4/" + + if key is None and secret is None: + secret = conf_data.get("API Token", None) # 处理api - token的情况 + if key is None and secret is None: + raise Exception("没有找到有效的DNS API密钥信息") + return cls(key, secret, base_url) + + +class AliyunDns(BaseDns): + dns_provider_name = "alidns" + + def __init__(self, key, secret): + self.key = str(key).strip() + self.secret = str(secret).strip() + self.url = "https://alidns.aliyuncs.com" + super(AliyunDns, self).__init__() + + def sign(self, accessKeySecret, parameters): # '''签名方法 + def percent_encode(encodeStr): + import urllib.request + encodeStr = str(encodeStr) + res = urllib.request.quote(encodeStr, '') + res = res.replace('+', '%20') + res = res.replace('*', '%2A') + res = res.replace('%7E', '~') + return res + + sortedParameters = sorted(parameters.items(), key=lambda parameters: parameters[0]) + canonicalizedQueryString = '' + for (k, v) in sortedParameters: + canonicalizedQueryString += '&' + percent_encode(k) + '=' + percent_encode(v) + stringToSign = 'GET&%2F&' + percent_encode(canonicalizedQueryString[1:]) + if sys.version_info[0] == 2: + h = hmac.new(accessKeySecret + "&", stringToSign, sha1) + else: + h = hmac.new(bytes(accessKeySecret + "&", encoding="utf8"), stringToSign.encode('utf8'), sha1) + signature = base64.encodebytes(h.digest()).strip() + return signature + + def add_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + random_int = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + paramsdata = { + "Action": "AddDomainRecord", + "Format": "json", + "Version": "2015-01-09", + "SignatureMethod": "HMAC-SHA1", + "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", + "SignatureNonce": str(random_int), + "AccessKeyId": self.key, + "DomainName": domain, + "RR": host, + "Type": r_type, + "Value": value, + "TTL": ttl, + } + + Signature = self.sign(self.secret, paramsdata) + paramsdata['Signature'] = Signature + req = requests.get(url=self.url, params=paramsdata) + if req.status_code != 200: + if req.json()['Code'] == 'IncorrectDomainUser' or req.json()['Code'] == 'InvalidDomainName.NoExist': + raise ValueError("这个阿里云账户下面不存在这个域名,添加解析失败") + elif req.json()['Code'] == 'InvalidAccessKeyId.NotFound' or req.json()['Code'] == 'SignatureDoesNotMatch': + raise ValueError("API密钥错误,添加解析失败") + else: + raise ValueError(req.json()['Message']) + + def get_record_list(self, domain, host: Optional[str] = None, r_type: Optional[str] = None): + random_int = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + params_data = { + "Action": "DescribeDomainRecords", + "Format": "json", + "Version": "2015-01-09", + "SignatureMethod": "HMAC-SHA1", + "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", + "SignatureNonce": str(random_int), + "AccessKeyId": self.key, + "DomainName": domain, + "PageNumber": 1, + "PageSize": 1000, + } + if host is not None and isinstance(host, str): + params_data["RRKeyWord"] = host + if r_type is not None and isinstance(r_type, str): + params_data["TypeKeyWord"] = r_type + + Signature = self.sign(self.secret, params_data) + params_data['Signature'] = Signature + req_data = requests.get(url=self.url, params=params_data).json() + record_list = req_data.get("DomainRecords", {}).get("Record", []) + return record_list + + def get_record_list_by_view(self, domain): + records = self.get_record_list(domain) + if records is None: + return [] + res = [] + for record in records: + res.append({ + "host": record['RR'], + "type": record['Type'], + "value": record['Value'], + "ttl": record['ttl'], + "modified_on": record['UpdateTimestamp'], + }) + return res + + def remove_record(self, domain: str, host: Optional[str] = None, r_type: Optional[str] = None): + records = self.get_record_list(domain, host, r_type) + if records is None: + return False, "未查询到记录信息,无法执行修改" + + random_int = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + params_data = { + "Action": "DeleteDomainRecord", + "Format": "json", + "Version": "2015-01-09", + "SignatureMethod": "HMAC-SHA1", + "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", + "SignatureNonce": str(random_int), + "AccessKeyId": self.key, + "RecordId": None, + } + for record in records: + if record['RR'] != host or record['Type'] != r_type: + continue + record_id = record["RecordId"] + p_data = params_data.copy() + p_data["RecordId"] = record_id + + Signature = self.sign(self.secret, p_data) + p_data['Signature'] = Signature + req = requests.get(url=self.url, params=p_data) + if req.status_code != 200: + raise ValueError("删除解析记录失败") + + def modify_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + records = self.get_record_list(domain, host, r_type) + if records is None: + return False, "未查询到记录信息,无法执行修改" + + random_int = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + params_data = { + "Action": "UpdateDomainRecord", + "Format": "json", + "Version": "2015-01-09", + "SignatureMethod": "HMAC-SHA1", + "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", + "SignatureNonce": str(random_int), + "AccessKeyId": self.key, + "RR": host, + "Type": r_type, + "Value": value, + "TTL": ttl, + "RecordId": None + } + + for record in records: + if record['RR'] != host and record['Type'] != r_type: + continue + + record_id = record["RecordId"] + p_data = params_data.copy() + p_data["RecordId"] = record_id + + Signature = self.sign(self.secret, p_data) + p_data['Signature'] = Signature + req = requests.get(url=self.url, params=p_data) + if req.status_code != 200: + if req.json()['Code'] == 'IncorrectDomainUser' or req.json()['Code'] == 'InvalidDomainName.NoExist': + raise ValueError("这个阿里云账户下面不存在这个域名,添加解析失败") + elif req.json()['Code'] == 'InvalidAccessKeyId.NotFound' or req.json()['Code'] == 'SignatureDoesNotMatch': + raise ValueError("API密钥错误,添加解析失败") + else: + raise ValueError(req.json()['Message']) + + def get_domain_list(self): + random_int = random.randint(11111111111111, 99999999999999) + now = datetime.datetime.utcnow() + otherStyleTime = now.strftime("%Y-%m-%dT%H:%M:%SZ") + paramsdata = { + "Action": "DescribeDomains", + "Format": "json", + "Version": "2015-01-09", + "SignatureMethod": "HMAC-SHA1", + "Timestamp": otherStyleTime, + "SignatureVersion": "1.0", + "SignatureNonce": str(random_int), + "AccessKeyId": self.key, + "PageNumber": 1, + "PageSize": 1000, + } + + Signature = self.sign(self.secret, paramsdata) + paramsdata['Signature'] = Signature + req = requests.get(url=self.url, params=paramsdata) + if req.status_code != 200: + req_json = req.json() + if req_json['Code'] == 'IncorrectDomainUser' or req_json['Code'] == 'InvalidDomainName.NoExist': + raise ValueError("这个阿里云账户下面不存在这个域名,添加解析失败") + elif req_json['Code'] == 'InvalidAccessKeyId.NotFound' or req_json['Code'] == 'SignatureDoesNotMatch': + raise ValueError("API密钥错误,添加解析失败") + else: + raise ValueError(req_json['Message']) + + domains = req.json()["Domains"]["Domain"] + return {d["DomainName"]: d for d in domains} + + @classmethod + def new(cls, conf_data: dict) -> "AliyunDns": + key = conf_data.get("key", None) or conf_data.get("AccessKey", "") + secret = conf_data.get("secret", None) or conf_data.get("SecretKey", "") + + return cls(key, secret) + + +# 未完善请勿使用 +class GoDaddyDns(BaseDns): + + _type = 0 # 0:lest 1:锐成 + http_timeout = 65 + + def __init__(self, sso_key: str, sso_secret: str, base_url='https://api.godaddy.com'): + self.sso_key = sso_key + self.sso_secret = sso_secret + self.base_url = base_url + super(GoDaddyDns, self).__init__() + self._headers = None + + def _get_auth_headers(self) -> dict: + if self._headers is not None: + return self._headers + self._headers = { + "Authorization": "sso-key {}:{}".format(self.sso_key, self.sso_secret) + } + return self._headers + + + def add_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + url = urljoin( + self.base_url, + "/v1/domains/{}/records".format(root), + ) + headers = self._get_auth_headers() + + body = [{ + "type": s_type, + "name": host, + "data": "{0}".format(value), + }] + + create_cloudflare_dns_record_response = requests.patch( + url, headers=headers, json=body, timeout=self.http_timeout + ) + if create_cloudflare_dns_record_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=create_cloudflare_dns_record_response.status_code, + response=self.load_response(create_cloudflare_dns_record_response), + ) + ) + + def modify_record(self, domain: str, host: str, r_type: str, value: str, weight: int = 1, ttl=600): + pass + + + def remove_record(self, domain: str, host: Optional[str] = None, r_type: Optional[str] = None): + headers = self._get_auth_headers() + + list_dns_url = urljoin( + self.base_url, + "/v1/domains/{}/records/{}/{}".format(domain_name, s_type, dns_name), + ) + + dns_response = requests.delete( + list_dns_url, headers=headers, timeout=self.http_timeout + ) + if dns_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=dns_response.status_code, + response=self.load_response(dns_response), + ) + ) + + def get_record_list(self, domain, host: Optional[str] = None, r_type: Optional[str] = None): + pass + + def get_record_list_by_view(self, domain): + pass + + + @classmethod + def new(cls, conf_data: dict) -> BaseDns: + key = conf_data.get("key", None) or conf_data.get("Key", "") + secret = conf_data.get("secret", None) or conf_data.get("Secret", "") + base_url = "https://api.godaddy.com" + + return cls(key, secret, base_url) + + +# 未上线 +class DNSLADns(BaseDns): + dns_provider_name = "dnsla" + _type = 0 # 0:lest 1:锐成 + + def __init__(self, api_id, api_secret): + self.api_id = api_id + self.api_secret = api_secret + self.base_url = "https://api.dns.la" + self.http_timeout = 65 # seconds + self._token = None + self.domain_list = None + super(DNSLADns, self).__init__() + + @classmethod + def new(cls, conf_data) -> BaseDns: + key = conf_data.get("key", None) or conf_data.get("APIID", "") + secret = conf_data.get("secret", None) or conf_data.get("API密钥", "") + + return cls(key, secret) + + def _get_auth_headers(self) -> dict: + if self._token is None: + self._token = base64.b64encode("{}:{}".format(self.api_id, self.api_secret).encode("utf-8")).decode("utf-8") + return {"Authorization": "Basic " + self._token} + + def find_dns_zone(self, domain_name): + url = urljoin(self.base_url, "/api/domainList?pageIndex=1&pageSize=1000") + headers = self._get_auth_headers() + find_dns_zone_response = requests.get(url, headers=headers, timeout=self.http_timeout) + if find_dns_zone_response.status_code != 200: + raise ValueError( + "Error creating DNS.LA domains: status_code={status_code} response={response}".format( + status_code=find_dns_zone_response.status_code, + response=self.load_response(find_dns_zone_response), + ) + ) + + result = find_dns_zone_response.json()["data"]["results"] + self.domain_list = result + have = False + for domain_data in result: + if domain_data["domain"].rstrip(".") == domain_name: + have = True + break + + if not have: + raise ValueError( + ( + "Error unable to get DNS zone for domain_name={domain_name}: " + "status_code={status_code} response={response}" + ).format( + domain_name=domain_name, + status_code=find_dns_zone_response.status_code, + response=self.load_response(find_dns_zone_response), + ) + ) + + @staticmethod + def _format_s_type_to_request(s_type): + trans = { + "A": 1, + "NS": 2, + "CNAME": 5, + "MX": 15, + "TXT": 16, + "AAAA": 28, + "SRV": 33, + "CAA": 257, + "URL": 256 + } + + if isinstance(s_type, (int, float)): + if int(s_type) in trans.values(): + return int(s_type) + + if isinstance(s_type, str): + if s_type in trans: + return trans[s_type] + return 16 + + def add_record(self, domain, s_type, host, value): + url = urljoin(self.base_url, "/api/record", ) + headers = self._get_auth_headers() + + domain_id = self._get_domain_id(domain) + + body = { + "domainId": domain_id, + "type": self._format_s_type_to_request(s_type), + "host": host, + "data": value, + "ttl": 600, + } + + create_dns_la_record_response = requests.post( + url, headers=headers, json=body, timeout=self.http_timeout + ) + if create_dns_la_record_response.status_code != 200: + raise ValueError( + "Error creating cloudflare dns record: status_code={status_code} response={response}".format( + status_code=create_dns_la_record_response.status_code, + response=self.load_response(create_dns_la_record_response), + ) + ) + + def create_dns_record(self, domain_name, domain_dns_value): + domain_name = domain_name.lstrip("*.") + root, zone, acme_txt = extract_zone(domain_name) + self.find_dns_zone(root) + + if self._type == 1: + return self.add_record(root, 'CNAME', acme_txt.replace('_acme-challenge.', ''), domain_dns_value) + else: + return self.add_record(root, 'TXT', acme_txt, domain_dns_value) + + def add_record_for_creat_site(self, domain, server_ip): + root, zone, _ = extract_zone(domain) + self.add_record(root, "A", zone, server_ip) + + def get_record_list(self, domain_id: str) -> list: + url = urljoin(self.base_url, "/api/recordList?pageIndex=1&pageSize=10&domainId={}".format(domain_id)) + headers = self._get_auth_headers() + get_record_list_response = requests.get(url, headers=headers, timeout=self.http_timeout) + if get_record_list_response.status_code != 200: + raise ValueError( + "Error unable to get record list : status_code={status_code} response={response}".format( + status_code=get_record_list_response.status_code, + response=self.load_response(get_record_list_response), + ) + ) + + result = get_record_list_response.json()["data"]["results"] + if isinstance(result, list): + return result + else: + return [] + + def _get_domain_id(self, domain_name: str) -> str: + if domain_name.count('.') > 2: + domain_name, _, _ = extract_zone(domain_name) + if self.domain_list is None: + self.find_dns_zone(domain_name) + domain_id = None + for domain_data in self.domain_list: + if domain_data["domain"].rstrip(".") == domain_name: + domain_id = domain_data["id"] + if domain_id is None: + raise ValueError( + "Error unable to get DNS zone for domain_name={domain_name}".format(domain_name=domain_name)) + return domain_id + + def remove_record(self, domain_name, dns_name, s_type): + domain_id = self._get_domain_id(domain_name) + record_list = self.get_record_list(domain_id) + trans_type = self._format_s_type_to_request(s_type) + remove_record_id_list = [] + for record in record_list: + if record["type"] == trans_type and (record["host"] == dns_name or record["displayHost"] == dns_name): + remove_record_id_list.append(record["id"]) + + headers = self._get_auth_headers() + + del_record_url_list = [urljoin(self.base_url, "/api/record?id={}".format(i)) for i in remove_record_id_list] + + for del_record_url in del_record_url_list: + requests.delete( + del_record_url, headers=headers, timeout=self.http_timeout + ) + + def delete_dns_record(self, domain_name, domain_dns_value): + domain_name = domain_name.lstrip("*.") + root, zone, acme_txt = extract_zone(domain_name) + self.remove_record(root, acme_txt, 'TXT') + + +class DomainListCache(object): + _CACHE_TIP = '{}/plugin/dns_api_manager/cache.tip'.format(public.get_panel_path()) + _CACHE_DATA = '{}/plugin/dns_api_manager/cache.json'.format(public.get_panel_path()) + + def __init__(self): + self._cache = None + self.read() + + def clear(self): + self._cache = {} + if os.path.exists(self._CACHE_DATA): + os.remove(self._CACHE_DATA) + + def set(self, cache_id, data: dict): + self._cache[cache_id] = data + + def get(self, cache_id): + if cache_id in self._cache: + return self._cache[cache_id] + return None + + def save(self): + now = int(datetime.datetime.now().timestamp()) + if self._cache: + res = public.writeFile(self._CACHE_DATA, json.dumps(self._cache)) + public.writeFile(self._CACHE_TIP, str(now + 60 * 60)) + + def _remove_cache(self): + now = int(datetime.datetime.now().timestamp()) + last = int(public.ReadFile(self._CACHE_TIP)) + if last < now: + if os.path.exists(self._CACHE_DATA): + os.remove(self._CACHE_DATA) + + def read(self): + self._remove_cache() + if not os.path.exists(self._CACHE_DATA): + self._cache = {} + + try: + data = json.loads(public.readFile(self._CACHE_DATA)) + if not isinstance(data, dict): + data = {} + except: + data = {} + + self._cache = data + + +class RealDnsMager(object): + """ + config = { + "CloudFlareDns": [ + { + "E-Mail": "122456944@qq.com", + "API Key": "dsgvfcdkjausvgfkjasdfgakj", + "ps": "xxx", + "id": 1, + "domains": [ # domains 可以不存在,内容是根域名 + ] + } + ] + ........ + }""" + + CONF_FILE = "{}/config/dns_mager.conf".format(public.get_panel_path()) + CLS_MAP: Dict = { + "AliyunDns": AliyunDns, + "DNSPodDns": DNSPodDns, + "CloudFlareDns": CloudFlareDns, + "GoDaddyDns": GoDaddyDns, + "DNSLADns": DNSLADns, + } + + NOT_USED_LIST: list = ["GoDaddyDns", "DNSLADns"] + + RULE_MAP: Dict[str, List[str]] = { + "AliyunDns": ["AccessKey", "SecretKey"], + "DNSPodDns": ["ID", "Token"], + "CloudFlareDns": ["E-Mail", "API Key"], + "GoDaddyDns": ["Key", "Secret"], + "DNSLADns": ["APIID", "API密钥"] + } + + def __init__(self): + self._config: Optional[Dict[str, Dict[str, Union[int, str]]]] = None + + @staticmethod + def _get_new_id() -> str: + return uuid4().hex + + @classmethod + def _read_config_old(cls) -> Optional[Dict[str, List[Dict[str, Union[str, list]]]]]: + old_config_file = "{}/config/dns_api.json".format(public.get_panel_path()) + if os.path.isfile(old_config_file): + try: + data = json.loads(public.readFile(old_config_file)) + except json.JSONDecodeError: + return None + res = {} + rule_list = ("AliyunDns", "DNSPodDns", "CloudFlareDns", "GoDaddyDns") + if isinstance(data, list): + for d in data: + if d["name"] not in rule_list: + continue + + conf_data = d.get("data", None) + if isinstance(data, list): + tmp = {i["name"]: i["value"] for i in conf_data if i["value"].strip()} + tmp["ps"] = "默认账户" + tmp["id"] = cls._get_new_id() + if len(tmp) > 2: + res[d["name"]] = [tmp] + + if res: + return res + return None + + @staticmethod + def _get_acme_dns_api() -> Dict[str, Dict[str, str]]: + path = '/root/.acme.sh' + if not os.path.exists(path + '/account.conf'): + path = "/.acme.sh" + account = public.readFile(path + '/account.conf') + if not account: + return {} + rule_map: Dict[str, Dict[str, str]] = { + "AliyunDns": { + "AccessKey": "SAVED_Ali_Key", + "SecretKey": "SAVED_Ali_Secret", + }, + "DNSPodDns": { + "ID": "SAVED_DP_Id", + "Token": "SAVED_DP_Key" + }, + "CloudFlareDns": { + "E-Mail": "SAVED_CF_MAIL", + "API Key": "SAVED_CF_KEY", + }, + "GoDaddyDns": { + "Key": "SAVED_GD_Key", + "Secret": "SAVED_GD_Secret", + }, + "DNSLADns": { + "APIID": "SAVED_LA_Id", + "API密钥": "SAVED_LA_Key" + } + } + res = {} + for rule_name, rule in rule_map.items(): + tmp = {} + for r_key, r_value in rule.items(): + account_res = re.search(r_value + r"\s*=\s*'(.+)'", account) + if account_res: + tmp[r_key] = account_res.groups()[0] + + if len(tmp) == len(rule): + res[rule_name] = tmp + return res + + @property + def config(self) -> dict: + if self._config is not None: + return self._config + + change = False + if not os.path.exists(self.CONF_FILE): + change = True + old_config = self._read_config_old() + if old_config is not None: + self._config = old_config + else: + self._config = {} + + l_data = self._get_config_data_from_letsencrypt_data() + if l_data is not None: + for tmp_conf in l_data: + key = tmp_conf["dns_name"] + if key not in self._config: + self._config[key] = [] + for v in self._config[key]: + if all([v.get(n, None) == m for n, m in tmp_conf["conf_data"].items()]): + break + else: + tmp_data = { + "ps": "配置中的默认账户", + "id": self._get_new_id(), + } + tmp_data.update(tmp_conf["conf_data"]) + self._config[key].append(tmp_data) + else: + try: + config_data = json.loads(public.readFile(self.CONF_FILE)) + except json.JSONDecodeError: + self._config = {} + else: + if isinstance(config_data, dict): + self._config = config_data + else: + self._config = {} + + acme_conf = self._get_acme_dns_api() + if acme_conf: + for key, value in acme_conf.items(): + if key not in self._config: + self._config[key] = [] + for v in self._config[key]: + if all([v.get(n, None) == m for n, m in value.items()]): + break + else: + change = True + value["ps"] = "检测到的acme_dns" + value["id"] = self._get_new_id() + self._config[key].append(value) + + if change: + self.save_config() + + return self._config + + def save_config(self): + if self._config is None: + _ = self.config + public.writeFile(self.CONF_FILE, json.dumps(self._config)) + + def get_dns_obj_by_domain(self, domain) -> BaseDns: + root, _ = extract_zone(domain) + for key, value in self.config.items(): + for dns_config in value: + if root in dns_config.get("domains", []): + return self.CLS_MAP[key].new(dns_config) + raise Exception("没有找到域名为{}的有效的DNS API密钥信息".format(domain)) + + def add_conf(self, + dns_type: str, + conf_data: List[dict], + ps: str, + domains: List[str], + force_domain: Optional[str]): + + if dns_type in self.NOT_USED_LIST: + return False, "当前DNS平台还未完全支持,请等待后续更新" + + if dns_type not in self.CLS_MAP: + return False, "不支持的DNS平台" + + f, data = self._parse_data(conf_data, dns_type) + if not f: + return False, data + + if not isinstance(domains, list): + return False, "域名参数格式错误" + + if dns_type not in self.config: + self.config[dns_type] = [] + + for v in self.config[dns_type]: + if all([v.get(n, None) == m for n, m in data.items()]): + return False, "该通行凭证已添加过" + + data["ps"] = ps + data["id"] = self._get_new_id() + root_list = self.paser_domains_list_to_root_list(domains) + all_domains = self._get_all_root(with_out=None) + for root in root_list: + if root in all_domains: + return False, "域名{}已绑定其他api账户,不能添加".format(root) + + if force_domain is not None and not isinstance(force_domain, str): + return False, "域名参数格式错误" + if force_domain is not None: + force_root = self.paser_domains_list_to_root_list([force_domain])[0] + if force_root not in root_list: + if force_root in all_domains: + self.remove_domains_by_root(force_root) + + data["domains"] = root_list + self.config[dns_type].append(data) + self.save_config() + return True, "保存成功" + + def _parse_data(self, conf_data: List[dict], dns_type: str) -> Tuple[bool, Union[dict, str]]: + data = {} + if not isinstance(conf_data, list): + return False, "参数格式错误" + for conf in conf_data: + if isinstance(conf, dict) and "name" in conf and "value" in conf: + data[conf.get("name")] = str(conf.get("value")).strip() + if not data: + return False, "参数格式错误,没有指定参数" + if dns_type == "CloudFlareDns" and len(data) == 1 and "API Token" in data: + return True, data + + for n in self.RULE_MAP[dns_type]: + if n not in data: + return False, "参数格式错误,参数名称与平台不对应" + + return True, data + + def modify_conf(self, + api_id: str, + dns_type: str, + conf_data: Optional[List[Dict]], + ps: Optional[str], + domains: Optional[List[str]], + force_domain: Optional[str]): # 强制添加的域名 + + if dns_type in self.NOT_USED_LIST: + return False, "当前DNS平台还未完全支持,请等待后续更新" + + if dns_type not in self.CLS_MAP: + return False, "不支持的DNS平台" + + target_idx = -1 + if dns_type not in self.config: + self.config[dns_type] = [] + + for idx, v in enumerate(self.config[dns_type]): + if api_id == v.get("id", None): + target_idx = idx + + if target_idx == -1: + return False, "不存在这个通行凭证" + + if conf_data is not None: + f, data = self._parse_data(conf_data, dns_type) + if not f: + return False, data + + self.config[dns_type][target_idx].update(**data) + if ps is not None: + self.config[dns_type][target_idx].update(ps=ps) + + if domains is not None and not isinstance(domains, list): + return False, "域名参数格式错误" + + if domains is not None: + root_list = self.paser_domains_list_to_root_list(domains) + all_domains = self._get_all_root(with_out=self.config[dns_type][target_idx].get("domains")) + for root in root_list: + if root in all_domains: + return False, "域名{}已绑定其他api账户,不能添加".format(root) + self.config[dns_type][target_idx]["domains"] = root_list + + if force_domain is not None and not isinstance(force_domain, str): + return False, "域名参数格式错误" + if force_domain is not None: + root = self.paser_domains_list_to_root_list([force_domain])[0] + self.remove_domains_by_root(root) + if "domains" not in self.config[dns_type][target_idx]: + self.config[dns_type][target_idx]["domains"] = [] + self.config[dns_type][target_idx]["domains"].append(root) + + self.save_config() + return True, "修改成功" + + def remove_domains_by_root(self, root: str): + for key, value in self.config.items(): + for dns_config in value: + domains = dns_config.get("domains", None) + if domains is not None and root in domains: + domains.remove(root) + + def _get_all_root(self, with_out: Optional[List[str]]) -> Set[str]: + all_domains = set( + chain(*[c.get("domains", []) for c in + chain(*[c_list for c_list in self.config.values()])] + ) + ) + if with_out is not None: + return all_domains - set(with_out) + return all_domains + + def test_domains_api(self, domains: List[str]) -> List[dict]: + res = [{}] * len(domains) + for idx, domain in enumerate(domains): + root = self.paser_domains_list_to_root_list([domain])[0] + for key, conf in self.config.items(): + for c in conf: + if root in c.get("domains", []): + res[idx] = { + "dns_name": key, + "conf": c, + "rooot": root, + "domain": domain + } + return res + + def remove_conf(self, api_id: str, dns_type: str): + if dns_type in self.NOT_USED_LIST: + return False, "当前DNS平台还未完全支持,请等待后续更新" + + if dns_type not in self.CLS_MAP: + return False, "不支持的DNS平台" + + if dns_type not in self.config: + self.config[dns_type] = [] + + target_idx = -1 + for idx, v in enumerate(self.config[dns_type]): + if api_id == v.get("id", None): + target_idx = idx + + if target_idx == -1: + return False, "不存在这个通行凭证" + + del self.config[dns_type][target_idx] + self.save_config() + return True, "删除成功" + + @classmethod + def paser_auth_to(cls, auth_to_string: str) -> Tuple[Optional[str], Optional[Dict[str, str]]]: + tmp = auth_to_string.split('|') + dns_name = tmp[0] + if dns_name not in cls.CLS_MAP: + return None, None + if len(tmp) != 3: + return None, None + + if tmp[2] == "": + key = None + secret = tmp[1] + else: + key = tmp[1] + secret = tmp[2] + + if dns_name == "CloudFlareDns" and key is None: + return "CloudFlareDns", {"API Token": secret} + elif key and secret: + return dns_name, dict(zip(cls.RULE_MAP[dns_name], [key, secret])) + return None, None + + @classmethod + def paser_domains_list_to_root_list(cls, domains_list: List[str]) -> List[str]: + res = [] + for domain in domains_list: + root, _ = extract_zone(domain) + if root in res: + continue + res.append(root) + return res + + @classmethod + def _get_config_data_from_letsencrypt_data(cls) -> Optional[List[Dict[str, Union[str, list, dict]]]]: + conf_file = "{}/config/letsencrypt.json".format(public.get_panel_path()) + if not os.path.exists(conf_file): + return None + tmp_config = public.readFile(conf_file) + try: + orders = json.loads(tmp_config)["orders"] + except (json.JSONDecodeError, KeyError): + return None + + res = {} + for order in orders: + if 'auth_type' in order and order['auth_type'] == "dns": + if order["auth_to"].find("|") == -1 or order["auth_to"].find("/") != -1: # 文件验证跳过 + continue + if order["auth_to"] in res: + tmp_conf = res[order["auth_to"]] + else: + dns_name, conf_dict = cls.paser_auth_to(order["auth_to"]) + if dns_name is None: + continue + tmp_conf = { + "dns_name": dns_name, + "conf_data": conf_dict, + "domains": [] + } + res[order["auth_to"]] = tmp_conf + root_list = order.get("domains", []) + for root in root_list: + if root not in tmp_conf["domains"]: + tmp_conf["domains"].append(root) + + if len(res) == 0: + return None + return list(res.values()) + + +class DNSApiManager: + _DEFAULT_ERROR = ( + "执行中发生了错误,您可以尝试:
                                    " + "1. 检查API通行证是否有效,或是否填写错误
                                    " + "2. 检查域名是否托管在该平台下
                                    " + "3. 检查解析值是否正确
                                    " + ) + + def __init__(self): + pass + + @staticmethod + def get_dns_api_conf(get=None): + api_init = '{}/config/dns_api_init_v2.json'.format(public.get_panel_path()) + + apis = json.loads(public.ReadFile(api_init)) + m = RealDnsMager() + result = [] + + for data in apis: + if data["name"] == "dns": + continue + if data["name"] in m.NOT_USED_LIST: + continue + if data["name"] in m.CLS_MAP: + conf_list = m.config.get(data["name"], None) + tmp = [] + if conf_list: + for conf in conf_list: + tmp_dict = { + "ps": conf.pop("ps", ""), + "domains": conf.pop("domains", []), + "id": conf.pop("id") + } + for table in data["add_table"]: + if table["fields"][0] in conf: + tmp_dict["conf"] = [{"name": f, "value": conf.get(f, "")} for f in table["fields"]] + if "conf" in tmp_dict: + tmp.append(tmp_dict) + data["data"] = tmp + result.append(data) + + return json_response(status=True, data=result) + + @staticmethod + def add_dns_api(get): + try: + dns_type = get.dns_type.strip() + ps = get.ps.strip() + conf_data = json.loads(get.pdata.strip()) + domains = json.loads(get.domains.strip()) + force_domain = None + if "force_domain" in get: + force_domain = get.force_domain.strip() + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + + f, msg = RealDnsMager().add_conf(dns_type, conf_data, ps, domains, force_domain) + return json_response(status=f, msg=msg) + + @staticmethod + def set_dns_api(get): + try: + dns_type = get.dns_type.strip() + api_id = get.api_id.strip() + ps = None + conf_data = None + domains = None + force_domain = None + if "ps" in get: + ps = get.ps.strip() + if "force_domain" in get: + force_domain = get.force_domain.strip() + if "pdata" in get: + conf_data = json.loads(get.pdata.strip()) + if "domains" in get: + domains = json.loads(get.domains.strip()) + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + try: + f, msg = RealDnsMager().modify_conf(api_id, dns_type, conf_data, ps, domains, force_domain) + return json_response(status=f, msg=msg) + except: + public.print_log(public.get_error_info()) + + @staticmethod + def remove_dns_api(get): + try: + dns_type = get.dns_type.strip() + api_id = get.api_id.strip() + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + f, msg = RealDnsMager().remove_conf(api_id, dns_type) + return json_response(status=f, msg=msg) + + @staticmethod + def remove_domain(get): + try: + domain = get.domain.strip() + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + root, _ = extract_zone(domain) + m = RealDnsMager() + m.remove_domains_by_root(root) + m.save_config() + return json_response(status=True, msg="删除成功") + + def query_dns(self, get): + domain = get.domain + dns_type = get.dns_type + res = public.query_dns(domain, dns_type) + if not res: + return json_response(status=False, msg="参数错误") + + return json_response(status=True, data=res) + + @staticmethod + def get_record_list(get): + """ + 获取解析记录列表 + """ + try: + domain = get.domain.strip() + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + + m = RealDnsMager() + try: + dns_onj = m.get_dns_obj_by_domain(domain) + return json_response(status=True, data=dns_onj.get_record_list_by_view(domain)) + except Exception as e: + return json_response(status=False, msg=str(e)) + + @classmethod + def create_record(cls, get): + """ + @创建解析记录 + """ + try: + domain = get.domain.strip() + host = get.host.strip() + r_type = get.r_type.strip() + value = get.value.strip() + ttl = int(get.ttl.strip()) + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + domain, _ = extract_zone(domain) + + try: + m = RealDnsMager() + dns_obj = m.get_dns_obj_by_domain(domain) + r_type = dns_obj.transform_record_type(r_type) + dns_obj.add_record(domain, host, r_type, value, ttl=ttl) + return json_response(status=True, msg="添加成功") + except: + # return public.returnMsg(False, public.get_error_info()) + return json_response(status=False, msg=cls._DEFAULT_ERROR) + + @classmethod + def delete_record(cls, get): + """ + @删除解析记录 + @domain String 解析记录所在的域名 + @recordId Int 解析记录 ID + """ + try: + domain = get.domain.strip() + host = get.host.strip() + r_type = get.r_type.strip() + except (json.JSONDecodeError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + domain, _ = extract_zone(domain) + + try: + m = RealDnsMager() + dns_obj = m.get_dns_obj_by_domain(domain) + dns_obj.remove_record(domain, host, r_type) + return json_response(status=True, msg="删除成功") + except: + # return public.returnMsg(False, public.get_error_info()) + return json_response(status=False, msg=cls._DEFAULT_ERROR) + + @staticmethod + def get_domain_list(get=None): + """ + @name 获取域名列表 + @param search 搜索关键字 + """ + res = [] + cache = DomainListCache() + cache_change = False + domain_change = False + m = RealDnsMager() + for key, value in m.config.items(): + if key in m.NOT_USED_LIST: + continue + for dns_config in value: + config_id = dns_config["id"] + dns_obj: BaseDns = m.CLS_MAP[key].new(dns_config) + cloud_domain_data = cache.get(config_id) + if cloud_domain_data is None: + try: + cloud_domain_data = dns_obj.get_domain_list() + except Exception: + public.print_log(public.get_error_info()) + cloud_domain_data = {} + cache_change = True + cache.set(config_id, cloud_domain_data) + + if "domains" not in dns_config: + dns_config['domains'] = [] + + domains = dns_config.get("domains") + for d in domains: + tmp = { + "name": d, + "api_id": config_id, + "api_type": key, + "cloud_have": False, + "ps": dns_config["ps"] + } + if d in cloud_domain_data: + tmp["cloud_have"] = True + + res.append(tmp) + + for cloud_d in cloud_domain_data.keys(): + if cloud_d not in domains: + m.remove_domains_by_root(cloud_d) # 删除其他地方的数据 + dns_config["domains"].append(cloud_d) + domain_change = True + res.append({ + "name": cloud_d, + "api_id": config_id, + "api_type": key, + "cloud_have": True, + "ps": dns_config["ps"] + }) + + if cache_change: + cache.save() + + if domain_change: + m.save_config() + + return json_response(status=True, data=res) diff --git a/mod/base/web_conf/domain_tool.py b/mod/base/web_conf/domain_tool.py new file mode 100644 index 00000000..3fe753bb --- /dev/null +++ b/mod/base/web_conf/domain_tool.py @@ -0,0 +1,335 @@ +import os +import re +from typing import Tuple, Optional, Union, List, Dict + +from .util import webserver, check_server_config, write_file, read_file, service_reload, listen_ipv6, use_http2 + + +def domain_to_puny_code(domain: str) -> str: + new_domain = '' + for dkey in domain.split('.'): + if dkey == '*' or dkey == "": + continue + # 匹配非ascii字符 + match = re.search(u"[\x80-\xff]+", dkey) + if not match: + match = re.search(u"[\u4e00-\u9fa5]+", dkey) + if not match: + new_domain += dkey + '.' + else: + new_domain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.' + if domain.startswith('*.'): + new_domain = "*." + new_domain + return new_domain[:-1] + + +def check_domain(domain: str) -> Optional[str]: + domain = domain_to_puny_code(domain) + # 判断通配符域名格式 + if domain.find('*') != -1 and domain.find('*.') == -1: + return None + + # 判断域名格式 + rep_domain = re.compile(r"^([\w\-*]{1,100}\.){1,24}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$") + if not rep_domain.match(domain): + return None + return domain + + +def is_domain(domain: str) -> bool: + domain_regex = re.compile( + r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,247}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(? Tuple[List[Tuple[str, str]], List[Dict]]: + res, error = [], [] + for i in domains: + if not i.strip(): + continue + d_list = [i.strip() for i in i.split(":")] + if len(d_list) > 1: + try: + p = int(d_list[1]) + if not (1 < p < 65535): + error.append({ + "domain": i, + "msg": "端口范围错误" + }) + continue + else: + d_list[1] = str(p) + except: + error.append({ + "domain": i, + "msg": "端口范围错误" + }) + continue + else: + d_list.append("80") + d, p = d_list + d = check_domain(d) + if isinstance(d, str): + res.append((d, p)), + continue + error.append({ + "domain": i, + "msg": "域名格式错误" + }) + + res = list(set(res)) + return res, error + + +class NginxDomainTool: + ng_vhost = "/www/server/panel/vhost/nginx" + + def __init__(self, conf_prefix: str = ""): + self.conf_prefix = conf_prefix + + # 在给定的配置文件中添加端口 + @staticmethod + def nginx_add_port_by_config(conf, *port: str, is_http3=False) -> str: + ports = set() + for p in port: + ports.add(p) + + # 设置端口 + rep_port = re.compile(r"\s*listen\s+[\[\]:]*(?P[0-9]+)(?P\s*default_server)?.*;[^\n]*\n", re.M) + use_ipv6 = listen_ipv6() + last_port_idx = None + need_remove_port_idx = [] + had_ports = set() + is_default_server = False + for tmp_res in rep_port.finditer(conf): + last_port_idx = tmp_res.end() + if tmp_res.group("ds") and tmp_res.group("ds").strip(): + is_default_server = True + if tmp_res.group("port") in ports: + had_ports.add(tmp_res.group("port")) + elif tmp_res.group("port") != "443": + need_remove_port_idx.append((tmp_res.start(), tmp_res.end())) + + if not last_port_idx: + last_port_idx = re.search(r"server\s*\{\s*?\n", conf).end() + + need_add_ports = ports - had_ports + d_s = " default_server" if is_default_server else "" + h2 = " http2" if use_http2() else "" + if need_add_ports or is_http3: + listen_add_list = [] + for p in need_add_ports: + if p == "443": + tmp = " listen 443 ssl{}{};\n".format(h2, d_s) + if use_ipv6: + tmp += " listen [::]:443 ssl{}{};\n".format(h2, d_s) + listen_add_list.append(tmp) + continue + + tmp = " listen {}{};\n".format(p, d_s) + if use_ipv6: + tmp += " listen [::]:{}{};\n".format(p, d_s) + listen_add_list.append(tmp) + + if is_http3 and "443" in (had_ports | had_ports): + listen_add_list.append(" listen 443 quic{};\n".format(d_s)) + if use_ipv6: + listen_add_list.append(" listen [::]:443 quic{};\n".format(d_s)) + + new_conf = conf[:last_port_idx] + "".join(listen_add_list) + conf[last_port_idx:] + return new_conf + return conf + + # 将站点配置的域名和端口,写到配置文件中 + def nginx_set_domain(self, site_name, *domain: Tuple[str, str]) -> Optional[str]: + ng_file = '{}/{}{}.conf'.format(self.ng_vhost, self.conf_prefix, site_name) + ng_conf = read_file(ng_file) + if not ng_conf: + return "nginx配置文件丢失" + + domains_set, ports = set(), set() + for d, p in domain: + domains_set.add(d) + ports.add(p) + + # 设置域名 + rep_server_name = re.compile(r"\s*server_name\s*(.*);", re.M) + new_conf = rep_server_name.sub("\n server_name {};".format(" ".join(domains_set)), ng_conf, 1) + + # 设置端口 + rep_port = re.compile(r"\s*listen\s+[\[\]:]*(?P[0-9]+)(?P\s*default_server)?.*;[^\n]*\n", re.M) + use_ipv6 = listen_ipv6() + last_port_idx = None + need_remove_port_idx = [] + had_ports = set() + is_default_server = False + for tmp_res in rep_port.finditer(new_conf): + last_port_idx = tmp_res.end() + if tmp_res.group("ds") is not None and tmp_res.group("ds").strip(): + is_default_server = True + if tmp_res.group("port") in ports: + had_ports.add(tmp_res.group("port")) + elif tmp_res.group("port") != "443": + need_remove_port_idx.append((tmp_res.start(), tmp_res.end())) + + if not last_port_idx: + last_port_idx = re.search(r"server\s*\{\s*?\n", new_conf).end() + + ports = ports - had_ports + if ports: + d_s = " default_server" if is_default_server else "" + listen_add_list = [] + for p in ports: + tmp = " listen {}{};\n".format(p, d_s) + if use_ipv6: + tmp += " listen [::]:{}{};\n".format(p, d_s) + listen_add_list.append(tmp) + + new_conf = new_conf[:last_port_idx] + "".join(listen_add_list) + new_conf[last_port_idx:] + + # 移除多余的port监听: + # 所有遍历的索引都在 last_port_idx 之前,所有不会影响之前的修改 ↑ + if need_remove_port_idx: + conf_list = [] + idx = 0 + for start, end in need_remove_port_idx: + conf_list.append(new_conf[idx:start]) + idx = end + conf_list.append(new_conf[idx:]) + new_conf = "".join(conf_list) + + # 保存配置文件 + write_file(ng_file, new_conf) + web_server = webserver() + if web_server == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return "配置失败" + if web_server == "nginx": + service_reload() + + +class ApacheDomainTool: + ap_vhost = "/www/server/panel/vhost/apache" + ap_path = "/www/server/apache" + + def __init__(self, conf_prefix: str = ""): + self.conf_prefix = conf_prefix + + # 将站点配置的域名和端口,写到配置文件中 + def apache_set_domain(self, + site_name, # 站点名称 + *domain: Tuple[str, str], # 域名列表,可以为多个 + template_path: Optional[str] = None, # 在新加端口时使用一个模板作为添加内容 + template_kwargs: Optional[dict] = None, # 在使用一个模板时的填充参数, + ) -> Optional[str]: + """ + template_path: 在新加端口时使用一个模板作为添加内容 + template_kwargs: 在使用一个模板时的填充参数 + port domains server_admin server_name 四个参数会自动生成并填充 + 没有传入 template_path 将会复制第一个虚拟机(VirtualHost)配置 + """ + ap_file = '{}/{}{}.conf'.format(self.ap_vhost, self.conf_prefix, site_name) + ap_conf: str = read_file(ap_file) + if not ap_conf: + return "nginx配置文件丢失" + + domains, ports = set(), set() + for i in domain: + domains.add(str(i[0])) + ports.add(str(i[1])) + + domains_str = " ".join(domains) + + # 设置域名 + rep_server_name = re.compile(r"\s*ServerAlias\s*(.*)\n", re.M) + new_conf = rep_server_name.sub("\n ServerAlias {}\n".format(domains_str), ap_conf) + + tmp_template_res = re.search(r"", new_conf) + if not tmp_template_res: + tmp_template = None + else: + tmp_template = tmp_template_res.group() + + rep_ports = re.compile(r"\d+)+\s*>") + need_remove_port = [] + for tmp in rep_ports.finditer(new_conf): + if tmp.group("port") in ports: + ports.remove(tmp.group("port")) + elif tmp.group("port") != "443": + need_remove_port.append(tmp.group("port")) + + if need_remove_port: + for i in need_remove_port: + tmp_rep = re.compile(r"".format(p), tmp_template, 1)) + + new_conf += "\n" + "\n".join(other_config_body_list) + write_file(ap_file, new_conf) + # 添加端口 + self.apache_add_ports(*ports) + web_server = webserver() + if web_server == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return "配置失败" + + if web_server == "apache": + service_reload() + + # 添加apache主配置文件中的端口监听 + @classmethod + def apache_add_ports(cls, *ports: Union[str, int]) -> None: + real_ports = set() + for p in ports: + real_ports.add(str(p)) + + ssl_conf_file = '{}/conf/extra/httpd-ssl.conf'.format(cls.ap_path) + if os.path.isfile(ssl_conf_file): + ssl_conf = read_file(ssl_conf_file) + if isinstance(ssl_conf, str) and ssl_conf.find('Listen 443') != -1: + ssl_conf = ssl_conf.replace('Listen 443', '') + write_file(ssl_conf_file, ssl_conf) + + ap_conf_file = '{}/conf/httpd.conf'.format(cls.ap_path) + if not os.path.isfile(ap_conf_file): + return + ap_conf = read_file(ap_conf_file) + if ap_conf is None: + return + + rep_ports = re.compile(r"Listen\s+(?P[0-9]+)\n", re.M) + last_idx = None + for key in rep_ports.finditer(ap_conf): + last_idx = key.end() + if key.group("port") in real_ports: + real_ports.remove(key.group("port")) + + if not last_idx: + return + new_conf = ap_conf[:last_idx] + "\n".join(["Listen %s" % i for i in real_ports]) + "\n" + ap_conf[last_idx:] + write_file(ap_conf_file, new_conf) diff --git a/mod/base/web_conf/ip_restrict.py b/mod/base/web_conf/ip_restrict.py new file mode 100644 index 00000000..72c5681d --- /dev/null +++ b/mod/base/web_conf/ip_restrict.py @@ -0,0 +1,326 @@ +import os +import re +import json +from typing import Tuple, Optional, Union +from ipaddress import ip_address + +from .util import webserver, check_server_config, write_file, read_file, DB, service_reload +from mod.base import json_response + + +class _BaseRestrict: + def __init__(self, config_file: str, site_name: str): + self._conf_file = config_file + self._conf = self._read_conf() + self.site_name = site_name + + def _read_conf(self): + default_conf = { + "restrict_type": "closed", + "black_list": [], + "white_list": [] + } + + if not os.path.exists(self._conf_file): + return default_conf + try: + conf = json.loads(read_file(self._conf_file)) + except: + conf = default_conf + return conf + + def to_view(self): + return self._conf + + +class _IpRestrict(_BaseRestrict): + def __init__(self, site_name: str, config_prefix: str): + setup_path = "/www/server/panel" + ip_restrict_conf_dir = "{}/data/ip_restrict_data".format(setup_path) + if not os.path.exists(ip_restrict_conf_dir): + os.makedirs(ip_restrict_conf_dir) + super().__init__("{}/{}{}".format(ip_restrict_conf_dir, config_prefix, site_name), site_name) + self.config_prefix = config_prefix + self.nginx_sub_file = "{}/vhost/ip-restrict/{}{}.conf".format(setup_path, self.config_prefix, self.site_name) + + @property + def restrict_type(self): + return self._conf.get("restrict_type", "black") + + @restrict_type.setter + def restrict_type(self, data: str): + if data in ("black", "white", "closed"): + self._conf["restrict_type"] = data + + @property + def black_list(self): + return self._conf.get("black_list", []) + + @black_list.setter + def black_list(self, list_data: list): + self._conf["black_list"] = list_data + + @property + def white_list(self): + return self._conf.get("white_list", []) + + @white_list.setter + def white_list(self, list_data: list): + self._conf["white_list"] = list_data + + def save(self) -> Tuple[bool, str]: + if not self._conf: # 没有的时候不操作 + return True, "操作成功" + write_file(self._conf_file, json.dumps(self._conf)) + + if self.restrict_type == "closed": + write_file(self.nginx_sub_file, "") + service_reload() + return True, "操作成功" + + tmp_conf = [] + if self.restrict_type == "white": + for i in self.white_list: + tmp_conf.append("allow {};".format(i)) + + tmp_conf.append("deny all; # 除开上述IP外,其他IP全部禁止访问") + elif self.restrict_type == "black": + for i in self.black_list: + tmp_conf.append("deny {};".format(i)) + else: + raise ValueError("错误的类型,无法操作") + + write_file(self.nginx_sub_file, "\n".join(tmp_conf)) + error_msg = check_server_config() + if error_msg is not None: + write_file(self.nginx_sub_file, "") + return False, "操作失败" + service_reload() + return True, "操作成功" + + # 删除网站时调用,删除配置文件 + def remove_config_for_remove_site(self): + if os.path.isfile(self.nginx_sub_file): + os.remove(self.nginx_sub_file) + + if os.path.isfile(self._conf_file): + os.remove(self._conf_file) + + +class RealIpRestrict: + + def __init__(self, config_prefix: str = ""): + self.config_prefix = config_prefix + self.web_server = webserver() + + # 获取某个站点的IP黑白名单详情 + def restrict_conf(self, site_name: str) -> Tuple[bool, Union[str, dict]]: + if self.web_server != "nginx": + return False, "不支持除nginx之外的服务器" + ip_conf = _IpRestrict(site_name, self.config_prefix) + if not self._get_status_in_nginx_conf(ip_conf): + ip_conf.restrict_type = "closed" + return True, ip_conf.to_view() + + # 从配置文件中获取状态 + def _get_status_in_nginx_conf(self, ip_conf: _IpRestrict) -> bool: + setup_path = "/www/server/panel" + ng_file = "{}/vhost/nginx/{}{}.conf".format(setup_path, self.config_prefix, ip_conf.site_name) + rep_include = re.compile(r"\sinclude +.*/ip-restrict/.*\.conf;", re.M) + ng_conf = read_file(ng_file) + if not isinstance(ng_conf, str): + return False + if rep_include.search(ng_conf): + return True + return False + + def _set_nginx_include(self, ip_conf: _IpRestrict) -> Tuple[bool, str]: + setup_path = "/www/server/panel" + ng_file = "{}/vhost/nginx/{}{}.conf".format(setup_path, self.config_prefix, ip_conf.site_name) + if not os.path.exists(os.path.dirname(ip_conf.nginx_sub_file)): + os.makedirs(os.path.dirname(ip_conf.nginx_sub_file), 0o600) + if not os.path.isfile(ip_conf.nginx_sub_file): + write_file(ip_conf.nginx_sub_file, "") + + ng_conf = read_file(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"\s*include\s+.*/ip-restrict/.*\.conf;", re.M) + if rep_include.search(ng_conf): + return True, "" + + _include_str = ( + "\n #引用IP黑白名单规则,注释后配置的IP黑白名单将无效\n" + " include {};" + ).format(ip_conf.nginx_sub_file) + + rep_redirect_include = re.compile(r"\s*include\s+.*/redirect/.*\.conf;", re.M) # 如果有重定向,添加到重定向之后 + redirect_include_res = rep_redirect_include.search(ng_conf) + if redirect_include_res: + new_conf = ng_conf[:redirect_include_res.end()] + _include_str + ng_conf[redirect_include_res.end():] + else: + if "#SSL-END" not in ng_conf: + return False, "添加配置失败,无法定位SSL相关配置的位置" + + new_conf = ng_conf.replace("#SSL-END", "#SSL-END" + _include_str) + write_file(ng_file, new_conf) + if self.web_server == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return False, "添加配置失败" + + return True, "" + + def set_ip_restrict(self, site_name: str, set_type: str) -> Tuple[bool, str]: + ip_restrict = _IpRestrict(site_name, self.config_prefix) + if set_type not in ("black", "white", "closed"): + return False, "不支持的类型【{}】".format(set_type) + ip_restrict.restrict_type = set_type + f, msg = self._set_nginx_include(ip_restrict) + if not f: + return False, msg + + return ip_restrict.save() + + def add_black_ip_restrict(self, site_name: str, *ips: str) -> Tuple[bool, str]: + try: + for ip in ips: + _ = ip_address(ip) # 引发valueError + except ValueError: + return False, "ip参数解析错误" + ip_restrict = _IpRestrict(site_name, self.config_prefix) + black_list = ip_restrict.black_list + for i in ips: + if i not in black_list: + black_list.append(i) + + ip_restrict.black_list = black_list + f, msg = self._set_nginx_include(ip_restrict) + if not f: + return False, msg + + return ip_restrict.save() + + def remove_black_ip_restrict(self, site_name: str, *ips: str): + ip_restrict = _IpRestrict(site_name, self.config_prefix) + black_list = ip_restrict.black_list + for i in ips: + if i in black_list: + black_list.remove(i) + + ip_restrict.black_list = black_list + f, msg = self._set_nginx_include(ip_restrict) + if not f: + return False, msg + + return ip_restrict.save() + + def add_white_ip_restrict(self, site_name: str, *ips: str) -> Tuple[bool, str]: + try: + for ip in ips: + _ = ip_address(ip) # 引发valueError + except ValueError: + return False, "ip参数解析错误" + ip_restrict = _IpRestrict(site_name, self.config_prefix) + white_list = ip_restrict.white_list + for i in ips: + if i not in white_list: + white_list.append(i) + + ip_restrict.white_list = white_list + f, msg = self._set_nginx_include(ip_restrict) + if not f: + return False, msg + + return ip_restrict.save() + + def remove_white_ip_restrict(self, site_name: str, *ips: str) -> Tuple[bool, str]: + ip_restrict = _IpRestrict(site_name, self.config_prefix) + white_list = ip_restrict.white_list + for i in ips: + if i in white_list: + white_list.remove(i) + + ip_restrict.white_list = white_list + + return ip_restrict.save() + + def remove_site_ip_restrict_info(self, site_name: str): + ip_restrict = _IpRestrict(site_name, self.config_prefix) + ip_restrict.remove_config_for_remove_site() + + +class IpRestrict: + + def __init__(self, config_prefix: str = ""): + self.config_prefix = config_prefix + self._ri = RealIpRestrict(self.config_prefix) + + # 获取ip控制信息 + def restrict_conf(self, get): + try: + site_name = get.site_name.strip() + except (AttributeError, json.JSONDecodeError): + return json_response(status=False, msg="参数错误") + + f, d = self._ri.restrict_conf(site_name) + if not f: + return json_response(status=f, msg=d) + return json_response(status=f, data=d) + + # 设置ip黑白名单状态 + def set_ip_restrict(self, get): + try: + site_name = get.site_name.strip() + set_ip_restrict = get.set_type.strip() + except (AttributeError, json.JSONDecodeError): + return json_response(status=False, msg="参数错误") + + f, m = self._ri.set_ip_restrict(site_name, set_ip_restrict) + return json_response(status=f, msg=m) + + # 添加黑名单 + def add_black_ip_restrict(self, get): + try: + site_name = get.site_name.strip() + value = get.value.strip() + except AttributeError: + return json_response(status=False, msg="参数错误") + + f, m = self._ri.add_black_ip_restrict(site_name, value) + return json_response(status=f, msg=m) + + # 移除黑名单 + def remove_black_ip_restrict(self, get): + try: + site_name = get.site_name.strip() + value = get.value.strip() + except (AttributeError, json.JSONDecodeError): + return json_response(status=False, msg="参数错误") + + f, m = self._ri.remove_black_ip_restrict(site_name, value) + return json_response(status=f, msg=m) + + # 添加白名单 + def add_white_ip_restrict(self, get): + try: + site_name = get.site_name.strip() + value = get.value.strip() + except (AttributeError, json.JSONDecodeError): + return json_response(status=False, msg="参数错误") + + f, m = self._ri.add_white_ip_restrict(site_name, value) + return json_response(status=f, msg=m) + + # 移除白名单 + def remove_white_ip_restrict(self, get): + try: + site_name = get.site_name.strip() + value = get.value.strip() + except (AttributeError, json.JSONDecodeError): + return json_response(status=False, msg="参数错误") + + f, m = self._ri.remove_white_ip_restrict(site_name, value) + return json_response(status=f, msg=m) + + diff --git a/mod/base/web_conf/limit_net.py b/mod/base/web_conf/limit_net.py new file mode 100644 index 00000000..13605201 --- /dev/null +++ b/mod/base/web_conf/limit_net.py @@ -0,0 +1,238 @@ +import os +import re +from typing import Tuple, Union + +from .util import webserver + + +class LimitNet(object): + + def get_limit_net(self, get) -> Union[bool, str]: + if webserver() != 'nginx': + return False, "" + try: + site_id = int(get.site_id) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + if self.config_prefix is None: + return public.returnMsg(False, "不支持的网站类型") + + # 取配置文件 + site_name = public.M('sites').where("id=?", (site_id,)).getField('name') + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + conf = public.readFile(filename) + if not isinstance(conf, str): + return public.returnMsg(False, "配置文件读取错误") + + # 站点总并发 + data = { + 'perserver': 0, + 'perip': 0, + 'limit_rate': 0, + } + + rep_per_server = re.compile(r"(?P.*)limit_conn +perserver +(?P\d+) *; *", re.M) + tmp_res = rep_per_server.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['perserver'] = int(tmp_res.group("target")) + + # IP并发限制 + rep_per_ip = re.compile(r"(?P.*)limit_conn +perip +(?P\d+) *; *", re.M) + tmp_res = rep_per_ip.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['perip'] = int(tmp_res.group("target")) + + # 请求并发限制 + rep_limit_rate = re.compile(r"(?P.*)limit_rate +(?P\d+)\w+ *; *", re.M) + tmp_res = rep_limit_rate.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['limit_rate'] = int(tmp_res.group("target")) + + self._show_limit_net(data) + return data + + @staticmethod + def _show_limit_net(data): + values = [ + [300, 25, 512], + [200, 10, 1024], + [50, 3, 2048], + [500, 10, 2048], + [400, 15, 1024], + [60, 10, 512], + [150, 4, 1024], + ] + for i, c in enumerate(values): + if data["perserver"] == c[0] and data["perip"] == c[1] and data["limit_rate"] == c[2]: + data["value"] = i + 1 + break + else: + data["value"] = 0 + + @staticmethod + def _set_nginx_conf_limit() -> Tuple[bool, str]: + # 设置共享内存 + nginx_conf_file = "/www/server/nginx/conf/nginx.conf" + if not os.path.exists(nginx_conf_file): + return False, "nginx配置文件丢失" + nginx_conf = public.readFile(nginx_conf_file) + rep_perip = re.compile(r"\s+limit_conn_zone +\$binary_remote_addr +zone=perip:10m;", re.M) + rep_per_server = re.compile(r"\s+limit_conn_zone +\$server_name +zone=perserver:10m;", re.M) + perip_res = rep_perip.search(nginx_conf) + per_serve_res = rep_per_server.search(nginx_conf) + if perip_res and per_serve_res: + return True, "" + elif perip_res or per_serve_res: + tmp_res = perip_res or per_serve_res + new_conf = nginx_conf[:tmp_res.start()] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;" + ) + nginx_conf[tmp_res.end():] + else: + # 通过检查第一个server的位置 + rep_first_server = re.compile(r"http\s*\{(.*\n)*\s*server\s*\{") + tmp_res = rep_first_server.search(nginx_conf) + if tmp_res: + old_http_conf = tmp_res.group() + # 在第一个server项前添加 + server_idx = old_http_conf.rfind("server") + new_http_conf = old_http_conf[:server_idx] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n" + ) + old_http_conf[server_idx:] + new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1) + else: + # 在没有配置其他server项目时,通过检查include server项目检查 + # 通检查 include /www/server/panel/vhost/nginx/*.conf; 位置 + rep_include = re.compile(r"http\s*\{(.*\n)*\s*include +/www/server/panel/vhost/nginx/\*\.conf;") + tmp_res = rep_include.search(nginx_conf) + if not tmp_res: + return False, "全局配置缓存配置失败" + old_http_conf = tmp_res.group() + + include_idx = old_http_conf.rfind("include ") + new_http_conf = old_http_conf[:include_idx] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n" + ) + old_http_conf[include_idx:] + new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1) + + public.writeFile(nginx_conf_file, new_conf) + if public.checkWebConfig() is not True: # 检测失败,无法添加 + public.writeFile(nginx_conf_file, nginx_conf) + return False, "全局配置缓存配置失败" + return True, "" + + # 设置流量限制 + def set_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + try: + site_id = int(get.site_id) + per_server = int(get.perserver) + perip = int(get.perip) + limit_rate = int(get.limit_rate) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + if per_server < 1 or perip < 1 or limit_rate < 1: + return public.returnMsg(False, '并发限制,IP限制,流量限制必需大于0') + + # 取配置文件 + site_info = public.M('sites').where("id=?", (site_id,)).find() + if not isinstance(site_info, dict): + return public.returnMsg(False, "站点信息查询错误") + else: + site_name = site_info["name"] + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + site_conf: str = public.readFile(filename) + if not isinstance(site_conf, str): + return public.returnMsg(False, "配置文件读取错误") + + flag, msg = self._set_nginx_conf_limit() + if not flag: + return public.returnMsg(False, msg) + + per_server_str = ' limit_conn perserver {};'.format(per_server) + perip_str = ' limit_conn perip {};'.format(perip) + limit_rate_str = ' limit_rate {}k;'.format(limit_rate) + + # 请求并发限制 + new_conf = site_conf + ssl_end_res = re.search(r"#error_page 404/404.html;[^\n]*\n", new_conf) + if ssl_end_res is None: + return public.returnMsg(False, "未定位到SSL的相关配置,添加失败") + ssl_end_idx = ssl_end_res.end() + rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *", re.M) + tmp_res = rep_limit_rate.search(new_conf) + if tmp_res is not None : + new_conf = rep_limit_rate.sub(limit_rate_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + limit_rate_str + "\n" + new_conf[ssl_end_idx:] + + # IP并发限制 + rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *", re.M) + tmp_res = rep_per_ip.search(new_conf) + if tmp_res is not None: + new_conf = rep_per_ip.sub(perip_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + perip_str + "\n" + new_conf[ssl_end_idx:] + + rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *", re.M) + tmp_res = rep_per_server.search(site_conf) + if tmp_res is not None: + new_conf = rep_per_server.sub(per_server_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + per_server_str + "\n" + new_conf[ssl_end_idx:] + + public.writeFile(filename, new_conf) + is_error = public.checkWebConfig() + if is_error is not True: + public.writeFile(filename, site_conf) + return public.returnMsg(False, 'ERROR:
                                    ' + is_error.replace("\n", '
                                    ') + '
                                    ') + + public.serviceReload() + public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_OPEN_SUCCESS', (site_name,)) + return public.returnMsg(True, 'SET_SUCCESS') + + # 关闭流量限制 + def close_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + if self.config_prefix is None: + return public.returnMsg(False, "不支持的网站类型") + try: + site_id = int(get.site_id) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + # 取回配置文件 + site_info = public.M('sites').where("id=?", (site_id,)).find() + if not isinstance(site_info, dict): + return public.returnMsg(False, "站点信息查询错误") + else: + site_name = site_info["name"] + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + site_conf = public.readFile(filename) + if not isinstance(site_conf, str): + return public.returnMsg(False, "配置文件读取错误") + + # 清理总并发 + rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *\n?", re.M) + rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *\n?", re.M) + rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *\n?", re.M) + + new_conf = site_conf + new_conf = rep_limit_rate.sub("", new_conf, 1) + new_conf = rep_per_ip.sub("", new_conf, 1) + new_conf = rep_per_server.sub("", new_conf, 1) + + public.writeFile(filename, new_conf) + is_error = public.checkWebConfig() + if is_error is not True: + public.writeFile(filename, site_conf) + return public.returnMsg(False, 'ERROR:
                                    ' + is_error.replace("\n", '
                                    ') + '
                                    ') + public.serviceReload() + public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_CLOSE_SUCCESS', (site_name,)) + return public.returnMsg(True, 'SITE_NETLIMIT_CLOSE_SUCCESS') diff --git a/mod/base/web_conf/logmanager.py b/mod/base/web_conf/logmanager.py new file mode 100644 index 00000000..4739189d --- /dev/null +++ b/mod/base/web_conf/logmanager.py @@ -0,0 +1,855 @@ +import os +import re +import json +import sys +from typing import Tuple, Optional, Union, List +from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, get_log_path, pre_re_key +from mod.base import json_response + + +class _BaseLogFormat: + panel_path = "/www/server/panel" + + def __init__(self): + self._config_file = "" + self._config: Optional[dict] = None + self._format_dict = None + self._log_format_dir = '' + + @property + def config(self) -> dict: + if self._config is None: + try: + self._config = json.loads(read_file(self._config_file)) + except (json.JSONDecodeError, TypeError, ValueError): + self._config = {} + return self._config + + def save_config(self): + if self._config is not None: + write_file(self._config_file, json.dumps(self._config)) + + @property + def log_format(self) -> dict: + raise NotImplementedError() + + def check_config(self, name: str, keys: List[str], space_character=None) -> Optional[str]: + if space_character and len(space_character) > 4: + return "间隔符过长,请输入小于4位的间隔符" + rep_name = re.compile(r"^\w+$") + if rep_name.match(name) is None: + return "名称只能包含数字、字母和下划线" + if name in ("combined", "main"): + return "请勿使用默认名称" + error_key = [] + for k in keys: + if k not in self.log_format: + error_key.append(k) + if error_key: + return "无法识别以下日志关键字:【{}】".format(",".join(error_key)) + + # 添加日志格式 + def add_log_format(self, name: str, keys: List[str], space_character=" ") -> Optional[str]: + error_msg = self.check_config(name, keys, space_character) + if error_msg: + return error_msg + if name in self.config: + return "该名称的日志格式已存在" + error_msg = self._set_to_config(name, keys, space_character, is_modify=False) + if error_msg: + return error_msg + + self.config[name] = {"keys": keys, "space_character": space_character, "sites": []} + self.save_config() + service_reload() + return None + + # 修改日志格式 + def modify_log_format(self, name: str, keys: List[str], space_character=None) -> Optional[str]: + error_msg = self.check_config(name, keys, space_character) + if error_msg: + return error_msg + if name not in self.config: + return "该名称的日志格式不存在" + + self.config[name]["keys"] = keys + if space_character: + self.config[name]["space_character"] = space_character + else: + space_character = self.config[name]["space_character"] + + error_msg = self._set_to_config(name, keys, space_character, is_modify=True) + if error_msg: + return error_msg + self.save_config() + service_reload() + return None + + # 删除日志格式 + def remove_log_format(self, name: str) -> Optional[str]: + if name not in self.config: + return "该名称的日志格式不存在" + if len(self.config[name].get("sites", [])) > 1: + return "该日志格式在【{}】网站中正在使用,请先移除".format(",".join(self.config[name]["sites"])) + self._remove_form_config(name) + + del self.config[name] + self.save_config() + service_reload() + return None + + def _set_to_config(self, name: str, keys: List[str], space_character, is_modify=False) -> Optional[str]: + raise NotImplementedError + + def _remove_form_config(self, name) -> None: + conf_file = self._log_format_dir + "/{}_format.conf".format(name) + if os.path.isfile(conf_file): + os.remove(conf_file) + + # 在配置文件中设置日志格式, log_format_name传入空字符串时,设置会默认 + def set_site_log_format_in_config(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]: + """ + mutil 为True时,不会自动重载配置 + """ + raise NotImplementedError() + + # 设置日志格式 + def set_site_log_format(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]: + if log_format_name not in self.config and log_format_name != "": + return "该名称的日志格式不存在" + error_msg = self.set_site_log_format_in_config(site_name, log_format_name, conf_prefix, mutil=mutil) + if error_msg is not None: + return error_msg + if "sites" not in self.config[log_format_name]: + self.config[log_format_name]["sites"] = [] + for name, sub_conf in self.config.items(): + if name == log_format_name: + sub_conf["sites"].append(site_name) # 记录到配置文件中 + + if site_name in sub_conf.get("sites", []): + sub_conf["sites"].remove(site_name) # 如果之前使用了其他的配置,则移除其他配置中的这个站点的关联 + + self.save_config() + + +class _NgLog(_BaseLogFormat): + + @property + def log_format(self) -> dict: + if self._format_dict is None: + self._format_dict = { + "server_addr": { + "name": "服务器地址", + "key": "$server_addr", + }, + "server_port": { + "name": "服务器端口", + "key": "$server_port", + }, + "host": { + "name": "域名", + "key": "$http_host", + }, + "remote_addr": { + "name": "客户端地址", + "key": "$server_addr", + }, + "remote_port": { + "name": "客户端端口", + "key": "$server_addr", + }, + "protocol": { + "name": "服务器协议", + "key": "$server_protocol", + }, + "req_length": { + "name": "请求长度", + "key": "$request_length", + }, + "method": { + "name": "请求方法", + "key": "$request_method", + }, + "uri": { + "name": "请求uri", + "key": "$request_uri", + }, + "status": { + "name": "状态码", + "key": "$status", + }, + "sent_bytes": { + "name": "发送字节数", + "key": "$body_bytes_sent", + }, + "referer": { + "name": "来源地址", + "key": "$http_referer", + }, + "user_agent": { + "name": "用户代理(User-Agent)", + "key": "$http_user_agent", + }, + "take_time": { + "name": "请求用时", + "key": "$request_time", + }, + } + return self._format_dict + + def __init__(self): + super().__init__() + self._config_file = "{}/data/ng_log_format.json".format(self.panel_path) + self._log_format_dir = "{}/vhost/nginx/log_format".format(self.panel_path) + + def _set_log_format_include(self) -> Optional[str]: + config_file = "/www/server/nginx/conf/nginx.conf" + config_data = read_file(config_file) + if not config_data: + return "配置文件丢失无法操作" + if not os.path.isdir(self._log_format_dir): + os.makedirs(self._log_format_dir) + rep_include = re.compile(r"include\s+/www/server/panel/vhost/nginx/log_format/\*\.conf\s*;") + if rep_include.search(config_data): + return + + rep_http = re.compile(r"\s*http\s*\{[^\n]*\n") + res = rep_http.search(config_data) + if not res: + return "主配置文件中缺少http配置项,无法添加" + include_str = "include {}/*.conf;\n".format(self._log_format_dir) + new_conf = config_data[:res.end()] + include_str + config_data[res.end():] + write_file(config_file, new_conf) + + def _set_to_config(self, name: str, keys: List[str], space_character, is_modify=False) -> Optional[str]: + error_msg = self._set_log_format_include() + if error_msg: + return error_msg + conf_file = self._log_format_dir + "/{}_format.conf".format(name) + write_file(conf_file, ( + "log_format {} '{}';".format(name, space_character.join(map(lambda x: self.log_format[x]["key"], keys))) + )) + + def set_site_log_format_in_config(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]: + """ + mutil 为True时,不会自动重载配置 + """ + config_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, conf_prefix, site_name) + config_data = read_file(config_file) + if not config_data: + return "配置文件丢失无法操作" + + start_idx, end_idx = self.get_first_server_log_idx(config_data) + if start_idx: + rep_access_log = re.compile(r"\s*access_log\s+(?P[^;\s]*)(\s+(?P\w+))?;") + res = rep_access_log.search(config_data[start_idx: end_idx]) + if res.group("name") == log_format_name: + return + new_access_log = "\n access_log {} {};".format(res.group("path"), log_format_name) + new_conf = config_data[:start_idx] + new_access_log + config_data[end_idx:] + else: + last_server_idx = config_data.rfind("}") # server 范围内最后一个}的位置 + if last_server_idx == -1: + return "配置文件格式错误无法操作" + log_path = "{}/{}.log".format(get_log_path(), site_name) + new_access_log = "\n access_log {} {};\n".format(log_path, log_format_name) + new_conf = config_data[:last_server_idx] + new_access_log + config_data[last_server_idx:] + write_file(config_file, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(config_file, config_data) + return "配置修改失败" + if webserver() == "nginx" and not mutil: + service_reload() + + # 获取配置文件中server等级的第一个access_log的位置 + @staticmethod + def get_first_server_log_idx(config_data) -> Tuple[Optional[int], Optional[int]]: + rep_server = re.compile(r"\s*server\s*\{") + res = rep_server.search(config_data) + if res is None: + return None, None + rep_log = re.compile(r"\s*access_log\s+(?P[^;\s]*)(\s+(?P\w+))?;", re.M) + s_idx = res.end() + l_n = 1 + length = len(config_data) + while l_n > 0: + next_l = config_data[s_idx:].find("{") + next_r = config_data[s_idx:].find("}") + if next_l == -1 and next_r == -1: # 都没有了跳过 + return None, None + if next_r == -1 and next_l != -1: # 还剩 { 但是没有 } ,跳过 + return None, None + if next_l == -1: + next_l = length + if next_l < next_r: + if l_n == 1: + res = rep_log.search(config_data[s_idx: s_idx + next_l]) + if res: + return s_idx + res.start(), s_idx + res.end() + l_n += 1 + else: + l_n -= 1 + if l_n == 0: + res = rep_log.search(config_data[s_idx: s_idx + next_l]) + if res: + return s_idx + res.start(), s_idx + res.end() + s_idx += min(next_l, next_r) + 1 + return None, None + + # 设置站点的日志路径 + def set_site_log_path(self, site_name, site_log_path, conf_prefix, mutil=False) -> Optional[str]: + if not os.path.isdir(site_log_path): + return "不是一个存在的文件夹路径" + + if site_log_path[-1] == "/": + site_log_path = site_log_path[:-1] + + # nginx + nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name) + nginx_config = read_file(nginx_config_path) + if not nginx_config: + return "网站配置文件丢失,无法配置" + + # nginx + old_log_file = self.nginx_get_log_file_path(nginx_config, site_name, is_error_log=False) + old_error_log_file = self.nginx_get_log_file_path(nginx_config, site_name, is_error_log=True) + + if old_log_file and old_error_log_file: + new_nginx_conf = nginx_config + log_file_rep = re.compile(r"access_log +" + pre_re_key(old_log_file)) + error_log_file_rep = re.compile(r"error_log +" + pre_re_key(old_error_log_file)) + if log_file_rep.search(nginx_config): + new_nginx_conf = log_file_rep.sub("access_log {}/{}.log".format(site_log_path, site_name), + new_nginx_conf, 1) + + if error_log_file_rep.search(nginx_config): + new_nginx_conf = error_log_file_rep.sub("error_log {}/{}.error.log".format(site_log_path, site_name), + new_nginx_conf, 1) + + write_file(nginx_config_path, new_nginx_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(nginx_config_path, nginx_config) + return "配置修改失败" + if webserver() == "nginx" and not mutil: + service_reload() + + else: + return "未找到日志配置,无法操作" + + @staticmethod + def nginx_get_log_file_path(nginx_config: str, site_name: str, is_error_log: bool = False): + log_file = None + if is_error_log: + re_data = re.findall(r"error_log +(/(\S+/?)+) ?(.*?);", nginx_config) + else: + re_data = re.findall(r"access_log +(/(\S+/?)+) ?(.*?);", nginx_config) + if re_data is None: + log_file = None + else: + for i in re_data: + file_path = i[0].strip(";") + if file_path != "/dev/null" and not file_path.endswith("purge_cache.log"): + if os.path.isdir(os.path.dirname(file_path)): + log_file = file_path + break + + logsPath = '/www/wwwlogs/' + if log_file is None: + if is_error_log: + log_file = logsPath + site_name + '.log' + else: + log_file = logsPath + site_name + '.error.log' + if not os.path.isfile(log_file): + log_file = None + + return log_file + + def get_site_log_path(self, site_name, conf_prefix) -> Union[str, dict]: + config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name) + config = read_file(config_path) + if not config: + return "站点配置文件丢失" + log_file = self.nginx_get_log_file_path(config, site_name, is_error_log=False) + error_log_file = self.nginx_get_log_file_path(config, site_name, is_error_log=False) + if not (error_log_file and log_file): + return "获取失败" + return { + "log_file": log_file, + "error_log_file": error_log_file, + } + + def close_access_log(self, site_name, conf_prefix) -> Optional[str]: + nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name) + nginx_config = read_file(nginx_config_path) + if not nginx_config: + return "网站配置文件丢失,无法配置" + + start_idx, end_idx = self.get_first_server_log_idx(nginx_config) + if not start_idx: + return None + new_conf = nginx_config + + while start_idx is not None: + new_conf = new_conf[:start_idx] + '# ' + new_conf[start_idx:] + start_idx, end_idx = self.get_first_server_log_idx(new_conf) + + write_file(nginx_config_path, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(nginx_config_path, nginx_config) + return "配置修改失败" + + return None + + # 未完成 + def open_access_log(self, site_name, conf_prefix) -> Optional[str]: + nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name) + nginx_config = read_file(nginx_config_path) + if not nginx_config: + return "网站配置文件丢失,无法配置" + + new_conf = nginx_config.replace("#") + + write_file(nginx_config_path, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(nginx_config_path, nginx_config) + return "配置修改失败" + + return None + + def access_log_is_open(self, site_name, conf_prefix) -> bool: + nginx_config_path = '/www/server/panel/vhost/nginx/{}{}.conf'.format(conf_prefix, site_name) + nginx_config = read_file(nginx_config_path) + if not nginx_config: + return False + + start_idx, end_idx = self.get_first_server_log_idx(nginx_config) + return start_idx is not None + + +class _ApLog(_BaseLogFormat): + + def set_site_log_format_in_config(self, site_name, log_format_name, conf_prefix, mutil=False) -> Optional[str]: + if log_format_name == "": + log_format_name = "combined" + config_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, conf_prefix, site_name) + config_data = read_file(config_file) + if not config_data: + return "配置文件丢失无法操作" + + custom_log_rep = re.compile(r'''\s*CustomLog\s+['"](?P.*)['"](\s+(?P.*))?''', re.M) + new_custom_log = '\n CustomLog "{}" %s\n' % log_format_name + new_conf_list = [] + idx = 0 + for tmp_res in custom_log_rep.finditer(config_data): + new_conf_list.append(config_data[idx:tmp_res.start()]) + new_conf_list.append(new_custom_log.format(tmp_res.group("path"))) + idx = tmp_res.end() + new_conf_list.append(config_data[idx:]) + new_conf = "".join(new_conf_list) + + write_file(config_file, new_conf) + if webserver() == "apache" and check_server_config() is not None: + write_file(config_file, config_data) + return "配置修改失败" + if webserver() == "apache" and not mutil: + service_reload() + + # 设置站点的日志路径 + def set_site_log_path(self, site_name, site_log_path, conf_prefix, mutil=False) -> Optional[str]: + if not os.path.isdir(site_log_path): + return "不是一个存在的文件夹路径" + + if site_log_path[-1] == "/": + site_log_path = site_log_path[:-1] + + # apache + apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name) + apache_config = read_file(apache_config_path) + if not apache_config: + return "网站配置文件丢失,无法配置" + + # apache + old_log_file = self.apache_get_log_file_path(apache_config, site_name, is_error_log=False) + old_error_log_file = self.apache_get_log_file_path(apache_config, site_name, is_error_log=True) + + if old_log_file and old_error_log_file: + new_apache_conf = apache_config + log_file_rep = re.compile(r'''CustomLog +['"]?''' + pre_re_key(old_log_file) + '''['"]?''') + error_log_file_rep = re.compile(r'''ErrorLog +['"]?''' + pre_re_key(old_error_log_file) + '''['"]?''') + if log_file_rep.search(apache_config): + new_apache_conf = log_file_rep.sub('CustomLog "{}/{}-access_log"'.format(site_log_path, site_name), + new_apache_conf) + + if error_log_file_rep.search(apache_config): + new_apache_conf = error_log_file_rep.sub('ErrorLog "{}/{}.-error_log"'.format(site_log_path, site_name), + new_apache_conf) + write_file(apache_config_path, new_apache_conf) + print(new_apache_conf) + if webserver() == "apache" and check_server_config() is not None: + write_file(apache_config_path, apache_config) + return "配置修改失败" + if webserver() == "apache" and not mutil: + service_reload() + else: + return "未找到日志配置,无法操作" + + @staticmethod + def apache_get_log_file_path(apache_config: str, site_name: str, is_error_log: bool = False): + log_file = None + if is_error_log: + re_data = re.findall(r'''ErrorLog +['"]?(/(\S+/?)+)['"]? ?(.*?)\n''', apache_config) + else: + re_data = re.findall(r'''CustomLog +['"]?(/(\S+/?)+)['"]? ?(.*?)\n''', apache_config) + if re_data is None: + log_file = None + else: + for i in re_data: + file_path = i[0].strip('"').strip("'") + if file_path != "/dev/null": + if os.path.isdir(os.path.dirname(file_path)): + log_file = file_path + break + + logsPath = '/www/wwwlogs/' + if log_file is None: + if is_error_log: + log_file = logsPath + site_name + '-access_log' + else: + log_file = logsPath + site_name + '-error_log' + if not os.path.isfile(log_file): + log_file = None + + return log_file + + @staticmethod + def close_access_log(site_name, conf_prefix) -> Optional[str]: + apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name) + apache_config = read_file(apache_config_path) + if not apache_config: + return "网站配置文件丢失,无法配置" + custom_log_rep = re.compile(r'''CustomLog +['"]?(/(\S+/?)+)['"]?(\s*.*)?''', re.M) + new_conf_list = [] + idx = 0 + for tmp_res in custom_log_rep.finditer(apache_config): + new_conf_list.append(apache_config[idx:tmp_res.start()]) + new_conf_list.append("# " + tmp_res.group()) + idx = tmp_res.end() + new_conf_list.append(apache_config[idx:]) + new_conf = "".join(new_conf_list) + write_file(apache_config_path, new_conf) + if webserver() == "apache" and check_server_config() is not None: + write_file(apache_config_path, apache_config) + return "配置修改失败" + return None + + @staticmethod + def open_access_log(site_name, conf_prefix) -> Optional[str]: + apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name) + apache_config = read_file(apache_config_path) + if not apache_config: + return "网站配置文件丢失,无法配置" + new_conf = apache_config.replace("#CustomLog", "CustomLog") + write_file(apache_config_path, new_conf) + if webserver() == "apache" and check_server_config() is not None: + write_file(apache_config_path, apache_config) + return "配置修改失败" + return None + + @staticmethod + def access_log_is_open(site_name, conf_prefix) -> bool: + apache_config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name) + apache_config = read_file(apache_config_path) + if not apache_config: + return False + if apache_config.find("#CustomLog") != -1: + return False + return True + + + def get_site_log_path(self, site_name, conf_prefix) -> Union[str, dict]: + config_path = '/www/server/panel/vhost/apache/{}{}.conf'.format(conf_prefix, site_name) + config = read_file(config_path) + if not config: + return "站点配置文件丢失" + log_file = self.apache_get_log_file_path(config, site_name, is_error_log=False) + error_log_file = self.apache_get_log_file_path(config, site_name, is_error_log=False) + if not (error_log_file and log_file): + return "获取失败" + return { + "log_file": log_file, + "error_log_file": error_log_file, + } + + @property + def log_format(self) -> dict: + if self._format_dict is None: + self._format_dict = { + "server_addr": { + "name": "服务器地址", + "key": "%A", + }, + "server_port": { + "name": "服务器端口", + "key": "%p", + }, + "host": { + "name": "域名", + "key": "%V", + }, + "remote_addr": { + "name": "客户端地址", + "key": "%{c}a", + }, + "remote_port": { + "name": "客户端端口", + "key": "%{remote}p", + }, + "protocol": { + "name": "服务器协议", + "key": "%H", + }, + "method": { + "name": "请求方法", + "key": "%m", + }, + "uri": { + "name": "请求uri", + "key": r"\"%U\"", + }, + "status": { + "name": "状态码", + "key": "%>s", + }, + "sent_bytes": { + "name": "发送字节数", + "key": "%B", + }, + "referer": { + "name": "来源地址", + "key": r"\"%{Referer}i\"", + }, + "user_agent": { + "name": "用户代理(User-Agent)", + "key": r"\"%{User-Agent}i\"", + }, + "take_time": { + "name": "请求用时", + "key": "%{ms}T", + }, + } + return self._format_dict + + def __init__(self): + super().__init__() + self._config_file = "{}/data/ap_log_format.json".format(self.panel_path) + self._log_format_dir = "{}/vhost/apache/log_format".format(self.panel_path) + + def _set_log_format_include(self) -> Optional[str]: + config_file = "/www/server/apache/conf/httpd.conf" + config_data = read_file(config_file) + if not config_data: + return "配置文件丢失无法操作" + if not os.path.isdir(self._log_format_dir): + os.makedirs(self._log_format_dir) + rep_include = re.compile(r"IncludeOptional\s+/www/server/panel/vhost/apache/log_format/\*\.conf") + if rep_include.search(config_data): + return + new_conf = config_data + """ + + IncludeOptional /www/server/panel/vhost/apache/log_format/*.conf + +""" + write_file(config_file, new_conf) + + def _set_to_config(self, name: str, keys: List[str], space_character, is_modify=False) -> Optional[str]: + error_msg = self._set_log_format_include() + if error_msg: + return error_msg + conf_file = self._log_format_dir + "/{}_format.conf".format(name) + write_file(conf_file, ( + 'LogFormat "{}" {}'.format(space_character.join(map(lambda x: self.log_format[x]["key"], keys)), name) + )) + + +class RealLogMgr: + + def __init__(self, conf_prefix: str = ""): + self.conf_prefix = conf_prefix + if webserver() == "nginx": + self._log_format_tool = _NgLog() + else: + self._log_format_tool = _ApLog() + + @staticmethod + def remove_site_log_format_info(site_name: str): + for logtool in (_NgLog(), _ApLog()): + for _, conf in logtool.config.items(): + if site_name in conf.get("sites", []): + conf["sites"].remove(site_name) + logtool.save_config() + + def log_format_data(self, site_name: str): + log_format_data = None + for name, data in self._log_format_tool.config.items(): + if site_name in data.get("sites", []): + log_format_data = data + log_format_data.update(name=name) + return { + "log_format": log_format_data, + "rule": self._log_format_tool.log_format, + "all_log_format": self._log_format_tool.config + } + + def add_log_format(self, name: str, keys: List[str], space_character=" ") -> Optional[str]: + return self._log_format_tool.add_log_format(name, keys, space_character) + + def modify_log_format(self, name: str, keys: List[str], space_character=None) -> Optional[str]: + return self._log_format_tool.modify_log_format(name, keys, space_character) + + def remove_log_format(self, name: str) -> Optional[str]: + return self._log_format_tool.remove_log_format(name) + + # log_format_name 为空字符串时表示恢复成默认的日志格式 + def set_site_log_format(self, site_name, log_format_name, mutil=False) -> Optional[str]: + return self._log_format_tool.set_site_log_format(site_name, log_format_name, self.conf_prefix, mutil) + + def set_site_log_path(self, site_name, site_log_path, mutil=False) -> Optional[str]: + return self._log_format_tool.set_site_log_path(site_name, site_log_path, self.conf_prefix, mutil) + + def get_site_log_path(self, site_name) -> Union[str, dict]: + return self._log_format_tool.get_site_log_path(site_name, self.conf_prefix) + + @staticmethod + def site_crontab_log(site_name: str, hour: int, minute: int, save: int) -> bool: + if DB("crontab").where("sName =? and sType = ?", ("ALL", "logs")).find(): + return True + + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + import crontab + crontabs = crontab.crontab() + args = { + "name": "切割日志[{}]".format(site_name), + "type": 'day', + "where1": '', + "hour": hour, + "minute": minute, + "sName": site_name, + "sType": 'logs', + "notice": '', + "notice_channel": '', + "save": save, + "save_local": '1', + "backupTo": '', + "sBody": '', + "urladdress": '' + } + res = crontabs.AddCrontab(args) + if res and "id" in res.keys(): + return True + return False + + +class LogMgr: + + def __init__(self, conf_prefix: str = ""): + self.conf_prefix = conf_prefix + self._real_log_mgr = RealLogMgr(self.conf_prefix) + + def log_format_data(self, get): + try: + site_name = get.site_name.strip() + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + data = self._real_log_mgr.log_format_data(site_name) + return json_response(status=True, data=data) + + def add_log_format(self, get): + try: + space_character = " " + format_name = get.format_name.strip() + keys = json.loads(get.keys.strip()) + if "space_character" in get: + space_character = get.space_character + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.add_log_format(format_name, keys, space_character) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, msg="添加成功") + + def modify_log_format(self, get): + try: + space_character = None + format_name = get.format_name.strip() + keys = json.loads(get.keys.strip()) + if "space_character" in get: + space_character = get.space_character + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.modify_log_format(format_name, keys, space_character) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, msg="修改成功") + + def remove_log_format(self, get): + try: + format_name = get.format_name.strip() + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.remove_log_format(format_name) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, msg="删除成功") + + def set_site_log_format(self, get): + try: + format_name = get.format_name.strip() + site_name = get.site_name.strip() + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.set_site_log_format(site_name, log_format_name=format_name) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, msg="添加成功") + + def set_site_log_path(self, get): + try: + log_path = get.log_path.strip() + site_name = get.site_name.strip() + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.set_site_log_path(site_name, site_log_path=log_path) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, msg="修改路径成功") + + def get_site_log_path(self, get): + try: + site_name = get.site_name.strip() + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.get_site_log_path(site_name) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, data=msg) + + def site_crontab_log(self, get): + try: + site_name = get.site_name.strip() + hour = int(get.hour.strip()) + minute = int(get.minute.strip()) + save = int(get.save.strip()) + except (AttributeError, json.JSONDecodeError, TypeError, ValueError): + return json_response(status=False, msg="参数类型错误") + + msg = self._real_log_mgr.site_crontab_log(site_name, hour=hour, minute=minute, save=save) + if isinstance(msg, str): + return json_response(status=False, msg=msg) + return json_response(status=True, data=msg) diff --git a/mod/base/web_conf/proxy.py b/mod/base/web_conf/proxy.py new file mode 100644 index 00000000..b9a9d7eb --- /dev/null +++ b/mod/base/web_conf/proxy.py @@ -0,0 +1,636 @@ +import os +import re +import json +import shutil +import sys +import traceback +from hashlib import md5 +from typing import Tuple, Optional, Union, List, Dict, Any +from .util import webserver, check_server_config, write_file, read_file, DB, service_reload, get_log_path, pre_re_key +from mod.base import json_response + + +class RealProxy: + panel_path = "/www/server/panel" + _proxy_conf_file = "{}/data/mod_proxy_file.conf".format(panel_path) + + def __init__(self, config_prefix: str): + self.config_prefix: str = config_prefix + self._config: Optional[List[dict]] = None + + # { + # "proxyname": "yyy", + # "sitename": "www.12345test.com", + # "proxydir": "/", + # "proxysite": "http://www.baidu.com", + # "todomain": "www.baidu.com", + # "type": 0, + # "cache": 0, + # "subfilter": [ + # {"sub1": "", "sub2": ""}, + # {"sub1": "", "sub2": ""}, + # {"sub1": "", "sub2": ""}], + # "advanced": 1, + # "cachetime": 1 + # } + + @property + def config(self) -> List[dict]: + if self._config is None: + try: + self._config = json.loads(read_file(self._proxy_conf_file)) + except (json.JSONDecodeError, TypeError, ValueError): + self._config = [] + return self._config + + def save_config(self): + if self._config is not None: + write_file(self._proxy_conf_file, json.dumps(self._config)) + + # 检查代理是否存在 + def _check_even(self, proxy_conf: dict, is_modify) -> bool: + for i in self.config: + if i["sitename"] == proxy_conf["sitename"]: + if is_modify is False: + if i["proxydir"] == proxy_conf["proxydir"] or i["proxyname"] == proxy_conf["proxyname"]: + return True + else: + if i["proxyname"] != proxy_conf["proxyname"] and i["proxydir"] == proxy_conf["proxydir"]: + return True + + # 检测全局代理和目录代理是否同时存在 + def _check_proxy_even(self, proxy_conf: dict, is_modify) -> bool: + n = 0 + if is_modify: + for i in self.config: + if i["sitename"] == proxy_conf["sitename"]: + n += 1 + if n == 1: + return False + for i in self.config: + if i["sitename"] == proxy_conf["sitename"]: + if i["advanced"] != proxy_conf["advanced"]: + return True + return False + + def check_args(self, get, is_modify=False) -> Union[str, dict]: + if check_server_config(): + return '配置文件出错请先排查配置' + data = { + "advanced": 0, + "proxydir": "", + "cache": 0, + "cachetime": 1, + "type": 0, + "todomain": "$host", + } + try: + data["proxyname"] = get.proxyname.strip() + data["sitename"] = get.sitename.strip() + if "proxydir" in get: + data["proxydir"] = get.proxydir.strip() + data["proxysite"] = get.proxysite.strip() + if "todomain" in get: + data["todomain"] = get.todomain.strip() + data["type"] = int(get.type.strip()) + data["cache"] = int(get.cache.strip()) + data["subfilter"] = json.loads(get.subfilter.strip()) + data["advanced"] = int(get.advanced.strip()) + data["cachetime"] = int(get.cachetime.strip()) + except: + return "参数错误" + + if is_modify is False: + if len(data["proxyname"]) < 3 or len(data["proxyname"]) > 40: + return '名称必须大于3小于40个字符串' + + if self._check_even(data, is_modify): + return '指定反向代理名称或代理文件夹已存在' + # 判断代理,只能有全局代理或目录代理 + if self._check_proxy_even(data, is_modify): + return '不能同时设置目录代理和全局代理' + # 判断cachetime类型 + if data["cachetime"] < 1: + return "缓存时间不能为空" + + rep = r"http(s)?\:\/\/" + rep_re_key = re.compile(r'''[?=\[\])(*&^%$#@!~`{}><,'"\\]+''') + # 检测代理目录格式 + if rep_re_key.search(data["proxydir"]): + return "代理目录不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]" + # 检测发送域名格式 + if get.todomain: + if re.search("[}{#;\"\']+", data["todomain"]): + return '发送域名格式错误:' + data["todomain"] + '
                                    不能存在以下特殊字符【 } { # ; \" \' 】 ' + if webserver() != 'openlitespeed' and not get.todomain: + data["todomain"] = "$host" + + # 检测目标URL格式 + if not re.match(rep, data["proxysite"]): + return '域名格式错误 ' + data["proxysite"] + if rep_re_key.search(data["proxysite"]): + return "目标URL不能有以下特殊符号 ?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\\,',\"]" + + if not data["proxysite"].split('//')[-1]: + return '目标URL不能为[http://或https://],请填写完整URL,如:https://www.bt.cn' + + for s in data["subfilter"]: + if not s["sub1"]: + continue + if not s["sub1"] and s["sub2"]: + return '请输入被替换的内容' + elif s["sub1"] == s["sub2"]: + return '替换内容与被替换内容不能一致' + return data + + def check_location(self, site_name, proxy_dir: str) -> Optional[str]: + # 伪静态文件路径 + rewrite_conf_path = "%s/vhost/rewrite/%s%s.conf" % (self.panel_path, self.config_prefix, site_name) + # vhost文件 + vhost_path = "%s/vhost/nginx/%s%s.conf" % (self.panel_path, self.config_prefix, site_name) + + rep_location = re.compile(r"location\s+(\^~\s*)?%s\s*{" % proxy_dir) + + for i in [rewrite_conf_path, vhost_path]: + conf = read_file(i) + if isinstance(conf, str) and rep_location.search(conf): + return '伪静态/站点主配置文件已经存在全局反向代理' + + @staticmethod + def _set_nginx_proxy_base(): + file = "/www/server/nginx/conf/proxy.conf" + setup_path = "/www/server" + if not os.path.exists(file): + conf = '''proxy_temp_path %s/nginx/proxy_temp_dir; +proxy_cache_path %s/nginx/proxy_cache_dir levels=1:2 keys_zone=cache_one:10m inactive=1d max_size=5g; +client_body_buffer_size 512k; +proxy_connect_timeout 60; +proxy_read_timeout 60; +proxy_send_timeout 60; +proxy_buffer_size 32k; +proxy_buffers 4 64k; +proxy_busy_buffers_size 128k; +proxy_temp_file_write_size 128k; +proxy_next_upstream error timeout invalid_header http_500 http_503 http_404; +proxy_cache cache_one;''' % (setup_path, setup_path) + write_file(file, conf) + + conf = read_file(file) + if conf and conf.find('include proxy.conf;') == -1: + rep = r"include\s+mime.types;" + conf = re.sub(rep, "include mime.types;\n\tinclude proxy.conf;", conf) + write_file(file, conf) + + def set_nginx_proxy_include(self, site_name) -> Optional[str]: + self._set_nginx_proxy_base() + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name) + ng_conf = read_file(ng_file) + if not ng_conf: + return "配置文件丢失" + cure_cache = '''location ~ /purge(/.*) { + proxy_cache_purge cache_one $host$1$is_args$args; + #access_log /www/wwwlogs/%s_purge_cache.log; + }''' % site_name + + proxy_dir = "{}/vhost/nginx/proxy/{}".format(self.panel_path, site_name) + if not os.path.isdir(os.path.dirname(proxy_dir)): + os.makedirs(os.path.dirname(proxy_dir)) + + if not os.path.isdir(proxy_dir): + os.makedirs(proxy_dir) + + include_conf = ( + "\n #清理缓存规则\n" + " %s\n" + " #引用反向代理规则,注释后配置的反向代理将无效\n" + " include /www/server/panel/vhost/nginx/proxy/%s/*.conf;\n" + ) % (cure_cache, site_name) + + rep_include = re.compile(r"\s*include.*/proxy/.*/\*\.conf\s*;", re.M) + if rep_include.search(ng_conf): + return + # 添加 引入 + rep_list = [ + (re.compile(r"\s*include\s+.*/rewrite/.*\.conf;(\s*#REWRITE-END)?"), False), # 先匹配伪静态,有伪静态就加到伪静态下 + (re.compile(r"#PHP-INFO-END"), False), # 匹配PHP配置, 加到php配置下 + (re.compile(r"\sinclude +.*/ip-restrict/.*\*\.conf;", re.M), False), # 匹配IP配置, 加其下 + (re.compile(r"#SECURITY-END"), False), # 匹配Referer配置, 加其下 + ] + + # 使用正则匹配确定插入位置 + def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool: + tmp_res = tmp_rep.search(ng_conf) + if not tmp_res: + return False + if use_start: + new_conf = ng_conf[:tmp_res.start()] + include_conf + tmp_res.group() + ng_conf[tmp_res.end():] + else: + new_conf = ng_conf[:tmp_res.start()] + tmp_res.group() + include_conf + ng_conf[tmp_res.end():] + + write_file(ng_file, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return False + return True + for r, s in rep_list: + if set_by_rep_idx(r, s): + break + else: + return "无法在配置文件中定位到需要添加的项目" + + now_ng_conf = read_file(ng_file) + # 清理文件缓存 + rep_location = re.compile(r"location\s+~\s+\.\*\\\.[^{]*{(\s*(expires|error_log|access_log).*;){3}\s*}\s*") + + new__ng_conf = rep_location.sub("", now_ng_conf) + write_file(ng_file, new__ng_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(ng_file, now_ng_conf) + + def un_set_nginx_proxy_include(self, site_name) -> Optional[str]: + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name) + ng_conf = read_file(ng_file) + if not ng_conf: + return "配置文件丢失" + rep_list = [ + re.compile(r"\s*#清理缓存规则\n"), + re.compile(r"\s*location\s+~\s+/purge[^{]*{[^}]*}\s*"), + re.compile(r"(#[^#\n]*\n)?\s*include.*/proxy/.*/\*\.conf\s*;[^\n]*\n"), + ] + new_conf = ng_conf + for rep in rep_list: + new_conf = rep.sub("", new_conf, 1) + + write_file(ng_file, new_conf) + if webserver() == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return "配置移除失败" + + def set_apache_proxy_include(self, site_name): + ap_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, site_name) + ap_conf = read_file(ap_file) + if not ap_conf: + return "配置文件丢失" + proxy_dir = "{}/vhost/apache/proxy/{}".format(self.panel_path, site_name) + + if not os.path.isdir(os.path.dirname(proxy_dir)): + os.makedirs(os.path.dirname(proxy_dir)) + if not os.path.isdir(proxy_dir): + os.makedirs(proxy_dir) + + include_conf = ( + " #引用反向代理规则,注释后配置的反向代理将无效\n" + " IncludeOptional /www/server/panel/vhost/apache/proxy/%s/*.conf\n" + ) % site_name + + rep_include = re.compile(r"\s*IncludeOptional.*/proxy/.*/\*\.conf\s*;", re.M) + if rep_include.search(ap_conf): + return + + # 添加 引入 + rep_list = [ + (re.compile(r"(.|\n)*?[^\n]*\n"), False), # 匹配PHP配置, 加到php配置下 + (re.compile(r"CustomLog[^\n]*\n"), False), # 匹配Referer配置, 加其下 + ] + + # 使用正则匹配确定插入位置 + def set_by_rep_idx(rep: re.Pattern, use_start: bool) -> bool: + new_conf_list = [] + last_idx = 0 + for tmp in rep.finditer(ap_conf): + new_conf_list.append(ap_conf[last_idx:tmp.start()]) + if use_start: + new_conf_list.append(include_conf) + new_conf_list.append(tmp.group()) + else: + new_conf_list.append(tmp.group()) + new_conf_list.append(include_conf) + last_idx = tmp.end() + if last_idx == 0: + return False + + new_conf_list.append(ap_conf[last_idx:]) + + new_conf = "".join(new_conf_list) + write_file(ap_file, new_conf) + if webserver() == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return False + return True + + for r, s in rep_list: + if set_by_rep_idx(r, s): + break + else: + return "无法在配置文件中定位到需要添加的项目" + + def un_set_apache_proxy_include(self, site_name) -> Optional[str]: + ng_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, site_name) + ap_conf = read_file(ng_file) + if not ap_conf: + return "配置文件丢失" + rep_include = re.compile(r"(#.*\n)?\s*IncludeOptiona.*/proxy/.*/\*\.conf\s*[^\n]\n") + + new_conf = rep_include.sub("", ap_conf) + + write_file(ng_file, new_conf) + if webserver() == "apache" and check_server_config() is not None: + write_file(ng_file, ap_conf) + return "配置移除失败" + + def set_nginx_proxy(self, proxy_data: dict) -> Optional[str]: + proxy_name_md5 = self._calc_proxy_name_md5(proxy_data["proxyname"]) + ng_proxy_file = "%s/vhost/nginx/proxy/%s/%s_%s.conf" % ( + self.panel_path, proxy_data["sitename"], proxy_name_md5, proxy_data["sitename"]) + if proxy_data["type"] == 0: + if os.path.isfile(ng_proxy_file): + os.remove(ng_proxy_file) + return + + random_string = self._random_string() + + # websocket前置map + map_file = "{}/vhost/nginx/0.websocket.conf".format(self.panel_path) + if not os.path.exists(map_file): + write_file(map_file, ''' +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +}''') + # 构造缓存配置 + ng_cache = r""" + if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) + { + expires 1m; + } + proxy_ignore_headers Set-Cookie Cache-Control expires; + proxy_cache cache_one; + proxy_cache_key $host$uri$is_args$args; + proxy_cache_valid 200 304 301 302 %sm;""" % proxy_data["cachetime"] + no_cache = r""" + set $static_file%s 0; + if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) + { + set $static_file%s 1; + expires 1m; + } + if ( $static_file%s = 0 ) + { + add_header Cache-Control no-cache; + }""" % (random_string, random_string, random_string) + + ng_proxy = ''' +#PROXY-START%s + +location ^~ %s +{ + proxy_pass %s; + proxy_set_header Host %s; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_http_version 1.1; + # proxy_hide_header Upgrade; + + add_header X-Cache $upstream_cache_status; + + #Set Nginx Cache + %s + %s +} + +#PROXY-END%s''' + + # 构造替换字符串 + ng_sub_data_list = [] + for s in proxy_data["subfilter"]: + if not s["sub1"]: + continue + if '"' in s["sub1"]: + s["sub1"] = s["sub1"].replace('"', '\\"') + if '"' in s["sub2"]: + s["sub2"] = s["sub2"].replace('"', '\\"') + ng_sub_data_list.append(' sub_filter "%s" "%s";' % (s["sub1"], s["sub2"])) + if ng_sub_data_list: + ng_sub_filter = ''' + proxy_set_header Accept-Encoding ""; + %s + sub_filter_once off;''' % "\n".join(ng_sub_data_list) + else: + ng_sub_filter = '' + + if proxy_data["proxydir"][-1] != '/': + proxy_dir = proxy_data["proxydir"] + "/" + else: + proxy_dir = proxy_data["proxydir"] + + if proxy_data["proxysite"][-1] != '/': + proxy_site = proxy_data["proxysite"] + "/" + else: + proxy_site = proxy_data["proxysite"] + + # 构造反向代理 + if proxy_data["cache"] == 1: + ng_proxy_cache = ng_proxy % ( + proxy_dir, proxy_dir, proxy_site, proxy_data["todomain"], ng_sub_filter, ng_cache, proxy_dir) + else: + ng_proxy_cache = ng_proxy % ( + proxy_dir, proxy_dir, proxy_site, proxy_data["todomain"], ng_sub_filter, no_cache, proxy_dir) + + write_file(ng_proxy_file, ng_proxy_cache) + if webserver() == "nginx" and check_server_config() is not None: + import public + public.print_log(check_server_config()) + os.remove(ng_proxy_file) + return "配置添加失败" + + def set_apache_proxy(self, proxy_data: dict): + proxy_name_md5 = self._calc_proxy_name_md5(proxy_data["proxyname"]) + ap_proxy_file = "%s/vhost/apache/proxy/%s/%s_%s.conf" % ( + self.panel_path, proxy_data["sitename"], proxy_name_md5, proxy_data["sitename"]) + if proxy_data["type"] == 0: + if os.path.isfile(ap_proxy_file): + os.remove(ap_proxy_file) + return + + ap_proxy = '''#PROXY-START%s + + ProxyRequests Off + SSLProxyEngine on + ProxyPass %s %s/ + ProxyPassReverse %s %s/ + +#PROXY-END%s''' % (proxy_data["proxydir"], proxy_data["proxydir"], proxy_data["proxysite"], + proxy_data["proxydir"],proxy_data["proxysite"], proxy_data["proxydir"]) + write_file(ap_proxy_file, ap_proxy) + + @staticmethod + def _random_string() -> str: + from uuid import uuid4 + return "bt" + uuid4().hex[:6] + + @staticmethod + def _calc_proxy_name_md5(data: str) -> str: + m = md5() + m.update(data.encode("utf-8")) + return m.hexdigest() + + def create_proxy(self, get) -> Optional[str]: + proxy_data = self.check_args(get, is_modify=False) + if isinstance(proxy_data, str): + return proxy_data + if webserver() == "nginx": + error_msg = self.check_location(proxy_data["sitename"], proxy_data["proxydir"]) + if error_msg: + return error_msg + + error_msg = self.set_nginx_proxy_include(proxy_data["sitename"]) + if webserver() == "nginx" and error_msg: + return error_msg + error_msg = self.set_apache_proxy_include(proxy_data["sitename"]) + if webserver() == "apache" and error_msg: + return error_msg + error_msg = self.set_nginx_proxy(proxy_data) + if webserver() == "nginx" and error_msg: + return error_msg + self.set_apache_proxy(proxy_data) + self.config.append(proxy_data) + self.save_config() + service_reload() + + def modify_proxy(self, get) -> Optional[str]: + proxy_data = self.check_args(get, is_modify=True) + if isinstance(proxy_data, str): + return proxy_data + idx = None + + for index, i in enumerate(self.config): + if i["proxyname"] == proxy_data["proxyname"] and i["sitename"] == proxy_data["sitename"]: + idx = index + break + if idx is None: + return "未找到该名称的反向代理配置" + + if webserver() == "nginx" and proxy_data["proxydir"] != self.config[idx]["proxydir"]: + error_msg = self.check_location(proxy_data["sitename"], proxy_data["proxydir"]) + if error_msg: + return error_msg + + error_msg = self.set_nginx_proxy_include(proxy_data["sitename"]) + if webserver() == "nginx" and error_msg: + return error_msg + error_msg = self.set_apache_proxy_include(proxy_data["sitename"]) + if webserver() == "apache" and error_msg: + return error_msg + error_msg = self.set_nginx_proxy(proxy_data) + if webserver() == "nginx" and error_msg: + return error_msg + self.set_apache_proxy(proxy_data) + self.config[idx] = proxy_data + self.save_config() + service_reload() + + def remove_proxy(self, site_name, proxy_name, multiple=False) -> Optional[str]: + idx = None + site_other = False + for index, i in enumerate(self.config): + if i["proxyname"] == proxy_name and i["sitename"] == site_name: + idx = index + if i["sitename"] == site_name and i["proxyname"] != proxy_name: + site_other = True + + if idx is None: + return "未找到该名称的反向代理配置" + + proxy_name_md5 = self._calc_proxy_name_md5(proxy_name) + ng_proxy_file = "%s/vhost/nginx/proxy/%s/%s_%s.conf" % ( + self.panel_path, site_name, proxy_name_md5, site_name) + ap_proxy_file = "%s/vhost/apache/proxy/%s/%s_%s.conf" % ( + self.panel_path, site_name, proxy_name_md5, site_name) + if os.path.isfile(ap_proxy_file): + os.remove(ap_proxy_file) + + if os.path.isfile(ng_proxy_file): + os.remove(ng_proxy_file) + del self.config[idx] + self.save_config() + if not site_other: + self.un_set_apache_proxy_include(site_name) + self.un_set_nginx_proxy_include(site_name) + if not multiple: + service_reload() + + def get_proxy_list(self, get) -> Union[str, List[Dict[str, Any]]]: + try: + site_name = get.sitename.strip() + except (AttributeError, ValueError, TypeError): + return "参数错误" + proxy_list = [] + web_server = webserver() + for conf in self.config: + if conf["sitename"] != site_name: + continue + md5_name = self._calc_proxy_name_md5(conf['proxyname']) + conf["proxy_conf_file"] = "%s/vhost/%s/proxy/%s/%s_%s.conf" % ( + self.panel_path, web_server, site_name, md5_name, site_name) + proxy_list.append(conf) + return proxy_list + + def remove_site_proxy_info(self, site_name): + idx_list = [] + for index, i in enumerate(self.config): + if i["sitename"] == site_name: + idx_list.append(index) + + for idx in idx_list[::-1]: + del self.config[idx] + + self.save_config() + + ng_proxy_dir = "%s/vhost/nginx/proxy/%s" % (self.panel_path, site_name) + ap_proxy_dir = "%s/vhost/apache/proxy/%s" % (self.panel_path, site_name) + + if os.path.isdir(ng_proxy_dir): + shutil.rmtree(ng_proxy_dir) + + if os.path.isdir(ap_proxy_dir): + shutil.rmtree(ap_proxy_dir) + + +class Proxy(object): + + def __init__(self, config_prefix=""): + self.config_prefix = config_prefix + self._p = RealProxy(self.config_prefix) + + def create_proxy(self, get): + msg = self._p.create_proxy(get) + if msg: + return json_response(status=False, msg=msg) + return json_response(status=True, msg="添加成功") + + def modify_proxy(self, get): + msg = self._p.modify_proxy(get) + if msg: + return json_response(status=False, msg=msg) + return json_response(status=True, msg="修改成功") + + def remove_proxy(self, get): + try: + site_name = get.sitename.strip() + proxy_name = get.proxyname.strip() + except: + return json_response(status=False, msg="参数错误") + msg = self._p.remove_proxy(site_name, proxy_name) + if msg: + return json_response(status=False, msg=msg) + return json_response(status=True, msg="删除成功") + + def get_proxy_list(self, get): + data = self._p.get_proxy_list(get) + if isinstance(data, str): + return json_response(status=False, msg=data) + else: + return json_response(status=True, data=data) \ No newline at end of file diff --git a/mod/base/web_conf/redirect.py b/mod/base/web_conf/redirect.py new file mode 100644 index 00000000..fcdf818c --- /dev/null +++ b/mod/base/web_conf/redirect.py @@ -0,0 +1,737 @@ +import os +import re +import json +import hashlib +import shutil +import time +from typing import Tuple, Optional, Union, Dict, List, Any +from urllib import parse +from itertools import product +from .util import webserver, check_server_config, write_file, read_file, DB, service_reload +from mod.base import json_response + + +class RealRedirect: + panel_path = "/www/server/panel" + _redirect_conf_file = "{}/data/redirect.conf".format(panel_path) + + _ng_redirect_domain_format = """ +if ($host ~ '^%s'){ + return %s %s%s; +} +""" + _ng_redirect_path_format = """ +rewrite ^%s(.*) %s%s %s; +""" + _ap_redirect_domain_format = """ + + RewriteEngine on + RewriteCond %%{HTTP_HOST} ^%s [NC] + RewriteRule ^(.*) %s%s [L,R=%s] + +""" + _ap_redirect_path_format = """ + + RewriteEngine on + RewriteRule ^%s(.*) %s%s [L,R=%s] + +""" + + def __init__(self, config_prefix: str): + self._config: Optional[List[Dict[str, Union[str, int]]]] = None + self.config_prefix = config_prefix + self._webserver = None + + @property + def webserver(self) -> str: + if self._webserver is not None: + return self._webserver + self._webserver = webserver() + return self._webserver + + @property + def config(self) -> List[Dict[str, Union[str, int, List]]]: + if self._config is not None: + return self._config + try: + self._config = json.loads(read_file(self._redirect_conf_file)) + except (json.JSONDecodeError, TypeError, ValueError): + self._config = [] + if not isinstance(self._config, list): + self._config = [] + return self._config + + def save_config(self): + if self._config is not None: + return write_file(self._redirect_conf_file, json.dumps(self._config)) + + def _check_redirect_domain_exist(self, site_name, + redirect_domain: list, + redirect_name: str = None, + is_modify=False) -> Optional[List[str]]: + res = set() + redirect_domain_set = set(redirect_domain) + for c in self.config: + if c["sitename"] != site_name: + continue + if is_modify: + if c["redirectname"] != redirect_name: + res |= set(c["redirectdomain"]) & redirect_domain_set + else: + res |= set(c["redirectdomain"]) & redirect_domain_set + return list(res) if res else None + + def _check_redirect_path_exist(self, site_name, + redirect_path: str, + redirect_name: str = None) -> bool: + for c in self.config: + if c["sitename"] == site_name: + if c["redirectname"] != redirect_name and c["redirectpath"] == redirect_path: + return True + return False + + @staticmethod + def _parse_url_domain(url: str): + return parse.urlparse(url).netloc + + @staticmethod + def _parse_url_path(url: str): + return parse.urlparse(url).path + + # 计算name md5 + @staticmethod + def _calc_redirect_name_md5(redirect_name) -> str: + md5 = hashlib.md5() + md5.update(redirect_name.encode('utf-8')) + return md5.hexdigest() + + def _check_redirect(self, site_name, redirect_name, is_error=False): + for i in self.config: + if i["sitename"] != site_name: + continue + if is_error and "errorpage" in i and i["errorpage"] in [1, '1']: + return i + if i["redirectname"] == redirect_name: + return i + return None + + # 创建修改配置检测 + def _check_redirect_args(self, get, is_modify=False) -> Union[str, Dict]: + if check_server_config() is not None: + return '配置文件出错请先排查配置' + + try: + site_name = get.sitename.strip() + redirect_path = get.redirectpath.strip() + redirect_type = get.redirecttype.strip() + domain_or_path = get.domainorpath.strip() + hold_path = int(get.holdpath) + + to_url = "" + to_path = "" + error_page = 0 + redirect_domain = [] + redirect_name = "" + status_type = 1 + + if "redirectname" in get and get.redirectname.strip(): + redirect_name = get.redirectname.strip() + if "tourl" in get: + to_url = get.tourl.strip() + if "topath" in get: + to_path = get.topath.strip() + if "redirectdomain" in get: + redirect_domain = json.loads(get.redirectdomain.strip()) + if "type" in get: + status_type = int(get.type) + if "errorpage" in get: + error_page = int(get.errorpage) + except (AttributeError, ValueError): + return '参数错误' + + if not is_modify: + if not redirect_name: + return "参数错误,配置名称不能为空" + # 检测名称是否重复 + if not (3 <= len(redirect_name) < 15): + return '名称必须大于2小于15个字符串' + + if self._check_redirect(site_name, redirect_name, error_page == 1): + return '指定重定向名称已存在' + + site_info = DB('sites').where("name=?", (site_name,)).find() + if not isinstance(site_info, dict): + return "站点信息查询错误" + else: + site_name = site_info["name"] + + # 检测目标URL格式 + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + if to_url and not re.match(rep, to_url): + return '目标URL格式不对【%s】' % to_url + + # 非404页面de重定向检测项 + if error_page != 1: + # 检测是否选择域名 + if domain_or_path == "domain": + if not redirect_domain: + return '请选择重定向域名' + # 检测域名是否已经存在配置文件 + repeat_domain = self._check_redirect_domain_exist(site_name, redirect_domain, redirect_name, is_modify) + if repeat_domain: + return '重定向域名重复 %s' % repeat_domain + + # 检查目标URL的域名和被重定向的域名是否一样 + tu = self._parse_url_domain(to_url) + for d in redirect_domain: + if d == tu: + return '域名 "%s" 和目标域名一致请取消选择' % d + else: + if not redirect_path: + return '请输入重定向路径' + if redirect_path[0] != "/": + return "路径格式不正确,格式为/xxx" + # 检测路径是否有存在配置文件 + if self._check_redirect_path_exist(site_name, redirect_path, redirect_name): + return '重定向路径重复 %s' % redirect_path + + to_url_path = self._parse_url_path(to_url) + if to_url_path.startswith(redirect_path): + return '目标URL[%s]以被重定向的路径[%s]开头,会导致循环匹配' % (to_url_path, redirect_path) + # 404页面重定向检测项 + else: + if not to_url and not to_path: + return '首页或自定义页面必须二选一' + if to_path: + to_path = "/" + + return { + "tourl": to_url, + "topath": to_path, + "errorpage": error_page, + "redirectdomain": redirect_domain, + "redirectname": redirect_name if redirect_name else str(int(time.time())), + "type": status_type, + "sitename": site_name, + "redirectpath": redirect_path, + "redirecttype": redirect_type, + "domainorpath": domain_or_path, + "holdpath": hold_path, + } + + def create_redirect(self, get) -> Tuple[bool, str]: + res_conf = self._check_redirect_args(get, is_modify=False) + if isinstance(res_conf, str): + return False, res_conf + + res = self._set_include(res_conf) + if res is not None: + return False, res + res = self._write_config(res_conf) + if res is not None: + return False, res + self.config.append(res_conf) + self.save_config() + service_reload() + return True, '创建成功' + + def _set_include(self, res_conf) -> Optional[str]: + flag, msg = self._set_nginx_redirect_include(res_conf) + if not flag and webserver() == "nginx": + return msg + flag, msg = self._set_apache_redirect_include(res_conf) + if not flag and webserver() == "apache": + return msg + + def _write_config(self, res_conf) -> Optional[str]: + if res_conf["errorpage"] != 1: + res = self.write_nginx_redirect_file(res_conf) + if res is not None: + return res + res = self.write_apache_redirect_file(res_conf) + if res is not None: + return res + else: + self.unset_nginx_404_conf(res_conf["sitename"]) + res = self.write_nginx_404_redirect_file(res_conf) + if res is not None: + return res + res = self.write_apache_404_redirect_file(res_conf) + if res is not None: + return res + + def modify_redirect(self, get) -> Tuple[bool, str]: + """ + @name 修改、启用、禁用重定向 + @author hezhihong + @param get.sitename 站点名称 + @param get.redirectname 重定向名称 + @param get.tourl 目标URL + @param get.redirectdomain 重定向域名 + @param get.redirectpath 重定向路径 + @param get.redirecttype 重定向类型 + @param get.type 重定向状态 0禁用 1启用 + @param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向 + @param get.holdpath 保留路径 0不保留 1保留 + @return json + """ + # 基本信息检查 + res_conf = self._check_redirect_args(get, is_modify=True) + if isinstance(res_conf, str): + return False, res_conf + + old_idx = None + for i, conf in enumerate(self.config): + if conf["redirectname"] == res_conf["redirectname"] and conf["sitename"] == res_conf["sitename"]: + old_idx = i + + res = self._set_include(res_conf) + if res is not None: + return False, res + res = self._write_config(res_conf) + if res is not None: + return False, res + + if old_idx is not None: + self.config[old_idx].update(res_conf) + else: + self.config.append(res_conf) + self.save_config() + service_reload() + return True, '修改成功' + + def _set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ng_redirect_dir = "%s/vhost/nginx/redirect/%s" % (self.panel_path, redirect_conf["sitename"]) + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"]) + if not os.path.exists(ng_redirect_dir): + os.makedirs(ng_redirect_dir, 0o600) + ng_conf = read_file(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"\sinclude +.*/redirect/.*\*\.conf;", re.M) + if rep_include.search(ng_conf): + return True, "" + redirect_include = ( + "#SSL-END\n" + " #引用重定向规则,注释后配置的重定向代理将无效\n" + " include {}/*.conf;" + ).format(ng_redirect_dir) + + if "#SSL-END" not in ng_conf: + return False, "添加配置失败,无法定位SSL相关配置的位置" + + new_conf = ng_conf.replace("#SSL-END", redirect_include) + write_file(ng_file, new_conf) + if self.webserver == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return False, "添加配置失败" + + return True, "" + + def _un_set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"]) + ng_conf = read_file(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"(#(.*)\n)?\s*include +.*/redirect/.*\*\.conf;") + if not rep_include.search(ng_conf): + return True, "" + + new_conf = rep_include.sub("", ng_conf, 1) + write_file(ng_file, new_conf) + if self.webserver == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return False, "移除配置失败" + + return True, "" + + def _set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ap_redirect_dir = "%s/vhost/apache/redirect/%s" % (self.panel_path, redirect_conf["sitename"]) + ap_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"]) + if not os.path.exists(ap_redirect_dir): + os.makedirs(ap_redirect_dir, 0o600) + + ap_conf = read_file(ap_file) + if not isinstance(ap_conf, str): + return False, "apache配置文件读取失败" + + rep_include = re.compile(r"\sIncludeOptional +.*/redirect/.*\*\.conf", re.M) + include_count = len(list(rep_include.finditer(ap_conf))) + if ap_conf.count("") == include_count: + return True, "" + + if include_count > 0: + # 先清除已有的配置 + self._un_set_apache_redirect_include(redirect_conf) + + rep_custom_log = re.compile(r"CustomLog .*\n") + rep_deny_files = re.compile(r"\n\s*#DENY FILES") + + include_conf = ( + "\n # 引用重定向规则,注释后配置的重定向代理将无效\n" + " IncludeOptional {}/*.conf\n" + ).format(ap_redirect_dir) + + new_conf = None + + def set_by_rep_idx(rep: re.Pattern, use_start: bool) -> bool: + new_conf_list = [] + last_idx = 0 + for tmp in rep.finditer(ap_conf): + new_conf_list.append(ap_conf[last_idx:tmp.start()]) + if use_start: + new_conf_list.append(include_conf) + new_conf_list.append(tmp.group()) + else: + new_conf_list.append(tmp.group()) + new_conf_list.append(include_conf) + last_idx = tmp.end() + + new_conf_list.append(ap_conf[last_idx:]) + + nonlocal new_conf + new_conf = "".join(new_conf_list) + write_file(ap_file, new_conf) + if self.webserver == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return False + return True + + if set_by_rep_idx(rep_custom_log, False) and rep_include.search(new_conf): + return True, "" + + if set_by_rep_idx(rep_deny_files, True) and rep_include.search(new_conf): + return True, "" + return False, "设置失败" + + def _un_set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ap_file = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, redirect_conf["sitename"]) + ap_conf = read_file(ap_file) + if not isinstance(ap_conf, str): + return False, "apache配置文件读取失败" + + rep_include = re.compile(r"(#(.*)\n)?\s*IncludeOptional +.*/redirect/.*\*\.conf") + if not rep_include.search(ap_conf): + return True, "" + + new_conf = rep_include.sub("", ap_conf) + write_file(ap_file, new_conf) + if self.webserver == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return False, "移除配置失败" + + return True, "" + + def write_nginx_redirect_file(self, redirect_conf: dict) -> Optional[str]: + conf_file = "{}/vhost/nginx/redirect/{}/{}_{}.conf".format( + self.panel_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]), + redirect_conf["sitename"] + ) + if redirect_conf["type"] == 1: + to_url = redirect_conf["tourl"] + conf_list = ["#REWRITE-START"] + if redirect_conf["domainorpath"] == "domain": + hold_path = "$request_uri" if redirect_conf["holdpath"] == 1 else "" + for sd in redirect_conf["redirectdomain"]: + if sd.startswith("*."): + sd = r"[\w.]+\." + sd[2:] + + conf_list.append(self._ng_redirect_domain_format % ( + sd, redirect_conf["redirecttype"], to_url, hold_path + )) + else: + redirect_path = redirect_conf["redirectpath"] + if redirect_conf["redirecttype"] == "301": + redirect_type = "permanent" + else: + redirect_type = "redirect" + hold_path = "$1" if redirect_conf["holdpath"] == 1 else "" + conf_list.append(self._ng_redirect_path_format % (redirect_path, to_url, hold_path, redirect_type)) + + conf_list.append("#REWRITE-END") + + conf_data = "\n".join(conf_list) + write_file(conf_file, conf_data) + + if self.webserver == "nginx": + error_msg = check_server_config() + if error_msg is not None: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + error_msg.replace("\n", '
                                    ') + '
                                    ' + else: + if os.path.exists(conf_file): + os.remove(conf_file) + + def write_apache_redirect_file(self, redirect_conf: dict) -> Optional[str]: + conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format( + self.panel_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]), + redirect_conf["sitename"] + ) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + to_url = redirect_conf["tourl"] + conf_list = ["#REWRITE-START"] + hold_path = "$1" if redirect_conf["holdpath"] == 1 else "" + if redirect_conf["domainorpath"] == "domain": + for sd in redirect_conf["redirectdomain"]: + if sd.startswith("*."): + sd = r"[\w.]+\." + sd[2:] + + conf_list.append(self._ap_redirect_domain_format % ( + sd, to_url, hold_path, redirect_conf["redirecttype"] + )) + else: + redirect_path = redirect_conf["redirectpath"] + conf_list.append(self._ap_redirect_path_format % (redirect_path, to_url, hold_path, redirect_conf["redirecttype"])) + + conf_list.append("#REWRITE-END") + + write_file(conf_file, "\n".join(conf_list)) + if self.webserver == "apache": + error_msg = check_server_config() + if error_msg is not None: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + error_msg.replace("\n", '
                                    ') + '
                                    ' + + def unset_nginx_404_conf(self, site_name): + """ + 清理已有的 404 页面 配置 + """ + need_clear_files = [ + "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name), + "{}/vhost/nginx/rewrite/{}{}.conf".format(self.panel_path, self.config_prefix, site_name), + ] + rep_error_page = re.compile(r'(?P.*)error_page +404 +/404\.html[^\n]*\n', re.M) + rep_location_404 = re.compile(r'(?P.*)location += +/404\.html[^}]*}') + clear_files = [ + { + "data": read_file(i), + "path": i, + } for i in need_clear_files + ] + for file_info, rep in product(clear_files, (rep_error_page, rep_location_404)): + if not isinstance(file_info["data"], str): + continue + tmp_res = rep.search(file_info["data"]) + if not tmp_res or tmp_res.group("prefix").find("#") != -1: + continue + file_info["data"] = rep.sub("", file_info["data"]) + + for i in clear_files: + if not isinstance(i["data"], str): + continue + write_file(i["path"], i["data"]) + + def write_nginx_404_redirect_file(self, redirect_conf: dict) -> Optional[str]: + """ + 设置nginx 404重定向 + """ + r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"]) + file_path = "{}/vhost/nginx/redirect/{}".format(self.panel_path, redirect_conf["sitename"]) + file_name = '%s_%s.conf' % (r_name_md5, redirect_conf["sitename"]) + conf_file = os.path.join(file_path, file_name) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + _path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"] + conf_data = ( + '#REWRITE-START\n' + 'error_page 404 = @notfound;\n' + 'location @notfound {{\n' + ' return {} {};\n' + '}}\n#REWRITE-END' + ).format(redirect_conf["redirecttype"], _path) + + write_file(conf_file, conf_data) + if self.webserver == "nginx": + error_msg = check_server_config() + if error_msg is not None: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + error_msg.replace("\n", '
                                    ') + '
                                    ' + + def write_apache_404_redirect_file(self, redirect_conf: dict) -> Optional[str]: + """ + 设置apache 404重定向 + """ + r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"]) + conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format( + self.panel_path, redirect_conf["sitename"], r_name_md5, redirect_conf["sitename"] + ) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + _path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"] + conf_data = """ +#REWRITE-START + + RewriteEngine on + RewriteCond %{{REQUEST_FILENAME}} !-f + RewriteCond %{{REQUEST_FILENAME}} !-d + RewriteRule . {} [L,R={}] + +#REWRITE-END +""".format(_path, redirect_conf["redirecttype"]) + + write_file(conf_file, conf_data) + if self.webserver == "apache": + error_msg = check_server_config() + if error_msg is not None: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + error_msg.replace("\n", '
                                    ') + '
                                    ' + + def remove_redirect(self, get, multiple=None) -> Tuple[bool, str]: + try: + site_name = get.sitename.strip() + redirect_name = get.redirectname.strip() + except AttributeError: + return False, "参数错误" + target_idx = None + have_other_redirect = False + target_conf = None + for i, conf in enumerate(self.config): + if conf["redirectname"] != redirect_name and conf["sitename"] == site_name: + have_other_redirect = True + if conf["redirectname"] == redirect_name and conf["sitename"] == site_name: + target_idx = i + target_conf = conf + + if target_idx is None: + return False, '没有指定的配置' + + r_md5_name = self._calc_redirect_name_md5(target_conf["redirectname"]) + ng_conf_file = "%s/vhost/nginx/redirect/%s/%s_%s.conf" % ( + self.panel_path, site_name, r_md5_name, site_name) + if os.path.exists(ng_conf_file): + os.remove(ng_conf_file) + + ap_conf_file = "%s/vhost/nginx/apache/%s/%s_%s.conf" % ( + self.panel_path, site_name, r_md5_name, site_name) + if os.path.exists(ap_conf_file): + os.remove(ap_conf_file) + + if not have_other_redirect: + self._un_set_apache_redirect_include(target_conf) + self._un_set_nginx_redirect_include(target_conf) + + del self.config[target_idx] + self.save_config() + if not multiple: + service_reload() + + return True, '删除成功' + + def mutil_remove_redirect(self, get): + try: + redirect_names = json.loads(get.redirectnames.strip()) + site_name = get.sitename.strip() + except (AttributeError, json.JSONDecodeError, TypeError): + return False, "参数错误" + del_successfully = [] + del_failed = [] + get_obj = type(get)() + for redirect_name in redirect_names: + get_obj.redirectname = redirect_name + get_obj.sitename = site_name + try: + flag, msg = self.remove_redirect(get, multiple=1) + if flag: + del_failed[redirect_name] = msg + continue + del_successfully.append(redirect_name) + except: + del_failed.append(redirect_name) + + service_reload() + if not del_failed: + return True, '删除重定向【{}】成功'.format(','.join(del_successfully)) + else: + return True, '重定向【{}】删除成功,【{}】删除失败'.format( + ','.join(del_successfully), ','.join(del_failed) + ) + + def get_redirect_list(self, get) -> Tuple[bool, Union[str, List[Dict[str, Any]]]]: + try: + error_page = None + site_name = get.sitename.strip() + if "errorpage" in get: + error_page = int(get.errorpage) + except (AttributeError, ValueError, TypeError): + return False, "参数错误" + redirect_list = [] + web_server = self.webserver + if self.webserver == 'openlitespeed': + web_server = 'apache' + for conf in self.config: + if conf["sitename"] != site_name: + continue + if error_page is not None and error_page != int(conf['errorpage']): + continue + if 'errorpage' in conf and conf['errorpage'] in [1, '1']: + conf['redirectdomain'] = ['404页面'] + + md5_name = self._calc_redirect_name_md5(conf['redirectname']) + conf["redirect_conf_file"] = "%s/vhost/%s/redirect/%s/%s_%s.conf" % ( + self.panel_path, web_server, site_name, md5_name, site_name) + conf["type"] = 1 if os.path.isfile(conf["redirect_conf_file"]) else 0 + redirect_list.append(conf) + return True, redirect_list + + def remove_site_redirect_info(self, site_name): + for i in range(len(self.config) - 1, -1, -1): + if self.config[i]["sitename"] == site_name: + del self.config[i] + self.save_config() + + m_path = self.panel_path + '/vhost/nginx/redirect/' + site_name + if os.path.exists(m_path): + shutil.rmtree(m_path) + + m_path = self.panel_path + '/vhost/apache/redirect/' + site_name + if os.path.exists(m_path): + shutil.rmtree(m_path) + + +class Redirect(RealRedirect): + + def __init__(self, config_prefix: str = ""): + super().__init__(config_prefix) + self.config_prefix = config_prefix + + def remove_redirect_by_project_name(self, project_name): + return self.remove_site_redirect_info(project_name) + + def create_project_redirect(self, get): + flag, msg = self.create_redirect(get) + return json_response(status=flag, msg=msg) + + def modify_project_redirect(self, get): + flag, msg = self.modify_redirect(get) + return json_response(status=flag, msg=msg) + + def remove_project_redirect(self, get): + flag, msg = self.remove_redirect(get) + return json_response(status=flag, msg=msg) + + def mutil_remove_project_redirect(self, get): + flag, msg = self.mutil_remove_redirect(get) + return json_response(status=flag, msg=msg) + + def get_project_redirect_list(self, get): + flag, data = self.get_redirect_list(get) + if not flag: + return json_response(status=flag, msg=data) + else: + return json_response(status=flag, data=data) diff --git a/mod/base/web_conf/referer.py b/mod/base/web_conf/referer.py new file mode 100644 index 00000000..28a5931f --- /dev/null +++ b/mod/base/web_conf/referer.py @@ -0,0 +1,363 @@ +import os +import re +import json +from dataclasses import dataclass +from typing import Tuple, Optional, Union, Dict +from .util import webserver, check_server_config, DB, \ + write_file, read_file, GET_CLASS, service_reload, pre_re_key +from mod.base import json_response + + +@dataclass +class _RefererConf: + name: str + fix: str + domains: str + status: str + return_rule: str + http_status: str + + def __str__(self): + return '{"name"="%s","fix"="%s","domains"="%s","status"="%s","http_status"="%s","return_rule"="%s"}' % ( + self.name, self.fix, self.domains, self.status, self.http_status, self.return_rule + ) + + +class RealReferer: + _referer_conf_dir = '/www/server/panel/vhost/config' # 防盗链配置 + _ng_referer_conf_format = r''' #SECURITY-START 防盗链配置 + location ~ .*\.(%s)$ { + expires 30d; + access_log /dev/null; + valid_referers %s; + if ($invalid_referer){ + %s; + } + } + #SECURITY-END''' + + def __init__(self, config_prefix: str): + if not os.path.isdir(self._referer_conf_dir): + os.makedirs(self._referer_conf_dir) + self.config_prefix: str = config_prefix + self._webserver = None + + @property + def webserver(self) -> str: + if self._webserver is not None: + return self._webserver + self._webserver = webserver() + return self._webserver + + def get_config(self, site_name: str) -> Optional[_RefererConf]: + try: + config = json.loads(read_file("{}/{}{}_door_chain.json".format(self._referer_conf_dir, self.config_prefix, site_name))) + except (json.JSONDecodeError, TypeError, ValueError): + config = None + if isinstance(config, dict): + return _RefererConf(**config) + return None + + def save_config(self, site_name: str, data: Union[dict, str, _RefererConf]) -> bool: + if isinstance(data, dict): + c = json.dumps(data) + elif isinstance(data, _RefererConf): + c = json.dumps(str(data)) + else: + c = data + + file_path = "{}/{}{}_door_chain.json".format(self._referer_conf_dir, self.config_prefix, site_name) + return write_file(file_path, c) + + # 检测参数,如果正确则返回 配置数据类型的值,否则返回错误信息 + @staticmethod + def check_args(get: Union[Dict, GET_CLASS]) -> Union[_RefererConf, str]: + res = {} + if isinstance(get, GET_CLASS): + try: + res["status"] = "true" if not hasattr(get, "status") else get.status.strip() + res["http_status"] = "false" if not hasattr(get, "http_status") else get.http_status.strip() + res["name"] = get.name.strip() + res["fix"] = get.fix.strip() + res["domains"] = get.domains.strip() + res["return_rule"] = get.return_rule.strip() + except AttributeError: + return "参数错误" + else: + try: + res["status"] = "true" if "status" not in get else get["status"].strip() + res["http_status"] = "false" if "http_status" not in get else get["http_status"].strip() + res["name"] = get["name"].strip() + res["fix"] = get["fix"].strip() + res["domains"] = get["domains"].strip() + res["return_rule"] = get["return_rule"].strip() + except KeyError: + return "参数错误" + + rconf = _RefererConf(**res) + if rconf.status not in ("true", "false") and rconf.return_rule not in ("true", "false"): + return "状态参数只能使用【true,false】" + if rconf.return_rule not in ('404', '403', '200', '301', '302', '401') and rconf.return_rule[0] != "/": + return "响应资源应使用URI路径或HTTP状态码,如:/test.png 或 404" + if len(rconf.domains) < 3: + return "防盗链域名不能为空" + if len(rconf.fix) < 2: + return 'URL后缀不能为空!' + return rconf + + def set_referer_security(self, rc: _RefererConf) -> Tuple[bool, str]: + error_msg = self._set_nginx_referer_security(rc) + if error_msg and self.webserver == "nginx": + return False, error_msg + error_msg = self._set_apache_referer_security(rc) + if error_msg and self.webserver == "apache": + return False, error_msg + service_reload() + self.save_config(rc.name, rc) + return True, "设置成功" + + def _set_nginx_referer_security(self, rc: _RefererConf) -> Optional[str]: + ng_file = '/www/server/panel/vhost/nginx/{}{}.conf'.format(self.config_prefix, rc.name) + ng_conf = read_file(ng_file) + if not isinstance(ng_conf, str): + return "nginx配置文件丢失,无法设置" + start_idx, end_idx = self._get_nginx_referer_security_idx(ng_conf) + if rc.status == "true": + if rc.return_rule[0] == "/": + return_rule = "rewrite /.* {} break".format(rc.return_rule) + else: + return_rule = 'return {}'.format(rc.return_rule) + + valid_args_list = [] + if rc.http_status == "true": + valid_args_list.extend(("none", "blocked")) + valid_args_list.extend(map(lambda x: x.strip(), rc.domains.split(","))) + valid_args = " ".join(valid_args_list) + + location_args = "|".join(map(lambda x: pre_re_key(x.strip()), rc.fix.split(","))) + if start_idx is not None: + new_conf = ng_conf[:start_idx] + "\n" + ( + self._ng_referer_conf_format % (location_args, valid_args, return_rule) + ) + "\n" + ng_conf[end_idx:] + else: + rep_redirect_include = re.compile(r"\sinclude +.*/redirect/.*\*\.conf;", re.M) + redirect_include_res = rep_redirect_include.search(ng_conf) + if redirect_include_res: + new_conf = ng_conf[:redirect_include_res.end()] + "\n" + ( + self._ng_referer_conf_format % (location_args, valid_args, return_rule) + ) + ng_conf[redirect_include_res.end():] + else: + if "#SSL-END" not in ng_conf: + return "添加配置失败,无法定位SSL相关配置的位置" + + new_conf = ng_conf.replace("#SSL-END", "#SSL-END\n" + self._ng_referer_conf_format % ( + location_args, valid_args, return_rule)) + + else: + if start_idx is None: + return + new_conf = ng_conf[:start_idx] + "\n" + ng_conf[end_idx:] + + write_file(ng_file, new_conf) + if self.webserver == "nginx" and check_server_config() is not None: + write_file(ng_file, ng_conf) + return "配置失败" + + @staticmethod + def _get_nginx_referer_security_idx(ng_conf: str) -> Tuple[Optional[int], Optional[int]]: + rep_security = re.compile( + r"(\s*#\s*SECURITY-START.*\n)?\s*location\s+~\s+\.\*\\\.\(.*(\|.*)?\)\$\s*\{[^}]*valid_referers" + ) + res = rep_security.search(ng_conf) + if res is None: + return None, None + + start_idx = res.start() + s_idx = start_idx + ng_conf[start_idx:].find("{") + 1 # 起始位置 + l_n = 1 + max_idx = len(ng_conf) + while l_n > 0: + next_l = ng_conf[s_idx:].find("{") + next_r = ng_conf[s_idx:].find("}") # 可能存在报错 + if next_r == -1: + return None, None + if next_l == -1: + next_l = max_idx + + if next_l < next_r: + l_n += 1 + else: + l_n -= 1 + s_idx += min(next_l, next_r) + 1 + + rep_comment = re.search(r"^\s*#\s*SECURITY-END[^\n]*\n", ng_conf[s_idx:]) + if rep_comment is not None: + end_idx = s_idx + rep_comment.end() + else: + end_idx = s_idx + + return start_idx, end_idx + + @staticmethod + def _build_apache_referer_security_conf(rc: _RefererConf) -> str: + r_conf_list = ["#SECURITY-START 防盗链配置"] + cond_format = " RewriteCond %{{HTTP_REFERER}} !{} [NC]" + if rc.http_status == "false": + r_conf_list.append(cond_format.format("^$")) + + r_conf_list.extend(map(lambda x: cond_format.format(x.strip()), rc.domains.split(","))) + + rule_format = " RewriteRule .({}) {} " + if rc.return_rule[0] == "/": + r_conf_list.append(rule_format.format( + "|".join(map(lambda x: x.strip(), rc.fix.split(","))), + rc.return_rule + )) + else: + r_conf_list.append(rule_format.format( + "|".join(map(lambda x: x.strip(), rc.fix.split(","))), + "/{s}.html [R={s},NC,L]".format(s=rc.return_rule) + )) + + r_conf_list.append(" #SECURITY-END") + + return "\n".join(r_conf_list) + + # 根据配置正则确定位置 并将配置文件添加进去 use_start 参数指定添加的前后 + def _add_apache_referer_security_by_rep_idx(self, + rep: re.Pattern, + use_start: bool, + ap_conf, ap_file, r_conf) -> bool: + tmp_conf_list = [] + last_idx = 0 + for tmp in rep.finditer(ap_conf): + tmp_conf_list.append(ap_conf[last_idx:tmp.start()]) + if use_start: + tmp_conf_list.append("\n" + r_conf + "\n") + tmp_conf_list.append(tmp.group()) + else: + tmp_conf_list.append(tmp.group()) + tmp_conf_list.append("\n" + r_conf + "\n") + last_idx = tmp.end() + if last_idx == 0: + return False + + tmp_conf_list.append(ap_conf[last_idx:]) + _conf = "".join(tmp_conf_list) + write_file(ap_file, _conf) + if self.webserver == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return False + return True + + def _set_apache_referer_security(self, rc: _RefererConf) -> Optional[str]: + ap_file = '/www/server/panel/vhost/apache/{}{}.conf'.format(self.config_prefix, rc.name) + ap_conf = read_file(ap_file) + if not isinstance(ap_conf, str): + return "nginx配置文件丢失,无法设置" + rep_security = re.compile(r"#\s*SECURITY-START(.|\n)#SECURITY-END.*\n") + res = rep_security.search(ap_conf) + if rc.status == "true": + r_conf = self._build_apache_referer_security_conf(rc) + if res is not None: + new_conf_list = [] + _idx = 0 + for tmp_res in rep_security.finditer(ap_conf): + new_conf_list.append(ap_conf[_idx:tmp_res.start()]) + new_conf_list.append("\n" + r_conf + "\n") + _idx = tmp_res.end() + new_conf_list.append(ap_conf[_idx:]) + new_conf = "".join(new_conf_list) + write_file(ap_file, new_conf) + if self.webserver == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return "配置修改失败" + + rep_redirect_include = re.compile(r"IncludeOptional +.*/redirect/.*\*\.conf.*\n", re.M) + rep_custom_log = re.compile(r"CustomLog .*\n") + rep_deny_files = re.compile(r"\n\s*#DENY FILES") + if self._add_apache_referer_security_by_rep_idx(rep_redirect_include, False, ap_conf, ap_file, r_conf): + return + if self._add_apache_referer_security_by_rep_idx(rep_custom_log, False, ap_conf, ap_file, r_conf): + return + if self._add_apache_referer_security_by_rep_idx(rep_deny_files, True, ap_conf, ap_file, r_conf): + return + return "设置添加失败" + + else: + if res is None: + return + + new_conf_list = [] + _idx = 0 + for tmp_res in rep_security.finditer(ap_conf): + new_conf_list.append(ap_conf[_idx:tmp_res.start()]) + _idx = tmp_res.end() + new_conf_list.append(ap_conf[_idx:]) + new_conf = "".join(new_conf_list) + write_file(ap_file, new_conf) + if self.webserver == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return "配置修改失败" + + def get_referer_security(self, site_name) -> Optional[dict]: + r = self.get_config(site_name) + if r is None: + return None + return json.loads(str(r)) + + def remove_site_referer_info(self, site_name): + file_path = "{}/{}{}_door_chain.json".format(self._referer_conf_dir, self.config_prefix, site_name) + if os.path.exists(file_path): + os.remove(file_path) + + # 从配置文件中获取referer配置信息 + # 暂时不实现,意义不大 + def _get_referer_security_by_conf(self, site_name): + if self.webserver == "nginx": + self._get_nginx_referer_security() + else: + self._get_apache_referer_security() + + +class Referer: + + def __init__(self, config_prefix: str): + self.config_prefix: str = config_prefix + self._r = RealReferer(self.config_prefix) + + def get_referer_security(self, get): + try: + site_name = get.site_name.strip() + except AttributeError: + return json_response(status=False, msg="参数错误") + + data = self._r.get_referer_security(site_name) + if data is None: + default_conf = { + "name": site_name, + "fix": "jpg,jpeg,gif,png,js,css", + "domains": "", + "status": "false", + "return_rule": "404", + "http_status": "false", + } + site_info = DB("sites").where("name=?", (site_name,)).field('id').find() + if not isinstance(site_info, dict): + return json_response(status=False, msg="站点查询错误") + domains_info = DB("domain").where("pid=?", (site_info["id"],)).field('name').select() + if not isinstance(domains_info, list): + return json_response(status=False, msg="站点查询错误") + + default_conf["domains"] = ",".join(map(lambda x: x["name"], domains_info)) + return json_response(status=True, data=default_conf) + + return json_response(status=True, data=data) + + def set_referer_security(self, get): + r = self._r.check_args(get) + if isinstance(r, str): + return json_response(status=False, msg=r) + + flag, msg = self._r.set_referer_security(r) + return json_response(status=flag, msg=msg) diff --git a/mod/base/web_conf/ssl.py b/mod/base/web_conf/ssl.py new file mode 100644 index 00000000..d33faf0b --- /dev/null +++ b/mod/base/web_conf/ssl.py @@ -0,0 +1,1327 @@ +import json +import os +import sys +import shutil +import time +# import OpenSSL +import re +from hashlib import md5 +from datetime import datetime, timedelta +from typing import Optional, Tuple, List, Dict, Union, Callable + +from mod.base import json_response +from .util import webserver, check_server_config, write_file, read_file, GET_CLASS, service_reload + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") +import public +import db +from panelAes import AesCryptPy3 + + +SSL_SAVE_PATH = "{}/vhost/ssl_saved".format(public.get_panel_path()) + + +class _SSLDatabase: + + def __init__(self): + db_path = "{}/data/db".format(public.get_panel_path()) + if not os.path.exists(db_path): + os.makedirs(db_path, 0o600) + self.db_file = '{}/data/db/ssl_data.db'.format(public.get_panel_path()) + if not os.path.exists(self.db_file): + self.init_db() + if not os.path.exists(SSL_SAVE_PATH): + os.makedirs(SSL_SAVE_PATH, 0o600) + + def init_db(self): + tmp_db = db.Sql() + setattr(tmp_db, "_Sql__DB_FILE", self.db_file) + + create_sql_str = ( + "CREATE TABLE IF NOT EXISTS 'ssl_info' (" + "'id' INTEGER PRIMARY KEY AUTOINCREMENT, " + "'hash' TEXT NOT NULL UNIQUE, " + "'path' TEXT NOT NULL, " + "'dns' TEXT NOT NULL, " + "'subject' TEXT NOT NULL, " + "'info' TEXT NOT NULL DEFAULT '', " + "'cloud_id' INTEGER NOT NULL DEFAULT -1, " + "'not_after' TEXT NOT NULL, " + "'use_for_panel' INTEGER NOT NULL DEFAULT 0, " + "'use_for_site' TEXT NOT NULL DEFAULT '[]', " + "'auth_info' TEXT NOT NULL DEFAULT '{}', " + "'create_time' INTEGER NOT NULL DEFAULT (strftime('%s'))" + ");" + ) + res = tmp_db.execute(create_sql_str) + if isinstance(res, str) and res.startswith("error"): + public.WriteLog("SSL管理", "建表ssl_info失败") + return + + index_sql_str = "CREATE INDEX IF NOT EXISTS 'hash_index' ON 'ssl_info' ('hash');" + + res = tmp_db.execute(index_sql_str) + if isinstance(res, str) and res.startswith("error"): + public.WriteLog("SSL管理", "为ssl_info建立索引hash_index失败") + return + tmp_db.close() + + def connection(self): + tmp_db = db.Sql() + setattr(tmp_db, "_Sql__DB_FILE", self.db_file) + tmp_db.table("ssl_info") + return tmp_db + + +ssl_db = _SSLDatabase() + + +class _LocalSSLInfoTool: + + def __init__(self): + self._letsencrypt = self.get_letsencrypt_conf() + + @staticmethod + def get_letsencrypt_conf(): + conf_file = "{}/config/letsencrypt_v2.json".format(public.get_panel_path()) + if not os.path.exists(conf_file): + conf_file = "{}/config/letsencrypt.json".format(public.get_panel_path()) + if not os.path.exists(conf_file): + return None + tmp_config = public.readFile(conf_file) + try: + orders = json.loads(tmp_config)["orders"] + except (json.JSONDecodeError, KeyError): + return None + return orders + + def get_auth(self, domains): + if self._letsencrypt is None: + return None + + for _, data in self._letsencrypt.items(): + if 'save_path' not in data: + continue + for d in data['domains']: + if d in domains: + return { + "auth_type": data.get('auth_type'), + "auth_to": data.get('auth_to') + } + + +class RealSSLManger: + _REFRESH_TIP = "{}/data/ssl_cloud_refresh.tip".format(public.get_panel_path()) + _OTHER_DATA_NAME = ("use_for_panel", "use_for_site",) + + def __init__(self, conf_prefix=""): + self._local_ssl_info_tool = None + self._vhost_path = "/www/server/panel/vhost" + self.conf_prefix = conf_prefix + self._tls_v3 = None + self._is_nginx_http3 = None + + # 与letsencrypt对接 + @property + def local_tool(self): + if self._local_ssl_info_tool is None: + self._local_ssl_info_tool = _LocalSSLInfoTool() + return self._local_ssl_info_tool + return self._local_ssl_info_tool + + # 用于部署 + @classmethod + def get_cert_for_deploy(cls, ssl_data: dict) -> Union[Dict, str]: + data = { + 'privkey': public.readFile(ssl_data["path"] + '/privkey.pem'), + 'fullchain': public.readFile(ssl_data["path"] + '/fullchain.pem') + } + if not isinstance(data["privkey"], str) or not isinstance(data["fullchain"], str): + return '证书读取错误!' + return data + + # 是否刷新 + @classmethod + def need_refresh(cls): + now = int(time.time()) + if not os.path.isfile(cls._REFRESH_TIP): + public.writeFile(cls._REFRESH_TIP, str(now)) + return True + last_time = int(public.readFile(cls._REFRESH_TIP)) + if last_time + 60 * 60 * 4 < now: + public.writeFile(cls._REFRESH_TIP, str(now)) + return True + return False + + # 获取hash指纹 + @staticmethod + def ssl_hash(cert_filename: str = None, certificate: str = None, ignore_errors: bool = False) -> Optional[str]: + if cert_filename is not None and os.path.isfile(cert_filename): + certificate = public.readFile(cert_filename) + + if not isinstance(certificate, str) or not certificate.startswith("-----BEGIN"): + if ignore_errors: + return None + raise ValueError("证书格式错误") + + md5_obj = md5() + md5_obj.update(certificate.encode("utf-8")) + return md5_obj.hexdigest() + + @staticmethod + def strf_date(sdate): + return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S')) + + # 获取证书信息 + @classmethod + def get_cert_info(cls, cert_filename: str = None, certificate: str = None): + if cert_filename is not None and os.path.isfile(cert_filename): + certificate = public.readFile(cert_filename) + + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + import ssl_info + return ssl_info.ssl_info().load_ssl_info_by_data(certificate) + + # try: + # result = { + # "issuer": '', + # "dns": [], + # } + # x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, certificate.encode("utf-8")) + # # 取产品名称 + # 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'] = cls.strf_date(x509.get_notAfter().decode("utf-8")[:-1]) + # # 取申请时间 + # result['notBefore'] = cls.strf_date(x509.get_notBefore().decode("utf-8")[:-1]) + # # 取可选名称 + # 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 + # if 'subject' in result: + # result['dns'].append(result['subject']) + # else: + # result['subject'] = result['dns'][0] + # return result + # except: + # return None + + # 通过文件名称检查并保存 + def save_by_file(self, cert_filename, private_key_filename, cloud_id=None, other_data: Optional[Dict] = None): + if not os.path.isfile(cert_filename) or not os.path.isfile(private_key_filename): + raise ValueError("不存在的证书") + + certificate = public.readFile(cert_filename) + private_key = public.readFile(private_key_filename) + if not isinstance(certificate, str) or not isinstance(private_key, str): + raise ValueError("证书格式错误") + return self.save_by_data(certificate, private_key, cloud_id=cloud_id) + + # 通过证书内容检查并保存 + def save_by_data(self, certificate: str, + private_key: str, + cloud_id: Optional[int] = None, + other_data: Optional[Dict] = None) -> Dict: + + if not certificate.startswith("-----BEGIN") or not private_key.startswith("-----BEGIN"): + raise ValueError("证书格式检查错误") + + if cloud_id is None: + cloud_id = -1 + + hash_data = self.ssl_hash(certificate=certificate) + db_data = self.get_ssl_info_by_hash(hash_data) + if db_data is not None: # 已经保存过的 + # 检查 cloud_id 与 保存的 cloud_id 不同时,更新cloud_id + if db_data['cloud_id'] != cloud_id and cloud_id != -1: + ssl_db.connection().where("id = ?", (db_data["id"],)).update({"cloud_id": cloud_id}) + db_data['cloud_id'] = cloud_id + db_data["dns"] = json.loads(db_data["dns"]) + db_data["info"] = json.loads(db_data["info"]) + db_data["auth_info"] = json.loads(db_data["auth_info"]) + db_data["use_for_site"] = json.loads(db_data["use_for_site"]) + return db_data + info = self.get_cert_info(certificate=certificate) + if info is None: + raise ValueError("证书信息解析错误") + + auth_info = self.local_tool.get_auth(info['dns']) + if auth_info is None: + auth_info = {} + + pdata = { + "hash": hash_data, + "path": "{}/{}".format(SSL_SAVE_PATH, hash_data), + "dns": json.dumps(info['dns']), + "subject": info['subject'], + "info": json.dumps(info), + "cloud_id": cloud_id, + "not_after": info["notAfter"], + "auth_info": json.dumps(auth_info) + } + + if other_data: + for key, other_data in other_data.items(): + if key in self._OTHER_DATA_NAME: + pdata[key] = other_data + + res_id = ssl_db.connection().insert(pdata) + if isinstance(res_id, str) and res_id.startswith("error"): + raise ValueError("数据库写入错误:" + res_id) + + pdata["id"] = res_id + if not os.path.exists(pdata["path"]): + os.makedirs(pdata["path"], 0o600) + + public.writeFile("{}/privkey.pem".format(pdata["path"]), private_key) + public.writeFile("{}/fullchain.pem".format(pdata["path"]), certificate) + public.writeFile("{}/info.json".format(pdata["path"]), pdata["info"]) + + pdata["info"] = info + return pdata + + # 通过hash指纹获取ssl信息 + @staticmethod + def get_ssl_info_by_hash(hash_data: str) -> Optional[dict]: + data = ssl_db.connection().where("hash = ?", (hash_data,)).find() + if isinstance(data, str): + raise ValueError("数据库查询错误:" + data) + if len(data) == 0: + return None + return data + + @staticmethod + def _get_cbc_key_and_iv(with_uer_info=True): + uer_info_file = "{}/data/userInfo.json".format(public.get_panel_path()) + try: + user_info = json.loads(public.readFile(uer_info_file)) + uid = user_info["uid"] + except (json.JSONDecodeError, KeyError): + return None, None, None + + md5_obj = md5() + md5_obj.update(str(uid).encode('utf8')) + bytes_data = md5_obj.hexdigest() + + key = '' + iv = '' + for i in range(len(bytes_data)): + if i % 2 == 0: + iv += bytes_data[i] + else: + key += bytes_data[i] + + if with_uer_info: + return key, iv, user_info + + return key, iv, None + + def get_cert_list(self, + param: Optional[Tuple[str, List]] = None, + force_refresh: bool = False, + local_refresh: bool = False) -> List: + if self.need_refresh() or force_refresh: + self._refresh_ssl_info_by_cloud() + self._get_ssl_by_local_data() + elif local_refresh: + self._get_ssl_by_local_data() + + return self._get_cert_list(param) + + # 获取证书列表 + @classmethod + def _get_cert_list(cls, param: Optional[Tuple[str, List]]) -> List: + db_conn = ssl_db.connection() + if param is not None and len(param) == 2 and isinstance(param[0], str) and isinstance(param[1], (tuple, list)): + db_conn.where(param[0], param[1]) + res = db_conn.select() + if isinstance(res, str): + raise ValueError("数据库查询错误:" + res) + + for value in res: + value["dns"] = json.loads(value["dns"]) + value["info"] = json.loads(value["info"]) + value["auth_info"] = json.loads(value["auth_info"]) + value["use_for_site"] = json.loads(value["use_for_site"]) + value['endtime'] = int((datetime.strptime(value['not_after'], "%Y-%m-%d").timestamp() + - datetime.today().timestamp()) / (60 * 60 * 24)) + + res.sort(key=lambda x: x["not_after"], reverse=True) + + return res + + # 从云端收集证书 + def _refresh_ssl_info_by_cloud(self): + key, iv, user_info = self._get_cbc_key_and_iv(with_uer_info=True) + if key is None or iv is None: + raise ValueError('面板未登录,无法链接云端!') + + AES = AesCryptPy3(key, "CBC", iv, char_set="utf8") + + # 对接云端 + url = "https://www.bt.cn/api/Cert_cloud_deploy/get_cert_list" + try: + res_text = public.httpPost(url, { + "uid": user_info["uid"], + "access_key": user_info["access_key"], + "serverid": user_info["serverid"], + }) + res_data = json.loads(res_text) + if res_data["status"] is False: + raise ValueError("获取云端数据失败") + + res_list = res_data['data'] + except: + raise ValueError("链接云端失败") + + change_set = set() + for data in res_list: + try: + privateKey = AES.aes_decrypt(data["privateKey"]) + certificate = AES.aes_decrypt(data["certificate"]) + cloud_id = data["id"] + change_data = self.save_by_data(certificate, privateKey, cloud_id) + change_set.add(change_data.get("id")) + except: + pass + + all_ids = ssl_db.connection().field("id").select() + for ssl_id in all_ids: + if ssl_id["id"] not in change_set: + ssl_db.connection().where("id = ?", (ssl_id["id"],)).update({"cloud_id": -1}) + + # 从本地收集证书 + def _get_ssl_by_local_data(self): # 从本地获取可用证书 + local_paths = ['/www/server/panel/vhost/cert', '/www/server/panel/vhost/ssl'] + for path in local_paths: + if not os.path.exists(path): + continue + for p_name in os.listdir(path): + pem_file = "{}/{}/fullchain.pem".format(path, p_name) + key_file = "{}/{}/privkey.pem".format(path, p_name) + if os.path.isfile(pem_file) and os.path.isfile(key_file): + try: + self.save_by_file(pem_file, key_file) + except: + pass + + panel_pem_file = "/www/server/panel/ssl/fullchain.pem" + panel_key_file = "/www/server/panel/ssl/privkey.pem" + if os.path.isfile(panel_pem_file) and os.path.isfile(panel_key_file): + try: + self.save_by_file(panel_pem_file, panel_key_file, other_data={"use_for_panel": 1}) + except: + pass + + # 从源储存位置删除 + @classmethod + def _remove_ssl_from_local(cls, ssh_hash: str): + local_path = '/www/server/panel/vhost/ssl' + if not os.path.exists(local_path): + return + + for p_name in os.listdir(local_path): + pem_file = "{}/{}/fullchain.pem".format(local_path, p_name) + + if os.path.isfile(pem_file): + hash_data = cls.ssl_hash(cert_filename=pem_file) + if hash_data == ssh_hash: + shutil.rmtree("{}/{}".format(local_path, p_name)) + + # 查询证书 + @staticmethod + def find_ssl_info(ssl_id=None, ssl_hash=None) -> Optional[dict]: + tmp_conn = ssl_db.connection() + if ssl_id is None and ssl_hash is None: + raise ValueError("没有参数信息") + if ssl_id is not None: + tmp_conn.where("id = ?", (ssl_id,)) + else: + tmp_conn.where("hash = ?", (ssl_hash,)) + + target = tmp_conn.find() + if isinstance(target, str) and target.startswith("error"): + raise ValueError("数据库查询错误:" + target) + + if not bool(target): + return None + + target["auth_info"] = json.loads(target["auth_info"]) + target["use_for_site"] = json.loads(target["use_for_site"]) + target["dns"] = json.loads(target["dns"]) + target["info"] = json.loads(target["info"]) + target['endtime'] = int((datetime.strptime(target['not_after'], "%Y-%m-%d").timestamp() + - datetime.today().timestamp()) / (60 * 60 * 24)) + return target + + @classmethod + def add_use_for_site(cls, site_id, ssl_id=None, ssl_hash=None) -> bool: + return cls.change_use_for_site(site_id, ssl_id, ssl_hash, is_add=True) + + @classmethod + def remove_use_for_site(cls, site_id, ssl_id=None, ssl_hash=None): + return cls.change_use_for_site(site_id, ssl_id, ssl_hash, is_add=False) + + @classmethod + def change_use_for_site(cls, site_id, ssl_id=None, ssl_hash=None, is_add=True): + target = cls.find_ssl_info(ssl_id=ssl_id, ssl_hash=ssl_hash) + if not target: + return False + try: + site_ids = json.loads(target["use_for_site"]) + except: + site_ids = [] + + if site_id in site_ids and is_add is False: + site_ids.remove(site_id) + up_res = ssl_db.connection().where("id = ?", (target["id"],)).update({"use_for_site": json.dumps(site_ids)}) + if isinstance(up_res, str) and up_res.startswith("error"): + raise ValueError("数据库查询错误:" + up_res) + + if site_id not in site_ids and is_add is True: + site_ids.append(site_id) + up_res = ssl_db.connection().where("id = ?", (target["id"],)).update({"use_for_site": json.dumps(site_ids)}) + if isinstance(up_res, str) and up_res.startswith("error"): + raise ValueError("数据库查询错误:" + up_res) + + return True + + def get_all_site_ssl(self): + all_sites = public.M("sites").select() + self.clear_use_for_site() + if isinstance(all_sites, str) and all_sites.startswith("error"): + raise ValueError(all_sites) + for site in all_sites: + prefix = "" if site["project_type"] == "PHP" else site["project_type"].lower() + "_" + tmp = self._get_site_ssl_info(site["name"], prefix=prefix) + if tmp is None: + continue + + hash_data = self.ssl_hash(cert_filename=tmp[0]) + self.add_use_for_site(site["id"], ssl_hash=hash_data) + + @staticmethod + def clear_use_for_site(): + ssl_db.connection().update({"use_for_site": "[]"}) + + @staticmethod + def _get_site_ssl_info(site_name, prefix='') -> Optional[Tuple[str, str]]: + path = os.path.join('/www/server/panel/vhost/cert/', site_name) + + pem_file = os.path.join(path, "fullchain.pem") + key_file = os.path.join(path, "privkey.pem") + if not os.path.isfile(pem_file) or not os.path.isfile(key_file): + path = os.path.join('/etc/letsencrypt/live/', site_name) + pem_file = os.path.join(path, "fullchain.pem") + key_file = os.path.join(path, "privkey.pem") + if not os.path.isfile(pem_file) or not os.path.isfile(key_file): + return None + + webserver = public.get_webserver() + if webserver == "nginx": + conf_file = "{}/vhost/nginx/{}{}.conf".format(public.get_panel_path(), prefix, site_name) + elif webserver == "apache": + conf_file = "{}/vhost/apache/{}{}.conf".format(public.get_panel_path(), prefix, site_name) + else: + conf_file = "{}/vhost/openlitespeed/detail/{}.conf".format(public.get_panel_path(), site_name) + + conf = public.readFile(conf_file) + if not conf: + return None + + if public.get_webserver() == 'nginx': + keyText = 'ssl_certificate' + elif public.get_webserver() == 'apache': + keyText = 'SSLCertificateFile' + else: + keyText = 'openlitespeed/detail/ssl' + + if conf.find(keyText) == -1: + return None + + return pem_file, key_file + + # 删除证书 + def remove_cert(self, ssl_id=None, ssl_hash=None, local: bool = False) -> Dict: + _, _, user_info = self._get_cbc_key_and_iv(with_uer_info=True) + if user_info is None: + raise ValueError('面板未登录,无法上传云端!') + + target = self.find_ssl_info(ssl_id=ssl_id, ssl_hash=ssl_hash) + if not target: + raise ValueError('没有指定的证书') + + if local: + shutil.rmtree(target["path"]) + self._remove_ssl_from_local(target["hash"]) # 把ssl下的也删除 + ssl_db.connection().delete(id=target["id"]) + + if target["cloud_id"] != -1: + url = "https://www.bt.cn/api/Cert_cloud_deploy/del_cert" + try: + res_text = public.httpPost(url, { + "cert_id": target["cloud_id"], + "hashVal": target["hash"], + "uid": user_info["uid"], + "access_key": user_info["access_key"], + "serverid": user_info["serverid"], + }) + res_data = json.loads(res_text) + if res_data["status"] is False: + return res_data + except: + if local: + raise ValueError("本地以删除成功, 链接云端失败, 无法删除云端数据") + raise ValueError("链接云端失败, 无法删除云端数据") + + if not local: + ssl_db.connection().where("id = ?", (target["id"],)).update({"cloud_id": -1}) + + return public.returnMsg(True, "删除成功") + + def mutil_remove_cert(self, ssl_id_list: List[int], local: bool = False): + result = [] + for i in ssl_id_list: + try: + ssl_id = int(i) + except: + result.append({"status": False, "msg": "id信息解析错误"}) + continue + res = self.remove_cert(ssl_id=ssl_id, local=local) + result.append(res) + return result + + # 下载证书 + def upload_cert(self, ssl_id=None, ssl_hash=None) -> Dict: + key, iv, user_info = self._get_cbc_key_and_iv() + if key is None or iv is None: + raise ValueError(False, '面板未登录,无法上传云端!') + + target = self.find_ssl_info(ssl_id=ssl_id, ssl_hash=ssl_hash) + if not target: + raise ValueError("没有指定的证书信息") + + data = { + 'privateKey': public.readFile(target["path"] + '/privkey.pem'), + 'certificate': public.readFile(target["path"] + '/fullchain.pem'), + "encryptWay": "AES-128-CBC", + "hashVal": target['hash'], + "uid": user_info["uid"], + "access_key": user_info["access_key"], + "serverid": user_info["serverid"], + } + if data["privateKey"] is False or data["certificate"] is False: + raise ValueError('证书文件读取错误') + + AES = AesCryptPy3(key, "CBC", iv, char_set="utf8") + data["privateKey"] = AES.aes_encrypt(data["privateKey"]) + data["certificate"] = AES.aes_encrypt(data["certificate"]) + # 对接云端 + url = "https://www.bt.cn/api/Cert_cloud_deploy/cloud_deploy" + + try: + res_text = public.httpPost(url, data) + res_data = json.loads(res_text) + if res_data["status"] is True: + cloud_id = int(res_data["data"].get("id")) + ssl_db.connection().where("id = ?", (target["id"],)).update({"cloud_id": cloud_id}) + + return res_data + else: + return res_data + except: + raise ValueError('链接云端失败') + + # ssl_hash 证书储存记录的唯一值 + def set_site_ssl_conf(self, site_name: str, ssl_data: dict, mutil=False) -> Optional[str]: + privkey = ssl_data["privkey"] + fullchain = ssl_data["fullchain"] + path = '/www/server/panel/vhost/cert/' + site_name + if not os.path.exists(path): + os.makedirs(path) + + csrpath = path + "/fullchain.pem" + keypath = path + "/privkey.pem" + + # 清理旧的证书链 + remove_list = [keypath, csrpath, path + "/certOrderId", path + "/README"] + for i in remove_list: + if os.path.exists(i): + os.remove(i) + public.ExecShell('rm -rf ' + path + '-00*') + public.ExecShell('rm -rf /etc/letsencrypt/archive/' + site_name) + public.ExecShell('rm -rf /etc/letsencrypt/archive/' + site_name + '-00*') + public.ExecShell('rm -f /etc/letsencrypt/renewal/' + site_name + '.conf') + public.ExecShell('rm -f /etc/letsencrypt/renewal/' + site_name + '-00*.conf') + + public.writeFile(keypath, privkey) + public.writeFile(csrpath, fullchain) + error_msg = self._set_ssl_conf_to_nginx(site_name, mutil) + if error_msg is not None and webserver() == "nginx": + return error_msg + error_msg = self._set_ssl_conf_to_apache(site_name, mutil) + if error_msg is not None and webserver() == "apache": + return error_msg + + # http3是否可用 + def is_nginx_http3(self): + """判断nginx是否可以使用http3""" + if getattr(self, "_is_nginx_http3", None) is None: + _is_nginx_http3 = public.ExecShell("nginx -V 2>&1| grep 'http_v3_module'")[0] != '' + setattr(self, "_is_nginx_http3", _is_nginx_http3) + return self._is_nginx_http3 + + # 在防火墙放行443 + @staticmethod + def open_firewall_443() -> None: + import firewalls + get = GET_CLASS() + get.port = '443' + get.ps = 'HTTPS' + firewalls.firewalls().AddAcceptPort(get) + + # 在nginx配置文件中设置ssl信息 + def _set_ssl_conf_to_nginx(self, site_name, mutil=False) -> Optional[str]: + # Nginx配置 + file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + ng_conf = read_file(file) + if not ng_conf: + return "配置文件丢失,配置失败" + + http3_header = "" + if self.is_nginx_http3(): + http3_header = '''\n add_header Alt-Svc 'quic=":443"; h3=":443"; h3-29=":443"; h3-27=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';''' + + if ng_conf.find('ssl_certificate') == -1: + sslStr = """#error_page 404/404.html; + ssl_certificate /www/server/panel/vhost/cert/%s/fullchain.pem; + ssl_certificate_key /www/server/panel/vhost/cert/%s/privkey.pem; + ssl_protocols %s; + ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + add_header Strict-Transport-Security "max-age=31536000";%s + error_page 497 https://$host$request_uri;""" % ( + site_name, site_name, self._get_tls_protocol(is_apache=False), http3_header + ) + + new_ng_conf = ng_conf.replace('#error_page 404/404.html;', sslStr) + # 添加端口 + from .domain_tool import NginxDomainTool + new_ng_conf = NginxDomainTool.nginx_add_port_by_config(new_ng_conf, "443", is_http3=self.is_nginx_http3()) + write_file(file, new_ng_conf) + if webserver() == "nginx" and check_server_config() is not None: + return "配置失败" + if webserver() == "nginx" and not mutil: + service_reload() + self.open_firewall_443() + + # 在apache配置文件中设置ssl信息 + def _set_ssl_conf_to_apache(self, site_name, mutil=False) -> Optional[str]: + ap_file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + ap_conf = read_file(ap_file) + if not ap_conf: + return "配置文件丢失,配置失败" + + tmp_template_res = re.search(r"", ap_conf) + if not tmp_template_res: + return "配置文件丢失,配置失败" + else: + tmp_template = tmp_template_res.group() + + rep_template_with_ports = re.compile(r"\d+)+\s*>(.|\n)*?") + target_vhost = None + for tmp in rep_template_with_ports.finditer(ap_conf): + if tmp.group("port") == "443": + target_vhost = tmp.group() + + if target_vhost and (target_vhost.find("SSLEngine On") or target_vhost.find("SSLCertificateFile")): + return + if not target_vhost: + rep_ports = re.compile(r"\d+)+\s*>") + target_vhost = rep_ports.sub("", tmp_template, 1) + + # 添加SSL配置 + ssl_conf = """ + #SSL + SSLEngine On + SSLCertificateFile /www/server/panel/vhost/cert/%s/fullchain.pem + SSLCertificateKeyFile /www/server/panel/vhost/cert/%s/privkey.pem + SSLCipherSuite EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5:ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL + SSLProtocol All -SSLv2 -SSLv3 %s + SSLHonorCipherOrder On + """ % (site_name, site_name, self._get_tls_protocol(is_apache=True)) + + rep_list = [ + (re.compile(r"#DENY FILES"), True), + (re.compile(r"CustomLog[^\n]*\n"), False), + ] + + # 使用正则匹配确定插入位置 + def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> Optional[str]: + tmp_res = tmp_rep.search(target_vhost) + if not tmp_res: + return None + if use_start: + new_conf = target_vhost[:tmp_res.start()] + ssl_conf + tmp_res.group() + target_vhost[tmp_res.end():] + else: + new_conf = target_vhost[:tmp_res.start()] + tmp_res.group() + ssl_conf + target_vhost[tmp_res.end():] + return new_conf + + ssl_vhost = None + for r, s in rep_list: + ssl_vhost = set_by_rep_idx(r, s) + if ssl_vhost is not None: + break + + if ssl_vhost is None: + return "无法定位SSL配置文件位置,配置失败" + + write_file(ap_file, ap_conf + "\n" + ssl_vhost) + # 添加端口 + from .domain_tool import ApacheDomainTool + ApacheDomainTool.apache_add_ports("443") + web_server = webserver() + if web_server == "apache" and check_server_config() is not None: + write_file(ap_file, ap_conf) + return "配置失败" + + if web_server == "apache" and not mutil: + service_reload() + self.open_firewall_443() + + def close_site_ssl_conf(self, site_name) -> Optional[str]: + error_msg = self._close_ssl_conf_to_nginx(site_name) + if error_msg is not None and webserver() == "nginx": + return error_msg + error_msg = self._close_ssl_conf_to_apache(site_name) + if error_msg is not None and webserver() == "apache": + return error_msg + service_reload() + return None + + def _close_ssl_conf_to_nginx(self, site_name) -> Optional[str]: + file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + ng_conf = read_file(file) + if not ng_conf: + return "配置文件丢失,配置失败" + rep_list = ( + re.compile("\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END"), # 关闭 强制https + re.compile(r"\s*ssl_(certificate|certificate_key|protocols|" + r"ciphers|prefer_server_ciphers|session_cache|session_timeout)[^;]*;"), # 关闭 强制https + re.compile(r"\s*add_header\s+(Strict-Transport-Security|Alt-Svc)[^;]*;"), # 关闭 https 请求头配置 + re.compile(r"\s*error_page\s+497\s+[^;]*;"), + re.compile(r"\s+listen\s+(\[::]:)?443.*;"), # 关闭端口监听 + ) + new_conf = ng_conf + for rep in rep_list: + new_conf = rep.sub("", new_conf) + + write_file(file, new_conf) + + def _close_ssl_conf_to_apache(self, site_name) -> Optional[str]: + file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + ap_conf = read_file(file) + if not ap_conf: + return "配置文件丢失,配置失败" + rep_list = ( + re.compile("\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END"), + re.compile(r"\n(.|\n)*"), + ) + new_conf = ap_conf + for rep in rep_list: + new_conf = rep.sub("", new_conf) + + write_file(file, new_conf) + + def _get_tls_protocol(self, is_apache=False): + """获取使用的协议 + @author baozi <202-04-18> + @param: + @return + """ + protocols = { + "TLSv1": False, + "TLSv1.1": True, + "TLSv1.2": True, + "TLSv1.3": False, + } + tls1_3 = self.get_tls13() + file_path = public.get_panel_path() + "/data/ssl_protocol.json" + if os.path.exists(file_path): + data = public.readFile(file_path) + if data is not False: + protocols = json.loads(data) + if protocols["TLSv1.3"] and tls1_3 == "": + protocols["TLSv1.3"] = False + if is_apache is False: + return " ".join([p for p, v in protocols.items() if v is True]) + else: + return " ".join(["-" + p for p, v in protocols.items() if v is False]) + else: + if tls1_3 != "": + protocols["TLSv1.3"] = True + if is_apache is False: + return " ".join([p for p, v in protocols.items() if v is True]) + else: + return " ".join(["-" + p for p, v in protocols.items() if v is False]) + + # 获取TLS1.3标记 + def get_tls13(self): + if self._tls_v3 is not None: + return self._tls_v3 + nginx_bin = '/www/server/nginx/sbin/nginx' + nginx_v = public.ExecShell(nginx_bin + ' -V 2>&1')[0] + nginx_v_re = re.search(r"nginx/(?P\d\.\d+).+OpenSSL\s+(?P\d\.\d+)", nginx_v) + if nginx_v_re: + ng_ver = nginx_v_re.group("ng_ver") + ssl_ver = nginx_v_re.group("ssl_ver") + if float(ng_ver) >= 1.15 and float(ssl_ver) >= 1.1: + self._tls_v3 = 'TLSv1.3' + else: + can_ng_ver = re.search(r'nginx/1\.(1[5-9]|2\d)', nginx_v) + openssl_v = public.ExecShell(nginx_bin + ' -V 2>&1|grep OpenSSL')[0].find('OpenSSL 1.1.') != -1 + if can_ng_ver and openssl_v: + self._tls_v3 = 'TLSv1.3' + + if self._tls_v3 is None: + self._tls_v3 = '' + return self._tls_v3 + + # HttpToHttps + def set_http_to_https(self, site_name: str): + # Nginx配置 + file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = read_file(file) + if conf: + if conf.find('ssl_certificate') == -1: + return public.returnMsg(False, '当前未开启SSL') + to_str = """#error_page 404/404.html; + #HTTP_TO_HTTPS_START + if ($server_port !~ 443){ + rewrite ^(/.*)$ https://$host$1 permanent; + } + #HTTP_TO_HTTPS_END +""" + conf = conf.replace('#error_page 404/404.html;', to_str) + write_file(file, conf) + + file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = public.readFile(file) + if conf: + to_str = ''' + #HTTP_TO_HTTPS_START + + RewriteEngine on + RewriteCond %{SERVER_PORT} !^443$ + RewriteRule (.*) https://%{SERVER_NAME}$1 [L,R=301] + + #HTTP_TO_HTTPS_END + SSLEngine On''' + conf = re.sub('SSLEngine On', to_str, conf, 1) + public.writeFile(file, conf) + + service_reload() + + # CloseToHttps + def close_to_https(self, site_name): + file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = public.readFile(file) + if conf: + rep_https = re.compile(r"(#HTTP_TO_HTTPS_START\s*)?if\s+\(\s*\$server_port\s+!~\s+443\s*\)" + r"[^{]*\{[^}]*}\s*(#HTTP_TO_HTTPS_END\s*)?") + new_conf = rep_https.sub('', conf) + write_file(file, new_conf) + + file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = public.readFile(file) + if conf: + rep_https = re.compile("\n\\s*#HTTP_TO_HTTPS_START(.|\n){1,300}#HTTP_TO_HTTPS_END") + new_conf = rep_https.sub('', conf) + write_file(file, new_conf) + + service_reload() + + # 是否有跳转到https + def is_to_https(self, site_name) -> bool: + file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + conf = public.readFile(file) + if conf: + if conf.find('HTTP_TO_HTTPS_START') != -1: + return True + if conf.find('$server_port !~ 443') != -1: + return True + return False + + def get_site_ssl_info(self, site_name: str) -> Optional[dict]: + try: + w_s = webserver() + if w_s == 'nginx': + conf_file = '{}/nginx/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + elif w_s == "apach": + conf_file = '{}/apache/{}{}.conf'.format(self._vhost_path, self.conf_prefix, site_name) + else: + return None + + if not os.path.exists(conf_file): + return None + + s_conf = public.readFile(conf_file) + if not s_conf: + return None + if w_s == "apach": + s_tmp = re.findall(r"SSLCertificateFile\s+(.+\.pem)", s_conf) + if not s_tmp: + return None + ssl_file = s_tmp[0] + else: + s_tmp = re.findall(r"ssl_certificate\s+(.+\.pem);", s_conf) + if not s_tmp: + return None + ssl_file = s_tmp[0] + + ssl_info = self.get_cert_info(cert_filename=ssl_file) + if not ssl_info: + return None + ssl_info['endtime'] = int( + int(time.mktime(time.strptime(ssl_info['notAfter'], "%Y-%m-%d")) - time.time()) / 86400) + return ssl_info + except: + return None + + +class SSLManager: + + def __init__(self, conf_prefix: str = ""): + self.conf_prefix = conf_prefix + + def set_site_ssl_conf(self, get): + ssl_id = None + ssl_hash = None + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + site_name = get.site_name.strip() + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "参数错误") + ssl_mgr = RealSSLManger(self.conf_prefix) + try: + info = ssl_mgr.find_ssl_info(ssl_id=ssl_id, ssl_hash=ssl_hash) + if not info: + return json_response(status=False, msg="未查询到证书信息") + ssl_data = ssl_mgr.get_cert_for_deploy(info) + if isinstance(ssl_data, str): + return json_response(status=False, msg=ssl_data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + + err_msg = ssl_mgr.set_site_ssl_conf(site_name=site_name, ssl_data=ssl_data) + if err_msg: + return json_response(status=False, msg=err_msg) + return json_response(status=True, msg="部署成功") + + def mutil_set_site_ssl_conf(self, get): + ssl_id = None + ssl_hash = None + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + site_names = json.loads(get.site_names.strip()) + except (ValueError, AttributeError, KeyError, json.JSONDecodeError): + return public.ReturnMsg(False, "参数错误") + ssl_mgr = RealSSLManger(self.conf_prefix) + try: + info = ssl_mgr.find_ssl_info(ssl_id=ssl_id, ssl_hash=ssl_hash) + if not info: + return json_response(status=False, msg="未查询到证书信息") + ssl_data = ssl_mgr.get_cert_for_deploy(info) + if isinstance(ssl_data, str): + return json_response(status=False, msg=ssl_data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + + result = { + "total": len(site_names), + "success": 0, + "failed": 0, + "success_list": [], + "failed_list": [], + "failed_msg": [] + } + for i in site_names: + err_msg = ssl_mgr.set_site_ssl_conf(site_name=i, ssl_data=ssl_data) + if err_msg: + result["failed"] += 1 + result["failed_list"].append(i) + result["failed_msg"].append(err_msg) + else: + result["success"] += 1 + result["success_list"].append(i) + + return json_response(status=True, data=result) + + def close_site_ssl_conf(self, get): + try: + site_name = get.site_name.strip() + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "参数错误") + + ssl_mgr = RealSSLManger(self.conf_prefix) + try: + err_msg = ssl_mgr.close_site_ssl_conf(site_name) + if err_msg: + return json_response(status=False, msg=err_msg) + return json_response(status=True, msg="关闭成功") + except Exception as e: + return json_response(status=False, msg=str(e)) + + def upload_cert_to_cloud(self, get): + ssl_id = None + ssl_hash = None + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "参数错误") + try: + data = RealSSLManger(self.conf_prefix).upload_cert(ssl_id, ssl_hash) + return json_response(status=True, data=data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + except Exception as e: + return json_response(status=False, msg="操作错误:" + str(e)) + + def remove_cloud_cert(self, get): + ssl_id = None + ssl_hash = None + local = False + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + + if "local" in get and get.local.strip() in ("1", 1, True, "true"): + local = True + + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "参数错误") + try: + data = RealSSLManger(self.conf_prefix).remove_cert(ssl_id, ssl_hash, local=local) + return json_response(status=data.get("status", True), msg=data.get("msg", ""), data=data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + except Exception as e: + return json_response(status=False, msg="操作错误:" + str(e)) + + def mutil_remove_cloud_cert(self, get): + local = False + try: + ssl_id_list = json.loads(get.ssl_id_list.strip()) + if "local" in get and get.local.strip() in ("1", 1, True, "true"): + local = True + + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "参数错误") + try: + data = RealSSLManger(self.conf_prefix).mutil_remove_cert(ssl_id_list, local=local) + return json_response(status=True, data=data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + except Exception as e: + return json_response(status=False, msg="操作错误:" + str(e)) + + # 未使用 + def refresh_cert_list(self, get=None): + try: + data = RealSSLManger(self.conf_prefix).get_cert_list(force_refresh=True) + return json_response(status=True, data=data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + except Exception as e: + return json_response(status=False, msg="操作错误:" + str(e)) + + def get_cert_info(self, get): + ssl_id = None + ssl_hash = None + try: + if "ssl_id" in get: + ssl_id = int(get.ssl_id) + if "ssl_hash" in get: + ssl_hash = get.ssl_hash.strip() + except (ValueError, AttributeError, KeyError): + return public.ReturnMsg(False, "参数错误") + try: + ssl_mager = RealSSLManger(self.conf_prefix) + target = ssl_mager.find_ssl_info(ssl_id, ssl_hash) + if target is None: + return json_response(status=False, msg="未获取到证书信息") + data = ssl_mager.get_cert_for_deploy(target) + if isinstance(data, dict): + target.update(data) + return json_response(status=True, data=target) + else: + return json_response(status=False, msg=data) + except ValueError as e: + return json_response(status=False, msg=str(e)) + except Exception as e: + return json_response(status=False, msg="操作错误:" + str(e)) + + def get_site_ssl_info(self, get): + try: + site_name = get.site_name.strip() + except (ValueError, AttributeError, KeyError): + return json_response(False, "参数错误") + ssl_info = RealSSLManger(self.conf_prefix).get_site_ssl_info(site_name) + if ssl_info is None: + return json_response(status=False, msg="未获取到证书信息") + else: + return json_response(status=True, data=ssl_info) + + def get_cert_list(self, get): + """ + search_limit 0 -> 所有证书 + search_limit 1 -> 没有过期的证书 + search_limit 2 -> 有效期小于等于15天的证书 但未过期 + search_limit 3 -> 过期的证书 + search_limit 4 -> 过期时间1年以上的证书 + """ + search_name = None + search_limit = 0 + force_refresh = False + + try: + if "search_name" in get: + search_name = get.search_name.strip() + if "search_limit" in get: + search_limit = int(get.search_limit.strip()) + if "force_refresh" in get and get.force_refresh.strip() in ("1", 1, "True", True): + force_refresh = True + + except (ValueError, AttributeError, KeyError): + return json_response(status=False, msg="参数错误") + + param = None + if search_name is not None: + param = ['subject LIKE ?', ["%{}%".format(search_name)]] + + now = datetime.now() + filter_func: Callable[[dict, ], bool] = lambda x: True + if search_limit == 1: + date = now.strftime("%Y-%m-%d") + filter_func: Callable[[dict, ], bool] = lambda x: x["not_after"] >= date + elif search_limit == 2: + date1 = now.strftime("%Y-%m-%d") + date2 = (now + timedelta(days=15)).strftime("%Y-%m-%d") + filter_func: Callable[[dict, ], bool] = lambda x: date1 <= x["not_after"] <= date2 + elif search_limit == 3: + date = now.strftime("%Y-%m-%d") + filter_func: Callable[[dict, ], bool] = lambda x: x["not_after"] < date + elif search_limit == 4: + date = (now + timedelta(days=366)).strftime("%Y-%m-%d") + filter_func: Callable[[dict, ], bool] = lambda x: x["not_after"] > date + try: + res_list = RealSSLManger(self.conf_prefix).get_cert_list(param=param, force_refresh=force_refresh) + res_list = list(filter(filter_func, res_list)) + res_list.sort(key=lambda x: x["not_after"]) + return json_response(status=True, data=res_list) + except ValueError as e: + return json_response(False, str(e)) + except Exception as e: + return json_response(False, "操作错误:" + str(e)) + + @staticmethod + def set_ssl_protocol(get): + """ 设置全局TLS版本 + @author baozi <202-04-18> + @param: + @return + """ + protocols = { + "TLSv1": False, + "TLSv1.1": False, + "TLSv1.2": False, + "TLSv1.3": False, + } + if "use_protocols" in get: + use_protocols = getattr(get, "use_protocols", []) + if isinstance(use_protocols, list): + for protocol in use_protocols: + if protocol in protocols: + protocols[protocol] = True + elif isinstance(use_protocols, str): + for protocol in use_protocols.split(","): + if protocol in protocols: + protocols[protocol] = True + else: + protocols["TLSv1.1"] = True + protocols["TLSv1.2"] = True + protocols["TLSv1.3"] = True + + else: + protocols["TLSv1.1"] = True + protocols["TLSv1.2"] = True + protocols["TLSv1.3"] = True + + public.print_log(protocols) + public.WriteFile(public.get_panel_path() + "/data/ssl_protocol.json", json.dumps(protocols)) + return public.returnMsg(True, 'SET_SUCCESS') + + @staticmethod + def get_ssl_protocol(get=None): + """ 获取全局TLS版本 + @author baozi <202-04-18> + @param: + @return + """ + protocols = { + "TLSv1": False, + "TLSv1.1": True, + "TLSv1.2": True, + "TLSv1.3": False, + } + file_path = public.get_panel_path() + "/data/ssl_protocol.json" + if os.path.exists(file_path): + data = public.readFile(file_path) + if data is not False: + protocols = json.loads(data) + return protocols + + return protocols diff --git a/mod/base/web_conf/util.py b/mod/base/web_conf/util.py new file mode 100644 index 00000000..0ebae060 --- /dev/null +++ b/mod/base/web_conf/util.py @@ -0,0 +1,200 @@ +import os +import sys +from typing import Optional, Tuple, Callable + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + + +def webserver() -> Optional[str]: + if os.path.exists('/www/server/apache/bin/apachectl'): + web_server = 'apache' + elif os.path.exists('/www/server/nginx/sbin/nginx'): + web_server = 'nginx' + elif os.path.exists('/usr/local/lsws/bin/lswsctrl'): + web_server = 'openlitespeed' + else: + web_server = None + return web_server + + +def check_server_config() -> Optional[str]: + w_s = webserver() + setup_path = "/www/server" + if w_s == 'nginx': + shell_str = ( + "ulimit -n 8192; " + "{setup_path}/nginx/sbin/nginx -t -c {setup_path}/nginx/conf/nginx.conf" + ).format(setup_path=setup_path) + result: Tuple[str, str] = public.ExecShell(shell_str) + searchStr = 'successful' + elif w_s == 'apache': + shell_str = ( + "ulimit -n 8192; " + "{setup_path}/apache/bin/apachectl -t" + ).format(setup_path=setup_path) + result: Tuple[str, str] = public.ExecShell(shell_str) + searchStr = 'Syntax OK' + else: + return None + if result[1].find(searchStr) == -1: + public.WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR', (result[1],)) + return result[1] + + +def read_file(filename, mode='r') -> Optional[str]: + """ + 读取文件内容 + @filename 文件名 + return string(bin) 若文件不存在,则返回None + """ + import os + if not os.path.exists(filename): + return None + fp = None + try: + fp = open(filename, mode=mode) + f_body = fp.read() + except: + return None + finally: + if fp and not fp.closed: + fp.close() + return f_body + + +def write_file(filename: str, s_body: str, mode='w+') -> bool: + """ + 写入文件内容 + @filename 文件名 + @s_body 欲写入的内容 + return bool 若文件不存在则尝试自动创建 + """ + try: + fp = open(filename, mode=mode) + fp.write(s_body) + fp.close() + return True + except: + try: + fp = open(filename, mode=mode, encoding="utf-8") + fp.write(s_body) + fp.close() + return True + except: + return False + + +def debug_api_warp(fn): + def inner(*args, **kwargs): + try: + return fn(*args, **kwargs) + except: + public.print_log(public.get_error_info()) + return { + + } + + return inner + + +# 重载Web服务配置 +def service_reload(): + setup_path = "/www/server" + if os.path.exists('{}/nginx/sbin/nginx'.format(setup_path)): + result = public.ExecShell('/etc/init.d/nginx reload') + if result[1].find('nginx.pid') != -1: + public.ExecShell('pkill -9 nginx && sleep 1') + public.ExecShell('/etc/init.d/nginx start') + elif os.path.exists('{}/apache/bin/apachectl'.format(setup_path)): + result = public.ExecShell('/etc/init.d/httpd reload') + else: + result = public.ExecShell('rm -f /tmp/lshttpd/*.sock* && /usr/local/lsws/bin/lswsctrl restart') + return result + + +# 防正则转译 +def pre_re_key(input_str: str) -> str: + re_char = ['$', '(', ')', '*', '+', '.', '[', ']', '{', '}', '?', '^', '|', '\\'] + res = [] + for i in input_str: + if i in re_char: + res.append("\\" + i) + else: + res.append(i) + return "".join(res) + + +def get_log_path() -> str: + log_path = public.readFile("{}/data/sites_log_path.pl".format(public.get_panel_path())) + if isinstance(log_path, str) and os.path.isdir(log_path): + return log_path + return public.GetConfigValue('logs_path') + + + +# 2024/4/18 上午9:44 域名编码转换 +def to_puny_code(domain): + try: + try: + import idna + except: + os.system("btpip install idna -I") + import idna + + import re + match = re.search(u"[^u\0000-u\001f]+", domain) + if not match: + return domain + try: + if domain.startswith("*."): + return "*." + idna.encode(domain[2:]).decode("utf8") + else: + return idna.encode(domain).decode("utf8") + except: + return domain + except: + return domain + + +# 2024/4/18 下午5:48 中文路径处理 +def to_puny_code_path(path): + if sys.version_info[0] == 2: path = path.encode('utf-8') + if os.path.exists(path): return path + import re + match = re.search(u"[\x80-\xff]+", path) + if not match: match = re.search(u"[\u4e00-\u9fa5]+", path) + if not match: return path + npath = '' + for ph in path.split('/'): + npath += '/' + to_puny_code(ph) + return npath.replace('//', '/') + + + +class _DB: + + def __call__(self, table: str): + import db + with db.Sql() as t: + t.table(table) + return t + + +DB = _DB() + +GET_CLASS = public.dict_obj + +listen_ipv6: Callable[[], bool] = public.listen_ipv6 + +ExecShell: Callable = public.ExecShell + + +def use_http2() -> bool: + versionStr = public.readFile('/www/server/nginx/version.pl') + if isinstance(versionStr, str): + if versionStr.find('1.8.1') == -1: + return True + return False diff --git a/mod/common/__init__.py b/mod/common/__init__.py new file mode 100644 index 00000000..fecc39d2 --- /dev/null +++ b/mod/common/__init__.py @@ -0,0 +1,8 @@ +from .limit_net import LimitNet +from .redirect import Redirect + + +__all__ = [ + "LimitNet", + "Redirect" +] diff --git a/mod/common/base.py b/mod/common/base.py new file mode 100644 index 00000000..bad1f6ee --- /dev/null +++ b/mod/common/base.py @@ -0,0 +1,47 @@ +from typing import Optional + + +class BaseProjectCommon: + setup_path = "/www/server/panel" + _allow_mod_name = { + "go", "java", "net", "nodejs", "other", "python", "proxy", + } + + def get_project_mod_type(self) -> Optional[str]: + _mod_name = self.__class__.__module__ + + # "projectModel/javaModel.py" 的格式 + if "/" in _mod_name: + _mod_name = _mod_name.replace("/", ".") + if _mod_name.endswith(".py"): + mod_name = _mod_name[:-3] + else: + mod_name = _mod_name + + # "projectModel.javaModel" 的格式 + if "." in mod_name: + mod_name = mod_name.rsplit(".", 1)[1] + + if mod_name.endswith("Model"): + return mod_name[:-5] + if mod_name in self._allow_mod_name: + return mod_name + return None + + @property + def config_prefix(self) -> Optional[str]: + if getattr(self, "_config_prefix_cache", None) is not None: + return getattr(self, "_config_prefix_cache") + p_name = self.get_project_mod_type() + if p_name == "nodejs": + p_name = "node" + + if isinstance(p_name, str): + p_name = p_name + "_" + + setattr(self, "_config_prefix_cache", p_name) + return p_name + + @config_prefix.setter + def config_prefix(self, prefix: str): + setattr(self, "_config_prefix_cache", prefix) diff --git a/mod/common/limit_net.py b/mod/common/limit_net.py new file mode 100644 index 00000000..f044654d --- /dev/null +++ b/mod/common/limit_net.py @@ -0,0 +1,239 @@ +import os +import re +from typing import Tuple + +import public +from .base import BaseProjectCommon + + +class LimitNet(BaseProjectCommon): + + def get_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + try: + site_id = int(get.site_id) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + if self.config_prefix is None: + return public.returnMsg(False, "不支持的网站类型") + + # 取配置文件 + site_name = public.M('sites').where("id=?", (site_id,)).getField('name') + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + conf = public.readFile(filename) + if not isinstance(conf, str): + return public.returnMsg(False, "配置文件读取错误") + + # 站点总并发 + data = { + 'perserver': 0, + 'perip': 0, + 'limit_rate': 0, + } + + rep_per_server = re.compile(r"(?P.*)limit_conn +perserver +(?P\d+) *; *", re.M) + tmp_res = rep_per_server.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['perserver'] = int(tmp_res.group("target")) + + # IP并发限制 + rep_per_ip = re.compile(r"(?P.*)limit_conn +perip +(?P\d+) *; *", re.M) + tmp_res = rep_per_ip.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['perip'] = int(tmp_res.group("target")) + + # 请求并发限制 + rep_limit_rate = re.compile(r"(?P.*)limit_rate +(?P\d+)\w+ *; *", re.M) + tmp_res = rep_limit_rate.search(conf) + if tmp_res is not None and tmp_res.group("prefix").find("#") == -1: # 有且不是注释 + data['limit_rate'] = int(tmp_res.group("target")) + + self._show_limit_net(data) + return data + + @staticmethod + def _show_limit_net(data): + values = [ + [300, 25, 512], + [200, 10, 1024], + [50, 3, 2048], + [500, 10, 2048], + [400, 15, 1024], + [60, 10, 512], + [150, 4, 1024], + ] + for i, c in enumerate(values): + if data["perserver"] == c[0] and data["perip"] == c[1] and data["limit_rate"] == c[2]: + data["value"] = i + 1 + break + else: + data["value"] = 0 + + @staticmethod + def _set_nginx_conf_limit() -> Tuple[bool, str]: + # 设置共享内存 + nginx_conf_file = "/www/server/nginx/conf/nginx.conf" + if not os.path.exists(nginx_conf_file): + return False, "nginx配置文件丢失" + nginx_conf = public.readFile(nginx_conf_file) + rep_perip = re.compile(r"\s+limit_conn_zone +\$binary_remote_addr +zone=perip:10m;", re.M) + rep_per_server = re.compile(r"\s+limit_conn_zone +\$server_name +zone=perserver:10m;", re.M) + perip_res = rep_perip.search(nginx_conf) + per_serve_res = rep_per_server.search(nginx_conf) + if perip_res and per_serve_res: + return True, "" + elif perip_res or per_serve_res: + tmp_res = perip_res or per_serve_res + new_conf = nginx_conf[:tmp_res.start()] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;" + ) + nginx_conf[tmp_res.end():] + else: + # 通过检查第一个server的位置 + rep_first_server = re.compile(r"http\s*\{(.*\n)*\s*server\s*\{") + tmp_res = rep_first_server.search(nginx_conf) + if tmp_res: + old_http_conf = tmp_res.group() + # 在第一个server项前添加 + server_idx = old_http_conf.rfind("server") + new_http_conf = old_http_conf[:server_idx] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n" + ) + old_http_conf[server_idx:] + new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1) + else: + # 在没有配置其他server项目时,通过检查include server项目检查 + # 通检查 include /www/server/panel/vhost/nginx/*.conf; 位置 + rep_include = re.compile(r"http\s*\{(.*\n)*\s*include +/www/server/panel/vhost/nginx/\*\.conf;") + tmp_res = rep_include.search(nginx_conf) + if not tmp_res: + return False, "全局配置缓存配置失败" + old_http_conf = tmp_res.group() + + include_idx = old_http_conf.rfind("include ") + new_http_conf = old_http_conf[:include_idx] + ( + "\n\t\tlimit_conn_zone $binary_remote_addr zone=perip:10m;" + "\n\t\tlimit_conn_zone $server_name zone=perserver:10m;\n" + ) + old_http_conf[include_idx:] + new_conf = rep_first_server.sub(new_http_conf, nginx_conf, 1) + + public.writeFile(nginx_conf_file, new_conf) + if public.checkWebConfig() is not True: # 检测失败,无法添加 + public.writeFile(nginx_conf_file, nginx_conf) + return False, "全局配置缓存配置失败" + return True, "" + + # 设置流量限制 + def set_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + try: + site_id = int(get.site_id) + per_server = int(get.perserver) + perip = int(get.perip) + limit_rate = int(get.limit_rate) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + if per_server < 1 or perip < 1 or limit_rate < 1: + return public.returnMsg(False, '并发限制,IP限制,流量限制必需大于0') + + # 取配置文件 + site_info = public.M('sites').where("id=?", (site_id,)).find() + if not isinstance(site_info, dict): + return public.returnMsg(False, "站点信息查询错误") + else: + site_name = site_info["name"] + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + site_conf: str = public.readFile(filename) + if not isinstance(site_conf, str): + return public.returnMsg(False, "配置文件读取错误") + + flag, msg = self._set_nginx_conf_limit() + if not flag: + return public.returnMsg(False, msg) + + per_server_str = ' limit_conn perserver {};'.format(per_server) + perip_str = ' limit_conn perip {};'.format(perip) + limit_rate_str = ' limit_rate {}k;'.format(limit_rate) + + # 请求并发限制 + new_conf = site_conf + ssl_end_res = re.search(r"#error_page 404/404.html;[^\n]*\n", new_conf) + if ssl_end_res is None: + return public.returnMsg(False, "未定位到SSL的相关配置,添加失败") + ssl_end_idx = ssl_end_res.end() + rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *", re.M) + tmp_res = rep_limit_rate.search(new_conf) + if tmp_res is not None : + new_conf = rep_limit_rate.sub(limit_rate_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + limit_rate_str + "\n" + new_conf[ssl_end_idx:] + + # IP并发限制 + rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *", re.M) + tmp_res = rep_per_ip.search(new_conf) + if tmp_res is not None: + new_conf = rep_per_ip.sub(perip_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + perip_str + "\n" + new_conf[ssl_end_idx:] + + rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *", re.M) + tmp_res = rep_per_server.search(site_conf) + if tmp_res is not None: + new_conf = rep_per_server.sub(per_server_str, new_conf) + else: + new_conf = new_conf[:ssl_end_idx] + per_server_str + "\n" + new_conf[ssl_end_idx:] + + public.writeFile(filename, new_conf) + is_error = public.checkWebConfig() + if is_error is not True: + public.writeFile(filename, site_conf) + return public.returnMsg(False, 'ERROR:
                                    ' + is_error.replace("\n", '
                                    ') + '
                                    ') + + public.serviceReload() + public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_OPEN_SUCCESS', (site_name,)) + return public.returnMsg(True, 'SET_SUCCESS') + + # 关闭流量限制 + def close_limit_net(self, get): + if public.get_webserver() != 'nginx': + return public.returnMsg(False, 'SITE_NETLIMIT_ERR') + if self.config_prefix is None: + return public.returnMsg(False, "不支持的网站类型") + try: + site_id = int(get.site_id) + except (AttributeError, TypeError, ValueError): + return public.returnMsg(False, "参数错误") + + # 取回配置文件 + site_info = public.M('sites').where("id=?", (site_id,)).find() + if not isinstance(site_info, dict): + return public.returnMsg(False, "站点信息查询错误") + else: + site_name = site_info["name"] + filename = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name) + site_conf = public.readFile(filename) + if not isinstance(site_conf, str): + return public.returnMsg(False, "配置文件读取错误") + + # 清理总并发 + rep_limit_rate = re.compile(r"(.*)limit_rate +(\d+)\w+ *; *\n?", re.M) + rep_per_ip = re.compile(r"(.*)limit_conn +perip +(\d+) *; *\n?", re.M) + rep_per_server = re.compile(r"(.*)limit_conn +perserver +(\d+) *; *\n?", re.M) + + new_conf = site_conf + new_conf = rep_limit_rate.sub("", new_conf, 1) + new_conf = rep_per_ip.sub("", new_conf, 1) + new_conf = rep_per_server.sub("", new_conf, 1) + + public.writeFile(filename, new_conf) + is_error = public.checkWebConfig() + if is_error is not True: + public.writeFile(filename, site_conf) + return public.returnMsg(False, 'ERROR:
                                    ' + is_error.replace("\n", '
                                    ') + '
                                    ') + public.serviceReload() + public.WriteLog('TYPE_SITE', 'SITE_NETLIMIT_CLOSE_SUCCESS', (site_name,)) + return public.returnMsg(True, 'SITE_NETLIMIT_CLOSE_SUCCESS') diff --git a/mod/common/redirect.py b/mod/common/redirect.py new file mode 100644 index 00000000..cff6683a --- /dev/null +++ b/mod/common/redirect.py @@ -0,0 +1,746 @@ +import os +import re +import json +import hashlib +import time +from typing import Tuple, Optional, Union, Dict, List +from urllib import parse +from itertools import product + +import public +from .base import BaseProjectCommon + + +class _RealRedirect: + setup_path = "/www/server/panel" + _redirect_conf_file = "{}/data/redirect.conf".format(setup_path) + + _ng_domain_format = """ +if ($host ~ '^%s'){ + return %s %s%s; +} +""" + _ng_path_format = """ +rewrite ^%s(.*) %s%s %s; +""" + _ap_domain_format = """ + + RewriteEngine on + RewriteCond %%{HTTP_HOST} ^%s [NC] + RewriteRule ^(.*) %s%s [L,R=%s] + +""" + _ap_path_format = """ + + RewriteEngine on + RewriteRule ^%s(.*) %s%s [L,R=%s] + +""" + + def __init__(self, config_prefix: str): + self._config: Optional[List[Dict[str, Union[str, int]]]] = None + self.config_prefix = config_prefix + self._webserver = None + + @property + def webserver(self) -> str: + if self._webserver is not None: + return self._webserver + self._webserver = public.get_webserver() + return self._webserver + + @property + def config(self) -> List[Dict[str, Union[str, int, List]]]: + if self._config is not None: + return self._config + try: + self._config = json.loads(public.readFile(self._redirect_conf_file)) + except (json.JSONDecodeError, TypeError, ValueError): + self._config = [] + if not isinstance(self._config, list): + self._config = [] + return self._config + + def save_config(self): + if self._config is not None: + return public.writeFile(self._redirect_conf_file, json.dumps(self._config)) + + def _check_redirect_domain_exist(self, site_name, + redirect_domain: list, + redirect_name: str = None, + is_modify=False) -> Optional[List[str]]: + res = set() + redirect_domain_set = set(redirect_domain) + for c in self.config: + if c["sitename"] != site_name: + continue + if is_modify: + if c["redirectname"] != redirect_name: + res |= set(c["redirectdomain"]) & redirect_domain_set + else: + res |= set(c["redirectdomain"]) & redirect_domain_set + return list(res) if res else None + + def _check_redirect_path_exist(self, site_name, + redirect_path: str, + redirect_name: str = None) -> bool: + for c in self.config: + if c["sitename"] == site_name: + if c["redirectname"] != redirect_name and c["redirectpath"] == redirect_path: + return True + return False + + @staticmethod + def _parse_url_domain(url: str): + return parse.urlparse(url).netloc + + @staticmethod + def _parse_url_path(url: str): + return parse.urlparse(url).path + + # 计算name md5 + @staticmethod + def _calc_redirect_name_md5(redirect_name) -> str: + md5 = hashlib.md5() + md5.update(redirect_name.encode('utf-8')) + return md5.hexdigest() + + def _check_redirect(self, site_name, redirect_name, is_error=False): + for i in self.config: + if i["sitename"] != site_name: + continue + if is_error and "errorpage" in i and i["errorpage"] in [1, '1']: + return i + if i["redirectname"] == redirect_name: + return i + return None + + # 创建修改配置检测 + def _check_redirect_args(self, get, is_modify=False) -> Union[str, Dict]: + if public.checkWebConfig() is not True: + return '配置文件出错请先排查配置' + + try: + site_name = get.sitename.strip() + redirect_path = get.redirectpath.strip() + redirect_type = get.redirecttype.strip() + domain_or_path = get.domainorpath.strip() + hold_path = int(get.holdpath) + + to_url = "" + to_path = "" + error_page = 0 + redirect_domain = [] + redirect_name = "" + status_type = 1 + + if "redirectname" in get and get.redirectname.strip(): + redirect_name = get.redirectname.strip() + if "tourl" in get: + to_url = get.tourl.strip() + if "topath" in get: + to_path = get.topath.strip() + if "redirectdomain" in get: + redirect_domain = json.loads(get.redirectdomain.strip()) + if "type" in get: + status_type = int(get.type) + if "errorpage" in get: + error_page = int(get.errorpage) + except (AttributeError, ValueError): + return '参数错误' + + if not is_modify: + if not redirect_name: + return "参数错误,配置名称不能为空" + # 检测名称是否重复 + if not (3 < len(redirect_name) < 15): + return '名称必须大于3小于15个字符串' + + if self._check_redirect(site_name, redirect_name, error_page == 1): + return '指定重定向名称已存在' + + site_info = public.M('sites').where("name=?", (site_name,)).find() + if not isinstance(site_info, dict): + return "站点信息查询错误" + else: + site_name = site_info["name"] + + # 检测目标URL格式 + rep = r"http(s)?\:\/\/([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9][a-zA-Z0-9]{0,62})+.?" + if to_url and not re.match(rep, to_url): + return '目标URL格式不对【%s】' % to_url + + # 非404页面de重定向检测项 + if error_page != 1: + # 检测是否选择域名 + if domain_or_path == "domain": + if not redirect_domain: + return '请选择重定向域名' + # 检测域名是否已经存在配置文件 + repeat_domain = self._check_redirect_domain_exist(site_name, redirect_domain, redirect_name, is_modify) + if repeat_domain: + return '重定向域名重复 %s' % repeat_domain + + # 检查目标URL的域名和被重定向的域名是否一样 + tu = self._parse_url_domain(to_url) + for d in redirect_domain: + if d == tu: + return '域名 "%s" 和目标域名一致请取消选择' % d + else: + if not redirect_path: + return '请输入重定向路径' + if redirect_path[0] != "/": + return "路径格式不正确,格式为/xxx" + # 检测路径是否有存在配置文件 + if self._check_redirect_path_exist(site_name, redirect_path, redirect_name): + return '重定向路径重复 %s' % redirect_path + + to_url_path = self._parse_url_path(to_url) + if to_url_path.startswith(redirect_path): + return '目标URL[%s]以被重定向的路径[%s]开头,会导致循环匹配' % (to_url_path, redirect_path) + # 404页面重定向检测项 + else: + if not to_url and not to_path: + return '首页或自定义页面必须二选一' + if to_path: + to_path = "/" + + return { + "tourl": to_url, + "topath": to_path, + "errorpage": error_page, + "redirectdomain": redirect_domain, + "redirectname": redirect_name if redirect_name else str(int(time.time())), + "type": status_type, + "sitename": site_name, + "redirectpath": redirect_path, + "redirecttype": redirect_type, + "domainorpath": domain_or_path, + "holdpath": hold_path, + } + + def create_redirect(self, get): + res_conf = self._check_redirect_args(get, is_modify=False) + if isinstance(res_conf, str): + return public.returnMsg(False, res_conf) + + res = self._set_include(res_conf) + if res is not None: + return public.returnMsg(False, res) + res = self._write_config(res_conf) + if res is not None: + return public.returnMsg(False, res) + self.config.append(res_conf) + self.save_config() + public.serviceReload() + return public.returnMsg(True, '创建成功') + + def _set_include(self, res_conf) -> Optional[str]: + flag, msg = self._set_nginx_redirect_include(res_conf) + if not flag: + return msg + flag, msg = self._set_apache_redirect_include(res_conf) + if not flag: + return msg + + def _write_config(self, res_conf) -> Optional[str]: + if res_conf["errorpage"] != 1: + res = self.write_nginx_redirect_file(res_conf) + if res is not None: + return res + res = self.write_apache_redirect_file(res_conf) + if res is not None: + return res + else: + self.unset_nginx_404_conf(res_conf["sitename"]) + res = self.write_nginx_404_redirect_file(res_conf) + if res is not None: + return res + res = self.write_apache_404_redirect_file(res_conf) + if res is not None: + return res + + def modify_redirect(self, get): + """ + @name 修改、启用、禁用重定向 + @author hezhihong + @param get.sitename 站点名称 + @param get.redirectname 重定向名称 + @param get.tourl 目标URL + @param get.redirectdomain 重定向域名 + @param get.redirectpath 重定向路径 + @param get.redirecttype 重定向类型 + @param get.type 重定向状态 0禁用 1启用 + @param get.domainorpath 重定向类型 domain 域名重定向 path 路径重定向 + @param get.holdpath 保留路径 0不保留 1保留 + @return json + """ + # 基本信息检查 + res_conf = self._check_redirect_args(get, is_modify=True) + if isinstance(res_conf, str): + return public.returnMsg(False, res_conf) + + old_idx = None + for i, conf in enumerate(self.config): + if conf["redirectname"] == res_conf["redirectname"] and conf["sitename"] == res_conf["sitename"]: + old_idx = i + + res = self._set_include(res_conf) + if res is not None: + return public.returnMsg(False, res) + res = self._write_config(res_conf) + if res is not None: + return public.returnMsg(False, res) + + if old_idx: + self.config[old_idx].update(res_conf) + else: + self.config.append(res_conf) + self.save_config() + public.serviceReload() + return public.returnMsg(True, '修改成功') + + def _set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ng_redirect_dir = "%s/vhost/nginx/redirect/%s" % (self.setup_path, redirect_conf["sitename"]) + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + if not os.path.exists(ng_redirect_dir): + os.makedirs(ng_redirect_dir, 0o600) + ng_conf = public.readFile(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"\sinclude +.*/redirect/.*\*\.conf;", re.M) + if rep_include.search(ng_conf): + return True, "" + redirect_include = ( + "#SSL-END\n" + " #引用重定向规则,注释后配置的重定向代理将无效\n" + " include {}/*.conf;" + ).format(ng_redirect_dir) + + if "#SSL-END" not in ng_conf: + return False, "添加配置失败,无法定位SSL相关配置的位置" + + new_conf = ng_conf.replace("#SSL-END", redirect_include) + public.writeFile(ng_file, new_conf) + if self.webserver == "nginx" and public.checkWebConfig() is not True: + public.writeFile(ng_file, ng_conf) + return False, "添加配置失败" + + return True, "" + + def _un_set_nginx_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ng_file = "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + ng_conf = public.readFile(ng_file) + if not isinstance(ng_conf, str): + return False, "nginx配置文件读取失败" + + rep_include = re.compile(r"(#(.*)\n)?\s*include +.*/redirect/.*\*\.conf;") + if not rep_include.search(ng_conf): + return True, "" + + new_conf = rep_include.sub("", ng_conf, 1) + public.writeFile(ng_file, new_conf) + if self.webserver == "nginx" and public.checkWebConfig() is not True: + public.writeFile(ng_file, ng_conf) + return False, "移除配置失败" + + return True, "" + + def _set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ap_redirect_dir = "%s/vhost/apache/redirect/%s" % (self.setup_path, redirect_conf["sitename"]) + ap_file = "{}/vhost/apache/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + if not os.path.exists(ap_redirect_dir): + os.makedirs(ap_redirect_dir, 0o600) + + ap_conf = public.readFile(ap_file) + if not isinstance(ap_conf, str): + return False, "apache配置文件读取失败" + + rep_include = re.compile(r"\sIncludeOptional +.*/redirect/.*\*\.conf", re.M) + # public.print_log(list(rep_include.finditer(ap_conf))) + include_count = len(list(rep_include.finditer(ap_conf))) + if ap_conf.count("
                                    ") == include_count: + return True, "" + + if include_count > 0: + # 先清除已有的配置 + self._un_set_apache_redirect_include(redirect_conf) + + rep_custom_log = re.compile(r"CustomLog .*\n") + rep_deny_files = re.compile(r"\n\s*#DENY FILES") + + include_conf = ( + "\n # 引用重定向规则,注释后配置的重定向代理将无效\n" + " IncludeOptional {}/*.conf\n" + ).format(ap_redirect_dir) + + new_conf = None + + def set_by_rep_idx(rep: re.Pattern, use_start: bool) -> bool: + new_conf_list = [] + last_idx = 0 + for tmp in rep.finditer(ap_conf): + new_conf_list.append(ap_conf[last_idx:tmp.start()]) + if use_start: + new_conf_list.append(include_conf) + new_conf_list.append(tmp.group()) + else: + new_conf_list.append(tmp.group()) + new_conf_list.append(include_conf) + last_idx = tmp.end() + + new_conf_list.append(ap_conf[last_idx:]) + + nonlocal new_conf + new_conf = "".join(new_conf_list) + public.writeFile(ap_file, new_conf) + if self.webserver == "apache" and public.checkWebConfig() is not True: + public.writeFile(ap_file, ap_conf) + return False + return True + + if set_by_rep_idx(rep_custom_log, False) and rep_include.search(new_conf): + return True, "" + + if set_by_rep_idx(rep_deny_files, True) and rep_include.search(new_conf): + return True, "" + return False, "设置失败" + + def _un_set_apache_redirect_include(self, redirect_conf: dict) -> Tuple[bool, str]: + ap_file = "{}/vhost/apache/{}{}.conf".format(self.setup_path, self.config_prefix, redirect_conf["sitename"]) + ap_conf = public.readFile(ap_file) + if not isinstance(ap_conf, str): + return False, "apache配置文件读取失败" + + rep_include = re.compile(r"(#(.*)\n)?\s*IncludeOptional +.*/redirect/.*\*\.conf") + if not rep_include.search(ap_conf): + return True, "" + + new_conf = rep_include.sub("", ap_conf) + public.writeFile(ap_file, new_conf) + if self.webserver == "apache" and public.checkWebConfig() is not True: + public.writeFile(ap_file, ap_conf) + return False, "移除配置失败" + + return True, "" + + def write_nginx_redirect_file(self, redirect_conf: dict) -> Optional[str]: + conf_file = "{}/vhost/nginx/redirect/{}/{}_{}.conf".format( + self.setup_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]), + redirect_conf["sitename"] + ) + if redirect_conf["type"] == 1: + to_url = redirect_conf["tourl"] + conf_list = ["#REWRITE-START"] + if redirect_conf["domainorpath"] == "domain": + hold_path = "$request_uri" if redirect_conf["holdpath"] == 1 else "" + for sd in redirect_conf["redirectdomain"]: + if sd.startswith("*."): + sd = r"[\w.]+\." + sd[2:] + + conf_list.append(self._ng_domain_format % ( + sd, redirect_conf["redirecttype"], to_url, hold_path + )) + else: + redirect_path = redirect_conf["redirectpath"] + if redirect_conf["redirecttype"] == "301": + redirect_type = "permanent" + else: + redirect_type = "redirect" + hold_path = "$1" if redirect_conf["holdpath"] == 1 else "" + conf_list.append(self._ng_path_format % (redirect_path, to_url, hold_path, redirect_type)) + + conf_list.append("#REWRITE-END") + + conf_data = "\n".join(conf_list) + public.writeFile(conf_file, conf_data) + + if self.webserver == "nginx": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + else: + if os.path.exists(conf_file): + os.remove(conf_file) + + def write_apache_redirect_file(self, redirect_conf: dict) -> Optional[str]: + conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format( + self.setup_path, redirect_conf["sitename"], self._calc_redirect_name_md5(redirect_conf["redirectname"]), + redirect_conf["sitename"] + ) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + to_url = redirect_conf["tourl"] + conf_list = ["#REWRITE-START"] + hold_path = "$1" if redirect_conf["holdpath"] == 1 else "" + if redirect_conf["domainorpath"] == "domain": + for sd in redirect_conf["redirectdomain"]: + if sd.startswith("*."): + sd = r"[\w.]+\." + sd[2:] + + conf_list.append(self._ap_domain_format % ( + sd, to_url, hold_path, redirect_conf["redirecttype"] + )) + else: + redirect_path = redirect_conf["redirectpath"] + conf_list.append(self._ap_path_format % (redirect_path, to_url, hold_path, redirect_conf["redirecttype"])) + + conf_list.append("#REWRITE-END") + + public.writeFile(conf_file, "\n".join(conf_list)) + if self.webserver == "apache": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + + def unset_nginx_404_conf(self, site_name): + """ + 清理已有的 404 页面 配置 + """ + need_clear_files = [ + "{}/vhost/nginx/{}{}.conf".format(self.setup_path, self.config_prefix, site_name), + "{}/vhost/nginx/rewrite/{}{}.conf".format(self.setup_path, self.config_prefix, site_name), + ] + rep_error_page = re.compile(r'(?P.*)error_page +404 +/404\.html[^\n]*\n', re.M) + rep_location_404 = re.compile(r'(?P.*)location += +/404\.html[^}]*}') + clear_files = [ + { + "data": public.readFile(i), + "path": i, + } for i in need_clear_files + ] + for file_info, rep in product(clear_files, (rep_error_page, rep_location_404)): + if not isinstance(file_info["data"], str): + continue + tmp_res = rep.search(file_info["data"]) + if not tmp_res or tmp_res.group("prefix").find("#") != -1: + continue + file_info["data"] = rep.sub("", file_info["data"]) + + for i in clear_files: + if not isinstance(i["data"], str): + continue + public.writeFile(i["path"], i["data"]) + + def write_nginx_404_redirect_file(self, redirect_conf: dict) -> Optional[str]: + """ + 设置nginx 404重定向 + """ + r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"]) + file_path = "{}/vhost/nginx/redirect/{}".format(self.setup_path, redirect_conf["sitename"]) + file_name = '%s_%s.conf' % (r_name_md5, redirect_conf["sitename"]) + conf_file = os.path.join(file_path, file_name) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + _path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"] + conf_data = ( + '#REWRITE-START\n' + 'error_page 404 = @notfound;\n' + 'location @notfound {{\n' + ' return {} {};\n' + '}}\n#REWRITE-END' + ).format(redirect_conf["redirecttype"], _path) + + public.writeFile(conf_file, conf_data) + if self.webserver == "nginx": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + + def write_apache_404_redirect_file(self, redirect_conf: dict) -> Optional[str]: + """ + 设置apache 404重定向 + """ + r_name_md5 = self._calc_redirect_name_md5(redirect_conf["redirectname"]) + conf_file = "{}/vhost/apache/redirect/{}/{}_{}.conf".format( + self.setup_path, redirect_conf["sitename"], r_name_md5, redirect_conf["sitename"] + ) + if redirect_conf["type"] != 1: + if os.path.exists(conf_file): + os.remove(conf_file) + return + + _path = redirect_conf["tourl"] if redirect_conf["tourl"] else redirect_conf["topath"] + conf_data = """ +#REWRITE-START + + RewriteEngine on + RewriteCond %{{REQUEST_FILENAME}} !-f + RewriteCond %{{REQUEST_FILENAME}} !-d + RewriteRule . {} [L,R={}] + +#REWRITE-END +""".format(_path, redirect_conf["redirecttype"]) + + public.writeFile(conf_file, conf_data) + if self.webserver == "apache": + isError = public.checkWebConfig() + if isError is not True: + if os.path.exists(conf_file): + os.remove(conf_file) + return 'ERROR: 配置出错
                                    ' + isError.replace("\n", '
                                    ') + '
                                    ' + + def remove_redirect(self, get, multiple=None): + try: + site_name = get.sitename.strip() + redirect_name = get.redirectname.strip() + except AttributeError: + return public.returnMsg(False, "参数错误") + target_idx = None + have_other_redirect = False + target_conf = None + for i, conf in enumerate(self.config): + if conf["redirectname"] != redirect_name and conf["sitename"] == site_name: + have_other_redirect = True + if conf["redirectname"] == redirect_name and conf["sitename"] == site_name: + target_idx = i + target_conf = conf + + if not target_idx: + return public.returnMsg(False, '没有指定的配置') + + r_md5_name = self._calc_redirect_name_md5(target_conf["redirectname"]) + public.ExecShell("rm -f %s/vhost/nginx/redirect/%s/%s_%s.conf" % ( + self.setup_path, site_name, r_md5_name, site_name)) + + public.ExecShell("rm -f %s/vhost/apache/redirect/%s/%s_%s.conf" % ( + self.setup_path, site_name, r_md5_name, site_name)) + + if not have_other_redirect: + self._un_set_apache_redirect_include(target_conf) + self._un_set_nginx_redirect_include(target_conf) + + del self.config[target_idx] + self.save_config() + if not multiple: + public.serviceReload() + + return public.returnMsg(True, '删除成功') + + def mutil_remove_redirect(self, get): + try: + redirect_names = json.loads(get.redirectnames.strip()) + site_name = json.loads(get.sitename.strip()) + except (AttributeError, json.JSONDecodeError, TypeError): + return public.returnMsg(False, "参数错误") + del_successfully = [] + del_failed = {} + get_obj = public.dict_obj() + for redirect_name in redirect_names: + get_obj.redirectname = redirect_name + get_obj.sitename = site_name + try: + result = self.remove_redirect(get, multiple=1) + if not result['status']: + del_failed[redirect_name] = result['msg'] + continue + del_successfully.append(redirect_name) + except: + del_failed[redirect_name] = '删除时出错了,请再试一次' + + public.serviceReload() + return { + 'status': True, + 'msg': '删除重定向 [ {} ] 成功'.format(','.join(del_successfully)), + 'error': del_failed, + 'success': del_successfully + } + + def get_redirect_list(self, get): + try: + error_page = None + site_name = get.sitename.strip() + if "errorpage" in get: + error_page = int(get.errorpage) + except (AttributeError, ValueError, TypeError): + return public.returnMsg(False, "参数错误") + redirect_list = [] + webserver = public.get_webserver() + if webserver == 'openlitespeed': + webserver = 'apache' + for conf in self.config: + if conf["sitename"] != site_name: + continue + if error_page is not None and error_page != int(conf['errorpage']): + continue + if 'errorpage' in conf and conf['errorpage'] in [1, '1']: + conf['redirectdomain'] = ['404页面'] + + md5_name = self._calc_redirect_name_md5(conf['redirectname']) + conf["redirect_conf_file"] = "%s/vhost/%s/redirect/%s/%s_%s.conf" % ( + self.setup_path, webserver, site_name, md5_name, site_name) + conf["type"] = 1 if os.path.isfile(conf["redirect_conf_file"]) else 0 + redirect_list.append(conf) + return redirect_list + + def remove_redirect_by_project_name(self, project_name): + for i in range(len(self.config) - 1, -1, -1): + if self.config[i]["sitename"] == project_name: + del self.config[i] + self.save_config() + m_path = self.setup_path + '/vhost/nginx/redirect/' + project_name + if os.path.exists(m_path): + public.ExecShell("rm -rf %s" % m_path) + m_path = self.setup_path + '/vhost/apache/redirect/' + project_name + if os.path.exists(m_path): + public.ExecShell("rm -rf %s" % m_path) + + +def test_api_warp(fn): + def inner(*args, **kwargs): + try: + return fn(*args, **kwargs) + except: + public.print_log(public.get_error_info()) + + return inner + + +class Redirect(BaseProjectCommon): + # 匹配目标URL的域名并返回 + + def remove_redirect_by_project_name(self, project_name): + if not isinstance(self.config_prefix, str): + return None + return _RealRedirect(self.config_prefix).remove_redirect_by_project_name(project_name) + + def create_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "不支持的网站类型") + return _RealRedirect(self.config_prefix).create_redirect(get) + + def modify_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "不支持的网站类型") + + return _RealRedirect(self.config_prefix).modify_redirect(get) + + def remove_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "不支持的网站类型") + return _RealRedirect(self.config_prefix).remove_redirect(get) + + def mutil_remove_project_redirect(self, get): + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "不支持的网站类型") + + return _RealRedirect(self.config_prefix).mutil_remove_redirect(get) + + def get_project_redirect_list(self, get): + public.print_log(get) + if not isinstance(self.config_prefix, str): + return public.returnMsg(False, "不支持的网站类型") + return _RealRedirect(self.config_prefix).get_redirect_list(get) diff --git a/mod/modController.py b/mod/modController.py new file mode 100644 index 00000000..c2fe11b8 --- /dev/null +++ b/mod/modController.py @@ -0,0 +1,100 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- + +# ------------------------------ +# 网站模型管理控制器 +# ------------------------------ +import json +import public +import re + + +class Controller: + + def __init__(self): + pass + + def model(self, args): + ''' + @name 调用指定项目模型 + @author wzz <2024/1/24 上午 11:07> + @param {"mod_name":"string<模型名称>","def_name":"string<方法名称>","data":JSON,} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: # 表单验证 + if args['mod_name'] in ['base']: return public.return_status_code(1000, '错误的调用!') + public.exists_args('def_name,mod_name', args) + if args['def_name'].find('__') != -1: return public.return_status_code(1000, + '调用的方法名称中不能包含“__”字符') + if not re.match(r"^\w+$", args['mod_name']): return public.return_status_code(1000, + r'调用的模块名称中不能包含\w以外的字符') + if not re.match(r"^\w+$", args['def_name']): return public.return_status_code(1000, + r'调用的方法名称中不能包含\w以外的字符') + except: + return public.get_error_object() + # 参数处理 + module_name = args['mod_name'].strip() + sub_mod_name = args['sub_mod_name'].strip() + mod_name = "{}Mod".format(args['mod_name'].strip()) + def_name = args['def_name'].strip() + model_index = None + if 'model_index' in args: model_index = args['model_index'] + + if not hasattr(args, 'data'): args.data = {} + if args.data: + if isinstance(args.data, str): + try: # 解析为dict_obj + pdata = public.to_dict_obj(json.loads(args.data)) + except: + return public.get_error_object() + else: + pdata = args.data + else: + pdata = args + + if isinstance(pdata, dict): + pdata = public.to_dict_obj(pdata) + + if not isinstance(pdata, public.dict_obj): + return public.return_error("传递的参数不是通用的内部对象") + + # 告诉加载器,要加载什么模块 + if model_index: pdata.model_index = model_index + + # 前置HOOK + hook_index = '{}_{}_LAST'.format(mod_name.upper(), def_name.upper()) + hook_result = public.exec_hook(hook_index, pdata) + if isinstance(hook_result, public.dict_obj): + pdata = hook_result # 桥接 + elif isinstance(hook_result, dict): + return hook_result # 响应具体错误信息 + elif isinstance(hook_result, bool): + if not hook_result: # 直接中断操作 + return public.return_data(False, {}, error_msg='前置HOOK中断操作') + + # 调用处理方法 + # result = run_object(pdata) + import PluginLoader + result = PluginLoader.module_run("{}/{}".format(module_name, sub_mod_name), def_name, pdata) + if isinstance(result, dict): + if 'status' in result and result['status'] == False and 'msg' in result: + if isinstance(result['msg'], str): + if result['msg'].find('Traceback ') != -1: + raise public.PanelError(result['msg']) + + # 后置HOOK + hook_index = '{}_{}_END'.format(mod_name.upper(), def_name.upper()) + hook_data = public.to_dict_obj({ + 'args': pdata, + 'result': result + }) + hook_result = public.exec_hook(hook_index, hook_data) + if isinstance(hook_result, dict): + result = hook_result['result'] + return result diff --git a/mod/project/docker/__init__.py b/mod/project/docker/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/project/docker/routetestMod.py b/mod/project/docker/routetestMod.py new file mode 100644 index 00000000..abca8bf9 --- /dev/null +++ b/mod/project/docker/routetestMod.py @@ -0,0 +1,65 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import os +# ------------------------------ +# Docker模型 +# ------------------------------ +import sys + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +os.chdir("/www/server/panel") +import public + + +class main(): + + def returnResult(self, get): + ''' + @name 模型测试方法,请求方式 + /mod/docker/routetestMod/returnResult + 支持form-data和json + + 使用通用的响应对象,返回json格式数据 + @author wzz <2024/2/19 上午 10:37> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + print(public.returnResult(msg="hello")) + return public.returnResult(msg="hello") + + def wsRequest(self, get): + """ + 处理websocket,ws测试方法,请求方式 + ws://192.168.x.x:8888/ws_mod + 连接成功后先发送第一条信息{"x-http-token":"token"} + 然后再发第二条信息,信息内容如下格式 + + 备注:如果需要使用apipost测试,请将__init__.py中ws模型路由的comReturn和csrf检查注释掉再测试 + @param get: + {"mod_name":"docker","sub_mod_name":"routetest","def_name":"wsRequest","ws_callback":"111"} + {"mod_name":"模型名称","sub_mod_name":"子模块名称","def_name":"函数名称","ws_callback":"ws必传参数,传111",其他参数接后面} + @return: + """ + if not hasattr(get, "_ws"): + return True + + import time + sum = 0 + while sum < 10: + time.sleep(0.2) + get._ws.send("hello\r\n") + sum += 1 + + return True + + +if __name__ == '__main__': + main().returnResult({}) diff --git a/mod/project/java/__init__.py b/mod/project/java/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/project/java/groupMod.py b/mod/project/java/groupMod.py new file mode 100644 index 00000000..108a1991 --- /dev/null +++ b/mod/project/java/groupMod.py @@ -0,0 +1,1169 @@ +import copy +import json +import os.path +import sys +import time +import psutil +from typing import Optional, Dict, Union, List, Tuple, Any, Iterable + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + +from mod.base import RealServer, json_response +from mod.project.java import utils +from mod.project.java.projectMod import debug, main as JavaProject + + +class Group: + GROUP_DATA_DIR = "/www/server/panel/data/java_group" + GROUP_TMP_DIR = "/var/tmp/springboot/group" + + def __init__(self, group_id: Optional[str] = None, group_data: Optional[dict] = None): + self.group_id = group_id + if isinstance(group_data, dict): + self.config: Optional[Dict[str, Union[str, List]]] = group_data + else: + self.config = self.load_group_data_by_id() + + if not os.path.exists(self.GROUP_DATA_DIR): + os.makedirs(self.GROUP_DATA_DIR, 0o600) + + if not os.path.exists(self.GROUP_TMP_DIR): + os.makedirs(self.GROUP_TMP_DIR) + + self.running_data = { + "length": 0, # 已完成的数量 + "remaining": 0, # 未完成的数量 + "executing": 0, # 正在执行的数量 + "projects": [], # 项目操作详情 + "msg": "", # 信息 + "status": True, + } + + # 如果有配置文件,则表示可以执行 + def can_running(self) -> bool: + return isinstance(self.config, dict) + + @staticmethod + def new_group_id() -> str: + from uuid import uuid4 + return uuid4().hex[::2] + + # 从配置文件中加载数据 + def load_group_data_by_id(self) -> Optional[dict]: + config_file = "{}/{}.json".format(self.GROUP_DATA_DIR, self.group_id) + try: + data = json.loads(public.readFile(config_file)) + except: + return None + if isinstance(data, dict): + return data + else: + return None + + def save_group_data(self): + if self.config: + config_file = "{}/{}.json".format(self.GROUP_DATA_DIR, self.group_id) + public.writeFile(config_file, json.dumps(self.config)) + + # 更新旧版数据 + @classmethod + def update_group_data(cls) -> None: + json_file = "/www/server/panel/class/projectModel/java_project_groups.json" + if not os.path.isfile(json_file): + return + data_str = public.readFile(json_file) + try: + data = json.loads(data_str) + except: + os.remove(json_file) + return + + try: + java_projects = public.M("sites").where('project_type=?', ('Java',)).field("id,name").select() + except: + return + + projects_dict = {i["name"]: i["id"] for i in java_projects} + + for idx, i in enumerate(data): + name = i.get("group_name", "默认分组-{}".format(idx + 1)) + projects = i.get("projects", []) + order = i.get("order", []) # type: list + tmp_p = [] + for p in projects: + if p["project_name"] in projects_dict and p["project_name"] in order: + tmp_p.append({ + "id": projects_dict[p["project_name"]], + "name": p["project_name"], + "level": order.index(p["project_name"]) + 1, + "check_info": { + "type": "port", + "port": [], + "wait_time": 180, + } + }) + + if tmp_p: + group_id = cls.new_group_id() + public.writeFile( + "{}/{}.json".format(cls.GROUP_DATA_DIR, group_id), + json.dumps({ + "group_name": name, + "projects": tmp_p, + "sort_type": "sequence", + }) + ) + + if os.path.isfile(json_file): + os.remove(json_file) + + # 检查数据 + def check_group_data(self) -> Optional[str]: + """ + 一个组的格式 + group = { + "group_name": "aaa", + "projects": [ + { + "id": 84, + "name": "tduck-api", + "level": 1, + "check_info": { + "type": ("port" or "active"), + "port": [8456, 8511], + "wait_time": 180, + } + } + ], + "sort_type": ("simultaneous" or "sequence") + } + """ + # 检查self.config是否为字典 + if not isinstance(self.config, dict): + return "参数格式错误" + + # 检查'sort_type'键是否存在且值为("simultaneous" or "sequence") + if "sort_type" not in self.config or self.config["sort_type"] not in ("simultaneous", "sequence"): + return "编排方式设置错误" + + if 'group_name' not in self.config or not isinstance(self.config["group_name"], str) \ + or not self.config["group_name"]: + return "分组名称设置错误" + + # 检查projects键是否存在且为列表 + if "projects" not in self.config or not isinstance(self.config["projects"], list): + return "项目列表设置错误" + + # 遍历projects列表中的每个项目 + for project in self.config["projects"]: + # 检查每个项目是否为字典 + if not isinstance(project, dict): + return "项目列表设置错误" + + # 检查'id', 'name', 'level', 'check_info'键是否存在 + if not all(key in project for key in ("id", "name", "level", "check_info")): + return "项目项目配置信息缺失" + + # 检查id、level是否为整数 + if not isinstance(project["id"], int) or not isinstance(project["level"], int): + return "项目id或优先级参数格式错误" + + # 检查'check_info'是否为字典 + if not isinstance(project["check_info"], dict): + return "项目{}检查策略设置错误".format(project["name"]) + + # 检查'check_info'中的type, 'port', 'wait_time' + if not all(key in project["check_info"] for key in ("type", "port", "wait_time")): + return "项目{}检查策略信息缺失".format(project["name"]) + + # 检查type的值 + if project["check_info"]["type"] not in ("port", "active"): + return "项目{}检查策略类型设置错误".format(project["name"]) + + # 如果所有检查都通过,则数据格式正确 + return None + + # 执行 check_info 中的判断,返回现在的进程是否属于在运行中, 如果不是运行中, 就是异常 + @staticmethod + def do_check_info(check_info: dict, process: psutil.Process): + if check_info["type"] == "port": + timeout = time.time() > process.create_time() + 60 * 3 + listen = [] + connections = process.connections() + for connection in connections: + if connection.status == "LISTEN": + listen.append(connection.laddr.port) + + if not check_info["port"]: + if not timeout and not bool(listen): + return "waiting" + if not bool(listen): + return 'failed' + else: + return 'succeeded' + else: + res = not bool(set(check_info["port"]) - set(listen)) # 如果所有的端口都在监听中,则返回True + if not timeout and not res: + return 'waiting' + if res: + return 'succeeded' + else: + return 'failed' + else: + create_time = process.create_time() + if time.time() > create_time + int(check_info["wait_time"]): + return "succeeded" + else: + return "waiting" + + @staticmethod + def is_running(pid: int): + try: + return psutil.Process(pid).is_running() + except: + return False + + # 获取运行状态信息 + def run_status(self, last_write_time: float) -> dict: + default_error = { + "running": False, + "msg": "操作出错,已退出", + "running_data": None, + "last_write_time": 0, + } + pid_file = "{}/{}.pid".format(Group.GROUP_TMP_DIR, self.group_id) + log_file = "{}/{}.log".format(Group.GROUP_TMP_DIR, self.group_id) + if not os.path.isfile(pid_file): + return default_error + try: + pid = int(public.readFile(pid_file)) + except: + return default_error + + try: + data = json.loads(public.readFile(log_file)) + except: # 如果读取出错,则说明进程出现问题,则杀死进程,并清除数据,返回错误 + if os.path.isfile(log_file): + os.remove(log_file) + if os.path.isfile(pid_file): + os.remove(pid_file) + if self.is_running(pid): + psutil.Process(pid).kill() + + return default_error + + # 如果进程不在运行,则返回上一次运行的数据(防止卡顿导致导致最后的数据没有读取到) + if not self.is_running(pid): + return { + "running": False, + "msg": "操作进程已退出", + "running_data": data, + "last_write_time": os.path.getmtime(log_file), + } + + # 如果还在运行, 则进行长链接, 等待最多5秒, 如果没有数据被写入, 且进程依旧在运行,则返回上一次的数据; + # 若不在运行则说明,且没有写入则说明等待期间出错了 (启动进程退出前必定会进行一次写入) + # 如果有数据被写入则返回最新数据 + + for i in range(50): + m_time = os.path.getmtime(log_file) + if abs(m_time - last_write_time) > 0.001: # 如果时间差大于0.001秒,则说明数据再次被写入了,则退出循环,返回数据 + now_write_time = m_time + break + elif i == 49: # 最后一次检测后,直接退出循环 + continue + else: + time.sleep(0.1) + else: # 如果 5 秒内没有数据被写入,则返回上一次的数据 + if not self.is_running(pid): # 不在运行, 说明等待期间出错了 + if os.path.isfile(log_file): + os.remove(log_file) + if os.path.isfile(pid_file): + os.remove(pid_file) + return default_error + + # 在运行,则返回上一次的数据 + return { + "running": self.is_running(pid), + "msg": "运行中", + "running_data": data, + "last_write_time": last_write_time, + } + + # + try: + data = json.loads(public.readFile(log_file)) + except: + if self.is_running(pid): + psutil.Process(pid).kill() + if os.path.isfile(log_file): + os.remove(log_file) + if os.path.isfile(pid_file): + os.remove(pid_file) + return { + "running": False, + "msg": "操作进程出错,已退出", + "running_data": None, + "last_write_time": 0, + } + running = self.is_running(pid) + return { + "running": running, + "msg": "运行中" if running else "操作进程已结束", + "running_data": data, + "last_write_time": now_write_time, + } + + # 获取可操作状态的信息 + def get_operation_info(self) -> Tuple[Iterable[str], str]: + pid_file = "{}/{}.pid".format(Group.GROUP_TMP_DIR, self.group_id) + try: + pid = int(public.readFile(pid_file)) + p = psutil.Process(pid) + if not p.is_running(): + return ("start", "stop"), "" + else: + last_cmd = p.cmdline()[-2] if len(p.cmdline()) == 4 else "" + return ("termination",), last_cmd + + except: + pass + return ("start", "stop"), "" + + def group_info(self, project_cache: Optional[dict] = None) -> Optional[dict]: + if not self.config: + return + if not project_cache: + project_ids = [i["id"] for i in self.config["projects"]] + if not project_ids: + data = copy.deepcopy(self.config) + data["group_id"] = self.group_id + return data + + projects = public.M('sites').where( + 'project_type=? and id IN ({})'.format(",".join(["?"] * len(project_ids))), + ('Java', *project_ids)).select() + project_cache = {} + for i in projects: + i["project_config"] = json.loads(i["project_config"]) + project_cache[i["id"]] = i + + j_pro = JavaProject() + + res = copy.deepcopy(self.config) + res["group_id"] = self.group_id + res["operation_info"], res["now_operation"] = self.get_operation_info() + running_num = 0 + for i in res["projects"]: + if i["id"] in project_cache: + try: + listen = [] + pid = j_pro.get_project_pid(project_cache[i["id"]]) + p = psutil.Process(int(pid)) + connections = p.connections() + for connection in connections: + if connection.status == "LISTEN": + listen.append(connection.laddr.port) + except: + i["running"] = False + i["project_status"] = 'not_run' + i["pid"] = None + i["listen"] = [] + continue + + running_num += 1 + i["running"] = True + i["project_status"] = self.do_check_info(i["check_info"], p) + i["pid"] = p.pid + i["listen"] = listen + + if not res["now_operation"]: + all_num = len(res["projects"]) + if all_num == 0: + res["operation_info"] = [] + else: + if running_num == all_num: + res["operation_info"] = ["stop"] + if running_num == 0: + res["operation_info"] = ["start"] + + return res + + # 运行一个Group相关的函数 + # ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ + def run_operation(self, operation: str) -> Optional[str]: + pid_file = "{}/{}.pid".format(Group.GROUP_TMP_DIR, self.group_id) + + if operation not in ("start", "stop"): + return "指定的操作不存在" + + try: + p = psutil.Process(public.readFile(pid_file)) + if p.is_running(): + return "操作运行中, 请等待上一个操作执行完成" + except: + pass + + if os.path.exists(pid_file): + os.remove(pid_file) + + self.build_run_sort(reverse=(operation == "stop")) + self.save_running_data() + panel_path = "/www/server/panel" + public.ExecShell( + "nohup {}/pyenv/bin/python3 {}/mod/project/java/group_script.py {} {} &> /tmp/group_script.log & \n" + "echo $! > {} ".format( + panel_path, panel_path, operation, self.group_id, pid_file) + ) + return None + + def build_run_sort(self, reverse=False) -> List[List[dict]]: + projects = self.config["projects"] # type: List[Dict[str, Union[int, str, dict]]] + if self.config["sort_type"] == "simultaneous": + projects_group = [projects] + else: + projects_group = [] + projects.sort(key=lambda x: x['level']) + last_level = -1 + for i in projects: + if i['level'] != last_level: + last_level = i['level'] + projects_group.append([i]) + else: + projects_group[-1].append(i) + + self.running_data['length'] = len(projects) + self.running_data['remaining'] = len(projects) + for g in projects_group: + names = [i["name"] for i in g] + self.running_data["projects"].append({ + "names": names, + "msg": "", + "running": False, + "data": { + i["name"]: {"status": False, "msg": "", "pid": 0, "name": i["name"]} + for i in g + } + }) + + if reverse: + self.running_data["projects"] = self.running_data["projects"][::-1] + return projects_group[::-1] + + return projects_group + + # 真正执行启动的逻辑 + def real_run_start(self): + pid_file = "{}/{}.pid".format(Group.GROUP_TMP_DIR, self.group_id) + pid = os.getpid() + public.writeFile(pid_file, str(pid)) + if not self.can_running(): + self.running_data["msg"] = "项目组配置文件错误,无法启动该项目组" + self.running_data["status"] = False + self.save_running_data() + return + + # 构建运行启动的信息,并返回运行顺序 + self.running_data["msg"] = "启动操作运行中" + run_sort = self.build_run_sort(reverse=False) + self.save_running_data() + for idx, g in enumerate(run_sort): + res_msg = self._run_start_one_step(g, sort_idx=idx) + if isinstance(res_msg, str): + self.running_data["msg"] = res_msg + self.running_data["status"] = False + self.save_running_data() + return + + self.running_data["msg"] = "启动操作运行成功" + self.running_data["status"] = True + self.save_running_data() + + def save_running_data(self): + log_file = "{}/{}.log".format(Group.GROUP_TMP_DIR, self.group_id) + public.writeFile(log_file, json.dumps(self.running_data)) + + # 执行启动任务的每一个优先级中所有的项目 + def _run_start_one_step(self, projects: List[dict], sort_idx: int) -> Optional[str]: + j_pro = JavaProject() + + step_running_data = self.running_data["projects"][sort_idx] # type: Dict[str, Any] + step_running_data["running"] = True + step_running_data["msg"] = "正在启动【{}】项目".format(", ".join(step_running_data["names"])) + self.running_data["remaining"] -= len(projects) + self.running_data["executing"] = len(projects) + self.save_running_data() + + need_wait_project = [] + error_msg = None + # 尝试启动每一个项目 + for p in projects: + project_data = j_pro.get_project_find(p["name"]) + if not project_data: + step_running_data["data"][p["name"]]["msg"] = "项目【{}】已丢失,无法启动".format(p["name"]) + continue + project_pid = j_pro.get_project_pid(project_data) + if project_pid: + step_running_data["data"][p["name"]]["status"] = True + step_running_data["data"][p["name"]]["msg"] = "项目【{}】已处于运行状态,未重新执行启动操作".format( + p["name"]) + step_running_data["data"][p["name"]]["pid"] = project_pid + continue + + res = j_pro.start_spring_boot_project(project_data, wait=False) # 不等待启动成功 + if not res["status"]: + step_running_data["data"][p["name"]]["status"] = False + step_running_data["data"][p["name"]]["msg"] = "在执行启动项目【{}】指令的时出现错误:{}".format( + p["name"], res["msg"]) + + error_msg = "出现无法处理的项目启动问题:" + res["msg"] + break # 出现无法启动的项目,直接退出 + else: + need_wait_project.append((project_data, p["check_info"])) + + if error_msg: + return error_msg + + if not need_wait_project: + return None + + self.save_running_data() + time.sleep(0.5) + while need_wait_project: + remove_idx = [] + error_projects = [] + change = False + for idx, (pd, check_info) in enumerate(need_wait_project): + if "process" not in pd or not isinstance(pd["process"], psutil.Process): + try: + pid = j_pro.get_project_pid(pd) + process = psutil.Process(int(pid)) + except: + process = None + else: + process = pd["process"] + + if not isinstance(process, psutil.Process) or not process.is_running(): + step_running_data["data"][pd["name"]]["status"] = False + step_running_data["data"][pd["name"]]["msg"] = "项目【{}】启动失败,详细情况请查看日志".format( + pd["name"]) + change = True + remove_idx.append(idx) + error_projects.append(pd["name"]) + continue + else: + if step_running_data["data"][pd["name"]]["pid"] == 0: + step_running_data["data"][pd["name"]]["pid"] = process.pid + change = True + + tmp_res = self.do_check_info(check_info, process) + if tmp_res == "succeeded": + step_running_data["data"][pd["name"]]["status"] = True + step_running_data["data"][pd["name"]]["msg"] = "项目【{}】启动成功".format(pd["name"]) + remove_idx.append(idx) + self.running_data["executing"] -= 1 + change = True + elif tmp_res == "failed": + step_running_data["data"][pd["name"]]["status"] = False + step_running_data["data"][pd["name"]]["msg"] = "项目【{}】启动失败,详细情况请查看日志".format( + pd["name"]) + remove_idx.append(idx) + error_projects.append(pd["name"]) + change = True + continue + + if remove_idx: + for idx in remove_idx: + need_wait_project.pop(idx) + + if error_projects: + return "项目【{}】启动失败无法继续执行启动操作,请查看日志".format(",".join(error_projects)) + if change is True: + self.save_running_data() + if need_wait_project: + time.sleep(0.2) + + step_running_data["running"] = False + self.save_running_data() + return + + def real_run_stop(self): + pid_file = "{}/{}.pid".format(Group.GROUP_TMP_DIR, self.group_id) + pid = os.getpid() + public.writeFile(pid_file, str(pid)) + if not self.can_running(): + self.running_data["msg"] = "项目组配置文件错误,无法停止该项目组" + self.running_data["status"] = False + self.save_running_data() + return + + # 构建运行启动的信息,并返回运行顺序 + self.running_data["msg"] = "项目组停止操作运行中" + run_sort = self.build_run_sort(reverse=True) + self.save_running_data() + for idx, g in enumerate(run_sort): + self._run_stop_one_step(g, sort_idx=idx) + + self.running_data["msg"] = "停止操作运行成功" + self.running_data["status"] = True + self.save_running_data() + + def _run_stop_one_step(self, projects: List[dict], sort_idx: int) -> None: + j_pro = JavaProject() + + step_running_data = self.running_data["projects"][sort_idx] # type: Dict[str, Any] + step_running_data["running"] = True + step_running_data["msg"] = "正在执行【{}】项目的停止任务".format(", ".join(step_running_data["names"])) + self.running_data["remaining"] -= len(projects) + self.running_data["executing"] = len(projects) + + need_wait_project = [] + # 尝试停止每一个项目 + for p in projects: + project_data = j_pro.get_project_find(p["name"]) + if not project_data: + step_running_data["data"][p["name"]]["msg"] = "项目【】已丢失,无法执行停止操作" + continue + + project_pid = j_pro.get_project_pid(project_data) + if not project_pid: + step_running_data["data"][p["name"]]["status"] = True + step_running_data["data"][p["name"]]["msg"] = "项目【{}】已停止".format(p["name"]) + continue + else: + project_data["pid"] = project_pid + + project_config = project_data["project_config"] + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + s_admin = RealServer() + if s_admin.daemon_status(server_name)["msg"] == "服务不存在!": + j_pro.stop_by_kill_pid(project_data) + if os.path.isfile(project_config["pids"]): + os.remove(project_config["pids"]) + else: + s_admin.daemon_admin(server_name, "stop") + utils.stop_by_user(project_data["id"]) + need_wait_project.append((project_data, p["check_info"])) + + if not need_wait_project: + return None + + self.save_running_data() + time.sleep(0.05) + wait_num = 0 + while need_wait_project and wait_num < 10: + remove_idx = [] + change = False + for idx, (pd, check_info) in enumerate(need_wait_project): + pid = pd["pid"] + pid_info = j_pro.real_process.get_process_info_by_pid(pid)["data"] + if not pid_info: + step_running_data["data"][pd["name"]]["status"] = True + step_running_data["data"][pd["name"]]["msg"] = "项目【{}】停止成功".format(pd["name"]) + remove_idx.append(idx) + self.running_data["executing"] -= 1 + change = True + + if remove_idx: + for idx in remove_idx: + need_wait_project.pop(idx) + + if change is True: + self.save_running_data() + + wait_num += 1 + if need_wait_project: + time.sleep(0.1) + + step_running_data["running"] = False + self.save_running_data() + + return + + def termination_operation(self) -> None: + pid_file = "{}/{}.pid".format(Group.GROUP_TMP_DIR, self.group_id) + try: + pid = int(public.readFile(pid_file)) + p = psutil.Process(pid) + p.kill() + os.remove(pid_file) + except: + pass + + +class GroupMager: + cache_type = ["springboot"] + + def __init__(self): + self.project_cache: Optional[dict] = None + + if not os.path.exists(Group.GROUP_DATA_DIR): + os.makedirs(Group.GROUP_DATA_DIR, 0o600) + + if not os.path.exists(Group.GROUP_TMP_DIR): + os.makedirs(Group.GROUP_TMP_DIR) + + def _get_project_cache(self): + if self.project_cache is not None: + return + java_projects = public.M("sites").where('project_type=?', ('Java',)).select() + _cache = {} + for i in java_projects: + config = json.loads(i["project_config"]) + if config["java_type"] in self.cache_type: + i["project_config"] = config + _cache[i["id"]] = i + + self.project_cache = _cache + + def group_list(self) -> List[dict]: + group_list = [] + for i in os.listdir(Group.GROUP_DATA_DIR): + file = "{}/{}".format(Group.GROUP_DATA_DIR, i) + if os.path.isfile(file) and i.endswith(".json"): + group_id = i[:-5] + try: + data = json.loads(public.readFile(file)) + except: + continue + g = Group(group_id, data) + group_list.append(g) + + if not group_list: + return [] + + self._get_project_cache() + res = [] + for i in group_list: + res.append(i.group_info(self.project_cache)) + + return res + + @staticmethod + def add_group(data: dict) -> Optional[str]: + g = Group(Group.new_group_id(), data) + res = g.check_group_data() + if isinstance(res, str): + return res + g.save_group_data() + + @staticmethod + def remove_group(group_id: str) -> None: + config_file = "{}/{}.json".format(Group.GROUP_DATA_DIR, group_id) + if os.path.isfile(config_file): + os.remove(config_file) + + @staticmethod + def add_project_to_group(group_id: str, project_name: str, check_info: dict, level: Optional[int]) -> Optional[str]: + g = Group(group_id) + if not g.config: + return "项目组不存在" + p = public.M("sites").where('project_type=? AND name=?', ('Java', project_name)).find() + if not p: + return "指定项目【{}】不存在".format(project_name) + project_config = json.loads(p["project_config"]) + if not project_config["java_type"] in GroupMager.cache_type: + return "指定项目【{}】不是spring boot项目, 不支持添加到项目组".format(project_name) + + used_project = [p["id"] for p in g.config["projects"]] + if p["id"] in used_project: + return "项目【{}】已存在于项目组【{}】".format(project_name, group_id) + + if level is None: + if not g.config["projects"]: + level = 1 + else: + level = max([p["level"] for p in g.config["projects"]]) + 1 + + g.config["projects"].append({ + "name": project_name, + "id": p["id"], + "check_info": check_info, + "level": level, + }) + res = g.check_group_data() + if isinstance(res, str): + return res + g.save_group_data() + + @staticmethod + def add_projects_to_group(group_id: str, project_ids: List[int]) -> Optional[str]: + g = Group(group_id) + if not g.config: + return "项目组不存在" + project_list = public.M("sites").where( + 'project_type=? AND id IN ({})'.format(",".join(["?"] * len(project_ids))), + ('Java', *project_ids) + ).select() + + used_project = [p["id"] for p in g.config["projects"]] + start_level = max([p["level"] for p in g.config["projects"]] + [1]) # 最小值为1 + for p in project_list: + if p["id"] in used_project: + return "项目【{}】已存在于项目组【{}】".format(p["name"], group_id) + project_config = json.loads(p["project_config"]) + if not project_config["java_type"] in GroupMager.cache_type: + return "指定项目【{}】不是spring boot项目, 不支持添加到项目组".format(p["name"]) + + g.config["projects"].append({ + "name": p["name"], + "id": p["id"], + "check_info": { + "type": "port", + "port": [], + "wait_time": 180, + }, + "level": start_level + 1, + }) + start_level += 1 + + res = g.check_group_data() + if isinstance(res, str): + return res + g.save_group_data() + + @staticmethod + def remove_project_from_group(group_id: str, project_id: int) -> Optional[str]: + g = Group(group_id) + if not g.config: + return "项目组不存在" + target_idx = None + for idx, p in enumerate(g.config["projects"]): + if p["id"] == project_id: + target_idx = idx + break + if target_idx is not None: + g.config["projects"].pop(target_idx) + g.save_group_data() + + @staticmethod + def modify_group(group_id: str, group_data: dict) -> Optional[str]: + g = Group(group_id, group_data) + res = g.check_group_data() + if isinstance(res, str): + return res + g.save_group_data() + + @staticmethod + def modify_group_projects(group_id: str, project_datas: list) -> Optional[str]: + g = Group(group_id) + if not g.config: + return "项目组不存在" + + project_ids = [p["id"] for p in project_datas] + project_list = public.M("sites").where( + 'project_type=? AND id IN ({})'.format(",".join(["?"] * len(project_ids))), + ('Java', *project_ids) + ).select() + + for p in project_list: + project_config = json.loads(p["project_config"]) + if not project_config["java_type"] in GroupMager.cache_type: + return "指定项目【{}】不是spring boot项目, 不支持设置到项目组".format(p["name"]) + + g.config["projects"] = project_datas + res = g.check_group_data() + if isinstance(res, str): + return res + + g.save_group_data() + + @staticmethod + def modify_group_project(group_id: str, project_id: int, + check_info: Optional[dict], level: Optional[int]) -> Optional[str]: + g = Group(group_id) + if not g.config: + return "项目组不存在" + p = public.M("sites").where('project_type=? AND id=?', ('Java', project_id)).find() + if not p: + return "指定项目不存在" + + target_idx = None + for idx, p in enumerate(g.config["projects"]): + if p["id"] == project_id: + target_idx = idx + break + + if target_idx is None: + return "指定项目不在该项目组内" + update_data = {} + if check_info is not None: + update_data["check_info"] = check_info + if level is not None: + update_data["level"] = level + + g.config["projects"][target_idx].update(update_data) + + res = g.check_group_data() + if isinstance(res, str): + return res + + g.save_group_data() + + @staticmethod + def change_sort_type(group_id: str, sort_type: str) -> Optional[str]: + g = Group(group_id) + if not g.config: + return "项目组不存在" + + if sort_type not in ("simultaneous", "sequence"): + return "排序方式错误" + g.config["sort_type"] = sort_type + g.save_group_data() + + +Group.update_group_data() + + +class main: + + def __init__(self): + pass + + @staticmethod + def group_list(get): + return GroupMager().group_list() + + @staticmethod + def group_info(get): + try: + group_id = get.group_id.strip() + except: + return json_response(status=False, msg="参数错误") + g = Group(group_id) + if not g.config: + return json_response(status=False, msg="项目组不存在") + else: + return json_response(status=True, data=g.group_info()) + + @staticmethod + def add_group(get): + try: + group_name = get.group_name.strip() + except: + return json_response(status=False, msg="参数错误") + res = GroupMager().add_group({ + "group_name": group_name, + "sort_type": "simultaneous", + "projects": [], + }) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="添加成功") + + @staticmethod + def remove_group(get): + try: + group_id = get.group_id.strip() + except: + return json_response(status=False, msg="参数错误") + GroupMager.remove_group(group_id) + return json_response(status=True, msg="删除成功") + + @staticmethod + def add_project_to_group(get): + check_info = { + "type": "port", + "port": [], + "wait_time": 180, + } + level = None + try: + group_id = get.group_id.strip() + project_name = get.project_name.strip() + if hasattr(get, "check_info") and get.check_info: + check_info = json.loads(get.check_info) + if hasattr(get, "level") and get.level: + level = int(get.level) + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.add_project_to_group(group_id, project_name, check_info, level) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="添加成功") + + @staticmethod + def add_projects_to_group(get): + + try: + group_id = get.group_id.strip() + if hasattr(get, "project_ids") and isinstance(get.project_ids, str): + project_ids = json.loads(get.project_ids) + else: + if isinstance(get.project_ids, list): + project_ids = get.project_ids + else: + return json_response(status=False, msg="参数错误") + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.add_projects_to_group(group_id, project_ids) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="添加成功") + + @staticmethod + def remove_project_from_group(get): + try: + group_id = get.group_id.strip() + project_id = int(get.project_id) + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.remove_project_from_group(group_id, project_id) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="删除成功") + + @staticmethod + def modify_group(get): + try: + group_id = get.group_id.strip() + if isinstance(get.group_data, str): + group_data = json.loads(get.group_data) + else: + group_data = get.group_data + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.modify_group(group_id, group_data) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="修改成功") + + @staticmethod + def modify_group_project(get): + check_info = None + level = None + try: + group_id = get.group_id.strip() + project_id = int(get.project_id) + if hasattr(get, "check_info") and get.check_info: + check_info = json.loads(get.check_info) + if hasattr(get, "level") and get.level: + level = int(get.level) + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.modify_group_project(group_id, project_id, check_info, level) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="修改成功") + + @staticmethod + def modify_group_projects(get): + try: + group_id = get.group_id.strip() + if isinstance(get.project_datas, str): + project_datas = json.loads(get.project_datas) + else: + project_datas = get.project_datas + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.modify_group_projects(group_id, project_datas) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="修改成功") + + @staticmethod + def change_sort_type(get): + try: + group_id = get.group_id.strip() + sort_type = get.sort_type.strip() + except: + return json_response(status=False, msg="参数错误") + res = GroupMager.change_sort_type(group_id, sort_type) + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="修改成功") + + @staticmethod + def start_group(get): + try: + group_id = get.group_id.strip() + except: + return json_response(status=False, msg="参数错误") + + g = Group(group_id) + if not g.config: + return json_response(status=False, msg="项目组不存在") + + res = g.run_operation("start") + + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="启动进行中") + + @staticmethod + def stop_group(get): + try: + group_id = get.group_id.strip() + except: + return json_response(status=False, msg="参数错误") + + g = Group(group_id) + if not g.config: + return json_response(status=False, msg="项目组不存在") + + res = g.run_operation("stop") + + if isinstance(res, str): + return json_response(status=False, msg=res) + else: + return json_response(status=True, msg="停止进行中") + + @staticmethod + def get_run_status(get): + last_write_time = 0 + try: + group_id = get.group_id.strip() + if hasattr(get, "last_write_time") and get.last_write_time: + last_write_time = float(get.last_write_time) + except: + return json_response(status=False, msg="参数错误") + + g = Group(group_id) + if not g.config: + return json_response(status=False, msg="项目组不存在") + + run_status = g.run_status(last_write_time) + run_status["running_step"] = None + if isinstance(run_status, dict): + if isinstance(run_status["running_data"], dict) and "projects" in run_status["running_data"]: + projects = run_status["running_data"]["projects"] + for i in projects: + if i["running"] is True: + run_status["running_step"] = i + + return json_response(status=True, data=run_status) + + @staticmethod + def termination_operation(get): + try: + group_id = get.group_id.strip() + except: + return json_response(status=False, msg="参数错误") + Group(group_id).termination_operation() + return json_response(status=True, msg="操作成功") + + @staticmethod + def spring_projects(get): + java_projects = public.M("sites").where('project_type=?', ('Java',)).field("name,id,project_config").select() + res = [] + for i in java_projects: + config = json.loads(i["project_config"]) + if config["java_type"] in GroupMager.cache_type: + res.append({ + "name": i["name"], + "id": i["id"] + }) + + return res diff --git a/mod/project/java/group_script.py b/mod/project/java/group_script.py new file mode 100644 index 00000000..c3deeb01 --- /dev/null +++ b/mod/project/java/group_script.py @@ -0,0 +1,34 @@ +import sys +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +from mod.project.java.groupMod import Group + + +def start_group(g_id: str): + g = Group(g_id) + g.real_run_start() + + +def stop_group(g_id: str): + g = Group(g_id) + g.real_run_stop() + + +if __name__ == '__main__': + if len(sys.argv) >= 3: + action = sys.argv[1] + group_id = sys.argv[2] + else: + print("参数错误") + exit(1) + + if action == "start": + start_group(group_id) + else: + stop_group(group_id) + + diff --git a/mod/project/java/java_web_conf.py b/mod/project/java/java_web_conf.py new file mode 100644 index 00000000..4ebf8897 --- /dev/null +++ b/mod/project/java/java_web_conf.py @@ -0,0 +1,575 @@ +import os +import re +import shutil +import sys +from typing import List, Optional, Union, Tuple + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public +from mod.base.web_conf.util import listen_ipv6, get_log_path, GET_CLASS, service_reload +from mod.base.web_conf import NginxDomainTool, ApacheDomainTool + + +class JavaNginxTool: + def __init__(self): + self._panel_path = "/www/server/panel" + self._vhost_path = "{}/vhost".format(self._panel_path) + self._nginx_bak_path = "/var/tmp/springboot/nginx_conf_backup" + if not os.path.exists(self._nginx_bak_path): + os.makedirs(self._nginx_bak_path, 0o600) + + def set_nginx_config(self, project_data: dict, domains: List[Tuple[str, Union[str, int]]], + use_ssl: bool = False, force_ssl=False): + if use_ssl: + use_http2_on = public.is_change_nginx_http2() + use_http3 = public.is_nginx_http3() + else: + use_http2_on = False + use_http3 = False + + project_config = project_data["project_config"] + if project_config['java_type'] == "springboot": + project_path = project_data["project_config"]["jar_path"] + else: + project_path = project_data["path"] + if os.path.isfile(project_path): + project_path = os.path.dirname(project_path) + + port_set = set() + domain_set = set() + use_ipv6 = listen_ipv6() + listen_ports_list = [] + for d, p in domains: + if str(p) == "443": # 443 端口特殊处理 + continue + if str(p) not in port_set: + listen_ports_list.append(" listen {};".format(str(p))) + if use_ipv6: + listen_ports_list.append(" listen [::]:{};".format(str(p))) + + port_set.add(str(p)) + domain_set.add(d) + + if use_ssl: + if not use_http2_on: + http2 = " http2" + else: + http2 = "" + listen_ports_list.append(" http2 on;") + + listen_ports_list.append(" listen 443 ssl{};".format(http2)) + if use_ipv6: + listen_ports_list.append(" listen [::]:443 ssl{};".format(http2)) + + if use_http3: + listen_ports_list.append(" listen 443 quic;") + if use_ipv6: + listen_ports_list.append(" listen [::]:443 quic;") + + listen_ports = "\n".join(listen_ports_list).strip() + + static_conf = self._build_static_conf(project_config, project_path) + proxy_conf = self._build_proxy_conf(project_config) + ssl_conf = "#error_page 404/404.html;" + if use_ssl: + ssl_conf += "\n" + self._build_ssl_conf(project_config, use_http3=use_http3, force_ssl=force_ssl) + + nginx_template_file = "{}/template/nginx/java_mod_http.conf".format(self._vhost_path) + nginx_conf_file = "{}/nginx/java_{}.conf".format(self._vhost_path, project_data["name"]) + + nginx_template = public.ReadFile(nginx_template_file) + if not isinstance(nginx_template, str): + return "读取模版文件失败" + + nginx_conf = nginx_template.format( + listen_ports=listen_ports, + domains=" ".join(domain_set), + site_path=project_path, + site_name=project_data["name"], + panel_path=self._panel_path, + log_path=get_log_path(), + ssl_conf=ssl_conf, + static_conf=static_conf, + proxy_conf=proxy_conf, + ) + rewrite_file = "{}/rewrite/java_{}.conf".format(self._vhost_path, project_data["name"]) + if not os.path.exists(rewrite_file): + public.writeFile(rewrite_file, '# 请将伪静态规则或自定义NGINX配置填写到此处\n') + apply_check = "{}/nginx/well-known/{}.conf".format(self._vhost_path, project_data["name"]) + if not os.path.exists(os.path.dirname(apply_check)): + os.makedirs(os.path.dirname(apply_check), 0o600) + if not os.path.exists(apply_check): + public.writeFile(apply_check, '') + + public.writeFile(nginx_conf_file, nginx_conf) + return None + + @staticmethod + def _build_proxy_conf(project_config: dict) -> str: + if "proxy_info" not in project_config: + return "" + + proxy_info = project_config["proxy_info"] + proxy_conf_list = [] + if not proxy_info: + return "" + ng_proxy = ''' #PROXY-START{proxy_dir} + location {proxy_dir} {{{rewrite} + proxy_pass {proxy_url}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;{add_headers} + proxy_set_header REMOTE-HOST $remote_addr; + add_header X-Cache $upstream_cache_status; + proxy_set_header X-Host $host:$server_port; + proxy_set_header X-Scheme $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 86400s; + proxy_send_timeout 30s; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + }} + #PROXY-END{proxy_dir}''' + for i in proxy_info: + if i.get("status", False): + continue + rewrite = "" + if "rewrite" in i and i["rewrite"].get("status", False): + rewrite = i["rewrite"] + src_path = i["src_path"] + if not src_path.endswith("/"): + src_path += "/" + target_path = rewrite["target_path"] + if target_path.endswith("/"): + target_path += target_path[:-1] + + rewrite = "\n rewrite ^{}(.*)$ {}/$1 break;".format(src_path, target_path) + + add_headers = "" + if "add_headers" in i: + header_tmp = " add_header {} {};" + add_headers_list = [header_tmp.format(h["k"], h["v"]) for h in i["add_headers"] if + "k" in h and "v" in h] + add_headers = "\n".join(add_headers_list) + if add_headers: + add_headers = "\n" + add_headers + + proxy_conf_list.append(ng_proxy.format( + proxy_dir=i["proxy_dir"], + rewrite=rewrite, + add_headers=add_headers, + proxy_url="http://127.0.0.1:{}".format(i["proxy_port"]), + )) + + return ("\n".join(proxy_conf_list) + "\n").lstrip() + + @staticmethod + def _build_static_conf(project_config: dict, default_path: str) -> str: + if project_config['java_type'] == "springboot" and "static_info" in project_config: + static_info = project_config["static_info"] + if not static_info.get("status", False): + return "" + index_str = "index.html" + index = static_info.get("index", "") + if index: + if isinstance(index, list): + index_str = " ".join(index) + elif isinstance(index, str): + index_str = " ".join([i.strip() for i in index.split(",") if i.strip()]) + + path = static_info.get("path") + if not path: + path = default_path + try_file = '' + if static_info.get("use_try_file", True): + try_file = " try_files $uri $uri/ /index.html;\n" + static_conf = ( + "location / {\n" + " root %s;\n" + " index %s;\n%s" + " }" + ) % (path, index_str, try_file) + + return static_conf + return "" + + def _build_ssl_conf(self, project_config: dict, use_http3=False, force_ssl=False) -> str: + force_ssl_str = "" + if force_ssl: + force_ssl_str = ''' + #HTTP_TO_HTTPS_START + if ($server_port !~ 443){ + rewrite ^(/.*)$ https://$host$1 permanent; + } + #HTTP_TO_HTTPS_END''' + http3_header = "" + if use_http3: + http3_header = '''\n add_header Alt-Svc 'quic=":443"; h3=":443"; h3-27=":443";h3-29=":443";h3-25=":443"; h3-T050=":443"; h3-Q050=":443";h3-Q049=":443";h3-Q048=":443"; h3-Q046=":443"; h3-Q043=":443"';''' + + return ''' ssl_certificate {vhost_path}/cert/{project_name}/fullchain.pem; + ssl_certificate_key {vhost_path}/cert/{project_name}/privkey.pem; + ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + add_header Strict-Transport-Security "max-age=31536000";{http3_header} + error_page 497 https://$host$request_uri;{force_ssl}'''.format( + vhost_path=self._vhost_path, + project_name=project_config["project_name"], + http3_header=http3_header, + force_ssl=force_ssl_str, + ) + + def open_nginx_config_file(self, project_data: dict, domains: List[Tuple[str, str]], ) -> Optional[str]: + project_name = project_data["name"] + back_path = "{}/{}".format(self._nginx_bak_path, project_name) + target_file = "{}/nginx/java_{}.conf".format(self._vhost_path, project_name) + if os.path.isfile(target_file): + return + + if os.path.isfile(back_path): + shutil.copyfile(back_path, target_file) + if os.path.isfile(target_file): + NginxDomainTool("java_").nginx_set_domain(project_name, *domains) + error_msg = public.checkWebConfig() + if not isinstance(error_msg, str): # 没有报错时直接退出 + service_reload() + return + + res = self.set_nginx_config(project_data, domains, use_ssl=False) + if not res: + service_reload() + return res + + def close_nginx_config_file(self, project_data: dict) -> None: + project_name = project_data["name"] + back_path = "{}/{}".format(self._nginx_bak_path, project_name) + target_file = "{}/nginx/java_{}.conf".format(self._vhost_path, project_name) + if not os.path.isfile(target_file): + return + + if os.path.isfile(back_path): + os.remove(back_path) + + shutil.move(target_file, back_path) + service_reload() + + def exists_nginx_ssl(self, project_name): + """ + 判断项目是否配置Nginx SSL配置 + """ + config_file = "{}/nginx/java_{}.conf".format(self._vhost_path, project_name) + if not os.path.exists(config_file): + return False, False + + config_body = public.readFile(config_file) + if isinstance(config_body, str): + return False, False + + is_ssl, is_force_ssl = False, False + if config_body.find('ssl_certificate') != -1: + is_ssl = True + if config_body.find('HTTP_TO_HTTPS_START') != -1: + is_force_ssl = True + return is_ssl, is_force_ssl + + def set_static_path(self, project_data: dict) -> Optional[Union[bool, str]]: + project_path = project_data["project_config"]["jar_path"] + static_str = self._build_static_conf(project_data["project_config"], project_path) + ng_file = "{}/nginx/java_{}.conf".format(self._vhost_path, project_data["name"]) + ng_conf = public.readFile(ng_file) + if not isinstance(ng_conf, str): + return "配置文件读取错误" + + static_conf = "#STATIC-START 静态资源相关配置\n {}\n #STATIC-END".format(static_str) + rep_static = re.compile(r"#STATIC-START(.*\n){2,9}\s*#STATIC-END.*") + res = rep_static.search(ng_conf) + if res: + new_ng_conf = ng_conf.replace(res.group(), static_conf) + public.writeFile(ng_file, new_ng_conf) + error_msg = public.checkWebConfig() + if not isinstance(error_msg, str): # 没有报错时直接退出 + service_reload() + return None + else: + public.writeFile(ng_file, ng_conf) + return 'WEB服务器配置配置文件错误ERROR:
                                    ' + \ + error_msg.replace("\n", '
                                    ') + '
                                    ' + + # 添加配置信息到配置文件中 + rep_list = [ + (re.compile(r"\s*#PROXY-LOCAl-START.*", re.M), True), # 添加到反向代理结尾的上面 + (re.compile(r"\s*#REWRITE-END.*", re.M), False), # 添加到伪静态的下面 + (re.compile(r"\s*#SSL-END.*", re.M), False), # 添加到SSL END的下面 + ] + + # 使用正则匹配确定插入位置 + def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool: + tmp_res = tmp_rep.search(ng_conf) + if not tmp_res: + return False + if use_start: + new_conf = ng_conf[:tmp_res.start()] + static_conf + tmp_res.group() + ng_conf[tmp_res.end():] + else: + new_conf = ng_conf[:tmp_res.start()] + tmp_res.group() + static_conf + ng_conf[tmp_res.end():] + + public.writeFile(ng_file, new_conf) + if public.get_webserver() == "nginx" and isinstance(public.checkWebConfig(), str): + public.writeFile(ng_file, ng_conf) + return False + return True + + for r, s in rep_list: + if set_by_rep_idx(r, s): + service_reload() + return None + else: + return False + + +class JavaApacheTool: + def __init__(self): + self._panel_path = "/www/server/panel" + self._vhost_path = "{}/vhost".format(self._panel_path) + self._apache_bak_path = "/var/tmp/springboot/httpd_conf_backup" + if not os.path.exists(self._apache_bak_path): + os.makedirs(self._apache_bak_path, 0o600) + + def set_apache_config_for_ssl(self, project_data): + domains = public.M('domain').where('pid=?', (project_data["id"],)).select() + domain_list = [(i["name"], i["port"]) for i in domains] + return self.set_apache_config(project_data, domain_list, use_ssl=True) + + def set_apache_config(self, project_data: dict, domains: List[Tuple[str, Union[str, int]]], + use_ssl: bool = False, force_ssl: bool = False): + name = project_data['name'] + port_set = set() + domain_set = set() + for d, p in domains: + port_set.add(str(p)) + domain_set.add(d) + + domains_str = ' '.join(domain_set) + project_config = project_data["project_config"] + if project_config['java_type'] == "springboot": + project_path = project_data["project_config"]["jar_path"] + else: + project_path = project_data["path"] + if os.path.isfile(project_path): + project_path = os.path.dirname(project_path) + + apache_template_file = "{}/template/apache/java_mod_http.conf".format(self._vhost_path) + apache_conf_file = "{}/apache/java_{}.conf".format(self._vhost_path, name) + + apache_template = public.ReadFile(apache_template_file) + if not isinstance(apache_template, str): + return "读取模版文件失败" + + apache_conf_list = [] + proxy_conf = self._build_proxy_conf(project_config) + for p in port_set: + apache_conf_list.append(apache_template.format( + site_path=project_path, + server_name='{}.{}'.format(p, project_path), + domains=domains_str, + log_path=get_log_path(), + server_admin='admin@{}'.format(name), + port=p, + ssl_config='', + project_name=name, + proxy_conf=proxy_conf, + )) + + if use_ssl: + ssl_config = '''SSLEngine On + SSLCertificateFile {vhost_path}/cert/{project_name}/fullchain.pem + SSLCertificateKeyFile {vhost_path}/cert/{project_name}/privkey.pem + SSLCipherSuite EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5 + SSLProtocol All -SSLv2 -SSLv3 -TLSv1 + SSLHonorCipherOrder On'''.format(project_name=name, vhost_path=public.get_vhost_path()) + if force_ssl: + ssl_config += ''' + #HTTP_TO_HTTPS_START + + RewriteEngine on + RewriteCond %{SERVER_PORT} !^443$ + RewriteRule (.*) https://%{SERVER_NAME}$1 [L,R=301] + + #HTTP_TO_HTTPS_END''' + + apache_conf_list.append(apache_template.format( + site_path=project_path, + server_name='{}.{}'.format("443", project_path), + domains=domains_str, + log_path=get_log_path(), + server_admin='admin@{}'.format(name), + port="443", + ssl_config=ssl_config, + project_name=name, + proxy_conf=proxy_conf, + )) + + apache_conf = '\n'.join(apache_conf_list) + public.writeFile(apache_conf_file, apache_conf) + ApacheDomainTool.apache_add_ports(*port_set) + return None + + @staticmethod + def _build_proxy_conf(project_config: dict) -> str: + if "proxy_info" not in project_config: + return "" + + proxy_info = project_config["proxy_info"] + proxy_conf_list = [] + if not proxy_info: + return "" + ap_proxy = ''' #PROXY-START{proxy_dir} + + ProxyRequests Off + SSLProxyEngine on + ProxyPass {proxy_dir} {proxy_url}/ + ProxyPassReverse {proxy_dir} {proxy_url}/ + RequestHeader set Host "%{Host}e" + RequestHeader set X-Real-IP "%{REMOTE_ADDR}e" + RequestHeader set X-Forwarded-For "%{X-Forwarded-For}e" + RequestHeader setifempty X-Forwarded-For "%{REMOTE_ADDR}e" + + #PROXY-END{proxy_dir}''' + + for i in proxy_info: + if i.get("status", False): + continue + + proxy_conf_list.append(ap_proxy.format( + proxy_dir=i["proxy_dir"], + proxy_url="http://127.0.0.1:{}".format(i["proxy_port"]), + )) + + return ("\n".join(proxy_conf_list) + "\n").lstrip() + + def open_apache_config_file(self, project_data: dict, domains: List[Tuple[str, str]]) -> Optional[str]: + project_name = project_data["name"] + back_path = "{}/{}".format(self._apache_bak_path, project_name) + target_file = "{}/apache/java_{}.conf".format(self._vhost_path, project_name) + if os.path.isfile(target_file): + return + + if os.path.isfile(back_path): + shutil.copyfile(back_path, target_file) + if os.path.isfile(target_file): + ApacheDomainTool("java_").apache_set_domain(project_name, *domains) + error_msg = public.checkWebConfig() + if not isinstance(error_msg, str): # 没有报错时直接退出 + service_reload() + return + + res = self.set_apache_config( + project_data, + domains=domains, + use_ssl=False, + ) + + if not res: + service_reload() + return res + + def close_apache_config_file(self, project_data: dict) -> None: + project_name = project_data["name"] + back_path = "{}/{}".format(self._apache_bak_path, project_name) + target_file = "{}/apache/java_{}.conf".format(self._vhost_path, project_name) + if not os.path.isfile(target_file): + return + + if os.path.isfile(back_path): + os.remove(back_path) + + shutil.move(target_file, back_path) + service_reload() + + def exists_apache_ssl(self, project_name) -> Tuple[bool, bool]: + """ + 判断项目是否配置Apache SSL配置 + """ + config_file = "{}/apache/java_{}.conf".format(self._vhost_path, project_name) + if not os.path.exists(config_file): + return False, False + + config_body = public.readFile(config_file) + if not isinstance(config_body, str): + return False, False + + is_ssl, is_force_ssl = False, False + if config_body.find('SSLCertificateFile') != -1: + is_ssl = True + if config_body.find('HTTP_TO_HTTPS_START') != -1: + is_force_ssl = True + return is_ssl, is_force_ssl + + +class JvavWebConfig: + + def __init__(self): + self._ng_conf_onj = JavaNginxTool() + self._ap_conf_onj = JavaApacheTool() + self.ws_type = public.get_webserver() + + def create_config(self, project_data: dict, domains: List[Tuple[str, Union[str, int]]], + use_ssl: bool = False, force_ssl=False): + ng_res = self._ng_conf_onj.set_nginx_config(project_data, domains, use_ssl, force_ssl=force_ssl) + ap_res = self._ap_conf_onj.set_apache_config(project_data, domains, use_ssl, force_ssl=force_ssl) + if self.ws_type == "nginx" and ng_res: + return ng_res + elif self.ws_type == "apache" and ap_res: + return ap_res + service_reload() + + def _open_config_file(self, project_data: dict): + domain_list = public.M('domain').where('pid=?', (project_data["id"],)).field("name,port").select() + domains = [(i["name"], str(i["port"])) for i in domain_list] + if not domains: + return "域名不能为空" + ng_res = self._ng_conf_onj.open_nginx_config_file(project_data, domains) + ap_res = self._ap_conf_onj.open_apache_config_file(project_data, domains) + if self.ws_type == "nginx" and ng_res: + return ng_res + elif self.ws_type == "apache" and ap_res: + return ap_res + + def _close_apache_config_file(self, project_data: dict) -> None: + self._ap_conf_onj.close_apache_config_file(project_data) + self._ng_conf_onj.close_nginx_config_file(project_data) + + def _set_domain(self, project_data: dict, domains: List[Tuple[str, str]]) -> Optional[str]: + ng_res = NginxDomainTool("java_").nginx_set_domain(project_data["name"], *domains) + ap_res = ApacheDomainTool("java_").apache_set_domain(project_data["name"], *domains) + if self.ws_type == "nginx" and ng_res: + return ng_res + elif self.ws_type == "apache" and ap_res: + return ap_res + + def _get_ssl_status(self, project_name) -> Tuple[bool, bool]: + if self.ws_type == "nginx": + return self._ng_conf_onj.exists_nginx_ssl(project_name) + elif self.ws_type == "apache": + return self._ap_conf_onj.exists_apache_ssl(project_name) + return False, False + + def _set_static_path(self, project_data: dict): + if self.ws_type == "nginx": + res = self._ng_conf_onj.set_static_path(project_data) + if res is None: + return None + elif res is False: + err_msg = public.checkWebConfig() + if isinstance(err_msg, str): + return 'WEB服务器配置配置文件错误ERROR:
                                    ' + \ + err_msg.replace("\n", '
                                    ') + '
                                    ' + + return self._open_config_file(project_data) + else: + return res + return "只支持nginx设置静态路由" diff --git a/mod/project/java/jmxquery/JMXQuery-0.1.8.jar b/mod/project/java/jmxquery/JMXQuery-0.1.8.jar new file mode 100644 index 0000000000000000000000000000000000000000..af2bf0c815b528fea40e9601564ccc8bcaeb2984 GIT binary patch literal 16891 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 str: + """ + Build a query string to pass via command line to JMXQuery Jar + + :return: The query string to find the MBean in format: + + {mBeanName}/{attribute}/{attributeKey} + + Example: java.lang:type=Memory/HeapMemoryUsage/init + """ + query = "" + if self.metric_name: + query += self.metric_name + + if ((self.metric_labels != None) and (len(self.metric_labels) > 0)): + query += "<" + keyCount = 0 + for key, value in self.metric_labels.items(): + query += key + "=" + value + keyCount += 1 + if keyCount < len(self.metric_labels): + query += "," + query += ">" + query += "==" + + query += self.mBeanName + if self.attribute: + query += "/" + self.attribute + if self.attributeKey: + query += "/" + self.attributeKey + + return query + + def to_string(self): + + string = "" + if self.metric_name: + string += self.metric_name + + if ((self.metric_labels != None) and (len(self.metric_labels) > 0)): + string += " {" + keyCount = 0 + for key, value in self.metric_labels.items(): + string += key + "=" + value + keyCount += 1 + if keyCount < len(self.metric_labels): + string += "," + string += "}" + else: + string += self.mBeanName + if self.attribute: + string += "/" + self.attribute + if self.attributeKey: + string += "/" + self.attributeKey + + string += " = " + string += str(self.value) + " (" + self.value_type + ")" + + return string + + +class JMXConnection(object): + """ + The main class that connects to the JMX endpoint via a local JAR to run queries + """ + + def __init__(self, connection_uri: str, jmx_username: str = None, jmx_password: str = None, java_path: str = DEFAULT_JAVA_PATH): + """ + Creates instance of JMXQuery set to a specific connection uri for the JMX endpoint + + :param connection_uri: The JMX connection URL. E.g. service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi + :param jmx_username: (Optional) Username if JMX endpoint is secured + :param jmx_password: (Optional) Password if JMX endpoint is secured + :param java_path: (Optional) Provide an alternative Java path on the machine to run the JAR. + Default is 'java' which will use the machines default JVM + """ + self.connection_uri = connection_uri + self.jmx_username = jmx_username + self.jmx_password = jmx_password + self.java_path = java_path + + def __run_jar(self, queries: List[JMXQuery], timeout) -> List[JMXQuery]: + """ + Run the JAR and return the results + + :param query: The query + :return: The full command array to run via subprocess + """ + + command = [self.java_path, '-jar', JAR_PATH, '-url', self.connection_uri, "-json"] + if (self.jmx_username): + command.extend(["-u", self.jmx_username, "-p", self.jmx_password]) + + queryString = "" + for query in queries: + queryString += query.to_query_string() + ";" + + command.extend(["-q", queryString]) + logger.debug("Running command: " + str(command)) + + jsonOutput = "[]" + try: + output = subprocess.run(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=timeout, + check=True) + + jsonOutput = output.stdout.decode('utf-8') + except subprocess.TimeoutExpired as err: + logger.error("Error calling JMX, Timeout of " + str(err.timeout) + " Expired: " + err.output.decode('utf-8')) + except subprocess.CalledProcessError as err: + logger.error("Error calling JMX: " + err.output.decode('utf-8')) + raise err + + logger.debug("JSON Output Received: " + jsonOutput) + metrics = self.__load_from_json(jsonOutput) + return metrics + + def __load_from_json(self, jsonOutput: str) -> List[JMXQuery]: + """ + Loads the list of returned metrics from JSON response + + :param jsonOutput: The JSON Array returned from the command line + :return: An array of JMXQuerys + """ + if "\n" in jsonOutput: + jsonOutput = jsonOutput.replace("\n", "") + if "\t" in jsonOutput: + jsonOutput = jsonOutput.replace("\t", "") + jsonMetrics = json.loads(jsonOutput) + metrics = [] + for jsonMetric in jsonMetrics: + mBeanName = jsonMetric['mBeanName'] + attribute = jsonMetric['attribute'] + attributeType = jsonMetric['attributeType'] + metric_name = None + if 'metricName' in jsonMetric: + metric_name = jsonMetric['metricName'] + metric_labels = None + if 'metricLabels' in jsonMetric: + metric_labels = jsonMetric['metricLabels'] + attributeKey = None + if 'attributeKey' in jsonMetric: + attributeKey = jsonMetric['attributeKey'] + value = None + if 'value' in jsonMetric: + value = jsonMetric['value'] + + metrics.append( + JMXQuery(mBeanName, attribute, attributeKey, value, attributeType, metric_name, metric_labels)) + return metrics + + def query(self, queries: List[JMXQuery], timeout=DEFAULT_JAR_TIMEOUT) -> List[JMXQuery]: + """ + Run a list of JMX Queries against the JVM and get the results + + :param queries: A list of JMXQuerys to query the JVM for + :return: A list of JMXQuerys found in the JVM with their current values + """ + return self.__run_jar(queries, timeout) diff --git a/mod/project/java/projectMod.py b/mod/project/java/projectMod.py new file mode 100644 index 00000000..3dfdf394 --- /dev/null +++ b/mod/project/java/projectMod.py @@ -0,0 +1,3252 @@ +import itertools +import json +import re +import shutil +import sys +import os +import time +import traceback +import threading +from datetime import datetime + +import psutil +from typing import Dict, List, Optional, Union, Any + +from mod.base import json_response +from mod.base.backup_tool import VersionTool +from mod.project.java import utils +from mod.base.process import RealUser, RealProcess + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + +from projectModel.watchModel import use_project_watch, add_project_watch, del_project_watch +from mod.base.web_conf import normalize_domain, is_domain, Redirect, remove_sites_service_config, RealSSLManger +from mod.base.web_conf import RealRedirect, RealLogMgr, Proxy, RealProxy +from mod.base.process.server import RealServer +from mod.project.java.java_web_conf import JvavWebConfig +from mod.project.java.server_proxy import RealServerProxy +from mod.base.git_tool import GitMager +from mod.project.java.springboot_parser import SpringConfigParser, SpringLogConfigParser + +_DEBUG = True + + +def debug(func): + if not _DEBUG: + return func + + def inner(*args, **kwargs): + try: + return func(*args, **kwargs) + except: + err = traceback.format_exc() + print(err) + public.print_log(err) + return {"msg": "报错了"} + + return inner + + +def set_java_service_link(): + java_service_bin = '/usr/bin/java-service' + java_service_src = '/www/server/panel/script/java-service.py' + if os.path.exists(java_service_src): + public.ExecShell("chmod 700 " + java_service_src) + if not os.path.exists(java_service_bin): + if os.path.exists(java_service_src): + public.ExecShell("ln -sf {} {}".format(java_service_src, java_service_bin)) + + +set_java_service_link() + + +class main(JvavWebConfig, Proxy, Redirect, GitMager): + + def __init__(self): + super().__init__() + self._bt_tomcat_path = "/usr/local/bttomcat" + self._mod_tomcat_path = "/usr/local/bt_mod_tomcat" + self._bt_jdk_path = "/usr/local/btjdk/" + self._java_path = "/www/server/java/" + self._vhost_path = "/www/server/panel/vhost" + self._java_project_path = "/var/tmp/springboot/" + self._java_project_vhost = "/var/tmp/springboot/vhost" + self._java_spring_boot_log_path = "/www/wwwlogs/java/springboot" + self._site_tomcat_path = '/www/server/bt_tomcat_web/' + if not os.path.exists(self._java_project_vhost): + os.makedirs(self._java_project_vhost + "/pids", 0o777) + os.makedirs(self._java_project_vhost + "/scripts", 0o755) + if not os.path.exists(self._java_project_vhost + "/env"): + os.makedirs(self._java_project_vhost + "/env", 0o755) + if not os.path.exists(self._java_spring_boot_log_path): + os.makedirs(self._java_spring_boot_log_path, 0o755) + if not os.path.exists(self._site_tomcat_path): + os.makedirs(self._site_tomcat_path, 0o755) + + if not os.path.exists(self._mod_tomcat_path): + os.makedirs(self._mod_tomcat_path, 0o755) + + self._default_java_log = "/www/wwwlogs/java" + if not os.path.exists(self._default_java_log): + os.makedirs(self._default_java_log, 0o701) + + # 实现Proxy初始化 + Proxy.__init__(self, config_prefix="java_") + # 实现Redirect初始化 + Redirect.__init__(self, config_prefix="java_") + + self._real_process: Optional[RealProcess] = None + + @property + def real_process(self) -> RealProcess: + if self._real_process is None: + self._real_process = RealProcess() + return self._real_process + + @staticmethod + def is_stop_by_user(project_id): + return utils.is_stop_by_user(project_id) + + @staticmethod + def write_project_log(log_str: str): + public.WriteLog("项目管理", log_str) + + def get_system_info(self, get=None): + return json_response(status=True, data={ + "jdk_info": self._local_jdk_info(), + "tomcat_status": self._bt_tomcat_info(), + }) + + @staticmethod + def _local_jdk_info() -> List[Dict]: + ret = [] + jdk_tool = utils.JDKManager() + current_java_home = jdk_tool.get_env_jdk() + # 获取已安装的JDK版本 + for version in jdk_tool.versions_list: + jdk_path = '/www/server/java/' + version + '/bin/java' + is_current = os.path.dirname(os.path.dirname(jdk_path)) == current_java_home + if os.path.exists('/www/server/java/' + version): + ret.append({'name': version, 'path': jdk_path, 'operation': 1, 'is_current': is_current}) + else: + ret.append({'name': version, 'path': '', 'operation': 0, 'is_current': False}) + name = '安装[{}]'.format(version) + install_data = public.M('tasks').where("status in (0, -1) and name=?", (name,)).find() + if install_data: + ret[-1]['operation'] = 3 + + ret.sort(key=lambda x: (x['operation'] == 1, x['operation'] == 3), reverse=True) + + # 检查其他JDK路径 + jdk_paths = [ + ('JDK', '/usr/bin/java'), + ('jdk8', '/usr/java/jdk1.8.0_121/bin/java'), + ('openjdk8', '/usr/local/btjdk/jdk8/bin/java'), + ('jdk7', '/usr/java/jdk1.7.0_80/bin/java') + ] + + for i in jdk_tool.custom_jdk_list: + is_current = utils.normalize_jdk_path(i) == current_java_home + ret.append({'name': '自定义JDK', 'path': i, 'operation': 1, 'is_current': is_current}) + + for name, path in jdk_paths: + if os.path.exists(path): + is_current = os.path.dirname(os.path.dirname(path)) == current_java_home + ret.append({'name': name, 'path': path, 'operation': 2, 'is_current': is_current}) + + return ret + + @staticmethod + def _bt_tomcat_info() -> List[Dict]: + versions = [7, 8, 9, 10] + data = [] + for i in versions: + soft_name = "安装[Java项目Tomcat-{}]".format(i) + install_data = public.M('tasks').where("status in (0, -1) and name=?", (soft_name,)).find() + tmp = utils.bt_tomcat(i).status() + tmp["version"] = i + if install_data: + tmp["is_install"] = True + else: + tmp["is_install"] = False + data.append(tmp) + return data + + @staticmethod + def process_for_create(get): + is_java_process = True + search = '' + + if hasattr(get, "is_java_process"): + is_java_process = get.is_java_process.strip() + if is_java_process in ("0", 0, False, "false"): + is_java_process = False + else: + is_java_process = True + if hasattr(get, "search"): + search = get.search.strip() + + res = [] + rep_bt_tomcat = re.compile(r"(/usr/local/bttomcat/.*)|(/www/server/bt_tomcat_web/.*)/jsvc") + rep_not_use = re.compile( + r"php|system|crond|NetworkManager|uwsgi|dotnet|bash|/www/server/tamper|mysql|nginx|httpd|tail|python|sshd" + ) + if is_java_process: + target_list = utils.jps() + for pid in target_list: + try: + p = psutil.Process(pid) + if search and not (search in p.name() or search in ' '.join(p.cmdline())): # 有搜索条件,但不符合搜索条件 + continue + if rep_bt_tomcat.search(p.exe()): + continue + res.append({ + "pid": pid, + "name": p.name(), + "exe": p.exe(), + "cmdline": p.cmdline(), + "username": p.username(), + "create_time:": p.create_time(), + "status": p.status(), + }) + except: + continue + + return res + + # 若未选择java进程, 则在所有进程中搜索 + for i in psutil.process_iter(["pid", "name", "exe", "cmdline", "username", "create_time", "status"]): + if not i.exe(): + continue + if rep_not_use.search(i.exe()): + continue + if search and not (search in i.name() or search in ' '.join(i.cmdline())): # 有搜索条件, 但不符合搜索条件 + continue + if rep_bt_tomcat.search(i.exe()): + continue + try: + res.append({ + "pid": i.pid, + "name": i.name(), + "exe": i.exe(), + "cmdline": i.cmdline(), + "username": i.username(), + "create_time:": i.create_time(), + "status": i.status(), + }) + except: + continue + + return res + + @staticmethod + def process_info_for_create(get): + try: + pid = int(get.pid.strip()) + except: + return json_response(status=False, msg="参数错误") + + try: + p = psutil.Process(pid) + + cmdline = p.cmdline() + ports = [] + for i in p.connections(): + if i.status == "LISTEN" and i.laddr and i.laddr.port not in ports: + ports.append(i.laddr.port) + user = p.username() + env = p.environ() + exe = p.exe() + except: + public.print_log(public.get_error_info()) + return json_response(status=False, msg="进程解析失败") + + pwd_path = env.get("PWD") + env_list = [] + for key, value in env.items(): + if key.lower().find("spring") != -1 or key.lower().find("java") != -1: + env_list.append({"k": key, "v": value}) + + java_bin = "" + if exe.endswith("java"): # 目前支持用户使用java命令启动的java进程 + java_bin = exe + if cmdline[0].endswith("java"): + cmdline[0] = java_bin + + jar_path = "" + target_jar_idx = None + for idx, i in enumerate(cmdline): + if i.endswith(".jar") or i.endswith(".war") and not (i.startswith("-") or "=" in i): + if not jar_path: + jar_path = i + target_jar_idx = idx + + # 处理获取的java路径 + project_name = "" + if jar_path: + if not jar_path.startswith("/"): # 处理相对路径 + jar_path = os.path.abspath(os.path.join(pwd_path, jar_path)) + cmdline[target_jar_idx] = jar_path + project_name = os.path.basename(jar_path).rsplit(".", 1)[0] + + if not os.path.exists(jar_path): + jar_path = None + + return json_response(status=True, data={ + "project_name": project_name, + "jar_path": jar_path, + "java_bin": java_bin, + "port": ports[0] if len(ports) else 0, + "user": user, + "env": env_list, + "cmdline": " ".join(cmdline), + "pid": pid, + }) + + @staticmethod + def check_spring_boot_args(get) -> Union[Dict, str]: + by_process = 0 + domains = [] + proxy_path = "/" + jmx_status = False + watch_file = False + env_file = "" + env_list = [] + try: + project_name = get.project_name.strip() + project_jar = get.project_jar.strip() + project_jdk = get.project_jdk.strip() + # port = int(get.port) + run_user = get.run_user.strip() + project_cmd = get.project_cmd.strip() # type:str + ps = get.project_ps.strip() + if hasattr(get, "env_file"): + env_file = get.env_file.strip() + if not isinstance(env_file, str) and os.path.exists(env_file): + return "环境变量文件不存在" + if hasattr(get, "env_list"): + env_list = get.env_list + if not isinstance(env_list, list): + return "环境变量格式错误" + if hasattr(get, "domains"): + domains = get.domains + if not isinstance(domains, list): + return "域名参数错误" + if hasattr(get, "proxy_path"): + proxy_path = get.proxy_path + if not isinstance(domains, str) and not proxy_path.startswith("/"): + return "代理路径必须是以/开头的字符串" + if hasattr(get, "jmx_status"): + jmx_status = utils.js_value_to_bool(get.jmx_status) + if hasattr(get, "watch_file"): + watch_file = utils.js_value_to_bool(get.watch_file) + if hasattr(get, "by_process"): + by_process = int(get.by_process) + if by_process < 0: + by_process = 0 + except (AttributeError, ValueError): + return "参数格式错误" + + if not project_name: + return "项目名称不能为空" + if not 1 <= len(project_name) <= 20: + return "项目名称不超过20字符" + if public.M('sites').where('name=?', (project_name,)).count(): + return '指定项目名称已存在: {}'.format(project_name) + + if not os.path.exists(project_jar): + return '请输入正确的jar包路径' + + project_jdk = utils.normalize_jdk_path(project_jdk) + if not isinstance(project_jdk, str): + return json_response(False, '项目JDK路径不存在') + if not os.path.exists(project_jdk): + return '请输入正确的JDK路径' + if not utils.test_jdk(project_jdk): + return '请输入正确的JDK路径' + + if project_cmd.find(project_jdk) == -1: + return '启动命令中不包含JDK路径,请确认无误再添加' + + if project_cmd.find(project_jar) == -1: + return '启动命令中不包含jar路径,请确认无误后再添加' + + # if not by_process and not utils.check_port(port): + # return '端口格式错误或已被其他进程使用' + + if domains: + if not public.is_apache_nginx(): + return "未安装Nginx" + err_msg = public.checkWebConfig() + if isinstance(err_msg, str): + return ( + 'WEB服务器配置配置文件错误ERROR:
                                    ' + + err_msg.replace("\n", '
                                    ') + '
                                    ' + ) + + if run_user not in [i["username"] for i in RealUser().get_user_list()["data"]]: + return '请输入正确的启动用户' + + if domains: + domains, err = normalize_domain(*domains) + if err: + return "
                                    ".join(["域名:{},错误信息:{}".format(i['domain'], i['msg']) for i in err]) + else: + for d, p in domains: + if public.M('domain').where('name=?', d).count(): + return '指定域名已存在: {}'.format(d) + + return { + "project_name": project_name, + "project_jar": project_jar, + "project_jdk": project_jdk, + # "port": port, + "run_user": run_user, + "project_cmd": project_cmd, + "ps": ps, + "env_list": env_list, + "env_file": env_file, + "domains": ["{}:{}".format(i[0], i[1]) for i in domains], + "proxy_path": proxy_path, + "jmx_status": jmx_status, + "watch_file": watch_file, + "by_process": by_process, + } + + def create_spring_boot_project(self, get): + config = self.check_spring_boot_args(get) + if isinstance(config, str): + return json_response(status=False, msg=config) + + project_config = { + 'ssl_path': '/www/wwwroot/java_node_ssl', + 'project_jdk': config["project_jdk"], + 'project_name': config["project_name"], + 'project_jar': config["project_jar"], + 'bind_extranet': 0 if not config["domains"] else 1, + 'domains': config["domains"], + 'run_user': get.run_user.strip(), + 'jmx_status': config["jmx_status"], + 'project_cmd': config["project_cmd"], + 'java_type': 'springboot', + 'jar_path': os.path.dirname(config["project_jar"]), + 'pids': "{}/pids/{}.pid".format(self._java_project_vhost, config["project_name"]), + 'logs': "{}/{}.log".format(self._java_spring_boot_log_path, config["project_name"]), + 'scripts': "{}/scripts/{}.sh".format(self._java_project_vhost, config["project_name"]), + 'watch_file': config["watch_file"], + 'env_list': config["env_list"], + 'env_file': config["env_file"], + 'proxy_path': config["proxy_path"], + "nohup_log": True, + "static_info": {}, + "proxy_info": [], + "daemon_status": False, + "server_name_suffix": "" + } + + pdata = { + 'name': config["project_name"], + 'path': config["project_jar"], + 'ps': config["ps"], + 'status': 1, + 'type_id': 0, + 'project_type': 'Java', + 'project_config': json.dumps(project_config), + 'addtime': public.getDate() + } + project_id = public.M('sites').insert(pdata) + if not isinstance(project_id, int): + return json_response(status=False, msg='创建项目失败') + pdata["project_config"] = project_config + pdata["id"] = project_id + + if project_config["domains"]: + for domain in project_config["domains"]: + domain_name, port = domain.split(":") + public.M('domain').insert( + { + 'name': domain_name, + 'pid': project_id, + 'port': port, + 'addtime': public.getDate() + } + ) + + error_msg = self._setup_spring_boot_project(pdata, by_process=config["by_process"]) + if error_msg: + return json_response(status=True, msg=error_msg) + return json_response(status=True, msg='项目创建成功, 若未能启动成功, 请查看日志信息,获取启动失败原因') + + def _setup_spring_boot_project(self, pdata, by_process=False) -> Optional[str]: + project_config = pdata["project_config"] + if project_config["jmx_status"]: + pass + # project_config["project_cmd"] = self._build_jmx_cmd(project_config["project_cmd"], project_config['jmx_status']) + if not by_process: + start_status = self._start_spring_boot_project(pdata, write_systemd_file=True, need_wait=False) + if isinstance(start_status, dict): + start_status = start_status["msg"] + + else: + public.writeFile(project_config['pids'], str(by_process)) + start_status = "添加成功" + + if project_config["watch_file"]: + add_project_watch( + p_name=project_config["project_name"], + p_type="java", + site_id=pdata["id"], + watch_path=project_config["project_jar"] + ) + if project_config["domains"]: + res = self.create_config( + pdata, + domains=[i.split(":", 1) for i in project_config["domains"]], + use_ssl=False, + ) + + return start_status + + # 实际启动项目的函数 + def _start_spring_boot_project(self, + project_data: dict, + write_systemd_file=True, + need_wait=True, + ) -> dict: + + old_pid = self.get_project_pid(project_data) + project_config = project_data["project_config"] + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + + # 先检查环境变量文件是否存在,没有的时候先写空 + env_file = project_config['env_file'] + env_list = project_config['env_list'] + env_path = "{}/env/{}.env".format(self._java_project_vhost, project_config["project_name"]) + + if isinstance(env_list, list): + if not os.path.exists(os.path.dirname(env_path)): + os.makedirs(os.path.dirname(env_path), 0o755) + public.writeFile(env_path, "\n".join( + ["{}={}".format(i["k"], i["v"]) for i in env_list if "k" in i and "v" in i] + )) + else: + if not os.path.isfile(env_path): + public.writeFile(env_path, "") + + if env_file and not os.path.isfile(env_file): + if not os.path.exists(os.path.dirname(env_file)): + os.makedirs(os.path.dirname(env_file), 0o755) + public.writeFile(env_file, "") + + log_file = project_config['logs'] + pid_file = project_config['pids'] + + if not os.path.exists(log_file): + public.writeFile(log_file, "") + + if not os.path.exists(pid_file): + public.writeFile(pid_file, "0") + + # 修改文件权限 + public.ExecShell( + "chown {usr}:{usr} {file}".format(usr=project_config["run_user"], file=project_config["project_jar"]) + ) + public.ExecShell( + "chown {usr}:{usr} {file}".format(usr=project_config["run_user"], file=log_file) + ) + public.ExecShell( + "chown {usr}:{usr} {file}".format(usr=project_config["run_user"], file=pid_file) + ) + utils.pass_dir_for_user(os.path.dirname(log_file), project_config['run_user']) + + # 如果存在服务文件,没有要求写入时,就直接执行启动停止操作 + s_admin = RealServer() + if not write_systemd_file: + res = s_admin.daemon_status(server_name) + msg = res['msg'] + if msg in ("运行中", "未运行"): + s_admin.daemon_admin(server_name, "restart") + if need_wait: + return self._wait_start_status(project_data, old_pid=old_pid) + else: + return json_response(status=True, msg="操作已执行") + + if msg == "操作失败!": + return res + # 还有一种服务文件丢失的情况走下面的写文件并启动 + + # 要求写入时, 重新写入启动文件 + project_cmd = project_config['project_cmd'] + # 前置准备 + + if project_config["nohup_log"]: + collect_log = log_file + else: + collect_log = '/dev/null' + + jar_path = project_config['jar_path'] + + env = "EnvironmentFile={}".format(env_path) + if env_file: + env += "\nEnvironmentFile={}".format(env_file) + + res = s_admin.create_daemon( + server_name=server_name, + pid_file=pid_file, + start_exec=project_cmd, + workingdirectory=jar_path, + user=project_config["run_user"], + logs_file=collect_log, + environments=env, + restart_type="always" if project_config.get("daemon_status", False) else "no" + ) + if not res["status"]: + return res + + s_admin.daemon_admin(server_name, "start") + if need_wait: + return self._wait_start_status(project_data, old_pid=old_pid) + return json_response(status=True, msg="操作已执行") + + def _wait_start_status(self, project_data: dict, old_pid: int) -> dict: + # 当为重启时,等待关闭 + if old_pid: + try: + p = psutil.Process(old_pid) + for i in range(20): + time.sleep(0.05) + if not p.is_running(): + break + except: + pass + + project_config = project_data['project_config'] + pid = self.get_project_pid(project_data) + if not pid: + for i in range(2): + time.sleep(0.05) + pid = self.get_project_pid(project_data) + if pid: + break + + if not pid: + return json_response(False, + msg="启动失败,详细信息请查看日志", + data=public.GetNumLines(project_config["logs"], 30)) + + for i in range(3): + port = self._get_port_by_pid(pid) + if port: + return json_response(True, "启动成功") + time.sleep(0.05) + if i > 1 and i % 5 == 0: + if pid not in psutil.pids(): + return json_response(False, + msg="启动失败,详细信息请查看日志", + data=public.GetNumLines(project_config["logs"], 30)) + + pid = self.get_project_pid(project_data) + if not pid: + return json_response(False, + msg="启动失败,详细信息请查看日志", + data=public.GetNumLines(project_config["logs"], 30)) + else: + return json_response(True, "未检查到端口监听,启动过程超时,请注意启动情况") + + @staticmethod + def _get_port_by_pid(pid: int) -> Optional[int]: + try: + p = psutil.Process(pid) + for i in p.connections(): + if i.status == "LISTEN": + return i.laddr.port + except: + return None + + # # 启动脚本 + # start_cmd = '''#!/bin/bash + # PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin + # export PATH + # {env} + # cd {jar_path} + # nohup {project_cmd} {collect_log} & + # echo $! > {pid_file} + # '''.format( + # jar_path=jar_path, + # project_cmd=project_cmd, + # pid_file=pid_file, + # log_file=log_file, + # env=env, + # collect_log=collect_log + # ) + # script_file = project_config['scripts'] + # # 写入启动脚本 + # public.writeFile(script_file, start_cmd) + # if os.path.exists(pid_file): + # os.remove(pid_file) + # public.set_mode(script_file, 755) + # # 修改文件权限 + # public.ExecShell( + # "chown {usr}:{usr} {file}".format(usr=project_config["run_user"], file=project_config["project_jar"]) + # ) + # if not os.path.exists(log_file): + # public.writeFile(log_file, "") + # + # public.ExecShell( + # "chown {usr}:{usr} {file}".format(usr=project_config["run_user"], file=log_file) + # ) + # utils.pass_dir_for_user(os.path.dirname(log_file), project_config['run_user']) + # # 执行脚本文件 + # res = public.ExecShell("bash {}".format(script_file), user=project_config['run_user'], env=os.environ.copy()) + # + # time.sleep(1) + # error_msg = '启动失败,您可以尝试查看控制台日志,以获取具体失败原因' + # if not os.path.exists(pid_file): + # return error_msg + # + # # 获取PID + # try: + # pid = int(public.readFile(pid_file)) + # except: + # return error_msg + # if pid not in psutil.pids(): + # return error_msg + # + # return None + + @staticmethod + def _build_jmx_cmd(cmd_str: str, jmx_status: bool): + has_jmx_args = cmd_str.find("-Dcom.sun.management.jmxremote") != -1 + if not jmx_status: + if not has_jmx_args: + return cmd_str + + cmd_str_list = cmd_str.split(" ") + for i in range(len(cmd_str_list) - 1, -1, -1): + if cmd_str_list[i].startswith("-Dcom.sun.management.jmxremote"): + cmd_str_list.pop(i) + if cmd_str_list[i].startswith("-Djava.rmi.server.hostname=127.0.0.1"): + cmd_str_list.pop(i) + + return " ".join(cmd_str_list) + + if jmx_status and has_jmx_args: + port = re.search(r"-Dcom\.sun\.management\.jmxremote\.port=(?P\d+)", cmd_str) + if not utils.check_port(port.group("port")): # 如果原来的port被占用了,则生成一个随机的port并替换 + cmd_str = re.sub( + r"-Dcom\.sun\.management\.jmxremote\.port=(\d+)", + "-Dcom.sun.management.jmxremote.port={}".format(utils.create_a_not_used_port()), + cmd_str + ) + return cmd_str + + add_cmd = ( + " -Dcom.sun.management.jmxremote" + " -Dcom.sun.management.jmxremote.port={}" + " -Djava.rmi.server.hostname=127.0.0.1" + " -Dcom.sun.management.jmxremote.local.only=true" + " -Dcom.sun.management.jmxremote.authenticate=false" + " -Dcom.sun.management.jmxremote.ssl=false" + ).format(utils.create_a_not_used_port()) + + cmd_str = cmd_str.lstrip(" ") + first_space_idx = cmd_str.find(" ") # 把jmx的配置放到第一个空格之后 + if first_space_idx == -1: + return cmd_str + add_cmd + else: + return cmd_str[:first_space_idx] + add_cmd + cmd_str[first_space_idx:] + + @staticmethod + def check_tomcat_args(get) -> Union[Dict, str]: + port = None + jdk_path = None + try: + domain = get.domain.strip() + tomcat_version = int(get.tomcat_version) + project_path = get.project_path.strip() + if hasattr(get, "port"): + port = int(get.port) + if hasattr(get, "jdk_path"): + jdk_path = get.jdk_path.strip() + ps = get.project_ps.strip() + except: + return "参数错误" + + if not is_domain(domain): + return "请输入正确的域名" + + if not os.path.exists(project_path): + os.makedirs(project_path) + public.set_own(project_path, 'www') + if tomcat_version not in (7, 8, 9, 10): + return "请选择正确的Tomcat版本" + + if jdk_path is not None: + if not os.path.exists(jdk_path): + return "JDK路径不存在" + jdk_path = utils.normalize_jdk_path(jdk_path) + if not isinstance(jdk_path, str): + return '项目JDK路径不存在' + if not utils.test_jdk(jdk_path): + return "JDK路径错误" + + if port is not None: + if not utils.check_port(port): + return "端口被占用" + + domain_list, err = normalize_domain(domain) + if not err: + domain = domain_list[0] + if public.M('domain').where('name=?', domain[0]).count(): + return '指定域名已存在: {}'.format(domain[0]) + project_name = domain[0] + domain = "{}:{}".format(domain[0], domain[1]) + else: + return "
                                    ".join(["域名:{},错误信息:{}".format(i['domain'], i['msg']) for i in err]) + + return { + "project_name": project_name, + "project_jdk": jdk_path, + "ps": ps, + "port": port, + "domains": [domain, ], + "tomcat_version": tomcat_version, + "project_path": project_path, + } + + def create_tomcat_project(self, get): + config = self.check_tomcat_args(get) + if isinstance(config, str): + return json_response(status=False, msg=config) + + tomcat = utils.bt_tomcat(config["tomcat_version"]) + if not tomcat.installed: + return json_response(status=False, msg="指定的Tomcat版本未安装") + + if config["project_jdk"] and tomcat.jdk_path != config["project_jdk"]: + if not tomcat.replace_jdk(config["project_jdk"]): + return json_response(status=False, msg="替换JDK失败") + + if config["port"] and not tomcat.set_port(config["port"]): + return json_response(status=False, msg="设置端口失败") + + if not tomcat.add_host(config["project_name"], config["project_path"]): + return json_response(status=False, msg="添加失败") + + # 有web服务且可以重启 + if public.is_apache_nginx() and not isinstance(public.checkWebConfig(), str): + bind_extranet = 1 + else: + bind_extranet = 0 + + if os.path.isfile(config["project_path"]): + path = os.path.basename(config["project_path"]) + else: + path = config["project_path"] + + project_config = { + 'project_name': config["project_name"], + 'bind_extranet': bind_extranet, + 'domains': config['domains'], + 'tomcat_version': config["tomcat_version"], + 'java_type': 'neizhi', + 'server_xml': '/usr/local/bt_mod_tomcat/tomcat%s/conf/server.xml' % config["tomcat_version"], + 'port': int(tomcat.port()), + 'auth': '1', # 默认开机自动启动 + 'logs': os.path.dirname(tomcat.log_file), + 'ssl_path': '/www/wwwroot/java_node_ssl', + "proxy_info": [], + } + + # 添加数据库信息 + pdata = { + 'name': config["project_name"], + 'path': config["project_path"], + 'ps': config["ps"], + 'status': 1, + 'type_id': 0, + 'project_type': 'Java', + 'project_config': json.dumps(project_config), + 'addtime': public.getDate() + } + tomcat.save_config_xml() + project_id = public.M('sites').insert(pdata) + pdata["id"] = project_id + for domain in config["domains"]: + domain_name, port = domain.split(":") + public.M('domain').insert({ + 'name': domain_name, + 'pid': project_id, + 'port': port, + 'addtime': public.getDate() + }) + tomcat.restart() + + pdata["project_config"] = project_config + + if bind_extranet: + self.create_config( + pdata, + domains=[i.split(":") for i in config['domains']], + use_ssl=False) + + return json_response(status=True, msg='项目创建成功') + + @staticmethod + def check_site_tomcat_args(get) -> Union[Dict, str]: + jdk_path = None + try: + domain = get.domain.strip() + tomcat_version = int(get.tomcat_version) + project_path = get.project_path.strip() + if hasattr(get, "project_jdk"): + jdk_path = get.project_jdk.strip() + port = int(get.port) + run_user = get.run_user.strip() + ps = get.project_ps.strip() + except: + return "参数错误" + + if not utils.check_port(port): + return "端口已被使用" + + if not is_domain(domain): + return "请输入正确的域名" + if not os.path.exists(project_path): + os.makedirs(project_path) + public.set_own(project_path, 'www') + if tomcat_version not in (7, 8, 9, 10): + return "请选择正确的Tomcat版本" + + if jdk_path: + if not os.path.exists(jdk_path): + return "JDK路径不存在" + jdk_path = utils.normalize_jdk_path(jdk_path) + if not isinstance(jdk_path, str): + return "JDK路径错误" + if not utils.test_jdk(jdk_path): + return "JDK检查错误" + + if run_user not in [i["username"] for i in RealUser().get_user_list()["data"]]: + return '请输入正确的启动用户' + + domain_list, err = normalize_domain(domain) + if err: + return "
                                    ".join(["域名:{},错误信息:{}".format(i['domain'], i['msg']) for i in err]) + else: + for d, p in domain_list: + if public.M('domain').where('name=?', d).count(): + return '指定域名已存在: {}'.format(d) + domain = domain_list[0] + project_name = domain[0] + domain = "{}:{}".format(domain[0], domain[1]) + + return { + "project_name": project_name, + "project_jdk": jdk_path, + "run_user": run_user, + "port": port, + "ps": ps, + "domains": [domain, ], + "tomcat_version": tomcat_version, + "project_path": project_path, + 'ssl_path': '/www/wwwroot/java_node_ssl', + } + + def create_site_tomcat_project(self, get): + config = self.check_site_tomcat_args(get) + if isinstance(config, str): + return json_response(status=False, msg=config) + + site_tomcat_path = os.path.join(self._site_tomcat_path, config["project_name"]) + if os.path.exists(site_tomcat_path): + return json_response(status=False, msg="该网站已经存在。如想建立请删除%s" % site_tomcat_path) + + # 首先需要先复制好文件过去 + if not os.path.exists(site_tomcat_path): + os.makedirs(site_tomcat_path) + + bt_tomcat_back = "/usr/local/bttomcat/tomcat_bak%d" + if not os.path.exists(bt_tomcat_back % config["tomcat_version"] + '/conf/server.xml'): + return json_response(False, "tomcat7的配置文件不存在,请重新安装tomcat7") + public.ExecShell('cp -r %s/* %s && chown -R %s:%s %s' % ( + bt_tomcat_back % int(config["tomcat_version"]), site_tomcat_path, + config["run_user"], config["run_user"], site_tomcat_path + )) + tomcat = utils.site_tomcat(config["project_name"]) + if not tomcat: + return json_response(status=False, msg="Tomcat未能初始化成功") + + # server.xml + if os.path.exists(site_tomcat_path + '/conf/server.xml'): + tomcat.reset_tomcat_server_config(config["port"]) + else: + os.system('rm -rf %s' % site_tomcat_path) + return json_response(False, "配置文件不存在请重新安装tomcat后尝试新建网站") + + if tomcat.jdk_path != config["project_jdk"]: + res = tomcat.replace_jdk(config["project_jdk"]) + if res: + os.system('rm -rf %s' % site_tomcat_path) + return json_response(status=False, msg="JDK替换失败") + + log_path = "/www/wwwlogs/java/{}".format(config["project_name"]) + if not os.path.exists(log_path) or os.path.isfile(log_path): + os.makedirs(log_path, mode=0o755) + tomcat.change_log_path(log_path, prefix=config["project_name"].replace(".", '_')) + + if not tomcat.add_host(config["project_name"], config["project_path"]): + os.system('rm -rf %s' % site_tomcat_path) + return json_response(status=False, msg="添加失败") + + # 有web服务且可以重启 + if public.is_apache_nginx() and not isinstance(public.checkWebConfig(), str): + bind_extranet = 1 + else: + bind_extranet = 0 + + if os.path.isfile(config["project_path"]): + path = os.path.basename(config["project_path"]) + else: + path = config["project_path"] + + project_config = { + 'project_name': config["project_name"], + "project_jdk": config["project_jdk"], + 'bind_extranet': bind_extranet, + 'domains': config["domains"], + 'tomcat_version': config["tomcat_version"], + 'java_type': 'duli', + 'server_xml': site_tomcat_path + "/conf/server.xml", + 'port': config["port"], + "run_user": config["run_user"], + 'logs': log_path, + "proxy_info": [], + } + + pdata = { + 'name': config["project_name"], + 'path': config["project_path"], + 'ps': config["ps"], + 'status': 1, + 'type_id': 0, + 'project_type': 'Java', + 'project_config': json.dumps(project_config), + 'addtime': public.getDate() + } + tomcat.save_config_xml() + project_id = public.M('sites').insert(pdata) + pdata["id"] = project_id + for domain in config["domains"]: + domain_name, port = domain.split(":") + public.M('domain').insert({ + 'name': domain_name, + 'pid': project_id, + 'port': port, + 'addtime': public.getDate() + }) + + tomcat.restart(by_user=config["run_user"]) + + pdata["project_config"] = project_config + if bind_extranet: + self.create_config( + pdata, + domains=[i.split(":") for i in config['domains']], + use_ssl=False) + + return json_response(status=True, msg='项目创建成功') + + def create_project(self, get): + try: + project_type = int(get.project_type) + except: + return json_response(status=False, msg="项目类型错误") + + if project_type == 0: + return self.create_spring_boot_project(get) + elif project_type == 1: + return self.create_tomcat_project(get) + else: + return self.create_site_tomcat_project(get) + + @staticmethod + def get_project_find(project_name: str) -> Optional[dict]: + project_info = public.M('sites').where('project_type=? AND name=?', ('Java', project_name)).find() + if not project_info: + return None + + # 做旧项目的兼容性处理 + project_info['project_config'] = json.loads(project_info['project_config']) + if "jmx_status" not in project_info['project_config']: + project_info['project_config']['jmx_status'] = False + if "nohup_log" not in project_info['project_config']: + project_info['project_config']['nohup_log'] = True + + if "env_file" not in project_info['project_config']: + project_info['project_config']['env_file'] = '' + if "env_list" not in project_info['project_config']: + project_info['project_config']['env_list'] = [] + + if "static_path" in project_info['project_config'] and "static_info" not in project_info['project_config']: + project_info['project_config']['static_info'] = { + "path": project_info['project_config']['static_path'], + "status": True, + "index": "", + "use_try_file": True + } + public.M('sites').where("id=?", (project_info['id'],)).update( + {'project_config': json.dumps(project_info['project_config'])} + ) + if "proxy_info" not in project_info['project_config']: + rsp = RealServerProxy(project_info) + proxy_list = rsp.get_proxy_list() + project_info['project_config']['proxy_info'] = proxy_list + if 'auth' in project_info['project_config'] and project_info['project_config']['auth'] in (1, "1") and \ + "daemon_status" not in project_info['project_config']: + project_info['project_config']['daemon_status'] = True + + if 'daemon_status' not in project_info['project_config']: + project_info['project_config']['daemon_status'] = False + + return project_info + + def modify_spring_boot_project(self, get, project_data: Optional[dict] = None): + if not project_data: + try: + project_name = self.get_project_find(get.project_name.strip()) + except: + return json_response(status=False, msg="项目名称错误") + if not project_data: + return json_response(status=False, msg="项目不存在") + else: + project_name = project_data['name'] + + change_flag = False + project_config = project_data['project_config'] + if hasattr(get, 'run_user') and get.run_user.strip() != project_config['run_user']: + project_config['run_user'] = get.run_user.strip() + change_flag = True + if hasattr(get, 'project_jdk'): + input_jdk = utils.normalize_jdk_path(get.project_jdk.strip()) + if not isinstance(input_jdk, str): + return json_response(False, 'JDK检测错误') + + if input_jdk != project_config['project_jdk']: + project_config['project_jdk'] = input_jdk + change_flag = True + + if hasattr(get, 'project_jar') and get.project_jar.strip() != project_config['project_jar']: + project_config['project_jar'] = get.project_jar.strip() + if not os.path.isfile(project_config['project_jar']): + return json_response(False, '项目jar包不存在') + project_config['jar_path'] = os.path.dirname(project_config['project_jar']) + change_flag = True + + if hasattr(get, 'project_cmd') and get.project_cmd.strip(): + if get.project_cmd.strip() != project_config['project_cmd']: + project_config['project_cmd'] = get.project_cmd.strip() + change_flag = True + else: + return json_response(False, '缺少project_cmd参数') + + if hasattr(get, 'daemon_status'): + daemon_status = utils.js_value_to_bool(get.daemon_status) + if daemon_status != project_config['daemon_status']: + project_config['daemon_status'] = daemon_status + change_flag = True + + # 检查jar包是否在cmd中 + if project_config['project_cmd'].find(get.project_jar.strip()) == -1: + return json_response(False, '项目jar包名称不在项目启动命令中,请检查') + + if hasattr(get, 'jmx_status') and utils.js_value_to_bool(get.jmx_status) != project_config['jmx_status']: + project_config['jmx_status'] = utils.js_value_to_bool(get.jmx_status) + change_flag = True + + if hasattr(get, "env_file") and get.env_file.strip() != project_config['env_file']: + project_config['env_file'] = get.env_file.strip() + if project_config['env_file'] and not os.path.exists(project_config['env_file']): + return json_response(False, '项目环境文件不存在') + change_flag = True + + if hasattr(get, "env_list"): + env_list = get.env_list + if isinstance(env_list, str): + try: + env_list = json.loads(env_list) + except: + return json_response(False, '项目环境变量格式错误') + if not isinstance(env_list, list): + return json_response(False, '项目环境变量格式错误') + if env_list != project_config['env_list']: + project_config['env_list'] = env_list + change_flag = True + + if hasattr(get, 'watch_file'): + project_config['watch_file'] = utils.js_value_to_bool(get.watch_file) + if project_config['watch_file']: + add_project_watch(p_name=get.project_name.strip(), + p_type="java", + site_id=project_data["id"], + watch_path=get.project_jar.strip()) + else: + del_project_watch(get.project_name.strip()) + + if change_flag: + project_config["change_flag"] = True + + pdata = { + 'name': project_name, + 'path': get.project_jar.strip(), + 'ps': get.project_ps.strip(), + 'project_config': json.dumps(project_config) + } + public.M('sites').where('name=?', (get.project_name,)).update(pdata) + + return json_response(True, '项目修改成功,重启后生效') + + def modify_tomcat_project(self, get, project_data: Optional[dict] = None): + if not project_data: + try: + project_name = self.get_project_find(get.project_name.strip()) + except: + return json_response(status=False, msg="项目名称错误") + if not project_data: + return json_response(status=False, msg="项目不存在") + else: + project_name = project_data['name'] + + project_config = project_data['project_config'] + tomcat = utils.bt_tomcat(project_config["tomcat_version"]) + + flag = False + # 更换JDK + if hasattr(get, 'project_jdk') and get.project_jdk: + jdk_path = utils.normalize_jdk_path(get.project_jdk.strip()) + if not isinstance(jdk_path, str): + return json_response(False, 'JDK检测错误') + if jdk_path != tomcat.jdk_path: + if not utils.test_jdk(jdk_path): + return json_response(False, '当前JDK不可用') + + res = tomcat.replace_jdk(jdk_path) + if not res: + return json_response(False, res) + flag = True + + if hasattr(get, 'project_path'): + if get.project_path.strip() == project_data['path']: + pass + else: + if not tomcat.set_host_path_by_name(project_name, get.project_path.strip()): + return json_response(False, '项目路径设置失败') + project_data['path'] = get.project_path.strip() + + # 更换描述 + if hasattr(get, 'project_ps'): + if get.project_ps.strip() != project_data['ps']: + project_data['ps'] = get.project_ps.strip() + + if flag: + tomcat.restart() + + pdata = { + 'path': project_data['path'], + 'ps': project_data['ps'], + } + public.M('sites').where('name=?', (get.project_name,)).update(pdata) + return json_response(True, '项目修改成功') + + def modify_site_tomcat_project(self, get, project_data: Optional[dict] = None): + if not project_data: + try: + project_name = self.get_project_find(get.project_name.strip()) + except: + return json_response(status=False, msg="项目名称错误") + if not project_data: + return json_response(status=False, msg="项目不存在") + else: + project_name = project_data['name'] + + project_config = project_data['project_config'] + tomcat = utils.site_tomcat(project_name) + if not tomcat: + return json_response(False, '项目的Tomcat已被删除,请尝试修复项目') + + flag = False + # 更换JDK + if hasattr(get, 'project_jdk') and get.project_jdk: + jdk_path = utils.normalize_jdk_path(get.project_jdk.strip()) + if not isinstance(jdk_path, str): + return json_response(False, 'JDK检测错误') + if jdk_path != tomcat.jdk_path: + if not utils.test_jdk(jdk_path): + return json_response(False, '当前JDK不可用') + + res = tomcat.replace_jdk(jdk_path) + if isinstance(res, str): + return json_response(False, res) + flag = True + project_config["project_jdk"] = jdk_path + + if hasattr(get, 'port'): + try: + port = int(get.port) + except: + return json_response(False, '端口信息错误') + + if port != tomcat.port(): + if not utils.check_port(port): + return json_response(False, '端口已被占用') + + res = tomcat.set_port(port) + if not res: + return json_response(False, '端口设置失败') + tomcat.save_config_xml() + flag = True + project_config["port"] = port + + # 更换描述 + if hasattr(get, 'project_ps'): + if get.project_ps.strip() != project_data['ps']: + project_data['ps'] = get.project_ps.strip() + + if hasattr(get, 'project_path'): + if get.project_path.strip() != project_data['path']: + if not tomcat.set_host_path_by_name(project_name, get.project_path.strip()): + return json_response(False, '项目路径设置失败') + project_data['path'] = get.project_path.strip() + flag = True + + if hasattr(get, 'run_user'): + if "run_user" not in project_config: + project_config["run_user"] = "root" + if get.run_user.strip() != project_config['run_user']: + run_user = get.run_user.strip() + if run_user not in [i["username"] for i in RealUser().get_user_list()["data"]]: + return json_response(False, '请输入正确的启动用户') + else: + project_config["run_user"] = run_user + flag = True + + if flag: + tomcat.restart(by_user=project_config["run_user"]) + + pdata = { + 'path': project_data['path'], + 'ps': project_data['ps'], + 'project_config': json.dumps(project_config), + } + public.M('sites').where('name=?', (get.project_name,)).update(pdata) + return json_response(True, '项目修改成功') + + def modify_project(self, get): + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(status=False, msg="参数错误") + if not project_data: + return json_response(status=False, msg="项目不存在") + project_config = project_data["project_config"] + if project_config["java_type"] == "springboot": + return self.modify_spring_boot_project(get, project_data) + elif project_config["java_type"] == "duli": + return self.modify_site_tomcat_project(get, project_data) + else: + return self.modify_tomcat_project(get, project_data) + + def get_project_pid(self, project_data: dict) -> Optional[int]: + # 从pid文件中获取项目pid + project_config = project_data["project_config"] + if project_config["java_type"] == "springboot": + pid_file = project_config["pids"] + pid = None + if os.path.isfile(pid_file): + try: + pid = int(public.readFile(pid_file)) + except: + pass + elif project_config["java_type"] == "neizhi": + pid = utils.bt_tomcat(project_config["tomcat_version"]).pid() + else: + pid = utils.site_tomcat(project_data["name"]).pid() + if not pid and project_config["java_type"] == "springboot": + try: + return self._get_pid_by_command(project_data) + except: + return None + + try: + psutil.Process(pid) + except: + return None + + return pid + + @staticmethod + def _get_pid_by_command(project_data: dict) -> Optional[int]: + project_config = project_data["project_config"] + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + print(server_name) + server_admin = RealServer() + pid = server_admin.get_daemon_pid(server_name)["data"] + if pid == 0: + time.sleep(0.5) + pid = server_admin.get_daemon_pid(server_name)["data"] + if isinstance(pid, int): + try: + p = psutil.Process(pid) + if p.is_running(): + public.writeFile(project_data["project_config"]['pids'], str(pid)) + return pid + except: + pass + + if project_config["java_type"] == "springboot": + jdk_path = utils.normalize_jdk_path(project_config['project_jdk']) + project_jar = project_config['project_jar'] + jar_name = os.path.basename(project_jar) + jar_cmd = project_config['project_cmd'] + port_rep = re.search(r"--server\.port=\d+", jar_cmd) + pids = [] + for pro in psutil.process_iter(['pid', 'exe', 'cmdline']): + if pro.status() == "zombie": + continue + try: + if port_rep and port_rep.group() not in pro.cmdline(): + continue + if jdk_path == utils.normalize_jdk_path(pro.exe()) and any((jar_name in i for i in pro.cmdline())): + pids.append(pro.pid) + except: + pass + + if not pids: + return None + + running_pid = [] + for pid in pids: + if pid in psutil.pids(): + running_pid.append(pid) + + if len(running_pid) == 1: + public.writeFile(project_data["project_config"]['pids'], str(running_pid[0])) + return running_pid[0] + for pid in running_pid: + p = psutil.Process(pid) + if p.ppid() not in running_pid: + public.writeFile(project_data["project_config"]['pids'], str(pid)) + return pid + + return None + + def project_process(self, project_data) -> Optional[psutil.Process]: + pid = self.get_project_pid(project_data) + if not pid: + return None + try: + return psutil.Process(pid) + except: + return None + + def start_spring_boot_project(self, project_data: dict, wait: bool = True): + change_flag = False + project_config = project_data['project_config'] + if "change_flag" in project_config: + change_flag = project_config.get("change_flag", False) + del project_config["change_flag"] + public.M("sites").where("id=?", (project_data["id"],)).update( + {"project_config": json.dumps(project_config)} + ) + res = self._start_spring_boot_project(project_data, change_flag, need_wait=wait) + if res["status"] is True: + utils.start_by_user(project_data["id"]) + return res + + def start_project(self, get): + try: + project_data = self.get_project_find(get.project_name) + except: + return json_response(False, '项目名称参数错误') + + if not project_data: + return json_response(False, '项目不存在') + + p = self.project_process(project_data) + if p and p.is_running(): + return json_response(False, '项目已启动') + + project_config = project_data['project_config'] + if project_config["java_type"] == "springboot": + return self.start_spring_boot_project(project_data) + + elif project_config["java_type"] == "neizhi": + res = utils.bt_tomcat(project_config["tomcat_version"]).start() + if not res: + return json_response(False, '项目启动失败') + else: + tomcat = utils.site_tomcat(project_config["project_name"]) + if not tomcat: + return json_response(False, '独立项目的Tomcat文件丢失,请尝试修复项目') + if not tomcat.running(): + res = tomcat.start() + if not res: + return json_response(False, '项目启动失败') + + utils.start_by_user(project_data["id"]) + return json_response(True, "项目启动成功") + + def stop_project(self, get): + try: + project_data = self.get_project_find(get.project_name) + except: + return json_response(False, '项目名称参数错误') + + if not project_data: + return json_response(False, '项目不存在') + + project_config = project_data['project_config'] + if project_config["java_type"] == "springboot": + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + s_admin = RealServer() + if s_admin.daemon_status(server_name)["msg"] == "服务不存在!": + self.stop_by_kill_pid(project_data) + if os.path.isfile(project_config["pids"]): + os.remove(project_config["pids"]) + else: + s_admin.daemon_admin(server_name, "stop") + utils.stop_by_user(project_data["id"]) + return json_response(True, msg="项目停止指令已执行") + elif project_config["java_type"] == "neizhi": + res = utils.bt_tomcat(project_config["tomcat_version"]).stop() + if not res: + return json_response(False, '项目停止失败') + else: + tomcat = utils.site_tomcat(project_config["project_name"]) + if not tomcat: + return json_response(False, '独立项目的Tomcat文件丢失,请尝试修复项目') + if tomcat.running(): + res = tomcat.stop() + if not res: + return json_response(False, '项目停止失败') + utils.stop_by_user(project_data["id"]) + return json_response(True, "项目停止成功") + + def stop_by_kill_pid(self, project_data): + pid = self.get_project_pid(project_data) + if not pid: + return + try: + p = psutil.Process(pid) + p.kill() + except: + pass + + def restart_project(self, get): + try: + project_data = self.get_project_find(get.project_name) + except: + return json_response(False, '项目名称参数错误') + + if not project_data: + return json_response(False, '项目不存在') + + project_config = project_data['project_config'] + if project_config["java_type"] == "springboot": + s_admin = RealServer() + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + if s_admin.daemon_status(server_name)["msg"] == "服务不存在!": + self.stop_by_kill_pid(project_data) + if os.path.isfile(project_config["pids"]): + os.remove(project_config["pids"]) + return self._start_spring_boot_project(project_data, write_systemd_file=True) + + if "change_flag" in project_config and project_config.get("change_flag", False): + del project_config["change_flag"] + s_admin.daemon_admin(server_name, "stop") + s_admin.del_daemon(server_name) + self.stop_by_kill_pid(project_data) + if os.path.isfile(project_config["pids"]): + os.remove(project_config["pids"]) + + public.M("sites").where("id=?", (project_data["id"],)).update( + {"project_config": json.dumps(project_config)} + ) + return self._start_spring_boot_project(project_data, write_systemd_file=True) + else: + return self._start_spring_boot_project(project_data, write_systemd_file=False) + + self.stop_project(get) + time.sleep(0.5) + self.start_project(get) + + return json_response(True, "项目重启已执行") + + def project_list(self, get): + """取项目列表""" + p = 1 + limit = 12 + callback = "" + order = "id desc" + search = "" + type_id = None + try: + if hasattr(get, "p"): + p = int(get.p) + if hasattr(get, "limit"): + limit = int(get.limit) + if hasattr(get, "callback"): + callback = get.callback.strip() + if hasattr(get, "order"): + order = get.order.strip() + if hasattr(get, "search"): + search = get.search.strip() + if hasattr(get, "type_id") and get.type_id: + type_id = int(get.type_id) + except: + return json_response(False, '参数错误') + + type_filter = '' + if type_id is not None: + type_filter = ' AND type_id=?' + + if search: + search = "%{}%".format(search) + if type_filter: + where_str = 'project_type=? AND (name LIKE ? OR ps LIKE ?)' + type_filter + where_args = ('Java', search, search, type_id) + else: + where_str = 'project_type=? AND (name LIKE ? OR ps LIKE ?)' + where_args = ('Java', search, search) + count = public.M('sites').where(where_str, where_args).count() + + data = public.get_page(count, p, limit, callback) + data['data'] = public.M('sites').where(where_str, where_args).limit( + data['shift'] + ',' + data['row']).order(order).select() + else: + if type_filter: + where_str = 'project_type=?' + type_filter + where_args = ('Java', type_id) + else: + where_str = 'project_type=?' + where_args = ('Java',) + count = public.M('sites').where(where_str, where_args).count() + data = public.get_page(count, p, limit, callback) + data['data'] = public.M('sites').where(where_str, where_args).limit( + data['shift'] + ',' + data['row']).order(order).select() + + for project_data in data['data']: + project_config = json.loads(project_data['project_config']) + project_data['project_config'] = project_config + + # 如果内置项目 或 独立项目 的tomcat配置文件丢失 + if project_config['java_type'] == 'neizhi' or project_config['java_type'] == 'duli': + if not os.path.exists(project_config['server_xml']): + + project_data['server_file_status'] = False + else: + project_data['server_file_status'] = True + + for i in range(len(data['data'])): + self.get_project_stat(data['data'][i]) + + return data + + def get_project_stat(self, project_data: dict) -> dict: + if isinstance(project_data['project_config'], str): + project_config = json.loads(project_data['project_config']) + project_data['project_config'] = project_config + + project_data["pid"] = self.get_project_pid(project_data) + if project_data["project_config"]["java_type"] == "springboot": + project_data["project_config"]["watch_file"] = use_project_watch(project_data["name"]) + project_data["listen"] = [] + project_data["ssl"] = RealSSLManger("java_").get_site_ssl_info(project_data["name"]) + if not project_data["ssl"]: + project_data["ssl"] = -1 + if project_data["pid"]: + project_data["pid_info"] = self.real_process.get_process_info_by_pid(project_data["pid"])["data"] + listen = [] + if project_data["pid_info"] and "connections" in project_data["pid_info"]: + for connection in project_data["pid_info"]["connections"]: + if connection["status"] == "LISTEN": + listen.append(connection["local_port"]) + project_data["listen"] = listen + if project_data['project_config']["java_type"] == "springboot": + project_data['project_config']["port"] = ",".join([str(i) for i in listen]) + else: + project_data["pid_info"] = None + + project_data["starting"] = False + if project_data["pid"] and not project_data["listen"]: + project_data["starting"] = True + + if os.path.exists("{}/nginx/{}.conf".format(self._vhost_path, project_data["name"])) or \ + os.path.exists("{}/apache/{}.conf".format(self._vhost_path, project_data["name"])): + + project_data["bind_extranet"] = True + else: + project_data["bind_extranet"] = False + + if project_data['project_config']["java_type"] == "duli" and "project_jdk" not in project_data['project_config']: + tomcat = utils.site_tomcat(project_data["name"]) + if tomcat: + project_data['project_config']["project_jdk"] = tomcat.jdk_path + else: + project_data['project_config']["project_jdk"] = "/usr/local/btjdk/jdk8" + + return project_data + + @staticmethod + def _project_domain_list(project_id: int): + return public.M('domain').where('pid=?', (project_id,)).select() + + def project_domain_list(self, get): + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, '指定项目不存在') + + data = self._project_domain_list(project_data['id']) + return json_response(True, data=data) + + def add_domains(self, get): + """ 为指定项目添加域名 """ + try: + if isinstance(get.domains, str): + domains = json.loads(get.domains) + else: + domains = get.domains + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not isinstance(domains, list): + return json_response(False, '域名参数错误') + + if not project_data: + return json_response(False, '指定项目不存在') + + project_id = project_data['id'] + project_name = project_data["name"] + + domain_list, err = normalize_domain(*domains) + if err: + return "
                                    ".join(["域名:{},错误信息:{}".format(i['domain'], i['msg']) for i in err]) + + res_domains = [] + for d, p in domain_list: + if not public.M('domain').where('name=?', (d,)).count(): + public.M('domain').add('name,pid,port,addtime', (d, project_id, p, public.getDate())) + self.write_project_log('成功添加域名{}到项目{}'.format(d, get.project_name)) + res_domains.append({"name": d, "status": True, "msg": '添加成功'}) + else: + self.write_project_log('添加域名错误,域名{}已存在'.format(d)) + res_domains.append({"name": d, "status": False, "msg": '添加失败,域名{}已存在'.format(d)}) + all_domain = self._project_domain_list(project_id) + print(all_domain) + # 写配置文件 + if utils.js_value_to_bool(project_data["project_config"]["bind_extranet"]): + res = self._set_domain(project_data, [(i["name"], str(i["port"])) for i in all_domain]) + if res: + return json_response(True, msg="域名记录成功,但配置文件写入时失败:" + res, data=res_domains) + return json_response(True, msg="添加成功", data=res_domains) + + def remove_domains(self, get): + """ 移除指定项目中的域名 """ + try: + if isinstance(get.domains, str): + remove_list = json.loads(get.domains) + else: + remove_list = get.domains + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, '指定项目不存在') + + if not isinstance(remove_list, list): + return json_response(False, '域名参数错误') + + all_domain = self._project_domain_list(project_data["id"]) + if not all_domain: + return json_response(False, '指定项目中没有域名可被删除') + + bind_extranet = False + if os.path.exists("{}/nginx/{}.conf".format(self._vhost_path, project_data["name"])) or \ + os.path.exists("{}/apache/{}.conf".format(self._vhost_path, project_data["name"])): + bind_extranet = True + + if len(all_domain) == 1 and bind_extranet: + return json_response(False, '请至少保留一个域名,如无需外网映射,关闭即可', data=[{ + "domain": all_domain[0]["name"], + "status": False, + "msg": "无法删除最后一个域名" + }]) + + all_domain.sort(key=lambda x: x["id"], reverse=True) + all_domain_id_dict = {i["id"]: i for i in all_domain} + + del_id_list = [i for i in all_domain_id_dict if i in remove_list] + + res_data = [] + # 说明选中了所有域名,这时需要保持一个默认域名 + default_domain = None + if len(all_domain_id_dict) == len(del_id_list) and bind_extranet: + default_domain = all_domain[0] + del_id_list.remove(default_domain["id"]) + + for i in del_id_list: + public.M('domain').delete(id=i) + res_data.append( + { + "domain": all_domain_id_dict[i]["name"], + "status": True, + "msg": "删除成功" + } + ) + self.write_project_log('成功删除域名{}'.format([all_domain_id_dict[i]["name"] for i in del_id_list])) + if default_domain: + res_data.append( + { + "domain": default_domain["name"], + "status": False, + "msg": "无法删除最后一个域名" + } + ) + + if bind_extranet: + now_domain = self._project_domain_list(project_data["id"]) + # 写配置文件 + res = self._set_domain(project_data, [(i["name"], str(i["port"])) for i in now_domain]) + if res: + return json_response(False, msg="域名记录删除成功,但配置文件写入时失败:" + res, data=res_data) + return json_response(True, msg="删除成功", data=res_data) + + def bind_extranet(self, get): + """开放外网映射""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + + if not public.is_apache_nginx(): + return json_response(False, "未安装Nginx") + + err_msg = public.checkWebConfig() + if isinstance(err_msg, str): + msg = 'WEB服务器配置配置文件错误ERROR:
                                    ' + \ + err_msg.replace("\n", '
                                    ') + '
                                    ' + return json_response(False, msg=msg) + + res = self._open_config_file(project_data) + if isinstance(res, str): + return json_response(False, msg=res) + project_config = project_data["project_config"] + project_config["bind_extranet"] = 1 + public.M('sites').where('id=?', (project_data["id"],)).update( + {"project_config": json.dumps(project_config)} + ) + return json_response(True, msg="设置成功") + + def unbind_extranet(self, get): + """关闭外网映射""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + + project_config = project_data["project_config"] + self._close_apache_config_file(project_data) + project_config["bind_extranet"] = 0 + public.M('sites').where('id=?', (project_data["id"],)).update( + {"project_config": json.dumps(project_config)} + ) + return json_response(True, msg="设置成功") + + def remove_project(self, get): + """删除指定项目""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + + project_config = project_data["project_config"] + project_name = project_data["name"] + if project_config['java_type'] == 'duli': + tomcat = utils.site_tomcat(project_name) + # 关闭独立项目 + if tomcat: + tomcat.stop() + if os.path.exists(tomcat.path): + shutil.rmtree(tomcat.path) + + elif project_config['java_type'] == 'neizhi': + # 删除tomcat站点 + tomcat = utils.bt_tomcat(project_config["tomcat_version"]) + tomcat.remove_host(project_name) + tomcat.save_config_xml() + if tomcat.running(): + tomcat.restart() + + elif project_config['java_type'] == 'springboot': + # 停止项目 + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + s_admin = RealServer() + s_admin.daemon_admin(server_name, "stop") + s_admin.del_daemon(server_name) + + pid_file = project_config['pids'] + if os.path.exists(pid_file): + os.remove(pid_file) + script_file = project_config['scripts'] + if os.path.exists(script_file): + os.remove(script_file) + env_path = "{}/env/{}.env".format(self._java_project_vhost, project_config["project_name"]) + if os.path.exists(env_path): + os.remove(env_path) + log_file = project_config['logs'] + if os.path.exists(log_file): + os.remove(log_file) + else: + return json_response(False, '项目类型错误') + + threading.Thread(target=remove_sites_service_config, args=(project_name, "java_")).start() + public.M('domain').where('pid=?', (project_data['id'],)).delete() + public.M('sites').where('name=?', (project_name,)).delete() + self.write_project_log('删除Java项目{}'.format(get.project_name)) + return json_response(True, '删除项目成功') + + def config_file_list(self, get): + """获取配置文件列表""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + project_name = project_data["name"] + res_list = [] + bind_extranet = int(project_data["project_config"]["bind_extranet"]) != 0 + if bind_extranet: + if public.get_webserver() == "nginx": + config_file = "{}/nginx/java_{}.conf".format(self._vhost_path, project_name) + res_list.append( + { + "name": "nginx配置文件", + "path": config_file, + "status": os.path.exists(config_file), + "type": "server" + } + ) + else: + config_file = "{}/apache/java_{}.conf".format(self._vhost_path, project_name) + res_list.append( + { + "name": "apache配置文件", + "path": config_file, + "status": os.path.exists(config_file), + "type": "server" + } + ) + rewrite_file = "{}/rewrite/java_{}.conf".format(self._vhost_path, project_name) + res_list.append( + { + "name": "伪静态配置文件", + "path": rewrite_file, + "status": os.path.exists(rewrite_file), + "type": "rewrite" + } + ) + + p_list = RealProxy("java_").get_proxy_list(public.to_dict_obj({"sitename": project_name})) + f, r_list = RealRedirect("java_").get_redirect_list(public.to_dict_obj({"sitename": project_name})) + for p in p_list: + res_list.append({ + "name": "反向代理【{}】".format(p["proxyname"]), + "path": p["proxy_conf_file"], + "status": os.path.exists(p["proxy_conf_file"]), + "type": "proxy" + }) + + if f: + for r in r_list: + res_list.append({ + "name": "重定向", + "path": r["redirect_conf_file"], + "status": os.path.exists(r["redirect_conf_file"]), + "type": "redirect" + }) + + if project_data["project_config"]["java_type"] == "neizhi": + tomcat = utils.bt_tomcat(project_data["project_config"]["tomcat_version"]) + res_list.append({ + "name": "Tomcat【{}】配置文件".format(project_data["project_config"]["tomcat_version"]), + "path": os.path.join(tomcat.path, "conf/server.xml"), + "status": tomcat.installed, + "type": "tomcat" + }) + elif project_data["project_config"]["java_type"] == "duli": + tomcat = utils.site_tomcat(project_data["name"]) + res_list.append({ + "name": "Tomcat配置文件", + "path": os.path.join(tomcat.path, "conf/server.xml"), + "status": tomcat.installed, + "type": "tomcat" + }) + else: + t = time.time() + pid = self.get_project_pid(project_data) + if not pid: + pid = 0 + spc = SpringConfigParser( + jar_path=project_data["project_config"]["project_jar"], + process=pid, + ) + used, _ = spc.app_config() + for i, _ in used: + if i == "命令行或环境变量": + continue + profile = os.path.basename(i)[len(spc.config_name):] # 取出 - + profile + 后缀部分 + if profile in (".yml", ".yaml", ".properties"): + profile = "" + else: + profile = profile.rsplit(".", 1)[0] + res_list.append({ + "name": "Spring配置" + profile, + "path": i, + "status": True, + "type": "local_spring" if i.startswith("/") else "spring", + "data": spc.raw_data.get(i, "") + }) + + sort_tuple = { + "local_spring": 10, + "spring": 9, + "tomcat": 8, + "server": 7, + "proxy": 6, + "redirect": 5, + "rewrite": 4, + + } + res_list.sort(key=lambda x: sort_tuple[x["type"]], reverse=True) + return json_response(True, data=res_list) + + # 未使用 + def project_log_list(self, get): + """获取日志文件列表""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + project_name = project_data["name"] + res = RealLogMgr(conf_prefix="java_").get_site_log_path(public.to_dict_obj({"sitename": project_name})) + res_list = [] + if isinstance(res, str): + res_list.extend([ + {"type": "access", "path": None, "log_size": 0, "msg": "无法从配置文件中获取日志文件路径"}, + {"type": "error", "path": None, "log_size": 0, "msg": "无法从配置文件中获取错误日志文件路径"} + ]) + else: + access_file = res["log_file"] + error_file = res["error_log_file"] + access_file_size = error_file_size = 0 + if os.path.isfile(access_file): + access_file_size = os.path.getsize(access_file) + + if os.path.isfile(error_file): + error_file_size = os.path.getsize(error_file) + + res_list.extend([ + {"type": "access", "path": access_file, "log_size": access_file_size, "msg": ""}, + {"type": "error", "path": error_file, "log_size": error_file_size, "msg": ""} + ]) + + project_config = project_data["project_config"] + if project_config["java_type"] == "springboot": + log_file = project_config['logs'] + if not os.path.isfile(log_file): + pass + + def get_command(self, get): + """获取命令, 包含设置和取消jmx的设置""" + project_cmd = None + jmx_status = None + try: + if hasattr(get, "project_cmd"): + project_cmd = get.project_cmd.strip() + if hasattr(get, "jmx_status"): + jmx_status = utils.js_value_to_bool(get.jmx_status) + project_jdk = utils.normalize_jdk_path(get.project_jdk.strip()) + project_jar = get.project_jar.strip() + except json.JSONDecodeError: + return json_response(False, '参数错误') + + if not isinstance(project_jdk, str): + return json_response(False, '项目JDK路径不存在') + + project_jdk = os.path.join(project_jdk, "bin/java") + if not os.path.isfile(project_jdk): + return json_response(False, '项目JDK不存在') + if not os.path.isfile(project_jar): + return json_response(False, '项目jar不存在') + + if not isinstance(project_cmd, str) and project_cmd: + cmd = '{} -jar {} -Xmx1024M -Xms256M'.format(project_jdk, project_jar) + cmd = self._build_jmx_cmd(cmd, jmx_status) + + else: + cmd = self._build_jmx_cmd(project_cmd, jmx_status) + + return json_response(True, data=cmd) + + def set_project_log_status(self, get): + """设置项目日志是否开启""" + try: + project_data = self.get_project_find(get.project_name.strip()) + status = utils.js_value_to_bool(get.status) + except: + return json_response(False, '参数错误') + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + + if project_data["project_config"]["java_type"] == "springboot": + project_config = project_data["project_config"] + project_config["nohup_log"] = status + project_config["change_flag"] = True + pdata = { + 'project_config': json.dumps(project_config) + } + public.M('sites').where('id=?', (project_data["id"],)).update(pdata) + + return json_response(True, "修改成功, 重启项目后生效") + else: + return json_response(False, "非springboot项目无法关闭") + + def get_jmx_status(self, get): + """设置项目日志是否开启""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + + project_config = project_data["project_config"] + if not project_config["java_type"] == "springboot": + return json_response(False, "目前支持springboot 项目的jmx 监控") + + pid = self.get_project_pid(project_data) + if not pid: + return json_response(False, "未启动的项目,不能获取jmx信息") + + jmx_info = self.get_jmx_data_by_pid(pid) + if not jmx_info: + return json_response(False, "项目未启用jmx,无法获取jmx信息") + jmx_url = 'service:jmx:rmi:///jndi/rmi://{}:{}/jmxrmi'.format(jmx_info["host"], jmx_info["port"]) + + from mod.project.java.jmxquery import JMXConnection, JMXQuery + try: + # 创建 JMXConnection + # JMX 连接信息 + jmxConnection = JMXConnection( + connection_uri=jmx_url, + java_path=os.path.join(project_config["project_jdk"], "bin/java") + ) + jmxQuery = [JMXQuery("*:*")] + # 执行查询 + metrics = jmxConnection.query(jmxQuery) + except Exception: + public.print_log(public.get_error_info()) + return json_response(False, "连接失败! {}".format(jmx_url)) + + # 创建 JMX 查询 + jmx_status_info = {} + type_list = ["MemoryPool", "GarbageCollector"] + percent_list = ["SystemCpuLoad", "ProcessCpuLoad"] + microsecond_list = [ + "StartTime", "Uptime", "endTime", "startTime", "CollectionTime", "CurrentThreadCpuTime", + "CurrentThreadUserTime", "endTime", "startTime", "CollectionTime", "TotalCompilationTime", + ] + nanoseconds_list = ["ProcessCpuTime", ] + size_list = ["FreePhysicalMemorySize", "TotalPhysicalMemorySize", "committed", "init", "max", "used"] + value_dict = { + "True": "是", + "False": "否", + "None": "无", + } + + # 解析结果 + for metric in metrics: + java_type_obj = re.search(r"type=([\w\s]+)", metric.mBeanName) + if not java_type_obj: + continue + java_type = java_type_obj.group(1) + name_obj = re.search(r"name=([\w\s]+)", metric.mBeanName) + name = None + if name_obj: + name = name_obj.group(1) + + if jmx_status_info.get(java_type) is None: + if java_type in type_list: + jmx_status_info[java_type] = [] + else: + jmx_status_info[java_type] = {} + type_info: Union[Dict[str, Any], List[Dict[str, Any]]] = jmx_status_info[java_type] + + if name is not None: + name = name.replace(" ", "_") + if isinstance(type_info, list): + for info in type_info: + if info["name"] == name: + type_info = info + break + else: + info = {"name": name} + type_info.append(info) + type_info = info + else: + if type_info.get(name) is None: + type_info[name] = {} + type_info = type_info[name] + + value = value_dict.get(str(metric.value)) + if value is None: + value = metric.value + + if metric.attributeKey: + if metric.attribute is not None and type_info.get(metric.attribute) is None: + type_info[metric.attribute] = {} + + if value == -1: + value = "无限制" + elif metric.attributeKey in size_list: + value = public.to_size(value) + elif metric.attributeKey in microsecond_list: + value = datetime.fromtimestamp(int(value) / 1000).strftime('%Y-%m-%d %H:%M:%S') + elif metric.attributeKey in nanoseconds_list: + value = "{} 秒".format(int(value) / 1e9) + + type_info[metric.attribute][metric.attributeKey] = value + else: + if metric.attribute in size_list: + value = public.to_size(value) + elif metric.attribute in percent_list: + value = "{}%".format(round(int(value) * 100, 2)) + elif metric.attribute in microsecond_list: + value = datetime.fromtimestamp(int(value) / 1000).strftime('%Y-%m-%d %H:%M:%S') + elif metric.attribute in nanoseconds_list: + value = "{} 秒".format(int(value) / 1e9) + type_info[metric.attribute] = value + + return json_response(True, data=jmx_status_info) + + @staticmethod + def get_jmx_data_by_pid(pid) -> Optional[dict]: + try: + p = psutil.Process(pid) + cmd_line = p.cmdline() + except: + return None + + data = { + "port": "", + "host": "127.0.0.1" + } + for i in cmd_line: + if i.startswith("-Dcom.sun.management.jmxremote.port"): + data["port"] = i.split("=")[1] + + if i.startswith("-Djava.rmi.server.hostname"): + data["host"] = i.split("=")[1] + if not data["port"]: + return None + return data + + def get_project_info(self, get): + """设置项目日志是否开启""" + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.project_name)) + + project_data = self.get_project_stat(project_data) + return json_response(True, data=project_data) + + # 上传版本 + def upload_version(self, get): + """ + 上传压缩包并存储为版本 + """ + if not hasattr(get, 'sitename'): + return json_response(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return json_response(False, '版本号不能为空') + if not hasattr(get, 'ps'): + get.ps = '' + try: + upload_files = os.path.join("/tmp", get.f_name) + if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + + from files import files + + file_obj = files() + ff = file_obj.upload(get) + if type(ff) == int: + return ff + if not ff['status']: + return json_response(False, ff['msg']) + + output_dir = str(os.path.join('/tmp', public.GetRandomString(16))) + os.makedirs(output_dir, 777) + is_jar_war = os.path.splitext(upload_files)[-1] in (".jar", ".war") + if not self.extract_archive(upload_files, output_dir)[0] and not is_jar_war: + return json_response(False, '解压失败,仅支持zip,tar.gz,tar,bz2,gz格式的压缩包') + + version_tool = VersionTool() + if not is_jar_war: + if len(os.listdir(output_dir)) == 1: + real_output_dir = str(os.path.join(output_dir, os.listdir(output_dir)[0])) + if os.path.isdir(real_output_dir): + output_dir = real_output_dir + + res = version_tool.publish_by_src_path(get.sitename, output_dir, get.version, get.ps, sync=True) + public.ExecShell('rm -rf {}'.format(output_dir)) + public.ExecShell('rm -rf {}'.format(upload_files)) + else: + res = version_tool.publish_by_file(get.sitename, upload_files, get.version, get.ps) + public.ExecShell('rm -rf {}'.format(upload_files)) + if res is None: + return public.returnResult(True, '添加成功') + return json_response(False, '添加失败' + res) + except: + return json_response(False, traceback.format_exc()) + + @staticmethod + def extract_archive(file_path, output_dir): + try: + import tarfile + import zipfile + import gzip + import bz2 + + name = os.path.basename(file_path) + if name.endswith('.tar.gz'): + with tarfile.open(file_path, 'r:gz') as tar: + tar.extractall(output_dir) + elif name.endswith('.zip'): + with zipfile.ZipFile(file_path, 'r') as zip_ref: + zip_ref.extractall(output_dir) + elif name.endswith('.gz'): + with gzip.open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-3]), 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + elif name.endswith('.tar'): + with tarfile.open(file_path, 'r') as tar: + tar.extractall(output_dir) + elif name.endswith('.bz2'): + with open(file_path, 'rb') as f_in, open(os.path.join(output_dir, name[:-4]), 'wb') as f_out: + with bz2.BZ2File(f_in) as bz: + shutil.copyfileobj(bz, f_out) + else: + return False, '文件格式错误.' + except: + return False, '解压失败' + + return True, '解压成功' + + # 获取列表 + @staticmethod + def get_version_list(get): + if not hasattr(get, 'sitename'): + return public.returnResult(False, '项目名称不能为空') + version_tool = VersionTool() + return json_response(True, data=version_tool.version_list(get.sitename)) + + # 删除版本 + @staticmethod + def remove_version(get): + if not hasattr(get, 'sitename'): + return json_response(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return json_response(False, '版本号不能为空') + version_tool = VersionTool() + if version_tool.remove(get.sitename, get.version) is None: + return public.returnResult(True, '删除成功') + return json_response(False, '删除失败') + + # 恢复版本 + def recover_version(self, get): + try: + if not hasattr(get, 'sitename'): + return json_response(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return json_response(False, '版本号不能为空') + + project_data = self.get_project_find(get.sitename) + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.sitename)) + + version_tool = VersionTool() + project_config = project_data['project_config'] + # spring jar 包情况 + if project_config["java_type"] == "springboot": + v_info = version_tool.get_version_info(get.sitename, get.version) + if not v_info: + return json_response(False, '指定版本不存在: {}'.format(get.version)) + + path = os.path.join(version_tool.pack_path, v_info['zip_name']) + if not os.path.isfile: + return json_response(False, '指定版本文件丢失') + if os.path.exists(project_config["project_jar"] + "_back"): + os.remove(project_config["project_jar"] + "_back") + if not path.endswith(".jar"): # 如果不是jar包,则解压并寻找jar包 + output_dir = str(os.path.join('/tmp', public.GetRandomString(16))) + os.makedirs(output_dir, 777) + if not self.extract_archive(path, output_dir)[0]: + return json_response(False, '解压失败') + else: + for f in os.listdir(output_dir): + if f.endswith(".jar"): + path = os.path.join(output_dir, f) + break + else: + return json_response(False, '未找到jar包文件') + + shutil.move(project_config["project_jar"], project_config["project_jar"] + "_back") + shutil.copyfile(path, project_config["project_jar"]) + + return json_response(True, "版本文件替换成功,重启后生效") + # war 包 + v_info = version_tool.get_version_info(get.sitename, get.version) + if not v_info: + return json_response(False, '指定版本不存在: {}'.format(get.version)) + path = os.path.join(version_tool.pack_path, v_info['zip_name']) + if path.endswith(".war") and os.path.isfile(project_data["path"]): # 如果是war包 + if os.path.exists(project_data["path"] + "_back"): + os.remove(project_data["path"] + "_back") + shutil.move(project_data["path"], project_data["path"] + "_back") + shutil.copyfile(path, project_data["path"]) + return json_response(True, "版本文件替换成功,重启后生效") + + res = version_tool.recover(get.sitename, get.version, project_data['path']) + if res is not True: + return json_response(False, res) + return json_response(True, '恢复成功') + except: + return json_response(False, traceback.format_exc()) + + def now_file_backup(self, get): + try: + if not hasattr(get, 'sitename'): + return json_response(False, '项目名称不能为空') + if not hasattr(get, 'version') or not get.version.strip(): + return json_response(False, '版本号不能为空') + else: + get.version = get.version.strip() + if not hasattr(get, 'ps'): + get.ps = '' + project_data = self.get_project_find(get.sitename) + if not project_data: + return json_response(False, '指定项目不存在: {}'.format(get.sitename)) + path = project_data['path'] + version_tool = VersionTool() + project_config = project_data['project_config'] + if project_config["java_type"] == "springboot": + res = version_tool.publish_by_file(get.sitename, project_config["project_jar"], get.version, get.ps) + else: + if os.path.isdir(path): + res = version_tool.publish_by_src_path(get.sitename, path, get.version, get.ps, sync=True) + elif os.path.isfile(path): + res = version_tool.publish_by_file(get.sitename, path, get.version, get.ps) + else: + return json_response(False, '添加失败') + if res is None: + return json_response(True, '添加成功') + return json_response(False, '添加失败' + res) + except: + return json_response(False, traceback.format_exc()) + + @staticmethod + def set_version_ps(get): + if not hasattr(get, 'sitename'): + return json_response(False, '项目名称不能为空') + if not hasattr(get, 'version'): + return json_response(False, '版本号不能为空') + if not hasattr(get, 'ps'): + get.ps = '' + version_tool = VersionTool() + res = version_tool.set_ps(get.sitename, get.version, get.ps) + if not res: + return json_response(False, '设置失败') + return json_response(True, '设置成功') + + def change_log_path(self, get): + """"修改日志文件地址 + @author baozi <202-03-13> + @param: + get ( dict ): 请求: 包含项目名称和新的路径 + @return + """ + try: + project_data = self.get_project_find(get.project_name.strip()) + new_log_path = get.path.strip().rstrip("/") + except: + return json_response(False, '参数错误') + if not project_data: + return json_response(False, '项目不存在') + if not new_log_path.startswith('/'): + return json_response(False, '路径格式错误') + if not os.path.exists(new_log_path): + os.makedirs(new_log_path, mode=0o777) + + project_config = project_data["project_config"] + if project_config['java_type'] == 'springboot': + project_config['logs'] = new_log_path + '/' + project_data["name"] + '.log' + pdata = { + 'name': project_data["name"], + 'project_config': json.dumps(project_config) + } + public.M('sites').where('id=?', (project_data["id"],)).update(pdata) + # 重启项目 + res = self.restart_project(get) + self.write_project_log('修改Java项目{}日志路径为:{}'.format(get.project_name, new_log_path)) + return json_response(True, "项目日志路径修改成功") + elif project_config['java_type'] == 'duli': + project_config['logs'] = new_log_path + '/' + tomcat = utils.site_tomcat(project_data["name"]) + if not tomcat.change_log_path(new_log_path, project_data["name"].replace(".", "_")): + return public.returnMsg(False, "项目日志路径修改失败") + pdata = { + 'name': project_data["name"], + 'project_config': json.dumps(project_config) + } + public.M('sites').where('name=?', (get.project_name.strip(),)).update(pdata) + # 重启项目 + tomcat.restart() + self.write_project_log('修改Java项目{}日志路径为:{}'.format(get.project_name, new_log_path)) + return json_response(True, "项目日志路径修改成功") + + elif project_config['java_type'] == 'neizhi': + tomcat = utils.bt_tomcat(project_config['tomcat_version']) + if not tomcat.change_log_path(new_log_path, str(project_config['tomcat_version'])): + return public.returnMsg(False, "项目日志路径修改失败") + + tomcat.restart() + self.write_project_log('修改Java项目{}日志路径为:{}'.format(get.project_name, new_log_path)) + return json_response(True, "项目日志路径修改成功") + else: + return json_response(False, "项目类型错误") + + def multi_remove_project(self, get): + """ + @name 批量删除项目 + @author baozi<2023-3-2> + @param get{ + project_names: list[string] <项目名称>所组成的列表 + } + @return dict + """ + try: + project_names = get.project_names + if isinstance(project_names, list): + project_names = [i.strip() for i in project_names] + else: + project_names = [] + except: + return json_response(False, "参数错误") + if not project_names: + return json_response(False, "未选中要删除的站点") + + projects = public.M('sites').where( + 'project_type=? AND name in ({})'.format(",".join(["?"] * len(project_names))), + ('Java', *project_names)).select() + + if not projects: + return public.returnMsg(False, "未选中要删除的站点") + + _duli, _neizh, _springboot = [], [], [] + for project in projects: + project['project_config'] = json.loads(project['project_config']) + if project['project_config']['java_type'] == 'duli': + _duli.append(project) + elif project['project_config']['java_type'] == 'neizhi': + _neizh.append(project) + elif project['project_config']['java_type'] == 'springboot': + _springboot.append(project) + + # 执行每种删除的独特操作 + if _duli: + self._multi_remove_duli(_duli) + if _neizh: + self._multi_remove_neizhi(_neizh) + if _springboot: + self._multi_remove_springboot(_springboot) + + # 清除Nginx, Apache 配置文件,并重起服务 + for i in projects: + remove_sites_service_config(i["name"], config_prefix="java_") + + # 从面板数据库删除信息 + project_ids = tuple([i["id"] for i in projects]) + public.M('domain').where('pid IN ({})'.format(",".join(["?"] * len(projects))), project_ids).delete() + public.M('sites').where('id IN ({})'.format(",".join(["?"] * len(project_ids))), project_ids).delete() + self.write_project_log('批量删除Java项目:[{}]'.format("; ".join([i["name"] for i in projects]))) + + for project in projects: + self.del_crontab(project["name"]) + + return json_response(True, "删除项目成功", data=[i["name"] for i in projects]) + + @staticmethod + def _multi_remove_duli(projects): + for project in projects: + # 关闭独立项目 + tomcat = utils.site_tomcat(project["name"]) + if tomcat: + tomcat.stop() + if os.path.exists(tomcat.path): + shutil.rmtree(tomcat.path) + + @staticmethod + def _multi_remove_neizhi(projects): + used_tomcat: Dict[int, utils.TomCat] = {} + for project in projects: + ver = int(project['project_config']['tomcat_version']) + if ver in used_tomcat: + tomcat = used_tomcat[ver] + else: + tomcat = utils.bt_tomcat(ver) + if not tomcat: + continue + used_tomcat[ver] = tomcat + + tomcat.remove_host(project["name"]) + + for t in used_tomcat.values(): + t.save_config_xml() + t.restart() + + def _multi_remove_springboot(self, projects): + for project in projects: + # 停止项目 + server_name = "spring_" + project["name"] + project.get("server_name_suffix", "") + s_admin = RealServer() + s_admin.daemon_admin(server_name, "stop") + s_admin.del_daemon(server_name) + + project_config = project["project_config"] + pid_file = project_config['pids'] + if os.path.exists(pid_file): + os.remove(pid_file) + script_file = project_config['scripts'] + if os.path.exists(script_file): + os.remove(script_file) + env_path = "{}/env/{}.env".format(self._java_project_vhost, project_config["project_name"]) + if os.path.exists(env_path): + os.remove(env_path) + log_file = project_config['logs'] + if os.path.exists(log_file): + os.remove(log_file) + + def multi_set_project(self, get): + """ + @name 批量设置项目 + @author baozi<2023-3-2> + @param get{ + project_names: list[string] <项目名称>所组成的列表 + } + @return dict + """ + try: + project_names = get.project_names + set_type = get.operation.strip() + except: + return json_response(False, "参数错误") + if set_type not in ["start", "stop"]: + return public.returnMsg(False, "操作信息错误") + if isinstance(project_names, list): + project_names = [i.strip() for i in project_names] + else: + project_names = [] + + projects = public.M('sites').where( + 'project_type=? AND name in ({})'.format(",".join(["?"] * len(project_names))), + ('Java', *project_names) + ).select() + + if not projects: + return json_response(False, "未选中要启动的站点") + + project_names = [i["name"] for i in projects] + spring_boot_projects = [] + duli_tomcat = [] + bt_tomcat = {} + error_list = [] + for project in projects: + project['project_config'] = json.loads(project['project_config']) + if project['project_config']['java_type'] == 'neizhi': + ver = int(project['project_config']['tomcat_version']) + if ver not in bt_tomcat: + tomcat = utils.bt_tomcat(project['project_config']['tomcat_version']) + if not tomcat: + error_list.append({"project_name": project["name"], "msg": "启动失败,没有安装Tomcat{}".format( + project['project_config']['tomcat_version'])}) + project_names.remove(project["name"]) + continue + bt_tomcat[ver] = tomcat + + if project['project_config']['java_type'] == 'duli': + tomcat = utils.site_tomcat(project["name"]) + if tomcat: + duli_tomcat.append(tomcat) + + if project['project_config']['java_type'] == 'springboot': + spring_boot_projects.append(project) + + for t in itertools.chain(bt_tomcat.values(), duli_tomcat): + if set_type == "start": + t.start() + else: + t.stop() + + for i in spring_boot_projects: + if set_type == "start": + self.start_project(public.to_dict_obj({"project_name": i["name"]})) + else: + self.stop_project(public.to_dict_obj({"project_name": i["name"]})) + + if error_list: + return json_response(True, msg="部分项目操作失败", data={ + "error_list": error_list, + "project_names": project_names + }) + return json_response(True, msg="启动成功" if set_type == "start" else "停止成功", data={ + "project_names": project_names + }) + + @staticmethod + def del_crontab(project_name: str): + """ + @name 删除项目日志切割任务 + @auther hezhihong<2022-10-31> + @return + """ + cron_name = '[勿删]Java项目[{}]运行日志切割'.format(project_name) + cron_path = public.GetConfigValue('setup_path') + '/cron/' + cron_list = public.M('crontab').where("name=?", (cron_name,)).select() + if cron_list: + for i in cron_list: + if not i: continue + cron_echo = public.M('crontab').where("id=?", (i['id'],)).getField('echo') + args = {"id": i['id']} + import crontab + crontab.crontab().DelCrontab(args) + del_cron_file = cron_path + cron_echo + public.ExecShell("crontab -u root -l| grep -v '{}'|crontab -u root -".format(del_cron_file)) + + def get_load_info(self, get): + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, "未找到项目") + + pid = self.get_project_pid(project_data) + if not pid: + return json_response(False, "项目未启动") + + res = self.real_process.get_process_tree(pid) + if isinstance(res["data"], list): + res["data"] = {i.get("pid", "0"): i for i in res["data"]} + return res + + def get_port_status(self, get): + try: + project_data = self.get_project_find(get.project_name.strip()) + except: + return json_response(False, '参数错误') + + if not project_data: + return json_response(False, "未找到项目") + + pid = self.get_project_pid(project_data) + if not pid: + return json_response(False, "项目未启动") + + ports = [] + try: + p = psutil.Process(pid) + for i in p.connections(): + if i.status == "LISTEN" and i.laddr.port not in ports: + ports.append(str(i.laddr.port)) + except: + pass + + if not ports: + return json_response(False, "未找到端口") + + res = {str(i): { + "port": i, + "fire_wall": None, + "nginx_proxy": None, + } for i in ports} + + from firewallModel.comModel import main + port_list = main().port_rules_list(get) + for i in port_list: + if str(i["Port"]) in res: + res[str(i["Port"])]['fire_wall'] = i + + rsp = RealServerProxy(project_data) + proxy_list = rsp.get_proxy_list() + for i in proxy_list: + if str(i["proxy_port"]) in res: + res[str(i["proxy_port"])]['nginx_proxy'] = i + + return json_response(True, "获取成功", data=list(res.values())) + + def add_server_proxy(self, get): + if not hasattr(get, "site_name") or not get.site_name.strip(): + return json_response(status=False, msg="参数错误") + + project_data = self.get_project_find(get.site_name) + if not project_data: + return json_response(False, "未找到项目") + + rp = RealServerProxy(project_data) + proxy_data = rp.check_args(get, is_modify=False) + if isinstance(proxy_data, str): + return json_response(status=False, msg=proxy_data) + + # 使用正则匹配尝试添加 + res = rp.create_proxy(proxy_data) + if res is None: + return json_response(status=True, msg="添加成功") + + # 尝试模板生成 + proxy_info = project_data['project_config'].get("proxy_info", []) + proxy_info.append(proxy_data) + project_data['project_config']['proxy_info'] = proxy_info + domain_list = self._project_domain_list(project_data['id']) + domains = [(i["name"], str(i["port"])) for i in domain_list] + + ssl, f_ssl = self._get_ssl_status(project_data['name']) + error_msg = self.create_config(project_data, domains, ssl, f_ssl) + if not error_msg: + public.serviceReload() + pdata = { + 'project_config': json.dumps(project_data['project_config']) + } + public.M("sites").where("id=?", (project_data['id'],)).update(pdata) + return json_response(status=True, msg="添加成功") + + return json_response(status=False, msg=error_msg) + + def modify_server_proxy(self, get): + if not hasattr(get, "site_name") or not get.site_name.strip(): + return json_response(status=False, msg="参数错误") + + project_data = self.get_project_find(get.site_name) + if not project_data: + return json_response(False, "未找到项目") + + rp = RealServerProxy(project_data) + proxy_data = rp.check_args(get, is_modify=True) + if isinstance(proxy_data, str): + return json_response(status=False, msg=proxy_data) + + proxy_info = project_data['project_config'].get("proxy_info", []) + idx = None + for index, i in enumerate(proxy_info): + if i["proxy_id"] == proxy_data["proxy_id"] and i["site_name"] == proxy_data["site_name"]: + idx = index + break + + if idx is None: + return json_response(status=False, msg="未找到该id的反向代理配置") + + # 使用正则匹配尝试添加 + res = rp.modify_proxy(proxy_data) + if res is None: + return json_response(status=True, msg="修改成功") + + # 尝试模板生成 + proxy_info[idx] = proxy_data + project_data['project_config']['proxy_info'] = proxy_info + domain_list = self._project_domain_list(project_data['id']) + domains = [(i["name"], str(i["port"])) for i in domain_list] + ssl, f_ssl = self._get_ssl_status(project_data['name']) + error_msg = self.create_config(proxy_data, domains, ssl, f_ssl) + if not error_msg: + public.serviceReload() + pdata = { + 'project_config': json.dumps(project_data['project_config']) + } + public.M("sites").where("id=?", (project_data['id'],)).update(pdata) + return json_response(status=True, msg="修改成功") + + return json_response(status=False, msg=error_msg) + + def remove_server_proxy(self, get): + try: + site_name = get.site_name.strip() + proxy_id = get.proxy_id.strip() + except: + return json_response(status=False, msg="参数错误") + project_data = self.get_project_find(site_name) + if not project_data: + return json_response(False, "未找到项目") + + rp = RealServerProxy(project_data) + msg = rp.remove_proxy(site_name, proxy_id) + if msg: + return json_response(status=False, msg=msg) + return json_response(status=True, msg="删除成功") + + def server_proxy_list(self, get): + try: + site_name = get.site_name.strip() + except: + return json_response(status=False, msg="参数错误") + + project_data = self.get_project_find(site_name) + if not project_data: + return json_response(False, "未找到项目") + + _p = RealServerProxy(project_data) + data = _p.get_proxy_list() + return json_response(status=True, data=data) + + @staticmethod + def check_env_for_project(get): + project_cmd = "" + by_process = 0 + env_list = [] + env_file = '' + try: + if hasattr(get, "project_cmd") and get.project_cmd: + project_cmd = get.project_cmd.strip() + project_jar = get.project_jar.strip() + if hasattr(get, "by_process") and get.by_process: + by_process = int(get.by_process) + if hasattr(get, "env_list") and get.env_list: + env_list = get.env_list + if hasattr(get, "env_file") and get.env_file: + env_file = get.env_file + except: + return json_response(status=False, msg="参数错误") + if not os.path.exists(project_jar): + return json_response(status=False, msg="jar文件不存在") + if env_file and not os.path.exists(env_file): + return json_response(status=False, msg="环境变量文件不存在") + + spring_parser = SpringConfigParser( + jar_path=project_jar, + process=by_process, + cmd=project_cmd, + env_list=env_list, + env_file=env_file, + ) + data = spring_parser.get_tip() + if not data: + return json_response(status=True, msg="未检测到配置问题", data=data) + + return json_response(status=True, data=data) + + def set_static_path(self, get): + try: + project_name = get.project_name.strip() + project_data = self.get_project_find(project_name) + status = utils.js_value_to_bool(get.status) + index = get.index.strip() + path = get.path.strip() + except: + return json_response(status=False, msg="参数错误") + + if not project_data: + return json_response(False, "未找到项目") + + if not project_data["project_config"]["java_type"] == "springboot": + return json_response(status=False, msg="非springboot项目无法设置静态文件") + + proxy_info = project_data['project_config'].get("proxy_info", []) + for i in proxy_info: + if i["proxy_dir"] == "/": + return json_response(status=False, msg="项目已存在根路由【/】配置,无法设置态文件配置") + + project_data["project_config"]["static_info"] = { + "status": status, + "index": index, + "path": path, + "use_try_file": True, + } + + res = self._set_static_path(project_data) + if isinstance(res, str): + return json_response(status=False, msg=res) + + public.M("sites").where("id=?", (project_data["id"],)).update({ + "project_config": json.dumps(project_data["project_config"]) + }) + + return json_response(status=True, msg="设置成功") + + def get_keep_status(self, get): + try: + project_name = get.project_name.strip() + project_data = self.get_project_find(project_name) + except: + return json_response(status=False, msg="参数错误") + from mod.project.java.project_update import ProjectUpdate + + if not project_data: + return json_response(False, "未找到项目") + + if not project_data["project_config"]["java_type"] == "springboot": + return json_response(status=False, msg="非springboot项目不支持该功能") + + p = ProjectUpdate(project_name, project_data["project_config"]["project_jar"]) + res = p.get_keep_status() + return res + + def update_project_by_restart(self, get): + try: + project_name = get.project_name.strip() + project_jar = get.project_jar.strip() + project_data = self.get_project_find(project_name) + except: + return json_response(status=False, msg="参数错误") + + if not project_data: + return json_response(False, "未找到项目") + + if not project_data["project_config"]["java_type"] == "springboot": + return json_response(status=False, msg="非springboot项目不支持该功能") + + from mod.project.java.project_update import ProjectUpdate + + p = ProjectUpdate(project_name, new_jar=project_jar) + res = p.restart_update() + return res + + def update_project_by_keep(self, get): + now_port = 0 + run_time = 0 + try: + project_name = get.project_name.strip() + project_jar = get.project_jar.strip() + if hasattr(get, "now_port") and get.now_port: + now_port = int(get.now_port) + if hasattr(get, "run_time") and get.run_time: + run_time = int(get.run_time) + project_data = self.get_project_find(project_name) + except: + return json_response(status=False, msg="参数错误") + + if not project_data: + return json_response(False, "未找到项目") + + if not project_data["project_config"]["java_type"] == "springboot": + return json_response(status=False, msg="非springboot项目不支持该功能") + + if not os.path.isfile(project_jar): + return json_response(status=False, msg="jar文件不存在") + + if public.get_webserver() != "nginx": + return json_response(status=False, msg="当前只支持nginx使用") + + ng_file = "/www/server/panel/vhost/nginx/java_{}.conf".format(project_name) + if not os.path.exists(ng_file): + return json_response(status=False, msg="未启用外网访问的不能进行不停机更新") + + panel_path = "/www/server/panel" + pid_file = "{}/keep/{}.pid".format(self._java_project_path, project_name) + public.ExecShell( + "nohup {}/pyenv/bin/python3 {}/mod/project/java/project_update.py {} {} {} {} &> /dev/null & \n" + "echo $! > {} ".format( + panel_path, panel_path, project_name, project_jar, now_port, run_time, pid_file) + ) + return json_response(status=True, msg="更新任务已开始") + + def force_stop(self, get): + try: + project_name = get.project_name.strip() + project_data = self.get_project_find(project_name) + except: + return json_response(status=False, msg="参数错误") + + if not project_data: + return json_response(False, "未找到项目") + + project_config = project_data["project_config"] + pid_file = "{}/keep/{}.pid".format(self._java_project_path, project_name) + pid_data = public.readFile(pid_file) + if isinstance(pid_data, str) and pid_data != "0": + try: + p = psutil.Process(int(pid_data)) + p.kill() + except: + pass + + service_list = [] + rep_service = re.compile(r"^spring_%s_\S{8}$" % public.prevent_re_key(project_name), re.M) + for i in os.scandir("/usr/lib/systemd/system"): + if i.is_file() and rep_service.match(i.name): + service_list.append(i.name) + + now_name = "spring_" + project_config["project_name"] + "_" + project_config.get("server_name_suffix", "") + if now_name in service_list: + service_list.remove(now_name) + + for i in service_list: + public.ExecShell("systemctl stop {}".format(i)) + os.remove("/usr/lib/systemd/system/{}".format(i)) + + public.ExecShell("systemctl daemon-reload") + upstream_file = "/www/server/panel/vhost/nginx/java_{}_upstream.conf".format(project_name) + if os.path.isfile(upstream_file): # 说明新旧同时存在 则删除旧的 + upstream_data = public.readFile(upstream_file) + if isinstance(upstream_data, str): + old_upstream_data = re.search(r"server 127\.0\.0\.1:(?P\d+);", upstream_data) + if old_upstream_data: + old_port = old_upstream_data.group("port") + ng_file = "/www/server/panel/vhost/nginx/java_{}.conf".format(project_name) + ng_data = public.readFile(ng_file) + if not isinstance(ng_data, str): + return "Nginx配置文件读取错误,无法取消轮询,使用新实例" + new_config = ng_data.replace("{}_backend".format(project_name), "127.0.0.1:{}".format(old_port)) + + public.writeFile(ng_file, new_config) + res = public.checkWebConfig() + if res is not True: + public.writeFile(ng_file, ng_data) + else: + os.remove(upstream_file) + public.serviceReload() + + return json_response(status=True, msg="强制停止成功") + + def keep_option(self, get): + try: + project_name = get.project_name.strip() + option = get.option.strip() + project_data = self.get_project_find(project_name) + except: + return json_response(status=False, msg="参数错误") + + if not project_data: + return json_response(False, "未找到项目") + + if option not in ("use_new", "use_old", "stop_new"): + return json_response(status=False, msg="参数错误") + + if not project_data["project_config"]["java_type"] == "springboot": + return json_response(status=False, msg="非springboot项目不支持该功能") + + from mod.project.java.project_update import ProjectUpdate + + p = ProjectUpdate(project_name, project_data["project_config"]["project_jar"]) + res = p.keep_option(option) + return res + + def get_spring_log_list(self, get): + try: + project_name = get.project_name.strip() + project_data = self.get_project_find(project_name) + except: + return json_response(status=False, msg="参数错误") + + if not project_data: + return json_response(False, "未找到项目") + + if not project_data["project_config"]["java_type"] == "springboot": + return json_response(status=False, msg="非springboot项目不支持该功能") + + pid = self.get_project_pid(project_data) + project_config = project_data["project_config"] + project_jar = project_config["project_jar"] + project_cmd = project_config["project_cmd"] + env_list = project_config["env_list"] + env_file = project_config["env_file"] + + spring_log_parser = SpringLogConfigParser( + jar_path=project_jar, + process=pid, + cmd=project_cmd, + env_list=env_list, + env_file=env_file, + ) + res = [] + for i in spring_log_parser.get_all_log_ptah(): + for j in os.scandir(i): + if j.is_file() and j.name.endswith(".log"): + log_path = os.path.join(i, j.name) + res.append(log_path) + + return json_response(status=True, data=res) + + @staticmethod + def get_spring_log_data(get): + try: + log_file = get.log_file.strip() + except: + return json_response(status=False, msg="参数错误") + + if not os.path.isfile(log_file): + return json_response(status=False, msg="日志文件不存在") + + return json_response(status=True, data=public.GetNumLines(log_file, 1000)) + + @staticmethod + def install_jdk_new(get): + if not hasattr(get, 'version') or not get.version.strip(): + return json_response(False, '版本号不能为空') + version = get.version.strip() + if os.path.exists('/www/server/java/' + version): + return json_response(False, '版本已经存在') + jdk_manager = utils.JDKManager() + if version not in jdk_manager.versions_list: + return public.returnMsg(False, '版本号不存在') + + jdk_manager.async_install_jdk(version) + + return json_response(True, '已添加到安装任务,请在消息盒子中查看安装情况') + + @staticmethod + def install_tomcat_new(get): + java_path = None + if not hasattr(get, 'version') or not get.version.strip(): + return json_response(False, '版本号不能为空') + version = get.version.strip() + if hasattr(get, 'java_path') and get.java_path.strip(): + java_path = get.java_path.strip() + + res = utils.TomCat.async_install_tomcat_new(version, java_path) + if res is not None: + return json_response(False, res) + + return json_response(True, '已添加到安装任务,请在消息盒子中查看安装情况') \ No newline at end of file diff --git a/mod/project/java/project_update.py b/mod/project/java/project_update.py new file mode 100644 index 00000000..957b549a --- /dev/null +++ b/mod/project/java/project_update.py @@ -0,0 +1,600 @@ +import copy +import os +import re +import sys +import json +import socket +import time +import traceback + +import psutil +import errno + +from typing import Optional, List +from threading import Thread +from urllib3.util import parse_url, Url + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +import public + + +from mod.base import RealServer +from mod.base import json_response +from mod.project.java.projectMod import main as java_mod +from mod.project.java import utils + + +class ProjectUpdate: + + def __init__(self, project_name: str, new_jar: str, new_port: int = None, run_time: int = None): + self.project_name = project_name + self.new_jar = new_jar + self.j_project = java_mod() + self.keep_path = self.j_project._java_project_path + "/keep" + + if not os.path.exists(self.keep_path): + os.makedirs(self.keep_path, 0o755) + + self.keep_log = "{}/{}.log".format(self.keep_path, self.project_name) + + # 不停机更新时使用 + self.new_port = new_port + self.run_time = run_time + self.keep_status = [] + self.new_project_config = None + self.old_project_config = None + + self.old_pro: Optional[psutil.Process] = None + self.new_pro: Optional[psutil.Process] = None + + self.end = False + self.old_project_data = None + self.proxy_data = { + "scheme": "http" + } + + @staticmethod + def new_suffix() -> str: + import uuid + return "_" + uuid.uuid4().hex[::4] + + def start_spring_project(self, project_data: dict, write_systemd_file=True, need_wait=True, ) -> dict: + return self.j_project._start_spring_boot_project(project_data, write_systemd_file, need_wait) + + def restart_update(self) -> dict: + project_data = self.j_project.get_project_find(self.project_name) + if not project_data: + return json_response(False, msg="项目不存在") + + project_config = project_data['project_config'] + old_jar = project_config['project_jar'] + if self.new_jar != old_jar: + if not os.path.isfile(self.new_jar): + return json_response(False, msg="项目jar包不存在") + + project_config['jar_path'] = os.path.dirname(self.new_jar) + project_config['project_jar'] = self.new_jar + old_jar_name = os.path.basename(old_jar) + project_cmd_list = project_config['project_cmd'].split(" ") + for i in range(len(project_cmd_list)): + if old_jar_name in project_cmd_list[i]: + project_cmd_list[i] = self.new_jar + break + + new_project_cmd = " ".join(project_cmd_list) + project_config['project_cmd'] = new_project_cmd + project_config["change_flag"] = True + + s_admin = RealServer() + server_name = "spring_" + project_config["project_name"] + project_config.get("server_name_suffix", "") + if s_admin.daemon_status(server_name)["msg"] == "服务不存在!": + self.j_project.stop_by_kill_pid(project_data) + if os.path.isfile(project_config["pids"]): + os.remove(project_config["pids"]) + return self.start_spring_project(project_data, write_systemd_file=True, need_wait=False) + + if "change_flag" in project_config and project_config.get("change_flag", False): + del project_config["change_flag"] + s_admin.daemon_admin(server_name, "stop") + s_admin.del_daemon(server_name) + self.j_project.stop_by_kill_pid(project_data) + if os.path.isfile(project_config["pids"]): + os.remove(project_config["pids"]) + + public.M("sites").where("id=?", (project_data["id"],)).update( + {"project_config": json.dumps(project_config)} + ) + return self.start_spring_project(project_data, write_systemd_file=True) + else: + return self.start_spring_project(project_data, write_systemd_file=False) + + # 实际执行启动的线程 + def run_task(self): + print("___________开始________________") + try: + res = self.start_new() + self.keep_status[0]["status"] = 1 + if res: + self.keep_status[0]["msg"] = res + return + else: + self.keep_status[0]["msg"] = "新实例已启动,新实例pid:{}".format(self.new_pro.pid) + res = self.set_nginx_upstream() + self.keep_status[1]["status"] = 1 + if res: + self.stop_new() + self.keep_status[1]["msg"] = res + return + else: + self.keep_status[1]["msg"] = "Nginx已配置完成轮询设置,您可以访问新实例了" + res = self.wait_time() + self.keep_status[2]["status"] = 1 + if res: + self.keep_status[2]["msg"] = res + return + else: + self.keep_status[2]["msg"] = "等待时间结束,新实例已启动成功" + res = self.stop_old() + self.keep_status[3]["status"] = 1 + self.keep_status[3]["msg"] = res if res else "停止旧实例成功,项目更新已结束" + public.M("sites").where("id=?", (self.old_project_data["id"],)).update( + {"project_config": json.dumps(self.new_project_config)} + ) + except: + print(traceback.format_exc()) + pass + + def stop_new(self): + new_server_name = "spring_" + self.project_name + self.new_project_config.get("server_name_suffix", "") + RealServer().server_admin(new_server_name, "stop") + RealServer().del_daemon(new_server_name) + if self.new_pro and self.new_pro.is_running(): + self.new_pro.kill() + + def start_new(self) -> Optional[str]: + self.keep_status[0]["status"] = -1 + self.new_project_config['server_name_suffix'] = self.new_suffix() + self.new_project_config['pids'] = "{}/pids/{}.pid".format( + self.j_project._java_project_vhost, self.project_name + self.new_project_config['server_name_suffix'] + ) + + if not self.new_port or self.new_port in self.old_listen_port() or \ + utils.check_port_with_net_connections(self.new_port): + self.new_port = utils.create_a_not_used_port() + + old_jar = self.old_project_config['project_jar'] + if self.new_jar != old_jar: + if not os.path.isfile(self.new_jar): + return "项目jar包不存在" + + self.new_project_config['jar_path'] = os.path.dirname(self.new_jar) + self.new_project_config['project_jar'] = self.new_jar + old_jar_name = os.path.basename(old_jar) + project_cmd_list = self.new_project_config['project_cmd'].split(" ") + for i in range(len(project_cmd_list)): + if old_jar_name in project_cmd_list[i]: + project_cmd_list[i] = self.new_jar + break + + new_project_cmd = " ".join(project_cmd_list) + self.new_project_config['project_cmd'] = new_project_cmd + + if "--server.port=" in self.new_project_config['project_cmd']: + self.new_project_config['project_cmd'] = re.sub( + r"--server\.port=\d+", + "--server.port={}".format(self.new_port), + self.new_project_config['project_cmd'] + ) + else: + self.new_project_config['project_cmd'] += " --server.port={}".format(self.new_port) + + self.old_project_data["project_config"] = self.new_project_config + + self.start_spring_project(self.old_project_data, write_systemd_file=True) + time.sleep(1) + new_pid = self.j_project.get_project_pid(self.old_project_data) + if not new_pid: + return "项目启动失败" + self.new_pro = psutil.Process(new_pid) + self.keep_status[0]["msg"] = "新实例pid为:{}".format(new_pid) + # 开始等待进程启动 + server_name = "spring_" + self.project_name + self.new_project_config.get("server_name_suffix", "") + wait_num = 1 + for i in range(5 * 60 * 2 - 2): + if self.end: + RealServer().server_admin(server_name, "stop") + RealServer().del_daemon(server_name) + return "退出操作" + if not self.new_pro.is_running(): + RealServer().del_daemon(server_name) + return "项目启动失败" + + conns = self.new_pro.connections() + for c in conns: + if c.status == "LISTEN" and c.laddr.port == self.new_port: + return + self.keep_status[0]["msg"] = "新实例pid为:{}, 正在等待该进程监听端口:{}, 已等待{}s".format(new_pid, self.new_port, wait_num) + wait_num += 0.5 + time.sleep(0.5) + + RealServer().server_admin(server_name, "stop") + RealServer().del_daemon(server_name) + return "启动超时" + + def old_listen_port(self) -> List[int]: + connects = self.old_pro.connections() + res = [] + for i in connects: + if i.status == "LISTEN": + res.append(i.laddr.port) + return res + + def set_nginx_upstream(self) -> Optional[str]: + self.keep_status[1]["status"] = -1 + ng_file = "/www/server/panel/vhost/nginx/java_{}.conf".format(self.project_name) + res = public.checkWebConfig() + if res is not True: + return "Nginx配置文件错误,无法开始轮询配置" + ng_data = public.readFile(ng_file) + if not isinstance(ng_data, str): + return "Nginx配置文件读取错误,无法开始轮询配置" + + old_proxy_res = None + for tmp_res in re.finditer(r"\s*proxy_pass\s+(?P\S+)\s*;", ng_data, re.M): + url: Url = parse_url(tmp_res.group("url")) + if url.hostname in ("127.0.0.1", "localhost", "0.0.0.0") and url.port in self.old_listen_port(): + old_proxy_res = tmp_res + self.proxy_data["scheme"] = url.scheme + self.proxy_data["old_port"] = url.port + if not old_proxy_res: + return "未找到原实例的代理配置" + + upstream_file = "/www/server/panel/vhost/nginx/java_{}_upstream.conf".format(self.project_name) + public.writeFile(upstream_file, """ +upstream {}_backend {{ + server 127.0.0.1:{}; + server 127.0.0.1:{}; +}} +""".format(self.project_name, self.proxy_data["old_port"], self.new_port)) + + new_config = ng_data.replace(old_proxy_res.group(), "\n proxy_pass {}://{}_backend;".format( + self.proxy_data["scheme"], self.project_name)) + + public.writeFile(ng_file, new_config) + + res = public.checkWebConfig() + if res is not True: + public.writeFile(ng_file, ng_data) + return "Nginx配置文件错误,无法开始轮询配置" + else: + public.serviceReload() + + def wait_time(self): + self.keep_status[2]["status"] = -1 + if not self.run_time: + self.run_time = 10 * 60 + for i in range(self.run_time): + if self.end: + return "退出操作" + self.keep_status[2]["msg"] = "已进入轮询测试等待" + if i > 0: + self.keep_status[2]["msg"] = "已进入轮询测试等待,已等待{}s, 共需等待{}s".format(i, self.run_time) + time.sleep(1) + if not self.new_pro.is_running(): + return "新示例已退出,无法继续执行操作" + return None + + def select_new_or_old(self, option: str): + if option == "use_new": + self.keep_status[2]["status"] = 1 + self.keep_status[2]["msg"] = "已跳过等待时间,使用新实例运行" + res = self.stop_old() + public.M("sites").where("id=?", (self.old_project_data["id"],)).update( + {"project_config": json.dumps(self.new_project_config)} + ) + self.keep_status[3]["status"] = 1 + self.keep_status[3]["msg"] = res if res else "停止旧实例成功,项目更新已结束" + return {"status": False if res else True, "msg": res if res else "停止旧实例成功,项目更新已结束"} + + self.keep_status[2]["status"] = 1 + self.keep_status[2]["msg"] = "已跳过等待时间,使用原实例运行" + self.keep_status[3]["name"] = "停止新实例" + self.keep_status[3]["status"] = 1 + ng_file = "/www/server/panel/vhost/nginx/java_{}.conf".format(self.project_name) + ng_data = public.readFile(ng_file) + if not isinstance(ng_data, str): + return {"status": False, "msg": "Nginx配置文件读取错误,无法取消轮询并使用原实例"} + res = public.checkWebConfig() + if res is not True: + return {"status": False, "msg": "Nginx配置文件错误,无法取消轮询并使用原实例"} + + upstream_file = "/www/server/panel/vhost/nginx/java_{}_upstream.conf".format(self.project_name) + new_config = ng_data.replace( + "{}_backend".format(self.project_name), + "127.0.0.1:{}".format(self.proxy_data["old_port"]) + ) + public.writeFile(ng_file, new_config) + res = public.checkWebConfig() + if res is not True: + public.writeFile(ng_file, ng_data) + return {"status": False, "msg": "Nginx配置文件设置错误,无法取消轮询并使用原实例"} + else: + os.remove(upstream_file) + public.serviceReload() + self.stop_new() + + return {"status": True, "msg": "停止新实例成功,项目更新已结束"} + + def stop_old(self): + self.keep_status[3]["status"] = -1 + ng_file = "/www/server/panel/vhost/nginx/java_{}.conf".format(self.project_name) + ng_data = public.readFile(ng_file) + if not isinstance(ng_data, str): + return "Nginx配置文件读取错误,无法取消轮询,使用新实例" + + res = public.checkWebConfig() + if res is not True: + return "Nginx配置文件错误,无法取消轮询,使用新实例" + + old_proxy_res = None + for tmp_res in re.finditer(r"\s*proxy_pass\s+(?P\S+)\s*;", ng_data, re.M): + if tmp_res.group("url").find("{}_backend".format(self.project_name)): + old_proxy_res = tmp_res + + if not old_proxy_res: + return "未找到轮询的代理配置" + + upstream_file = "/www/server/panel/vhost/nginx/java_{}_upstream.conf".format(self.project_name) + if os.path.isfile(upstream_file): + os.remove(upstream_file) + + new_config = ng_data.replace(old_proxy_res.group(), "\n proxy_pass {}://127.0.0.1:{};".format( + self.proxy_data["scheme"], self.new_port)) + + public.writeFile(ng_file, new_config) + + res = public.checkWebConfig() + if res is not True: + public.writeFile(ng_file, ng_data) + return "Nginx配置文件错误,无法结束轮询配置" + else: + public.serviceReload() + + old_server_name = "spring_" + self.project_name + self.old_project_config.get("server_name_suffix", "") + RealServer().server_admin(old_server_name, "stop") + RealServer().del_daemon(old_server_name) + if self.old_pro and self.old_pro.is_running(): + self.old_pro.kill() + + return None + + def keep_update(self): + pid_file = "{}/{}.pid".format(self.keep_path, self.project_name) + log_file = "{}/{}.log".format(self.keep_path, self.project_name) + pid = os.getpid() + public.writeFile(pid_file, str(pid)) + if os.path.exists(log_file): + os.remove(log_file) + + project_data = self.j_project.get_project_find(self.project_name) + if not project_data: + return json_response(False, msg="项目不存在") + + project_config = project_data['project_config'] + self.old_project_data = project_data + self.old_project_config = project_config + self.new_project_config = copy.deepcopy(project_config) + + try: + self.old_pro = psutil.Process(self.j_project.get_project_pid(project_data)) + except: + pass + if not self.old_pro: + return json_response(False, msg="项目未启动") + + self.end = False + self.keep_status = [ + { + "name": "启动新实例", + "status": 0, + "msg": "", + }, + { + "name": "设置Nginx轮询", + "status": 0, + "msg": "", + }, + { + "name": "等待并检查新实例", + "status": 0, + "msg": "", + }, + { + "name": "停止旧实例", + "status": 0, + "msg": "", + } + ] + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + socket_file = "{}/{}.socket".format(self.keep_path, self.project_name) + # 清理旧的socket文件,如果存在 + if os.path.exists(socket_file): + os.remove(socket_file) + + # 设置为非阻塞 + sock.bind(socket_file) + sock.setblocking(False) # 0表示非阻塞,1表示阻塞 + sock.listen(2) + + update_run_task = Thread(target=self.run_task) + update_run_task.start() + + while True: + if not update_run_task.is_alive(): + public.writeFile(log_file, json.dumps(self.keep_status)) + break + + try: + # 读取客户端发送的数据 + conn, _ = sock.accept() + data = conn.recv(1024) + except socket.error as e: + if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK): + raise e + time.sleep(0.1) + continue + + if not data: + time.sleep(0.1) + continue + + # 打印接收到的数据 + print("Received:", data.decode()) + data_str = data.decode() + if data_str == "stop_new": + if self.keep_status[0]["status"] == -1: + self.end = True + update_run_task.join() + public.writeFile(log_file, json.dumps(self.keep_status)) + conn.sendall(json.dumps({ + "status": True, + "msg": "已关闭更新任务,并停止新实例" + }).encode()) + break + else: + conn.sendall(json.dumps({ + "status": False, + "msg": "新实例启动完成,已加入轮询,无法继续执行该操作" + }).encode()) + elif data_str == "status": + conn.sendall(json.dumps(self.keep_status).encode()) + elif data_str in ("use_new", "use_old"): + if self.keep_status[2]["status"] != -1: + conn.sendall(json.dumps({ + "status": False, + "msg": "已超过轮询等待时间,无法执行该操作" + }).encode()) + else: + self.end = True + update_run_task.join() + public.writeFile(log_file, json.dumps(self.keep_status)) + res = self.select_new_or_old(data_str) + conn.sendall(json.dumps(res).encode()) + + time.sleep(0.1) + + # 关闭服务器端socket + sock.close() + # 清理旧的socket文件,如果存在 + if os.path.exists(socket_file): + os.remove(socket_file) + + def get_keep_status(self): + try: + log_file = "{}/{}.log".format(self.keep_path, self.project_name) + pid_file = "{}/{}.pid".format(self.keep_path, self.project_name) + log_data = public.readFile(log_file) + data = None + if isinstance(log_data, str): + try: + data = json.loads(log_data) + except: + pass + + if data: + return json_response(True, data={ + "running": False, + "keep_msg": data + }) + + pid_data = public.readFile(pid_file) + er_msg = "没有正在进行的更新任务" + if not isinstance(pid_data, str): + return json_response(False, msg=er_msg) + try: + pid = int(pid_data) + if not psutil.pid_exists(pid): + return json_response(False, msg=er_msg) + except: + return json_response(False, msg=er_msg) + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.connect("{}/{}.socket".format(self.keep_path, self.project_name)) + except Exception: + public.print_log(public.get_error_info()) + return json_response(False, msg="链接错误请尝试强制停止更新") + data = b"status" + sock.sendall(data) + + # 接收响应 + sock.settimeout(1) + response = sock.recv(1024 * 2) + sock.close() + try: + data = json.loads(response.decode()) + except: + public.print_log(public.get_error_info()) + return json_response(False, msg="链接错误请尝试强制停止更新") + return json_response(True, data={ + "running": True, + "keep_msg": data + }) + except: + public.print_log(public.get_error_info()) + return json_response(False, msg="链接错误请尝试强制停止更新") + + def keep_option(self, option: str) -> dict: + try: + pid_file = "{}/{}.pid".format(self.keep_path, self.project_name) + pid_data = public.readFile(pid_file) + er_msg = "没有正在进行的更新任务, 无法执行操作" + if not isinstance(pid_data, str) or pid_data == "0": + return json_response(False, msg=er_msg) + try: + pid = int(pid_data) + if not psutil.pid_exists(pid): + return json_response(False, msg=er_msg) + except: + return json_response(False, msg=er_msg) + + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.connect("{}/{}.socket".format(self.keep_path, self.project_name)) + except Exception: + return json_response(False, msg="链接错误,无法执行操作,请尝试强制停止更新") + + sock.sendall(option.encode()) + + # 接收响应 + sock.settimeout(10) + response = sock.recv(1024) + sock.close() + try: + data = json.loads(response.decode()) + except: + public.print_log(public.get_error_info()) + return json_response(False, msg="链接错误请尝试强制停止更新") + if isinstance(data, dict): + return json_response(data['status'], msg=data['msg']) + else: + return json_response(False, msg="链接错误请尝试强制停止更新") + except: + public.print_log(public.get_error_info()) + return json_response(False, msg="链接错误请尝试强制停止更新") + + +if __name__ == '__main__': + def run_main(project_name: str, new_jar: str, new_port: int, run_time: int,): + pu = ProjectUpdate(project_name, new_jar=new_jar, run_time=run_time, new_port=new_port) + pu.keep_update() + + if len(sys.argv) == 5: + run_main(sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4])) + diff --git a/mod/project/java/server_proxy.py b/mod/project/java/server_proxy.py new file mode 100644 index 00000000..3aa96a07 --- /dev/null +++ b/mod/project/java/server_proxy.py @@ -0,0 +1,644 @@ +import os +import re +import json +from typing import Optional, Union, List, Dict, Any +from mod.base.web_conf.util import check_server_config, write_file, read_file, service_reload +from mod.base import json_response +from urllib3.util import Url, parse_url + +import public + + +class RealServerProxy: + panel_path = "/www/server/panel" + default_headers = ( + "Host", "X-Real-IP", "X-Forwarded-For", "REMOTE-HOST", "X-Host", "X-Scheme", "Upgrade", "Connection" + ) + + def __init__(self, project_data: dict): + self.config_prefix: str = "java_" + + site_name = project_data["name"] + self.project_id = project_data["id"] + self.project_config = project_data["project_config"] + + self._config: Optional[List[dict]] = None + self._ng_file: str = "{}/vhost/nginx/{}{}.conf".format(self.panel_path, self.config_prefix, site_name) + self._ap_file: str = "{}/vhost/apache/{}{}.conf".format(self.panel_path, self.config_prefix, site_name) + self.site_name = site_name + self.ws_type = public.get_webserver() + + @staticmethod + def new_id() -> str: + from uuid import uuid4 + return uuid4().hex[::3] + + @property + def config(self) -> List[dict]: + if self._config is None: + if "proxy_info" in self.project_config: + self._config = self.project_config["proxy_info"] + else: + self._config = [] + + return self._config + + def save_config(self): + if self._config is not None: + self.project_config["proxy_info"] = self._config + public.M("sites").where("id=?", (self.project_id,)).update( + {"project_config": json.dumps(self.project_config)} + ) + + # 检查代理是否存在 + def _check_even(self, proxy_conf: dict, is_modify) -> Optional[str]: + if is_modify is True: + for i in self.config: + if i["proxy_dir"] == proxy_conf["proxy_dir"] and i["proxy_id"] != proxy_conf["proxy_id"]: + return '指定反向代理名称或代理文件夹已存在' + if i["proxy_port"] == proxy_conf["proxy_port"] and i["proxy_id"] != proxy_conf["proxy_id"]: + return '指定反向代理端口已存在对应的代理' + else: + for i in self.config: + if i["proxy_port"] == proxy_conf["proxy_port"]: + return '指定反向代理端口已存在对应的代理' + + def check_args(self, get, is_modify=False) -> Union[str, dict]: + err_msg = check_server_config() + if isinstance(err_msg, str): + return 'WEB服务器配置配置文件错误ERROR:
                                    ' + \ + err_msg.replace("\n", '
                                    ') + '
                                    ' + data = { + "proxy_dir": "/", + "status": 1, + "proxy_id": self.new_id(), + "rewrite": { + "status": False, + "src_path": "", + "target_path": "", + }, + "add_headers": [], + } + try: + data["site_name"] = get.site_name.strip() + if "proxy_dir" in get: + data["proxy_dir"] = get.proxy_dir.strip() + if "proxy_id" in get: + data["proxy_id"] = get.proxy_id.strip() + data["proxy_port"] = int(get.proxy_port) + data["status"] = int(get.status.strip()) + if hasattr(get, "rewrite"): + data["rewrite"] = get.rewrite + if isinstance(get.rewrite, str): + data["rewrite"] = json.loads(get.rewrite) + + if hasattr(get, "add_headers"): + data["add_headers"] = get.add_headers + if isinstance(get.add_headers, str): + data["add_headers"] = json.loads(get.add_headers) + except: + public.print_log(public.get_error_info()) + return "参数错误" + + if not 1 < data["proxy_port"] < 65536: + return '代理端口范围错误' + + if not data["proxy_dir"].endswith("/"): + data["proxy_dir"] += "/" + + evn_msg = self._check_even(data, is_modify) + if isinstance(evn_msg, str): + return evn_msg + + rep_re_key = re.compile(r'''[?=\[\])(*&^%$#@!~`{}><,'"\\]+''') + special = r'''?,=,[,],),(,*,&,^,%,$,#,@,!,~,`,{,},>,<,\,',"''' + # 检测代理目录格式 + if rep_re_key.search(data["proxy_dir"]): + return "代理路由不能有以下特殊符号" + special + + if not isinstance(data["rewrite"], dict): + return "路由重写配置错误" + if "status" not in data["rewrite"] or not data["rewrite"]["status"]: + data["rewrite"] = { + "status": False, + "src_path": "", + "target_path": "", + } + else: + if not ("src_path" in data["rewrite"] and "target_path" in data["rewrite"]): + return "路由重写参数配置错误" + if not isinstance(data["rewrite"]["src_path"], str) or not isinstance(data["rewrite"]["target_path"], str): + return "路由重写参数配置错误" + if rep_re_key.search(data["rewrite"]["src_path"]): + return "路由重写匹配路由不能有以下特殊符号" + special + if rep_re_key.search(data["rewrite"]["target_path"]): + return "路由重写目标路由不能有以下特殊符号" + special + + if not isinstance(data["add_headers"], list): + return "自定义代理头配置错误" + else: + rep_blank_space = re.compile(r"\s+") + for h in data["add_headers"]: + if "k" not in h or "v" not in h: + return "自定义代理头配置错误" + if not isinstance(h["k"], str) or not isinstance(h["v"], str): + return "自定义代理头配置错误" + if rep_blank_space.search(h["k"]) or rep_blank_space.search(h["v"]): + return "代理头配置中不能包含有空格" + if h["k"] in self.default_headers: + return '代理头配置中不能包含有默认头【{}】'.format(h["k"]) + + return data + + def check_location(self, proxy_dir: str) -> Optional[str]: + # 伪静态文件路径 + rewrite_conf_path = "%s/vhost/rewrite/%s%s.conf" % (self.panel_path, self.config_prefix, self.site_name) + + rep_location = re.compile(r"s*location\s+(\^~\s*)?%s\s*{" % proxy_dir) + + for i in [rewrite_conf_path, self._ng_file]: + conf = read_file(i) + if isinstance(conf, str) and rep_location.search(conf): + return '伪静态/站点主配置文件已经存路径【{}】的配置'.format(proxy_dir) + + @staticmethod + def _set_nginx_proxy_base(): + file = "/www/server/nginx/conf/proxy.conf" + setup_path = "/www/server" + if not os.path.exists(file): + conf = '''proxy_temp_path %s/nginx/proxy_temp_dir; +proxy_cache_path %s/nginx/proxy_cache_dir levels=1:2 keys_zone=cache_one:10m inactive=1d max_size=5g; +client_body_buffer_size 512k; +proxy_connect_timeout 60; +proxy_read_timeout 60; +proxy_send_timeout 60; +proxy_buffer_size 32k; +proxy_buffers 4 64k; +proxy_busy_buffers_size 128k; +proxy_temp_file_write_size 128k; +proxy_next_upstream error timeout invalid_header http_500 http_503 http_404; +proxy_cache cache_one;''' % (setup_path, setup_path) + write_file(file, conf) + + conf = read_file(file) + if conf and conf.find('include proxy.conf;') == -1: + conf = re.sub(r"include\s+mime\.types;", "include mime.types;\n\tinclude proxy.conf;", conf) + write_file(file, conf) + + # websocket前置map + map_file = "/www/server/panel/vhost/nginx/0.websocket.conf" + if not os.path.exists(map_file): + write_file(map_file, ''' +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +}''') + + @staticmethod + def build_proxy_conf(proxy_data: dict) -> str: + ng_proxy = ''' + #PROXY-START{proxy_dir} + location {proxy_dir} {{{rewrite} + proxy_pass {proxy_url}; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;{add_headers} + proxy_set_header REMOTE-HOST $remote_addr; + add_header X-Cache $upstream_cache_status; + proxy_set_header X-Host $host:$server_port; + proxy_set_header X-Scheme $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 86400s; + proxy_send_timeout 30s; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + }} + #PROXY-END{proxy_dir} +''' + + rewrite = "" + if "rewrite" in proxy_data and proxy_data["rewrite"].get("status", False): + rewrite = proxy_data["rewrite"] + src_path = rewrite["src_path"] + if not src_path.endswith("/"): + src_path += "/" + target_path = rewrite["target_path"] + if target_path.endswith("/"): + target_path = target_path[:-1] + + rewrite = "\n rewrite ^{}(.*)$ {}/$1 break;".format(src_path, target_path) + + add_headers = "" + if "add_headers" in proxy_data: + header_tmp = " proxy_set_header {} {};" + add_headers_list = [header_tmp.format(h["k"], h["v"]) for h in proxy_data["add_headers"] if + "k" in h and "v" in h] + add_headers = "\n".join(add_headers_list) + if add_headers: + add_headers = "\n" + add_headers + + # 构造替换字符串 + proxy_dir = proxy_data["proxy_dir"] + proxy_site = "http://127.0.0.1:{}".format(proxy_data["proxy_port"]) + + proxy = ng_proxy.format( + proxy_dir=proxy_dir, + proxy_url=proxy_site, + rewrite=rewrite, + add_headers=add_headers, + ) + + return proxy + + def add_nginx_proxy(self, proxy_data: dict) -> Optional[str]: + ng_conf = read_file(self._ng_file) + if not ng_conf: + return "Nginx配置文件不存在" + + proxy_str = self.build_proxy_conf(proxy_data) + + # 添加配置信息到配置文件中 + rep_list = [ + (re.compile(r"\s*#PROXY-LOCAl-END.*", re.M), True), # 添加到反向代理结尾的上面 + (re.compile(r"\s*#ROXY-END.*", re.M), False), # 添加到其他的反向代理的下面 + (re.compile(r"\s*#\s*HTTP反向代理相关配置结束\s*<<<.*", re.M), False), # 添加到其他的反向代理的下面 + (re.compile(r"\s*include\s*/www/server/panel/vhost/rewrite/.*(\s*#.*)?"), False), + # 添加到伪静态的下面 + # (re.compile(r"(#.*)?\s*location\s+/\.well-known/\s*{"), True), # 添加到location /.well-known/上面 + ] + + # 使用正则匹配确定插入位置 + def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool: + tmp_res = tmp_rep.search(ng_conf) + if not tmp_res: + return False + if use_start: + new_conf = ng_conf[:tmp_res.start()] + proxy_str + tmp_res.group() + ng_conf[tmp_res.end():] + else: + new_conf = ng_conf[:tmp_res.start()] + tmp_res.group() + proxy_str + ng_conf[tmp_res.end():] + + write_file(self._ng_file, new_conf) + if self.ws_type == "nginx" and check_server_config() is not None: + write_file(self._ng_file, ng_conf) + return False + return True + + for r, s in rep_list: + if set_by_rep_idx(r, s): + break + else: + return "无法在配置文件中定位到需要添加的项目" + + def _unset_nginx_proxy(self, proxy_data) -> Optional[str]: + ng_conf = read_file(self._ng_file) + if not isinstance(ng_conf, str): + return "配置文件不存在" + + proxy_dir = proxy_data["proxy_dir"] + rep_start_end = re.compile(r"\s*#PROXY-START%s(.|\n)*?#PROXY-END%s[^\n]*" % (proxy_dir, proxy_dir)) + if rep_start_end.search(ng_conf): + new_ng_conf = rep_start_end.sub("", ng_conf) + write_file(self._ng_file, new_ng_conf) + if self.ws_type == "nginx": + err_msg = check_server_config() + if isinstance(err_msg, str): + write_file(self._ng_file, ng_conf) + return err_msg + else: + return + + rep_location = re.compile(r"(\s*#.*?)\s*location\s+(\^~\s*)?%s\s*{" % proxy_dir) + res = rep_location.search(ng_conf) + if res: + end_idx = self.find_nginx_block_end(ng_conf, res.end() + 1) + if not end_idx: + return + + block = ng_conf[res.start(): end_idx] + if block.find("proxy_pass") == -1: # 如果这块内不包含proxy_pass 则跳过 + return + + # 异除下一个注释行 + res_end = re.search(r"\s*#PROXY-END.*", ng_conf[end_idx + 1:]) + if res_end: + end_idx += res_end.end() + + new_ng_conf = ng_conf[:res.start()] + ng_conf[end_idx + 1:] + write_file(self._ng_file, new_ng_conf) + if self.ws_type == "nginx": + err_msg = check_server_config() + if isinstance(err_msg, str): + write_file(self._ng_file, ng_conf) + return err_msg + + @staticmethod + def find_nginx_block_end(data: str, start_idx: int) -> Optional[int]: + if len(data) < start_idx + 1: + return None + + level = 1 + line_start = 0 + for i in range(start_idx + 1, len(data)): + if data[i] == '\n': + line_start = i + 1 + if data[i] == '{' and line_start and data[line_start: i].find("#") == -1: # 没有注释的下一个{ + level += 1 + elif data[i] == '}' and line_start and data[line_start: i].find("#") == -1: # 没有注释的下一个} + level -= 1 + if level == 0: + return i + + return None + + @staticmethod + def build_apache_conf(proxy_data: dict) -> str: + return ''' + #PROXY-START{proxy_dir} + + ProxyRequests Off + SSLProxyEngine on + ProxyPass {proxy_dir} {url}/ + ProxyPassReverse {proxy_dir} {url}/ + RequestHeader set Host "%{{Host}}e" + RequestHeader set X-Real-IP "%{{REMOTE_ADDR}}e" + RequestHeader set X-Forwarded-For "%{{X-Forwarded-For}}e" + RequestHeader setifempty X-Forwarded-For "%{{REMOTE_ADDR}}e" + + #PROXY-END{proxy_dir} +'''.format(proxy_dir=proxy_data["proxy_dir"], url="http://127.0.0.1:{}".format(proxy_data["proxy_port"])) + + def add_apache_proxy(self, proxy_data: dict) -> Optional[str]: + ap_conf = read_file(self._ap_file) + if not ap_conf: + return "Apache配置文件不存在" + + proxy_str = self.build_apache_conf(proxy_data) + + # 添加配置信息到配置文件中 + rep_list = [ + (re.compile(r"#ROXY-END[^\n]*\n"), False), # 添加到其他的反向代理的下面 + (re.compile(r"#\s*HTTP反向代理相关配置结束\s*<<<[^\n]*\n"), False), # 添加到其他的反向代理的下面 + ( + re.compile(r"\s*(#SSL[^\n]*)?\s*[^\n]*\s*.*/.well-known/[^\n]*\s*"), + True # 添加到location /.well-known/上面 + ), + (re.compile(r"\s*[^\n]*\n?"), True), + ] + + # 使用正则匹配确定插入位置 + def set_by_rep_idx(tmp_rep: re.Pattern, use_start: bool) -> bool: + new_conf_list = [] + change_flag = False + start_idx = 0 + for tmp in tmp_rep.finditer(ap_conf): + change_flag = True + new_conf_list.append(ap_conf[start_idx:tmp.start()]) + start_idx = tmp.end() + if use_start: + new_conf_list.append(proxy_str) + new_conf_list.append(tmp.group()) + else: + new_conf_list.append(tmp.group()) + new_conf_list.append(proxy_str) + + if not change_flag: + return False + + new_conf_list.append(ap_conf[start_idx:]) + write_file(self._ap_file, "".join(new_conf_list)) + if self.ws_type == "apache" and check_server_config() is not None: + write_file(self._ap_file, ap_conf) + return False + return True + + for r, s in rep_list: + if set_by_rep_idx(r, s): + break + else: + return "无法在配置文件中定位到需要添加的项目" + + def remove_apache_proxy(self, proxy_data) -> Optional[str]: + ap_conf = read_file(self._ap_file) + if not isinstance(ap_conf, str): + return "配置文件不存在" + + proxy_dir = proxy_data["proxy_dir"] + rep_start_end = re.compile(r"\s*#PROXY-START%s(.|\n)*?#PROXY-END%s[^\n]*" % (proxy_dir, proxy_dir)) + if rep_start_end.search(ap_conf): + new_ap_conf = rep_start_end.sub("", ap_conf) + write_file(self._ap_file, new_ap_conf) + if self.ws_type == "apache": + err_msg = check_server_config() + if isinstance(err_msg, str): + write_file(self._ap_file, ap_conf) + return err_msg + else: + return + + rep_if_mod = re.compile( + r"(\s*#.*)?\s*\s*(.*\n){3,5}\s*" + r"ProxyPass\s+%s\s+\S+/\s*(.*\n){1,2}\s*(\s*#.*)?" % proxy_dir) + + res = rep_if_mod.search(ap_conf) + if res: + new_ap_conf = rep_if_mod.sub("", ap_conf) + write_file(self._ap_file, new_ap_conf) + if self.ws_type == "apache": + err_msg = check_server_config() + if isinstance(err_msg, str): + write_file(self._ap_file, ap_conf) + return err_msg + + def create_proxy(self, proxy_data: dict) -> Optional[str]: + for i in self.config: + if i["proxy_dir"] == proxy_data["proxy_dir"]: + proxy_data["proxy_id"] = i["proxy_id"] + return self.modify_proxy(proxy_data) + + if self.ws_type == "nginx": + err_msg = self.check_location(proxy_data["proxy_dir"]) + if err_msg: + return json_response(False, err_msg) + + self._set_nginx_proxy_base() + error_msg = self.add_nginx_proxy(proxy_data) + if self.ws_type == "nginx" and error_msg: + return error_msg + error_msg = self.add_apache_proxy(proxy_data) + if self.ws_type == "apache" and error_msg: + return error_msg + self.config.append(proxy_data) + self.save_config() + service_reload() + + def modify_proxy(self, proxy_data: dict) -> Optional[str]: + idx = None + for index, i in enumerate(self.config): + if i["proxy_id"] == proxy_data["proxy_id"] and i["site_name"] == proxy_data["site_name"]: + idx = index + break + + if idx is None: + return "未找到该id的反向代理配置" + + if proxy_data["proxy_dir"] != self.config[idx]["proxy_dir"] and self.ws_type == "nginx": + err_msg = self.check_location(proxy_data["proxy_dir"]) + if err_msg: + return json_response(False, err_msg) + + self._set_nginx_proxy_base() + error_msg = self._unset_nginx_proxy(self.config[idx]) + if self.ws_type == "nginx" and error_msg: + return error_msg + + error_msg = self.remove_apache_proxy(self.config[idx]) + if self.ws_type == "apache" and error_msg: + return error_msg + + error_msg = self.add_nginx_proxy(proxy_data) + if self.ws_type == "nginx" and error_msg: + return error_msg + + error_msg = self.add_apache_proxy(proxy_data) + if self.ws_type == "apache" and error_msg: + return error_msg + + self.config[idx] = proxy_data + self.save_config() + service_reload() + + def remove_proxy(self, site_name, proxy_id, multiple=False) -> Optional[str]: + idx = None + for index, i in enumerate(self.config): + if i["proxy_id"] == proxy_id and i["site_name"] == site_name: + idx = index + + if idx is None: + return "未找到该名称的反向代理配置" + + err_msg = self._unset_nginx_proxy(self.config[idx]) + if err_msg and self.ws_type == "nginx": + return err_msg + + error_msg = self.remove_apache_proxy(self.config[idx]) + if self.ws_type == "apache" and error_msg: + return error_msg + + del self.config[idx] + self.save_config() + if not multiple: + service_reload() + + def get_proxy_list_by_nginx(self) -> List[Dict[str, Any]]: + ng_conf = read_file(self._ng_file) + if not isinstance(ng_conf, str): + return [] + + rep_location = re.compile(r"\s*location\s+([=*~^]*\s+)?(?P\S+)\s*{") + proxy_location_path_info = {} + for tmp in rep_location.finditer(ng_conf): + end_idx = self.find_nginx_block_end(ng_conf, tmp.end() + 1) + if end_idx and ng_conf[tmp.start(): end_idx].find("proxy_pass") != -1: + p = tmp.group("path") + if not p.endswith("/"): + p += "/" + proxy_location_path_info[p] = (tmp.start(), end_idx) + + res_pass = re.compile(r"proxy_pass\s+(?P\S+)\s*;", re.M) + remove_list = [] + local_host = ("127.0.0.1", "localhost", "0.0.0.0") + for i in self.config: + if i["proxy_dir"] in proxy_location_path_info: + start_idx, end_idx = proxy_location_path_info[i["proxy_dir"]] + block = ng_conf[start_idx: end_idx] + res_pass_res = res_pass.search(block) + if res_pass_res: + url = parse_url(res_pass_res.group("pass")) + if isinstance(url, Url) and url.hostname in local_host and url.port == i["proxy_port"]: + i["status"] = True + proxy_location_path_info.pop(i["proxy_dir"]) + continue + + remove_list.append(i) + + need_save = False + for i in remove_list: + self.config.remove(i) + need_save = True + + for path, (start_idx, end_idx) in proxy_location_path_info.items(): + block = ng_conf[start_idx: end_idx] + res_pass_res = res_pass.search(block) + if res_pass_res: + url = parse_url(res_pass_res.group("pass")) + if isinstance(url, Url) and url.hostname in ("127.0.0.1", "localhost", "0.0.0.0"): + self.config.insert(0, { + "proxy_id": self.new_id(), + "site_name": self.site_name, + "proxy_dir": "/", + "proxy_port": url.port, + "status": 1, + "rewrite": { + "status": False, + "src_path": "", + "target_path": "", + }, + "add_headers": [], + }) + need_save = True + if need_save: + self.save_config() + + return self.config + + def get_proxy_list_by_apache(self) -> List[Dict[str, Any]]: + ap_conf = read_file(self._ap_file) + if not isinstance(ap_conf, str): + return [] + + rep_proxy_pass = r"ProxyPass\s+%s\s+\S+/" + mian_location_use = False + for i in self.config: + if i["proxy_dir"] == "/": + mian_location_use = True + rep_l = re.search(rep_proxy_pass % i["proxy_dir"], ap_conf, re.M) + if rep_l: + i["status"] = 1 + else: + i["status"] = 0 + + if not mian_location_use: + res_l = re.search( + r"\s*\s*(.*\n){3,5}\s*" + r"ProxyPass\s+/\s+(?P\S+)/\s*(.*\n){1,2}\s*", ap_conf) + + if not res_l: + return self.config + + url = parse_url(res_l.group("pass")) + if isinstance(url, Url) and url.hostname in ("127.0.0.1", "localhost", "0.0.0.0"): + self.config.insert(0, { + "proxy_id": self.new_id(), + "site_name": self.site_name, + "proxy_dir": "/", + "proxy_port": url.port, + "status": 1, + "rewrite": { + "status": False, + "src_path": "", + "target_path": "", + }, + "add_headers": [], + }) + self.save_config() + + return self.config + + def get_proxy_list(self) -> List[Dict[str, Any]]: + if self.ws_type == "nginx": + return self.get_proxy_list_by_nginx() + else: + return self.get_proxy_list_by_apache() + + diff --git a/mod/project/java/springboot_parser.py b/mod/project/java/springboot_parser.py new file mode 100644 index 00000000..bc8615bc --- /dev/null +++ b/mod/project/java/springboot_parser.py @@ -0,0 +1,1047 @@ +import itertools +import json +import os +import re +import zipfile + +import psutil +import yaml +import public +from mod.project.java import utils + +from typing import Optional, List, Tuple, AnyStr, Dict, Callable + +""" +针对jar包的【spring boot】配置文件加载逻辑 + +原理:spring boot 会在固定路径下寻找配置文件 + PWD = 运行目录 + CLASSES = jar包中的类目录 + 默认路径顺序: + PWD/config > PWD > CLASSES/config > CLASSES + +# 补充 + --spring.config.name == application 可以用于指定配置文件前缀 + +这个些路径可以被配置项spring.config.location 更改,可以指定为多个目录或文件,但该配置项目,一般不会在配置文件中使用,而是在命令行 +或者环境变量中使用。 +在加载完成application.properties 或 application.yml文件后,一般会根据配置项spring.profiles.active 来加载 +子配制项, 例如 spring.profiles.active=dev, 则会加载application-dev.properties 或 application-dev.yml文件。 +同时,spring boot 会根据配置项spring.profiles.include 来加载子配制项, 例如 spring.profiles.include=dev1,dev2, +则会加载application-dev1(2).properties 或 application-dev1(2).yml文件。 +按照激活顺序,后面的配置项将会覆盖前面的配置项。 + +故: 该模块主要支持从jar、命令行中环境变量中获取spring boot配置信息 + +判断依据:命令行 > 环境变量 > jar包中配置文件 +命令行 和 环境变量 + 先检查spring.config.location (SPRING_CONFIG_LOCATION) + 有: 从location 位置加载 + 无: 从jar加载 + 在检查 spring.profiles.active (SPRING_CONFIG_ACTIVE) + 有: 记录一下激活的 flag + 无: 不记录 + + 解析文件 + 先从 jar 和 location 加载所有配置 + 然后 如果没有 flag : 从配置所有 主配置 (application.properties 或 application.yml)中加载 flag项(spring.profiles.active) + 然后 从包含的 flag 和 主配置 加载 spring.profiles.include + + 最后 组合这些项目并分析其中的外部依赖 + +""" + + +class SpringConfigParser: + other_server_keywords = ( + "redis", + "rabbitmq", + "rocketmq", + "kafka", + "elasticsearch", + ) + rep_jdbc_url = re.compile(r"jdbc:(?P\S+)://(?P.*?):(?P\d+)/(?P[^?\s]*)") + localhost_key = ("localhost", "127.0.0.1", "0.0.0.0", public.get_server_ip(), public.get_network_ip()) + + # 其他服务解析host时, 不支持 ${XXX} 的格式 + rep_host_port = re.compile( + r'^(?P(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|' # ipv4 + r'(\[?(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::[0-9a-fA-F]{0,4})]?)|localhost))' # ipv6 + localhost + r':(?P[0-9]{1,5})$') # 端口 + + def __init__(self, jar_path: str, process: int = -1, cmd: str = None, + env_list: List[dict] = None, env_file: str = None): + self.jar_path = jar_path + self.find_flag_list = [] # 获取到的配置文件flag列表 + self.location_conf_list = [] # 从外部加载的配置文件列表 + self.not_used_flag_list = [] # 从外部加载的配置文件列表 + self.cmd = cmd + self.pwd: str = "" + self.config_name = "application" + if not isinstance(process, int) or process <= 0: + process = -1 + try: + p = psutil.Process(process) + self.pwd = p.cwd() + except: + self.process_env = {} + else: + self.process_env = p.environ() + if not self.cmd: + self.cmd = " ".join(p.cmdline()) + + self.find_by_default = True + + if not self.pwd or not os.path.isdir(self.pwd): + self.pwd = os.path.dirname(self.jar_path) + + self.my_sql_root_auth = None + + if env_list: + for i in env_list: + if "k" in i and "v" in i: + self.process_env[i["k"]] = i["v"] + break + if env_file: + self.process_env.update(self.get_env_file_data(env_file)) + + self.location_tip_list = [] + self.raw_data = {} + + @staticmethod + def get_env_file_data(env_file) -> dict: + if not os.path.exists(env_file): + return {} + data = {} + out, _ = public.ExecShell(". {} && env".format(env_file), env={"PATH": os.environ["PATH"]}) + for i in out: + if "=" not in i: + continue + k, v = i.split("=", 1) + if k in ("PATH", "SHLVL", "PWD", "_"): + continue + if k and v: + data[k] = v + + def format_location_conf(self, location_str: str) -> List[str]: + res = [] + for i in location_str.split(","): + tmp = i.strip() + if not tmp: + continue + if tmp.startswith("file:"): + tmp = tmp[5:] + if tmp.startswith("/"): + if os.path.exists(tmp): + res.append(tmp) + else: + self.location_tip_list.append({ + "type": "config_location", + "level": "error", + "msg": "配置文件路径:{}不存在于文件系统中".format(tmp) + }) + else: + path = os.path.abspath(os.path.join(self.pwd, tmp)) + if os.path.exists(path): + res.append(os.path.join(self.pwd, tmp)) + else: + self.location_tip_list.append({ + "type": "config_location", + "level": "error", + "msg": "配置文件路径:{}不存在于文件系统中".format(tmp) + }) + + return res + + def _parser_by_cmd_or_env(self): + """从命令行或者环境变量中获取配置信息""" + # 检查 location 配置项 + # 先从cmd中获取, 再从环境变量中获取 + location_find = False + name_find = False + flag_find = False + if self.cmd: + rep_location = re.compile(r"-[-D]spring\.config\.location=(?P\S+)") + rep_name = re.compile(r"-[-D]spring\.config\.name=(?P\S+)") + rep_flag = re.compile(r"-[-D]spring\.profiles\.active=(?P\S+)") + + res = rep_location.search(self.cmd) + if res: + location_find = True + location_str = res.group("location").strip("'\"") + location_file = self.format_location_conf(location_str) + self.location_conf_list.extend(location_file) + self.find_by_default = False + + res = rep_name.search(self.cmd) + if res: + name_find = True + self.config_name = res.group("name").strip().strip("'\"") + + res = rep_flag.search(self.cmd) + if res: + flag_find = True + flags = res.group("flag").strip("'\"").split(",") + self.find_flag_list.extend(flags) + + if self.process_env: + if "SPRING_PROFILES_ACTIVE" in self.process_env and not flag_find: + flags = self.process_env["SPRING_PROFILES_ACTIVE"].split(",") + self.find_flag_list.extend(flags) + + if "SPRING_CONFIG_NAME" in self.process_env and not name_find: + self.config_name = self.process_env["SPRING_CONFIG_NAME"] + + if "SPRING_CONFIG_LOCATION" in self.process_env and not location_find: + location_str = self.process_env["SPRING_CONFIG_LOCATION"] + location_file = self.format_location_conf(location_str) + self.location_conf_list.extend(location_file) + self.find_by_default = False + + # 从默认路径结构中获取配置文件信息 + def _get_all_config_by_default(self) -> List[Tuple[str, Dict]]: + y_jar, p_jar = self.get_jar_config(self.jar_path) + y_custom, p_custom = self.search_config_file_by_path(self.pwd) + if os.path.exists(os.path.join(self.pwd, "config")): + y_custom_c, p_custom_c = self.search_config_file_by_path(os.path.join(self.pwd, "config")) + y_custom += y_custom_c + p_custom += p_custom_c + + y_jar = self.to_utf8(y_jar) + p_jar = self.to_utf8(p_jar) + + for i, data in itertools.chain(y_jar, y_custom, p_jar, p_custom): + self.raw_data[i] = data + + return self.parse_application_yaml(y_jar + y_custom) + self.parse_application_properties(p_jar + p_custom) + + def _get_all_config_by_location(self) -> List[Tuple[str, Dict]]: + y_list, p_list = [], [] + for i in self.location_conf_list: + if os.path.isdir(i): + y_list_tmp, p_list_tmp = self.search_config_file_by_path(i) + y_list.extend(y_list_tmp) + p_list.extend(p_list_tmp) + elif os.path.isfile(i): + if i.endswith(".yml") or i.endswith(".yaml"): + data = public.readFile(i) + if data: + y_list.append((i, data)) + elif i.endswith(".properties"): + data = public.readFile(i) + if data: + p_list.append((i, data)) + + for i, data in itertools.chain(y_list, p_list): + self.raw_data[i] = data + + return self.parse_application_yaml(y_list) + self.parse_application_properties(p_list) + + def app_config(self) -> Tuple[List[Tuple[str, dict]], List[Tuple[str, dict]]]: + """获取所有的配置信息, 并分为主配置和其他配置""" + self._parser_by_cmd_or_env() + if self.find_by_default: + all_config = self._get_all_config_by_default() + else: + all_config = self._get_all_config_by_location() + + mian_conf, other_conf = [], [] + main_name = [self.config_name + i for i in (".yml", ".yaml", ".properties")] + for i, conf in all_config: + file_name = os.path.basename(i) + if file_name in main_name: + mian_conf.append((i, conf)) + else: + other_conf.append((i, conf)) + + used_conf = mian_conf + self._get_flags_by_mian_conf(mian_conf) + + use_flag = [] + ned_del_other_conf = [] + if self.find_flag_list: + for idx, conf_data in enumerate(other_conf): + file_name = os.path.basename(conf_data[0]) + tmp_flag = file_name[len(self.config_name) + 1:].rsplit(".", 1)[0] + if tmp_flag in self.find_flag_list: + use_flag.append(tmp_flag) + used_conf.append(conf_data) + ned_del_other_conf.append(idx) + + self.not_used_flag_list = [i for i in self.find_flag_list if i not in use_flag] + + include_list = self._get_include_by_config_list(used_conf) + if include_list: + for idx, conf_data in enumerate(other_conf): + file_name = os.path.basename(conf_data[0]) + tmp_flag = file_name[len(self.config_name) + 1:].rsplit(".", 1)[0] + if tmp_flag in include_list: + used_conf.append(conf_data) + ned_del_other_conf.append(idx) + + for i in ned_del_other_conf: + del other_conf[i] + + cmd_or_env_data = self.get_cmd_or_env_config() + used_conf.append(("命令行或环境变量", cmd_or_env_data)) # 加到最后,优先级最高 + # public.print_log(used_conf) + return used_conf, other_conf + + def get_cmd_or_env_config(self) -> dict: + data = {} + if self.process_env: + for k, v in self.process_env.items(): + if not k.startswith("SPRING"): + continue + + key_list = [j.strip().lower() for j in k.split("_") if j.strip()] + node = data + for n in key_list[:-1]: + if n not in node: + node[n] = {} + node = node[n] + node[key_list[-1]] = v + + if self.cmd: + rep_spring = re.compile(r"-[-D]spring\.(?P\S*?)=(?P\S+)") + for i in rep_spring.finditer(self.cmd): + key = "spring." + i.group("key").lower() + value = i.group("value") + key_list = [j.strip() for j in key.split(".") if j.strip()] + node = data + for n in key_list[:-1]: + if n not in node: + node[n] = {} + node = node[n] + node[key_list[-1]] = value + + return data + + def _get_flags_by_mian_conf(self, mian_conf: List[Tuple[str, dict]]): + if self.find_flag_list: # 在命令和环境变量中找到了就不需要了 + return + + flags = "" + for i, conf in mian_conf: + tmp_f = conf.get("spring", {}).get("profiles", {}).get("active", None) # active 只有最后一个生效 + if isinstance(tmp_f, str): + flags = tmp_f + + if isinstance(flags, str) and flags: + self.find_flag_list.extend([i.strip() for i in flags.split(",") if i.strip()]) + + @staticmethod + def _get_include_by_config_list(config_list: List[Tuple[str, dict]]) -> List[str]: + include_list = [] + for i, conf in config_list: + flags = conf.get("spring", {}).get("profiles", {}).get("include", None) + if isinstance(flags, str): + include_list.extend([i.strip() for i in flags.split(",") if i.strip()]) + return include_list + + def get_jar_config(self, jar_file: str) -> Tuple[List[Tuple[str, AnyStr]], List[Tuple[str, AnyStr]]]: + """获取jar文件中的配置文件""" + if not os.path.exists(jar_file): + return [], [] + if not zipfile.is_zipfile(jar_file): # 判断是否为zip文件 + return [], [] + # 打开jar文件 + yaml_list = [] + prop_list = [] + with zipfile.ZipFile(jar_file, 'r') as jar: + for i in jar.namelist(): + # 查询所有文件中可能是配置文件的项目 + i_base_name = os.path.basename(i) + if i_base_name.find(self.config_name) == -1: + continue + + try: + if i_base_name.endswith(".yml") or i_base_name.endswith(".yaml"): + with jar.open(i) as f: + yaml_list.append((i, f.read())) + + if i.endswith(".properties"): + with jar.open(i) as f: + prop_list.append((i, f.read())) + except: + # public.print_log("压缩文件读取错误" + public.get_error_info()) + continue + + return yaml_list, prop_list + + # 检测是不是项目的目录 + # 暂不使用 + @staticmethod + def test_spring_boot_name(spring_path: str, target_name: str) -> bool: + if os.path.isfile(spring_path + "/pom.xml"): + data = public.readFile(spring_path + "/pom.xml") + if data: + start_idx = data.find("") + if start_idx != -1: + name = data[start_idx + 12: data.find("", start_idx)] + if target_name.startswith(name): + return True + + if os.path.isfile(spring_path + "/build.gradle"): + data = public.readFile(spring_path + "/build.gradle") + if data: + base_name_rep = re.compile(r'''archivesBaseName\s*=\s*['"]?(?P\S+)["']?''', re.M) + res = base_name_rep.search(data) + if res and target_name.startswith(res.group("name")): + return True + return False + + # 暂不使用 + def get_spring_project_src_by_path(self, path: str, target_name: str) -> Optional[str]: + """" + 尝试寻找项目源文件位置 + 寻找本层级、父层级、和两个子层级的spring boot项目目录 "src/main/resources"目录 和 "src/pom.xml" + """ + if not os.path.isdir(path): + return None + + parent_path = os.path.dirname(path) + if os.path.isdir(parent_path + "/src/main/resources"): + if self.test_spring_boot_name(parent_path, target_name): + return parent_path + + def _get_by_sub(inpt_path: str, limit_num: int) -> Optional[str]: + if limit_num < 0: + return None + + for i in os.listdir(inpt_path): + if i.startswith("."): + continue + p = os.path.join(inpt_path, i) + if os.path.isdir(p): + if os.path.isdir(p + "/src/main/resources"): + if self.test_spring_boot_name(p, target_name): + return p + if limit_num >= 1: + res = _get_by_sub(p, limit_num - 1) + if res is not None: + return res + return None + + return _get_by_sub(path, 2) + + # 暂不使用 + def get_custom_spring_config(self, path: str) -> Tuple[List[Tuple[str, AnyStr]], List[Tuple[str, AnyStr]]]: + """ + 尝试寻找外部配置文件 + 寻找本层级和子层级的spring boot配置文件 + """ + if not os.path.isdir(path): + return [], [] + + yaml_list, prop_list = [], [] + + def _get_by_sub(inpt_path: str, limit_num: int): + if limit_num < 0: + return None + y, p = self.search_config_file_by_path(inpt_path) + yaml_list.extend(y) + prop_list.extend(p) + + for i in os.listdir(inpt_path): + tmp_p = os.path.join(inpt_path, i) + if os.path.isdir(tmp_p): + _get_by_sub(tmp_p, limit_num - 1) + + _get_by_sub(path, 2) + return yaml_list, prop_list + + # 用于搜索外部配置文件 + def search_config_file_by_path(self, path: str) -> Tuple[List[Tuple[str, AnyStr]], List[Tuple[str, AnyStr]]]: + yaml_list, prop_list = [], [] + for i in os.listdir(path): + # 查询所有文件中可能是配置文件的项目 + p = os.path.join(path, i) + if i.find(self.config_name) != -1 and os.path.isfile(p): + if i.endswith(".yml") or i.endswith(".yaml"): + tmp_data = public.readFile(p) + if isinstance(tmp_data, str): + yaml_list.append((p, tmp_data)) + + if i.endswith(".properties"): + tmp_data = public.readFile(p) + if isinstance(tmp_data, str): + prop_list.append((p, tmp_data)) + + return yaml_list, prop_list + + @staticmethod + def to_utf8(file_data_list: List[Tuple[str, AnyStr]]) -> List[Tuple[str, str]]: + res_list = [] + for i, data in file_data_list: + if isinstance(data, bytes): + try: + new_data = data.decode("utf-8") + except: + continue + else: + res_list.append((i, new_data)) + return res_list + + # 合并两个配置文件 + @classmethod + def merge_dict_tries(cls, root: dict, node: dict): + # 合并字典树, 用于分档的yaml配置文件 + for k, v in node.items(): + if isinstance(v, dict): + if k not in root: + root[k] = {} + cls.merge_dict_tries(root[k], v) + else: + root[k] = v + + @classmethod + def parse_application_yaml(cls, conf_data_list: List[Tuple[str, AnyStr]]) -> List[Tuple[str, Dict]]: + res_list = [] + for i, data in conf_data_list: + try: + d = yaml.safe_load_all(data) + if isinstance(d, dict): + res_list.append((i, d)) + else: + tmp = {} + for j in d: + cls.merge_dict_tries(tmp, j) + res_list.append((i, tmp)) + except: + print("yaml解析错误", i) + continue + + return res_list + + @staticmethod + def _parse_application_properties(data: str) -> dict: + res_dict = {} + + # 添加时处理key + def add_to_res_dict(res_dict_data: dict, tmp_k: str, tmp_value: str): + key_list = tmp_k.split(".") + if len(key_list) == 1: + res_dict_data[key_list[0]] = tmp_value + return + + node = res_dict_data + for n in key_list[:-1]: + if n not in node: + node[n] = {} + node = node[n] + node[key_list[-1]] = tmp_value + + last_line = "" + for line in data.split("\n"): + line = line.lstrip() # type: str + if line.startswith("#"): + continue + if line.endswith("\\"): + last_line += line.rstrip("\\") + continue + else: + if last_line: + line = last_line + line + last_line = "" + if "=" in line: + k, v = line.split("=", 1) + tmp_v = v.strip() + + if "#" not in tmp_v and not tmp_v.startswith("'") and not tmp_v.startswith('"'): + add_to_res_dict(res_dict, k.strip(), tmp_v) + continue + + if (tmp_v.startswith("'") and tmp_v.endswith("'")) or (tmp_v.startswith('"') and tmp_v.endswith('"')): + value = tmp_v.strip("\"'") + add_to_res_dict(res_dict, k.strip(), value) + continue + + if tmp_v.startswith("#"): + continue + + last_idx = 0 + for _ in range(tmp_v.count("#")): + idx = tmp_v.find("#", last_idx + 1) + last_idx = idx + if tmp_v[idx - 1] != "\\": + break + + add_to_res_dict(res_dict, k.strip(), tmp_v[:last_idx].strip()) + + return res_dict + + @classmethod + def parse_application_properties(cls, conf_data_list: List[Tuple[str, AnyStr]]) -> List[Tuple[str, Dict]]: + res_list = [] + for i, data in conf_data_list: + try: + tmp = cls._parse_application_properties(data) + if tmp: + res_list.append((i, tmp)) + except: + print("properties解析错误", i) + continue + + return res_list + + def check_config_env(self, use_conf: List[Tuple[str, dict]]): + """ + 检测项目依赖的环境是否存在 + MySQL、Redis、RabbitMQ、Kafka、Elasticsearch、MongoDB + + Mysql检测用户是否存在 + """ + db_conf = {} + other_server_conf = {} + for i, spring_conf in use_conf: # 检查的同时根据配置路径即 .spring.data.redis + self.check_one_config_env(spring_conf, i, db_conf, other_server_conf) + + l_db, s_db = self.check_db_env(list(db_conf.values())) + l_service, s_service = self.check_service_env(list(other_server_conf.values())) + return l_db, s_db, l_service, s_service + + def check_one_config_env(self, + conf: dict, + file: str, + db_conf: Dict[str, dict] = None, + other_server_conf: Dict[str, dict] = None, + ) -> None: + + def _parse_keyword_nodes(node: dict) -> Tuple[str, str]: + """ + 形式1: 使用:分割 + # Kafka + spring.kafka.bootstrap-servers = localhost:9092 + # 该字段见 Kafka 安装包中的 consumer.proerties,可自行修改, 修改完毕后需要重启 Kafka + spring.kafka.consumer.group-id = test-consumer-group + spring.kafka.consumer.enable-auto-commit = true + spring.kafka.consumer.auto-commit-interval = 3000 + + 形式2: + spring.data.redis.init.database = 11 + spring.data.redis.init.host = localhost + spring.data.redis.init.port = 6379 + """ + host, port = "", "" + if "host" in node: + host = node["host"] + if "port" in node: + port = node["port"] + + if host and port: + return host, port + + for v in node.values(): + if isinstance(v, dict): + host, port = _parse_keyword_nodes(v) + if host and port: + return host, port + + elif isinstance(v, str): + if ":" not in v: + continue + res = self.rep_host_port.search(v) + if res: + return res.group("host"), res.group("port") + return "", "" + + def traversal_all_node(node: dict, last_key: str): + for k, v in node.items(): + if not isinstance(v, dict): # 如果子层级不是字典,不在检查 + continue + # 检查是不是一个数据库配置, 如果是就不必要检查子层级 + if "url" in v and isinstance(v["url"], str): + res = self.rep_jdbc_url.search(v["url"]) + if res: + tmp = { + "url": v["url"], + "db": res.group("db"), + "host": res.group("host"), + "port": res.group("port"), + "name": res.group("name"), + "file": file, + } + if last_key + "." + k in db_conf: + db_conf[last_key + "." + k].update(tmp) + if "password" in v: + db_conf[last_key + "." + k]["password"] = v["password"] + if "username" in v: + db_conf[last_key + "." + k]["username"] = v["username"] + else: + tmp["password"] = v.get("password", "") + tmp["username"] = v.get("username", "") + db_conf[last_key + "." + k] = tmp + continue + + # 检测关键字 + if k in self.other_server_keywords: + host, port = _parse_keyword_nodes(v) + if host and port: + tmp = { + "key": k, + "host": host, + "port": port, + "file": file, + } + if last_key + "." + k in other_server_conf: + other_server_conf[last_key + "." + k].update(tmp) + else: + other_server_conf[last_key + "." + k] = tmp + continue + + # 不是一个数据库配置, 检查子层级 + traversal_all_node(v, last_key + "." + k) + + traversal_all_node(conf, "") + + # 处理 ${XXX} 格式的名称 + for i in db_conf.values(): + for tmp_k, tmp_v in i.items(): + if isinstance(tmp_v, str) and tmp_v.startswith("${") and tmp_v.endswith("}"): + tmp_v_list = tmp_v[2:-1].split(".") + tmp_node = conf + for j in tmp_v_list[:-1]: + tmp_node = tmp_node.get(j, {}) + i[tmp_k] = tmp_node.get(tmp_v_list[-1], "") + + return None + + def check_db_env(self, db_conf: List[dict]): + local_conf = [] + server_conf = [] + for i in db_conf: + i["database"] = True # 远程数据库默认不检查 + i["listening"] = True + i["auth"] = True + if i["host"] in self.localhost_key: + i["is_local"] = True + local_conf.append(i) + if i["db"] in ("mysql", "postgresql", "mongodb"): + i["database"], i["auth"] = self.has_database(i["db"], i["name"], i["username"], i["password"]) + + i["listening"] = False + try: + i["listening"] = utils.check_port_with_net_connections(int(i["port"])) is False + except: + pass + + else: + i["is_local"] = False + i["listening"] = self.port_is_open(i["host"], i["port"]) + server_conf.append(i) + + return local_conf, server_conf + + def check_service_env(self, service_conf: List[dict]): + local_conf = [] + server_conf = [] + for i in service_conf: + i["listening"] = True # 远程数据库默认不检查 + if i["host"] in self.localhost_key: + i["is_local"] = True + local_conf.append(i) + i["listening"] = False + try: + i["listening"] = utils.check_port_with_net_connections(int(i["port"])) is False + except: + pass + + else: + i["is_local"] = False + i["listening"] = self.port_is_open(i["host"], i["port"]) + server_conf.append(i) + + return local_conf, server_conf + + @staticmethod + def port_is_open(host: str, port): + import socket + try: + + # 创建一个socket对象 + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # 设置超时时间,防止连接挂起 + sock.settimeout(5) # 5秒超时 + # 尝试连接到指定的端口 + result = sock.connect_ex((host, int(port))) + # 关闭socket + sock.close() + # 如果连接成功,connect_ex返回0,否则返回错误代码 + return result == 0 + except socket.error as e: + print(f"Socket error: {e}") + return False + + def has_database(self, db_type: str, db_name: str, user_name: str, password: str) -> Tuple[bool, bool]: + """ + 返回数据库是否存在, 和数据库密码是否正确 + """ + if self.my_sql_root_auth is None and db_type == "mysql": + try: + self.my_sql_root_auth = public.M("config").where("id=1", ()).find()["mysql_root"] + except: + self.my_sql_root_auth = False + try: + find = public.M("databases").where("name=? AND LOWER(type)=?", (db_name, db_type.lower())).find() + if find: + if find["username"] == user_name and find["password"] == password: + return True, True # 数据库和密码都存在 + if user_name == "root" and db_type == "mysql": + if self.my_sql_root_auth == password or self.my_sql_root_auth is False: + return True, True # 数据库存在, 用的root密码 + return True, False # 数据库存在, 密码未知 + else: + if user_name == "root" and db_type == "mysql" and \ + (self.my_sql_root_auth == password or self.my_sql_root_auth is False): + + return False, True # 数据库不存在, 用的root密码 + return False, False + except: + return False, False + + # 被外部调用的函数 + def get_tip(self) -> List[dict]: + use, other = self.app_config() + l_db, s_db, l_service, s_service = self.check_config_env(use) + res = [] + # error 大概率存在错误的 + # 配置文件路径不存在 + if self.location_tip_list: + res.extend(self.location_tip_list) + + # 本地数据库不存在 + for i in l_db: + if i["listening"] is False: + res.append({ + "type": "local_database", + "level": "error", + "msg": "链接本地{}数据库的端口{}未被监听,可能是配置错误或数据库服务未开启".format(i["db"], i["port"]) + }) + + # 本地数据连接不上 + for i in l_db: + if i["auth"] is False: + res.append({ + "type": "local_database_auth", + "level": "warn", + "file": i["file"], + "msg": "链接到本地的{}数据库【{}】的用户名【{}】或密码【{}】可能错误".format( + i["db"], i["name"], i["username"], i["password"]) + }) + + # warn 可能存在错误的 + # 本地数据库中没有数据数据库 + if l_db: + for i in l_db: + if i["database"] is False: + res.append({ + "type": "local_database", + "level": "warn", + "file": i["file"], + "msg": "本地的{}数据库服务中未检测到数据库【{}】".format(i["db"], i["name"]) + }) + # 本地服务未监听端口 + if l_service: + for i in l_service: + if i["listening"] is False: + res.append({ + "type": "local_service", + "level": "warn", + "file": i["file"], + "msg": "链接本地【{}服务】的端口{}未被监听,可能是配置错误或服务未开启".format(i["key"], i["port"]) + }) + + # tip 有较低可能得存在的问题 + # 远程服务 或 远程数据库 + + if s_db or s_service: + for i in itertools.chain(s_db, s_service): + if i["listening"] is False: + if "db" in i: + msg = "链接远程{}数据库服务【{}:{}/{}】,可能存在问题".format( + i["db"], i["host"], i["port"], i["name"] + ) + else: + msg = "链接远程{}服务【{}:{}】,可能存在问题".format(i["key"], i["host"], i["port"]) + res.append({ + "type": "service", + "level": "tip", + "file": i["file"], + "msg": msg + }) + + if self.not_used_flag_list: + res.append({ + "type": "spring_profiles", + "level": "tip", + "file": None, + "msg": "配置文件中提及了这些profiles ->【{}】,但实际未找到相关配置文件".format(",".join(self.not_used_flag_list)) + }) + + return res + + +class SpringLogConfigParser(SpringConfigParser): + """ + 日志解析部分,主要依靠在配置中指定的logging.config + """ + + @staticmethod + def get_config_data_by_use(use: List[Tuple[str, dict]], key: str) -> Optional[str]: + key_list = [i for i in key.split(".") if i] + if len(key_list) == 0: + return None + + res_data = None + for _, config in use: + tmp_config = config + for k in key_list: + if k not in tmp_config: + break + tmp_config = tmp_config[k] + else: + # 如果没有退出表示上面的key依序存在,且最后一次取出来的就是所需要的值 + res_data = tmp_config + + if not res_data or not isinstance(res_data, str): + return None + + return res_data + + # 获取jar包内文件 + @staticmethod + def get_jar_file_path(jar_file: str, jar_file_path: str) -> str: + if not zipfile.is_zipfile(jar_file): # 判断是否为zip文件 + return "" + # 打开jar文件 + data = b'' + with zipfile.ZipFile(jar_file, 'r') as jar: + if jar_file_path in jar.namelist(): + with jar.open(jar_file_path) as f: + data = f.read() + + if isinstance(data, bytes): + try: + return data.decode("utf-8") + except: + pass + elif isinstance(data, str): + return data + + return "" + + def get_all_log_ptah(self) -> List[str]: + use, other = self.app_config() + logging_config_data = self.get_config_data_by_use(use, "logging.config") + if not logging_config_data: + return [] + if not logging_config_data.endswith(".xml"): + return [] + if logging_config_data.startswith("classpath:"): + jar_file_path = "BOOT-INF/classes/{}".format(logging_config_data[10:].lstrip("/")) + file_data = self.get_jar_file_path(self.jar_path, jar_file_path) + filename = os.path.basename(jar_file_path) + else: + if logging_config_data.startswith("file:"): + logging_config_data = logging_config_data[5:] + file_data = public.readFile(logging_config_data) + filename = os.path.basename(logging_config_data) + + if not file_data or not isinstance(file_data, str): + return [] + + # 闭包处理获取信息的过程 + def get_config(key: str) -> Optional[str]: + return self.get_config_data_by_use(use, key) + if filename.find("logback") != -1: + return self.logback_parser(file_data, get_config) + elif filename.find("log4j") != -1: + return self.log4j_parser(file_data) + elif file_data.find("rollingPolicy") != -1: + return self.logback_parser(file_data, get_config) + elif file_data.find("RollingFile") != -1: + return self.log4j_parser(file_data) + + return [] + + # 从property标签加属性获取是通用的 + @staticmethod + def __update_property_by_attr(file_data: str, property_dict: dict): + rep_property = re.compile(r'''<[pP]roperty.*?/([pP]roperty)?>''') + rep_attr = re.compile(r'''(?P\S+)=['"](?P.*?)['"]''') + + for tmp in rep_property.finditer(file_data): + tmp_dict = {} + for attr in rep_attr.finditer(tmp.group()): + tmp_dict[attr.group("k")] = attr.group("v") + if "name" in tmp_dict and "value" in tmp_dict: + property_dict[tmp_dict["name"]] = tmp_dict["value"] + + def logback_parser(self, file_data: str, get_config: Callable[[str], Optional[str]]) -> List[str]: + property_dict = dict() + rep_spring_property = re.compile(r'''''') + rep_attr = re.compile(r'''(?P\S+)=['"](?P.*?)['"]''') + rep_file_name_pattern = re.compile(r"(?P.*?)[^}]*)}") + + for tmp in rep_spring_property.finditer(file_data): + tmp_dict = {} + for attr in rep_attr.finditer(tmp.group()): + tmp_dict[attr.group("k")] = attr.group("v") + if "name" in tmp_dict and "source" in tmp_dict: + source_data = get_config(tmp_dict["source"]) + if source_data: + property_dict[tmp_dict["name"]] = source_data + + self.__update_property_by_attr(file_data, property_dict) + + res_list = [] + for tmp in rep_file_name_pattern.finditer(file_data): + tmp_data = tmp.group("file_name_pattern") + for var in rep_var.finditer(tmp_data): + if var.group("var") in property_dict: + tmp_data = tmp_data.replace(var.group(), property_dict[var.group("var")]) + res_list.append(tmp_data) + return self.__normalize_res_list(res_list) + + def log4j_parser(self, file_data: str) -> List[str]: + property_dict = dict() + rep_property = re.compile(r'<[pP]roperty.*?>(?P.*?).+?)['"]''') + rep_file_name = re.compile(r'''<[Rr]ollingFile.*fileName=['"](?P[^'"]*?)['"](.|\n)*?>''') + rep_var = re.compile(r"\$\{(?P[^}]*)}") + + for tmp in rep_property.finditer(file_data): + tmp_search = rep_name_attr.search(tmp.group()) + if tmp_search: + property_dict[tmp_search.group("value")] = tmp.group("prop") + + self.__update_property_by_attr(file_data, property_dict) + + res_list = [] + for tmp in rep_file_name.finditer(file_data): + tmp_data = tmp.group("file_name") + for var in rep_var.finditer(tmp_data): + var_str = var.group("var") + if var_str in property_dict: + tmp_data = tmp_data.replace(var.group(), property_dict[var_str]) + + res_list.append(tmp_data) + + return self.__normalize_res_list(res_list) + + def __normalize_res_list(self, res_list: List[str]) -> List[str]: + res = [] + for i in res_list: + file_path = os.path.dirname(i) + if not file_path.startswith("/"): + file_path = os.path.abspath(os.path.join(self.pwd, file_path)) + # 可能还有占位符 + if os.path.basename(file_path).find("%") != -1 and not os.path.isdir(file_path): + file_path = os.path.dirname(file_path) + + if os.path.isdir(file_path) and file_path not in res: + res.append(file_path) + return res diff --git a/mod/project/java/utils.py b/mod/project/java/utils.py new file mode 100644 index 00000000..6c09fc0d --- /dev/null +++ b/mod/project/java/utils.py @@ -0,0 +1,946 @@ +import json +import re +import sys +import time +import zipfile +import os +import yaml +import psutil +import platform +from xml.etree.ElementTree import Element, ElementTree, parse, XMLParser +from typing import Optional, Dict, Tuple, AnyStr, List, Any +import threading +import itertools + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +if "/www/server/panel" not in sys.path: + sys.path.insert(0, "/www/server/panel") + +import public + + +def get_jar_war_config(jar_war_file: str) -> Optional[List[Tuple[str, AnyStr]]]: + """获取jar文件中的配置文件""" + if not os.path.exists(jar_war_file): + return None + if not zipfile.is_zipfile(jar_war_file): # 判断是否为zip文件 + return None + # 打开jar文件 + res_list = [] + with zipfile.ZipFile(jar_war_file, 'r') as jar: + for i in jar.namelist(): + # 查询所有文件中可能是配置文件的项目 + if i.endswith("application.yaml") or i.endswith("application.yml"): + with jar.open(i) as f: + res_list.append((i, f.read())) + + if not res_list: + return None + + return res_list + + +def to_utf8(file_data_list: List[Tuple[str, AnyStr]]) -> List[Tuple[str, str]]: + res_list = [] + for i, data in file_data_list: + if isinstance(data, bytes): + try: + new_data = data.decode("utf-8") + except: + continue + else: + res_list.append((i, new_data)) + return res_list + + +def parse_application_yaml(conf_data_list: List[Tuple[str, AnyStr]]) -> List[Tuple[str, Dict]]: + res_list = [] + for i, data in conf_data_list: + d = yaml.safe_load(data) + if isinstance(d, dict): + res_list.append((i, d)) + + return res_list + + +# 接收一个jdk路径并将其规范化 +def normalize_jdk_path(jdk_path: str) -> Optional[str]: + if jdk_path.endswith("/java"): + jdk_path = os.path.dirname(jdk_path) + if jdk_path.endswith("/bin"): + jdk_path = os.path.dirname(jdk_path) + if jdk_path.endswith("/jre"): + jdk_path = os.path.dirname(jdk_path) + if not os.path.isdir(jdk_path): + return None + if not os.path.exists(os.path.join(jdk_path, "bin/java")): + return None + return jdk_path + + +def test_jdk(jdk_path: str) -> bool: + java_bin = os.path.join(jdk_path, "bin/java") + if os.path.exists(java_bin): + out, err = public.ExecShell("{} -version 2>&1".format(java_bin)) # type: str, str + if out.lower().find("version") != -1: + return True + return False + + +class TomCat: + + def __init__(self, tomcat_path: str): + self.path = tomcat_path.rstrip("/") # 移除多余的右"/" 统一管理 + self._jdk_path: Optional[str] = None + self._config_xml: Optional[ElementTree] = None + self._bt_tomcat_conf: Optional[dict] = None + self._log_file = None + self._version = None + + @property + def jdk_path(self) -> Optional[str]: + p = os.path.join(self.path, "bin/daemon.sh") + if not os.path.exists(p): + return None + + tmp_data = public.readFile(p) + if isinstance(tmp_data, str): + rep_deemon_sh = re.compile(r"^JAVA_HOME=(?P.*)\n", re.M) + re_res_jdk_path = rep_deemon_sh.search(tmp_data) + if re_res_jdk_path: + self._jdk_path = re_res_jdk_path.group("path").strip() + self._jdk_path = normalize_jdk_path(self._jdk_path) + return self._jdk_path + + return None + + def version(self) -> Optional[int]: + if isinstance(self._version, int): + return self._version + v_file = os.path.join(self.path, "version.pl") + if os.path.isfile(v_file): + ver = public.readFile(v_file) + if isinstance(ver, str): + try: + ver_int = int(ver.split(".")[0]) + self._version = ver_int + return self._version + except: + pass + return None + + @property + def log_file(self) -> str: + if self._log_file is not None: + return self._log_file + default_file = os.path.join(self.path, "logs/catalina-daemon.out") + target_sh = os.path.join(self.path, "bin/daemon.sh") + file_data = public.readFile(target_sh) + conf_path = os.path.join(self.path, "conf/logpath.conf") + if not isinstance(file_data, str): + return default_file + rep = re.compile(r'''\n\s?test ?"\.\$CATALINA_OUT" ?= ?\. +&& +CATALINA_OUT=['"](?P\S+)['"]''') + if rep.search(file_data): + self._log_file = rep.search(file_data).group("path") + public.writeFile(conf_path, os.path.dirname(self._log_file)) + return self._log_file + + if os.path.isfile(conf_path): + path = public.readFile(conf_path) + else: + return default_file + log_file = os.path.join(path, "catalina-daemon.out") + if os.path.exists(log_file): + self._log_file = log_file + return self._log_file + + ver = self.version() + if ver: + log_file = os.path.join(path, "catalina-daemon-{}.out".format(ver)) + return log_file + else: + return os.path.join(path, "catalina-daemon.out") + + @property + def bt_tomcat_conf(self) -> Optional[dict]: + if self._bt_tomcat_conf is None: + p = os.path.join(self.path, "bt_tomcat.json") + if not os.path.exists(p): + self._bt_tomcat_conf = {} + return self._bt_tomcat_conf + try: + self._bt_tomcat_conf = json.loads(public.readFile(p)) + except: + self._bt_tomcat_conf = {} + return self._bt_tomcat_conf + + def save_bt_tomcat_conf(self): + if self._bt_tomcat_conf is not None: + p = os.path.join(self.path, "bt_tomcat.json") + public.writeFile(p, json.dumps(self._bt_tomcat_conf)) + + def change_log_path(self, log_path: str, prefix: str = "") -> bool: + log_path = log_path.rstrip("/") + target_sh = os.path.join(self.path, "bin/daemon.sh") + if not os.path.exists(target_sh): + return False + file_data = public.readFile(target_sh) + if not isinstance(file_data, str): + return False + rep = re.compile(r'''\n ?test ?"\.\$CATALINA_OUT" ?= ?\. && {0,3}CATALINA_OUT="[^\n]*"[^\n]*\n''') + if prefix and not prefix.startswith("-"): + prefix = "-{}".format(prefix) + repl = '\ntest ".$CATALINA_OUT" = . && CATALINA_OUT="{}/catalina-daemon{}.out"\n'.format(log_path, prefix) + file_data = rep.sub(repl, file_data) + public.writeFile(target_sh, file_data) + conf_path = os.path.join(self.path, "conf/logpath.conf") + public.WriteFile(conf_path, log_path) + return True + + @property + def config_xml(self) -> Optional[ElementTree]: + if self._config_xml is None: + p = os.path.join(self.path, "conf/server.xml") + if not os.path.exists(p): + return None + + self._config_xml = parse(p, parser=XMLParser(encoding="utf-8")) + return self._config_xml + + def set_port(self, port: int) -> bool: + if self.config_xml is None: + return False + conf_elem = self.config_xml.findall("Service/Connector") + if conf_elem is None: + return False + for i in conf_elem: + if 'protocol' in i.attrib and 'port' in i.attrib: + if i.attrib['protocol'] == 'HTTP/1.1': + i.attrib['port'] = str(port) + return True + return False + + def pid(self) -> Optional[int]: + pid_file = os.path.join(self.path, 'logs/catalina-daemon.pid') + if os.path.exists(pid_file): + # 使用psutil判断进程是否在运行 + try: + pid = public.readFile(pid_file) + return int(pid) + except: + return None + return None + + def port(self) -> int: + if self.config_xml is None: + return 0 + for i in self.config_xml.findall("Service/Connector"): + if i.attrib.get("protocol") == "HTTP/1.1" and 'port' in i.attrib: + return int(i.attrib.get("port")) + return 8080 + + @property + def installed(self) -> bool: + start_path = os.path.join(self.path, 'bin/daemon.sh') + conf_path = os.path.join(self.path, 'conf/server.xml') + if not os.path.exists(self.path): + return False + if not os.path.isfile(start_path): + return False + if not os.path.isfile(conf_path): + return False + return True + + def running(self) -> bool: + pid = self.pid() + if pid: + try: + p = psutil.Process(pid) + return p.is_running() + except: + return False + return False + + def status(self) -> dict: + return { + "status": os.path.exists(self.path) and os.path.exists(os.path.join(self.path, "bin/daemon.sh")), + "jdk_path": self.jdk_path, + "path": self.path, + "running": self.running(), + "port": self.port(), + "stype": "built" if os.path.exists(os.path.join(self.path, "conf/server.xml")) else "uninstall" + } + + def save_config_xml(self) -> bool: + if self.config_xml is None: + return False + p = os.path.join(self.path, "conf/server.xml") + + def _indent(elem: Element, level=0): + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + _indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + _indent(self.config_xml.getroot()) + self.config_xml.write(p, encoding="utf-8", xml_declaration=True) + return True + + def host_by_name(self, name: str) -> Optional[Element]: + if self.config_xml is None: + return None + engines = self.config_xml.findall("Service/Engine") + if not engines: + return None + engine = engines[0] + for h in engine: + if h.tag == "Host" and h.attrib.get("name", None) == name: + return h + return None + + def add_host(self, name: str, path: str) -> bool: + if self.config_xml is None: + return False + if not os.path.exists(path): + os.makedirs(path) + if self.host_by_name(name): + return False + engine = self.config_xml.findall("Service/Engine") + if not engine: + return False + path_name = "" + if os.path.isfile(path): + app_base = os.path.dirname(path) + if path.endswith(".war"): + path_name = os.path.basename(path).rsplit(".", 1)[0] + else: + app_base = path + + host = Element("Host", attrib={ + "appBase": app_base, + "autoDeploy": "true", + "name": name, + "unpackWARs": "true", + "xmlNamespaceAware": "false", + "xmlValidation": "false", + }) + + context = Element("Context", attrib={ + "docBase": path, + "path": path_name, + "reloadable": "true", + "crossContext": "true", + }) + host.append(context) + + engine[0].append(host) + return True + + def set_host_path_by_name(self, name: str, path: str) -> bool: + if self.config_xml is None: + return False + for i in self.config_xml.findall("Service/Engine/Host"): + if i.attrib.get("name", None) != name: + continue + for j in i: + if j.tag == "Context": + j.attrib["docBase"] = path + return True + return False + + def remove_host(self, name: str) -> bool: + if self.config_xml is None: + return False + target_host = self.host_by_name(name) + if not target_host: + return False + engine = self.config_xml.findall("Service/Engine") + if not engine: + return False + engine[0].remove(target_host) + return True + + def mutil_remove_host(self, name_list: List[str]) -> bool: + if self.config_xml is None: + return False + for name in name_list: + self.remove_host(name) + return False + + def start(self, by_user: str = "root") -> bool: + if not self.running(): + daemon_file = os.path.join(self.path, "bin/daemon.sh") + if not os.path.isfile(self.log_file): + public.ExecShell("touch {}".format(self.log_file)) + public.ExecShell("chown {}:{} {}".format(by_user, by_user, self.log_file)) + public.ExecShell("bash {} start".format(daemon_file), user=by_user) + + return self.running() + + def stop(self) -> bool: + if self.running(): + daemon_file = os.path.join(self.path, "bin/daemon.sh") + public.ExecShell("bash {} stop".format(daemon_file)) + return not self.running() + + def restart(self, by_user: str = "root") -> bool: + daemon_file = os.path.join(self.path, "bin/daemon.sh") + if self.running(): + public.ExecShell("bash {} stop".format(daemon_file)) + if not os.path.isfile(self.log_file): + public.ExecShell("touch {}".format(self.log_file)) + public.ExecShell("chown {}:{} {}".format(by_user, by_user, self.log_file)) + public.ExecShell("bash {} start".format(daemon_file), user=by_user) + return self.running() + + def replace_jdk(self, jdk_path: str) -> Optional[str]: + jdk_path = normalize_jdk_path(jdk_path) + if not jdk_path: + return "jdk路径错误或无法识别" + + deemon_sh_path = "{}/bin/daemon.sh".format(self.path) + if not os.path.isfile(deemon_sh_path): + return 'Tomcat启动文件丢失!' + + deemon_sh_data = public.readFile(deemon_sh_path) + if not isinstance(deemon_sh_data, str): + return 'Tomcat启动文件读取失败!' + + # deemon_sh + rep_deemon_sh = re.compile(r"^JAVA_HOME=(?P.*)\n", re.M) + re_res_deemon_sh = rep_deemon_sh.search(deemon_sh_data) + if not re_res_deemon_sh: + return 'Tomcat启动文件解析失败!' + + jsvc_make_path = None + for i in os.listdir(self.path + "/bin"): + tmp_dir = "{}/bin/{}".format(self.path, i) + if i.startswith("commons-daemon") and os.path.isdir(tmp_dir): + make_path = tmp_dir + "/unix" + if os.path.isdir(make_path): + jsvc_make_path = make_path + break + + if jsvc_make_path is None: + return 'Jsvc文件丢失!' + + # 重装jsvc + if os.path.isfile(self.path + "/bin/jsvc"): + os.rename(self.path + "/bin/jsvc", self.path + "/bin/jsvc_back") + + if os.path.isfile(jsvc_make_path + "/jsvc"): + os.remove(jsvc_make_path + "/jsvc") + + shell_str = r''' +cd {} +make clean +./configure --with-java={} +make + '''.format(jsvc_make_path, jdk_path) + public.ExecShell(shell_str) + if os.path.isfile(jsvc_make_path + "/jsvc"): + os.rename(jsvc_make_path + "/jsvc", self.path + "/bin/jsvc") + public.ExecShell("chmod +x {}/bin/jsvc".format(self.path)) + os.remove(self.path + "/bin/jsvc_back") + else: + os.rename(self.path + "/bin/jsvc_back", self.path + "/bin/jsvc") + return 'Jsvc编译失败!' + + new_deemon_sh_data = deemon_sh_data[:re_res_deemon_sh.start()] + ( + 'JAVA_HOME={}\n'.format(jdk_path) + ) + deemon_sh_data[re_res_deemon_sh.end():] + public.writeFile(deemon_sh_path, new_deemon_sh_data) + return None + + def reset_tomcat_server_config(self, port: int): + ret = ''' + + + + + + + + + + + + + + + + + + + +'''.format(create_a_not_used_port(), port) + public.WriteFile(self.path + '/conf/server.xml', ret) + + @staticmethod + def _get_os_version() -> str: + # 获取Centos + if os.path.exists('/usr/bin/yum') and os.path.exists('/etc/yum.conf'): + return 'Centos' + # 获取Ubuntu + if os.path.exists('/usr/bin/apt-get') and os.path.exists('/usr/bin/dpkg'): + return 'Ubuntu' + return 'Unknown' + + @classmethod + def async_install_tomcat_new(cls, version: str, jdk_path: Optional[str]) -> Optional[str]: + os_ver = cls._get_os_version() + if version == "7" and os_ver == 'Ubuntu': + return '操作系统不支持!' + + if jdk_path: + jdk_path = normalize_jdk_path(jdk_path) + if not jdk_path: + return 'jdk路径错误或无法识别' + if not test_jdk(jdk_path): + return '指定的jdk不可用' + + if not jdk_path: + jdk_path = '' + + shell_str = ( + 'rm -rf /tmp/1.sh && ' + '/usr/local/curl/bin/curl -o /tmp/1.sh %s/install/src/webserver/shell/new_jdk.sh && ' + 'bash /tmp/1.sh install %s %s' + ) % (public.get_url(), version, jdk_path) + + if not os.path.exists("/tmp/panelTask.pl"): # 如果当前任务队列并未执行,就把日志清空 + public.writeFile('/tmp/panelExec.log', '') + soft_name = "Java项目Tomcat-" + version + task_id = public.M('tasks').add( + 'id,name,type,status,addtime,execstr', + (None, '安装[{}]'.format(soft_name), 'execshell', '0', time.strftime('%Y-%m-%d %H:%M:%S'), shell_str)) + + cls._create_install_wait_msg(task_id, version) + + @staticmethod + def _create_install_wait_msg(task_id: int, version: str): + from panel_msg.msg_file import message_mgr + + file_path = "/tmp/panelExec.log" + if not os.path.exists(file_path): + public.writeFile(file_path, "") + + soft_name = "Java项目Tomcat-" + version + data = { + "soft_name": soft_name, + "install_status": "等待安装" + soft_name, + "file_name": file_path, + "self_type": "soft_install", + "status": 0, + "task_id": task_id + } + title = "等待安装" + soft_name + res = message_mgr.collect_message(title, ["Java环境管理", soft_name], data) + if isinstance(res, str): + public.WriteLog("消息盒子", "安装信息收集失败") + return None + return res + + +def bt_tomcat(ver: int) -> Optional[TomCat]: + if ver not in (7, 8, 9, 10) and ver not in ("7", "8", "9", "10"): + return None + return TomCat(tomcat_path="/usr/local/bttomcat/tomcat%d" % int(ver)) + + +def site_tomcat(site_name: str) -> Optional[TomCat]: + tomcat_path = os.path.join("/www/server/bt_tomcat_web", site_name) + if not os.path.exists(tomcat_path): + return None + return TomCat(tomcat_path=tomcat_path) + + +class JDKManager: + + def __init__(self): + self._versions_list: Optional[List[str]] = None + self._custom_jdk_list: Optional[List[str]] = None + self._jdk_path = "/www/server/java" + self._custom_file = "/www/server/panel/data/get_local_jdk.json" + if not os.path.exists(self._jdk_path): + os.makedirs(self._jdk_path, 0o755) + + @property + def versions_list(self) -> List[str]: + if self._versions_list: + return self._versions_list + jdk_json_file = '/www/server/panel/data/jdk.json' + tip_file = '/www/server/panel/data/jdk.json.pl' + try: + last_refresh = int(public.readFile(tip_file)) + except ValueError: + last_refresh = 0 + versions_data = public.readFile(jdk_json_file) + if time.time() - last_refresh > 3600: + public.run_thread(public.downloadFile, ('{}/src/jdk/jdk.json'.format(public.get_url()), jdk_json_file)) + public.writeFile(tip_file, str(int(time.time()))) + + try: + versions = json.loads(versions_data) + except Exception: + versions = { + "x64": [ + "jdk1.7.0_80", "jdk1.8.0_371", "jdk-9.0.4", "jdk-10.0.2", + "jdk-11.0.19", "jdk-12.0.2", "jdk-13.0.2", "jdk-14.0.2", + "jdk-15.0.2", "jdk-16.0.2", "jdk-17.0.8", "jdk-18.0.2.1", + "jdk-19.0.2", "jdk-20.0.2" + ], + "arm": [ + "jdk1.8.0_371", "jdk-11.0.19", "jdk-15.0.2", "jdk-16.0.2", + "jdk-17.0.8", "jdk-18.0.2.1", "jdk-19.0.2", "jdk-20.0.2" + ], + "loongarch64": [ + "jdk-8.1.18", "jdk-11.0.22", "jdk-17.0.10", "jdk-21.0.2" + ] + } + arch = platform.machine() + if arch == "aarch64" or 'arm' in arch: + arch = "arm" + elif arch == "loongarch64": + arch = "loongarch64" + elif arch == "x86_64": + arch = "x64" + + self._versions_list = versions.get(arch, []) + return self._versions_list + + def jdk_list_path(self) -> List[str]: + return ["{}/{}".format(self._jdk_path, i) for i in self.versions_list] + + @property + def custom_jdk_list(self) -> List[str]: + if self._custom_jdk_list: + return self._custom_jdk_list + + try: + self._custom_jdk_list = json.loads(public.readFile(self._custom_file)) + except: + self._custom_jdk_list = [] + + if not isinstance(self._custom_jdk_list, list): + self._custom_jdk_list = [] + + return self._custom_jdk_list + + def add_custom_jdk(self, jdk_path: str) -> Optional[str]: + jdk_path = normalize_jdk_path(jdk_path) + if not jdk_path: + return "jdk路径错误或无法识别" + + if jdk_path in self.custom_jdk_list or jdk_path in self.jdk_list_path: + return + + self.custom_jdk_list.append(jdk_path) + public.writeFile(self._custom_file, json.dumps(self.custom_jdk_list)) + + def remove_custom_jdk(self, jdk_path: str) -> None: + if jdk_path not in self.custom_jdk_list: + return + + self.custom_jdk_list.remove(jdk_path) + public.writeFile(self._custom_file, json.dumps(self.custom_jdk_list)) + + def async_install_jdk(self, version: str) -> None: + sh_str = "cd /www/server/panel/install && /bin/bash install_soft.sh {} install {} {}".format(0, 'jdk', version) + + if not os.path.exists("/tmp/panelTask.pl"): # 如果当前任务队列并未执行,就把日志清空 + public.writeFile('/tmp/panelExec.log', '') + task_id = public.M('tasks').add( + 'id,name,type,status,addtime,execstr', + (None, '安装[{}]'.format(version), 'execshell', '0', time.strftime('%Y-%m-%d %H:%M:%S'), sh_str)) + + self._create_install_wait_msg(task_id, version) + + @staticmethod + def _create_install_wait_msg(task_id: int, version: str): + from panel_msg.msg_file import message_mgr + + file_path = "/tmp/panelExec.log" + if not os.path.exists(file_path): + public.writeFile(file_path, "") + + data = { + "soft_name": version, + "install_status": "等待安装" + version, + "file_name": file_path, + "self_type": "soft_install", + "status": 0, + "task_id": task_id + } + title = "等待安装" + version + res = message_mgr.collect_message(title, ["Java环境管理", version], data) + if isinstance(res, str): + public.WriteLog("消息盒子", "安装信息收集失败") + return None + return res + + def install_jdk(self, version: str) -> Optional[str]: + if version not in self.versions_list: + return "版本不存在, 无法安装" + + if os.path.exists(self._jdk_path + "/" + version): + return "已存在的版本, 无法再次安装,如需再次安装请先卸载" + + if os.path.exists("{}/{}.pl".format(self._jdk_path, version)): + return "安装任务进行中,请勿再次添加" + + public.writeFile("{}/{}.pl".format(self._jdk_path, version), "installing") + t = threading.Thread(target=self._install_jdk, args=(version,)) + t.start() + return None + + def _install_jdk(self, version: str) -> None: + try: + log_file = "{}/{}_install.log".format(self._jdk_path, version) + if not os.path.exists('/www/server/panel/install/jdk.sh'): + public.ExecShell('wget -O /www/server/panel/install/jdk.sh ' + public.get_url() + '/install/0/jdk.sh') + public.ExecShell('bash /www/server/panel/install/jdk.sh install {} 2>&1 > {}'.format(version, log_file)) + except: + pass + public.ExecShell('rm -rf /www/server/java/{}.*'.format(version)) + + def uninstall_jdk(self, version: str) -> Optional[str]: + if not os.path.exists(self._jdk_path + "/" + version): + return "没有安装指定的版本,无法卸载" + public.ExecShell('rm -rf /www/server/java/{}*'.format(version)) + return + + @staticmethod + def set_jdk_env(jdk_path) -> Optional[str]: + if jdk_path != "": + jdk_path = normalize_jdk_path(jdk_path) + if not jdk_path: + return "jdk路径错误或无法识别" + + # 写入全局的shell配置文件 + profile_path = '/etc/profile' + java_home_line = "export JAVA_HOME={}".format(jdk_path) if jdk_path else "" + path_line = "export PATH=$JAVA_HOME/bin:$PATH" + profile_data = public.readFile(profile_path) + if not isinstance(profile_data, str): + return "无法读取环境变量文件" + + rep_java_home = re.compile(r"export\s+JAVA_HOME=.*\n") + rep_path = re.compile(r"export\s+PATH=\$JAVA_HOME/bin:\$PATH\s*?\n") + if rep_java_home.search(profile_data): + profile_data = rep_java_home.sub(java_home_line, profile_data) + elif jdk_path: + profile_data = profile_data + "\n" + java_home_line + + if rep_path.search(profile_data): + if not jdk_path: + profile_data = rep_path.sub("", profile_data) + elif jdk_path: + profile_data = profile_data + "\n" + path_line + + try: + with open(profile_path, "w") as f: + f.write(profile_data) + except PermissionError: + return "无法修改环境变量,可能是系统加固插件拒绝了操作" + except: + return "修改失败" + + return + + @staticmethod + def get_env_jdk() -> Optional[str]: + profile_data = public.readFile('/etc/profile') + if not isinstance(profile_data, str): + return None + current_java_home = None + for line in profile_data.split("\n"): + if 'export JAVA_HOME=' in line: + current_java_home = line.split('=')[1].strip().replace('"', '').replace("'", "") + + return current_java_home + + +def jps() -> List[int]: + dir_list = [i for i in os.listdir("/tmp") if i.startswith("hsperfdata_")] + return [int(j) for j in itertools.chain(*[os.listdir("/tmp/" + i) for i in dir_list]) if j.isdecimal()] + + +def js_value_to_bool(value: Any) -> bool: + if isinstance(value, bool): + return value + if isinstance(value, str): + return value.lower() in ("true", "yes", "1") + return bool(value) + + +def check_port_with_net_connections(port: int) -> bool: + try: + for conn in psutil.net_connections(): + if conn.status == 'LISTEN' and conn.laddr.port == port: + return False + except: + pass + return True + + +def check_port(port) -> bool: + """ + 返回false表示端口不可用 + """ + if not isinstance(port, int): + port = int(port) + if port == 0: + return False + if not 0 < port < 65535: + return False + project_list = public.M('sites').field('name,path,project_config').select() + for project_find in project_list: + try: + project_config = json.loads(project_find['project_config']) + except json.JSONDecodeError: + continue + if 'port' not in project_config: + continue + if int(project_config['port']) == port: + return False + + try: + for conn in psutil.net_connections(): + if conn.status == 'LISTEN' and conn.laddr.port == port: + return False + except: + pass + return True + + +def pass_dir_for_user(path_dir: str, user: str): + """ + 给某个用户,对应目录的执行权限 + """ + import stat + if not os.path.isdir(path_dir): + return + try: + import pwd + uid_data = pwd.getpwnam(user) + uid = uid_data.pw_uid + gid = uid_data.pw_gid + except: + return + + if uid == 0: + return + + if path_dir[:-1] == "/": + path_dir = path_dir[:-1] + + while path_dir != "/": + path_dir_stat = os.stat(path_dir) + if path_dir_stat.st_uid != uid or path_dir_stat.st_gid != gid: + old_mod = stat.S_IMODE(path_dir_stat.st_mode) + if not old_mod & 1: + os.chmod(path_dir, old_mod+1) + path_dir = os.path.dirname(path_dir) + + +def create_a_not_used_port() -> int: + """ + 生成一个可用的端口 + """ + import random + while True: + port = random.randint(2000, 65535) + if check_port_with_net_connections(port): + return port + + +# 记录项目是通过用户停止的 +def stop_by_user(project_id): + file_path = "{}/data/push/tips/project_stop.json".format(public.get_panel_path()) + if not os.path.exists(file_path): + data = {} + else: + data_content = public.readFile(file_path) + try: + data = json.loads(data_content) + except json.JSONDecodeError: + data = {} + data[str(project_id)] = True + public.writeFile(file_path, json.dumps(data)) + + +# 记录项目是通过用户操作启动的 +def start_by_user(project_id): + file_path = "{}/data/push/tips/project_stop.json".format(public.get_panel_path()) + if not os.path.exists(file_path): + data = {} + else: + data_content = public.readFile(file_path) + try: + data = json.loads(data_content) + except json.JSONDecodeError: + data = {} + data[str(project_id)] = False + public.writeFile(file_path, json.dumps(data)) + + +def is_stop_by_user(project_id): + file_path = "{}/data/push/tips/project_stop.json".format(public.get_panel_path()) + if not os.path.exists(file_path): + data = {} + else: + data_content = public.readFile(file_path) + try: + data = json.loads(data_content) + except json.JSONDecodeError: + data = {} + if str(project_id) not in data: + return False + return data[str(project_id)] + +# # 内置项目复制Tomcat +# def check_and_copy_tomcat(version: int): +# old_path = "/usr/local/bttomcat/tomcat_bak%d" +# new_path = "/usr/local/bt_mod_tomcat/tomcat%d" +# if not os.path.exists("/usr/local/bt_mod_tomcat"): +# os.makedirs("/usr/local/bt_mod_tomcat", 0o755) +# +# src_path = old_path % version +# if not os.path.exists(old_path % version) or not os.path.isfile(src_path + '/conf/server.xml'): +# return +# if os.path.exists(new_path % version): +# return +# else: +# os.makedirs(new_path % version) +# +# public.ExecShell('cp -r %s/* %s ' % (src_path, new_path % version,)) +# t = bt_tomcat(version) +# if t: +# t.reset_tomcat_server_config(8330 + version - 6) + + +# def tomcat_install_status() -> List[dict]: +# res_list = [] +# install_path = "/usr/local/bttomcat/tomcat_bak%d" +# for i in range(7, 11): +# src_path = install_path % i +# start_path = src_path + '/bin/daemon.sh' +# conf_path = src_path + '/conf/server.xml' +# if os.path.exists(src_path) and os.path.isfile(start_path) and os.path.isfile(conf_path): +# res_list.append({"version": i, "installed": True}) +# else: +# res_list.append({"version": i, "installed": False}) +# return res_list + diff --git a/mod/project/php/__init__.py b/mod/project/php/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/mod/project/php/aepgMod.py b/mod/project/php/aepgMod.py new file mode 100644 index 00000000..87f04bb2 --- /dev/null +++ b/mod/project/php/aepgMod.py @@ -0,0 +1,1691 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: wzz +# ------------------------------------------------------------------- +import json +import os +# ------------------------------ +# 一键应用环境包模型 +# ------------------------------ +import sys +import time + +if "/www/server/panel/class" not in sys.path: + sys.path.insert(0, "/www/server/panel/class") + +os.chdir("/www/server/panel") +import public +import panelMysql +import db_mysql + +try: + from BTPanel import cache +except: + from cachelib import SimpleCache + cache = SimpleCache() + + +class main(): + def __init__(self): + # 2024/5/6 上午10:09 前期临时使用版本列表,后期再动态获取 + self._php_versions = ['52', '53', '54', '55', '56', '70', '71', '72', '73', '74', '80', '81', '82', '83'] + self._mysql_versions = ['5.1', '5.5', '5.6', '5.7', '8.0'] + # self._nginx_versions = ['1.8', '1.10', '1.12', '1.14', '1.16', '1.18', '1.20', '1.22', '1.24'] + self._nginx_install = True + self._php_install = True + self._mysql_install = True + self._ftpd_install = True + self.APP_PACKAGE_DB_NAME = "BT_APP_PACKAGE_DB_NAME" + self.APP_PACKAGE_DB_USER = "BT_APP_PACKAGE_DB_USER" + self.APP_PACKAGE_DB_PASS = "BT_APP_PACKAGE_DB_PASS" + self._MYSQLDUMP_BIN = public.get_mysqldump_bin() + self._rewrite_file = "{panel_path}/vhost/rewrite/{site_name}.conf" + self._package_dir = "/www/backup/package" + self._upload_package_dir = "/www/backup/upload_package" + self._temp_backup_dir = "{}/temp".format(self._package_dir) + self._temp_upload_package_dir = "{}/temp".format(self._upload_package_dir) + self._upload_package_path = os.path.join(self._upload_package_dir, "{app_name}") + self._backup_dir = None + self.skey = "app_package_create" + self._package_conf = { + "app_name": "", + "app_version": "", + "exclude_dir": [], + "php_versions": [], + "php_libs": [], + "php_functions": "", + "mysql_versions": [], + "init_sql": 0, + "db_character": "", + "db_config_file": [], + "nginx_install": True, + "php_install": True, + "mysql_install": False, + "ftpd_install": True, + "run_path": "/", + "dir_permission": [], + "update_log": "", + "size": 0, + "success_url": "", + } + + # 2024/5/6 上午10:26 返回支持选择的PHP列表 + def get_php_versions(self, get): + ''' + @name 返回支持选择的PHP列表 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return { + "used": public.get_site_php_version(get.site_name), + "versions": self._php_versions, + } + + # 2024/5/6 上午10:26 返回支持的mysql列表 + def get_mysql_versions(self, get): + ''' + @name 返回支持的mysql列表 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + used = "00" + if os.path.exists("{}/mysql/bin/mysqld".format(public.get_setup_path())): + version = public.ReadFile("{}/mysql/version.pl".format(public.get_setup_path())) + if version != "" and "." in version: + used = version.rsplit(".", 1)[0] + + return { + "used": used if "." in used else "00", + "versions": self._mysql_versions, + } + + # 2024/5/6 上午10:38 获取指定版本PHP的当前配置情况 + def get_php_config(self, get): + ''' + @name 获取指定版本PHP的当前配置情况 + @author wzz <2024/5/6 上午10:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.version = get.get("version/s", "00") + if get.version == "00": + return public.returnResult(status=False, msg="php_version不能为空,请传入需要获取扩展的PHP版本") + + import ajax + a = ajax.ajax() + res = a.GetPHPConfig(get) + + is_install = [] + for r in res["libs"]: + if r["status"]: + is_install.append(r) + + res["libs"] = is_install + + return public.returnResult(status=True, data=res) + + # 2024/5/8 下午4:31 获取环境信息 + def get_env_info(self, get): + ''' + @name 获取环境信息 + @author wzz <2024/5/8 下午4:31> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + result = cache.get(self.skey) + last_php_functions = "" + last_php_versions = "" + last_mysql_versions = "" + last_init_sql = 0 + last_db_name = "" + last_db_id = "" + last_db_config_files_l = [] + if isinstance(result, dict): + last_php_functions = result["php_functions"] if "php_functions" in result else "" + last_php_versions = result["php_versions"].split(",") if "php_versions" in result else "" + last_mysql_versions = result["mysql_versions"].split(",") if "mysql_versions" in result else "" + last_init_sql = result["init_sql"] if "init_sql" in result else 0 + last_db_config_files = result["db_config_files"] if "db_config_files" in result else [] + for db_c in last_db_config_files: + last_db_config_files_l.append({"file": db_c, "body": ""}) + last_db_name = result["db_name"] if "db_name" in result else "" + last_db_id = result["db_id"] if "db_id" in result else None + + php_version = self.get_php_versions(get) + php_version["last_php_versions"] = last_php_versions + mysql_version = self.get_mysql_versions(get) + mysql_version["last_mysql_versions"] = last_mysql_versions + mysql_version["last_init_sql"] = last_init_sql + mysql_version["last_db_config_files"] = last_db_config_files_l + mysql_version["last_db_name"] = last_db_name + mysql_version["last_db_id"] = last_db_id + + data = { + "php": php_version, + "mysql": mysql_version, + "db": { + "used": {}, + "all": [], + }, + "db_config_file": [], + "last_php_functions": last_php_functions, + } + + get.site_info = public.M('sites').where('name=?', (get.site_name,)).find() + if not get.site_info: + return public.returnResult(status=False, msg="获取网站信息失败") + + get.db_info = public.M('databases').where('pid=?', (get.site_info['id'],)).find() + get.db_all = public.M('databases').select() + data["db"]["all"] = get.db_all + get.config_list = [] + if get.db_info: + data["db"]["used"] = get.db_info + + stdout, stderr = public.ExecShell("find {site_path}/* -name *.php|xargs grep \"{db_name}\"".format( + site_path=get.site_info["path"], + db_name=get.db_info["name"], + )) + find_result = stdout.split("\n") + for fr in find_result: + if not fr: + continue + + find_file = fr.split(":")[0] + if not os.path.exists(find_file): + continue + if not find_file.endswith(".php"): + continue + find_body = fr.split(":")[1] + if not find_body: + continue + + if len(get.config_list) > 0: + for conf in get.config_list: + if find_file in conf["file"]: + break + else: + get.config_list.append({"file": find_file, "body": find_body}) + else: + get.config_list.append({"file": find_file, "body": find_body}) + + data["db_config_file"] = get.config_list + self._package_conf["db_config_file"] = get.config_list + + return public.returnResult(status=True, data=data) + + # 2024/5/6 下午3:50 获取指定目录的权限信息 + def get_path_permission(self, get): + ''' + @name 获取指定目录的权限信息 + @author wzz <2024/5/6 下午3:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + import pwd + site_path_stat = os.stat(get.check_path) + info = pwd.getpwuid(site_path_stat.st_uid) + + return { + "path": get.check_path, + "pw_name": info.pw_name, + "st_mode": site_path_stat.st_mode, + } + except Exception as e: + return {} + + # 2024/5/11 下午6:02 列出至少X级的目录 + def scan_directory(self, directory): + for root, dirs, files in os.walk(directory): + yield root, dirs, files + # 限制深度为2 + if root.count(os.sep) >= directory.count(os.sep) + 2: + del dirs[:] + + # 2024/5/11 上午11:34 根据目录获取最多两层目录 + def get_dir_list(self, get): + ''' + @name 根据目录获取最多两层目录 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.path = get.site_info["path"] + get.dir_permission_list = [] + has_root = False + root_path = "" + for root, dirs, files in self.scan_directory(get.path): + if not has_root: + get.check_path = root + path_perm = self.get_path_permission(get) + path_perm["path"] = "/" + if path_perm: + get.dir_permission_list.append(path_perm) + has_root = True + root_path = root + + if len(dirs) > 0: + for d in dirs: + get.check_path = os.path.join(root, d) + path_perm = self.get_path_permission(get) + path_perm["path"] = get.check_path.replace(root_path, "") + if path_perm: + get.dir_permission_list.append(path_perm) + + if len(files) > 0: + for f in files: + get.check_path = os.path.join(root, f) + path_perm = self.get_path_permission(get) + path_perm["path"] = get.check_path.replace(root_path, "") + if path_perm: + get.dir_permission_list.append(path_perm) + + # 2024/5/6 下午3:52 为指定目录设置权限 + def set_path_permission(self, get): + ''' + @name 为指定目录设置权限 + @author wzz <2024/5/6 下午3:52> + @param get.path/s: 指定目录 + get.permission/dict = { + "pw_name": "root", + "st_mode": 16877 + } + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + import pwd + import stat + pw_info = pwd.getpwnam(get.permission["pw_name"]) + public.ExecShell("chown -R {}:{} {}".format(pw_info.pw_uid, pw_info.pw_gid, get.path)) + public.ExecShell("chmod -R {} {}".format(stat.S_IMODE(get.permission["st_mode"]), get.path)) + + # 2024/5/6 下午3:54 检查是否设置成功 + site_path_stat = os.stat(get.path) + info = pwd.getpwuid(site_path_stat.st_uid) + if info.pw_name == get.permission["pw_name"] and site_path_stat.st_mode == get.permission["st_mode"]: + return public.returnResult(status=True, msg="设置成功") + else: + return public.returnResult(status=False, msg="设置失败") + except Exception as e: + return public.returnResult(status=False, msg="设置失败: {}".format(str(e))) + + # 2024/5/6 下午3:30 获取指定网站的根目录权限和运行目录权限 + def get_site_permission(self, get): + ''' + @name 获取指定网站的根目录权限和运行目录权限 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + get.check_path = get.site_info["path"] + get.root_path_permission = self.get_path_permission(get) + get.root_path_permission.pop("path") + from panelSite import panelSite + site_obj = panelSite() + get.id = get.site_info["id"] + runPath = site_obj.GetSiteRunPath(get) + get.check_path = get.site_info["path"] + runPath["runPath"] + get.run_path = runPath["runPath"] + get.run_path_permission = self.get_path_permission(get) + get.run_path_permission.pop("path") + + # 2024/5/6 下午3:37 获取指定目录权限 + get.dir_permission = { + "root_permission": get.root_path_permission, + "run_permission": get.run_path_permission, + } + except Exception as e: + get.dir_permission = {} + + # 2024/5/6 下午5:01 获取系统类型 + def get_os_type(self): + ''' + @name 获取系统类型 + @author wzz <2024/5/6 下午5:02> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if os.path.exists('/usr/bin/yum'): + return "1" + elif os.path.exists('/usr/bin/apt-get'): + return "3" + else: + return "0" + + # 2024/5/6 下午4:54 安装指定运行环境 + def install_env_soft(self, get): + ''' + @name 安装指定运行环境 + @author wzz <2024/5/6 下午4:55> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.lib_name = get.get("name/s", "") + get.version = get.get("version/s", "") + if get.lib_name == "" or get.version == "": + return public.returnResult(status=False, msg="name或version不能为空") + + # 2024/3/28 上午 10:36 检测是否已存在安装任务 + mmsg = "安装[{}]".format(get.lib_name + "-" + get.version) + if public.M('tasks').where('name=? and status=?', (mmsg, "-1")).count(): + return public.returnMsg(False, "已存在安装任务,请勿重复添加!") + + public.ExecShell("rm -rf {}/install/{}.sh".format(public.get_panel_path(), get.lib_name)) + execstr = ("wget -O /tmp/{name}.sh {url}/install/{os_type}/{name}.sh && " + "bash /tmp/{name}.sh install {version}").format( + name=get.lib_name, + url=public.get_url(), + os_type=self.get_os_type(), + version=get.version, + ) + public.M('tasks').add('id,name,type,status,addtime,execstr', + (None, mmsg, 'execshell', '0', time.strftime('%Y-%m-%d %H:%M:%S'), execstr)) + + return public.returnResult(status=True, msg="安装任务添加成功") + + # 2024/5/6 下午5:07 将指定目录压缩成tar.gz包 + def tar_path(self, get): + ''' + @name 将指定目录压缩成tar.gz包 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.path = get.get("path/s", "") + if get.path == "": + return public.returnResult(status=False, msg="path不能为空") + + get.target_path = get.get("target_path/s", "") + if get.target_path == "": + return public.returnResult(status=False, msg="target_path不能为空") + + if not os.path.exists(get.target_path): + public.ExecShell("mkdir -p {}".format(get.target_path)) + if not os.path.isdir(get.target_path): + return public.returnResult(status=False, msg="保存位置必须是一个目录") + + get.package_name = get.get("package_name/s", "") + if get.package_name == "": + return public.returnResult(status=False, msg="package_name不能为空") + get.package_name = get.package_name.replace(".tar.gz", "") + + # 2024/5/6 下午5:08 排除的目录 + exclude = [ + get.path + "/.git", + get.path + "/.svn", + get.path + "/.idea", + get.path + "/.vscode", + get.path + "/.vs", + get.path + "/.github", + get.path + "/.gitignore", + get.path + "/.gitattributes", + get.path + "/.gitmodules", + get.path + "/.gitkeep", + get.path + "/.gitlab-ci.yml", + get.path + "/.gitlab", + get.path + "/.gitlab-ci", + get.path + "/.user.ini", + ] + try: + exclude.extend(get.exclude_dir) + except: + pass + + exclude_str = "" + for e in exclude: + if not os.path.exists(e): + continue + + exclude_str += " --exclude='{}'".format(e) + + temp_sites_path = os.path.join(self._temp_backup_dir, "temp_sites") + if not os.path.exists(temp_sites_path): + public.ExecShell("mkdir -p {}".format(temp_sites_path)) + if not os.path.exists(os.path.join(temp_sites_path, get.app_name)): + public.ExecShell("mkdir -p {}".format(os.path.join(temp_sites_path, get.app_name))) + public.ExecShell(r"\cp -r {path}/* {temp_sites_path}/{app_name}/".format( + path=get.path, + temp_sites_path=temp_sites_path, + app_name=get.app_name, + )) + + public.ExecShell( + "cd {temp_sites_path} && tar -zcvf {target_path}/{package_name}.tar.gz {exclude_str} {target_dir} ".format( + temp_sites_path=temp_sites_path, + target_path=get.target_path, + package_name=get.package_name, + exclude_str=exclude_str, + target_dir=get.app_name, + )) + + if not os.path.exists("{}/{}.tar.gz".format(get.target_path, get.package_name)): + return public.returnResult(status=False, msg="打包失败") + + return public.returnResult(status=True, msg="打包成功") + + def __get_db_name_config(self, db_name: str): + from database import database + database = database() + db_find = public.M("databases").where("name=? AND LOWER(type)=LOWER('mysql')", (db_name,)).find() + + if db_find["db_type"] == 0: # 本地数据库 + result = panelMysql.panelMysql().execute("show databases") + isError = database.IsSqlError(result) + if isError: + return public.returnResult(status=False, msg=isError) + db_password = public.M("config").where("id=?", (1,)).getField("mysql_root") + if not db_password: + return public.returnResult(status=False, msg="数据库密码为空!请先设置数据库密码!") + try: + db_port = int(panelMysql.panelMysql().query("show global variables like 'port'")[0][1]) + except: + db_port = 3306 + if not db_password: + return public.returnResult(status=False, msg="{} 数据库密码不能为空".format(db_find["name"])) + conn_config = { + "db_host": "localhost", + "db_port": db_port, + "db_user": db_find["username"], + "db_password": db_find["password"], + } + elif db_find["db_type"] == 1: + # 远程数据库 + conn_config = json.loads(db_find["conn_config"]) + res = database.CheckCloudDatabase(conn_config) + if isinstance(res, dict): return public.returnResult(status=False, msg=res) + conn_config["db_port"] = int(conn_config["db_port"]) + elif db_find["db_type"] == 2: + conn_config = public.M("database_servers").where("id=? AND LOWER(db_type)=LOWER('mysql')", + db_find["sid"]).find() + res = database.CheckCloudDatabase(conn_config) + if isinstance(res, dict): return public.returnResult(status=False, msg=res) + conn_config["db_name"] = None + conn_config["db_port"] = int(conn_config["db_port"]) + else: + return public.returnResult(status=False, msg="{} 未知的数据库类型".format(db_find["name"])) + return public.returnResult(status=True, data=conn_config) + + # 2024/5/7 上午10:24 备份指定数据库为sql文件 + def backup_database(self, get): + ''' + @name 备份指定数据库为sql文件 + @author wzz <2024/5/7 上午10:28> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.db_name = get.get("db_name/s", "") + if get.db_name == "": + return public.returnResult(status=False, msg="db_name不能为空") + get.db_id = get.get("db_id/s", "") + if get.db_id == "": + return public.returnResult(status=False, msg="db_id不能为空") + + if not os.path.exists(self._MYSQLDUMP_BIN): + return public.returnResult(status=False, msg="缺少备份工具,请先通过软件管理安装MySQL!") + + db_find = public.M("databases").where("id=? AND name=? AND LOWER(type)=LOWER('mysql')", + (get.db_id, get.db_name)).find() + if not db_find: + return public.returnResult(status=False, msg="数据库[{}]不存在".format(get.db_name)) + + from database import database + database = database() + + db_name = db_find["name"] + + conn_config = self.__get_db_name_config(db_name) + if not conn_config["status"]: + return conn_config + + conn_config = conn_config["data"] + + mysql_obj = db_mysql.panelMysql() + flag = mysql_obj.set_host( + conn_config["db_host"], + conn_config["db_port"], + None, + conn_config["db_user"], + conn_config["db_password"] + ) + + if flag is False: + return public.returnMsg(False, database.GetMySQLError(mysql_obj._ex)) + + # 2024/5/7 上午10:56 备份目录待定 + self._MYSQL_BACKUP_DIR = "/www/backup/database" + db_backup_dir = os.path.join(self._MYSQL_BACKUP_DIR, db_name) + if not os.path.exists(db_backup_dir): + os.makedirs(db_backup_dir) + + file_name = "{db_name}_{backup_time}_mysql_data_{number}".format( + db_name=db_name, + backup_time=time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()), + number=public.GetRandomString(5), + ) + + get.db_charset = public.get_database_character(db_name) + self._package_conf["db_character"] = get.db_charset + + set_gtid_purged = "" + resp = public.ExecShell("{} --help | grep set-gtid-purged >> /tmp/backup_sql.log".format( + self._MYSQLDUMP_BIN))[0] + if resp.find("--set-gtid-purged") != -1: + set_gtid_purged = "--set-gtid-purged=OFF" + + if db_find["db_type"] == 2: + db_user = conn_config["db_user"] + db_password = conn_config["db_password"] + db_port = int(conn_config["db_port"]) + else: + db_user = "root" + db_password = public.M("config").where("id=?", (1,)).getField("mysql_root") + db_port = conn_config["db_port"] + + shell = "'{mysqldump_bin}' {set_gtid_purged} --opt --skip-lock-tables --single-transaction --routines --events --skip-triggers --default-character-set='{db_charset}' --force " \ + "--host='{db_host}' --port={db_port} --user='{db_user}' --password='{db_password}' '{db_name}'".format( + mysqldump_bin=self._MYSQLDUMP_BIN, + set_gtid_purged=set_gtid_purged, + db_charset=get.db_charset, + db_host=conn_config["db_host"], + db_port=db_port, + db_user=db_user, + db_password=db_password, + db_name=db_name, + ) + + get.export_file = os.path.join(db_backup_dir, file_name + ".sql") + shell += "| tee /tmp/backup_sql.log > '{backup_path}' ".format(backup_path=get.export_file) + public.ExecShell(shell, env={"MYSQL_PWD": conn_config["db_password"]}) + + if not os.path.exists(get.export_file): + return public.returnResult(status=False, msg="备份失败") + + return public.returnResult(status=True, msg="备份成功", data={"file": get.export_file}) + + # 2024/5/7 下午5:24 备份指定网站的伪静态 + def backup_rewrite(self, get): + ''' + @name 备份指定网站的伪静态 + @author wzz <2024/5/7 下午5:41> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + self._rewrite_file = self._rewrite_file.format(panel_path=public.get_panel_path(), site_name=get.site_name) + if not os.path.exists(self._rewrite_file): + return public.returnResult(status=True, msg="伪静态文件不存在,不需要备份") + + public.ExecShell(r"\cp -r {rewrite_file} {backup_dir}/rewrite.conf".format( + rewrite_file=self._rewrite_file, + backup_dir=self._backup_dir) + ) + if not os.path.exists("{}/rewrite.conf".format(self._backup_dir)): + return public.returnResult(status=False, msg="备份失败") + + return public.returnResult(status=True, msg="备份成功") + + # 2024/5/9 下午2:19 还原指定包的伪静态到指定网站中 + def restore_rewrite(self, get): + ''' + @name 还原指定包的伪静态到指定网站中 + @author wzz <2024/5/9 下午2:20> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + self._rewrite_file = self._rewrite_file.format(panel_path=public.get_panel_path(), site_name=get.site_name) + + public.ExecShell(r"\cp -r {_temp_import_dir}/{app_name}/rewrite.conf {_rewrite_file}".format( + _temp_import_dir=self._temp_import_dir, + app_name=get.app_name, + _rewrite_file=self._rewrite_file, + )) + + # 2024/5/9 下午6:05 还原指定上传包的伪静态到指定网站中 + def restore_upload_rewrite(self, get): + ''' + @name 还原指定上传包的伪静态到指定网站中 + @author wzz <2024/5/9 下午6:05> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + self._rewrite_file = self._rewrite_file.format(panel_path=public.get_panel_path(), site_name=get.site_name) + + public.ExecShell(r"\cp -r {_upload_package_path}/{app_name}/rewrite.conf {_rewrite_file}".format( + _upload_package_path=self._upload_package_path, + app_name=get.app_name, + _rewrite_file=self._rewrite_file, + )) + + # 2024/5/8 上午9:45 获取指定数据库连接配置文件的相对路径,并将匹配到的数据库账号密码和数据库名 + def get_db_config_file(self, get): + ''' + @name 获取指定数据库连接配置文件的相对路径,并将匹配到的数据库账号密码和数据库名 + @author wzz <2024/5/8 上午9:45> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if len(get.db_config_files) == 0: + return public.returnResult(status=False, msg="数据库配置文件不能为空,请选择.php文件") + + get.config_list = [] + for db_conf in get.db_config_files: + if not db_conf: + continue + if not os.path.exists(db_conf): + return public.returnResult(status=False, msg="{}不存在".format(db_conf)) + if not db_conf.endswith(".php"): + return public.returnResult(status=False, msg="{}不是PHP文件".format(db_conf)) + + # 2024/5/9 上午10:06 打开指定文件,将匹配到的内容替换成self.APP_PACKAGE_DB_NAME,self.APP_PACKAGE_DB_USER,self.APP_PACKAGE_DB_PASS + conf_body = public.readFile(db_conf) + if not conf_body: + return public.returnResult(status=False, msg="{}内容为空".format(db_conf)) + + db_find = public.M("databases").where("id=? AND name=? AND LOWER(type)=LOWER('mysql')", + (get.db_id, get.db_name)).find() + if db_find: + conf_body = conf_body.replace(db_find["username"], self.APP_PACKAGE_DB_USER) + conf_body = conf_body.replace(db_find["name"], self.APP_PACKAGE_DB_NAME) + conf_body = conf_body.replace(db_find["password"], self.APP_PACKAGE_DB_PASS) + public.writeFile(db_conf, conf_body) + + get.config_list.append(db_conf.replace(get.path + "/", "")) + self._package_conf["db_config_file"] = get.config_list + + return public.returnResult(status=True, msg="据库连接配置文件处理成功") + + # 2024/5/7 下午6:32 更新json配置文件 + def update_package_conf(self, get): + ''' + @name 更新json配置文件 + @author wzz <2024/5/7 下午6:32> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + self._package_conf["app_name"] = get.app_name + self._package_conf["app_version"] = get.app_version + self._package_conf["exclude_dir"] = get.exclude_dir + self._package_conf["php_versions"] = get.php_versions + self._package_conf["php_functions"] = get.php_functions + self._package_conf["mysql_versions"] = get.mysql_versions + self._package_conf["init_sql"] = get.init_sql + self._package_conf["run_path"] = get.run_path + self._package_conf["dir_permission"] = get.dir_permission + self._package_conf["update_log"] = get.update_log + + public.writeFile("{}/dir_permission.json".format(self._backup_dir), json.dumps(get.dir_permission_list)) + public.writeFile("{}/package.json".format(self._backup_dir), json.dumps(self._package_conf)) + + # 2024/5/8 上午10:05 将所有数据打包成一个压缩包 + def tar_package(self, get): + ''' + @name 将所有数据打包成一个压缩包 + @author wzz <2024/5/8 上午10:06> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.package_name = get.app_name + "_v" + get.app_version + get.app_package_dir = os.path.join(self._package_dir, get.app_name) + if not os.path.exists(get.app_package_dir): + public.ExecShell("mkdir -p {}".format(get.app_package_dir)) + + if get.export_file != "": + public.ExecShell(r"\cp -r {export_file} {temp_backup_dir}/packages/{app_name}/init.sql".format( + export_file=get.export_file, + temp_backup_dir=self._temp_backup_dir, + app_name=get.app_name, + )) + + public.ExecShell( + "cd {temp_backup_dir}/packages/ && tar -zcvf {app_package_dir}/{package_name}.tar.gz {app_name}".format( + temp_backup_dir=self._temp_backup_dir, + app_package_dir=get.app_package_dir, + package_name=get.package_name, + app_name=get.app_name, + )) + + public.ExecShell("rm -rf {}/*".format(self._temp_backup_dir)) + + if not os.path.exists("{}/{}.tar.gz".format(get.app_package_dir, get.package_name)): + return public.returnResult(status=False, msg="打包失败") + + get.size = os.path.getsize("{}/{}.tar.gz".format(get.app_package_dir, get.package_name)) + + return public.returnResult(status=True, msg="打包成功", + data={"file": "{}/{}.tar.gz".format(get.app_package_dir, get.package_name)}) + + # 2024/5/8 下午3:33 更新数据库 + def update_database(self, get): + ''' + @name 更新数据库 + @author wzz <2024/5/8 下午3:34> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # CREATE TABLE `app_package` ( + # `id` INTEGER PRIMARY KEY AUTOINCREMENT, + # `pid` INTEGER, + # `site_name` TEXT, + # `app_name` TEXT, + # `app_version` TEXT, + # `package_name` TEXT, + # `package_path` TEXT, + # `size` TEXT, + # `addtime` TEXT, + # `update_log` TEXT, + # `php_versions` TEXT, + # `php_libs` TEXT, + # `php_functions` TEXT, + # `mysql_versions` TEXT, + # `db_character` TEXT, + # `init_sql` TEXT, + # `db_config_file` TEXT + # ); + find_version = public.M('app_package').where('site_name=? AND app_name=? AND app_version=?', + (get.site_name, get.app_name, get.app_version)).find() + + try: + php_libs = json.dumps(self._package_conf["php_libs"]) + except: + php_libs = "[]" + + if find_version and "site_name" in find_version: + public.M('app_package').where('id=?', (find_version["id"],)).save( + 'package_name,package_path,size,update_log,addtime,php_versions,php_libs,php_functions,mysql_versions,db_character,init_sql,db_config_file', + (get.package_name, "{}/{}.tar.gz".format(get.app_package_dir, get.package_name), get.size, + get.update_log, time.strftime('%Y-%m-%d %H:%M:%S'), get.php_versions, php_libs, get.php_functions, + get.mysql_versions, get.db_charset, get.init_sql, json.dumps(get.config_list)) + ) + else: + public.M('app_package').add( + 'pid,site_name,app_name,app_version,package_name,package_path,size,addtime,update_log,php_libs,php_versions,php_functions,mysql_versions,db_character,init_sql,db_config_file', + (get.site_info["id"], get.site_name, get.app_name, get.app_version, get.package_name, + "{}/{}.tar.gz".format(get.app_package_dir, get.package_name), get.size, + time.strftime('%Y-%m-%d %H:%M:%S'), get.update_log, php_libs, get.php_versions, get.php_functions, + get.mysql_versions, get.db_charset, get.init_sql, json.dumps(get.config_list)) + ) + + # 2024/5/9 下午4:42 更新上传应用包的数据库 + def update_upload_database(self, get): + ''' + @name 更新上传应用包的数据库 + @author wzz <2024/5/9 下午4:43> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # CREATE TABLE `app_package_upload` ( + # `id` INTEGER PRIMARY KEY AUTOINCREMENT, + # `app_name` TEXT, + # `app_version` TEXT, + # `package_name` TEXT, + # `package_path` TEXT, + # `size` TEXT, + # `addtime` TEXT, + # `update_log` TEXT, + # `php_versions` TEXT, + # `php_libs` TEXT, + # `php_functions` TEXT, + # `mysql_versions` TEXT, + # `db_character` TEXT, + # `init_sql` TEXT, + # `db_config_file` TEXT + # ); + find_version = public.M('app_package_upload').where('app_name=? AND app_version=?', + (get.app_name, get.package_info["app_version"])).find() + + try: + php_libs = json.dumps(get.package_info["php_libs"]) + except: + php_libs = "[]" + + self._upload_package_path = self._upload_package_path.format(app_name=get.app_name) + + if find_version and "app_name" in find_version: + public.M('app_package_upload').where('id=?', (find_version["id"],)).save( + 'package_path,size,update_log,addtime,php_versions,php_libs,php_functions,mysql_versions,db_character,init_sql,db_config_file', + (self._upload_package_path, get.size, get.package_info["update_log"], + time.strftime('%Y-%m-%d %H:%M:%S'), get.package_info["php_versions"], php_libs, get.package_info["php_functions"], + get.package_info["mysql_versions"], get.package_info["db_character"], get.package_info["init_sql"], + json.dumps(get.package_info["db_config_file"])) + ) + else: + public.M('app_package_upload').add( + 'app_name,app_version,package_path,size,addtime,update_log,php_libs,php_versions,php_functions,mysql_versions,db_character,init_sql,db_config_file', + (get.app_name, get.package_info["app_version"], + self._upload_package_path, get.size, + time.strftime('%Y-%m-%d %H:%M:%S'), get.package_info["update_log"], php_libs, get.package_info["php_versions"], + get.package_info["php_functions"], get.package_info["mysql_versions"], get.package_info["db_character"], + get.package_info["init_sql"], json.dumps(get.package_info["db_config_file"])) + ) + + # 2024/5/6 上午10:49 创建一件应用环境包 + def create(self, get): + ''' + @name 创建一件应用环境包 + @author wzz <2024/5/6 上午10:50> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + get.app_name = get.get("app_name/s", "") + if get.app_name == "": + return public.returnResult(status=False, msg="app_name不能为空") + get.app_version = get.get("app_version/s", "") + if get.app_version == "": + return public.returnResult(status=False, msg="app_version不能为空") + + get.php_versions = get.get("php_versions/s", "00") + # get.php_libs = get.get("php_libs/s", "") + get.php_functions = get.get("php_functions/s", "") + get.mysql_versions = get.get("mysql_versions/s", "00") + get.init_sql = int(get.get("init_sql/d", 0)) + get.db_name = get.get("db_name/s", "") + if get.db_name == "" and get.init_sql == 1: + return public.returnResult(status=False, msg="db_name不能为空") + get.db_id = get.get("db_id/s", "") + if get.db_id == "" and get.init_sql == 1: + return public.returnResult(status=False, msg="db_id不能为空") + + get.exclude_dir = get.get("exclude_dir", "[]") + if type(get.exclude_dir) == str: + try: + get.exclude_dir = json.loads(get.exclude_dir) + except: + get.exclude_dir = [] + else: + get.exclude_dir = [] + + get.db_config_files = get.get("db_config_files", "[]") + if type(get.db_config_files) == str: + get.db_config_files = json.loads(get.db_config_files) + + get.success_url = get.get("success_url/s", "") + if not get.success_url.startswith("/"): + get.success_url = get.success_url + "/" + self._package_conf["success_url"] = get.success_url + + get.update_log = get.get("update_log/s", "") + get.update_log = public.xssencode2(get.update_log) + + # 2024/5/9 上午11:56 处理php扩展和函数 + get.version = public.get_site_php_version(get.site_name) + if get.version != "00": + php_config = self.get_php_config(get) + self._package_conf["php_libs"] = php_config["data"]["libs"] + self._package_conf["php_functions"] = get.php_functions + + # 2024/5/7 下午6:15 处理打包目录 + self._backup_dir = os.path.join(self._temp_backup_dir, "packages", get.app_name) + if os.path.exists(self._backup_dir): + public.ExecShell("rm -rf {}".format(self._backup_dir)) + public.ExecShell("mkdir -p {}".format(self._backup_dir)) + + get.site_info = public.M('sites').where('name=?', (get.site_name,)).find() + if not get.site_info: + return public.returnResult(status=False, msg="获取网站信息失败") + + get.path = get.site_info["path"] + get.target_path = self._backup_dir + get.package_name = get.app_name + + # 2024/5/6 上午11:01 备份数据库 + get.export_file = "" + get.db_charset = "" + get.config_list = [] + if get.init_sql == 1: + bk_result = self.backup_database(get) + if not bk_result["status"]: + return public.returnResult(status=False, msg=bk_result["msg"]) + + # 2024/5/8 上午9:51 获取指定数据库连接配置文件的相对路径 + d_result = self.get_db_config_file(get) + if not d_result["status"]: + return public.returnResult(status=False, msg=d_result["msg"]) + + # 2024/5/6 上午11:00 备份网站目录 + self.tar_path(get) + + # 2024/5/6 下午5:41 备份伪静态 + self.backup_rewrite(get) + + # 2024/5/8 上午9:55 获取原来网站目录的根目录和运行目录权限 + self.get_site_permission(get) + + # 2024/5/11 下午6:14 获取网站目录下最多2层的文件和目录权限 + self.get_dir_list(get) + + # 2024/5/8 上午9:59 更新压缩包里面的json配置文件 + self.update_package_conf(get) + + # 2024/5/8 上午10:12 将所有数据打包成一个压缩包 + self.tar_package(get) + + # 2024/5/8 下午3:42 更新数据库 + self.update_database(get) + + # 2024/5/10 下午3:38 保存这次填的php、mysql版本和php函数到一个json文件,方便下一次创建的时候自动填充 + last_config = { + "php_versions": get.php_versions, + "php_functions": get.php_functions, + "mysql_versions": get.mysql_versions, + "init_sql": get.init_sql, + "db_config_files": get.db_config_files, + "db_name": get.db_name, + "db_id": int(get.db_id) if get.db_id != "" else "", + } + cache.set(self.skey, last_config, 86400) + + public.WriteLog("SITE_APP_PACKAGE", "创建一键应用环境包[{}]".format(get.app_name)) + public.set_module_logs('site_app_package', 'create', 1) + + return public.returnResult(status=True, msg="打包成功") + + # 2024/5/10 下午12:10 删除指定的应用包 + def delete(self, get): + ''' + @name 删除指定的应用包 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + get.app_name = get.get("app_name/s", "") + if get.app_name == "": + return public.returnResult(status=False, msg="app_name不能为空") + + get.app_version = get.get("app_version/s", "") + if get.app_version == "": + return public.returnResult(status=False, msg="app_version不能为空") + + find_version = public.M('app_package').where('site_name=? AND app_name=? AND app_version=?', + (get.site_name, get.app_name, get.app_version)).find() + if not find_version: + return public.returnResult(status=False, msg="应用包不存在") + + public.ExecShell("rm -rf {}".format(find_version["package_path"])) + + public.M('app_package').where('id=?', (find_version["id"],)).delete() + return public.returnResult(status=True, msg="删除成功") + + # 2024/5/9 上午10:41 返回指定网站的app_package表所有数据 + def get_db_all_result(self, get): + ''' + @name 返回指定网站的app_package表所有数据 + @author wzz <2024/5/9 上午10:42> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + # return public.M('app_package').where("site_name=?", (get.site_name,)).order('addtime desc').select() + return public.M('app_package').where("site_name=?", (get.site_name,)).order('app_version asc').select() + + # 2024/5/9 上午11:02 返回指定网站的app_package表的某条数据 + def get_db_result(self, get): + ''' + @name 返回指定网站的app_package表的某条数据 + @author wzz <2024/5/9 上午11:03> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return public.M('app_package').where("site_name=? and app_name=? and app_version=?", (get.site_name, get.app_name, get.app_version)).find() + + # 2024/5/9 下午5:15 返回上传数据库中所有的数据 + def get_upload_db_all_result(self, get): + ''' + @name 返回上传数据库中所有的数据 + @author wzz <2024/5/9 下午5:15> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return public.M('app_package_upload').order('app_version asc').select() + + # 2024/5/9 下午5:19 返回上传数据库中指定包的数据 + def get_upload_db_result(self, get): + ''' + @name 返回上传数据库中指定包的数据 + @author wzz <2024/5/9 下午5:19> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + return public.M('app_package_upload').where("app_name=? and app_version=?", (get.app_name, get.app_version)).find() + + # 2024/5/9 上午10:39 获取所有的应用包列表 + def get_list(self, get): + ''' + @name 获取所有的应用包列表 + @author wzz <2024/5/9 上午10:40> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + all_result = self.get_db_all_result(get) + for result in all_result: + result["status"] = True + result["info"] = "正常" + if not os.path.exists(result["package_path"]) or os.path.getsize(result["package_path"]) < 10: + result["status"] = False + result["info"] = "文件小于10B或压缩包不存在,请确认包是否正常,如果异常请删除重新创建!" + + try: + result["db_config_file"] = json.loads(result["db_config_file"]) + except: + pass + + try: + result["php_libs"] = json.loads(result["php_libs"]) + except: + pass + + all_result.reverse() + public.set_module_logs('site_app_package', 'get_list', 1) + return public.returnResult(status=True, data=all_result) + + # 2024/5/9 上午11:17 获取指定包中的package_conf + def get_package_conf(self, get): + ''' + @name 获取指定包中的package_conf + @author wzz <2024/5/9 上午11:18> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + get.package_info = json.loads(public.readFile(get.package_json_path)) + except: + get.package_info = {} + + # 2024/5/9 下午3:09 检查当前站点的php设置是否符合包要求的php + def check_php_mysql(self, get): + ''' + @name 检查当前站点的php设置是否符合包要求的php + @author wzz <2024/5/9 下午3:10> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.version = public.get_site_php_version(get.site_name) + if not get.version in get.package_info["php_versions"]: + return public.returnResult(status=False, msg="当前站点PHP版本[{}]不符合包要求的PHP版本[{}]".format( + get.version, + get.package_info["php_versions"] + )) + + used_mysql = self.get_mysql_versions(get)["used"] + if get.package_info["mysql_versions"] != "00": + if not used_mysql in get.package_info["mysql_versions"]: + return public.returnResult(status=False, msg="当前站点MySQL版本[{}]不符合包要求的MySQL版本[{}]".format( + used_mysql, + get.package_info["mysql_versions"] + )) + + return public.returnResult(status=True, msg="当前站点PHP和MySQL版本符合包要求") + + # 2024/5/9 下午3:06 设置php函数到指定php版本 + def set_php_disable(self, get): + ''' + @name 设置php函数到指定php版本 + @author wzz <2024/5/9 下午3:07> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if len(get.package_info["php_functions"]) != 0: + php_config = self.get_php_config(get) + get.disable_functions = php_config["data"]["disable_functions"] + + p_disable_functions = get.package_info["php_functions"].split(",") + if len(p_disable_functions) != 0: + for p_disable_function in p_disable_functions: + if p_disable_function in get.disable_functions: + get.disable_functions = get.disable_functions.replace(p_disable_function + ",", "") + + from config import config + c = config() + c.setPHPDisable(get) + + # 2024/5/9 下午3:07 安装php扩展到指定php版本 + def install_phplib(self, get): + ''' + @name 安装php扩展到指定php版本 + @author wzz <2024/5/9 下午3:08> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if len(get.package_info["php_libs"]) != 0: + for lib_name in get.package_info["php_libs"]: + get.lib_name = lib_name["name"] + self.install_env_soft(get) + + # 2024/5/9 下午3:08 从已经解压的应用包中找到网站文件,然后解压拷贝到指定网站目录 + def copy_site(self, get): + ''' + @name 从已经解压的应用包中找到网站文件,然后解压拷贝到指定网站目录 + @author wzz <2024/5/9 下午3:08> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + public.ExecShell("cd {_temp_import_dir}/{app_name} && tar -zxvf {app_name}.tar.gz".format( + _temp_import_dir=self._temp_import_dir, + app_name=get.app_name, + )) + public.ExecShell(r"\cp -r {_temp_import_dir}/{app_name}/{app_name}/* {site_path}".format( + _temp_import_dir=self._temp_import_dir, + app_name=get.app_name, + site_path=get.site_info["path"], + )) + + for dir_permission in get.package_info["dir_permission"].keys(): + if dir_permission == "root_permission": + get.path = get.site_info["path"] + if dir_permission == "run_permission": + if get.package_info["run_path"] == "/": + continue + + get.path = os.path.join(get.site_info["path"], get.package_info["run_path"]) + + get.permission = get.package_info["dir_permission"][dir_permission] + self.set_path_permission(get) + + # 2024/5/11 下午6:22 恢复网站目录下最多2层的文件和目录权限 + self.set_dir_list_permission(get) + + # 2024/5/11 下午6:22 恢复网站目录下最多2层的文件和目录权限 + def set_dir_list_permission(self, get): + ''' + @name 恢复网站目录下最多2层的文件和目录权限 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + try: + dir_permission_list = json.loads(public.readFile("{}/dir_permission.json".format(self._backup_dir))) + except: + return + + for dir_permission in dir_permission_list: + get.path = os.path.join(get.site_info["path"], dir_permission["path"]) + get.permission = dir_permission + self.set_path_permission(get) + + # 2024/5/9 下午5:52 从上传的应用包目录找到网站文件,然后解压拷贝到指定网站目录 + def copy_upload_site(self, get): + ''' + @name 从上传的应用包目录找到网站文件,然后解压拷贝到指定网站目录 + @author wzz <2024/5/9 下午5:52> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + public.ExecShell("cd {upload_package_path} && tar -zxvf {app_name}.tar.gz".format( + upload_package_path=self._upload_package_path, + app_name=get.app_name, + )) + public.ExecShell(r"\cp -r {upload_package_path}/{app_name}/* {site_path}".format( + upload_package_path=self._upload_package_path, + app_name=get.app_name, + site_path=get.site_info["path"], + )) + + for dir_permission in get.package_info["dir_permission"].keys(): + if dir_permission == "root_permission": + get.path = get.site_info["path"] + if dir_permission == "run_permission": + if get.package_info["run_path"] == "/": + continue + + get.path = os.path.join(get.site_info["path"], get.package_info["run_path"]) + + get.permission = get.package_info["dir_permission"][dir_permission] + self.set_path_permission(get) + + # 2024/5/11 下午6:22 恢复网站目录下最多2层的文件和目录权限 + self.set_dir_list_permission(get) + + # 2024/5/9 下午3:09 检查包json配置文件中的init_sql是否为1,如果是则执行查询网站关联的数据库并导入init.sql + def import_init_sql(self, get): + ''' + @name 检查包json配置文件中的init_sql是否为1,如果是则执行查询网站关联的数据库并导入init.sql + @author wzz <2024/5/9 下午3:09> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if get.package_info["init_sql"] == 1: + get.db_info = public.M('databases').where('pid=?', (get.site_info['id'],)).find() + if get.db_info: + for db_conf in get.package_info["db_config_file"]: + db_conf = os.path.join(get.site_info["path"], db_conf) + if not os.path.exists(db_conf): + continue + + conf_body = public.readFile(db_conf) + if not conf_body: + return public.returnResult(status=False, msg="{}内容为空".format(db_conf)) + + conf_body = conf_body.replace(self.APP_PACKAGE_DB_USER, get.db_info["username"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_NAME, get.db_info["name"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_PASS, get.db_info["password"]) + + public.writeFile(db_conf, conf_body) + + from database import database + database = database() + get.file = "{}/{}/init.sql".format(self._temp_import_dir, get.app_name) + get.name = get.db_info["name"] + import_result = database.InputSql(get) + if not import_result["status"]: + return public.returnResult(status=False, msg=import_result["msg"]) + + return public.returnResult(status=True, msg="导入init.sql成功") + + return public.returnResult(status=True, msg="不需要导入") + + # 2024/5/9 下午6:08 检查指定上传包json配置文件中的init_sql是否为1,如果是则执行查询网站关联的数据库并导入init.sql + def import_upload_init_sql(self, get): + ''' + @name 检查指定上传包json配置文件中的init_sql是否为1,如果是则执行查询网站关联的数据库并导入init.sql + @author wzz <2024/5/9 下午6:08> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.db_info = public.M('databases').where('pid=?', (get.site_info['id'],)).find() + if get.db_info: + if get.package_info["init_sql"] == 1: + from database import database + database = database() + get.file = "{}/init.sql".format(self._upload_package_path) + get.name = get.db_info["name"] + import_result = database.InputSql(get) + if not import_result["status"]: + return public.returnResult(status=False, msg=import_result["msg"]) + + for db_conf in get.package_info["db_config_file"]: + sample_db_conf = "" + if "sample" in db_conf: + sample_db_conf = db_conf.replace("sample_", "").replace("_sample", "").replace("-sample", "").replace("sample-", "").replace(".sample", "").replace("sample.", "").replace("sample", "") + db_conf = db_conf.replace("sample_", "").replace("_sample", "").replace("-sample", "").replace("sample-", "").replace(".sample", "").replace("sample.", "").replace("sample", "") + + if sample_db_conf != "": + sample_db_conf = os.path.join(get.site_info["path"], sample_db_conf) + if os.path.exists(sample_db_conf): + conf_body = public.readFile(sample_db_conf) + if conf_body: + conf_body = conf_body.replace(self.APP_PACKAGE_DB_USER, get.db_info["username"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_NAME, get.db_info["name"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_PASS, get.db_info["password"]) + + public.writeFile(sample_db_conf, conf_body) + + db_conf = os.path.join(get.site_info["path"], db_conf) + if not os.path.exists(db_conf): + public.ExecShell("cp -f {} {}".format(sample_db_conf, db_conf)) + + conf_body = public.readFile(db_conf) + if not conf_body: + return public.returnResult(status=False, msg="{}内容为空".format(db_conf)) + + conf_body = conf_body.replace(self.APP_PACKAGE_DB_USER, get.db_info["username"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_NAME, get.db_info["name"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_PASS, get.db_info["password"]) + + public.writeFile(db_conf, conf_body) + else: + db_conf = os.path.join(get.site_info["path"], db_conf) + if not os.path.exists(db_conf): + continue + + conf_body = public.readFile(db_conf) + if not conf_body: + return public.returnResult(status=False, msg="{}内容为空".format(db_conf)) + + conf_body = conf_body.replace(self.APP_PACKAGE_DB_USER, get.db_info["username"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_NAME, get.db_info["name"]) + conf_body = conf_body.replace(self.APP_PACKAGE_DB_PASS, get.db_info["password"]) + + public.writeFile(db_conf, conf_body) + + return public.returnResult(status=True, msg="导入init.sql成功") + + # 2024/5/9 上午10:54 应用指定的应用环境包到指定网站 + def apply_site(self, get): + ''' + @name 应用指定的应用环境包到指定网站 + @author wzz <2024/5/9 上午10:55> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + get.app_name = get.get("app_name/s", "") + if get.app_name == "": + return public.returnResult(status=False, msg="app_name不能为空") + + get.app_version = get.get("app_version/s", "") + if get.app_version == "": + return public.returnResult(status=False, msg="app_version不能为空") + + get.site_info = public.M('sites').where('name=?', (get.site_name,)).find() + if not get.site_info: + return public.returnResult(status=False, msg="获取网站信息失败") + + get.package_info = self.get_db_result(get) + if not get.package_info: + return public.returnResult(status=False, msg="获取应用包信息失败") + + if not os.path.exists(get.package_info["package_path"]): + return public.returnResult(status=False, msg="应用包不存在") + + # 2024/5/9 上午11:15 创建临时导入目录 + self._temp_import_dir = os.path.join(self._temp_backup_dir, "import") + if os.path.exists(self._temp_import_dir): + public.ExecShell("rm -rf {}".format(self._temp_import_dir)) + public.ExecShell("mkdir -p {}".format(self._temp_import_dir)) + + # 2024/5/9 上午11:15 解压应用包 + public.ExecShell("tar -zxvf {} -C {}".format(get.package_info["package_path"], self._temp_import_dir)) + + # 2024/5/9 上午11:23 处理包里面的json配置文件 + get.package_json_path = "{_temp_import_dir}/{app_name}/package.json".format( + _temp_import_dir=self._temp_import_dir, + app_name=get.app_name, + ) + + self.get_package_conf(get) + if not get.package_info: + return public.returnResult(status=False, msg="包异常,获取package.json配置文件失败") + + # 2024/5/9 上午11:41 检查当前站点的php设置是否符合包要求的php + check_result = self.check_php_mysql(get) + if not check_result["status"]: + return public.returnResult(status=False, msg=check_result["msg"]) + + # 2024/5/9 上午11:53 设置php函数到指定php版本 + self.set_php_disable(get) + # 2024/5/9 下午12:08 安装php扩展到指定php版本 + self.install_phplib(get) + # 2024/5/9 下午2:37 还原指定包的伪静态到指定网站中 + self.restore_rewrite(get) + # 2024/5/9 上午11:01 从已经解压的应用包中找到网站文件,然后解压拷贝到指定网站目录 + self.copy_site(get) + # 2024/5/9 上午11:16 检查包json配置文件中的init_sql是否为1,如果是则执行查询网站关联的数据库并导入init.sql + import_result = self.import_init_sql(get) + if not import_result["status"]: + return public.returnResult(status=False, msg=import_result["msg"]) + + public.set_module_logs('site_app_package', 'apply_site', 1) + return public.returnResult(status=True, msg="应用成功") + + # 2024/5/9 下午3:50 上传应用包到指定位置 + def upload_package(self, get): + ''' + @name 上传应用包到指定位置 + @author wzz <2024/5/9 下午3:51> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + if not os.path.exists(self._temp_upload_package_dir): + public.ExecShell("mkdir -p {}".format(self._temp_upload_package_dir)) + + get.f_path = get.get("f_path/s", "") + get.f_name = get.get("f_name/s", "") + get.f_size = get.get("f_size/s", "") + get.f_start = get.get("f_start/s", "") + get.blob = get.get("blob/s", "") + + from files import files + fileObj = files() + upload_result = fileObj.upload(get) + if type(upload_result) == dict and not upload_result["status"]: + return public.returnResult(status=False, msg=upload_result["msg"]) + + get.size = os.path.getsize(os.path.join(self._temp_upload_package_dir, get.f_name)) + + if get.size != int(get.f_size): + return public.returnResult(status=False, msg="上传文件大小不一致,上传失败!") + + # 2024/5/9 下午4:26 解压压缩包 + public.ExecShell("tar -zxvf {}/{} -C {}".format(self._temp_upload_package_dir, get.f_name, self._upload_package_dir)) + + # 2024/5/9 下午4:27 获取配置信息写入数据库 + get.app_name = get.f_name.split("_v")[0] + # 2024/5/9 上午11:23 处理包里面的json配置文件 + get.package_json_path = "{_upload_package_dir}/{app_name}/package.json".format( + _upload_package_dir=self._upload_package_dir, + app_name=get.app_name, + ) + + self.get_package_conf(get) + if not get.package_info: + public.ExecShell("rm -rf {}".format(os.path.join(self._temp_upload_package_dir, get.f_name))) + public.ExecShell("rm -rf {}".format(os.path.join(self._upload_package_dir, get.app_name))) + return public.returnResult(status=False, msg="应用包异常,获取package.json配置文件失败") + + # 2024/5/9 下午4:29 更新数据库 + self.update_upload_database(get) + + public.set_module_logs('site_app_package', 'upload_package', 1) + return public.returnResult(status=True, msg="上传成功") + + # 2024/5/9 下午5:13 获取上传列表的所有包数据 + def get_upload_list(self, get): + ''' + @name 获取上传列表的所有包数据 + @author wzz <2024/5/9 下午5:13> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + all_result = self.get_upload_db_all_result(get) + for result in all_result: + result["status"] = True + result["info"] = "正常" + if not os.path.exists(result["package_path"]) or os.path.getsize(result["package_path"]) < 10: + result["status"] = False + result["info"] = "文件小于10B或压缩包不存在,请确认包是否正常,如果异常请删除重新创建!" + + try: + result["db_config_file"] = json.loads(result["db_config_file"]) + except: + pass + + try: + result["php_libs"] = json.loads(result["php_libs"]) + except: + pass + + all_result.reverse() + + public.set_module_logs('site_app_package', 'get_upload_list', 1) + return public.returnResult(status=True, data=all_result) + + # 2024/5/10 下午12:16 删除指定的上传包 + def delete_upload(self, get): + ''' + @name 删除指定的上传包 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.app_name = get.get("app_name/s", "") + if get.app_name == "": + return public.returnResult(status=False, msg="app_name不能为空") + + get.app_version = get.get("app_version/s", "") + if get.app_version == "": + return public.returnResult(status=False, msg="app_version不能为空") + + find_version = public.M('app_package_upload').where('app_name=? AND app_version=?', + (get.app_name, get.app_version)).find() + if not find_version: + return public.returnResult(status=False, msg="应用包不存在") + + public.ExecShell("rm -rf {}".format(find_version["package_path"])) + + public.M('app_package_upload').where('id=?', (find_version["id"],)).delete() + return public.returnResult(status=True, msg="删除成功") + + # 2024/5/9 下午6:33 获取上传列表中某一个包的数据 + def get_upload_result(self, get): + ''' + @name 获取上传列表中某一个包的数据 + @author wzz <2024/5/9 下午6:33> + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"sbt 2 + tatus":True/False,"msg":"提示信息"} + ''' + get.app_name = get.get("app_name/s", "") + if get.app_name == "": + return public.returnResult(status=False, msg="app_name不能为空") + + get.app_version = get.get("app_version/s", "") + if get.app_version == "": + return public.returnResult(status=False, msg="app_version不能为空") + + installed_phps = [] + for v in self._php_versions: + if os.path.exists(public.get_setup_path() + "/php/" + v + "/bin/php"): + installed_phps.append(v) + + result = self.get_upload_db_result(get) + result["installed_phps"] = installed_phps + result["installed_mysql"] = self.get_mysql_versions(get)["used"] + + result["status"] = True + result["info"] = "正常" + if not os.path.exists(result["package_path"]) or os.path.getsize(result["package_path"]) < 10: + result["status"] = False + result["info"] = "文件小于10B或压缩包不存在,请确认包是否正常,如果异常请删除重新创建!" + + try: + result["db_config_file"] = json.loads(result["db_config_file"]) + except: + pass + + try: + result["php_libs"] = json.loads(result["php_libs"]) + except: + pass + + return public.returnResult(status=True, data=result) + + # 2024/5/9 下午3:35 创建网站并应用应用包 + def create_site(self, get): + ''' + @name 创建网站并应用应用包到指定网站中,如果网站已经存在则直接应用应用包到指定网站中,如果网站不存在则创建网站并应用应用包到指定网站中 + @param "data":{"参数名":""} <数据类型> 参数描述 + @return dict{"status":True/False,"msg":"提示信息"} + ''' + get.site_name = get.get("site_name/s", "") + if get.site_name == "": + return public.returnResult(status=False, msg="site_name不能为空") + + get.port = "80" + if ":" in get.site_name: + get.port = get.site_name.split(":")[1] + get.site_name = get.site_name.split(":")[0] + + get.webname = get.get("webname", "") + if get.webname == "": + return public.returnResult(status=False, msg="webname不能为空") + + get.app_name = get.get("app_name/s", "") + if get.app_name == "": + return public.returnResult(status=False, msg="app_name不能为空") + + get.app_version = get.get("app_version/s", "") + if get.app_version == "": + return public.returnResult(status=False, msg="app_version不能为空") + + get.ps = get.get("ps/s", "") + get.ps = public.xssencode2(get.ps) + + # 2024/5/9 上午11:23 处理包里面的json配置文件 + get.package_json_path = "{_upload_package_dir}/{app_name}/package.json".format( + _upload_package_dir=self._upload_package_dir, + app_name=get.app_name, + ) + + self.get_package_conf(get) + if not get.package_info: + return public.returnResult(status=False, msg="包异常,获取package.json配置文件失败") + + self._upload_package_path = self._upload_package_path.format(app_name=get.app_name) + + # 2024/5/9 下午5:28 选择get.package_info["php_versions"]里面版本最高的php版本 + get.php_list = get.package_info["php_versions"].split(",") + get.php_list.sort() + get.php_list.reverse() + for php_version in get.php_list: + if os.path.exists(public.get_setup_path() + "/php/" + php_version + "/bin/php"): + get.php_version = php_version + break + + from panelSite import panelSite + tmp_args = { + 'webname': get.webname, + 'type': 'PHP', + 'port': get.port, + 'ps': get.ps, + 'path': os.path.join("/www/wwwroot", get.site_name), + 'type_id': 0, + 'version': get.php_version, + 'ftp': False, + 'sql': False, + } + if get.package_info["db_config_file"]: + tmp_args["sql"] = "MySQL" + tmp_args["codeing"] = get.package_info["db_character"] if get.package_info["db_character"] != "" else "utf8mb4" + tmp_args["datauser"] = get.site_name.replace(".", "_") + tmp_args["datapassword"] = public.GetRandomString(16) + + args = public.to_dict_obj(tmp_args) + add_site_result = panelSite().AddSite(args) + if "status" in add_site_result and not add_site_result["status"]: + return public.returnResult(status=False, msg=add_site_result["msg"]) + if not add_site_result["siteStatus"]: + return public.returnResult(status=False, msg="创建网站失败") + + get.site_info = public.M('sites').where('name=?', (get.site_name,)).find() + if not get.site_info: + return public.returnResult(status=False, msg="获取网站信息失败") + + get.version = public.get_site_php_version(get.site_name) + if not get.version in get.package_info["php_versions"]: + return public.returnResult(status=False, msg="当前站点PHP版本[{}]不符合包要求的PHP版本[{}]".format( + get.version, + get.package_info["php_versions"] + )) + + # 2024/5/9 上午11:53 设置php函数到指定php版本 + self.set_php_disable(get) + # 2024/5/9 下午12:08 安装php扩展到指定php版本 + self.install_phplib(get) + # 2024/5/9 下午2:37 还原指定上传包的伪静态到指定网站中 + self.restore_upload_rewrite(get) + # 2024/5/9 下午5:54 从上传的应用包目录找到网站文件,然后解压拷贝到指定网站目录 + self.copy_upload_site(get) + # 2024/5/9 上午11:16 检查包json配置文件中的init_sql是否为1,如果是则执行查询网站关联的数据库并导入init.sql + import_result = self.import_upload_init_sql(get) + + if not import_result["status"]: + return public.returnResult(status=False, msg=import_result["msg"]) + + success_url = "http://{}".format(get.site_name) + if "success_url" in get.package_info: + success_url = success_url + get.package_info["success_url"] + + add_site_result["success_url"] = success_url + + public.set_module_logs('site_app_package', 'create_site', 1) + return public.returnResult(status=True, msg="网站创建成功", data=add_site_result) diff --git a/mod/project/php/php_asyncMod.py b/mod/project/php/php_asyncMod.py new file mode 100644 index 00000000..0be873a2 --- /dev/null +++ b/mod/project/php/php_asyncMod.py @@ -0,0 +1,1898 @@ +# coding: utf-8 +# ------------------------------------------------------------------- +# 宝塔Linux面板 +# ------------------------------------------------------------------- +# Copyright (c) 2015-2017 宝塔软件(http:#bt.cn) All rights reserved. +# ------------------------------------------------------------------- +# Author: sww +# ------------------------------------------------------------------- +# php异步项目部署模块 +# ------------------------------ +import traceback +import re, os, sys, json, time, hashlib +import shutil + +if '/www/server/panel/' not in sys.path: + sys.path.insert(0, '/www/server/panel/') +from mod.base.web_conf import IpRestrict, remove_sites_service_config +from mod.base.process.process import RealProcess +from mod.base.process.server import RealServer +from mod.base.database_tool import add_database +from mod.base.web_conf import NginxDomainTool, check_domain +from mod.base.web_conf.dir_tool import DirTool +from mod.base.web_conf.proxy import Proxy +from mod.base.backup_tool import VersionTool +from mod.base.web_conf.referer import Referer +from mod.base.web_conf.redirect import Redirect +from mod.base.process.server import syssafe_admin + +if '/www/server/panel/class/' not in sys.path: + sys.path.insert(0, '/www/server/panel/class/') +import public +import tarfile +import psutil + +try: + import zipfile +except: + public.ExecShell("btpip install zipfile") + import zipfile +try: + import gzip +except: + public.ExecShell("btpip install indexed-gzip") + import gzip +try: + import bz2 +except: + public.ExecShell("btpip install bz2file") + import bz2 + + +class main(IpRestrict, RealProcess, Proxy, Redirect): # 继承并使用同ip黑白名单限制 SSLManager ssl继承证书管理 + _phpa_path = '/www/server/phpa_project' # + _phpa_logs = '{}/logs'.format(_phpa_path) + setupPath = public.get_setup_path() + is_ipv6 = os.path.exists(setupPath + '/panel/data/ipv6.pl') + siteName = None + sitePath = None + sitePort = None + phpVersion = None + pids = None + _log_name = '项目管理' + _php_logs_path = "/www/wwwlogs/php_async" + __proxyfile = '{}/data/proxyfile.json'.format(public.get_panel_path()) + group_file = "/www/server/panel/data/phpa_project_groups.json" + + def __init__(self): + IpRestrict.__init__(self) + Proxy.__init__(self) + RealProcess.__init__(self) + Redirect.__init__(self) + if not os.path.exists(self.group_file): public.writeFile(self.group_file, '{}') + + def create_project(self, get): + try: + public.set_module_logs('phpmod', 'create_phpmod', 1) + if not hasattr(get, 'webname'): + return public.returnResult(False, '请输入域名!') + if not hasattr(get, 'site_path'): + return public.returnResult(False, '请输入项目路径!') + if not os.path.exists(get.site_path): public.ExecShell('mkdir -p {}'.format(get.site_path)) + if not hasattr(get, 'project_proxy_path') and hasattr(get, 'open_proxy') and int(get.open_proxy) == 1: + return public.returnResult(False, '设置反向代理!') + if not hasattr(get, 'project_port') or get.project_port == '': + get.project_port = 0 + if not hasattr(get, 'php_version'): + return public.returnResult(False, '请选择PHP版本!') + if not hasattr(get, 'project_cmd'): + return public.returnResult(False, '请输入启动命令!') + if not hasattr(get, 'is_power_on'): + get.is_power_on = 1 + if not hasattr(get, 'project_ps'): + get.project_ps = '' + if not hasattr(get, 'sql'): + get.sql = "" + if not hasattr(get, 'run_user'): + get.run_user = 'www' + if not hasattr(get, 'composer_version'): + get.composer_version = '2.7.3' + if not os.path.exists('/www/server/phpa_project/logs'): + public.ExecShell('mkdir -p /www/server/phpa_project/logs') + public.ExecShell('chmod -R 777 /www/server/phpa_project/logs') + # 设置项目目录权限 + public.ExecShell("chown -R {}:{} {}".format(get.run_user.strip(), get.run_user.strip(), get.site_path)) + public.ExecShell("chmod -R 755 {}".format(get.site_path)) + webname = json.loads(get.webname) + # 域名重复性检查 + domains = [webname['domain']] + webname['domainlist'] + for domain in domains: + if public.M('sites').where('name=?', (domain.split(':')[0].strip(),)).count(): + return public.returnResult(False, '指定域名已存在: {}'.format(domain)) + + # 域名存在检查 + get.project_cmd = get.project_cmd.strip() + + # 创建项目 + if public.M('sites').where('name=?', (webname['domain'].split(':')[0].strip(),)).count(): + return public.returnResult(False, '指定项目已存在: {}'.format(webname)) + + self.siteName = self.ToPunycode(webname['domain'].split(':')[0].strip()) + self.sitePath = self.ToPunycodePath(get.site_path) + self.sitePort = 80 if ':' not in webname['domain'] else webname['domain'].split(':')[1].strip() + self.phpVersion = get.php_version + # 添加nginx网站配置文件 + self.nginxAdd() + # 添加多余的域名 + domainlist = [] + for domain in domains: + domainname = domain.split(':')[0].strip() + domainport = '80' if ':' not in domain else domain.split(':')[1].strip() + domainlist.append((domainname, domainport)) + if len(domainlist) > 1: + NginxDomainTool().nginx_set_domain(self.siteName, *domainlist) + # 创建默认文档 + if not os.path.exists('{}/index.ttml'.format(self.sitePath)): + connect = """ + + + + + 恭喜,异步项目创建成功! + + + +

                                    ?S3d&KGYnay7w6>$Qjn!@|SUmrCqqnEE6RjO+ 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

                                    kaas$;Ai0Ab*SeL-M!D-y?sA{N2JtZ9~0J{y{Ob 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≶M>I>Q5a-YmVyARK>%YRDY#9i|Q{OwUTn` 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

                                    ued>Y~7 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%0Aav&;)=x`yif6diVrGzV5@8oRihpe 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

                                    E(*EK0)-9=eGTc8*)hIfOFDqUS`=2I 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 z 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

                                    DSTP;> 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

                                    &aS!yz^4(L#eM2 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

                                    F1*wP$C{w?x*lfRPuKID%zg?-5n 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
                                    BZbf^dQmwL=Uv{4-w67XC5JXn&?rYCx{*+ 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

                                    @{RB$v`4|S@9KQuh!k!iJV^( zf?~zWK%A`jh6uTlgMYcr-a_`a{Ch(9J97ArKcQgEIT*;MAX|crEdx{W0kSWUeOQ_| z2ia6)pCFrttcGklvJlw}WL0EM$Sh>?|1V?)GE>&9bw4VPupz6+&4igPN7z)3%t7X> zkU@JYl#_bM0&&%>6RCtHlC1L$S&A$}mN%=lgo5lNRi3H%vDiwupUUk|*(_vC(QG*< zn0-c6w?7LJ+c_*gEugu`WdA3!`IN{m#OzBoeueC7CEpO4b;`d(_C4F@#%a&U2V?ew zzED3ZE$`Aw~K0H5h6K(`M;>~FJ!AZW+N}Q;vid#ydK%#$k#*m5As&X{zdj*>Eoc?7iX19&+{ds zvixo)Umy8~N;cpxrKB3?8zJ8w`NpNvfyg&O-WvI)vS&5lOmTA}yC>yaC~m0;QrucJk6(^_Th-yA0C9_ayMjn-kk=tEvA#)sSnF~|pd0db$ah2Dh~c}Hd-IdaCZvl1EqBiGM?d>7@r5?kEvj(lHfi}F2??^$}r zL%tXCu1fYUJ@h{EeR!l-8rw#jZ3W+Y8E5AEAwK~5{^Zi2iR3_j_mQao-H}g2elT*z z=MdyaAwLxP5y%fiE;qH74wE0^5Ro5=yhp)hCoR|O^P`dXLeBl4x_qC@sZ!*}Da!sI zj|Lz95wO?uo#sOKt34xK;(mvpNITH1{2JspAiq{6*C}4#qD-9|l^6cs zgnTse;mB`6ezR(hSf{DRk;q3Ozg_vQ$hrBS4x7VO;TYt1D!D^(>^fWIcWDCeZi(AH z$nQr!9{GK$$sZQTF<9jjgp6hxJ%IdSi?@a@T$Sj?``TO8I-pKR`YO`TO;k^{%gd zSwD8a`Y}Hs{}B09k(95Xkp%g4)V4%E1Nm>rn~(>{%gBA?20t^Avpr!PEaaXNc1Dbw ztyo3wC~=9jB^HFa)UMJ6d5HX1

                                    W^khEr1bI!Jq{uTR&EtO=xaRcvGvqV%)&3ay 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�< zPkcM^9Y#uFKs~6&Bfg8+36zEyT1}4cCGLptBTk9$Ck}}pAb#4m>cOL%WQ;z5lE)NUL}5=_%-5xoAME& z#GHoDH;CURe$zC$b@i6bLfz^aPWzGgC&QumN3FnCotwP7_*astiGL%RjQDqwiHZMkwD{8|k{D)&FmDeh zssaj0RACqnzN)X2lj!}A9~;9bQyRr}FiSGkfGX=$H<(OAGBe4vBzkf>p@L+36BF^F ziW!m_jbsTVGZ`Q1zFKibG7HJJoVNai6KO)@9R z7?MG=9ojal1GjE<|A3iN+^^0Nfsbk&~Pi{ zh@BLM7%&Tyj3ZfOSPwXDK(Z*wV(!cu`hGDl9R7H=fW zk}PL$x0k6B8_QZHE0C;0vLeY!rp;uFWyh11O}iS-S0!1^U{DW&Uuzc08YBmhtVyyx z$yy{EldMg$0m(Wf>yxZ25|FHCyr(R&S`DQ7&tyZAjm##FICk{d-^^!DHX+%HWK)tY zNj4+df@E{q`XP%yOy%Cz4s~0TY)7(}DRgwugxS6(%HmknBscC&^x;cHiHuaI&{?pn;@Q;N}_p?p~7pNcK0L z)avAr2a+5}avF)~ehi7wco@k+Bs%$R2|ssg>O1Y?5Qk^8-`+tY#=Dcm9sVFCEMYgk9#!)-1ga$1PPrs`gXArz;@#WU@7*+iAo+3BYN{5t)%H2v(VMw)c~F?%f`6HhAte^JtfNG)IxWev`ao;4jM{HqFa5n*oy(yK^UB;A>GCDIK^S0-JH zR1;sFbXC&TjDiv$%|(7ux(4Z*Mt!lp$fRsfx;E*0r0bBbYtM))gaMn~`lK7!wM1U` zjUpS7ZbiB=>E@)HkXq&EUk`6)gHeg5!gJ_a)uW?CFr(W=|eKs`fxqt!p@)pmyB{NDm@C*mR5LE0NSXV0tL&S)_-N z>iTffBS`hW&f&M%lOAb8WMn;x^k~v!%!@NP?XByi$GP@+(vwMD36R}9k@O^c?Aqh4 zwL7P{_EguNMtb_teS1J2LVBiA#8~KT(#uITyK|M7GoxT^bDo*z^n6l9^m_P0*IwjW zrGRwDwt$gZbp+|9YTfgfnPRj%SCC$520{dtLt>*`O{#9MA$^eaTGHD|uOq#Y^mua6x~klsOhAL*T>cNwBIkaSvlH|ag3 zdS{JGP6`PNy-#{S=>tX!IWXdOo76+39_hoR6G$H+eS-8+Cl`;AKJFOR=?%f-C`kGw zsVRR;sw#;#3BQ|r)Xl({x1Er#w` zSF%C&Q%&mD_vPD|{GLPFk_w+4>8qp?%$G=?BYoa<>79}eEdh%;#pf@YmQapSLl_QU zCVhoeTQ-)q3Rr1!d27Z$uaUk%`fpOz{~T+7(I{FgPv0bcOAnaCU|_thsgb^8@?sb7 zl72z@9_c5fLg@$An-F5+_95veo6W*shHs# z+q#&3rHU8QuLq7!EXg2lRP>#GNBX_QWbj+LouSi3Kay`o`V*Ph=4Z0CNq-@mi}Y8r z8A*R5o1FA_vPnq)ApO(!-YTi3g~Dti@eJ9-Ca@1aKsG7aWQJ$)q+A`%I-7!QYO*QG zrZT~ldnTT-zf40m1KG4=$E%%=Z2BRc?6_3R{Y$(5LNj$mS(mfNVZ8z4^sz#%vTVMq~?;jWd@u6BRBdTXSUK*@|RKlPyQK44J|@SMI1N(jZ@+Yy}N8cKnK# zz3**Jwi4MIWGj=cPPU5cVO6r#jAz{9ueJKGAw;NN%a~Bpk-1ShR<;h=A!O^4ZAZ2q z*`{RclWjz{0ojH_)G*uSGI?H za!-Rta%x@eP3E=$2>Axjeq;xb?QaacuQu#oO?DvJ!DMFI$VAYCOkU0zVe^ugF02|p z4<$Q|>@c!p$POnvlI$O3N2qkr0w3|A&EhDsqs?h_dc*eiX2+79Om-ZZoMCsUvR;ys zoj`UXne7NL4d7a`Q^-y=YbCVn@5a@qlU+o12HDwUXOf*|loHR&mTBQ5JBRFiH@kDm z&NGCY?7Jjec7dCwivo-rFDAQ!>=Lre$=p7a>{2pU|0&{XrUG6U@}ILS$*!{b>W=*I zOqjXG{p9vRXV;OvLv}q`MkYi(N+xpOLv|C{?PNET-D14G(yL^*N)_lG_hh%(o9+$T zJIGWTq`BWoc9#jYyjiD09nSA1dx-2lvipZemlDe!aH3=jqi#A6lRaY7mkzg_tzdhM zEFycH%p-e(>}j$IuD2)2p0X5~482xoYR=}jsCkx3eTCu8ckwBq`ytPd0 zznWS?mfFg~O0ODX?M_andLCUAWF?uzwjy)q{~R@isSf#snOLhe5@?5)d$Ud~mYC58k;-Ktf zvQKSMKKsOWQAy>hPvhaw$^J|B1=*JdjLFxKw)u+eTgN0{i$%%4Q9F3%i?-$lhaj8B3JNd+9e~|rYRbdj_e4>E`{;EdECn2BI z=q{1b+{Hin{Co!T#mQ$R zcjte)EC=~a+p8a3O35vHTfRo+mP=>zAgE7 z<_xGDE8pJzYX|ZjEkY3!D!Ne;Cf}KSH}YM?U5dEL?dUBVOG&?c_u;%&z9;!UeaZI|sHS7bPvdZV0QnK*2a+E~{x@=$cTz?%KZyKbHy81qMmcnFZEc!% z>RPYp%7tq8vUOVq(o(Fbc_jHUaPnOBxyZ}@bxHiQ^u1;8y9D&{xKg%~q$O@1r+9ptxZ zA<6a=<+rOfoQn@_QsOJg(cR?Fkl#c882P>A50c+U{(!}R`TeF*Wmx5qDALRyB7c

                                    I3-W^84*#@$WJA7guE{o{?AY>_ArlfOy+md#EctX?Z%Fuz0ozPSzgf5_h@e{Xm#TZcld|DZeA zU-v&G|C;n z#Yl=dC`M6?76!~x7h^P#))OfPN1V8o)g*`;#UaI<6iZXgMX?~o+!XUs%tJA+B@d+v zi&BiE7;g(n#UfVGJ!YX1TNfiXv3IyoEG7j= zvACt7>|#lZrA*I=I%c&s`Z5$NQ!God0>yF^%NzV+NWnIE7R8DbE9t1c!8`A<`^v)< zt5B>-u`0!C7DCEzFIG1M+LYI@Z7C3nMS@K!6!yCJ$?9#MDqt?QpxByXONy<8gu(hj+rA-dTWmwI zJ%vJm$wUi^?bME0LTGoJf{Ps~_An+dcA_v%NFn`iG@{s*Vz1n;vYk3 zu?`jHNW03^7DrK>LUA<3F+;k}4SFoaaqi-H<3=0w1lOMET4QgDlhr!NPo+4`GERRn zGsPJcms6ZcaUq3Bc^<{t6zAAZ7Cj?TEzUKbH(olQLM6)PtD7N~tuHR35J4}daOeMM zN2gYcWSxskDQqvWfvwi~;R=fDD6XUshg%3RcKjcXF4s_8Yr4+W&y1ey4vOn3ZlVy& z-Dt6@*h8Hd7&lYgVvH?~^wKgW>BVi-V#V7jKcu*WQl!6=;x~%BDDzsY|D<@) z5M?92MDemMjcK*9_}9Rl6ZD~YWy}JjDPDE$Yp(sbYhQQm8*1HSZ&8RL-!@J#En%hj zmi4;u)3J*#N%0=+P6;bMqWIY2GI=lBs#API@hQb;Hig+Ual7NC zFDSmJ_>$rq3ibT8rFx35tWHC7*OR7mq-MV}$4s3&_l)8Pik~Tdr1;6S%E-#n_3{;q zUnorZTdq=|ng;uwQX29P${8vCq@0R!BFf1v=~PZkISHkmw@+zNfOR4fla4B(=Ida5CyPKy`j;5TOat!5Ml!KIGH5TO@=33ju zoWh$)qT@Q7V`-s1rSkvF4)sPkKjo^F3s5dexgh1DlnYTVZ01tCd&+U`My?i%~8?xwsw9xw@UcXg-PLz0_Ycucawn@y`%DmCKsPRW3K=aZ#>7slx`{%2&CP zz@S{2a+P6!Y^?>{!>dtlNVz)YT9j*0nyYLFlxaGW;&N@u^(dwMb-k`hgg(+FhMDy# zH!$NakYtO?jVL#z+?a9`GfYEi5V>>ZW|Vp_q*X5%)x`7VmXtS8Zbf+_<<^wCvkm1Q zl-p7Y|JzaSI56&c%I%G-%N^{$cCnL5V!1P=Q~s_iN5tJIcefB&$QL9=i#;j#p%g*) zw%nIgipj;Xl>1VOF8fg)K)Jut0!Dhhd`*H}9%w#^)@Y=B#M|XTlt)n>O!*JWLnseb zw>m3{@-Umly{}RpZZC{87CnOU$gzvsN}`coyEx0EDK*wHreQR?^>!TP@#c+aS;x@m z&VOFvYRZ!+FQPn|@+`_zC{L$6mGU&xPU9aHUy1W6&!9Zh_*3K%OBnXfro4bsta3i( zxdO}QG(WB(-2qKwZb|fO8Yp< zt0=Ff6nNLj92@tVxasS4l-HYR=)aOFd-6ugnDQpd2Pto+yo>S{N-6wXDW%XVg_#ze z8O+lEcaGU?9n(3Qh*ri+G4Oqq_fTph;as+gB#DzVR^CtffboyM3M7N@A<8ExAEs1# zL=60hcof8faVT%SH=L>ZW0 zT88#_eI&^0${0JLe33Gx6jx`IC1p-o*suG}Y_Q24Dl5v`s?Ehey1%@})lCt%rR+w} zafPWY-8Y5y9Hpe-c@07Ng3U`G{iNX(w3aVXzE1fv<*SsM-7BLPS;ce^Iz}o7w9t&ZGQ@ z>H^A-sg|YuglbmGPpKxO{EYHf%Fijkru>5PE6Oj26dI*lvJpqATkW|NE zD8Hlpk@9;-njdUxl2)6b5~sGhLHV;yb_hbF(QlN0QvOc)hp7%>Nw6uqRZT=Sv26;L zDUnsGCK1V~CN=hxOjVOpO-D5a)znl|QcY!%h!RIbJX}pfHLdwJI-pw|D<-L?r-Et* zsu?Xxl9wV$l#Z%qqMC(jW)pKkIQP943oHFz%|uLSRs}ce8rhEAJrUGb5V_@n$s3UB;6WII;xtRY950`idwU?JM&R3LN!0tLR1S- zEhr|GHMDv~os3Z}Of^n-Z0=Gc;*)AT)lyW8QY}ffn7X1`oN5UZYOU>xH=IaXil$nc zY8m^Z22v}ESIbfDNwqxHMpP?MtxdHe)f!YQQLRd)2x3{PRg^j~p;&eY2cE40P`MC5 z-gvbp)mp;s;FAaHV=Aj!hiZMQb*a`f`YL!4iLIXvs5Ug1rJx*FQ*BJO4b>)8TT*RG zwV6FH;gY(jHg`1L!Z0t+uePGv+M;;(N7FFXwp2S(ZAZ1e>;G?5I|wavOmvVXJ*u6k zcBOKe5Gmfu>ZYVRsB{Fx4OF{R?J@f34~(gV3ZwO2R2pS(s)MNZp*oOiU#bI)fh*eu zMzz23j5t%^S%(_Vxy0wJY2J%?wK|yUFsehS-2301Ix_EC3br!m%}weEs*|aXq&k7> zD5~SBj;7Muy}05S)5b=-JqAJsKkm_Qe8@QnI$o-&&yqVg#`{P-#dDKamZCv?iC^G++HDX7}eX< zGgG}o^*xo?_*1HPsXn55kLp9J_sujI*meY*G5nR|CBIZ3Q<*nn{I6*myM9Kc$3E8! z)Tq92?Ux3LGW=5G)mKzs3x{K#SloU6hU#0Y?}inTmdTWQs(zsQgX%}BU#Whg`gw4d z{p~m%gZ~%Phc?P@RKFV=n00XHr&WJa&qzHH^>mhCtS6?P#L!+(>e|U%JGpD8pq`p~ zO4AsilWDne2w>8_!kNz7~tNAp!au%3l_e(G7N2dQVH z9z{Jn^+@UwMn^l?X(^ovV680a$gAaFQIA${)MLzJQ7*ThgL)q7vD9;#EQlfHz}It8 z&uv4<9Eic{d8y|!w99k~61{YzUVwTL>IJD6HlC~(vissjA-NuBE1PyPo_cBOMX8sd zUd+vCaif$XI1y29QoSVgQbsy*~A-)N50(M!mW%07=KzYnUFc*Q8#{f;Ayi4x7BSdL8QZELg7BwMrgE z2a2_&2I~!|H>2K=dQ&SOtv90HnA(#6N(>G~8H0~5=w`Gz^%iEiRAy!q+>&}Ln~NqT z=B&4&-kEw^>K&+sqU{YQ8r`W}>K&-##ypZT zk=6L;Q0l{ke?yDpUG}9G6CO={1hu{Pk@`rRwbHrbbEEq))W=gB(W#HKzYJ*!OHu01 ziPUFNpG18+^~uzy2vh@DLR7o@pW3#7shp4c4C*rlwjoN-$Th0Zraq7Q9BMiLhuET_ zR+`8EFpHx_PvOm|b`kZH)E86VN_`3SwbYvaWz?6NAU|mf#MGBlUqgKb^_4@y zLVXpr>3@qR1hW=uHMBgm`Z{Vc!1dHFYh+Zo(S{S}TR%5b-(qYmPe9Kr@~v;9zMuMb z>U*f~puW@ONCGGqrM}Blcdhd5>)pk@)b|ZsHHWF(^Y<9N=$X{g1COKhpwa6g>PM*` zrgomc7(?1mIoK05UrPZE=*YDm#>V*0k>X5oeZHH4)d(^&7 zR#L1~n|8YxpG4HQ_$z6W1jzxQPN@s(jM|<5VCRy`Z7P-t#s$U&aWe#AG z@=fa3soyY7BKnGxZ(EZ3E$X+;qmW&wb^e2f6QjOsIMK_hBoQXd@6#+o{Q=F?)E`p+ zMEw!!S*@z7ej%nasEC+`+upwq5g_mJAa*nB(dTH`CCJqM4RvW}4|} zW~7;(#`ZoNtBBBIwg#G+#5RLB&SzAX&S_?$89_5E&1^;s`LxpBjV%G2{%;0oMj8eb zp$OaZOq$U&^U;i<8A~%rV~f9rYRyy&G|ikea}ECXZ_86Pa~pFu^U%y|cH9*gNt$Tp zr&)+*0UApx8hH9Bh-((687HM-?4bxqzEd-vM)O*fW__B)Xx5-voMu&;C1`AOme_!1 zDdWawX_{qBj_i6_t(ws+XQrxI-nA>Zc172&8yNX(45_@trn$-=rHl1_iqtdKN zvo6hAG;7-(vArR19lJKXt!ESzW%||z=(Myxvn9<|?#|Xjcii>1G(#t8yZBLBx7mSaXPO;pb~5E4WGV?J-O}tr zv#b5l41{_U%)8ULDG0IRE`xI~nxkm;ra72qADX|>>`Sws{p+etX!duQKY-@Ify2%o z*smQp{2pD<2uM5s)3$j&xPsL`H;2$1PID;DVJ7k#QOBDsTG9N2<_M#s{7fU_kpqkA zQ0qr&j;1+<<`|k2X^y2if#x^|&+)^~o=mNnO|sdXM02td4yHx~)!gEuQ)$G6r_r40 zzMf8VhN<^`cOBH>OU+phOJ{4_xE;uIxB5SqW@!Jf9ob5AK8O&t#T=5Ml%QG+G54jwP)_ zIbBUbQ`3|*mTxym)XHsW8X7y$&%O%z#(N#jD>Tp2yg>6D&GWX)TWZAph320$V%is_ zb!lFrarG)IENCJ*#L|YPS83j&d5z}Z#v$rl>F(xrnm1_P)DicieeGA8$KmH~ns-be zDjYB%|6_(*%ZSZ;^rhe4r#~5u^#6}EAJTkH^AXLLG#}G^M)L{HrvoP)Xxp46<|bL6 z(|ln>6eMc3^Ih&`qZxf=Y#m5;irT{%3>pY*jC_=bTAo711z7~=}%4{ z`cu%Kmj0CVr!k`Ur=mZ#v93O1S9L3&ygwcN>8+wk*@?bGNPk9~%TPSkpNamg^k=4T zFaI^`AjHbb^k<_#yTPxd$J4)Pxt9I_{SD}kq(3+PQS`^sA5DLZVMzmt#Qj0~a~Naj zGjdz{bJCy7(zf#RCE$wV`t#6Vi2l6v=a(I!Ki`O5rXR7(!2*W9TSo6MXbdA-_ZOzW zzL}W*IQomw*Lv$Y^vBa*l>XB67o)G3OT|=^(O-i8QuMVtpwPyZqo}p}%NWl4n(~_T zm!m(l{;N(_puZaZ73r@+eze)=|L51W+?};utC#=kR~-=j z*Pw1(dkc(qne{iMFWPNHe^dG!)8E9nQDP;___*$DW-=)G5+n7upuYqCE$MG78qwd1 z{?_!j5%43n{Z6yi0{7rY@6g|lz9s(+x7PoT^mj6}i>v!PJ9_P6e(BJvS${YBpVHr* z{>Ak7pno*|J?S4xe=qv`(%+l@KDHj`LJ3ER{pgDv`#XdmK>t7+MI7R?#Pmg-gKWjq zNN_OyLq_g+}=->xY3cNJN@j=*!m?kT?JDSh}Ep zEB)K(->#pmXOq@D-2FRUdl&t?=|4jM9{N`8Pyb%}_vyl2-)|QaCb;VdUHg!0AO7p+ zqx6S|D5&j?$dvP zzSi$_^F`NcrFi(A(eA6H_!av9rvIw@`kMVJ-DTK*o&FocUx#i=)tJ=3P5&JmPQ3KY zIGtoj|6TeY(0`Bq`*u@{iQ7r>ibfyO|Hurnh_dhU^gnh?{>hM_(f^EAbpM?GAN0SV z|0De`>3>aMy6!7euu23i+q!-vx!=+M)?QL3*i_GB;C-+6vC{v+AhEH2qA#xgnf@>8 zfAm7Lm}1lp+5T_zf45oaXh-QuXUXV+Np<<3}dI+m3CS^KW6SFXs1(ayxPt{JELhK$-X2``;^<6Xy>Gz znRX=YEVQ%J&PqF*`3%?T#EHoihR}|n9nc{gHm-aa#ggqP+CkdUv}1I0Xlt-xzMX@1 ztmRweJIQ~RC(zDCyCCh{wDZxNJ`a}7aA>`BA;r~`>(QY)vpR}9MZfe(>yVI_;>Ujs+ zEoir<-ICUve-%;K$#_yb?KZUA({5`qMQiVYvYz!v*%8C@jU)uf5^sk|Pb7zUiXb+%0 z(3n<1 zv`VGA4c!K$IPhk(r|m7Sz17HM_x0HAb}d_Gzlv$^b-X9q-A#Ls7~T%iRnK}?vV_#+ z{eIeqX&<1qlOL>>uYJgbSINg4jOrG108qZMpYI4{sXNBg|ZMSw}_4XS_AzU&5lk=CZK*OJ;C zhbjIFt%&$4?Q2HCJ^HbPB$mrTy%5W z?sa9W5w>8$?W0pMyThp!hgavM~O(k(-`7~PU| zi_o;n6q1oZfVOTs4FR)K_PQlx)tb_qg&qU(-cx2>P`;qR-{|WR=V|fMbHx7 zZWX#c=~kuNl5RD+4d_;$+dZ%tJy7lPRrgP`N4Q@-fu6fBHyZX>#lO`tT&k%w7cuG^Gu3%bo59yT|FKGy}R4>!A`XrkMSZb!PU>9&>Z({1Av zyL#S^Zu`HkcMv3_)?C9D4ZEG_cB9*wZdbZpEIA;Jy=-gnh*!Ja>GrUiXQ>gO>o+=uQ!x_#*ma4fN(_At`zuRMksGU)_kp}(1N)C!9vv^$9IP`ZQZ4zZiU zxmp>`?l3y>n>zkTWK{~3sGm#ok@2w-C129TK>29NYhVFK{hv@F0yN~Wpx_jyFau^np z?~&Au-0hKp=P$O*r+EIlN$KvVdyvlF|2||jgn7N4lkQ=X`65aE3 z%J~bP7o<+q@RzoYrF)sqRn7<=Wy0rpjP6ysx9DD@b1T1!(TsUsr+b6$ zP5bXjFBtH0xx2UN^yYUdKrxKem&p1q-RE@g(S1nwKHUel(jlgmY|3_YAJKiL@^ZS5 z={}+R)OvHFgtdA`K5+L1-B)y9(&^3ehZ{?)`rY)^*L2^QY-+`!`_^V{!%6!MY0)2e zc0sxyc^0~#cy@BSpLup-x?kvi)fQAEjgkPJ@Ma-^S-|cOx<7e#B6Cz!+-Pa2zw_)Q zJUgjDGV3{mZ#}@XdJC{s0`Tnrqv|c7^th4r?=ZtJ=rN2Z%!%zVGdRr5F7;Q z&&C3_6jVRC>)O!FN|>46thNlKH_PzNN7@TAN{SBkW}`Qf-t6@H=*>ZIm_e~OC%w7o z%`K#9&lCNIl--pxoZg5@{ITBTTzaGE%}=kN-aPb1t7LiuL+2ja`Efl>mc1i;MQ=Wf z_z6h81?Vkk{=aqoB5Wag<}vCwFlHTki_%+--eUBYr?)u0arBm;=Qi-s`4_#VY$VM zD)f~6bE9IpwimjrS#NcE>(N_--rDrmq_>s|6#a%G#S?lWZ(X~Wl%Lkv1|}%>LzIDZ9;ET1Jd~QjqRFE=xt8#W_nxDJD%Q_^d`{Tir%jD zwx+iOy=~}iYrd25b~}SpZ~Om^yVBc{-cH6Yt9@tlZL}Jtw~L|Cq^bLT=&1@f`K|=u zrIw|)r)gPlFH@_YPJz*U!GGv!{jXv;+FM(&*^l1g^!BGG;t!zb;y-c;%#lznlF?!G z4x)FkO!UB*iS$&?q4uo+CMV6}PPQ_d-VyYUrgtR0qfEI(r1~{0{}_75(K}WJ3gK=V z?oB!H^iH66F})M%oki~?dZ*JnnV!ynH!DQXHh-{gsCSyd&u-43cc#V6B^32ahNZLV zsa|aXx4X$j^F+P#=v_qbeE0qZ^kn=kr^0^j1t*|O=v_zeQhJxUimW^9KTMQdPVXvu zE(D~V0eV;38g{$6T1JnaSaGdIq@D3}?Y*9!T6Y6I7jcpikcG5%-AL~yZl(7uz1!$LK<{>X6Y1SS?@n_j&EQx{9=*Hh-EC9qc7G4Od#&yAGkend`)$~= zpxZ-CF_zwg^q!#i5WPp~J#1###QMlkKWkf+_8z16_#`Qtbe^R5485n!byqjtd)h4a zkmqkI{~Wzn={-;HC3-K=d(p~K)dRbW7ACoi-MidW(+&#((QE$iTRA_yj@}?W7Xp(1Cb^jYen9WTfiap~uv9&pDd>GnPsaHZ zdSB7|l%BZyUwWTeGxe3mn8uke=zVEk!h9Z;44_wcWg(&nZ{I8-uqr-dNcOym8Wwk>fr!3#k|6FL}%2 z?T)t`-g*H;Xw*lTJc;oRl(&!9tLmQ)MdmS#VVfm`88UR&dBV=5_2r8tbYE#7uEQ1!NV*Bx|qH#_3(gtxQ7*geq= z-mZAI9M7z=TJh+MczfXOkGCh@1iZcQ_Ql)VY^Jx5c?CkauD85q9iK6OKkK0sb@vXy z`zPLkc>gf3X6QA=3B7~x-2R`EpfD!ukI+?^wK} z@s3eG%&?uW*3T5g2sqA6s3yTZZR4ZXorrgmdf^efPaK->!#f4)j+_DSX1rVMPkGdyo&Sq>TmSl3%DU=`cL&~Gcz5DWG-OHy zDqJIRPo&;s>n~)cM3OP>KDN30`V^Qv)S&Mf1EIuXL{!CIz_`Vwb!Eug2@}8obs9 zP4anEzMN`r5br;xAO`RE4Kw15aq>gFAMrlIQyV|VQ^}v;eQu#D?^C@0;(caJkg-y| z>I5ZgqwPz)uk2pUbgmKJ*LdIIeS`O{HYqYArcaEX@9};ZvLqH^FiL*HpBztc{S)sO zyk9Nwr6wDMq;bFF{b4c^<&Jo~zwjrsl@!_%e2MApHGc~H>F}q-*M48`m=OJ`4NJam z-1;wfh_Fe`pB{fEd^ZFT+5U|7zE+M)mF&N>;4gzeEB?Ir;19!}4S!Dj+41MFl#d~& zMKi_zT=;W4SJ~y{sB`iA@JHhh$M44I*7)1vZ)3pIzuf2* ze>?o`@pmxWzLwkR(d4)j{?5id0a8Dsc~=67Y&ZP7@ps2R7k>|Y(Yz+g^6PJYn~L+=Qs^8bCkb_o7K_y=3Zw5;}iJkEW4DE?tq zjzw=Q?+E`0{FCsH#6JfADEy;M#1gb=(+l{=;va8X=F9$nYE>B7C%B?cv=WR_C*z-i ze+vGohAzY5Y3_RZP$0;zhBtR?tA7^$+4!>m^BN7}kNxXBd{J^fz6!qp|6+Wp$wfmn z8}W+y`--Lf1{=e|A z$9HKP0+RI0F!^u%8;!jxMBq2B-i&`I{w?@6^NWA0m17|_arJimI|jxc(8nUW;!niC z%VHfeda|6>_IvQ9clYA!!~5{X!~5|c!hZn&L3_;!!oB%0{`2^cxEdeDe@bQ+|1tc> z-OUqbqV36(8bSPh|1|zH?%}h4KY4EGzcxTn9*!^j{|f#~_%EArQ+1PM^eX<__^;us zb+6;UVUch(%7s2`2?iB$r z&BBlIGyDW!YHrrU>NWi2_=Pc6e=3%1Z&vt!;n(;d;5Yakerx}ogq%VA|ClFckbB=K z6jn`lKE(eW|0DeW;(x4m;eUef0I7Fm0W^H@KN}b;JbaFCgE;&z@V~T@?fxrMPrLcr zUBAKq-ranQ|DA6B{_hX?zqsc=y6aE)KmYH4-IHJO-9kC{3H~4Wf131VFY6KClMCQUqfhPHa|!U@XBnf~75KSz!VF zmfw*;MXf-v9D(5ECNIPmVIWwMU?uA#Y)zaUutKm3!IlK85^O-Q8o{~*s}rn4po-QM z{pMi?YZ0t%E7P^HwtjNgrRj=61?v&4uNzyODA*`ThY}oSZ4{IBlZO@vipLWi zNpK9oQ3OXDon|^xJH|9PmOzUo-N;&_>D4@f69`TvIFaBK*UXa$PBt*;ClfjpeF{z^ zINc1LMw;TrT;k7}1eX(>MQ|a(*#zeioI_yUnZZdl8pzKlxM0LC{Ud|L%(*prT|{sx z!NmlZSYzG6=oX6$1jWmY)e`Z3KiCXia0S8j1XmJ>^H&jEZFWP+|BC#`;|i`NxX!#v zO{a=ZYxNBTw-QJkmheOHZ>N$s8V{8t8r*EmkvR=+F|Wh;c^kp)MwL9|tB>tFP&mAk zSO}a*B-!0XC>ZF&zX=_> z#iImD{+Vdu3xz-pKTi-mtp9y{>_=Vsn z0=NI0%(Y^|M$fMVzZr@y7t(&0NhJ7#;7{`gs_ zy?M6jMK~MboP@JWUL)K43Fpw&pljSq3Fju9hj18SKVcu?aHs4Rhanu{t|Q%bl+mmx zWjLD9)*zUfm5(QN2GxjOsJ3>S z(wU~bL%0Xw{)BrH?nk&6;XZ`6dApi9Oe-(KeF-P@oiU>|K|hfyamXJF4f$&&D8#ox{2GQds&4F>k)ro{B+4^ElRfH!?j)bQWp6XUnS&ECp;pv2;`3%Af z3C|=vhwv;yJKWrwuRX4f1LqQ6U^(I8d3s2AzWqxgJLP_bN(e6^6lgCdywpiSQ6L+! z4vbU3JU`)OgqNEiJKwUCLaE`Eg#RYIitu{Es|l|$b1lYNMb{EuXLEBZUk40@HxO#A zw_D_;CmQdEg6o}xHxb@Jcr)Q`gtrjhYEmBY9~*Q$q9$1K=g^>L)Bl}6hX9%And|C_{vGE_a+mA`-S;FV6Edr$5qJ;1T!Z!(DBz%SNCBl~t z8=_V<%DV_(C49{y8;?9r__{?e!#8Xxw}TMjTZH1k+k`IP##-^NL(zLyh0DAm^aul2 zwM_vSTg=o*k0QcY3^a&}ONKo?|C}%*{E#pw6qgFZhS2tCC9DW*dsEAHbXEBZvV|>S zM`$}gkC?Eh0l=Gqc`mtc;cE z(HDfj5`Ibe1L0SM->4!&@zeZ&BVP3ysoxSxdA~PI>O=jse^K}&;V*05XOQ);PsG%FEAvzZqm zqal@4OQSi6i28{}8`C7pXn<&5 zqIo9qN9~H{8+qtE`njqXAX<`WL88To79v{I^f6kPXc2>un0frM;*SA4TAav&Kh~cv z|E`S@qNRw&5se`lYaCX9RN-_he`z9l`?^_%NUtqtd=`cCX%iclPcHq6RwP=VXeFXG ziB=|Bjc663Rc$jsHQ)7#M5`06VKvHJo40l@(ON|760J?NjmxoMl_yiW1FO^6N~vHhkpXnm_}Npu9!k>(i7szgWGw?^$dM8^=FM070C zi9{CSCOV$z1QVaosEKk3OK+NtP9}1zeWO!|PPNknhS+$`$Ll&Y_(Q#Mwb#@Nu*D%Ai9j`ax;MH ztfdhPXio+gE|6u#cZ%s2Qgm5v@GdV=V28-LArcmGd&SYK=M! z^noE(aMG|y(WK}jqECoEw&n|d^5UXTM>cDX*z-8~^F*H!eMj^;kxKr8=u5K(^3tQP z91m@coBh=HMc)v8>!{TluGo6*=zF4{iGCpZ(d?wTWD@aD#zs@{Ux&v)@LfD7@myv- zthBi;P7)78@kp6IvF`E}6~`tXLEKL~l31I6y2ESSK!bQRv6kD!UXpk$@lwQNtnigf zXSemqIO3&Eqcxb)%R*_qEb(f@%Mq_kto6SexrZwduSmSoq(H8!ljg>&5U;AQEc0Bg zuy0oaAN4zfadc^Awt%NcVs@IFhlSm>P5`Ra$ z5%G=08xtQ(yb1BX#G4ZDOuQNKcEp<#Z|$171@V@|&i@y~g)vnprHHq265RIhlD8+` zNq8gP!Bw=QnHLSR)}O)f7Vkp57xAvddlG9;+AE27C*H%pb(;yu*%PF-h>v)0;(dlV zZ@7}D9Zw)Wgm^#V1Bv%1KEQY=R*2`C?}-0Fe6TrM@jorV6(6KcT5KVq&~oMZ#D@|e zL3|kT;kM(SyPiH{+c{g-r(Hg`xJfv!XQ#l^=FtH9%l&mk7rP9r{%_#_*v z%G}9t#U~S=LVT*(v6bhrxgME?_;lj49S6=JK9l&YNz-sDR!SslH5QJ=iVKL(BR*e} zG8aOMV>Dbye5nW^zKHl@;!C7`Hr!CAy+(Yle<8k%_!{ENiLZ3Puxtv)>Z^#awtMFS zIr6S0mQGzqeEt9bxs=5=2C+BwQ*KFo6Y;CWHxoZVd<*ei#J3XPL3|tW?KUARvSt4b zPInSdv_BQH696nu|WOoHuPws{n(+tHwTYP8<-&#Gx&nzWZ?EXlM^%V)adGqiD{QnQUpva?$0F#RYLq?B-PgNw(-y;?Ic% zw$E$~sAdY!+73GYg7{nFFC8bpBL0ThW%0#d+qWuEbFa$Fa$S|f>h~lY6aPRmg7`<0 znTUTPnF0lq<3EUhA^x5CSM#0X-wc7SUochh$!o`7F397z|zQ`2v$ zC2X*xR9O8~f+9Bd`52O=N!9cNy<}O7hs$Io%ag22vI5C!BrB4v zWVC4$gXKj0$|S3hxbf(pjNx+oOB8d$$BI*`0JBw zptNVRCnhX$X+x5Ytbf=0GC|6JNH!rkieyugeMmMV*`8!`lC4O#Alb5S&0p+8^)>Qc zldVa%F`bpgOSZM~d$OJJe~yWx-`JgG2a=shb|i86pX;q>Z`xP8knG{uvMb4MB)bcf z21pyfXwzvCDH`@7QS#5H_GCyql6^_kvsq#a1&dR`5$gm9gR zog73W$Q?{_n3_g%h~wd*7DE{7xinf#B)cQ6;Zi8+j!EihlCwyTAvu}kSdtS+jw3n# zZ=Yd)$I=r?PBKN%gp0e9oI-Ls$*Cl#sSqPfaI%)3L1L@#tv9*jce5eM*(4W}oI`Rx z$+;xwnHtI(sCI+F1tb@d*j^=*Eb~wTQgR8&Wh9r9*rb&d!DcE=&^r8UVH3xN0``@p z3y@qz(vVzD@-)dcBzKcsOL7azbtIDY^&~ggC(812zBx02jJavRC*_Lbg|aJ2+4xzidhM=O~~a+g*u*-zvBt^aaiTh%D9DS4IT4U*SLUN>3mq1tZG z1)I0*Pvsyvsgb-x5|O-1;*-2b;#pihQNqvLM!y_>4g8aUL{`f(%FI=>7h;l(Bq6c1 zA1hEJpxOT*$w_LG!r3TQS6Py|*r>Xc(6wa0vwJP+R3shA*Cc}^pOE~A3 zUyHlbgHW>U&V!Qp2{6g|8$!`{ql85%`IqpP* z!<}URB%Pe(FH*}Ju`0|iolH6f>6FF_sj$`}Nwd`H#6z7C>71k^N#`Q%Bb}Rc zm?@xGWY?5*IOzz(gPw>yH76ZKIv;62>Aa*00li5&Kst}b&}>4=#mBVTLf&;czj0gY zDK{@&kn~{Eg-ACbU6@qmEJC`3``4nRi;=o?dQ~J2C=(-Hl5`o;rAU`19izAb=~&Wn z<|WFYi7he_DqQ_nx-98(W_#5a=()xq=?bK4kgiC&D(OnX4C%^-93{-BtMn~@g@Q|@ ztC6m5lCnnK=vuKR>AIwAk*-6!wt+_#si)AX<<{!;NZ0Rs%nbvzMgK`#la43diF8BK zEl4*a-IR1=(oIZiQaV+)m`uuMq?`A>u#5dE@}xECmZaM{lC~nXL4m12x{V=U{&~6` z>5it<>Gq^{{)_!Yp`2~Qz|N%mknTddJL#^ZySe{bkXDHee>NcS^hgl*Rek{&>Mpvg`)))=K)RQo{#;~pNd)e8ODa|7c#Qi(lbd{YSNvUTZYygnGmI^`ye#4W$1v%qS0ezTENoM$()5UVOlnd9%CTVxusXpqYsD zHqv`YZzr8ddI#y9R-lgXkR>-6-9>u0R_K^dF0C}o+)MfZ>3yX48xQ5SX-D_*(~~|( z`q04AGmU)qX_YWy(}zeO;R`YRQT`(|K1THm>Ejf_)e~gDlRil%I6Xxsm4BM_Gty^B zBhqI{-zI&I^cB+QNnfxkWWLfDb&wY6OQbLNt$cm|o~c_;q$a;g`kHk&hQQZJ-y(g3 zRB1(8H)s1=*nFsd@}|;vNPW_GNfrFJP$a3#LQFjas+7WN2}ncJT|8g5R(cy z0&Py3l4cg;l{Hq@(86Dn7Nm_+9O+I)S{qM>ltNcCGA*efI;aKvTCG2{Y@hUf(oaY~ zApKDAP*J2GIjiEj0GmdUG(RP^4Ju6#!oT6+bJ9Oazaagd^h;9J{uSx}n5L)@(JW^* z{f6{g(?@yT>34>6wae_=52Qbn{z&?h@k%crtaS%!orI$N&xLKklKw{e2kGwy4Dm-- zX}xLdUu4sfO-42)+2mwXSdD6(d>$b%n~KcM|El3)a%MRmWG)NT{yRO{EMzl~%}h3< zwKSW_47)xT57kp^T$asBM*mTs83!#4m#$^AlPyCw2ibgNbCQiDn~SWEY;Lk)78O+Z zPSR0!L^hmkg!M0K!ieqr9(b2*6xje-KiOzCzyF~gThTb6K0^KD3%ii5OtzW{JzIrrRSVAR zr}8B!^(@&MWb2ZxNwyA|od1cA&pNoxX7@~#@^!QI$ksQ?#Z{wcgGqAFHYD4EY$LKw z$u=h2#5kZ&^b^{%&B!(%defemytX9Unrth3sM3U-Y#Xv|?Z4)M%;Kuvp6nj79mq~0 z+mTGX+KFr*vYpA)UNy&7dy(x*wj0^*`oy*)6SYdiQz7aBv%Sdnwvn&?JanksItdIl94m5EKD$)g$xQINO>>#p3$qptv#JKd#HU?Lt_AoLVhFBys zQ>aHTA4ztU^#U>limY0zk0Cpe>{znn%s$&Y$2*CiFe$UUJNJge+f zvdhU%BRiMubh5L^&LBI}diE>NmwK8uWoMI}WA3*eIs-y>9@)iY=aXGTrWm_ghmkzGlq@~@=?NgoZd z*Sin3|Bre5+6kXb;JMMzC@hI8mvS-PjbC%(GcYQ(Ep?NW~m&jhWDNCJ! zt_`d-E0?`WCaPW|6DMCMdz;Ly|H{-B#s96BH*2OFflVXX>|L_=M%GUlq=#%{=8=VD zK3QNRcc&VT+K4P3ddD*&A4T0@+7o-;sSx_BGijWS_Z)e@f;y{}JT|iOTct zLmV~?d_^Yv@6H2tu_Ll?$iB5vu>O=@$oI&;C;Nr$2eKcH=k8mwpB%J)HlApSQ1&a? zZw@EdsOwi>Pxc4-qhx=QOWFS-Uy*z=@;S*TC!djg3i7GRrzD?Bfw-YzveA=IL#`P0 zzsRR0pN`zd9me^gtLl>W#DiPp!)#pF>5F&qhAGiA>LjeQb`07Axg* zk&hvtn|ulKVdV3W_sJ}i54Uy4`G}zfmER%JCtsC( zHIwNhXZ9U*j1H9}UxR#2^S<1anOU}61guBC4*9xv+L6>>J(QvZ`T80o+lR)_@#I^O zZ%Do=`9|cM7)0}phk7n;9W;RkKy}lVK~v) z+2FAk`F`YklTRSu$Lx=6QNFLu>GW@BXt8(pCqKYuG&C?5_hh^Be~_Ou$GPkAfB)AU65ZQ` zpggGjWb(7fPa!{@{8Y0x`Dw;&Sy$t^q;Lkg0<(YbO_ZNae$L3nwO)9lc7SvoK9Bqc z^7F|rC%=IFQt}JQFS6>?m*p2*TqW1?Mpve?>~d9lnYCB=cjzL&g8W)?0nCQd_4&PRsmB!7(jFY?F9Q}QRsUn75#`~~u-$e$&Dn*14S^`(iOXJPa_NB+EFQh0M? zXYv=xUv>uMCF{$K`LB?_DihcL?dQfWtMqm9_sFF(ZjTk$-EdYEfUo;Cu2Pj6aIK zs~0wUej@*s{AcoCOjvqN1SqnU|3O1tU=0=NYDWZqmDCRH*7PDC(s8AG0L*sMpWl>VhMbS?&H$@-CFpDS4;nxVR7*1i?o)jY}M#|r| zBrCO4J)JmQjHZ~6Vt`^^iIQTTzA+BK;<@JNi}@)Q&}$<{yTr3%L5hVa79KIa?*Dq> zfu)^9ibW~bpjeDzIf}(8#!)OmF_vOUiZK*RnfOGcmUC*~p*2r2uSq+G6-!enaD9y< zY*~BZ)W_@uqCyQ>iDCtc6}8I7zBN|7NwG4;DrPh!2pNH5Rf^RpRyQjl3`hjV)io(L zq*#k$U5d3S*0DB9^|T5@(Y#_kit!Zd+so>Riw)e~zyIui!2&E48&PaZu`$Ia_LU5a zGy+L;Gm0(Em=~KJ1?Le_7#f}ua zQ0zppvzbBFE}JYY6}wXGp-C%>-3(5}?z)=#NctKy6?;+aN3l1BTmLokUv?IGv9EP) z#RQAUne6tbIKVzpC_@OB=UIrPqbSrmm46V0wlMmX;$VtHC=Q(@2oW&%&VO_D2#O;O zhiiX5YV4P4IK|NvCr}(iaXiJb4w=XG9e1pCOhX-qY>1BcEl#00$^3A2Ud72~E=0Bj zvGC;-r%_x@aXN)Ka|Xrv6l$YDF5aF+aW=&{<^c-TL%W?=L(a4DyR39^0mX$vxJgY? zSpHp#izzO3HFcK$cp)A1DJD`p zPjMH;6BGj20~GgA+^gD0&hsM0eL^*b8>y)`QHEIYAjKmT54jQ^b_ZBbwEWNFQTOI! z6ptH);*7geJV_xf|d;sa+8T#{Asz9CbfI{FZ^(#1yYXnBo(PPYv_Jq|CTDqf8jn$ImHL zzA*WfBt`M1wOT%KA!vO=;kE!3{`F9E?)JiW6hBjZPw|6Z>;F^&`q9<((~uLWE5$Dq zzna|DgBz*8^*#2a^+d%Vl(IH|QqDv17v*e}lTpqzN~k)U2qOF13o^oBh(Q~kKy>2gNOStw_soLQ4Zw$NKWtax6|N{RKzmRjbbn&s@2 z!zt&WoSSk^O51}%Z(2N45-*1#cU5nSMcNT)>CY)gQ1(-fq#R`^y5SWYEn3N=DRscp zab{W-Ly!$A=cQbZaz4sMDd(qLkW!Ps=bGWwerV-Flr9pk$gQGSrTl+0iIj^`E={>O z6_T}1WJ$4F5us~r1zEWU<+_w>Qku=DT#It;fo1weKKqGnODGEM z;d+$g#S_Z)DQ#SAKW*hLJQ%?nQ65AoB6p?SgmOE|O(|vGH>2FjRHodVatq2WT_Vc7 z=0graLL>BY8_I1}g|Sq2Lk6wfo^mJ39VjPd{@8Q%Q02~)ZvRgOOjdus|C#eCccYv@ zxjUtJE6yl|##UGuNVH^L-Fmq=gJIexgX_$l>1X2V76E4DLpd&sF_;I z@wR334Z*cMnDRu*Lnx1?Je2ZC%EKrR7g}`2sBIEnssnYMtPEXwl%dEPdkm!_KPv55 zL!D-m%i}3eFcnrsqD}iWmM2l3NqI8mX_Wf&)c$NS!<(9>yu0#r$}_XXCbKD_s9z|rrnHnU&O+u!Y?BN|NO{!Mv{ zRvb{?NO=?G&Bg&$J!B;$3+;%Lb zSHg+m{~F5oDL?r8{=RaYD`*7)#b*228@*9(#dXw^7$A1Crdy59B=d#XA{P~gc7s{U~f42C8h!L;U z=a#=xDrIh(NonEMs6VL0)jz2wr~Hd*GV7-VLPf~M!)gkuDWy9WoY52MW;Hd{3{=xl zO=~(OE~(?LrlXpElDM^}Wi=z!tVUQh6V=RAvlvgVvJ>iU>nHUZ6;!iP&2Ci5N|NW*^(iJ}1Pgew&(#R3kw%^zt-gQspQ=y#spg>?O*LS#Ux`dF zFR=~Pyj1fU5>&pxBc4Vaytw*&P)%sK$ zQf)vr-uBrOdoTWC)cjvkZA7&x)y9_HQEg()pJWx)kmIN}r`noo3#zTCwzMdda-5Xv zf3Z6ez7o^6HJc{8VUeF%sJ5rtjcNy~ovC)D+R2!yEDPBd%_mm7Q0;2>szpsU%6F&Q zhiVV1y{Y!JR45bcUPhjF0(opZC;EM7+A<=1m}(izoJR7abp%e<>(S<&iPsuQTxd<*`Y zmnCYoi=&D4L=&;}w>p_hf1W~hI@PIEr)d${h)q8s7kCo2%XR8mepE&2{!%r&rjUH|T~+B;7{!Z};RzcfHA7Z+6#PsBYCw z-+zzNPdD!2?e2PqyWZ)p6Nkz)zL}=nL*-H3OZ6<(eN>v#y`Snqss~KmhuYF+s)wi^ zrFz)LfYl==P_0+hRsFT$_HnAGsGgv5@qZyiHr7ghn(7&g%t&5Zx}_Pw>NzTb^F`vBE%G}7qbsfEKosCEA*^$JvfQ4gb@jCv;O z$*HHMo`QPF|9{6KUG-GdQ&Y=QX_eE-c2%BXJstIo)EWY~Rf@IS0!UyOxM9RrDr9Er z*{Nrtp4AMCv{J5SjgeQ+rmJ!%sOO-b+jv;dNj;ZQtCz)+$sRyX%GyK$c3NZD&=3v{6Hs1Un>IJA5R0m`XIb#D;|5|U3 zqh5r1De6V37pGp#jI;T5s&@(MB_%uSwp5Pnq<~$I5htm~>N;%Wi>a5UUY>dxbv@L} zQZHwIwIHEF)KVE4S>1X?>haVoQLjb4GWF`zt5C1j|L6wPIt6A@Xvh`22KAbzViJ~k zr5$DKwW-&mUWa;J1CkN8t68LaeQM|Y>vPQ@TkAHY-kN$N>P@LPrryMOD~Sj{n)j(U zqu!EwbF(@17S@%^^2;Bt-4Xy9=i`p0-iCSy>TRj@d^-_lZl_B4hl6*$BlS*3ty-sE z@2-DQ??QbV^{&(hQSU~*5B2WUdr|K}y{GX(ll8KGQbW0R_1LaNSr9R9MA~)WIdpPwG)>y4f z*`lI8iu!09ugUlu=EW;D?F4EG;&>xQDy)?9E5D^ak@{5XlN@;`Q=cL;-oJKkCS9v| zhH^9N)2T0`K7;x^Y60vV>a(cN9%_`mX*hAAOPib(PtK>lh+56L(E1B`6!ITrSzsZbMt@8g(eWRVftkP(M%oI`s?G zuTj59{R;I<)Gr%ta&%?4bv5mK)%YOJ2-Vu-qJD$gQUs~rq<+i9A)}8lIg4&Zm@5=FAIjT3Lj?4&a(e6enKX#SJ}K{)=XLn#pM9rJ0;&4w@-wW}%sq z#_jy9u2$S`rly(365E<--E}%+X=C=EW(Iei(OqY9S1kc^;bQymtTfo61Ja0QHk&nT zX188d{9pAWnmK7k(#%COf@W@-;WWc&`V6Jwr>uv})KG8eTxFV3H1k;Gx#_1FO*5d4 z9Bl}oEEJ6a1?ZI-ZElV&}dwP@Bh0%Wn9b*vw0*0qY9UqmAU zHl*2rX1u+l_|J;ZnhvU1VW!!bW?Py~Xf~(WlxDNO<7O8(YzA4~ZnFi=HbNB5mNZ+@ zY;Al{rz6uLKC23QXFHl5+>LGYWQc8cq}j`60$W-pq(ZJemD1h8fwv)GMJ0lQ(?#(Hf(n*9yWqFg*Ngd9k7FpUa7h{lcG zF5bX8U%epP(i}o_IL)Cpif<14UtC!>tVXiUku)dJ97S_1&CxV&|9>?{{fL293{?LA zo0=mqV$>xyC(@ika}v$T*6=0IwPCvvc`D6meU}|!dST%~eR~GYl{7-y1vF>ToJ(^y z%{h}o4eIEPKj+b$Z+2jd|JqtkEB`{8OK2{lx!8HC6K$|8E74p^bGe1T8cFst8_TGH z+7Ls@#?2L0sY(1Q8Zqi>n(Juv$+c!}M7AuU{r7s)dg0Sb6L~k${F}yYCoW=yK&$;` zn%ik^8Cd2$np@Bi6?Rk5v@`-^ghj3G#}9Xm*zv7Pia1K zJp7pE6Z4Mrrb^H!GR`K=&uEnJuZDb1^M#2?tkYFr) zwk?b_>na`!PVKC;SaVdAWU3amv(t{Eor88b?VPl83EuWE8R2$r+F`VP=IIF%+Rpls z_i0DajT9!L;%&PC?Q*mW(k@QB z5bdJ03)3!Q20_f12%L)1E@lFiEozsb9Yeb$?NU~klEBqd%Qm)SX_uxQXG6jvy%1d6 zWoVbRZ4HF;yG4@dUyXJJ+LdWnq_yK~OJ(7IG} zF+pQPsakVqy4$Ar6(B;>(ib~y8-R7wBu&xO!(2PWGhT-vMSX+P!Hf(C$O4nO~c2x2xWiqP6?k z{dF6-6VA?}Wr}XgC?Ttu9gAG52(;h`Dh8#I^p|@?| zFJIK2A5D9Vvk5lDupLudG5J*5<7rQ#J%RQ_S>s7F9r8xnlW9*e0GQ!g-lAviX|!k2 zo=$s)>8aF5OVrylZ8W6br#+kY9ElN`#kOA zv`^DMLHiW#lcqS1e#6osN76n+`B-=uw!_Ep-K^a<_Dw6ECl zaI08=%MuLQ*9Mkdg7$UVHwKp7#>~as|4%oxZ_!%(-%O?Cq8)77cWE=)_h>^}!9Jk% zM(s7bYl5#m3aqaYVWw6QZR{|q_UbsWtu2_)YGnP3yUA%Q+Je@>VE8q5j)`HQrfo#F zIdY@^G5Yt1X**hRW{~!CS{3yv?fbN!(0)Ms5$%UoqtaOopC4Ow!m6&ClHNmN{V(lj zGP8C}fN-u}zWst$3i2hb%>tVNl*ml^7`yK5cwBOVIO#1`vPqaT8 zVfwGi7ZNmHXn&#oefXAA!(VB2_}70d)+B9G$v1CL`zPJxw13e#Jx;g2lrJIFrI=UI@rl*_1v{K(HVlTJ6n~84bzBR1pa`TK6 z>1L&y%^2QcGE#5S%}zImrBx3Ng@n&;F1mT?=B67(H;is1U7tnIy5V#q`Yzql2GsiL zMhCnudna8#-Du;OqS>-1-GEw6H_uSp>E@$bkZyju11~oTYNP7fb}gJ zAZ~Yy(yc?c7~S%8i_?vzTY_#$`@{-d$`wAwdLkQ&s@3D@mb0n7ZfUw@=$18OyTJ0M zS#l@DbNN5r3UsT{tw^WEUwcXVbSoRGyH)5`wXVv!IDH!O!@aX6-CD!qpNH>p zyA633#O#kWv0ImJQ@Zu&Hlkaf&c%O>&*McT-G{m>bUV-;L$@Q{0dzaj?M1gU z-R^X|(CuoSyJpr_nTbwYKpdiB8r>drd)i7;>qcj>uwl11-2}RQ==L?^FA*q~b^Vld z`_b(`#9_Cx=^DBN=?kg(9c?Vf!&rF%JxsIbdgzivhOk5(gJdN&fx})fhpgYnG zlu8y*jq^v_C-OA32BABa?p!*3ej45JbSKfBKzHJ(F-pYxr{#wmx=yA$mF^TXs*^0$ z#DQg7y3^^-qC105J0#3+pw&5##<#QS&N0H&Ux?2d$#&<_T}gL7-6eDq^o2Gs6sry7 z7tvkZH(|wr8?ozt{*MIo7)}eHlIS5@&cg3Xeg^OU&T}5{--PLr?L6uI4V~tec;E-~<{WU*BL(j;t`(^Yf@U1=S+04C$9T=cG{8|)h= zZZ~v-O6Ta&WV)?l7=j|*`*feveL(jq-G_9a(0$~r&Bw~Y9{K!EcA9{uu-90G?!R=O znX4cX=&BJ%_XVAJ`z761_T+&dO}jOh)O}6&jaeI!tr3u!pzjzI9=>OAJlzir4yXH( z!D;D!q7$7z)BQ&G3*E0KWozBPERVhWo$e1SME#ct*8D>E7lTtVI2nVJo5Ur0L3D5m z2B(ylVvdUZ>cOcQbWstBOiZ5pRR*VHaCQc#XAlNwU~pyzXOzFn;7tD~qd`qTOk{9Y zBS1g-LW8pj43@kh{0z>);M@$($)NIomLJ`_R3Dlp92~}ApD{#4iWueV4vt`O83so( zxB!Er7#v`*U;kxrbl>8>b%^R*2IpaLeiQoOytcLD;C#B;aq#-DQMDk0OEI_*gNvE- zGq^B=i!ivT$xg9BMa5N@48q{z3@*XolE!l}N43kg$g~cQVQ?&idtbCC)xr+ zTW8$O;F>n-Q7<#NHiPRixDJEs8m{L0SO3G0*&Np3`V4Mhhmy~g_V1+dh`!asBN^O? z!QB|#n89rr+=Rg`8Qhe?E%YISo7vU`gPZF*u-tm4gT||^`2UzX3uwuTWL@JfAAZ_s zq;Z#S+@*1Mcbmc8T?c!(4Gx35yE6>#Fj#GOE8O27SQQdWSl397fNLCd5*;X+l4e-dXgHqIV*_qv;(-?-+W= zsz}LpU{iIL30JQ^!H7}0-SuCYJ?Wi9?{s=6(>smcDJB{@k)DnK_?wm2>T2YjLGMgc zf0a;bpdrKFJDc9c^u+uN=$%XNAN0;Ms3_7LhmMeH`^?X*dl5T`M1)$O|2RI*5>W5Fi&LMLGNyQchbAd?3?ypRv$IS z?A=4}UVR@u_F<#S*nU5~$LKvk?_qim(tF4#9IO0*<@e}4Lhn(tR1&nhBWKimoZgcT zoKF}?tj(wBss7)UWL&YBMbW)y>3u}+IeKr#4#*`Dn4-n;35r`%}Tc&dM1-1KwnKv*AsSHx1qtc-r&f z-*{7MVia#Gn;vxq7-|-8TD+O?ro)rT9gjD?N&Ml*@Mgrbsa1oAFeYw$ZaC)6f;X!n zM{v^Xge7lwypecw;0>7y2*B!yHwa%@l)8y&-dt8Z z=4s1QhY)XGyp{3B;4O_eAKs#P^W!aqw}8FTI=i4zr8yuS)#NRVXQzP=Es3|(fS=|%rOVzjc;oPv#T#q6XlrWF>~eU^TYRX}eI-e}74TNXTgl9e zew075p00woHr}duYv8Shx4KzB2}@9syu3B>*0Oa*iXq} zJz15}<5n}lNvw*~ybYz2cpKquY!#|T?{$a?5@@|$xFFV zZ+E;sECL^Pl@)!dki**xZ-2bK@%F>p$GG&wlz98VVdqePEcc%$kVyc6&=(tjTB zIK1P{x9Y#T3f|II6%2SM;hlUUr3sU%zN;@!n+smCA|Ca z9>=>M?;*Sg@E){j3%%l>2df3Vhw&c6d&HhK^?%gk6X&1Adj{_*W2|*Y zN3@&9Sxn+Ri}#$81{Mt}m*we~7hN6ozb*E&n_tFz7w;9kxA0!Yv$O`@Yk04lQ;^(s z)kL}X2A*E`rWkI@6tWk-*$DTN zoo%WBq5!21pFPrTpp z{;(NX`7ezo^prml{={a%6eReQ=nMX&_;&b<@laj1d#1qO9)C*wCGn@iAC5mY{w(;@ z;DbLc{&e+9|rC;CEBrD|ObcxuyrUlD&LOTVZc{U|H#uY$ie z{;K$^8HI|P{MGT-v{8V+#$Xg+gLHo_bAqaK^Vh*&*Y1@4Q41!E_3$^xUmt%H{0;Co zwoNkq4e?zGknVKTA7)kjP4PF=GFx*f;;oE^zXkp_uCrU>Z#A^RSbR$dnPM83WdFAt zI(K_BWrBzCWC#4?@pr^O5Pv89J@Lhg-S8z)i(Qpzx1H^+U%TV)F~O0o^qEu}e=q!f z@%P5x$3QMEliZCb`{C=q`#V`5U^c|;>b$OzzZ>zY5c3bhKNA07eBt2`{6pPB@4pY7 zVLMCUAC52o_)mMC`X!C_kHS9&|7Z)Pq;9gh{;~L~|8Zj(%@8S^@K3=l@|dP@-Rr zD8lhCFwq-Jr3)9~KZ}1c{-yZ(*CnQj`md6o){D#V@6wDO{^j^r;NOUUrK3|1UhBSG zjem_X#K{!@I`^YuZ}mlX#srP;&VR9AZo@<*tKg@0@Uh zf$(npdtA%+;y;dmAO6Gm_YZEsf583gL3e#<@M_o2BkuaByFT{U%}==7o^;oz-1TX9 zeMZ;8f_?nw@L$J&9{)8}Dd4|=|DwC+C3k(8C3jKi8k@&989 zSfN)@kpC9JDfn*_%!U6B{&)EA;`i|1!xtXj$Nvcb1ANv0yUqP1N{w;+k4=2iajhly zKgIt7|1195zIm`vq@GbFv{Dym5Bti5ez4oonYALagyB}vOEMs zW`9J9z;7*&AQ)-hN||&eg&tV=LnvAuaHMfhU7YTkkk2t>e!1REKD#1Od& zdCXuF0=LnS#=OC11JM&~ZUB%51zQpvNU#;b9t2wxY)7ySft$&dQYeOzR}Ho&*oj~V z!%wi|1O=2z20Ih%Mz9OPu0!uGD6e7kD34*?+}*l4%Xb8O66{B?7s1|6H$~4rW~&u= z2m9I>%D(JRaDY-b9A}6XuQbv$@=hQ)(N@jqHihEBNd!6q{;pA1KWNT3 zIF;Z-g3}1@BsiVmN`f;8E+#mW;2#8M5u9yrF|?dB=e?5Mxde8krm$@u&=7t;!9@fY znBxd8H1b@I!f-Bs5s2-V5nMuWsj))coaOH$H&w4MC%D3JB1CDgW|P-d1UD00O>iB- zH3ZjM)HKjOYe%1(%;g2w69@n|+BO;zy>8IXRDzJ^CW2cCZYQ{v;5MUKHwYN=M}c(b z4*RYK<%X2s3)J!F2<|3$gy0^6`w8wPxX)TsIH!+hVS)z;9wvCuf$*WBcb?P0O~%16 zZ{m2A;0c1q2p-oj7Sb!GQYJHalHe(`X3|+BTXy3af@g>3mBm}rVpLP}=LudTc!A($ zf)@#t`LUW4L!bfv6@pidR~oq8u#VzJg4YS&CipkO8xttB+x|oFCc#@qkCGB%g{*NP zalC6H(Bm?9!FvSn6MP^fXrj%2Ty=fBoxo1H6dQ31ug4 zN$`aLSng0GtVB6T*utI_8a3SXp!bu4JB%D}755kEwz8eVCG$@>u za7qzII2qyOgj%zs3H^E1DQ8KpBAkkFYQkyEX3DIoVE*0538y2Rop5?W2xlOi(MBbD zP&6q14`(8@u@fjiwNf?{EToO!ea^NCESp34B<+I^ARphI6vW{gbNTZD4n&@S-6mm z)5C=c7ZDn5kRp4l>e+BH!X-^L!o>-f5TY!bp_Es+lu1PM-SXGrGK9+!ic#YT$0~}J z7clP0&L|QK^{gHHMYzI*1h259F;TcO;aY^N5Ux(RD&cA_P}HVU8|q*t!Ziq;%aiEk zRju!96OJcbhj3kEyO^+YWVi~~Biw*+eS4j5Hk0de$l*qW`w?zTxI5t{gxe5qO1K5# zX0El(ZH#Hd%Vj&OUz9n8Mz1|`R2_rjeBcO~4J zaF=0^%s=drV{|nS74BxrBGogQ?m@UW;hu!b|Lef4NsX(wOZ58??(3G4jT0+W{z14u z;bDXa5FSV{56- z;TaY|DLj+VUH;r+#qeyxb8MbhdKaEc_z%MKOf=)R*Ol;m6ND%+*njOwBD{+5dcvy-ud#pG42U>zE#Y;9^3K|l=oXWU z0qh3C8wawpn8aGRnebnPw-DY=c&p=yj(~JKMeC}k-V#FuQQP^A@Xi6XMz(Bbcn{&r zg!d9YMR*_KqlEVpK0^2a;X{NE8gmB0rzUg5hi!B(1FP^`@l5y_p$-80gz$02(u7aw zy9EZi!8Ag$d!Fza!e=cS)CQHtqUS~|@w>oJ_yXZe!aU)NHd0oXg@4WChp!O6P53I| ze+XYAe8Yk8bs>b%#`5}N5vCzizr1C$F!C&NA6jq}zC-vS;k$(IsWquE;rk{jqtMOv znnn7E(AaKG$^3>^a!>fFb<=M9oJdOgU!v&=zaadU@Jqs&@GHWA@M}W1@=L43!k*30 zg%XEvXwgttG|1qGAz@_gOEl62!%sq35sLF>mK_Fin^_JE!qV)F$~#4ja;ITUsL_F| zzzkd0<&Lm#Hz?2c$QWG-zatXUWdDC9{DJTn!XF(fej@zYRPv#7Wav!#!rR>9(r<*n z8!>Y01{J;6_W!rWqlt(nwr8VB+;!5y@9z8LL{kw>K{TZ~VrObhe9_cI)0whF(-2K- z23#}L0<-oOQ%|EAh!!E5k!TbVM6(giL^QLFYcwmj)K)~Z804Z^jWe24j%FtsNi+x1 zaH1iiVOGztC4_q2%~P9UiALx;>MmEGBbyV=Nwgr*XreJhvj6iE%}q3qAzZ8E2AfjK zlE?#&9ZNJH(fmYiort94auGxe5iM-(i#fVY5|0)oT9#-rq9utICt5<*(zYp+q-4FL zrA(&L((Yfn@pC<*wTQ+Ot!ZYI_ZvdA=;Q|Gonq1HZ>tGe7V)=8ml))dx?uDf~a~e2Ny{SyAo|pv;)yL zMB5YT*ihT|l4v`NWR&uiwklePb|kVrpNV!N+S#DxW<0IDT!nMdZbW+#?M}3(!^0k? z^-_T&ZZ=Jf_9oiLM*UjJ9_>rCKhb^$a#14RDvgT{Fp=38f$Dgo1Bs3xI*8~{qJxPJ zv0kjThlMU0l|+XT9j;kU^Vcf8SC%O{lIR$sqlk{SmnxK+`}~nhK16gZ(Q)QJ)l&?F2I*6+Yu3%vi7bR0T1enO)2NlmG;184O>`cSI^}l$ zQ5S?#(;5+fq5C4%*}Ry|U&uTvQDFbQm{_1vv)>S1Li7aDr9^iST}C9{UQTp1(G^54 z|8LA6%mPp3q;?IFOZY_B5?v=W8Xz@8EvI0jzk%pxq8o{B(w`&#-!l4`DT{6)x{c^o z-E0FTQ*f1lO5*>ro@&}o_At7W=mDa;i0&o2o9G_1dm8Xc;&M09eMI+LtZ2c!XRGI; z2Z@|Jg)uZjLm^fu8O*qUy2?LS0s z8u9Y@GLM7Rh0!}i?^?l&E26I_bki)uRYX1FsfZ-#ABcRSf+!%0i9({t`X~z{ zjg!ZT5~5USv`MZgvlNplrd5GD`%@BiL={m>R2z%zzs{g-@R8Mxn*=3@Zw-(-el+^d z>DTv~&9TIS=5(STiT)(|iRd??pNW34JLP|)UyY4UD~Wz5`eVTP3H~LXh7#J*s40XZ;wgxyG?LU2edgVXcxvJi#M2PZN<1y`OvKaKC^(*;7~&a- zXSDWZZS)bhr7Y5gcxK{R%*ZIJly1t7#j_C)5zkIM$I!!C#p!m}7oX!{#KY~48b2z% zAUhL}B%YUe6!Bcda}tj>N{0S6a?xwen#FSy&tq*WL>It>;dl)3!o>3tFGM`Q*~WMQ z;sv!ktD!|U`Ie;(Gm6CGMTlMgXF!;Q9xqOOA@LH#>gkfi8xk)?ydv?^#AAtmH}4>*#2XQBNxU)f zX2hEiZ))U;jDc~BHz(dgHhILnww<_pqvn6NC*GQP8%5D(fn%kBKCvN2yq(Ez*fzvF z5btPcS46F=)Ggkb_&DNShz};-m3Uv`-H7*cqTHQ$4`P+H{h!^y;=PSUCg^<(b+WGU ze#9!XnU;8eVs`|9I;a5H*!Xwi1Bnl^pylDmMn&H%r$KxO@lnKw5-ZyOmiRDY3FwGH zYlaXtD>x}AjgKZi#_~&AFd{=5A8V+4@FwEpiO(cHf%s(N6Nyi963-P}!w2p5jzK!^HAw(l|*=K{w zV1-nCC-GeaO(edX_&(x$i0?I9B)4Unbp!GJwhz^qt*GJv;F?W63U7v8*C*Aca;-}r0XLPlXXb_f^*@&MfmMgKbo4NA%MdFu;U$*#P z7DFGE^Tn?ce@grs@q5It6aR-;OmO=@D`Tc-O?O1eTSl`?v{Fy;+r+}~yO#R9;c)vH z-`*$wi1-8I4=o;$r;w>JG=5C1M5|@R4RvyA@n^(7@#n-}5&xI?3mXZ_q!{>h3V?P( zSep22VvqQn(Tl6)o{c+1iD0ZojGllvBMynp+7m~_@f;_PB2IK2x_lbD&atK~K#p_b z!t~{tKV`rj2rIJlh-=dE#0|;y#4X7h#2v|S;y%gb#L|L4iN7QMnfQC+pVSxPABcY( zB)9DRjuAl<|kQ*WC4-|hp)1v zm_K3L=VW1$MeGs9jiO56NfsknmSk~~rAU?_afg4&QwWVl$WE z?MTF&O-R-uS&u|r7;pJuVO}5Q8#UxiT-rI=fJAKHNKKiDRd1=tQzuBupd})2YjbeP zW+a=FY~g^h3(1xwTUks#2yEo`1aR$xe2J%>+6% zuOVLTDwvIWb1_{>b|=}3WDl#lN%pjYG|knD9##98boL?HpJZQ>{mjZ~3nL}{qa-f%i78BN+2iyBDup#%hW*`D|^vhB=?fsO>)l! z{77V~1W4{9d5GkGch3VP585Pxj(OF8m46U(3?Yw@JVWv*$>U~flE}A@yg`&Hpz!1deOThdb2y7de3Zf@;=E2S|&E)zo*!t z=Z4;oNIq5&rFKRy_^ZG`@+nC`@)^ljB%hOL^4HDyBwsi*erc{*956h8P2!V$<0$Wu zc>m*AO{PMEAu1$ENFtKhe7zQWNHLAVl%ycZY+Z2T>VMW$vDVxqrCDhcik6JI>F=Z= zot>m5otUH}`GurU@*~N&B;QQ{&&tw>@5Nh^ABO&Om;3S)$? z$?q0#h(eiP1xU%Cq!WoY+bBSmR%SDugmgO6NlB+5os4vH!-=e)v{L$&PDwhIRl3S+ zq*L3(eL4;4wDuwu_ezfHT{=A}q%)9eN4Rm~>Zrq~bkEL2IveTCQU}smNL8(BeVkx| zq>0w%9Hfhp4w24FI*fEq(&40|NJkiXD#S`hn&niHm}=NGC!UTboriQTV_NFcZUfUH z96o+E=@`<5NarJ+-{=>{Qn!Cgx}Z5VeKg`r7bacQJ-)~U;mR5qC5w};M7jj&IMO9a zmo;FgOOY;3y3Al+&rFlusUyI&)LNVMjVE1>bVY+)y1b$$(iH~Lqe)+QNLMCZjdT^# zRrR2m9es=&tCOxvx(4Z5q)K2~#gUyHm#$5^&d`yET0CYk-n^D$x5n2a%}CcLy@7NC z(nColh+RoHBHfyFW6~{1HzC#huQCaW|KB9tjC6C`HDb1H%&n(elG@3?1`_c~*h{w| z-JWz?((UXK&1i^1Atc>_bQjVc9j|sG-Pu-@n+j++xs7pE>t)j2NcSV%opdkKJxJY> z76n2|Gg=FKlkTH%!ZL&!AEo=6#I>1QxcndxDq=aNbmCzBpQdMxRYq(>R{)U2YB^k@rQQ@8W8N^8>NNKYa)`%iiT>4|2^ z#mPZfqC|@E^AysvNKYj_ZGe-Zn??5Nq-T(xX(77aKIhWz&a+9+v3p!F;G};Z>1Cv1 zsc6$vwvvVP0@4dfFS1x#Ikbl#GiK^3(~e6>FSQ=&&k3Trob)=V8y~>nM zLN~--W6usAU;Sh~MJm8tFa5O%J~bj6nBGYG3h7OxkCNU@dLQX6q<53vO6rn1>1|eK zncl9eh1uyH5+CWEq<0zSrFxR4tY3N$>AgCEYl0D0HG6tL=|gCH;q*aa)lRS00NLL7 zFzF-qT^U-{nl2&SK1TX1>EonNkv>8Cq@{wTq-uYjyGRA*XKaO_n6D^H0ZIBC>5HV# zlfGcTC?MAeS0kGAB~r!zZkJkjC4H6jQ_|N+-z0sV^bJclrEWoi$y7^#?=Vhk)-rvI z^aIkjN#8g3l)gjyF6n!wt#VkaOgn04o%f{0q{7UHq#v1f$OyD{gL!1lcw+^7pYh@{e|=o(qG*@zmfiKb5%~_<}S@L{7E(u*~H4cnU6mC zC|gjFO-eRB*<@tXkWEfDHJRK0A)AtHDuagxM8dFyD?g&?o@`pO>Fm7{rASg#oXtQs zE7^=>ZpZ!??kAhc_>j#^HjDMb^6(bnlg&mpyG7K}DaC|Wze6@ewg}lUvN2@C$>t;* zK{krawt%+jd$CdWL@3Helgar1-6jFGA~lY<@D^{8KcH zUTzm#&6F)fwy<2@oWDF}9A2{}Ta;{Bvc<@jB3qnn3Cl+BvQ^2(ldVR!HreWA zYmK`2Mlx4mkPTL5O=kR&+_QDa)-{tQsR>;YS+*Y8hGgrLZD16teJ2sJjmS1O-b(rs zgdA2TL2OU98QGR(o0Dx}MpAs9(DGJf+euH!wkF$#Y+K`lQ#$*iMxHLob|Bk}Y)7(P z$!z@>+0JCD|9{h@ptmb7%>;lw$#y5(!#R}6%&#erq5R>hSCQ>awvQ>PJi_h2k?m_O zYmAWXPj(#H0c3}gNlXWksqq7)k~XO)>oM*vvV+MEvA#%fx(d(PVPr>>=p;en~fVXR!W@8DrUElJxF#t*?naH zBD>o?eh1l|WOtc+(#TyODX%HxJ&ui91#HDjWK!D)tnU||GkT%NB?7XC$Q~tonCua^ z5A8XY1T)GXBYT4EaqH1Q3&ecm)l+02l8L-G$YfYvA$ykWMF;HX$OMBIY*tZl5<;|x zq{`H@m&jfo8h?U`Z@fhID%oo?IF{*>JTxkAagP6n_4`(HEMm#@@p+(L&pV(*CWk>qob4<{cYALeY-&c;0#|B#QcmN%V*d=&Xy zR&y>s86c$<@eLS6DuJxi`qy(2L9|8_(C$27Tmf zldoe^5NP#L{jd}&A5XrXQWNBE*`}ngh$Y{Ud=v7G$Tv1SsVZ5q=1tw>{@jZEVDhcW4`^{<=V*g*8Va&FPyn zI+lD_^4;wFU<*FsGv9-JFLF%;yL}F&l6KqP6CN=z>`T5M`ToXvF;>(ng38segKVIh z|9xovAIT5Y)duF$Q)LhGL&%RHKa|`ym9nRdMTZZqI=#S8ekA!((zW4hO>46!_AjyS zd~&__O!DK%Pa{8`{8VyD=OpqIM||+X&;v{AN`5l=DJm-+_433c=Kgi$(+`=>8kkRa ztae90UTSlTr{8sWi$!{aS-AtBd8%=qIZL@y%tkHiL`MuoP3J>W%8%VpC^BY{5kSxhYoq(A{m)k1yRP67sy|-@pAs+gw~|;%9Z7>kiSL# zD!I+-lD|g&`v0N)4e~c_=$dP(lshfUb&C9L^7qJ1j~oH63RG;=9tf)X%O&)j{6q3D z$v-0hl>B4zPb~fvj8!V2;XwWw`R8WYCLqn$){&bFa9_S6_sPE||HeGEG7ovrV$s|) zbDfXq9ieena z)D-hkOhYk@Vp@t>D5j%;VtR@h%qyv0qL|TMr>0bIP|RfFC}tkL+|N=IT`6Xzn9cM; ztd^P=vs27LF=UjOK>uj~D~40dMKOY6q_rtt6{E}!7jsfL`!BZWe&N3m@$(p{R41T3 zLoqMK7~7vmPpP`Qn4e;CiUlYZp;(Y&VG6l`zYXgD^^{4iSd?NhQ8Mh24~zpEG8aow zEJLv*#ZnWrSOI^bQy^`dK8j^2#v1t5j9Q@H*AFDwlVM(utSms0FcaU#V56o*qtdWG{un=N7*A|C$T<`6@ZVSuv1`$t`fG7bI>}T;sm375a=2WCsC+7 zCsUkBaSFw$HfGVjdBtg_%f;ywuKq`pzpf(`XHlGO5rUePt&j#4=TcliaUO*mD@mh^ z^R1DA6)G;IxP(H7e~DKYQ&=sBflAsWQe`s@u@dy{6qi$6M{xzk)f87!TxI(dNg@uM z6xUE(>%{j@8+02V1pZqnuBW)!O4ExQC~l;<$qDfV#!XA~b&eCo`|M-(4Z*aWo5GbFpv+L8J>#g`Or3pmr$FU+uv za;qNXH;b<+N{VkNe2N~0^QSTb>b2b*P^1(gMM4o##EJ=xJY@m}q2b=hF(N*5T+Jy8 z1IDnc?ClEpi;6;=tSM|Uiy_K5)>3r#U7@&UE{bm{zIPz}Zi3+Ko*yV=|J7?t0a5(y zuD?+HLE-9u?8o0Ie%J5zvCvn){7LC5&B}@Gm%)$aq?EH#PDVK$<>Zu8Q%*rSrLbW{ zNnt)IHlRD= z6Ux~rM^VmBIYg<&U)qmCxo-VFjB=!f9OZDz5qjzHwO_T=*_Fo_hs!xB=cd$u=bB@i z?*_q)fJZqG<-EovbwrAG@O_l?QEoyxKjjLP3s5dexgh0YlnYTVY#9uti_1mKdY6mZ z2_|ClKD$sZPPv4QtOYjZ-ZjZmE=9R4<kA^(fb-w90?V zb#xUoZBLSNyzLz%FxxM>c>~G~ZB9_u&;H!Vb!1~*ZNyq`O1UTHW|TWnZce!^H}zP$*JvMY%Pl%j6GyfziAjrILiUV~_Pqx>oK;xf|t9lsgYFK)H*R)fv+)abcFT z+?{d{dt{Jbk_#^PqCAjtZ_52B_o3X^e7f>)%kO15!*YL07s;0gP-@KLY?stf)??M@ zC=a4Mlv4dY#IUMr4OR8X@0Sw9;bzdJ%cYh8&r5lv&O;x){Ka-EdU-TIiXq3)1(e6q zjH5h`>OIQism4&AK=~o%iIlffoQ=UqBnq~WMcIPE2$5Ng_c{b&l zDkr8q%euL`R?WxO3pvE{T*~t)&!cqbzYHv%#);(xlowirAp28ZBqUhyQ(ioJc?;$# zFQL4e@>0qxC@-VD+#&&)Tv<`YOXZc6ZpucAZ@WK93$CHOiSk;?>&$>yuSJ*;d%Y{` zebcVe{qja*pa$3F&6Jh}GPAGLTzMEZsqGuCkHxBg$VX zKc*}w#pexe+DI`DZiln()cfYs%FOM7bH^3Zzu!T#~!7A^i7mD zE^{$b8B#{}-Ov(KCgv7(gBmg2&nTVRDt{tztFWysDZi(zC|k;!(q;aSoY~%M0_rIH zrX7ks%5O)#yffwhG5+Nbls{AcNcod-Ri;n8lB5hUzZf?Z!~dlTb}+oEMS$*e{b)O=VrErl6YAm_OiNW$S;3ZTzR^^{A$$f@(Ud z8SRbL^cJXBn%s1Uq06{w^>j58)y$T+x6SC*w`zoHR;uAtvr!FE%}zCk$x(yo=U1A* zqH36lTT-h=P|Zm-lFEXAW2xFy-k=&yH80g%2AFDYs(CCQD+4PG%fLz_WIU_+sE($Z zpK3#@1*r7Yf>cXTiR?wG7N#;gX0A$;Svo$WT8wIObi!g)pAs;Q7uok64eS+E86%%>{X6b60cUKT6N^V&!Sqzz%QF7 zFlgtTYIUl0sn(!c({i@Lc4eDCP_0e1&Oi$GUTJqVo@xWC^$ZfytjaB(-S#)rO69Do zji~ma+L&rPs!gc2pxTs5!T%j*>Z{F-aMHYsf3rRwFh7EJN2 zl)c)XY8R>(fzWMv1MQ%v;z0Bm}*z5-AqS}|GNttRC@?@RC~JXUhe9)0CjeQ zYG0~DsP>~0@%vL9U=RgkPg;$LZne0N_7O)VN`}{#{oBw zSsh7rl#Rq?^hBPq#6Rhx`9fXbtBbHI>LBp=UqkW$azG=EmU_>-AW}?+(vb~^+H`+ z(Wc(0?yv?2cB#6{iW4Si`aM(+Q{7ATfCI^WRQKC^MTv1$>wl#?4_Q;SQjIrhs+?VI5o>ydLavW>R=c!(#dV}gE zs+Ub=%z(T?^%~Wyqwd;4kLXJEI+Z%IhOKl~f!WczSxTWS(7zqQZ&9i5Z`;(T#>h&E zRPPQwwwWap9@~uSeQH7L18QOHL#jWhqxF5B-z{QbE+?>{yTgl zEg$IFRm*>>F9)-?R9{nhwogm-jU}-wm4L?Tn(K&96;RoXfD_yuj^|RAj4Gi@6{uLi zEUhufgjcZjS-gnkFlbjk$QG& zsAr*`iCSe>&VU^Gz9FifmD=|Iw5c?GRIO3ZK|P#$hSd^xr(TwNtR;Sx6spHr zJZ!Ji60m=pn%65(YyLMif?aVb^~%(%Qmb0mh zqh6bO1L}3CwSenQ>UF8dQ>*gd)&dykrFY_W!#3rryMQp|O(U3f-XG zxJDNB=G0qJZ$Z7KnS#Ny#)_?}HL(3-0&nd_+fnaDy*>3#)H_h`XuOii+INIurq*XS zYBz^o?`pBupdw3dtKOY@59&RYQ6Krp-GjV3_1@I`QSU>&ui?j~g+)L0{?y{q0hS}J zWhLDHIa*_GauH_^qJEM3U~0+w5bCq352Zei`Y`GvsSl@i>+;l~J|@SbsNMN5_Uthx zHS5~3)|%9_KA!qy>Jz9>lAEJG(Kw)8Sp+Bfg8CF{T~DPx-ArzMny!}6P?wcnuFrHF zKFj90^jS%4Jcs%U>T{{Z`SYkR7}`gV|AYE`lZ!(s^@Y?IQ(t6LMJ~rXDomUdE}_2k zFPbm2Li*Yce>XZ+!(U%XeGTP8hx&c$cd6eqo=6dNHHdyd z{o&{pMi_5(zl>r1G4(gppHP2J{VDZl#)rZFD#kaF^o4anF3G^~CG}U-Uu(c?A`^-< z@~wL`Q&5YUzLrba?<#Mo1L~0ad+La~vSWqom^yJ^Qrmghe#zW5r!M~brL-Bq3IDCB z8+Swd*Dsy>vrql4`|{o3cO550{R8zM)IYl4e;WKw{j>Y?7wTXC`sFw3-v{5_Q-9hG z%|sLa+f3~KoP=i5zkZpFX7a&zyT6%|W-1wc>!V6Cgr8;_nlUuf(#%XV9nFk1)6+QK zN?1aOG`)ewlK;kYaZ*xfW}%sbW>%WnY^>o{DAUYtTVBh_HbXSS%z`QIYKEJjn-OLc z8chM{`zXPQW=?k)h@-kFM@!)%dv@+GakQ#cAfJS%hW*ngvZsB{*wvA)1Bl zPkkioW>Fef#VR`Wi^7#=37TbSmZVvlW+|gzldy{0W!2@2n`LR-&Oh=jvge9do8@RP zqFJ6MqgjFG0-6llBeW%6k94$XL)jcC@R*^p-a(JQQFiq>pk;*iQ)(m*t9OtXo(PSsl)^Eaj0 zjAnC{jg5-dm){?`nB==9%?>nM(QHezHO)3w`lVP`&f?kcX||(r&e!H}RgnC5njL9& zq1lOM=aDb`W>!KB)Cj2Am1Yk)KAPP$TST+FnC4`(r{?t>F?$&$CbfNNj-%O^=3tur zXw>HZGzXa9k(prwlM- zR|O%>X;$Z8U(TR8QzpXV8BroD*PKmr9?dy4=MG(~9V4~<_(BpE%|B>t|9Kk;>gK1# z*b8Z%q7fMGpt+dlS{ilja+*tME;Zs!zL$xIHp_Ox=QLN)TtjoE6PXx$wN3si&2`$2 zR&}ooym96_n%ihZhFkw-oVkJKW||vmZcHl;Pig*3^BK+OHe+im$EE3iqxpj7Ynm^e4u17N z?=l-Dr_jj$hcq&V0gZ3_tGE%3xeU#JH4#m0JK+eT2BAbNvT3YY^_nK99ZOTt&P-F% z{6Y12t4)In@am}S6);zP?p|or=^{qb~@8Iv-!5Zl6D3&O7;cXnQZCMy8j(HMzw!` zqn(9zUfNk{N7BwlJB)UA+BrrnthM(4lxLwG8v1Cg%~-X=X-Am#69;5-WGLEEv~$tU zNjrMP<&}nD27761J2$O6|4RYSV!v4>MmvUfG1~cP7o?q^Ry)6KW8sS0kq>GYqFvai zm6{kyi`aO)UDR4zV_Dk8X_um1f_6z86llml7~Hi>(=IdY(Y;luW~rOjO1vyB-;Sf* zgI4x`eOl4KI_(Oy;>?P)D@k6~tUxZkYgeIN)u5$G3LS~ku4XH1W|mOapk14GP1?1b zE^q13brtP8wCkE04i40A$J4I&KM17VfOcEj4QaQc-H3KG+KnxKYd4{_tqF#nT*#_Z zgwl3%!&|!rt>S;B7yg>-X}6}ei7gl5SayqcJKCLT#SQU)2OHYUfV4XrqaBWO>gJ(Bh~+M{TX zHpvR)hSFmkR7^E&SN8UJ+7q;Ul9mLTAe4=4Poh1G_GH>KX-}a&o%U4P)Ba}|;t+Di z&@!)@8`X87aqZc(|F8-A_8i)CY0uNTMXh1fb;Kg~(z=qK_5#`qhp)MYnT`{OTRhWV zOeZd>r(e@vLi-r)rL?!ws-1t*UQT-r?G?0F(`x@uA;hiFk(z724pnN&zqHrVD*nGp zA{f2Gp|0=O)80gT1MQ9GaRkEkzI4y3YpO1=ET0X5HBxSGqrHdrcG^2>1&lkak54UP zL502RF50^Vn9(cTYy)J?^|b291GM+i-fw$uIHMt(L;E1@L&gUc@wN}UUml@-)GB#& zmZj=Qb(Tc?IPF`sPtZP3`y}l%v`^7KZAzy=LPo~K_blymrgyT3vN;BT7ieFjeUbK6 z+Ls)nUZ#D;VgPw$Nl;gjVb8u!`|r^!yeml1zTr?LWWH&(Mdj=2m-4sm+q9q3ifJFw zzDxU|)9&|Z-=}p&`-428Zq|r?hCgUOrv1d;C1Qk6(~-|;zo7k`)|GZBeWZ`!?MvFP ztS|EW;)Bui4c+9lJ=#94ZYXGd+L$(=4J~&e!?l>85*g~uyd<!0q$zUE?XR@I)9Qo*=agjz6;Wv1)c#2~3Ef0=6T2lpitEfW&`nA=nN?6L zH|R=+=%%0>MK>kgEOb-R%|JIb-85zpWSXR~-L!Pm(M>;Mfn!E2u-4#8HzS=+v%1ze zCL7z$L^reDuZeOgX*Vm~9CWkM&2Bb9>0cpPnCyn=hDmXZX(F;4PB(&Xq%qU{l(N2W z(#=V?2;FG91?lFZo0raQ0WB-k&11b#VBC$No8O9ryZNlYlDk6sZh@gwHg_#7M7OX- zILeBPBzd}SQMx7Q7Nc9-xUF}|@(Z8cl5|Vy5u-;#%Wi49WwgFy^ol3gDvxd~ofLc= z-JW#I(QQVzJl#5UE6}Y>w<6t2X7rRKk^R3yjjuvytAN!S-D-3qcy+op)V@tGs&}eZ z>eixL+jt^i=woEBOSeAVc)Im$-GL^21rOul26P)+Y}Rc^x6vR+G3tUB-M=<*O>Js? zQ}EDjPPZf77Ia(FZArJ4wIH=x_6*y0wcCbn2OF4o+tO`Ex4otHOlRFne9dM`4D5JyA)OWfnCJz51+krg^c3ACbdv9- zbb8}ubXU<`PIrZIOb|76T{+^byDewcT}|gEn!9W0uBEe6AkC`lPK8zS#qz@44Rrsa zyOHizx|__TbT`x8Vsn(bM^`Nv)Ig)V%}ix?yRlAItJC*8tqY=7>G7)`p}U(-*L&#h zr@NQ#K6}gH2D1ha&^>6#93*4Ahv*(2I@y-`4kfz(QMxDS9;18QBqb(Dp^RxyS_>2G z$D&4bmFVQ_m_oBK?_q;i{?uDUK3fsZe0h{h+x>sx@qKO`5cvOMky+-#2 z-RpFg@UapIc|)x_@BTxl$8FA~`AJBbhx;#ia znI-v{PP_f<6`#<3O81%Ur@d4hknGeQYxxVh9^IF8Um3=(+TNU0_ch%&CJ{;8?$O7$ zB4pJ<>+NPYLcWtYBEDUoE~bAGT|!?Eru3(!llXq3%jr70g07}3=_;E()+@wqWud!< zuC-oh!HBLVi$2}=bl=i_=U9E8-Kip(?gzRb&6>$@Dey2&{F%Pg`4_rB>3*ftFTdG; zb(BE&`>4xz)feld^twM0{fSKr)Rgw5eS8l3lhU7x{$%v0wCTD2AJOfLGRtCM^1!(BG2&rt~*AQ{2}qm_eGp zwtzcl#IwH+f_Vvo{?_z&puY|M?X3u*zb*akOr|>b)(no0tJ{PAj`Vl37NimCiF-Y8j=U5s>D&CTsa}7tfgz&55B+`38W>LYqkjPX{jIZ_vXrgp zYs~mpRM0<={t@&KqJJp;gKY%eKV-tNS6-}tn8T9Wn^$l(v>ZwQX!=J9z4mqmqWazN za4h|^=^sb`WctU`KgrBR{{;Fc+I*rY(fVca+>m(+{nP26>RLO^DA$NgS0n5U`e)KV zYcL0`_N|Jhe-8cg>7PsgJe6VCOJxoE&h)8@* zGyO~HUrYZo`d8AwocbdJd}(yh0(u-{+;x1rGLBYlvRO>PWu0%e}~ND;s=Y z^_?%$ozcy;et=f<{TF5rT0heGq;&~eKi1HQ;T%h)@TX{9hSsHM{Q|9ASN1ttxsrK- zg36K_q&DG8w3>98hMnBYK=bkqyp7QMEm}*oeuvh-(E2@ESD^I=v@SO&6V#v3`t$!Q zEe7{r*aBLARm-W^{f5>*jGAaxfhwRAL#->(`lkjG#8D~yH(EoqQk7cK`mfP~|BN>` z`PEjBFQU~)YoIMqPdOp3dKaNJLu;(@OlyMHwC)W#7S_#HYU*>e7Ahy~MeA4a)`n-n zTLYdA&(W+t6J=aF<<;Q1@XYxS?1C35;H?R7Eq<0%NzIGe=e2>i9=vtnt*f?n&EAtV&HX#*V1v~5v;=!9*1e~s_bzL=yiqH170_H-IeAvn+uzi%id=2 zdMYktF)hEsxdps!;Pryn2i}(Odh=n^mh+4!?FVlwcw4K?&%QxrLp$9TUSD|I!P{PS zjh`1}=HKWYJl5~8zPla|cmv>(yaVA)hc^h`N$>{4+Z)~xcss)z3U3s=VeoYI1-#)J z1$iSBe!i}&DG1w+@XX3j8ZnXXN+QterOr-j(o9hIbykQ{bHr?^Jj^1Y@*nN5AcK#@0In-nsB5!#fM! znS@#G3^l?#8{Rnz4cR9Q()C(|G&XX3nG0mgk5yz|HT?>!sU!(F*B~o6KGS|VIsz$@R9-f|qt=D7Y z;oZo=2)vtAPM@oRMO8=vY7>-q~@l#Vvy< zHh00Jc-#$dHoSY_Jq_<(c#puF0q+5L_h|&^-LLqr;>CmT9#(z#9#TmlsFY#GsNSRS zo=`{NFp^x@v!g~(hGw_~O?vV0a_{X_I?|FE$G#ur4 z_O@2)CcHWD-h%f6yqDn3h4&&~?;LoffUY3*=D}M4?`3$e!kZ8875#|S(wMw%JZG<# zK_|d_jRZ6|uN(R-)NGOh@P;YhWN9FO8{Q&#@4z#oPNKt$p3Zv@-UskZ{$CMUtQ_a( zUrBJ0C-6R1jF{>2ma4$7q8NqbOEt3|_XY5N8B5J!t>$%107oYF9N-wE48R zYJmfIQR4`ji?ANlDFqbH1YQm=g_o&1^nXu}@6hK3yi)VeY?e&q$keys?+)LFzXf~; z{>JcY@Yjd$!fyk=0Y10INQP3) z!K}Xl{0-p)zUXWVMUhhjWRtJsdiZVCU;FJ8lDc;I?cr|@H@b7f!`5+GyEp_ zof>a?LWlM&slR?_@~*=<$}E)-m0otIX*!|?zbpJ6@Vmk9t}Ldtr{C8Ko5Am?bvSsW zaq~A@V6$AyobLn{Ou@s8pG3KaGjXn z7ycml{ooH&GwAn+zXSXM>Oq*lr6nh9TqDF_*dGjkh`Lw8%!mCBgg*@aX!yh7kAgn} zKEDD6PS)sb(WEEf?+9NbfA~8o3gmb_DD?@?uJCtJ6U`@z$XNKhDX9Oy1@QNPe-8XT z;U5QoFZhSS-y1%A+z0-6`1``&k65rx`1`{jr|@v-R5yc^cK-lvnHJ2S0RJG>7g9`_ zbufGi%%N)D8BMW%6X8D`K52dg{3AR3`IK^#6lQGc9}WK)l~S&t_RZm+43GU|SM8qj zkB5IM{1f0$g3lh!iWD=AuHy^;Wca60H8dXM##NeD|1|iM;hzp)6G!H+9aM|`GvS{N z|13qH0;aO0r_RFXU)RGw5B}xw&xd~r{0rb;44+2;Q4%->Y^07kH)6_(82?iEy7`N9 zNvhNe>J{*>hJPjetC(}s_9^o!y;I;{LsA=gxR%6e8T2819w9BX@y+AAA%4^Cvk%6?I{!^+EJlBE=H=n&f1OHiNG=Vhh`rtngf0jy1eMO>Dat{1g z;lBX?W%yjl!^U5PKM(#(iUNaYDgx1={4inV&o?A`Wi_o{gTH`{H$U17{_DmjEY#(Z z6m{ ze*^zN_}{|+4gPoVe}Vr!{2v<6_?H7o_&>t`8U9ZiGZ0@=Oc}9UEgn6u|110zs`wN( z3WLfssqrWLKj5!aA2AqBuYPST+h6ei))qRQsLP@Ke`vel|I3n}OMp+dX7DLyA$%Wx zpuFJOcN9*Jt^5dn3g7Ji^35l}Od7EB%w08}^m7C@d^S>2X!R8}9Tft_PA__*z(G(` zAk87Mnk)($5cEN?27(R<)a)BfbHb-DSNF8i}pceu&^Gl~svh-U_3aFw5TOk;QU~2>e5p08?KZ0!$5CBa9 zA=n;4U!x1BtHhEK{0a=i_+SSF1JpR{Xj)Th2nHb-j$kl?A*#Y!XK0;k2!?5o3g-v} zW+s9JB=FpE7VL;%EP|a7j6tw7g3(GMT6jhdjC_J!5bUZ^5M#5xeOm@^iC{Mbdm`8! z!5%7O{Pqf!jvgx53&Gy1h6IKIK(LQt^u7un1t8cT!Q%+VAvg!Ycmxv>9Dv|(1P3BG z6u|@phalkh-|r@Z2+R}?Skkl7H1(a`G0m)_te3;2yR1g9)hb8oR8o#1Q#H<2*HIqG$19J2B9$tE=F(( zf=f03z}%+ELLj&t!4*0u$i|sU4X)%30&@xwlW75IcmslK5M0kmd<54bV2rWqTW`vD zW*&m62z2w8f%YZ@w;&)X%=aH}`mA}W#f}g#gjlue6~XNY9zZY+!3+dMoZtB+2nJzXO=gQggPj#CiaTR)juw=e|vA-JE+Wg3Bzr=bSz|04)!{~uPsNF_S8Ge;wM z6oDqqI8&gN=MeHI5WI)rNd)r{JcVEu0%G(mf|&@OQ3{jSr(Mm)5j=y+x=2f^D2-cf%?kT|Iwn1x}%A|9d1b`X#tKO$I+;4=gt zAov6U#fizE89L4VKT7oy1RoRfRsJIQ6anr3NxC?eVxSWBIf8Exe1YI=1j`J}Un2O5 zLjV;{9@@hIjM~ItEch0|4+yvfv_*rQ;CmGtT?adbih$rJ1S=8zjDYl6j$j3XUkpcn zRYtTm`}IgDZRd9ce`qX3t)v*JV*ZJMlKdBfzqN5bH}psFFM|JC1|OmdOWvuK_7L8W zz(+{q5Fl(u5F%U)L4?pk5F;oMBnbHZ-#;67*cCyh$THXqa_x)zTLVgUMOUTVX%CwacG80{7)&zo;Fvn>jBp!-EeLxc+yr4)gk2DBs`)FfV$sH@ zi{ufX^b_2>T%1Quo1yy)`?*byken!>v?|=`2EKfi}}K zMZ;|o4nepb!W|H9kFc-eNT}$@LKAw0{Z(K%stN}n9H=8K@|{6iI0)fj?UYhPPaO_L zI1=G7gu}IE=89&7F*LN9Xge3BQ2=_v0>_6>*5@8>;{q@@MBfJgaooZmi+YwGfsN*cQ ztmtqS^48A~PB$IhRX3Oj$-(;!sP`hA(K2{Bany3uZ>-3NBYYU) za|jUD$*wHGcKC2ds`-$eKh!nY810ur5?tJXc!Yj8z4*(*$6X)CBhtGp)oLRn$b9f6(UQ2a=1YS zBo>i_r~y$8k&$rHysx7>e4{lGt%GPyL~H5krUWTk8&MnOFJ*~}Pn#Su|Fb@#^$>CW z{6#9~ytw`?0-_Fx1W{W=8zS0BZ6*OA^|XFFL>u!)5hop@_7pZmjVf|eMi&)B02@p zv5466afpsr3Fp5I45AYdnNOhdb?ya=CLuamg@I&OksvEiMRW$D(-7(Ue_bTTxQNNs zXfmQR)t^%QNI;Ikqq7lRgXkPYmmxYA(FKUkLv%iEqvq>4%BEgN7b3b8(M5a<(Zz@^ zsmB-`;3%)@gA}C85nYAo3Pe|`{+6>)Ujh5HEVDXd0rs5#534E~AWhBAUMH z6~h!$hv40-C=&)YrIL9cqK^^XkLU$N4D`V9ryo%_xmLbO~dC5C^qUd!*ix4eD z^oBlRBoMu6%C{6GW!&3{-Z3}aU+Jh$=|e*G0V1A8dnBU8>LYZfW4vMFhloBxw4{z; z%MkshIU>sWazvjZ`U=rfL|-8K4AJK;L*|pU(JF2r-1>-M%IuOy{3_(dQME@fCPi-cR9wlFGFA+%)`G{gff$1eg6m@ty zRdeg&0E$z*7UFdfuZ_5k8bitew>%KjcwNNn=}rXV!q@f528f#x1M$X) z1@VTO9Ao4Yb1TP>h}$A=*SPP=hU`4ga6aPph&w7HgS{6UffA% zj`XH8;%){9o;gg?E#3xkf5h7&?uU3g#O8a8qaH-uS4|lY z=NYqh%h2{p^LPiu19X_9v>K>V#8DVkARdf(6yhO>ha(<}c$fwoBmZrFQMVH!9)VaF z&MDY5xzy`;N5s1z-U;y-#CilY;?de#-S{vu&d4X;6)_F|SOs3Y*o~#SIZ}Z3D&7*;h%ZNcKH^IeUw~MX|N1S|_#*We@x_QQVNO5| z8ZYK{;>#2lo^`()MS{YAHR3A~U!@(9G8}sybRe~CwWT0hR5+!n#tkFUA^9kh@V0HJmO~&Kc^fasm%dsh-V?5O_6B6 zl^QXJN{aXe#B)_)i7caKh5RMN3lYyl{0ici5zp5ZWn>vqo=4igiuiTJuNf&_V3=t5 z-v2#4(;$8W@jHm$MErK+?LOkS>iaBIi`A;Ui+GU=JPCN>8qKdzu-`}g4dTU!KSlfj z;w6YbL~P=J&ZIL~(ziZF{E222jA)sDmm>ZW@n?w5Rz8k2<1h3?vUr&)AQPU-m#+{r zEw5)WsYO+L@-5<@5r2pH2gGLmr&`4y5%c;FSh~uXDQ}qdLxT;=?xImmC&ef-Ly~;)>wG7=2af!InYJ@!D7O)vA zNQtfRUv?0Z8j?0hTqJ8EX+Xk{8Z*+kN82Q4leLhn&6mykg$$H8Z z=JA+f zg9;MAa>%kCBxpySG$Nhc8%bv*U68aO*+lh%;Rk8Rz&qI#iJ2#*m@z<2x*_SVT9foZ z!q=H;A-+A4^g*(@d9F)9k@TudAd)T3&)#)iGxu8|*;>h>)wV&hEt2ic{dO!hJCXF& ze|hPBeV!O`zGpgat4yKkW5B$rg|&Ff87z<9Sv-9wg#@rIVyqF zujD)=mm)bI$wf#mKw|7an`M%Lv)jqVNG{RYcAAJ8y3`8EWk~q?7vqsHC_7$y6jas;#S^xJwU}uB&8 zl3S46j^tJ(JaBSs-KbKQGRq24`$HMlu)493(I3yJQffQ_kBaFCv+5x_$}C zJR~ox{xYXZGV+FiDT8Pnens*cl8=!rK=L+{*O9!5WFe9_3>nR~FQxWd%3og0)gLAnJ%V>U(hD89l3+dXV zs-_T0r5k;PmXM_DB6|kudPqM&x<1m$NH;*rqy&&|gH(`qLb@T+jT>*g1}TRCtkYKg zPTGzTnw$1WdC1KFkTxRifK(5EP({-dgDnx(oK+dMcM`F zrpoT8ml{*>Bhqe2yXzA=XdlE(tXr8r+YISrshR?*AMA_NcUIN zXaOjwT46lWLy#VT^kCEefk-DHJxIIYc03vunj^w+>J~^3MS2)hqfI?dRQk|3q(>kn z)<+^e7U@w)Cu+cx9*tD<|N0`MN|Ss;dK}Ud*bdU;^TakzT@pQ=KDesI6Uw^lGG+YfPK!$A6JtiS#OkhxsU$%uAJ@Hs60? zM*20}sblEMtCZQCbFOlkjZ0uA6UA24PApIWcw@AO!<@B9r=?VeW(;txjY^0rU z{iLGBEC&sBx?Er6{jPnH{)&_}S0Mcp>2FAXCy@>Cf0&vp6&O;C);s+R>EGH2Jqy{v zk!<=e(irJ~NPR<6-Ryu=w*W8#Y5I+Ka+UEKf@}k1ZIJ=lM##9q>K2 zHb&MSSugNo#k7!_nH0`CBHIL+=Kqz{Stn%8=BD%NCoR0u2s!J5tcUqay#O*zQzPqU z%I<2r^e5*dSoSofIUXn5g7>Tc)g?g4dL!EoSsznpD^qT5%56-!Ez4EvqfKpZ9`-e5 zKU4NMk2CLa23wTlTv!TdFARC5kc*nht*T2YXuHw!{BHPJOV-%T- z%xteyu91d2BO8ru3@d96oS_uOAlnt$1<1xCI~v(;$i^WfJN8Ak2eQ3Pg*`Q^!*3a6 zduyOhY0vgi3DKMVknOKoTuw%?q(#ccBRdq?0mu$UcA!pMXA_Veq_B~*ola~SI*X1B z*&!P1GWV4ohU{<^74;yeAUgutk;qK^&n_s#3gI!xCL^1OjQ7VPJ8l)S3c&HmPC#bl zoD?9M>(50t3E3IQPDXYrvQzY-nS4Zc8nV+h<}n}k)m?-XV}51_mS}xo~+eRhj`fx zeoF<}ecJfgFAV$-AftwoHIE^C2-zc)H)IbhP4wnb?N?3QSd~wXO0p-A&7_S&_9U{W zkUh;U>WVmL30adpGRKuYi=1qI4tX17&m&_yvygp&Y&Nn5$mSq>8QBZS=BiKBUS4eJ zJrEhCcb;krH*wRHG4jdgBYRB`OX1vE#%~I6SfM#;H7ttP>&RFxMD~^;?;8XI*_*4l zzF5oAl$$8pXm`vlo% z$hZZ70pZWr=Mp0J@43X1}N5~3fL?qEY@+$va z0yG6#hAdZ1>+(ten3c#XcA?~A$$hN3jeJey4szYmfV_s>Mc$wU=kYmhH8e0}7Cd;{dDpNy(}WClCm5cx)GzW6$Uq1n&d zA@7KMW8@u>x95gRuEYRWOp^#)l0oo@Mc#zGv+_Fcq)EA)1NL*(XtNQ@RlW)GUdX#3 z?~Z&^T@BrfZ3rGo)VUJ&TR+-vRjmUA0d5PpIQK z2>B4?gB5H>anuNon-uq9$VVd|j(kV+a|H5{$VU-nBEAYiGB@7|`Ob|eX?{b`uF1zB zAB%h!9TDfdHoZE5__9R48)p-c?~Z&ARu*|J@t;IOO{vH!FXrr}=)! znfzgnf?pQyq=r2okNf}yTRC;$s$tiFJCGlQoOGsC9E#i+e}2`SW{}?|rUlLqM}CBk zVRbvn|I~-(MgYQ+a~Ds!V=5@-va2fqb&60|PK}P~SQW`PqsH2VX`RkyB1CL4F?c z3z1U*E>LMFl#QNI8rO2;9ei`!1wPiy+RWT}cuIoQr`Vh^lk>8Dc z3i8{KUxWN+N{$@M7mdy(IddI7rKUgA@W7Y-$4F0@;8ycr7$yb&5trDHQqta?3V5c zRUCUXAgB0!fc$;ri#776;t-M-M<6HJKU&4aRaWa` zH~AludeAbG8u^#Vzd`;L@~?H&%;zertoAMP?-V<=DqI3|5^uY6G=04ua$UsEztlL8GRmXBk$cGhLH-}| zfAy?dGi!jnl|0ppW}omV?*ddLT8Q#wU!Qxq188d-qC)}sRoN7vPu4dZHZiv|>Hp;!aOnrc<3v-DrIJjL25+8ABq zJboLM^I}~T?NF?T0w~r;u|eY_`zlk|k^U5J7jZm~@$-Yo*jG`lo z_9!~2C>D*%7Yc^%abx`^6rB`b8g`;w=nz3yJ}vv!yPCFqZo!_2#TRf zx5*DQFPf=2>S8#G5h~SGG!Dv&ktjx?*iq}uUD7=FZr-5S8N~%CMx&UBVhoCLD0V@y zFN$4J?1^G5irsZ2SnQ_YsZT!y#U2{PG3Q_GrGBc|8^u0qLzv{G@X$UK`=QuhVWU%H zO=ZV;6nyaj6r8LvD?f_~`Z4t4AQT6$S_{qjS;}y6C<+xq%?T9j;wTi9A3bPJ8OJ05 zy>@Xlier>??1JZfG8s}Fi{f+?$Dv^F$D`mOOD>8N8b3Nk)r9ZTD;1MaoUGBf(w|$v zSm9I@CjY-W)H_#zAb-$1B>(>U08&TYe;wBWgpdd&$*R8aIq`i=Icc_UjZbNZ9 zifQ#VZzd0^(+x&urlWWq#a$>^|85c!g>G;_ajz+7nDRbT-jCuT6c3nt&Ht?CvIhSs z9x>&k`n>)Z^@4N31%Z5$9jzKSDH)$exe*G=(oz)9pm+<#vnb}Hcn-x}6wjl0K}}yV z3&m^{b95j^x#gMs%!iN{%HjiaSWNW2Kl1n?&1>^JpX5kLd8q*{S1YN;&T*~y)RIFiDH=|!Vf%c^BW49 zrteUEtsbHH2F16km8<2@ysj<)#SdJMXHfVF1>gD^1?|5%ZcbVD3yNP+tWb|Xk&{Gf zpiumdf{_1#;x7~{QJ9mT8F)~7)xuEh|JH#y4=gRr`47c^C|Vl_+@VI87I@Gy6h4Xq z1(_J52vJ0;vo!1)pqh1AMS>zV#w|mUYj`&Dx|X5e8n8>0grP#Y9!jP$Pb3pj+M28= z9mS}unbJksfRgKc_(Ug~QLbsqwNPsEM=$DZxsG|bZv7`qbM>z;3I~wx;$`8t6C`X_it_>14iX~B?rzl6E9EWm8l)Izc z3FWRRcSgAjO3npXC^`T8E+Y_?1X?Yw(JIHH+>OemAn^%jHp@Lw?uBwsRS-6Q%1o4d zqud|mJ{=DwK}gB{xKq1%(I!os_GrGOt-h-~;)_S3JOJe(D9M5ejlGtr_bCrzBPb76 z0V9yqxbjexN9fS9Jd9HwC=b^>XUFkPTa8l1FONcb9Ll3n9-}@{sX9?@S9z?qKqt-! zr#v3zB*V%RP_lxq{87EDm-HI?)+wmSyHimvL3tX=yHTEw@_LkKpu7y_WR&NkJQL+v z>SwjzvzwRmY#dVi9F~T&=bI!d$uYt)1F zeo>MUb5P!k@^O?iP(Fn6K9molyx+9{02?%uP@Cz51r2riFv`dDXt?qb!^%f>EYD6E za_hfOpnM+XlPI4-`INzk_J5|zAVsSl(SII*7o@?MNTF51(XX= z&P6#7<%=l!DWwA&Z~3of*cO^aDPKnUn(2K$%2!aD&(@RIBmy@dap#xH@arf)K)Dd* zJ1E~k`8LWoQJRxid7Lsuol`&Dcg85CBqW^P?TT_yOTRlC&)g8@`zRNy4%Uw~P|5ia zyzkER+SgaP=2E!Q~4#zuTV0h zNCNh4`Z^=NJU;~Gw;FsReN-fVTE*E_n(_a%miL5-JN-J5)BR^-wve+MueT zak71hmr?1O%p@qe{3s?Mm|qw0vN5tWVtw0&a7$f{~W)k$3^L83XT znpIU5k``2(qS^#i7p+Mn#tICms;;QGT;y|9-B5K$)uVajGJ5TfBY)uQJSi7dPgI-h zC|i%6xeiq?RA-^u64joldZQYOst>BZsJ242Evl_i@%+yzI(Fc2hRbHE?NI3;f<2mb z@5HwsszIpwqcR_VJE0nZ ziVJ?;MMV!z_($n%e!H%nj@m{wsM;CTXnOFDJ5JX~<}kL;Yd2K8q8h6(kocsD5@&Z* zd(H zISAF^s18QOV*^h|bqFecf$_%1F*CHJaB{d&9f9gdO;S+?Mv|mFJKgO5sE$E(3aW{y zjz@JYs^eIpXPDMptpN8sm0@i8I zo%Fmx`yXR3CyBgU3Ww6js&kLny$*P}WY)#a$pLv<0V^HE*ENtvFO}^nsJQ-wU0`l*Ks623R8+U3x)BxEdESHSCRAj^%{tU#7)D=100_4JbsMVN$tGoDj|N>e zSCIvGp}G^*bWPNg7pe%P)!nG>Gh)xT?nR|>g}NkinKx`!weW>Iqa2p`zqG zjEcd(IlWU8o)oahP(7|Lj4H|3X?v?DQO!j46so5i_qdHoOZ_&hs?Rg1p4CW$5m)t` z;nDM`W~r%R`i|7Nd>vGCP`!-m1ynDhnyUn)04QEBp_)hEp{MasF?skyH6PV$s9r(! zDsfjcNd78b1p9SujoBMws#5WW*n;X!u_zvIiPebeZB&$ycTjzUiokq=Y7wf%sNO@R zIlAV5uh6Uov%b{_sC2zVQ+MjuM@Ed7p!!(t3A?Dr3Du{lzC^VY)iP9{8N7(>7n(aF z{B#+N*$50{?24x8>pGm8E@n(yeT!-Zs_#(!gz9@#Kj4B>N8j4e6@}L zinSK1|HQIUwW7*Ud8neMH+rD*Q3a^z5$@6Pmrm=s1j5u-Acz%oR`$ySg3Nb zEK~&r233j5uwUJmN)`q%YjEY0a@A#uwZN+NW%rhR1 z8xm_{Q@Ongv%b|qtf69c6l+Vdn#AfZRwuDG6{}e+-N++WXR%tu+C&@V<{c)`#(vm5 zdo)`UtE*Vu^iXC4085$x=b;tF^8YkBHrq2Hw${!%s1PX~*+>|th=je)ckXT$1 zWb&^30#3Wn#X3~1qs2N*ti!d_PA7_Wgkj2&VjZQk9+RHnN&p&Cv5paIqV`3mD|W|< zb(~notJIO~EEQV5K2Iz%{T8t%iFK)1CyRBKSf_|}hFGVHr4tPt1B!LJCIpC=!Z2B^ zGc_!vk>Mh`?&SN~VqGNGIbxkJmVOCEEM5Pj>{E%lz|^@=(^Cvwwcm@yxM&=W^dBK!oT`Sgg_2tL9?P4NXPe$J) z)>N@>RFuhX?)IJfr&t8k+XY6YJ5skzv^~j4qgJ@&r>=Vm--w9T!YoAlB1jJulWwv7Qs_8L@c&n_2TfIU(3w zEJ1dg(^15lE!JGI=7{xzQcp>G2PI0Z7sYx><%*o5{M<$oy(|{-oiElGV!a~P2V%V{ z)|+CzCe{M=iOgE4z`rimLb1&LFEWL#sV=`I)?%^V7Hg4M?}(*SD(dtJj4H)@Vwss= zstf_(bFy2->_f4ZiuI9LpEk}qK&&NVeJs`|s}jPjNv-6NO7;CSu|8LVGdw01uRST& zGO>OT>r1h|73(Xp^s_HweO;Fs9f=I&!|%l6;(uKfp{zNH-Tf%mat}`VzV{mM z{g%Cm^@~`)v3;?A6>EjMU1NNdIEvjLVwGa86suJ%3duiW{Uz4lWCYi2H1=*oG{yQ? zEbjkWjizGfi4}?EixsGFG91!}VdH{lcqE!wu~-`Ui>b2zC-!DyZ!C6; z*zLvcD0ZXR9Vpu^!xnbxIgukiv75y1q(+Ze%-BinW@UlhSu-|gts(X%Vs{g}i`eGm zf7(@BGi)uxbaGJa?qc_7{_15_WU4I*V)qn#8?iSRdrPs|crS(j@h0WkRMzh;_SS@7 z>^`QKt+W?Lxs=9%cZt2N*nP#`PHat3s9~p$4`74+#O~iXcU={vS^6GCA8hlxEx?BVJrcvxIr<_GR8_9(G;5qn3m$1ngEn<%rS zYdc6o0!ltTNMDDiW{JJ4*kg4=7J=c)E9#oPyV!?{y@%N2#NJcveZ=PB&v%Hux1!19 zKP{oXuYq|#vG-T(_?(y0ZM@irh<$+A2Z?>4*b|g@gw|x{4N@ir#F6s-To^+p9wzpY zVjnK{5!yQ|^J^efWcw(wk5+BsmRml&og|$o_I+X>EB4i5A1C&CVjnN|nPQ(H_Gx0D zDE7%>6R=5Sv8ou~+Wrc$PZ9f6-s=+`Ad{**9O+N7&k%cZ9T$as``TSIOxahbhRVqmoPHAf zO0gOLn^7CNrd>=CoBv)T_EfP+@Eb^cv9A;RdJbMRtmP1f^Xc@?_KjlS)H1x6#_(Jz z$9TiOS?s&SzD4Y5V&6&##lB7K+f}<6z*B2zQu@CqHu-g@*wZyC;}%hh*-%gHyT#`E zqEdZ_Z9<4f_B%rzir(BW_6uS^AokN@KPdJiVm~CdCjXm1GMmtq?~jW81iiD^`%mA zBKF7XVhq@Z@1KgjRGkHb2NJxI%KN$4UyJ>P*vpjKN`Wtpj&MKzj~!;A!qDM29S(5XNKyDh z?3K!8PAF@ee~SH=x=xM-`ts*LVz-L@uh?e)FFgWzaV?ohVer{6C)~vjG#a-WN1V09sfn|u`RR(& zAPx^aIk#?XNyC=mM)#ey#c87nH!d9{VHrC)>x#3HIO~Z6aoE%bhO;+l4x9q!h&UUn z@KEH;w5&L7#pxhUJ8>G7EY8N_v~L;V@M_#_62VgnI3305Bu-P~)K}`cdi9%>G^evT zdx_H`&JN;iB2FK1x`@+LoK3~)rp$7>YVXu>r#rzDr-wM3wLfSYm#uc@m&LS&&BfV5 zM|m_{Z7Dy!#Mx4u-mKg_ZyyrGfV7o3+lsTbINPXYpxq;;)N5xuar(1;akdwyuQ;6c zy_=$Fq|q$OqQp7_#2G2hKyikMGf14lo#vUXSj<#Bl8ZA`9CM5V2|^d<3>RmF^4+jr zoKfQJD$b7L>>|!i;_R%lN_*&x7H14)Ksz!XR`VVd8)vLIdy2E0^4-~8oINzv#@L?H z!m}Kyamv}f#W_ZteZ)CLoPEVPK%D)=*-}Kpx*7+K%9g4 zuLeub!JVy1YWbZ*#UULI6X$TH6lc1gztsW3@*m{$z2? z`440bbx>hGTN=I+=Nxe<73YdmigTVgzlp=jUy5^qIB$t_p*Zuzxk#Lc#JO0U8yZ)z z<|X1>D$X_HTqe#{jni5Ga&buTE4l8dX^}o@y8ATq{AyE9VcFqTB6}@w%#-U(d3|G- z`Q~P-I5##uc1hzKH;KcG;{89<(aqxADb6k8+-`o}YRcQ11`Ohjxu0grJ6Mu6;!HO` z?_z0g?q>Vu`Mu)I=ycRQt9!rCbb7xy53H{FVB?av%qtIfIBGe87v~Xi9u;R!(_P<* z^O!h~cbNRII8@AM#d%Vknc_Sp&eN)%WCt&EBhE9cZ=MrpmN?I^s-QA6dsWTu|1+d` z!IX2wc~P8~8b@Ey3FAmT0xZtUh7P=WRh(A{5C0{=ybV-Ieh!cymQk+PfKMh`giQ|j&w>bZ3 z<@?zh+4LW|ro>T@S`7z1mgZloXQ++8ag!4E{~%1n$xVZ)p>xJgdDG$Wm0FE7R3WdV z_MFr#sWnN>mRbj?Ia;|^lUk$HT&b-iwFd3?qf?}|hQ3l;Q)+8TZEfCnIO26}zt+ak zWEZKe%Nz6hdQw|oY8y&z1F7NvK&nZ{!*~krIH_$UwRWa8&ODCO3g*o>JRVYMV=~SEsetmD(08JGJ!f)c6KVsr8oHR_3M;OTONzdDc9+^%0w%THSh5bw&fotd zwLMsx`g@sjZ>b$5wSA!%uaoW|1pJY8yMNbO8>Kba3#gK?I5ayCm-pR#d_xj9d2l-tY9{RL9H zP-+*O`-|8<$!;iriPSFD7uR0bG;+Drt~EDTNbO3gU1RRAlG@cRP1i|n3QImwYB#D# zNR1Dt8o{`}(>o7I?FNk^{wL)jdMULx z%=0&;wn%DJ`FG6Ew^?>x=u7Qg-gG$npU$s+CbjpZ_JP#im)c_f)p_A*oz{BP)cH_q zA2mg2nkyZS*wVcIF(FhFO%q!CR9r`DOQrUw)Y!-mQu|zLUrFr?seRehN)(o{Y+Sx_ zH9TKS?OW5xH!S&3YTrri`})0>4^o|fl-h4n`$=lQNbP5-EpL&l}|F(=^FC^~@ zwy8fyc4sNI-%a_4)K==l^*1E&=E+}DqcQngYQ&UU+-e^FYmohqb&M(Y_)xC}i=`Hr z3Zc}ZdJFt%;7O!L^PWnrkXk0SoDcPtg%5OIaJCa$F|xHikiccz;Anz*i7 z_`_GKm3A9UsrFx8kGqz*#{Scf&;q+{l(6cTP2}UQC+>FU`TF8+Ans=79^#6)UBul` z+z#SyByKzNye)M><;vZdDx+4^Z7*&k?^~K2RVBBhxXt1=t^T)DOUDCDwa(^sHU8%3 zCTxfI=HaH|8vE~dV`Xu>tCeCT>ZTFD&cpx5_cO@ zVQZFrDDJkZhO{{f^Y(@;eOYRFI(dcf7bG#NA0%)g5WdQKmFrVB~({?kp}_8!hf`;*L?xb$2l^?5gnS zTVq-B-oUWCDfckto~s}3E$+VJ?nB_~_D5VcJI)}rKTB5T)zWmNxFp&M;vOjOq2f*u z_h9-jaSvkqrn1_9-k6$)iF>@bhl_iRxJQV4l(y)_e^oG71tPl z_iS;m6qoV;h32oiJe+6lDaIEN1>PHSU1T0!Ebe9EUc!6x^U}H$nF^Pi@{0N^;$CGc zTy4rJtLtAwV9b;2#Jxq_>&3lMTpduDhf|5Wxw*-d{}cD-x^*S)=E<$%-Y4#D;@)X~ z-fqfihT3}ox#6W*SufcC&hh0+{eUyP~3;rq`MEX znzW zclK&T=7{@3{ocGqcE2d@O9se!rhM7J$>;TsUNN2W=WG1eR9GPH>*i*mDc>;Vo8p?_ z&DDX75raCl-xc>qaTkgEfw=FjhT(m27xP}#LFMg3alaAwBXK`9KbM&DV}tT11{k8Y zRNQ6aer8bq+>~Fe5&+e&FU9?;j=K@LueI@2?EY5V?^-%OA@294{DGx`kWYRR_iu53 zHt;VOm*D{){!*_X?h0{#7xyv7@ttwOoL=M5;qn%H8v-ynr8LyUt)#)021_5(Gfv+{ z8k|l?Em2EOFWI0jKpNEe>%~BwLyRL{mxi^Zp@aErZE0vD4I65M4eOZFz}&E&G_22` z=E(-q0COYyWV!~f=I2JHY-`GPrrg++?WLiSH}#s*&`}zi>Yl~)*hw0ibv&Xy8W+&e z(s}N0(y)m%bWtEzoU22IhOW}kLmIkCLw9Y5sIfs3r(rW`7$OZlrGaL7b7|P#pt*%9 zdzo@e(@Srzr;>&~(y*1e*;*R5m4>RBUS}uF z3=WeawqtheFgeU$n89JbVP?D`C%Iv^o!D`hnVFe6e^qI4d4S4zD!WrzU!TG%6AUL(*@%iHH6Wm77f zY1RS{7Dz?@1&o+%No8v)TM0LlNrLT>L#S+Hx!VeA&kL&Z_J;ERUxe>yxDyo_EII2F ziM(|Zl-Cd&$m*#Z4^94`;T}{@p|U5HLoK-%mA$DPV0<4c`wB6>pW*(3U02BuQp8*) z$w5Ml%S=8*xjmwq4s9f6hm?J~hb?@>;xl}Hvas`!Jtuj|qxr)k-#;>Mw z4HfxZOy)wiVlO=)o(mbp!;m?WYa7iT*~KTu7nwk<0& zY#HW)dgP@}`Wedm*$p&ASLdcWTs*6*dkLtoy=cl@W&f!`8 zs4hr#A<@@~x8f=?$*C?vby4AxD_ul&G2vR=V-D3NdhW04QdC!L98^s18>BS=xNf?eRI(>O4%8i!7?^Q5`{5I%=eLk@naqs-vlnGd{*}tY*n- zUL8->!hUsqJ(KLwpD53LCEt+hMrN{cDcQu5n`%-XbE=zDeVOVOR8OM1CDkLTZbfxJ zs*|YhOLc3i+sT1B)orYv+jjlDoOFKIW1_P?)g3e@RClDhC)J&#{#19Sx;xcf#Kf$2 zrK-z;9=U5nw1y_%qmXFes_sQ~Zy5-i1N-R67I)JE@;?(Fq`E)V1E?Nsxd$5B5};X! zSW*`eBZnCtZaCTS2pM7{7XPb93%6=m{I5!*j+3NGEdEzdpn9T8Y>lIOGSzD=_Y|uC zqI$mZQ>mV&I@+Amsh(lvOvAINo=f%YGWQ(WKa~7DC9<8UUQkLdqBRBsV9lUrq2h1=lXZdvm8Ka1p@MHbb&sZOVQ57ql^i0?JLue|#nFhA-|BvhwT zohHMoNgdpWsLrt4?O{Q?iDpuL)W{=-vjkPSC--sl_JobvlT@Ff`jqjfi?I{5z`Ms%02k1_%ye#8NN>S4XPT1slI9W7S*?@eoFNnsvl8(m+Jdw z{$3H&Oy>itAIdEz=Qes`Q~j8#ME>EDRmQ!~sD4fLbE;qH-Dfu=)i0@j)fsqbXW;FE zRKMw?ufd<{_l7^HT$4Xi{fX+&n$+?Ah3aoqf0Yv|@u}xj)!*A&T`lKMf>i&cCV}NI zY7!p*rZ%SqoPQK+vr(H}yHxocf;zsnxv2TnD%5J)^ELew8M-Tw+qPDx=1^ zwb9gV?^hc`Z7j8asEwocGPUv4&Z4$Hwe3yc)`HqZYBrr}8&ccC05ahLa4pHr&Q=Tf^-HOD8)}+u4LW8t&Bf-p^fkY)x$!YDZJsmD<7P zb~kFfQ`?uC#(!#i8188(|NmXRQQMo^J_T1)wfzir|4;1z!vhV=_+QigKQ()Zs2yh9 z_I|a=#*Z*O(oo}nS?6P@>EcN3SZW&osaf!^>E^%vV4K>B)O7!E{A6mU6kO2ic`7xH z|I|(|C1+66_+R=zo7&^l&Y^ZCwR5Ri7_Xg2&6bav#(!!T8eU|0v7nW82{n!X)O1l0 ziE)kpa@h@X6}73L+x(EdkpWTcAx5~{C>j+sOh8tkqf>xzZsC{aZ&kR2=Hlu1Av(WxUgHw~{}$4}eWeu988k_S_1UP;Ze$MX>rK=7pB*nN;52@RJzTPx73?swXFfmLGGeK>2J-4LI z=XyWmU55P)=Qiv%>hn=wnfms<5bqoG=4gS;z7|KUMIwN_SQEvzLDX^hMO2}N`14^aC4Cer@kfi9jR|+3dKaS_+Q_~Bo_bc+Zo^9 za0kKC$xhVwq^`?B*A3G7yHMY?Aua{czKnYU*BV|YXhmF4UE_aIE9z5lBm&$-{V(b_ zQ-6y3E!1aH{||K;@ms0iMg2DFcXZACKkB!4UA>Jtxl_$$BX?0k{ch^_QNM?}{Qm1f zQ7Copr~W{XMx0~f6^#x_zLw`|5x%l^>?Yi zLH%v&Z&H7&Gw77gpnL5)MKr0uQ*dkedxq}|icjhvQva6vN7TQd{xS8>sDDEJQ_1c6 zY+`57M@pWSc=dT_aAbyGQvaIzS4CwygV!|w^5=`Sjnu!R{xkLOssCvG^Mh&f zk|e4BLj8B@zf%8AgoT31|DgV-NIHY}5$x|?OZ{)0MXCRT6XVQ=1J3L?0nQvab4lHB z=9DJLa@;@P7N>$!)j8wT3@!dU7XKZK|Bl9g%ZZ+Fogh6KRWN zo;Y&NgOlR)!^v=RsT)qKs5y>KiIR8WbmR2LnY(C|$YseE#Oa7cmvU!boCR>^!ga`D`N2B0@TYr8k2+ndiE8{GWV`1OXB|yj88Gy5rkRoY*R>4`#$f|-eNpMydBA4llt95bK!dVAr zZ4qiqwND1(43;F$AVHH1DYiyk7m4OjoKZN#aMr^auKn55r6X`gN)08cR@%vn5m<4davI7i?}hwO?Y1GfdvhB%v8GdIH7SgRz@+Qt3oY-+u|8P4X?w{mse z6X9%$vm?${I8x#y?RFiJt-Bt-!-&kB?Tu{L_2^-p!E%VYLuZI55}lLIPB=T4d>0{d zS;Ke3IT&Ymoc(bA4`*+Yb6^=NO#hagHro zjdNUQ-B>uz2{_V4A`i zs;ru4;hcwacIo*Xb9=7Y%A7pElwW{zVZl4=?t*hM&J8%1;9O?m*ZT6a|O>^+D2;BM$7swg z-}c}X6E+s zr`GUeoKH;hsi2q`|D47gIA7p=i}R&OO!yVf*G9f6Bv6MF0)R)Skv>g#&W7JM{3Gfps@~( z6=|$SV*rg+XskqIW%;nG@L76WwIr+4Sd+#Y-P1oQC2P@GTX^?Fk9LL~AcV$18sn`F zgJ=w7IJ6Nj9Ug6OGMjY)4}Y8k1;jX*yf!XKB()@-10oYm;n4 zW80!1iVyH4m&W#%wS(b~f+pOV#y&K5q49qv-<8I0G)Zlr17H+ zkEU^q5E&iuW|HG*oJiw%<0ptWDb7kc$s{KWie~r2i_?(yo=M|Wlgn2VGSf~MG;&7Q zP4gN#i^lmh&Zco5jdM(Lu6Qd(R+3#S$Q-`F@Io3g!!ObjO9fG*aVd>YXnBwxw?|ZRWzs{j2am;~pCK(zs8OQdUtejR$BvPvb!vkI;1Yc!9=SG+w0fI*pfTykZVt7BoMv(s-@lrZ2Nvy5ddYG8Sbk-lp*$jdy6g zt2)y^5w}8UyiemJ8XwS*FYPr-*d^omF%8v`WU2WXZcgKK+&O7{LE~2%U()!F##c1H zF;`y;T7AA1Ebb;6-_!Vs#t$^)W7odkex@Pcf2u^R1Zn(6<8Km z<2xbUoMk(OfpZAG(I2h z*|_uLZic%6?wYs@;x3Q75bn~r3*$<=7QtN%cTwpOF%iUFT)8E2mo&*zf*QNF(t8J2 zR{`8*ahDS!m&Q7G1>BW!S5zn3T?24e64G;bxT~0ORovB$tR@vUvWB2sR{yndH^f~V zcNFeAxa;B$#2sv94N@H|3wMZ+&hVXahvKe>I}CTYtmYytbZ|%Djw~d_N4&VBamV3~ zF;`;+b^gegIqrDe2_{@$>S<&m?gm06SxRhaa4r73n-sOh-Bd_tu9tB)$K4Be3tXws zmblyCZY5c`lMJ^O?3t_XwzxZ35!>Nv@V6|xP23%Ecao$88A04#40ko$4R;UR-ElR# ziYvK*pNjO>kj4DLR-hvM#wdl2q^xCh|wUnmHgs{=*3G&$HLhZNp$55qkYSK~kK zWZWYPHPM$&z_s}A9$g4c=U7~s0LS5;jC(xpiMS_-ypO_3WvfpSp#%i8Iu-W}T#f&h z)Fq&t^JkjmEJ5i6n+)gRYW&AN&+vRfBNrH6h->j*x!JUG8DXz@k%W$v6y}Wou zj(de1{>wvwz5;Ns(sxO+A$PAqp8DN?%LcoD{Rqpt4)+Ez!M$Ezy50GO32)R-4vLrH z?oB4SSwER@Z^5-M5afNIe9z$ChPNs1?RYZ(@4)>A_fFi`aPPvsANOwD`>c}p7~b0% zZsFyoc`f+>?hM=qjZd}FoQC@luEu})%yELP{~oQ@F3LTySNH$8&zF)Ha9=cuy`OboHvUSHD=6k> z_`3Od1NQ?X_HxaA3-@i@cX8iQwHcRK)_b_`3-8RaMuE8c{uftj`#J8%xSyKZCxyNs z?q~7~1rvUO`xWk&!sRN>zwWFz)YQJki*dih{S)_l+#gg&e*575i2DcbPq@E!=8)U) zXTx7Q>m4k_ivG>;_o6uk;{Ju#!2KK9KfC|nRkZ@%YxHl=$UL}s9$u(*@O;BSu(RF`#+!KHMWU&~InFSX1aE%46tBOPm>IV4 zay$$BUR%_xxGp7PJNuD%bK`Z)yt}A_@p%kwf_d`^H>(Bkmc?5TZwb7G@D{~e*h-fg zE~3PhW-f-exNtEk(EKcExD?*fc+1GnU&@usgc|?xmN)+^;0?fAQMitycFRhFqEH~- zDtN2ntz#yu;jM0D4Z}56OJxOR`BN(SN$!kd6+3xPKTZ!F%r{U+U|KWy=a;tjK( zx(FX`lJyKn7>+a?WjNY!jG#D|pQ7N6!yAvcey1MkFH_{2HW%JRybbYm3Fw|Vy|dnH zR-cUwH}0M(ZQP_YqG4n+0_p$F@kirrf%gL5mU!pmZH0Fz-XuIJVr#q|@T7;g!`oIC zBYDn}h^jv-@wV^G_FKof9dAdxUGR3o+qpAh8Oart;EA^@-fo4mNTl2Gq)Ye1+XHWJ zJW<%I;~yX^vg$l^1>Qb*`|3lw)V$cudi&!Ygm(bmft}e7EEYsj#yc4A5NU$^Xs9z{ zGpo#Dcqif=j&~g1WISot5h9nql3(KB9ffynXHJpe zsn0KJqD5SQ_W<67csJu+geR@O81GWNOQc<*AT7eX4DWL7gyJ_rZ%Xs8#JdLXDm>l2 z6pM~Im#(-8?^<1Yyz8uP*Bjm-Xu=!urW9PTGvXx6y#-H(Pbzbpc*DC@(0Wyd@{Xbv zlI)BSoxAYv#k(8t9-U+M+|(KI0p5Lh_e)Ynq&;afE9gPINAN8Ed(-e9#+#1!km&TZ zVn!F<47{1zuD|ZkR>n?+CIJ&X4=-ZSE=R|oGo zQLt)1uXWaOc@e)K-b;Aj;=PRb9^NZ>Z{od*_d4Ed=1?E~@N}0V#Om-C-aFQWw*}=v z8}D5i2r0|P>wUaW@ubNg<9&$tQ6ZNsnW(1liR=xm^w03V!uuTWi_XY?9pt9?QZ}5z zE%$3YeG2IQ?0!8ikYg+FJN()3zQ_9w?+3h}@qWbnNlav7tB&3;zu^6<_qF^K?z11{ z7Qp)*?=QSR@cz_=>7gq+Bgb_ABNOItyniI#%CGR`lF5lbJHCfM2YwZQPW*~G^yexf zTW929I!pZ;e#4aO_>R<5eATYNvRoSX@x@z!ABsepFH=-T0Uvz*jaz5rRrqoD zv_Co{pT$oMQ^U-#g`bu6<@k>H50C`LRq-| zHF~}Z^Vh;(yEAIKc$S)&|AF|!@CV_`og$4LEZuG<>*5cU&?g>r#AO8i;rJu**TWwn zBim{It2635S-$b@{O2yLEo1Oy)W+gpjz11xMru6%R`~1VZ-PGoe?$C<_#5abTL94+ z?r(&@agmhbtX7-iZ-KuV{^o_eXEOU+7B{So?SU|!VfZKDAC7-K{$$fR!thALqYRH0 zlm_A-gMX|^jw>XB+6tW~C*q$ZGhEu$8NHFNZvH9wXXDF+KLh_%{L@5Ja_w$9UFu+d z&cxTrWBqfC6>$#!`S|DJpC?IisC}h(xh3N{>}JP@NbfS6HQt7 zWy;~-g0JzdGx|MKyR~eEH1>Y{JMiztm#(-=!`B!z?D%)6+w#sHGu?M8I*tUzs3Ip|2zEO@%87x?R~Gu|H1G_ z!=DU)HvGl#SHs@~wa)Tu>lxD6Kk@&@|4WB*lha>Q1CKw3`VuB{}>t=O`pq4Ag z>2~5okmyv`tc+k0f)>HN1UW$$L7SkTlxWMDKKK*#CzwZFr=4f`vP7OIjxq^R_6#G6aheEJ?69!4lmMpCaATJ)_I&vlPM7 zLW(Af3Bj@iD-bM4u)Oq}WQi;Dvm(I&A)PT-T9a4Orb+w>Rw3AcU{!(<1gjAYBv_qb zErK-&*3>)2Sywdmo(7lg0TdnL`X22V2t`-Zm2AaR{wDX69~o=tY7HM!YAv6`Pc9NO=m-b?Flv_ z*n(hVf=vlF5qX&_`~S^*le$s_TM}$Tuoc19W|}V33egaiC|~(EY*`Zp#7}X-j!fCF(=sF`p_OWgFTGzX}Fi+-hyIfd|!gY z3HBp6j9`Cjj>Z2#YeI04;lYN72+EFy;Lu{rxKh%a5BM(1SiRy)ZgzHt5t9cfo@7=jHFRkpVJ7= zAvm2tAA7BJXG(WTSp;VhoUOGg*6ZM0g7YN8h>jRqhg?7)bMHces|hY5(BQ9ON^gft z>|VIk@G`^84X+@$Qhn;3a+RQP8=Y$e&G1@+>#btfNfGK$d;SL5f?Dnrg1ZQABDkI4 zW`bJ@ZXvMxNS}{A#td&0S8|ojxx*Tzf04k*-2_t!?jg9J;9i3JBn-;7U-flF9w5-a z1tCe**ZDt<;9-L41p4zLUiggQb1iqIHnuaaAAwZnE8*Q!JJva({0+hP1m6;Tr*a89 z7I_GMAdvg(N2~Ks)-ykAiLzG-^eupJIWDl#{GCt&`yT{<6Z}bF!GHKia_>tK1pg4u zCQ(I$ux+6U=OCO*$|9Uoh4ME#!U~~o^$DwlH6wcX)BUaPnzZI4`w_Z?yAyhZ%M<#9 z3lRo{a}$Py5n+=M9ad?=-`{I*gfU@%b7gM?!c?moW`_C}fG{^~8;bufQ5G&JCjeHf zZo+v9JB0H{5EJJz^j7Wp2V=`59(7+;BZX9rsYqe{|es&qg@P zaJ1nV!?A|r495$Krrvtt1j3?cHZX4+mhz1x*Cd+|?nt;P;pT*!NfB~bZN+UtxPy@` z3AZxMNrqb++VNw!t?}&)w-+pGSj;!Vod|a!+*$fT?ojLDT?uzHwLN8br(4`mX5I0G zdk`K#xF_L$gnJS0L%6rZ88Hz-TxP>E#WbQ$63D}D}F?-S$czWzt?pc&*%nJv%yk z&>*~l@JqrQ2|pp6Lii-%O@t2+-b^Shy@l{LLh0UHrQgI*?iAV965dXDhr~B2S_e`4 zL|Sn#;oXGyNZ(5RWfs}E+(#(q7#c#wTpta>2MK2qP9=PZa2nxs8S%o2%>?=WXNEq~ zO8A|zj#v>sLiiZrEW$@+MYG0A%siIxal$902(j%NP%+!52;U-nn($@9X9!;;e3tOJ zK9zi)@CB{OxLXA^&WA7QdadLY!q*93C45b?w8A~};SIt!wNML^`hGuroA5nC{S!ch z?-rUi&E6;c(1ahT{y+T)KO+3NSc61CgO~=BPYFNM*DK-Yf=2ZHi+(B@er4_b+VC60 zZwY^}Zv4)Y-xqyLsOP$b8vo@og<)1FhL31wSa~ALBjN2nX@v5#_qgfYW&zrkugJxhlE=^B} zaeWIc3N%BS5lxL|Ca1L0_rEkX_;<$Hb5=9cwlrHbyJ_Y$yG)@?Qy&3TZM>(w+U!qr zZtYmxnbWlQzs-4Q&es{g0?m0l<2RApRuyE!++2X>D4Gk>l&pnlE<sism1v~4nOLGLxk)qI9Ujt)j{Yz<%rnwo-F*G-#IhN)`n&W7$Z&u^WHcpUa zpVmnYH|+l6JQ0d3nj4$)CWe~|cGiE!+fJI&<}>c{0sYO>znna;5aS`djie znrG5Ho#q*mER>a~?OCPHIW(`Oc`nUMXr4#&Ld!kh@Pght)4Yi0#f7caM+W+8nwQbM z!Za_}tiQjfc_q!OluN~|2Q2p*n*UZ|kIr>8@1%J>&0A>RKywPs8~fzmMDym(gyqD8 zURqVTUv4vUtDyDX?KJOD6H~ArxQph!H1AfSn%q<5(!9@-(t!_1^@?1vqB)i3Cp4$g ze46HTnvc_bh~_Ms57V3}A)V$7L3t{t`G^p?%^VK3(6ogpT>%y%LZ_<39=36x1 zG5Ontt?_pa-xG<&u@7i|MDs%tilJqFTsX9HKc)FI&Ch6lL-TXX{etF~g)+^rOy}#O zQ1kXJ%^ytdJHziw%^!uB`2O6*_@+Ff*Z+UU6C0U=)moF})HYFwWE@_)F2c5Xfcni7*K^fX!h;uz=4-pz*)g1LzU}oAnAomjKw( zP#*z|Yz;fZHn4*ww}tIMH~*59t5n+&^ohQZz%H=6>F;W&PXR`x+Iztsu;>5hL6UO8 zKCmyG2>ZdICfOejfP>(`PIR_N1mR#fM2M`Ua&;mZro-T9I2?|E$&w{P(>xN6l10#x zh2$7G&P2M013jZp6nu1i|G)+!8AI^X?OFw6Ulq-iG zS|!nhbKznTav_NF1%>fSHq2P z4O|CuJ6|g|vTQ;1{9HDoTn{%CT=%;2JjE2a8Ez6`As2*O3KO^$?t=D0N;FeW{h8`FX=o zzyCM#Hhcu{!29qnyeC#gCs^(W@S%{xT(aO}_!K@7phPT?oA3wt$r|-zQ7-%}Nx4KBeucl_H;`@r?|tNd3KtKi^Ec7#@DI^!y)~CC zxrpWv)EY)}5iLhlA?hNk5(Pvx@jz53a)}(GhNu<(&CnzAdnH;*6cVLGO`^yQDRPNo zqNJ!yfkYWmZiTj_f#$7E)K78?6O;5OT7+nBqIrqBiRLkV{T4{dl1m8Dd_)Tp%}=yI z(F&29+CoGN7v4(AqC`s&EoQlk3(6%~L`xDaZNjAreIn_KWkpgrv{IHQ8cDPQ(b_~S z60JfsfM{j2T1ilJ3T2{IiPj)mjc9d|6b^+Dtx2@j|7xy7G?Zu{(O~m4sPr&|Xx&0n zs;8qL4I^65bcPFx&>W5^+ck=4W1`VS6NttTjkDabMU{xg6Rj^+g>!4~M4}CeHV{cs zJtF=1r%)i;L^kk5n;LE=sKu#d3*v8ywj|z?Xe(mrkV!<36Kzc-qp%IpzC_y+?W|`+ z(RM`J6YXexhwf$C^OLU6r$Xg^;JC*1zqSJ`3Av&Gt za-uVc&LKJziQ$oc0#tZbGI3s_bBQi6LwyTObiSaJD`gQ~Xm}Bky#i2)eRddKYVykj zt$(f{y2@;?6zoit;l5g(=zxktnch=~t|hvG=sKe73mw6-!Z%8NO!Fq9TZnEJF18{h zk}oAxjp$aQ+oUYxw-en_aycH+b6TRii5@g3_YmDnq+k9J-Pf6@J!$-bvahBR%^;eF zEP|>tU9il0xU9)cBFTD$Xjb7t=0=(InCUM~^aQa-^d!+2L{AaDMf5b$t3=NbJx}y3 z(Q|6@-x1gIAM3XliC!xC%O-h6&|2{t(HllyFXzddQe4U3CVJoM{0`B(MDJ;qJSKQc zo0AWSJ|p_jy6B@Ki%1^#`JaY=8Ooo47Ki@`s-Jjv z;<;23&p|w=kir{rh1emk64yj1u88aMXwY#!G^+-&TWA(0#6IzY!~t=C;*dBaZW1Tv z8Rd4Dm+9V~Hmck0ZA8pLl$ktLHx@*EjO! zZNr|^p?G6r-R%=^Ladwr|DF`Zo145i0y>a*E34Hc;&X|&CO(dM8{)l)w#Y&*P>h#Jb~4mg0G72YvCKpL1k{rH6_6?HA!tNbC?k6lA4a?nv6Q|q@&3g7 ziM-4`pwPG3auD$$W^!<6gZT?N@u8*W;lxJ~Pd0@ki1qwmc$s@N@v+3mh(ug^BLe**C-mOPR8Bq7F67EL44(ld!qB|hDf`Uz;MbB2)8)mg-66Q5HgMQ(LIkN9!o z^NDXGzJT}|;tPo{BbIyTQsRq=_2W<335ZoM5?@YyrEU*meYGc9#8(ktT__a##4-v~ zkbm73Ur&6UWEHu@HxS=g)S;BzL@YypGx04&DTQ2+Sik=?ay#(@#CH(iO?;2-^dy=8VKafbUvh)A=Cz6o(XOh{8eqfTZMqL(~@CUcNDBy*BfN#-J{$Wg6k$%C^vNopi@A?j8hepS*S z@kv|~Pc+4wLQuk`$Wy^t!>9BpFFd_2qM^;(R!1 zlXQ{vQ$Oa4q(8~r_TLlaf%kkNBps54Nai7#pJZMVc?+Nw=xo@rx-CGmplGUFDMeel zFv+6AN%Z{3$YLami~k}iKK0g4mLyqPc8 zt=Rmbv9u9LW|WTNaw7{v?uZgp=sEK;}Wu4@>@M7r_M$w|Pk=1B7s)*TxAOSWedrbS|ue{lUz%34T*KK)l=k_bzP}*1IbM$(N%zCN-4jY+;v(g{JnB0~C)qIoWLz1^h-XnRN$^=li2s4$tQ)}bUxD}RJr^UpjyeVNG0@rO)Am#8Mta}7Cz1GZ>9e~Nd7j3KS}=TQP8sfA)SMCHqzO9+!n5+O6i=1gtS81B(0J< zq&0Pt*40B%^Rz+gle(l{uUjoT4M@WtNih#oIp%MXMx+U;?*B^l1L;bn_Q6lOvdLE|<*Sj}&%RRq z30Ubzzl666tWEYj={lrukPak0l~i8=kPar@j&um=hNSC~j@D_J4kaB%I)Ze#CD#*_ z%k)QC-_@( z3&Z~|0i=^eV#2LSZ6Qee#Q$`A(!)u2AhqMibVt&iNNq((b^lMgOIiAEq($X-FC{(u ze@T+=MS39V-lY4H>Ox?)8viYKe@p8AzqGaZpB_xAVV_h#0Tp8WutJS=GUFsbK=_QtXk>SOCX3?c4xvbDMeud$cq*ocax=)r= zQa{2Wy_WPk(pyQdC%u{U22zdrq&Jo=x=F46duQE3s(Xnd*EDZ4yq#3ffArFN-bH$k z$@T9-7`fN*KEwM-r;+l2-(cyh^H{0Fl04NJNeF zO|sFXZ;@%VBz>Fo9nz1CYy2mDkMw=h{6Nsihk};%G3nPP`GoXSBcG9eN%}eI7ot$8 znf$BXBi;DDtB>&S zf|JcAq~vpuEl4&eSxPn+nNL(d0$?82NW(~`74L!k99+)H~i;OhM5Yk5> zCeufNQa>Z>B5M^oWVsMBeG5$1^Zu8tza{4;n~$uUY#vMYy#F;mZ{f}O{ABtQ0J%g+ zwh-BxWDAomL$(On5@d^-ntceAE#60S$%2zDMYeQdZko%Itw^?+2 zI(Gh(=_7z8_56oylwc_tBZO=$+2Lg4$hIRJPqs0c9Ry_)On)MoocW012Bpb|>3`Y!|W}$#&{vzH^}? z60%*%b}N#CWOD6Cwg=h1WP6hBZNk0!=>#p(d-a8o>1$#m zhZS`on@n~+*%4&tkR3^O3fWO)$C=L2WXF<0oeuieQx;Pj(X731lbs$Ss#lH~)o% z>|bOS>a$bHP9r=0e>Kk}JDcpRUj5SQT(Zl_&Lg{o?0m8d$u1~#bY;mdA}b?*PfaZN zXBPZ>>Tm^_XkJNnHJQf$o-nHozos`ycCB!-p6~z3ZXkP*>_)OX$)=FqV!1bw-Q34S zM#o})c55LdyPeE}f7vr;br;#)WcQKXL-xP>|LlIU2YTxxglsC=V`S6F9wwWvZnahq zmHj+}>=Bd9)GXDXMW*i~ilk*dPWBYp6J#3vd$Y)%CVQr|dY0_D|IK}YmUQxqv^FDq ziB>@NGTFyu8vn^&C40+=EdklAB1FYlf6%-j{syA{ImB89n00tKbfA8 z8__kwKM>8hW(+o+0Sb3~8-Ht4XWN6yyV+$Vf~prfYh(q(6=|(TYXGfPXstwR<(@)&TCu8{ z7yH20>a^CPwFWIc|0$f5g|1zafwab&+d;Gj(;7i*2(4izS=Vr=7>Y72JO62|C&Jzm zX^o^ciq>cmmX#b+>W`zP5uVm~!}Sd((3+^qrcY}_6K+&UXl+bu6Iz=}vY23kv^F=L zEokjUYfIx>(b~?)B*U!@w=vwdtj6}Vb~VWkw05Mmv+ayEm<4Xld}LwJ)v1Xzgcwf5QW4>EVxXOCCh)U?aNur)BZK$Is!kj-)l& z{)pcdCNJ+3t2RfU9>{`kuzVtZ}DOU>(P-C$X|`8P7f@TOAX7Fw^;lA)hL>sDI# z(z=b-owRN@wL1zcYtdaMxtrEKg{>q_ejlyrwC<<%pe1z`FwLp7rWHE9Nb4b55BEvV zr1dndM`-DpKdo7Yj}{8F9;fxBNuDSq#-A#4pP}^vt!HUH*IOT2&zJo#@&9Ghf2pj` zD{9ztx4uT}D_XD9`jFNewB9z&H)*|9IJe|GwB9rFZt3BDTKboQL|CZN`iR!2rt`7k zC#B|Rw7#JAd9SABep%*zP3t#W-_ZJ**0;2Np!FRs{qj>3%KHCEOTPm8U*Rtzq4jHz zQ2FmB`Gb~z|5NDG`kQ<>t$)ZDC!dYHANlO$@-wYD$Q|-I$>slb%|%`*{1ixDBd`Cj zq(SbJyX1QQBUzIF$yX&`-8`&TXp*l{sF`prK_flC0y934 zd=UA%QqrWlvX5^cY>*0SN+s(WNL&`q+ z`aHk4Cgc~AUrc_{|N6hA&@`*d$S)_qiu?-lD|_Wa$gl3TBA33pjr>~jDdg9YTiDO9 z@6{*2vEbx4k^hJMW^z6G@AY71-C88cZzsQp{0?#p`}v){n&fwvI`@)4Kz<*&zWEhl zDSwb$|M*WI$#n9U$R8qq0{Qp5`J?1B$R7~}a^3$Mnbk+%mV^B9K9VQNpCx~a{Am#u z&dHxCIQetr`W8TvB-Y_xsa zv(v88o`ZIU_MEhJ^DnlAA9d2M7MymSwr%^{PAPF|d%X@#IiMZW4rw=4M|!C(ANBV{ z{!*f!fEN8gJEOe>?H28MXy>%MX}9G^0A|>))ag%q?!vb8&=FGdd1)^~dp_C=(w?7o zPvkFc7oxpzp;^?E_M)`)Z$8uRiT|aaC26lrdnww>(_WhPvLdIwOmFLGFIRe4f%X8} zx)2nlnC42Flo4#NLVI=Et6C8n{Pj}#8noA@y(aCo3aehEy-wkZ_8{83(H=~D2iiku zZ$^7v+T&;srM(_)eH;)g+QUnqBWRDNJ(BjQqHd+a7~1+Yp=v#)kEgvM?e%Hvwx9Nd zLT+524y*|q(cXmi#%kC*k=ptcP&RFI+FQ`xj`o(cx0b(=N_$dK^U}jMv~}lS=$p^& z)tl8p2<;td>*k-fJqWaW_W!i?8Nu{-r+qkWQP_|69%`$~d(z&Ewr%^{dKOeRO5?w9 z+WXVCt$zCe(?5{*K|+dZn8Y3d+K19UOig;qnoRpF+DFhnp7xQnk1iPrq;K87-xucfUg zKcZaf-$4Hk?HlR$Bkd{ln?(C2`pIOwnSL^jZ=wAW?f=kzh4!tqr_;WT_Fek0*1ny# zesMutzi<!&g$httYEUTDse4P^-QB1=RqCbG{a1H)cX$8K%q5in zd-uL?c6N4l)@Jv1Hy8R#4Sz=e@96(I{lB8WegcC2`nzCxEaBJm|JIn_RF?O0{Jy9E z2NTn;fJzkof5w@C{=d-wFZ%yV|KI7~-T$%JKj{BwrJ<+iZ=C7q{}28DtvI5s8|6%2 z(Ks{W%!)G;&MY`HOKc_36SMsvsRU>Cp8T9Rb)30y7QmSsXMUV{aMbhT%v;qAj!ebf zjEvvMuy0Spf;cWtKb-zrT~4h^C9yb8m9NQrI5q{CD&(_yP8+9zlj1aSVw?~s>Skmu zVyTi!15OL4z{zm(|8Gop3&^tT;4Fr-5Y8ev191NH{G!CP>Hd!qi{t2;A7_b58_tqA zdh$C}InL5JE8r|+nwK>kY^Ym6ILix)2BTLr93p7MP@G{#tkmPIJWZY0j*zn&&gwX} ztK+PJv!==G6o9if&bm13RB5Y(IKwS!JsjQq5hl(CI64tbqj?0*NSsY@y8a($l%*Qo zQ)2r+&RAn^inCcat(6+bHh-Kgah}K73TI!Ot#S6m8HXcQZG)q(9%ow|_5VieD}WK| z{Bd;u$B3PKIjaB1+0AhGUab0mt9*0*Dli^L_kT=G{lCTTha(NNKh9}52jCoub0E$k zIO_j#4p#ZD<%i;!&vy=+hS~N1I7i`}fO9m?v6k?dUaI48)bmfR1e_CbPQj77J$Wj# z62R%s0ywART!eE5&UrXz;+%tX7LIQIXjOFWITvTbv>I@93c$Gl=fX;|S8o^NTxwDJ z6;LBC!?^;d>;HSqD|^I5oO^Jt#<>mW8k|Wu*Wz4n33Urd#k6nSARHrZ#JL&gCJlCL z^%fl6{HYY0%^PplDF8qDjG-M0))o77w2J|`*0qxV0RY4(anFHhlE+_ z$9V+jNt{P<9>;kM=YOiol$hib6)nOzPvJac%%>|EoM&;Kt5o&0>E;j4i-s@Ze2()n z&WAX!;Jk_RD$W}?ui@C@-!hVfnpRW2g=4!w&fAqvoOf~Ft71*yeVh-fC=06p$N3oN zQzJg9OvU+3g8FDlzrgtc=S!S#Oy(<`uZ5^|3gUc=^BvCjQ*$xqkCg_UZJnQSZQ^i# z!Ljwfqy8V~cij1K{=l8lf`8)th4Zh`e;fABf8FVDXHZqH`hOuR#GMIuPTZMsXS3K@ z4D|@OCons%z6eaU4RHCz|B zzXXl%RD7d7T)*O5@&Gr+ZQzC$t6y}fVsUjAFqyYKinyyTaaY0}R*4yNW!zON{k^oS;jW3hy2Y-cm0ZnA?pnC( z7<27P(rEeq&%#Emhr1o_`nY3pH^3cf%nflj!W~g*=;_%QceKUoPr!tUy9w?X;Z#@I zjk784R=AttZh^bGaQ?gH?sm6;aJRoyj~2;&~zIe5U+|zOO`=64;IA_YWeGHIEcSfCUL9VDE7@IydpYjKxR>Hq zp8%Sgr_U5GNbZ0;NFir3HLS&-iUh> z?#)8m7`X-aR-xr1MS2fh<>i@zdxz1JaqsNL%6D4i`$g_uxcB1TjeC#q7gw`+Zi)PjH3hocMui}1z`x@>i zxUb{Bhx-QZ+qiGyz7>qPUXqBl@-ZdccX0LNKl**eMOQJk@8f=i`vLBU!Cc#_a`{sj zMJeXTO2~IPT;C2J9+4*xc``Q)33k@Vf4QhjW<1BU%VOc{|2yqWN3?k4wUkxrsXyxH((7iKWxE$uem9C&jY-<-cUx6$($&MTJ8y z5#CaGF`oK%yu>iYYjr;)=;)y20-qv{1I^*!f2K^|6sH*=n3U52So$$7opfK@vFx)Yi zc`4D4w=>?Jcv3UF? zM;wQD3f}Q}C*qx;%#lk9;>ox;S?WY9K{#5yr{bM9wMFnw7pAq#nRt)sza4pJ;hl|l zJ>EHZm*bs_cd>rz(3^mF9^M6b=j+h^dkbS;h<8y%OHe+FhR#}yc_WD#G8aCA8Qhs8}X!#Zo#`*1-e<>+M}Pr zGq{Uw@t@w)c+Uj$JRpA&C_#&QPJf8xJ#Q#O`$hdpk#b&= zk6Ih?3f^~kui|}#_nP`@?{&O)@ZP|CQ@>d^a(2A8@ZOd#S9Lt=gzw_LkM~}eqcR`h zeW*c6(oH3u;VZmP%#2U*zQFqo?{f(TBY&?D?@J++-Z;VgTEFKn|GVLR6Kp&J?^{6& zevfDCc28`Rq4zu9Pk6uL{fzgEOtQgjUkS=b@bG>US_nbBKSV9wpLl=iHv_*NCyx?Z zz<==m#h(R#I{X>&r^lZ`NB4A*>^_NY_%q>)^WR9?Q9i_{-|h5g#h(LzHvHLxjgJau zy-KRrz4i?JIq~K9U$@DJ_od173xvLW>$$|A7yoYj`S1_ImoN7$hd)1l3%?J39ltMr zfBXgT`>BzBcm1#3#;*y-2nXN8cdMYFN$M+r#Rm8>egnVRd0r`3b)MA&%-^Pd)@K?kig+By;4g8__tLxO}55rggk8hWM zeDlNps>V_OualsdF=)47?)|g*YkJB-^Vz6;M+lfulqmv z;|+EHr}FAb$cNee{kp!z-yi<~`~%Iys0&s9;~$KFHvS>_$KoGqt$COX0sO=9kHS9! z|HxoAz0F&-JO0u5`cuIwD2RWYdJq42{8RByz(2{DCkh4|e~5oF{wX3ag;^3}pO^@Gq86 zvB{WK+(7!O481GxFT=lFREe;leyh#D5?{XPwWvuhs>9#E8ej6d2LA^9Yw@pBNiEU! zl~qPh5~e)(H&#sioAK|!zXjj6{ry{IL1hBB3(=z|<4?iAQ~2r=G`aYHnMW%69{g|c z@5O%u|33UDOyGX}2k;-pf6yBDAz{j6^dtEH!+#Y2v0xO!(S9-t{Ku8<$~=kxBK}kO z&*JO-fBa_z)fXxKoZ<8MFI1c=3jZbiSMguA*jK8463D+T_^0=^b9Z#=-nw}5vKgE~U{1{*Ne|r2+Bvu;Kgg?Xo0$&&Z^63BA`d{LI zrIG_L6^@J%Sr6z$=6_3F9MpHzUHtDY?GM!F#QzchZ~UL|{}OrppYeaOSlj>cZU4vD z$pv5izsdZmn*X!sAL=t1|6lnuH}&ZZr>8!H$QV7NU?opozW*sQ@{LeI`3p$F*{ILn zlboZHq&^q*KGf%?u8V&Yn^y?^gO$3h{{=CWe}S>>%DQg;%R^nh|13J?`_F>%{b#`% zb>06lT0Q})mgqpMd(?gE5p~fe%l<%$P?d{%lX@tU5)}RN_czpI>MiOC^;9_eA>2{R zn*ka1Tm&?&d<6KG6GXYpT-4jtx2E2qzB2WN%&-B}SERl$^?}qEG0vjYbu3fYp8#k= zrI+XtOHyBs`XK7dP+yAr(v?gvc3JAW_@A0kXzI&T*AGWbqfs zsIOzOt5RQ$x_do5b8%!Ka{#|_)xc>0N3?ikc>o~uZv7wM zlR1(4S=1%fsh0L+!&7>(r%^w{IHzkuReoj_W%Sw9Cz$4Q49^ublg^`lfpN|kG|3C8 z>z9A3phaCm{d(%v$hwUB)zmMiu6zE}uMh##Bc+>I(N$XN*HFKX`nCTtyCuEBbWRd9 z;zsJ$)4H*@5UfM}R{0|$>bFtVd{@r?vEJfQ59BI`V#=^j|=uVPf~xI`cu?p6hBS<1?taG z*PVar&vwO>e!h}7{)@e|FH?Wr6uv_JRqC%zl{e-a)Zgr;(oEm#rFzGdyh~j@ze&DN z{X=7Z(BpLdzr}t+{TJ$=QvcRuKBN9Q^{=SwC*Y`0^ZVcW*T(rqm2@?KNBt-2-&Y*! zKTy}hUwyPtdikfKEof6f{Wt2rQ~!&)UH+{9`Tu49Hc8zJ5}G5a<-3*(vQRq1pKs?B4%0PJ}W^I?-1Eg4GGuAXu}CC0L7K z?fi^|2=4eZ@34uC);Y@X~ z#y9^TY)-HZ!4|?Xro93LTM=wcFs@2fp()&!U^^4j{U3rI1gqG7Fa1Wali|+t@3iv2 zv;?~{_OuU0tvoGuouB(g7LKlPbJu!;5LGN2o5FKw^sj_U_XL` z3HB#AsFsMp0R#uumiw{h?NICgzgl!x?W#p;%kEBah+u8;BWkA)A~=lTaDqz-jvzRO z;K-W$Lao2_uA>Z(7Ob87H^H$4XA>MpaH8>#H$0*LZU2fqf%KNsj5wL#6oOMLCC;ex zE$!(9XA+#Ds=CV05?a%0M&}S*KyYp^Y68J|1m{c88TCxXG_eZ_E+V)@I0P5h&Y6B1 zZI=;TLvT64RhHcqCV!<04EfIJiH27zN0VPmaD#EKBe-6fV$7|=pG0so!HpG%;3g%s z?6(L(aBIaEvk7h|xTEHsTx*_C8#a&E7K863c$i>H&3}gAE~$L0|GN$EA-K24xz9NF z6FgwVgFXI3!KfXL^GL1Fjpgzcu_$&_C&Et?)iFmVcxnUGyg!bJtFk&i}JD$1jGz8vaJ`I}LH&e-KFP{7LY) z@&6LE7XL?y36U}z)6tm0i0P$qHMTJ$jhTew|2C}ZS2Sj!F$axVO>8zAvkM`TDnB8j zF{jc>%+=F54~->g%uAy`jrnNwm2WoJf|ul>As_k_?4v=c6QviRu^^2ajeZjBH|jEt z8q~Mn0cX%~Xv8#J8V#vu8XgVblGL>(8-Yqn{i_lwNLbMp6=}CoenMkm8Yzv;f-Og zBaAuHaAU(!COo>f^PEokgWV@n#_Sa2&ETUW6(#wlS+ zXl!e-+tJvb#t#2q!%j4IR>^K&yU^IRNAFhgX^8*dhsK_!WG}<;4&Lp0T##w})(Kwst zVl>X7*@wotG-Vi0pdpr=N8>sg=hL`?h9tR|#)UL4s<{``PQ6)ds2wAH<`Np0*A{q% z#-%mc(QRBNsGU>n>?g5T(zu$&RWv5*xDcioBP*`PH8ifREf9$YElxR-#`QGrp>YF^ zyJ$?Jal6&ejWlkeaZBxLsgs*)&im?x8n@E8O=8_8x34Xg^;<(~>5f`W2Et?-cWOgx z9Zso;UhwYP3hP+fducpK<31Yq*W@}=GKPlwe<7^4AENOXjfcff8jsZG-9sK4j|xgI z^~-;;0zOXTCmK)C_>_i>&1Y#mMdN8%23Wq&)QK6KOgS_!pWde?WFO8T2t`1dq^sP&N+ zoJiwGtqt`;KhyYÿ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

                                    Q?bw#B&=DTGMb# zOvLjN*NEpMo}UvBZBYmkA@{sX{#2XWDL_F5qqddSu zugLs}Hz6Kpnia8TWV*s#?j_!gcyr?Ih_@i#ns`g%t*q?|EgU8BHpJVSMcI*$-1TJ! zZ{mH#(vaomHZP%pJKm4jf`79a)vLL4d?4`�L={NqjJ|%0GnoP-}9}3#J8=!{Nk7 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

                                    =_6pS3Qe zbu+DtXkAY0Vp^BcGP62bm(aRYJ-cq%+Jtz zoYs^6`-I@Ag?4iMDOyhtobixwG1C(BbN2hl1X|D2dU4>44`{tGX0kN~&Kxm%@fn@x zWm+H8dWF_ov|gq4`Uu@&*m_OSUEgrAZ}xuO>1|r71Im_nM?ChY1vKheA++A7_2KB& z3ba0u$AL4|em@#G(@vcW()xtfceFmGrPBF~))%xsSDfWEaOMOdw7zssN&bq~Hv?x1 z`Fg}-)AS!A$=?o)pH+`s7_{W;OzyM&`|V8YM-pk>pGc;q^)tz2w0@!W+o=}BfHnPE_D zGNb6i2ga{TGP9uNKr$;y-{{4vB(sq~GRLUr70T=mhY7l? z(TEg?xi$=oDklmtsfGSGk43_W~c^*>1F;hTF&#_{^4B=hp@B_#7vifw+{ zCzAYw>_CzQNY^7-kaR_og-AXiS(rqjFG8{l$)Y6dk}O8DGRfj3%aSZXvNXw(BuiOm ztyZ8LV$<`#>k!;id_9?cZoNQ}x*7=TP zdy*YRB-z2{{?b>8vTkROm>(UoDD6tJ56NyMdy?!{iTDpAIb0G< zSE%}FmoWJ^$&r>EsXm1+tApfdlH*8@AvxBFQlSc6mcz;MB>(AKY#FlzP;HkcOjHd{ zCpn4a6ornC3HUiOl2b`eGhbR$(tfLPoaudn5BsY*;M{>RG>MGO3sSYc-(U3u3HsX7ou)qN5rAvsm>%=}Mzb@*;_xo+2qo9w#YDx>nh&8XPA` z@&t)n{wN4V(kM@pJV){j$+Ko6u8O3n>iB2!Jc*2d>?SLVkJa9nNZurQ8PhYbkof&? z606psK00}w6E0?kWNLadyOVDk4lJgAf1+UI`gaBaaDHGGwBSZGm*|{p{qU!O5>%r{^OEoC0&G6 zK@5;W+DAG&>71m)Naqm$z;KIxaF z8;~AFx*_TQq#Kd$O1d%WwxpXl&!(g{;3u^y0I79GBR6<}bW2h#`)^OWm44l68(n=r zgl|W>lM}c1g5H62N6WOHQD3?<=`L0->S)wSS?TOXx)15@cBUiU!|t3>^GWw4-HUYZ zfh8ZY{Ewa-xzRhM`?^@0^@wd$qIe$Qi9L|?aMFWFeO{3I6u>g!VA4bSqz^4K4>h}& z^f0@ke=zH00wf-#>qyfTDhM68u%M47eT4KF(u+usC6%w^NKYU=o>VH`O%Vp`)rq91 zke=kRvh^R6V;SXx|MWD{(eW@GH;`UU z+AH*H6d9Y*r`M5QuZIaNIMsiXa3kqWmISRZr+WN%OPfdUy#nd2q$=^-NR?N&dmFif z^iCCrJlfp|(z@y0r1z2DLuxy}rnr26mh^rTIBpk%mMsrDe2Da6>%82F^ik61NGFh{ zq>qtSNhg|7l&EBgWl>Y|ln*(R{nMzc?S(4u- z{e)Ci{e9ARo${WkV%gPYZq@R+0W~q?6F!lR6 z=@)&&r!oesIsYRY?v?E;(yvLsu{c91r{kp4#cC+Y8`fA}@Fep*L5Je!Pca@%*)z)2F8A5AtT+0>4BOjO&mX~<;x z<2S8oprV&JvgyfYBb$M2X0jQ{X0kf0RM?K#Kgnhxo7M716x|W3{wsrQ4zk(Fh8bN` zT9u2^n9WHx*I+p+ALgH$tdDGavJqq}k&Pr;VQ2IAM#4^9;BWXsWD7fS5r>PCEl#$WaQk|L=WK}~WT_sVY-utd z|2q$vzY3ZyKjgXs*@}NjAX`~D*(wfKC0oOh)yP)=i^nNzlC9;)+78zt+nQ`$vW>{r z>v58;PqqQshJVFh2-(JDTaax+wwZg})R+fjJ~aN%wsfbhdUA$bw;|is>D&E9C)~ON9$&Mg9lI-6$BvWnC;V>Zjh>=>Kte0@;aVIt}FGOrJiGokDh|yPoRsG_upl&Jfj$^J`r5!uCd_o-|(6{7aLvrEV> zwb-iiD=VawvdeA$l3hV|1KE{i*N|OBcC{V1@wVgAt|jwrqfDnY$5{~e2Hf>zHS%s`h7FtSIepyRQ zBR3vqaoev!7L(QcqOrPYj;tYstZ7dAq{psM4N1sSvVtrlYm?=JPMTU;_I9kp(oi!~ zpihu>$sV_1yiE!1Bo3J}_Nl&AXSC-XHT$--JVP#h^(@(sWY3X(NT%xgI@t?kuaJqs z9)Gt%j%D4;KC^y@>{YVYq>!b|N6kB}-BO#qK_>c}WN%pk$w^m>WN(|HtQ2_mF4_B{ zlfBoAFxdw-WKw=w{(nR!;UANIM)nC=&+^CTs+N17lYL3{g}tEXV+^ahCh9A)Z^^zU z`^KhYddXHSmUcjdRWIvJ5K=w2F6lA}U{Xr)E|J$g;tT~YVZWoYB zg5}bmlBOj^w zkdLr7s4AiAnvb#;r-q#Olh19NJNX!L-6%VmoouyfSDkz;`8?#~%&I{hq9)oCw;-R7 zd?72-e17-t2MuyNH9)?g$y8RTM&%2WFG9W~`J&|79e$BqyTCru%a^dOO}O-l(U&q@ z>#_MV!%)$=4-cpL{*@D^5$u28L@eW?nWT--LW)qezGrd~(_Uc(<$G zKNrJ}`XyIBY)QT?`BrvV=GLXiwTowfOOc?--^1aa4)-D-T>mHEhkRdhSN`IZwlwAg$Q8E($&V$M{ipAG=L$7@p6l6zl}`-9Qe&}!tdsaffHhvfbcWbXQ36cu0N!O;ubx)XV7;cBfW z&&l5=Z<9YwZpxp$a9EN*L9UY{YAKIf8eIq4QxW--4xh5?S&aV~@>j^8C4Y(hIkjc- z=N-O4{-V_%y-s+RZ>Z%jn-Eo3ogB|!C4bYszvj^FKgr*)(S>fZGO=$Nvt^X<&&l5< z|BU=STNBRTC;!-aJ|O>){3GL(4v}B&@0b_a|GY^4sp-=B{%bl`3?}mn@-OuQrac^! zTPq`%{g1De<=@brkNjKOQjrac?& zS&U)=tw*q4(r~zf8)4W($F3yn-N6{W_M43DZs!EvC z7}^80=W%>)+GEXWFb=M}+VdL4dnnpk|KEW2KWMLP>Z`o~?FDHsMSCIIi&-^jFHCz8 z+KXC;q6o~T|DF8*vrdtI}SB_G+|Ox4v78X3I`t-C280+UwHxH)z^x(_Y7x zAk-w6A8m*_<@S2C*EfM`-Kv@G4QcN|dn4LA(%zW%*0eXFy_u(OQ;VlYmF>-GZ%KO# z``up`4k+PU8NS~No@v`ySKi*1_71eSqir`o+sfWihg)sjazE=(jA19*JJa6H@m(D5 zssmzu%k8L-_U;xRle{PGLul_sdw<${J7pi*_VO3)eND1DltIn|X!pDvNLxq1e4Jn@ z8al++KG=u{sn#7z`&inC(f+p?eYFqws(3`-i0vFXlJ?QG-T40?xA|-zV`V4Y(taH6 z6KLBLFtqJa7|9&9{4|4oX!|7EG3}FSPw=cb#o?*6Pow<^?bB)BKwAa;mrT%*^?eiTu&xpMXNZWRIrK%$Bi`?mA`yHfQLi-xpm(sq{oi3w& zIqfTYcSssf?=s2uRkW|}d2u$s|Fi8^wzl0eP4(O&b0h7$Xx~KJuKJ{XGwoZ1*hQ&r zy8=`f!_vOpo$jz-(x{DP>%9MZ1Sd53m`jv8Z=_7}8U zvpk!54{~eduKWP6+XI^9IOh#vNOJHXThf~s-gU(cRW}-8-CB%59aX78R z>F7-FoHGo$&S-0oCShiy7%~f;Ssj^eh=Tvl?r@kX*g?)Y>C8o^pU!Z1>Kmeupfhp^ zALUM?ZG~{q*BGY^IGo$zSUU3zxsJ1HHpn@jyV@&Ybo}ULX907%sid=rkIwqaF?;Sr`KEDHXG1z$(AkL2X3nrNolWQr4gSyd2c|9Zn_K-fWJ`xz(a|oz zJ0jBAhK}9*VBL^LAgXnp?dcpvX9qgMccil)ot^0HL1$+=yV2Q&jt%}t9rHmidUSTT zB;T>MjSM?`(%IXE>}5oa**g2svBiJ;Rac@N>dyXj4)SOmK<7Ygst|gQEPD3v2}Yoz6+qCROe_qC(${^MIGz#I65cL zIi8LyeUed!o|1YC+ zx&5v-v%6kl_*!4lk*M3~TutW&I@i!qeY=*us#gjdX6I za}%AL`+piXaQ2J?XRk4E_CM*EUB}3sv@D=NZ+Dq@2+GQZ&YhzdUs)y-bnd1ziHIuG~j5vvhnW!GVLCY?v=Jmw4&`uBCKn9fA~nkk6! z2P#uK6^U}5s-TOF=sZrRMkjT;a#aPF&}q9q({@G8_Dd=?RXdW@PN3`h40;KZU>7$^O0LzV;z%vRcqZ} z()pT>YWP>yN2n57PoZ4>hR%2H<=ehT{$~pva=LbHIzQ0)jn0pBexdUdouB((s@WV+ zt{Sg){QjR_@7DR9VhTEcP)tt8u7IPM%$mQNRc~ymb*f@YifJjP(zu;sYKm!WFGeP* zC)#aw>h3Kj#q<=TC}yC5Vn&KtC}yIV*?KW`ow~H)&CMxhrI^i|)Dkb-vpb%AiqRAcQ1nyGM=^$C9*O~v z|J)Q~`zFjgaLz8`960AZ%b!9=0A8e+cW{nb%uk`M4WC_lGAS0MScGCBiiLaft!XRg zi$$#n6r!bZaf%%%mY^`heu^c{D6UwFVrdFpO>hy#GV(>SY~NbvSv^$b$AuItP;5Z4 zBE>osD^aXYu`lj3ft-!T8n~?#t@kR$%M`Csyh7oj zT^sXketNqdawj9gdA`GVp*iZ3a?_UO4uSmC{ZW|YM@mMDw% zw|36Xknbseq4?U9GUQR(dJLQy=Gg3}PIUVKHl+#lB6>w5qwhwZK;%R&FrSAolx&>k~lUB|| z=>z_9W_!@ElwINtrt75Q%GoIW33%nRUL7rmnZ_vRpqx{WkN1uJALU#~>!_KR!+RBs zas;Iwf0@QqvGRQ7mnlb6E<@Q*xhUlr%6TaVD0TSPmwYwdP$Lg-qv{3sWwlTW$u_nTkS5@xJlSfeQ!sC}y?n?J=%H8OWq}-iSxwi-9nUs4{?%lVmD0}rXhjJgv zBPsW#Jd|=j$^*R=_IG%I<(|%Qlm}7zR%2o|G5bSV$U4eX-RXA5oN-P+mcKDW%MQR3|U9xG6>YR-Q@rQ3L1pQK}By zL3uT$FVvUUIOnxupuEo7f_YTMxS3Mtzl7gp-t}4CxP|i8q5Qv%^7bD8;3%k6n++)M zqI`@}h4?7tJ=R#P?c8gFz4AVX_d9&R;e!qzqI}pmjpq?T;gl0Bb1wQ$&=xSuiIkK6 z8e&maM5nA$)+m+pkuhrk=8!U`^qc(4x;CsS8xETeTMiS4sl&`+E@;Wo@h(egL3bL; zlJaB9F6C>Kk5fMH8S(_>lawmlrzoEu4A=EG<+GH}nT^V@fAvnLQ@%j?GUbb2M!NiM zO$$QMGWHcp?*f#!nsSz}Q@%s_2Bj4Ko1Wyi3aC3d z-Kpr>{cpDK(4ErgTC(oi7O*W<$zyj~i+OiCx@J&EcY3-r(4E=w8R^bs(t1yJswqi> z8PBYAXQMlZ5itm zI^BM{|DZdD?l`&wy%6cn?efRcoyRDeN_b4@&g=B~9NLuv{o%QE7ofWW-393`MRy^( zi_=}0?xN1Kh~V&(l$OPW7_PyyrD6&7Q*@WK-pYQLcDM}P<>)Re+&(q@?(#xxx!!nu z|F^pm-IXmT)x^4X`9IxN>H6gln#r#9)$r}Uqq_#(t?8~wcSE{s(OsYJ+H~zmjt!@~ z>q?qO+w#+hCVT^1iIVo~Zsflk)7^~jCc;%%bT_pkv+~-U?v|$Rx?9+=LEdH1*xkwy zo%iW(Lw6Ut+q#79=FPboSi}yedoJI&qG-P82KoNSok zzqe*yzWvYXQ>VIAl1frZDwWh~^Z#&ZNxkB@(K;0^rgj=y{Hlv7oQ~ERXq}DLnP{EW z%W?8`%2)NEs|<|oJhaXy!l7ft9owMAbS^||VmDNDwf@CuU5eHuv@Q{a^A|T$RAZN+ zbp={B{-Wv*Q53CQKvtEmfZ-{x^YmR zo6x$22R6{s6rf%s{8r&yrz`I5Xx)L<-Dus3)?NK;jg@1W;{!PrdXG*R(l%(_2ZuU* zKb#?GF_(|gdJruKt%uNh0j-D8dIGIS(0UxLM=j!G%7RuIAyZRQPol+oehRIpO${ci zIW(R;gO+aX78?)Yi1Ycre=yl8#M#A+@*-NVq4g44FS7-$242x=1fKtCy-H~9^*UN_ z8}l1zy@}Ray5Qyia{mw21Fe?2Lr)$Ttr9J&cZ8OYR)|(0eliG7XGz6qrD!E`4Nasv z4O+jg46R%%4&FD|>Y!Ehey~DMpgK{7)_Z8xXz^aqo7HMuEVKsDdIv2|8FWi7CK=ZG z9If}!vh)A6qgo$ojokW3W^J$XKY=qNTA!lz9a^8E^^J+dSYMz;ZTS){e*gQ9r0uR} zSDFSbdc79oubI*MR&+D-(E1(@gML8kceH*)>ld_sLhEN)N9+Q$XLct2SG0c9Y@)31 zkyp0$2U>rj^{05c@;Fn$nR?X3JEb`sH~1y5GcBAM;7ljU9lIW%%=9KPU(Wl^OmOCg zGc%mo;LHMNRxPsFyScz&yF0VPnG?<&l9RCr+4Rm_2BErTMe(4ZGY=e_6rA}~Gvb+l z==keoI5~ZAmWDGF&L}u`5Xc!OO>l-A9AS{309+vL;vSH*0GvgQI2z7^a27FqAvoOH z&=sbFoJ{GNbadf79Byk8E+H?Ivm~6QbPFYo3Z*v5g18GzhGh+oF*p{^a&T6Fvpiol zma@M)E5cc+Z@Ftl(fs=&oK@g#4rf(3o5EQQ&L(hHhqE4>HQ=lbXH7V3sZRLM1y&bN z13K%#q4R$ZKJ)9j>f$YdkQ0eRhXD>KA!`U6qE^v00 zghQ_#HL(R}H$v7S96SHxjE6HpRmw%Qqa7dRPM@K(Hyk$NK5!0#voD+j;Oqy-c7C}$ zB%505KrMj{*XE0JFq}i+{G)e3j;cZ+o1O$HO^JCJMES7K?Mj$UApuwGi@wa}u1B;hbW`blB*H*-7D?reTDO1?O~S z@zhgr&V+NeA!kWjD6=v>2hO>0&Nuu#jpwXw%A>Zv5DueHgfj`wMR4eFv!yiaQxPwL zbE#TT#kovTxV0zza)VdExw7xMleENR-x2+4xUBqZ;P`N^h4U~of@5caoa^D-0OvM1 zH^SimUQlygO{syrS>5EBb4w~BQuOEAk{XrcnQw)aCqm>%5bJwH-AB)teI=3mJO&1_!RCKIG@4gSo%5K z>EV0kn{#g!31ipWysvl%Ea$0_WHN zpYwM(f57>(7x(WJ+^OJBElwuxP6Kz^zhlu*RAad_z@62Q8CCP{OmJr&7w&v;d8!gW*nqyD!{5;qDE0FWvXc zQa`(t1`~H5jVjMB1(%5&2zP(D2mBuc?I`ykF>?+T^C55#hs%mO6z)HJY|niG_b{c# zWR=blaF2p}WH)`?maIgM)~k?X;hqWiIJm@*hkK$$IiZ^c+>>-JtH*f?+|%Kn3imWo z*1lE`vru+t3~N*hxMx}Pv*Dfxmo0Lxc>b?w=PM$gn<&qPaBqP-5$-i`FM@j+T(-g` za3}S6_$8)$>HpyWf_o+0%i&%jBJ+KI0ui`Z!M(bR^Oa)2y%z3`7IYol>*3xYe#+3R zjeqMKPxmIcHxCEsPDO3dI>7)MCmMivYK91ni|Xo z^XkBT2W|nkhFij|dU4rCbZ)y1+<{&}tSoTfh5J6-_qx#;1owj;gUa<0+@Ilo4EMiq zKY{zXF??zj`x#|6q^s>;!u_fjk#!KaKC~3oyGcASujXhe6R8f`O)A{y_%Fh z`~q)UxWB@i3hr-kIUxM5lJ;uyPq=?|<0>gj5Z=^E#G9sbtX;X_tqN~lc&ou%8{X>h)`Yi)qA+FJjow`4FJi$rgvSASBY0cG+Zf*F@HQFbx1C~7Tfn;ilP0>i1w7jVR!O%SzSU)x)Hd*T zfT#06_PQNBI|9VlUSW5Hx06-Q&Ju=1Md$dxyCJ)2yGP>fVQ@S=w!7{BinyneRqgE! zZ(n%(=+*WYWN(XMe@*?~0q_okcOblj`e;>lXI}4Mc!$6h3GZ0=bHFHcalNfR{+oc z|K*(uPapqk3g*-)$K)mxUh?R2*ybIx73~wSl zxxczD9j$RZEg|{H_+(t=dl@{&{TICJ;9U;yN+Vt&Hd?LTRq(Fn*Ow!vYzyxi3$k-Z z+!$5uT@UXjcsIbiQT%k_s%h+I1FqV|@E(BopuVP|*fia_KJ^}k_lTJL{viraVR?_i zdj{U)@ScLlCU}yIM0p0-G`v5=n+)%1jS}RSdGl|0Jp4oW9K7dM;bdb?(k}8|fcKIK z`J#vt=4E)V!F$DGz1o#fi9f+!{JJ~_-W!sZU$uB|!JiS{+wfRFtndt83tj-vf#>sV zGdx$`TF;Zr>_qkz3B1tSI)ayI_sxqH%KM)^_L@|<8cPdRcsaa|)_TGVc<;a~;Wh9o zc(v@hQ|+HwT=H{apt&2ct5~AX5p~KzBZh%-|9G`_cgq4D0Y`_iox3cUM3vR<1NO}AL0ED z?le4YO! zBFKf9KOOw(;m@E1DPGsJL?Qi|B%W_aKz$tng+Hso*$mndP!31_obU$-Dsk(b|R42rvH<{Dt5z0)Js8*h|Omf2RJ>2jVXde--#kz#j{LN%+gaUkd)x zvc{PL)nJ=X;V%n+jK(qA?VCJA2PXXG;I9aOdH5@6B}iuW(D`43zY_eFDUu?x(l~SZ ztHNIw{%Y{ogugm`8~lg=V?^tpBkuhT{#x+Yfp1FydUDu;a^|G)*Mq+S{BiKtSK`b< z!muD4!l&_nkD7osyvn`_{Qcl>3V#Rqo5APC(6#V4w}Nk>SoB`{TfyHJ{?_ofkz0?t z%8aNI{&w)Um%1{+mG#La{2k$shrbj2-Qe#Ge;4I0`mV~+B!s^^{5{nA?Aq+DQj7`k z_lD0_+e;L3G8UI1{yy;c9lp=p5`%Qr_WtmXhJOJ3L*X9?{}A{G!9Q3nHTq&1^Bg67 z3T8)V>3AVsEq)mMBfzyhM-sLm$0+|u_(v%hHX^g*eyh*%pK=}x{{;BQ!9Tv+`Vx}k ziGLz|{RONin@V&F{L|r|3ZFk!v3+FLgX-iA_-C>Ss`?oE;jG{#wX@lf>RmgC5(%0cT@ZW&{HvBg&mOPWXD_bf2 zM?X~cTN0+n@4^q^dn^=uAAX?CT+UDog=LT7Cz64eYjI5^eby&n@H6=D8b!Kx2)8PXUWfd4N1cZm0Gw6Ia$BOI~u8is!$x>EQM{zvdXHT+|PpAZgP zgpzy)pLP2=d&=-Jcaf-$sVH9|Scy#opJU+H2B9L3}hW<)TPvglfU!7K=7Q#!${oWj@&2xdnB1alym3&EV? z>`o}`*W8f|=B6o*U>-HJ(GkptU?~LiBUk{z5CkI-^dT6IU?_rqwHh^+14}SWh;5Te z>w=L8MoH6-Sh8>NS^BQo62WK$iz8SN!NS_V4i-}JH664NEP`NB1a?Ig#qNK1$X1^t zSOUS4eT!Y8=p0ozt_Dk6#AOgHk6>8@V#PVQ1OMx{+Ch(Dmpbk!P*GcKw#hh>TBo}f$ait2vUWwqg?v_p}XvY^>mCX7^hnV zubmpf1_(Asupxp?5Nw3Ne*VaFES!kB-U&8Euvzb8aH{u^j}UBuV0#3tifs^VrH&SC zO{k~)xlpjJ5w{zB-Ju(bV8`BvOTo@O8-`#P1bZUb6~P_|D3#234VXMO#{HvUJOchA zttARa2CmA3y%6k+U~dHbsH1T_;ps9Jb3X+8i;asfI>c1i0}-5s;2;D?AvhSp;Rp`V zM`Qtm4ntrk(1JrvKh5pWJuHPDAypA_B;lxw*qT(xQ%FQ`41!}3oS>gO2ge!ZcrnxV z2u{?QvEU>G-2ZKf$a<6JoN8>RAvj~`*255-E;{Y@;7svwqExbHBe)B}IS8mo=OVZi z!FdQKA~+wxg$TM!q!zoXh8^)=gkX{>|HXaRKB07;nSkID&9g!-LvV$Lqu^f%F7I1Z z`woJHITy7MF4_6HVBi?4jn-ScC z;8vA_orRBd<8}mh>UdRf2O$kS$w>>725N9Of_o4Uxfj6$2=43qc!aF4;C}H4c~DJu z|4f7k9!9`#z79w5h=^SCazqLqvwR=dt2F;fgsiBi5YB*LG6D`7Pa`lhK6nPfvzp%n zDhV~8$nyxMC?kF(6}+IWUQVOIO9)<8M`JT-f0p9CiXcJo8Un{+y^i1w1pm>G@PaoH zP_VZ-fb{QfV++?8fz}!t4G5Ksi@-ym9e)Hq0^9#(y^TCgi|!yo5RbZO+ff&tMTmf^ z@*V=JE43w43bf?8_6#}*8UzJ`%F-zn#IDLYF{stVw6cN$1n+dGR0MYJnmp2n_Yr)B z-~$AoA@~r%CkQ@5@UhOf^N`yfZ;iT$XKN|jr)n=DpY!uf1YaQdQtitrfa$2+nbh|P zIR3NX-)I75sQCH+N1CAGvDY6A{wT_u2U!LFjNlgpe;NKO0&WWMQ~)dXcLaY7;(zMC z_bxr03L*Xf#uHA1a9V`Zi$7Fz=u&syD6ZET5w3)ACWH$ioEhQ#2xmb!r_O?gvl^UD zy&&XLVh)6MFC|M6&V_J3gpfvsa~qro;k**QJBHB6oOrj<_5WXleF#S)9ExxlLQVm1 zaZ6or{e2MfMo_{L-M9!x8UF$ZN6S)G+y!OmcCFTMVT4N{Ttv*0a8ZMcAzWP9t-lZH z2$w{-th72@%0ibm`Ik{Bb5yJ`2*(<-9Ksb4F0Y_&s3ci&P_UH|ZiR3awSKrN!VM6v zhHxCh)e)|(!7y9{;hG57A})ETKgxX_g#7a(XWkbPt|!+PZFRy+zel*fb|JY*8g7Vi zQ$sdF$o=1&5Yqo+*F6c}4B_T_<*2Kyw?Me161Nc#;noNbN4O2b-4JezP^Uc++7@uQ zy_A`pqU?xpCxo>8|BY~GX_1I#|C51hkZ^Z|dn4Qf;RFjBuQN`Z7DM~uFWgJ%*isZB z>*QdB`yxC5;eH7B7o8Rh{q-u^YMESgN=LkKzY&w%jZzA+iX zM+kKwD%ANOgpU*Ik-|KQ@L7aUA$$hmWQ(HxU+Fm+cs^D#Jcp1P{yf4LY#5m$o#zN< z-5=pg2wyR_m*ss@J-ph#b*Jm(NBBCzUlG26@Lhy&B1{mzh0sO#HbOg1N;52M$$kqR z?dh}B8V5Xt{)o39vaTH<)R7p3asg2lt=(h7p$AZ-+6YU8tc?!BjG+ka@*mD7k6mD# zg_=^DRv~Omb8590ZO+ine@ftY`mUPT_v{3O?;-pi;rj?bNB99kw!XgqMfefI!TTRr zIV?WiWud+TM%cXthyr|pki;($evR-eggR`aw(;s0g;H|W#&3vbLa2{_5&nT_8iao$ni}C>Dpxd>3_M#huoNSH zK1OWOw1}odGy|gPWjQcqLOS1~8Ksk5wTWg%G>b9MifA@{wMRO$i)Lr*>!@KgC!)g< z&4p+kL_o9*qPY?ATIP8W4Mk+#i4M8-&O3G?sM#{Z!*b8>@FzQD%8qqLBBM}Wp zG(tMVwL|xR?$Ibj3vlpclo7}FKKDknAfkn2bF%of_o77**$!{CC?fSEM2jQh;!z)5 z$Y!9DM@u1ETKxQ4;h_g=+-2-2d`dMi`o!GmZ! zOK|&97ky&Lj)-;=19dzCJEHLxx<}tKn;J3!kvu<$_9T=}D&@Tq z?W4ld7L{J@hlmXOBRYV;lx6ye4n%YaqJt0}%vj38MhQgb8MeNO=ukvD{G(x+6wW@; z5s1z~bR?n^5glcj9*yXDL|sKa)+omj4yJa3aC=2`5~9w4pbSaHB?nFeFAi78+VRSJf^XpQnXyNmi zH?2FWv?5++@L&4oh+pwUS0H6CxDv@;h^|8XD59$o4IsJ((H)5BD!Ccabt=B9@p?oz zAiCM`8x7h79o;0mNDQ|SnyTN5$QA|B?Olq6-ihc{M0X*2$VzuNBAR)sA4K;WywBkM zh#o-1!#_4|cWXdA4Ga@A}2u;%%QKR}ci5*oC*Ac}kQW7I~JlM|7?>O#bCaO{hZsAK8d{SV#L6j~Wi zZLk?k{~g4~AbJ;ZKce>#{fg*)>!cqb`U=s9h(1H~5u#5KeXPM#{g;~mZ{cjX9_4dH zU+74QLci1`rSX%E`<+Jk=xbRx(Km>`6+dSLv3-w-s-;f=5dA26-#=uS>Gfwsy8ma$ zmaEz8Z;0nb^gH6I5&dD5KMm^sAH;V58>7?0kEcOA17h9(V?om)p1zB#MdBF|&!l@E zdZ9BL&nyOKH8`8W*$vKNa884B83f|F5znJmW(pi|dg;uEcz$CTVzAGk9sVZ+eGc(3 z#2X+Uj(8cwBM>iySXV$J<{2%@zX0L|5sy}ta^zWYk#5~0UKsIGh!;V;1mZ;zFOHb? zrjMHh$;mz&FUb_dPaRfLOLt!pFN=6}EAkk`V-c@{csazBWO>9ZsNd00XFaG+Rzj?i zUn%h63OcswAhz+JoZ?vn@p_2YM7)-EFsb~prU1n27_=+k_ysZD9U_i1xIQ775pRfi zZ^RoR-cD|fcw@wy7{jKBw?Vua;;rpcZ*GSHpk+yxjE8^Xx;f%#`Oq{m=8;?iKGEPvXu?0E9#=47g zu-^7T%xc_s*s>oZ-VgDCi1$Z)KzCg)Hd?c4(1Q>kjQ9|JaXI86O#~cNV+wXS;zKp& z$A@W+VCy)0JwhwW_(;S@A?BpUE56_At@7ey5!(nKYYISoyulL?pNQCo`uHTo8vKoZ zO0PuX>`eiP&p>D|V8}U$&qaJb;`3BMK6FsfnPxcR3yo)Dw-+P6 z81dCcnS}Tf#B!}8zSQ7lgxU#>FGp-WG`>OIEPq|!co(r5OPmd3;#zDP(HBBhq2ntK5f_Lf#EA}C$FVd)>9bGeh$)_q=TQ%J z+JWowxFZG{v`Vuab@5V$REX;?&LAR)2N1K9zHj)u2K7fsF4(nZk3T^Cq0%wmHR6wv zOpBPM_!;r1h?(+di0PJWA^yDYj3>F=(267e((?LB!mD3=jrd2z-yr@D@wa3U{XWY4 zJz`zzMO=^M{7J9fZ%N``5dUGsUlIR?_;>XehIXNt|3v&3_-_Ro8_86C*NhZ%GL7sm zKJ@D*(;=A~$@EBOMlu7Enbe5Mj2h)=o>3p@3QJ}|GCPu4RbEA(O)GsCJedQ@TuA0r zmGT~JZVYjTO`uVigIF>Tl71xfA{l~YK8+#C{NiMq-619EGyb8cy$*GN`IvI>$NkgSSidnBtN*#gPx zNY+BK29h;Zc4`9uk--5lSsTeZOh?;KD$%+~)&Zw?L&D1+x>clkLAbKExg$Aa zFz75Jp51*l{9GhgSR&^kIUmVH!>M~0cJmsHcoC9|k+7a8A-NRECCb9&L2}vOsab-T z_g+;-E&;AW@^4G?Y9!YnxgN>2-MC1u6Vj9O1|&E3UKR1C!IHA`-h$*dOTk<~D%9=W z6fCgFp-Vsvg~ zn9ZMLNIpiABVkAASo#H$%6Q8ENz?5AMD{-txqy(oGnm4ANIpXHzFw942S`5b2K^0@ ze1hb^NIpgKB@$iHgXD81E`*yny_E}+uS8TLUmN^JL3;fb$#=qgp+BI#5RxC!=8*dn z+8nNbM)C)eUk3SqMe-Yx-_=zu1|g>H!U zMrd!1_Qq&$j`k+vR63iYy;&Ex1h+7vz7scOD@iW4ZMZvZ$hHQzLwox!#RS+9?fuc- z3GH3AiQL{9?On(w2G!wiXzz*k?r85JqA24HP9W@3EX}>p=J>xK+WVlrujtI079gio zo*J5WJ+u!%`@p^pqW%NOc90lo^Ry2}`w)vRx1k=Te<<1|+J~V{hQqBYjzIecw2wsl ze6)|!5y$q?XrG1lF=(HR_OWQ6fc9}{^Zbu_-DyH_N^PHr_DM1cv{RPq!>;xzXrGSu zsc74u-?bPSXE~mM_L*`WldW4Q&in1N(LM+5^Ek($eXeFY4tJM)!6P(iQ}HIDeW3=! zwx$C^E<*d_F0N6reF@r^Y5SnfPXJg~eh_Ufa?!pC?Yq#v8SUH9z6I@DS!9_a6h>*@j`khHmf)$It|ssb zxQn#DXy1+YeQ4i<_Pu?N{3OGP4^Kw5???LqeUB!;?mbp}525`$+7F}6dV2)z$I*Ti zZC?JP|2hduubx1g=Rb8GPf+PhM*A5HdRjbcg=f)zPE9~JCe!ChlJ*p|Uq$-`v|mR1 zMYLa1rffL+)2J|9n6zKv%~zu?-WBcF&}J^Lqx~k^5$!kBK;$tY(S954|L6_}MYP3Q z+tK2;?V`<5ofYn(?dvFl@W5b*Hpl-ig)_okv_D1rU9{PK-b4FCwBMH*-~K?=!iRl98+O;C{W02~u#M!U zAyL}?8QN5^&(Z!0?Ju-2X@4m*__R=V{`~(Mbuo4RYqUA|^CS#8zm@ga{toT$Rcbaj zJ&H8j+CQSrA^j(`e@FXgw11H^L8|+!?C17xY64oVgsR~`lm%DW?Z1%Dh}2HKrc)!G z4(T*Vr@iP12c=>hzl7JU>Iqtm0>m>Fh|Qhr^fO73rKv zM+bFc zN<*eI{*eYp8C(GAf`*JHWY$RS^3QqKGvXrXFr!70?TvIXq<0}*9O)8Tr%2i*k#2`{ zDWn@BT^i{+NS8snoUGz>S)^lzFZw9bvD8~4(%y7=q-!8u0qN>US2VVjbdD=s+2AS$ zS2egAVc(o97(9CT*rCSEUx0mpbS=ZzCT?l2i*y5|>meP-Ghh~1|Nl}H!bR>h=8ceU zi*#e8TiEL+NZCgoN4i<}*cj5y$=o}HmTrl3E3#SQTO*Z`uh2n=bbF+iAl(5e>uN`& z?2HN)oHLu>zZH!1^CeGAaX3wbm`UW;@B(*2O`iF9A2dm-Hi>E6-} zKHbF;7rb1N@fdcxKhlGd9)R>fg>s>k9welzpXnj;bEb9_pBqfv6iW|9dMeVxke-0_ zaHK~e)%hQ!N2^dzLGAU#%{az zq!%Hbs6ItcAdmN?EZQX14`X$g!|A0+Z$^5V#rhZ0>yciL^eUuRkYdCurLju$YNXd1 za*cv`bSE|cKZm;>ryT)GZ$!$zc2nPKWacT>(Q*!_w;+|nAL*?IZ_}95i*<)l?$j(S zhP#p8qXtqP-iwSie;?8>k=~C~o_VAXAbk+&WTX!veGKWt+VV>uL28%(Fh@SzX-ywT z`V`V9kUrTzj;&*Rl-!QzL|{BfpGNu&(q{+xpVL6iAns$PQ;=pzUqISI`XbUdz@LMp zuOfXJ=_`MyV7a`8^mWGKT+qM4=R}aciS%uxES)6jUz@qm?eMo$4pR0+7paewwQ4-$ z*sTJj-1#*jO+utGQr--}1e5-?&NqZt!2O+0j+C9WgLD9CfwVG)vTt&r@j0!LHgY(u zzu2(O9VX{HNZ&IaJ3p*s-&bnn5&j|4&yjwF^b@2Xi*Ax2{S@hEUHtD-BmJU_BmD{) zkp36h)JVTZ`YY0Jkp6`9TckfAWjFbL#00m0+&fHR1pg_ljDFM}=wuf5XQaRUZ^}r2 zL;5Gu-$fMXA3A%ccI5u=X~;PKpQfEq;nN_S1=+O7W<)lfQKm;WLr=nP?PW6=ornKV z8*z@>0@T2(%;tLcF49z zwj;6~MCU-Epq+&C=_mg#M%fkFcx1a7zPrIa#LzX;vI)W!QJX=?_R{MMMUiy0mzO*cA#vP>>y-EAUhb@Kam|`jUq#$@4hW$IcJAzOv?^KcDRP? z3wR%(tUhE%YVf~jXW5w;zpJQpJ45IPar9mp<1 zb{(>dkzIjo60*yXU4raV-9E>>7@cm(>|e+(m$6C;ztB-qS0cLx*;UA{R`26&!!(j< z6K29udMVnCvlRPgv;VN?)Dg zNn}qc_pZ!J@M&c4A$tZ{j_g@vA+qO?y^id8WG^F|g6u_$_=5VyBV&>2uTR6Dy@v@R zdj;8R7W%3}+23vAK=uYQ>cE@G-bSV|M?F!wYy9WMI2PAI<{@(x%5h_z!;$&OtTR6R zWB=NtMdyExvZ!wvmLf(*HKV?@<%r;arg-Wj%Y@VW&C4I89vx%_vQl-$|5j%evIbe* zy#|>!JhA~~UEO(`HWZxnCz!Lco+2_bULiQ=LkCA<%9>fV+rUkS3 zOl8!~04&`X$i7DQC9Q zvY(Lstlcs4@E1x zPm6py;r!nUm2Ih`kk5d8X5=#>pGmSYh&_mQRBngoNm*q$^4XBDgnV}7{mAD)K1A8& zb0VM15Fnoy`P|6oQIED77_#5W$mc^ozf3z)Xr&M;Rv+@AB6h9xd>HaYku!@?$VYVP z$VUpf>RaRsAYTaiXucv}P(&uerM+GkM!twBl#o!A#gH$Kd<^m>kS~pVN#sjOF=#-t z6HWRI`7+4u&%Zp%PB}Ta_{#znp{@=f%WS*~w(qytK4bAww9@@$EGtG?B5&@0b_<=Y^iAfr9sR%(`SXK;Ju zw*Q;&sDg{W6Y`x0@m-MbI*9LvT%Q0SxBcIIe3x#NJ>{Rt_d^1YFtik##B3CQTqRet_iR9Kj}*+8m^7y@{(14?!*ufV!S^?@;8_x5JPhVVsBe6ocI} zw<$n59*z7sOB8u%q9NS+7)yWmiOQvw(t5Lw+gp(~)0{ z{0!vhAwLuO*_Pv3V&+-C{2b);{BWE29!ajm&qsbCa$9#RyVsCUM1GOB!zh3?8S+WU z_2rj_0$wF*TdT+~L(b@I;>(d=h5QQSS89G&cmBR_$J(tVOTw4Ihuj_mM z3KQuDszzoex(`t(vDBEx(sFqcf2C{mA)s0ITZ(qd%xs30G?Q!#x*S{>X@pD0&PQ_dkO{j=j3b3*_t+yxxs< zwo}}1k^h4HJLEqi{~q}d!$!-JQ7m=gpOF8o(TR;hby5nyB4>sFhWvN!1I~Maet#o9 z`4jnHn!)Lu?4HE#OpVSADpY40gVUlj9XkB~`=1iPG!vZ}(U~2cnb4UPote>@MRX35 zw2_!sXEt8oCT1EKojK3}I&-2kSKrMW%FjkRJ9}qtbmq|o5iH=%8|sVl&U`4ILuY;z zE1)w3#k}bBq4O9z8voJhM`sUohFSW0-fd18HvsUYVOXcZW9$qOOC^ zis-D4&Pv83_Zm8@ptG7uvT9d`zactn7{i+AtVP6l*6xNHeO+|c>(cFYeS;gIvkN*K zqO+yFZiLRp=xk>ACI&Ypq+sZ5Zm(N(Uk%?1ogL8O&tFWpJKLbM?I6CLF>K#0k>NWU z+zFkX#nUV4u14I=;O_rJACJyy=uAN82z2&DXMc3|LT4Xz_Er;98KepOqO+e~Ih_$c z^_1N2odYcBKy(i3-)A>;4mNm*!G9S1r@=!F9%k@xLdG)uNOX=x$IgFtjz;Gg1zE}# zdK@|@pmV$^%v9N(h|Wo)Ce2~^$;NYv!BZ8nm*(l{T!YRT=vNZ7r}M7 zBBs8{`Hzlm1a#;tAbx?t3k^;r)WZ`dor}?-hEK9$USdgIYVb1UNWr=lBH6A$=PGor zB(WQW&edY*Nq#Ll_oH(iI`^V;Jvw)za|1fJqjRI>XkOONO@`lWQ0G6Ky{ezy*Aq-qJ%G+5=sbwd!z7}k@5RJ+?XLFvXfFk|@8jsagU%D^ z{0E&U(RtRgV3f(|JY)FNgc5^uB3BI^cJSBGc^;it(3yhHi|D+-(Lf`BTZX?m80+o6jlWeEyw|Zqo(vraoe&)toj~sQjz)ICSw9!fRYMkig=oBVcr#B9FN;v>K6*`U4x&J%bCz^p5SEH~3X=*aB zVn!5p1eC&a-Y;fBF$aoSC2cXAR^P?!@>GzYED&|4~5&3ZwcSwtQMm#Y; z=MF*?9RHU>L1cuzati3{d-N#7hoa~wj$+s#MSH6~#sr1SP#WG-8I4W#eC-RC^kT`;fQgkiIZb8XTxG+6x*QK1jQC8 zDAi`_Cae(UzPZ@uOHpizVrvvzY573wf?2TP)Q;Pt*a5|MD7IHFG$&{=ss?sMv6I$} z6py=vYMotB9E@UD6nmlA4aImAyQA2H%=|fmVkwsiDE3q-XaUlG;Ko6*H;VmH?1N%I z6#J5m%SH+&$(7~-C=Qe)_pp8rQtn5*Xi^=5f;B)fjzB>n53^z(s%AS6>K^qr*z{&6t?**J-i6T zWhl7+YkMxmBx}}73|=aqm0Fo?bS;X@QCx-M3KUn8sD@^%(W_isje>_iS0=6^i2phi zH=?*6#SP-24|DE6G-4O53L4&O_YoVeq*jw++=Aj(6c3=_K_K#Q2|#!Uin~$ViQ+E4 z4mnWfle`lJ7aa_}7sY+-s@C6mDCi!wETL58K@<#iRUE9>rrQ z9`9RX`o0exHg5kWGZR7aB#Ng{Jl(hWW+*22J#um165~*?dY>iUzwwr&n~FY<@_ZCi zQ0{@^1(ZWjyolmU6fdF3P`r%7Mez!XH&DE)@v5+$&Ej>LvMi=3Z=#?su?TOgElB4h z-D_E>qgS?$I9VsLAwG)0kPtwSdcGvoOf#V08E<*($_!)Z>;pBwx_>jJ%g zg_5;RHTxCC*C;6MH)2-Mw1h=${8(GS3r!Kw3gvBp`2L^e7ZwrIV;N9P|kyL zc9e4&55NCqnsfGMpAubulymE{GK-6HUX=40+x%Th->o~N>_fQ;%AqJ%M%j;YF_gnl zE`)M8$^}r4F#eG!N2v*zjzN^8yD6YtP)N5L%Y{)cGDu%k?In>GN4c~iOBh_z;8MfN z-was><#H&OHGB-pv3)JeVsv3*F(91g^okHzCs5z9OZ^6HyY&UC%_6qxhYCr^@egY zl$#HRZZR0T6-w6R)+o0(o^4QWJIJ%05bFpi%>`8Mh;pZH#6g~2h@g}U2&K82%iVjo z+m+){9))rO$^$HPPn3HN#@ZXD?teqMufhFL?yu=m6W@#SS(OK(JQU?Y7J9HjZpf0~ zyf;Gr$=AUKJxn8cdAPwN3?8YZ#C9~wGf*Cb@+6eU8s#{H$1A!RPB3_)UVG|c&a_hg zAC#w}JWWBuPakABbC7Zt%CjwqTfkkOb5Wiryq6TEs!(2t(nmQF<&7vWLU}dEPm~v< zoP_cUl$W5q9Ob35oXg8l(wU!<&`LDbLuEm-^zBhxV**^u{#0E>rh^= zLm(WMc{;beL7$u`3qJRuya^=_b`rlC+9>bg zlQ~tPw8K9#W0>-@C|^bS9LkqaK9BMR4O!(B&0q9alrQR-898Y&^6XjpvcXqGT#b75 z8p^j(zK-%ulsx=H8F<$B-?Ht?x4N$=+36gVcK?rsqI6Mu@=lYV4-xvc%D`Y~FhUul zOi?B%+oLZ0bojEfkJ!&X>?$*q1{0zW0=^ za+)RbD|ls5`5UU4QT~oGAu3xIDQQ6N`Y0gnR-ndmwhiV2& zg=%_@;aqoAGoqSFo?ElB)YjE3sDNr#RCA!34b|)#&AL_%zjLhSL^YRO2fXNGy?wd# zKs7h2`B2S+YF-t@tPY(3spdyDM9GrKhaZzxLs2b(svp&8RKrk>L^T}Mh*4)vk-l{e zm1-2K1$ypZy1aOATeTpnMNKgN0-lL1jLOD;)-Cn9S`5|V!{6dX?z-4bHhi@ts^w5E zg=$$;OQTvw!n3E-QMN40I|kKQH42$&^RrQ^ zgKA?`TcX+o)n=$R6qWw$Q=6k^ddp*^3a>RS$CupxPJJj;QuTwG*n{QSFRsS5(^n6>~4*ZaU4v|KC-6Sjyv3O;Ck!wP?nt5%)s1 zH>!OU(MqTLOsf4*vBvgCbqJ~hP#vfs(GNm(u+|z~PD4;}eblm~4mEffsv}Tw@E38& zsTBA~R7Z93LCP_x&Ovo7sxwd>hw5Zh$D=yYL_L8~ES-~73bvH$@AM!Z1G?3>j@P3z{VKy@*yNyNKfDp^vO zqPmQTnwTbc^)LS63)SVKNSCg}M=ztg3hz>HuST;ps%ua`i0WEY_oBKE)vc(mC#Nyr zfa*q6H>0AJ-lg0mUG3rBEr1l2J_IoS+fm&yi1P>;v%3q`-Kb>Y+hk;(y6QgEhoZV4 zRf6gPRL`TL0FR;4_g_|5`UDKsBd8t~g>m)zII5>mJt2x-?Gu0=KG`Twqk6`WXHoI| zr&gwYBX%0H`vg=|P&uexK=lTy7g4=pY%dvnSv8{sUq$sgs@I6iM>_afs|CG@>Mc}n z>+?o&{>Na8kk63KsfVsnJX8THzene}&?+=~WH1)Je`mwnsJBLyq8@@OL-jqX9MuP? zI;d(bTdD$8iHf%XrG2YDFK37PJ*Rq52Kg&!~RY@~z@2p#Mr>dZ#$=KrauN3G(Zo}m}Fo=F1NGozk`^lmYYGMll@ZjeJXqs)mKsOJ(J zABtgKKGgG|o*(tRsORfaBmnDBaxR3r5A{l@hoW8#bwBFSsE45*iF!Ed5wSa-{ zfO?GDv>r<$>gAXb>gAOk|B}SH={lR^<}7!M120lQPcRJ zLps2R7HNF~>Qhm(Aa?mlZMT4meu~T|KD6TM(@>v*`gEP;Fp5Pyb1;RoQBOjB4(f@h z&qd9OIuG^vJx=D*OXR{HMZ#QU$?8;qR)X~dX7?JIY?K=mw#_ z3bk&jMSV5uYeeM3i>K=AP~T|u>+SW1QImRKNud5WY7YJjX&oV@y#+P-Z|(cnW2kRK zeHZH6CB~3(sPE7*u=>uf3?ho>9@O`vzSk1JPm??8OieO8Fqr#8sI>u#`eD?MSTj9} z`f1dU8UDCIyXj7<`=q@-g?ciP-h5g=gZeep&!T=2^>d<&lL|1!kQWGt?5tIry}o4d zWz?@2!oxrR?YsVDow2E3M;)Sm1GV{|>o-x$|7L7&583D6L-wUqEz~x}*A8l)|M_p< z$x|DHZ!i#r4c&_sp^j18&qnJ6b(^{9*_qufGk$$bJ7oW^UHaQGbY<`@jE`8<3D@s?^U;0ZC6jL;WS{ z&ryHTJ4L`Df?sgdU!mqyxU!<~bOCp4>u*s1jQU&DKcM~&_4k^>Xoj&+8m@mt{gWuN z%v+YlFQ|V<{VVF<^kE3u$lM#X{!kn7;DOrrFEmr3nG?;_Xl6q*4VoFzOshj~bbB__ z8Jr%CE^gJsL*1g!WN>DKvlyJU8)W$G2In9gw)9$P=0Y0@D~gvW8ZnGQ`qn&Di9p&2GErHVEq(2UgTj5VXmPth!(%5O%a zSq#mBXcq1}jHOtJP--uH5i~Xh@Rg0#ERJRgDSR(;!W@j|ZquChE3TSknCz=(tcxhHbvof01(5!-HRqZnHnkIhA%~`Tp9nBg; zHcR?0I6;-qF_3lMuuj%SqYYm)>!9I1PCQ2;?<<;dXf`lpeGz&8ZL=YojZ8PVVX=fx z0K(XYqmyX1FziXr%@_2+o9P3jcjA7 z3SHvOj%ar3YLTjxW<#?Jnq$!Hisn!>yP?@jlR~pQnmy3$iDo>S3G~2^IPU=Y;h&;o zWDa?io4wH-fMy>w`=i+x&3=-f3dxL2>ogoS2ckIy%|U1m)?BXx3AzOu4a@RRX(`uJ zYLPvr{?bFc7t~iS`FVdY~<;Q337QfOdv}f|MU#n6^&`J@ zrh}&FHjO+C8hI)-188bAjjEjr%mGA={tgEV_+6EAE5ah&4*|{LGuxs zj}?nfyyr*rrg1c%qWMfEqA^Ji5`X{HP+QpUU!wU+TG8D*Q?-1J<{P$M|F&CE^U-{Z z<~s~bi$*_xLGuHeKhgY%=2tX78Rcif5d$lt`K5oeLx>ykH#EN+@&{p;jz+(L!N636 zhD>d6nqKI@boM&E!5Ih*nGpjs4dOG8nxwxYVPIAa%#VTDFfb1WX2-zX7?=YCb75f4 zzxCPJpolCodl}0$FfRt?69aoaYldU*zz__Kz(5}c^tF`A%V~F@9|OZMFkIJna{%PC zdVztFio$g0Wg1uj1G`{gGzM13z=9Z91_KLWU~vpAjDf{4u!!6;RH}hRr5jXE&NxG_ z#lR96SQ-OMVqht06lLJE=7)VZOslpY;P}4^2F74u1q_VE!1Bxs1IsDLu7+?-X*_qj$-Lc)^ zcB^W3U-I8`=G3XKuCA`G?r9e>a%EjA(o9RSGzprn^^u2}gi|PzSjn==P%0y-Y(!-% zDjQQ7Mr9K!o5?k6R5n#M2`(F1E1Q=%l`W`jskjNDGF&5-Rn*GXRCMQ$I^~nUpt22> z(NwmjvOSei5`@Zj8qj1?*@4Q=RCc7YQ%NcHr((yr#DJ>pu2goTGG@@R2P%)mC}mG7 zgvuUNj-#?CmBXm)Mdbi0dsEq0X7Z_wrDFI02)|_?mHnuUqq4t^jBeGoTQxb5%E59` zCzXRlEEU-TD&r=hA0ij1QaMzzW57@LzgMJZB6LZPT>fLX*;kIDa*Pp=R*Ks2SgDt^ zX;V(g&9e=eK;;A~r%;(lMOs+#I#Kzh{;Zs=X^|KgKZDAtR0N(zMOuNowGc|3BLkVr znN-fw;vv0I(OC=IIaDsCaxRsNshmgULMrD|xj;opqtj<;y@IBqZELBC#dwLvC^M)P zDG!sWTux<@QlzRGZRd+CS5Uc%iYhB{Ntp!Q=r%-v=YQGz_9@CQcD3!R zQF&UcDUE2F7R%0$_N-2rSDvHtB9-Tm5-@>RPs=fE^f*2 ziLrfJ(k<-gM)|_vmow16rt&S7Z#opcjJk~Ldn!L!K)VaN@}ttVuvMr8R2(aBwi;XU z4EGII1!@hTr7@(U^FPujEFqOTju}!Xm6Xb_7NblDNn&#{-E-YG5l%pFN1#zn<4+=%z`tM;?B$h^((!c zS#f6jzmz#}=ERxDnCHToyX4du9eV<}6L5Z|MN{tGD;&iXhT>JQ>qP=XBf^FT6@S3BEO+yt$^$^cecWj=1-5GmklV+NSy6(w!s-CDZ<%S z|7GbUm%Srrdz>AVS-P510;TWm?1Zx)&dxY{;Ov4k24^(RuKL%DL4;E^mt5Ojogzx6 z%{2*o;*7=F3ukYw8H6YUKvjDm9Nqt;JA0)%RmwP=LvZ%TISA(foCCFkBxV`%p~)GK zbMT;X9O88Z}I%&Q~;T(f=wBUm;pRPpFN@F<=S2FZ? zTq$M~a4y9;0q0_zi8vSOR%+)&oRe_Q!Z{h|G@Mg#PSw$(r0AA6EH6&S(WNmRWjJSw z{2sYGONMgJ**NFpoP%?&8ecV(k*QYohF^elp)yM?Cs3q`1ZkC=OSC^Jm9XMcx}L+i z9A}dHN|)Z5jB^jp6*$+KQ}n+IM<40HxmxWhY_bi>xmIymadWQ6xeezAoSSiO#JNd| znJzoZr@3GJ zcmeG@Pe!?EZ)H zkJCgwgY&F})ocBvs4L}poEpvxI6vaNi1P-{OOk4wmvQvjN1RvG$kG-&Qvb_Z!0l=Z zvG69sAjSS&oVRe^!Fk(;QMwaeyM5 zBj*#GZ*e}w`5fo7a$A@7x1BF=zQ*|y=PM=3k0Wz0%KwcyZ`IRJTI9aN`5s3uf3Vz_ zQmuCW3CF5>r-I{Xfzd?SC_oCEW|ogr#ffkNoKP((eGIX%v?*eFX&;?Bj&(wu7^i`g znGu=>6RT8Z6kxTqlbg*89G&^soE4}Rn2sxQe#ZF|=NFvcael?I{r~0quRI2=RgLTd z)pNBtf8qRt^S3&svN9f=e@iFs%(!#n&VoA|?yS8$xU=KVp-$PEW&v^M!krg)Zrpi# z<&+5Sd|hM#HHf<)?ozl5;Vy=YQ5H71h(S63BF*Smxbg(Ntwy`;oANG zig)LK+(Ec@?Z3OYrLy?Um~ zxJk4fi+e8aKDfu??u$DfcR$<%v`D+-EIGOb$c%QNwxRApdIoC99(sF;doZr3cL?rb zxQA-%Ae>V9yNCBonW)Yw6LF8kJxZNwx1({75xVLr-Dypl)c>dBO8tK_?gZQuaZkXV zs8yNSR%eXelhh7Ek-1jQyHjva#XU{$bu>1emvhg+JyUBNnPYU%!nK)yWj<%{%3H}L zMY=b@JrDN^-1BiS!@U6aA{}hHdIUgta4*KaL?Y7rJ=9im!Ok#~e7QwB$)HXCh+?wr z>k7{exL4s`s}nWu)wtK_0kQQi*C|Bzx+=S?rqwKac{?c9QO{~`*H8oO2fTNn_cPWxOd~;W5~S*?^C+eJnAKE4@rr_wN^k& z?-X1q*gF4%E0@2?)To@KmMy{Jd`!!p1e7|>eZsOpZUK^ib}H^uxG&)9;xFzr+~;wh z5!{^5;yzd6<*#i)^oj}deW@h&KWI4aE4Z)XzJ>c5?&~@{mpqad$9==#o9bM6S=_gA zrDVQ?`>s-?d+ENX%*K!Vff76Pk8t&*rzSz}X;-yB!F6yy#r+!hGgIJmgVz6dJN

                                    ^76S~B*f0&}t&>FT8VvbHLVGm@@Fx}jOuCS8Yg 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

                                    ~?#C{L^{gi*u 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%0DQloruJ|A(75>QxmE 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